Pairsのコミュニティをword2vecとSVMで分類してみた

eureka, Inc.
Eureka Engineering
Published in
10 min readDec 16, 2017

こんにちは!エンジニアの原田です。

この記事は、eureka Advent Cakendar 2017 16日目の記事です。

昨日はBIチームSotaroくんの Prophetでシンプルなモデルから始めるWeb系データの時系列予測 でした!

今日の記事では、Pairsのテキストデータをコーパスとしてword2vecのモデルを作成し、SVMでコミュニティ分類してみた話をします。

word2vec

word2vecは単語の分散表現を得ることができる手法です。今回はPairsのデータに適用してみたことが主題なので原理の詳細な説明はここでは割愛しますが、ざっくり説明するとある単語の周辺語を推測させる(または周辺語からある単語を推測する)タスクをニューラルネットワークに解かせることで得られる隠れ層の重みを単語の分散表現として利用するものです。

実用上word2vecの便利なところは、”類似した使われ方をする単語”がベクトル空間上で近くなること、そして単語ベクトル同士の加算減算をすることで意味を表現できることが挙げられます。

これらの性質を利用して、まずは単語の分散表現をword2vecで作ったのち、単語のベクトルの総和を取ってコミュニティのベクトルを作成します。

モデル構築

ユーザー個人を特定できない形で抽出した自己紹介文のテキストデータ (1GB程度) を学習させます。

まずはMeCabで分かち書きをします。また、辞書には新語や固有名詞に強い mecab-ipadic-NEologd を用いました。

$ mecab -Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd user_introduction.txt >> user_introduction.owakati

次に、word2vecで学習を行います。

CBOW, 200次元, また他のオプション指定もすべて ./demo-word.sh に記載の値をそのまま使用しています。

$ ./word2vec -train ./user_introduction.owakati -output vectors.bin -cbow 1 -size 200 -window 8 -negative 25 -hs 0 -sample 1e-4 -threads 20 -binary 1 -iter 15

さて、いい感じに学習できたか、Pythonで確かめてみましょう。( Python 3.6.3 を使っています )

gensim.models.keyedvectorsを使ってモデルを読み込み、most_similarメソッドで類似語を表示してみます。(most_similarメソッドは学習した語彙の中で、クエリの単語とのcos類似度が高いものを表示してくれるメソッドです。)

$ ipython> from gensim.models.keyedvectors import KeyedVectors
> model = KeyedVectors.load_word2vec_format('./vectors.bin', binary=True, unicode_errors='ignore')
> model.most_similar('恋愛')
[('恋', 0.5758500099182129),
('奥手', 0.4325772523880005),
('交際', 0.4319232404232025),
('一途', 0.42254775762557983),
('臆病', 0.42047062516212463),
('タイプ', 0.4184700846672058),
('慎重', 0.41724157333374023),
('駆け引き', 0.40409812331199646),
('アタック', 0.40105104446411133),
('付き合う', 0.3981892466545105)]
> model.most_similar('交際')
[('付き合い', 0.8156538009643555),
('つきあい', 0.7542433142662048),
('付きあい', 0.708429217338562),
('交際相手', 0.6600335836410522),
('付合い', 0.6374734044075012),
('付き合える', 0.6279394626617432),
('お相手', 0.6105906367301941),
('婚活', 0.584967315196991),
('結婚相手', 0.5695748925209045),
('つき合い', 0.5656948089599609)]
> model.most_similar('真剣')
[('本気', 0.7479203939437866),
('前提', 0.6736781001091003),
('見据え', 0.603636622428894),
('末長く', 0.5698366761207581),
('年齢的', 0.5632830858230591),
('真面目', 0.5573984980583191),
('まじめ', 0.5476504564285278),
('末永く', 0.5378878116607666),
('きちんと', 0.4986216127872467),
('結婚相手', 0.48136672377586365)]

いくつかPairsっぽさのある単語を投げてみましたが、いい感じに関係ある単語が取れていますね。

さて、次はこのモデルを用いて、コミュニティ名から素性ベクトルを作成します。

コミュニティのベクトル作成

コミュニティ名を形態素解析して、単語のベクトルの総和を取り、素性ベクトルとします。

たとえば 夏フェスに一緒に参加しよう! というコミュニティ名を 夏/フェス/に/一緒/に/参加/しよ/う/! に分解し、

vec(夏フェスに一緒に参加しよう!) 
= vec(夏) + vec(フェス) + ... + vec(しよ) + vec(う) + vec(!)

のようにして計算します。

以下がコミュニティのベクトルを作るコードです。事前に分かち書き済のコミュニティ名テキストファイルを読み込んで、コミュニティのベクトルを作成します。

# -*- coding: utf-8 -*-import numpy
from gensim.models.keyedvectors import KeyedVectors
def main():
model = KeyedVectors.load_word2vec_format('./vectors.bin', binary=True, unicode_errors='ignore')
f = open('./community.owakati', 'r')
for line in f:
community_words = line.rstrip().split(' ')
community_name = ''.join(community_words)
try:
community_vec = text_to_vec(community_words, model)
community_vec = normalize(community_vec)
numpy.savetxt('./vec/' + community_name, community_vec)
except:
pass
def text_to_vec(words, model):
word_vecs = []
for word in words:
try:
word_vecs.append(model[word])
except:
pass
if len(word_vecs) == 0:
return None
text_vec = numpy.zeros(word_vecs[0].shape, dtype = word_vecs[0].dtype)
for word_vec in word_vecs:
text_vec = text_vec + word_vec
return text_vecdef normalize(vec):
return vec / numpy.linalg.norm(vec)
if __name__ == '__main__':
main()

これでコミュニティ毎に素性ベクトルを作成できました。

コミュニティ分類器の作成

さあ、いよいよコミュニティを分類してみます。

それぞれのコミュニティは音楽、生活、地域、映画などの27カテゴリいずれかに属しているので、カテゴリをラベルとして labels リストを作成、さきほどつくった素性ベクトルは features リストに入れます。

そして 9:1 で トレーニングデータ:テストデータ に分割し、トレーニングデータを使って線形SVMで分類器を作成、テストデータを分類器にかけたAccuracy(正解率)を見ています。

from sklearn.cross_validation import train_test_split
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score
# トレーニングデータ:テストデータ を 9:1 に分割
data_train, data_test, label_train, label_test = train_test_split(features, labels, test_size=0.1, random_state=1)
# トレーニングデータから分類器を作成 (Linear SVM)
estimator = LinearSVC(C=1.0)
estimator.fit(data_train, label_train)
# テストデータを分類器に入れる
label_predict = estimator.predict(data_test)
# Accuracy
print accuracy_score(label_test, label_predict)

結果

対象となるデータ数や種類を変えて分類器をつくり、Accuracyをはかりました。

分類対象|カテゴリ数|カテゴリ内容|Accuracy 
2 占い / 学校 0.9577
2 アート / スポーツ 0.8738
4 芸能人&テレビ / 恋愛&結婚 / 映画 / 本&マンガ 0.7468
27 すべて 0.5574

占い / 学校 の2カテゴリ分類はかなりいい結果が出ていたので中身を見てみたところ、それぞれに特徴的な語彙が多かったので分類しやすかったのかと思われます。( 占い,星座の名前,運勢など / 部活,XX学生,教科の名前など )
同じ2カテゴリ分類の アート / スポーツ や、4カテゴリの分類でもそれなりの正解率となっていますが、27カテゴリ全部を対象として正確に分けようとすると、あまり実用的ではない数字になってくるので、カテゴリ分類をこれで完全に自動化するのは難しそうですが、サジェスト機能程度の見せ方なら使えるかもしれませんね。

また、ここから正解率を上げていくためにはグリッドサーチを行って適切なパラメータを決定する、線形SVMではなくRBFカーネルを用いる、素性にテキスト以外(例えば参加者数やサムネイル画像などのデータ)を含める、word2vecではなくfastTextを試してみる、前処理を適切に行う、TF-IDFを使う...など工夫のしどころはたくさん考えられますが、長くなってしまうのでそれはまた別の機会に。

おわりに

Pairsのユーザー自己紹介文をコーパスとしてword2vecのモデルを構築、コミュニティのベクトルを作成してSVMでカテゴリ分類を試みてみました。いかがでしたでしょうか。

明日はBIチーム ミズキさんの記事です!お楽しみに!

参考資料

O'Reilly Japan, word2vecによる自然言語処理

絵で理解するWord2vecの仕組み

--

--

eureka, Inc.
Eureka Engineering

Learn more about how Eureka technology and engineering