news 2026/10/20 5:34:23

手写Python协同过滤:在线购物系统商品推荐实战全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写Python协同过滤:在线购物系统商品推荐实战全解

1. 没有商品-用户矩阵,协同过滤就无法落地:先理清数据底座

做在线购物系统的商品推荐,很多人第一反应是去套一个算法库,跑通别人给的示例数据,再把模型塞进 Web 服务里。真上线后才发现,推荐结果要么是空的,要么推荐出来的商品用户完全不感兴趣,新商品永远排不上去。问题几乎都出在源头:你没有把“谁对什么商品产生了什么行为”这件事,整理成基于协同过滤(collaborative filtering)的推荐算法真正能吃的输入。

协同过滤说白了就是“物以类聚,人以群分”。它不依赖商品详情、用户画像这类内容标签,而是完全从行为数据里找规律。同一个在线购物系统里,用户 A 买过商品 X,用户 B 也买过商品 X,那么 A 和 B 的偏好就有重叠;反过来,商品 X 和商品 Y 经常被同一批人买走,那这两个商品就是隐式关联。算法的输入只有一张表:用户 ID、商品 ID、行为数值。这一张表就是常说的商品-用户交互矩阵,每一行是一个用户,每一列是一件商品,格子里的数字代表用户对该商品的反馈强度。

我这次做的项目,就是一个纯 Python 实现的、基于协同过滤的在线购物系统推荐模块。整体思路不复杂,但要把每个环节做扎实,涉及数据清洗、相似度计算、Top-N 推荐生成、离线训练与在线服务拆分、冷启动兜底、效果评估,链路比看着长得多。下面我把整个过程拆开讲,每一步都会给出为什么这么做,以及我实际跑项目中遇到的坑。

1.1 显式评分与隐式反馈,购物系统到底拿什么当“格子”

很多教材用电影评分数据集举例,用户会给电影打 1 到 5 星,矩阵里天然有数值。但绝大多数在线购物系统没有评分功能,用户留下的只是行为记录:搜索、浏览商品详情页、把商品加入购物车、提交订单、产生售后行为。这些统称隐式反馈(implicit feedback),和评分这种显式反馈(explicit feedback)相比,信号要弱,噪声更大,但胜在数据量巨大且采集没有门槛。

我把隐式反馈按强度做了等级映射,这是项目里第一个值得细说的决策:

行为类型偏好置信度矩阵中赋值备注
用户浏览过商品详情页弱1数据量大,但误点和误触很多
用户将商品加入购物车中2有明确购买意向,比较可靠
用户提交订单并完成支付强3最强的正反馈信号
用户多次购买同一商品强3 + 次数衰减权重回购在日用品类里特别关键
加购后超过 30 天未购买弱负向0.5说明价格或决策链路有问题,不单独做负向也能用

这里我不建议把“未购买”当作 0 塞进矩阵。在线购物平台的用户大多只是随便逛逛,大量未购买不代表不喜欢,它可能只是还没到决策时机。如果机械地把所有 0 都当成负反馈,协同过滤的结果会被严重扭曲。稍微好一点的做法是:对活跃用户保留浏览行为作为弱信号,对完全不活跃的新用户单独走冷启动策略。

赋值映射只是第一步。矩阵的最终形态,应当是商品为行、用户为列的稀疏矩阵,还是用户为行、商品为列的稀疏矩阵,取决于我后面选的相似度算法方向。这一点在第 2 节展开。

1.2 矩阵稀疏度是推荐效果的天花板

我最初用某个公开的电商脱敏数据集试跑,大概 5600 个用户、19000 个商品,非零行为记录有 32 万条。粗看数据量不错,但计算一下稀疏度:

稀疏度 = 1 - 非零格子数 / (用户数 × 商品数)

也就是 1 - 320000 / (5600 × 19000) ≈ 0.997,矩阵 99.7% 的格子是空的。这个数字在真实在线购物系统里只会更夸张。服饰、图书、3C 这类长尾商品类目,动辄几十万 SKU,普通用户买过的商品可能不到 50 个,稀疏程度经常超过 99.9%。

稀疏意味着两件事。第一,绝大多数用户之间的共同购买商品很少,用户两两之间的相似度会趋近于空集;第二,如果直接用稠密矩阵做相似度计算,内存会立刻爆炸。所以从一开始我就决定,所有矩阵运算都用稀疏矩阵结构来完成,计算相似度时不把空白格子物理展开。后面 3.2 节给出具体代码。

还有一个容易被忽略的点:交互数据的时间窗口。第 1.1 节里我把行为赋值为 2、3,但如果不做时间衰减,一个用户三个月前加购过、这周又加购过的商品,会被一视同仁。在实际项目中,我用了一个简单的时间折扣:行为发生距离当前天数记为 d,权重乘上 1 / log2(d + 2),让近期行为权重明显高于老旧行为。这个改动很小,却让相似度矩阵的时效性提升很多。

1.3 数据清洗里最容易被忽视的三个点

在线购物系统的原始行为日志,直接拿来建模一定会翻车。我整理出三个必须处理的点:

第一,同用户同商品同行为去重。用户一天之内可能反复进入同一个商品详情页十几次,如果不过滤,Python 里逐条处理会非常慢,建议按 user_id、item_id、behavior_type 分组,只保留每个分组内时间最新的一条,或者在赋值时按次数做一次非线性衰减。高频访问本身反映兴趣,但次数抖增的边际信号很小,权重给到 1 到 1.5 就足够。

第二,剔除异常行为用户。我见过有测试账号批量刷购物车,也有爬虫号把整个类目浏览了一遍。这类账号会把相似关系搅浑,让“买了 A 也会买 B”的规律变得不真实。处理方式是设置行为数量上下限:单用户活跃行为超过全站用户 99 分位的,直接剔除;行为数量小于 2 的用户也剔除,因为在协同过滤里他们无法参与相邻计算。

第三,商品维度要过滤无效 SKU。部分商品可能是赠品、补差价链接、线下提货码,这类商品没有真正意义上的“相似推荐价值”。我在代码里直接用商品属性表做一次白名单过滤,只保留状态正常、允许推荐的商品。别看这步简单,它能避免推荐接口把赠品链接返回给用户,属于直接影响线上体验的细节。

数据底座做扎实后,协同过滤才有讨论价值。下面进入算法主干。

2. 用户-用户协同过滤和商品-商品协同过滤,购物场景怎么选

协同过滤按比较方向分成两大类:用户-用户(User-Based)和商品-商品(Item-Based)。它们只是相似度计算的坐标系不同,但落到在线购物系统里,推荐效果和工程复杂度差异很大。

2.1 用户-用户协同过滤的核心逻辑

用户-用户协同过滤先找“和我行为画像相似的人”。假设用户 A 的历史行为是浏览了{手机、耳机、充电器},用户 B 的历史行为是{手机、耳机、支架},A 和 B 在商品集合上的行为向量方向接近,那么 A 可能也需要支架,B 可能也会对充电器感兴趣。

计算路径是:

  1. 把每个用户表示成对所有商品的偏好向量;
  2. 计算用户 A 与全量用户之间的相似度,比如余弦相似度或皮尔逊相关系数;
  3. 取相似度最高的 K 个用户作为邻居;
  4. 聚合邻居购买过而 A 没购买过的高频商品,按分数排序生成推荐。

用公式理解,用户 u 对商品 i 的预测偏好 score(u, i) 可以写成邻居对商品 i 的加权和:

score(u, i) = Σ (sim(u, v) × r(v, i)) / Σ sim(u, v)

其中 sim(u, v) 是用户 u 和 v 的相似度,r(v, i) 是用户 v 对商品 i 的行为值。代码实现不复杂,我自己第一版就写了这个。但放在大型在线购物系统里,它有一个致命问题:用户数量远大于商品数量。一个数十万用户的平台,每次推荐要对几十万用户两两计算相似度,矩阵规模是亿级,实时算不现实,必须离线预计算。而且用户偏好变化快,用户相似度矩阵的更新频率要求高。

另一个问题发生在真实购物数据里:大部分用户只买过几件商品,两个用户之间共同购买商品数量常常是 0。没有任何交集的两个向量的余弦相似度就是 0,结果就是大量用户找不到邻居,推荐退化为热销榜。

2.2 商品-商品协同过滤为什么更稳

商品-商品协同过滤反过来,它比较的是商品之间的行为相似性。如果商品 X 和商品 Y 同时出现在大量用户的购买列表或加购组合里,那这两个商品彼此就是相似的,用户买了 X,系统就推荐 Y。

这个方向在购物系统里明显更可靠,原因有三个。

第一,在线购物平台的商品数量虽然大,但相比用户数量还是小一个量级。商品之间的相似度矩阵更容易在离线任务里算完,也更容易缓存和更新。

第二,商品的关联关系变化比用户偏好慢。用户可能这周想买电子产品,下周想买运动装备,但“手机和耳机一起出现”这个规律长期稳定。所以商品相似度矩阵可以每天更新一次,工程压力小很多。

第三,推荐结果可解释性好。页面上可以理直气壮地写“买过这件商品的用户也买了下面这些”,用户很容易理解推荐逻辑,不像用户-用户协同过滤,解释成“和你相似的人买了”反而容易引起隐私联想。

所以在这个项目里,主线我选的是 Item-Based Collaborative Filtering。下文所有代码都围绕商品相似度矩阵展开。

2.3 相似度计算公式:余弦相似度与均值中心化

相似度计算的常见选择有三类:余弦相似度、皮尔逊相关系数、调整后的余弦相似度。这里重点说前两个,因为购物系统里它们最常用。

余弦相似度的公式是:

sim(i, j) = (Σ R(u,i) × R(u,j)) / (√Σ R(u,i)² × √Σ R(u,j)²)

它只看向量方向,不看向量长度。用户 A 买过 10 件商品、用户 B 买过 100 件商品,只要两个行为向量的方向一致,相似度就高。这在隐式反馈里比较有用,因为行为数量多少很多时候不能代表偏好强度差异。

皮尔逊相关系数本质上是在余弦相似度之前把每个用户的行为值减掉该用户均值,做了一次中心化。公式变成:

sim(i, j) = Σ (R(u,i) - mean_i) × (R(u,j) - mean_j) / (√Σ (R(u,i) - mean_i)² × √Σ (R(u,j) - mean_j)²)

这等于把不同的评分尺度对齐了。在显式评分中,用户 A 习惯打高分,用户 B 习惯打低分,直接算余弦会被个人尺度带偏;中心化后比较的是“偏离自己均值的方向”,更贴近真实偏好。

那购物系统用哪个?我的经验是:如果矩阵里全是 0/1 或 0/1/2/3 这种弱人工量纲,余弦相似度和皮尔逊区别不大;如果做均值中心化会引入负值,稀疏矩阵反而变得无法保持高效的稀疏结构。所以我在这个项目里直接采用余弦相似度,只是在相似度矩阵生成后,再做一次 Top-K 邻居截断,只保留每个商品相似度最高的 20 个邻居,其余置 0。这样既保证矩阵稀疏,也保证推荐时候选商品不会一下子膨胀到几千个。

3. 不依赖现成推荐框架,用 Python 手写一个可运行的推荐引擎

很多人在场景里被要求“用 Python 实现协同过滤推荐系统”,第一反应是装一个现成推荐库,然后调 fit 和 predict。但对一个在线购物系统来说,推荐模块要定制的地方非常多:行为权重映射、时间衰减、特定过滤规则、结果解释语。在这些前提下,从零写一个简洁版本,反而比套框架更容易控制。

这一节给出的代码都是可直接运行的核心部分,我用的是 pandas、numpy、scipy 和 scikit-learn,组合起来足够应付十万级商品、百万级交互数据的离线计算。

3.1 构造可复现的模拟数据

正式接入平台数据之前,我用一段模拟数据跑通全链路,避免一上来就被脏数据干扰判断。

import pandas as pd import numpy as np np.random.seed(42) # 模拟在线购物交互日志:user_id, item_id, behavior_type, timestamp users = range(1, 2001) items = range(1, 5001) data = [] behavior_map = {"browse": 1, "cart": 2, "buy": 3} for _ in range(200000): user_id = np.random.choice(users) item_id = np.random.choice(items) behavior_type = np.random.choice( list(behavior_map.keys()), p=[0.6, 0.25, 0.15] ) timestamp = np.random.randint(1600000000, 1700000000) data.append({ "user_id": user_id, "item_id": item_id, "behavior_type": behavior_type, "timestamp": timestamp, }) df = pd.DataFrame(data) print(df.head()) print("交互数:", len(df))

拿到日志后立即做两件事:按第 1.1 节的映射规则把行为转换成数值,同时按第 1.2 节的时间衰减规则调整权重。

df["behavior_value"] = df["behavior_type"].map(behavior_map) max_ts = df["timestamp"].max() df["days_diff"] = (max_ts - df["timestamp"]) / 86400 df["time_weight"] = 1.0 / np.log2(df["days_diff"] + 2) # 最终参与协同过滤的偏好数值 df["value"] = df["behavior_value"] * df["time_weight"] # 去掉行为数过少的用户 user_activity = df.groupby("user_id")["item_id"].count() valid_users = user_activity[user_activity >= 2].index df = df[df["user_id"].isin(valid_users)]

注意,真实平台的用户行为价值会跨越好几个量级,如果编码后数值差异过大,建议先做一次 log1p。我这里的 scale 本来就很接近,直接乘时间权重即可。

3.2 构建商品相似度矩阵的完整过程

这一步是整个推荐系统的核心。思路是把交互数据重构成“商品 × 用户”的稀疏矩阵,矩阵的行是商品,列是用户,格子是行为权重。然后再计算行与行之间的余弦相似度,得到“商品-商品”相似度矩阵。

from scipy.sparse import csr_matrix from sklearn.metrics.pairwise import cosine_similarity # 商品-用户矩阵 item_user = df.pivot_table( index="item_id", columns="user_id", values="value", fill_value=0 ) # 转换为 CSR 稀疏矩阵 sparse_item_user = csr_matrix(item_user.values) # 按商品两两计算余弦相似度 item_similarity = cosine_similarity(sparse_item_user) # 转成 DataFrame,方便按商品名取相似商品 item_sim_df = pd.DataFrame( item_similarity, index=item_user.index, columns=item_user.index ) def get_similar_items(item_id, top_k=20): if item_id not in item_sim_df.index: return pd.Series(dtype=float) sim_scores = item_sim_df.loc[item_id] sim_scores = sim_scores.drop(index=item_id).sort_values(ascending=False) return sim_scores.head(top_k)

不要小看 pivot_table 这一步。在真实项目里,商品数和用户数都很大,直接 pivot 成稠密矩阵会非常吃内存。此时可以换用 scipy.sparse.coo_matrix 配合 label encoding 来构造,本质上就是手动把 (row_index, col_index, value) 三元组喂给稀疏矩阵。下面这个写法在数据量大时更可靠:

from scipy.sparse import coo_matrix user_encoder = {u: i for i, u in enumerate(df["user_id"].unique())} item_encoder = {i: idx for idx, i in enumerate(df["item_id"].unique())} row_idx = df["item_id"].map(item_encoder).values col_idx = df["user_id"].map(user_encoder).values values = df["value"].values sparse_matrix = coo_matrix( (values, (row_idx, col_idx)), shape=(len(item_encoder), len(user_encoder)) ).tocsr() item_similarity = cosine_similarity(sparse_matrix)

这样做的另一个好处是:如果你想改成用户-用户协同过滤,只需要把 row_idx 和 col_idx 对调,生成的就是用户-用户相似度矩阵。坐标系切换只是换一下行列语义。

3.3 基于相似商品池生成用户 Top-N 推荐

有了商品相似度矩阵,给用户做推荐就很直观:找到用户买过或加购过的商品,也就是历史商品集合 Seeds,把每个 Seed 的相似商品取出来,剔除用户已经购买过的商品,然后按相似度加权求和排序,取 Top-N。

def recommend_for_user(user_id, top_n=10): # 该用户最近有过行为的商品 user_items = df.loc[df["user_id"] == user_id, "item_id"].tolist() if not user_items: return [] scores = {} for seed in user_items: if seed not in item_sim_df.index: continue sim_series = item_sim_df.loc[seed].drop(index=seed) for candidate, sim_score in sim_series.items(): if candidate in user_items: # 用户已经直接接触过,不重复推 continue scores[candidate] = scores.get(candidate, 0) + sim_score ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) return ranked[:top_n]

这个版本适合理解原理,但线上效率不够,因为对每个用户都会展开所有历史商品的每个相似邻居。更高效的做法是:在构建相似度矩阵时,直接把超过 Top-K 的相似度截断为 0,这样每个 Seed 最多带出 K 个候选商品,分数汇总的规模就受控了。更进一步,你可以把 top_n=500 个用户的缓存结果放到 Redis 里,用户请求时直接读缓存,而不是现场跑一遍推荐逻辑。

3.4 计算时间和内存的真实问题

一开始我以为 5000 个商品的相似度会很轻松,实际跑的时候发现,原始 DataFrame 版本逐行循环计算余弦相似度,跑完用了 20 多分钟。换成 scipy 稀疏矩阵后,同样的数据量几秒钟就出结果。差异来自两个地方:

第一,pivot_table().fillna(0) 在商品多、用户多时会生成密集矩阵,内存消耗是 O(商品数 × 用户数),几万商品乘以几十万用户会直接让笔记本内存告急。coo_matrix + tocsr 只存非零格子,内存缩减到原来的百分之几。

第二,类似 cosine_similarity 这类向量运算,底层走的是编译好的线性代数库,比 Python 的 for 循环快几个数量级。能用矩阵运算表达的,不要自己写双层循环。

另外一个细节是 Top-K 截断。我保留了每个商品相似度最高的 20 个邻居,其余置 0。这样最后给用户推荐的商品池只有“用户已买商品数 × 20”的规模,几乎不会出现几万个候选商品一起算分的情况。对大平台来说,这就是推荐服务延迟的命脉。

4. 从离线脚本到在线购物系统,推荐模块的工程边界

程序跑通只是第一步。把协同过滤推荐真正嵌进在线购物系统,需要回答好几个工程问题:相似度矩阵在哪里算?推荐结果何时算?接口怎么设计?数据更新频率怎样?这一节讲我在项目里采用的架构和分工。

4.1 离线训练与在线计算的职责划分

我最终采用的架构可以归纳成一张简单的分工表:

环节运行时机做什么使用数据
数据抽取每小时增量从行为日志提取新增交互,写入宽表订单、加购、浏览日志
商品相似度计算每天一次全量重算商品-商品相似度矩阵近 30 天有效交互
热门兜底列表每 6 小时一次计算全站热销、类目热销、新品列表近 7 天行为
用户个性化推荐每次请求从缓存取用户推荐结果,过滤库存和曝光预计算缓存 + 实时过滤
实时补推每次请求对实时的购物车、收藏行为做即时相似推荐商品相似度矩阵

这个设计的核心思路是:协同过滤的相似关系是稳定的,不要求秒级更新,完全可以每天离线算一次;用户最终的推荐清单涉及库存、上下架、用户最近曝光等实时信息,这些必须在接口层动态过滤。两者不能混在一起,否则要么离线结果经常失效,要么在线计算扛不住并发。

我这里没有用复杂的大数据组件,就是普通 Python 脚本加 airflow 定时任务。数据量增长到千万级交互后,脚本依然能在一小时内跑完,已经够用。如果商品量到几十万、交互量到上亿,可以再考虑把矩阵拆到分布式计算,但思路不变。

4.2 推荐接口的数据流设计与缓存

线上服务我用的是一个轻量级 Web 框架,接口逻辑其实非常薄,因为真正的计算压力在缓存和离线环节。看核心代码:

# 缓存逻辑简化为 get_from_cache 和 set_to_cache def get_recommendations(user_id: str, limit: int = 10): cached = get_from_cache(f"rec:user:{user_id}") if cached: return filter_available_items(cached)[:limit] # 冷启动用户没有缓存,走兜底策略 recs = get_fallback_recommendations(user_id) # 放入缓存,TTL 设为 15 分钟 set_to_cache(f"rec:user:{user_id}", recs, ttl=900) return recs[:limit]

用户请求进来后,先查缓存;缓存命中就直接过滤可用商品,不需要进行任何计算。用户最近 15 分钟内的行为可能变化,所以 TTL 设短一些。对系统里大多数只逛不买的用户,这个缓存命中率非常高。

为了让个性化推荐不依赖每次都跑全量逻辑,我还有一个后台任务:用户产生“加入购物车”或“提交订单”这类强行为时,立刻异步更新该用户的个性化候选集。这样用户下一次打开首页推荐位时,最新兴趣已经生效,而不是等到第二天相似度矩阵重建。

4.3 线上最容易踩的坑:重复曝光、库存失效与过滤顺序

第三天中午我遇到了最经典的坑:同一件商品在用户的“猜你喜欢”和“相关推荐”里反复出现。用户买了手机,推荐位把手机壳重复推了 8 次,比例严重失衡。原因是缓存里只存了商品 ID 列表,没有记录用户近期的曝光记录,推荐算法本身也不知道用户在别的页面已经看到过哪些商品。

我的做法是引入曝光过滤表,每次请求拿到推荐列表后,先和历史曝光做差集。曝光表的维度是 user_id + item_id + 最近 30 天,量大的时候放到 Redis 里用 set 保存,判断直接走 SISMEMBER,非常快。过程可以写成:

def filter_available_items(rec_items, user_id): result = [] for item_id in rec_items: if is_item_off_shelf(item_id): continue if has_been_exposed(user_id, item_id, days=30): continue if is_out_of_stock(item_id): continue result.append(item_id) if len(result) >= 10: break return result

顺序很重要:先过滤库存和下架,再过滤曝光。库存和上下架数据变动频繁,要实时查;曝光过滤可以稍微宽松,因为用户对同一商品的曝光容忍度还有一段距离。如果把曝光过滤放在第一位,遇到大促商品秒杀时,推荐位会白白浪费很多流量。

4.4 冷启动场景的三层兜底策略

协同过滤在冷启动场景天然失效。新用户没有历史行为,矩阵里该用户所有格子都是 0,算法根本算不出他的相似邻居;新商品没有交互记录,其他商品和它的相似度也全部是 0。这需要一个和协同过滤并列的兜底策略。

我把兜底分成三层:

第一层,新用户冷启动。直接返回平台热销榜、类目热销榜、新品榜的混合结果。用户首次点击某个商品后,推荐接口立刻把该商品所属二级类目的热销商品加入结果,相当于先用热门内容完成冷启动探索。

第二层,新商品冷启动。给新商品加一个“新品探索期”的阶段权重,在用户最近浏览过的同类目下插入一部分位置。这个权重会随商品积累的交互数逐渐衰减,并在积累满一定交互量后交给正常协同过滤流程。

第三层,弱行为用户的增强策略。这类用户有少量浏览记录,但不足以计算可靠的相似用户。此时可以把他的浏览商品当作 Seed,直接用商品相似度矩阵找相似商品,这本质上就是利用大批用户的行为“替”单个用户做判断。这也是商品-商品协同过滤在线上的又一优势:即使只点了一个商品,推荐也能立刻响应。

冷启动兜底策略和协同过滤不是二选一,而是组合。我在线上把两者的比例设为:协同过滤个性化结果占主力,热销兜底补充 20%-30%,新品探索固定占 1-2 个推荐位。

5. 推荐效果评估与调优,不能只盯“预测准不准”

推荐系统上线后,最容易犯的错误是只看模型离线指标。离线 AUC 高、RMSE 低,不代表用户真的会点你的推荐位。我在这个项目里把评估拆成离线指标和线上观察两层。

5.1 离线指标怎么组合才不骗人

针对协同过滤,我主要看四个指标:

指标含义我取的目标值依据
RMSE预测行为值与真实值的均方根误差对隐式反馈只做参考,不作为主判断
Precision@N推荐 Top-N 里被用户采纳的比例主要看加购和下单,浏览点击只做辅助
Recall@N用户实际采纳的商品里有多少被推荐命中数值一般偏低,追求高会牺牲精准度
覆盖率被推荐过的商品占全部可推荐商品的比例覆盖率太低说明算法只会推头部商品

离线评测我的做法是:把近 30 天交互数据按时间切分,前 25 天的行为用于生成相似度矩阵,后 5 天的行为作为用户真实采纳,衡量推荐列表是否命中。之所以按时间切分而不是随机切分,是因为推荐系统天然有时序,用未来数据训练再预测过去没有意义。

一个容易踩的坑是评估数据集本身不平衡。在线购物系统里下单永远是少数,大多数用户的近 5 天记录只有浏览行为。如果你把“购买”当成唯一采纳指标,那 Precision@10 可能只有 1%-2%,看起来很惨,但这不代表算法不行。我最后同时统计“点击入详情”“加入购物车”“支付”三档信号,点击做粗选判断,购物车和支付做严肃业务判断。

5.2 线上观察的三个层次

离线指标看模型的理论上限,线上验证才看实际闭环。我不建议直接全局放量,而是按用户 ID 尾号做小流量 AB,让实验组和对照组各覆盖一部分用户,观察三类指标:

  • 曝光点击率 CTR:推荐位曝光后用户是否愿意点进详情页,反映推荐位与用户兴趣的匹配度。
  • 加购率:点击后是否加入购物车,反映商品价格与需求的匹配度。
  • 转化贡献:推荐位带来的订单金额占全站成交的比例,这个值不会很高,但趋势必须向上。

这里有一个细节:推荐位本身有位置因素,同一个推荐位放在首页顶部和放在购物车底部,CTR 天然不同。做实验时必须控制位置,只比较同一个推荐位上的不同策略,否则结论全被位置噪声污染。

5.3 我实测下来最有用的几个调优动作

项目跑了两轮后,我把调优重心放在这几个地方,效果都比较明显:

第一,行为权重重新标定。最初我给的权重是浏览 1、加购 2、购买 3,但实际数据显示,购买行为虽然占比低,却是加购行为置信度的 3-5 倍。我把购买权重提到 5、加购提到 2、浏览保持 1,Top-N 的精确率提升明显。

第二,时间窗口从 90 天缩短到 30 天。用户兴趣变化快,90 天前买过的品类和现在的关联已经太弱了。缩短窗口后,推荐结果对季节品、促销期的反应更快,覆盖率也更好看。

第三,相似度 Top-K 从 50 降到 20。K 越大,候选池越杂,推荐的“发散感”越强。K 越小,推荐越收敛到用户的既有兴趣。在线购物系统里,用户点开首页“猜你喜欢”时更希望看到关联紧密的商品,所以我把 K 降到 20,再配合少量类目拓展位,既保住了精准度又不显得死板。

第四,加一层商品级去重。同一款商品的不同 SKU(颜色、尺码不同)在相似度矩阵里高度相似,如果不做系列聚合,推荐位会出现一条连衣裙的四个颜色全被推出来的情况。我按款号把 SKU 聚合到新品 ID,相似度矩阵直接建在款号粒度上,再在接口层映射回具体 SKU。

6. 项目总结与我的几条实战体会

这个基于 Python 实现的协同过滤推荐系统,最后稳定跑在购物系统的推荐位上。如果只挑核心链路,就是四步:商品-用户稀疏矩阵 -> 商品相似度矩阵 -> Top-N 候选集 -> 接口层实时过滤与缓存。每一步单独看都不难,难的是把数据清洗、矩阵稀疏表示、离线在线分工、冷启动兜底全部捏合在一起。

从这套项目里,我也总结了几条属于自己的经验,写在这当作收尾。

第一个体会是:推荐系统的上限由数据质量决定,下限由算法决定。行为权重的映射、时间窗口的选择、异常用户的剔除,这些看似琐碎的处理,对最终推荐效果的影响远超过从用户-用户切到商品-商品这种算法层面的调整。与其花时间追求更复杂的模型,不如先把数据管道做干净。

第二个体会是:别把所有用户都塞进协同过滤。活跃用户、新用户、弱行为用户的行为模式差异巨大,一套模型打天下必然在一部分用户身上失效。我在线上增加了按用户活跃度分桶的逻辑,不同桶走不同推荐策略,整体效果比只在算法上打补丁好得多。

第三个体会是:推荐系统上线只是起点。用户行为在变,商品结构在变,促销节奏在变。我每隔两周就会回看一次曝光点击率、加购率、覆盖率的变化,一旦发现覆盖率持续下降,我就知道算法在向头部商品过度集中,该重新调整相似度矩阵的截断参数或引入更多探索位了。

如果有人也想在自己的在线购物项目里实现类似功能,我最后给出的建议只有一条:先花一个星期把数据流和评估链路搭起来,再动算法。手写一个基于协同过滤的推荐引擎,用不了太多代码,但要让它在真实流量里稳定产出,功夫全在看不见的数据工程和线上细节里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:33:37

电源硬件设计Agent防幻觉架构:让大模型不敢乱定参数

做电源硬件设计的Agent,最怕的不是它答不上来,而是它一本正经地给出一份看起来天衣无缝、实际会炸板的设计方案。这句话是我折腾了差不多一个季度的防幻觉架构之后最深的体会。今天想聊的,是我们基于Deepseek Harness搭建的一套电源硬件设计a…

作者头像 李华
网站建设 2026/10/20 5:33:30

基于Python的网络小说分析系统:从爬虫分词到可视化毕设全攻略

如果你正在为毕业设计选题发愁,同时又想做一个“看起来有技术含量、论文好写、答辩不容易被问倒”的系统,那“基于Python的网络小说分析系统”是一个非常典型的稳妥选择。这个题目听起来像是一个完整的工程项目:抓取小说数据、清洗文本、分词…

作者头像 李华
网站建设 2026/10/20 5:33:24

PAP协议深度解析:个人智能体的身份、授权与审计机制

1. 从"智能体各自为战"说起:PAP协议到底想解决什么如果你最近在关注智能体(Agent)领域的技术动态,大概率会频繁刷到"Person Agent Protocol"这个词。缩写PAP,中文一般译作"个人智能体协议&qu…

作者头像 李华
网站建设 2026/10/20 5:32:24

C# DirectX三维动画导入与蒙皮渲染完整实战解析

简介:这是一份面向C#开发者的DirectX三维动画学习资源,基于VS2017环境,演示从设备初始化、顶点/索引缓冲到骨骼动画的完整流程。适合想要在.NET平台下突破游戏图形编程壁垒的读者,解决C#调用原生DirectX的互操作难题。压缩包共405…

作者头像 李华
网站建设 2026/10/20 5:32:18

线性代数核心概念详解:从矩阵变换到特征值分解

1. 从"解方程组"到"看透结构":线性代数真正的打开方式我接触线性代数的第一感觉,和很多人一样——满纸的计算规则,矩阵、行列式、特征值,每个概念都能做题,拼起来却不知道在干什么。直到工作之后反…

作者头像 李华