简介:面向机器学习初学者与从业者的Python数据建模与分析代码集,覆盖广义可加模型GAM、梯度提升决策树GBDT、决策树CART、神经网络BP及深度学习DNN等主流算法,每种模型均配有独立数据集,可直接运行训练并观察建模过程。资源包内共184个文件,以notebook代码笔记为主,另有35个CSV数据文件、说明文档及少量可视化图片,整体压缩为304MB,目录组织清晰,便于按模型查找对应练习。目前已有110人学习下载,代码并非孤立脚本,而是完整记录数据预处理、特征分析、模型训练、参数调优与效果评估的整个流程,既能帮助新手系统入门,也可作为实际项目中的建模参考。通过这套代码,读者能快速理解不同算法的适用场景与实现细节,节省自行搭建环境与调试的时间,适合作为机器学习课程配套练习或竞赛前复习资料。
1. 拿到Python机器学习全套代码之后,为什么先别急着跑数据建模
拿到一份《Python机器学习全套代码-数据建模与分析》时,多数人的第一反应是打开笔记本,按目录顺序从头跑一遍。我第一次也这么干,结果在第三个脚本就卡住了——报错的不是算法,而是数据读进来之后列名对不上。后来我意识到,这类“全套代码”真正的价值,不是把每个算法写一遍,而是把数据建模与分析的完整链路压缩成可复现的步骤:环境、预处理、特征工程、建模、调参、评估。它适合两类人:一类是刚开始做数据建模、希望照着完整链路跑通的新手;另一类已经是业务里做分析的工程师,想用一套更规范的模板减少重复劳动。下面内容围绕“全套代码”如何落地,讲清楚每一步的关键参数,也把最容易翻车的地方单独拎出来。
2. 先搭出能跑通全套代码的Python环境:依赖锁定与两个版本坑
2.1 为什么先卡死在环境而不是算法
很多“全套代码”下载下来之后,执行pip install -r requirements.txt就能跑。但如果你是在公司内网、离线环境或者刚装好的Python 3.12上重新建环境,就会看到一串兼容性报错。根源是机器学习库之间的依赖不是线性的:pandas、numpy、scikit-learn 三者存在版本绑定,一个升级会连带着其他库被迫换版本。
我一般会强调一个原则:环境是数据建模最先要锁定的东西,而不是最后才处理。你从别人那里拿到一套代码,第一件事不是看模型,而是看它要求的Python版本和依赖范围。如果对方没有提供requirements.txt,我会用pip freeze生成一份快照,记录当前能跑通的环境。配合python -m venv做隔离,避免把系统Python环境弄乱。
实际项目中,我见过某开发者把代码从自己的笔记本复制到服务器上,原环境Python 3.9,服务器装了3.12,结果sklearn模型全部无法加载。这属于典型的“环境漂移”。所谓全套代码,如果缺少环境说明,只能算半套。所以在动手前,先用一小段时间把环境这步做扎实,后面才能往下走。
2.2 用虚拟环境和requirements.txt锁定底座
常见做法是:
# 创建虚拟环境,名字用 .venv 避免和源码目录混淆 python -m venv .venv # Linux/macOS 激活 source .venv/bin/activate # Windows 激活命令换成 .venv\Scripts\activate # 升级 pip,避免老版本 pip 解析依赖太慢 python -m pip install --upgrade pip # 安装核心依赖,版本范围给足但不过松 pip install "pandas>=2.0,<3.0" "numpy>=1.24,<2.0" "scikit-learn>=1.3,<1.6" "matplotlib>=3.7,<4.0"这段命令做了四件事:用虚拟环境把项目依赖隔离到当前目录;保证pip有足够能力解析新版包;安装数据建模最常用的四个库;用>=和<限定版本范围,既兼容旧代码,又不至于锁死导致小版本修不了的bug。需要注意,如果全套代码里用到xgboost、lightgbm这类提升树库,它们和numpy的版本耦合更紧,最好在同一个虚拟环境里统一安装,不要依赖全局环境。
离线环境是另一个高频场景。先在有网机器上执行pip download -r requirements.txt -d ./packages,再把整个packages目录拷到内网机器,执行pip install --no-index --find-links=./packages -r requirements.txt。这套命令能保证全套代码在封闭环境里也装得上,是“全套代码”落到生产环境的关键一步。
如果对方没有给requirements.txt,我一般用下面这个命令把import列表打出来,人工确认哪些是第三方库:
grep -h "^import\|^from" *.py | sort -u这条命令会把当前目录所有Python文件里的import语句汇总去重。不要小看这一步,它能在不运行代码的情况下快速找出依赖全集,避免在pip install时报安装一半失败。
锁定环境的另一个务实做法是把Python版本写进.python-version文件。虽然看起来多此一举,但半年后你回看项目时,能立刻知道当时跑通的Python是3.10还是3.12。配合requirements.txt,相当于给全套代码配了环境后悔药。有人问为什么不用conda,conda在解决非Python依赖时有优势,但如果你只是跑机器学习建模,venv足够。conda环境下切换Python版本方便,代价是环境体积大、解析慢。我一般只在需要指定gcc或hdf5等系统库时才用conda。
2.3 两个最容易踩的版本坑
坑一是numpy版本与Python版本的绑定。比如Python 3.10以下的环境里强行装numpy 2.x,几乎所有从numpy导入C扩展的库都会在import阶段爆错。我一般先执行python -c "import numpy; print(numpy.__version__)"确认当前版本,再安装其他库。如果全套代码里已经有requirements.txt,尽量以它为准,不要自作主张升级大版本。
坑二是scikit-learn的pickle兼容问题。旧版本训练出的模型,在新版本里joblib.load可能直接加载失败,症状是“找不到类”或者“AttributeError: X has no attribute Y”。解决方法是:如果是自己训练,训练和加载务必用同一环境;如果是别人给的全套代码,不要试图在新环境里加载别人的旧模型,重新训练一次比折腾兼容性更省时间。
还有一个容易被忽略的点:如果全套代码里包含PyTorch或TensorFlow,它们对CUDA的依赖会让环境问题放大。我遇到过目标机器没有GPU却安装了GPU版torch的情况,代码仍能跑但性能极差。安装大库之前,先明确运行环境是CPU还是GPU,再选择对应的安装源。
以下表是我常用的依赖范围,供你在没有更明确依赖文件时做参考。这只是起点,不是万能答案。
| 库 | 版本建议 | 排查重点 |
|---|---|---|
| pandas | >=2.0,<3.0 | 列类型变化后API差异 |
| numpy | >=1.24,<2.0 | C扩展库兼容性 |
| scikit-learn | >=1.3,<1.6 | pickle跨版本兼容 |
| matplotlib | >=3.7,<4.0 | 中文字体与GUI后端 |
环境跑通后,先别急着进模型。下一步是把数据预处理模块化,这才是数据建模与分析真正拉开差距的地方。
3. 数据建模的起点:把数据清洗和特征工程写成可复用函数
3.1 全套代码里最容易被跳过的数据质量体检
很多“Python机器学习全套代码”会先把数据read进来,然后直接dropna + get_dummies,就进入模型训练。这种代码在demo上能跑通,在真实业务数据上几乎必翻车。真实数据的缺失模式、分布偏斜、类别基数都和demo完全不同。比如某流失预测项目里,用户年龄字段缺失了40%,如果直接dropna会损失近一半样本,后续所有模型都会被带到沟里。
我拿到任何建模任务,第一步是给数据做一次体检,而不是急着建模。重点看三件事:缺失率超过30%的特征,除非业务上非常关键,否则考虑删除或单独编码;数值型特征是否存在极端值,比如金额字段出现负数、年龄出现200;类别型特征的唯一值数量,如果某个特征有上万种取值,直接做One-Hot会撑爆内存。
体检代码一般长这样:
# check_data.py import pandas as pd def data_health_report(df): report = pd.DataFrame({ 'dtype': df.dtypes.astype(str), 'missing_rate': df.isna().mean().round(4), 'unique_count': df.nunique(), 'sample_values': df.apply(lambda s: list(s.dropna().unique()[:3])) }) # 单独标注高缺失和低基数特征,方便后面决定处理策略 high_missing = report[report['missing_rate'] > 0.3].index.tolist() print('高缺失特征:', high_missing) return report这里的missing_rate保留了四位小数,阈值0.3是我常用的默认值,不代表所有情况都该删。如果特征缺失30%但对业务解释很重要,优先考虑置为固定值再加一个“是否缺失”标记。sample_values这个字段看着不起眼,但它能让你快速发现脏数据,比如“性别”列混入了“0”和“男”两种写法,这类数据只有人眼才能发现。
3.2 用统一的预处理Pipeline替代散落的代码
“全套代码”最大的痛点是每个脚本里都写了一遍fillna、StandardScaler、LabelEncoder。一旦训练集和测试集分开处理,很容易出现编码不一致。常见做法是:用一个Pipeline把数值列和类别列分开处理,只对训练集拟合,再transform到测试集。
# build_preprocessor.py from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.preprocessing import OneHotEncoder, StandardScaler def build_preprocessor(num_cols, cat_cols): # 数值列:中位数填充 + 标准化,中位数比均值更抗离群值 num_pipeline = Pipeline([ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) # 类别列:众数填充 + 独热编码,handle_unknown避免测试集出现新类别时报错 cat_pipeline = Pipeline([ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore')) ]) preprocessor = ColumnTransformer([ ('num', num_pipeline, num_cols), ('cat', cat_pipeline, cat_cols) ]) return preprocessor这个函数的两个关键参数值得展开说。SimpleImputer(strategy='median')对含离群点的连续特征更稳,均值容易被极端值带偏。OneHotEncoder(handle_unknown='ignore')是专门对付“训练集没有某类别、测试集突然出现”的,ignore之后该类别会变成全零向量,而不是直接抛异常。
使用它在训练和测试集上:
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y) preprocessor = build_preprocessor(num_cols, cat_cols) # 关键:fit_transform只能用在训练集 X_train_processed = preprocessor.fit_transform(X_train) X_test_processed = preprocessor.transform(X_test)这里特别注意:fit_transform只能用在训练集。如果在测试集上也调用fit_transform,等于让测试集参与了参数学习,属于最常见的数据泄漏。很多“全套代码”在线下跑出高分、线上验证大跌,原因就是测试集被“提前看过”。对线性模型来说,一旦测试集的均值/方差也参与了标准化,最终评估就失去了参照意义。
3.3 参数怎么改:缺失率、编码方式和离群值处理边界
不同建模场景对预处理的容错完全不同。拿缺失值来说:树模型(随机森林、XGBoost)本身能处理缺失,可以少填充或不填充;线性回归、SVM、神经网络对缺失敏感,必须填充或删除。全套代码里如果只有一个统一的填充策略,在换模型时记得再回来调整。
类别编码方面,如果类别基数小于8,One-Hot没问题;如果超过几十甚至上百,One-Hot会制造上千个稀疏列,这时优先用目标编码(target encoding)或类别嵌入。全套代码如果只是用pd.get_dummies,在变量多的时候会非常吃内存。目标编码有个风险,它用目标变量来编码类别,容易产生数据泄漏,需要配合交叉验证独立计算,新手不要直接对全量数据编码。
离群值处理边界:不要一上来就删。先算z-score或IQR,再看样本量。样本量在几万以上时,删掉极少数异常点影响不大;如果总共只有几百条样本,删一条都可能改变结论。我常用的做法是先保留异常值跑一次模型,再删除后跑一次,对比指标再决定。这是最朴素也最可信的验证方式。
标准化和归一化也经常被搞混。StandardScaler对近似正态的字段有效;MinMaxScaler对有限取值区间更友好。线性模型和神经网络通常需要把特征放到同一量级,树模型不需要。全套代码里常见的问题是对所有特征无脑标准化,这不会让模型崩溃,但会让可解释性变差。我的习惯是:树模型只做缺失值处理,线性模型才做标准化。
为什么Pipeline比手动fillna + scale更好?Pipeline放在sklearn的交叉验证里不会被拆散,避免“先全量标准化再交叉验证”导致的信息泄漏。用ColumnTransformer能把多个处理串成一个对象,配合后面的GridSearchCV一起用最方便。这也是我推荐把预处理写成函数的原因:改动一次,所有调用点同步生效。
特征工程这一步,是把业务理解转成代码的环节。常见的坑有两个:一是用了未来信息,比如用客户当月的实际续费金额预测月底是否流失,这等于把答案当特征;二是对同一份数据反复做同样的特征,导致模型过拟合。全套代码里的特征工程部分,建议先审查每个特征的业务含义,而不是直接跑。宁可少而准,不要多而乱。
4. 建模与调参:从默认参数到网格搜索,避免被“假精度”误导
4.1 先定基准模型,再谈调参
装好环境、做完预处理,很多人直接上XGBoost然后开始fine-tune。我不建议这么做。原因是“全套代码”里如果给你的是复杂模型的调参流程,你很难判断哪些提升来自参数,哪些来自碰运气。
我一般会先跑一个简单基准,比如逻辑回归或决策树,记录它在交叉验证下的AUC或F1。然后用复杂模型去对比,如果复杂模型在多个fold上都没有稳定超过基准,说明问题不在模型,而在特征或数据质量。这一步能劝退大量无意义调参,也能在汇报时给出“为什么需要复杂模型”的正当理由。
# baseline_model.py from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score # 用逻辑回归做基准,max_iter给足避免不收敛 baseline = LogisticRegression(max_iter=1000, random_state=42) scores = cross_val_score(baseline, X_train_processed, y_train, cv=5, scoring='roc_auc') print('baseline AUC: %.4f (±%.4f)' % (scores.mean(), scores.std()))max_iter=1000是逻辑回归在特征量较大时最容易忽略的参数,默认值100在部分求解器下会不收敛。cv=5只是起始值,样本量少时一般用3或分层5折。cross_val_score返回的是每个fold的分数,看均值和标准差才有意义:均值看模型整体水平,标准差看稳定性。如果标准差大于0.05,说明模型对数据划分很敏感,先不要急着调参,回去看数据是否有分段异常。
4.2 用网格搜索还是随机搜索:全套代码里的标准框架
数据建模与分析流程里,调参是最花时间也最容易被过度设计的一环。我的原则是:参数少时用网格搜索,参数多时用随机搜索。如果计算资源有限,随机搜索通常更划算,因为它能在相同的尝试次数里覆盖更大的参数空间。
下面是一个结合预处理Pipeline和交叉验证的GridSearchCV例子:
# tune_model.py from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV, StratifiedKFold # 这部分假设 preprocessor 已经在上一步定义好 model = RandomForestClassifier(random_state=42) # 参数网格只放最敏感的三个:树数量、最大深度、最小叶子样本数 param_grid = { 'n_estimators': [100, 200], 'max_depth': [5, 10, None], 'min_samples_leaf': [1, 4] } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) grid_search = GridSearchCV( model, param_grid, cv=cv, scoring='roc_auc', n_jobs=-1, refit=True, verbose=1 ) grid_search.fit(X_train_processed, y_train) print('best params:', grid_search.best_params_) print('best cv score:', grid_search.best_score_)这段代码里scoring='roc_auc'针对二分类;多分类建议换成f1_macro或roc_auc_ovr。refit=True表示用完整训练集重新训练一遍最优参数模型,这样后面拿到的grid_search.best_estimator_才能直接用于测试集。n_jobs=-1用满所有核,但如果你同时开着好几个脚本,整机资源会被拖垮。
不要觉得GridSearchCV输出best_score就是最终精度。它是在训练集内部交叉验证得到的分数,只能用来比较参数好坏;最终对外报告要看留出集上的分数。这是数据建模与分析里最容易被误解的一点。很多“全套代码”把best_score打印在最后,让人误以为模型精度就是它。
4.3 参数范围和搜索结果的使用边界
参数范围设定有实测经验可抄。随机森林里max_depth默认None容易过拟合,对中小数据集我一般从3到15试。min_samples_leaf从1开始,但如果数据有噪音,4或8往往比1更稳。n_estimators超过300后边际收益极小,耗时却线性上升。所以我把搜索重点放在深度和叶子节点上,树数量只放两个档位。
代码里如果把preprocessor和model一起放进Pipeline,参数名要加前缀。例如:
# 把预处理和模型放进同一个管道 from sklearn.pipeline import Pipeline pipe = Pipeline([ ('preprocessor', preprocessor), ('model', RandomForestClassifier(random_state=42)) ]) param_grid = { 'model__n_estimators': [100, 200], 'model__max_depth': [5, 10, None] }这里model__n_estimators中的双下划线是sklearn的固定语法,用来区分Pipeline里不同环节的参数。如果忘记加前缀,GridSearchCV会报“Invalid parameter”错误。这个细节也是新手常问的点。
提升树(XGBoost/LightGBM)的常用搜索范围是:learning_rate 0.01到0.3,越小越稳但需要更多树;subsample 0.7到1.0,控制每棵树用的样本比例;n_estimators配合early_stopping_rounds,不必网格搜太多。在XGBoost中指定early_stopping_rounds=10,如果连续10轮验证集分数不升就停,能明显节省时间。全套代码如果给了这个参数,说明作者真的跑过大任务。
我见过有人把调参结果完全照搬,不看业务背景。某个流失模型在A客户里最优的min_samples_leaf=2,但B客户数据量大了10倍,同样的参数导致过拟合。参数是数据的函数,不是固定答案。每次拿到新数据,重跑一遍搜索比记忆“最佳值”可靠。调参结束后,我会输出特征重要性,检查是否出现“无业务含义但重要性极高”的特征。如果出现,十有八九是特征泄漏。例如用客户当月的投诉次数预测当月的流失,投诉本身就是结果,模型分数虚高是必然的。
评估指标的选择也影响调参方向。分类问题如果样本不平衡,AUC相对稳健;但如果业务关心少数类召回,F2或recall@K更合适。回归问题用RMSE或MAE,RMSE对离群值敏感,MAE更抗噪。全套代码里通常只默认一个指标,我习惯在建模前就定好最终指标,调参全程用它,否则调参会变成“用不同指标轮流证明模型不错”。
5. 避坑:跑全套代码最常见的5个翻车点与排查建议
以下5个问题来自我处理数据和建模时反复遇到的场景,不一定每套代码都会全部遇到,但只要遇到一个,就足以让人怀疑整套方案。读的时候建议对照自己的代码流程逐条排查。
5.1 数据泄漏:为什么交叉验证的分数虚高
模型在交叉验证里AUC 0.95,放到线下小流量测试只有0.6,这种“线下好、线上差”的场景,第一嫌疑就是数据泄漏。最常见的泄漏是预处理跑到了全量数据上,或者特征里包含了目标值同期的信息。比如用当月的账单逾期金额预测当月的客户流失,逾期本身就是流失的强相关信号,模型拿到这个特征后分数当然虚高。再比如在train_test_split之前就做了标准化,测试集的统计信息被模型看到,交叉验证分数也会被抬高。解决方法是把所有需要学习的处理步骤放进Pipeline,只在不同fold的训练部分拟合。具体做法是用ColumnTransformer加GridSearchCV,而不要手动fit_transform整个数据集。同时检查特征生成时间,确保特征只使用预测时刻之前的信息。我排查泄漏时只问自己一句:这个字段在我真正做预测的那一刻,一定拿得到吗?拿不到就是泄漏,没有第二种解释。
5.2 随机种子不一致:结果无法复现
同一套代码跑两次,模型分数和预测结果不同,导致复盘困难。在需要向业务方解释模型时,这是最尴尬的场景。原因主要有两个:一是代码里有的地方设了random_state,有的地方没设;二是sklearn、train_test_split、交叉验证各自依赖不同的随机数生成器,全局环境一换,结果就飘。解决方法是把所有可能随机的地方都固定,并且不要只依赖sklearn的随机种子,numpy和random也要单独固定。代码可以写成:
import numpy as np import random as rn from sklearn.model_selection import train_test_split # 全局固定随机种子,保证脚本可复现 SEED = 42 np.random.seed(SEED) rn.seed(SEED) # 分类问题用 stratify 保持类别比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=SEED, stratify=y)这里stratify=y对分类问题尤其重要,它能保持训练集和测试集的类别比例一致。对回归问题则不需要。如果用了xgboost,记得在其参数里也设置random_state=SEED,因为它自己维护随机数生成器,不读全局种子。
5.3 特征顺序和维度不一致:训练能跑,预测时报错
训练阶段一切正常,但在部署或批量预测时报“ValueError: Number of features of the model must match input”。原因往往是训练时的特征列顺序和预测时不一致。比如训练时用df[['a','b','c']],预测时数据源列名变成['a','c','b'],模型只匹配列的位置,不匹配列名,一旦顺序变化就报错。这在使用pipeline时特别容易隐藏,因为ColumnTransformer在训练时会重排列。解决方法是把所有特征处理都放到同一个ColumnTransformer里,并保存列名快照。预测前用model.feature_names_in_检查当前输入列是否完全一致。我常在训练结束前加一行断言:
# 用 sklearn 记录的 feature_names_in_ 做最后校验 assert list(pipeline.feature_names_in_) == list(predict_df.columns), "特征列不一致"这条断言看似多余,但在模型上线时能救你一命。
5.4 类别不平衡导致“准确率很高,但实际没用”
全套代码输出的准确率95%,但业务人员投诉模型没用,只预测了多数类。比如流失率只有5%,全部预测“不流失”也能拿95%准确率。这类问题的根因是正负样本比严重失衡时,准确率被多数类主导,模型学到的最优策略是“永远猜多数类”,对业务毫无意义。解决方法是分类评估不要只看准确率,改用AUC、F1、召回率、精确率,并在训练时使用class_weight='balanced'或过采样。至少要保证测试集的类别比例与真实一致,也就是在train_test_split里加stratify=y。如果全套代码只输出了准确率,说明作者没有做这个基础评估。在调参时,scoring也要跟着换成f1或recall,否则GridSearchCV会继续优化一个对业务无用的指标。
5.5 中间产物没保存:调试成本被无限放大
你改了一个特征,需要重跑整个流程,但数据清洗一步跑一次要40分钟。每次调整都在重复劳动,实验效率极低。原因是脚本直接从原始数据流到模型输出,没有保存清洗后或特征工程后的中间数据集,一旦需要回溯只能从头跑。解决方法是给流程加一个缓存层,比如跑完预处理后保存X_train_processed.parquet,下次加载直接用:
import pandas as pd import os processed_path = 'data/processed_train.parquet' if os.path.exists(processed_path): # 数据不变时直接读缓存,省去重复清洗 X_train_processed = pd.read_parquet(processed_path) else: X_train_processed = preprocessor.fit_transform(X_train) pd.DataFrame(X_train_processed).to_parquet(processed_path)注意这里的缓存只适用于数据不变的情况。如果你改了预处理逻辑,必须手动删除缓存文件,否则会继续用旧的中间结果,产生的错误非常隐蔽。我吃过这个亏:改完缺失值策略后忘了删缓存,后面所有实验都是基于旧特征的,白白浪费了几天。
6. 把全套代码变成自己的东西:三个随手能用的增量技巧
调参与评估做完,整套代码已经能稳定产出分数。但如果只是能用,半年后再回来大概率看不懂。我一般会再做三件小事,让代码从“能用”变成“好用”。
第一件事:把实验配置抽成字典,所有可变项放在顶部。比如:
# experiment_config.py EXPERIMENT = { 'data_path': 'data/raw.csv', 'target': 'churn', 'test_size': 0.2, 'random_state': 42, 'model': 'random_forest', 'param_grid': { 'n_estimators': [100, 200], 'max_depth': [5, 10, None] } }这样做的好处是:换数据、换目标列、换调参范围时,只改配置不动逻辑。新手可能觉得多此一举,但当你同时维护三个建模任务时,配置驱动能避免改错函数。
第二件事:自动记录每次实验的分数和参数,追加到一个CSV里,而不是只打印在控制台。记录的内容包括时间、数据版本、特征集、模型、参数、CV分数、留出集分数。这样调试时能快速对比历史结果,不用翻聊天记录。
第三件事:保留一个最终留出集。调参过程中不要在同一个留出集上反复评估,它应该只被用一次,作为对外宣称的最终指标。我自己的习惯是:先切出20%放一边,所有探索和调参都在剩余80%内部的训练/验证集完成,最后跑一次留出集,记录结果就不再碰。这是检验模型是否真的稳的好方法。
我自己最深刻的一次教训,是把GridSearchCV的best_score当成最终指标去汇报,结果业务方自己复现时分数差了不少。原因是best_score是内部验证分数,不是留出集分数。从那以后,我每次调完参都会用一个从未参与训练和搜索的样本集做最终验证,分数才敢写进报告。数据建模与分析这条路,没有“万能的完整代码”,只有不断被验证的流程。希望帮到你。
本文还有配套的精品资源,点击获取