全连接限制图像的尺寸,而卷积则不关心图像尺寸大小,只需要接受输入的通道数,输出的通道数和卷积核大小即可确定图像尺寸的变换过程,即
padding:对输入图片进行填充,一般用0填充,padding=1,代表填充一圈,保证卷积前后的图像尺寸大小一致,padding计算公式如下:
stride步长:指的是卷积核每次滑动的距离大小
本文采用GoogleNet来构建深度网络模型
1. 数据集构建
每个像素点即每条数据中的值范围为0-255,有的数字过大不利于训练且难以收敛,故将其归一化到(0-1)之间
# 数据集处理
transform = transforms.Compose([
transforms.ToTensor(), # 转化成Tensor张量
transforms.Normalize((0.1307,), (0.3081,)) # 归一化处理,将其(0-255)映射到(0-1)
])
# 1.准备数据集
train_dataset = datasets.MNIST(root="../DataSet/mnist",
train=True,
transform=transform,
download=False)
test_dataset = datasets.MNIST(root="../DataSet/mnist",
train=False,
transform=transform,
download=False)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
2. 构建GoogleNet---构造Inception单元
# 定义GoogleNet---构造 Inception 单元---GoogleNet不改变图片的尺寸大小即 w 和 h 不变,只改变其 channel 大小
class GoogleNet(torch.nn.Module):
def __init__(self, input_channels):
super(GoogleNet, self).__init__()
# 第一个分支
self.branch_pool1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
# 第二个分支
self.branch_pool2_1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
self.branch_pool2_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 第三个分支
# ---padding = (k-1)/2 k为卷积核大小, 即可保证卷积后图片大小不变
# ---padding作用: 保证卷积后的图片大小与原图片一致即作用于 w 和 h
# ---同时用0填充,保证足够多的信息量也不存在噪音问题
self.branch_pool3_1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
self.branch_pool3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch_pool3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 第四个分支
self.branch_pool4 = torch.nn.Conv2d(input_channels, 24, kernel_size=1)
# 不管 input_channels 是多少,输出的 channels 是24*3+16=88
def forward(self, x):
branch1 = self.branch_pool1(x) # torch.Size([64, 16, 28, 28])
branch2 = self.branch_pool2_2(self.branch_pool2_1(x)) # torch.Size([64, 24, 28, 28])
branch3 = self.branch_pool3_3(self.branch_pool3_2(self.branch_pool3_1(x))) # torch.Size([64, 24, 28, 28])
branch4 = self.branch_pool4(F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)) # torch.Size([64, 24, 28, 28])
outputs = [branch1, branch2, branch3, branch4]
# GoogleNet 要求每个分支输出的图片通道数可以不一样, 但其他维度的尺寸必须一样,这样才可以保证能做 cat 连接
# 即(batch, channel_branch, width, height)----->(batch, channel_branch, width, height)
# (batch, channels, width, height): 沿着 channel 通道的方向将这些张量连接起来
return torch.cat(outputs, dim=1)
3.采用GoogleNet的神经网络来构建模型
# 2.构建网络模型---模型是针对批次样本的处理情况
class Module(torch.nn.Module):
def __init__(self):
super(Module, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5, bias=False)
self.googleNet1 = GoogleNet(input_channels=10)
self.googleNet2 = GoogleNet(input_channels=20)
self.conv2 = torch.nn.Conv2d(88, 20, kernel_size=5, bias=False)
# 下采样并不改变 channel 数量,只改变图片大小
self.maxPooling = torch.nn.MaxPool2d(2)
self.fc = torch.nn.Linear(1408, 10)
# 卷积---池化---激活函数---GoogleNet---数据扁平化处理---全连接层
def forward(self, x):
size = x.size(0) # torch.Size([64, 1, 28, 28])
x = F.relu(self.maxPooling(self.conv1(x))) # torch.Size([64, 10, 12, 12])
x = self.googleNet1(x) # torch.Size([64, 88, 12, 12])
x = F.relu(self.maxPooling(self.conv2(x))) # torch.Size([64, 20, 4, 4])
x = self.googleNet2(x) # torch.Size([64, 88, 4, 4])
# 数据扁平化处理,为接下来的全连接测做准备
# Flatten data from (64, 88, 4, 4) to (64,1408)
x = x.view(size, -1)
x = self.fc(x)
# 全连接层之后不需要跟激活函数,因为激活函数 softmax 的作用包含在 CrossEntropyLoss 中
# softmax 函数的作用包含在 CrossEntropyLoss 中
return x
4. 构建损失函数和优化器
损失函数采用CrossEntropyLoss
优化器采用 SGD 随机梯度优化算法
# 构造损失器和优化器
# softmax 函数的作用包含在 CrossEntropyLoss 中,交叉熵算法
criterion = torch.nn.CrossEntropyLoss()
opt = optim.SGD(params=model.parameters(), lr=0.01, momentum=0.5)
# 动态更新学习率------每隔step_size : lr = lr * gamma
schedule = optim.lr_scheduler.StepLR(opt, step_size=10, gamma=0.5, last_epoch=-1)
5.完整代码
# -*- codeing = utf-8 -*-
# @Time : 2022/4/12 8:57
# @Software : PyCharm
# 超参数: 训练之前设置的参数
# 模型参数: 训练过程中得到的参数
# Average Pooling: 均值池化---
# network in network:
# 1*1 的卷积核: 单个1*1的卷积后的图片大小不变即:c*w*h---------->1*w*h
# 若需要输出16个通道的图片,则只需要将输出通道设置为16,pytorch自动构建16个通道,1*1的卷积核
# 1*1 的卷积不改变图片尺寸,即c1*w*h---------->c2*w*h
# 1*1 的卷积可以有效降低通道数量,大大减少网络模型的浮点数运算量
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim
# Maxpooling: 最大池化,寻找每个空间的最大值然后组成一个新的图像
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
batch_size = 64
transform = transforms.Compose([
transforms.ToTensor(), # 转化成Tensor张量
transforms.Normalize((0.1307,), (0.3081,)) # 归一化处理,将其(0-255)映射到(0-1)
])
# 1.准备数据集
train_dataset = datasets.MNIST(root="../DataSet/mnist",
train=True,
transform=transform,
download=False)
test_dataset = datasets.MNIST(root="../DataSet/mnist",
train=False,
transform=transform,
download=False)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
# 定义GoogleNet---构造 Inception 单元---GoogleNet不改变图片的尺寸大小即 w 和 h 不变,只改变其 channel 大小
class GoogleNet(torch.nn.Module):
def __init__(self, input_channels):
super(GoogleNet, self).__init__()
# 第一个分支
self.branch_pool1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
# 第二个分支
self.branch_pool2_1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
self.branch_pool2_2 = torch.nn.Conv2d(16, 24, kernel_size=5, padding=2)
# 第三个分支
# ---padding = (k-1)/2 k为卷积核大小, 即可保证卷积后图片大小不变
# ---padding作用: 保证卷积后的图片大小与原图片一致即作用于 w 和 h
# ---同时用0填充,保证足够多的信息量也不存在噪音问题
self.branch_pool3_1 = torch.nn.Conv2d(input_channels, 16, kernel_size=1)
self.branch_pool3_2 = torch.nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch_pool3_3 = torch.nn.Conv2d(24, 24, kernel_size=3, padding=1)
# 第四个分支
self.branch_pool4 = torch.nn.Conv2d(input_channels, 24, kernel_size=1)
# 不管 input_channels 是多少,输出的 channels 是24*3+16=88
def forward(self, x):
branch1 = self.branch_pool1(x) # torch.Size([64, 16, 28, 28])
branch2 = self.branch_pool2_2(self.branch_pool2_1(x)) # torch.Size([64, 24, 28, 28])
branch3 = self.branch_pool3_3(self.branch_pool3_2(self.branch_pool3_1(x))) # torch.Size([64, 24, 28, 28])
branch4 = self.branch_pool4(F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)) # torch.Size([64, 24, 28, 28])
outputs = [branch1, branch2, branch3, branch4]
# GoogleNet 要求每个分支输出的图片通道数可以不一样, 但其他维度的尺寸必须一样,这样才可以保证能做 cat 连接
# 即(batch, channel_branch, width, height)----->(batch, channel_branch, width, height)
# (batch, channels, width, height): 沿着 channel 通道的方向将这些张量连接起来
return torch.cat(outputs, dim=1)
# 2.构建网络模型---模型是针对批次样本的处理情况
class Module(torch.nn.Module):
def __init__(self):
super(Module, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5, bias=False)
self.googleNet1 = GoogleNet(input_channels=10)
self.googleNet2 = GoogleNet(input_channels=20)
self.conv2 = torch.nn.Conv2d(88, 20, kernel_size=5, bias=False)
# 下采样并不改变 channel 数量,只改变图片大小
self.maxPooling = torch.nn.MaxPool2d(2)
self.fc = torch.nn.Linear(1408, 10)
# 卷积---池化---激活函数---GoogleNet---数据扁平化处理---全连接层
def forward(self, x):
size = x.size(0) # torch.Size([64, 1, 28, 28])
x = F.relu(self.maxPooling(self.conv1(x))) # torch.Size([64, 10, 12, 12])
x = self.googleNet1(x) # torch.Size([64, 88, 12, 12])
x = F.relu(self.maxPooling(self.conv2(x))) # torch.Size([64, 20, 4, 4])
x = self.googleNet2(x) # torch.Size([64, 88, 4, 4])
# 数据扁平化处理,为接下来的全连接测做准备
# Flatten data from (64, 88, 4, 4) to (64,1408)
x = x.view(size, -1)
x = self.fc(x)
# 全连接层之后不需要跟激活函数,因为激活函数 softmax 的作用包含在 CrossEntropyLoss 中
# softmax 函数的作用包含在 CrossEntropyLoss 中
return x
model = Module().to(device)
# 3.构造损失器和优化器
# softmax 函数的作用包含在 CrossEntropyLoss 中,交叉熵算法
criterion = torch.nn.CrossEntropyLoss()
opt = optim.SGD(params=model.parameters(), lr=0.01, momentum=0.5)
# 动态更新学习率------每隔step_size : lr = lr * gamma
schedule = optim.lr_scheduler.StepLR(opt, step_size=10, gamma=0.5, last_epoch=-1)
# 4.训练数据集
def train():
running_loss = 0
for batch_idx, (inputs, target) in enumerate(train_loader, 0):
inputs, target = inputs.to(device), target.to(device)
opt.zero_grad()
y_pred_data = model(inputs)
loss = criterion(y_pred_data, target)
loss.backward()
opt.step()
running_loss += loss.item()
if batch_idx % 300 == 299:
print("[%5d, %5d] loss: %.5f" % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss == 0.0
# 5.测试数据集
def verify():
correct = 0
total = 0
with torch.no_grad(): # 该语句下的所有tensor在进行反向传播时,不会被计算梯度
for (images, labels) in test_loader:
images, labels = images.to(device), labels.to(device)
# 数据进入模型进行计算
outputs = model(images)
# 沿着维度为1的方向(行方向) 寻找每行最大元素的值与其下标
_, predicted = torch.max(outputs.data, dim=1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print("==============================")
print("Accuracy on test set: %d%%" % (100 * correct / total))
print("==============================")
if __name__ == '__main__':
for epoch in range(15):
train()
verify()
# GoogleNet: 分支卷积--->cat(dim=1:channels)---全连接
# 使用 卷积 + GoogleNet + 全连接 的神经网络的准确率在 99% 左右, 同时减少了参数量和计算量