딥러닝 공부

골빈해커의 3분 딥러닝 텐서플로맛_ch.3 텐서플로 프로그래밍 101 (텐서플로우 2.0)

Woonys 2021. 7. 24. 01:11
반응형

텐서플로를 사용하면서 빠르게 딥러닝을 직접 구현해보면 좋겠다고 생각해서 초심자용 책을 고르던 중, 동료에게 추천받은 책이 바로 <골빈해커의 3분 딥러닝_텐서플로맛>이다. (뭔가 이름이 맛있어 보인다...)

 

책을 살까 했는데, 정말 다행스럽게도 밀리의 서재에서 이 책을 pdf 형태로 읽을 수 있더라. 덕분에 책을 구매하거나 도서관에서 빌리지 않고도 빠르게 공부할 수 있게 됐다.

 

하지만 진짜 문제가 생겼으니..챕터 1,2까지만 해도 신나게 공부하다가 챕터 3에서 턱 막혔다. 왜냐!

 

이 책에 나오는 예제가 모두 텐서플로 1.x 버전으로 작성된 코드로 되어 있었다..

 

물론 텐서플로 2.x 버전에서도 1.x 버전을 쓸 수는 있다. 아래 패키지를 import하면 가능하긴 하다.

import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

하지만 조금 서치해보면서 내린 결론은 "2.0을 배우는 게 훨씬 좋다!"였다. 1.x 버전과 2.x 버전의 차이는 여러 가지가 있지만 가장 큰 것 중 하나가 세션과 플레이스홀더의 유무다. (차이는 다른 곳에서 잘 설명해뒀으니 검색 ㄱㄱ) 1.0 버전이 원리적으로 밑바탕을 이해하기 좋은 버전인 것도 아닌데 굳이 2.x 편하게 쓸 수 있는 상황에서 1.x 배우고 또 2.x 배우는 건 시간낭비라 생각했다. 그래서 2.x로 수정본이 있는지 찾아봤다. 근데 없네???

 

휴...내린 결론은 "내가 일일이 바꿔가면서 배우자!"였다. 그래서 앞으로 연재할 내용은 위의 책에서 1.x로 쓰여진 예제를 모두 2.5로 바꿔서 입력하려고 한다.

 

1.x 버전의 가장 큰 특징은 지연 실행(lazy evaluation)이다. 각 텐서 및 텐서의 연산에 대해 변수를 정의한 뒤, 실행하는 코드를 원하는 시점에 넣어서 실제 연산을 수행하게끔 한다. 예를 들어 우리가 파이썬에서 함수를 정의하면 def func()로 정의한 다음 실제로 그 함수를 쓸 시점에서 func()를 입력하지 않나.

 

반면 2.x에서는 즉시 실행(eager evaluation)이다.  따로 연산을 수행하는 함수나 변수가 들어갈 매개변수를 정의하지 않고 바로 연산을 수행한다. 그래서 훨씬 직관적이고 빠르다.

 

3.1 텐서와 그래프 실행

1.x 버전에서 볼 수 있는 특징 첫 번째는 세션(session)이다. 세션은 그래프를 실행하기 위한 함수다. 예를 들어 파이썬에서 변수 간의 일반적인 사칙연산을 한다고 하면, 변수를 정의하고 바로 연산을 때리면 결과값이 출력된다. 예컨대 아래와 같은 식이다.

 

a = 1
b = 2
print(a+b)
3

하지만 기존 파이썬 패키지에 없는 새로운 연산을 하고 싶다면 함수를 정의해줘야 한다. 텐서플로도 마찬가지다. 텐서플로 프로그램의 구조는 1) 그래프 생성 2) 그래프 실행 두 가지로 분리되어 있다. 그래프 생성 파트에서 변수 및 함수를 정의해주고 나면 그래프 실행에서는 실제로 그 함수에 변수를 넣어 실행(이에 해당하는 함수가 session)을 한다. 그래서 1.x 버전에서는 1+2=3을 아래와 같은 방식으로 실행한다.

 

#ver.1.x
#그래프 생성
a = tf.constant(10)
b = tf.constant(32)
c = tf.add(a, b)

#그래프 실행
sess = tf.Session()
#sess= 그래프 실행을 위한 함수
print(sess.run([a, b, c]))
sess.close()

하지만 텐서플로 2.x 버전에서는 이 복잡한 구조가 모두 생략된다. 아주아주 간단하다. 원래 파이썬 하듯이 하면 된다.

ver. 2.x
a = tf.constant(1)
b = tf.constant(2)
c = tf.add(a, b)

print(c)
$ tf.Tensor(3, shape=(), dtype=int32)

 

3.2 플레이스홀더와 변수

세션에 이어 또 하나 복잡한 녀석이 나오는데, 바로 플레이스홀더(Placeholder)다 플레이스홀더는 그래프에 사용할 입력값을 나중에 받기 위해 사용하는 매개변수다. 예컨대 y=2x+3이라고 할 때, x가 매개변수지 않나. 실제 입력값이 들어가기 전 일종의 빈껍데기라고 생각하면 된다.

 

예전 버전으로 y = x*W + b 행렬곱을 계산하려고 한다고 하자. 먼저 x자리에 데이터를 넣어야 하니 x 자리를 플레이스홀더로 만들어준다. 이후에 각 변수 W, b를 정의하고 행렬곱을 계산한 다음 최종적으로 session을 정의해서 run을 돌리면 된다.

ver 1.x
#그래프 생성 => 각 그래프에 들어갈 변수 지정
X = tf.placeholder(tf.float32, [None, 3])
print(X)

x_data = [[1, 2, 3], [4, 5, 6]] #2*3 행렬

W = tf.Variale(tf.random_normal([3, 2])) #3*2 행렬
b = tf.Variable(tf.random_normal([2, 1])) #2*1 행렬

expr = tf.matmul(X, W) + b #행렬곱 계산

#그래프 실행

sess = tf.Session()
#앞에서 정의한 변수를 초기화 => 처음 실행하면 연산 실행 전에 변수 초기화 필요
sess.run(tf.global_variables_initializer()) 


print(x_data)
print(sess.run(W))
print(sess.run(b))

print(sess.run(expr, feed_dict={x: x_data}))

sess.close()

(딱 봐도 개 복잡하다..)

 

이걸 2.x 버전으로 바꾸면 어떻게 될까? 앞서 말했듯, 2.x 버전에서는 세션과 플레이스홀더가 빠진다. 따로 매개변수를 만들고 그 안에 넣을 필요 없이 변수를 정의한 다음, 함수를 정의하고 그 안에 변수를 때려박으면 된다. 이미 함수 안에서 변수가 호출되는데 굳이 매개변수를 따로 만들어야 할 이유가 없어진다.

 

#ver. 2.x

#변수 정의
W = tf.Variable(tf.random_normal(shape=(2,3), name='W')
b = tf.Variable(tf.random_normal(shape=(2,)), name='b')
print(W)
print(b)

@tf.function #decorator 사용 => decorator
def model(x): #ver.2.x에서는 함수로 정의 => 생각해보면 그래프 실행에 해당하는 sess 역시 하나의 함수.
	return W*x + b

out_a = model([[1, 2, 3], [4, 5, 6]])

print(out_a)

 

3.3 선형 회귀 모델 구현하기

자, 이 챕터의 마지막인 선형 회귀 모델 구현이다. 파이썬에서도 오질나게 했던 선형 회귀지만 새로운 방식으로 접할 때면 늘 새롭다. 1.x 버전은 책에 잘 적혀 있으니 이제부터는 2.x 내용만 적기로 한다.

 

#학습 데이터
x_data = [1,2,3]
y_data = [2,4,6]

#weight & bias 변수 정의
W = tf.Variable(tf.random.normal([1]), name='Weight')
b = tf.Variable(tf.random.normal([1]), name='bias')

#y' => 모델이 예측하는 y에 대한 가설 값
hypothesis = x_data * W + b

#cost function 정의
cost = tf.reduce_mean(tf.square(hypothesis-y_data)
#Optimizer 정의
sgd = tf.keras.optimizers.SGD(learning_rate=0.01)

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(1, input_dim=1))

model.compile(loss = 'mean_squared_error', optimizer=sgd)

model.fit(x_data, y_data, epochs=1000)

print(model.predict(np.array([5])))


#Result
Epoch 1000/1000
1/1 [==============================] - 0s 3ms/step - loss: 2.0492e-04
[[9.954774]]

여기서 keras 패키지를 사용하면 위의 코드보다 훨씬 더 간결해진다. 그런데 방금 loss 값을 비교해보니 위의 loss 값이 훨씬 낮다(약 3배 차이). 확실히 코드가 길어지는 만큼 최적화가 더 잘 된건가...? 이건 더 공부해봐야 알 수 있을 것 같다.

 

#훨씬 더 간단한 version by keras

x_data = [1,2,3]
y_data = [4,5,6]

#W, b를 따로 정의하지 않고 model 변수 내에 함께 저장

model=tf.keras.models.Sequential([
	tf.keras.layers.Dense(1, kernel_initializer='random_uniform', bias_initializer='random_uniform')
    #kernel_initializer=W, bias_initializer=b에 해당
model.compile(loss = 'mean_squared_error', optimizer=tf.keras.optimizers.SGD(learning_rate=0.01)

model.fit(x_data, y_data, epochs=1000)

#result
Epoch 1000/1000
1/1 [==============================] - 0s 3ms/step - loss: 6.3246e-04

 

코드가 훨씬 간결하고 짧아진 반면, loss값은 차이가 난다. 이건 여러 번 실험해도 마찬가지다. 1e-04는 같으나 앞자리 값이 3배 차이다.

반응형