文章目錄
- Yolov5 代碼筆記
- 1. train(hyp)函數重點部分
- 1.1 載入圖片路徑
- 1.2 創建模型
- 1.3 確定訓練和測試圖片的尺寸
- 1.4 根據參數的形式(是否爲weight,是否爲bias等)定義模型的不同部分,併爲不同部分設置不同的學習率及參數
- 1.5 載入預訓練模型(ckpt)及權重(ckpt['model'])
- 1.6 定義學習率變化趨勢,這裏採用的是Cosine Learning Rate Decay
- 1.7 載入數據集,定義數據集迭代器
- 1.8 定義訓練集和驗證集的數據讀取器(dataloader和testloader),二者的區別是testloader使用的測試圖片保留了原圖的寬長比(即是個矩形輸入),並且沒有數據增強及Mosaic的過程(self.augment=False, self.mosaic=False)。
- 1.9 統計不同類別的樣本數
- 1.10 開始訓練
- 2. 數據集文件 (dataset.py中class LoadImagesAndLabels)
- 3. 模型文件 (yolo.py)
- 4. Loss函數(utils.py compute_loss函數)
- 5. Q & A
博客中每個字都是自己碼的,圖也是自己畫的,轉載或使用請徵得同意。謝謝。
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個框,則矩陣的維度就是。
如果數據集是第一次被訓練,則首先會對所有labels進行緩存,這樣後續讀取label會比較快。
2.3 數據增強
2.3.1 Mosaic (dataset.py load_mosaic函數)
Mosaic數據增強的方法是隨機挑選4張圖構成一個大圖,將大圖輸入後續流程。
Step 1 從範圍內隨機選出和,這兩個是大圖的中心位置。
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張小圖,表示小圖在大圖中的位置(可以理解爲小圖在大圖中的相對座標),表示小圖自己的絕對座標。
下圖是兩種情況下的相對座標和絕對座標說明。第一種是特殊情況,即隨機到的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之後的大圖進行旋轉,縮放等常規操作。假設圖片尺寸爲,經過Mosaic之後的尺寸是,經過旋轉,縮放之後,圖片尺寸又變爲,這個尺寸也是輸入網絡的圖片尺寸。
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裏說年底會公佈文章,只有等他的文章了,不知道會不會對這個問題有解釋。