第3章 類似度に基づく推薦
準備
次のコードを書きなさい。
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)
ユーザプロファイル
ユーザ\(u\)のユーザプロファイル\(\boldsymbol{p}_{u}\)は次式で求められる。
\[\boldsymbol{p}_{u} = \frac{1}{\mid I_{u}^{+} \mid} \sum_{i \in I_{u}^{+}} \boldsymbol{x}_{i}\]ここで、\(I_{u}^{+}\)は対象ユーザ\(u\)が「好き」と評価したアイテム集合であり、\(\boldsymbol{x}_{i}\)はアイテム$i$の特徴ベクトルである。
01 好きなアイテム集合に含まれるアイテムの特徴ベクトルの集合
x
から\(I_{u}^{+}\)に含まれるアイテムの特徴ベクトルの集合をndarray
として生成するコードを書きなさい。
コード
print('x[Iu+] = \n{}'.format(【 問01 】))
結果
x[Iu+] =
[[5. 3.]
[6. 2.]
[4. 1.]]
★
- 整数配列インデキシングを使う。
★★
- リスト内包表記を使う。
02 特徴ベクトルの総和
次式により、\(I_{u}^{+}\)に含まれるアイテムの特徴ベクトルの総和を求めるコードを書きなさい。
\[\sum_{i \in I_{u}^{+}} \boldsymbol{x}_{i}\]コード
print('sum(x[Iu+]) = {}'.format(【 問02 】))
結果
sum(x[Iu+]) = [15. 6.]
★★
- 整数配列インデキシングを使う。
numpy.sum()
を使う。
★★★
- リスト内包表記を使う。
numpy.sum()
を使う。
03 ユーザプロファイル
ユーザ\(u\)のユーザプロファイル\(\boldsymbol{p}_{u}\)をndarray
として求めるコードを書きなさい。得られたndarray
をpu
とすること。
コード
【 問03 】
print('pu = {}'.format(pu))
結果
pu = [5. 2.]
★★
- 整数配列インデキシングを使う。
numpy.sum()
を使う。
★★★
- リスト内包表記を使う。
numpy.sum()
を使う。
コサイン類似度
ユーザプロファイル\(\boldsymbol{p}_{u}\)とアイテム\(i\)の特徴ベクトル\(\boldsymbol{x}_{i}\)のコサイン類似度は次式で定義される。
\[\mathrm{cos}(\boldsymbol{p}_{u}, \boldsymbol{x}_{i}) = \frac{\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}}{\| \boldsymbol{p}_{u} \| \| \boldsymbol{x}_{i} \|}\]ここで、\(\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}\)は二つのベクトル\(\boldsymbol{p}_{u}\)と\(\boldsymbol{x}_{i}\)の内積であり、次式のように表される。
\[\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i} = \sum_{k=1}^{d} p_{u,k} x_{i,k}\]\(d\)はベクトルの次元数である。また、\(\| \boldsymbol{p}_{u} \|\)はベクトル\(\boldsymbol{p}_{u}\)のノルム(大きさ)であり、次式のように表される。
\[\| \boldsymbol{p}_{u} \| = \sqrt{\boldsymbol{p}_{u} \cdot \boldsymbol{p}_{u}} = \sqrt{\sum_{k=1}^{d} p_{u,k}^{2}}\]このコサイン類似度関数を次のコードのとおり定義する。
関数
def cos(pu, xi):
"""
コサイン類似度関数:ユーザプロファイルpuとアイテムiの特徴ベクトルxiのコサイン類似度を返す。
Parameters
----------
pu : ndarray
ユーザuのユーザプロファイル
xi : ndarray
アイテムiの特徴ベクトル
Returns
-------
float
コサイン類似度
"""
【 問04 】
print('num = {}'.format(num))
【 問05 】
print('den_u = {:.3f}'.format(den_u))
【 問06 】
print('den_i = {:.3f}'.format(den_i))
cosine = num / (den_u * den_i)
return cosine
コード
u = 0
i = 7
print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))
u = 0
i = 11
print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))
結果
num = 24.0
den_u = 5.385
den_i = 4.472
cos(p0, x7) = 0.997
num = 34.0
den_u = 5.385
den_i = 8.062
cos(p0, x11) = 0.783
このとき、関数の仕様を満たすように、次の問いに答えなさい。
04 ベクトルの内積
内積\(\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}\)を求めるコードを書きなさい。得られた値をnum
とすること。
★
@
演算子を使う。
★
numpy.dot()
を使う。
★★★
- リスト内包表記を使う。
range()
を使う。numpy.sum()
を使う。
★★★
numpy.sum()
を使う。@
演算子を使わない。numpy.dot()
を使わない。- リスト内包表記を使わない。
05 ユーザプロファイルのノルム
\(\boldsymbol{p}_{u}\)のノルム\(\| \boldsymbol{p}_{u} \|\)を求めるコードを書きなさい。得られた値をden_u
とすること。
★
numpy.linalg.norm()
を使う。
★★
@
演算子を使う。numpy.sqrt()
を使う。
★★★
- リスト内包表記を使う。
range()
を使う。numpy.sum()
を使う。numpy.sqrt()
を使う。
★★★
numpy.sum()
を使う。numpy.sqrt()
を使う。numpy.linalg.norm()
を使わない。@
演算子を使わない。numpy.dot()
を使わない。- リスト内包表記を使わない。
06 特徴ベクトルのノルム
\(\boldsymbol{x}_{i}\)のノルム\(\| \boldsymbol{x}_{i} \|\)を求めるコードを書きなさい。得られた値をden_i
とすること。
★
numpy.linalg.norm()
を使う。
★★
@
演算子を使う。numpy.sqrt()
を使う。
★★★
- リスト内包表記を使う。
range()
を使う。numpy.sum()
を使う。numpy.sqrt()
を使う。
★★★
numpy.sum()
を使う。numpy.sqrt()
を使う。numpy.linalg.norm()
を使わない。@
演算子を使わない。numpy.dot()
を使わない。- リスト内包表記を使わない。
推薦
スコア関数\(\mathrm{score}(u, i)\)はユーザ\(u\)がアイテム\(i\)を好む程度をスコアとして返す関数であり、次式のように定義される。
\[\mathrm{score}(u, i) = \mathrm{cos}(\boldsymbol{p}_{u}, \boldsymbol{x}_{i})\]このスコア関数を次のコードのとおり定義する。
関数
def score(u, i):
"""
スコア関数:ユーザuのアイテムiに対するスコアを返す。
Parameters
----------
u : int
ユーザuのID(ダミー)
i : int
アイテムiのID
Returns
-------
float
スコア
"""
return cos(pu, x[i])
順序付け関数\(\mathrm{order}(u, I)\)は、アイテム集合\(I\)が与えられたとき、ユーザ\(u\)向けの推薦リストを返す関数である。ここでは、スコア上位\(K\)件のアイテム集合を推薦リストとして返すものとする。この順序付け関数を次のコードのとおり定義する。
def order(u, I):
"""
順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
Parameters
----------
u : int
ユーザuのID
I : ndarray
アイテム集合
Returns
-------
list
タプル(アイテムID: スコア)を要素にした推薦リスト
"""
【 問07 】
print('scores = ')
pprint.pprint(scores)
【 問08 】
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))
結果
scores =
{7: 0.9965457582448796,
8: 0.9832820049844603,
9: 0.9656157585206697,
10: 0.8541985556144386,
11: 0.783110847498294,
12: 0.9191450300180578}
rec_list =
7: 0.997
8: 0.983
9: 0.966
このとき、関数の仕様を満たすように、次の問いに答えなさい。
07 各アイテムに対するスコア
引数に渡されたアイテム集合\(I\)について、ユーザ\(u\)の各アイテム\(i \in I\)に対するスコア\(\mathrm{score}(u, i)\)を求め、(i: score(u, i))
をペアとした辞書を生成するコードを書きなさい。生成した辞書をscores
とすること。
★★
for
ループを使う。
★★★
- 辞書内包表記を使う。
08 推薦リスト
scores
内の(i: score(u, i))
のペアをscore(u, i)
の降順にソートし、上位TOP_K
件のリストを生成するコードを書きなさい。得られたリストを辞書に変換したものをrec_list
とすること。
★★★
sorted()
を使う。dict.items()
を使う。lambda
式を使う。- スライシングを使う。