一、minist分类
mnist是黑白手写数字数据集,定义了两种网络,只有全连接的网络和有卷积层的网络。
class FC2Layer(nn.Module):
def __init__(self, input_size, n_hidden, output_size):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
# 下式等价于nn.Module.__init__(self)
super(FC2Layer, self).__init__()
self.input_size = input_size
# 这里直接用 Sequential 就定义了网络,注意要和下面 CNN 的代码区分开
self.network = nn.Sequential(
nn.Linear(input_size, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, n_hidden),
nn.ReLU(),
nn.Linear(n_hidden, output_size),
nn.LogSoftmax(dim=1)
)
def forward(self, x):
x = x.view(-1, self.input_size)
return self.network(x)
class CNN(nn.Module):
def __init__(self, input_size, n_feature, output_size):
# 执行父类的构造函数,所有的网络都要这么写
super(CNN, self).__init__()
# 下面是网络里典型结构的一些定义,一般就是卷积和全连接
# 池化、ReLU一类的不用在这里定义
self.n_feature = n_feature
self.conv1 = nn.Conv2d(in_channels=1, out_channels=n_feature, kernel_size=5)
self.conv2 = nn.Conv2d(n_feature, n_feature, kernel_size=5)
self.fc1 = nn.Linear(n_feature*4*4, 50)
self.fc2 = nn.Linear(50, 10)
# 下面的 forward 函数,定义了网络的结构,按照一定顺序,把上面构建的一些结构组织起来
# 意思就是,conv1, conv2 等等的,可以多次重用
def forward(self, x, verbose=False):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x, kernel_size=2)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, kernel_size=2)
x = x.view(-1, self.n_feature*4*4)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.log_softmax(x, dim=1)
return x
CNN由两层卷积层和两层全连接层构成,使用ReLU激活函数,最大池化层将输出每个kernelsize*kernelsize大小内最大的元素,用于减小数据量、降低卷积对位置的敏感和提取特征。最后softmax输出每个类别的概率。
全连接网络和卷积网络训练后的结果如图,可见在训练数据上两者的损失都降到了较低的水平,但在测试集上CNN的准确度高了许多。
在将图片中的像素打乱后再训练,发现全连接网络的准确率几乎不变,而卷积网络的准确率大幅降低了,说明卷积操作确实会提取图像特征,对位置信息、像素关系等特征敏感。
二、CIFAR10 数据集分类
CIFAR10是一个有十类物体的彩色数据集,训练了两个卷积网络进行分类。结果如下。
![](https://img-blog.csdnimg.cn/e9c37242d0394a5c89e4aa65c6ecbf16.jpeg)
![](https://img-blog.csdnimg.cn/b4a373aed90d460697bdc1b35d324957.jpeg)
VGG在测试集上的准确率比网络1高的多,这些提升来自以下几点改进。
1.进行了不同的数据增强
网络1只进行了参数全为0.5的归一化,而VGG进行了随机裁剪、随机水平翻转和更符合数据集特征的归一化,这使训练数据更丰富可以提高模型的泛化性。
2.VGG网络更深更宽
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
class VGG(nn.Module):
def __init__(self):
super(VGG, self).__init__()
self.cfg = [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M']
self.features = self._make_layers(self.cfg)
self.classifier = nn.Linear(512, 10)
def forward(self, x):
out = self.features(x)
out = out.view(out.size(0), -1)
out = self.classifier(out)
return out
def _make_layers(self, cfg):
layers = []
in_channels = 3
for x in cfg:
if x == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1),
nn.BatchNorm2d(x),
nn.ReLU(inplace=True)]
in_channels = x
layers += [nn.AvgPool2d(kernel_size=1, stride=1)]
return nn.Sequential(*layers)
可见网络1只有2个卷积层且宽度最大只有16,而VGG有8个卷积层,且最大宽度达到了512,显然VGG更大的网络较适合CIFAR10分类任务。
3.VGG使用了批量归一化
VGG在网络中使用了批量归一化,使数据分布变平稳,可以加快训练速度使训练过程变平稳。
三、问题总结
1.dataloader 里面 shuffle 取不同值有什么区别?
shuffle表示是否要将数据打乱,一般在训练数据上要将数据打乱,为了增加数据的独立性,也有可能在数据集中相同标签的数据集中在某一区间,训练数据会更新权值,而一次更新中只有同一标签的数据不利于模型达到更好的效果,所以需要打乱。而测试数据不会更新权值所以不需要打乱。
2.transform 里,取了不同值,这个有什么区别?
transform里是对数据进行的处理,可以对训练数据进行不同的增强,如剪裁、旋转、缩放等,之后进行归一化,归一化中的参数是数据在三个通道上的均值和标准差。如对数据求出均值和标准差再进行归一化,那么处理后的数据分布会服从正态分布。如果简单全用0.5,则可将数据映射至【-1,1】,但不确定其分布。
3.epoch 和 batch 的区别?
所有数据都在网络上完成一次正向和反向传播为一个epoch,在这过程中每次都会取所有数据中batch大小的数据进行。
4.1x1的卷积和 FC 有什么区别?主要起什么作用?
1×1卷积主要用于通道融合,可以融合特征,输出规定通道数。全连接一般在模型的最后,用于将上层输出对应至类别。
5.residual leanring 为什么能够提升准确率?
首先残差结构相当于一个直连通道,将之前的特征跳跃的输入到之后的网络中,融合了不同尺度的特征,可以提升准确率。而且残差结构可以保持网络不变坏,如果一个卷积层没有对减小损失有贡献那么它就得不到梯度,最终的效果就是通过残差直接跳过这层,而不会使网络的性能变差。
7.代码练习二里,卷积以后feature map 尺寸会变小,如何应用 Residual Learning?
残差结构中使用1×1卷积来统一通道数,使用3×3等卷积来输出尺寸相同的特征图。
8.有什么方法可以进一步提升准确率?
可以进行数据增强,生成一些裁剪或拼接的图片。
可以使用dropout抑制过拟合,提高测试精度。
可以使用学习率调整策略,使模型收敛到一个更好点。
可以使用在大数据集上训练过的预训练模型。