卷积神经网络——pytorch与paddle实现卷积神经网络
卷积神经网络——pytorch与paddle实现卷积神经网络
本文将深入探讨卷积神经网络的理论基础,并通过PyTorch和PaddlePaddle两个深度学习框架来展示如何实现卷积神经网络模型。我们将首先介绍卷积神经网络、图像处理的基本概念,这些理论基础是理解和实现卷积神经网络的基础。通过PyTorch和PaddlePaddle的代码示例,我们将展示如何设计、训练和评估一个卷积神经网络模型,从而让读者能够直观地理解并掌握这两种框架在计算机视觉问题中的应用。
本文部分为torch框架以及部分理论分析,paddle框架对应代码可见卷积神经网络paddle
import torch
print("pytorch version:",torch.__version__)
pytorch version: 2.0.1+cu118
图像卷积的基本原理
图像卷积运算实际上是在进行图像空间域的滤波。卷积层是CNN的核心组件,通过卷积运算提取特征。卷积运算是指从图像的左上角开始,开一个与模板(也称为卷积核或滤波器)同样大小的活动窗口,窗口图像与模板像元对应起来相乘再相加,并用计算结果代替窗口中心的像元亮度值。然后,活动窗口向右移动一列(或一行),并作同样的运算。以此类推,从左到右、从上到下,即可得到一幅新图像。
二维离散卷积公式用于描述两个二维信号(在图像处理中通常是一个图像和一个滤波器或卷积核)之间的卷积运算。给定一个二维图像信号 f [ i , j ] f[i, j] f[i,j] 和一个二维滤波器 h [ k , l ] h[k, l] h[k,l],二维离散卷积的结果 g [ x , y ] g[x, y] g[x,y] 可以通过以下公式计算:
g [ x , y ] = ∑ k = − ∞ ∞ ∑ l = − ∞ ∞ f [ i , j ] ⋅ h [ x − i , y − j ] g[x, y] = \sum_{k=-\infty}^{\infty} \sum_{l=-\infty}^{\infty} f[i, j] \cdot h[x-i, y-j] g[x,y]=k=−∞∑∞l=−∞∑∞f[i,j]⋅h[x−i,y−j]
其中, i i i 和 j j j 表示图像中像素的坐标,而 x x x 和 y y y 表示卷积结果中像素的坐标。实际上,在离散环境中,图像的尺寸是有限的,所以卷积核 h [ k , l ] h[k, l] h[k,l] 只在有限的区域内非零,因此上述求和实际上也只在有限的范围内进行。
更具体地,如果我们有一个 M × N M \times N M×N 的图像和一个 m × n m \times n m×n 的滤波器,那么对于输出图像中的每个位置 ( x , y ) (x, y) (x,y),卷积操作可以定义为:
g [ x , y ] = ∑ k = 0 m − 1 ∑ l = 0 n − 1 f [ x + k − m / 2 , y + l − n / 2 ] ⋅ h [ k , l ] g[x, y] = \sum_{k=0}^{m-1} \sum_{l=0}^{n-1} f[x+k-m/2, y+l-n/2] \cdot h[k, l] g[x,y]=k=0∑m−1l=0∑n−1f[x+k−m/2,y+l−n/2]⋅h[k,l]
在实际应用中,我们还需要处理边界条件,例如通过填充(如零填充)或裁剪图像边缘。下方动图过程来源于CoolGPU’s Blog,展示了卷积运算的过程。
该过程展示了一张 1 × 5 × 5 1 \times 5 \times 5 1×5×5的图像,以及一个 3 × 3 3 \times 3 3×3的卷积核。在卷积运算中,我们首先将卷积核在图像上滑动,每次滑动一个像素的距离。在每次滑动中,我们计算卷积核与图像对应位置像素的乘积,并将这些乘积相加得到一个值,作为卷积运算的结果。最后,我们将这些结果拼接在一起,得到一个新的 1 × 3 × 3 1 \times 3 \times 3 1×3×3的图像。
该过程展示了一张 3 × 5 × 5 3 \times 5 \times 5 3×5×5的图像,即该图像有三个通道,以及一个 3 × 3 × 3 3 \times 3 \times 3 3×3×3的卷积核。卷积核与图像进行卷积运算。最后,将这些结果对应值加在一起,算上偏置项,得到一个新的 1 × 3 × 3 1 \times 3 \times 3 1×3×3的图像。
该过程与上方过程类似,但此时,我们给图像外边填充了一圈0,同时卷积核每次移动步长变为2,这样我们最终也得到了一个 1 × 3 × 3 1 \times 3 \times 3 1×3×3的图像。
卷积层设计及其梯度反向传播
首先我们导入一张图片,然后利用卷积层对其进行滤波,提取特征。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline# 展示图像
image = cv2.imread('img/cells.png')
plt.imshow(image)
可以看到,该图片为 1133 × 1700 × 3 1133 \times 1700 \times 3 1133×1700×3的形状,我们将其转化为 1 × 3 × 1133 × 1700 1 \times 3 \times 1133 \times 1700 1×3×1133×1700的形式
# 将 ndarray 转换为 torch tensor
tensor = torch.from_numpy(image)
tensor = tensor.to(torch.float32) # 将数据类型转化为float# 调整 tensor 的维度,使其变为 (1, 3, 1133, 1700)
tensor = tensor.permute(2, 0, 1).unsqueeze(0)
接下来我们设计一个卷积层,实现图像的卷积操作。
import torch.nn as nn
import torch.nn.functional as Fclass ConvLayer(nn.Module):'''卷积层设计'''def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):super(ConvLayer, self).__init__()# 构建卷积核self.kernel = nn.Parameter(torch.randn(out_channels, in_channels, kernel_size, kernel_size))# 构建偏置项self.bias = nn.Parameter(torch.randn(out_channels))self.stride = strideself.padding = paddingdef forward(self, x):# 卷积操作out = F.conv2d(x, self.kernel, self.bias, stride=self.stride, padding=self.padding)return out
接下来,我们利用该卷积层,实现对图像在空间域的均值滤波,让我们来试试吧!
conv = ConvLayer(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=1)# 构建均值滤波卷积核
conv.kernel = nn.Parameter(torch.ones_like(conv.kernel))
conv.bias = nn.Parameter(torch.zeros_like(conv.bias))out_1 = conv(tensor[0, 0, :, :].reshape(1, 1, 1133, 1700))
out_2 = conv(tensor[0, 1, :, :].reshape(1, 1, 1133, 1700))
out_3 = conv(tensor[0, 2, :, :].reshape(1, 1, 1133, 1700))
# 分析可知,每一个像素由9个值相加得到,我们对每一个像素除以9,并将其合并为图像
out_1, out_2, out_3 = out_1/9, out_2/9, out_3/9out = torch.cat((out_1, out_2, out_3), dim=0) # 合并
# 将out转化为ndarry,并将形状调整为1133*1700*3的整形数组
out = out.squeeze().detach().numpy().transpose(1, 2, 0) # 从tensor中取出数据,调整形状,转置
out = out.astype(np.uint8) # 转化为整形数组
plt.imshow(out)
这样,我们就利用卷积神经网络实现了图像的均值滤波过程。
卷积神经网络解决分类问题的简单实现
从上述过程可以看到,卷积实际上是对图像进行某种滤波,提取图像特征的过程。下方我们将利用卷积神经网络,实现对图像的分类任务。首先我们导入数据集,并构建卷积神经网络。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms # 构建数据集
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor(), download=True) # 构建数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False) # 构建卷积神经网络
class CNN(nn.Module):def __init__(self):super(CNN, self).__init__()# 构建卷积层self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)# 构建全连接层self.fc1 = nn.Linear(32 * 7 * 7, 128)self.fc2 = nn.Linear(128, 10)def forward(self, x):# 卷积操作x = F.relu(self.conv1(x))x = F.max_pool2d(x, 2)x = F.relu(self.conv2(x))x = F.max_pool2d(x, 2)# 展平操作x = x.view(x.size(0), -1)# 全连接操作x = F.relu(self.fc1(x))x = self.fc2(x)return x# 构建模型实例
device = 'cuda' if torch.cuda.is_available else 'cpu'
model = CNN().to(device)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练模型
model.train()
for epoch in range(5):for data in train_loader:imgs, labels = dataoutputs = model(imgs.to(device))loss = criterion(outputs, labels.to(device))optimizer.zero_grad()loss.backward()optimizer.step()# 测试模型
model.eval()
total = 0
correct = 0
for data in test_loader:imgs, labels = dataoutputs = model(imgs.to(device))_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels.to(device)).sum()print('Accuracy: %.2f %%' % (100 * correct / total))
Accuracy: 98.80 %
可以看到该模型具有较高的精度,我们不妨将图像进行的每一次操作呈现出来,看看卷积层到底提取了什么样的特征。
首先我们将模型参数保存下来。
# 保存模型参数
torch.save(model, 'data/model_CNN_parameters.pt')
接下来,我们就可以在任意时间点加载这次训练好的模型了。
# 加载模型
model = torch.load('data/model_CNN_parameters.pt')
让我们看看每一次操作时,图像的特征变换
# 加载某个图片
img = imgs[3, :].reshape(1, 28, 28)
img = img.detach().numpy().transpose(1, 2, 0) # 从tensor中取出数据,调整形状,转置
# 将图像放缩到0~255
img = 255 * img
img = img.astype(np.uint8) # 转化为整形数组
plt.imshow(img)
人类可以较明显的识别出该数字是几,让我们将图像送入网络看看网络的识别结果。
model = model.to(device)
test_out = model(imgs[3, :].reshape(1, 1, 28, 28).to(device))
_, prediction = torch.max(test_out.data, 1)
prediction
tensor([4], device='cuda:0')
可以看到,网络的识别结果也是准确的。让我们将网络每一步图像处理进行的操作展示出来。
x_out1 = F.relu(model.conv1(imgs[3, :].reshape(1, 1, 28, 28).to(device)))
x_out2 = F.max_pool2d(x_out1, 2)
x_out3 = F.relu(model.conv2(x_out2))
x_out4 = F.max_pool2d(x_out3, 2)
import math
def transform_imag(img):# 将tensor格式转换为合适的图片_, channels, cols, rows = img.shapeimg = img.reshape(channels, cols, rows).to('cpu')img = img.detach().numpy().transpose(1, 2, 0) # 从tensor中取出数据,调整形状,转置# 将图像放缩到0~255img = 255 * imgimg = img.astype(np.uint8) # 转化为整形数组return imgdef show_Features(img):# 展示img中包含的特征c, r, channels = img.shapecols = round(math.sqrt(channels))for i in range(channels):plt.subplot(cols, math.ceil(channels / cols), i+1)plt.imshow(img[:, :, i].reshape(c, r, 1))plt.show()img1 = transform_imag(x_out1)
show_Features(img1)
img2 = transform_imag(x_out2)
show_Features(img2)
img3 = transform_imag(x_out3)
show_Features(img3)
img4 = transform_imag(x_out4)
show_Features(img4)
可以看到,网络图像越到后边层,各图像内容越简单且越抽象。
卷积神经网络(CNN)提取高阶特征的过程是一个层次性的数据处理与抽象过程,CNN通过卷积层利用卷积核在输入图像上滑动,执行卷积操作以捕捉图像的局部特征,如边缘、纹理等;随着网络层次的加深,这些简单特征被逐层抽象与组合,形成更复杂的特征表示,例如形状、物体的部分等。在此过程中,多个卷积核被用于每一层以提取不同的特征,而池化层则通过下采样操作降低特征图的维度,同时保留重要信息,增强特征的鲁棒性。最终,通过全连接层整合前面提取的特征,形成针对特定任务(如分类或回归)的高度抽象的特征表示,从而实现高阶特征的提取与利用。这一过程体现了CNN从底层到高层的特征学习和表示能力,是其在图像识别等任务中取得优异表现的关键。