CNN 시각화

이전에는 Fashion MNIST 데이터셋 분류 모델을 CNN으로 구현해보았습니다.

이번 포스팅에서는 합성곱 계층을 시각화 해보겠습니다.


먼저 이전에 CNN으로 구현해서 저장해두었던 모델을 불러와서 모델의 계층을 확인해 보았습니다.

from tensorflow.keras.models import load_model

model = load_model('./models/fasion_mnist_cnn_model.h5')

# 모델의 계층 확인
model.layers
[<keras.layers.convolutional.Conv2D at 0x7fc642527280>,
 <keras.layers.pooling.MaxPooling2D at 0x7fc6421718b0>,
 <keras.layers.convolutional.Conv2D at 0x7fc641dd48b0>,
 <keras.layers.pooling.MaxPooling2D at 0x7fc6425958e0>,
 <keras.layers.core.flatten.Flatten at 0x7fc642596d60>,
 <keras.layers.core.dense.Dense at 0x7fc642598310>,
 <keras.layers.core.dropout.Dropout at 0x7fc642598df0>,
 <keras.layers.core.dense.Dense at 0x7fc642596490>]
model.layers
[<keras.layers.convolutional.Conv2D at 0x7fc642532e20>,
 <keras.layers.pooling.MaxPooling2D at 0x7fc642532220>,
 <keras.layers.convolutional.Conv2D at 0x7fc64252bc10>,
 <keras.layers.pooling.MaxPooling2D at 0x7fc64252bb50>,
 <keras.layers.core.flatten.Flatten at 0x7fc642528f10>,
 <keras.layers.core.dense.Dense at 0x7fc6425285b0>,
 <keras.layers.core.dense.Dense at 0x7fc642528a00>]

첫번째 층인 Conv2D 계층의 가중치를 확인하기 위해 모델의 layers속성에 0번째 인덱스로 접근해서 conv객체에 저장하겠습니다.

conv객체의 weights속성에는 그 계층의 필터와 가중치가 포함되어 있습니다.

(필터의 갯수만큼 가중치의 갯수가 정해집니다.)

conv = model.layers[0]
print(conv.weights[0].shape, conv.weights[1].shape)
(3, 3, 1, 32) (32,)

conv객체의 weights[0]은 필터의 가중치, weights[1]은 절편입니다.

크기를 출력해보면, 필터의 크기가 3x3x1(흑백 이미지이므로 채널은 1)이고, 32개의 필터가 사용되었음을 알 수 있습니다.

weights[1]에서 필터의 갯수가 32이므로 절편의 갯수도 32임을 확인할 수 있습니다.


층의 가중치 분포

이제 층의 가중치 분포를 시각화해보겠습니다.

conv객체의 weights[0]의 가중치를 numpy 배열로 변환하여 conv_weights 객체에 저장하고,

reshape을 사용해서 2번째 차원이 1이 되도록 즉, 열 벡터가 되도록 쭉 펼쳐서

히스토그램을 통해 시각화를 해봅니다.

import numpy as np
import matplotlib.pyplot as plt

conv_weights = conv.weights[0].numpy()
plt.hist(conv_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

# 훈련 전 모델
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

no_training_model = Sequential()
no_training_model.add(Conv2D(32, kernel_size=3, activation='relu', padding='same', input_shape=(28,28,1)))


no_training_conv = no_training_model.layers[0]
no_training_conv_weights = no_training_model.weights[0].numpy()

conv_weights = no_training_conv.weights[0].numpy()
plt.hist(no_training_conv_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

약 100개 정도의 원소가 0에 가까운 값을 가지고 있고, 약 20개 정도는 0.25 ~ 0.5의 값을 가지고 있습니다.

마이너스 값을 가진 원소들도 확인 할 수 있는데요.

학습하기전의 그래프 모양은 -부터 +까지 균등분포 형태를 가질것입니다.

학습 전에는 가중치가 무작위로 초기화되어 규칙성이 없기 때문입니다.

하지만, 학습을 마친 필터는 규칙성이 생기므로 이러한 분포를 나타냅니다.

큰 양의 값과 큰 음의 값은 입력 이미지에서 어떠한 특징을 감지하기 위한 값들이고,

0에 가까운 값일 수록 의미있는 값을 학습하는 가중치라고 할 수 없습니다.

층의 가중치 시각화

필터를 (3x3)크기로 32개 만큼 시각화 했습니다.

matplotlib의 imshow()는 배열의 값을 최솟값에 가까울수록 어둡게, 최댓값에 가까울수록 밝게 이미지로 출력해줍니다.

이 수치가 동일한 기준을 갖기 위해 vmin, vmax으로 설정했습니다.


가중치 시각화도 마찬가지로 학습하기 전은 흑백의 정도에 규칙성이 없이 균등하게 나타납니다.

하지만, 학습을 마친 필터에는 나름의 규칙성이 있는 이미지화가 되어 다음과 같이 출력됩니다.

fig, axs = plt.subplots(2, 16, figsize=(15, 2))

for i in range(2):
    for j in range(16):
        axs[i, j].imshow(conv_weights[:, :, 0, i * 16 + j], vmin=-0.5, vmax=0.5)
        axs[i, j].axis('off')
        
plt.show()

# 학습 전
fig, axs = plt.subplots(2, 16, figsize=(15, 2))

for i in range(2):
    for j in range(16):
        axs[i, j].imshow(no_training_conv_weights[:, :, 0, i * 16 + j], vmin=-0.5, vmax=0.5)
        axs[i, j].axis('off')
        
plt.show()

합성곱의 특성맵 시각화

특성맵을 시각화 하기위해 함수형API를 사용하여 모델을 구축해야합니다.

이전에는 Sequential클래스를 사용했지만, 보다 복잡한 모델(다중 입력 또는 다중 출력)을 구성하기 위해서는

함수형API를 사용합니다.


  • 함수형 API

    케라스에서 가장 권장하는 방법 중 하나입니다.

    복잡한 모델을 유연하게 구성할 수 있습니다.

    다중 입력, 출력이 가능합니다.



from tensorflow.keras.models import Model

from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, GlobalAveragePooling2D

from tensorflow.keras.layers import Input

from tensorflow.keras.utils import plot_model



# 함수형 API는 Input층을 통해서 입력값의 형태를 정합니다.

inputs = Input(shape=(28, 28, 3))

x = Conv2D(32, kernel_size=3, activation='relu', padding='same')(inputs)

x = Conv2D(32, kernel_size=3, activation='relu', padding='same')(x)

x = MaxPooling2D(2)(x)

x = GlobalAveragePooling2D()(x)

x = Dense(10, activation='softmax')(x)



# 위에서 정의된 층을 포함하고 있는 모델을 생성합니다.

model = Model(inputs=inputs, outputs=x)

첫 번째 특성 맵 시각화

첫번째 Conv층의 특성맵을 시각화하기 위해 앞서 기존에 만든 model의 중간 부분만 뽑아오겠습니다.

model의 Input Layer를 입력으로, model의 첫번째 Conv층을 출력으로 지정해서 새로운 모델을 생성했습니다.


conv_acti에 임의로 하나의 데이터를 입력해서 특성맵을 뽑아보았습니다.

from tensorflow.keras.models import Model

conv_acti = Model(model.input, model.layers[0].output)

inputs = X_train[0:1].reshape(-1, 28, 28, 1) / 255.0  # X_train의 (1, 28, 28, 1)
feature_map = conv_acti.predict(inputs)

fig, axs = plt.subplots(4, 8, figsize=(15, 8))
for i in range(4):
    for j in range(8):
        axs[i, j].imshow(feature_map[0, :, :, i * 8 + j])
        axs[i, j].axis('off')
plt.show()

두 번째 특성 맵 시각화

첫 번째 특성 맵을 시각화할때 사용한 input을 그대로 사용해서 두 번째 합성곱층의 특성 맵을 시각화해보았습니다.

두 번째 합성곱 층은 아래의 결과를 보았을 때, 어떤 부분을 학습하는 지 유추하기 어렵습니다.

conv_acti2 = Model(model.input, model.layers[2].output)
feature_map = conv_acti2.predict(inputs)

fig, axs = plt.subplots(8, 8, figsize=(15, 8))
for i in range(8):
    for j in range(8):
        axs[i, j].imshow(feature_map[0, :, :, i * 8 + j])
        axs[i, j].axis('off')
plt.show()

따라서, 낮은 층에서 저수준 특성(눈에 보이는 확실한 패턴)을 학습하고, 층이 깊어질수록 고수준 특성(추상적인 패턴)을

학습함을 알 수 있습니다.

댓글남기기