pytorch入门 - VGG16神经网络
1. VGG16背景介绍

VGG-16是由牛津大学Visual Geometry Group(VGG)在2014年提出的深度卷积神经网络模型,它在当年的ImageNet大规模视觉识别挑战赛(ILSVRC)中取得了优异的成绩。
VGG-16的主要贡献在于展示了通过使用更小的卷积核(3×3)和增加网络深度可以显著提升模型性能。
VGG网络系列包括VGG-11、VGG-13、VGG-16和VGG-19等不同深度的变体,其中VGG-16是最为流行的一个版本。"16"表示网络中包含权重参数的层数(13个卷积层和3个全连接层)。
VGG网络的主要特点包括:
- 全部使用3×3的小卷积核
- 通过堆叠多个卷积层增加网络深度
- 每经过一个池化层,特征图数量翻倍
- 简单而统一的架构设计
2. VGG16架构详解

VGG-16的网络架构可以分为两个主要部分:卷积层部分和全连接层部分。
卷积层部分
VGG-16包含5个卷积块,每个块后接一个最大池化层:
- Block1: 2个卷积层(64通道) + 最大池化
- Block2: 2个卷积层(128通道) + 最大池化
- Block3: 3个卷积层(256通道) + 最大池化
- Block4: 3个卷积层(512通道) + 最大池化
- Block5: 3个卷积层(512通道) + 最大池化
所有卷积层都使用3×3的卷积核,padding=1保持空间尺寸不变,激活函数使用ReLU。池化层使用2×2的窗口,stride=2,将特征图尺寸减半。
全连接层部分
卷积层后接3个全连接层:
- 第一个全连接层:4096个神经元
- 第二个全连接层:4096个神经元
- 第三个全连接层:1000个神经元(对应ImageNet的1000类)
在训练时,全连接层使用了Dropout(0.5)来防止过拟合。
3. 每层参数计算详解
让我们详细计算VGG16各层的参数数量。假设输入为224×224×3的RGB图像。
卷积层参数计算
卷积层的参数数量计算公式为:
参数数量 = (卷积核宽度 × 卷积核高度 × 输入通道数 + 1) × 输出通道数
(其中+1是偏置项)
以Block1的第一个卷积层为例:
- 输入通道:3
- 输出通道:64
- 卷积核:3×3
参数数量 = (3×3×3 + 1)×64 = 28×64 = 1,792
全连接层参数计算
全连接层的参数数量计算公式为:
参数数量 = (输入特征数 + 1) × 输出特征数
以第一个全连接层为例:
- 输入特征数:512×7×7 = 25,088
- 输出特征数:4096
参数数量 = (25,088 + 1)×4,096 = 102,764,544
VGG16总参数
整个VGG16网络约有1.38亿参数,其中大部分(约1.24亿)来自第一个全连接层。
4. 代码实现详解
以下是完整的VGG16实现代码,我们将逐部分解释:
模型定义 (models.py)
import os
import sys
sys.path.append(os.getcwd())
import torch
from torch import nn
from torchsummary import summary
class VGG16(nn.Module):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Block1: 2个卷积层(64通道) + 最大池化
self.block1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# Block2: 2个卷积层(128通道) + 最大池化
self.block2 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# Block3: 3个卷积层(256通道) + 最大池化
self.block3 = nn.Sequential(
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# Block4: 3个卷积层(512通道) + 最大池化
self.block4 = nn.Sequential(
nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# Block5: 3个卷积层(512通道) + 最大池化
self.block5 = nn.Sequential(
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
# 全连接层部分
self.block6 = nn.Sequential(
nn.Flatten(),
nn.Linear(in_features=512 * 7 * 7, out_features=4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 10),
)
# 权重初始化
for m in self.modules():
print(m)
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")
if m.bias is not None:
nn.init.constant_(m.bias, 0)
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
def forward(self, x):
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
x = self.block4(x)
x = self.block5(x)
x = self.block6(x)
return x
if __name__ == "__main__":
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = VGG16().to(device=device)
print(model)
summary(model, input_size=(1, 224, 224), device=str(device))
训练代码 (train.py)
import os
import sys
sys.path.append(os.getcwd())
import time
from torchvision.datasets import FashionMNIST
from torchvision import transforms
from torch.utils.data import DataLoader, random_split
import numpy as np
import matplotlib.pyplot as plt
import torch
from torch import nn, optim
import copy
import pandas as pd
from VGG16_model.model import VGG16
def train_val_date_load():
train_dataset = FashionMNIST(
root="./data",
train=True,
download=True,
transform=transforms.Compose(
[
transforms.Resize(size=224),
transforms.ToTensor(),
]
),
)
train_date, val_data = random_split(
train_dataset,
[
int(len(train_dataset) * 0.8),
len(train_dataset) - int(len(train_dataset) * 0.8),
],
)
train_loader = DataLoader(
dataset=train_date, batch_size=16, shuffle=True, num_workers=1
)
val_loader = DataLoader(
dataset=val_data, batch_size=16, 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)
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),
)
)
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),
)
)
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/vgg16_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)
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)
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/vgg16_net_output.png")
if __name__ == "__main__":
traindatam, valdata = train_val_date_load()
result = train_model_process(VGG16(), traindatam, valdata, 10)
matplot_acc_loss(result)
测试代码 (test.py)
import os
import sys
sys.path.append(os.getcwd())
import torch
from torch.utils.data import DataLoader, random_split
from torchvision import datasets, transforms
from torchvision.datasets import FashionMNIST
from VGG16_model.model import VGG16
def test_data_load():
test_dataset = FashionMNIST(
root="./data",
train=False,
download=True,
transform=transforms.Compose(
[
transforms.Resize(size=224),
transforms.ToTensor(),
]
),
)
test_loader = DataLoader(
dataset=test_dataset, batch_size=16, 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 = VGG16()
model.load_state_dict(torch.load("./models/vgg16_net_best_model.pth"))
test_model_process(model, test_loader)
5. 代码实现关键点解析
模型定义关键点
卷积块设计:每个卷积块包含多个卷积层,使用3×3卷积核和ReLU激活函数,最后接一个2×2的最大池化层。
权重初始化:使用Kaiming初始化方法初始化卷积层权重,使用正态分布初始化全连接层权重。
输入适配:原始VGG16设计用于224×224×3的输入,这里调整为224×224×1以适应FashionMNIST数据集。
训练过程关键点
数据加载:使用FashionMNIST数据集,调整大小为224×224以适应VGG16。
训练循环:包含完整的训练和验证过程,记录每轮的损失和准确率。
模型保存:保存验证集上表现最好的模型参数。
可视化:绘制训练和验证的损失及准确率曲线。
测试过程关键点
模型加载:从保存的文件加载最佳模型参数。
评估模式:使用
model.eval()和torch.no_grad()确保评估过程不计算梯度。准确率计算:统计所有测试样本的预测准确率。
6. 总结
VGG-16是一个经典的深度卷积神经网络,其主要特点包括:
简单统一的设计:全部使用3×3卷积核和2×2池化层,结构清晰。
深度优势:通过增加网络深度提高了特征提取能力。
小卷积核优势:多个小卷积核堆叠可以模拟大感受野,同时减少参数数量。
广泛应用:虽然现在有更高效的网络,但VGG16仍广泛用于特征提取和迁移学习。
pytorch入门 - VGG16神经网络的更多相关文章
- Pytorch入门随手记
Pytorch入门随手记 什么是Pytorch? Pytorch是Torch到Python上的移植(Torch原本是用Lua语言编写的) 是一个动态的过程,数据和图是一起建立的. tensor.dot ...
- pytorch 入门指南
两类深度学习框架的优缺点 动态图(PyTorch) 计算图的进行与代码的运行时同时进行的. 静态图(Tensorflow <2.0) 自建命名体系 自建时序控制 难以介入 使用深度学习框架的优点 ...
- 超简单!pytorch入门教程(五):训练和测试CNN
我们按照超简单!pytorch入门教程(四):准备图片数据集准备好了图片数据以后,就来训练一下识别这10类图片的cnn神经网络吧. 按照超简单!pytorch入门教程(三):构造一个小型CNN构建好一 ...
- pytorch入门2.0构建回归模型初体验(数据生成)
pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...
- pytorch入门2.1构建回归模型初体验(模型构建)
pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...
- Pytorch入门——手把手教你MNIST手写数字识别
MNIST手写数字识别教程 要开始带组内的小朋友了,特意出一个Pytorch教程来指导一下 [!] 这里是实战教程,默认读者已经学会了部分深度学习原理,若有不懂的地方可以先停下来查查资料 目录 MNI ...
- Pytorch入门上 —— Dataset、Tensorboard、Transforms、Dataloader
本节内容参照小土堆的pytorch入门视频教程.学习时建议多读源码,通过源码中的注释可以快速弄清楚类或函数的作用以及输入输出类型. Dataset 借用Dataset可以快速访问深度学习需要的数据,例 ...
- Pytorch入门中 —— 搭建网络模型
本节内容参照小土堆的pytorch入门视频教程,主要通过查询文档的方式讲解如何搭建卷积神经网络.学习时要学会查询文档,这样会比直接搜索良莠不齐的博客更快.更可靠.讲解的内容主要是pytorch核心包中 ...
- Pytorch入门下 —— 其他
本节内容参照小土堆的pytorch入门视频教程. 现有模型使用和修改 pytorch框架提供了很多现有模型,其中torchvision.models包中有很多关于视觉(图像)领域的模型,如下图: 下面 ...
- 第一章:PyTorch 入门
第一章:PyTorch 入门 1.1 Pytorch 简介 1.1.1 PyTorch的由来 1.1.2 Torch是什么? 1.1.3 重新介绍 PyTorch 1.1.4 对比PyTorch和Te ...
随机推荐
- Ubuntu安装配置redis
更新安装相关依赖库 下面步骤一步一步来 sudo apt update sudo apt install build-essential sudo apt-get install manpages-d ...
- Golang 入门 : Go语言介绍
简介 Go 语言又称 Golang,由 Google 公司于 2009 年发布,近几年伴随着云计算.微服务.分布式的发展而迅速崛起,跻身主流编程语言之列,和 Java 类似,它是一门静态的.强类型的. ...
- nodejs调用shell
shelljs https://github.com/shelljs/shelljs 实例 var shell = require('shelljs'); if (!shell.which('git' ...
- lombok用法
加入 maven 依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lo ...
- spring的控制反转(IoC)
ioc的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系 解耦的思路: 第一步:使用反射来创建对象,而避免使用new关键字. 第二步:通过读取配置文件来获取要创建的对象全限定类名
- TortoiseGit安装(Windows10环境)
1.前往官网下载 https://tortoisegit.org/download/ 根据自己系统位数进行选择 2.双击运行 3.默认即可,第一个是基于PuTTY的SSH客户端,与Windows兼容更 ...
- 康谋方案 | 康谋BRICK2与车载以太网设备轻松集成
导读:在当下,汽车行业在安全性.舒适性.智能和万物互联等方面彻底改变了传统车辆的定义.随着这一趋势,汽车行业逐渐开始采用车载以太网来进行车内数据通讯,比如100Base-T1.1000Base-T1, ...
- 如何0基础学stm32?
如何0基础学stm32? 作为一个混迹嵌入式领域十余年的老兵,每次看到"0基础学STM32"这样的提问,我都忍不住想笑,又有些无奈.这就像问"如何0基础学开飞机" ...
- 探秘Transformer系列之(29)--- DeepSeek MoE
探秘Transformer系列之(29)--- DeepSeek MoE 目录 探秘Transformer系列之(29)--- DeepSeek MoE 0x00 概述 0x01 难点 1.1 负载均 ...
- JDK的SPI有什么缺陷?dubbo做了什么改进?
JDK的SPI机制的缺点 ⽂件中的所有类都会被加载且被实例化.这样也就导致获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类.如果不想用某些实现类, ...