Pytorch深度学习-用GoogleNet训练MNIST数据集

全连接限制图像的尺寸,而卷积则不关心图像尺寸大小,只需要接受输入的通道数,输出的通道数和卷积核大小即可确定图像尺寸的变换过程,即

H_{out} = {H_{in}+2*padding-kernalsize\over stride}+1.
W_{out} = {W_{in}+2*padding-kernalsize\over stride}+1.
padding:对输入图片进行填充,一般用0填充,padding=1,代表填充一圈,保证卷积前后的图像尺寸大小一致,padding计算公式如下:
padding = {kernalsize-1\over 2}.

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% 左右, 同时减少了参数量和计算量




6.结果展示

result.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容