PythonでWord2vecを使い類似語を表示するスクリプトを作った【自然言語処理, gensim】
- 作成日: 2020-10-05
- 更新日: 2023-12-26
- カテゴリ: 自然言語処理
Word2vecとは?
自然言語処理で類似語を判定したいとなったとき、Pythonで使われるライブラリがWord2vecです。
これはGoogleのトマス・ミコロフ氏が筆頭に開発した技術で、サンプルとなるテキストからモデルを作成し、そのモデルを使って類似語などを検出できるようにするというものです。
今回はこのWord2vecを使ったコマンドライン・スクリプトを作成しましたので紹介したいと思います。
Word2vecのインストール
pip
を使い環境にWord2vec(gensim
)をインストールします。
$ pip install gensim
また、作成するスクリプトのために以下のライブラリもインストールします。
$ pip install BeautifulSoup4 janome
BeautifulSoup4
はHTMLを解析するパーサーです。
janome
は形態素解析を行うライブラリです。
Janomeについては↓の記事も併せてごらんください。
スクリプトの仕様
作成するスクリプトはコマンドラインから実行します。
スクリプトは以下のインターフェースを持ちます。
python script.py [url] [positive-words]
第1引数にurl
を取ります。このurl
からモデルの元になるテキストを取得します。
テキストはHTMLなどを除いて、プレインなテキストのみを利用します。
第2引数のpositive-words
は類似させたい単語のリスト(カンマ区切り)です。
たとえば青空文庫にある夏目漱石の「吾輩は猫である」からモデルを作成し、「猫」と類似させたいときは↓のように使います。
python script.py https://青空文庫の作品のURL.html 猫
URLのコンテンツはスクリプトの実行ディレクトリ以下にキャッシュ・ファイルとして保存され、同じURLが指定されたときはそのキャッシュファイル(URLをハッシュ化したファイル名)が利用されます。
キャッシュファイルを削除すれば再びURLからコンテンツを読み込むようになります。
スクリプトのコード
以下がスクリプトのコードです。
from janome.analyzer import Analyzer
from janome.tokenfilter import POSKeepFilter
from gensim.models import word2vec
from urllib.request import urlopen
from bs4 import BeautifulSoup
import sys, os, hashlib
def extract_words(s):
"""
文字列から名詞と代名詞を抜き出し単語のリストを生成する
"""
token_filters = [POSKeepFilter(['名詞', '代名詞'])]
a = Analyzer(token_filters=token_filters)
return [tok.base_form for tok in a.analyze(s)]
def read_content(url):
"""
URLのコンテンツをテキスト(タグ含まない)として読み込む
"""
with urlopen(url) as fin:
content = fin.read().decode('sjis', 'ignore')
soup = BeautifulSoup(content, 'html.parser')
return soup.get_text()
def save_cache_words(fname, words):
"""
単語リストをキャッシュに保存する
"""
with open(fname, 'w') as fout:
for w in words:
fout.write(w + '\n')
def load_cache_words(fname):
"""
キャッシュから単語リストを読み込む
"""
with open(fname, 'r') as fin:
return [line.rstrip() for line in fin.readlines()]
def read_words(url):
"""
URLから単語リストを生成する
キャッシュがある場合はそれを参照する
"""
fname = hashlib.md5(url.encode()).hexdigest() + '.txt'
if os.path.exists(fname):
return load_cache_words(fname) # load words from cache file
content = read_content(url)
words = extract_words(content)
save_cache_words(fname, words) # save cache file
return words
def main():
if len(sys.argv) < 3:
print('python script.py [url] [positive-words]')
sys.exit(0)
# 生成するモデルの元となる内容のURL
url = sys.argv[1]
# 近似対象の単語リスト
positive_words = sys.argv[2].split(',')
# URLの内容を読み込み、サンプルとなる単語のリストを生成
words = read_words(url)
# モデルを生成
model = word2vec.Word2Vec([words], size=100, min_count=5, window=5, iter=100)
# positive_wordsと近似する単語を出力する
ret = model.wv.most_similar(positive=positive_words)
for item in ret:
print(item[0], item[1])
if __name__ == '__main__':
main()
キャッシュワークなどで長くなってしまったんですが、肝心のWord2vecの部分は↓だけです。
# モデルを生成
model = word2vec.Word2Vec([words], size=100, min_count=5, window=5, iter=100)
# positive_wordsと近似する単語を出力する
ret = model.wv.most_similar(positive=positive_words)
for item in ret:
print(item[0], item[1])
word2vec.Word2Vec
クラスをインスタンス化すればモデルが作成できます。
このとき、第1引数にモデルの元になる単語リスト(list or tuple
)を指定します。
単語リストはURLのコンテンツから形態素解析を経て生成され、変数words
に保存されています。
2020/11/16 追記
スクリプトにバグがありました。
model = word2vec.Word2Vec(words, size=100, min_count=5, window=5, iter=100)
以前は↑のようにしていましたが、これは正しくは
model = word2vec.Word2Vec([words], size=100, min_count=5, window=5, iter=100)
です。Word2Vec()
の第1引数には行列を渡すので↑のようになります。
Word2Vec
クラスの引数は↓の通りです。
- size ... 圧縮する次元数
- min_count ... 出現頻度の低いものをカットします
- window ... 対象の単語の前後にある単語を拾うときに、窓の広さを決める
- iter ... 学習のループ回数(デフォルトは5)。学習が足りない時に指定する
学習が足りない時はmodel.wv.most_similar()
の値が1に近いものが多くなり、model.__dict__['wv']
の値も小さくなるそうです。
こういう場合はiter
の値を大きくします。
生成したモデルからmodel.wv.most_similar(positive=positive_words)
を実行します。
このとき引数positive
に類似させたい単語のリストを指定します。
このスクリプトを↓のように実行すると、引数の単語リストの類似単語が得られます。
*生成には乱数を使っているため同じ結果にはなりません。
$ python script.py https://青空文庫の作品のURL.html 猫
智慧 0.8598859906196594
属 0.8410282731056213
まさ 0.8302000164985657
野良 0.8289262652397156
おのれ 0.8250987529754639
動作 0.817663311958313
ねこ 0.8173679113388062
膏 0.813643753528595
一般 0.8116267919540405
人間 0.7986590266227722
おわりに
出力結果を見ると類似してるのかな?という結果ですが、ここら辺は謎です。
学習に使ったデータによってはあまり類似しないのかもしれません。
そもそものデータに類似語が少ないというケースですかね。うーん。謎。