論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
レコメンデーション(推薦システム)
Recommendation Systems
ユーザーが 「次に好きそうなもの」 を、過去の行動データや属性データから予測して提示するアルゴリズムの総称。
Amazon の「この商品を買った人はこんな商品も…」、 Netflix のおすすめ作品、 Spotify の「あなたへの曜日プレイリスト」――どれもこの仕組みが裏で動いています。
機械学習応用 協調フィルタリング 類似度ベース 情報検索

🔖 キーワード索引

recommendation」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「recommendation」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

recommendation統計分析SSDSE-B-2026前提条件適用範囲落とし穴関連手法Python 実装検証方法

これらのキーワードは「recommendation の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

好みのものを提案する仕組みです。

ユーザーが喜びそうな物を探します。

動画サイトのおすすめ機能に似ています。

おすすめを決める3つの方法を学びます。

💡 暗黙的フィードバック — 「★を押してくれないユーザー」をどう扱うか

現実のサービスでは、 ユーザーが ★1〜5 のような明示的な評価 をしてくれることは稀です。 代わりに 暗黙的なシグナル(クリック、 滞在時間、 購入、 視聴完走率、 スクロール深度、 再生スキップ等)を学習信号として使います。

明示的 vs 暗黙的の比較

項目明示的フィードバック暗黙的フィードバック
★1〜5、 👍👎、 レビュー クリック、 購入、 滞在時間、 再生時間
少ない(積極的なユーザーのみ) 圧倒的に多い(全行動が信号)
意味の明確さ はっきり「好き/嫌い」 「クリックしたが嫌いだった」可能性も
負例 低評価が「負例」として明示 「見なかった」のは「興味なし」と「知らなかった」が混在
代表的手法 SVD、 BiasMF、 SVD++ ALS(Alternating Least Squares)、 BPR(Bayesian Personalized Ranking)

ALS(Alternating Least Squares)の信頼度重み付け

暗黙的フィードバック用に Koren ら(2008)が提案した古典的手法:

【ALS の目的関数】
$$\min_{P, Q} \sum_{u, i} c_{u,i}\bigl(p_{u,i} - \mathbf{p}_u^{\top}\mathbf{q}_i\bigr)^2 + \lambda\bigl(\|P\|_F^2 + \|Q\|_F^2\bigr)$$
$p_{u,i}$=観測ありなら 1、 なしなら 0(好み)/ $c_{u,i} = 1 + \alpha r_{u,i}$=信頼度(観測回数が多いほど大)/ 全 (u, i) を回す(観測なしも 0 として参加)。

P と Q を交互に固定して最小二乗で解くため SGD より並列化しやすく、 Spark MLlib などで大規模実装が確立されています。

BPR(Bayesian Personalized Ranking)

Rendle ら(2009)が提案した ペアワイズ学習 アプローチ。 「観測済アイテム i は未観測アイテム j より好まれるはず」をベイズ的最尤推定で学習:

$$\max_{\Theta} \sum_{(u, i, j) \in D_S} \ln \sigma\bigl(\hat{r}_{u,i} - \hat{r}_{u,j}\bigr) - \lambda \|\Theta\|^2$$
$D_S$=(u, i, j) のトリプル集合(i: ユーザー u 観測済、 j: 未観測)。 $\sigma$=シグモイド関数。 ランキング AUC を直接最適化することに相当。

🛡 公平性・プライバシー・倫理

推薦システムは 「誰に何を見せるか」 を決める強力な装置。 副作用として以下の倫理的問題が議論されています:

⚖️ 公平性(Fairness)
推薦が特定のグループ(性別、 人種、 年齢)に不利な結果を出すリスク。 例:求人推薦で女性に低賃金職が偏ったり、 ローン推薦で人種バイアスが出たり。 対策:群別 Coverage 監視Demographic Parity 制約Counterfactual Fairness の導入。
🔒 プライバシー(Privacy)
行動履歴を中央サーバに集めること自体が漏洩リスク。 対策:連合学習(Federated Learning)差分プライバシー(Differential Privacy)オンデバイス推薦。 Apple や Google が積極的に実装。
📰 フィルターバブル・エコーチェンバー
過去の嗜好に合うものばかり推薦されることで政治的二極化や情報格差を助長。 SNS のニュース推薦は社会問題化済。 対策:意図的多様化、 反対意見の混在、 ユーザーへの説明可能性。
🎰 中毒性とエンゲージメント最大化
「滞在時間」を最大化すると、 ユーザーの幸福を犠牲にする可能性。 TikTok の「For You」アルゴリズムや YouTube の「次の動画」が議論の的。 短期 KPI(CTR、 滞在時間)と長期 KPI(満足度、 継続率、 ウェルビーイング)のトレードオフ管理が必要。
🪞 説明可能性(Explainability)
「なぜこれが推薦されたか」をユーザーに説明できない深層モデルが主流。 透明性確保のため、 後付けで「説明文」を生成する技術(LIME、 SHAP、 Attention 可視化)も研究中。 EU の GDPR は「自動化された決定の説明を受ける権利」を明文化。

🧪 SSDSE 47 都道府県の「完全」推薦パイプライン

これまでの議論を踏まえ、 SSDSE-B-2026 全 47 都道府県を使った End-to-End 推薦パイプライン を構築します。 「都道府県=ユーザー」「指標=アイテム」のアナロジーを最後まで突き詰めます。

パイプライン全体図

  1. データ取得pd.read_csv('data/raw/SSDSE-B-2026.csv')
  2. 前処理 — 欠損補完、 最新年抽出、 数値列のみ
  3. 標準化 — Z-score で指標スケール統一
  4. 類似度計算 — 県間コサイン類似度 47 × 47
  5. 推薦生成 — 各県の最近傍 Top 5
  6. 評価 — 「地理的隣県=正解」と仮定して Precision@5
  7. 多様化 — MMR(Maximal Marginal Relevance)でリランキング

完全実装

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #7。SSDSE-B-2026 を読み込みます。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 期待される df.head()(簡略表示 / 都道府県ごとに年度降順で並ぶ): # 年度 地域コード 都道府県 総人口 総人口(男) ... # 0 2023 R01000 北海道 5092000 2405000 ... # 1 2022 R01000 北海道 5140000 2427000 ... # 2 2021 R01000 北海道 5183000 2446000 ... # 3 2020 R01000 北海道 5224614 2465088 ... # 4 2019 R01000 北海道 5259000 2480000 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.pairwise import cosine_similarity

# === 1. データ取得 ===
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)

# === 2. 前処理:最新年抽出 + 数値列のみ ===
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
X_raw = df.select_dtypes(include=[np.number]).dropna(axis=1).drop(columns=['年度'])  # 定数列(年度)を除外 → 109指標

# === 3. 標準化 ===
scaler = StandardScaler()
X = scaler.fit_transform(X_raw)

# === 4. 全県間コサイン類似度(47×47)===
sim = cosine_similarity(X)
sim_df = pd.DataFrame(sim, index=X_raw.index, columns=X_raw.index)

# === 5. 各県への推薦 Top 5 ===
def recommend(pref_name, k=5):
    s = sim_df[pref_name].drop(pref_name)
    return s.sort_values(ascending=False).head(k)

print('東京都への推薦:')
print(recommend('東京都'))
print()
print('秋田県への推薦:')
print(recommend('秋田県'))
print()
print('沖縄県への推薦:')
print(recommend('沖縄県'))

# === 6. 評価:地理的隣県を正解と仮定(テスト用)===
# 簡易的に「同じ地方ブロックの県」を正解と定義
chiho = {
    '東京都': ['神奈川県','千葉県','埼玉県','茨城県','栃木県','群馬県','山梨県'],
    '大阪府': ['京都府','兵庫県','奈良県','和歌山県','滋賀県'],
    '沖縄県': ['鹿児島県','宮崎県','熊本県','長崎県','大分県','佐賀県','福岡県'],
}

def precision_at_k(pref, k=5):
    rec = recommend(pref, k).index.tolist()
    rel = chiho.get(pref, [])
    if not rel: return None
    return len(set(rec) & set(rel)) / k

for p in chiho:
    print(f'{p}: Precision@5 = {precision_at_k(p):.2f}')

# === 7. MMR でリランキング(多様性を入れる)===
def mmr_rerank(target, candidates, lambda_=0.7, k=5):
    selected = []
    remaining = list(candidates)
    while len(selected) < k and remaining:
        best, best_score = None, -np.inf
        for c in remaining:
            relevance = sim_df.loc[target, c]
            diversity = 0 if not selected else max(sim_df.loc[c, s] for s in selected)
            score = lambda_ * relevance - (1 - lambda_) * diversity
            if score > best_score:
                best_score, best = score, c
        selected.append(best)
        remaining.remove(best)
    return selected

# 候補を Top 15 から、 多様性 30% で再ランキングして Top 5 を取り直し
candidates = recommend('東京都', k=15).index.tolist()
diversified = mmr_rerank('東京都', candidates, lambda_=0.7, k=5)
print('東京都 多様化リランキング Top5:', diversified)
📤 実行例(実行時の標準出力) 東京都への推薦: 都道府県 大阪府 0.905130 愛知県 0.884018 神奈川県 0.875596 福岡県 0.853852 千葉県 0.834570 Name: 東京都, dtype: float64 秋田県への推薦: 都道府県 青森県 0.888726 福井県 0.859979 島根県 0.778511 岩手県 0.773482 鳥取県 0.768829 Name: 秋田県, dtype: float64 沖縄県への推薦: 都道府県 鹿児島県 0.671848 長崎県 0.610750 愛媛県 0.527521 宮崎県 0.525080 和歌山県 0.486029 Name: 沖縄県, dtype: float64 東京都: Precision@5 = 0.40 大阪府: Precision@5 = 0.20 沖縄県: Precision@5 = 0.60 東京都 多様化リランキング Top5: ['大阪府', '神奈川県', '愛知県', '福岡県', '兵庫県']
💬 読み方:cosine は「方向の類似」を測る。大きさが違っても傾向が似ていれば高くなる。

典型的な出力(解釈つき)

ターゲット県Top 5 推薦結果(解釈)
東京都 大阪 → 愛知 → 神奈川 → 福岡 → 千葉(人口規模が大きく、出生・婚姻・転入などの件数が大きい大都市圏プロファイル)
秋田県 青森 → 福井 → 島根 → 岩手 → 鳥取(人口小規模・少子高齢・積雪寒冷の地方県プロファイル)
沖縄県 鹿児島 → 長崎 → 愛媛 → 宮崎 → 和歌山(温暖・南日本+地方中核という人口・気候プロファイル)
愛知県 神奈川 → 千葉 → 東京 → 大阪 → 埼玉(大都市圏に属する人口集中県)

面白い発見:単純なコサイン類似度だけでも、 地理的隣県・人口規模・人口構造 がうまく反映されています。 秋田の最上位は隣県の青森ですが、 続く「秋田 → 福井・島根」のように、 地理は離れていても 「人口小規模・少子高齢化」という共通プロファイル が浮かび上がる。 これがレコメンデーションの面白さです。

MMR(Maximal Marginal Relevance)で多様性を入れる

$$\mathrm{MMR}(c) = \lambda \cdot \mathrm{sim}(c, \text{target}) - (1-\lambda) \cdot \max_{s \in S} \mathrm{sim}(c, s)$$
$S$=すでに選択済の推薦集合、 $\lambda$=関連性 vs 多様性のトレードオフ(0.7 程度が標準)。 関連性が高くても、 既選択と似ているとペナルティ。

$\lambda = 1.0$ では純粋にコサイン類似度順、 $\lambda = 0.0$ では最も既選択と異なるものを選ぶ。 中間で関連性と多様性のバランスを取ります。

🎯 ハイブリッド推薦の設計パターン

純粋な協調フィルタリングやコンテンツベースだけでは限界があるため、 実務システムではほとんどが ハイブリッド設計を採用しています。 主要な組み合わせパターン:

パターン仕組み長所
① 重み付き混合(Weighted) $\text{score} = \alpha \cdot \text{CF} + (1-\alpha) \cdot \text{CB}$ シンプル、 解釈可能
② スイッチング(Switching) 状況により CF or CB を選択 コールドスタート時は CB、 充実後は CF
③ 混合(Mixed) 複数モデルの結果を並べて表示 多様性確保、 比較しやすい
④ 特徴量結合(Feature Combination) CB の属性を CF の特徴量に追加 属性情報も学習に活用
⑤ カスケード(Cascade) 段階的に絞り込み(candidate gen → ranking) 大規模向け、 計算効率良い
⑥ メタレベル(Meta-level) 1 モデルの出力を別モデルの入力に 複雑な相互作用を学習
⑦ アンサンブル(Ensemble) 複数モデルの結果を加重平均 Netflix Prize 優勝の鍵

Netflix Prize の優勝アンサンブル

2009 年に賞金 100 万 USD を獲得した「BellKor's Pragmatic Chaos」チームは、 100+ 種類の異なるモデルをアンサンブルしていました。 内訳の例:

この勝利は、 単一モデルの最適化より 「多様なモデルの組み合わせ」がより強力という教訓を残しました。

現代の業界標準:2 段階システム

  1. 候補生成(Candidate Generation, Recall):数百万アイテムから数百〜数千に絞る。 速度優先。
  2. リランキング(Ranking):絞られた候補を精緻にスコアリング。 精度優先。

候補生成では Two-Tower モデルや近似最近傍検索(ANN; FAISS, ScaNN)、 リランキングでは GBDT(LightGBM, XGBoost)や DNN を多用。 YouTube、 TikTok、 Pinterest など主要プラットフォームの推薦システムはこのパターン。

🛠 推薦システム構築の実用 Tips

① 前処理での落とし穴

② オフライン評価のベストプラクティス

③ オンライン A/B テストの設計

④ 本番運用での監視

⑤ よくある失敗事例

失敗原因対策
新ユーザーに何も推薦できない コールドスタート未対策 人気アイテム、 アンケート、 コンテンツベース
「面白くない推薦ばかり」 過度のパーソナライズ 多様性、 セレンディピティを KPI に
マイナー商品が永遠に売れない 人気バイアスのフィードバックループ 人気度逆数重み、 探索率の確保
オフラインで好成績、 本番で失敗 評価指標とビジネス KPI の乖離 A/B テストを最初から
推薦サーバが落ちる レイテンシ・スケーラビリティ未考慮 キャッシュ、 ANN、 マイクロサービス化

🧪 公平性と倫理 — 推薦システムが社会に与える影響

推薦システムは 「数億人の毎日の選択」を方向付ける強力な装置です。 技術的問題だけでなく、 社会的・倫理的影響も計算に入れた設計が求められています。

① 推薦のバイアスと差別

2018 年、 Amazon の社内採用 AI で「女性応募者を不利に評価する」バイアスが発覚し、 同社は AI 採用を中止しました。 これは過去の採用データ(男性偏重)を学習したことが原因。 推薦システムでも同じ問題が頻発:

② 公平性指標の定式化

指標定義意味
Demographic Parity $P(\hat{Y}=1 | A=0) = P(\hat{Y}=1 | A=1)$ 属性 A によらず推薦率が同じ
Equal Opportunity $P(\hat{Y}=1 | Y=1, A=0) = P(\hat{Y}=1 | Y=1, A=1)$ 真の好みが同じなら推薦率も同じ
Calibration 予測スコアが属性によらず実確率と一致 スコアの解釈が公平
Counterfactual Fairness 「属性が違ったら推薦も違うか?」を反実仮想で評価 因果的に公平

③ プライバシー保護技術

推薦の精度とユーザープライバシーは相反する側面があります:

④ 説明可能な推薦(Explainable Recommendation)

「なぜこれが推薦されたか」を説明する技術。 EU GDPR では「自動化された決定の説明を受ける権利」が明文化されました。

📍 あなたが今見ているもの

🍰 まずはやさしく

ネット上の案内役のようなものです。

欲しい物を早く見つけるために使います。

通販サイトの「おすすめ」が例です。

この技術がどう役立つかを読みます。

EC サイトで買い物中、 こんなブロックを見たことがあるはずです:

あなたへのおすすめ
「この商品を見た人はこちらも見ています」
「あなたと似た購買履歴の方が高評価」

これらは レコメンデーション(推薦システム) の出力です。 ユーザーごとに膨大なアイテム(数百万〜数億)の中から、「この人が好みそうな数件」 を上位に並べることで、 ユーザー体験と売上を同時に押し上げます。 GAFAM、 Netflix、 Spotify、 TikTok、 メルカリ……現代のあらゆるサービスの中核技術です。

🎨 直感で掴む — 「あなたに似た人」を見つけて代用する

🍰 まずはやさしく

似た好みの人を探す方法です。

自分に合う物を予想するために使います。

友達の好きな映画を参考にする感覚です。

似ている人をどう見つけるかを読みます。

レコメンデーションの仕組みを、 一言で言うと:

👤 ターゲットユーザーと 嗜好が似た別ユーザー を集めてきて、
🎁 その人たちが 高評価したがターゲットはまだ見ていないアイテム を推薦する。

たとえば「あなた」「友達 A」「友達 B」の 3 人が、 過去にこんな映画評価をしていたとします:

ユーザー千と千尋ジョーズラブ・アクチュアリーマッドマックス
あなた★5★2★4
友達 A★5★1★5★1
友達 B★1★5★1★5

3 作品ぶんの評価から、 「あなた」は友達 A と嗜好が近く、 友達 B とは正反対 だと分かります。 友達 A は「マッドマックス」に ★1 をつけているので、 「あなた」にも「マッドマックスは合わない」と推薦しない のが妥当な判断。

「協調フィルタリング」とは、 まさにこの 「似た嗜好の人が高評価したものをあなたにも推薦する」 作業を、 数百万ユーザー × 数百万アイテムでスケールさせる手法です。

3 大流派の使い分けマップ

方式類似度の元データ強み弱み
協調フィルタリング
(Collaborative Filtering)
ユーザーの行動履歴(評価、 購入、 クリック) アイテムの中身を知らなくてもよい/意外な発見がある 新規ユーザー・新規アイテムに弱い(コールドスタート)
コンテンツベース
(Content-Based)
アイテムの属性(ジャンル、 タグ、 テキスト埋め込み) 新規アイテムでもすぐ推薦可能/説明が容易 過去の嗜好の 狭い範囲 にしか推薦できない(飽きやすい)
ハイブリッド
(Hybrid)
行動 + 属性 + 文脈 両方の弱みを補える/実用システムの主流 実装が複雑/チューニングコスト大

📐 数式 — ユーザーベース協調フィルタリングの予測式

🍰 まずはやさしく

好みを計算する式のことです。

おすすめの点数を予測するために使います。

テストの点数のように数値で考えます。

予測するための計算式について読みます。

ユーザー $u$ がまだ評価していないアイテム $i$ について、 評価値 $\hat{r}_{u,i}$ を予測します:

【ユーザーベース CF の予測式】
$$\hat{r}_{u,i} = \bar{r}_u + \frac{\displaystyle\sum_{v \in N_k(u)} \mathrm{sim}(u, v)\,\bigl(r_{v,i} - \bar{r}_v\bigr)}{\displaystyle\sum_{v \in N_k(u)} |\mathrm{sim}(u, v)|}$$
$N_k(u)$=ユーザー $u$ と類似度が高い上位 k 人の近傍集合(neighborhood)

類似度 $\mathrm{sim}(u, v)$ には コサイン類似度 または Pearson 相関 が頻用されます。 コサイン類似度の定義は:

【コサイン類似度】
$$\mathrm{cos}(\mathbf{u}, \mathbf{v}) = \frac{\mathbf{u} \cdot \mathbf{v}}{\|\mathbf{u}\| \, \|\mathbf{v}\|} = \frac{\sum_i u_i v_i}{\sqrt{\sum_i u_i^2}\,\sqrt{\sum_i v_i^2}}$$
2 つの評価ベクトルが「同じ方向を向いているか」を −1 〜 +1 で測る。 +1 で完全に同方向、 0 で直交、 −1 で正反対。

もう 1 つの主流:行列分解(Matrix Factorization)

巨大なユーザー × アイテム評価行列 $R \in \mathbb{R}^{m \times n}$ を、 2 つの低次元行列に分解します:

【行列分解の枠組み】
$$R \approx P \, Q^{\top}, \qquad \hat{r}_{u,i} = \mathbf{p}_u^{\top} \mathbf{q}_i$$
$P \in \mathbb{R}^{m \times k}$=ユーザー潜在因子行列、 $Q \in \mathbb{R}^{n \times k}$=アイテム潜在因子行列、 $k$=潜在次元数(数十〜数百)。

Netflix Prize(2006-2009、 賞金 100 万 USD)で SVD ベースの行列分解が圧勝して以来、 業界標準の 1 つになりました。

🔬 数式を「言葉」で読み解く

ユーザーベース CF の予測式 $\hat{r}_{u,i} = \bar{r}_u + \sum_v \mathrm{sim}(u,v)(r_{v,i} - \bar{r}_v) / \sum_v |\mathrm{sim}(u,v)|$ を分解します。

$\bar{r}_u$
ユーザー $u$ の 普段の評価の平均。 厳しい人なら ★2.5、 甘い人なら ★4.0 など、 個人差を吸収するためのオフセット。
$N_k(u)$
$u$ と 類似度の高い上位 k 人(k-Nearest Neighbors)。 全ユーザーを使わず、 近所だけ参照することで計算量・ノイズを削減。
$\mathrm{sim}(u, v)$
$u$ と $v$ の 嗜好の近さ。 Pearson 相関なら -1 〜 +1、 コサインなら 0 〜 1(正の評価値の場合)。 重みとして使われる。
$r_{v,i} - \bar{r}_v$
$v$ の 「いつもより高い/低い」。 $v$ が甘い評価者の ★5 と、 厳しい評価者の ★5 では意味が違うので、 平均からの差で比較する。
分子の和
近傍ユーザーたちの「平均からのズレ」を 類似度で重み付けして足す。 似ている人ほど発言力が大きい。
分母 $\sum |\mathrm{sim}(u, v)|$
規格化。 これがないと近傍が多いほど絶対値が膨らんでしまう。 重み付き平均にするための分母。

要するに:「自分の平均評価点」を起点に、 「似た人が普段より高評価/低評価したぶんを類似度で重み付け平均して足す」――これが協調フィルタリングの核心です。

🔬 行列分解(Matrix Factorization)を数学的に

Netflix Prize 優勝アルゴリズムの中核技術。 ユーザー × アイテム評価行列 $R$ を 2 つの低次元行列に分解します:

【行列分解の最適化問題】
$$\min_{P, Q} \sum_{(u,i) \in \Omega} \bigl(r_{u,i} - \mathbf{p}_u^{\top}\mathbf{q}_i\bigr)^2 + \lambda\bigl(\|P\|_F^2 + \|Q\|_F^2\bigr)$$
$\Omega$=観測された (u, i) のペア、 $\lambda$=L2 正則化、 $\|\cdot\|_F$=フロベニウスノルム。 観測済セルだけ誤差を取り、 過学習を正則化で抑制。

確率的勾配降下(SGD)での解法

各観測 $(u, i, r_{u,i})$ ごとに:

  1. 予測誤差 $e_{u,i} = r_{u,i} - \mathbf{p}_u^{\top}\mathbf{q}_i$ を計算
  2. $\mathbf{p}_u \leftarrow \mathbf{p}_u + \eta\bigl(e_{u,i}\mathbf{q}_i - \lambda \mathbf{p}_u\bigr)$
  3. $\mathbf{q}_i \leftarrow \mathbf{q}_i + \eta\bigl(e_{u,i}\mathbf{p}_u - \lambda \mathbf{q}_i\bigr)$

$\eta$=学習率(0.005〜0.05 程度)。 数十エポックで収束。 全観測を 1 度なめるごとに 1 エポック。

バイアス項を入れた発展形(biasMF)

$$\hat{r}_{u,i} = \mu + b_u + b_i + \mathbf{p}_u^{\top}\mathbf{q}_i$$
$\mu$=全体平均、 $b_u$=ユーザーの厳しさ/甘さ、 $b_i$=アイテムの人気度。 これらを潜在因子と別に学習することで予測精度が向上。

実装:NumPy で 30 行 SGD

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #6。数値結果を出力します。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape = (564, 112) ※ 47都道府県 × 12年(2012-2023, 欠損ゼロ) # df[['年度','都道府県','総人口']].head(): # 年度 都道府県 総人口 # 0 2023 北海道 5092000 # 1 2022 北海道 5140000 # 2 2021 北海道 5183000 # 3 2020 北海道 5224614 # 4 2019 北海道 5259000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# ── この抜粋で使う観測リストを用意します(架空)──
# 評価ログはこの教材に同梱していないので、低ランク構造+ノイズの
# <架空の>評価データをここで作る。
import numpy as np; np.random.seed(42)  # 合成観測の再現性

_nu, _ni, _k = 47, 100, 8
_P0 = np.random.normal(0, 1, (_nu, _k))
_Q0 = np.random.normal(0, 1, (_ni, _k))
observations = []
for _u in range(_nu):
    for _i in np.random.choice(_ni, 20, replace=False):
        _r = 3 + 0.3 * (_P0[_u] @ _Q0[_i]) / _k + np.random.normal(0, 0.2)
        observations.append((_u, int(_i), float(np.clip(_r, 1, 5))))


# 観測リスト: [(user_id, item_id, rating), ...]  ← 合成データ(低ランク構造+ノイズ)を上流で生成
n_users, n_items, k = 47, 100, 8
eta, lam, n_epochs = 0.01, 0.05, 50

P = np.random.normal(0, 0.1, (n_users, k))
Q = np.random.normal(0, 0.1, (n_items, k))
bu = np.zeros(n_users)
bi = np.zeros(n_items)
mu = np.mean([r for _,_,r in observations])

for epoch in range(n_epochs):
    np.random.shuffle(observations)
    sse = 0
    for u, i, r in observations:
        pred = mu + bu[u] + bi[i] + P[u] @ Q[i]
        e = r - pred
        sse += e**2
        bu[u] += eta * (e - lam * bu[u])
        bi[i] += eta * (e - lam * bi[i])
        P[u] += eta * (e * Q[i] - lam * P[u])
        Q[i] += eta * (e * P[u] - lam * Q[i])
    rmse = np.sqrt(sse / len(observations))
    if epoch % 10 == 0:
        print(f'Epoch {epoch}: RMSE={rmse:.4f}')

# 推薦: ユーザー u の未観測アイテムスコア
def recommend(u, observed_items, top_n=10):
    scores = mu + bu[u] + bi + P[u] @ Q.T
    scores[list(observed_items)] = -np.inf
    return np.argsort(-scores)[:top_n]
Epoch 0: RMSE=0.2216 Epoch 10: RMSE=0.2056 Epoch 20: RMSE=0.2014 Epoch 30: RMSE=0.1992 Epoch 40: RMSE=0.1977
💬 読み方:エポックを追うごとに訓練 RMSE が 0.2216 → 0.1977 と単調に減少 → biasMF の SGD が収束している。 減り幅がわずか 0.024 しかないのは学習の失敗ではなく、 この合成データの設計上そうなるためである。 評価値は 3 + 0.3·(P₀ᵤ·Q₀ᵢ)/8 + N(0, 0.2) で作っており、 学習可能な信号成分の標準偏差は 0.104、 一方ノイズの標準偏差は 0.2。 つまり RMSE は原理的に 0.2 程度までしか下がらない ── ここが「ベイズ誤差」に相当する下限である。 実際 Epoch 40 の 0.1977 はすでにその下限をわずかに下回っており、 これは訓練 RMSE なのでノイズを覚え始めている(過学習の入り口)ことを意味する。 RMSE の絶対値だけを見て「良い/悪い」を判断してはいけない。 データに含まれるノイズ量を知らないと、 その RMSE が下限に達しているのか、 まだ改善余地があるのかを区別できない。 実務では必ず検証データの RMSE を併記し、 訓練 RMSE との乖離で過学習を監視する。 合成観測(seed=42)での再現値。

🧮 SSDSE 実データで「都道府県レコメンド」

🧮 SSDSE 実データで「都道府県レコメンド」

本来のレコメンデーションは「ユーザー × アイテム」の関係ですが、 教育的アナロジーとして 「都道府県 = ユーザー」「社会指標 = アイテム」 と見立て、 「東京と嗜好(指標プロファイル)が似た県」を推薦してみます。 SSDSE-B-2026 から 5 指標 × 5 県を抜粋:

人口(万人)出生数(件)婚姻件数(件)年平均気温(℃)年降水量(mm)
東京1409863487177417.61396.5
神奈川923539913817618.01377.0
大阪876552923851318.01343.5
秋田913611230213.72208.5
沖縄14712549631623.82291.5

各県を 5 次元ベクトルとして扱い、 標準化してからコサイン類似度 を計算します(生の値だと「人口」の桁が他指標を支配するため)。

STEP 1 各指標を Z 標準化
指標ごとに $z = (x - \mu)/\sigma$ で変換。 例えば人口の平均は 689.2 万人、 標準偏差は 501.7 万人(5 県・母標準偏差)なので、 東京の人口 z = (1409 - 689.2)/501.7 ≈ +1.43、 秋田の人口 z = (91 - 689.2)/501.7 ≈ −1.19。
STEP 2 東京と各県のコサイン類似度
$\mathrm{cos}(\text{東京}, \text{神奈川}) \approx +0.81$ (正の高い類似度 → プロファイル近い)
$\mathrm{cos}(\text{東京}, \text{大阪}) \approx +0.78$
$\mathrm{cos}(\text{東京}, \text{沖縄}) \approx -0.78$ (気温・降水が対照的で逆向き)
$\mathrm{cos}(\text{東京}, \text{秋田}) \approx -0.80$ (ほぼ正反対)
STEP 3 ランキングを出す
正の類似を示すのは ① 神奈川、 ② 大阪 の 2 県。 沖縄・秋田はいずれも負(逆向き)で、 特に秋田が最遠。 人口規模が大きく出生・婚姻件数も多い都市型プロファイルが効いた結果です(沖縄は温暖・多雨という気候軸で対照的)。

解釈:もし「東京の人」を 47 都道府県のどれかに 1 つ「住み替え推薦」したいなら、 ライフスタイルが最も近い 神奈川 が筆頭候補。 これが コンテンツベース 推薦(属性ベクトルの類似度)の最小例です。

合成 2 ユーザーの評価ベクトルからコサイン類似度を計算する。

Step 1: 評価

ユーザー A: [5, 4, 0, 3, 0] ユーザー B: [4, 5, 0, 4, 0]

Step 2: コサイン類似度

A·B = 5·4+4·5+0+3·4+0 = 20+20+0+12+0 = 52 |A| = √(25+16+0+9+0) = √50 ≈ 7.071 |B| = √(16+25+0+16+0) = √57 ≈ 7.550 cos = 52 / (7.071·7.550) ≈ 0.974

🐍 Python で再現

1
2
3
4
5
import numpy as np
A = np.array([5, 4, 0, 3, 0])
B = np.array([4, 5, 0, 4, 0])
cos = (A @ B) / (np.linalg.norm(A) * np.linalg.norm(B))
print(f"cos: {cos:.3f}")

📤 実行結果

cos: 0.974

💬 手計算 (Step 2) 0.974 と Python 出力が完全一致。

🐍 Python 実装 — SSDSE-B で「東京に似た県」を推薦

① ライブラリと前処理

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #1。最初のスニペットです。SSDSE-B-2026 を読み込みます。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 期待される df.head()(簡略表示 / 都道府県ごとに年度降順で並ぶ): # 年度 地域コード 都道府県 総人口 総人口(男) ... # 0 2023 R01000 北海道 5092000 2405000 ... # 1 2022 R01000 北海道 5140000 2427000 ... # 2 2021 R01000 北海道 5183000 2446000 ... # 3 2020 R01000 北海道 5224614 2465088 ... # 4 2019 R01000 北海道 5259000 2480000 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.pairwise import cosine_similarity

# データ読込
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)

# 都道府県名を index に、 最新年の数値列のみ抽出
df_latest = df[df['年度'] == df['年度'].max()].copy()
df_latest = df_latest.set_index('都道府県')
features = df_latest.select_dtypes(include=[np.number])
features = features.dropna(axis=1).drop(columns=['年度'])  # 欠損列+定数列(年度)を落とす → 109指標

print(features.shape)  # 例: (47, 100+)
print(features.head())
📤 実行例(実行時の標準出力) (47, 109) 総人口 総人口(男) 総人口(女) 日本人人口 ... 都道府県 ... 北海道 5092000 2405000 2688000 5041000 ... 青森県 1184000 559000 626000 1177000 ... 岩手県 1163000 562000 602000 1154000 ... 宮城県 2264000 1105000 1160000 2239000 ... 秋田県 914000 432000 482000 909000 ... [5 rows x 109 columns] → 47都道府県 × 109 指標(2023年・欠損ゼロ)の特徴量行列
💬 読み方:cosine は「方向の類似」を測る。大きさが違っても傾向が似ていれば高くなる。

② コサイン類似度で「全県 × 全県」の類似度行列を作る

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #2。数値結果を出力します。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape = (564, 112) ※ 47都道府県 × 12年(2012-2023, 欠損ゼロ) # df[['年度','都道府県','総人口']].head(): # 年度 都道府県 総人口 # 0 2023 北海道 5092000 # 1 2022 北海道 5140000 # 2 2021 北海道 5183000 # 3 2020 北海道 5224614 # 4 2019 北海道 5259000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Z 標準化(指標の単位を揃える)
scaler = StandardScaler()
X = scaler.fit_transform(features)

# コサイン類似度行列(47×47)
sim_matrix = cosine_similarity(X)
sim_df = pd.DataFrame(sim_matrix, index=features.index, columns=features.index)

# 参考:3 ペアの実測コサイン(東京 vs 神奈川/沖縄/鳥取)
for p in ['神奈川県', '沖縄県', '鳥取県']:
    print(f'東京-{p.replace("県", "")} cosine: {sim_df.loc["東京都", p]:.3f}')
print(sim_df.loc['東京都'].sort_values(ascending=False).head(10))
# 自分自身を除いた上位5県を表示
print(sim_df.loc['東京都'].drop('東京都').sort_values(ascending=False).head(5))
📤 実行例(実行時の標準出力) 東京-神奈川 cosine: 0.876 東京-沖縄 cosine: -0.274 東京-鳥取 cosine: -0.811 都道府県 東京都 1.000000 大阪府 0.905130 愛知県 0.884018 神奈川県 0.875596 福岡県 0.853852 千葉県 0.834570 兵庫県 0.804261 埼玉県 0.776995 北海道 0.612496 静岡県 0.409760 Name: 東京都, dtype: float64 都道府県 大阪府 0.905130 愛知県 0.884018 神奈川県 0.875596 福岡県 0.853852 千葉県 0.834570 Name: 東京都, dtype: float64
💬 読み方:cosine は「方向の類似」を測る。大きさが違っても傾向が似ていれば高くなる。

③ ユーザーベース協調フィルタリング(仮想評価行列で)

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #3。数値結果を出力します。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape = (564, 112) ※ 47都道府県 × 12年(2012-2023, 欠損ゼロ) # df[['年度','都道府県','総人口']].head(): # 年度 都道府県 総人口 # 0 2023 北海道 5092000 # 1 2022 北海道 5140000 # 2 2021 北海道 5183000 # 3 2020 北海道 5224614 # 4 2019 北海道 5259000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 仮に 5 指標を「アイテム」と見立てて評価値行列を構築
items = ['出生数', '死亡数', '婚姻件数', '離婚件数', '転入者数(日本人移動者)']
R = features[items].copy()  # 47×5 の「評価行列」

# 行ごと平均を引いて平均中心化
R_centered = R.sub(R.mean(axis=1), axis=0)

# 県間類似度(ユーザー類似度)
user_sim = cosine_similarity(R_centered.fillna(0))
user_sim = pd.DataFrame(user_sim, index=R.index, columns=R.index)

# 東京の「婚姻件数」を伏せて、 上位 5 近傍から予測してみる
target_user = '東京都'
target_item = '婚姻件数'
neighbors = user_sim[target_user].drop(target_user).sort_values(ascending=False).head(5)

num = sum(neighbors[v] * (R.loc[v, target_item] - R.loc[v].mean()) for v in neighbors.index)
den = neighbors.abs().sum()
pred = R.loc[target_user].mean() + num / den

print(f'予測値: {pred:.1f}, 実測値: {R.loc[target_user, target_item]:.1f}')
📤 実行例(実行時の標準出力) 予測値: 117350.3, 実測値: 71774.0
💬 読み方:近傍 5 県から東京の「婚姻件数」を再構成した値。5 指標を生の件数のまま「評価値」に見立てた教育用トイなので、指標間のスケール差の影響で誤差は大きめ(実務では標準化やバイアス項が必須)。

④ scikit-learn 風 API:implicit / surprise ライブラリ

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #4。処理の続きです。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape = (564, 112) ※ 47都道府県 × 12年(2012-2023, 欠損ゼロ) # df[['年度','都道府県','総人口']].head(): # 年度 都道府県 総人口 # 0 2023 北海道 5092000 # 1 2022 北海道 5140000 # 2 2021 北海道 5183000 # 3 2020 北海道 5224614 # 4 2019 北海道 5259000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# pip install scikit-surprise
from surprise import Dataset, Reader, KNNBasic, SVD
from surprise.model_selection import cross_validate, KFold

# 仮想「県 × 指標 × 値」のロング形式を作る
long_df = R.stack().reset_index()
long_df.columns = ['user', 'item', 'rating']

reader = Reader(rating_scale=(long_df['rating'].min(), long_df['rating'].max()))
data = Dataset.load_from_df(long_df[['user','item','rating']], reader)

# ユーザーベース kNN
algo = KNNBasic(sim_options={'name':'cosine','user_based':True})
cross_validate(algo, data, measures=['RMSE','MAE'], cv=KFold(3, random_state=42, shuffle=True), verbose=True)

# 行列分解(SVD)
svd = SVD(n_factors=10, n_epochs=20, random_state=0)
cross_validate(svd, data, measures=['RMSE','MAE'], cv=KFold(3, random_state=42, shuffle=True), verbose=True)
📤 実行例(実行時の標準出力) Evaluating RMSE, MAE of algorithm KNNBasic on 3 split(s). Fold 1 Fold 2 Fold 3 Mean Std RMSE (testset) 31774.2596 45776.9935 27356.7392 34969.3308 7852.0826 MAE (testset) 18559.5043 17399.6670 17401.8513 17787.0075 546.2384 Evaluating RMSE, MAE of algorithm SVD on 3 split(s). Fold 1 Fold 2 Fold 3 Mean Std RMSE (testset) 385257.9011 387573.5396 386952.3926 386594.6111 978.6219 MAE (testset) 383861.0253 384507.1795 385632.5641 384666.9230 731.9954
💬 読み方:件数を生値のまま評価値に見立てた合成デモなので RMSE の絶対値は大きい(スケール差が支配的)。ここでの要点は cv=KFold(random_state=42) による再現性と、KNN と SVD の相対比較の枠組み。

⑤ ランキング指標(Precision@K, Recall@K, nDCG)

🎯 このコードでやること:推薦システム — 都道府県観光属性に基づく類似度推薦に関連するステップ #5。数値結果を出力します。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape = (564, 112) ※ 47都道府県 × 12年(2012-2023, 欠損ゼロ) # df[['年度','都道府県','総人口']].head(): # 年度 都道府県 総人口 # 0 2023 北海道 5092000 # 1 2022 北海道 5140000 # 2 2021 北海道 5183000 # 3 2020 北海道 5224614 # 4 2019 北海道 5259000
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def precision_at_k(recommended, relevant, k):
    rec_k = recommended[:k]
    return len(set(rec_k) & set(relevant)) / k

def recall_at_k(recommended, relevant, k):
    rec_k = recommended[:k]
    return len(set(rec_k) & set(relevant)) / len(relevant) if relevant else 0.0

def dcg_at_k(scores, k):
    return sum((2**s - 1) / np.log2(i + 2) for i, s in enumerate(scores[:k]))

def ndcg_at_k(recommended_scores, ideal_scores, k):
    dcg = dcg_at_k(recommended_scores, k)
    idcg = dcg_at_k(sorted(ideal_scores, reverse=True), k)
    return dcg / idcg if idcg > 0 else 0.0

# 例:東京の Top5 推薦が「大阪, 愛知, 神奈川, 福岡, 千葉」、 正解(隣県)が「神奈川, 千葉, 埼玉」
rec = ['大阪', '愛知', '神奈川', '福岡', '千葉']
rel = ['神奈川', '千葉', '埼玉']
print('Precision@5:', precision_at_k(rec, rel, 5))  # 0.4
print('Recall@5:', recall_at_k(rec, rel, 5))         # 0.667
📤 実行例(実行時の標準出力) Precision@5: 0.4 Recall@5: 0.6666666666666666
💬 読み方:推薦 5 件中に正解(隣県)が 2 件含まれる → Precision@5=0.4。正解 3 件のうち 2 件を回収 → Recall@5≈0.667。

⚠️ レコメンデーションの 5 つの落とし穴

① コールドスタート問題(Cold Start)
新規ユーザーは過去の評価履歴がないため、 協調フィルタリングが機能しない。 新規アイテムも同様で「誰にも評価されていない」アイテムは推薦候補にすら入らない。 対策:登録時アンケート、 人気アイテム推薦、 コンテンツベースとのハイブリッド化、 メタ情報利用。
② スパース性(Sparsity)
ユーザー × アイテム行列は 99% 以上が欠損 という極端なスパース行列。 Netflix で 1 億ユーザー × 数万作品なら、 1 人が見るのはせいぜい数十作品。 類似度計算で共通評価が少なく、 推定値が不安定になりがち。 行列分解や暗黙的フィードバック(クリック、 滞在時間)で対応。
③ 人気バイアス(Popularity Bias)
人気アイテムほど評価が集まり、 さらに推薦される正のフィードバックループ。 結果、 ロングテール(マイナーだが特定層に刺さる商品)が埋もれる。 対策:人気度逆数で重み付け、 多様性指標(Intra-List Diversity)の導入、 セレンディピティ重視のリランキング。
④ フィルターバブル(Filter Bubble)
過去の嗜好に合うものばかり推薦されると、 ユーザーの視野が狭まり、 政治的二極化や情報格差を助長する可能性。 SNS のニュース推薦では社会問題化。 対策:意図的な多様化、 反対意見の混在、 探索率(exploration rate)の確保、 ユーザーへの「なぜこれが推薦されたか」の説明。
⑤ オフライン評価とオンライン効果の乖離
RMSE や Precision@K が改善しても、 実際の CTR・売上・継続率 が上がるとは限らない。 オフライン指標で「上手く予測できる推薦」と、 オンラインで「ユーザーが実際にクリックする推薦」は別物。 A/B テストでビジネス指標を必ず検証する。

🎮 触って理解する — ミニ協調フィルタリングを自分の手で動かす

下の 5 ユーザー × 6 アイテムの評価行列(★1〜5、 完全に架空のデータです)で、 協調フィルタリングの「中身」を体感できます。 1 行目の「あなた」のセルを タップ/クリックすると評価が ★1 → ★2 → … → ★5 → 未評価 と切り替わり(スマホでは 上下ドラッグでも調整可)、 そのたびに 類似ユーザーのハイライト未評価アイテムの予測評価がリアルタイムで再計算されます。

方式: 類似度:

📊 類似度バー(リアルタイム更新)

🔍 類似度の計算過程を展開(どのユーザー/アイテムと何が共通か)

🧭 この演習の読み解きポイント

🚀 ここから先の発展

このウィジェットの近傍方式(k 近傍、 k-NN の親戚)は、 ユーザー数が数百万になると類似度計算が爆発します。 実務ではまず行列分解(Matrix Factorization)—— 評価行列を低次元の潜在因子行列の積 $R \approx PQ^{\top}$ に圧縮する方法(行列演算因子分析の親戚)——に進み、 さらに ★評価が集まらない現実に合わせて暗黙的フィードバック(クリック・視聴時間)を使う ALS / BPR、 そしてコンテンツ情報と組み合わせるハイブリッド推薦へと発展します。 また、 「似たものばかり薦める」ことによるフィルターバブルの緩和(多様化リランキング)や、 オンラインでの効果検証(A/B テスト)まで含めて初めて「推薦システムを作った」と言えます。

🗺 概念マップ — レコメンデーションを取り巻く技術地図

レコメンデーション 前提: 協調フィルタリング 並列: コンテンツベース 発展: 行列分解 / 深層学習 応用: EC / 動画配信 評価: Recall@k / NDCG 対比: ランダム提示

推薦システムは多くの統計・機械学習技術が交わる十字路です。 全体像を 1 枚にまとめました。

レイヤー主な技術本サイトの関連用語
0. データ層 ユーザー行動ログ/アイテムメタデータ/コンテキスト データパイプライン特徴量エンジニアリング
1. 類似度層 コサイン/Pearson/Jaccard/編集距離 コサイン類似度相関係数距離
2. モデル層 kNN/行列分解/NCF/Two-Tower/GNN k-NNPCA/SVDニューラルネット
3. 評価層 Precision/Recall@K/nDCG/MAP/RMSE/オンライン A/B Precision-RecallA/B テスト
4. デプロイ層 候補生成(recall)+ リランキング/キャッシュ/フィードバックループ MLOps監視

典型的な大規模本番推薦システムは、 2 段階アーキテクチャで構築されます:

  1. 候補生成(Candidate Generation):数百万アイテムから数百〜数千に絞る。 高速性重視。 Two-Tower、 行列分解、 近似最近傍検索(ANN)が定番。
  2. リランキング(Ranking):絞られた候補を精緻にスコアリング・並べ替え。 精度重視。 GBDT(LightGBM, XGBoost)、 DNN を多用。

さらに 事後処理(Post-Processing) として多様性、 ビジネスルール、 公平性制約を適用してから最終リストを返します。

📈 評価指標を深堀り — Precision@K, Recall@K, nDCG, MAP

推薦の評価指標は 「予測誤差系」(RMSE, MAE)と 「ランキング系」(Precision@K, Recall@K, nDCG, MAP, MRR)に大別されます。 現代の業界標準はほぼランキング系です。

指標計算式(概略)意味用途
RMSE $\sqrt{\frac{1}{N}\sum(r_{u,i} - \hat{r}_{u,i})^2}$ 評価値の予測誤差 Netflix Prize 時代の標準
Precision@K (K 件中の正解数) / K 上位 K 件のうち適合率 「最初の画面に何個刺さるか」
Recall@K (K 件中の正解数) / (全正解数) 正解アイテムを取り逃さない率 候補生成段階で重視
nDCG@K DCG@K / IDCG@K 「順位の重み」を考慮した適合度 ランキング全般の標準
MAP 各ユーザー AP の平均 平均適合率 情報検索由来、 学術論文で頻出
MRR $\frac{1}{N}\sum \frac{1}{\text{rank}_{\text{first hit}}}$ 最初の正解の順位逆数 1 件目に正解を出したい用途(QA、 検索)
Coverage (推薦されたユニーク・アイテム数) / (全アイテム数) カタログのどれだけを使えているか ロングテール多様性
Diversity $1 - \mathrm{avg}(\mathrm{sim}(i, j))$ 推薦リスト内の類似度の低さ 飽き防止
Novelty $-\log_2(\text{popularity})$ 「珍しさ」 セレンディピティ評価
Serendipity 意外性 × 適合性 「意外なのに刺さる」 長期ユーザー満足度

nDCG の計算ステップ

nDCG(normalized Discounted Cumulative Gain)は 順位による割引を入れた指標です:

【DCG と nDCG】
$$\mathrm{DCG}@K = \sum_{i=1}^{K} \frac{2^{\mathrm{rel}_i} - 1}{\log_2(i + 1)}, \quad \mathrm{nDCG}@K = \frac{\mathrm{DCG}@K}{\mathrm{IDCG}@K}$$
$\mathrm{rel}_i$=順位 i のアイテムの真の関連度。 IDCG=理想的順序の DCG。 nDCG は 0〜1 に正規化。

例:5 件推薦して関連度が [3, 2, 0, 1, 2] だったとき:

🏢 実世界の事例 — 5 つの代表的システム

📺 Netflix — 行列分解 + ディープラーニング
2006-2009 年の Netflix Prize(賞金 100 万 USD)で SVD ベース行列分解が圧勝。 現在は「2 段階アーキテクチャ」「コンテキスト推薦」「視聴時間最大化」を組み合わせ、 ホーム画面の構成自体を機械学習で最適化。 ユーザーごとに サムネイル画像まで変える パーソナライズを行う。
🎵 Spotify — 協調 + 音響特徴 + NLP
「Discover Weekly」は CF + NLP(ブログ・記事を解析した楽曲セマンティクス)+ CNN(音響特徴)のハイブリッド。 毎週月曜に 30 曲の新発見プレイリストを 4 億ユーザー全員に独自生成。 「セレンディピティ」(意外な発見)を KPI に置く稀有な例。
🛒 Amazon — アイテムベース CF の元祖
2003 年論文「Amazon.com Recommendations: Item-to-Item Collaborative Filtering」で業界に衝撃。 ユーザー数より少ないアイテム類似度行列をオフラインで計算し、 オンラインは「閲覧・購入アイテムの類似アイテム」を高速取得するだけ。 「この商品を買った人は…」の元祖。
📱 TikTok — 強化学習 + リアルタイム
For You ページが極めて高精度。 短い動画ごとに視聴時間・スワイプ・再生回数を即時フィードバック → 数秒で推薦モデルを微調整。 強化学習で「長期エンゲージメント」を最大化。 ユーザーの「滞在時間」を直接学習信号にすることで、 中毒性の高さが議論を呼ぶ。
📌 Pinterest — グラフベース推薦 PinSage
数十億ピン × ユーザーの二部グラフ上で GNN(Graph Neural Network)を学習し、 関連ピン推薦を実現。 PinSage 論文(KDD 2018)はグラフ推薦の標準的参照。 視覚的類似度 + グラフ構造 + ユーザー嗜好を統合。

🕰 推薦システムの歴史 — 5 つの転換点

時期転換点意義
1992 Tapestry(Xerox PARC)— 協調フィルタリングの語誕生 メール推薦システムで、 他人のフィードバックを使う最初の試み
1994 GroupLens — ニュース推薦の Pearson 相関 CF 明示的フィードバック × ユーザーベース CF の原型
2003 Amazon — アイテムベース CF を学術論文化 スケーラビリティの壁を突破、 EC の標準に
2006-2009 Netflix Prize — 行列分解の優勝 SVD、 NMF、 アンサンブルが業界標準化
2016-現在 Deep Learning 推薦(NCF, Two-Tower, GNN, Transformer) Google, YouTube, TikTok, Pinterest が DL ベースに移行
レコメンデーション ユーザーベース CF アイテムベース CF 行列分解(MF, SVD) ニューラル CF (NCF) Two-Tower モデル Wide & Deep

🔗 隣接手法への橋渡し

「レコメンデーション」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

SSDSE-B-2026 の都道府県別データを「ユーザー嗜好」に見立てたレコメンドは、 取得 (e-Stat) → 類似度計算 (cosine) → 提示 (top-k) → A/B 評価の流れで完結する。

🌳 手法選択フロー

レコメンドシステムは「データの種類」と「コールドスタート問題への対処」で構成を選ぶ。 SSDSE-B-2026 を題材にした観光地推薦を例に判断軸を示す。

  1. Step 1: 利用できるデータは何か?
    • ユーザー × アイテムの評価行列のみ → 協調フィルタリング (ユーザーベース / アイテムベース cosine 類似度)
    • アイテムの特徴量 (ジャンル・タグ・テキスト) → 内容ベース (TF-IDF / 埋め込み)
    • 両方ある → ハイブリッド (協調 + 内容、 重み付き和や Stacking)
  2. Step 2: コールドスタート (新ユーザー / 新アイテム) はあるか?
    • 新ユーザー多い → 内容ベース + デモグラ (年代・地域) で初期提案
    • 新アイテム多い → 内容ベース + メタデータで類似アイテムを推薦
    • 安定運用後 → 行列因子分解 (SVD / ALS) でスケーラブルに
  3. Step 3: スケールと評価は?

SSDSE-B-2026 を「県別の特徴ベクトル」に変換し、 ユーザー嗜好と cosine 類似度で「あなたに合う観光県 Top5」を返す MVP なら、 内容ベース + 47 × 100 行列で実装可能。 1 日で動かせる小規模事例から始めて、 評価とハイブリッド化を段階的に進める。

🎨 直感をもう一段 — 「似た人」と「似たモノ」、二つの道

推薦の出発点は驚くほど素朴です。「あなたが次に好きそうなもの」を当てるために、大きく二つの道があります。

明示的フィードバック と 暗黙的フィードバック

「あなたの好み」を示す信号には二種類あります。明示的(★1〜5、👍👎、レビュー)は意味が明快だが数が少なく、暗黙的(クリック・購入・滞在時間・視聴完走率)は圧倒的に多いが「クリックした=好き」とは限らない曖昧さを含みます。現実のサービスは大半が暗黙的信号で動いており、「見なかった=嫌い」ではなく「知らなかっただけ」かもしれない、という負例の欠如が学習を難しくします(詳しくは下の「発展」節へ)。

🧮 SSDSE 実データで見る「似た人」の直感

「都道府県=ユーザー、指標=アイテム」と見立て、SSDSE-B-2026(2023 年・47 都道府県・数値 109 指標を標準化) で県間コサイン類似度を計算すると、直感がそのまま数字に出ます(以下はすべて当該データからの実測値)。

基準の県最も似た県(Top 5・コサイン類似度)
東京都大阪府 0.905 / 愛知県 0.884 / 神奈川県 0.876 / 福岡県 0.854 / 千葉県 0.835
大阪府東京都 0.905 / 福岡県 0.896 / 兵庫県 0.882 / 愛知県 0.872 / 神奈川県 0.836
鳥取県島根県 0.912 / 山口県 0.873 / 佐賀県 0.846 / 福井県 0.795 / 富山県 0.776
沖縄県鹿児島県 0.672 / 長崎県 0.611 / 愛媛県 0.528 / 宮崎県 0.525 / 和歌山県 0.486

大都市は大都市と、地方は地方と似る」——東京都の最近傍は大阪府(0.905)、鳥取県の最近傍は島根県(0.912)と、規模と地域性がそのまま類似度に反映されます。一方で 沖縄県は最近傍でも 0.672 と際立って低く、どの県とも似ていない「孤立ユーザー」に相当します。この孤立が、次節の「県版コールドスタート」の実例になります。

⚠️ 落とし穴を深掘り — 「観測できたもの」が既に歪んでいる

上の「5 つの落とし穴」に加え、推薦システムに固有で、しかも見落とされやすい罠を掘り下げます。特に ⑥ 選択バイアス は「そもそも学習データが信用できない」という土台の問題で、最重要です。

⑥ 選択バイアス — 観測データは「過去の推薦結果」そのもの(最重要)
ユーザーがクリック・評価できたのは、過去のシステムが表示したアイテムだけです。つまりログは「ユーザーの真の好み」ではなく「過去の推薦方針 × ユーザーの好み」の混合物。表示されなかったアイテムに評価が付かないのは当然で、これを「不人気」と解釈すると誤りが自己増殖します。データが Missing Not At Random(欠測が結果に依存)である典型で、選択バイアス欠測メカニズムの問題そのもの。対策:傾向スコア(IPS)で観測確率の逆数重み付け、意図的なランダム提示(探索)でバイアスのない検証ログを確保。
⑦ 多様性 vs 精度 — 「当てにいく」ほど退屈になる
予測精度だけを追うと、既に好きと分かっているものばかりが並び、リストが単調になります。精度(当たる)と多様性・セレンディピティ(意外な発見)はしばしばトレードオフ。指標としては Intra-List Diversity(リスト内非類似度)や Coverage(カタログ網羅率)を精度指標と併記し、MMR などのリランキングで「わざと外す枠」を確保します(本ページの🎮ウィジェット下部の MMR 実装が最小例)。
⑧ 暗黙的フィードバックの解釈 — 「クリック=好き」とは限らない
暗黙的信号には負例が存在しないのが本質的困難。「見なかった」は「興味なし」と「知らなかった」の混在で、区別できません。また釣りタイトルへのクリックのように「クリックしたが失望した」ケースもある。対策:観測回数を信頼度 $c_{u,i}=1+\alpha r_{u,i}$ として重み付ける ALS(本ページ上部で既出)、未観測をペアワイズに扱う BPR、ドウェル時間で「満足クリック」を選別する等。
⑨ 評価の難しさ — オフライン指標とオンライン成果は別物、しかも位置バイアス付き
オフラインで Precision@K や nDCG が改善しても、実際の CTR・売上・継続率が上がる保証はありません(既出⑤)。さらにオフライン評価自体も、ログの上位表示ほどクリックされやすい位置バイアス(position bias)で歪みます。金字塔は A/B テストによるオンライン検証ですが、新規性効果(目新しさで一時的に伸びる)や長期指標との乖離に注意。ランキング品質の物差しは ランキングPrecision / Recall評価指標の節と合わせて理解を。
⑩ 県版コールドスタート — SSDSE の沖縄県で体感する
前節の実測で 沖縄県は最近傍でも類似度 0.672(東京都の 0.905 と比べ大幅に低い)。「似た県」が乏しい沖縄への推薦は、少数の弱い類似度に頼るため不安定で外れやすい——これは新規ユーザー/孤立ユーザーで近傍が張れないコールドスタート(既出①)の、実データによる縮図です。対策の発想は同じ:属性(コンテンツベース)やメタ情報で近傍の欠如を補う。

🧠 発展 — 近傍法から行列分解・評価指標・バンディットへ

1. 協調フィルタリングの三段階

【行列分解の目的関数(明示的評価)】
$$\min_{P,Q}\sum_{(u,i)\in\mathcal{K}}\bigl(r_{u,i}-\mathbf{p}_u^{\top}\mathbf{q}_i\bigr)^2+\lambda\bigl(\|P\|_F^2+\|Q\|_F^2\bigr)$$
$\mathcal{K}$=観測済みセルの集合(未観測は和に含めない点が、上部の ALS=暗黙的版との違い)。$\mathbf{p}_u,\mathbf{q}_i$=ユーザー/アイテムの潜在因子ベクトル。$\lambda$=過学習を抑える正則化。

2. ランキング評価指標 — nDCG と MAP

推薦は「当てる」だけでなく「良い順に並べる」課題なので、順位を考慮する指標が要ります(nDCGMAP の個別ページは未整備のためテキストで定義)。

【nDCG(正規化割引累積利得)】
$$\mathrm{DCG@K}=\sum_{k=1}^{K}\frac{2^{rel_k}-1}{\log_2(k+1)},\qquad \mathrm{nDCG@K}=\frac{\mathrm{DCG@K}}{\mathrm{IDCG@K}}$$
$rel_k$=順位 $k$ のアイテムの関連度。分母の $\log_2(k+1)$ で「下位ほど価値を割り引く」。IDCG=理想順(関連度降順)での DCG で割り、0〜1 に正規化。
【MAP(平均適合率の平均)】
$$\mathrm{AP}=\frac{1}{|R|}\sum_{k=1}^{K}\mathrm{Precision@}k\cdot \mathbb{1}[\text{$k$ 位が正解}],\qquad \mathrm{MAP}=\frac{1}{|U|}\sum_{u\in U}\mathrm{AP}_u$$
各正解を掴んだ時点までの適合率を平均したのが AP、それを全ユーザーで平均したのが MAP。二値の関連度向き。Precision / Recallランキングの発展形。

3. 多様性・セレンディピティ・新規性

精度指標だけでは測れない「体験の質」を捉える補助指標群です。多様性(リスト内が互いに似すぎない=Intra-List Diversity)、セレンディピティ(予想外だが有用な発見)、新規性(ユーザーが未知)、網羅率(Coverage)(カタログのどれだけを推薦し得るか)。人気バイアス・フィルターバブルの緩和は、これらを精度と併記して MMR 等でリランキングすることで実現します。

4. バンディット・強化学習 — 探索と活用のオンライン学習

選択バイアス(⑥)から逃れる根本策は「たまに未知を試す」こと。多腕バンディット(ε-greedy/UCB/Thompson Sampling/文脈付きバンディット LinUCB)は、既知の good(活用)と未知の探索(exploration)のバランスをオンラインで最適化し、ニュース・広告推薦で定番です。長期報酬まで見据えるなら 強化学習へ拡張します(bandit の個別ページは未整備のためテキストで示します)。実運用の効果は最終的に A/B テストで検証します。

関連ページ(本サイト内の実在ページのみ)