分散表現とWord2vec|実践的自然言語処理入門 #3

#1ではBoWと形態素解析の導入、#2では特徴語抽出とtf-idfについて取り扱いました。

#3ではここまで出てきた疎行列(Sparse matrix)の取り扱いにあたって分散表現とWord2vecについて取り扱いたいと思います。
以下目次になります。

1. 疎行列の取り扱いと局所表現・分散表現
2. Word2vecの仕組み
3. Word2vecの実装
4. まとめ


1. 疎行列の取り扱いと局所表現、分散表現

・背景(自然言語処理における疎行列の取り扱い問題)

自然言語処理を行う際にBoW的なアプローチ(生起頻度のカウントとtf-idf値での計算のどちらも含みます)を行うにあたって一番ネックになりうるのは疎行列(Sparse matrix)の問題です。

https://scikit-learn.org/0.16/modules/feature_extraction.html#sparsity
この疎行列の問題については上記のscikit-learnのUser Guideでも言及されています。こちらの解説がわかりやすいので、下記に該当箇所のキャプチャを貼ります。

f:id:lib-arts:20190125175531p:plain

1stパラグラフでは「ほとんどの文書においてはコーパス(辞書やBoWの列のことを示唆しています)のとても小さな単語集合を扱っているため、結果として得られるBoWの99%以上が0になる」と記されています。ちょっとこれだと抽象的なので、2ndパラグラフで具体的に記述されています。

2ndパラグラフでは「例として10,000の短文の文書群を考えた際に、100,000の単語によって構成される一方で一つの文書あたりで使用する重複のないユニークな単語の数は100~1,000ほどだ」と述べられています。これにより行列のサイズ自体はとても大きな一方で、ほとんどが0だということがわかります。直感的に言い換えるなら経済系の記事にスポーツ系の単語は出てきにくいし、教育系の記事に娯楽系の単語が出てきにくいということです。

3rdパラグラフでは、「この疎行列を扱うためにscipy.sparseのようなsparse representationを用いる」と述べられています。scipy.sparseは#2の記事でも出ていましたが、疎行列を考慮した上での実装だと理解しておくと良いです。行列それぞれの値を保持しては0ばかりになるため、A行B列が0以外のときのみ値を保持するという形の実装になってます。また、"sparse representation"を訳さなかったのは、representationという単語が表現という意味合いを持つ重要なキーワードだからです。 これはこの後の局所表現(local representation)と分散表現(distributed representation)の話に繋がっていきます。

 

・scipy.sparse以外の解決策の案(局所表現と分散表現)

scipy.sparseのようにA行B列の値が0以外のときのみ情報を持つという考え方ももちろん有用です。が、もっとエレガントな表現はないでしょうか。2013年頃に流行ったWord2vecがこちらの別の案を提示してくれるのですが詳細は2節で行うとして、ここでは局所表現と分散表現について考え方の導入を行います。
局所表現(local representation)はBoWのような表現のことです。単語が存在するという情報を局所的に持つので局所表現と呼びます。この表記の方法は自然言語を行列の形で取り扱い、教師あり学習アルゴリズムを適用できるという意味では良いのですが、若干効率が悪いです。
ここでちょっと違った視点から見てみようというのが分散表現(distributed representation)です。内容に入る前に知人を知人に紹介するケースを少し改めて考え直してみましょう。どのように紹介するのが良いでしょうか。大体の方は「年齢は〇〇歳くらい、男性、仕事は〇〇、趣味は〇〇、性格は〇〇、有名人の〇〇に似てる」のような情報を言うのではないかと思います。決して現在の日本国民の中で6342万2212番目に生まれた方ですとは言わないと思います。上記はちょっとした小噺ですが、これが分散表現と局所表現の違いです。局所表現はid表記のため、1hot-Vector(一つが1で他が残り0)の形式でしか値を持てませんが、分散表現は複数のパラメータで一つの単語や概念(さっきの例だと知人)を表します。この際、日本国民を全員表すには1hot-Vectorだと1億2,000万ほどの数字が必要なのですが、分散表現だとパラメータが0と1の2通りを取るとしても2^30 \fallingdotseq 10^9 > 1.2億となり、たった30個のパラメータで数多くの概念を表現することができます。

このように分散表現は局所表現より少ないパラメータで多くの概念を表現するポテンシャルを持ちます。
ここまででなんとなくの前提が分かりましたが、分散表現をどのようにして作り出せば良いでしょうか?ここで出てくるのがWord2vecで2節で仕組み、3節で実装について取りまとめます。


2. Word2vecの仕組み

Word2vecを考えるにあたっての背景である、局所表現と分散表現については1節でまとめました。
ここではWord2vecをどのように作っているかについてまとめられればと思います。厳密に書くとややこしいので、非常に簡易化して話を進めます。大体の仕組みのイメージがつけばそれで十分なので詳しい話はここではあえて行わないようにします。
ここでの考え方のベースになるのは言語モデルという考え方です。「ある単語の次に何が続くかを予測するモデル」とだけ一旦把握しておいてください。また、これをニューラルネットワークで予測する問題だと考えます。この時に要素(辞書の単語)が100,000ほどある1hot-Vectorを入れて200ほどの隠れ層を通して次の単語を予測する問題を考えたとします。そして大体の予測が行えるようになったとします。
この時、予測ができてよかったことに加えて200個ほどの隠れ層に着目します。これは圧縮と同様に捉えることができ、100,000の単語の情報が200個のパラメータに圧縮し、また復元する流れとも見れます。ということは、200個のパラメータは100,000の単語の意味的な情報を持っていると考えることができます。この200個のパラメータを分散表現として用いようというのがWord2vecです。2がtoの意味で『Word to Vector』というニュアンスを持っています。
ニューラルネットワークを用いないネガティブサンプリングなど様々あると言われています。

この辺は上記の「深層学習による自然言語処理」の3章の記述が非常にわかりやすかったので詳しく知りたい方は参考にすると良いかと思います。ちなみに今回の形式でニューラルネットワークをベースに説明した理由としては、翻訳などで大きな成果が出ているseq2seqのモデルなど含むRNN系の言語処理においては実は同様の処理を行なっていると考えることができるからです。詳細については長くなるので参考本を参照いただけたらと思います。


3. Word2vecの実装

from janome.tokenizer import Tokenizer
from gensim.models import word2vec

 

# 単語の分かち書き&スペースで区切る
text_space = ""
t = Tokenizer()
with codecs.open('text_file_name.txt', 'r', 'utf-8') as f:
    txt = f.read()
for token in t.tokenize(txt, stream=True):
    text_space += token.surface
    text_space += " "

 

# ファイル書き込み
with codecs.open('wakachigaki_file_name.txt', 'w', 'utf-8') as file:
    file.write(text_space)

 

# Word2vecのモデルの作成
sentences = word2vec.LineSentence('wakachigaki_file_name.txt')
model = word2vec.Word2Vec(sentences,
              sg=1,
              size=100,
              min_count=1,
              window=10,
              hs=1,
              negative=0)
model.save('model_name.model')

 

# モデルの読み込みと類義語の計算
model = word2vec.Word2Vec.load("model_name.model")
model.most_similar(positive="単語", topn=10)

上記のようにgensimとJanomeを用いたWord2vecの簡単な実装についてまとめておきました。読み込み元のファイルなどを変更するだけで様々なテキストについて実行することができます。(Windowsの方はファイル読み込みができない場合はcodecsなど使用するのが良さそうです)

 

参考としてWikipediaのデータを用いて、類義語を出力してみた結果についてまとめます。

◆ "スター・ウォーズ"
[('ルーカスフィルム', 0.7677389979362488), ('反乱者たち', 0.7672862410545349), ('スター・ウォーズ/クローン・ウォーズ', 0.7568692564964294), ('Skywalker', 0.7462978363037109), ('Vader', 0.7386180758476257), ('Darth', 0.7348513603210449), ('アンリーシュド', 0.7302812337875366), ('マーク・ハミル', 0.7291985154151917), ('スターキラー', 0.7291843295097351), ('ダークホースコミックス', 0.7194232940673828)]

◆ "ジェダイ"
[('ヨーダ', 0.8546147346496582), ('ルーク', 0.8495832681655884), ('メイス・ウィンドゥ', 0.8388921618461609), ('シスの暗黒卿', 0.8304752707481384), ('ジェダイの騎士', 0.8251877427101135), ('ベイダー', 0.8240576386451721), ('フォース', 0.8231958746910095), ('パルパティーン', 0.8225476741790771), ('アソーカ・タノ', 0.810978889465332), ('ライトセーバー', 0.8034700155258179)]

◆ "ポケモン"
[('ポケットモンスター', 0.8412479162216187), ('ハートゴールドソウルシルバー', 0.7880970239639282), ('あがき', 0.7801591157913208), ('デオキシス', 0.7634245753288269), ('オメガルビーアルファサファイア', 0.7629392147064209), ('ファイアレッドリーフグリーン', 0.7616440653800964), ('ミュウツー', 0.7608321309089661), ('サン・ムーン', 0.7568148970603943), ('ポケットモンスターダイヤモンド・パール', 0.7520866394042969), ('シンオウ地方', 0.7429976463317871)]

◆ "織田信長"
[('天正', 0.8434195518493652), ('本能寺の変', 0.8411773443222046), ('信長', 0.8407463431358337), ('明智光秀', 0.8404603600502014), ('武田信玄', 0.8318426609039307), ('武田氏', 0.827631413936615), ('朝倉義景', 0.8265658617019653), ('足利義昭', 0.8241444826126099), ('家康', 0.8218310475349426), ('織田家', 0.8208842873573303)]

◆ "スティーブ・ジョブズ"
[('NeXT', 0.788788378238678), ('ジョブズ', 0.7813189029693604), ('Lisa', 0.7764381766319275), ('アップルコンピュータ', 0.7493448853492737), ('NeXTSTEP', 0.738331139087677), ('引揚げ', 0.7378888130187988), ('アップル', 0.7333810925483704), ('ロス・ペロー', 0.7182155251502991), ('ページ記述言語', 0.713791012763977), ('STマイクロエレクトロニクス', 0.709499716758728)]

東京メトロ
[('東京地下鉄', 0.9180809855461121), ('都営地下鉄', 0.9129495024681091), ('有楽町線', 0.8748745918273926), ('副都心線', 0.8597694635391235), ('東西線', 0.8401104211807251), ('千代田線', 0.8398270606994629), ('東京都交通局', 0.8391836881637573), ('銀座線', 0.8338072896003723), ('日比谷線', 0.8261150121688843), ('丸ノ内線', 0.8236370086669922)]

Wikipediaはデータとしては重たいので、手軽に行いたい方は青空文庫などからデータを引っ張ってくるのが良いかもしれません。

青空文庫 Aozora Bunko


4. まとめ

#3では言語処理において分散表現を導入するにあたってWord2vecについて解説を行いました。
#4ではここまでで言語の行列表記については大体取り扱えたため、簡単なアプリケーション例であるcos類似度を用いた文書分類について解説できればと思います。

 

↓以降の記事リストは下記です。

言語処理におけるグラフ理論とネットワーク分析|実践的自然言語処理入門 #6 - lib-arts’s diary