가중치 시각화

CNN은 여러 개의 필터를 사용해 이미지에서 특징을 학습한다.

각 필터는 커널이라 부르는 가중치와 절편을 가지고 있다.

 

가중치의 경우 입력 이미지를 2차원으로 적용하면 어떤 특징을 크게 두드러지게하는데, 그림으로 보면

 

색이 있는 공간은 값이 높고, 아니면 낮을 것이다.

 

이전에 학습했던 데이터를 불러오자 checkpoint 파일 불러오기

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

[<keras.layers.convolutional.conv2d.Conv2D at 0x24c86ab22b0>,
 <keras.layers.pooling.max_pooling2d.MaxPooling2D at 0x24c86a9c220>,
 <keras.layers.convolutional.conv2d.Conv2D at 0x24cfacd7580>,
 <keras.layers.pooling.max_pooling2d.MaxPooling2D at 0x24cfacd4be0>,
 <keras.layers.reshaping.flatten.Flatten at 0x24cfabec670>,
 <keras.layers.core.dense.Dense at 0x24cfabe01c0>,
 <keras.layers.regularization.dropout.Dropout at 0x24cfb2fe400>,
 <keras.layers.core.dense.Dense at 0x24cfabfe760>]

conv = 모델의 첫 번째 층 : 첫 번째 CNN ( 필터 32개, 커널 사이즈 3x3 )

conv의 가중치의 첫 번째 원소와 두번 째 원소의 크기를 출력해보자.

첫 번째 원소 = 가중치

두 번째 원소 = 절편

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

(3, 3, 1, 32) (32,)

커널의 크기 3x3이므로 3,3,1이 커널의 크기로 출력되었고, 필터 개수 32개 임을 확인할 수 있다.

절편은 (32,) ==> 필터마다 1개의 절편 존재하므로 32개의 상수가 있음을 확인할 수 있다.

 

conv_weights = conv.weights[0].numpy()
print(conv_weights.mean(), conv_weights.std())

-0.025013467 0.2464292

가중치 배열의 평균 / 표준편차를 계산

0에 가깝고 표준편차는 0.27이다.

 

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

균일해보이지 않다!!!

 

 

이제 훈련 전의 데이터의 가중치를 살펴보자.

 

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

not_training_conv = not_training.layers[0]
print(not_training_conv.weights[0].shape)

(3, 3, 1, 32)

 

평균 / 표준편차를 출력해보자.

not_training_weights = not_training_conv.weights[0].numpy()

print(not_training_weights.mean(),not_training_weights.std())

-0.003274826 0.08374746

아까처럼 가중치의 배열을 그림으로 표현해보자.

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

 

8-1에서 배운 내용을 토대로 패션 MNIST 구현해보자.

 

from tensorflow import keras 
from sklearn.model_selection import train_test_split
(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
train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42 )

reshape.(-1,28,28,1) -> 4차원 배열로 

 

이제 합성곱 신경망을 만들어보자..!

합성곱 신경망 만들기

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

매개변수 설명 : 필터 32개를 사용

커널의 크기는 3x3

활성화 함수 : 렐루 함수

세임패딩 사용 

입력의 차원 28,28,1

 

풀링 층 추가  ( 차원 축소시키는것 )

model.add(keras.MaxPooling2D(2))

최대 풀링 = MaxPooling

평균 풀링 = AveragePooling

 

위 코드에서는 최대값 풀링을 사용했고, (2,2)로  설정했다.   ---> 가로세로가 같으면 숫자 하나만 써도 가능

 

패션 MNIST 이미지 => 28x28크기에 세임 패딩 -> 출력된 특성 맵 또한 28x28 -> 2,2 풀링했으므로 특성맵 크기 절반으로 됨. 특성 맵의 깊이 32가 됨, 따라서 최대 풀링을 통과한 특성 맵의 크기는 (14,14,32)가 될 것.

 

첫 번째 CNN풀링층 다음에 두 번째 CNN 풀링 층을 추가해보자.

model.add(keras.layers.Conv2D(64, kernel_size=3, activation = 'relu',
			padding = 'same'))
model.add(keras.layers.MaxPooling2D(2))

처음과 동일 (필터개수 64개 만 바뀌었다.)

최종 출력되는 특성 맵은 (7,7,64)가 될 것이다.

 

이제 이 특성맵을 일렬로 펼쳐보자.

 

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

은닉층과 출력층 사이에 드롭아웃을 넣었다.

 

이제 summary() 해보자.

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 28, 28, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 14, 14, 32)       0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, 14, 14, 64)        18496     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 7, 7, 64)         0         
 2D)                                                             
                                                                 
 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
_________________________________________________________________

출력된 것을 보고 각 층을 통과하면서 특성맵의 깊이가 32, 64로 각각 변하는것을 볼 수 있다.

 

모델 컴파일과 훈련

model checkpont 콜백, early stopping 콜백을 같이 사용

model.compile(optimizer = 'adam', loss='sparse_categorical_crossentropy',metrics='accuracy')
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-cnn-model.h5',
                                                save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience = 2,
                                                  restore_best_weights=True)
early_stopping_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_stopping_cb])

실행

Epoch 1/20
1500/1500 [==============================] - 27s 17ms/step - loss: 0.5075 - accuracy: 0.8179 - val_loss: 0.3253 - val_accuracy: 0.8817
Epoch 2/20
1500/1500 [==============================] - 25s 17ms/step - loss: 0.3334 - accuracy: 0.8809 - val_loss: 0.2711 - val_accuracy: 0.9009
Epoch 3/20
1500/1500 [==============================] - 24s 16ms/step - loss: 0.2825 - accuracy: 0.8971 - val_loss: 0.2612 - val_accuracy: 0.9053
Epoch 4/20
1500/1500 [==============================] - 23s 16ms/step - loss: 0.2540 - accuracy: 0.9085 - val_loss: 0.2359 - val_accuracy: 0.9146
Epoch 5/20
1500/1500 [==============================] - 25s 17ms/step - loss: 0.2302 - accuracy: 0.9148 - val_loss: 0.2358 - val_accuracy: 0.9121
Epoch 6/20
1500/1500 [==============================] - 26s 18ms/step - loss: 0.2109 - accuracy: 0.9232 - val_loss: 0.2291 - val_accuracy: 0.9134
Epoch 7/20
1500/1500 [==============================] - 30s 20ms/step - loss: 0.1924 - accuracy: 0.9289 - val_loss: 0.2284 - val_accuracy: 0.9178
Epoch 8/20
1500/1500 [==============================] - 26s 17ms/step - loss: 0.1774 - accuracy: 0.9330 - val_loss: 0.2292 - val_accuracy: 0.9195
Epoch 9/20
1500/1500 [==============================] - 28s 19ms/step - loss: 0.1652 - accuracy: 0.9387 - val_loss: 0.2363 - val_accuracy: 0.9193

에포크 절반도 안되어서 조기종료가 되었다.

 

손실그래프를 그려보자.

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~8번째 에포크 같다

 

EarlyStopping클래스에서 restore_best_weights 매개변수를 True로 지정해서 

현재 model 객체가 최적의 모델 파라미터로 복원되어 있다.

 

즉 ModelCheckpoint 콜백이 저장한 best-cnn-model.h5파일을 다시 읽을 필요 X

 

이제 세트에 대한 성능을  평가해보자.

model.evaluate(val_scaled, val_target)

375/375 [==============================] - 2s 5ms/step - loss: 0.2284 - accuracy: 0.9178
[0.22835350036621094, 0.9178333282470703]

fit()의 결과 중 에포크 7번째 때와 결과값이 같다.!! --> 7번째가 최적의 에포크

 

첫 번째 샘플 이미지를 확인해보자.!!

핸드백인것을 볼 수 있다.

 

predict() 메소드를 이용해서 10개의 클래스에 대한 예측을 해보자.

preds = model.predict(val_scaled[0:1])
print(preds)

1/1 [==============================] - 0s 75ms/step
[[1.8688267e-19 4.8781057e-26 4.4762546e-23 1.3098311e-21 1.7252202e-19
  4.3341434e-19 1.1465990e-20 3.6049053e-22 1.0000000e+00 2.2943934e-20]]

결과를 보면 아홉번 째가 1이고 나머지는 0에 가까운 숫자들뿐이다.

 

시각화를 해보면 더 직관적일 것이다.

이제 리스트로 정리해보자

classes= ['티셔츠','바지','스웨터','드레스','코트','샌달','셔츠','스니커즈','가방','앵클 부츠']
import numpy as np
print(classes[np.argmax(preds)])

가방

합성곱 ( convolution) : 이미지의 유용한 특징,특성을 드러내게 하는 역할을 함.

이미지처리에 좋은 성능을 낼 수 있다!!!

 

앞서 배운 인공신경망의 dense에서는 입력개수마다 가중치가 있다. (모든 입력에 가중치가 존재)

 

뉴런(=출력층)

 

앞에서 MNIST 이미지는 784개의 픽셀을 입력받는 은닉층의 뉴런 개수가 100개 => 출력도 100개였다.

 

합성곱은 위와 다르게 입력데이터 전체에 가중치를 적용X

                일부에만 가중치를 곱한다.

 

일부 = n개의 픽셀에만 !!

합성곱은 한번의 연산으로 끝나지않음!!

그림으로 이해해보자.

 

 

총 8개의 출력 (3,1,0), (1,0,7),,,,,,(2,4,5) 

가중치 w1,w2,w3은 모두 같은 값

 

앞에서 배운 밀집층의 뉴런은 입력개수만큼 10개의 가중치를 가지고 1개의 출력을 생성.

 

합성곱 신경망은 3개의 가중치를 가지고 8개의 출력을 생성

 

합성곱에서 사용할 가중치의 개수 = 하이퍼파라미터!!!!!!!!!!!!

 

CNN에서는 뉴런 = 필터 이라고 생각하면 된다.

 

뉴런 개수 = 필터

가중치 = 커널 ( 도장) 

 

 

한 개의 필터 : 빨간 박스 = 3x3의 총 9개의특성 x 9개의 가중치 = 1개의 출력

                       + 3번 더 이동하여 총 4개의 출력!!!

 

이를 2차원으로 표현하자면

 

1번 출력 |  2번 출력

--------------------------               이렇게 행렬?처럼 나타낼 수 있다.

3번 출력 |  4번 출력                  

 

위와 같이 (2,2) 크기를 쌓으면 3차원 배열이 된다.

 

만약 필터가 3개가 있다면  (2,2,3)인 3차원 배열로 표현할 수 있다.

 

 

 

구현하기

합성곱 = Conv2D

매개변수  ==> 필터의 개수, 커널사이즈=커널의크기, 활성화 함수, 패딩, 스트라이드

keras.layers.Conv2D(10, kernel_size=(3,3), activation = 'relu', padding='same', strides=1)

 

패딩 / 스트라이드

위의 예시는 (4,4)크기의 입력에 (3,3)크기의 커널(가중치, 도장)을 적용하여 (2,2)크기로 압축? 특성맵을 만들었다.

 

그런데 만약 커널 크기는 (3,3) 그대로하고 출력의 크기를 (4,4)로 만들고 싶다면???

 

--> 입력(4,4)과 동일한 크기의 출력을 만드려면 마치 더 큰 입력에 합성곱하는 척해야 한다.!!

 

예를 들어 , 사실은 4x4이지만 6x6처럼 바꿔준 후 3x3 커널로 합성곱하면 4x4특성 맵으로 만들 수 있다.

그림으로 이해하기

 

이렇게 주위를 가상의 원소로 채우는 것을 패딩이라고한다.

(추우니까 두껍게 입자=패딩)

 

위와 같이 입력/특성을 맞추기 위해 주위를 0으로 패딩하는것 = 세임 패딩

패딩없이 순수한 배열 그대로 합성곱 해서 특성 맵을 만드는것= 밸리드 패딩

keras.layers.Conv2D(10, kernel_size=(3,3), activation = 'relu', padding='same', strides=1)

위 padding => 기본값은 밸리드이다. 세임하고싶은 'same'이라고 써주기!

 

그리고 도장(커널)은 한 칸씩 이동하는데, 만약 두 칸씩 이동한다면 특성 맵( 출력 ) 은 반토막이 날 것이다.

이렇게 n칸씩 지정하는 것을 스트라이드라고 하고, 기본값은 1이다 !!!

 

 

폴링 ( pooling )

풀링: 합성곱 층에서 만든 특성 맵의 가로세로의 크기를 줄이는 역할

 

그림으로 이해

 

위 그림처럼 (2,2,3) 특성맵에 풀링을 적용하게되면 마지막 차원인 개수!만 유지하고 너비,높이가 줄어듦 (1,1,3)

 

위 그림에서는 2,2 ->  1,1로 축소

도장을 찍은 영역에서 가장 큰 값 / 평균 값을 계산하는데, 이를 최대풀링, 평균풀링이라고 한다.

 

특성맵 4개중에서 가장 큰 값 고르고, 그렇게 총 4개를 골랐다면, 그값으로 이루어진 축소 특성맵을 생성!!!!!!!!!!!!!!!!

그림으로 추가 설명

위 그림에서 stride = 2인것 또한 확인할 수 있다.

 

keras.layers.Maxpooling2D(2, strides=2, padding='valid')

괄호안에 숫자는 풀링의 크기를 지정이다!!!

 

총정리

 

+ Recent posts