강좌/MachineLearning

TensorFlow #003 TensorFlow 의 상수, 변수, 치환자 뜯어보기

여름나라겨울이야기 2016. 12. 2. 15:59
728x90

지난 글에서 텐서플로우의 constant, Variable, placeholder 에 대해 맛 보았다.(왜? Variable 만 대문자로 시작하는가? 타이핑할 때마다 조심스럽다. 추론, 다른 2개는 Method 이고, Variable 은 Class 이니까?)

이번에는 이들을 다방면에서 뜯어 보자. 일단 코드 보자.

# coding=UTF8
import tensorflow as tf

a = 1
b = tf.constant(2, name="b")
c = tf.Variable(3, name="c")
d = tf.placeholder(tf.int32, name="d")

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

print "a + b: ", a + b
print sess.run(a + b)

print "a + c: ", a + c
print sess.run(a + c)

print "a + d: ", a + d
print sess.run(a + d, feed_dict={d: 4})

print "b + c: ", b + c
print sess.run(b + c)

print "b + d: ", b + d
print sess.run(b + d, feed_dict={d: 4}) 

print "b + d: ", b + d
print sess.run(c + d, feed_dict={d: 4})

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

일반 변수 a, 상수 텐서  b, 변수 텐서 c, 치환자 텐서 d 를 서로 조합해서 더하기 연산을 정의하고, 각 더하기 연산이 어떤 텐서로 변환되는지 확인해 보는 코드이다. 실행 결과는 아래와 같다.

텐서를 포함한 연산 자체가 텐서가 됨을 확인할 수 있다. 다만, 치환자를 사용한 경우에는 텐서의 shape 을 추론할 수 없다는 것이 조금 신경이 쓰인다.

a 는 텐서가 아님으로 안 보인다 치고, 변수 텐서인 c 와 치환자 텐서인 d 는 보이는데, 상수 텐서인 b 는 왜 안 보이는가로 좌절하지 말고 add(0-11) 노드를 클릭해 보자.

숨어있던 상수 텐서 b 가 보인다. 그런데 x 이름의 노드는 뭐다냥... 그리고 더하기 연산들이 add ~ add_11 까지의 이름으로 보니 뭐가 뭔지 구분이 안 된다. 연산식도 텐서라 했으니 이름을 지정해 줄 수 있지 않을까? 바로 앞의 강좌에서 보았듯이 연산식 텐서에도 이름을 줄 수 있다. + 연산자가 아닌 텐서플로우가 제공해 주는 메서드들을 사용하면 된다.

참고: https://www.tensorflow.org/versions/r0.11/api_docs/python/math_ops.html#arithmetic-operators

# coding=UTF8
import tensorflow as tf

a = 1
b = tf.constant(2, name="b")
c = tf.Variable(3, name="c")
d = tf.placeholder(tf.int32, name="d")

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

add_ab = tf.add(a, b, name="add_ab")
print "a + b: ", add_ab
print sess.run(add_ab)

add_ac = tf.add(a, c, name="add_ac")
print "a + c: ", add_ac
print sess.run(add_ac)

add_ad = tf.add(a, d, name="add_ad")
print "a + d: ", add_ad
print sess.run(add_ad, feed_dict={d: 4})

add_bc = tf.add(b, c, name="add_bc")
print "b + c: ", add_bc
print sess.run(add_bc)

add_bd = tf.add(b, d, name="add_bd")
print "b + d: ", add_bd
print sess.run(add_bd, feed_dict={d: 4}) 

add_cd = tf.add(c, d, name="add_cd")
print "c + d: ", add_cd
print sess.run(add_cd, feed_dict={d: 4})

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

실행 결과는 이전과 동일하다. 다만 연산식을 가진 텐서에 이름이 예쁘게 지정되었다는 것 뿐...

a + b: Tensor("add_ab:0", shape=(), dtype=int32) 3 a + c: Tensor("add_ac:0", shape=(), dtype=int32) 4 a + d: Tensor("add_ad:0", dtype=int32) 5 b + c: Tensor("add_bc:0", shape=(), dtype=int32) 5 b + d: Tensor("add_bd:0", dtype=int32) 6 c + d: Tensor("add_cd:0", dtype=int32) 7

그래프도 확인해 보자.

우와. 그래프는 많이 달라졌다. 그리고 텐서가 아닌 a 가 그래프에서 x 라는 이름으로 인식되어졌다는 것을 금방 알아챌 수 있을 것이다. 역시 작명은 중요하다. 각 노드를 클릭해서 이리 저리 더 살펴보는 것은 읽는 자의 몫으로.... 재미난 것은 b 나 c 노드를 클릭하면 모든 b 나 c 가 동시에 선택되어져서 빨간 불이 들어오는데 반해, x 는 서로 연동되서 선택되지 않는다는 것 정도...(선언할 때 텐서인가 아닌가가 이렇게 중요합니다.) 

상수 텐서, 변수 텐서, 치환자 텐서를 각각 언제 쓸 것인가는 아직 지식이 부족함으로 건너 뛰도록 하자. 다만, 텐서 선언시에 shape 이 무엇인지를 파헤쳐 보자.

텐서의 shape (구조)은 무엇인가?

상수, 변수, 치환자 텐서의 선언부를 다시 살펴보자.

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

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

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

다른 인자들은 대충 감이 오는데 텐서의 구조라고 하는 shape 은 애매모호하다. 아래 코드로 다양한 실험을 해보자.

# coding=UTF8
import tensorflow as tf

a = tf.constant(1, name="a")
b = tf.constant(1, name="b", shape=None)
c = tf.constant(1, name="c", shape=())
d = tf.constant(1, name="d", shape=[])
e = tf.constant(1, name="e", shape=[1])
f = tf.constant(1, name="f", shape=[2])
g = tf.constant(1, name="g", shape=[10])
h = tf.constant(1, name="h", shape=[1, 1])
i = tf.constant(1, name="i", shape=[1, 2])
j = tf.constant(1, name="j", shape=[2, 1])
k = tf.constant(1, name="k", shape=[2, 2])
l = tf.constant(1, name="l", shape=[3, 1])
m = tf.constant(1, name="m", shape=[3, 2])
n = tf.constant(3, name="n", shape=[7, 7])
o = tf.constant([[4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5]], name="o")
p = tf.constant(1, name="p", shape=[2, 2, 2])
q = tf.constant(1, name="q", shape=[2, 2, 2, 2])

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

print a, sess.run(a)
print b, sess.run(b)
print c, sess.run(c)
print d, sess.run(d)
print e, sess.run(e)
print f, sess.run(f)
print g, sess.run(g)
print h, sess.run(h)
print i, sess.run(i)
print j, sess.run(j)
print k, sess.run(k)
print l, sess.run(l)
print m, sess.run(m)
print n, sess.run(n)
print o, sess.run(o)
print p, sess.run(p)
print q, sess.run(q)

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

결과는 아래와 같다.

Tensor("a:0", shape=(), dtype=int32) 1
Tensor("b:0", shape=(), dtype=int32) 1
Tensor("c:0", shape=(), dtype=int32) 1
Tensor("d:0", shape=(), dtype=int32) 1
Tensor("e:0", shape=(1,), dtype=int32) [1]
Tensor("f:0", shape=(2,), dtype=int32) [1 1]
Tensor("g:0", shape=(10,), dtype=int32) [1 1 1 1 1 1 1 1 1 1]
Tensor("h:0", shape=(1, 1), dtype=int32) [[1]]
Tensor("i:0", shape=(1, 2), dtype=int32) [[1 1]]
Tensor("j:0", shape=(2, 1), dtype=int32) [[1]
 [1]]
Tensor("k:0", shape=(2, 2), dtype=int32) [[1 1]
 [1 1]]
Tensor("l:0", shape=(3, 1), dtype=int32) [[1]
 [1]
 [1]]
Tensor("m:0", shape=(3, 2), dtype=int32) [[1 1]
 [1 1]
 [1 1]]
Tensor("n:0", shape=(7, 7), dtype=int32) [[3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]
 [3 3 3 3 3 3 3]]
Tensor("o:0", shape=(4, 4), dtype=int32) [[4 3 2 1]
 [1 2 3 4]
 [5 6 7 8]
 [8 7 6 5]]
Tensor("p:0", shape=(2, 2, 2), dtype=int32) [[[1 1]
  [1 1]]

 [[1 1]
  [1 1]]]
Tensor("q:0", shape=(2, 2, 2, 2), dtype=int32) [[[[1 1]
   [1 1]]

  [[1 1]
   [1 1]]]


 [[[1 1]
   [1 1]]

  [[1 1] 

[1 1]]]] 

결과를 보고 텐서의 shape 에 대해 추론을 통해 짐작하길 바란다.

0 차원 텐서 - 0 차원 배열


a = tf.constant(1, name="a")
b = tf.constant(1, name="b", shape=None)
c = tf.constant(1, name="c", shape=())
d = tf.constant(1, name="d", shape=[]) 

결과

Tensor("a:0", shape=(), dtype=int32) 1
Tensor("b:0", shape=(), dtype=int32) 1
Tensor("c:0", shape=(), dtype=int32) 1 
Tensor("d:0", shape=(), dtype=int32) 1 

a, b, c, d 는 선언 방식이 다르지만 결국 1개의 값을 갖는 구조임을 알 수 있다.


1 차원 텐서 - 1 차원 배열


e = tf.constant(1, name="e", shape=[1])
f = tf.constant(1, name="f", shape=[2])
g = tf.constant(1, name="g", shape=[10]) 


결과

Tensor("e:0", shape=(1,), dtype=int32) [1]
Tensor("f:0", shape=(2,), dtype=int32) [1 1] 
Tensor("g:0", shape=(10,), dtype=int32) [1 1 1 1 1 1 1 1 1 1] 

shape=[n] 형식의 텐서의 경우 1차원 배열임을 알 수 있다. 짐작컨데 초기값이 하나가 아닌 1차원 배열 텐서는 다음과 같이 만들 수 있을 것이다.

r = tf.constant([1, 2, 3, 4, 5], name='r')
s = tf.constant([1, 2, 3, 4, 5], name='s', shape=None) 
t = tf.constant([1, 2, 3, 4, 5], name='t', shape=[5])

이 경우 r 과 s, t 는 다음과 같이 동일한 구조의 텐서를 만들어 낸다.

Tensor("r:0", shape=(5,), dtype=int32) [1 2 3 4 5] 
Tensor("s:0", shape=(5,), dtype=int32) [1 2 3 4 5] 
Tensor("t:0", shape=(5,), dtype=int32) [1 2 3 4 5]

그리고 아래의 경우는 조금 흥미롭다.

w = tf.constant([1, 2, 3, 4, 5], name='w', shape=[10]) 

결과

Tensor("w:0", shape=(10,), dtype=int32) [1 2 3 4 5 5 5 5 5 5] 


2 차원 텐서 - 2 차원 배열 (행 x 열)


h = tf.constant(1, name="h", shape=[1, 1])
i = tf.constant(1, name="i", shape=[1, 2])
j = tf.constant(1, name="j", shape=[2, 1])
k = tf.constant(1, name="k", shape=[2, 2])
l = tf.constant(1, name="l", shape=[3, 1])
m = tf.constant(1, name="m", shape=[3, 2])
n = tf.constant(3, name="n", shape=[7, 7])
o = tf.constant([[4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5]], name="o") 

결과

Tensor("h:0", shape=(1, 1), dtype=int32) [[1]] Tensor("i:0", shape=(1, 2), dtype=int32) [[1 1]] Tensor("j:0", shape=(2, 1), dtype=int32) [[1] [1]] Tensor("k:0", shape=(2, 2), dtype=int32) [[1 1] [1 1]] Tensor("l:0", shape=(3, 1), dtype=int32) [[1] [1] [1]] Tensor("m:0", shape=(3, 2), dtype=int32) [[1 1] [1 1] [1 1]] Tensor("n:0", shape=(7, 7), dtype=int32)

[[3 3 3 3 3 3 3] [3 3 3 3 3 3 3] [3 3 3 3 3 3 3] [3 3 3 3 3 3 3] [3 3 3 3 3 3 3] [3 3 3 3 3 3 3] [3 3 3 3 3 3 3]] Tensor("o:0", shape=(4, 4), dtype=int32)

[[4 3 2 1] [1 2 3 4] [5 6 7 8]

 [8 7 6 5 ]] 

shape=[m, n] 인 경우 m x n 인 2차원 배열이 만들어지는 것을 확인할 수 있다. 아래 같은 경우는 만들면 안 되겠지만 시도는 해보자.

y = tf.constant([[4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5]], name="y", shape=[4, 10]) 

결과는 흥미롭기 보다는 이렇게 써서는 안 되는 이유를 알려 준다.

Tensor("y:0", shape=(4, 10), dtype=int32) 
[[4 3 2 1 1 2 3 4 5 6]
 [7 8 8 7 6 5 5 5 5 5]
 [5 5 5 5 5 5 5 5 5 5

[5 5 5 5 5 5 5 5 5 5]] 


3 차원 텐서 - 3 차원 배열 (면 x 행 x 열)


p = tf.constant(1, name="p", shape=[2, 2, 2]) 

결과

Tensor("p:0", shape=(2, 2, 2), dtype=int32)

[ [ [1 1] [1 1] ]

[ [1 1] [1 1] ] ]

shape=[m, n, o] 인 경우 m x n x o 인 3차원 배열이 만들어지는 것을 확인할 수 있다.


4 차원 텐서 - 4 차원 배열 (큐브 x 면 x 행 x 열)


 q = tf.constant(1, name="q", shape=[2, 2, 2, 2])

결과

Tensor("q:0", shape=(2, 2, 2, 2), dtype=int32) [ [ [ [1 1]

[1 1] ]


[ [1 1]

[1 1] ]

]


[
[ [1 1]

[1 1] ]


[ [1 1]

[1 1] ]

]

]

shape=[m, n, o, p] 인 경우 m x n x o x p 인 4차원 배열이 만들어지는 것을 확인할 수 있다.


결론


텐서라는 것은 n 차원의 배열 데이터이다. 또는 n 차원의 벡터들의 묶음이다. (확신이.. ㅡㅡ;) shape 인자를 이용하면 원하는 차원 구조를 가진 텐서를 만들 수 있다. 몇 차원까지 가능한지 흥미롭긴 하지만.... 

마지막으로 다음 문제를 풀어보자.

shape=[m, n]

은 몇 차원에 어떠한 구조를 가지는 텐서인가?


Tip

텐서의 구조(shape), 차원(rank), 크기(size, 요소 갯수)를 확인해 주는 함수가 존재한다.

다음 코드를 실행해 보자.

여유가 된다면 위에서 작업했던 모든 텐서의 구조, 차원, 크기를 확인해 보는 것은 읽는 자의 몫으로...

print a, sess.run(tf.shape(a)), sess.run(tf.rank(a)), sess.run(tf.size(a))


반응형