pytorch实战(一)简单卷积网络CIFAR10图片分类

之前已经学习了:

如何利用pytorch设计简单的神经网络

如何制作数据集

何如使用设计的网络进行训练

接下来利用一个简单的

1.CIFAR10数据集介绍

官方网站:http://www.cs.toronto.edu/~kriz/cifar.html

10个类别,每个类别6000张,一共60000张图片,50000张用来训练,10000张用来测试

pytorch实战(一)简单卷积网络CIFAR10图片分类_第1张图片

这里可以查看各种方法在该数据集上的表现:https://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html 

准确率75%-96%

2.网络结构

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        #输入通道3,输出通道64,卷积核3X3,其他参数可默认
        #输入32X32,输出30X30
        self.conv1 = nn.Conv2d(3, 64, 3)
        #最大池化卷积核大小2X2,步长为2
        #输入30X30,输出15X15
        self.pool1 = nn.MaxPool2d(2, 2)

        #输入通道64,输出通道128,卷积核3X3,其他参数可默认
        #输入15X15,输出13X13
        self.conv2 = nn.Conv2d(64, 128, 3)
        #最大池化卷积核大小3X3,步长为2
        #输入13X13,输出6X6
        self.pool2 = nn.MaxPool2d(3, 2)

        #输入通道128,输出通道256,卷积核3X3,其他参数可默认
        #输入6X6,输出4X4
        self.conv3 = nn.Conv2d(128, 256, 3)
        #输入通道256,输出通道64,卷积核1X1,其他参数可默认,1X1卷积缩减通道数
        #输入4X4,输出4X4
        self.conv4 = nn.Conv2d(256, 64, 1)

        #全连接层输入单元数1024,输出384
        self.fc1 = nn.Linear(1024, 384)
        #全连接层输入单元数384,输出192
        self.fc2 = nn.Linear(384, 192)
        #全连接层输入单元数192,输出10
        self.fc3 = nn.Linear(192, 10)

    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))      
        x = x.view(x.shape[0], -1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.softmax(self.fc3(x))
        return x
  1. 卷积层1:卷积核大小3*3,卷积核移动步长1,卷积核个数64,池化大小2*2,池化步长2,池化类型为最大池化,激活函数ReLU;
  2. 卷积层2:卷积核大小3*3,卷积核移动步长1,卷积核个数128,池化大小2*2,池化步长2,池化类型为最大池化,激活函数ReLU;
  3. 卷积层3:卷积核大小3*3,卷积核移动步长1,卷积核个数256,激活函数ReLU;
  4. 卷积层4:卷积核大小1*1,卷积核移动步长1,卷积核个数64,激活函数ReLU;
  5. 全连接层:隐藏层单元数384,激活函数ReLU;
  6. 全连接层:隐藏层单元数192,激活函数ReLU;
  7. 全连接层:隐藏层单元数10,激活函数softmax。

注:

1.因为CIFAR10数据集图片分辨率低32X32,所以卷积核选得小3X3;

2.CONV3时图片尺寸已经减小到4X4了,再做maxpool会丢失很多信息;

3.CONV3后通道数为256,使用1X1卷积缩减通道至64,保证FC1的输入为1024。

3.初步实验结果

我们这里没有划分训练集、验证集、测试集,因为torch.utils.data.random_split(dataset, lengths)在pytorch0.4.1以上才可以使用,我们的是0.4.0,并且python版本2.7,CUDA版本8.0,没有用conda,环境先不能随便改,先凑合着用吧。

pytorch实战(一)简单卷积网络CIFAR10图片分类_第2张图片

1)50个epoch下来,训练集准确率89%,测试集准确率75%,loss从2.3降低到1.57

之前尝试过训练200个epoch,到100以后网络就发散了

pytorch实战(一)简单卷积网络CIFAR10图片分类_第3张图片

可能是没有使用learning rate decay的原因,尝试使用

2)按照一定步长衰减

scheduler = lr_scheduler.StepLR(optimizer, step_size=25, gamma=0.1)
for e in range(200):
    net.train()
    scheduler.step()

 按照区间衰减

scheduler = lr_scheduler.MultiStepLR(optimizer, [30, 80], 0.1)

参考链接:https://www.jianshu.com/p/a20d5a7ed6f3

 pytorch实战(一)简单卷积网络CIFAR10图片分类_第4张图片

有所好转,但是后边学习率太低,基本在65个epoch后准确率不再提升,停留在93%,Loss停留在1.52,为什么别人的网络直接训练准确率就可以达到接近1?!

3)尝试换一种优化方式

这里可以看NG的课:改善深层神经网络:超参数调试、正则化以及优化

讲了各种优化算法的特点,adam结合了动量法和RMSprop,通过计算一阶矩、二阶矩更新权重,是目前应用的比较广泛的算法

改用adam优化方法,并进行learning rate decay,adam快得多,基本上5个epoch后0.001的学习率就用不了了(学习率太大导致开始发散了),和参考博客中所说的一直用0.001就能训练到99%准确率相去甚远。

optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
scheduler = lr_scheduler.MultiStepLR(optimizer, [5,80,100], 0.5)#[50,80,90,100,150]

 pytorch实战(一)简单卷积网络CIFAR10图片分类_第5张图片

200个epoch后准确率达到了92%,loss达到了1.53,已经提升不上来了 

4)尝试自适应学习率调整

参考链接:

https://blog.csdn.net/chanbo8205/article/details/89283226

https://pytorch.org/docs/stable/optim.html?highlight=torch%20optim%20lr_scheduler%20reducelronplateau#torch.optim.lr_scheduler.ReduceLROnPlateau

torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, 
verbose=False, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
 
'''
mode(str)- 模式选择,有 min 和 max 两种模式, min 表示当指标不再降低(如监测loss), max 表示当指标不再升高(如监测 accuracy)。
factor(float)- 学习率调整倍数(等同于其它方法的 gamma),即学习率更新为 lr = lr * factor
patience(int)- 忍受该指标多少个 step 不变化,当忍无可忍时,调整学习率。
verbose(bool)- 是否打印学习率信息, print(‘Epoch {:5d}: reducing learning rate of group {} to {:.4e}.’.format(epoch, i, new_lr))
threshold_mode(str)- 选择判断指标是否达最优的模式,有两种模式, rel 和 abs。
当 threshold_mode == rel,并且 mode == max 时, dynamic_threshold = best * ( 1 +threshold );
当 threshold_mode == rel,并且 mode == min 时, dynamic_threshold = best * ( 1 -threshold );
当 threshold_mode == abs,并且 mode== max 时, dynamic_threshold = best + threshold ;
当 threshold_mode == abs,并且 mode == min 时, dynamic_threshold = best - threshold;
threshold(float)- 配合 threshold_mode 使用。
cooldown(int)- “冷却时间“,当调整学习率之后,让学习率调整策略冷静一下,让模型再训练一段时间,再重启监测模式。
min_lr(float or list)- 学习率下限,可为 float,或者 list,当有多个参数组时,可用 list 进行设置。
eps(float)- 学习率衰减的最小值,当学习率变化小于 eps 时,则不调整学习率。
'''
>>> optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
>>> scheduler = ReduceLROnPlateau(optimizer, 'min')
>>> for epoch in range(10):
>>>     train(...)
>>>     val_loss = validate(...)
>>>     # Note that step should be called after validate()
>>>     scheduler.step(val_loss)

A)使用"abs"模式 

5个epoch提升不到0.1%就降低学习率

optimizer = torch.optim.Adam(net.parameters(), lr=0.0005)
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=5,
verbose=True, threshold=0.001, threshold_mode='abs', cooldown=0, min_lr=0, eps=1e-08)

由之前实验可知,前5个epoch时学习率设置为0.001是可以的,但是这个步长对于这种自适应算法来说还是太大,来不及反应(达到patience=5 之前就已经发散,学习率减少,走向极值点更加缓慢),所以初始学习率设置为0.0005,虽然开始时可能要多走几步,但是整体效果会变好。

#可以看出自适应衰减学习率效果拔群 
...
epoch :48, Train Loss:1.621545, Train ACC:0.839394
epoch :49, Train Loss:1.625893, Train ACC:0.834579
epoch :50, Train Loss:1.622842, Train ACC:0.837796
epoch :51, Train Loss:1.624915, Train ACC:0.835818
Epoch    51: reducing learning rate of group 0 to 2.5000e-04.
epoch :52, Train Loss:1.625912, Train ACC:0.834799
epoch :53, Train Loss:1.597294, Train ACC:0.863551
epoch :54, Train Loss:1.589617, Train ACC:0.871204
...

因为训练后期准确率提高得很缓慢,所以patience设置得过小会导致训练后期学习率衰减过快,如下图所示,准确率已经停留在94.8%了

...
epoch :177, Train Loss:1.512846, Train ACC:0.948110
Epoch   177: reducing learning rate of group 0 to 9.7656e-07.
epoch :178, Train Loss:1.512963, Train ACC:0.947990
epoch :179, Train Loss:1.512961, Train ACC:0.947990
epoch :180, Train Loss:1.512831, Train ACC:0.948110
epoch :181, Train Loss:1.512830, Train ACC:0.948110
epoch :182, Train Loss:1.512829, Train ACC:0.948110
epoch :183, Train Loss:1.512830, Train ACC:0.948110
Epoch   183: reducing learning rate of group 0 to 4.8828e-07.
epoch :184, Train Loss:1.512888, Train ACC:0.948050
epoch :185, Train Loss:1.513065, Train ACC:0.947870
epoch :186, Train Loss:1.512881, Train ACC:0.948050
epoch :187, Train Loss:1.512880, Train ACC:0.948050
epoch :188, Train Loss:1.512820, Train ACC:0.948110
epoch :189, Train Loss:1.512818, Train ACC:0.948110
Epoch   189: reducing learning rate of group 0 to 2.4414e-07.
epoch :190, Train Loss:1.512877, Train ACC:0.948050
epoch :191, Train Loss:1.512876, Train ACC:0.948050
...

pytorch实战(一)简单卷积网络CIFAR10图片分类_第6张图片

B)使用"rel"模式 

20个epoch提升不到100.01%就降低学习率,希望训练后期学习率不要衰减得太快

optimizer = torch.optim.Adam(net.parameters(), lr=0.0005)
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=20,
verbose=True, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
epoch :494, Train Loss:1.507320, Train ACC:0.953804
epoch :495, Train Loss:1.507399, Train ACC:0.953744
epoch :496, Train Loss:1.507283, Train ACC:0.953864
epoch :497, Train Loss:1.507480, Train ACC:0.953645
epoch :498, Train Loss:1.507357, Train ACC:0.953784
epoch :499, Train Loss:1.507282, Train ACC:0.953844
epoch :500, Train Loss:1.507480, Train ACC:0.953664

 

pytorch实战(一)简单卷积网络CIFAR10图片分类_第7张图片

准确率提高到95.3%,

总结:

1、学习率从大的开始试,开始时过大会导致网络发散,这是不允许的,找到一个20个epoch网络不发散,准确率最后稳定在某一数值的学习率作为初始学习率;

2、训练后期准确率提升很慢,所以patience不能太小,要不然很快学习率就衰减到根本走不动的地步;

 

4.如何提高准确度

1)添加BN

batch norm 可以是网络收敛更快,normalization是给输入数据做的,batch norm是一样的操作,只不过是应用在中间层的输出上。

好处:

1.加快训练速度,这样我们就可以使用较大的学习率来训练网络。

2.提高网络的泛化能力。

3.BN层本质上是一个归一化网络层,可以替代局部响应归一化层(LRN层)。

4.可以打乱样本训练顺序(这样就不可能出现同一张照片被多次选择用来训练)论文中提到可以提高1%的精度。

参考链接:

https://blog.csdn.net/donkey_1993/article/details/81871132

添加BN后网络收敛速度变快,在60个epoch后准确率就可以达到94%,而且learnin rate 一直都是初始的0.0005,没有decay,说明可以使用更大学习率

网络泛化能力略有提升

...
#在60个epoch后准确率就可以达到94%
('epoch = ', 60)
('train_acc:', 0.9417638297032828)
('val_acc:', 0.7876)
...
#在100个epoch后准确率已经比不用BN时的95.3%高了
('epoch = ', 100)
('train_acc:', 0.9581631747159091)
('val_acc:', 0.7844)
...
#学习率减半以后
('epoch = ', 300)
('train_acc:', 0.9790359651199495)
('val_acc:', 0.7896)
...

pytorch实战(一)简单卷积网络CIFAR10图片分类_第8张图片

2)正则化

经过BN以后,训练准确率进一步提高,但是过拟合问题严重,解决途径:正则化、dropout

正则化就是给参数加入惩罚项,也叫weight decay

drop out 是随机失活,用得比较广泛,我们尝试一下

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 3)
        self.conv1_bn = nn.BatchNorm2d(64)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.dropout_1 = nn.Dropout(0.5)

        self.conv2 = nn.Conv2d(64, 128, 3)
        self.conv2_bn = nn.BatchNorm2d(128)
        self.pool2 = nn.MaxPool2d(3, 2)
        self.dropout_2 = nn.Dropout(0.5)

        self.conv3 = nn.Conv2d(128, 256, 3)
        self.conv3_bn = nn.BatchNorm2d(256)
        self.conv4 = nn.Conv2d(256, 64, 1)
        self.conv4_bn = nn.BatchNorm2d(64)
        self.dropout_3 = nn.Dropout(0.5)

        self.fc1 = nn.Linear(1024, 384)
        self.dropout_4 = nn.Dropout(0.5)
        self.fc2 = nn.Linear(384, 192)
        self.fc3 = nn.Linear(192, 10)

    def forward(self, x):
        x = self.dropout_1(self.pool1(F.relu(self.conv1_bn(self.conv1(x)))))
        x = self.dropout_2(self.pool2(F.relu(self.conv2_bn(self.conv2(x)))))
        x = F.relu(self.conv3_bn(self.conv3(x)))
        x = self.dropout_3(F.relu(self.conv4_bn(self.conv4(x))))
        x = x.view(x.shape[0], -1)
        x = self.dropout_4((F.relu(self.fc1(x)))
        x = F.relu(self.dropout_4(self.fc2(x)))
        x = F.softmax(self.fc3(x))
        return x

注意,我们将dropout放在了maxpool之后,尝试过放在maxpool之前,发现准确率到70就训练不上去了

pytorch实战(一)简单卷积网络CIFAR10图片分类_第9张图片

过拟合问题得到了很大的解决,但是总体的准确率训练不上去,卡在70% ,训练、验证、检测都是70%。

可能是因为网络参数比较少,采用随机失活(p=0.5)导致拟合能力降低,考虑加深网络层数来提高拟合能力。

参数越多模型拟合能力越强;失活参数越多,模型泛化能力越强;这是一个trade off,我们调整一下神经元失活比例试一试

#drop out 设置
AlexNet(
  (conv1): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
  (conv1_bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (dropout_1): Dropout(p=0.3)
  (conv2): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
  (conv2_bn): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (pool2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  (dropout_2): Dropout(p=0.3)
  (conv3): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1))
  (conv3_bn): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (conv4): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1))
  (conv4_bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (dropout_3): Dropout(p=0.1)
  (fc1): Linear(in_features=1024, out_features=384, bias=True)
  (fc2): Linear(in_features=384, out_features=192, bias=True)
  (fc3): Linear(in_features=192, out_features=10, bias=True)
)
#学习率设置
optimizer = torch.optim.Adam(net.parameters(), lr=0.0005)
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=10,
verbose=True, threshold=0.0002, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
...
('epoch = ', 210)
Epoch   210: reducing learning rate of group 0 to 2.5000e-04.
('train_acc:', 0.9481065538194445)
('val_acc:', 0.823)
...
('epoch = ', 294)
Epoch   294: reducing learning rate of group 0 to 1.2500e-04.
('train_acc:', 0.9705132378472222)
('val_acc:', 0.8276)
('epoch = ', 295)
...

pytorch实战(一)简单卷积网络CIFAR10图片分类_第10张图片

可以看到由于失活神经元比例降低了,模型的拟合能力上去了,最终训练准确率达到了98%,验证准确率达到了83%,

我们接下来就在保证模型拟合能力足够的前提下,尽量增大失活比例,对参数进行调试

 

3)数据增强

利用数据增强减少过拟合问题

normMean = [0.4948052, 0.48568845, 0.44682974]
normStd = [0.24580306, 0.24236229, 0.2603115]
normTransform = transforms.Normalize(normMean, normStd)
#训练集数据增强
trainTransform = transforms.Compose([
	#transforms.Resize(32),
    #随机剪裁,因为要保证图像大小不变,所以进行了padding,其实只有这行算是数据增强
	transforms.RandomCrop(32, padding=4),
	transforms.ToTensor(),
	#normTransform
	])
#验证集数据预处理
validTransform = transforms.Compose([
	transforms.ToTensor(),
	normTransform
	])

关于数据增强很多博客都没有说明白,其实分为两种方法:离线、在线

离线是指通过增强使数据集变大,例如50000张图片,增强后变成100000张;

在线是指每次训练时对mini_batch中的图片使用增强,数据集大小始终是50000,当数据集很大时这种在线方法可以节约时间。

pytorch采用的是在线方法

cifar_norm_mean = (0.49139968, 0.48215827, 0.44653124)
cifar_norm_std = (0.24703233, 0.24348505, 0.26158768)

im_aug = transforms.Compose([
    transforms.RandomCrop(32,padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5)
    transforms.ToTensor()
    transforms.Normalize(cifar_norm_mean, cifar_norm_std),
])

经过数据增强后准确率训练、验证、测试都是80%,上不去了

你可能感兴趣的:(pytorch,机器学习)