前言:YOLOv3代碼中也提供了參數搜索,可以為對應的數據集進化一套合適的超參數。本文建檔分析一下有關這部分的操作方法以及其參數的具體進化方法。
1. 超參數
YOLOv3中的 超參數在train.py中提供,其中包含了一些數據增強參數設置,具體內容如下:
hyp = {'giou': 3.54, # giou loss gain
'cls': 37.4, # cls loss gain
'cls_pw': 1.0, # cls BCELoss positive_weight
'obj': 49.5, # obj loss gain (*=img_size/320 if img_size != 320)
'obj_pw': 1.0, # obj BCELoss positive_weight
'iou_t': 0.225, # iou training threshold
'lr0': 0.00579, # initial learning rate (SGD=1E-3, Adam=9E-5)
'lrf': -4., # final LambdaLR learning rate = lr0 * (10 ** lrf)
'momentum': 0.937, # SGD momentum
'weight_decay': 0.000484, # optimizer weight decay
'fl_gamma': 0.5, # focal loss gamma
'hsv_h': 0.0138, # image HSV-Hue augmentation (fraction)
'hsv_s': 0.678, # image HSV-Saturation augmentation (fraction)
'hsv_v': 0.36, # image HSV-Value augmentation (fraction)
'degrees': 1.98, # image rotation (+/- deg)
'translate': 0.05, # image translation (+/- fraction)
'scale': 0.05, # image scale (+/- gain)
'shear': 0.641} # image shear (+/- deg)
2. 使用方法
在訓練的時候,train.py提供了一個可選參數--evolve
, 這個參數決定了是否進行超參數搜索與進化(默認是不開啟超參數搜索的)。
具體使用方法也很簡單:
python train.py --data data/voc.data
--cfg cfg/yolov3-tiny.cfg
--img-size 416
--epochs 273
--evolve
實際使用的時候,需要進行修改,train.py中的約444行:
for _ in range(1): # generations to evolve
將其中的1修改為你想設置的迭代數,比如200代,如果不設置,結果將會如下圖所示,實際上就是只有一代。
3. 原理
整個過程比較簡單,對於進化過程中的新一代,都選了了適應性最高的前一代(在前幾代中)進行突變。以上所有的參數將有約20%的 1-sigma的正態分布幾率同時突變。
s = 0.2 # sigma
整個進化過程需要搞清楚兩個點:
- 如何評判其中一代的好壞?
- 下一代如何根據上一代進行進化?
第一個問題:判斷好壞的標准。
def fitness(x):
w = [0.0, 0.0, 0.8, 0.2]
# weights for [P, R, mAP, F1]@0.5
return (x[:, :4] * w).sum(1)
YOLOv3進化部分是通過以上的適應度函數判斷的,適應度越高,代表這一代的性能越好。而在適應度中,是通過Precision,Recall ,mAP,F1這四個指標作為適應度的評價標准。
其中的w是設置的加權,如果更關心mAP的值,可以提高mAP的權重;如果更關心F1,則設置更高的權重在對應的F1上。這里分配mAP權重為0.8、F1權重為0.2。
第二個問題:如何進行進化?
進化過程中有兩個重要的參數:
第一個參數為parent, 可選值為single
或者weighted
,這個參數的作用是:決定如何選擇上一代。如果選擇single,代表只選擇上一代中最好的那個。
if parent == 'single' or len(x) == 1:
x = x[fitness(x).argmax()]
如果選擇weighted,代表選擇得分的前10個加權平均的結果作為下一代,具體操作如下:
elif parent == 'weighted': # weighted combination
n = min(10, len(x)) # number to merge
x = x[np.argsort(-fitness(x))][:n] # top n mutations
w = fitness(x) - fitness(x).min() # weights
x = (x * w.reshape(n, 1)).sum(0) / w.sum() # new parent
第二個參數為method,可選值為1,2,3
, 分別代表使用三種模式來進化:
# Mutate
method = 2
s = 0.2 # 20% sigma
np.random.seed(int(time.time()))
g = np.array([1, 1, 1, 1, 1, 1, 1, 0, .1, \
1, 0, 1, 1, 1, 1, 1, 1, 1]) # gains
# 這里的g類似加權
ng = len(g)
if method == 1:
v = (np.random.randn(ng) *
np.random.random() * g * s + 1) ** 2.0
elif method == 2:
v = (np.random.randn(ng) *
np.random.random(ng) * g * s + 1) ** 2.0
elif method == 3:
v = np.ones(ng)
while all(v == 1):
# 為了防止重復,直到有變化才停下來
r = (np.random.random(ng) < 0.1) * np.random.randn(ng)
# 10% 的突變幾率
v = (g * s * r + 1) ** 2.0
for i, k in enumerate(hyp.keys()):
hyp[k] = x[i + 7] * v[i]
# 進行突變
另外,為了防止突變過程,導致參數出現明顯不合理的范圍,需要用一個范圍進行框定,將超出范圍的內容剪切掉。具體方法如下:
# Clip to limits
keys = ['lr0', 'iou_t', 'momentum',
'weight_decay', 'hsv_s',
'hsv_v', 'translate',
'scale', 'fl_gamma']
limits = [(1e-5, 1e-2), (0.00, 0.70),
(0.60, 0.98), (0, 0.001),
(0, .9), (0, .9), (0, .9),
(0, .9), (0, 3)]
for k, v in zip(keys, limits):
hyp[k] = np.clip(hyp[k], v[0], v[1])
最終訓練的超參數搜索的結果可視化:
參考資料: