Chapter 8. 이미지를 위한 인공 신경망

김영빈·2022년 9월 7일
0

혼공머신

목록 보기
7/7
post-thumbnail

8-1 합성곱 신경망의 구성 요소

학습 목표

  • 합성곱 신경망을 구성하는 기본 개념과 동작 원리를 배우고 간단한 합성곱, 풀링 계산 방법을 익힙니다.

1) 합성곱 층이란?

📕 완전 연결 신경망 밀집층(dense)의 뉴런

  • 인공 신경망에서는 가중치와 절편을 랜덤하게 초기화한 다음 에포크를 반복하면서 경사 하강법 알고리즘을 사용하여 손실이 낮아지도록 최적의 가중치와 절편을 찾아간다.
    이것을 모델 훈련이라고 한다.
    ex) 밀집층에 뉴런이 3개 있다면 출력은 입력 개수와 상관없이 뉴런의 개수를 따라 3개이다.
    <dense의 뉴런 출력 과정>

📕 합성곱 층의 뉴런

  • 임의로 정한 가중치의 개수 만큼 특성과 곱해져 한개의 출력을 만들고,
    한 칸 이동하여 새로운 출력을 만든다.
  • 중요한 것은 첫번째 합성곱에 사용된 가중치와 절편이 두번째부터 끝까지 모든 합성곱에도 동일하게 적용되어야 한다는 점이다.
    <convolutional의 뉴런 출력 과정>
    <convolutional의 뉴런 출력 결과>
  • 합성곱 신경망(convolutional neural network, CNN)에서는 완전 연결 신경망과 달리
    뉴런을 filter 혹은 kernel이라고 부른다.

  • 2차원에서도 합성곱층 적용이 가능하다.
    입력이 2차원 배열이면 필터도 2차원이여야 한다.
    <2차원 배열 합성곱 출력 과정>

  • 필터가 이동 가능한 횟수만큼 출력을 만들어낼 수 있고,
    이러한 합성곱 계산을 통해 얻은 출력을 특성 맵(feature map)이라고 한다.
    <2차원 배열 합성곱 특성 맵 출력>

  • 합성곱 층에 있는 필터의 가중치(커널)을 다르게 하여 여러개의 특성 맵(출력)을 얻을 수 있다.
    <여러개의 특성 맵으로 만들어지는 출력층>

2) 케라스 합성곱 층 생성

📕 케라스 합성곱 층

  • 케라스 합성곱 층은 Conv2D 클래스로 제공된다.(왼쪽에서 오른쪽으로, 위에서 아래로 이동)
  • 매개변수
    1) 필터의 개수(출력의 개수)
    2) 필터에 사용할 커널 크기(kernel_size = ), 보통 (3,3)이나 (5,5)가 권장됨
    3) 활성화 함수(activation = )
from tensorflow import keras
keras.layers.Conv2D(10, kernel_size=(3,3), activation='relu')
▶
<keras.layers.convolutional.Conv2D at 0x7faa3e808990>

📕 패딩(padding)

  • (4,4) 크기의 입력에 (3,3) 크기의 커널을 활용하여 출력의 크기를 (2,2)가 아닌 (4,4)로 만드려면?
    - 실제 입력 크기는 (4,4)이지만 입력 배열의 주위를 가상의 원소를 만들고 0으로 채워서 (6,6)인 입력으로 만든다.

  • 이 과정을 패딩(padding)이라고 한다.
    <패딩을 활용한 특성 맵 출력>

  • 패딩의 종류
    - 세임 패딩(same padding) : 입력과 특성 맵의 크기를 동일하게 만들기 위해 입력 주위에 0 으로 패딩하는것. 합성곱 신경망에서는 이 방법이 자주 사용됨
    - 밸리드 패딩(valid padding) : 패딩없이 순수 입력 배열에서만 합성곱을 하여 특성 맵을 만드는 방법으로 특성 맵의 크기가 줄어든다.

  • 패딩을 사용하는 이유?
    - 만약 패딩이 없다면 입력의 모서리 값은 필터를 통과하는 경우가 중앙 값에 비해 현저히 적기때문에 패딩을 통해 모서리에 있는 정보도 특성 맵으로 잘 전달될 수 있도록 해준다. 적절한 패딩은 이미지의 가장자리에 있는 정보를 잃어버리지 않도록 도와준다.
    <패딩을 사용하여 모서리 정보의 필터 통과 횟수를 증가시킴>

  • 케라스를 통한 패딩 사용

keras.layers.Conv2D(10, kernel_size=(3,3), activation='relu', padding='same')
▶
<keras.layers.convolutional.Conv2D at 0x7faa3d4c7c90>

📕 스트라이드(stride)

  • 커널의 이동을 한칸에서 두칸으로 늘리면 그만큼 특성맵의 크기도 작아진다.
    이를 스트라이드라고 한다.
# stride의 기본값은 1이다.
keras.layers.Conv2D(10, kernel_size=(3,3), activation='relu', padding='same', strides=1)
▶
<keras.layers.convolutional.Conv2D at 0x7faa3d25f110>
  • 커널의 이동 크기를 (1,1)과 같이 튜플을 사용해 각각 지정할 수 있으나,
    커널의 이동 크기를 가로세로 방향으로 다르게 지정하는 경우는 거의 없으며,
    1보다 큰 스트라이드를 사용하는 경우도 드물다.

📕 풀링(pooling)

  • 풀링(pooling)은 합성곱 층에서 만든 특성 맵의 가로세로 크기를 줄이는 역할을 한다. 하지만 특성맵의 개수는 줄이지 않는다.
    <풀링을 포함한 합성곱 층 생성 과정>

  • 풀링의 종류
    - 풀링 또한 합성곱처럼 입력 위를 지나가면서 필터링을 한다.
    하지만 풀링에는 가중치가 없으며 필터링 영역에서 특정 값을 고른다.
    - 최대 풀링(max pooling) : 가장 큰 값을 선택
    keras.layers.MaxPooling2D()로 사용
    - 평균 풀링(average pooling) : 평균 값을 선택
    keras.layers.AveragePooing2D()로 사용,
    평균을 통해 특성 맵의 중요 정보를 희석시킬 수 있기 때문에 잘 사용하지 않음
    <(2,2) 최대 풀링 적용>

  • 풀링 영역은 합성곱 층 커널 이동과는 다르게 겹치지 않고 이동한다.
    즉 스트라이드가 2이다.
    ex) (3,3) 풀링이면 가로세로 세 칸씩 이동

  • 매개변수
    1) 풀링의 크기 : 대부분 2로 설정하여 가로세로 크기를 절반으로 줄인다.
    가로세로 방향의 풀링 크기를 정수 튜플로 지정할 수 있으나, 극히 드물다.
    2) stride : 기본값이 자동으로 풀링의 크기로 설정되므로 따로 지정할 필요는 없다.
    3) padding : 기본값은 valid로 패딩을 하지 않는다. 이 매개변수를 바꾸는 경우는 거의 없다.

keras.layers.MaxPooling2D(2, strides=2, padding='valid')
▶
<keras.layers.pooling.MaxPooling2D at 0x7faa3d3308d0>

3) 합성곱 신경망의 전체 구조

📕 합성곱 신경망 구조

  • 입력 크기 = (4,4) / 필터 개수 = 3 / 합성곱층 커널 = (3,3) / padding = same 의 합성곱 신경망 구조

  • 합성곱의 필터가 (3,3) 3개 이므로 각각 (3,3) 크기의 가중치를 가지고 있으며 필터마다 절편이 하나씩 있다.

  • 추가적인 설정이 없으면 패딩은 텐서플로에서 자동으로 추가하므로 세임 패딩,
    스트라이드는 1이기에 특성 맵의 크기는 입력과 동일한 (4,4)이다.

  • 모든 필터로 만들어진 특성 맵을 합쳐 (4,4,3) 특성 맵이 만들어 진다.

  • 최대 풀링을 사용하여 특성 맵의 크기를 절반으로 줄여 특성맵은 (2,2,3)이 된다.

❗ discussion
풀링을 사용하는 이유는 합성곱에서 스트라이드를 크게 하여 특성 맵을 줄이는 것보다 풀링 층에서 줄이는 것이 더 나은 성능을 내기 때문이다.

  • 마지막으로 Flatten 클래스를 이용하여 (2,2,3)을 (12)의 1차원 배열로 변경하여 출력층(dense)의 입력으로 나타낸다.

📕 컬러 이미지를 사용한 합성곱

  • 패션 MNIST 데이터는 흑백 이미지이기에 2차원 배열로 표현할 수 있다.

  • 컬러이미지는 RGB 채널로 구성되어 있기에 컴퓨터는 이를 3차원 배열로 표시한다.
    ex) 입력이 (4,4,3)으로 전달됨

    <컬러이미지 입력층>

  • 이 경우 깊이가 있는 3차원 필터를 사용하여 합성곱을 진행한다.
    이때 커널의 크기는 (3,3,3)이 되며 이 합성곱의 계산은 27개의 원소에 27개의 가중치를 곱하고 절편을 더하는 방식이다.
    입력이나 필터의 차원이 몇개인지 상관없이 2차원 합성곱과 같이 항상 출력은 하나의 값이고, 이 값이 특성 맵에 있는 하나의 원소가 되는 것이다.

    <컬러이미지 합성곱 커널>

  • keras 합성곱 층은 항상 3차원 입력을 기대한다.
    따라서 패션 MNIST 데이터처럼 흑백이미지의 경우 깊이 차원에 1을 추가한 3차원 배열로 변환하여 전달한다.

  • 합성곱 층-풀링 층 다음에 다시 또 합성곱 층이 오는 경우

  • 첫번째 합성곱 층-풀링 층을 통과한 특성 맵의 크기가 (4,4,5)라면,
    두번째 합성곱 층의 필터의 너비와 높이가 각각 3일때, 필터의 커널은 (3,3,5)가 된다. 따라서 하나의 필터는 335 = 45개의 가중치를 곱하고 절편을 더한 1개의 출력을 만든다.

  • 이 과정을 통해 두번째 합성곱 층의 필터 개수가 10개라면 특성맵의 크기는 (2,2,10)이 된다. 그리고 마지막에 특성 맵을 모두 flatten하여 dense층의 입력으로 사용한다.

❗ discussion
합성곱 신경망은 너비와 높이는 점점 줄어들고 깊이는 점점 늘어나는 것이 특징이다.

📕 키워드 정리

합성곱
밀집층과 비슷하게 입력과 가중치를 곱하고 절편을 더하는 선형 계산이나,
입력층의 일부만 사용하여 선형 계산을 수행한다.

특성 맵
합성곱 층이나 풀링 층의 출력 배열을 의미한다.
필터 하나가 하나의 특성 맵을 만든다.

패딩
합성곱 층의 입력 주위에 추가한 0으로 채워진 픽셀
패딩을 사용하지 않을 시 valid padding, 합성곱 층의 입출력 크기를 동일하게 만드는 패딩을 same padding이라고 한다.

스트라이드
합성곱 층에서 필터가 입력 위를 이동하는 크기로, 일반적으로 1픽셀을 사용한다.

풀링
가중치 없이 특성 맵의 가로세로 크기를 줄이는 역할.
최대 풀링과 평균 풀링이 있다.

8-2 합성곱 신경망을 사용한 이미지 분류

학습 목표

  • 케라스 API를 사용해 합성곱 신경망 모델을 만들어 패션 MNIST 이미지를 분류하는 방법을 배웁니다.

1) 패션 MNIST 분류 모델 생성

📕 패션 MNIST 데이터 불러오기

  • 데이터 불러와서 훈련, 검증, 테스트 셋 나누고, 정규화
from tensorflow import keras
from sklearn.model_selection import train_test_split

# fashion mnist data set 불러오기
(train_input, train_target), (test_input,test_target) = keras.datasets.fashion_mnist.load_data()

# 정규화
train_scaled = train_input.reshape(-1,28,28,1) / 255.0   # reshape(-1,28,28) : (48000, 28, 28)을 (-1(개수 알아서 맞춰라), 28(행), 28(열), 1(두께))로 바꾸어라

# 훈련 셋에서 검증 셋 나누기
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size = 0.2, random_state=42)
  • 결과
    하나의 이미지에 대한 깊이(채널) 차원을 넣어주어 (48000,28,28) 크기인 train_input(3차원 배열)이 (48000,28,28,1) 크기인 train_scaled(4차원 배열) 되었다.

📕 합성곱 신경망 만들기

  • Sequential 클래스의 객체를 만들고 첫번째 합성곱 층 추가
model = keras.Sequential()
# 필터 개수 = 32 / 커널 크기 = (3,3) / 활성함수 = relu / 세임 패딩
model.add(keras.layers.Conv2D(32, kernel_size=3, activation='relu', padding='same', input_shape=(28,28,1)))
  • 풀링 층 추가
# (2,2) 크기의 풀링 추가
model.add(keras.layers.MaxPooling2D(2))
  • 결과
    이미지가 (28,28) 크기이고 세임 패딩을 적용했기 때문에 특성 맵의 크기 또한 동일하며,
    필터 개수는 32개이기에 특성 맵의 깊이는 32이다.
    그 다음 (2,2)풀링을 적용했기에 특성 맵의 크기는 절반으로 줄어든다.
    따라서 풀링 층을 통과한 특성맵의 크기는 (14,14,32)가 된다.

❗ discussion
풀링 층을 통과한 특성 맵의 크기는 정방형 특성 맵(m,m)과 정방형 풀링(n,n)의 경우
(m//n,m//n)이다.

  • 두번째 합성곱-풀링 층 추가
model.add(keras.layers.Conv2D(64, kernel_size=3, activation='relu', padding='same'))
model.add(keras.layers.MaxPooling2D(2))
  • 3차원 특성 맵 Flatten 층 / 은닉층 추가하여 Dropout / 출력층 softmax
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dropout(0.4))
model.add(keras.layers.Dense(10, activation='softmax'))
  • 결과
model.summary()
▶
Model: "sequential_12"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d_11 (Conv2D)          (None, 28, 28, 32)        320       
                                                                 
 max_pooling2d_26 (MaxPoolin  (None, 14, 14, 32)       0         
 g2D)                                                            
                                                                 
 conv2d_12 (Conv2D)          (None, 14, 14, 64)        18496     
                                                                 
 max_pooling2d_27 (MaxPoolin  (None, 7, 7, 64)         0         
 g2D)                                                            
                                                                 
 flatten (Flatten)           (None, 3136)              0         
                                                                 
 dense (Dense)               (None, 100)               313700    
                                                                 
 dropout (Dropout)           (None, 100)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 333,526
Trainable params: 333,526
Non-trainable params: 0
_________________________________________________________________
  • 모델 layer 시각적으로 확인
keras.utils.plot_model(model)
  • 결과
    왼쪽에는 층의 이름이, 오른쪽에는 클래스가 나타난다.

❗ discussion
InputLayer 클래스 : 케라스가 자동으로 추가해주는 입력층

  • 모델 layer와 shape 확인
keras.utils.plot_model(model, show_shapes=True)
  • 결과
    왼쪽 상단에는 층의 이름이, 하단에는 클래스가, 오른쪽에는 입출력 shape가 나타난다.

  • 지금까지 만든 신경망

2) 패션 MNIST 분류 모델 훈련

📕 모델 컴파일과 훈련

  • Adam 옵티마이저 / ModelCheckpoint & EarlyStopping 콜백 사용
# 옵티마이저, 손실함수, 측정항목 입력
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics='accuracy')

# 콜백 설정
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-cnn-model.h5', save_best_only=True)
early_stoping_cb = keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True)

# 훈련 객체 저장
history = model.fit(train_scaled, train_target, epochs=20, validation_data = (val_scaled, val_target), callbacks=[checkpoint_cb, early_stoping_cb])
▶
Epoch 1/20
1500/1500 [==============================] - 17s 4ms/step - loss: 0.5154 - accuracy: 0.8150 - val_loss: 0.3177 - val_accuracy: 0.8854
Epoch 2/20
1500/1500 [==============================] - 5s 3ms/step - loss: 0.3399 - accuracy: 0.8782 - val_loss: 0.2809 - val_accuracy: 0.8983
Epoch 3/20
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2913 - accuracy: 0.8945 - val_loss: 0.2667 - val_accuracy: 0.9022
Epoch 4/20
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2617 - accuracy: 0.9067 - val_loss: 0.2409 - val_accuracy: 0.9111
Epoch 5/20
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2337 - accuracy: 0.9138 - val_loss: 0.2323 - val_accuracy: 0.9148
Epoch 6/20
1500/1500 [==============================] - 5s 3ms/step - loss: 0.2163 - accuracy: 0.9200 - val_loss: 0.2233 - val_accuracy: 0.9172
Epoch 7/20
1500/1500 [==============================] - 8s 5ms/step - loss: 0.1985 - accuracy: 0.9252 - val_loss: 0.2160 - val_accuracy: 0.9223
Epoch 8/20
1500/1500 [==============================] - 6s 4ms/step - loss: 0.1800 - accuracy: 0.9319 - val_loss: 0.2172 - val_accuracy: 0.9222
Epoch 9/20
1500/1500 [==============================] - 6s 4ms/step - loss: 0.1703 - accuracy: 0.9360 - val_loss: 0.2234 - val_accuracy: 0.9212

📕 성능 평가

  • 손실 그래프를 통해 조기 종료가 잘 이루어졌는지 확인해보자
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train','val'])
plt.show();
  • 결과 이미지

  • 결과
    훈련 셋의 손실 점수는 지속적으로 하락하는 가운데,
    에포크가 7인 지점에서 검증 셋의 손실점수가 저점을 찍고 올라가고 있다.
    patience=2이므로 9에서도 손실점수가 상승하여 에포크 9에서 조기 종료한것으로 보인다.
    따라서 7번째 에포크가 최적의 모델이라고 할 수 있다.

  • evaluate 메서드 사용 성능평가

model.evaluate(val_scaled, val_target)
▶
375/375 [==============================] - 1s 2ms/step - loss: 0.2160 - accuracy: 0.9223
[0.21600665152072906, 0.9223333597183228]
  • 결과
    fit() 메서드의 출력 중 일곱번째 에포크의 출력과 동일한 것으로 보아.
    EarlyStopping 콜백이 model 객체를 최적의 모델 파라미터로 잘 복원하였다.

3) 패션 MNIST 분류 모델 활용 예측

📕 MNIST 데이터 예측

  • 검증 셋 데이터 확인
# 맷플롯립 흑백 이미지의 깊이 차원은 없다
plt.imshow(val_scaled[0].reshape(28,28), cmap='gray_r')
plt.show();
  • 결과 이미지
    핸드백 이미지

  • 모델 활용 예측

preds = model.predict(val_scaled[0:1])
print(preds)
▶
[[5.5839118e-17 1.9493851e-24 7.2439644e-20 8.5426052e-21 2.0688551e-19
  3.4010700e-16 3.1647051e-18 1.4182638e-16 1.0000000e+00 2.5273449e-16]]
  • 결과
    9번째 클래스의 값이 1에 가깝고 나머지는 0에 수렴한다.

  • 결과 시각화

plt.bar(range(1,11),preds[0])
plt.xlabel('class')
plt.ylabel('prob.')
plt.show()
  • 결과 이미지
  • 아홉번째 클래스 확인
import numpy as np
classes = ['티셔츠', '바지','스웨터','드레스','코트','샌달','셔츠','스니커즈','가방','앵클 부츠']
print(classes[np.argmax(preds)])
▶
가방
  • 결과
    preds에서 가장 큰 값의 인덱스를 classes에서 찾아본 결과가 가방으로 실제 이미지와 같은 값이였다.

📕 test set 확인

# 정규화
test_scaled = test_input.reshape(-1,28,28,1) / 255.0
# 성능평가
model.evaluate(test_scaled, test_target)
▶
313/313 [==============================] - 1s 3ms/step - loss: 0.2386 - accuracy: 0.9179
[0.2386104017496109, 0.917900025844574]
  • 결과
    test set의 결과 0.9179는 val set의 결과 0.9223보다 조금 더 작다.
    해당 모델을 실전 투입 시에는 약 91%의 성능을 보일 것이다.

8-3 합성곱 신경망의 시각화

학습 목표

  • 합성곱 층의 가중치와 특성 맵을 시각화하여 신경망이 이미지에서 어떤 것을 학습하는지 이해해봅니다.

1) 가중치 시각화

📕 훈련 모델 가중치 확인

  • 앞서 만든 모델의 layer 확인
from tensorflow import keras
model = keras.models.load_model('best-cnn-model.h5')

model.layers
▶
[<keras.layers.convolutional.Conv2D at 0x7ff47470c510>,
 <keras.layers.pooling.MaxPooling2D at 0x7ff47405e550>,
 <keras.layers.convolutional.Conv2D at 0x7ff45e19a3d0>,
 <keras.layers.pooling.MaxPooling2D at 0x7ff474703950>,
 <keras.layers.core.flatten.Flatten at 0x7ff4740f08d0>,
 <keras.layers.core.dense.Dense at 0x7ff4e001bf90>,
 <keras.layers.core.dropout.Dropout at 0x7ff447e19410>,
 <keras.layers.core.dense.Dense at 0x7ff47449c5d0>]
  • 첫번째 합성곱 층의 가중치와 절편의 크기 출력
conv = model.layers[0]

print(conv.weights[0].shape, conv.weights[1].shape)    # 첫째 원소 : 가중치 크기/ 둘째 원소 : 절편 크기
▶
(3, 3, 1, 32) (32,)

❗ discussion
conv.weights는 파이썬 리스트로 첫번째 원소로 가중치, 두번째 원소로 절편을 가진다.
위 코드에서는 shape로 형태를 확인하였다.

  • 가중치 배열의 평균과 표준편차 확인
conv_weights = conv.weights[0].numpy()

print(conv_weights.mean(), conv_weights.std())
▶
-0.016392192 0.21075654
  • 결과
    이 가중치의 평균값은 0에 가깝고 표준편차는 0.27이다.

  • 가중치 분포 히스토그램

import matplotlib.pyplot as plt
plt.hist(conv_weights.reshape(-1,1))    # 1차원 배열로 변환
plt.xlabel('weight')
plt.ylabel('count')
plt.show();
  • 결과 이미지

❗ discussion
0을 중심으로 종 모양 분포를 띠고 있다.
훈련하기 전의 가중치와 비교하여 어떤 의미를 학습한 것인지 확인해보자.

  • 32개 커널의 그래프 영역을 시각화
    이 배열의 마지막 차원(필터 개수)을 순회하면서 가중치 값을 차례로 출력한다.
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();
  • 결과 이미지

  • 결과
    가중치 값에서 어떤 패턴을 볼 수 있다.
    예를 들어 왼쪽하단의 가중치는 왼쪽 세개의 픽셀의 값이 높으므로(밝은 부분의 값이 높다.) 왼쪽에 놓인 직선을 만나면 크게 활성화될 것이다.

❗ discussion
imshow() 함수는 배열에 있는 최댓값과 최솟값을 사용해 픽셀의 강도를 표현한다.
그 배열 내에서 최댓값이면 가장 밝게 표현된다.
따라서 두 배열을 imshow() 함수로 비교하는 것은 바람직하지 않다.
절대적인 기준을 정하기 위해서 vmin가 vmax로 matplotlib 컬러맵의 기준을 잡아주었다.

📕 미훈련 모델 가중치 시각화

  • 훈련하지 않은 빈 합성곱 신경망 가중치 확인
# 합성곱층 생성
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)
▶
(3, 3, 1, 32)
  • 평균, 표준편차 확인
no_training_weights = no_training_conv.weights[0].numpy()
print(no_training_weights.mean(), no_training_weights.std())
▶
-0.00556233 0.08236996
  • 결과
    평균은 훈련한 가중치(-0.016392192)와 동일하게 0에 가깝지만, 표준편차는 이전( 0.21075654)과는 달리 매우 작다.

  • 가중치 히스토그램

plt.hist(no_training_weights.reshape(-1,1))
plt.xlabel('weight')
plt.ylabel('count')
plt.show()
  • 결과 이미지

  • 결과
    대부분의 가중치가 -0.15~0.15 사이에 있고 비교적 고른 분포를 보인다.
    훈련 이전에는 신경망의 가중치를 균등 분포에서 랜덤하게 값을 선택하기 때문이다.

  • 32개 커널의 그래프 영역을 시각화

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()
  • 결과 이미지

❗ discussion
전체적으로 가중치가 큰 차이없이 초기화되었다.
합성곱 신경망이 분류 정확도를 높이기 위해 유용한 패턴을 학습하여 가중치를 적용시킨 것이다.

2) 함수형 API

📕 함수형 API 활용

  • 함수형 API는 Sequential 클래스로는 생성이 어려운 좀 더 복잡한 모델을
    케라스의 Model 클래스를 사용하여 모델을 만드는 API이다.

  • Dense 층 2개로 이루어진 완전 연결 신경망 함수 API 구현

dense1= keras.layers.Dense(100, activation='sigmoid')
dense2= keras.layers.Dense(10, activation='sigmoid')
  • dense 층 객체를 함수처럼 호출
    입력값 inputs를 Dense 층에 통과시킨 후 출력값 hidden을 만들어줌
hidden = dense1(inputs)
  • 첫번째 층의 출력을 입력으로 사용하여 두번째 층 호출
outputs = dense2(hidden)
  • inputs와 outputs를 Model 클래스로 연결
model = keras.Model(inputs, outputs)
  • 위 과정을 그림으로 나타내보자

    입력에서 출력까지 층을 호출한 결과를 계속 이어주고,
    Model 클래스에 입력과 최종 출력을 지정한다.
    Sequential 클래스는 InputLayer 클래스를 자동으로 추가하고 호출하지만,
    Model 클래스에서는 수동으로 만들어서 호출해야한다.
    즉 inputs가 inputLayer 클래스의 출력값이 되어야 한다.

  • input() 함수 활용 입력값 생성

inputs = keras.Input(shape=(784,))
  • 전체 모델 모식도
    입력부터 출력까지 연결 후 Model 클래스에 입력과 출력을 지정하여 모델을 만든다.

📕 함수형 API 활용 합성곱 층 모델링

  • 함수형 API를 활용하면 중간에 다양한 형태로 층을 연결할 수 있다.

  • model 객체의 층

  • 특성맵을 시각화하기 위해서 첫번째 Conv2D의 출력을 필요로 한다.
    model 객체의 입력과 Conv2D의 출력을 알 수 있다면 이 둘을 연결하여 새로운 모델을 얻어 보자

  • model 객체의 입력 추출

print(model.input)
▶
KerasTensor(type_spec=TensorSpec(shape=(None, 28, 28, 1), dtype=tf.float32, name='conv2d_3_input'), name='conv2d_3_input', description="created by layer 'conv2d_3_input'")
  • model.inputmodel.layers[0].output을 연결하는 새로운 conv_acti 모델 생성
conv_acti = keras.Model(model.input, model.layers[0].output)

3) 특성 맵 시각화

📕 conv_acti의 특성 맵 확인

  • 패션 MNIST train set 첫번째 샘플 확인
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
plt.imshow(train_input[0], cmap='gray_r')
plt.show()
  • 결과 이미지

  • 해당 샘플의 합성곱층 특성 맵 출력

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

❗ discussion
predict() 메서드는 항상 입력의 첫번째 차원이 배치 차원일 것으로 기대한다.
따라서 train_input[0:1] (2차원 샘플)로 선택 후 (784,) 크기를 (28,28,1) 크기로 변경하고 255로 나누어 전처리 해준다.

  • 특성 맵 크기 확인
print(feature_maps.shape)
▶
(1, 28, 28, 32)
  • 결과
    세임 패딩과 32개 필터를 사용한 특성맵이므로 (28,28,32)이다.
    첫번째 차원은 배치 차원이므로 샘플의 개수(=1)이다.

  • 특성 맵 시각화

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개의 필터로 인해 각각 강하게 활성화된 부분을 보여준다.

  • 필터의 가중치와 특성 맵을 비교해보자.(나의 코랩과 다름)

    - 첫번째 필터는 오른쪽에 있는 수직선을 감지하며,
    첫번째 특성 맵은 이 필터가 감지한 수직선이 강하게 활성화됨
    - 세번째 필터는 전체적으로 밝은 색이므로 전면이 모두 칠해진 영역을 감지하며,
    세번째 특성 맵에서 흑백 부츠 이미지에서 검은 영역이 잘 활성화됨
    - 마지막 필터는 전체적으로 낮은 음수값이므로,
    부츠의 배경이 상대적으로 활성화됨

📕 두번째 합성곱 층 특성 맵 확인

  • 모델 생성
# 입력과 두번째 합성곱층을 연결한 모델 생성
conv2_acti = keras.Model(model.input, model.layers[2].output)

# 첫번째 샘플을 predict() 메서드에 전달
inputs = train_input[0:1].reshape(-1,28,28,1) / 255.0
feature_maps = conv2_acti.predict(inputs)
  • 특성 맵 크기 확인
print(feature_maps.shape)
▶
(1, 14, 14, 64)

❗ discussion
첫번째 풀링 층에서 가로세로가 절반으로 줄었고,
필터 개수는 64개 이므로 배치 차원 제외 (14,14,64)이다.

  • 특성 맵 시각화
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()
  • 결과 이미지

❗ discussion
첫번째 합성곱 층과 달리 시각적으로 이해하기 어렵다.
첫번째 합성곱 층의 특성 맵과 두번째 합성곱 층의 필터가 곱해져
두번째 합성곱 층의 특성 맵이 되기 때문에
특성 맵에서 어떤 부위를 감지하는지 직관적으로 이해하기 어렵다.

이런현상은 합성곱 층을 많이 쌓을수록 심해진다.
합성곱 신경망의 앞부분은 이미지의 시각적인 정보를 감지하고
뒷부분은 앞쪽에서 감지한 시각적인 정보를 바탕으로 추상적인 정보를 학습한다고 볼 수 있다.

📕 키워드 정리

가중치 시각화
합성곱 층의 가중치를 이미지로 출력하는 것을 말한다.
가중치가 시각적인 패턴을 학습하는지 알아볼 수 있다.

특성 맵 시각화
합성곱 층의 활성화 출력을 이미지로 그리는 것을 말한다.
각 필터가 어느 부분을 활성화시키는지 확인할 수 있다.

함수형 API
케라스에서 신경망 모델을 만드는 방법 중 하나.
Model 클래스에 모델의 입력과 출력을 지정한다.

profile
개발도상인 냄비짱

0개의 댓글