ML01. 선형회귀 (Linear Regression)로 시작하는 머신러닝
공부 시간으로 시험 점수 예측하기
머신러닝을 처음 접하면 가장 먼저 등장하는 개념이 선형회귀(Linear Regression) 입니다.
머신러닝, 딥러닝, AI 분야를 공부하고 싶다면, 선형회귀부터 시작하는 것이 가장 좋습니다.
다른 알고리즘들은 지금 당장 몰라도 괜찮지만, 선형회귀를 이해하지 못하면 이후 개념들을 제대로 이해하기 어렵습니다.
이 글에서는 수학(특히 미적분) 을 깊이 알지 않아도 괜찮습니다.
실제 파이썬 코드를 하나씩 뜯어보면서,
-
선형회귀가 무엇인지
-
왜 이런 식으로 동작하는지
를 직관적으로 설명해보겠습니다.
수학의 필요성은 앞으로 ML공부를 계속하다 보면 자연스럽게 체감하게 됩니다.
1. 문제 설정: 우리가 풀고 싶은 문제
다음과 같은 데이터가 있다고 가정해봅시다.
| 공부 시간 (X) | 시험 점수 (y) |
|---|---|
| 1시간 | 10점 |
| 5시간 | 50점 |
| 10시간 | 100점 |
이 데이터는 이미 주어져 있는 데이터입니다. (지도학습)
이제 우리가 알고 싶은 것은 이것입니다.
“3시간 공부하면 몇 점일까?”
|
| [ 어떻게 기계가 선을 찾도록 할까? ] |
즉,
-
입력 (X): 공부 시간
-
출력 (Y): 시험 점수
를 예측하는 공식(모델) 을 만들고 싶습니다.
네, 위 데이터에 자를 대고 선을 죽 그으면 되지만 그걸 기계에게 수행하도록 하고 싶습니다.
그것이 머신러닝, 딥러닝, A.I 입니다.
2. 선형회귀란 무엇인가?
선형회귀는 직선 하나로 데이터를 설명하려는 방법입니다.
가장 단순한 형태는 다음과 같습니다.
중학교 때 배우는 1차 함수와 완전히
같습니다.
다만 기호만 조금 바꿔서 사용합니다.
-
: 입력값 (공부 시간)
-
: 예측값 (예측 점수)
-
: 가중치 (기울기)
-
: 편향 (절편)
아래 예를 살펴보겠습니다
-
라면 결과는
-
라면 결과는
가중치 는 공부 시간이 1시간 늘어날 때 점수가 얼마나 늘어나는지를 의미합니다.
우리는 인간이니 미리 예측해 봅시다. (사실은 기계에게 시켜야함)
주어진 데이터셋은 1시간 공부하면 10점, 5시간 50점, 10시간 100점 이라고 합니다.
그럼 모델이 f(x) = W * X + b 라면, W=10, b=0이면 딱입니다.
X(3시간) * W(10) + b(0) = 30 점
X(5시간) * W(10) + b(0) = 50 점
X(7시간) * W(10) + b(0) = 70 점
그런데 방금 W=10, b=0은 우리가 예측한 것이고 이걸 기계가 그것도 자동으로 찾도록 해야합니다.
우리가 할 일
우리의 목표는 단 하나입니다.
실제 점수 에 가장 가까운 직선, 즉 가장 좋은 와 를 찾는 것.
(뒤에 나올 비용함수 J를 최소화하는 W,b를 찾는 것)
이 과정을 학습(training) 이라고 부릅니다.
|
| [W,b 를 학습해가는 과정] |
3. 데이터 준비 (입력 X, 정답 y)
import numpy as np import matplotlib.pyplot as plt X = np.array([1, 5, 10]).reshape(-1, 1) y = np.array([10, 50, 100]) plt.scatter(X, y, color='red') plt.plot(X, y) plt.show()
-
X : 공부 시간 (입력값)
-
y : 실제 점수 (정답)
그래프로 그려보면 거의 완벽한 직선임을 알 수 있습니다.
(예시로 만든 간단한 샘플, 처음부터 복잡하면 이해도 ↓)
입력(X)과 정답(y)이 함께 주어지고 이를 기반으로 학습하는 방식을 지도학습(Supervised Learning) 이라고 합니다.
4. 예측값 계산 (모델, 가설 함수)
y_hat = np.dot(X, W) + b
위 코드는 다음 수식을 그대로 옮긴 것입니다.
-
처음에는 보통 , 으로 시작합니다.
-
즉, 아무것도 모르는 상태에서 출발합니다.
5. 얼마나 틀렸는지 계산하기 (비용 함수)
err = np.sum( (y_hat-y)**2 )
위 코드는 아래 오차와 손실을 합쳐 그대로 파이썬코드로 옮긴 것입니다.
오차 (Error)
y hat은 예측치 입니다.
만약 "5시간 공부했는데 넌 0점이 나올거야" 라고 예측하면,
0은 y hat이고, 실제는 50점이므로 0-50= -50 만큼 오차가 발생하고 이게 error 입니다.
예를 들어,
이라면,
-
예측값
-
실제값
손실 함수 (Loss)
오차를 그대로 쓰지 않고 제곱합니다.
음수, 양수 구분없이 양수로 만들고 (오직 차이만 중요) 그 값을 제곱하면 크게 벌줘서 미분에도 유리합니다.
비용 함수 (Cost)
cost = 1/(2*m)*err
모든 데이터에 대한 평균 손실입니다.
Loss와 Cost의 차이는 Loss를 합쳐서 평균한 것을 Cost라고 생각하면 됩니다.
이를 평균제곱오차 (Mean Square Error) 라고 합니다.
참고로 위에서 구한 err은 np.sum( ) 으로 벡터를 스칼라로 바꾼값입니다.
아래 수학식은 뭔가 복잡한데 같은 파이썬코드는 간단합니다.
Cost가 작을수록 좋은 모델입니다.
m은 데이터의 갯수, 평균을 구할때 쓰이니 위 Cost Funnction 수식이 당연하고, 갑자기 분모에 등장한 2는 뒤에 설명할 미분시 비용함수의 수식간단화를 위해 관습적으로 사용합니다. (중요 X)
6. 왜 미분이 필요한가?
우리는 다음 질문에 답해야 합니다.
“를 조금 바꾸면 cost는 증가할까? 감소할까?”
이를 알기 위해 미분(기울기) 을 사용합니다.
7. 기울기 계산 (Gradient)
위에서 구한 비용함수(J) 를 미분해, 비용을 최소화 하는 W, b를 찾습니다.
(비용함수를 W에 대해 미분하면 J를 최소화하는 W를 찾을 수 있습니다.)
dj_dw = np.dot(X.T, err) / m dj_db = np.sum(err) / m
W에 대한 미분
b에 대한 미분
아래 그림은 직접 필기한 비용함수 J를 W, b에 대해 미분하는 과정입니다.
이해가 안되어도 학습 흐름에 크게 중요하지 않습니다.
(미적분학을 공부한 사람은 왜 분모에 2m을 두었는지 이해 가능)
![]() |
| [Cost Function 미분, W, b에 대한] |
이 값들은 어느 방향으로 움직이면 cost가 줄어드는지 알려줍니다.
| [비용함수 미분, Andrew Ng. Cousera] |
8. 경사하강법 (Gradient Descent)
W = W - lr * dj_dw
b = b - lr * dj_db
- dj_dw : 비용함수, J를 W에 대해 미분한 평균가중치
- dj_db : 비용함수, J를 b에 대해 미분한 평균 편향
: 학습률 (learning rate)
-
너무 크면 폭주 (0.001, 0.01, 적당)
-
너무 작으면 느림
이 과정을 수백~수천 번 반복하면 cost는 점점 줄어들고 최적의 직선을 찾게 됩니다.
위 "비용함수 미분" 그림의 우측 그래프가 Cost 함수를 W에 대해 미분하며 경사하강해 minimized W,b 를 찾는 여정입니다.
딥러닝이란?
딥러닝은 이러한 선형 모델을 여러 개 쌓아 ‘레이어(layer)’ 구조로 확장한 것입니다.
각 레이어는,
- 입력값에 대해 선형 변환(가중치 · 입력 + 편향) 을 수행하고
- 그 결과를 비선형 함수(ReLU, Sigmoid 등) 로 한 번 더 변형합니다.
이 과정을 여러 층에 걸쳐 반복하면,
단순한 직선 하나로는 표현할 수 없는 복잡한 곡선, 패턴, 특징(feature) 을 단계적으로 학습할 수 있게 됩니다.
결국 딥러닝은 “선형회귀 + 비선형 함수”를 깊게(deep) 쌓아 올린 모델이라고 볼 수 있습니다.
(다층 퍼셉트론, MLP 이라고 합니다)
이러한 구조에서는 수천 개에서 많게는 수조 개에 달하는 파라미터(가중치, 편향) 에 대해
행렬, 벡터 연산과 미분 계산을 반복적으로 수행해야 합니다.
이처럼 대규모 병렬 연산이 핵심이 되는 작업은 GPU에 특화된 구조를 가진 NVIDIA GPU가 특히 잘 수행할 수 있는 영역이며, 이것이 딥러닝 학습에서 GPU가 필수적인 이유입니다.
9. 학습 과정 확인
for i in range(epochs):
dj_dw, dj_db = compute_gradient(X, y, W, b, Lambda)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
cost = compute_cost(X, y, W, b, Lambda)
print(f'epoch={i}, W={np.round(W, 3)}, b={b:.3f}, cost={cost:.3f}')
return W, b
시간이 지날수록
-
cost 감소
-
W, b 안정화
되는 것을 확인할 수 있습니다.
![]() |
| [epoch 진행, 비용감소] |
10. 최종 예측
# prediction
def predict(X, W, b):
return np.dot(X, W) + b
X_test = np.array([2, 4, 6, 8]).reshape(-1, 1)
y_pred = predict(X_test, W_final, b_final)
for i in range(len(X_test)):
print(f'X_test={X_test[i]}, y={y_pred[i]:.3f}')
이제 모델에게 묻습니다.
“공부 2, 4, 5, 8 시간 하면 몇 점일까?”
학습된 직선이 그 답을 예측해줍니다.
W가 약 10이므로 2시간 공부하면 20점, 4시간 40점으로 잘 학습되었음을 확인가능합니다.
정리
-
선형회귀는 직선 하나를 학습하는 것
-
목표는 오차(cost)를 최소화하는 찾기
-
경사하강법은 조금씩 내려가며 최적점을 찾는 방법
다음 단계
이 글을 이해했다면 다음으로 넘어갈 수 있습니다.
-
로지스틱 회귀 (분류)
-
다중 변수 선형회귀
-
정규화 (L2, Ridge)
아직 헷갈린다면 내용과 코드를 반복해서 다시 보는 것을 추천합니다.
아래는 전체 소스코드입니다.
Linear_Func.py
import numpy as np
def compute_cost(X, y, W, b, Lambda=0):
m, n = X.shape
y_hat = np.dot(X, W) + b
err = np.sum( (y_hat-y)**2 )
cost = 1/(2*m)*err
regular = Lambda/(2*m)*np.sum(W**2)
total_cost = cost + regular
return total_cost
def compute_gradient(X, y, W, b, Lambda=0):
m, n = X.shape
y_hat = np.dot(X, W) + b
err = y_hat - y
dj_dw = np.dot(X.T, err) / m
dj_db = np.sum(err) / m
regular = Lambda/m*W
dj_dw += regular
return dj_dw, dj_db
def gradient_descent(X, y, W, b, lr, epochs, Lambda=0):
for i in range(epochs):
dj_dw, dj_db = compute_gradient(X, y, W, b, Lambda)
W = W - lr * dj_dw
b = b - lr * dj_db
if i%100==0:
cost = compute_cost(X, y, W, b, Lambda)
print(f'epoch={i}, W={np.round(W, 3)}, b={b:.3f}, cost={cost:.3f}')
return W, b
main.py
import numpy as np
import matplotlib.pyplot as plt
# X, 공부시간
X = np.array([1, 5, 10]).reshape(-1, 1)
# y, 실제성적
y = np.array([10, 50, 100])
m, n = X.shape
print(m, n)
plt.xlabel('Learning time')
plt.ylabel('Score')
plt.plot(X, y)
plt.scatter(X, y, color='red')
plt.show()
# init parameters
W = np.zeros(n)
b = 0
lr = 0.001
epochs = 1000
Lambda = 0
# machine learning
from Linear_Func import gradient_descent
W_final, b_final = gradient_descent(X, y, W, b, lr, epochs, Lambda)
print(f'Final w: {np.round(W_final, 3)}, Final b: {b_final:.3f}')
# prediction
def predict(X, W, b):
return np.dot(X, W) + b
X_test = np.array([2, 4, 6, 8]).reshape(-1, 1)
y_pred = predict(X_test, W_final, b_final)
for i in range(len(X_test)):
print(f'X_test={X_test[i]}, y={y_pred[i]:.3f}')
머신러닝에 첫발을 내디딘 것을 축하합니다.




댓글
댓글 쓰기