首頁 > 軟體

yolov5中anchors設定範例詳解

2022-06-24 14:03:26

yolov5中增加了自適應錨定框(Auto Learning Bounding Box Anchors),而其他yolo系列是沒有的。

一、預設錨定框

Yolov5 中預設儲存了一些針對 coco資料集的預設錨定框,在 yolov5 的組態檔*.yaml 中已經預設了640×640影象大小下錨定框的尺寸(以 yolov5s.yaml 為例):

# anchors
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

 anchors引數共有三行,每行9個數值;且每一行代表應用不同的特徵圖;

1、第一行是在最大的特徵圖上的錨框

2、第二行是在中間的特徵圖上的錨框

3、第三行是在最小的特徵圖上的錨框;

在目標檢測任務中,一般希望在大的特徵圖上去檢測小目標,因為大特徵圖才含有更多小目標資訊,因此大特徵圖上的anchor數值通常設定為小數值,而小特徵圖上數值設定為大數值檢測大的目標。

二、自定義錨定框

1、訓練時自動計算錨定框

yolov5 中不是隻使用預設錨定框,在開始訓練之前會對資料集中標註資訊進行核查,計算此資料集標註資訊針對預設錨定框的最佳召回率,當最佳召回率大於或等於0.98,則不需要更新錨定框;如果最佳召回率小於0.98,則需要重新計算符合此資料集的錨定框。

核查錨定框是否適合要求的函數在 /utils/autoanchor.py 檔案中:

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

 其中 thr 是指 資料集中標註框寬高比最大閾值,預設是使用 超參檔案 hyp.scratch.yaml 中的 “anchor_t” 引數值。

核查主要程式碼如下:

    def metric(k):  # compute metric
        r = wh[:, None] / k[None]
        x = torch.min(r, 1. / r).min(2)[0]  # ratio metric
        best = x.max(1)[0]  # best_x
        aat = (x > 1. / thr).float().sum(1).mean()  # anchors above threshold
        bpr = (best > 1. / thr).float().mean()  # best possible recall
        return bpr, aat
 
    bpr, aat = metric(m.anchor_grid.clone().cpu().view(-1, 2))

其中兩個指標需要解釋一下(bpr 和 aat):

bpr(best possible recall) 

aat(anchors above threshold) 

 其中 bpr 引數就是判斷是否需要重新計算錨定框的依據(是否小於 0.98)。

重新計算符合此資料集標註框的錨定框,是利用 kmean聚類方法實現的,程式碼在  /utils/autoanchor.py 檔案中:

def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
    """ Creates kmeans-evolved anchors from training dataset
        Arguments:
            path: path to dataset *.yaml, or a loaded dataset
            n: number of anchors
            img_size: image size used for training
            thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
            gen: generations to evolve anchors using genetic algorithm
            verbose: print all results
        Return:
            k: kmeans evolved anchors
        Usage:
            from utils.autoanchor import *; _ = kmean_anchors()
    """
    thr = 1. / thr
    prefix = colorstr('autoanchor: ')
 
    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
 
    def anchor_fitness(k):  # mutation fitness
        _, best = metric(torch.tensor(k, dtype=torch.float32), wh)
        return (best * (best > thr).float()).mean()  # fitness
 
    def print_results(k):
        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
        print(f'{prefix}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr')
        print(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: ', end='')
        for i, x in enumerate(k):
            print('%i,%i' % (round(x[0]), round(x[1])), end=',  ' if i < len(k) - 1 else 'n')  # use in *.cfg
        return k
 
    if isinstance(path, str):  # *.yaml file
        with open(path) as f:
            data_dict = yaml.load(f, Loader=yaml.SafeLoader)  # model dict
        from utils.datasets import LoadImagesAndLabels
        dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True)
    else:
        dataset = path  # dataset
 
    # Get label wh
    shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True)
    wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)])  # wh
 
    # Filter
    i = (wh0 < 3.0).any(1).sum()
    if i:
        print(f'{prefix}WARNING: Extremely small objects found. {i} of {len(wh0)} labels are < 3 pixels in size.')
    wh = wh0[(wh0 >= 2.0).any(1)]  # filter > 2 pixels
    # wh = wh * (np.random.rand(wh.shape[0], 1) * 0.9 + 0.1)  # multiply by random scale 0-1
 
    # Kmeans calculation
    print(f'{prefix}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
    k *= s
    wh = torch.tensor(wh, dtype=torch.float32)  # filtered
    wh0 = torch.tensor(wh0, dtype=torch.float32)  # unfiltered
    k = print_results(k)
 
    # Plot
    # k, d = [None] * 20, [None] * 20
    # for i in tqdm(range(1, 21)):
    #     k[i-1], d[i-1] = kmeans(wh / s, i)  # points, mean distance
    # fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True)
    # ax = ax.ravel()
    # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
    # fig, ax = plt.subplots(1, 2, figsize=(14, 7))  # plot wh
    # ax[0].hist(wh[wh[:, 0]<100, 0],400)
    # ax[1].hist(wh[wh[:, 1]<100, 1],400)
    # fig.savefig('wh.png', dpi=200)
 
    # Evolve
    npr = np.random
    f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1  # fitness, generations, mutation prob, sigma
    pbar = tqdm(range(gen), desc=f'{prefix}Evolving anchors with Genetic Algorithm:')  # progress bar
    for _ in pbar:
        v = np.ones(sh)
        while (v == 1).all():  # mutate until a change occurs (prevent duplicates)
            v = ((npr.random(sh) < mp) * npr.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0)
        kg = (k.copy() * v).clip(min=2.0)
        fg = anchor_fitness(kg)
        if fg > f:
            f, k = fg, kg.copy()
            pbar.desc = f'{prefix}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}'
            if verbose:
                print_results(k)
 
    return print_results(k)

對 kmean_anchors()函數中的引數做一下簡單解釋(程式碼中已經有了英文註釋):

  • path:包含資料集檔案路徑等相關資訊的 yaml 檔案(比如 coco128.yaml), 或者 資料集張量(yolov5 自動計算錨定框時就是用的這種方式,先把資料集標籤資訊讀取再處理)
  • n:錨定框的數量,即有幾組;預設值是9
  • img_size:影象尺寸。計算資料集樣本標籤框的寬高比時,是需要縮放到 img_size 大小後再計算的;預設值是640
  • thr:資料集中標註框寬高比最大閾值,預設是使用 超參檔案 hyp.scratch.yaml 中的 “anchor_t” 引數值;預設值是4.0;自動計算時,會自動根據你所使用的資料集,來計算合適的閾值。
  • gen:kmean聚類演演算法迭代次數,預設值是1000
  • verbose:是否列印輸出所有計算結果,預設值是true

如果你不想自動計算錨定框,可以在 train.py 中設定引數即可:

parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')

2、訓練前手動計算錨定框

如果使用 yolov5 訓練效果並不好(排除其他原因,只考慮 “預設錨定框” 這個因素), yolov5在核查預設錨定框是否符合要求時,計算的最佳召回率大於0.98,沒有自動計算錨定框;此時你可以自己手動計算錨定框。【即使自己的資料集中目標寬高比最大值小於4,預設錨定框也不一定是最合適的】

 首先可以自行編寫一個程式,統計一下你所訓練的資料集所有標籤框寬高比,看下寬高比主要分佈在哪個範圍、最大寬高比是多少? 比如:你使用的資料集中目標寬高比最大達到了 5:1(甚至 10:1) ,那肯定需要重新計算錨定框了,針對coco資料集的最大寬高比是 4:1 。

然後在 yolov5 程式中建立一個新的 python 檔案 test.py,手動計算錨定框:

import utils.autoanchor as autoAC
 
# 對資料集重新計算 anchors
new_anchors = autoAC.kmean_anchors('./data/mydata.yaml', 9, 640, 5.0, 1000, True)
print(new_anchors)

輸入資訊如下(只擷取了部分):

autoanchor: Evolving anchors with Genetic Algorithm: fitness = 0.6604:  87%|████████▋ | 866/1000 [00:00<00:00, 2124.00it/s]autoanchor: thr=0.25: 0.9839 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.662-mean/best, past_thr=0.476-mean: 15,20,  38,25,  55,65,  131,87,  97,174,  139,291,  256,242,  368,382,  565,422
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  39,26,  54,64,  127,87,  97,176,  142,286,  257,245,  374,379,  582,424
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  39,26,  54,63,  126,86,  97,176,  143,285,  258,241,  369,381,  583,424
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  39,26,  54,63,  127,86,  97,176,  143,285,  258,241,  369,380,  583,424
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  39,26,  53,63,  127,86,  97,175,  143,284,  257,243,  369,381,  582,422
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  40,26,  53,62,  129,85,  96,175,  143,287,  256,240,  370,378,  582,419
autoanchor: Evolving anchors with Genetic Algorithm: fitness = 0.6605: 100%|██████████| 1000/1000 [00:00<00:00, 2170.29it/s]
Scanning '..coco128labelstrain2017.cache' for images and labels... 128 found, 0 missing, 2 empty, 0 corrupted: 100%|██████████| 128/128 [00:00<?, ?it/s]
autoanchor: thr=0.25: 0.9849 best possible recall, 3.84 anchors past thr
autoanchor: n=9, img_size=640, metric_all=0.267/0.663-mean/best, past_thr=0.476-mean: 15,20,  40,26,  53,62,  129,85,  96,175,  143,287,  256,240,  370,378,  582,419
[[     14.931      20.439]
 [     39.648       25.53]
 [     53.371       62.35]
 [     129.07      84.774]
 [     95.719      175.08]
 [     142.69      286.95]
 [     256.46      239.83]
 [      369.9       378.3]
 [     581.87      418.56]]
 
Process finished with exit code 0

輸出的 9 組新的錨定框即是根據自己的資料集來計算的,可以按照順序替換到你所使用的組態檔*.yaml中(比如 yolov5s.yaml)。就可以重新訓練了。

參考的博文(表示感謝!):

https://github.com/ultralytics/yolov5

https://blog.csdn.net/flyfish1986/article/details/117594265

https://zhuanlan.zhihu.com/p/183838757

https://blog.csdn.net/aabbcccddd01/article/details/109578614

總結

到此這篇關於yolov5中anchors設定詳解的文章就介紹到這了,更多相關yolov5 anchors設定內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com