What is Tensorflow?

KimSeongJung
Public AI
Published in
16 min readNov 25, 2021

Deep in dive Tensorflow 연재 글의 첫번째 포스트 입니다.
Deep In Dive Tensorflow 연재는 텐서플로우를 좀 더 깊게 알아보기 위한 목적으로 제작되고 있습니다.
해당 포스트는 tensorflow 공식 홈페이지tensorflow github 을 기반으로 제작 되었습니다.

1. Tensorflow란?

공식 문서를 보면 아래와 같이 텐서플로우를 소개할 수 있습니다.

TensorFlow는 머신러닝을 위한 엔드 투 엔드 오픈소스 플랫폼입니다.
도구, 라이브러리, 커뮤니티 리소스로 구성된 포괄적이고 유연한 생태계를 통해 연구원들은 ML에서 첨단 기술을 구현할 수 있고
개발자들은 ML이 접목된 애플리케이션을 손쉽게 빌드 및 배포할 수 있습니다.

현재 와서 텐서플로우는 여러가지 기능들이 추가 됨으로서 플랫폼으로 소개 되고 있지만 기본적으로 Tensorflow 는 Deep Learning 을 위한 Framework 라고 생각해도 무방합니다. 즉, 텐서플로우는 유저가 쉽게 ML Application 구현 하도록 뼈대 및 뼈대에다 살을 붙이기 위한 여러가지 도구들을 제공합니다.
ML Application 을 만들기 위한 복잡한 제어 과정 및 기타 과정은(state 관리 , auto diff 등) Tensorflow가 뒷단에서 처리하고 유저는 만들고자 하는 알고리즘에만 집중할 수 있도록 함으로서 효율적이고 능률적으로 ML Application을 만들수 있게 도와 줍니다.

조금만 더 깊게 Tensorlfow 바라보기

텐서플로우를 이해하기 위해 Tensorflow 을 설명한 논문을 한번 열어 봅시다.

논문에 소개하는 텐서플로우는 ML 모델 구현과 ML 알고리즘의 인터페이스라고 소개 하고 있습니다. 즉 사용자가 생각하는 알고리즘을 구현 할 수 있도록 해주는 부분을 Tensorflow 가 담당하고 있다라는 것 입니다. 또한 텐서플로우로 구현된 알고리즘은 여러 시스템(heterogeneous)에 바로 적용하다라고 이야기 합니다. (모바일이든 컴퓨터든 임베디드 기기던 상관없이)

오른쪽 글의 HighLight 을 살펴 보면 텐서플로우로 구현된 알고리즘은 stateful dataflow garph 로 표현된다 라는 문장을 확인할 수 있습니다.
stateful, dataflow, graph 단어를 하나하나 살펴보면서 텐서플로우를 좀더 깊게 이해해 보도록 하겠습니다.

Graph

Graph란 Node와 Edge로 이루어진 부분집합을 의미합니다.

그래프(영어: graph, 문화어: 그라프)는 일부 객체들의 쌍들이 서로 연관된 객체의 집합을 이루는 구조이다.
일련의 꼭짓점들과 그 사이를 잇는 변들로 구성된 조합론적 구조로 볼 수 있다.
그래프를 연구하는 수학의 분야를 그래프 이론이라고 한다.
“그래프”라는 용어는 1878년 J. J. 실베스터에 의해 처음 사용되었다 [from wiki] (
https://ko.wikipedia.org/wiki/%EA%B7%B8%EB%9E%98%ED%94%84_(%EC%88%98%ED%95%99))

위 그림에서 보디는 동그라미를 Node라 부르고 노드와 노드를 연결하는 선을 Edge 라고 부릅니다.
Tensorflow 는 위 Graph을 활용해 알고리즘을 구현합니다.
Graph 속 노드는 특별히 Operation 이라고 부르고 , Edge 는 Tensor 라고 부릅니다.

알고리즘은 많은 연산 및 기능들로 구성되어 있습니다.
텐서플로우는 연산 및 기능을 Operation 으로 사용됩니다. 그리고 Operation 과 Operation 으로 연결되는 Edge 는 Tensor 라고 표현합니다.
그럼 Tensor와 Operation 이 정확히 무엇인지 살펴 봅시다.

Tensor

텐서플로우 공식 홈페이지에 Tensor의 정의를 보면 아래와 같습니다.

Tensors are multi-dimensional arrays with a uniform type (called a dtype). You can see all supported dtypes at tf.dtypes.DType.

텐서플로우에서는 텐서를 다차원이면서 동시에 배열속 모든 element 의 DataType 이 동일한 배열 이라고 정의 하였습니다.

아래 예시를 쉽게 이해할 수 있습니다. 아래 그림을 보면 0차원 , 1차원, 2차원 ,3차원 텐서를 확인해 볼 수 있습니다.

(만약 차원을 파악하기 어렵다면 방법은 양끝 대괄호 개 수를 세는 것 입니다. 3차원은 양끝 대괄호의 개 수 가 3개입니다.)
텐서플로우에서 우리는 데이터를 텐서로 표현합니다.
아래와 같은 예시가 있다면 우리는 데이터를 아래와 같이 텐서로 표현할 수 있습니다.

Cancer sizeAgelabel93malignant89benign22benign

import tensorflow as tf 
data = tf.constant([[9, 3, 1], [8, 2, 0], [2, 2, 0]])
print(x)
# >>> <tf.Tensor: shape=(3, 3), dtype=int32, numpy=
# array([[9, 3, 1],
# [8, 2, 0],
# [2, 2, 0]], dtype=int32)>

Operation

텐서플로우 Node 을 Operation 이라고 부릅니다.
텐서플로우에서 Opearation 의 정의를 살펴 보겠습니다.

An Operation is a node in a tf.Graph that takes zero or more Tensor objects as input, and produces zero or more Tensor objects as output. Objects of type Operation are created by calling a Python op constructor (such as tf.matmul) within a tf.function or under a tf.Graph.as_default context manager.

요약 하자면 Operation 은 input 으로 Tensor을 0개 이상 받을수 있고 출력 또한 0개 이상의 Tensor을 출력으로 내보낼수 있다.
라고 이야기 하고 있습니다. 좀 더 생각해 보면 Operation은 입력으로 아무것도 받지 않을수 있지만 받는다면 텐서를 받는다.
그리고 출력 또한 아무것도 출력하지 않을수 있지만 출력을 한다면 하나 이상의 텐서를 출력한다. 라고 이야기 합니다.
즉 이뜻은 Operation의 주요 기능은 텐서를 계산하거나 연산하는 기능을 가지고 있다는 것을 의미합니다.

예를 들어보자면
가령 3 + 2 를 해본다고 했을 때

순차적으로 생각해 보면 아래와 같습니다.
3이라는 상수를 만들어야 하고
2라는 상수를 만들어야 하고
3과 2를 더하는 연산을 수행해야 합니다.

이를 Tensorflow 로 구현하면 아래와 같습니다.

# 3이라는 상수를 만듭니다.
a = tf.constant(3)
# 2이라는 상수를 만듭니다.
b = tf.constant(2)
# a, b를 더합니다.
c = tf.add(a, b)

위 코드에서 tf.constant 함수는 입력으로 숫자를 받고 해당 숫자를 Tensor로 변환하는 기능을 합니다.
즉 기능을 하기에 위 텐서플로우 함수는 Operation 을 생성하는 기능을 가지고 있다는 것을 알 수 있습니다.
그리고 한 가지 생각을 더하자면 해당 Operation 의 출력값을 return 하는 것 도 알수 있습니다.

코드를 보면서 생각해 봅시다.(안봐도 괜찮습니다.)

@tf_export("constant", v1=[])
def constant(value, dtype=None, shape=None, name="Const"):

# ...(생략)

g = ops.get_default_graph()
tensor_value = attr_value_pb2.AttrValue()
tensor_value.tensor.CopyFrom(
tensor_util.make_tensor_proto(
value, dtype=dtype, shape=shape, verify_shape=verify_shape,
allow_broadcast=allow_broadcast))
dtype_value = attr_value_pb2.AttrValue(type=tensor_value.tensor.dtype)
attrs = {"value": tensor_value, "dtype": dtype_value}
const_tensor = g._create_op_internal( # pylint: disable=protected-access
"Const", [], [dtype_value.type], attrs=attrs, name=name).outputs[0]
if op_callbacks.should_invoke_op_callbacks():
callback_outputs = op_callbacks.invoke_op_callbacks(
"Const", tuple(), attrs, (const_tensor,), op_name=name, graph=g)
if callback_outputs is not None:
const_tensor, = callback_outputs
return const_tensor

여기서 주의 깊게봐야 봐야 할 코드는 아래 코드 입니다.
특히 tf.constant 함수 내용중 _create_op_internal을 살펴봅시다.
해당 코드를 살펴 보면 외부에 보이지 않으면서 내부적으로 operation 을 생성한다는 것을 알 수 있습니다.
그리고 graph instance 의 method인것을 보아 graph 에 operation 을 추가한다. 라는 것을 알 수 있습니다.
single underscore 임으로 외부 사용자에게 보여지지 않은 함수임을 알수 있습니다.
이는 즉 텐서플로우가 operation 생성은 tensorflow 가 제공하는 op.constructor 을 사용해 operation 을 생성하는것을 의도하고 있음을 살펴 볼수 있습니다.

const_tensor = g._create_op_internal(  # pylint: disable=protected-access
"Const", [], [dtype_value.type], attrs=attrs, name=name).outputs[0]

그리고 해당 함수(_create_op_internal)의 outputs 로는 여러개의 tensor 가 생성됨을 파악할 수 있습니다.

Dataflow

왜 Tensorflow 을 Dataflow 라고 할까요?
해당 사유에 대해 생각해 보겠습니다.
우리는 tensor 부분에서 data 을 tensor로 표현 할 수 있나는 것을 배웠습니다.
즉 이뜻은 data 가 흐른다, Tensor 가 흐른다로 이해 할 수 있습니다.
그럼 어디로 흐를까? 라고 생각할 수 있는데 그것은 바로 operation 과 operation 사이를 흐른다 라는 것 입니다.
즉 Tensorflow 는 operation 과 operation 사이를 tensor가 흐르게 합니다.

흐른다라는 개념이 모호하게 느껴질수 있는데 아래 예시를 보면서 보다 자세히 설명 하도록 하겠습니다.

위 그래프에서 가령 tensor d 을 생성한다고 가정 했을때
tensor d 을 수행하기 위해 tensor a, b 을 생성해야 하고
생성된 tensor a, b는 아래 그림과 같이 + operation 으로 흘러가야 합니다.

반면 tensor d 를 생성할 시 필요 없는 tensor c 는 생성되지 않습니다.
이 점이 바로 tensorflow 의 dataflow 특징을 보여줍니다.
특정 operation나 tensor 을 실행 하기 위해 필요한 노드나 텐서들만 수행 한다는 점이죠.
(❖ 물론 이 개념도 tensorflow 2.x 오면서 graph 을 작게 작게 쪼개면서 해당 특징들이 유저들에게 두두러져 보이지 않습니다. )

하지만 단순한 graph 개념에서는 연결된 노드 사이의 순서는 결정 되지 않습니다. 단순히 연결 되어 있을 뿐이죠.
그래서 Tensorflow 에서는 어떤 노드가 먼저 실행 되어야 하는 dependnecy을 지정 합니다.
(❖ 물론 이 개념도 tensorflow 2.x 오면서 tensorflow 자체에서 처리하게 됩니다.)
지정된 순서에 따라 노드를 실행하고 다음 노드로 tensor을 흘려주게 됩니다.

우리는 이 과정들을 통해 tensorflow가 차용하고 있는 dataflow 개념에 대해 배우고 해당 개념을 통해 텐서플로우를 보다 깊게 이해할 수 있었습니다.

Stateful

Stateful , Stateless 는 일반적으로 server-client 에서 자주 사용되는 용어 입니다.
Stateful 이란 서버가 client 의 상태를 저장하는 것이죠.
Tensorflow 는 Graph 상태를 저장하기 위해 Session 방법을 사용합니다.
(네 맞습니다. 보통 Web programming 에서 client 의 상태를 저장하기위해 사용되는 cookie , session에서 그 session 개념과 유사합니다.)
지금은 tensorflow 2.x 버전이 나오면서 session의 기능이 뒤로 감쳐졌지만 그래도 해당 개념을 익히는것은 텐서플로우를 보다 깊게 조망할수 있는 기회를 줄거라 생각합니다.
tensorflow 1.x 버전의 코드를 사용하면서 Tensorflow 에서 stateful 이라는 개념이 어떻게 사용되는지 파악해 보도록 하겠습니다.

Graph 정의 시에는 실제 값이 생성 되지 않습니다. 단순히 어떤 Tensor가 어떤 Operation 으로 흘러갈지를 결정할 뿐이죠.
Python 은 Interpreter 언어이고 바로 바로 값들이 출력됩니다.
하지만 Tensorflow 1.x 코드는 바로바로 값을 생성하지 않습니다.
(해당 포스트에서는 Tensorflow eager excution 은 고려하지 않습니다.)

예시를 들어 보겠습니다. 아래 그림에서 2개의 Random Variable(정규 분포)을 생성합니다. 그리고 그 두개의 변수를 더하는 간단한 Graph 입니다.
이 때 Graph 을 생성한다고 해서 텐서(a, b, c)에 값이 들어가 있지 않습니다.

a_init = tf.random.normal(shape=[])
a = tf.Variable(a_init)
b_init = tf.random.normal(shape=[])
a = tf.Variable(b_init)
c = tf.add(a, b)
print(a, b, c)

graph 을 만든다는 것은 단순히 어떤 텐서가 어떤 operation 에 들어가고 출력되는지를 정의 할 뿐입니다.
실제로 Graph 의 각 요소를 구동시키고 값을 얻어낼려면 Graph를 실행하는 Session 을 생성해야 합니다.
아래 코드에서 Session 을 instance 을 생성하고 tensor c 을 실행 시켜 봅니다.

위에서 본 것 처럼 Graph가 아니라 Session을 통해 Graph 의 실제 값들을 실행하고 Variable의 상태(state)를 저장 할 수 있습니다.
그렇기 때문에 Tensorflow 가 stateful 하다라고 말할 수 있습니다.
계속 변화하는 변수 상태의 값을 저장 할 수 있는 것이죠.

# define graph 
a_init = tf.random.normal(shape=[])
a = tf.Variable(a_init)
b_init = tf.random.normal(shape=[])
a = tf.Variable(b_init)
c = tf.add(a, b)
# execute graph
sess = tf.Session()
sess.run(c)

이런 구조를 가지면 다양한 환경에서 이점이 많습니다.
아래 예시를 통해 어떤 이점이 있는지 살펴 봅니다.

하나의 Graph을 생성하고 여러개의 Session 을 생성해 사용 가능합니다.
이는 병렬처리에 매우 유용하게 사용되거나 해당 프로그램을 Web 서비스시 분산처리에 매우 유용하게 사용될수 있음을 알수 있습니다.
(실제로 Web programming 에서 Web Framework(ex. django)가 WAS 서버에서 작동되는 원리를 생각해보면 알수 있습니다.)

하나의 그래프를 여러개의 Session을 사용해 생성하는 코드를 생성해 봅니다.

%tensorflow_version 1.x
import tensorflow as tf
# Define graph
a_init = tf.random.normal(shape=[])
a = tf.Variable(a_init)
b_init = tf.random.normal(shape=[])
a = tf.Variable(b_init)
c = tf.add(a, b)
# Generate Multi Session
sess1 = tf.Session()
print(sess1.run(c))
sess2 = tf.Session()
print(sess2.run(c))
sess3 = tf.Session()
print(sess3.run(c))
# >>> 1.3981419
# -0.042078532
# 0.48863256

여기서 매번 새로운 값을 생성하는 Random Variable 을 통해 각 Session 마다 다른 값들이 들어가 있는 것들을 확인해 볼 수 있습니다.
즉 Session 은 서로 분리되어 있으며 값들을 공유하지 않는다는 것들을 확인할수 있습니다.
이를 통해 하나의 그래프를 가지고 여러 유저에게 독립적인 서비스를 제공이 상당히 간단해 진것들을 확인 할 수 있습니다.

Sum up

해당 포스트를 통해 Tensorflow 가 어떤 목적으로 설계되어 있고 어떻게 정의 내릴수 있는지를 살펴 보았습니다.
특히 tensorflow 을 설명하는 키워드인 stateful graph dataflow에 대한 정의를 보다 자세히 들여다 보면서
tensorflow 의 설계와 아이디어 그리고 활용을 살펴 보았습니다.

감사합니다.

--

--