AI+电商:智能推荐系统实战

灵阙学院 | 行业 AI 系列


引言:你以为你在"逛",其实你在被"导航"

打开某电商 App,首页瀑布流里出现一双跑鞋——你昨天刚在社交平台聊过跑步。你没搜索、没收藏,但推荐系统已经"知道"了。点进去看了 30 秒没下单,退出后发现"猜你喜欢"里出现了三双不同价位的替代款。

这不是巧合。这是推荐系统在毫秒级完成了"召回 -> 粗排 -> 精排 -> 重排"的四阶段管道。2025 年的头部电商,推荐系统贡献超过 40% 的 GMV。在信息过载的时代,推荐系统是连接"人"和"货"最高效的桥梁。

但搭建一个工业级推荐系统,远比在 Jupyter Notebook 里跑一个 collaborative filtering 复杂得多。本文从架构到算法到工程,拆解电商推荐系统的完整技术栈。


一、推荐系统全局架构

┌────────────────────────────────────────────────────────────────┐
│                   电商推荐系统四阶段管道                         │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  全量商品池 (千万级)                                            │
│       |                                                        │
│  +--------+   目标:从千万中选出千级候选                         │
│  | 召回层  |   多路并行:协同过滤 / 向量检索 / 热门 / 规则       │
│  | Recall |   延迟预算:< 50ms                                 │
│  +---+----+                                                    │
│      |  ~1000 候选                                             │
│  +---v------+  目标:轻量排序,筛到百级                         │
│  | 粗排层    |  模型:双塔 / 浅层 NN                            │
│  | Pre-rank |  延迟预算:< 20ms                                │
│  +---+------+                                                  │
│      |  ~100 候选                                              │
│  +---v------+  目标:精确预估 CTR/CVR/GMV                      │
│  | 精排层    |  模型:DIN / DIEN / DCN-V2 / 多目标             │
│  | Ranking  |  延迟预算:< 50ms                                │
│  +---+------+                                                  │
│      |  ~30 候选                                               │
│  +---v------+  目标:多样性、新鲜度、商业策略                   │
│  | 重排层    |  逻辑:MMR 去重 / 类目打散 / 运营插入            │
│  | Re-rank  |  延迟预算:< 10ms                                │
│  +---+------+                                                  │
│      |                                                         │
│  最终展示 (~10-20 items per page)                              │
└────────────────────────────────────────────────────────────────┘

1.1 为什么需要四阶段?

核心矛盾:精度与延迟的 trade-off

阶段 候选量 模型复杂度 延迟
召回 千万 -> 千 极低(向量内积) < 50ms
粗排 千 -> 百 低(双塔/浅层) < 20ms
精排 百 -> 30 高(深度交叉网络) < 50ms
重排 30 -> 10-20 规则+轻量模型 < 10ms

如果直接用精排模型扫千万商品,延迟会到分钟级。四阶段管道是工程和算法的平衡点。


二、召回层:多路并行

2.1 召回策略矩阵

召回路 原理 优势 劣势
ItemCF 买了 A 的人也买了 B 可解释、冷启快 热门偏差
UserCF 相似用户喜欢什么 发现潜在兴趣 稀疏性
向量召回 User/Item 向量近邻 泛化强 需大量数据
热门召回 全局/类目 TopN 保底兜底 无个性化
标签召回 用户兴趣标签匹配 可控性强 依赖标签体系
图召回 用户-商品二部图游走 高阶关系 计算开销大

2.2 双塔模型实现

"""
双塔模型 (Two-Tower Model)
训练后分别导出 user_embedding 和 item_embedding
在线用 Faiss/Milvus 做 ANN 检索
"""
import torch
import torch.nn as nn
import torch.nn.functional as F


class UserTower(nn.Module):
    """用户塔:编码用户特征为固定维度向量"""

    def __init__(self, num_users: int, num_categories: int, dim: int = 64):
        super().__init__()
        self.user_emb = nn.Embedding(num_users, dim)
        self.age_fc = nn.Linear(1, 16)
        self.gender_emb = nn.Embedding(3, 8)
        self.cat_emb = nn.Embedding(num_categories, 32)
        self.fc = nn.Sequential(
            nn.Linear(dim + 16 + 8 + 32, 128),
            nn.ReLU(),
            nn.Linear(128, dim),
        )

    def forward(self, user_id, age, gender, hist_categories):
        u = self.user_emb(user_id)
        a = self.age_fc(age.unsqueeze(-1).float())
        g = self.gender_emb(gender)
        h = self.cat_emb(hist_categories).mean(dim=1)  # mean pooling
        x = torch.cat([u, a, g, h], dim=-1)
        return F.normalize(self.fc(x), dim=-1)


class ItemTower(nn.Module):
    """商品塔:编码商品特征为固定维度向量"""

    def __init__(self, num_items: int, num_cats: int, num_brands: int, dim: int = 64):
        super().__init__()
        self.item_emb = nn.Embedding(num_items, dim)
        self.cat_emb = nn.Embedding(num_cats, 32)
        self.brand_emb = nn.Embedding(num_brands, 16)
        self.price_fc = nn.Linear(1, 16)
        self.fc = nn.Sequential(
            nn.Linear(dim + 32 + 16 + 16, 128),
            nn.ReLU(),
            nn.Linear(128, dim),
        )

    def forward(self, item_id, category, brand, price):
        i = self.item_emb(item_id)
        c = self.cat_emb(category)
        b = self.brand_emb(brand)
        p = self.price_fc(price.unsqueeze(-1).float())
        x = torch.cat([i, c, b, p], dim=-1)
        return F.normalize(self.fc(x), dim=-1)


class TwoTowerModel(nn.Module):
    """训练时计算余弦相似度,推理时分别导出向量"""

    def __init__(self, user_tower, item_tower):
        super().__init__()
        self.user_tower = user_tower
        self.item_tower = item_tower
        self.temperature = nn.Parameter(torch.tensor(0.07))

    def forward(self, user_feats, item_feats):
        u_vec = self.user_tower(**user_feats)
        i_vec = self.item_tower(**item_feats)
        return torch.matmul(u_vec, i_vec.T) / self.temperature.exp()

2.3 ANN 检索:毫秒级从千万中找到 Top-K

"""使用 Faiss 构建 ANN 索引,线上 < 5ms 召回"""
import faiss
import numpy as np


def build_index(embeddings: dict[int, np.ndarray], dim: int = 64):
    """构建 IVF-PQ 索引,支持千万级商品"""
    ids = list(embeddings.keys())
    vecs = np.stack([embeddings[i] for i in ids]).astype("float32")

    quantizer = faiss.IndexFlatIP(dim)
    index = faiss.IndexIVFPQ(quantizer, dim, 256, 8, 8)
    index.train(vecs)
    index.add(vecs)
    index.nprobe = 32
    return index, ids


def recall_topk(index, ids, user_vec, k=200):
    """在线召回 top-K"""
    scores, indices = index.search(user_vec.reshape(1, -1).astype("float32"), k)
    return [(ids[idx], float(s)) for idx, s in zip(indices[0], scores[0]) if idx >= 0]

三、精排层:CTR/CVR 多目标预估

3.1 工业常用模型对比

模型 核心思想 优势 复杂度
DeepFM FM + DNN 并行 低阶+高阶特征交叉
DIN 注意力加权用户历史 捕捉与候选相关的行为 中高
DIEN 序列建模用户兴趣演化 理解兴趣变迁
DCN-V2 显式交叉网络 自动特征交叉
MMOE 多门混合专家 多目标优化

3.2 DIN 注意力层

class DINAttention(nn.Module):
    """
    DIN 核心创新:不同目标商品,用户历史中相关行为权重不同。
    看过的鞋子对当前推荐鞋子更有参考价值。
    """

    def __init__(self, dim: int):
        super().__init__()
        self.attn = nn.Sequential(
            nn.Linear(dim * 4, 64), nn.ReLU(), nn.Linear(64, 1),
        )

    def forward(self, target, history, mask):
        """
        target: (B, D)  候选商品向量
        history: (B, L, D)  用户历史行为序列
        mask: (B, L)  填充位置为 False
        """
        B, L, D = history.shape
        t = target.unsqueeze(1).expand(-1, L, -1)
        # 拼接: target, history, 差, 积
        inp = torch.cat([t, history, t - history, t * history], dim=-1)
        scores = self.attn(inp).squeeze(-1)  # (B, L)
        scores = scores.masked_fill(~mask.bool(), float("-inf"))
        weights = torch.softmax(scores, dim=-1)
        return torch.bmm(weights.unsqueeze(1), history).squeeze(1)

3.3 多目标融合

最终得分 = w1 * pCTR + w2 * pCVR + w3 * pGMV + w4 * freshness + w5 * diversity

其中:
  pCTR  = 预估点击率
  pCVR  = 预估转化率
  pGMV  = 预估客单价 * pCVR
  freshness = 商品上架时间衰减
  diversity = 与已展示商品的类目差异度

权重配比是一门艺术:偏重 pGMV 导致高价霸屏,偏重 pCTR 导致标题党泛滥。通常需要持续 A/B 测试调优。


四、冷启动:推荐系统的阿喀琉斯之踵

4.1 三种冷启动场景

类型 场景 策略
用户冷启动 新注册用户 引导选兴趣 + 热门兜底 + 探索-利用
商品冷启动 新上架商品 内容特征召回 + 流量扶持 + 类似嫁接
系统冷启动 新平台 规则策略 + 热门排行 + 快速积累行为

4.2 新用户首屏策略

新用户首次打开 App:

  Step 1: 引导页收集 3-5 个兴趣标签
  Step 2: 基于标签 + 人口属性初始化画像
  Step 3: 首屏混合策略
           40% 基于初始画像的个性化推荐
           30% 全站热门(保底)
           20% Exploration(随机探索不同品类)
           10% 新品 / 运营活动
  Step 4: 实时根据点击/停留行为更新画像
  Step 5: 第 5 次打开后切换到正常推荐

4.3 新商品冷启动:内容理解

def cold_start_embedding(
    title: str, image_url: str, attributes: dict,
    text_encoder, image_encoder,
) -> np.ndarray:
    """
    新商品无行为数据,但有标题/图片/属性。
    融合多模态特征,映射到与热商品相同的向量空间。
    """
    text_vec = text_encoder.encode(title)
    img_vec = image_encoder.encode(image_url)
    attr_vec = encode_attributes(attributes)

    combined = np.concatenate([
        0.4 * text_vec, 0.3 * img_vec, 0.3 * attr_vec,
    ])
    return combined / np.linalg.norm(combined)

五、实时推荐:从 T+1 到毫秒级

5.1 实时特征管道

用户行为事件流 (Kafka)
      |
  +---v---+
  | Flink |  实时计算:
  | 流处理 |  - 最近 30min 点击品类分布
  +---+---+  - 实时 Session 兴趣向量
      |       - 价格敏感度变化
  +---v---+
  | Redis |  存储实时特征
  | 特征库|  TTL = 30min ~ 24h
  +---+---+
      |
  +---v----+
  | 推理    |  合并离线 + 实时特征
  | 服务    |  -> 精排模型推理
  +--------+

5.2 Session-based 推荐模型选择

方法 原理 延迟 适用场景
Item-KNN (Session) 当前点击物品的近邻 极低 未登录用户
GRU4Rec GRU 编码点击序列 短 Session
SASRec Self-Attention 编码 中高 中等 Session
BERT4Rec 双向 Transformer 长 Session

六、A/B 测试框架

推荐系统的每次迭代都必须经过线上实验验证。

6.1 分流与指标

用户请求
    |
  +-v----------+
  | 分流网关    |  user_id hash 分配实验组
  +--+-----+---+
     |     |
   实验A  实验B    指标收集 -> 数据仓库 -> 统计检验
   新模型  旧模型

6.2 关键指标

指标 定义 重要性
CTR 点击 / 曝光 核心
CVR 下单 / 点击 核心
GMV/UV 人均成交额 核心
多样性 (ILS) 推荐列表品类覆盖率 体验
新鲜度 新商品曝光占比 生态
覆盖率 被推出的商品占总商品比 生态

6.3 统计显著性检查清单

  1. 样本量充足:Power Analysis 计算最小样本量
  2. 统计显著:p-value < 0.05 且置信区间不跨零
  3. 业务显著:CTR 提升 0.01% 不值得上线
  4. 观察期:至少覆盖完整周期(通常 7 天以上)
  5. SRM 检查:Sample Ratio Mismatch 检测分流是否均匀

七、常见错误与避坑指南

错误 后果 正确做法
离线好就上线 线上效果可能反降 离线只做筛选,A/B 定生死
召回只有一路 推荐同质化 至少 3-5 路并行
特征穿越 训练用了未来信息 严格按时间切分
忽略位置偏差 第一位天然 CTR 高 训练时加 position feature
只优化 CTR 标题党泛滥 多目标 + 长期留存指标
推荐全是同类 "推荐不懂我" MMR + 类目打散 + 多样性约束
冷启动硬编码 维护成本高 模型化的冷启动策略
没有降级方案 模型挂了就白屏 热门/规则作为降级兜底
不监控特征漂移 性能无声衰退 特征分布自动对比 + 告警

八、系统可观测性

8.1 监控分层

秒级实时:
  - 推理延迟 P50/P99
  - QPS / 错误率
  - 特征缺失率

小时级:
  - 各路召回命中率
  - CTR/CVR 趋势
  - 热门商品集中度

日级:
  - 推荐覆盖率(长尾有没有被推出去)
  - 新用户留存与推荐相关性
  - 模型特征漂移检测

8.2 告警规则

指标 阈值 动作
P99 延迟 > 200ms 持续 5 分钟 自动降级到缓存策略
错误率 > 1% 持续 1 分钟 告警 + 自动回滚模型版本
CTR 日环比降 > 20% 持续 24 小时 人工排查
召回结果为空 单次 降级到热门兜底

九、总结

电商推荐系统是"数据飞轮"的典型案例:更好的推荐 -> 更多点击/购买 -> 更多行为数据 -> 更好的模型 -> 更好的推荐。但飞轮转起来之前,你需要扎实的工程基础。

三条核心建议:

  1. 架构先行:四阶段管道是骨架,先把数据流跑通再优化算法
  2. 多路召回:不要把赌注押在单一召回策略上
  3. A/B 测试文化:每一次模型迭代都必须有可衡量的线上验证

Maurice | maurice_wen@proton.me