首页 / 资讯中心 / 文章详情

机器学习模型评估与优化:交叉验证、正则化与特征缩放实战

机器学习模型评估与优化:交叉验证、正则化与特征缩放实战 ★ FEATURED ARTICLE
1. 先把目标定清楚笔记2到底要解决什么问题说实话学 machine learning 最怕的不是公式看不懂而是代码能跑、模型能训练但你完全不知道它在真实数据上会不会翻车。这篇笔记2我打算接着上一篇继续写重点不再是最基础的线性回归和手写梯度下降而是把评估、交叉验证、正则化、特征缩放这几件平时最容易忽略、却真正决定模型能不能用的事情串起来。如果你只是跟着教程把模型 fit 了一遍那这篇内容很值得往下看如果你已经用 sklearn 做过几个小项目这篇文章更像是一个检查清单看看自己哪里还差一步。我写的时候尽量不堆公式每个概念都配一个实际场景让你能直接拿着这套思路去跑自己的数据。1.1 从“能跑通”到“跑得好”我见过不少人包括我自己刚开始的时候跑完几个经典入门数据集就觉得自己已经入门了。但真实项目完全不是这么回事数据里有缺失、有异常、有类别不平衡特征有的量纲差几百倍你以为模型训练得很顺利最后拿到测试集上却发现指标惨不忍睹。这个阶段最典型的困惑是训练集上准确率 98%测试集上只有 70%到底哪里出了问题别人说用正则化、用交叉验证我也照着写了但为什么结果变化不大为什么同一个模型换一批数据结果就差这么多这些问题没有一个可以靠“背 API”解决而是要理解背后的逻辑。笔记2要做的就是把这些逻辑补齐让你手里不只有fit和predict还有一套判断模型好坏的方法论。1.2 学这篇之前你最好已经掌握了什么虽然我尽量写得通俗但还是有前置知识不然看某些段落会觉得莫名其妙。建议先确认自己满足这张表里的条件前置内容需要到什么程度Python 与 sklearn能读写数据能调用fit/predict能看懂常见报错机器学习基础概念知道线性回归、逻辑回归、决策树大致是怎么回事梯度下降知道模型通过反复更新参数来减小损失Pandas / NumPy能做简单的数据清洗、分组统计、筛选列如果这些还不太熟建议先把基础部分消化掉再回来看这篇。不是故意劝退而是跳过前置会学得很痛苦。举个例子如果你不理解“参数”和“损失函数”的关系后面看正则化和学习率就会像看天书。2. 模型评估别再用准确率骗自己了这是我想说的第一个重点。很多初学者拿到一个分类任务第一反应就是跑出准确率看到 90% 就觉得模型很厉害。但准确率很可能在骗你尤其是在类别不平衡的场景下。2.1 为什么准确率最容易误导人先看一个实际场景假设一份客户流失数据里1000 个客户中只有 10 个人真正流失了。此时哪怕你写一个“永远预测所有人都不流失”的傻瓜模型准确率也能到 99%。这个模型有用吗当然没用因为公司真正关心的是那 10 个会流失的人一个都没找到。准确率把所有类别“一视同仁”但现实里不同类别的重要性往往完全不同。所以我不建议在分类问题上只盯着准确率至少要看混淆矩阵和由它派生出来的指标。混淆矩阵长这样预测为流失预测为不流失实际流失TP真正例FN假负例实际不流失FP假正例TN真负例TP 是你成功抓出来的流失客户FN 是你漏掉的流失客户FP 是你误伤的正常客户。这四个数字合在一起比一个孤零零的准确率信息量大得多。2.2 精确率、召回率与 F1到底哪个更重要从混淆矩阵可以算出两个非常常用的指标精确率 TP / (TP FP)意思是“你预测成流失的人里有多少是真的流失了”。召回率 TP / (TP FN)意思是“真实流失的人里你找回了多少”。精确率高代表你预测的正例质量高误伤少召回率高代表你能把正例尽可能找回来哪怕误伤多一点也无所谓。问题是你很难两个同时高把阈值调低召回率上去了精确率往往会掉把阈值调高精确率上去了召回率又会降。这两个指标的权衡要看业务。如果是银行风控把“坏客户”漏掉代价很大可能更看重召回率如果是推荐系统推荐了一堆不相关的内容用户会很烦可能更看重精确率。如果不想纠结就用 F1 分数它是精确率和召回率的调和平均数值高的模型通常在这两个方向上都比较均衡。我在实际项目里习惯先打印classification_report把 precision、recall、F1 全部看一遍再结合业务判断该优化哪个指标。只看一个数很容易被误导。2.3 回归任务看什么MAE、MSE 和 R²分类说完回归也不能落下。回归任务里最常见的指标是平均绝对误差MAE、均方误差MSE和决定系数 R²。MAE 算的是预测值和真实值差的绝对值平均直观单位跟目标一样。MSE 算的是差的平方平均对离群点非常敏感。R² 表示模型解释了目标变量多少比例的波动越接近 1 越好。举个例子房价预测时大多数房子的价格可能在两三百万但有几套豪宅价格上亿。MAE 会把差距平摊看着还正常MSE 因为取平方会把几套豪宅的偏差放大得非常明显导致模型为了迁就极端值把普通房子都预测偏了。我一般会同时看 MAE 和 R²MAE 看平均偏差水平R² 看整体解释能力。如果两者矛盾比如 MAE 下降但 R² 也下降就要怀疑是否有离群点把模型带偏了。这时候再做异常值分析不要急着换模型。3. 过拟合与正则化模型不是越复杂越好第二个重点也是新手最常踩的坑模型在训练集上完美一到测试集就崩。这就是过拟合。3.1 过拟合的本质偏差和方差的较量过拟合可以这样理解模型太聪明把训练数据里的噪声也当成规律记住了。考试前把题目答案背得滚瓜烂熟换了新题目反而不会做。更严谨一点机器学习里有“偏差-方差”这对概念偏差高模型过于简单训练集都拟合不好这叫欠拟合。方差高模型过于复杂训练集拟合得很好但换数据表现大跌这叫过拟合。遇到这种情况一开始不要急着调模型结构先看训练集和验证集的差距。如果训练损失很低、验证损失很高基本可以确认是方差问题。此时最简单的不是换一个更大更牛的模型而是降低模型复杂度或者给模型加约束。3.2 L1 和 L2 正则化两兄弟的不同做法正则化就是在损失函数里加一个惩罚项让模型不要那么“放肆”地拟合数据。常见的两种是 L1 和 L2。L1 正则化在原损失后面加 λ × 权重绝对值之和。L2 正则化在原损失后面加 λ × 权重平方和之和。直觉上L1 会把一部分权重直接压成 0相当于自动做特征选择L2 会把所有权重都压得比较小但不会清零让模型更平滑、更稳定。如果你有一堆特征且不太确定哪些有用可以先试 L1如果你更关心模型整体稳定L2 是默认选择。在 sklearn 的逻辑回归里正则化强度不是直接传 λ而是传C。注意C是正则化强度的倒数C越大正则化越弱C越小正则化越强。我第一次用的时候就在这里搞反了花了好一阵才反应过来。from sklearn.linear_model import LogisticRegression # C 越小正则化越强权重越会被压缩 model LogisticRegression(penaltyl2, C0.1, max_iter1000)3.3 K折交叉验证让评估结果更有底气既然不能只用训练集评估怎么才能更可靠地知道模型效果答案是交叉验证。K 折交叉验证会把训练数据切成 K 份每次拿出 1 份做验证剩下 K-1 份做训练轮流 K 次最后得到 K 个分数。这样做的意义在于你不会因为某一次数据划分的运气好而误判模型。比如一个模型在某一折上 F1 有 0.75换个折只剩 0.55说明模型很不稳定。平均值能给你一个更可靠的估计标准差能告诉你稳定性。from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression model LogisticRegression(max_iter1000, C0.1) scores cross_val_score(model, X, y, cv5, scoringf1) print(平均 F1:, scores.mean()) print(标准差:, scores.std())这里有个容易忽略的点交叉验证只是用来评估和选参数最后真正上线还是要用全部训练数据重新训练模型。很多人跑完cross_val_score直接拿结果当最终模型就大错特错了。4. 梯度下降现场 debug学习率、初始化与特征缩放笔记1里可能已经写过梯度下降公式但理论是理论实际训练时你会遇到各种让人抓狂的问题。这一节专门讲我怎么排查训练过程中的异常。4.1 损失曲线不会骗人训练模型时我最先看的永远是损失曲线。它比任何指标都诚实。如果损失一开始就飙升到近乎无穷大通常是学习率太大参数直接“飞”出去了。如果损失下降得非常缓慢很久才降一点点可能是学习率太小或者特征尺度差异太夸张。如果训练损失很低但验证损失先降后升基本是过拟合的信号。一个实用的习惯是把损失用对数坐标打印因为损失初期可能从 10 降到 0.1后期从 0.1 降到 0.01跨度很大线性坐标看不出细节。我用的工具是简单的循环加 matplotlib不需要什么复杂框架。4.2 特征缩放为什么能救收敛速度特征缩放是个成本极低、收益极高的操作。假设有两个特征一个取值范围是 0 到 10另一个是 0 到 100000梯度下降的路径会在参数空间里形成非常窄长的等高线参数更新就像在夹缝里走路容易来回震荡收敛极其缓慢。把特征都缩放到差不多的尺度后等高线接近圆形梯度方向更直接收敛速度会明显提升。常见做法有两种MinMaxScaler把特征压缩到 [0, 1] 区间适合分布比较均匀的连续特征。StandardScaler把特征变成均值 0、方差 1适合大多数模型尤其是线性模型和神经网络。代码很简单from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意一个关键细节fit缩放器只能用在训练集上。测试集必须用同一个已经训练好的 scaler 去做transform不能重新fit。如果对整个数据集一起缩放模型会“偷看”测试集的信息这叫数据泄漏会让评估结果虚高。4.3 学习率、迭代次数和早停的配合除了特征缩放学习率和迭代次数也需要配合调。学习率太大损失爆炸学习率太小训练半天没什么进展。我习惯先从一个比较小的值开始比如 0.01再根据损失曲线调整而不是一上来就乱试。在框架支持的情况下还可以做早停early stopping训练过程中记录验证损失如果验证损失连续好几个轮次都没有下降就停止训练。这个方法能有效防止过拟合比单纯调小学习率更直接。best_val_loss float(inf) patience 0 for epoch in range(max_epochs): train_loss train_one_epoch(model, X_train, y_train) val_loss evaluate(model, X_val, y_val) if val_loss best_val_loss: best_val_loss val_loss patience 0 else: patience 1 if patience 5: print(fEarly stop at epoch {epoch}) break早停不是万能药正则化和交叉验证该用还是要用但它是一个非常符合直觉的兜底策略既然继续训练只会让验证集变差那就停下来。5. 实战记录一个完整练手流程前面讲了很多概念现在把它们串起来。我拿一份脱敏的客户流失数据练手大约 8000 行、20 个特征标签是“是否流失”。任务很简单预测用户会不会流失并找出最关键的因素。这部分是我自己真实跑过的流程代码做了简化但思路完全保留。5.1 先建一个能跑的 baseline拿到数据第一步不是上模型而是先把数据切好然后跑一个最简单的逻辑回归作为 baseline。没有 baseline后面做的任何优化都无法判断是否真的有效。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report data pd.read_csv(customer_churn.csv) X data.drop(churn, axis1) y data[churn] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) model LogisticRegression(max_iter1000) model.fit(X_train_scaled, y_train) print(classification_report(y_test, model.predict(X_test_scaled)))我这份数据里流失类大约只占 18%属于类别不平衡。所以我没有只看准确率而是重点看 F1。第一版 baseline 跑出来F1 大概在 0.61 左右不算好但至少有了对比基准。5.2 用交叉验证选正则化强度接下来我用网格搜索调C同时配合交叉验证。注意这里我把StandardScaler和逻辑回归包进一个Pipeline避免缩放过程造成数据泄漏。from sklearn.model_selection import GridSearchCV from sklearn.pipeline import make_pipeline pipe make_pipeline( StandardScaler(), LogisticRegression(max_iter1000) ) param_grid { logisticregression__C: [0.01, 0.1, 1, 10] } grid GridSearchCV( pipe, param_grid, cv5, scoringf1 ) grid.fit(X_train, y_train) print(最佳参数:, grid.best_params_) print(交叉验证最佳 F1:, grid.best_score_)跑下来之后C0.1比默认的C1.0效果更好。原因不复杂特征多数据又有噪声稍微强一点的正则化让模型不那么纠结于个别特征泛化能力自然更强。这一步把交叉验证 F1 从 0.61 提到了 0.66 左右。5.3 在独立测试集上做最终确认网格搜索得到的best_score_是在交叉验证中的平均分数它不能当作最终上线指标因为你已经在这个验证集上选了参数相当于对验证集产生了“间接过拟合”。所以我最后一定会回到最开始留出的测试集重新评估一次。best_model grid.best_estimator_ test_pred best_model.predict(X_test) print(classification_report(y_test, test_pred))最终测试集的 F1 大约在 0.67 左右和交叉验证的结果差别不大说明模型比较稳定。如果交叉验证分数很高、测试集分数却大幅下降那就要担心是不是在验证集上调参调过头了或者随机种子选得太巧。5.4 复盘这次练手踩过的三个坑第一个坑也是最常见的就是我一开始在全量数据上做了缺失值填充然后才切分训练测试集。这看起来没问题但实际上fillna用了整个数据集的统计量已经让测试集信息混进了训练过程。后来我改成先切分再单独对训练集和测试集做填充评估结果才更可信。第二个坑是刚开始只看了准确率。因为流失类只占 18%一个“全部预测不流失”的模型准确率也能到 82%我差点以为这个模型还不错。后来换成 F1 和混淆矩阵才看清对少数类的识别能力其实很弱。第三个坑是为了调高 F1 不断增加特征结果交叉验证分数不升反降。后来用 L1 正则化观察哪些特征的权重变成 0才发现不少高基数类别特征只是噪声删掉之后模型反而更稳。这说明在特征工程阶段不要迷信特征数量质量比数量重要得多。5.5 一些实操里值得反复提醒自己的事这次练手下来我最大的感受是机器学习项目的破局点往往不在模型本身而在数据切分、特征缩放、评估指标这些“脏活累活”上。之前我很喜欢追新模型后来才发现把 baseline 做扎实把评估流程搞正确比盲目换模型有用得多。另外我会把每一次实验的配置记下来包括数据量、缺失值处理方式、特征列表、C值、交叉验证分数和测试集分数。这样过两周再回头看还能想起来当时为什么做这个决定而不是靠模糊记忆复盘。最后再分享一个小技巧如果你也经常在同一个数据集上反复调参最好设置固定的随机种子。否则即使代码一模一样换一次数据划分测试集分数可能波动好几个百分点让你误以为模型改进了。固定种子并不能让结果更真实但至少能让每一次实验都处在同一把尺子下比较。这种习惯我觉得比多学十个算法还重要。
阅读完成 · 觉得有帮助?
咨询建站