顔認証AIのFaceNetは顔のどこに注目しているのか

# はじめに
この記事は[顔学2020アドベントカレンダー](https://qiita.com/advent-calendar/2020/facial_study)の6日目の記事です．
前回投稿したFaceNetを使って，顔認証AIが顔のどこに注目して人を判別しているのかを可視化してみます．

# FaceNet
顔認証のための顔認識モデルです．512次元の空間上に顔を埋め込むいわゆるEmbedding Modelで，空間上のユークリッド距離を使って顔の類似度を計算することで顔認証を行います．

[前回の記事](https://qiita.com/Takuya-Shuto-engineer/items/4dcbadbd16e16c3b1677)でFaceNetの使い方を紹介しています．

# GradCAM
画像認識モデルがどこに注目をしているのかをヒートマップで可視化してくれるモデルです．画像の顕著性（Saliency）の可視化と似たような分野です．画像の顕著性については人間の視覚特性（Treismanの特徴統合理論）を根拠に画像処理を行うのですが，[GradCAM](https://arxiv.org/abs/1610.02391)は画像認識モデルの出力を参考にします．

最終層の出力は基本的にクラスラベルになっているので，クラスラベルの予測値の中で最も大きな値を認識結果とします．GradCAMでは，最も大きい値の出力（予測値）を計算する上で，寄与の大きかった画像箇所を逆算することで注目箇所を推定します．

![スクリーンショット 2020-12-08 204620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/457c91b8-643a-b40e-b0c1-b718d5933ff5.png)
![スクリーンショット 2020-12-08 204733.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/b2fb434e-5d81-65e4-a635-152886fb586d.png)

# FaceNet（Embedding Model）にどう導入するか
FaceNetの出力は空間上の座標なので，何か特定のクラスを表す認識結果を出力するわけではありません．なので，どの出力の値が重要なのか判断するのが非常に難しいです．

すごく厳密に寄与した出力チャネルを決める研究もあるみたいなんですが，ここでは簡単のために全出力の絶対値の中で最も大きかった値を採用しました．

[Adapting Grad-CAM for Embedding Networks](https://deepai.org/publication/adapting-grad-cam-for-embedding-networks)

[DCGAN](https://arxiv.org/abs/1511.06434)や[InterFaceGAN](https://arxiv.org/abs/2005.09635)などの論文が示唆するように，深層学習のモデルが構築する中間表現（潜在変数）には意味的軸があり，また現実世界の法則を反映したような軸同士の相関関係を持っています．（年齢を変化させると自然と眼鏡が出てきたり，白髪になったりする．つまり，意味的軸同士のcos類似度が高い．）

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/cb29da0a-0d35-76d6-a13c-f48b45ac7139.png)

FaceNetも多くの顔画像をきれいに分布させる顔空間を構築しているはずです．なので，なんらかの意味的定量化がなされた空間上であれば，各要素にも固有の意味を含んでいると仮定し，各座標値の絶対値を評価すればそれなりの結果になるだろうと見当をつけました．

# 実装（コード）
動作環境はGoogle Colaboratoryです．前回のノートブックに追記で書いています．

### データ
[前回の記事](https://qiita.com/Takuya-Shuto-engineer/items/4dcbadbd16e16c3b1677)で使った首相データを再利用します．

### ライブラリのインストール

```console
!pip install tf-explain tensorflow==2.0.0 mira keras-facenet
```

### FaceNetで顔ベクトルを取得

```python
from mira.detectors import MTCNN
from keras_facenet import FaceNet
from PIL import Image
import numpy as np
import cv2
import matplotlib.pyplot as plt
import os

detector = MTCNN() # 顔領域検出器
embedder = FaceNet() # FaceNetのモデルを持つクラス

img = img = cv2.imread(FILE_PATH) # 画像読み込み
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # RGB形式に変換
face = detector.detect(img_rgb)[0] # 画像中に複数の顔が検出されることもある．先頭をとってくる．
embedding = embedder.embeddings([face.selection.extract(img_rgb)])
```

### GradCAMで注目箇所を可視化

```python
from tf_explain.core.grad_cam import GradCAM

# 
model = embedder.model # FaceNetのモデル（KerasのModelクラス）
model.summary()

# 前処理
img = face.selection.extract(img_rgb)
img = cv2.resize(img, (160, 160))
X = np.float32([embedder._normalize(img)])
data = (X, None)

# 出力の重要要素を決定
abs = np.abs(embedding) # 絶対値
top_channel = np.argmax(abs) # 絶対値が最も大きい要素番号

# GradCAMで可視化
explainer = GradCAM()
grid = explainer.explain(data, model, class_index=top_channel, layer_name="Block8_6_Conv2d_1x1")
explainer.save(grid, ".", "grad_cam.png")
```

結果の例をいくつか貼っておきます．
![abe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/17512df7-edfc-7064-942e-d74d28ffa2a0.png)![suga.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593087/b90822c3-278f-3e06-36a6-c94e015223a2.png)

重要要素の決定の妥当性は置いておき，結果自体は顕著性っぽいものがとれてそうですね．主に鼻から目にかけての領域を一番重要視していると今回は判断できそうです．

自分が人の顔を見る際に真っ先に目が行くのが鼻や目のあたりなので，直感的にも正しい気がしています．

ちなみに，西洋人と東洋人で顔を観察するときの視点移動方法は違うらしいですね．今回の結果は鼻を中心に顕著性が分布したので，どちらかというと東洋人っぽい観察の特性だと思いました．
[Culture Shapes How We Look at Faces](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0003022)

# さいごに
今回はFaceNetが顔認識を行う際にどこを重視してみているのか，独自の指標で可視化してみました．この指標が本当に正しいかはちょっと担保できませんが，少し試してみる程度ならいい結果が得られたと思っています．

コードは[こちら](https://colab.research.google.com/drive/1SnBvOmDVa_KN6SqbhJJK90yvMddASZmp?usp=sharing)から利用できます．前回の記事のノートブックに追記する形で書いてあるので，後半まで順番に実行してください．

結局，修論のためにお休みをもらうといっておきながら記事のネタを思いついたので帰ってきてしまいました．また気が向いたら更新するくらいの気持ちでカレンダーを埋めていこうと思います．

# 参考
- [Culture Shapes How We Look at Faces](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0003022)
- [GradCAM](https://arxiv.org/abs/1610.02391)
- [tf-explain](https://github.com/sicara/tf-explain)
- [keras-facenet](https://github.com/faustomorales/keras-facenet)
- [日本一詳しくGrad-CAMとGuided Grad-CAMのソースコードを解説してみる(Keras実装)](https://qiita.com/kinziro/items/69f996065b4a658c42e8)
- [全22行のコードでGradCAM。tf_explainは、使い易いかも、お薦め！](https://qiita.com/enoughspacefor/items/0f4568ecec4f5277cf37)