naive_bayes (NAIVE_BAYES) Dictionary は、多項 Naive Bayes モデルを使ってテキストを分類します。これはテキスト向けの標準的なイベントモデルで、入力内のN-gramが各クラスにどれだけ出現するかに基づいて、各クラスをスコアリングします。クラスごとのn-gram の出現回数のテーブルを与えると、読み込み時に一度だけそれをコンパイルしてモデルを作成し、その後は渡された任意のテキストの分類に使用します。
感情分析、トピックやスパムのラベル付け、言語や文字体系の判定といった、高速で軽量なテキスト分類に適しています。
Dictionary は、次の3つの関数のいずれかでクエリできます。
naiveBayesClassifierは予測されたクラスIDを返します。naiveBayesClassifierWithProbは予測されたクラスとその確率を返します。naiveBayesClassifierWithAllProbsは、すべてのクラスとその確率を返します。
dictGet でも分類できます (注意を参照) 。もう1つの関数 naiveBayesNgrams は分類を行いません。代わりに、Dictionary と同じ方法でテキストをN-gramに分割するため、生テキストから学習データを作成できます (生テキストから学習データを構築する を参照) 。
クイックスタート
n = 1) モデルを構築します。
1. クラスごとの n-gram の出現回数を格納したソーステーブルを作成します。
1) および負例 (0) クラスで出現する頻度:
NAIVE_BAYES レイアウトの Dictionary を作成します:
PRIMARY KEY ngram は ngram カラムをキーにしますが、NAIVE_BAYES Dictionary では、この「キー」は分類対象として渡すテキストを指し、参照のために保存されている値ではありません (Dictionary structure を参照) 。LAYOUT はモデルを設定します。class_attribute 'class_id' は class_id をクラスラベルとして指定し (つまり、もう一方の attribute である count はクラスごとの出現回数です) 、n 1 はユニグラムを使用し、mode 'token' はテキストを空白区切りの単語に分割します (Layout parameters を参照) 。
4. 分類 — naiveBayesClassifier はクラス ID を返します:
1 は正のクラスに対応します。
0 は負のクラスに対応します。
dictGet でも同じ結果が得られます。
0.64 でクラス 0 (負) です。
naiveBayesClassifierWithAllProbs は、可能性が最も高いクラスから最も低いクラスまで、確率の合計が 1.0 になる形ですべてのクラスを返します。ここでは、負のクラスが 0.64、正のクラスが 0.36 です。
仕組み
(n-gram, class, count) という観測値です。Dictionary がロードされると、これらの行はモデルに一度だけ取り込まれます。重複する (n-gram, class) の行は合算され、count = 0 の行は無視されます。
分類 (クエリ時) 。 文字列を分類する際、モデルは次のように動作します。
modeとnに従って文字列を n-gram に分割します (トークン化モードを参照) 。- クラス事前確率と、そのクラスで入力中の n-gram がどれだけ出現していたかを組み合わせて、各クラスのスコアを計算します。
- スコアに基づいてクラスを順位付けします。最も高いスコアのクラスが
naiveBayesClassifierの予測結果として返されます。naiveBayesClassifierWithProbとnaiveBayesClassifierWithAllProbsは、そのクラス、またはすべてのクラスについての確率も返します。
alpha です。平滑化は、学習時にある 1 つの n-gram がそのクラスに現れなかったというだけで、モデルがそのクラスに 0 のスコアを与えてしまうのを防ぎます。alpha が小さいほど、モデルは学習データにより強く依存するため、あるクラスが他より大幅に高いスコアを得ることがありますが、学習データが少ない場合や偏っている場合には、モデルが敏感になりすぎることもあります。alpha が大きいほど、n-gram の出現回数の影響は小さくなり、クラス間のスコアはより近くなります。alpha が非常に大きい場合、n-gram の情報はほとんど効かなくなり、スコアは主にクラス事前確率 (次に説明します) によって決まります。
2 つ目はクラス事前確率です。これは、モデルがテキストを見る前に、各クラスがどれくらい起こりやすいとみなすかを表します。どの n-gram も考慮される前に各クラスに与えられる初期スコアとして機能するため、事前確率が高いクラスほど予測されやすくなります。これをどう設定するかは priors_mode に依存します。デフォルトでは (proportional)、学習データ内の n-gram 総数が多いクラスほど高い初期スコアを持ちます。uniform ではすべてのクラスが同じ条件で始まるため、n-gram だけが判定を左右します。explicit では、各クラスの初期値を自分で設定します。事前確率モードを参照してください。
学習データ内のどこにも一度も現れなかった n-gram は無視されます。これはモデルの語彙に含まれていないため、どのクラスにも有利にも不利にもなりません。
このアルゴリズムは、テキスト分類のための多項ナイーブベイズモデルに従います。詳しくは Manning, Raghavan & Schütze, Introduction to Information Retrieval, ch. 13 (Text Classification and Naive Bayes) を参照してください。
Dictionary の構造
NAIVE_BAYES Dictionary は固定の構造を持ちます。
PRIMARY KEYは単一のStringカラム、つまり n-gram です。クエリ時、この”key”は保存済みのルックアップキーではなく、分類対象として渡すテキストそのものを指します。- これに加えて、符号なし整数の属性をちょうど 2 つ 宣言します。クラスラベルと出現回数です。クラス ID は内部的に常に
UInt32を使用するため、属性をUInt64として宣言していても、クラスラベルはUInt32(最大4294967295) に収まっている必要があります。これより大きい値は、Dictionary の作成時ではなくロード時に拒否されます。同じことは宣言した型にも当てはまります。元データのクラス ID または回数が宣言した属性型に収まらない場合、暗黙に切り捨てられるのではなく、ロードに失敗します。 class_attributeレイアウトパラメータは、どの属性がクラスラベルかを指定します。もう一方は自動的に回数として扱われます。2 つの属性は、どちらの順序で宣言してもかまいません。
(n-gram, class) につき 1 行で、そのクラスにその n-gram が何回出現したかを表します。これらの回数は、コーパスをトークン化して結果をグループ化することで生成します。独自の学習パイプラインで行うことも、ClickHouse で生テキストから学習データを構築することもできます (生のテキストから学習データを構築する を参照) 。Dictionary はそれらを取り込むだけです。
モデルの更新。 モデルはテーブルをバックエンドに持つ Dictionary であるため、再学習はテーブルを更新して再ロードすることで行います。
レイアウトパラメータ
Dictionary は、
CREATE DICTIONARY DDL (上記のクイックスタートと同様) または XML 設定ファイルで定義できます。そのファイルの配置場所については、Dictionary layoutsを参照してください。以下の例では、すべてのレイアウトオプションを設定して全体を確認できるようにしています。必須なのは class_attribute、n、mode のみで、その他のデフォルト値は上の表に示しています。設定ファイルでは、事前確率は prior 要素を繰り返して記述します (以下の例のとおり、クラスごとに 1 つ) 。また、byte と codepoint のパディングトークンは数値で記述します (config では raw bytes を保持できません) 。さらに、token リテラルは必要に応じて XML エスケープされるため、<s> は <s> になります。
- DDL
- 設定ファイル
トークン化モード
mode は「トークン」を何とみなすかを決めるため、N-gram の形もそれによって決まります。元の N-gram は、同じ mode と n で生成されている必要があります。
byte— 各トークンは 1 バイトです。UTF-8 は前提としません。n = 2の場合、'abc'からはバイト bi-gram の'ab'、'bc'が生成されます。適している用途 は、任意のバイト列に対する言語やエンコーディングの検出、および文字より細かい単位の特徴が重要なあらゆるデータです。通常はn >= 2と組み合わせて使います。codepoint— 各トークンは 1 つの Unicode コードポイントです。入力は UTF-8 として解釈されます。n = 1の場合、'café'からはコードポイント'c'、'a'、'f'、'é'が生成されます。適している用途 は、文字体系や言語の検出、および空白による単語境界が当てにならない短いテキストや CJK テキストです。 (元の N-gram は有効な UTF-8 である必要があります。クエリ入力は寛容にデコードされます。詳しくは Notes を参照してください。)token— 各トークンは、ASCII whitespace (space、tab、newline、carriage return、form feed、vertical tab。連続する場合は 1 つの separator として扱われます) で区切られた単語です。U+00A0(no-break space) やU+2003(em space) などの非 ASCII の Unicode whitespace は separator ではなく、トークン内にそのまま残ります。分割するのは whitespace だけで、小文字化や記号の除去は行われません。したがって、'Hello, World!'は'Hello,'と'World!'というトークンになり (カンマ、!、大文字はすべて保持されます) 、n = 2の場合は 1 つの bi-gram'Hello, World!'を形成します。適している用途 は、空白区切りの言語における単語レベルの分類 — 感情、topic、スパム、文の言語などです。
事前確率モード
priors_mode は、その設定方法を選びます。
-
proportional(デフォルト) — 各クラスの事前確率は、学習データ内でのそのクラスの合計 N-gram 数に比例します。つまり、そのクラスのcountカラムの合計であり、行数や学習文書数ではありません。そのため、より多く出現したクラスほど、初期状態で起こりやすいと見なされます。学習時のクラス比率 (合計 N-gram 数ベース) が、クエリ時に想定される出現頻度と一致している場合は、これを選択してください。指定するものはありません — 元のカウントから導出されます。 -
uniform— 最初はすべてのクラスが同じ確率になるため、どのクラスも有利にならず、予測は完全に入力の N-gram に基づいて決まります。クラスが均等である場合や、学習時の頻度がクエリ時に各クラスが現れる頻度を反映していない場合は、これを選択してください。指定するものはありません。 -
explicit—priors [(0, 0.6), (1, 0.4)]を使って事前確率を指定します。クラスごとに 1 つの(class, probability)ペアを与え、各確率は 0 より大きく 1 以下で、合計は1.0でなければなりません。実際のベース率が分かっていて、それが学習データと異なる場合は、これを選択してください。たとえば、学習セットは均等でも、本番トラフィックのうちスパムは 1% しかない、といった場合です。各クラスの実際に想定される比率から、これらを計算してください。
境界トークン (パディング)
n > 1 の場合にのみ意味を持ち、テキストの先頭と末尾でモデルがシグナルを利用できるようになるため、精度の向上に役立ちます。
有効な理由。 n > 1 の場合、テキスト中央のN-gramは左右両方のコンテキストを完全に持ちますが、最初と最後のトークンはそうではありません。境界トークンを追加すると、「テキストの開始」と「テキストの終了」を示すN-gramが生成され、モデルは位置に関連したパターンを学習できます。たとえば、メッセージの先頭に現れるときに特徴的な単語や、単語の末尾に典型的な文字などです。
必要な対応:
- 各側を個別に決定する。
start_tokenとend_tokenは独立しています。一方のみ、両方、またはどちらも設定しないことが可能です。空の値はその側がパディングされないことを意味します。 - 実際のデータと衝突しないレアな値を選択する。 たとえば、
byteには0x01/0xFF、codepointにはU+10FFFE/U+10FFFF、tokenには<s>/</s>などを使用します。 - 同じパディングでトレーニング用N-gramを生成する。 Dictionaryはクエリ入力にパディングを適用しますが、ソースには適用しないため、境界トークンはロードするN-gramにあらかじめ組み込まれている必要があります。一致を保証する最も簡単な方法は、
naiveBayesNgramsを使用してソースを構築し、layoutに指定するのと同じstart_token、end_token(およびnとmode) を渡すことです。これにより、Dictionaryがクエリ時に生成するパディング済みN-gramと完全に一致するN-gramが出力されます。
-
byte— バイト値を表す数値で、10進数または0x16進数で指定します ('1'と'0x01'は同じです) : -
codepoint— UTF-8コードポイントを表す数値で、10進数または0x16進数で指定します ('1114110'と'0x10FFFE'は同じです) : -
token— リテラルのトークン文字列:
生テキストから学習データを作成する
naiveBayesNgrams 関数を使って N-gram に分割します。レイアウトと同じ n、mode、start_token、end_token を指定すると、Dictionary が想定するものとまったく同じ N-gram が生成されるため、学習データはクエリ時にモデルが参照するデータと一致します。
(class_id, text) の行からなるテーブルがある場合、1 つの GROUP BY で (ngram, class_id, count) の元データを作成します:
training_data は、NAIVE_BAYES Dictionary の有効なソースになりました (ここでは token ユニグラム を使用しています。レイアウトに合わせて n と mode の引数を変更してください) 。この Dictionary はクエリ入力を与えられたとおりにそのままトークン化するため、学習テキストが小文字化されていてもクエリテキストがそうでない場合、両者の N-gram は一致せず、モデルの精度が低下します。
事前確率と文書数
proportional の事前確率 (デフォルト) は、各クラスの総 N-gram 数に基づいて重み付けされ、文書数には基づきません。古典的な文書頻度ベースの事前確率 (documents_in_class / total_documents) を使いたい場合は、生の docs テーブルから計算し、priors_mode 'explicit' を指定して渡してください。training_data から Dictionary を作成し、新しいレビューを分類します:
1 はポジティブ、0 はネガティブなので、どちらのレビューも正しく分類されています。
その他の例
n = 2, mode 'byte'; class 0 = 文字 a–d からなる文字列、class 1 = 文字 x–z) :
n = 1、mode 'codepoint'、class 0 = ラテン文字、1 = キリル文字) :
store_source を使って学習データを読み出します:
n = 2、mode 'codepoint'、class 0 = 英語、1 = スペイン語) 。学習用の N-gram は naiveBayesNgrams を使って生の単語から構築され、関数とレイアウトの両方に渡される境界トークンによって、モデルは各単語の先頭と末尾の文字を利用できます:
注記
- 計算型 Dictionary のセマンティクス。 これは 計算型 の Dictionary です。
dictGet(dict, '<class_attribute>', text)はtextを分類します (キーは保存済みのキーではなく、分類対象の入力です) 。count 属性にはクエリできず、dictHasは常に1を返します。 - ロード時のソース検証。 すべてのソース N-gram は、設定された
nとmodeに一致している必要があります (codepointmode では、有効な UTF-8 であることも必要です) 。一致しない場合、ロードは失敗します。count が 0 の行は無視されるため (仕組み を参照) 、空のソース、または count が 0 の行しかないソースは学習対象がなく、ロードに失敗します。 - クエリ時のトークン化は寛容です。 ソース検証とは異なり、クエリ入力が拒否されることはありません。
codepointmode では、有効な UTF-8 でないバイト列は、クエリを失敗させるのではなく、可能な限りデコードされます。tokenmode では、単語の区切りとして扱われるのは ASCII whitespace のみです (U+00A0のような Unicode whitespace は token 内に残ります) 。不正な入力でも分類は行われますが、通常は事前確率に基づく分類になります。これは、その N-gram が学習済みのものと一致しないためです。