CV 02 Vit 叶子图片分类
前言
Vit——Vision Transformer
这里通过kaggle的叶子分类任务来使用预训练(Pre-train)模型Vit来提升我们的任务表示
1.观察模型&处理数据
1.1 模型探索
无论是基于python的特性(适配各个领域的包),还是NLP里大行其道的Pre-train范式,拥有快速了解一个包的特性以适用于我们工作的能力,将极大的提升我们工作的效率和结果。所以下面我们来快速体验一下HuggingFace给出的模型范例,并针对我们的任务进行相应的数据处理。
1 | from transformers import ViTFeatureExtractor, ViTForImageClassification |
上面的代码可以自行运行
1.1.1 示例解读
上十行代码: 首先通过requests库拿到一张图片并用image生成图片形式,下面两行加载了Vit16的特征提取器和HF特供的图片分类适配模型
下面我们看看 特征提取后的输入(inputs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29# inputs 输出如下
'''
{'pixel_values': tensor([[[[ 0.1137, 0.1686, 0.1843, ..., -0.1922, -0.1843, -0.1843],
[ 0.1373, 0.1686, 0.1843, ..., -0.1922, -0.1922, -0.2078],
[ 0.1137, 0.1529, 0.1608, ..., -0.2314, -0.2235, -0.2157],
...,
[ 0.8353, 0.7882, 0.7333, ..., 0.7020, 0.6471, 0.6157],
[ 0.8275, 0.7961, 0.7725, ..., 0.5843, 0.4667, 0.3961],
[ 0.8196, 0.7569, 0.7569, ..., 0.0745, -0.0510, -0.1922]],
[[-0.8039, -0.8118, -0.8118, ..., -0.8902, -0.8902, -0.8980],
[-0.7882, -0.7882, -0.7882, ..., -0.8745, -0.8745, -0.8824],
[-0.8118, -0.8039, -0.7882, ..., -0.8902, -0.8902, -0.8902],
...,
[-0.2706, -0.3176, -0.3647, ..., -0.4275, -0.4588, -0.4824],
[-0.2706, -0.2941, -0.3412, ..., -0.4824, -0.5451, -0.5765],
[-0.2784, -0.3412, -0.3490, ..., -0.7333, -0.7804, -0.8353]],
[[-0.5451, -0.4667, -0.4824, ..., -0.7412, -0.6941, -0.7176],
[-0.5529, -0.5137, -0.4902, ..., -0.7412, -0.7098, -0.7412],
[-0.5216, -0.4824, -0.4667, ..., -0.7490, -0.7490, -0.7647],
...,
[ 0.5686, 0.5529, 0.4510, ..., 0.4431, 0.3882, 0.3255],
[ 0.5451, 0.4902, 0.5137, ..., 0.3020, 0.2078, 0.1294],
[ 0.5686, 0.5608, 0.5137, ..., -0.2000, -0.4275, -0.5294]]]])}
'''
inputs['pixel_values'].size()
# torch.Size([1, 3, 224, 224])可以看到是一个字典类型的tensor数据,其维度为(b, C, W, H)
因此我们喂给模型的数据也得是四维的结构
接下来看看模型吐出来的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# outputs 输入如下
'''
MaskedLMOutput(loss=tensor(0.4776, grad_fn=<DivBackward0>), logits=tensor([[[[-0.0630, -0.0475, -0.1557, ..., 0.0950, 0.0216, -0.0084],
[-0.1219, -0.0329, -0.0849, ..., -0.0152, -0.0143, -0.0663],
[-0.1063, -0.0925, -0.0350, ..., 0.0238, -0.0206, -0.2159],
...,
[ 0.2204, 0.0593, -0.2771, ..., 0.0819, 0.0535, -0.1783],
[-0.0302, -0.1537, -0.1370, ..., -0.1245, -0.1181, -0.0070],
[ 0.0875, 0.0626, -0.0693, ..., 0.1331, 0.1088, -0.0835]],
[[ 0.1977, -0.2163, 0.0469, ..., 0.0802, -0.0414, 0.0552],
[ 0.1125, -0.0369, 0.0175, ..., 0.0598, -0.0843, 0.0774],
[ 0.1559, -0.0994, -0.0055, ..., -0.0215, 0.2452, -0.0603],
...,
[ 0.0603, 0.1887, 0.2060, ..., 0.0415, -0.0383, 0.0990],
[ 0.2106, 0.0992, -0.1562, ..., -0.1254, -0.0603, 0.0685],
[ 0.0256, 0.1578, 0.0304, ..., -0.0894, 0.0659, 0.1493]],
[[-0.0348, -0.0362, -0.1617, ..., 0.0527, 0.1927, 0.1431],
[-0.0447, 0.0137, -0.0798, ..., 0.1057, -0.0299, -0.0742],
[-0.0725, 0.1473, -0.0118, ..., -0.1284, 0.0010, -0.0773],
...,
[-0.0315, 0.1065, -0.1130, ..., 0.0091, -0.0650, 0.0688],
[ 0.0314, 0.1034, -0.0964, ..., 0.0144, 0.0532, -0.0415],
[-0.0205, 0.0046, -0.0987, ..., 0.1317, -0.0065, -0.1617]]]],
grad_fn=<UnsafeViewBackward0>), hidden_states=None, attentions=None) '''可以看到有loss、logits、hidden_states、attentions,而我们的范例只取了logits作为结果输出。这里并不是说其他的部分没用,是只取适配下游任务的输出即可。详情可研究Vit的论文
最后通过
argmax
函数和model.config.id2label
得出标签相对应的文字argmax就是返回最大值的位置下标、model.config.id2label配置了对应标签的名称,也知道了最后的classifier层是1000维的
1.1.2 小结
通过以上探索,我们可以得出:
- 输入的维度为(batch_size, 3, 224, 224)
- 最后的classifier需由1000改成我们叶子的类别数
1.2 数据处理
接下来我们将探索数据的特性,并修改以适应我们的模型
1.2.1 EDA
即Exploratory Data Analysis
首先导入所需包
1 | # 导入各种包 |
查看初始数据
train_df = pd.read_csv('/kaggle/input/classify-leaves/train.csv')
使用下面代码给分类配上序号
1 | def num_map(file_path): |
1.2.2 图片数据查看
1 | path = '/kaggle/input/classify-leaves/' |
这里我们做一下维度转换 即 [0, 1, 2] 换成 [2, 1, 0], 并只取某一个通道 看看
1 | # np.asarray(img).shape |
2.Preprocessing
接下来我们分别要做 数据增强、数据类定义、数据加载器测试
2.1.1 先来算个平均值标准差
这里算的mean跟std是为了Normalize我们的数据使训练更稳定
1 | import os |
2.1.2 数据增强
transforms.Compose
1 | train_transform = transforms.Compose([ |
2.2.1 Dataset
1 | class imgdataset(Dataset): |
2.2.2 模型测试
1 | train_dataset = imgdataset('/kaggle/input/classify-leaves/', |
这里可以直接看到transforms的ToTensor方式已经将我们的数据修改乘(C, W, H)形式(原来的是 C在最后)
1 | test_ot = model(samples[0]) |
3. 训练循环
3.1 plot
1 | def plot_loss_update(epoch, mb, train_loss, valid_loss): |
上面是一个 在训练过程中绘制ACC的包 fastprogress
3.2 train_valid
1 | def train_loop(net, device, criterion, opti, lr, lr_scheduler, batch_size, |
上面我们定义两个数组保存ACC的值,以绘制图形
3.3 kfold_save
1 | def kfold_loop(data, save_path, config): |
这里我们进行k折验证
3.4 config
最后我们定义超参数,以及其他构件
1 | seed = 1222 |
打包配置
1 | config = { |
4. 训练 & 结果分析
1 | !mkdir model_save |
这里第一个fold 出了点问题,总之valid_acc应该是从6%到了23% 后面就是跟下图一样了
这里我截取了两个fold进行数据查看 (1fold在p100上训练大概40分钟左右)
- 随着模型在训练集上的准确率上升,模型在验证集上的准确率也跟train_acc逐步拟合,当然由于验证集的数据没有训练过,中间有一些抖动。但是模型最后还是学到东西了的。
小结:
由于硬件资源的限制,就不再进行训练(模型还是在继续提升的),我们省略了模型融合和提交结果的验证,这里简单提下
- 以投票方式的模型融合为例,Vit的投票结果占权重0.4,剩下的ResNeSt和ResNeXt各占0.25, VGG占0.1,最后决定输出的标签
- test上就是valid部分 只输出176维度里最大值的位置即可
总结
此次我们学习了Pre-train的范式、K-fold验证、DataAugment。
- 重点是理解‘拿来主义’,总之拿来就用
- k折交叉验证只是验证的一种方式
- 数据增强则需要在理解数据集的基础上进行,是炼丹师必修的一门,当然也有非常多中增强数据的方式
之后我们将进行对比学习的讲解