推薦システムを作ることで、機械学習を学ぶ。
準備
import numpy as np
np.nan
を使う。np.array
を使う。Du_list = [[5, 3, 1],
[6, 2, 1],
[4, 1, 1],
[8, 5, -1],
[2, 4, -1],
[3, 6, -1],
[7, 6, -1],
[4, 2, np.nan],
[5, 1, np.nan],
[8, 6, np.nan],
[3, 4, np.nan],
[4, 7, np.nan],
[4, 4, np.nan]]
Du = np.array(Du_list)
print(f'Du = \n{Du}')
np.shape
を使う。print(f'Duの形状 = {Du.shape}')
np.shape
を使う。print(f'Duの行数 = {Du.shape[0]}')
np.shape
を使う。print(f'Duの列数 = {Du.shape[1]}')
np.size
を使う。
np.shape[0]*np.shape[1]
でもよい。print(f'Duの全要素数 = {Du.size}')
アイテムは、インデックスと読み替えてもこの場合差し支えなさそう。
I = np.arange(Du.shape[0])
print(f'I = {I}')
numpy行列中にある評価値以外を取り出す。
x = Du[:, :-1]
print(f'x = \n{x}')
i = 0
print(f'x{i} = {x[i]}')
ru = Du[:, -1]
print(f'ru = {ru}')
print(f'ruの形状 = {ru.shape}')
print(f'ruの全要素数 = {ru.size}')
i = 2
j = 5
print(f'ru{i}からru{j-1}までの評価値 = {ru[i:j]}')
ru[::-1]
により、逆順で値を取り出す。
print(f'ruの逆順 = {ru[::-1]}')
ru[i]
を使う。
i = 0
print(f'ru{i} = {ru[i]}')
ここでは、評価値について、その第成分がnp.nan
であるときユーザーがアイテムを未評価であるとして問題を解く。
np.isnan(nparray)
を使う。
print(f'ユーザuが未評価 = {np.isnan(ru)}')
np.isnan(nparray)
に対して、~
をつけてあげればTrue
とFalse
が反転するので題意を満たす。
print(f'ユーザuが評価済み = {~np.isnan(ru)}')
16で見た評価済みのアイテムのみを抽出する。
np.arange()
について、16の結果を使ってインデキシングすることで解決する。
Iu = np.arange(ru.shape[0])[~np.isnan(ru)]
print(f'Iu = {Iu}')
ユーザが評価済みで、なおかつ評価値がであるようなアイテム集合を作る。
np.where(ruの条件, True, False)
を使う。
Iuplus = np.arange(ru.shape[0])[np.where(ru == 1, True, False)]
print(f'Iu+ = {Iuplus}')
np.where()
の条件部分をいじればよい。
Iuminus = np.arange(ru.shape[0])[np.where(ru == -1, True, False)]
print(f'Iu- = {Iuminus}')
np.nan
かどうかでインデキシングする。numpy.setdiff1d()
については、また今度。Iu_not = np.arange(ru.shape[0])[np.isnan(ru)]
print(f'Iu_not = {Iu_not}')
17でやったインデキシングをDu
に直接適用すればできるはず。
DuL = Du[~np.isnan(ru)]
print(f'DuL = \n{DuL}')
np.shape
でDuL
の行数を求めればよい。
print(f'|DuL| = {DuL.shape[0]}')
np.where(ruの条件, True, False)
の長さは13なので、これでインデキシングしたい場合DuL
を対象としてもエラーが発生する。(一敗)
そのため今回はDuL[:, -1]
、つまりDuL
の中でも評価値の部分に対してnp.where
を利用した。
print(f'|DuL+| = {DuL[np.where(DuL[:, -1] == 1, True, False)].shape[0]}')
print(f'|DuL-| = {DuL[np.where(DuL[:, -1] == -1, True, False)].shape[0]}')
21でやったこととほぼ同じことをやればよい。~
を外すだけ。
DuU = Du[np.isnan(ru)]
print(f'DuU = \n{DuU}')
対象をDuU
として、22と同じことを行う。
print(f'|DuU| = {DuU.shape[0]}')
準備
import pprint
import numpy as np
np.set_printoptions(precision=3)
正確に内容をトレースし、np.nan
とnp.array
を使う。
R_list = [[np.nan, 4, 3, 1, 2, np.nan],
[5, 5, 4, np.nan, 3, 3],
[4, np.nan, 5, 3, 2, np.nan],
[np.nan, 3, np.nan, 2, 1, 1],
[2, 1, 2, 4, np.nan, 3]]
R = np.array(R_list)
print(f'R = \n{R}')
第1章で親の顔ほど見たnp.arange(np.shape[0])
を使う。
U = np.arange(R.shape[0])
print(f'U = {U}')
02と同様に、np.arange(np.shape[1])
でなんとかする。
I = np.arange(R.shape[1])
print(f'I = {I}')
公式のヒント的にU
に対してnp.size
を使うらしい。行列ではなくベクトルの場合だとnp.shape
との違いがいまいちわからなかったりするので調べておこうと思う。
print(f'|U| = {U.size}')
04と同じように書く。
print(f'|I| = {I.size}')
とは与えてもらえるので、インデックスを指定して値を取り出す。
u = 0
i = 1
print(f'r{u}{i} = {R[u, i]}')
第1章では同じような状況でnp.size
を使っていた。今回は、np.shape[0]*np.shape[1]
で値を取り出してみる。
print(f'Rの全要素数 = {R.shape[0]*R.shape[1]}')
np.isnan(R)
によりR
の各成分が欠損値かどうかを確認する。今回の場合は観測されているならばTrue
とするようなので別途~
を付け加える必要がある。
print(f'観測値 = \n{~np.isnan(R)}')
np.count.nonzero()
を使えばよいらしい。そのまま真偽値のみで構成された行列に適用すると、True
の数を数えてくれるようだ。
print(f'|R| = {np.count_nonzero(~np.isnan(R))}')
の定義に基づいて計算すればよい。少しコードが冗長になると思ったので、R.shape[index]
ではなくR.size
を使って計算している。
sparsity = (R.size-np.count_nonzero(~np.isnan(R)))/R.size
print(f'sparsity = {sparsity:.3f}')
が与えられるので、R
のu
行目の中で欠損値ではないインデックスのみを返すようにする。
u = 0
print(f'I{u} = {I[~np.isnan(R[u, :])]}')
ポイントは、多分以下だと思う。
ndarray
のリストとしてまとめるという点
Iu = [I[~np.isnan(R[u, :])] for u in range(U.size)]
print('Iu = ')
pprint.pprint(Iu)
とが与えられるので、11のように評価済みアイテムを得てその共通部分をとることで望みのベクトルが得られる。共通部分の取り方は、案内通りnp.intersect1d(ndarray1, ndarray2)
を使えばよい。
u = 0
v = 1
temp_u = Iu[u]
temp_v = Iu[v]
Iuv = np.intersect1d(temp_u, temp_v)
print(f'I{u}{v} = {Iuv}')
が与えられるので、U
から列目がnp.nan
でないインデックスを返すように~np.isnan()
を使う。
i = 0
print(f'U{i} = {U[~np.isnan(R[:, i])]}')
11のコードを参考に12のコードを作ったように、14のコードを修正する。
Ui = [U[~np.isnan(R[:, i])] for i in range(I.size)]
print('Ui = ')
pprint.pprint(Ui)
13と同じノリで作ることができる。
i = 0
j = 4
temp_i = Ui[i]
temp_j = Ui[j]
Uij = np.intersect1d(temp_i, temp_j)
print(f'U{i}{j} = {Uij}')
まずは、何も考えずにR
をnp.mean()
で平均することを考えたい。すると、悲しいことに以下のようになるのでnp.nan
を避ける必要がある。
np.mean(R)
ここで、元ネタの記事にもある通りnp.nanmean()
をすることでnp.nan
を除外した平均を算出できる。
print(f'R全体の平均評価値 = {np.nanmean(R):.3f}')
便利だったnp.nanmean()
もnp.mean()
と同様にaxis
を設定できるのでそれを素直に行うことで結果を得る。この場合axis=0
を指定する必要がある。
ri_mean = np.nanmean(R, axis=0)
print(f'ri_mean = {ri_mean}')
18の、axis=1
版を作ればよい。
ru_mean = np.nanmean(R, axis=1)
print(f'ru_mean = {ru_mean}')
記載がある通り、ndarray.reshape()
を使うことで望みの結果を得る。今回はru_mean
を縦ベクトルのような表示にすることが目標となっていて、reshape(-1, 1)
とするのがいいらしいが紛らわしいと思ってしまった。
print(f'ru_mean = \n{ru_mean.reshape(-1, 1)}')
R - ru_mean
は計算できず、エラーが発生する。ブロードキャストによって計算できそうだと期待してやってみた結果これがでたのでちょっとつらいが内包表記
によってR
との差をとるような計算を試みた。理想的な解答とはかなり乖離していると思われるので、あとで調べてメモっておく。
以下、内包表記を利用した計算。一応値は合っているのだがndarray.reshape()
を使う方法を思いつかなかったのが悲しい。
rubar = np.array([[rm] * I.size for rm in ru_mean])
R2 = R - rubar
print(f'R\' = \n{R2}')
以下、第4章と同時に追記
ru_mean
ではなくru_mean.reshape(-1, 1)
を使えばR
との差が直接計算できるということに後で気づいたのでコードを次のセルに記載する。
R2 = R - ru_mean.reshape(-1, 1)
print(f'R\' = \n{R2}')
準備
import pprint
import numpy as np
# 上位K件
TOP_K = 3
Du = np.array([
[5, 3, +1],
[6, 2, +1],
[4, 1, +1],
[8, 5, -1],
[2, 4, -1],
[3, 6, -1],
[7, 6, -1],
[4, 2, np.nan],
[5, 1, np.nan],
[8, 6, np.nan],
[3, 4, np.nan],
[4, 7, np.nan],
[4, 4, np.nan],
])
I = np.arange(Du.shape[0])
x = Du[:,:-1]
ru = Du[:,-1]
Iu = I[~np.isnan(ru)]
Iup = I[ru==+1]
Iun = I[ru==-1]
Iu_not = np.setdiff1d(I, Iu)
x
は以下の通りであるから、ru
が1
となっているインデックスのみがTrue
になるようなベクトルを使ってあげればよいはず。
x
従って、以下のように書くことで答えを得た。np.where()
を用いることによって、ru
の値が1
のところのみTrue
になるようにしている。
print(f'x[Iu+] = \n{x[np.where(ru == 1, True, False)]}')
これは、01で得たIu+
に対して、axis
を工夫して総和をとればよいはずだ。今回は行方向に和をとるはずだから、axis=0
を使えばいいかもしれない。
print(f'sum(x[Iu+]) = {x[np.where(ru == 1, True, False)].sum(axis=0)}')
02のコードについて、総和ではなく平均をとるように修正すればよい。
pu = x[np.where(ru == 1, True, False)].mean(axis=0)
print(f'pu = {pu}')
ここでは、04から06までの3問を解いて一部コードを与えられた関数cos
を完成させることを考える。
内積num
は、よく用いられる@
かnp.dot()
を利用して計算されるので今回もそれを用いて計算していく。
ユーザプロファイルのノルムden_u
は、np.linalg.norm()
をユーザプロファイルのベクトルに使うことで計算可能なはずだ。引数ord
は何も設定しない場合デフォルトで2
になったような気もするが、一応指定はしておく。
05のコードに対して、np.linalg.norm()
の引数に指定するベクトルをxi
に変更すればよい。
以下では、04から06までの問の答えを用意してcos
関数を作成し、実行結果を確認している。
def cos(pu, xi):
"""
コサイン類似度関数:ユーザプロファイルpuとアイテムiの特徴ベクトルxiのコサイン類似度を返す。
Parameters
----------
pu : ndarray
ユーザuのユーザプロファイル
xi : ndarray
アイテムiの特徴ベクトル
Returns
-------
float
コサイン類似度
"""
num = pu@xi
print(f'num = {num}')
den_u = np.linalg.norm(pu, ord=2)
print('den_u = {:.3f}'.format(den_u))
den_i = np.linalg.norm(xi, ord=2)
print('den_i = {:.3f}'.format(den_i))
cosine = num / (den_u * den_i)
return cosine
u = 0
i = 7
print(f'cos(p{u}, x{i}) = {cos(pu, x[i]):.3f}')
u = 0
i = 11
print(f'cos(p{u}, x{i}) = {cos(pu, x[i]):.3f}')
ここでは、07と08の問題を解いて一部コードを与えられた関数order
を完成させることを考える。
結果から、変数scores
は、以下のような条件を満たす辞書型の変数となる。
np.nan
だったアイテムのインデックス
Iu_not
があるのでこれを用いるこれらを満たすよう、今回は辞書の内包表記を用いて記述する。
単純に辞書型の変数scores
をバリューによって並び替えればよい。引数reverse
をTrue
にすることで降順にする点もポイント。
以下では、07から08までの問の答えを用意してorder
関数を作成し、実行結果を確認している。cos
関数をそのまま実行するとprint
文が実行されてしまうので、cos2
関数を用意してプリントされないようにしている。
def cos2(pu, xi):
"""
コサイン類似度関数:ユーザプロファイルpuとアイテムiの特徴ベクトルxiのコサイン類似度を返す。
Parameters
----------
pu : ndarray
ユーザuのユーザプロファイル
xi : ndarray
アイテムiの特徴ベクトル
Returns
-------
float
コサイン類似度
"""
num = pu@xi
den_u = np.linalg.norm(pu, ord=2)
den_i = np.linalg.norm(xi, ord=2)
cosine = num / (den_u * den_i)
return cosine
def score(u, i):
"""
スコア関数:ユーザuのアイテムiに対するスコアを返す。
Parameters
----------
u : int
ユーザuのID(ダミー)
i : int
アイテムiのID
Returns
-------
float
スコア
"""
return cos2(pu, x[i])
def order(u, I):
"""
順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
Parameters
----------
u : int
ユーザuのID
I : ndarray
アイテム集合
Returns
-------
list
タプル(アイテムID: スコア)を要素にした推薦リスト
"""
scores = {i: score(u, i) for i in I}
print('scores = ')
pprint.pprint(scores)
rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]
return rec_list
u = 0
rec_list = order(u, Iu_not)
print('rec_list = ')
for i, scr in rec_list:
print('{}: {:.3f}'.format(i, scr))
準備
import pprint
import numpy as np
np.set_printoptions(precision=3)
# 上位K件
TOP_K = 3
# 近傍アイテム数
K_ITEMS = 3
# しきい値
THETA = 0
Du = np.array([
[5, 3, +1],
[6, 2, +1],
[4, 1, +1],
[8, 5, -1],
[2, 4, -1],
[3, 6, -1],
[7, 6, -1],
[4, 2, np.nan],
[5, 1, np.nan],
[8, 6, np.nan],
[3, 4, np.nan],
[4, 7, np.nan],
[4, 4, np.nan],
])
I = np.arange(Du.shape[0])
x = Du[:,:-1]
ru = Du[:,-1]
Iu = I[~np.isnan(ru)]
Iup = I[ru==+1]
Iun = I[ru==-1]
Iu_not = np.setdiff1d(I, Iu)
この問題では、一部コードを与えられた関数dist
を完成させる。dist
では2つのベクトルを入力としてそのユークリッド距離を返すものなので、2つのベクトルの差に対してnp.linalg.norm
を使えばよいはずだ。
def dist(xi, xj):
"""
距離関数:アイテムiの特徴ベクトルxiとアイテムjの特徴ベクトルxjのユークリッド距離を返す。
Parameters
----------
xi : ndarray
アイテムiの特徴ベクトル
xj : ndarray
アイテムjの特徴ベクトル
Returns
-------
float
ユークリッド距離
"""
distance = np.linalg.norm(xi-xj, ord = 2)
return distance
i = 7
j = 2
print(f'dist(x{i}, x{j}) = {dist(x[i], x[j]):.3f}')
i = 7
j = 3
print(f'dist(x{i}, x{j}) = {dist(x[i], x[j]):.3f}')
Iu
とIu_not
の両方からアイテムを取り出し、アイテムの持つベクトルの距離をdist
関数を使って求める問題である。とりあえずnp.array()
により2重内包表記の結果を包むことで答えと同じものが返ってくるようにはしたけど、題意的には正解と言いがたいかもしれない。あと、正直D[np.ix_(Iu_not,Iu)]
がよくわかっていない。
D = np.array([[dist(x[i], x[j]) for i in Iu] for j in Iu_not])
print(f'D = \n{D}')
x
の各行xindex
とIu
に含まれる各アイテムiu
の持つベクトルx[iu]
の距離を求めて02と同じようにndarray
に変換し、argsort
によって昇順に番号をつけることで望みの結果を得た。
Ii = np.array([[dist(xindex, x[iu]) for iu in Iu] for xindex in x]).argsort()
print(f'Ii = \n{Ii}')
Iiの結果に対して、スライシングすることで最初のTOP_K
列分だけを抽出する。これを使って評価値が+1
だったアイテムと近いアイテムがどれであったかを確認することができる。各行の持つ数値の中に3以上のものがあったのならば、「嫌い」と評価したアイテムが最近傍の3件に含まれていることを示している。
Ii = Ii[:, :TOP_K]
print(f'Ii = \n{Ii}')
Iu_not
からアイテムをとってきて、保存したIi
からアイテムで行を指定して抽出することを考える。
Ii = {i: Ii[i] for i in Iu_not}
print('Ii = ')
pprint.pprint(Ii)
ここでは、06から08の問題を解いて一部コードを与えられた関数predict1
を完成させる。
06では、Ii[i]
にnp.isin()
を使ったインデキシングをすることで答えを得る。Ii[i]
はアイテム集合のうち、距離が近かったアイテム3件の中に「好き」と評価したアイテムが入っているかどうかを判断するための数値が入っている。
07では、06と同様の方法で答えを得ることができる。
Iip
とIin
に対してnp.size()
を使うことでの近傍アイテム集合中に「好き」と評価したアイテムがいくつあるか、「嫌い」と評価したアイテムがいくつあるかがわかるので、その結果に対して素直にif文を使ってあげればよい。
def predict1(u, i):
"""
予測関数(多数決方式):多数決方式によりユーザuのアイテムiに対する予測評価値を返す。
Parameters
----------
u : int
ユーザuのID(ダミー)
i : int
アイテムiのID
Returns
-------
float
予測評価値
"""
Iip = Ii[i][np.isin(Ii[i], Iup)]
print(f'I{i}+ = {Iip}')
Iin = Ii[i][np.isin(Ii[i], Iun)]
print(f'I{i}- = {Iin}')
if Iip.size > Iin.size:
rui = 1
elif Iip.size < Iin.size:
rui = -1
else:
rui = 0
return rui
u = 0
i = 7
print(f'predict1({u}, {i}) = {predict1(u, i):.3f}')
u = 0
i = 9
print(f'predict1({u}, {i}) = {predict1(u, i):.3f}')
06から08で完成させた関数predict1
を参考に、rui
の部分を平均方式に修正したものを関数predict2
として作成する。
def predict2(u, i):
"""
予測関数(多数決方式):多数決方式によりユーザuのアイテムiに対する予測評価値を返す。
Parameters
----------
u : int
ユーザuのID(ダミー)
i : int
アイテムiのID
Returns
-------
float
予測評価値
"""
Iip = Ii[i][np.isin(Ii[i], Iup)]
Iin = Ii[i][np.isin(Ii[i], Iun)]
rui = (Iip.size - Iin.size)/K_ITEMS
return rui
u = 0
i = 7
print(f'predict2({u}, {i}) = {predict1(u, i):.3f}')
u = 0
i = 9
print(f'predict2({u}, {i}) = {predict1(u, i):.3f}')
ここでは、問10を解いて一部コードを与えられた関数order
を完成させる。そのために、scores
に格納された値score(u, i)
がTHETA
未満になるようなペアを除くための工夫を考える。
問題の指定からscore
はpredict2
関数で作るものとして、scores
の部分をいじる必要がある。ここでは辞書内包表記の中でif文を用いることによってTHETA
未満の値をすべて削除することを考える。
def score(u, i):
"""
スコア関数:ユーザuのアイテムiに対するスコアを返す。
Parameters
----------
u : int
ユーザuのID
i : int
アイテムiのID
Returns
-------
float
スコア
"""
return predict2(u, i)
def order(u, I):
"""
順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
Parameters
----------
u : int
ユーザuのID
I : ndarray
アイテム集合
Returns
-------
list
タプル(アイテムID: スコア)を要素にした推薦リスト
"""
scores = {i: score(u, i) for i in I if score(u, i) >= THETA}
rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]
return rec_list
u = 0
rec_list = order(u, Iu_not)
print('rec_list = ')
for i, scr in rec_list:
print('{}: {:.3f}'.format(i, scr))
準備
import pprint
import numpy as np
np.set_printoptions(precision=3)
# 近傍ユーザ数
K_USERS = 3
# 閾値
THETA = 0
R = np.array([
[np.nan, 4, 3, 1, 2, np.nan],
[5, 5, 4, np.nan, 3, 3 ],
[4, np.nan, 5, 3, 2, np.nan],
[np.nan, 3, np.nan, 2, 1, 1 ],
[2, 1, 2, 4, np.nan, 3 ],
])
U = np.arange(R.shape[0])
I = np.arange(R.shape[1])
Ui = [U[~np.isnan(R)[:,i]] for i in I]
Iu = [I[~np.isnan(R)[u,:]] for u in U]
ru_mean = np.nanmean(R, axis=1)
R2 = R - ru_mean.reshape((ru_mean.size, 1))
以下では、01から03の問題を解いて一部コードを与えられた関数pearson1
を完成させる。
01はピアソンの相関係数の分子を、取得した各ベクトルから計算するというものである。Iuv
は、ユーザやユーザによる評価値が存在する列のみを集めたIu[u]
やIu[v]
の共通項をとることで比較可能なアイテムの評価を取得したものである。これに対し、各ユーザの評価を平均したものを作って差をとり、内積を計算すればよい。
内積を作る際に作ったユーザに関するベクトルをrui
として、それに対してnp.linalg.norm(uri, ord = 2)
を使ってあげればよい。
内積を作る際に作ったユーザに関するベクトルをrvi
として、それに対してnp.linalg.norm(uvi, ord = 2)
を使ってあげればよい。
def pearson1(u, v):
"""
評価値行列Rにおけるユーザuとユーザvのピアソンの相関係数を返す。
Parameters
----------
u : int
ユーザuのID
v : int
ユーザvのID
Returns
-------
float
ピアソンの相関係数
"""
Iuv = np.intersect1d(Iu[u], Iu[v])
# R[u][Iu[u]]とR[v][Iu[v]]のIu[u]とIu[v]の部分をIuvにしてしまい時間を溶かした。
# ユーザの評価はすべて平均に反映する点に注意が必要かも。
rui = R[u][Iuv] - R[u][Iu[u]].mean()
rvi = R[v][Iuv] - R[v][Iu[v]].mean()
num = np.dot(rui, rvi)
print(f'num = {num}')
den_u = np.linalg.norm(rui, ord = 2)
print(f'den_u = {den_u:.3f}')
den_v = np.linalg.norm(rvi, ord = 2)
print(f'den_v = {den_v:.3f}')
prsn = num / (den_u * den_v)
return prsn
u = 0
v = 1
prsn = pearson1(u, v)
print(f'pearson1({u}, {v}) = {prsn:.3f}')
以下では、04から06の問題を解いて一部コードを与えられた関数pearson2
を完成させる。
04は01と同じようにピアソンの相関係数の分子を、取得した各ベクトルから計算するというものである。R
でなくR2
を利用するところが大きな変更点となる。
内積を作る際に作ったユーザに関するベクトルをrui
として、それに対してnp.linalg.norm(uri, ord = 2)
を使ってあげればよい。02と違い、R2
を使う点がポイントとなっている。
03と同様に内積を作る際に作ったユーザに関するベクトルをrvi
として、それに対してnp.linalg.norm(uvi, ord = 2)
を使ってあげればよい。R2
を使う点がポイント。
def pearson2(u, v):
"""
平均中心化評価値行列R2におけるユーザuとユーザvのピアソンの相関係数を返す。
Parameters
----------
u : int
ユーザuのID
v : int
ユーザvのID
Returns
-------
float
ピアソンの相関係数
"""
Iuv = np.intersect1d(Iu[u], Iu[v])
rui = R2[u][Iuv]
rvi = R2[v][Iuv]
num = np.dot(rui, rvi)
print('num = {}'.format(num))
den_u = np.linalg.norm(rui, ord = 2)
print('den_u = {:.3f}'.format(den_u))
den_v = np.linalg.norm(rvi, ord = 2)
print('den_v = {:.3f}'.format(den_v))
prsn = num / (den_u * den_v)
return prsn
u = 0
v = 1
prsn = pearson2(u, v)
print(f'pearson2({u}, {v}) = {prsn:.3f}')
ユーザ対ユーザについてその相関係数をR2
によって計算するという問題。内包表記で2重リストを作って、np.array()
すればよさそう。以下では、それらに加えて関数sim
で利用するのが関数pearson2
だと各種print
が発生する問題があったのでそれを取り除いた関数pearson3
を別途作って対応した。
def pearson3(u, v):
"""
平均中心化評価値行列R2におけるユーザuとユーザvのピアソンの相関係数を返す。
Parameters
----------
u : int
ユーザuのID
v : int
ユーザvのID
Returns
-------
float
ピアソンの相関係数
"""
Iuv = np.intersect1d(Iu[u], Iu[v])
rui = R2[u][Iuv]
rvi = R2[v][Iuv]
num = np.dot(rui, rvi)
den_u = np.linalg.norm(rui, ord = 2)
den_v = np.linalg.norm(rvi, ord = 2)
prsn = num / (den_u * den_v)
return prsn
def sim(u, v):
"""
ユーザ類似度関数:ユーザuとユーザvのユーザ類似度を返す。
Parameters
----------
u : int
ユーザuのID
v : int
ユーザvのID
Returns
-------
float
ユーザ類似度
"""
return pearson3(u, v)
S = np.array([[sim (u, v) for u in U] for v in U])
print(f'S = \n{S}')
以下では、08と09の問題を解いて一部コードを与えられた処理について完成させる。08ではあらかじめユーザの類似度を与えた行列S
から作った辞書が与えられていて、キーはユーザ、バリューはまた別の辞書となっていることがUu
からわかる。
08では、Uu
のバリューの中で各キーに対して格納されている辞書を類似度の順番に並べて上位K_USERS
件のみを残すというものであるため、その辞書のソートを行う。
09ではUu
のバリュー側に格納された辞書について、条件式を使ってそのバリューの値がTHETA
未満のものを除外する。
Uu = {u: {v: S[u,v] for v in U if u!=v} for u in U}
print('Uu = ')
pprint.pprint(Uu)
Uu = {u: dict(sorted(Uu[u].items(), key=lambda x:x[1], reverse=True)[:K_USERS]) for u in Uu.keys()}
print('Uu = ')
pprint.pprint(Uu)
Uu = {u: {v[0]: v[1] for v in Uu[u].items() if v[1] >= THETA} for u in Uu.keys()}
print('Uu = ')
pprint.pprint(Uu)
Uu = {u: np.array(list(Uu[u].keys())) for u in U}
print('Uu = ')
pprint.pprint(Uu)
以下では、10と11の問題を解いて一部コードを与えられた関数predict
について完成させる。
Uui
は、アイテムi
を評価したユーザUi[i]
とユーザu
の近傍にいるユーザUu[u]
の共通点を、np.intersect1d()
を使って求めることで作ることができる。
11では、予測評価値のUui
の大きさが0
でなかった場合に発生する複雑そうな項を作っていく。sim(u, v)
はS[u, v]
であったので、u
に対するUui
をインデックスに指定してあげてndarray
状のベクトルを取得して計算に用いる。についてはR2[v, i]
が相当する部分だったはずなのでv
部分をUui
で指定してあげて計算用のndarray
を取得する。こうして取得したベクトルを、前者のndarray
は分子の内積計算と分母の絶対値をとった和の計算に、後者のndarray
は分子の内積計算に用いればよい。
def predict(u, i):
"""
予測関数:ユーザuのアイテムiに対する予測評価値を返す。
Parameters
----------
u : int
ユーザuのID
i : int
アイテムiのID
Returns
-------
float
ユーザuのアイテムiに対する予測評価値
"""
Uui = np.intersect1d(Uu[u], Ui[i])
print(f'U{u}{i} = {Uui}')
if Uui.size <= 0:
return ru_mean[u]
else:
rui_pred = ru_mean[u] + np.dot(S[u][Uui], R2[:, i][Uui])/np.abs(S[u][Uui]).sum()
return rui_pred
u = 0
i = 0
print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
u = 0
i = 5
print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
行列状のR
に対して、np.nan
の部分のみを予測値predict(u, i)
で埋めてあげればよい。余計なprintが発生する関係で、ここではpredict12
関数を別途用意した上で、2重内包表記の各成分を求めつつ最後にnp.array()
でndarray
としたR3
を作った。
def predict12(u, i):
"""
予測関数:ユーザuのアイテムiに対する予測評価値を返す。
Parameters
----------
u : int
ユーザuのID
i : int
アイテムiのID
Returns
-------
float
ユーザuのアイテムiに対する予測評価値
"""
Uui = np.intersect1d(Uu[u], Ui[i])
if Uui.size <= 0:
return ru_mean[u]
else:
rui_pred = ru_mean[u] + np.dot(S[u][Uui], R2[:, i][Uui])/np.abs(S[u][Uui]).sum()
return rui_pred
R3 = np.array([[predict12(r0, r1) if np.isnan(R[r0, r1]) else R[r0, r1] for r1 in range(R.shape[1])] for r0 in range(R.shape[0])])
print(f'R\'\' = \n{R3}')
準備
import pprint
import numpy as np
np.set_printoptions(precision=3)
# 近傍アイテム数
K_ITEMS = 3
# 閾値
THETA = 0
R = np.array([
[np.nan, 4, 3, 1, 2, np.nan],
[5, 5, 4, np.nan, 3, 3 ],
[4, np.nan, 5, 3, 2, np.nan],
[np.nan, 3, np.nan, 2, 1, 1 ],
[2, 1, 2, 4, np.nan, 3 ],
])
U = np.arange(R.shape[0])
I = np.arange(R.shape[1])
Ui = [U[~np.isnan(R)[:,i]] for i in I]
Iu = [I[~np.isnan(R)[u,:]] for u in U]
ru_mean = np.nanmean(R, axis=1)
R2 = R - ru_mean.reshape((ru_mean.size, 1))
問題01では、一部コードのみ与えられた関数cos
を完成させていく。することとしては、内積をとるためのベクトルをとるためのインデックスはUij
に用意されているのでそれを用いてR
からi
とj
のベクトルを取得することである。
def cos(i, j):
"""
評価値行列Rにおけるアイテムiとアイテムjのコサイン類似度を返す。
Parameters
----------
i : int
アイテムiのID
j : int
アイテムjのID
Returns
-------
float
コサイン類似度
"""
Uij = np.intersect1d(Ui[i], Ui[j])
cosine = np.dot(R[Uij, i], R[Uij, j]) / (np.linalg.norm(R[Uij, i]) * np.linalg.norm(R[Uij, j]))
return cosine
i = 0
j = 4
cosine = cos(i, j)
print(f'cos({i}, {j}) = {cosine:.3f}')
問題02では、一部コードのみ与えられた関数adjusted_cos
を完成させていく。することとしては、問題01で作った部分をR
ではなくR2
を利用する形に書き直せばよい。
def adjusted_cos(i, j):
"""
評価値行列R2におけるアイテムiとアイテムjの調整コサイン類似度を返す。
Parameters
----------
i : int
アイテムiのID
j : int
アイテムjのID
Returns
-------
cosine : float
調整コサイン類似度
"""
Uij = np.intersect1d(Ui[i], Ui[j])
Uij = np.intersect1d(Ui[i], Ui[j])
cosine = np.dot(R2[Uij, i], R2[Uij, j]) / (np.linalg.norm(R2[Uij, i]) * np.linalg.norm(R2[Uij, j]))
return cosine
i = 0
j = 4
cosine = adjusted_cos(i, j)
print(f'adjusted_cos({i}, {j}) = {cosine:.3f}')
問題03では、アイテム間の類似度行列を作成する。2重内包表記の各成分を、与えられた関数sim
による計算によって与えればよい。
def sim(i, j):
"""
アイテム類似度関数:アイテムiとアイテムjのアイテム類似度を返す。
Parameters
----------
i : int
アイテムiのID
j : int
アイテムjのID
Returns
-------
float
アイテム類似度
"""
return adjusted_cos(i, j)
S = np.array([[sim(i, j) for i in I] for j in I])
print('S = \n{}'.format(S))
以下では、04と05の問題を解いて一部コードを与えられた処理について完成させる。04ではあらかじめユーザの類似度を与えた行列S
から作った辞書が与えられていて、キーはユーザ、バリューはまた別の辞書となっていることがIi
からわかる。これに対して、少し複雑だが入れ子になっているほうの辞書のバリューによって並べ替えを行う。
05ではIi
のバリュー側に格納された辞書について、条件式を使ってそのバリューの値がTHETA
未満のものを除外する。
# アイテム-アイテム類似度行列から対象アイテムを除外した辞書
Ii = {i: {j: S[i,j] for j in I if i != j} for i in I}
print('Ii = ')
pprint.pprint(Ii)
Ii = {i[0]: dict(sorted(i[1].items(), key=lambda x:x[1], reverse=True)[:K_USERS]) for i in Ii.items()}
print('Ii = ')
pprint.pprint(Ii)
Ii = {i: {j[0]: j[1] for j in Ii[i].items() if j[1] >= THETA} for i in Ii.keys()}
print('Ii = ')
pprint.pprint(Ii)
# 各アイテムの類似アイテム集合をまとめた辞書
Ii = {i: np.array(list(Ii[i].keys())) for i in I}
print('Ii = ')
pprint.pprint(Ii)
Iiu
は、ユーザu
が評価したアイテムIu[u]
とアイテムの近傍にあるアイテムIi[i]
の共通点を、np.intersect1d()
を使って求めることで作ることができる。
07では、予測評価値のIiu
の大きさが0
でなかった場合に発生する複雑そうな項を作っていく。やっていることは基本的に第5章の11に書いた操作で、対象の変数を変えた以外は同じと考えてもさしつかえないように思う。
def predict(u, i):
"""
予測関数:ユーザuのアイテムiに対する予測評価値を返す。
Parameters
----------
u : int
ユーザuのID
i : int
アイテムiのID
Returns
-------
float
ユーザuのアイテムiに対する予測評価値
"""
Iiu = np.intersect1d(Ii[i], Iu[u])
print(f'I{i}{u} = {Iiu}')
if Iiu.size <= 0:
return ru_mean[u]
else:
rui_pred = ru_mean[u] + np.dot(S[i][Iiu], R2[u, :][Iiu])/np.abs(S[i][Iiu]).sum()
return rui_pred
u = 0
i = 0
print(f'r{u}{i} = {predict(u, i):.3f}')
u = 0
i = 5
print(f'r{u}{i} = {predict(u, i):.3f}')
準備
import numpy as np
import numpy.linalg as LA
np.set_printoptions(precision=3)
# 縮約後の次元数
DIM = 2
Du = np.array([
[5, 3, 3, +1],
[6, 2, 5, +1],
[4, 1, 5, +1],
[8, 5, 9, -1],
[2, 4, 2, -1],
[3, 6, 5, -1],
[7, 6, 8, -1],
[4, 2, 3, np.nan],
[5, 1, 8, np.nan],
[8, 6, 6, np.nan],
[3, 4, 2, np.nan],
[4, 7, 5, np.nan],
[4, 4, 4, np.nan],
])
I = np.arange(Du.shape[0])
x = Du[:,:-1]
ru = Du[:,-1]
結果欄から、x
の行方向つまりaxis=0
について平均をとればよい。
xk_mean = x.mean(axis=0)
print('xk_mean = {}'.format(xk_mean))
01と同様にvarで分散をとればよい。求める値は、数式から見ても不偏分散ではないことに注意。
s2 = x.var(axis=0)
print(f's^2 = {s2}')
01と02の結果を用いて、標準化を行う。numpy
のブロードキャストによって単純に差や商をとる計算が使えるので普通に計算すればよい。一応、np.sqrt()
を使う必要があることに注意。
x2 = (x - xk_mean) / np.sqrt(s2)
print(f'x\' = \n{x2}')
03で計算済みのx2
を用いて共分散を求めるとよい。np.cov
の引数でbias=True
を用いることで不偏分散ではない分散を求めることができる。
k = 0
l = 1
skl = np.cov(x2[:, k], x2[:, l], bias = True)[0, 1]
print(f's{k}{l} = {skl:.3f}')
04でも使ったnp.cov
を使って分散共分散行列を求めればよい。ただ、np.cov
にそのままx2
を入れると行対行の分散共分散行列が作られるのでx2.T
という転置したものを引数に指定する。
S = np.cov(x2.T, bias = True)
print(f'S = \n{S}')
問題の案内にもある通り、np.linalg.eig()
を利用して固有値と固有ベクトルを求める。
lmd, v = LA.eig(S)
print(f'λ = {lmd}')
print(f'v = \n{v}')
固有値が大きい順に並べた場合のインデックスを求める。そのためにnp.argsort()
を用いればよいのだが、今回は降順なのでlmd
を引数に指定する際に符号を逆にしてやる必要がある。
indices = np.argsort(-lmd)
print(f'indices = {indices}')
lmd
のインデキシングにindices
を使ってあげればよい。
lmd = lmd[indices]
print(f'λ = {lmd}')
v
の列方向について、indices
でその順番を指定してあげればよい。
v = v[:, indices]
print(f'v = \n{v}')
v
のDIM
列めまでをスライシングすることによってV
を作ればよい。
V = v[:, :DIM]
print(f'V = \n{V}')
数式の定義から、主成分得点はx2
のi
行めとv
のk
列目の内積で得られるのでその通りに計算する。
i = 0
k = 0
xik3 = np.dot(x2[i, :] ,v[:, k])
print(f'x{i}{k}\'\' = {xik3:.3f}')
@
演算子を使って、x2
とV
の行列計算を行えばよい。
x3 = x2 @ V
print(f'x\'\' = \n{x3}')
固有値の和の中で第主成分がどれだけの大きさを占めるかを、寄与率と呼んでいる。分散共分散行列はその性質上固有値が0未満にはならないので、分母が0
になることもなく安心して割合の計算を行うことができたはず。
# k=0とすると第1主成分になる。おそらくインデックスの都合。
k = 0
pk = lmd[k]/lmd.sum()
print(f'第{k+1}主成分の寄与率 = {pk:.3f}')
13を参考に、pk
の分子の部分をスライスした部分の和としてあげればよい。
k = 2
ck = lmd[:k].sum()/lmd.sum()
print(f'第{k}主成分までの累積寄与率 = {ck:.3f}')
np.hstack()
を使ってx3
とru
を横に結合する。np.hstack()
の引数に()
を入れないといけないことや、ru
にreshape
を使う必要があることに注意。
Du2 = np.hstack((x3, ru.reshape(-1, 1)))
print(f'Du\' = \n{Du2}')
準備
import numpy as np
import numpy.linalg as LA
np.set_printoptions(precision=3)
# 縮約後の次元数
DIM = 2
R = np.array([
[np.nan, 4, 3, 1, 2, np.nan],
[5, 5, 4, np.nan, 3, 3 ],
[4, np.nan, 5, 3, 2, np.nan],
[np.nan, 3, np.nan, 2, 1, 1 ],
[2, 1, 2, 4, np.nan, 3 ],
])
U = np.arange(R.shape[0])
I = np.arange(R.shape[1])
Ui = [U[~np.isnan(R)[:,i]] for i in I]
Iu = [I[~np.isnan(R)[u,:]] for u in U]
ru_mean = np.nanmean(R, axis=1)
R2 = R - ru_mean.reshape((ru_mean.size, 1))
R2
で行方向に平均をとればよい。np.nanmean()
によってnp.nan
の処理を行う必要があることも忘れずに。
ri2_mean = np.nanmean(R2,axis = 0)
print('ri2_mean = {}'.format(ri2_mean))
01と同じようにして分散をとればよい。
s2 = np.nanvar(R2,axis = 0)
print(f's^2 = {s2}')
共分散をとる際に、np.nan
が絡むので今回はnp.cov
ではなくnp.dot
で内積をとる形で値を求める。R2[Uij, i]
でi
列目かつi
列目とj
列目でnp.nan
でない行のみを取得することができるのだが、数式の定義にもあるように平均との差分をとる部分とUij
の要素数で割る部分を忘れずに実装する。
i = 0
j = 1
Uij = np.intersect1d(Ui[i], Ui[j])
sij = np.dot(R2[Uij, i] - np.nanmean(R2[:, i]), R2[Uij, j] - np.nanmean(R2[:, j]))/Uij.size
print(f's{i}{j} = {sij:.3f}')
03で行った処理を自分で関数化してから2重リスト内包表記を使って解いた。関数my_sij
は途中のUij
のサイズが0
の場合S
の値を0
に変更する必要があるためUij.size
も返り値にふくめているが結局関数の中でゼロ割が起こるのでそこで発生したnp.nan
を0
にしてしまうやり方のほうがよさそうだなと思った。
def my_sij(i, j):
Uij = np.intersect1d(Ui[i], Ui[j])
sij = np.dot(R2[Uij, i] - np.nanmean(R2[:, i]), R2[Uij, j] - np.nanmean(R2[:, j]))/Uij.size
return Uij.size, sij
S = np.array([[my_sij(i, j)[1] if my_sij(i, j)[0] else 0 for i in I] for j in I])
print(f'S = \n{S}')
LA.eig()
で計算すればよい。v
の値の符号が逆になってしまっているのだが、これは問題ないはず。
以下のサイトで、符号が逆でも問題ないらしいことを確認している。主成分得点に関するサイトで、下のほうに記載がある。
lmd, v = LA.eig(S)
print(f'λ = {lmd}')
print(f'v = \n{v}')
v
について固有値の大きさ順に列を並び替えた上で、DIM
列までをスライシングすればよい。
indices = np.argsort(-lmd)
V = v[:, indices][:, :DIM]
print(f'V = \n{V}')
第7章の11でもやったように内積を計算すればよいのだが、直接R2[u, :]
やV[:, k]
を計算するとnp.nan
のせいでよくないことに気をつけなければならない。
u = 0
k = 0
puk = np.dot(R2[u, Iu[u]] ,V[Iu[u], k])/Iu[u].size
print(f'p{u}{k} = {puk:.3f}')
第7章の12でもやったように行列計算をすればよいのだが、前問のように直接R2
やV
を計算するとnp.nan
のせいでよくないのでnp.nan
を排除して2重リスト内包表記で求めることになる。今回は関数my_puk
を用意してnp.nan
を排除した上で計算を行う。
def my_puk(u, k):
puk = np.dot(R2[u, Iu[u]], V[Iu[u], k]) / Iu[u].size
return puk
P = np.array([[my_puk(u, k) for k in range(DIM)] for u in U])
print(f'P = \n{P}')
準備
import pprint
import numpy as np
from fractions import Fraction
# 上位K件
TOP_K = 3
# スムージングパラメタ
ALPHA = 1
# クラス数
N = 2
# 各特徴量がとりうる値のユニーク数
M = [2, 2, 2, 2, 2, 2]
# しきい値
THETA = 0.5
Du = np.array([
[1, 0, 0, 0, 1, 0, +1],
[0, 1, 0, 0, 1, 0, +1],
[1, 1, 0, 0, 1, 0, +1],
[1, 0, 0, 1, 1, 0, +1],
[1, 0, 0, 0, 0, 1, +1],
[0, 1, 0, 1, 0, 1, +1],
[0, 0, 1, 0, 1, 0, -1],
[0, 0, 1, 1, 1, 0, -1],
[0, 1, 0, 0, 1, 1, -1],
[0, 0, 1, 0, 0, 1, -1],
[1, 1, 0, 1, 1, 0, np.nan],
[0, 0, 1, 0, 1, 1, np.nan],
[0, 1, 1, 1, 1, 0, np.nan],
])
I = np.arange(Du.shape[0])
x = Du[:,:-1]
ru = Du[:,-1]
Iu = I[~np.isnan(ru)]
Iu_not = np.setdiff1d(I, Iu)
DuL = Du[Iu]
xL = x[Iu]
ruL = ru[Iu]
DuU = Du[Iu_not]
xU = x[Iu_not]
省略
問題01から09では、一部コードのみ与えられた関数P_prior
、P_cond
、P
を完成させていく。
01では、事前確率の分子を求める。説明から、与えられたと同じ評価値を持つアイテムの数を数えればよい。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
np.where(ru == r, 1, 0).sum()
02では、事前確率の分母を求める。説明から、評価値を持つアイテムの数を数えればよい。
# 以下、試しに計算するのに用いたコードとその計算結果
ruL.size
# 問題01と02の結果を利用したもの。スムージングない版。
def P_prior(r):
"""
評価値がrとなる事前確率を返す。
Parameters
----------
r : int
評価値
Returns
-------
Fraction
事前確率
"""
num = np.where(ru == r, 1, 0).sum()
den = ruL.size
prob = Fraction(num, den, _normalize=False)
return prob
r = +1
print(f'P(R={r:+}) = {P_prior(r)}')
r = -1
print(f'P(R={r:+}) = {P_prior(r)}')
03では、与えられた評価値と評価値が一致し、なおかつ特徴量に関する部分が同じアイテムの数を数えることで望みの結果を得ることができる。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
k = 1
i = 2
Du[:, k][(Du[i, k] == Du[:, k])&(ru == r)].size
04では、与えられたに評価値が一致するアイテムの数を数えることで望みの結果を得ることができる。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
ru[ru == r].size
# 問題03と04の結果を利用したもの。スムージングない版。
def P_cond(i, k, r):
"""
評価値がrとなる条件下でアイテムiの特徴量kに関する条件付き確率を返す。
Parameters
----------
i : int
アイテムiのID
k : int
特徴量kのインデックス
r : int
評価値
Returns
-------
Fraction
条件付き確率
"""
num = Du[:, k][(Du[i, k] == Du[:, k])&(ru == r)].size
den = ru[ru == r].size
prob = Fraction(num, den, _normalize=False)
return prob
i = 10
k = 0
r = +1
print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
r = -1
print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
事前確率pp
と、各特徴量に関する条件付き確率の積を書けることで計算すればよい。warningが発生しているが、解決は時間があればまた後でおこなうつもり。
def P(i, r):
"""
アイテムiの評価値がrとなる確率を返す。
Parameters
----------
i : int
アイテムiのID
r : int
評価値
Returns
-------
Fraction
事前確率
list of Fraction
各特徴量に関する条件付き確率
float
好き嫌いの確率
"""
pp = P_prior(r)
pk = [P_cond(i, k, r) for k in range(0, x.shape[1])]
prob = float(pp) * np.array([float(p) for p in pk]).prod()
return pp, pk, prob
i = 10
r = +1
pp, pk, prob = P(i, r)
left = 'P(R={:+}|'.format(r) + ','.join(map(str, map(int, x[i]))) + ')'
right = str(pp) + '×' + '×'.join(map(str, pk))
print(f'{left} = {right} = {prob:.3f}')
r = -1
pp, pk, prob = P(i, r)
left = 'P(R={:+}|'.format(r) + ','.join(map(str, map(int, x[i]))) + ')'
right = str(pp) + '×' + '×'.join(map(str, pk))
print(f'{left} = {right} = {prob:.3f}')
ほぼ問題01と同じように解く。ALPHA
だけは足すようにする。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
np.where(ru == r, 1, 0).sum() + ALPHA
ほぼ問題02と同じように解く。N*ALPHA
だけは足すようにする。
# 以下、試しに計算するのに用いたコードとその計算結果
ruL.size + N * ALPHA
# 問題06と07の結果を利用したもの。スムージングあり版。
def P_prior(r):
"""
評価値がrとなる事前確率を返す。
Parameters
----------
r : int
評価値
Returns
-------
Fraction
事前確率
"""
num = np.where(ru == r, 1, 0).sum() + ALPHA
den = ruL.size + N * ALPHA
prob = Fraction(num, den, _normalize=False)
return prob
ほぼ問題03と同じように解く。ALPHA
だけは足すようにする。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
k = 1
i = 2
Du[:, k][(Du[i, k] == Du[:, k])&(ru == r)].size + ALPHA
ほぼ問題04と同じように解く。ALPHA
とM
の第成分の積だけは忘れず足すようにする。
# 以下、試しに計算するのに用いたコードとその計算結果
r = 1
k = 1
ru[ru == r].size + ALPHA * M[k]
# 問題08と09の結果を利用したもの。スムージングあり版。
def P_cond(i, k, r):
"""
評価値がrとなる条件下でアイテムiの特徴量kに関する条件付き確率を返す。
Parameters
----------
i : int
アイテムiのID
k : int
特徴量kのインデックス
r : int
評価値
Returns
-------
Fraction
条件付き確率
"""
num = Du[:, k][(Du[i, k] == Du[:, k])&(ru == r)].size + ALPHA
den = ru[ru == r].size + ALPHA * M[k]
prob = Fraction(num, den, _normalize=False)
return prob
問題10では、一部コードのみ与えられた関数score
を完成させる。やることとしては問題05でみたP(i, r)
を使って、P(i, +1)[2] / (P(i, +1)[2] + P(i, -1)[2])
を計算してあげればよい。
def score(u, i):
"""
スコア関数:ユーザuのアイテムiに対するスコアを返す。
Parameters
----------
u : int
ユーザuのID(ダミー)
i : int
アイテムiのID
Returns
-------
float
スコア
"""
scr = P(i, +1)[2] / (P(i, +1)[2] + P(i, -1)[2])
return scr
u = 0
scores = {i: score(u, i) for i in Iu_not}
print('scores = ')
pprint.pprint(scores)
scores
の作り方をいじって、if文によりTHETA
よりもscore(u, i)
が小さいものを除去してやる必要がある。
def order(u, I):
"""
順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
Parameters
----------
u : int
ユーザuのID
I : ndarray
アイテム集合
Returns
-------
list
タプル(アイテムID: スコア)を要素にした推薦リスト
"""
scores = {i: score(u, i) for i in I if score(u, i) >= THETA}
rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]
return rec_list
u = 0
rec_list = order(u, Iu_not)
print('rec_list = ')
for i, scr in rec_list:
print('{}: {:.3f}'.format(i, scr))
準備
import pprint
import numpy as np
Du = np.array([
[1, 0, 0, 0, 1, 0, +1],
[0, 1, 0, 0, 1, 0, +1],
[1, 1, 0, 0, 1, 0, +1],
[1, 0, 0, 1, 1, 0, +1],
[1, 0, 0, 0, 0, 1, +1],
[0, 1, 0, 1, 0, 1, +1],
[0, 0, 1, 0, 1, 0, -1],
[0, 0, 1, 1, 1, 0, -1],
[0, 1, 0, 0, 1, 1, -1],
[0, 0, 1, 0, 0, 1, -1],
[1, 1, 0, 1, 1, 0, np.nan],
[0, 0, 1, 0, 1, 1, np.nan],
[0, 1, 1, 1, 1, 0, np.nan],
])
I = np.arange(Du.shape[0])
x = Du[:,:-1]
ru = Du[:,-1]
Iu = I[~np.isnan(ru)]
Iu_not = np.setdiff1d(I, Iu)
DuL = Du[Iu]
xL = x[Iu]
ruL = ru[Iu]
DuU = Du[Iu_not]
xU = x[Iu_not]
問題01から03では、一部コードのみ与えられた関数G
を完成させる。問題01では、評価値を与えられたアイテムの数で「好き」と評価されたアイテムの数を割ってあげてpp
を計算する。
問題01と同じように計算すればよい。
定義通りに計算すればよい。
def G(DL):
"""
訓練データDLのジニ係数を返す。
Parameters
----------
DL : ndarray
訓練データDL
Returns
-------
float
ジニ係数
ただし、DLに事例が含まれていないときは0
"""
if DL.shape[0] == 0:
return 0
r = DL[:,-1]
pp = DL[r == +1].shape[0] / DL.shape[0]
pn = DL[r == -1].shape[0] / DL.shape[0]
gini = 1 - (pp**2 + pn**2)
return gini
print(f'G(DuL) = {G(DuL):.3f}')
問題04から06では、一部コードのみ与えられた関数G_partitioned
とその結果を表示するためのコードを完成させる。問題04では、DuL
から訓練データから特徴量が0
となる行のみを抜き出すような処理を書く。特徴量の列に注目して、インデクシングを行えばよい。
問題05では、DuL
から訓練データから特徴量