├── google82cfde3f0924b03a.html
├── _config.yml
├── _includes
└── header.html
├── _layouts
└── default.html
├── README.md
├── ja
├── chap12.md
├── chap11.md
├── chap08.md
├── chap03.md
├── chap10.md
├── chap13.md
├── chap04.md
├── chap06.md
├── chap07.md
├── chap09.md
├── chap02.md
├── chap01.md
└── chap05.md
└── src
├── chap12.ipynb
├── chap11.ipynb
├── chap03.ipynb
├── chap08.ipynb
├── chap02.ipynb
└── chap06.ipynb
/google82cfde3f0924b03a.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google82cfde3f0924b03a.html
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
2 | google_analytics: G-78BW75TFPQ
3 | plugins:
4 | - jekyll-sitemap
5 |
6 |
--------------------------------------------------------------------------------
/_includes/header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 | Home
15 |
16 |
--------------------------------------------------------------------------------
/_layouts/default.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% seo %}
11 | {% include head-custom.html %}
12 |
13 |
14 |
15 |
16 |
17 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | {% include header.html %}
2 |
3 | # recsys-python | Pythonによる推薦システム演習問題集
4 |
5 | ## 概要
6 | recsys-pythonはPythonによる推薦システムの演習問題集です。推薦システムの基本である、評価履歴や評価値行列の扱いから、内容ベース推薦システム、近傍ベース協調フィルタリング、推薦システムの評価などに関する問題を取り揃えています。現時点では、**13章構成**で**全163問**用意しています。今後、内容を変更したり、問題を追加、削除したりすることもあります。
7 |
8 | ## 動機
9 | [言語処理100本ノック](https://nlp100.github.io/ja/)を参考にさせて頂き、推薦システム版の演習問題集を開発したいと思ったのがきっかけです。個人での学習に加え、大学での授業や研究室等でご活用いただければ幸いです。
10 |
11 | ## 到達目標
12 |
13 | - Pythonを学習しながら推薦システムの基本を修得できる。
14 | - 評価履歴や評価値行列を扱いながらNumPyによる行列演算やベクトル演算を修得できる。
15 | - 数式をPythonでコーディングする方法を修得できる。
16 |
17 | ## 取り組み方
18 | 下記の目次から各章の問題に取り組んでください。
19 |
20 | 1. 各問題に書かれている指示文にしたがってコードを記述してください。
21 | 2. 各問題には**コード**と**結果**が記載されています。大問にまとめて記載されている場合もあります。
22 | 3. **コード**中には`【 問01 】`のように空欄があります。**結果**に記載のとおりの内容が出力されるように、この空欄に入る適切なコードを記述してください。
23 |
24 | ###### 要件について
25 |
26 | - 各問題には要件が記載されています。この要件をすべて満たすようにコードを記述してください。
27 | - 要件の難易度が★の数で示されています。
28 | - この要件はヒントにもなります。考えたり調べたりする際の手がかりにしてください。
29 | - 問題によっては複数の要件パターンが記載されているものもあります。この場合はいずれかの要件パターンを満たすので十分です。
30 | - Pythonは自由度が高い言語で、コードの書き方は幾通りもあります。そのため、要件を設定することで、ある程度の縛りを課しています。ただし、Pythonに慣れている方は、要件に縛られず独自の書き方で記述されるのも良いでしょう。作成者が想定しているよりももっと良い書き方があるかもしれません。
31 |
32 | ## 目次
33 |
34 | ### 評価履歴と評価値行列
35 |
36 | - [第1章 評価履歴](ja/chap01.md)
37 | - [第2章 評価値行列](ja/chap02.md)
38 |
39 | ### 内容ベース推薦システム(近傍ベース方式)
40 |
41 | - [第3章 類似度に基づく推薦](ja/chap03.md)
42 | - [第4章 k近傍法](ja/chap04.md)
43 |
44 | ### 近傍ベース協調フィルタリング
45 |
46 | - [第5章 ユーザベース協調フィルタリング](ja/chap05.md)
47 | - [第6章 アイテムベース協調フィルタリング](ja/chap06.md)
48 |
49 | ### 次元削減
50 | - [第7章 評価履歴の次元削減](ja/chap07.md)
51 | - [第8章 評価値行列の次元削減](ja/chap08.md)
52 |
53 | ### 内容ベース推薦システム(モデルベース方式)
54 |
55 | - [第9章 単純ベイズ分類器](ja/chap09.md)
56 | - [第10章 決定木](ja/chap10.md)
57 |
58 | ### 推薦システムの評価
59 |
60 | - [第11章 嗜好予測の正確性](ja/chap11.md)
61 | - [第12章 好き嫌い分類に基づく評価指標](ja/chap12.md)
62 | - [第13章 推薦順位に基づく正確性](ja/chap13.md)
63 |
64 | ## 参考
65 |
66 | PythonおよびNumPyについては、下記の公式チュートリアルが参考になります。
67 | - [Python チュートリアル](https://docs.python.org/ja/3.9/tutorial/)
68 | - [Quickstart tutorial — NumPy Manual](https://docs.scipy.org/doc/numpy/user/quickstart.html)
69 |
70 | 演習問題に含まれる数式等については、下記定の書籍にて解説しています。
71 | - 奥健太,**『基礎から学ぶ推薦システム ~情報技術で嗜好を予測する~』**,コロナ社,2022.
72 | - ※Pythonコードは本書には含まれておりません。
73 |
74 | ## 作成者
75 |
76 | 龍谷大学 [推薦システム研究室](https://recsyslab.org/) 奥 健太
77 |
--------------------------------------------------------------------------------
/ja/chap12.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第12章 好き嫌い分類に基づく評価指標 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第12章 好き嫌い分類に基づく評価指標
9 |
10 | ## テストデータと推薦リスト
11 | 次の評価値行列$$\boldsymbol{R}^{\mathit{test}}$$はテストデータである。$$\boldsymbol{R}$$の$$(u, i)$$成分はユーザ$$u$$がアイテム$$i$$に与えた評価値$$r_{u,i}$$を表す。ただし、$$-$$で示した要素はテストデータの対象ではないことを表す。また、$$\boldsymbol{R}^{\mathit{test}}$$に含まれる成分の集合を$$R^{\mathit{test}}$$と表す。
12 |
13 | $$
14 | \boldsymbol{R}^{\mathit{test}} = \left[
15 | \begin{array}{rrrrrrrrrr}
16 | 5 & 4 & 3 & - & 5 & 4 & 2 & 2 & - & - \\
17 | \end{array}
18 | \right]
19 | $$
20 |
21 | 次の行列$$\hat{\boldsymbol{R}}^{A}$$、$$\hat{\boldsymbol{R}}^{B}$$は、それぞれ推薦システムA、推薦システムBによる推薦リストである。$$\hat{\boldsymbol{R}}^{A}$$、$$\hat{\boldsymbol{R}}^{B}$$の$$(u, i)$$成分は、それぞれユーザ$$u$$向けの推薦システムA、推薦システムBによる推薦リストにおける順位を表す。
22 |
23 | $$
24 | \hat{\boldsymbol{R}}^{A} = \left[
25 | \begin{array}{rrrrrrrrrr}
26 | 1 & 6 & 3 & - & 4 & 2 & 5 & 7 & - & - \\
27 | \end{array}
28 | \right]
29 | $$
30 |
31 | $$
32 | \hat{\boldsymbol{R}}^{B} = \left[
33 | \begin{array}{rrrrrrrrrr}
34 | 4 & 3 & 1 & - & 6 & 7 & 2 & 5 & - & - \\
35 | \end{array}
36 | \right]
37 | $$
38 |
39 | ## 準備
40 | 次のコードを書きなさい。
41 |
42 | ```python
43 | import numpy as np
44 |
45 | # テストデータ
46 | R = np.array([
47 | [5, 4, 3, np.nan, 5, 4, 2, 2, np.nan, np.nan],
48 | ])
49 | U = np.arange(R.shape[0])
50 | I = np.arange(R.shape[1])
51 | Iu = [I[~np.isnan(R)[u,:]] for u in U]
52 |
53 | # 推薦システムAによる推薦リスト
54 | RA = np.array([
55 | [1, 6, 3, np.nan, 4, 2, 5, 7, np.nan, np.nan],
56 | ])
57 |
58 | # 推薦システムBによる推薦リスト
59 | RB = np.array([
60 | [4, 3, 1, np.nan, 6, 7, 2, 5, np.nan, np.nan],
61 | ])
62 | ```
63 |
64 | ## 混同行列
65 | 次の表は混同行列である。
66 |
67 | | | 推薦された | 推薦されなかった |
68 | | ---- | --------- | ----------------|
69 | | 好き | 好きなアイテムが推薦された数(TP) | 好きなアイテムが推薦されなかった数(FN) |
70 | | 嫌い | 嫌いなアイテムが推薦された数(FP) | 嫌いなアイテムが推薦されなかった数(TN) |
71 |
72 | ここでは、評価値が4以上のアイテムを好きなアイテムとみなす。次の関数は、ユーザ`u`向け推薦リスト`RS`の上位`K`件における混同行列の各値を返す関数である。
73 |
74 | ###### 関数
75 |
76 | ```python
77 | def confusion_matrix(u, RS, K):
78 | """
79 | ユーザu向け推薦リストRSの上位K件における混同行列の各値を返す。
80 |
81 | Parameters
82 | ----------
83 | u : int
84 | ユーザuのID
85 | RS : ndarray
86 | 推薦リストRS
87 | K : int
88 | 上位K件
89 |
90 | Returns
91 | -------
92 | int
93 | TP
94 | int
95 | FN
96 | int
97 | FP
98 | int
99 | TN
100 | """
101 | 【 問01 】
102 | print('like = {}'.format(like))
103 | 【 問02 】
104 | print('recommended@{} = {}'.format(K, recommended))
105 | 【 問03 】
106 | print('TP@{} = {}'.format(K, TP))
107 | 【 問04 】
108 | print('FN@{} = {}'.format(K, FN))
109 | 【 問05 】
110 | print('FP@{} = {}'.format(K, FP))
111 | 【 問06 】
112 | print('TN@{} = {}'.format(K, TN))
113 | return TP, FN, FP, TN
114 | ```
115 |
116 | ###### コード
117 |
118 | ```python
119 | u = 0
120 | K = 3
121 | TP, FN, FP, TN = confusion_matrix(u, RA, K)
122 | print('混同行列 = \n{}'.format(np.array([[TP, FN], [FP, TN]])))
123 | ```
124 |
125 | ###### 結果
126 |
127 | ```bash
128 | like = [ True True False True True False False]
129 | recommended@3 = [ True False True False True False False]
130 | TP@3 = 2
131 | FN@3 = 2
132 | FP@3 = 1
133 | TN@3 = 2
134 | 混同行列 =
135 | [[2 2]
136 | [1 2]]
137 | ```
138 |
139 | このとき、次の問いに答えなさい。
140 |
141 | ### 01 好きなアイテムか否かの判定
142 | `R`において、ユーザ`u`が評価済みのアイテム集合を対象に評価値が4以上の要素には`True`を、4未満の要素には`False`を入れたブール値配列を生成するコードを書きなさい。得られたブール値配列を`like`とすること。
143 |
144 | ★★
145 | 1. 整数配列インデキシングを使う。
146 |
147 | ### 02 推薦されたアイテムか否かの判定
148 | 推薦リスト`RS`において順位が`K`以内の要素には`True`を、それ以外の要素には`False`を入れたブール値配列を生成するコードを書きなさい。得られたブール値配列を`recommended`とすること。
149 |
150 | ★★
151 | 1. 整数配列インデキシングを使う。
152 |
153 | ### 03 好きなアイテムが推薦された数(TP)
154 | 上位$$K$$件の推薦リストにおいて、好きなアイテムが推薦された数$$\mathit{TP}@K$$を取得するコードを書きなさい。得られた値を`TP`とすること。
155 |
156 | ★★
157 | 1. `numpy.logical_and()`を使う。
158 | 2. `numpy.count_nonzero()`を使う。
159 |
160 | ### 04 好きなアイテムが推薦されなかった数(FN)
161 | 上位$$K$$件の推薦リストにおいて、好きなアイテムが推薦されなかった数$$\mathit{FN}@K$$を取得するコードを書きなさい。得られた値を`FN`とすること。
162 |
163 | ★★
164 | 1. `numpy.logical_and()`を使う。
165 | 2. `numpy.count_nonzero()`を使う。
166 |
167 | ### 05 嫌いなアイテムが推薦された数(FP)
168 | 上位$$K$$件の推薦リストにおいて、嫌いなアイテムが推薦された数$$\mathit{FP}@K$$を取得するコードを書きなさい。得られた値を`FP`とすること。
169 |
170 | ★★
171 | 1. `numpy.logical_and()`を使う。
172 | 2. `numpy.count_nonzero()`を使う。
173 |
174 | ### 06 嫌いなアイテムが推薦されなかった数(TN)
175 | 上位$$K$$件の推薦リストにおいて、嫌いなアイテムが推薦されなかった数$$\mathit{TN}@K$$を取得するコードを書きなさい。得られた値を`TN`とすること。
176 |
177 | ★★
178 | 1. `numpy.logical_and()`を使う。
179 | 2. `numpy.count_nonzero()`を使う。
180 |
181 | ## 真陽性率と偽陽性率
182 | 上位$$K$$件の推薦リストにおける真陽性率$$\mathit{TPR}@K$$、偽陽性率$$\mathit{FPR}@K$$は、それぞれ次式で定義される。
183 |
184 | $$
185 | \mathit{TPR}@K = \frac{\mathit{TP}@K}{\mathit{TP}@K + \mathit{FN}@K}
186 | $$
187 |
188 | $$
189 | \mathit{FPR}@K = \frac{\mathit{FP}@K}{\mathit{FP}@K + \mathit{TN}@K}
190 | $$
191 |
192 | ###### コード
193 |
194 | ```python
195 | 【 問07 】
196 | print('TPR@{} = {:.3f}'.format(K, TPR))
197 | 【 問08 】
198 | print('FPR@{} = {:.3f}'.format(K, FPR))
199 | ```
200 |
201 | ###### 結果
202 |
203 | ```bash
204 | TPR@3 = 0.500
205 | FPR@3 = 0.333
206 | ```
207 |
208 | このとき、次の問いに答えなさい。
209 |
210 | ### 07 真陽性率(TPR)
211 | 上位$$K$$件の推薦リストにおける真陽性率$$\mathit{TPR}@K$$を求めるコードを書きなさい。得られた値を`TPR`とすること。
212 |
213 | ★
214 | 1. `TP`を参照する。
215 | 2. `FN`を参照する。
216 |
217 | ### 08 偽陽性率(FPR)
218 | 上位$$K$$件の推薦リストにおける偽陽性率$$\mathit{FPR}@K$$を求めるコードを書きなさい。得られた値を`TPR`とすること。
219 |
220 | ★
221 | 1. `FP`を参照する。
222 | 2. `TN`を参照する。
223 |
224 | ## 適合率と再現率
225 | 上位$$K$$件の推薦リストにおける適合率$$\mathit{precision}@K$$、再現率$$\mathit{recall}@K$$は、それぞれ次式で定義される。
226 |
227 | $$
228 | \mathit{precision}@K = \frac{\mathit{TP}@K}{\mathit{TP}@K + \mathit{FP}@K}
229 | $$
230 |
231 | $$
232 | \mathit{recall}@K = \frac{\mathit{TP}@K}{\mathit{TP}@K + \mathit{FN}@K}
233 | $$
234 |
235 | また、上位$$K$$件の推薦リストにおけるF値$$F_{1}@K$$は、次式で定義される。
236 |
237 | $$
238 | F_{1}@K = \frac{2 \cdot \mathit{precision}@K \cdot \mathit{recall}@K}{\mathit{precision}@K + \mathit{recall}@K}
239 | $$
240 |
241 | ###### コード
242 |
243 | ```python
244 | 【 問09 】
245 | print('precision@{} = {:.3f}'.format(K, precision))
246 | 【 問10 】
247 | print('recall@{} = {:.3f}'.format(K, recall))
248 | 【 問11 】
249 | print('F1@{} = {:.3f}'.format(K, F1))
250 | ```
251 |
252 | ###### 結果
253 |
254 | ```bash
255 | precision@3 = 0.667
256 | recall@3 = 0.500
257 | F1@3 = 0.571
258 | ```
259 |
260 | このとき、次の問いに答えなさい。
261 |
262 | ### 09 適合率
263 | 上位$$K$$件の推薦リストにおける適合率$$\mathit{precision}@K$$を求めるコードを書きなさい。得られた値を`precision`とすること。
264 |
265 | ★
266 | 1. `TP`を参照する。
267 | 2. `FP`を参照する。
268 |
269 | ### 10 再現率
270 | 上位$$K$$件の推薦リストにおける再現率$$\mathit{recall}@K$$を求めるコードを書きなさい。得られた値を`recall`とすること。
271 |
272 | ★
273 | 1. `TP`を参照する。
274 | 2. `FN`を参照する。
275 |
276 | ### 11 F値
277 | 上位$$K$$件の推薦リストにおけるF値$$F_{1}@K$$を求めるコードを書きなさい。得られた値を`F1`とすること。
278 |
279 | ★
280 | 1. `precision`を参照する。
281 | 2. `recall`を参照する。
282 |
--------------------------------------------------------------------------------
/ja/chap11.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第11章 嗜好予測の正確性 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第11章 嗜好予測の正確性
9 |
10 | ## テストデータと予測評価値
11 | 次の評価値行列$$\boldsymbol{R}^{\mathit{test}}$$はテストデータである。$$\boldsymbol{R}$$の$$(u, i)$$成分はユーザ$$u$$がアイテム$$i$$に与えた評価値$$r_{u,i}$$を表す。ただし、$$-$$で示した要素はテストデータの対象ではないことを表す。また、$$\boldsymbol{R}^{\mathit{test}}$$に含まれる成分の集合を$$R^{\mathit{test}}$$と表す。
12 |
13 | $$
14 | \boldsymbol{R}^{\mathit{test}} = \left[
15 | \begin{array}{rrrrrrrrrr}
16 | - & 4 & - & - & - & - & 2 & - & - & - \\
17 | - & - & - & - & 2 & - & - & - & 5 & - \\
18 | - & - & - & - & - & - & - & 3 & - & - \\
19 | \end{array}
20 | \right]
21 | $$
22 |
23 | 次の評価値行列$$\hat{\boldsymbol{R}}^{A}$$、$$\hat{\boldsymbol{R}}^{B}$$は、それぞれ推薦システムA、推薦システムBによる予測評価値である。$$\hat{\boldsymbol{R}}^{A}$$、$$\hat{\boldsymbol{R}}^{B}$$の$$(u, i)$$成分は、それぞれ推薦システムA、推薦システムBによる予測評価値$$\hat{r}_{u,i}$$を表す。
24 |
25 | $$
26 | \hat{\boldsymbol{R}}^{A} = \left[
27 | \begin{array}{rrrrrrrrrr}
28 | - & 2 & - & - & - & - & 2 & - & - & - \\
29 | - & - & - & - & 2 & - & - & - & 3 & - \\
30 | - & - & - & - & - & - & - & 3 & - & - \\
31 | \end{array}
32 | \right]
33 | $$
34 |
35 | $$
36 | \hat{\boldsymbol{R}}^{B} = \left[
37 | \begin{array}{rrrrrrrrrr}
38 | - & 3 & - & - & - & - & 1 & - & - & - \\
39 | - & - & - & - & 3 & - & - & - & 4 & - \\
40 | - & - & - & - & - & - & - & 4 & - & - \\
41 | \end{array}
42 | \right]
43 | $$
44 |
45 | ## 準備
46 | 次のコードを書きなさい。
47 |
48 | ```python
49 | import numpy as np
50 |
51 | # とりうる評価値の最大値
52 | R_MAX = 5
53 | # とりうる評価値の最小値
54 | R_MIN = 1
55 |
56 | # テストデータ
57 | R = np.array([
58 | [np.nan, 4, np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan],
59 | [np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan, 5, np.nan],
60 | [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan],
61 | ])
62 | U = np.arange(R.shape[0])
63 | I = np.arange(R.shape[1])
64 |
65 | # 推薦システムAによる予測評価値
66 | RA = np.array([
67 | [np.nan, 2, np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan],
68 | [np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan, 3, np.nan],
69 | [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan],
70 | ])
71 |
72 | # 推薦システムBによる予測評価値
73 | RB = np.array([
74 | [np.nan, 3, np.nan, np.nan, np.nan, np.nan, 1, np.nan, np.nan, np.nan],
75 | [np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan, np.nan, 4, np.nan],
76 | [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 4, np.nan, np.nan],
77 | ])
78 | ```
79 |
80 | ## 平均絶対誤差
81 | 平均絶対誤差$$\mathit{MAE}$$は次式で定義される。
82 |
83 | $$
84 | \mathit{MAE} = \frac{\sum_{(u,i) \in R^{\mathit{test}}} \mid \hat{r}_{u,i} - r_{u,i} \mid}{\mid R^{\mathit{test}} \mid}
85 | $$
86 |
87 | ###### コード
88 |
89 | ```python
90 | 【 問01 】
91 | print('MAE_{} = {:.3f}'.format('A', MAE_A))
92 | 【 問02 】
93 | print('MAE_{} = {:.3f}'.format('B', MAE_B))
94 | ```
95 |
96 | ###### 結果
97 |
98 | ```bash
99 | MAE_A = 0.800
100 | MAE_B = 1.000
101 | ```
102 |
103 | このとき、次の問いに答えなさい。
104 |
105 | ### 01 推薦システムAのMAE
106 | 推薦システムAの$$\mathit{MAE}^{A}$$を求めるコードを書きなさい。得られた値を`MAE_A`とすること。
107 |
108 | ★★★
109 | 1. 二重のリスト内包表記を使う。
110 | 2. `numpy.abs()`を使う。
111 | 3. `numpy.count_nonzero()`を使う。
112 | 4. `numpy.isnan()`を使う。
113 | 5. `~`演算子を使う。
114 | 6. `numpy.nansum()`を使う。
115 |
116 |
117 | ### 02 推薦システムBのMAE
118 | 推薦システムBの$$\mathit{MAE}^{B}$$を求めるコードを書きなさい。得られた値を`MAE_B`とすること。
119 |
120 | ★★★
121 | 1. 二重のリスト内包表記を使う。
122 | 2. `numpy.abs()`を使う。
123 | 3. `numpy.count_nonzero()`を使う。
124 | 4. `numpy.isnan()`を使う。
125 | 5. `~`演算子を使う。
126 | 6. `numpy.nansum()`を使う。
127 |
128 | ## 平均二乗誤差
129 | 平均二乗誤差$$\mathit{MSE}$$は次式で定義される。
130 |
131 | $$
132 | \mathit{MSE} = \frac{\sum_{(u,i) \in R^{\mathit{test}}} (\hat{r}_{u,i} - r_{u,i})^{2}}{\mid R^{\mathit{test}} \mid}
133 | $$
134 |
135 | ###### コード
136 |
137 | ```python
138 | 【 問03 】
139 | print('MSE_{} = {:.3f}'.format('A', MSE_A))
140 | 【 問04 】
141 | print('MSE_{} = {:.3f}'.format('B', MSE_B))
142 | ```
143 |
144 | ###### 結果
145 |
146 | ```bash
147 | MSE_A = 1.600
148 | MSE_B = 1.000
149 | ```
150 |
151 | このとき、次の問いに答えなさい。
152 |
153 | ### 03 推薦システムAのMSE
154 | 推薦システムAの$$\mathit{MSE}^{A}$$を求めるコードを書きなさい。得られた値を`MSE_A`とすること。
155 |
156 | ★★★
157 | 1. 二重のリスト内包表記を使う。
158 | 2. `numpy.count_nonzero()`を使う。
159 | 3. `numpy.isnan()`を使う。
160 | 4. `~`演算子を使う。
161 | 5. `numpy.nansum()`を使う。
162 |
163 | ### 04 推薦システムBのMSE
164 | 推薦システムAの$$\mathit{MSE}^{B}$$を求めるコードを書きなさい。得られた値を`MSE_B`とすること。
165 |
166 | ★★★
167 | 1. 二重のリスト内包表記を使う。
168 | 2. `numpy.count_nonzero()`を使う。
169 | 3. `numpy.isnan()`を使う。
170 | 4. `~`演算子を使う。
171 | 5. `numpy.nansum()`を使う。
172 |
173 | ## 二乗平均平方根誤差
174 | 二乗平均平方根誤差$$\mathit{RMSE}$$は次式で定義される。
175 |
176 | $$
177 | \mathit{RMSE} = \sqrt{\frac{\sum_{(u,i) \in R^{\mathit{test}}} (\hat{r}_{u,i} - r_{u,i})^{2}}{\mid R^{\mathit{test}} \mid}}
178 | $$
179 |
180 | ###### コード
181 |
182 | ```python
183 | 【 問05 】
184 | print('RMSE_{} = {:.3f}'.format('A', RMSE_A))
185 | 【 問06 】
186 | print('RMSE_{} = {:.3f}'.format('B', RMSE_B))
187 | ```
188 |
189 | ###### 結果
190 |
191 | ```bash
192 | RMSE_A = 1.265
193 | RMSE_B = 1.000
194 | ```
195 |
196 | このとき、次の問いに答えなさい。
197 |
198 | ### 05 推薦システムAのRMSE
199 | 推薦システムAの$$\mathit{RMSE}^{A}$$を求めるコードを書きなさい。得られた値を`RMSE_A`とすること。
200 |
201 | ★
202 | 1. `MSE_A`を参照する。
203 | 2. `numpy.sqrt()`を使う。
204 |
205 | ★★★
206 | 1. 二重のリスト内包表記を使う。
207 | 2. `numpy.count_nonzero()`を使う。
208 | 3. `numpy.isnan()`を使う。
209 | 4. `~`演算子を使う。
210 | 5. `numpy.nansum()`を使う。
211 | 6. `numpy.sqrt()`を使う。
212 |
213 | ### 06 推薦システムBのRMSE
214 | 推薦システムBの$$\mathit{RMSE}^{B}$$を求めるコードを書きなさい。得られた値を`RMSE_B`とすること。
215 |
216 | ★
217 | 1. `MSE_B`を参照する。
218 | 2. `numpy.sqrt()`を使う。
219 |
220 | ★★★
221 | 1. 二重のリスト内包表記を使う。
222 | 2. `numpy.count_nonzero()`を使う。
223 | 3. `numpy.isnan()`を使う。
224 | 4. `~`演算子を使う。
225 | 5. `numpy.nansum()`を使う。
226 | 6. `numpy.sqrt()`を使う。
227 |
228 | ## 正規化MAEと正規化RMSE
229 | 正規化MAE$$\mathit{NMAE}$$と正規化RMSE$$\mathit{NRMSE}$$は、それぞれ次式で定義される。
230 |
231 | $$
232 | \mathit{NMAE} = \frac{\mathit{MAE}}{r_{\mathit{max}} - r_{\mathit{min}}}
233 | $$
234 |
235 | $$
236 | \mathit{NRMSE} = \frac{\mathit{RMSE}}{r_{\mathit{max}} - r_{\mathit{min}}}
237 | $$
238 |
239 | ここで、$$r_{\mathit{max}}$$、$$r_{\mathit{min}}$$は、それぞれ、とりうる評価値の最大値、最小値を表す。
240 |
241 | ###### コード
242 |
243 | ```python
244 | # NMAE
245 | 【 問07 】
246 | print('NMAE_{} = {:.3f}'.format('A', NMAE_A))
247 | 【 問08 】
248 | print('NMAE_{} = {:.3f}'.format('B', NMAE_B))
249 |
250 | # NRMSE
251 | 【 問09 】
252 | print('NRMSE_{} = {:.3f}'.format('A', NRMSE_A))
253 | 【 問10 】
254 | print('NRMSE_{} = {:.3f}'.format('B', NRMSE_B))
255 | ```
256 |
257 | ###### 結果
258 |
259 | ```bash
260 | NMAE_A = 0.200
261 | NMAE_B = 0.250
262 | NRMSE_A = 0.316
263 | NRMSE_B = 0.250
264 | ```
265 |
266 | このとき、次の問いに答えなさい。
267 |
268 | ### 07 推薦システムAのNMAE
269 | 推薦システムAの$$\mathit{NMAE}^{A}$$を求めるコードを書きなさい。ただし、$$r_{\mathit{max}}$$、$$r_{\mathit{min}}$$は、それぞれ`R_MAX`、`R_MIN`とする。得られた値を`NMAE_A`とすること。
270 |
271 | ★
272 | 1. `MAE_A`を参照する。
273 |
274 | ### 08 推薦システムBのNMAE
275 | 推薦システムAの$$\mathit{NMAE}^{B}$$を求めるコードを書きなさい。ただし、$$r_{\mathit{max}}$$、$$r_{\mathit{min}}$$は、それぞれ`R_MAX`、`R_MIN`とする。得られた値を`NMAE_B`とすること。
276 |
277 | ★
278 | 1. `MAE_B`を参照する。
279 |
280 | ### 09 推薦システムAのNRMSE
281 | 推薦システムAの$$\mathit{NRMSE}^{A}$$を求めるコードを書きなさい。ただし、$$r_{\mathit{max}}$$、$$r_{\mathit{min}}$$は、それぞれ`R_MAX`、`R_MIN`とする。得られた値を`NRMSE_A`とすること。
282 |
283 | ★
284 | 1. `RMSE_A`を参照する。
285 |
286 | ### 10 推薦システムBのNRMSE
287 | 推薦システムBの$$\mathit{NRMSE}^{B}$$を求めるコードを書きなさい。ただし、$$r_{\mathit{max}}$$、$$r_{\mathit{min}}$$は、それぞれ`R_MAX`、`R_MIN`とする。得られた値を`NRMSE_B`とすること。
288 |
289 | ★
290 | 1. `RMSE_B`を参照する。
291 |
--------------------------------------------------------------------------------
/ja/chap08.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第8章 評価値行列の次元削減 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第8章 評価値行列の次元削減
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import numpy as np
15 | import numpy.linalg as LA
16 | np.set_printoptions(precision=3)
17 |
18 | # 縮約後の次元数
19 | DIM = 2
20 |
21 | R = np.array([
22 | [np.nan, 4, 3, 1, 2, np.nan],
23 | [5, 5, 4, np.nan, 3, 3 ],
24 | [4, np.nan, 5, 3, 2, np.nan],
25 | [np.nan, 3, np.nan, 2, 1, 1 ],
26 | [2, 1, 2, 4, np.nan, 3 ],
27 | ])
28 | U = np.arange(R.shape[0])
29 | I = np.arange(R.shape[1])
30 | Ui = [U[~np.isnan(R)[:,i]] for i in I]
31 | Iu = [I[~np.isnan(R)[u,:]] for u in U]
32 | ru_mean = np.nanmean(R, axis=1)
33 | R2 = R - ru_mean.reshape((ru_mean.size, 1))
34 | ```
35 |
36 | ## 分散共分散行列
37 |
38 | アイテム$$i$$の分散$$s_{i}^{2}$$は次式で求められる。
39 |
40 | $$
41 | s_{i}^{2} = \frac{1}{\mid U_{i} \mid} \sum_{u \in U_{i}} (r_{u,i}^{'} - \overline{r}_{i}^{'})^{2}
42 | $$
43 |
44 | ここで、$$\overline{r}_{i}$$はアイテム$$i$$に対して与えられた平均中心化評価値の平均値であり、次式で求められる。
45 |
46 | $$
47 | \overline{r}_{i}^{'} = \frac{1}{\mid U_{i} \mid} \sum_{u \in U_{i}} r_{u,i}^{'}
48 | $$
49 |
50 | アイテム$$i$$とアイテム$$j$$の共分散$$s_{i,k}$$は次式で求められる。
51 |
52 | $$
53 | s_{i,j} =
54 | \begin{cases}
55 | \frac{1}{\mid U_{i,j} \mid} \sum_{u \in U_{i,j}} (r_{u,i}^{'} - \overline{r}_{i}^{'}) (r_{u,j}^{'} - \overline{r}_{j}^{'}) & (U_{i,j} \neq \emptyset) \\
56 | 0 & (U_{i,j} = \emptyset)
57 | \end{cases}
58 | $$
59 |
60 | ここで、$$U_{i,j}$$はアイテム$$i$$とアイテム$$j$$の両方のアイテムに評価値を与えているユーザ集合である。
61 |
62 | 各アイテムについて求めた分散、共分散をまとめると、次式のように分散共分散行列$$\boldsymbol{S}$$が得られる。
63 |
64 | $$
65 | \boldsymbol{S} = \left[
66 | \begin{array}{rrrrrr}
67 | 0.336 & 0.893 & 0.169 & -0.659 & -0.057 & -0.572 \\
68 | 0.893 & 1.348 & 0.505 & -1.466 & 0.166 & -0.817 \\
69 | 0.169 & 0.505 & 0.505 & -0.655 & -0.183 & -0.270 \\
70 | -0.659 & -1.466 & -0.655 & 1.279 & -0.109 & 0.752 \\
71 | -0.057 & 0.166 & -0.183 & -0.109 & 0.137 & -0.015 \\
72 | -0.572 & -0.817 & -0.270 & 0.752 & -0.015 & 0.494
73 | \end{array}
74 | \right]
75 | $$
76 |
77 | このとき、次の問いに答えなさい。
78 |
79 | ### 01 各アイテムに対して与えられた平均中心化評価値の平均値
80 | `R2`において各アイテムに対して与えられた平均中心化評価値の平均値$$\overline{r}_{i}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`ri2_mean`とすること。
81 |
82 | ###### コード
83 |
84 | ```python
85 | 【 問01 】
86 | print('ri2_mean = {}'.format(ri2_mean))
87 | ```
88 |
89 | ###### 結果
90 |
91 | ```bash
92 | ri2_mean = [ 0.367 0.588 0.4 -0.037 -0.938 -0.383]
93 | ```
94 |
95 | ★
96 | 1. `numpy.nanmean()`を使う。
97 |
98 | ★★★
99 | 1. 二重のリスト内包表記を使う。
100 | 2. numpy.sum()を使う。
101 | 3. numpy.array()を使う。
102 | 4. numpy.nanmean()を使わない。
103 |
104 | ### 02 各アイテムの平均中心化評価値の分散
105 | `R2`において各特徴量の平均中心化評価値の分散$$s_{i}^{2}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`s2`とすること。
106 |
107 | ###### コード
108 |
109 | ```python
110 | 【 問02 】
111 | print('s^2 = {}'.format(s2))
112 | ```
113 |
114 | ###### 結果
115 |
116 | ```bash
117 | s2 = [0.336 1.348 0.505 1.279 0.137 0.494]
118 | ```
119 |
120 | ★
121 | 1. `numpy.nanvar()`を使う。
122 |
123 | ★★★
124 | 1. 二重のリスト内包表記を使う。
125 | 2. `numpy.sum()`を使う。
126 | 3. `numpy.array()`を使う。
127 |
128 | ★★★
129 | 1. リスト内包表記を使う。
130 | 2. `numpy.nansum()`を使う。
131 | 3. `numpy.array()`を使う。
132 | 4. 二重のリスト内包表記を使わない。
133 |
134 | ### 03 アイテムiとアイテムjの平均中心化評価値の共分散
135 | アイテム$$i$$とアイテム$$j$$の平均中心化評価値の共分散$$s_{i,j}$$を求めるコードを書きなさい。ただし、$$U_{i,j} = \emptyset$$のとき、$$s_{i,j} = 0$$とする。得られた値を`sij`とすること。
136 |
137 | ###### コード
138 |
139 | ```python
140 | i = 0
141 | j = 1
142 | Uij = np.intersect1d(Ui[i], Ui[j])
143 | 【 問03 】
144 | print('s{}{} = {:.3f}'.format(i, j, sij))
145 | ```
146 |
147 | ###### 結果
148 |
149 | ```bash
150 | s01 = 0.892
151 | ```
152 |
153 | ★★
154 | 1. リスト内包表記を使う。
155 | 2. `numpy.sum()`を使う。
156 | 3. 条件式を使う。
157 |
158 | ### 04 分散共分散行列
159 | 分散共分散行列$$\boldsymbol{S}$$を`ndarray`として求めるコードを書きなさい。得られた`ndarray`を`S`とすること。
160 |
161 | ###### コード
162 |
163 | ```python
164 | 【 問04 】
165 | print('S = \n{}'.format(S))
166 | ```
167 |
168 | ###### 結果
169 |
170 | ```bash
171 | S =
172 | [[ 0.336 0.892 0.169 -0.659 -0.057 -0.572]
173 | [ 0.892 1.348 0.505 -1.466 0.166 -0.817]
174 | [ 0.169 0.505 0.505 -0.655 -0.183 -0.27 ]
175 | [-0.659 -1.466 -0.655 1.279 -0.109 0.752]
176 | [-0.057 0.166 -0.183 -0.109 0.137 -0.015]
177 | [-0.572 -0.817 -0.27 0.752 -0.015 0.494]]
178 | ```
179 |
180 | ★★★
181 | 1. `numpy.zeros()`を使う。
182 | 2. 二重の`for`ループを使う。
183 | 3. `numpy.intersect1d()`を使う。
184 | 4. リスト内包表記を使う。
185 | 5. `numpy.sum()`を使う。
186 |
187 | ## 固有値・固有ベクトル
188 | 分散共分散行列$$\boldsymbol{S}$$に対して、
189 |
190 | $$
191 | \boldsymbol{S} \boldsymbol{v} = \lambda \boldsymbol{v} \;\;\;\; (\boldsymbol{x} \neq \boldsymbol{0})
192 | $$
193 |
194 | を満たす$d$次元ベクトル$$\boldsymbol{v}$$と実数$$\lambda$$が存在するとき、$$\lambda$$を行列$\boldsymbol{S}$の固有値,$$\boldsymbol{v}$$を$$\lambda$$に関する行列$$\boldsymbol{S}$$の固有ベクトルという。このとき、次の問いに答えなさい。
195 |
196 | ### 05 固有値・固有ベクトル
197 |
198 | 分散共分散行列$$\boldsymbol{S}$$の固有値$$\lambda$$、固有ベクトル$$\boldsymbol{v}$$を求めるコードを書きなさい。`ndarray`として得られた固有値、固有ベクトルを、それぞれ`lmd`、`v`とすること。
199 |
200 | ###### コード
201 |
202 | ```python
203 | 【 問05 】
204 | print('λ = {}'.format(lmd))
205 | print('v = \n{}'.format(v))
206 | ```
207 |
208 | ###### 結果
209 |
210 | ```bash
211 | λ = [ 3.909 0.48 0.233 -0.315 -0.049 -0.16 ]
212 | v =
213 | [[ 0.327 0.228 0.484 -0.685 0.279 -0.245]
214 | [ 0.609 0.211 -0.099 0.565 0.371 -0.344]
215 | [ 0.245 -0.806 -0.097 -0.134 -0.202 -0.472]
216 | [-0.583 0.126 0.374 0.258 -0.019 -0.661]
217 | [ 0.028 0.462 -0.624 -0.294 -0.394 -0.393]
218 | [-0.348 -0.157 -0.465 -0.204 0.767 -0.087]]
219 | ```
220 |
221 | ★
222 | 1. `numpy.linalg.eig()`を使う。
223 |
224 | ### 06 第d主成分までの固有ベクトル
225 | 第`DIM`主成分までの対応する固有ベクトルを列ベクトルとして並べた行列$$\boldsymbol{V}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`V`とすること。
226 |
227 | ###### コード
228 |
229 | ```python
230 | 【 問06 】
231 | print('V = \n{}'.format(V))
232 | ```
233 |
234 | ###### 結果
235 |
236 | ```bash
237 | V =
238 | [[ 0.327 0.228]
239 | [ 0.609 0.211]
240 | [ 0.245 -0.806]
241 | [-0.583 0.126]
242 | [ 0.028 0.462]
243 | [-0.348 -0.157]]
244 | ```
245 |
246 | ★★★
247 | 1. `numpy.argsort()`を使う。
248 | 2. 整数配列インデキシングを使う。
249 | 3. スライシングを使う。
250 |
251 | ## 主成分得点
252 | ユーザ$$u$$の第$$k$$主成分得点$$p_{u,k}$$は次式で求められる。
253 |
254 | $$
255 | p_{u,k} = \frac{\sum_{i \in I_{u}} r_{u,i}^{'} v_{k,i}}{\mid I_{u} \mid}
256 | $$
257 |
258 | すべてのユーザについて、第$$d$$主成分までの主成分得点を計算すると、次式の潜在因子行列$$\boldsymbol{P}$$が得られる。
259 |
260 | $$
261 | \boldsymbol{P} = \left[
262 | \begin{array}{rr}
263 | -0.474 & 0.127 \\
264 | -0.251 & -0.027 \\
265 | -0.195 & 0.463 \\
266 | -0.214 & -0.017 \\
267 | 0.445 & -0.009
268 | \end{array}
269 | \right]
270 | $$
271 |
272 | このとき、次の問いに答えなさい。
273 |
274 | ### 07 ユーザuの第k主成分得点
275 | ユーザ$$u$$の第$$k$$主成分得点$$p_{u,k}$$を求めるコードを書きなさい。得られた値を`puk`とすること。
276 |
277 | ###### コード
278 |
279 | ```python
280 | u = 0
281 | k = 0
282 | 【 問07 】
283 | print('p{}{} = {:.3f}'.format(u, k, puk))
284 | ```
285 |
286 | ###### 結果
287 |
288 | ```bash
289 | p00 = 0.474
290 | ```
291 |
292 | ★★
293 | 1. リスト内包表記を使う。
294 | 2. `numpy.sum()`を使う。
295 |
296 | ### 08 潜在因子行列
297 | 潜在因子行列$$\boldsymbol{P}$$を`ndarray`としてまとめて求めるコードを書きなさい。ただし、潜在因子行列$$\boldsymbol{P}$$の次元数は`DIM`とする。得られた`ndarray`を`P`とすること。
298 |
299 | ###### コード
300 |
301 | ```python
302 | 【 問08 】
303 | print('P = \n{}'.format(P))
304 | ```
305 |
306 | ###### 結果
307 |
308 | ```bash
309 | P =
310 | [[ 0.474 -0.127]
311 | [ 0.251 0.027]
312 | [ 0.195 -0.463]
313 | [ 0.214 0.017]
314 | [-0.445 0.009]]
315 | ```
316 |
317 | ★★★
318 | 1. `numpy.zeros()`を使う。
319 | 2. 二重の`for`ループを使う。
320 | 3. リスト内包表記を使う。
321 | 4. `numpy.sum()`を使う。
322 |
323 | ★★★
324 | 1. 三重のリスト内包表記を使う。
325 | 2. `numpy.sum()`を使う。
326 |
--------------------------------------------------------------------------------
/ja/chap03.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第3章 類似度に基づく推薦 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第3章 類似度に基づく推薦
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 |
17 | # 上位K件
18 | TOP_K = 3
19 |
20 | Du = np.array([
21 | [5, 3, +1],
22 | [6, 2, +1],
23 | [4, 1, +1],
24 | [8, 5, -1],
25 | [2, 4, -1],
26 | [3, 6, -1],
27 | [7, 6, -1],
28 | [4, 2, np.nan],
29 | [5, 1, np.nan],
30 | [8, 6, np.nan],
31 | [3, 4, np.nan],
32 | [4, 7, np.nan],
33 | [4, 4, np.nan],
34 | ])
35 | I = np.arange(Du.shape[0])
36 | x = Du[:,:-1]
37 | ru = Du[:,-1]
38 |
39 | Iu = I[~np.isnan(ru)]
40 | Iup = I[ru==+1]
41 | Iun = I[ru==-1]
42 | Iu_not = np.setdiff1d(I, Iu)
43 | ```
44 |
45 | ## ユーザプロファイル
46 | ユーザ$$u$$のユーザプロファイル$$\boldsymbol{p}_{u}$$は次式で求められる。
47 |
48 | $$
49 | \boldsymbol{p}_{u} = \frac{1}{\mid I_{u}^{+} \mid} \sum_{i \in I_{u}^{+}} \boldsymbol{x}_{i}
50 | $$
51 |
52 | ここで、$$I_{u}^{+}$$は対象ユーザ$$u$$が「好き」と評価したアイテム集合であり、$$\boldsymbol{x}_{i}$$はアイテム$i$の特徴ベクトルである。
53 |
54 | ### 01 好きなアイテム集合に含まれるアイテムの特徴ベクトルの集合
55 | `x`から$$I_{u}^{+}$$に含まれるアイテムの特徴ベクトルの集合を`ndarray`として生成するコードを書きなさい。
56 |
57 | ###### コード
58 |
59 | ```python
60 | print('x[Iu+] = \n{}'.format(【 問01 】))
61 | ```
62 |
63 | ###### 結果
64 |
65 | ```bash
66 | x[Iu+] =
67 | [[5. 3.]
68 | [6. 2.]
69 | [4. 1.]]
70 | ```
71 |
72 | ★
73 | 1. 整数配列インデキシングを使う。
74 |
75 | ★★
76 | 1. リスト内包表記を使う。
77 |
78 | ### 02 特徴ベクトルの総和
79 | 次式により、$$I_{u}^{+}$$に含まれるアイテムの特徴ベクトルの総和を求めるコードを書きなさい。
80 |
81 | $$
82 | \sum_{i \in I_{u}^{+}} \boldsymbol{x}_{i}
83 | $$
84 |
85 | ###### コード
86 |
87 | ```python
88 | print('sum(x[Iu+]) = {}'.format(【 問02 】))
89 | ```
90 |
91 | ###### 結果
92 |
93 | ```bash
94 | sum(x[Iu+]) = [15. 6.]
95 | ```
96 |
97 | ★★
98 | 1. 整数配列インデキシングを使う。
99 | 2. `numpy.sum()`を使う。
100 |
101 | ★★★
102 | 1. リスト内包表記を使う。
103 | 2. `numpy.sum()`を使う。
104 |
105 | ### 03 ユーザプロファイル
106 | ユーザ$$u$$のユーザプロファイル$$\boldsymbol{p}_{u}$$を`ndarray`として求めるコードを書きなさい。得られた`ndarray`を`pu`とすること。
107 |
108 | ###### コード
109 |
110 | ```python
111 | 【 問03 】
112 | print('pu = {}'.format(pu))
113 | ```
114 |
115 | ###### 結果
116 |
117 | ```bash
118 | pu = [5. 2.]
119 | ```
120 |
121 | ★★
122 | 1. 整数配列インデキシングを使う。
123 | 2. `numpy.sum()`を使う。
124 |
125 | ★★★
126 | 1. リスト内包表記を使う。
127 | 2. `numpy.sum()`を使う。
128 |
129 | ## コサイン類似度
130 |
131 | ユーザプロファイル$$\boldsymbol{p}_{u}$$とアイテム$$i$$の特徴ベクトル$$\boldsymbol{x}_{i}$$のコサイン類似度は次式で定義される。
132 |
133 | $$
134 | \mathrm{cos}(\boldsymbol{p}_{u}, \boldsymbol{x}_{i}) = \frac{\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}}{\| \boldsymbol{p}_{u} \| \| \boldsymbol{x}_{i} \|}
135 | $$
136 |
137 | ここで、$$\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}$$は二つのベクトル$$\boldsymbol{p}_{u}$$と$$\boldsymbol{x}_{i}$$の内積であり、次式のように表される。
138 |
139 | $$
140 | \boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i} = \sum_{k=1}^{d} p_{u,k} x_{i,k}
141 | $$
142 |
143 | $$d$$はベクトルの次元数である。また、$$\| \boldsymbol{p}_{u} \|$$はベクトル$$\boldsymbol{p}_{u}$$のノルム(大きさ)であり、次式のように表される。
144 |
145 | $$
146 | \| \boldsymbol{p}_{u} \| = \sqrt{\boldsymbol{p}_{u} \cdot \boldsymbol{p}_{u}} = \sqrt{\sum_{k=1}^{d} p_{u,k}^{2}}
147 | $$
148 |
149 | このコサイン類似度関数を次のコードのとおり定義する。
150 |
151 | ###### 関数
152 |
153 | ```python
154 | def cos(pu, xi):
155 | """
156 | コサイン類似度関数:ユーザプロファイルpuとアイテムiの特徴ベクトルxiのコサイン類似度を返す。
157 |
158 | Parameters
159 | ----------
160 | pu : ndarray
161 | ユーザuのユーザプロファイル
162 | xi : ndarray
163 | アイテムiの特徴ベクトル
164 |
165 | Returns
166 | -------
167 | float
168 | コサイン類似度
169 | """
170 | 【 問04 】
171 | print('num = {}'.format(num))
172 | 【 問05 】
173 | print('den_u = {:.3f}'.format(den_u))
174 | 【 問06 】
175 | print('den_i = {:.3f}'.format(den_i))
176 |
177 | cosine = num / (den_u * den_i)
178 | return cosine
179 | ```
180 |
181 | ###### コード
182 |
183 | ```python
184 | u = 0
185 | i = 7
186 | print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))
187 | u = 0
188 | i = 11
189 | print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))
190 | ```
191 |
192 | ###### 結果
193 |
194 | ```bash
195 | num = 24.0
196 | den_u = 5.385
197 | den_i = 4.472
198 | cos(p0, x7) = 0.997
199 | num = 34.0
200 | den_u = 5.385
201 | den_i = 8.062
202 | cos(p0, x11) = 0.783
203 | ```
204 |
205 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
206 |
207 | ### 04 ベクトルの内積
208 | 内積$$\boldsymbol{p}_{u} \cdot \boldsymbol{x}_{i}$$を求めるコードを書きなさい。得られた値を`num`とすること。
209 |
210 | ★
211 | 1. `@`演算子を使う。
212 |
213 | ★
214 | 1. `numpy.dot()`を使う。
215 |
216 | ★★★
217 | 1. リスト内包表記を使う。
218 | 2. `range()`を使う。
219 | 3. `numpy.sum()`を使う。
220 |
221 | ★★★
222 | 1. `numpy.sum()`を使う。
223 | 2. `@`演算子を使わない。
224 | 3. `numpy.dot()`を使わない。
225 | 4. リスト内包表記を使わない。
226 |
227 | ### 05 ユーザプロファイルのノルム
228 | $$\boldsymbol{p}_{u}$$のノルム$$\| \boldsymbol{p}_{u} \|$$を求めるコードを書きなさい。得られた値を`den_u`とすること。
229 |
230 | ★
231 | 1. `numpy.linalg.norm()`を使う。
232 |
233 | ★★
234 | 1. `@`演算子を使う。
235 | 2. `numpy.sqrt()`を使う。
236 |
237 | ★★★
238 | 1. リスト内包表記を使う。
239 | 2. `range()`を使う。
240 | 3. `numpy.sum()`を使う。
241 | 4. `numpy.sqrt()`を使う。
242 |
243 | ★★★
244 | 1. `numpy.sum()`を使う。
245 | 2. `numpy.sqrt()`を使う。
246 | 3. `numpy.linalg.norm()`を使わない。
247 | 4. `@`演算子を使わない。
248 | 5. `numpy.dot()`を使わない。
249 | 6. リスト内包表記を使わない。
250 |
251 | ### 06 特徴ベクトルのノルム
252 | $$\boldsymbol{x}_{i}$$のノルム$$\| \boldsymbol{x}_{i} \|$$を求めるコードを書きなさい。得られた値を`den_i`とすること。
253 |
254 | ★
255 | 1. `numpy.linalg.norm()`を使う。
256 |
257 | ★★
258 | 1. `@`演算子を使う。
259 | 2. `numpy.sqrt()`を使う。
260 |
261 | ★★★
262 | 1. リスト内包表記を使う。
263 | 2. `range()`を使う。
264 | 3. `numpy.sum()`を使う。
265 | 4. `numpy.sqrt()`を使う。
266 |
267 | ★★★
268 | 1. `numpy.sum()`を使う。
269 | 2. `numpy.sqrt()`を使う。
270 | 3. `numpy.linalg.norm()`を使わない。
271 | 4. `@`演算子を使わない。
272 | 5. `numpy.dot()`を使わない。
273 | 6. リスト内包表記を使わない。
274 |
275 | ## 推薦
276 |
277 | スコア関数$$\mathrm{score}(u, i)$$はユーザ$$u$$がアイテム$$i$$を好む程度をスコアとして返す関数であり、次式のように定義される。
278 |
279 | $$
280 | \mathrm{score}(u, i) = \mathrm{cos}(\boldsymbol{p}_{u}, \boldsymbol{x}_{i})
281 | $$
282 |
283 | このスコア関数を次のコードのとおり定義する。
284 |
285 | ###### 関数
286 |
287 | ```python
288 | def score(u, i):
289 | """
290 | スコア関数:ユーザuのアイテムiに対するスコアを返す。
291 |
292 | Parameters
293 | ----------
294 | u : int
295 | ユーザuのID(ダミー)
296 | i : int
297 | アイテムiのID
298 |
299 | Returns
300 | -------
301 | float
302 | スコア
303 | """
304 | return cos(pu, x[i])
305 | ```
306 |
307 | 順序付け関数$$\mathrm{order}(u, I)$$は、アイテム集合$$I$$が与えられたとき、ユーザ$$u$$向けの推薦リストを返す関数である。ここでは、スコア上位$$K$$件のアイテム集合を推薦リストとして返すものとする。この順序付け関数を次のコードのとおり定義する。
308 |
309 | ```python
310 | def order(u, I):
311 | """
312 | 順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
313 |
314 | Parameters
315 | ----------
316 | u : int
317 | ユーザuのID
318 | I : ndarray
319 | アイテム集合
320 |
321 | Returns
322 | -------
323 | list
324 | タプル(アイテムID: スコア)を要素にした推薦リスト
325 | """
326 | 【 問07 】
327 | print('scores = ')
328 | pprint.pprint(scores)
329 | 【 問08 】
330 | return rec_list
331 | ```
332 |
333 | ###### コード
334 |
335 | ```python
336 | u = 0
337 | rec_list = order(u, Iu_not)
338 | print('rec_list = ')
339 | for i, scr in rec_list:
340 | print('{}: {:.3f}'.format(i, scr))
341 | ```
342 |
343 | ###### 結果
344 |
345 | ```bash
346 | scores =
347 | {7: 0.9965457582448796,
348 | 8: 0.9832820049844603,
349 | 9: 0.9656157585206697,
350 | 10: 0.8541985556144386,
351 | 11: 0.783110847498294,
352 | 12: 0.9191450300180578}
353 | rec_list =
354 | 7: 0.997
355 | 8: 0.983
356 | 9: 0.966
357 | ```
358 |
359 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
360 |
361 | ### 07 各アイテムに対するスコア
362 | 引数に渡されたアイテム集合$$I$$について、ユーザ$$u$$の各アイテム$$i \in I$$に対するスコア$$\mathrm{score}(u, i)$$を求め、`(i: score(u, i))`をペアとした辞書を生成するコードを書きなさい。生成した辞書を`scores`とすること。
363 |
364 | ★★
365 | 1. `for`ループを使う。
366 |
367 | ★★★
368 | 1. 辞書内包表記を使う。
369 |
370 | ### 08 推薦リスト
371 | `scores`内の`(i: score(u, i))`のペアを`score(u, i)`の降順にソートし、上位`TOP_K`件のリストを生成するコードを書きなさい。得られたリストを辞書に変換したものを`rec_list`とすること。
372 |
373 | ★★★
374 | 1. `sorted()`を使う。
375 | 2. `dict.items()`を使う。
376 | 3. `lambda`式を使う。
377 | 4. スライシングを使う。
378 |
--------------------------------------------------------------------------------
/ja/chap10.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第10章 決定木 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第10章 決定木
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 |
17 | Du = np.array([
18 | [1, 0, 0, 0, 1, 0, +1],
19 | [0, 1, 0, 0, 1, 0, +1],
20 | [1, 1, 0, 0, 1, 0, +1],
21 | [1, 0, 0, 1, 1, 0, +1],
22 | [1, 0, 0, 0, 0, 1, +1],
23 | [0, 1, 0, 1, 0, 1, +1],
24 | [0, 0, 1, 0, 1, 0, -1],
25 | [0, 0, 1, 1, 1, 0, -1],
26 | [0, 1, 0, 0, 1, 1, -1],
27 | [0, 0, 1, 0, 0, 1, -1],
28 | [1, 1, 0, 1, 1, 0, np.nan],
29 | [0, 0, 1, 0, 1, 1, np.nan],
30 | [0, 1, 1, 1, 1, 0, np.nan],
31 | ])
32 | I = np.arange(Du.shape[0])
33 | x = Du[:,:-1]
34 | ru = Du[:,-1]
35 |
36 | Iu = I[~np.isnan(ru)]
37 | Iu_not = np.setdiff1d(I, Iu)
38 | DuL = Du[Iu]
39 | xL = x[Iu]
40 | ruL = ru[Iu]
41 | DuU = Du[Iu_not]
42 | xU = x[Iu_not]
43 | ```
44 |
45 | ## ジニ係数
46 |
47 | 訓練データ$$D_{u}^{L}$$のジニ係数$$G(D^{L}_{u})$$は次式で定義される。
48 |
49 | $$
50 | G(D^{L}_{u}) = 1 - \{(p^{+})^{2} + (p^{-})^{2}\}
51 | $$
52 |
53 | ここで、$$p^{+}$$は訓練データ$$D_{u}^{L}$$における「好き」な事例が含まれる割合、$$p^{-}$$は「嫌い」な事例が含まれる割合を表し、それぞれ次式で求められる。
54 |
55 | $$
56 | p^{+} = \frac{\mid D^{L+}_{u} \mid}{\mid D^{L}_{u} \mid}
57 | $$
58 |
59 | $$
60 | p^{-} = \frac{\mid D^{L-}_{u} \mid}{\mid D^{L}_{u} \mid}
61 | $$
62 |
63 | ここで、$$\mid D_{u}^{L} \mid$$は訓練事例数である。$$\mid D_{u}^{L+} \mid$$、$$\mid D_{u}^{L-} \mid$$はそれぞれ訓練事例に含まれる正事例数、負事例数である。これらのジニ係数を返す関数を次のコードのとおり定義する。
64 |
65 | ###### 関数
66 |
67 | ```python
68 | def G(DL):
69 | """
70 | 訓練データDLのジニ係数を返す。
71 |
72 | Parameters
73 | ----------
74 | DL : ndarray
75 | 訓練データDL
76 |
77 | Returns
78 | -------
79 | float
80 | ジニ係数
81 | ただし、DLに事例が含まれていないときは0
82 | """
83 | if DL.shape[0] == 0: return 0
84 | r = DL[:,-1]
85 | 【 問01 】
86 | 【 問02 】
87 | 【 問03 】
88 | return gini
89 | ```
90 |
91 | ###### コード
92 |
93 | ```python
94 | print('G(DuL) = {:.3f}'.format(G(DuL)))
95 | ```
96 |
97 | ###### 結果
98 |
99 | ```bash
100 | G(DuL) = 0.480
101 | ```
102 |
103 | このとき、次の問いに答えなさい。
104 |
105 | ### 01 「好き」な事例が含まれる割合
106 | $$p^{+}$$を求めるコードを書きなさい。得られた値を`pp`とすること。
107 |
108 | ★
109 | 1. ブール値インデキシングを使う。
110 | 2. `ndarray.shape`を使う。
111 |
112 | ### 02 「嫌い」な事例が含まれる割合
113 | $$p^{-}$$を求めるコードを書きなさい。得られた値を`pn`とすること。
114 |
115 | ★
116 | 1. ブール値インデキシングを使う。
117 | 2. `ndarray.shape`を使う。
118 |
119 | ### 03 ジニ係数
120 | ジニ係数を求めるコードを書きなさい。得られた値を`gini`とすること。
121 |
122 | ★
123 | 1. べき乗を使う。
124 |
125 | ## 分割の良さ
126 | 訓練データ$$D_{u}^{L}$$を$$D_{u}^{L0}$$と$$D_{u}^{L1}$$に分割したときのジニ係数は次式で定義される。
127 |
128 | $$
129 | G(D^{L}_{u} \rightarrow [D^{L0}_{u}, D^{L1}_{u}]) = \frac{\mid D^{L0}_{u} \mid G(D^{L0}_{u}) + \mid D^{L1}_{u} \mid G(D^{L1}_{u})}{\mid D^{L0}_{u} \mid + \mid D^{L1}_{u} \mid}
130 | $$
131 |
132 | このジニ係数を返す関数を次のコードのとおり定義する。
133 |
134 | ```python
135 | def G_partitioned(DL0, DL1):
136 | """
137 | 訓練データをDL0とDL1に分割したときのジニ係数を返す。
138 |
139 | Parameters
140 | ----------
141 | DL0 : ndarray
142 | 訓練データDL0
143 | DL1 : ndarray
144 | 訓練データDL1
145 |
146 | Returns
147 | -------
148 | float
149 | ジニ係数
150 | """
151 | 【 問06 】
152 | return gini
153 | ```
154 |
155 | ###### コード
156 |
157 | ```python
158 | # 特徴量kを含まない訓練事例集合
159 | k = 0
160 | 【 問04 】
161 | # 特徴量kを含む訓練事例集合
162 | print('DuL0 = \n{}'.format(DuL0))
163 | 【 問05 】
164 | # 特徴量kを基準に分割したときのジニ係数
165 | print('DuL1 = \n{}'.format(DuL1))
166 | print('G(DuL → [DuL0, DuL1]) = {:.3f}'.format(G_partitioned(DuL0, DuL1)))
167 | ```
168 |
169 | ###### 結果
170 |
171 | ```bash
172 | DuL0 =
173 | [[ 0. 1. 0. 0. 1. 0. 1.]
174 | [ 0. 1. 0. 1. 0. 1. 1.]
175 | [ 0. 0. 1. 0. 1. 0. -1.]
176 | [ 0. 0. 1. 1. 1. 0. -1.]
177 | [ 0. 1. 0. 0. 1. 1. -1.]
178 | [ 0. 0. 1. 0. 0. 1. -1.]]
179 | DuL1 =
180 | [[1. 0. 0. 0. 1. 0. 1.]
181 | [1. 1. 0. 0. 1. 0. 1.]
182 | [1. 0. 0. 1. 1. 0. 1.]
183 | [1. 0. 0. 0. 0. 1. 1.]]
184 | G(DuL → [DuL0, DuL1]) = 0.267
185 | ```
186 |
187 | このとき、次の問いに答えなさい。
188 |
189 |
190 | ### 04 特徴量kを含まない訓練事例集合
191 | 訓練データ$$D_{u}^{L}$$から特徴量$$k$$を含まない($$x_{i,k}=0$$となる)事例集合を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`DuL0`とすること。
192 |
193 | ★★
194 | 1. ブール値インデキシングを使う。
195 |
196 | ### 05 特徴量kを含む訓練事例集合
197 | 訓練データ$$D_{u}^{L}$$から特徴量$$k$$を含む($$x_{i,k}=1$$となる)事例集合を`ndarray`として生成するコードを書きなさい。取得した`ndarray`を`DuL1`とすること。
198 |
199 | ★★
200 | 1. ブール値インデキシングを使う。
201 |
202 | ### 06 特徴量kを基準に分割したときのジニ係数
203 | 特徴量$$k$$を基準に分割したときのジニ係数を求めるコードを書きなさい。得られた値を`gini`とすること。
204 |
205 | ★★
206 | 1. `ndarray.shape`を使う。
207 | 2. `G(DL)`関数を呼ぶ。
208 |
209 | ## 決定木の学習
210 |
211 | 次の関数は、入力された訓練データ`DL`を各特徴量で分割したときの(特徴量のインデックス: ジニ係数)をペアにした辞書を返す関数である。
212 |
213 | ```python
214 | def get_ginis(DL):
215 | """
216 | 訓練データDLを各特徴量で分割したときの(特徴量のインデックス: ジニ係数)をペアにした辞書を返す。
217 |
218 | Parameters
219 | ----------
220 | DL : ndarray
221 | 訓練データDL
222 |
223 | Returns
224 | -------
225 | dict
226 | (特徴量のインデックス: ジニ係数)をペアにした辞書
227 | """
228 | ginis = {}
229 | for k in range(0, x.shape[1]):
230 | DL0 = DL[DL[:,k]==0]
231 | DL1 = DL[DL[:,k]==1]
232 | ginis[k] = G_partitioned(DL0, DL1)
233 | return ginis
234 | ```
235 |
236 | このとき、次の問いに答えなさい。
237 |
238 | ### 07 レベル0の選択基準
239 | `get_ginis()`関数から得られた`ginis`からジニ係数が最小となる特徴量のインデックスを取得するコードを書きなさい。得られた値を`k0`とすること。
240 |
241 | ###### コード
242 |
243 | ```python
244 | # レベル0(根ノード)の選択基準
245 | ginis = get_ginis(DuL)
246 | print('ginis = ')
247 | pprint.pprint(ginis)
248 | 【 問07 】
249 | print('k0 = {}'.format(k0))
250 | DuL0 = DuL[DuL[:,k0]==0]
251 | DuL1 = DuL[DuL[:,k0]==1]
252 | print('DuL0 = \n{}'.format(DuL0))
253 | print('DuL1 = \n{}'.format(DuL1))
254 | ```
255 |
256 | ###### 結果
257 |
258 | ```bash
259 | ginis =
260 | {0: 0.26666666666666666,
261 | 1: 0.45,
262 | 2: 0.17142857142857146,
263 | 3: 0.4761904761904763,
264 | 4: 0.4761904761904763,
265 | 5: 0.4666666666666666}
266 | k0 = 2
267 | DuL0 =
268 | [[ 1. 0. 0. 0. 1. 0. 1.]
269 | [ 0. 1. 0. 0. 1. 0. 1.]
270 | [ 1. 1. 0. 0. 1. 0. 1.]
271 | [ 1. 0. 0. 1. 1. 0. 1.]
272 | [ 1. 0. 0. 0. 0. 1. 1.]
273 | [ 0. 1. 0. 1. 0. 1. 1.]
274 | [ 0. 1. 0. 0. 1. 1. -1.]]
275 | DuL1 =
276 | [[ 0. 0. 1. 0. 1. 0. -1.]
277 | [ 0. 0. 1. 1. 1. 0. -1.]
278 | [ 0. 0. 1. 0. 0. 1. -1.]]
279 | ```
280 |
281 | ★★
282 | 1. `min()`を使う。
283 |
284 | ### 08 レベル1の選択基準
285 | レベル0の分割で得られた`DuL0`からレベル1a(レベル1の左端ノード)の選択基準となる特徴量のインデックスを取得するコードを書きなさい。得られた値を`k1a`とすること。
286 |
287 | ###### コード
288 |
289 | ```python
290 | # レベル1a(レベル1の左端ノード)の選択基準
291 | 【 問08 】
292 | print('k1a = {}'.format(k1a))
293 | DuL00 = DuL0[DuL0[:,k1a] == 0]
294 | DuL01 = DuL0[DuL0[:,k1a] == 1]
295 | print('DuL00 = \n{}'.format(DuL00))
296 | print('DuL01 = \n{}'.format(DuL01))
297 | ```
298 |
299 | ###### 結果
300 |
301 | ```bash
302 | k1a = 0
303 | DuL00 =
304 | [[ 0. 1. 0. 0. 1. 0. 1.]
305 | [ 0. 1. 0. 1. 0. 1. 1.]
306 | [ 0. 1. 0. 0. 1. 1. -1.]]
307 | DuL01 =
308 | [[1. 0. 0. 0. 1. 0. 1.]
309 | [1. 1. 0. 0. 1. 0. 1.]
310 | [1. 0. 0. 1. 1. 0. 1.]
311 | [1. 0. 0. 0. 0. 1. 1.]]
312 | ```
313 |
314 | ★★
315 | 1. `get_ginis()`関数を呼ぶ。
316 | 2. `min()`を使う。
317 |
318 | ### 09 レベル2の選択基準
319 | レベル1の分割で得られた`DuL00`からレベル2a(レベル2の左端ノード)の選択基準となる特徴量のインデックスを取得するコードを書きなさい。得られた値を`k2a`とすること。
320 |
321 | ###### コード
322 |
323 | ```python
324 | # レベル2a(レベル2の左端ノード)の選択基準
325 | 【 問09 】
326 | print('k2a = {}'.format(k2a))
327 | DuL000 = DuL00[DuL00[:,k2a] == 0]
328 | DuL001 = DuL00[DuL00[:,k2a] == 1]
329 | print('DuL000 = \n{}'.format(DuL000))
330 | print('DuL001 = \n{}'.format(DuL001))
331 | ```
332 |
333 | ###### 結果
334 |
335 | ```bash
336 | k2a = 3
337 | DuL000 =
338 | [[ 0. 1. 0. 0. 1. 0. 1.]
339 | [ 0. 1. 0. 0. 1. 1. -1.]]
340 | DuL001 =
341 | [[0. 1. 0. 1. 0. 1. 1.]]
342 | ```
343 |
344 | ★★
345 | 1. `get_ginis()`関数を呼ぶ。
346 | 2. `min()`を使う。
347 |
348 | ## 嗜好予測
349 |
350 | 次の関数は、訓練データ`DL`から決定木を学習する関数`train()`およびユーザ`u`のアイテム`i`に対する予測評価値を返す関数`predict()`である。
351 |
352 | ```python
353 | def train(DL, key=0):
354 | """
355 | 学習関数:訓練データDLから決定木を学習する。
356 |
357 | Parameters
358 | ----------
359 | DL : ndarray
360 | 訓練データDL
361 | key : int
362 | キー値
363 | """
364 | if len(DL) <= 0:
365 | return
366 | elif np.count_nonzero(DL[:,-1]==-1) <= 0:
367 | dtree[key] = '+1'
368 | return
369 | elif np.count_nonzero(DL[:,-1]==+1) <= 0:
370 | dtree[key] = '-1'
371 | return
372 |
373 | ginis = get_ginis(DL)
374 | k = min(ginis, key=ginis.get)
375 | dtree[key] = k
376 | DL0 = DL[DL[:,k] == 0]
377 | DL1 = DL[DL[:,k] == 1]
378 | train(DL0, key * 2 + 1)
379 | train(DL1, key * 2 + 2)
380 |
381 |
382 | def predict(u, i, key=0):
383 | """
384 | 予測関数:ユーザuのアイテムiに対する予測評価値を返す。
385 |
386 | Parameters
387 | ----------
388 | u : int
389 | ユーザuのID(ダミー)
390 | i : int
391 | アイテムiのID
392 | key : int
393 | キー値
394 |
395 | Returns
396 | -------
397 | int
398 | ユーザuのアイテムiに対する予測評価値
399 | """
400 | if type(dtree[key]) == str: return int(dtree[key])
401 | k = dtree[key]
402 | if x[i,k] == 0:
403 | return predict(u, i, key * 2 + 1)
404 | elif x[i,k] == 1:
405 | return predict(u, i, key * 2 + 2)
406 | ```
407 |
408 | ###### コード
409 |
410 | ```python
411 | dtree = {}
412 | train(DuL)
413 | print('dtree = {}'.format(dtree))
414 |
415 | u = 0
416 | 【 問10 】
417 | print('ruU_pred = {}'.format(ruU_pred))
418 | ```
419 |
420 | ###### 結果
421 |
422 | ```bash
423 | dtree = {0: 2, 1: 0, 3: 3, 7: 5, 15: '+1', 16: '-1', 8: '+1', 4: '+1', 2: '-1'}
424 | ruU_pred = {10: 1, 11: -1, 12: -1}
425 | ```
426 |
427 | このとき、次の問いに答えなさい。
428 |
429 | ### 10 予測対象データに対する嗜好予測
430 | 予測対象データ$$D_{u}^{U}$$内の各アイテム$$i$$について予測評価値$$\hat{r}_{u,i}$$を求め、`i: predict(u, i)`をペアとした辞書を生成するコードを書きなさい。生成した辞書を`ruU_pred`とすること。
431 |
432 | ★★★
433 | 1. 辞書内包表記を使う。
434 | 2. `predict()`関数を呼ぶ。
435 |
--------------------------------------------------------------------------------
/ja/chap13.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第13章 推薦順位に基づく正確性 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第13章 推薦順位に基づく正確性
9 |
10 | ## テストデータと推薦リスト
11 | 次の評価値行列$$\boldsymbol{R}^{\mathit{test}}$$はテストデータである。$$\boldsymbol{R}$$の$$(u, i)$$成分はユーザ$$u$$がアイテム$$i$$に与えた評価値$$r_{u,i}$$を表す。ただし、$$-$$で示した要素はテストデータの対象ではないことを表す。また、$$\boldsymbol{R}^{\mathit{test}}$$に含まれる成分の集合を$$R^{\mathit{test}}$$と表す。
12 |
13 | $$
14 | \boldsymbol{R}^{\mathit{test}} = \left[
15 | \begin{array}{rrrrrrrrrr}
16 | 5 & 4 & 3 & - & 5 & 4 & 2 & 2 & - & - \\
17 | 3 & 3 & 3 & 3 & 2 & - & 4 & - & 5 & - \\
18 | 4 & - & 3 & 5 & 4 & 3 & - & 3 & - & - \\
19 | \end{array}
20 | \right]
21 | $$
22 |
23 | 次の行列$$\hat{\boldsymbol{R}}^{A}$$は、推薦システムAによる推薦リストである。$$\hat{\boldsymbol{R}}^{A}$$の$$(u, i)$$成分は、それぞれユーザ$$u$$向けの推薦システムA、推薦システムBによる推薦リストにおける順位を表す。
24 |
25 | $$
26 | \hat{\boldsymbol{R}}^{A} = \left[
27 | \begin{array}{rrrrrrrrrr}
28 | 1 & - & 3 & - & 4 & 2 & 5 & - & - & - \\
29 | 4 & 1 & - & 3 & - & - & 5 & - & 2 & - \\
30 | - & - & 5 & 3 & 4 & 2 & - & 1 & - & - \\
31 | \end{array}
32 | \right]
33 | $$
34 |
35 | ## 準備
36 | 次のコードを書きなさい。
37 |
38 | ```python
39 | import math
40 | import numpy as np
41 | np.set_printoptions(precision=3)
42 |
43 | # 上位K件
44 | TOP_K = 5
45 | # 対数の底
46 | ALPHA = 2
47 |
48 | # テストデータ
49 | R = np.array([
50 | [5, 4, 3, np.nan, 5, 4, 2, 2, np.nan, np.nan],
51 | [3, 3, 3, 3, 2, np.nan, 4, np.nan, 5, np.nan],
52 | [4, np.nan, 3, 5, 4, 3, np.nan, 3, np.nan, np.nan],
53 | ])
54 | U = np.arange(R.shape[0])
55 | I = np.arange(R.shape[1])
56 | Iu = [I[~np.isnan(R)[u,:]] for u in U]
57 |
58 | # 推薦システムAによる推薦リスト
59 | RA = np.array([
60 | [1, np.nan, 3, np.nan, 4, 2, 5, np.nan, np.nan, np.nan],
61 | [4, 1, np.nan, 3, np.nan, np.nan, 5, np.nan, 2, np.nan],
62 | [np.nan, np.nan, 5, 3, 4, 2, np.nan, 1, np.nan, np.nan],
63 | ])
64 |
65 | def confusion_matrix(u, RS, K):
66 | """
67 | ユーザu向け推薦リストRSの上位K件における混同行列の各値を返す。
68 |
69 | Parameters
70 | ----------
71 | u : int
72 | ユーザuのID
73 | RS : ndarray
74 | 推薦リストRS
75 | K : int
76 | 上位K件
77 |
78 | Returns
79 | -------
80 | int
81 | TP
82 | int
83 | FN
84 | int
85 | FP
86 | int
87 | TN
88 | """
89 | like = R[u,Iu[u]]>=4
90 | recommended = RS[u,Iu[u]]<=K
91 | TP = np.count_nonzero(np.logical_and(like, recommended))
92 | FN = np.count_nonzero(np.logical_and(like, ~recommended))
93 | FP = np.count_nonzero(np.logical_and(~like, recommended))
94 | TN = np.count_nonzero(np.logical_and(~like, ~recommended))
95 | return TP, FN, FP, TN
96 | ```
97 |
98 | ## 平均逆順位
99 | 平均逆順位$$\mathit{MRR}$$は次式で定義される。
100 |
101 | $$
102 | \mathit{MRR} = \frac{1}{\mid U \mid} \sum_{u \in U} \frac{1}{k_{u}}
103 | $$
104 |
105 | ここで、$$k_{u}$$はユーザ$$u$$向けの推薦リストにおいて最初にユーザ$$u$$が好きなアイテムが見つかったときの順位を表す。ここでは、評価値が4以上のアイテムを好きなアイテムとみなす。
106 |
107 | ###### コード
108 |
109 |
110 | ```python
111 | u = 0
112 | 【 問01 】
113 | print('like = \n{}'.format(like))
114 | 【 問02 】
115 | print('ku = {}'.format(ku))
116 | 【 問03 】
117 | print('MRR = {:.3f}'.format(MRR))
118 | ```
119 |
120 | ###### 結果
121 |
122 |
123 | ```bash
124 | like =
125 | [[ True True False False True True False False False False]
126 | [False False False False False False True False True False]
127 | [ True False False True True False False False False False]]
128 | ku = [1. 2. 3.]
129 | MRR = 0.611
130 | ```
131 |
132 | このとき、次の問いに答えなさい。
133 |
134 | ### 01 好きなアイテムか否かの判定
135 | `R`において、評価値が4以上の要素には`True`を、4未満の要素には`False`を入れたブール値配列を生成するコードを書きなさい。得られたブール値配列を`like`とすること。
136 |
137 | ★
138 | 1. 比較演算子を使う。
139 |
140 | ### 02 最初に好きなアイテムが見つかったときの順位
141 | 各ユーザ向けの推薦リストにおいて最初にそのユーザが好きなアイテムが見つかったときの順位$$k_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`ku`とすること。
142 |
143 | ★★★
144 | 1. リスト内包表記を使う。
145 | 2. ブール値インデキシングを使う。
146 | 3. `numpy.nanmin()`を使う。
147 | 4. `numpy.array()`を使う。
148 |
149 | ### 03 MRR
150 | $$\mathit{MRR}$$を求めるコードを書きなさい。得られた値を`MRR`とすること。
151 |
152 | ★★★
153 | 1. リスト内包表記を使う。
154 | 2. `numpy.sum()`を使う。
155 | 3. `ndarray.size`を使う。
156 |
157 | ## 平均適合率
158 | 第$$K$$位までのユーザ$$u$$向けの推薦リストの平均適合率$$\mathit{AP}_{u}$$は次式で定義される。
159 |
160 | $$
161 | \mathit{AP}_{u} = \frac{1}{\sum_{k=1}^{K} \mathit{rel}_k} \sum_{k=1}^{K} \mathit{rel}_k \cdot \mathit{precision}@k
162 | $$
163 |
164 | ここで、$$\mathit{precision}@k$$は順位$$k$$における適合率を表す。$$\mathit{rel}_k$$は次式で定義される。
165 |
166 | $$
167 | \mathit{rel}_k =
168 | \begin{cases}
169 | 1 & (\text{第$k$位が好きなアイテムであるとき}) \\
170 | 0 & (\text{otherwise})
171 | \end{cases}
172 | $$
173 |
174 | また、すべてのユーザの平均適合率を平均した$$\mathit{MAP}$$は次式で定義される。
175 |
176 | $$
177 | \mathit{MAP} = \frac{1}{\mid U \mid} \sum_{u \in U} \mathit{AP}_{u}
178 | $$
179 |
180 | ###### コード
181 |
182 | ```python
183 | # 各順位における適合率
184 | precisions = []
185 | for u in U:
186 | precisions_u = []
187 | for k in range(1, Iu[u].size+1):
188 | TP, FN, FP, TN = confusion_matrix(u, RA, k)
189 | precision_uk = TP / (TP + FP)
190 | precisions_u.append(precision_uk)
191 | precisions.append(precisions_u)
192 | print('precisions = \n{}'.format(precisions))
193 |
194 | 【 問04 】
195 | print('ranked_R = \n{}'.format(ranked_R))
196 | 【 問05 】
197 | print('ranked_like = \n{}'.format(ranked_like))
198 | 【 問06 】
199 | print('rel = \n{}'.format(rel))
200 | 【 問07 】
201 | print('APu = {}'.format(APu))
202 | 【 問08 】
203 | print('MAP = {:.3f}'.format(MAP))
204 | ```
205 |
206 | ###### 結果
207 |
208 | ```bash
209 | precisions =
210 | [[1.0, 1.0, 0.6666666666666666, 0.75, 0.6, 0.6, 0.6], [0.0, 0.5, 0.3333333333333333, 0.25, 0.4, 0.4, 0.4], [0.0, 0.0, 0.3333333333333333, 0.5, 0.4, 0.4]]
211 | ranked_R =
212 | [[ 5. 4. 3. 5. 2. 4. nan 2. nan nan]
213 | [ 3. 5. 3. 3. 4. 3. 2. nan nan nan]
214 | [ 3. 3. 5. 4. 3. 4. nan nan nan nan]]
215 | ranked_like =
216 | [[ True True False True False True False False False False]
217 | [False True False False True False False False False False]
218 | [False False True True False True False False False False]]
219 | rel =
220 | [[1 1 0 1 0 1 0 0 0 0]
221 | [0 1 0 0 1 0 0 0 0 0]
222 | [0 0 1 1 0 1 0 0 0 0]]
223 | APu = [0.917 0.45 0.417]
224 | MAP = 0.594
225 | ```
226 |
227 | このとき、次の問いに答えなさい。
228 |
229 | ### 04 評価値行列の並べ替え
230 | `RA`に示された順位にしたがって、`R`の各行をユーザごとの推薦順位の昇順に並べ替えた`ndarray`を生成するコードを書きなさい。生成した`ndarray`を`ranked_R`とすること。
231 |
232 | ★★★
233 | 1. リスト内包表記を使う。
234 | 2. `numpy.argsort()`を使う。
235 | 3. `numpy.array()`を使う。
236 |
237 | ### 05 好きなアイテムか否かの判定
238 | `ranked_R`において、評価値が4以上の要素には`True`を、4未満の要素には`False`を入れたブール値配列を生成するコードを書きなさい。得られたブール値配列を`ranked_like`とすること。
239 |
240 | ★
241 | 1. 比較演算子を使う。
242 |
243 | ### 06 好きなアイテムか否かの判定
244 | `ranked_like`において、`True`の要素には`1`を、`False`の要素には`0`を入れた`ndarray`を生成するコードを書きなさい。生成した`ndarray`を`rel`とすること。
245 |
246 | ★★★
247 | 1. リスト内包表記を使う。
248 | 2. `map()`を使う。
249 | 3. `list()`を使う。
250 | 4. `numpy.array()`を使う。
251 |
252 | ### 07 各ユーザのAP
253 | 上位`TOP_K`件の推薦リストについて各ユーザの$$\mathit{AP}_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`APu`とすること。
254 |
255 | ★★★
256 | 1. 二重のリスト内包表記を使う。
257 | 2. `numpy.sum()`を使う。
258 | 3. `numpy.array()`を使う。
259 |
260 | ### 08 MAP
261 | 上位`TOP_K`件の推薦リストについて$$\mathit{MAP}$$を求めるコードを書きなさい。得られた値を`MAP`とすること。
262 |
263 | ★★
264 | 1. `numpy.sum()`を使う。
265 | 2. `ndarray.size`を使う。
266 |
267 | ## DCG
268 | ユーザ$$u$$向けの推薦リストの$$\mathit{DCG}_{u}$$は次式で定義される。
269 |
270 | $$
271 | \mathit{DCG}_{u} = \sum_{i \in I_{u}^{\mathit{rec}}, k_{i} \leq K} \frac{r_{u,i}}{\max (1, \log_{\alpha} k_{i})}
272 | $$
273 |
274 | ここで、$$I_{u}^{\mathit{rec}}$$はユーザ$$u$$向けの推薦リストに含まれるアイテム集合である。$$k_{i}$$は推薦リストにおけるアイテム$$i$$の順位を表す。$$\alpha$$は対数の底であり、ここでは、$$\alpha = 2$$とする。
275 |
276 | ユーザ$$u$$向けの推薦リストの$\mathit{nDCG}_{u}$は次式で定義される。
277 |
278 | $$
279 | \mathit{nDCG}_{u} = \frac{\mathit{DCG}_{u}}{\mathit{IDCG}_{u}}
280 | $$
281 |
282 | ここで、$$\mathit{IDCG}_{u}$$は、ユーザ$$u$$のテストデータを理想的な順位(評価値が高い順)に並べ替えた推薦リストのDCGを表す。すべてのユーザのnDCGの平均値を$$\mathit{nDCG}$$とすると、次式で定義される。
283 |
284 | $$
285 | \mathit{nDCG} = \frac{1}{\mid U \mid} \sum_{u \in U} \mathit{nDCG}_{u}
286 | $$
287 |
288 | ###### コード
289 |
290 | ```python
291 | Iu_rec = [I[~np.isnan(RA[u])] for u in U]
292 | 【 問09 】
293 | print('DCGu = {}'.format(DCGu))
294 |
295 | 【 問10 】
296 | print('RI = \n{}'.format(RI))
297 | 【 問11 】
298 | print('Iu_recI = \n{}'.format(Iu_recI))
299 | 【 問12 】
300 | print('IDCGu = {}'.format(IDCGu))
301 | 【 問13 】
302 | print('nDCGu = {}'.format(nDCGu))
303 | 【 問14 】
304 | print('nDCG = {:.3f}'.format(nDCG))
305 | ```
306 |
307 | ###### 結果
308 |
309 | ```bash
310 | DCGu = [14.254 13.115 12.447]
311 | RI =
312 | [[ 1 3 5 8 2 4 6 7 9 10]
313 | [ 3 4 5 6 7 8 2 9 1 10]
314 | [ 2 7 4 1 3 5 8 6 9 10]]
315 | Iu_recI =
316 | [[0 1 2 4 5]
317 | [0 1 2 6 8]
318 | [0 2 3 4 5]]
319 | IDCGu = [15.816 13.685 14.316]
320 | nDCGu = [0.901 0.958 0.869]
321 | nDCG = 0.910
322 | ```
323 |
324 | このとき、次の問いに答えなさい。
325 |
326 | ### 09 各ユーザのDCG
327 | 各ユーザの$$\mathit{DCG}_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。ただし、$$\alpha$$は`ALPHA`とする。得られた`ndarray`を`DCGu`とすること。
328 |
329 | ★★★
330 | 1. 二重のリスト内包表記を使う。
331 | 2. `math.log()`を使う。
332 | 3. `numpy.max()`を使う。
333 | 4. `numpy.sum()`を使う。
334 | 5. `numpy.array()`を使う。
335 |
336 | ### 10 理想的な推薦順位
337 | `R`において、各ユーザにとっての理想的な推薦順位を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`RI`とすること。
338 |
339 | ★★★
340 | 1. `numpy.argsort()`を2回使う。
341 |
342 | ### 11 理想的な推薦リスト
343 | `RI`から上位`TOP_K`以内のアイテム集合を各ユーザにとっての理想的な推薦リストとする。このとき各ユーザにとっての理想的な推薦リストを`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`Iu_recI`とすること。
344 |
345 | ★★★
346 | 1. リスト内包表記を使う。
347 | 2. ブール値インデキシングを使う。
348 | 3. `numpy.array()`を使う。
349 |
350 | ### 12 各ユーザのIDCG
351 | 各ユーザの$$\mathit{IDCG}_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。ただし、$$\alpha$$は`ALPHA`とする。得られた`ndarray`を`IDCGu`とすること。
352 |
353 | ★★★
354 | 1. 二重のリスト内包表記を使う。
355 | 2. `math.log()`を使う。
356 | 3. `numpy.max()`を使う。
357 | 4. `numpy.sum()`を使う。
358 | 5. `numpy.array()`を使う。
359 |
360 | ### 13 各ユーザのnDCG
361 | 各ユーザの$$\mathit{nDCG}_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`nDCGu`とすること。
362 |
363 | ★
364 | 1. `DCGu`を参照する。
365 | 2. `IDCGu`を参照する。
366 |
367 | ### 14 nDCG
368 | $$\mathit{nDCG}$$を求めるコードを書きなさい。得られた値を`nDCG`とすること。
369 |
370 | ★★
371 | 1. リスト内包表記を使う。
372 | 2. `numpy.sum()`を使う。
373 | 3. `ndarray.size`を使う。
374 |
375 |
--------------------------------------------------------------------------------
/src/chap12.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "chap12.ipynb",
7 | "provenance": [],
8 | "collapsed_sections": []
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | }
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "markdown",
21 | "source": [
22 | "# 第12章 好き嫌い分類に基づく評価指標"
23 | ],
24 | "metadata": {
25 | "id": "PY6XkURfakYL"
26 | }
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "source": [
31 | "## テストデータと推薦リスト"
32 | ],
33 | "metadata": {
34 | "id": "KlnM8e_Falmz"
35 | }
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "source": [
40 | "## 準備"
41 | ],
42 | "metadata": {
43 | "id": "0by0CbRianH2"
44 | }
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": 1,
49 | "metadata": {
50 | "id": "AxQYHGPOaIZi"
51 | },
52 | "outputs": [],
53 | "source": [
54 | "import numpy as np\n",
55 | "\n",
56 | "# テストデータ\n",
57 | "R = np.array([\n",
58 | " [5, 4, 3, np.nan, 5, 4, 2, 2, np.nan, np.nan],\n",
59 | "])\n",
60 | "U = np.arange(R.shape[0])\n",
61 | "I = np.arange(R.shape[1])\n",
62 | "Iu = [I[~np.isnan(R)[u,:]] for u in U]\n",
63 | "\n",
64 | "# 推薦システムAによる推薦リスト\n",
65 | "RA = np.array([\n",
66 | " [1, 6, 3, np.nan, 4, 2, 5, 7, np.nan, np.nan],\n",
67 | "])\n",
68 | "\n",
69 | "# 推薦システムBによる推薦リスト\n",
70 | "RB = np.array([\n",
71 | " [4, 3, 1, np.nan, 6, 7, 2, 5, np.nan, np.nan],\n",
72 | "])"
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "source": [
78 | "## 混同行列"
79 | ],
80 | "metadata": {
81 | "id": "qOPyKBqMa7Je"
82 | }
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "source": [
87 | "### 01 好きなアイテムか否かの判定\n",
88 | "### 02 推薦されたアイテムか否かの判定\n",
89 | "### 03 好きなアイテムが推薦された数(TP)\n",
90 | "### 04 好きなアイテムが推薦されなかった数(FN)\n",
91 | "### 05 嫌いなアイテムが推薦された数(FP)\n",
92 | "### 06 嫌いなアイテムが推薦されなかった数(TN)"
93 | ],
94 | "metadata": {
95 | "id": "TDk369ArbBQS"
96 | }
97 | },
98 | {
99 | "cell_type": "code",
100 | "source": [
101 | "def confusion_matrix(u, RS, K):\n",
102 | " \"\"\"\n",
103 | " ユーザu向け推薦リストRSの上位K件における混同行列の各値を返す。\n",
104 | "\n",
105 | " Parameters\n",
106 | " ----------\n",
107 | " u : int\n",
108 | " ユーザuのID\n",
109 | " RS : ndarray\n",
110 | " 推薦リストRS\n",
111 | " K : int\n",
112 | " 上位K件\n",
113 | "\n",
114 | " Returns\n",
115 | " -------\n",
116 | " int\n",
117 | " TP\n",
118 | " int\n",
119 | " FN\n",
120 | " int\n",
121 | " FP\n",
122 | " int\n",
123 | " TN\n",
124 | " \"\"\"\n",
125 | " # 01\n",
126 | " like = R[u,Iu[u]]>=4\n",
127 | " print('like = {}'.format(like))\n",
128 | " \n",
129 | " # 02\n",
130 | " recommended = RS[u,Iu[u]]<=K\n",
131 | " print('recommended@{} = {}'.format(K, recommended))\n",
132 | " \n",
133 | " # 03\n",
134 | " TP = np.count_nonzero(np.logical_and(like, recommended))\n",
135 | " print('TP@{} = {}'.format(K, TP))\n",
136 | " \n",
137 | " # 04\n",
138 | " FN = np.count_nonzero(np.logical_and(like, ~recommended))\n",
139 | " print('FN@{} = {}'.format(K, FN))\n",
140 | " \n",
141 | " # 05\n",
142 | " FP = np.count_nonzero(np.logical_and(~like, recommended))\n",
143 | " print('FP@{} = {}'.format(K, FP))\n",
144 | "\n",
145 | " # 06\n",
146 | " TN = np.count_nonzero(np.logical_and(~like, ~recommended))\n",
147 | " print('TN@{} = {}'.format(K, TN))\n",
148 | "\n",
149 | " return TP, FN, FP, TN"
150 | ],
151 | "metadata": {
152 | "id": "pNmFIDi_a82i"
153 | },
154 | "execution_count": 2,
155 | "outputs": []
156 | },
157 | {
158 | "cell_type": "code",
159 | "source": [
160 | "u = 0\n",
161 | "K = 3\n",
162 | "TP, FN, FP, TN = confusion_matrix(u, RA, K)\n",
163 | "print('混同行列 = \\n{}'.format(np.array([[TP, FN], [FP, TN]])))"
164 | ],
165 | "metadata": {
166 | "colab": {
167 | "base_uri": "https://localhost:8080/"
168 | },
169 | "id": "ZreNwQ0ea_0r",
170 | "outputId": "5d428b71-6d2e-4a24-8afc-69c3a3513e28"
171 | },
172 | "execution_count": 3,
173 | "outputs": [
174 | {
175 | "output_type": "stream",
176 | "name": "stdout",
177 | "text": [
178 | "like = [ True True False True True False False]\n",
179 | "recommended@3 = [ True False True False True False False]\n",
180 | "TP@3 = 2\n",
181 | "FN@3 = 2\n",
182 | "FP@3 = 1\n",
183 | "TN@3 = 2\n",
184 | "混同行列 = \n",
185 | "[[2 2]\n",
186 | " [1 2]]\n"
187 | ]
188 | }
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "source": [
194 | "## 真陽性率と偽陽性率"
195 | ],
196 | "metadata": {
197 | "id": "zd9si8nNdmj5"
198 | }
199 | },
200 | {
201 | "cell_type": "markdown",
202 | "source": [
203 | "### 07 真陽性率(TPR)"
204 | ],
205 | "metadata": {
206 | "id": "mpHBT1k8dp7V"
207 | }
208 | },
209 | {
210 | "cell_type": "code",
211 | "source": [
212 | "TPR = TP / (TP + FN)\n",
213 | "print('TPR@{} = {:.3f}'.format(K, TPR))"
214 | ],
215 | "metadata": {
216 | "id": "1-VuXRsEduAl",
217 | "colab": {
218 | "base_uri": "https://localhost:8080/"
219 | },
220 | "outputId": "0eb6fe42-092a-47ec-a599-72d53e7e94a5"
221 | },
222 | "execution_count": 4,
223 | "outputs": [
224 | {
225 | "output_type": "stream",
226 | "name": "stdout",
227 | "text": [
228 | "TPR@3 = 0.500\n"
229 | ]
230 | }
231 | ]
232 | },
233 | {
234 | "cell_type": "markdown",
235 | "source": [
236 | "### 08 偽陽性率(FPR)\n",
237 | "\n"
238 | ],
239 | "metadata": {
240 | "id": "pEoe-xE4drXl"
241 | }
242 | },
243 | {
244 | "cell_type": "code",
245 | "source": [
246 | "FPR = FP / (FP + TN)\n",
247 | "print('FPR@{} = {:.3f}'.format(K, FPR))"
248 | ],
249 | "metadata": {
250 | "id": "8wqD54Uody2e",
251 | "colab": {
252 | "base_uri": "https://localhost:8080/"
253 | },
254 | "outputId": "b672e956-1944-428f-d3db-235e47803f86"
255 | },
256 | "execution_count": 5,
257 | "outputs": [
258 | {
259 | "output_type": "stream",
260 | "name": "stdout",
261 | "text": [
262 | "FPR@3 = 0.333\n"
263 | ]
264 | }
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "source": [
270 | "## 適合率と再現率"
271 | ],
272 | "metadata": {
273 | "id": "MQPmTeQjd6cg"
274 | }
275 | },
276 | {
277 | "cell_type": "markdown",
278 | "source": [
279 | "### 09 適合率"
280 | ],
281 | "metadata": {
282 | "id": "UytQsxEueDuY"
283 | }
284 | },
285 | {
286 | "cell_type": "code",
287 | "source": [
288 | "precision = TP / (TP + FP)\n",
289 | "print('precision@{} = {:.3f}'.format(K, precision))"
290 | ],
291 | "metadata": {
292 | "id": "xEoh63mCd7r2",
293 | "colab": {
294 | "base_uri": "https://localhost:8080/"
295 | },
296 | "outputId": "12b08699-deed-498e-9b12-efd035cd4109"
297 | },
298 | "execution_count": 6,
299 | "outputs": [
300 | {
301 | "output_type": "stream",
302 | "name": "stdout",
303 | "text": [
304 | "precision@3 = 0.667\n"
305 | ]
306 | }
307 | ]
308 | },
309 | {
310 | "cell_type": "markdown",
311 | "source": [
312 | "### 10 再現率"
313 | ],
314 | "metadata": {
315 | "id": "9cF6UzS7eJmg"
316 | }
317 | },
318 | {
319 | "cell_type": "code",
320 | "source": [
321 | "recall = TP / (TP + FN)\n",
322 | "print('recall@{} = {:.3f}'.format(K, recall))"
323 | ],
324 | "metadata": {
325 | "id": "Tbkr5t_DeRF7",
326 | "colab": {
327 | "base_uri": "https://localhost:8080/"
328 | },
329 | "outputId": "90b21bf4-c4a4-4a9a-e455-f5cb84f17c9c"
330 | },
331 | "execution_count": 7,
332 | "outputs": [
333 | {
334 | "output_type": "stream",
335 | "name": "stdout",
336 | "text": [
337 | "recall@3 = 0.500\n"
338 | ]
339 | }
340 | ]
341 | },
342 | {
343 | "cell_type": "markdown",
344 | "source": [
345 | "### 11 F値"
346 | ],
347 | "metadata": {
348 | "id": "84jVTHVLeLDy"
349 | }
350 | },
351 | {
352 | "cell_type": "code",
353 | "source": [
354 | "F1 = (2 * precision * recall) / (precision + recall)\n",
355 | "print('F1@{} = {:.3f}'.format(K, F1))"
356 | ],
357 | "metadata": {
358 | "colab": {
359 | "base_uri": "https://localhost:8080/"
360 | },
361 | "id": "nxOUoQEleUej",
362 | "outputId": "5c014981-e450-4362-dad1-2a434476b7e3"
363 | },
364 | "execution_count": 8,
365 | "outputs": [
366 | {
367 | "output_type": "stream",
368 | "name": "stdout",
369 | "text": [
370 | "F1@3 = 0.571\n"
371 | ]
372 | }
373 | ]
374 | }
375 | ]
376 | }
--------------------------------------------------------------------------------
/ja/chap04.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第4章 k近傍法 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第4章 k近傍法
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 | np.set_printoptions(precision=3)
17 |
18 | # 上位K件
19 | TOP_K = 3
20 | # 近傍アイテム数
21 | K_ITEMS = 3
22 | # しきい値
23 | THETA = 0
24 |
25 | Du = np.array([
26 | [5, 3, +1],
27 | [6, 2, +1],
28 | [4, 1, +1],
29 | [8, 5, -1],
30 | [2, 4, -1],
31 | [3, 6, -1],
32 | [7, 6, -1],
33 | [4, 2, np.nan],
34 | [5, 1, np.nan],
35 | [8, 6, np.nan],
36 | [3, 4, np.nan],
37 | [4, 7, np.nan],
38 | [4, 4, np.nan],
39 | ])
40 | I = np.arange(Du.shape[0])
41 | x = Du[:,:-1]
42 | ru = Du[:,-1]
43 |
44 | Iu = I[~np.isnan(ru)]
45 | Iup = I[ru==+1]
46 | Iun = I[ru==-1]
47 | Iu_not = np.setdiff1d(I, Iu)
48 | ```
49 |
50 | ## 距離
51 | アイテム$$i$$の特徴ベクトル$$\boldsymbol{x}_{i}$$とアイテム$$j$$の特徴ベクトル$$\boldsymbol{x}_{j}$$のユークリッド距離は次式で定義される。
52 |
53 | $$
54 | \mathrm{dist}(\boldsymbol{x}_{i}, \boldsymbol{x}_{j}) = \sqrt{\sum_{k=1}^{d} (x_{j,k} - x_{i,k})^{2}}
55 | $$
56 |
57 | ここで、$$d$$はベクトルの次元数である。
58 |
59 | この距離関数を次のコードのとおり定義する。
60 |
61 | ###### 関数
62 |
63 | ```python
64 | def dist(xi, xj):
65 | """
66 | 距離関数:アイテムiの特徴ベクトルxiとアイテムjの特徴ベクトルxjのユークリッド距離を返す。
67 |
68 | Parameters
69 | ----------
70 | xi : ndarray
71 | アイテムiの特徴ベクトル
72 | xj : ndarray
73 | アイテムjの特徴ベクトル
74 |
75 | Returns
76 | -------
77 | float
78 | ユークリッド距離
79 | """
80 | 【 問01 】
81 | return distance
82 | ```
83 |
84 | ###### コード
85 |
86 | ```python
87 | i = 7
88 | j = 2
89 | print('dist(x{}, x{}) = {:.3f}'.format(i, j, dist(x[i], x[j])))
90 | i = 7
91 | j = 3
92 | print('dist(x{}, x{}) = {:.3f}'.format(i, j, dist(x[i], x[j])))
93 | ```
94 |
95 | ###### 結果
96 |
97 | ```bash
98 | dist(x7, x2) = 1.000
99 | dist(x7, x3) = 5.000
100 | ```
101 |
102 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
103 |
104 | ### 01 ユークリッド距離
105 | $$\boldsymbol{x}_{i}$$と$$\boldsymbol{x}_{j}$$のユークリッド距離$$\mathrm{dist}(\boldsymbol{x}_{i}, \boldsymbol{x}_{j})$$を求めるコードを書きなさい。得られた値を`distance`とすること。
106 |
107 | ★★★
108 | 1. `ndarray.size`を使う。
109 | 2. リスト内包表記を使う。
110 | 3. `range()`を使う。
111 | 4. `numpy.sum()`を使う。
112 | 5. `numpy.sqrt()`を使う。
113 |
114 | ★★★
115 | 1. `numpy.sum()`を使う。
116 | 2. `numpy.sqrt()`を使う。
117 | 4. リスト内包表記を使わない。
118 |
119 | ## 近傍アイテム
120 | ユーザ$$u$$の未評価アイテム集合$$\overline{I}_{u}$$の各対象アイテム$$i \in \overline{I}_{u}$$について、ユーザ$$u$$の評価済みアイテム集合$$I_{u}$$の中から近傍アイテム集合$$I_{i}$$を見つける。ここでは、アイテム$$i$$とのユークリッド距離が最も近傍する$$k$$個のアイテムをアイテム$$i$$の近傍アイテム集合$$I_{i}$$とする。
121 |
122 | ### 02 アイテム-アイテム距離行列
123 | 各対象アイテム$$i \in \overline{I}_{u}$$と各アイテム$$j \in I_{u}$$のユークリッド距離$$\mathrm{dist}(\boldsymbol{x}_{i}, \boldsymbol{x}_{j})$$を要素としたアイテム-アイテム距離行列を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`D`とすること。
124 |
125 | ###### コード
126 |
127 | ```python
128 | 【 問02 】
129 | print('D = \n{}'.format(D[np.ix_(Iu_not,Iu)]))
130 | ```
131 |
132 | ###### 結果
133 |
134 | ```bash
135 | D =
136 | [[1.414 2. 1. 5. 2.828 4.123 5. ]
137 | [2. 1.414 1. 5. 4.243 5.385 5.385]
138 | [4.243 4.472 6.403 1. 6.325 5. 1. ]
139 | [2.236 3.606 3.162 5.099 1. 2. 4.472]
140 | [4.123 5.385 6. 4.472 3.606 1.414 3.162]
141 | [1.414 2.828 3. 4.123 2. 2.236 3.606]]
142 | ```
143 |
144 | ★★★
145 | 1. `numpy.zeros()`を使う。
146 | 2. 二重の`for`ループを使う。
147 |
148 | ★★★
149 | 1. 二重のリスト内包表記を使う。
150 | 2. `numpy.array()`を使う。
151 |
152 | ### 03 距離の昇順に並べ替えたインデックスの配列
153 | 各アイテム$$i \in I$$について、評価済みアイテム集合$$I_{u}$$を対象に距離の昇順に並べ替えたインデックスの配列を`ndarray`としてまとめて生成するコードを書きなさい。生成した`ndarray`を`Ii`とすること。
154 |
155 | ###### コード
156 |
157 | ```python
158 | 【 問03 】
159 | print('Ii = \n{}'.format(Ii))
160 | ```
161 |
162 | ###### 結果
163 |
164 | ```bash
165 | Ii =
166 | [[0 1 2 4 3 5 6]
167 | [1 0 2 3 6 4 5]
168 | [2 0 1 4 5 3 6]
169 | [3 6 0 1 5 2 4]
170 | [4 5 0 2 1 6 3]
171 | [5 4 0 6 1 2 3]
172 | [6 3 0 5 1 4 2]
173 | [2 0 1 4 5 3 6]
174 | [2 1 0 4 3 5 6]
175 | [3 6 0 1 5 4 2]
176 | [4 5 0 2 1 6 3]
177 | [5 6 4 0 3 1 2]
178 | [0 4 5 1 2 6 3]]
179 | ```
180 |
181 | ★★
182 | 1. `numpy.argsort()`を使う。
183 |
184 | ### 04 近傍k件のアイテムのインデックス配列
185 | `Ii`から`K_ITEMS`列目までを残したインデックスの配列を`ndarray`としてまとめて生成するコードを書きなさい。生成した`ndarray`を`Ii`とすること。
186 |
187 | ###### コード
188 |
189 | ```python
190 | 【 問04 】
191 | print('Ii = \n{}'.format(Ii))
192 | ```
193 |
194 | ###### 結果
195 |
196 | ```bash
197 | Ii =
198 | [[0 1 2]
199 | [1 0 2]
200 | [2 0 1]
201 | [3 6 0]
202 | [4 5 0]
203 | [5 4 0]
204 | [6 3 0]
205 | [2 0 1]
206 | [2 1 0]
207 | [3 6 0]
208 | [4 5 0]
209 | [5 6 4]
210 | [0 4 5]]
211 | ```
212 |
213 | ★
214 | 1. スライシングを使う。
215 |
216 | ### 05 各対象アイテムの近傍アイテム集合
217 | `Ii`から未評価アイテム集合$$\overline{I}_{u}$$の各対象アイテム$$i \in \overline{I}_{u}$$について、`(i: Ii[i])`のペアを要素とした辞書を生成するコードを書きなさい。生成した辞書を`Ii`とすること。
218 |
219 | ###### コード
220 |
221 | ```python
222 | 【 問05 】
223 | print('Ii = ')
224 | pprint.pprint(Ii)
225 | ```
226 |
227 | ###### 結果
228 |
229 | ```bash
230 | Ii =
231 | {7: array([2, 0, 1]),
232 | 8: array([2, 1, 0]),
233 | 9: array([3, 6, 0]),
234 | 10: array([4, 5, 0]),
235 | 11: array([5, 6, 4]),
236 | 12: array([0, 4, 5])}
237 | ```
238 |
239 | ★★★
240 | 1. 辞書内包表記を使う。
241 |
242 | ## 嗜好予測(多数決方式)
243 | 多数決方式によるユーザ$$u$$のアイテム$$i$$への予測評価値$$\hat{r}_{u,i}$$は次式により定義される。
244 |
245 | $$
246 | \hat{r}_{u,i} =
247 | \begin{cases}
248 | +1 & (\mid I_{i}^{+} \mid > \mid I_{i}^{-} \mid) \\
249 | -1 & (\mid I_{i}^{+} \mid < \mid I_{i}^{-} \mid) \\
250 | 0 & (\mid I_{i}^{+} \mid = \mid I_{i}^{-} \mid)
251 | \end{cases}
252 | $$
253 |
254 | ここで、$$I_{i}^{+}, I_{i}^{-}$$はアイテム$$i$$の近傍アイテム集合$$I_{i}$$のうち、ユーザ$$u$$がそれぞれ「好き」、「嫌い」と評価したアイテム集合を表す。この予測関数を次のコードのとおり定義する。
255 |
256 | ###### 関数
257 |
258 | ```python
259 | def predict1(u, i):
260 | """
261 | 予測関数(多数決方式):多数決方式によりユーザuのアイテムiに対する予測評価値を返す。
262 |
263 | Parameters
264 | ----------
265 | u : int
266 | ユーザuのID(ダミー)
267 | i : int
268 | アイテムiのID
269 |
270 | Returns
271 | -------
272 | float
273 | 予測評価値
274 | """
275 | 【 問06 】
276 | print('I{}+ = {}'.format(i, Iip))
277 | 【 問07 】
278 | print('I{}- = {}'.format(i, Iin))
279 |
280 | 【 問08 】
281 | return rui
282 | ```
283 |
284 | ###### コード
285 |
286 | ```python
287 | u = 0
288 | i = 7
289 | print('predict1({}, {}) = {:.3f}'.format(u, i, predict1(u, i)))
290 | u = 0
291 | i = 9
292 | print('predict1({}, {}) = {:.3f}'.format(u, i, predict1(u, i)))
293 | ```
294 |
295 | ###### 結果
296 |
297 | ```bash
298 | I7+ = [2 0 1]
299 | I7- = []
300 | predict1(0, 7) = 1.000
301 | I9+ = [0]
302 | I9- = [3 6]
303 | predict1(0, 9) = -1.000
304 | ```
305 |
306 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
307 |
308 | ### 06 近傍アイテム集合のうち「好き」と評価したアイテム集合
309 | アイテム$$i$$の近傍アイテム集合$$I_{i}$$のうち、ユーザ$$u$$が「好き」と評価したアイテム集合$$I_{i}^{+}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iip`とすること。
310 |
311 | ★★
312 | 1. `numpy.isin()`を使う。
313 | 2. ブール値インデキシングを使う。
314 |
315 | ### 07 近傍アイテム集合のうち「嫌い」と評価したアイテム集合
316 | アイテム$$i$$の近傍アイテム集合$$I_{i}$$のうち、ユーザ$$u$$が「嫌い」と評価したアイテム集合$$I_{i}^{-}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iin`とすること。
317 |
318 | ★★
319 | 1. `numpy.isin()`を使う。
320 | 2. ブール値インデキシングを使う。
321 |
322 | ### 08 多数決方式による予測評価値
323 | 多数決方式によるユーザ$$u$$のアイテム$$i$$への予測評価値$$\hat{r}_{u,i}$$を求めるコードを書きなさい。得られた値を`rui`とすること。
324 |
325 | ★
326 | 1. `if`文を使う。
327 | 2. `ndarray.size`を使う。
328 |
329 |
330 | ## 嗜好予測(平均方式)
331 | 平均方式によるユーザ$$u$$のアイテム$$i$$への予測評価値$$\hat{r}_{u,i}$$は次式により定義される。
332 |
333 | $$
334 | \hat{r}_{u,i} = \frac{1}{k} \sum_{j \in I_{i}} r_{u,j}
335 | $$
336 |
337 | この予測関数を次のコードのとおり定義する。ここで、`K_ITEMS`は近傍$$k$$件を表す定数である。
338 |
339 | ###### 関数
340 |
341 | ```python
342 | def predict2(u, i):
343 | """
344 | 予測関数(平均方式):平均方式によりユーザuのアイテムiに対する評価値を予測する。
345 |
346 | Parameters
347 | ----------
348 | u : int
349 | ユーザuのID(ダミー)
350 | i : int
351 | アイテムiのID
352 |
353 | Returns
354 | -------
355 | float
356 | 予測評価値
357 | """
358 | 【 問09 】
359 | return rui
360 | ```
361 |
362 | ###### コード
363 |
364 | ```python
365 | u = 0
366 | i = 7
367 | print('predict2({}, {}) = {:.3f}'.format(u, i, predict2(u, i)))
368 | u = 0
369 | i = 9
370 | print('predict2({}, {}) = {:.3f}'.format(u, i, predict2(u, i)))
371 | ```
372 |
373 | ###### 結果
374 |
375 | ```bash
376 | predict2(0, 7) = 1.000
377 | predict2(0, 9) = -0.333
378 | ```
379 |
380 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
381 |
382 | ### 09 平均方式による予測評価値
383 | 平均方式によるユーザ$$u$$のアイテム$$i$$への予測評価値$$\hat{r}_{u,i}$$を求めるコードを書きなさい。得られた値を`rui`とすること。
384 |
385 | ★★
386 | 1. リスト内包表記を使う。
387 | 2. `numpy.sum()`を使う。
388 |
389 |
390 | ## 推薦
391 |
392 | スコア関数$$\mathrm{score}(u, i)$$はユーザ$$u$$がアイテム$$i$$を好む程度をスコアとして返す関数であり、次式のように定義される。
393 |
394 | $$
395 | \mathrm{score}(u, i) = \hat{r}_{u,i}
396 | $$
397 |
398 | このスコア関数を次のコードのとおり定義する。
399 |
400 | ###### 関数
401 |
402 | ```python
403 | def score(u, i):
404 | """
405 | スコア関数:ユーザuのアイテムiに対するスコアを返す。
406 |
407 | Parameters
408 | ----------
409 | u : int
410 | ユーザuのID
411 | i : int
412 | アイテムiのID
413 |
414 | Returns
415 | -------
416 | float
417 | スコア
418 | """
419 | return predict2(u, i)
420 | ```
421 |
422 | 順序付け関数$$\mathrm{order}(u, I)$$は、アイテム集合$$I$$が与えられたとき、ユーザ$$u$$向けの推薦リストを返す関数である。ここでは、スコア上位$$K$$件のアイテム集合を推薦リストとして返すものとする。ただし、$$\mathrm{score}(u, i) < \theta$$となるアイテム$$i$$は推薦リストから除外する。この順序付け関数を次のコードのとおり定義する。
423 |
424 | ###### 関数
425 |
426 | ```python
427 | def order(u, I):
428 | """
429 | 順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
430 |
431 | Parameters
432 | ----------
433 | u : int
434 | ユーザuのID
435 | I : ndarray
436 | アイテム集合
437 |
438 | Returns
439 | -------
440 | list
441 | タプル(アイテムID: スコア)を要素にした推薦リスト
442 | """
443 | scores = {i: score(u, i) for i in I}
444 | 【 問10 】
445 | rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]
446 | return rec_list
447 | ```
448 |
449 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
450 |
451 | ### 10 推薦リスト
452 | `scores`から`score(u, i) < THETA`となるペアを除いた辞書を生成するコードを書きなさい。生成した辞書を`scores`とすること。
453 |
454 | ###### コード
455 |
456 | ```python
457 | u = 0
458 | rec_list = order(u, Iu_not)
459 | print('rec_list = ')
460 | for i, scr in rec_list:
461 | print('{}: {:.3f}'.format(i, scr))
462 | ```
463 |
464 | ###### 結果
465 |
466 | ```bash
467 | rec_list =
468 | 7: 1.000
469 | 8: 1.000
470 | ```
471 |
472 | ★★★
473 | 1. 辞書内包表記を使う。
474 | 2. `dict.items()`を使う。
475 | 3. 条件式を使う。
476 |
--------------------------------------------------------------------------------
/ja/chap06.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第6章 アイテムベース協調フィルタリング | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第6章 アイテムベース協調フィルタリング
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 | np.set_printoptions(precision=3)
17 |
18 | # 近傍アイテム数
19 | K_ITEMS = 3
20 | # 閾値
21 | THETA = 0
22 |
23 | R = np.array([
24 | [np.nan, 4, 3, 1, 2, np.nan],
25 | [5, 5, 4, np.nan, 3, 3 ],
26 | [4, np.nan, 5, 3, 2, np.nan],
27 | [np.nan, 3, np.nan, 2, 1, 1 ],
28 | [2, 1, 2, 4, np.nan, 3 ],
29 | ])
30 | U = np.arange(R.shape[0])
31 | I = np.arange(R.shape[1])
32 | Ui = [U[~np.isnan(R)[:,i]] for i in I]
33 | Iu = [I[~np.isnan(R)[u,:]] for u in U]
34 | ru_mean = np.nanmean(R, axis=1)
35 | R2 = R - ru_mean.reshape((ru_mean.size, 1))
36 | ```
37 |
38 | ## コサイン類似度
39 | アイテム$$i$$とアイテム$$j$$のコサイン類似度$$\mathrm{cos}(i, j)$$は次式で定義される。
40 |
41 | $$
42 | \mathrm{cos}(i, j) = \frac{\sum_{u \in U_{i,j}} r_{u,i} r_{u,j}}{\sqrt{\sum_{u \in U_{i,j}} r_{u,i}^{2}} \sqrt{\sum_{u \in U_{i,j}} r_{u,j}^{2}}}
43 | $$
44 |
45 | ここで、$$U_{i,j}$$はアイテム$$i$$とアイテム$$j$$の両方を評価済みのユーザ集合である。このコサイン類似度関数を次のコードのとおり定義する。
46 |
47 | ###### 関数
48 |
49 | ```python
50 | def cos(i, j):
51 | """
52 | 評価値行列Rにおけるアイテムiとアイテムjのコサイン類似度を返す。
53 |
54 | Parameters
55 | ----------
56 | i : int
57 | アイテムiのID
58 | j : int
59 | アイテムjのID
60 |
61 | Returns
62 | -------
63 | float
64 | コサイン類似度
65 | """
66 | Uij = np.intersect1d(Ui[i], Ui[j])
67 |
68 | 【 問01 】
69 | return cosine
70 | ```
71 |
72 | ###### コード
73 |
74 | ```python
75 | i = 0
76 | j = 4
77 | cosine = cos(i, j)
78 | print('cos({}, {}) = {:.3f}'.format(i, j, cosine))
79 | ```
80 |
81 | ###### 結果
82 |
83 | ```python
84 | cos(0, 4) = 0.996
85 | ```
86 |
87 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
88 |
89 | ### 01 アイテムiとアイテムjのコサイン類似度
90 | アイテム$$i$$とアイテム$$j$$のコサイン類似度$$\mathrm{cos}(i, j)$$を求めるコードを書きなさい。得られた値を`cosine`とすること。
91 |
92 | ★★★
93 | 1. リスト内包表記を使う
94 | 2. `numpy.sum()`を使う。
95 | 3. `numpy.sqrt()`を使う。
96 |
97 | ## 調整コサイン類似度
98 | 平均中心化評価値行列$$\boldsymbol{R}^{'}$$を用いると、アイテム$$i$$とアイテム$$j$$の調整コサイン類似度$$\mathrm{cos}(i, j)^{'}$$は次式で定義される。
99 |
100 | $$
101 | \mathrm{cos}(i, j)^{'} = \frac{\sum_{u \in U_{i,j}} r_{u,i}^{'} r_{u,j}^{'}}{\sqrt{\sum_{u \in U_{i,j}} r_{u,i}^{'2}} \sqrt{\sum_{u \in U_{i,j}} r_{u,j}^{'2}}}
102 | $$
103 |
104 | この調整コサイン類似度関数を次のコードのとおり定義する。
105 |
106 | ###### 関数
107 |
108 | ```python
109 | def adjusted_cos(i, j):
110 | """
111 | 評価値行列R2におけるアイテムiとアイテムjの調整コサイン類似度を返す。
112 |
113 | Parameters
114 | ----------
115 | i : int
116 | アイテムiのID
117 | j : int
118 | アイテムjのID
119 |
120 | Returns
121 | -------
122 | cosine : float
123 | 調整コサイン類似度
124 | """
125 | Uij = np.intersect1d(Ui[i], Ui[j])
126 |
127 | 【 問02 】
128 | return cosine
129 | ```
130 |
131 | ###### コード
132 |
133 | ```python
134 | i = 0
135 | j = 4
136 | cosine = adjusted_cos(i, j)
137 | print('cos({}, {})\' = {:.3f}'.format(i, j, cosine))
138 | ```
139 |
140 | ###### 結果
141 |
142 | ```python
143 | cos(0, 4)' = -0.868
144 | ```
145 |
146 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
147 |
148 | ### 02 アイテムiとアイテムjの調整コサイン類似度
149 | アイテム$$i$$とアイテム$$j$$の調整コサイン類似度$$\mathrm{cos}(i, j)^{'}$$を求めるコードを書きなさい。得られた値を`cosine`とすること。
150 |
151 | ★★★
152 | 1. リスト内包表記を使う。
153 | 2. `numpy.sum()`を使う。
154 | 3. `numpy.sqrt()`を使う。
155 |
156 | ## アイテム-アイテム類似度行列
157 | アイテム$$i$$とアイテム$$j$$のアイテム類似度$$\mathrm{sim}(i, j)$$は次式で定義される。
158 |
159 | $$
160 | \mathrm{sim}(i, j) = \mathrm{cos}(i, j)^{'}
161 | $$
162 |
163 | このアイテム類似度関数を次のコードのとおり定義する。
164 |
165 | ###### 関数
166 |
167 | ```python
168 | def sim(i, j):
169 | """
170 | アイテム類似度関数:アイテムiとアイテムjのアイテム類似度を返す。
171 |
172 | Parameters
173 | ----------
174 | i : int
175 | アイテムiのID
176 | j : int
177 | アイテムjのID
178 |
179 | Returns
180 | -------
181 | float
182 | アイテム類似度
183 | """
184 | return adjusted_cos(i, j)
185 | ```
186 |
187 | 各アイテム$$i$$と各アイテム$$j$$のアイテム類似度$$\mathrm{sim}(i, j)$$を要素とした行列をアイテム-アイテム類似度行列$$\boldsymbol{\mathcal{S}}$$とする。アイテム-アイテム類似度行列$$\boldsymbol{\mathcal{S}}$$は次式のとおりとなる。
188 |
189 | $$
190 | \boldsymbol{\mathcal{S}} = \left[
191 | \begin{array}{rrrrrr}
192 | 1.000 & 0.842 & 0.494 & -0.829 & -0.868 & -0.987 \\
193 | 0.842 & 1.000 & 0.896 & -0.788 & -0.910 & -0.942 \\
194 | 0.494 & 0.896 & 1.000 & -0.583 & -0.845 & -0.514 \\
195 | -0.829 & -0.788 & -0.583 & 1.000 & 0.469 & 0.497 \\
196 | -0.868 & -0.910 & -0.845 & 0.469 & 1.000 & 1.000 \\
197 | -0.987 & -0.942 & -0.514 & 0.497 & 1.000 & 1.000
198 | \end{array}
199 | \right]
200 | $$
201 |
202 | このとき、次の問いに答えなさい。
203 |
204 | ### 03 アイテム-アイテム類似度行列
205 | アイテム-アイテム類似度行列$$\boldsymbol{\mathcal{S}}$$を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`S`とすること。
206 |
207 | ###### コード
208 |
209 | ```python
210 | 【 問03 】
211 | print('S = \n{}'.format(S))
212 | ```
213 |
214 | ###### 結果
215 |
216 | ```
217 | S =
218 | [[ 1. 0.842 0.494 -0.829 -0.868 -0.987]
219 | [ 0.842 1. 0.896 -0.788 -0.91 -0.942]
220 | [ 0.494 0.896 1. -0.583 -0.845 -0.514]
221 | [-0.829 -0.788 -0.583 1. 0.469 0.497]
222 | [-0.868 -0.91 -0.845 0.469 1. 1. ]
223 | [-0.987 -0.942 -0.514 0.497 1. 1. ]]
224 | ```
225 |
226 | ★★★
227 | 1. `numpy.zeros()`を使う。
228 | 2. 二重の`for`ループを使う。
229 |
230 | ★★★
231 | 1. 二重のリスト内包表記を使う。
232 | 2. `numpy.array()`を使う。
233 |
234 | ## 類似アイテムの選定
235 | アイテム$$i$$の類似アイテム集合を$$I^{i} \subseteq I$$とする。ここでは、アイテム類似度が上位$$k$$件のアイテムを類似アイテム集合として選定する。ただし、類似度がしきい値$$\theta$$未満のアイテムは除外する。
236 |
237 | 次のコードはアイテム`i`の類似アイテム集合`Ii`を選定するためのコードである。ここで、`K_ITEMS`は上位$$k$$件を表す定数であり、`THETA`はしきい値$$\theta$$を表す定数である。
238 |
239 | ###### コード
240 |
241 | ```python
242 | # アイテム-アイテム類似度行列から対象アイテムを除外した辞書
243 | Ii = {i: {j: S[i,j] for j in I if i != j} for i in I}
244 | print('Ii = ')
245 | pprint.pprint(Ii)
246 | 【 問04 】
247 | print('Ii = ')
248 | pprint.pprint(Ii)
249 | 【 問05 】
250 | print('Ii = ')
251 | pprint.pprint(Ii)
252 | # 各アイテムの類似アイテム集合をまとめた辞書
253 | Ii = {i: np.array(list(Ii[i].keys())) for i in I}
254 | print('Ii = ')
255 | pprint.pprint(Ii)
256 | ```
257 |
258 | ###### 結果
259 |
260 | ```bash
261 | Ii =
262 | {0: {1: 0.8418791389638738,
263 | 2: 0.49365474375598073,
264 | 3: -0.8291725540450335,
265 | 4: -0.8682431421244593,
266 | 5: -0.987241120712647},
267 | 1: {0: 0.8418791389638738,
268 | 2: 0.896314672184623,
269 | 3: -0.7876958617794716,
270 | 4: -0.9099637547345425,
271 | 5: -0.9419581446623225},
272 | 2: {0: 0.49365474375598073,
273 | 1: 0.896314672184623,
274 | 3: -0.5833076828172804,
275 | 4: -0.8451542547285166,
276 | 5: -0.5144957554275266},
277 | 3: {0: -0.8291725540450335,
278 | 1: -0.7876958617794716,
279 | 2: -0.5833076828172804,
280 | 4: 0.4685212856658182,
281 | 5: 0.49665813370370504},
282 | 4: {0: -0.8682431421244593,
283 | 1: -0.9099637547345425,
284 | 2: -0.8451542547285166,
285 | 3: 0.4685212856658182,
286 | 5: 1.0},
287 | 5: {0: -0.987241120712647,
288 | 1: -0.9419581446623225,
289 | 2: -0.5144957554275266,
290 | 3: 0.49665813370370504,
291 | 4: 1.0}}
292 | Ii =
293 | {0: {1: 0.8418791389638738, 2: 0.49365474375598073, 3: -0.8291725540450335},
294 | 1: {0: 0.8418791389638738, 2: 0.896314672184623, 3: -0.7876958617794716},
295 | 2: {0: 0.49365474375598073, 1: 0.896314672184623, 5: -0.5144957554275266},
296 | 3: {2: -0.5833076828172804, 4: 0.4685212856658182, 5: 0.49665813370370504},
297 | 4: {2: -0.8451542547285166, 3: 0.4685212856658182, 5: 1.0},
298 | 5: {2: -0.5144957554275266, 3: 0.49665813370370504, 4: 1.0}}
299 | Ii =
300 | {0: {1: 0.8418791389638738, 2: 0.49365474375598073},
301 | 1: {0: 0.8418791389638738, 2: 0.896314672184623},
302 | 2: {0: 0.49365474375598073, 1: 0.896314672184623},
303 | 3: {4: 0.4685212856658182, 5: 0.49665813370370504},
304 | 4: {3: 0.4685212856658182, 5: 1.0},
305 | 5: {3: 0.49665813370370504, 4: 1.0}}
306 | Ii =
307 | {0: array([1, 2]),
308 | 1: array([2, 0]),
309 | 2: array([1, 0]),
310 | 3: array([5, 4]),
311 | 4: array([5, 3]),
312 | 5: array([4, 3])}
313 | ```
314 |
315 | ### 04 類似度上位k件のアイテム集合
316 | `Ii`から、各アイテム$$i \in I$$について類似度上位`K_ITEMS`件のみを残した辞書を生成するコードを書きなさい。生成した辞書を`Ii`とすること。
317 |
318 | ★★★
319 | 1. 辞書内包表記を使う。
320 | 2. `sorted()`を使う。
321 | 3. `dict.items()`を使う。
322 | 4. `lambda`式を使う。
323 | 5. スライシングを使う。
324 | 6. `dict()`を使う。
325 |
326 | ### 05 類似度がしきい値以上のアイテム集合
327 | `Ii`から、各アイテム$$i \in I$$について類似度がしきい値`THETA`以上のみを残した辞書を生成するコードを書きなさい。生成した辞書を`Ii`とすること。
328 |
329 | ★★★
330 | 1. 二重の辞書内包表記を使う。
331 | 2. `dict.items()`を使う。
332 | 3. 条件式を使う。
333 |
334 | ## 嗜好予測
335 | ユーザ$$u$$のアイテム$$i$$に対する予測評価値$$\hat{r}_{u,i}$$は次式で求められる。
336 |
337 | $$
338 | \hat{r}_{u,i} =
339 | \begin{cases}
340 | \frac{\sum_{j \in I_{u}^{i}} \mathrm{sim}(i, j) \cdot r_{u,j}}{\sum_{j \in I_{u}^{i}} \mid \mathrm{sim}(i,j) \mid} & (I_{u}^{i} \neq \emptyset) \\
341 | \overline{r}_{u} & (I_{u}^{i} = \emptyset)
342 | \end{cases}
343 | $$
344 |
345 | ここで、$$I_{u}^{i}$$は類似アイテム集合$$I^{i}$$の中でユーザ$$u$$が評価値を与えているアイテム集合を表す。$$\emptyset$$は空集合を表す。この予測関数を次のコードのとおり定義する。
346 |
347 | ###### 関数
348 |
349 | ```python
350 | def predict(u, i):
351 | """
352 | 予測関数:ユーザuのアイテムiに対する予測評価値を返す。
353 |
354 | Parameters
355 | ----------
356 | u : int
357 | ユーザuのID
358 | i : int
359 | アイテムiのID
360 |
361 | Returns
362 | -------
363 | float
364 | ユーザuのアイテムiに対する予測評価値
365 | """
366 | 【 問06 】
367 | print('I{}{} = {}'.format(i, u, Iiu))
368 |
369 | if Iiu.size <= 0: return ru_mean[u]
370 | 【 問07 】
371 |
372 | return rui_pred
373 | ```
374 |
375 | ###### コード
376 |
377 | ```python
378 | u = 0
379 | i = 0
380 | print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
381 | u = 0
382 | i = 5
383 | print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
384 | ```
385 |
386 | ###### 結果
387 |
388 | ```
389 | I00 = [1 2]
390 | r00 = 3.630
391 | I50 = [3 4]
392 | r05 = 1.668
393 | ```
394 |
395 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
396 |
397 | ### 06 類似アイテム集合の中でユーザuが評価値を与えているアイテム集合
398 | 類似アイテム集合$$I^{i}$$の中でユーザ$$u$$が評価値を与えているアイテム集合$$I_{u}^{i}$$を`ndarray`として生成するコードを書きなさい。生成された`ndarray`を`Iiu`とすること。
399 |
400 | ★
401 | 1. `numpy.intersect1d()`を使う。
402 |
403 | ### 07 予測評価値
404 | $$I_{u}^{i} \neq \emptyset$$のとき、ユーザ$$u$$のアイテム$$i$$に対する予測評価値$$\hat{r}_{u,i}$$を求めるコードを書きなさい。得られた値を`rui_pred`とすること。
405 |
406 | ★★★
407 | 1. リスト内包表記を使う。
408 | 2. `numpy.sum()`を使う。
409 | 3. `numpy.abs()`を使う。
410 |
--------------------------------------------------------------------------------
/ja/chap07.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第7章 評価履歴の次元削減 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第7章 評価履歴の次元削減
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import numpy as np
15 | import numpy.linalg as LA
16 | np.set_printoptions(precision=3)
17 |
18 | # 縮約後の次元数
19 | DIM = 2
20 |
21 | Du = np.array([
22 | [5, 3, 3, +1],
23 | [6, 2, 5, +1],
24 | [4, 1, 5, +1],
25 | [8, 5, 9, -1],
26 | [2, 4, 2, -1],
27 | [3, 6, 5, -1],
28 | [7, 6, 8, -1],
29 | [4, 2, 3, np.nan],
30 | [5, 1, 8, np.nan],
31 | [8, 6, 6, np.nan],
32 | [3, 4, 2, np.nan],
33 | [4, 7, 5, np.nan],
34 | [4, 4, 4, np.nan],
35 | ])
36 | I = np.arange(Du.shape[0])
37 | x = Du[:,:-1]
38 | ru = Du[:,-1]
39 | ```
40 |
41 | ## 分散共分散行列
42 |
43 | 特徴量$$k$$の分散$$s_{k}^{2}$$は次式で求められる。
44 |
45 | $$
46 | s_{k}^{2} = \frac{1}{\mid I \mid} \sum_{i \in I} (x_{i,k} - \overline{x}_{k})^{2}
47 | $$
48 |
49 | ここで、$$\overline{x}_{k}$$は特徴量$$k$$の平均値である。
50 |
51 | 特徴量$$x_{i,k}$$を標準化した値$$x_{i,k}^{'}$$は次式で求められる。
52 |
53 | $$
54 | x_{i,k}^{'} = \frac{x_{i,k} - \overline{x}_{k}}{s_{k}}
55 | $$
56 |
57 | 標準化された特徴量$$k$$と特徴量$$l$$の共分散$$s_{k,l}$$は次式で求められる。
58 |
59 | $$
60 | s_{k,l} = \frac{1}{\mid I \mid} \sum_{i \in I} x_{i,k}^{'} x_{i,l}^{'}
61 | $$
62 |
63 | 各特徴量について求めた分散、共分散をまとめると、次式のように分散共分散行列$$\boldsymbol{S}$$が得られる。
64 |
65 | $$
66 | \boldsymbol{S} = \left[
67 | \begin{array}{rrrrrr}
68 | 1.000 & 0.191 & 0.749 \\
69 | 0.191 & 1.000 & 0.163 \\
70 | 0.749 & 0.163 & 1.000
71 | \end{array}
72 | \right]
73 | $$
74 |
75 | このとき、次の問いに答えなさい。
76 |
77 | ### 01 各特徴量の平均値
78 | `x`において各特徴量の平均値$$\overline{x}_{k}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`xk_mean`とすること。
79 |
80 | ###### コード
81 |
82 | ```python
83 | 【 問01 】
84 | print('xk_mean = {}'.format(xk_mean))
85 | ```
86 |
87 | ###### 結果
88 |
89 | ```bash
90 | xk_mean = [4.846 3.923 5. ]
91 | ```
92 |
93 | ★
94 | 1. `numpy.mean()`を使う。
95 |
96 | ### 02 各特徴量の分散
97 | `x`において各特徴量の分散$$s_{k}^{2}$$を`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`s2`とすること。
98 |
99 | ###### コード
100 |
101 | ```python
102 | 【 問02 】
103 | print('s^2 = {}'.format(s2))
104 | ```
105 |
106 | ###### 結果
107 |
108 | ```bash
109 | s^2 = [3.361 3.763 4.769]
110 | ```
111 |
112 | ★
113 | 1. `numpy.var()`を使う。
114 |
115 | ★★★
116 | 1. 二重のリスト内包表記を使う。
117 | 2. `numpy.sum()`を使う。
118 | 3. `numpy.array()`を使う。
119 |
120 | ★★★
121 | 1. リスト内包表記を使う。
122 | 2. `numpy.sum()`を使う。
123 | 3. `numpy.array()`を使う。
124 | 4. 二重のリスト内包表記を使わない。
125 |
126 | ★★★
127 | 1. `numpy.sum()`を使う。
128 | 2. リスト内包表記を使わない。
129 |
130 |
131 | ### 03 各特徴量の標準化
132 | `x`において各特徴量を`ndarray`としてまとめて標準化するコードを書きなさい。得られた`ndarray`を`x2`とすること。
133 |
134 | ###### コード
135 |
136 | ```python
137 | 【 問03 】
138 | print('x\' = \n{}'.format(x2))
139 | ```
140 |
141 | ###### 結果
142 |
143 | ```bash
144 | x' =
145 | [[ 0.084 -0.476 -0.916]
146 | [ 0.629 -0.991 0. ]
147 | [-0.462 -1.507 0. ]
148 | [ 1.72 0.555 1.832]
149 | [-1.552 0.04 -1.374]
150 | [-1.007 1.071 0. ]
151 | [ 1.175 1.071 1.374]
152 | [-0.462 -0.991 -0.916]
153 | [ 0.084 -1.507 1.374]
154 | [ 1.72 1.071 0.458]
155 | [-1.007 0.04 -1.374]
156 | [-0.462 1.586 0. ]
157 | [-0.462 0.04 -0.458]]
158 | ```
159 |
160 | ★★★
161 | 1. 二重のリスト内包表記を使う。
162 | 2. `numpy.sqrt()`を使う。
163 | 3. `numpy.array()`を使う。
164 |
165 | ★★★
166 | 1. リスト内包表記を使う。
167 | 2. `numpy.sqrt()`を使う。
168 | 3. `numpy.array()`を使う。
169 | 4. 二重のリスト内包表記を使わない。
170 |
171 | ★★★
172 | 1. `numpy.sqrt()`を使う。
173 | 2. リスト内包表記を使わない。
174 |
175 | ### 04 標準化された特徴量kと特徴量lの共分散
176 | 標準化された特徴量$$k$$と特徴量$$l$$の共分散$$s_{k,l}$$を求めるコードを書きなさい。得られた値を`skl`とすること。
177 |
178 | ###### コード
179 |
180 | ```python
181 | k = 0
182 | l = 1
183 | 【 問04 】
184 | print('s{}{} = {:.3f}'.format(k, l, skl))
185 | ```
186 |
187 | ###### 結果
188 |
189 | ```bash
190 | s01 = 0.191
191 | ```
192 |
193 | ★
194 | 1. `numpy.cov()`を使う。
195 | 2. `numpy.cov()`において`bias=True`を指定する。
196 |
197 | ★★
198 | 1. リスト内包表記を使う。
199 | 2. `numpy.sum()`を使う。
200 |
201 | ★★
202 | 1. `numpy.sum()`を使う。
203 | 2. リスト内包表記を使わない。
204 |
205 | ### 05 分散共分散行列
206 | 分散共分散行列$$\boldsymbol{S}$$を`ndarray`として求めるコードを書きなさい。得られた`ndarray`を`S`とすること。
207 |
208 | ###### コード
209 |
210 | ```python
211 | 【 問05 】
212 | print('S = \n{}'.format(S))
213 | ```
214 |
215 | ###### 結果
216 |
217 | ```bash
218 | S =
219 | [[1. 0.191 0.749]
220 | [0.191 1. 0.163]
221 | [0.749 0.163 1. ]]
222 | ```
223 |
224 | ★
225 | 1. `numpy.cov()`を使う。
226 | 2. `numpy.cov()`において`bias=True`を指定する。
227 |
228 | ★★
229 | 1. `numpy.zeros()`を使う。
230 | 2. 二重の`for`ループを使う。
231 | 3. リスト内包表記を使う。
232 | 4. `numpy.sum()`を使う。
233 |
234 | ★★★
235 | 1. 三重のリスト内包表記を使う。
236 | 2. `numpy.sum()`を使う。
237 | 3. `numpy.array()`を使う。
238 |
239 | ## 固有値・固有ベクトル
240 | 分散共分散行列$$\boldsymbol{S}$$に対して、
241 |
242 | $$
243 | \boldsymbol{S} \boldsymbol{v} = \lambda \boldsymbol{v} \;\;\;\; (\boldsymbol{x} \neq \boldsymbol{0})
244 | $$
245 |
246 | を満たす$d$次元ベクトル$$\boldsymbol{v}$$と実数$$\lambda$$が存在するとき、$$\lambda$$を行列$\boldsymbol{S}$の固有値,$$\boldsymbol{v}$$を$$\lambda$$に関する行列$$\boldsymbol{S}$$の固有ベクトルという。このとき、次の問いに答えなさい。
247 |
248 | ### 06 固有値・固有ベクトル
249 |
250 | 分散共分散行列$$\boldsymbol{S}$$の固有値$$\lambda$$、固有ベクトル$$\boldsymbol{v}$$を求めるコードを書きなさい。`ndarray`として得られた固有値、固有ベクトルを、それぞれ`lmd`、`v`とすること。
251 |
252 | ###### コード
253 |
254 | ```python
255 | 【 問06 】
256 | print('λ = {}'.format(lmd))
257 | print('v = \n{}'.format(v))
258 | ```
259 |
260 | ###### 結果
261 |
262 | ```bash
263 | λ = [1.826 0.25 0.924]
264 | v =
265 | [[-0.679 -0.71 0.186]
266 | [-0.291 0.028 -0.956]
267 | [-0.674 0.704 0.225]]
268 | ```
269 |
270 | ★
271 | 1. `numpy.linalg.eig()`を使う。
272 |
273 | ### 07 固有値の降順にソートしたインデックス配列
274 | 固有値`lmd`について、降順にソートしたインデックスの配列を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`indices`とすること。
275 |
276 | ###### コード
277 |
278 | ```python
279 | 【 問07 】
280 | print('indices = {}'.format(indices))
281 | ```
282 |
283 | ###### 結果
284 |
285 | ```bash
286 | indices = [0 2 1]
287 | ```
288 |
289 | ★★
290 | 1. `numpy.argsort()`を使う。
291 |
292 | ### 08 固有値の降順に固有値配列をソート
293 | 固有値の降順にソートした固有値配列を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`lmd`とすること。
294 |
295 | ###### コード
296 |
297 | ```python
298 | 【 問08 】
299 | print('λ = {}'.format(lmd))
300 | ```
301 |
302 | ###### 結果
303 |
304 | ```bash
305 | λ = [1.826 0.924 0.25 ]
306 | ```
307 |
308 | ★
309 | 1. 整数配列インデキシングを使う。
310 |
311 | ### 09 固有値の降順に固有ベクトル配列をソート
312 | 固有値の降順にソートした固有ベクトル配列を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`v`とすること。
313 |
314 | ###### コード
315 |
316 | ```python
317 | 【 問09 】
318 | print('v = \n{}'.format(v))
319 | ```
320 |
321 | ###### 結果
322 |
323 | ```bash
324 | v =
325 | [[-0.679 0.186 -0.71 ]
326 | [-0.291 -0.956 0.028]
327 | [-0.674 0.225 0.704]]
328 | ```
329 |
330 | ★
331 | 1. 整数配列インデキシングを使う。
332 |
333 | ### 10 第d主成分までの固有ベクトル
334 | 第`DIM`主成分までの対応する固有ベクトルを列ベクトルとして並べた行列$$\boldsymbol{V}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`V`とすること。
335 |
336 | ###### コード
337 |
338 | ```python
339 | 【 問10 】
340 | print('V = \n{}'.format(V))
341 | ```
342 |
343 | ###### 結果
344 |
345 | ```bash
346 | V =
347 | [[-0.679 0.186]
348 | [-0.291 -0.956]
349 | [-0.674 0.225]]
350 | ```
351 |
352 | ★
353 | 1. スライシングを使う。
354 |
355 | ## 主成分得点
356 | アイテム$$i$$の第$$k$$主成分得点$$x_{i,k}^{''}$$は次式で求められる。
357 |
358 | $$
359 | x_{i,k}^{''} = \sum_{l = 1}^{d} x_{i,l}^{'} v_{k,l}
360 | $$
361 |
362 | アイテム$$i$$の次元削減後の特徴ベクトル$$\boldsymbol{x}_{i}^{''}$$は次式で求められる。
363 |
364 | $$
365 | \boldsymbol{x}_{i}^{''\mathsf{T}} = \boldsymbol{x}_{i}^{'\mathsf{T}} \boldsymbol{V}
366 | $$
367 |
368 | このとき、次の問いに答えなさい。
369 |
370 | ### 11 アイテムiの第k主成分得点
371 | アイテム$$i$$の第$$k$$主成分得点$$x_{i,k}^{''}$$を求めるコードを書きなさい。得られた値を`xik3`とすること。
372 |
373 | ###### コード
374 |
375 | ```python
376 | i = 0
377 | k = 0
378 | 【 問11 】
379 | print('x{}{}\'\' = {:.3f}'.format(i, k, xik3))
380 | ```
381 |
382 | ###### 結果
383 |
384 | ```bash
385 | x00'' = 0.699
386 | ```
387 |
388 | ★★
389 | 1. リスト内包表記を使う。
390 | 2. `numpy.sum()`を使う。
391 |
392 | ### 12 各アイテムの次元削減後の特徴ベクトル
393 | 各アイテムの次元削減後の特徴ベクトルを`ndarray`としてまとめて求めるコードを書きなさい。得られた`ndarray`を`x3`とすること。
394 |
395 | ###### コード
396 |
397 | ```python
398 | 【 問12 】
399 | print('x\'\' = \n{}'.format(x3))
400 | ```
401 |
402 | ###### 結果
403 |
404 | ```bash
405 | x'' =
406 | [[ 0.699 0.264]
407 | [-0.139 1.065]
408 | [ 0.752 1.355]
409 | [-2.564 0.202]
410 | [ 1.969 -0.636]
411 | [ 0.373 -1.211]
412 | [-2.035 -0.496]
413 | [ 1.219 0.656]
414 | [-0.545 1.766]
415 | [-1.788 -0.601]
416 | [ 1.598 -0.535]
417 | [-0.148 -1.603]
418 | [ 0.611 -0.227]]
419 | ```
420 |
421 | ★
422 | 1. `@`演算子を使う。
423 |
424 | ## 寄与率
425 | 第$$k$$主成分の寄与率は次式で求められる。
426 |
427 | $$
428 | \frac{\lambda_{k}}{\sum_{l=1}^{d} \lambda_{l}}
429 | $$
430 |
431 | 第$$k$$主成分までの累積寄与率は次式で求められる。
432 |
433 | $$
434 | \frac{\sum_{l=1}^{k} \lambda_{l}}{\sum_{l=1}^{d} \lambda_{l}}
435 | $$
436 |
437 | このとき、次の問いに答えなさい。
438 |
439 | ### 13 第k主成分の寄与率
440 | 第$$k$$主成分の寄与率を求めるコードを書きなさい。得られた値を`pk`とすること。
441 |
442 | ###### コード
443 |
444 | ```python
445 | k = 0
446 | 【 問13 】
447 | print('第{}主成分の寄与率 = {:.3f}'.format(k+1, pk))
448 | ```
449 |
450 | ###### 結果
451 |
452 | ```bash
453 | 第1主成分の寄与率 = 0.609
454 | ```
455 |
456 | ★★
457 | 1. リスト内包表記を使う。
458 | 2. `numpy.sum()`を使う。
459 |
460 | ### 14 第k主成分までの累積寄与率
461 | 第$$k$$主成分までの累積寄与率を求めるコードを書きなさい。得られた値を`ck`とすること。
462 |
463 | ###### コード
464 |
465 | ```python
466 | k = 2
467 | 【 問14 】
468 | print('第{}主成分までの累積寄与率 = {:.3f}'.format(k, ck))
469 | ```
470 |
471 | ###### 結果
472 |
473 | ```bash
474 | 第2主成分までの累積寄与率 = 0.917
475 | ```
476 |
477 | ★★
478 | 1. リスト内包表記を使う。
479 | 2. `numpy.sum()`を使う。
480 |
481 | ## 推薦
482 | 次元削減後の評価履歴$$D_{u}^{'}$$は、次式のとおり、次元削減後の特徴ベクトル$$\boldsymbol{x}_{i}^{''}$$と元の評価値$$r_{u,i}$$を結合することで得られる。
483 |
484 | $$
485 | D_{u}^{'} = \left[
486 | \begin{array}{rrr}
487 | 0.699 & 0.264 & +1 \\
488 | -0.139 & 1.065 & +1 \\
489 | 0.752 & 1.355 & +1 \\
490 | -2.564 & 0.202 & -1 \\
491 | 1.969 & -0.636 & -1 \\
492 | 0.373 & -1.211 & -1 \\
493 | -2.035 & -0.496 & -1 \\
494 | 1.219 & 0.656 & ? \\
495 | -0.545 & 1.766 & ? \\
496 | -1.788 & -0.601 & ? \\
497 | 1.598 & -0.535 & ? \\
498 | -0.148 & -1.603 & ? \\
499 | 0.611 & -0.227 & ?
500 | \end{array}
501 | \right]
502 | $$
503 |
504 | このとき、次の問いに答えなさい。
505 |
506 | ### 15 次元削減後の評価履歴
507 | 次元削減後の評価履歴$$D_{u}^{'}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Du2`とすること。
508 |
509 | ###### コード
510 |
511 | ```python
512 | 【 問15 】
513 | print('R\' = \n{}'.format(Du2))
514 | ```
515 |
516 | ###### 結果
517 |
518 | ```bash
519 | Du' =
520 | [[ 0.699 0.264 1. ]
521 | [-0.139 1.065 1. ]
522 | [ 0.752 1.355 1. ]
523 | [-2.564 0.202 -1. ]
524 | [ 1.969 -0.636 -1. ]
525 | [ 0.373 -1.211 -1. ]
526 | [-2.035 -0.496 -1. ]
527 | [ 1.219 0.656 nan]
528 | [-0.545 1.766 nan]
529 | [-1.788 -0.601 nan]
530 | [ 1.598 -0.535 nan]
531 | [-0.148 -1.603 nan]
532 | [ 0.611 -0.227 nan]]
533 | ```
534 |
535 | ★★
536 | 1. `numpy.hstack()`を使う。
537 | 2. `ndarray.reshape()`を使う。
538 |
539 | ★★
540 | 1. `numpy.append()`を使う。
541 | 2. `ndarray.reshape()`を使う。
542 |
543 | ★★
544 | 1. `numpy.concatenate()`を使う。
545 | 2. `ndarray.reshape()`を使う。
546 |
--------------------------------------------------------------------------------
/ja/chap09.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第9章 単純ベイズ分類器 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第9章 単純ベイズ分類器
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 | from fractions import Fraction
17 |
18 | # 上位K件
19 | TOP_K = 3
20 | # スムージングパラメタ
21 | ALPHA = 1
22 | # クラス数
23 | N = 2
24 | # 各特徴量がとりうる値のユニーク数
25 | M = [2, 2, 2, 2, 2, 2]
26 | # しきい値
27 | THETA = 0.5
28 |
29 | Du = np.array([
30 | [1, 0, 0, 0, 1, 0, +1],
31 | [0, 1, 0, 0, 1, 0, +1],
32 | [1, 1, 0, 0, 1, 0, +1],
33 | [1, 0, 0, 1, 1, 0, +1],
34 | [1, 0, 0, 0, 0, 1, +1],
35 | [0, 1, 0, 1, 0, 1, +1],
36 | [0, 0, 1, 0, 1, 0, -1],
37 | [0, 0, 1, 1, 1, 0, -1],
38 | [0, 1, 0, 0, 1, 1, -1],
39 | [0, 0, 1, 0, 0, 1, -1],
40 | [1, 1, 0, 1, 1, 0, np.nan],
41 | [0, 0, 1, 0, 1, 1, np.nan],
42 | [0, 1, 1, 1, 1, 0, np.nan],
43 | ])
44 | I = np.arange(Du.shape[0])
45 | x = Du[:,:-1]
46 | ru = Du[:,-1]
47 |
48 | Iu = I[~np.isnan(ru)]
49 | Iu_not = np.setdiff1d(I, Iu)
50 | DuL = Du[Iu]
51 | xL = x[Iu]
52 | ruL = ru[Iu]
53 | DuU = Du[Iu_not]
54 | xU = x[Iu_not]
55 | ```
56 |
57 | ## 問題設定
58 | アイテム$$i$$ついて好む確率、嫌う確率はそれぞれ次式のように表される。
59 |
60 | $$
61 | P(R = +1 \mid x_{i,1}, \ldots, x_{i,d})
62 | $$
63 |
64 | $$
65 | P(R = -1 \mid x_{i,1}, \ldots, x_{i,d})
66 | $$
67 |
68 | ここで、$$d$$は特徴ベクトル$$\boldsymbol{x}_{i}$$の次元数である。ベイズの定理および単純ベイズ仮定を用いると、上式はそれぞれ次式のように表される。
69 |
70 | $$
71 | \frac{P(R = +1) \prod_{k=1}^{d} P(X_{k} = x_{i,k} \mid R = +1)} {P(x_{i,1}, \ldots, x_{i,d})}
72 | $$
73 |
74 | $$
75 | \frac{P(R = -1) \prod_{k=1}^{d} P(X_{k} = x_{i,k} \mid R = -1)} {P(x_{i,1}, \ldots, x_{i,d})}
76 | $$
77 |
78 |
79 | ## 事前確率
80 |
81 | 上式の$$P(R = +1)$$、$$P(R = -1)$$は事前確率であり、ユーザ$$u$$が好む/嫌う確率を表す。それぞれ次式で表される。
82 |
83 | $$
84 | P(R = +1) = \frac{\mid D^{L+}_{u} \mid}{\mid D^{L}_{u} \mid}
85 | $$
86 |
87 | $$
88 | P(R = -1) = \frac{\mid D^{L-}_{u} \mid}{\mid D^{L}_{u} \mid}
89 | $$
90 |
91 | ここで、$$\mid D_{u}^{L} \mid$$は訓練事例数である。$$\mid D_{u}^{L+} \mid$$、$$\mid D_{u}^{L-} \mid$$はそれぞれ訓練事例に含まれる正事例数、負事例数である。これらの事前確率を返す関数を次のコードのとおり定義する。
92 |
93 | ###### 関数
94 |
95 | ```python
96 | def P_prior(r):
97 | """
98 | 評価値がrとなる事前確率を返す。
99 |
100 | Parameters
101 | ----------
102 | r : int
103 | 評価値
104 |
105 | Returns
106 | -------
107 | Fraction
108 | 事前確率
109 | """
110 | 【 問01 】【 問06 】
111 | 【 問02 】【 問07 】
112 | prob = Fraction(num, den, _normalize=False)
113 | return prob
114 | ```
115 |
116 | ###### コード
117 |
118 | ```python
119 | r = +1
120 | print('P(R={:+}) = {}'.format(r, P_prior(r)))
121 | r = -1
122 | print('P(R={:+}) = {}'.format(r, P_prior(r)))
123 | ```
124 |
125 | ###### 結果
126 |
127 | ```bash
128 | P(R=+1) = 6/10
129 | P(R=-1) = 4/10
130 | ```
131 |
132 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
133 |
134 | ### 01 評価値がrとなる事前確率(分子)
135 | 事前確率の式の分子を求めるコードを書きなさい。得られた値を`num`とすること。
136 |
137 | ★
138 | 1. `ndarray.shape`を使う。
139 | 2. ブール値インデキシングを使う。
140 |
141 | ### 02 評価値がrとなる事前確率(分母)
142 | 事前確率の式の分母を求めるコードを書きなさい。得られた値を`den`とすること。
143 |
144 | ★
145 | 1. `ndarray.shape`を使う。
146 |
147 | ## 特徴量kに関する条件付き確率
148 | 上式の$$P(X_{k} = x_{i,k} \mid R = +1)$$、$$P(X_{k} = x_{i,k} \mid R = -1)$$は、特徴量$$k$$に関する条件付き確率であり、それぞれ次式のように表される。
149 |
150 | $$
151 | P(X_{k} = x_{i,k} \mid R = +1) = \frac{\mid D^{L+}_{u}(x_{i,k}) \mid}{\mid D^{L+}_{u} \mid}
152 | $$
153 |
154 | $$
155 | P(X_{k} = x_{i,k} \mid R = -1) = \frac{\mid D^{L-}_{u}(x_{i,k}) \mid}{\mid D^{L-}_{u} \mid}
156 | $$
157 |
158 | ここで、$$\mid D^{L+}_{u}(x_{i,k}) \mid$$は評価値が$$+1$$である訓練事例のうち、属性$$k$$の特徴量が対象アイテム$$i$$の特徴量$$x_{i,k}$$に一致する事例数を表す。同様に、$$\mid D^{L-}_{u}(x_{i,k}) \mid$$は評価値が$$-1$$である訓練事例のうち、属性$$k$$の特徴量が対象アイテム$$i$$の特徴量$$x_{i,k}$$に一致する事例数を表す。これらの条件付き確率を返す関数を次のコードのとおり定義する。
159 |
160 | ###### 関数
161 |
162 | ```python
163 | def P_cond(i, k, r):
164 | """
165 | 評価値がrとなる条件下でアイテムiの特徴量kに関する条件付き確率を返す。
166 |
167 | Parameters
168 | ----------
169 | i : int
170 | アイテムiのID
171 | k : int
172 | 特徴量kのインデックス
173 | r : int
174 | 評価値
175 |
176 | Returns
177 | -------
178 | Fraction
179 | 条件付き確率
180 | """
181 | 【 問03 】【 問08 】
182 | 【 問04 】【 問09 】
183 | prob = Fraction(num, den, _normalize=False)
184 | return prob
185 | ```
186 |
187 | ###### コード
188 |
189 | ```python
190 | i = 10
191 | k = 0
192 | r = +1
193 | print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
194 | r = -1
195 | print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
196 | ```
197 |
198 | ###### 結果
199 |
200 | ```bash
201 | P(X0=x10,0|R=+1) = 4/6
202 | P(X0=x10,0|R=-1) = 0/4
203 | ```
204 |
205 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
206 |
207 | ### 03 特徴量kに関する条件付き確率(分子)
208 | 特徴量$$k$$に関する条件付き確率の式の分子を求めるコードを書きなさい。得られた値を`num`とすること。
209 |
210 | ★★★
211 | 1. `ndarray.shape`を使う。
212 | 2. ブール値インデキシングを使う。
213 |
214 | ### 04 特徴量kに関する条件付き確率(分母)
215 | 特徴量$$k$$に関する条件付き確率の式の分母を求めるコードを書きなさい。得られた値を`den`とすること。
216 |
217 | ★
218 | 1. `ndarray.shape`を使う。
219 |
220 | ## 嗜好予測
221 | 次の関数は、アイテム`i`の評価値が`r`となる確率を返す関数である。
222 |
223 | ###### 関数
224 |
225 | ```python
226 | def P(i, r):
227 | """
228 | アイテムiの評価値がrとなる確率を返す。
229 |
230 | Parameters
231 | ----------
232 | i : int
233 | アイテムiのID
234 | r : int
235 | 評価値
236 |
237 | Returns
238 | -------
239 | Fraction
240 | 事前確率
241 | list of Fraction
242 | 各特徴量に関する条件付き確率
243 | float
244 | 好き嫌いの確率
245 | """
246 | pp = P_prior(r)
247 | pk = [P_cond(i, k, r) for k in range(0, x.shape[1])]
248 | 【 問05 】
249 | return pp, pk, prob
250 | ```
251 |
252 | ###### コード
253 |
254 | ```python
255 | i = 10
256 | r = +1
257 | pp, pk, prob = P(i, r)
258 | left = 'P(R={:+}|'.format(r) + ','.join(map(str, map(int, x[i]))) + ')'
259 | right = str(pp) + '×' + '×'.join(map(str, pk))
260 | print('{} = {} = {:.3f}'.format(left, right, prob))
261 |
262 | r = -1
263 | pp, pk, prob = P(i, r)
264 | left = 'P(R={:+}|'.format(r) + ','.join(map(str, map(int, x[i]))) + ')'
265 | right = str(pp) + '×' + '×'.join(map(str, pk))
266 | print('{} = {} = {:.3f}'.format(left, right, prob))
267 | ```
268 |
269 | ###### 結果
270 |
271 | ```bash
272 | P(R=+1|1,1,0,1,1,0) = 6/10×4/6×3/6×6/6×2/6×4/6×4/6 = 0.030
273 | P(R=-1|1,1,0,1,1,0) = 4/10×0/4×1/4×1/4×1/4×3/4×2/4 = 0.000
274 | ```
275 |
276 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
277 |
278 | ### 05 好き嫌いの確率
279 | `pp`と`pk`を使って好き嫌いの確率を求めるコードを書きなさい。得られた値を`prob`とすること。
280 |
281 | ★★
282 | 1. `numpy.prod()`を使う。
283 | 2. `float()`を使う。
284 |
285 | ## ラプラススムージング
286 | ラプラススムージングを適用すると、事前確率はそれぞれ次式のように表される。
287 |
288 | $$
289 | P(R = +1) = \frac{\mid D^{L+}_{u} \mid + \alpha}{\mid D^{L}_{u} \mid + \alpha n}
290 | $$
291 |
292 | $$
293 | P(R = -1) = \frac{\mid D^{L-}_{u} \mid + \alpha}{\mid D^{L}_{u} \mid + \alpha n}
294 | $$
295 |
296 | ここで、$$\alpha$$はスムージングパラメタであり、$$n$$はクラス数である。これらの事前確率を返す関数を`P_prior()`のとおり定義する。同様に、特徴量$$k$$に関する条件付き確率はそれぞれ次式のように表される。
297 |
298 | $$
299 | P(X_{k} = x_{i,k} \mid R = +1) = \frac{\mid D^{L+}_{u}(x_{i,k}) \mid + \alpha}{\mid D^{L+}_{u} \mid + \alpha m_{k}}
300 | $$
301 |
302 | $$
303 | P(X_{k} = x_{i,k} \mid R = -1) = \frac{\mid D^{L-}_{u}(x_{i,k}) \mid + \alpha}{\mid D^{L-}_{u} \mid + \alpha m_{k}}
304 | $$
305 |
306 | ここで、$$m_{k}$$は特徴量$$k$$がとりうる値のユニーク数である。これらの条件付き確率を返す関数を`P_cond()`のとおり定義する。
307 |
308 | ###### コード
309 |
310 | ```python
311 | r = +1
312 | print('P(R={:+}) = {}'.format(r, P_prior(r)))
313 | r = -1
314 | print('P(R={:+}) = {}'.format(r, P_prior(r)))
315 | ```
316 |
317 | ###### 結果
318 |
319 | ```bash
320 | P(R=+1) = 7/12
321 | P(R=-1) = 5/12
322 | ```
323 |
324 | ###### コード
325 |
326 | ```python
327 | i = 10
328 | k = 0
329 | r = +1
330 | print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
331 | r = -1
332 | print('P(X{}=x{},{}|R={:+}) = {}'.format(k, i, k, r, P_cond(i, k, r)))
333 | ```
334 |
335 | ###### 結果
336 |
337 | ```bash
338 | P(X0=x10,0|R=+1) = 5/8
339 | P(X0=x10,0|R=-1) = 1/6
340 | ```
341 |
342 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
343 |
344 |
345 | ### 06 評価値がrとなる事前確率(分子)(ラプラススムージングあり)
346 | ラプラススムージングを適用したとき、事前確率の式の分子を求めるコードを書きなさい。ただし、スムージングパラメタを`ALPHA`とする。得られた値を`num`とすること。
347 |
348 | ★
349 | 1. `ndarray.shape`を使う。
350 | 2. ブール値インデキシングを使う。
351 |
352 | ### 07 評価値がrとなる事前確率(分母)(ラプラススムージングあり)
353 | ラプラススムージングを適用したとき、事前確率の式の分母を求めるコードを書きなさい。ただし、スムージングパラメタを`ALPHA`、クラス数を`N`とする。得られた値を`den`とすること。
354 |
355 | ★
356 | 1. `ndarray.shape`を使う。
357 |
358 | ### 08 特徴量kに関する条件付き確率(分子)(ラプラススムージングあり)
359 | ラプラススムージングを適用したとき、特徴量$$k$$に関する条件付き確率の式の分子を求めるコードを書きなさい。ただし、スムージングパラメタを`ALPHA`とする。得られた値を`num`とすること。
360 |
361 | ★★★
362 | 1. `ndarray.shape`を使う。
363 | 2. ブール値インデキシングを使う。
364 |
365 | ### 09 特徴量kに関する条件付き確率(分母)(ラプラススムージングあり)
366 | ラプラススムージングを適用したとき、特徴量$$k$$に関する条件付き確率の式の分母を求めるコードを書きなさい。ただし、スムージングパラメタを`ALPHA`、各特徴量がとりうる値のユニーク数の配列を`M`とする。得られた値を`den`とすること。
367 |
368 | ★
369 | 1. `ndarray.shape`を使う。
370 |
371 | ## 推薦
372 | スコア関数$$\mathrm{score}(u, i)$$はユーザ$$u$$がアイテム$$i$$を好む程度をスコアとして返す関数であり、次式のように定義される。
373 |
374 | $$
375 | \mathrm{score}(u, i) = \frac{P(R = +1) \prod_{k=1}^{d} P(X_{k} = x_{i,k} \mid R = +1)}{P(R = +1) \prod_{k=1}^{d} P(X_{k} = x_{i,k} \mid R = +1) + P(R = -1) \prod_{k=1}^{d} P(X_{k} = x_{i,k} \mid R = -1)}
376 | $$
377 |
378 | スコア関数を次のコードのとおり定義する。
379 |
380 | ###### 関数
381 |
382 | ```python
383 | def score(u, i):
384 | """
385 | スコア関数:ユーザuのアイテムiに対するスコアを返す。
386 |
387 | Parameters
388 | ----------
389 | u : int
390 | ユーザuのID(ダミー)
391 | i : int
392 | アイテムiのID
393 |
394 | Returns
395 | -------
396 | float
397 | スコア
398 | """
399 | 【 問10 】
400 | return scr
401 | ```
402 |
403 |
404 | 順序付け関数$$\mathrm{order}(u, I)$$は、アイテム集合$$I$$が与えられたとき、ユーザ$$u$$向けの推薦リストを返す関数である。ここでは、スコア上位$$K$$件のアイテム集合を推薦リストとして返すものとする。ただし、$$\mathrm{score}(u, i) < \theta$$となるアイテム$$i$$は推薦リストから除外する。この順序付け関数を次のコードのとおり定義する。
405 |
406 | ###### 関数
407 |
408 | ```python
409 | def order(u, I):
410 | """
411 | 順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。
412 |
413 | Parameters
414 | ----------
415 | u : int
416 | ユーザuのID
417 | I : ndarray
418 | アイテム集合
419 |
420 | Returns
421 | -------
422 | list
423 | タプル(アイテムID: スコア)を要素にした推薦リスト
424 | """
425 | scores = {i: score(u, i) for i in I}
426 | 【 問11 】
427 | rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]
428 | return rec_list
429 | ```
430 |
431 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
432 |
433 | ### 10 ユーザuのアイテムiに対するスコア
434 | ユーザ$$u$$のアイテム$$i$$に対するスコアを求めるコードを書きなさい。得られた値を`scr`とすること。
435 |
436 | ###### コード
437 |
438 | ```python
439 | u = 0
440 | scores = {i: score(u, i) for i in Iu_not}
441 | print('scores = ')
442 | pprint.pprint(scores)
443 | ```
444 |
445 | ###### 結果
446 |
447 | ```bash
448 | scores =
449 | {10: 0.9646054787625311, 11: 0.05517691284650013, 12: 0.18936236007174223}
450 | ```
451 |
452 | ★★
453 | 1. `P()`関数を呼ぶ。
454 |
455 | ### 11 推薦リスト
456 | `scores`から`score(u, i) < THETA`となるペアを除いた辞書を生成するコードを書きなさい。生成した辞書を`scores`とすること。
457 |
458 | ###### コード
459 |
460 | ```python
461 | u = 0
462 | rec_list = order(u, Iu_not)
463 | print('rec_list = ')
464 | for i, scr in rec_list:
465 | print('{}: {:.3f}'.format(i, scr))
466 | ```
467 |
468 | ###### 結果
469 |
470 | ```bash
471 | rec_list =
472 | 10: 0.965
473 | ```
474 |
475 | ★★★
476 | 1. 辞書内包表記を使う。
477 | 2. `dict.items()`を使う。
478 | 3. 条件式を使う。
479 |
--------------------------------------------------------------------------------
/ja/chap02.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第2章 評価値行列 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第2章 評価値行列
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 | np.set_printoptions(precision=3)
17 | ```
18 |
19 | ## 評価値行列
20 |
21 | 次の行列$$\boldsymbol{R}$$は評価値行列である。
22 |
23 | $$
24 | \boldsymbol{R} = \left[
25 | \begin{array}{rrrrrr}
26 | ? & 4 & 3 & 1 & 2 & ? \\
27 | 5 & 5 & 4 & ? & 3 & 3 \\
28 | 4 & ? & 5 & 3 & 2 & ? \\
29 | ? & 3 & ? & 2 & 1 & 1 \\
30 | 2 & 1 & 2 & 4 & ? & 3 \\
31 | \end{array}
32 | \right]
33 | $$
34 |
35 | $$\boldsymbol{R}$$の$$u$$行目はユーザ$$u \in U$$を表し、$$i$$列目はアイテム$$i \in I$$を表す。ここで、$$\boldsymbol{R}$$におけるユーザ数は$$\mid U \mid = 5$$、アイテム数は$$\mid I \mid = 6$$となる。$$\boldsymbol{R}$$の$$(u, i)$$成分はユーザ$$u$$がアイテム$$i$$に与えた評価値$$r_{u,i}$$を表す。ただし、$$?$$は欠損値であることを表す。このとき、次の問いに答えなさい。
36 |
37 | ### 01 評価値行列の生成
38 | 評価値行列$$\boldsymbol{R}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`R`とすること。
39 |
40 | ###### コード
41 |
42 | ```python
43 | 【 問01 】
44 | print('R = \n{}'.format(R))
45 | ```
46 |
47 | ###### 結果
48 |
49 | ```bash
50 | R =
51 | [[nan 4. 3. 1. 2. nan]
52 | [ 5. 5. 4. nan 3. 3.]
53 | [ 4. nan 5. 3. 2. nan]
54 | [nan 3. nan 2. 1. 1.]
55 | [ 2. 1. 2. 4. nan 3.]]
56 | ```
57 |
58 | ★
59 | 1. `numpy.array()`を使う。
60 | 2. `numpy.nan`を使う。
61 |
62 | ### 02 ユーザ集合
63 | `R`の各行のインデックス`u`は各ユーザ$$u$$のユーザIDに対応する。`R`からユーザ集合$$U$$(ユーザIDを要素としたベクトル)を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`U`とすること。
64 |
65 | ###### コード
66 |
67 | ```python
68 | 【 問02 】
69 | print('U = {}'.format(U))
70 | ```
71 |
72 | ###### 結果
73 |
74 | ```bash
75 | U = [0 1 2 3 4]
76 | ```
77 |
78 | ★
79 | 1. `numpy.arange()`を使う。
80 | 2. `ndarray.shape`を使う。
81 |
82 | ### 03 アイテム集合
83 | `R`の各列のインデックス`i`は各アイテム$$i$$のアイテムIDに対応する。`R`からアイテム集合$$I$$(アイテムIDを要素としたベクトル)を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`I`とすること。
84 |
85 | ###### コード
86 |
87 | ```python
88 | 【 問03 】
89 | print('I = {}'.format(I))
90 | ```
91 |
92 | ###### 結果
93 |
94 | ```bash
95 | I = [0 1 2 3 4 5]
96 | ```
97 |
98 | ★
99 | 1. `numpy.arange()`を使う。
100 | 2. `ndarray.shape`を使う。
101 |
102 | ### 04 ユーザ数
103 | `U`からユーザ数$$\mid U \mid$$を取得するコードを書きなさい。
104 |
105 | ###### コード
106 |
107 | ```python
108 | print('|U| = {}'.format(【 問04 】))
109 | ```
110 |
111 | ###### 結果
112 |
113 | ```bash
114 | |U| = 5
115 | ```
116 |
117 | ★
118 | 1. `ndarray.size`を使う。
119 |
120 | ### 05 アイテム数
121 | `I`からアイテム数$$\mid I \mid$$を取得するコードを書きなさい。
122 |
123 | ###### コード
124 |
125 | ```python
126 | print('|I| = {}'.format(【 問05 】))
127 | ```
128 |
129 | ###### 結果
130 |
131 | ```bash
132 | |I| = 6
133 | ```
134 |
135 | ★
136 | 1. `ndarray.size`を使う。
137 |
138 | ### 06 評価値
139 | `R`からユーザ$$u$$のアイテム$$i$$に対する評価値$$r_{u,i}$$を取得するコードを書きなさい。
140 |
141 | ###### コード
142 |
143 | ```python
144 | u = 0
145 | i = 1
146 | print('r{}{} = {}'.format(u, i, 【 問06 】))
147 | ```
148 |
149 | ###### 結果
150 |
151 | ```bash
152 | r01 = 4.0
153 | ```
154 |
155 | ★
156 | 1. インデキシングを使う。
157 |
158 | ## 評価値行列の疎性
159 | 評価値行列$$\boldsymbol{R}$$の疎性$$\mathrm{sparsity}$$は次式で求められる。
160 |
161 | $$
162 | \mathrm{sparsity} = 1 - \frac{\mid R \mid}{\mid U \mid \mid I \mid}
163 | $$
164 |
165 | ここで、$$\mid R \mid$$は評価値が与えられた成分の数、すなわち観測値数(欠損値でない要素数)を表す。このとき、次の問いに答えなさい。
166 |
167 | ### 07 評価値行列の全要素数
168 | `R`の全要素数を取得するコードを書きなさい。ただし、欠損値も含む。
169 |
170 | ###### コード
171 |
172 | ```python
173 | print('Rの全要素数 = {}'.format(【 問07 】))
174 | ```
175 |
176 | ###### 結果
177 |
178 | ```bash
179 | Rの全要素数 = 30
180 | ```
181 |
182 | ★
183 | 1. `ndarray.size`を使う。
184 |
185 | ### 08 観測されているか否かの判定
186 | `R`において、観測値の要素には`True`を、欠損値の要素には`False`を入れたブール値配列を生成するコードを書きなさい。
187 |
188 | ###### コード
189 |
190 | ```python
191 | print('観測値 = \n{}'.format(【 問08 】))
192 | ```
193 |
194 | ###### 結果
195 |
196 | ```bash
197 | 観測値 =
198 | [[False True True True True False]
199 | [ True True True False True True]
200 | [ True False True True True False]
201 | [False True False True True True]
202 | [ True True True True False True]]
203 | ```
204 |
205 | ★
206 | 1. `numpy.isnan()`を使う。
207 | 2. `~`演算子を使う。
208 |
209 | ### 09 評価値行列の観測値数
210 | `R`における観測値数$$\mid R \mid$$を取得するコードを書きなさい。
211 |
212 | ###### コード
213 |
214 | ```python
215 | print('|R| = {}'.format(【 問09 】))
216 | ```
217 |
218 | ###### 結果
219 |
220 | ```bash
221 | |R| = 22
222 | ```
223 |
224 | ★★
225 | 1. `numpy.isnan()`を使う。
226 | 2. `~`演算子を使う。
227 | 3. `numpy.count_nonzero()`を使う。
228 |
229 | ★★
230 | 1. `numpy.isnan()`を使う。
231 | 2. `~`演算子を使う。
232 | 3. `numpy.size`を使う。
233 | 4. ブール値インデキシングを使う。
234 |
235 | ### 10 評価値行列の疎性
236 | 評価値行列$$\boldsymbol{R}$$の疎性$$\mathrm{sparsity}$$を求めるコードを書きなさい。得られた値を`sparsity`とすること。
237 |
238 | ###### コード
239 |
240 | ```python
241 | 【 問10 】
242 | print('sparsity = {:.3f}'.format(sparsity))
243 | ```
244 |
245 | ###### 結果
246 |
247 | ```
248 | sparsity = 0.267
249 | ```
250 |
251 | ★★
252 | 1. `numpy.isnan()`を使う。
253 | 2. `~`演算子を使う。
254 | 3. `numpy.count_nonzero()`を使う。
255 | 4. `numpy.size`を使う。
256 |
257 | ## 評価済みアイテム集合
258 | アイテム集合$$I$$のうちユーザ$$u$$が評価済みのアイテム集合を$$I_{u} \subseteq I$$、ユーザ$$v$$が評価済みのアイテム集合を$$I_{v} \subseteq I$$とすると、ユーザ$$u$$とユーザ$$v$$の共通の評価済みアイテム集合は$$I_{u,v} = I_{u} \cap I_{v}$$と表される。また、ユーザ集合$$U$$のうちアイテム$$i$$を評価済みのユーザ集合を$$U_{i} \subseteq U$$、アイテム$$j$$を評価済みのユーザ集合を$$U_{j} \subseteq U$$とすると、アイテム$$i$$とアイテム$$j$$の両方を評価済みのユーザ集合は$$U_{i,j} = U_{i} \cap U_{j}$$と表される。このとき、次の問いに答えなさい。
259 |
260 | ### 11 ユーザuが評価済みのアイテム集合
261 | `I`からユーザ$$u$$が評価済みのアイテム集合$$I_{u}$$を`ndarray`として生成するコードを書きなさい。
262 |
263 | ###### コード
264 |
265 | ```python
266 | u = 0
267 | print('I{} = {}'.format(u, 【 問11 】))
268 | ```
269 |
270 | ###### 結果
271 |
272 | ```bash
273 | I0 = [1 2 3 4]
274 | ```
275 |
276 | ★★
277 | 1. `numpy.isnan()`を使う。
278 | 2. `~`演算子を使う。
279 | 3. ブール値インデキシングを使う。
280 |
281 | ### 12 各ユーザの評価済みアイテム集合
282 | `I`から各ユーザの評価済みのアイテム集合を`ndarray`のリストとしてまとめて生成するコードを書きなさい。得られたリストを`Iu`とすること。
283 |
284 | ###### コード
285 |
286 | ```python
287 | 【 問12 】
288 | print('Iu = ')
289 | pprint.pprint(Iu)
290 | ```
291 |
292 | ###### 結果
293 |
294 | ```bash
295 | Iu =
296 | [array([1, 2, 3, 4]),
297 | array([0, 1, 2, 4, 5]),
298 | array([0, 2, 3, 4]),
299 | array([1, 3, 4, 5]),
300 | array([0, 1, 2, 3, 5])]
301 | ```
302 |
303 | ★★
304 | 1. `for`ループを使う。
305 | 2. `numpy.isnan()`を使う。
306 | 3. `~`演算子を使う。
307 | 4. ブール値インデキシングを使う。
308 | 5. `list.append()`を使う。
309 |
310 | ★★★
311 | 1. リスト内包表記を使う。
312 | 2. `numpy.isnan()`を使う。
313 | 3. `~`演算子を使う。
314 | 4. ブール値インデキシングを使う。
315 |
316 | ### 13 ユーザuとユーザvの共通の評価済みアイテム集合
317 | `Iu`からユーザ$$u$$とユーザ$$v$$の共通の評価済みアイテム集合$$I_{u,v}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iuv`とすること。
318 |
319 | ###### コード
320 |
321 | ```python
322 | u = 0
323 | v = 1
324 | 【 問13 】
325 | print('I{}{} = {}'.format(u, v, Iuv))
326 | ```
327 |
328 | ###### 結果
329 |
330 | ```bash
331 | I01 = [1 2 4]
332 | ```
333 |
334 | ★
335 | 1. `numpy.intersect1d`を使う。
336 |
337 | ### 14 アイテムiを評価済みのユーザ集合
338 | `U`からアイテム$$i$$を評価済みのユーザ集合$$U_{i}$$を`ndarray`として生成するコードを書きなさい。
339 |
340 | ###### コード
341 |
342 | ```python
343 | i = 0
344 | print('U{} = {}'.format(i, 【 問14 】))
345 | ```
346 |
347 | ###### 結果
348 |
349 | ```bash
350 | U0 = [1 2 4]
351 | ```
352 |
353 | ★★
354 | 1. `numpy.isnan()`を使う。
355 | 2. `~`演算子を使う。
356 | 3. ブール値インデキシングを使う。
357 |
358 | ### 15 各アイテムの評価済みユーザ集合
359 | `U`から各アイテムの評価済みユーザ集合$$U_{i}$$を`ndarray`のリストとしてまとめて生成するコードを書きなさい。得られたリストを`Ui`とすること。
360 |
361 | ###### コード
362 |
363 | ```python
364 | 【 問15 】
365 | print('Ui = ')
366 | pprint.pprint(Ui)
367 | ```
368 |
369 | ###### 結果
370 |
371 | ```bash
372 | Ui =
373 | [array([1, 2, 4]),
374 | array([0, 1, 3, 4]),
375 | array([0, 1, 2, 4]),
376 | array([0, 2, 3, 4]),
377 | array([0, 1, 2, 3]),
378 | array([1, 3, 4])]
379 | ```
380 |
381 | ★★
382 | 1. `for`ループを使う。
383 | 2. `numpy.isnan()`を使う。
384 | 3. `~`演算子を使う。
385 | 4. ブール値インデキシングを使う。
386 | 5. `list.append()`を使う。
387 |
388 | ★★★
389 | 1. リスト内包表記を使う。
390 | 2. `numpy.isnan()`を使う。
391 | 3. `~`演算子を使う。
392 | 4. ブール値インデキシングを使う。
393 |
394 | ### 16 アイテムiとアイテムjの両方を評価済みのユーザ集合
395 | `Ui`からアイテム$$i$$とアイテム$$j$$の両方を評価済みのユーザ集合$$U_{i,j}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Uij`とすること。
396 |
397 | ###### コード
398 |
399 | ```python
400 | i = 0
401 | j = 4
402 | 【 問16 】
403 | print('U{}{} = {}'.format(i, j, Uij))
404 | ```
405 |
406 | ###### 結果
407 |
408 | ```bash
409 | U04 = [1 2]
410 | ```
411 |
412 | ★
413 | 1. `numpy.intersect1d`を使う。
414 |
415 | ## 平均中心化評価値行列
416 | ユーザ$$u$$の平均評価値$$\overline{r}_{u}$$は次式で求められる。
417 |
418 | $$
419 | \overline{r}_{u} = \frac{\sum_{i \in I_{u}} r_{u,i}}{\mid I_{u} \mid}
420 | $$
421 |
422 | ユーザ$$u$$のアイテム$$i$$に対する評価値$$r_{u,i}$$からユーザ$$u$$の平均評価値$$\overline{r}_{u}$$を引いた評価値を平均中心化評価値$$r_{u,i}^{'}$$とよび、次式で表される。
423 |
424 | $$
425 | r_{u,i}^{'} = r_{u,i} - \overline{r}_{u}
426 | $$
427 |
428 | 評価値行列$$\boldsymbol{R}$$の評価値$$r_{u,i}$$を平均中心化評価値$$r_{u,i}^{'}$$に置き換えた評価値行列を平均中心化評価値行列$$\boldsymbol{R}^{'}$$とよび、次式のようになる。
429 |
430 | $$
431 | \boldsymbol{R}^{'} = \left[
432 | \begin{array}{rrrrrr}
433 | & 1.5 & 0.5 & -1.5 & -0.5 & \\
434 | 1 & 1 & 0 & & -1 & -1 \\
435 | 0.5 & & 1.5 & -0.5 & -1.5 & \\
436 | & 1.25 & & 0.25 & -0.75 & -0.75 \\
437 | -0.4 & -1.4 & -0.4 & 1.6 & & 0.6
438 | \end{array}
439 | \right]
440 | $$
441 |
442 | このとき、次の問いに答えなさい。
443 |
444 | ### 17 評価値行列全体の平均評価値
445 | `R`全体の平均評価値を求めるコードを書きなさい。ただし、欠損値は無視する。
446 |
447 | ###### コード
448 |
449 | ```python
450 | print('R全体の平均評価値 = {:.3f}'.format(【 問17 】))
451 | ```
452 |
453 | ###### 結果
454 |
455 | ```bash
456 | R全体の平均評価値 = 2.864
457 | ```
458 |
459 | ★
460 | 1. `numpy.nanmean()`を使う。
461 |
462 | ### 18 各アイテムの平均評価値
463 | `R`において各アイテムの平均評価値$$\overline{r}_{i}$$を`ndarray`としてまとめて求めるコードを書きなさい。ただし、欠損値は無視する。得られた`ndarray`を`ri_mean`とすること。
464 |
465 | ###### コード
466 |
467 | ```python
468 | 【 問18 】
469 | print('ri_mean = {}'.format(ri_mean))
470 | ```
471 |
472 | ###### 結果
473 |
474 | ```bash
475 | ri_mean = [3.667 3.25 3.5 2.5 2. 2.333]
476 | ```
477 |
478 | ★★
479 | 1. `numpy.nanmean()`を使う。
480 |
481 | ### 19 各ユーザの平均評価値
482 | `R`において各ユーザの平均評価値$$\overline{r}_{u}$$を`ndarray`としてまとめて求めるコードを書きなさい。ただし、欠損値は無視する。得られた`ndarray`を`ru_mean`とすること。
483 |
484 | ###### コード
485 |
486 | ```python
487 | 【 問19 】
488 | print('ru_mean = {}'.format(ru_mean))
489 | ```
490 |
491 | ###### 結果
492 |
493 | ```bash
494 | ru_mean = [2.5 4. 3.5 1.75 2.4 ]
495 | ```
496 |
497 | ★★
498 | 1. `numpy.nanmean()`を使う。
499 |
500 | ★★★
501 | 1. 二重のリスト内包表記を使う。
502 | 2. `numpy.sum()`を使う。
503 | 3. `numpy.array()`を使う。
504 | 4. `numpy.nanmean()`を使わない。
505 |
506 | ### 20 評価値ベクトルの形状変換
507 | `ru_mean`の形状を`(5, 1)`に変換するコードを書きなさい。
508 |
509 | ###### コード
510 |
511 | ```python
512 | print('ru_mean = \n{}'.format(【 問20 】))
513 | ```
514 |
515 | ###### 結果
516 |
517 | ```bash
518 | ru_mean =
519 | [[2.5 ]
520 | [4. ]
521 | [3.5 ]
522 | [1.75]
523 | [2.4 ]]
524 | ```
525 |
526 | ★
527 | 1. `ndarray.reshape()`を使う。
528 |
529 | ### 21 平均中心化評価値行列
530 | 評価値行列$$\boldsymbol{R}$$の平均中心化評価値行列$$\boldsymbol{R}^{'}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`R2`とすること。
531 |
532 | ###### コード
533 |
534 | ```python
535 | 【 問21 】
536 | print('R\' = \n{}'.format(R2))
537 | ```
538 |
539 | ###### 結果
540 |
541 | ```bash
542 | R' =
543 | [[ nan 1.5 0.5 -1.5 -0.5 nan]
544 | [ 1. 1. 0. nan -1. -1. ]
545 | [ 0.5 nan 1.5 -0.5 -1.5 nan]
546 | [ nan 1.25 nan 0.25 -0.75 -0.75]
547 | [-0.4 -1.4 -0.4 1.6 nan 0.6 ]]
548 | ```
549 |
550 | ★★
551 | 1. `ndarray.reshape()`を使う。
552 |
553 |
--------------------------------------------------------------------------------
/ja/chap01.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第1章 評価履歴 | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第1章 評価履歴
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import numpy as np
15 | ```
16 |
17 | ## 評価履歴
18 | 次の行列$$D_{u}$$はユーザ$$u$$の評価履歴である。
19 |
20 | $$
21 | D_{u} = \left[
22 | \begin{array}{rrr}
23 | 5 & 3 & +1 \\
24 | 6 & 2 & +1 \\
25 | 4 & 1 & +1 \\
26 | 8 & 5 & -1 \\
27 | 2 & 4 & -1 \\
28 | 3 & 6 & -1 \\
29 | 7 & 6 & -1 \\
30 | 4 & 2 & ? \\
31 | 5 & 1 & ? \\
32 | 8 & 6 & ? \\
33 | 3 & 4 & ? \\
34 | 4 & 7 & ? \\
35 | 4 & 4 & ? \\
36 | \end{array}
37 | \right]
38 | $$
39 |
40 | このとき、次の問いに答えなさい。
41 |
42 | ### 01 評価履歴の生成
43 | 評価履歴$$D_{u}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Du`とすること。
44 |
45 | ###### コード
46 |
47 | ```pythoon
48 | 【 問01 】
49 | print('Du = \n{}'.format(Du))
50 | ```
51 |
52 | ###### 結果
53 |
54 | ```bash
55 | Du =
56 | [[ 5. 3. 1.]
57 | [ 6. 2. 1.]
58 | [ 4. 1. 1.]
59 | [ 8. 5. -1.]
60 | [ 2. 4. -1.]
61 | [ 3. 6. -1.]
62 | [ 7. 6. -1.]
63 | [ 4. 2. nan]
64 | [ 5. 1. nan]
65 | [ 8. 6. nan]
66 | [ 3. 4. nan]
67 | [ 4. 7. nan]
68 | [ 4. 4. nan]]
69 | ```
70 |
71 | ★
72 | 1. `numpy.array()`を使う。
73 | 2. `numpy.nan`を使う。
74 |
75 | ### 02 評価履歴の形状
76 | `Du`の形状を取得するコードを書きなさい。
77 |
78 | ###### コード
79 |
80 | ```python
81 | print('Duの形状 = {}'.format(【 問02 】))
82 | ```
83 |
84 | ###### 結果
85 |
86 | ```bash
87 | Duの形状 = (13, 3)
88 | ```
89 |
90 | ★
91 | 1. `ndarray.shape`を使う。
92 |
93 | ### 03 評価履歴の行数
94 | `Du`の行数を取得するコードを書きなさい。
95 |
96 | ###### コード
97 |
98 | ```python
99 | print('Duの行数 = {}'.format(【 問03 】))
100 | ```
101 |
102 | ###### 結果
103 |
104 | ```bash
105 | Duの行数 = 13
106 | ```
107 |
108 | ★
109 | 1. `ndarray.shape`を使う。
110 |
111 | ### 04 評価履歴の列数
112 | `Du`の列数を取得するコードを書きなさい。
113 |
114 | ###### コード
115 |
116 | ```python
117 | print('Duの列数 = {}'.format(【 問04 】))
118 | ```
119 |
120 | ###### 結果
121 |
122 | ```bash
123 | Duの列数 = 3
124 | ```
125 |
126 | ★
127 | 1. `ndarray.shape`を使う。
128 |
129 | ### 05 評価履歴の全要素数
130 | `Du`の全要素数を取得するコードを書きなさい。ただし、欠損値も含む。
131 |
132 | ###### コード
133 |
134 | ```python
135 | print('Duの全要素数 = {}'.format(【 問05 】))
136 | ```
137 |
138 | ###### 結果
139 |
140 | ```bash
141 | Duの全要素数 = 39
142 | ```
143 |
144 | ★
145 | 1. `ndarray.size`を使う。
146 |
147 | ## アイテム
148 | 評価履歴$$D_{u}$$の$$i$$行目はアイテム$$i \in I$$に関するデータを表す。ここで、評価履歴$$D_{u}$$に含まれるアイテム集合は$$I = \{0, 1, \ldots, 12\}$$となる。$$D_{u}$$の$$i$$行目の1列目と2列目はアイテム$$i$$の特徴ベクトル$$\boldsymbol{x}_{i}$$に対応する。このとき、次の問いに答えなさい。
149 |
150 | ### 06 アイテム集合
151 | `Du`の各行のインデックス`i`は各アイテム$$i$$のアイテムID $$i$$に対応する。`Du`からアイテム集合$$I$$(アイテムIDを要素としたベクトル)を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`I`とすること。
152 |
153 | ###### コード
154 |
155 | ```python
156 | 【 問06 】
157 | print('I = {}'.format(I))
158 | ```
159 |
160 | ###### 結果
161 |
162 | ```bash
163 | I = [ 0 1 2 3 4 5 6 7 8 9 10 11 12]
164 | ```
165 |
166 | ★★
167 | 1. `numpy.arange()`を使う。
168 | 2. `ndarray.shape`を使う。
169 |
170 | ### 07 アイテムの特徴ベクトルの集合
171 | `Du`からアイテムの特徴ベクトルの集合(各アイテムの特徴ベクトル$$\boldsymbol{x}_{i}$$を縦に結合した行列)を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`x`とすること。
172 |
173 | ###### コード
174 |
175 | ```python
176 | 【 問07 】
177 | print('x = \n{}'.format(x))
178 | ```
179 |
180 | ###### 結果
181 |
182 | ```bash
183 | x =
184 | [[5. 3.]
185 | [6. 2.]
186 | [4. 1.]
187 | [8. 5.]
188 | [2. 4.]
189 | [3. 6.]
190 | [7. 6.]
191 | [4. 2.]
192 | [5. 1.]
193 | [8. 6.]
194 | [3. 4.]
195 | [4. 7.]
196 | [4. 4.]]
197 | ```
198 |
199 | ★★
200 | 1. スライシングを使う。
201 |
202 | ### 08 アイテムiの特徴ベクトル
203 | `x`からアイテム$$i$$の特徴ベクトル$$\boldsymbol{x}_{i}$$を取得するコードを書きなさい。
204 |
205 | ###### コード
206 |
207 | ```python
208 | i = 0
209 | print('x{} = {}'.format(i, 【 問08 】))
210 | ```
211 |
212 | ###### 結果
213 |
214 | ```bash
215 | x0 = [5. 3.]
216 | ```
217 |
218 | ★
219 | 1. インデキシングを使う。
220 |
221 | ## 評価値
222 | 評価履歴$$D_{u}$$の$$i$$行目3列目はユーザ$$u$$のアイテム$$i$$に対する評価値$$r_{u,i}$$に対応する。$$r_{u,i}=+1$$は「好き」を、$$r_{u,i}=-1$$は「嫌い」を表す。ただし、$$?$$はユーザ$$u$$が未評価、すなわち欠損値であることを示す。このとき、次の問いに答えなさい。
223 |
224 | ### 09 評価値集合
225 | `Du`から評価値集合(各評価値$$r_{u,i}$$を要素としたベクトル)を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`ru`とすること。
226 |
227 | ###### コード
228 |
229 | ```python
230 | 【 問09 】
231 | print('ru = {}'.format(ru))
232 | ```
233 |
234 | ###### 結果
235 |
236 | ```bash
237 | ru = [ 1. 1. 1. -1. -1. -1. -1. nan nan nan nan nan nan]
238 | ```
239 |
240 | ★★
241 | 1. スライシングを使う。
242 |
243 | ### 10 評価値集合の形状
244 | `ru`の形状を取得するコードを書きなさい。
245 |
246 | ###### コード
247 |
248 | ```python
249 | print('ruの形状 = {}'.format(【 問10 】))
250 | ```
251 |
252 | ###### 結果
253 |
254 | ```bash
255 | ruの形状 = (13,)
256 | ```
257 |
258 | ★
259 | 1. `ndarray.shape`を使う。
260 |
261 | ### 11 評価値集合の全要素数
262 | `ru`の全要素数を取得するコードを書きなさい。ただし、欠損値も含む。
263 |
264 | ###### コード
265 |
266 | ```python
267 | print('ruの全要素数 = {}'.format(【 問11 】))
268 | ```
269 |
270 | ###### 結果
271 |
272 | ```bash
273 | ruの全要素数 = 13
274 | ```
275 |
276 | ★
277 | 1. `ndarray.size`を使う。
278 |
279 | ★
280 | 1. `ndarray.shape`を使う。
281 |
282 | ### 12 評価値集合の部分集合
283 | `ru`の`i`番目から`j-1`番目までの評価値集合を取得するコードを書きなさい。
284 |
285 | ###### コード
286 |
287 | ```python
288 | i = 2
289 | j = 5
290 | print('ru{}からru{}までの評価値 = {}'.format(i, j-1, 【 問12 】))
291 | ```
292 |
293 | ###### 結果
294 |
295 | ```bash
296 | ru2からru4までの評価値 = [ 1. -1. -1.]
297 | ```
298 |
299 | ★
300 | 1. スライシングを使う。
301 |
302 | ### 13 評価値集合の要素の逆順
303 | `ru`の要素を逆順に取得するコードを書きなさい。
304 |
305 | ###### コード
306 |
307 | ```python
308 | print('ruの逆順 = {}'.format(【 問13 】))
309 | ```
310 |
311 | ###### 結果
312 |
313 | ```bash
314 | ruの逆順 = [nan nan nan nan nan nan -1. -1. -1. -1. 1. 1. 1.]
315 | ```
316 |
317 | ★★
318 | 1. スライシングを使う。
319 |
320 | ### 14 アイテムiに対する評価値
321 | `ru`からアイテム$$i$$に対する評価値$$r_{u,i}$$を取得するコードを書きなさい。
322 |
323 | ###### コード
324 |
325 | ```python
326 | i = 0
327 | print('ru{} = {}'.format(i, 【 問14 】))
328 | ```
329 |
330 | ###### 結果
331 |
332 | ```bash
333 | ru0 = 1.0
334 | ```
335 |
336 | ★
337 | 1. インデキシングを使う。
338 |
339 | ## アイテム集合
340 | アイテム集合$$I$$のうちユーザ$$u$$が評価済みのアイテム集合を$$I_{u} \subseteq I$$と表す。このうち、ユーザ$$u$$が「好き」と評価したアイテム集合を$$I_{u}^{+} \subseteq I$$($$r_{u,i}=+1$$を満たすアイテム集合)、ユーザ$$u$$が「嫌い」と評価したアイテム集合を$$I_{u}^{-} \subseteq I$$($$r_{u,i}=-1$$を満たすアイテム集合)と表す。また、ユーザ$$u$$が未評価のアイテム集合は$$\overline{I}_{u} = I \setminus I_{u}$$と表される。ここで、$$I \setminus I_{u}$$は$$I$$から$$I_{u}$$を引いた差集合を表す。このとき、次の問いに答えなさい。
341 |
342 | ### 15 ユーザuが未評価であるか否かの判定
343 | `ru`において、欠損値の要素には`True`を、それ以外の要素には`False`を入れたブール値配列を生成するコードを書きなさい。
344 |
345 | ###### コード
346 |
347 | ```python
348 | print('ユーザuが未評価 = {}'.format(【 問15 】))
349 | ```
350 |
351 | ###### 結果
352 |
353 | ```bash
354 | ユーザuが未評価 = [False False False False False False False True True True True True True]
355 | ```
356 |
357 | ★
358 | 1. `numpy.isnan()`を使う。
359 |
360 | ### 16 ユーザが評価済みであるか否かの判定
361 | `ru`において、欠損値の要素には`False`を、それ以外の要素には`True`を入れたブール値配列を生成するコードを書きなさい。
362 |
363 | ###### コード
364 |
365 | ```python
366 | print('ユーザuが評価済み = {}'.format(【 問16 】))
367 | ```
368 |
369 | ###### 結果
370 |
371 | ```bash
372 | ユーザuが評価済み = [ True True True True True True True False False False False False False]
373 | ```
374 |
375 | ★
376 | 1. `numpy.isnan()`を使う。
377 | 2. `~`演算子を使う。
378 |
379 | ### 17 ユーザuが評価済みのアイテム集合
380 | `I`からユーザ$$u$$が評価済みのアイテム集合$$I_{u}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iu`とすること。
381 |
382 | ###### コード
383 |
384 | ```python
385 | 【 問17 】
386 | print('Iu = {}'.format(Iu))
387 | ```
388 |
389 | ###### 結果
390 |
391 | ```bash
392 | Iu = [0 1 2 3 4 5 6]
393 | ```
394 |
395 | ★★
396 | 1. `numpy.isnan()`を使う。
397 | 2. `~`演算子を使う。
398 | 3. ブール値インデキシングを使う。
399 |
400 | ### 18 ユーザuが「好き」と評価したアイテム集合
401 | `I`からユーザ$$u$$が「好き」と評価したアイテム集合$$I_{u}^{+}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iup`とすること。
402 |
403 | ###### コード
404 |
405 | ```python
406 | 【 問18 】
407 | print('Iu+ = {}'.format(Iup))
408 | ```
409 |
410 | ###### 結果
411 |
412 | ```bash
413 | Iu+ = [0 1 2]
414 | ```
415 |
416 | ★★
417 | 1. ブール値インデキシングを使う。
418 |
419 | ### 19 ユーザuが「嫌い」と評価したアイテム集合
420 | `I`からユーザ$$u$$が「嫌い」と評価したアイテム集合$$I_{u}^{-}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iun`とすること。
421 |
422 | ###### コード
423 |
424 | ```python
425 | 【 問19 】
426 | print('Iu- = {}'.format(Iun))
427 | ```
428 |
429 | ###### 結果
430 |
431 | ```bash
432 | Iu- = [3 4 5 6]
433 | ```
434 |
435 | ★★
436 | 1. ブール値インデキシングを使う。
437 |
438 | ### 20 ユーザuが未評価のアイテム集合
439 | `I`からユーザ$$u$$が未評価のアイテム集合$$\overline{I}_{u}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`Iu_not`とすること。
440 |
441 | ###### コード
442 |
443 | ```python
444 | 【 問20 】
445 | print('Iu_not = {}'.format(Iu_not))
446 | ```
447 |
448 | ###### 結果
449 |
450 | ```bash
451 | Iu_not = [ 7 8 9 10 11 12]
452 | ```
453 |
454 | ★★
455 | 1. `numpy.isnan()`を使う。
456 | 2. ブール値インデキシングを使う。
457 |
458 | ★★
459 | 1. `numpy.setdiff1d()`を使う。
460 |
461 | ## 訓練データと予測対象データ
462 | 評価履歴$$D_{u}$$からユーザ$$u$$の訓練データ$$D_{u}^{L}$$を作成すると、次のとおりとなる。
463 |
464 | $$
465 | D_{u}^{L} = \left[
466 | \begin{array}{rrr}
467 | 5 & 3 & +1 \\
468 | 6 & 2 & +1 \\
469 | 4 & 1 & +1 \\
470 | 8 & 5 & -1 \\
471 | 2 & 4 & -1 \\
472 | 3 & 6 & -1 \\
473 | 7 & 6 & -1 \\
474 | \end{array}
475 | \right]
476 | $$
477 |
478 | ここで、訓練データ$$D_{u}^{L}$$に含まれる事例数は$$\mid D_{u}^{L} \mid = 7$$となる。また、訓練データ$$D_{u}^{L}$$のうち「好き」と評価された事例集合(正事例集合)を$$D_{u}^{L+}$$、「嫌い」と評価された事例集合(負事例集合)を$$D_{u}^{L-}$$と表す。それぞれの事例数は$$\mid D_{u}^{L+} \mid = 3$$、$$\mid D_{u}^{L-} \mid = 4$$となる。同様に、ユーザ$$u$$向けの予測対象データ$$D_{u}^{U}$$は次のとおりとなる。
479 |
480 | $$
481 | D_{u}^{U} = \left[
482 | \begin{array}{rrr}
483 | 4 & 2 & ? \\
484 | 5 & 1 & ? \\
485 | 8 & 6 & ? \\
486 | 3 & 4 & ? \\
487 | 4 & 7 & ? \\
488 | 4 & 4 & ? \\
489 | \end{array}
490 | \right]
491 | $$
492 |
493 | ここで、予測対象データ$$D_{u}^{U}$$に含まれる事例数は$$\mid D_{u}^{U} \mid = 6$$となる。このとき、次の問いに答えなさい。
494 |
495 | ### 21 訓練データ
496 | `Du`から訓練データ$$D_{u}^{L}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`DuL`とすること。
497 |
498 | ###### コード
499 |
500 | ```python
501 | 【 問21 】
502 | print('DuL = \n{}'.format(DuL))
503 | ```
504 |
505 | ###### 結果
506 |
507 | ```bash
508 | DuL =
509 | [[ 5. 3. 1.]
510 | [ 6. 2. 1.]
511 | [ 4. 1. 1.]
512 | [ 8. 5. -1.]
513 | [ 2. 4. -1.]
514 | [ 3. 6. -1.]
515 | [ 7. 6. -1.]]
516 | ```
517 |
518 | ★
519 | 1. 整数配列インデキシングを使う。
520 |
521 | ### 22 訓練事例数
522 | `DuL`から訓練事例数$$\mid D_{u}^{L} \mid$$を取得するコードを書きなさい。
523 |
524 | ###### コード
525 |
526 | ```python
527 | print('|DuL| = {}'.format(【 問22 】))
528 | ```
529 |
530 | ###### 結果
531 |
532 | ```bash
533 | |DuL| = 7
534 | ```
535 |
536 | ★
537 | 1. `ndarray.shape`を使う。
538 |
539 | ### 23 正事例数
540 | `DuL`から正事例数$$\mid D_{u}^{L+} \mid$$を取得するコードを書きなさい。
541 |
542 | ###### コード
543 |
544 | ```python
545 | print('|DuL+| = {}'.format(【 問23 】))
546 | ```
547 |
548 | ###### 結果
549 |
550 | ```bash
551 | |DuL+| = 3
552 | ```
553 |
554 | ★★
555 | 1. 整数配列インデキシングを使う。
556 | 2. ブール値インデキシングを使う。
557 | 3. `ndarray.shape`を使う。
558 |
559 | ### 24 負事例数
560 | `DuL`から負事例数$$\mid D_{u}^{L-} \mid$$を取得するコードを書きなさい。
561 |
562 | ###### コード
563 |
564 | ```python
565 | print('|DuL-| = {}'.format(【 問24 】))
566 | ```
567 |
568 | ###### 結果
569 |
570 | ```bash
571 | |DuL-| = 4
572 | ```
573 |
574 | ★★
575 | 1. 整数配列インデキシングを使う。
576 | 2. ブール値インデキシングを使う。
577 | 3. `ndarray.shape`を使う。
578 |
579 | ### 25 予測対象データ
580 | `Du`から予測対象データ$$D_{u}^{U}$$を`ndarray`として生成するコードを書きなさい。得られた`ndarray`を`DuU`とすること。
581 |
582 | ###### コード
583 |
584 | ```python
585 | 【 問25 】
586 | print('DuU = \n{}'.format(DuU))
587 | ```
588 |
589 | ###### 結果
590 |
591 | ```bash
592 | DuU =
593 | [[ 4. 2. nan]
594 | [ 5. 1. nan]
595 | [ 8. 6. nan]
596 | [ 3. 4. nan]
597 | [ 4. 7. nan]
598 | [ 4. 4. nan]]
599 | ```
600 |
601 | ★
602 | 1. 整数配列インデキシングを使う。
603 |
604 | ### 26 予測対象事例数
605 | `DuU`から訓練事例数$$\mid D_{u}^{U} \mid$$を取得するコードを書きなさい。
606 |
607 | ###### コード
608 |
609 | ```python
610 | print('|DuU| = {}'.format(【 問26 】))
611 | ```
612 |
613 | ###### 結果
614 |
615 | ```bash
616 | |DuU| = 6
617 | ```
618 |
619 | ★
620 | 1. `ndarray.shape`を使う。
621 |
--------------------------------------------------------------------------------
/ja/chap05.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 第5章 ユーザベース協調フィルタリング | recsys-python
3 | layout: default
4 | ---
5 |
6 | {% include header.html %}
7 |
8 | # 第5章 ユーザベース協調フィルタリング
9 |
10 | ## 準備
11 | 次のコードを書きなさい。
12 |
13 | ```python
14 | import pprint
15 | import numpy as np
16 | np.set_printoptions(precision=3)
17 |
18 | # 近傍ユーザ数
19 | K_USERS = 3
20 | # 閾値
21 | THETA = 0
22 |
23 | R = np.array([
24 | [np.nan, 4, 3, 1, 2, np.nan],
25 | [5, 5, 4, np.nan, 3, 3 ],
26 | [4, np.nan, 5, 3, 2, np.nan],
27 | [np.nan, 3, np.nan, 2, 1, 1 ],
28 | [2, 1, 2, 4, np.nan, 3 ],
29 | ])
30 | U = np.arange(R.shape[0])
31 | I = np.arange(R.shape[1])
32 | Ui = [U[~np.isnan(R)[:,i]] for i in I]
33 | Iu = [I[~np.isnan(R)[u,:]] for u in U]
34 | ru_mean = np.nanmean(R, axis=1)
35 | R2 = R - ru_mean.reshape((ru_mean.size, 1))
36 | ```
37 |
38 | ## ピアソンの相関係数
39 | ユーザ$$u$$とユーザ$$v$$のピアソンの相関係数$$\mathrm{pearson}(u, v)$$は次式で定義される。
40 |
41 | $$
42 | \mathrm{pearson}(u, v) = \frac{\sum_{i \in I_{u,v}} (r_{u,i} - \overline{r}_{u})(r_{v,i} - \overline{r}_{v})}{\sqrt{\sum_{i \in I_{u,v}} (r_{u,i} - \overline{r}_{u})^{2}} \sqrt{\sum_{i \in I_{u,v}} (r_{v,i} - \overline{r}_{v})^{2}}}
43 | $$
44 |
45 | ここで、$$I_{u,v}$$はユーザ$$u$$とユーザ$$v$$の共通の評価済みアイテム集合である。また、$$\overline{r}_{u}$$はユーザ$$u$$の平均評価値を表す。このピアソンの相関係数の関数を次のコードのとおり定義する。
46 |
47 | ###### 関数
48 |
49 | ```python
50 | def pearson1(u, v):
51 | """
52 | 評価値行列Rにおけるユーザuとユーザvのピアソンの相関係数を返す。
53 |
54 | Parameters
55 | ----------
56 | u : int
57 | ユーザuのID
58 | v : int
59 | ユーザvのID
60 |
61 | Returns
62 | -------
63 | float
64 | ピアソンの相関係数
65 | """
66 | Iuv = np.intersect1d(Iu[u], Iu[v])
67 |
68 | 【 問01 】
69 | print('num = {}'.format(num))
70 | 【 問02 】
71 | print('den_u = {:.3f}'.format(den_u))
72 | 【 問03 】
73 | print('den_v = {:.3f}'.format(den_v))
74 |
75 | prsn = num / (den_u * den_v)
76 | return prsn
77 | ```
78 |
79 | ###### コード
80 |
81 | ```python
82 | u = 0
83 | v = 1
84 | prsn = pearson1(u, v)
85 | print('pearson1({}, {}) = {:.3f}'.format(u, v, prsn))
86 | ```
87 |
88 | ###### 結果
89 |
90 | ```python
91 | num = 2.0
92 | den_u = 1.658
93 | den_v = 1.414
94 | pearson1(0, 1) = 0.853
95 | ```
96 |
97 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
98 |
99 | ### 01 ピアソンの相関係数(分子)
100 | ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分子である次式を求めるコードを書きなさい。得られた値を`num`とすること。
101 |
102 | $$
103 | \sum_{i \in I_{u,v}} (r_{u,i} - \overline{r}_{u})(r_{v,i} - \overline{r}_{v})
104 | $$
105 |
106 | ★★★
107 | 1. リスト内包表記を使う。
108 | 2. `numpy.sum()`を使う。
109 |
110 | ### 02 ピアソンの相関係数の算出(分母左部)
111 | ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分母の左部である次式を求めるコードを書きなさい。得られた値を`den_u`とすること。
112 |
113 | $$
114 | \sqrt{\sum_{i \in I_{u,v}} (r_{u,i} - \overline{r}_{u})^{2}}
115 | $$
116 |
117 | ★★★
118 | 1. リスト内包表記を使う。
119 | 2. `numpy.sum()`を使う。
120 | 3. `numpy.sqrt()`を使う。
121 |
122 | ### 03 ピアソンの相関係数の算出(分母右部)
123 | ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分母の右部である次式を求めるコードを書きなさい。得られた値を`den_v`とすること。
124 |
125 | $$
126 | \sqrt{\sum_{i \in I_{u,v}} (r_{v,i} - \overline{r}_{v})^{2}}
127 | $$
128 |
129 | ★★★
130 | 1. リスト内包表記を使う。
131 | 2. `numpy.sum()`を使う。
132 | 3. `numpy.sqrt()`を使う。
133 |
134 |
135 | ## 平均中心化評価値行列に基づくピアソンの相関係数
136 | 平均中心化評価値行列$$\boldsymbol{R}^{'}$$を用いると、ユーザ$$u$$とユーザ$$v$$のピアソンの相関係数$$\mathrm{pearson}(u, v)$$は次式で定義される。
137 |
138 | $$
139 | \mathrm{pearson}(u, v) = \frac{\sum_{i \in I_{u,v}} r_{u,i}^{'} r_{v,i}^{'}}{\sqrt{\sum_{i \in I_{u,v}} r_{u,i}^{'2}} \sqrt{\sum_{i \in I_{u,v}} r_{v,i}^{'2}}}
140 | $$
141 |
142 | ここで、$$r_{u,i}^{'}$$は$$r_{u,i}$$の平均中心化評価値を表す。このピアソンの相関係数の関数を次のコードのとおり定義する。
143 |
144 | ###### 関数
145 |
146 | ```python
147 | def pearson2(u, v):
148 | """
149 | 平均中心化評価値行列R2におけるユーザuとユーザvのピアソンの相関係数を返す。
150 |
151 | Parameters
152 | ----------
153 | u : int
154 | ユーザuのID
155 | v : int
156 | ユーザvのID
157 |
158 | Returns
159 | -------
160 | float
161 | ピアソンの相関係数
162 | """
163 | Iuv = np.intersect1d(Iu[u], Iu[v])
164 |
165 | 【 問04 】
166 | print('num = {}'.format(num))
167 | 【 問05 】
168 | print('den_u = {:.3f}'.format(den_u))
169 | 【 問06 】
170 | print('den_v = {:.3f}'.format(den_v))
171 |
172 | prsn = num / (den_u * den_v)
173 | return prsn
174 | ```
175 |
176 | ###### コード
177 |
178 | ```python
179 | u = 0
180 | v = 1
181 | prsn = pearson2(u, v)
182 | print('pearson2({}, {}) = {:.3f}'.format(u, v, prsn))
183 | ```
184 |
185 | ###### 結果
186 |
187 | ```python
188 | num = 2.0
189 | den_u = 1.658
190 | den_v = 1.414
191 | pearson2(0, 1) = 0.853
192 | ```
193 |
194 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
195 |
196 | ### 04 ピアソンの相関係数(分子)
197 | `R2`を参照し、ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分子である次式を求めるコードを書きなさい。得られた値を`num`とすること。
198 |
199 | $$
200 | \sum_{i \in I_{u,v}} r_{u,i}^{'} r_{v,i}^{'}
201 | $$
202 |
203 | ★★★
204 | 1. リスト内包表記を使う。
205 | 2. `numpy.sum()`を使う。
206 |
207 | ### 05 ピアソンの相関係数の算出(分母左部)
208 | `R2`を参照し、ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分母の左部である次式を求めるコードを書きなさい。得られた値を`den_u`とすること。
209 |
210 | $$
211 | \sqrt{\sum_{i \in I_{u,v}} r_{u,i}^{'2}}
212 | $$
213 |
214 | ★★★
215 | 1. リスト内包表記を使う。
216 | 2. `numpy.sum()`を使う。
217 | 3. `numpy.sqrt()`を使う。
218 |
219 | ### 06 ピアソンの相関係数の算出(分母右部)
220 | `R2`を参照し、ピアソンの相関係数$$\mathrm{pearson}(u, v)$$の分母の右部である次式を求めるコードを書きなさい。得られた値を`den_v`とすること。
221 |
222 | $$
223 | \sqrt{\sum_{i \in I_{u,v}} r_{v,i}^{'2}}
224 | $$
225 |
226 | ★★★
227 | 1. リスト内包表記を使う。
228 | 2. `numpy.sum()`を使う。
229 | 3. `numpy.sqrt()`を使う。
230 |
231 | ## ユーザ-ユーザ類似度行列
232 | ユーザ$$u$$とユーザ$$v$$のユーザ類似度$$\mathrm{sim}(u,v)$$は次式で定義される。
233 |
234 | $$
235 | \mathrm{sim}(u,v) = \mathrm{pearson}(u,v)
236 | $$
237 |
238 | このユーザ類似度関数を次のコードのとおり定義する。
239 |
240 | ###### 関数
241 |
242 | ```python
243 | def sim(u, v):
244 | """
245 | ユーザ類似度関数:ユーザuとユーザvのユーザ類似度を返す。
246 |
247 | Parameters
248 | ----------
249 | u : int
250 | ユーザuのID
251 | v : int
252 | ユーザvのID
253 |
254 | Returns
255 | -------
256 | float
257 | ユーザ類似度
258 | """
259 | return pearson2(u, v)
260 | ```
261 |
262 | 各ユーザ$$u$$と各ユーザ$$v$$のユーザ類似度$$\mathrm{sim}(u, v)$$を要素とした行列をユーザ-ユーザ類似度行列$$\boldsymbol{\mathcal{S}}$$とする。ユーザ-ユーザ類似度行列$$\boldsymbol{\mathcal{S}}$$は次式のとおりとなる。
263 |
264 | $$
265 | \boldsymbol{\mathcal{S}} = \left[
266 | \begin{array}{rrrrrr}
267 | 1.000 & 0.853 & 0.623 & 0.582 & -0.997 \\
268 | 0.853 & 1.000 & 0.649 & 0.968 & -0.853 \\
269 | 0.623 & 0.649 & 1.000 & 0.800 & -0.569 \\
270 | 0.582 & 0.968 & 0.800 & 1.000 & -0.551 \\
271 | -0.997 & -0.853 & -0.569 & -0.551 & 1.000
272 | \end{array}
273 | \right]
274 | $$
275 |
276 | このとき、次の問いに答えなさい。
277 |
278 | ### 07 ユーザ-ユーザ類似度行列
279 | ユーザ-ユーザ類似度行列$$\boldsymbol{\mathcal{S}}$$を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`S`とすること。
280 |
281 | ###### コード
282 |
283 | ```python
284 | 【 問07 】
285 | print('S = \n{}'.format(S))
286 | ```
287 |
288 | ###### 結果
289 |
290 | ```bash
291 | S =
292 | [[ 1. 0.853 0.623 0.582 -0.997]
293 | [ 0.853 1. 0.649 0.968 -0.853]
294 | [ 0.623 0.649 1. 0.8 -0.569]
295 | [ 0.582 0.968 0.8 1. -0.551]
296 | [-0.997 -0.853 -0.569 -0.551 1. ]]
297 | ```
298 |
299 | ★★★
300 | 1. `numpy.zeros()`を使う。
301 | 2. 二重の`for`ループを使う。
302 |
303 | ★★★
304 | 1. 二重のリスト内包表記を使う。
305 | 2. `numpy.array()`を使う。
306 |
307 | ## 類似ユーザの選定
308 | ユーザ$$u$$の類似ユーザ集合を$$U^{u} \subseteq U$$とする。ここでは、ユーザ類似度が上位$$k$$人のユーザを類似ユーザ集合として選定する。ただし、しきい値$$\theta$$未満のユーザは除外する。
309 |
310 | 次のコードはユーザ`u`の類似ユーザ集合`Uu`を選定するためのコードである。ここで、`K_USERS`は上位$$k$$人を表す定数であり、`THETA`はしきい値$$\theta$$を表す定数である。
311 |
312 | ###### コード
313 |
314 | ```python
315 | # ユーザ-ユーザ類似度行列から対象ユーザを除外した辞書
316 | Uu = {u: {v: S[u,v] for v in U if u!=v} for u in U}
317 | print('Uu = ')
318 | pprint.pprint(Uu)
319 | 【 問08 】
320 | print('Uu = ')
321 | pprint.pprint(Uu)
322 | 【 問09 】
323 | print('Uu = ')
324 | pprint.pprint(Uu)
325 | # 各ユーザの類似ユーザ集合をまとめた辞書
326 | Uu = {u: np.array(list(Uu[u].keys())) for u in U}
327 | print('Uu = ')
328 | pprint.pprint(Uu)
329 | ```
330 |
331 | ###### 結果
332 |
333 | ```bash
334 | Uu =
335 | {0: {1: 0.8528028654224417,
336 | 2: 0.6225430174794672,
337 | 3: 0.5816750507471109,
338 | 4: -0.9968461286620518},
339 | 1: {0: 0.8528028654224417,
340 | 2: 0.6488856845230501,
341 | 3: 0.9684959969581863,
342 | 4: -0.8528028654224418},
343 | 2: {0: 0.6225430174794672,
344 | 1: 0.6488856845230501,
345 | 3: 0.7999999999999998,
346 | 4: -0.5685352436149611},
347 | 3: {0: 0.5816750507471109,
348 | 1: 0.9684959969581863,
349 | 2: 0.7999999999999998,
350 | 4: -0.550920031004556},
351 | 4: {0: -0.9968461286620518,
352 | 1: -0.8528028654224418,
353 | 2: -0.5685352436149611,
354 | 3: -0.550920031004556}}
355 | Uu =
356 | {0: {1: 0.8528028654224417, 2: 0.6225430174794672, 3: 0.5816750507471109},
357 | 1: {0: 0.8528028654224417, 2: 0.6488856845230501, 3: 0.9684959969581863},
358 | 2: {0: 0.6225430174794672, 1: 0.6488856845230501, 3: 0.7999999999999998},
359 | 3: {0: 0.5816750507471109, 1: 0.9684959969581863, 2: 0.7999999999999998},
360 | 4: {1: -0.8528028654224418, 2: -0.5685352436149611, 3: -0.550920031004556}}
361 | Uu =
362 | {0: {1: 0.8528028654224417, 2: 0.6225430174794672, 3: 0.5816750507471109},
363 | 1: {0: 0.8528028654224417, 2: 0.6488856845230501, 3: 0.9684959969581863},
364 | 2: {0: 0.6225430174794672, 1: 0.6488856845230501, 3: 0.7999999999999998},
365 | 3: {0: 0.5816750507471109, 1: 0.9684959969581863, 2: 0.7999999999999998},
366 | 4: {}}
367 | Uu =
368 | {0: array([1, 2, 3]),
369 | 1: array([3, 0, 2]),
370 | 2: array([3, 1, 0]),
371 | 3: array([1, 2, 0]),
372 | 4: array([], dtype=float64)}
373 | ```
374 |
375 | このとき、次の問いに答えなさい。
376 |
377 | ### 08 類似度上位k人のユーザ集合
378 | `Uu`から、各ユーザ$$u \in U$$について類似度上位`K_USERS`人のみを残した辞書を生成するコードを書きなさい。生成した辞書を`Uu`とすること。
379 |
380 | ★★★
381 | 1. 辞書内包表記を使う。
382 | 2. `sorted()`を使う。
383 | 3. `dict.items()`を使う。
384 | 4. `lambda`式を使う。
385 | 5. スライシングを使う。
386 | 6. `dict()`を使う。
387 |
388 | ### 09 類似度がしきい値以上のユーザ集合
389 | `Uu`から、各ユーザ$$u \in U$$について類似度がしきい値`THETA`以上のみを残した辞書を生成するコードを書きなさい。生成した辞書を`Uu`とすること。
390 |
391 | ★★★
392 | 1. 二重の辞書内包表記を使う。
393 | 2. `dict.items()`を使う。
394 | 3. 条件式を使う。
395 |
396 | ## 嗜好予測
397 | ユーザ$$u$$のアイテム$$i$$に対する予測評価値$$\hat{r}_{u,i}$$は次式で求められる。
398 |
399 | $$
400 | \hat{r}_{u,i} = \begin{cases}
401 | \overline{r}_{u} + \frac{\sum_{v \in U_{i}^{u}} \mathrm{sim}(u, v) \cdot r_{v,i}^{'}}{\sum_{v \in U_{i}^{u}} \mid \mathrm{sim} (u, v) \mid} & (U_{i}^{u} \neq \emptyset)\\
402 | \overline{r}_{u} & (U_{i}^{u} = \emptyset)
403 | \end{cases}
404 | $$
405 |
406 | ここで、$$U_{i}^{u}$$は類似ユーザ集合$$U^{u}$$の中でアイテム$$i$$を評価済みのユーザ集合を表す。$$\emptyset$$は空集合を表す。この予測関数を次のコードのとおり定義する。
407 |
408 | ###### 関数
409 |
410 | ```python
411 | def predict(u, i):
412 | """
413 | 予測関数:ユーザuのアイテムiに対する予測評価値を返す。
414 |
415 | Parameters
416 | ----------
417 | u : int
418 | ユーザuのID
419 | i : int
420 | アイテムiのID
421 |
422 | Returns
423 | -------
424 | float
425 | ユーザuのアイテムiに対する予測評価値
426 | """
427 | 【 問10 】
428 | print('U{}{} = {}'.format(u, i, Uui))
429 |
430 | if Uui.size <= 0: return ru_mean[u]
431 | 【 問11 】
432 |
433 | return rui_pred
434 | ```
435 |
436 | ###### コード
437 |
438 | ```python
439 | u = 0
440 | i = 0
441 | print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
442 | u = 0
443 | i = 5
444 | print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))
445 | ```
446 |
447 | ###### 結果
448 |
449 | ```
450 | U00 = [1 2]
451 | r00 = 3.289
452 | U05 = [1 3]
453 | r05 = 1.601
454 | ```
455 |
456 | このとき、関数の仕様を満たすように、次の問いに答えなさい。
457 |
458 | ### 10 類似ユーザ集合の中でアイテムiを評価済みのユーザ集合
459 | ユーザ$$u$$の類似ユーザ集合$$U^{u}$$の中でアイテム$$i$$を評価済みのユーザ集合$$U_{i}^{u}$$を`ndarray`として生成するコードを書きなさい。生成された`ndarray`を`Uui`とすること。
460 |
461 | ★
462 | 1. `numpy.intersect1d()`を使う。
463 |
464 | ### 11 予測評価値
465 | $$U_{i}^{u} \neq \emptyset$$のとき、ユーザ$$u$$のアイテム$$i$$に対する予測評価値$$\hat{r}_{u,i}$$を求めるコードを書きなさい。得られた値を`rui_pred`とすること。
466 |
467 | ★★★
468 | 1. リスト内包表記を使う。
469 | 2. `numpy.sum()`を使う。
470 | 3. `numpy.abs()`を使う。
471 |
472 | ## 評価値行列の補完
473 | 評価値行列$$\boldsymbol{R}$$の欠損値を予測評価値で補完した行列$$\boldsymbol{R}^{''}$$とする。行列$$\boldsymbol{R}^{''}$$は次式のとおりとなる。
474 |
475 | $$
476 | \boldsymbol{R}^{''} = \left[
477 | \begin{array}{rrrrrr}
478 | 3.289 & 4 & 3 & 1 & 2 & 1.601 \\
479 | 5 & 5 & 4 & 3.449 & 3 & 3 \\
480 | 4 & 4.747 & 5 & 3 & 2 & 2.638 \\
481 | 2.524 & 3 & 2.384 & 2 & 1 & 1 \\
482 | 2 & 1 & 2 & 4 & 2.400 & 3
483 | \end{array}
484 | \right]
485 | $$
486 |
487 | このとき、次の問いに答えなさい。
488 |
489 | ### 12 評価値行列の補完
490 | 評価値行列$$\boldsymbol{R}$$の欠損値を予測評価値で補完した行列$$\boldsymbol{R}^{''}$$を`ndarray`として生成するコードを書きなさい。生成した`ndarray`を`R3`とすること。
491 |
492 | ###### コード
493 |
494 | ```python
495 | 【 問12 】
496 | print('R\'\' = \n{}'.format(R3))
497 | ```
498 |
499 | ###### 結果
500 |
501 | ```bash
502 | R'' =
503 | [[3.289 4. 3. 1. 2. 1.601]
504 | [5. 5. 4. 3.449 3. 3. ]
505 | [4. 4.747 5. 3. 2. 2.638]
506 | [2.524 3. 2.384 2. 1. 1. ]
507 | [2. 1. 2. 4. 2.4 3. ]]
508 | ```
509 |
510 | ★★
511 | 1. `ndarray.copy()`を使う。
512 | 2. 二重の`for`ループを使う。
513 | 4. `continue`文を使う。
514 |
515 | ★★★
516 | 1. 二重のリスト内包表記を使う。
517 | 2. 条件式を使う。
518 |
--------------------------------------------------------------------------------
/src/chap11.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "chap11.ipynb",
7 | "provenance": []
8 | },
9 | "kernelspec": {
10 | "name": "python3",
11 | "display_name": "Python 3"
12 | },
13 | "language_info": {
14 | "name": "python"
15 | }
16 | },
17 | "cells": [
18 | {
19 | "cell_type": "markdown",
20 | "source": [
21 | "# 第11章 嗜好予測の正確性"
22 | ],
23 | "metadata": {
24 | "id": "8DON_QEvW7lI"
25 | }
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "source": [
30 | "## テストデータと予測評価値"
31 | ],
32 | "metadata": {
33 | "id": "Yo9czgALW8_O"
34 | }
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "source": [
39 | "## 準備"
40 | ],
41 | "metadata": {
42 | "id": "o-a0_lUDXJeh"
43 | }
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": 1,
48 | "metadata": {
49 | "id": "SxKYQ13pW3vF"
50 | },
51 | "outputs": [],
52 | "source": [
53 | "import numpy as np\n",
54 | "\n",
55 | "# とりうる評価値の最大値\n",
56 | "R_MAX = 5\n",
57 | "# とりうる評価値の最小値\n",
58 | "R_MIN = 1\n",
59 | "\n",
60 | "# テストデータ\n",
61 | "R = np.array([\n",
62 | " [np.nan, 4, np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan],\n",
63 | " [np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan, 5, np.nan],\n",
64 | " [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan],\n",
65 | "])\n",
66 | "U = np.arange(R.shape[0])\n",
67 | "I = np.arange(R.shape[1])\n",
68 | "\n",
69 | "# 推薦システムAによる予測評価値\n",
70 | "RA = np.array([\n",
71 | " [np.nan, 2, np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan],\n",
72 | " [np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan, 3, np.nan],\n",
73 | " [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan],\n",
74 | "])\n",
75 | "\n",
76 | "# 推薦システムBによる予測評価値\n",
77 | "RB = np.array([\n",
78 | " [np.nan, 3, np.nan, np.nan, np.nan, np.nan, 1, np.nan, np.nan, np.nan],\n",
79 | " [np.nan, np.nan, np.nan, np.nan, 3, np.nan, np.nan, np.nan, 4, np.nan],\n",
80 | " [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 4, np.nan, np.nan],\n",
81 | "])"
82 | ]
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "source": [
87 | "## 平均絶対誤差"
88 | ],
89 | "metadata": {
90 | "id": "6feMHCDyXRS4"
91 | }
92 | },
93 | {
94 | "cell_type": "markdown",
95 | "source": [
96 | "### 01 推薦システムAのMAE"
97 | ],
98 | "metadata": {
99 | "id": "0DD19dovXqqn"
100 | }
101 | },
102 | {
103 | "cell_type": "code",
104 | "source": [
105 | "MAE_A = np.nansum([[np.abs(RA[u,i] - R[u,i]) for i in I] for u in U]) / np.count_nonzero(~np.isnan(R))\n",
106 | "print('MAE_{} = {:.3f}'.format('A', MAE_A))"
107 | ],
108 | "metadata": {
109 | "colab": {
110 | "base_uri": "https://localhost:8080/"
111 | },
112 | "id": "ezJRUnwUXVj1",
113 | "outputId": "415710ea-b6e7-481a-b84d-6ed09db43cab"
114 | },
115 | "execution_count": 2,
116 | "outputs": [
117 | {
118 | "output_type": "stream",
119 | "name": "stdout",
120 | "text": [
121 | "MAE_A = 0.800\n"
122 | ]
123 | }
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "source": [
129 | "### 02 推薦システムBのMAE"
130 | ],
131 | "metadata": {
132 | "id": "NUbwMOSSXsyo"
133 | }
134 | },
135 | {
136 | "cell_type": "code",
137 | "source": [
138 | "MAE_B = np.nansum([[np.abs(RB[u,i] - R[u,i]) for i in I] for u in U]) / np.count_nonzero(~np.isnan(R))\n",
139 | "print('MAE_{} = {:.3f}'.format('B', MAE_B))"
140 | ],
141 | "metadata": {
142 | "colab": {
143 | "base_uri": "https://localhost:8080/"
144 | },
145 | "id": "-A4N7YdgXxOL",
146 | "outputId": "00f521ad-a441-43db-e130-eb5567ec7d50"
147 | },
148 | "execution_count": 3,
149 | "outputs": [
150 | {
151 | "output_type": "stream",
152 | "name": "stdout",
153 | "text": [
154 | "MAE_B = 1.000\n"
155 | ]
156 | }
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "source": [
162 | "## 平均二乗誤差"
163 | ],
164 | "metadata": {
165 | "id": "r46czGrZX0Ep"
166 | }
167 | },
168 | {
169 | "cell_type": "markdown",
170 | "source": [
171 | "### 03 推薦システムAのMSE"
172 | ],
173 | "metadata": {
174 | "id": "AX6-ASukX4m6"
175 | }
176 | },
177 | {
178 | "cell_type": "code",
179 | "source": [
180 | "MSE_A = np.nansum([[(RA[u,i] - R[u,i])**2 for i in I] for u in U]) / np.count_nonzero(~np.isnan(R))\n",
181 | "print('MSE_{} = {:.3f}'.format('A', MSE_A))"
182 | ],
183 | "metadata": {
184 | "colab": {
185 | "base_uri": "https://localhost:8080/"
186 | },
187 | "id": "Py9gpFdQX74N",
188 | "outputId": "33035f04-4c1a-499a-9621-249f1f68b035"
189 | },
190 | "execution_count": 4,
191 | "outputs": [
192 | {
193 | "output_type": "stream",
194 | "name": "stdout",
195 | "text": [
196 | "MSE_A = 1.600\n"
197 | ]
198 | }
199 | ]
200 | },
201 | {
202 | "cell_type": "markdown",
203 | "source": [
204 | "### 04 推薦システムBのMSE"
205 | ],
206 | "metadata": {
207 | "id": "vEOHzxcGX-BY"
208 | }
209 | },
210 | {
211 | "cell_type": "code",
212 | "source": [
213 | "MSE_B = np.nansum([[(RB[u,i] - R[u,i])**2 for i in I] for u in U]) / np.count_nonzero(~np.isnan(R))\n",
214 | "print('MSE_{} = {:.3f}'.format('B', MSE_B))"
215 | ],
216 | "metadata": {
217 | "colab": {
218 | "base_uri": "https://localhost:8080/"
219 | },
220 | "id": "4yb2oQemX9Wq",
221 | "outputId": "6fa56eea-23d6-408b-b414-aa2ebba0df7d"
222 | },
223 | "execution_count": 5,
224 | "outputs": [
225 | {
226 | "output_type": "stream",
227 | "name": "stdout",
228 | "text": [
229 | "MSE_B = 1.000\n"
230 | ]
231 | }
232 | ]
233 | },
234 | {
235 | "cell_type": "markdown",
236 | "source": [
237 | "## 二乗平均平方根誤差"
238 | ],
239 | "metadata": {
240 | "id": "kVUDFE55YTHK"
241 | }
242 | },
243 | {
244 | "cell_type": "markdown",
245 | "source": [
246 | "### 05 推薦システムAのRMSE"
247 | ],
248 | "metadata": {
249 | "id": "wCkxswaKYUHW"
250 | }
251 | },
252 | {
253 | "cell_type": "code",
254 | "source": [
255 | "RMSE_A = np.sqrt(MSE_A)\n",
256 | "print('RMSE_{} = {:.3f}'.format('A', RMSE_A))"
257 | ],
258 | "metadata": {
259 | "colab": {
260 | "base_uri": "https://localhost:8080/"
261 | },
262 | "id": "coMSbtPmYX9H",
263 | "outputId": "2d8fdb1d-6620-4a9d-95e9-965f5e99010e"
264 | },
265 | "execution_count": 6,
266 | "outputs": [
267 | {
268 | "output_type": "stream",
269 | "name": "stdout",
270 | "text": [
271 | "RMSE_A = 1.265\n"
272 | ]
273 | }
274 | ]
275 | },
276 | {
277 | "cell_type": "code",
278 | "source": [
279 | "RMSE_A = np.sqrt(np.nansum([[(RA[u,i] - R[u,i])**2 for i in I] for u in U]) / np.count_nonzero(~np.isnan(R)))\n",
280 | "print('RMSE_{} = {:.3f}'.format('A', RMSE_A))"
281 | ],
282 | "metadata": {
283 | "colab": {
284 | "base_uri": "https://localhost:8080/"
285 | },
286 | "id": "tm8w0p5lYeHz",
287 | "outputId": "874b5836-db18-41e4-c401-72ab0c49bad5"
288 | },
289 | "execution_count": 7,
290 | "outputs": [
291 | {
292 | "output_type": "stream",
293 | "name": "stdout",
294 | "text": [
295 | "RMSE_A = 1.265\n"
296 | ]
297 | }
298 | ]
299 | },
300 | {
301 | "cell_type": "markdown",
302 | "source": [
303 | "### 06 推薦システムBのRMSE"
304 | ],
305 | "metadata": {
306 | "id": "Dody1aemYVnS"
307 | }
308 | },
309 | {
310 | "cell_type": "code",
311 | "source": [
312 | "RMSE_B = np.sqrt(MSE_B)\n",
313 | "print('RMSE_{} = {:.3f}'.format('B', RMSE_B))"
314 | ],
315 | "metadata": {
316 | "colab": {
317 | "base_uri": "https://localhost:8080/"
318 | },
319 | "id": "ClPC_xtoYkmm",
320 | "outputId": "8eff6cac-2529-4cfc-fb1e-87857c59e86b"
321 | },
322 | "execution_count": 8,
323 | "outputs": [
324 | {
325 | "output_type": "stream",
326 | "name": "stdout",
327 | "text": [
328 | "RMSE_B = 1.000\n"
329 | ]
330 | }
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "source": [
336 | "RMSE_B = np.sqrt(np.nansum([[(RB[u,i] - R[u,i])**2 for i in I] for u in U]) / np.count_nonzero(~np.isnan(R)))\n",
337 | "print('RMSE_{} = {:.3f}'.format('B', RMSE_B))"
338 | ],
339 | "metadata": {
340 | "colab": {
341 | "base_uri": "https://localhost:8080/"
342 | },
343 | "id": "pY1rJppNYm2D",
344 | "outputId": "64a5a146-fe90-4eaf-c0fc-bbe14ded7312"
345 | },
346 | "execution_count": 9,
347 | "outputs": [
348 | {
349 | "output_type": "stream",
350 | "name": "stdout",
351 | "text": [
352 | "RMSE_B = 1.000\n"
353 | ]
354 | }
355 | ]
356 | },
357 | {
358 | "cell_type": "markdown",
359 | "source": [
360 | "## 正規化MAEと正規化RMSE"
361 | ],
362 | "metadata": {
363 | "id": "g4MMC1jYY13L"
364 | }
365 | },
366 | {
367 | "cell_type": "markdown",
368 | "source": [
369 | "### 07 推薦システムAのNMAE"
370 | ],
371 | "metadata": {
372 | "id": "zFEDIyG9Y4FW"
373 | }
374 | },
375 | {
376 | "cell_type": "code",
377 | "source": [
378 | "NMAE_A = MAE_A / (R_MAX - R_MIN)\n",
379 | "print('NMAE_{} = {:.3f}'.format('A', NMAE_A))"
380 | ],
381 | "metadata": {
382 | "colab": {
383 | "base_uri": "https://localhost:8080/"
384 | },
385 | "id": "HWP2fizaY_FM",
386 | "outputId": "ecddf41c-02a2-482b-c959-f3f3f77ded64"
387 | },
388 | "execution_count": 10,
389 | "outputs": [
390 | {
391 | "output_type": "stream",
392 | "name": "stdout",
393 | "text": [
394 | "NMAE_A = 0.200\n"
395 | ]
396 | }
397 | ]
398 | },
399 | {
400 | "cell_type": "markdown",
401 | "source": [
402 | "### 08 推薦システムBのNMAE"
403 | ],
404 | "metadata": {
405 | "id": "0KttpELBY6al"
406 | }
407 | },
408 | {
409 | "cell_type": "code",
410 | "source": [
411 | "NMAE_B = MAE_B / (R_MAX - R_MIN)\n",
412 | "print('NMAE_{} = {:.3f}'.format('B', NMAE_B))"
413 | ],
414 | "metadata": {
415 | "colab": {
416 | "base_uri": "https://localhost:8080/"
417 | },
418 | "id": "E4xxC8h5ZCtD",
419 | "outputId": "cbd21581-2cd3-47ce-e23b-b41d1d69b5ff"
420 | },
421 | "execution_count": 11,
422 | "outputs": [
423 | {
424 | "output_type": "stream",
425 | "name": "stdout",
426 | "text": [
427 | "NMAE_B = 0.250\n"
428 | ]
429 | }
430 | ]
431 | },
432 | {
433 | "cell_type": "markdown",
434 | "source": [
435 | "### 09 推薦システムAのNRMSE"
436 | ],
437 | "metadata": {
438 | "id": "1bbSAjbFY7VZ"
439 | }
440 | },
441 | {
442 | "cell_type": "code",
443 | "source": [
444 | "NRMSE_A = RMSE_A / (R_MAX - R_MIN)\n",
445 | "print('NRMSE_{} = {:.3f}'.format('A', NRMSE_A))"
446 | ],
447 | "metadata": {
448 | "colab": {
449 | "base_uri": "https://localhost:8080/"
450 | },
451 | "id": "_fXibkEsZE--",
452 | "outputId": "9165946f-679a-449f-a47d-ffce8fdb0c57"
453 | },
454 | "execution_count": 12,
455 | "outputs": [
456 | {
457 | "output_type": "stream",
458 | "name": "stdout",
459 | "text": [
460 | "NRMSE_A = 0.316\n"
461 | ]
462 | }
463 | ]
464 | },
465 | {
466 | "cell_type": "markdown",
467 | "source": [
468 | "### 10 推薦システムBのNRMSE"
469 | ],
470 | "metadata": {
471 | "id": "iWxHW1s1Y9Lw"
472 | }
473 | },
474 | {
475 | "cell_type": "code",
476 | "source": [
477 | "NRMSE_B = RMSE_B / (R_MAX - R_MIN)\n",
478 | "print('NRMSE_{} = {:.3f}'.format('B', NRMSE_B))"
479 | ],
480 | "metadata": {
481 | "colab": {
482 | "base_uri": "https://localhost:8080/"
483 | },
484 | "id": "HlZzzo_vZGSv",
485 | "outputId": "b1d55efa-a2f9-4eab-9d07-525d1ca7d9ce"
486 | },
487 | "execution_count": 13,
488 | "outputs": [
489 | {
490 | "output_type": "stream",
491 | "name": "stdout",
492 | "text": [
493 | "NRMSE_B = 0.250\n"
494 | ]
495 | }
496 | ]
497 | }
498 | ]
499 | }
--------------------------------------------------------------------------------
/src/chap03.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "chap03.ipynb",
7 | "provenance": [],
8 | "collapsed_sections": []
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | }
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "markdown",
21 | "source": [
22 | "# 第3章 類似度に基づく推薦"
23 | ],
24 | "metadata": {
25 | "id": "s3oR5Uhv0Q3-"
26 | }
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "source": [
31 | "## 準備"
32 | ],
33 | "metadata": {
34 | "id": "fWWNpzne0VPi"
35 | }
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": 1,
40 | "metadata": {
41 | "id": "VZhvSMmL0BGL"
42 | },
43 | "outputs": [],
44 | "source": [
45 | "import pprint\n",
46 | "import numpy as np\n",
47 | "\n",
48 | "# 上位K件\n",
49 | "TOP_K = 3\n",
50 | "\n",
51 | "Du = np.array([\n",
52 | " [5, 3, +1],\n",
53 | " [6, 2, +1],\n",
54 | " [4, 1, +1],\n",
55 | " [8, 5, -1],\n",
56 | " [2, 4, -1],\n",
57 | " [3, 6, -1],\n",
58 | " [7, 6, -1],\n",
59 | " [4, 2, np.nan],\n",
60 | " [5, 1, np.nan],\n",
61 | " [8, 6, np.nan],\n",
62 | " [3, 4, np.nan],\n",
63 | " [4, 7, np.nan],\n",
64 | " [4, 4, np.nan],\n",
65 | "])\n",
66 | "I = np.arange(Du.shape[0])\n",
67 | "x = Du[:,:-1]\n",
68 | "ru = Du[:,-1]\n",
69 | "\n",
70 | "Iu = I[~np.isnan(ru)]\n",
71 | "Iup = I[ru==+1]\n",
72 | "Iun = I[ru==-1]\n",
73 | "Iu_not = np.setdiff1d(I, Iu)"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "source": [
79 | "## ユーザプロファイル"
80 | ],
81 | "metadata": {
82 | "id": "UiQ-t4_c1YWO"
83 | }
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "source": [
88 | "### 01 好きなアイテム集合に含まれるアイテムの特徴ベクトルの集合"
89 | ],
90 | "metadata": {
91 | "id": "HSZ3mLL71be7"
92 | }
93 | },
94 | {
95 | "cell_type": "code",
96 | "source": [
97 | "print('x[Iu+] = \\n{}'.format(x[Iup]))"
98 | ],
99 | "metadata": {
100 | "colab": {
101 | "base_uri": "https://localhost:8080/"
102 | },
103 | "id": "HlynJnpu1ghx",
104 | "outputId": "774c6fbe-ab45-4f13-eab5-e8508880b692"
105 | },
106 | "execution_count": 2,
107 | "outputs": [
108 | {
109 | "output_type": "stream",
110 | "name": "stdout",
111 | "text": [
112 | "x[Iu+] = \n",
113 | "[[5. 3.]\n",
114 | " [6. 2.]\n",
115 | " [4. 1.]]\n"
116 | ]
117 | }
118 | ]
119 | },
120 | {
121 | "cell_type": "code",
122 | "source": [
123 | "print('x[Iu+] = \\n{}'.format(np.array([x[i] for i in Iup])))"
124 | ],
125 | "metadata": {
126 | "colab": {
127 | "base_uri": "https://localhost:8080/"
128 | },
129 | "id": "x0jbbHf41vyl",
130 | "outputId": "7ca59d4a-dd86-405d-db60-a6fcf2cbca53"
131 | },
132 | "execution_count": 3,
133 | "outputs": [
134 | {
135 | "output_type": "stream",
136 | "name": "stdout",
137 | "text": [
138 | "x[Iu+] = \n",
139 | "[[5. 3.]\n",
140 | " [6. 2.]\n",
141 | " [4. 1.]]\n"
142 | ]
143 | }
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "source": [
149 | "### 02 特徴ベクトルの総和"
150 | ],
151 | "metadata": {
152 | "id": "352_K2ZV162H"
153 | }
154 | },
155 | {
156 | "cell_type": "code",
157 | "source": [
158 | "print('sum(x[Iu+]) = {}'.format(np.sum(x[Iup], axis=0)))"
159 | ],
160 | "metadata": {
161 | "colab": {
162 | "base_uri": "https://localhost:8080/"
163 | },
164 | "id": "jCq-mqol18-u",
165 | "outputId": "30ba5d54-1014-46c5-ac3f-73695f8a3405"
166 | },
167 | "execution_count": 4,
168 | "outputs": [
169 | {
170 | "output_type": "stream",
171 | "name": "stdout",
172 | "text": [
173 | "sum(x[Iu+]) = [15. 6.]\n"
174 | ]
175 | }
176 | ]
177 | },
178 | {
179 | "cell_type": "code",
180 | "source": [
181 | "print('sum(x[Iu+]) = {}'.format(np.sum([x[i] for i in Iup], axis=0)))"
182 | ],
183 | "metadata": {
184 | "colab": {
185 | "base_uri": "https://localhost:8080/"
186 | },
187 | "id": "SBLlKE962Lm7",
188 | "outputId": "ff836c8e-ddf8-483a-b90b-d1f0fbd8c69a"
189 | },
190 | "execution_count": 5,
191 | "outputs": [
192 | {
193 | "output_type": "stream",
194 | "name": "stdout",
195 | "text": [
196 | "sum(x[Iu+]) = [15. 6.]\n"
197 | ]
198 | }
199 | ]
200 | },
201 | {
202 | "cell_type": "markdown",
203 | "source": [
204 | "### 03 ユーザプロファイル"
205 | ],
206 | "metadata": {
207 | "id": "XSj-Yh7w2Q7e"
208 | }
209 | },
210 | {
211 | "cell_type": "code",
212 | "source": [
213 | "pu = np.array((1 / Iup.size) * np.sum(x[Iup], axis=0))\n",
214 | "print('pu = {}'.format(pu))"
215 | ],
216 | "metadata": {
217 | "colab": {
218 | "base_uri": "https://localhost:8080/"
219 | },
220 | "id": "QRSX8_ad2Tk6",
221 | "outputId": "bb1b436e-082a-4021-b2cb-9f34214903ce"
222 | },
223 | "execution_count": 6,
224 | "outputs": [
225 | {
226 | "output_type": "stream",
227 | "name": "stdout",
228 | "text": [
229 | "pu = [5. 2.]\n"
230 | ]
231 | }
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "source": [
237 | "pu = np.array((1 / Iup.size) * np.sum([x[i] for i in Iup], axis=0))\n",
238 | "print('pu = {}'.format(pu))"
239 | ],
240 | "metadata": {
241 | "colab": {
242 | "base_uri": "https://localhost:8080/"
243 | },
244 | "id": "lXk_A4BJ2Zgr",
245 | "outputId": "765e93a9-11dc-42cd-e41b-b19db0747b49"
246 | },
247 | "execution_count": 7,
248 | "outputs": [
249 | {
250 | "output_type": "stream",
251 | "name": "stdout",
252 | "text": [
253 | "pu = [5. 2.]\n"
254 | ]
255 | }
256 | ]
257 | },
258 | {
259 | "cell_type": "markdown",
260 | "source": [
261 | "## コサイン類似度"
262 | ],
263 | "metadata": {
264 | "id": "ML3xrs9Z2p6O"
265 | }
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "source": [
270 | "### 04 ベクトルの内積\n",
271 | "### 05 ユーザプロファイルのノルム\n",
272 | "### 06 特徴ベクトルのノルム"
273 | ],
274 | "metadata": {
275 | "id": "K59K676y2sAZ"
276 | }
277 | },
278 | {
279 | "cell_type": "code",
280 | "source": [
281 | "def cos(pu, xi):\n",
282 | " \"\"\"\n",
283 | " コサイン類似度関数:ユーザプロファイルpuとアイテムiの特徴ベクトルxiのコサイン類似度を返す。\n",
284 | "\n",
285 | " Parameters\n",
286 | " ----------\n",
287 | " pu : ndarray\n",
288 | " ユーザuのユーザプロファイル\n",
289 | " xi : ndarray\n",
290 | " アイテムiの特徴ベクトル\n",
291 | "\n",
292 | " Returns\n",
293 | " -------\n",
294 | " float\n",
295 | " コサイン類似度\n",
296 | " \"\"\"\n",
297 | " # 04\n",
298 | " num = pu@xi\n",
299 | "# num = np.dot(pu, xi)\n",
300 | "# d = pu.size\n",
301 | "# num = np.sum([pu[k] * xi[k] for k in range(0, d)])\n",
302 | "# num = np.sum(pu * xi)\n",
303 | " print('num = {}'.format(num))\n",
304 | " # 05\n",
305 | " den_u = np.linalg.norm(pu)\n",
306 | "# den_u = np.sqrt(pu@pu)\n",
307 | "# den_u = np.sqrt(np.sum([pu[k]**2 for k in range(0, d)]))\n",
308 | "# den_u = np.sqrt(np.sum(pu**2))\n",
309 | " print('den_u = {:.3f}'.format(den_u))\n",
310 | " # 06\n",
311 | " den_i = np.linalg.norm(xi)\n",
312 | "# den_i = np.sqrt(xi@xi)\n",
313 | "# den_i = np.sqrt(np.sum([xi[k]**2 for k in range(0, d)]))\n",
314 | "# den_i = np.sqrt(np.sum(xi**2))\n",
315 | "# print('den_i = {:.3f}'.format(den_i))\n",
316 | " print('den_i = {:.3f}'.format(den_i))\n",
317 | "\n",
318 | " cosine = num / (den_u * den_i)\n",
319 | " return cosine"
320 | ],
321 | "metadata": {
322 | "id": "_lSe_nH43TQX"
323 | },
324 | "execution_count": 8,
325 | "outputs": []
326 | },
327 | {
328 | "cell_type": "code",
329 | "source": [
330 | "u = 0\n",
331 | "i = 7\n",
332 | "print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))\n",
333 | "u = 0\n",
334 | "i = 11\n",
335 | "print('cos(p{}, x{}) = {:.3f}'.format(u, i, cos(pu, x[i])))"
336 | ],
337 | "metadata": {
338 | "id": "EEL0sPTC3ogn",
339 | "colab": {
340 | "base_uri": "https://localhost:8080/"
341 | },
342 | "outputId": "2c2653e6-8cd1-462e-b19c-6846aa630619"
343 | },
344 | "execution_count": 9,
345 | "outputs": [
346 | {
347 | "output_type": "stream",
348 | "name": "stdout",
349 | "text": [
350 | "num = 24.0\n",
351 | "den_u = 5.385\n",
352 | "den_i = 4.472\n",
353 | "cos(p0, x7) = 0.997\n",
354 | "num = 34.0\n",
355 | "den_u = 5.385\n",
356 | "den_i = 8.062\n",
357 | "cos(p0, x11) = 0.783\n"
358 | ]
359 | }
360 | ]
361 | },
362 | {
363 | "cell_type": "markdown",
364 | "source": [
365 | "## 推薦"
366 | ],
367 | "metadata": {
368 | "id": "Tx-qL07C5ht5"
369 | }
370 | },
371 | {
372 | "cell_type": "code",
373 | "source": [
374 | "def score(u, i):\n",
375 | " \"\"\"\n",
376 | " スコア関数:ユーザuのアイテムiに対するスコアを返す。\n",
377 | "\n",
378 | " Parameters\n",
379 | " ----------\n",
380 | " u : int\n",
381 | " ユーザuのID(ダミー)\n",
382 | " i : int\n",
383 | " アイテムiのID\n",
384 | "\n",
385 | " Returns\n",
386 | " -------\n",
387 | " float\n",
388 | " スコア\n",
389 | " \"\"\"\n",
390 | " return cos(pu, x[i])"
391 | ],
392 | "metadata": {
393 | "id": "cvK3OOFM5kEs"
394 | },
395 | "execution_count": 10,
396 | "outputs": []
397 | },
398 | {
399 | "cell_type": "markdown",
400 | "source": [
401 | "### 07 各アイテムに対するスコア\n",
402 | "### 08 推薦リスト"
403 | ],
404 | "metadata": {
405 | "id": "UrLNr8H76pmm"
406 | }
407 | },
408 | {
409 | "cell_type": "code",
410 | "source": [
411 | "def order(u, I):\n",
412 | " \"\"\"\n",
413 | " 順序付け関数:アイテム集合Iにおいて、ユーザu向けの推薦リストを返す。\n",
414 | "\n",
415 | " Parameters\n",
416 | " ----------\n",
417 | " u : int\n",
418 | " ユーザuのID\n",
419 | " I : ndarray\n",
420 | " アイテム集合\n",
421 | "\n",
422 | " Returns\n",
423 | " -------\n",
424 | " list\n",
425 | " タプル(アイテムID: スコア)を要素にした推薦リスト\n",
426 | " \"\"\"\n",
427 | " # 07\n",
428 | "# scores = {}\n",
429 | "# for i in I:\n",
430 | "# scores[i] = score(u, i)\n",
431 | " scores = {i: score(u, i) for i in I}\n",
432 | " print('scores = ')\n",
433 | " pprint.pprint(scores)\n",
434 | " # 08\n",
435 | " rec_list = sorted(scores.items(), key=lambda x:x[1], reverse=True)[:TOP_K]\n",
436 | " return rec_list"
437 | ],
438 | "metadata": {
439 | "id": "zgjYej8E6wlt"
440 | },
441 | "execution_count": 11,
442 | "outputs": []
443 | },
444 | {
445 | "cell_type": "code",
446 | "source": [
447 | "u = 0\n",
448 | "rec_list = order(u, Iu_not)\n",
449 | "print('rec_list = ')\n",
450 | "for i, scr in rec_list:\n",
451 | " print('{}: {:.3f}'.format(i, scr))"
452 | ],
453 | "metadata": {
454 | "id": "fCA6a6Us64eV",
455 | "colab": {
456 | "base_uri": "https://localhost:8080/"
457 | },
458 | "outputId": "3b65cc2a-6bec-4cee-a9e3-7de3a0a7cfb6"
459 | },
460 | "execution_count": 12,
461 | "outputs": [
462 | {
463 | "output_type": "stream",
464 | "name": "stdout",
465 | "text": [
466 | "num = 24.0\n",
467 | "den_u = 5.385\n",
468 | "den_i = 4.472\n",
469 | "num = 27.0\n",
470 | "den_u = 5.385\n",
471 | "den_i = 5.099\n",
472 | "num = 52.0\n",
473 | "den_u = 5.385\n",
474 | "den_i = 10.000\n",
475 | "num = 23.0\n",
476 | "den_u = 5.385\n",
477 | "den_i = 5.000\n",
478 | "num = 34.0\n",
479 | "den_u = 5.385\n",
480 | "den_i = 8.062\n",
481 | "num = 28.0\n",
482 | "den_u = 5.385\n",
483 | "den_i = 5.657\n",
484 | "scores = \n",
485 | "{7: 0.9965457582448796,\n",
486 | " 8: 0.9832820049844603,\n",
487 | " 9: 0.9656157585206697,\n",
488 | " 10: 0.8541985556144386,\n",
489 | " 11: 0.783110847498294,\n",
490 | " 12: 0.9191450300180578}\n",
491 | "rec_list = \n",
492 | "7: 0.997\n",
493 | "8: 0.983\n",
494 | "9: 0.966\n"
495 | ]
496 | }
497 | ]
498 | }
499 | ]
500 | }
--------------------------------------------------------------------------------
/src/chap08.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "chap08.ipynb",
7 | "provenance": [],
8 | "collapsed_sections": []
9 | },
10 | "kernelspec": {
11 | "name": "python3",
12 | "display_name": "Python 3"
13 | },
14 | "language_info": {
15 | "name": "python"
16 | }
17 | },
18 | "cells": [
19 | {
20 | "cell_type": "markdown",
21 | "source": [
22 | "# 第8章 評価値行列の次元削減"
23 | ],
24 | "metadata": {
25 | "id": "ZrPITsu7sjQ5"
26 | }
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "source": [
31 | "## 準備"
32 | ],
33 | "metadata": {
34 | "id": "f8GO15U9sk4u"
35 | }
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": 1,
40 | "metadata": {
41 | "id": "N0jpJFCej7md"
42 | },
43 | "outputs": [],
44 | "source": [
45 | "import numpy as np\n",
46 | "import numpy.linalg as LA\n",
47 | "np.set_printoptions(precision=3)\n",
48 | "\n",
49 | "# 縮約後の次元数\n",
50 | "DIM = 2\n",
51 | "\n",
52 | "R = np.array([\n",
53 | " [np.nan, 4, 3, 1, 2, np.nan],\n",
54 | " [5, 5, 4, np.nan, 3, 3 ],\n",
55 | " [4, np.nan, 5, 3, 2, np.nan],\n",
56 | " [np.nan, 3, np.nan, 2, 1, 1 ],\n",
57 | " [2, 1, 2, 4, np.nan, 3 ],\n",
58 | "])\n",
59 | "U = np.arange(R.shape[0])\n",
60 | "I = np.arange(R.shape[1])\n",
61 | "Ui = [U[~np.isnan(R)[:,i]] for i in I]\n",
62 | "Iu = [I[~np.isnan(R)[u,:]] for u in U]\n",
63 | "ru_mean = np.nanmean(R, axis=1)\n",
64 | "R2 = R - ru_mean.reshape((ru_mean.size, 1))"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "source": [
70 | "## 分散共分散行列"
71 | ],
72 | "metadata": {
73 | "id": "crQjxjR7sqX6"
74 | }
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "source": [
79 | "### 01 各アイテムに対して与えられた平均中心化評価値の平均値"
80 | ],
81 | "metadata": {
82 | "id": "bFNdqGcdst6M"
83 | }
84 | },
85 | {
86 | "cell_type": "code",
87 | "source": [
88 | "ri2_mean = np.nanmean(R2, axis=0)\n",
89 | "print('ri2_mean = {}'.format(ri2_mean))"
90 | ],
91 | "metadata": {
92 | "colab": {
93 | "base_uri": "https://localhost:8080/"
94 | },
95 | "id": "pH4PdSdms37L",
96 | "outputId": "5eb76e23-1ad6-4ebb-ac4e-417aeb8f498e"
97 | },
98 | "execution_count": 2,
99 | "outputs": [
100 | {
101 | "output_type": "stream",
102 | "name": "stdout",
103 | "text": [
104 | "ri2_mean = [ 0.367 0.588 0.4 -0.037 -0.938 -0.383]\n"
105 | ]
106 | }
107 | ]
108 | },
109 | {
110 | "cell_type": "code",
111 | "source": [
112 | "ri2_mean = np.array([(1 / Ui[i].size) * np.sum([R2[u,i] for u in Ui[i]]) for i in I])\n",
113 | "print('ri2_mean = {}'.format(ri2_mean))"
114 | ],
115 | "metadata": {
116 | "colab": {
117 | "base_uri": "https://localhost:8080/"
118 | },
119 | "id": "1eV6o3k535M4",
120 | "outputId": "982dad92-2f83-41d6-8b35-d646606ff5b0"
121 | },
122 | "execution_count": 3,
123 | "outputs": [
124 | {
125 | "output_type": "stream",
126 | "name": "stdout",
127 | "text": [
128 | "ri2_mean = [ 0.367 0.588 0.4 -0.037 -0.938 -0.383]\n"
129 | ]
130 | }
131 | ]
132 | },
133 | {
134 | "cell_type": "markdown",
135 | "source": [
136 | "### 02 各アイテムの平均中心化評価値の分散"
137 | ],
138 | "metadata": {
139 | "id": "hJwPNOXys7AJ"
140 | }
141 | },
142 | {
143 | "cell_type": "code",
144 | "source": [
145 | "s2 = np.nanvar(R2, axis=0)\n",
146 | "print('s2 = {}'.format(s2))"
147 | ],
148 | "metadata": {
149 | "colab": {
150 | "base_uri": "https://localhost:8080/"
151 | },
152 | "id": "oxhoU-VQs_Tu",
153 | "outputId": "2a97b020-f14d-43c3-a4f5-624279cdfc9c"
154 | },
155 | "execution_count": 4,
156 | "outputs": [
157 | {
158 | "output_type": "stream",
159 | "name": "stdout",
160 | "text": [
161 | "s2 = [0.336 1.348 0.505 1.279 0.137 0.494]\n"
162 | ]
163 | }
164 | ]
165 | },
166 | {
167 | "cell_type": "code",
168 | "source": [
169 | "s2 = np.array([(1 / Ui[i].size) * np.sum([(R2[u,i] - ri2_mean[i])**2 for u in Ui[i]]) for i in I])\n",
170 | "print('s2 = {}'.format(s2))"
171 | ],
172 | "metadata": {
173 | "colab": {
174 | "base_uri": "https://localhost:8080/"
175 | },
176 | "id": "nWfHEVgcs__0",
177 | "outputId": "18a0f852-e52f-4bc5-ca15-1027a8c1c976"
178 | },
179 | "execution_count": 5,
180 | "outputs": [
181 | {
182 | "output_type": "stream",
183 | "name": "stdout",
184 | "text": [
185 | "s2 = [0.336 1.348 0.505 1.279 0.137 0.494]\n"
186 | ]
187 | }
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "source": [
193 | "s2 = np.array([(1 / Ui[i].size) * np.nansum((R2[:,i] - ri2_mean[i])**2) for i in I])\n",
194 | "print('s2 = {}'.format(s2))"
195 | ],
196 | "metadata": {
197 | "colab": {
198 | "base_uri": "https://localhost:8080/"
199 | },
200 | "id": "wFD0SAuks8cG",
201 | "outputId": "ceb118d6-9234-4f4a-dda9-b9457737e5a4"
202 | },
203 | "execution_count": 6,
204 | "outputs": [
205 | {
206 | "output_type": "stream",
207 | "name": "stdout",
208 | "text": [
209 | "s2 = [0.336 1.348 0.505 1.279 0.137 0.494]\n"
210 | ]
211 | }
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "source": [
217 | "### 03 アイテムiとアイテムjの平均中心化評価値の共分散"
218 | ],
219 | "metadata": {
220 | "id": "X9S-9sxhtMvH"
221 | }
222 | },
223 | {
224 | "cell_type": "code",
225 | "source": [
226 | "i = 0\n",
227 | "j = 1\n",
228 | "Uij = np.intersect1d(Ui[i], Ui[j])\n",
229 | "sij = (1 / Uij.size) * np.sum([(R2[u,i] - ri2_mean[i]) * (R2[u,j] - ri2_mean[j]) for u in Uij]) if Uij.size > 0 else 0\n",
230 | "print('s{}{} = {:.3f}'.format(i, j, sij))"
231 | ],
232 | "metadata": {
233 | "colab": {
234 | "base_uri": "https://localhost:8080/"
235 | },
236 | "id": "72s9HyKDtN_c",
237 | "outputId": "18f45366-b3cc-4226-9595-dc57e3a3d591"
238 | },
239 | "execution_count": 7,
240 | "outputs": [
241 | {
242 | "output_type": "stream",
243 | "name": "stdout",
244 | "text": [
245 | "s01 = 0.892\n"
246 | ]
247 | }
248 | ]
249 | },
250 | {
251 | "cell_type": "markdown",
252 | "source": [
253 | "### 04 分散共分散行列"
254 | ],
255 | "metadata": {
256 | "id": "8yc3WDQqtWtM"
257 | }
258 | },
259 | {
260 | "cell_type": "code",
261 | "source": [
262 | "S = np.zeros((I.size, I.size))\n",
263 | "for i in I:\n",
264 | " for j in I:\n",
265 | " Uij = np.intersect1d(Ui[i], Ui[j])\n",
266 | " S[i,j] = (1 / Uij.size) * np.sum([(R2[u,i] - ri2_mean[i]) * (R2[u,j] - ri2_mean[j]) for u in Uij]) if Uij.size > 0 else 0\n",
267 | "print('S = \\n{}'.format(S))"
268 | ],
269 | "metadata": {
270 | "colab": {
271 | "base_uri": "https://localhost:8080/"
272 | },
273 | "id": "HHoDRj85tXxZ",
274 | "outputId": "1b5d114e-f290-4cb6-bc1a-d5bab832b322"
275 | },
276 | "execution_count": 8,
277 | "outputs": [
278 | {
279 | "output_type": "stream",
280 | "name": "stdout",
281 | "text": [
282 | "S = \n",
283 | "[[ 0.336 0.892 0.169 -0.659 -0.057 -0.572]\n",
284 | " [ 0.892 1.348 0.505 -1.466 0.166 -0.817]\n",
285 | " [ 0.169 0.505 0.505 -0.655 -0.183 -0.27 ]\n",
286 | " [-0.659 -1.466 -0.655 1.279 -0.109 0.752]\n",
287 | " [-0.057 0.166 -0.183 -0.109 0.137 -0.015]\n",
288 | " [-0.572 -0.817 -0.27 0.752 -0.015 0.494]]\n"
289 | ]
290 | }
291 | ]
292 | },
293 | {
294 | "cell_type": "markdown",
295 | "source": [
296 | "## 固有値・固有ベクトル"
297 | ],
298 | "metadata": {
299 | "id": "pgBzzrbLtgpC"
300 | }
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "source": [
305 | "### 05 固有値・固有ベクトル"
306 | ],
307 | "metadata": {
308 | "id": "b_RQm6N1tjHH"
309 | }
310 | },
311 | {
312 | "cell_type": "code",
313 | "source": [
314 | "lmd, v = LA.eig(S)\n",
315 | "print('λ = {}'.format(lmd))\n",
316 | "print('v = \\n{}'.format(v))"
317 | ],
318 | "metadata": {
319 | "colab": {
320 | "base_uri": "https://localhost:8080/"
321 | },
322 | "id": "PrvtLYWQtl4G",
323 | "outputId": "eb214b04-ed65-42ca-a7e7-4ddfdaf68a80"
324 | },
325 | "execution_count": 9,
326 | "outputs": [
327 | {
328 | "output_type": "stream",
329 | "name": "stdout",
330 | "text": [
331 | "λ = [ 3.909 0.48 0.233 -0.315 -0.049 -0.16 ]\n",
332 | "v = \n",
333 | "[[ 0.327 0.228 0.484 -0.685 0.279 -0.245]\n",
334 | " [ 0.609 0.211 -0.099 0.565 0.371 -0.344]\n",
335 | " [ 0.245 -0.806 -0.097 -0.134 -0.202 -0.472]\n",
336 | " [-0.583 0.126 0.374 0.258 -0.019 -0.661]\n",
337 | " [ 0.028 0.462 -0.624 -0.294 -0.394 -0.393]\n",
338 | " [-0.348 -0.157 -0.465 -0.204 0.767 -0.087]]\n"
339 | ]
340 | }
341 | ]
342 | },
343 | {
344 | "cell_type": "markdown",
345 | "source": [
346 | "### 06 第d主成分までの固有ベクトル"
347 | ],
348 | "metadata": {
349 | "id": "1GdM4nSmt7Gn"
350 | }
351 | },
352 | {
353 | "cell_type": "code",
354 | "source": [
355 | "indices = np.argsort(lmd)[::-1]\n",
356 | "v = v[:, indices]\n",
357 | "V = v[:, :DIM]\n",
358 | "print('V = \\n{}'.format(V))"
359 | ],
360 | "metadata": {
361 | "colab": {
362 | "base_uri": "https://localhost:8080/"
363 | },
364 | "id": "mDUcZqgJt9OQ",
365 | "outputId": "8275888d-f989-4fbd-fc2b-6d58e6fe5f65"
366 | },
367 | "execution_count": 10,
368 | "outputs": [
369 | {
370 | "output_type": "stream",
371 | "name": "stdout",
372 | "text": [
373 | "V = \n",
374 | "[[ 0.327 0.228]\n",
375 | " [ 0.609 0.211]\n",
376 | " [ 0.245 -0.806]\n",
377 | " [-0.583 0.126]\n",
378 | " [ 0.028 0.462]\n",
379 | " [-0.348 -0.157]]\n"
380 | ]
381 | }
382 | ]
383 | },
384 | {
385 | "cell_type": "markdown",
386 | "source": [
387 | "## 主成分得点"
388 | ],
389 | "metadata": {
390 | "id": "vmKTiXuOuHqA"
391 | }
392 | },
393 | {
394 | "cell_type": "markdown",
395 | "source": [
396 | "### 07 ユーザuの第k主成分得点"
397 | ],
398 | "metadata": {
399 | "id": "ocxpg_g8uJgs"
400 | }
401 | },
402 | {
403 | "cell_type": "code",
404 | "source": [
405 | "u = 0\n",
406 | "k = 0\n",
407 | "puk = np.sum([R2[u,i] * V[i,k] for i in Iu[u]]) / Iu[u].size\n",
408 | "print('p{}{} = {:.3f}'.format(u, k, puk))"
409 | ],
410 | "metadata": {
411 | "colab": {
412 | "base_uri": "https://localhost:8080/"
413 | },
414 | "id": "bxJKE8ejuLhF",
415 | "outputId": "fa01e66f-05a2-48b0-ff46-67040bf6d96d"
416 | },
417 | "execution_count": 11,
418 | "outputs": [
419 | {
420 | "output_type": "stream",
421 | "name": "stdout",
422 | "text": [
423 | "p00 = 0.474\n"
424 | ]
425 | }
426 | ]
427 | },
428 | {
429 | "cell_type": "markdown",
430 | "source": [
431 | "### 08 潜在因子行列"
432 | ],
433 | "metadata": {
434 | "id": "T3zYNUfBuPUn"
435 | }
436 | },
437 | {
438 | "cell_type": "code",
439 | "source": [
440 | "P = np.zeros((U.size, DIM))\n",
441 | "for u in U:\n",
442 | " for k in range(0, DIM):\n",
443 | " P[u,k] = np.sum([R2[u,i] * V[i,k] for i in Iu[u]]) / Iu[u].size\n",
444 | "print('P = \\n{}'.format(P))"
445 | ],
446 | "metadata": {
447 | "colab": {
448 | "base_uri": "https://localhost:8080/"
449 | },
450 | "id": "QL9Bo4wquQf8",
451 | "outputId": "9d84de93-7e27-400e-fc81-dea18ca1da3b"
452 | },
453 | "execution_count": 12,
454 | "outputs": [
455 | {
456 | "output_type": "stream",
457 | "name": "stdout",
458 | "text": [
459 | "P = \n",
460 | "[[ 0.474 -0.127]\n",
461 | " [ 0.251 0.027]\n",
462 | " [ 0.195 -0.463]\n",
463 | " [ 0.214 0.017]\n",
464 | " [-0.445 0.009]]\n"
465 | ]
466 | }
467 | ]
468 | },
469 | {
470 | "cell_type": "code",
471 | "source": [
472 | "P = np.array([[np.sum([R2[u,i] * V[i,k] for i in Iu[u]]) / Iu[u].size for k in range(0, DIM)] for u in U])\n",
473 | "print('P = \\n{}'.format(P))"
474 | ],
475 | "metadata": {
476 | "colab": {
477 | "base_uri": "https://localhost:8080/"
478 | },
479 | "id": "FlLjYODpuXSh",
480 | "outputId": "fe9c4350-94af-4a52-9f6e-0e0e7afb5d23"
481 | },
482 | "execution_count": 13,
483 | "outputs": [
484 | {
485 | "output_type": "stream",
486 | "name": "stdout",
487 | "text": [
488 | "P = \n",
489 | "[[ 0.474 -0.127]\n",
490 | " [ 0.251 0.027]\n",
491 | " [ 0.195 -0.463]\n",
492 | " [ 0.214 0.017]\n",
493 | " [-0.445 0.009]]\n"
494 | ]
495 | }
496 | ]
497 | }
498 | ]
499 | }
--------------------------------------------------------------------------------
/src/chap02.ipynb:
--------------------------------------------------------------------------------
1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"chap02.ipynb","provenance":[],"authorship_tag":"ABX9TyPnh/LVENZLGlzQSB/YyQpU"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"markdown","source":["# 第2章 評価値行列"],"metadata":{"id":"-acO_0yc0DVT"}},{"cell_type":"markdown","source":["## 準備"],"metadata":{"id":"XzzsV5JC0IRR"}},{"cell_type":"code","execution_count":1,"metadata":{"id":"mEjom-Gcz6WI","executionInfo":{"status":"ok","timestamp":1651236701310,"user_tz":-540,"elapsed":54,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}}},"outputs":[],"source":["import pprint\n","import numpy as np\n","np.set_printoptions(precision=3)"]},{"cell_type":"markdown","source":["## 評価値行列"],"metadata":{"id":"scc2I-xT0dY6"}},{"cell_type":"markdown","source":["### 01 評価値行列の生成"],"metadata":{"id":"x20w0QsW1U9r"}},{"cell_type":"code","source":["R = np.array([\n"," [np.nan, 4, 3, 1, 2, np.nan],\n"," [5, 5, 4, np.nan, 3, 3 ],\n"," [4, np.nan, 5, 3, 2, np.nan],\n"," [np.nan, 3, np.nan, 2, 1, 1 ],\n"," [2, 1, 2, 4, np.nan, 3 ],\n","])\n","print('R = \\n{}'.format(R))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"OxUz3LTU1l8f","executionInfo":{"status":"ok","timestamp":1651236701310,"user_tz":-540,"elapsed":52,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"fba6dc86-ac55-4787-b65e-fd1199ba4e7b"},"execution_count":2,"outputs":[{"output_type":"stream","name":"stdout","text":["R = \n","[[nan 4. 3. 1. 2. nan]\n"," [ 5. 5. 4. nan 3. 3.]\n"," [ 4. nan 5. 3. 2. nan]\n"," [nan 3. nan 2. 1. 1.]\n"," [ 2. 1. 2. 4. nan 3.]]\n"]}]},{"cell_type":"markdown","source":["### 02 ユーザ集合"],"metadata":{"id":"WV6htcN-1sRk"}},{"cell_type":"code","source":["U = np.arange(R.shape[0])\n","print('U = {}'.format(U))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"M62YQdwB1u9u","executionInfo":{"status":"ok","timestamp":1651236701311,"user_tz":-540,"elapsed":51,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"028a040c-223b-4118-b2e4-83d61748e3ff"},"execution_count":3,"outputs":[{"output_type":"stream","name":"stdout","text":["U = [0 1 2 3 4]\n"]}]},{"cell_type":"markdown","source":["### 03 アイテム集合"],"metadata":{"id":"pXRFGFck1y-u"}},{"cell_type":"code","source":["I = np.arange(R.shape[1])\n","print('I = {}'.format(I))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"uP3TriZS11C_","executionInfo":{"status":"ok","timestamp":1651236701311,"user_tz":-540,"elapsed":48,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"0c25fef9-e74d-4948-8d0c-f5848680bb63"},"execution_count":4,"outputs":[{"output_type":"stream","name":"stdout","text":["I = [0 1 2 3 4 5]\n"]}]},{"cell_type":"markdown","source":["### 04 ユーザ数"],"metadata":{"id":"E2Qq8gdr16Zb"}},{"cell_type":"code","source":["print('|U| = {}'.format(U.size))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"1Rjesu4N19Oo","executionInfo":{"status":"ok","timestamp":1651236701311,"user_tz":-540,"elapsed":46,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"98a609ee-5eaf-42ca-ce38-e82af1332d30"},"execution_count":5,"outputs":[{"output_type":"stream","name":"stdout","text":["|U| = 5\n"]}]},{"cell_type":"markdown","source":["### 05 アイテム数"],"metadata":{"id":"hkW8F6UU2BiR"}},{"cell_type":"code","source":["print('|I| = {}'.format(I.size))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"ebmSljHL2PXu","executionInfo":{"status":"ok","timestamp":1651236701312,"user_tz":-540,"elapsed":43,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"848170cc-59c2-4d01-9568-3f379623b0ec"},"execution_count":6,"outputs":[{"output_type":"stream","name":"stdout","text":["|I| = 6\n"]}]},{"cell_type":"markdown","source":["### 06 評価値"],"metadata":{"id":"tvNgLT9t2S0W"}},{"cell_type":"code","source":["u = 0\n","i = 1\n","print('r{}{} = {}'.format(u, i, R[u,i]))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"5syDWS1G2YI2","executionInfo":{"status":"ok","timestamp":1651236701312,"user_tz":-540,"elapsed":40,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"1dfd7bd8-e84e-423f-9196-6e79827568c3"},"execution_count":7,"outputs":[{"output_type":"stream","name":"stdout","text":["r01 = 4.0\n"]}]},{"cell_type":"markdown","source":["## 評価値行列の疎性"],"metadata":{"id":"X_fqyvLU08EK"}},{"cell_type":"markdown","source":["### 07 評価値行列の全要素数"],"metadata":{"id":"UQU7plGv2fva"}},{"cell_type":"code","source":["print('Rの全要素数 = {}'.format(R.size))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"LpSzZjN32mma","executionInfo":{"status":"ok","timestamp":1651236701312,"user_tz":-540,"elapsed":38,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"8ce9e1d5-6ce3-410b-9f2b-fff69b70af84"},"execution_count":8,"outputs":[{"output_type":"stream","name":"stdout","text":["Rの全要素数 = 30\n"]}]},{"cell_type":"markdown","source":["### 08 観測されているか否かの判定"],"metadata":{"id":"yTm3ow5s2q_W"}},{"cell_type":"code","source":["print('観測値 = \\n{}'.format(~np.isnan(R)))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Wczhef3Z2xLv","executionInfo":{"status":"ok","timestamp":1651236701313,"user_tz":-540,"elapsed":37,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"e6c0cc80-7158-4f53-a976-885711e8a947"},"execution_count":9,"outputs":[{"output_type":"stream","name":"stdout","text":["観測値 = \n","[[False True True True True False]\n"," [ True True True False True True]\n"," [ True False True True True False]\n"," [False True False True True True]\n"," [ True True True True False True]]\n"]}]},{"cell_type":"markdown","source":["### 09 評価値行列の観測値数"],"metadata":{"id":"yX7eq2L02sJJ"}},{"cell_type":"code","source":["print('|R| = {}'.format(np.count_nonzero(~np.isnan(R))))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"aQvCEKwB28N_","executionInfo":{"status":"ok","timestamp":1651236701313,"user_tz":-540,"elapsed":34,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"29cecef4-5897-45e3-d2cc-c439413f8497"},"execution_count":10,"outputs":[{"output_type":"stream","name":"stdout","text":["|R| = 22\n"]}]},{"cell_type":"code","source":["print('|R| = {}'.format(R[~np.isnan(R)].size))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Ry5MytQG3BUT","executionInfo":{"status":"ok","timestamp":1651236701313,"user_tz":-540,"elapsed":32,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"2132d729-f447-4435-ea22-8bb94c412824"},"execution_count":11,"outputs":[{"output_type":"stream","name":"stdout","text":["|R| = 22\n"]}]},{"cell_type":"markdown","source":["### 10 評価値行列の疎性"],"metadata":{"id":"xVVsbd3y2tHO"}},{"cell_type":"code","source":["sparsity = 1 - np.count_nonzero(~np.isnan(R)) / (I.size * U.size)\n","print('sparsity = {:.3f}'.format(sparsity))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"ZXJhU0DpxXWw","executionInfo":{"status":"ok","timestamp":1651236701314,"user_tz":-540,"elapsed":32,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"b599c302-2649-4a91-8bbe-8aaa2d4d3bca"},"execution_count":12,"outputs":[{"output_type":"stream","name":"stdout","text":["sparsity = 0.267\n"]}]},{"cell_type":"markdown","source":["## 評価済みアイテム集合"],"metadata":{"id":"d7asTnlK04J3"}},{"cell_type":"markdown","source":["### 11 ユーザuが評価済みのアイテム集合"],"metadata":{"id":"qz6kO4-L3Rps"}},{"cell_type":"code","source":["u = 0\n","print('I{} = {}'.format(u, I[~np.isnan(R)[u,:]]))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"umiKUNMc3UFh","executionInfo":{"status":"ok","timestamp":1651236701314,"user_tz":-540,"elapsed":30,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"a08993de-d90d-490b-8ac2-6caaf42e189e"},"execution_count":13,"outputs":[{"output_type":"stream","name":"stdout","text":["I0 = [1 2 3 4]\n"]}]},{"cell_type":"markdown","source":["### 12 各ユーザの評価済みアイテム集合"],"metadata":{"id":"Do86J9i-02Ru"}},{"cell_type":"code","source":["Iu = []\n","for u in U:\n"," Iu.append(I[~np.isnan(R)[u,:]])\n","print('Iu = ')\n","pprint.pprint(Iu)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"S6_gmWAI1JYK","executionInfo":{"status":"ok","timestamp":1651236701314,"user_tz":-540,"elapsed":28,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"0bc4c72f-1b26-4d7d-a778-005d537bca3c"},"execution_count":14,"outputs":[{"output_type":"stream","name":"stdout","text":["Iu = \n","[array([1, 2, 3, 4]),\n"," array([0, 1, 2, 4, 5]),\n"," array([0, 2, 3, 4]),\n"," array([1, 3, 4, 5]),\n"," array([0, 1, 2, 3, 5])]\n"]}]},{"cell_type":"code","source":["Iu = [I[~np.isnan(R)[u,:]] for u in U]\n","print('Iu = ')\n","pprint.pprint(Iu)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"7TDwP0HJ5Etw","executionInfo":{"status":"ok","timestamp":1651236701315,"user_tz":-540,"elapsed":27,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"1864d868-1cd1-48a9-abcf-924598ddf638"},"execution_count":15,"outputs":[{"output_type":"stream","name":"stdout","text":["Iu = \n","[array([1, 2, 3, 4]),\n"," array([0, 1, 2, 4, 5]),\n"," array([0, 2, 3, 4]),\n"," array([1, 3, 4, 5]),\n"," array([0, 1, 2, 3, 5])]\n"]}]},{"cell_type":"markdown","source":["### 13 ユーザuとユーザvの共通の評価済みアイテム集合"],"metadata":{"id":"RsaVo_ZD5d7M"}},{"cell_type":"code","source":["u = 0\n","v = 1\n","Iuv = np.intersect1d(Iu[u], Iu[v])\n","print('I{}{} = {}'.format(u, v, Iuv))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"Q3-iXDm05nKx","executionInfo":{"status":"ok","timestamp":1651236701315,"user_tz":-540,"elapsed":25,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"ddb55d25-829d-4c31-f79d-5287c4a4f53e"},"execution_count":16,"outputs":[{"output_type":"stream","name":"stdout","text":["I01 = [1 2 4]\n"]}]},{"cell_type":"markdown","source":["### 14 アイテムiを評価済みのユーザ集合"],"metadata":{"id":"PLpB9BLN5k3a"}},{"cell_type":"code","source":["i = 0\n","print('U{} = {}'.format(i, U[~np.isnan(R)[:,i]]))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"jrtouET25r-M","executionInfo":{"status":"ok","timestamp":1651236701315,"user_tz":-540,"elapsed":23,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"0915a84f-475f-4961-bc4e-210d5f6995db"},"execution_count":17,"outputs":[{"output_type":"stream","name":"stdout","text":["U0 = [1 2 4]\n"]}]},{"cell_type":"markdown","source":["### 15 各アイテムの評価済みユーザ集合"],"metadata":{"id":"5RLhlfZa5xDT"}},{"cell_type":"code","source":["Ui = []\n","for i in I:\n"," Ui.append(U[~np.isnan(R)[:,i]])\n","print('Ui = ')\n","pprint.pprint(Ui)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"RoGjGjEWyuzA","executionInfo":{"status":"ok","timestamp":1651236701683,"user_tz":-540,"elapsed":389,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"a607b34b-48dc-4571-c0fb-0851dc7c402d"},"execution_count":18,"outputs":[{"output_type":"stream","name":"stdout","text":["Ui = \n","[array([1, 2, 4]),\n"," array([0, 1, 3, 4]),\n"," array([0, 1, 2, 4]),\n"," array([0, 2, 3, 4]),\n"," array([0, 1, 2, 3]),\n"," array([1, 3, 4])]\n"]}]},{"cell_type":"code","source":["Ui = [U[~np.isnan(R)[:,i]] for i in I]\n","print('Ui = ')\n","pprint.pprint(Ui)"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"dPnAx8ZvDlCU","executionInfo":{"status":"ok","timestamp":1651236701683,"user_tz":-540,"elapsed":33,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"50052e9c-1a1a-489a-89a3-cd74a5a63ac0"},"execution_count":19,"outputs":[{"output_type":"stream","name":"stdout","text":["Ui = \n","[array([1, 2, 4]),\n"," array([0, 1, 3, 4]),\n"," array([0, 1, 2, 4]),\n"," array([0, 2, 3, 4]),\n"," array([0, 1, 2, 3]),\n"," array([1, 3, 4])]\n"]}]},{"cell_type":"markdown","source":["### 16 アイテムiとアイテムjの両方を評価済みのユーザ集合"],"metadata":{"id":"WnDjxgl56h--"}},{"cell_type":"code","source":["i = 0\n","j = 4\n","Uij = np.intersect1d(Ui[i], Ui[j])\n","print('U{}{} = {}'.format(i, j, Uij))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"TfkmXCTN6ocs","executionInfo":{"status":"ok","timestamp":1651236701684,"user_tz":-540,"elapsed":26,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"4d039b82-4da7-43dd-f6a5-f0c1823f67df"},"execution_count":20,"outputs":[{"output_type":"stream","name":"stdout","text":["U04 = [1 2]\n"]}]},{"cell_type":"markdown","source":["## 平均中心化評価値行列"],"metadata":{"id":"tsouQMTQ6ziU"}},{"cell_type":"markdown","source":["### 17 評価値行列全体の平均評価値"],"metadata":{"id":"mkgeKHQc62Jx"}},{"cell_type":"code","source":["print('R全体の平均評価値 = {:.3f}'.format(np.nanmean(R)))"],"metadata":{"id":"AJwAeUqq7F5q","executionInfo":{"status":"ok","timestamp":1651236701685,"user_tz":-540,"elapsed":24,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"colab":{"base_uri":"https://localhost:8080/"},"outputId":"a5ab2ca1-c107-41c0-f172-42a4c5660a6d"},"execution_count":21,"outputs":[{"output_type":"stream","name":"stdout","text":["R全体の平均評価値 = 2.864\n"]}]},{"cell_type":"markdown","source":["### 18 各アイテムの平均評価値"],"metadata":{"id":"l-8QmUkU65Ow"}},{"cell_type":"code","source":["ri_mean = np.nanmean(R, axis=0)\n","print('ri_mean = {}'.format(ri_mean))"],"metadata":{"id":"_16qTO3T7JjL","executionInfo":{"status":"ok","timestamp":1651236701685,"user_tz":-540,"elapsed":22,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"colab":{"base_uri":"https://localhost:8080/"},"outputId":"cbfe6a5c-40ad-4d2f-b625-b2147e705340"},"execution_count":22,"outputs":[{"output_type":"stream","name":"stdout","text":["ri_mean = [3.667 3.25 3.5 2.5 2. 2.333]\n"]}]},{"cell_type":"markdown","source":["### 19 各ユーザの平均評価値"],"metadata":{"id":"N6bxnYPf66ZQ"}},{"cell_type":"code","source":["ru_mean = np.nanmean(R, axis=1)\n","print('ru_mean = {}'.format(ru_mean))"],"metadata":{"id":"HRDivU9L7L8_","executionInfo":{"status":"ok","timestamp":1651236701685,"user_tz":-540,"elapsed":20,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"colab":{"base_uri":"https://localhost:8080/"},"outputId":"4d70e13e-e6d0-4fcf-8e6d-f51652f20c9c"},"execution_count":23,"outputs":[{"output_type":"stream","name":"stdout","text":["ru_mean = [2.5 4. 3.5 1.75 2.4 ]\n"]}]},{"cell_type":"code","source":["ru_mean = np.array([np.sum([R[u,i] for i in Iu[u]]) / Iu[u].size for u in U])\n","print('ru_mean = {}'.format(ru_mean))"],"metadata":{"id":"4gcg5qtB7R97","executionInfo":{"status":"ok","timestamp":1651236701686,"user_tz":-540,"elapsed":19,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"colab":{"base_uri":"https://localhost:8080/"},"outputId":"9cd7386b-4fad-4bcb-de77-3da623c0c70b"},"execution_count":24,"outputs":[{"output_type":"stream","name":"stdout","text":["ru_mean = [2.5 4. 3.5 1.75 2.4 ]\n"]}]},{"cell_type":"markdown","source":["### 20 評価値ベクトルの形状変換"],"metadata":{"id":"OyofS_He6-aa"}},{"cell_type":"code","source":["print('ru_mean = \\n{}'.format(ru_mean.reshape((ru_mean.size, 1))))"],"metadata":{"id":"ONKP925o7dHI","executionInfo":{"status":"ok","timestamp":1651236701686,"user_tz":-540,"elapsed":17,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"colab":{"base_uri":"https://localhost:8080/"},"outputId":"3e0ac274-4b1d-45ad-9483-31049afee3eb"},"execution_count":25,"outputs":[{"output_type":"stream","name":"stdout","text":["ru_mean = \n","[[2.5 ]\n"," [4. ]\n"," [3.5 ]\n"," [1.75]\n"," [2.4 ]]\n"]}]},{"cell_type":"markdown","source":["### 21 平均中心化評価値行列"],"metadata":{"id":"TAURDZ_46_vt"}},{"cell_type":"code","source":["R2 = R - ru_mean.reshape((ru_mean.size, 1))\n","print('R\\' = \\n{}'.format(R2))"],"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"id":"7uuBFd-pzVUP","executionInfo":{"status":"ok","timestamp":1651236701686,"user_tz":-540,"elapsed":15,"user":{"displayName":"奥 健太Oku Kenta","userId":"09120163983084738166"}},"outputId":"60dd0ce8-ff60-44ef-b247-5e880e34f126"},"execution_count":26,"outputs":[{"output_type":"stream","name":"stdout","text":["R' = \n","[[ nan 1.5 0.5 -1.5 -0.5 nan]\n"," [ 1. 1. 0. nan -1. -1. ]\n"," [ 0.5 nan 1.5 -0.5 -1.5 nan]\n"," [ nan 1.25 nan 0.25 -0.75 -0.75]\n"," [-0.4 -1.4 -0.4 1.6 nan 0.6 ]]\n"]}]}]}
--------------------------------------------------------------------------------
/src/chap06.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "name": "chap06.ipynb",
7 | "provenance": []
8 | },
9 | "kernelspec": {
10 | "name": "python3",
11 | "display_name": "Python 3"
12 | },
13 | "language_info": {
14 | "name": "python"
15 | }
16 | },
17 | "cells": [
18 | {
19 | "cell_type": "markdown",
20 | "source": [
21 | "# 第6章 アイテムベース協調フィルタリング"
22 | ],
23 | "metadata": {
24 | "id": "ltnItb_wBYCx"
25 | }
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "source": [
30 | "## 準備"
31 | ],
32 | "metadata": {
33 | "id": "t75jBwBgBkXB"
34 | }
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": 1,
39 | "metadata": {
40 | "id": "_ESpG9dOBTEz"
41 | },
42 | "outputs": [],
43 | "source": [
44 | "import pprint\n",
45 | "import numpy as np\n",
46 | "np.set_printoptions(precision=3)\n",
47 | "\n",
48 | "# 近傍アイテム数\n",
49 | "K_ITEMS = 3\n",
50 | "# 閾値\n",
51 | "THETA = 0\n",
52 | "\n",
53 | "R = np.array([\n",
54 | " [np.nan, 4, 3, 1, 2, np.nan],\n",
55 | " [5, 5, 4, np.nan, 3, 3 ],\n",
56 | " [4, np.nan, 5, 3, 2, np.nan],\n",
57 | " [np.nan, 3, np.nan, 2, 1, 1 ],\n",
58 | " [2, 1, 2, 4, np.nan, 3 ],\n",
59 | "])\n",
60 | "U = np.arange(R.shape[0])\n",
61 | "I = np.arange(R.shape[1])\n",
62 | "Ui = [U[~np.isnan(R)[:,i]] for i in I]\n",
63 | "Iu = [I[~np.isnan(R)[u,:]] for u in U]\n",
64 | "ru_mean = np.nanmean(R, axis=1)\n",
65 | "R2 = R - ru_mean.reshape((ru_mean.size, 1))"
66 | ]
67 | },
68 | {
69 | "cell_type": "markdown",
70 | "source": [
71 | "## コサイン類似度"
72 | ],
73 | "metadata": {
74 | "id": "iygCfFT7BqNV"
75 | }
76 | },
77 | {
78 | "cell_type": "markdown",
79 | "source": [
80 | "### 01 アイテムiとアイテムjのコサイン類似度"
81 | ],
82 | "metadata": {
83 | "id": "ggUXSoNeBu_M"
84 | }
85 | },
86 | {
87 | "cell_type": "code",
88 | "source": [
89 | "def cos(i, j):\n",
90 | " \"\"\"\n",
91 | " 評価値行列Rにおけるアイテムiとアイテムjのコサイン類似度を返す。\n",
92 | "\n",
93 | " Parameters\n",
94 | " ----------\n",
95 | " i : int\n",
96 | " アイテムiのID\n",
97 | " j : int\n",
98 | " アイテムjのID\n",
99 | "\n",
100 | " Returns\n",
101 | " -------\n",
102 | " float\n",
103 | " コサイン類似度\n",
104 | " \"\"\"\n",
105 | " Uij = np.intersect1d(Ui[i], Ui[j])\n",
106 | " \n",
107 | " # 01\n",
108 | " num = np.sum([R[u,i] * R[u,j] for u in Uij])\n",
109 | " den_i = np.sqrt(np.sum([R[u,i]**2 for u in Uij]))\n",
110 | " den_j = np.sqrt(np.sum([R[u,j]**2 for u in Uij]))\n",
111 | " cosine = num / (den_i * den_j)\n",
112 | " return cosine"
113 | ],
114 | "metadata": {
115 | "id": "pEnZr841Bv7c"
116 | },
117 | "execution_count": 2,
118 | "outputs": []
119 | },
120 | {
121 | "cell_type": "code",
122 | "source": [
123 | "i = 0\n",
124 | "j = 4\n",
125 | "cosine = cos(i, j)\n",
126 | "print('cos({}, {}) = {:.3f}'.format(i, j, cosine))"
127 | ],
128 | "metadata": {
129 | "colab": {
130 | "base_uri": "https://localhost:8080/"
131 | },
132 | "id": "jhsm6hpYB4WE",
133 | "outputId": "2f0adf19-bda7-451c-ddbd-90cb18c05f82"
134 | },
135 | "execution_count": 3,
136 | "outputs": [
137 | {
138 | "output_type": "stream",
139 | "name": "stdout",
140 | "text": [
141 | "cos(0, 4) = 0.996\n"
142 | ]
143 | }
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "source": [
149 | "## 調整コサイン類似度"
150 | ],
151 | "metadata": {
152 | "id": "X4jv4a7JCJk5"
153 | }
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "source": [
158 | "### 02 アイテムiとアイテムjの調整コサイン類似度"
159 | ],
160 | "metadata": {
161 | "id": "GQv1i2_dCMnW"
162 | }
163 | },
164 | {
165 | "cell_type": "code",
166 | "source": [
167 | "def adjusted_cos(i, j):\n",
168 | " \"\"\"\n",
169 | " 評価値行列R2におけるアイテムiとアイテムjの調整コサイン類似度を返す。\n",
170 | "\n",
171 | " Parameters\n",
172 | " ----------\n",
173 | " i : int\n",
174 | " アイテムiのID\n",
175 | " j : int\n",
176 | " アイテムjのID\n",
177 | "\n",
178 | " Returns\n",
179 | " -------\n",
180 | " cosine : float\n",
181 | " 調整コサイン類似度\n",
182 | " \"\"\"\n",
183 | " Uij = np.intersect1d(Ui[i], Ui[j])\n",
184 | " \n",
185 | " # 02\n",
186 | " num = np.sum([R2[u,i] * R2[u,j] for u in Uij])\n",
187 | " den_i = np.sqrt(np.sum([R2[u,i]**2 for u in Uij]))\n",
188 | " den_j = np.sqrt(np.sum([R2[u,j]**2 for u in Uij]))\n",
189 | " cosine = num / (den_i * den_j)\n",
190 | " return cosine"
191 | ],
192 | "metadata": {
193 | "id": "zdSmzQVeCNZv"
194 | },
195 | "execution_count": 4,
196 | "outputs": []
197 | },
198 | {
199 | "cell_type": "code",
200 | "source": [
201 | "i = 0\n",
202 | "j = 4\n",
203 | "cosine = adjusted_cos(i, j)\n",
204 | "print('cos({}, {})\\' = {:.3f}'.format(i, j, cosine))"
205 | ],
206 | "metadata": {
207 | "colab": {
208 | "base_uri": "https://localhost:8080/"
209 | },
210 | "id": "gKWXK-VRCb8b",
211 | "outputId": "2cbfab44-8d5b-428b-ed11-beba02d83b26"
212 | },
213 | "execution_count": 5,
214 | "outputs": [
215 | {
216 | "output_type": "stream",
217 | "name": "stdout",
218 | "text": [
219 | "cos(0, 4)' = -0.868\n"
220 | ]
221 | }
222 | ]
223 | },
224 | {
225 | "cell_type": "markdown",
226 | "source": [
227 | "## アイテム-アイテム類似度行列"
228 | ],
229 | "metadata": {
230 | "id": "nbSKkRTXCgfa"
231 | }
232 | },
233 | {
234 | "cell_type": "code",
235 | "source": [
236 | "def sim(i, j):\n",
237 | " \"\"\"\n",
238 | " アイテム類似度関数:アイテムiとアイテムjのアイテム類似度を返す。\n",
239 | "\n",
240 | " Parameters\n",
241 | " ----------\n",
242 | " i : int\n",
243 | " アイテムiのID\n",
244 | " j : int\n",
245 | " アイテムjのID\n",
246 | "\n",
247 | " Returns\n",
248 | " -------\n",
249 | " float\n",
250 | " アイテム類似度\n",
251 | " \"\"\"\n",
252 | " return adjusted_cos(i, j)"
253 | ],
254 | "metadata": {
255 | "id": "WNRuQ-tDCjWU"
256 | },
257 | "execution_count": 6,
258 | "outputs": []
259 | },
260 | {
261 | "cell_type": "markdown",
262 | "source": [
263 | "### 03 アイテム-アイテム類似度行列"
264 | ],
265 | "metadata": {
266 | "id": "ffIFP_E2CmQ3"
267 | }
268 | },
269 | {
270 | "cell_type": "code",
271 | "source": [
272 | "S = np.zeros((I.size, I.size))\n",
273 | "for i in I:\n",
274 | " for j in I:\n",
275 | " S[i,j] = sim(i, j)\n",
276 | "print('S = \\n{}'.format(S))"
277 | ],
278 | "metadata": {
279 | "colab": {
280 | "base_uri": "https://localhost:8080/"
281 | },
282 | "id": "Ql9jkLlHCo8B",
283 | "outputId": "fa73b787-01ed-4f4b-c3e3-93074ba2dab8"
284 | },
285 | "execution_count": 7,
286 | "outputs": [
287 | {
288 | "output_type": "stream",
289 | "name": "stdout",
290 | "text": [
291 | "S = \n",
292 | "[[ 1. 0.842 0.494 -0.829 -0.868 -0.987]\n",
293 | " [ 0.842 1. 0.896 -0.788 -0.91 -0.942]\n",
294 | " [ 0.494 0.896 1. -0.583 -0.845 -0.514]\n",
295 | " [-0.829 -0.788 -0.583 1. 0.469 0.497]\n",
296 | " [-0.868 -0.91 -0.845 0.469 1. 1. ]\n",
297 | " [-0.987 -0.942 -0.514 0.497 1. 1. ]]\n"
298 | ]
299 | }
300 | ]
301 | },
302 | {
303 | "cell_type": "code",
304 | "source": [
305 | "S = np.array([[sim(i, j) for j in I] for i in I])\n",
306 | "print('S = \\n{}'.format(S))"
307 | ],
308 | "metadata": {
309 | "colab": {
310 | "base_uri": "https://localhost:8080/"
311 | },
312 | "id": "lphqyOy1C3z3",
313 | "outputId": "655ac605-eb2b-4b8b-f5bb-0052a9fb358f"
314 | },
315 | "execution_count": 8,
316 | "outputs": [
317 | {
318 | "output_type": "stream",
319 | "name": "stdout",
320 | "text": [
321 | "S = \n",
322 | "[[ 1. 0.842 0.494 -0.829 -0.868 -0.987]\n",
323 | " [ 0.842 1. 0.896 -0.788 -0.91 -0.942]\n",
324 | " [ 0.494 0.896 1. -0.583 -0.845 -0.514]\n",
325 | " [-0.829 -0.788 -0.583 1. 0.469 0.497]\n",
326 | " [-0.868 -0.91 -0.845 0.469 1. 1. ]\n",
327 | " [-0.987 -0.942 -0.514 0.497 1. 1. ]]\n"
328 | ]
329 | }
330 | ]
331 | },
332 | {
333 | "cell_type": "markdown",
334 | "source": [
335 | "## 類似アイテムの選定"
336 | ],
337 | "metadata": {
338 | "id": "L0P4G3nuDBQD"
339 | }
340 | },
341 | {
342 | "cell_type": "markdown",
343 | "source": [
344 | "### 04 類似度上位k件のアイテム集合\n",
345 | "### 05 類似度がしきい値以上のアイテム集合"
346 | ],
347 | "metadata": {
348 | "id": "tHYDLjVzDF-y"
349 | }
350 | },
351 | {
352 | "cell_type": "code",
353 | "source": [
354 | "# アイテム-アイテム類似度行列から対象アイテムを除外した辞書\n",
355 | "Ii = {i: {j: S[i,j] for j in I if i != j} for i in I}\n",
356 | "print('Ii = ')\n",
357 | "pprint.pprint(Ii)\n",
358 | "# 04\n",
359 | "Ii = {i: dict(sorted(Ii[i].items(), key=lambda x:x[1], reverse=True)[:K_ITEMS]) for i in I}\n",
360 | "print('Ii = ')\n",
361 | "pprint.pprint(Ii)\n",
362 | "# 05\n",
363 | "Ii = {i: {j:s for j,s in Ii[i].items() if s >= THETA} for i in I}\n",
364 | "print('Ii = ')\n",
365 | "pprint.pprint(Ii)\n",
366 | "# 各アイテムの類似アイテム集合をまとめた辞書\n",
367 | "Ii = {i: np.array(list(Ii[i].keys())) for i in I}\n",
368 | "print('Ii = ')\n",
369 | "pprint.pprint(Ii)"
370 | ],
371 | "metadata": {
372 | "colab": {
373 | "base_uri": "https://localhost:8080/"
374 | },
375 | "id": "9pwupZIHDI7m",
376 | "outputId": "3ac838df-31a9-4f8a-831b-cf03b95d9ecb"
377 | },
378 | "execution_count": 9,
379 | "outputs": [
380 | {
381 | "output_type": "stream",
382 | "name": "stdout",
383 | "text": [
384 | "Ii = \n",
385 | "{0: {1: 0.8418791389638738,\n",
386 | " 2: 0.49365474375598073,\n",
387 | " 3: -0.8291725540450335,\n",
388 | " 4: -0.8682431421244593,\n",
389 | " 5: -0.987241120712647},\n",
390 | " 1: {0: 0.8418791389638738,\n",
391 | " 2: 0.896314672184623,\n",
392 | " 3: -0.7876958617794716,\n",
393 | " 4: -0.9099637547345425,\n",
394 | " 5: -0.9419581446623225},\n",
395 | " 2: {0: 0.49365474375598073,\n",
396 | " 1: 0.896314672184623,\n",
397 | " 3: -0.5833076828172804,\n",
398 | " 4: -0.8451542547285166,\n",
399 | " 5: -0.5144957554275266},\n",
400 | " 3: {0: -0.8291725540450335,\n",
401 | " 1: -0.7876958617794716,\n",
402 | " 2: -0.5833076828172804,\n",
403 | " 4: 0.4685212856658182,\n",
404 | " 5: 0.49665813370370504},\n",
405 | " 4: {0: -0.8682431421244593,\n",
406 | " 1: -0.9099637547345425,\n",
407 | " 2: -0.8451542547285166,\n",
408 | " 3: 0.4685212856658182,\n",
409 | " 5: 1.0},\n",
410 | " 5: {0: -0.987241120712647,\n",
411 | " 1: -0.9419581446623225,\n",
412 | " 2: -0.5144957554275266,\n",
413 | " 3: 0.49665813370370504,\n",
414 | " 4: 1.0}}\n",
415 | "Ii = \n",
416 | "{0: {1: 0.8418791389638738, 2: 0.49365474375598073, 3: -0.8291725540450335},\n",
417 | " 1: {0: 0.8418791389638738, 2: 0.896314672184623, 3: -0.7876958617794716},\n",
418 | " 2: {0: 0.49365474375598073, 1: 0.896314672184623, 5: -0.5144957554275266},\n",
419 | " 3: {2: -0.5833076828172804, 4: 0.4685212856658182, 5: 0.49665813370370504},\n",
420 | " 4: {2: -0.8451542547285166, 3: 0.4685212856658182, 5: 1.0},\n",
421 | " 5: {2: -0.5144957554275266, 3: 0.49665813370370504, 4: 1.0}}\n",
422 | "Ii = \n",
423 | "{0: {1: 0.8418791389638738, 2: 0.49365474375598073},\n",
424 | " 1: {0: 0.8418791389638738, 2: 0.896314672184623},\n",
425 | " 2: {0: 0.49365474375598073, 1: 0.896314672184623},\n",
426 | " 3: {4: 0.4685212856658182, 5: 0.49665813370370504},\n",
427 | " 4: {3: 0.4685212856658182, 5: 1.0},\n",
428 | " 5: {3: 0.49665813370370504, 4: 1.0}}\n",
429 | "Ii = \n",
430 | "{0: array([1, 2]),\n",
431 | " 1: array([2, 0]),\n",
432 | " 2: array([1, 0]),\n",
433 | " 3: array([5, 4]),\n",
434 | " 4: array([5, 3]),\n",
435 | " 5: array([4, 3])}\n"
436 | ]
437 | }
438 | ]
439 | },
440 | {
441 | "cell_type": "markdown",
442 | "source": [
443 | "## 嗜好予測"
444 | ],
445 | "metadata": {
446 | "id": "GKCeNrCXDgVg"
447 | }
448 | },
449 | {
450 | "cell_type": "markdown",
451 | "source": [
452 | "### 06 類似アイテム集合の中でユーザuが評価値を与えているアイテム集合\n",
453 | "### 07 予測評価値"
454 | ],
455 | "metadata": {
456 | "id": "Y_VHzZb8DyG3"
457 | }
458 | },
459 | {
460 | "cell_type": "code",
461 | "source": [
462 | "def predict(u, i):\n",
463 | " \"\"\"\n",
464 | " 予測関数:ユーザuのアイテムiに対する予測評価値を返す。\n",
465 | "\n",
466 | " Parameters\n",
467 | " ----------\n",
468 | " u : int\n",
469 | " ユーザuのID\n",
470 | " i : int\n",
471 | " アイテムiのID\n",
472 | " \n",
473 | " Returns\n",
474 | " -------\n",
475 | " float\n",
476 | " ユーザuのアイテムiに対する予測評価値\n",
477 | " \"\"\"\n",
478 | " # 06\n",
479 | " Iiu = np.intersect1d(Ii[i], Iu[u])\n",
480 | " print('I{}{} = {}'.format(i, u, Iiu))\n",
481 | "\n",
482 | " if Iiu.size <= 0: return ru_mean[u]\n",
483 | " # 07\n",
484 | " num = np.sum([(S[i,j] * R[u,j]) for j in Iiu])\n",
485 | " den = np.sum([np.abs(S[i,j]) for j in Iiu])\n",
486 | " rui_pred = num / den\n",
487 | " \n",
488 | " return rui_pred"
489 | ],
490 | "metadata": {
491 | "id": "uLe2sEnUD1Op"
492 | },
493 | "execution_count": 10,
494 | "outputs": []
495 | },
496 | {
497 | "cell_type": "code",
498 | "source": [
499 | "u = 0\n",
500 | "i = 0\n",
501 | "print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))\n",
502 | "u = 0\n",
503 | "i = 5\n",
504 | "print('r{}{} = {:.3f}'.format(u, i, predict(u, i)))"
505 | ],
506 | "metadata": {
507 | "colab": {
508 | "base_uri": "https://localhost:8080/"
509 | },
510 | "id": "fhwTmQhvD-am",
511 | "outputId": "3b38988b-8cee-4713-d46c-964bd13aae24"
512 | },
513 | "execution_count": 11,
514 | "outputs": [
515 | {
516 | "output_type": "stream",
517 | "name": "stdout",
518 | "text": [
519 | "I00 = [1 2]\n",
520 | "r00 = 3.630\n",
521 | "I50 = [3 4]\n",
522 | "r05 = 1.668\n"
523 | ]
524 | }
525 | ]
526 | }
527 | ]
528 | }
--------------------------------------------------------------------------------