본문 바로가기

AI/딥러닝(Deep Learning)

[딥러닝/DL]6. 합성곱 신경망의 시각화

728x90
반응형

이번에는 이전 글에서 합성곱 층이 이미지에서 어떤 것을 학습했는지 알아보기 위해 합성곱 층의 가중치특성 맵을 그림으로 시각화해 보겠다.

이 과정을 통해 합성곱 신경망의 동작 원리에 대한 통찰을 키울 수 있다.

 

가중치 시각화

합성곱 층은 여러 개의 필터(커널)을 사용해 이미지의 특징을 학습한다.

각 필터는 가중치와 절편을 가진다.

  • 가중치 : 어떤 특징을 크게 두드러지게 표현함.
  • 절편 : 시각적으로 의미 없음

이제 모델이 어떤 가중치를 학습했는지 확인하기 위해 체크포인트 파일을 읽어 들이겠다.

model = keras.models.load_model('best-cnn-model.h5')
model.layers

layers 속성을 통해 모델의 층을 볼 수 있다.

 

첫번쨰 합성곱의 가중치를 조사해보자.

가중치와 철편은 층의 weights 속성에 저장되어있다.

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

이전에 전달되는 입력의 깊이가 1이므로 커널의 크기가 (3, 3, 1)이고 필터 개수가 32이므로 가중치는 (3, 3, 1, 32)이고 절편은 필터마다 1개씩 있으므로 (32, )이다. 

 

가중치의 평균과 표준편차를 구해보자.

conv_weights = conv.weights[0].numpy()

print("가중치 평균 :",conv_weights.mean())  
print("가중치 표준편차 :",conv_weights.std())

가중치의 분포를 알아보기 위해 히스토그램을 그려보자

import matplotlib.pyplot as plt
plt.hist(conv_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

 

이번에는 32개의 필터(커널)을 16개 씩 두줄에 출력해 보자.

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()

이제 비교를 위해서 훈련을 하지 않은 빈 합성곱 신경망을 만들어보자.

no_training_model = keras.Sequential()

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

no_training_conv = no_training_model.layers[0]
print(no_training_conv.weights[0].shape)

같은 가중치이다.

이제 이 가중치의 평균과 표준 편차를 출력하자.

no_training_weights = no_training_conv.weights[0].numpy()
print("가중치 평균 :",no_training_weights.mean())  
print("가중치 표준편차 :",no_training_weights.std())

이 가중치 배열을 히스토그램으로 출력해보자.

plt.hist(no_training_weights.reshape(-1, 1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()

대부분의 가중치가 -0.15 ~ 0.15 사이에서 비교적 균등하게 분포되어 있다.

그 이유는 텐서플로가 신경망의 가중치를 처음 초기화 시 균등 분포에서 랜덤하게 값을 선택하기 때문이다.

 

이번에는 필터(커널)을 출력해보자.

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_weights[:,:,0,i*16 + j], vmin=-0.5, vmax=0.5)
        axs[i, j].axis('off')

plt.show()

전체적으로 가중치가 밋밋하게 초기화 되었다.

 

함수형 API

여태 까지 우리는 신경망 모델을 만들때 Sequential 클래스를 사용했다.

  • Sequential은 층을 차례대로 쌓는다.
  • Sequential은 자동으로 InputLayer 클래스를 추가한다.

InputLayer 클래스는 입력층이다.

  • 함수형 API는 InputLayer 따로 만들어서 넣어줘야 한다.
  • 케라스는 InputLayer를 만들 수 있는 Input() 함수를 지원한다.
  • Input() 함수는 shape 매개변수에 입력 형태를 넣어준다.

모델의 입력은 input 속성을 통해 알 수 있다.

print(model.input)

 

하지만 딥러닝에는 복잡한 모델이 많이 존재한다.

예를 들어 입력이 2개 이거나 출력이 2개인 경우도 존재한다.

이럴때 사용하는 방법이 함수형 API(Functional API)이다.

 

예1 - Dense 층 2개

inputs = keras.Input(shape=(784,))
dense1 = keras.layers.Dense(100,activation='sigmoid')
dense2 = keras.layers.Dense(10,activation='softmax')

hidden = dense1(inputs)
outputs = dense2(hidden)

model = keras.Model(inputs,outputs)
model.summary()

Model 클래스는 입력과 출력을 매개변수로 받아서 모델을 완성한다.

 

예2 - 두개의 신경망을 결합

# ANN1
inputs1 = keras.Input(shape=(784,))
hidden1 = keras.layers.Dense(200, activation='relu')(inputs1)
outputs1 = keras.layers.Dense(100, activation='relu')(hidden1)

# ANN2
inputs2 = keras.Input(shape=(784,))
hidden2 = keras.layers.Dense(100, activation='relu')(inputs2)
outputs2 = keras.layers.Dense(50, activation='relu')(hidden2)

# 결합
added = keras.layers.Concatenate()([outputs1, outputs2])

# ANN3
hidden_final = keras.layers.Dense(20,activation='relu')(added)
output_final = keras.layers.Dense(10,activation='softmax')(hidden_final)

# 모델 생성
model_joined = keras.Model( [inputs1,inputs2], output_final)
# 모델 출력
keras.utils.plot_model(model_joined)

 

층을 생성할 때 뒤에 "(층이 들어가있는 변수)"를 주면 첫번째 예제에서 함수에 매개변수로 전달하는 것과 같은 기능을 한다. 

Concatenate 클래스로 두 신경망을 결합할 수 있다.

 

특성 맵 시각화

먼저 케라스로 패션 MNIST 데이터셋을 읽은 후 훈련 세트에 있는 첫 샘플을 시각화 해보겠다.

(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
plt.imshow(train_input[0], cmap='gray_r')
plt.show()

훈련된 합성곱 모델의 첫번째 합성곱 층만 떼어 모델 생성

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

이제 샘플을 conv_acti 모델에 주입하여 Conv2D 층이 만드는 특성 맵을 출력해보자.

inputs = train_input[0:1].reshape(-1, 28, 28, 1)/255.0

feature_maps = conv_acti.predict(inputs)
print(feature_maps.shape)

(배치차원, X, Y, 깊이)

fig, axs = plt.subplots(4, 8, figsize=(15,8))

for i in range(4):
    for j in range(8):
        axs[i, j].imshow(feature_maps[0,:,:,i*8 + j])
        axs[i, j].axis('off')

plt.show()

이 특성 맵은 32개의 필터로 인해 입력 이미지에서 강하게 활성화된 부분을 보여준다.

앞에서의 가중치 필터와 비교를 해보자.

ex.

  • 첫번쨰 필터는 평행선을 감지한다.
  • 다섯번쨰 필터는 전면이 모두 칠해진 영역을 감지한다.

두번째 합성곱 층이 만든 특성 맵도 같은 방식으로 확인할 수 있다.

conv2_acti = keras.models.Model(model.input, model.layers[2].output)
feature_maps = conv2_acti.predict(train_input[0:1].reshape(-1, 28, 28, 1)/255.0)
print(feature_maps.shape)
fig, axs = plt.subplots(8, 8, figsize=(12,12))

for i in range(8):
    for j in range(8):
        axs[i, j].imshow(feature_maps[0,:,:,i*8 + j])
        axs[i, j].axis('off')

plt.show()

이 특성맵은 시각적으로 이해하기가 어렵다.

왜 이런 결과가 나올까?

  • 두 번째 합성곱 층의 필터는 (3, 3, 32)이고 첫번쨰 필터가 앞서 출력한 32개의 특성 맵과 곱해져 두 번째 합성곱 층의 첫 번째 특성 맵이 된다.
  • 밑의 그림처럼 계산된 출력은 (14, 14, 32) 특성 맵에서 어떤 부위를 감지하는지 직관적으로 이해하기 어렵다.

이러한 현상은 합성곱 층을 많이 쌓을 수록 심해진다.

  • 합성곱 신경망의 앞부분에 있는 합성곱 층은 이미지의 시각적인 정보를 감지
  • 합성곱 신경망의 뒷부분에 있는 합성곱 층은 앞쪽에서 감지한 시각적인 정보를 바탕으로 추상적인 정보를 학습한다.

이 것이 바로 합성곱 신경망이 MNIST 이미지를 인식하여 10개의 클래스를 찾아낼 수 있는 이유이다.

728x90
반응형