GoogLeNet 是 Google 在 2014 年 ILSVRC(ImageNet Large Scale Visual Recognition Challenge)比赛中提出的一种深度卷积神经网络模型,其关键创新在于引入了 Inception 模块,大大提高了网络的参数利用率与计算效率。

本文将通过完整的 PyTorch 实现,从背景、结构、参数计算、源码讲解四个方面系统阐述 GoogLeNet,并附带可运行代码与注释。

GoogLeNet 背景简介

GoogLeNet 出自论文《Going Deeper with Convolutions》,是 ILSVRC-2014 冠军模型,准确率远超同期网络如 AlexNet 与 VGG。

该网络最核心的创新是 Inception 模块,它将不同大小的卷积核(如 1x1、3x3、5x5)及池化操作并行组合,使得网络能提取多尺度特征,同时大幅减少参数量(尤其是 5x5 卷积前的降维 1x1 卷积)。

GoogLeNet 网络结构解析

GoogLeNet 并不是单纯的层叠卷积+池化,它引入的 Inception 模块 结构如下:

  • 1x1 卷积分支:直接进行 1x1 卷积;

  • 3x3 卷积分支:1x1 卷积降维 → 3x3 卷积;

  • 5x5 卷积分支:1x1 卷积降维 → 5x5 卷积;

  • 池化分支:3x3 最大池化 → 1x1 卷积。

每个 Inception 的输出会在通道维度上拼接,形成更丰富的特征表达。

整网包括如下模块组合:

  1. 卷积+池化(b1、b2)

  2. 多组 Inception + 池化(b3、b4、b5)

  3. 自适应平均池化 → 全连接层输出分类结果(b6)

GoogLeNet 每层参数计算分析(以 b1 为例)

以第一层为例:

nn.Conv2d(1, 64, 7, 2, 3)
  • 输入通道数:1(灰度图)

  • 输出通道数:64

  • 卷积核大小:7x7

  • 步幅:2

  • 填充:3

输出尺寸为:

H_out = floor((224 + 2×3 - 7)/2) + 1 = 112
W_out = 112
输出张量尺寸 = [64, 112, 112]

池化层:

nn.MaxPool2d(3, 2, 1)

输出尺寸变为:

H_out = floor((112 + 2×1 - 3)/2) + 1 = 56
W_out = 56
输出张量尺寸 = [64, 56, 56]

类似方法可计算各层形状,尤其要注意每个 Inception 的输出通道数是四个分支通道数的 加和

GoogLeNet 模型完整源码解释(含注释)

model.py - GoogLeNet 模型定义
import torch  # 导入PyTorch主库
from torch import nn # 导入神经网络模块
from torchsummary import summary # 导入模型结构摘要工具 # 定义Inception模块
class Inception(nn.Module):
def __init__(
self, in_channels, out1x1, out3x3red, out3x3, out5x5red, out5x5, pool_proj
):
super(Inception, self).__init__() # 调用父类构造函数
self.ReLu = nn.ReLU() # 定义ReLU激活函数 self.branch1x1 = nn.Conv2d(in_channels, out1x1, kernel_size=1) # 1x1卷积分支 self.branch3x3 = nn.Sequential(
nn.Conv2d(in_channels, out3x3red, kernel_size=1), # 1x1卷积降维
nn.ReLU(), # 激活
nn.Conv2d(out3x3red, out3x3, kernel_size=3, padding=1), # 3x3卷积
) self.branch5x5 = nn.Sequential(
nn.Conv2d(in_channels, out5x5red, kernel_size=1), # 1x1卷积降维
nn.ReLU(), # 激活
nn.Conv2d(out5x5red, out5x5, kernel_size=5, padding=2), # 5x5卷积
) self.branch_pool = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1), # 3x3最大池化
nn.Conv2d(in_channels, pool_proj, kernel_size=1), # 1x1卷积
) def forward(self, x):
p1 = self.ReLu(self.branch1x1(x)) # 1x1卷积分支前向传播
p2 = self.ReLu(self.branch3x3(x)) # 3x3卷积分支前向传播
p3 = self.ReLu(self.branch5x5(x)) # 5x5卷积分支前向传播
p4 = self.ReLu(self.branch_pool(x)) # 池化分支前向传播
outputs = [p1, p2, p3, p4] # 合并所有分支输出
return torch.cat(outputs, 1) # 在通道维度拼接 # 定义GoogLeNet主结构
class GoogLeNet(nn.Module):
def __init__(self, num_classes=10):
super(GoogLeNet, self).__init__() # 调用父类构造函数 self.b1 = nn.Sequential(
nn.Conv2d(1, 64, 7, 2, 3), # 7x7卷积,步长2,填充3
nn.ReLU(), # 激活
nn.MaxPool2d(3, 2, 1) # 3x3最大池化,步长2,填充1
) self.b2 = nn.Sequential(
nn.Conv2d(64, 64, 1), # 1x1卷积
nn.ReLU(), # 激活
nn.Conv2d(64, 192, 3, 1, 1), # 3x3卷积
nn.ReLU(), # 激活
nn.MaxPool2d(3, 2, 1), # 3x3最大池化
) self.b3 = nn.Sequential(
Inception(192, 64, 96, 128, 16, 32, 32), # 第一个Inception模块
Inception(256, 128, 128, 192, 32, 96, 64), # 第二个Inception模块
nn.MaxPool2d(3, 2, 1), # 池化
) self.b4 = nn.Sequential(
Inception(480, 192, 96, 208, 16, 48, 64), # 多个Inception模块
Inception(512, 160, 112, 224, 24, 64, 64),
Inception(512, 128, 128, 256, 24, 64, 64),
Inception(512, 112, 128, 288, 32, 64, 64),
Inception(528, 256, 160, 320, 32, 128, 128),
nn.MaxPool2d(3, 2, 1), # 池化
) self.b5 = nn.Sequential(
Inception(832, 256, 160, 320, 32, 128, 128), # 倒数第二个Inception
Inception(832, 384, 192, 384, 48, 128, 128), # 最后一个Inception
) self.b6 = nn.Sequential(
nn.AdaptiveAvgPool2d((1, 1)), # 自适应平均池化到1x1
nn.Flatten(), # 展平
nn.Linear(1024, num_classes), # 全连接输出
) # 权重初始化
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") # He初始化
if m.bias is not None:
nn.init.constant_(m.bias, 0) # 偏置初始化为0
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01) # 正态分布初始化
if m.bias is not None:
nn.init.constant_(m.bias, 0) # 偏置初始化为0 def forward(self, x):
x = self.b1(x) # 第一阶段
x = self.b2(x) # 第二阶段
x = self.b3(x) # 第三阶段
x = self.b4(x) # 第四阶段
x = self.b5(x) # 第五阶段
x = self.b6(x) # 分类阶段
return x # 返回输出 # 测试模型结构
if __name__ == "__main__":
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 选择设备
model = GoogLeNet().to(device=device) # 实例化模型并移动到设备
print(summary(model, (1, 224, 224))) # 打印模型结构摘要

模型训练代码详解(train.py)

import os  # 操作系统相关
import sys # Python运行环境相关 sys.path.append(os.getcwd()) # 添加当前目录到模块搜索路径 import time # 计时
from torchvision.datasets import FashionMNIST # FashionMNIST数据集
from torchvision import transforms # 数据预处理
from torch.utils.data import (
DataLoader, # 批量数据加载
random_split, # 数据集划分
)
import numpy as np # 数值计算
import matplotlib.pyplot as plt # 绘图
import torch # PyTorch主库
from torch import nn, optim # 神经网络和优化器
import copy # 深拷贝
import pandas as pd # 数据处理 from GoogLeNet_model.model import GoogLeNet # 导入GoogLeNet模型 batch_size = 32 # 批量大小
加载训练集与验证集
def train_val_date_load():  # 加载训练集和验证集
train_dataset = FashionMNIST(
root="./data", # 数据路径
train=True, # 加载训练集
download=True, # 自动下载
transform=transforms.Compose(
[
transforms.Resize(size=224), # 缩放到224x224
transforms.ToTensor(), # 转为Tensor
]
),
) train_date, val_data = random_split(
train_dataset,
[
int(len(train_dataset) * 0.8), # 80%训练集
len(train_dataset) - int(len(train_dataset) * 0.8), # 20%验证集
],
) train_loader = DataLoader(
dataset=train_date,
batch_size=batch_size,
shuffle=True,
num_workers=1, # 训练集加载器
) val_loader = DataLoader(
dataset=val_data,
batch_size=batch_size,
shuffle=True,
num_workers=1, # 验证集加载器
) return train_loader, val_loader # 返回加载器
训练主流程
def train_model_process(model, train_loader, val_loader, epochs=10):  # 训练过程
device = "cuda" if torch.cuda.is_available() else "cpu" # 设备选择
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam优化器
criterion = nn.CrossEntropyLoss() # 交叉熵损失
model.to(device) # 模型转到设备 best_model_wts = copy.deepcopy(model.state_dict()) # 最佳模型参数
best_acc = 0.0 # 最佳准确率
train_loss_all = [] # 训练损失
val_loss_all = [] # 验证损失
train_acc_all = [] # 训练准确率
val_acc_all = [] # 验证准确率 since = time.time() # 训练开始时间 for epoch in range(epochs): # 轮次循环
print(f"Epoch {epoch + 1}/{epochs}") # 当前轮次 train_loss = 0.0 # 本轮训练损失
train_correct = 0 # 本轮训练正确数 val_loss = 0.0 # 本轮验证损失
val_correct = 0 # 本轮验证正确数 train_num = 0 # 本轮训练样本数
val_num = 0 # 本轮验证样本数 for step, (images, labels) in enumerate(train_loader): # 训练集循环
images = images.to(device) # 图片转设备
labels = labels.to(device) # 标签转设备 model.train() # 训练模式 outputs = model(images) # 前向传播
pre_lab = torch.argmax(outputs, dim=1) # 预测标签
loss = criterion(outputs, labels) # 损失计算 optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 参数更新 train_loss += loss.item() * images.size(0) # 累计损失
train_correct += torch.sum(pre_lab == labels.data) # 累计正确数
train_num += labels.size(0) # 累计样本数 print(
"Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Acc:{:.4f}".format(
epoch + 1,
epochs,
step + 1,
len(train_loader),
loss.item(),
torch.sum((pre_lab == labels.data) / batch_size),
)
) for step, (images, labels) in enumerate(val_loader): # 验证集循环
images = images.to(device)
labels = labels.to(device)
model.eval() # 评估模式 with torch.no_grad(): # 不计算梯度
outputs = model(images)
pre_lab = torch.argmax(outputs, dim=1)
loss = criterion(outputs, labels) val_loss += loss.item() * images.size(0)
val_correct += torch.sum(pre_lab == labels.data)
val_num += labels.size(0) print(
"Epoch [{}/{}], Step [{}/{}], Val Loss: {:.4f}, Acc:{:.4f}".format(
epoch + 1,
epochs,
step + 1,
len(val_loader),
loss.item(),
torch.sum((pre_lab == labels.data) / batch_size),
)
) # 记录每轮训练和验证的损失与准确率
train_loss_all.append(train_loss / train_num)
val_loss_all.append(val_loss / val_num)
train_acc = train_correct.double() / train_num
val_acc = val_correct.double() / val_num
train_acc_all.append(train_acc.item())
val_acc_all.append(val_acc.item()) print(
f"Train Loss: {train_loss / train_num:.4f}, Train Acc: {train_acc:.4f}, "
f"Val Loss: {val_loss / val_num:.4f}, Val Acc: {val_acc:.4f}"
) if val_acc_all[-1] > best_acc: # 更新最佳模型
best_acc = val_acc_all[-1]
best_model_wts = copy.deepcopy(model.state_dict()) time_elapsed = time.time() - since # 总耗时
print(
f"Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s\n"
f"Best val Acc: {best_acc:.4f}"
) torch.save(model.state_dict(), "./models/google_net_best_model.pth") # 保存模型
train_process = pd.DataFrame(
data={
"epoch": range(1, epochs + 1), # 轮次
"train_loss_all": train_loss_all, # 训练损失
"val_loss_all": val_loss_all, # 验证损失
"train_acc_all": train_acc_all, # 训练准确率
"val_acc_all": val_acc_all, # 验证准确率
}
) return train_process # 返回训练过程
训练过程可视化
def matplot_acc_loss(train_process):  # 绘制曲线
plt.figure(figsize=(12, 5)) # 画布大小 plt.subplot(1, 2, 1) # 第1个子图
plt.plot(
train_process["epoch"], train_process["train_loss_all"], label="Train Loss"
)
plt.plot(
train_process["epoch"], train_process["val_loss_all"], label="Val Loss"
)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Loss vs Epoch")
plt.legend() plt.subplot(1, 2, 2) # 第2个子图
plt.plot(
train_process["epoch"], train_process["train_acc_all"], label="Train Acc"
)
plt.plot(
train_process["epoch"], train_process["val_acc_all"], label="Val Acc"
)
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.title("Accuracy vs Epoch")
plt.legend() plt.tight_layout() # 自动调整
plt.ion() # 交互模式
plt.show() # 显示图像
plt.savefig("./models/google_net_output.png") # 保存图片
训练主入口
if __name__ == "__main__":  # 主程序入口
traindatam, valdata = train_val_date_load() # 加载数据
result = train_model_process(
GoogLeNet(), traindatam, valdata, 10
) # 训练模型
matplot_acc_loss(result) # 绘制曲线

模型测试代码详解(test.py)

import os  # 操作系统相关
import sys # Python运行环境相关 sys.path.append(os.getcwd()) # 添加当前目录到模块搜索路径
import torch # PyTorch主库
from torch.utils.data import (
DataLoader, # 批量数据加载
random_split, # 数据集划分(未用到)
)
from torchvision import datasets, transforms # torchvision数据集和变换
from torchvision.datasets import FashionMNIST # FashionMNIST数据集
from GoogLeNet_model.model import GoogLeNet # 导入GoogLeNet模型
加载测试集
def test_data_load():  # 加载测试数据
test_dataset = FashionMNIST(
root="./data", # 数据路径
train=False, # 加载测试集
download=True, # 自动下载
transform=transforms.Compose(
[
transforms.Resize(size=224), # 缩放到224x224
transforms.ToTensor(), # 转为Tensor
]
),
) test_loader = DataLoader(
dataset=test_dataset,
batch_size=128,
shuffle=True,
num_workers=1, # 测试集加载器
) return test_loader # 返回加载器 print(test_data_load()) # 打印测试集加载器(调试)
测试过程
def test_model_process(model, test_loader):  # 测试过程
device = "cuda" if torch.cuda.is_available() else "cpu" # 设备选择
model.to(device) # 模型转到设备
model.eval() # 评估模式 correct = 0 # 正确数
total = 0 # 总数 with torch.no_grad(): # 不计算梯度
for images, labels in test_loader: # 测试集循环
images, labels = images.to(device), labels.to(device)
outputs = model(images) # 前向传播
_, predicted = torch.max(outputs, 1) # 预测标签
total += labels.size(0) # 累计总数
correct += torch.sum(predicted == labels.data) # 累计正确数 accuracy = correct / total * 100 # 准确率(百分比)
print(f"Test Accuracy: {accuracy:.2f}%") # 打印准确率
主入口测试
if __name__ == "__main__":  # 主程序入口
test_loader = test_data_load() # 加载测试集
model = GoogLeNet() # 实例化模型
model.load_state_dict(
torch.load("./models/google_net_best_model.pth")
) # 加载模型参数
test_model_process(model, test_loader) # 测试模型

总结

本文完整地实现并解释了 GoogLeNet 模型的架构、参数计算与 PyTorch 训练/测试流程:

  • 结构创新在于 Inception 模块 的多路径设计;

  • 模型参数高效,训练稳定;

  • 适配任何图像分类任务,代码可复用性强。

pytorch入门 - GoogLeNet神经网络的更多相关文章

  1. Pytorch入门随手记

    Pytorch入门随手记 什么是Pytorch? Pytorch是Torch到Python上的移植(Torch原本是用Lua语言编写的) 是一个动态的过程,数据和图是一起建立的. tensor.dot ...

  2. pytorch 入门指南

    两类深度学习框架的优缺点 动态图(PyTorch) 计算图的进行与代码的运行时同时进行的. 静态图(Tensorflow <2.0) 自建命名体系 自建时序控制 难以介入 使用深度学习框架的优点 ...

  3. 超简单!pytorch入门教程(五):训练和测试CNN

    我们按照超简单!pytorch入门教程(四):准备图片数据集准备好了图片数据以后,就来训练一下识别这10类图片的cnn神经网络吧. 按照超简单!pytorch入门教程(三):构造一个小型CNN构建好一 ...

  4. pytorch入门2.0构建回归模型初体验(数据生成)

    pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...

  5. pytorch入门2.1构建回归模型初体验(模型构建)

    pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...

  6. Pytorch入门——手把手教你MNIST手写数字识别

    MNIST手写数字识别教程 要开始带组内的小朋友了,特意出一个Pytorch教程来指导一下 [!] 这里是实战教程,默认读者已经学会了部分深度学习原理,若有不懂的地方可以先停下来查查资料 目录 MNI ...

  7. Pytorch入门上 —— Dataset、Tensorboard、Transforms、Dataloader

    本节内容参照小土堆的pytorch入门视频教程.学习时建议多读源码,通过源码中的注释可以快速弄清楚类或函数的作用以及输入输出类型. Dataset 借用Dataset可以快速访问深度学习需要的数据,例 ...

  8. Pytorch入门中 —— 搭建网络模型

    本节内容参照小土堆的pytorch入门视频教程,主要通过查询文档的方式讲解如何搭建卷积神经网络.学习时要学会查询文档,这样会比直接搜索良莠不齐的博客更快.更可靠.讲解的内容主要是pytorch核心包中 ...

  9. 第一章:PyTorch 入门

    第一章:PyTorch 入门 1.1 Pytorch 简介 1.1.1 PyTorch的由来 1.1.2 Torch是什么? 1.1.3 重新介绍 PyTorch 1.1.4 对比PyTorch和Te ...

  10. [pytorch] Pytorch入门

    Pytorch入门 简单容易上手,感觉比keras好理解多了,和mxnet很像(似乎mxnet有点借鉴pytorch),记一记. 直接从例子开始学,基础知识咱已经看了很多论文了... import t ...

随机推荐

  1. BUUCTF---RSA1

    RSA基础概念 rsa原理: RSA公开密钥密码体制的原理是:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥 RSA算法的具体描述如下: (1) ...

  2. BUUCTF---世上无难事

    1.题目 给出一大串密文,说flag藏在其种并且是32位 2.解题 结合题目所给信息,flag在其中32位,并且语句通顺,想来是移位密码,需要加偏移量Mod的那种,最后数字应该代表了key is XX ...

  3. 离线版nRF Connect for Desktop安装方法

    首先确保两台电脑都安装了nRF Connect for Desktop 先在一台能连网的电脑上安装自己想要的App 然后把APP拷贝到没有网的电脑上 从%USERPROFILE%\.nrfconnec ...

  4. System V信号量 vs. POSIX信号量:核心区别与选型指南

    System V信号量 vs. POSIX信号量:核心区别与选型指南 最近在学习linux系统编程的章节,接触到了两种信号量,所以专门研究了二者的区别,将二者的对比记录于此. 在Linux多线程/进程 ...

  5. 本地学习环境minikube安装

    有感于K8S太强大和自己的太无知,索性来系统学习下K8S.网上一番攻略,起码先得有个本地学习环境,所以安装一个minikube,下面记录安装过程,供有需要的人使用. 看看minikube架构: 我是在 ...

  6. gitlab tortoisegit puttyGen

    使用puttyGen生成公私秘钥注意: 生成后的public key有时会在gitlab识别不出,要多重新生成才行 将puttyGen框中的内容复制进gitlab就行 生成时无需设置密码 选择rsa就 ...

  7. Jmeter参数化总结

    参数化步骤: 1.连接数据库 2.获取account表手机号数据 3.获取手机号个数 4.增加For Each控制器 5.将请求添加至循环控制器里面 脚本:循环登录.jmx 页面如下: 下面主要说明F ...

  8. Bean注入几种方式 (放入Spring容器)

    目录 1.XML方式注入 set方式注入 构造方法注入 2.注解方式注入 @Component + @ComponentScan @Configuration + @Bean + @Component ...

  9. 倍增 & Tarjan 求解LCA

    什么是LCA? 假设我们有一棵树: 1 / \ 2 3 / \ / 4 5 6 对于 \(2\) 和 \(6\) 的LCA,就是最近公共祖先,即为距离 \(2\) 和 \(6\) 最近的两个节点公有的 ...

  10. 代码随想录第一天 | Leecode 704 二分查找、27 移除元素、977 有序数组的平方

    前言 今天是我开始刷Leecode的第一天,同时这也是开通博客园第一篇博客.我希望能在每篇博客中记录下我做出每一道题的过程,为此我想先说明一下我的博客内容的结构. 题目描述:首先说明题目的要求以及测试 ...