본문 바로가기
데이터 과학 관련 스터디/모두의 딥러닝

[모두의 딥러닝] 4장 오차 수정하기 : 경사 하강법

by inhovation97 2020. 5. 10.

4장 관련 포스팅입니다.

제목을 보면 알 수 있듯이 경사 하강법은 오차를 수정하는 기법입니다.

대부분의 데이터는 입력 변수가 여러개인 다중 회귀를 이용하며, 다중 회귀는 최소 제곱법을 쓰지 못하기 때문에 경사 하강법은 매우 중요합니다.

경사 하강법은 먼저 기울기 a와 오차간의 관계를 알아야 합니다. 왼쪽에 우리가 3장에서 최소 제곱법을 이용해 그었던 주황색 회귀선이 있습니다. 이 회귀선의 기울기가 커져서 가팔라 진다고 생각해봅시다. 예측값과 실제값과의 오차가 커지겠죠? 기울기가 작아져도 마찬가지일 겁니다. 그렇다면 우리는 이 관계를 좌표 평면위에 함수로 나타낼 수 있어요.

 

 

<단일 회귀 경사 하강법 적용>

위에 그림은 기울기 a와 오차간의 관계를 나타낸 함수입니다. 그림을 보면 알겠지만, a와 오차의 관계함수를 이용해 그 기울기가 가장 작을때의 점 m을 찾는 것이 바로 경사 하강법입니다. 기울기 a와 똑같이 y절편 b도 오차와의 관계가 동일하기 때문에 b도 경사 하강법을 이용해 구합니다.이렇게 왼쪽의 그림처럼 기울기가 점점 작아지는 값을 찾아가 미분값이 0일때인 m을 찾아야 하는데, 기울기의 부호를 바꿔 찾을 때 적절한 거리를 찾지 못하고 너무 멀리 이동하면 오른쪽의 그림처럼 m과의 거리가 더 멀어질 수가 있습니다. 그래서 우리는 '학습률'을 이용합니다. 1보다 작은 학습률을 곱해줘서 화살표가 아래로 향해 m을 효율적으로 찾게끔 해주는 것이지요.

이제 우리가 3장에 최소 제곱법을 통해서 구했던 단순 회귀의 a,b가 정말로 경사 하강법에서도 오차가 가장 작은 값이 었는지를 코딩을 통해 확인해보겠습니다. 먼저 첫 번째 식인 평균 제곱 오차(MSE)식을 구한 뒤 이를 a편미분 b 편미분한 식을 이용해서 코딩을 하겠습니다.

# 공부 시간 x와 성적y의 리스트를 만들기
data = [[2,81],[4,93],[6,91],[8,97]]
x = [i[0] for i in data]
y = [i[1] for i in data]

# 그래프로 나타내기
plt.figure(figsize=(8,5))
plt.scatter(x,y)
plt.show()

# 리스트로 되어 있는 x와y값을 넘파이 배열로 바꾸기(인덱스를 주어 하나씩 불러와 계산이 가능하게 하기 위함)
x_data = np.array(x)
y_data = np.array(y)

# 기울기 a와 절편b의 값 초기화
a = 0
b = 0

# 학습률 정하기
lr = 0.05

# 몇 번 반복될지 설정(0부터 세므로 원하는 수+1)
epochs = 2001

# 경사 하강법 시작
for i in range(epochs):
    y_pred = a*x_data+b
    error = y_data-y_pred
    # 오차 함수를 a,b로 미분한 값
    a_diff = -(1/len(x_data))* sum(x_data* (error))
    b_diff = -(1/len(x_data))* sum(y_data-y_pred)
    # 학습률을 곱해 기존의 a,b값을 각각 업데이트
    a = a-lr * a_diff
    b = b-lr * b_diff
    
    #100번 반복될 때마다 현재 a값, b값을 출력
    if i % 100 == 0:
        print('epochs=%.f, 기울기=%.04f, 절편=%.04f' % (i,a,b))
        
# 앞서 구한 기울기와 절편을 이용해 그래프를 다시 그리기
y_pred=a*x_data+b
plt.scatter(x,y)
plt.plot([min(x_data), max(x_data)],[min(y_pred),max(y_pred)])
plt.show

epochs가 100단위로 반복될 때마다 a=2.3, b=79 로 수렴하는게 보이시죠? 경사 하강법이 최소 제곱법과 마찬가지로 최소의 오차를 찾아서 최선의 회귀선을 그려주는 것임을 알 수 있습니다.

여기까지 단일 회귀에서 경사 하강법을 알아보았습니다. 그럼 이제 더욱 중요한 다중 회귀에서도 적용 해봐야겠지요!

 

<다중회귀 경사 하강법 적용>

공부한 시간 (x1) 2 4 6 8
과외 수업 횟수(x2) 0 4 2 3
성적 (y) 81 93 91 97
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d #3D 그래프 그리는 라이브러리 가져옴

ax = plt.axes(projection='3d') # 그래프 유형 정하기
ax.set_xlabel('study_hours')
ax.set_ylabel('private_class')
ax.set_zlabel('score')
ax.scatter(x1, x2, y)
plt.show

입력 변수가 하나 더 늘어나면서 차원이 1차원 커졌으므로 3D 플롯을 그렸습니다. 이제 경사 하강법을 적용합니다.

# 리스트 배열의 x, y 값을 넘파이 배열로 바꿈
x1_data = np.array(x1)
x2_data = np.array(x2)
y_data = np.array(y)

#기울기 a와 절편 b의 값 초기화
a1=0
a2=0
b=0

lr=0.05  # 학습률

# 몇 번 반복할지 설정 (원하는 수 + 1)
epochs = 2001

# 경사 하강법 시작

for i in range(epochs):
    y_pred = a1 * x1_data + a2 * x2_data + b     # y를 구하는 식 세움
    error = y_data - y_pred
    # 오차 함수를 a1로 미분한 값
    a1_diff = -(1/len(x1_data)) * sum(x1_data * (error))
    # 오차 함수를 a2로 미분한 값
    a2_diff = -(1/len(x2_data)) * sum(x2_data * (error))
    # 오차 함수를 b로 미분한 값
    b_new = -(1/len(x1_data)) * sum(y_data - y_pred)
    a1 = a1 - lr * a1_diff                       # 학습률을 곱해 기존의 기울기, y절편을 업데이트
    a2 = a2 - lr * a2_diff
    b = b - lr * b_diff
    
    if i % 100 == 0:
        print('epoch = %.f, 기울기1 = %.04f, 기울기2 = %.04f, 절편 = %.04f' % (i, a1, a2, b))

이렇게 결과가 도출 됩니다. 왠지 이유는 모르겠지만 책에 나와있는 결과랑 다른 결과가 나왔습니다. 틀린 코드도 없고 가장 중요한 데이터 코드와 업데이트하는 미분 값 코드 모두 똑같은데 틀린 이유는 잘 모르겠네요...

 

아무튼 오늘 경사 하강법 포스팅은 이렇게 마무리 하겠습니다!

댓글