[텐서플로우 정리] 09. argmax 함수

argmax 함수가 어떤 역할을 하는지만 알면 충분한데, 갑자기 궁금해졌다. 두 번째 파라미터로 전달되는 차원이 무엇을 의미하는건지. 너무 당연하게 전달하는 배열의 차원을 알려달라는 것인지 알았다.


import tensorflow as tf
import functions

a1 = tf.Variable([0.1, 0.3, 0.5])
functions.showOperation(tf.argmax(a1, 0))
[출력 결과]
2

두 번째 파라미터는 one-hot-encoding을 적용할 차원을 알려주는 매개변수이다. 1차원 배열에 대해서는 0, 2차원 배열에 대해서는 0과 1, 3차원 배열에 대해서는 0, 1, 2를 사용할 수 있다. 헷갈릴 수 있는데, 0은 열(column), 1은 행(row), 2는 면(page, 행열)을 가리킨다. 그러고 보니 flag 2에 대해서는 정확한 용어를 모르겠다. 보통 차원(dimension)을 얘기할 때 점선면이라고 말하니까, 여기서는 '면'이라고 하고 page라고 읽겠다.

1차원 배열을 전달했으니까 사용할 수 있는 플래그는 0 밖에 없다. 그런데, 느낌이 이상한 것이 1행밖에 없는데 왜 결과가 한 개밖에 나오지 않을까,라고 생각할 수도 있다. 그러나, 1차원 배열을 행이 아니라 열로 생각할 수도 있다. 다시 말해 열이 1개 있기 때문에 결과도 하나가 나왔다,라고 보면 된다.


import tensorflow as tf
import functions

a2 = tf.Variable([[0.1, 0.3, 0.5]])
functions.showOperation(tf.argmax(a2, 0))
functions.showOperation(tf.argmax(a2, 1))
[출력 결과]
[0 0 0]
[2]

1행 3열의 2차원 배열을 사용했다. 사용할 수 있는 플래그는 0과 1의 두 가지다.

먼저 열 단위로 찾기 위해서 0을 전달했다. 결과는 3개가 나왔고 모두 0이다. 열 단위로는 데이터가 1개밖에 없으니까, 위치는 항상 0이 될 수밖에 없다.

행 단위로 찾기 위해서 1을 전달하면, 결과는 1개만 나온다. 행이 하나밖에 없으니까. 0번째 행에서 가장 큰 값은 0.5이고, 2번째 위치에 있어서 2를 반환했다.


import tensorflow as tf
import functions

a3 = tf.Variable([[[0.1, 0.3, 0.5],
[0.3, 0.5, 0.1]],
[[0.5, 0.1, 0.3],
[0.1, 0.3, 0.5]],
[[0.3, 0.5, 0.1],
[0.5, 0.1, 0.3]]])

functions.showOperation(tf.argmax(a3, 0))
functions.showOperation(tf.argmax(a3, 1))
functions.showOperation(tf.argmax(a3, 2))
[출력 결과]
[[1 2 0]
[2 0 1]]
[[1 1 0]
[0 1 1] [1 0 1]]
[[2 1]
[0 2]
[1 0]]

a3은 3x2x3 크기를 갖는 3차원 배열이다. 어떻게 이런 결과가 나왔는지 말로 하는 것이 쉽지 않다. 행과 열, 페이지에 따라 계산한 방식을 빨간색으로 표시해 보았다. 행열 기준의 경우에는 행과 같은 행에 있으면서 열이 일치하는 요소끼리 비교하고 있다. 0행의 0열과 1열, 1행의 0열과 1열 등등.

플래그 0 (열 기준)
[[0.1, 0.3, 0.5], [0.3, 0.5, 0.1]]
[[0.5, 0.1, 0.3], [0.1, 0.3, 0.5]]
[[0.3, 0.5, 0.1], [0.5, 0.1, 0.3]]
[[  1     2     0 ], [  2     0     1 ]]

플래그 1 (행 기준)
[[0.1, 0.3, 0.5],
[0.3, 0.5, 0.1]]    [[1 1 0],
[[0.5, 0.1, 0.3],
[0.1, 0.3, 0.5]]     [0 1 1],
[[0.3, 0.5, 0.1],
[0.5, 0.1, 0.3]]     [1 0 1]]

플래그 2 (행열 기준)
[[0.1, 0.3, 0.5], [0.3, 0.5, 0.1]]     [[2 1],
[[0.5, 0.1, 0.3], [0.1, 0.3, 0.5]]      [0 2],
[[0.3, 0.5, 0.1], [0.5, 0.1, 0.3]]      [1 0]]

텐서플로우 실수 오차(정밀도)

텐서플로우에서 이상한 점을 발견했다. 그런데, 이게 틀리다고 얘기할 수는 없겠지만, 지금까지의 실수 연산하고는 다른 점이어서 정리한다.

텐서플로우의 기본이 되는 코드를 하나 만들었다. hypothesis를 통해서 새로운 입력이 들어왔을 때의 결과를 예측하고 있다. hypothesis는 W와 X를 곱하는 아주 단순한 코드다. 결과를 예측하기 위해 X는 placeholder로 처리했다.

import tensorflow as tf
import numpy as np

x_data = [1., 2., 3.]
y_data = [2., 4., 6.]

W = tf.Variable(0.5)
X = tf.placeholder(tf.float32)

hypothesis = W * X
cost = tf.reduce_mean(tf.square(hypothesis-y_data))
rate = tf.constant(0.1)

optimizer = tf.train.GradientDescentOptimizer(rate)
train = optimizer.minimize(cost)

sess = tf.Session()
sess.run(tf.initialize_all_variables())

for i in range(3):
sess.run(train, feed_dict={X: x_data})
print(i, sess.run(W))
print('-'*30)

t = np.array([3.6]) # x가 3.6일 때의 y 예측 시도
ww = sess.run(W)
hh = sess.run(hypothesis, {X: t})

print('manual : {}'.format((ww*t)[0]))
print('hypothesis : {}'.format(hh[0]))
[출력 결과]
0 1.9
1 1.99333
2 1.99956
------------------------------
manual : 7.198400115966797
hypothesis : 7.198400020599365

출력된 결과를 보면, hypothesis를 통해 계산한 값과 직접 계산한 값이 다르게 나온다. 많이 다르지는 않고 미세하게 소숫점 뒷자리에서 달라진다. 실제로는 같을 때도 있고, 다르게 나올 때도 있다.

텐서플로우를 통해 hypothesis를 계산하는 것과 직접 W와 X를 곱하는 것이 같다는 것을 이해하지 못하면, 지금 코드는 의미가 없다. 이 부분은 단순 계산이기 때문에, W와 X가 복잡해지면 직접 할 수가 없으니까, 텐서플로우에 hypothesis를 계산해 달라고 부탁하는 것일 뿐이다.


아래 코드는 텐서플로우의 곱셈 코드다. 실수 2개를 곱해서 결과를 numpy로 변환을 해서 출력하고 있다. 텐서플로우는 결과를 numpy로 반환하기 때문에 함수 이름 또한 op2Numpy라고 만들었다. op2Numpy 함수는 functions.py 파일에 들어 있다.

import tensorflow as tf
import functions

a, b = 0.4, 0.6

print(functions.op2Numpy(tf.mul(a, b)))
print('{}'.format(functions.op2Numpy(tf.mul(a, b))))
[출력 결과]
0.24
0.24000000953674316

같은 함수를 호출했는데, 출력 결과가 다르게 나왔다. print 함수는 일정 자릿수 이상을 출력하지 않는 관계로 첫 번째 출력에서는 0.24까지만 표시됐다. 전체 자릿수 출력을 위해 문자열의 format 함수를 사용했다.

놀랍게도 아래쪽에 숨겨진 값들이 보인다. 컴퓨터에서는 모든 숫자를 2진수의 형태, 즉 비트(bit) 단위로 저장을 해야 한다. 2의 32승으로 실수를 저장한다는 뜻은 2의 32승 갯수만큼의 실수 종류만 저장할 수 있다는 뜻이다. 10진수를 2진수로 변환하는 과정에서 오차는 필연적으로 발생할 수밖에 없다.

마찬가지로 0.6을 10진수로 표현하는 것은 쉽지만, 2진수로 표현하게 되면 굉장히 길게 저장을 하게 되는데, 이 부분에서 오차가 발생하게 된다. 텐서플로우는 이렇게 발생하는 오차에 대한 처리 없이, 2진수 자체를 계산에 활용하고 있는 것처럼 보인다. 어쩌면 이런 오차 하나를 수정하는 것조차도 성능에 영향을 줄 수 있기 때문에 무시하는 것일 수도 있다. 실수 하나에 대해서라고 쉽게 얘기하지만, 한 번 연산에 수천, 수만 개의 연산이 포함될 수 있기 때문에 신중한 영역일 수도 있다.

0.24가 맞는지 0.24000000953674316이 맞는지 나는 판단할 수 없다. 그러나, 간혹 우리가 생각했던 결과가 나올 수 있다는 것은 명심해야 한다. 매우 작은 오차를 여러 번 적용하는 과정에서 꽤 큰 오차로 변할 수도 있지 않을까, 하는 걱정이 든다. 오차를 고민하지 않는 제일 좋은 방법은 직접 계산하려고 시도하지 말고, 텐서플로우에 계산을 맡기는 것이다. 첫 번째 코드에서 올바른 코드가 무엇이냐고 묻는다면, hypothesis를 사용한 코드라고 대답할 수 있다. 언제나.

그냥 결과가 다르게 나와서 궁금했을 뿐이고, 당연히 텐서플로우의 원칙을 따르는 것이 맞다. 궁금증을 풀었으니, 푹 자야겠다. 이렇게 단순한 문제일 거라고 생각하지 못하고, 온갖 상상을 적용해 보고 나서야 여기에 올 수 있었다. 개운하지는 않다.

[텐서플로우 정리] 08. 초기값과 난수

이번 글에서는 변수의 초기값을 주는 함수들에 대해 살펴 본다.
이번 코드 또한 functions.py 파일에 있는 함수를 이용해서 결과를 출력하고 있다. (functions.py)


코딩할 때마다 나오는 개념인데.. 매번 헷갈려서 정리한다. uniform이란 단어의 뜻을 이제서야 알았다.
정규분포(normal distribution) - 통계 확률분포에서 가장 중요한 분포로 종 모양(bell shpae)으로 표현되는 확률
균등(균일)분포(uniform distribution) - 각각의 구간에서 동일한 확률로 표현되는 분포로 사각형 모양
잘린(절단)정규분포(truncated normal distribution) - 정규분포에서 일부 구간을 잘라낸 분포
나무위키(정규분포)
나부랭이의 수학블로그(균등분포 개념정리)
잘린정규분포   truncated normal distribution(위키피디아, 영어)


tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
Outputs random values from a normal distribution.

Args:
shape: A 1-D integer Tensor or Python array. The shape of the output tensor.
mean: A 0-D Tensor or Python value of type dtype. The mean of the normal distribution.
stddev: A 0-D Tensor or Python value of type dtype. The standard deviation of the normal distribution.
dtype: The type of the output.
seed: A Python integer. Used to create a random seed for the distribution. See set_random_seed for behavior.
name: A name for the operation (optional).

Returns:
A tensor of the specified shape filled with random normal values.


tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
Outputs random values from a truncated normal distribution.

The generated values follow a normal distribution with specified mean and standard deviation, except that values whose magnitude is more than 2 standard deviations from the mean are dropped and re-picked.

Args:
shape: A 1-D integer Tensor or Python array. The shape of the output tensor.
mean: A 0-D Tensor or Python value of type dtype. The mean of the truncated normal distribution.
stddev: A 0-D Tensor or Python value of type dtype. The standard deviation of the truncated normal distribution.
dtype: The type of the output.
seed: A Python integer. Used to create a random seed for the distribution. See set_random_seed for behavior.
name: A name for the operation (optional).

Returns:
A tensor of the specified shape filled with random truncated normal values.


tf.random_uniform(shape, minval=0, maxval=None, dtype=tf.float32, seed=None, name=None)
Outputs random values from a uniform distribution.

The generated values follow a uniform distribution in the range [minval, maxval). The lower bound minval is included in the range, while the upper bound maxval is excluded.

For floats, the default range is [0, 1). For ints, at least maxval must be specified explicitly.

In the integer case, the random integers are slightly biased unless maxval - minval is an exact power of two. The bias is small for values of maxval - minval significantly smaller than the range of the output (either 2**32 or 2**64).

Args:
shape: A 1-D integer Tensor or Python array. The shape of the output tensor.
minval: A 0-D Tensor or Python value of type dtype. The lower bound on the range of random values to generate. Defaults to 0.
maxval: A 0-D Tensor or Python value of type dtype. The upper bound on the range of random values to generate. Defaults to 1 if dtype is floating point.
dtype: The type of the output: float32, float64, int32, or int64.
seed: A Python integer. Used to create a random seed for the distribution. See set_random_seed for behavior.
name: A name for the operation (optional).

Returns:
A tensor of the specified shape filled with random uniform values.

Raises:
ValueError: If dtype is integral and maxval is not specified.


import tensorflow as tf
from functions import showOperation as showOp

showOp(tf.zeros([2,3])) # [[ 0. 0. 0.] [ 0. 0. 0.]]
showOp(tf.ones([2,3], tf.int32)) # [[1 1 1] [1 1 1]]
showOp(tf.zeros_like(tf.ones([2,3]))) # [[ 0. 0. 0.] [ 0. 0. 0.]]
showOp(tf.fill([2,3], 2)) # [[2 2 2] [2 2 2]]
showOp(tf.fill([2,3], 2.0)) # [[ 2. 2. 2.] [ 2. 2. 2.]]
print('# ------------------------------------------------ #')
showOp(tf.linspace(1.0, 10.0, 4)) # [ 1. 4. 7. 10.]
showOp(tf.range(5)) # [0 1 2 3 4]
showOp(tf.range(0, 5)) # [0 1 2 3 4]
showOp(tf.range(0, 10, 2)) # [0 2 4 6 8]
print('# ------------------------------------------------ #')
# tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
# [[-0.14479451 -0.50265551 1.38471031] [-2.46794224 0.56639165 -0.59352636]]
showOp(tf.random_normal([2, 3]))
# [[ 5.0084033 6.28203726 5.49111032] [ 4.81292725 4.8216362 4.82326126]]
showOp(tf.random_normal([2, 3], mean=5.0))
# [[ 0.02739749 0.15692481 -0.18835409] [-0.20757729 0.76803416 -0.07633832]]
showOp(tf.random_normal([2, 3], stddev=0.35))
# [[ 4.7160387 5.51960945 5.0228653 ] [ 4.14505386 5.03473711 5.20692873]]
showOp(tf.random_normal([2, 3], mean=5.0, stddev=0.35, seed=1))
print('# ------------------------------------------------ #')
# -0.1293 -0.0633 0.0984 0.0508 0.1916 0.1197 -0.3135 -0.0823 -0.0300 0.0430
for _ in range(10):
showOp(tf.reduce_sum(tf.random_normal([2, 3], stddev=0.35) / 6))
# 5.03024 4.9332 4.97116 4.96042 4.99974 5.15176 4.961 4.64518 5.26943 5.18463
for _ in range(10):
showOp(tf.reduce_sum(tf.random_normal([2, 3], mean=5.0, stddev=0.35) / 6))
print('# ------------------------------------------------ #')
# tf.random_uniform(shape, minval=0, maxval=None, dtype=tf.float32, seed=None, name=None)
# [[ 0.67391849 0.66735017 0.0794853 ] [ 0.64219582 0.47089899 0.68388402]]
showOp(tf.random_uniform([2, 3]))
# [[ 1.28794432 2.02983379 2.73823977] [ 2.97947931 4.48065186 4.69495058]]
showOp(tf.random_uniform([2, 3], minval=5)) # no error. do not work.
# [[ 13.32174492 3.7804091 10.80069256] [ 8.99943733 0.50998271 9.10907364]]
showOp(tf.random_uniform([2, 3], maxval=15))
# [[ 8.30814171 8.18294716 6.43582296] [ 5.31517982 9.81415558 8.0894165 ]]
showOp(tf.random_uniform([2, 3], 5, 10))
# [[ 7.41970491 7.5310154 6.27571869] [ 5.62371159 9.66505051 8.32304668]]
showOp(tf.random_uniform([2, 3], 5, 10, seed=7))
print('# ------------------------------------------------ #')
# tf.random_shuffle(value, seed=None, name=None)
showOp(tf.random_shuffle(tf.Variable([1,2,3,4,5,6]))) # [3 4 6 5 2 1]
showOp(tf.random_shuffle(tf.Variable([[1,2], [3,4], [5,6]]))) # [[5 6] [3 4] [1 2]]
print('# ------------------------------------------------ #')
# [[ 0.23903739 0.92039955 0.05051243] [ 0.49574447 0.83552229 0.02647042]]
showOp(tf.random_uniform([2, 3], seed=1))
# [[ 0.23903739 0.92039955 0.05051243] [ 0.49574447 0.83552229 0.02647042]]
showOp(tf.random_uniform([2, 3], seed=1))
print('# ------------------------------------------------ #')
# tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
# The generated values follow a normal distribution with specified mean and standard deviation,
# except that values whose magnitude is more than 2 standard deviations
# from the mean are dropped and re-picked.
# [[-0.50039721 -1.03818107 -0.1909811 ] [-0.41344216 -0.91877717 0.24455361]] showOp(tf.truncated_normal([2,3])) # [[ 3.40512562 -0.11788981 1.92399371] [ 6.6576376 4.21847486 4.41285849]] showOp(tf.truncated_normal([2,3], stddev=3.5))

[텐서플로우 정리] 07. 세션과 구구단

텐서플로우를 구동해서 실질적인 결과를 보여준다.
여기까지 익히면 머신러닝을 학습하기 전의 준비 과정으로 충분하다.

1부터 3까지 반복하는 코드와 구구단을 출력하는 코드를 만들어 봤다.
각각 두 가지, 세 가지 버전으로 만들었는데,
많은 차이가 나진 않지만, 나름대로 모든 방법이 의미를 갖기 때문에 차이점을 주목해서 봤으면 한다.
가장 중요한 코드는 마지막에 있는 placeholder를 사용하는 방법이다.

# 예제1 : 1부터 3까지 증가
# 예제2 : 구구단

import tensorflow as tf

def one2three_1():
state = tf.Variable(1)
one = tf.constant(1)

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

for _ in range(3):
print(sess.run(state))
state = tf.add(state, one)

def one2three_2():
state = tf.Variable(0)
one = tf.constant(1)
value = tf.add(state, one)
update = tf.assign(state, value)

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

for _ in range(3):
print(sess.run(update))

def table99_1(which):
level = tf.constant(which)
state = tf.Variable(1)

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

for i in range(1, 10):
left, rite = sess.run(level), sess.run(state)
state = tf.add(state, 1)

print('{} x {} = {:2}'.format(left, rite, left*rite))

def table99_2(which):
# update를 처리하면 연계된 모든 Tensor 객체도 함께 처리됨
level = tf.constant(which)
state = tf.Variable(0)
add = tf.add(state, 1)
value = tf.assign(state, add)
update = tf.mul(level, value)

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

for _ in range(9):
# left, rite, result = sess.run([level, state, update])

result = sess.run(update)
left, rite = level.eval(), state.eval()

print('{} x {} = {:2}'.format(left, rite, result))

def table99_3(which):
left = tf.placeholder(tf.int32)
rite = tf.placeholder(tf.int32)
update = tf.mul(left, rite)

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

for i in range(1, 10):
result = sess.run(update, feed_dict={left: which, rite: i})
print('{} x {} = {:2}'.format(which, i, result))

one2three_1()
one2three_2()

table99_1(7)
table99_2(7)
table99_3(7)
[출력 결과 1]
1
2
3

[출력 결과 2]
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63


[텐서플로우 정리] 06. 수학 함수

제곱, 로그, 지수 등의 다양한 수학 함수에 대해 살펴 본다.
생각했던 것보다 함수가 많아 정리하기도 힘이 들었다. 대신 코딩할 때는 도움 되겠다.

import tensorflow as tf
import functions

c2, c3 = tf.constant([1.2, 5.6]), tf.constant([-4, -1, 7])
v2, v3 = tf.Variable([2.3, 4.5]), tf.Variable([-2, 3, 5])

print('-----------add_n------------') # same shape aod dtype 덧셈
functions.showOperation(tf.add_n([c2, v2])) # [ 3.5 10.10000038]
functions.showOperation(tf.add_n([c3, v3, v3])) # [-8 5 17]

print('-----------abs------------')
functions.showOperation(tf.abs(c2)) # [ 1.20000005 5.5999999 ]
functions.showOperation(tf.abs([c3, v3])) # [[4 1 7] [2 3 5]]

print('-----------neg------------')
functions.showOperation(tf.neg(c2)) # [-1.20000005 -5.5999999 ]
functions.showOperation(tf.neg([c3, v3])) # [[ 4 1 -7] [ 2 -3 -5]]

print('-----------sign------------')
functions.showOperation(tf.sign(c2)) # [ 1. 1.]
functions.showOperation(tf.sign([c3, v3])) # [[-1 -1 1] [-1 1 1]]

print('-----------square------------')
functions.showOperation(tf.square(c2)) # [ 1.44000006 31.3599987 ]
functions.showOperation(tf.square([c3, v3])) # [[16 1 49] [ 4 9 25]]

print('-----------round------------')
functions.showOperation(tf.round(c2)) # [ 1. 6.]
functions.showOperation(tf.round([c3, v3])) # [[-4 -1 7] [-2 3 5]]

print('-----------sqrt------------') # 정수 사용 불가. 음수는 nan.
functions.showOperation(tf.sqrt(c2)) # [ 1.44000006 31.3599987 ]
functions.showOperation(tf.sqrt([c2, v2])) # [[ 1.09544516 2.36643171] [ 1.5165751 2.12132025]]

print('-----------pow------------') # 갯수 일치 주의
functions.showOperation(tf.pow(c2, 2)) # [ 1.44000006 31.3599987 ]
# [[ 1.44000006 175.61599731] [ 5.28999996 91.125 ]]
functions.showOperation(tf.pow([c2, v2], [2, 3]))
# [[ 1.44000006 175.61599731] [ 5.28999996 91.125 ]]
functions.showOperation(tf.pow([c2, v2], [[2, 3], [2, 3]]))

print('-----------exp------------') # 정수 사용 불가. 음수 동작.
functions.showOperation(tf.exp(c2)) # [ 3.320117 270.4263916]
functions.showOperation(tf.exp([c2, v2])) # [[ 3.320117 270.4263916 ] [ 9.97418213 90.01712799]]

print('-----------log------------') # 정수 사용 불가. 음수는 nan.
functions.showOperation(tf.log(c2)) # [ 0.18232159 1.72276664]
functions.showOperation(tf.log([c2, v2])) # [[ 0.18232159 1.72276664] [ 0.83290911 1.50407743]]

print('-----------ceil------------') # 정수 사용 불가.
functions.showOperation(tf.ceil(c2)) # [ 2. 6.]
functions.showOperation(tf.ceil([c2, v2])) # [[ 2. 6.] [ 3. 5.]]

print('-----------floor------------') # 정수 사용 불가.
functions.showOperation(tf.floor(c2)) # [ 1. 5.]
functions.showOperation(tf.floor([c2, v2])) # [[ 1. 5.] [ 2. 4.]]

print('-----------maximum------------')
functions.showOperation(tf.maximum(c2, v2)) # [ 2.29999995 5.5999999 ]
# [[ 2.29999995 5.5999999 ] [ 2.29999995 5.5999999 ]]. [c2, c3] is error because shape is different.
functions.showOperation(tf.maximum([c2, c2], [v2, v2]))

print('-----------minimum------------')
functions.showOperation(tf.minimum(c2, v2)) # [ 1.20000005 4.5 ]
# [[ 1.20000005 4.5 ] [ 1.20000005 4.5 ]]
functions.showOperation(tf.minimum([c2, c2], [v2, v2]))

print('-----------sin------------') # 정수 사용 불가.
functions.showOperation(tf.sin(c2)) # [ 0.93203908 -0.63126671]
# [[ 0.93203908 -0.63126671] [ 0.74570525 -0.97753012]]
functions.showOperation(tf.sin([c2, v2]))

print('-----------cos------------') # 정수 사용 불가.
functions.showOperation(tf.cos(c2)) # [ 0.36235771 0.7755658 ]
# [[ 0.36235774 0.77556586] [-0.66627598 -0.21079579]]
functions.showOperation(tf.cos([c2, v2]))

print('-----------tan------------') # 정수 사용 불가.
functions.showOperation(tf.tan(c2)) # [ 2.5721519 -0.81394345]
# [[ 2.5721519 -0.81394345] [-1.1192137 4.63733196]]
functions.showOperation(tf.tan([c2, v2]))

c4 = tf.constant([[1, 3, 5], [0, 2, 4]])
v4 = tf.Variable([[1, 2], [3, 7], [8, 9]])

print('-----------matmul------------') # (2x3)*(3x2) = (2x2), (3x2)*(2x3) = (3x3)
functions.showOperation(tf.matmul(c4, v4)) # [[50 68] [38 50]]
functions.showOperation(tf.matmul(v4, c4)) # [[ 1 7 13] [ 3 23 43] [ 8 42 76]]


[텐서플로우 정리] 05. Shapes & Shaping

텐서플로우 내부를 보여주거나 변경하는 함수에 대해 소개한다.
텐서플로우에 이러한 역할을 하는 함수가 몇 개 없어 간략하게 정리할 수 있었다.

rank. 배열의 차원.

Rank

Math

Entity

Python example

0

Scalar

(magnitude only)

s = 483

1

Vector

(magnitude and direction)

v = [1.1, 2.2, 3.3]

2

Matrix

(table of numbers)

m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

3

3-Tensor

(cube of numbers)

t = [[[2], [4], [6]], [[8], [10], [12]], [[14], [16], [18]]]

n

n-Tensor

(you get the idea)

....


shape. 배열의 모양(2x5는 2행 5열)

Rank

Shape

Dimension number

Example

0

[]

0-D

A 0-D tensor. A scalar.

1

[D0]

1-D

A 1-D tensor with shape [5].

2

[D0, D1]

2-D

A 2-D tensor with shape [3, 4].

3

[D0, D1, D2]

3-D

A 3-D tensor with shape [1, 4, 3].

n

[D0, D1, ... Dn-1]

n-D

A tensor with shape [D0, D1, ... Dn-1].


내부 정보를 출력하는 함수로 functions.py에 만들었던 함수다.

def showConstantDetail(t):
sess = tf.InteractiveSession()
print(t.eval())
print('shape :', tf.shape(t))
print('size :', tf.size(t))
print('rank :', tf.rank(t))
print(t.get_shape())

sess.close()


shape을 변경할 수 있는 2개의 함수를 살펴 본다.

import tensorflow as tf
import functions

c1 = tf.constant([1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 3, 7])
v1 = tf.Variable([[1, 2, 3], [7, 8, 9]])

print('-----------reshape------------')
functions.showOperation(tf.reshape(c1, [2, -1])) # [[1 3 5 7 9 0] [2 4 6 8 3 7]]
functions.showOperation(tf.reshape(c1, [-1, 3])) # [[1 3 5] [7 9 0] [2 4 6] [8 3 7]]
functions.showOperation(tf.reshape(v1, [-1])) # [1 2 3 7 8 9]

c2 = tf.reshape(c1, [2, 2, 1, 3])
c3 = tf.reshape(c1, [1, 4, 1, 3, 1])

print('-----------squeeze------------') # reemoves dimensions of size 1
# [[[[1 3 5]] [[7 9 0]]] [[[2 4 6]] [[8 3 7]]]]
functions.showOperation(c2)
functions.showOperation(tf.squeeze(c2)) # [[[1 3 5] [7 9 0]] [[2 4 6] [8 3 7]]]

# [[[[[1] [3] [5]]] [[[7] [9] [0]]] [[[2] [4] [6]]] [[[8] [3] [7]]]]]
functions.showOperation(c3)
functions.showOperation(tf.squeeze(c3)) # [[1 3 5] [7 9 0] [2 4 6] [8 3 7]]

reshape 함수에 사용된 -1은 나누어 떨어지는 숫자를 자동으로 적용하겠다는 뜻이다. 12개의 요소가 있을 때, [3,-1]이라는 것은 [3,4]와 같고, [2,2,-1]이라는 것은 [2,2,3]과 같다. 행과 열, 페이지에서 중심이 되는 것을 지정한 다음에, 나머지를 계산하기 싫을 때 주로 사용된다.

[텐서플로우 정리] 04. Slicing & Joining

다차원 배열을 자르거나, 잘린 배열을 연결하는 함수들에 대해 살펴 본다.
이 영역에 속한 텐서플로우 함수들은 갯수가 많아서 전체를 정리하지 못했다.
지금까지의 경험으로 좀더 사용될 것 같은 함수들 위주로만 사용법을 정리했다.

이번 코드에서는 원본과 출력 결과를 자세하게 살펴 보아야 한다.
다차원 배열이 등장하다 보니, 데이터 갯수 때문에 결과 확인이 조금 어렵다.

import tensorflow as tf
import functions

c1 = tf.constant([1, 3, 5, 7, 9, 0, 2, 4, 6, 8])
c2 = tf.constant([1, 3, 5])
v1 = tf.constant([[1, 2, 3, 4, 5, 6], [7, 8, 9, 0, 1, 2]])
v2 = tf.constant([[1, 2, 3], [7, 8, 9]])

print('-----------slice------------')
functions.showOperation(tf.slice(c1, [2], [3])) # [5 7 9]
functions.showOperation(tf.slice(v1, [0, 2], [1, 2])) # [[3 4]]
functions.showOperation(tf.slice(v1, [0, 2], [2, 2])) # [[3 4] [9 0]]
functions.showOperation(tf.slice(v1, [0, 2], [2,-1])) # [[3 4 5 6] [9 0 1 2]]

print('-----------split------------')
functions.showOperation(tf.split(0, 2, c1)) # [[1, 3, 5, 7, 9], [0, 2, 4, 6, 8]]
functions.showOperation(tf.split(0, 5, c1)) # [[1, 3], [5, 7], [9, 0], [2, 4], [6, 8]]
functions.showOperation(tf.split(0, 2, v1)) # [[[1, 2, 3, 4, 5, 6]], [[7, 8, 9, 0, 1, 2]]]
functions.showOperation(tf.split(1, 2, v1)) # [[[1, 2, 3], [7, 8, 9]], [[4, 5, 6], [0, 1, 2]]]

print('-----------tile------------')
functions.showOperation(tf.tile(c2, [3])) # [1 3 5 1 3 5 1 3 5]
# [[1 2 3 1 2 3] [7 8 9 7 8 9] [1 2 3 1 2 3] [7 8 9 7 8 9]]
functions.showOperation(tf.tile(v2, [2, 2]))

print('-----------pad------------') # 2차원에 대해서만 동작
# [[0 0 0 0 0 0 0]
# [0 0 1 2 3 0 0]
# [0 0 7 8 9 0 0]
# [0 0 0 0 0 0 0]]
functions.showOperation(tf.pad(v2, [[1, 1], [2, 2]], 'CONSTANT'))
# [[9 8 7 8 9 8 7]
# [3 2 1 2 3 2 1]
# [9 8 7 8 9 8 7]
# [3 2 1 2 3 2 1]] # 3 2 1 2 3 2 1 2 3 2 1 처럼 반복
functions.showOperation(tf.pad(v2, [[1, 1], [2, 2]], 'REFLECT'))
# [[2 1 1 2 3 3 2]
# [2 1 1 2 3 3 2]
# [8 7 7 8 9 9 8]
# [8 7 7 8 9 9 8]] # 3 2 1 (1 2 3) 3 2 1. 가운데와 대칭
functions.showOperation(tf.pad(v2, [[1, 1], [2, 2]], 'SYMMETRIC'))

print('-----------concat------------')
functions.showOperation(tf.concat(0, [c1, c2])) # [1 3 5 7 9 0 2 4 6 8 1 3 5]
functions.showOperation(tf.concat(1, [v1, v2])) # [[1 2 3 4 5 6 1 2 3] [7 8 9 0 1 2 7 8 9]]
# functions.showOperation(tf.concat(0, [v1, v2])) # error. different column size.

c3, c4 = tf.constant([1, 3, 5]), tf.constant([[1, 3, 5], [5, 7, 9]])
v3, v4 = tf.constant([2, 4, 6]), tf.constant([[2, 4, 6], [6, 8, 0]])

print('-----------pack------------') # 차원 증가. tf.pack([x, y]) = np.asarray([x, y])
functions.showOperation(tf.pack([c3, v3])) # [[1 3 5] [2 4 6]]
functions.showOperation(tf.pack([c4, v4])) # [[[1 3 5] [5 7 9]] [[2 4 6] [6 8 0]]]

t1 = tf.pack([c3, v3])
t2 = tf.pack([c4, v4])

print('-----------unpack------------') # 차원 감소
functions.showOperation(tf.unpack(t1)) # [[1, 3, 5], [2, 4, 6]]
functions.showOperation(tf.unpack(t2)) # [[[1, 3, 5], [5, 7, 9]], [[2, 4, 6], [6, 8, 0]]]

print('-----------reverse------------')
functions.showOperation(tf.reverse(c1, [True])) # [8 6 4 2 0 9 7 5 3 1]
functions.showOperation(tf.reverse(v1, [True, False])) # [[7 8 9 0 1 2] [1 2 3 4 5 6]]
functions.showOperation(tf.reverse(v1, [True, True ])) # [[2 1 0 9 8 7] [6 5 4 3 2 1]]

print('-----------transpose------------') # perm is useful to multi-dimension .
functions.showOperation(tf.transpose(c3)) # [1 3 5]. not 1-D.
functions.showOperation(tf.transpose(c4)) # [[1 5] [3 7] [5 9]]
functions.showOperation(tf.transpose(c4, perm=[0, 1])) # [[1 3 5] [5 7 9]]
functions.showOperation(tf.transpose(c4, perm=[1, 0])) # [[1 5] [3 7] [5 9]]

print('-----------gather------------')
functions.showOperation(tf.gather(c1, [2, 5, 2, 5])) # [5 0 5 0]
functions.showOperation(tf.gather(v1, [0, 1])) # [[1 2 3 4 5 6] [7 8 9 0 1 2]]
functions.showOperation(tf.gather(v1, [[0, 0], [1, 1]])) # [[[1 2 3 4 5 6] [1 2 3 4 5 6]] [[7 8 9 0 1 2] [7 8 9 0 1 2]]]

print('-----------one_hot------------') # make one-hot matrix.
# [[ 1. 0. 0.]
# [ 0. 1. 0.]
# [ 0. 0. 1.]
# [ 0. 1. 0.]]
functions.showOperation(tf.one_hot([0, 1, 2, 1], 3))
# [[ 0. 0. 0. 1.]
# [ 0. 0. 0. 0.]
# [ 0. 1. 0. 0.]]
functions.showOperation(tf.one_hot([3, -1, 1], 4))


[텐서플로우 정리] 03. 관계 연산, 논리 연산

텐서플로우에서 제공하는 관계 연산과 논리 연산 함수에 대해 살펴 본다.
관계 연산은 참(True)과 거짓(False)을 만들어 내고, 논리 연산은 참과 거짓을 갖고 계산을 한다.

import tensorflow as tf
import functions

c1, c2, c3 = tf.constant([1, 2]), tf.constant([1.0, 2.0]), tf.constant([1])
v1, v2 = tf.Variable([1, 3]), tf.Variable([1.0, 3.0])

print('-----------equal------------')
functions.showOperation(tf.equal(c1, v1)) # [ True False]
functions.showOperation(tf.equal(c2, v2)) # [ True False]
# functions.showOperation(tf.equal(c1, c2)) # error. different type.

print('-----------not_equal------------')
functions.showOperation(tf.not_equal(c1, v1)) # [False True]
functions.showOperation(tf.not_equal(c2, v2)) # [False True]
# functions.showOperation(tf.not_equal(c1, c3)) # error. different size.

print('-----------less------------')
functions.showOperation(tf.less(c1, v1)) # [False True]
functions.showOperation(tf.less(c2, v2)) # [False True]

print('-----------less_equal------------')
functions.showOperation(tf.less_equal(c1, v1)) # [ True True]
functions.showOperation(tf.less_equal(c2, v2)) # [ True True]

print('-----------greater------------')
functions.showOperation(tf.greater(c1, v1)) # [False False]
functions.showOperation(tf.greater(c2, v2)) # [False False]

print('-----------greater_equal------------')
functions.showOperation(tf.greater_equal(c1, v1)) # [ True False]
functions.showOperation(tf.greater_equal(c2, v2)) # [ True False]

c4 = tf.constant([[1, 3], [5, 7]])
v4 = tf.Variable([[2, 4], [6, 8]])

# if True, select c4. if False, select v4.
cond1 = tf.Variable([[True, True ], [False, False]])
cond2 = tf.Variable([[True, False], [False, True ]])

print('-----------select------------')
functions.showOperation(tf.select(cond1, c4, v4)) # [[1 3] [6 8]]
functions.showOperation(tf.select(cond2, c4, v4)) # [[1 4] [6 7]]

c5 = tf.constant([[True , True], [False, False]])
v5 = tf.Variable([[False, True], [True , False]])

functions.showOperation(tf.where(c5)) # [[0 0] [0 1]]
functions.showOperation(tf.where(v5)) # [[0 1] [1 0]]

functions.showOperation(tf.select(cond1, c4, v4)) # [[1 3] [6 8]]
functions.showOperation(tf.select(cond2, c4, v4)) # [[1 4] [6 7]]

print('-----------and, or, xor, not------------')
functions.showOperation(tf.logical_and(c5, v5)) # [[False True] [False False]]
functions.showOperation(tf.logical_or (c5, v5)) # [[ True True] [ True False]]
functions.showOperation(tf.logical_xor(c5, v5)) # [[ True False] [ True False]]
functions.showOperation(tf.logical_not(c5)) # [[False False] [ True True]]

결과를 보면 함수가 하는 역할을 알 수 있기 때문에 별도의 주석을 달지 않았는데, select 함수의 경우는 결과를 보고 이해하기가 쉽지 않다. select 함수의 두 번째 매개변수는 참일 때 사용하고, 세 번째 매개변수는 거짓일 때 사용한다. 첫 번째 매개변수로 전달된 참과 거짓 값으로부터 참이라면 앞에 오는 것을, 거짓이라면 뒤에 오는 것을 선택하는 구조이다.

c = tf.Variable([1, 2])
v = tf.Variable([3, 4])
showOperation(tf.select([True, False], c, v))       # [1 4]
showOperation(tf.select([True, False], v, c))       # [3 2]

c와 v를 전달한다면 c는 참일 때 사용하고, v는 거짓일 때 사용한다. 마지막 줄에서처럼 순서를 바꾸면, 결과도 달라진다.

[텐서플로우 정리] 02. 산술 연산

텐서플로우에서 상수와 변수를 만들어 보고,
이들을 사용해서 덧셈, 뺄셈, 곱셈, 나눗셈 등의 산술 연산을 수행해 본다.

Tensor 출력을 위해 functions.py 파일의 함수를 사용한다. 출력 결과는 코드 오른쪽에 주석으로 달아 놓았다.
코드는 매우 단순하다. 여기서 볼 것은 함수에 전달되는 매개 변수의 형태다. 1-D, 2-D 등등.

import tensorflow as tf
import functions

c1, c2 = tf.constant([3]), tf.constant([1, 5])
v1, v2 = tf.Variable([5]), tf.Variable([2, 4])

functions.showConstant(c1)
functions.showConstant(c2)
functions.showVariable(v1)
functions.showVariable(v2)

print('-----------add------------')
functions.showOperation(tf.add(c1, v1)) # [8]
functions.showOperation(tf.add(c2, v2)) # [3 9]
functions.showOperation(tf.add([c2, v2], [c2, v2])) # [[ 2 10] [ 4 8]]

print('-----------sub------------')
functions.showOperation(tf.sub(c1, v1)) # [-2]
functions.showOperation(tf.sub(c2, v2)) # [-1 1]

print('-----------mul------------')
functions.showOperation(tf.mul(c1, v1)) # [15]
functions.showOperation(tf.mul(c2, v2)) # [ 2 20]

print('-----------div------------')
functions.showOperation(tf.div(c1, v1)) # [0]
functions.showOperation(tf.div(c2, v2)) # [0 1]

print('-----------truediv------------')
functions.showOperation(tf.truediv(c1, v1)) # [ 0.6]
functions.showOperation(tf.truediv(c2, v2)) # [ 0.5 1.25]

print('-----------floordiv------------')
functions.showOperation(tf.floordiv(c1, v1)) # [0]
functions.showOperation(tf.floordiv(c2, v2)) # [0 1]

print('-----------mod------------')
functions.showOperation(tf.mod(c1, v1)) # [3]
functions.showOperation(tf.mod(c2, v2)) # [1 1]


[텐서플로우 정리] 01. 텐서플로우 화면 출력 함수

텐서플로우가 왜 어려울까?
대부분의 사람들은 텐서플로우가 아니라 머신러닝이 어렵다고 한다.
텐서플로우를 사용하는 방법에 대한 소개는 정말 간략하고,
나머지는 머신러닝에서 텐서플로우가 적용되는 방식에 대해 설명한다.

텐서플로우를 사용하기 위한 기본 개념 일부를 코드로 정리했다.
당연한 것일 수도 있지만, 나는 이것 때문에 머신러닝을 익히는 것이 많이 힘들었다.

텐서플로우를 익히려면 Tensor 객체에 들어간 값의 확인이 첫 번째다.
그런데, 텐서플로우에서는 아직 모르기 때문에 값을 보여주려고 하지 않는다.
이번 줄에서 만든 코드가 어떤 데이터를 갖는지 알아야 다음 줄도 이해할 수가 있는데.. 말이다.

왜 이 생각을 못했는지 모르겠지만, 간단하게 처리할 수 있었다.
아래 함수들을 사용해서 텐서플로우를 공부했는데, 답답한 것 없이 너무 통쾌했다.

아래에는 상수, 변수, 연산자 등의 출력 함수가 있다. 모듈의 이름은 functions.py로 해서 import 해서 사용했다.
너무 간단한 관계로 코드에 대한 설명은 생략한다.
어찌 됐든 이 파일은 이후 나오는 텐서플로우 코드의 기본 코드로 활용된다.

# functions.py

import tensorflow as tf
import functools, operator

def getLength(t):
temp = (dim.value for dim in t.get_shape()) # dim is Dimension class.
return functools.reduce(operator.mul, temp)

def showConstant(t):
sess = tf.InteractiveSession()
print(t.eval())
sess.close()

def showConstantDetail(t):
sess = tf.InteractiveSession()
print(t.eval())
print('shape :', tf.shape(t))
print('size :', tf.size(t))
print('rank :', tf.rank(t))
print(t.get_shape())

sess.close()

def showVariable(v):
sess = tf.InteractiveSession()
v.initializer.run()
print(v.eval())
sess.close()

def var2Numpy(v):
sess = tf.InteractiveSession()
v.initializer.run()
n = v.eval()
sess.close()

return n

def op2Numpy(op):
sess = tf.InteractiveSession()
init = tf.initialize_all_variables()
sess.run(init)
ret = sess.run(op)
sess.close()

return ret

def showOperation(op):
print(op2Numpy(op))


텐서보드 사용법

TensorBoard는 TensorFlow에 기록된 로그를 그래프로 시각화시켜서 보여주는 도구다.


1. TensorBoard 실행
tensorboard --logdir=/tmp/sample
루트(/) 폴더 밑의 tmp 폴더 밑의 sample 폴더에 기록된 로그를 보겠다,라는 명령.
logdir 뒤에는 로그가 기록된 폴더를 명시한다.

기록된 폴더는 소스 코드를 구동시킬 때 명시하게 되어 있다.
즉, 소스 코드를 구동하지 않으면 로그도 없으므로 볼 것도 없다.
다만 로그가 그냥 기록되는 것은 아니고 소스 코드에 직접 관련 코드를 넣어야 한다.

2. tensorboard 정체
앞에서 실행한 명령의 tensorboard는
파이썬 설치 폴더 아래의 tensorflow 폴더 밑에 있는 tensorboard.py 파일을 가리킨다.

맥에서 tensorboard.py 파일의 위치
/usr/local/lib/python3.5/site-packages/tensorflow/tensorboard/tensorboard.py

3. 로그 위치
소스 코드에서 로그를 기록하기 위한 코드이다.
writer = tf.train.SummaryWriter("/tmp/test_logs", session.graph)

소스 코드를 구동하면 "/tmp/test_logs" 폴더에 확장자가 local인 파일이 생성된다.
-rw-r--r--   1 applekoong  wheel  681  8  1 21:37 events.out.tfevents.1470055070.applekoong.local

파일 이름이 꽤 길고 복잡하다.
이 파일이 있어야 tensorboard에서 결과를 볼 수 있다.

4. 포트 번호
tensorboard 기본 포트번호는 6006이다. 필요에 따라 포트 번호를 바꿀 수 있다.
tensorboard --logdir=/tmp/sample --port=8008

tensorboard 명령을 실행할 때 port 옵션을 사용해서 포트 번호를 지정할 수 있다.
동시에 여러 개의 로그를 보고 싶을 때 사용할 수 있다.

5. 웹 브라우저
콘솔에서 명령을 입력했을 때, 출력된 메시지를 보면 ip 주소가 나온다.
웹 브라우저를 열고 해당 주소와 포트 번호를 입력하면 그래프를 볼 수 있다.

입력 주소에는 두 가지가 있다.
0.0.0.0:6006 또는 localhost:6006

6. tensorboard 종료
웹 브라우저는 단순히 로그를 시각화하는 역할만 한다.
웹 브라우저를 종료한다고 해서 tensorboard가 종료되는 것이 아니다.

tensorboard 명령을 입력하면 해당 콘솔은 블록(대기) 상태로 들어간다.
ctrl+c를 입력하면 tensorboard가 종료된다. 맥에서도 ctrl+c를 사용한다. cmd+c가 아니다.

7. 주의사항
tensorboard를 실행하면 콘솔 창에 알 수 없는 경고가 뜬다.
결론적으로 결과를 보는 데에는 지장이 없다. 구글링해도 나오지 않는다.

WARNING:tensorflow:IOError [Errno 2] No such file or directory:
    '/usr/local/lib/python3.5/site-packages/tensorflow/tensorboard/TAG'
    on path /usr/local/lib/python3.5/site-packages/tensorflow/tensorboard/TAG
WARNING:tensorflow:Unable to read TensorBoard tag

또 하나는 로그 폴더를 잘못 입력해도 문제가 발생하지 않는다.
다만 웹 브라우저에 결과가 나타나지 않을 뿐이다.
tensorboard 입장에서는 폴더가 없다고 해서 이상한 행동을 할 필요는 없다고 보는 듯..
처음에 폴더 지정이 어색해서 여러 번 실수를 했다.

로그 결과는 계속 누적된다.
이전 결과는 사라지지 않고 남아 있다. 그러나, 웹 브라우저에서는 최신의 결과만 볼 수 있다.
이전 결과를 볼 수는 있겠지만, 화면에서 예전 결과를 선택하는 방법은 찾지 못했다.

이전 로그가 있을 때 뜨는 경고 메시지.
WARNING:tensorflow:Found more than one graph event per run. Overwriting the graph with the newest event.

8. 예제 1
tensorboard에 점 하나 찍는 예제
import tensorflow as tf

a = tf.constant(3.0)
b = tf.constant(5.0)
c = a * b

# tensorboard에 point라는 이름으로 표시됨
c_summary = tf.summary.scalar('point', c)
merged = tf.summary.merge_all()

with tf.Session() as sess:
writer = tf.summary.FileWriter('./board/sample_1', sess.graph)

result = sess.run([merged])
sess.run(tf.global_variables_initializer())

writer.add_summary(result[0])



9. 예제 2
두 개의 직선을 출력하는 예제. 김성훈 교수님께서 말씀하신 5단계를 순서대로 보여준다.
import tensorflow as tf

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

add = tf.add(X, Y)
mul = tf.multiply(X, Y)

# step 1: node 선택
add_hist = tf.summary.scalar('add_scalar', add)
mul_hist = tf.summary.scalar('mul_scalar', mul)

# step 2: summary 통합. 두 개의 코드 모두 동작.
merged = tf.summary.merge_all()
# merged = tf.summary.merge([add_hist, mul_hist])

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

# step 3: writer 생성
writer = tf.summary.FileWriter('./board/sample_2', sess.graph)

for step in range(100):
# step 4: 노드 추가
summary = sess.run(merged, feed_dict={X: step * 1.0, Y: 2.0})
writer.add_summary(summary, step)

# step 5: 콘솔에서 명령 실행
# tensorboard --logdir=./board/sample_2
# 콘솔 폴더가 현재 폴더(.)가 아니라면, 절대 경로 지정
# tensorboard --logdir=~/PycharmProjects/test/board/sample_2

윈도우에서 텐서플로우 사용하기

며칠 동안 설치라는 미명하에
눈을 빨갛게 하고 애들한테 화를 내며 알아낸 것을 적어보려 한다.
일이 안 되면 애들한테 굉장히 예민해지는 직업병이 있다.

텐서플로우는 리눅스와 매킨토시에서만 동작한다.
마이크로소프트도 자신만의 머신러닝 도구를 갖고 있고 오픈까지 했기 때문일까?
사용자가 가장 많은
프로그래머가 아닌 사람들에게까지 저변을 확대하고 있는 시점에서
윈도우를 지원하지 않는다는 것은 정말 이상한 일이다.
다만 도커(docker)를 사용해 보니
어느 정도는 이것 때문에 윈도우 버전을 제공하지 않았구나, 라는 느낌을 받을 수 있었다.


도커는 리눅스 운영체제를 윈도우나 맥에서 사용할 수 있게 해주는 프로그램이라고 생각하면 된다.
다시 말해, 텐서플로우가 동작하는 리눅스 운영체제를
가상 머신(virtual machine)처럼 윈도우 위에서 대신 동작시켜 주는 엔진으로,
윈도우에서 직접 텐서플로우를 다룰 수는 없다.
일반적으로 가상 머신은 느리고, 도커는 엄청 빠르다고 한다. 
이번에 처음 써본 느낌은 "최고 중의 최고"라고 생각한다. 사용법도 무지 쉽다.

순서대로 따라가 보자. 
화면을 일일이 캡쳐하는 식의 이미지는 넣지 않겠다. 그리 어렵지 않으니까.

1. 도커 설치
가장 먼저 할 일은 도커를 설치하는 것이다. 
개인적으로는 이게 제일 어려웠는데, 두 가지 방법이 있다.
도커 툴박스(Docker Toolbox)와 도커포윈도우(Docker for Windows).

구글링을 하면 도커 툴박스를 설치하는 것으로 시작하는 문서가 대부분이다.
그러나, 도커 툴박스는 윈도우 10 이전의 버전에서만 사용하는 것이 좋다.
도커 홈페이지의 글을 읽어보면
2016년 7월 현재, 무료로 설치할 수 있는 버전인 윈도우 10 Pro같은 최신 버전은
도커포윈도우를 설치하면 성능에 유리하다고 한다. 네이티브(native) 코드를 직접 사용하기 때문에.
참, 운영체제는 반드시 64비트여야 한다.

  도커 툴박스 https://www.docker.com/products/docker-toolbox
  도커포윈도우 https://docs.docker.com/docker-for-windows

주의할 점이 또 있다.
도커 툴박스는 오라클에서 배포하는 VirtualBox라는 가상 머신 프로그램을 설치하는데,
도커포윈도우는 최신 버전의 Hyper-V 프로그램을 사용하고, 당연히 Hyper-V가 더 낫다.
도커 제작사에서 그렇게 얘기하니까. 
그런데, 구글링을 하면 VirtualBox에 대한 얘기를 많이 하기 때문에
도커 설치 후 잘 동작하지 않으면 VirtualBox 설치를 안 했기 때문으로 착각할 수 있다.

두 가지 모두, 설치는 기본 옵션만 사용한다.
그 어떤 것도 바꾸지 말고 계속해서 엔터키만 눌러서 진행한다.
나는 두 대의 컴퓨터에 각각 설치해 봤다.
그런데, 두 개 모두 설치하니까 VirtualBox와 Hyper-V 충돌이 발생했다. 한 가지만 사용하자.

사용법은 두 가지가 같다.
도커 툴박스는 Docker Quickstart Terminal이라는 전용 콘솔을 사용하고,
도커포윈도우는 윈도우에 기본 제공되는 콘솔을 사용한다.
콘솔의 모양이 살짝 다르지만, 이후 진행 과정에서 입력하는 내용은 완전히 똑같다.
입력 문구나 에러가 발생했을 때의 메시지까지 모두 똑같다.

2. 콘솔 창 열기
도커 툴박스를 설치했다면, 새로 설치된 프로그램에서 Docker 그룹을 찾는다.
그 안에 보면 Docker Quickstart Terminal 메뉴가 있다. 이걸 클릭하면 된다.

도커 툴박스를 사용하는 설치 방법을 보여주는 문서를 참고할 수도 있다.
http://www.netinstructions.com/how-to-install-and-run-tensorflow-on-a-windows-pc/

도커포윈도우라면, 윈도우 기본 콘솔을 열어야 한다.
화면 왼쪽 아래 있는 창문처럼 생긴 윈도우 버튼을 누른다.
'명령 프롬프트' 메뉴가 있으면 누르고 없으면 검색 버튼을 누르고 입력 창에 cmd라고 입력한다.

윈도우 10 화면이다. 이 부분에 cmd라고 입력한다.

아무 것도 없는 검정 화면이 콘솔이다. 왼쪽에 있는 >를 프롬프트(prompt)라고 부른다.
자기 컴퓨터 이름으로 나타나기 때문에 서로 다를 수 있다.

보통 도커 사용법을 검색하면
이 시점에서 가상으로 뭔가를 만들라고 하는데, 전혀 그럴 필요 없다.
도커를 사용하는 목적이 가상 파일 배포에 있는 것이 아니라 
다른 사람들이 만들어 놓은 가상 파일을 가져다 이용하는 데 있기 때문에
이 시점에선 할 게 없다. 뒤에서 명령어 복사해서 붙여넣을 거니까.

3. 텐서플로우 공식 설치 페이지로 이동
구글에서 제공하는 공식 페이지가 있다.
https://www.tensorflow.org/versions/master/get_started/os_setup.html

 
구글링해서 들어가면 예전 문서가 많아서 0.9 버전같은 이전 버전으로 연결될 수도 있다.
이럴 경우 왼쪽에 보면 버전을 선택할 수 있도록 되어 있는데,
여기서 master를 선택하면 언제나 최신 버전이 된다. 현재 버전은0.10.

4. Docker install로 이동
Pip, Virtualenv, Anaconda, Docker, From sources와 같은 방법으로 설치할 수 있다.
우리는 이 중에서 도커를 사용해서 설치하려 하고 있다.

페이지 중간쯤에 보면 Docker installation 항목이 나온다.
대충 읽어보면, 구글에서는 도커 이미지를 4가지 종류로 제공한다고 말하고 있다.
cpu binary, cpu binary + source, gpu binary, gpu binary + source.

여기 있는 4가지 중의 하나를 고른다. 
나는 노트북에서 돌릴 거라서 첫 번째에 있는 cpy binary 버전을 사용했다.

  docker run -it -p 8888:8888 gcr.io/tensorflow/tensorflow
  nvidia-docker run -it -p 8888:8888 gcr.io/tensorflow/tensorflow:latest-gpu

위의 그림 바로 아래에 위와 같은 도커 명령이 있다.
명령 오른쪽 끝에 있는 것이 이미지 이름이다. 다른 버전을 고르고 싶다면, 해당 이미지 이름을 사용하면 된다.
이들 버전은 모두 IPython Notebook이라는 웹 기반의 파이썬 인터페이스와 연동되어 있다.
그래서, 명령 중간에 -p 8888:8888처럼 웹 브라우져에서 접근할 수 있는 포트 번호를 쓰게 되어 있다.
앞뒤가 똑같은 포트번호~~. 번호는 아무 거나 줘도 되지만, 같은 것을 주는 것이 좋다.
8888과 6006을 가장 많이 사용한다 아래 명령어는 gpu 버전에 사용한다.

5. 도커 명령 입력
도커 툴박스 전용 콘솔 혹은 기본 콘솔 창에 위의 내용을 붙여 넣고 엔터를 누른다.
첫 번째 나오는 메시지에 "뭐가 없어서 할 수 없다"처럼 뜨는데, 무시하면 된다.
컴퓨터에 없기 때문에 서버로부터 가져오겠다, 정도의 문구다.
다운로드 받고, 압축도 풀고 하면서 많은 것을 한다.
시간은 10분 정도 걸리는 것 같다.

설치가 끝나면, 자동으로 해당 이미지 환경으로 들어간다.
정확하게 설명하면, 설치한 리눅스 환경으로 들어간다.
커서 왼쪽에 있는 프롬프트 모양이 $나 #으로 바뀌었고, 이제부터 리눅스 명령어를 사용해야 한다.
리눅스 운영체제를 도커의 도움으로 돌리고 있으니까.

그런데, 여기서 IPython Notebook으로 자동 진입하기 때문에
실제로는 몇몇 색상이 나타나긴 하지만, 커서 같은 것은 없다.
출력된 도움말을 보면 ctrl+c를 누르면 종료한다고 되어 있다.
종료하면 리눅스를 탈출해서 이전의 콘솔 환경으로 돌아간다. 그 전에 웹 브라우저를 띄워보자.
그리고, localhost:8888이라고 주소창에 입력하자. 그러면, 아래와 같은 그림을 볼 수 있다.
IPython Notebook이 동작 중이라서 프롬프트는 보이지 않는다.



6. 리눅스 서버 재진입
ctrl+c를 누르고 빠져 나오자.
IPython Notebook은 따로 공부가 필요하다. 구글링해서 필요한 만큼만 공부하도록 하자. 

  docker run -it gcr.io/tensorflow/tensorflow bash

포트 번호 없이 입력하면 리눅스 bash 환경으로 들어갈 수 있다.
오른쪽 끝에 있는 bash를 쉘(shell)이라고 부른다. 윈도우 콘솔과 같은 개념이다.

파이썬을 모르면 할 수 있는게 없다. python 또는 python3이라고 입력하고 엔터를 누른다.
놀랍게도 우리가 설치한 리눅스 서버는 버전2와 3을 모두 설치해 놓았다.
아쉬운 점은 텐서플로우는 버전 2에만 연결되어 있고, 버전 3에서는 사용할 수 없다.

7. 텐서플로우 예제 테스트
bash 환경에서 python이라고 입력했다고 가정하자. python3에서는 동작하지 않으니까.
python을 입력했다면 >>>가 나와 있는 파이썬 인터프리터로 진입한 상태이다.
여기서 입력하는 모든 것을 파이썬이 해석한다.
빠져나올 때는 exit()라고 둥근 괄호까지 입력해야 한다.

Run a TensorFlow demo model 항목에 보면 코드가 한 줄 나와 있다. 아래 코드를 복사해서 붙여 넣자.

 $ python -c 'import os; import inspect; import tensorflow; 
              print(os.path.dirname(inspect.getfile(tensorflow)))'

나는 두 줄로 썼지만, 타이핑하지 말고 꼭 복사해서 붙여 넣자.
텐서플로우가 설치된 경로를 화면에 출력한다.
Run a TensorFlow demo model 항목에 나와 있는 것처럼 경로가 표시된다.

 /usr/local/lib/python2.7/dist-packages/tensorflow
 
/usr/local/lib/python2.7/site-packages/tensorflow

8. mnist 모델 구동
구글에서 코딩해 놓은 mnist 모델이 있다. 이걸 돌려보자.
명령어는 아래와 같은 데, 같은 곳에 숨어 있어서 잘 보이지 않는다.
일단 python 콘솔에서 빠져 나온다. 이 명령은 파이썬 외부에서 파이썬 코드를 실행시킨다.

  python -m tensorflow.models.image.mnist.convolutional

2대의 데스크탑에서 돌려봤는데, 한 번은 55분, 한 번은 1시간 30분 걸렸다.
python이라고 했으니까, 버전 2를 사용하고 있음을 알겠다.

8,500회 반복하고, 100번에 한 번씩 결과를 출력한다.
최종 성능은 99를 넘는 훌륭한 모델이다. 입력 명령에 있는 것처럼 convolutional 모델을 사용하고 있다.
김성훈 교수님께서 말씀하시던 99%가 이걸 보고 말씀하신 것 같다.

9. mnist 실행 결과
두 번의 실행 결과에서 cpu가 좋지 않은 컴퓨터의 성능이 더 좋았다.
컴퓨터에 설치된 지저분한 것들이 좋은 컴퓨터에 많았던 것은 사실이다.
두 번째로는 성능 좋은 쪽에서는 도커 툴박스에서 실행했다.
느낌이 뭐라 그럴까.., 얘가 동작하고 있나.. 하는 느낌이 들 정도로 기분이 묘했다.
일단은 구글에서 얘기한 것처럼 도커포윈도우가 좋은 성능을 낸다고 보는 게 맞는 것 같다.

하나 더.
도커 허브에서 우분투 이미지를 가져다가 파이썬3과 텐서플로우까지 직접 설치했다.
성능 안 좋은 컴퓨터 얘기다.
그런데, 여기서 돌렸던 mnist 모델은 성능이 40% 가량 좋게 나왔다.
이게 뭐야? 구글이 텐서플로우 이미지에다 무슨 짓을 한건가?
다시 테스트를 해보겠지만, 정말 정말 놀라운 결과였다.

64비트 Windows 10 Pro. 인텔 코어 i5-2500 3.3Ghz. 메모리 8G
1시 32분에 실행해서 2시 57분에 종료(1시간 25분 소요)
최종 에러율 0.8%(정확도 99.2%)

Step 0 (epoch 0.00), 6.5 ms
Minibatch loss: 12.053, learning rate: 0.010000
Minibatch error: 90.6%
Validation error: 84.6%
Step 100 (epoch 0.12), 584.6 ms
Minibatch loss: 3.283, learning rate: 0.010000
Minibatch error: 6.2%
Validation error: 6.8%
... 중략
Step 8400 (epoch 9.77), 585.5 ms
Minibatch loss: 1.600, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.7%
Step 8500 (epoch 9.89), 586.3 ms
Minibatch loss: 1.594, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.8%
Test error: 0.8%

64비트 Windows 10 Pro. 인텔 코어 i3-2112 3.3Ghz. 메모리 8G
11시 27분에 실행해서 12시 22분에 종료(55분 소요)
최종 에러율 0.8%(정확도 99.2%)

Step 0 (epoch 0.00), 7.5 ms
Minibatch loss: 12.054, learning rate: 0.010000
Minibatch error: 90.6%
Validation error: 84.6%
Step 100 (epoch 0.12), 369.5 ms
Minibatch loss: 3.289, learning rate: 0.010000
Minibatch error: 6.2%
Validation error: 7.0%
... 중략
Step 8400 (epoch 9.77), 371.0 ms
Minibatch loss: 1.595, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.8%
Step 8500 (epoch 9.89), 370.5 ms
Minibatch loss: 1.617, learning rate: 0.006302
Minibatch error: 1.6%
Validation error: 0.8%
Test error: 0.8%

64비트 Windows 10 Pro. 인텔 코어 i5-6500 3.2Ghz. 메모리 8G
8시 54분에 실행해서 9시 37분에 종료(43분 소요)
최종 에러율 0.8%(정확도 99.2%)

Step 8500 (epoch 9.89), 285.4 ms
Minibatch loss: 1.617, learning rate: 0.006302
Minibatch error: 1.6%
Validation error: 0.8%
Test error: 0.8%

머신러닝 제대로 돌려보려고 구매한 조립 컴퓨터.
GPU 버전을 설치해야 하지만, 막 조립을 끝내고 mnist만 돌려봄.
CPU가 많이 차이나는데, 시간은 그에 비해 차이가 나지 않는 것 같다는 생각이 듬.