[Python 100일 챌린지] Day 94 - 선형 회귀 모델
드디어 첫 회귀 모델! 선형 회귀는 머신러닝의 가장 기본이자 가장 중요한 알고리즘입니다. 집값, 주가, 매출… 숫자를 예측하는 모든 곳에 사용되죠. 오늘은 공부 시간으로 시험 점수를 예측해봅니다!
(30분 완독 ⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- Day 91: 머신러닝이란?
- Day 92: scikit-learn 설치
- Day 93: 데이터 전처리 기초
- 중학교 수학 (일차함수 y = ax + b)
🎯 학습 목표 1: 선형 회귀의 원리 이해하기
💡 걱정 마세요! 복잡한 수식은 모두 건너뜁니다. 중학교 때 배운 일차함수
y = ax + b만 기억하면 충분해요. 천천히 따라오세요! 😊
선형 회귀란?
데이터를 가장 잘 설명하는 직선 찾기
1
2
3
4
5
6
# 예시: 공부 시간 vs 시험 점수
공부시간 = [1, 2, 3, 4, 5]
점수 = [50, 55, 60, 65, 70]
# 선형 회귀가 찾는 것: y = 5x + 45
# "공부 1시간 늘면 점수 5점 오른다!"
일차함수 복습
1
2
3
4
5
y = ax + b
y: 예측할 값 (종속변수)
x: 입력 값 (독립변수)
a: 기울기 (얼마나 가파른가?)
b: 절편 (x=0일 때 y값)
시각화로 이해하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import matplotlib.pyplot as plt
import numpy as np
# 데이터
study_hours = np.array([1, 2, 3, 4, 5])
scores = np.array([50, 55, 60, 65, 70])
# 그래프 그리기
plt.figure(figsize=(8, 6))
plt.scatter(study_hours, scores, color='blue', s=100, label='실제 데이터')
# 직선 그리기 (y = 5x + 45)
x_line = np.linspace(0, 6, 100)
y_line = 5 * x_line + 45
plt.plot(x_line, y_line, color='red', linewidth=2, label='y = 5x + 45')
plt.xlabel('공부 시간 (시간)', fontsize=12)
plt.ylabel('시험 점수 (점)', fontsize=12)
plt.title('선형 회귀: 공부 시간 vs 시험 점수', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('linear_regression.png', dpi=150, bbox_inches='tight')
print("그래프 저장: linear_regression.png")
선형 회귀의 목표
오차(Error)를 최소화하는 직선 찾기
1
2
3
4
5
6
7
# 실제값 vs 예측값
실제 = [50, 55, 60, 65, 70]
예측 = [50, 55, 60, 65, 70] # 완벽!
오차 = 실제 - 예측 # [0, 0, 0, 0, 0]
# 머신러닝이 하는 일: 오차의 제곱합을 최소화
# MSE (Mean Squared Error) = (오차²의 평균)
🎯 학습 목표 2: LinearRegression으로 모델 만들기
기본 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.linear_model import LinearRegression
import numpy as np
# 1. 데이터 준비
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1) # 2차원으로!
y = np.array([50, 55, 60, 65, 70])
# 2. 모델 생성
model = LinearRegression()
# 3. 모델 학습
model.fit(X, y)
# 4. 계수 확인
print(f"기울기 (a): {model.coef_[0]:.2f}")
print(f"절편 (b): {model.intercept_:.2f}")
print(f"=> y = {model.coef_[0]:.2f}x + {model.intercept_:.2f}")
출력:
1
2
3
기울기 (a): 5.00
절편 (b): 45.00
=> y = 5.00x + 45.00
예측하기
1
2
3
4
5
6
# 새로운 데이터 예측
new_hours = np.array([[6], [7], [10]])
predictions = model.predict(new_hours)
for hours, score in zip(new_hours, predictions):
print(f"{hours[0]}시간 공부 → 예상 점수: {score:.1f}점")
출력:
1
2
3
6시간 공부 → 예상 점수: 75.0점
7시간 공부 → 예상 점수: 80.0점
10시간 공부 → 예상 점수: 95.0점
실전 예제: 집값 예측
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import numpy as np
# 집 데이터 (평수 vs 가격)
house_size = np.array([20, 30, 40, 50, 60, 70, 80, 90, 100]).reshape(-1, 1)
house_price = np.array([2, 3.5, 5, 6, 7.5, 9, 10, 11.5, 13]) # 억원
# 훈련/테스트 분리
X_train, X_test, y_train, y_test = train_test_split(
house_size, house_price, test_size=0.2, random_state=42
)
# 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 예측
y_pred = model.predict(X_test)
# 결과 출력
print("테스트 결과:")
for size, actual, pred in zip(X_test, y_test, y_pred):
print(f"평수: {size[0]:3.0f}평 | 실제: {actual:.1f}억 | 예측: {pred:.1f}억")
# 새 집 예측
new_house = np.array([[75]])
predicted_price = model.predict(new_house)
print(f"\n75평 집 예상 가격: {predicted_price[0]:.2f}억원")
출력:
1
2
3
4
5
테스트 결과:
평수: 30평 | 실제: 3.5억 | 예측: 3.6억
평수: 100평 | 실제: 13.0억 | 예측: 12.8억
75평 집 예상 가격: 9.53억원
🎯 학습 목표 3: 모델 성능 평가하기
R² Score (결정계수)
모델이 데이터를 얼마나 잘 설명하는가?
1
2
3
4
5
6
7
8
9
10
11
from sklearn.metrics import r2_score
# R² 계산
r2 = model.score(X_test, y_test)
print(f"R² Score: {r2:.4f}")
# 해석:
# 1.0 = 완벽한 예측 (100%)
# 0.8 = 매우 좋음 (80%)
# 0.5 = 보통 (50%)
# 0.0 = 예측 못함
MAE (Mean Absolute Error)
평균 절대 오차
1
2
3
4
5
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(y_test, y_pred)
print(f"MAE: {mae:.2f}억원")
print(f"평균적으로 {mae:.2f}억원 정도 오차")
MSE (Mean Squared Error)
평균 제곱 오차 (큰 오차에 더 큰 페널티)
1
2
3
4
5
6
7
8
from sklearn.metrics import mean_squared_error
mse = mean_squared_error(y_test, y_pred)
print(f"MSE: {mse:.2f}")
# RMSE (Root MSE) - 원래 단위로
rmse = np.sqrt(mse)
print(f"RMSE: {rmse:.2f}억원")
시각화: 실제 vs 예측
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
# 산점도: 실제값
plt.scatter(X_train, y_train, color='blue', s=100, alpha=0.6, label='훈련 데이터')
plt.scatter(X_test, y_test, color='green', s=100, alpha=0.6, label='테스트 데이터')
# 회귀선
x_range = np.linspace(house_size.min(), house_size.max(), 100)
y_range = model.predict(x_range.reshape(-1, 1))
plt.plot(x_range, y_range, color='red', linewidth=2, label='회귀선')
plt.xlabel('평수 (평)', fontsize=12)
plt.ylabel('가격 (억원)', fontsize=12)
plt.title('선형 회귀: 집값 예측', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('house_price_prediction.png', dpi=150, bbox_inches='tight')
print("그래프 저장: house_price_prediction.png")
💻 실전 예제: 연봉 예측
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error
import numpy as np
# 데이터: 경력(년) vs 연봉(만원)
experience = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).reshape(-1, 1)
salary = np.array([2800, 3200, 3600, 4000, 4500, 5000, 5500, 6000, 6500, 7000])
# 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(
experience, salary, test_size=0.2, random_state=42
)
# 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 예측
y_pred = model.predict(X_test)
# 평가
r2 = r2_score(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
print(f"기울기: {model.coef_[0]:.2f} (경력 1년당 연봉 상승)")
print(f"절편: {model.intercept_:.2f}만원")
print(f"\nR² Score: {r2:.4f}")
print(f"MAE: {mae:.2f}만원")
# 미래 예측
future_exp = np.array([[15], [20]])
future_salary = model.predict(future_exp)
print("\n미래 연봉 예측:")
for exp, sal in zip(future_exp, future_salary):
print(f"경력 {exp[0]}년 → 예상 연봉: {sal:,.0f}만원")
출력:
1
2
3
4
5
6
7
8
9
기울기: 466.67 (경력 1년당 연봉 상승)
절편: 2333.33만원
R² Score: 1.0000
MAE: 0.00만원
미래 연봉 예측:
경력 15년 → 예상 연봉: 9,333만원
경력 20년 → 예상 연봉: 11,667만원
📊 다중 선형 회귀
여러 개의 특징 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from sklearn.linear_model import LinearRegression
import numpy as np
# 데이터: [평수, 방개수, 층수] vs 가격
X = np.array([
[30, 2, 5], # 30평, 방2개, 5층
[50, 3, 10],
[70, 4, 15],
[40, 2, 8],
[60, 3, 12]
])
y = np.array([3.5, 6.0, 9.0, 5.0, 7.5]) # 억원
# 모델 학습
model = LinearRegression()
model.fit(X, y)
# 계수 확인
print("계수:")
print(f"평수: {model.coef_[0]:.3f}")
print(f"방개수: {model.coef_[1]:.3f}")
print(f"층수: {model.coef_[2]:.3f}")
print(f"절편: {model.intercept_:.3f}")
# 새 집 예측
new_house = np.array([[45, 3, 7]]) # 45평, 3개 방, 7층
predicted_price = model.predict(new_house)
print(f"\n45평, 3개 방, 7층 → {predicted_price[0]:.2f}억원")
출력:
1
2
3
4
5
6
7
계수:
평수: 0.117
방개수: 0.150
층수: 0.033
절편: -0.150
45평, 3개 방, 7층 → 5.84억원
⚠️ 선형 회귀의 한계
1. 선형 관계만 모델링
1
2
3
4
5
6
7
# 비선형 데이터에는 부적합
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1)
y = np.array([1, 4, 9, 16, 25]) # y = x²
model = LinearRegression()
model.fit(X, y)
print(f"R² Score: {model.score(X, y):.4f}") # 낮음!
2. 이상치에 민감
1
2
3
# 이상치가 있으면 직선이 크게 왜곡
X = np.array([1, 2, 3, 4, 100]).reshape(-1, 1)
y = np.array([2, 4, 6, 8, 1000]) # 마지막이 이상치
3. 외삽(Extrapolation) 주의
1
2
# 훈련 범위: 1~10
# 예측 범위: 100 → 부정확!
📝 요약
- 선형 회귀: 데이터를 가장 잘 설명하는 직선 찾기
- 공식: y = ax + b (a=기울기, b=절편)
- 사용법: LinearRegression().fit(X, y)
- 평가: R²(높을수록 좋음), MAE/MSE(낮을수록 좋음)
- 다중 회귀: 여러 특징을 동시에 사용 가능
🧪 연습 문제
문제: 광고비와 매출 예측
광고비(만원)와 매출(만원) 데이터로 회귀 모델을 만들어보세요.
1
2
3
4
5
6
7
ad_cost = np.array([100, 200, 300, 400, 500]).reshape(-1, 1)
sales = np.array([500, 900, 1300, 1700, 2100])
# TODO:
# 1. LinearRegression 모델 학습
# 2. R² Score 계산
# 3. 광고비 600만원일 때 예상 매출 예측
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.linear_model import LinearRegression
import numpy as np
ad_cost = np.array([100, 200, 300, 400, 500]).reshape(-1, 1)
sales = np.array([500, 900, 1300, 1700, 2100])
# 1. 모델 학습
model = LinearRegression()
model.fit(ad_cost, sales)
# 2. R² Score
r2 = model.score(ad_cost, sales)
print(f"R² Score: {r2:.4f}")
# 3. 예측
new_ad_cost = np.array([[600]])
predicted_sales = model.predict(new_ad_cost)
print(f"광고비 600만원 → 예상 매출: {predicted_sales[0]:.0f}만원")
출력:
1
2
R² Score: 1.0000
광고비 600만원 → 예상 매출: 2500만원
🤔 자주 묻는 질문 (FAQ)
Q1: 언제 선형 회귀를 사용해야 하나요?
A: 숫자를 예측하고 싶고, 관계가 직선에 가까울 때! 📈
선형 회귀가 좋은 경우:
- ✅ 집값 예측 (평수, 방 개수 → 가격)
- ✅ 매출 예측 (광고비 → 매출)
- ✅ 시험 점수 예측 (공부 시간 → 점수)
- ✅ 연봉 예측 (경력, 학력 → 연봉)
선형 회귀가 나쁜 경우:
- ❌ 분류 문제 (합격/불합격, 스팸/정상 → Day 95 로지스틱 회귀 사용)
- ❌ 명확한 비선형 관계 (인구 증가, 바이러스 확산 → 다항 회귀)
- ❌ 시계열 데이터 (주가, 날씨 → ARIMA, LSTM)
팁: 일단 선형 회귀로 시작하세요! 간단하고 해석하기 쉬워서 baseline으로 완벽합니다.
Q2: 내 데이터가 직선이 아닌데 어떻게 하죠?
A: 여러 방법이 있습니다! 😊
방법 1: 다항 회귀 (Polynomial Regression)
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 2차식으로 변환: x, x²
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
# 선형 회귀 적용
model = LinearRegression()
model.fit(X_poly, y)
# 이제 곡선을 모델링할 수 있어요!
방법 2: 데이터 변환
1
2
3
4
5
6
7
import numpy as np
# 로그 변환 (지수 관계일 때)
X_log = np.log(X + 1)
# 제곱근 변환
X_sqrt = np.sqrt(X)
방법 3: 다른 알고리즘 사용
1
2
3
4
5
6
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
# 의사결정나무는 비선형 관계도 잘 학습해요
model = DecisionTreeRegressor()
model.fit(X, y)
어떤 방법을 선택해야 할까?
- 일단 선형 회귀로 R² 확인
- R² < 0.7이면 산점도 그려서 패턴 확인
- 곡선 패턴이면 다항 회귀 시도
- 복잡한 패턴이면 트리 기반 모델 시도
Q3: R² Score를 어떻게 해석하나요?
A: “모델이 데이터를 얼마나 설명하는가?” 입니다!
R² 값의 의미:
1
2
3
4
5
6
R² = 1.0 # 100% 완벽 (모든 점이 직선 위에)
R² = 0.95 # 95% 설명 (매우 우수)
R² = 0.80 # 80% 설명 (좋음)
R² = 0.50 # 50% 설명 (보통)
R² = 0.0 # 0% 설명 (예측 못함)
R² < 0 # 평균보다 못함 (뭔가 잘못됨!)
실생활 비유:
선생님이 “공부하면 성적 오른다”고 했는데:
- R² = 0.9: “맞아! 공부한 만큼 오르네!” ✅
- R² = 0.5: “음… 공부도 중요하지만 다른 요인도 있나봐” 🤔
- R² = 0.1: “공부랑 성적이랑 별 상관 없는데?” ❌
분야별 기준: | 분야 | 좋은 R² | 설명 | |——|———|——| | 물리학 | > 0.95 | 법칙이 명확 | | 공학 | > 0.80 | 제어된 환경 | | 경제학 | > 0.50 | 변수가 많음 | | 사회과학 | > 0.30 | 인간 행동은 복잡 |
주의: R² 1.0은 의심하세요! 과적합일 수 있습니다.
Q4: MAE, MSE, RMSE… 뭐가 다른가요?
A: 모두 “오차”를 측정하지만 방식이 다릅니다!
실생활 예시: 집값 예측 (단위: 억원)
1
2
3
실제 = [5, 10, 15, 20]
예측 = [6, 9, 16, 18]
오차 = [1, 1, 1, 2]
MAE (Mean Absolute Error):
1
MAE = (1 + 1 + 1 + 2) / 4 = 1.25억원
- 의미: “평균적으로 1.25억원 차이 나요”
- 장점: 이해하기 쉬움, 단위 그대로
- 사용: 오차가 골고루 중요할 때
MSE (Mean Squared Error):
1
MSE = (1² + 1² + 1² + 2²) / 4 = 1.75
- 의미: 오차를 제곱해서 평균
- 장점: 큰 오차에 더 큰 페널티
- 단점: 단위가 제곱 (억원²)
- 사용: 큰 오차를 피하고 싶을 때
RMSE (Root Mean Squared Error):
1
RMSE = √1.75 = 1.32억원
- 의미: MSE의 제곱근 (원래 단위로)
- 장점: MSE 장점 + 해석하기 쉬움
- 사용: 가장 많이 사용됨!
어떤 걸 써야 할까?
1
2
3
4
5
6
# 보통은 RMSE 사용 (균형잡힘)
from sklearn.metrics import mean_squared_error
import numpy as np
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"평균 오차: {rmse:.2f}원")
Q5: 데이터가 몇 개 있어야 하나요?
A: 특징 개수의 10배 이상이 좋습니다! 📊
경험 법칙:
1
2
3
4
5
6
7
8
9
10
11
12
13
# 특징 1개 (단순 선형 회귀)
- 최소: 30개
- 권장: 100개 이상
# 특징 5개 (다중 선형 회귀)
- 최소: 50개
- 권장: 100-200개
# 특징 10개
- 최소: 100개
- 권장: 200-500개
# 일반 공식: 데이터 수 > 10 × 특징 수
실전 예시:
1
2
3
4
# 집값 예측: [평수, 방개수, 층수, 연식, 역세권]
특징_개수 = 5
필요_데이터 = 5 × 10 = 50개 (최소)
권장_데이터 = 100-200개
데이터가 적을 때 대처법:
- 특징 줄이기: 중요한 특징만 선택
1 2
# 5개 → 2개로 줄임 X = df[['평수', '역세권']] # 가장 중요한 것만
- Cross-validation: 데이터를 여러 번 재사용
1 2
from sklearn.model_selection import cross_val_score scores = cross_val_score(model, X, y, cv=5)
- 정규화 (Regularization): 과적합 방지
1 2
from sklearn.linear_model import Ridge, Lasso model = Ridge(alpha=1.0) # 정규화 추가
Q6: 훈련 R²은 높은데 테스트 R²은 낮아요. 왜 그런가요?
A: 전형적인 과적합(Overfitting) 입니다! 🚨
증상 확인:
1
2
3
4
5
6
7
8
9
train_r2 = model.score(X_train, y_train)
test_r2 = model.score(X_test, y_test)
print(f"훈련 R²: {train_r2:.3f}")
print(f"테스트 R²: {test_r2:.3f}")
# 과적합 예시
# 훈련 R²: 0.999 😊
# 테스트 R²: 0.600 😰
원인:
- 데이터가 너무 적음
- 특징이 너무 많음
- 모델이 너무 복잡함 (다항 회귀 차수가 높음)
해결 방법:
방법 1: 더 많은 데이터 수집
1
2
# 가장 확실한 방법!
# 데이터 50개 → 200개
방법 2: 특징 줄이기
1
2
3
4
# 상관관계 낮은 특징 제거
from sklearn.feature_selection import SelectKBest
selector = SelectKBest(k=5) # 상위 5개만
X_new = selector.fit_transform(X, y)
방법 3: 정규화 사용
1
2
3
4
5
from sklearn.linear_model import Ridge
# Ridge: L2 정규화 (과적합 방지)
model = Ridge(alpha=1.0)
model.fit(X_train, y_train)
방법 4: Cross-validation으로 검증
1
2
3
4
5
from sklearn.model_selection import cross_val_score
# 5-fold CV로 평균 성능 확인
scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print(f"평균 R²: {scores.mean():.3f} ± {scores.std():.3f}")
좋은 모델의 기준:
1
2
3
4
5
6
7
8
9
# 좋은 예
훈련 R² = 0.85
테스트 R² = 0.82
# 차이가 적음! ✅
# 나쁜 예
훈련 R² = 0.99
테스트 R² = 0.60
# 차이가 큼! ❌
📚 다음 학습
Day 95: 분류 모델 (로지스틱 회귀) ⭐⭐⭐
내일은 분류 문제를 풀어봅니다. 합격/불합격, 스팸/정상 등!
“선형 회귀는 단순하지만 강력합니다. 복잡한 모델보다 해석이 쉬워서 실무에서도 많이 사용됩니다!” 🚀
Day 94/100 Phase 10: AI/ML 입문 #100DaysOfPython
