简介:一套面向机器学习学习者与从业者的Python代码资源,专注数据建模与分析场景,系统覆盖广义可加模型(GAM)、梯度提升决策树(GBDT)、CART决策树、BP神经网络和深度神经网络(DNN)等主流算法,并配套相应数据与训练记录。压缩包共184个文件,以117个ipynb代码文件为核心,辅以35个csv数据集、13个txt说明、8个png图表、5个xlsx结果表及少量zip和json文件,整体大小约304MB,目录结构清晰,便于按需检索。目前已有110人浏览学习,每个模型都对应独立的数据集,可直观对比不同算法在同一问题上的性能表现,适合从数据处理、模型选择到结果评估的完整实践;数据集覆盖多种业务场景,有助于将算法与实际问题对应理解。具体实现中,GAM采用非参数平滑函数处理非线性关系,GBDT通过集成弱学习器提升预测精度,CART包含剪枝防止过拟合,BP与DNN则演示了神经网络从构建到训练的过程;代码覆盖数据预处理、参数调优和效果评估,既适合初学者跟随练习,也适合有经验者快速参考。
1. Python机器学习全套代码包:五个模型一次配齐,上手就能跑
做数据建模的人应该都有过这种经历:想对比GAM、GBDT、BP神经网络在同一个业务数据集上的表现,结果每个模型都要重新找代码、调库、处理数据格式,光准备工作就能耗掉大半天。这份“Python机器学习全套代码-数据建模与分析”压缩包,把五个常用模型的完整训练代码和配套数据集打包在一起,解压之后按顺序跑就能出结果。它解决的核心问题是:当你需要快速建立多个模型的基准对比,或者想系统学习从数据预处理到模型评估的完整流程时,不用再东拼西凑。适合两类人:一是刚入门机器学习、需要可运行实例来理解算法原理的学习者;二是工作中需要快速验证模型效果、做方案选型的从业者。包里每个代码文件对应一份独立数据集,模型和数据的匹配关系清晰,跑通一个就能类推到其余几个。
2. 数据与模型映射关系:五套数据集分别喂给谁
2.1 压缩包内部文件结构说明
拿到压缩包后,首先映入眼帘的是一批CSV文件和对应的Python脚本。从文件命名来看,这些数据文件分成了两组:第一组是语义清晰的业务数据,比如浏览记录.csv、广告.csv、用户.csv、叶子形状.csv;第二组是以d2p01F、d2p02F这种编码方式命名的数据文件,比如d2p02F.csv、d2p15F.csv、d2p14F.csv等。这种命名方式说明它们来自某个标准实验数据集库,d2p这个前缀通常是某个公开数据集采集批次或实验代次编号,后面的数字代表不同的采集场景或特征组合。这种命名在学术练习和教学场景中很常见,好处是数据来源可追溯,坏处是不看配套说明的话不容易直观判断内容。
从数据建模的角度看,这些CSV文件基本上都是二维表格结构,行是样本记录,列是特征和标签。浏览记录.csv可能包含用户ID、浏览时长、页面类别等字段,适合做分类或回归任务;广告.csv通常包含曝光渠道、点击行为、转化标记等字段,是CTR预测的典型素材;用户.csv偏向用户画像数据,可能含年龄、活跃度、消费分层等;叶子形状.csv则明显是图像特征向量化后的结果,每行代表一片叶子的形态学特征和类别标签。实际使用前,我建议先用pandas的info()方法快速查看每个CSV的列名、数据类型和缺失值情况,再决定用哪个模型去拟合。
2.2 业务数据集的模型匹配建议
根据我的实践经验,不同数据集适合的模型有明显倾向。用户.csv这类表格型数据,特征维度不会太高,样本量中等,非常适合用GBDT和CART来建模,因为树模型对特征尺度的鲁棒性好,不需要做标准化。广告.csv如果包含大量类别型特征(比如渠道ID、素材类型),GBDT同样是一个稳妥选择,它能自动处理类别特征的切分点选择。叶子形状.csv这种特征维度较高、且特征之间可能存在非线性关系的数据,BP神经网络和DNN更容易发挥优势,因为网络结构可以自动学习特征组合。
这里有一个选择模型的通用逻辑:先看样本量和特征维度的比例。如果样本量少于特征维度的10倍,优先考虑树模型或带正则化的线性模型;如果样本量充足且特征维度适中,神经网络模型可以尝试。再看数据中的关系复杂度,线性关系明显就用GAM或线性模型,非线性关系强则GBDT或神经网络更合适。这套资源的设计思路恰好覆盖了从统计模型到深度学习的谱系,你可以在同一份数据集上跑多个模型做对比,观察它们在训练集和验证集上的性能差异,这是理解模型选择原理最直接的方式。
提示:压缩包内每个代码文件都对应一套特定数据,跑代码之前先确认CSV文件名和脚本中read_csv()加载的路径是否一致,否则会报FileNotFoundError。
3. 从零构建GAM与CART:统计模型到树模型的落地
3.1 GAM广义可加模型的核心概念与适用前提
GAM(Generalized Additive Model,广义可加模型)是线性模型的自然延伸。传统的线性回归假设因变量和自变量之间是线性关系,形式为y = β0 + β1x1 + ... + βpxp。但实际业务中,很多关系并非直线,比如年龄与消费金额之间通常呈现倒U型曲线,收入与幸福感之间也存在边际递减效应。GAM的核心思路是保持可加性,但允许每个变量通过平滑函数f(x)来拟合,形式变为y = β0 + f1(x1) + f2(x2) + ... + fp(xp)。这样既保留了线性模型的可解释性,又能捕捉非线性关系。
在Python生态中,实现GAM的常见做法是使用pyGAM库或statsmodels的gam模块。pyGAM的接口比较简洁,fit()方法接收特征矩阵和标签向量,内部通过惩罚样条(penalized spline)来拟合每个特征的非线性函数。样条本质上就是分段多项式,在节点之间用低阶多项式逼近曲线,惩罚项控制曲线的光滑度,防止过度扭曲。使用GAM时要注意,它适合特征数量较少(10个以下)的场景,因为每个特征都要拟合一个平滑函数,特征太多会导致计算开销快速上升且解释性下降。
3.2 GAM代码实现与参数调优
假设你已经解压了资源包,浏览记录.csv中有浏览时长(browse_time)、页面深度(page_depth)和是否购买(purchase)三个字段。下面代码演示如何用pyGAM构建一个二分类GAM模型:
import pandas as pd from pygam import LogisticGAM, s from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score # 加载数据 df = pd.read_csv('浏览记录.csv') X = df[['browse_time', 'page_depth']].values y = df['purchase'].values # 划分训练集和验证集 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 构建GAM:两个特征都使用样条平滑项 gam = LogisticGAM(s(0, n_splines=15) + s(1, n_splines=15)) gam.fit(X_train, y_train) # 验证集预测与评估 y_pred = gam.predict_proba(X_val) print('GAM Validation AUC:', roc_auc_score(y_val, y_pred)) # 查看每个特征的贡献度 for i, term in enumerate(gam.terms): print(f'Feature {i} contribution range: ' f'({gam.partial_dependence(i)[0].min():.3f}, ' f'{gam.partial_dependence(i)[0].max():.3f})')代码逻辑分四步:第一步用read_csv加载数据并取出特征和标签;第二步按8:2比例分层划分数据集,stratify=y保证训练和验证集中购买样本的比例一致;第三步构建LogisticGAM模型,s(0, n_splines=15)表示对第0个特征使用15个基函数的样条平滑项,数字15是样条节点的数量,节点越多曲线拟合能力越强但过拟合风险越大;第四步在验证集上计算AUC作为评估指标。partial_dependence()方法输出每个特征在整个样本空间的边际贡献范围,用于判断特征影响的方向和强度。如果AUC不理想,优先调整n_splines,从10逐步增加到20,观察验证集AUC是否先升后降,找到拐点就是合适的复杂度。
注意:GAM对缺失值和异常值敏感,使用前建议用SimpleImputer填补缺失值,并用箱线图查看特征分布,对极端值做截断处理。
3.3 CART决策树的分裂逻辑与剪枝策略
决策树CART(Classification And Regression Trees)的原理可以用一句话概括:递归地将特征空间划分为矩形区域,使每个区域内的样本尽可能同质。分裂时选择特征的依据是基尼系数(分类)或均方误差(回归)。基尼系数的计算方式是:Gini = 1 - Σ(p_k)²,其中p_k是当前节点中第k类样本的占比。分裂的目标是让子节点的基尼系数加权和最小。这个计算过程在每个节点对所有特征的所有取值进行扫描,选择最优分裂点,因此决策树的训练本质上是特征选择过程。
决策树最大的问题是容易过拟合——树如果长得太深,会记住训练集中的每个细节,包括噪声。这就是剪枝必须存在的原因。剪枝分为预剪枝和后剪枝。预剪枝在构建过程中提前终止分裂,限制条件包括max_depth最大深度、min_samples_split最小分裂样本数、min_samples_leaf叶子节点最小样本数;后剪枝是等树完全长成后,自底向上用验证集误差判断是否合并某些叶子节点。实际工程中,大多数情况下用好预剪枝的三个参数就够了。scikit-learn中的DecisionTreeClassifier提供了完整的参数控制,下面是使用资源包中用户.csv构建CART模型的代码:
import pandas as pd from sklearn.tree import DecisionTreeClassifier, export_text from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, classification_report # 加载用户数据 df = pd.read_csv('用户.csv') # 假设特征列名为:age, activity_score, consumption_tier X = df[['age', 'activity_score', 'consumption_tier']] y = df['is_high_value'] # 是否为高价值用户 # 数据划分 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 构建决策树,预剪枝参数限制模型复杂度 clf = DecisionTreeClassifier( criterion='gini', max_depth=5, # 限制最大深度,防止过拟合 min_samples_split=10 # 内部节点至少10个样本才允许分裂 ) clf.fit(X_train, y_train) # 验证集评估 y_pred = clf.predict(X_test) print('Accuracy:', accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred)) # 输出树结构文本,辅助解释 print(export_text(clf, feature_names=['age', 'activity_score', 'consumption_tier']))这段代码中,max_depth=5是最关键的参数。深度为5的二叉树的叶子节点最多32个,模型复杂度和可解释性相对平衡。如果数据存在类别不均衡问题,在DecisionTreeClassifier中设置class_weight='balanced'可以缓解。min_samples_split=10的含义是当一个节点包含的样本数少于10时不再尝试分裂,这个参数对噪声数据有很好的抑制效果。export_text输出的树形结构可以用来向业务方解释模型决策规则,这是决策树相比黑盒模型的优势。如果验证集准确率明显低于训练集,说明过拟合,把max_depth降到3或4,同时把min_samples_split提升到20试试。
3.4 GAM与CART的选择边界对比
GAM和CART都能处理非线性问题,但适用场景有明显差异。GAM的优势在可解释性和统计推断,它能给出每个特征的影响函数形状,还能输出置信区间,适合需要向业务方解释特征影响方向的项目,但它的拟合能力较弱,无法捕捉特征之间的交互作用(除非显式添加交互项)。CART的优势是自动发现特征交互,比如“年龄大于30且消费分高”这种规则能被树结构自然表达,但单棵树的预测精度通常不高,且对特征空间的切分是分段常数,不够光滑。
从实践角度看,如果项目目标是探索性分析、理解数据规律,GAM是首选;如果目标是预测和分类,尤其特征存在交互关系时,CART(或者它的集成版本GBDT)更实用。这套资源把两个模型放在一起,正好可以做一个对比实验:在同一份数据上分别用GAM和CART建模,对比AUC或准确率,同时对比可视化输出——GAM画出每个特征的平滑曲线,CART画出树结构,这个对比过程对理解两种模型的本质差异非常有帮助。
4. GBDT与BP神经网络实战:梯度提升和反向传播的调参路线
4.1 GBDT的三个核心思想
GBDT(Gradient Boosting Decision Tree,梯度提升决策树)是目前表格数据领域最强大的机器学习算法之一。它能取得高精度的原因可以拆解为三个方面。第一是残差学习:每一棵新树拟合的是之前所有树的预测值与真实值之间的残差(梯度方向),而不是直接拟合目标值。比如预测房价偏差了10万,下一棵树就专门学习如何修正这个10万的偏差。第二是加法模型:最终预测是所有树的加权求和,每一棵树的权重通常由学习率决定。第三是梯度下降视角:在函数空间中使用梯度下降,损失函数的负梯度作为残差的近似值,这使得GBDT可以适配不同损失函数(回归用MSE,二分类用log loss)。
在Python中,主流实现是scikit-learn的GradientBoostingClassifier和LightGBM、XGBoost。资源包中如果代码基于sklearn实现,核心参数差异不会很大。GBDT的超参数很多,但真正影响结果的是三个:n_estimators(树的数量)、learning_rate(学习率)、max_depth(每棵树的深度)。三者之间存在联动关系:学习率越低,需要越多的树来达到同样的效果;树的深度越大,单棵树拟合能力越强,越容易过拟合。
4.2 GBDT参数调整策略与验证
下面是基于广告.csv构建GBDT二分类模型的完整流程。这个数据集的特征包括广告位ID、创意类型、曝光时间、历史点击率等,目标变量是用户是否点击:
import pandas as pd from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import roc_auc_score # 加载广告数据 df = pd.read_csv('广告.csv') # 假设特征列已做数值化编码处理 X = df.drop('clicked', axis=1) y = df['clicked'] # 划分数据 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 构建GBDT模型 gbdt = GradientBoostingClassifier( n_estimators=200, # 树的数量 learning_rate=0.05, # 学习率,低学习率配多棵树 max_depth=3, # 限制单棵树深度 subsample=0.8, # 每棵树使用80%的样本,引入随机性 random_state=42 ) # 训练模型 gbdt.fit(X_train, y_train) # 验证集评估 y_pred_proba = gbdt.predict_proba(X_val)[:, 1] print('GBDT Validation AUC:', roc_auc_score(y_val, y_pred_proba)) # 输出特征重要性 feature_importance = sorted( zip(X.columns, gbdt.feature_importances_), key=lambda x: x[1], reverse=True ) for feature, importance in feature_importance[:5]: print(f'{feature}: {importance:.4f}')参数组合的逻辑是:learning_rate=0.05决定了需要较多棵树,200棵树在这个学习率下通常能收敛,但具体最优值要看验证曲线。max_depth=3是防止过拟合的关键设置,GBDT整体树深度不宜太深,因为通过多棵树累加,每棵树只负责修正一部分残差,单棵树太复杂反而容易记住噪声。subsample=0.8表示每棵树随机抽取80%的样本训练,这个机制与随机森林的bootstrap采样类似,能降低方差。如果验证集AUC低于0.75,建议先调learning_rate(降为0.02)并同步增加n_estimators到500,再观察是否有提升;AUC还是没有变化,就需要检查特征工程,看是否有重要信息没有编码进特征。
提示:GBDT对类别型特征处理比较吃力,如果广告.csv中的广告位ID是字符串且类别数很多,务必先做数值编码(如目标编码或频率编码),不要直接用LabelEncoder,否则树模型会在无序编码上产生无意义的分裂。
4.3 BP神经网络的结构设计与训练细节
BP(Back Propagation,反向传播)神经网络是深度学习的基础模型,典型结构包含输入层、若干隐藏层、输出层。它解决的核心问题是如何对多层网络的权重进行学习。正向传播负责把输入信号经过每一层的加权求和与激活函数处理,传递到输出层并计算损失;反向传播则根据损失函数对每个权重的偏导数(梯度),从输出层逆向逐层更新参数,更新的基本公式是:w := w - η * ∂L/∂w,其中η是学习率。梯度计算依赖链式法则,这也是BP的核心数学基础。激活函数的选择对BP网络影响很大,隐层常用ReLU(f(x)=max(0,x))来避免梯度消失,二分类输出层用sigmoid得到概率值。
使用d2p开头的数据集时,由于不知道具体特征含义,通用做法是先标准化数据再进入网络。BP神经网络的权重初始化对结果影响显著,最简单有效的做法是使用xavier或he初始化(不过sklearn的MLPClassifier内部已处理)。下面的代码演示用MLPClassifier训练BP网络,同样以广告.csv为例:
import pandas as pd from sklearn.neural_network import MLPClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score # 加载数据 df = pd.read_csv('广告.csv') X = df.drop('clicked', axis=1) y = df['clicked'] # 数据划分 X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # 神经网络对特征尺度敏感,必须先标准化 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_val_scaled = scaler.transform(X_val) # 构建BP网络:单隐藏层,50个神经元 mlp = MLPClassifier( hidden_layer_sizes=(50,), # 单个隐藏层,50个神经元 activation='relu', # 隐层激活函数 solver='adam', # 优化器 alpha=0.001, # L2正则化系数 max_iter=500, # 最大迭代次数 random_state=42 ) mlp.fit(X_train_scaled, y_train) # 验证集评估 y_pred_proba = mlp.predict_proba(X_val_scaled)[:, 1] print('BP Validation AUC:', roc_auc_score(y_val, y_pred_proba))这段代码的关键设计在于数据标准化。BP网络的梯度计算依赖特征尺度,如果特征A取值范围是0到1,特征B是0到10000,那么权重更新的速度会严重不均衡,使用StandardScaler将每个特征变换到均值0、方差1的分布后,训练才能稳定收敛。hidden_layer_sizes=(50,)表示只用一个隐藏层、每层50个神经元,这是BP网络最常见的配置。如果验证集表现不佳,可以尝试(64, 32)这种双隐藏层结构,但要注意参数多了容易过拟合。alpha=0.001是L2正则化的强度,数值越大泛化能力越强但对数据拟合能力下降。max_iter=500如果训练不收敛会收到ConvergenceWarning,这时候不要盲目加大迭代次数,优先检查数据标准化是否做了,以及学习率是否合适。
4.4 GBDT与BP在表格数据上的选择逻辑
GBDT和BP在表格数据上的表现差异有规律可循。GBDT对特征缩放不敏感,不需要标准化,可以直接吃原始特征;它对异常值有一定鲁棒性,因为树的切分只依赖排序而非数值大小;GBDT在小样本场景下表现更稳定,且每个特征的重要性可以通过分裂增益统计出来,模型解释性好。BP需要特征工程配合,比如标准化、处理相关性高的特征;它的优势在于特征维度很高时(比如上百维),可以通过隐藏层的特征组合自动学习高阶特征交互,在图像、文本这类数据上无可替代。
实际选型时,我通常先跑一个GBDT作为baseline,因为它的参数鲁棒性最强,只要树的数量够、深度限制合理,AUC不会太差。如果GBDT的AUC达到0.75左右而BP只有0.70,不要认定BP不行,尝试增加隐藏层神经元数量、调整学习率或增加数据量。对于这份资源包里的叶子形状.csv这类数据,特征可能是图像的形状描述符,维度可能较高且关系复杂,BP和DNN更有可能发挥优势。
# 用一个简单实验对比三种模型在同一数据上的表现 import numpy as np from sklearn.metrics import roc_auc_score models = { 'GBDT': gbdt, 'BP': mlp, 'GAM': gam } # 注意:GAM和BP需要不同的数据预处理 # 对比结论:在广告数据上,GBDT通常AUC最高,GAM可解释性最好 for name, model in models.items(): # 实际使用时按各自要求处理数据后再传入 pass上面代码仅示意,真正的对比实验需要分别用原始特征(GBDT)、标准化特征(BP)、平滑样条(GAM)分别建模,然后用统一指标在同一个验证集上评估。这也是这套资源最值得做的实验:五个模型,五套数据,跑完就能得到一份模型对比表。
5. 常见问题排查与避坑指南:数据路径、特征尺度与过拟合
5.1 报错FileNotFoundError:数据文件路径与代码脚本不匹配
现象:运行代码脚本时立刻报错文件不存在,或者读取到的DataFrame列数不对,模型训练直接崩溃。
原因一:压缩包解压后,代码脚本中的read_csv()使用的是相对路径(比如'浏览记录.csv'),但当前工作目录不在数据文件所在目录;原因二:代码中加载的是d2p01F.csv但实际数据集文件叫d2p10F.csv,文件名搞混了。原因三:文件编码问题,某些CSV包含中文列名,默认utf-8编码读取时报UnicodeDecodeError。
解决:先统一工作目录。我的一般做法是在脚本开头强制切换目录,或者使用绝对路径拼接:
import os import pandas as pd # 将工作目录切换到当前脚本所在目录 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, 'data') # 加载数据时拼接完整路径 df = pd.read_csv(os.path.join(DATA_DIR, '浏览记录.csv'), encoding='utf-8-sig') print(f'数据形状: {df.shape}')encoding='utf-8-sig'比'utf-8'多了BOM头处理能力,兼容Windows下保存的带BOM的CSV文件,这是一类很常见的隐蔽问题。如果还是打不开,把CSV用记事本打开第一行,看看分隔符到底是逗号还是制表符,如果是制表符需要在read_csv中设置sep='\t'。
5.2 神经网络Loss曲线震荡不收敛
现象:训练BP或DNN模型时,损失函数在前几十个迭代周期内上下剧烈波动,验证集指标忽高忽低,最终收敛效果很差。
原因:最常见的是学习率设置过大,导致权重更新步长超过最优区域,在损失函数低谷附近来回震荡;其次是数据标准化缺失或做得不对,特征值域差异过大导致梯度方向不稳定;第三是mini-batch size太小(比如batch_size=1),单样本梯度噪声大。
解决:先确认数据已做StandardScaler标准化,然后降低优化器学习率。sklearn的MLPClassifier无法直接指定学习率,但有learning_rate_init参数(默认0.001),可以改到0.0001重试。如果使用自建网络或TensorFlow/PyTorch,通常的做法是给优化器加学习率衰减策略,或者从0.001起步递归减半:
# 以PyTorch为例(仅示意) # optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 每10个epoch将学习率乘以0.5 # scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)训练过程中记录每个epoch的loss数值,如果loss从0.5降到0.2之后不再下降且平稳,说明学习率偏低或模型容量不够;如果持续小幅波动,可以试试增大batch_size来稳定梯度。
5.3 GBDT过拟合:训练集AUC接近1.0,验证集只有0.7
现象:GBDT模型在训练集上预测几乎完美,AUC高达0.98以上,但验证集AUC徘徊在0.70左右,差距明显。
原因:树的数量太多或单棵树深度过深,模型记住了训练数据中的个体噪声。GBDT每一棵树都在拟合前序模型的残差,当训练次数多到一定程度后,残差中剩下的几乎都是噪声,树继续拟合这些噪声就是过拟合的直接机制。
解决:检查feature_importances_中重要性最高的特征是否存在无意义的ID类列(比如用户ID、广告ID),这类高基数特征容易被树切分利用,但对新样本没有泛化价值,直接删掉。然后调整参数:降低max_depth到2或3,提高min_samples_leaf到15以上,subsample降低到0.7。最有效的做法是早停:训练时保留验证集,每增加一棵树就计算一次验证集AUC,当连续10次没有提升时停止训练。sklearn的GradientBoostingClassifier没有内置早停,需要手动循环或改用自己的验证集做每轮检查,也可以切换到LightGBM直接使用early_stopping_rounds参数。
5.4 特征重要性分布异常:某一个特征占比超过90%
现象:查看GBDT的feature_importances_时发现某个特征贡献了绝大多数分裂增益,其它特征几乎被忽略,验证集AUC却不理想。
原因:这个特征可能是数据中的记录编号或时间戳等唯一性标识,与目标变量的关联是虚假的。比如广告.csv中如果有一个“曝光记录ID”字段,树模型会利用它记住每条样本,造成特征重要性虚高,而真实业务特征完全没有发挥作用。
解决:建模前先做特征审查,把方差低到极致的常量特征和基数接近样本量的ID特征剔除。我的习惯是写一个简单的特征筛查函数,计算每个特征的unique值数量,如果超过样本量的80%就标记为疑似ID特征,在建模时排除:
import pandas as pd df = pd.read_csv('广告.csv') n_samples = len(df) for col in df.columns: n_unique = df[col].nunique() if n_unique > n_samples * 0.8: print(f'警告: 特征 {col} 唯一值占比过高,可能为ID特征') # df = df.drop(columns=[col]) # 确认后取消注释执行删除5.5 GAM模型对异常值极其敏感导致效果退化
现象:GAM模型训练完成后,partial_dependence输出的曲线出现异常陡峭的拐点,验证集AUC甚至低于随机猜测的0.5。
原因:GAM中的样条平滑是基于所有样本点拟合的,极端异常值会严重拉动局部曲线形状。比如浏览时长这个特征,99%的用户在20分钟以内,但有几个异常值达到300分钟,样条函数会在异常值区间产生很大的局部波动,扭曲整体曲线。
解决:对连续特征做截断(winsorize)处理,把超过99分位数的值压缩到99分位数的位置。在GAM场景中,这个操作通常是标准的预处理步骤。我的做法是使用scipy的percentile逻辑自己写,不引入过多依赖:
import numpy as np def winsorize_series(s, lower=0.01, upper=0.99): """对序列做分位数截断,消除极端异常值影响""" q_low, q_high = s.quantile([lower, upper]).values return s.clip(lower=q_low, upper=q_high) df['browse_time'] = winsorize_series(df['browse_time'])截断之后重新训练GAM,观察曲线形状是否平滑很多。如果仍然异常,检查数据中是否有负值或不合理的离散值(比如浏览时长出现-1),这些需要单独处理。
6. 模型对比实验设计与快速验证技巧
在跑通五个模型的单点训练后,最高价值的操作是把它们放到统一起跑线做对比实验。这个过程本身就是一个完整的数据建模工作流:加载数据、统一划分、分别训练、汇总评估、输出对比表。我把这个流程封装成一个可复用的实验框架,每来一份新数据都能立刻跑出基线对比结果。
第一步是建立统一的样板代码。用一个参数字典描述每个模型的关键配置,然后循环训练和评估,收集结果:
import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score def run_model_comparison(X, y, model_configs, test_size=0.2, seed=42): """ 统一模型对比实验 model_configs: {'模型名': (模型实例, 是否需标准化)} """ X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=test_size, random_state=seed, stratify=y ) results = {} for name, (model, need_scale) in model_configs.items(): if need_scale: from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_tr = scaler.fit_transform(X_train) X_va = scaler.transform(X_val) else: X_tr, X_va = X_train, X_val model.fit(X_tr, y_train) y_pred = model.predict_proba(X_va)[:, 1] auc = roc_auc_score(y_val, y_pred) results[name] = round(auc, 4) print(f'{name}: AUC = {auc:.4f}') return results这个函数的关键参数是seed固定为42,作用是保证每次实验的数据划分一致,模型评估结果可比。注意,stratify=y只适用于分类问题且类别不是特别多的情况,如果y是连续值回归目标,去掉这个参数改用random_state控制划分。need_scale标志区分了GBDT这类树模型(不需要标准化)和BP、DNN这类神经网络模型(必须标准化)。
第二步是跑完一轮对比后的结果解读策略。假设GBDT的AUC是0.80,BP是0.76,GAM是0.72。不能直接下结论说GBDT最优,要先看差异是否在合理范围内。通常AUC差距小于0.03时可以视为统计学上无显著差异,需要进一步用交叉验证(比如5折)来确认。交叉验证代码可以复用上面的框架,在外面套一层KFold循环即可:
from sklearn.model_selection import StratifiedKFold # 5折交叉验证示例 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) cv_scores = [] for train_idx, val_idx in skf.split(X, y): X_cv_train, X_cv_val = X.iloc[train_idx], X.iloc[val_idx] y_cv_train, y_cv_val = y.iloc[train_idx], y.iloc[val_idx] # 内部复用run_model_comparison中的训练逻辑 # 将每个fold的AUC追加到cv_scores列表第三步是把实验过程沉淀成一个可读性强的输出。格式不需要花哨,一张CSV表格就够了:
results_df = pd.DataFrame([ {'Model': 'GAM', 'AUC': 0.72, 'Params': 'n_splines=15'}, {'Model': 'CART', 'AUC': 0.68, 'Params': 'max_depth=5'}, {'Model': 'GBDT', 'AUC': 0.80, 'Params': 'lr=0.05,n_est=200'}, {'Model': 'BP', 'AUC': 0.76, 'Params': 'hidden=(50,),alpha=0.001'}, {'Model': 'DNN', 'AUC': 0.78, 'Params': 'hidden=(128,64),dropout=0.2'} ]) results_df.to_csv('model_comparison_results.csv', index=False)这份CSV可以直接作为模型选型报告的依据。我习惯在跑完对比后额外做一件事:把排名前两名的模型做集成,比如将GBDT和DNN的预测概率取平均,通常能再提升2-3个百分点的AUC。但要注意集成前提是两个模型的误差相关性较低,如果它们预测错的样本高度重合,集成收益很小。
最后一个技巧是关于超参数快速搜索的。五个模型的参数组合很多,全部网格搜索跑一遍非常耗时。我的习惯是每类模型先固定其它参数,只调一个最敏感的参数。GBDT先调learning_rate,BP先调hidden_layer_sizes,GAM先调n_splines,CART先调max_depth。每次得到一个粗优值后,再用三五个候选值精调。不要一上来就GridSearchCV全参数搜索,那会把简单问题复杂化。使用这套资源的过程本质上是建立自己的建模检查清单:数据加载检查、特征审查、模型基线对比、参数粗调、结果记录。把这五个环节固化成流程后,后续处理任何表格数据都能在一个小时内得到可靠的基准结果,这份代码包的价值就从“能跑通”变成了“能复用”。
我从经历过一次数据没标准化直接硬跑BP导致loss下不去的翻车事故以后,现在每次做模型对比都强制走一遍上面的完整流程:先跑baseline,确认特征没问题,再谈调参。希望帮到你。
本文还有配套的精品资源,点击获取