2017年5月19日金曜日

機械学習で好きなボカロ曲を探せたらいいなって話

 CourseraのMachineLearningコースの何週目かで出てきたCollaborative filteringってのが、ボカロ曲の好みを機械学習してオヌヌメ曲を提示することができそうだなと思ってたので、復習も兼ねて作ってみました。AndrewNg先生が「長くても24時間でプロトタイプを作れ」みたいなこと仰ってたので、本日の実shおっとこれ以上は言えない。
 ちゃんとしたデータがないので現実的ではないでしょうけど、グーグルアンケートでやれるだけやってみようかな。


 この手の記事の書き方がわからないので、地の文で書き続けていきます。

【0.ライブラリ読み込み】
作った関数はfunctions_cf.pyに入ってます。末尾参照。

import numpy as np
from functions_cf import *
import matplotlib.pyplot as plt

【1.データ作成】
登場する変数
music_size:扱う楽曲数 今回は5とした
sample_size:アンケート回答者 今回は100とした
cover_size:アンケート回答者が知らなかった楽曲数 今回は50

楽曲5曲はそれぞれA,B,C,D,Eと呼ぶことにします。100人の回答者がその5曲に対して順位付けを5-1とつけてもらうことにします(数字が大きいほど好き)。それを100*5の行列scoreとします。
ひとまず次のような設定にしました。
・AとB、CとDは同系統
・AとBが好きな人はCとDが好きではなく、Eはさらに好きではない
・CとDが好きな人は、Eが好きではなく、AとBはさらに好きではない
・Eが好きな人は、CとDは好きではなく、AとBはさらに好きではない
という前提でデータ作り。つまり言い換えれば
・(A,B)が(5,4)or(4,5)な人:(C,D)は(3,2)(2,3)のいずれか、Eは1
・(C,D)が(5,4)or(4,5)な人:(A,B)は(2,1)(1,2)のいずれか、Eは3
・Eが5な人:(A,B)は(2,1)or(1,2)、(C,D)は(4,3)or(3,4)
こんな行列を作るmaking_score関数を作って

score=making_score(sample_size, music_size)

で作ると。
 ところが現実問題、全員が全員アンケートの全曲を知っているとは限らないので、scoreからcover_sizeの数の分だけランダムに0にしたdata、および「dataのうち0でないものを真、0のものを偽」とした行列Rを作ります。こちらもmaking_data関数を作って

data, R=making_data(score, cover_size)

で吐いてもらいました。
なお0が意味するのは評価が0というわけではなく、値がないことを意味する便宜上の0です。学習するときにはこのdataを学習し、scoreを予測することを目標とします。


【2.学習準備】
登場する変数
feature_size:機械学習で学んでもらう特徴の数 今回は3としました
theta:各サンプルの好みの特徴を表す行列(これを学習してもらう)
features:各楽曲の持つ特徴の行列(これも学習してもらう)
learning_rate:学習率
lam:正規化項
iter:学習回数
J_list:誤差関数記録

まず学習する2つを乱数で決めてやる。

features=np.random.randn(feature_size, music_size)
theta=np.random.randn(sample_size, feature_size+1)

featuresは実際に使う時に全部1の列を先頭に追加するので、thetaの余分な1行との内積でバイアスとして使われます。

他初期設定

learning_rate=0.3
lam=0.003
iter=2000
J_list=np.array([])

なおlearning_rateとlamはクロスバリデーションで求めました。クロスバリデーションといっても、データセットの出所は同じ関数ですけど。

【3.学習】
0. iter回繰り返し
1. featuresとthetaからscoreの予測値predictionを出させる
2. 記録用に二乗誤差Jを求める
3. 誤差関数の偏微分delta_theta, delta_featuresを求める
4. thetaの調整
5. featuresの調整
6. 記録用に今回のJをJ_listに追加

てな具合。以下。

for i in range(iter):
prediction=predict(np.r_[np.ones(features.shape[1]).reshape(1, -1), features], theta)
J=cost_func(data, prediction, R, theta, lam)
delta_theta, delta_features=delta_func(data, prediction, np.r_[np.ones(features.shape[1]).reshape(1, -1), features], R, theta, lam)
theta=theta-learning_rate*delta_theta
features=features-learning_rate*delta_features
J_list=np.r_[J_list, J]

【4.結果観測】
matplotlibの使い方はこの3行以外知らない模様。

fig=plt.figure()
plt.plot(np.arange(0, J_list.shape[0], 1), J_list)
plt.show()

sub=prediction-score
result=np.c_[prediction.reshape(-1), data.reshape(-1), sub.reshape(-1)]
np.save('result.npy', result)

二乗誤差をプロットしたのがこんな感じ


ちなみにresult[0:100]についてはこんな感じ。
左から順にprediction, data, prediction-scoreです。

array([[ 1.24964192,  1.        ,  0.24964192],
       [ 1.37293008,  2.        , -0.62706992],
       [ 3.89820064,  4.        , -0.10179936],
       [ 3.65735034,  3.        ,  0.65735034],
       [ 4.47454032,  5.        , -0.52545968],
       [ 1.52484356,  0.        , -0.47515644],
       [ 1.55074509,  1.        ,  0.55074509],
       [ 3.98696449,  5.        , -1.01303551],
       [ 3.97059504,  4.        , -0.02940496],
       [ 3.85918495,  3.        ,  0.85918495],
       [ 1.24810015,  1.        ,  0.24810015],
       [ 1.62459262,  2.        , -0.37540738],
       [ 3.66986787,  4.        , -0.33013213],
       [ 3.54198014,  3.        ,  0.54198014],
       [ 4.79943488,  5.        , -0.20056512],
       [ 1.69793326,  1.        ,  0.69793326],
       [ 1.58617317,  2.        , -0.41382683],
       [ 4.1376663 ,  4.        ,  0.1376663 ],
       [ 4.10344883,  5.        , -0.89655117],
       [ 3.63891658,  3.        ,  0.63891658],
       [ 1.71531344,  1.        ,  0.71531344],
       [ 1.69460638,  2.        , -0.30539362],
       [ 4.02300241,  5.        , -0.97699759],
       [ 3.93272179,  4.        , -0.06727821],
       [ 3.91481096,  3.        ,  0.91481096],
       [ 1.57404885,  1.        ,  0.57404885],
       [ 1.29974807,  2.        , -0.70025193],
       [ 3.43582389,  3.        ,  0.43582389],
       [ 3.5243598 ,  4.        , -0.4756402 ],
       [ 2.27410224,  0.        , -2.72589776],
       [ 1.10700249,  1.        ,  0.10700249],
       [ 1.16714062,  2.        , -0.83285938],
       [ 4.07213299,  4.        ,  0.07213299],
       [ 4.12135044,  3.        ,  1.12135044],
       [ 3.87078936,  5.        , -1.12921064],
       [ 1.68449955,  2.        , -0.31550045],
       [ 1.60428171,  1.        ,  0.60428171],
       [ 4.14871254,  5.        , -0.85128746],
       [ 4.28263841,  4.        ,  0.28263841],
       [ 3.41619002,  3.        ,  0.41619002],
       [ 4.62560833,  4.        ,  0.62560833],
       [ 4.05205221,  5.        , -0.94794779],
       [ 2.55317212,  3.        , -0.44682788],
       [ 2.66680138,  2.        ,  0.66680138],
       [ 0.83417602,  1.        , -0.16582398],
       [ 4.58030892,  4.        ,  0.58030892],
       [ 4.0197366 ,  5.        , -0.9802634 ],
       [ 2.57726657,  3.        , -0.42273343],
       [ 2.79122591,  2.        ,  0.79122591],
       [ 0.69624294,  1.        , -0.30375706],
       [ 1.20624761,  2.        , -0.79375239],
       [ 1.10612152,  1.        ,  0.10612152],
       [ 4.20693688,  3.        ,  1.20693688],
       [ 4.23614686,  4.        ,  0.23614686],
       [ 3.56631529,  5.        , -1.43368471],
       [ 1.73888174,  2.        , -0.26111826],
       [ 1.64337964,  1.        ,  0.64337964],
       [ 4.12220661,  5.        , -0.87779339],
       [ 4.14964689,  4.        ,  0.14964689],
       [ 3.55789362,  3.        ,  0.55789362],
       [ 1.13147051,  1.        ,  0.13147051],
       [ 1.32433383,  2.        , -0.67566617],
       [ 3.94218514,  3.        ,  0.94218514],
       [ 4.01280332,  4.        ,  0.01280332],
       [ 4.10728622,  5.        , -0.89271378],
       [ 1.28113118,  2.        , -0.71886882],
       [ 1.63935595,  1.        ,  0.63935595],
       [ 3.71823625,  3.        ,  0.71823625],
       [ 3.71832546,  4.        , -0.28167454],
       [ 4.5450203 ,  5.        , -0.4549797 ],
       [ 1.77791569,  2.        , -0.22208431],
       [ 1.66842039,  1.        ,  0.66842039],
       [ 4.11773873,  4.        ,  0.11773873],
       [ 4.09306547,  5.        , -0.90693453],
       [ 3.61683769,  3.        ,  0.61683769],
       [ 1.47290005,  1.        ,  0.47290005],
       [ 1.50925726,  2.        , -0.49074274],
       [ 3.92312391,  0.        ,  0.92312391],
       [ 3.9062157 ,  4.        , -0.0937843 ],
       [ 3.82848362,  0.        , -1.17151638],
       [ 1.46103827,  1.        ,  0.46103827],
       [ 1.58404067,  2.        , -0.41595933],
       [ 3.50582218,  0.        , -1.49417782],
       [ 3.73732263,  4.        , -0.26267737],
       [ 3.22673005,  3.        ,  0.22673005],
       [ 1.72104299,  2.        , -0.27895701],
       [ 1.838129  ,  1.        ,  0.838129  ],
       [ 3.95118648,  4.        , -0.04881352],
       [ 4.11197255,  5.        , -0.88802745],
       [ 3.77340792,  3.        ,  0.77340792],
       [ 1.34400899,  2.        , -0.65599101],
       [ 1.40548812,  1.        ,  0.40548812],
       [ 3.93117907,  4.        , -0.06882093],
       [ 3.67388799,  3.        ,  0.67388799],
       [ 4.36215808,  5.        , -0.63784192],
       [ 2.19485857,  0.        ,  1.19485857],
       [ 1.96226504,  2.        , -0.03773496],
       [ 4.32091318,  4.        ,  0.32091318],
       [ 4.46082193,  5.        , -0.53917807],
       [ 3.15809731,  3.        ,  0.15809731]])

【全体像】
貼っておきます。誤差関数と偏微分あたりがエッセンスかと思われるので、その辺に間違いがないかチェックしてもらえると助かります。

main.py
# coding:utf-8

import numpy as np
from functions_cf import *
import matplotlib.pyplot as plt

sample_size=100
music_size=5
feature_size=3
cover_size=np.int(sample_size*music_size/10)

score=make_score(sample_size, music_size)
data, R=make_data(score, cover_size)

features=np.random.randn(feature_size, music_size)
theta=np.random.randn(sample_size, feature_size+1)

learning_rate=0.3
lam=0.003
iter=2000
J_list=np.array([])

for i in range(iter):
prediction=predict(np.r_[np.ones(features.shape[1]).reshape(1, -1), features], theta)
J=cost_func(data, prediction, R, theta, lam)
delta_theta, delta_features=delta_func(data, prediction, np.r_[np.ones(features.shape[1]).reshape(1, -1), features], R, theta, lam)
theta=theta-learning_rate*delta_theta
features=features-learning_rate*delta_features
J_list=np.r_[J_list, J]
if i%500==0:
print(J)

fig=plt.figure()
plt.plot(np.arange(0, J_list.shape[0], 1), J_list)
plt.show()

sub=prediction-score
result=np.c_[prediction.reshape(-1), data.reshape(-1), sub.reshape(-1)]
np.load('result.npy', result)

functions_cf.py
import numpy as np

def predict(features, theta):
out=np.dot(theta, features)
return out

def cost_func(data, prediction, R, theta, lam):
sub=prediction-data
J=np.sum(sub*sub*R)/(np.sum(R)*2)+np.sum(theta*theta)/(np.sum(R)*2)*lam
return J

def delta_func(data, prediction, features, R, theta, lam):
delta_theta=np.dot((prediction-data)*R, features.T)/np.sum(R)
delta_theta[:, 1:]=delta_theta[:, 1:]+theta[:, 1:]*lam/np.sum(R)
delta_features=np.dot(theta.T, (prediction-data)*R)/np.sum(R)
delta_features=delta_features+features*lam
return delta_theta, delta_features[1:, :]

def make_score(s, m):
score=np.zeros(s*m).reshape(s, m)
mask=np.random.randint(0, 3, s)

for i in range(mask.shape[0]):
if mask[i]==0:
temp=np.random.randn(1)
if temp>=0:
score[i, 0]=5
score[i, 1]=4
else:
score[i, 0]=4
score[i, 1]=5
temp=np.random.randn(1)
if temp>=0:
score[i, 2]=3
score[i, 3]=2
else:
score[i, 2]=2
score[i, 3]=3
score[i, 4]=1

elif mask[i]==1:
temp=np.random.randn(1)
if temp>=0:
score[i, 0]=1
score[i, 1]=2
else:
score[i, 0]=2
score[i, 1]=1
temp=np.random.randn(1)
if temp>=0:
score[i, 2]=5
score[i, 3]=4
else:
score[i, 2]=4
score[i, 3]=5
score[i, 4]=3

else:
temp=np.random.randn(1)
if temp>=0:
score[i, 0]=1
score[i, 1]=2
else:
score[i, 0]=2
score[i, 1]=1
temp=np.random.randn(1)
if temp>=0:
score[i, 2]=3
score[i, 3]=4
else:
score[i, 2]=4
score[i, 3]=3
score[i, 4]=5

return score

def make_data(score, cover_size):
data=score.copy()
data=data.reshape(-1)
mask=np.random.choice(data.size, cover_size)
data[mask]=0
data=data.reshape(score.shape)
R=(data!=0)
return data, R

PS
ガチの機械学習の人に見られたらボコボコにされそう。

0 件のコメント:

コメントを投稿