聪明的人脸识别3——Pytorch 搭建自己的Facenet人脸识别平台
聰明的人臉識別3——Pytorch 搭建自己的Facenet人臉識別平臺
- 學(xué)習(xí)前言
- 什么是Facenet
- 源碼下載
- Facenet的實(shí)現(xiàn)思路
- 一、預(yù)測部分
- 1、主干網(wǎng)絡(luò)介紹
- 2、根據(jù)初步特征獲得長度為128的特征向量
- 3、l2標(biāo)準(zhǔn)化
- 4、構(gòu)建分類器(用于輔助Triplet Loss的收斂)
- 二、訓(xùn)練部分
- 1、數(shù)據(jù)集介紹
- 2、LOSS組成
- 訓(xùn)練自己的Facenet人臉識別算法
學(xué)習(xí)前言
好的還有Pytorch版。
什么是Facenet
谷歌人臉識別算法,發(fā)表于 CVPR 2015,利用相同人臉在不同角度等姿態(tài)的照片下有高內(nèi)聚性,不同人臉有低耦合性,提出使用 cnn + triplet mining 方法,在 LFW 數(shù)據(jù)集上準(zhǔn)確度達(dá)到 99.63%。
通過 CNN 將人臉映射到歐式空間的特征向量上,實(shí)質(zhì)上:不同圖片人臉特征的距離較大;通過相同個體的人臉的距離,總是小于不同個體的人臉這一先驗(yàn)知識訓(xùn)練網(wǎng)絡(luò)。
測試時只需要計(jì)算人臉特征EMBEDDING,然后計(jì)算距離使用閾值即可判定兩張人臉照片是否屬于相同的個體。
簡單來講,在使用階段,facenet即是:
1、輸入一張人臉圖片
2、通過深度卷積網(wǎng)絡(luò)提取特征
3、L2標(biāo)準(zhǔn)化
4、得到一個長度為128特征向量。
源碼下載
https://github.com/bubbliiiing/facenet-pytorch
Facenet的實(shí)現(xiàn)思路
一、預(yù)測部分
1、主干網(wǎng)絡(luò)介紹
facenet的主干網(wǎng)絡(luò)起到提取特征的作用,原版的facenet以Inception-ResNetV1為主干特征提取網(wǎng)絡(luò)。
本文一共提供了兩個網(wǎng)絡(luò)作為主干特征提取網(wǎng)絡(luò),分別是mobilenetv1和Inception-ResNetV1,二者都起到特征提取的作用,為了方便理解,本博文中會使用mobilenetv1作為主干特征提取網(wǎng)絡(luò)。
MobilenetV1模型是Google針對手機(jī)等嵌入式設(shè)備提出的一種輕量級的深層神經(jīng)網(wǎng)絡(luò),其使用的核心思想便是depthwise separable convolution(深度可分離卷積塊)。
深度可分離卷積塊由兩個部分組成,分別是深度可分離卷積和1x1普通卷積,深度可分離卷積的卷積核大小一般是3x3的,便于理解的話我們可以把它當(dāng)作是特征提取,1x1的普通卷積可以完成通道數(shù)的調(diào)整。
下圖為深度可分離卷積塊的結(jié)構(gòu)示意圖:
深度可分離卷積塊的目的是使用更少的參數(shù)來代替普通的3x3卷積。
我們可以進(jìn)行一下普通卷積和深度可分離卷積塊的對比:
對于普通卷積而言,假設(shè)有一個3×3大小的卷積層,其輸入通道為16、輸出通道為32。具體為,32個3×3大小的卷積核會遍歷16個通道中的每個數(shù)據(jù),最后可得到所需的32個輸出通道,所需參數(shù)為16×32×3×3=4608個。
對于深度可分離卷積結(jié)構(gòu)塊而言,假設(shè)有一個深度可分離卷積結(jié)構(gòu)塊,其輸入通道為16、輸出通道為32,其會用16個3×3大小的卷積核分別遍歷16通道的數(shù)據(jù),得到了16個特征圖譜。在融合操作之前,接著用32個1×1大小的卷積核遍歷這16個特征圖譜,所需參數(shù)為16×3×3+16×32×1×1=656個。
可以看出來深度可分離卷積結(jié)構(gòu)塊可以減少模型的參數(shù)。
如下就是MobileNet的結(jié)構(gòu),其中Conv dw就是分層卷積,在其之后都會接一個1x1的卷積進(jìn)行通道處理,
2、根據(jù)初步特征獲得長度為128的特征向量
利用主干特征提取網(wǎng)絡(luò)我們可以獲得一個特征層,它的shape為(batch_size, h, w, channels),我們可以將其取全局平均池化,方便后續(xù)的處理(batch_size, channels)。
我們可以將平鋪后的特征層進(jìn)行一個神經(jīng)元個數(shù)為128的全連接。此時我們相當(dāng)于利用了一個長度為128的特征向量代替輸入進(jìn)來的圖片。這個長度為128的特征向量就是輸入圖片的特征濃縮。
class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return x3、l2標(biāo)準(zhǔn)化
在獲得一個長度為128的特征向量后,我們還需要進(jìn)行l(wèi)2標(biāo)準(zhǔn)化的處理。
這個L2標(biāo)準(zhǔn)化是為了使得不同人臉的特征向量可以屬于同一數(shù)量級,方便比較。
在進(jìn)行l(wèi)2標(biāo)準(zhǔn)化前需要首先計(jì)算2-范數(shù):
∣∣x∣∣2=∑i=1Nxi2||\textbf{x}||_2 =\sqrt{\sum_{i=1}^Nx_i^2}∣∣x∣∣2?=∑i=1N?xi2??,也就是歐幾里得范數(shù),即向量元素絕對值的平方和再開方。
L2標(biāo)準(zhǔn)化就是每個元素/L2范數(shù);
在pytorch代碼中,只需要一行就可以實(shí)現(xiàn)l2標(biāo)準(zhǔn)化的層。
class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return x到這里,我們輸入進(jìn)來的圖片,已經(jīng)變成了一個經(jīng)過l2標(biāo)準(zhǔn)化的長度為128的特征向量了!
4、構(gòu)建分類器(用于輔助Triplet Loss的收斂)
當(dāng)我們完成第三步后,我們已經(jīng)可以利用這個預(yù)測結(jié)果進(jìn)行訓(xùn)練和預(yù)測了。
但是由于僅僅只是用Triplet Loss會使得整個網(wǎng)絡(luò)難以收斂,本文結(jié)合Cross-Entropy Loss和Triplet Loss作為總體loss。
Triplet Loss用于進(jìn)行不同人的人臉特征向量歐幾里得距離的擴(kuò)張,同一個人的不同狀態(tài)的人臉特征向量歐幾里得距離的縮小。
Cross-Entropy Loss用于人臉分類,具體作用是輔助Triplet Loss收斂。
想要利用Cross-Entropy Loss進(jìn)行訓(xùn)練需要構(gòu)建分類器,因此對第三步獲得的結(jié)果再次進(jìn)行一個全連接用于分類。
構(gòu)建代碼如下,當(dāng)我們在進(jìn)行網(wǎng)絡(luò)的訓(xùn)練的時候,可使用分類器輔助訓(xùn)練,在預(yù)測的時候,分類器是不需要的:
class Facenet(nn.Module):def __init__(self, backbone="mobilenet", dropout_keep_prob=0.5, embedding_size=128, num_classes=None, mode="train"):super(Facenet, self).__init__()if backbone == "mobilenet":self.backbone = mobilenet()flat_shape = 1024elif backbone == "inception_resnetv1":self.backbone = inception_resnet()flat_shape = 1792else:raise ValueError('Unsupported backbone - `{}`, Use mobilenet, inception_resnetv1.'.format(backbone))self.avg = nn.AdaptiveAvgPool2d((1,1))self.Dropout = nn.Dropout(1 - dropout_keep_prob)self.Bottleneck = nn.Linear(flat_shape, embedding_size,bias=False)self.last_bn = nn.BatchNorm1d(embedding_size, eps=0.001, momentum=0.1, affine=True)if mode == "train":self.classifier = nn.Linear(embedding_size, num_classes)def forward(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)x = self.last_bn(x)x = F.normalize(x, p=2, dim=1)return xdef forward_feature(self, x):x = self.backbone(x)x = self.avg(x)x = x.view(x.size(0), -1)x = self.Dropout(x)x = self.Bottleneck(x)before_normalize = self.last_bn(x)x = F.normalize(before_normalize, p=2, dim=1)return before_normalize, xdef forward_classifier(self, x):x = self.classifier(x)return x二、訓(xùn)練部分
1、數(shù)據(jù)集介紹
我們使用的數(shù)據(jù)集是CASIA-WebFace數(shù)據(jù)集,我已經(jīng)對其進(jìn)行了預(yù)處理,將其屬于同一個人的圖片放到同一個文件夾里面,并且進(jìn)行了人臉的提取和人臉的矯正。
數(shù)據(jù)集里面有很多的文件夾,每一個文件夾里面存放同一個人的不同情況下的人臉。不同文件夾存放不同人的臉。
這是\0000045文件夾里面的人臉,屬于同一個人。
這是\0000099文件夾里面的人臉,屬于同一個人。
2、LOSS組成
facenet使用Triplet Loss作為loss。
Triplet Loss的輸入是一個三元組
- a:anchor,基準(zhǔn)圖片獲得的128維人臉特征向量
- p:positive,與基準(zhǔn)圖片屬于同一張人臉的圖片獲得的128維人臉特征向量
- n:negative,與基準(zhǔn)圖片不屬于同一張人臉的圖片獲得的128維人臉特征向量
我們可以將anchor和positive求歐幾里得距離,并使其盡量小。
我們可以將anchor和negative求歐幾里得距離,并使其盡量大。
我們所使用的公式為。
L=max(d(a,p)?d(a,n)+margin,0)L=max(d(a,p)?d(a,n)+margin,0) L=max(d(a,p)?d(a,n)+margin,0)
d(a,p)就是anchor和positive的歐幾里得距離。
d(a,n)就是negative和positive的歐幾里得距離。
margin是一個常數(shù)。
d(a,p)前面為正符號,所以我們期望其越來越小。
d(a,n)前面為負(fù)符號,所以我們期望其越來越大。
即我們希望,同一個人的不同狀態(tài)的人臉特征向量歐幾里得距離小。
不同人的人臉特征向量歐幾里得距離大。
但是由于僅僅只是用Triplet Loss會使得整個網(wǎng)絡(luò)難以收斂,本文結(jié)合Cross-Entropy Loss和Triplet Loss作為總體loss。
Triplet Loss用于進(jìn)行不同人的人臉特征向量歐幾里得距離的擴(kuò)張,同一個人的不同狀態(tài)的人臉特征向量歐幾里得距離的縮小。
Cross-Entropy Loss用于人臉分類,具體作用是輔助Triplet Loss收斂。
訓(xùn)練自己的Facenet人臉識別算法
博客中所使用的例子為CASIA-WebFace數(shù)據(jù)集。
下載數(shù)據(jù)集,放在根目錄下的dataset文件夾下。
運(yùn)行根目錄下的txt_annotation.py,生成訓(xùn)練所需的cls_train.txt。
cls_train.txt中每一行都存放了一張圖片和它對應(yīng)的類別(需要類別是因?yàn)橛?xùn)練時會用交叉熵?fù)p失輔助收斂。)
下載facenet_inception_resnetv1.pth或者facenet_mobilenet.pth放在model_data文件夾內(nèi)。
在train.py中指定合適的模型預(yù)訓(xùn)練權(quán)重路徑。facenet_inception_resnetv1.pth是我已經(jīng)訓(xùn)練過的基于inception_resnetv1的facenet網(wǎng)絡(luò);
facenet_mobilenet.pth是我已經(jīng)訓(xùn)練過的基于mobilenet的facenet網(wǎng)絡(luò)。
運(yùn)行train.py開始訓(xùn)練。
總結(jié)
以上是生活随笔為你收集整理的聪明的人脸识别3——Pytorch 搭建自己的Facenet人脸识别平台的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【精品盘点】2020年最受欢迎的6个知识
- 下一篇: 深度学习人脸检测与人脸识别