강좌/MachineLearning

TensorFlow #002 TensorFlow 의 상수 텐서, 변수 텐서, 치환자 텐서, 연산자 텐서 맛보기

여름나라겨울이야기 2016. 12. 1. 18:18
728x90

TensorFlow 는 연산을 바로 실행하는 것이 아니라 Graph 로 먼저 변환한 후에 Session 을 실행해서, Graph 의 실행 결과를 받게 된다고 한다. 고로 기존의 사칙 연산 형태로 계산하면 원하는 값을 얻을 수 없다.

간단한 사칙 연산 코드를 만들어 보자.

기존의 사칙 연산 - 순수 Python


위 코드는 순수 Python 코드이다. 이를 TensorFlow 를 이용해 계산하기 위해서는 다른 방식이 필요하다.


TensorFlow Session 에서 사칙 연산하기

Tensorflow 를 이용해서 연산을 하려면 Session 이 필요하다고 했다.

아래와 같은 코드가 필요하다.

# 텐서플로우 라이브러리 임포트
import tensorflow as tf

# 텐서플로우 세션 준비
sess = tf.Session()

# 텐서플로우 세션에서 연산 실행
sess.run(연산 코드)

그럼 아래 코드를 실행해 보자.

import tensorflow as tf

sess = tf.Session()

sess.run(1 + 2) 

허거걱...

텐서플로우에게 1 + 2를 물었을 때 텐서플로우가 답했다. 

"시방! 님이 던진 것은 텐서가 아니여. 지금 장난 나랑 하냐!"

그렇다 텐서플로우는 텐서가 아닌 것은 취급하지 않는다.

그래서 우리는 텐서를 만들어야 한다. 텐서는 뭐냐고? 스킵... 도망...

import tensorflow as tf

sess = tf.Session()

a = tf.constant(1 + 2)
sess.run(a) 

텐서플로우에서 상수 텐서를 정의하는 constant 를 이용해 1 + 2 의 결과를 텐서 a 에 담았고, 이를 Session 의 run 메서드를 이용해 실행해 보았다. 정상적으로 실행된다. 그럼 여기서 a 자체는 무엇인가? 확인해 보자.

a 는 텐서이다. 그것도 <tf.Tensor 'Const:0' shape=() dtype=int32> 이러한 텐서이다. 텐서플로우 공식 문서에서 constant 에 대해 찾아보자(버전마다 조금씩 다르다. 필자가 도커 이미지로 설치한 버전은 r.0.11 로 verify_shape 따위가 없다).

https://www.tensorflow.org/versions/r0.12/api_docs/python/constant_op.html#constant

tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)

value 는 값이겠고,

dtype 은 숫자냐 문자냐 뭐 그런 것 같고,

shape 은 구조(?)이고,

name 은 이름이고,

verify_shape 은.. 음 아.. 어.. 그렇다. (value 가 shape 에 부합하는지 검증할거냐 말거냐.... 음 아.. 어.. 그렇다.)

강조! 버전마다 조금씩 다르다. 필자가 도커 이미지로 설치한 버전 r.0.11 의 tf.constant 스펙에는 verify_shape 따위가 없다.

위에서 값이 None 인 경우는 value 에서 알아내라는 의미이다.


TensorFlow 의 상수(Constant)를 이용한 사칙 연산

위와 같이 연산식 자체를 텐서 상수로 쓸 수도 있지만,

각 숫자를 텐서로 만들어서 사칙 연산을 수행하는 것이 더 깔끔한(?) 코드를 만들 것 같으니 코드를 아래와 같이 수정하고 실행해 보자.

import tensorflow as tf
a = tf.constant(1)
b = tf.constant(2)

sess = tf.Session()

sess.run(a + b)
sess.run(a - b)
sess.run(a * b)
sess.run(a / b)


실행 결과는 아래 그림과 같다.

위 그림에서는 두 개의 텐서(a, b)를 + 연산으로 묶은 연산 자체가 텐서임을 확인하는 코드도 추가해 보았다. 그 외의 다양한 테스트는 읽는 자의 몫으로 남기도록 하겠...


그리고 텐서 플로우는 텐서 연산을 위해 그래프를 만든다고 했는데, 연산에 대한 그래프를 확인할 수 있는 툴을 TensorBoard 라는 이름으로 제공한다. 
이를 확인하기 위해 아래 코드처럼 빨간 부분을 추가해서 다시 구동해 보자.

# coding=UTF8
import tensorflow as tf

a = tf.constant(1)
b = tf.constant(2)

sess = tf.Session()

print sess.run(a + b)
print sess.run(a - b)
print sess.run(a * b)
print sess.run(a / b)


# Start: TensorBoard 그래프용 로그 기록
tf.merge_all_summaries()
tf.train.SummaryWriter("/tmp/tensorflowlogs", sess.graph)
# Finish: TensorBoard 그래프용 로그 기록


# coding=UTF8

한글도 (주석으로만 썼지만) 사용할 수 있게 했다.


tf.merge_all_summaries()

tf.train.SummaryWriter("/tmp/tensorflowlogs", sess.graph)

텐서플로우의 TensorBoard 로 Graph 를 확인할 수 있도록 로그를 생성하고, /tmp/tensorflowlogs 폴더 밑에 저장하도록 했다. 위 두 문장의 위치는 소스 맨 끝으로 하는 것을 권장한다. 위 두 문장의 위치에 따라 그래프를 어디서부터 어디까지 그릴 지가 결정된다. 소스 끝에 있어야 모든 그래프를 확인할 수 있다. 실행 결과는 이전과 똑같다. 

Graph 용 로그를 기록했으니 Graph 를 확인해 보자. 이전 글에서 설명했던 Kitematic 창으로 가서 [Exec] 를 클릭해 bash 터미널을 구동하자(또는 jupyter 창이 떠 있는 웹브라우저의 우측 상단 New 밑에 Terminal 을 선택하면 웹브라우저 상에서 터미널(콘솔)창을 볼 수 있다).

tensorboard --logdir=/tmp/tensorflowlogs

를 입력하고 엔터키를 누른다. (--logdir 뒤의 경로는 소스에 기록한 경로이다.)

터미널 메시지를 보면 웹브라우저를 통해 ip:6006 포트로 접근하라고 되어 있지만 이는 도커에서 구동되는 게스트 OS 기준이고 실제 우리는 호스트 OS 를 통해서 접근해야 하기 때문에 Kitematic 의 Settings 안 Ports 탭에서 게스트 OS 6006 포트로 포워딩 되어 있는 호스트 OS 의 포트로 접근해야 한다. 고로 http://localhost:호스트port 되겠다.

웹브라우저를 통해 접근해서 GRAPHS 탭을 보면 선언한 2개의 텐서(우리는 a, b)에 대한 그래프를 확인한 수 있다. 우리가 텐서를 선언할 때 별도로 이름을 주지 않았기에 텐서플로우가 자동으로 지어준 이름인 Const, Const_1 으로 표시된다. 그래프의 노드를 클릭해 보면 다양한 정보를 확인할 수 있다.

노드명이 영 마음에 들지 않으니 텐서들에 이름을 부여해 보자.

# coding=UTF8
import tensorflow as tf

a = tf.constant(1, name="conA")
b = tf.constant(2, name="conB")

sess = tf.Session()

print sess.run(a + b)
print sess.run(a - b)
print sess.run(a * b)
print sess.run(a / b)

# Start: TensorBoard 그래프용 로그 기록
tf.merge_all_summaries()
tf.train.SummaryWriter("/tmp/tensorflowlogs", sess.graph)
# Finish: TensorBoard 그래프용 로그 기록


소스를 실행하고 나서 텐서보드 화면을 리로딩하고 GRAPHS 탭으로 이동해 보자.

위 그림은 conA 노드 중 하나를 클릭했을 때, 같은 노드들이 선택되고 상세 정보가 우측 상단에 나타난 것을 볼 수 있다. 또한, 노드명(conA, conB)이 예쁘게 보이는 것을 확인할 수 있다.

Tip. Easy

텐서보드 화면을 리로딩했을 때 위에 처럼 안 보이고 이전 화면처럼 자동 부여된 노드명이 보일 수도 있다.

이는 텐서보드가 로그를 120초 단위로 확인해서 화면에 반영하기 때문이다.

차분히 120초를 기다린 후, 화면 리로딩을....


Tip. Middle

120초가 너무 길다면 reolad_interval 옵션을 사용해 보자.

tensorboard --logdir=xxxx --reload_interval=sec


Tip. High

실시간이 필요하다면 TensorBoard 가 실행 중인 터미널에서 Ctrl + C 시전 후, TensorBoard 재실행


TensorFlow 의 변수(Variable)를 이용한 사칙 연산

스펙부터 보자.

https://www.tensorflow.org/versions/r0.11/api_docs/python/state_ops.html#Variable

그런데 Variable 은 메서드가 아니라 클래스다. 우와 볼 것 많겠다. 하지만 보고 싶은 것만 보리라.

tf.Variable(<initial-value>, name=<optional-name>)


import tensorflow as tf

a = tf.Variable(1)
b = tf.Variable(2)

sess = tf.Session()

sess.run(tf.initialize_all_variables())

print sess.run(a + b)
print sess.run(a - b)
print sess.run(a * b)
print sess.run(a / b) 

실행 결과는?

귀차니즘이 시작되었다. 각 명령을 셀별로 나누어 하던 것을 그냥 한 셀에 모았다. 그리고 각 셀이 하나의 반환값을 하단에 출력했었는데, 이제는 한 셀에 몰아서 하나의 결과가 아닌 다수의 결과를 출력하기 위해 print 함수를 사용했다. 결정적으로 달라진 부분은

변수 선언 부분과

a = tf.Variable(1)

b = tf.Variable(2)

아래 코드이다.

sess.run(tf.initialize_all_variables())

변수를 사용하는 코드는 변수를 실제 사용하기 전에 반드시 초기화 해줘야 하는 것이 텐서플로우의 규칙이다. 이 때 각 변수별로 초기화하기 보다는 앞에 선언된 모든 변수를 초기화 하기 위해  initialize_all_variables() 를 사용했다. TensorBoard 에서 확인해 볼 수 있도록 소스를 추가해 보자.

# coding=UTF8
import tensorflow as tf

a = tf.Variable(1, name="varA")
b = tf.Variable(2, name="varB")

sess = tf.Session()

sess.run(tf.initialize_all_variables())

print sess.run(a + b)
print sess.run(a - b)
print sess.run(a * b)
print sess.run(a / b) 

# Start: TensorBoard 그래프용 로그 기록
tf.merge_all_summaries()
tf.train.SummaryWriter("/tmp/tensorflowlogs", sess.graph)
# Finish: TensorBoard 그래프용 로그 기록

TensorFlow 의 치환자(Place Holder)를 이용한 사칙 연산

https://www.tensorflow.org/versions/r0.12/api_docs/python/io_ops.html#placeholder

tf.placeholder(dtype, shape=None, name=None)


# coding=UTF8
import tensorflow as tf

a = tf.placeholder(tf.int32, shape=[], name='phA')
b = tf.placeholder(tf.int32, shape=[], name='phB')

sess = tf.Session()

print sess.run(a + b, feed_dict={a: 1, b: 2})
print sess.run(a - b, feed_dict={a: 1, b: 2})
print sess.run(a * b, feed_dict={a: 1, b: 2})
print sess.run(a / b, feed_dict={a: 1, b: 2}

#sess.run(tf.initialize_all_variables())

# Start: TensorBoard 그래프용 로그 기록
tf.merge_all_summaries()
tf.train.SummaryWriter("/tmp/tensorflowlogs", sess.graph)
# Finish: TensorBoard 그래프용 로그 기록

치환자를 선언하는 부분

a = tf.placeholder(tf.int32, shape=[], name='phA')

b = tf.placeholder(tf.int32, shape=[], name='phB')

과 Session 에서 연산식을 실행할 때 치환자에 실제 값을 제공하는 feed_dict 부분을 유심히 살펴보기 바란다.

sess.run(a + b, feed_dict={a: 1, b: 2})

실행 결과는 아래 그림과 같다.



텐서보드에서 확인한 Graph 는 아래와 같다.


연산자 텐서

텐서와 그 연산은 Session.run 을 통해 평가된다고 했다. 아래와 같은 코드가 있다면 연산 자체는 과연 무엇이 되는 것일까?

import tensorflow as tf

a = tf.constant(1)
b = tf.Variable(1)
c = tf.placeholder(tf.int32, shape=[])

print a + 1 # 주석 이건 무엇일까?
print b + 1 # 주석 이건 무엇일까?
print c + 1  # 주석 이건 무엇일까?

결과는 다음과 같다.

Tensor("add:0", shape=(), dtype=int32)
Tensor("add_1:0", shape=(), dtype=int32) 
Tensor("add_2:0", shape=(), dtype=int32) 

텐서를 이용한 연산식은 텐서가 됨을 알 수 있다. 여기서는 이를 연산자 텐서라고 하자. 이 연산자 텐서를 실제 실행하는 것은 Session 의 run 메소드이다. 각 연산자 텐서를 실행하는 것은 독자의 몫으로 남기도록 하겠다. 충실히 따라 왔다면

* Session 을 얻는 법

* 변수 텐서를 초기화 하는 법

* 치환자 텐서에 값을 치환 하는 법

* Session 에서 텐서를 실행하는 법

등을 적용해야 한다는 것을 기억할 것이다. 아래 접혀진 부분을 보기 전에 꼭 직접해 보길 바란다.


연산자 텐서에 이름 주기

마지막 소스에 텐서보드용 로그 파일을 생성한 후, 텐서보드를 보면 연산자 텐서의 이름이 참 안 이쁘다. 연산자 텐서에도 다른 텐서처럼 이름을 줄 수 있지 않을까? 텐서플로우는 기본 사칙 연산 뿐만 아니라 다양한 연산 메소드를 제공하고 이를 사용하면 연산자 텐서에 원하는 이름을 정해 줄 수 있다.

import tensorflow as tf

a = tf.constant(1)
b = tf.Variable(1)
c = tf.placeholder(tf.int32, shape=[])

myAdd_1 = tf.add(a, 1)
myAdd_2 = tf.add(b, 1, name="addVariableAndOne")
myAdd_3 = tf.add(c, 1, name="addPlaceholderAndOne")

print myAdd_1
print myAdd_2
print myAdd_3

# Session 얻기
sess = tf.Session()

# 변수 텐서 초기화
init = tf.initialize_all_variables()
sess.run(init)

# 연산식 실행 및 출력
print sess.run(myAdd_1)
print sess.run(myAdd_2)
print sess.run(myAdd_3, feed_dict={c: 1})

tf.merge_all_summaries()
tf.train.SummaryWriter("/tmp/log", sess.graph) 

더하기 연산을 + 연산자를 쓰지 않고 텐서플로우의 add 메소드를 사용한 것을 볼 수 있다. 이렇게 텐서플로우가 제공하는 연산 메소드를 쓰면 연산자 텐서에 이름을 줄 수 있다(옵션이다).


텐서플로우가 연산자 텐서를 바로 실행하지 않고 Session 을 이용하는 이유를 짐작해 보자.

빠른 속도를 위해 C++ 사용

텐서플로우가 현재 C++ 과 Python 용 라이브러리르 제공하고 추후에는 더 많은 언어를 지원한다고 한다. 그럼에도 불구하고 Session 은 더 빠른 결과를 얻기 위해 C++ 을 사용한다고 한다. 다음 순서를 기억하자.

1. 텐서플로우 라이브러리를 이용해 GRAPH 정의(우린 Python 을 사용했다.)

2. GRAPH 를 Session 을 통해 실행(내부적으로 C++ 을 사용한다.)


연산식을 재사용

머신 러닝은 학습으로 연산식의 계수를 추론하고, 이후 연산식을 적용하는 두 단계로 이루어진다는 것을 기억해 두자.

추론된 연산식을 가지고 있다가 추론이 필요한 경우가 이를 사용하는 법에 대해서는 추후에 다루도록 하겠다.

반응형