책이 '청소년을 위한 000' 마냥 말투도 친절하고 내용도 친절해서 읽는 재미가 있다. 책이 어려우면 스릴 있고 쏙쏙 들어오면 재미가 있는 법이다. 물론.. 재밌는 걸 더 열심히 읽는다ㅎ 스릴 있는 건 힘 빨려서 쭉쭉 못 읽음..
<오늘의 주제>
3장 머신러닝의 기초를 다집시다~
선형 회귀 : 입력에 대한 모델 함수가 선형인 머신러닝 알고리즘.
기울기 w, 절편 b, 입력 x에 대하여 타깃 y는
y = wx+b
로 나타낼 수 있다.
우리가 일반적으로 수학에서 푸는 1차 함수 문제는 w, b가 결정되어있는 함수에서 x 값에 대한 y값을 구하는 문제이다. 반면, 선형 회귀는 x, y가 주어진 상황에서 w, b의 값을 구하는 데에 집중한다. w, b를 찾은 함수 y = wx+b를 모델이라고 하며, 우리는 선형 회귀 모델로 새로운 x값에 대한 y를 예측할 수 있다.
그러면 선형 회귀 알고리즘으로 풀 수 있는 문제의 데이터를 직접 가져와서 살펴보자. 당뇨병 환자 데이터셋은 사이킷런에서 제공하는 데이터셋이다.
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
print(diabetes.data.shape, diabetes.target.shape) # .shape : 배열의 크기 확인
# 출력 (442, 10) (442,)
# -> 입력 데이터는 442x10 크기의 2차원 배열, 타깃은 442개의 요소를 가진 1차원 배열
입력 데이터의 442개의 행은 442개의 샘플(환자)이 준비되어있다는 뜻이며, 10개의 열은 각 샘플에 샘플의 특성 10가지(환자의 키, 혈압 등)가 포함되어있다는 뜻이다. 이러한 특성(feature)을 독립 변수, 속성이라고도 한다.
이 책에서 선형 회귀 알고리즘을 공부하기 위해 사용하는 특성은 10가지 특성 중 세 번째 특성이다. 세 번째 특성과 타깃 변수와의 관계가 선형인지 직접 출력해보자.
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
import matplotlib.pyplot as plt
x = diabetes.data[:, 2] //입력 x는 442개 전체 샘플(:)중 3번째(인덱스 2) 특성
y = diabetes.target //y는 타깃
plt.scatter(x, y) //산점도 출력
plt.xlabel('x')
plt.ylabel('y')
plt.show()
오호 선형이다.
굳이 특성 중 하나만 골라 출력해본 이유는 10가지 특성을 모두 표현한 11차원 그래프를 그릴 수 없기도 하고, 한두 가지만 출력한 2~3차원 그래프가 이해하기도 쉽기 때문이다.
경사 하강법은 기울기를 사용해 모델을 조금씩 수정해가며 최적의 모델을 찾는 최적화 알고리즘이다. 물론 선형 회귀를 푸는 알고리즘은 경사 하강법 외에도 정규 방정식, 결정 트리 등 다양하다.
1.1)에서 선형 회귀 모델은
y = wx+b
의 형태라고 하였다. 하지만 지금은 수정 과정에 있는(학습 중인) 모델을 살펴볼 것이므로 이를
ŷ = wx+b
라고 하자. ŷ 은 모델의 예측값이고, y는 타깃이다. 경사하강법에서는 ŷ가 y에 가까워지도록 모델을 조정한다.
그런데 어떻게 조정할까? 우선 w와 b를 각각 증가시켰을 때 ŷ가 증가하는지 감소하는지를 확인한다. 예를 들어,
- w, b를 임의로 초기화한다. (예를 들어 1.0)
ŷ = 1x+1
에 첫 번째 샘플 x[0]을 넣어 ŷ 를 계산한다.- 타깃과 ŷ 를 비교한다.
- w값과 b값을 각각 조금 변경해 ŷ 가 증가하는지 감소하는지 살핀다. (예를 들어 0.1 씩)
w_inc = w + 0.1 ŷ_inc = x[0] * w_inc + b
- 예측값이 얼마나 증가(감소)했는지 변화율을 계산한다.
w_rate = (ŷ_inc - ŷ) / (w_inc - w) b_rate = (ŷ_inc - ŷ) / (b_inc - b)
이때 5. 의 변화율을 x[0]에 대한 w, b의 변화율이라고 한다.
경사 하강법에서는 이 변화율을 4.에서 w, b값을 조금씩 조정할 때 사용한다. 오잉? 특이한가? 하지만 계속 공부하다보면 나름 합리적일 것이다. (예전에서 교양에서 변화율을 더한다고 해서 대체 왜요..? 하고 이해가 안 갔었는데 이젠 이해할 수 있다..)
- 만약 변화율이 양수이고 ŷ이 y보다 작으면 w의 변화율을 더한다. 변화율이 양수이므로 w에 변화율을 더하면 ŷ도 증가해 y에 더 가까워진다.
- 만약 변화율이 음수이고 ŷ이 y보다 작으면 w의 변화율을 더한다. 변화율이 음수이므로 w에 변화율을 더하면 w가 감소하고, w와 ŷ은 반대로 작용하므로 ŷ는 증가해 y에 더 가까워진다.
따라서 w의 변화율이 음수이든, 양수이든 변화율을 더해 업데이트한다는 방법이 똑같으므로 방법을 하나로 통일할 수 있어 매우 합리적이다. 우오앙 너무 쉬워
하지만 이 방법은 ŷ가 y보다 작을 때에만 사용할 수 있다는 단점이 있다. 그리고 변화율만 곱해서는 ŷ가 y보다 한참 작을 때 큰 폭으로 수정하지 못한다는 단점도 있다. 따라서 우리에겐 더 좋은 방법이 필요하다.
오차 역전파 : ŷ와 y의 차이(오차)를 이용해 w와 b를 업데이트 함
오차와 변화율을 곱한 값만큼 업데이트를 하면 ŷ와 y의 차이가 클 때, ŷ가 y보다 커서 ŷ를 감소시켜야할 때에도 사용할 수 있다.
오차와 x[0]일 때의 변화율을 곱해 업데이트를 한다
err = y[0] - ŷ
w_new = w + w_rate * err
b_new = b + 1 * err
두 번째 샘플 x[1]에서의 오차로 변화율을 새롭게 업데이트 한다
ŷ = x[1] * w_new + b_new
err = y[1] - ŷ
w_rate = x[1] //
w_new += w_rate * err
b_new += b_rate * err
전체 샘플에 대하여 반복한다
for xi, yi in zip(x, y) : #zip 함수 : 여러 배열에서 동시에 하나씩 요소를 꺼내주는 함수
ŷ = xi * w + b
err = yi - ŷ
w_rate = xi
w += w_rate * err
b += 1 * err
여러 epoch를 반복한다.
epoch도 교양에서 나올 때 대체 얘가 뭔데 반복하지..? 하고 너무너무 궁금했었는데 (왜 안 찾아봤냐면.. 안 찾아본 게 아니고 찾아보고도 이해를 못 했다) 드디어 나왔다-!
epoch는 전체 훈련 데이터를 사용해 한 단위의 작업을 진행하는 것을 의미한다. 경사하강법은 epoch를 여러 번 반복하여 최적화 모델을 찾는다.
모델로 예측하기
입력 x에 없던 새로운 데이터가 발생하면 모델에 x를 입력하기만 하면 된다.
+) 한 가지 빼먹은 것이 있어 보충 설명!!
2, 3에서 왜 w의 변화율을 xi (x[1])로 업데이트하느냐를 빼먹었다. 그건 별 게 아니고 w의 변화율을 구하는 식을 써보면 그 값이 결국 이전 x의 샘플 값이기 때문이다.
경작 16일차 1~2장 정리에서 손실 함수에 대해 이렇게 써놨었다.
손실 함수 : 학습 과정에서 모델의 예측값과 타깃값이 얼마나 다른지를 계산하는 함수이다. 즉, 모델이 찾은 규칙(가중치와 절편)을 얼마나 수정해야 하느냐는 것이다.
따라서 경사하강법은 손실 함수의 값이 최소가 되는 지점을 찾아가는 방법이라고도 할 수 있다. ŷ과 y의 차이가 최소가 될 때까지 가중치와 절편을 수정해 모델을 찾기 때문이다.
좀 더 구체적으로 둘의 관계를 말하자면, 2.3)에서 살펴본 방법(오차에 변화율을 곱해 업데이트)은 제곱 오차라는 손실 함수를 미분하는 것과 같다. 음? 그럼 제곱오차란 뭐냐?
제곱 오차는 타깃값과 예측값의 차이를 제곱한 것이다.
SE = (y - ŷ)^2
제곱 오차가 최소가 되면 산점도 그래프를 가장 잘 표현하는 직선을 그릴 수 있다. 그러면 제곱 오차의 최솟값은 또 어떻게 구하느냐?
w에 대하여 SE를 미분
SE를 (y - ŷ)^2 대신 1/2 * (y - ŷ)^2 로 하면 깔끔하게 -(y-ŷ)x로 떨어져서 SE를 2로 나눈 함수를 편미분 하는 경우가 대부분이다.
하여튼 w에 대한 SE의 변화율을 구했으니 이제 w를 업데이트해보자. 손실함수의 낮은 쪽으로 이동해야 하므로 w에 변화율을 더하지 않고 빼는 방법을 이용한다.
그런데 이 식..어디에서 본 것 같지 않은가? 2. 3)에서 변화율을 직접 구해서 더할 때
err = y - ŷ
w = w + w_rate * err
에서 봤다. 오 대박 이렇게 손실함수와 경사하강법의 관계에 대해서 이해하게 되었다. 이는 b에 대해 제곱 오차를 미분했을 때에도 마찬가지이다.
b에 대하여 SE를 미분
이 식도
err = y - ŷ
b = b + 1 * err
와 유사하다.
그렇기 때문에 실제로는 2.2)와 2.3)처럼 변화율을 구하는 방법보다 편미분을 사용해 변화율을 구하게 된다. 또한, 변화율은 인공지능 분야에서 gradient라고 불린다. 교양 들을 때 gradient 그레디언트 하실 때마다 가끔 헛갈렸는데 더 이상 헛갈릴 일도 없겠다.. 성장하는 내 모습? 상당히 즐거워요~
import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
x = diabetes.data[:, 2]
y = diabetes.target
class Unit:
def __init__(self):
self.w = 1.0 #가중치와 절편의 시작값
self.b = 1.0
def forpass(self, x): #ŷ을 구하는 1차 함수 메소드 (정방향 계산)
y_hat = x * self.w + self.b
return y_hat
def backprop(self, x, err): #가중치와 절편에 대한 그레디언트를 계산하는 메소드 (역방향 계산)
w_grad = x * err
b_grad = 1 * err
return w_grad, b_grad
def fit(self, x, y, epochs = 100): #훈련 메소드
for i in range(epochs): #100 에포크 반복
for xi, yi in zip(x, y): #모든 샘플에 대하여
y_hat = self.forpass(xi) #예측값 구하기
err = -(yi - y_hat) #오차 구하기
w_grad, b_grad = self.backprop(xi, err) #그레디언트 구하기
self.w -= w_grad #w 업데이트
self.b -= b_grad #b 업데이트
unit = Unit()
unit.fit(x, y)
plt.scatter(x, y) #샘플의 데이터와 타깃을 산점도 그래프로 그리기
pt1 = (-0.1, -0.1 * unit.w + unit.b) #(x, y) = (입력값, 예측값)
pt2 = (0.15, 0.15 * unit.w + unit.b) #(x2, y2) = (입력값2, 예측값2)
plt.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]]) #(x,y)와 (x2, y2)로 선형 함수 그리기
plt.xlabel('x')
plt.ylabel('y')
plt.show()
를 돌리면?
짜란~
역방향과 정방향은 별 게 아니고 유닛에서 계산에 들어가는 신호와 나오는 신호가 흐르는 방향이다.
끝!
출처 : 정직하게 코딩하며 배우는 딥러닝 입문 Do it!