Baseline of CAM
学習目標
CAMは分類モデルから得ることができる.
CAMで物体の位置を見つけることができます.
CAMを視覚的に比較できます.
学習内容
CAM、Grad-CAMのモデルを準備する
CAM
Grad-CAM
Detection with CAM
CAMは、フィーチャー抽出CNNネットワークの後ろに、GAPとsoftmax layerが接続されている必要がありますが、Grad-CAMはありません.
このセクションでは、CAM、Grad-CAMを実装し、まずCAMモデルを実装し、このモデルを使用してGrad-CAMから可視化結果を抽出します.
CAMはクラスの活性化度合いを表し、デフォルトモデルは分類を実行します.
ただし、最終的な目標は、位置を特定し、正解と比較し、位置情報を記録したデータを含むことである.
Tensorflow Datasets
Standford dogsデータセットの使用 120種写真判別 分類問題データセット 金庫:ラベルこの位置情報 必要なライブラリの読み込み
ラーニングデータセット:12000ページ
評価用データセット:8580
データのダウンロード
フィーチャーのチェック
image:画像の入力
Label:画像の正しいクラスインデックス
bbox:バインドボックス、物体の位置を矩形領域とマークする
属性のチェック
xywh
固定枠の中心をx,y、小幅w,hで表す.
ex:(x_center, y_center, width, height)
x,yは、中心点ではなく左上隅の点を指す可能性がある.
minmax
バインドボックスを構成する座標の最上位値と最上位値をマークします.
ex: (x_min, x_max, y_min, y_max)
これは座標の屈折値ではなく、画像全体の幅と高さを基準に正規化された相対値タグです.
BBoxFeature
このバインドボックスはminmaxを表し、tfdsは高さを最初の軸([ymin、xmin、ymx、xmax)として表します.
CAMのモデルの作成
イメージデータ:ImageNet
トレーニングモデル:ResNet 50
Grad-CAMモデルは、プール層+Softmax層(アクティブなアクティブ関数、fc層)から作成されます.
実装を試みる
CAMモデル構造
分類モードに類似
プール="avg"FC層ではなく(GAP計算可能)
normalize_and_resize_img() シーク湿潤データと検証データの簡単な前処理(正規化、スケーリング) apply_normalize_on_dataset()
配置の設定
input bbox情報を含む
りようりつ
ダイレクトラベル(バインドボックス)を無効にする 略図により間接学習物体領域を学習する.
メモリが不足する場合がありますので、カーネルを再起動してから試してください.
プロパティマッピング
ソフトMax層の重み(クラス確率を得る)
必要なクラスの出力値
モデルがどのように入力画像を表示するかを直感的に理解するために,CAMを入力サイズと同じにすることで可視化する.
したがって、モデルとアイテムを受け取ると、入力画像と同じサイズのCAMを返す関数を作成する必要があります.
generate cam()を実装するために,レイヤの結果値を出力として新しいモデルを定義し,フィードフォワード後にCAMを計算することを実装した.
最後に,CAMを入力画像の大きさに調整した.
Grad-CAMでは、適用モデルを柔軟に選択し、作成したモデルを再利用できるため、モデルを観察してもcam modelを使用できます.
新しいイメージを選びます
grad camは,観察が必要な層と正解クラスの予測値との間でグラデーションを求め,GAP演算を適用して各層の重み付けを求めるために用いられる.
そして、各チャンネルの重みと特性マッピングを重み付けてcamイメージを得る.これが最終CAMの画像である.
CAMとは異なり、Grad-CAMは、グラデーション計算を行う観察層activation layerを抽出することによってCAM画像を書き込むためにactivation layerの名前を受け入れて使用します.
次にgenerate grad cam()は、所望のレイヤの出力と特定のクラスの予測との間のグラデーションを取得し、重みとして使用する.
セーフティボックス
CAMで物体位置の検出をします.
新しいイメージを選びます
generate cam():CAM画像を抽出
get bbox():バインドボックスを作成する関数 score threshold:しきい値以下のバインドボックス をクリアする.
OpenCV利用 ConTours()とminAreaReac()の検索:長方形 の検索
回転rect():回転のバインドボックスを取得する
boxpoints():頂点に切り替えます.
intデータ型に変換します.
バインドボックスは、CAMとGrad-CAMの2つの方法で取得できます.
IOUは、2つの領域の交差領域の幅を合計する.これにより、面積の大きさにかかわらず、最適なゼロが見つかったかどうかと記録の相対パーセントを得ることができます.
CAMは分類モデルから得ることができる.
CAMで物体の位置を見つけることができます.
CAMを視覚的に比較できます.
学習内容
CAM、Grad-CAMのモデルを準備する
CAM
Grad-CAM
Detection with CAM
! mkdir -p ~/aiffel/class_activation_map
CAM、Grad-CAMのモデルを準備するCAMは、フィーチャー抽出CNNネットワークの後ろに、GAPとsoftmax layerが接続されている必要がありますが、Grad-CAMはありません.
このセクションでは、CAM、Grad-CAMを実装し、まずCAMモデルを実装し、このモデルを使用してGrad-CAMから可視化結果を抽出します.
CAMはクラスの活性化度合いを表し、デフォルトモデルは分類を実行します.
ただし、最終的な目標は、位置を特定し、正解と比較し、位置情報を記録したデータを含むことである.
Tensorflow Datasets
Standford dogsデータセットの使用
# TensorFlow and tf.keras
import tensorflow as tf
from tensorflow import keras
# Helper libraries
import numpy as np
import matplotlib.pyplot as plt
import tensorflow_datasets as tfds
import copy
import cv2
from PIL import Image
print('슝=3')
GPUの検証|tf.config.list_physical_devices('GPU')
Stanford dogsデータセットラーニングデータセット:12000ページ
評価用データセット:8580
# 최초 수행시에는 다운로드가 진행됩니다. 오래 걸릴 수 있으니 유의해 주세요.
(ds_train, ds_test), ds_info = tfds.load(
'stanford_dogs',
split=['train', 'test'],
shuffle_files=True,
with_info=True,
)
print('슝=3')
ラーニングデータの確認tfds.show_examples(ds_train, ds_info)
評価データの確認tfds.show_examples(ds_test, ds_info)
オブジェクトの位置についてフィーチャーのチェック
image:画像の入力
Label:画像の正しいクラスインデックス
bbox:バインドボックス、物体の位置を矩形領域とマークする
属性のチェック
ds_info.features
バインドボックスの表示方法xywh
固定枠の中心をx,y、小幅w,hで表す.
ex:(x_center, y_center, width, height)
x,yは、中心点ではなく左上隅の点を指す可能性がある.
minmax
バインドボックスを構成する座標の最上位値と最上位値をマークします.
ex: (x_min, x_max, y_min, y_max)
これは座標の屈折値ではなく、画像全体の幅と高さを基準に正規化された相対値タグです.
BBoxFeature
このバインドボックスはminmaxを表し、tfdsは高さを最初の軸([ymin、xmin、ymx、xmax)として表します.
CAMのモデルの作成
イメージデータ:ImageNet
トレーニングモデル:ResNet 50
Grad-CAMモデルは、プール層+Softmax層(アクティブなアクティブ関数、fc層)から作成されます.
CAMモデル構造
分類モードに類似
プール="avg"FC層ではなく(GAP計算可能)
num_classes = ds_info.features["label"].num_classes
base_model = keras.applications.resnet50.ResNet50(
include_top=False, # Imagenet 분류기 fully connected layer 제거
weights='imagenet',
input_shape=(224, 224,3),
pooling='avg', # GAP를 적용
)
x = base_model.output
preds = keras.layers.Dense(num_classes, activation='softmax')(x)
cam_model = keras.Model(inputs=base_model.input, outputs=preds)
cam_model.summary()
CAMモデルの学習配置の設定
input
りようりつ
def normalize_and_resize_img(input):
# Normalizes images: `uint8` -> `float32`
image = tf.image.resize(input['image'], [224, 224])
input['image'] = tf.cast(image, tf.float32) / 255.
return input['image'], input['label']
def apply_normalize_on_dataset(ds, is_test=False, batch_size=16):
ds = ds.map(
normalize_and_resize_img,
num_parallel_calls=2
)
ds = ds.batch(batch_size)
if not is_test:
ds = ds.repeat()
ds = ds.shuffle(200)
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)
return ds
print('슝=3')
# 데이터셋에 전처리와 배치처리를 적용합니다.
ds_train_norm = apply_normalize_on_dataset(ds_train)
ds_test_norm = apply_normalize_on_dataset(ds_test)
# 구성된 배치의 모양을 확인해 봅니다.
for input in ds_train_norm.take(1):
image, label = input
print(image.shape)
print(label.shape)
tf.random.set_seed(2021)
cam_model.compile(
loss='sparse_categorical_crossentropy',
optimizer=tf.keras.optimizers.SGD(lr=0.01),
metrics=['accuracy'],
)
print('슝=3')
history_cam_model = cam_model.fit(
ds_train_norm,
steps_per_epoch=int(ds_info.splits['train'].num_examples/16),
validation_steps=int(ds_info.splits['test'].num_examples/16),
epochs=3,
validation_data=ds_test_norm,
verbose=1,
use_multiprocessing=True,
)
学習済みウェイトの保存import os
cam_model_path = os.getenv('HOME')+'/aiffel/class_activation_map/cam_model.h5'
cam_model.save(cam_model_path)
print("저장 완료!")
CAMメモリが不足する場合がありますので、カーネルを再起動してから試してください.
# 커널 재시작 이후 실습을 위해, 이전 스텝의 코드를 모아서 한꺼번에 실행합니다.
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
import tensorflow_datasets as tfds
import copy
import cv2
from PIL import Image
(ds_train, ds_test), ds_info = tfds.load(
'stanford_dogs',
split=['train', 'test'],
shuffle_files=True,
with_info=True,
)
def normalize_and_resize_img(input):
# Normalizes images: `uint8` -> `float32`
image = tf.image.resize(input['image'], [224, 224])
input['image'] = tf.cast(image, tf.float32) / 255.
return input['image'], input['label']
def apply_normalize_on_dataset(ds, is_test=False, batch_size=16):
ds = ds.map(
normalize_and_resize_img,
num_parallel_calls=2
)
ds = ds.batch(batch_size)
if not is_test:
ds = ds.repeat()
ds = ds.shuffle(200)
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)
return ds
print('슝=3')
CAMの作成はバッチ単位ではなく、単一の画像データ単位で行われるため、get one()関数で個々に抽出される.def get_one(ds):
ds = ds.take(1)
sample_data = list(ds.as_numpy_iterator())
bbox = sample_data[0]['objects']['bbox']
image = sample_data[0]['image']
label = sample_data[0]['label']
return sample_data[0]
print('슝=3')
データの準備が整いました.前のステップで学習したモデルにロードしてCAMを作成します.item = get_one(ds_test)
print(item['label'])
plt.imshow(item['image'])
plt.show()
import os
cam_model_path = os.getenv('HOME')+'/aiffel/class_activation_map/cam_model.h5'
cam_model = tf.keras.models.load_model(cam_model_path)
print('슝=3')
CAM作成条件プロパティマッピング
ソフトMax層の重み(クラス確率を得る)
必要なクラスの出力値
したがって、モデルとアイテムを受け取ると、入力画像と同じサイズのCAMを返す関数を作成する必要があります.
generate cam()を実装するために,レイヤの結果値を出力として新しいモデルを定義し,フィードフォワード後にCAMを計算することを実装した.
最後に,CAMを入力画像の大きさに調整した.
def generate_cam(model, item):
item = copy.deepcopy(item)
width = item['image'].shape[1]
height = item['image'].shape[0]
img_tensor, class_idx = normalize_and_resize_img(item)
# 학습한 모델에서 원하는 Layer의 output을 얻기 위해서 모델의 input과 output을 새롭게 정의해줍니다.
# model.layers[-3].output에서는 우리가 필요로 하는 GAP 이전 Convolution layer의 output을 얻을 수 있습니다.
cam_model = tf.keras.models.Model([model.inputs], [model.layers[-3].output, model.output])
conv_outputs, predictions = cam_model(tf.expand_dims(img_tensor, 0))
conv_outputs = conv_outputs[0, :, :, :]
class_weights = model.layers[-1].get_weights()[0] #마지막 모델의 weight activation을 가져옵니다.
cam_image = np.zeros(dtype=np.float32, shape=conv_outputs.shape[0:2])
for i, w in enumerate(class_weights[:, class_idx]):
# W * f 를 통해 class별 activation map을 계산합니다.
cam_image += w * conv_outputs[:, :, i]
cam_image /= np.max(cam_image) # activation score를 normalize합니다.
cam_image = cam_image.numpy()
cam_image = cv2.resize(cam_image, (width, height)) # 원래 이미지의 크기로 resize합니다.
return cam_image
print('슝=3')
正確な位置表示を実現できます.cam_image = generate_cam(cam_model, item)
plt.imshow(cam_image)
plt.show()
CAMイメージを元のイメージに合わせてみましょう.def visualize_cam_on_image(src1, src2, alpha=0.5):
beta = (1.0 - alpha)
merged_image = cv2.addWeighted(src1, alpha, src2, beta, 0.0)
return merged_image
print('슝=3')
origin_image = item['image'].astype(np.uint8)
cam_image_3channel = np.stack([cam_image*255]*3, axis=-1).astype(np.uint8)
blended_image = visualize_cam_on_image(cam_image_3channel, origin_image)
plt.imshow(blended_image)
plt.show()
Grad-CAMGrad-CAMでは、適用モデルを柔軟に選択し、作成したモデルを再利用できるため、モデルを観察してもcam modelを使用できます.
新しいイメージを選びます
item = get_one(ds_test)
print(item['label'])
plt.imshow(item['image'])
plt.show()
Grad-CAMを使用したCAMの作成grad camは,観察が必要な層と正解クラスの予測値との間でグラデーションを求め,GAP演算を適用して各層の重み付けを求めるために用いられる.
そして、各チャンネルの重みと特性マッピングを重み付けてcamイメージを得る.これが最終CAMの画像である.
CAMとは異なり、Grad-CAMは、グラデーション計算を行う観察層activation layerを抽出することによってCAM画像を書き込むためにactivation layerの名前を受け入れて使用します.
次にgenerate grad cam()は、所望のレイヤの出力と特定のクラスの予測との間のグラデーションを取得し、重みとして使用する.
def generate_grad_cam(model, activation_layer, item):
item = copy.deepcopy(item)
width = item['image'].shape[1]
height = item['image'].shape[0]
img_tensor, class_idx = normalize_and_resize_img(item)
# Grad cam에서도 cam과 같이 특정 레이어의 output을 필요로 하므로 모델의 input과 output을 새롭게 정의합니다.
# 이때 원하는 레이어가 다를 수 있으니 해당 레이어의 이름으로 찾은 후 output으로 추가합니다.
grad_model = tf.keras.models.Model([model.inputs], [model.get_layer(activation_layer).output, model.output])
# Gradient를 얻기 위해 tape를 사용합니다.
with tf.GradientTape() as tape:
conv_output, pred = grad_model(tf.expand_dims(img_tensor, 0))
loss = pred[:, class_idx] # 원하는 class(여기서는 정답으로 활용) 예측값을 얻습니다.
output = conv_output[0] # 원하는 layer의 output을 얻습니다.
grad_val = tape.gradient(loss, conv_output)[0] # 예측값에 따른 Layer의 gradient를 얻습니다.
weights = np.mean(grad_val, axis=(0, 1)) # gradient의 GAP으로 class별 weight를 구합니다.
grad_cam_image = np.zeros(dtype=np.float32, shape=conv_output.shape[0:2])
for k, w in enumerate(weights):
# 각 class별 weight와 해당 layer의 output을 곱해 class activation map을 얻습니다.
grad_cam_image += w * output[:, :, k]
grad_cam_image /= np.max(grad_cam_image)
grad_cam_image = grad_cam_image.numpy()
grad_cam_image = cv2.resize(grad_cam_image, (width, height))
return grad_cam_image
print('슝=3')
複数層のCAM画像を抽出します.grad_cam_image = generate_grad_cam(cam_model, 'conv5_block3_out', item)
plt.imshow(grad_cam_image)
plt.show()
grad_cam_image = generate_grad_cam(cam_model, 'conv4_block3_out', item)
plt.imshow(grad_cam_image)
plt.show()
grad_cam_image = generate_grad_cam(cam_model, 'conv3_block3_out', item)
plt.imshow(grad_cam_image)
plt.show()
Detection with CAMセーフティボックス
CAMで物体位置の検出をします.
新しいイメージを選びます
item = get_one(ds_test)
print(item['label'])
plt.imshow(item['image'])
plt.show()
cam_image = generate_cam(cam_model, item)
plt.imshow(cam_image)
plt.show()
generate cam():CAM画像を抽出
get bbox():バインドボックスを作成する関数
OpenCV利用
回転rect():回転のバインドボックスを取得する
boxpoints():頂点に切り替えます.
intデータ型に変換します.
def get_bbox(cam_image, score_thresh=0.05):
low_indicies = cam_image <= score_thresh
cam_image[low_indicies] = 0
cam_image = (cam_image*255).astype(np.uint8)
contours,_ = cv2.findContours(cam_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
rotated_rect = cv2.minAreaRect(cnt)
rect = cv2.boxPoints(rotated_rect)
rect = np.int0(rect)
return rect
print('슝=3')
bboxはcamイメージで取得され、画像上で可視化される.rect = get_bbox(cam_image)
rect
image = copy.deepcopy(item['image'])
image = cv2.drawContours(image, [rect], 0, (0,0,255), 2)
plt.imshow(image)
plt.show()
Intersection Over Union(IOU)バインドボックスは、CAMとGrad-CAMの2つの方法で取得できます.
IOUは、2つの領域の交差領域の幅を合計する.これにより、面積の大きさにかかわらず、最適なゼロが見つかったかどうかと記録の相対パーセントを得ることができます.
rect의 좌표는 (x, y) 형태로, bbox는 (y_min, x_min, y_max, x_max)의 normalized 형태로 주어집니다.
def rect_to_minmax(rect, image):
bbox = [
rect[:,1].min()/float(image.shape[0]), #bounding box의 y_min
rect[:,0].min()/float(image.shape[1]), #bounding box의 x_min
rect[:,1].max()/float(image.shape[0]), #bounding box의 y_max
rect[:,0].max()/float(image.shape[1]) #bounding box의 x_max
]
return bbox
print('슝=3')
rectをminmax bboxに変換します.pred_bbox = rect_to_minmax(rect, item['image'])
pred_bbox
データのground truth bboxを確認します.item['objects']['bbox']
IOUを計算して両者の類似度を決定する.def get_iou(boxA, boxB):
y_min = max(boxA[0], boxB[0])
x_min= max(boxA[1], boxB[1])
y_max = min(boxA[2], boxB[2])
x_max = min(boxA[3], boxB[3])
interArea = max(0, x_max - x_min) * max(0, y_max - y_min)
boxAArea = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1])
boxBArea = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1])
iou = interArea / float(boxAArea + boxBArea - interArea)
return iou
print('슝=3')
get_iou(pred_bbox, item['objects']['bbox'][0])
Reference
この問題について(Baseline of CAM), 我々は、より多くの情報をここで見つけました https://velog.io/@qsdcfd/Baseline-of-CAMテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol