LoginSignup
9
7

More than 5 years have passed since last update.

word2vecの類似単語推測をSQLで行う

Posted at

大きめの自然言語のデータを扱うのに、word2vecがとても便利です。いろいろ使い道があると思いますが、私はよく表記ゆれの吸収のレイヤとして使用します。

実際に活用する場面では、「このキーワードに近い単語」というより、「近い単語をすべて使って集計する◯◯の数字」といったものが出てきます。例えば、「xx という単語と、それに近い単語を含んだページをみたユーザ数」とかです。このリクエストに応えるために、例えば

  1. 事前にDBから自然言語のデータを抜き出して前処理し、word2vecのモデルデータを作成しておく
  2. 単語を受け取って、word2vecで近い単語のリストを取り出す
  3. 2.のリストを使って、DBでユーザ数を集計

といったフローが発生します。2.と3.が何回も発生するのでこの作業の効率化を考えます。DB上で「word2vecで近い単語のリストを取り出す」ができるようになると、SQL叩くだけになるので効率化できそうな気がします。

準備

gensim の Word2Vecを使用して、事前にモデルデータを作成しておきます。また、以下のようなスクリプトでtsvとして出力できるようにしておきます。モデルパラメータ(次元)の変更に強いように、縦持ちでデータを持つことにします。

dump.py
import gensim
from gensim.models.word2vec import Word2Vec

FORMAT="%s\t%d\t%f"

if __name__ == '__main__':
    model = Word2Vec.load(...)
    for word in model.vocab:
        vector = model[word]
        for idx,value in enumerate(vector):
            print (FORMAT % (word, idx, value)).encode('utf-8')

また、モデルを格納するテーブルを以下のように定義します。ここではpostgresを使用することにします。

create table word2vec (
  keyword varchar(30) not null,
  idx int not null,
  value double precision not null
);

一時ファイルを作らず、psqlにパイプでデータを流すと楽チンです。

python ./dump.py | psql -c "\copy word2vec from pstdin"

クエリ

word2vecでは単語間の近さを計算することができます。幾つか種類があると思いますが、ここでは最も基本的なコサイン類似度を使ってみようと思います。

query.sql

with targets as (
  select 'man'::text as keyword
),
config as (
  select
    -- 1キーワードあたりいくつの関連キーワードを取り出すか?
    10 as maximum_num_related_keywords_by_target
),
-- 以降は計算処理
similarity as( -- コサイン類似度の計算
select
  targets.keyword as target_keyword,
  other_kv.keyword as related_keyword,
  sum(target_kv.value * other_kv.value) / sqrt(sum(target_kv.value * target_kv.value) * sum(other_kv.value * other_kv.value)) as cosine_similarity
from
  targets
  inner join word2vec target_kv on targets.keyword = target_kv.keyword
  left outer join word2vec other_kv on target_kv.idx = other_kv.idx
group by
  targets.keyword,
  other_kv.keyword
),
ranked_similarity as ( -- コサイン類似度に基づいた順位付け
select
  target_keyword,
  related_keyword,
  cosine_similarity,
  DENSE_RANK() over (partition by target_keyword order by cosine_similarity desc) as related_keyword_rank
from
  similarity
)
-- 出力
select
  target_keyword,
  related_keyword,
  cosine_similarity
from
  ranked_similarity
  cross join config
where
  related_keyword_rank <= maximum_num_related_keywords_by_target
order by
  target_keyword, cosine_similarity desc

結果

googleの公開している word2vecのサンプル (の、githubのクローン)で使用されているデータ(text8)で動作確認をしてみます。 gensimでは以下のような出力が得られます。

>>> for k,v in model.most_similar(positive=['man']):
...     print k,v
...
woman 0.567330121994
girl 0.547901391983
daredevil 0.504863023758
young 0.481207370758
immortal 0.473287910223
spider 0.47292047739
story 0.444978773594
beast 0.444529414177
himself 0.443396568298
bride 0.440255999565

同じモデルで、query.sqlを実行してみると以下のような出力が得られます。入力した単語(man)が含まれていること以外は、単語の並びやsimilarityの値が同様のものを得られていることがわかります。このクエリで得られた related_keyword を使用して、さらに別のテーブルへjoinして各種集計を行うことができます。

 target_keyword | related_keyword | cosine_similarity
----------------+-----------------+-------------------
 man            | man             |                 1
 man            | woman           | 0.567330055211273
 man            | girl            |  0.54790106993787
 man            | daredevil       | 0.504863090926911
 man            | young           | 0.481207192597222
 man            | immortal        | 0.473287587423905
 man            | spider          | 0.472920298213122
 man            | story           |  0.44497852578098
 man            | beast           | 0.444529328298847
 man            | himself         |  0.44339640098128

まとめ

word2vecの類似単語推測をSQLで行いました。いくつか課題があると思っていて、何かアイデアがあったらコメントいただけるとありがたいです。

  • 含まれている単語数によりますが、そこそこ計算時間がかかります。この記事ではセットアップの手間を省くためpostgresで行いました。ログデータなどを溜め込んでいるビッグデータ対応なサービスや製品だとこのアプローチは有効なように思います。
  • 学習部分は(DBの)外でやるので、できればSQLでも学習できるとさらに楽できそうです。
9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7