In [0]:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

텐서 만들기 및 조작

학습 목표:

  • 텐서플로우 변수 초기화 및 할당
  • 텐서 만들기 및 조작
  • 선형대수학의 덧셈 및 곱셈 지식 되살리기(이 주제가 생소한 경우 행렬 덧셈곱셈 참조)
  • 기본 텐서플로우 수학 및 배열 작업에 익숙해지기

In [0]:
from __future__ import print_function

import tensorflow as tf

벡터 덧셈

텐서에서 여러 일반적인 수학 연산을 할 수 있습니다(TF API). 다음 코드는 각기 정확히 6개 요소를 가지는 두 벡터(1-D 텐서)를 만들고 조작합니다.


In [0]:
with tf.Graph().as_default():
  # Create a six-element vector (1-D tensor).
  primes = tf.constant([2, 3, 5, 7, 11, 13], dtype=tf.int32)

  # Create another six-element vector. Each element in the vector will be
  # initialized to 1. The first argument is the shape of the tensor (more
  # on shapes below).
  ones = tf.ones([6], dtype=tf.int32)

  # Add the two vectors. The resulting tensor is a six-element vector.
  just_beyond_primes = tf.add(primes, ones)

  # Create a session to run the default graph.
  with tf.Session() as sess:
    print(just_beyond_primes.eval())

텐서 형태

형태는 텐서의 크기와 차원 수를 결정하는 데 사용됩니다. 텐서 형태는 목록으로 표현하며, i번째 요소는 i 차원에서 크기를 나타냅니다. 그리고 이 목록의 길이는 텐서의 순위(예: 차원 수)를 나타냅니다.

자세한 정보는 텐서플로우 문서를 참조하세요.

몇 가지 기본 예:


In [0]:
with tf.Graph().as_default():
  # A scalar (0-D tensor).
  scalar = tf.zeros([])

  # A vector with 3 elements.
  vector = tf.zeros([3])

  # A matrix with 2 rows and 3 columns.
  matrix = tf.zeros([2, 3])

  with tf.Session() as sess:
    print('scalar has shape', scalar.get_shape(), 'and value:\n', scalar.eval())
    print('vector has shape', vector.get_shape(), 'and value:\n', vector.eval())
    print('matrix has shape', matrix.get_shape(), 'and value:\n', matrix.eval())

브로드캐스팅

수학에서는 같은 형태의 텐서에서 요소간 연산(예: addequals)만 실행할 수 있습니다. 하지만 텐서플로우에서는 텐서에서 기존에는 호환되지 않았던 연산을 실행할 수 있습니다. 텐서플로우는 요소간 연산에서 더 작은 배열을 확장하여 더 큰 배열과 같은 형태를 가지게 하는 브로드캐스팅(Numpy에서 차용한 개념)을 지원합니다. 예를 들어 브로드캐스팅을 통해 다음과 같은 결과를 얻을 수 있습니다.

  • 피연산자에 크기가 [6]인 텐서가 필요한 경우 크기가 [1] 또는 크기가 []인 텐서가 피연산자가 될 수 있습니다.
  • 연산에 크기가 [4, 6]인 텐서가 필요한 경우 다음 크기의 텐서가 피연산자가 될 수 있습니다.
    • [1, 6]
    • [6]
    • []
  • 연산에 크기가 [3, 5, 6]인 텐서가 필요한 경우 다음 크기의 텐서가 피연산자가 될 수 있습니다.

    • [1, 5, 6]
    • [3, 1, 6]
    • [3, 5, 1]
    • [1, 1, 1]
    • [5, 6]
    • [1, 6]
    • [6]
    • [1]
    • []

참고: 텐서가 브로드캐스팅되면 텐서의 항목은 개념적으로 복사됩니다. (성능상의 이유로 실제로 복사되지는 않음. 브로드캐스팅은 성능 최적화를 위해 개발됨.)

전체 브로드캐스팅 규칙 세트는 Numpy 브로드캐스팅 문서에 이해하기 쉽게 잘 설명되어 있습니다.

다음 코드는 앞서 설명한 텐서 덧셈을 실행하지만 브로드캐스팅을 사용합니다.


In [0]:
with tf.Graph().as_default():
  # Create a six-element vector (1-D tensor).
  primes = tf.constant([2, 3, 5, 7, 11, 13], dtype=tf.int32)

  # Create a constant scalar with value 1.
  ones = tf.constant(1, dtype=tf.int32)

  # Add the two tensors. The resulting tensor is a six-element vector.
  just_beyond_primes = tf.add(primes, ones)

  with tf.Session() as sess:
    print(just_beyond_primes.eval())

행렬 곱셈

선형대수학에서 두 개의 행렬을 곱할 때는 첫 번째 행렬의 수가 두 번째 행렬의 수와 같아야 했습니다.

  • 3x4 행렬과 4x2 행렬을 곱하는 것은 유효합니다. 이렇게 하면 3x2 행렬을 얻을 수 있습니다.
  • 4x2 행렬과 3x4 행렬을 곱하는 것은 유효하지 않습니다.

In [0]:
with tf.Graph().as_default():
  # Create a matrix (2-d tensor) with 3 rows and 4 columns.
  x = tf.constant([[5, 2, 4, 3], [5, 1, 6, -2], [-1, 3, -1, -2]],
                  dtype=tf.int32)

  # Create a matrix with 4 rows and 2 columns.
  y = tf.constant([[2, 2], [3, 5], [4, 5], [1, 6]], dtype=tf.int32)

  # Multiply `x` by `y`. 
  # The resulting matrix will have 3 rows and 2 columns.
  matrix_multiply_result = tf.matmul(x, y)

  with tf.Session() as sess:
    print(matrix_multiply_result.eval())

텐서 형태 변경

텐서 덧셈과 행렬 곱셈에서 각각 피연산자에 제약조건을 부여하면 텐서플로우 프로그래머는 자주 텐서의 형태를 변경해야 합니다.

tf.reshape 메서드를 사용하여 텐서의 형태를 변경할 수 있습니다. 예를 들어 8x2 텐서를 2x8 텐서나 4x4 텐서로 형태를 변경할 수 있습니다.


In [0]:
with tf.Graph().as_default():
  # Create an 8x2 matrix (2-D tensor).
  matrix = tf.constant([[1,2], [3,4], [5,6], [7,8],
                        [9,10], [11,12], [13, 14], [15,16]], dtype=tf.int32)

  # Reshape the 8x2 matrix into a 2x8 matrix.
  reshaped_2x8_matrix = tf.reshape(matrix, [2,8])
  
  # Reshape the 8x2 matrix into a 4x4 matrix
  reshaped_4x4_matrix = tf.reshape(matrix, [4,4])

  with tf.Session() as sess:
    print("Original matrix (8x2):")
    print(matrix.eval())
    print("Reshaped matrix (2x8):")
    print(reshaped_2x8_matrix.eval())
    print("Reshaped matrix (4x4):")
    print(reshaped_4x4_matrix.eval())

또한 tf.reshape를 사용하여 텐서의 차원 수(\'순위\')를 변경할 수도 있습니다. 예를 들어 8x2 텐서를 3-D 2x2x4 텐서나 1-D 16-요소 텐서로 변경할 수 있습니다.


In [0]:
with tf.Graph().as_default():
  # Create an 8x2 matrix (2-D tensor).
  matrix = tf.constant([[1,2], [3,4], [5,6], [7,8],
                        [9,10], [11,12], [13, 14], [15,16]], dtype=tf.int32)

  # Reshape the 8x2 matrix into a 3-D 2x2x4 tensor.
  reshaped_2x2x4_tensor = tf.reshape(matrix, [2,2,4])
  
  # Reshape the 8x2 matrix into a 1-D 16-element tensor.
  one_dimensional_vector = tf.reshape(matrix, [16])

  with tf.Session() as sess:
    print("Original matrix (8x2):")
    print(matrix.eval())
    print("Reshaped 3-D tensor (2x2x4):")
    print(reshaped_2x2x4_tensor.eval())
    print("1-D vector:")
    print(one_dimensional_vector.eval())

실습 #1: 두 개의 텐서를 곱하기 위해 두 텐서의 형태를 변경합니다.

다음 두 벡터는 행렬 곱셈과 호환되지 않습니다.

  • a = tf.constant([5, 3, 2, 7, 1, 4])
  • b = tf.constant([4, 6, 3])

이 벡터를 행렬 곱셈에 호환될 수 있는 피연산자로 형태를 변경하세요. 그런 다음 형태가 변경된 텐서에서 행렬 곱셈 작업을 호출하세요.


In [0]:
# Write your code for Task 1 here.

해결 방법

해결 방법을 보려면 아래를 클릭하세요.


In [0]:
with tf.Graph().as_default(), tf.Session() as sess:
  # Task: Reshape two tensors in order to multiply them
  
  # Here are the original operands, which are incompatible
  # for matrix multiplication:
  a = tf.constant([5, 3, 2, 7, 1, 4])
  b = tf.constant([4, 6, 3])
  # We need to reshape at least one of these operands so that
  # the number of columns in the first operand equals the number
  # of rows in the second operand.

  # Reshape vector "a" into a 2-D 2x3 matrix:
  reshaped_a = tf.reshape(a, [2,3])

  # Reshape vector "b" into a 2-D 3x1 matrix:
  reshaped_b = tf.reshape(b, [3,1])

  # The number of columns in the first matrix now equals
  # the number of rows in the second matrix. Therefore, you
  # can matrix mutiply the two operands.
  c = tf.matmul(reshaped_a, reshaped_b)
  print(c.eval())

  # An alternate approach: [6,1] x [1, 3] -> [6,3]

변수, 초기화, 할당

지금까지 수행한 모든 연산은 정적 값(tf.constant)에서 실행되었고; eval()을 호출하면 항상 같은 결과가 반환되었습니다. 텐서플로우에서는 변수 객체를 정의할 수 있으며, 변수 값은 변경할 수 있습니다.

변수를 만들 때 초기 값을 명시적으로 설정하거나 이니셜라이저(예: 분포)를 사용할 수 있습니다.


In [0]:
g = tf.Graph()
with g.as_default():
  # Create a variable with the initial value 3.
  v = tf.Variable([3])

  # Create a variable of shape [1], with a random initial value,
  # sampled from a normal distribution with mean 1 and standard deviation 0.35.
  w = tf.Variable(tf.random_normal([1], mean=1.0, stddev=0.35))

텐서플로우의 한 가지 특징은 변수 초기화가 자동으로 실행되지 않는다는 것입니다. 예를 들어 다음 블록에서는 오류가 발생합니다.


In [0]:
with g.as_default():
  with tf.Session() as sess:
    try:
      v.eval()
    except tf.errors.FailedPreconditionError as e:
      print("Caught expected error: ", e)

변수를 초기화하는 가장 쉬운 방법은 global_variables_initializer를 호출하는 것입니다. eval()과 거의 비슷한 Session.run()의 사용을 참고하세요.


In [0]:
with g.as_default():
  with tf.Session() as sess:
    initialization = tf.global_variables_initializer()
    sess.run(initialization)
    # Now, variables can be accessed normally, and have values assigned to them.
    print(v.eval())
    print(w.eval())

초기화된 변수는 같은 세션 내에서는 값을 유지합니다. 하지만 새 세션을 시작하면 다시 초기화해야 합니다.


In [0]:
with g.as_default():
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # These three prints will print the same value.
    print(w.eval())
    print(w.eval())
    print(w.eval())

변수 값을 변경하려면 할당 작업을 사용합니다. 할당 작업을 만들기만 하면 실행되는 것은 아닙니다. 초기화와 마찬가지로 할당 작업을 실행해야 변수 값이 업데이트됩니다.


In [0]:
with g.as_default():
  with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # This should print the variable's initial value.
    print(v.eval())

    assignment = tf.assign(v, [7])
    # The variable has not been changed yet!
    print(v.eval())

    # Execute the assignment op.
    sess.run(assignment)
    # Now the variable is updated.
    print(v.eval())

로드 및 저장과 같이 여기에서 다루지 않은 변수에 관한 주제도 더 많이 있습니다. 자세히 알아보려면 텐서플로우 문서를 참조하세요.

실습 #2: 주사위 2개 10번 굴리기를 시뮬레이션합니다.

주사위 시뮬레이션을 만듭니다. 여기에서 10x3 2-D 텐서를 생성하며 조건은 다음과 같습니다.

  • 12는 각각 주사위 1개를 1번 던졌을 때의 값입니다.
  • 3은 같은 줄의 열 12의 합입니다.

예를 들어 첫 번째 행의 값은 다음과 같을 수 있습니다.

  • 14
  • 23
  • 37

텐서플로우 문서를 참조하여 이 문제를 해결해 보세요.


In [0]:
# Write your code for Task 2 here.

해결 방법

해결 방법을 보려면 아래를 클릭하세요.


In [0]:
with tf.Graph().as_default(), tf.Session() as sess:
  # Task 2: Simulate 10 throws of two dice. Store the results
  # in a 10x3 matrix.

  # We're going to place dice throws inside two separate
  # 10x1 matrices. We could have placed dice throws inside
  # a single 10x2 matrix, but adding different columns of
  # the same matrix is tricky. We also could have placed
  # dice throws inside two 1-D tensors (vectors); doing so
  # would require transposing the result.
  dice1 = tf.Variable(tf.random_uniform([10, 1],
                                        minval=1, maxval=7,
                                        dtype=tf.int32))
  dice2 = tf.Variable(tf.random_uniform([10, 1],
                                        minval=1, maxval=7,
                                        dtype=tf.int32))

  # We may add dice1 and dice2 since they share the same shape
  # and size.
  dice_sum = tf.add(dice1, dice2)

  # We've got three separate 10x1 matrices. To produce a single
  # 10x3 matrix, we'll concatenate them along dimension 1.
  resulting_matrix = tf.concat(
      values=[dice1, dice2, dice_sum], axis=1)

  # The variables haven't been initialized within the graph yet,
  # so let's remedy that.
  sess.run(tf.global_variables_initializer())

  print(resulting_matrix.eval())