[핸즈온 머신러닝 ch9]9강텐서플로우 사용해보기 2편

2018. 9. 3. 08:10Python-이론/python-인공지능2

9.6 경사 하강법 구현


이번에는 정규방정식이 아닌 경사하강법을 사용하여 회귀 문제를 해결해보겠습니다. 


9.6.1 직접 그레디언트 계산 





import numpy as np
import tensorflow as tf
from sklearn.datasets import fetch_california_housing
from sklearn.preprocessing import StandardScaler

n_epochs = 1000
learning_rate = 0.01

scaler = StandardScaler()
housing = fetch_california_housing()

m, n = housing.data.shape # 데이터 개수, 특성 수
scaled_housing_data = scaler.fit_transform(housing.data)
scaled_housing_data_plus_bias = np.c_[np.ones((m, 1)), scaled_housing_data]

X = tf.constant(scaled_housing_data_plus_bias, dtype=tf.float32, name='X')
y = tf.constant(housing.target.reshape(-1, 1), dtype=tf.float32, name='y')
theta = tf.Variable(tf.random_uniform([n + 1, 1], -1.0, 1.0), name="theta")
y_pred = tf.matmul(X, theta, name="predictions")

error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
#gradients = 2 / m * tf.matmul(tf.transpose(X), error) 손수 경사하강법 계산
#gradients = tf.gradients(mse, [theta])[0] 자동 미분
#training_op = tf.assign(theta, theta - learning_rate * gradients) 직접 경사하강법 계산, 자동 미분
optimizer = tf.train.GradientDescentOptimizer(learning_rate= learning_rate)
training_op = optimizer.minimize(mse)
init = tf.global_variables_initializer()

with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
if epoch % 100 == 0:
print("Epoch", epoch,"MSE =",mse.eval())
sess.run(training_op)


새롭게 쓰는 함수 


random_uniform() 함수는 난수를 담은 텐서를 생성하는 노드를 그래프에 생성합니다. 


assign() 함수는 변수에 새로운 값을 할당하는 노드를 생성합니다. 


1. 직접 그레디언트 계산하기 


앞서 공부했던 4강에 있던 공식을 코드로 표현한 것이다. 

training_op를 평가해서 훈련 한번할 때 마다 theta에 새로운 값을 할당 해준다.  


2. 자동 미분 사용 

tf.gradients를 사용하면 자동으로 미분해주어 효율적으로 그레디언트를 계산합니다. 

gradients() 함수는 하나의 연산과 변수 리스트를 받아 각 변수에 대한 연산의 그레디언트를 계산하는 새로운 연산을 만듭니다. 

여러가지 미분 방법이 있지만 텐서플로우는 후진 모드 자동 미분을 사용합니다. 

신경망처럼 입력이 많고 출력이 적을 때 완벽한 방법입니다. 

모든 그래디언트를 구하기 위한 순회 수 noutput + 1 만큼 반복합니다. 


3. 옵티마이저 사용 

#주석을 사용하지 않는 코드가 옵티마이저 입니다. 

사이킷런 처럼 자기가 직접 구현할 필요없이 편하게 사용하라고 제공해주는 방법입니다. 



9.7 훈련 알고리즘에 데이터 주입 


미니 배치 경사하강법을 구현하기 위해 이전 코드를 변경해보겠습니다. 이렇게 하려면 매 반복에서 X와 y를 다음번 미니배치로 바꾸어야 합니다.


가장 간단한 방법은 플레이스홀더 노드를 사용하는 것입니다. 이노드는 아무 것도 계산하지 않고 주입한 데이터를 출력하기만 합니다. 이노드는 전형적으로 훈련을 하는 동안 텐서플로우에 훈련 데이터를 전달하기 위해 사용됩니다.  


간단 사용법 


import tensorflow as tf

A = tf.placeholder(dtype=tf.float32, shape=(None, 3), name="A")
B = A + 5

with tf.Session() as sess:
B_result = B.eval(feed_dict={A: [[1, 2, 3]]})
B_result2 = B.eval(feed_dict={A: [[4, 5, 6], [7, 8, 9]]})
print(B_result)#[6, 7, 8]
print(B_result2)#[9,10,11], [12, 13, 14]

placeholder는 그릇같이 일단 만들어 놓고 코드를 실행시킬때 feed_dict로 대입시켜주면된다. 


위에서 했던 경사하강법에 조금만 코드를 바꾸어서 실행시켜 보겠습니다. 

이전의 코드에서 아래의 코드를 바꿔주거나 추가해주면 된다. 


X = tf.placeholder(dtype=tf.float32, shape=(None, n+1), name="X")
y = tf.placeholder(dtype=tf.float32, shape=(None, 1), name="y")

batch_size = 100
n_batches = int(np.ceil(m / batch_size))# ceil 올림

def fetch_batch(batch_size):
np.random.seed(42)
indices = np.random.randint(m, size=batch_size) #상한 값 안적어주면 m이 상한값으로 설정된다.
print(m)
X_batch = scaled_housing_data_plus_bias[indices]
y_batch = housing.target.reshape(-1, 1)[indices]
return X_batch, y_batch

with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
for batch in range(n_batches):
X_batch, y_batch = fetch_batch(batch_size)
sess.run(training_op, feed_dict={X:X_batch, y:y_batch})


9.8 모델 저장과 복원 


모델을 훈련시키고 나서 필요할 때 다시 쓸 수 있도록 모델을 자기 컴퓨터내에 저장할 수 있게 만들 수 있습니다. 그리고 훈련 중 오류가 발생해도 체크포인트를 지정할 수 있어서 다음에 훈련을 할 때 그 체크포인트 부터 다시 훈련 시킬 수 있습니다.


1. 저장하기 


init 변수 밑에 

saver = tf.train.Saver()

sess.run 밑에 

saver.save(sess, "./mymodel.ckpt")


그리고 최종본을 저장하고 싶다면 

with tf.Session() as sess와 아래 라인으로 

saver.save(sess, "./finalmodel.ckpt")을 사용하여 저장하면된다. 


2. 복원하기 


변수를 초기화 시켜주는 자리에 saver.restore를 사용하여 이전의 변수와 모델을 사용할 수 있다. 

save.restore(sess, "model File")


그리고 좀더 세부적으로 저장 또는 복원할 변수를 지정하거나 별도의 이름을 사용할 수 있습니다. 

예를 들어 

Saver 는 theta 변수만 weights란 이름으로 저장하고 복원할 것입니다. 


saver = tf.train.Saver({"weights":theta}) 


그리고 save는 메서드는 기본적으로 .meta 확장자를 가진 동일 이름의 두번째 파일에 그래픠의 구조를 저장합니다. 

tf.train.import_meta_graph()를 통해서 그래프의 구조를 읽어 들일 수 있습니다. 이 그래프는 기본 그래프로 추가되며, 변환된 Saver 인스턴트로 그래프의 상태를 복원하는 데 이용할 수 있습니다. 


saver = tf.train.import_meta_graph("./final.ckpt.meta")#이런식으로 호출
saver.restore(sess, "./final_model.ckpt")#init.run 대신 호출

이렇게 하면 원래 만들었던 코드를 찾아보지 않고도 그래프 구조와 변숫값을 포함해 저장된 모델을 완벽하게 복원할 수 있습니다.


변수값 혹은 오퍼레이션 값 불러내기


graph = tf.get_default_graph()
theta = graph.get_tensor_by_name("theta:0")

tf.get_defaul_graph()를 통해서 graph 객체를 만든 후 


get_tensor_by_name을 호출 후 변수의 이름을 인자로 넣어주면 거기에 해당하는 값을 불러올 수 있습니다.,


get_operation_by_name을 호출 후 오퍼레이션의 이름을 인자로 넣어주면 거기에 해당하는 값을 불러올 수 있습니다. 


사용가능한 이름 확인하기 


for op in tf.get_default_graph().get_operations():
print(op.name)


9.10 이름 범위 


신경망 처럼 복잡한 모델을 다룰 때는 계산 그래프가 수천 개의 노드로 인해 어질러지기 쉽습니다. 이를 피하려면 이름 범위를 만들어 관련 있는 노드들을 그룹으로 묶어야합니다. 

예를 들어 이전 코드를 수정해 보겠습니다.   


with tf.name_scope("loss") as scope:
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")

이런식으로 작성해주면 텐서보드에 loss라는 커다른 노드가 생깁니다. 


9.11 모듈화 


두개의 RELU 출력을 더하는 그래프를 만든다고 가정합시다. RELU는 선형함수인데 양수는 그대로 출력하고 음수일때는 0을 출력합니다. 


아래와 같이 작성하지 않는다면 코드를 반복해서 적어야 하는 경우가 많고 유지보수하기 어려워진다. 

다행히 텐서플로우에는 DRY(Don't Repeat Yourself)원칙을 유지하게 도와줍니다. 

import numpy as np
import tensorflow as tf


def relu(X):
w_shape = (int(X.get_shape()[1]), 1)
w = tf.Variable(tf.random_normal(w_shape), name="weights")
b = tf.Variable(0.0, name="bias")
z = tf.add(tf.matmul(X, w), b, name="z")
return tf.maximum(z, 0, name='relu')


X = tf.placeholder(dtype=tf.float32, shape=(None, 3), name="X")
relus = [relu(X) for i in range(5)]
result = tf.add_n(relus, name="output")
print(result)


이런식으로 relu함수를 만들어서 반복할 경우를 없애준다.  그리고 함수 이름밑에

 

함수 이름 

with tf.name_scope('relu'):

이런식으로 작성해주면 텐서 보드에서 더 이쁘게 볼 수 있다. 


9.12 변수 공유 


그래프의 여러 구성 요소 간에 변수를 공유하고 싶다면, 간단한 해결 방법은 변수를 먼저 만들고 필요한 함수에 매개변수로 전달하는 것입니다. 


import numpy as np
import tensorflow as tf


def relu(X, threshold):
with tf.name_scope("loss"):
w_shape = (int(X.get_shape()[1]), 1)
w = tf.Variable(tf.random_normal(w_shape), name="weights")
b = tf.Variable(0.0, name="bias")
z = tf.add(tf.matmul(X, w), b, name="z")
return tf.maximum(z, threshold, name='relu')

threshold = tf.Variable(0.0, name="threshold")
X = tf.placeholder(dtype=tf.float32, shape=(None, 3), name="X")
relus = [relu(X, threshold) for i in range(5)]
result = tf.add_n(relus, name="output")
print(result)

이와 같은 경우에는 문제없이 잘 작동합니다. 하지만 이럴 경우 공유 변수가 너무 많아지면 복잡해지는 문제점이 있습니다. 


그래서 텐서플로우에서는 이것보다 더 깔끔하고 모듈화하기 좋은 다른 방법을 제공합니다. 복잡하지만 많이 쓰이니 알아보도록 하겠습니다.

기본 아이디어는 get_variable() 함수를 사용해 공유 변수가 아직 존재하지 않을 때는 새로 만들고 이미 있을때는 재사용하는 것 입니다.  


variable_scope를 통해서 재사용해야할지 만들어야할지 결정합니다.


아래와 같은 방법으로 변수를 만들 때 

with tf.variable_scope("relu"):
threshold = tf.get_variable("threshold", shape=(),
initializer=tf.constant_initializer(0.0))


함수내에서 만들어서 사용할 때

with tf.variable_scope("relu", reuse=True): #재사용 여부를 True로 지정하면
threshold = tf.get_variable("threshold") #선언해줄 때 모형, 초기화 값 등을 지정해줄 필요가 없습니다.


위와 같이 코드를 작성 해주어도 문제가 없지만 함수내에서 쓸 변수는 함수내에서 선언해서 사용해주는 것이 좋습니다. 위의 코드를 그런 식으로 바꾸어 보겠습니다. 

import numpy as np
import tensorflow as tf


def relu(X):
threshold = tf.get_variable("threshold", shape=(),
initializer=tf.constant_initializer(0.0))
w_shape = (int(X.get_shape()[1]), 1)
w = tf.Variable(tf.random_normal(w_shape), name="weights")
b = tf.Variable(0.0, name="bias")
z = tf.add(tf.matmul(X, w), b, name="z")
return tf.maximum(z, threshold, name='relu')
X = tf.placeholder(dtype=tf.float32, shape=(None, 3), name="X")
relus = []
for i in range(5):
with tf.variable_scope("relu", reuse= (i >= 1)) as scope:
relus.append(relu(X))
result = tf.add_n(relus, name="output")
print(result)

i가 0일 때만 생성되고 0 이후로는 재사용이 승인되어 따로 구분할 필요 없이 하나의 코드만으로 사용할 수 있다.