AlexNet是一个深度卷积神经网络,最初由Alex Krizhevsky和他的同事在2012年开发。它被设计用来为ImageNet LSVRC-2010比赛进行图像分类,在那里它取得了最先进的成绩。
本文使用CIFAR10数据集数据集进行训练
import numpy as np
import torch
import torch.nn as nn
from torchvision import datasets
from torchvision import transforms
# 子集随机采样
from torch.utils.data.sampler import SubsetRandomSampler
# 设定GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
device
from torch.utils.data import SubsetRandomSampler
pseudo_dataset = list(range(10, 20))
subRandomSampler1 = SubsetRandomSampler(pseudo_dataset[:7])
subRandomSampler2 = SubsetRandomSampler(pseudo_dataset[7:])
print("for subset random sampler #1: ")
for data in subRandomSampler1:
print(data, end=" ")
print("\nfor subset random sampler #2: ")
for data in subRandomSampler2:
print(data, end=" ")
def get_train_valid_loader(data_dir,
batch_size,
augment,
random_seed,
valid_size=0.1,
shuffle=True):
# 数据集中每个通道 (R, G, B) 的平均值和标准偏差来定义变量normalize。
normalize = transforms.Normalize(
mean=[0.4914, 0.4822, 0.4465],
std=[0.2023, 0.1994, 0.2010],
)
# 定义验证集的transforms
valid_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
# 定义训练集的transforms
# 增强训练集,以便进行更平稳的训练并增加图像的数量
if augment:
train_transform = transforms.Compose([
# 随机裁剪
transforms.RandomCrop(32, padding=4),
# 以给定的概率随机水平翻转给定的PIL图像,这里使用默认值0.5
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize,
])
else:
train_transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
# 下载数据集
train_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=train_transform,
)
valid_dataset = datasets.CIFAR10(
root=data_dir, train=True,
download=True, transform=valid_transform,
)
# 获取训练集的标签
num_train = len(train_dataset)
indices = list(range(num_train))
# 找到训练集和验证集划分的区间
split = int(np.floor(valid_size * num_train))
# 打乱数据集
if shuffle:
np.random.seed(random_seed)
np.random.shuffle(indices)
# 划分训练集和验证集
train_idx, valid_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
valid_sampler = SubsetRandomSampler(valid_idx)
train_loader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, sampler=train_sampler)
valid_loader = torch.utils.data.DataLoader(
valid_dataset, batch_size=batch_size, sampler=valid_sampler)
return train_loader, valid_loader
我们将训练数据集分成训练集和验证集(90:10的比例),并随机地从整个训练集中将数据分入两个不同的子集。
我们指定批次大小,并在加载时对数据集进行shuffle,这样每个批次的标签类型都有一定的差异性。这将提高我们最终模型的功效。
有关transform的使用可以看这一篇
CV学习笔记【1】:transforms
def get_test_loader(data_dir,
batch_size,
shuffle=True):
normalize = transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
)
# 定义测试集的transform
transform = transforms.Compose([
transforms.Resize((227,227)),
transforms.ToTensor(),
normalize,
])
dataset = datasets.CIFAR10(
root=data_dir, train=False,
download=True, transform=transform,
)
data_loader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=shuffle
)
return data_loader
在训练集、验证集和测试集中我们都使用了DataLoader,因为DataLoader允许我们分批迭代数据,数据是在迭代过程中加载的,而不是一下子就启动到你的RAM中。
# CIFAR10 dataset
train_loader, valid_loader = get_train_valid_loader(data_dir = 'data/', batch_size = 64, augment = False, random_seed = 1)
test_loader = get_test_loader(data_dir = 'data/', batch_size = 64)
class AlexNet(nn.Module):
def __init__(self, num_classes=10):
super().__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
nn.BatchNorm2d(96),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer2 = nn.Sequential(
nn.Conv2d(96, 256, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.layer3 = nn.Sequential(
nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer4 = nn.Sequential(
nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(384),
nn.ReLU())
self.layer5 = nn.Sequential(
nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size = 3, stride = 2))
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(9216, 4096),
nn.ReLU())
self.fc1 = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU())
# 最后输出的通道数为10,即待分类的数量
self.fc2= nn.Sequential(
nn.Linear(4096, num_classes))
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.layer5(out)
# 改变张量的形状以输入全联接层
out = out.reshape(out.size(0), -1)
out = self.fc(out)
out = self.fc1(out)
out = self.fc2(out)
return out
BatchNorm2d
在PyTorch中定义任何神经网络(无论是否是CNN)的第一步是定义一个继承了nn.Module的类,因为它包含了许多我们需要利用的方法。
此后有两个主要步骤。首先是在__init__
中初始化我们将在CNN中使用的层,另一个是在forward()
中定义这些层处理图像的顺序。
对于架构本身,我们首先使用 nn.Conv2D
函数定义卷积层,并设置适当的核大小和输入/输出通道。我们还使用 nn.MaxPool2D
函数进行最大池化。PyTorch的好处是,我们可以使用 nn.Sequential
函数将卷积层、激活函数和最大池合并为一个单独的层(它们将被单独应用,但这有助于组织)。然后,我们使用 nn.Linear
和 nn.Dropout
以及 nn.ReLU
定义全连接层,并将这些与 nn.Sequential
函数结合起来。最后,我们的最后一层输出10个神经元,这是我们对10类物体的最终预测。
在训练之前,我们需要设置一些超参数,如损失函数和优化器,以及批次大小、学习率和epochs的数量。
num_classes = 10
num_epochs = 20
batch_size = 64
learning_rate = 0.005
# 迁移模型到GPU上
model = AlexNet(num_classes).to(device)
# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay = 0.005, momentum = 0.9) # weight_decay:权值衰减,防止过拟合
# 跟踪训练时的步骤
total_step = len(train_loader)
for epoch in range(num_epochs):
# 训练
for i, (images, labels) in enumerate(train_loader):
# 将读取到的数据张量转移到GPU上
images = images.to(device)
labels = labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# 验证
with torch.no_grad():
correct = 0
total = 0
for images, labels in valid_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} validation images: {} %'.format(5000, 100 * correct / total))
在前向过程中,我们使用我们的模型进行预测,并根据这些预测和我们的实际标签来计算损失。
接下来,我们进行后向处理,实际更新我们的权重,以改善我们的模型。在每次更新之前,我们使用 optimizer.zero_grad()
函数将梯度设置为零。然后,我们使用 loss.backward()
函数计算新的梯度。最后,我们用 optimizer.step()
函数更新权重。
另外,在每个历时结束时,我们也使用我们的验证集来计算模型的准确性。在这种情况下,我们不需要梯度,所以我们用 torch.no_grad()
来快速评估。这里的验证主要确保我们的模型在正确的下降方向上。
# 保存模型参数
torch.save(model.state_dict(), 'model/alexnet/alexnet_model.pth') # 只保存模型的参数,不保存模型
torch.save(optimizer.state_dict(), 'model/alexnet/alexnet_optimizer.pth') # 保存优化器的参数,如学习率等
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
del images, labels, outputs
print('Accuracy of the network on the {} test images: {} %'.format(10000, 100 * correct / total))
我们首先了解了AlexNet模型的结构和不同种类的层;
接下来,我们使用Torchvision加载并预处理了CIFAR10数据集;
然后,我们使用PyTorch从头开始建立我们的AlexNet模型;
最后,我们在CIFAR10数据集上训练和测试了我们的模型,该模型似乎在测试数据集上表现良好,只需最少的训练(6个epochs)
参考资料