🎓 レベル:標準 | 重要度:A(必須)
📎 関連:第8章 レコメンデーション 目次 | 前提:協調フィルタリング(近傍法・行列分解)
要点(BLUF)
- 協調フィルタリング(近傍法・行列分解) がユーザーたちの行動履歴を使うのに対し、コンテンツベース推薦はアイテムの属性(特徴ベクトル)を使い、「ユーザーが好んだアイテムに似た属性のもの」を薦めます。ユーザープロファイル =好んだアイテムの特徴の平均、スコア = プロファイルとアイテムのコサイン類似度。製品を属性の束と捉える発想は コンジョイント分析(部分効用・WTP・市場シェア) そのものです。
- 最大の利点はコールドスタート——履歴ゼロの新規アイテムでも、属性さえあれば推薦できる。合成データ(既存アイテム30+新規1、ジャンル特徴5次元)で、嗜好に合致する新規アイテムを、協調フィルタリングは評価ゼロ点で推薦不可なのに、コンテンツベースは で Top-2 に拾います。CF の弱点をコンテンツが補う、という補完関係がここで効きます。
- ハイブリッド の重み を に振ると、新規アイテムの順位が2位(コンテンツ重視)から最下位 26位(CF重視)へ移ります。両系統は一長一短で、 の調整で「既存の売れ筋を活かしつつ、新規も拾う」バランスを取る——ただしコンテンツは似たものに偏りやすい(セレンディピティ低下)など固有の弱点もあり、万能ではありません。
1. コンテンツベースとは:属性で「似たもの」を薦める
協調フィルタリング(近傍法・行列分解) は強力でしたが、コールドスタートという致命的な弱点がありました——履歴のない新規ユーザー/新規アイテムは、評価行列に痕跡が無いので推薦に乗らない。新作映画は誰の「おすすめ」にも現れず、入会したての客には何も出せない。ところが実務では、毎日のように新商品が追加され、新規客が訪れます。ここで発想を変えます。アイテムの中身(属性)を見れば、評価が一つも無くても「似たもの」は分かる——これがコンテンツベース推薦です。
仕組みは素直です。各アイテムを特徴ベクトルで表します——映画なら「アクション/ロマンス/コメディ/SF/ドキュメンタリー」のジャンル成分、商品なら価格帯・カテゴリ・ブランドなど。これは製品を属性の束として測る コンジョイント分析(部分効用・WTP・市場シェア) の特徴量設計と同じ土俵です。次に、ユーザーが過去に好んだアイテムの特徴を平均して、その人の好みを一本のベクトル=ユーザープロファイルにまとめます。「この人はアクションとSFが好き」がベクトルとして表れる。あとは、プロファイルに似た属性のアイテムを クラスタリングによるセグメンテーション(k-means) と同じコサイン類似度で探し、上位を薦めるだけ。
協調フィルタリングとの対比が肝心です。協調は他人から学ぶ(あなたが知らないジャンルでも、似た人が好めば薦められる)。コンテンツはアイテムの中身から薦める(他人がゼロでも、属性が合えば薦められる)。だから、履歴ゼロの新規アイテムはコンテンツの独壇場です。逆に、コンテンツは「あなたの過去の好みに似たもの」しか出せず、視野が広がりにくい。両者は弱点が裏返し——なので実務では重み で混ぜるハイブリッドが定番になります。
flowchart LR L["好んだアイテム集合 L(の特徴ベクトル)"] --> U["ユーザープロファイル u = 特徴の平均"] X["全アイテムの特徴ベクトル xᵢ"] --> SC["content = cos(u, xᵢ)"] U --> SC CF["協調フィルタリングのスコア(08-01)"] --> H["ハイブリッド score = α・CF + (1-α)・content"] SC --> H H --> REC["推薦(新規アイテムも拾える)"]
2. ユーザープロファイル・コサインスコア・ハイブリッド(数式)
各アイテム を特徴ベクトル で表します( は属性の数、本ノートでは5ジャンル)。ユーザーが過去に好んだアイテムの集合を とすると、ユーザープロファイルはその特徴の平均ベクトルです。
「好んだものの平均的な属性」が、その人の好みの中心になります。アイテム へのコンテンツスコアは、プロファイルと特徴ベクトルのコサイン類似度で測ります。
コサインは向き(属性の構成比)だけを見て大きさを無視するので、「アクション寄りかSF寄りか」というジャンルの方向が一致するほど高くなります。ここで決定的なのは、この計算に評価行列が一切要らないこと。 さえあれば、評価ゼロの新規アイテムでもスコアが付きます——コールドスタートに強い理由です。
最後にハイブリッド。協調フィルタリングのスコア (協調フィルタリング(近傍法・行列分解) の予測)とコンテンツスコアを、重み で線形結合します。
なら純粋な協調、 なら純粋なコンテンツ、中間で両取り。CF とコンテンツはスケールが違う(CF は評価スケール、コンテンツは の )ので、合成前に両者を に正規化してから混ぜるのが実務の作法です。 は「既存の行動データをどれだけ信じ、属性での外挿をどれだけ効かせるか」を決めるツマミで、コールドスタートの多寡などに応じて調整します。
3. コールドスタートをコンテンツで救う(コード)
アイテム30個、ジャンル特徴5次元(各アイテムは主ジャンル1つが強い)を合成し、アクション・SF 好きのユーザーを置きます。好んだアイテムからプロファイルを作り、コサインで全アイテムをスコアリング。そこへ評価ゼロだが特徴のある新規アイテム(アクション+SFの新作)を1つ加え、協調フィルタリングは評価ゼロで推薦できないのに対し、コンテンツベースは特徴から正しくスコアできることを示します。最後にハイブリッド の を振り、新規アイテムの順位がどう動くかを見ます。sklearn は使わず、コサインは自前です。
import numpy as np
import pandas as pd
rng = np.random.default_rng(2)
genres = ["アクション", "ロマンス", "コメディ", "SF", "ドキュメンタリー"]
n_exist, n_feat = 30, 5
# 既存アイテムの特徴ベクトル(5ジャンル)。各アイテムは主ジャンル1つが強い(弱い副成分+ノイズ)
prim = rng.integers(0, n_feat, n_exist)
X_exist = rng.uniform(0.0, 0.25, (n_exist, n_feat))
X_exist[np.arange(n_exist), prim] += rng.uniform(0.7, 1.0, n_exist)
# ユーザーの嗜好:アクション・SF を好む。高評価アイテム=嗜好との整合が高い上位5件
taste = np.array([0.9, 0.0, 0.0, 0.9, 0.0])
liked = np.sort(np.argsort(-(X_exist @ taste))[:5])
print("ユーザーが高評価したアイテム liked =", liked.tolist())
# ユーザープロファイル u = 好んだアイテムの特徴ベクトルの平均
profile = X_exist[liked].mean(axis=0)
print("ユーザープロファイル u =", np.round(profile, 3).tolist(), "(順に", "/".join(genres), ")")
# 新規アイテム:特徴はあるが評価ゼロ(コールドスタート)。アクション+SF を両取りした新作
x_new = np.array([0.90, 0.10, 0.10, 0.90, 0.10])
X = np.vstack([X_exist, x_new]) # 31アイテム、index 30 = 新規
n_items, new_id = n_exist + 1, n_exist
# コサイン類似度(自前)でコンテンツスコア score(u,i)=cos(u, x_i)
def cosine(u, M):
un = u / (np.linalg.norm(u) + 1e-12)
Mn = M / (np.linalg.norm(M, axis=1, keepdims=True) + 1e-12)
return Mn @ un
content = cosine(profile, X) # 全31アイテムのコンテンツスコア
# CF(協調フィルタリング)の予測を模擬:既存は評価履歴から予測値、新規は0(履歴なし)
cf = np.concatenate([np.clip(rng.normal(3.3, 0.5, n_exist), 1, 5), [0.0]])
# 0..1 に正規化して合成可能に(CFは評価スケール、contentはcosで尺度が違うため)
norm01 = lambda v: (v - v.min()) / (v.max() - v.min() + 1e-12)
content_n, cf_n = norm01(content), norm01(cf)
# === コールドスタートの実演 ===
print(f"\n=== コールドスタート:新規アイテム(ID={new_id}) ===")
print(f" CF予測 : {cf[new_id]:.2f} → 評価履歴ゼロ。協調フィルタは推薦できない")
print(f" コンテンツ : cos={content[new_id]:.3f} → 特徴ベクトルがあるので推薦できる")
# === コンテンツベース Top-5 推薦(既に好んだ liked は除外)===
cand = np.array([i for i in range(n_items) if i not in liked])
top = cand[np.argsort(-content[cand])[:5]]
df = pd.DataFrame({
"アイテムID": top,
"コンテンツスコア": content[top],
"新規?": ["★新規" if i == new_id else "" for i in top],
}, index=[f"第{r+1}位" for r in range(5)])
print("\n=== コンテンツベース Top-5 推薦(嗜好プロファイルに近い順)===")
print(df.to_string(formatters={"コンテンツスコア": "{:.3f}".format}))
# === ハイブリッド score = α*CF + (1-α)*content:α を振って新規アイテムの順位を見る ===
print("\n=== ハイブリッド score = α*CF + (1-α)*content ===")
print(f" (候補{len(cand)}件中での新規アイテムID={new_id}の順位。α大=CF重視)")
for a in [0.0, 0.25, 0.5, 0.75, 1.0]:
h = a * cf_n + (1 - a) * content_n
rank = int((h[cand] > h[new_id]).sum() + 1)
note = "← 履歴ゼロの新規が埋もれる" if a == 1.0 else ("← コンテンツが新規を拾う" if a == 0.0 else "")
print(f" α={a:.2f}: 新規スコア={h[new_id]:.3f}, {len(cand)}件中 {rank:2d}位 {note}")
出力:
ユーザーが高評価したアイテム liked = [2, 15, 23, 24, 27]
ユーザープロファイル u = [0.775, 0.124, 0.141, 0.422, 0.118] (順に アクション/ロマンス/コメディ/SF/ドキュメンタリー )
=== コールドスタート:新規アイテム(ID=30) ===
CF予測 : 0.00 → 評価履歴ゼロ。協調フィルタは推薦できない
コンテンツ : cos=0.955 → 特徴ベクトルがあるので推薦できる
=== コンテンツベース Top-5 推薦(嗜好プロファイルに近い順)===
アイテムID コンテンツスコア 新規?
第1位 7 0.959
第2位 30 0.955 ★新規
第3位 13 0.951
第4位 11 0.614
第5位 26 0.579
=== ハイブリッド score = α*CF + (1-α)*content ===
(候補26件中での新規アイテムID=30の順位。α大=CF重視)
α=0.00: 新規スコア=0.994, 26件中 2位 ← コンテンツが新規を拾う
α=0.25: 新規スコア=0.745, 26件中 3位
α=0.50: 新規スコア=0.497, 26件中 13位
α=0.75: 新規スコア=0.248, 26件中 26位
α=1.00: 新規スコア=0.000, 26件中 26位 ← 履歴ゼロの新規が埋もれる
出力の意味:まずユーザーのプロファイルが立ちます。アクション・SF 好きという嗜好から、好んだアイテム(ID [2, 15, 23, 24, 27])の特徴を平均すると、プロファイル ——アクション ・SF が突出し、好みがベクトルとして表れています。
次が本題のコールドスタート。新規アイテム(ID30、アクション+SFの新作)について、協調フィルタリングの予測は ——評価履歴がゼロなので、内積も加重平均も計算できず、推薦に乗せられません。ところがコンテンツベースは 。属性ベクトルがプロファイルとほぼ同じ向きを向いているので、一つも評価が無くても高くスコアできるのです。これがコンテンツベースの存在意義です。
コンテンツベース Top-5 を見ると、新規アイテム(ID30、)が堂々の2位で推薦リストに入っています(1位は既存のアクション強アイテム7、僅差)。 位までが 超で、 位以降は 台へ急落——嗜好に合う「アクション+SF」の塊と、そうでないアイテムがくっきり分かれています。履歴ゼロの新作が、初日からおすすめ上位に並ぶ——協調フィルタリング単独では絶対にできなかったことです。
最後のハイブリッドが両系統の橋渡しです。(CF を信じる重み)を に振ると、新規アイテムの順位は 2位 → 3位 → 13位 → 26位 → 26位 と沈みます。(純コンテンツ)では新規が2位に来るのに、(純CF)では履歴ゼロゆえ最下位。逆に言えば、 を上げるほど「みんなの行動で裏打ちされた既存アイテム」が優先される。 は「新規をどれだけ攻めて拾うか/既存の実績をどれだけ重んじるか」のツマミだと分かります。コールドスタートが課題なら を下げ、既存資産を活かしたいなら上げる——この一本のパラメータで両系統を連続的に混ぜられるのがハイブリッドの妙です。
特徴空間と の効き方を1枚にまとめます(このブロックはデータ生成からスコア計算までを内部で再実行し、上と同じ値を描きます)。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
# データ生成 → プロファイル → コンテンツ/CF/ハイブリッド をこのブロック内で再実行し、
# (左) 特徴空間(SVDで2D)のアイテム配置とユーザープロファイル、
# (右) α に対する「新規アイテム」と「高CFの既存アイテム」の推薦順位の入れ替わりを描く。
rng = np.random.default_rng(2)
n_exist, n_feat = 30, 5
prim = rng.integers(0, n_feat, n_exist)
X_exist = rng.uniform(0.0, 0.25, (n_exist, n_feat))
X_exist[np.arange(n_exist), prim] += rng.uniform(0.7, 1.0, n_exist)
taste = np.array([0.9, 0.0, 0.0, 0.9, 0.0])
liked = np.sort(np.argsort(-(X_exist @ taste))[:5])
profile = X_exist[liked].mean(axis=0)
x_new = np.array([0.90, 0.10, 0.10, 0.90, 0.10])
X = np.vstack([X_exist, x_new])
n_items, new_id = n_exist + 1, n_exist
def cosine(u, M):
un = u / (np.linalg.norm(u) + 1e-12)
Mn = M / (np.linalg.norm(M, axis=1, keepdims=True) + 1e-12)
return Mn @ un
content = cosine(profile, X)
cf = np.concatenate([np.clip(rng.normal(3.3, 0.5, n_exist), 1, 5), [0.0]])
norm01 = lambda v: (v - v.min()) / (v.max() - v.min() + 1e-12)
content_n, cf_n = norm01(content), norm01(cf)
cand = np.array([i for i in range(n_items) if i not in liked])
best_cf = cand[np.argmax(cf[cand])] # 候補内で最もCFの高い既存アイテム
fig, ax = plt.subplots(1, 2, figsize=(11.8, 4.6))
# 左:特徴空間(SVDで2次元へ)。プロファイル近傍が推薦される様子
Xc = X - X.mean(axis=0)
_, _, Vt = np.linalg.svd(Xc, full_matrices=False)
W = Vt[:2].T
co = Xc @ W
pc = (profile - X.mean(axis=0)) @ W
others = [i for i in range(n_items) if i not in liked and i != new_id]
ax[0].scatter(co[others, 0], co[others, 1], s=40, color="C7", alpha=0.5, label="既存アイテム")
ax[0].scatter(co[liked, 0], co[liked, 1], s=70, color="C0", label="好んだアイテム(liked)")
ax[0].scatter(*pc, s=320, marker="*", color="C2", zorder=6, label="ユーザープロファイル u")
ax[0].scatter(co[new_id, 0], co[new_id, 1], s=240, marker="X", color="C3", zorder=6,
label="新規アイテム(評価ゼロ)")
ax[0].set_xlabel("特徴 第1主成分")
ax[0].set_ylabel("特徴 第2主成分")
ax[0].set_title("特徴空間:プロファイルの近くを推薦する")
ax[0].legend(loc="best", fontsize=8.5)
ax[0].grid(alpha=0.3)
# 右:α に対する順位(小さいほど上位)。新規は沈み、高CF既存は浮く=ハイブリッドの調整
alphas = np.linspace(0, 1, 51)
rank_new, rank_cf = [], []
for a in alphas:
h = a * cf_n + (1 - a) * content_n
rank_new.append(int((h[cand] > h[new_id]).sum() + 1))
rank_cf.append(int((h[cand] > h[best_cf]).sum() + 1))
ax[1].plot(alphas, rank_new, "-", color="C3", lw=2.2, label=f"新規アイテム(ID={new_id}, 履歴ゼロ)")
ax[1].plot(alphas, rank_cf, "-", color="C0", lw=2.2, label=f"高CFの既存アイテム(ID={best_cf})")
ax[1].invert_yaxis()
ax[1].set_xlabel("ハイブリッド重み α(0=コンテンツのみ, 1=CFのみ)")
ax[1].set_ylabel("推薦順位(上が上位)")
ax[1].set_title("α で新規と既存の優先が入れ替わる")
ax[1].legend(loc="center right", fontsize=9)
ax[1].grid(alpha=0.3)
fig.tight_layout()
plt.show()
左パネルは、5次元の特徴を SVD で2次元に落とした特徴空間です。緑の星がユーザープロファイル、青が好んだアイテム、赤い × が新規アイテム。新規アイテムがプロファイルと好んだアイテムの塊のすぐ近くに位置し、灰色の他アイテム群から離れているのが見て取れます——「プロファイルの近くを薦める」というコンテンツベースの幾何が、そのまま絵になっています。右パネルは に対する推薦順位で、上ほど上位。 を上げる(CF重視にする)と、新規アイテム(赤)は履歴ゼロゆえ沈み、入れ替わりに高CFの既存アイテム(青)が浮上します。二本の線が中ほどで交差する——これが「ハイブリッドの で、新規と既存の優先順位を連続的に調整している」ことの可視化です。
⚠️ よくある誤解
- コンテンツベースは「似たものばかり」になりやすい:プロファイルに近い属性を薦める仕組みなので、ユーザーの過去の嗜好の周辺をぐるぐる回りがちです(アクション好きにアクションばかり)。意外な良い出会い=セレンディピティが低下し、新ジャンルへの広がりが生まれにくい。これは協調フィルタリングが「似た他人経由で未知のジャンルを運んでくる」のと対照的な弱点で、過度な専門化(over-specialization)と呼ばれます。多様性を確保するには別途の工夫(探索の注入・カテゴリの強制混在)が要ります。
- 推薦の品質が特徴量の品質に直結する:コンテンツベースは「アイテムの属性をどう設計したか」がすべてです。ジャンル5次元しか持たなければ、ジャンルが同じだけの凡作と名作を区別できません。良い特徴量(メタデータ・テキスト埋め込み・画像特徴…)が無ければ精度は頭打ちで、特徴量エンジニアリングが成否を握る——協調フィルタリングが「中身を知らなくても行動さえあれば動く」のと、ここも裏返しの性質です。
- ハイブリッドは万能ではなく、 の調整が要る:CF とコンテンツを混ぜれば双方の良いとこ取り、とは限りません。本ノートで見たように、 が大きすぎれば新規が埋もれ、小さすぎれば既存の行動データという貴重な資産を捨てます。最適な は、コールドスタートの頻度・データの疎密・狙う多様性で変わり、A/Bテスト(A/Bテストの設計と分析)で実地に決めるべき量です。さらにスケールの違う2スコアを正規化してから混ぜる必要があり、合成は見た目ほど自明ではありません。
- コンテンツは「自分の過去」に閉じ、協調は「他者」から学べる:コンテンツベースが使えるのはそのユーザー自身の履歴と、アイテムの属性だけ。あなたがまだ触れていない領域は、属性が合わない限り出てきません。対して協調フィルタリングは、似た他人の行動を経由して「あなたの履歴には無いが、あなたに似た人が好んだもの」を運んできます。どちらが上ではなく、情報源が違う(自分の属性 vs 他人の行動)——だから両者は補完で、実務はハイブリッドに落ち着きます。
- 特徴量・埋め込みの理論、オンライン最適化は別テキスト:テキストの TF-IDF・単語埋め込み・画像/音声の特徴抽出といった特徴量の作り方の理論は機械学習テキストの守備範囲です。また「薦めて反応を見て や候補を更新し続ける」オンライン最適化(多腕バンディット)は 08-03(予定)で扱います。本ノートは重複させず、コンテンツベースとハイブリッドをマーケのコールドスタート対策としてどう使うかに絞っています。
関連ノート
- 協調フィルタリング(近傍法・行列分解)(前提・対の手法。協調が「みんなの行動」を使い、コンテンツが「アイテムの属性」を使う。協調の弱点コールドスタートをコンテンツが補い、 で混ぜてハイブリッドにする)
- 第8章 レコメンデーション 目次
- コンジョイント分析(部分効用・WTP・市場シェア)(製品=属性の束。部分効用で属性の価値を測る発想が、コンテンツベースの特徴ベクトルとコサインスコアに直結する)
- 特徴量エンジニアリング・テキスト/画像の埋め込みの理論は機械学習テキスト、薦めて反応を見て更新するオンライン最適化(多腕バンディット)は 08-03(予定)、最適な を実地に決める検証は A/Bテストの設計と分析 で扱います
- マーケティング・サイエンス 全体目次