미리 보기 - 텐서플로를 사용해 순환 신경망을 만들어 영화 리뷰 데이터셋에 적용해서 리뷰를 긍정/부정으로 분류하기

<<<키워드>>>

- 원 핫 인코딩

- 단어 임베딩

IMDB 데이터셋

유명한 영화 데이터베스인 imdb.com에서 수집한 리뷰를 감상평에 따라 긍정 / 부정으로 분류해 놓은 데이터셋이다.

총 50,000개의 샘플로 이루어져 있고 훈련데이터와 테스트 데이터에 각각 25,000개씩 나누어져 있다!

 

데이터로 텍스트 자체를 신경망에 전달?? X 컴퓨터는 숫자 데이터를 다루기 용이하므로 단어를 숫자 데이터로 바꾸는 작업 필요하다.! like 원-핫 인코딩   --> 토큰으로 변환 !!

하나의 샘플은 여러 개의 토큰으로 이루어져 있고 1개의 토큰이 1개의 타임스텝에 해당한다.

 

IMDB 데이터셋을 두 가지 방법으로 순환 신경망에 적용시켜보자!

1. 원-핫 인코딩

2. 단어 임베딩

 

 

keras 데이터셋 패키지의 imdb 모듈을 import해서 자주 사용하는 단어 500개를 불러오자.

( num_words 매개변수를 500으로 지정 )

from tensorflow.keras.datasets import imdb
(train_input, train_target),(test_input, test_target) = imdb.load_data(num_words=500)

print( train_input.shape, test_input.shape )
(25000,) (25000,)

 

훈련 세트와 테스트 세트의 크기 25,000씩인것까지 확인!

 

근데 (25000,) 을 보듯이 배열이 1차원이다.! 

텍스트 데이터 특성상 길이가 제각각이므로 고정 2차원 배열에 담는것보다 메모리 효율적이므로 1차원에 저장하는것이다!

즉, 넘파이 배열안에  정수,숫자가 아닌 개별 리뷰가 담겨있다고 볼 수 있다.

 

첫 번째 리뷰의 길이를 출력해보자!!

print(len(train_input[0]))

218

218개의 토큰으로 이루어져 있다.!

 

이제 길이말고 내용을 출력해보고 싶다!

print(train_input[0])

[1, 14, 22, 16, 43, 2, 2, 2, 2, 65, 458, 2, 66, 2, 4, 173,36, 256, 5, 25, 100, 43, 2, 112, 50,
2, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 2, 2, 17, 2, 38, 13
, 447, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 469, 4, 22, 71, 87, 12, 16, 43, 2, 38, 
76, 15, 13, 2, 4, 22, 17, 2, 17, 12, 16, 2, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2, 2
, 16, 480, 66, 2, 33, 4, 130, 12, 16, 38, 2, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 
12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 2, 36, 
71, 43, 2, 476, 26, 400, 317, 46, 7, 4, 2, 2, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2, 56, 26
, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30,2, 18, 51, 36, 28, 224, 92
, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178,32]

텐서플로에 있는 IMDB 리뷰 데이터는 이미 정수로 변환되어서 개별 토큰으로 출력된다.

참고로 num_words=500으로 지정했기 때문에 어휘 사전에는 500개의 단어만 들어가있다.

따라서 어휘 사전에 없는 단어는 모두 2로 표시 된다!!!

 

이제 타겟 데이터를 출력해보자.!

print(train_target[:20])

[1 0 0 1 0 0 1 0 1 0 1 0 0 0 0 0 1 1 0 1]

타겟 데이터 => 0: 부정, 1: 긍정


데이터를 더 살펴보기 전 훈련세트에서 검증세트를 떼어 놓자 !

from sklearn.model_selection import train_test_split
train_input, val_input, train_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state= 42)

20%를 검증세트로 떼어 놓았으니 훈련 세트의 크기는 20,000개로 줄어들것.

 

 

이제 훈련 세트에 대해 몇 가지를 건드려보자.!

각 리뷰의 길이들을 확인해보고 싶다.!! 

 

-> 각 리뷰의 길이를 계산해 넘파이 배열에 담고, 리스트 함축 구문을 이용해 train_input의 원소를 순회하면서 길이를 알아보자.

import numpy as np
lengths = np.array([len(x) for x in train_input])

print(np.mean(lengths), np.median(lengths))
239.00925 178.0

평균 = 239     /   중간값 = 178

 

평균값과 중간값이 차이가 많이 나므로 데이터의 분포가 한쪽에 치우쳐져 있을 것이다.

import matplotlib.pyplot as plt
plt.hist(lengths)
plt.xlabel('length')
plt.ylabel('frequency')
plt.show()

대부분의 리뷰 길이는 300이 안되는것을 확인할 수 있다.!

 

연습삼아 100개의 단어만 사용해보자!

하지만 100개의 단어도 안되는 리뷰가 굉장히 많으므로 앞서 배운 패딩을 적용시켜야 한다.

 

보통 업계에서 패딩을 나타내는 토큰으로는 0을 사용한다고 한다.

 

케라스의 시퀀스 데이터의 길이는 맞추는 메소드를 이용해보자 (pad_sequences()).

이 메소드를 이용해서 train_input의 길이를 100으로 간편하게 맞춰보자.

from tensorflow.keras.preprocessing.sequence import pad_sequences
train_seq = pad_sequences(train_input, maxlen=100) # 패딩으로 train_input길이를 100으로 맞춤

# 100보다 길면 잘라내고 짧으면 0으로 패딩 ->

100으로 패딩한 train_seq을 출력해보자.

print(train_seq.shape)

(20000, 100)

아무래도 2차원 배열이 데이터를 다루기에 효율적이므로,

패딩을 이용해 길이를 같게 해줌으로써 2차원 배열로 만든것 같다!

샘플 개수 = 25,000개에서 20%떼어냈으므로 20,000개,

토큰 개수 = 100으로 패딩했으므로 100개로  이루어진 2차원배열이다!!

 

train_seq에 있는 첫 번째 샘플을 출력해보자.

 

print(train_seq[0])

[ 10   4  20   9   2 364 352   5  45   6   2   2  33 269   8   2 142   2
   5   2  17  73  17 204   5   2  19  55   2   2  92  66 104  14  20  93
  76   2 151  33   4  58  12 188   2 151  12 215  69 224 142  73 237   6
   2   7   2   2 188   2 103  14  31  10  10 451   7   2   5   2  80  91
   2  30   2  34  14  20 151  50  26 131  49   2  84  46  50  37  80  79
   6   2  46   7  14  20  10  10 470 158]

이 샘플의 앞뒤에 패딩값 0이 없는것으로 보아 100보다는 긴 리뷰라고 볼 수 있다.!

 

원래 샘플의 앞vs뒤 부분중 어디가 잘렸을지 확인해보기 위해 train_input에 있는 원본 샘플의 끝을 먼저 보자.

print(train_input[0][-10:])

[6, 2, 46, 7, 14, 20, 10, 10, 470, 158]

음수 인덱스슬라이싱을 사용해 train_input[0]에 있는 마지막 10개의 토큰을 출력했다

train_seq[0]의 출력값과 정확히 일치하는것 또한 확인할 수 있다.

 

그러면????

샘플 앞부분이 잘렸다는 것!!

 

참고)  pad_sequences 메소드는 기본적으로 maxlen보다 긴 시퀀스의 앞부분을 자른다고 한다!!!

이유-> 일반적으로 텍스트 中 중요한 정보는 뒷부분(어미)에 나올 확률이 높기 때문..

 

하지만!! 

그럼에도 불구하고 앞부분을 자르고 싶다면 

pad_sequences( truncating = 'post') 로 설정해주면 된다. truncating = 'pre'가 default값

 

print(train_seq[5])

[  0   0   0   0   1   2 195  19  49   2   2 190   4   2 352   2 183  10
  10  13  82  79   4   2  36  71 269   8   2  25  19  49   7   4   2   2
   2   2   2  10  10  48  25  40   2  11   2   2  40   2   2   5   4   2
   2  95  14 238  56 129   2  10  10  21   2  94 364 352   2   2  11 190
  24 484   2   7  94 205 405  10  10  87   2  34  49   2   7   2   2   2
   2   2 290   2  46  48  64  18   4   2]

이 6번째 샘플을 보면 앞 부분이 0으로 채워져 있는것으로 보아 리뷰 길이가 100이 안되는것을 확인할 수 있다.

 

또한, 앞 부분이 0으로 채워져 있는것으로 보아 위와 같은 이유(중요한 부분 -> 뒤에 있다.)로 인해

패딩토큰은 시퀀스의 뒷부분이 아닌 앞부분에 추가된다는것을 엿볼 수 있다.

 

하지만!!!!!

또 뒷부분을 0으로 채우고싶다면

pad_sequences( padding = 'post')로 바꿔주면 된다.!

 

이제 검증 세트의 길이도 100으로 맞춰보고 본격적으로 해보자.

val_seq = pad_sequences(val_input, maxlen=100)

훈련 세트, 검증 세트 모두 100으로 패딩완료.

 

순환 신경망 만들기

가장 간단한 클래스인 SimpleRNN클래스를 이용

IMDB 리뷰 긍정/부정 분류 모델이므로 이진분류---> 따라서 활성화 함수는 시그모이드로 선택

 

from tensorflow import keras

model = keras.Sequential()
model.add(keras.layers.SimpleRNN(8, input_shape=(100, 500)))
model.add(keras.layers.Dense(1, activation = 'sigmoid'))

<<SimpleRNN의 첫 번째 매개변수 X = 사용할 뉴런의 개수>> -> CNN과 동일

 

input_shape = (100,500)으로 했는데, 첫 번째 차원이 100인 것은 앞에서 샘플의 길이를 100으로 지정했기 때문이다.!

 

두 번째 차원이 500인 것은, 원-핫 인코딩을 위해 배열의 길이를 설정해준것!

(기존의 정수는 크기가 제각각이지만, 그 크기에는 순서와 의미가 없다. 따라서 그것을 제거해주는 원핫인코딩이 필요)

 

새로 등장한 개념인 순환층또한 활성화 함수가 필요하다! SimpleRNN의 기본 활성화 함수는 

하이퍼볼릭 탄젠트 함수인 tanh라는 매우 강력해보이는 함수를 사용한다. tanh => -1~1출력하는 시그모이드

 

 

우리는 원-핫 인코딩을 제공하는 모듈을 사용하도록 하자!

train_oh라는 매개변수 : train_seq을 원핫인코딩으로 변환한 배열

train_oh = keras.utils.to_categorical(train_seq)

print(train_oh.shape)
(20000, 100, 500)

배열 크기 20,000개인 이유 => 정수 하나하나 모두 500차원의 배열로 변경되었음 (20000, 100)크기의 train_seq이 

(20000,100,500으로 변한것)


이렇게 샘플 데이터의 크기가 1차원 정수배열(100,)에서 2차원 배열 (100, 500)로 바꿔야하므 SimpleRNN 클래스의 input_shape매개변수의 값을 (100,500)으로 지정한 것이다.

 

이제 인코딩이 잘 되었는지 확인해보자.

print(train_oh[0][0][:12])


[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]

< train의 첫 번째 샘플의 첫 번째 토큰 中 12개 원소 > -> 인코딩이 잘 되었음을 확인

 

이제 val도 인코딩

val_oh = keras.utils.to_categorical(val_seq)

 

모델 요약 !

model.summary()

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 simple_rnn (SimpleRNN)      (None, 8)                 4072      
                                                                 
 dense (Dense)               (None, 1)                 9         
                                                                 
=================================================================
Total params: 4,081
Trainable params: 4,081
Non-trainable params: 0
_________________________________________________________________

 

rmsprop = keras.optimizers.RMSprop(learning_rate=0.4)

model.compile(optimizer = rmsprop, loss = 'binary_crossentropy',
				metrics=['accuracy'])
                
check_point_cb = keras.callbacks.ModelCheckpoint('best-simplernn-model.h5',
						save_best_only = True)
earlystopping_cb= kerars.callbacks.EarlyStopping(patience=3, restore_best_weights=True)

history=model.fit(train_oh,train_target, epochs=100, batch_size=64,
				validation_data = (val_oh, val_target),
				callbacks = [check_point_cb, earlystopping_cb])
Epoch 1/100
313/313 [==============================] - 7s 19ms/step - loss: 0.7534 - accuracy: 0.4992 - val_loss: 0.7433 - val_accuracy: 0.5002
Epoch 2/100
313/313 [==============================] - 6s 18ms/step - loss: 0.7456 - accuracy: 0.4998 - val_loss: 0.7360 - val_accuracy: 0.4996
Epoch 3/100
313/313 [==============================] - 6s 18ms/step - loss: 0.7392 - accuracy: 0.4992 - val_loss: 0.7300 - val_accuracy: 0.5032
Epoch 4/100
313/313 [==============================] - 6s 18ms/step - loss: 0.7332 - accuracy: 0.4992 - val_loss: 0.7240 - val_accuracy: 0.5048
Epoch 5/100
...............................................................................................
313/313 [==============================] - 6s 18ms/step - loss: 0.6931 - accuracy: 0.5112 - val_loss: 0.6934 - val_accuracy: 0.5028
Epoch 33/100
313/313 [==============================] - 6s 18ms/step - loss: 0.6931 - accuracy: 0.5127 - val_loss: 0.6933 - val_accuracy: 0.5028

조기 종료(33회) 시의 에포크를 확인해보자! 정확도도 약 80%로 뛰어난 편이라고 할 수 있다.

 

이제 훈련 손실과 검증 손실을 이용해 그래프를 그려보자.

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend(['train','val'])
plt.show()

단어 임베딩

순환 신경망에서 텍스트 처리 방법의 일종으로,

각 단어를 고정된 크기의 실수 벡터로 만들어 주는 것.!

 

보통 NLP에서는 원-핫 인코딩 보다 단어 임베딩이 더 좋다고한다..!

(원핫 인코딩된 숫자보다 의미 있는 값으로 채워줄 수 있다고해서)

 

또한 단어 임베딩의 장점 : 입력으로 정수를 받을 수 있다..!

<<즉, 원-핫 인코딩으로 변경된 train_oh 배열이 아니 train_seq를 사용할 수 있다. -> 메모리 효율적>>

 

이 또한 케라스 라이브러리에 있는 모듈을 사용해보자.

model2 = keras.Sequential()
model2.add(keras.layers.Embedding(500, 16, input_length = 100))
model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation = 'sigmoid'))

Embedding에 있는 500은 어휘 사전의 크기이다. (앞서서 설정한 500)

              ""               16은  임베딩 벡터의 크기이다.

 

모델 요약!

model2.summary()

Model2: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_1 (Embedding)     (None, 100, 16)           8000      
                                                                 
 simple_rnn_2 (SimpleRNN)    (None, 8)                 200       
                                                                 
 dense_2 (Dense)             (None, 1)                 9         
                                                                 
=================================================================
Total params: 8,209
Trainable params: 8,209
Non-trainable params: 0
_________________________________________________________________

 

 

키워드 :  순차 데이터

               피드포워드 신경망

               타임스텝

               은닉 상태

               

 

댓글 등 텍스트를 인식하여 어떠한 평가, 의견등을 분석해 볼 수 있을까??

ex) 상품 판매 홈페이지에서 댓글을 통해 상품에 대한 긍정 / 부정적인 의견 구분하기

 

순차 데이터

순차 데이터는 텍스트, 리스트와 같이 순서에 의미가 있는 데이터를 말한다. ( 시계열 데이터 )

우리가 여태까지 사용했던 feature 데이터 등은 순서가 중요하지 않았다.

( 사용자 A : "별로지만 추천해요." ==> 긍정 부정 판단시 애매함, 순서 고려해야함 !! )

 

앞서 우리가 머신러닝 말고도 딥러닝의 완전 연결 신경망이나 CNN에서는 하나의 샘플에 대해 정방향 계산을 수행하고 

나면, 그 샘플은 버려지고 다시 사용하지 않았다.!!

이렇게 입력데이터의 흐름이 앞으로만 전달되는 신경망을 피드포워드 신경망이라고 한다.!!

신경망이 이전에 처리했던 샘플을 처리하는데 재사용하기 위해 이렇게 데이터의 흐름 앞으로 전해져선 안된다고 한다.

다음 샘플을 위해선 이전 데이터가 신경망 층에 순환될 필요가 있다. 이러한 신경망을 순환 신경망이라고 한다.

 

순환 신경망

순환 신경망은 일반적인 완전 연결 신경망과 거의 비슷하다.

완전 연결 신경망에서 이전 데이터의 처리 흐름을 순환하는 고리 하나만 추가하면 된다!!!

( 아래 그림에서 은닉층에 있는 붉은 고리처럼 )

그림을 보면 뉴런의 출력이 다시 자기 자신으로 되돌아온다. (재사용)

 

예를 들어 A,B,C 3개의 샘플을 처리하는 순환 신경망의 뉴런을 가정해보자. 첫 번째 A를 처리하고 난 뒤는 어떻게 될까?

첫 번째 샘플인 A를 처리하고, 다시 뉴런으로 들어간다. 그다음 B를 처리할 때 앞에서 A를 사용해 만든 출력 OA를 함께 사용한다.

따라서 OB에는 A에 대한 정보도 어느정도 포함되어 있을 것이다. 그다음 C도 마찬가지로 Ob를 함꼐 사용한다. (누적되겠지?)

이렇게 샘플을 처리하는 한 단계를 타임스텝이라고 말한다.

※OC에는 A보다 B에 대한 정보가 더 많다!!※--> 최신화 가중치?

 

순환 신경망에서는 층을 이라고 말한다.

하나의 셀에는 여러 개의 뉴런이 있지만 완전 연결 신경망과 달리 뉴런을 모두 표시하지 않고 하나의 셀로 층을 표현한다. 

또 셀의 출력을 은닉 상태라고 말한다.!!

 

--> summary : 기존의 연결 신경망 : 입력에 어떤 가중치를 곱하고 활성화 함수를 통과시                             켜 다음층으로 보내는것. 

                            + 층의 출력을 다음 스텝에서 재사용하면 순환 신경망

 

일반적으로 은닉층의 활성화 함수는 하이퍼볼릭 탄젠트 함수를 사용한다. tanh 

모양은 시그모이드함수와 비슷하게 생겼다.

차이점은 시그모이드와 달리 -1 ~ 1의 범위를 가진다.

 

아까 순환신경망은 재사용 빼고 다 같다고 했는데, 그렇다면 가중치는 어떻게 될까??

 

순환 신경망에서의 뉴런은 가중치가 하나 더 있다. 

이전 타임스텝의 은닉 상태에 곱해지는 가중치

Wx : 입력에 곱해지는 가중치

Wh : 이전 타임스텝의 은닉 상태에 곱해지는 가중치

셀의 은닉 상태 ( = 출력 )가 다음 타임스텝에서 재사용되기 때문에 타임스텝으로 셀을 나눠서 다시 그림을 보자.

 

셀의 가중치와 입출력

순환 신경망의 셀에서 필요한 가중치의 크기를 계산 해보자.

입력층 4개, 순환층 3개

Wx :  4 x 3 = 12

Wh :  3 x 3 = 9개

Wh의 크기는 n^2이다.

 

이제 모델 파라미터 개수 = 가중치 + 절편

12 + 9 + 3 이므로 24가지의 파라미터가 있음을 확인할 수 있다.!!

 

 

순환층의 입력 / 출력

CNN을 생각해 보면 합성곱 층의 입력은 전형적으로 하나의 샘플이 3개의 차원을 가집니다.

너비, 높이, 채널이다. 입력이 합성곱 층과 풀링 층을 통과하면 너비, 높이, 채널의 크기가 달라지지만 차원의 개수는 그대로 유지된다 !!!

 

순환층은 일반적으로 샘플마다 2개의 차원을 가진다. 

보통 하나의 샘플을 하나의 시퀀스라고 말한다고 한다고 한다.

시퀀스 안에는 여러 개의 아이템이 들어 있는데, 여기에서 시퀀스의 길이가 타임스텝의 길이가 된다!!!

 

ex) " I am a boy. "

이 샘플은 4개의 단어로 이루어져 있는데, 각 단어를 3개의 어떤 숫자로 표현한다고 가정해보자.

이런 입력이 순환층을 통과하면 두 번째, 세 번째 차원이 사라지고 순환층의 뉴런 개수만큼 출력된다.

이를 차근차근 설명해 보겠다. 

 

 

 

 

 

가중치 시각화

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