0


CNN经典网络模型(二):AlexNet简介及代码实现(PyTorch超详细注释版)

一、开发背景

AlexNet由Hinton和他的学生Alex Krizhevsky设计,模型名字来源于论文第一作者的姓名Alex。该模型以很大的优势获得了2012年ISLVRC竞赛的冠军网络,分类准确率由传统的 70%+提升到 80%+,自那年之后,深度学习开始迅速发展。

ImageNet是一个在2009年创建的图像数据集,从2010年开始到2017年举办了七届的ImageNet 挑战赛——ImageNet Large Scale Visual Recognition ChallengeI (LSVRC),在这个挑战赛上诞生了AlexNet、ZFNet、OverFeat、VGG、Inception、ResNet、WideResNet、FractalNet、DenseNet、ResNeXt、DPN、SENet 等经典模型。

二、网络结构

Alexnet模型为8层深度网络,由5个卷积层和3个全连接层构成,不计LRN层和池化层。AlexNet 跟 LeNet 结构类似,但使用了更多的卷积层和更大的参数空间来拟合大规模数据集 ImageNet。它是浅层神经网络和深度神经网络的分界线,如下图所示:

网络详解:AlexNet网络结构详解(含各层维度大小计算过程)与PyTorch实现

三、模型特点

  1. 使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算;
  2. 使用大数据训练,是百万级ImageNet图像数据,提升算法的准确率,避免过拟合;
  3. 使用ReLU作为激活函数,解决了SIgmoid在网络较深时的梯度消失问题,使收敛更快;
  4. 使用随机丢弃技术(dropout)以0.5的概率选择性地将隐藏层神经元的输出设置为零,以这种方式“dropped out”的神经元既不参与前向传播,也不参与反向传播,避免模型的过拟合;
  5. 重叠最大池化(overlapping max pooling),池化的步长小于核尺寸,使得输出之间会有重叠和覆盖,提升了特征的丰富性,并且避免平均池化的模糊化效果;
  6. 使用 LRN 局部响应归一化(Local Response Normalization)层,对局部神经元的活动创建竞争机制,使得响应较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力,使准确率更高;
  7. 进行数据增强,随机从256256的原始图像中截取224224大小的区域(以及水平翻转的镜像),相当于增强了(256-224)(256-224)2=2048倍的数据量,减轻过拟合,提升泛化能力。

为什么Dropout有效?

Dropout背后理念和集成模型很相似。在Drpout层,不同的神经元组合被关闭,这代表了一种不同的结构,所有这些不同的结构使用一个的子数据集并行地带权重训练,而权重总和为1。如果Dropout层有 n 个神经元,那么会形成 2n 个不同的子结构。在预测时,相当于集成这些模型并取均值。这种结构化的模型正则化技术有利于避免过拟合。Dropout有效的另外一个视点是:由于神经元是随机选择的,所以可以减少神经元之间的相互依赖,从而确保提取出相互独立的重要特征。

四、代码实现

  • model.py :定义AlexNet网络模型
  • train.py:加载数据集并训练,计算loss和accuracy,保存训练好的网络参数
  • predict.py:用自己的数据集进行分类测试
  • spilit_data.py:划分给定的数据集为训练集和测试集

注意:代码实现没有还原两个小型GPU同时运算的设计特点,而是在一个模型中运行

1. model.py

  1. # 导入pytorch库
  2. import torch
  3. # 导入torch.nn模块
  4. from torch import nn
  5. # nn.functional:(一般引入后改名为F)有各种功能组件的函数实现,如:F.conv2d
  6. import torch.nn.functional as F
  7. # 定义AlexNet网络模型
  8. # MyLeNet5(子类)继承nn.Module(父类)
  9. class MyAlexNet(nn.Module):
  10. # 子类继承中重新定义Module类的__init__()和forward()函数
  11. # init():进行初始化,申明模型中各层的定义
  12. def __init__(self):
  13. # super:引入父类的初始化方法给子类进行初始化
  14. super(MyAlexNet, self).__init__()
  15. # 卷积层,输入大小为224*224,输出大小为55*55,输入通道为3,输出为96,卷积核为11,步长为4
  16. self.c1 = nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4, padding=2)
  17. # 使用ReLU作为激活函数
  18. self.ReLU = nn.ReLU()
  19. # MaxPool2d:最大池化操作
  20. # 最大池化层,输入大小为55*55,输出大小为27*27,输入通道为96,输出为96,池化核为3,步长为2
  21. self.s1 = nn.MaxPool2d(kernel_size=3, stride=2)
  22. # 卷积层,输入大小为27*27,输出大小为27*27,输入通道为96,输出为256,卷积核为5,扩充边缘为2,步长为1
  23. self.c2 = nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2)
  24. # 最大池化层,输入大小为27*27,输出大小为13*13,输入通道为256,输出为256,池化核为3,步长为2
  25. self.s2 = nn.MaxPool2d(kernel_size=3, stride=2)
  26. # 卷积层,输入大小为13*13,输出大小为13*13,输入通道为256,输出为384,卷积核为3,扩充边缘为1,步长为1
  27. self.c3 = nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1)
  28. # 卷积层,输入大小为13*13,输出大小为13*13,输入通道为384,输出为384,卷积核为3,扩充边缘为1,步长为1
  29. self.c4 = nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1)
  30. # 卷积层,输入大小为13*13,输出大小为13*13,输入通道为384,输出为256,卷积核为3,扩充边缘为1,步长为1
  31. self.c5 = nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1, padding=1)
  32. # 最大池化层,输入大小为13*13,输出大小为6*6,输入通道为256,输出为256,池化核为3,步长为2
  33. self.s5 = nn.MaxPool2d(kernel_size=3, stride=2)
  34. # Flatten():将张量(多维数组)平坦化处理,神经网络中第0维表示的是batch_size,所以Flatten()默认从第二维开始平坦化
  35. self.flatten = nn.Flatten()
  36. # 全连接层
  37. # Linear(in_features,out_features)
  38. # in_features指的是[batch_size, size]中的size,即样本的大小
  39. # out_features指的是[batch_size,output_size]中的output_size,样本输出的维度大小,也代表了该全连接层的神经元个数
  40. self.f6 = nn.Linear(6*6*256, 4096)
  41. self.f7 = nn.Linear(4096, 4096)
  42. # 全连接层&softmax
  43. self.f8 = nn.Linear(4096, 1000)
  44. self.f9 = nn.Linear(1000, 2)
  45. # forward():定义前向传播过程,描述了各层之间的连接关系
  46. def forward(self, x):
  47. x = self.ReLU(self.c1(x))
  48. x = self.s1(x)
  49. x = self.ReLU(self.c2(x))
  50. x = self.s2(x)
  51. x = self.ReLU(self.c3(x))
  52. x = self.ReLU(self.c4(x))
  53. x = self.ReLU(self.c5(x))
  54. x = self.s5(x)
  55. x = self.flatten(x)
  56. x = self.f6(x)
  57. # Dropout:随机地将输入中50%的神经元激活设为0,即去掉了一些神经节点,防止过拟合
  58. # “失活的”神经元不再进行前向传播并且不参与反向传播,这个技术减少了复杂的神经元之间的相互影响
  59. x = F.dropout(x, p=0.5)
  60. x = self.f7(x)
  61. x = F.dropout(x, p=0.5)
  62. x = self.f8(x)
  63. x = F.dropout(x, p=0.5)
  64. x = self.f9(x)
  65. return x
  66. # 每个python模块(python文件)都包含内置的变量 __name__,当该模块被直接执行的时候,__name__ 等于文件名(包含后缀 .py )
  67. # 如果该模块 import 到其他模块中,则该模块的 __name__ 等于模块名称(不包含后缀.py)
  68. # “__main__” 始终指当前执行模块的名称(包含后缀.py)
  69. # if确保只有单独运行该模块时,此表达式才成立,才可以进入此判断语法,执行其中的测试代码,反之不行
  70. if __name__ == '__main__':
  71. # rand:返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数,此处为四维张量
  72. x = torch.rand([1, 3, 224, 224])
  73. # 模型实例化
  74. model = MyAlexNet()
  75. y = model(x)

2. train.py

  1. import torch
  2. from torch import nn
  3. from model import MyAlexNet
  4. from torch.optim import lr_scheduler
  5. from torchvision import transforms
  6. from torchvision.datasets import ImageFolder
  7. from torch.utils.data import DataLoader
  8. import os
  9. import matplotlib.pyplot as plt
  10. # 解决中文显示问题
  11. # 运行配置参数中的字体(font)为黑体(SimHei)
  12. plt.rcParams['font.sans-serif'] = ['simHei']
  13. # 运行配置参数总的轴(axes)正常显示正负号(minus)
  14. plt.rcParams['axes.unicode_minus'] = False
  15. ROOT_TRAIN = 'D:/pycharm/AlexNet/data/train'
  16. ROOT_TEST = 'D:/pycharm/AlexNet/data/val'
  17. # 将图像的像素值归一化到[-1,1]之间
  18. normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  19. # Compose():将多个transforms的操作整合在一起
  20. train_transform = transforms.Compose([
  21. # Resize():把给定的图像随机裁剪到指定尺寸
  22. transforms.Resize((224, 224)),
  23. # RandomVerticalFlip():以0.5的概率竖直翻转给定的PIL图像
  24. transforms.RandomVerticalFlip(),
  25. # ToTensor():数据转化为Tensor格式
  26. transforms.ToTensor(),
  27. normalize])
  28. val_transform = transforms.Compose([
  29. transforms.Resize((224, 224)),
  30. transforms.ToTensor(),
  31. normalize])
  32. # 加载训练数据集
  33. # ImageFolder:假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,其构造函数如下:
  34. # ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
  35. # root:在root指定的路径下寻找图像,transform:对输入的图像进行的转换操作
  36. train_dataset = ImageFolder(ROOT_TRAIN, transform=train_transform)
  37. # DataLoader:将读取的数据按照batch size大小封装给训练集
  38. # dataset (Dataset):加载数据的数据集
  39. # batch_size (int, optional):每个batch加载多少个样本(默认: 1)
  40. # shuffle (bool, optional):设置为True时会在每个epoch重新打乱数据(默认: False)
  41. train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
  42. # 加载训练数据集
  43. val_dataset = ImageFolder(ROOT_TEST, transform=val_transform)
  44. val_dataloader = DataLoader(val_dataset, batch_size=32, shuffle=True)
  45. # 如果有NVIDA显卡,可以转到GPU训练,否则用CPU
  46. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  47. # 模型实例化,将模型转到device
  48. model = MyAlexNet().to(device)
  49. # 定义损失函数(交叉熵损失)
  50. loss_fn = nn.CrossEntropyLoss()
  51. # 定义优化器(随机梯度下降法)
  52. # params(iterable):要训练的参数,一般传入的是model.parameters()
  53. # lr(float):learning_rate学习率,也就是步长
  54. # momentum(float, 可选):动量因子(默认:0),矫正优化率
  55. optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
  56. # 学习率每隔10轮变为原来的0.5
  57. # StepLR:用于调整学习率,一般情况下会设置随着epoch的增大而逐渐减小学习率从而达到更好的训练效果
  58. # optimizer (Optimizer):更改学习率的优化器
  59. # step_size(int):每训练step_size个epoch,更新一次参数
  60. # gamma(float):更新lr的乘法因子
  61. lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
  62. # 定义训练函数
  63. def train(dataloader, model, loss_fn, optimizer):
  64. loss, current, n = 0.0, 0.0, 0
  65. # dataloader: 传入数据(数据包括:训练数据和标签)
  66. # enumerate():用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中
  67. # enumerate返回值有两个:一个是序号,一个是数据(包含训练数据和标签)
  68. # x:训练数据(inputs)(tensor类型的),y:标签(labels)(tensor类型)
  69. for batch, (x, y) in enumerate(dataloader):
  70. # 前向传播
  71. image, y = x.to(device), y.to(device)
  72. # 计算训练值
  73. output = model(image)
  74. # 计算观测值(label)与训练值的损失函数
  75. cur_loss = loss_fn(output, y)
  76. # torch.max(input, dim)函数
  77. # input是具体的tensor,dim是max函数索引的维度,0是每列的最大值,1是每行的最大值输出
  78. # 函数会返回两个tensor,第一个tensor是每行的最大值;第二个tensor是每行最大值的索引
  79. _, pred = torch.max(output, axis=1)
  80. # 计算每批次的准确率
  81. # output.shape[0]为该批次的多少,output的一维长度
  82. # torch.sum()对输入的tensor数据的某一维度求和
  83. cur_acc = torch.sum(y == pred)/output.shape[0]
  84. # 反向传播
  85. # 清空过往梯度
  86. optimizer.zero_grad()
  87. # 反向传播,计算当前梯度
  88. cur_loss.backward()
  89. # 根据梯度更新网络参数
  90. optimizer.step()
  91. # item():得到元素张量的元素值
  92. loss += cur_loss.item()
  93. current += cur_acc.item()
  94. n = n + 1
  95. train_loss = loss / n
  96. train_acc = current / n
  97. # 计算训练的错误率
  98. print('train_loss==' + str(train_loss))
  99. # 计算训练的准确率
  100. print('train_acc' + str(train_acc))
  101. return train_loss, train_acc
  102. # 定义验证函数
  103. def val(dataloader, model, loss_fn):
  104. loss, current, n = 0.0, 0.0, 0
  105. # eval():如果模型中有Batch Normalization和Dropout,则不启用,以防改变权值
  106. model.eval()
  107. with torch.no_grad():
  108. for batch, (x, y) in enumerate(dataloader):
  109. # 前向传播
  110. image, y = x.to(device), y.to(device)
  111. output = model(image)
  112. cur_loss = loss_fn(output, y)
  113. _, pred = torch.max(output, axis=1)
  114. cur_acc = torch.sum(y == pred)/output.shape[0]
  115. loss += cur_loss.item()
  116. current += cur_acc.item()
  117. n = n+1
  118. val_loss = loss / n
  119. val_acc = current / n
  120. # 计算验证的错误率
  121. print('val_loss=' + str(val_loss))
  122. # 计算验证的准确率
  123. print('val_acc=' + str(val_acc))
  124. return val_loss, val_acc
  125. # 定义画图函数
  126. # 错误率
  127. def matplot_loss(train_loss, val_loss):
  128. # 参数label = ''传入字符串类型的值,也就是图例的名称
  129. plt.plot(train_loss, label='train_loss')
  130. plt.plot(val_loss, label='val_loss')
  131. # loc代表了图例在整个坐标轴平面中的位置(一般选取'best'这个参数值)
  132. plt.legend(loc='best')
  133. plt.xlabel('loss')
  134. plt.ylabel('epoch')
  135. plt.title("训练集和验证集的loss值对比图")
  136. plt.show()
  137. # 准确率
  138. def matplot_acc(train_acc, val_acc):
  139. plt.plot(train_acc, label = 'train_acc')
  140. plt.plot(val_acc, label = 'val_acc')
  141. plt.legend(loc = 'best')
  142. plt.xlabel('acc')
  143. plt.ylabel('epoch')
  144. plt.title("训练集和验证集的acc值对比图")
  145. plt.show()
  146. #开始训练
  147. loss_train = []
  148. acc_train = []
  149. loss_val = []
  150. acc_val = []
  151. # 训练次数
  152. epoch = 20
  153. # 用于判断最佳模型
  154. min_acc = 0
  155. for t in range(epoch):
  156. lr_scheduler.step()
  157. print(f"epoch{t+1}\n----------")
  158. # 训练模型
  159. train_loss, train_acc = train(train_dataloader, model, loss_fn, optimizer)
  160. # 验证模型
  161. val_loss, val_acc = val(val_dataloader, model, loss_fn)
  162. loss_train.append(train_loss)
  163. acc_train.append(train_acc)
  164. loss_val.append(val_loss)
  165. acc_val.append(val_acc)
  166. # 保存最好的模型权重
  167. if val_acc > min_acc:
  168. folder = 'save_model'
  169. # path.exists:判断括号里的文件是否存在的意思,括号内可以是文件路径,存在为True
  170. if not os.path.exists(folder):
  171. # os.mkdir() 方法用于以数字权限模式创建目录
  172. os.mkdir('save_model')
  173. min_acc = val_acc
  174. print(f"save best model,第{t+1}轮")
  175. # torch.save(state, dir):保存模型等相关参数,dir表示保存文件的路径+保存文件名
  176. # model.state_dict():返回的是一个OrderedDict,存储了网络结构的名字和对应的参数
  177. torch.save(model.state_dict(), 'save_model/best_model.pth')
  178. # 保存最后一轮权重
  179. if t == epoch-1:
  180. torch.save(model.state_dict(), 'save_model/best_model.pth')
  181. matplot_loss(loss_train, loss_val)
  182. matplot_acc(acc_train, acc_val)
  183. print('done')

3. predict.py

  1. import torch
  2. from model import MyAlexNet
  3. from torch.autograd import Variable
  4. from torchvision import transforms
  5. from torchvision.transforms import ToPILImage
  6. from torchvision.datasets import ImageFolder
  7. from torch.utils.data import DataLoader
  8. ROOT_TRAIN = 'D:/pycharm/AlexNet/data/train'
  9. ROOT_TEST = 'D:/pycharm/AlexNet/data/val'
  10. # 将图像的像素值归一化到[-1,1]之间
  11. normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
  12. val_transform = transforms.Compose([
  13. transforms.Resize((224, 224)),
  14. transforms.ToTensor(),
  15. normalize
  16. ])
  17. # 加载训练数据集
  18. val_dataset = ImageFolder(ROOT_TEST, transform=val_transform)
  19. # 如果有NVIDA显卡,转到GPU训练,否则用CPU
  20. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  21. # 模型实例化,将模型转到device
  22. model = MyAlexNet().to(device)
  23. # 加载train.py里训练好的模型
  24. model.load_state_dict(torch.load(r'D:\pycharm\AlexNet\save_model\best_model.pth'))
  25. # 结果类型
  26. classes = [
  27. "cat",
  28. "dog"
  29. ]
  30. # 把Tensor转化为图片,方便可视化
  31. show = ToPILImage()
  32. # 进入验证阶段
  33. model.eval()
  34. for i in range(1):
  35. x, y = val_dataset[i][0], val_dataset[i][1]
  36. # show():显示图片
  37. show(x).show()
  38. # torch.unsqueeze(input, dim),input(Tensor):输入张量,dim (int):插入维度的索引,最终扩展张量维度为4维
  39. x = Variable(torch.unsqueeze(x, dim=0).float(), requires_grad=False).to(device)
  40. with torch.no_grad():
  41. pred = model(x)
  42. # argmax(input):返回指定维度最大值的序号
  43. # 得到预测类别中最高的那一类,再把最高的这一类对应classes中的那一类
  44. predicted, actual = classes[torch.argmax(pred[0])], classes[y]
  45. # 输出预测值与真实值
  46. print(f'predicted:"{predicted}", actual:"{actual}"')

4. spilit_data.py

  1. import os
  2. from shutil import copy
  3. import random
  4. # 如果file不存在,创建file
  5. def mkfile(file):
  6. if not os.path.exists(file):
  7. os.makedirs(file)
  8. # 获取data文件夹下所有除.txt文件以外所有文件夹名(即需要分类的类名)
  9. # os.listdir():用于返回指定的文件夹包含的文件或文件夹的名字的列表
  10. file_path = 'D:/pycharm/AlexNet/data_name'
  11. pet_class = [cla for cla in os.listdir(file_path) if ".txt" not in cla]
  12. # 创建训练集train文件夹,并由类名在其目录下创建子目录
  13. mkfile('data/train')
  14. for cla in pet_class:
  15. mkfile('data/train/' + cla)
  16. # 创建验证集val文件夹,并由类名在其目录下创建子目录
  17. mkfile('data/val')
  18. for cla in pet_class:
  19. mkfile('data/val/' + cla)
  20. # 划分比例,训练集 : 验证集 = 8 : 2
  21. split_rate = 0.2
  22. # 遍历所有类别的图像并按比例分成训练集和验证集
  23. for cla in pet_class:
  24. # 某一类别的子目录
  25. cla_path = file_path + '/' + cla + '/'
  26. # iamges列表存储了该目录下所有图像的名称
  27. images = os.listdir(cla_path)
  28. num = len(images)
  29. # 从images列表中随机抽取k个图像名称
  30. # random.sample:用于截取列表的指定长度的随机数,返回列表
  31. # eval_index保存验证集val的图像名称
  32. eval_index = random.sample(images, k=int(num * split_rate))
  33. for index, image in enumerate(images):
  34. if image in eval_index:
  35. image_path = cla_path + image
  36. new_path = 'data/val/' + cla
  37. # copy():将源文件的内容复制到目标文件或目录
  38. copy(image_path, new_path)
  39. # 其余图像保存在训练集train中
  40. else:
  41. image_path = cla_path + image
  42. new_path = 'data/train/' + cla
  43. copy(image_path, new_path)
  44. # '\r' 回车,回到当前行的行首,而不会换到下一行,如果接着输出,本行以前的内容会被逐一覆盖
  45. # <模板字符串>.format(<逗号分隔的参数>)
  46. # end="":将print自带的换行用end中指定的str代替
  47. print("\r[{}] processing [{}/{}]".format(cla, index + 1, num), end="")
  48. print()
  49. print("processing done!")

五、参考内容

1. 文章

《ImageNet Classification with Deep Convolutional Neural Networks》http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf

2. 视频

从0开始撸代码--手把手教你搭建AlexNet网络模型训练自己的数据集(猫狗分类)https://www.bilibili.com/video/BV18L4y167jr?p=4&vd_source=78dedbc0ab33a4edb884e1ef98f3c6b8

AlexNet代码(超详细注释)+数据集下载地址:

https://download.csdn.net/download/qq_43307074/86730471

标签: cnn 网络 pytorch

本文转载自: https://blog.csdn.net/qq_43307074/article/details/126027818
版权归原作者 华科附小第一名 所有, 如有侵权,请联系我们删除。

“CNN经典网络模型(二):AlexNet简介及代码实现(PyTorch超详细注释版)”的评论:

还没有评论