數(shù)據(jù)加載
yolov5的數(shù)據(jù)加載部分由create_dataloader函數(shù)實(shí)現(xiàn)(位于utils/datasets.py)醉鳖,其中關(guān)于數(shù)據(jù)增強(qiáng)和加載的部分主要由LoadImagesAndLabels和InfiniteDataLoader負(fù)責(zé)近忙,并基于torch_distributed_zero_first(rank)進(jìn)行不同進(jìn)程之間的數(shù)據(jù)同步纹笼。
數(shù)據(jù)同步
在yolov5的模型訓(xùn)練中涉及了多進(jìn)程并行運(yùn)算滔吠。其中,主進(jìn)程實(shí)現(xiàn)數(shù)據(jù)的預(yù)讀取并緩存盅惜,然后其它子進(jìn)程則從緩存中讀取數(shù)據(jù)并進(jìn)行一系列運(yùn)算糙捺。為了完成數(shù)據(jù)的正常同步,yolov5中基于torch.distributed.barrier()函數(shù)實(shí)現(xiàn)了上下文管理器torch_distributed_zero_first:
@contextmanager
def torch_distributed_zero_first(local_rank: int):
"""
Decorator to make all processes in distributed training wait for each local_master to do something.
"""
if local_rank not in [-1, 0]:
torch.distributed.barrier()
yield
if local_rank == 0:
torch.distributed.barrier()
torch_distributed_zero_first使用方式:
with torch_distributed_zero_first(rank):
dataset = LoadImagesAndLabels(......)
rank表示當(dāng)前的進(jìn)程號(hào)候味,主進(jìn)程由編號(hào)0表示刃唤,子進(jìn)程則由編號(hào)1、2白群、3...等表示尚胞。上述代碼的運(yùn)行邏輯:
- 進(jìn)入torch_distributed_zero_first(rank)上下文作用域;
- 判斷當(dāng)前進(jìn)程號(hào)local_rank是否為-1或0帜慢,如果不是則說(shuō)明為子進(jìn)程笼裳,運(yùn)行torch.distributed.barrier()等待主進(jìn)程的數(shù)據(jù)處理完畢,如果是則當(dāng)前為主進(jìn)程崖堤,不需要等待侍咱;
- yield后,運(yùn)行with作用域范圍內(nèi)的代碼密幔;
- 作用域范圍內(nèi)代碼運(yùn)行完成后楔脯,繼續(xù)yield的后續(xù)操作,判斷當(dāng)前進(jìn)程號(hào)是否為0(即是否為主進(jìn)程)胯甩,如果是昧廷,則運(yùn)行torch.distributed.barrier()可以解開其它子進(jìn)程的阻塞。
注意 對(duì)于torch.distributed.barrier()函數(shù)的作用可以參考https://stackoverflow.com/questions/59760328/how-does-torch-distributed-barrier-work進(jìn)行理解偎箫。
數(shù)據(jù)增強(qiáng)
數(shù)據(jù)增強(qiáng)相關(guān)的方法在LoadImagesAndLabels類中實(shí)現(xiàn)木柬。
Rectangular Training
通常YOLO系列網(wǎng)絡(luò)的輸入都是預(yù)處理后的方形圖像數(shù)據(jù),如416 * 416淹办、608 * 608眉枕。當(dāng)原始圖像為矩形時(shí),會(huì)將其填充為方形(如下圖:方形輸入),但是填充的灰色區(qū)域其實(shí)就是冗余信息速挑,不論是在訓(xùn)練還是推理階段谤牡,這些冗余信息都會(huì)增加耗時(shí)。
為了減少圖像的冗余數(shù)據(jù)姥宝,輸入圖像由方形改為矩形(如下圖:矩形輸入):將長(zhǎng)邊resize為固定尺寸(如416)翅萤,短邊按同樣比例resize,然后把短邊的尺寸盡量少地填充為32的倍數(shù)腊满。
這種方法在推理階段稱為矩形推理(Rectangular Inference)套么,在訓(xùn)練階段則稱為矩形訓(xùn)練(Rectangular Training)。推理階段直接對(duì)圖像進(jìn)行resize和pad就行碳蛋,但是訓(xùn)練階段輸入的是一個(gè)批次的圖像集合胚泌,需要保持批次內(nèi)的圖像尺寸一致,因此處理邏輯相對(duì)復(fù)雜一些疮蹦。
代碼:
if self.rect:
# Sort by aspect ratio
# 首先根據(jù)高寬比排序诸迟,就可以保證每個(gè)batch內(nèi)的圖像高寬比相近。
s = self.shapes # wh
ar = s[:, 1] / s[:, 0] # aspect ratio 高/寬
irect = ar.argsort()
self.img_files = [self.img_files[i] for i in irect]
self.label_files = [self.label_files[i] for i in irect]
self.labels = [self.labels[i] for i in irect]
self.shapes = s[irect] # wh
ar = ar[irect]
# Set training image shapes
shapes = [[1, 1]] * nb # hw
for i in range(nb):
ari = ar[bi == i]
mini, maxi = ari.min(), ari.max()
if maxi < 1: # 高寬比最大值都小于1愕乎,則說(shuō)明batch內(nèi)的圖全都是高小于寬
shapes[i] = [maxi, 1] # 設(shè)置寬為固定比例1阵苇,高的比例為maxi
elif mini > 1: # 高寬比最小值都大于1,說(shuō)明batch內(nèi)的圖都是高>寬
shapes[i] = [1, 1 / mini] # 設(shè)置高為固定比例1感论,寬的比例為1 / mini
運(yùn)行邏輯:
- 根據(jù)數(shù)據(jù)集中所有圖像的shape計(jì)算高寬比ar绅项;
- 對(duì)長(zhǎng)寬比ar進(jìn)行argsort,即對(duì)ar內(nèi)的元素進(jìn)行排序(升序)比肄,并針對(duì)排序后的元素對(duì)應(yīng)取得其在ar中的索引快耿,構(gòu)成索引序列irect;
- 根據(jù)索引序列irect取得排序后的self.img_files芳绩、self.label_files掀亥、self.labels 、self.shapes和ar妥色;
- 初始化nb(即batch數(shù)量)個(gè)shape為[1,1]搪花,組成shapes;
- 從ar中對(duì)應(yīng)取出每個(gè)batch的高寬比列表ari嘹害,取其中的最大撮竿、最小值;
- 如果當(dāng)前batch的高寬比最大值小于1笔呀,則將shapes內(nèi)該batch對(duì)應(yīng)的值設(shè)為[maxi, 1]幢踏,而如果最大值<=1且最小值>1,則設(shè)置為[1, 1 / mini]许师,如果都不符合默認(rèn)[1,1]房蝉。
參考:https://github.com/ultralytics/yolov3/issues/232
Mosaic
未完待續(xù)