Facial recognition 人臉辨識

人臉辨識這個名詞,自從iPhone推出了人臉解鎖,以及Amazon Go的無人商店,相關的門禁、安控應用已經有如雨後春筍般快速地融入我們的生活中。再拉回台灣,國家門面的自動通關服務,也從最早的指紋辨識來到現在人臉辨識。隨著科技進步,人工智慧不斷地被應用在更多的場景,協助人們改善生活品質,進而豐富人生。本文將會以深入淺出的方式,帶著大家來探討人臉辨別的發展與挑戰…

一、前言:
本文將會先介紹什麼是人臉辨識?它的應用場景為何?在發展的過程中,它面臨了哪些挑戰?以及科學家是怎麼克服這些困難的,最後以個人的心得做為結論。
另外,我特別要感謝活耀在兩岸的AI大師尹相志老師,他除了很願意分享他的精闢見解與世界上 AI最新的突破,協助台灣的碼農在 AI的領域能夠更上一層樓。也非常地大愛為台灣做了許多貢獻,例如在許多公開場合分享 AI的技術觀念,也在實務上分享了他的心得。例如優化了警察在OCR的作業,能有效地提升犯罪/車禍現場的鑑識作業效率。優化了台灣廟宇記錄片黑白轉彩色的作業,讓台灣在地化的文化薪火相傳得以延續。

二、何謂人臉辨識?它是生物辨識的一環,與指紋、聲紋、虹膜…在歸類在相同的範疇中。它的運作如同停車場的車牌辨識,當你經過出入口時,攝影機鏡頭捕抓到你,然後進行比對,判斷出你要付多少錢?同理,人臉辨識在門禁管理的應用就是經過逐像素的特徵值處理,然後與事先登錄的人臉進行特徵值的比對,判斷出你是誰。
如果要對應到機器視覺 Computer vision的些個基礎任務,Classification分類、Object detection目標檢測、Segmentation語意分割、Posture動作檢測,人臉辨識會界於前面二個。要先找到圖像中的人臉,接著再辨識是否為資料庫中的 account,所以在運作時你會看見 Bounding box邊界框(一張臉一個框)。
下圖是 Azure Face API 的相關的應用,細節可以參考微軟官網上對臉部辨識的說明,例如人臉辨識 Face verification: 查看兩張臉屬於同一個人的可能性,並獲得信賴分數

人臉偵測 Face detection:偵測一或多張人臉,以及姿勢、臉部遮蓋物以及人臉分部位置等細節,包括影像中每一張臉的 27 個特徵點 landmark (待會再細部介紹人臉特徵提取,這邊先簡要的了解,透過微軟的API,它可以選擇性地傳回不同種類之臉部相關資料的動作)、年齡、情緒、性別、髮色、頭部姿勢、眼鏡、戴口罩、笑…等屬性。細節可以參考官網的說明

三、人臉辨識的常見應用情境:
Azure Face API 可以處理36*36~4096*4096的影像輸入,提供了以下ABC三個主要功能,在不同場景的應用。至於各行各業究竟有什麼熱門的應用,我也幫大家整理在下面:
       A. Face identification是一種 one to many 的身份辨識,在Azure的 Group(上限為一百萬群,可分為 person / large person group)中可以註冊多個 Person(上限為一萬人),每個用戶可以註冊248張人臉照片。
       B. Face verify是一種 one to one 的身份辨識,可以做 Face ID的比對
       C.Find similar faces,呼叫 matchFace 可以回傳非本人,較相似的人臉。經過我的測試,並不能拿來做親緣辨識(取代滴血認親),它的應用我還在研究中。
       D. (非Azure Face API)Watch list,是一種 many to many 的清單監看。例如待會會提到的通輯犯監看

  • 健康控管:疫情期間,大家最關心的體溫檢測,就可以透過 Face detection 找到人臉並標示溫度度量值在方框上,當體溫超過37℃就發警報。有了AI 就不用管制人流,快速地通過,有效改善大排長龍等候體溫量測的問題。
  • 身分驗證:透過事先的建檔,來驗證攝影機當前的人員是否為資料庫中的成員?又可以分成 identification與verification的呼叫,例如應用在零售業的門市 VIP辨識、手機解鎖、本人/親緣識別、下面的門禁管理…等
  • 門禁管理:企業員工的進出管理,甚至是出缺勤的管理(取代打卡機或識別證)、機場的出入境、住家的智慧門鎖、醫院診所的智慧藥櫃、非接觸性的出入(醫院要儘量減少傳染的機會)、活體識別也是近期較熱門的延伸應用
  • 安防監控:通輯犯識別(大陸用天網系統在演唱會現場抓到跨省的嫌疑犯)、不明人士尾隨員工進入、未經授權員工進入管制區域、非人類篩選(如果是貓/狗/老鼠…進入管制區域不會觸發告警)…等
  • 影像處理:視訊開會、公關、廣告…等場合的隱私權保護,可處理主角以外的人臉糢糊化
  • 智慧零售:分析客戶來到門市的人數、時間分佈、性別、是否為VIP、行走動線(可做為產品擺放的規劃依據)
  • 休閒娛樂:遊戲、虛擬實境、模仿秀、廣告、影片分析,甚至是元宇宙

四、人臉辨識的關鍵發展

未提供說明。
  1. 方向梯度直方圖 HOG (Histogram of oriented gradients):自從法國研究員在2005年把這個1986年的特徵描述( Feature description)的演算法發揚光大後,因為只需要灰階的 data input少量的資料與計算量,相機的人臉自動對焦、監控攝影機的人類與物體辨別…等物件偵測的應用,變得開始普及。另外,這種邊緣辨識應用的先趨與生活化,也間接促使 Intel 將二值化這類影像功能開源成現在的Open CV2,可以算是 AI 發展的重要里程碑之一。
  2. 臉部特徵向量 Eigen Face:將人臉去計算共變異數矩陣(或稱為協方差矩陣)來提取特徵值與特徵向量,將訓練集中多張人臉,透過 PCA(Principal component analysis)主成份分析(演算法)留下值得保存的重要特徵向量,可以有效地降低儲存空間的需求。比對時,要先減去均值向量,如果每一個特徵向量的維數與原始圖像的一致,就可以判斷為同一個圖像。
  3. 臉部取景:包含了全身、半身、臉部特寫,甚至是90度、45度…等不同角度的側臉,都會影響辨識的結果。以上述所說的早期技術,取景的 Pattern必需要一致,所幸目前的人臉對齊或是校正技術,已經能夠大大的改善這個問題。但是值得注意的是,臉部對齊時也會同時抵消 Rotational 翻轉類型的數據增強 Data augmentation,360度的旋轉只剩下垂直與水平翻轉的特定角度是有效的。
  4. 由分類任務到度量任務:以DeepID V1為例,在2014第一個版本時,較早期的作法是採用 ConvNet,並設計成分類問題,讓類神經的輸出等於人臉樣本總數。但是會遇到幾個問題,包括人臉增減之後,模型就要重新訓練;應用情境適侷限,這種設計只適合 one to one;到了2015 Google 引入了 Siamese neural network 這種權重共享的模型架構,並加入 Triplet loss 設計成度量任務,來推出 FaceNet。除了能迎合隨時都有新生兒出生的現實,也透過計算歐幾里德距離的方式,來擴大one to many 或是 many to many 的應用情境,好的結果應該是組間差距大,組內差距小。例如你可以將每個家人的照片都上傳5張照片到智慧門鎖中,然後它可以有效地區分家人與非家人,做好門禁的把關。VggFace是末代的 ImageNet 的冠軍,它採用了 SeNet + ResNet,在每個輪廻的 Convolution + Pooling 循環都要做 Squeeze and Excitation,降維到1/16,再升維回來做變數的篩選,再使用類似 attention 的機制,與上一個輪廻的權重做加總,有效地傳遞權重又能避免 gradient vanish or gradient explosion。
  5. 人臉特徵點 Facial landmark概念:在山川地貌中,只要有 Landmark 不管登山者來自東南西北,都能對目的地有所依據。同理,臉部的 68個特徵點,讓觀測者不管是由上下左右側著看都能藉此輕易地找到五官,而且還可以進一步進行人臉對齊。另外像是 Snapchat filter可以將人臉變成狗/豬/動物臉…也是基礎 Landmark,直接找到人的五官再將動物眼耳鼻舌的圖層或覆蓋或重疊。至於,特徵點的數量會基礎於運算力的需求、辨識率的高低、是否要做表情…等因素,所以除了常見的68個特徵點,還有5~10000其他的版本。例如5個特徵點(雙眼+鼻子+嘴巴),就常用於運算資源有限的邊緣裝置、多人的大合照;68個特徵點,是不包含額頭(81個特徵點)的人臉辨識最常用的選擇,熱門的開源函式庫就有 OpenCV、Dlib。例如我們可以在 Github 中找到許多效果不錯的 Dlib sample code。接著是著重臉部細節的美顏App 或是要做表情辨識的用途,就會需要136(68*2)個特徵點。走到極致就必需介紹商湯與曠視,大陸機器視覺的二大龍頭。從136更往上就跳到了無法靠人工標記,要靠模型與內插數學運算才能到達的1000個特徵點(商湯),舉個更極端的例子,像是為阿里巴較提供刷臉支付的曠視科技成名代表作Face++,讓是透過8000個特徵點(如今又來到了10000)來實現98%的人臉辨識正確率。下圖是Azure Face API 的27個 Face landmark。
  6. 人臉特徵點子任務:姑且不管表情辨識,機器視覺從根本來看,就是一個逐像素掃描的動作,我們該如何讓它做的更快更好的優化呢?就是能幫它找出重點的子任務,例如先透過二眼的外眼角、二眼的膧孔、13個特徵點的外輪廓、Bounding box 快速地找出人臉,來縮小運算範圍;透過人類好理解的廻歸去預測這68個特徵點的位置、或是熱力圖 Heap map去推估這68個特徵點的位置(前者會以二維的 68*2 輸出,人類好理解,但已經被拉平,喪失任一點去推估非相鄰點(較遠的點)的能力。而後者是以 68*N個像素*N個像素,雖然N*N已經超過人類可以理解的高維度,所以會再透過較低維度的高斯分佈模型來給定答案,再配合打橫沙漏型的 HourGlass模型,與 WingLoss 來訓練它,來實現更好的效果);這樣子比較任二張臉,都會比做完逐像素的全圖掃描來的有效率。
  7. 從DSP(Digital signal processing)到深度學習:相對於類比信號處理 DSP 的效果更加的穩定、準確、抗干擾、靈活,可惜,當時的技術還有許多的限制,例如不能處理非正面、回應速度慢…等。到了深度學習(細節可以參考之前的文章)之後,因為技術上的突破,更多的應用情境也讓商業化更為普及了。
  8. 模型架構的發展(如尹相志老師分享的上圖),從孿生神經網路 Siamese neural network,像是二個共享權重的連體嬰並使用 Triplet loss進行 Metric learning 度量學習的 Facenet,讓模型是學 Anchor 分別到 Negative & Positive 三角關係的夾角(在下圖中左邊模型是標準的Siamese NN,二邊會使用相同的模型並共享相同的權重,網路一的任務是學習本人(正樣本),網路二的任務是學習非本人(負樣本);右邊則是不分享權重的變型 Pseudo Siamese NN,視需求可以是CNN網路一搭配LSTM網路二。至於孿生網路在人臉辨識的機轉上,它是將二張人臉的 input,用相同的權重也就是五官的特徵向量基準,一起投射到新的空間,再用損失函數去比較二者在歐基里德的遠近距離)。又歷經了 SENet (2007年最後一屆的 ImageNet的冠軍模型) 像是 VGGFace2,再到近期使用 Large margin loss 的 ArcFace。在人臉的分類(像與不像)的激活函數,不再使用 Softmax一刀切但相交面仍有重覆的機會、SphereFace 以某個夾角區份但還是有相鄰的極小機會、ArcFace 以二個類似SVM 的直線,區分開並減少相交與誤判的機會。
     
  • 人臉辨識的限制:基礎於68點的特徵點與超球(geodesic correspondence)的概念,一個 ArcFace 模型,如果能取得不被部份遮蓋的全臉 input,大約可以在128維的條件下,辨識出包含雙胞胎在內的120億個人類個體,如果戴上口罩就會因為特徵值的降低,下降至4000個人類個體的規模(驗證了巧婦難為無米之炊的道理)。
A face diagram with all 27 landmarks labeled

五、人臉辨識的優化的技巧:為了達到九成以上的辨識率,以下我整理一些增加子任務或是模型設計的相關技巧。

  1. 物件偵測:在辨識誰是誰之前,需要先找到人臉!許多人應該覺得 Yolo是不二之選,但是此次的任務我是選擇SSD。這二種演算法都能夠獲取淺層特徵Box定位,深層特徵Class類別的結果。SSD是一秒數百幀,單向地一邊向下分解,一邊產生定位與類別的結果,篩選出最終的結果,適合類別單純或不精細,非大合照的任務。至於 Yolo V4,我之前有聽過作者中研院王博士與廖所長的演講,Yolo是一秒數幀,為了辨識的精準度,其資訊流會比較複雜,它一開始會向下,等到分解到最小,又會逐漸地向上採樣,透過數次的上上下下的特徵融合與權重加大,比較適合處理複雜的任務。
    ** 或許你會說王博士已經用C語言寫出了 Tiny Yolo V4,但相對地也有人用C語言把 SSD推至一秒1000 FPS(Frame per second)或是1MB模型Size的極致。
  2. 人臉校正 Face alignment 以及 Face morphing 人臉變形:前者是側臉轉正臉,後者是人臉對齊(Morphing 寫用在 Power Point 二張圖片,的過場特效上),都可以在非正臉的 input上面得到優化
  3. 全通道的數據增強 Augmentation:翻轉(垂直/水平 Flip/不同角度 Rotation)、裁減Crop、灰階 Gray scale、高斯噪音 Gaussian noise、高亮 Lighting、色彩處理(色票 Multiply、色相 Hue、飽和度 Saturation)、對比正規化 Contrast normalization、格狀遮罩 Grid Mask(下面有二張範例圖片,就是透過子任務來提高辨識的難度,讓模型有機會舉一反三)
  4. 針對通道的數據增強 Augmentation:灰階 Gray scale、高斯噪音 Gaussian noise、色票 Multiply、色相 Hue、飽和度 Saturation、對比正規化 Contrast normalization
  5. 正則化數據增強 Augmentation:隨機丟失 Dropout、針對通道隨機丟失 Dropout by channel、擦去 Erasing…
  6. 高階數據增強 Augmentation:這個類別是尹相志老師這類高手在使用的,例如:特徵點擾動(讓原來 N個特徵點稍微上下左右的偏移,來增加難度)、配飾轉換(透過戴眼鏡、帽子…這些飾品與配件來增難度)、妝容轉換(在美妝、美肌…之類盛行的環境下,即使你的數據集不是這種照片,也可以透過自帶美妝/美肌效果來提高難度。免得真的有一天,智慧門鎖的女主人,在畫完新娘妝之後,回家開不了門,就可尷尬了)、3D臉部生成(以2D來模擬3D建模後的特徵點,將能有效地模擬各種側臉的難度)
  7. 負樣本:又稱為 Hard exmaple mining 像藝人白雲與康康是很容區分出來的,但如果是藝人宋芸樺 vs. 夏于喬就很挑戰了。會需要像是 Hard minibatch 的進階手法去介入訓練時的採樣。
  8. 損失函失Loss function的設計:廣意的人臉辨識主要精神是,要做到組內差距小,組間差距大(同一個人的不同照片的特徵向量必須是縮限在極狹窄的扇形區域中,也就是固定角度但是允許有scale的差異,而不同人的特徵向量,則需要相隔在指定margin之外確保分類正確性)。狹意的人臉辨識可以透過廻歸法或是熱力圖法,著重在N個(例如68個)特徵點的對比,計算 Wing Loss是比較建議的作法
new_rfbmodel.summary()

priors nums:17640
0 parameters have set trainable
--------------------------------------------------------------------------------------------------------------------------------
                     Layer (type)                       Output Shape               Weight                          Bias      Param #   FLOPS #                  
==============================================================================
backbond1.0.conv  [Conv2d]                              [None, 16, 240, 320]       'weight', [16, 3, 3, 3]                   432  66,278,400  
backbond1.0.norm  [BatchNorm]                           [None, 16, 240, 320]       'weight', [16]                  'bias', [16]  32  4,761,600  
backbond1.0.activation  [Relu]                          [None, 16, 240, 320]                                                 0  0  
backbond1.0  [Conv2d_Block]                             [None, 16, 240, 320]                                                 0  0  
backbond1.1.0.conv  [DepthwiseConv2d]                   [None, 16, 240, 320]       'weight', [16, 1, 3, 3]                   144  22,041,600  
backbond1.1.0.norm  [BatchNorm]                         [None, 16, 240, 320]       'weight', [16]                  'bias', [16]  32  4,761,600  
backbond1.1.0.activation  [Relu]                        [None, 16, 240, 320]                                                 0  0  
backbond1.1.0  [DepthwiseConv2d_Block]                  [None, 16, 240, 320]                                                 0  0  
backbond1.1.1.conv  [Conv2d]                            [None, 32, 240, 320]       'weight', [32, 16, 1, 1]                  512  78,566,400  
backbond1.1.1.norm  [BatchNorm]                         [None, 32, 240, 320]       'weight', [32]                  'bias', [32]  64  9,676,800  
backbond1.1.1.activation  [Relu]                        [None, 32, 240, 320]                                                 0  0  
backbond1.1.1  [Conv2d_Block]                           [None, 32, 240, 320]                                                 0  0  
backbond1.2.0.conv  [DepthwiseConv2d]                   [None, 32, 120, 160]       'weight', [32, 1, 3, 3]                   288  11,040,000  
backbond1.2.0.norm  [BatchNorm]                         [None, 32, 120, 160]       'weight', [32]                  'bias', [32]  64  2,419,200  
backbond1.2.0.activation  [Relu]                        [None, 32, 120, 160]                                                 0  0  
backbond1.2.0  [DepthwiseConv2d_Block]                  [None, 32, 120, 160]                                                 0  0  
backbond1.2.1.conv  [Conv2d]                            [None, 32, 120, 160]       'weight', [32, 32, 1, 1]                  1,024  39,302,400  
backbond1.2.1.norm  [BatchNorm]                         [None, 32, 120, 160]       'weight', [32]                  'bias', [32]  64  2,419,200  
backbond1.2.1.activation  [Relu]                        [None, 32, 120, 160]                                                 0  0  
backbond1.2.1  [Conv2d_Block]                           [None, 32, 120, 160]                                                 0  0  
backbond1.3.0.conv  [DepthwiseConv2d]                   [None, 32, 120, 160]       'weight', [32, 1, 3, 3]                   288  11,040,000  
backbond1.3.0.norm  [BatchNorm]                         [None, 32, 120, 160]       'weight', [32]                  'bias', [32]  64  2,419,200  
backbond1.3.0.activation  [Relu]                        [None, 32, 120, 160]                                                 0  0  
backbond1.3.0  [DepthwiseConv2d_Block]                  [None, 32, 120, 160]                                                 0  0  
backbond1.3.1.conv  [Conv2d]                            [None, 32, 120, 160]       'weight', [32, 32, 1, 1]                  1,024  39,302,400  
backbond1.3.1.norm  [BatchNorm]                         [None, 32, 120, 160]       'weight', [32]                  'bias', [32]  64  2,419,200  
backbond1.3.1.activation  [Relu]                        [None, 32, 120, 160]                                                 0  0  
backbond1.3.1  [Conv2d_Block]                           [None, 32, 120, 160]                                                 0  0  
backbond1.4.0.conv  [DepthwiseConv2d]                   [None, 32, 60, 80]         'weight', [32, 1, 3, 3]                   288  2,760,000  
backbond1.4.0.norm  [BatchNorm]                         [None, 32, 60, 80]         'weight', [32]                  'bias', [32]  64  604,800  
backbond1.4.0.activation  [Relu]                        [None, 32, 60, 80]                                                   0  0  
backbond1.4.0  [DepthwiseConv2d_Block]                  [None, 32, 60, 80]                                                   0  0  
backbond1.4.1.conv  [Conv2d]                            [None, 64, 60, 80]         'weight', [64, 32, 1, 1]                  2,048  19,656,000  
backbond1.4.1.norm  [BatchNorm]                         [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.4.1.activation  [Relu]                        [None, 64, 60, 80]                                                   0  0  
backbond1.4.1  [Conv2d_Block]                           [None, 64, 60, 80]                                                   0  0  
backbond1.5.0.conv  [DepthwiseConv2d]                   [None, 64, 60, 80]         'weight', [64, 1, 3, 3]                   576  5,524,800  
backbond1.5.0.norm  [BatchNorm]                         [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.5.0.activation  [Relu]                        [None, 64, 60, 80]                                                   0  0  
backbond1.5.0  [DepthwiseConv2d_Block]                  [None, 64, 60, 80]                                                   0  0  
backbond1.5.1.conv  [Conv2d]                            [None, 64, 60, 80]         'weight', [64, 64, 1, 1]                  4,096  39,316,800  
backbond1.5.1.norm  [BatchNorm]                         [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.5.1.activation  [Relu]                        [None, 64, 60, 80]                                                   0  0  
backbond1.5.1  [Conv2d_Block]                           [None, 64, 60, 80]                                                   0  0  
backbond1.6.0.conv  [DepthwiseConv2d]                   [None, 64, 60, 80]         'weight', [64, 1, 3, 3]                   576  5,524,800  
backbond1.6.0.norm  [BatchNorm]                         [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.6.0.activation  [Relu]                        [None, 64, 60, 80]                                                   0  0  
backbond1.6.0  [DepthwiseConv2d_Block]                  [None, 64, 60, 80]                                                   0  0  
backbond1.6.1.conv  [Conv2d]                            [None, 64, 60, 80]         'weight', [64, 64, 1, 1]                  4,096  39,316,800  
backbond1.6.1.norm  [BatchNorm]                         [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.6.1.activation  [Relu]                        [None, 64, 60, 80]                                                   0  0  
backbond1.6.1  [Conv2d_Block]                           [None, 64, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch1.0.conv  [Conv2d]          [None, 8, 60, 80]          'weight', [8, 64, 1, 1]                   512  4,910,400  
backbond1.7.branch1.0.branch1.0.norm  [BatchNorm]       [None, 8, 60, 80]          'weight', [8]                   'bias', [8]  16  144,000  
backbond1.7.branch1.0.branch1.0  [Conv2d_Block]         [None, 8, 60, 80]                                                    0  0  
backbond1.7.branch1.0.branch1.1.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 8, 3, 3]                   1,152  11,054,400  
backbond1.7.branch1.0.branch1.1.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch1.1.activation  [Relu]      [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch1.1  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch1.2.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 16, 3, 3]                  2,304  22,113,600  
backbond1.7.branch1.0.branch1.2.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch1.2  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch2.0.conv  [Conv2d]          [None, 8, 60, 80]          'weight', [8, 64, 1, 1]                   512  4,910,400  
backbond1.7.branch1.0.branch2.0.norm  [BatchNorm]       [None, 8, 60, 80]          'weight', [8]                   'bias', [8]  16  144,000  
backbond1.7.branch1.0.branch2.0  [Conv2d_Block]         [None, 8, 60, 80]                                                    0  0  
backbond1.7.branch1.0.branch2.1.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 8, 3, 3]                   1,152  11,054,400  
backbond1.7.branch1.0.branch2.1.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch2.1.activation  [Relu]      [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch2.1  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch2.2.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 16, 3, 3]                  2,304  22,113,600  
backbond1.7.branch1.0.branch2.2.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch2.2  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch3.0.conv  [Conv2d]          [None, 8, 60, 80]          'weight', [8, 64, 1, 1]                   512  4,910,400  
backbond1.7.branch1.0.branch3.0.norm  [BatchNorm]       [None, 8, 60, 80]          'weight', [8]                   'bias', [8]  16  144,000  
backbond1.7.branch1.0.branch3.0  [Conv2d_Block]         [None, 8, 60, 80]                                                    0  0  
backbond1.7.branch1.0.branch3.1.conv  [Conv2d]          [None, 12, 60, 80]         'weight', [12, 8, 3, 3]                   864  8,289,600  
backbond1.7.branch1.0.branch3.1.norm  [BatchNorm]       [None, 12, 60, 80]         'weight', [12]                  'bias', [12]  24  220,800  
backbond1.7.branch1.0.branch3.1.activation  [Relu]      [None, 12, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch3.1  [Conv2d_Block]         [None, 12, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch3.2.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 12, 3, 3]                  1,728  16,584,000  
backbond1.7.branch1.0.branch3.2.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch3.2.activation  [Relu]      [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch3.2  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0.branch3.3.conv  [Conv2d]          [None, 16, 60, 80]         'weight', [16, 16, 3, 3]                  2,304  22,113,600  
backbond1.7.branch1.0.branch3.3.norm  [BatchNorm]       [None, 16, 60, 80]         'weight', [16]                  'bias', [16]  32  297,600  
backbond1.7.branch1.0.branch3.3  [Conv2d_Block]         [None, 16, 60, 80]                                                   0  0  
backbond1.7.branch1.0  [ShortCut2d]                     [None, 48, 60, 80]                                                   0  0  
backbond1.7.branch1.1.conv  [Conv2d]                    [None, 64, 60, 80]         'weight', [64, 48, 1, 1]                  3,072  29,486,400  
backbond1.7.branch1.1.norm  [BatchNorm]                 [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.7.branch1.1  [Conv2d_Block]                   [None, 64, 60, 80]                                                   0  0  
backbond1.7.branch2.conv  [Conv2d]                      [None, 64, 60, 80]         'weight', [64, 64, 1, 1]                  4,096  39,316,800  
backbond1.7.branch2.norm  [BatchNorm]                   [None, 64, 60, 80]         'weight', [64]                  'bias', [64]  128  1,219,200  
backbond1.7.branch2  [Conv2d_Block]                     [None, 64, 60, 80]                                                   0  0  
backbond1.7  [ShortCut2d]                               [None, 64, 60, 80]                                                   0  0  
classification_headers.0.0  [DepthwiseConv2d]           [None, 64, 60, 80]         'weight', [64, 1, 3, 3]         'bias', [64]  640  6,134,400  
classification_headers.0.1  [Conv2d]                    [None, 15, 60, 80]         'weight', [15, 64, 1, 1]        'bias', [15]  975  9,350,400  
regression_headers.0.0  [DepthwiseConv2d]               [None, 64, 60, 80]         'weight', [64, 1, 3, 3]         'bias', [64]  640  6,134,400  
regression_headers.0.1  [Conv2d]                        [None, 12, 60, 80]         'weight', [12, 64, 1, 1]        'bias', [12]  780  7,478,400  
backbond2.0.0.conv  [DepthwiseConv2d]                   [None, 64, 30, 40]         'weight', [64, 1, 3, 3]                   576  1,381,200  
backbond2.0.0.norm  [BatchNorm]                         [None, 64, 30, 40]         'weight', [64]                  'bias', [64]  128  304,800  
backbond2.0.0.activation  [Relu]                        [None, 64, 30, 40]                                                   0  0  
backbond2.0.0  [DepthwiseConv2d_Block]                  [None, 64, 30, 40]                                                   0  0  
backbond2.0.1.conv  [Conv2d]                            [None, 128, 30, 40]        'weight', [128, 64, 1, 1]                 8,192  19,659,600  
backbond2.0.1.norm  [BatchNorm]                         [None, 128, 30, 40]        'weight', [128]                 'bias', [128]  256  612,000  
backbond2.0.1.activation  [Relu]                        [None, 128, 30, 40]                                                  0  0  
backbond2.0.1  [Conv2d_Block]                           [None, 128, 30, 40]                                                  0  0  
backbond2.1.0.conv  [DepthwiseConv2d]                   [None, 128, 30, 40]        'weight', [128, 1, 3, 3]                  1,152  2,763,600  
backbond2.1.0.norm  [BatchNorm]                         [None, 128, 30, 40]        'weight', [128]                 'bias', [128]  256  612,000  
backbond2.1.0.activation  [Relu]                        [None, 128, 30, 40]                                                  0  0  
backbond2.1.0  [DepthwiseConv2d_Block]                  [None, 128, 30, 40]                                                  0  0  
backbond2.1.1.conv  [Conv2d]                            [None, 128, 30, 40]        'weight', [128, 128, 1, 1]                16,384  39,320,400  
backbond2.1.1.norm  [BatchNorm]                         [None, 128, 30, 40]        'weight', [128]                 'bias', [128]  256  612,000  
backbond2.1.1.activation  [Relu]                        [None, 128, 30, 40]                                                  0  0  
backbond2.1.1  [Conv2d_Block]                           [None, 128, 30, 40]                                                  0  0  
backbond2.2.0.conv  [DepthwiseConv2d]                   [None, 128, 30, 40]        'weight', [128, 1, 3, 3]                  1,152  2,763,600  
backbond2.2.0.norm  [BatchNorm]                         [None, 128, 30, 40]        'weight', [128]                 'bias', [128]  256  612,000  
backbond2.2.0.activation  [Relu]                        [None, 128, 30, 40]                                                  0  0  
backbond2.2.0  [DepthwiseConv2d_Block]                  [None, 128, 30, 40]                                                  0  0  
backbond2.2.1.conv  [Conv2d]                            [None, 128, 30, 40]        'weight', [128, 128, 1, 1]                16,384  39,320,400  
backbond2.2.1.norm  [BatchNorm]                         [None, 128, 30, 40]        'weight', [128]                 'bias', [128]  256  612,000  
backbond2.2.1.activation  [Relu]                        [None, 128, 30, 40]                                                  0  0  
backbond2.2.1  [Conv2d_Block]                           [None, 128, 30, 40]                                                  0  0  
classification_headers.1.0  [DepthwiseConv2d]           [None, 128, 30, 40]        'weight', [128, 1, 3, 3]        'bias', [128]  1,280  3,069,600  
classification_headers.1.1  [Conv2d]                    [None, 10, 30, 40]         'weight', [10, 128, 1, 1]       'bias', [10]  1,290  3,093,600  
regression_headers.1.0  [DepthwiseConv2d]               [None, 128, 30, 40]        'weight', [128, 1, 3, 3]        'bias', [128]  1,280  3,069,600  
regression_headers.1.1  [Conv2d]                        [None, 8, 30, 40]          'weight', [8, 128, 1, 1]        'bias', [8]  1,032  2,474,400  
backbond3.0.0.conv  [DepthwiseConv2d]                   [None, 128, 15, 20]        'weight', [128, 1, 3, 3]                  1,152  690,900  
backbond3.0.0.norm  [BatchNorm]                         [None, 128, 15, 20]        'weight', [128]                 'bias', [128]  256  153,000  
backbond3.0.0.activation  [Relu]                        [None, 128, 15, 20]                                                  0  0  
backbond3.0.0  [DepthwiseConv2d_Block]                  [None, 128, 15, 20]                                                  0  0  
backbond3.0.1.conv  [Conv2d]                            [None, 256, 15, 20]        'weight', [256, 128, 1, 1]                32,768  19,660,500  
backbond3.0.1.norm  [BatchNorm]                         [None, 256, 15, 20]        'weight', [256]                 'bias', [256]  512  306,600  
backbond3.0.1.activation  [Relu]                        [None, 256, 15, 20]                                                  0  0  
backbond3.0.1  [Conv2d_Block]                           [None, 256, 15, 20]                                                  0  0  
backbond3.1.0.conv  [DepthwiseConv2d]                   [None, 256, 15, 20]        'weight', [256, 1, 3, 3]                  2,304  1,382,100  
backbond3.1.0.norm  [BatchNorm]                         [None, 256, 15, 20]        'weight', [256]                 'bias', [256]  512  306,600  
backbond3.1.0.activation  [Relu]                        [None, 256, 15, 20]                                                  0  0  
backbond3.1.0  [DepthwiseConv2d_Block]                  [None, 256, 15, 20]                                                  0  0  
backbond3.1.1.conv  [Conv2d]                            [None, 256, 15, 20]        'weight', [256, 256, 1, 1]                65,536  39,321,300  
backbond3.1.1.norm  [BatchNorm]                         [None, 256, 15, 20]        'weight', [256]                 'bias', [256]  512  306,600  
backbond3.1.1.activation  [Relu]                        [None, 256, 15, 20]                                                  0  0  
backbond3.1.1  [Conv2d_Block]                           [None, 256, 15, 20]                                                  0  0  
classification_headers.2.0  [DepthwiseConv2d]           [None, 256, 15, 20]        'weight', [256, 1, 3, 3]        'bias', [256]  2,560  1,535,400  
classification_headers.2.1  [Conv2d]                    [None, 10, 15, 20]         'weight', [10, 256, 1, 1]       'bias', [10]  2,570  1,541,400  
regression_headers.2.0  [DepthwiseConv2d]               [None, 256, 15, 20]        'weight', [256, 1, 3, 3]        'bias', [256]  2,560  1,535,400  
regression_headers.2.1  [Conv2d]                        [None, 8, 15, 20]          'weight', [8, 256, 1, 1]        'bias', [8]  2,056  1,233,000  
extra.0  [Conv2d]                                       [None, 64, 15, 20]         'weight', [64, 256, 1, 1]       'bias', [64]  16,448  9,868,200  
extra.1  [DepthwiseConv2d]                              [None, 64, 8, 10]          'weight', [64, 1, 3, 3]         'bias', [64]  640  102,240  
extra.2  [Conv2d]                                       [None, 256, 8, 10]         'weight', [256, 64, 1, 1]       'bias', [256]  16,640  2,662,240  
extra.3  [Relu]                                         [None, 256, 8, 10]                                                   0  0  
classification_headers.3  [Conv2d]                      [None, 15, 8, 10]          'weight', [15, 256, 3, 3]       'bias', [15]  34,575  5,531,840  
regression_headers.3  [Conv2d]                          [None, 12, 8, 10]          'weight', [12, 256, 3, 3]                 27,648  4,423,600  
================================================================
Total params: 299,926
Trainable params: 299,926
Non-trainable params: 0
Total MACC: 429,089,640
Total FLOPs: 0.85692 GFLOPs
----------------------------------------------------------------
Input size (MB): 3.52
Forward/backward pass size (MB): 355.32
Params size (MB): 1.14
Estimated Total Size (MB): 359.98
----------------------------------------------------------------
Out[12]:
SsdDetectionModel(
  (palette): (128, 255, 128)
  (palette): (128, 255, 128)
)

六、人臉辨識在雲端與邊緣:目前的數位攝影機規格都不錯,你可以依據成本、精密度(商用/工業用/微距)、使用情境來選擇欲佈署的架構與裝置(USB/Network/PoE Switch supported…)。像我家中是用瑞典大廠 Axis,它的WebCam 是含有CPU(一般都只有晶片)的裝置,就像iPhone可以去市集下載新的功能至你的攝影機。早期我有向著後陽台設定電子圍籬,就因為鄰居的貓半夜經過多次誤觸警報,只好放棄室外監控,讓第二支室內攝影機的電子圍籬,由二線升到一線。幾年後,原廠新增的非人類的篩選,我只需更新軟體,二線又可以回到一線的警戒。這就是一個邊緣運算的好例子。

  1. 公有雲以 Azure 為例,任一個數據中心都有四個足球場這麼大的容量,以獨立的水、電、網路、GPU、硬碟,來提供近乎無限的運算資源,再加上超過 90種合規 Compliance 認証,比放在銀行保管箱還安全(假設任一個合規每三個月都要來你家稽核一次,如果你想要做歐美生意,光是定期的稽核報告就會讓你寫到手軟 ISO 27001、ISO 27018、SOC 1、SOC 2、SOC3、FedRAMP、HITRUST、MTCS、IRAP、 ENS)。至於網路你也不用擔心,Azure 在全世界的資料中心都已經佈建好海底電纜的國際網路,不管你是用ADSL、Fiber、還是4G/5G上網,都能享受到傳輸時免費的加密功能(儲存時的加密另有收費的解決方案可以選購)。==> 換言之,在雲端你可以不受限制的發展你的人臉辨識模型,然後以 API的方式來提供人臉比對。
  2. 邊緣裝置提供了較佳的移動性,但是卻犧牲了資源的擴充性。資源雖然有限,也不代表就跑不動,就像是曾經支配 PDA 手持市場的微型資料庫 Sybase SQL Anywhere,僅需要1MB的記億體與 2KB的硬碟空間,也能在PDA上面跑的嚇嚇叫。Tensor Flow Lite、Onnx是目前邊緣裝置的主流框架,如果你在電腦或是雲端環境開發時是用 Tensor Flow,你可以選擇輸出成 Tensor Flow Lite;如果你在電腦或是雲端環境開發時是用 Pytorch,你可以選擇 Pytorch => Onnx 或是 Pytorch => Onnx => Tensor Flow => Tensor Flow Lite
  3. 至於 ASIC(Application Specific Integrated Circuit) 特定應用積體電路,因為我還沒有接觸過,將來有機會再介紹給大家。

七、人臉辨識的法律或是道德問題

  1. 國內法律:隨著台灣日前出現了嚴重地 AI換臉犯罪,總統對子虛烏有的公眾人物性愛影片大怒,馬上就讓內政部在刑法上能夠增訂深偽犯罪條款。相信後續的規範一定會愈來愈清楚…
  2. 國外法律:如果你做的是歐美生意,你需要留意歐盟正考慮暫時為期 3~5年在公共場域使用人臉辨識科技禁令,而美國在伊利諾伊州則是有生物識別數據保密法 Biometric Information Privacy Act,另外還有加州消費者隱私法 California Consumer Privacy Act 或是華盛頓州的法令,都有明確地規範企業與政府該怎麼應用生物識別數據的使用。
  3. 道德:水能載舟亦能覆舟,Azure 在雲端 AI服務經過了數年的發展後,已經規範了明確地準則在產品與服務的設計中,包含當責、透明、公平(之前提供給警察局的人臉辨識方案中,黑人的辨識率偏低,經調查發現訓練集都是以白人為主,顧及公平性就先行解約,事後再去補足黑人的訓練資料來完善自家的產品)、可靠性與安全性、隱私與安全、包容。細節可以參考官網的說明
AI Values from Future Computed

八、結論:

  1. 不管是人臉辨識還是其他深度學習的 AI模型,DNN發展至今已經能實作出令人咋舌的精密人腦的功能,例如反省、遺忘、抽象能力。透過 backward propagation 反向去更新權重,然後再接續下一個批次的 forward propagation。愈學愈好,類似人類在算數學的反覆驗算行為;透過 Drop out 隨機地忘掉一半的參數,讓機器設法重新去學會更有用的特徵。類似「滿招損,謙受益」理念,以空杯的心態虛心求教,可以學得更多更好;透過 encoder 降維學習,再用 decoder 升維驗證,抽象地去學習事物的表徵與脈絡。
  2. 想讓 AI 做到人類的舉一反三,並不是背多分,花是買市面上各個牌子的參考書,除了耗時也費力(資料集的取得在個資法或是其他生物識別數據保密法的前題,只會更困難)而且效果很有限。真的有效的方法是讓 AI去進行多個子任務的訓練,例如在 ArcFace 的主要模型框架之外,再增加 data augmentation 來提升抗雜訊或是對不同環境的適應力,甚至再增加對口罩的辨識子任務…等
  3. 若想要在邊緣裝置運行人臉辨識,AI瘦身是必要的行為,包含透過結構性的網路修剪或是簡化節點的結構,另一個就是降低的精密度來減少參數的運算量。抑或是我接下來要去研究的知識蒸餾 Knowledge distillation 讓大模型所獲得的知識可以遷移至小模型中。

九、其他:

  1. 人臉特徵點 Facial landmark 基礎於五官特徵的中心點定位,故人類眼耳口鼻嘴大多相似的佈局,並不能直接拿來做人臉辨識,只能成為快速定位人臉在整張圖像的區域、進行人臉對齊、或是表情辨識。如果你有看過 Lie to me 探討人類微表情與打擊犯罪的國外影集,個人覺得這是一個更大的 AI好題目…
  2. 中文翻譯為空想性錯視 Pareidolia 指的是,人類可以用簡單線條的火柴人去會意一個人,同理在身邊有許多符號 Symbol 也能類比或會意出更高階的意涵。例如火星上Cydonia地區有一個外星迷很熱烈討論的 face on Mars(下圖),愛好者就已經著手透過 Pareidolia 去掃描地圖去探索形而上的未知或是巧合。
  3. Uber 為了乘客安全的司機驗證 App 所開發出來的 CoordConv 座標捲積來改善捲積逐像素處理時,雲深不知處的先天缺陷的問題,也只值得將來繼續再來寫文章探討…
  4. 用聲音去預測長相,美國海岸警衛隊就有採用類似 MIT麻省理工學院的 Speach2Face 模型,來揪出浪費國家資源的惡作劇報案民眾(每年約150個),情節重大者甚至能夠成為刑事訴訟的証據之一。

李秉錡 Christian Lee
Once worked at Microsoft Taiwan