이번에는 이전 글에서 합성곱 층이 이미지에서 어떤 것을 학습했는지 알아보기 위해 합성곱 층의 가중치와 특성 맵을 그림으로 시각화해 보겠다.
이 과정을 통해 합성곱 신경망의 동작 원리에 대한 통찰을 키울 수 있다.
가중치 시각화
합성곱 층은 여러 개의 필터(커널)을 사용해 이미지의 특징을 학습한다.
각 필터는 가중치와 절편을 가진다.
- 가중치 : 어떤 특징을 크게 두드러지게 표현함.
- 절편 : 시각적으로 의미 없음
이제 모델이 어떤 가중치를 학습했는지 확인하기 위해 체크포인트 파일을 읽어 들이겠다.
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개의 클래스를 찾아낼 수 있는 이유이다.
'AI > 딥러닝(Deep Learning)' 카테고리의 다른 글
[딥러닝/DL]8. 순환 신경망으로 IMDB리뷰 분류하기 (1) | 2022.12.08 |
---|---|
[딥러닝/DL]7. 순차 데이터와 순환 신경망 (1) | 2022.12.08 |
[딥러닝/DL]5. 합성곱 신경망을 사용한 이미지 분류 (1) | 2022.12.08 |
[딥러닝/DL]4. 합성곱 신경망 - 구성요소 (0) | 2022.12.07 |
[딥러닝/DL]3. 신경망 모델 훈련 (0) | 2022.12.07 |