最近在 B站 刘二大人 学习PyTorch ,上传一些学习用的代码,仅供参考和交流。
目录
1、卷积层
卷积神经网络中每层卷积层(Convolutional layer)由若干卷积单元组成,每个卷积单元的参数都是反向传播算法最佳化得到的。卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网路能从低级特征中迭代提取更复杂的特征,主要作用包括:
-
特征提取:卷积层通过卷积操作可以有效地提取图像中的特征,如边缘、角点、纹理等。
-
局部感受野:每个卷积核只关注输入数据的一个小区域,即局部感受野,有助于模型捕捉局部特征。在多个卷积层叠加下,网络能够逐渐整合这些局部特征以识别更复杂的模式。
-
参数共享:在卷积层中,同一个过滤器的权重在整个输入图像上共享。权重共享大大减少了模型的参数数量,从而降低了过拟合的风险。
-
平移不变性:由于参数共享的存在,卷积网络对输入图像的小幅平移具有一定的不变性。换句话说,即使图像中的对象稍微移动,卷积网络也可以有效地识别它。
-
降低维度:通过使用步长(Stride)和池化层,卷积层可以降低数据的空间维度。
那么,卷积层是如何计算的呢?
卷积层作为一个3x3的窗口在输入层上面滑动,每个单元进行乘法运算,相当于矩阵运算中的点乘操作,最后相加得到处理后的结果,如图所示:
运算的结果为:
上述是单个通道的卷积计算,在实际应用中,一般为彩色图片,具有R、G、B三个通道,需要将单个通道计算结果进行再次相加,如下图所示:
那么经过卷积层后输出尺寸如何计算呢?
其中,W为输入的大小(宽度或者高度),K为卷积核的大小,P为填充的大小,S为步长。S表示卷积窗口移动的步长,P表示填充,常见的填充有三种:
-
有效(Valid)或零填充(Zero-padding):P=0,不在输入图像周围添加额外的零值像素,最终会导致输出尺寸减小。
-
相同(Same)填充: 通过设置P,使得卷积操作后输出的尺寸与输入尺寸相同。对于奇数大小的卷积核,可以通过P=(K-1)/2计算相同填充的大小:
-
全填充(Full padding): 通过设置 P,使得卷积操作后输出的每个像素都由输入的所有像素参与计算。全填充会导致输出尺寸增加。
在程序中,通过torch.nn.Conv2d创建卷积层,代码如下:
# 创建 Conv2d 层
conv_layer = torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride)
in_channels
表示输入通道的数量,out_channels
表示输出通道的数量,kernel_size
表示卷积核的大小,stride
表示卷积操作的步幅。
在下图中,输入通道为n,输出通道为m,输出的尺寸可以根据公式计算,而这需要m组n通道的卷积核实现,假设卷积核为正方形,卷积核大小为k,卷积核表示为一个m✖n✖k✖k的张量。
2、池化层
池化层用于降采样和减少特征映射的空间维度。最大池化和平均池化是两种常见的池化操作,它们分别选取池化窗口中的最大值或平均值,从而减小数据的空间尺寸。池化与卷积层类似,计算时只选取池化窗口中的最大值或平均值,而无须进行点乘相加操作。
当窗口移动步长为stride=2时,最大池化计算如图所示:
计算结果如下:
3、ReLU函数
ReLU(Rectified Linear Unit)是深度学习中常用的激活函数之一。它是一种非线性函数,通常应用于神经网络的隐藏层,以引入非线性特性。
即当输入x>0时,输出保持不变,否则,输出为0。
4、卷积神经网络
卷积神经网络(Convolutional Neural Network,CNN)是一种专门用于处理具有网格状结构数据(如图像、语音)的深度学习模型,由一个或多个卷积层和顶端的全连通层组成,同时也包括关联权重核池化层(pooling layer)。本次学习所用数据集为MNIST数据集,可参考:PyTorch深度学习实践 09.多分类问题-优快云博客
先使用两层卷积层、池化层进行特征提取,最后将输出的二维张量平铺为一维向量,使用全连接神经网络进行分类操作。在卷积层核池化层之间一般有激活函数,如sigmoid函数、ReLU函数,本次使用ReLU函数。
完整代码如下:
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import matplotlib.pyplot as plt
#加载数据集
batch_size=64
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,))
])
train_dataset=datasets.MNIST(root='../dataset/mnist/',
train=True,
download=False,
transform=transform)
train_loader = DataLoader(train_dataset,
shuffle=True,
batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/',
train=False,
download=False,
transform=transform)
test_loader = DataLoader(test_dataset,
shuffle=False,
batch_size=batch_size)
#构造模型
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
self.pooling = torch.nn.MaxPool2d(2)
self.fc = torch.nn.Linear(320, 10)
def forward(self, x):
# flatten data from (n,1,28,28) to (n, 784)
batch_size = x.size(0)#[batch_size,channels,height,width]]
x = F.relu(self.pooling(self.conv1(x)))
x = F.relu(self.pooling(self.conv2(x)))
x = x.view(batch_size, -1) # -1 此处自动算出的是320
# print(x.shape)
#x = x.view(batch_size, -1):将多维的特征图展平为一维向量。view函数是PyTorch中用于重塑张量的函数。batch_size参数保持不变,
# 意味着每个样本在展平后仍然是独立的。-1表示让PyTorch自动计算这个维度应该有多少元素,以确保所有元素都包含在内且保持原来的批量大小不变。
x = self.fc(x)
return x
model=Net()
#定义损失函数
criterion=torch.nn.CrossEntropyLoss()#损失函数,不求均值
#定义优化器
optimizer=torch.optim.SGD(model.parameters(),lr=0.01,momentum=0.6)#SGD优化器,带动量
losses=[]
accs=[]
def train(epoch):
running_loss=0.0
for batch_idx,data in enumerate(train_loader,0):
inputs,target=data
optimizer.zero_grad()
#forward+backward+update
outputs=model(inputs)
loss=criterion(outputs,target)
loss.backward()
optimizer.step()
running_loss+=loss.item()
if batch_idx%300==299:
print('[%d %5d] loss:%.3f'%(epoch + 1, batch_idx + 1, running_loss / 300))
losses.append(running_loss / 300)
running_loss=0
def test():
correct=0
total=0
with torch.no_grad():
for data in test_loader:
images,labels=data
outputs=model(images)
_,predicted=torch.max(outputs.data,dim=1)
total+=labels.size(0)
correct+=(predicted==labels).sum().item()
acc=100*correct/total
print('Accuracy on test set:%0.3f'%(acc))
accs.append(acc)
if __name__=='__main__':
for epoch in range(15):
train(epoch)
test()
plt.figure(12)
plt.plot(losses, label='loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Variation of Cross-Entropy Loss')
plt.figure(2)
plt.plot(accs, label='acc')
plt.xlabel('epoch')
plt.ylabel('acc')
plt.title('Accuracy Evolution')
plt.show()
训练过程中损失函数值变化如下:
最终测试集准确率为98.8%,测试集准确率变化如下: