reniew's blog
cs and deep learning

CS20(TensorFlow) Lecture Note (3): Linear and Logistic Regression

|

스탠포드의 TensorFlow 강의인 cs20 강의의 lecture note를 정리한 글입니다. 강의는 오픈되지 않아서 Lecture note, slide 위주로 정리된 글임을 참고 해주시길 바랍니다. 강의의 자세한 Syllabus 및 자료들을 아래 링크를 참고해 주세요.

CS20: TensorFlow for Deep Learning Research


Post list

3. Linear and Logistic Regression

이번 Lecture는 프로젝트로 진행된다. 앞서 배웠던 operation, sessiom, variable, constant 등을 이용해서 두개의 프로젝트를 진행한다.

첫 번째는 birth rate를 통해 life expectancy를 예측하는 linear regression 모델을 만드는 것이다.

두 번째는 MNIST 손글씨 데이터를 logistic regression을 통해 예측하는 모델을 만든다.

두 모델의 다른 점은 linear와 logistic의 차이뿐만 아니라 구현과정에서 데이터를 읽는 방식을 다르게 진행했다. 첫 번쨰 모델에서는 일반적이고 이전에 배웠던 방법인 tf.placeholder를 이용해서 데이터를 받을 것이고, 두 번째에서는 최근의 방법인 tf.data를 통해서 데이터를 다룰 것이다. 두 방법모두 익히는 것이 좋으므로 모델을 만들면서 차이와 구현방법에 대해서 알아보도록 하자.

Linear regression: Predict life expectancy from birth rate

제목에서 나와 있듯이 birth rate를 통해서 life expectancy를 예측하는 모델을 만들 것이다. 다르게는 두 수치간의 관계를 찾는다고 생각하도 된다. 우선은 우리가 알아볼 데이터에 대해서 알아보자.

  • X = brith rate, (Type:float)
  • Y = life expectancy, (Type:float)
  • 총 190개의 데이터

위 데이터를 가지고 모델을 만들 것이다. 우리는 두 수치간의 관계가 linear하다고 생각하고, linear한 모델을 만들 것이다. 즉 아래의 식으로 모델링을 한다.

이제 우리는 데이터를 통해 w,b를 학습시킬것이다. 이제 코드를 살펴보자. 전체 코드는 github을 참고하자.

우선은 우리가 필요한 라이브러리들을 import한다. utils은 데이터 다운로드, 데이터 읽기등을 위해 미리 만들어 놓은 utils.py 파일이다.

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2' # 로그 레벨, warning이상만 logging
import time

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

import utils

데이터를 읽어오자. utilsread_birth_life_data 함수를 사용하면된다.

DATA_FILE = 'data/birth_life_2010.txt'

data, n_samples = utils.read_birth_life_data(DATA_FILE)

이제는 데이터를 받을 X, Y를 정의해야 한다. 여기서는 placeholder를 사용한다.

X = tf.placeholder(dtype=tf.float32)
Y = tf.placeholder(dtype=tf.float32)

그리고 linear regression 모델의 파라미터인 w와 b를 get_variable함수로 생성한다. 처음에는 모두 0으로 초기화 하자.

w = tf.get_variable(name ='weight', shape=(), initializer=tf.zeros_initializer())
b = tf.get_variable(name = 'bias', shape=(), initializer=tf.zeros_initializer())

참고로 여기서 w,b,X,Y 모두 scalar값이다. 따라서 shape는 shape = ()으로 설정하거나 shape = []으로 설정하면 된다.

이제 우리의 모델을 정의한다. linear 모델이므로 아래와 같이 작성한다.

Y_predicted = X * w + b

그리고 loss 와 optimizer를 정해줘야 한다.

loss = tf.square(Y - Y_predicted, name='loss')
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)

마지막으로 학습시간 측정을 위해 시작 시간을 측정한다.

start = time.time()

이제 필요한 것을 다 선언했으므로 Session을 통해 실행하자.

with tf.Session() as sess:

    # 모든 변수 일괄 초기화
    sess.run(tf.global_variables_initializer())
    writer = tf.summary.FileWriter('./graphs/linear_reg', sess.graph)

    # 총 100에폭
    for i in range(100):
        total_loss = 0
        for x, y in data:
            # Execute train_op and get the value of loss.
            # Don't forget to feed in data for placeholders
            _, loss_ = sess.run([optimizer, loss], feed_dict={X: x, Y: y})
            total_loss += loss_

        print('Epoch {0}: {1}'.format(i, total_loss/n_samples))

    # close the writer when you're done using it
    writer.close()

    # Step 9: output the values of w and b
    w_out, b_out = sess.run([w, b])

각 에폭마다 average loss 값을 출력하고 모든 학습이 끝나면 w와 b값을 저장한다. 그리고 학습에 소요된 시간과 마지막 loss, w, b 값을 모두 출력한다.

print('Took: %f seconds' %(time.time() - start))
print('last value of loss, w, b: {0}, {1}, {2}'.format(total_loss/n_samples, w_out, b_out))

Control flow: Huber loss

간단한 모델인 만큼 구현도 간단하게 끝났다. 다음 모델로 넘어가기 전에 Loss함수에 대해서 얘기해보자. 여기 모델에서는 일반적인 Square loss 함수를 사용했다. 다른 loss를 사용하려 하는데, tensorflow에서 제공하는 함수가 없어 직접 정의해서 사용해야 한다고 하자. 우리가 사용할 loss함수는 Huber loss로 수식은 다음과 같이 구성된다.

조건에 따라 함수가 달라지는 경우이다. 이럴 경우에는 어떻게 해야 할까, 만약 파이썬 코드로 짠다면, 즉 Pythonic하게 짠다면 if문을 사용해야 할것이다. TensorFlow도 다음과 같은 경우에 사용할 수 있는 함수가 있다. tf.cond함수이다. 함수의 구성은 다음과 같다.

tf.cond(pred, true_fn=None, false_fn=None, ...)

즉 pred 조건식에 따라 true이면 ture_fn함수를 사용, false이면 false_fn함수를 사용한다. 따라서 huber loss는 다음과 같이 tf.cond를 사용해서 정의할 수 있다.

def huber_loss(label, prediction, delta=14.0):
    residual = tf.abs(label - prediction)
    def f1(): return 0.5*tf.square(residual)
    def f2(): return delta*residual-0.5*tf.square(delta)
    return tf.cond(residual < delta, f1,f2)

cond함수는 true, false의 경우로 구분하는데 많은 case로 나누는 경우는 tf.case함수를 사용하면 된다.

tf.data

위의 코드에서는 데이터를 사용할 때 placeholder를 사용했다. 하지만 placeholder는 오래된 방식이고, 이 방식에 대해서는 다양한 의견이 있다. 찬성의 의견은 data 처리를 TF 밖에서 쉽게 할 수 있다는 점이고, 단점은 데이터 처리를 single쓰레드로 처리해야 하고 데이터 병목현상으로 느려진다는 점이다. 따라서 이러한 문제를 해결하기 위한 것이 tf.data이다.

tf.data를 사용하는 방법에 대해서 알아보자.

tf.data.Dataset.from_tensor_slices((feature, labels))

여기서 featurelabels은 Tensor 자료형이여야 한다. 하지만 tensor 자료형은 numpy 자료형과 같으므로 numpy 자료형을 넣어도 된다. 즉 위의 모델에서 데이터를 tf.data로 읽는다면 다음과 같이 작성하면 된다.

data, n_samples = utils.read_birth_life_data(DATA_FILE)

dataset = tf.data.Dataset.from_tensor_slices((data[:,0], data[:,1]))

그리고 tf.data.Dataset에는 데이터 파일을 Tensorflow file format parser로 바로 읽을 수 있는 여러 방법이 있다.

  • tf.data.TextLineDataset(filenames) 파일의 각 줄을 하나의 데이터로 읽는다. 주로 csv파일 읽을 때나 기계번역 분야의 데이터에서 많이 사용된다.
  • tf.data.FixedLengthRecordData(filenames) 고정된 길이의 데이터에서 주로 사용된다. 정해진 길이 만큼 하나의 데이터로 받는다. 자주 사용되는 곳 또한 고정된 길이로 구성된 데이터에서 많이 사용된다. 예를 들면 CIFAR 데이터 혹은 ImageNet 데이터와 같은 것들을 읽을 때 사용한다.
  • tf.data.TFRecordDataset(filenames) tfrecord 형식의 데이터에 사용한다.

데이터를 읽는 방법을 알아봤다. 이제는 데이터를 사용할 때를 살펴보자. 기존의 코드에서는 for문을 통해서 데이터의 값을 하나씩 뽑아서 사용했다. tf.data에서는 iterator를 사용하면 더욱 쉽게 데이터를 하나씩 사용할 수 있다.

iterator = dataset.make_one_shot_interator()
X, Y = iterator.get_netx()

이러한 방식으로 사용하면 된다. dataset.make_one_shot_interator()는 데이터를 하나씩 사용할 떄 사용하는 방식이고 batch방식등 여러 기능을 제공한다. 위와 같이 정의한 data를 session에서 다음과 같이 while문으로 사용하면 된다.

sess.run(iterator.initializer) # initialize the iterator
    total_loss = 0
    try:
        while True:
            _, l = sess.run([optimizer, loss])
            total_loss += l
    except tf.errors.OutOfRangeError:
        pass

tf.data를 사용하는 것은 placeholder를 사용하는 것보다 32.4% 정도 빠르다고 알려져 있다. 따라서 tf.data를 사용하는 것을 추천한다.

Optimizer

Optimizer를 사용하는 방법은 매우 간단하다. 단 몇줄의 코드만으로도 복잡한 구성의(미분, update) optimizer를 쉽게 사용할 수 있다.

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
sess.run([optimizer])

optimizer를 사용하면 미분값 계산, update 등을 자동으로 수행한다. 따라서 관련된 모든 variable에 적용이되는데, 경우에 따라서 update를 안해야하는 variable이 있을 수 있다. 그러한 variable의 경우에는 option으로 trainable=False만 지정해주면 train 되지 않도록 쉽게 설정 가능하다

그리고 위에서 사용한 GD opmizer뿐만 아니라 다른 다양한 optimizer또한 tensorflow 함수로 제공한다. 아래는 optimizer의 리스트이다.

  • tf.train.Optimizer
  • tf.train.GradientDescentOptimizer
  • tf.train.AdadeltaOptimizer
  • tf.train.AdagradOptimizer
  • tf.train.AdagradDAOptimizer
  • tf.train.MomentumOptimizer
  • tf.train.AdamOptimizer
  • tf.train.FtrlOptimizer
  • tf.train.ProximalGradientDescentOptimizer
  • tf.train.ProximalAdagradOptimizer
  • tf.train.RMSPropOptimizer

다양한 optimizer에 대해서 Sebastain Ruder의 블로그에 정리한 글이 있는데 내용의 일부만 인용하면 다음과 같다.

“RMSprop is an extension of Adagrad that deals with its radically diminishing learning rates. It is identical to Adadelta, except that Adadelta uses the RMS of parameter updates in the numerator update rule. Adam, finally, adds bias-correction and momentum to RMSprop. Insofar, RMSprop, Adadelta, and Adam are very similar algorithms that do well in similar circumstances. Kingma et al. [15] show that its bias-correction helps Adam slightly outperform RMSprop towards the end of optimization as gradients become sparser. Insofar, Adam might be the best overall choice.”

내용이 길지만 요약하면 결국 다음과 같다.

“Adam을 사용하자”

logistic Regression with MNIST

이번에는 MNIST 손글씨 데이터를 통해 손글씨 이미지를 보고 숫자를 예측하는 모델을 만들어 보도록 한다. MNIST데이터는 0~9까지의 숫자의 손글씨로 이루어져 있고, 각 이미지는 28x28 pixel로 이루어져 있다. 여기서는 28x28 이미지를 평평하게 1-d tensor(vector)로 만들어서 사용할 것이다. 데이터는 아래와 같은 형태이다.

mnist

이제 모델을 만들어 보자. 우선은 임포트부터 진행한다.

import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'

import numpy as np
import tensorflow as tf
import time

import utils

다음으로는 우리가 사용할 값들을 정의한다.

learning_rate = 0.01
batch_size = 128
n_epochs = 30
n_train = 60000
n_test = 10000

그리고 데이터를 불러온 뒤 tf.data활용해 data를 불러오고, batch사이즈로 나눠주자.

mnist_folder = 'data/mnist'
utils.download_mnist(mnist_folder) # 404 Not Found 에러 발생 시 unit의 download_mnist함수의 url 마지막에 '/' 추가
train, val, test = utils.read_mnist(mnist_folder, flatten=True)

train_data = tf.data.Dataset.from_tensor_slices(train)
train_data = train_data.shuffle(10000) # 선택
train_data = train_data.batch(batch_size)

test_data = tf.data.Dataset.from_tensor_slices(test)
test_data = test_data.batch(batch_size)

그리고 iterator를 만들고 초기화 방법을 정한다.

iterator = tf.data.Iterator.from_structure(train_data.output_types,
                                           train_data.output_shapes)
img, label = iterator.get_next()

train_init = iterator.make_initializer(train_data)	# initializer for train_data
test_init = iterator.make_initializer(test_data)	# initializer for train_data

모델의 파라미터인 w,b 를 생성한다. img 크기에 맞게 shape을 정해준다. 그리고 w는 평균 0, 표준편차 분산 0.01의 정규분포로 초기화하고, b는 0으로 초기화한다.

w = tf.get_variable(name='weight', shape=(784,10), initializer=tf.random_normal_initializer(0,0.01))
b = tf.get_variable(name='bias', shape=(1,10), initializer=tf.zeros_initializer())

logit과 softmax 함수를 정의하고 loss함수를 정의한다.

logits = tf.matmul(img,w) + b

entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=label, name='entropy')
loss = tf.reduce_mean(entropy, name = 'loss')

Optimizer는 Adam optimizer를 사용한다.

optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)

예측하는 연산과, 예측이 맞았는지 확인하는 것과 정확도 계산 연산을 정의한다.

preds = tf.nn.softmax(logits)
correct_preds = tf.equal(tf.argmax(preds, 1), tf.argmax(label, 1))
accuracy = tf.reduce_sum(tf.cast(correct_preds, tf.float32))

이제 Session을 통해 정의한 것들을 실행하자.

writer = tf.summary.FileWriter('./graphs/logreg', tf.get_default_graph())
with tf.Session() as sess:

    start_time = time.time()
    sess.run(tf.global_variables_initializer())

    # train the model n_epochs times
    for i in range(n_epochs):
        sess.run(train_init)	# drawing samples from train_data
        total_loss = 0
        n_batches = 0
        try:
            while True:
                _, l = sess.run([optimizer, loss])
                total_loss += l
                n_batches += 1
        except tf.errors.OutOfRangeError:
            pass
        print('Average loss epoch {0}: {1}'.format(i, total_loss/n_batches))
    print('Total time: {0} seconds'.format(time.time() - start_time))

    # test the model
    sess.run(test_init)			# drawing samples from test_data
    total_correct_preds = 0
    try:
        while True:
            accuracy_batch = sess.run(accuracy)
            total_correct_preds += accuracy_batch
    except tf.errors.OutOfRangeError:
        pass

    print('Accuracy {0}'.format(total_correct_preds/n_test))
writer.close()

참고로 위의 코드중에서 데이터를 suffle하는 것을 선택하도록 했는데, suffle을 안하고 실행시 정확도가 91.34%로 일정했는데, suffle을 하면 88~93%사이의 값으로 정확도가 변했다. 즉 suffle은 경우에따라 좋을 수도 있고 안좋을 수도 있다.

Comments