본문 바로가기
Tensorflow

Simple Linear Regression with Tensorflow 2.0 (2)

by 청양호박이 2020. 1. 26.

지난번에 Simple Linear Regression Analysis(단순선형회귀)에 대해서 알아보았습니다. 그리고 이를 Numpy를 가지고 구현해 보았습니다. 이번에는 Numpy대신 Machine Learning(이하 ML) 패키지인 Tensorflow 2.0으로 구현해 보겠습니다. 

 

혹시 이 글을 처음으로 보시는 분들은 아래 글에서 Simple Linear Regression Analysis에 대한 간략한 정의와 필요한 구성요소에 대한 정보를 확인하고 넘어오시기를 바라겠습니다. 

2020/01/24 - [Tensorflow] - [Tensorflow 2.0] 03. Simple Linear Regression (1)

 

[Tensorflow 2.0] 03. Simple Linear Regression (1)

이번에는 Machine learning의 지도학습(Supervised Learning) 중 하나인 Linear Regression(선형회귀법) 중 하나인 Simple Linear Regression Analysis(단순선형회귀)에 대해서 알아보겠습니다. 간단하게 Linear R..

ayoteralab.tistory.com

Tensorflow로 구현하기 위한 절차는 아래와 같습니다.

 

  • Make dataset from sci-kit learning
  • Set value, hypothesis, loss, gradient
  • Make dataset with mini-batch from tf.data.dataset
  • Training and Predict

 

 

1. Make dataset


사이킷런에서 제공하는 datasets으로 make_regression을 합니다. 비교를 위해서 지난번과 동일한 인자로 생성합니다. 하지만 동일한 분포에서 random으로 생성하니 엄밀하게는 다를 수 있습니다.

(전체 코드를 위한 패키지 import는 여기서 해줍니다.)

 

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression

print(tf.__version__)

X, y = make_regression(n_samples=100, n_features=1, bias=10.0, noise=10.0, random_state=1)
y = np.expand_dims(y, axis=1)

plt.scatter(X, y)
plt.xlabel('X')
plt.ylabel('y')
plt.show()

########################################################################################
2.0.0

100개의 dataset이 생성되었습니다. 그리고 y는 list로 생성이 되기 때문에 n x 1 Matrix로 만들어 줍니다. 이를위해서 Numpy의 expand_dim( ) 메서드를 사용했습니다.

 

그 다음에는 train용 data와 test용 데이터를 나눠줍니다. 

train_x = X[:80]
test_x = X[80:]

train_y = y[:80]
test_y = y[80:]

 

 

2. Set value, hypothesis, loss, gradient


이제 실제적인 tensorflow 코딩이 시작됩니다. 나중에 기회가 될 지는 모르겠지만, tensorflow 1.x 와 tensorflow 2.0 에는 많은 차이가 있어 비교하는 내용을 한번 적어보겠습니다. 하지만 이제는 2.0이 정식버전이 되었으니, 과거는 크게 연연하지 않아도 되겠죠??

 

[Set value]

최종적으로 구하고자 하는 hypothesis에서 변수는 W (weight)와 b (bias)입니다. 이는 tensorflow에서는 variable로 설정해야 합니다. 설정하는 방법은 tf.Variable이며, 안에 최초의 초기값을 설정해 줍니다.

        self.w = tf.Variable(tf.random.uniform([1], dtype=tf.double))
        self.b = tf.Variable(tf.zeros([1], dtype=tf.double))

보통 W는 random으로 생성하고, bias는 0으로 생성합니다. SLRA이기 때문에 각 각 Matrix가 아닌 원소1개로 생성합니다. 그 다음으로는 학습을 반복할 epochs와 learning_rate를 설정합니다.

        self.epochs = 100
        self.learning_rate = 0.01

 

[hypothesis, loss, gradient]

SLRA에서의 hypothesis는

h = W * x + b

loss는

L = (1/n) * (y^ - y) ^ 2

gradient는 

Gradient = (1/n) * (y^ - y) * x 

라고 했었습니다. 이걸 그대로 tensorflow 코드로 옮겨주면 됩니다. 

    def train_batch(self, dataset_batch_x, dataset_batch_y):        
        with tf.GradientTape() as tape:
            hypothesis = dataset_batch_x * self.w + self.b
            loss = tf.reduce_mean(tf.square(hypothesis - dataset_batch_y))
            loss_w, loss_b = tape.gradient(loss, [self.w, self.b])
        self.w.assign_sub(self.learning_rate * loss_w)
        self.b.assign_sub(self.learning_rate * loss_b)
        return loss

여기서 차이점은 tensorflow는 자동 미분을 위한 tf.GradientTape 라는 API를 제공하기 때문에, 이를 사용해서 미분하고자 하는 대상인 loss와 미분 기준인 W, b를 입력해주면 미분 결과를 리턴해 줍니다. 그럼 리턴 받은 결과를 기존에 GDA과 같이....

W new = W old - learning_rate * gradient

 

이것을 적용해주면 되는데, 그게 바로 assign_sub입니다. tf.Variable에 어떤값을 빼서 다시 그 변수에 넣어주는 메서드 라고 생각하면 됩니다.

 

 

3. mini-batch


이번에는 dataset을 일정 단위로 쪼개서 학습을 진행하는 mini-batch에 대해서 알아보겠습니다. tensorflow는 data를 자체 dataset으로 만들고 이를 연산할때 잘 섞어서 원하는 양많큼 넣어주고 학습할 수 있도록 지원해 줍니다.

        dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
        dataset = dataset.shuffle(buffer_size=50).batch(10)

위에서 만들어 놓은 train data 80개를 tf.data.Dataset으로 만드는 방법입니다. 이때, from_tensor_slices() takes 1 positional argument 이기 때문에, 독립변수 셋과 종속변수 셋은 ( ) tuple로 묶어서 넣어줄 수 있도록 합니다. 그리고 mini-batch를 위해서 batch( ) 메서드로 원하는 size를 선택합니다. 이때, 넣어주는 값을 골고루 섞기 위해서 shuffle을 해 주는데, 역시 주의할 점은.... buffer_size는 batch size보다 같거나 커야 합니다.

 

shuffle을 해주는 이유는 설정된 epoch마다 dataset을 섞을 수 있기 때문인데, overfitting을 피하기 위해 중요합니다. 이 부분은 나중에 기회가 되면 다루겠습니다. (under fitting vs over fitting)

    def train(self, train_x, train_y):
        dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
        dataset = dataset.shuffle(buffer_size=50).batch(10)
        
        loss_mem = []
        for e in range(self.epochs):
            for each, (x,y) in enumerate(dataset):
                loss = self.train_batch(x, y)
            print('epoch {0}: loss is {1:.4f}'.format(e, float(loss)))
            loss_mem.append(loss)
        return loss_mem

tf.data.Dataset은 batch size만큼 shuffle로 데이터를 쪼개서 제공을 하는데, (x, y)의 셋으로 보내 줍니다. 따라서 for each 느낌으로 쭉 loop돌려주면서 gradient를 구하고 학습시켜주면 됩니다. 

 

기존과 같이 loss_mem을 통해서 얼마나 loss가 줄어드는지 확인해 보겠습니다.

 

 

4. Training and Predict


model = SLRA()
loss_mem = model.train(train_x, train_y)

x_epoch = list(range(len(loss_mem)))

plt.plot(x_epoch, loss_mem)
plt.title('Loss plot')
plt.xlabel('epochs')
plt.ylabel('Loss status')
plt.show()

SLRA class를 통해서 인스턴스를 생성하고, train 메서드를 train 데이터셋을 넣어서 훈련시킵니다. 그리고 loss의 변화량을 살펴보면...

epochs가 100번까지 가면서, 어느정도 수렴하였기 때문에 정상적으로 학습이 진행됬다고 볼 수 있겠습니다. 

그럼 전체 코드를 확인해 보면서 train된 결과 그래프와, RMSE (Root Mean Square Error)를 확인해 보겠습니다.

class SLRA():
    def __init__(self):
        self.w = tf.Variable(tf.random.uniform([1], dtype=tf.double))
        self.b = tf.Variable(tf.zeros([1], dtype=tf.double))
        self.epochs = 100
        self.learning_rate = 0.01
        print(self.w, self.b)
        
    def train_batch(self, dataset_batch_x, dataset_batch_y):        
        with tf.GradientTape() as tape:
            hypothesis = dataset_batch_x * self.w + self.b
            loss = tf.reduce_mean(tf.square(hypothesis - dataset_batch_y))
            loss_w, loss_b = tape.gradient(loss, [self.w, self.b])
        self.w.assign_sub(self.learning_rate * loss_w)
        self.b.assign_sub(self.learning_rate * loss_b)
        return loss
            
    def train(self, train_x, train_y):
        dataset = tf.data.Dataset.from_tensor_slices((train_x, train_y))
        dataset = dataset.shuffle(buffer_size=50).batch(10)
        
        loss_mem = []
        for e in range(self.epochs):
            for each, (x,y) in enumerate(dataset):
                loss = self.train_batch(x, y)
            #print('epoch {0}: loss is {1:.4f}'.format(e, float(loss)))
            loss_mem.append(loss)
        return loss_mem
            
    def test(self, target_x):
        res = target_x * self.w + self.b
        return res
    
    def predict(self, test_x, test_y):
        y_hat = test_x * self.w + self.b
        error = y_hat - test_y
        mse = np.mean(error * error)
        rmse = np.sqrt(mse)
        return rmse

test를 통해서 W * x + b 그래프와 원본 데이터를 확인해 보면

plt.scatter(train_x, train_y)
plt.plot(train_x, model.test(train_x), '-r')
plt.show()

다음과 같이 정상적으로 학습이 되었으며, RMSE의 경우는....

print(model.predict(test_x, test_y))

####################################
11.034846021989146

로 Numpy로 구현한 결과랑 크게 다르지 않음을 확인할 수 있습니다. 이렇게 SLRA을 마치겠습니다.

 

-Ayotera Lab-

댓글