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ではベクトルの各要素が、それぞれ特定の単語に対応しているので、横軸にその単語を並べています。

グラフ化したBag of Words

Figure 1. グラフ化したBag of Words

これを見てわかるとおり、 「私はラーメンが好きです」のBoWは、「富士山は日本一高い山です」のBoWよりも「私は私のことが好きなあなたが好きです」のBoWに似ています(値がゼロでない次元の一致が多いということです)。 これは、「私は私のことが好きなあなたが好きです」という⽂と、「私はラーメンが好きです」という⽂が、ともに“私”という主語の嗜好を表した⽂である、という類似性を捉えているといえます。

BoWは単語の出現頻度をベクトル化したものなので、この例でいえば「私」や「好き」といった単語に対応する値がプラスになり、似るのだ、というのは至極当然の結果のように思えます。確かにそのとおりなのですが、この例が⽰しているのは、もう少し深⻑な事柄です。それは、「これら2つの⽂章はいずれも“私”という主語の嗜好を表している」という⽂意の類似性を、「私」や「好き」といった単語の出現頻度の類似性で、ある程度捉えられているということです。すなわち、これら2つの文章はいずれも「“私”という主語の嗜好を表している」という点で類似していると考えられるのです。

ここでは、プログラムで扱いやすい固定長の配列を得るために、元の日本語の文から単語の出現頻度という情報のみを抜き出して特徴ベクトルにしましたが、文意を捉えるためには(ある程度は)それで十分だというのが、この手法の面白いところです。

また、単語の出現頻度のみに注目するということは、他の情報(語順情報など)は無視しているということですが、これはより積極的なメリットになりえます。 例えば、「明日友達と遊園地に遊びに行く」と「友達と遊園地に明日遊びに行く」という例を考えてみましょう。この2文は語順は異なりますが同じ意味です。 そして、BoWを求めると、同じ特徴ベクトルが得られるのがわかるでしょう。 語順の違いを無視したことにより、文意の同一性を特徴ベクトルの同一性に反映できたのです。 これは語順の違いを無視するBoWの長所を端的に示す例です。 特に日本語のような語順が自由な言語では、語順を無視することで文意の同一性を捉えることができるケースはよくあります。

ただし、語順情報が重要な役割を持つ場合もあり、そのような時には上記のようなBoWの性質はデメリットになりえます。 有名な例に「犬が人を噛んだ」と「人が犬を噛んだ」というのがあります[1]。この2つの文はまったく違うことを表していて、大半のケースでは区別するのが望ましいと思われますが、BoWではこの2つを区別できません。どちらの文からBoWを作っても、同じベクトルができてしまいます。 この例では、単語の出現順序の違いから2文の意味が大きく異なっていますが、BoWはまさに単語の出現順序情報を捨ててしまい、この2文を区別できなくなってしまっています。

最初に述べたとおりBoWは基本的な手法ですので、利点に加え、このような限界もあります。そこで、BoWを改良する方法の1つとして、「明日友達と遊園地に遊びに行く」と「友達と遊園地に明日遊びに行く」が同じベクトルに変換され、「犬が人を噛んだ」と「人が犬を噛んだ」は違うベクトルに変換されるような手法を考えてみましょう。わかち書きの結果だけでなく、形態素解析で得られる品詞情報「助詞」を使うのが1つの手ですね。

Tip
Google検索における特徴抽出

Googleで「人が犬を噛んだ」と「犬が人を噛んだ」を検索してみました[2]。 4、5番目が違いますが、ほぼ同じ結果になりました。

人が犬を噛んだ

人が犬を噛んだ



犬が人を噛んだ

犬が人を噛んだ



まさかGoogleがBoWだけに頼っているとは思えませんが、単語をわかち書きした上で、ある程度は順序情報を無視するような処理が行われていると予想できます(Googleの検索結果は変化しますし、様々な要因を考慮してパーソナライズされているため、読者が試しても同じ結果になるとは限らないことに注意してください)。

最後に、Bag of Wordsという名前の由来に触れておきます。 Bag of Wordsは単語の出現順序を無視して出現回数だけを数えるものでした。 これが、文を単語(Word)に分解して、袋(Bag)にバラバラに放り込み、個数を数えるというイメージにつながっています。 「順序を無視して個数だけ数える」という特徴から連想されるネーミングになっています。

"Bag" of Words

Figure 2. "Bag" of Words

未知語

辞書生成と特徴ベクトルの算出(特徴抽出)は分かれているため、辞書の作成に使う文集合と、特徴抽出の対象となる文集合が同じである必要はありません。

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 を表現するのに十分な種類の単語が含まれるよう注意しなければなりません。


1. 正確にはこの例文はBoWの説明のための例文として有名なわけではありません。気になる方は“Man bites dog”で検索してみましょう。
2. 2019-01-31時点の結果。Google Chromeのシークレットウィンドウで閲覧。