简介:面向数据科学与机器学习入门者,这份源代码合集按 chapter1~chapter9 逐层递进,覆盖线性回归、逻辑回归、决策树与随机森林、支持向量机、聚类分析、神经网络与深度学习、集成学习以及模型选择与调优等主题。每个章节均配有可运行的 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_state=0)这段代码有四个关键点。sys.path.append('../chapter5')的作用是把 chapter5 目录临时加进 Python 的模块搜索路径,否则解释器在当前目录下找不到 adspy_shared_utilities 这个文件。train_test_split的返回值顺序固定是训练特征、测试特征、训练标签、测试标签,写错顺序会在模型评估时得到一套完全错位的指标,而且非常难排查。random_state=0固定随机种子,保证别人复现你的代码时得到完全一样的划分结果。tools这个别名是约定俗成的写法,后面的 Notebook 里所有绘图调用都依赖它。
提示:adspy_shared_utilities2.py 出现后,老 Notebook 依然 import 旧模块而不报错,就是因为旧文件没有被覆盖。复现时如果两个文件并存,优先用文件名里带 2 的版本,功能通常更完整。
2.2 标准化为什么在PCA和KNN里反复出现
这份源码给了两个最典型的证据:chapter8 里有一张 standardization.png,chapter5 的 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_splits=5) for train_idx, test_idx in tscv.split(X): print(f"train: {train_idx.min()}~{train_idx.max()}, test: {test_idx.min()}~{test_idx.max()}")n_splits=5表示生成 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(degree=3, include_bias=False) X_poly = poly.fit_transform(X) ridge = Ridge(alpha=1.0) lasso = Lasso(alpha=0.1) ridge.fit(X_poly, y) lasso.fit(X_poly, y)degree=3把单特征扩展成 x、x²、x³ 三个特征,模型因此能拟合曲线,但代价是参数数量爆炸且容易过拟合。include_bias=False表示不额外生成全 1 的常数列,因为 sklearn 的线性模型会自动处理截距。Ridge 的 L2 惩罚把系数压向 0 但不会压成精确的 0;Lasso 的 L1 惩罚在 alpha 足够大时会把一部分系数直接变成 0,起到特征选择的作用。在媒体投放这个场景里,如果某个渠道的系数被 Lasso 压成 0,业务上的解释就是这个渠道对销售额没有增量贡献,可以直接砍预算。
| 模型 | 惩罚项 | 系数行为 | 典型使用场景 |
|---|---|---|---|
| LinearRegression | 无 | 最小二乘解 | 基线模型 |
| Ridge / L2 | Σw² | 收缩但非零 | 特征多且彼此相关 |
| Lasso / L1 | Σ | w | |
| ElasticNet | 混合 | 介于两者之间 | 高维稀疏场景 |
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(C=1.0, max_iter=1000) log_reg.fit(X_train_scaled, y_train) gnb = GaussianNB() # 连续特征,假设每个类别内服从高斯分布 bnb = BernoulliNB() # 二值特征,例如 0/1 标志位 mnb = MultinomialNB() # 计数特征,例如词频逻辑回归有一个容易忽略的参数是C,它和 Ridge 的 alpha 一样控制正则化强度,但方向相反:C 越小正则越强,系数越接近 0。max_iter=1000是因为 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 用 KNN,chapter6 加 SVM,刚好凑成三种处理非线性边界的路径:
from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC tree = DecisionTreeClassifier(max_depth=4, min_samples_leaf=5) knn = KNeighborsClassifier(n_neighbors=5, weights='distance') svm = SVC(kernel='rbf', C=10.0, gamma=0.1)决策树最值得调的参数不是max_depth,而是min_samples_leaf。把叶子节点最小样本数设为 5 到 10,比单纯限制深度更能防止过拟合,预测也更稳定。KNN 的weights='distance'让距离更近的样本拥有更大投票权重,可以缓解样本重叠区域的毛刺。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_components=2, svd_solver='randomized', random_state=42) X_pca = pca.fit_transform(X_scaled) print(pca.explained_variance_ratio_) print(pca.explained_variance_ratio_.sum())explained_variance_ratio_返回每个主成分解释的方差占比,累加值表示这两个成分保住了原始信息的多少。如果累加成只有 60%,说明强行降到二维丢掉的信息太多;超过 90% 则几乎无损。svd_solver='randomized'走随机化 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_clusters=k, n_init=10, random_state=42) km.fit(X_scaled) inertia.append(km.inertia_) plt.plot(range(1, 11), inertia, marker='o') plt.xlabel('k') plt.ylabel('inertia') plt.show()inertia_是样本到所属簇中心的距离平方和,k 增大时 inertia 必然下降,肘部法则就是找曲线由陡变平的那个拐点,那里通常是 k=4 到 6 之间。n_init=10表示用 10 组随机质心初始化各跑一遍,取最优结果,避免陷入局部最优;random_state=42保证每次运行的可复现性。聚类完成后还要做一步业务校准:把每个簇的年龄均值、收入均值、消费评分均值列出来,看能否归纳出“高收入低消费”这类可解释的客户画像,否则就算聚类出漂亮的图形,业务方也无法执行。数据分析师 SQL 的功底在这一步就体现出来了,分群结果要能落到库里,而不是只活在 Notebook 里。
4.3 随机森林与Bagging/Boosting的差异
chapter7 里同时有 bagging.mp4、boost.mp4 和随机森林案例演示,还配了多棵树的 dot 文件,作者明显是想让你直观对比两种集成路径。随机森林是 Bagging 的代表:每棵树在行和列上随机采样,最后投票;Boosting 是串行训练弱学习器,每棵树去拟合前面所有树的残差。一个降低方差,一个降低偏差,方向完全不同。
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=200, max_depth=10, min_samples_leaf=3, max_features='sqrt', n_jobs=-1 ) rf.fit(X_train, y_train) importances = rf.feature_importances_n_estimators不是越大越好,200 棵树通常已经够用,再增加只会延长训练时间;max_features='sqrt'表示每个节点只随机挑 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,否则输入特征不在标准化的尺度上,预测结果很可能失真。
本文还有配套的精品资源,点击获取