케라스로 딥러닝하기

0. 시작하기전에

김태영, 블록과 함께하는 파이썬 딥러닝 케라스, 디지털북스, 2017 을 읽고 (복습을 위해)마음대로 정리한 것입니다. 설명이 많이 부족하니 자세한 내용은 책이나 저자의 블로그를 참고하세요.

1. 케라스(Keras)

케라스는 Theano와 TensorFlow를 백엔드(back-end)로 사용하는 딥러닝 라이브러리로서 Torch에서 영감을 얻어 직관적 API를 제공합니다. 창시자는 Google의 소프트웨어 엔지니어인 Francois Chollet입니다.

  • 직관적 API
  • Theano, TensorFlow가 백엔드
  • 빠르게 성장하고 있는 프레임워크

1.1 케라스의 기본개념

케라스로 딥러닝모델을 만들 때는 다음과 같은 순서로 작성합니다.

  1. 데이터 불러오고 전처리
  2. 모델을 구성하기
  3. 모델 학습과 평가하기
  4. 모델로 예측하기

2. 레이어

케라스에서 사용되는 레이어는 층층이 쌓아서 모델을 만드는 개념입니다.

2.1 인공 신경망

케라스에서는 Dense라는 클래스로 구현이 되어 있습니다. 아래와 같이 사용하죠.

Dense(8, input_dim = 4, activation = 'relu')
  • 첫번째 인자 : 출력 뉴런의 수
  • input_dim : 입력 뉴런의 수, 입력층에서만 사용
  • activation : 활성화 함수
    • linear(default 값): 계산된 값이 그대로 출력
    • sigmoid : 이진 분류 문제에 주로 사용
    • relu : 은닉층에 주로 사용
    • softmax : 출력층에 주로 사용

2.2 레이어 쌓기

4개의 입력값을 받아 이진 분류하는 모델은 다음과 같습니다.

model = Sequential()
model.add(Dense(8, input_dim = 4, activation = 'relu'))
model.add(Dense(8, activation = 'relu'))
model.add(Dense(1, activation = 'sigmoid'))

2.3 다층 퍼셉트론 신경망 모델

간단한 다층 퍼셉트론 신경망 모델로 이진 분류를 해보겠습니다. 예제로 사용할 데이터는 Pima 인디언의 당뇨병 발병 데이터 입니다.

1) 데이터 준비하기

이제 ics.uci.edu 에서는 더이상 제공되지 않습니다. 그래서 저는 Kaggle에서 diabetes.csv 파일을 다운로드 했습니다. 간략하게 파일을 살펴보면 다음과 같습니다.

  • 인스턴스의 수 : 768개
  • 속성의 수 : 8 (자세한것은 생략)
  • 클래스의 수 : 2 (당뇨병 유/무)

pandas로 데이터의 모양을 살펴보겠습니다.

In [1]:
import pandas as pd

df = pd.read_csv("../diabetes.csv")
df.tail()
Out[1]:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
763 10 101 76 48 180 32.9 0.171 63 0
764 2 122 70 27 0 36.8 0.340 27 0
765 5 121 72 23 112 26.2 0.245 30 0
766 1 126 60 0 0 30.1 0.349 47 1
767 1 93 70 31 0 30.4 0.315 23 0

2) 데이터 전처리

데이터를 불러오기 앞서, 랜덤 시드를 명시적으로 정합니다. 이것을 하지 않으면 실행할 때마다 다른 결과가 나오므로 파라미터 조정시에는 고정해 주는것이 좋습니다.

In [2]:
import numpy as np

np.random.seed(5)  # 랜덤시드 고정시키기
# 다운받은 파일이 같은 폴더에 있다고 가정합니다.
dataset = np.genfromtxt("../diabetes.csv", delimiter=",")
# 데이터를 학습용과 검증용으로 나눕니다.
x_train = dataset[1:700, 0:8]  # 700개는 학습용으로 68개는 검증용으로 쓰겠습니다
y_train = dataset[1:700, 8]
x_test = dataset[700:, 0:8]
y_test = dataset[700:, 8]
x_train  # 데이터를 확인해봅니다.
Out[2]:
array([[   6.   ,  148.   ,   72.   , ...,   33.6  ,    0.627,   50.   ],
       [   1.   ,   85.   ,   66.   , ...,   26.6  ,    0.351,   31.   ],
       [   8.   ,  183.   ,   64.   , ...,   23.3  ,    0.672,   32.   ],
       ..., 
       [   3.   ,  169.   ,   74.   , ...,   29.9  ,    0.268,   31.   ],
       [   0.   ,   99.   ,    0.   , ...,   25.   ,    0.253,   22.   ],
       [   4.   ,  127.   ,   88.   , ...,   34.5  ,    0.598,   28.   ]])

3) 모델 구성하기

Dense 레이어만을 사용하여 다층 퍼셉트론 신경망 모델을 만들겠습니다.

  • 첫번째 레이어는 은닉층으로 8개의 속성을 입력받아 12개로 출력합니다.
  • 두번째 레이어는 은닉층으로 12개의 뉴런을 입력받아 8개로 출력합니다.
  • 마지막 레이어는 출력 레이어로 8개의 뉴런을 입력받아 1개의 뉴런을 출력합니다.

은닉층은 relu 함수를 사용하고 출력층은 sigmoid 함수를 사용하겠습니다.

In [3]:
import keras
from keras.models import Sequential
from keras.layers import Dense

# 모델 만들기
model = Sequential()
model.add(Dense(12, input_dim=8, activation="relu"))
model.add(Dense(8, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
# 모델 학습과정 설정
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])
# loss : 평가에 사용된 손실 함수입니다. 이진 분류에서는 binary_crossentropy를 사용합니다
# optimizer : 최적 파라미터를 찾는 알고리즘으로 경사 하강법의 하나인 adam을 사용합니다.
# metrics : 평가척도, 분류문제에서는 보통 accuracy를 씁니다.
Using TensorFlow backend.

4) 학습과 평가

먼저 fit() 기능을 사용해 모델을 학습시킵니다. 필요한 설정은 다음과 같습니다.

  • 첫번째 인자 : 입력 변수, 여기서는 8가지 속성입니다.
  • 두번째 인자 : 출력 변수, 여기서는 당뇨병 유/무 입니다.
  • batch_size : 가중치를 업데이트할 배치 크기
  • verbose : 학습과정 표시여부, 보고 싶다면 1

시험셋을 이용해 학습한 모델을 평가하겠습니다. evaluate() 기능을 이용해 간단히 수행할 수 있습니다.

In [4]:
hist = model.fit(x_train, y_train, epochs=200, batch_size=64, verbose=0)
# 모델 평가하기
score = model.evaluate(x_test, y_test)
print("정확도는 {}% 입니다.".format(score[1] * 100))
69/69 [==============================] - 0s 306us/step
정확도는 73.91304365102795% 입니다.

2.4 히스토리 기능

히스토리 기능은 케라스에 기본으로 탑재되어 있습니다. 별도의 설정없이 아래와 같이 사용할 수 있습니다.

In [5]:
import matplotlib.pyplot as plt

%matplotlib inline

fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2, sharey=False)
ax0.plot(hist.history["acc"], label="acc")
ax0.set(title="Accuracy")
ax1.plot(hist.history["loss"], label="loss")
ax1.set(title="Accuracy")
Out[5]:
[<matplotlib.text.Text at 0x7fd29067ec88>]
No description has been provided for this image

3. 합성곱(convolution) 신경망 레이어

케라스에서 제공하는 합성곱 레이어 중 영상처리에 주로 사용되는 Conv2D 레이어를 살펴 보겠습니다. 아래는 사용 예제 입니다.

Conv2D(1, (2, 2), padding = 'valid', input_shape = (4, 4, 1), activation = 'relu')
  • 첫번째 인자: 합성곱 필터의 숫자
  • 두번째 인자: 합성곱 커널의 (행,열)
  • padding : 경계 처리 방법
    • valid : 출력사이즈는 입력 사이즈보다 작아집니다.
    • same : 출력 이미지 사이즈와 입력 사이즈가 동일합니다.
  • input_shape : 입력형태, (행, 열, 채널 수); 채널은 흑백이면 1, 컬러면 3입니다.
  • activation : 활성화 함수를 설정

사소한 변화를 무시하는 Max Pooling 레이어 1차원으로 바꾸는 Flatten 레이어

3.1 합성곱(convolution) 신경망 모델 만들기

이미지 기반의 분류에는 합성곱 신경맘 모델이 적합합니다. 여기서 우리는 이미지파일의 분류를 해보도록 하겠습니다.

1) 데이터 준비하기

저자의 블로그 에서 제공하고 있는 데이터를 가지고 살펴 보겠습니다. 원, 사각형, 삼각형 데이터셋을 예제로 살펴보겠습니다. 구성은 훈련셋과 시험셋으로 되어 있습니다.
아래 그림은 훈련셋입니다.

훈련 데이터

Figure from tykimos.github.io

다음은 시험용 데이터입니다. 시험 데이터

Figure from tykimos.github.io

2) 데이터 전처리

수정된 전체 코드는 다음과 같습니다. 참고로 시험셋은 데이터 부풀리기를 할 필요가 없으니, test_datagen 객체 생성 시에는 별도의 파라미터를 추가하지 않았습니다. 그리고 fit_generator함수에서 steps_per_epoch의 값은 기존 15개에서 더 많은 수 (현재 예는 1500개)로 설정합니다.
batch_size * steps_per_epoch가 전체 샘플 수 인데, 데이터 부풀리기를 하지 않을 때는 기존의 15개의 배치사이즈(3개)로 전체 45개를 모두 학습에 사용할 수 있지만, ImageDataGenerator함수를 통해 데이터 부풀리기는 할 때는 하나의 샘플로 여러 개의 결과를 얻기 때문에 요청하는 데로 무한의 샘플이 제공됩니다.
여기서는 100배 정도인 1500개로 설정했습니다.

In [6]:
# 필요한 라이브러리 불러오기
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Flatten, Dropout
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.preprocessing.image import ImageDataGenerator

# 랜덤시드 고정시키기
np.random.seed(5)
# 데이터셋 불러오기
train_datagen = ImageDataGenerator(
    rescale=1.0 / 255,
    rotation_range=10,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.7,
    zoom_range=[0.9, 2.2],
    horizontal_flip=True,
    vertical_flip=True,
    fill_mode="nearest",
)

train_generator = train_datagen.flow_from_directory(
    "../hard_handwriting_shape/train",
    target_size=(24, 24),
    batch_size=3,
    class_mode="categorical",
)

test_datagen = ImageDataGenerator(rescale=1.0 / 255)

test_generator = test_datagen.flow_from_directory(
    "../hard_handwriting_shape/test",
    target_size=(24, 24),
    batch_size=3,
    class_mode="categorical",
)
Found 45 images belonging to 3 classes.
Found 15 images belonging to 3 classes.

3) 모델 학습및 평가하기

In [ ]:
import warnings

warnings.filterwarnings("ignore", message="numpy.dtype size changed")
# numpy로인한 경고 무시

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), activation="relu", input_shape=(24, 24, 3)))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dense(3, activation="softmax"))

# 모델 설정하기
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])

# 모델 학습시키기
model.fit_generator(
    train_generator,
    steps_per_epoch=1000,
    epochs=20,
    validation_data=test_generator,
    validation_steps=5,
    verbose=0,
)

# 모델 평가하기
scores = model.evaluate_generator(test_generator, steps=5)
print("예측의 정확도는 {} % 입니다.".format(scores[1] * 100))

4) 모델로 예측하기

In [7]:
# 모델 예측하기
print("-- 예측 --")
output = model.predict_generator(test_generator, steps=5)
np.set_printoptions(formatter={"float": lambda x: "{0:0.3f}".format(x)})
print(output)
예측의 정확도는 73.33333373069763 % 입니다.

도형을 분류하는 간단한 문제에서 데이터 부풀리기 방법에 대하여 알아보았고, 각 파라미터 별로 어떻게 데이터가 부풀어지는지 살펴보았습니다.

데이터 부풀리기 방법은 학습용 데이터가 충분하지 않을때 성능 개선에 큰 도움을 줍니다.

4. 순환 신경망 모델 RNN (Recurrent Neural Network)

순환 신경망 모델은 순차적인 자료에서 규칙을 찾습니다. 케라스에서 제공하는 순환 신경망 레이어중 가장 흔히 쓰이는 LSTM(Long Short-Term Memory units) 레이어를 알아보겠습니다.

LSTM 레이어

사용법은 Dense 레이어와 비슷하지만, 시퀀스 출력여부와 상태 유지 설정으로 다양한 형태로 구성할 수 있습니다.

LSTM(3, input_dim= 1, input_length=4)
  • 첫번째 인자:
  • input_dim
  • input_length:

4.1 순환 신경망 모델 만들기

앞서 살펴본 LSTM 레이어를 사용해 순환 신경망 모델을 만들어 보겠습니다.

1) 데이터 준비

준비한 데이터는 사람의 Y염색체 DNA서열중 반복서열이 있는 조각입니다. 서열은 아래와 같습니다.

AGATAGACAGATTAGATGATAGGGAGATGATACATAGAAGGTAGATAGA
TAGATAGATAGATAGATAGATAGATAGATAGATAGATAATACATAGATA
ATTAATGGATAGATATATAGA

약 87%의 정확도로 AGAT가 반복됩니다. 길이는 약 30번 반복이고요.

DNA 서열 처럼 문자로 구성되어 있는 것은 모델 입출력으로 사용할 수 없기 때문에 각각을 를 숫자로 변환할 수 있는 사전을 하나 만들어봅니다.
첫번째 사전은 DNA를 숫자로, 두번째 사전은 숫자를 DNA로 만들어 줍니다.

In [22]:
# 사용할 패키지 불러오기
import keras
from keras.models import Sequential
from keras.layers import Dense, LSTM
from keras.utils import np_utils
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

# 코드 사전 정의
code2idx = {"A": 0, "C": 1, "T": 2, "G": 3}
idx2code = {0: "A", 1: "C", 2: "T", 3: "G"}

이제 DNA 서열을 가지고 학습용 데이터로 만들어 보겠습니다.

In [23]:
# 2. 데이터셋 생성하기
seq = list(
    "AGATAGACAGATTAGATGATAGGGAGATGATACATAGAAGG\
TAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAATACATA\
GATAATTAATGGATAGATATATAGA"
)


# 데이터셋 생성 함수
def seq2dataset(seq, window_size):
    dataset = []
    for i in range(len(seq) - window_size):
        subset = seq[i : (i + window_size + 1)]
        dataset.append([code2idx[item] for item in subset])
    return np.array(dataset)


dataset = seq2dataset(seq, window_size=4)

print(dataset.shape)
print(dataset[:4])  # 앞쪽 4개만 확인하기
(115, 5)
[[0 3 0 2 0]
 [3 0 2 0 3]
 [0 2 0 3 0]
 [2 0 3 0 1]]

DNA 서열을 5개씩 자르고 각각에 DNA를 숫자로 변환했습니다.

2) 데이터 전처리하기

In [24]:
# 입력(X)과 출력(Y) 변수로 분리하기
x_train = dataset[:, 0:4]
y_train = dataset[:, 4]

max_idx_value = 13

# 입력값 정규화 시키기
x_train = x_train / float(max_idx_value)

# 입력을 (샘플 수, 타입스텝, 특성 수)로 형태 변환
x_train = np.reshape(x_train, (115, 4, 1))

# 라벨값에 대한 one-hot 인코딩 수행
y_train = np_utils.to_categorical(y_train)

one_hot_vec_size = y_train.shape[1]

print("one hot encoding vector size is ", one_hot_vec_size)
one hot encoding vector size is  4

3) 모델 학습 및 평가하기

In [25]:
# 3. 모델 구성하기
model = Sequential()
model.add(LSTM(128, input_shape=(4, 1)))
model.add(Dense(one_hot_vec_size, activation="softmax"))

# 4. 모델 학습과정 설정하기
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])


# 손실 이력 클래스 정의
class LossHistory(keras.callbacks.Callback):
    def init(self):
        self.losses = []

    def on_epoch_end(self, batch, logs={}):
        self.losses.append(logs.get("loss"))


history = LossHistory()  # 손실 이력 객체 생성
history.init()

# 5. 모델 학습시키기
model.fit(x_train, y_train, epochs=2000, batch_size=14, callbacks=[history], verbose=0)

# 6. 학습과정 시각화
plt.plot(history.losses)
plt.ylabel("loss")
plt.xlabel("epoch")
plt.show()

# 7. 모델 평가하기
scores = model.evaluate(x_train, y_train)
print("정확도는 {} % 입니다.".format(scores[1] * 100))

# 8. 모델 사용하기
pred_count = 115  # 최대 예측 개수 정의
No description has been provided for this image
115/115 [==============================] - 0s 810us/step
정확도는 87.82608742299287 % 입니다.

4) 모델로 예측하기

In [26]:
# 한 구절만 예측
# AGAT
seq_out = ["A", "G", "A", "T"]
pred_out = model.predict(x_train)

for i in range(pred_count):
    idx = np.argmax(pred_out[i])  # one-hot 인코딩을 인덱스 값으로 변환
    seq_out.append(
        idx2code[idx]
    )  # seq_out는 최종 서열이므로 인덱스 값을 코드로 변환하여 저장

print("조각씩 서열 예측하기  : ", "".join(seq_out))
조각씩 서열 예측하기  :  AGATAGATAGATAAGATAATAGAGAGATAATAGATAGATGGTAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGTGCATAGATAGTGAGTGGATAGATAGAGAGA
In [27]:
# 곡 전체 예측

seq_in = ["A", "G", "A", "T"]
seq_out = seq_in
# 코드를 인덱스값으로 변환
seq_in = [code2idx[it] / float(max_idx_value) for it in seq_in]

for i in range(pred_count):
    sample_in = np.array(seq_in)
    sample_in = np.reshape(sample_in, (1, 4, 1))  # 샘플 수, 타입스텝 수, 속성 수
    pred_out = model.predict(sample_in)
    idx = np.argmax(pred_out)
    seq_out.append(idx2code[idx])
    seq_in.append(idx / float(max_idx_value))
    seq_in.pop(0)

print("한번에 서열 예측하기: ", "".join(seq_out))
한번에 서열 예측하기:  AGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGA

예측결과 확인

Sequence alignment 한 결과는 다음과 같습니다.

Original       AGATAGACAGATTAGATGATAGGGAGATGATACATAGAAGGTAGATAGATAGATAGATAG	60
step-wise      AGATAGATGGATAAGATAATAGAGAGATAATAGATAGATGGTAGATAGATAGATAGATAG	60
whole          AGATAGATAGATAGATAGATAGATAGAT--AGATAGATAGATAGATAGATAGATAGATAG	58
               *******  ***      ****  ****           * *******************

Original       ATAGATAGATAGATAGATAGATAGATAATACATAGATAATT--AATGGATAGATATATAG	118
step-wise      ATAGATAGATAGATAGATAGATAGATAGTACATAGATAGTA--AGTAAATAGATAGATAT	118
whole          ATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAGATAG	118
               ***************************                  *  ******* *** 

Original       A	119
step-wise      A	119
whole          A	119
               *

조각조각 예측하는 방법이 전체를 한번에 예측하는 것 보다는 정확한 것을 알 수 있습니다.

5. 상태유지 LSTM 모델

이번에는 상태유지(Stateful) LSTM 모델에 대해서 알아보겠습니다. 여기서 상태유지라는 것은 현재 학습된 상태가 다음 학습 시 초기 상태로 전달된다는 것을 의미합니다

상태 유지 모드에서는 현재 샘플의 학습 상태가 다음 샘플의 초기 상태

긴 시퀀스 데이터를 처리할 때 권장됩니다. LSTM 레이어 생성시 다음과 같이 처리하면 됩니다.

model = Sequential()
model.add(LSTM(128, batch_input_shape = (1, 4, 1), stateful = True))
model.add(Dense(one_hot_vec_size, activation='softmax'))

5.1 상태유지 순환 신경망 RNN 모델 만들기

1) 데이터 준비

시간의 흐름에 따라 진폭이 -1 에서 1사이로 변하는 코사인 데이터를 만들어 보겠습니다. 이러한 데이터를 시계열 수치라고 하죠.

In [55]:
# 0. 사용할 패키지 불러오기
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM, Dropout
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt

%matplotlib inline
# 1. 데이터셋 생성하기
signal_data = np.cos(np.arange(1000) * (20 * np.pi / 1000))[:, None]
plt.plot(np.arange(1000), signal_data)  # 생성한 데이터 시각화
Out[55]:
[<matplotlib.lines.Line2D at 0x7fd12f04d588>]
No description has been provided for this image

2) 데이터 전처리

데이터를 모델에 학습시키기 위해서 전처리가 필요합니다.

In [56]:
def create_dataset(signal_data, look_back=1):
    dataX, dataY = [], []
    for i in range(len(signal_data) - look_back):
        dataX.append(signal_data[i : (i + look_back), 0])
        dataY.append(signal_data[i + look_back, 0])
    return np.array(dataX), np.array(dataY)


class CustomHistory(keras.callbacks.Callback):
    def init(self):
        self.train_loss = []
        self.val_loss = []

    def on_epoch_end(self, batch, logs={}):
        self.train_loss.append(logs.get("loss"))
        self.val_loss.append(logs.get("val_loss"))


look_back = 40

# 데이터 전처리
scaler = MinMaxScaler(feature_range=(0, 1))
signal_data = scaler.fit_transform(signal_data)

# 데이터 분리
train = signal_data[0:700]
val = signal_data[600:700]
test = signal_data[700:]

# 데이터셋 생성
x_train, y_train = create_dataset(train, look_back)
x_val, y_val = create_dataset(val, look_back)
x_test, y_test = create_dataset(test, look_back)

# 데이터셋 전처리
x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
x_val = np.reshape(x_val, (x_val.shape[0], x_val.shape[1], 1))
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))

3) 모델 학습과 평가

시계열 수치를 입력받아 예측하기 위해 상태 유지 다층 순환신경망 모델을 사용하겠습니다. 코드는 아래와 같습니다.

학습 시간이 아주 오래걸리니 주의하세요.

In [57]:
# 2. 모델 구성하기
model = Sequential()
for i in range(2):
    model.add(
        LSTM(
            32,
            batch_input_shape=(1, look_back, 1),
            stateful=True,
            return_sequences=True,
        )
    )
    model.add(Dropout(0.3))
model.add(LSTM(32, batch_input_shape=(1, look_back, 1), stateful=True))
model.add(Dropout(0.3))
model.add(Dense(1))

# 3. 모델 학습과정 설정하기
model.compile(loss="mean_squared_error", optimizer="adam")

# 4. 모델 학습시키기
custom_hist = CustomHistory()
custom_hist.init()

for i in range(50):
    model.fit(
        x_train,
        y_train,
        epochs=1,
        batch_size=1,
        shuffle=False,
        callbacks=[custom_hist],
        validation_data=(x_val, y_val),
        verbose=0,
    )
    model.reset_states()

학습과정을 시각화해보도록 하겠습니다.

In [58]:
# 5. 학습과정 살펴보기
plt.plot(custom_hist.train_loss)
plt.plot(custom_hist.val_loss)
plt.ylim(0.0, 0.15)
plt.ylabel("loss")
plt.xlabel("epoch")
plt.legend(["train", "val"])
plt.show()
No description has been provided for this image

모델이 얼마나 정확한지 확인하겠습니다.

In [59]:
# 6. 모델 평가하기
trainScore = model.evaluate(x_train, y_train, batch_size=1, verbose=0)
model.reset_states()
print("Train Score: ", trainScore)
valScore = model.evaluate(x_val, y_val, batch_size=1, verbose=0)
model.reset_states()
print("Validataion Score: ", valScore)
testScore = model.evaluate(x_test, y_test, batch_size=1, verbose=0)
model.reset_states()
print("Test Score: ", testScore)
Train Score:  0.00237784640335
Validataion Score:  0.000651868844193
Test Score:  0.00211231139425

4) 모델로 예측하기

모델을 가지고 코사인 곡선이 그려지는지 예측해보겠습니다. 그리고 원본과 겹쳐서 시각화해보겠습니다.

In [61]:
# 7. 모델 사용하기
look_ahead = 260
xhat = x_test[0]
predictions = np.zeros((look_ahead, 1))
for i in range(look_ahead):
    prediction = model.predict(np.array([xhat]), batch_size=1)
    predictions[i] = prediction
    xhat = np.vstack([xhat[1:], prediction])

plt.figure(figsize=(12, 5))
plt.plot(np.arange(look_ahead), predictions, "r", label="prediction")
plt.plot(np.arange(look_ahead), y_test[:look_ahead], label="test function")
plt.legend()
plt.show()
No description has been provided for this image

그림에서 볼 수 있듯이 진폭과 주기가 유사한 형태의 결과를 얻었습니다. 상태유지 모드에서는 보통 배치 사이즈를 1로 설정합니다. 하지만 서로 성격이 다른 시계열 자료가 있다면 갯수를 추가해야 합니다. 예를 들어 주식 3종목을 학습한다면 배치 사이즈는 3이 되어야 합니다.

마치며

딥러닝 라이브러리 케라스에 대해 알아 보았습니다. 케라스는 너무나도 편리합니다. 짧은 코드로 다양한 딥러닝 모델(다층 퍼셉트론, 합성곱 신경망, 순환 신경망등)을 쉽게 생성하고 테스트 해볼수 있죠.

진짜 문제는 어느 상황에 어떠한 모델을 쓰고 적절한 파라미터 값은 무엇인지 아는 것입니다. 이것은 좀 더 많은 경험과 공부가 필요 할 것 같습니다.

텐서플로(Tensorflow)로 배우는 딥러닝

출처: 다카이 에츠지, 텐서플로로 시작하는 딥러닝, Jpub, 2017

시작하며

텐서플로로 시작하는 딥러닝이라는 책을 읽고 복습하기 위한 jupyter notebook입니다. 마음대로 정리한 것이다 보니, 설명은 충분하지 않습니다. 구체적인 내용은 을 읽어보시기 바랍니다.

파이썬과 텐서플로의 최근 버전에 맞추어 코드를 조금씩 수정하였습니다.

1장. 텐서플로 입문

텐서플로(TensorFlow)는 구글이 오픈 소스로 공개한 머신러닝 라이브러리 입니다.

머신러닝의 3단계

머신러닝은 데이터 속에 있는 수학적 규칙을 컴퓨터로 발견해 내는 방법입니다. 여기서는 다음의 3단계에 걸쳐서 머신러닝을 수행합니다.

  1. 주어진 데이터로 미지의 데이터를 예측하는 수식을 생각한다.
  2. 수식에 포함된 파라미터를 판단하는 오차 함수를 준비한다.
  3. 오차 함수를 최소화 할 수 있도록 파라미터 값을 결정한다.

텐서플로 예제

먼저 필요한 라이브러리를 불러옵니다. 여기서는 tensorflow와 수치계산에 필요한 Numpy와 시각화를 위한 matplotlib을 추가로 사용했습니다.

In [1]:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

tf.__version__  # 사용한 텐서플로 버전 확인
Out[1]:
'1.3.0'

문제 설명

월별 평균 기온을 예측하는 문제를 텐서플로를 사용해 풀어 보겠습니다. 다음은 1년간 월별 평균 기온입니다.

1월 | 2월 | 3월 | 4월 | 5월 | 6월 | 7월 | 8월| 9월| 10월| 11월| 12월 ---|---|---|---|---|---|---|---|---|---|---|---|--- 5.2 | 5.7 | 8.6 | 14.9 | 18.2 | 20.4 | 25.5 | 26.4 | 22.8 | 17.5 | 11.1 | 6.6

이 데이터를 기반으로 내년의 월별 평균 기온을 예측하려면 어떻게 해야 할까요?

In [2]:
# 월별 평균기온을 시각화 하기
temp = [5.2, 5.7, 8.6, 14.9, 18.2, 20.4, 25.5, 26.4, 22.8, 17.5, 11.1, 6.6]
plt.plot(temp, "o-")
Out[2]:
[<matplotlib.lines.Line2D at 0x7f5b04830470>]
No description has been provided for this image

그림에서 볼 수 있듯이, 월별 평균 기온에서는 규칙성이 있어 보입니다. 이처럼 주어진 데이터에서 법칙을 발견한다면 내년의 기온을 계산할 수 있을 것입니다. 아래의 코드는 텐서플로를 사용해 규칙을 찾는 방법입니다.

주요 용어 설명

  • X는 트레이닝 세트로서 주어진 데이터로 구성되어 있습니다. 텐서플로에서는 이러한 변수를 placeholder라고 합니다.
  • w 최적화할 값입니다. 이과 같은 변수를 variable이라고 합니다.
  • y는 placeholder와 variable로부터 계산된 값입니다.
In [3]:
# Placeholder x를 정의한다.
x = tf.placeholder(tf.float32, [None, 5])
# Variable w를 정의한다.
w = tf.Variable(tf.zeros([5, 1]))
# 계산식 y를 정의한다.
y = tf.matmul(x, w)
# Placeholder t를 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
# 오차 함수 loss를 정의한다.
loss = tf.reduce_sum(tf.square(y - t))
# 트레이닝 알고리즘 train_step을 정의한다.
# AdamOptimizer는 트레이닝 알고리즘 중 한 종류
train_step = tf.train.AdamOptimizer().minimize(loss)
# 세션을 준비하고 Variable을 초기화한다.
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# 트레이닝 세트 데이터를 준비한다.
train_t = np.array([5.2, 5.7, 8.6, 14.9, 18.2, 20.4, 25.5, 26.4, 22.8, 17.5, 11.1, 6.6])
train_t = train_t.reshape([12, 1])
train_x = np.zeros([12, 5])
for row, month in enumerate(range(1, 13)):
    for col, n in enumerate(range(0, 5)):
        train_x[row][col] = month**n
# 경사 하강법을 이용한 파라미터 최적화를 100000회 반복한다.
i = 0
for _ in range(100000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 20000 == 0:
        loss_val = sess.run(loss, feed_dict={x: train_x, t: train_t})
        print("Step: %d, Loss: %f" % (i, loss_val))
Step: 20000, Loss: 29.290693
Step: 40000, Loss: 27.663746
Step: 60000, Loss: 24.766474
Step: 80000, Loss: 22.974419
Step: 100000, Loss: 22.416885

경사 하강법으로 파라미터를 최적화하면서 파라미터 보정을 10만회 반복합니다. 1만 회 실행할 때마다 오차 함숫값(Loss)을 계산해서 출력합니다.

실행결과를 보면 파라미터 보정을 반복하면 오차값이 감소함을 알 수 있습니다. 그러나 실제로 언제까지 감소하는지 예측하는것은 간단하지 않습니다. 여기서는 일단은 학습을 중단하고 기온을 예측해보겠습니다.

In [4]:
# 트레이닝 후 파라미터 값을 확인한다.
w_val = sess.run(w)
print("파라미터 값 : ", w_val)


# 트레이닝 후 파라미터를 이용해 예측기온을 계산하는 함수를 정의한다.
def predict(x):
    result = 0.0
    for n in range(0, 5):
        result += w_val[n][0] * x**n
    return result


# 예측기온 그래프를 그린다.
fig = plt.figure()
subplot = fig.add_subplot(1, 1, 1)
subplot.set_xlim(1, 12)
subplot.scatter(range(1, 13), train_t)
linex = np.linspace(1, 12, 100)
liney = predict(linex)
subplot.plot(linex, liney, color="b")
plt.show()
파라미터 값 :  [[ 3.76468992]
 [-1.58954322]
 [ 1.78510237]
 [-0.20117806]
 [ 0.00539352]]
No description has been provided for this image

얻어진 결과를 시각화하였습니다. 점으로 찍혀진 실제 데이터 사이로 예측 곡선이 지나가는 것을 확인 할 수 있습니다. 예측선을 통해서 온도를 예상할 수 있습니다. 이렇게 텐서플로의 기본적인 과정을 살펴보았습니다. 앞으로도 머신러닝의 3단계를 잘 기억해 두기 바랍니다.

2장. 분류 알고리즘의 기초

이항 분류기

데이터를 두 종류로 분류하는 것을 이항 분류기(binary classifier) 라고 합니다. 다시한번 머신러닝 모델의 3단계에 따라 단계적으로 알아보겠습니다.

여기서는 간단한 예로 바이러스 감염 확률을 계산해보도록 하겠습니다. 주어진 데이터를 감염되었다/감염되지 않았다로 분류하겠습니다. 분석할 임의의 데이터를 다음같이 만들겠습니다.

In [5]:
# 필요한 라이브러리를 불러옵니다.
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from numpy.random import multivariate_normal, permutation
import pandas as pd
from pandas import DataFrame

# 트레이닝 데이터를 준비한다.
np.random.seed(20180311)  # 난수 생성

n0, mu0, variance0 = 20, [10, 11], 20
data0 = multivariate_normal(mu0, np.eye(2) * variance0, n0)
df0 = DataFrame(data0, columns=["x1", "x2"])
df0["t"] = 0  # 비감염자 데이터

n1, mu1, variance1 = 15, [18, 20], 22
data1 = multivariate_normal(mu1, np.eye(2) * variance1, n1)
df1 = DataFrame(data1, columns=["x1", "x2"])
df1["t"] = 1  # 감염자 데이터

df = pd.concat([df0, df1], ignore_index=True)
train_set = df.reindex(permutation(df.index)).reset_index(drop=True)
train_set.head()  # 트레이닝 데이터 확인
Out[5]:
x1 x2 t
0 8.757376 13.871496 0
1 10.477299 6.924247 0
2 18.508789 16.273824 1
3 14.363211 18.352569 1
4 24.465507 18.948523 1

t의 값이 0이면 비감염자, 감염자면 1로 구분합니다. 표는 보기가 불편하니 시각화를 해보죠.

In [6]:
fig = plt.figure(figsize=(6, 6))
subplot = fig.add_subplot(1, 1, 1)
subplot.set_ylim([0, 30])
subplot.set_xlim([0, 30])
subplot.scatter(df1.x1, df1.x2, marker="x", label="Infected")
subplot.scatter(df0.x1, df0.x2, marker="o", label="Normal")
plt.legend()
plt.show()
No description has been provided for this image

직관적으로 감염자를 구분할 수 있는 가상의 선을 그려볼 수 있습니다. 이제 텐서플로를 사용해 풀어보도록 하겠습니다.

텐서플로로 계산할 때는 데이터를 numpy의 array로 사용해야 합니다.

In [7]:
train_x = train_set[["x1", "x2"]].as_matrix()
train_t = train_set["t"].as_matrix().reshape([len(train_set), 1])
In [8]:
#  트레이닝 세트 데이터에 대해 t=1일 확률을 구하는 계산식 p를 준비한다.
x = tf.placeholder(tf.float32, [None, 2])
w = tf.Variable(tf.zeros([2, 1]))
w0 = tf.Variable(tf.zeros([1]))
f = tf.matmul(x, w) + w0
p = tf.sigmoid(f)  # 로지스틱 회귀
# 오차 함수 loss와 트레이닝 알고리즘 train_step을 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
train_step = tf.train.AdamOptimizer().minimize(loss)  # 최우추정법
# 정답률 accuracy를 정의한다.
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 세션을 준비하고 Variable을 초기화한다.
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# 경사 하강법에 의한 파라미터 최적화를 20000회 반복한다.
i = 0
for _ in range(20000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 4000 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: train_x, t: train_t}
        )
        print("Step: %d, Loss: %f, Accuracy: %f" % (i, loss_val, acc_val))
Step: 4000, Loss: 13.849513, Accuracy: 0.885714
Step: 8000, Loss: 9.701274, Accuracy: 0.885714
Step: 12000, Loss: 7.537327, Accuracy: 0.942857
Step: 16000, Loss: 6.264729, Accuracy: 0.942857
Step: 20000, Loss: 5.439410, Accuracy: 0.971429

실행 결과에서 오차 함수의 값은 계속 감소하지만, 정확도는 일정한 값이상 올라가지 않습니다. 이쯤에서 학습을 중단하고 파라미터값을 확인하도록 하겠습니다.

In [9]:
w0_val, w_val = sess.run([w0, w])
w0_val, w1_val, w2_val = w0_val[0], w_val[0][0], w_val[1][0]
print("파라미터의 값: ", w0_val, w1_val, w2_val)
#  추출한 파라미터 값을 이용해 결과를 그래프로 출력한다.
train_set0 = train_set[train_set["t"] == 0]
train_set1 = train_set[train_set["t"] == 1]

fig = plt.figure(figsize=(6, 6))
subplot = fig.add_subplot(1, 1, 1)
subplot.set_ylim([0, 30])
subplot.set_xlim([0, 30])
subplot.scatter(train_set1.x1, train_set1.x2, marker="x")
subplot.scatter(train_set0.x1, train_set0.x2, marker="o")

linex = np.linspace(0, 30, 10)
liney = -(w1_val * linex / w2_val + w0_val / w2_val)  # 경계선 그리기
subplot.plot(linex, liney)

field = [
    [
        (1 / (1 + np.exp(-(w0_val + w1_val * x1 + w2_val * x2))))
        for x1 in np.linspace(0, 30, 100)
    ]
    for x2 in np.linspace(0, 30, 100)
]
subplot.imshow(
    field, origin="lower", extent=(0, 30, 0, 30), cmap=plt.cm.gray_r, alpha=0.5
)
plt.show()
파라미터의 값:  -16.915 0.583585 0.481423
No description has been provided for this image

농담(진하면 감염자)은 확률을 나타냅니다. 그래서 가상의 선의 경계부근에서 감염/비감염 데이터가 혼재되어 있다는 것을 알 수 있습니다.

테스트 세트를 통한 검증

머신러닝에서 트레이닝 세트에 대한 정확도를 계산하는 것은 그다지 의미가 없습니다. 중요한것은 미지의 데이터에 대한 예측 정확도를 향상시키는 것이기 때문입니다.

다수의 파라미터를 포함하는 모델을 사용하면 트레이닝 데이터에 대한 정확도는 높은 반면, 미지의 데이터에 대한 예측은 정확하지 못한 과적합(overfitting) 이 일어날 수 있습니다.

과적합을 피하기 위한 방법으로는 트레이닝 데이터를 임의로 나누어 (보통, 80%은 트레이닝, 20%는 테스트용) 놓는 방법이 있습니다. 여기서는 앞서 사용한 코드를 수정해 확인하도록 하겠습니다.

In [10]:
# 트레이닝 데이터 준비
n0, mu0, variance0 = 800, [10, 11], 20
data0 = multivariate_normal(mu0, np.eye(2) * variance0, n0)
df0 = DataFrame(data0, columns=["x", "y"])
df0["t"] = 0
n1, mu1, variance1 = 600, [18, 20], 22
data1 = multivariate_normal(mu1, np.eye(2) * variance1, n1)
df1 = DataFrame(data1, columns=["x", "y"])
df1["t"] = 1
df = pd.concat([df0, df1], ignore_index=True)
df = df.reindex(permutation(df.index)).reset_index(drop=True)
# 트레이닝 세트 데이터에서 20%의 데이터를 테스트 세트로 분리한다.
num_data = int(len(df) * 0.8)
train_set = df[:num_data]
test_set = df[num_data:]
# (x, y)와 t를 각각 모은 것을 NumPy의 array 오브젝트로 추출해둔다.
train_x = train_set[["x", "y"]].as_matrix()
train_t = train_set["t"].as_matrix().reshape([len(train_set), 1])
test_x = test_set[["x", "y"]].as_matrix()
test_t = test_set["t"].as_matrix().reshape([len(test_set), 1])
# 각종 계산식을 정의한다.
x = tf.placeholder(tf.float32, [None, 2])
w = tf.Variable(tf.zeros([2, 1]))
w0 = tf.Variable(tf.zeros([1]))
f = tf.matmul(x, w) + w0
p = tf.sigmoid(f)
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
train_step = tf.train.AdamOptimizer().minimize(loss)
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 세션을 준비하고 Variable을 초기화한다.
sess = tf.Session()
sess.run(tf.global_variables_initializer())
# 경사 하강법에 의한 파라미터 최적화를 2500회 반복하면서 트레이닝 세트와 테스트 세트에 대한 정답률 변화를 기록한다.
train_accuracy = []
test_accuracy = []
for _ in range(2500):
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    acc_val = sess.run(accuracy, feed_dict={x: train_x, t: train_t})
    train_accuracy.append(acc_val)
    acc_val = sess.run(accuracy, feed_dict={x: test_x, t: test_t})
    test_accuracy.append(acc_val)
# 결과를 그래프로 출력한다.
fig = plt.figure(figsize=(8, 6))
subplot = fig.add_subplot(1, 1, 1)
subplot.plot(
    range(len(train_accuracy)), train_accuracy, linewidth=2, label="Training set"
)
subplot.plot(range(len(test_accuracy)), test_accuracy, linewidth=2, label="Test set")
subplot.legend(loc="upper left")
Out[10]:
<matplotlib.legend.Legend at 0x7f5aa863dcc0>
No description has been provided for this image

현저하지는 않지만, 트레이닝 세트와 데스트 세트에서 정답률의 변화 양상이 다르다는 것을 수 있습니다.

임의로 만든 난수 데이터이기 때문에 차이가 적을 수 밖에 없습니다.

앞서 설명했듯 머신러닝으로 얻어진 모델의 성능테스트 데이터에 대한 정확도로 판정해야 한다는 것을 기억하세요.

3장. 단층 신경망을 이용한 분류

단층 신경망에 대한 설명

단층신경망

figure from [inbi](http://www.inbi.ai/case_study.html)

앞의 바이러스 감염확률을 계산하는 문제에서 단층 신경망을 사용해 분류를 진행 해보도록 하겠습니다. 여기서는 은닉 계층이 도입됨에 따라 달라지는 것에 집중하세요. 먼저, 필요한 모듈을 불러오고, 트레이닝 데이터를 생성하겠습니다.

In [24]:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from numpy.random import multivariate_normal, permutation
import pandas as pd
from pandas import DataFrame

# 난수를 생성한다.
np.random.seed(20180311)
tf.set_random_seed(20180311)


# 트레이닝 세트 데이터를 생성한다.
def generate_datablock(n, mu, var, t):
    data = multivariate_normal(mu, np.eye(2) * var, n)
    df = DataFrame(data, columns=["x1", "x2"])
    df["t"] = t
    return df


df0 = generate_datablock(15, [7, 7], 22, 0)
df1 = generate_datablock(15, [22, 7], 22, 0)
df2 = generate_datablock(10, [7, 22], 22, 0)
df3 = generate_datablock(25, [20, 20], 22, 1)

df = pd.concat([df0, df1, df2, df3], ignore_index=True)
train_set = df.reindex(permutation(df.index)).reset_index(drop=True)

# (x1, x2)와 t를 각각 모은 것을 NumPy의 array 오브젝트로 추출해둔다.
train_x = train_set[["x1", "x2"]].as_matrix()
train_t = train_set["t"].as_matrix().reshape([len(train_set), 1])

df.tail()  # 생성된 트레이닝 데이터를 확인합니다.
Out[24]:
x1 x2 t
60 19.706643 24.780606 1
61 17.686418 12.477638 1
62 14.573116 24.185052 1
63 26.026245 12.522857 1
64 23.832026 29.230884 1
In [25]:
#  단층 신경망을 이용한 이항 분류기 모델을 정의한다.
num_units = 2
mult = train_x.flatten().mean()
x = tf.placeholder(tf.float32, [None, 2])
w1 = tf.Variable(tf.truncated_normal([2, num_units]))
b1 = tf.Variable(tf.zeros([num_units]))
hidden1 = tf.nn.tanh(tf.matmul(x, w1) + b1 * mult)  # 활성화 함수를 하이퍼볼릭 탄젠트로
w0 = tf.Variable(tf.zeros([num_units, 1]))
b0 = tf.Variable(tf.zeros([1]))
p = tf.nn.sigmoid(tf.matmul(hidden1, w0) + b0 * mult)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy를 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
# 지금까지 이용했던 Adamoptimizer대신 GradientDescentOptimzer를 사용했다.
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 1000회 반복한다.
i = 0
for _ in range(1000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 200 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: train_x, t: train_t}
        )
        print("Step: {}, Loss: {}, Accuracy: {}".format(i, loss_val, acc_val))
Step: 200, Loss: 58.25847244262695, Accuracy: 0.6615384817123413
Step: 400, Loss: 31.748950958251953, Accuracy: 0.8461538553237915
Step: 600, Loss: 19.171358108520508, Accuracy: 0.9076923131942749
Step: 800, Loss: 19.16461944580078, Accuracy: 0.9076923131942749
Step: 1000, Loss: 19.15259552001953, Accuracy: 0.9076923131942749

2장에서는 파라미터가 최적값에 수렴하기 위해 2만회 반복해야 했지만, 여기서는 그보다 훨씬 적은 1000회로 최적값에 수렴하고 있습니다. 이것은 학습 알고리즘의 변경에 의한 효과로 그 중요성을 반증합니다. 이제 결과를 그래프로 출력해보도록 하겠습니다.

In [26]:
# 시각화 함수를 만들어 재사용합니다.
def make_plot():
    train_set1 = train_set[train_set["t"] == 1]
    train_set2 = train_set[train_set["t"] == 0]
    fig = plt.figure(figsize=(6, 6))
    subplot = fig.add_subplot(1, 1, 1)
    subplot.set_ylim([0, 30])
    subplot.set_xlim([0, 30])
    subplot.scatter(train_set1.x1, train_set1.x2, marker="x")
    subplot.scatter(train_set2.x1, train_set2.x2, marker="o")
    locations = []
    for x2 in np.linspace(0, 30, 100):
        for x1 in np.linspace(0, 30, 100):
            locations.append((x1, x2))
    p_vals = sess.run(p, feed_dict={x: locations})
    p_vals = p_vals.reshape((100, 100))
    subplot.imshow(
        p_vals, origin="lower", extent=(0, 30, 0, 30), cmap=plt.cm.gray_r, alpha=0.5
    )  # 얻어진 확률을 색의 농담으로 그림에 표시한다.


make_plot()
No description has been provided for this image

바이러스 감염확률을 농담으로 표현한 것으로 은닉계층에 의해 4개의 영역으로 분할되어 있을 알 수 있습니다. 오른쪽 위의 영역은 확률이 50% 이상으로 되고, 그 옆의 영역은 50%이하라고 생각할 수 있습니다.

이렇게 해서 단일 신경망의 은닉 계층 효과를 구체적으로 확인 할 수 있었습니다. 그 다음으로 신경망의 노드 개수와 활성화 함수 변경 효과에 대해서 알아 보겠습니다.

3.2 노드의 개수를 늘렸을 때의 효과

노드의 개수를 늘리는 것은 그림에서 영역이 분할되는 개수를 늘리는것과 같습니다. 아래 코드를 실행해 봅시다.

In [27]:
# (x1, x2)와 t를 각각 모은 것을 NumPy의 array 오브젝트로 추출해둔다.
train_x = train_set[["x1", "x2"]].as_matrix()
train_t = train_set["t"].as_matrix().reshape([len(train_set), 1])

#  단층 신경망을 이용한 이항 분류기 모델을 정의한다.
num_units = 4  # 노드의 개수를 4개로 변경
mult = train_x.flatten().mean()
x = tf.placeholder(tf.float32, [None, 2])
w1 = tf.Variable(tf.truncated_normal([2, num_units]))
b1 = tf.Variable(tf.zeros([num_units]))
hidden1 = tf.nn.tanh(tf.matmul(x, w1) + b1 * mult)
w0 = tf.Variable(tf.zeros([num_units, 1]))
b0 = tf.Variable(tf.zeros([1]))
p = tf.nn.sigmoid(tf.matmul(hidden1, w0) + b0 * mult)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy를 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 1000회 반복한다.
i = 0
for _ in range(1000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 1000 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: train_x, t: train_t}
        )
        print("Step: {}, Loss: {}, Accuracy: {}".format(i, loss_val, acc_val))

# 시각화 하기
make_plot()
Step: 1000, Loss: 13.514551162719727, Accuracy: 0.9230769276618958
No description has been provided for this image

결과를 보면 색이 짙은 부분의 모양이 변한것을 알 수 있습니다. 바이러스에 감염된 데이터를 보다 정확하게 감싸고 있습니다. 이와 같이 노드의 개수를 증가시킴으로 보다 복잡한 데이터에 대응할 수 있게 됩니다.

3.3 활성화 함수를 변경 할 때

신경망에서 들어오는 입력신호의 총합을 출력신호로 변환하는 함수를 활성화함수(activation function)라고 합니다. 활성화 함수를 기존의 하이퍼볼릭 탄젠트에서 ReLU(정규화 선형 함수;Rectufued Linear Unit)로 변경해보겠습니다.

In [28]:
#  단층 신경망을 이용한 이항 분류기 모델을 정의한다.
num_units = 2
mult = train_x.flatten().mean()
x = tf.placeholder(tf.float32, [None, 2])
w1 = tf.Variable(tf.truncated_normal([2, num_units]))
b1 = tf.Variable(tf.zeros([num_units]))
hidden1 = tf.nn.relu(tf.matmul(x, w1) + b1 * mult)  # 활성화 함수를 ReLU로 변경한다.
w0 = tf.Variable(tf.zeros([num_units, 1]))
b0 = tf.Variable(tf.zeros([1]))
p = tf.nn.sigmoid(tf.matmul(hidden1, w0) + b0 * mult)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy를 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 1000회 반복한다.
i = 0
for _ in range(1000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 1000 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: train_x, t: train_t}
        )
        print("Step: {}, Loss: {}, Accuracy: {}".format(i, loss_val, acc_val))

# 시각화하기
make_plot()
Step: 1000, Loss: 17.51049041748047, Accuracy: 0.892307698726654
No description has been provided for this image

그림을 자세히 보면 알 수 있듯이 경계가 완만하게 변한 것을 알 수 있습니다. 경계의 모양을 바꾸는것이 ReLU를 사용하는 본질적인 이유는 아니지만, 이과 같은 예를 통해 직감적인 효과를 이해 할 수 있습니다.

다수의 파라미터를 갖는 신경망에서는 ReLU가 최적화가 더 잘 이루어집니다.

3.3 다층 신경망으로의 확장

지금까지는 은닉 계층이 하나인 단층 신경망에 대해 알아보았습니다. 다음 단계로 은닉 계층을 증가시킨 다층 신경망을 알아보겠습니다.

다층 신경망을 써야 하는 이유

단층 신경망을 이용해 아래 그림과 같은 데이터를 제대로 분류할 수 없기 때문입니다.

In [16]:
def generate_datablock(n, mu, var, t):
    data = multivariate_normal(mu, np.eye(2) * var, n)
    df = DataFrame(data, columns=["x1", "x2"])
    df["t"] = t
    return df


df0 = generate_datablock(30, [-7, -7], 18, 1)
df1 = generate_datablock(30, [-7, 7], 18, 0)
df2 = generate_datablock(30, [7, -7], 18, 0)
df3 = generate_datablock(30, [7, 7], 18, 1)

df = pd.concat([df0, df1, df2, df3], ignore_index=True)
train_set = df.reindex(permutation(df.index)).reset_index(drop=True)

train_set1 = train_set[train_set["t"] == 1]
train_set2 = train_set[train_set["t"] == 0]

fig = plt.figure(figsize=(6, 6))
subplot = fig.add_subplot(1, 1, 1)
subplot.set_ylim([-15, 15])
subplot.set_xlim([-15, 15])
subplot.scatter(train_set1.x1, train_set1.x2, marker="x")
subplot.scatter(train_set2.x1, train_set2.x2, marker="o")
Out[16]:
<matplotlib.collections.PathCollection at 0x7f5aa0e44e48>
No description has been provided for this image

기존의 단일 신경망은 출력 계층이 평면을 단순하게 직선으로 분할 하려는데 문제가 있어요. 따라서 출력 계층을 확장하는 다층 신경망을 구성해야함을 의미합니다. 아래의 코드처럼 신경망을 추가해서 풀어보겠습니다.

In [17]:
# (x1, x2)와 t를 각각 모은 것을 NumPy의 array 오브젝트로 추출해둔다.
train_x = train_set[["x1", "x2"]].as_matrix()
train_t = train_set["t"].as_matrix().reshape([len(train_set), 1])

# 2계층 신경망을 이용한 이항 분류기 모델을 정의한다.
num_units1 = 2
num_units2 = 2

x = tf.placeholder(tf.float32, [None, 2])
w1 = tf.Variable(tf.truncated_normal([2, num_units1]))
b1 = tf.Variable(tf.zeros([num_units1]))
hidden1 = tf.nn.tanh(tf.matmul(x, w1) + b1)
w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))
b2 = tf.Variable(tf.zeros([num_units2]))
hidden2 = tf.nn.tanh(tf.matmul(hidden1, w2) + b2)
w0 = tf.Variable(tf.zeros([num_units2, 1]))
b0 = tf.Variable(tf.zeros([1]))
p = tf.nn.sigmoid(tf.matmul(hidden2, w0) + b0)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy를 정의한다.
t = tf.placeholder(tf.float32, [None, 1])
loss = -tf.reduce_sum(t * tf.log(p) + (1 - t) * tf.log(1 - p))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(loss)
correct_prediction = tf.equal(tf.sign(p - 0.5), tf.sign(t - 0.5))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 3000회 반복한다.
i = 0
for _ in range(5000):
    i += 1
    sess.run(train_step, feed_dict={x: train_x, t: train_t})
    if i % 1000 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: train_x, t: train_t}
        )
        print("Step: %d, Loss: %f, Accuracy: %f" % (i, loss_val, acc_val))

# 얻어진 확률을 색의 농담으로 그림에 표시한다.
train_set1 = train_set[train_set["t"] == 1]
train_set2 = train_set[train_set["t"] == 0]

fig = plt.figure(figsize=(6, 6))
subplot = fig.add_subplot(1, 1, 1)
subplot.set_ylim([-15, 15])
subplot.set_xlim([-15, 15])
subplot.scatter(train_set1.x1, train_set1.x2, marker="x")
subplot.scatter(train_set2.x1, train_set2.x2, marker="o")

locations = []
for x2 in np.linspace(-15, 15, 100):
    for x1 in np.linspace(-15, 15, 100):
        locations.append((x1, x2))
p_vals = sess.run(p, feed_dict={x: locations})
p_vals = p_vals.reshape((100, 100))
subplot.imshow(
    p_vals, origin="lower", extent=(-15, 15, -15, 15), cmap=plt.cm.gray_r, alpha=0.5
)
Step: 1000, Loss: 80.748276, Accuracy: 0.625000
Step: 2000, Loss: 57.396599, Accuracy: 0.733333
Step: 3000, Loss: 55.855778, Accuracy: 0.725000
Step: 4000, Loss: 26.411263, Accuracy: 0.941667
Step: 5000, Loss: 24.682444, Accuracy: 0.941667
Out[17]:
<matplotlib.image.AxesImage at 0x7f5aa00f8748>
No description has been provided for this image

그림에서 볼 수 있듯이 두 개의 신경망을 구성함으로서 데이터를 제대로 분류 할 수 있는것을 볼 수 있습니다. 이제까지 예시들은 설명을 위한 간단한 것으로 앞으로 살펴볼 복잡한 데이터에서는 이것만으로 충분하지 않습니다.

4장. 합성곱 필터(Convolution Filter)를 이용한 이미지분류

합성곱 필터

구체적인 내용은 다음 링크에 정리가 잘 되어 있습니다.

간단하게 말하자면 입력 데이터의 특징만 추려내는 방법이라고 할 수 있겠습니다.

4.3 합성곱 필터를 이용한 필기체 분류

단층 합성곱 필터(CNN)를 사용해 MNIST 데이터를 분석해보도록 하겠습니다.

In [18]:
# 필요한 라이브러리를 불러오고 난수의 시드를 설정한다.
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data

np.random.seed(20180312)
tf.set_random_seed(20180312)

# MNIST 데이터 세트를 준비한다.

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting /tmp/data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting /tmp/data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting /tmp/data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting /tmp/data/t10k-labels-idx1-ubyte.gz
In [19]:
# 필터에 해당하는 Variable을 준비하고 입력 데이터에 필터와 풀링 계층을 적용하는 계산식을 정의한다.
num_filters = 16  # 사용할 필터의수 임의의 숫자
x = tf.placeholder(tf.float32, [None, 784])
x_image = tf.reshape(x, [-1, 28, 28, 1])
W_conv = tf.Variable(tf.truncated_normal([5, 5, 1, num_filters], stddev=0.1))
h_conv = tf.nn.conv2d(x_image, W_conv, strides=[1, 1, 1, 1], padding="SAME")
h_pool = tf.nn.max_pool(
    h_conv, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME"
)

# 풀링 계층의 출력을 전 결합층을 경유해서 소프트맥스 함수로 입력하는 계산식을 정의한다.
h_pool_flat = tf.reshape(h_pool, [-1, 14 * 14 * num_filters])
num_units1 = 14 * 14 * num_filters
num_units2 = 1024

w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))
b2 = tf.Variable(tf.zeros([num_units2]))
hidden2 = tf.nn.relu(tf.matmul(h_pool_flat, w2) + b2)

w0 = tf.Variable(tf.zeros([num_units2, 10]))
b0 = tf.Variable(tf.zeros([10]))
p = tf.nn.softmax(tf.matmul(hidden2, w0) + b0)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy를 정의한다.
t = tf.placeholder(tf.float32, [None, 10])
loss = -tf.reduce_sum(t * tf.log(p))
train_step = tf.train.AdamOptimizer(0.0005).minimize(loss)
correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 4000회 반복한다.
i = 0
for _ in range(4000):
    i += 1
    batch_xs, batch_ts = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, t: batch_ts})
    if i % 1000 == 0:
        loss_val, acc_val = sess.run(
            [loss, accuracy], feed_dict={x: mnist.test.images, t: mnist.test.labels}
        )
        print("Step: %d, Loss: %f, Accuracy: %f" % (i, loss_val, acc_val))
Step: 1000, Loss: 792.982544, Accuracy: 0.974400
Step: 2000, Loss: 633.976257, Accuracy: 0.980800
Step: 3000, Loss: 646.613098, Accuracy: 0.981300
Step: 4000, Loss: 699.803589, Accuracy: 0.981300

이처럼 MNIST 필기체 데이터에 단층 CNN을 사용해서 약 98%의 정확도를 달성했습니다. 여기서

In [20]:
# 합성곱 필터의 값과 최초 9개의 이미지 데이터에 대해 합성곱 필터와 풀링 계층을 적용한 결과를 얻는다.
filter_vals, conv_vals, pool_vals = sess.run(
    [W_conv, h_conv, h_pool], feed_dict={x: mnist.test.images[:9]}
)

# 합성곱 필터와 풀링계층를 적용한 결과를 이미지로 출력한다.
# 합성곱 필터를 적용한 후에는 픽셀값이 음의 값을 갖는 경우도 있으므로 배경(픽셀값 0) 부분이 흰색이 되지 않는다는 점에 주의하기 바란다.

fig = plt.figure(figsize=(10, num_filters + 1))

for i in range(num_filters):
    subplot = fig.add_subplot(num_filters + 1, 10, 10 * (i + 1) + 1)
    subplot.set_xticks([])
    subplot.set_yticks([])
    subplot.imshow(filter_vals[:, :, 0, i], cmap=plt.cm.gray_r, interpolation="nearest")

for i in range(9):
    subplot = fig.add_subplot(num_filters + 1, 10, i + 2)
    subplot.set_xticks([])
    subplot.set_yticks([])
    subplot.set_title("%d" % np.argmax(mnist.test.labels[i]))
    subplot.imshow(
        mnist.test.images[i].reshape((28, 28)),
        vmin=0,
        vmax=1,
        cmap=plt.cm.gray_r,
        interpolation="nearest",
    )

    for f in range(num_filters):
        subplot = fig.add_subplot(num_filters + 1, 10, 10 * (f + 1) + i + 2)
        subplot.set_xticks([])
        subplot.set_yticks([])
        subplot.imshow(
            pool_vals[i, :, :, f], cmap=plt.cm.gray_r, interpolation="nearest"
        )
No description has been provided for this image

그리 명료하지는 않지만 잘 살펴보면 특정 방향의 모서리를 추출하는 필터와 풀릴 계층에 의해 이미지가 축소된 것을 알 수 있습니다.

끝으로, 테스트 데이터에서 올바르게 분류할 수 없었던 데이터에 대해 확인하도록 하겠습니다. 각 데이터에 대해 0~9 일 확률을 막대그래프로 표시했습니다.

In [21]:
# 올바르게 분류할 수 없었던 몇몇 데이터에 대해 각각의 문자일 확률을 확인한다.

fig = plt.figure(figsize=(12, 10))
c = 0
for image, label in zip(mnist.test.images, mnist.test.labels):
    p_val = sess.run(p, feed_dict={x: [image]})
    pred = p_val[0]
    prediction, actual = np.argmax(pred), np.argmax(label)
    if prediction == actual:
        continue
    subplot = fig.add_subplot(5, 4, c * 2 + 1)
    subplot.set_xticks([])
    subplot.set_yticks([])
    subplot.set_title("%d / %d" % (prediction, actual))
    subplot.imshow(
        image.reshape((28, 28)),
        vmin=0,
        vmax=1,
        cmap=plt.cm.gray_r,
        interpolation="nearest",
    )
    subplot = fig.add_subplot(5, 4, c * 2 + 2)
    subplot.set_xticks(range(10))
    subplot.set_xlim(-0.5, 9.5)
    subplot.set_ylim(0, 1)
    subplot.bar(range(10), pred, align="center")
    c += 1
    if c == 10:
        break
No description has been provided for this image

각각의 이미지 위에 있는 숫자는 예측/정답을 나타내고 오른쪽 그래프는 각각의 확률을 나타내고 있습니다. 결과를 보면 정말 엉뚱하게 예측한 값도 있는 반면, 사람이 판단하기에도 애매한 숫자도 보입니다.

5장. 다층 합성곱 필터 신경망

드디어 합성곱 신경망의 전체 구조를 완성 시켜보겠습니다. 이전 장에서 합성곱 필터 -> 풀링 계층 -> 전 결합층 -> 소프트맥스 함수라는 과정을 통해 98%의 정확도를 달성했는데, 이번에는 합성곱 필터를 다층화한 CNN을 구성해서 얼마나 정확한 학습을 하는지 확인해 보겠습니다.

In [22]:
# 데이터를 준비한다.
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

# 첫 번째 단계의 합성곱 필터와 풀링 계층을 정의한다.
num_filters1 = 32
x = tf.placeholder(tf.float32, [None, 784])
x_image = tf.reshape(x, [-1, 28, 28, 1])
W_conv1 = tf.Variable(tf.truncated_normal([5, 5, 1, num_filters1], stddev=0.1))
h_conv1 = tf.nn.conv2d(x_image, W_conv1, strides=[1, 1, 1, 1], padding="SAME")
b_conv1 = tf.Variable(tf.constant(0.1, shape=[num_filters1]))
h_conv1_cutoff = tf.nn.relu(h_conv1 + b_conv1)
h_pool1 = tf.nn.max_pool(
    h_conv1_cutoff, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME"
)

# 두 번째 단계의 합성곱 필터와 풀링 계층을 정의한다.
num_filters2 = 64
W_conv2 = tf.Variable(
    tf.truncated_normal([5, 5, num_filters1, num_filters2], stddev=0.1)
)
h_conv2 = tf.nn.conv2d(h_pool1, W_conv2, strides=[1, 1, 1, 1], padding="SAME")
b_conv2 = tf.Variable(tf.constant(0.1, shape=[num_filters2]))
h_conv2_cutoff = tf.nn.relu(h_conv2 + b_conv2)
h_pool2 = tf.nn.max_pool(
    h_conv2_cutoff, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME"
)

# 전 결합층, 드롭아웃 계층, 소프트맥스 함수를 정의한다.
h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * num_filters2])
num_units1 = 7 * 7 * num_filters2
num_units2 = 1024
w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))
b2 = tf.Variable(tf.constant(0.1, shape=[num_units2]))
hidden2 = tf.nn.relu(tf.matmul(h_pool2_flat, w2) + b2)
keep_prob = tf.placeholder(tf.float32)
hidden2_drop = tf.nn.dropout(hidden2, keep_prob)
w0 = tf.Variable(tf.zeros([num_units2, 10]))
b0 = tf.Variable(tf.zeros([10]))
p = tf.nn.softmax(tf.matmul(hidden2_drop, w0) + b0)

# 오차 함수 loss, 트레이닝 알고리즘 train_step, 정답률 accuracy을 정의한다.
t = tf.placeholder(tf.float32, [None, 10])
loss = -tf.reduce_sum(t * tf.log(p))
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)
correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 세션을 준비하고 Variable을 초기화한다.
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

# 파라미터 최적화를 4000회 반복한다.
i = 0
for _ in range(4000):
    i += 1
    batch_xs, batch_ts = mnist.train.next_batch(50)
    sess.run(train_step, feed_dict={x: batch_xs, t: batch_ts, keep_prob: 0.5})
    if i % 1000 == 0:
        loss_vals, acc_vals = [], []
        for c in range(4):
            start = len(mnist.test.labels) / 4 * c
            end = len(mnist.test.labels) / 4 * (c + 1)
            loss_val, acc_val = sess.run(
                [loss, accuracy],
                feed_dict={
                    x: mnist.test.images[int(start) : int(end)],  # numpy 변경사항
                    t: mnist.test.labels[int(start) : int(end)],
                    keep_prob: 1.0,
                },
            )
            loss_vals.append(loss_val)
            acc_vals.append(acc_val)
        loss_val = np.sum(loss_vals)
        acc_val = np.mean(acc_vals)
        print("Step: %d, Loss: %f, Accuracy: %f" % (i, loss_val, acc_val))
Extracting /tmp/data/train-images-idx3-ubyte.gz
Extracting /tmp/data/train-labels-idx1-ubyte.gz
Extracting /tmp/data/t10k-images-idx3-ubyte.gz
Extracting /tmp/data/t10k-labels-idx1-ubyte.gz
Step: 1000, Loss: 960.280762, Accuracy: 0.970400
Step: 2000, Loss: 634.499390, Accuracy: 0.979900
Step: 3000, Loss: 580.983398, Accuracy: 0.981700
Step: 4000, Loss: 461.817566, Accuracy: 0.985200

아주 큰 차이는 아니지만, 다중 CNN 필터를 사용하면 0.4%의 정확도가 증가했습니다.

마치며

텐서플로로 시작하는 딥러닝은 두껍지는 않지만 딥러닝의 개념을 설명하는데 많은 노력을 기울이고 있습니다. 쉬운 예시와 수학적 증명은 저와 같은 초보자가 감을 잡는데 도움을 줍니다. 딥러닝과 텐서플로를 이제 공부하려고 한다면 한번쯤 읽어 보시는것을 추천드립니다.