DEiT实战:使用DEiT实现图像分类任务(一)

2023-10-29

摘要

DEiT是FaceBook在2020年提出的一篇Transformer模型。该模型解决了Transformer难以训练的问题,三天内使用4块GPU,完成了ImageNet的训练,并且没有使用外部数据,达到了SOTA水平。
DEiT提出的蒸馏策略只增加了对token的蒸馏,没有引入其他的重要架构。如下图:
在这里插入图片描述
蒸馏令牌与类令牌的使用类似:它通过自注意力与其他令牌交互,并在最后一层后由网络输出。蒸馏令牌允许模型从老师的输出中学习,就像在常规蒸馏中一样,同时与类令牌保持互补。这一点我们可以代码中找到:

 self.dist_token = nn.Parameter(torch.zeros(1, 1, self.embed_dim))
 self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))

cls_tokens 是类令牌,dist_token 是蒸馏令牌,确实很像是,仔细看都没有找到差别。

 def forward(self, x):
        x, x_dist = self.forward_features(x)
        x = self.head(x)
        x_dist = self.head_dist(x_dist)
        if self.training:
            return x, x_dist
        else:
            # during inference, return the average of both classifier predictions
            return (x + x_dist) / 2

如果想让模型从Teacher模型里学习,将 if self.training:设置为true,这样我们就可以像论文中那样使用RegNet做Teacher,使用DeiT模型做Student去蒸馏x_dist,否则将两者做平均实现二者的互补。
timm里面的代码和官方的代码有所不通,不过逻辑上是一样的,下面是timm的代码:

 def forward_head(self, x, pre_logits: bool = False) -> torch.Tensor:
        if pre_logits:
            return (x[:, 0] + x[:, 1]) / 2
        x, x_dist = self.head(x[:, 0]), self.head_dist(x[:, 1])
        if self.distilled_training and self.training and not torch.jit.is_scripting():
            # only return separate classification predictions when training in distilled mode
            return x, x_dist
        else:
            # during standard train / finetune, inference average the classifier predictions
            return (x + x_dist) / 2

(终于搞明白了。憋了好几天了,直到看了官方的代码才理解。)
等我有时间了再写一篇使用外部模型蒸馏的教程。

这篇文章主要讲解如何使用DEiT完成图像分类任务,接下来我们一起完成项目的实战。本例选用的模型是deit_small_patch16_224和deit_small_distilled_patch16_224,在植物幼苗数据集上实现了96%和97%的准确率。deit_small_patch16_224是没有蒸馏token的操作,deit_small_distilled_patch16_224有蒸馏token的操作,从结果上看蒸馏还是有不错的效果。
论文链接:https://arxiv.org/abs/2012.12877v2
论文翻译:https://wanghao.blog.csdn.net/article/details/128180419?spm=1001.2014.3001.5502
视频讲解:https://www.zhihu.com/zvideo/1587194506348040192
DeiT测试结果:
在这里插入图片描述

在这里插入图片描述

DeiT_dist测试结果:

在这里插入图片描述
在这里插入图片描述

通过这篇文章能让你学到:

  1. 如何使用数据增强,包括transforms的增强、CutOut、MixUp、CutMix等增强手段?
  2. 如何实现DEit模型和DEiT_dist模型实现训练?
  3. 如何使用pytorch自带混合精度?
  4. 如何使用梯度裁剪防止梯度爆炸?
  5. 如何使用DP多显卡训练?
  6. 如何绘制loss和acc曲线?
  7. 如何生成val的测评报告?
  8. 如何编写测试脚本测试测试集?
  9. 如何使用余弦退火策略调整学习率?
  10. 如何使用AverageMeter类统计ACC和loss等自定义变量?
  11. 如何理解和统计ACC1和ACC5?
  12. 如何使用EMA?

安装包

安装timm

使用pip就行,命令:

pip install timm

本文实战用的timm里面的模型。

数据增强Cutout和Mixup

为了提高成绩我在代码中加入Cutout和Mixup这两种增强方式。实现这两种增强需要安装torchtoolbox。安装命令:

pip install torchtoolbox

Cutout实现,在transforms中。

from torchtoolbox.transform import Cutout
# 数据预处理
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    Cutout(),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])

])

需要导入包:from timm.data.mixup import Mixup,

定义Mixup,和SoftTargetCrossEntropy

  mixup_fn = Mixup(
    mixup_alpha=0.8, cutmix_alpha=1.0, cutmix_minmax=None,
    prob=0.1, switch_prob=0.5, mode='batch',
    label_smoothing=0.1, num_classes=12)
 criterion_train = SoftTargetCrossEntropy()

参数详解:

mixup_alpha (float): mixup alpha 值,如果 > 0,则 mixup 处于活动状态。

cutmix_alpha (float):cutmix alpha 值,如果 > 0,cutmix 处于活动状态。

cutmix_minmax (List[float]):cutmix 最小/最大图像比率,cutmix 处于活动状态,如果不是 None,则使用这个 vs alpha。

如果设置了 cutmix_minmax 则cutmix_alpha 默认为1.0

prob (float): 每批次或元素应用 mixup 或 cutmix 的概率。

switch_prob (float): 当两者都处于活动状态时切换cutmix 和mixup 的概率 。

mode (str): 如何应用 mixup/cutmix 参数(每个’batch’,‘pair’(元素对),‘elem’(元素)。

correct_lam (bool): 当 cutmix bbox 被图像边框剪裁时应用。 lambda 校正

label_smoothing (float):将标签平滑应用于混合目标张量。

num_classes (int): 目标的类数。

EMA

EMA(Exponential Moving Average)是指数移动平均值。在深度学习中的做法是保存历史的一份参数,在一定训练阶段后,拿历史的参数给目前学习的参数做一次平滑。具体实现如下:

class EMA():
    def __init__(self, model, decay):
        self.model = model
        self.decay = decay
        self.shadow = {}
        self.backup = {}

    def register(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                self.shadow[name] = param.data.clone()

    def update(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                new_average = (1.0 - self.decay) * param.data + self.decay * self.shadow[name]
                self.shadow[name] = new_average.clone()

    def apply_shadow(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                self.backup[name] = param.data
                param.data = self.shadow[name]

    def restore(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}

加入到模型中。

# 初始化
ema = EMA(model, 0.999)
ema.register()

# 训练过程中,更新完参数后,同步update shadow weights
def train():
    optimizer.step()
    ema.update()

# eval前,apply shadow weights;eval之后,恢复原来模型的参数
def evaluate():
    ema.apply_shadow()
    # evaluate
    ema.restore()

针对没有预训练的模型,容易出现EMA不上分的情况,这点大家要注意啊!

项目结构

DEiT_demo
├─data1
│  ├─Black-grass
│  ├─Charlock
│  ├─Cleavers
│  ├─Common Chickweed
│  ├─Common wheat
│  ├─Fat Hen
│  ├─Loose Silky-bent
│  ├─Maize
│  ├─Scentless Mayweed
│  ├─Shepherds Purse
│  ├─Small-flowered Cranesbill
│  └─Sugar beet
├─mean_std.py
├─makedata.py
├─ema.py
├─train.py
├─train_dist.py
└─test.py

mean_std.py:计算mean和std的值。
makedata.py:生成数据集。
ema.py:EMA脚本
train.py:训练DEiT模型
train_dist.py:训练蒸馏策略的DEiT模型。

为了能在DP方式中使用混合精度,还需要在模型的forward函数前增加@autocast(),如果使用GPU训练导入包from torch.cuda.amp import autocast,如果使用CPU,则导入from torch.cpu.amp import autocast。
在这里插入图片描述

计算mean和std

为了使模型更加快速的收敛,我们需要计算出mean和std的值,新建mean_std.py,插入代码:

from torchvision.datasets import ImageFolder
import torch
from torchvision import transforms

def get_mean_and_std(train_data):
    train_loader = torch.utils.data.DataLoader(
        train_data, batch_size=1, shuffle=False, num_workers=0,
        pin_memory=True)
    mean = torch.zeros(3)
    std = torch.zeros(3)
    for X, _ in train_loader:
        for d in range(3):
            mean[d] += X[:, d, :, :].mean()
            std[d] += X[:, d, :, :].std()
    mean.div_(len(train_data))
    std.div_(len(train_data))
    return list(mean.numpy()), list(std.numpy())

if __name__ == '__main__':
    train_dataset = ImageFolder(root=r'data1', transform=transforms.ToTensor())
    print(get_mean_and_std(train_dataset))

数据集结构:

image-20220221153058619

运行结果:

([0.3281186, 0.28937867, 0.20702125], [0.09407319, 0.09732835, 0.106712654])

把这个结果记录下来,后面要用!

生成数据集

我们整理还的图像分类的数据集结构是这样的

data
├─Black-grass
├─Charlock
├─Cleavers
├─Common Chickweed
├─Common wheat
├─Fat Hen
├─Loose Silky-bent
├─Maize
├─Scentless Mayweed
├─Shepherds Purse
├─Small-flowered Cranesbill
└─Sugar beet

pytorch和keras默认加载方式是ImageNet数据集格式,格式是

├─data
│  ├─val
│  │   ├─Black-grass
│  │   ├─Charlock
│  │   ├─Cleavers
│  │   ├─Common Chickweed
│  │   ├─Common wheat
│  │   ├─Fat Hen
│  │   ├─Loose Silky-bent
│  │   ├─Maize
│  │   ├─Scentless Mayweed
│  │   ├─Shepherds Purse
│  │   ├─Small-flowered Cranesbill
│  │   └─Sugar beet
│  └─train
│      ├─Black-grass
│      ├─Charlock
│      ├─Cleavers
│      ├─Common Chickweed
│      ├─Common wheat
│      ├─Fat Hen
│      ├─Loose Silky-bent
│      ├─Maize
│      ├─Scentless Mayweed
│      ├─Shepherds Purse
│      ├─Small-flowered Cranesbill
│      └─Sugar beet

新增格式转化脚本makedata.py,插入代码:

import glob
import os
import shutil

image_list=glob.glob('data1/*/*.png')
print(image_list)
file_dir='data'
if os.path.exists(file_dir):
    print('true')
    #os.rmdir(file_dir)
    shutil.rmtree(file_dir)#删除再建立
    os.makedirs(file_dir)
else:
    os.makedirs(file_dir)

from sklearn.model_selection import train_test_split
trainval_files, val_files = train_test_split(image_list, test_size=0.3, random_state=42)
train_dir='train'
val_dir='val'
train_root=os.path.join(file_dir,train_dir)
val_root=os.path.join(file_dir,val_dir)
for file in trainval_files:
    file_class=file.replace("\\","/").split('/')[-2]
    file_name=file.replace("\\","/").split('/')[-1]
    file_class=os.path.join(train_root,file_class)
    if not os.path.isdir(file_class):
        os.makedirs(file_class)
    shutil.copy(file, file_class + '/' + file_name)

for file in val_files:
    file_class=file.replace("\\","/").split('/')[-2]
    file_name=file.replace("\\","/").split('/')[-1]
    file_class=os.path.join(val_root,file_class)
    if not os.path.isdir(file_class):
        os.makedirs(file_class)
    shutil.copy(file, file_class + '/' + file_name)

完成上面的内容就可以开启训练和测试了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DEiT实战:使用DEiT实现图像分类任务(一) 的相关文章

随机推荐

  • 使用ajax请求提交数据时,日期类型无法转换为JAVA中的日期类型

    在做毕业项目时碰见日期类型无法传到后台controller中的问题 因为springMVC中没有提供默认的日期转换器 前端页面传过来的日期类字符串无法转换为java中的日期类型 使用 DatetimeFormat注解完成转换 做一个笔记 前
  • 【压力测试 2】JMeter压力测试之Internal server error 500 问题解决思路

    一 JMeter客户端实现有两种方式 1 Java 选择压测时 链接是复用的 代码中的http调用都加了连接池 2 httpclient4 压测时 每请求一次都创建一个新的链接 jmeter5 0以前默认关闭了连接复用 5 0上是打开的 即
  • Hive 计算用户留存率(次日,3日,N日)

    文章目录 什么是用户留存率 创建数据源 计算留存率 计算 N 日的留存率 什么是用户留存率 用户留存率是指在特定时间段内 用户在使用某个产品或应用程序后 再次使用该产品或应用程序的比例 它可以帮助公司了解用户是否喜欢他们的产品 并提供有价值
  • Unity 雨水滴到屏幕效果

    文章目录 前言 一 实现过程 1 代码 2 代码分步解析 总结 参考 前言 本文主要介绍用unity实现雨水滴到屏幕的效果 文章介绍的是基础实现 读完这篇文章再去实现复杂效果会更得心应手些 我们先看更高级效果的图片 一 实现过程 1 代码
  • 微信朋友圈点赞如何设计测试用例,微信发语音消息,购物车,支付页面如何设计软件测试用例?

    做过软件测试或者面试过的人应该了解这个问题你肯定被问过 属于面试必问 就算没遇到 预测你以后面试肯定会遇到 希望你能想到想到我分享的思路 那么如何轻松优雅的回答这个题目 我给大家分享一个万能公式 打遍天下无敌手的招式所以正在学习软件测试的小
  • Yolov7学习笔记(三)训练部分

    文章目录 导读 训练前期属性配置 权值平滑 开始训练 导读 按批将图片进行预测后 将结果与真实框进行loss计算 训练相对预测部分 多了损失函数的计算以及反向求导梯度下降 进而更新函数 训练前期属性配置 创建模型 model YoloBod
  • mysql传入乱码_mysql 插入中文乱码解决方案 --转了

    MySQL 4 1的字符集支持 Character Set Support 有两个方面 字符集 Character set 和排序方式 Collation 对于字符集的支持细化到四个层次 服务器 server 数据库 database 数据
  • 机器学习模型上线及优化流程

    机器学习模型上线及优化流程 时间 2020 07 09 文章目录 机器学习模型上线及优化流程 通用流程 1 业务问题界定和定义 2 数据样本的筛选和构造 3 label的定义与标注 4 选择机器学习算法 5 确定模型性能的度量方式 6 模型
  • 判断一个字符串是否全部由数字组成?

    代码实现如下 public class StringClassTest public static void main String args String str 123b56c7 if isAllNumber str System ou
  • FreeRTOS学习笔记—FreeRTOS 中断配置和临界段

    文章目录 一 Cortex M 中断 1 1 中断简介 1 2 优先级分组定义 二 用于中断屏蔽的特殊寄存器 2 1 PRIMASK 寄存器 2 2 FAULTMASK 寄存器 2 3 BASEPRI寄存器 三 临界段代码 3 1 任务级临
  • Photo Sphere Viewer 全景图

    Photo Sphere Viewer 一款基于 three js 的简易3D插件 方便 实用 友好 看到网上有些API有错误 所以自己写一个修正版的 该js插件可以360度旋转查看全景图 也可以上下180度查看图片 使用该插件的唯一要求是
  • 从巨石应用到微服务应用,从ESB到APIGateway,从前后端分离到中台出现,九九归一,Rest要一统天下?

    本文系作者本人原创 如需转载 请务必写明出处 谢谢 题目很长 想说的东西很多 一 IT的进化论 达尔文的进化论同样适用于IT世界 能大行其道的IT技术 确实是优胜劣汰 自然的选择 有人说J2EE想解决很多东西 可惜它不够人性 有人说 SOA
  • 贪吃蛇游戏

    C语言实现经典游戏贪吃蛇 吃到果实蛇的长度会变大 如果碰到墙壁或者果实就会gameover windows平台下实现 代码如下 gmae h pragma once pragma once include
  • 你真的知道GET和POST两种基本请求方法的区别吗?

    GET和POST是HTTP请求的两种基本方法 要说它们的区别 接触过WEB开发的人都能说出一二 最直观的区别就是GET把参数包含在URL中 POST通过request body传递参数 你可能自己写过无数个GET和POST请求 或者已经看过
  • java中参数传递详解:call by value(按值传递)和 call by reference(按引用传递)

    参数传递的一些知识 不全面部分望指点 call by value 按值传递 call by reference 按引用传递 特点 call by value 不改变值 即操作的是一个值的拷贝 所以原值不变 call by reference
  • Linux安装部署 redis详细教程

    背景 Redis版本 6 0 1 服务器版本 Linux CentOS 7 6 64位 一 下载 进入官网找到下载地址 https redis io download 鼠标右击选择 复制链接地址 进入到Xshell控制台 进入usr 输入w
  • 数字三角形(java)

    问题描述 在数字三角形中寻找一条从顶部到底边的路径 使得路径上所经过的数字之和最大 路径上的每一步都只能往左下或 右下走 只需要求出这个最大和即可 不必给出具体路径 三角形的行数大于1小于等于100 数字为 0 99输入格式 输入格式 5
  • Java实现贪吃蛇大作战小游戏(完整版)

    大家好 今天尝试用swing技术写一个贪吃蛇大作战小游戏 供大家参考 效果展示 目录 效果展示 一 游戏界面 二 得分情况 项目介绍 项目背景 总体需求 实现过程 代码展示 主类 Demo类 MyPanel类 构造方法 初始化方法 绘制方法
  • Go微服务实践 - Rpc核心概念理解

    概述 从0研究一下Golang已经Golang的微服务生态体系 Golang的微服务首先要从Rpc开始 在升级到Grpc 详细介绍这些技术点都在解决什么技术问题 Rpc Rpc Remote Procedure Call 远程过程调用 简单
  • DEiT实战:使用DEiT实现图像分类任务(一)

    DEiT实战 摘要 安装包 安装timm 数据增强Cutout和Mixup EMA 项目结构 计算mean和std 生成数据集 摘要 DEiT是FaceBook在2020年提出的一篇Transformer模型 该模型解决了Transform