PyLearn NLP 03: Word 2 Vec その2

日大生産工 大前佑斗

前回は、学習済みのw2vを扱いました。ただしそれですと、自分の目的にあった使い方ができない場合があります。そのためここでは、一からモデルを作ってみる、ということを学びます。

0. 準備

mecabでエラーが出る場合はターミナルで、

pip3 install mecab-python3==0.996.5

としてから実行しましょう。その後、使うライブラリをインポートします。

In [161]:
from gensim.models import Word2Vec
import numpy as np
import MeCab

今回、中心に使用するgensimは以下にドキュメントがあります。 https://radimrehurek.com/gensim/models/word2vec.html

1. テキストの用意

今回は、口コミ文章内に登場する用語の類似を算出するためのw2vを開発してみます。 このため、あるエアコンの口コミを適当に用意しました。

  • 上3例: ポジティブな口コミ
  • 下3例: ネガティブな口コミ

なお、今回は練習用の例ですので、少数データで行います。でも、本来は莫大な量のテキストが必要であることを忘れないでください。

In [251]:
mail_text =[
    "すぐに温度が下がり、部屋が冷たくなります。この商品はとても良いと思います。",
    "良い商品で嬉しいです。冷たい風が素早く発生し、部屋の温度が下がりました。",
    "エアコンのボタンを押すと、すごい勢いで動作しました。嬉しいです。",
    "リモコンのボタンを入れても、動きませんでした。ダメな製品で、良くないです。",
    "部屋が寒くなりすぎて、困りました。ダメな製品だと思います。",
    "たまに電源がつかないので、困る。部屋も寒くなりすぎるし、返品したいくらいダメな商品だ。"
]

2. 分かち書き

続きて、mecabを活用し、形態素解析にかけ、品詞ごとに分解します。word2vectorを使用するには、分かち書きに分解しておくことが必要です。

In [252]:
def wakachi(txt):
    Dic = MeCab.Tagger("-Ochasen")
    node = Dic.parseToNode(txt)

    Info01, Info02, Word = [], [], []

    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
    
    return Word

TrainingDat = []
for i in range(len(mail_text)):
    temp = wakachi(mail_text[i])
    temp.pop(0) # 最初の空白文字を削除
    temp.pop(-1) # 最後の空白文字を削除
    TrainingDat.append(temp)
    print("TrainingDat[", i, "]: \n", TrainingDat[i])
    print("")
TrainingDat[ 0 ]: 
 ['すぐ', 'に', '温度', 'が', '下がり', '、', '部屋', 'が', '冷たく', 'なり', 'ます', '。', 'この', '商品', 'は', 'とても', '良い', 'と', '思い', 'ます', '。']

TrainingDat[ 1 ]: 
 ['良い', '商品', 'で', '嬉しい', 'です', '。', '冷たい', '風', 'が', '素早く', '発生', 'し', '、', '部屋', 'の', '温度', 'が', '下がり', 'まし', 'た', '。']

TrainingDat[ 2 ]: 
 ['エアコン', 'の', 'ボタン', 'を', '押す', 'と', '、', 'すごい', '勢い', 'で', '動作', 'し', 'まし', 'た', '。', '嬉しい', 'です', '。']

TrainingDat[ 3 ]: 
 ['リモコン', 'の', 'ボタン', 'を', '入れ', 'て', 'も', '、', '動き', 'ませ', 'ん', 'でし', 'た', '。', 'ダメ', 'な', '製品', 'で', '、', '良く', 'ない', 'です', '。']

TrainingDat[ 4 ]: 
 ['部屋', 'が', '寒く', 'なり', 'すぎ', 'て', '、', '困り', 'まし', 'た', '。', 'ダメ', 'な', '製品', 'だ', 'と', '思い', 'ます', '。']

TrainingDat[ 5 ]: 
 ['たまに', '電源', 'が', 'つか', 'ない', 'ので', '、', '困る', '。', '部屋', 'も', '寒く', 'なり', 'すぎる', 'し', '、', '返品', 'し', 'たい', 'くらい', 'ダメ', 'な', '商品', 'だ', '。']

3. w2vの学習

続いて、分かち書きに変換されたデータを用い、w2vを学習させてみます。

In [253]:
mod = Word2Vec(
    TrainingDat,
    sg=1, #skip-gram
    min_count=1, # 登場回数がこれ未満の単語は、除去
    window = 5, # 前後いくつまでの単語を対象とするか
    iter = 3000, # 学習回数
    alpha = 0.1, # 学習係数(0.1, 0.01あたりを推奨)
    seed = 0 # 乱数シード(自然数を指定、結果の再現性を保証)
)

これで学習が終わりました。このモデルは、どんな単語の入力を受け付けているか、表示してみます。

In [254]:
mod.wv.vocab.keys()
Out[254]:
dict_keys(['すぐ', 'に', '温度', 'が', '下がり', '、', '部屋', '冷たく', 'なり', 'ます', '。', 'この', '商品', 'は', 'とても', '良い', 'と', '思い', 'で', '嬉しい', 'です', '冷たい', '風', '素早く', '発生', 'し', 'の', 'まし', 'た', 'エアコン', 'ボタン', 'を', '押す', 'すごい', '勢い', '動作', 'リモコン', '入れ', 'て', 'も', '動き', 'ませ', 'ん', 'でし', 'ダメ', 'な', '製品', '良く', 'ない', '寒く', 'すぎ', '困り', 'だ', 'たまに', '電源', 'つか', 'ので', '困る', 'すぎる', '返品', 'たい', 'くらい'])

ここに表示されているワード以外は使用できないので、注意が必要です。学習済みのw2vを使用して何か単語を入力したときにエラーが出る場合は、ここにない単語を入力している場合がほとんどです。

4. 単語同士の類似

続いて、学習済みモデルを活用して、2単語間の類似度を算出してみます。

In [255]:
w1 = "返品"
w2 = "ダメ"
dist12 = mod.wv.similarity(w1, w2)
print("[", w1, "] と [", w2, "] の類似度: ", np.round(dist12, 3))

w3 = "嬉しい"
w4 = "ダメ"
dist34 = mod.wv.similarity(w3, w4)
print("[", w3, "] と [", w4, "] の類似度: ", np.round(dist34, 3))

w5 = "嬉しい"
w6 = "良い"
dist56 = mod.wv.similarity(w5, w6)
print("[", w5, "] と [", w6, "] の類似度: ", np.round(dist56, 3))
[ 返品 ] と [ ダメ ] の類似度:  0.308
[ 嬉しい ] と [ ダメ ] の類似度:  -0.097
[ 嬉しい ] と [ 良い ] の類似度:  0.306

同時に発生しそうな用語は類似度が高く、対象的な用語は類似度が低く出ました。他にもいくつか試してもて、類似度をきちんと測れてそうならばokです。もしダメそうなら、

  • w2vの教師データのテキストを増やす。
  • w2vのパラメータを変更してみる(学習回数iterを増やすなど)。

を検討してください。

5. 簡単なアプリケーションの開発

ある口コミが入力されたとき、それがポジティブなものか、ネガティブなものか、当てるシステムを開発してみましょう。アルゴリズムの簡単流れは以下の通りです。

  • (A) 口コミテキストの入力
  • (B) 形態素解析により、分かち書き
  • (C)単語をw2vに入力し、
    • (C-1)「ダメ」「返品」との類似度を算出し、NegativeScoreに加算
    • (C-2)「良い」「嬉しい」との類似度を算出し、PositiveScoreに加算
  • 上記処理を、すべての単語に実施。ただし、w2vが使用できない単語はスキップ
  • (D) NegativeScoreとPositiveScoreを表示
  • (E) 判定結果の出力
In [261]:
# 処理 (A)
input_text = "部屋が寒くなりすぎるし、動かないときもあるし、困ります。"
#input_text = "このエアコンはすぐに部屋が冷える!"
#input_text = "このエアコンは、部屋がよく冷えるけど、たまに動かないから困るよ。"

print("\n *** 解析対象テキスト ****")
print(input_text)

# 処理(B)
wakachi_text = wakachi(input_text)
print("\n *** 形態素解析(分かち書き) ****")
print(wakachi_text)

# 処理(C)
NegativeScore = 0
PositiveScore = 0

# 処理 (C-1)
print("\n *** NegativeScore 算出ログ ****")
NegWord1 = "ダメ"
NegWord2 = "返品"
for i in range(len(wakachi_text)):
    try: # 類似度算出にトライ
        temp_dist1 = mod.wv.similarity(wakachi_text[i], NegWord1)
        temp_dist2 = mod.wv.similarity(wakachi_text[i], NegWord2)
    except Exception as e: # エラーが出る場合
        print("「", wakachi_text[i], "」を扱えないので、スキップします。")
    else: # エラーが出ない場合
        print("「", wakachi_text[i], "」のNegativeScore加算値:", temp_dist1+temp_dist2)
        NegativeScore = NegativeScore + temp_dist1 + temp_dist2
        
# 処理 (C-2)
print("\n *** PositiveScore 算出ログ ****")
PosWord1 = "良い"
PosWord2 = "嬉しい"
for i in range(len(wakachi_text)):
    try: # 類似度算出にトライ
        temp_dist1 = mod.wv.similarity(wakachi_text[i], PosWord1)
        temp_dist2 = mod.wv.similarity(wakachi_text[i], PosWord2)
    except Exception as e: # エラーが出る場合
        print("「", wakachi_text[i], "」を扱えないので、スキップします。")
    else: # エラーが出ない場合
        print("「", wakachi_text[i], "」のPositiveScore加算値:", temp_dist1+temp_dist2)
        PositiveScore = PositiveScore + temp_dist1 + temp_dist2

# 処理 (D)
print("\n *** スコア表示 ***")
print("   NegativeScore = ", np.round(NegativeScore, 3))
print("   PositiveScore = ", np.round(PositiveScore, 3))

# 処理 (E)
print("\n *** 推定結果 ***")
if PositiveScore - NegativeScore >= 0.5: # ポジが0.5以上高いなら
    print("この口コミは、ポジティブだと判定されました。")
elif NegativeScore - PositiveScore >= 0.5: # ネガが0.5以上高いなら
    print("この口コミは、ネガティブだと判定されました。")
else: 
    print("この口コミは、中立だと判定されました。")
 *** 解析対象テキスト ****
部屋が寒くなりすぎるし、動かないときもあるし、困ります。

 *** 形態素解析(分かち書き) ****
['', '部屋', 'が', '寒く', 'なり', 'すぎる', 'し', '、', '動か', 'ない', 'とき', 'も', 'ある', 'し', '、', '困り', 'ます', '。', '']

 *** NegativeScore 算出ログ ****
「  」を扱えないので、スキップします。
「 部屋 」のNegativeScore加算値: 0.28512898
「 が 」のNegativeScore加算値: 0.14241599
「 寒く 」のNegativeScore加算値: 0.9009086
「 なり 」のNegativeScore加算値: 0.5947194
「 すぎる 」のNegativeScore加算値: 1.075419
「 し 」のNegativeScore加算値: 0.36019638
「 、 」のNegativeScore加算値: 0.4640528
「 動か 」を扱えないので、スキップします。
「 ない 」のNegativeScore加算値: 0.8751143
「 とき 」を扱えないので、スキップします。
「 も 」のNegativeScore加算値: 0.8773414
「 ある 」を扱えないので、スキップします。
「 し 」のNegativeScore加算値: 0.36019638
「 、 」のNegativeScore加算値: 0.4640528
「 困り 」のNegativeScore加算値: 0.4172914
「 ます 」のNegativeScore加算値: 0.1880509
「 。 」のNegativeScore加算値: 0.3859151
「  」を扱えないので、スキップします。

 *** PositiveScore 算出ログ ****
「  」を扱えないので、スキップします。
「 部屋 」のPositiveScore加算値: 0.38702887
「 が 」のPositiveScore加算値: 0.4856022
「 寒く 」のPositiveScore加算値: -0.0031087778
「 なり 」のPositiveScore加算値: -0.05457054
「 すぎる 」のPositiveScore加算値: 0.056680374
「 し 」のPositiveScore加算値: 0.4891296
「 、 」のPositiveScore加算値: 0.22027394
「 動か 」を扱えないので、スキップします。
「 ない 」のPositiveScore加算値: 0.09967588
「 とき 」を扱えないので、スキップします。
「 も 」のPositiveScore加算値: -0.05863347
「 ある 」を扱えないので、スキップします。
「 し 」のPositiveScore加算値: 0.4891296
「 、 」のPositiveScore加算値: 0.22027394
「 困り 」のPositiveScore加算値: 0.08255501
「 ます 」のPositiveScore加算値: 0.37960505
「 。 」のPositiveScore加算値: 0.41526577
「  」を扱えないので、スキップします。

 *** スコア表示 ***
   NegativeScore =  7.391
   PositiveScore =  3.209

 *** 推定結果 ***
この口コミは、ネガティブだと判定されました。

6. おわりに

こんな感じで、ポジティブ/ネガティブをうまく判定できました。 このモデルは、

  • 「ダメ」「返品」との類似度
  • 「良い」「嬉しい」との類似度

を利用しています。でも、入力した口コミを見ても、この用語は直接利用されていません。これがw2vの利点で、直接指定した単語が入っていなくても、それらの単語との類似度が高ければ、判定に使うことができるのです。

なお、これはアプリケーションの一例でしかないので、随時、好きなように工夫してください。他、今回は口コミのデータ数が少ないので、限定的な判定しかできませんが、もっとたくさんの口コミを入れてw2vを学習させることで、もっと色々なことができるようになるでしょうね。興味のある方は、ぜひ取り組んでください。