• 从头学pytorch(七):dropout防止过拟合


    上一篇讲了防止过拟合的一种方式,权重衰减,也即在loss上加上一部分(frac{lambda}{2n} |oldsymbol{w}|^2),从而使得w不至于过大,即不过分偏向某个特征.

    这一篇介绍另一种防止过拟合的方法,dropout,即丢弃某些神经元的输出.由于每次训练的过程里,丢弃掉哪些神经元的输出都是随机的,从而可以使得模型不过分依赖于某些神经元的输出,从而达到防止过拟合的目的.

    需要注意的一点是:并不是简单地丢弃掉某些神经元的输出,对留下的输出,我们要改变他们的值,以保证丢弃前后的输出的期望不变

    比如,对如下神经网络:输入个数为4,隐藏单元个数为5,且隐藏单元(h_i)(i=1, ldots, 5))的计算表达式为

    [h_i = phileft(x_1 w_{1i} + x_2 w_{2i} + x_3 w_{3i} + x_4 w_{4i} + b_i ight) ]

    这里(phi)是激活函数,(x_1, ldots, x_4)是输入,隐藏单元(i)的权重参数为(w_{1i}, ldots, w_{4i}),偏差参数为(b_i)。当对该隐藏层使用丢弃法时,该层的隐藏单元将有一定概率被丢弃掉。设丢弃概率为(p),那么有(p)的概率(h_i)会被清零,有(1-p)的概率(h_i)会除以(1-p)做拉伸。丢弃概率是丢弃法的超参数。具体来说,设随机变量(xi_i)为0和1的概率分别为(p)(1-p)。使用丢弃法时我们计算新的隐藏单元(h_i')

    [h_i' = frac{xi_i}{1-p} h_i ]

    由于(E(xi_i) = 1-p),因此

    [E(h_i') = frac{E(xi_i)}{1-p}h_i = h_i ]

    从零开始实现

    以一个三层的多层感知机为例.两个隐藏层的输出个数都是256.

    导入必要的包

    import torch
    import torch.nn as nn
    import numpy as np
    import sys
    import utils
    

    utils.py中包含了常用的一些实现,比如数据集加载,sgd实现之类的函数都实现在了这个文件里.

    实现dropout

    def dropout(X,drop_prob):
        X = X.float()
        keep_prob = 1 - drop_prob
    
        if keep_prob == 0:
            return torch.zeros_like(X)
        mask = (torch.rand(X.shape) < keep_prob).float()
        #print("mask:
    ",mask)
    
        return mask * X/keep_prob
    
    X=torch.arange(16).view(2,8)
    

    drop_prob是丢弃的概率.其中我们用torch.rand(X.shape) < keep_prob去生成一组bool数,表明要丢弃哪些,保留哪些.

    关于torch中几种分布的用法,参考https://zhuanlan.zhihu.com/p/31231210

    • 均匀分布
      torch.rand(*sizes, out=None) → Tensor
      返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数。张量的形状由参数sizes定义。

    • 标准正态分布
      torch.randn(*sizes, out=None) → Tensor
      返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。张量的形状由参数sizes定义。

    • 离散正态分布
      torch.normal(means, std, out=None) → → Tensor
      返回一个张量,包含了从指定均值means和标准差std的离散正态分布中抽取的一组随机数。
      标准差std是一个张量,包含每个输出元素相关的正态分布标准差。

    • 线性间距向量
      torch.linspace(start, end, steps=100, out=None) → Tensor
      返回一个1维张量,包含在区间start和end上均匀间隔的step个点。
      输出张量的长度由steps决定。

    加载数据

    batch_size,num_workers = 256,4
    train_iter,test_iter = utils.load_data(batch_size,num_workers)
    

    utils.py中

    def load_data(batch_size,num_workers):
        mnist_train = torchvision.datasets.FashionMNIST(root='/home/sc/disk/keepgoing/learn_pytorch/Datasets/FashionMNIST',
                                                        train=True, download=True,
                                                        transform=transforms.ToTensor())
        mnist_test = torchvision.datasets.FashionMNIST(root='/home/sc/disk/keepgoing/learn_pytorch/Datasets/FashionMNIST',
                                                    train=False, download=True,
                                                    transform=transforms.ToTensor())
    
        train_iter = torch.utils.data.DataLoader(
            mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
        test_iter = torch.utils.data.DataLoader(
            mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
        
        return train_iter,test_iter
    

    定义模型参数

    num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
    
    W1 = torch.tensor(np.random.normal(0, 0.01, size=(num_inputs, num_hiddens1)), dtype=torch.float, requires_grad=True)
    b1 = torch.zeros(num_hiddens1, requires_grad=True)
    W2 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens1, num_hiddens2)), dtype=torch.float, requires_grad=True)
    b2 = torch.zeros(num_hiddens2, requires_grad=True)
    W3 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens2, num_outputs)), dtype=torch.float, requires_grad=True)
    b3 = torch.zeros(num_outputs, requires_grad=True)
    
    params = [W1, b1, W2, b2, W3, b3]
    

    模型定义

    drop_prob1, drop_prob2 = 0.2, 0.5
    def net(X, is_training=True):
        X = X.view(-1, num_inputs)
        H1 = (torch.matmul(X, W1) + b1).relu()
        if is_training:  # 只在训练模型时使用丢弃法
            H1 = dropout(H1, drop_prob1)  # 在第一层全连接后添加丢弃层
        H2 = (torch.matmul(H1, W2) + b2).relu()
        if is_training:
            H2 = dropout(H2, drop_prob2)  # 在第二层全连接后添加丢弃层
        return torch.matmul(H2, W3) + b3
    

    通常,倾向于在更靠近原始输入的层,drop概率更低.

    精度评估函数定义

    def evaluate_accuracy(data_iter, net):
        acc_sum, n = 0.0, 0
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                net.eval() # 评估模式, 这会关闭dropout
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
                net.train() # 改回训练模式
            else: # 自定义的模型
                if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                    # 将is_training设置成False
                    acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                else:
                    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
            n += y.shape[0]
        return acc_sum / n
    

    损失函数

    loss = nn.CrossEntropyLoss()
    

    优化算法

    def sgd(params, lr, batch_size):
        for param in params:
            param.data -= lr * param.grad / batch_size # 注意这里更改param时用的param.data
    

    训练

    注意一点:这里的loss我们是直接用的torch的nn.CrossEntropyLoss(),这个loss已经是除了batch_size,是求了均值的.所以l很小,相应的梯度也很小.
    而更新参数的时候用的是手写的sgd,又除了一次batch_size.所以为了快速收敛,lr要设置大一点.

    def train(train_iter,test_iter,num_epochs,net,loss,params,sgd,batch_size,lr):
        for epoch in range(num_epochs):
            train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
            for X,y in train_iter:
                y_hat=net(X)   #前向传播
                """
                这里的loss我们是直接用的torch的nn.CrossEntropyLoss(),这个loss是求了均值的.l很小,相应的梯度也很小.
                更新参数的时候用的是手写的sgd. lr要设置大一点.
                """
                l = loss(y_hat,y).sum()#计算loss
                
                #清空梯度
                if params is not None and params[0].grad is not None:
                    for param in params:
                        param.grad.data.zero_()
    
                l.backward()#反向传播
                sgd(params, lr, batch_size) #更新参数
    
                train_l_sum += l.item()
                train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
                n += y.shape[0]
            test_acc = evaluate_accuracy(test_iter, net)
            print('epoch %d, loss %.4f, train_acc %.3f,test_acc %.3f'
                  % (epoch + 1, train_l_sum / n, train_acc_sum/n, test_acc))
    
    num_epochs,lr=5,100
    train(train_iter,test_iter,num_epochs,net,loss,params,sgd,batch_size,lr)
    

    输出如下:

    epoch 1, loss 0.0045, train_acc 0.558,test_acc 0.736
    epoch 2, loss 0.0023, train_acc 0.786,test_acc 0.792
    epoch 3, loss 0.0019, train_acc 0.825,test_acc 0.796
    epoch 4, loss 0.0017, train_acc 0.839,test_acc 0.845
    epoch 5, loss 0.0016, train_acc 0.850,test_acc 0.823
    

    简洁实现

    数据加载

    同前

    模型定义及模型参数初始化

    net = nn.Sequential(
        utils.FlattenLayer(),
        nn.Linear(num_inputs,num_hiddens1),
        nn.ReLU(),
        nn.Dropout(drop_prob1),
        nn.Linear(num_hiddens1,num_hiddens2),
        nn.ReLU(),
        nn.Dropout(drop_prob2),
        nn.Linear(num_hiddens2,10)
    )
    

    直接使用nn.Dropout()类.

    for param in net.parameters():
        nn.init.normal_(param,mean=0,std=0.01)
    

    精度评估函数定义

    同前. 要注意的是在测试模式下,我们不想做dropout.要net.eval()关闭dropout.

    def evaluate_accuracy(data_iter, net):
        acc_sum, n = 0.0, 0
        for X, y in data_iter:
            if isinstance(net, torch.nn.Module):
                net.eval() # 评估模式, 这会关闭dropout
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
                net.train() # 改回训练模式
            else: # 自定义的模型
                if('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
                    # 将is_training设置成False
                    acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
                else:
                    acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
            n += y.shape[0]
        return acc_sum / n
    

    损失函数

    loss = nn.CrossEntropyLoss()
    

    优化算法

    optimizer = torch.optim.SGD(net.parameters(),lr=0.5)
    

    用torch.optim.SGD(net.parameters(),lr=0.5)表明用随机梯度下降法去计算net.parameters()的梯度.

    训练

    • 前向传播,计算输出
    • 计算loss
    • 反向传播,计算梯度
    • 根据梯度,更新参数
    • 循环往复,输入下一个batch的数据
    def train_use_torch(train_iter,test_iter,num_epochs,net,loss,params,optimizer,batch_size,lr):
        for epoch in range(num_epochs):
            train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
            for X,y in train_iter:
                y_hat = net(X)  #前向传播,计算输出
                l = loss(y_hat,y).sum()#计算loss
                optimizer.zero_grad()#梯度清空
    
                l.backward()#反向传播,计算梯度
    
                optimizer.step()#根据梯度,更新参数
                
                train_l_sum += l.item()
                train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
                n += y.shape[0]
            
            test_acc = evaluate_accuracy(test_iter, net)
            print('epoch %d, loss %.4f, train_acc %.3f,test_acc %.3f'
                  % (epoch + 1, train_l_sum / n, train_acc_sum/n, test_acc))
    
    num_epochs,lr=5,0.5
    train_use_torch(train_iter,test_iter,num_epochs,net,loss,params,optimizer,batch_size,lr)
    

    输出

    epoch 1, loss 0.0045, train_acc 0.556,test_acc 0.745
    epoch 2, loss 0.0023, train_acc 0.787,test_acc 0.817
    epoch 3, loss 0.0019, train_acc 0.821,test_acc 0.803
    epoch 4, loss 0.0017, train_acc 0.837,test_acc 0.809
    epoch 5, loss 0.0016, train_acc 0.847,test_acc 0.840
    
  • 相关阅读:
    设计模式-代理模式
    设计模式-策略模式
    设计模式-单例模式
    优先队列
    n!中质因子个数
    计算组合数
    高精度
    memset用法
    质因子分解
    素数筛法
  • 原文地址:https://www.cnblogs.com/sdu20112013/p/12124742.html
Copyright © 2020-2023  润新知