当前位置:   article > 正文

AlexNet代码讲解(Pytorch)

alexnet代码

引言

文通过代码实现了AlexNet算法,使用的是pytorch框架,版本为1.7.1。另外本专栏的所有算法都有对应的Libtorch版本(Libtorch版本的AlexNet地址),算法原理本文不做过多阐述。本文针对小白对代码以及相关函数进行讲解,建议配合代码进行阅读,代码中我进行了详细的注释,因此读者可以更加容易理解代码的含义,本文只展示了部分代码,全部代码可以通过GitHub下载

本文使用的数据集为0~9的手写数据集,全部代码主要分为以下几个部分:

1、定义AlexNet网络结构(model.py)

2、训练卷积神经网络(train.py)

3、输入图片预测结果(predict.py)

4、将保存的模型参数 .pth文件(Pythorch用)转变为 .pt文件(Libtorch用) (pytocpp.py)

model.py

AlexNet的网络结构如下所示:

首先是特征提取部分的网络结构,其中每一次卷积后都需要加ReLu激活函数。

层名\参数

输入通道数

输出通道数

卷积核大小

步长

填充数

备注

卷积层

3

96

11

4

2

后接ReLu

最大池化层

3

2

0

卷积层

96

256

5

1

2

后接ReLu

最大池化层

3

2

0

卷积层

256

384

3

1

1

后接ReLu

卷积层

384

384

3

1

1

后接ReLu

卷积层

384

256

3

1

1

后接ReLu

最大池化层

3

2

0

此部分代码如下

  1. self.features = nn.Sequential(
  2. nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=2),
  3. nn.ReLU(inplace=True),
  4. nn.MaxPool2d(3, 2),
  5. nn.Conv2d(96, 256, 5, 1, 2),
  6. nn.ReLU(inplace=True),
  7. nn.MaxPool2d(3, 2),
  8. nn.Conv2d(256, 384, 3, 1, 1),
  9. nn.ReLU(inplace=True),
  10. nn.Conv2d(384, 384, 3, 1, 1),
  11. nn.ReLU(inplace=True),
  12. nn.Conv2d(384, 256, 3, 1, 1),
  13. nn.ReLU(inplace=True),
  14. nn.MaxPool2d(3, 2)
  15. )

然后是线性分类部分的网络结构:

层名\参数

输入通道数

输出通道数

备注

Dropout层

0.5

全连接层

256*6*6

2048

后接ReLu

Dropout层

0.5

全连接层

2048

2048

后接ReLu

全连接层

2048

NUM_CLASS

此部分代码如下:

  1. self.classifier = nn.Sequential(
  2. nn.Dropout(0.5),
  3. nn.Linear(256*6*6, 2048),
  4. nn.ReLU(inplace=True),
  5. nn.Dropout(0.5),
  6. nn.Linear(2048, 2048),
  7. nn.ReLU(inplace=True),
  8. nn.Linear(2048, NUM_CLASS) # NUM_CLASS为类别总数
  9. )

先介绍以下用到的函数吧

  1. import torch.nn as nn
  2. import torch
  3. # 卷积层 计算公式为 -> 卷积后图像尺寸=(原图像尺寸+2*填充大小-卷积核大小)/步长+1
  4. # in_channels为输入通道数
  5. # out_channels为输出通道数
  6. # kernel_size为卷积核尺寸 比如11代表(11*11)
  7. # stride卷积的步长
  8. # padding为零填充数
  9. nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=2)
  10. # 最大池化层
  11. # kernel_size为卷积核大小
  12. # stride为步长
  13. nn.MaxPool2d(kernel_size=3, stride=2)
  14. # ReLu激活函数
  15. # inplace=True会改变输入数据的值,节省反复申请与释放内存的空间与时间,效率更好
  16. nn.ReLU(inplace=True)
  17. nn.Dropout(0.5), # Dropout随机丢弃神经元,0.5代表随机丢弃50%
  18. torch.flatten(x, start_dim=1) # (将矩阵x展开成一维行向量)
  19. # 全连接层
  20. # in_features 输入部分神经元个数
  21. # out_features 输出部分神经元个数
  22. nn.Linear(in_features,out_features)

以0~9的手写数据集为例,在AlexNet中要求输入224*224*3的彩色三通道RGB图片,经过特征提取部分的神经网络输出256*6*6大小的张量,再经过线性分类部分的神经网络得到1*10的张量,假设这10个数中第4个数最大,则最终预测此图像为数字3。至于权重初始化代码,是官方例程中给出的,参考一下就好。

train.py

定义完网络结构之后就可以正式开始训练了!

具体分为以下几个步骤:

1、定义数据集

2、定义网络结构

3、定义损失函数以及优化器

4、开始训练

首先是数据集的定义,本文用到了torchvision中datasets.ImageFolder函数,此函数对数据集的摆放格式有一定要求,以0~9的手写数据集为例,具体布置格式如下图所示:

在定义数据集的同时需要对所有的图片进行预处理,包括将图片resize成(224,224)的大小,转变为张量,进行标准化等等。

部分代码如下所示:

  1. from torchvision import datasets, transforms
  2. from torch.utils.data import DataLoader
  3. # transforms.Compose可以对张量进行一系列操作,各种操作存储在列表内
  4. transform = {
  5. # ToTensor()能够把像素的值域范围从0-255变换到0-1之间,
  6. # 而后面的transform.Normalize()则把0-1变换到(-1,1).
  7. 'train': transforms.Compose([transforms.Resize((224, 224)),
  8. transforms.RandomHorizontalFlip(), # 随机翻转
  9. transforms.ToTensor(), # 转换为张量
  10. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 标准化处理
  11. ]),
  12. 'test': transforms.Compose([transforms.Resize((224, 224)),
  13. transforms.ToTensor(),
  14. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
  15. }
  16. train_dataset = datasets.ImageFolder(root='./AlexNet/Project1/DATASET/TRAIN', transform=transform['train'])
  17. test_dataset = datasets.ImageFolder(root='./AlexNet/Project1/DATASET/TEST', transform=transform['test'])
  18. # 参数shuffle代表是否打乱数据集(以乱序排列)
  19. train_loader = DataLoader(train_dataset, batch_size=2, shuffle=True)
  20. test_loader = DataLoader(test_dataset, batch_size=2, shuffle=False)

定义完数据集后,接下来是定义网络结构:(如果使用GPU进行训练,需要设置为CUDA),代码如下:

  1. # 2、定义网络结构并设置为CUDA
  2. device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  3. # 这个AlexNet是我们在model.py中定义的类(网络结构)
  4. net = AlexNet(NUM_CLASS=10, init_weight=True)
  5. net.to(device) # 转化为CUDA

接下来是定义损失函数以及优化器,我们这里使用的是交叉熵损失函数(CrossEntropyLoss)以及SGD优化器(随机梯度下降),如果使用GPU进行训练损失函数也要设置为CUDA,具体代码如下:

  1. # 3、定义损失函数及优化器,损失函数设置为CUDA
  2. loss_function = nn.CrossEntropyLoss()
  3. optimizer = optioms.SGD(params=net.parameters(), lr=learning_rate)
  4. loss_function.to(device)

好的,最后就开始训练了,在训练过程中学习率(learning rate)逐步减小会比较好,更改学习率则需要对优化器(optimizer)进行设置,在optimizer中存有字典(param_groups),在字典中可以设置学习率,设置方法如下:

  1. for param_group in optimizer.param_groups: # 其中的元素是2个字典;optimizer.param_groups[0]: 长度为6的字典,包括[‘amsgrad’, ‘params’, ‘lr’, ‘betas’, ‘weight_decay’, ‘eps’]这6个参数;
  2. # optimizer.param_groups[1]: 好像是表示优化器的状态的一个字典;
  3.     param_group['lr'] = learning_rate # 更改全部的学习率

然后就需要从上文定义的train_loader中取数据(image与target)进行训练(若使用GPU训练则image与target都要设置为CUDA),具体步骤为:1、将图片输入网络得到1*10的张。2、将神经网络的输出与标签(target)进行对比并计算其损失。3、通过优化器进行梯度下降和反向传播更新参数。4、保存模型参数。5、不断重复(迭代)以上步骤直到达到要求(损失值小于设定值或者完成设定的迭代次数)。部分代码如下所示。

  1. # 4、开始训练
  2. for epoch in range(num_epochs):
  3. net.train() # 网络有Dropout,BatchNorm层时一定要加
  4. if epoch == 4:
  5. learning_rate = 0.0001 # 第四次迭代时学习率设置为0.0001
  6. if epoch == 6:
  7. learning_rate = 0.00001
  8. for param_group in optimizer.param_groups: # 其中的元素是2个字典;optimizer.param_groups[0]: 长度为6的字典,包括[‘amsgrad’, ‘params’, ‘lr’, ‘betas’, ‘weight_decay’, ‘eps’]这6个参数;
  9. # optimizer.param_groups[1]: 好像是表示优化器的状态的一个字典;
  10. param_group['lr'] = learning_rate # 更改全部的学习率
  11. print('\n\nStarting epoch %d / %d' % (epoch + 1, num_epochs))
  12. print('Learning Rate for this epoch: {}'.format(learning_rate))
  13. total_loss = 0.
  14. for i, (images, target) in enumerate(train_loader): # image为图片,target为其对应的标签(类别名)
  15. images, target = images.cuda(), target.cuda() # 设置为CUDA
  16. pred = net(images) # 图片输入网络得到预测结果
  17. loss = loss_function(pred, target) # 将预测结果与实际标签比对(计算两者之间的损失值)
  18. total_loss += loss.item()
  19. optimizer.zero_grad() # 将梯度归零,有助于梯度下降
  20. loss.backward() # 反向传播 计算梯度
  21. optimizer.step() # 根据梯度 更新模型参数
  22. if (i + 1) % 5 == 0: # 打印训练的信息
  23. print('Epoch [%d/%d], Iter [%d/%d] Loss: %.4f, average_loss: %.4f' % (epoch + 1, num_epochs,
  24. i + 1, len(train_loader), loss.item(), total_loss / (i + 1)))
  25. validation_loss = 0.0
  26. net.eval()
  27. for i, (images, target) in enumerate(test_loader): # 导入dataloader 说明开始训练了 enumerate 建立一个迭代序列
  28. images, target = images.cuda(), target.cuda()
  29. pred = net(images) # 将图片输入
  30. loss = loss_function(pred, target)
  31. validation_loss += loss.item() # 累加loss值 (固定搭配)
  32. validation_loss /= len(test_loader) # 计算平均loss
  33. if best_test_loss > validation_loss:
  34. best_test_loss = validation_loss
  35. print('get best test loss %.5f' % best_test_loss)
  36. torch.save(net.state_dict(), 'AlexNet.pth') # 保存模型参数

predict.py

知道了如何训练那预测阶段也十分容易了,大致思路为,使用opencv读取一张图片然后经过和训练阶段一样的预处理操作,将其输入神经网络得到1*10维的张量,通过判断10个数中第几个数最大,就能知道该图片所属的类别。废话不多说,代码如下

  1. import cv2
  2. import torch
  3. transform = transforms.Compose([ transforms.ToTensor(), # 转换为张量
  4. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
  5. # 进行预测
  6. class Predict:
  7. def __init__(self, image_root, net1): # image_root->需要预测的图片路径,net1->网络结构
  8. self.img_root = image_root
  9. self.model = net1
  10. def result(self):
  11. img = cv2.imread(img_root) # opencv读取图片
  12. img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR->RGB
  13. img = cv2.resize(img, (224, 224)) # 将图片尺寸变为224*224
  14. img = transform(img) # 将图片对应的像素矩阵变为张量并且标准化 size=(3,224,224))
  15. img = torch.unsqueeze(img, 0) # 执行完后尺寸为(1,3,224,224)
  16. result = self.model(img) # 将图片输入神经网络得到结果
  17. return result

注意事项

通过opencv读取到的图像彩色三通道的BGR格式的图像,而上文我们说到AlexNet需要输入的是RGB格式的图片,所以需要执行img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)这段代码,值得注意的是 transforms.ToTensor() 这段代码 将尺寸为 (224,224,3)的图像矩阵变成了 (3,224,224)。在Pytorch中神经网络处理图片的格式应为 [ batch_size , channel , height , width ] ,所以需要通过torch.unsqueeze来实现这个要求,另外这个操作在训练时是由 train_loader = DataLoader(train_dataset, batch_size=2, shuffle=True) 这行代码实现的,batch_size指的是同时输入神经网络的图片的个数,batch_size为1则输出(1,10)的张量得到一张图片的结果,batch_size为2则会输出(2,10)的张量得到两张图片识别的结果。

另外在此文件中我还附上了绘制混淆矩阵的方法,只需要将代码83行之后的注释全部清除即可(运行会花费一定时间)。

最后附上效果图:

结尾

由于本专栏所有代码都有C++版本,所以提供了一段代码用于将Python版本的模型参数文件转变为C++版本的,如果需要请注意Pytorch与Libtorch版本要一致(在这里是都为1.7.1),读者也可以使用C++进行训练。本文的代码也可以使用其他数据集进行训练,但是需要对代码进行小小的更改,更改方法放在了GitHub中,另外本文用到的手写数据集也可以一并在GitHub中下载

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/104217?site
推荐阅读
相关标签
  

闽ICP备14008679号