本周观看了李沐老师的《动手学深度学习》,李沐老师很注重代码的编写,但是对概念的讲解并不是特别多,所以还要去结合一些其他的资料去进行了解,下面是本周的所看的课程总结。
目录
感知机
感知机是给定输入X,权重w,偏移b,输出sigma(+b),当sigma(x)中,x大于0时,输出为1,其他条件下,输出为-1
它是一个二分类问题,输出-1或者1
训练感知机如下:
在条件判断语句中,为误差的情况下,若判断错误,则更新权重w和偏差b
感知机的收敛定理如下:
但是感知机不能够拟合XOR函数,也就是异或问题,只能产生线性分割面。
多层感知机
在感知机中不能够拟合XOR函数,但是利用多层感知机可以解决这个问题,学习两个简单函数,再进行组合就可以。
如图所示,我们可以首先利用图中的y轴作为判断,y左边为正例,y右边为反例,再利用图中的x轴作为判断,x上边为正例,x下边为反例,再将这两次得到的结果相乘,最后就是得到我们真正的分类。
引出了单隐藏层的概念,如图所示:
其中超参数的意思是人为可以设置,可以选择的参数;而模型参数是通过训练模型自己得到的参数。
其中m为隐藏神经元的个数,n为样本特征数,其中在隐藏层中我们需要非线性的激活函数,如果用线性的激活函数sigma,则 最后的模型还是线性模型。
常见的激活函数有阶跃函数,当x>0时,输出为1,当x<=0时,输出为0;sigmoid函数
Tanh激活函数,输入投影到(-1,1)
ReLU函数
多层感知机解决多类分类问题,多类分类与softmax的唯一区别就是多加了隐藏层(带非线性化的全连接层),其他没有本质区别。
之后,进行逐步压缩
相较于线性softmax,多层感知机的灵魂在于多出来的中间隐藏层的激活函数。
并且如果有多层隐藏层的话,前面到后面的隐藏层神经元越来越少;在第一个隐藏层开始时,先将input放大,然后再逐步压缩,如果一开始不放大,压缩的很小,这样会丢失许多特征信息。
多层感知机的从零开始实现
1、首先引入模块
import torch
from torch import nn
from d2l import torch as d2l
2、设置批量大小,训练集,测试集
batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
3、实现一个具有单隐藏层的多层感知机,包含256个隐藏单元
num_inputs,num_outpus,num_hiddens = 784,10,256
W1 = nn.Parameter(torch.randn(num_inputs,num_hiddens,requires_grad=True))
b1 = nn.Parameter(torch.zeros(num_hiddens,requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens,num_outpus,requires_grad=True))
b2 = nn.Parameter(torch.zeros(num_outpus,requires_grad=True))
params = [W1,b1,W2,b2]
4、实现ReLU激活函数
def relu(X):
a = torch.zeros_like(X)
return torch.max(X,a)
5、实现我们的模型
def net(X):
X = X.reshape((-1,num_inputs))
H = relu(X@W1 + b1) # @为matmul
return (H@W2 + b2)
loss = nn.CrossEntropyLoss()
6、多层感知机的训练过程与softmax回归的训练过程完全相同
num_epochs = 10
lr = 0.1
updater = torch.optim.SGD(params,lr=lr)
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,updater)
多层感知机的简洁实现
区别主要在于创建模型时,直接使用nn.Flatten(),nn.ReLU()
代码如下:
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))
def init_weighs(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight,std=0.01)
net.apply(init_weighs)
batch_size,lr,num_epochs = 256,0.1,10
loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(),lr=lr)
train_iter,test_iter= d2l.load_data_fashion_mnist(batch_size=batch_size)
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
多层感知机pycharm实现,利用tensorboard
1、引入相关模块
import torch
from torch import nn
from torch.utils.data import DataLoader
import torchvision
from torch.utils.tensorboard import SummaryWriter
2、创建训练集,测试集,并求长度
train_data = torchvision.datasets.FashionMNIST(root='../data', train=True, transform=torchvision.transforms.ToTensor())
test_data = torchvision.datasets.FashionMNIST(root='../data', train=False, transform=torchvision.transforms.ToTensor())
train_data_size = len(train_data) # 训练集合长度
test_data_size = len(test_data) # 测试集合长度
# print(train_data_size, test_data_size)
3、利用dataloader数据加载器
train_dataloader = DataLoader(dataset=train_data,batch_size=256,shuffle=True)
test_dataloader = DataLoader(dataset=test_data,batch_size=256,shuffle=True)
4、创建模型,同多层感知机简洁实现
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.model = nn.Sequential(
nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
def forward(self, x):
x = self.model(x)
return x
5、将模型实例化
net = Net()
6、创建损失函数,学习率,优化器等参数
loss_fn = nn.CrossEntropyLoss()
lr = 0.01
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
7、进行训练
total_train_step = 0 # 训练训练步数
total_test_step = 0 # 测试训练步数
epochs = 10
writer = SummaryWriter('../logs')
for epoch in range(epochs):
print(f'第{epoch+1}轮训练开始')
net.train() # 训练
for data in train_dataloader:
imgs,targets = data
outputs = net(imgs)
loss = loss_fn(outputs, targets)
optimizer.zero_grad() # 梯度清零
loss.backward() # 自动求导
optimizer.step() # 优化
total_train_step += 1
if total_train_step % 100 == 0:
print(f'训练次数{total_train_step},loss:{loss.item()}')
writer.add_scalar('train_loss', loss.item(), global_step=total_train_step)
net.eval() # 评估,测试
total_test_loss = 0 # 总损失
total_accuracy = 0 # 准确率
with torch.no_grad():
for data in test_dataloader:
imgs,targets = data
outputs = net(imgs)
loss = loss_fn(outputs, targets)
total_test_loss += loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print(f'整体测试集的误差:{total_test_loss}')
print(f'整体测试集的准确度:{total_accuracy/test_data_size}')
writer.add_scalar('test_loss', total_test_loss, global_step=total_test_step)
writer.add_scalar('accuracy', total_accuracy/test_data_size, global_step=total_test_step)
total_train_step += 1
writer.close()
第1轮训练开始
训练次数100,loss:1.865459680557251
训练次数200,loss:1.401742935180664
整体测试集的误差:52.42628455162048
整体测试集的准确度:0.652400016784668
第2轮训练开始
训练次数300,loss:1.1382524967193604
训练次数400,loss:0.9439620971679688
整体测试集的误差:37.7695706486702
整体测试集的准确度:0.6811000108718872
第3轮训练开始
训练次数500,loss:0.9452598094940186
训练次数600,loss:0.8075324296951294
训练次数700,loss:0.7888470888137817
整体测试集的误差:32.52859479188919
整体测试集的准确度:0.7199000120162964
第4轮训练开始
训练次数800,loss:0.7249954342842102
训练次数900,loss:0.783866286277771
整体测试集的误差:30.158737897872925
整体测试集的准确度:0.7389000058174133
第5轮训练开始
训练次数1000,loss:0.6621206998825073
训练次数1100,loss:0.7127969264984131
整体测试集的误差:28.307612121105194
整体测试集的准确度:0.7520999908447266
第6轮训练开始
训练次数1200,loss:0.6374375820159912
训练次数1300,loss:0.5847610235214233
训练次数1400,loss:0.633319079875946
整体测试集的误差:26.60741612315178
整体测试集的准确度:0.767300009727478
第7轮训练开始
训练次数1500,loss:0.6142456531524658
训练次数1600,loss:0.6475222110748291
整体测试集的误差:25.413392186164856
整体测试集的准确度:0.777999997138977
第8轮训练开始
训练次数1700,loss:0.6077256202697754
训练次数1800,loss:0.5440351366996765
整体测试集的误差:24.776959896087646
整体测试集的准确度:0.785099983215332
第9轮训练开始
训练次数1900,loss:0.5533126592636108
训练次数2000,loss:0.7141172885894775
训练次数2100,loss:0.6235008835792542
整体测试集的误差:23.892668545246124
整体测试集的准确度:0.7904999852180481
第10轮训练开始
训练次数2200,loss:0.5329833626747131
训练次数2300,loss:0.5671280026435852
整体测试集的误差:23.495265007019043
整体测试集的准确度:0.7946000099182129
可见误差越来越小,准确度越来越高
模型选择,过拟合,欠拟合
对于模型选择,我们更关心泛化误差,泛化误差是指在新数据上的误差。
我们可以用三种数据集,训练集,验证集,测试集来进行训练和评估
训练集用于模型的训练;验证集用于调整参数;测试集用于测试,进行评估,只能使用一次,不能用来调整超参数,一旦发生了就不能改变了。
如果数据集很小的话,我们可以使用k折交叉验证
当数据很复杂,但是模型容量很低,容易出现欠拟合;当数据很简单,模型容量很高的话,容易出现过拟合
我们利用代码查看欠拟合,过拟合的现象
1、首先引入相关模块
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l
2、设置阶数、训练测试集的大小、初始化w
max_degree = 20 # 多项式最大阶数
n_train,n_test = 100,100 # 训练集,测试集大小
true_w = np.zeros(max_degree)
true_w[0:4]=np.array([5,1.2,-3.4,5.6])
3、生成相应的特征,记录不同阶数的feature
features = np.random.normal(size=(n_test+n_train,1)) # (200,1)
np.random.shuffle(features)
poly_features = np.power(features,np.arange(max_degree).reshape(1,-1)) # (200,20)
4、生成真实的labels
for i in range(max_degree):
poly_features[:,i] /= math.gamma(i+1)
labels = np.dot(poly_features,true_w) # poly_features(200,20) true_w (1,20)
labels += np.random.normal(scale=0.1,size=labels.shape)
5、将w,features,不同阶数的features,真实labels转化为tensor数据类型
true_w,features,poly_features,labels = [
torch.tensor(x,dtype=torch.float32) for x in [true_w,features,poly_features,labels]
]
6、查看相应维度
true_w.shape,features.shape,poly_features.shape,labels.shape)
'''
(torch.Size([20]),
torch.Size([200, 1]),
torch.Size([200, 20]),
torch.Size([200]))
'''
7、定义损失函数
def evaluate_loss(net,data_iter,loss):
metric = d2l.Accumulator(2)
for X,y in data_iter:
out = net(X)
y = y.reshape(out.shape)
l = loss(out,y)
metric.add(l.sum(),l.numel())
return metric[0]/metric[1]
8、训练模型
def train(train_features,test_features,train_labels,test_labels,num_epochs=400):
loss = nn.MSELoss()
input_shpae = train_features.shape[-1]
net = nn.Sequential(nn.Linear(input_shpae,1,bias=False))
batch_size = min(10,train_labels.shape[0])
train_iter = d2l.load_array((train_features,train_labels.reshape(-1,1)),batch_size)
test_iter = d2l.load_array((test_features,test_labels.reshape(-1,1)),batch_size,is_train=False)
trainer = torch.optim.SGD(net.parameters(),lr=0.01)
animator = d2l.Animator(xlabel='epoch',ylabel='loss',yscale='log',xlim=[1,num_epochs],ylim=[1e-3,1e2],legend=['train','test'])
for epoch in range(num_epochs):
d2l.train_epoch_ch3(net,train_iter,loss,trainer)
if epoch ==0 or (epoch+1) % 20 ==0:
animator.add(epoch+1,(evaluate_loss(net,train_iter,loss),evaluate_loss(net,test_iter,loss)))
print('weight:',net[0].weight.data.numpy())
9、拿前4列数据
train(poly_features[:n_train, :4], poly_features[n_train:, :4],
labels[:n_train], labels[n_train:])
最终的train和test的间距不大,说明没有发生很明显的过拟合
10、拿前两列来训练
train(poly_features[:n_train, :2], poly_features[n_train:, :2],
labels[:n_train], labels[n_train:])
数据都没有给全,训练不到位,欠拟合
11、取所有维度进行训练,包含16列的噪音
train(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:])
这是过拟合现象,之前学习的很不错,但是之后数据有了很多噪音,导致train和loss直接的间距变大。
权重衰退
权重衰退是广泛使用的正则化技术,就是处理过拟合的一种方式。
我们可以通过控制参数选择范围来控制参数容量,控制权重矩阵范数可以使得减少一些特征的权重,使模型简单,减轻过拟合。
使用均方范数(L2)作为硬性限制
不限制偏移b,小的Theta代表着有更强的正则项,我们一般不会直接使用这个函数。
使用均方范数L2作为柔性限制
其中损失函数后面加的叫做惩罚函数,或者惩罚项,一般我们成为正则项
添加正则项后,把权重往你所希望的地方靠近,损失L(w,b)变大,离原点越近,正则项越小。
lambda是一个超参数,控制了整个正则项的重要程度,我们可以通过增加lambda来控制模型的复杂度,让它不要太复杂。
至于正则化,这个听起来很拗口的概念,我们可以这样去理解它
正则化
降低模型参数>=正则化>=减小模型参数个数
我们将到原点的距离,也就是欧式距离,成为L2范数,具体公式如下:
L1范数也叫做曼哈顿距离
一个Lp范数,当p大于等于1的时候,图像才是凸的,我们可以进行凸优化,我们一般考虑L2范数
正则化只需要规定权重W即可,在下图中C是对W的约束,minmaxL是对原问题的等价问题,我们利用拉格朗日乘子来证明,下图的红线代表损失函数等高线,中间代表最小值对应的点,绿色的框代表可行域范围,只要确定了可行域范围,就能找到在约束条件下的最值点。
我们将这个函数变形,变成L2正则化表达式L(w,lambda) = J(w)+lambda*||w||2
这是我们常用的正则化表达式,比之前的少了参数C,那么这就是代表任意的半径范围,参数C的作用是决定我么绿色框圆的半径;而lambda的作用是调节两个梯度的大小,在下图中,红色梯度是损失函数的梯度,绿色梯度是约束条件的梯度,梯度相反。
于是 lambda = 损失函数梯度大小 / 约束条件梯度大小 ,从而求出适合的大小
演示对最优解的影响
其中绿线代表损失函数L的等高线;绿点代表损失函数L的最优点,当然也是最小点;橘黄色圆圈代表可行域范围,以原点为中心点等高线。
注:在这里我们的绿点就不是最优解了,因为它有橘黄色圆圈可行域的范围控制,对于橘黄色圆圈来说就比较大。
在这个图中,中间的绿点沿着蓝色箭头走,损失函数L的值会增大,但是正则项的值会减少,当走到w*处,橘黄色圆圈与损失函数L相切,这个点使得最优解向原点偏移,对应最优解的值会变得更小一些,模型的复杂度变低。
参数更新法则
计算梯度与之前一样,但是加入了正则项, 多出来了(1-学习率*lambda),这个值是小于1,大于0的一个数字,先把当前值缩小,再沿着负梯度方向学习。
归根结底,正则项可以更好的简化模型复杂度,防止过拟合现象的发生。
从零实现权重衰退的代码
1、引入相关模块
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
2、定义训练样本数,测试样本数,特征维度,批量大小
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 # 真实的w和b
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)
3、初始化模型参数
def init_params():
w = torch.normal(0,1,size=(num_inputs,1),requires_grad=True) # 均值为0,方差为1,(200,1)
b = torch.zeros(1,requires_grad=True)
return [w,b]
4、定义L2范数惩罚项
def L2_penalty(w):
return torch.sum(w.pow(2)) / 2
5、训练
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:
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()) # 计算范数
6、当lambda(lambd)参数为0时,忽略正则化项训练 训练误差在减少,测试误差不变
train(lambd=0) # 忽略正则化项训练 训练误差在减少,测试误差不变
7、将lambda(lambd)参数设为3,减轻了过拟合程度,范数还是大,可以通过调大lambda减小w
train(lambd=3) # 减轻了过拟合程度,范数还是大,可以通过调大lambda减小w
简洁实现权重衰退
我们可以通过pytorch提供的sgd接口中的weight decay 参数,写入正则项lambda
简洁实现过程为:建立网络、损失函数、优化器(根据反向传播求得梯度 用优化器更新参数)、从训练集取出数据,进行训练,梯度清0,算损失,反向传播,优化。
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs,1))
for param in net.parameters():
param.data.normal_() # net中的所有参数进行正态分布初始化
loss = nn.MSELoss()
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.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())
"weight_decay": wd 是正则化项的参数,通常用来避免过拟合,减少模型的复杂度。
当wd也就是lambda参数为0时
train_concise(3)
wd也就是lambda参数为3时
train_concise(3)
我们发现与我们从零开始实现权重衰退时的图几乎一样。
个人总结
这周跟着李沐老师学习了感知机,多层感知机,了解了过拟合,欠拟合,并且知道了如何通过正则项来防止过拟合,下周将继续学习,并且阅读相应的文献,理论与实践相结合。