第二周学习:卷积神经网络基础
Part 1:视频学习
1、卷积神经网络的应用
卷积神经网络无处不在:可用于分类、检索、检测、分割
具体举例:人脸识别(分类、检测等)、图像生成(GAN)、图像风格转换、自动驾驶(分类、分割等结合)
2、传统神经网络vs卷积神经网络
(1)搭建神经网络的步骤:①搭建神经网络结构②找到合适的损失函数(如:交叉熵损失函数、均方误差等)③找到合适的优化函数,更新参数(如:BP算法、随机梯度下降等)
(2)全连接神经网络:全部由全连接层组成的神经网络
(3)全连接神经网络也可用于计算机视觉,但因处理图像时权重矩阵的参数过多,出现过拟合现象。而卷积神经网络通过卷积核将神经元相连实现了局部关联,同时因为使用的卷积核不变,所以实现了参数共享,大大降低了参数数量。
(4)全连接神经网络与卷积神经网络的相同之处:都是层级结构
3、神经网络基本组成结构
一个典型的卷积神经网络通常由卷积层、池化层和全连接层交叉堆叠而成
(1)卷积:卷积是对两个实变函数的一种数学操作。一维卷积常用于信号处理,为卷积核,卷积即为一种内积操作。
在图像处理中,图像是以二维矩阵的形式输入到神经网络的,因此采用二维卷积,即 用卷积核掠过全图像,求内积。
(2)权重(weights):卷积核中的每一个值称为权重
(3)感受野(receptive field):在进行一次卷积时,卷积核所对应的输入区域即为感受野
(4)特征图(activation map / feature map):经一次卷积后所输出的结果即为特征图
(5)padding:有时因为步长大或者因为边界特征不能很好提取,因此在边界会打补丁。通常用0来填充
(6)步长(stride):卷积核每次移动的距离
(7)深度(depth):feature map的厚度,也等于卷积核的个数
(8)卷积过程:以输入为7*7*3,卷积核大小为3*3,步长为2,padding为1为例:

(9)输出特征图的大小:
未加padding:
加padding:
其中,N为输入大小,F为卷积核大小,stride为步长
(10)池化(pooling):保留了主要特征的同时减少参数和计算量,防止过拟合,提高模型泛化能力。一般处于卷积层与卷积层之间,全连接层与全连接层之间。
主要有最大池化法和平均池化法。
(11)全连接层(FC layer):两层之间所有神经元都有权重链接,通常在卷积神经网络尾部,全连接层参数量通常最大。通常进行展层之后与神经元相连。
4、卷积神经网络典型结构
(1)Alexnet:

特点:大数据训练:百万级ImageNet图像数据
非线性激活函数:ReLU(解决了sigmoid的梯度消失、计算速度快、收敛速度快)
防止过拟合:Dropout(训练时随机失活神经元,测试时整合所有神经元)
Data augmentation(通过平移、翻转、对称、改变RGB通道等方式)
其他:双GPU实现
(2)ZFNet:
(3)VGG:
VGG是一个更深的网络将AlexNet的8层扩展到了16/19层。先训练前11层,将模型参数固定后,再训练后面的。为迁移学习提供了模型参数。


(4)GoogleNet:

由Stem部分(stem network)(卷积 – 池化 – 卷积 –卷积 – 池化)+多个Inception结构堆叠+输出(除了最后的类别输出层外没有额外的全连接层)
特点:带inception 模块;
没有 FC层;
用辅助分类器解决模型深度过深导致的梯度消失问题。
inception模块:
串联不同的filter输出,但出现了参数过多的情况,因此,引入1*1卷积核解决 :
辅助分类器:通过将两个辅助分类器产生的loss加在最后的loss上一起去优化。
inception V3:通过用多个小的卷积核代替大的卷积核实现了降低参数量及增加非线性激活函数:增加非线性激活函数使网络产生更多独立特(disentangled feature),表征能力更强,训练更快。
(5)ResNet
特点:深度非常深:有152层,且可灵活实现层数调整;
除了输出层外没有其他全连接层;
由Residual block组成。
Residual block:


通过将输入再加到输出上避免了由于层数过多导致的梯度消失现象,同时可以去掉相同的主体部分,从而突出微小的变化,用来训练非常深的网络。
Part 2:代码练习
1、MNIST 数据集分类:构建简单的CNN对 mnist 数据集进行分类
(1)加载数据:
PyTorch里包含了 MNIST, CIFAR10 等常用数据集,调用 torchvision.datasets 即可把这些数据由远程下载到本地,下面给出MNIST的使用方法:
torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
- root 为数据集下载到本地后的根目录,包括 training.pt 和 test.pt 文件
- train,如果设置为True,从training.pt创建数据集,否则从test.pt创建。
- download,如果设置为True, 从互联网下载数据并放到root文件夹下
- transform, 一种函数或变换,输入PIL图片,返回变换之后的数据。
- target_transform 一种函数或变换,输入目标,进行变换。
另外值得注意的是,DataLoader是一个比较重要的类,提供的常用操作有:batch_size(每个batch的大小), shuffle(是否进行随机打乱顺序的操作), num_workers(加载数据的时候使用几个子进程)
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy
from google.colab import drive
import sys
from keras.datasets import mnist
# 一个函数,用来计算模型中有多少参数
def get_n_params(model):
np=0
for p in list(model.parameters()):
np += p.nelement()
return np
# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
input_size = 28*28 # MNIST上的图像尺寸是 28x28
output_size = 10 # 类别为 0 到 9 的数字,因此为十类
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=True, download=True,
transform=transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])),
batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('./data', train=False,
transform=transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))])),
batch_size=1000, shuffle=True)
#显示数据集中的部分图像
plt.figure(figsize=(8, 5))
for i in range(25):
plt.subplot(5, 5, i + 1)
image, _ = train_loader.dataset.__getitem__(i)
plt.imshow(image.squeeze().numpy(),'gray')
plt.axis('off');
输出结果:
(2)创建网络:
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数init中。
只要在nn.Module的子类中定义了forward函数,backward函数就会自动被实现(利用autograd)。
#全连接神经网络
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):
# view一般出现在model类的forward函数中,用于改变输入或输出的形状
# x.view(-1, self.input_size) 的意思是多维的数据展成二维
# 代码指定二维数据的列数为 input_size=784,行数 -1 表示我们不想算,电脑会自己计算对应的数字
# 在 DataLoader 部分,我们可以看到 batch_size 是64,所以得到 x 的行数是64
# 大家可以加一行代码:print(x.cpu().numpy().shape)
# 训练过程中,就会看到 (64, 784) 的输出,和我们的预期是一致的
# forward 函数的作用是,指定网络的运行过程,这个全连接网络可能看不啥意义,
# 下面的CNN网络可以看出 forward 的作用。
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
#卷积-》池化-》卷积-》池化-》全连接-》全连接
定义训练及测试函数:
# 训练函数
def train(model):
model.train()
# 主里从train_loader里,64个样本一个batch为单位提取样本进行训练
for batch_idx, (data, target) in enumerate(train_loader):
# 把数据送到GPU中
data, target = data.to(device), target.to(device)
optimizer.zero_grad()#清零梯度
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))
#测试函数
def test(model):
model.eval()
test_loss = 0
correct = 0
for data, target in test_loader:
# 把数据送到GPU中
data, target = data.to(device), target.to(device)
# 把数据送入模型,得到预测结果
output = model(data)
# 计算本次batch的损失,并加到 test_loss 中
test_loss += F.nll_loss(output, target, reduction='sum').item()
# get the index of the max log-probability,最后一层输出10个数,
# 值最大的那个即对应着分类结果,然后把分类结果保存在 pred 里
pred = output.data.max(1, keepdim=True)[1]
# 将 pred 与 target 相比,得到正确预测结果的数量,并加到 correct 中
# 这里需要注意一下 view_as ,意思是把 target 变成维度和 pred 一样的意思
correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()
test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
accuracy))
(3)在全连接神经网络上训练:
n_hidden = 8 # number of hidden units
model_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))
train(model_fnn)
test(model_fnn)
输出结果:

(4)在卷积神经网络上训练:
n_features = 6 # number of feature maps
model_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))
train(model_cnn)
test(model_cnn)
输出结果:

含有相同参数的CNN明显比FC训练效果要好,主要由于卷积层和池化层。
(5)打乱像素顺序后再在两个网络上训练:考虑到CNN在卷积与池化上的优良特性,如果我们把图像中的像素打乱顺序,这样 卷积 和 池化 就难以发挥作用了,为了验证这个想法,我们把图像中的像素打乱顺序再试试。
首先下面代码展示随机打乱像素顺序后,图像的形态:
# 这里解释一下 torch.randperm 函数,给定参数n,返回一个从0到n-1的随机整数排列
perm = torch.randperm(784)#28*28
plt.figure(figsize=(8, 4))
for i in range(10):
image, _ = train_loader.dataset.__getitem__(i)
# permute pixels
image_perm = image.view(-1, 28*28).clone()
image_perm = image_perm[:, perm]
image_perm = image_perm.view(-1, 1, 28, 28)
plt.subplot(4, 5, i + 1)
plt.imshow(image.squeeze().numpy(), 'gray')
plt.axis('off')
plt.subplot(4, 5, i + 11)
plt.imshow(image_perm.squeeze().numpy(), 'gray')
plt.axis('off')
打乱之后结果:
重新定义训练与测试函数,我们写了两个函数 train_perm 和 test_perm,分别对应着加入像素打乱顺序的训练函数与测试函数。(相比原训练和测试函数只多了打乱像素顺序这一步)
与之前的训练与测试函数基本上完全相同,只是对 data 加入了打乱顺序操作。
# 对每个 batch 里的数据,打乱像素顺序的函数
def perm_pixel(data, perm):
# 转化为二维矩阵
data_new = data.view(-1, 28*28)
# 打乱像素顺序
data_new = data_new[:, perm]
# 恢复为原来4维的 tensor
data_new = data_new.view(-1, 1, 28, 28)
return data_new
# 训练函数
def train_perm(model, perm):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
# 像素打乱顺序
data = perm_pixel(data, perm)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))
# 测试函数
def test_perm(model, perm):
model.eval()
test_loss = 0
correct = 0
for data, target in test_loader:
data, target = data.to(device), target.to(device)
# 像素打乱顺序
data = perm_pixel(data, perm)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item()
pred = output.data.max(1, keepdim=True)[1]
correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()
test_loss /= len(test_loader.dataset)
accuracy = 100. * correct / len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
accuracy))
在全连接网络上训练与测试
perm = torch.randperm(784)
n_hidden = 8 # number of hidden units
model_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))
train_perm(model_fnn, perm)
test_perm(model_fnn, perm)
训练结果:

在卷积神经网络上训练:
perm = torch.randperm(784)
n_features = 6 # number of feature maps
model_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))
train_perm(model_cnn, perm)
test_perm(model_cnn, perm)
训练结果:

从打乱像素顺序的实验结果来看,全连接网络的性能基本上没有发生变化,但是 卷积神经网络的性能明显下降。
这是因为对于卷积神经网络,会利用像素的局部关系,但是打乱顺序以后,这些像素间的关系将无法得到利用。
2、CIFAR10 数据集分类:使用 CNN 对 CIFAR10 数据集进行分类
下面将使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为3x32x32,也就是RGB的3层颜色通道,每层通道内的尺寸为32*32。
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 注意下面代码中:训练的 shuffle 是 True,测试的 shuffle 是 false
# 训练时可以打乱顺序增加多样性,测试是没有必要
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=8,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
展示 CIFAR10 里面的一些图片:
def imshow(img):
plt.figure(figsize=(8,8))
img = img / 2 + 0.5 # 转换到 [0,1] 之间
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 得到一组图像
images, labels = iter(trainloader).next()
# 展示图像
imshow(torchvision.utils.make_grid(images))
# 展示第一行图像的标签
for j in range(8):
print(classes[labels[j]])

接下来定义网络,损失函数和优化器:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)#in_channels=3, out_channels=6, kernel_size=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)))#卷积-relu-池化
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
# 网络放到GPU上
net = Net().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)#Adam优化器
训练网络:
for epoch in range(10): # 重复多轮训练
for i, (inputs, labels) in enumerate(trainloader):
inputs = inputs.to(device)
labels = labels.to(device)
# 优化器梯度归零
optimizer.zero_grad()
# 正向传播 + 反向传播 + 优化
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 输出统计信息
if i % 100 == 0:
print('Epoch: %d Minibatch: %5d loss: %.3f' %(epoch + 1, i + 1, loss.item()))
print('Finished Training')
共训练10轮,loss最后为0.858

现在我们从测试集中取出8张图片:
# 得到一组图像
images, labels = iter(testloader).next()
# 展示图像
imshow(torchvision.utils.make_grid(images))
# 展示图像的标签
for j in range(8):
print(classes[labels[j]])

我们把图片输入模型,看看CNN把这些图片识别成什么:
outputs = net(images.to(device))
_, predicted = torch.max(outputs, 1)
# 展示预测的结果
for j in range(8):
print(classes[predicted[j]])

网络在整个数据集上的表现:
correct = 0
total = 0
for data in testloader:
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))

准确率为62%,不好也不差
3、使用 VGG16 对 CIFAR10 分类
VGG的网络结构在视频学习中已给出,不多赘述。
因模型参数过多,进行了适当修改,修改之后的结构如下:
64 conv, maxpooling,
128 conv, maxpooling,
256 conv, 256 conv, maxpooling,
512 conv, 512 conv, maxpooling,
512 conv, 512 conv, maxpooling,
softmax
(1)定义 dataloader
import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 使用GPU训练,可以在菜单 "代码执行工具" -> "更改运行时类型" 里进行设置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
需要注意的是,这里的 transform,dataloader 和之前定义的有所不同,主要是进行了剪裁RandomCrop,并进行了padding
(2)VGG网络定义:
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(cfg)
self.classifier = nn.Linear(2048, 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)
# 网络放到GPU上
net = VGG().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
(3)网络训练
训练的代码和以前是完全一样的:
for epoch in range(10): # 重复多轮训练
for i, (inputs, labels) in enumerate(trainloader):
inputs = inputs.to(device)
labels = labels.to(device)
# 优化器梯度归零
optimizer.zero_grad()
# 正向传播 + 反向传播 + 优化
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 输出统计信息
if i % 100 == 0:
print('Epoch: %d Minibatch: %5d loss: %.3f' %(epoch + 1, i + 1, loss.item()))
print('Finished Training')
(4)测试验证准确率:
测试的代码和之前也是完全一样的:
correct = 0
total = 0
for data in testloader:
images, labels = data
images, labels = images.to(device), labels.to(device)
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %.2f %%' % (
100 * correct / total))
Part 3:问题回答
1、dataloader 里面 shuffle 取不同值有什么区别?
dataloader里面的shuffle的作用是选择是否对每次迭代时的数据进行打乱,有True和Flase两个取值。取True时就会进行打乱,Flase就不会,对无序列特征的数据shuffle为True可使数据更具有独立性,而具有序列特征的数据一般不进行打乱。
在实验一中,使shuffle的取值为Flase后,FC训练结果和CNN训练结果分别如下:


准确率并没有太大改变,而改变像素排列顺序后,FC和CNN的训练结果:
FC的准确率大幅降低,怀疑出现了过拟合现象。而CNN的准确率也有降低但不如FC的幅度大。
2、transform 里,取了不同值,这个有什么区别?
transform是一种常见的对图像进行预处理的方式,从而提高模型的泛化能力,主要有:中心化、标准化、缩放、剪裁、旋转、翻转、填充、噪声添加、灰度变换、线性变换、仿射变换、亮度、饱和度及对比度变换等方式,采用transforms.Compose(),将一系列的transforms有序组合,实现时按照这些方法依次对图像操作。
以实验1、2、3为例,都进行了ToTensor和Normalize,ToTensor将PIL Image或者 ndarray 转换为tensor,并且归一化至[0-1],而Normalize则是用均值和标准差归一化数据,即input[channel] = (input[channel] - mean[channel]) / std[channel],1、2、3进行标准化时的均值和标准差不同,最后归一化也不同。
3、epoch 和 batch 的区别?
一个epoch代表全部数据进入网络一次,这个时候,整个网络结构只对这批数据全部走完一次,损失函数的下降还不够,因此需要多次epoch。需要多少个epoch才能使得网络训练到较为合理的地步,暂时没有定论,具体问题具体分析。
batch指的是将一个epoch(所有数据)分割成几份,每份的大小为batch size。进行batch的愿因:①有时候一整份数据量过多,无法一次性全部输入②分batch,损失函数会向着所有batch都下降,而不是总体下降的方向下降,更不容易陷入局部最优解(鞍点),泛化性更强,但不容易收敛。
4、1x1的卷积和 FC 有什么区别?主要起什么作用?
1*1的卷积虽然和FC输出的大小一样,但是1*1的卷积因为参数共享,所使用的参数为1*1+1=2远小于FC所使用的参数。且,FC根据图像大小预先设定好后,因参数固定,不能再应用于其他地方,而1*1卷积则能适用于任意输入尺寸。1*1的卷积主要通过卷积核的个数,改变原图像的通道数,实现升降维,同时在inception模型中1*1的卷积的加入,减少了串联后的多参数,并使网络的非线性增加。
5、residual leanring 为什么能够提升准确率?
residual leanring通过再输入之后再加上本身,使其很难发生梯度消失现象,同时,如果一次卷积之后为0,则会相当于使该层消失,使整个网络的层数调整更加的灵活。
6、代码练习二里,网络和1989年 Lecun 提出的 LeNet 有什么区别?
LeNet原文地址:Gradient-based learning applied to document recognition | IEEE Journals & Magazine | IEEE Xplore
LeNet网络结构如下:

主要区别:在激活函数,LeNet使用sigmoid函数,而实验2使用ReLU函数;LeNet使用平均池化法进行下采样,而实验2使用最大池化法
7、代码练习二里,卷积以后feature map 尺寸会变小,如何应用 Residual Learning?
使用1*1的卷积核来改变特征图大小,从而使f(x)与x一致可相加。
8、有什么方法可以进一步提升准确率?
(1)增加训练轮数(不一定能增加准确率)
如实验2,训练50轮,准确率下降(应该使过拟合了,训练集的loss只有0.1~0.2):![]()
(2)增加dropout层
实验二增加如下dropout层:
def dropout_layer(X, dropout):
assert 0 <= dropout <= 1#断言,确保dropout在0-1之间
# dropout=1,所有元素都被丢弃
if dropout == 1:
return torch.zeros_like(X)
# dropou=0,所有元素都被保留
if dropout == 0:
return X
# 其他情况,dropout在0-1之间
mask = (torch.rand(X.shape) > dropout).float()#返回0和1的向量
return mask * X / (1.0 - dropout)#进行中间值拓展
dropout1, dropout2 = 0.2, 0.3
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))
if self.training == True:
# 在第一个全连接层之后添加一个dropout层
x = dropout_layer(x, dropout1)
x = F.relu(self.fc2(x))
if self.training == True:
# 在第二个全连接层之后添加一个dropout层
x = dropout_layer(x, dropout2)
x = self.fc3(x)
return x
但是准确率也降低了。。。。。。不知道哪有问题
(3)增加数据量
(4)改变神经网络结构,增加层数等
(5)使用多个小的卷积核去等价替代大的卷积核
用两个3*3的卷积核去替代5*5的卷积核:
self.conv1 = nn.Conv2d(3, 3, 3)
self.conv3 = nn.Conv2d(3, 6, 3)
训练20轮:
emmm,略有提升。
Part 3 遇到的问题
主要就是提升准确率的时候,要么就准确率反而下降,要么就准确率提升不明显。
另外,在实验三中,出现了如下报错:
网上找到的解决方法是:
out = out.view(out.size(0),-1)
改为:
out = out.view(-1,2048)
还有就是colab登太长时间,直接用不了GPU了
本文详细讲解了卷积神经网络的基础概念,如其广泛应用、与传统神经网络的区别,以及典型结构AlexNet、VGG和GoogleNet。通过实例分析和代码实践,探讨了MNIST和CIFAR10数据集的分类,比较了全连接网络与卷积网络的效果,以及如何通过打乱像素顺序来验证CNN特性。
5万+

被折叠的 条评论
为什么被折叠?



