news 2026/9/26 8:41:31

天猫复购预测源码实战:从特征工程到模型融合的完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
天猫复购预测源码实战:从特征工程到模型融合的完整流程

简介:本资源为阿里天池大赛学习赛「天猫复购预测」的完整案例包,面向计算机、人工智能、通信工程、自动化等专业的在校学生与教师,也适合企业员工及具备一定基础的小白进阶学习,可用于课程设计、毕业设计、作业提交或项目初期立项演示。包内共7个文件,以3个csv数据集、2个py脚本、1个ipynb笔记本和1个md说明文档为主,压缩包约4.01MB,涵盖用户信息、训练集与测试集数据,以及逻辑回归、随机森林两种建模脚本和一份数据分析与可视化笔记,结构清晰便于按模块查阅。目前已有488人学习下载。读者可借此掌握复购预测的完整赛题流程,包括数据读取、特征处理、模型训练与结果可视化,并对照README快速上手;代码均经测试运行成功,答辩评审平均分达96分,也可在此基础上修改扩展,实现其他功能。

1. 天猫复购预测这套源码,到底能帮你省下多少试错时间

电商复购预测是个典型的二分类问题,但真正动手做过的人都知道,难的不是模型选型,而是从原始行为日志到特征矩阵这一段。阿里天池的学习赛里,天猫复购预测给了一份约 42 万条用户-商家交互记录,字段包括 user_id、merchant_id、action_type、time_stamp 等,目标是预测用户在给定商家处未来是否会产生复购。这套 Tmall-Repurchase-Prediction-main 源码包,把数据读取、特征工程、模型训练和可视化四个环节都拆成了独立文件,LogisticRegression.py 和 RandomForestClassifier.py 分别给出两个基线,配套的 notebook 负责探索性分析。适合谁?计算机、人工智能、通信工程等专业的在校生拿来做课设或毕设,也适合刚转行数据挖掘的从业者拿来跑通第一个完整流程。它不解决前沿算法问题,但能让你在半天内看到一条从 CSV 到预测结果的完整链路,省掉自己搭脚手架的时间。

2. 数据读取与特征工程:从三张 CSV 到模型可用的特征矩阵

2.1 三张 CSV 的分工与读取方式

拿到压缩包解压后,目录下有三个数据文件:user_info_format1.csv、train_format1.csv、test_format1.csv。user_info_format1.csv 存的是用户侧画像,包含 user_id、age_range、gender 三列,其中 age_range 和 gender 存在大量缺失值,这是天猫赛题的已知特点。train_format1.csv 是训练集,每行是一条 user_id + merchant_id 的交互记录,附带 label 列(1 表示复购,0 表示未复购),以及该用户在 2015 年 5 月 1 日至 6 月 30 日期间对该商家的 action_type 汇总。test_format1.csv 结构相同但没有 label,是待预测样本。

常见做法是用 pandas 读取,注意 dtype 指定,避免 user_id 被读成科学计数法:

import pandas as pd user_info = pd.read_csv('user_info_format1.csv', dtype={'user_id': str}) train = pd.read_csv('train_format1.csv', dtype={'user_id': str, 'merchant_id': str}) test = pd.read_csv('test_format1.csv', dtype={'user_id': str, 'merchant_id': str}) print('user_info shape:', user_info.shape) print('train shape:', train.shape) print('test shape:', test.shape) print('train label 分布:\n', train['label'].value_counts())

逻辑说明:user_id 和 merchant_id 在原始数据里是长整型,但 pandas 默认会推断为 int64,后续做 merge 时如果 test 里某些 id 超出 int32 范围会出问题,统一转成 str 更稳妥。参数方面,dtype 字典只对指定列生效,其余列仍走默认推断。打印 shape 和 label 分布是每次读数据后的固定动作,label 分布能直接告诉你正负样本比例——天猫这份数据里正样本大约占 6% 到 7%,属于典型的不平衡场景,后面选模型和调参都要围绕这个事实来。

2.2 特征构造:把行为日志转成数值列

原始 train 和 test 里,每个 user-merchant 对只有一行,但 action_type 有 0、1、2、3 四种取值(分别对应点击、加购、收藏、购买),time_stamp 是日期。直接把这些列丢给模型没有意义,需要做聚合。源码里的做法是:对每个 user_id 统计其在所有商家上的行为次数,再对每个 merchant_id 统计其被所有用户的行为次数,最后把这两组统计量 merge 回主表。

# 用户侧行为统计 user_actions = train.groupby('user_id')['action_type'].agg(['count', 'nunique']).reset_index() user_actions.columns = ['user_id', 'user_action_count', 'user_action_types'] # 商家侧行为统计 merchant_actions = train.groupby('merchant_id')['action_type'].agg(['count', 'nunique']).reset_index() merchant_actions.columns = ['merchant_id', 'merchant_action_count', 'merchant_action_types'] # 合并回主表 train_feat = train.merge(user_actions, on='user_id', how='left') train_feat = train_feat.merge(merchant_actions, on='merchant_id', how='left')

逻辑说明:groupby 后 agg 里 count 统计行为总次数,nunique 统计行为类型数,这两个特征能粗略刻画用户活跃度和商家热度。merge 用 left 保留主表所有行,避免因为某些 user_id 在统计结果里缺失而丢样本。参数上,how='left' 是必须的,如果用 inner 会丢掉那些只出现在 test 里的 user_id。这一步做完,特征列从原来的几列扩展到十几列,包括 age_range、gender 的编码结果。

注意:age_range 和 gender 的缺失值不要直接 fillna(0),因为 0 在原始编码里有实际含义(比如 gender 的 0 代表女性)。源码里用的是 fillna(-1) 再单独做一列缺失标记,这个细节在答辩时经常被问到。

2.3 训练集与测试集的特征对齐

特征工程最容易翻车的地方是:训练集构造完特征后,测试集必须用完全相同的逻辑再走一遍,且列顺序要一致。源码里把特征构造封装成了一个函数,train 和 test 分别调用,最后用 train_feat.drop('label', axis=1).columns 去对齐 test_feat 的列。

def build_features(df, user_info, user_actions, merchant_actions): df = df.merge(user_info, on='user_id', how='left') df = df.merge(user_actions, on='user_id', how='left') df = df.merge(merchant_actions, on='merchant_id', how='left') df['age_range'] = df['age_range'].fillna(-1) df['gender'] = df['gender'].fillna(-1) return df train_feat = build_features(train, user_info, user_actions, merchant_actions) test_feat = build_features(test, user_info, user_actions, merchant_actions) feature_cols = [c for c in train_feat.columns if c not in ['user_id', 'merchant_id', 'label']] X_train = train_feat[feature_cols] y_train = train_feat['label'] X_test = test_feat[feature_cols]

逻辑说明:build_features 函数把 merge 和 fillna 串起来,保证 train 和 test 走同一条路径。feature_cols 用列表推导排除 id 列和 label 列,剩下的就是模型输入。这里有个隐藏坑:如果 user_actions 和 merchant_actions 是从 train 里统计出来的,那 test 里的 user_id 可能没出现在统计结果中,merge 后会产生 NaN,需要再 fillna(0)。源码里在 build_features 之后加了一步 X_train = X_train.fillna(0),这一步不能省。

3. 两个基线模型怎么跑:LogisticRegression 与 RandomForestClassifier 的参数与结果对比

3.1 LogisticRegression.py 的完整运行流程

LogisticRegression.py 是这套源码里最容易跑通的脚本。它依赖 sklearn 的 LogisticRegression 类,输入是上一步构造好的 X_train 和 y_train。脚本里设置了 class_weight='balanced',这是处理不平衡样本的关键参数——它会让模型在训练时自动给少数类更高的权重,避免模型把所有样本都预测成 0。

from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, classification_report X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=42, stratify=y_train) lr = LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42) lr.fit(X_tr, y_tr) y_pred_proba = lr.predict_proba(X_val)[:, 1] print('AUC:', roc_auc_score(y_val, y_pred_proba)) print(classification_report(y_val, lr.predict(X_val)))

逻辑说明:train_test_split 里 stratify=y_train 保证验证集的正负比例和训练集一致,否则在不平衡数据上验证集可能一个正样本都没有。class_weight='balanced' 是核心参数,它按 n_samples / (n_classes * np.bincount(y)) 自动计算权重。max_iter=1000 是因为默认的 100 次迭代在特征维度较高时经常不收敛,会报 ConvergenceWarning。AUC 是这类比赛的标准评估指标,天猫赛题官方也是用 AUC 排名。classification_report 能让你看到 precision、recall、f1 三个指标在正负类上的分布,通常 recall 会偏低,这是不平衡数据的正常表现。

3.2 RandomForestClassifier.py 的调参与特征重要性

RandomForestClassifier.py 比逻辑回归多了一层参数调整空间。源码里设置了 n_estimators=100、max_depth=10、min_samples_split=5,这三个参数直接决定模型复杂度和过拟合程度。

from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=100, max_depth=10, min_samples_split=5, class_weight='balanced', random_state=42, n_jobs=-1 ) rf.fit(X_tr, y_tr) y_pred_proba_rf = rf.predict_proba(X_val)[:, 1] print('RF AUC:', roc_auc_score(y_val, y_pred_proba_rf)) import pandas as pd feat_imp = pd.Series(rf.feature_importances_, index=feature_cols).sort_values(ascending=False) print(feat_imp.head(10))

逻辑说明:n_estimators=100 是树的数量,太少会欠拟合,太多训练时间线性增长,100 是常见起点。max_depth=10 限制每棵树深度,防止在稀疏特征上过拟合。min_samples_split=5 表示节点样本数少于 5 就不再分裂。n_jobs=-1 让所有 CPU 核心参与训练。class_weight='balanced' 在随机森林里同样有效,原理和逻辑回归一致。feature_importances_ 输出每个特征的重要性得分,通常 user_action_count 和 merchant_action_count 会排在前列,这说明行为频次比用户画像更有预测力。如果某个特征重要性接近 0,可以考虑在后续迭代中删掉,减少噪声。

3.3 两个模型的对比与选型建议

维度LogisticRegressionRandomForestClassifier
训练速度快,秒级较慢,分钟级
可解释性强,系数直接反映特征方向中,靠 feature_importances_
不平衡处理class_weight='balanced'同样支持
典型 AUC0.60-0.650.63-0.68
调参空间小,主要调 C 和 penalty大,n_estimators、max_depth 等

选型建议:如果只是跑通流程交作业,逻辑回归足够,代码短、结果稳。如果想让 AUC 好看一点,随机森林更合适,但要注意 max_depth 不要设太大,否则验证集 AUC 会掉。源码里两个脚本都保留了,方便对比。实际比赛中,这两个基线通常会被 XGBoost 或 LightGBM 替换,但作为理解数据和验证特征工程的工具,它们已经够用。

4. 可视化 notebook 怎么用:从数据分布到模型评估的完整链路

4.1 天猫复购数据分析与可视化.ipynb 的结构

这个 notebook 是整套源码里信息密度最高的文件。它按顺序做了四件事:加载数据并查看基本信息、绘制 label 分布和用户行为分布、展示特征相关性热力图、绘制 ROC 曲线对比两个模型。每个 cell 都有 markdown 说明,适合直接拿来当实验报告的基础。

import matplotlib.pyplot as plt import seaborn as sns fig, axes = plt.subplots(1, 2, figsize=(12, 4)) train['label'].value_counts().plot(kind='bar', ax=axes[0], title='Label Distribution') train['action_type'].value_counts().plot(kind='bar', ax=axes[1], title='Action Type Distribution') plt.tight_layout() plt.show()

逻辑说明:label 分布图能直观看到正负样本比例,action_type 分布图能看出哪种行为最多。天猫数据里 action_type=0(点击)通常占绝大多数,action_type=3(购买)最少。这两张图在答辩时是很好的开场素材,能说明为什么需要处理不平衡。参数上,figsize 控制画布大小,tight_layout 防止标签重叠。

4.2 特征相关性热力图与共线性排查

notebook 里有一段用 seaborn 画 heatmap 的代码,计算的是数值特征之间的 Pearson 相关系数。这一步的目的是排查共线性——如果两个特征相关系数超过 0.9,说明它们携带的信息高度重叠,可以考虑删掉一个。

plt.figure(figsize=(10, 8)) corr = X_train.corr() sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', center=0) plt.title('Feature Correlation Heatmap') plt.show()

逻辑说明:corr() 默认算 Pearson 相关系数,只对数值列有效。annot=True 在格子里显示数值,fmt='.2f' 保留两位小数。cmap='coolwarm' 让正相关偏红、负相关偏蓝,center=0 保证颜色以 0 为中心对称。如果发现 user_action_count 和 merchant_action_count 相关系数很高,不用急着删,先看业务含义——它们分别刻画用户和商家,维度不同,即使数值相关也可能都有用。

4.3 ROC 曲线对比与阈值选择

notebook 最后一段把逻辑回归和随机森林的 ROC 曲线画在同一张图上,并计算 AUC 值。ROC 曲线的好处是它不依赖具体阈值,能全面反映模型在不同阈值下的表现。

from sklearn.metrics import roc_curve fpr_lr, tpr_lr, _ = roc_curve(y_val, y_pred_proba) fpr_rf, tpr_rf, _ = roc_curve(y_val, y_pred_proba_rf) plt.figure(figsize=(8, 6)) plt.plot(fpr_lr, tpr_lr, label=f'LR (AUC={roc_auc_score(y_val, y_pred_proba):.3f})') plt.plot(fpr_rf, tpr_rf, label=f'RF (AUC={roc_auc_score(y_val, y_pred_proba_rf):.3f})') plt.plot([0, 1], [0, 1], 'k--', label='Random') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.legend() plt.show()

逻辑说明:roc_curve 返回 fpr、tpr 和阈值三个数组,这里只用前两个。对角线虚线代表随机猜测,曲线越靠左上角越好。legend 里把 AUC 值嵌进标签,方便对比。阈值选择上,如果业务更看重召回率(比如宁可误推也不漏推),可以把阈值从默认的 0.5 调低到 0.3 左右,具体看 precision-recall 曲线的拐点。

5. 避坑与常见问题:跑这套源码时最容易翻车的五个地方

5.1 现象:运行 LogisticRegression.py 报 ConvergenceWarning

原因:默认 max_iter=100,在特征维度较高或数据未标准化时, lbfgs 求解器迭代不够,无法收敛。解决:把 max_iter 调到 1000 以上,同时对数值特征做 StandardScaler 标准化。源码里没有加标准化步骤,这是可以改进的地方。

5.2 现象:merge 后训练集行数变多

原因:user_actions 或 merchant_actions 里有重复的 user_id 或 merchant_id,导致 merge 时产生笛卡尔积。解决:在 groupby 之后加 drop_duplicates,或者用 validate='many_to_one' 参数让 pandas 在合并时检查关系。源码里没有加这个检查,数据干净时没问题,但换数据集就容易翻车。

5.3 现象:test_format1.csv 读取后 user_id 变成科学计数法

原因:pandas 默认把长整型推断为 int64,但某些 id 超过 10 位后显示为科学计数法。解决:读取时指定 dtype={'user_id': str},或者在 to_csv 时加 float_format。这个坑在提交结果时尤其致命,因为格式不对会被平台直接拒收。

5.4 现象:随机森林训练时间过长

原因:n_estimators 设得太大,或者 max_depth 没有限制,树在稀疏特征上无限分裂。解决:先把 n_estimators 降到 50 跑通流程,再逐步加到 100 或 200。max_depth 建议从 10 开始试,观察验证集 AUC 变化。n_jobs=-1 能利用多核,但如果数据量特别大,内存可能成为瓶颈。

5.5 现象:notebook 里画图中文显示为方块

原因:matplotlib 默认字体不支持中文。解决:在 notebook 开头加 plt.rcParams['font.sans-serif'] = ['SimHei'] 和 plt.rcParams['axes.unicode_minus'] = False。这两行是固定搭配,缺一不可,否则负号也会显示异常。

6. 进阶技巧:把 AUC 从 0.65 推到 0.70 的三个实操方向

第一个方向是时间窗口特征。原始数据只给了 5 月和 6 月的汇总,但用户行为的时间分布本身有信息量。我一般会按周切分,统计每个用户在每一周的行为次数,构造出 8 个周度特征。这样模型能捕捉到“临近预测期行为突然增多”的模式,AUC 通常能涨 1 到 2 个点。代码上就是在 groupby 里加一个 week 列,再 pivot 成宽表。

train['week'] = pd.to_datetime(train['time_stamp'], format='%Y%m%d').dt.isocalendar().week weekly = train.groupby(['user_id', 'week'])['action_type'].count().unstack(fill_value=0) weekly.columns = [f'week_{c}_count' for c in weekly.columns] train_feat = train_feat.merge(weekly, on='user_id', how='left')

逻辑说明:isocalendar().week 把日期转成周数,unstack 把长表变宽表,每个用户一行、每周一列。fill_value=0 保证没有行为的周填 0 而不是 NaN。merge 回主表后,这些周度特征和原有的汇总特征一起进模型。参数上,format='%Y%m%d' 必须和原始日期格式一致,否则 to_datetime 会解析失败。

第二个方向是交叉特征。user_id 和 merchant_id 单独看都是高基数类别特征,直接做 one-hot 会维度爆炸。常见做法是统计每个 user-merchant 对的行为次数,作为一列新特征。这个特征在原始数据里其实已经隐含了,但显式构造出来能让树模型更容易切分。

pair_count = train.groupby(['user_id', 'merchant_id'])['action_type'].count().reset_index() pair_count.columns = ['user_id', 'merchant_id', 'pair_action_count'] train_feat = train_feat.merge(pair_count, on=['user_id', 'merchant_id'], how='left')

逻辑说明:groupby 两个键后 count,得到每个用户-商家对的总行为次数。这个特征对随机森林特别有用,因为树可以在 pair_action_count 上直接做阈值切分。注意 merge 的键要同时包含 user_id 和 merchant_id,否则会错位。

第三个方向是模型融合。逻辑回归和随机森林的预测结果做加权平均,权重按验证集 AUC 分配。我试过 0.4 倍 LR 加 0.6 倍 RF,AUC 比单独用 RF 高 0.5 个点左右。代码很简单,就是两个 predict_proba 结果加权求和,但前提是两个模型的验证集划分必须一致,否则融合没有意义。

final_proba = 0.4 * y_pred_proba + 0.6 * y_pred_proba_rf print('Ensemble AUC:', roc_auc_score(y_val, final_proba))

逻辑说明:权重 0.4 和 0.6 是经验值,可以用网格搜索找最优,但差距通常不大。关键是两个模型要在同一个 y_val 上评估,所以 train_test_split 的 random_state 要固定。融合后的结果如果比单模型好,说明两个模型的误差有互补性;如果变差,说明它们犯了同样的错误,这时候换模型比调权重更有效。

从那以后我每次拿到一份新的比赛数据,都强制先跑一遍逻辑回归基线,把 AUC 记下来,再跑随机森林,最后看融合有没有提升。这套流程看起来笨,但能避免一上来就上复杂模型、结果连基线都没跑通的尴尬。希望帮到你。

本文还有配套的精品资源,点击获取

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

金融服务系统架构设计与高可用实战:从账户到对账的全链路解析

1. 项目概述:一个金融服务系统的真实样貌做金融科技这行快十年了,每年都会接触到大量以"financial-services"命名的系统项目。很多刚入行的朋友一看到这个名字就头大,觉得金融系统遥不可及,实际上拆开来看,它…

作者头像 李华
网站建设 2026/9/26 8:38:32

墨水屏与AI结合:打造高效笔记整理与检索方案

1. 墨水屏与AI结合,到底解决了什么核心痛点 第一次把墨水屏和AI搭在一起用,是在去年整理一批会议纪要的时候。当时手里攒了三个月的纸质笔记,翻起来费劲,想扫描成电子版又嫌麻烦,用平板记吧,屏幕盯久了眼睛…

作者头像 李华
网站建设 2026/9/26 8:38:29

金融服务平台架构实践:微服务、分布式事务与幂等设计

手上这个代号为 financial-services 的项目,是我去年带队从零搭起来的一套金融服务基础平台。它不是面向C端用户的App,而是公司内部统一的资产域:账户开立、余额变更、交易流水、支付渠道接入、对账通知这些能力,全都在这一层收敛…

作者头像 李华
网站建设 2026/9/26 8:38:22

WorkBuddy国际版与国内版架构差异及海外配置实战指南

1. 从一次海外部署翻车说起:为什么国内版跑得好好的,出海就出问题去年下半年,我帮一家做跨境电商工具的小团队做技术顾问,他们用 WorkBuddy 国内版做自动化工作流编排,本地跑得挺顺,结果业务扩展到东南亚和…

作者头像 李华
网站建设 2026/9/26 8:38:19

LLM推理中Prefill阶段的核心原理与工程优化

1. Prefill阶段到底在干什么:不是“热身”,而是大模型推理的真正起点Prefill这个词在LLM工程实践中常被轻描淡写地称为“首token生成前的准备阶段”,但这种说法极具误导性。它根本不是热身,而是整个自回归推理过程中计算密度最高、…

作者头像 李华
网站建设 2026/9/26 8:35:48

Web3数据科学:链上状态跃迁与非结构化特征工程

1. 为什么“Web3的数据科学”不是把Python脚本跑在区块链浏览器上很多人第一次听说“Web3的数据科学”,下意识反应是:不就是用pandas读取Etherscan导出的CSV,再画个交易量折线图?我试过——结果连最基础的地址字段都对不上。导出的…

作者头像 李华