駆け出しエンジニアの作業ノート

駆け出しエンジニアが作業ノート風にまとめるページ(関係無い事もしばしば)

Word2Vecを使ったプレイリスト生成実験

少し日が空いてしまいましたが、学習は継続しています。プレイリスト生成についてですが、Word2Vecを用いたモデル構築を行うことにしました。

 

文章の分かち書きの手法を応用し、Last.fmの書式にならい「アーティスト名 - 曲名」を単語とし、Last.fmの類似度データを使って、類似度が高いアイテムの集合をプレイリストとみなして、学習させることにしました。

 

次のようなコードを書いてみました。

 

 

import psycopg2
import psycopg2.extras
from gensim.models import word2vec

host_name =
port_number =
dbname =
rolename =
passwd =

def track_search():
conn = psycopg2.connect(database=dbname, host=host_name, port=port_number, user=rolename, password=passwd)
cur = conn.cursor()
cur.execute("SELECT DISTINCT artist, song FROM track_data")
track_list = cur.fetchall()
conn.close()
return track_list

def similar_artist_search():
conn = psycopg2.connect(database=dbname, host=host_name, port=port_number, user=rolename, password=passwd)
cur = conn.cursor()
track_list = track_search()
model_dev = []
for i in range(len(track_list)):
track = track_list[i]
print(track)
cur.execute("SELECT to_artist, to_song FROM similar_track WHERE from_artist = %s AND from_song = %s AND match_index > 0.01", track)
similar_track_list = cur.fetchall()
similar_track_list.append(track)
print(similar_track_list)
similar_group = []
for j in range(len(similar_track_list)):
track = similar_track_list[j]
artist = track[0]
song = track[1]
artist_song = artist + " - " + song
print(artist_song)
similar_group.append(artist_song)
print(similar_group)
model_dev.append(similar_group)
print(model_dev)
model = word2vec.Word2Vec(model_dev, size=100, min_count=1, window=5, iter=3)
model.save("similar_song.model")


if __name__ == '__main__':
similar_artist_search()

 

途中中断もあり、まだ終わっていませんがモデル構築が終了次第、結果をお知らせしようと思います。

アイテムベースの協調フィルタリング

以下の本を購入して、協調フィルタリングの勉強をしています。

 

 

集合知プログラミング

集合知プログラミング

 

 

ユーザーベースでの推薦だとユーザー数が増えるほど計算が膨大となる一方で、アイテムベースの協調フィルタリングでは、アイテム間の類似度計算のメンテナンスが大変なぶんレコメンドが容易になると書かれていました。

 

Last.fmに話を置き換えると、既に楽曲の類似度についてはAPIで提供されており、それを使えば良いという方向に話が落ち着きます。ただ、Last.fmというサービス自体が元々イギリス発祥で有る事から、日本でメジャー楽曲でも楽曲間類似度が計算されていない楽曲が多数存在します。そのため、現段階での自分の中での課題は、楽曲間類似度が計算されていない楽曲同士をどのように結び付けるかです。なお、自分が楽曲間類似度をもとにプレイリストを生成するプログラムは以下で公開させて頂いております。

 

github.com

 

ベイズ統計の本を読みながら検討してみます。

 

 

 

MeCabのユーザー辞書作成と反映方法

MeCabを用いていると、特にIT関係の単語は自分の意図しない部分で形態素が分かれてしまうことがあります。そのような場合は、ユーザーが自分で辞書を作成する事が出来ます。以下の記事を参考にしましたが、一部は自分で追加しました。なお、ユーザー辞書の元となるcsvファイルの場所は任意の場所で大丈夫です。

 

qiita.com

MeCabの辞書をカスタマイズする | mwSoft

まず、MeCabのユーザー辞書ですが、これを探すのにとても時間が掛かりました。

 

$ which mecab

/usr/local/bin/mecab

 

 

MeCabはここにあるとばかり思っていましたが、実際は違っていました。

 

 

ls -ltr /usr/local/bin/mecab

lrwxr-xr-x  1 {ユーザー名}  admin  31  2  2 21:09 /usr/local/bin/mecab -> ../Cellar/mecab/0.996/bin/mecab

 

という事でエイリアスが貼ってあり、実際には

 

 

$ /usr/local/Cellar/mecab/0.996/libexec/mecab

 

にありました。これで、一安心して、Qiitaのコマンドを実行しようとしましたが、今度はエイリアス先のMeCabにはシステム辞書が存在していませんでした。辞書ファイルはエイリアス元に存在するという非常にややこしい位置関係となっていました。で、完成したシェルスクリプトは以下のようになりました。

 

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -d /usr/local/lib/mecab/dic/ipadic -u /usr/local/lib/mecab/dic/ipadic/user.dic -f utf-8 -t utf-8 /Users/{user_name}/Documents/mecab_user_dic.csv

 

なお、このようなシェルスクリプトはいちいち打ち込むのが大変なので、「.sh」で保存し、二回目以降はシェルファイルの実行で手続きを簡単にしています。

 

よろしければ、参考にしてみて下さい。なお、作ったユーザー辞書を反映する場合は以下の記事を参考にして下さい。

 

psyduck-take-it-easy.hatenablog.com

 

協調フィルタリングの勉強

本日は協調フィルタリングの勉強を兼ねて、以下の記事のコードを再現実験を行いました。

 

qiita.com

 

ただし、エラーが発生したので、「similarity.py」と「recommend.py」は一部を書き換えて実行しました。

 

# similarity.py
import
math
import dataset


def get_similairty(person1, person2):

## 両者とも見た映画の集合を取る
set_person1 = set(dataset.dataset[person1].keys())
set_person2 = set(dataset.dataset[person2].keys())
set_both = set_person1.intersection(set_person2)

if len(set_both)==0: #共通でみた映画がない場合は類似度を0とする
return 0

list_destance = []

for item in set_both:
# 同じ映画のレビュー点の差の2乗を計算
# この数値が大きいほど「気が合わない」=「似ていない」と定義できる
distance = pow(dataset.dataset[person1][item]-dataset.dataset[person2][item], 2)
list_destance.append(distance)

return 1/(1+math.sqrt(sum(list_destance))) #各映画の気の合わなさの合計の逆比的な指標を返す

if __name__ == '__main__':
get_similairty()

 

 

# recommend.py 
import
dataset
import similarity

def get_recommend(person, top_N):

totals = {}
simSums = {} #推薦度スコアを入れるための箱を作っておく

# 自分以外のユーザのリストを取得してFor文を回す
# -> 各人との類似度、及び各人からの(まだ本人が見てない)映画の推薦スコアを計算するため
list_others = dataset.dataset.keys()
list_others = list(list_others)
list_others.remove(person)

for other in list_others:
# 本人がまだ見たことが無い映画の集合を取得
set_other = set(dataset.dataset[other])
set_person = set(dataset.dataset[person])
set_new_movie = set_other.difference(set_person)

# あるユーザと本人の類似度を計算(simは0~1の数字)
sim = similarity.get_similairty(person, other)

# (本人がまだ見たことがない)映画のリストでFor分を回す
for item in set_new_movie:

# "類似度 x レビュー点数" を推薦度のスコアとして、全ユーザで積算する
totals.setdefault(item,0)
totals[item] += dataset.dataset[other][item]*sim

# またユーザの類似度の積算値をとっておき、これで上記のスコアを除する
simSums.setdefault(item,0)
simSums[item] += sim

rankings = [(total/simSums[item],item) for item,total in totals.items()]
rankings.sort()
rankings.reverse()

result = [i[1] for i in rankings][:top_N]
print(result)

if __name__ == '__main__':
get_recommend('Toby',2)

 

結果は以下の通りとなりました。

 

['The Night Listener', 'Lady in the Water'] 

 

元記事と同じ結果が出力されました。今後はこのコードを使って、last.fmの分析に使えたらと思います。

last.fmを盛り上げたい

音楽系SNSlast.fm」というサービスをご存じでしょうか。私は2年以上利用しています。

www.last.fm

 

日本では、楽曲の聴取履歴の管理とレコメンデーション機能を利用する事が出来ます。また、開発用のAPIが無料で公開されおり、APIの使い方を練習するには非常に最適なコンテンツとなっています。

 

しかし、日本での利用者が少ないせいか、日本の楽曲については「日本国内ではメジャー」とされる楽曲でも、レコメンド結果が出力されないというものもあります。

 

自分でレコメンドエンジンを構築するには、膨大なデータが必要となります。やり方を調べると、この本に詳細なやり方が書かれていました。

 

Sparkによる実践データ解析 ―大規模データのための機械学習事例集

Sparkによる実践データ解析 ―大規模データのための機械学習事例集

 

 

ただ、肝心なデータセットについては既にリンク切れとなっていました。もう少し調べて、ここにたどり着きました。

 

github.com

 

いずれにせよ、自前のPCでは処理しきれない計算量なので、どこかにクラウドを借りないといけないと思います。もう少し検討してみます。

形態素数え上げプログラムの修正

前に書いた記事に載せた、形態素ごとに数え上げるプログラムが早速動かなくなったので、修正します。

 

 

psyduck-take-it-easy.hatenablog.com

 

 

import MeCab
import io
import pandas

def text_analysis():
text = ""
m = MeCab.Tagger("-Ochasen -u /usr/local/lib/mecab/dic/ipadic/user.dic")
sentence = m.parse(text)
sentence = io.StringIO(sentence)
sentence = pandas.read_csv(sentence, sep='\t', header=None)
sentence = sentence[0].value_counts()
print(sentence)

if __name__ == '__main__':
text_analysis()

 

textに解析したい本文を代入するのですが、そのまま入れると以下のようなエラーが発生します。

 

SyntaxError: Non-UTF-8 code starting with '\xe3' in file [ファイル名] on line 6, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

 

現状として、最初に"\xe3"を本文の最初に入れて、

 

text = "\xe3[テキスト本文]"

 

となるようにしてみました。

 

 

Word2Vecによるモデル構築実験

機械学習の第一歩として、Word2Vecによるモデル構築実験を行うことにしました。

 

既にWikipediaを使ったモデル構築例があるので、違う物を使うことにしました。対象はずばり、このブログの本文。

 

もちろん、膨大なデータ量が無いと正確性が出ないのは百も承知なので、1ヶ月ごとの定点観測でどのくらい変わるかを行いたいと思います。

 

まず、はてなブログから本文を取得し、テキストファイルに保存するまでのコード

 

from bs4 import BeautifulSoup
import urllib.request
import re

def blog_scraping():
url = "http://psyduck-take-it-easy.hatenablog.com/entry/2018/02/11/124647"
html = urllib.request.urlopen(url)
soup = BeautifulSoup(html, "html.parser")
div = soup.find("div", class_="entry-content").get_text()
div = re.sub('\n', '', div)
print(div)
f = open("test_model.txt", "a", encoding="utf-8")
f.write(div)
f.write("\n")
f.close()

if __name__ == '__main__':
blog_scraping()

 

次に、取得したテキストを形態素解析し、Word2Vecでモデル構築するコード

import MeCab
from gensim.models import word2vec
import re
import io
import pandas

def model_develop():
bindata = open("test_model.txt", "r").read()
text = re.sub("\n", "", bindata)
text = text.split("。")
tokeData = []
for i in range(len(text)):
try:
sentence = text[i]
m = MeCab.Tagger("-Ochasen")
sentence = m.parse(sentence)
sentence = io.StringIO(sentence)
sentence = pandas.read_csv(sentence, sep='\t', header=None)
sentence = sentence.loc[(sentence[3].str.find("名詞") >= 0)]
texts = list(sentence.iloc[:, 0])
tokeData.append(texts)
except:
print("[]")
print(tokeData)
model = word2vec.Word2Vec(tokeData, size=100, min_count=1, window=5, iter=3)
model.save("test_model.model")

if __name__ == '__main__':
model_develop()

ただし、ユーザー独自の辞書を使用する場合には

 

sentence = text[i]
m = MeCab.Tagger("-Ochasen -u /usr/local/lib/mecab/dic/ipadic/user.dic")
sentence = m.parse(sentence)

 

上記のようにユーザー辞書を定義する必要があります。

最後にモデルを呼び出すコード

 

from gensim.models import word2vec

model = word2vec.Word2Vec.load("test_model.model")
data = model.most_similar(positive=["こと"])
print(data)

 

最初の実行結果は以下の通り

 

[('感想', 0.22741879522800446), ('1', 0.19070367515087128), ('風', 0.18442809581756592), ('手', 0.11609397828578949), ('ノート', 0.11308078467845917), ('開設', 0.09799911081790924), ('機械学習', 0.07675206661224365), ('開発', 0.06593617796897888), ('年', 0.06529303640127182), ('主', 0.0587189644575119)]

 

という感じでした。なお、このコードを書くにあたり以下の記事を参考にしました。

own-search-and-study.xyz

 

ちなみに「こと」を入れた理由ですが、これは形態素ごとに数を数えて最初に結果が出たのがこれでした。

 

※以下のコードが動かなくなったので、修正しました。

 

psyduck-take-it-easy.hatenablog.com

 

 

数え上げに使用したコードは以下の通りです。

 

from bs4 import BeautifulSoup
import urllib.request
import MeCab
import re
import io
import pandas

def blog_scraping():
url = "http://psyduck-take-it-easy.hatenablog.com/entry/2018/02/11/124647"
html = urllib.request.urlopen(url)

soup = BeautifulSoup(html, "html.parser")
div = soup.find("div", class_="entry-content").get_text()
div = re.sub('\n', '', div)
print(div)
return div

def text_analysis():
text = blog_scraping()
m = MeCab.Tagger("-Ochasen -u /usr/local/lib/mecab/dic/ipadic/user.dic")
sentence = m.parse(text)
print(sentence)
sentence = io.StringIO(sentence)
sentence = pandas.read_csv(sentence, sep='\t', header=None)
sentence = sentence[0].value_counts()
print(sentence)

if __name__ == '__main__':
text_analysis()

 

とりあえず、色んなジャンルを書いてみるか