当前位置:   article > 正文

pytorch实现AlexNet(含完整代码)_pytorch alexnet

pytorch alexnet

写在前面

 

        本人小白,下面的文字简单记录自己在pytorch的基础之上,实现AlexNet文章中的模型,也希望能和其他的朋友一起交流心得。(记录贴,只用来整理自己思路

AlexNet论文

        AlexNet的文章中主要介绍了利用deep convolutional neural networks(深度卷积神经网络)在数据集中取得很好的结果。在当时,其开创性主要包括以下几个方面:模型构建,ReLu(),Local Response Normalization,Overlapping Pooling以及Reducing Overfitting。

ReLu()

        当时的神经网络中激活函数主要采用的是tanh(x)和sigmoid(x)函数,文章中提出使用Relu()函数来降低训练的时间,具体区别可以看这里:Relu()和tanh(),sigmoid()

LNR

        LNR在网络中主要用来归一化,但是在后来神经网络的发展过程中已经不经常使用,所以这里不再赘述

Overlapping Pooling

        文章中池化层的stride=2,kernal_size=3,因此部分数据会有重叠,这种池化层的设计与stride=2,kernal_size=2的相比,会降低0.4%的错误率

Reducing Overfitting

        减小过拟合的方法有两种,数据增强和DropOut。

模型构建

        AlexNet的卷积网络包含5个卷积层,3个全连接层,具体的顺序如下:

         其中在1,2层卷积之后,使用Max pooling池化层,3,4,5层之后再次使用Max Pooling层。之后是三个全连接层。

        原文在两个GPU上运行,因此会把数据分为两部分,而且图片格式为224X224X3,之后的卷积层、池化层的stride,padding都需要手动计算。

数据集

数据集下载

        这次采用的数据集是猫狗数据集,提取码485q    网盘链接

        下载之后解压,会得到一个训练集,其中猫和狗的图片会在同一个文件夹中,因此我们需要先将其分类,再进行下一步的操作。(因为在训练时如果不分类无法进行,当然,如果有更好的方法,望告知)

      

数据集分类

        首先,新建一个文件夹 'train_0',下面包含两个空文件夹 '0' ,'1' 之后会将train里面的文件分别移动到'train_0'下面的'0','1'中。   

  1. import os
  2. import re
  3. import shutil
  4. origin_path=r'E:\buttle\kaggle\train'
  5. target_path_0=r'E:\buttle\kaggle\train_0\0'
  6. target_path_1=r'E:\buttle\kaggle\train_0\1'
  7. file_list=os.listdir(origin_path)
  8. for i in range(len(file_list)):
  9. old_path=os.path.join(origin_path,file_list[i])
  10. print(file_list[i])
  11. result=re.findall(r'\w+',file_list[i])[0]
  12. if result=='cat':
  13. shutil.move(old_path,target_path_0)
  14. else:
  15. shutil.move(old_path,target_path_1)

        origin_path,target_path_0,tarfet_path_1的绝对路径对照自己的进行设置

file_list=os.listdir(origin_path)

        接收路径,返回指定的路径下文件或者文件夹列表,列表元素类型为 ‘str’,实际上列表中元素均为文件夹下图片的名称

  1. for i in range(len(file_list)):
  2. old_path=os.path.join(origin_path,file_list[i])
  3. result=re.findall(r'\w+',file_list[i])[0]

        遍历列表,old_path指的是列表下的每一个图片的路径

       观察到每一张图片的名称都会有‘cat’,‘dog’这种含有分类信息的字符,因此匹配图片名称中的字符。

        findall()函数返回图片名称中字符的列表,其中re.findall()[0]元素是‘cat'或者‘dog’,因此可以作为分类的条件。

  1. if result=='cat':
  2. shutil.move(old_path,target_path_0)
  3. else:
  4. shutil.move(old_path,target_path_1)

        根据名称,把图片移动到对应的文件夹中

AlexNet代码复现

网络模型构建

1.卷积层的构建

  1. class AlexNet(nn.Module):
  2. def __init__(self,num_classes=2):
  3. super(AlexNet, self).__init__()
  4. self.features=nn.Sequential(
  5. nn.Conv2d(3,48, kernel_size=11),
  6. nn.ReLU(inplace=True),
  7. nn.MaxPool2d(kernel_size=3,stride=2),
  8. nn.Conv2d(48,128, kernel_size=5, padding=2),
  9. nn.ReLU(inplace=True),
  10. nn.MaxPool2d(kernel_size=3,stride=2),
  11. nn.Conv2d(128,192,kernel_size=3,stride=1,padding=1),
  12. nn.ReLU(inplace=True),
  13. nn.Conv2d(192,192,kernel_size=3,stride=1,padding=1),
  14. nn.ReLU(inplace=True),
  15. nn.Conv2d(192,128,kernel_size=3,stride=1,padding=1),
  16. nn.ReLU(inplace=True),
  17. nn.MaxPool2d(kernel_size=3,stride=2),
  18. )

        类的继承,Sequential的使用        

        其中Conv2的()的参数,尤其是有关stride和padding的,需要自己计算。

2.全连接层的构建

  1. self.classifier=nn.Sequential(
  2. nn.Linear(6*6*128,2048),
  3. nn.ReLU(inplace=True),
  4. nn.Dropout(0.5),
  5. nn.Linear(2048,2048),
  6. nn.ReLU(inplace=True),
  7. nn.Dropout(),
  8. nn.Linear(2048,num_classes),
  9. )

        这里面我没有过多关注Dropout()应该放的位置

3.前向传播

  1. def forward(self,x):
  2. x=self.features(x)
  3. x=torch.flatten(x,start_dim=1)
  4. x=self.classifier(x)
  5. return x

        中间卷积层---全连接层,需要将x由6*6*128的张量变为一维向量,因此需要用到flatten()

        flatten()和view()函数有什么区别?

数据增强

  1. def DataEnhance(sourth_path,aim_dir,size):
  2. name=0
  3. #得到源文件的文件夹
  4. file_list=os.listdir(sourth_path)
  5. #创建目标文件的文件夹
  6. if not os.path.exists(aim_dir):
  7. os.mkdir(aim_dir)
  8. for i in file_list:
  9. img=Image.open('%s\%s'%(sourth_path,i))
  10. name+=1
  11. transform1=transforms.Compose([
  12. transforms.ToTensor(),
  13. transforms.ToPILImage(),
  14. transforms.Resize(size),
  15. ])
  16. img1=transform1(img)
  17. img1.save('%s/%s'%(aim_dir,name))

           数据增强,实质上就是在数据过少的情况下,对原有的数据进行灰度、裁切、旋转、镜像、明度、色调、饱和度变化的一系列过程,用来增加数据量。

        ima-=Ima.open(Ima_path) --打开路径下的图片,并且类型为PIL类型

        transform:在torchvision下面的类,主要用于图形的变换

        transform.Compose()--串联多个图形变换

        transfrom.Totensor()和transform.ToPILImage()--将输入数据的通道进行转换,并且将数据归一化到[0,1],详细解释可以看这里:关于Totensor和ToPILImage

        transform.Resize()--重新设置图片大小,通过拉伸改变图片

        最后将源路径图片变换后保存到目标路径

训练集、测试集、验证集的导入

        在前面,我们已经将图片分类好,并且经过数据增强,增加了数据集的数量,下一步,我们准备将数据集如何导入代码

  1. #归一化处理
  2. normalize=transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])
  3. path_1=r'E:\buttle\kaggle\train'
  4. trans_1=transforms.Compose([
  5. transforms.Resize((65,65)),
  6. transforms.ToTensor(),
  7. normalize,
  8. ])
  9. #数据集
  10. train_set=ImageFolder(root=path_1,transform=trans_1)
  11. #数据加载器
  12. train_loader=torch.utils.data.DataLoader(train_set,batch_size=BATCH_SIZE,
  13. shuffle=True,num_workers=0)

transform.Normalize()--逐通道对图像标准化,可以加快模型的收敛性,普遍认为   

                                        ​​​​​​​      output=(input-mean)/std

更详细的解析参考:关于Normalize的理解

ImageFolder(root,transform=None,target_transform=None,loader=default_loader)

其中root--存储路径,transform--图片转换

该函数默认数据集已经自动按照要求分类,每一个文件夹下面保存一个类别的图片

  1. dataset=ImageFolder(...)
  2. print(dataset.classes)
  3. >>>['cat','dog']
  4. print(dataset.calss_to_idx)
  5. >>>['cat':0,'dog':1]
  6. print(data.imgs)
  7. >>>[('....jpg',0),('..jpg',1)]

        个人认为ImageFolder为数据集中每一种图片生成‘label’,并把label和图片一一对应

        torch.utils.data.DataLoader--用来将训练集分成多个小组,通过迭代抛出data,其中data中包含了图像的数据(inputs)和标签(labels)

训练

  1. #定义模型
  2. model=AlexNet().to(DEVICE)
  3. #优化器的选择
  4. optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.9,weight_decay=0.0005)
  1. def train_model(model,device,train_loader,optimizer,epoch):
  2. train_loss=0
  3. model.train()
  4. for batch_index,(data,label) in enumerate(train_loader):
  5. data,label=data.to(device),label.to(device)
  6. optimizer.zero_grad()
  7. output=model(data)
  8. loss=F.cross_entropy(output,label)
  9. loss.backward()
  10. optimizer.step()
  11. if batch_index%300==0:
  12. train_loss=loss.item()
  13. print('Train Epoch:{}\ttrain loss:{:.6f}'.format(epoch,loss.item()))
  14. return train_loss

        enumerate(iterable,start=0)--返回一个枚举对象,其中包含一个计数值i和通过迭代获取的值data,data中包括了图像数据(inputs)和标签(labels)

        model.train()--模型进入训练模式

        optimizer.zero_grad()--梯度清零,重新开始记录

        loss.item()—只返回loss函数中数据类型,不去管梯度回传的部分,因为loss()中会包含梯度回传,好呢容易跑满显寸

  1. def test_model(model,device,test_loader):
  2. model.eval()
  3. correct=0.0
  4. test_loss=0.0
  5. #不需要梯度的记录
  6. with torch.no_grad():
  7. for data,label in test_loader:
  8. data,label=data.to(device),label.to(device)
  9. output=model(data)
  10. test_loss+=F.cross_entropy(output,label).item()
  11. pred=output.argmax(dim=1)
  12. correct+=pred.eq(label.view_as(pred)).sum().item()
  13. test_loss/=len(test_loader.dataset)
  14. print('Test_average_loss:{:.4f},Accuracy:{:3f}\n'.format(
  15. test_loss,100*correct/len(test_loader.dataset)
  16. ))
  17. acc=100*correct/len(test_loader.dataset)
  18. return test_loss,acc

测试

model.eval()--模型的测试模式(评估模式),关闭BN层和Dropout层,同时,在评估模式时,使用‘with torch.no_grad():',因为评估模式不需要梯度的回传。

pre=output.argmax(dim=1)--返回在dim=1的维度上(每一列)的最大值的索引号

pred.eq(label.view_as(pred))--其中view_as将label的格式返回成与pred相同的张量

训练开始

  1. list=[]
  2. Train_Loss_list=[]
  3. Valid_Loss_list=[]
  4. Valid_Accuracy_list=[]
  5. #Epoc的调用
  6. for epoch in range(1,EPOCH+1):
  7. #训练集训练
  8. train_loss=train_model(model,DEVICE,train_loader,optimizer,epoch)
  9. Train_Loss_list.append(train_loss)
  10. torch.save(model,r'E:\buttle\kaggle\save_model\model%s.pth'%epoch)
  11. #验证集进行验证
  12. test_loss,acc=test_model(model,DEVICE,valid_loader)
  13. Valid_Loss_list.append(test_loss)
  14. Valid_Accuracy_list.append(acc)
  15. list.append(test_loss)

        torch.save(model,path)--将模型中可学习的参数保存到指定的路径,在后续的数据处理中,可以将表现好的模型参数加载出来重新进行测试

  1. #验证集的test_loss
  2. min_num=min(list)
  3. min_index=list.index(min_num)
  4. print('model%s'%(min_index+1))
  5. print('验证集最高准确率: ')
  6. print('{}'.format(Valid_Accuracy_list[min_index]))
  7. #取最好的进入测试集进行测试
  8. model=torch.load(r'E:\buttle\kaggle\save_model\model%s.pth'%(min_index+1))
  9. model.eval()
  10. accuracy=test_model(model,DEVICE,test_loader)
  11. print('测试集准确率')
  12. print('{}%'.format(accuracy))

        torch.load(PATH)--载入模型,然后进行测试

绘图

  1. #字体设置,字符显示
  2. plt.rcParams['font.sans-serif']=['SimHei']
  3. plt.rcParams['axes.unicode_minus']=False
  4. #坐标轴变量含义
  5. x1=range(0,EPOCH)
  6. y1=Train_Loss_list
  7. y2=Valid_Loss_list
  8. y3=Valid_Accuracy_list
  9. #图表位置
  10. plt.subplot(221)
  11. #线条
  12. plt.plot(x1,y1,'-o')
  13. #坐标轴批注
  14. plt.ylabel('训练集损失')
  15. plt.xlabel('轮数')

plt.reParams[]—设置字体,因为对于中文字体,如果设置不好字体会出现乱码

plt-plot(x,y,'-o') —线条设置

Q&A

准确率为什么*100?

——其实不*100得到小数,于结果影响不大

数据增强?

——因为数据集图片12500张,所以我在测试的时候没有进行数据的增强

完整代码

  1. import torch
  2. import os
  3. from torch import nn
  4. from torch.nn import functional as F
  5. from torch.autograd import Variable
  6. import matplotlib.pyplot as plt
  7. from torchvision.datasets import ImageFolder
  8. import torch.optim as optim
  9. import torch.utils.data
  10. from PIL import Image
  11. import torchvision.transforms as transforms
  12. #超参数设置
  13. DEVICE=torch.device('cuda'if torch.cuda.is_available() else 'cpu')
  14. EPOCH=2
  15. BATCH_SIZE=256
  16. #网络模型构建
  17. class AlexNet(nn.Module):
  18. def __init__(self,num_classes=2):
  19. super(AlexNet, self).__init__()
  20. self.features=nn.Sequential(
  21. nn.Conv2d(3,48, kernel_size=11),
  22. nn.ReLU(inplace=True),
  23. nn.MaxPool2d(kernel_size=3,stride=2),
  24. nn.Conv2d(48,128, kernel_size=5, padding=2),
  25. nn.ReLU(inplace=True),
  26. nn.MaxPool2d(kernel_size=3,stride=2),
  27. nn.Conv2d(128,192,kernel_size=3,stride=1,padding=1),
  28. nn.ReLU(inplace=True),
  29. nn.Conv2d(192,192,kernel_size=3,stride=1,padding=1),
  30. nn.ReLU(inplace=True),
  31. nn.Conv2d(192,128,kernel_size=3,stride=1,padding=1),
  32. nn.ReLU(inplace=True),
  33. nn.MaxPool2d(kernel_size=3,stride=2),
  34. )
  35. self.classifier=nn.Sequential(
  36. nn.Linear(6*6*128,2048),
  37. nn.ReLU(inplace=True),
  38. nn.Dropout(0.5),
  39. nn.Linear(2048,2048),
  40. nn.ReLU(inplace=True),
  41. nn.Dropout(),
  42. nn.Linear(2048,num_classes),
  43. )
  44. def forward(self,x):
  45. x=self.features(x)
  46. x=torch.flatten(x,start_dim=1)
  47. x=self.classifier(x)
  48. return x
  49. #数据预处理
  50. #数据路径
  51. # aim_dir0=r'shdj'
  52. # aim_dir1=r'sdj'
  53. # source_path0=r'efeeeeee'
  54. # source_path1=r'ddddddddd'
  55. #数据增强
  56. # def DataEnhance(sourth_path,aim_dir,size):
  57. # name=0
  58. # #得到源文件的文件夹
  59. # file_list=os.listdir(sourth_path)
  60. # #创建目标文件的文件夹
  61. # if not os.path.exists(aim_dir):
  62. # os.mkdir(aim_dir)
  63. #
  64. # for i in file_list:
  65. # img=Image.open('%s\%s'%(sourth_path,i))
  66. # print(img.size)
  67. #
  68. # name+=1
  69. # transform1=transforms.Compose([
  70. # transforms.ToTensor(),
  71. # transforms.ToPILImage(),
  72. # transforms.Resize(size),
  73. # ])
  74. # img1=transform1(img)
  75. # img1.save('%s/%s'%(aim_dir,name))
  76. #
  77. # name+=1
  78. # transform2=transforms.Compose([
  79. # transforms.ToTensor(),
  80. # transforms.ToPILImage(),
  81. # transforms.ColorJitter(brightness=0.5,contrast=0.5,hue=0.5)
  82. # ])
  83. # img2 = transform1(img)
  84. # img2.save('%s/%s' % (aim_dir, name))
  85. #
  86. # name+=1
  87. # transform3=transforms.Compose([
  88. # transforms.ToTensor(),
  89. # transforms.ToPILImage(),
  90. # transforms.RandomCrop(227,pad_if_needed=True),
  91. # transforms.Resize(size)
  92. # ])
  93. # img3 = transform1(img)
  94. # img3.save('%s/%s' % (aim_dir, name))
  95. #
  96. # name+=1
  97. # transform4=transforms.Compose([
  98. # transforms.Compose(),
  99. # transforms.ToPILImage(),
  100. # transforms.RandomRotation(60),
  101. # transforms.Resize(size),
  102. # ])
  103. # img4 = transform1(img)
  104. # img4.save('%s/%s' % (aim_dir, name))
  105. #
  106. #
  107. # DataEnhance(source_path0,aim_dir0,size)
  108. # DataEnhance(source_path1,aim_dir1,size)
  109. #对文件区分为训练集,测试集,验证集
  110. #归一化处理
  111. normalize=transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])
  112. #训练集
  113. path_1=r'E:\buttle\kaggle\train'
  114. trans_1=transforms.Compose([
  115. transforms.Resize((65,65)),
  116. transforms.ToTensor(),
  117. normalize,
  118. ])
  119. #数据集
  120. train_set=ImageFolder(root=path_1,transform=trans_1)
  121. #数据加载器
  122. train_loader=torch.utils.data.DataLoader(train_set,batch_size=BATCH_SIZE,
  123. shuffle=True,num_workers=0)
  124. #测试集
  125. path_2=r'E:\buttle\kaggle\test'
  126. trans_2=transforms.Compose([
  127. transforms.Resize((65,65)),
  128. transforms.ToTensor(),
  129. normalize,
  130. ])
  131. test_data=ImageFolder(root=path_2,transform=trans_2)
  132. test_loader=torch.utils.data.DataLoader(test_data,batch_size=BATCH_SIZE,
  133. shuffle=True,num_workers=0)
  134. #验证集
  135. path_3=r'E:\buttle\kaggle\valid'
  136. valid_data=ImageFolder(root=path_2,transform=trans_2)
  137. valid_loader=torch.utils.data.DataLoader(valid_data,batch_size=BATCH_SIZE,
  138. shuffle=True,num_workers=0)
  139. #定义模型
  140. model=AlexNet().to(DEVICE)
  141. #优化器的选择
  142. optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.9,weight_decay=0.0005)
  143. #训练过程
  144. def train_model(model,device,train_loader,optimizer,epoch):
  145. train_loss=0
  146. model.train()
  147. for batch_index,(data,label) in enumerate(train_loader):
  148. data,label=data.to(device),label.to(device)
  149. optimizer.zero_grad()
  150. output=model(data)
  151. loss=F.cross_entropy(output,label)
  152. loss.backward()
  153. optimizer.step()
  154. if batch_index%300==0:
  155. train_loss=loss.item()
  156. print('Train Epoch:{}\ttrain loss:{:.6f}'.format(epoch,loss.item()))
  157. return train_loss
  158. #测试部分的函数
  159. def test_model(model,device,test_loader):
  160. model.eval()
  161. correct=0.0
  162. test_loss=0.0
  163. #不需要梯度的记录
  164. with torch.no_grad():
  165. for data,label in test_loader:
  166. data,label=data.to(device),label.to(device)
  167. output=model(data)
  168. test_loss+=F.cross_entropy(output,label).item()
  169. pred=output.argmax(dim=1)
  170. correct+=pred.eq(label.view_as(pred)).sum().item()
  171. test_loss/=len(test_loader.dataset)
  172. print('Test_average_loss:{:.4f},Accuracy:{:3f}\n'.format(
  173. test_loss,100*correct/len(test_loader.dataset)
  174. ))
  175. acc=100*correct/len(test_loader.dataset)
  176. return test_loss,acc
  177. #训练开始
  178. list=[]
  179. Train_Loss_list=[]
  180. Valid_Loss_list=[]
  181. Valid_Accuracy_list=[]
  182. #Epoc的调用
  183. for epoch in range(1,EPOCH+1):
  184. #训练集训练
  185. train_loss=train_model(model,DEVICE,train_loader,optimizer,epoch)
  186. Train_Loss_list.append(train_loss)
  187. torch.save(model,r'E:\buttle\kaggle\save_model\model%s.pth'%epoch)
  188. #验证集进行验证
  189. test_loss,acc=test_model(model,DEVICE,valid_loader)
  190. Valid_Loss_list.append(test_loss)
  191. Valid_Accuracy_list.append(acc)
  192. list.append(test_loss)
  193. #验证集的test_loss
  194. min_num=min(list)
  195. min_index=list.index(min_num)
  196. print('model%s'%(min_index+1))
  197. print('验证集最高准确率: ')
  198. print('{}'.format(Valid_Accuracy_list[min_index]))
  199. #取最好的进入测试集进行测试
  200. model=torch.load(r'E:\buttle\kaggle\save_model\model%s.pth'%(min_index+1))
  201. model.eval()
  202. accuracy=test_model(model,DEVICE,test_loader)
  203. print('测试集准确率')
  204. print('{}%'.format(accuracy))
  205. #绘图
  206. #字体设置,字符显示
  207. plt.rcParams['font.sans-serif']=['SimHei']
  208. plt.rcParams['axes.unicode_minus']=False
  209. #坐标轴变量含义
  210. x1=range(0,EPOCH)
  211. y1=Train_Loss_list
  212. y2=Valid_Loss_list
  213. y3=Valid_Accuracy_list
  214. #图表位置
  215. plt.subplot(221)
  216. #线条
  217. plt.plot(x1,y1,'-o')
  218. #坐标轴批注
  219. plt.ylabel('训练集损失')
  220. plt.xlabel('轮数')
  221. plt.subplot(222)
  222. plt.plot(x1,y2,'-o')
  223. plt.ylabel('验证集损失')
  224. plt.xlabel('轮数')
  225. plt.subplot(212)
  226. plt.plot(x1,y3,'-o')
  227. plt.ylabel('验证集准确率')
  228. plt.xlabel('轮数')
  229. #显示
  230. plt.show()

最后,我是按照B站up的视频写的,所以在这儿放个链接,up的视频讲的更加详细。

https://www.bilibili.com/video/BV1aK4y1p7dyhttps://www.bilibili.com/video/BV1aK4y1p7dyhttps://www.bilibili.com/video/BV1aK4y1p7dy

欢迎交流!

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

闽ICP备14008679号