はじめに
k-meansで、BOW、Tf-IDFをやってみましたが、そもそも文書数が少ないので、その中での情報だけを使って文書の特徴を出そうとするのには限界がありました。
そこで、事前に大量の文書を用いて計算されたWord2vecを用いることで、文書中の単語の特徴を明確にし、さらに文書の特徴も明確になると考えました。
そのために、まず、Word2vecを扱う方法を調査・実験してみました。
※実行環境は、SageMaker Studio Lab です。
Word2Vecとは
便宜上、「Word2vec」と称していますが、学習済み単語ベクトル表現、分散表現、単語の埋め込みなどと呼ばれます。
より具体的には、以下を参照ください。
- Word2vec(Wikipedia)
- Word2Vec(AI用語事典)
- Word2Vecを理解する(Qiita)
- Word2vecとは|モデルの種類やその仕組み、活用事例まで紹介!(AINOW)
- Word2Vecとは | 分散表現・Skip-gram法とCBOWの仕組み・ツールや活用事例まで徹底解説(Ledge.ai)
- 単語の埋め込み(Wikipedia)
JuliaでWord2vec
JuliaではWord2vecを扱うのに、次の2つのライブラリがあります
Word2Vec.jl | テキストを与えて、学習させ、単語ベクトルを得ることができます。 |
Embeddings.jl | 学習済みの単語ベクトルが組み込まれています。(指定により取得します) |
今回は、学習済み単語ベクトルが必要なので、「Embeddings.jl」を採用します。
「Embeddings.jl」では次の3つの単語ベクトルを使うことができます。
今回は、日本語を扱いたいので、「FastText」を使います。
FastTextの読み込み
まず必要に応じてパッケージを追加してください。
using Pkg; Pkg.add("Embeddings")
次に、FastTextのデータのダウンロードですが、これはFastTextの読み込み指定の時に、ダウンロード済みかどうかを判定して、まだの場合は自動的にダウンロードします。
FastTextの日本語は「FastText_Text{:ja}」のように指定します。
using Embeddings
const embtable = load_embeddings(FastText_Text{:ja})
上記を初めて実行した場合には、「stdin> 」のようなプロンプトが出ます。
半角英字の「y」を入力し、Enterを押下してください。
この後は、少し時間がかかります。(SageMaker Studio lab 上で200秒くらい)
メッセージが表示されます。本来は、上記のプロンプトの前に表示されるべきものも含まれているようですが、ほとんどがダウンロードの途中表示です。
ここで、ダウンロード、解凍したものが次の場所に格納されます。(SageMaker Studio lab の場合)
この場所や、ファイル名を意識する必要はありませんが参考までの情報です。
/home/studio-lab-user/.julia/datadeps/FastText ja CommonCrawl Text/cc.ja.300.vec
以上のダウンロードの処理は、load_embedding()を呼び出した最初の一度のみ行われます。次回以降はダウンロードされないので、プロンプトもメッセージも表示されませんが、ダウンロード済みデータの読み込みには時間がかかるのは同じです。
Embeddings.jlがやってくれるのはここまでです。
単語ベクトルの各種操作は、それぞれ定義する必要があります。とりあえず、3つの関数を定義します。(vec、cosine、closest)
# 単語文字列からインデックスを取得するテーブル
const get_word_index = Dict(word=>ii for (ii,word) in enumerate(embtable.vocab))
# 単語文字列をベクトルに変換
function vec(word)
ind = get_word_index[word]
emb = embtable.embeddings[:,ind]
return emb
end
using Distances
# ベクトル間のcos類似度
cosine(x,y)=1-cosine_dist(x, y)
# 指定ベクトルに最も近い単語文字列をn件表示する
function closest(v, n=20)
list=[(x,cosine(embtable.embeddings'[x,:], v)) for x in 1:size(embtable.embeddings)[2]]
topn_idx=sort(list, by = x -> x[2], rev=true)[1:n]
return [embtable.vocab[a] for (a,_) in topn_idx]
end
Word2vecを試してみます
1. 定番の「王様-男性+女性」を試してみます
closest(vec("王様")-vec("男性")+vec("女性"), 5)
結果
5-element Vector{String}: "王様" "女王" "王さま" "ラジオキッズ" "王妃" "お姫様"
1番目に「女王」が来てほしかったのですが、2番目になってしまいました。
2. MathWorks社公式のword2vecの例題を日本語にして試してみます
italy = vec("イタリア")
rome = vec("ローマ")
paris = vec("パリ")
word = closest(italy - rome + paris, 5)
結果
5-element Vector{String}: "パリ" "フランス" "ニース" "ブリュッセル" "ストラスブール"
1番目はパリですが、2番目にフランスが来ています。
3. いろいろな単語間の類似度を計算してみます
cosine(vec("科学"), vec("技術"))
# 0.58602834f0
cosine(vec("米国"), vec("アメリカ"))
# 0.79781175f0 ← 高い
cosine(vec("英国"), vec("イギリス"))
# 0.8229223f0 ← 高い
cosine(vec("英国"), vec("タピオカ"))
# 0.13167423f0 ← 低い
cosine(vec("東京"), vec("大阪"))
# 0.6551998f0
まとめ
いろいろと試してみましたが、単語ベクトルの値は、学習されるデータによって変わるので、必ずしも期待通りの結果にはならないことがあります。
それでも、BOWやTF-IDFと比べると、単語そのものの意味をとることができているように見えます。
コメント