老码农和你一起学AI系列:模型调优与评估-调优基础与网格搜索
本文系统介绍了机器学习模型调优的核心概念与方法。首先区分了模型参数与超参数,指出调优的本质是调整人工设定的超参数。其次剖析了过拟合与欠拟合问题,阐释了偏差-方差权衡的底层逻辑。文章重点讲解了网格搜索方法,包括数据划分、参数空间确定等关键步骤,并通过sklearn的GridSearchCV实现鸢尾花分类任务调优的完整示例。最后建议通过手动实现简化版网格搜索来加深理解,为学习更高效的调优方法打下基础。
在 AI 模型训练中,“训练出模型” 只是第一步,“让模型效果更好” 才是核心目标 —— 而 “调优” 就是实现这个目标的关键手段。本节课我们会从基础概念出发,逐步拆解调优的逻辑,最终掌握最经典的调优方法 “网格搜索”。

一、先搞懂:为什么需要调优?
要理解调优的意义,必须先分清三个极易混淆的核心概念:模型参数与超参数、过拟合与欠拟合、偏差 - 方差权衡。这三者共同决定了 “调优的必要性”。
1. 模型参数 vs. 超参数:谁是 “可手动调整的旋钮”?
简单来说,两者的本质区别是 “是否由模型自己学习得到”:
- 模型参数:是模型在训练过程中 “自主学习” 的变量,比如线性回归中的权重(y=wx+b 里的 w)、神经网络的神经元连接系数。这些参数是模型 “思考的依据”,我们无法手动修改,只能通过数据训练让模型优化。
- 超参数:是在训练前 “人工设定” 的变量,是模型的 “配置项”。比如决策树的 “最大深度”(树最多分几层)、随机森林的 “树的数量”、梯度下降的 “学习率”(每一步更新参数的幅度)。调优,本质上就是调整超参数—— 因为这些 “人工设定的旋钮” 直接影响模型的学习效果。
举个生活化例子:把模型比作 “面包机”,模型参数是面包机根据面粉、水的比例 “自动计算” 的烘烤时长(无法手动改),超参数就是我们提前设定的 “烘烤温度”(可手动调)—— 温度太高会烤焦,太低会没熟,这就是超参数需要调优的原因。
2. 过拟合与欠拟合:模型的 “两种失败状态”
调优的直接目标,是避免模型陷入 “过拟合” 或 “欠拟合” 这两种无效状态:
- 欠拟合(Underfitting):模型 “没学会” 数据中的规律。比如用简单的线性模型预测复杂的房价(房价受面积、地段、学区等多因素非线性影响),模型只能拟合出一条直线,无法捕捉真实规律,最终训练集和测试集的误差都很大。
- 过拟合(Overfitting):模型 “学太死”,把训练集中的 “噪声” 当成了 “规律”。比如用深度很深的决策树预测房价,模型记住了训练集中某个特殊样本(比如某套房子因业主急售价格异常低),导致在训练集上误差极小,但在新的测试集上误差极大 —— 相当于学生死记硬背模拟题,考试遇到新题就不会了。
3. 偏差 - 方差权衡:调优的 “底层逻辑”
过拟合和欠拟合的本质,是 “偏差(Bias)” 与 “方差(Variance)” 的平衡:
- 偏差:模型对数据规律的 “拟合能力不足”(比如用线性模型拟合非线性数据),导致欠拟合。偏差高的模型 “太简单”,连训练数据的基本规律都抓不住。
- 方差:模型对数据噪声的 “敏感度过高”(比如深度决策树),导致过拟合。方差高的模型 “太复杂”,把训练数据中的偶然因素当成了必然规律。
调优的核心,就是在 “降低偏差” 和 “控制方差” 之间找平衡点 —— 比如调整决策树的 “最大深度”:深度太小(比如 1 层)会导致偏差高(欠拟合),深度太大(比如 20 层)会导致方差高(过拟合),找到合适的深度就是调优的过程。
二、调优前的准备:数据与参数空间
在开始调优前,有两项关键工作必须做好:数据划分和参数空间确定—— 这直接决定了调优的有效性和效率。
1. 数据划分:训练集、验证集、测试集的 “分工”
为什么不能把所有数据都用来训练?因为我们需要 “客观判断超参数的好坏”,所以必须把数据分成三部分:
- 训练集(Training Set):占比通常 60%-80%,用于模型学习参数(比如让决策树从数据中学习分支规则)。
- 验证集(Validation Set):占比通常 10%-20%,用于 “筛选超参数”。比如我们尝试了 3 种决策树深度(3、5、7),用训练集训练 3 个模型后,用验证集计算每个模型的误差,选误差最小的深度(比如 5)—— 这一步是 “调优的核心环节”。
- 测试集(Test Set):占比通常 10%-20%,用于 “最终评估模型泛化能力”。测试集必须是模型 “从未见过的数据”,用来模拟模型在真实场景中的表现。绝对不能用测试集调优—— 否则会像 “用高考题当模拟题复习”,失去评估的意义。
举个例子:假设我们有 1000 条房价数据,划分成 700 条训练集、150 条验证集、150 条测试集。用训练集训练不同深度的决策树,用验证集选出 “深度 5” 的模型,最后用测试集评估这个模型在新房价预测中的 accuracy(准确率),这才是合理的流程。
2. 参数空间确定:给超参数 “划定搜索范围”
参数空间是 “所有待尝试的超参数组合” 的集合,确定参数空间时要避免两个误区:“范围太大” 和 “粒度太细”。
如何合理设定参数空间?
参考经验值:每个超参数都有行业通用的合理范围,比如:
- 学习率(用于梯度下降类模型):通常在 1e-5 ~ 1e-1 之间(即 0.00001 到 0.1),太小会导致训练太慢,太大容易跳过最优解。
- 决策树最大深度:通常在 3 ~ 20 之间,太深容易过拟合,太浅容易欠拟合。
- 随机森林中树的数量:通常在 10 ~ 200 之间,太少会导致方差高,太多会增加计算成本但效果提升有限。
- 避免 “盲目枚举”:比如把学习率的范围设为 0 ~ 1、粒度设为 0.00001,会产生 10 万种组合,计算量极大却无意义 —— 合理的做法是 “在经验范围内,用对数尺度或步长划分”(比如学习率设为 [1e-5, 1e-4, 1e-3, 1e-2, 1e-1])。
三、网格搜索(Grid Search):最直观的调优方法
网格搜索是最基础、最易理解的超参数调优方法,核心思想是 “暴力枚举所有参数组合”—— 就像在一张 “参数网格” 上,逐个测试每个格子的效果。
1. 原理:“遍历所有组合,选最好的”
假设我们要调优两个超参数:决策树的 “最大深度(max_depth)” 和 “最小样本分裂数(min_samples_split)”:
- 设定 max_depth 的候选值:[3, 5, 7]
- 设定 min_samples_split 的候选值:[2, 4]
网格搜索会生成 3×2=6 种参数组合:
(max_depth=3, min_samples_split=2)
(max_depth=3, min_samples_split=4)
(max_depth=5, min_samples_split=2)
(max_depth=5, min_samples_split=4)
(max_depth=7, min_samples_split=2)
(max_depth=7, min_samples_split=4)
然后,针对每种组合,用训练集训练模型,用验证集计算模型性能(比如准确率、误差),最终选择 “验证集性能最好” 的组合 —— 这就是网格搜索的完整逻辑。
2. 实战演示:用 Scikit-learn 实现 GridSearchCV
Scikit-learn(简称 sklearn)是 Python 中最常用的机器学习库,其中 GridSearchCV 类已经封装了网格搜索的逻辑,还支持 “交叉验证”(进一步提升结果可靠性)。
步骤 1:准备数据与模型
我们用经典的 “鸢尾花数据集”(分类任务,预测花的品种),以 “随机森林” 为模型进行调优。
# 1. 导入所需库
from sklearn.datasets import load_iris # 鸢尾花数据集
from sklearn.ensemble import RandomForestClassifier # 随机森林模型
from sklearn.model_selection import train_test_split, GridSearchCV # 数据划分、网格搜索
from sklearn.metrics import accuracy_score # 评估指标(准确率)
# 2. 加载数据并划分训练集、测试集(这里用train_test_split先分训练+验证集和测试集)
iris = load_iris()
X = iris.data # 特征(花的萼片长度、宽度等)
y = iris.target # 标签(花的品种:0、1、2)
# 划分训练+验证集(80%)和测试集(20%)
X_train_val, X_test, y_train_val, y_test = train_test_split(
X, y, test_size=0.2, random_state=42 # random_state固定随机种子,保证结果可复现
)
步骤 2:定义参数网格
我们调优随机森林的两个超参数:
- n_estimators:树的数量(候选值:[10, 50, 100])
- max_depth:树的最大深度(候选值:[3, 5, 7])
# 定义参数网格:key是超参数名,value是候选值列表
param_grid = {
'n_estimators': [10, 50, 100],
'max_depth': [3, 5, 7]
}
步骤 3:创建 GridSearchCV 对象并训练
GridSearchCV 的核心参数:
- estimator:要调优的模型(这里是随机森林)
- param_grid:参数网格
- cv:交叉验证的折数(比如 cv=5,即把训练 + 验证集分成 5 份,每次用 4 份训练、1 份验证,循环 5 次,取平均性能)
- scoring:评估指标(比如分类任务用 “accuracy”,回归任务用 “neg_mean_squared_error”)
# 1. 初始化模型
rf_model = RandomForestClassifier(random_state=42)
# 2. 初始化网格搜索
grid_search = GridSearchCV(
estimator=rf_model,
param_grid=param_grid,
cv=5, # 5折交叉验证
scoring='accuracy', # 用准确率评估
n_jobs=-1 # 并行计算(用所有CPU核心,加速搜索)
)
# 3. 训练网格搜索(会自动遍历所有参数组合)
grid_search.fit(X_train_val, y_train_val)
步骤 4:查看结果并评估
# 1. 输出最佳超参数组合和最佳验证集准确率
print("最佳超参数组合:", grid_search.best_params_)
# 输出示例:最佳超参数组合:{'max_depth': 5, 'n_estimators': 100}
print("最佳验证集准确率:", grid_search.best_score_)
# 输出示例:最佳验证集准确率:0.9583(即95.83%)
# 2. 用最佳模型评估测试集性能
best_model = grid_search.best_estimator_ # 获取最佳参数对应的模型
y_pred = best_model.predict(X_test)
test_accuracy = accuracy_score(y_test, y_pred)
print("测试集准确率:", test_accuracy)
# 输出示例:测试集准确率:1.0(即100%,说明模型泛化能力好)
3. 网格搜索的优点与缺点
优点:
- 结果可靠:遍历所有组合,不会错过潜在的最优参数,适合小参数空间。
- 易于实现:sklearn 等库已封装成熟,无需手动写枚举逻辑。
- 支持并行:通过 n_jobs=-1 可利用多 CPU 核心加速,减少等待时间。
缺点:
- 计算成本极高:参数数量增加时,组合数呈 “指数级增长”—— 比如 3 个超参数各有 10 个候选值,组合数是 10×10×10=1000 种;5 个超参数各有 10 个候选值,组合数就是 10 万种,训练时间会变得非常长。
- 维度灾难(Curse of Dimensionality):当超参数数量(维度)增加时,有效参数组合的占比会急剧下降,大部分组合都是无效的,导致搜索效率极低。
四、最后小结
为了更深入理解网格搜索的 “枚举逻辑”,建议在鸢尾花数据集上手动实现一个简化版的网格搜索,步骤如下:
练习目标:
手动枚举随机森林的 2 个超参数(n_estimators=[10,50]、max_depth=[3,5]),找到验证集准确率最高的组合。
练习步骤:
- 数据划分:将鸢尾花数据集按 7:3 划分为训练集和验证集(不用测试集,简化流程)。
- 手动生成参数组合:列出所有 4 种组合:(10,3)、(10,5)、(50,3)、(50,5)。
- 循环训练与评估:
- 对每种组合,初始化随机森林模型,用训练集训练。
- 用验证集预测,计算准确率。
- 筛选最优组合:对比 4 种组合的验证集准确率,输出最优参数和对应的准确率。
提示:
手动实现的核心是 “for 循环遍历参数组合”,代码框架如下:
# 1. 数据划分(训练集+验证集)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.3, random_state=42)
# 2. 定义超参数候选值
n_estimators_list = [10, 50]
max_depth_list = [3, 5]
# 3. 初始化最优结果
best_accuracy = 0
best_params = None
# 4. 手动遍历所有组合
for n_estimators in n_estimators_list:
for max_depth in max_depth_list:
# 训练模型
model = RandomForestClassifier(
n_estimators=n_estimators,
max_depth=max_depth,
random_state=42
)
model.fit(X_train, y_train)
# 评估验证集准确率
val_accuracy = accuracy_score(y_val, model.predict(X_val))
print(f"参数组合(n_estimators={n_estimators}, max_depth={max_depth}):验证集准确率={val_accuracy}")
# 更新最优结果
if val_accuracy > best_accuracy:
best_accuracy = val_accuracy
best_params = (n_estimators, max_depth)
# 5. 输出最优结果
print(f"\n最优参数组合:n_estimators={best_params[0]}, max_depth={best_params[1]}")
print(f"最优验证集准确率:{best_accuracy}")
通过手动实现,你会更直观地感受到 “网格搜索的枚举本质”,以及为什么当参数数量增加时,计算量会急剧上升 —— 这也为后续学习 “随机搜索”“贝叶斯优化” 等更高效的调优方法打下基础。
更多推荐



所有评论(0)