%matplotlib inline# 导包
import torch
from torch import nn
from d2l import torch as d2l
# 定义所需的train个数,test个数,输入的维度,样本数量,由于这里是想使用正则化来减少过拟合,所以训练数据比较小
n_train,n_test,num_input,batch_size = 20,100,200,5
# 产生权重和偏置的大小
true_w,true_b = torch.ones((num_input,1)) * 0.01 ,0.05
# 产生训练和测试数据,d2l.synthetic_data:产生n_train个y,y = true_w * x + true_b + noise,返回x,y
train_data = d2l.synthetic_data(true_w,true_b,n_train)
test_data = d2l.synthetic_data(true_w,true_b,n_test)
# 数据集进行mini_batch,自动分成batch_size个样本
train_iter = d2l.load_array(train_data,batch_size)
test_iter = d2l.load_array(test_data,batch_size,is_train=False)
# 定义所需参数
def init_params():
# 产生平均值为0,偏差为1,维度大小为(num_input行,1列)的权重,是否需要建立梯度为True
w = torch.normal(0,1,size=(num_input,1),requires_grad=True)
# 产生一个值为0的偏置,是否需要建立梯度为True
b = torch.zeros(1,requires_grad=True)
return w,b
# 定义l2项的正则,用来一会在loss函数后面加上
def l2_penalty(w):
return torch.sum(w ** 2)/2
"""
minloss = (y_hat - y) + w ** 2 * lambd
定义训练函数
这里简化了loss函数和正则项,loss为(y_hat - y),l2正则项为(w ** 2 * lambd)
lambd是正则化罚的力度,这个值越大,那么loss中l2正则项也会越大,
为了使loss尽可能min,那么就要将l2尽可能小下来,而超参数lambd固定,更新时就只好降低w的值
w的值逐渐降低,模型的弹性也会逐渐降低,可以理解为由于参数对于输出的影响逐渐下降导致模型的“参数”变少,使得模型复杂度降低,从而减少过拟合
当w下降到一定值时,由于降低l2所带来的loss的减少无法弥补由于模型弹性减少带来的弥合减少造成loss的增加,在反向更新中又会加大w的值,
最终趋于稳定,w停留在一个值上,
而之所以不直接去掉产生w,是因为w可以给模型增加一定的弹性,这个弹性可以在训练中有机器自己决定一部分的弹性,而非全部又人为定,
另外个人认为w还可以使得loss曲线更加平滑,而不是崎岖不平
"""
def train(lambd):
# w,b赋值
w,b = init_params()
# 使用匿名函数返回net(一个线性函数),loss(损失函数)两个实例,
# 匿名函数:定义一个临时函数,输入值为lambda后面的X,输出为后面的函数体,不需要加return,这里返回的是两个实例
net,loss = lambda X:d2l.linreg(X,w,b),d2l.squared_loss
# 定义训练轮数和学习率
num_epochs,lr = 100,0.003
# 实例化一个d2l库中的绘图函数,具体参数不了解
animator = d2l.Animator(xlabel='num_epochs',ylabel='loss',yscale='log',
xlim=[5,num_epochs],legend=['train','test',])
# 开始训练
for epoch in range(num_epochs):
# 取出train_iter中的x,y
for x,y in train_iter:
# 计算loss,并在项中加入正则
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)))
# 打印最后一轮结束后w的范数,就是w这个张量的值的大小
print("w的l2范数是:",torch.norm(w).item())
train(lambd=4)
#简洁表达的过程大体上没什么不一样,lambd改成了wd
def train_concise(wd):
#net直接使用nn库中自带的linear,传入输入维度(num_input)和输出维度1
net = nn.Sequential(nn.Linear(num_input, 1))
#取出net中的参数,按照正态分布对参数进行赋值
for param in net.parameters():
param.data.normal_()
#实例一个损失函数,直接使用nn中的MSELoss,reduction=‘none’表示直接返回n分样本的loss
loss = nn.MSELoss(reduction='none')
#轮数,学习率
num_epochs, lr = 100, 0.003
#优化器直接使用optim中的sgd,只不过将wd传入weight_decay表示正则,这里的包含关系是SGD(【参数{参数w,罚},{参数b}】,学习率)
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:
#d2l中的训练函数已经帮我们清空过梯度了,但是这里的优化器是用的nn中自带的sgd,不会帮我们自动清空,所以我们要先手动清零
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(lambd=5)