Pytorch | 从零构建AlexNet对CIFAR10进行分类

Pytorch | 从零构建AlexNet对CIFAR10进行分类

CIFAR10数据集

CIFAR-10数据集是由加拿大高级研究所(CIFAR)收集整理的用于图像识别研究的常用数据集,基本信息如下:

  • 数据规模:该数据集包含60,000张彩色图像,分为10个不同的类别,每个类别有6,000张图像。通常将其中50,000张作为训练集,用于模型的训练;10,000张作为测试集,用于评估模型的性能。
  • 图像尺寸:所有图像的尺寸均为32×32像素,这相对较小的尺寸使得模型在处理该数据集时能够相对快速地进行训练和推理,但也增加了图像分类的难度。
  • 类别内容:涵盖了飞机(plane)、汽车(car)、鸟(bird)、猫(cat)、鹿(deer)、狗(dog)、青蛙(frog)、马(horse)、船(ship)、卡车(truck)这10个不同的类别,这些类别都是现实世界中常见的物体,具有一定的代表性。

下面是一些示例样本:

AlexNet

AlexNet是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton在2012年提出的一种深度卷积神经网络,在ImageNet图像识别挑战赛中取得了巨大成功,推动了深度学习在计算机视觉领域的快速发展。以下是对它的详细介绍:

网络结构

  • 卷积层:包含5个卷积层,这些卷积层通过不同的卷积核大小、步长和填充方式,逐步提取图像的特征。
  • 池化层:有3个最大池化层,用于减小特征图的尺寸,同时保留关键特征,减少计算量和过拟合风险。
  • 全连接层 :包括3个全连接层,用于对提取的特征进行分类,最后一层输出分类结果。

    上图为AlexNet原文中的网络结构(针对ImageNet,图片尺寸为224×224),本文是针对CIFAR10,其尺寸为32×32,因此结构不太相同,比如卷积核的大小,具体可以参考下面的代码。

技术创新点

  • ReLU激活函数:使用ReLU(Rectified Linear Unit)作为激活函数,解决了传统激活函数在深度网络中梯度消失的问题,加快了训练速度。
  • Dropout正则化:在全连接层中使用了Dropout技术,随机丢弃部分神经元,防止过拟合,提高模型的泛化能力。
  • 重叠池化:采用重叠池化(Overlapping Pooling),即池化窗口之间有重叠,有助于提取更多的特征信息,提升模型的性能。
  • 多GPU训练:首次利用多GPU进行并行训练,大大提高了训练速度,使得在大规模数据集上训练深度网络成为可能。

性能表现

  • 在ImageNet数据集上,AlexNet的top-5错误率大幅降低至15.3%,相比之前的方法有了显著提升,展示了其强大的图像识别能力。
  • 能够学习到丰富的图像特征,对不同类别的物体具有很好的区分能力,在实际应用中取得了很好的效果。

影响和意义

  • 推动深度学习发展:AlexNet的成功引起了学术界和工业界对深度学习的广泛关注,激发了更多研究人员对深度神经网络的研究兴趣,推动了深度学习技术的快速发展。
  • 开启卷积神经网络新时代:为后续的卷积神经网络研究提供了重要的参考和借鉴,许多新的网络结构和技术都是在AlexNet的基础上发展而来的。
  • 拓展应用领域:由于其在图像识别任务上的出色表现,AlexNet及其改进模型被广泛应用于计算机视觉的各个领域,如目标检测、图像分割、人脸识别等。

AlexNet结构代码详解

结构代码

python 复制代码
import torch
import torch.nn as nn


class AlexNet(nn.Module):
    def __init__(self, num_classes):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            # input size: (B, 3, 32, 32)   (Batch_size, Channel, Height, Width)
            nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1), # (B, 64, 16, 16)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 64, 8, 8)
            nn.Conv2d(64, 192, kernel_size=3, padding=1),   # (B, 192, 8, 8)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 192, 4, 4)
            nn.Conv2d(192, 384, kernel_size=3, padding=1),  # (B, 384, 4, 4)
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),  # (B, 256, 4, 4)
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),  # (B, 256, 4, 4)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 256, 2, 2)
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 2 * 2, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 256 * 2 *2)
        x = self.classifier(x)
        return x

代码详解

以下是对上述AlexNet代码的详细解释:

特征提取层 self.features

这部分构建了AlexNet的特征提取层,是一个由多个层组成的顺序结构(通过nn.Sequential来定义)。

  • 第一个卷积层
    nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1)表示输入图像的通道数为3(通常对应RGB图像的红、绿、蓝三个通道),输出的通道数为64(即卷积核的数量为64,意味着会生成64个不同的特征图),卷积核大小是3×3,步长为2(在空间维度上每次移动2个像素),填充为1(在图像边缘进行1个像素的填充,这样可以保证输入输出图像尺寸在卷积操作下能按预期变化),经过这个卷积层后,输入尺寸为(B, 3, 32, 32)的图像数据会变成(B, 64, 16, 16)

  • 激活函数层
    nn.ReLU(inplace=True)是使用修正线性单元(Rectified Linear Unit)作为激活函数,inplace=True表示直接在输入的张量上进行修改(节省内存空间),对经过卷积后的特征图进行非线性变换,增强网络的表达能力。

  • 池化层
    nn.MaxPool2d(kernel_size=2)是最大池化层,池化核大小为2×2,它会在每个2×2的窗口内选取最大值作为输出,起到下采样的作用,减少数据量同时保留重要特征,比如经过第一次池化后特征图尺寸从(B, 64, 16, 16)变为(B, 64, 8, 8)

后续依次重复卷积、激活、池化等操作 ,不断提取图像的特征,逐步降低特征图的尺寸同时增加特征图的深度(通道数),最终经过这一系列操作后得到尺寸为(B, 256, 2, 2)的特征图。

分类部分self.classifier

python 复制代码
self.classifier = nn.Sequential(
    nn.Dropout(),
    nn.Linear(256 * 2 * 2, 4096),
    nn.ReLU(inplace=True),
    nn.Dropout(),
    nn.Linear(4096, 4096),
    nn.ReLU(inplace=True),
    nn.Linear(4096, num_classes)
)

这部分构建了AlexNet的分类器,同样是顺序结构。

  • Dropout层
    nn.Dropout()是一种正则化技术,在训练过程中以一定概率(默认0.5)随机将神经元的输出设置为0,防止过拟合,提高模型的泛化能力。这里使用了两次Dropout,分别在不同的全连接层之前。

  • 全连接层

第一个nn.Linear(256 * 2 * 2, 4096)表示将经过特征提取后展平的特征向量(尺寸为256 * 2 * 2,因为前面特征提取部分最后得到的特征图尺寸是(B, 256, 2, 2),展平后维度就是256 * 2 * 2)映射到一个4096维的向量空间,后面接着激活函数nn.ReLU(inplace=True)进行非线性变换。然后又是一个Dropout层和一个同样输出维度为4096的全连接层以及相应的激活函数,最后通过nn.Linear(4096, num_classes)将4096维的向量映射到指定的类别数(num_classes)维度,得到最终的分类预测结果。

前向传播forward

python 复制代码
def forward(self, x):
    x = self.features(x)
    x = x.view(x.size(0), 256 * 2 *2)
    x = self.classifier(x)
    return x

forward方法定义了数据在网络中的前向传播过程。

  • 特征提取
    首先x = self.features(x),将输入数据x送入到之前定义的特征提取部分(features),按照特征提取层中定义的卷积、激活、池化等操作依次对输入数据进行处理,得到提取后的特征图。
  • 特征图展平
    x = x.view(x.size(0), 256 * 2 *2)这行代码将特征图进行展平操作,使其变成一个二维张量,其中第一维对应批次大小(x.size(0)表示批次中的样本数量),第二维就是展平后的特征向量长度(由前面特征提取最后得到的特征图尺寸计算得出),这样才能输入到后面的全连接层中进行分类处理。
  • 分类预测
    最后x = self.classifier(x)将展平后的特征向量送入分类器部分(classifier),经过全连接层、激活函数、Dropout等操作逐步得到最终的分类预测结果,然后通过return x返回这个预测结果。

训练和测试

训练代码train.py

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from models import AlexNet
import matplotlib.pyplot as plt

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# 定义数据预处理操作
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])

# 加载CIFAR10训练集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,
                                          shuffle=True, num_workers=2)

# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 实例化模型
model = AlexNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练轮次
epochs = 15

def train(model, trainloader, criterion, optimizer, device):
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data[0].to(device), data[1].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()

    epoch_loss = running_loss / len(trainloader)
    epoch_acc = 100. * correct / total
    return epoch_loss, epoch_acc

if __name__ == "__main__":
    loss_history, acc_history = [], []
    for epoch in range(epochs):
        train_loss, train_acc = train(model, trainloader, criterion, optimizer, device)
        print(f'Epoch {epoch + 1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')
        loss_history.append(train_loss)
        acc_history.append(train_acc)
        # 保存模型权重,每5轮次保存到weights文件夹下
        if (epoch + 1) % 5 == 0:
            torch.save(model.state_dict(), f'weights/alexnet_epoch_{epoch + 1}.pth')
    # 绘制损失曲线
    plt.plot(range(1, epochs+1), loss_history, label='Loss', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('Training Loss Curve')
    plt.legend()
    plt.savefig('results\\train_loss_curve.png')
    plt.close()

    # 绘制准确率曲线
    plt.plot(range(1, epochs+1), acc_history, label='Accuracy', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy (%)')
    plt.title('Training Accuracy Curve')
    plt.legend()
    plt.savefig('results\\train_acc_curve.png')
    plt.close()

测试代码test.py

python 复制代码
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from models import AlexNet

import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# 定义数据预处理操作
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])

# 加载CIFAR10测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,
                                         shuffle=False, num_workers=2)

# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 实例化模型
model = AlexNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()

# 加载模型权重
weights_path = "weights/alexnet_epoch_15.pth"  
model.load_state_dict(torch.load(weights_path, map_location=device))

def test(model, testloader, criterion, device):
    model.eval()
    running_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            inputs, labels = data[0].to(device), data[1].to(device)
            outputs = model(inputs)
            loss = criterion(outputs, labels)

            running_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()

    epoch_loss = running_loss / len(testloader)
    epoch_acc = 100. * correct / total
    return epoch_loss, epoch_acc

if __name__ == "__main__":
    test_loss, test_acc = test(model, testloader, criterion, device)
    print("================AlexNet Test================")
    print(f"Load Model Weights From: {weights_path}")
    print(f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}%')

训练过程和测试结果

训练过程损失函数变化曲线:


训练过程准确率变化曲线:

测试结果:

代码汇总

项目github地址
项目结构:

|--data
|--models
	|--__init__.py
	|--alexnet.py
|--results
|--weights
|--train.py
|--test.py

alexnet.py

python 复制代码
import torch
import torch.nn as nn


class AlexNet(nn.Module):
    def __init__(self, num_classes):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            # input size: (B, 3, 32, 32)   (Batch_size, Channel, Height, Width)
            nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1), # (B, 64, 16, 16)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 64, 8, 8)
            nn.Conv2d(64, 192, kernel_size=3, padding=1),   # (B, 192, 8, 8)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 192, 4, 4)
            nn.Conv2d(192, 384, kernel_size=3, padding=1),  # (B, 384, 4, 4)
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),  # (B, 256, 4, 4)
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),  # (B, 256, 4, 4)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),    # (B, 256, 2, 2)
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 2 * 2, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 256 * 2 *2)
        x = self.classifier(x)
        return x

train.py

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from models import AlexNet
import matplotlib.pyplot as plt

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# 定义数据预处理操作
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])

# 加载CIFAR10训练集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=False, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,
                                          shuffle=True, num_workers=2)

# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 实例化模型
model = AlexNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练轮次
epochs = 15

def train(model, trainloader, criterion, optimizer, device):
    model.train()
    running_loss = 0.0
    correct = 0
    total = 0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data[0].to(device), data[1].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()

    epoch_loss = running_loss / len(trainloader)
    epoch_acc = 100. * correct / total
    return epoch_loss, epoch_acc

if __name__ == "__main__":
    loss_history, acc_history = [], []
    for epoch in range(epochs):
        train_loss, train_acc = train(model, trainloader, criterion, optimizer, device)
        print(f'Epoch {epoch + 1}: Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%')
        loss_history.append(train_loss)
        acc_history.append(train_acc)
        # 保存模型权重,每5轮次保存到weights文件夹下
        if (epoch + 1) % 5 == 0:
            torch.save(model.state_dict(), f'weights/alexnet_epoch_{epoch + 1}.pth')
    # 绘制损失曲线
    plt.plot(range(1, epochs+1), loss_history, label='Loss', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.title('Training Loss Curve')
    plt.legend()
    plt.savefig('results\\train_loss_curve.png')
    plt.close()

    # 绘制准确率曲线
    plt.plot(range(1, epochs+1), acc_history, label='Accuracy', marker='o')
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy (%)')
    plt.title('Training Accuracy Curve')
    plt.legend()
    plt.savefig('results\\train_acc_curve.png')
    plt.close()

test.py

python 复制代码
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
from models import AlexNet

import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# 定义数据预处理操作
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.491, 0.482, 0.446), (0.247, 0.243, 0.261))])

# 加载CIFAR10测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,
                                         shuffle=False, num_workers=2)

# 定义设备(GPU优先,若可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 实例化模型
model = AlexNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()

# 加载模型权重
weights_path = "weights/alexnet_epoch_15.pth"  
model.load_state_dict(torch.load(weights_path, map_location=device))

def test(model, testloader, criterion, device):
    model.eval()
    running_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            inputs, labels = data[0].to(device), data[1].to(device)
            outputs = model(inputs)
            loss = criterion(outputs, labels)

            running_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()

    epoch_loss = running_loss / len(testloader)
    epoch_acc = 100. * correct / total
    return epoch_loss, epoch_acc

if __name__ == "__main__":
    test_loss, test_acc = test(model, testloader, criterion, device)
    print("================AlexNet Test================")
    print(f"Load Model Weights From: {weights_path}")
    print(f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}%')
相关推荐
lovelin+v1750304096617 分钟前
智能化API:如何重塑企业业务流程与用户体验
大数据·人工智能·爬虫·python·api
MYT_flyflyfly1 小时前
LRM-典型 Transformer 在视觉领域的应用,单个图像生成3D图像
人工智能·深度学习·transformer
明月醉窗台1 小时前
深度学习(15)从头搭建模型到训练、预测示例总结
人工智能·python·深度学习·目标检测·计算机视觉
三天不学习1 小时前
Visual Studio 玩转 IntelliCode AI辅助开发
ide·人工智能·ai编程·visual studio·intellicode
martian6651 小时前
深入详解线性代数基础知识:理解矩阵与向量运算、特征值与特征向量,以及矩阵分解方法(如奇异值分解SVD和主成分分析PCA)在人工智能中的应用
人工智能·线性代数·矩阵·特征向量
乌漆嘎嘎黑2 小时前
训练的Loss和输出突然全是nan【小白找bug】
pytorch·python·bug·llama·大模型权重文件
过9532 小时前
2024年全球安全光幕装置行业总体规模、主要企业国内外市场占有率及排名
人工智能·安全·百度
东方佑2 小时前
类A* llm解码 幻觉更低更稳定
人工智能·深度学习·机器学习
产品大道2 小时前
无人预见的人工智能创业大难题
人工智能·搜索引擎·百度