yolov5代碼閱讀筆記

文章目錄

博客中每個字都是自己碼的,圖也是自己畫的,轉載或使用請徵得同意。謝謝。

Yolov5 代碼筆記

Yolov5源碼:https://github.com/ultralytics/yolov5
該筆記主要對四個函數的重點部分進行分析,分別是train.py中的train(hyp)函數,數據集整理dataset.py,模型文件yolo.py及loss函數計算utils.py中的compute_loss函數。

1. train(hyp)函數重點部分

1.1 載入圖片路徑

# Configure
init_seeds(1)
with open(opt.data) as f:
	data_dict = yaml.load(f, Loader=yaml.FullLoader)  # model dict
train_path = data_dict['train']
test_path = data_dict['val']
nc = 1 if opt.single_cls else int(data_dict['nc'])  # number of classes

1.2 創建模型

model = Model(opt.cfg).to(device)

1.3 確定訓練和測試圖片的尺寸

imgsz, imgsz_test = [make_divisible(x, gs) for x in opt.img_size]  # image sizes (train, test)

1.4 根據參數的形式(是否爲weight,是否爲bias等)定義模型的不同部分,併爲不同部分設置不同的學習率及參數

imgsz, imgsz_test = [make_divisible(x, gs) for x in opt.img_size]  # image sizes (train, test)

1.5 載入預訓練模型(ckpt)及權重(ckpt[‘model’])

if weights.endswith('.pt'):  # pytorch format
    ckpt = torch.load(weights, map_location=device)  # load checkpoint
    # load model
    try:
        ckpt['model'] = {k: v for k, v in ckpt['model'].state_dict().items() if model.state_dict()[k].numel() == v.numel()}
        model.load_state_dict(ckpt['model'], strict=False)

1.6 定義學習率變化趨勢,這裏採用的是Cosine Learning Rate Decay

# Scheduler https://arxiv.org/pdf/1812.01187.pdf
lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1  # cosine
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
scheduler.last_epoch = start_epoch - 1  # do not move

1.7 載入數據集,定義數據集迭代器

dataset = LoadImagesAndLabels(train_path, imgsz, batch_size, 
                              augment=True,
                              hyp=hyp,  # augmentation hyperparameters
                              rect=opt.rect,  # rectangular training
                              cache_images=opt.cache_images,
                              single_cls=opt.single_cls)

1.8 定義訓練集和驗證集的數據讀取器(dataloader和testloader),二者的區別是testloader使用的測試圖片保留了原圖的寬長比(即是個矩形輸入),並且沒有數據增強及Mosaic的過程(self.augment=False, self.mosaic=False)。

dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, num_workers=nw, shuffle=not opt.rect, pin_memory=True, collate_fn=dataset.collate_fn)

# Testloader
testloader=torch.utils.data.DataLoader(LoadImagesAndLabels(test_path, imgsz_test, batch_size, hyp=hyp, rect=True, cache_images=opt.cache_images, single_cls=opt.single_cls), batch_size=batch_size, num_workers=nw, pin_memory=True, collate_fn=dataset.collate_fn)

1.9 統計不同類別的樣本數

labels = np.concatenate(dataset.labels, 0)
c = torch.tensor(labels[:, 0])  # classes
# cf = torch.bincount(c.long(), minlength=nc) + 1.
# model._initialize_biases(cf.to(device))
plot_labels(labels)
tb_writer.add_histogram('classes', c, 0)
# Exponential moving average
ema = torch_utils.ModelEMA(model)

1.10 開始訓練

1.10.1 學習率warmup

for j, x in enumerate(optimizer.param_groups):
# bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
    x['lr'] = np.interp(ni, xi, [0.1 if j == 2 else 0.0, x['initial_lr'] * lf(epoch)])
    if 'momentum' in x:
        x['momentum'] = np.interp(ni, xi, [0.9, hyp['momentum']])

1.10.2 計算loss (utils.py comput_loss函數)

loss, loss_items = compute_loss(pred, targets.to(device), model)

1.10.3 計算驗證集指標(test.py)

在這裏插入圖片描述

1.10.4 保存模型

在這裏插入圖片描述

2. 數據集文件 (dataset.py中class LoadImagesAndLabels)

2.1 生成圖片列表(self.img_files)和標籤列表(self.label_files)

在這裏插入圖片描述
在這裏插入圖片描述

2.2 快速有效地讀取label信息

該類的__init__函數剩餘的部分主要的目的就是解決快速有效讀取label信息的問題。如果之前已經生成了代表label的npy文件,則直接讀取。生成的npy的格式是一張圖對應一個矩陣,如果一張圖中有5個框,則矩陣的維度就是5×55\times5
如果數據集是第一次被訓練,則首先會對所有labels進行緩存,這樣後續讀取label會比較快。
在這裏插入圖片描述

2.3 數據增強

2.3.1 Mosaic (dataset.py load_mosaic函數)

Mosaic數據增強的方法是隨機挑選4張圖構成一個大圖,將大圖輸入後續流程。
Step 1[0.5×image_size,1.5×image_size][0.5 \times image\_size, 1.5 \times image\_size]範圍內隨機選出xcxcycyc,這兩個是大圖的中心位置。

xc, yc = [int(random.uniform(s * 0.5, s * 1.5)) for _ in range(2)]  # mosaic center x, y

Step 2 除了輸入圖片外,再隨機挑選三張圖片,索引號爲indices。

indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)]  # 3 additional image indices

Step 3 把Step 2挑選的四張圖片分別放在左上、左下、右上和右下四個位置。
在這裏插入圖片描述
圖中i=0:3代表4張小圖,x1a,y1a,x2a,y2ax1a, y1a, x2a,y2a表示小圖在大圖中的位置(可以理解爲小圖在大圖中的相對座標),x1b,y1b,x2b,y2bx1b, y1b, x2b,y2b表示小圖自己的絕對座標。
下圖是兩種情況下的相對座標和絕對座標說明。第一種是特殊情況,即隨機到的xc和yc正好在大圖的中心位置;而第二種情況是一般情況下的一種。圖中的padw和padh用於Step 4的座標變換。
在這裏插入圖片描述
在這裏插入圖片描述
Step 4 Mosaic小圖中的座標變換。
在這裏插入圖片描述
Step 5 對生成的座標進行clip

np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:])  # use with random_affine

Step 6 對Mosaic之後的大圖進行旋轉,縮放等常規操作。假設圖片尺寸爲640×640×3640 \times 640 \times 3,經過Mosaic之後的尺寸是1280×1280×31280 \times 1280 \times 3,經過旋轉,縮放之後,圖片尺寸又變爲640×640×3640 \times 640 \times 3,這個尺寸也是輸入網絡的圖片尺寸。
在這裏插入圖片描述

2.3.2 色彩空間hsv增強(dataset.py augment_hsv函數)

在這裏插入圖片描述

2.3.3 隨機水平翻轉和垂直翻轉

在這裏插入圖片描述

3. 模型文件 (yolo.py)

3.1 parse_model函數,讀入模型yaml中的參數定義

self.model, self.save = parse_model(self.md, ch=[ch])

3.1.1 執行對應的模塊

m = eval(m) if isinstance(m, str) else m  # eval strings

例如:運行eval(‘Focus’)會輸出<class ‘models.common.Focus’>

3.1.2 對網絡組件進行處理,生成所需要的格式

m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args)  # module
t = str(m)[8:-2].replace('__main__.', '')  # module type
np = sum([x.numel() for x in m_.parameters()])  # number params
m_.i, m_.f, m_.type, m_.np = i, f, t, np  # attach index, 'from' index, type, number params
print('%3s%15s%3s%10.0f  %-40s%-30s' % (i, f, n, np, t, args))  # print
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1)  # append to savelist
layers.append(m_)
ch.append(c2)

3.2 class Model

該class包括模型的主幹網絡,其中有一個比較重要的操作是計算stride(輸入和特徵圖之間的尺度比值)。最後輸出的是一個列表,列表的長度爲3,代表的是darknet的3個尺度輸出。這3個尺度的輸出跟輸入去求stride,這三層對應的層數是模型打印出來的[-1, 11, 16],其中-1代表從上一層接收到的輸出。
在這裏插入圖片描述

3.3 class Detector

訓練過程:當模型的輸入圖片尺寸爲[1,3,64,64]時,Detector類的輸入是2.2中產生的長度爲3的列表,維度分別爲[1,90,8,8], [1,90,4,4],[1,90,2,2]。Detector類的輸出是把上述維度變成[1,3,30,8,8],[1,3,30,4,4],[1,3,30,2,2]。
測試過程:Detector類就要生成多個錨框(anchors),並且將這些anchors進行尺寸映射。
在這裏插入圖片描述

4. Loss函數(utils.py compute_loss函數)

4.1 build_targets函數

我理解的是對每一層(共3層)的anchors進行篩選,並最終選出有用的anchors

4.2 giou loss

在這裏插入圖片描述

4.3 class loss和obj loss

在這裏插入圖片描述

5. Q & A

5.1 Focus模塊是什麼?

5.2 輸入尺寸和Focus模塊的第一個卷積層尺寸不對應的問題

斷點調試的時候發現,圖片輸出尺寸爲(1, 3, 64, 64),但是第一個卷積層的Input_channel是12。我原來以爲是Mosaic四張圖的堆疊,但是仔細讀了Mosaic部分的代碼之後,覺得又不是。Github上問了作者,也沒得到答覆。他在別的Issue裏說年底會公佈文章,只有等他的文章了,不知道會不會對這個問題有解釋。
在這裏插入圖片描述

5.3 build_targets那塊不是特別理解,我大概的理解是通過一個閾值去選了部分的anchors,還有待明確。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章