公司动态

【深度学习】模型选择、过拟合与欠拟合

📅 2026/9/2 6:59:08
【深度学习】模型选择、过拟合与欠拟合
一、训练误差 vs 泛化误差训练误差Training Error模型在训练数据上的误差。泛化误差Generalization Error模型在从未见过的新数据上的误差。类比训练误差 平时做课堂练习的正确率泛化误差 期末考试的成绩。关键结论我们真正关心的是泛化误差而不是训练误差。因为我们训练模型不是为了在训练集上得高分而是为了处理将来遇到的新数据。二、验证数据集 vs 测试数据集为了正确地估计泛化误差我们需要把手里的数据拆成三份2.1 训练数据集Training Set用来训练模型参数。比如把全部数据的 60% 拿来。2.2 验证数据集Validation Set用来选择/评估模型、调超参数学习率、层数、Dropout 比例等。不能和训练数据混在一起否则失去意义。比如把 60% 训练集里再拿出 20% ~ 50% 作为验证集。# 从训练集中划分出验证集n_trainint(0.8*len(train_dataset))train_subtorch.utils.data.Subset(train_dataset,range(0,n_train))val_subtorch.utils.data.Subset(train_dataset,range(n_train,len(train_dataset)))# train_sub 用于训练val_sub 用于调参2.3 测试数据集Test Set只用一次的数据集最终评估性能用。在任何情况下都不能用来调超参数否则结果就是作弊。类比验证集 模拟考试可以反复做用来调整复习方向测试集 正式高考只考一次考完就定论了如果你拿高考真题来练手那高考成绩就毫无意义了为什么不能拿测试集调参因为一旦你根据测试集的结果调整了模型测试集就不再没见过了它的评估就失去了泛化意义——就像上过考场见过真题的人成绩已经不反映真实水平。三、K-折交叉验证K-Fold Cross Validation当数据不够多的时候单纯拿出一部分做验证集太浪费。这时用K-折交叉验证。3.1 算法过程算法K-折交叉验证把训练数据均匀分成 K 块For i 1, 2, …, K:使用第 i 块作为验证数据集其余 K-1 块合并作为训练数据集训练模型记录第 i 折的验证误差报告 K 个验证误差的平均值常用 K 5 或 K 10。数据分割示意K5 折1: [验证][训练][训练][训练][训练] 折2: [训练][验证][训练][训练][训练] 折3: [训练][训练][验证][训练][训练] 折4: [训练][训练][训练][验证][训练] 折5: [训练][训练][训练][训练][验证] ↑ 每折轮流当一次验证集3.2 代码实现importtorchfromtorch.utils.dataimportDataset,DataLoaderdefk_fold_cross_validation(dataset,k,train_fn): 对 dataset 做 K-折交叉验证。 train_fn: 训练函数接收 (train_loader, val_loader)返回验证集误差 total_lenlen(dataset)fold_sizetotal_len//k val_losses[]foriinrange(k):# 第 i 折的验证集索引val_indiceslist(range(i*fold_size,(i1)*fold_size))train_indiceslist(set(range(total_len))-set(val_indices))train_subtorch.utils.data.Subset(dataset,train_indices)val_subtorch.utils.data.Subset(dataset,val_indices)train_loaderDataLoader(train_sub,batch_size256,shuffleTrue)val_loaderDataLoader(val_sub,batch_size256,shuffleFalse)# 每一折重新训练一个模型val_losstrain_fn(train_loader,val_loader)val_losses.append(val_loss)print(f折{i1}: 验证误差 {val_loss:.6f})# 报告 K 个验证误差的平均returnfloat(torch.tensor(val_losses).mean())K5avg_lossk_fold_cross_validation(train_dataset,K,my_train_fn)print(f{K}-折平均验证误差 {avg_loss:.6f})3.3 K5 和 K10 怎么选选项优点缺点适用K5训练快每折训练 80% 数据验证集稍大偏差稍高数据中等追求速度K10每折训练 90% 数据更接近全量训练慢成本高数据少且珍贵追求精度原则数据越少、越珍贵K 越大数据很多时直接固定一个验证集就行不必交叉验证。四、多项式函数拟合实战我们用一个三次多项式做实验4.1 生成带噪声的真实数据importtorchimportmatplotlib.pyplotasplt n_train100# 训练样本n_test100# 测试样本# 真实函数y 1.2x - 3.4x^2 5.6x^3 5再加一点噪声deftrue_fn(x):return1.2*x-3.4*x**25.6*x**35# 生成 x 和带噪声的 yfeaturestorch.randn(n_trainn_test,1)noisetorch.normal(0,0.5,sizefeatures.shape)labelstrue_fn(features)noise# 用多项式特征扩展把一列 x 变成 [x, x^2, x^3, ...]defpoly_features(x,degree):returntorch.cat([x**iforiinrange(1,degree1)],1)# 我们用次数 3 的多项式特征去拟合和真实函数次数一致degree3poly_featurespoly_features(features,degree)4.2 三个模型对比欠拟合 / 合适 / 过拟合我们用三种模型容量来拟合——次数不足、刚好、过高模型用它拟合结果线性次数 1三次数据欠拟合三次多项式次数 3三次数据刚好合适高次多项式次数 e.g.7三次数据过拟合importtorchfromtorchimportnndeftrain_poly(degree,epochs500,lr1e-2):用指定次数的多项式模型去拟合三次函数的数据# 多项式特征features_polytorch.cat([features**iforiinrange(1,degree1)],1)n_train100train_ffeatures_poly[:n_train]train_llabels[:n_train]test_ffeatures_poly[n_train:]test_llabels[n_train:]# 线性模型对多项式特征做线性组合等价于多项式拟合netnn.Linear(degree,1,biasFalse)net.weight.data.normal_(0,0.01)lossnn.MSELoss()trainertorch.optim.SGD(net.parameters(),lrlr)forepochinrange(epochs):trainer.zero_grad()lloss(net(train_f),train_l)l.backward()trainer.step()train_lossloss(net(train_f),train_l).item()test_lossloss(net(test_f),test_l).item()returntrain_loss,test_lossfordegreein[1,3,7]:tr,tetrain_poly(degree)print(f次数{degree}: 训练误差{tr:.6f}| 泛化(测试)误差{te:.6f})五、过拟合与欠拟合欠拟合Underfitting模型过于简单训练误差都无法降低。连课堂例题都做不对。过拟合Overfitting模型过于复杂训练误差很低但泛化误差很高。把例题全背下来了换个问法就废。六、模型容量与 VC 维7.1 模型容量Model Capacity模型容量 模型拟合各种函数的能力。低容量模型难以拟合训练数据欠拟合。高容量模型可以记住所有训练数据但往往过拟合。两种极端都不好。我们的目标是容量刚好够拟合真实规律但又不会大到把噪声也背下来。7.2 VC 维VC Dimension怎么定量衡量模型容量一个常用指标就是VC 维。VC 维的定义一个模型最多能打散shatter多少个数据点——即不管这些点的标签怎么排列正/负模型都能找到一个参数把这组点完全分开。二维平面上的线性分类器VC 维 3。它最多能完全分开 3 个点任意正负组合都能用一条直线分开但 4 个点就不行了比如 XOR 布局。数据点越多、越分散需要的 VC 维越大。VC 维 3二维直线的例子3 个点无论正负怎么排总能画一条直线分开 → 能打散VC维至少34 个点存在排法如 XOR无法用直线分开 → 打不散所以 VC维 4结论二维线性分类器 VC 维 3直觉VC 维越大模型越能记但也越容易过拟合。而在我们的代码里多项式次数越高等价于 VC 维越高。八、触发过拟合的典型场景场景为什么容易过拟合训练数据太少模型把少量样本的噪声当成了规律模型参数太多容量过大拟合能力超过真实复杂度训练轮数过多epoch 过长越训越背训练集验证误差开始反弹特征维度远大于样本数如基因数据、图像展平向量八、应对过拟合的策略8.1 降低模型容量少几层网络 / 少几个隐藏单元高次多项式 → 降到合适的次数前面实验里次数 7 → 次数 38.2 数据增强Data Augmentation让数据变多变花降低对噪声的记忆# 图像随机翻转 旋转制造更多样本transformtorchvision.transforms.Compose([torchvision.transforms.RandomHorizontalFlip(),torchvision.transforms.RandomRotation(10),torchvision.transforms.ToTensor(),])8.3 正则化Regularization给损失函数加上对大权重的惩罚让模型不要太张狂。L2 正则化权重衰减 Weight Decay# 优化器自带 weight_decaytrainertorch.optim.SGD(net.parameters(),lr0.01,weight_decay0.05)# weight_decay 让每一步更新时权重都往 0 缩一点抑制过拟合8.4 Dropout训练时随机失活一部分神经元防止过度依赖某些特征netnn.Sequential(nn.Linear(784,256),nn.ReLU(),nn.Dropout(0.5),# 训练时随机丢弃 50% 神经元nn.Linear(256,128),nn.ReLU(),nn.Dropout(0.5),nn.Linear(128,10),)8.5 早停Early Stopping在验证误差开始回升的那一刻停止训练best_valfloat(inf)forepochinrange(num_epochs):train_one_epoch()val_lossevaluate(model,val_loader)ifval_lossbest_val:# 出现了更低的验证误差best_valval_loss torch.save(model.state_dict(),best.pt)# 存最好的# 如果连续 N 轮验证误差不再下降就早停ifno_improve_countpatience:break参考资料《动手学深度学习》第 4 章模型选择、欠拟合和过拟合Bishop, C. (2006). Pattern Recognition and Machine Learning.