摘要:本文探讨了深度学习中的模型选择与过拟合问题。在模型评价方面,指出泛化能力是核心指标,受参数量、参数取值和训练样本数量影响。重点分析了欠拟合和过拟合现象,其中过拟合表现为训练误差小而测试误差大。为应对过拟合,介绍了正则化技术,特别是L2正则化(权重衰减),通过向损失函数添加惩罚项来提升泛化性能。文章通过PyTorch实现展示了权重衰减的效果:未正则化时模型明显过拟合,而加入L2正则化后测试误差显著降低。最后强调正则化是平衡模型复杂度、防止过拟合的有效方法。
1.模型选择(model selection)
1.1模型的评价
多层感知机即深度神经网络的训练过程就是用模型拟合训练集数据的过程。使用不同的隐藏层数和隐藏单元数的模型都可以达到满足拟合要求的程度,即训练误差(training error)满足要求。但不同模型在测试集的表现却不一定优秀,即模型的泛化能力不同,即泛化误差(generalization error)不同。
在训练模型的过程中,影响模型泛化能力的因素主要有三点:
(1)可调整参数的数量。参数量较多时,模型容易过拟合;
(2)参数的取值。参数的权值取值范围较大时,模型可能过拟合;
(3)训练样本的数量。样本越少,过拟合越容易。
因此,评价选择的模型就是看模型的泛化能力。
1.2拟合问题
欠拟合是值模型通过训练无法达到训练误差的要求,通常表明选择的模型过于简单,无法满足拟合要求。通常表现是模型训练中继续迭代训练误差无法继续减小。欠拟合的原因是模型过于简单或者训练不足导致。
过拟合是指在训练集中表现优秀但测试集中表现较差的情况,即训练误差远小于验证误差。这种情况下训练已经无法提高模型质量了。
由于不能基于训练误差来估计泛化误差,因此简单地最小化训练误差并不一定意味着泛化误差的减小。机器学习模型需要防止过拟合,即防止泛化误差过大。
为了防止过拟合,研究者们想出了很多方法。
1.3防止过拟合
正则化技术就是防止模型过拟合的方法。正则化方法即为在此时向原始模型引入额外信息,以便防止过拟合和提高模型泛化性能的一类方法的统称。在实际的深度学习场景中我们几乎总是会发现,最好的拟合模型(从最小化泛化误差的意义上)是一个适当正则化的大型模型。权重衰减是最广泛的正则化技术之一,也被成为L2正则化。
范数L1,L2和Lp的表达式如下,L1和L2是Lp范数的特里。
使用了权重衰减后,将原来的训练目标进行了调整,是将范数当作惩罚项添加到了最小化损失中:
训练目标:最小化训练标签上的预测损失->最小化预测损失和惩罚项之和
2权重衰减的实现
2.1 从零实现
2.1.1模型建立
%matplotlib inline
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
#生成数据
n_train,n_test,num_inputs,batch_size=20,100,200,5
true_w,true_b=torch.ones((num_inputs,1))*0.01,0.05
train_data=d2l.synthetic_data(true_w,true_b,n_train)
train_iter=d2l.load_array(train_data,batch_size)
test_data=d2l.synthetic_data(true_w,true_b,n_test)
test_iter=d2l.load_array(test_data,batch_size,is_train=False)
2.1.2初始化模型参数
#初始化函数
def init_params():
w=torch.normal(0,1,size=(num_inputs,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)
return [w,b]
2.1.3定义L2范数惩罚
#L2范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2))/2
2.1.4训练定义
#训练代码
def train(lambd):
w,b=init_params()
net,loss=lambda X:d2l.linreg(X,w,b),d2l.squared_loss
num_epochs,lr=100,0.003
animator=d2l.Animator(xlabel='epochs',ylabel='loss',yscale='log',xlim=[5,num_epochs],legend=['train','test'])
for epoch in range(num_epochs):
for X, y in train_iter:
#增加了L2范数惩罚项
#广播机制使l2_penalty(w)成为一个长度为batch_size的向量
l=loss(net(X),y)+lambd*l2_penalty(w)
l.sum().backward()
d2l.sgd([w,b],lr,batch_size)
if(epoch+1)%5==0:
animator.add(epoch+1,(d2l.evaluate_loss(net,train_iter,loss),d2l.evaluate_loss(net,test_iter,loss)))
print('w的L2范数是:',torch.norm(w).item())
2.1.5训练
忽略正则化训练
#忽略正则化直接训练
train(lambd=0)
注意,训练误差减小但测试误差没有减小,意味着出现了严重的过拟合。
使用权重衰减
#使用权重衰减
train(lambd=3)
注意,训练误差在减小后有轻微反弹,但测试误差减小,符合我们期望从正则化中得到的效果。
2.2函数实现
将权重衰减用函数实现:
def train_concise(wd):
net=nn.Sequential(nn.Linear(num_inputs,1))
for param in net.parameters():
param.data.normal_()
loss=nn.MSELoss(reduction='none')
num_epochs,lr=100,0.003
#偏置参数没有衰减
trainer=torch.optim.SGD([{"params":net[0].weight,'weight_decay':wd},{"params":net[0].bias}],lr=lr)
animator=d2l.Animator(xlabel='epochs',ylabel='loss',yscale='log',xlim=[5,num_epochs],legend=['train','test'])
for epoch in range(num_epochs):
for X, y in train_iter:
trainer.zero_grad()
l=loss(net(X),y)
l.mean().backward()
trainer.step()
if(epoch+1)%5==0:
animator.add(epoch+1,(d2l.evaluate_loss(net,train_iter,loss),d2l.evaluate_loss(net,test_iter,loss)))
print('w的L2范数是:',net[0].weight.norm().item())
#无正则化
train_concise(0)
#权重衰减
train_concise(3)
正则化是处理过拟合的常用方法:在训练集中的损失函数中加入惩罚项,以降低已学习的模型的复杂度。