机器学习的入门笔记(第八周)

本周观看了李沐老师的《动手学深度学习》,李沐老师很注重代码的编写,但是对概念的讲解并不是特别多,所以还要去结合一些其他的资料去进行了解,下面是本周的所看的课程总结。

目录

感知机

多层感知机

多层感知机的从零开始实现

多层感知机的简洁实现

多层感知机pycharm实现,利用tensorboard

模型选择,过拟合,欠拟合

权重衰退

正则化

演示对最优解的影响

参数更新法则

从零实现权重衰退的代码

简洁实现权重衰退

个人总结


感知机

感知机是给定输入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)

我们发现与我们从零开始实现权重衰退时的图几乎一样。

个人总结

这周跟着李沐老师学习了感知机,多层感知机,了解了过拟合,欠拟合,并且知道了如何通过正则项来防止过拟合,下周将继续学习,并且阅读相应的文献,理论与实践相结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值