在神經網絡訓練時,還涉及到一些tricks,如網絡權重的初始化方法,優化器種類(權重更新),圖片預處理等,繼續填坑。
1. 神經網絡初始化(Network Initialization )
1.1 初始化原因
我們構建好網絡,開始訓練前,不能默認的將所有權重系數都初始化為零,因為所有卷積核的系數都相等時,提取特征就會一樣,反向傳播時的梯度也會存在對稱性,網絡會退化會線性模型。另外網絡層數較深時,初始化權重過大,會出現梯度爆炸,而過小又會出現梯度消失。一般權重初始化時需要考慮兩個問題:
(1)權重參數全部相同時,會有梯度更新對稱性問題(詳細見https://www.zhihu.com/question/36068411?sort=created)
如下圖中w,b系數都相同時,a1,a2,a3的值也會相同,反過來進行梯度更新時,w,b也是按相同的速率在更新。(不管是哪個神經元,它的 前向傳播和反向傳播的算法都是一樣的,如果初始值也一樣的話,不管訓練多久,它們最終都一樣,都無法打破對稱(fail to break the symmetry),那每一層就相當於只有一個神經元,最終L層神經網絡就相當於一個線性的網絡,如Logistic regression)
(2)采用飽和激活函數時,進行隨機初始化時,權重分布會使神經元輸出處於激活函數的梯度飽和區域。
(詳細見:https://www.jianshu.com/p/03009cfdf733)
1.2 初始化方法
常用初始化方法有Gaussain initialization, Xavier initialization, Kaiming(MSRA) initialization 。pytorch的torch.nn.init模塊中包含了常用的初始化函數。
Gaussian initialization: 采用高斯分布初始化權重參數
nn.init.normal_(tensor, mean=0, std=1) 能實現不同均值和標准差的高斯分布
nn.init.unoform_(tensor, a=0, b=1) 能實現(a, b)范圍內的均勻分布
import torch import torch.nn as nn w = torch.empty(3, 5) nn.init.uniform_(w, a=0, b=1) #初始化為(0, 1)范圍內的均勻分布 nn.init.normal_(w, mean=0, std=1) #初始化為均值為0, 標准差為1的正態分布 nn.init.constant_(w, 0.3) # 全部初始化為常量值0.3 nn.init.eye_(w) # 初始化為單位矩陣(對角線為1) print(w)
Xavier Initialization: 均值為0, 標准差根據輸入神經元和輸出神經元的參數個數決定,適合采用tanh和sigmoid等激活函數的模型,詳細見下面論文
論文:Understanding the difficulty of training deep feedforward neural networks
nn.init.xavier_uniform_(tensor, gain=1): 根據xavier,實現了(-a, a)范圍內的均勻分布,其中a的計算公式如下:
gain:增益,可以理解為縮放倍數,
fan_in: in_channel*Kw*Kh (輸入channel個數, kernel的寬和高)
fan_out: out_channel*Kw*Kh (輸出channel個數, kernel的寬和高)
nn.init.xavier_normal_(tensor, gain=1): 根據xavier,實現了mean=0, std=std 的高斯分布,其中std的計算公式如下:
w = torch.empty(3, 3, 5, 5) #fan_in=75, fan_out=75 nn.init.xavier_uniform_(w, gain=1) #初始化為(-sqrt(6/150), sqrt(6/150))范圍內的均勻分布 nn.init.xavier_normal_(w, gain=1) #初始化為mean=0, std=sqrt(2/150)的正態分布
關於fan_in和fan_out的計算方式,pytorch的實現代碼如下:

def _calculate_fan_in_and_fan_out(tensor): dimensions = tensor.ndimension() if dimensions < 2: raise ValueError("Fan in and fan out can not be computed for tensor with fewer than 2 dimensions") if dimensions == 2: # Linear fan_in = tensor.size(1) fan_out = tensor.size(0) else: num_input_fmaps = tensor.size(1) num_output_fmaps = tensor.size(0) receptive_field_size = 1 if tensor.dim() > 2: receptive_field_size = tensor[0][0].numel() fan_in = num_input_fmaps * receptive_field_size fan_out = num_output_fmaps * receptive_field_size return fan_in, fan_out
關於gain的值選取,pytorch推薦如下:
也可以通過函數計算: gain = nn.init.calculate_gain("leaky_relu", 0.2) ( leaky_relu with negative_slope=0.2)
#第一個參數為nn.functional中的函數名,第二個為可選參樹。
Kaiming/He Initialization: 均值為0, 標准差根據輸入神經元的參數個數決定, 特別采用適合ReLU激活函數的模型,詳細見下面論文
論文:《Delving Deep into Rectifiers:Surpassing Human-Level Performance on ImageNet Classification》
nn.init.kaiming_uniform_(tensor, a=0, mode="fan_in", nonlinearity="leaky_relu"), 實現了(-bound, bound)范圍內的均勻分布,計算公式如下:
nn.init.kaiming_normal_(tensor, a=0, mode="fan_in", nonlinearity="leaky_relu"): 實現了mean=0, std=std的正態分布,計算公式如下:
參數: tensor – n 維 torch.Tensor a – 該層后面一層的整流函數中負的斜率 (默認為 0,此時為 Relu) mode – ‘fan_in’ (default) 或者 ‘fan_out’。使用fan_in保持weights的方差在前向傳播中不變;使用fan_out保持weights的方差在反向傳播中不變。 nonlinearity – 非線性函數 (nn.functional 中的名字),推薦只使用 ‘relu’ 或 ‘leaky_relu’ (default)。 w = torch.empty(3, 3, 5, 5) #fan_in=75, fan_out=75 nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu') #初始化為(-sqrt(6/75), sqrt(6/75))范圍內的均勻分布 nn.init.kaiming_normal_(w, mode='fan_out', nonlinearity='relu') #初始化為mean=0, std=sqrt(2/75)的正態分布
1.3 初始化網絡
在實際項目,要根據網絡中的卷積核,BN和Linear等進行分開初始化,代碼如下:

#coding:utf-8 import torch import torch.nn as nn class MyNet(nn.Module): def __init__(self): super(MyNet, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1) self.bn1 = nn.BatchNorm2d(64, eps=1e-05, momentum=0.1) self.relu1 = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(2, stride=2, padding=0) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) self.bn2 = nn.BatchNorm2d(128, eps=1e-05, momentum=0.1) self.relu2 = nn.ReLU(inplace=True) self.maxpool2 = nn.MaxPool2d(2, stride=2, padding=0) self.fc1 = nn.Linear(6 * 64 * 64, 150) self.relu3 = nn.ReLU(inplace=True) self.fc2 = nn.Linear(150, 3) self.softmax = nn.Softmax(dim=1) def froward(x): x = self.maxpool1(self.relu1(self.bn1(self.conv1(x)))) x = self.maxpool2(self.relu2(self.bn2(self.conv2(x)))) x = x.view(6 * 64 * 64, -1) x = self.relu3(self.fc1(x)) x = self.softmax(self.fc1(x)) return x #初始化方法一 def init_weights(net): for m in net.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_uniform_(m.weight, a=0, mode="fan_in", nonlinearity="relu") nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.normal_(m.weight, mean=0, std=1e-3) nn.init.constant_(m.bias, 0) net = MyNet() init_weights(net) print(list(net.parameters()) #初始化方式二 def init_weights(m): if isinstance(m, nn.Conv2d): nn.init.xavier_uniform_(m.weight.data) nn.init.constant_(m.bias.data, 0.1) elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() elif isinstance(m, nn.Linear): m.weight.data.normal_(0, 0.01) m.bias.data.zero_() net = MyNet() net.apply(init_weights) print(list(net.parameters()))
2. 優化器(optimizer)
神經網絡訓練時,采用梯度下降,更新權重參數,逐漸逼近最小loss的方式。pytorch中優化梯度下降的算法有多種,包括基於動量的SGD, SGD+Momentum, Nesterov, 和自適應的Adagrad, Adam.。
2.1 動量法
動量更新: 動量法旨在通過每個參數在之前迭代中的梯度,來改變當前位置參數的梯度,在梯度穩定的地方能夠加速更新的速度,在梯度不穩定的地方能夠穩定梯度。
2.1.1 SGD(stachastic gradient desent)
隨機梯度下降:最簡單的梯度下降優化算法, 每一個mini-batch 更新一次權重參數, 公式如下, w為權重參數, 表示損失函數關於
的梯度,
表示學習率。
SGD存在兩個問題:
(1) SGD方法中的高方差振盪使得網絡很難穩定收斂,即會之字形搖擺,收斂速度慢。
(2)可能會陷入局部最小值而無法跳出。
2.1.2 SGD+Momentum: 加速訓練,跳出局部最小值
帶動量的SGD優化器:在SGD的基礎上引入動量,如下面公式中, 保存上一次的梯度,
為衰減系數。可以發現每次梯度更新時,都會引入上一次的梯度值,如果本次梯度和上一次梯度方向相同,會加速梯度下降,而方向不一致時,能減小梯度下降,從而保證:梯度方向不變的維度上速度變快,梯度方向有所改變的維度上的更新速度變慢,這樣就可以加快收斂並減小震盪。
2.1.3 Nesetrov: 也是一種動量更新的方式,不是很理解,更新公式如下, 詳細解釋參見https://zhuanlan.zhihu.com/p/79981927
pytorch的optim.SGD()實現了上述三種優化算法:
optim.SGD(params, lr, momentum=0, dampening=0, weight_decay=0, nesterov=False)
params: 需要更新的權重參數,生成器對象
lr: 學習速率,常用0.001
momentum: 動量的權重系數,對應上面式子中的,默認為0,表示不采用動量更新。常用0.8和0.9
nesterov: 是否采用Nesterov動量更新方式
weight_decay: L2正則懲罰項的系數, 默認為0表示不進行正則化懲罰。常用1e-5
from torch import optim optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) #model.parameters(), 神經網絡的權重參數
2.2 自適應法
自適應更新:它通過每個參數的歷史梯度,動態更新每一個參數的學習率,使得每個參數的更新率都能夠逐漸減小。前期梯度加大的,學習率減小得更快,梯度小的,學習率減小得更慢些。(能解決 SGD 遇到鞍點或者極小值點后學習變慢的問題)
2.2.1 Adagrad:自適應的調節學習率,更新算法如下,h中儲存了梯度的平方值,梯度越大時,h越大,而學習率會越小。
optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)
lr_decay:
init_accumulator_value
d = (torch.randn(5, 3) for i in range(3)) optimizer = optim.Adagrad(d, lr=0.001) print(optimizer)
2.2.2 RMSprop (Root mean square): Adagrad有個問題,其學習率會不斷地衰退,會使得很多任務在達到最優解之前學習率就已經過量減小,所以RMSprop采用了使用指數衰減平均來慢慢丟棄先前得梯度歷史,防止學習率過早地減小。其更新公式如下:
optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
alpha: 對應上式子中的ρ, 默認為0.99
eps: 為數學穩定項,對應上式子中的δ, 默認為1e-08
params = (torch.randn(5, 3) for i in range(3)) optimizer = optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False) print(optimizer)
2.2.3 Adam: 結合了上述的動量(Momentum)和自適應(Adaptive),同時對梯度和學習率進行動態調整。如果說動量相當於給優化過程增加了慣性,那么自適應過程就像是給優化過程加入了阻力。速度越快,阻力也會越大。更新公式如下:
optim.Adam(params, lr=0.001, beats=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
betas: 對應上述式子中的β1,β2, 默認β1=0.9, β2=0.999
params = (torch.randn(5, 3) for i in range(3)) optimizer = optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=Fals) print(optimizer)
2.3 優化器使用
上 述幾種優化器,對params中的參數全部使用相同的學習速率,在實際訓練模型時,也可以為模型不同層設置不同的學習速率。代碼如下:
使用相同的學習速率:

#coding:utf-8 import torch import torch.nn as nn from torch import optim class MyNet(nn.Module): def __init__(self): super(MyNet, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1) self.bn1 = nn.BatchNorm2d(64, eps=1e-05, momentum=0.1) self.relu1 = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(2, stride=2, padding=0) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) self.bn2 = nn.BatchNorm2d(128, eps=1e-05, momentum=0.1) self.relu2 = nn.ReLU(inplace=True) self.maxpool2 = nn.MaxPool2d(2, stride=2, padding=0) self.fc1 = nn.Linear(6 * 64 * 64, 150) self.relu3 = nn.ReLU(inplace=True) self.fc2 = nn.Linear(150, 3) self.softmax = nn.Softmax(dim=1) def froward(x): x = self.maxpool1(self.relu1(self.bn1(self.conv1(x)))) x = self.maxpool2(self.relu2(self.bn2(self.conv2(x)))) x = x.view(6 * 64 * 64, -1) x = self.relu3(self.fc1(x)) x = self.softmax(self.fc1(x)) return x model = MyNet() optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-05) print(optimizer) #dataset得自己實現 for input, target in dataset: optimizer.zero_grad() output = model(input) loss = loss_fn(output, target) loss.backward() optimizer.step()
使用不同的學習速率:

#coding:utf-8 import torch import torch.nn as nn from torch import optim class MyNet(nn.Module): def __init__(self): super(MyNet, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1) self.bn1 = nn.BatchNorm2d(64, eps=1e-05, momentum=0.1) self.relu1 = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(2, stride=2, padding=0) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) self.bn2 = nn.BatchNorm2d(128, eps=1e-05, momentum=0.1) self.relu2 = nn.ReLU(inplace=True) self.maxpool2 = nn.MaxPool2d(2, stride=2, padding=0) self.fc1 = nn.Linear(6 * 64 * 64, 150) self.relu3 = nn.ReLU(inplace=True) self.fc2 = nn.Linear(150, 3) self.softmax = nn.Softmax(dim=1) def froward(x): x = self.maxpool1(self.relu1(self.bn1(self.conv1(x)))) x = self.maxpool2(self.relu2(self.bn2(self.conv2(x)))) x = x.view(6 * 64 * 64, -1) x = self.relu3(self.fc1(x)) x = self.softmax(self.fc1(x)) return x model = MyNet() conv1_params = list(map(id, model.conv1.parameters())) conv2_params = list(map(id, model.conv2.parameters())) base_params = filter(lambda p: id(p) not in conv1_params+conv2_params, model.parameters()) params = [{"params": base_params}, {"params": model.conv1.parameters(), "lr": 0.001, "weight_decay":1e-05}, {"params": model.conv2.parameters(), "lr": 0.001, "weight_decay":1e-05} ] optimizer = optim.Adam(params, lr=0.01, weight_decay=1e-04) #base_params采用lr=0.01, weight_decay=1e-04; conv1和conv2層采用單獨的學習速率 print(optimizer) #dataset得自己實現 for input, target in dataset: optimizer.zero_grad() output = model(input) loss = loss_fn(output, target) loss.backward() optimizer.step()
3. 圖片預處理
在進行訓練模型前,有時候會對圖片進行預處理,主要包括兩方面: PCA降維和圖片增強
3.1 PCA降維
提取圖片的主要特征,同時能對一些較大的圖片進行壓縮。原理推導過程如下:(參考1,參考2)
數學原理參考:http://blog.codinglabs.org/articles/pca-tutorial.html
PCA算法步驟
(1)將數據組織成d =m*n(特征為n維, 每個維度上有每個數據)的矩陣,減去每一個維度上的均值(若不同維度間的數據尺度相差大時,還需除以標准差)
(2)計算矩陣d的協方差矩陣(dT *d)
(3)對協方差矩陣進行特征分解(或奇異值分解)
(4) 選取較大特征值對應的特征向量作為基向量,將矩陣的映射到基向量空間,即完成降維
使用代碼如下:

#coding:utf-8 import numpy as np #對x進行PCA將維 x = np.random.randn(1000, 500) x -= np.mean(x, axis=0) cov = np.dot(x.T, x)/(x.shape[0] -1) #x的協方差矩陣 u, s, v = np.linalg.svd(cov) #奇異值分解 x_reduced = np.dot(x, u[:, :100]) #取前100個特征向量(根據特征值大小從到小排列)

#coding:utf-8 import numpy as np from sklearn.decomposition import PCA import cv2 def my_pca(x, n=100): #對x進行PCA將維 x = x-np.mean(x, axis=0) cov = np.dot(x.T, x)/(x.shape[0] -1) #x的協方差矩陣, (n-1:除以n-1表示是樣本方差; 若是所有樣本的方差則除以n;參見https://www.cnblogs.com/datamining-bio/p/9267759.html) # cov = np.cov(x, rowvar=0) #計算協方差矩陣,注意此處rowvar=0表示,一列數據表示一個特征/一個維度 u, s, v = np.linalg.svd(cov) #奇異值分解 x_reduced = np.dot(x, u[:, :n]) #取前100個特征向量(根據特征值大小從到小排列) return x_reduced def my_pca2(x, n=100): #對x進行PCA將維 x = x-np.mean(x, axis=0) cov = np.dot(x.T, x)/(x.shape[0] -1) #x的協方差矩陣, (n-1:除以n-1表示是樣本方差; 若是所有樣本的方差則除以n;參見https://www.cnblogs.com/datamining-bio/p/9267759.html) #cov = np.cov(x, rowvar=0) #計算協方差矩陣,注意此處rowvar=0表示,一列數據表示一個特征/一個維度 eig_values, eig_vector = np.linalg.eig(cov) #特征值分解 index = np.argsort(-eig_values)[:n] #按特征值排序,挑選出特征值最大的100個特征值index,隨后取出其對應的100個特征向量 x_reduced = np.dot(x, eig_vector[:, :n]) #取前100個特征向量(根據特征值大小從到小排列) return x_reduced if __name__ == "__main__": data = np.random.randn(1000, 500) #表示1000條數據,一條數據有500個特征 r1 = my_pca(data) r2= my_pca2(data) pca=PCA(n_components=100) #加載PCA算法,設置降維后主成分數目為2 reduced_x=pca.fit_transform(data)#對樣本進行降維 #三種方法計算出來的降維數據並不近似相等,不是很理解?? print(np.allclose(r1, reduced_x, equal_nan=True)) #False print(np.allclose(r2, reduced_x, equal_nan=True)) #False print(np.allclose(r2, r1, equal_nan=True)) #False
3.2 圖片增強
對圖片進行旋轉,顏色轉換,投影變換等來增強圖片的多樣性,減少模型訓練過擬合,使模型更加魯棒,簡單代碼如下:

#!/usr/bin/env python #coding:utf-8 import cv2 as cv import numpy as np import random import argparse import os def crop(img,roi): """ img: 圖像矩陣 roi: 待截取的區域,格式為:(top,bottom,left,right) """ top,bottom,left,right=roi bottom = img.shape[0] if bottom>img.shape[0] else bottom right = img.shape[1] if right>img.shape[1] else right return img[top:bottom,left:right] def color_shift(img,shift=50): """ img: 圖像矩陣 shift: 像素值偏移值 """ r_offset = random.randint(-abs(shift),abs(shift)) g_offset = random.randint(-abs(shift),abs(shift)) b_offset = random.randint(-abs(shift),abs(shift)) img = img+np.array([b_offset,g_offset,r_offset]) img[img<0]=0 img[img>255]=255 return img def rotate(img,angle=30,scale=1): """ img: 圖像矩陣 angle: 選轉角度 scale:縮放因子 """ rows,cols = img.shape[:2] M = cv.getRotationMatrix2D((int(cols/2),int(rows/2)),angle,scale) img_rotate = cv.warpAffine(img,M,(cols,rows)) return img_rotate def perspective(img,random_margin=60): """ img: 圖像矩陣 random_margin: 隨機值,用來生成坐標 """ height,width = img.shape[:2] x1 = random.randint(-random_margin, random_margin) y1 = random.randint(-random_margin, random_margin) x2 = random.randint(width - random_margin - 1, width - 1) y2 = random.randint(-random_margin, random_margin) x3 = random.randint(width - random_margin - 1, width - 1) y3 = random.randint(height - random_margin - 1, height - 1) x4 = random.randint(-random_margin, random_margin) y4 = random.randint(height - random_margin - 1, height - 1) dx1 = random.randint(-random_margin, random_margin) dy1 = random.randint(-random_margin, random_margin) dx2 = random.randint(width - random_margin - 1, width - 1) dy2 = random.randint(-random_margin, random_margin) dx3 = random.randint(width - random_margin - 1, width - 1) dy3 = random.randint(height - random_margin - 1, height - 1) dx4 = random.randint(-random_margin, random_margin) dy4 = random.randint(height - random_margin - 1, height - 1) pts1 = np.float32([[x1, y1], [x2, y2], [x3, y3], [x4, y4]]) pts2 = np.float32([[dx1, dy1], [dx2, dy2], [dx3, dy3], [dx4, dy4]]) M = cv.getPerspectiveTransform(pts1,pts2) img_pers = cv.warpPerspective(img,M,(width,height)) return img_pers # def perspective(img,pos1,pos2): # """ # img: 圖像矩陣 # pos1: 原圖像中四組坐標值, 格式為((x1,y1),(x2,y2),(x3,y3),(x4,y4)) # pos2 投影變換后圖像中四組坐標值,格式同from # """ # pos1 = np.float32(pos1) # pos2 = np.float32(pos2) # M = cv.getPerspectiveTransform(pos1,pos2) # img_pers = cv.warpPerspective(img,M,(img.shape[1],img.shape[0])) # return img_pers def argparser(): parser = argparse.ArgumentParser() parser.add_argument("--img_input", type=str,help="輸入圖片路徑") parser.add_argument("--img_output", type=str,help="輸出圖片路徑") parser.add_argument("--roi", nargs='*',type=int,help="待截取的區域,格式為:(top,bottom,left,right)") parser.add_argument("--shift",type=int,help="像素值偏移值") parser.add_argument("--angle",type=float,help="旋轉角度") parser.add_argument("--scale",type=float,help="縮放因子") parser.add_argument("--margin",type=int,help="隨機值,用來生成坐標") return parser.parse_args() if __name__=="__main__": parse = argparser() img_dir = parse.img_input out_dir = parse.img_output if not os.path.exists(out_dir): os.makedirs(out_dir) if os.path.exists(img_dir): for file in os.listdir(img_dir): if file.endswith(("jpg","png")): img = cv.imread(os.path.join(img_dir,file)) if parse.roi and len(parse.roi)==4: img = crop(img,parse.roi) if parse.shift: img = color_shift(img,parse.shift) if parse.angle and parse.scale: img = rotate(img,parse.angle,parse.scale) if parse.margin: img = perspective(img,parse.margin) cv.imwrite(os.path.join(out_dir,file),img)
gluoncv的SSD算法中對圖片的增強處理,包括brightness,contrast,hue,saturation的處理,源碼如下:

"""Experimental image transformations.""" from __future__ import division import random import numpy as np import mxnet as mx from mxnet import nd def random_color_distort(src, brightness_delta=32, contrast_low=0.5, contrast_high=1.5, saturation_low=0.5, saturation_high=1.5, hue_delta=18): """Randomly distort image color space. Note that input image should in original range [0, 255]. Parameters ---------- src : mxnet.nd.NDArray Input image as HWC format. brightness_delta : int Maximum brightness delta. Defaults to 32. contrast_low : float Lowest contrast. Defaults to 0.5. contrast_high : float Highest contrast. Defaults to 1.5. saturation_low : float Lowest saturation. Defaults to 0.5. saturation_high : float Highest saturation. Defaults to 1.5. hue_delta : int Maximum hue delta. Defaults to 18. Returns ------- mxnet.nd.NDArray Distorted image in HWC format. """ def brightness(src, delta, p=0.5): """Brightness distortion.""" if np.random.uniform(0, 1) > p: delta = np.random.uniform(-delta, delta) src += delta return src return src def contrast(src, low, high, p=0.5): """Contrast distortion""" if np.random.uniform(0, 1) > p: alpha = np.random.uniform(low, high) src *= alpha return src return src def saturation(src, low, high, p=0.5): """Saturation distortion.""" if np.random.uniform(0, 1) > p: alpha = np.random.uniform(low, high) gray = src * nd.array([[[0.299, 0.587, 0.114]]], ctx=src.context) gray = mx.nd.sum(gray, axis=2, keepdims=True) gray *= (1.0 - alpha) src *= alpha src += gray return src return src def hue(src, delta, p=0.5): """Hue distortion""" if np.random.uniform(0, 1) > p: alpha = random.uniform(-delta, delta) u = np.cos(alpha * np.pi) w = np.sin(alpha * np.pi) bt = np.array([[1.0, 0.0, 0.0], [0.0, u, -w], [0.0, w, u]]) tyiq = np.array([[0.299, 0.587, 0.114], [0.596, -0.274, -0.321], [0.211, -0.523, 0.311]]) ityiq = np.array([[1.0, 0.956, 0.621], [1.0, -0.272, -0.647], [1.0, -1.107, 1.705]]) t = np.dot(np.dot(ityiq, bt), tyiq).T src = nd.dot(src, nd.array(t, ctx=src.context)) return src return src src = src.astype('float32') # brightness src = brightness(src, brightness_delta) # color jitter if np.random.randint(0, 2): src = contrast(src, contrast_low, contrast_high) src = saturation(src, saturation_low, saturation_high) src = hue(src, hue_delta) else: src = saturation(src, saturation_low, saturation_high) src = hue(src, hue_delta) src = contrast(src, contrast_low, contrast_high) return src
參考:https://www.jianshu.com/p/03009cfdf733
https://zhuanlan.zhihu.com/p/39076763
https://zhuanlan.zhihu.com/p/79981927
https://segmentfault.com/a/1190000012668819?utm_source=tag-newest