CV-Model【2】:Alexnet

文章目录

  • 前言
  • 1. 设置环境
    • 1.1. 导入相关库
    • 1.2. 下载数据集
      • 1.2.1. 设置训练集和验证集
      • 1.2.2. 设置测试集
      • 1.2.3. 使用DataLoader下载并读取数据集
  • 2. 搭建神经网络
    • 2.1. 网络模型
    • 2.2. 设置超参数
  • 3. 训练模型
    • 3.1. 训练模型并验证模型
    • 3.2. 测试模型
  • 总结


前言

AlexNet是一个深度卷积神经网络,最初由Alex Krizhevsky和他的同事在2012年开发。它被设计用来为ImageNet LSVRC-2010比赛进行图像分类,在那里它取得了最先进的成绩。

本文使用CIFAR10数据集数据集进行训练


1. 设置环境

1.1. 导入相关库

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
  • SubsetRandomSampler
    设置子集的随机采样,多用于将数据集分成多个集合。参数是一个数据集(或可迭代对象)
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=" ")

1.2. 下载数据集

1.2.1. 设置训练集和验证集

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

1.2.2. 设置测试集

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中。

1.2.3. 使用DataLoader下载并读取数据集

# 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)

2. 搭建神经网络

2.1. 网络模型

  • 网络结构
    CV-Model【2】:Alexnet_第1张图片

  • 代码

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

  • 使得输入的一批feature map满足均值为0,方差为1的分布归零
  • pool操作并不改变张量的通道数

在PyTorch中定义任何神经网络(无论是否是CNN)的第一步是定义一个继承了nn.Module的类,因为它包含了许多我们需要利用的方法。
此后有两个主要步骤。首先是在__init__中初始化我们将在CNN中使用的层,另一个是在forward()中定义这些层处理图像的顺序。

对于架构本身,我们首先使用 nn.Conv2D 函数定义卷积层,并设置适当的核大小和输入/输出通道。我们还使用 nn.MaxPool2D 函数进行最大池化。PyTorch的好处是,我们可以使用 nn.Sequential 函数将卷积层、激活函数和最大池合并为一个单独的层(它们将被单独应用,但这有助于组织)。然后,我们使用 nn.Linearnn.Dropout 以及 nn.ReLU 定义全连接层,并将这些与 nn.Sequential 函数结合起来。最后,我们的最后一层输出10个神经元,这是我们对10类物体的最终预测。

2.2. 设置超参数

在训练之前,我们需要设置一些超参数,如损失函数和优化器,以及批次大小、学习率和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)

3. 训练模型

3.1. 训练模型并验证模型

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') # 保存优化器的参数,如学习率等

3.2. 测试模型

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)

参考资料

你可能感兴趣的:(#,CV,深度学习,pytorch,python)