oryou-sanのブログ

備忘録です

Word2Vecを使ってみる

はじめに

Python自然言語処理系のライブラリであるgensimでWord2Vecを使ってみます。


データ準備

Word2Vecにかけるデータを準備します。分かち書きされている文章をリストにまとめたものならOKです。
前回の記事で紹介したMeCabを使っています。

oryou-san.hatenablog.com

import MeCab
mecab = MeCab.Tagger("-Ochasen")

s1 = 'おいしいパスタつくったお前'
s2 = 'あなたを詐欺罪と器物損壊罪で訴えます'
s3 = 'その点トッポってすげぇよな'

data =[s1,s2,s3]

def wakachigaki_bow(s):
    wakachi = mecab.parse(s).split('\n')
    wakachi = [w.split('\t') for w in wakachi if len(w.split('\t')) > 1]
    wakachi = list(filter(lambda x: x[3][:2] == '名詞'
                      or x[3][:2] == '動詞' 
                      or x[3][:3] == '形容詞', wakachi))
    wakachi_base = [w[2] for w in wakachi]
    return wakachi_base

sentences = [wakachigaki_bow(s) for s in data]

print(sentences)

# [['おいしい', 'パスタ', 'つくる', 'お前'], ['あなた', '詐欺', '罪', '器物', '損壊', '罪', '訴える'], ['点', 'トッポ', 'すげる', 'ぇよな']]


早速まわしてみる

準備したデータをモデルにつっこみます。

from gensim.models import Word2Vec
model = Word2Vec(sentences, min_count=1)

print(model.wv['パスタ'])
print(model.wv.most_similar('パスタ', topn = 3))

# [ 9.7702928e-03  8.1651136e-03  1.2809705e-03  5.0975773e-03
#   1.4081288e-03 -6.4551616e-03 -1.4280510e-03  6.4491653e-03
#  -4.6173073e-03 -3.9930656e-03  4.9244044e-03  ...]

# [('ぇよな', 0.17272794246673584), ('点', 0.16694681346416473), ('器物', 0.11117953807115555)]

学習済みのモデルから試しに2つ出力してみました。

・単語の分散表現
.wv['key']で単語の分散表現を出力できます(デフォルトだと100次元)。

・類語
.wv.most_similar('key', topn = n)でkeyとなった単語の類語(上位n個)を出力できます。
コサイン類似度の高いものから順に単語を出力しているようです。


参考

models.word2vec – Word2vec embeddings — gensim

MeCabで分かち書き

はじめに

MeCabというライブラリを使って日本語の分かち書きをしてみます。


まずは使ってみる

出力はこんな感じになります。

s = 'おいしいパスタつくったお前'

import MeCab
mecab = MeCab.Tagger("-Ochasen")

print(mecab.parse(s))

# おいしい	オイシイ	おいしい	形容詞-自立	形容詞・イ段	基本形
# パスタ	パスタ	パスタ	名詞-一般		
# つくっ	ツクッ	つくる	動詞-自立	五段・ラ行	連用タ接続
# た	タ	た	助動詞	特殊・タ	基本形
# お前	オマエ	お前	名詞-代名詞-一般		
# EOS

出力は単語毎に'\n'(改行)で区切られ、文末には'EOS'が付されます。
各単語は原型・読み・品詞 などの情報が付加され、'\t'(タブ)で区切られています。


分かち書きの結果を出力

次に、品詞の情報などを取り除いて単純に分かち書きの結果を出力してみます。

wakachi = mecab.parse(s).split('\n')
wakachi = [w.split('\t') for w in wakachi if len(w.split('\t')) > 1]

wakachi_surface = [w[0] for w in wakachi]
wakachi_surface = ' '.join(wakachi_surface)
    
print(wakachi_surface)

# おいしい パスタ つくっ た お前

これを関数にして、複数文をforでとりこんでみます。

def wakachigaki(s):
    wakachi = mecab.parse(s).split('\n')
    wakachi = [w.split('\t') for w in wakachi if len(w.split('\t')) > 1]
    wakachi_surface = [w[0] for w in wakachi]
    wakachi_surface = ' '.join(wakachi_surface)
    return wakachi_surface

s1 = 'おいしいパスタつくったお前'
s2 = 'あなたを詐欺罪と器物損壊罪で訴えます'
s3 = 'その点トッポってすげぇよな'

data =[s1,s2,s3]

for s in data:
    print(wakachigaki(s))

# おいしい パスタ つくっ た お前
# あなた を 詐欺 罪 と 器物 損壊 罪 で 訴え ます
# その 点 トッポ って すげ ぇよな


分かち書きの結果を出力(応用編)

このままでも機械学習にかける前処理としては充分かもしれませんが、一工夫して以下の処理を加えてみます。

・活用が存在する品詞(形容詞、動詞)はその原型を出力とする
・出力は名詞・動詞・形容詞に絞る

def wakachigaki_bow(s):
    wakachi = mecab.parse(s).split('\n')
    wakachi = [w.split('\t') for w in wakachi if len(w.split('\t')) > 1]
    wakachi = list(filter(lambda x: x[3][:2] == '名詞'
                      or x[3][:2] == '動詞' 
                      or x[3][:3] == '形容詞', wakachi))

    wakachi_base = [w[2] for w in wakachi]
    wakachi_base = ' '.join(wakachi_base)
    return wakachi_base

for s in data:
    print(wakachigaki_bow(s))

# おいしい パスタ つくる お前
# あなた 詐欺 罪 器物 損壊 罪 訴える
# 点 トッポ すげる ぇよな

LDA(トピックモデル)を使ってみる

はじめに

LDAを試しに使ってみたので備忘として残します。
加藤公一氏著の機械学習図鑑からコードを拝借しました。


学習&分類

以下書籍からの引用です。(コードはgithubから落とせます。)
やってることは以下の通りです。

・scikit-learnのdataset"fetch_20newsgroups"よりサンプルデータを取得
・CountVectorizerにてサンプルデータの文章をベクトル化
・ベクトル化された文章をLDAで次元削減(サンプルデータのラベルが20種類なので、これに合わせて20次元に削減)

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
 
# removeで本文以外の情報を取り除く
data = fetch_20newsgroups(remove=('headers', 'footers', 'quotes'))

max_features = 1000
# 文書 データをベクトルに変換(n_data, max_features=1,000)
tf_vectorizer = CountVectorizer(max_features=max_features,
stop_words='english')
tf = tf_vectorizer.fit_transform(data.data)

n_topics = 20
model = LatentDirichletAllocation(n_components=n_topics)
model.fit(tf)

CountVectorizerクラスに設定されている引数2つは以下の通りです。

・max_features
文章をベクトル化する際に最頻n個の単語のみを考慮するように設定できます。
ここでは最頻1,000個の単語をベクトル化するように設定しています。

・stop_words
文章をベクトル化するにあたり除外すべき単語をここで指定します。
ここでは'english'となっていますがこれでbuilt-inの英語のstop-wordsが指定できます。
.get_stop_words()のメソッドで登録されている単語群が確認できます。

stop_words = tf_vectorizer.get_stop_words()
stop_words = [w for w in stop_words]

print(f'len:{len(stop_words)} \nword_list: {stop_words}')
# len:318 
# word_list: ['anyone', 'you', 'to', 'thereby', 'via', 'someone', 'seeming', 'find', 'least', 'besides', 'now', 'thick', 'itself', 'own', 'back', 'latter', 'became', 'take', 'name', 'moreover', 'due', 'could', .....


結果

書籍だとトピック毎に頻出する単語をリストにして結果を解釈しておりましたので同じように結果を出力してみます。
やってることは以下の通りです。

・文章毎に削減した20次元のベクトル(トピック)のうち、一番値の大きいものを選ぶ(一番もっともらしいトピックを選ぶ)
・同じトピックに分類された文章毎に単語を頻出頻度順(降順)に出力

import numpy as np

# 文書データのベクトルを20次元に削減(n_data, n_topics=20)
tpc_cls = np.argmax(model.transform(tf), axis=1)

for a in range(20):
    tpc_id = [i for i,c in enumerate(tpc_cls) if c == a]
    tpc_stc = [s for i,s in enumerate(data.data) if i in tpc_id]
    tf_tpc = tf_vectorizer.fit_transform(tpc_stc)
    term_frequency = np.array(tf_tpc.sum(axis=0))[0]
    print(f'topic_{a+1}')
    print([tf_vectorizer.get_feature_names()[b] for b in term_frequency.argsort()[:-10:-1]])

出力はこんな感じになるはずです。

topic_1
['god', 'jesus', 'people', 'bible', 'christ', 'does', 'christian', 'believe', 'church']
topic_2
['don', 'know', 'think', 'people', 'does', 'just', 'like', 'say', 'god']
topic_3
['00', 'new', '50', 'sale', '10', 'dos', 'good', 'offer', '20']
topic_4
['president', 'mr', 'stephanopoulos', 'people', 'think', 'going', 'know', 'don', 'jobs']
topic_5
['entry', 'file', 'cx', 'output', 'w7', 'program', 'c_', 'chz', 't7']
topic_6
['key', 'encryption', 'chip', 'use', 'clipper', 'keys', 'privacy', 'government', 'security']
topic_7
['pts', 'period', 'la', 'play', 'power', 'pp', '10', 'pt', 'scorer']
topic_8
['space', 'nasa', '1993', 'research', 'university', 'launch', 'program', 'information', 'use']
topic_9
['people', 'israel', 'government', 'right', 'israeli', 'state', 'war', 'don', 'jews']
topic_10
['said', 'people', 'know', 'didn', 'don', 'just', 'went', 'like', 'time']
topic_11
['edu', 'com', 'available', 'use', 'file', 'image', 'window', 'ftp', 'program']
topic_12
['10', '25', '55', '11', '12', '14', '15', '20', '16']
topic_13
['gun', 'file', 'guns', 'firearms', 'crime', 'control', 'law', '000', 'use']
topic_14
['scsi', 'use', 'bit', 'data', 'bus', 'ide', 'chip', 'like', 'does']
topic_15
['team', 'game', 'year', 'season', 'games', 'hockey', 'players', 'league', 'nhl']
topic_16
['drive', 'windows', 'card', 'use', 'thanks', 'disk', 'problem', 'know', 'does']
topic_17
['like', 'just', 'good', 'don', 'time', 'car', 'think', 'know', 've']
topic_18
['ax', 'max', 'g9v', 'b8f', 'a86', 'pl', '145', '1d9', '0t']
topic_19
['db', 'armenian', 'turkish', 'armenians', 'people', 'turkey', 'jews', 'turks', 'genocide']
topic_20
['people', 'just', 'like', 'think', 'don', 'time', 'post', 'know', 'does']

topic_1はキリスト教、topic_8は宇宙、といったところでしょうか。
反対にtopic_12,18, 20あたりは解釈が難しく、stop_wordsを見直すなどしてより精度の高い分類ができるような工夫が必要になりそうです。