AlexNet论文解读以Pytorch实现
- 一、AlexNet背景
-
- 1、ILSVRC
- 2、GPU
- 二、AlexNet研究成果及意义
-
- 1、研究成果
- 2、研究意义
- 三、AlexNet网络结构
-
- 1.网络结构层的具体操作
- 2、具体操作
-
- (1)激活函数
- (2)LRN(目前几乎不用)
- (3)pooling(池化)
- 四、AlexNet训练技巧
-
- 1、Data Augmentation
- 2、Dropout
- 五、分析实验结果
-
- 1、实验结果
- 2.卷积核可视化
- 3.特征的相似性
- 六、结论
- 七、代码实现(含具体解释)
-
- 1.采用预训练模型
-
- (1)数据集及处理
- (2)dataset
- (3)dataset封装
- (4)建立预训练模型
- (5)损失函数,优化器,学习率
- (6)训练和验证
- (7)预训练模型识别
- (8)pyqt5视觉界面识别(预训练模型)
- 八、卷积核、特征图可视化
一、AlexNet背景
1、ILSVRC
在讨论AlexNet在此之前,首先要知道一场著名的比赛,即ILSVRC(ImageNet Large Scale Visual Recognition Challenge)大规模图像识别挑战赛是李飞飞等人于2010年创办的图像识别挑战赛,自2010年以来已举办8届。它也是近年来机器视觉领域最受欢迎、最权威的学术竞赛之一。 比赛项目包括:图像分类:(lassification)、目标定位(Object localization)、目标检测(Object detection)、视频目标检测(Object detection from video)、场景分类(Scene classification)、场景解析(Scene parsing)。 大量经典模型在比赛中脱颖而出: alexnet, vgg, googlenet, resnet, densenet等 实际上ILSVRC数据集不等于ImageNet数据集是从中挑选出来的。大规模数据集是AlexNet成功奠定了基础。 下面是ILSVRC的网址链接 ILSVRC网址
2、GPU
Alexnet除了大规模数据集的成功外,还得益于强大的计算资源GPU
二、AlexNet研究成果及意义
1、研究成果
AlexNet在ILSVRC-2012年超过第二名.9个百分点夺冠。下表是错误率。
Model | Top-1(val) | Top-5(val) | Top-5(test) | 注 |
---|---|---|---|---|
SIFT FVs[7] | - | - | 26.2% | ILSVRC 2012年分类任务第二名的结果 |
1 CNN | 40.7% | 18.2% | - | 训练一个AlexNet的结果 |
5 CNNs | 38.1% | 16.4% | 16.4% | 训练五个AlexNet取平均结果 |
1 CNN* | 39.0% | 16.6% | - | 在最后一个池化层后,增加第六个卷积层ImageNet 2011 (秋) 数据集预训练 |
7 CNNs* | 36.7% | 15.4% | 15.3% | 两个预训练微调,5CNNs取平均值 |
2、研究意义
2012年前的分类任务大多采用机器学习方法,即特征提取->特征筛选->输入分类机。 2012年以后采用深度学习方法,将特色工程与分类相结合。 因此,卷积神经网络开启了统治计算机视觉的序幕,加速了计算机视觉应用的实施。
三、AlexNet网络结构
1.网络结构层的具体操作
下面是论文中作者的网络结构图 首先要知道AlexNet网络的一些基础: 由5层卷积层和3层全连接层网络组成。 2.由于计算能力的限制,作者分为两部分GPU训练(图中上下两个代表)GPU,第一层和第三层与前面的所有信息相连,但现在GPU算力用一块GPU也可以) 3、LRN(后介绍)出现在第一卷和第二卷积层之后,但后来有论文提出LRN效果不明显,pytorch中将其去掉。 4、pooling:1、2、5卷积层出现后 5、ReLU:采用所有层 6、Dropout:使用前两层全连接层 : conv1->ReLU->Pool->LRN 作者在论文中输入图像的大小是224*根据公式,224声明中存在一些问题padding计算(224-11)/4 1=54与论文原图中的55不一致,如果不加入padding输入应为227 * 227,pytorch中导模型AlexNet网络将padding设置为了2. : conv2->ReLU->Pool->LRN : conv3->ReLU 这里实现了特征层的交互 : conv4->ReLU : conv5->ReLU->Pool : Dropout->Linear->ReLu : Dropout->Linear->ReLu : Linear 下图是每层的尺寸计算和参数的大小。在计算前详细解释了一些基本文章,这里不再过分强调。
2、具体操作
(1)激活函数
本文提到了激活函数 对卷积神经网络的深入理解——基础篇(卷积、激活、池化、差反传) 论文中作者提到了两种饱和的激活函数sigmoid激活函数和tanh激活函数
认为饱和的激活函数梯度下降会比较慢,故采用了非饱和的激活函数,以加快梯度下降的速度。 上图是作者用四层网络做的一个实验,在错误率下降到0.25时采用非饱和激活函数ReLU比饱和激活函数tanh快了六倍。
(2)LRN(目前几乎不采用)
全称Local Response Normalization,局部响应标准化,其受启发于侧抑制(lateral inhibition):细胞分化变为不同时,它会对周围细胞产生抑制信号,阻止他们向相同方向分化,最终表现为细胞命运的不同 论文中采用LRN在top-1和top-5的错误率上分别下降了1.4%和1.2%。 公式如下: b x , y i = a x , y i / ( k + α ∑ j = m a x ( 0 , i − n / 2 ) m i n ( N − 1 , i + n / 2 ) ( a x , y i ) 2 ) β b_{x,y}^{i}=a_{x,y}^{i}/(k+\alpha \sum_{j=max(0,i-n/2)}^{min(N-1,i+n/2)}(a_{x,y}^{i})^2)^{\beta } bx,yi=ax,yi/(k+αj=max(0,i−n/2)∑min(N−1,i+n/2)(ax,yi)2)β 看着比较复杂,不过原理相对简单, 先来看下图, 下图中假设当前位置的值为 a x , y i a_{x,y}^{i} ax,yi,那么根据LRN的原理,它必然受 i i i这个通道周围通道值得影响,假设 a x , y i a_{x,y}^{i} ax,yi受周围 n / 2 n/2 n/2个通道的值的影响,那么显然 a x , y i a_{x,y}^{i} ax,yi周围的值越大经过LRN后得到的 b x , y i b_{x,y}^{i} bx,yi的值就应该越小 ( k + α ∑ j = m a x ( 0 , i − n / 2 ) m i n ( N − 1 , i + n / 2 ) ( a x , y i ) 2 ) β 就 代 表 了 周 围 的 值 , (k+\alpha \sum_{j=max(0,i-n/2)}^{min(N-1,i+n/2)}(a_{x,y}^{i})^2)^{\beta }就代表了周围的值, (k+αj=max(0,i−n/2)∑min(N−1,i+n/2)(ax,yi)2)β就代表了周围的值, a x , y i 表 示 原 先 的 值 , a_{x,y}^{i}表示原先的值, ax,yi表示原先的值, b x , y i 表 示 经 过 抑 制 的 值 b_{x,y}^{i}表示经过抑制的值 bx,yi表示经过抑制的值 论文中采用LRN在top-1和top-5的错误率上分别下降了1.4%和1.2%。
(3)pooling(池化)
Overlapping Pooling(带重叠的池化),其实就是设置了步长的一个池化,之前的文章中我也具体介绍过,这里不再细讲。作者所采用的是核大小为3, 步长为2的一个池化,在top-1和 top-5的错误率上分别下降了0.4%和0.3%。
四、AlexNet训练技巧
1、Data Augmentation
论文中采用如下技巧 (1)针对位置 训练阶段: 1、图片统一缩放至256256 2、随机位置裁剪出224224区域(这里改为227*227) 3、随机进行水平翻转
测试阶段: 1、图片统一缩放至256256 2、裁剪出5个224224区域(这里改为227227) 3、均进行水平翻转,共得到10张224224图片(这里改为227*227) (2)针对颜色 通过PCA方法修改RGB通道的像素值,实现颜色扰动,效果有限,仅在top-1提升1个点(top-1 acc约62.5%),PCA方法比较麻烦,现在使用也比较少。
2、Dropout
这里我在神经网络基础部分也详细介绍过,见下面链接卷积神经网络的深入理解-正则化方法篇 不再细说。随机的停止某些神经元的计算,训练的时候采用,测试的时候停止。
五、实验结果即分析
1、实验结果
Model | Top-1(val) | Top-5(val) | Top-5(test) | 注 |
---|---|---|---|---|
SIFT+FVs[7] | - | - | 26.2% | ILSVRC 2012分类任务第二名的结果 |
1 CNN | 40.7% | 18.2% | - | 训练一个AlexNet的结果 |
5 CNNs | 38.1% | 16.4% | 16.4% | 训练五个AlexNet取平均值结果 |
1 CNN* | 39.0% | 16.6% | - | 最后一个池化层后额外添加第六个卷积层并使用ImageNet 2011 (秋) 数据集预训练 |
7 CNNs* | 36.7% | 15.4% | 15.3% | 两个预训练微调,与5CNNs取平均值 |
集成思想
2、卷积核可视化
如图所示这个是在输入图像224 * 224(这里改为227 * 227)也就是第一层进行11*11的一个卷积核的可视化,作者发现,训练过程中会出现这种现象,GPU1核学习到了包含频率和方向的特征,而GPU2核学习到了包含颜色的特征。
3、特征的相似性
相似图片第二个全连接层输出的特征向量的欧式距离相近。由此得到启发启发:可以用AlexNet提取高级特征进行图像检索、图像聚类、图像编码,这里其实已经有很多网络采用了这种思想,比如行人重识别。
六、结论
网络层之间是有一定相关性的,移除或是加入一层都会影响网络的性能。作者在论文中提到移除任何中间层都会引起网络损失大约2%的top-1性能。
七、代码实现(含具体讲解)
代码部分主要采用两种实现方式,第一种是调用pytorch中的alexnet网络并加载预训练模型进行实现(不要小看这种方式)。第二种自构建alexnet网络训练模型。
1、采用预训练模型进行
这部分花了我很长时间,有点繁琐,之后应该还会更改完善,那么我们开始吧。
(1)数据集以及处理
给出网盘地址: 链接:https://pan.baidu.com/s/1Pe5QNW-nltrf8qVboi1Yow?pwd=ld61 提取码:ld61
数据集采用猫狗数据集,直接从一个train文件中分割出训练集和验证集,这是train文件中的图片以及图片命名, 如下代码将数据集进行分割并存放于txt文件中,用于之后进行读取训练
import os
import random
# 用于产生txt文件,相当于dataset操作
def Data_division(data_path,save_txt_train_path,save_txt_eval_path):
lists = os.listdir(data_path)
for list in lists:
if list in ['train']:
datalist = []
datadir = os.path.join(os.path.abspath(data_path),list)
data = os.listdir(datadir)
for n in data:
if n.startswith('cat'):
datalist.append(os.path.join(datadir,n) + "\t" + str(0) + "\n")
elif n.startswith('dog'):
datalist.append(os.path.join(datadir,n) + "\t" + str(1) + "\n")
random.shuffle(datalist)
print("总数据集的个数:", len(datalist))
# 划分训练集验证集
train_list = []
eval_list = []
for i in range(int(len(datalist) * 0.9)):
train_list.append(datalist[i])
for i in range(int(len(datalist) * 0.9), len(datalist)):
eval_list.append(datalist[i])
print("训练数据个数:", len(train_list))
print("验证数据个数:", len(eval_list))
# 将数据路径存放于txt文件里
with open(save_txt_train_path, 'w', encoding='UTF-8') as f:
for train_img in train_list:
f.write(str(train_img))
f.close()
with open(save_txt_eval_path, 'w', encoding='UTF-8') as f:
for eval_img in eval_list:
f.write(eval_img)
f.close()
# 说明:这里将train中的猫狗图片分为训练集和测试集
if __name__ == '__main__':
Data_division('../data','../data/train.txt','../data/eval.txt')
txt文件保存格式如下:前者路径,后者类别
(2)dataset
这一步我们要将txt文件中的数据读取出来并进行数据增强,返回为分离的数据和标签。这里详细说明一下transform这一部分。将上面训练,测试的数据增广操作再写一遍
训练阶段 | 测试阶段 |
---|---|
图片统一缩放至256*256 | 图片统一缩放至256*256 |
随机位置裁剪出224x224区域 | 裁剪出5个224x224区域,进行水平翻转得到10张224*224图片 |
随机进行水平翻转 |
这里我为什么改为224x224,而非上面的227x227呢,这是由于pytorch内置的网络更改了部分网络层中的padding,使得网络输入与论文中的输入一致。
训练阶段 | 测试阶段 |
---|---|
transforms.Resize((256,256)) | transforms.Resize((256, 256)) |
transforms.CenterCrop(256) | |
transforms.RandomCrop(224) | transforms.TenCrop(224, vertical_flip=False) |
transforms.RandomHorizontalFlip(p=0.5) | |
transforms.ToTensor() | transforms.ToTensor() |
transforms.Normalize() | transforms.Normalize() |
torch.stack() |
我们主要来看一下测试阶段的TenCrop操作,它的作用见我的这篇博客神经网络数据增强transforms的相关操作(持续更新),主要作用是裁剪图片中心和四个角并做翻转得到10张图片,操作后会出现这样一个问题,我们希望对图片处理后得到的是一张(b,c,h,w),但是经过TenCrop处理后得到的却是这样一个list[(b,c,h,w),(b,c,h,w)…(b,c,h,w)],因此需要用到torch.stack()这个操作扩展一维变成(b,10,c,h,w),
如下为代码部分:
# -*- coding: utf-8 -*-
import torch
from PIL import Image
from torch.utils.data import Dataset
from torchvision import transforms
class CatDogDataset(Dataset):
def __init__(self,txt_path,img_size,is_train=True):
self.imgs_info = self.Separate_data(txt_path)
self.img_size = img_size
self.is_train = is_train
self.train_transform = transforms.Compose([
transforms.Resize((256)),
transforms.CenterCrop(256),
transforms.RandomCrop(img_size),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
self.val_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.TenCrop(224, vertical_flip=False),
# transforms.Lambda()函数自行定义transform操作
transforms.Lambda(lambda crops: torch.stack([transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(transforms.ToTensor()(crop)) for crop in crops])),
])
# 分离数据和标签[路径,标签]
def Separate_data(self,txt_path):
with open(txt_path,'r',encoding='utf-8') as f:
imgs_info = f.readlines()
imgs_info = list(map(lambda x: x.strip().split('\t'), imgs_info))
return imgs_info
def __getitem__(self, index):
img_path, label = self.imgs_info[index]
img = Image.open(img_path).convert('RGB')
if self.is_train:
image_datasets = self.train_transform(img)
else:
image_datasets = self.val_transform(img)
label = int(label)
return image_datasets,label
def __len__(self):
return len(self.imgs_info)
类中函数不太理解的看我这篇博客 卷积神经网络构建的python基础-详细理解(Pytorch) 最终的返回值如下:
train | val |
---|---|
(图片(b,c,h,w),标签) | (图片(b,10,c,h,w),标签) |
图片维数不一样?先接着往下看,后面告知怎么处理。
(3)dataset封装
train_loader = DataLoader(dataset=train_datasets, batch_size=batch_size, shuffle=True, num_workers=0)
val_loader = DataLoader(dataset=val_datasets, batch_size = batch_size, num_workers=0)
(4)预训练模型搭建
数据准备好后我们就要开始搭建网络了,这一部分由于是调用pytorch内置的网络,因此比较简单,分为以下几步 (1)下载好AlexNet预训练模型, (2)搭建网络,更改网络层(pytoch中alexnet网络所分类别是1000类,我们这里只分2类)
如下搭建网络代码:
import torchvision.models as models
alexnet_model = models.alexnet() # 网络
alexnet_model.load_state_dict(torch.load(path_state_dict)) #导入参数
下面是更改网络层代码
num_ftrs = alexnet_model.classifier._modules["6"].in_features
alexnet_model.classifier._modules["6"] = nn.Linear(num_ftrs, n_class)
实际上是将最后一层的全连接层输出只能1000改为2,即:
Linear(in_features=4096, out_features=1000, bias=True)
变为:
Linear(in_features=4096, out_features=2, bias=True)
判断GPU是否可用
if torch.cuda.is_available():
device = 'cuda'
else:
device = 'cpu'
alexnet_model.to(device)
(5)损失函数、优化器、学习率
这一部分重点主要是优化器,我们需要阻碍一下除全连接层外的所有参数更新。见如下代码,注意代码中的参数并没有完全冻结,想要完全冻结的可以将学习率置为0.
# 损失函数、优化器、学习率 criterion = nn.CrossEntropyLoss( 标签:
lrn系列热继电器