之前已经学习了:
如何利用pytorch设计简单的神经网络
如何制作数据集
何如使用设计的网络进行训练
接下来利用一个简单的
1.CIFAR10数据集介绍
官方网站:http://www.cs.toronto.edu/~kriz/cifar.html
10个类别,每个类别6000张,一共60000张图片,50000张用来训练,10000张用来测试
这里可以查看各种方法在该数据集上的表现: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:卷积核大小3*3,卷积核移动步长1,卷积核个数64,池化大小2*2,池化步长2,池化类型为最大池化,激活函数ReLU;
- 卷积层2:卷积核大小3*3,卷积核移动步长1,卷积核个数128,池化大小2*2,池化步长2,池化类型为最大池化,激活函数ReLU;
- 卷积层3:卷积核大小3*3,卷积核移动步长1,卷积核个数256,激活函数ReLU;
- 卷积层4:卷积核大小1*1,卷积核移动步长1,卷积核个数64,激活函数ReLU;
- 全连接层:隐藏层单元数384,激活函数ReLU;
- 全连接层:隐藏层单元数192,激活函数ReLU;
- 全连接层:隐藏层单元数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,环境先不能随便改,先凑合着用吧。
1)50个epoch下来,训练集准确率89%,测试集准确率75%,loss从2.3降低到1.57
之前尝试过训练200个epoch,到100以后网络就发散了
可能是没有使用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
有所好转,但是后边学习率太低,基本在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]
200个epoch后准确率达到了92%,loss达到了1.53,已经提升不上来了
4)尝试自适应学习率调整
参考链接:
https://blog.csdn.net/chanbo8205/article/details/89283226
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
...
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
准确率提高到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)
...
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就训练不上去了
过拟合问题得到了很大的解决,但是总体的准确率训练不上去,卡在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)
...
可以看到由于失活神经元比例降低了,模型的拟合能力上去了,最终训练准确率达到了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%,上不去了