机器学习实战:从数据预处理到模型调优的完整链路解析
简介面向数据科学与机器学习入门者这份源代码合集按 chapter1chapter9 逐层递进覆盖线性回归、逻辑回归、决策树与随机森林、支持向量机、聚类分析、神经网络与深度学习、集成学习以及模型选择与调优等主题。每个章节均配有可运行的 Jupyter Notebook 源码和案例数据案例涉及媒体投放回归预测、车辆回归分析、泰坦尼克号决策树、KNN 分类、PCA 降维与 KMeans 聚类等部分章节还提供 .dot 决策树结构图、演示图片与 mp4 讲解视频方便对照代码理解算法原理和实操细节。资源共 80 个文件核心为 36 个 .ipynb 笔记另有 Python 脚本、训练好的 .joblib 模型、CSV 数据及可视化文档等压缩包大小约 54.72MB。已有 1053 人学习下载适合希望系统提升机器学习实践能力的数据分析师或高校学生按章节顺序即可完成从数据预处理、模型训练到结果评估的完整流程。1. 从李宏毅机器学习到九章Notebook这组源代码的打开方式机器学习入门的真实门槛往往不在吴恩达或李宏毅的课堂视频里而在你听完课后打开Jupyter Notebook的第一秒面对空白的单元格不知道第一行代码该写什么。这份来自数据分析师专栏的源代码正好填补了这段空档。chapter1到chapter9从媒体投放线性回归案例一路推进到随机森林、PCA和KMeans每个Notebook都带好了数据集、工具模块和讲解视频。它的价值不在于模型多新而在于把数据分析师实际工作里那一整条链路——数据加载、标准化、建模、调参与验证——完整地跑了一遍。适合的人群是那些看过李宏毅机器学习课程、也翻过机器学习实战却始终觉得“代码不是自己写出来”的入门者和转岗分析师。2. 特征工程与数据预处理adspy_shared_utilities和标准化在PCA/KNN中的双重作用2.1 工具模块adspy_shared_utilities拆解与目录导入adspy_shared_utilities.py 在 chapter5 和 chapter6 里连续出现还带着 adspy_shared_utilities2.py 两个版本后缀。这种命名习惯在教学中很常见作者写到后半程发现原模块不够用直接复制出一个 v2而不是改原文件为的是保证前面的 Notebook 还能继续运行。模块本身不实现任何机器学习算法它封装的是数据拆分和可视化这两类重复劳动。我拿到这类代码第一步是先读 .py 文件而不是去打开 .ipynb因为整个专栏的数据加载方式、随机种子、图形风格全都在这个文件里。常见的封装包括三类把数据集切成训练集和测试集的函数把分类器决策边界画到二维坐标系里的绘图函数以及控制图像尺寸坐标范围的通用参数。先读清楚它能省掉大量从 Notebook 里反推逻辑的时间。import sys sys.path.append(../chapter5) import adspy_shared_utilities as tools X_train, X_test, y_train, y_test tools.train_test_split(X, y, random_state0)这段代码有四个关键点。sys.path.append(../chapter5)的作用是把 chapter5 目录临时加进 Python 的模块搜索路径否则解释器在当前目录下找不到 adspy_shared_utilities 这个文件。train_test_split的返回值顺序固定是训练特征、测试特征、训练标签、测试标签写错顺序会在模型评估时得到一套完全错位的指标而且非常难排查。random_state0固定随机种子保证别人复现你的代码时得到完全一样的划分结果。tools这个别名是约定俗成的写法后面的 Notebook 里所有绘图调用都依赖它。提示adspy_shared_utilities2.py 出现后老 Notebook 依然 import 旧模块而不报错就是因为旧文件没有被覆盖。复现时如果两个文件并存优先用文件名里带 2 的版本功能通常更完整。2.2 标准化为什么在PCA和KNN里反复出现这份源码给了两个最典型的证据chapter8 里有一张 standardization.pngchapter5 的 KNN 实例也绕不开特征缩放。两个场景背后的原理其实不同。KNN 用欧氏距离度量样本相似度假设一个水果分类案例里重量特征取值范围是 50 到 200直径特征只有 3 到 8距离计算时直径的贡献几乎被重量淹没模型等于只用了一个特征。PCA 则是另一套逻辑主成分找的是方差最大的投影方向方差的大小直接受量纲影响把直径单位从厘米换算成毫米第一主成分的方向就会被这个假的大方差拐走。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)fit_transform只在训练集上调用一次测试集必须复用训练集里学到的均值和标准差不能重新 fit否则测试数据的信息会流进预处理阶段最终评估结果偏乐观。这一点在生产环境的特征管道里同样是硬性要求很多数据分析师在面试时就是栽在这里。缩放方式公式适用场景StandardScaler(x - μ) / σ特征近似正态分布PCA、SVM、KNN 前处理MinMaxScaler(x - min) / (max - min)特征范围已知神经网络输入层RobustScaler(x - 中位数) / IQR存在离群点中位数和四分位距更稳不做缩放-决策树、随机森林基于分裂阈值不受单调变换影响回到那张 standardization.png作者在 PCA 的笔记里放这张图是想提醒读者做 PCA 之前先问自己一句当前特征量纲一致吗量纲不一致的主成分分析结果基本没有解释性第一主成分大概率只是“数值最大的那个特征”的投影而不是数据真正的方差结构。2.3 训练集测试集划分从随机split到时序切分在 chapter1 到 chapter9 的所有案例里train_test_split都是标准开局。但真实业务里时间序列数据的分割要比随机 split 严格得多。chapter1 里正好有两个时序数据分析的 Notebook这里单独提一下。如果是按时间排序的数据随机打乱再划分会让模型偷看到未来。正确做法是按时间顺序切分前 70% 做训练后 30% 做测试。sklearn 里可以用TimeSeriesSplit它保证训练集永远在测试集之前。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, test_idx in tscv.split(X): print(ftrain: {train_idx.min()}~{train_idx.max()}, test: {test_idx.min()}~{test_idx.max()})n_splits5表示生成 5 组划分每组训练集都比前一组多一段历史数据测试集始终是紧随其后的时间窗口。这里最值得记住的原则是随机 split 适用于横截面数据时序数据必须按时间顺序 split否则模型评估的不仅是模型能力还有“穿越”带来的虚假信息。3. 五类监督模型从线性回归到SVM的参数递进逻辑3.1 线性回归、多项式回归与梯度在分类问题里的落点chapter1 的内容不复杂但它是整套代码里最值得反复看的部分。媒体投放案例做的是广告渠道花费对销售额的预测这是典型的多重线性回归基础线性模型介绍带着最小二乘推导L1 和 L2 惩罚则是在给后面的正则化打底。机器学习里的梯度这个词最早接触的场景就是回归的 loss 对参数求导。线性回归有闭式解(X^T X)^(-1) X^T y但特征数量上万时矩阵求逆代价极高工程里更多用梯度下降迭代。多项式扩展在代码里是这样用的from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge, Lasso poly PolynomialFeatures(degree3, include_biasFalse) X_poly poly.fit_transform(X) ridge Ridge(alpha1.0) lasso Lasso(alpha0.1) ridge.fit(X_poly, y) lasso.fit(X_poly, y)degree3把单特征扩展成 x、x²、x³ 三个特征模型因此能拟合曲线但代价是参数数量爆炸且容易过拟合。include_biasFalse表示不额外生成全 1 的常数列因为 sklearn 的线性模型会自动处理截距。Ridge 的 L2 惩罚把系数压向 0 但不会压成精确的 0Lasso 的 L1 惩罚在 alpha 足够大时会把一部分系数直接变成 0起到特征选择的作用。在媒体投放这个场景里如果某个渠道的系数被 Lasso 压成 0业务上的解释就是这个渠道对销售额没有增量贡献可以直接砍预算。模型惩罚项系数行为典型使用场景LinearRegression无最小二乘解基线模型Ridge / L2Σw²收缩但非零特征多且彼此相关Lasso / L1ΣwElasticNet混合介于两者之间高维稀疏场景3.2 逻辑回归与贝叶斯判别式和生成式两条路径chapter2 的逻辑回归练习和 chapter4 的贝叶斯_高斯、贝叶斯_伯努利、贝叶斯_多项式放在一起看是一场很直观的对照实验。逻辑回归是判别模型直接建模 P(y|x)用一条边界把类别分开贝叶斯是生成模型分别估计 P(x|y) 和 P(y)再通过贝叶斯公式反推后验概率。from sklearn.linear_model import LogisticRegression from sklearn.naive_bayes import GaussianNB, BernoulliNB, MultinomialNB log_reg LogisticRegression(C1.0, max_iter1000) log_reg.fit(X_train_scaled, y_train) gnb GaussianNB() # 连续特征假设每个类别内服从高斯分布 bnb BernoulliNB() # 二值特征例如 0/1 标志位 mnb MultinomialNB() # 计数特征例如词频逻辑回归有一个容易忽略的参数是C它和 Ridge 的 alpha 一样控制正则化强度但方向相反C 越小正则越强系数越接近 0。max_iter1000是因为 sklearn 默认用 LBFGS 求解特征多了不收敛会报警告提高迭代上限是标准解法。高斯贝叶斯里没有 C 可调它假设特征独立且类内高斯分布这个假设在文本、图像这类强相关特征上会很吃亏但在中小表格数据里往往能给出一个不坏的基线而且训练速度几乎为零。模型建模路径对特征的偏好LogisticRegression判别直接求决策边界数值特征标准化后更稳GaussianNB生成类内高斯分布连续特征BernoulliNB生成二值分布0/1 标志特征MultinomialNB生成多项式分布词频、计数值3.3 决策树、KNN与SVM三种非线性边界的选择依据chapter5 到 chapter6 的安排很有意思chapter5 是三个 KNN 实例配了 shuiguo.jpg 水果数据chapter6 直接在一个 KNN 的 Notebook 里塞进了 SVM文件名也写成了“KNN实例2(带有svm的算法)”。这种并列不是为了炫技而是让你在同一次运行里比较两种算法的决策边界。CSDN 和 GitHub 上最抢眼的机器学习开源项目永远是人脸识别但对于头三年的数据分析师把水果分类、泰坦尼克、鸢尾花这几个经典数据集的模型边界吃透比急着去跑深度学习检测模型重要得多。chapter3 还有一组 titanic 的 dot 文件那是决策树的可视化结果chapter5 用 KNNchapter6 加 SVM刚好凑成三种处理非线性边界的路径from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC tree DecisionTreeClassifier(max_depth4, min_samples_leaf5) knn KNeighborsClassifier(n_neighbors5, weightsdistance) svm SVC(kernelrbf, C10.0, gamma0.1)决策树最值得调的参数不是max_depth而是min_samples_leaf。把叶子节点最小样本数设为 5 到 10比单纯限制深度更能防止过拟合预测也更稳定。KNN 的weightsdistance让距离更近的样本拥有更大投票权重可以缓解样本重叠区域的毛刺。SVM 对特征缩放极其敏感这就是前面强调 StandardScaler 的原因C 控制误分类惩罚gamma 控制单个样本的影响半径——gamma 太大会把边界切成一个个小岛C 太大会为迁就离群点牺牲 margin。这三个模型在同一个标准化后的数据集上各跑一遍对比决策边界就能直观理解“模型假设”这四个字到底在说什么。4. PCA降维、KMeans聚类与随机森林无监督与集成方法的实战参数4.1 PCA案例中的标准化与explained_variance_ratio_chapter8 的 PCA 案例解析里有一组按顺序排好的图片standardization.png、iris_2_component_PCA.png、iris_keep_2_components.png。这个顺序其实就是操作流程先标准化再降到二维最后把降维后的分布画出来。案例用的数据是鸢尾花四个特征降到两个主成分方便在二维平面里直接观察不同类别是否可分。from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler X_scaled StandardScaler().fit_transform(X) pca PCA(n_components2, svd_solverrandomized, random_state42) X_pca pca.fit_transform(X_scaled) print(pca.explained_variance_ratio_) print(pca.explained_variance_ratio_.sum())explained_variance_ratio_返回每个主成分解释的方差占比累加值表示这两个成分保住了原始信息的多少。如果累加成只有 60%说明强行降到二维丢掉的信息太多超过 90% 则几乎无损。svd_solverrandomized走随机化 SVD 路线特征维度高时比标准特征值分解快很多代价是结果有轻微随机性所以用random_state固定。chapter8 里还放了 PCA_timingOld.png 和 PCA_timingNew.png 两张耗时对比图作者显然在实践中吃过高维矩阵分解的亏。工业上数据分析师做 PCA最常见的目的不是特征压缩而是把高维数据降到二维三维后把散点图拿给业务方看聚类结果或异常点。这时一定要把标准化做在前面否则 PCA 画的图解释不了。4.2 Mall_Customers.csv与KMeans肘部法则chapter9 用的 Mall_Customers.csv 是 UCI 上的经典商场客户数据字段包括客户 ID、性别、年龄、年收入和消费评分。任务是把客户分群方便运营做差异化投放。这一步在真实工作里的后续动作通常是先聚类再给每个簇打标签最后用 SQL 把标签回写到客户宽表让业务方按标签直接圈选人群。from sklearn.cluster import KMeans import matplotlib.pyplot as plt inertia [] for k in range(1, 11): km KMeans(n_clustersk, n_init10, random_state42) km.fit(X_scaled) inertia.append(km.inertia_) plt.plot(range(1, 11), inertia, markero) plt.xlabel(k) plt.ylabel(inertia) plt.show()inertia_是样本到所属簇中心的距离平方和k 增大时 inertia 必然下降肘部法则就是找曲线由陡变平的那个拐点那里通常是 k4 到 6 之间。n_init10表示用 10 组随机质心初始化各跑一遍取最优结果避免陷入局部最优random_state42保证每次运行的可复现性。聚类完成后还要做一步业务校准把每个簇的年龄均值、收入均值、消费评分均值列出来看能否归纳出“高收入低消费”这类可解释的客户画像否则就算聚类出漂亮的图形业务方也无法执行。数据分析师 SQL 的功底在这一步就体现出来了分群结果要能落到库里而不是只活在 Notebook 里。4.3 随机森林与Bagging/Boosting的差异chapter7 里同时有 bagging.mp4、boost.mp4 和随机森林案例演示还配了多棵树的 dot 文件作者明显是想让你直观对比两种集成路径。随机森林是 Bagging 的代表每棵树在行和列上随机采样最后投票Boosting 是串行训练弱学习器每棵树去拟合前面所有树的残差。一个降低方差一个降低偏差方向完全不同。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, max_depth10, min_samples_leaf3, max_featuressqrt, n_jobs-1 ) rf.fit(X_train, y_train) importances rf.feature_importances_n_estimators不是越大越好200 棵树通常已经够用再增加只会延长训练时间max_featuressqrt表示每个节点只随机挑 sqrt(n_features) 个特征做分裂这是随机森林和普通 Bagging 的本质区别它让树与树之间的相关性下降集成效果才出得来n_jobs-1让所有 CPU 核心并行训练数据量大时体感差别非常明显。feature_importances_给出每个特征的重要性排序这是随机森林作为数据分析师常用模型的核心理由不光能预测还能解释哪些变量在驱动结果。这里顺带回答一个高频面试问题树模型假设独立同分布吗严格说单棵决策树的分裂过程并不依赖 i.i.d. 假设但 Bagging 之所以能在方差上做文章靠的是自助采样近似出多组独立同分布的样本。随机森林的统计保证依然建立在 i.i.d. 框架下。面试里遇到这个问题答案不是简单的“是”或“否”而是先分清训练算法和集成理论各自的底层假设。5. 跑通源码后的排错与验证从pycache到joblib模型持久化5.1 __pycache__与.cpython-38.pyc的来龙去脉解压这份源码后你会发现除了 .ipynb 和 .py还有一层__pycache__目录里面是adspy_shared_utilities.cpython-38.pyc这类文件。Python3 解释器在 import 一个模块时会自动把编译后的字节码缓存到该目录文件名里的 cpython-38 表示这是 CPython 3.8 生成的。这些文件本地运行无害但提交代码时要排除掉否则会造成大量无效 diff。__pycache__/ *.pyc .ipynb_checkpoints/.ipynb_checkpoints是 Jupyter 自动保存的笔记本快照目录同样不需要进版本库。分析源码里出现.ipynb_checkpoints是正常现象不用紧张在 .gitignore 里忽略即可。5.2 资源文件配对与模型验证路径chapter5 里 shuiguo.jpg 和 shuiguo.txt 是配套出现的图片负责可视化展示txt 负责存标签或者特征解释。跑这种带图片的 Notebook 时最容易踩的坑是路径问题——在哪个目录下启动 jupyter决定了相对路径的起点。如果从项目根目录启动代码里的shuiguo.jpg就找不到需要改成chapter5/shuiguo.jpg或者用绝对路径。判断依据很简单看资源文件跟当前运行的 .ipynb 是不是在同一个目录层级。5.3 用joblib和dot验证模型与决策路径chapter3 里的 music.joblib 和 iris.joblib 是已经训练好并持久化的模型文件。joblib 是 sklearn 官方推荐的模型存取方案对 numpy 数组的序列化效率比 pickle 高加载后可以直接复用做预测不用重新跑一遍训练。dot -Tpng tree1.dot -o tree1.pngfrom joblib import load model load(iris.joblib) print(model.predict([[5.1, 3.5, 1.4, 0.2]]))tree1.dot、tree2.dot 这些文件是 Graphviz 保存的决策树结构用上面的 dot 命令行就能渲染成 PNG 图片和 Notebook 里的输出对比两棵树的剪枝结构是否一致一眼就能看出来。load(iris.joblib)不需要指定模型类型joblib 会把对象完整读回来predict传入的特征顺序要和训练时一致这是模型复用里最常见的出错点。最后强调一点joblib 保存的是训练完成时的模型不包含标准化器的参数所以部署时要把 scaler 也一并 dump 下来加载模型前先跑一遍 transform否则输入特征不在标准化的尺度上预测结果很可能失真。本文还有配套的精品资源点击获取