이 포스트는 Do it! 정직하게 코딩하며 배우는 딥러닝 입문 pp.46~74를 참고하였습니다.
선형 회귀란?
위키 백과에서는 다음과 같이 설명되어 있습니다.
- 통계학에서, 선형 회귀(線型回歸, 영어: linear regression)는 종속 변수 y와 한 개 이상의 독립 변수 (또는 설명 변수) x와의 선형 상관 관계를 모델링하는 회귀분석 기법이다. 한 개의 설명 변수에 기반한 경우에는 단순 선형 회귀, 둘 이상의 설명 변수에 기반한 경우에는 다중 선형 회귀라고 한다
말이 어려운데, 요약하자면 어떤 데이터를 대표할 수 있는 선형 그래프를 찾는 것이라고 보면 됩니다.
다음과 같은 데이터를 보겠습니다.

위와 같은 데이터가 있을 때, 위 데이터를 가장 잘 나타낼 수 있는 1차 함수는 무엇일까요?

우리는 직관적으로 가운데 그래프가 위 데이터를 가장 잘 나타내는 그래프라고 생각할 수 있습니다. 하지만 컴퓨터로 위 그래프를 찾으려면 어떻게 해야할까요? 그리고 만약 데이터가 이렇게 시각화 할 수 있는 2차원,3차원을 넘는 그 이상의 차원의 데이터를 대표하는 직선을 알고싶다면?
선형 회귀의 목표는 이렇게 입력 데이터
나중에 설명할 경사 하강법(gradient descent)는 이런 직선의 방정식을 찾는 방법 중 하나입니다.
데이터 확인하기
이번 챕터에서 우리가 할 일은, 당뇨병 환자의 데이터를 통해서 그 환자의 병이 1년 후 얼마나 진전되었는가를 예측하는 모델을 만드는 것입니다. 따라서 입력 데이터는 환자에 대한 데이터일 것이고, 출력은 병이 얼마나 진전되었는지를 나타내는 값일 것입니다.
다음 코드로 당뇨병 환자에 대한 데이터를 불러오고, 데이터의 구조를 확인해봅니다.(line1은 주피터 노트북에서 matplotlib을 사용하는 경우에만 입력하세요.)
%matplotlib inline from sklearn.datasets import load_diabetes diabetes = load_diabetes() print(diabetes.data.shape, diabetes.target.shape)
위 코드를 통해 diabetes.data은 442x10, diabetes.target은 442x1 의 크기임을 알 수 있습니다.

위 데이터를 그림으로 나타내면 다음과 같습니다.

여기서 행은 샘플이고, 열은 샘플의 특성을 나타냅니다. 즉, 이 데이터에는 당뇨병 환자 442명에 대한 데이터가 들어있습니다. 각 행은 각각의 환자에 대응되고, 각 열은 환자에 대한 특성에 대응됩니다. 특성 중에서는 환자의 혈압, 혈당, 몸무게, 키 등 10개의 특성이 있습니다.
위 데이터 중 앞 부분 3개의 입력 샘플(환자)만 출력해서 확인해보겠습니다.
diabetes.data[0:3]

이번엔 앞 부분 3개의 출력 데이터를 확인해보겠습니다.

첫 번째 입력(샘플)에 대응되는 출력은 151, 두 번째 입력에 대응되는 출력은 75, 세 번째 입력에 대응되는 출력은 141입니다. 즉, 각 환자에 대한 병의 진전도가 151, 75, 141이라는 것입니다.
예측값과 변화율
우리가 과학분야를 공부할 때는 특정한 값을 나타낼 때, 어떤 기호를 쓸지에 대한 약속이 있습니다. 예를 들어 속도는

위 그림의 직선은
컴퓨터는 이런 데이터를 이용하여 어떻게 저런 적절한 방정식을 찾을까요? 컴퓨터는 처음에 무작위로 아무 직선이나 하나 만들어 보고, 모든 데이터들과 하나하나 비교해보며 수정해가면서 오차가 가장 적은 적절한 방정식을 찾습니다.
수십년 전, 컴퓨팅 파워가 그렇게 높지 않았을 때는 이런 방법이 불가능했지만, 지금은 GPU와 CPU의 성능이 매우 발달했기 때문에 어떻게 보면 이런 무식한(?) 방법으로 적절한 예측값을 찾을 수 있는 것입니다.
훈련 데이터
1. 무작위로
2.
3. 예측값
4. 예측값과 실제값의 오차가 줄어들도록
5. 모든 샘플을 처리할 때까지 2~4를 반복합니다.
이제, 위의 방법을 코드를 통해 직접 확인해보겠습니다.
1. 무작위로
이번 예시에서는
w = 1.0 b = 1.0
2.
x = diabetes.data[:, 2] y = diabetes.target y_hat = x[0]*w + b print(y_hat)

3. 예측값
print(y[0])

4. 예측값과 실제값의 오차가 줄어들도록
line 2에서 오차에 x[0]을 곱하는 부분은 일단 지금은 넘어갑니다. 이 부분은 나중에 경사 하강법에서 자세히 설명합니다. 일단 지금은 오차에 비례하여
err = y[0] - y_hat w_new = w + x[0]*err b_new = b + 1*err print(w_new, b_new)

위 결과,
이렇게 조정된 값을 가지고, 다시 예측값과 비교를 해보겠습니다.
y_hat = x[0]*w_new + b_new print(y[0]) print(y_hat)

처음에는 예측값과 실제값이 150정도 차이났는데 이젠 거의 비슷해졌네요. 하지만, 이렇게 첫 번째 샘플만으로 계산한 값을 다른 샘플에도 적용하면 어떻게 될까요?
y_hat = x[1]*w_new + b_new print(y[1]) print(y_hat)

이번엔 75정도로 차이가 꽤 크게 납니다. 따라서,
5. 모든 샘플을 처리할 때까지 2~4를 반복합니다.
이번엔 두 번째 샘플을 사용하여
y_hat = x[1]*w_new + b_new err = y[1] - y_hat w_new = w_new + x[1]*err b_new = b_new + 1*err print(w_new, b_new)

오차가 또 줄었네요. 이런 방식으로 모든 샘플에 대해 적용합니다.
for x_i, y_i in zip(x, y): y_hat = x_i * w + b err = y_i - y_hat w_rate = x_i w = w + w_rate*err b = b + 1*err print(w, b)

모든 샘플에 대해 계산하여 최종적으로 얻어진
이제, 위 값을 통해 얻어진 직선을 그려보겠습니다.

어느정도 데이터를 잘 나타내는 것 같지만, 그렇게 썩 좋아보이지는 않습니다. 이 경우, 위의 모든 과정을 또다시 반복하면 결과가 더 좋아집니다(많이 반복했다고 항상 결과가 더 좋아지는 것은 아닙니다!). 이렇게 전체 훈련 데이터를 모두 이용하여 한 단위의 작업을 진행하는 것을 에포크(epoch)라고 합니다.
for _ in range(1, 100): for x_i, y_i in zip(x, y): y_hat = x_i * w + b err = y_i - y_hat w_rate = x_i w = w + w_rate*err b = b + 1*err print(w, b)

plt.scatter(x, y) pt1 = (-0.1, -0.1*w + b) pt2 = (0.15, 0.15*w + b) plt.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]]) plt.xlabel('x') plt.ylabel('y') plt.show()

100번의 반복으로 얻은
만들어진 모델을 통해 새로운 데이터 예측하기
이제, 기존 환자 데이터에 있던 값 말고 새로운 데이터가 발생했다고 가정하고, 이 데이터의 예측 값을 확인해봅니다.
x_new = 0.18 y_pred = x_new*w + b print(y_pred) plt.scatter(x, y) plt.scatter(x_new, y_pred) plt.xlabel('x') plt.ylabel('y') plt.show()


손실 함수와 경사 하강법
경사 하강법을 기술적인 말로 하면, "어떤 손실 함수(loss function)가 정의되었을 때, 손실 함수의 값이 최소가 되는 지점을 찾아가는 방법" 입니다. 비용 함수(cost function) 또는 목적 함수(object function)라고도 하는데, 같은 의미로 보시면 됩니다. 아무튼, 중요한 점은 예측값을 원본 데이터와 비교했을 때 그 오차를 손실 또는 비용 함수로 나타내며, 이 값이 가장 작아지는 지점의 값을 찾는 것입니다.
지금까지 설명한 방법에서는 "제곱 오차"라는 손실 함수를 사용했었습니다. 이는 타깃 값에서 예측 값을 빼고, 이를 제곱한 것입니다.
위의 제곱 오차는

제곱 오차 함수의 최솟값을 구하기 위해서는, 제곱 오차 함수의 기울기를 따라서 함수의 값이 낮은 쪽으로 이동하면 됩니다.

위와 같은 그래프를 생각해보겠습니다. 만약 기울기가 음수라면, 즉
따라서, 기울기가 음수인 경우에는
그러면 ,이제
만약 여기서 제곱 오차의 계수가
앞에서 언급했듯이 미분된 값을
이번에는 제곱 오차 함수를 같은 방식으로
따라서, 위의 식들은 다음 코드에 대응됩니다.
y_hat = x_i * w + b err = y_i - y_hat w_rate = x_i w = w + w_rate*err b = b + 1*err
인공지능 분야에서는 변화율 대신 그레이디언트(gradient)라는 용어를 사용합니다.
선형 회귀를 위한 뉴런(유닛)의 구현
신경망 알고리즘에서는 정방향 계산(forpass)와 역방향 계산(backprop)이 있습니다. 정방향 계산이란, 이전에 설명한 것처럼
정방향 계산을 그림으로 나타내면 다음과 같습니다.

역방향 계산의 그림은 다음과 같습니다.

이제, 위 내용들을 파이썬 코드로 작성해보겠습니다.
Neuron 클래스의 전체 코드는 다음과 같습니다.
class Neuron: def __init__(self): self.w = 1.0 self.b = 1.0 def forpass(self, x): y_hat = x*self.w + self.b return y_hat def backprop(self, x, err): w_grad = -err*x b_grad = -err*1 return w_grad, b_grad def fit(self, x, y, epochs=100): for i in range(epochs): for x_i, y_i in zip(x, y): y_hat = self.forpass(x_i) err = y_i - y_hat w_grad, b_grad = self.backprop(x_i, err) self.w -= w_grad self.b -= b_grad
1. __init__() 메서드 작성하기
class Neuron: def __init__(self): self.w = 1.0 self.b = 1.0
Neuron 클래스를 만들 때,
2. 정방향 계산 만들기
def forpass(self, x): y_hat = x*self.w + self.b return y_hat
정방향 계산 식인
3. 역방향 계산 만들기
def backprop(self, x, err): w_grad = -err*x b_grad = -err*1 return w_grad, b_grad
편미분을 통해 얻은 기울기 계산을 구현한 부분입니다.
4. 훈련을 위한 fit() 메서드 구현하기
def fit(self, x, y, epochs=100): for i in range(epochs): for x_i, y_i in zip(x, y): y_hat = self.forpass(x_i) err = y_i - y_hat w_grad, b_grad = self.backprop(x_i, err) self.w -= w_grad self.b -= b_grad
forpass()를 호출하여 예측값을 구하고, 그 후 오차를 계산합니다. 이렇게 계산한 오차를 통해 역전파로 가중치
5. 모델을 훈련하고, 훈련 결과 확인하기
neuron = Neuron() neuron.fit(x, y) plt.scatter(x, y) pt1 = (-0.1, -0.1*neuron.w + neuron.b) pt2 = (0.15, 0.15*neuron.w + neuron.b) plt.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]]) plt.xlabel('x') plt.ylabel('y') plt.show()

'Study > MachineLearning' 카테고리의 다른 글
[Do it!] 6. 이미지 분류하기 - 합성곱 신경망 (0) | 2021.09.07 |
---|---|
[Do it!] 5. 다중 분류 (0) | 2021.09.06 |
[Do it!] 4. 2개의 층 연결하기 - 다층 신경망 (0) | 2021.09.06 |
[Do it!] 3. 훈련 노하우 배우기 (0) | 2021.09.03 |
[Do it!] 2. 분류하는 뉴런 만들기 - 이진 분류 (0) | 2021.09.03 |