免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

电商用户行为分析与推荐系统实战:从数据清洗到协同过滤

电商用户行为分析与推荐系统实战:从数据清洗到协同过滤 简介面向电子商务用户行为分析与服务推荐的Python数据挖掘实战资源包适合具备一定Python基础并希望借助真实场景提升建模能力的学习者。资源以“用户行为分析—特征探索—推荐模型”为主线覆盖pandas数据处理、可视化、协同过滤与矩阵分解等关键知识点并包含数据清洗、特征缩放、模型评估等实操环节。压缩包仅含5个文件体积约79KB包括2个Jupyter Notebook、1个Excel数据表、1个SQL脚本及1个说明文本其中Notebook分步骤展示完整分析流程和模型构建Excel与SQL提供可直接导入的原始数据文本则补充使用说明与环境要点。目前已有417人学习下载内容精炼、开箱即用结合真实电商场景有助于理解推荐系统的实际落地逻辑可作为入门推荐系统与数据挖掘项目的速通参考资料。1. 电商用户行为数据挖掘为什么值得当实战项目做一份带着完整代码和数据集的电子商务用户行为分析项目是 Python 数据挖掘与机器学习入门阶段性价比最高的实战素材。这类资源包的结构高度统一数据集来自真实电商平台的用户行为日志每一行是一次行为记录分析侧要求给出用户分层与转化诊断推荐侧要求训练一个能给用户返回 Top-N 商品列表的模型。把这套流程跑通等于把 pandas 数据处理、特征工程、协同过滤、模型评估这几块硬技能全部串了一遍。更实际的价值在于面试时被问“项目里的数据怎么清洗的、推荐用了什么召回策略”答案都在下文这套可复现的步骤里。下面按数据清洗、行为分析、推荐建模、效果验证的顺序完整展开所有代码基于 Python 3.9 与 pandas、scipy、implicit 这几个常用库。2. 用户行为数据预处理字段清洗与特征工程的落地代码2.1 五字段结构与时间戳的两个坑这类数据集最常见的格式是淘宝用户行为数据集那一套五个字段用逗号分隔原始数据没有表头读进来后长这样字段名含义数据挖掘中的角色user_id用户 ID分层与推荐的分组键item_id商品 ID物品侧聚合键category_id类目 ID泛化特征与冷启动兜底behavior_typepv / cart / fav / buy标签来源与反馈权重timestampUnix 秒级时间戳时间切分与行为序列排序上手先踩两个坑第一timestamp 是秒级还是毫秒级决定后续日期转换是否正确秒级时间戳一般在 1.4e9 到 1.6e9 这个量级毫秒级是 14 位数字转换单位差 1000 倍日期会直接错乱第二pv 行为通常占九成以上若不做截断或加权模型会天然偏向热门商品后面的特征工程必须对行为类型分层统计而不是笼统计数。2.2 清洗脚本重复记录、异常时间与稀疏过滤拿到原始 CSV 后我一般先做三轮清洗空值与异常时间戳过滤、同一用户同一商品同一行为的去重、冷门商品与低活用户过滤。去重这一步容易被忽略实际日志里用户反复刷新页面会生成大量重复 pv如果不处理后续统计的用户活跃度会整体虚高。import pandas as pd cols [user_id, item_id, category_id, behavior_type, timestamp] df pd.read_csv(UserBehavior.csv, sep,, headerNone, namescols) # 1. 时间戳范围校验秒级 Unix 时间戳应落在 2014-2018 之间 df df.dropna() df df[(df[timestamp] 1_400_000_000) (df[timestamp] 1_599_999_999)] # 2. 同一用户对同一商品的同一行为只保留最早一条 df df.sort_values(timestamp).drop_duplicates( subset[user_id, item_id, behavior_type], keepfirst) # 3. 过滤行为数过少的商品和用户降低矩阵稀疏度 item_cnt df.groupby(item_id)[user_id].count() hot_items item_cnt[item_cnt 20].index user_cnt df.groupby(user_id)[behavior_type].count() active_users user_cnt[user_cnt 50].index df df[df[item_id].isin(hot_items) df[user_id].isin(active_users)] print(df.shape) print(df[behavior_type].value_counts(normalizeTrue))drop_duplicates 的 subset 参数把“用户-商品-行为”三元组作为判重键sort_values 保证保留时间最早的那条门限 20 和 50 不是固定值数据量翻倍时按比例上调即可原则是砍掉长尾后矩阵仍保留 95% 以上的行为总量。过滤比例可以在命令行里用df.groupby(item_id)[user_id].count().describe()快速查看分布再定。2.3 特征工程用户侧、物品侧与时间切片清洗完的数据要拆成三张特征表分别服务不同环节用户侧统计特征给 RFM 分层用物品侧统计特征给召回排序用时间切片特征给模型训练和验证用。这里的关键是行为类型要拆开统计混合计数会丢掉“加购多但购买少”这类有价值的信息。df[date] pd.to_datetime(df[timestamp], units).dt.date # 用户侧四类行为次数 活跃天数 user_feat df.groupby(user_id).agg( total_pv(behavior_type, lambda x: (x pv).sum()), total_cart(behavior_type, lambda x: (x cart).sum()), total_fav(behavior_type, lambda x: (x fav).sum()), total_buy(behavior_type, lambda x: (x buy).sum()), active_days(date, nunique), ) user_feat[cart_buy_ratio] user_feat[total_cart] / (user_feat[total_buy] 1) # 物品侧浏览转购买率用于热门商品召回时的排序权重 item_feat df.groupby(item_id).agg( pv_cnt(behavior_type, lambda x: (x pv).sum()), buy_cnt(behavior_type, lambda x: (x buy).sum()), ) item_feat[buy_rate] item_feat[buy_cnt] / (item_feat[pv_cnt] 1e-9) # 时间切片把数据集按日期切成训练窗口和验证窗口 train_df df[df[date] df[date].max()] test_df df[df[date] df[date].max()]lambda 里(x pv).sum()对 groupby 后的每个分组单独判断布尔值再求和比先筛选再合并的写法快而且避免索引错位cart_buy_ratio 加 1 的平滑是防止购买数为 0 时除零。物品侧的 buy_rate 要小心pv 极少但有一次购买的冷门商品会拿到虚高转化率所以应该在过滤掉低 pv 商品之后再用这个特征。3. 用户行为分析实战转化漏斗、RFM 分层与商品关联3.1 漏斗口径按去重人数算而不是按次数算转化漏斗是电商行为分析的第一个标准动作但口径经常出错。如果直接对行为记录计数一个用户点 20 次 pv 就会把 pv 阶段的体量放大 20 倍漏斗每一层之间的“衰减”完全失真。正确的口径是对每个行为阶段的 user_id 去重后计数反映“有多少人走到了这一步”。funnel {} for beh in [pv, cart, fav, buy]: funnel[beh] df[df[behavior_type] beh][user_id].nunique() funnel_df pd.Series(funnel).to_frame(user_cnt) funnel_df[conv_rate] funnel_df[user_cnt] / funnel_df.loc[pv, user_cnt] print(funnel_df)把 cv 率放在 pv 人数上做分母得到的是整体转化率如果要看相邻阶段转化应该用cart 人数 / pv 人数、buy 人数 / cart 人数逐级计算。用 pandas 的to_csv把 funnel_df 落盘后可以直接丢给 BI 工具画漏斗图不需要额外写绘图脚本。fav 和 cart 的顺序在不同业务里不一致收藏偏意愿、加购偏行动业务上一般把加购视作比收藏更接近购买的动作。3.2 RFM 分层没有金额字段时怎么打分经典 RFM 模型用最近一次购买时间 R、购买频次 F、消费金额 M 三个维度给用户打分但这套行为数据集没有订单金额字段。常见替代方案是用“购买商品去重数”作为 M 的近似它反映用户购买广度比频次更能区分“反复买同一款”和“买了很多款”的两类用户。from datetime import timedelta ref_date df[date].max() timedelta(days1) buy_df df[df[behavior_type] buy] # R最后一次购买距今的天数 r_df buy_df.groupby(user_id)[date].max().reset_index() r_df[recency_days] (ref_date - r_df[date]).dt.days # F购买次数 f_df buy_df.groupby(user_id).size().reset_index(namefreq) # M购买过的商品去重数 m_df buy_df.groupby(user_id)[item_id].nunique().reset_index(namedepth) rfm r_df[[user_id, recency_days]].merge(f_df, onuser_id).merge(m_df, onuser_id) for col in [recency_days, freq, depth]: rfm[col _score] pd.qcut(rfm[col], 4, labelsFalse, duplicatesdrop)qcut 按分位数切成 4 档返回 0 到 3 的整数分比手动设阈值客观得多recency_days 是越小越好需要反向处理把分数对 3 做差rfm[recency_score] 3 - rfm[recency_score]。三个分数组合后可以继续分群比如“高 R 高 F 高 M”是核心用户“低 R 高 F”是沉睡风险高的老客户这个群标签可以直接写回 user_feat 表作为后续推荐重排的权重特征。duplicatesdrop 不能漏数据存在大量并列值时默认的 qcut 会直接报错。3.3 商品关联挖掘自连接构造共现矩阵的取舍做“搭配购”推荐最直接的办法是找商品共现关系同一个用户购买记录里同时出现的商品彼此有搭配可能。实现上用 DataFrame 自连接即可但全量商品互相 join 会产生大量内存开销所以实操中要先把商品限制在购买次数排名前 500 的集合内。top_items buy_df[item_id].value_counts().head(500).index buy_top buy_df[buy_df[item_id].isin(top_items)] pairs buy_top.merge(buy_top, onuser_id) pairs pairs[pairs[item_id_x] pairs[item_id_y]] # 去掉自连接重复对 pair_cnt pairs.groupby([item_id_x, item_id_y]).size().reset_index(nameco_cnt) pair_cnt pair_cnt[pair_cnt[co_cnt] 5] print(pair_cnt.sort_values(co_cnt, ascendingFalse).head(10))item_id_x item_id_y的条件让每一对商品只保留一种排列顺序避免 (A,B) 和 (B,A) 重复计入co_cnt 大于等于 5 的过滤是为了去掉只被一两个用户同时买过的偶发组合。这个表的局限在于只反映“同时购买”不反映先后顺序做“买 A 推荐 B”时需要再限定购买时间先后重算一遍。关联规则里的支持度和置信度可以在此基础上扩展置信度 co_cnt / 包含 A 的购买记录数用来筛掉“A 很热门所以什么都跟它共现”的干扰项。4. 服务推荐模型ItemCF 与 ALS 矩阵分解的两种实现4.1 ItemCF稀疏矩阵余弦相似度的手写路径协同过滤里最直白的基线是 ItemCF商品相似度来自“哪些用户同时买了它们”再用相似商品的历史交互来给用户打分。手写实现时不要用稠密二维数组商品数上万后内存会直接爆炸正确做法是用 scipy 的 csr_matrix 存用户-商品交互矩阵。import numpy as np from scipy.sparse import csr_matrix # 行为权重购买 加购 收藏 浏览 df[weight] df[behavior_type].map({pv: 1.0, fav: 2.0, cart: 3.0, buy: 5.0}) ui df.groupby([user_id, item_id])[weight].max().reset_index() users sorted(ui[user_id].unique()) items sorted(ui[item_id].unique()) u_idx {u: i for i, u in enumerate(users)} i_idx {i: j for j, i in enumerate(items)} mat csr_matrix((ui[weight], (ui[user_id].map(u_idx), ui[item_id].map(i_idx))), shape(len(users), len(items))) # 列向量归一化后矩阵内积就是余弦相似度 col_norm mat.multiply(1.0 / (mat.multiply(mat).sum(axis0).A1 ** 0.5 1e-9)) item_sim (col_norm.T col_norm).tocsr()mat.multiply(mat).sum(axis0).A1先算出每列平方和再开方得到每件商品的向量模长稀疏矩阵做内积时只有非零位置参与计算复杂度远低于稠密矩阵。给用户生成推荐时把该用户的交互向量和相似度矩阵相乘得到对所有商品的预测分再过滤掉已经买过的商品取 Top-N。def recommend(user_id, top_n20): u u_idx[user_id] scores (item_sim mat[u].T).toarray().flatten() score_series pd.Series(scores, indexitems) bought set(ui.loc[ui[user_id] user_id, item_id]) return score_series[~score_series.index.isin(bought)].sort_values( ascendingFalse).head(top_n)ItemCF 的优点是解释性强“因为你看过 A所以推荐同类的 B”可以直接给到前端话术缺点是相似度矩阵是商品数乘商品数5 万商品就是 25 亿个元素必须维持稀疏存储并按相似度阈值截断只保留每件商品 top 100 的相似邻居。4.2 ALS用 implicit 库跑隐式反馈矩阵分解用户行为数据没有评分只有“点了没点、买没买”这类隐式反馈用矩阵分解里的加权 ALS 更合适。implicit 库是 Python 里做得最顺手的实现输入要求是物品-用户矩阵物品在行与前面构造的 mat 互为转置。import implicit model implicit.als.AlternatingLeastSquares( factors64, iterations15, regularization0.1, alpha40, ) model.fit(mat.T) # 转为 items × users 矩阵 user_vecs model.user_factors # 用户向量矩阵 (users × factors) item_vecs model.item_factors # 物品向量矩阵 (items × factors) ids, scores model.recommend( u_idx[10001], mat.T[u_idx[10001]], N20, filter_already_liked_itemsTrue, )factors 是 embedding 维度64 是性价比比较高的起点维度太小欠拟合、太大训练时间和内存都涨iterations 15 轮足够收敛继续加大收益递减regularization 控制过拟合在日志场景下 0.1 偏保守数据越稀疏这个值要提得越高alpha 是置信度缩放的倍数40 的含义是“交互一次的置信度是 0 的 40 倍”把点击这种弱信号放大。recommend 里的mat.T[u_idx[10001]]取的是该用户在所有商品上的交互行向量filter_already_liked_items 会自动把已交互商品剔除。和 ItemCF 相比ALS 在稀疏大数据上泛化更好但 embedding 不可直接解释两类模型的关系可以这么看维度ItemCF 手写实现ALS 矩阵分解可解释性强可回溯相似商品弱向量无语义数据要求交互矩阵即可需要调 alpha 和正则冷门商品区分度差靠向量泛化扩展性相似度矩阵平方膨胀随 embedding 线性扩展4.3 召回融合把两路分数归一化后再加权线上推荐不会只跑一个模型常见做法是 ItemCF 和 ALS 各出一路召回再叠加热门兜底最后把分数融合排序。两路分数的量纲完全不同直接相加会被数值大的一路带偏所以要先把每路分数做 min-max 归一化再赋权重合并。def minmax(score_dict): lo, hi min(score_dict.values()), max(score_dict.values()) return {k: (v - lo) / (hi - lo 1e-9) for k, v in score_dict.items()} itemcf_res minmax(dict(zip(items, item_scores))) als_res minmax(dict(zip(items, als_scores))) merge_scores {} for item in set(itemcf_res) | set(als_res): merge_scores[item] 0.7 * itemcf_res.get(item, 0) 0.3 * als_res.get(item, 0)权重 0.7 对 0.3 代表业务上更信任 ItemCF 的可解释结果这个比例应该用离线评测结果来定而不是拍脑袋。融合后要留一小部分位置给热门商品兜底否则新用户和冷门商品永远没有曝光机会这个策略在下一章展开。5. 推荐效果评估与防过拟合的几个实操细节5.1 时间切分验证数据泄漏就藏在这里评估推荐模型最常犯的错是用随机划分切训练集和测试集用户过去的行为和未来的购买混在一个集合里模型“偷看”了未来离线指标虚高上线就崩。正确做法是按时间切分用前 N-7 天训练最后 7 天作为测试只有测试期内真实发生的购买才计入命中。from datetime import timedelta split_day df[date].max() - timedelta(days7) train df[df[date] split_day] test df[(df[date] split_day) (df[behavior_type] buy)]5.2 PrecisionN、RecallN 与覆盖率三件套评测指标至少算三个精确率衡量推荐列表里有多少是用户真正买的召回率衡量用户买的东西有多少被推荐到了覆盖率衡量推荐系统有没有只推头部热门商品。def evaluate(user_ids, rec_func, test_buy, n20): hits, rec_total, buy_total 0, 0, 0 rec_items set() for uid in user_ids: rec set(rec_func(uid, n)) bought set(test_buy.get(uid, [])) hits len(rec bought) rec_total n buy_total len(bought) rec_items | rec precision hits / rec_total recall hits / max(buy_total, 1) coverage len(rec_items) / len(items) return precision, recall, coverage对每个测试用户各取 20 条推荐做交集累积统计后统一计算不要在单个用户上算平均单个用户的购买极稀疏平均会得到一堆 0 分丧失区分度。覆盖率低于 20% 说明模型被热门商品绑架了要下调融合里热门兜底的权重。5.3 把相似度和向量落盘离线算完线上只查表最后一个实用技巧不要在线上实时重算相似度或跑 ALS 预测把离线训练产出的中间结果落盘线上服务只做查表和 Top-N 截断。这样推荐接口的延迟能从几十毫秒降到个位数毫秒而且模型更新与线上服务解耦。np.save(user_factors.npy, user_vecs) np.save(item_factors.npy, item_vecs) sim_coo item_sim.tocoo() sim_df pd.DataFrame({ item_i: np.asarray(items)[sim_coo.row], item_j: np.asarray(items)[sim_coo.col], sim: sim_coo.data, }) sim_df.to_parquet(item_sim.parquet, indexFalse)落盘文件的更新放在定时任务里每天凌晨用前一天的全量行为重训一次模型白天服务只读快照。检查推荐质量时把上一轮的 user_factors.npy 用np.load读回算两个用户的向量内积能直接定位“相似用户”的语义是否合理。这套离线计算、线上查表的流水线配上 5.1 节的时间切分验证就是电商推荐系统最小可用的基线形态。本文还有配套的精品资源点击获取
返回列表