一、AlexNet网络
资料推荐
PyTorch:
官方文档:https://pytorch.org/docs/stable/index.html
PyTorch模型训练实用教程:https://github.com/TingsongYu/PyTorch_Tutorial
代码:
paper with code:https://paperswithcode.com
SOTA代码:https://www.jiqizhixin.com/sota
OpenCV:
文档:https://docs.opencv.org/3.4/d6/d00/tutorial_py_root.html
论文导读
1、一些数据集介绍
-
Mnist:
手写识别数据集,类似于编程语言学习中的HelloWorld,是入门CV领域的最常用数据集;训练数据有5万张,测试有1万张,图片格式为Gray,分辨率为28x28;
-
Cifar-10
十分类的数据集,入门数据集之一;训练数据有5万张,测试数据有1万张,图片格式为RGB,分辨率为32x32;
-
ILSVRC-2012
大规模图像识别挑战赛2012年数据集,类别有一千类,训练数据为120万,测试数据为15万,图片为RGB格式,分辨率也来到100以上;
-
ImageNet数据集
CV领域中整合的大型类别数据集,有21841个类别,其中总共有1400多万张图片;
Top-5 error评价指标:只要模型输出的5个类别中有一个是正确的,那么不惩罚模型;
论文概要
- 摘要
1、在ILSVRC-2010的120万张图片上训练的网络,获得top-1和top-5 error分别是37.5%和17%;
2、该网络(AlexNet)由5个卷积层和3个全连接层组成,共6000万参数,65万神经元;
3、为加快训练,采用非饱和激活函数——ReLU,采用GPU训练;
4、为减轻过拟合,采用Dropout;
- 网络结构
特殊点:
1、在第三层卷积层,网络和前面所有信息相连接;
2、第一个和第二个卷积层后有LRN网络结构;
重点介绍:
1、ReLU非饱和激活函数:加速训练,公式为f(x) = max(0, x);优点还有防止梯度消失,使网络具有稀疏性;对比sigmoid激活函数:y = 1 / 1 + e-x,sigmoid梯度会消失,而ReLU不会;
2、LRN结构(已经弃用):
LRN叫做局部响应标准化,有助于AlexNet泛化能力的提升,受真实神经元侧抑制启发,使用后top-1和top-5都有提升;
侧抑制:细胞分化为不同时,会对周围细胞产生抑制信号,阻止他们向相同方向分化,最终表现为细胞命运的不同;
3、重叠池化:也就是说kernel的移动步长小于kernel大小,精度提升了一些;
- 训练技巧
数据增强:
1、针对位置
训练阶段:
- 图片统一缩放至256x256
- 随机位置裁剪出224x224区域,总共得到1024张图片
- 随机进行水平翻转,得到2048张图片
测试阶段:
- 图片统一缩放至256x256
- 裁剪出四个角和中心的224x224区域
- 进行水平翻转,得到10张图片
2、色彩扰动
对RGB三通道进行PCA主成分分析,增加扰动因子,最终增加图像色彩的多样性;(淘汰)
Dropout(随机失活):
为了实现不同模型对数据集的训练;
注意事项:训练和测试阶段的数据尺度发生变化,测试时神经元输出需要乘以p失活概率
- 实验结果分析
最终的分类模型是采用7CNNs两个预训练微调,与5CNNs(训练五个AlexNet取平均值)取平均值;
特征相似性:相似图片的第二个全连接层输出特征向量的欧式距离相近;
启发:可用网络提取高级特征进行图像检索、图像聚类、图像编码等;
论文总结
启发点:
1、深度与宽度可决定网络能力;
2、更强大GPU及更多的数据可进一步提高模型性能;
3、图片缩放细节,对短边先缩放;
4、ReLU不需要对输入进行标准化来防止饱和现象,而sigmoid、tanh激活函数有必要对输入做标准化;
5、卷积核学习到频率、方向和颜色特征;
6、相似图片具有相近的高级特征;
7、图像检索可基于高级特征,效果应高于基于原始图像;
8、网络结构具有相关性,不能轻易移除某一层;
9、采用视频数据,可能有新突破(视频理解);
代码讲解
关键函数:
1、torch.topk(input,k,dim=None,largest=True,sorted=True,out=None)
功能:找出前k大的数据,及其索引序号
返回值:Tensor(前k大的值)、LongTensor(前k大的值所在位置)
2、transforms.TenCrop(size,vertical_flip=False)
功能:在图像的四个角以及中心裁剪出5张图片,进行翻转得到10张图片;
vertical_flip:是否垂直翻转,False的话为水平翻转;
返回值:一个BCHW的四维矩阵,B为10;
3、torchvision.utils.make_grid(tensor, nrow=8, padding=2, normalize=False, range=None, scale_each=False, pad_value=0)
功能:制作图像网格;
tensor:图像数据,BCHW形式;
nrow:行数,列数自动计算; padding:图像间距,单位为像素;
normalize:是否将像素值标准化; range:标准化范围;
padding_value:padding的像素值;
inference部分
代码结构:
加载图片——加载模型——模型推理——获取类别——分类结果可视化
代码说明:
1、加载图片
def process_img(path_img):
# 在ImageNet数据集上的均值和标准差
norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]
# 图像变换方法
inference_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop((224, 224)),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
# 读入图像,并且转成RGB
img_rgb = Image.open(path_img).convert('RGB')
# 将图像数据转换成tensor
img_tensor = img_transform(img_rgb, inference_transform)
# 加一个维度,chw --> bchw
img_tensor.unsqueeze_(0)
img_tensor = img_tensor.to(device)
return img_tensor, img_rgb
2、加载模型
用官方给定的model.alexnet()即可得到模型网络结构;
在全连接层之前,为了确保传入的特征图为6x6,加了一个自适应的平均池化:
self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
将模型结构打印出来:
from torchsummary import summary
summary(model, input_size=(3, 224, 224), device="cpu")
3、打印结果
将图片分类的top5输出在图片上:
plt.imshow(img_rgb)
plt.title("predict:{}".format(pred_str))
top5_num = top5_idx.cpu().numpy().squeeze()
text_str = [cls_n[t] for t in top5_num]
for idx in range(len(top5_num)):
plt.text(5, 15+idx*30, "top {}:{}".format(idx+1, text_str[idx]), bbox=dict(fc='yellow'))
plt.savefig('test1.jpg')
plt.show()
train部分
代码结构:
构建DataLoader——构建模型——构建损失函数——构建优化器——迭代训练
1、数据部分
- transform部分
# 训练数据的数据处理函数
train_transform = transforms.Compose([
transforms.Resize((256)), # 与(256, 256)的区别在于先从短边缩放
transforms.CenterCrop(256), # 裁剪得到(256, 256)
transforms.RandomCrop(224), # 随机裁剪,得到1024张图片
transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转,得到2048张图片
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std),
])
# 测试数据的数据处理函数
valid_transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.TenCrop(224, vertical_flip=False), #得到10张裁剪图片
# 用一个列表生成式将10张图片合并成tensor[10,B,C,H,W]
transforms.Lambda(lambda crops: torch.stack([normalizes(transforms.ToTensor()(crop)) for crop in crops])),
])
- DataSet的编写
# 首先是__init__函数
def __init__(self, data_dir, mode="train", split_n=0.9, rng_seed=620, transform=None):
self.mode = mode
self.data_dir = data_dir
self.rng_seed = rng_seed
self.split_n = split_n
self.data_info = self._get_img_info() #data_info存储所有图片路径和标签,在DataLoader中通过index读取样本
self.transform = transform
# 其次是__getitem__函数
def __getitem__(self, index):
path_img, label = self.data_info[index]
img = Image.open(path_img).convert('RGB') # 0~255
if self.transform is not None:
img = self.transform(img) # 在这里做transform,转为tensor等等
return img, label
# 计算数据长度的函数
def __len__(self):
if len(self.data_info) == 0:
raise Exception("\ndata_dir:{} is a empty dir! Please checkout your path to images!".format(self.data_dir))
return len(self.data_info)
# 数据信息获取
def _get_img_info(self):
img_names = os.listdir(self.data_dir)
img_names = list(filter(lambda x: x.endswith('.jpg'), img_names))
random.seed(self.rng_seed)
random.shuffle(img_names)
img_labels = [0 if n.startswith('cat') else 1 for n in img_names]
split_idx = int(len(img_labels) * self.split_n) # 25000* 0.9 = 22500
if self.mode == "train":
img_set = img_names[:split_idx] # 数据集90%训练
label_set = img_labels[:split_idx]
elif self.mode == "valid":
img_set = img_names[split_idx:]
label_set = img_labels[split_idx:]
else:
raise Exception("self.mode 无法识别,仅支持(train, valid)")
path_img_set = [os.path.join(self.data_dir, n) for n in img_set]
data_info = [(n, l) for n, l in zip(path_img_set, label_set)]
return data_info
2、损失函数
criterion = nn.CrossEntropyLoss()
3、验证集处理TenCrop输出
bs, ncrops, c, h, w = inputs.size() # [4, 10, 3, 224, 224]
outputs = alexnet_model(inputs.view(-1, c, h, w)) # [40, 3, 224, 224]
outputs_avg = outputs.view(bs, ncrops, -1).mean(1) # [4, 2]
总结:该网络是进入CV领域的第一个网络,有一定的参考价值,通过代码实现也清楚了数据的使用,以及训练和测试的步骤