目錄
YOLOv4 = CSPDarknet53 + SPP + PAN + YOLOv3
YOLOv4採用的trick能夠分爲如下幾類:
git
- 用於骨幹網的 Bag of Freebies(BoF):
CutMix
和Mosaic
數據加強,DropBlock
正則化,Label Smooth - 用於骨幹網的 Bag of Specials(BoS):
Mish
,跨階段部分鏈接(CSP
),多輸入加權剩餘鏈接(MiWRC
) - 用於檢測器的 Bag of Specials(BoS):
Mish
,SPP
塊,SAM
塊,PAN
路徑彙集塊,DIoU-NMS
本文就YOLOv4
中涉及和採用的部分數據加強tricks進行總結和學習。github
MixUp
論文:https://arxiv.org/pdf/1710.09412.pdf
代碼(官方):https://github.com/hongyi-zhang/mixup
復現版本:https://github.com/tengshaofeng/ResidualAttentionNetwork-pytorch
mixup主要是用於圖像分類,從訓練樣本中隨機抽取兩個樣本進行簡單的隨機加權求和,同時樣本的標籤也對應加權求和,而後預測結果與加權求和以後的標籤求損失,在反向求導更新參數,公式定義以下:
算法
由公式能夠看到,加權融合同時做用在圖片和label兩個維度。
Pytorch代碼以下:
app
def mixup_data(x, y, alpha=1.0, use_cuda=True): '''Compute the mixup data. Return mixed inputs, pairs of targets, and lambda''' if alpha > 0.: lam = np.random.beta(alpha, alpha) else: lam = 1. batch_size = x.size()[0] if use_cuda: index = torch.randperm(batch_size).cuda() else: index = torch.randperm(batch_size) mixed_x = lam * x + (1 - lam) * x[index,:] y_a, y_b = y, y[index] return mixed_x, y_a, y_b, lam
由代碼看出,mixup_data
並非同時取出兩個batch,而是取一個batch,並將該batch中的樣本ID順序打亂(shuffle),而後再進行加權求和。
總體的MixUp
算法流程以下:
dom
- 對於輸入的一個batch的待測圖片images,咱們將其和隨機抽取的圖片進行融合,融合比例爲lam,獲得混合張量inputs;
- 第1步中圖片融合的比例lam是[0,1]之間的隨機實數,符合beta分佈,相加時兩張圖對應的每一個像素值直接相加,即 inputs = lam*images + (1-lam)*images_random;將1中獲得的混合張量inputs傳遞給model獲得輸出張量outpus,
- 隨後計算損失函數時,咱們針對兩個圖片的標籤分別計算損失函數,而後按照比例lam進行損失函數的加權求和,即loss = lam * criterion(outputs, targets_a) + (1 - lam) * criterion(outputs, targets_b);
- 反向求導更新參數。
Pytorch代碼以下:函數
criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=base_learning_rate, momentum=0.9, weight_decay=args.decay) """ 訓練 """ def train(epoch): print('\nEpoch: %d' % epoch) net.train() train_loss = 0 correct = 0 total = 0 for batch_idx, (inputs, targets) in enumerate(trainloader): if use_cuda: inputs, targets = inputs.cuda(), targets.cuda() """ generate mixed inputs, two one-hot label vectors and mixing coefficient """ inputs, targets_a, targets_b, lam = mixup_data(inputs, targets, args.alpha, use_cuda) inputs, targets_a, targets_b = Variable(inputs), Variable(targets_a), Variable(targets_b) outputs = net(inputs) """ 計算loss """ loss_func = mixup_criterion(targets_a, targets_b, lam) loss = loss_func(criterion, outputs) """ 更新梯度 """ optimizer.zero_grad() loss.backward() optimizer.step()
其中mixup_criterion
定義以下,損失函數是輸出的預測值對這兩組標籤分別求損失.學習
def mixup_criterion(y_a, y_b, lam): return lambda criterion, pred: lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)
- 1
- 2
目標檢測實現見:
mixup for detection
ui
CutMix
論文:https://arxiv.org/abs/1905.04899v2
代碼:https://github.com/clovaai/CutMix-PyTorch
spa
CutMix的處理方式也比較簡單,一樣也是對一對圖片作操做,簡單講就是隨機生成一個裁剪框Box,裁剪掉A圖的相應位置,而後用B圖片相應位置的ROI放到A圖中被裁剪的區域造成新的樣本,計算損失時一樣採用加權求和的方式進行求解。
兩張圖合併操做定義以下:
其中,M表示二進制0,1矩陣,表示從兩個圖像中刪除並填充的位置,實際就是用來標記須要裁剪的區域和保留的區域,裁剪的區域值均爲0,其他位置爲1。1是全部元素都是1的矩陣,維度大小與M相同。圖像A和B組合獲得新樣本,最後兩個圖的標籤也對應求加權和。權值同mixup同樣是採用bata分佈隨機獲得,alpha的值爲論文中取值爲1,這樣加權係數就服從beta分佈,請注意,主要區別在於CutMix用另外一個訓練圖像中的補丁替換了圖像區域,而且比Mixup生成了更多的本地天然圖像。
.net
爲了對二進制掩碼M進行採樣,首先要對剪裁區域的邊界框B= (r_x, r_y, r_w, r_h}進行採樣,用來對樣本x_A和x_B作裁剪區域的指示標定。
裁剪區域的邊界框採樣公式以下:
W,H是二進制掩碼矩陣M的寬高大小,且剪裁區域的比例知足:
肯定好裁剪區域B以後,將二進制掩碼M中的裁剪區域B置0,其餘區域置1,這樣就就完成了掩碼M的採樣,而後將M點乘A將樣本A中的剪裁區域B移除,(1-M)點乘B將樣本B中的剪裁區域B進行裁剪填充到樣本A,造成一個全新樣本。
- 生成剪裁區域
"""輸入爲:樣本的size和生成的隨機lamda值""" def rand_bbox(size, lam): W = size[2] H = size[3] """論文裏的公式2,求出B的rw,rh""" cut_rat = np.sqrt(1. - lam) cut_w = np.int(W * cut_rat) cut_h = np.int(H * cut_rat) # uniform """論文裏的公式2,求出B的rx,ry(bbox的中心點)""" cx = np.random.randint(W) cy = np.random.randint(H) # np.clip限制大小 """限制B座標區域不超過樣本大小""" bbx1 = np.clip(cx - cut_w // 2, 0, W) bby1 = np.clip(cy - cut_h // 2, 0, H) bbx2 = np.clip(cx + cut_w // 2, 0, W) bby2 = np.clip(cy + cut_h // 2, 0, H) return bbx1, bby1, bbx2, bby2
- 應用CutMix大概流程
for i, (input, target) in enumerate(train_loader): # measure data loading time data_time.update(time.time() - end) input = input.cuda() target = target.cuda() r = np.random.rand(1) if args.beta > 0 and r < args.cutmix_prob: # generate mixed sample """設定lamda的值,服從beta分佈""" lam = np.random.beta(args.beta, args.beta) rand_index = torch.randperm(input.size()[0]).cuda() """獲取batch裏面的兩個隨機樣本 """ target_a = target target_b = target[rand_index] """獲取裁剪區域bbox座標位置 """ bbx1, bby1, bbx2, bby2 = rand_bbox(input.size(), lam) """將原有的樣本A中的B區域,替換成樣本B中的B區域""" input[:, :, bbx1:bbx2, bby1:bby2] = input[rand_index, :, bbx1:bbx2, bby1:bby2] # adjust lambda to exactly match pixel ratio """根據剪裁區域座標框的值調整lam的值 """ lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (input.size()[-1] * input.size()[-2])) # compute output """計算模型輸出 """ output = model(input) """計算損失 """ loss = criterion(output, target_a) * lam + criterion(output, target_b) * (1. - lam) else: # compute output output = model(input) loss = criterion(output, target)
Mosaic
Mosaic能夠說是YOLOv4中的一個亮點,可是Mosaic並非YOLOv4提出的(我不是槓精),在u版舊版本的yolo3(現已更新)中就已經有mosaic的實現。
Mosaic混合了4個訓練圖像, 所以,混合了4個不一樣的上下文,而CutMix僅混合了2個輸入圖像,這就是Mosaic更強的緣由。
能夠理解爲Mosaic混合更多圖像創造了更多的可能性,見多識廣。
順便一提的是YOLOv4後的Stitcher小目標檢測方法與Mosaic有點相似,也是拼接了四個圖像,用以提高小目標檢測。參考個人博文:Stitcher學習筆記:提高小目標檢測 — 簡單而有效
直接上Mosaic的代碼,摘自https://github.com/ultralytics/yolov3/blob/master/utils/datasets.py
def load_mosaic(self, index): # loads images in a mosaic labels4 = [] s = self.img_size xc, yc = [int(random.uniform(s * 0.5, s * 1.5)) for _ in range(2)] # mosaic center x, y indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices for i, index in enumerate(indices): # Load image img, _, (h, w) = load_image(self, index) # place img in img4 if i == 0: # top left img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc # xmin, ymin, xmax, ymax (large image) x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h # xmin, ymin, xmax, ymax (small image) elif i == 1: # top right x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h elif i == 2: # bottom left x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h) x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h) elif i == 3: # bottom right x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h) x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h) img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax] padw = x1a - x1b padh = y1a - y1b # Labels x = self.labels[index] labels = x.copy() if x.size > 0: # Normalized xywh to pixel xyxy format labels[:, 1] = w * (x[:, 1] - x[:, 3] / 2) + padw labels[:, 2] = h * (x[:, 2] - x[:, 4] / 2) + padh labels[:, 3] = w * (x[:, 1] + x[:, 3] / 2) + padw labels[:, 4] = h * (x[:, 2] + x[:, 4] / 2) + padh labels4.append(labels) # Concat/clip labels if len(labels4): labels4 = np.concatenate(labels4, 0) # np.clip(labels4[:, 1:] - s / 2, 0, s, out=labels4[:, 1:]) # use with center crop np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:]) # use with random_affine # Augment # img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)] # center crop (WARNING, requires box pruning) img4, labels4 = random_affine(img4, labels4, degrees=self.hyp['degrees'], translate=self.hyp['translate'], scale=self.hyp['scale'], shear=self.hyp['shear'], border=-s // 2) # border to remove return img4, labels4
參考:
https://blog.csdn.net/ouyangfushu/article/details/105575258
https://blog.csdn.net/weixin_38715903/article/details/103999227
https://zhuanlan.zhihu.com/p/138855612
https://www.zhihu.com/question/308572298