YOLOv5之autoanchor看这一篇就够了

2023-05-16

简单粗暴,废话也不罗嗦了,学习目的就是解决下面三个问题,

1. 默认anchor_t设置为4,这个参数如何调整?有没有必要调整?(首先网上很多说这个参数是长宽比是错误的,其只是控制anchor设置宽松度的阈值)

2. 代码如何完成自动化anchor聚类的,有点魔化如何实现的?

3. 聚类之后结果一定比人工算感受野的好吗?

传送门: yolov5/autoanchor.py

https://github.com/ultralytics/yolov5/blob/master/utils/autoanchor.py​github.com/ultralytics/yolov5/blob/master/utils/autoanchor.py

粗看结构:

        来先粗略的概览一下函数名来了解一下整个过程,以coco128数据作为学习数据集,这是一个阉割版的COCO数据集,里面只有128张图片,929个标注框。总揽一下yolov5工程下utils/autoanchor.py这个文件,文件中函数

def check_anchor_order(m)

def check_anchors(dataset, model, thr=4.0, imgsz=640)
    def metric(k)

def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True)
    def metric(k, wh)
    def anchor_fitness(k)
    def print_results(k, verbose=True)

        从上面函数名还是比较明确的,kmeans就是很常用的聚类方法,说明anchor是通过kmeans聚类而来。明确后,进一步查看函数内子函数,metric应该是某种评价指标用于判断是否需要聚类或者聚类的好坏,anchor_fitness一看就是在根据kmeans在拟合anchor了,print_result看名字就不重要直接忽略。


细看门道:

1)check_anchor_order(不重要)

def check_anchor_order(m):
 # Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary
1:    a = m.anchors.prod(-1).mean(-1).view(-1)  # mean anchor area per output layer
2:    da = a[-1] - a[0]  # delta a
3:    ds = m.stride[-1] - m.stride[0]  # delta s
4:    if da and (da.sign() != ds.sign()):  # same order
5:        LOGGER.info(f'{PREFIX}Reversing anchor order')
6:       m.anchors[:] = m.anchors.flip(0)

        1:m.anchors是从配置文件读取的anchor[3x3x2]分别表示3层特征 x 3个anchor x [w, h],第1行先计算每个anchor的面积,然后在计算每一层的均值,最终a表示三层特征层平均anchor的小3x1的矩阵。以models/yolov5s.yaml

在代码段中1:表示标记,用于在描述和代码中进行对应,
在后文标记中[400, 300],表示变量或者tensor的shape为[400, 300]

        yolov5s模型默认的anchor配置

a = [(10*13+16*30+33*23)/3, (30*61+62*45+59*119)/3, (116*90+156*198+375*326)/3] = tensor([ 456.33334, 3880.33325, 54308.66797])

2: da, 计算差值最大的两个anchor平均值,通常最后一个特征层感受野最大。

3: ds, 计算下采样最大最小的差值,stride=[8, 16, 32],所以差值32-8=24

4: 判断anchor是不是最后一层比第一层大,如果不是说明顺序错了。anchors需要按照从特征层8, 16,32的顺序,从小到大写

        就只是判断了stride和anchor大小是不是同向的,也比较tricky.

2) check_orders(重点)

        代码太长,不过精髓就在这了一定要仔细看细细品,第九行是整个聚类的精髓

1: m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1]  # Detect()
2: shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
3: scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1))  # augment scale
4: wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float()  

1: 获得检测头,获取anchor、stride、na(number of anchors)、nc(number of classes)、nl(number of layers)等检测头相关属性

2: dataset.shapes记录了训练集上所有图像大小,以coco128数据集为例,是一个 [128 x 2]的numpy array。这句找到128张图像上宽高最大的数值,将dataset.shapes/最大值 归一化到0到1,按照比例所放到最大640边长。

3: 根据均匀分布,随机生成0.9~1.1之间的缩放尺度

4: dataset.labels其中格式为[类别,x, y , w, h],xywh均已经归一化了,最终得到缩放之后标签的宽和高,shape为[929,2]的tensor, coco128训练集上共有929个标注框,由此得到wh一个轻微扰动后在训练集上所有标注框的宽高的集合

5: stride = m.stride.to(m.anchors.device).view(-1, 1, 1)  # model strides
6: anchors = m.anchors.clone() * stride  # current anchors
7: bpr, aat = metric(anchors.cpu().view(-1, 2))

5~6: stride=[8, 16,32],是一个[3,1]的张量;m.anchors是根据当前stride归一的结果,anchors计算完将三层特征上的anchor全部还原到[640,640]这个图像尺度上。

7: metric其实输入是anchors[9x2]和第4步中wh[929x2]

def metric(k):  # compute metric
8:     r = wh[:, None] / k[None]
9:     x = torch.min(r, 1 / r).min(2)[0]  # ratio metric
10:    best = x.max(1)[0]  # best_x
11:    aat = (x > 1 / thr).float().sum(1).mean()  # anchors above threshold
12:    bpr = (best > 1 / thr).float().mean()  # best possible recall
13:    return bpr, aat

8: [929x2] / [9, 2] = [929x9x2],计算得到929个标注框的宽高与9个anchor宽高取比值,宽比宽,高比高。这时候标注框有比anchor大的有比他小的,且尺度从零点几到几百都有可能,很难设定阈值。

9:明确一点,比值的目的是让标签和anchor尽量相近,当比值接近1表示设置的很合理,跟标签都重合了,反之数值越大或者越小都表示设置的不好。这里有个小技巧,既然过大过小都没意义,那取倒数把特别大的变特别小,接近1的几乎没变。再看min(r, 1/r).min(),将比值都变换到0到1之间,这里面越接近1越好,把特别大的或特特别小的数值统一转换为特别小。妙还是妙的,也足够优雅!

10: 此时x [929x9] ,取出匹配程度最高的,也就是越接近1的所以用max。9个anchor中和标签宽高比匹配成对最高的,也就是数值最大最接近1的。best[929x1]

11: 还有个点需要提示一下,我们在认定anchor是否匹配标签,只要9个anchor有一个超过阈值即可,并不需要9个anchor都匹配。所以上一步中只取最大值,在第9步时选择宽高比值最小的最小的都匹配宽高肯定都匹配。

在一个 为什么是x>1/thr 不是 x>thr? 可以这么理解,x已经是比值且归一化0到1,1/thr中1表示标准直就是1,thr表示相差几倍比值以内。比如设置为4,含义就是gt与anchor宽高相差不能超过4倍,其实是很宽泛的要求。

12:两个指标解释一下含义,

        aat表示在训练集上平均有几个anchor超过阈值,所有anchor都参与计算。例如使用coco128/yolov5s配置文件计算为4.26695,表示平均每个标签可以匹配4.26个anchor,这个结果也是很不错的。

        bpr计算了最佳情况下,挑选9个anchor中最高比值的一个,每个标签对应一个最高分匹配结果,最后判断最佳情况下有多少超过阈值。

由此我们解决了问题1,也弄清楚了 bpraat两个关键指标的含义
thresh是否需要调整?如何调整?实际效果好坏需要实验调整,不过整体上thresh设置越大对anchor设置的要求越松,越小对anchor设置的要求越高。个人理解越小适合打比赛更好的挖掘数据分布来聚类anchor,当然可能会影响到算法的范化能力。甚至可以将anchor_t作为参数,将1-5之间步进式画出anchor_t和bpr或anchor_t和aat的图像作为判断依据。

3)kmean_anchors(水到渠成)

        当bpr <= 0.98时,对anchor进行聚类,不过在做这个实验时要先将预设的anchor调整一下才能走到下一步。先回顾一下kmeans

        kmeans聚类算法具体实现过程

def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):

     npr = np.random
1:  thr = 1 / thr

2:  def metric(k, wh):  # compute metrics
        r = wh[:, None] / k[None]
        x = torch.min(r, 1 / r).min(2)[0]  # ratio metric
        # x = wh_iou(wh, torch.tensor(k))  # iou metric
    return x, x.max(1)[0]  # x, best_x

3:  def anchor_fitness(k):  # mutation fitness
         _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
         return (best * (best > thr).float()).mean()  # fitness

4:  def print_results(k, verbose=True):
        k = k[np.argsort(k.prod(1))]  # sort small to large
        x, best = metric(k, wh0)
        bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n  # best possible recall, anch > thr
        s = f'{PREFIX}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr\n' \

     f'{PREFIX}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, ' \
     f'past_thr={x[x > thr].mean():.3f}-mean: '
     for x in k:
        s += '%i,%i, ' % (round(x[0]), round(x[1]))
     if verbose:
         LOGGER.info(s[:-2])
     return k

     # Get label wh
5:  shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
6:  wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)])  # wh

    # Filter
7:  i = (wh0 < 3.0).any(1).sum()
     if i:
        LOGGER.info(f'{PREFIX}WARNING: Extremely small objects found: {i} of {len(wh0)} labels are < 3 pixels in size')
8:   wh = wh0[(wh0 >= 2.0).any(1)]  # filter > 2 pixels
      # wh = wh * (npr.rand(wh.shape[0], 1) * 0.9 + 0.1)  # multiply by random scale 0-1

      # Kmeans init
      try:
          LOGGER.info(f'{PREFIX}Running kmeans for {n} anchors on {len(wh)} points...')
          assert n <= len(wh)  # apply overdetermined constraint
9:       s = wh.std(0)  # sigmas for whitening
10:      k = kmeans(wh / s, n, iter=30)[0] * s  # points
         assert n == len(k)  # kmeans may return fewer points than requested if wh is insufficient or too similar
      except Exception:
         LOGGER.warning(f'{PREFIX}WARNING: switching strategies from kmeans to random init')
11:     k = np.sort(npr.rand(n * 2)).reshape(n, 2) * img_size  # random init
      wh, wh0 = (torch.tensor(x, dtype=torch.float32) for x in (wh, wh0))
      k = print_results(k, verbose=False)

3: fitness计算anchor和标签宽高比超过阈值的那些比值的均值,其实就是作为评价匹配程度好不好的一个数值,最佳为1最差为0,前面已经介绍国原因了就不多废话了。

5~6: 跟之前一样处理,获得标签宽高并缩放

7~8:any其中一个元素不为空/0/None输出True, all全部都得不为空/0/None输出True.就是过滤了一下,宽高必须有一个大于2,感觉用all更合适

9:这里对数据进行了白化(whitening),一方面降低数据间依赖,另一方面让每一个特征的方差为1。主要是为了主成分分析,消除方差占比较小的特征维,再一个就是标准化。

10:标签宽高标准化后作为kmeans的输入,kmeans需要制定聚类中心数量,这里为n=9,kemans叠代30轮。最终返回聚类后的anchor框,要是返回数量少于9个,就随机生成从9个anchor.

# Evolve
    f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1  # fitness, generations, mutation prob, sigma
    pbar = tqdm(range(gen), bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}')  # progress bar
    for _ in pbar:
1:     v = np.ones(sh)
        while (v == 1).all():  # mutate until a change occurs (prevent duplicates)
            v = ((npr.random(sh) < mp) * random.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
2:      kg = (k.copy() * v).clip(min=2.0)
         fg = anchor_fitness(kg)
3:      if fg > f:
4:         f, k = fg, kg.copy()
            pbar.desc = f'{PREFIX}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}'
            if verbose:
                print_results(k, verbose)
    return print_results(k)

        这一段主要是由于kmeans聚类之后fitness未必能够达到要求,在通过1000步相对较小的扰动可能会找到最好的结果。在获取v的数值时,也是以高斯分布随的方式获得扩展倍数,更符合在聚类基础上左右扰动切保证中心高概率的需要,最后做到优中选优。聚类其实也不是什么黑魔法,anchor有9个最简单的就是kmeans, 但毕竟聚类有一定随机性(随机初始化聚类中心)所以后续通过进化的方式又模拟了1000次细微扰动并计算评价指标,在其中优中取优,最终输出聚类后anchor,一切都正正好。

        最后一个问题的答案也就比较清晰了,通过kmeans确实可以提升anchor在特定数据集上的精度或者召回,但是不见得人工设置的anchor就会差,只是作为生产力工具更加简化了训练流程,基本不需要考虑anchor怎么设置,少做了一些发散而已,其实相差不多回归都能handle。在一个工具可以帮我们发现一些隐含的问题,更可以作为一个数据和模型的诊断工具,既方便又高效~

来源:YOLOv5之autoanchor看这一篇就够了 - 知乎 (zhihu.com)

参考:Training YOLO? Select Anchor Boxes Like This | by Olga Chernytska | Towards Data Science

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

YOLOv5之autoanchor看这一篇就够了 的相关文章

  • 空闲时间的处理:OnIdle,以消息循环过程中为例(顺便解释PeekMessage与GetMessage的不同)

    所谓空闲时间 xff08 idle time xff09 xff0c 是指 系统中没有任何消息等待处理 的时间 举个例子 xff0c 没有任何程序使用定时器 xff08 timer xff0c 它会定时送来WM TIMER xff09 xf
  • 七层网络学习

    七层网络协议 OSI协议 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 传输层 xff1a TCP IP协议 xff0c 就是在数据包外面在加一层 xff0c 加的数据是源端口和目标端口 xff1b 网络层 xff1a 在数据
  • 生成m个长度在n以内的随机字符串

    开发环境 xff1a VS Code xff08 1 45 1 xff09 include lt stdio h gt include lt stdlib h gt include lt time h gt define M 15 defi
  • windows下MFC定时器开发学习

    在VS2010中新建一个项目 xff0c Visual C 43 43 gt MFC gt MFC应用程序 xff0c 命名为 Timer gt 确定 xff0c 选择MFC向导建立基于对话框 xff1b 2 在自动生成的对话框模板中 xf
  • MFC 获取与更新控件

    EDIT控件 xff1a 获取控件值 xff1a CString str GetDlgItem IDC EDIT1 gt GetWindowText str IDC EDIT1为控件ID 更新控件值 xff1a CString str 61
  • TypeError: Expected ‘Iterator‘ as the return annotation for __iter__ of ExperienceSourceDataset

    问题 xff1a 使用pl bolts时产生错误 TypeError Expected 39 Iterator 39 as the return annotation for iter of ExperienceSourceDataset
  • 为什么调用了KillTimer()函数后,还是会进入OnTimer函数?

    今天写MFC定时器代码时 xff0c 发现调用了KillTimer 函数后 xff0c 还是会进入OnTimer函数 实现如下 xff1a 通过按钮控制定时器的开关 按钮回调函数 void CtimerDlg OnBnClickedButt
  • linux GDB调试

    前言 GDB调试 xff08 GNU debug xff09 是unix下的调试工具 xff0c 可以调试C和C 43 43 xff1b 程序怎么才能使用GDB xff1f 编译的时候加上 g xff0c 保留调试参数 xff1b 如果是别
  • 为什么基类的析构函数必须是虚函数

    因为当定义基类的指针指向子类对象时 xff0c 在调用析构函数的时候 xff0c 如果析构函数是虚函数 xff0c 那么就会调到子类的析构函数 xff0c 所以如果子类申请了新的内存的话 xff0c 那这块的内存就能被释放 xff1b 但是
  • python

    获取输入 获取string输入 xff1a str1 61 str input 获取数字输入 xff1a num1 61 int input
  • linux指令学习

    find 根据文件的名字查找 find name 34 list c 34 或者 find name 39 list c 39 xff1a 查找根目录下 xff0c 名字为list c的文件 grep 根据文件的内容查找 grep n he
  • windows快捷键

    1 非常实用的截图 xff1a shift 43 windows键 43 s
  • NLP思维

    前言 想让自己的思维有深度 xff0c NLP思维逻辑必须要会 xff01 通常在低层次的问题在高层次能轻易找到解决方法 xff0c 如果在同层次或者更低层次寻找解决问题的办法 xff0c 往往会消耗更多的精力 NLP思维分层 xff1a
  • linux不生成core dump文件

    今天尝试core dump功能调试 xff0c 发现一直没法生成崩溃dump文件 代码如下 xff1a include 34 iostream 34 using namespace std int main int a 61 10 cin
  • Windows下断点调试技巧

    添加普通断点 在代码行号左边空白处 xff0c 左键单击即可添加断点 xff1b 添加数据断点 xff08 监控数据变化 xff09 在需要监控的数据的行添加普通断点 xff1b 通过Debug模式运行软件 xff1b 等软件运行到需要监控
  • E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)

    sudo apt get innstall时报错 E Could not get lock var lib dpkg lock frontend open 11 Resource temporarily unavailable E Unab
  • 【损失函数】图像分割损失CELoss中添加 OHEM

    语义分割中常用交叉熵损失CE xff0c 在应用中通常添加OHEM以获取更好的收敛 xff08 经验阈值是0 7 xff09 xff0c 这里OHEM思想的来源是topk loss xff0c 其介绍参考 论文 损失函数 Learning
  • 开发板ifconfig时,没有wlan0

    问题 xff1a 开发板起来后 xff0c 输入ifconfig指令时 xff0c 只能看到eth0和lo xff0c 没看到wlan0 xff1b 原因 xff1a 没有挂载wifi驱动模块 xff1b 具体操作如下 xff1a 输入ls
  • ubuntu下将开发板中的内容导出

    方法 xff1a 通过tftp xff0c 开发板是客户端 xff0c 电脑是服务器 步骤 xff1a 搭建tftp环境 xff0c 开发板默认有tftp客户端功能 xff0c 需要在电脑端搭建tftp服务器环境 xff1b 在开发板指令终
  • sh脚本文件运行方式和区别

    有如下shell脚本test sh bin bash b 61 10 echo b sh test sh 和 bash test sh 指令在一个新开的子shell终端执行 xff0c 也就是说sh脚本中的变量不会在当前终端生效 xff0c

随机推荐

  • Ubuntu安装多个版本QT后怎么修改执行qmake使用的QT版本?

    查看当前QT版本 xff1a 输入指令 qmake v 可以看到当前版本是QT 4 8 7 查看QT编译器选择配置文件 xff1a cd usr lib x86 64 linux gnu qt default qtchooser sudo
  • ubuntu qt 创建工程时 no walid kit

    sudo apt get install qt sdk 之后输入sudo apt get qt default 点击 options gt Build amp Run gt Kits gt Desktop gt Qt Version 选择Q
  • QT创建Qlabel控件后没有显示

    在主窗口创建qlabel控件后 xff0c 控件没有显示 问题原因 xff1a 没有指定控件的父对象 指定父对象后 xff0c 就能显示了
  • QT 制作图片旋转、反转

    参考链接 xff1a QGraphicsPixmapItem QPropertyAnimation QTransform 自定义图片控件旋转 缩放 图形视图框架 三 xff09 码农家园 codenong com 代码 xff1a 工程文件
  • QT资源文件(.qrc)的编写与应用

    qrc文件 xff0c 这个是Qt的资源文件 xff0c 如果在pro文件中不包含的话 xff0c 在编译的时候会提示找不到相应资源的错误 xff1b 下面说一下手动修改pro和编写qrc文件的方法 我们直接在命令行下执行qmake pro
  • linux 查看硬盘内存使用情况

    sudo rm rf home wukai local share Trash 清空回收站
  • 耗时统计、日志

    linux struct timeval t1 t2 double timeuse gettimeofday amp t1 NULL foo gettimeofday amp t2 NULL timeuse 61 t2 tv sec t1
  • Ubuntu磁盘分区

    磁盘格式化 xff1a 低级格式化 xff1a 空白磁盘划分柱面 分区以及磁道 高级格式化 xff1a 低级格式化后的逻辑上的结构化 即建立文件系统 磁盘设备命名 xff1a IDE设备由内部设备连接来区分 xff0c 最多连接4个设备 x
  • 流量变现的10种方式

    在互联网飞速发展的今天 xff0c 流量就等于金钱 xff0c 流量越大意味着赚的钱越多 流量如何变现呢 xff1f 以下10种方式可供参考 xff1a 打造个人品牌变现 xff1a 通过写文章或发布短视频 xff0c 提高自己的知名度 x
  • ubuntu 下 .7z 文件解压时,子文件夹内的内容被解压到根文件夹问题

    7z e log4cplus 2 0 8 7z o home wukai Documents log4cplus 参数使用 e 时 xff0c 会导致子文件夹内的内容被解压到根文件夹 xff0c 导致子文件夹没东西 xff0c 且覆盖了根文
  • configure: error: cannot find sources (src/logger.cxx) in . or ..

    配置的时候 xff0c 找不到文件 查看下src文件夹下是不是没有这个文件 xff0c 如果没有 xff0c 可能性有一下两个 1 解压的时候出错 xff0c 导致此文件被解压到其他文件夹 xff1b xff08 参考https mp cs
  • windows下python下载及安装

    下载python安装包 进入python官网 xff1a https www python org 鼠标移动到 Downloads gt 34 Windows 34 上 xff0c 可以看到最新版本是3 11 3版本 点击 Windows
  • 修改环境变量

    点击 windows 按钮 xff0c 输入 环境 xff0c 点击右侧的 编辑系统环境变量 点击 环境变量 按钮 按如下顺序将python添加到环境变量中 然后再把所有弹框的确定按钮都点下
  • windows下创建python文件

    1 打开python IDLE 按下 windows 按钮 xff0c 输入python xff0c 单击 IDLE Python 3 9 64 bit 点击File gt New File 新文件未命令 xff0c 内容空 随意编辑代码
  • python代码注释

    在python中 xff0c 存在三种类型的注释 xff1a 单行注释 多行注释和中文声明注释 1 单行注释 xff08 在需要注释的内容前面加 xff09 注释内容 2 多行注释 xff08 将要注释的内容包含在 或者 内 xff09 3
  • python3.9.13 IDLE设置缩进值

    Options gt 34 Configure IDLE 34 gt 34 Windows 34 Indent spaces 即是缩进值
  • unindent does not match any outer indentation level

    python运行时 xff0c 报错 unindent does not match any outer indentation level 有某行的缩进和其他行不匹配
  • python分行

    方式一 xff1a print 34 123 34 34 456 34 方式二 xff1a print 34 wer asd 34 输出 123456 werasd
  • python命名规范

    1 模块名 xff1a 尽量短小 xff0c 全部小写 xff0c 可以使用下划线分隔多个字母 如 xff1a func 1 func 2 2 类名 xff1a 采用单词首字母大写的方式 如 xff1a Student Teacher 3
  • YOLOv5之autoanchor看这一篇就够了

    简单粗暴 xff0c 废话也不罗嗦了 xff0c 学习目的就是解决下面三个问题 xff0c 1 默认anchor t设置为4 xff0c 这个参数如何调整 xff1f 有没有必要调整 xff1f xff08 首先网上很多说这个参数是长宽比是