一、为什么需要模型评估
训练出来的模型准确率高,不代表它就是一个好模型。一个常见陷阱是过拟合:模型在训练集上表现完美,但面对新数据时一塌糊涂。模型评估的核心目标是回答一个问题——这个模型能不能在未知数据上稳定可靠地工作。
1.1 过拟合 vs 欠拟合
| 现象 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
| 过拟合 | 训练集准、测试集差 | 模型太复杂 | 增加数据、正则化、降低复杂度 |
| 欠拟合 | 训练集和测试集都差 | 模型太简单 | 增加特征、换更复杂模型 |
1.2 评估指标体系
不同任务需要不同的评估指标,准确率不是万能的:
| 指标 | 公式 | 适用场景 |
|---|---|---|
| 准确率 | 正确数 / 总数 | 类别均衡 |
| 精确率 | TP / (TP + FP) | 关注误报代价(垃圾邮件) |
| 召回率 | TP / (TP + FN) | 关注漏报代价(疾病检测) |
| F1值 | 精确率与召回率的调和平均 | 精确率与召回率需兼顾 |
TP = 真正例,FP = 假正例,FN = 假负例,TN = 真负例
二、交叉验证实操
2.1 为什么简单划分不够
单次 train_test_split 的结果受随机种子影响,可能偏乐观或偏悲观。K 折交叉验证将数据分成 K 份,轮流用其中 1 份做测试、其余做训练,最终取平均,结果更可靠。
2.2 代码实操:K 折交叉验证
fromsklearn.datasetsimportload_breast_cancerfromsklearn.model_selectionimportcross_val_score,StratifiedKFoldfromsklearn.ensembleimportRandomForestClassifierfromsklearn.preprocessingimportStandardScalerfromsklearn.pipelineimportPipelineimportnumpyasnp# 加载数据data=load_breast_cancer()X,y=data.data,data.target# 用 Pipeline 封装标准化 + 模型,避免数据泄露pipeline=Pipeline([('scaler',StandardScaler()),('rf',RandomForestClassifier(n_estimators=100,random_state=42))])# 5 折分层交叉验证cv=StratifiedKFold(n_splits=5,shuffle=True,random_state=42)scores=cross_val_score(pipeline,X,y,cv=cv,scoring='f1')print(f"各折 F1 值:{scores}")print(f"平均 F1 值:{scores.mean():.4f}+/-{scores.std():.4f}")输出结果:
各折 F1 值: [0.9722 0.9722 0.9861 0.9653 0.9861] 平均 F1 值: 0.9764 +/- 0.00802.3 学习曲线分析
学习曲线展示训练集大小与模型表现的关系,是诊断过拟合/欠拟合的利器:
fromsklearn.model_selectionimportlearning_curveimportmatplotlib.pyplotasplt train_sizes,train_scores,val_scores=learning_curve(pipeline,X,y,cv=5,train_sizes=np.linspace(0.1,1.0,10),scoring='f1',n_jobs=-1)train_mean=train_scores.mean(axis=1)val_mean=val_scores.mean(axis=1)plt.figure(figsize=(10,6))plt.plot(train_sizes,train_mean,'o-',color='#FF6B6B',label='Training F1')plt.plot(train_sizes,val_mean,'o-',color='#4D96FF',label='Validation F1')plt.xlabel('Training Set Size',fontsize=12)plt.ylabel('F1 Score',fontsize=12)plt.title('Learning Curve',fontsize=14)plt.legend(fontsize=12)plt.grid(True,alpha=0.3)plt.tight_layout()plt.savefig('learning_curve.png',dpi=150,bbox_inches='tight')plt.show()如何判读学习曲线:
- 训练线高、验证线低,两线差距大 →过拟合
- 两条线都低,差距小 →欠拟合
- 两条线都高且接近 →理想状态
三、特征工程实操
3.1 特征工程的核心地位
业界有一句名言:数据和特征决定了机器学习的上限,模型和算法只是逼近这个上限。特征工程的质量直接决定最终效果。
3.2 数值型特征处理
importpandasaspdimportnumpyasnpfromsklearn.preprocessingimportStandardScaler,MinMaxScaler,RobustScaler# 模拟数据np.random.seed(42)data=pd.DataFrame({'age':np.random.normal(35,10,1000).clip(18,70),'income':np.random.lognormal(10,1,1000),# 偏态分布'score':np.random.uniform(0,100,1000)})# 方式一:标准化(均值为0,标准差为1)—— 适合大多数场景scaler_std=StandardScaler()data_std=scaler_std.fit_transform(data)# 方式二:归一化(缩放到0-1)—— 适合距离类算法scaler_minmax=MinMaxScaler()data_minmax=scaler_minmax.fit_transform(data)# 方式三:稳健缩放(用中位数和四分位距)—— 适合有离群值的数据scaler_robust=RobustScaler()data_robust=scaler_robust.fit_transform(data)3.3 特征选择
fromsklearn.feature_selectionimportSelectKBest,f_classif,mutual_info_classiffromsklearn.ensembleimportRandomForestClassifier# 方式一:方差分析(F检验)选择 Top-K 特征selector_f=SelectKBest(f_classif,k=10)X_selected_f=selector_f.fit_transform(X,y)selected_features_f=data.feature_names[selector_f.get_support()]print("F检验选出的特征:",list(selected_features_f))# 方式二:随机森林特征重要性rf=RandomForestClassifier(n_estimators=100,random_state=42)rf.fit(X,y)importances=rf.feature_importances_ top10_idx=np.argsort(importances)[::-1][:10]print("\n随机森林 Top10 重要特征:")foriintop10_idx:print(f"{data.feature_names[i]:30s}{importances[i]:.4f}")# 方式三:互信息法(能捕捉非线性关系)selector_mi=SelectKBest(mutual_info_classif,k=10)X_selected_mi=selector_mi.fit_transform(X,y)selected_features_mi=data.feature_names[selector_mi.get_support()]print("\n互信息选出的特征:",list(selected_features_mi))3.4 特征选择策略对比
| 方法 | 原理 | 优势 | 局限 |
|---|---|---|---|
| 方差分析 F 检验 | 线性相关性 | 计算快 | 只能发现线性关系 |
| 互信息法 | 信息论统计量 | 能发现非线性关系 | 计算量较大 |
| 随机森林重要性 | 分裂增益统计 | 准确、通用 | 需训练完整模型 |
| 递归特征消除 RFE | 逐步剔除 | 效果好 | 计算开销最大 |
四、综合实战:完整 Pipeline 流程
将上述知识整合为一个完整的机器学习 Pipeline,这也是实际项目中的标准做法:
fromsklearn.pipelineimportPipelinefromsklearn.model_selectionimportcross_val_score,GridSearchCVfromsklearn.preprocessingimportStandardScalerfromsklearn.feature_selectionimportSelectKBest,f_classiffromsklearn.ensembleimportRandomForestClassifierfromsklearn.datasetsimportload_breast_cancerimportnumpyasnp# 加载数据data=load_breast_cancer()X,y=data.data,data.target# 构建 Pipeline:标准化 -> 特征选择 -> 随机森林pipeline=Pipeline([('scaler',StandardScaler()),('feature_selection',SelectKBest(f_classif)),('classifier',RandomForestClassifier(random_state=42))])# 网格搜索调参param_grid={'feature_selection__k':[10,15,20,'all'],'classifier__n_estimators':[50,100,200],'classifier__max_depth':[4,6,8,None]}grid=GridSearchCV(pipeline,param_grid,cv=5,scoring='f1',n_jobs=-1)grid.fit(X,y)print(f"最佳参数:{grid.best_params_}")print(f"最佳 F1 值:{grid.best_score_:.4f}")# 查看参数组合的详细结果results=pd.DataFrame(grid.cv_results_)for_,rowinresults.nsmallest(5,'rank_test_score').iterrows():print(f"F1={row['mean_test_score']:.4f}| "f"k={row['params']['feature_selection__k']}, "f"n_est={row['params']['classifier__n_estimators']}, "f"depth={row['params']['classifier__max_depth']}")五、小结
- 模型评估不是可选项,交叉验证是衡量模型泛化能力的标准方法
- 学习曲线是诊断过拟合/欠拟合的第一工具,比单纯看准确率更有价值
- 特征工程决定模型上限,三种缩放方式各有所长,按数据特征选择
- Pipeline将预处理、特征选择、模型训练封装为整体,既防止数据泄露又方便调参
- 网格搜索 + 交叉验证是调参的黄金组合,实际项目中几乎必用