Word2Vecを使って類似検索キーワードを発見する

# はじめに
Word2Vecを使って、VALUESの保有するWeb行動ログデータから類似の検索キーワードを発見する
というサービスを作ってみました。

Word2Vecって何？という方はこちらを一読いただくとよいかと思います。
・[絵で理解するWord2vecの仕組み](https://qiita.com/Hironsan/items/11b388575a058dc8a46a)
・[word2vec（Skip-Gram Model）の仕組みを恐らく日本一簡潔にまとめてみたつもり](http://www.randpy.tokyo/entry/word2vec_skip_gram_model)

# Word2Vecのモデル作成

## コーパスの用意
元データとして、VALUESのWeb行動ログデータから検索キーワードを抽出します。
最小単位の文章として成立するように、スペースを含んだ複合キーワードのみを用います。
例えば下記のようなデータとなります。

```text:keyword.txt
ダイエット 炭水化物
ダイエット レシピ
もずく ダイエット
ダイエット 腹 締め付け 効果
：
```
※上記は「ダイエット」の部分一致で例を示していますが、実際は一定のデータ期間中に発生した
　すべての検索キーワードを対象のデータとしています。

## モデルの作成
gensimというPythonのライブラリを使用します。

```python:train.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from gensim.models import word2vec
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

sentences = word2vec.LineSentence('keyword.txt')
model = word2vec.Word2Vec(sentences,
                          sg=1,
                          size=200,
                          min_count=10,
                          sample=0)
model.save('keyword.model')
```

## 類似キーワードの確認
作成したモデルを使って、次のようなコードで類似キーワードを確認できます。

```python:most_similar.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from gensim.models import word2vec

model = word2vec.Word2Vec.load('keyword.model')

results = model.most_similar(positive=['ダイエット'], topn=10)

for result in results:
	print(result[0] + "\t" + "%.3f" % result[1])
```
※上記例では「ダイエット」の類似キーワードを出力しています。

結果は、下記のようになりました。

```text:
$python3 most_similar.py
痩せる	0.850
ダイエット方法	0.824
やせる	0.754
減量	0.748
糖質制限	0.699
太る	0.692
脂肪燃焼	0.689
だいえっと	0.686
ダイエツト	0.681
痩せ	0.679
```
※入力した「ダイエット」と類似度が高いキーワードが出力されています。

# サービスの起動
作成したモデルのファイルサイズが大きい場合、1リクエストごとにモデルファイルを読み込んでいると
オーバーヘッドが大きくなります。
今回、Pythonの比較的簡易なWebフレームワークであるBottleを使ってWebサービス化し、
モデルは起動時に読み込むことで高速にレスポンスを返すことにしました。
サンプルのコードは下記の通りです。

```python:keyword_daemon.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from bottledaemon import daemon_run
from bottle import route, HTTPResponse

@route("/keyword/")
@route("/keyword/<target_word>")
def keyword(target_word=None):
    out = {'success': True, 'errcode': 0, 'errmessage': '', 'result': {'similar_list': []}}
    tartget_list = target_word.split(' ')

    results = model.most_similar(positive=tartget_list, topn=100)
    similar_list = []
    for result in results:
        record = {'keyword': result[0], 'score': '%.3f' % result[1]}
        similar_list.append(record)
    out['result']['similar_list'] = similar_list

    jsonstr = json.dumps(out, ensure_ascii=False)
    response = HTTPResponse(status=200, body=jsonstr)
    response.set_header("Content-Type", "application/json")
    return response

if __name__ == "__main__":
    import sys
    if sys.argv[1] == 'start':
        import json
        from gensim.models import word2vec
        model = word2vec.Word2Vec.load('keyword.model')

    daemon_run(host='0.0.0.0', port=5000)
```


# Wikipediaをコーパスとした時の違い

## Wikipedia
・文字通り辞書・事典としての意味合いが強くワードの網羅性と文章量が豊富で
　ある程度のデータ量を必要とするWord2Vecにおいては使い勝手が非常によい。
　（既にダンプ化されたものが用意されているのも魅力）
・文章として登録されているものなので、MeCabなど形態素による分かち書きが必要になる。
　（形態素のやり方によっては意図通りに単語が分かれないこともたまにある）

## 検索キーワード
・検索キーワードは、人が何かしらの意図を持って（調べたい商品、悩み、事柄など）
　入力しているワードのため、人の行動を知る分野（特にマーケティング分野）と相性が良い。
・複合キーワードの場合、入力時に人が意味を理解してスペースで分けていることが大半なため、
　形態素で分かち書きをする必要がない。
・肌感に合う結果を得るためには、膨大な検索キーワードデータが必要になる。

# 何に使える？

## SEO・リスティングのワード拡充に使える
・新たなユーザーニーズを発見し、SEOやリスティングのワードに活用
・人では想起しづらい専門用語系の類似ワードにも有効

例えば「ダイエット」の類似キーワードを抽出し、それぞれのワードの検索ボリュームと
リスティングの競合性をGoogleのキーワードプランナーで調べて
縦軸に検索ボリューム、横軸に競合性をプロットすると下記のようになります。

![compeplot_20190304_194241_yVWGH.png](https://qiita-image-store.s3.amazonaws.com/0/270785/078b5780-7df8-3abc-ad27-3efc92f00d81.png)

ここから理解できることは、
「ダイエット」はメジャーキーワードであり検索ボリュームはあるが競合性も高く買いづらい...
が、類似ワードで見ると「筋トレ」「糖質制限」「有酸素運動」などがボリュームがあって
比較的競合性が低く買いワードである、と考えることができます。

※キーワードプランナーとの連携はこちらで紹介しています。
[AdWords APIでキーワードプランナーの検索ボリューム・CPC・競合性を取得する](https://qiita.com/zak_y/items/58e07ef042605b113f86)

## コンテンツマーケティングに使える
・コンテンツ企画のアイデア出しに
・LPやクリエイティブのワード選定に
・サイトコンテンツの運用、ターゲティング広告に

例えば「ダイエット」の類似キーワードを抽出し、それぞれのワード検索者の性別年代を調べて
縦軸に平均年齢、横軸の男女をプロットすると下記のようになります。

![attrplot_20190304_194241_yVWGH.png](https://qiita-image-store.s3.amazonaws.com/0/270785/d75e6a3e-3057-0654-adc0-a7063bc26a32.png)

ここから理解できることは、
（当然ながら）「ダイエット」系のワードは女性に多い。
ただ、「バルクアップ」「筋トレ」など男性向きなワードも存在する。
女性の中でも若い人は「産後ダイエット」などでも検索している。
など、どのターゲットに合わせてどのような類似ワードを使えば有用そうか
を俯瞰してみることができます。

## 検索での競合ブランドがわかる
・ブランド名を指定すると検索から見た類似ブランドがわかる
・新規参入カテゴリの市場調査に活用

例えば「シチュー」の類似キーワードを調べると、下記のようなキーワードが抽出できます。

```text:
ビーフシチュー	0.838
ハヤシライス	0.819
ポトフ	0.811
ハッシュドビーフ	0.804
グラタン	0.791
クリームシチュー	0.789
ホワイトシチュー	0.777
キーマカレー	0.773
ラタトゥイユ	0.77
ミネストローネ	0.768
```
例えがあまりよくないかもしれませんが、、、
シチューの類似が「ビーフシチュー」「クリームシチュー」などは想像できますが
「ハヤシライス」「ポトフ」「グラタン」などが競合にあがるとはちょっと想像の範囲を越えますよね。

# 参考にしたサイト
・[絵で理解するWord2vecの仕組み](https://qiita.com/Hironsan/items/11b388575a058dc8a46a)
・[word2vec（Skip-Gram Model）の仕組みを恐らく日本一簡潔にまとめてみたつもり](http://www.randpy.tokyo/entry/word2vec_skip_gram_model)
・[「OK word2vec ! "マジ卍"の意味を教えて」 Pythonでword2vec実践してみた](http://www.randpy.tokyo/entry/python_word2vec)
・[【Python】Word2Vecの使い方](https://qiita.com/kenta1984/items/93b64768494f971edf86)

# おわりに
Word2Vecの魅力を知ってから、これを何か形にしてみたい！という思いがようやく一つの
サービスになりました。
今はまだ社内ツールの位置付けですが、近い将来主力サービスの中にも組み込んでいきたい
と思っています。
まずは今回わかりやすい検索キーワードを元データに取り組んでみましたが、特に言葉でなくとも
閲覧サイトであったり閲覧商品であったり、類似を調べたいニーズはたくさんありそうなので、
今後もWord2Vecを使った面白そうなサービスを開発していきたいと思います。