torch=1.6.0+cu101
CUDA: 10.1
cuDNN: 7.6.2
在许多应用程序中,我们处理的是具有多个通道的图像。一个典型的例子是RGB图像。每个RGB通道强调原始图像的不同方面,如图1所示。
多通道卷积就如图2所示。每个核被应用到前一层的输入通道上,以生成一个输出通道。这是一个核扩展的过程。我们对所有核重复这样的过程,以生成多个通道。然后将这些通道中的每一个相加,形成一个单独的输出通道。下图过程应该更清晰。
这里输入层是一个5 x 5 x 3矩阵,有3个通道。滤波器是一个3 x 3 x 3矩阵。首先,滤波器中的每个核分别应用于输入层中的三个通道。进行三次卷积,产生3个尺寸为3 x 3的通道。
然后将这三个通道相加(元素相加),形成一个单通道(3 x 3 x 1)。该通道是使用滤波器(3 x 3 x 3矩阵)对输入层(5 x 5 x 3矩阵)进行卷积的结果。
LeNet 是卷积神经网络的奠基人 Yann LeCun 在1998 年提出的,用于解决手写数字识别的视觉任务。它的结构非常简单,我们通过这个简单的网络来学习时下最为流行的卷积神经网络结构。如今各大深度学习框架中使用的 LeNet 都是经过简化改进过的 LeNet-5N(5 表示网络具有5个层),如图1所示,和原始的 LeNet 有些许不同,激活函数数也从原来的tanh 改为现在常用的 ReLU。
如今的 LeNet 网络与卷积层之后紧跟池化层再接ReLU 层的套路不同,而是卷积层 1一ReLU一池化层1一卷积层 2一 ReLU一 池化层2再接全连接层,但卷积层后紧接池化层的模式依旧不变。在PyTorch 中默认使用的padding模式是 valid而不是 same (same 指的是输人特征图和输出特征图的尺寸保持一致,vali模式的输出特征图经过滤波器后可能会变小)。
import torch
import torch.nn.functional as F
from torchvision import datasets, transforms
def Conv2d(x, weight, bias, stride, padding):
'''
x:输入特征图
weight:卷积核
bias:偏置值
stride:步长
padding:填充
'''
# 批样本量(batch size), 输入通道,输入的长和宽
n, c, h_in, w_in = x.shape
# 输出通道,输入通道,卷积核尺寸(k, j)
d, c, k, j = weight.shape
if padding != 0: # padding不等于0时,做补零操作
x_padding = torch.zeros(n, c, h_in + 2*padding, w_in + 2*padding).to(x.device)
x_padding[:, :, padding:-padding, padding:-padding] = x # 对输入进行补零操作
if padding == 0: # padding等于0时,不做补零操作
x_padding = x
x_padding = x_padding.unfold(2, k, stride)# 按照滑动窗展开
x_padding = x_padding.unfold(3, j, stride)
out = torch.einsum( # 按照滑动窗相乘,并将所有输入通道上卷积结果相加
'nchwkj, dckj->ndhw',
x_padding, weight
)
out = out + bias.view(1, -1, 1, 1) # 添加偏置值
return out
def ReLU(x):
out = torch.clamp(x, min=0) # 截断元素值至[0, x]之间
return out
def Linear(x, weight, bias):
out = torch.matmul(x, weight) + bias.view(1, -1) # 做矩阵乘法,加上偏置值
return out
def model(x, params):
x = Conv2d(x=x, weight=params[0], bias=params[1], stride=1, padding=2)
x = F.max_pool2d_with_indices(input=x, kernel_size=2, stride=2)[0] # max_pool2d_with_indices函数返回一个元组,元组内是一个tensor类型
x = Conv2d(x=x, weight=params[2], bias=params[3], stride=1, padding=2)
x = ReLU(x)
x = F.max_pool2d_with_indices(input=x, kernel_size=2, stride=2)[0]
x = x.view(x.size(0), -1)
x = Linear(x, params[4], params[5])
x = ReLU(x)
x = Linear(x, params[6], params[7])
return x
init_std = 0.1
params = [
torch.randn(20,1,5,5) * init_std, # Conv1的卷积核(weight)与偏置值(bias)
torch.zeros(20),
torch.randn(50,20,5,5) * init_std, # Conv2的卷积核(weight)与偏置值(bias)
torch.zeros(50),
torch.randn(2450, 500) * init_std, # Linear1的weight与偏置值(bias)
torch.zeros(500),
torch.randn(500, 10) * init_std, # Linear2的weight与偏置值(bias)
torch.zeros(10)
]
for p in params: # 设置所有的参数可导便于训练
p.requires_grad = True
batch_size_train = 64 # 训练批样本量
batch_size_test = 64 # 测试批样本量
train_dataset = datasets.MNIST(root="./data/mnist", # 数据集下载后的存放路径
train=True,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
]),
download=True
)
test_dataset = datasets.MNIST(root="./data/mnist", # 数据集下载后的存放路径
train=False,
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
])
)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
# # 从数据集中每次抽出batch_size个样本
batch_size=batch_size_train,
shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size_test,
shuffle=False)
lr = 0.01 # 学习率
epochs = 10 # 训练周期
log_interval = 100 # 显示训练结果间隔
for epoch in range(epochs):
for idx, (data, label) in enumerate(train_loader):
output = model(data, params) # 前向计算
loss = F.cross_entropy(output, label) # 计算交叉熵损失函数
for p in params:
if p.grad is not None:
p.grad.zero_() # 注意:一定在反向传播前清除梯度值
loss.backward() # 反向传播
for p in params:
p.data = p.data - lr * p.grad.data # SGD更新参数,只能用.data,否则是对于可微变量的赋值 破坏前向计算,导致不可微分
if idx % log_interval == 0:
print("Epoch %3d [%3d/%3d]\tLoss: %.4f" % (epoch, idx, len(train_loader), loss.item()))
correct_num = 0
total_num = 0
with torch.no_grad(): # 测试时关闭自动微分
for data, label in test_loader:
output = model(data, params)
pred = output.max(1)[1] # 求出置信度最高的结果,按行求最大值
# 将预测结果predicts与标签labels对比(两个数组比较相同元素个数),求出预测结果predicts中与标签labels相同的元素的个数
correct_num += (pred == label).sum().item()
total_num += len(data)
acc = correct_num / total_num # 计算测试集正确率 输出测试结果
print("...Testing @ Epoch %03d\tAcc: %.4f" % (epoch, acc))
直到训练循环结束。可以看到每一轮测试的准确率在逐步提升。以我的实验结果来看,10轮训练周期后就可以达到 98%的淮确率。大家可以自我探索网络结构和超参数设置,训练达到更高准确率的水平。
链接: https://pan.baidu.com/s/1jgbH_H5oviSStlpHqJlL4w
提取码: 47id