【深度之眼cs231n第七期】笔记(十八)

目录

pytorch.ipynb

导入相关的包

import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torch.utils.data import sampler

import torchvision.datasets as dset
import torchvision.transforms as T
import numpy as np
import timeit

加载CIFAR-10数据

class ChunkSampler(sampler.Sampler):
    """从start下标开始,采样num_samples个数据样本"""
    def __init__(self, num_samples, start = 0):
        self.num_samples = num_samples
        self.start = start
    
    def __iter__(self):
        return iter(range(self.start, self.start + self.num_samples))
    
    def __len__(self):
        return self.num_samples

NUM_TRAIN = 49000
NUM_VAL = 1000
# 下载训练数据到'./cs231n/datasets'
cifar10_train = dset.CIFAR10('./cs231n/datasets', train=True, download=True,
                           transform=T.ToTensor())
# 从下标0开始,采样49000个数据作为训练集
loader_train = DataLoader(cifar10_train, batch_size=64, sampler=ChunkSampler(NUM_TRAIN, 0))
cifar10_val = dset.CIFAR10('./cs231n/datasets', train=True, download=True,
                           transform=T.ToTensor())
loader_val = DataLoader(cifar10_val, batch_size=64, sampler=ChunkSampler(NUM_VAL, NUM_TRAIN))
cifar10_test = dset.CIFAR10('./cs231n/datasets', train=False, download=True,
                          transform=T.ToTensor())
loader_test = DataLoader(cifar10_test, batch_size=64)

一些有用的参数和函数

dtype = torch.FloatTensor # CPU类型
# 每使用一百个小批量数据后(也就是使用100*64个数据后),打印一次loss
print_every = 100

# m是模型,如果模型里有reset_parameters()这个函数,则重新初始化模型的参数
def reset(m):
    if hasattr(m, 'reset_parameters'):
        m.reset_parameters()

# 把一张图片拉伸为向量,在卷积层和全连接层中间使用
class Flatten(nn.Module):
    def forward(self, x):
        N, C, H, W = x.size()
        # 把C*H*W的数据拉伸为向量,view()操作相对于np.reshape()
        return x.view(N, -1)

定义一个简单的模型

simple_model = nn.Sequential(
		# 卷积层,输入通道为3,输出通道为32,卷积核大小为7,步长为2
		# (3,32,32)->(32,13,13),其中13=1+(32-7)//2
                nn.Conv2d(3, 32, kernel_size=7, stride=2),
                nn.ReLU(inplace=True),
                Flatten(), # 拉伸为向量
                # 5408=32*13*13,10是类别个数
                nn.Linear(5408, 10), # 全连接层
              )

# 设置模型的数据类型为CPU的数据类型
simple_model.type(dtype)
# 交叉熵损失函数
loss_fn = nn.CrossEntropyLoss().type(dtype)
# Adam优化器
optimizer = optim.Adam(simple_model.parameters(), lr=1e-2)

仿照上面的模型写一个模型,要求如下:

  • 卷积层,32个7x7的卷积核,步长为1
  • 激活层,ReLU
  • 空间批量归一化层,在pytorch中的函数为BatchNorm2d
  • 最大池化层,卷积核大小是2x2,步长为2
  • 全连接层,输出1024个神经元
  • 激活层,ReLU
  • 全连接层,1024个输入,10个输出
  • 使用交叉熵损失函数和RMSprop优化器
fixed_model_base = nn.Sequential(
                    # (3,32,32)->(32,26,26)
                    nn.Conv2d(3,32,kernel_size=7),
                    nn.ReLU(inplace=True),
                    # 32是卷积层的输出通道数
                    nn.BatchNorm2d(32),
                    # (32,26,26)->(32,13,13)
                    nn.MaxPool2d(kernel_size=2,stride=2),
                    Flatten(),
                    # 5408=32*13*13
                    nn.Linear(5408,1024),
                    nn.ReLU(inplace=True),
                    nn.Linear(1024,10)
            )

fixed_model = fixed_model_base.type(dtype)
fixed_loss = nn.CrossEntropyLoss().type(dtype)
fixed_optimizer = optim.RMSprop(fixed_model_base.parameters())

测试输出维度是否正确

# 随机生成输入
x = torch.randn(64, 3, 32, 32).type(dtype)
x_var = Variable(x.type(dtype))
ans = fixed_model(x_var)        # 把数据输入模型,得到输出
np.array_equal(np.array(ans.size()), np.array([64, 10]))  
# True

检查GPU是否可用,可用才能运行后面的代码

torch.cuda.is_available()
# True

使用GPU来得到输出,并检验输出维度

import copy
gpu_dtype = torch.cuda.FloatTensor
fixed_model_gpu = copy.deepcopy(fixed_model_base).type(gpu_dtype)

x_gpu = torch.randn(64, 3, 32, 32).type(gpu_dtype)
x_var_gpu = Variable(x.type(gpu_dtype))
ans = fixed_model_gpu(x_var_gpu)
np.array_equal(np.array(ans.size()), np.array([64, 10]))
# True

在CPU上前向传播所花的时间

%%timeit 
ans = fixed_model(x_var)
# 37.7 ms ± 2.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

在GPU上前向传播所花的时间,可以看到明显比CPU上所花的时间少

%%timeit 
torch.cuda.synchronize() # 确保没有未完成的GPU操作
ans = fixed_model_gpu(x_var_gpu)
torch.cuda.synchronize() # 确保没有未完成的GPU操作
# 3.11 ms ± 12.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

在GPU上训练模型

loss_fn = nn.CrossEntropyLoss().type(gpu_dtype)
optimizer = optim.RMSprop(fixed_model_gpu.parameters(),lr=1e-3)

# 设置模型的模式为"training",有些模型在训练模式和测试模式下使用不同的操作,比如Dropout和BatchNorm
fixed_model_gpu.train()
# 每次加载一个小批量数据(64个)
for t, (x, y) in enumerate(loader_train):
    x_var = Variable(x.type(gpu_dtype))
    y_var = Variable(y.type(gpu_dtype).long())
    # 前向传播,计算得分
    scores = fixed_model_gpu(x_var)    
    # 使用损失函数计算loss
    loss = loss_fn(scores, y_var)
    # 总共49000个数据,一批64个,总共765.625批,每100批打印一次loss,所以会打印7次
    if (t + 1) % print_every == 0:
        print('t = %d, loss = %.4f' % (t + 1, loss.item()))
    
    # 清零旧的梯度
    optimizer.zero_grad()
    # 计算梯度
    loss.backward()    
    # 更新梯度
    optimizer.step()

把训练过程和计算准确率分别写成函数

# 和上一小节类似
def train(model, loss_fn, optimizer, num_epochs = 1):
    for epoch in range(num_epochs):
        print('Starting epoch %d / %d' % (epoch + 1, num_epochs))
        model.train()
        for t, (x, y) in enumerate(loader_train):
            x_var = Variable(x.type(gpu_dtype))
            y_var = Variable(y.type(gpu_dtype).long())
            scores = model(x_var)            
            loss = loss_fn(scores, y_var)
            if (t + 1) % print_every == 0:
                print('t = %d, loss = %.4f' % (t + 1, loss.item()))
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

# 计算准确率
def check_accuracy(model, loader):
    # 如果是训练模式,则计算验证数据集的准确率
    if loader.dataset.train:
        print('Checking accuracy on validation set')
    # 否则,计算测试数据集的准确率
    else:
        print('Checking accuracy on test set')   
    
    num_correct = 0
    num_samples = 0
    model.eval() # 模型设置为测试模式,Dropout、BatchNorm等会使用保存的数据预测结果
    # 每次取出一批数据(64个)
    for x, y in loader:
        # 不需要计算梯度
        with torch.no_grad():
            x_var = Variable(x.type(gpu_dtype))
        
        # 计算得分,进行预测
        scores = model(x_var)
        _, preds = scores.data.cpu().max(1)
        num_correct += (preds == y).sum()
        num_samples += preds.size(0)
    
    acc = float(num_correct) / num_samples
    print('Got %d / %d correct (%.2f)' % (num_correct, num_samples, 100 * acc))

在GPU上训练模型,得到的loss应该在1.2-1.4左右,验证集的准确率应该在50-60%.左右

torch.cuda.random.manual_seed(12345)
# 重新初始化参数
fixed_model_gpu.apply(reset)
train(fixed_model_gpu, loss_fn, optimizer, num_epochs=1)
check_accuracy(fixed_model_gpu, loader_val)

现在尝试不同的网络结构、超参数、损失函数、优化器来训练一个模型,使其在CIFAR-10验证集上达到>=70%的准确率。

一些考虑的方向:

  • 卷积核大小:上面用了7x7的卷积核,但是更小的卷积核可能更有效(三个3x3的卷积核和一个7x7的卷积核有相同的感受视野,但是前者的参数是后者的27/49)。
  • 卷积核的个数:上面用了32个卷积核,卷积核应该多一些还是少一些呢?
  • 下采样的方法:使用池化还是用步长较大的卷积来实现下采样呢?
  • 批量归一化:尝试在卷积层后加上空间批量归一化,在全连接层后加上批量归一化,这会不会训练的速度更快?
  • 网络结构:上面的网络比较浅,是不是深度神经网络会好一些? 一些可以尝试的结构:
    • [conv-relu-pool]xN -> [affine]xM -> [softmax or SVM]
    • [conv-relu-conv-relu-pool]xN -> [affine]xM -> [softmax or SVM]
    • [batchnorm-relu-conv]xN -> [affine]xM -> [softmax or SVM]
  • 全局平均池化:构造模型,使卷积层的输出为(C,H,W),其中C是类别个数,H、W应该比较小(比如7)。
    然后使用一个平均池化,池化核大小为(H,W),得到(C,1,1)就是得分。参考 Google’s Inception Network 里的Table1。
  • 正则化:添加L2正则化或者Dropout。

训练技巧
对于每个网络结构,都应该微调学习率和正则化参数,这时候应该注意一下几点:

  • 如果一组参数比较好,那么它们在几百次迭代以内就会得到不错的结果
  • 记住粗调-微调方法:先在一个很大的范围内寻找超参数,每组超参数只迭代较少的次数。一旦发现某组超参数结果不错,再在附近搜索超参数,并加大训练周期
  • 使用验证集来寻找超参数,测试集只能在最后使用一次

更多的方法
喜欢冒险的话,可以尝试一下结构:

  • 更新的策略:在之前的作业中用到过SGD+momentum、RMSprop、Adam等,可以尝试AdaGrad或者AdaDelta
  • 激活函数:可以尝试使用leaky ReLU、参数ReLU、 ELU、MaxOut等
  • 集成模型
  • 数据增强
  • 一些新的网络结构(2017年时比较新,现在已经不新了)
    • ResNets:输入直接加到输出
    • DenseNets :将前一层的输入连接在一起

仿照MobileNetv1写了一个网络
在这里插入图片描述
左边是正常的卷积,右边是深度可分离卷积,也就是MobileNet用到的卷积。
在这里插入图片描述
网络结构如下:
注意:在输出的loss中,每个周期的前100批总是比前一个周期少很多,这应该是学习率减少造成的

model_2 = nn.Sequential(
                # (3,32,32)->(32,30,30)
                nn.Conv2d(3, 32, kernel_size=3),
                nn.BatchNorm2d(32),
                nn.ReLU(inplace=True),                
                
                # (32,30,30)->(32,28,28)
                nn.Conv2d(32,32, kernel_size=3, groups=32),
                nn.BatchNorm2d(32),
                nn.ReLU(inplace=True),
                # (32,28,28)->(64,28,28)
                nn.Conv2d(32, 64, kernel_size=1),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),                
                
                # (64,28,28)->(64,28,28)
                nn.Conv2d(64, 64, kernel_size=3,padding=1,groups=64),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),
                # (64,28,28)->(64,28,28)
                nn.Conv2d(64, 64, kernel_size=1),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),
                # (64,28,28)->(64,14,14)
                nn.Conv2d(64, 64, kernel_size=3,stride=2,padding=1,groups=64),
                nn.BatchNorm2d(64),
                nn.ReLU(inplace=True),
                # (64,14,14)->(128,14,14)
                nn.Conv2d(64, 128, kernel_size=1),
                nn.BatchNorm2d(128),
                nn.ReLU(inplace=True),                
                
                # (128,14,14)->(128,14,14)
                nn.Conv2d(128, 128, kernel_size=3,padding=1,groups=128),
                nn.BatchNorm2d(128),
                nn.ReLU(inplace=True),
                # (128,14,14)->(128,14,14)
                nn.Conv2d(128, 128, kernel_size=1),
                nn.BatchNorm2d(128),
                nn.ReLU(inplace=True),
                # (128,14,14)->(128,7,7)
                nn.Conv2d(128, 128, kernel_size=3,stride=2,padding=1,groups=128),
                nn.BatchNorm2d(128),
                nn.ReLU(inplace=True),
                # (128,7,7)->(256,7,7)
                nn.Conv2d(128, 256, kernel_size=1),
                nn.BatchNorm2d(256),
                nn.ReLU(inplace=True),                
                
                # (256,7,7)->(256,7,7)
                nn.Conv2d(256, 256, kernel_size=3,padding=1,groups=256),
                nn.BatchNorm2d(256),
                nn.ReLU(inplace=True),
                # (256,7,7)->(256,7,7)
                nn.Conv2d(256, 256, kernel_size=1),
                nn.BatchNorm2d(256),
                nn.ReLU(inplace=True),
                # Global Average Pooling
                nn.AvgPool2d(kernel_size=7),
                Flatten(),
                nn.Linear(256,10)
              )

model_2.type(gpu_dtype)
loss_fn_2 = nn.CrossEntropyLoss().type(gpu_dtype)
optimizer_2 = optim.Adam(model_2.parameters(), lr=1e-3)
train(model_2, loss_fn_2, optimizer_2, num_epochs=4)
check_accuracy(model_2, loader_val)
# Checking accuracy on validation set
# Got 740 / 1000 correct (74.00)

model只是把model_2中的深度可分离卷积换回正常卷积,所以比较慢

%%timeit 
torch.cuda.synchronize()
ans = model(x_var_gpu)
torch.cuda.synchronize()
# 21.3 ms ± 200 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

model_2使用深度可分离卷积,比较快,但是准确率比较低

%%timeit 
torch.cuda.synchronize()
ans = model_2(x_var_gpu)
torch.cuda.synchronize()
# 15.4 ms ± 78.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

最后的准确率(事实上model在验证集上的准确率比较高,但是本来就是想写一下MobileNetv1,所以直接用这个作为最后模型了)

best_model = model_2
check_accuracy(best_model, loader_test)
# Checking accuracy on test set
# Got 7333 / 10000 correct (73.33)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值