文章目录
VGG16 是一种经典的卷积神经网络(CNN)架构,由牛津大学的 Visual Geometry Group(VGG)提出,特别是在 2014 年的 ImageNet 挑战中表现出色。VGG16 具有简洁而有效的结构,广泛应用于图像分类任务。CIFAR-10(Canadian Institute For Advanced Research 10)是一个常用的小型图像分类数据集,用于训练和测试机器学习模型。它包含了 10 个不同类别的图像,每个类别有 6,000 张图像。
本文使用CIFAR-10数据集,训练VGG16模型,学习实践神经网络模型训练的过程和原理。
VGG16_CIFAR-10 训练测试代码
代码实现了一个基于 VGG16 模型的图像分类任务,使用 CIFAR-10 数据集进行训练和测试
1. 导入必要的库
from torchvision import models
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import time
torch是 PyTorch 库,提供了各种用于构建和训练神经网络的功能。torchvision是一个常用的图像处理库,提供了预训练模型和数据集等。models:从torchvision中导入模型,VGG16 就是从这里加载的。datasets:用于加载常见的数据集(如 CIFAR-10、ImageNet 等)。transforms:用于定义图像预处理操作。DataLoader:帮助将数据集分批次加载,用于训练和评估模型。time:用于计算每个 epoch 的时间。
2. 设置线程数
torch.set_num_threads(4)
- 设置每个进程使用的线程数为 4,这有助于在多核 CPU 上提高并行性能,尤其在数据加载和训练时。
3. 数据预处理
transform = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
transforms.Resize(224):将图像调整为 224x224 的大小,适应 VGG16 模型的输入要求。transforms.ToTensor():将图像转换为 Tensor 格式,并将像素值从[0, 255]范围转换到[0.0, 1.0]。transforms.Normalize(mean, std):对图像进行标准化,将每个通道(RGB)的均值和标准差标准化。这里使用的是在 ImageNet 数据集上计算的均值和标准差,常用于大多数预训练模型(如 VGG16)。
4. 加载 CIFAR-10 数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)
datasets.CIFAR10:加载 CIFAR-10 数据集,包含 10 类图像,每类 6,000 张图像。train=True表示加载为训练集,train=False表示加载为测试集。transform=transform:应用前面定义的图像预处理。DataLoader:将数据集分批加载,batch_size=64表示每次加载 64 张图像。shuffle=True表示训练集每个 epoch 重新打乱数据,num_workers=0表示数据加载使用主线程(你可以根据机器的 CPU 核数调整这个值以提高性能)。
5. 加载 VGG16 模型
model = models.vgg16(weights=None)
model.classifier[6] = nn.Linear(model.classifier[6].in_features, 10)
models.vgg16(weights=None):加载没有预训练权重的 VGG16 模型。weights=None表示不加载 ImageNet 上的预训练权重。model.classifier[6]:VGG16 的最后一层是一个全连接层,model.classifier[6]就是最后一层,默认输出 1000 类(ImageNet 上的类别数)。这里将其修改为输出 10 类,以适应 CIFAR-10 数据集。
6. 设置设备(GPU 或 CPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
- 通过
torch.cuda.is_available()检查是否有可用的 GPU,如果有则使用 GPU,否则使用 CPU。 model.to(device):将模型移到指定设备(GPU 或 CPU)。
7. 设置损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss():使用交叉熵损失函数,这通常用于多分类任务。optimizer = optim.Adam(model.parameters(), lr=0.001):使用 Adam 优化器,学习率为 0.001。Adam 是一种自适应学习率优化算法,通常能较好地处理大规模数据集。
8. 训练函数
def train(model, train_loader, criterion, optimizer, num_epochs=10):
model.train()
epoch_times = []
for epoch in range(num_epochs):
start_time = time.time()
running_loss = 0.0
correct = 0
total = 0
for i, (inputs, labels) in enumerate(train_loader):
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
# 每个 step 打印一次
if (i + 1) % 10 == 0:
step_loss = running_loss / (i + 1)
step_acc = 100. * correct / total
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {step_loss:.4f}, Accuracy: {step_acc:.2f}%')
epoch_loss = running_loss / len(train_loader)
epoch_acc = 100. * correct / total
epoch_end_time = time.time()
epoch_duration = epoch_end_time - start_time
epoch_times.append(epoch_duration)
print("*" * 50)
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.4f}, Accuracy: {epoch_acc:.2f}%, Time: {epoch_duration:.2f}s')
print("*" * 50)
avg_epoch_time = sum(epoch_times) / num_epochs
print(f'\nAverage Epoch Time: {avg_epoch_time:.2f}s')
model.train():将模型设置为训练模式,启用 Dropout 和 BatchNorm 等训练时特有的行为。 丢弃(Dropout)是指在训练的过程中,隐藏层的神经元会有一定数量的被丢弃掉,可以防止网络的过度拟合;BatchNorm 是对每一层的输入进行标准化,使其在训练过程中保持均值为 0,方差为 1,从而避免不同层的输入数据分布发生变化,加速收敛。optimizer.zero_grad():清空之前的梯度,以防止它们在计算时累加。在 PyTorch 等深度学习框架中,梯度是累加的,在每次进行反向传播时,新的梯度会加到之前计算出的梯度上,所以需要手动清除。loss.backward():反向传播,计算梯度。反向传播(backpropagation)是计算梯度的过程,在每次计算损失(loss)并执行反向传播时,PyTorch 会自动计算出每个参数的梯度,并将它们累积到 .grad 属性中。optimizer.step():更新模型的参数。- 计算并打印每个 batch 的损失和准确率,输出每个 epoch 的平均损失、准确率和所用时间。
9. 测试函数
def test(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
accuracy = 100. * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')
model.eval():将模型设置为评估模式,禁用 Dropout 和 BatchNorm 等训练时特有的行为。model.train() 会激活训练模式,启用 Dropout 层(即随机丢弃一部分神经元)以及使用当前 mini-batch 的均值和方差来标准化数据的 BatchNorm 层。(实测这两个模式的训练时长差不多)with torch.no_grad():禁用梯度计算,节省内存和计算资源。- 计算并输出测试集的准确率。
10. 训练和测试模型
train(model, train_loader, criterion, optimizer, num_epochs=10)
test(model, test_loader)
train():训练模型 10 个 epoch。test():在训练完成后,对模型进行测试并输出准确率。
11. 保存模型
# 保存模型权重
torch.save(model.state_dict(), 'vgg16_model.pth')
# 保存整个模型(包括结构和权重)
torch.save(model, 'vgg16_full_model.pth')
完整代码
from torchvision import models
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import time
# 设置每个进程使用的线程数
torch.set_num_threads(4)
# 数据预处理
transform = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
# 加载数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=0)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=0)
# 直接加载没有预训练权重的 VGG16 模型
model = models.vgg16(weights=None)
# 最后一层全连接层的输出类别数为 10(CIFAR-10 有 10 类)
model.classifier[6] = nn.Linear(model.classifier[6].in_features, 10)
# 使用 GPU 如果可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 设置损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练函数
def train(model, train_loader, criterion, optimizer, num_epochs=10):
model.train()
epoch_times = []
for epoch in range(num_epochs):
start_time = time.time()
running_loss = 0.0
correct = 0
total = 0
for i, (inputs, labels) in enumerate(train_loader):
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
# 每个 step 打印一次
if (i + 1) % 10 == 0: # 每 10 个 batch 打印一次
step_loss = running_loss / (i + 1)
step_acc = 100. * correct / total
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {step_loss:.4f}, Accuracy: {step_acc:.2f}%')
epoch_loss = running_loss / len(train_loader)
epoch_acc = 100. * correct / total
epoch_end_time = time.time()
epoch_duration = epoch_end_time - start_time
epoch_times.append(epoch_duration)
print("*" * 50)
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {epoch_loss:.4f}, Accuracy: {epoch_acc:.2f}%, Time: {epoch_duration:.2f}s')
print("*" * 50)
avg_epoch_time = sum(epoch_times) / num_epochs
print(f'\nAverage Epoch Time: {avg_epoch_time:.2f}s')
# 测试函数
def test(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
accuracy = 100. * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')
# 训练和测试模型
train(model, train_loader, criterion, optimizer, num_epochs=10)
test(model, test_loader)
# 代码用于学习模型训练过程与原理,故不保存模型
运行训练脚本
在 PyCharm 中选择之前创建的 Anaconda 环境:
打开 PyCharm ,创建一个新的项目。到 Python 解释器设置,在先前配置的解释器中中,选择conda环境,选择envs目录下之前配置好的pytorch环境。

在pycharm中直接运行模型训练即可


每训练完一个minibatch,会输出损失值和正确率,训练完一个epoch会输出训练时间

训练过程中,GPU内存的占用即VGG模型的大小,这里是10.8GB