Face Recognition Loss on Mnist with Pytorch
寫在前面
這篇文章的重點不在於講解FR的各種Loss,因為知乎上已經有很多,搜一下就好,本文主要提供了各種Loss的Pytorch實現以及Mnist的可視化實驗,一方面讓大家藉助代碼更深刻地理解Loss的設計,另一方面直觀的比較各種Loss的有效性,是否漲點並不是我關注的重點,因為這些Loss的設計理念之一就是增大收斂難度,所以在Mnist這樣的簡單任務上訓練同樣的epoch,先進的Loss並不一定能帶來點數的提升,但從視覺效果可以明顯的看出特徵的分離程度,而且從另一方面來說,分類正確不代表一定能能在用歐式/餘弦距離做1:1驗證的時候也正確...
本文主要仿照CenterLoss文中的實驗結構,使用了一個相對複雜一些的LeNet升級版網路,把輸入圖片Embedding成2維特徵向量以便於可視化。
對了,代碼里用到了TensorBoardX來可視化,當然如果你沒裝,可以注釋掉相關代碼,我也寫了本地保存圖片,雖然很不喜歡TensorFlow,但TensorBoard還是真香,比Visdom強太多了...
早就想寫這篇文章了,趁著五一假期終於...
具體代碼在Github:https://github.com/MccreeZhao/FR-Loss-on-Mnist/blob/master/README.md 有興趣的話點個Star呀~雖然剛起步還沒什麼東西
文章里只展示loss寫法
Softmax
公式推導
Pytorch代碼實現
class Linear(nn.Module):
def __init__(self):
super(Linear, self).__init__()
self.weight = nn.Parameter(torch.Tensor(2,10))#(input,output)
self.bias = nn.Parameter(torch.Tensor(1,10))
nn.init.xavier_uniform_(self.weight)
def forward(self, x):
out = x.mm(self.weight)+self.bias
return out
criterion = nn.CrossEntropyLoss()
loss = criterion(out,label)
#CrossEntropyLoss等同於nn.LogSoftmax()+nn.NLLLoss()
emmm...現實生活中根本沒人會這麼寫好吧!明明就有現成的Linear層啊喂!
寫成這樣只是為了方便統一框架...
可視化

這一張圖是二維化的特徵,注意觀察不同兩類任意點之間的餘弦距離和歐氏距離

這張圖是將特徵歸一化的結果,能更好的反映餘弦距離,豎線是該類在最後一個FC層的權重,等同於類別中心(這一點對於理解loss的發展還是挺關鍵的)
後面的圖片也都是這種形式,大家可以比較著來看
Modified Softmax
公式推導
去除了權重的模長和偏置對loss的影響,將特徵映射到了超球面,同時避免了樣本量差異帶來的預測傾向性(樣本量大可能導致權重模長偏大)
Pytorch代碼實現
class Modified(nn.Module):
def __init__(self):
super(Modified, self).__init__()
self.weight = nn.Parameter(torch.Tensor(2,10))#(input,output)
nn.init.xavier_uniform_(self.weight)
self.weight.data.uniform_(-1,1).renorm_(2,1,1e-5).mul_(1e5)
#因為renorm採用的是maxnorm,所以先縮小再放大以防止norm結果小於1
def forward(self, x):
w=self.weight
ww=w.renorm(2,1,1e-5).mul(1e5)
out = x.mm(ww)
return out
可視化

這裡要提一句,如果大家留心的話可以發現,雖然modified loss並沒有太好的聚攏效果,但確讓類別中心準確地落在了feature的中心,這對於網路的性能是有很大好處的,但是具體原因我沒想出來...希望能有大佬在評論區給解釋一下...
NormFace
既然權重的模長有影響,Feature的模長必然也有影響,具體還是看文章,另外,質量差的圖片feature模長往往較短,做normalize之後消除了這個影響,有利有弊,還沒有達成一致觀點,目前主流的Loss還是包括feature normalize的
公式推導
可視化
就是一個字:猛! 感覺有了NormFace,後面的花式Loss都體現不出來效果了...
Pytorch代碼實現
class NormFace(nn.Module):
def __init__(self):
super(NormFace, self).__init__()
self.weight = nn.Parameter(torch.Tensor(2,10))#(input,output)
nn.init.xavier_uniform_(self.weight)
self.weight.data.uniform_(-1,1).renorm_(2,1,1e-5).mul_(1e5)
#因為renorm採用的是maxnorm,所以先縮小再放大以防止norm結果小於1
def forward(self, x, epoch):
x=x.renorm(2,0,1e-5).mul(1e5)
w=self.weight
ww=w.renorm(2,1,1e-5).mul(1e5)
out = x.mm(ww)#*64
return out
SphereFace:A-softmax
為了進一步約束特徵向量之間的餘弦距離,我們人為地增加收斂難度,給兩個向量之間的夾角乘上一個因子:m
公式推導
Pytorch代碼實現
class AngleLinear(nn.Module):
def __init__(self, m=4):
super(AngleLinear, self).__init__()
self.weight = nn.Parameter(torch.Tensor(2, 10)) # (input,output)
nn.init.xavier_uniform_(self.weight)
self.weight.data.renorm_(2, 1, 1e-5).mul_(1e5)
# 因為renorm採用的是maxnorm,所以先縮小再放大以防止norm結果小於1
self.m = m
self.mlambda = [ # 求cos(mx)的公式
lambda x: x ** 0,
lambda x: x ** 1,
lambda x: 2 * x ** 2 - 1,
lambda x: 4 * x ** 3 - 3 * x,
lambda x: 8 * x ** 4 - 8 * x ** 2 + 1,
lambda x: 16 * x ** 5 - 20 * x ** 3 + 5 * x
]
def forward(self, input, epoch):
#注意,在原始的A-softmax中是不對x進行標準化的,
#標準化可以提升性能,也會增加收斂難度,A-softmax本來就很難收斂
#可能因為難收斂所以才沒有在人臉中使用,但是mnist是能夠收斂的
x = input#.renorm(2,0,1e-5).mul(1e5) # (Batch_size,F) F=len(feature),here is 2
w = self.weight # (F,Classnum)
ww = w.renorm(2, 1, 1e-5).mul(1e5)
x_norm = x.pow(2).sum(1).pow(0.5)
cos_theta = x.mm(ww) / x_norm.view(-1, 1)
cos_theta = cos_theta.clamp(-1, 1) # 防止出現異常
# 以上計算出了傳統意義上的cos_theta,但為了cos(m*theta)的單調遞減,需要使用phi_theta
cos_m_theta = self.mlambda[self.m](cos_theta)
# 計算theta,依據theta的區間把k的取值定下來
theta = cos_theta.data.acos()
k = (self.m * theta / 3.1415926).floor()
phi_theta = ((-1) ** k) * cos_m_theta - 2 * k
x_cos_theta = cos_theta * x_norm.view(-1, 1)
x_phi_theta = phi_theta * x_norm.view(-1, 1)
output = (x_cos_theta,x_phi_theta)
#注意,只有標籤對應項需要替換為cos(m*theta),所以兩個值都需要傳遞給損失函數
return output
class AngleLoss(nn.Module):
def __init__(self,gamma = 0):
super(AngleLoss,self).__init__()
self.gamma = gamma
self.it = 0
self.LambdaMin = 3
self.LambdaMax = 30000.0
self.lamb = 30000.0#lambda和正則化表達式的關鍵字重名了
def forward(self, input,target):
self.it += 1#用來調整lambda
x_cos_theta,x_phi_theta = input
target = target.view(-1,1) #(B,1)
onehot = torch.zeros(target.shape[0], 10).cuda().scatter_(1, target, 1)
self.lamb = max(self.LambdaMin,self.LambdaMax/(1+0.2*self.it))
output = x_cos_theta*1.0#如果不乘可能會有數值錯誤?
output[onehot.byte()] -= x_cos_theta[onehot.byte()]*(1.0+0)/(1+self.lamb)
#不能直接置零,因為是按比例分配cos和phi的
output[onehot.byte()] += x_phi_theta[onehot.byte()]*(1.0+0)/(1+self.lamb)
#到這一步可以等同於原來的Wx+b=y的輸出了,
#到這裡使用了Focal Loss,如果直接使用cross_Entropy的話似乎效果會減弱許多
log = F.log_softmax(output)
log = log.gather(1,target)
log = log.view(-1)
pt = log.data.exp()
loss = -1*(1-pt)**self.gamma*log
loss = loss.mean()
#loss = F.cross_entropy(x_cos_theta,target.view(-1))
return loss,output
可視化
InsightFace(ArcSoftmax)
公式推導
Pytorch代碼實現
class ArcMarginProduct(nn.Module):
def __init__(self,s=256, m=0.01):
super(ArcMarginProduct, self).__init__()
self.in_feature = 2
self.out_feature = 10
self.s = s
self.m = m
self.weight = nn.Parameter(torch.Tensor(2, 10)) # (input,output)
nn.init.xavier_uniform_(self.weight)
self.weight.data.renorm_(2, 1, 1e-5).mul_(1e5)
self.cos_m = math.cos(m)
self.sin_m = math.sin(m)
# 為了保證cos(theta+m)在0-pi單調遞減:
self.th = math.cos(3.1415926 - m)
self.mm = math.sin(3.1415926 - m) * m
def forward(self, x, label):
cosine = F.normalize(x).mm(F.normalize(self.weight, dim=0))
sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
phi = cosine * self.cos_m - sine * self.sin_m # 兩角和公式
# 為了保證cos(theta+m)在0-pi單調遞減:
phi = torch.where((cosine - self.th) > 0, phi, cosine - self.mm)
one_hot = torch.zeros_like(cosine)
one_hot.scatter_(1, label.view(-1, 1), 1)
output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
output = output * self.s
#這裡這個s比較關鍵,如果設置的不夠大會導致收斂困難,
# 比較容易出現兩個類別的Feature重疊的現象,
# 這也是加性Margin相對於乘性Margin的一個比較弱勢的地方,
# 對於特徵向量的收縮要求足夠,但是對兩類特徵向量之間的距離約束不夠
loss = F.cross_entropy(cosine, label)
return loss, output
可視化

ArcSoftmax需要更久的訓練,這個收斂還不夠充分...顏值堪憂,另外ArcSoftmax經常出現類別在特徵空間分布不均勻的情況,這個也有點費解,難道在訓FR模型的時候先用softmax然後慢慢加margin有奇效?SphereFace那種退火的訓練方式效果好會不會和這個有關呢...
Center Loss
亂入一個歐式距離的細作
公式推導
其中 是每個類別對應的一個中心,在這裡就是一個二維坐標啦
Pytorch代碼實現
class centerloss(nn.Module):
def __init__(self,num_classes=10,feat_dim=2):
super(centerloss,self).__init__()
self.center = nn.Parameter(10*torch.randn(10,2).cuda())
self.num_classes = num_classes
self.feat_dim = feat_dim
def forward(self,feature,label):
batch_size = label.size()[0]
nCenter = self.center.index_select(dim=0,index=label)
distance = feature.dist(nCenter)
loss = (1/2.0/batch_size)*distance
return loss
這裡實現的是center的部分,還要跟原始的CEloss相加的,具體看github吧
可視化

會不會配合weight norm效果更佳呢?以後再說吧...
總結
先寫到這裡,如果大家有興趣可以去github點個star之類的...作為一個研一快結束的弱雞剛剛學會使用github...也是沒誰了...
參考文獻:
Wang M, Deng W. Deep face recognition: A survey[J]. arXiv preprint arXiv:1804.06655, 2018.
覺得自己真是個小機靈鬼...(逃)
推薦閱讀:
TAG:深度學習(DeepLearning) | 人臉識別 | PyTorch |
