首页 > 其他分享 >AlexNet文献阅读与代码实现

AlexNet文献阅读与代码实现

时间:2025-01-12 22:34:19浏览次数:1  
标签:nn 代码 torch train 文献 model self AlexNet

目录

AlexNet文献阅读与代码实现

前言:笔者目前研一,刚开始入门深度学习,所以想记录一下自己学习的过程,接下来的时间里,我会定期阅读深度学习领域的经典文献,并尝试用代码实现它们,也欢迎大家积极评论。

:博客本身更侧重于代码实现,关于一些深度学习的概念,如卷积核之类的内容,大家有疑惑的话可以自行搜索。

文献地址:

ImageNet Classification with Deep Convolutional Neural Networks

文献内容讲解:

9年后重读深度学习奠基作之一:AlexNet【论文精读·2】_哔哩哔哩_bilibili

一些术语:

术语 含义
trick 一些技巧,比如alexnet就提到它采用了dropout、relu这样的技巧来加速训练并避免过拟合

一些超参

超参名 含义
weight decay weight decay是一种正则化技术,防止模型在训练数据上过拟合
momentum momentum是一种加速梯度下降的优化方法,它通过考虑前一个步骤的更新方向,来帮助当前的梯度更新更快地收敛
batchsize 将几个数据放到一起作为一个批次

可以在优化器里很轻松的设置它们:

optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)

文献内容介绍

​ 这篇文章是2012年发表的,作者为Alex Krizhevsky,Ilya Sutskever,Geoffrey Hinton(前段时间拿到诺贝尔物理学家的那位,因为这事我才了解到这位大佬)。AlexNet在2012年的ImageNet比赛中取得了显著成功。(AlexNet是一个大型的用于图片分类的数据集),这篇文章最终发表在2012年的NIPS(NeurIPS)会议上。自此之后,深度学习在计算机视觉领域得到广泛应用。

​ AlexNet由五层卷积,和三层全连接组成,网络结构如下所示:

pEPyjgK.png

​ 网络是上下两层的原因是GPU容量不够,所以作者把图片切开,分成两层来训练,最后拼接起来。他们用的GPU是GTX 580,只有3G的内存,而我们现在实现的时候不需要再切开了。所以我们可以把上下拼起来来看这个网络结构。(当然,作者在第二层的卷积后,做了一次上下两层连接的工作,我们现在忽略这一步)

文章第三节采用了一些trick来加速训练。

  1. Relu

​ 之前的文章主要采用sigmod来作为激活函数,而本文采用了relu,relu训练起来会快很多。(不过我也不知道为什么)

  1. 用多个GPU训练

​ 文章中介绍他如何采用多个GPU来训练,也就是把数据切开来分开训练

  1. 正则化(local response normalization)

​ 就是把数据归一化(现在来看这个东西不太重要)

  1. overlapping pooling

​ 对传统的pooling技术做了改进(这个我也没懂)

文章第四节介绍了一些方法来避免过拟合

  1. 数据增强

​ 原图像是256 * 256的,它随机裁剪出224 * 224的部分出来,这样数据量就变为原来2048倍(现在来看不能这么算,因为裁剪出来的很多是一样的)

  1. dropout

​ 作者把前两层全连接层中随机的50%的权重设为0,以此来避免过拟合

代码实现

​ 为了减小内存的开销,我们这里采用的数据集是minist数据集(也就是手写数字识别的数据集),也就是把输入图像变为1 * 28 * 28大小的图像,输出类别变为了10(也就是0-9)。代码目录如下所示:

pEPyy7j.png

​ 在data目录下,存放我们的minist数据集,model.py存放我们设计好的网络模型,train.py实现对模型的训练,并在训练完成后保存模型权重,test.py加载保存好的权重并进行测试。

​ 一般而言深度学习的项目会有一个专门的文件夹用来处理数据,我们这里为了方便起见,直接调用了别人做好的数据,因此不用再额外写处理数据的代码。(数据集是自动下载的,权重是训练过程中保存的,也就是只要有3个.py文件,项目就可以运行起来)

model.py

AlextNet类是我们设计的网络模型,后面的main函数是我们用来对当前文件做测试的(建议为每个python文件都添加main函数,这样方便我们单独测试每一个文件)

import torch
import torch.nn as nn


class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()

        self.features = nn.Sequential(
            nn.Conv2d(1, 96, kernel_size=5, stride=1),  # 输入通道为1(灰度图)
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),  # 下采样

            nn.Conv2d(96, 256, kernel_size=5, stride=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(256, 384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),

            nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),

            nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),  # 这里的池化
        )

        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 2 * 2, 4096),  # 修改为256 * 2 * 2
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, 10),  # MNIST有10个类
        )

    def forward(self, x):
        print(f"Input shape: {x.shape}")  # 打印输入形状

        # 第一层
        x = self.features[0](x) # 经过5 * 5的卷积 大小变为[1, 96, 24, 24]
        x = self.features[1](x) # 经过relu 大小不变
        x = self.features[2](x) # 经过池化 大小变为[1, 96, 12, 12]

        # 第二层
        x = self.features[3](x) # 经过5 * 5的卷积
        x = self.features[4](x)
        x = self.features[5](x)

        # 第三层
        x = self.features[6](x) # 经过3 * 3的卷积
        x = self.features[7](x)

        # 第四层
        x = self.features[8](x) # 经过3 * 3的卷积
        x = self.features[9](x)

        # 第五层
        x = self.features[10](x) # 经过3 * 3的卷积
        x = self.features[11](x)
        x = self.features[12](x)  # Max Pool

        x = x.view(x.size(0), 256 * 2 * 2)  # 展平

        # 后面的全连接层
        x = self.classifier(x)  # 分类

        return x


if __name__ == "__main__":
    model = AlexNet()
    # 使用 torch.randn 生成随机输入,形状为 (1, 1, 28, 28)
    test_input = torch.randn(1, 1, 28, 28)  # 生成一个随机的28x28图像

    output = model(test_input)
    print(output.shape)  # 应该输出 (1, 10) 对应于一个样本的10个类的输出

train.py

import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from model import AlexNet  # 确保你的model.py文件在合适的位置

# 设置随机种子以确保可重复性
torch.manual_seed(42)

# 配置超参数
batch_size = 64
learning_rate = 0.001
num_epochs = 10
data_dir = './data'  # 数据存放路径

# 数据变换(适合MNIST的数据)
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))  # 假设输入通道为1(灰度图)
])

# 检查数据存放路径是否存在,如果不存在,则下载数据集
if not os.path.exists(data_dir):
    os.makedirs(data_dir)

# 载入MNIST数据集,设置download=True以下载数据(如果尚未存在)
train_dataset = datasets.MNIST(root=data_dir, train=True, download=True, transform=transform)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)

# 创建模型实例
model = AlexNet()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)

# 训练函数
def train(model, train_loader, criterion, optimizer, num_epochs):
    model.train()  # 设定模型为训练模式
    best_accuracy = 0.0  # 记录最佳准确率

    for epoch in range(num_epochs):
        running_loss = 0.0
        correct = 0
        total = 0
        for i, (images, labels) in enumerate(train_loader):
            images, labels = images.to(device), labels.to(device)

            # 前向传播
            outputs = model(images)
            loss = criterion(outputs, labels)

            # 反向传播和优化
            optimizer.zero_grad()  # 清空梯度
            loss.backward()  # 计算梯度
            optimizer.step()  # 更新参数

            # 统计
            running_loss += loss.item()
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

            # 每十个batch打印loss和准确率
            if (i + 1) % 10 == 0:
                print(f'Epoch [{epoch + 1}/{num_epochs}], Step [{i + 1}/{len(train_loader)}], Loss: {loss.item():.4f}, Accuracy: {100 * correct / total:.2f}%')

        # 计算并打印每个epoch的平均loss和准确率
        avg_loss = running_loss / len(train_loader)
        avg_accuracy = correct / total * 100
        print(f'Epoch [{epoch + 1}/{num_epochs}], Average Loss: {avg_loss:.4f}, Average Accuracy: {avg_accuracy:.2f}%')

        # 保存最佳模型
        if avg_accuracy > best_accuracy:
            best_accuracy = avg_accuracy
            torch.save(model.state_dict(), 'best_model.pth')  # 保存模型权重
            print(f'Saved best model with accuracy: {best_accuracy:.2f}%')

if __name__ == "__main__":
    train(model, train_loader, criterion, optimizer, num_epochs)

test.py

import torch  
import torch.nn as nn  
from torch.utils.data import DataLoader  
from torchvision import datasets, transforms  
from model import AlexNet  # 确保你的model.py文件在合适的位置  

# 设置设备  
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  

# 数据变换(适合MNIST的数据)  
transform = transforms.Compose([  
    transforms.ToTensor(),  
    transforms.Normalize((0.5,), (0.5,))  # 假设输入通道为1(灰度图)  
])  

# 载入MNIST测试集  
test_dataset = datasets.MNIST(root='./data', train=False, download=False, transform=transform)  
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)  

# 创建模型实例并加载最佳模型权重  
model = AlexNet()  
model.load_state_dict(torch.load('best_model.pth'))  
model.to(device)  
model.eval()  # 设置为评估模式  

# 测试函数  
def test(model, test_loader):  
    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.data, 1)  

            total += labels.size(0)  # 总样本数量  
            correct += (predicted == labels).sum().item()  # 统计正确分类的样本数量  

    accuracy = 100 * correct / total  
    print(f'Test Accuracy: {accuracy:.2f}%')  

if __name__ == "__main__":  
    test(model, test_loader)

最终输出的准确率为99.2%,可以通过调参,将结果调到99.5%,手写数字识别数据集还是比较简单的,后续我会尝试用其他更大的数据集进行训练。

内容总结

我对卷积的认知就是提取特征并降低维度。因为图像往往都比较大,比如说224 * 224,如果直接把它展平,作为一个向量输入到全连接网络中,参数量就太大了,这样很难去训练。所以我们在使用全连接网络之前会通过卷积来提取图像特征,然后再它图像展平作为全连接网络的输入,最终得到我们想要的结果。大家也可以尝试多加几层卷积,来尝试对准确率的影响。

标签:nn,代码,torch,train,文献,model,self,AlexNet
From: https://www.cnblogs.com/-yuanyuan/p/18667489

相关文章