ユーニックス総合研究所

  • home
  • archives
  • python-gensim-word2vec

PythonでWord2vecを使い類似語を表示するスクリプトを作った【自然言語処理, gensim】

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  

おわりに

出力結果を見ると類似してるのかな?という結果ですが、ここら辺は謎です。
学習に使ったデータによってはあまり類似しないのかもしれません。
そもそものデータに類似語が少ないというケースですかね。うーん。謎。

参照