Bag of Words詳解
本稿は、拙著「15Stepで踏破 自然言語処理アプリケーション開発入門」の第2章 Step04の内容の一部を、出版社の許可を得て掲載しています。
■1章 演習に入るまえの予備知識
1 序論・自然言語処理と機械学習
2 本書の執筆・開発環境
3 機械学習のためのPythonの基礎
4 数値計算ライブラリNumPy
5 本書で利用するその他の主要ライブラリ■2章 基礎を押さえる7ステップ
Step 01 対話エージェントを作ってみる
Step 02 前処理
Step 03 形態素解析とわかち書き
Step 04 特徴抽出
Step 05 特徴量変換
Step 06 識別器
Step 07 評価■3章 ニューラルネットワークの6ステップ
Step 08 ニューラルネットワーク入門
Step 09 ニューラルネットワークによる識別器
Step 10 ニューラルネットワークの詳細と改善
Step 11 Word Embeddings
Step 12 Convolutional Neural Networks
Step 13 Recurrent Neural Networks■4章 2ステップの実践知識
Step 14 ハイパーパラメータ探索
Step 15 データ収集
Bag of Words再訪
文字列をベクトル(固定長の list
またはnumpy配列)に変換する「特徴抽出」の一手法として、 Text classificationで、簡単な対話エージェントを作ってみるチュートリアルではBag of Wordsを紹介しました。 Bag of Wordsは、それぞれの文における単語の出現回数をカウントして、それを特徴量とするというシンプルな手法でした。 実装や使い方については上記の記事の中で解説したので、ここではBag of Wordsの性質を詳しく見てみましょう。
Bag of Wordsの性質
Bag of Wordsがどういった性質の特徴ベクトルなのかを考えてみます。
まず、[feature-vectorization]で述べたとおり、Bag of Wordsは元となった文の特徴を表したベクトルになっています。 例えば、次の3つの例文があるとします。
-
私は私のことが好きなあなたが好きです
-
私はラーメンが好きです
-
富士山は日本一高い山です
これらの例文から得られたBoWの各要素の値を棒グラフで表すと、グラフ化したBag of Wordsになります。 BoWではベクトルの各要素が、それぞれ特定の単語に対応しているので、横軸にその単語を並べています。
これを見てわかるとおり、 「私はラーメンが好きです」のBoWは、「富士山は日本一高い山です」のBoWよりも「私は私のことが好きなあなたが好きです」のBoWに似ています(値がゼロでない次元の一致が多いということです)。 これは、「私は私のことが好きなあなたが好きです」という⽂と、「私はラーメンが好きです」という⽂が、ともに“私”という主語の嗜好を表した⽂である、という類似性を捉えているといえます。
BoWは単語の出現頻度をベクトル化したものなので、この例でいえば「私」や「好き」といった単語に対応する値がプラスになり、似るのだ、というのは至極当然の結果のように思えます。確かにそのとおりなのですが、この例が⽰しているのは、もう少し深⻑な事柄です。それは、「これら2つの⽂章はいずれも“私”という主語の嗜好を表している」という⽂意の類似性を、「私」や「好き」といった単語の出現頻度の類似性で、ある程度捉えられているということです。すなわち、これら2つの文章はいずれも「“私”という主語の嗜好を表している」という点で類似していると考えられるのです。
ここでは、プログラムで扱いやすい固定長の配列を得るために、元の日本語の文から単語の出現頻度という情報のみを抜き出して特徴ベクトルにしましたが、文意を捉えるためには(ある程度は)それで十分だというのが、この手法の面白いところです。
また、単語の出現頻度のみに注目するということは、他の情報(語順情報など)は無視しているということですが、これはより積極的なメリットになりえます。 例えば、「明日友達と遊園地に遊びに行く」と「友達と遊園地に明日遊びに行く」という例を考えてみましょう。この2文は語順は異なりますが同じ意味です。 そして、BoWを求めると、同じ特徴ベクトルが得られるのがわかるでしょう。 語順の違いを無視したことにより、文意の同一性を特徴ベクトルの同一性に反映できたのです。 これは語順の違いを無視するBoWの長所を端的に示す例です。 特に日本語のような語順が自由な言語では、語順を無視することで文意の同一性を捉えることができるケースはよくあります。
ただし、語順情報が重要な役割を持つ場合もあり、そのような時には上記のようなBoWの性質はデメリットになりえます。 有名な例に「犬が人を噛んだ」と「人が犬を噛んだ」というのがあります[1]。この2つの文はまったく違うことを表していて、大半のケースでは区別するのが望ましいと思われますが、BoWではこの2つを区別できません。どちらの文からBoWを作っても、同じベクトルができてしまいます。 この例では、単語の出現順序の違いから2文の意味が大きく異なっていますが、BoWはまさに単語の出現順序情報を捨ててしまい、この2文を区別できなくなってしまっています。
最初に述べたとおりBoWは基本的な手法ですので、利点に加え、このような限界もあります。そこで、BoWを改良する方法の1つとして、「明日友達と遊園地に遊びに行く」と「友達と遊園地に明日遊びに行く」が同じベクトルに変換され、「犬が人を噛んだ」と「人が犬を噛んだ」は違うベクトルに変換されるような手法を考えてみましょう。わかち書きの結果だけでなく、形態素解析で得られる品詞情報「助詞」を使うのが1つの手ですね。
Tip
|
Google検索における特徴抽出
人が犬を噛んだ
犬が人を噛んだ
|
最後に、Bag of Wordsという名前の由来に触れておきます。 Bag of Wordsは単語の出現順序を無視して出現回数だけを数えるものでした。 これが、文を単語(Word)に分解して、袋(Bag)にバラバラに放り込み、個数を数えるというイメージにつながっています。 「順序を無視して個数だけ数える」という特徴から連想されるネーミングになっています。
未知語
辞書生成と特徴ベクトルの算出(特徴抽出)は分かれているため、辞書の作成に使う文集合と、特徴抽出の対象となる文集合が同じである必要はありません。
texts_A = [
...
]
vectorizer.fit(texts_A)
texts_B = [
...
]
bow = vectorizer.transform(texts_B)
この場合、辞書の生成はあくまでも、.fit()
に渡された texts_A
に含まれる文に基づいて行われます。 その後 texts_B
を対象に .transform()
で特徴抽出を行うわけですが、 texts_A
に登場しなかった単語は辞書にも含まれないので、BoW化の時には無視されます。
3章の例の範囲では、このようにBoW化の対象とは異なる文集合を使って辞書を作成するメリットはあまりありません。しかし例えば、texts_B
にあえて無視したい単語(無意味な絵文字や顔文字、無意味な単語など)がある場合などには有効でしょう。
逆に、BoW化の対象となる文集合と、BoW辞書の構築に用いる文集合を分ける場合は、辞書の生成に使う文集合 texts_A
のほうに texts_B
を表現するのに十分な種類の単語が含まれるよう注意しなければなりません。