CAMの発展と実装（Grad-CAM, Score-CAM, Group-CAM）

CAM (Class Activation Mapping) は XAI (Explainable AI) の一種で、CNN Classifier の入力データとラベルに対して、CNN がどの部分に注目してそのラベルだと判断したのかという根拠を示すものである。

![cnn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/65370576-e65d-f0eb-cd35-867a81ff6e72.png)

CNN (Convolutional Neural Network) の構成は、一般的には上記の図のようなものである。

色々省略はしているが、直方体で示しているのが Conv. Layer（Convolutional Layer：畳み込み層）で、台形で示しているのが Linear Layer（線形結合層）。幾層も重ねた Conv. Layer で画像から特徴を抽出し、最後の Linear Layer でその抽出した特徴からラベルを推測する。なので、特徴を抽出する部分を Feature Part、ラベルを推測する部分を Classifier Part と呼ぶことにする。

また、Conv. Layer は Block という単位に分けられることが一般的で、Block を通過するごとに画像のサイズは小さくなり（一般的には縦横ともに半分になる）、その代わり channel 数は多くなる。
（ちなみに一般的に最初の Conv. Layer（上記画像における白い直方体）は画像の色（RGB）を分配する特別な役割を持つので、今回の考慮からは除外する）

![conv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/92e801b1-ec1a-054d-c910-eaab165fc46a.png)

Conv. Layer を直方体で表してきたが、具体的には直方体の高さと奥行きがそれぞれ画像の高さと横幅、そして直方体の幅が Conv. Layer の channel の数に相当する。

channel とは何かというと、ぶっちゃけて言うと「形」である。

Conv. Layer は channel の数だけ weight ＝形を持ち、そして height と width は、その形が画像のどこに存在するのかを表す。

最初の方の Conv. Layer は形として「縦の線」「横の線」といった単純な形を認識する役割を持ち、画像からそれら単純で細かい形を抽出する。そして後になればなるほど、形は複雑に（丸・三角等）、形の種類は多く、そして認識する大きさも大きくなる。

![forward.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/4bc326d1-307a-2fed-46fa-7f8e7b0fb0b5.png)

CAM は、このような画像の forward（順伝播）時の Conv. Layer の出力（＝ Activation）と、ラベルのスコア、そして backward（逆伝播）時の Conv. Layer の出力（＝ Gradient）を使って、CNN Classifier が画像のどこに注目していくのかを示すものである。

CAM の生成物は heatmap として画像に overlay させて示すことが多い。この heatmap を CAM では Saliency Map（特徴マップ）と呼ぶ。以下の説明では、中間生成物も Saliency Map と呼んでしまっているが、そこは生暖かい目で見て欲しい。

モデルによって Gradient を使ったり使わなかったり、Gradient の代わりに上記の図にあるように Classifier Part の weight である class weight を使ったりと色々あるのだが、それは以降で説明する。

Conv. Layer から Activation や Gradient を取り出して CAM を作る際に、どの Conv. Layer から取り出すかが設定のひとつであるのだが、ほとんどのモデルでは Feature Part の中の最後の Conv. Layer を使う。複数の Layer に対応しているモデルもあるが、その際にも Block の最後の Conv. Layer、さらに複数の Block で同じサイズ（height x weight）を出力する場合は、その中でも最後の Conv. Layer を使うことが一般的である。（最後の Conv. Layer がその Block で抽出した特徴をまとめあげる役割を持つから）

ちなみに、入力が画像でなくとも、Tabular Data であったり Text であったりしても、Conv 2D が Conv 1D になるだけなので、同様に CAM は実現できる。

# CAM の分類と発展

今まで様々な CAM が提唱されているが、その多くは以下の３種類に分類され、大体この順番に発展している。

1. CAM と Grad-CAM、そして Gradient を滑らかにするモデル
2. channel の weight を作るモデル
3. channel をクラスタリングするモデル

## CAM と Grad-CAM と 滑らかな Gradient

* CAM
    * B. Zhou, et al. "[Learning Deep Features for Discriminative Localization](https://arxiv.org/abs/1512.04150)" CVPR 2016.
* Grad-CAM
    * R. Selvaraju, et al. "[Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization](https://arxiv.org/abs/1610.02391)" ICCV 2017.
* Grad-CAM++
    * A. Chattopadhyay, et al. "[Grad-CAM++: Improved Visual Explanations for Deep Convolutional Networks](https://arxiv.org/abs/1710.11063)" WACV 2018.
* Smooth Grad-CAM++
    * D. Omeiza, et al. "[Smooth Grad-CAM++: An Enhanced Inference Level Visualization Technique for Deep Convolutional Neural Network Models](https://arxiv.org/abs/1908.01224)" IntelliSys 2019.
* XGrad-CAM
    * R. Fu, et al. "[Axiom-based Grad-CAM: Towards Accurate Visualization and Explanation of CNNs](https://arxiv.org/abs/2008.02312)" BMVC 2020.
* IntegratedGrad
    * M. Sundararajan, et al. "[Axiomatic Attribution for Deep Networks](https://arxiv.org/abs/1703.01365)" ICML 2017.
* HiRes-CAM
    * R. Draelos, et al. "[Use HiResCAM instead of Grad-CAM for faithful explanations of convolutional neural networks](https://arxiv.org/abs/2011.08891)" arXiv 2020.

![cam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/543955c1-204d-8fc4-5bd5-50c01d6cefb8.png)

まず CAM の概略を上記の図に示す。

最後の Conv. Layer の Activation を抜き出すと、それは入力画像の中に各 channel（＝特徴）がどこにあるのか、という情報になっている。ただし、Activation をそのまま使っても、それは単に「画像に特徴的な部分があった」という情報に過ぎない。そこで、Activation に何らかの weight を掛けて、「『犬』と判断する場合にどの特徴を見たか」という情報にしなければならない。

Classifier Part の重みは、まさにこれを指し示すものである。これが vanilla な CAM モデルである。

ただし、vanilla CAM には「CNN Classifier の構成に強く依存する」という欠点がある。

ターゲットとなる Conv. Layer は必ず最後の Conv. Layer でなければならないし、最後の Conv. Layer から Classifier Part まで余計な処理が入ってはならない。そして Classifier Part で Linear Layer 以外の処理が入っていてもダメ（ReLU ぐらいなら大丈夫だけど）。

![gradcam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/ef6b158b-58bc-a2bf-6507-4ee945ac55c7.png)

これを解決したのが Grad-CAM である。Grad-CAM は class weight の代わりに Gradient を使うので、Conv. Layer を使うネットワーク、すなわち CNN ならば必ず実装できる。

ただし Grad-CAM にも欠点があって、それは「Gradient は不安定」という点である。

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/8b5322d2-c1d0-aa55-f099-bf863685d734.png)

上は Grad-CAM の結果（Saliency Map）なのだが、犬の顔だけではなく、猫の首あたりにも反応してしまっている。

Gradient はその名の通り、本来は Back Propagation を行うために逆伝播された誤差、すなわち傾きである。（もっと正確に言うと、スコアを Activaiton で偏微分したもの）

なので、以下のような問題点が出てくる。

* Activationで強く反応している箇所の影響を受けやすい
    * Gradient の値が跳ね上がる
* 同じラベルのものが並んでいるような場合に細切れになる
    * 局所的な値だから

なので、「いかに Gradient を滑らかにするか」ということが重要になり、この点に関するモデルが次々と発表された。

* Grad-CAM++
    * Gradient の２階偏微分が０になる（加速度が０になるようなもの）仮定を導入して滑らかにする
* Smooth Grad-CAM++
    * 入力にノイズを加えたものをいくつか計算して、その平均を取る（SmoothGrad）ことによってさらに滑らかにする
* XGrad-CAM
    * 境界条件を考えて、滑らかな Gradient を導出する
* IntegratedGrad
    * blur した画像から元の画像まで、経路積分のように区間を区切って計算することによって、滑らかな Gradient を得る
* HiRes-CAM
    * これは例外で、Grad-CAM およびその発展形は Gradient を平均して channel に対する weight を計算していたが、平均せずにそのまま掛けてしまうことによって、高解像度な Saliency Map が得られるというもの

## channel の weight を作るモデル

Gradient をいかに滑らかにするかという発展していた CAM だが、ここで「そもそも Gradient を使っているのが悪いのだから、Gradient に頼らない CAM を作ろう」という動きが出てくる。それが以下のモデルである。

先に言っておくと、「Gradient に頼らない」だけで「Gradient を使わない」とは一言も言ってないことに注意。

* Eigen-CAM
    * M. Muhammad, et al. "[Eigen-CAM: Class Activation Map using Principal Components](https://arxiv.org/abs/2008.00299)" IJCNN 2020.
* Ablation-CAM
    * H. Ramaswamy, et al. "[Ablation-CAM: Visual Explanations for Deep Convolutional Network via Gradient-free Localization](https://openaccess.thecvf.com/content_WACV_2020/html/Desai_Ablation-CAM_Visual_Explanations_for_Deep_Convolutional_Network_via_Gradient-free_Localization_WACV_2020_paper.html)" WACV 2020.
* Score-CAM
    * H. Wang, et al. "[Score-CAM: Score-Weighted Visual Explanations for Convolutional Neural Networks](https://arxiv.org/abs/1910.01279)" CVF 2020.

![eigencam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/29318a32-2c48-7586-bb88-8e184d979e54.png)

上記の図の雲の部分は、Gradient を使わなくてもいいし、前の章で挙げたいずれかのモデルを使っても良い、何をしても良い部分とする。

そうして出来た中間生成物の Saliency Map を、channel x position という平面に変形し SVD (Singular Value Decomposition) する。

SVD は、数学的には非正方行列の固有値分解であり、データ分析的には２つの関連した事象を独立した空間に分解する手法である。

なので channel x position 行列を SVD して出来た Channel Space の縦ベクトルは、元の channel x position 平面における channel 成分の固有ベクトルと言うことが出来、その 1-st Eigen-Vector （対応する固有値がもっとも大きい固有ベクトル）をそのまま channel の weight とするという手法が Eigen-CAM である。

Saliency Map の生成時に Gradient を使わなければ、純粋に Activaiton の情報だけで Saliency Map を生成できることになる。すごい。

ただしこの場合、「『犬』というラベル」という情報（スコアや Gradient）が一切無いので、どうやっても同じ Saliency Map、Top rank の場合の Saliency Map が生成されることになる。

そこで、最初の雲の部分を Grad-CAM にする（EigenGrad-CAM）と、ラベルに対応した Saliency Map が生成できる。

しかし Eigen-CAM にも欠点があって、それは SVD は結果的に固有値が正の値になれば Channel Space, Position Space の正負はどうでも良いので、weight の正負が逆転してしまうことがあるということである。

![ablation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/5e598845-a17e-b5a4-5639-ec4c288e7582.png)

Ablation-CAM は非常に大胆な手法で各 channel の weight を作成する。

先程と同様になんらかの方法で生成した中間生成物の Saliency Map に関して、特定のチャンネルを落としてしまう（Ablation＝切除）。その Ablation したものを Conv. Layer の直後、すなわち Classifier Part にのみ流すことによって Ablation したスコアを得る。

もし Ablation した channel が重要な channel であれば、Ablation スコアはガクッと下がるはず。逆に重要な channel でなければ、Ablation スコアはそれほど変わらないはず。なので、元のスコアと Abation スコアの差分（正確には傾き）を各 channel のスコアとするという手法が Ablation-CAM である。

MobileNet V3 の場合、最終 Conv. Layer の channel 数は 960 もあるのだが、Classifier Part を通すだけなので非常に軽量だし、スコアを用いているので非常に綺麗な Saliency Map が作れる。

しかし、vanilla CAM と同じくネットワーク構成に非常に依存するという問題点がある。特に ResNet のような shortcut があるモデルだと不可能。

![abscission.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/07f5a841-4313-403e-d897-6acc4e3b8658.png)

Ablation-CAM と真逆のことをするのが Score-CAM である。

Ablation とは逆に、特定の channel だけを取り出して、それを normalize した後に元画像のマスクとする。すると、その channel が注目する箇所だけが抜き出された画像になり、それを再びネットワーク全体に通すことで、新たなスコアが得られる。その channel が重要な channel であれば、余計な情報が無くなる分スコアは高くなり、重要な channel じゃなければ、スコアは非常に低くなる。このスコアと元のスコアとの差分を weight とするのが Score-CAM である。

Ablation（切除）と逆なので、これを Abscission（離脱）と勝手に名付けた。

より mask を強調するために Grad-CAM と組み合わせた方が良い結果が得られる、960 回も画像をネットワークに通さないといけないので非常に重いという問題がありつつも、ネットワーク構成にまったく依存しないという良い点がある。

## channel をクラスタリングするモデル

CAM としてネットワーク構成に依存しない方が良いが、さすがに Score-CAM は重すぎる。

そして Score-CAM を色々調べていくと、どうやら channel には良い channel と悪い channel があることが分かってくる。良い channel だけを取り出したい、もしくは channel をクラスタリングしたい。

まさにこの channel をクラスタリングをすることが、最近の CAM の潮流である。

* Group-CAM
    * Q. Zhang, et al. "[Group-CAM: Group Score-Weighted Visual Explanations for Deep Convolutional Networks](https://arxiv.org/abs/2103.13859)" arXiv 2021.
* Cluster-CAM
    * Z. Feng, et al. "[Cluster-CAM: Cluster-Weighted Visual Interpretation of CNNs' Decision in Image Classification](https://arxiv.org/abs/2302.01642)" arXiv 2023.
* Cosine-CAM
    * Original Implementation

![groupcam.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/b9492324-cc09-d4a8-0728-2a9045ad0631.png)

Activation や Gradient から中間生成物の Saliency Map を作るのは、今までと同じように Grad-CAM でも Grad-CAM++ でも何でも良い。

この中間生成物の Saliency Map を Eigen-CAM と同様に channel x position 平面に変形して、普通に k-Means して channel group を生成するのが Group-CAM。

その後は、Eigen-CAM でも Ablation-CAM でも Score-CAM でも何でも良いので、channel group 毎のweight を計算して最終的な Saliency Map を作る。

しかし、実は、Group-CAM の論文には「Clustering の手法として k-Means を使う」とは一言も書かれていない。Clustering なら k-Means だろうと勝手に想像してやっているだけである。

Clustering 手法にちゃんと言及しているのが Cluster-CAM。Clustering 手法には k-Means と Spectral Clustering があり、cosine 距離で clustering するのが良いだろうと。

Cluster-CAM で提唱しているのはもうひとつあって、それが Cognition-base と Cognition-scissors。

channel group の中で、最も良いものの weight を 1、最も悪いものの weight を -1 として、それ以外は無視（weight を 0）するというものだが、これは対象物が複数ある場合にあまり良くない結果になるように思う。

Cluster-CAM で cosine 距離を使いたい気持ちは非常にワカル。犬の顔の部分を特徴とする channel が複数ある場合に、それらを group にまとめたいのだが、それが強く出ている channel と弱く出ている channel とがあると、euclid 距離を使う k-Means では別の group になってしまう可能性がある。cosine 距離を使えば、その問題は回避できるだろう。

しかし、理論的には Spectral Clustering を使えば cosine 距離での k-Means は実現できるのだが、実際には上手くいかない。Spectral Clustering は連結グラフであることが前提なのだが、普通に cosine 距離でラプラス行列を作っても連結グラフになることは保証できない。むしろ部分グラフが出来てしまうことの方が多い。

そこで、擬似的な cosine 距離で k-Means してしまおうというのが Cosine-CAM である。私が名付けた。

Tabular Data で k-Means する時は、standardize するべきである。しかし channel-position 平面を standardize すると、Saliency Map がぐちゃぐちゃになってしまう。経験則であって、原因はよく分かってないけど。（たぶん centerize が悪さをしているように思える）

そこで、channel space の横ベクトル（各々の channel に対応するベクトル）を L2-Norm が 1 になるように normalize してから k-Means することにした。これだとそこそこ良い感じになる。

長さ 1 のベクトルの euclid 距離をなぜ擬似的な cosine 距離と見做してよいかは、[ここ](https://qiita.com/tetutaro/items/ffbfce12d3d6bda48ffa#%E5%8D%98%E8%AA%9E%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E3%81%AE%E5%B9%B3%E5%9D%87%E3%81%A8%E3%81%AF)を参照のこと。

## 複数 Layer 対応

* Poly-CAM
    * A. Englebert, et al. "[Poly-CAM: High resolution class activation map for convolutional neural networks](https://arxiv.org/abs/2204.13359)" ICPR 2022.

複数の layer を使うモデルは、Zoom-CAM, Layer-CAM といくつかあるが、どれを実装しても上手くいかない。とりあえず両者の良い所どりをして作ってはみたが、どうにもイマイチである。

しかし、Poly-CAM はコンセプトから違い、これは上手くいく。

* 一番下位（最後に近い）Activation, Gradient で普通に Saliency Map を作る
* 上位の Activation を、局所的に normalize, upsampling することによって、より高解像度化する mask とする

# heatmap 一覧

上記のモデル（以外にも余分なものが含まれているけど）の heatmap を一覧にしたものが、以下。

犬（bull mastiff）の positive な Saliency Map を heatmap にしたものと、猫（tiger cat）の positive, negative 両方の Saliency Map を heatmap にしたもの。

![dog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/d7c3e7a9-c070-9d8a-1650-f181246b4150.png)

![cat.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/f9c05325-6740-436e-c0a2-d4739b6e0f24.png)

# 実装

ということで、以下に実装を載せておく。

https://github.com/tetutaro/class_activation_mapping