电商推荐系统从0到1:协同过滤+深度学习双塔模型的架构演进与实时排序实践

一、 从"搜不到"到"推不准",再到"比你更懂你"

讲一个我们踩过的坑。

我们的电商平台上线第一年,搜索功能尚不完善,用户找不到商品,于是我们决定上"推荐"------在首页、购物车底部、订单完成页展示"猜你喜欢"。

第一版推荐系统极其简单:最近30天卖得最好的商品,按销量排序展示。

结果呢?

女性用户看到了"男士剃须刀"。

已经买过iPhone的用户又看到"iPhone推荐"。

母婴用户看到了"电竞键盘"。

点击率?不到2%。用户吐槽:"这推荐系统是随便乱推的吧?"

更严重的是,我们尝试用"热门商品"逻辑做首页推荐,导致头部商品越推越爆,长尾商品永远没有曝光机会------商家开始抱怨"我们小商家没活路了"。

那一刻我们意识到:推荐不是"把卖得好的推给所有人",而是"把最可能买的商品推给最需要的人"。 这背后是算法、工程、产品的综合博弈。

本文将完整复盘我们从热门商品推荐 → 协同过滤 → 深度学习双塔模型的演进之路,重点分享算法选型、架构设计以及在线实时排序的实战经验。

二、 什么是"推荐系统"?为什么要做?

2.1 推荐系统的核心公式

text

推荐 = 用户画像 × 商品画像 × 匹配算法 × 场景上下文

用户画像:你是谁?(年龄、性别、偏好、历史行为)

商品画像:这是什么?(类目、品牌、价格、属性、描述)

匹配算法:怎么判断"你"和"它"是否匹配?(协同过滤、深度学习、多路召回)

场景上下文:现在是什么场景?(首页、购物车、订单完成页、节日大促)

2.2 推荐的价值

维度 无推荐 有推荐(优化后) 提升幅度

首页转化率 3% 12% 提升 4倍

客单价 120元 168元 提升 40%

用户停留时长 1.8分钟 5.2分钟 提升 189%

长尾商品曝光率 8% 37% 提升 362%

本质:推荐系统是电商平台从"人找货"到"货找人"的引擎。

三、 技术选型:推荐系统的主流架构

我们对比了行业主流方案:

方案 适用场景 优点 缺点 结论

热门推荐(Most Popular) 冷启动阶段 简单易实现 个性化差、马太效应严重 ❌ 被淘汰

协同过滤(CF) 用户行为数据丰富 召回效果好、实现中等 冷启动问题、稀疏性问题 ✅ V2.0采用

深度学习(双塔/DIN) 海量数据和算力 精度高、可处理多模态特征 工程复杂、算力成本高 ✅ V3.0采用

图神经网络(GNN) 社交关系丰富 挖掘高阶关系 技术前沿、工程不成熟 🔮 未来探索

最终演进路径:

V1.0(上线1-3月):热门推荐 + 人工规则(快速上线)

V2.0(上线3-12月):协同过滤(ItemCF + UserCF,提升个性化)

V3.0(上线12月+):双塔模型(DSSM)+ 实时排序(XGBoost/LightGBM)

四、 V2.0:协同过滤(Collaborative Filtering)

协同过滤是推荐系统的"经典算法",核心思想:"物以类聚,人以群分"。

4.1 ItemCF(基于物品的协同过滤)

核心逻辑:用户A买了商品X,那么和X"相似"的商品Y也应该推荐给A。

实现步骤:

text

Step 1: 构建用户-物品倒排表

用户1: 商品A, 商品B, 商品C

用户2: 商品A, 商品C, 商品D

用户3: 商品B, 商品C, 商品E

Step 2: 计算物品相似度矩阵

sim(A, C) = 2/3 ≈ 0.67(A和C同时被2个用户购买过,总用户3个)

sim(A, B) = 1/3 ≈ 0.33

sim(B, C) = 2/3 ≈ 0.67

Step 3: 为用户生成推荐

用户3购买过B, C, E

→ 推荐与B最相似的商品:A(sim=0.33)

→ 推荐与C最相似的商品:A(sim=0.67), D(sim=0.33)

→ 最终排序:A(0.67+0.33=1.0), D(0.33)

核心代码(Spark实现):

scala

// 1. 读取用户行为数据(点击/加购/下单/收藏)

val userItemDF = spark.read.parquet("user_behavior.parquet")

.select("user_id", "item_id", "behavior_type", "behavior_time")

// 2. 行为加权:下单=5分,加购=3分,收藏=2分,点击=1分

val weightedDF = userItemDF.withColumn("weight",

when("behaviortype"==="buy",5).when("behavior_type" === "buy", 5) .when("behaviortype"==="buy",5).when("behavior_type" === "cart", 3)

.when($"behavior_type" === "favor", 2)

.otherwise(1))

// 3. 计算用户-物品评分矩阵

val userItemScore = weightedDF.groupBy("user_id", "item_id")

.agg(sum("weight").as("score"))

// 4. 计算物品相似度(余弦相似度)

val itemSimilarity = userItemScore

.rdd.map(row => (row.getLong(1), (row.getLong(0), row.getDouble(2))))

.groupByKey()

.flatMap { case (itemId, userScores) =>

// 构建向量

val vector = userScores.map { case (userId, score) => (userId, score) }.toMap

// 计算相似度(需要两个物品共同评分用户数>阈值)

// 实际实现使用ALS或Spark MLlib的相似度计算

}

4.2 协同过滤的"三大天坑"

问题 现象 解决方案

冷启动 新用户无历史行为,新商品无用户评分 新用户→热门推荐;新商品→内容特征(类目/品牌/价格)匹配

稀疏性 用户-物品矩阵99%为空,相似度计算不准 矩阵分解(ALS/SVD),将高维矩阵降维到低维隐向量

流行度偏差 热门商品相似度都很高,冷门商品被忽略 引入流行度惩罚因子,对热门商品的相似度打折扣

4.3 ALS(交替最小二乘法)------解决稀疏性问题

python

from pyspark.ml.recommendation import ALS

from pyspark.ml.evaluation import RegressionEvaluator

ALS模型训练

als = ALS(

maxIter=10,

rank=50, # 隐向量维度

regParam=0.01, # 正则化参数

userCol="user_id",

itemCol="item_id",

ratingCol="score",

coldStartStrategy="drop"

)

model = als.fit(trainData)

为每个用户推荐Top10商品

userRecs = model.recommendForAllUsers(10)

效果数据:

指标 热门推荐 协同过滤(CF) 提升幅度

点击率(CTR) 2.1% 6.8% 提升 224%

转化率(CVR) 0.8% 3.2% 提升 300%

用户人均曝光商品数 12个 34个 提升 183%

长尾商品曝光 8% 29% 提升 262%

五、 V3.0:深度学习双塔模型(DSSM)

协同过滤虽好,但有两个根本局限:

只用行为数据,无法利用商品属性、文本描述、图片等丰富信息。

无法做实时个性化,模型训练全量更新耗时。

于是我们引入业界主流的双塔模型(Two-Tower) 架构。

5.1 双塔模型架构

text

相似度计算

(内积 / 余弦相似度)

/

/

用户塔(User Tower)\] \[物品塔(Item Tower)

| |

MLP多层神经网络\] \[MLP多层神经网络

| |

用户特征\] \[商品特征

├─ 基础画像:性别/年龄/地域 ├─ 基础属性:类目/品牌/价格

├─ 行为序列:最近浏览/加购 ├─ 文本特征:标题/描述(BERT向量)

├─ 统计特征:近7天/30天偏好 ├─ 图像特征:商品主图(CNN向量)

└─ 场景特征:时间/设备/渠道 └─ 统计特征:点击率/转化率

5.2 用户塔(User Tower)实现(TensorFlow)

python

import tensorflow as tf

from tensorflow.keras import layers, Model

def build_user_tower(user_feature_columns):

"""构建用户塔"""

1. 输入层

inputs = {}

for col in user_feature_columns:

inputscol.name = layers.Input(shape=(1,), name=col.name)

复制代码
# 2. 类别特征嵌入
embeddings = []
for col in user_feature_columns:
    if col.type == 'categorical':
        embedding = layers.Embedding(col.vocab_size, col.embed_dim)(inputs[col.name])
        embedding = layers.Flatten()(embedding)
        embeddings.append(embedding)
    else:  # 数值特征
        numeric = layers.Dense(1)(inputs[col.name])
        embeddings.append(numeric)

# 3. 拼接所有特征
concat = layers.Concatenate()(embeddings)

# 4. MLP层(多层神经网络)
x = layers.Dense(256, activation='relu')(concat)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.2)(x)

x = layers.Dense(128, activation='relu')(x)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.2)(x)

x = layers.Dense(64, activation='relu')(x)   # 最终用户向量维度=64

return Model(inputs=inputs, outputs=x)

构建物品塔(结构类似)

def build_item_tower(item_feature_columns):

... 类似结构,输出64维向量

pass

5.3 模型训练:双塔联合训练

python

1. 构建双塔模型

user_tower = build_user_tower(user_features)

item_tower = build_item_tower(item_features)

2. 用户塔输入

user_inputs = user_tower.input

user_embedding = user_tower.output

3. 物品塔输入

item_inputs = item_tower.input

item_embedding = item_tower.output

4. 计算相似度(内积)

similarity = layers.Dot(axes=1, normalize=True)(user_embedding, item_embedding)

5. 输出层(二分类:点击/未点击)

output = layers.Dense(1, activation='sigmoid')(similarity)

6. 完整模型

model = Model(inputs=user_inputs + item_inputs, outputs=output)

model.compile(optimizer='adam', loss='binary_crossentropy', metrics='AUC')

7. 训练

model.fit(

x=user_data, item_data,

y=labels,

epochs=10,

batch_size=512,

validation_split=0.2,

callbacks=[

tf.keras.callbacks.EarlyStopping(patience=3),

tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=2)

]

)

5.4 在线推理架构

text

用户请求推荐

特征工程服务 (实时拼接用户特征 + 场景特征)

用户向量计算 (从Redis缓存读取,或调用用户塔实时计算)

向量检索 (Faiss/Elasticsearch向量插件,从亿级商品中召回Top1000)

精排阶段 (LightGBM/XGBoost模型,对1000个候选商品打分排序)

业务重排 (多样性、新鲜度、商业化等业务规则干预)

返回Top20给用户

关键组件选型:

组件 选型 说明

特征存储 Redis + Feature Store 用户实时特征缓存,毫秒级读取

向量检索 Faiss(Facebook开源) 亿级向量检索,延迟<10ms

精排模型 LightGBM 树模型,可解释性好,训练快

模型部署 TensorFlow Serving 支持GPU推理,高吞吐

AB实验 自研AB平台 支持多组实验并行

六、 多路召回:不只是双塔

双塔模型只是"召回"的一种方式。为了提升召回覆盖率和多样性,我们采用多路召回策略:

text

用户请求

├── 路线1:双塔模型召回(个性化,500个)

├── 路线2:协同过滤召回(相似兴趣,200个)

├── 路线3:热门商品召回(兜底,100个)

├── 路线4:实时行为召回(浏览加购,200个)

│ └─ 用户最近浏览的同类商品

├── 路线5:场景化召回(节日/天气,100个)

│ └─ "夏日防晒"、"冬季保暖"等场景

└── 路线6:补充召回(新品/长尾,100个)

└─ 保证新商品有机会曝光

合并去重\] → \[精排\] → \[重排\] → \[返回Top20

各路线优势互补:

召回路线 覆盖率 精准度 多样性 实时性

双塔模型 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐

协同过滤 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐

热门商品 ⭐ ⭐⭐ ⭐ ⭐⭐⭐⭐⭐

实时行为 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐

场景化 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐

七、 实时排序:从"离线批量"到"毫秒级个性化"

召回之后,需要对候选商品精排------决定最终展示顺序。

7.1 离线排序(我们踩过的坑)

最初,我们每天凌晨用离线数据训练一次排序模型(XGBoost),白天用这个模型进行预测。

问题:

用户早上浏览了"球鞋",下午模型还是推荐"衬衫"------无法实时捕捉用户兴趣变化。

大促期间用户行为剧烈变化,离线模型严重滞后。

7.2 实时排序方案

核心思路:实时特征 + 在线模型 + 动态权重

java

@Service

public class RealtimeRankingService {

复制代码
public List<Item> rank(List<Item> candidates, User user) {
    // 1. 提取实时特征(毫秒级)
    Map<String, Double> features = extractRealtimeFeatures(user, candidates);
    
    // 2. 基础分:用在线模型(LightGBM)打分
    Map<Long, Double> baseScores = lgbModel.predict(candidates, features);
    
    // 3. 动态权重调整(根据实时行为)
    // 用户刚刚点击了"跑步鞋",提高"跑步鞋"类目的权重
    String recentCategory = user.getRecentClickCategory();
    if (recentCategory != null) {
        for (Item item : candidates) {
            if (item.getCategory().equals(recentCategory)) {
                baseScores.put(item.getId(), baseScores.get(item.getId()) * 1.5);
            }
        }
    }
    
    // 4. 多样性惩罚(避免推荐太相似的商品)
    Set<String> seenCategories = new HashSet<>();
    for (Item item : candidates) {
        String category = item.getCategory();
        if (seenCategories.contains(category)) {
            baseScores.put(item.getId(), baseScores.get(item.getId()) * 0.8);  // 降权
        }
        seenCategories.add(category);
    }
    
    // 5. 排序输出
    return candidates.stream()
        .sorted((a, b) -> Double.compare(baseScores.get(b.getId()), baseScores.get(a.getId())))
        .limit(20)
        .collect(Collectors.toList());
}

}

7.3 实时特征工程(Redis存储)

java

// 用户实时行为特征(存储在Redis中,TTL=1小时)

public class RealtimeFeatureService {

复制代码
public UserRealtimeFeatures getFeatures(Long userId) {
    String key = "user:realtime:" + userId;
    Map<String, String> data = redis.hgetAll(key);
    
    return UserRealtimeFeatures.builder()
        .recentViewItems(parseIds(data.get("views")))      // 最近浏览的10个商品
        .recentCartItems(parseIds(data.get("carts")))      // 最近加购的商品
        .recentCategory(data.get("last_category"))         // 最后浏览的类目
        .recentBrand(data.get("last_brand"))               // 最后浏览的品牌
        .avgPrice(Double.parseDouble(data.get("avg_price"))) // 平均浏览价格带
        .sessionTime(Long.parseLong(data.get("session_time"))) // 当前会话时长
        .build();
}

// 每次用户行为时更新Redis
public void updateRealtime(Long userId, Behavior behavior) {
    String key = "user:realtime:" + userId;
    redis.expire(key, 3600);  // 设置1小时过期
    
    // 更新最近浏览列表(只保留最新的10个)
    redis.lpush(key + ":views", behavior.getItemId());
    redis.ltrim(key + ":views", 0, 9);
    
    // 更新最近类目
    redis.set(key + ":last_category", behavior.getCategory());
    
    // 更新平均价格(滑动窗口)
    redis.incrByFloat(key + ":total_price", behavior.getPrice());
    redis.incr(key + ":price_count");
    double avgPrice = redis.get(key + ":total_price") / redis.get(key + ":price_count");
    redis.set(key + ":avg_price", String.valueOf(avgPrice));
}

}

八、 业务规则与重排:推荐系统的"最后一道防线"

算法推荐的结果,必须经过业务规则的修正,否则会出现各种奇葩问题。

8.1 常见的业务规则

规则 说明 实现方式

去重 同一商品不能连续出现 推荐结果去重

多样性 同一类目最多占30% 类目比例控制

新鲜度 新商品必须有曝光机会 新品加权(boost=1.5)

时效性 过季商品不推荐 季节性过滤

商业化 付费商品优先展示 广告位固定插入

库存过滤 缺货商品不推荐 实时库存校验

价格带 不推荐远超用户历史价格带的商品 价格过滤(±50%)

8.2 重排实现

java

public List rerank(List items, User user) {

List result = new ArrayList<>();

Set seenIds = new HashSet<>();

Map<String, Integer> categoryCount = new HashMap<>();

复制代码
for (Item item : items) {
    // 1. 去重
    if (seenIds.contains(item.getId())) {
        continue;
    }
    
    // 2. 多样性控制(同一类目最多3个)
    String category = item.getCategory();
    if (categoryCount.getOrDefault(category, 0) >= 3) {
        continue;
    }
    
    // 3. 价格过滤(不超过用户历史平均价格的2倍)
    if (item.getPrice() > user.getAvgPrice() * 2) {
        continue;  // 太贵了,不推荐
    }
    
    // 4. 库存过滤
    if (!inStock(item.getId())) {
        continue;
    }
    
    // 通过所有规则
    result.add(item);
    seenIds.add(item.getId());
    categoryCount.put(category, categoryCount.getOrDefault(category, 0) + 1);
    
    if (result.size() >= 20) {
        break;
    }
}

// 如果结果不足20个,从备选池中补充(放宽规则)
if (result.size() < 20) {
    result.addAll(fallbackItems(user, 20 - result.size()));
}

return result;

}

九、 踩坑实录:推荐系统生产环境的"四大天坑"

坑1:特征穿越(Feature Leakage)

问题:训练模型时使用了未来数据(如"商品在7天后的销量"作为特征),模型离线AUC很高(0.85),上线后CTR反而下降。

原因:训练时特征中包含了未来信息,模型学会了"作弊",上线时无法获取未来信息,预测不准。

解决方案:

所有特征必须来自预测时刻之前的数据。

特征时间戳必须严格< label时间戳。

建立特征血缘追踪系统,自动检测特征穿越。

坑2:推荐结果"同质化"

问题:用户看了"耐克跑鞋",推荐结果全是"耐克跑鞋",用户失去兴趣。

原因:多样性约束不足。

解决方案:

在重排阶段引入MMR(最大边际相关性) 算法,平衡相关性和多样性。

硬性约束:同一类目不超过30%,同一品牌不超过20%。

坑3:模型线上与线下效果不一致

问题:离线AUC=0.82,上线CTR只比随机高一点点。

原因:

离线数据采样偏差(只用了点击数据,忽视了曝光未点击的负样本)。

线上特征获取不到或延迟。

解决方案:

训练数据必须包含曝光未点击的负样本。

特征实时性和完整性监控,缺失率超过5%就告警。

坑4:新用户冷启动

问题:新用户没有任何历史行为,推荐系统无法个性化。

解决方案(多级策略):

text

新用户注册

├─ Step1: 兴趣选择(注册时让用户选择3-5个兴趣标签)

│ └─ 按标签匹配推荐商品

├─ Step2: 热门商品(前3次曝光)

│ └─ 展示平台热门商品

├─ Step3: 实时学习(第4次开始)

│ └─ 根据用户点击行为实时调整推荐

└─ Step4: 探索与利用(EE策略)

└─ 20%流量探索新品类,80%流量利用已有知识

十、 最终成果与AB实验数据

经过三个版本的迭代,最终成果如下:

指标 V1.0 热门推荐 V2.0 协同过滤 V3.0 双塔+实时排序 总提升

首页点击率(CTR) 2.1% 6.8% 11.2% 提升 433%

转化率(CVR) 0.8% 3.2% 5.6% 提升 600%

人均浏览商品数 8个 16个 28个 提升 250%

用户停留时长 1.8分钟 3.2分钟 6.1分钟 提升 239%

长尾商品曝光率 8% 29% 43% 提升 438%

推荐带来的GMV占比 5% 18% 35% 提升 600%

用户满意度(NPS) 32 48 72 提升 125%

AB实验数据(双塔模型 VS 协同过滤):

实验组 CTR CVR 人均GMV 结论

对照组(协同过滤) 6.8% 3.2% 45.2元 基线

实验组(双塔模型) 9.4% 4.7% 63.8元 ✅ 胜出,提升显著

实验组(双塔+实时排序) 11.2% 5.6% 78.5元 ✅ 胜出,进一步提升

十一、 总结与未来规划

11.1 核心经验总结

经验 具体内容

分阶段推进 不要一上来就搞深度学习。先热门→协同过滤→双塔,循序渐进,每个阶段都有业务价值

召回是基础 召回决定了推荐的天花板。多路召回比单路召回效果好得多

实时性是关键 用户兴趣变化很快,离线模型无法捕捉实时偏好。实时特征+在线模型是方向

业务规则不可少 纯算法的推荐结果经常"离谱",需要业务规则做最后把关

AB实验是唯一标准 离线指标只是参考,CTR/CVR/GMV才是硬道理。持续做AB实验

11.2 未来规划

多模态融合:将商品图片特征(CNN)和文本特征(BERT)融入双塔模型,提升新品推荐质量。

强化学习(RL):引入Contextual Bandit算法,在"探索"和"利用"之间找到最优平衡。

图神经网络(GNN):构建用户-商品-品牌-类目的异构图网络,挖掘高阶关系。

可解释推荐:为用户展示"为什么推荐这个商品",提升用户信任度。

多目标优化:不只是优化CTR,同时优化CVR、停留时长、多样性等多个目标。

文末互动:

你们的推荐系统用的是什么算法?协同过滤、双塔还是更先进的模型?推荐系统的"翻车"经历有哪些(比如给男生推荐了姨妈巾)?欢迎在评论区分享,一起打造更好的推荐引擎!

相关推荐
乔粒科研工坊1 小时前
文献解读 |“三虚拟”上线- AI 虚拟细胞爆火-综述
人工智能·文献阅读·虚拟细胞·文献分享·虚拟敲除
jinxindeep1 小时前
LingBot-Vision:以边界为中心的视觉预训练新范式,驱动密集空间感知与深度估计新突破
人工智能
Java编程爱好者2 小时前
一种适合程序员的 Agent 协作方式的实践
人工智能
甜味弥漫2 小时前
从痛点出发,彻底搞懂 RAG:为什么 AI 不会"查资料",以及如何让大模型拥有企业知识库
人工智能
卡梅德生物科技小能手2 小时前
卡美德生物科普MMP9(基质金属蛋白酶9):细胞外基质重塑的关键调节因子
经验分享·深度学习·生活
甜味弥漫2 小时前
MCP 是什么?为什么 AI Agent 需要 MCP?
人工智能
weixin_446260852 小时前
HumanForge:基于多智能体伪造推理的以人为中心深度伪造视频基准
人工智能·音视频
2401_859506242 小时前
玉石加工全链路技术变革:五大维度拆解材质改性、精密雕刻与智能溯源
大数据·人工智能
神奇小汤圆2 小时前
大模型微调界的“全自动工厂”:进来时是基座模型,出去时是AI专家
人工智能