使用k-means聚类anchors

article/2025/11/5 16:32:12

在之前讲yolo理论基础知识时有提到过,从yolov2开始使用的anchors都是通过聚类得到的。如果想了解更多yolo相关的知识可以看看我在bilibili上录得视频:https://www.bilibili.com/video/BV1yi4y1g7ro

今天补下之前没有细讲的聚类anchors相关知识,所使用的代码参考的是yolov3 spp以及yolov5中生成anchors的方法。

文章目录

  • K-means理论简介
  • K-means在anchors中的应用
  • yolov5中聚类anchors代码讲解
  • 聚类anchors需要注意的坑


K-means理论简介

k-means是非常经典且有效的聚类方法,通过计算样本之间的距离(相似程度)将较近的样本聚为同一类别(簇)。使用k-means时主要关注两个问题(个人认为):1.如何表示样本与样本之间的距离(核心问题),这个一般需要根据具体场景去设计,不同的方法聚类效果也不同,最常见的就是欧式距离。2.分为几类,这个也是需要根据应用场景取选择的,也是一个超参数。

k-means算法主要流程如下:

  • 1 手动设定簇的个数k,假设k=2。
  • 2 在所有样本中随机选取k个样本作为簇的初始中心,如下图(random clusters)中两个黄色的小星星代表随机初始化的两个簇中心。
  • 3 计算每个样本离每个簇中心的距离(这里以欧式距离为例),然后将样本划分到离它最近的簇中。如下图(step 0)用不同的颜色区分不同的簇。
  • 4 更新簇的中心,计算每个簇中所有样本的均值(方法不唯一)作为新的簇中心。如下图(step 1)所示,两个黄色的小星星已经移动到对应簇的中心。
  • 5 重复第3步到第4步直到簇中心不在变化或者簇中心变化很小满足给定终止条件。如下图(step2)所示,最终聚类结果。

生成以上聚类过程图片的代码:
plot_kmeans.py


K-means在anchors中的应用

在之前讲faster rcnn理论时,使用的anchors都是作者通过经验手工设计的, 但为什么这么设计作者并未提及。那这里为什么要聚类anchors?yolov2论文中有这么一段话The network can learn to adjust the boxes appropriately but if we pick better priors for the network to start with we can make it easier for the network to learn to predict good detections.简单的说如果我们一开始就选择了合适的anchors,那么网络就更容易去学习得到好的检测器。那什么才算好的anchors呢?作者通过计算Avg IOU即所有目标bboxes与anchors最大IOU的均值作为指标,Avg IOU越大代表得到的anchors越好。
上面已经简单介绍了k-means算法的过程,下面在说下yolov2中是怎么利用k-means算法进行聚类得到anchors的。这里主要关注的是如何定义样本之间的距离。论文中有这么一句话,If we use standard k-means with Euclidean distance larger boxes generate more error than smaller boxes.简单的说就是直接使用欧式距离来计算效果不是很好。那么用什么表示距离呢,论文中使用1-IOU(bboxes, anchors)表示距离,如果bbox与对应的簇中心(anchor)IOU越大,则距离越近(1-IOU(bboxes, anchors)越小)。如下图所示采用Cluster SSE(Sum of Square Error) 误差平方和(欧式距离)和采用Cluster IOU相比,Cluster IOU对应的Avg IOU更大,当然你想使用Cluster SSE也是可以的。并且在anchors个数相同的情况下Cluster IOU得到的Avg IOU比Faster RCNN中手工设计(Anchor Boxes)的Avg IOU更高。

在这里插入图片描述

下面是我参考几个开源项目自己改的代码。使用k-means算法,1-IOU(bboxes, anchors)作为样本之间的距离进行聚类。代码很简单,简要介绍下:

  • 1 在所有的bboxes中随机挑选k个作为簇的中心。
  • 2 计算每个bboxes离每个簇的距离1-IOU(bboxes, anchors)
  • 3 计算每个bboxes距离最近的簇中心,并分配到离它最近的簇中
  • 4 根据每个簇中的bboxes重新计算簇中心,这里默认使用的是计算中值,自己也可以改成其他方法
  • 5 重复3到4直到每个簇中元素不在发生变化

yolo_kmeans.py

import numpy as npdef wh_iou(wh1, wh2):# Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2wh1 = wh1[:, None]  # [N,1,2]wh2 = wh2[None]  # [1,M,2]inter = np.minimum(wh1, wh2).prod(2)  # [N,M]return inter / (wh1.prod(2) + wh2.prod(2) - inter)  # iou = inter / (area1 + area2 - inter)def k_means(boxes, k, dist=np.median):"""yolo k-means methodsrefer: https://github.com/qqwweee/keras-yolo3/blob/master/kmeans.pyArgs:boxes: 需要聚类的bboxesk: 簇数(聚成几类)dist: 更新簇坐标的方法(默认使用中位数,比均值效果略好)"""box_number = boxes.shape[0]last_nearest = np.zeros((box_number,))# 在所有的bboxes中随机挑选k个作为簇的中心。clusters = boxes[np.random.choice(box_number, k, replace=False)]while True:# 计算每个bboxes离每个簇的距离 1-IOU(bboxes, anchors)distances = 1 - wh_iou(boxes, clusters)# 计算每个bboxes距离最近的簇中心current_nearest = np.argmin(distances, axis=1)# 每个簇中元素不在发生变化说明以及聚类完毕if (last_nearest == current_nearest).all():break  # clusters won't changefor cluster in range(k):# 根据每个簇中的bboxes重新计算簇中心clusters[cluster] = dist(boxes[current_nearest == cluster], axis=0)last_nearest = current_nearestreturn clusters

代码链接:
https://github.com/WZMIAOMIAO/deep-learning-for-image-processing/blob/master/others_project/kmeans_anchors/yolo_kmeans.py


yolov5中聚类anchors代码讲解

如果你是直接使用yolov5的训练脚本,那么它会自动去计算下默认的anchors与你数据集中所有目标的best possible recall,如果小于0.98就会根据你自己数据集的目标去重新聚类生成anchors,反之使用默认的anchors。

下面代码是我根据yolov5中聚类anchors的代码简单修改得到的。基本流程不变,主要改动了三点:1.对代码做了些简化。2.把使用pytorch的地方都改成了numpy(感觉这样会更通用点,但numpy效率确实没有pytorch高)。3.作者默认使用的k-means方法是scipy包提供的,使用的是欧式距离。我自己改成了基于1-IOU(bboxes, anchors)距离的方法。当然我只是注释掉了作者原来的方法,如果想用自己把注释取消掉就行了。但在我使用测试过程中,还是基于1-IOU(bboxes, anchors)距离的方法会略好点。

完整代码链接:
https://github.com/WZMIAOMIAO/deep-learning-for-image-processing/tree/master/others_project/kmeans_anchors

其实在yolov5生成anchors中不仅仅使用了k-means聚类,还使用了Genetic Algorithm遗传算法,在k-means聚类的结果上进行mutation变异。接下来简单介绍下代码流程:

  • 读取训练集中每张图片的wh以及所有bboxes的wh(这里是我自己写的脚本读取的PASCAL VOC数据)
  • 将每张图片中wh的最大值等比例缩放到指定大小img_size,由于读取的bboxes是相对坐标所以不需要改动
  • 将bboxes从相对坐标改成绝对坐标(乘以缩放后的wh)
  • 筛选bboxes,保留wh都大于等于两个像素的bboxes
  • 使用k-means聚类得到n个anchors
  • 使用遗传算法随机对anchors的wh进行变异,如果变异后效果变得更好(使用anchor_fitness方法计算得到的fitness(适应度)进行评估)就将变异后的结果赋值给anchors,如果变异后效果变差就跳过,默认变异1000次。
  • 将最终变异得到的anchors按照面积进行排序并返回

main.py

import random
import numpy as np
from tqdm import tqdm
from scipy.cluster.vq import kmeansfrom read_voc import VOCDataSet
from yolo_kmeans import k_means, wh_ioudef anchor_fitness(k: np.ndarray, wh: np.ndarray, thr: float):  # mutation fitnessr = wh[:, None] / k[None]x = np.minimum(r, 1. / r).min(2)  # ratio metric# x = wh_iou(wh, k)  # iou metricbest = x.max(1)f = (best * (best > thr).astype(np.float32)).mean()  # fitnessbpr = (best > thr).astype(np.float32).mean()  # best possible recallreturn f, bprdef main(img_size=512, n=9, thr=0.25, gen=1000):# 从数据集中读取所有图片的wh以及对应bboxes的whdataset = VOCDataSet(voc_root="/data", year="2012", txt_name="train.txt")im_wh, boxes_wh = dataset.get_info()# 最大边缩放到img_sizeim_wh = np.array(im_wh, dtype=np.float32)shapes = img_size * im_wh / im_wh.max(1, keepdims=True)wh0 = np.concatenate([l * s for s, l in zip(shapes, boxes_wh)])  # wh# Filter 过滤掉小目标i = (wh0 < 3.0).any(1).sum()if i:print(f'WARNING: Extremely small objects found. {i} of {len(wh0)} labels are < 3 pixels in size.')wh = wh0[(wh0 >= 2.0).any(1)]  # 只保留wh都大于等于2个像素的box# Kmeans calculation# print(f'Running kmeans for {n} anchors on {len(wh)} points...')# s = wh.std(0)  # sigmas for whitening# k, dist = kmeans(wh / s, n, iter=30)  # points, mean distance# assert len(k) == n, print(f'ERROR: scipy.cluster.vq.kmeans requested {n} points but returned only {len(k)}')# k *= sk = k_means(wh, n)# 按面积排序k = k[np.argsort(k.prod(1))]  # sort small to largef, bpr = anchor_fitness(k, wh, thr)print("kmeans: " + " ".join([f"[{int(i[0])}, {int(i[1])}]" for i in k]))print(f"fitness: {f:.5f}, best possible recall: {bpr:.5f}")# Evolve# 遗传算法(在kmeans的结果基础上变异mutation)npr = np.randomf, sh, mp, s = anchor_fitness(k, wh, thr)[0], k.shape, 0.9, 0.1  # fitness, generations, mutation prob, sigmapbar = tqdm(range(gen), desc=f'Evolving anchors with Genetic Algorithm:')  # progress barfor _ in pbar: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)kg = (k.copy() * v).clip(min=2.0)fg, bpr = anchor_fitness(kg, wh, thr)if fg > f:f, k = fg, kg.copy()pbar.desc = f'Evolving anchors with Genetic Algorithm: fitness = {f:.4f}'# 按面积排序k = k[np.argsort(k.prod(1))]  # sort small to largeprint("genetic: " + " ".join([f"[{int(i[0])}, {int(i[1])}]" for i in k]))print(f"fitness: {f:.5f}, best possible recall: {bpr:.5f}")if __name__ == "__main__":main()

运行结果如下,注意由于随机性每次结果都会有些差异,如果要能够复现,需要固定numpy以及random包的随机数种子。

read data info.: 100%|██████████| 5717/5717 [00:00<00:00, 6549.98it/s]
kmeans: [12, 18] [27, 31] [33, 69] [75, 48] [65, 118] [125, 137] [164, 268] [299, 166] [382, 337]
fitness: 0.73256, best possible recall: 0.99956
Evolving anchors with Genetic Algorithm: fitness = 0.7358: 100%|██████████| 1000/1000 [00:05<00:00, 182.22it/s]
genetic: [13, 23] [34, 31] [30, 75] [79, 66] [69, 143] [142, 134] [169, 270] [331, 177] [391, 338]
fitness: 0.73582, best possible recall: 0.99930

聚类anchors需要注意的坑

有时使用自己聚类得到的anchors的效果反而变差了,此时你可以从以下几方面进行检查:

  • 注意输入网络时训练的图片尺寸。这是个很重要的点,因为一般训练/验证时输入网络的图片尺寸是固定的,比如说640x640,那么图片在输入网络前一般会将最大边长缩放到640,同时图片中的bboxes也会进行缩放。所以在聚类anchors时需要使用相同的方式提前去缩放bboxes,否则聚类出来的anchors并不匹配。比如你的图片都是1280x1280大小的,假设bboxes都是100x100大小的,如果不去缩放bboxes,那么聚类得到的anchors差不多是在100x100附近。而实际训练网络时bboxes都已经缩放到50x50大小了,此时理想的anchors应该是50x50左右而不是100x100了。
  • 如果使用预训练权重,不要冻结太多的权重。现在训练自己数据集时一般都是使用别人在coco等大型数据上预训练好的权重。而这些权重是基于coco等数据集上聚类得到的结果,并不是针对自己数据集聚类得到的。所以网络为了要适应新的anchors需要调整很多权重,如果你冻结了很多层(假设只去微调最后的预测器,其他权重全部冻结),那么得到的结果很大几率还没有之前的anchors好。当可训练的权重越来越多,一般使用自己数据集聚类得到的anchors会更好一点(前提是自己聚类的anchors是合理的)。

http://chatgpt.dhexx.cn/article/vIv4uonJ.shtml

相关文章

细说物体检测中的Anchors

点击上方“AI公园”&#xff0c;关注公众号&#xff0c;选择加“星标“或“置顶” 作者&#xff1a;Raghul Asokan 编译&#xff1a;ronghuaiyang 导读 给大家再次解释一下Anchors在物体检测中的作用。 今天&#xff0c;我将讨论在物体检测器中引入的一个优雅的概念 —— Ancho…

带图讲解,深度学习YOLO里面的anchors的进阶理解

如果有了解过yolo网络&#xff0c;那肯定也听说过anchors&#xff0c;当然anchors这个概念布置在YOLO里面才有&#xff0c;在其他的目标检测中也存在anchors这个概念。对于anchors计算的一些公式这篇文章就不进行讲解了&#xff0c;这篇文章主要是讲在训练网络模型过程中anchor…

Linux终端的网易云音乐——musicbox

网易云音乐是听歌的不错的选择&#xff0c;如果能够在命令行听歌就更cool了。特来推荐musicbox。 网易云音乐的musicbox是网易云音乐命令行版本&#xff0c;这款命令行的客户端使用 Python 构建&#xff0c;以 mpg123 作为播放后端。提供了很多使用的功能&#xff0c;如&#x…

MusicStore-2

1.按照MusicStore-1步骤创建mvc项目&#xff0c;并初始化数据库 2.修改HomeController using Chapter8.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc;namespace Chapter8.Controllers {public class…

音乐i网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字) &#xff1a;

musicbox(暂停/启动,停止,下一曲上一首)

主界面 按下开始 按下暂停 按下停止 下一首 上一首 代码 activity_main.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical&…

node-webkit-MusicBox 基于nwjs ,html5 ,制作的音乐盒子

太长&#xff1f;单击目录直接去看最终效果&#xff0c;在最下边 文件下载地址&#xff1a;http://download.csdn.net/detail/u013934914/9180053 1.思路&#xff08;简单设想&#xff09; index.html 实现 对页面的显示&#xff0c;并调用绑定ymusic.js中的方法 需要&…

算数计算机音乐模拟器,Musicalculator

musicalculatorapp它是一个音乐旋律软件&#xff0c;在这上面你可以随时随地的记录你有灵感时创作出来的乐谱&#xff0c;还可以放好听的音色包进行自动的弹奏&#xff0c;还可以根据自己的想法设定速度音长&#xff0c;这一款非常的适合喜爱音乐的用户。该应用只是一款音乐计算…

HTML5 CSS3实战——自定义音乐播放器(一)

前几天才刚开始接触HTML5和CSS3。学习了一下里面的一些炫酷的新特性。不过&#xff0c;对于原生的HTML5的媒体播放器&#xff0c;真的不得不吐槽&#xff1a;就三个按钮 界面还那么丑。所以觉得自己整一个好看的播放器。老话说&#xff1a;说不如干&#xff0c;纸上谈兵终觉浅。…

NetEase MusicBox —— Linux系统里的网易云音乐(转载)

功能特性 320kbps的高品质音乐 歌曲&#xff0c;艺术家&#xff0c;专辑检索 网易 22 个歌曲排行榜 网易新碟推荐 网易精选歌单 网易 DJ 节目 私人歌单&#xff0c;每日推荐 随心打碟 本地收藏&#xff0c;随时加 ? 播放进度及播放模式显示 Vimer 式快捷键让操作丝般顺滑 可使…

MusicBox - 仿千千静听

大学毕业时业余之作 最新下载地址&#xff1a; 匿名提取文件连接 http://pickup.mofile.com/5412819180446197 或登录Mofile&#xff0c;使用提取码 5412819180446197 提取文件 MusicBox 停止开发&#xff01;&#xff01; 淘宝开卖源代码 有意者 http://item.taobao.co…

音乐播放器

用HTML做了个音乐播放器&#xff0c;可以循环播放&#xff0c;选择歌曲&#xff0c;以及自动播放下一首&#xff0c;运用了js和json知识&#xff0c;下面是效果图和源码&#xff0c;有兴趣的可以试试哦 效果图&#xff1a; 源码&#xff1a;html <span style"color:#9…

MusicPlay播放器

文章目录 一丶前端布局二丶Activity三丶Service实验演示 一丶前端布局 Layout的框架 一个自定义的音乐播放器&#xff0c;页面布局大致如下 二丶Activity 首先定义控件 // 获取界面中显示歌曲标题、作者文本框TextView title, author;// 喜欢&#xff0c;播放&#xff0c;暂…

MusicPlay 音乐播放器(纯前端)

纯Css,js前端音乐播放器,界面UI比较好。我特别喜欢,适合二次开发,调用了网易云第三方接口以及将歌曲链接导出可以访问的歌曲链接,实现了异步歌曲搜索播放,以及异步显示歌词等.结合html5 新特性实现歌曲暂停,下一首,快进等等.为大二前端练手项目.采用渐变质背景 是前端练手的好…

HTML5+JS实现简易的音乐播放器

HTML5JS实现简易的音乐播放器 播放器实现的功能 播放/暂停音乐切换歌曲&#xff0c;上/下一首歌音量最大或静音音乐播放时间实时变化进度条拖拽歌曲图片切换 播放器效果展示 代码展示 html <div class"musicBox" id"musicBox"><audio src"…

音乐播放器MusicBox

音乐播放器MusicBox 功能界面分析 界面要求较为简洁&#xff0c;仅需两个TextView&#xff0c;用于输出音乐名称及歌手姓名&#xff0c;下方三个ImageButton&#xff0c;用于播放&#xff0c;暂停或切换上一首&#xff0c;下一首歌曲。 实现 总体规划 在MainActivity中&a…

索尼音乐应用android,Sony Music Center

Sony Music Center是全新的索尼音乐中心也是原来的songpal应用&#xff0c;作为sony手机上面的专业播放器的软件&#xff0c;它能够为用户播放各种音乐上面的内容&#xff0c;它可以直接的为用户解析各种音乐的内容&#xff0c;让用户能够随时的在手机上面收听到自己所喜欢的无…

MusicLibrary:一个丰富的音乐播放封装库

code小生,一个专注 Android 领域的技术分享平台 作者&#xff1a;lizixian18链接&#xff1a;https://github.com/lizixian18/MusicLibrary声明&#xff1a;本文是 lizixian18 投稿,转载等请联系作者获得授权。 MusicLibrary 一个丰富的音乐播放封装库&#xff0c;针对快速集成…

MusicBox

MusicBox 编写音乐盒代码&#xff0c;并添加音乐播放的 上一首 和 下一首 控制。 1.添加所需的图片以及音乐文件&#xff1b; 2.编写xml文件&#xff0c;上一首、下一首、暂停、播放按钮以及歌曲的名字和作者 <?xml version"1.0" encoding"utf-8"?&…

Musicplayer

Musicplayer Python 音乐播放器 功能&#xff1a; 首先可以实现本地音乐的播放&#xff0c;能够打开电脑端的文件&#xff0c;文件中进行多选&#xff0c;实现暂停、上一曲、下一曲的功能。在此基础上&#xff0c;完成随机播放列表和网络端下载的功能。 设计方法&#xff1a; 本…