1. 从这份经典实例笔记说起
很多人学机器学习,第一反应是找一堆经典实例,跑通代码就算学会。我当时也这么干,结果发现跑得越顺,对模型的判断力反而越弱。直到我把Python机器学习经典实例重新当成一份“外科手术图谱”来读,才真正体会到:经典例子不是拿来直接复制的,而是拿来拆解的。跑通代码只说明环境没问题,并不代表你知道为什么这个场景选线性回归而不是决策树,为什么这个特征要先做缩放,为什么验证分数高得离谱反而要警惕。
这份笔记没有追求把所有算法都覆盖到,而是挑出最常出现在真实项目里的几类:回归、分类、集成、聚类、降维,以及贯穿始终的数据处理与模型评估。适合两种人:一种是想系统补一遍机器学习方法论的在职人员,另一种是学完基础语法、准备动手做第一个端到端项目的初学者。如果你只想无脑复制代码,这份笔记帮不了你;如果你想弄懂代码背后的判断标准,它会是一个不错的起点。
1.1 我为什么会沉淀这份笔记
现在网上Python机器学习的资源多到看不完,教程、视频、开源代码都有。我刚开始学的时候,也是跟着别人的代码敲,敲完就感觉会了,但一换数据集就废。后来一位做数据分析的朋友点醒我:你缺的不是代码,是决策链。经典实例的价值,恰恰就是把这些决策链一条一条摆在你面前:为什么选这个算法,为什么做这个预处理,为什么调这个参数,用什么证据来判断效果。于是我把那套Python机器学习经典实例从头到尾重读了一遍,边跑边记,这份笔记就是当时沉淀下来的。
我原本以为整理这份笔记只是把代码注释抄下来,后来发现真正有价值的是那些被写在注释之外的东西:模型输出某个结果时,我第一反应是什么;排查了很久才发现的问题,根源竟然只是切分数据时少了一个参数。这些内容教科书不会写,论坛里零零散散能找到,但它们恰恰决定了你能不能在真实项目里独立干活。这份笔记把这些内容尽量集中在一起,方便自己日后翻阅,也方便和同行交流。
1.2 记录这份笔记时用到的工具与组织方式
案例代码大部分基于Python的标准机器学习生态:用于数据处理的Pandas和NumPy,用于建模的scikit-learn,偶尔用Matplotlib画曲线。我把具体的版本号略去了,因为版本差异不影响核心思路,只会影响个别默认参数。整份笔记按“数据处理—有监督建模—集成学习—无监督学习—评估排查”的顺序组织,这也是实际项目中处理问题的顺序:先清洗数据,再选模型,再调优,最后验证。
我故意不把每个算法的数学推导搬进来。比如讲到支持向量机时,我不会长篇大论讲对偶问题,而是关注它的决策边界、核函数选择和特征缩放要求。原理需要懂到“能解释出为什么这么选”的程度,剩下的交给教科书。这也符合经典实例笔记的定位:以动手为主,以判断为辅,以复现为底线。每一个概念尽量用生活化的话讲清楚,看到公式也不怕,因为最终要落回到代码和结果上。
2. 数据预处理与特征工程:最容易翻车却最值得细读的章节
2.1 标准化、归一化与RobustScaler:三者的使用边界
经典实例里第一个让我开眼的点就是特征缩放。很多算法,比如逻辑回归、支持向量机和KMeans,本质上依赖样本之间的距离或者梯度下降的收敛路径。如果特征A的范围是0到1,特征B的范围是1万到10万,那么B会直接主导距离和梯度,模型学出来的东西根本不是特征的均衡表达。这就好比在计算两个人的相似度时,身高用米、体重用克,克这个单位会把其他维度全部淹没。
标准化(StandardScaler)把每一列减去均值再除以标准差,输出的均值近似0、方差为1;归一化(MinMaxScaler)把每一列压缩到0到1区间,适合已知上下界、且没有极端异常值的特征。但我更常用的是RobustScaler,它基于中位数和四分位距做缩放,对异常值没那么敏感。实际项目里,特征常混着长尾分布,直接标准化后异常点仍会让数据分布很别扭,RobustScaler往往更稳。没有绝对的好坏,关键是在同一份数据上选定一种,并保持一致。
这里有一个顺序问题必须强调:缩放器只能用训练集的信息去拟合,然后再转换训练集和测试集。如果你对全量数据先做fit,或者先手动计算均值再切分,都会让测试集的信息悄悄混入训练过程。很多经典实例的代码只写两行,但这两行背后的顺序,决定了你有没有埋下数据泄漏的雷。
2.2 缺失值处理和类别特征编码:两个很容易被忽略的顺序问题
缺失值处理很简单,但坑在顺序。常见做法是用SimpleImputer,参数可以选均值、中位数或最频繁值。数值特征推荐用中位数,因为它对异常值更稳;类别特征则用最频繁值。如果缺失比例超过一半,与其填补不如直接把这一列删掉,否则造出来的数据会严重污染模型。还有一个容易被忽略的细节:把“是否缺失”也做成一个0/1特征,有时候缺失本身也是信息。
类别特征编码要提醒的是OneHotEncoder。直接用标签编码会引入大小关系,比如把“红、绿、蓝”编码成0、1、2,很多线性模型会认为蓝色大于红色。OneHotEncoder会生成哑变量,避免这种顺序幻觉。但也要注意稀疏性:类别特别多的时候,特征维度会爆炸,此时可以考虑对低频类别做合并。这两步都必须先对训练集fit,再对测试集transform,切忌把全部数据一起fit后再切分,这是经验中最常见的数据泄漏来源。
2.3 数据切分:随机切分不是万能的
train_test_split是绝大多数经典实例的默认动作,可它的默认参数是shuffle=True。对于普通横截面数据,随机打乱没问题;但对于带时间顺序的数据,随机切分等于让模型“偷看未来”。比如预测下个月销量,训练集里混入后几个月的样本,验证集里反而出现早期样本,这种成绩再高也没有意义。正确做法是TimeSeriesSplit或者按时间节点切分,保证训练集时间永远在测试集之前。
切分时另一个容易翻车的点是类别不平衡。如果正样本只占5%,随机切分时验证集可能只分到零星几个正样本,导致指标大幅波动。解决办法是stratify参数,按类别比例分层抽样。我在几个模拟项目里都吃过这个亏:第一次用默认切分,第二次用stratify,同一个模型AUC能差0.1以上。所以拿到数据后第一步不是建模,而是看类别分布,再决定切分策略。经典实例里没有强调这一步,实际操作时却往往决定实验的可靠性。
3. 监督学习经典实例拆解:回归与分类是同一套决策链
3.1 线性回归:不要急着看R2,先看残差
线性回归是几乎所有经典实例的起点,但我一开始犯过一个典型错误:训练完直接看R2,R2漂亮就以为万事大吉。直到有一次在某个销售预测Demo上,R2高达0.95,可把预测结果画出来才发现,所有高数值样本都被系统性低估了。后来我才学会先画残差图。真正的残差应该随机分布在0附近,没有明显形状。如果残差随预测值呈喇叭形分布,说明存在异方差;如果残差有明显的曲线趋势,说明模型漏掉了非线性关系。
解决残差不干净的办法通常有三条:一是对目标变量做对数变换或Box-Cox,把右偏分布拉正;二是加入多项式特征,比如使用PolynomialFeatures;三是换一个对非线性更友好的模型。经典实例里对线性回归的扩展还有一个关键点:多特征下的共线性。VIF较高的特征会让系数解释变得不可靠,即使预测能力没崩,也要小心。实际项目中如果目标是解释业务规律,共线性问题比预测精度更需要关注,因为回归系数在这时会变得飘忽不定。
3.2 逻辑回归与SVM:概率和边界的不同取舍
逻辑回归虽然名字里带回归,其实是分类模型。它输出的不是一个整数类别,而是一个概率,然后按阈值(默认0.5)转成类别。这个概率很有用,比如在风控场景里,比起直接说“拒绝”,倒不如说“违约概率是多少”。多分类场景下,它默认走一对多策略,本质上是为每个类别训练一个二分类器,最后看谁的分数高。经典实例里如果追求可解释性,逻辑回归很难被替代。
支持向量机则是另外一种思路:在特征空间里找一个最大间隔的分离边界,边界附近的少数点(支持向量)决定整个模型。SVM最让人困惑的是核函数。线性核适合特征多、样本多、边界本来就接近线性的情况;高斯核(RBF)适合非线性决策边界,但它有两个关键参数:C控制误分类惩罚,gamma控制单个样本的影响范围。gamma太大容易过拟合,会在每个样本周围画一个孤立的小圈;C太大也会让模型尽全力拟合异常点。用SVM之前一定要做特征缩放,不然支持向量的判断完全被大尺度特征带偏。
3.3 朴素贝叶斯:“朴素”假设为什么在文本场景还能赢
朴素贝叶斯是经典实例里最反直觉的模型之一。它假设特征之间相互独立,这在现实里几乎不可能成立,比如文本里“金融”和“银行”明显强相关。可即便如此,它依然能在垃圾邮件识别、情感极性判断这类任务上表现不错,还在小样本和高维稀疏特征下非常稳。原因在于它只需要估计每个特征在每个类别下的条件概率,训练本质上就是数数加上平滑,迭代开销极小,对噪声的容忍度也很高。
使用MultinomialNB时,平滑参数alpha的作用是给每个特征的概率加上一个很小的计数,避免某个词只在训练集出现一次就获得极端概率。alpha越大,概率估计越接近均匀分布,模型越保守。经典坑之一:对文本做词频计数时,用TF-IDF而不是原始计数通常效果更好,因为原始计数会放大高频停用词的影响。但无论用哪种,都必须保证词典在训练集上建立,测试集只做转换,否则信息会提前泄露。
4. 集成学习与模型调优:把精度顶上去的实战技巧
4.1 随机森林和梯度提升:找对它们各自的“主场”
随机森林和梯度提升都是把多个弱学习器组合起来,但思路完全相反。随机森林属于Bagging,并列训练很多棵决策树,每棵树看到的数据和特征子集略有不同,最后取平均。它的好处是方差小、抗过拟合、对异常值相对稳,而且训练过程天然适合并行。梯度提升属于Boosting,一棵一棵地训练,后面的树专门拟合前面树的残差,整体偏差越压越低。代价是更容易把训练集噪声也学进去,需要更小心地控制学习率和树深度。
怎么选?我的经验是:样本量大、特征噪声多、想快速得到一个稳定基线时,首选随机森林;当数据比较规整、需要压榨到最高精度、且你有耐心做调参时,再用梯度提升。经典实例里常常先用随机森林跑一遍,再用梯度提升刷分数。两者在sklearn里实现方式差异不大,但参数敏感性差异很大:随机森林对参数不敏感,默认参数常常就够用;梯度提升要调的东西更多,后文专门说。
| 对比维度 | 随机森林 | 梯度提升 |
|---|---|---|
| 组合方式 | Bagging,并行训练 | Boosting,串行纠错 |
| 主要降低 | 方差 | 偏差 |
| 对异常值容忍度 | 较高 | 较低 |
| 参数敏感性 | 较低 | 较高 |
| 典型适用 | 快速稳定基线、高噪数据 | 追求精度、数据相对干净 |
4.2 网格搜索、随机搜索和参数范围的实战经验
模型调参最容易踩的坑是一上来就GridSearchCV大网格,比如把max_depth从1试到50,把n_estimators从10试到1000。这样不但慢,还容易在噪声上过拟合。正确策略是从粗到细。先用一个中等范围把主要参数粗糙扫一遍,找出大致好区域,再在小范围内精调。比如随机森林,我一般先固定n_estimators为200左右,然后扫max_depth从3到20,接着扫min_samples_split,最后再回头动n_estimators。样本量不大时,树的棵数超过某个阈值后精度提升非常有限,反而白白增加训练时间。
如果参数空间很大,随机搜索通常比网格搜索更高效,因为每个参数都有连续范围时,网格搜索大量时间浪费在没差异的位置上;随机搜索按分布抽样,能以更少次数覆盖更有价值的组合。虽然随机搜索听起来不如网格“严谨”,但实战中我遇到不少结果差不多的案例。另外每次调优都要记住固定随机种子,否则换了数据切分,参数搜索结果根本不可比,复现实验会变成玄学。如果你用GridSearchCV,可以把cv参数设成5,再用n_jobs=-1开启并行,但要控制好整体组合数。
4.3 用学习曲线确认过拟合还是欠拟合
调参之前一定要先判断模型的病根。学习曲线以训练样本量为横轴,画出训练集分数与交叉验证分数。训练分数和验证分数都很低,并且两条线接近,这是欠拟合,加正则化没用,应该增加特征、提高模型复杂度或者换更强的模型。训练分数高、验证分数低,两条线之间拉开鸿沟,这是过拟合,方向反过来:增加样本量、简化模型或加强正则化。
还有一个常常被忽视的行为:训练初期样本少的时候,两条线差距大是正常的,因为模型在小数据上容易记住现有样本;随着样本增加,验证曲线慢慢追上来才算健康。如果验证曲线始终上不来,不是数据量不够,而是模型容量不够或者特征表达力太弱。我自己习惯把学习曲线存成图,和训练日志一起留档,迭代新版本时回头看一眼,往往能避免很多无效调参。经典实例里的学习曲线经常被当成“配图”略过,但它是判断模型状态最直观的工具。
5. 无监督学习:聚类和降维,没有标签怎么评估效果
5.1 KMeans:K值、缩放和簇形状三个问题
KMeans在经典实例里出现的频率很高,因为它直观、快、好解释。但KMeans有三个前提经常被忽略:一是必须做特征缩放,因为它靠距离判断相似度;二是它假设簇是凸的、近似等大小的球形分布,如果数据是两个套在一起的大圆环,KMeans会强行切成两半;三是最小化的是簇内平方和,而不是“有没有业务意义”。所以在动手前先可视化,哪怕只降到两维看一眼,也能避免方向性错误。
K的选择最常用的是肘部法:画K值与簇内误差平方和(WCSS)的曲线,找下降趋势变缓的拐点。但肘部在真实数据里经常不清晰。这时候我会结合轮廓系数,它衡量一个样本与自己簇内样本的相似度和最近旁簇样本的差异,取值范围在-1到1,越大越说明簇分得开。如果业务上有先验知识,比如某运营活动要分五类人群,那就先按业务定K,再回头验证簇中心是否可解释。算法输出不等于业务结论,这是无监督学习里最容易忘的一点。
5.2 主成分分析:降维、可视化和一个容易忽略的前提
主成分分析的神奇之处在于,它能从相关矩阵里找出方差变化最大的几个方向,然后把数据投影到这些方向上。使用之前必须先做标准化。否则量纲大的维度会自动成为“主要成分”,那主成分分析就变成“按单位排序”了。经典实例里通常用explained_variance_ratio_查看每个主成分解释的方差比例,再画累计曲线。经验上保留到累计解释率85%到95%附近即可,不要一味追求到99%,那样留下的噪声和复杂度和收益不成正比。
主成分分析很适合把高维数据降到二维或三维做可视化,帮助理解样本分布。但它有个局限:主成分是原始特征的线性组合,可解释性会下降。比如第一个主成分可能是“年龄×0.6+收入×0.7+消费频次×0.3”,很难直接命名。如果目标是特征工程,而不是可视化,我更倾向于先用领域知识选特征,再用主成分分析验证思路。还有一点,主成分分析对稀疏矩阵非常不友好,先稠密化往往得不偿失,这种情况更适合直接把原始稀疏特征送进线性模型。
6. 模型评估与常见问题排查:从症状指向病因
6.1 训练集表现差和验证集表现差意味着两套修复方案
拿到模型报告,不要只盯着验证集分数机械调参。先分区看:训练集分数低,说明模型根本没能学出训练数据里的模式,这是欠拟合或数据质量差。验证集分数低而训练集高,才是过拟合。两者的修复方向基本相反。欠拟合时忙调正则化是浪费时间;过拟合时拼命加特征会雪上加霜。所以经典实例里一个很重要的提醒是:性能差并不是一个笼统的问题,先判断落在哪一边。
还有一种“太好了”也要警惕。如果验证集分数高得离谱,接近100%,第一反应应该是数据泄漏,而不是“我模型厉害”。最常见的泄漏来自预处理步骤:先对全量数据做了标准化或填补缺失值再切分,导致测试集信息混进训练过程;或者是特征里包含了目标变量的强泄露变量,比如用“是否退款”去预测“是否满意”。检查思路是逐条看特征生成逻辑,尤其警惕那些在预测时点根本拿不到但历史上存在的信息。
6.2 随机种子和样本不平衡:两个影响可复现性的暗门
随机种子看起来只是个小参数,实际上决定了你每次实验拿到的是不是同一份数据切分、同一个初始化。经典代码里经常写random_state=42,它并不仅仅是为了“演示稳定”,而是为了让你同时在多个候选模型上比较时,其他条件一致。如果你不固定种子,两个模型之间的差距可能来自随机性而不是模型本身。更稳妥的做法是同时跑多次交叉验证,用均值和标准差来报告结果,而不是只看一次验证集分数。
样本不平衡是另一个容易被指标掩盖的问题。正负样本比例1比99时,一个“永远预测负样本”的哑模型准确率也有99%,但这个模型毫无价值。此时应改用F1、召回率、精确率、AUC等指标,并在模型里设置class_weight='balanced',让少数类的损失权重更高。也可以在切分时用分层抽样,保证验证集里有足够少数类样本来计算可靠指标。经典实例里对这些处理得很细,我最初跳过了,结果调了半天准确率还在99%,后来才明白不是模型坏,是评估方式错了。
6.3 踩坑汇总:一张速查表解决八成常见问题
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 训练分数高、验证分数低 | 过拟合或数据泄漏 | 先检查预处理是否只用了训练集,再看模型复杂度,最后看数据量 |
| 训练分数和验证分数都低 | 欠拟合或特征表达不足 | 增加特征、提高模型复杂度,必要时换模型 |
| 验证分数接近100% | 数据泄漏 | 逐条检查特征生成逻辑,重点看是否存在目标变量的强相关变量 |
| 同样代码每次结果不同 | 随机种子未固定或样本太少 | 固定random_state,改用多次交叉验证取均值 |
| 准确率很高但业务无效 | 样本不平衡或指标选错 | 改用F1/AUC/召回率,设置class_weight |
| 聚类结果不断变化 | KMeans初始化随机 | 设置n_init和random_state,多次运行取稳定结果 |
整理这份笔记让我最大的收获不是记住了多少模型,而是养成了一种习惯:拿到任何机器学习问题,先问数据从哪来、任务属于哪一类、评价指标是什么,再谈算法。很多经典实例看起来简单,真正动手复现一遍,才知道细节里藏着多少坑。希望这份笔记也能给你同样的体感:代码是骨架,判断才是灵魂。