ストーリー
要件の整理
interface RecommendationRequirements {
functional: {
itemToItem: "この商品を見た人はこちらも見ています";
personalized: "あなたへのおすすめ";
trending: "人気の商品/コンテンツ";
collaborative: "似たユーザーが好んだアイテム";
};
nonFunctional: {
latency: "p99 < 100ms(リアルタイム推薦)";
freshness: "新しいアイテムが1時間以内に推薦候補に入る";
scale: "1億ユーザー、1000万アイテム";
accuracy: "CTR(Click Through Rate) 5%以上";
};
}
レコメンデーションの3つのアプローチ
// 1. コンテンツベースフィルタリング
class ContentBasedFiltering {
// アイテムの特徴量を使って類似アイテムを見つける
findSimilar(item: Item): Item[] {
// 特徴量ベクトルの類似度(コサイン類似度)を計算
const itemVector = this.extractFeatures(item);
// ジャンル、タグ、説明文から特徴を抽出
return this.items
.map(candidate => ({
item: candidate,
similarity: this.cosineSimilarity(itemVector, this.extractFeatures(candidate)),
}))
.sort((a, b) => b.similarity - a.similarity)
.slice(0, 20)
.map(r => r.item);
}
}
// 2. 協調フィルタリング
class CollaborativeFiltering {
// ユーザーの行動パターンから推薦する
// 「AさんとBさんは過去に似た商品を買った → Aが買ってBが未購入の商品を推薦」
recommend(userId: string): Item[] {
const similarUsers = this.findSimilarUsers(userId);
const candidateItems = this.getItemsFromSimilarUsers(similarUsers);
return this.rankByPredictedRating(userId, candidateItems);
}
}
// 3. ハイブリッドアプローチ(実運用推奨)
class HybridRecommender {
recommend(userId: string, context: Context): Item[] {
// 複数の推薦結果をブレンド
const contentBased = this.contentFilter.recommend(userId);
const collaborative = this.collaborativeFilter.recommend(userId);
const trending = this.trendingService.getPopular();
// 重み付きスコアで最終ランキング
return this.blendAndRank(contentBased, collaborative, trending, context);
}
}
ハイレベル設計
┌──────────────────────────────────────────────────────┐
│ │
│ [ユーザーリクエスト] │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ 候補生成 │────→│ ランキング │ │
│ │ (Candidate) │ │ (Ranking/Scoring) │ │
│ └──────┬──────┘ └────────┬─────────┘ │
│ │ │ │
│ ┌──────┴──────────────────────┴────────────┐ │
│ │ データレイヤー │ │
│ │ │ │
│ │ [ユーザープロファイル] [アイテム特徴量] │ │
│ │ [行動ログ] [モデル] │ │
│ │ [類似度キャッシュ] [トレンドキャッシュ] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ [オフラインパイプライン] │
│ 行動ログ → 特徴量抽出 → モデル学習 → モデルデプロイ │
│ │
└──────────────────────────────────────────────────────┘
2ステージアーキテクチャ
// Stage 1: 候補生成(Candidate Generation)
// 数百万アイテムから数百件に絞り込む
class CandidateGenerator {
async generate(userId: string): Promise<CandidateItem[]> {
const candidates: CandidateItem[] = [];
// 過去に見たアイテムの類似アイテム
const recentViews = await this.getRecentViews(userId, 50);
for (const view of recentViews) {
const similar = await this.similarityIndex.getNearest(view.itemId, 20);
candidates.push(...similar);
}
// 協調フィルタリングからの候補
const cfCandidates = await this.collaborativeFilter.getCandidates(userId, 100);
candidates.push(...cfCandidates);
// トレンドアイテム
const trending = await this.trendingService.getTopItems(50);
candidates.push(...trending);
// 重複排除して返す
return this.deduplicate(candidates);
}
}
// Stage 2: ランキング(Ranking)
// 候補をスコアリングして上位を返す
class Ranker {
async rank(userId: string, candidates: CandidateItem[]): Promise<RankedItem[]> {
const userProfile = await this.getUserProfile(userId);
return candidates
.map(candidate => ({
...candidate,
score: this.calculateScore(userProfile, candidate),
}))
.sort((a, b) => b.score - a.score)
.slice(0, 20); // 上位20件を返す
}
private calculateScore(user: UserProfile, item: CandidateItem): number {
// 複数のシグナルを組み合わせ
const relevance = this.relevanceModel.predict(user, item);
const freshness = this.freshnessScore(item.publishedAt);
const popularity = item.engagementRate;
const diversity = this.diversityBonus(item);
return relevance * 0.5 + freshness * 0.2 + popularity * 0.2 + diversity * 0.1;
}
}
コールドスタート問題
// 新規ユーザーや新規アイテムへの対処
const COLD_START_STRATEGIES = {
newUser: {
strategy: "人気アイテム + デモグラフィック情報ベースの推薦",
steps: [
"1. まず全体のトレンドアイテムを表示",
"2. 初回登録時に興味カテゴリを選択させる",
"3. 行動データが蓄積されたら徐々にパーソナライズ",
],
},
newItem: {
strategy: "コンテンツベースフィルタリング + ブーストスコア",
steps: [
"1. メタデータ(カテゴリ、タグ)から類似アイテムを特定",
"2. 新着ブーストスコアを加算して露出を増やす",
"3. エンゲージメントデータが溜まったら通常ランキングに移行",
],
},
};
まとめ
| ポイント | 内容 |
|---|---|
| 3つのアプローチ | コンテンツベース、協調フィルタリング、ハイブリッド |
| 2ステージ構成 | 候補生成(広く)→ ランキング(精緻に) |
| コールドスタート | 人気/トレンド → カテゴリ選択 → パーソナライズへ段階的移行 |
| オフライン学習 | モデルの学習はバッチ処理、推論はリアルタイム |
チェックリスト
- 3つのレコメンデーションアプローチを比較できた
- 2ステージアーキテクチャの設計を理解した
- コールドスタート問題の対処法を把握した
- オフラインとオンラインの役割分担を理解した
次のステップへ
次は「データパイプラインの設計」を学びます。大量のデータを収集・変換・格納するETL/ELTパイプラインの設計を掘り下げます。
推定読了時間: 30分