├── 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 |
18 |
19 | {% if site.github.is_project_page %} 20 | View on GitHub 21 | {% endif %} 22 | 23 | 24 |

{{ page.title | default: site.title }}

25 |

{{ site.description | default: site.github.project_tagline }}

26 | 27 | {% if site.show_downloads %} 28 |
29 | Download this project as a .zip file 30 | Download this project as a tar.gz file 31 |
32 | {% endif %} 33 |
34 |
35 | 36 | 37 |
38 |
39 | {{ content }} 40 |
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 | } --------------------------------------------------------------------------------