[AI] day3. 머신러닝 알고리즘 - 선형 회귀 분석 (Linear Regression Analysis)



1. 선형 회귀 (Linear Rgression)



1) 회귀 분석 (regression Analysis)


관찰된 연속형 변수들에 대해 두 변수 사이의 모형을 구한 뒤 적합도를 특정해 내는 분석 방법. 데이터들이 어떤 특성을 가지고 있는지 파악하고 경향성 및 의존성을 수식으로 작성하여 앞으로 발생할 일을 예측하는 분석 방법이다. 말이 좀 어려운데, 쉽게 말해서 어떤 데이터를 보고 다른 데이터랑 비교해서 이 데이터가 어떤 특성을 가지는지 예상하는 것이다.

종속 변수와 독립 변수가 1 : 1 상태면 단순 회귀분석 (Simple Regression Analysis), 종속 변수와 독립 변수가 1 : 다 상태이면 다중 회귀분석 (Multiple Regression Analysis) 라고 한다.




2) 단순 선형 회귀 (Single Variable Linear Regression)


한개의 종속 변수 y와 한개 이상의 독립 변수 x와의 선형 상관 관계를 이루는 모델을 선형 회귀라고 하며, 여기서 y : x = 1 : 1 인 관계를 단순 선형 회귀라고 한다. 아래의 코드를 작성해보자.
import tensorflow as tf
from matplotlib import pyplot as plt
import  numpy as np

TrainDataNumer = 100
learningRate = 0.01
totalStep = 1001


np.random.seed(321)

xTRainData = list()
yTRainData = list()

xTRainData = np.random.normal(0.0, 1.0, size=TrainDataNumer)

for x in xTRainData:
    y = 10 * x + 3 + np.random.normal(0.0, 3)
    yTRainData.append(y)

plt.plot(xTRainData, yTRainData, 'bo')
plt.title("Train Data")
plt.show()


  • 학습 데이터의 수(TrainDataNumer)를 100개로 정하고, 학습률(learningRate)을 0.01로, 학습 횟수(totalStep)를 1001로 설정하고, numpy를 이용해 난수를 생성해준다. 
  • seed값에 대해 설명하자면, 어떤 특정 숫자를 설정해 놓을 때, 컴퓨터가 특정 알고리즘을 이용하여 그 수를 기점으로 마치 난수처럼 보이는 수열을 형성하는 것을 seed라고 한다.
  • 기본 세팅 후에 np.random.normal(정규분포)을 이용해 x 학습 데이터를 생성해준다. 이 때 np.random.normal(평균, 표준편차, 난수의 개수) 이다. 즉, 평균 0, 표준편차 1인 100개의 난수를 생성하는 것이다.
  • 학습 데이터 x를 이용해서 선형 데이터를 생성해주자. y는 x에 관해 10 * x + 3의 선형 관계를 이루게 만들어 준다. 따라서 xTrainData list를 돌면서 10 * x + 3의 선형 관계를 띄는 난수를 생성해서 y list에 집어 넣는 것이다.
  • matplotlib는 표를 그려주는 라이브러리인데, https://matplotlib.org/users/installing.html 여기를 참고해서 다운로드 할 수 있다.


위의 코드를 실행한 결과 아래와 같은 표가 나온다.


표를 보면 x 와 y가 완벽한 선형은 아니더라도, 선형의 특성을 이루게 되는데, 이처럼 직선의 특징을 가지고 있는 것을 선형, 직선의 특징을 가지고 있지 않은 것을 비선형이라고 한다. 선 모양이라고 선형, 곡선 모양이라고 비선형이라고 하면 잘못된 표현이다.

이번엔 모델을 생성하기 위한 변수들을 초기화 해보자.



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

# <step 1>
TrainDataNumer = 100
learningRate = 0.01
totalStep = 1001


np.random.seed(321)

xTRainData = list()
yTRainData = list()

xTRainData = np.random.normal(0.0, 1.0, size=TrainDataNumer)

for x in xTRainData:
    y = 10 * x + 3 + np.random.normal(0.0, 3)
    yTRainData.append(y)


# <step 2>
W = tf.Variable(tf.random_uniform([1]))
b = tf.Variable(tf.random_uniform([1]))

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

plt.plot(xTRainData, yTRainData, 'bo')
plt.title("Train Data")
plt.show()

  • W(Weight)과 b(bias) 변수의 최적화된 값을 찾기 위해 random한 값을 넣어준다. 이 변수 초기화 값에 의해 성능 차이가 발생할 수 있다.
  • tf.random_uniform(shape, min, max) 이므로 1차원 행렬의 난수를 생성한다.
  • 여기서 변수에 난수를 생성해주는데, 머신러닝의 의의는 시작지점이 달라도 결국 비용이 최소가 되는 지점으로 이동하는 것이다. 즉 난수를 생성해서 어떤 값이 나와도 결국은 cost가 최소가 된다는 것이다.
  • x, y는 각각 XTrainData와 YTrainData가 들어갈 플레이스홀더이다.


다음으로 학습 모델 그래프를 구성해보자.


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

# <step 1>
TrainDataNumer = 100
learningRate = 0.01
totalStep = 1001


np.random.seed(321)

xTRainData = list()
yTRainData = list()

xTRainData = np.random.normal(0.0, 1.0, size=TrainDataNumer)

for x in xTRainData:
    y = 10 * x + 3 + np.random.normal(0.0, 3)
    yTRainData.append(y)


# <step 2>
W = tf.Variable(tf.random_uniform([1]))
b = tf.Variable(tf.random_uniform([1]))

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

# <step 3>

#가설 그래프는 작성자가 원하는 방법으로 선언해주면 된다.
hypo = W * X + b
# 또는 텐서플로우의 기능을 이용하여
# hypo = tf.add(tf.multiply(W,x), b)
# 라고 작성할 수도 있다.

costFunc = tf.reduce_mean(tf.square(hypo - Y))

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learningRate)
train = optimizer.minimize(costFunc)

plt.plot(xTRainData, yTRainData, 'bo')
plt.title("Train Data")
plt.show()

  • 일단 가설 그래프 작성은 텐서플로우의 기본 기능을 이용해도 되고, 직접 그래프를 작성해도된다.
  • reduce_mean() 함수는 특정 차원을 제거하고 평균을 구하는 함수이다. 
  • tf.square()는 제곱을 계산하는 함수다.
이게 무슨 의미냐, 일단 아래의 개념을 먼저 보도록 하자.

- 평균 제곱 오차 (Mean Square Error)



말 그대로 예측값과 실제 결과값의 오차에 제곱을 취해준 것의 평균으로 최소 오차값을 찾는 방법이다. 비용은 값들의 오차를 통해 최소 오차를 찾아서 비용이 최소가 되는 것이 가장 최적인 경우이므로, 모든 데이터 간의 오차를 구해준 것이다.

평균 제곱 오차 MSE 의 식은 아래와 같다.


이 때 제곱을 취해준 이유는, 오차 중에 음이 되는 경우가 존재하기 때문에 음의 값을 양의 값으로 치환해서 정확한 편차의 합을 구할 수 있으며, 게다가 오차의 제곱은 제곱하긴 전의 값보다 더 큰 값이 나오기 때문에 오차가 더 작은 데이터보다 패널티가 더 크게 작용하여 더 신뢰할 수 있는 값을 구할 수 있다.

따라서 비용함수 costFunc은 가설함수 - y (h(xi) - yi) ^ 2에 해당) 에 tf.square (제곱 연산)을 한 후 reduce_mean으로 평균을 구해준 평균 제곱 오차 함수이다.




이번에는 optimizer, 최적화 함수에 대한 얘기를 해보자. 최적화 함수는 학습률에 대해 GradientDescentOptimizer라는 함수를 이용하고 있다. 이를 위해 다시 경사하강법에 대해 알아봐야한다.

-경사하강법 (Gradient Descent Algorithm)


경사하강법은 최적화하는 값들을 θ (여기서는 W와 b, 즉 weight와 bias다.) 라고 할 때, 함수 f(θ) 에 대하여 최적화를 할 때 이 함수의 기울기를 기울기가 낮은 쪽으로 일정 크기만큼 계속 이동해서 최솟값을 찾는 것이다. 우리는 optimizer 함수에 learning_rate 를 0.01만큼 주었는데, 즉 기울기를 0.01만큼 계속 이동시키면서 최솟값을 찾는다는 것을 의미하게 된다. 

θ 의 변화식은 아래와 같다.
일단 가설함수 H(x)와 비용함수 cost(θ) 가 아래와 같다고 할 때,



이를 위의 변화식에 대입한다.



비용함수를 편미분을 하면 최적화 알고리즘의 경사를 구하는 수식을 구할 수 있게 된다.


이것이 우리가 수학을 해야하는 이유이다! 인공지능 관련 종사자가 되려면 선형대수학은 물론 미적분에 대해서도 잘 알아야 한다.
그림으로 표현한다면 아래와 같은 그래프가 나온다. 이 때 맨 왼쪽의 기울기가 초기값이며, 기울기가 x축에 대해 평행한 것이 기울기가 최소인 값, 즉 cost(w)의 최솟값이 되겠다.

이를 통해 하나 생각해볼 점이 생긴다. learning_rate는 기울기를 이동하는 요인인데, 이 값에 비례해서 이동량이 증가할 때, 어떻게 보면 learning_rate가 더 작아야 최솟값에 도달하는데에 최적화가 될 수도 있다고 생각할 수 있다. 가령 0.3 -> 0.2 -> 0.1 ... 으로 이동하는 것보단 0.3 -> 0.29 -> 0.28... 이런식으로 이동했을 때 어떤 지점이 가장 작을지에 대해선 후자가 더 정확하다는 것이다.
요컨데 학습률이 작을수록 학습량이 너무 많아져 학습 시간이 길어진다는 것이다. 그렇다면 크게 설정해야하냐? 라고 한다면, 역시 그것도 아니다. 학습량이 커지게 되면 최솟값을 찾기전에 기울기가 증가하는 방향으로 이동하여 (이차함수의 오른쪽부분으로 이동) 오히려 발산하는 Over Shooting 문제가 발생할 수 있다. 즉 최솟값을 찾지 못하고 학습이 끝나버린다는 이야기다. 따라서 learning_rate를 적절한 수치로 조정해주는 것이 정확도를 높일 수 있는 요인이 되겠다.

이제 다음 코드를 추가 작성해보자.


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

# <step 1>
TrainDataNumer = 100
learningRate = 0.01
totalStep = 1001


np.random.seed(321)

xTRainData = list()
yTRainData = list()

xTRainData = np.random.normal(0.0, 1.0, size=TrainDataNumer)

for x in xTRainData:
    y = 10 * x + 3 + np.random.normal(0.0, 3)
    yTRainData.append(y)


# <step 2>
W = tf.Variable(tf.random_uniform([1]))
b = tf.Variable(tf.random_uniform([1]))

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

# <step 3>

#가설 그래프는 작성자가 원하는 방법으로 선언해주면 된다.
hypo = W * X + b
# 또는 텐서플로우의 기능을 이용하여
# hypo = tf.add(tf.multiply(W,x), b)
# 라고 작성할 수도 있다.

costFunc = tf.reduce_mean(tf.square(hypo - Y))

optimizer = tf.train.GradientDescentOptimizer(learning_rate=learningRate)
train = optimizer.minimize(costFunc)

# <step 4> implementation
sess = tf.Session()
sess.run(tf.global_variables_initializer())

WeightValueList = list()
costFunctionValueList = list()

print("-----------------------------------------------------------------------------------------")
print("Train(Optimization) Start")

for step in range(totalStep):
    cost_val, W_val, b_val, _ = sess.run([costFunc, W, b, train], feed_dict={X: xTRainData, Y: yTRainData})

    WeightValueList.append(W_val)
    costFunctionValueList.append(cost_val)

    if(step % 50 == 0):
        print("Step : {}, cost : {}, W : {}. b : {}".format(step, cost_val, W_val, b_val))

    if step % 100 == 0:
        plt.plot(xTRainData, W_val * xTRainData + b_val, label = 'Step : {}'.format(step), linewidth=0.5)

print("Train Finished")
print("-----------------------------------------------------------------------------------------")
print("[Train Result]")
cost_train = sess.run(costFunc, feed_dict={X : xTRainData, Y: yTRainData})
w_train = sess.run(W)
b_train = sess.run(b)

print("Train cost : {}, W : {}, b : {}".format(cost_train, w_train, b_train))
print("-----------------------------------------------------------------------------------------")
print("[Test Result]")
testXValue = [2.5]
resultYValue = sess.run(hypo, feed_dict={X : testXValue})

print("x value is {}, y value is {}".format(testXValue, resultYValue))
print("-----------------------------------------------------------------------------------------")

plt.plot(xTRainData, sess.run(W) * xTRainData + sess.run(b), 'r', label='Fitting Line', linewidth=2)
plt.plot(xTRainData, yTRainData, 'bo', label = 'Train data')
plt.legend()
plt.title("Train Result")
plt.show()

plt.plot(WeightValueList,costFunctionValueList)
plt.title("costFunction curve")
plt.xlabel("Weight")
plt.ylabel("costFunction value")
plt.show()

sess.close()

1. 세션을 세팅한다.
2. 앞에서 선언한 W, b 의 변수에 대해 초기화 해준다.
3. 가중치와 비용함수를 담기 위한 리스트를 생성한다.
4. cost 함수와 W, b, train에 대해 session으로 돌리고 각각 cost_val, W_val, b_val에 저장해준다.
5. cost_val과 W_val은 각각 생성했던 리스트에 저장해준다.
6. 50회마다 중간 연산 출력
7. 100회마다 plt로 그래프 출력

-- 여기까지가 학습을 위한 과정이다. 학습이 끝나면 최적화된 W, b의 값이 업데이트 되고 cost 함수가 계산이 끝난다. cost_train, W_train, b_train에 각각 업데이트된 값을 넣어주자.
그 후 테스트 값인 2.5를 넣어서 session을 돌려 결과 값을 확인해본다.

위 과정의 출력과 그래프는 각각 아래와 같이 나온다.
(1) 학습 중간 결과

(2) 학습 결과 그래프
(3) 비용함수


그래프를 간단하게 분석해보면 step 0에서는 결과 예측이 엉망이다. Fitting Line과 비슷하기는 망정 오히려 수평에 가까운 결과가 나온다. 하지만 Step 100부터 조금씩 기울기가 비슷해지더니 점점 기울기가 일치해가는 모양으로 나온다. 이처럼 머신러닝은 수많은 데이터를 분석해 나가면서 결과에 근접하게 예측해나가는 것이다.

댓글