深度学习笔记
文章目录
- 深度学习笔记
- 1. 前言
- 2. 卷积神经网络CNN
- `① CNN的基本组成`
- `② 重要概念`
- `③ 卷积层⭐`
- `④ 池化层`
- `⑤ 全连接层`
- `⑥ CNN的优势`
1. 前言
Pytorch官网手册:Pytorch官网手册
这里推荐一个卷积操作可视化的网站:卷积操作可视化网站
深度学习基本流程:获取数据->数据预处理->搭建模型->定义超参数(包括但不限于损失函数,优化器,学习率等)->将数据送入模型->模型训练->模型评估.
通常呢,我习惯于分成三大部分,也可以想象成代码的编写分成三大板块:
- 第一部分就是数据集的读取。
- 第二部分就是模型的搭建,这部分由卷积层、激活函数、池化层、全连接层组成,同时我还习惯于将学习率、损失函数、优化器、Batchsize等超参数放在这一部分。
- 第三部分就是模型训练和模型评估,可以选择打印loss值,acc等指标来帮助训练。
以上就是一个深度学习的简单流程了。
2. 卷积神经网络CNN
① CNN的基本组成
CNN主要由输入层、卷积层、激活函数层、池化层、全连接层、填充层、步幅等组成,基本上只讲卷积层、池化层、全连接层。
② 重要概念
- 多层感知机(Multi-Layer Perceptron,MLP)
至为n维输入向量的各个分量,至为各个输入分量连接到感知机的权量(或称权值),theta为阈值,f为激活函数,o为标量输出。理想的激活函数通常为阶跃函数或者sigmoid函数。感知机的输出是输入向量x与权重向量w求得内积后,经激活函数f所得到的标量。单层感知器类似一个逻辑回归模型,可以做线性分类任务,但是不能做更复杂的任务。
MLP由单层感知机推广而来,最主要的特点是有多个神经元层.一般第一层称为输入层,中间的层为隐藏层,最后一层为输出层。MLP没有规定隐藏层的数量,因此可以根据实际处理需求选择合适的隐藏层层数,且对于隐藏层和输出层中每层神经元的个数也没有限制。
- 感受野
感受野指在前向传播期间可能影响x计算的所有元素(来自所有先前层),换句话说就是输出feature map上某个元素受输入图像上影响的区域,或者说输出feature map上能看到的输入图像传过来的特征点。
- 权值共享
权值共享就是给一张输入图片,用一个filter扫这张图,filter里面的数就是权值,这张图的每个位置被同样的filter扫,所以用的是一个权值,也就叫共享。这里推荐一个回答:如何理解卷积神经网络中的权值共享?
③ 卷积层⭐
- 用于对图像进行特征提取操作,其卷积核权重是共享权值的,需要掌握stride(步幅)、padding(填充)、以及卷积的计算等概念。
- 卷积的计算: 假定输入一个3×3的矩阵,kernel_size是2×2大小的矩阵,卷积层的计算方式就是两个蓝色部分的矩阵相乘:0×0+1×1+2×3+3×4 = 19,1×0+2×1+4×2+5×3 = 25。另外两个计算方式同样。
现在扩展到二维:其计算其实与一维是差不多的,如:(1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56。
用代码来模拟一下一维的:
import torch
from torch import nn
def corr2d(X, K): # X 是输入,K是卷积核
h, w = K.shape # 获取卷积核的大小
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum() # 累加
return Y
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) # 模拟一个输入
K = torch.tensor([[0, 1], [2, 3]]) # 模拟一个卷积核
corr2d(X, K)
- 步长(stride): 实质就是filter在原图上扫描时,需要跳跃的格数,通过跳格,可以减小输出的高和宽,减少filter与原图做的扫描时的重复计算,提升效率。Pytorch框架中的stride一般取1或2。
- stride太小,重复计算较多,计算量大,训练效率降低;
- stride太大,会造成信息遗漏,无法有效提炼数据背后的特征;
- 上面的图介绍了stride=2的情况,假设输入大小为5×5的图像,kernel_size=3,stride=2,那么经过一轮的运算,输出大小变为了2×2(注意:行列都是移动两格)。将该例子用代码实现:
import torch.nn as nn
import torch
def comp_conv2d(conv2d, X):
# 这⾥的(1,1)表⽰批量⼤⼩和通道数都是1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# 省略前两个维度:批量⼤⼩和通道
return Y.reshape(Y.shape[2:])
X = torch.rand(size=(8, 8))
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
- 填充(padding): 在输入图像的边界填充元素(默认填0)。若采用小卷积核,图像经过多层卷积层后会丢失像素,卷积层越深丢失的像素就越多,到最后可能重要的特征都没有了.所以填充的作用是保持卷积后图像分辨率不变,防止丢失特征太多,同时获取图片边缘的特征。
填充的方式有:valid padding(有效填充)、same padding(相同填充)、full padding(全填充)、arbitrary padding(任意填充)。注意:在pytorch实际上是没有这些方式选的,TensorFlow框架。
- valid padding(有效填充): 不填充,只使用原始图像,不允许卷积核超出原始图像边界。
- same padding(相同填充): 保证填充后输入和输出的大小相同。填充像素点p=(kernel-1)/2就能保证相同。
- full padding(全填充): 每个像素在每个方向上被访问的次数相同。填充像素点p=kernel-1。
这里就演示一下pytorch任意填充的方式,实际上pytorch框架有封装好的填充函数:
import torch
from torch import nn
# 此函数初始化卷积层权重,并对输⼊和输出提⾼和缩减相应的维数
def comp_conv2d(conv2d, X):
# 这⾥的(1,1)表⽰批量⼤⼩和通道数都是1
X = X.reshape((1, 1) + X.shape)
Y = conv2d(X)
# 省略前两个维度:批量⼤⼩和通道
return Y.reshape(Y.shape[2:])
# 输入图像大小为5×5
X = torch.rand(size=(5, 5))
# 卷积核大小为3×3,向边缘填充一层,则输入图像大小为7×7
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
# 输出大小 = (5-3+2×1)/1 + 1 = 5
comp_conv2d(conv2d, X).shape
④ 池化层
池化层(pooling)也叫汇聚层,其任务是对图像进行下采样,降低图像分辨率。池化层的作用:使特征图变小,简化网络计算复杂度;压缩特征,提取主要特征。
- 放大图像也称为上采样:目的是把低分辨率图像采样成高分辨率图像。
- 缩小图像也称为下采样:目的是使得图像符合显示区域的大小;生成对应图像的缩略图。
- 想进一步了解可参考该链接:图像缩放_百度百科 (baidu.com)
Pooling的方法有最大池化(Max Pooling)和平均池化(Average Pooling)。
- 最大池化: 就是在一个卷积核内,选出该区域里最大的数值并输出。最大池化因为取的是最大值,所以可以提取到边缘性的信息。
- 其计算方式如下图:max{0,1,3,1,2,2,0,0,2}=3
- 平均池化: 就是在一个卷积核内,将该区域内的全部数值相加并求平均,平均池化因为考虑了感受野中所有像素的平均信息,所以生成的特征也会更加平滑。
- 其计算方式如下图:(1+3+1+2+2+2)/9=1.2
用代码来模拟一下上面两种池化的操作:
import torch
def pool2d(X, pool_size, mode):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
pool2d(X, (2, 2), mode='max') # mode可取'max'/'avg'
⑤ 全连接层
由最上方的第一张图可以知道:全连接层是放在最后的,何为全连接层?全连接层就是将最后一层卷积得到的特征图(矩阵)展开成一维向量,并为分类器提供输入。
【全连接层的作用】:全连接层在整个网络卷积神经网络中起到“分类器”的作用。如果说卷积层、池化层和激活函数等操作是将原始数据映射到隐层特征空间的话(特征提取+选择的过程),全连接层则起到将学到的特征表示映射到样本的标记空间的作用。换句话说,就是把特征整合到一起(高度提纯特征),方便交给最后的分类器或者回归。
参考链接:
CNN中的几个概念(全连接,卷积,线性,全卷积)理解 - 知乎 (zhihu.com)
[总结]
学习完上面的知识后,下面来介绍如何计算经过某一卷积或池化后的输出尺寸,公式如下:
- input表示我们输入样本的大小.
- kernel表示卷积核的大小,或称为感受野.
- padding表示填充层,在pytorch里面,padding=1表示填充一层边缘像素,默认填的值为0.
- stride表示步长,即kernel移动的距离,默认为1.
现在实现一个著名的卷积神经网络——LeNet:
一般复现一个神经网络的时候都会根据论文或论文中的结构图来复现:
import torch
import torch.nn as nn
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 下面的是卷积层+池化层
self.conv = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
)
# 下面的都是全连接层
self.fc = nn.Sequential(
nn.Flatten()
nn.Linear(16*5*5, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
⑥ CNN的优势
- 学习原理上的改进。
卷积神经网络不再是有监督学习,不需要从图像中提取特征,而是直接从原始图像数据进行学习,这样可以最大程度的防止信息在还没有进入网络之前就丢失。
- 学习方式的改进。
全连接神经网络一层的结果是与上一层的节点全部连接的,100×100的图像,如果隐藏层也是大小(100×100)的神经元,那么一层就有 10^8 个参数。要优化和存储这样的参数量,是无法想象的,所以经典的神经网络,基本上隐藏层在一两层左右。而卷积神经网络某一层的结点,只与上一层的一个图像块相连。
用于产生同一个图像中各个空间位置像素的卷积核是同一个,这就是所谓的权值共享。对于与全连接层同样多的隐藏层,假如每个神经元只和输入10×10的局部patch相连接,且卷积核移动步长为10,则参数为:100×100×10×10,降低了2个数量级。又能更好的学习,参数又低,卷积神经网络当然是可以成功了。