Assginment3:Basis of CNN
本博客为OUC2022秋季软件工程第三次作业
文章目录
一、视频学习
第一部分:卷积神经网络相关概念
概括
-
卷积神经网络的应用:分类、检索、检测、分割;人脸识别、图像生成、智能驾驶。
-
深度学习三部曲:搭建神经网络结构,提取特征;合适的损失函数;合适的优化参数;
-
传统神经网络和卷积神经网络:
使用传统神经网络:参数太多,会导致过拟合现象,泛化性能差
卷积神经网络:局部关联;参数共享
相同之处:层级结构 -
卷积神经网络基本组成结构:卷积;池化;全连接
卷积
-
一维卷积
应用:信号处理,计算信号延迟累计
滤波器(卷积核) f=[f1,f2,f3]长度为m
信号序列 x=[x1,x2,x3,…]
卷积:
-
二维卷积
用于图像处理
矩阵内积Y=WX+b
-
卷积的具体操作举例
输入有多个channel时,如(R,G,B),使用两个三维权重矩阵
大小不匹配时,进行零填充(padding)
-
输出特征图的大小的计算
行列数=(N+padding*2-F)/stride+1
N–输入大小
F–卷积核大小
stride–步长
padding–填充大小 -
深度的概念;
depth/channel=filter个数 -
输出=特征图大小*深度
-
参数量=(单个filter大小+1)×filter个数
-
卷积的可视化理解:不同的卷积核关注不同的特征
池化
- 相当于缩放,参数量0,有filter和stride
- 作用:保留主要特征,减少参数量和计算量,防止过拟合,提高模型泛化能力
- 位于卷积层与卷积层间、全连接层与全连接层之间
- 常用方法:
最大值池化(分类)
平均值池化
全连接
一般放在卷积神经网络的尾部,参数量大
小结
第二部分:卷积神经网络典型结构
发展历程
AlexNet
- 影响最大的是收敛速度
- 总结用到的一些优化方法:
1.ReLU激活函数
2.DropOut(随机失活):从减少参数量方面考虑,训练时每次随机关闭一些神经元以降低参数量,整合时使用全部。
3.水平翻转:增加样本量
4.改变EGB通道强度:对rgb空间做高斯扰动。
VGG16
- VGG的思想:增加深度,通过固定参数实现训练
GoogleNet
是模型结构的改进:除了类别输出层没有额外的全连接层
- inception模块:通过多个卷积核增加特征多样性
inceptionV2:降维
inceptionV3:继续降维,裂变,用小的卷积核替代大的卷积核
优点:降低参数量;增加了非线性激活函数(裂变)
ResNet
残差学习网络,深度152层而无梯度消失问题,适用于很深的网络。
- 与传统卷积网络的差异
结构比较:
- 传统结构:复合函数f(h…)求导,结果是乘积形式。
残差block:f(h…)+x求导,结果为1+?的形式,解决了梯度消失问题。 - 灵活性考虑极端情况:
f(x)=0此时输出f(x)+x=x,可以自调节结构深度
疑问
1.为什么最大值池化更适用于分类问题?
2.filter和stride是如何选取的?
二、代码练习
(一)练习1 MNIST 数据集分类:构建简单的CNN对 mnist 数据集进行分类。
1.引入所需库,加载数据集(MNIST)
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
# 一个函数,用来计算模型中有多少参数
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")
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(加载数据的时候使用几个子进程)
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(20): #元素总数=行*列
plt.subplot(4, 5, i + 1) #4行5列
image, _ = train_loader.dataset.__getitem__(i)
plt.imshow(image.squeeze().numpy(),'gray')
plt.axis('off');
2.构建网络
-
Softmax函数常用的用法是指定参数dim就可以:
(1)dim=0:对每一列的所有元素进行softmax运算,并使得每一列所有元素和为1。
(2)dim=1:对每一行的所有元素进行softmax运算,并使得每一行所有元素和为1。 -
LogSoftmax其实就是对softmax的结果进行log,即Log(Softmax(x))
LogSoftmax的作用:
速度变快,数据稳定(些值经过softmax后概率非常低, 会下溢出. 所以一般会取概率的log表示概率) -
forward 函数定义了网络的结构,按照一定顺序,把上面构建的一些结构组织起来。
-
x.view()将向量铺平,便于传入全连接层,比如使矩阵的每行存放一张图片的各个参数,即使每行对应一张图片。
-
网络层次:卷积-relu激活-池化-卷积-激活-池化-矩阵变形处理(便于传入全连接层)-全连接-激活-全连接-输出
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) #Log(Softmax(x))
)
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) #最后分为10类
# 下面的 forward 函数,定义了网络的结构,按照一定顺序,把上面构建的一些结构组织起来
# 意思就是,conv1, conv2 等等的,可以多次重用
def forward(self, x, verbose=False)