自分のツイートを word2vec で学習させて遊ぶ

全ツイートの内容を word2vec に放り込んで遊んだ。

数年前から word2vec はスゴいらしいという話を聞いていたが、なんとなくスルーしていた。
Python の gensim パッケージで手軽に word2vec を使えるようなので、試してみた。

今回使った環境

  • Windows 10 (64bit)
  • Python 3.6.0 (Anaconda 4.3.0)
  • MeCab 0.996

word2vec は、2014年に Google で開発された自然言語処理の手法。比較的計算量が少ないところが特徴らしい。

ざっくりとした流れ

word2vec という名前の由来は、おそらく "word to vector" の略だと思う(出典はない)。単語 (word) からベクトル (vector) を学習する。

word2vec は、大量の文章を渡されると、単語の前後関係から、各単語をよく表すn次元のベクトルを学習する。
一見すると機械的な方法だが、なぜか単語どうしの意味的な関連性まで学習できてしまう(ことがある)らしい。
ベクトルの次元数 n は、自由に設定できる。ネット上のサンプルを見ていると n=100 くらいに設定していることが多かった。

細かい話

学習には2層のニューラルネットワーク (入力層、隠れ層、出力層からなる。ふつう出力層はカウントしないので2層といっている) を使っているらしい。意外と少ない!
学習時の「前後の単語を見る」処理として、CBOW (Continuous Bag-of-words) と skip-gram の2種類が提案されている。どちらを使うかによって、NNの構造がすこし異なる。
Python の gensim パッケージではどちらも使えるが、今回は性能が良いらしい skip-gram を使うことにする。

参考:
Word2vec - Wikipedia
絵で理解するWord2vecの仕組み
【論文シリーズ】CBOWとSkip-gramについて


データの準備

さて、word2vec の学習には、大きめの文章データが必要となる。
なにか良さそうなものはないかと考えて、約12万ツイートある mogesystem の投稿内容を使うことにした。
たまに漫画やアニメの話をするので、ひょっとすると登場人物の名前などを学習できるかもしれない。


自分のツイートをダウンロードする

全ツイートの履歴は twitter.com/settings/account から取得できる。
ボタンを押してしばらくすると、メールでダウンロードURLが送られてくる。
ZIPファイルをダウンロードして解凍したなかに tweets.csv というファイルがある。
この tweets.csv には、1行1ツイートとして全投稿内容が含まれている。

今回はこの tweets.csv を使うことにする。
tweets.csv の内容
ろくなツイートでないことは気にしてはいけない

ツイートの本文だけを抜き出す

tweets.csv をテキストエディタで開いてみると、投稿日時といったメタ情報や、RTしたツイートまでもが、CSVデータとして記録されている。
ここでは「自分が投稿したツイートの本文」だけが必要なので、処理していく。

以下にリストアップしたものは不要となる。
  • リツイート
  • 本文以外のデータ(投稿日時やツイートIDなど)
  • 本文中のURL
  • 本文中のスクリーンネーム (@~)
  • 本文中のハッシュタグ (#~)
さらにツイート本文は、一部の記号がエスケープシーケンス (&等) で格納されているため、これも変換しておく必要がある。

これらの作業を手動でこなすのはムリがあるので、Pythonで処理するコードを書いた。

create_tweet_list.py
import pandas as pd
import numpy as np

# 読み込み
df_all = pd.read_csv('tweets.csv')

# リツイート以外のツイート (retweeted_status_idが空っぽの行) を抜き出す
df_mytweet = df_all.ix[df_all['retweeted_status_id'].isnull(), ['text']]

# 置き換えルール
mytweet = df_mytweet['text']
replaceList = [
    ('@[a-zA-Z0-9_]{1,15}', ''),
    ('https?://[\w/:%#\$&\?\(\)~\.=\+\-]+', ''),
    ('[##][A-Za-zA-Za-z一-鿆0-90-9ぁ-ヶヲ-゚ー]+', ''),
    ('\n', ' '),
    ('&lt;', '<'),
    ('&gt;', '>'),
    ('&amp;', '&'),
]
for t in replaceList:
    mytweet = mytweet.str.replace(t[0], t[1])

# 前後の空白文字を削除
mytweet = mytweet.str.strip()

# 空行を削除
mytweet = mytweet.replace('', np.nan).dropna()

# テキストファイルに保存
with open('tweet_list.txt', 'w', encoding='utf-8') as f:
    for line in mytweet.values:
        f.writelines(line + '\n')

あらかじめ "pip install pandas" で Pandas をインストールしておき、
python create_tweet_list.py
を実行すると、tweets.csvを読み込んで tweet_list.txt というファイルを作ってくれる。

ツイート本文を抜き出したファイル tweet_list.txt
ヘンなツイートが多いのはご愛嬌


ツイート本文の前処理(分かち書き・原形への変換)

こうしてできあがった tweet_list.txt をさっそく word2vec で学習したいところだが、そのままではうまく扱えない。

文章を word2vec へぶっこむには、単語ごとにスペースで区切ってやる必要がある。
英文は最初から単語スペース区切りになっているが、日本語の文章はどこからどこまでが一単語なのかはわからない。

この「単語ごとにスペースで区切る」処理は、わかち書きと呼ばれている。わかち書きには、日本語形態素解析エンジン MeCab(メカブ)がよく使われている。

今回はコマンドラインから MeCab を利用する。Python にも MeCab のバインディングがあるようだが、どうやら Windows との親和性が悪いようで使えなかった。

さっそく MeCab をインストールする。
MeCab のサイトから、インストーラをダウンロードする。
インストール中に辞書のエンコーディングを聞かれるが、今回は UTF-8 にする。
最後に、MeCabのインストールフォルダにパスを通せば、セットアップ完了。

基本的には、以下のコマンドで tweet_list.txt をわかち書きできる。
mecab tweet_list.txt -o wakati.txt -Owakati

今回の用途では、動詞や形容詞を原形に変換しておくと都合がいい。
なぜこんなことをするかというと、文章中の「見た」「見ろ」といった動詞すべてを「見る」という同じ単語として関係性を学習させたいからだ。

MeCab では、以下のようなオプションをつけて、分かち書きしつつ原形へ変換できる。
mecab tweet_list.txt -o wakati_w2v.txt -F"%%f[6] " -U"%%m " -E"\n"
(MeCab の詳細仕様は taku910.github.io/mecab/format.html に書いてある。フォーマット指定には % を使うが、コマンドプロンプトの仕様で %% と書く必要がある。)

わかち書きしつつ原形にしたツイート wakati_w2v.txt
単語がすべて原形になったので日本語不自由っぽい

ユーザ辞書を追加する

ところで、MeCabの出力結果をみてみると、特殊な固有名詞などがバラバラに分解されてしまうことがある。
あまりツイートしない単語なら問題ないかもしれないが、ツイート頻度の高いキャラクターの名前だったりすると、word2vec の学習結果に影響する可能性がある。

MeCab はユーザ辞書を追加できるため、うまく分かれていない単語を見つけ出し、辞書をつくった。

ユーザ辞書のつくり方は taku910.github.io/mecab/dic.html 、辞書作成に必要な「品詞ID」については taku910.github.io/mecab/posid.html に書いてある。

mywords.csv (UTF-8エンコーディングで保存する)
赤座,,,100,名詞,固有名詞,人名,姓,*,*,赤座,アカザ,アカザ
タンコブ,,,100,名詞,一般,*,*,*,*,タンコブ,タンコブ,タンコブ
肩甲骨,,,100,名詞,固有名詞,一般,*,*,*,肩甲骨,ケンコウコツ,ケンコウコツ
ヤマノススメ,,,100,名詞,固有名詞,一般,*,*,*,ヤマノススメ,ヤマノススメ,ヤマノススメ
ゆるゆり,,,100,名詞,固有名詞,一般,*,*,*,ゆるゆり,ゆるゆり,ゆるゆり
肉巻きおにぎり,,,100,名詞,一般,*,*,*,*,肉巻きおにぎり,ニクマキオニギリ,ニクマキオニギリ
逆流性食道炎,,,100,名詞,一般,*,*,*,*,逆流性食道炎,ギャクリュウセイショクドウエン,ギャクリュウセイショクドウエン
磯波,,,100,名詞,固有名詞,人名,名,*,*,磯波,アカザ,アカザ
返信,,,100,名詞,一般,*,*,*,*,返信,ヘンシン,ヘンシン
鍵垢,,,100,名詞,一般,*,*,*,*,鍵垢,カギアカ,カギアカ
規制垢,,,100,名詞,一般,*,*,*,*,規制垢,キセイアカ,キセイアカ
ふぁぼ,,,100,名詞,一般,*,*,*,*,ふぁぼ,ファボ,ファボ
電気素量,,,100,名詞,固有名詞,一般,*,*,*,電気素量,デンキソリョウ,デンキソリョウ
焼肉定食,,,100,名詞,固有名詞,人名,姓,*,*,焼肉定食,ヤキニクテイショク,ヤキニクテイショク
おーいぇえーあはぁん,,,100,名詞,固有名詞,一般,*,*,*,おーいぇえーあはぁん,オーイエーアハーン,オーイエーアハーン
ちなつ,,,100,名詞,固有名詞,人名,名,*,*,ちなつ,チナツ,チナツ
赤外線天文衛星,,,100,名詞,固有名詞,一般,*,*,*,赤外線天文衛星,セキガイセンテンモンエイセイ,セキガイセンテンモンエイセイ

mywords.csv から辞書ファイル mywords.dic を生成するバッチファイルを作った。

create_dic.bat
@echo off

REM -d DIR:     システム辞書があるディレクトリ
REM -u FILE:    FILE というユーザファイルを作成
REM -f charset: CSVの文字コード
REM -t charset: バイナリ辞書の文字コード

mecab-dict-index -d "C:\Program Files (x86)\MeCab\dic\ipadic" -u mywords.dic -f utf-8 -t utf-8 mywords.csv


バッチファイルを実行して生成された mywords.dic を適当なフォルダへコピーする。
ここでは C:\Program Files (x86)\MeCab\userdic を作成し、その中にコピーした。

最後に、設定ファイルを開いて、ユーザ辞書へのパスを追加する。
C:\Program Files (x86)\MeCab\etc\mecabrc を開いて
userdic = "C:\Program Files (x86)\MeCab\userdic\mywords.dic"
を追記する。

これでもう一度 MeCab を実行すると、ユーザ辞書をつかってわかち書きできるようになる。


word2vec を使う

準備が整ったので、ようやく word2vec にツイートを学習させる。
あらかじめ Python の gensim パッケージをインストールしておく。
pip install gensim

ベクトルを学習させる

前処理したツイート wakati_w2v.txt を word2vec に放り込む。
gensim のインターフェースは簡素なので、実は数行で書ける(ドキュメントは ここ にある)。

今回は skip-gram を使って、100次元のベクトルを学習させる。

w2v_learn.py
from gensim.models import word2vec

sentences = word2vec.LineSentence('wakati_w2v.txt')
model = word2vec.Word2Vec(sentences,
                          sg=1,         # 訓練アルゴリズム; 0: CBOW, 1: skip-gram
                          size=100,     # ベクトルの次元数
                          window=10,    # 同じ文章内の単語どうしの最大距離
                          min_count=2,  # これより出現回数の少ない単語は含まない
                          hs=1,         # 1: 階層的ソフトマックス, hs==0 && negative!=0: ネガティブサンプリングを使う
                          negative=0    # ネガティブサンプリング(ノイズワード)の数
                          )
model.save('tweet_list.model')

全部で5MBの wakati_w2v.txt を使って、学習に1~2分くらいかかった。
生成される tweet_list.model は 29.2MB だった。意外とでかい!

類似する単語をリストアップする

生成されたモデルをつかって、いろいろな操作ができる。
まずは、ある単語に似た単語をリストアップする、というタスクをやってみる。

w2v_similar.py
from gensim.models import word2vec
import sys

model   = word2vec.Word2Vec.load('tweet_list.model')
results = model.most_similar(positive=sys.argv[1], topn=10)

for r in results:
    print(r[0], '\t', r[1])

w2v_similar.py はコマンドライン引数として受け取った単語に似た単語を10個表示してくれる。

自分のデータセットでは、
python w2v_similar.py 結衣
を実行すると

先輩     0.821367084980011
濃厚     0.7531079649925232
杉浦     0.7434007525444031
バッキンガム     0.7008762955665588
センパーイ       0.6812834143638611
京子     0.6799753308296204
綾乃     0.6702271103858948
罰金     0.6633200645446777
野獣     0.6593067646026611
ちなつ   0.6481812000274658

という結果になった。

『ゆるゆり』の登場人物である 結衣 から、京子、杉浦、綾乃、ちなつ といった他の登場人物を類似した単語として学習できている。
また、「結衣先輩」と呼んでいたためか 先輩 や センパーイ も類似した単語として挙げられている。

python w2v_similar.py 言語
真似る   0.6424436569213867
ポインタ         0.6400958895683289
アセンブリ       0.6016155481338501
枠組み   0.5786939263343811
初等     0.5606597065925598
アルゴリズム     0.552629828453064
NET      0.5475132465362549
組み込める       0.5434015989303589
DxLib    0.5420008897781372
C        0.5419571399688721 
なんとなくプログラミング言語やライブラリの名前が見える。

python w2v_similar.py Aphex
Twin             0.8739225268363953
Radiator         0.8268277645111084
Fingerbib        0.8095642924308777
Stone            0.8080536127090454
Tassels          0.7786843180656433
Twig             0.7654238343238831
vordhosbn        0.7652990221977234
カーンカ         0.7626219987869263
Calx             0.7598056197166443
Balsalm          0.7510707378387451
Aphex Twin の曲名の一部が出てきている。

python w2v_similar.py 神奈川
兵庫     0.8190442323684692
長崎     0.8164616227149963
栃木     0.8114367723464966
石川     0.8072326183319092
香川     0.8070265054702759
埼玉     0.802727460861206
広島     0.7998003959655762
鳥取     0.7940439581871033
静岡     0.7927784323692322
山梨     0.7881968021392822
日本の県名が出てきた。


おわり

というわけで、今更ながら word2vec で遊んだ。
学習の設定を変えると出力結果も変わってくるので、うまく調整すればもっと良い結果になりそう(どのような状態が「良い結果」なのかよくわからないが……)。
ベクトル表現をそのまま取り出せば、他にもおもしろいことができるかもしれない。


参考にしたもの

Python で「老人と海」を word2vec する


コメント

このブログの人気の投稿

基本波形の生成

(1)C言語で音声合成もどき ~WAVファイルを生成する~

(2)C言語で音声合成もどき ~母音の生成~