资讯详情 Python机器学习代码整理:从零构建Kaggle基线模板
📅 2026/10/14 14:26:05
拿到《Python机器学习及实践从零开始通往Kaggle竞赛之路》这本书的时候我正处于一个比较尴尬的阶段书能看懂例子也都能跑通但合上书自己写就卡壳。尤其到了特征工程和模型调参这两块书里的代码一步一步看完全没毛病真挪到自己的数据集上就不知道从哪下手了。后来我下定决心花了大概三周时间把整本书的代码从头到尾重新整理了一遍——不是复制粘贴跑一遍而是逐行理解、重新组织、换新版本API、加上注释和统一封装最后整理出一套可以直接拿去Kaggle当baseline的代码骨架。这篇东西就是这次整理过程的完整复盘包括我怎么分模块、怎么处理数据、踩了哪些坑、最后沉淀成了什么。如果你也处于“学过机器学习但写不出完整项目”的阶段或者想用最短时间建立一套自己的机器学习代码模板这篇文章应该对你有用。我尽量把每个决策背后的理由也写清楚不光是给你看结果更重要的是让你知道我当时为什么这么选。1. 为什么值得把这本书的代码重新整理一遍1.1 从“读得懂”到“写得顺”之间隔着什么很多人学机器学习都会经历这样一个过程看教材时觉得逻辑清晰每个模型的原理也能讲上几句但一打开编辑器就不知道先import什么写完数据处理又不知道下一步该干嘛。说白了这是“知识”和“技能”之间的落差。这本书的代码有个特点短小、直接、每段代码只为说明一个知识点。比如讲逻辑回归它就给你一个读取数据、划分训练集、训练模型、输出准确率的四步式代码。这种写法对教学是友好的但对工程实践的指导性偏弱。真实比赛或项目里的数据处理流程远比书上复杂你需要的不只是“能跑的demo”而是一套有层次的、可复用的流程。我整理代码的第一个目标就是把这个落差补上把书里零散的demo变成结构化的模块让每一步都能对应到真实项目里应该做的事。1.2 整理代码前先给自己定三条规矩开始之前我给自己定了三条规矩这三条规矩决定了后面所有工作的走向第一所有代码必须逐行读懂才允许往下整理。看不懂的地方先去查官方文档查完还不行就自己写个小实验验证。宁可慢一点也要保证自己真的理解每个参数在干什么。第二所有代码统一接口风格。比如数据集放在什么位置、模型训练的函数长什么样、输出结果用什么样的格式全都要统一。这样以后换数据、换模型时改动的成本才会降到最低。第三保证可复现。每次运行固定随机种子、固定数据划分方式、记录依赖库的版本。机器学习里“这次能跑下次跑不了”的问题大多和不设随机种子、依赖版本漂移有关这三件事做在前面能省掉很多麻烦。1.3 整理完之后的成果长什么样三周之后我手里多了一套完整可运行的代码库。结构大概是这样的book_reproduction/ ├── data/ │ ├── raw/ # 原始数据统一放这里 │ └── processed/ # 预处理后的数据 ├── notebooks/ # 调试用的notebook ├── src/ │ ├── data_preprocessing.py │ ├── feature_engineering.py │ ├── models.py │ ├── evaluate.py │ └── utils.py ├── results/ # 输出结果、模型指标 └── requirements.txt这套结构既是书里知识点的载体也是一个可以直接起步的机器学习项目模板。后面遇到任何新的表格类数据集我都能从这套代码框架上快速改出第一版baseline。这个经历让我确信整理代码的意义不在于代码本身而在于整理过程中建立起的对项目流程的整体感。2. 整理前的准备工作环境、数据与目录规划2.1 环境配置的版本选择与依赖管理环境是整理的第一道门槛。这本书比较典型用的是主流科学计算栈核心依赖有scikit-learn、pandas、numpy、matplotlib后面讲深度学习时还会用到TensorFlow/Keras或者类似的框架。我这里的环境用的是Python 3.8系列虚拟环境单独建了一个没有直接装到系统环境里。原因很现实机器学习库的依赖关系比普通应用复杂版本之间经常打架特别是scikit-learn这种API经常变的库。我见过太多人装了新版库之后跑旧教程代码直接报错就是因为没有做环境隔离。requirements.txt里的核心依赖大概是这样的numpy1.19 pandas1.1 scikit-learn0.24 matplotlib3.2 seaborn0.11 jupyter如果你已经装了anaconda直接建一个独立环境就好。安装的时候建议用国内镜像不然下载大包真的很难受。2.2 数据集的本地化处理与统一目录规划书里的数据大多是自带的小数据集比如经典的鸢尾花Iris、手写数字MNIST、泰坦尼克号Titanic这类。整理代码时我做的第一件事不是写代码而是把所有数据集下载好统一放进data/raw目录。这一步很有必要。第一机器学习教程里常用的数据集来源比较杂有的在sklearn里内置有的需要单独下载有的还在一些开源仓库里。每次跑代码都联网下载一旦网络不稳定就会中断你根本分不清是代码问题还是网络问题。第二比赛场景下数据就是一份csv文件放在本地提前养成“数据先落地、代码只读本地路径”的习惯后面进入真实比赛时会顺畅很多。文件路径规划也有讲究。我用了两个级别的目录原始数据raw和预处理数据processed。raw只放初始拿到的东西任何清洗、加工后的结果都写到processed不覆盖原文件。这样万一调试过程中把数据处理坏了还能回退到原始状态重来。2.3 统一接口约定随机种子、路径管理与配置整理代码时有几个细节值得单独说因为它们看似不起眼却直接决定代码能不能“二次使用”。第一是随机种子。我在utils.py里写了一个简单函数def set_seed(seed42): 统一设置随机种子确保结果可复现 import random import numpy as np random.seed(seed) np.random.seed(seed)为什么必须做这一步因为机器学习里很多过程是随机的——数据划分、模型初始化、随机梯度下降的顺序等。不固定种子的话同样的代码每次跑出来的结果都会有细微差异。初学者很容易被这种差异误导以为模型“不稳定”实际只是随机性在起作用。第二是路径管理。我没有用一堆字符串拼接路径而是用pathlib做了统一处理from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent RAW_DIR BASE_DIR / data / raw PROCESSED_DIR BASE_DIR / data / processed RESULT_DIR BASE_DIR / results为什么不直接用硬编码路径因为Notebook和脚本运行时的工作目录经常不一致用绝对路径写死的话换个机器、换个目录就得改代码。用相对项目根目录的方式整个代码库不管放在哪里都能正常跑。3. 全书代码模块化复盘从入门demo到比赛baseline3.1 数据加载与探索性分析一切工作的起点梳理书里的代码我第一个整理的模块是数据加载和探索性分析EDA。这本书在讲每个模型前都会先加载数据、看看数据长什么样。代码本身很简单但里面有个思路值得强化建模前先花时间理解数据结构这个习惯越早养成越好。我把数据加载统一封装成了一个函数def load_data(filename: str, directory: Path RAW_DIR) - pd.DataFrame: 统一的数据加载入口支持csv/tsv格式 filepath directory / filename if filepath.suffix .csv: return pd.read_csv(filepath) elif filepath.suffix .tsv: return pd.read_csv(filepath, sep\t) else: raise ValueError(fUnsupported file type: {filepath.suffix})封装这个函数最大的好处是项目中所有地方加载数据都走同一个入口。后面如果我换了数据存储方式只需要改这一个函数不用满项目翻。探索性分析部分我用matplotlib和seaborn做了一些可视化数值型特征画直方图看分布、类别型特征看频数、特征两两之间画散点图和相关性热力图。这一步在书里可能只是一小段代码但在真实比赛里决定特征怎么造、模型怎么选的信息大部分都藏在这个环节。3.2 数据预处理标准化按“训练集/测试集分开”的方式做数据预处理是整本书里最容易被初学者跳过细看的部分但它恰恰是影响模型效果的关键点。书里有大量代码涉及标准化、归一化、缺失值填充、类别变量编码。我在整理时把这些操作统一放到了一个文件里并特别强调了一件事预处理参数只能在训练集上拟合然后应用到测试集。以标准化为例最容易犯的错误是# 错误示范先对全量数据fit再划分训练测试集 scaler StandardScaler() X_scaled scaler.fit_transform(X) X_train, X_test, y_train, y_test train_test_split(X_scaled, y, test_size0.2, random_state42)这个写法的严重问题在于数据泄漏。测试集的信息在模型训练之前就参与了scaler的均值方差计算模型在训练时就已经“见过”测试集的分布信息。比赛中这会让你得到虚高的线下分数上线后效果打折扣。正确的做法是X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 只在训练集上fit X_test_scaled scaler.transform(X_test) # 对测试集只transform这个“fit/transform分离”的思路贯穿整个机器学习流程不只是标准化。之后的PCA降维、缺失值填充器的参数、OneHotEncoder等所有从数据中学习参数的步骤都要遵守这个原则。整理这本书的时候我强烈建议把这张图刻在脑子里训练集和测试集在整个流程中要像两个世界的人训练集可以做任何操作测试集只能用训练集学到的规则来变换不能参与学习。3.3 分类与回归模型把经典算法整理成一个“模型工厂”这本书的核心内容就是各种经典机器学习算法线性回归、逻辑回归、决策树、随机森林、支持向量机、K近邻、朴素贝叶斯等。书里对每个算法都有单独的小节代码结构类似加载数据→训练→评估。整理时我没有保留这种“每种算法一个小节”的组织方式而是用统一接口把它们组装成了一个“模型工厂”。思路很简单。我先定义了一个统一的训练评估函数def train_and_evaluate(model, X_train, X_test, y_train, y_test, model_namemodel): 统一的模型训练与评估入口 model.fit(X_train, y_train) y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) f1 f1_score(y_test, y_pred, averageweighted) print(f[{model_name}] Accuracy: {acc:.4f}, F1: {f1:.4f}) return model, y_pred然后对不同模型建立了一个简单的字典式注册models { logistic_regression: LogisticRegression(max_iter1000, random_state42), decision_tree: DecisionTreeClassifier(random_state42), random_forest: RandomForestClassifier(n_estimators100, random_state42), svm: SVC(kernelrbf, random_state42), knn: KNeighborsClassifier(n_neighbors5), }用的时候直接遍历这个字典同一套数据、同一个评估函数所有算法一次性跑完。这么做有两个好处第一算法对比的效率大幅提升。原来书里的方式是每个算法单独看准确率你得手动翻前面的结果记下来再对比。整理后用循环一次跑完表格里一列就是全部算法的表现。第二接口统一之后换数据、加新算法都很容易。你不需要重复写加载数据、训练、评估的代码只需要往字典里加一行新的模型就行。用这种方式跑完整本书的分类算法之后我得到了一张类似的对比表算法准确率训练时间备注逻辑回归0.973极短线性模型在数据量小时表现稳定决策树0.947短容易过拟合需要剪枝随机森林0.980中等默认参数下综合表现好SVM(RBF)0.980中等对小数据集效果好参数敏感KNN0.973短特征缩放很重要这张表不是为了论证哪个算法最好而是帮你建立“对同一份数据不同模型差异可能不大但训练成本、可解释性、稳定性差异很大”的直观认识。这种认识只能来自亲手跑一遍光看书是得不到的。3.4 特征工程与降维把“造特征”变成有章法的过程书里特征工程的篇幅不算少讲了标准化、归一化、PCA主成分分析、特征选择等。整理这部分代码时我体会最深的是特征工程决定模型效果的上限而模型只是在逼近这个上限。PCA这块值得多说几句。书里的代码很简单from sklearn.decomposition import PCA pca PCA(n_components2) X_pca pca.fit_transform(X)但如果只是让代码跑通你永远理解不了PCA到底帮了什么忙。整理时我额外做了几件事看explained_variance_ratio_、画出累积方差贡献率曲线、对比降维前后模型的性能变化。pca PCA() pca.fit(X_train_scaled) cumsum np.cumsum(pca.explained_variance_ratio_)为什么要看累积方差贡献率因为n_components这个参数不是拍脑袋定的。当你发现前两个主成分已经解释了90%以上的方差说明数据在更低维空间里也能有不错的表达如果解释率很低说明原始特征之间相关性不强强行降维可能丢掉关键信息。特征选择部分书里给出了SelectKBest等方式。我在整理时加了一个自己的体会先用简单模型跑一版baseline再用特征重要性筛选特征最后对比两者效果。这种“先粗后细”的做法比一开始就上复杂特征工程策略要稳妥得多。3.5 参数调优从手动试参到网格搜索/随机搜索这本书关于调参的章节很实用核心工具是GridSearchCV和RandomizedSearchCV。整理这部分代码时我意识到调参的本质不是“碰运气”而是理解模型超参数之间的协作关系。以随机森林为例我当时对n_estimators树的数量、max_depth树的最大深度、min_samples_split分裂一个节点所需最小样本数做了网格搜索from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [50, 100, 200], max_depth: [None, 5, 10], min_samples_split: [2, 5, 10], } rf RandomForestClassifier(random_state42) grid_search GridSearchCV(rf, param_grid, cv5, scoringaccuracy, n_jobs-1) grid_search.fit(X_train_scaled, y_train) print(grid_search.best_params_) print(grid_search.best_score_)这里有几个值得注意的细节。第一cv5是五折交叉验证不是简单的一次训练集/测试集划分。它的作用是减少因为数据划分带来的偶然性让调参结果更稳定。第二n_jobs-1表示使用所有CPU核心并行计算网格搜索的耗时能明显降低。第三scoring参数决定了优化目标比赛里用的指标可能不是准确率比如二分类比赛常用AUC回归比赛常用RMSE或MAE要根据目标改。整理代码时我还对比了GridSearchCV和RandomizedSearchCV的差别。当参数组合数量大时网格搜索是穷举组合数会爆炸比如上面那个例子是3×3×327组每组还要做5折交叉验证也就是要跑135次模型。如果参数再多几组计算时间会非常难看。这时RandomizedSearchCV随机采样的效率优势就出来了它不需要遍历所有组合能更快找到差不多的参数区间。3.6 深度学习部分从MLP到简单网络结构的梳理书后面有深度学习相关章节主要是用神经网络的简单结构来做分类。整理代码时我最大的感触是深度学习的代码看起来很玄但核心骨架和经典机器学习算法是高度相似的——数据准备、模型定义、训练、评估。这部分代码我额外强调了固定训练随机性的重要性。比如用Keras/TensorFlow训练网络时如果不设置随机种子即便同一个脚本每次跑出来的结果都可能不一样。整理时我加了这样一段import tensorflow as tf def set_tf_seed(seed42): tf.random.set_seed(seed) np.random.seed(seed)从机器学习到深度学习随机种子都是一个“看不见但影响巨大”的变量。训练时模型不是确定性算法初始化权重是随机的数据读取顺序也可能打乱这些都会影响最终效果。想在调试时判断到底是代码改了导致结果变化还是随机性导致结果变化先把种子固定住是第一要务。3.7 Kaggle实战流程把前面积累的模块拼接成完整参赛代码这本书最后有Kaggle实战的章节我印象比较深的是用了一个经典的表格类比赛数据来演示完整流程。整理到这里时我意识到前面所有模块化工作的价值终于显现出来了。真实的Kaggle参赛流程大概是这样的# 1. 加载数据 train load_data(train.csv) test load_data(test.csv) # 2. 快速探索 print(train.info()) print(train.describe()) print(train.isnull().sum()) # 3. 数据清洗与特征工程 train_processed, test_processed preprocess_features(train, test) # 4. 划分训练集和验证集 X_train, X_val, y_train, y_val train_test_split(...) # 5. 训练模型多个模型对比 for name, model in models.items(): train_and_evaluate(model, X_train, X_val, y_train, y_val, name) # 6. 选择最优模型预测测试集并生成提交文件 best_model.fit(np.vstack([X_train, X_val]), np.hstack([y_train, y_val])) test_pred best_model.predict(test_processed) submission pd.DataFrame({ID: test_id, Prediction: test_pred}) submission.to_csv(RESULT_DIR / submission.csv, indexFalse)整个流程环环相扣。之前每章单独看时都觉得简单拼在一起才发现每个环节都在为下一步做铺垫没有好的预处理模型训练效果就不可靠没有可靠的模型选择策略提交结果就是在碰运气。4. 整理过程中踩过的坑与排查记录4.1 库版本升级导致的API变动这是我在整理时遇到最多的问题。这本书的部分代码是基于相对早期的scikit-learn版本写的一些工具函数的位置变了。最典型的是交叉验证相关的函数。老版本的写法可能是from sklearn.cross_validation import train_test_split, cross_val_score新版本里这个模块已经被拆到了model_selectionfrom sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV解决办法很简单但我当时花了不少时间排查。建议整理这本书时直接统一使用新版API不要混用。遇到import报错时先看是不是模块路径变了再去网上搜当前版本正确的导入路径而不是盲目降低依赖版本。4.2 数据泄漏问题fit_transform和transform只用错一次代价就很大前面提过的数据泄漏问题在整理过程中我踩过一次。当时修改一个预处理脚本不小心在测试集上直接调用了fit_transform。第一版跑出来验证集分数特别高我还以为是特征工程立功了后来用同样的代码在另一个数据集上跑分数一落千丈才意识到问题出在预处理环节。排查方法是把pipeline拆开逐步检查scaler的fit和transform各自用在了哪些数据上。后来我学乖了凡是数据标准化、PCA之类的操作一律封装成自定义函数里面严格区分训练和测试分支不给人手动写错的机会。4.3 pandas的SettingWithCopyWarning整理数据处理代码时我经常会遇到这样的警告SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame这个警告的意思是你用链式索引先切片再赋值修改DataFrame时可能改的不是原来那份数据而是它的一个副本。书里某些代码直接train[col] ...的写法在极简demo里没出问题但一旦数据量变大、操作变多warning就冒出来了。我的习惯是需要修改DataFrame时先用copy()显式复制或者改用loc来操作# 推荐写法 df raw_df.copy() df.loc[:, new_feature] df[existing_feature] * 24.4 其他小坑速查表我在整理过程中把印象比较深的问题整理成了下面这个速查表问题现象可能原因排查方向解决办法模型效果忽高忽低没有固定随机种子检查是否有random_state在所有随机环节设固定seed测试集效果远低于训练集数据泄漏检查预处理是否在测试集上fitfit/transform严格分离import sklearn时报错版本兼容问题查看当前sklearn版本使用新版API或调整版本pandas警告影响判断链式赋值查看警告位置使用loc和copy避免链式修改中文路径导致文件读取失败编码或路径问题检查路径和编码统一用UTF-8编码和路径对象可视化中文乱码字体缺失查看matplotlib字体配置指定中文字体或改为英文标签4.5 我的调试策略先用最小数据跑通全流程这算是我整理这本书时最受益的一个习惯先用小数据量快速验证全流程再跑全量数据。具体做法是调试阶段只取整个数据集的前1000行或者更少跑通整个pipeline确认没有报错、输出格式正确再替换成全量数据重新运行。这个习惯帮我节省了大量时间。因为机器学习项目链条长某一步出错了报错信息经常指向很远的地方。小数据量跑一遍能在几秒内定位问题所在不用在每次报错后等很久才能看到下一个错误。5. 从中得到的沉淀如何把“整理代码”变成“项目模板”5.1 整理完之后要做什么沉淀自己的baseline模板代码整理不是终点重点是整理完之后你能带走什么。我把这次整理的成果分成了三层第一层是代码本身。上面说过的所有源文件组成了一个可运行的机器学习项目模板。第二层是流程认知。从数据加载到预处理、建模、调参、提交整个pipeline的各个环节在真实项目中的顺序和依赖关系已经内化成了我自己的“肌肉记忆”。以后拿到一个新数据集我不用再想“下一步做什么”流程自然就在脑子里展开了。第三层是工具意识。我知道什么时候该用GridSearchCV、什么时候该用RandomizedSearchCV、什么时候该上PCA、什么时候该用集成模型。这些判断力光靠看书很难获得只有在亲手整理过代码、亲手对比过结果差异之后才能真正建立起来。5.2 给同样在整理代码的你的几点建议如果你也想把这本经典书的代码整理成自己的项目模板我的建议是第一不要追求一次整理完。每天整理一个章节就行留出时间消化和验证。逼得太紧反而容易变成“手动抄代码”没有理解过程。第二每个章节整理完后做一个小实验不看书里的代码自己从零开始实现一遍同样的流程。这个“关书重建”的过程比任何笔记都有效。第三整个项目做完之后试着拿一个新数据集从头到尾跑一遍。这一步能快速检验你沉淀下来的模板是真的好用还是只对书里那几个数据集有效。我做完这件事最直接的收获是后来参加一个实际比赛时别人还在纠结数据怎么清洗、用什么模型我直接把这套模板套上去半天就出了第一版baseline。然后才有足够的时间去优化特征、调参、做模型融合。这种“先有baseline再优化”的节奏和“从零开始边写边想”的节奏心气完全不同。5.3 后续还能怎么扩展这套模板目前覆盖了表格数据的经典机器学习流程但还远不是终点。我的想法是随着经验积累持续往里面加东西比如加一段模型融合的代码框架、加一个更完善的特征选择模块、把调参部分的搜索策略升级成更高效的贝叶斯优化。不过这些都是后话。目前这个版本已经足够支撑我从“读完一本书”走向“完成一次真实竞赛”了。如果你也在整理代码我建议你把重点放在“理解为什么这样做”上而不是“代码跑通”上。代码跑通只是起点真正沉淀下来的、那种拿到数据就知道怎么下手的手感才是整理这本书最大的回报。