텐서플로(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%의 정확도가 증가했습니다.

마치며

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