텐서플로 텐서

이름에서 알 수 있듯이, 텐서플로는 텐서를 포함한 계산을 정의하고 실행하는 프레임워크입니다. 텐서(tensor)는 벡터와 행렬을 일반화한 것이고 고차원으로 확장 가능합니다. 내부적으로 텐서플로는 기본적으로 제공되는 자료형을 사용해 n-차원 배열로 나타냅니다.

텐서플로 프로그램을 작성할 때, tf.Tensor를 주로 조작하고 전달합니다. tf.Tensor 객체는 부분적으로 정의된 연산으로 표현되고 이것은 결국 값으로 변환됩니다. 텐서플로 프로그램은 tf.Tensor 객체 그래프를 만드는 것으로 먼저 시작하고, 각각의 텐서가 다른 텐서를 기반으로 어떤 식으로 계산될 수 있는지 구체화하고, 그 다음 그래프를 실행해서 원하는 결과를 얻게 됩니다.

tf.Tensor는 다음과 같은 속성을 가지고 있습니다:

  • 자료형 (예를 들어, float32 또는 int32, string)
  • 형태(shape)

텐서안의 각각 원소는 동일한 자료형이고 항상 그 자료형을 알 수 있습니다. 형태(즉, 차원 수와 각 차원마다 길이)는 일부만 알 수 있습니다. 대부분 연산은 입력값 형태를 알 수 있다면 형태가 완전하게 정의된 텐서를 만들지만, 일부 경우에서는 그래프를 실행한 이후에 텐서 형태를 알 수 있기도 합니다.

일부 특별한 텐서는 텐서플로 가이드문서의 다른 부분에서 다뤄질 것입니다. 핵심인 텐서는 다음과 같습니다:

예외인 tf.Variable을 제외한다면, 텐서 값은 변경할 수 없습니다. 즉, 텐서를 한번 실행시킨 경우에는 오직 하나의 값만을 가집니다. 그러나, 동일한 텐서를 다시 실행시킨다면 다른 값을 가질 수 있습니다; 예를 들어 텐서가 디스크로부터 데이터를 읽어들인 결과이거나, 무작위 숫자를 생성하는 경우입니다.

랭크(Rank)

tf.Tensor 객체의 랭크는 그 차원의 수입니다. 랭크의 동의어는 order 또는 degree, n-dimension입니다. 텐서플로의 랭크는 수학에서 사용하는 행렬의 랭크와는 다릅니다. 다음 표에서 알 수 있는 것처럼, 텐서플로의 각 랭크는 각각 다른 수학 개체(entity)에 해당됩니다.

랭크 수학 개체
0 스칼라(Scalar) (크기(magnitude)만)
1 벡터(Vector) (크기와 방향(direction))
2 행렬(Matrix) (숫자 표)
3 3-텐서 (숫자 큐브(cube))
n n-텐서 (알 수 있을겁니다(you get the idea))

랭크 0

다음은 랭크 0 변수 생성 예입니다:

mammal = tf.Variable("코끼리", tf.string)
ignition = tf.Variable(451, tf.int16)
floating = tf.Variable(3.14159265359, tf.float64)
its_complicated = tf.Variable(12.3 - 4.85j, tf.complex64)

랭크 1

랭크 1 tf.Tensor 객체를 생성하기 위해서 초기값으로 리스트를 사용할 수 있습니다. 예를 들어:

mystr = tf.Variable(["안녕하세요"], tf.string)
cool_numbers  = tf.Variable([3.14159, 2.71828], tf.float32)
first_primes = tf.Variable([2, 3, 5, 7, 11], tf.int32)
its_very_complicated = tf.Variable([12.3 - 4.85j, 7.5 - 6.23j], tf.complex64)

고차원 랭크

랭크 2 tf.Tensor 객체는 최소 한 개 이상의 열과 행으로 구성됩니다:

mymat = tf.Variable([[7],[11]], tf.int16)
myxor = tf.Variable([[False, True],[True, False]], tf.bool)
linear_squares = tf.Variable([[4], [9], [16], [25]], tf.int32)
squarish_squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)
rank_of_squares = tf.rank(squarish_squares)
mymatC = tf.Variable([[7],[11]], tf.int32)

유사하게, 고차원 랭크 텐서는 n-차원 배열로 구성됩니다. 예를 들어, 이미지 처리에서는 각각 배치 수와 이미지 높이, 이미지 너비, 색상 채널에 해당하는 4차원 랭크 텐서가 사용됩니다.

my_image = tf.zeros([10, 299, 299, 3])  # 배치 x 높이 x 너비 x 색상

tf.Tensor 객체 랭크 구하기

tf.Tensor 객체의 랭크를 알기 위해서는 tf.rank 메서드를 호출합니다. 예를 들어, 다음 메서드는 이전 섹션에서 정의된 tf.Tensor의 랭크를 알려줍니다.

r = tf.rank(my_image)
# 이 코드가 실행된 후 r은 4라는 값을 가지게 됩니다.

tf.Tensor 원소 참조하기

tf.Tensor는 n-차원 배열로 구성되어 있기 때문에 때문에, tf.Tensor의 원소 하나에 접근하기 위해서는 n개의 인덱스가 필요합니다.

랭크 0 텐서(스칼라)인 경우 이미 하나의 숫자이기 때문에 인덱스가 필요없습니다.

랭크 1 텐서(벡터)인 경우 숫자 하나에 접근하기 위해서는 인덱스 한 개를 전달해야 합니다:

my_scalar = my_vector[2]

벡터로부터 원소 한 개를 동적으로 선택하기 위해서 []안에 스칼라형 tf.Tensor를 인덱스로 사용할 수 있습니다.

랭크 2이상의 고차원 텐서인 경우에는 좀 더 흥미롭습니다. 예상한 것처럼 랭크 2인 tf.Tensor를 위해 인덱스로 2개를 전달해야 스칼라 한 개를 반환합니다:

my_scalar = my_matrix[1, 2]

그러나, 한 개만 전달한다면 다음과 같이 행렬의 부분 벡터를 반환합니다:

my_row_vector = my_matrix[2]
my_column_vector = my_matrix[:, 3]

: 표기는 "해당 차원을 남겨라"라는 파이썬 슬라이싱(slicing) 문법입니다. 이러한 표기법은 고차원 텐서에서 부분 벡터와 부분 행렬, 다른 부분 텐서들까지도 접근할 수 있도록 만들어 주기 때문에 유용합니다.

형태

텐서의 형태(shape)는 각 차원에 있는 원소 개수로 표현됩니다. 텐서플로는 그래프 계산 과정에서 자동으로 텐서 형태를 추론합니다. 이렇게 추론된 형태는 랭크를 알고 있는 경우도 있고 그렇지 않는 경우도 있습니다. 만약에 랭크를 알고 있는 경우라도 각 차원의 원소 개수를 알고 있는 경우도 있고 그렇지 않는 경우도 있습니다.

텐서플로 문서에서 텐서 차원을 표현하기 위해서 3가지 용어를 사용합니다: 랭크, 형태, 차원. 다음 표는 각 용어가 다른 용어와 어떻게 연관되어 있는지를 보여줍니다.

랭크 형태 차원 예제
0 [] 0-차원 스칼라인 0-차원 텐서.
1 [D0] 1-차원 형태가 [5]인 1-차원 텐서.
2 [D0, D1] 2-차원 형태가 [3, 4]인 2-차원 텐서.
3 [D0, D1, D2] 3-차원 형태가 [1, 4, 3]인 3-차원 텐서.
n [D0, D1, ... Dn-1] n-차원 형태가 [D0, D1, ... Dn-1]인 텐서.

형태는 파이썬 리스트 / 정수형 튜플 또는 tf.TensorShape으로 표현될 수 있습니다.

tf.Tensor 객체 형태 얻기

tf.Tensor의 형태를 알기 위한 2가지 방법이 있습니다. 그래프를 생성하는 동안 텐서의 형태를 알 수 있는 것은 종종 유용합니다. 형태는 tf.Tensor객체의 shape 속성으로 알 수 있습니다. 이 메서드는 TensorShape를 반환하고, 이러한 방식은 완전하게 알지 못하는 형태를 표현하는데 편리한 방법입니다 (그래프를 생성할 때 모든 형태를 알 수 없기 때문에).

실행 시점에 다른 tf.Tensor 객체의 완전한 형태를 표현하는 tf.Tensor를 얻을 수도 있습니다. 이것은 tf.shape 연산을 통해서 확인할 수 있습니다. 이를 통해, 입력 tf.Tensor의 동적인 형태에 연동된 다른 텐서를 생성함으로써 텐서 형태를 변경하는 그래프를 생성할 수 있습니다.

예를 들어 다음은 주어진 행렬의 열 개수와 동일한 크기의 0으로 구성된 벡터를 만드는 예입니다.

zeros = tf.zeros(my_matrix.shape[1])

tf.Tensor 형태 변경

텐서의 원소 개수는 모든 형태의 크기를 곱한 것입니다. 스칼라의 원소 개수는 항상 1입니다. 원소 개수가 같은 경우에도 형태는 다양할 수 있기 때문에, 그 원소 개수를 유지하면서 tf.Tensor의 형태를 변경할 수 있는 것은 유용합니다. 이것은 tf.reshape으로 처리할 수 있습니다.

다음은 텐서 형태를 변경하는 예입니다:

rank_three_tensor = tf.ones([3, 4, 5])
matrix = tf.reshape(rank_three_tensor, [6, 10])  # 기존 내용을 6x10 행렬로
                                                 # 형태 변경
matrixB = tf.reshape(matrix, [3, -1])  # 기존 내용을 3x20 행렬로 형태 변경
                                       # -1은 차원 크기를 계산하여
                                       # 자동으로 결정하라는 의미
matrixAlt = tf.reshape(matrixB, [4, 3, -1])  # 기존 내용을 4x3x5 텐서로
                                             # 형태 변경

# 형태가 변경된 텐서의 원소 개수는
# 원래 텐서의 원소 개수와 같습니다.
# 그러므로 다음은 원소 개수를 유지하면서
# 마지막 차원에 사용 가능한 수가 없기 때문에 에러를 발생합니다.
yet_another = tf.reshape(matrixAlt, [13, 2, -1])  # 에러!

자료형

차원뿐만 아니라, 텐서는 자료형도 가지고 있습니다. 전체 자료형을 확인하려면 tf.DType를 참고하세요.

tf.Tensor가 한 개이상의 자료형을 가지는 것은 불가능합니다. 임의의 데이터 구조를 직렬화한 string를 저장한 tf.Tensor는 예외입니다.

tf.cast를 이용해서 tf.Tensor의 자료형을 다른 것으로 변경하는 것은 가능합니다:

# 정수형 텐서를 실수형으로 변환.
float_tensor = tf.cast(tf.constant([1, 2, 3]), dtype=tf.float32)

tf.Tensor의 자료형을 확인하기 위해서는 Tensor.dtype 속성을 활용하세요.

파이썬 객체를 이용해서 tf.Tensor를 생성할 때 자료형을 선택적으로 명시할 수 있습니다. 그렇지 않으면 텐서플로가 데이터 표현에 적합한 자료형을 선택합니다. 텐서플로는 파이썬 정수를 tf.int32로 변환하고 파이썬 실수는 tf.float32으로 변환합니다. 그외에 동일한 규칙을 넘파이(numpy)를 배열로 변경할 때 사용합니다.

텐서 계산하기(evaluate)

일단 계산 그래프를 만들었다면, 특정 tf.Tensor를 생성하거나 텐서에 할당된 값을 가져오는 계산을 실행할 수 있습니다. 이것은 텐서플로 작업의 많은 부분에서 필요할 뿐 아니라 디버깅에 종종 유용합니다.

텐서를 계산하는 가장 간단한 방법은 Tensor.eval 메서드를 사용하는 것입니다. 예를 들어:

constant = tf.constant([1, 2, 3])
tensor = constant * constant
print(tensor.eval())

eval 메서드는 기본 tf.Session이 활성화된 경우에만 작동합니다 (자세한 내용은 그래프와 세션에서 확인하세요).

Tensor.eval은 텐서와 같은 내용을 가지는 넘파이 배열을 반환합니다.

때때로 현재 사용할 수 없는 동적인 정보에 의존하기 때문에 컨텍스트(context)가 없는 tf.Tensor는 계산할 수 없는 경우가 있습니다. 예를 들어, placeholder에 의존하는 텐서는 그 placeholder에 해당하는 값이 제공되지 않으면 계산할 수 없습니다.

p = tf.placeholder(tf.float32)
t = p + 1.0
t.eval()  # 플레이스홀더(placeholder)가 값을 가지고 있지 않기 때문에, 이것은 실패할 것입니다.
t.eval(feed_dict={p:2.0})  # 플레이스홀더에 해당하는 값을 제공받기 때문에
                           # 이것은 성공할 것입니다.

플레이스홀더뿐만 아니라 어떤 tf.Tensor도 값을 제공받는 것이 가능하다는 것에 유의하세요.

다른 모델 구조는 tf.Tensor를 계산하는 것을 복잡하게 만들 수 있습니다. 텐서플로는 함수안이나 제어 흐름안에 정의된 tf.Tensor를 직접 계산할 수 없습니다. 만약에 tf.Tensor가 큐(queue)에 있는 값을 사용한다면, 무언가가 큐에 들어간 후에 만 tf.Tensor 계산을 할 수 있습니다; 그렇지 않으면 계산은 멈출(hang) 것입니다. 큐와 같이 작업할 때, tf.Tensor를 계산하기 전 tf.train.start_queue_runners를 호출하세요.