1.卷积神经网络--二维卷积层
卷积神经网络(convolutional neural network)是含有卷积层(convolutional layer)的神经网络。卷积神经网络中使用最常见的是二维卷积层,它有高和宽两个空间维度,常用来处理图像数据。
1.1卷积运算
虽然卷积层得名于卷积(convolution)运算,但我们通常在卷积层中使用更加直观的互相关(cross-correlation)运算。在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组, 这个二维核在卷积计算中又称卷积核或过滤器(filter)。
在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。
下面我们将上述过程通过covn2d函数实现。它接受输入数组X
与核数组K
,并输出数组Y
。
先构造输入数组X和核数组K:
import torch
from torch import nn
X = torch.ones(6,8)
X[:,2:6] = 0
X = torch.reshape(input,[1,1,6,8]) # 转换为4维数据,即NCHW(图像个数、通道、高、宽)
# 定义一个垂直边缘检测的卷积核
K = torch.tensor([[-1,0,1],
[-2,0,2],
[-1,0,1]])
K = torch.reshape(K,[1,1,3,3]).float() # 转换为4维数据,float类型
输出:
X:
tensor([[[[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.],
[1., 1., 0., 0., 0., 0., 1., 1.]]]])
K:
tensor([[[[-1., 0., 1.],
[-2., 0., 2.],
[-1., 0., 1.]]]])
通过conv2d函数实现卷积运算:
import torch.nn.functional as F
# 未避免图像边缘损失,一般需设置padding,取值一般为K的高|宽/2向下取整
Y = F.conv2d(X,K,padding=1)
输出Y:
tensor([[[[ 3., -3., -3., 0., 0., 3., 3., -3.], [ 4., -4., -4., 0., 0., 4., 4., -4.], [ 4., -4., -4., 0., 0., 4., 4., -4.], [ 4., -4., -4., 0., 0., 4., 4., -4.], [ 4., -4., -4., 0., 0., 4., 4., -4.], [ 3., -3., -3., 0., 0., 3., 3., -3.]]]])
1.2二维卷积层
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。
下面来自定义一个二维卷积层神经网络模型。
在 PyTorch 中,构建神经网络通常需要继承 nn.Module 类。nn.Module 是所有神经网络模块的基类,需要定义以下两个部分:
__init__()
:定义网络层。forward()
:定义数据的前向传播过程。
在__init__()
里我们声明weight
和bias
这两个模型参数。前向计算函数forward
则是直接调用conv2d函数。
# 定义一个简单的卷积神经网络模型,继承nn.Module类
class Mynn(nn.Module):
def __init__(self,ksize):
super().__init__()
self.weight = nn.Parameter(torch.randn(ksize))
self.bias = nn.Parameter(torch.randn(1))
def forward(self,input):
return nn.functional.conv2d(input,self.weight,self.bias,padding=1)
1.3通过数据学习卷积核
最后来看一个例子,它使用上述输入数据X
和输出数据Y
来学习我们构造的垂直边缘检测核数组K
。
首先构造一个卷积层神经网络,其卷积核将被初始化成3*3的随机数组。接下来在每一次迭代中,我们使用平方误差来比较Y
和卷积层的输出,然后计算梯度来更新权重。
# 实例化一个卷积核大小为3*3的2D卷积层神经网络
mynn = Mynn((1,1,3,3))
# 设置训练步骤数为400
step = 400
# 设置学习率为0.005
lr = 0.005
for i in range(step):
Y_hat = mynn(X)
l = ((Y_hat - Y) ** 2).sum() # 计算损失函数,这里是均方误差(MSE),即预测值Y_hat和真实值Y之间的差的平方和。
l.backward() # 对损失函数进行反向传播,计算梯度。
# 梯度下降
mynn.weight.data -= lr * mynn.weight.grad
mynn.bias.data -= lr * mynn.bias.grad
# 梯度清零,为下一次迭代做准备。
# fill_(0)方法用于将张量中的所有元素填充为0。
mynn.weight.grad.fill_(0)
mynn.bias.grad.fill_(0)
# 每50步打印一次当前的训练步数和损失值。
# item()方法用于获取张量中的单个元素值。
if (i + 1) % 50 == 0:
print('Step %d, loss %.3f' % (i + 1, l.item()))
# 查看学习到的卷积核参数
print("学习到的卷积核参数:")
print("weight: ", mynn.weight.data)
print("bias: ", mynn.bias.data)
输出:
Step 50, loss 1.628 Step 100, loss 0.716 Step 150, loss 0.321 Step 200, loss 0.146 Step 250, loss 0.066 Step 300, loss 0.031 Step 350, loss 0.014 Step 400, loss 0.007 学习到的卷积核参数: weight: tensor([[[[-0.9985, -0.0297, 1.0480], [-1.9643, 0.0064, 1.9546], [-1.0424, 0.0224, 1.0040]]]]) bias: tensor([5.6758e-05])
可以看到,学到的卷积核的权重参数与我们之前定义的核数组K
较接近,而偏置参数接近0。