In [1]:
%load_ext watermark
%watermark -v -p numpy,scipy,matplotlib,sklearn,tensorflow
CPython 3.6.8
IPython 7.2.0

numpy 1.15.4
scipy 1.1.0
matplotlib 3.0.2
sklearn 0.20.2
tensorflow 1.13.1

9장 – 텐서플로 시작하기

이 노트북은 9장에 있는 모든 샘플 코드와 연습문제 해답을 가지고 있습니다.

설정

파이썬 2와 3을 모두 지원합니다. 공통 모듈을 임포트하고 맷플롯립 그림이 노트북 안에 포함되도록 설정하고 생성한 그림을 저장하기 위한 함수를 준비합니다:

In [2]:
# 파이썬 2와 파이썬 3 지원
from __future__ import division, print_function, unicode_literals

# 공통
import numpy as np
import os

# 일관된 출력을 위해 유사난수 초기화
def reset_graph(seed=42):
    tf.reset_default_graph()
    tf.set_random_seed(seed)
    np.random.seed(seed)

# 맷플롯립 설정
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12

# 한글출력
plt.rcParams['font.family'] = 'NanumBarunGothic'
plt.rcParams['axes.unicode_minus'] = False

# 그림을 저장할 폴더
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "tensorflow"

def save_fig(fig_id, tight_layout=True):
    path = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID, fig_id + ".png")
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format='png', dpi=300)

계산 그래프 만들고 실행하기

In [3]:
import tensorflow as tf

reset_graph()

x = tf.Variable(3, name="x")
y = tf.Variable(4, name="y")
f = x*x*y + y + 2
WARNING:tensorflow:From /home/haesun/anaconda3/envs/handson-ml/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
In [4]:
f
Out[4]:
<tf.Tensor 'add_1:0' shape=() dtype=int32>
In [5]:
sess = tf.Session()
sess.run(x.initializer)
sess.run(y.initializer)
result = sess.run(f)
print(result)
42
In [6]:
sess.close()
In [7]:
with tf.Session() as sess:
    x.initializer.run()
    y.initializer.run()
    result = f.eval()
In [8]:
result
Out[8]:
42
In [9]:
init = tf.global_variables_initializer()

with tf.Session() as sess:
    init.run()
    result = f.eval()
In [10]:
result
Out[10]:
42
In [11]:
init = tf.global_variables_initializer()
In [12]:
sess = tf.InteractiveSession()
init.run()
result = f.eval()
print(result)
42
In [13]:
sess.close()
In [14]:
result
Out[14]:
42

그래프 다루기

In [15]:
reset_graph()

x1 = tf.Variable(1)
x1.graph is tf.get_default_graph()
Out[15]:
True
In [16]:
graph = tf.Graph()
with graph.as_default():
    x2 = tf.Variable(2)

x2.graph is graph
Out[16]:
True
In [17]:
x2.graph is tf.get_default_graph()
Out[17]:
False
In [18]:
w = tf.constant(3)
x = w + 2
y = x + 5
z = x * 3

with tf.Session() as sess:
    print(y.eval())  # 10
    print(z.eval())  # 15
10
15
In [19]:
with tf.Session() as sess:
    y_val, z_val = sess.run([y, z])
    print(y_val)  # 10
    print(z_val)  # 15
10
15

선형 회귀

정규방정식을 사용해서

In [20]:
import numpy as np
from sklearn.datasets import fetch_california_housing

reset_graph()

housing = fetch_california_housing()
m, n = housing.data.shape
housing_data_plus_bias = np.c_[np.ones((m, 1)), housing.data]

X = tf.constant(housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
XT = tf.transpose(X)
theta = tf.matmul(tf.matmul(tf.matrix_inverse(tf.matmul(XT, X)), XT), y)

with tf.Session() as sess:
    theta_value = theta.eval()
In [21]:
theta_value
Out[21]:
array([[-3.7171074e+01],
       [ 4.3633682e-01],
       [ 9.3871783e-03],
       [-1.0717344e-01],
       [ 6.4540231e-01],
       [-4.1238391e-06],
       [-3.7809242e-03],
       [-4.2373490e-01],
       [-4.3720812e-01]], dtype=float32)

넘파이와 비교

In [22]:
X = housing_data_plus_bias
y = housing.target.reshape(-1, 1)
theta_numpy = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)

print(theta_numpy)
[[-3.69419202e+01]
 [ 4.36693293e-01]
 [ 9.43577803e-03]
 [-1.07322041e-01]
 [ 6.45065694e-01]
 [-3.97638942e-06]
 [-3.78654265e-03]
 [-4.21314378e-01]
 [-4.34513755e-01]]

사이킷런과 비교

In [23]:
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing.data, housing.target.reshape(-1, 1))

print(np.r_[lin_reg.intercept_.reshape(-1, 1), lin_reg.coef_.T])
[[-3.69419202e+01]
 [ 4.36693293e-01]
 [ 9.43577803e-03]
 [-1.07322041e-01]
 [ 6.45065694e-01]
 [-3.97638942e-06]
 [-3.78654265e-03]
 [-4.21314378e-01]
 [-4.34513755e-01]]

배치 경사 하강법을 사용해서

경사 하강법은 먼저 특성 벡터의 스케일을 조정해야 합니다. 텐서플로를 사용해 할 수 있지만 그냥 여기서는 사이킷런을 사용하겠습니다.

In [24]:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaled_housing_data = scaler.fit_transform(housing.data)
scaled_housing_data_plus_bias = np.c_[np.ones((m, 1)), scaled_housing_data]
In [25]:
print(scaled_housing_data_plus_bias.mean(axis=0))
print(scaled_housing_data_plus_bias.mean(axis=1))
print(scaled_housing_data_plus_bias.mean())
print(scaled_housing_data_plus_bias.shape)
[ 1.00000000e+00  6.60969987e-17  5.50808322e-18  6.60969987e-17
 -1.06030602e-16 -1.10161664e-17  3.44255201e-18 -1.07958431e-15
 -8.52651283e-15]
[ 0.38915536  0.36424355  0.5116157  ... -0.06612179 -0.06360587
  0.01359031]
0.11111111111111005
(20640, 9)

수동으로 그래디언트 계산하기

In [26]:
reset_graph()

n_epochs = 1000
learning_rate = 0.01

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
gradients = 2/m * tf.matmul(tf.transpose(X), error)
training_op = tf.assign(theta, theta - learning_rate * gradients)

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        if epoch % 100 == 0:
            print("에포크", epoch, "MSE =", mse.eval())
        sess.run(training_op)
    
    best_theta = theta.eval()
에포크 0 MSE = 9.161542
에포크 100 MSE = 0.71450055
에포크 200 MSE = 0.56670487
에포크 300 MSE = 0.55557173
에포크 400 MSE = 0.5488112
에포크 500 MSE = 0.5436363
에포크 600 MSE = 0.53962904
에포크 700 MSE = 0.5365092
에포크 800 MSE = 0.53406775
에포크 900 MSE = 0.5321473
In [27]:
best_theta
Out[27]:
array([[ 2.0685523 ],
       [ 0.8874027 ],
       [ 0.14401656],
       [-0.34770885],
       [ 0.36178368],
       [ 0.00393811],
       [-0.04269556],
       [-0.66145283],
       [-0.6375278 ]], dtype=float32)

자동미분 사용하기

gradients = ... 라인만 빼고 위와 동일합니다:

In [28]:
reset_graph()

n_epochs = 1000
learning_rate = 0.01

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
In [29]:
gradients = tf.gradients(mse, [theta])[0]
In [30]:
training_op = tf.assign(theta, theta - learning_rate * gradients)

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        if epoch % 100 == 0:
            print("에포크", epoch, "MSE =", mse.eval())
        sess.run(training_op)
    
    best_theta = theta.eval()

print("best_theta:")
print(best_theta)
에포크 0 MSE = 9.161542
에포크 100 MSE = 0.7145004
에포크 200 MSE = 0.56670487
에포크 300 MSE = 0.55557173
에포크 400 MSE = 0.5488112
에포크 500 MSE = 0.5436363
에포크 600 MSE = 0.53962904
에포크 700 MSE = 0.5365092
에포크 800 MSE = 0.53406775
에포크 900 MSE = 0.5321473
best_theta:
[[ 2.0685525 ]
 [ 0.8874027 ]
 [ 0.14401658]
 [-0.34770882]
 [ 0.36178368]
 [ 0.00393811]
 [-0.04269556]
 [-0.6614528 ]
 [-0.6375277 ]]

ab에 대한 다음 함수의 편도함수를 어떻게 구할 수 있나요?

In [31]:
def my_func(a, b):
    z = 0
    for i in range(100):
        z = a * np.cos(z + i) + z * np.sin(b - i)
    return z
In [32]:
my_func(0.2, 0.3)
Out[32]:
-0.21253923284754914
In [33]:
reset_graph()

a = tf.Variable(0.2, name="a")
b = tf.Variable(0.3, name="b")
z = tf.constant(0.0, name="z0")
for i in range(100):
    z = a * tf.cos(z + i) + z * tf.sin(b - i)

grads = tf.gradients(z, [a, b])
init = tf.global_variables_initializer()

$a=0.2$와 $b=0.3$일 때 함수 값을 계산하고 그 다음 $a$와 $b$에 대한 편미분을 구합니다:

In [34]:
with tf.Session() as sess:
    init.run()
    print(z.eval())
    print(sess.run(grads))
-0.21253741
[-1.1388494, 0.19671395]

GradientDescentOptimizer 사용하기

In [35]:
reset_graph()

n_epochs = 1000
learning_rate = 0.01

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
In [36]:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)
In [37]:
init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        if epoch % 100 == 0:
            print("에포크", epoch, "MSE =", mse.eval())
        sess.run(training_op)
    
    best_theta = theta.eval()

print("best_theta:")
print(best_theta)
에포크 0 MSE = 9.161542
에포크 100 MSE = 0.7145004
에포크 200 MSE = 0.56670487
에포크 300 MSE = 0.55557173
에포크 400 MSE = 0.5488112
에포크 500 MSE = 0.5436363
에포크 600 MSE = 0.53962904
에포크 700 MSE = 0.5365092
에포크 800 MSE = 0.53406775
에포크 900 MSE = 0.5321473
best_theta:
[[ 2.0685525 ]
 [ 0.8874027 ]
 [ 0.14401658]
 [-0.34770882]
 [ 0.36178368]
 [ 0.00393811]
 [-0.04269556]
 [-0.6614528 ]
 [-0.6375277 ]]

모멘텀 옵티마이저 사용하기

In [38]:
reset_graph()

n_epochs = 1000
learning_rate = 0.01

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
In [39]:
optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate,
                                       momentum=0.9)
In [40]:
training_op = optimizer.minimize(mse)

init = tf.global_variables_initializer()
In [41]:
with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        sess.run(training_op)
    
    best_theta = theta.eval()

print("best_theta:")
print(best_theta)
best_theta:
[[ 2.068558  ]
 [ 0.8296286 ]
 [ 0.11875337]
 [-0.26554456]
 [ 0.3057109 ]
 [-0.00450251]
 [-0.03932662]
 [-0.89986444]
 [-0.87052065]]

훈련 알고리즘에 데이터 주입하기

플레이스홀더 노드

In [42]:
reset_graph()

A = tf.placeholder(tf.float32, shape=(None, 3))
B = A + 5
with tf.Session() as sess:
    B_val_1 = B.eval(feed_dict={A: [[1, 2, 3]]})
    B_val_2 = B.eval(feed_dict={A: [[4, 5, 6], [7, 8, 9]]})

print(B_val_1)
[[6. 7. 8.]]
In [43]:
print(B_val_2)
[[ 9. 10. 11.]
 [12. 13. 14.]]

미니배치 경사 하강법

In [44]:
n_epochs = 1000
learning_rate = 0.01
In [45]:
reset_graph()

X = tf.placeholder(tf.float32, shape=(None, n + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")
In [46]:
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)

init = tf.global_variables_initializer()
In [47]:
n_epochs = 10
In [48]:
batch_size = 100
n_batches = int(np.ceil(m / batch_size))
In [49]:
def fetch_batch(epoch, batch_index, batch_size):
    np.random.seed(epoch * n_batches + batch_index)  # not shown in the book
    indices = np.random.randint(m, size=batch_size)  # not shown
    X_batch = scaled_housing_data_plus_bias[indices] # not shown
    y_batch = housing.target.reshape(-1, 1)[indices] # not shown
    return X_batch, y_batch

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        for batch_index in range(n_batches):
            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})

    best_theta = theta.eval()
In [50]:
best_theta
Out[50]:
array([[ 2.0703337 ],
       [ 0.8637145 ],
       [ 0.12255151],
       [-0.31211874],
       [ 0.38510373],
       [ 0.00434168],
       [-0.01232954],
       [-0.83376896],
       [-0.8030471 ]], dtype=float32)

모델의 저장과 복원

In [51]:
reset_graph()

n_epochs = 1000                                                                       # 책에는 없습니다.
learning_rate = 0.01                                                                  # 책에는 없습니다.

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name="X")            # 책에는 없습니다.
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name="y")            # 책에는 없습니다.
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")                                      # 책에는 없습니다.
error = y_pred - y                                                                    # 책에는 없습니다.
mse = tf.reduce_mean(tf.square(error), name="mse")                                    # 책에는 없습니다.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)            # 책에는 없습니다.
training_op = optimizer.minimize(mse)                                                 # 책에는 없습니다.

init = tf.global_variables_initializer()
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        if epoch % 100 == 0:
            print("에포크", epoch, "MSE =", mse.eval())                                # 책에는 없습니다.
            save_path = saver.save(sess, "/tmp/my_model.ckpt")
        sess.run(training_op)
    
    best_theta = theta.eval()
    save_path = saver.save(sess, "/tmp/my_model_final.ckpt")
에포크 0 MSE = 9.161542
에포크 100 MSE = 0.7145004
에포크 200 MSE = 0.56670487
에포크 300 MSE = 0.55557173
에포크 400 MSE = 0.5488112
에포크 500 MSE = 0.5436363
에포크 600 MSE = 0.53962904
에포크 700 MSE = 0.5365092
에포크 800 MSE = 0.53406775
에포크 900 MSE = 0.5321473
In [52]:
best_theta
Out[52]:
array([[ 2.0685525 ],
       [ 0.8874027 ],
       [ 0.14401658],
       [-0.34770882],
       [ 0.36178368],
       [ 0.00393811],
       [-0.04269556],
       [-0.6614528 ],
       [-0.6375277 ]], dtype=float32)
In [53]:
with tf.Session() as sess:
    saver.restore(sess, "/tmp/my_model_final.ckpt")
    best_theta_restored = theta.eval() # 책에는 없습니다.
WARNING:tensorflow:From /home/haesun/anaconda3/envs/handson-ml/lib/python3.6/site-packages/tensorflow/python/training/saver.py:1266: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.
Instructions for updating:
Use standard file APIs to check for files with this prefix.
INFO:tensorflow:Restoring parameters from /tmp/my_model_final.ckpt
In [54]:
np.allclose(best_theta, best_theta_restored)
Out[54]:
True

theta"weights"와 같은 다른 이름으로 저장하고 복원하는 Saver 객체를 원할 경우엔:

In [55]:
saver = tf.train.Saver({"weights": theta})

기본적으로 Saver 객체는 .meta 확장자를 가진 두 번째 파일에 그래프 구조도 저장합니다. tf.train.import_meta_graph() 함수를 사용하여 그래프 구조를 복원할 수 있습니다. 이 함수는 저장된 그래프를 기본 그래프로 로드하고 상태(즉, 변수 값)를 복원할 수 있는 Saver 객체를 반환합니다:

In [56]:
reset_graph()
# 빈 그래프로 시작합니다

saver = tf.train.import_meta_graph("/tmp/my_model_final.ckpt.meta")  # 그래프 구조를 로드합니다.
theta = tf.get_default_graph().get_tensor_by_name("theta:0") # 책에는 없습니다.

with tf.Session() as sess:
    saver.restore(sess, "/tmp/my_model_final.ckpt")  # 그래프 상태를 로드합니다.
    best_theta_restored = theta.eval() # 책에는 없습니다.
INFO:tensorflow:Restoring parameters from /tmp/my_model_final.ckpt
In [57]:
np.allclose(best_theta, best_theta_restored)
Out[57]:
True

이를 사용하면 그래프를 만든 파이썬 코드가 없이도 미리 훈련된 모델을 임포트할 수 있습니다. 모델을 저장하고 변경할 때도 매우 편리합니다. 이전에 저장된 모델을 구축한 코드의 버전을 찾지 않아도 로드할 수 있습니다.

그래프 시각화

쥬피터 노트북안에서

주피터 노트북에서 그래프를 나타내기 위해 https://tensorboard.appspot.com/ 에 서비스 중인 텐서보드 서버를 사용하겠습니다(즉, 인터넷 연결이 안되면 작동되지 않습니다). 제가 아는 한 이 코드는 Alex Mordvintsev가 딥드림 튜토리얼에서 처음 사용했습니다. 또는 tfgraphviz를 사용할 수도 있습니다.

In [58]:
from tensorflow_graph_in_jupyter import show_graph
In [59]:
show_graph(tf.get_default_graph())

텐서보드 사용하기

In [60]:
reset_graph()

from datetime import datetime

now = datetime.utcnow().strftime("%Y%m%d%H%M%S")
root_logdir = "tf_logs"
logdir = "{}/run-{}/".format(root_logdir, now)
In [61]:
n_epochs = 1000
learning_rate = 0.01

X = tf.placeholder(tf.float32, shape=(None, n + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)

init = tf.global_variables_initializer()
In [62]:
mse_summary = tf.summary.scalar('MSE', mse)
file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())
In [63]:
n_epochs = 10
batch_size = 100
n_batches = int(np.ceil(m / batch_size))
In [64]:
with tf.Session() as sess:                                                        # 책에는 없습니다.
    sess.run(init)                                                                # 책에는 없습니다.

    for epoch in range(n_epochs):                                                 # 책에는 없습니다.
        for batch_index in range(n_batches):
            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)
            if batch_index % 10 == 0:
                summary_str = mse_summary.eval(feed_dict={X: X_batch, y: y_batch})
                step = epoch * n_batches + batch_index
                file_writer.add_summary(summary_str, step)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})

    best_theta = theta.eval()                                                     # 책에는 없습니다.
In [65]:
file_writer.close()
In [66]:
best_theta
Out[66]:
array([[ 2.0703337 ],
       [ 0.8637145 ],
       [ 0.12255151],
       [-0.31211874],
       [ 0.38510373],
       [ 0.00434168],
       [-0.01232954],
       [-0.83376896],
       [-0.8030471 ]], dtype=float32)

이름 범위

In [67]:
reset_graph()

now = datetime.utcnow().strftime("%Y%m%d%H%M%S")
root_logdir = "tf_logs"
logdir = "{}/run-{}/".format(root_logdir, now)

n_epochs = 1000
learning_rate = 0.01

X = tf.placeholder(tf.float32, shape=(None, n + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0, seed=42), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")
In [68]:
with tf.name_scope("loss") as scope:
    error = y_pred - y
    mse = tf.reduce_mean(tf.square(error), name="mse")
In [69]:
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(mse)

init = tf.global_variables_initializer()

mse_summary = tf.summary.scalar('MSE', mse)
file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())
In [70]:
n_epochs = 10
batch_size = 100
n_batches = int(np.ceil(m / batch_size))

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        for batch_index in range(n_batches):
            X_batch, y_batch = fetch_batch(epoch, batch_index, batch_size)
            if batch_index % 10 == 0:
                summary_str = mse_summary.eval(feed_dict={X: X_batch, y: y_batch})
                step = epoch * n_batches + batch_index
                file_writer.add_summary(summary_str, step)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})

    best_theta = theta.eval()

file_writer.flush()
file_writer.close()
print("best_theta:")
print(best_theta)
best_theta:
[[ 2.0703337 ]
 [ 0.8637145 ]
 [ 0.12255151]
 [-0.31211874]
 [ 0.38510373]
 [ 0.00434168]
 [-0.01232954]
 [-0.83376896]
 [-0.8030471 ]]
In [71]:
print(error.op.name)
loss/sub
In [72]:
print(mse.op.name)
loss/mse
In [73]:
reset_graph()

a1 = tf.Variable(0, name="a")      # name == "a"
a2 = tf.Variable(0, name="a")      # name == "a_1"

with tf.name_scope("param"):       # name == "param"
    a3 = tf.Variable(0, name="a")  # name == "param/a"

with tf.name_scope("param"):       # name == "param_1"
    a4 = tf.Variable(0, name="a")  # name == "param_1/a"

for node in (a1, a2, a3, a4):
    print(node.op.name)
a
a_1
param/a
param_1/a

모듈화

중복이 많습니다:

In [74]:
reset_graph()

n_features = 3
X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")

w1 = tf.Variable(tf.random_normal((n_features, 1)), name="weights1")
w2 = tf.Variable(tf.random_normal((n_features, 1)), name="weights2")
b1 = tf.Variable(0.0, name="bias1")
b2 = tf.Variable(0.0, name="bias2")

z1 = tf.add(tf.matmul(X, w1), b1, name="z1")
z2 = tf.add(tf.matmul(X, w2), b2, name="z2")

relu1 = tf.maximum(z1, 0., name="relu1")
relu2 = tf.maximum(z1, 0., name="relu2")  # Oops, cut&paste error! Did you spot it?

output = tf.add(relu1, relu2, name="output")

relu() 함수를 사용해 더 나아졌습니다:

In [75]:
reset_graph()

def relu(X):
    w_shape = (int(X.get_shape()[1]), 1)
    w = tf.Variable(tf.random_normal(w_shape), name="weights")
    b = tf.Variable(0.0, name="bias")
    z = tf.add(tf.matmul(X, w), b, name="z")
    return tf.maximum(z, 0., name="relu")

n_features = 3
X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
relus = [relu(X) for i in range(5)]
output = tf.add_n(relus, name="output")
In [76]:
file_writer = tf.summary.FileWriter("logs/relu1", tf.get_default_graph())

이름 범주를 사용하면 훨씬 더 낫습니다:

In [77]:
reset_graph()

def relu(X):
    with tf.name_scope("relu"):
        w_shape = (int(X.get_shape()[1]), 1)                          # 책에는 없습니다.
        w = tf.Variable(tf.random_normal(w_shape), name="weights")    # 책에는 없습니다.
        b = tf.Variable(0.0, name="bias")                             # 책에는 없습니다.
        z = tf.add(tf.matmul(X, w), b, name="z")                      # 책에는 없습니다.
        return tf.maximum(z, 0., name="max")                          # 책에는 없습니다.
In [78]:
n_features = 3
X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
relus = [relu(X) for i in range(5)]
output = tf.add_n(relus, name="output")

file_writer = tf.summary.FileWriter("logs/relu2", tf.get_default_graph())
file_writer.close()

변수 공유

threshold 변수를 공유하는 기본적인 방법은 relu() 함수 밖에서 정의한 후 매개변수를 통해 전달하는 것입니다:

In [79]:
reset_graph()

def relu(X, threshold):
    with tf.name_scope("relu"):
        w_shape = (int(X.get_shape()[1]), 1)                        # 책에는 없습니다.
        w = tf.Variable(tf.random_normal(w_shape), name="weights")  # 책에는 없습니다.
        b = tf.Variable(0.0, name="bias")                           # 책에는 없습니다.
        z = tf.add(tf.matmul(X, w), b, name="z")                    # 책에는 없습니다.
        return tf.maximum(z, threshold, name="max")

threshold = tf.Variable(0.0, name="threshold")
X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
relus = [relu(X, threshold) for i in range(5)]
output = tf.add_n(relus, name="output")
In [80]:
reset_graph()

def relu(X):
    with tf.name_scope("relu"):
        if not hasattr(relu, "threshold"):
            relu.threshold = tf.Variable(0.0, name="threshold")
        w_shape = int(X.get_shape()[1]), 1                          # 책에는 없습니다.
        w = tf.Variable(tf.random_normal(w_shape), name="weights")  # 책에는 없습니다.
        b = tf.Variable(0.0, name="bias")                           # 책에는 없습니다.
        z = tf.add(tf.matmul(X, w), b, name="z")                    # 책에는 없습니다.
        return tf.maximum(z, relu.threshold, name="max")
In [81]:
X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
relus = [relu(X) for i in range(5)]
output = tf.add_n(relus, name="output")
In [82]:
reset_graph()

with tf.variable_scope("relu"):
    threshold = tf.get_variable("threshold", shape=(),
                                initializer=tf.constant_initializer(0.0))
In [83]:
with tf.variable_scope("relu", reuse=True):
    threshold = tf.get_variable("threshold")
In [84]:
with tf.variable_scope("relu") as scope:
    scope.reuse_variables()
    threshold = tf.get_variable("threshold")
In [85]:
reset_graph()

def relu(X):
    with tf.variable_scope("relu", reuse=True):
        threshold = tf.get_variable("threshold")
        w_shape = int(X.get_shape()[1]), 1                          # 책에는 없습니다.
        w = tf.Variable(tf.random_normal(w_shape), name="weights")  # 책에는 없습니다.
        b = tf.Variable(0.0, name="bias")                           # 책에는 없습니다.
        z = tf.add(tf.matmul(X, w), b, name="z")                    # 책에는 없습니다.
        return tf.maximum(z, threshold, name="max")

X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
with tf.variable_scope("relu"):
    threshold = tf.get_variable("threshold", shape=(),
                                initializer=tf.constant_initializer(0.0))
relus = [relu(X) for relu_index in range(5)]
output = tf.add_n(relus, name="output")
In [86]:
file_writer = tf.summary.FileWriter("logs/relu6", tf.get_default_graph())
file_writer.close()
In [87]:
reset_graph()

def relu(X):
    with tf.variable_scope("relu"):
        threshold = tf.get_variable("threshold", shape=(), initializer=tf.constant_initializer(0.0))
        w_shape = (int(X.get_shape()[1]), 1)
        w = tf.Variable(tf.random_normal(w_shape), name="weights")
        b = tf.Variable(0.0, name="bias")
        z = tf.add(tf.matmul(X, w), b, name="z")
        return tf.maximum(z, threshold, name="max")

X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
with tf.variable_scope("", default_name="") as scope:
    first_relu = relu(X)     # 공유 변수를 만든 후
    scope.reuse_variables()  # 재사용합니다.
    relus = [first_relu] + [relu(X) for i in range(4)]
output = tf.add_n(relus, name="output")

file_writer = tf.summary.FileWriter("logs/relu8", tf.get_default_graph())
file_writer.close()
In [88]:
reset_graph()

def relu(X):
    threshold = tf.get_variable("threshold", shape=(),
                                initializer=tf.constant_initializer(0.0))
    w_shape = (int(X.get_shape()[1]), 1)                        # 책에는 없습니다.
    w = tf.Variable(tf.random_normal(w_shape), name="weights")  # 책에는 없습니다.
    b = tf.Variable(0.0, name="bias")                           # 책에는 없습니다.
    z = tf.add(tf.matmul(X, w), b, name="z")                    # 책에는 없습니다.
    return tf.maximum(z, threshold, name="max")

X = tf.placeholder(tf.float32, shape=(None, n_features), name="X")
relus = []
for relu_index in range(5):
    with tf.variable_scope("relu", reuse=(relu_index >= 1)) as scope:
        relus.append(relu(X))
output = tf.add_n(relus, name="output")
In [89]:
file_writer = tf.summary.FileWriter("logs/relu9", tf.get_default_graph())
file_writer.close()

추가 내용

In [90]:
reset_graph()

with tf.variable_scope("my_scope"):
    x0 = tf.get_variable("x", shape=(), initializer=tf.constant_initializer(0.))
    x1 = tf.Variable(0., name="x")
    x2 = tf.Variable(0., name="x")

with tf.variable_scope("my_scope", reuse=True):
    x3 = tf.get_variable("x")
    x4 = tf.Variable(0., name="x")

with tf.variable_scope("", default_name="", reuse=True):
    x5 = tf.get_variable("my_scope/x")

print("x0:", x0.op.name)
print("x1:", x1.op.name)
print("x2:", x2.op.name)
print("x3:", x3.op.name)
print("x4:", x4.op.name)
print("x5:", x5.op.name)
print(x0 is x3 and x3 is x5)
x0: my_scope/x
x1: my_scope/x_1
x2: my_scope/x_2
x3: my_scope/x
x4: my_scope_1/x
x5: my_scope/x
True

첫 번째 variable_scope() 블럭은 이름이 my_scope/x인 공유 변수 x0를 만듭니다. 공유 변수 이외의 모든 연산에 대해서는 (공유되지 않는 변수를 포함하여) 변수 범위가 일반적인 이름 범위처럼 작동합니다. 그래서 두 변수 x1x2에 접두사 my_scope/가 붙습니다. 하지만 텐서플로는 이름을 고유하게 만들기 위해 my_scope/x_1, my_scope/x_2처럼 인덱스를 추가시킵니다.

두 번째 variable_scope() 블럭은 my_scope 범위에 있는 공유 변수를 재사용합니다. 그래서 x0 is x3가 참입니다. 여기에서도 공유 변수를 제외한 모든 연산은 이름 범주와 같이 작동합니다. 첫 번째 블럭과 다르기 때문에 텐서플로가 고유한 범주 이름을 만듭니다(my_scope_1). 변수 x4의 이름은 my_scope_1/x가 됩니다.

세 번째 블럭은 공유 변수 my_scope/x를 다루는 다른 방식을 보여 줍니다. 루트 범위(이름이 빈 문자열입니다)에서 variable_scope()를 만들고 공유 변수의 전체 이름(즉, "my_scope/x")으로 get_variable()을 호출합니다.

문자열

In [91]:
reset_graph()

text = np.array("Do you want some café?".split())
text_tensor = tf.constant(text)

with tf.Session() as sess:
    print(text_tensor.eval())
[b'Do' b'you' b'want' b'some' b'caf\xc3\xa9?']

Autodiff

노트: 자동 미분 내용은 extra_autodiff.ipynb 노트북으로 옮겨졌습니다.

연습문제 해답

1. to 11.

부록 A 참조.

12. 텐서플로를 사용한 미니배치 경사 하강법으로 구현한 로지스틱 회귀

먼저 사이킷런의 make_moons() 함수를 사용해 moons 데이터셋을 만듭니다:

In [92]:
from sklearn.datasets import make_moons

m = 1000
X_moons, y_moons = make_moons(m, noise=0.1, random_state=42)

데이터를 잠깐 들여다 보겠습니다:

In [93]:
plt.plot(X_moons[y_moons == 1, 0], X_moons[y_moons == 1, 1], 'go', label="양성")
plt.plot(X_moons[y_moons == 0, 0], X_moons[y_moons == 0, 1], 'r^', label="음성")
plt.legend()
plt.show()

모든 샘플에 추가적인 편향 특성($x_0 = 1$)을 추가해야 합니다. 이렇게 하려면 입력 행렬 $\mathbf{X}$의 왼쪽에 1로 채워진 열을 추가해야 합니다:

In [94]:
X_moons_with_bias = np.c_[np.ones((m, 1)), X_moons]

확인해 보죠:

In [95]:
X_moons_with_bias[:5]
Out[95]:
array([[ 1.        , -0.05146968,  0.44419863],
       [ 1.        ,  1.03201691, -0.41974116],
       [ 1.        ,  0.86789186, -0.25482711],
       [ 1.        ,  0.288851  , -0.44866862],
       [ 1.        , -0.83343911,  0.53505665]])

좋네요. 이제 y_train의 크기를 바꾸어 열 벡터로 만들겠습니다(즉, 하나의 열이 있는 2D 배열입니다):

In [96]:
y_moons_column_vector = y_moons.reshape(-1, 1)

이제 데이터셋을 훈련 세트와 테스트 세트로 나눕니다:

In [97]:
test_ratio = 0.2
test_size = int(m * test_ratio)
X_train = X_moons_with_bias[:-test_size]
X_test = X_moons_with_bias[-test_size:]
y_train = y_moons_column_vector[:-test_size]
y_test = y_moons_column_vector[-test_size:]

좋습니다. 이제 훈련 배치를 생성하기 위한 간단한 함수를 만들겠습니다. 이 함수는 각 배치를 위해 훈련 세트에서 랜덤하게 샘플을 선택합니다. 하나의 배치에 동일한 샘플이 여러번 들어갈 수 있고 한 번의 에포크에 모든 훈련 샘플이 포함되지 않을 수 있습니다(사실 샘플의 3분의 2 정도가 포함됩니다). 하지만 실전에서 별 문제가 되지 않고 코드가 간단해 집니다:

In [98]:
def random_batch(X_train, y_train, batch_size):
    rnd_indices = np.random.randint(0, len(X_train), batch_size)
    X_batch = X_train[rnd_indices]
    y_batch = y_train[rnd_indices]
    return X_batch, y_batch

작은 배치 하나를 만들어 보겠습니다:

In [99]:
X_batch, y_batch = random_batch(X_train, y_train, 5)
X_batch
Out[99]:
array([[ 1.        ,  1.93189866,  0.13158788],
       [ 1.        ,  1.07172763,  0.13482039],
       [ 1.        , -1.01148674, -0.04686381],
       [ 1.        ,  0.02201868,  0.19079139],
       [ 1.        , -0.98941204,  0.02473116]])
In [100]:
y_batch
Out[100]:
array([[1],
       [0],
       [0],
       [1],
       [0]])

좋습니다! 모델에 주입할 데이터가 준비되었으므로 모델을 만들 차례입니다. 간단하게 시작해서 기능을 점차 추가해 보겠습니다.

먼저 기본 그래프를 리셋합니다.

In [101]:
reset_graph()

moons 데이터셋은 두 개의 입력 특성을 가지므로 각 샘플은 평면 위의 한 점입니다(즉, 2차원입니다):

In [102]:
n_inputs = 2

로지스틱 회귀 모델을 만들어 보겠습니다. 4장에서 보았던 것처럼 이 모델은 먼저 (선형 회귀 모델과 동일하게) 입력의 가중치 합을 계산하고 그 결과를 시그모이드 함수에 적용하여 양성 클래스에 대한 추정 확률을 만듭니다:

$\hat{p} = h_\mathbf{\theta}(\mathbf{x}) = \sigma(\mathbf{\theta}^T \cdot \mathbf{x})$

$\mathbf{\theta}$는 편향 $\theta_0$와 가중치 $\theta_1, \theta_2, \dots, \theta_n$를 포함한 파라미터 벡터입니다. 입력 벡터 $\mathbf{x}$는 상수 항 $x_0 = 1$과 입력 특성 $x_1, x_2, \dots, x_n$을 포함합니다.

한 번에 여러 샘플에 대한 예측을 만들 수 있어야 하므로 하나의 입력 벡터보다는 입력 행렬 $\mathbf{X}$를 사용합니다. $i^{th}$ 번째 행이 $i^{th}$ 번째 입력 벡터의 전치$(\mathbf{x}^{(i)})^T$입니다. 다음 식을 사용하여 각 샘플이 양성 클래스에 속할 확률을 추정할 수 있습니다:

$ \hat{\mathbf{p}} = \sigma(\mathbf{X} \cdot \mathbf{\theta})$

모델을 만들기 위해 준비를 마쳤습니다:

In [103]:
X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")
theta = tf.Variable(tf.random_uniform([n_inputs + 1, 1], -1.0, 1.0, seed=42), name="theta")
logits = tf.matmul(X, theta, name="logits")
y_proba = 1 / (1 + tf.exp(-logits))

사실 텐서플로는 tf.sigmoid() 함수를 가지고 있어 마지막 라인을 다음과 같이 바꿀 수 있습니다:

In [104]:
y_proba = tf.sigmoid(logits)

4장에서 보았듯이 로그 손실은 로지스틱 회귀에 사용하기 좋은 비용 함수입니다:

$J(\mathbf{\theta}) = -\dfrac{1}{m} \sum\limits_{i=1}^{m}{\left[ y^{(i)} log\left(\hat{p}^{(i)}\right) + (1 - y^{(i)}) log\left(1 - \hat{p}^{(i)}\right)\right]}$

직접 구현하는 것도 한가지 방법입니다:

In [105]:
epsilon = 1e-7  # 로그를 계산할 때 오버플로우를 피하기 위해
loss = -tf.reduce_mean(y * tf.log(y_proba + epsilon) + (1 - y) * tf.log(1 - y_proba + epsilon))

하지만 텐서플로의 tf.losses.log_loss() 함수를 사용할 수 있습니다:

In [106]:
loss = tf.losses.log_loss(y, y_proba)  # 기본적으로 epsilon = 1e-7 가 사용됩니다
WARNING:tensorflow:From /home/haesun/anaconda3/envs/handson-ml/lib/python3.6/site-packages/tensorflow/python/ops/losses/losses_impl.py:514: to_float (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.cast instead.

나머지는 아주 기본적입니다. 옵티마이저를 만들고 비용 함수를 최소화시키도록 합니다:

In [107]:
learning_rate = 0.01
optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
training_op = optimizer.minimize(loss)

(이 간단한 예에서) 남은 것은 변수 초기화입니다:

In [108]:
init = tf.global_variables_initializer()

모델을 훈련하고 예측을 만들 준비가 되었습니다!

다음 코드에는 특별한 것은 없습니다. 앞서 선형 회귀에서 사용했던 것과 사실상 동일합니다:

In [109]:
n_epochs = 1000
batch_size = 50
n_batches = int(np.ceil(m / batch_size))

with tf.Session() as sess:
    sess.run(init)

    for epoch in range(n_epochs):
        for batch_index in range(n_batches):
            X_batch, y_batch = random_batch(X_train, y_train, batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        loss_val = loss.eval({X: X_test, y: y_test})
        if epoch % 100 == 0:
            print("에포크:", epoch, "\tLoss:", loss_val)

    y_proba_val = y_proba.eval(feed_dict={X: X_test, y: y_test})
에포크: 0 	Loss: 0.79260236
에포크: 100 	Loss: 0.34346348
에포크: 200 	Loss: 0.3075404
에포크: 300 	Loss: 0.29288894
에포크: 400 	Loss: 0.28533572
에포크: 500 	Loss: 0.28047803
에포크: 600 	Loss: 0.27808294
에포크: 700 	Loss: 0.27615443
에포크: 800 	Loss: 0.27551997
에포크: 900 	Loss: 0.27491233

노트: 배치를 만들 때 에포크 수를 사용하지 않았으므로 두 개의 for 반복을 중첩하지 않고 하나의 for 반복을 사용할 수 있습니다. 하지만 훈련 시간을 에포크의 개수로 생각하는게 편리합니다(즉, 알고리즘이 훈련 세트를 모두 훑고 지나가는 횟수).

테스트 세트에 있는 각 샘플에 대해서 y_proba_val은 해당 샘플이 양성 클래스에 속할 모델의 추정 확률을 담고 있습니다. 예를 들어 다음은 첫 번째 다섯 개 샘플의 추정 확률입니다:

In [110]:
y_proba_val[:5]
Out[110]:
array([[0.54895616],
       [0.7072436 ],
       [0.51900256],
       [0.99111354],
       [0.50859046]], dtype=float32)

각 샘플을 분류하기 위해서 최대 가능도 방법(maximum likelihood)을 사용합니다. 추정 확률이 0.5보다 크거나 같으면 양성으로 분류합니다:

In [111]:
y_pred = (y_proba_val >= 0.5)
y_pred[:5]
Out[111]:
array([[ True],
       [ True],
       [ True],
       [ True],
       [ True]])

경우에 따라 0.5말고 다른 임계값을 사용해야 할 수 있습니다. 가령 높은 정밀도(대신 낮은 재현율)를 원한다면 임계값을 높이고 재현율을 높이려면(대신 낮은 정밀도) 임계값을 낮춥니다. 자세한 내용은 3장을 참고하세요.

모델의 정밀도와 재현율을 계산해 보겠습니다:

In [112]:
from sklearn.metrics import precision_score, recall_score

precision_score(y_test, y_pred)
Out[112]:
0.8627450980392157
In [113]:
recall_score(y_test, y_pred)
Out[113]:
0.8888888888888888

이 예측이 어떻게 보이는지 그래프로 나타내 보겠습니다:

In [114]:
y_pred_idx = y_pred.reshape(-1) # 열 벡터를 1차원 배열로 바꿉니다
plt.plot(X_test[y_pred_idx, 1], X_test[y_pred_idx, 2], 'go', label="양성")
plt.plot(X_test[~y_pred_idx, 1], X_test[~y_pred_idx, 2], 'r^', label="음성")
plt.legend()
plt.show()

음 결과가 좋지 않네요. 그렇죠? 하지만 로지스틱 회귀 모델은 선형적인 결정 경계를 가지므로 최선에 가까운 것 같습니다(잠시 후에 보겠지만 특성을 더 추가하지 않는다면 말이죠).

다시 시작해 보죠. 이번에는 연습문제에 나열된 모든 부가 기능을 추가해 보겠습니다:

  • 재사용이 용이하도록 logistic_regression() 함수 안에서 그래프를 정의합니다.
  • 훈련하는 동안 일정한 간격으로 Saver 객체를 사용해 체크포인트를 저장하고 훈련이 끝날 때 최종 모델을 저장합니다.
  • 훈련이 중지되고 다시 시작할 때 마지막 체크포인트를 복원합니다.
  • 텐서보드에서 그래프가 잘 정돈되어 보이도록 이름 범위를 사용하여 그래프를 정의합니다.
  • 서머리(summary)를 추가해 텐서보드에서 학습 곡선을 나타냅니다.
  • 학습률, 미니배치 크기 같은 하이퍼파라미터를 바꾸어 보면서 학습 곡선의 모양을 관찰합니다.

시작하기 전에 입력에 ${x_1}^2$, ${x_2}^2$, ${x_1}^3$ 그리고 ${x_2}^3$ 네 개의 특성을 추가합니다. 연습문제에 포함되어 있지는 않지만 특성을 추가하면 모델의 성능이 향상되는 것을 확인할 수 있습니다. 여기서는 수동으로 특성을 추가하지만 sklearn.preprocessing.PolynomialFeatures을 사용할 수 있습니다.

In [115]:
X_train_enhanced = np.c_[X_train,
                         np.square(X_train[:, 1]),
                         np.square(X_train[:, 2]),
                         X_train[:, 1] ** 3,
                         X_train[:, 2] ** 3]
X_test_enhanced = np.c_[X_test,
                        np.square(X_test[:, 1]),
                        np.square(X_test[:, 2]),
                        X_test[:, 1] ** 3,
                        X_test[:, 2] ** 3]

특성이 추가된 훈련 세트는 다음과 같습니다:

In [116]:
X_train_enhanced[:5]
Out[116]:
array([[ 1.00000000e+00, -5.14696757e-02,  4.44198631e-01,
         2.64912752e-03,  1.97312424e-01, -1.36349734e-04,
         8.76459084e-02],
       [ 1.00000000e+00,  1.03201691e+00, -4.19741157e-01,
         1.06505890e+00,  1.76182639e-01,  1.09915879e+00,
        -7.39511049e-02],
       [ 1.00000000e+00,  8.67891864e-01, -2.54827114e-01,
         7.53236288e-01,  6.49368582e-02,  6.53727646e-01,
        -1.65476722e-02],
       [ 1.00000000e+00,  2.88850997e-01, -4.48668621e-01,
         8.34348982e-02,  2.01303531e-01,  2.41002535e-02,
        -9.03185778e-02],
       [ 1.00000000e+00, -8.33439108e-01,  5.35056649e-01,
         6.94620746e-01,  2.86285618e-01, -5.78924095e-01,
         1.53179024e-01]])

좋습니다. 이제 기본 그래프를 초기화합니다:

In [117]:
reset_graph()

그래프를 만들기 위해 logistic_regression() 함수를 정의합니다. 입력 X와 타깃 y의 정의를 포함하지 않았습니다. 이 함수에서 정의할 수도 있지만 그렇게 하지 않아야 다양한 경우에 이 함수를 사용할 수 있습니다(예를 들어, 로지스틱 회귀 모델에 주입하기 전에 입력에 대해 전처리 단계를 추가할 수 있습니다).

In [118]:
def logistic_regression(X, y, initializer=None, seed=42, learning_rate=0.01):
    n_inputs_including_bias = int(X.get_shape()[1])
    with tf.name_scope("logistic_regression"):
        with tf.name_scope("model"):
            if initializer is None:
                initializer = tf.random_uniform([n_inputs_including_bias, 1], -1.0, 1.0, seed=seed)
            theta = tf.Variable(initializer, name="theta")
            logits = tf.matmul(X, theta, name="logits")
            y_proba = tf.sigmoid(logits)
        with tf.name_scope("train"):
            loss = tf.losses.log_loss(y, y_proba, scope="loss")
            optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
            training_op = optimizer.minimize(loss)
            loss_summary = tf.summary.scalar('log_loss', loss)
        with tf.name_scope("init"):
            init = tf.global_variables_initializer()
        with tf.name_scope("save"):
            saver = tf.train.Saver()
    return y_proba, loss, training_op, loss_summary, init, saver

텐서보드를 위해 서머리를 저장할 로그 디렉토리 이름을 생성하는 함수를 만듭니다:

In [119]:
from datetime import datetime

def log_dir(prefix=""):
    now = datetime.utcnow().strftime("%Y%m%d%H%M%S")
    root_logdir = "tf_logs"
    if prefix:
        prefix += "-"
    name = prefix + "run-" + now
    return "{}/{}/".format(root_logdir, name)

이제 logistic_regression() 함수를 사용해 그래프를 만듭니다. 텐서보드용 서머리를 로그 디렉토리에 저장하기 위해 FileWriter도 만듭니다:

In [120]:
n_inputs = 2 + 4
logdir = log_dir("logreg")

X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name="X")
y = tf.placeholder(tf.float32, shape=(None, 1), name="y")

y_proba, loss, training_op, loss_summary, init, saver = logistic_regression(X, y)

file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())

드디어 모델을 학습시킬 수 있습니다! 이전에 훈련 세션이 중지되었는지부터 검사하고 그렇다면 체크포인트를 로드하고 저장된 에포크 횟수부터 훈련을 이어갑니다. 이 예에서는 별도의 파일에 에포트 횟수를 저장했지만 11장에서 모델에 일부로 훈련 스텝을저장하는 방법을 배우겠습니다. 예를 들어 global_step이란 훈련되지 않는 변수를 옵티마이저의 minimize() 메서드에 전달합니다.

다시 시작할 때 마지막 체크포인트가 제대로 복원되는지 확인하기 위해 훈련을 중지시켜 볼 수 있습니다.

In [121]:
n_epochs = 10001
batch_size = 50
n_batches = int(np.ceil(m / batch_size))

checkpoint_path = "/tmp/my_logreg_model.ckpt"
checkpoint_epoch_path = checkpoint_path + ".epoch"
final_model_path = "./my_logreg_model"

with tf.Session() as sess:
    if os.path.isfile(checkpoint_epoch_path):
        # 체크포인트 파일이 있으면 모델을 복원하고 에포크 횟수를 로드합니다
        with open(checkpoint_epoch_path, "rb") as f:
            start_epoch = int(f.read())
        print("중지되었던 훈련입니다. 에포크를 이어갑니다.", start_epoch)
        saver.restore(sess, checkpoint_path)
    else:
        start_epoch = 0
        sess.run(init)

    for epoch in range(start_epoch, n_epochs):
        for batch_index in range(n_batches):
            X_batch, y_batch = random_batch(X_train_enhanced, y_train, batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        loss_val, summary_str = sess.run([loss, loss_summary], feed_dict={X: X_test_enhanced, y: y_test})
        file_writer.add_summary(summary_str, epoch)
        if epoch % 500 == 0:
            print("에포크:", epoch, "\t손실:", loss_val)
            saver.save(sess, checkpoint_path)
            with open(checkpoint_epoch_path, "wb") as f:
                f.write(b"%d" % (epoch + 1))

    saver.save(sess, final_model_path)
    y_proba_val = y_proba.eval(feed_dict={X: X_test_enhanced, y: y_test})
    os.remove(checkpoint_epoch_path)
에포크: 0 	손실: 0.62998503
에포크: 500 	손실: 0.16122366
에포크: 1000 	손실: 0.1190321
에포크: 1500 	손실: 0.097329214
에포크: 2000 	손실: 0.08369793
에포크: 2500 	손실: 0.07437582
에포크: 3000 	손실: 0.06750215
에포크: 3500 	손실: 0.062206898
에포크: 4000 	손실: 0.058026787
에포크: 4500 	손실: 0.05456297
에포크: 5000 	손실: 0.051708277
에포크: 5500 	손실: 0.04923773
에포크: 6000 	손실: 0.047167283
에포크: 6500 	손실: 0.045376644
에포크: 7000 	손실: 0.04381875
에포크: 7500 	손실: 0.042374235
에포크: 8000 	손실: 0.041089173
에포크: 8500 	손실: 0.039970923
에포크: 9000 	손실: 0.038920265
에포크: 9500 	손실: 0.038010757
에포크: 10000 	손실: 0.037155706

여기에서도 추정 확률이 0.5보다 크거나 같은 샘플을 모두 양성으로 분류하면 예측이 됩니다:

In [122]:
y_pred = (y_proba_val >= 0.5)
In [123]:
precision_score(y_test, y_pred)
Out[123]:
0.9797979797979798
In [124]:
recall_score(y_test, y_pred)
Out[124]:
0.9797979797979798
In [125]:
y_pred_idx = y_pred.reshape(-1) # 열 벡터 대신 1차원 배열
plt.plot(X_test[y_pred_idx, 1], X_test[y_pred_idx, 2], 'go', label="양성")
plt.plot(X_test[~y_pred_idx, 1], X_test[~y_pred_idx, 2], 'r^', label="음성")
plt.legend()
plt.show()

훨씬 더 좋아졌네요! 새로 추가한 특성이 확실히 도움이 많이 되었습니다.

텐서보드 서버를 시작해서 최근 실행을 찾아 학습 곡선을 확인해 보세요(즉, 에포크 횟수에 대해 테스트 세트로 평가한 손실이 얼마나 되는지):

$ tensorboard --logdir=tf_logs

이제 하이퍼파라미터(가령, batch_sizelearning_rate)를 조정하면서 훈련을 여러번 실행해 보고 학습 곡선을 비교해 보겠습니다. 그리드 서치나 랜덤 서치를 구현해서 이 과정을 자동화할 수도 있습니다. 다음은 배치 크기와 학습률에 대한 간단한 랜덤 서치 구현입니다. 간단하게 하기 위해서 체크포인트 관리 부분은 제외했습니다.

In [126]:
from scipy.stats import reciprocal

n_search_iterations = 10

for search_iteration in range(n_search_iterations):
    batch_size = np.random.randint(1, 100)
    learning_rate = reciprocal(0.0001, 0.1).rvs(random_state=search_iteration)

    n_inputs = 2 + 4
    logdir = log_dir("logreg")
    
    print("반복", search_iteration)
    print("  logdir:", logdir)
    print("  batch_size:", batch_size)
    print("  learning_rate:", learning_rate)
    print("  훈련: ", end="")

    reset_graph()

    X = tf.placeholder(tf.float32, shape=(None, n_inputs + 1), name="X")
    y = tf.placeholder(tf.float32, shape=(None, 1), name="y")

    y_proba, loss, training_op, loss_summary, init, saver = logistic_regression(
        X, y, learning_rate=learning_rate)

    file_writer = tf.summary.FileWriter(logdir, tf.get_default_graph())

    n_epochs = 10001
    n_batches = int(np.ceil(m / batch_size))

    final_model_path = "./my_logreg_model_%d" % search_iteration

    with tf.Session() as sess:
        sess.run(init)

        for epoch in range(n_epochs):
            for batch_index in range(n_batches):
                X_batch, y_batch = random_batch(X_train_enhanced, y_train, batch_size)
                sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
            loss_val, summary_str = sess.run([loss, loss_summary], feed_dict={X: X_test_enhanced, y: y_test})
            file_writer.add_summary(summary_str, epoch)
            if epoch % 500 == 0:
                print(".", end="")

        saver.save(sess, final_model_path)

        print()
        y_proba_val = y_proba.eval(feed_dict={X: X_test_enhanced, y: y_test})
        y_pred = (y_proba_val >= 0.5)
        
        print("  정밀도:", precision_score(y_test, y_pred))
        print("  재현율:", recall_score(y_test, y_pred))
반복 0
  logdir: tf_logs/logreg-run-20190305070102/
  batch_size: 54
  learning_rate: 0.004430375245218265
  훈련: .....................
  정밀도: 0.9797979797979798
  재현율: 0.9797979797979798
반복 1
  logdir: tf_logs/logreg-run-20190305070221/
  batch_size: 22
  learning_rate: 0.0017826497151386947
  훈련: .....................
  정밀도: 0.9797979797979798
  재현율: 0.9797979797979798
반복 2
  logdir: tf_logs/logreg-run-20190305070520/
  batch_size: 74
  learning_rate: 0.00203228544324115
  훈련: .....................
  정밀도: 0.9696969696969697
  재현율: 0.9696969696969697
반복 3
  logdir: tf_logs/logreg-run-20190305070621/
  batch_size: 58
  learning_rate: 0.004491523825137997
  훈련: .....................
  정밀도: 0.9797979797979798
  재현율: 0.9797979797979798
반복 4
  logdir: tf_logs/logreg-run-20190305070738/
  batch_size: 61
  learning_rate: 0.07963234721775589
  훈련: .....................
  정밀도: 0.9801980198019802
  재현율: 1.0
반복 5
  logdir: tf_logs/logreg-run-20190305070850/
  batch_size: 92
  learning_rate: 0.0004634250583294876
  훈련: .....................
  정밀도: 0.912621359223301
  재현율: 0.9494949494949495
반복 6
  logdir: tf_logs/logreg-run-20190305070940/
  batch_size: 74
  learning_rate: 0.047706818419354494
  훈련: .....................
  정밀도: 0.98
  재현율: 0.98989898989899
반복 7
  logdir: tf_logs/logreg-run-20190305071041/
  batch_size: 58
  learning_rate: 0.0001694044709524274
  훈련: .....................
  정밀도: 0.9
  재현율: 0.9090909090909091
반복 8
  logdir: tf_logs/logreg-run-20190305071156/
  batch_size: 61
  learning_rate: 0.04171461199412461
  훈련: .....................
  정밀도: 0.9801980198019802
  재현율: 1.0
반복 9
  logdir: tf_logs/logreg-run-20190305071308/
  batch_size: 92
  learning_rate: 0.00010742922968438615
  훈련: .....................
  정밀도: 0.8823529411764706
  재현율: 0.7575757575757576

하이퍼파라미터의 적절한 스케일을 감잡을 수 없을 때 사이파이(SciPy)의 stats 모듈의 reciprocal() 함수를 사용하여 난수 분포를 얻을 수 있습니다. 좀 더 자세한 내용은 2장의 연습문제 해답을 보세요.