Word2Vecとは、word to vectorの略語で、その名の通り、ワードをベクトルに変換する仕組みを意味します。googleによって開発されたアルゴリズムで、いろいろなところで使用されています。今回は、w2vを用いて、単語と単語の類似度を算出する方法、単語から単語を引き算したり、足し算したりする方法を学びます。
# *** 50秒くらいかかります。連打しないこと ***
import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('model.vec', binary=False)
次に、学習済みモデルmodelを使用して、指定した単語の類似度を測ってみましょう。例として、「りんご」と類似する単語を「5」個取り出してみます。これは、以下のように記述すればokです。
useword = "りんご" # 検出対象のワード
num = 10 # 上位何個の単語を検出するか
v = model[useword] # model.wv[useword]はv4で消えるので、この記法
wd = model.most_similar( [ v ], [], num)
print(wd)
似ている単語が出てきました。数字がセットになっていることがわかります。この数字は類似度を表しており、最大1を取ります。一番上にある単語「りんご」は、完全に一致しているので、類似度が最大となります。続いて「リンゴ、イチゴ、ブルーベリー...」と続きます。
なお、i番目に似ている単語とその類似度は、以下のように書けばアクセスできます。
i = 4
print("単語: ", wd[i][0])
print("類似度: ", wd[i][1])
ちなみに、modelに登録されていない単語は、類似度を測れません。例えば、以下のコードはエラーが出ます。
# v = model["りんごん"]
このように新たな単語を登録したいときは、そのワードが入った文章をいくつも用意し、モデルを学習させる必要があります。これについては、次回以降で解説します。
続いて、指定した2つの単語同士の類似度を求めてみます。これは、similarity関数を利用します。
w1 = "りんご"
w2 = "みかん"
w3 = "ラジオ"
d12 = model.similarity(w1, w2)
d13 = model.similarity(w1, w3)
d23 = model.similarity(w2, w3)
print("「" + w1 + "」と「" + w2 + "」の類似度: " + str(d12))
print("「" + w1 + "」と「" + w3 + "」の類似度: " + str(d13))
print("「" + w2 + "」と「" + w3 + "」の類似度: " + str(d23))
果物同士は似ていて、ラジオとは似ていないようです。 graphvizを使用すると、この関係を可視化できます。これについては、http://int-info.com/PyLearn/PyLearnTIPS01.html を参照してくださ。
from graphviz import Graph
nG = Graph(format="pdf") # 拡張子の設定 (pdf/pngなど)
nG.attr("node", shape="square", color="black")
# エッジの定義
nG.edge(w1, w2, label=str(round(d12, 3)), penwidth=str(d12*10))
nG.edge(w1, w3, label=str(round(d13, 3)), penwidth=str(d13*10))
nG.edge(w2, w3, label=str(round(d23, 3)), penwidth=str(d23*10))
# ノードの定義
nG.node(w1, shape="circle", color="blue", fixedsize="True", width="0.7", height="0.7")
nG.node(w2, shape="circle", color="blue", fixedsize="True", width="0.7", height="0.7")
nG.node(w3, shape="circle", color="blue", fixedsize="True", width="0.7", height="0.7")
# 描画
nG.render("ngraphs")
display(nG)
こんな感じになりました。類似しているほど、パスを太くしています。これだけだとつまらないですが、単語数が多くなると面白そうですね。
続いて、単語同士の演算について考えてみます。 複数の単語同士を足したり引いたりしたい場合は、足したい単語をmost_similar関数のpositiveに、引きたい単語をnegative引数に与えればokです。複数入れてもokです。掛け算や割り算はありません。
# 人間 - 知性?
t = model.most_similar(positive=['人間'], negative=['知性'])
print(t)
# -> 人間から知性を抜くと、動物になるようです。
# 女 + 王 ?
t = model.most_similar(positive=['女', '王'], negative=[])
print(t)
# -> 女性を王様にすると、王妃や王女になるようです。
# サラリーマン + 夢 ?
t = model.most_similar(positive=['サラリーマン', '夢'], negative=[])
print(t)
# サラリーマンが夢を持つと、フリーターや貧乏になるようです...
# 大人 - 年齢?
t = model.most_similar(positive=['大人'], negative=['年齢'])
print(t)
# -> 大人から年齢を引くと、子供になるようです。
# 人間 + 労働 + 残業?
t = model.most_similar(positive=['人間', '労働', '残業'], negative=[])
print(t)
# 人間が働いて残業をすると、賃金になるようです。
色々例を出しましたが、多くの場合、おかしい結果が返ってきます。モデルの学習をより良くするともう少しそれらしい回答が期待できますが、限界もあるでしょう。自然言語の難しさを表していると言えます。
形態素解析とmecabを利用して、簡単なアプリケーションを作ってみます。
機能:
アルゴリズム:
# 50秒くらいかかるので、一回のみ実行
import MeCab
import gensim
import random
model = gensim.models.KeyedVectors.load_word2vec_format('model.vec', binary=False)
# テキスト入力
print("")
print("*** 入力テキスト ***")
text = input()
# 品詞情報の取得
Info01, Info02 = [], []
Word = []
tag = MeCab.Tagger("-Ochasen")
node = tag.parseToNode(text)
while node:
Info01.append(node.feature.split(",")[0]) # 品詞情報1
Info02.append(node.feature.split(",")[1]) # 品詞情報2
Word.append(node.surface.split(",")[0]) # 単語
node=node.next
# 解析対象を抜き取り
TargetWord = []
TargetNumber = []
for i in range(len(Word)):
# 検出したい品詞を決定(名詞、動詞、形容詞)
# if Info01[i] == "名詞" or Info01[i] == "動詞" or Info01[i] == "形容詞":
if Info01[i] == "名詞" or Info01[i] == "動詞":
# if Info01[i] == "名詞":
TargetWord.append(Word[i])
TargetNumber.append(i)
wv = []
for i in range(len(TargetWord)):
wv.append(model[TargetWord[i]])
# 単語の類似度の検索
SimWord = []
GetWord = []
NumGetWord = 5 # 検出したい単語数
for i in range(len(TargetWord)):
SimWord.append(model.most_similar( [ wv[i] ], [], NumGetWord))
temp_txt = ""
for j in range(1, NumGetWord):
temp_txt = temp_txt + SimWord[i][j][0] + " / "
GetWord.append(temp_txt)
# 純粋な単語のみを取り出し
PureWord = []
for i in range(0, len(GetWord)):
PureWord.append(GetWord[i].split("/"))
# 自動文章生成
GenerationTxtList = []
NumGenerate = 5 # <- 生成文章の数
for m in range(0, NumGenerate):
GenerationTxt = ""
k=0
for i in range(0, len(Word)):
# 置き換え単語の場合、置き換え
changeFlag = 0
for j in range(0, len(TargetNumber)):
if i == TargetNumber[j]:
r = random.randint(0, len(PureWord[k])-2) # <- 乱数生成
GenerationTxt = GenerationTxt + PureWord[k][r]
k=k+1
changeFlag = 1
break
# 置き換えではない場合、そのまま結合
if changeFlag == 0:
GenerationTxt = GenerationTxt + Word[i]
# 生成文章をappend
GenerationTxtList.append(GenerationTxt)
# 生成テキストの表示
print("")
print(" *** 生成テキスト ***")
for i in range(0, len(GenerationTxtList)):
print(GenerationTxtList[i])
# 類似用語の出力
print("")
print("*** 検出単語一覧 ***")
for i in range(0, len(TargetWord)):
print("")
print("第"+str(i)+"単語: " + TargetWord[i])
print(GetWord[i])
てきとうに作ってみましたが、結構おもしろい結果になりました。Webアプリ化するといいかもしれませんね。
本日は、すでに学習されているモデルを利用して、w2vの威力を体験してみました。次回は実際に自分でモデルを作る方法などを学習していきます。