본문 바로가기

CSE/Intelligent

[ML]TensorFlow - 텐서, 세션, 정규분포 만들기

Machine Learning with TensorFlow(1)

Tensor

스칼라, 벡터, 행렬, 텐서

  1. 스칼라

사물에는 특징이 있습니다. 어떤 사물을 묘사하려먼 그 사물만의 특징을 설명해야 합니다. 가령, 옷 한벌에 대해 설명하고 싶다면 치수, 재질 그리고 색상 등의 요소를 포함하겠지요. 그 값들은 스칼라로 표현됩니다.

 

  1. 벡터

사물이 갖는 특성을 일렬로 나타내면 벡터가 됩니다. 그리고 이 벡터는 특성벡터라고 부릅니다. 특성벡터를 머신에게 주입해서 현상에 대해 학습하도록 할 겁니다.

 

  • 사물이 갖는 여러가지 특성값(스칼라)을 순서있는 리스트로 묶으면 특성 벡터(Feature Vector)가 된다.
  • 달리 말해, 순서있는 리스트는 벡터라 할 수 있다.
  1. 행렬

머신에 자료를 주입할 때 사물 여러 개의 특성을 묶어 표현할 필요가 있습니다. 굳이 특성벡터를 하나 하나 어프로치하면서 데이터를 주입하긴 번거롭습니다.대신 행렬이라는 자료구조를 사용하면 되니까요.

 

  • 여러 사물의 특성 벡터를 순서있는 리스트로 묶으면 행렬이 된다.
  • 달리 말해, 행렬은 특성벡터로 이루어진 벡터이다.
  • 달리 말해, 행렬은 특성값의 벡터의 벡터이다.

행렬은 벡터의 벡터 / 벡터의 순서있는 리스트라는 개념을 잊지 않는 것이 API를 다루는데 도움이 됩니다.

 

  1. 텐서

비슷한 전개로, 여러 개의 행렬도 묶어서 자료구조를 만들 수 있습니다. 이 자료는 텐서라 부릅니다.

  • 여러 행렬을 순서있는 리스트로 묶으면 텐서가 된다.
  • 달리 말해, 텐서는 행렬로 이루어진 벡터이다.
  • 달리 말해, 텐서는 특성벡터의 벡터의 벡터이다.
  • 달리 말해, 텐서는 특성값의 벡터의 벡터의 벡터이다.

말장난을 하는 것이 아닙니다.

텐서는 행렬의 벡터 / 행렬의 순서있는 리스트입니다. 이 개념을 잊지 않는 것이 API를 다루는데 도움이 됩니다.

 

왜냐하면 Python을 다뤄본 사람은 순서있는 리스트에 매우 익숙할 것이며, 지금 텐서/행렬/벡터의 의미가 뭔지 잘 알았습니다.

그렇기 때문에 텐서를 코드로 이해하는 데 심란함이 생기지 않을 겁니다.

 

  1. 텐서의 랭크
  • 텐서는 일반화 된 자료구조이다. 원소를 특정하기 위해 최소 n개의 인덱스가 필요하면 rank-n 텐서라고 한다.
  • 특성벡터는 rank-1 텐서이다.
  • 특성벡터의 벡터(행렬)는 rank-2 텐서이다.
  • 특성벡터의 벡터의 벡터(부를 말이 없으니 텐서) rank-3 텐서이다.
  • 특성벡터의 벡터의 벡터의 벡터는 rank-4 텐서이다.

랭크를 묻는 것은 특성벡터의 차원을 묻는 것이 아닙니다. $[1,2,3,4]$ 는 4차원 벡터이지만 rank-1 텐서이지요.

 

  1. 개념 확인

질문1. 행렬은 벡터의 벡터이다. (O)

질문2. 텐서는 행렬의 벡터이다. (O)

질문3. 텐서는 벡터의 벡터이다. (O)

: 특성벡터의 벡터의 벡터이니까

 

질문4. $[[[[2, 3, 4]]]]$은 rank-4 텐서이다. (O)

: 4라는 값을 얻기위해 인덱싱 해 봅시다. "세 번째 원소를 알려주세요"라고 하면 될까요?

 

땡, 그렇지 않습니다.

$[[[[2,3,4]]]]$ 는 벡터의 벡터의 벡터의 벡터이고

세 번째 원소를 알려달라고 하면 벡터의 셋 째 원소는 존재하지 않기 때문에 잘못된 요청입니다.

 

4라는 값을 얻기 위해선, "벡터의 첫 요소의 첫 요소의 첫 요소의 세 번째 요소를 알려주세요" 라고 해야합니다.

 

코드로 나타내면

[[[[2,3,4]]]][0][0][0][2] 입니다.

 

4개의 인덱스가 필요한 rank-4 텐서임을 아시겠나요?

#4차원벡터 #랭크-1텐서
[1,2,3,4]
#2*3행렬 #랭크-2텐서
[[2,4,8],[3,6,9]]
#3*3*3텐서 #랭크-3텐서
[[[1,2,3],[4,5,6],[7,8,9]],
 [[10,11,12],[13,14,15],[16,17,18]],
 [[19,20,21,],[22,23,24],[25,26,27]]]

Tensorflow로 Tensor를 만들어 보자

import tensorflow as tf
import numpy as np

m1 = [[1., 2.], [3., 4.]]
m2 = np.array([[1., 2.], [3., 4.]], dtype=np.float32)
m3 = tf.constant([[1., 2.], [3., 4.]])

#리스트를 텐서로
t1 = tf.convert_to_tensor(m1, dtype=np.float32)
#ndarray를 텐서로
t2 = tf.convert_to_tensor(m2, dtype=np.float32)
#텐서를 텐서로 m3와 t3는 동일한 텐서상수를 가리킨다
t3 = tf.convert_to_tensor(m3, dtype=np.int32)

대부분의 텐서플로우 오퍼레이션은 입력 값을 내부에서 텐서로 변환하므로 텐서로의 명시적 변환은 선택적입니다.

tf.zeros() & tf.ones()

원소가 0이나 1로 채워진 특정 모양의 텐서를 생성할 수 있습니다. 텐서의 모양은 shape 인자에 리스트를 넘겨서 정한다.

#0으로만 이루어진 10*10행렬
tf.zeros([10,10])
#1로만 이루어진 3*3*3텐서
tf.zeros([3,3,3])

tf Operators

텐서플로우의 오퍼레이터는 어떤 차원의 텐서를 입력으로 받고, 같은 차원의 텐서로 결과를 출력하는 연산게이트라 할 수 있습니다. 기본적인 오퍼레이터들을 링크에서 확인 가능합니다. tf math ops

tf.add(x,y)
tf.substract(x,y)
tf.pow(x,y)
tf.exp(x)
tf.sqrt(x)
tf.div(x,y)
tf.truediv(x,y) #입력을 실수형으로 변환하여 나눗셈 함
tf.floordiv(x,y) #결과값을 내려서 반환
tf.mod(x,y)

이 연산들을 조합하면 더 복잡한 연산을 만들 수 있습니다. 여기서 표준정규분포확률을 구하는 연산을 정의해 보도록 하죠.

 

  • $X$가 평균이 $\mu$이고 표준편차가 $\sigma$인 정규분포를 따를 때, $P(X=x)$ 는 다음과 같다고 한다.

$$P(X=x)={1\over{\sigma \sqrt{2\pi}}}\exp(-{(x-\mu)^2\over2\sigma^2})$$

이 수식을 텐서가 흘러갈 수 있는 오퍼레이터로 바꾸러면

더하기, 빼기, 나누기, 스퀘어, 익스포넨셜 연산을 tf에 내장된 텐서 op를 이용하여 나타내야 합니다.

 

다만, 편리하게도 사칙연산에 대해서는 암묵적으로 텐서 연산이 적용됩니다. 숫자 상수들도 마찬가지로 텐서 상수로 변환되므로 아래 코드처럼 연산을 정의하는 것으로 충분합니다.

normal_distribution = ( 1.0 / (tf.sqrt(2.0 * pi) * sigma)
                    * tf.exp(tf.negative(tf.pow(x - mean, 2.0) / 2.0 * tf.pow(sigma, 2.0))) )

mean과 mu까지 플레이스홀더로 하면 일반적인 정규분포확률이 됩니다. 하지만 이 코드는 상수mean과 mu로 표준정규분포확률 연산을 정의한 것입니다.

 

어떤 연산 게이트를 사용하려면 회로에 올리고 입력 값을 쑤셔넣어야합니다.

tf 연산자도 마찬가지 입니다.

 

위 코드는 도면에 연산을 올리고 normal_distribution 변수가 해당 노드를 참조하도록 한 것입니다.

연산을 수행하려면 세션을 수립하고 플레이스홀더 x에 텐서를 주입하고 이 노드를 통과하도록 해야합니다.

이 과정이 포함된 코드는 아래를 보십시오.

import tensorflow as tf
import numpy as np
from math import pi

x_sample = np.linspace(-1,1,5) # -1~1사이의 5개 숫자
x = tf.placeholder(dtype=tf.float32)

mean = 0.
sigma = 1.
normal_distribution = ( 1.0 / (tf.sqrt(2.0 * pi) * sigma)
                    * tf.exp(tf.negative(tf.pow(x - mean, 2.0) / 2.0 * tf.pow(sigma, 2.0))) )
print(normal_distribution)

print('let\'s do this!')
with tf.Session() as sess :
    for i in range(len(x_sample)) :
        p = sess.run(normal_distribution, feed_dict={x:x_sample[i]})
        print('P(X={})='.format(x_sample[i]),p)
    sess.close()

tf Session

세션은 연산이 일어나는 환경이라 할까요. 텐서플로우의 세션은 디지털 회로의 회로도와 비슷합니다. 회로도 종이가 각종 연산게이트와 메모리(레지스터), 인풋, 아웃풋, 상수모듈, 그리고 비트 신호가 흐르는 회선을 담아 내는 것처럼, 텐서플로우 세션을 오퍼레이터 노드와 엣지로 이루어진 그래프 도면으로 연상하면 도움이 될 겁니다. 텐서가 그래프를 따라 흐르기 때문에 TensorFlow라 명명 되었겠지요.

 

  • 세션 : 회로도 종이
  • placeholder : 인풋
  • Variable : 메모리 레지스터
  • constant : 상수 모듈
  • op : 연산 모듈

세션이 담고 있는 환경은 그래프로 표현할 수 있습니다. op를 노드로, 이들간의 상호작용이 엣지로 나타냅니다. placeholder, Variable, constant는 각각 인풋, 메모리 레지스터, 상수 모듈로 이해하면 쉽습니다.

 

placeholder는 장소인데, 세션에 의해 이곳에 값이 주입됩니다.

Variable은 값을 저장할 수 있는 메모리이며 초기화가 필수이고, tf.assign() op를 사용해 값을 저장할 수 있습니다.

constant는 바뀌지 않는 상수입니다.

Tensor Evaluation

텐서객체를 print한다고 해서 숫자값이 나오거나 연산이 수행된 값이 나오지는 않습니다.

텐서 객체는 세션에 배치되어 있는 텐서를 가리킬 뿐이죠.

연산을 수행하고 결과 값을 취해오려면 세션에게 요청해야 합니다!

 

그러기 위해서 tf.Session()을 호출하여 세션을 수립하고 세션에게 어떤 연산을 run 합니다,

혹은, tf.InteractiveSession() 을 호출하여 인터랙티브 세션을 수립하고 텐서의 .eval() 메서드를 호출하여도 됩니다.

 

인터렉티브 세션을 수립하게 되면, 텐서의 .eval() 메서드를 사용함에 따라 세션에 대한 참조를 코드 이곳저곳 주고 받을 필요가 없어지고, 사용자 입장에서 연산값을 인터랙티브하게 취할 수 있게 됩니다.