머신러닝

머신러닝 - 다중회귀

haniru 2026. 1. 11. 19:14

기대수명 데이터 불러오기

life.describe()

 

  • count: 데이터의 개수 (비어있는 칸을 제외하고 숫자가 몇 개 들어있는지 알려줌)
  • mean: 평균값
  • std: 표준편차 (숫자들이 평균에서 얼마나 멀리 떨어져 있는지 나타냄. 숫자가 클수록 데이터가 들쑥날쑥하다는 뜻)
  • min: 최솟값
  • 25%, 50%, 75%: 데이터를 작은 순서대로 줄 세웠을 때 각각 1/4, 2/4(중간), 3/4 위치에 있는 값
  • max: 최댓값

print(life.columns)

표(life)의 가장 윗줄에 적힌 항목들의 이름만 따로 모아서 보

 

import pandas as pd


file = './life_expectancy.csv'

life = pd.read_csv(file)
print(life.head(3))
print(life.describe())
print(life.columns)

 

히트맵으로 시각화

이미지(히트맵) 읽는 법

이 복잡해 보이는 표를 읽는 법은 간단하다.

  1. 색깔의 의미: 우측 막대를 보면 위로 갈수록 밝고(흰색/살구색), 아래로 갈수록 어둡다(검은색).
    • 밝은색: 두 항목이 **'비례'**한다. (하나가 올라가면 다른 하나도 올라감)
    • 어두운색: 두 항목이 **'반비례'**한다. (하나가 올라가면 다른 하나는 내려감)
  2. 숫자의 의미 (상관계수):
    • 1에 가까울수록: 강력한 비례 관계 (예: 교육 수준이 높을수록 기대수명도 확실히 높다)
    • -1에 가까울수록: 강력한 반비례 관계 (예: 사망률이 높을수록 기대수명은 확실히 낮다)
    • 0에 가까울수록: 아무 상관 없음 (예: 인구수와 기대수명은 별 상관이 없다)

 

  • 기대수명(Life expectancy)과 교육(Schooling): 0.75
    • 매우 높은 양수다. 교육을 오래 받을수록 기대수명이 늘어나는 경향이 아주 뚜렷하다는 의미다.
  • 기대수명과 수입 자원 구성(Income composition of resources): 0.72
    • 국가의 경제적 자원 활용 능력이 좋을수록 기대수명이 높다.
  • 기대수명과 성인 사망률(Adult mortality): -0.7
    • 강한 음수다. 당연하게도 성인 사망률이 높을수록 그 나라의 기대수명은 뚝 떨어진다.
  • 기대수명과 HIV/AIDS: -0.56
    • 에이즈 발생률이 높을수록 기대수명이 낮아지는 관계를 보여준다.
  • GDP와 지출 비율(Percentage expenditure): 0.9
    • 거의 1에 가깝다. 돈이 많은 나라일수록 보건 등에 쓰는 비용 비율이 압도적으로 높다는 것을 알 수 있다.
  • 마른 정도(Thinness 1-19 years)와 (Thinness 5-9 years): 0.94
    • 이건 항목 자체가 비슷한 것이라 숫자가 매우 높게 나온 것이다. (어린이 비만도가 높으면 청소년 비만도도 높다는 당연한 소리다.)

 

import seaborn as sns
import matplotlib.pyplot as plt

# 그림의 크기를 가로 22, 세로 20 크기로 아주 크게 설정한다. 데이터 항목이 많아서 글자가 겹치지 않게 크게 만드는 작업이다.
sns.set(rc={'figure.figsize':(22,20)})
# corr()은 '상관계수'를 구하는 명령이다. 두 항목이 얼마나 서로 연관되어 있는지를 -1에서 1 사이의 숫자로 계산한다.
correlation_matrix = life.corr(numeric_only=True).round(2)
# 위에서 구한 숫자 표를 색깔이 들어간 지도(히트맵)로 그린다. annot=True: 칸 안에 숫자(상관계수)를 직접 표시하라는 뜻
sns.heatmap(data=correlation_matrix, annot=True)

 

전체 상관계수 표에서 '기대수명(Life expectancy)' 항목만 쏙 뽑아서 다른 요소들과의 관계를 숫자로 확인

A. 기대수명을 높이는 긍정적 요인 (양의 상관관계)

숫자가 1에 가까울수록 해당 수치가 높을 때 기대수명도 함께 올라간다.

  • Schooling (0.752): 교육을 많이 받을수록 기대수명이 길어진다. (가장 강력한 상관관계)
  • Income composition of resources (0.725): 국가의 자원 활용 및 소득 수준이 좋을수록 수명이 길다.
  • BMI (0.568): 체질량 지수가 높을수록 수명이 긴 것으로 나오는데, 이는 영양 상태가 양호한 국가일수록 수명이 높다는 의미로 해석된다.
  • GDP (0.461) / Polio (0.466) / Diphtheria (0.479): 경제력과 백신 접종률(폴리오, 디프테리아)도 수명 연장에 기여한다.

B. 기대수명을 낮추는 부정적 요인 (음의 상관관계)

숫자가 -1에 가까울수록 해당 수치가 높을 때 기대수명은 오히려 낮아진다.

  • Adult mortality (-0.696): 성인 사망률이 높을수록 당연히 기대수명은 낮아진다.
  • HIV/AIDS (-0.557): 에이즈 발생률이 높을수록 수명에 치명적인 영향을 미친다.
  • Thinness 1-19 years (-0.477): 아동 및 청소년의 저체중(영양 부족)은 수명을 낮추는 요인이다.

C. 영향이 미미한 요인

숫자가 0에 가까우면 서로 별 상관이 없다는 뜻이다.

  • Population (-0.022): 인구수 자체는 그 나라 사람들의 수명과 거의 관계가 없다.
  • Year (0.170): 연도가 흐른다고 해서 수명이 드라마틱하게 변하는 정도는 아니다.
print(life.corr(numeric_only=True).round(3)['Life expectancy'])

 

 

기대수명에 가장 큰 영향을 미치는 핵심 요인들을 순서대로 뽑아내기

수명에 "얼마나 큰 영향을 주는가"를 알고 싶을 때는 비례든 반비례든 0에서 멀리 떨어진(숫자가 큰) 항목을 찾아야 한다. 그래서 np.abs()를 써서 모든 숫자를 양수로 바꾼 뒤 비교하는 것이다.

  1. Income composition of resources (0.72): 국가 자원 소득 수준이 높을수록 수명과 매우 밀접함.
  2. Adult mortality (0.70): 성인 사망률이 수명 결정에 강력한 영향을 미침. (원래는 마이너스였으나 절대값으로 인해 상위에 오름)
  3. BMI (0.57): 체질량 지수(영양 상태)가 그다음으로 중요함.
  4. HIV/AIDS (0.56): 에이즈 발생률 역시 주요 변수임.
  5. Thinness 1-19 years (0.48): 아동/청소년기 저체중(영양 부족) 문제.
  6. Diphtheria (0.48): 디프테리아 백신 접종률.
import numpy as np # 수학 계산을 돕는 넘파이 라이브러리를 불러옴

# 1. 모든 항목과 '기대수명' 간의 상관계수를 구하고 소수점 둘째 자리까지 반올림함
c = life.corr(numeric_only=True).round(2)['Life expectancy']

# 2. 상관계수에 '절대값'을 씌움 (마이너스 부호를 없앰)
# 관계가 '반비례(-)'여도 수치 자체가 크면 영향력이 큰 것이기 때문임
c = np.abs(c)

# 3. 값을 큰 순서대로 정렬하고, 자기 자신(기대수명-기대수명)을 제외한 상위 7개를 출력함
# [1:8]의 의미: 0번째(자기 자신, 1.0)를 빼고 1번째부터 7번째까지 가져오라는 뜻임
print(c.sort_values(ascending=False)[1:8])

 

기대수명과 가장 관련이 없는(영향력이 거의 없는) 항목 5개를 골라내기

print(c.sort_values(ascending=False)[-6:-1]) # 앞서 절대값을 취한 상관계수(c)를 숫자가 **큰 순서(영향력이 강한 순서)**대로 정렬 후 리스트의 뒷부분을 잘라내기

 

기대수명과 관련 깊은 핵심 항목들을 한데 모아, 서로 어떤 모양으로 어울려 있는지 한눈에 확인하는 시각화

히트맵이 단순히 '관계의 점수'를 보여줬다면, 이 페어플롯은 **"실제로 데이터가 어떤 형태(직선인지, 곡선인지)로 움직이는지"**를 생생하게 보여준다

 

  • 우상향 직선 모양 (Life expectancy & Schooling / Income...):
    • 점들이 오른쪽 위로 쭉 뻗어 있다. 교육 수준과 경제 수준이 올라갈수록 수명이 직선적으로 정직하게 늘어난다는 것을 의미한다. 가장 예측하기 쉬운 예쁜 모양이다.
  • 우하향 곡선 모양 (Life expectancy & Adult mortality):
    • 왼쪽 위에서 오른쪽 아래로 미끄럼틀처럼 내려오는 모양이다. 사망률이 낮을 때는 수명이 높다가, 사망률이 일정 수준을 넘어가면 수명이 급격히 떨어진다.
  • 'L'자 모양 (Life expectancy & HIV/AIDS):
    • 대부분의 점이 왼쪽(에이즈 발생 낮음)에 몰려 있고, 에이즈 수치가 조금이라도 높아지면 수명이 바닥으로 곤두박질치는 모습이다. 특정 질병이 수명에 얼마나 치명적인지 보여준다.
  • 부채꼴 또는 뭉쳐 있는 모양 (BMI 관련):
    • 다른 그래프들에 비해 점들이 넓게 퍼져 있다. 이는 관계는 분명히 있지만, 다른 요인들에 비해 예외 상황이 많거나 데이터가 아주 고르지는 않다는 뜻이다.

 

# '페어플롯'은 데이터 항목들을 두 개씩 짝지어 모든 경우의 수를 그래프로 그려주는 기능이다.
# 항목이 6개라면 6 * 6 = 36개의 그래프가 바둑판 형태로 나타난다.
sns.pairplot(life[['Life expectancy', 'Schooling',\
                  'Income composition of resources',\
                  'Adult mortality', 'BMI', 'HIV/AIDS']])
plt.show()

 

'비어있는 칸(결측치)'이 어디에 얼마나 있는지 확인하는 과정

 

  • Population (652): 전체 데이터 중 인구수 정보가 없는 칸이 652개나 된다. 가장 많이 비어 있다.
  • Hepatitis B (553), GDP (448): 이 항목들도 데이터 누락이 심각한 편이다.
  • HIV/AIDS, Country, Year 등 (0): 이 항목들은 누락 없이 모든 데이터가 완벽하게 채워져 있다.

 

life.isna().sum().sort_values(ascending=False)

 

기대수명과 가장 관련이 깊은 5가지 핵심 변수들만 따로 모아서, 그 안에 데이터 구멍(결측치)이 각각 몇 개나 있는지 집중적으로 확인하는 과정

 

  • HIV/AIDS: 0개 (깨끗함)
  • Adult mortality: 10개 (양호함)
  • Schooling: 163개 (관리가 필요함)
  • Income composition of resources: 167개 (관리가 필요함)

 

main_features = ['Schooling', 'Income composition of resources', 'Adult mortality', 'BMI', 'HIV/AIDS']
life[main_features].isna().sum()

 

분석에 방해가 되는 '구멍 난 데이터'를 정리하고, AI를 학습시키기 위해 '문제집'과 '정답지'를 만드는 과정

X와 y 모두 1649개로 숫자가 똑같다. 문제 개수와 정답 개수가 정확히 일치해야 AI 학습이 가능하다.

life.dropna(inplace=True) # 표(life)에서 빈 칸(결측치)이 하나라도 포함된 줄(행)을 통째로 삭제한다. 원본 데이터를 수정.

X = life[main_features] # 우리가 미리 골라둔 5가지 핵심 항목(교육, 소득, 사망률 등)을 **'문제(입력 데이터)'**로 설정한다
y = life['Life expectancy'] # 우리가 맞추고 싶은 목표인 '기대수명'을 **'정답(출력 데이터)'**으로 설정한다
print(X.shape, y.shape ) # 분리된 데이터들의 크기를 확인

 

데이터를 공부용과 시험용으로 나눠서(80:20), 선형 회귀라는 AI 학생에게 공부를 시킨 뒤, 처음 보는 문제로 시험을 봐서 실력을 숫자로 확인하는 과정

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# X_train, y_train (공부용): AI가 학습할 때 사용할 문제집과 정답지다. 전체의 80%를 할당했다.
# X_test, y_test (시험용): AI가 한 번도 본 적 없는 문제들이다. 공부를 마친 AI가 얼마나 잘 맞히는지 확인하는 용도다. 전체의 20%(test_size=0.2)를 할당했다.
# random_state=84: 데이터를 섞어서 나눌 때 사용하는 '난수 번호'다. 이 번호를 지정해두면 나중에 코드를 다시 실행해도 똑같은 구성으로 데이터가 나뉜다. 실험의 일관성을 위한 장치다.
X_train, X_test, y_train, y_test = train_test_split(X, y,\
                                                    random_state=84,\
                                                    test_size = 0.2)
print(X_train, X_test, y_train, y_test)
regr = LinearRegression() # '선형 회귀'라는 이름의 AI 모델을 하나 생성. 머릿속이 텅 빈 똑똑한 학생을 한 명 데려온 것과 같다. 이 학생은 "입력값(교육, 소득 등)과 결과값(수명) 사이의 직선적인 규칙"을 찾는 데 특화되어 있다.
regr.fit(X_train, y_train) # 준비한 80%의 문제집(X_train)과 정답(y_train)을 학생에게 주고 "둘 사이의 관계를 찾아내라"고 명령하는 과정이다.
print('선형 회귀 모델의 점수=', regr.score(X_test, y_test)) # 공부를 마친 AI에게 한 번도 안 보여준 20%의 문제(X_test)를 풀게 한 뒤, 실제 정답(y_test)과 얼마나 비슷한지 점수를 매기는 단계

 

선형회귀 그래프를 시각적으로 확인

 

  • 점이 빨간 선에 바짝 붙어 있을수록: AI가 정답을 아주 잘 맞히고 있다는 뜻이다.
  • 점이 빨간 선에서 멀리 떨어져 있을수록: AI가 실제보다 너무 높게 혹은 낮게 엉뚱한 예측을 했다는 뜻이다.
  • 전체적인 모양: 점들이 빨간 선을 따라 길게 모여 있다면, 이 AI 모델은 기대수명을 예측하는 데 꽤 성공적이라고 볼 수 있다.

 

plt.figure(figsize=(6,6))
y_test_predict = regr.predict(X_test) # 공부를 마친 AI(regr)에게 시험 문제(X_test)를 주고 답을 써보라고 시키는 것이다. 그 결과물(AI가 예측한 기대수명 값들)이 y_test_predict에 저장된다.
plt.scatter(y_test, y_test_predict) # 가로축에는 실제 정답(y_test)을, 세로축에는 AI가 쓴 답(y_test_predict)을 놓고 점을 찍는다.
plt.plot(y_test, y_test, color='r', linewidth=3) # 가로와 세로가 똑같은 지점($y = x$)을 잇는 빨간색 직선을 긋는다.

 

앞서 했던 '핵심 5개 항목' 분석에서 한 걸음 더 나아가, 데이터셋에 있는 거의 모든 숫자 정보(19가지)를 AI에게 다 퍼부어주고 학습시키는 과정

이 코드에서 출력되는 '선형회귀 모델의 점수'는 통계학에서 **결정계수**라고 부른다.

  • 0.0 ~ 1.0 사이의 값: 1에 가까울수록 AI가 정답을 거의 완벽하게 맞힌다는 뜻이다.
  • 변화 관찰: 5개의 핵심 정보만 썼을 때의 점수와 19개 전체 정보를 썼을 때의 점수를 비교해 봐라. 보통 정보가 많아질수록 점수가 약간 더 올라가는 경향이 있다.
import pandas as pd


file = './life_expectancy.csv'
life = pd.read_csv(file)

life.dropna(inplace=True) # 결측값 제거

# 'Country', 'Year', 'Status', 'Life expectancy'를 제외한 속성 사용
# 텍스트 데이터: 선형 회귀 AI는 '숫자'만 계산할 수 있다. 'Korea'나 'Developed' 같은 글자는 숫자로 바꿔주는 복잡한 과정(인코딩) 없이는 이해하지 못한다.

수치적 의미: 국가 이름은 단순히 이름일 뿐, 그 자체가 수치적인 크기(1보다 2가 크다 등)를 갖지 않기 때문에 일반적인 회귀 분석에서는 바로 사용하기 어렵다.
X = life[['Year','Adult mortality','Infant deaths', 'Alcohol',
          'Percentage expenditure','Hepatitis B', 'Measles',
          'BMI', 'Under-five deaths', 'Polio',
          'Total expenditure','Diphtheria', 'HIV/AIDS', 'GDP',
          'Population','Thinness 1-19 years','Thinness 5-9 years',
          'Income composition of resources', 'Schooling']]
y = life['Life expectancy']

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=84, test_size = 0.2) # 1,649개의 데이터를 다시 섞어서 80%는 공부용, 20%는 시험용으로 나눈다.
regr = LinearRegression()
regr.fit(X_train, y_train) # AI가 19가지 정보와 기대수명 사이의 복잡한 공식을 찾아낸다.
print('선형회귀 모델의 점수 =', regr.score(X_test, y_test).round(3))

 

숫자로만 확인했던 영향력 순위를 막대 그래프로 시각화하여, 어떤 데이터를 중요하게 다뤄야 할지 직관적으로 보여주기

 

  • 가장 위에 있는 막대: 기대수명과 가장 끈끈하게 연결된 핵심 요인이다. (예: Schooling, Income composition of resources)
  • 가장 아래에 있는 막대: 기대수명과 거의 상관이 없는 요인이다. (예: Population)
  • 막대의 길이: 0.7 이상이면 매우 강력한 관계, 0.3~0.7 사이면 뚜렷한 관계, 0.1 이하면 거의 관계가 없다고 해석하면 된다.

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

file = './life_expectancy.csv'

life = pd.read_csv(file)
life.dropna(inplace=True) # 결측값 제거

# 'Country', 'Status', 'Life expectancy'를 제외한 속성 사용
X = life[['Year','Adult mortality','Infant deaths', 'Alcohol',
         'Percentage expenditure','Hepatitis B', 'Measles',
         'BMI', 'Under-five deaths', 'Polio',
         'Total expenditure','Diphtheria', 'HIV/AIDS', 'GDP',
         'Population','Thinness 1-19 years','Thinness 5-9 years',
         'Income composition of resources', 'Schooling']]

# 기대수명과 다른 모든 숫자 데이터들 사이의 상관계수를 계산한다.
c = life.corr(numeric_only=True)['Life expectancy']

# 절대값을 씌운다. 양의 상관관계(비례)든 음의 상관관계(반비례)든, '영향력의 크기' 자체만 보기 위함이다.
c = np.abs(c)       # 상관계수의 절대값을 취한다

# 영향력이 적은 순서대로(오름차순) 정렬한다. 이렇게 정렬해야 그래프를 그렸을 때 위로 갈수록 막대가 길어져 보기가 좋다.
c.sort_values(ascending=True, inplace=True)

c = c[:-1] #'기대수명' 자기 자신과의 상관계수(항상 1.0)를 그래프에서 제외한다. 당연한 결과는 빼고 다른 변수들만 비교하기 위함이다.

plt.figure(figsize=(7,7))
plt.barh(c.index, c.values) # 가로 막대 그래프
plt.title('Correlation Coef with Life expectancy')
plt.xlabel('Features')
plt.ylabel('Correlation Coef')

 

실제 데이터가 아니라, 우리가 직접 가상의 데이터를 만들어내는 과정

import numpy as np
import matplotlib.pyplot as plt

m = 100    # 점(데이터)을 100개 만들겠다는 뜻이다.
np.random.seed(84)   # 난수 발생기를 고정한다.
X = 8 * np.random.rand(m, 1) - 4 # -4에서 4 사이의 숫자를 랜덤하게 100개 생성한다.
y = 0.5 * X ** 2 + 2 * X + 1 + np.random.randn(m, 1) # 2차 함수 공식을 사용해 세로축 값 만들기. np.random.randn은 약간의 '오차'를 더하는 역할

plt.figure(figsize=(6,4))
plt.plot(X, y, "b^") # 생성한 데이터를 그래프로 그린다. "b^": 파란색(b, blue)의 삼각형(^) 모양으로 점을 찍으라는 뜻

 

휘어진 곡선 데이터에 억지로 직선을 그어보는 과정

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

regr = LinearRegression() # 직선을 그리는 도구(선형 회귀 모델)를 준비. y=ax+b 라는 직선 공식만 만들 수 있음.
regr.fit(X, y) # 데이터(X, y)를 보고 가장 적절한 a(기울기)와 b(절편)를 찾음
print('선형회귀 모델의 점수 =', regr.score(X, y).round(3))
print('선형회귀 모델의 계수와 절편 =', regr.coef_, regr.intercept_)
# regr.coef_(계수): 공식 y = ax + b에서 **기울기(a)**를 뜻함
# regr.intercept_ (절편): 공식에서 **b**를 뜻함

 

직선 시각화

 

  • 문제점: 점들은 곡선(2차 함수)을 그리며 아래로 볼록한 모양인데, 빨간 선은 그냥 직선
  • 오차: 양 끝부분과 가운데 부분에서 빨간 선과 파란 점들 사이의 거리가 꽤 멀 거야. 이 **'거리의 차이'**가 바로 예측 실패(오차)를 의미.
  • 결론: 지금 사용한 AI 모델(단순 선형 회귀)은 "데이터가 굽어 있다"는 사실을 전혀 모르기 때문에, 아무리 노력해도 저 직선이 최선이다.

 

plt.figure(figsize=(7,6))
y_predict = regr.predict(X) # 예측값 계산: AI(regr)에게 100개의 문제($X$)를 주고 답안지를 작성하게 함. y=ax+b에 맞춰 y값을 계산함
plt.scatter(X, y) # 2차 함수 공식으로 만들었던 휘어진 모양의 파란점 찍기
plt.plot(X.flatten(), y_predict, color='r') # y_predict를 빨간색 선으로 연결 (X.flatten(): 2차원 형태의 데이터를 1차원으로 평평하게 펴줌)

 

실습용 데이터를 만들고 그 모양을 다듬는 과정

import numpy as np
from sklearn.preprocessing import PolynomialFeatures # 직선 데이터를 곡선 데이터로 변환해주는 마법사

# np.arange(6)은 0부터 5까지 숫자 6개를 만듦
# reshape(3, -1)은 이 6개의 숫자를 **3행(세 줄)**으로 다시 배치
# -1의 의미: "행은 3개로 정해줄 테니, 열(칸)은 전체 개수에 맞춰서 네가 알아서 계산해"라는 파이썬의 편리한 기능
t = np.arange(6).reshape(3, -1)   # 간단한 샘플 데이터 t를 생성(2차원)
print(t)

 

직선만 이해하는 AI에게 "제곱이나 두 변수의 곱(x * y)" 같은 추가 정보를 계산해서 넘겨주는 과정

AI(선형 회귀)는 기본적으로 직선(y = ax + b)만 그릴 수 있다. 하지만 우리가 데이터에 **제곱값*을 미리 계산해서 넣어주면, AI는 이를 별개의 새로운 변수로 인식하고 계산에 포함시킴

 

  • 1열 (Bias): 모든 행에 1이 추가됨. (상수항)
  • 2, 3열 (Original): 원래 데이터가 그대로 들어옴.
  • 4열: 첫 번째 숫자의 제곱값.
  • 5열: 두 숫자를 서로 곱한 값.
  • 6열: 두 번째 숫자의 제곱값.

 

poly = PolynomialFeatures(degree=2) # 데이터를 2차 다항식 형태로 변환할 도구를 준비해. degree=2는 제곱항까지 만들겠다는 뜻
new_t = poly.fit_transform(t) # 실제로 데이터 t를 변환. 원래 있던 숫자들을 조합해서 새로운 열(Column)들을 만들어냄
print(new_t)

 

변환된 데이터의 각 열(Column)이 어떤 수학적 조합으로 만들어진 것인지 그 '이름표'를 확인하는 기능

 

  • '1': 상수항 (모든 행에 1을 추가함)
  • 'x0': 원래 데이터의 첫 번째 열
  • 'x1': 원래 데이터의 두 번째 열
  • 'x0^2': 첫 번째 열을 제곱한 값
  • 'x0 x1': 첫 번째 열과 두 번째 열을 곱한 값
  • 'x1^2': 두 번째 열을 제곱한 값
print(poly.get_feature_names_out())

 

직선밖에 모르던 AI에게 제곱항이라는 새 무기를 쥐여주어, 휘어진 데이터를 완벽하게 학습시킨 과정

 

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 1. 가상 데이터 생성
m = 100    # 점 100개 생성
# -4에서 4 사이의 랜덤한 X값 생성 (가로축)
X = 8 * np.random.rand(m, 1) - 4
# 2차 함수 공식(0.5x² + 2x + 1)에 노이즈를 섞어 y값 생성 (세로축)
y = 0.5 * X ** 2 + 2 * X + 1 + np.random.randn(m, 1)

# 2. 다항 특성 변환 (Feature Engineering)
# degree=2: x²항까지 만들겠다는 뜻, include_bias=False: 상수항(1)은 모델이 알아서 계산하므로 제외
poly_features = PolynomialFeatures(degree = 2, include_bias = False)
# X(x)를 넣어서 X_poly(x, x²)라는 두 개의 열을 가진 데이터로 변환함
X_poly = poly_features.fit_transform(X)

# 3. 모델 학습
regr = LinearRegression()
regr.fit(X_poly, y) # AI에게 x와 x² 데이터를 주고 y와의 관계(공식)를 찾으라고 시킴

# 4. 결과 출력
print('다항 회귀 모델의 점수 =', regr.score(X_poly, y))
# coef_는 [x의 계수, x²의 계수] 순서로 나옴 (약 2와 0.5 근처)
print('다항 회귀 모델의 계수 =', regr.coef_, '절편 =', regr.intercept_)

# 5. 시각화를 위한 매끄러운 곡선용 데이터 생성
# -4부터 4까지 일정한 간격으로 50개의 점을 생성 (예측선을 부드럽게 그리기 위함)
domain = np.linspace(-4, 4, 50).reshape(-1, 1)
# 훈련할 때와 똑같이 x²항을 추가해서 2차원으로 변환 (AI가 이해할 수 있는 형태)
domain_2 = poly_features.fit_transform(domain)

# 6. 그래프 그리기
plt.figure(figsize=(6,4))
y_predict = regr.predict(domain_2) # 50개의 점에 대한 AI의 예측값 계산
plt.scatter(X, y) # 실제 흩어져 있는 파란색 점들(데이터)을 찍음
plt.plot(domain, y_predict, color='r', linewidth=4) # AI가 찾은 빨간색 곡선(예측선)을 그림

 

3차 함수 형태의 가상의 데이터를 만드는 과정

# 2개의 변곡점을 가진 데이터를 생성하고 시각화하는 코드
import numpy as np
import matplotlib.pyplot as plt

m = 100 # 데이터 점을 100개 만들겠다는 설정
X = 8 * np.random.rand(m, 1) - 4 # 가로축(X) 값을 -4에서 4 사이의 랜덤한 숫자로 채운다.
y = .5 * X ** 3 + .5 * X ** 2 + X + 3 + np.random.randn(m, 1) # 3차 방정식을 사용해 세로축(y) 값을 만든다.
plt.figure(figsize=(6,5))
plt.plot(X, y, "b^") # 파란색(b) 삼각형(^) 모양으로 점들을 화면에 출력한다.

 

**3차 다항 회귀(Cubic Regression)**를 사용하여 데이터를 학습하고, 그 결과를 시각화하는 과정

# 다항 회귀 모델을 만들고 점수와 계수, 절편을 출력하는 코드
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
import numpy as np
import matplotlib.pyplot as plt

# 가상 데이터 생성
m = 100    # 점 100개 생성
# -4에서 4 사이의 랜덤한 X값 생성 (가로축)
X = 8 * np.random.rand(m, 1) - 4
# 2차 함수 공식(0.5x² + 2x + 1)에 노이즈를 섞어 y값 생성 (세로축)
y = 0.5 * X ** 2 + 2 * X + 1 + np.random.randn(m, 1)

# 1. 다항 특성 변환기 설정 (3차항까지 생성)
# degree=3: x, x², x³ 항을 모두 만들어서 AI에게 제공함
poly_features = PolynomialFeatures(degree = 3, include_bias = False)

# 2. 데이터 변환 (뻥튀기)
# 원래 1개였던 X 데이터를 [x, x², x³] 3개 열을 가진 데이터로 변환
X_poly = poly_features.fit_transform(X)

# 3. 모델 생성 및 학습
regr = LinearRegression()
regr.fit(X_poly, y) # AI가 y = w₁x + w₂x² + w₃x³ + b 공식을 찾아냄

# 4. 결과 출력
print('다항 회귀 모델의 점수 =', regr.score(X_poly, y))
print('다항 회귀 모델의 계수 =', regr.coef_) # x, x², x³ 각각의 가중치(w)
print('절편 =', regr.intercept_) # 상수항(b)

# 5. 예측 곡선 생성 과정
# -4에서 4까지 일정한 간격으로 100개의 점 생성
X_new = np.linspace(-4, 4, 100).reshape(100, 1)

# 예측할 데이터도 학습할 때와 똑같이 3차항까지 변환해야 함
X_new_poly = poly_features.transform(X_new)

# AI가 계산한 예측값
y_new = regr.predict(X_new_poly)

# 6. 시각화
plt.figure(figsize=(6,6))
plt.plot(X, y, "b^", label='Data')
plt.plot(X_new, y_new, "r-", label="Prediction")
plt.legend()

 

데이터를 단 20개만 만들어서 학습용과 시험용으로 나누고, 그중 학습용 데이터가 그래프상에서 어디에 위치하는지 확인하는 과정

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

m = 20 # 데이터를 20개만 생성한다. 데이터 양이 매우 적은 편인데, 이는 보통 과대적합(Overfitting) 현상을 관찰하기 위해 의도적으로 적게 설정한 것이다.
X = 8 * np.random.rand(m, 1) - 4 # 3차 함수
y = .5 * X ** 3 + .5 * X ** 2 + X + 3 + np.random.randn(m, 1)

# 20개의 데이터를 학습용 데이터, 테스트 데이터로 구분하자
X_train, X_test, y_train, y_test = train_test_split(X, y) # 20개의 데이터를 랜덤하게 섞어서 **학습용(Train)**과 **테스트용(Test)**으로 분리한다.

plt.figure(figsize=(6,5))
plt.xlim(-5, 5); plt.ylim(-28, 50) # 그래프의 가로(X)와 세로(Y) 범위를 강제로 고정한다. / x, y 범위를 제한함
plt.plot(X_train, y_train, "b^")   # 분리된 데이터 중 학습용 데이터 15개만 파란색 삼각형 / 학습용 데이터를 그리자

 

 

직선이나 완만한 곡선을 넘어, 데이터의 아주 미세한 굴곡까지 전부 따라가려는 '과대적합(Overfitting)' 상태의 AI 모델을 만드는 과정

이 모델의 결과는 숫자상으로는 완벽해 보이지만, 실제로는 심각한 문제를 안고 있다.

  • 완벽한 점수: 데이터 점이 15개인데 모델의 무기(차수)가 20개나 되기 때문에, AI는 모든 점을 오차 없이 정확하게 통과하는 미친 듯이 휘어진 선을 그려낼 수 있다.
  • 미쳐버린 계수 (coef_): 출력된 계수들을 보면 숫자가 1.23e+10처럼 터무니없이 큰 값들이 섞여 있을 것이다. 점 하나하나를 맞추기 위해 공식의 숫자들을 억지로 키웠기 때문이다.
  • 과대적합 (Overfitting):
    • 학습용 데이터는 귀신같이 맞추지만(암기), **새로운 데이터(테스트용)**를 주면 완전히 엉뚱한 답을 내놓는다.
    • 비유하자면, 수학의 원리를 배운 게 아니라 문제집의 숫자와 점 위치를 통째로 외워버린 학생과 같다.
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

poly_features = PolynomialFeatures(degree = 20, include_bias = False) # 20개의 차수로 뻥튀기. 차수가 20이라는 것은 그래프가 최대 19번까지 굽어질 수 있다는 뜻이다. 모델이 매우 복잡하고 유연해진다.
X_poly = poly_features.fit_transform(X_train)
regr = LinearRegression()
regr.fit(X_poly, y_train) # 단 15개 정도의 학습용 데이터 점(X_train)을 맞추기 위해 20차 함수 공식을 만든다.

print('다항 회귀 모델의 점수 =', regr.score(X_poly, y_train)) # 학습용 데이터에 대한 점수를 확인한다.
print('다항 회귀 모델의 계수 =', regr.coef_)
print('절편 =', regr.intercept_)

 

암기만 했던 AI에게 **공부할 때 보지 못한 새로운 문제(테스트 데이터)**를 주고 실력을 검증하는 과정

# 테스트용 데이터 X도 학습할 때와 마찬가지로 20차항까지 뻥튀기하는 과정이다.
X_poly = poly_features.fit_transform(X_test)

# 20차 함수를 외워버린 AI에게 한 번도 안 본 문제(X_test)를 풀게 한 뒤, 실제 정답(y_test)과 대조해서 점수를 매긴다.
print('테스트 데이터에 대한 모델의 점수 =', regr.score(X_poly, y_test))

 

 

이 코드는 '폭주한 AI'가 그린 엉망진창 성적표를 눈으로 직접 확인하는 과정

# 예측 곡선을 매끄럽게 그리기 위해 -4부터 4까지 100개의 점을 아주 촘촘하게 만듦
X_new = np.linspace(-4, 4, 100).reshape(100, 1)

# 이 100개의 점들도 AI가 이해할 수 있게 20차항까지 뻥튀기해주는 작업
X_new_poly = poly_features.transform(X_new)

y_new = regr.predict(X_new_poly)

plt.figure(figsize=(7,6))
plt.xlim(-5, 5); plt.ylim(-28, 50) # x, y 범위를 제한함

# AI가 열심히 공부(암기)했던 **학습용 데이터(파란 삼각형)**를 그림
plt.plot(X_train, y_train, 'b^')

# AI가 한 번도 본 적 없는 **테스트 데이터(커다란 투명 빨간 원)**를 그림. 이게 바로 실제 정답지
plt.scatter(X_test, y_test, c='red', s=200, alpha=0.4)

# AI가 예측한 빨간색 예측 곡선을 그림
plt.plot(X_new, y_new, "r-", label="Prediction curve")
plt.legend()

구분 현상 이유
학습용 데이터 백발백중 (빨간 선이 점을 다 통과함) 모델의 힘(차수 20)이 데이터(15개)보다 너무 강함
테스트 데이터 엉망진창 (빨간 원과 선이 따로 놂) 데이터의 규칙을 배운 게 아니라 점의 위치를 암기함
곡선의 모양 미친 듯이 요동침 점들을 다 통과하려고 공식의 계수를 무리하게 키움

 

통째로 암기해 버리는 '과대적합(Overfitting)' 현상을 실험하는 코드다. 데이터는 단 20개뿐인데, 무기(차수)는 20차항까지 사용하여 모델이 얼마나 폭주하는지 보여준다.

 

  • 훈련 데이터 점수 (1.0 근처): AI가 학습용 데이터의 모든 점을 완벽하게 통과하므로 점수가 매우 높게 나온다. 하지만 이는 실력이 아니라 단순 암기의 결과다.
  • 테스트 데이터 점수 (매우 낮은 음수): 암기만 한 AI는 조금만 위치가 다른 새로운 데이터가 들어오면 완전히 엉뚱한 예측을 한다. 그래서 점수가 형편없거나 마이너스로 떨어진다.

왜 이런 결과가 나오는가?

  1. 데이터 부족: 데이터가 너무 적어서 AI가 일반적인 규칙을 발견할 기회가 부족했다.
  2. 과도한 자유도: 차수(degree)가 너무 높아서 모델이 불필요할 정도로 복잡해졌다.
  3. 결과: AI가 데이터에 섞인 '오차(noise)'까지 데이터의 특징이라고 착각하여 공식을 복잡하게 꼬아버린 것이다.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 1. 데이터 생성 단계
m = 20      # 데이터 개수를 20개로 매우 적게 설정 (과대적합이 잘 일어나게 함)
X = 8 * np.random.rand(m, 1) - 4  # -4에서 4 사이의 무작위 X값 생성
# 실제 정답 공식: 0.5x³ + 0.5x² + x + 3 에 약간의 오차(noise)를 더함
y = .5 * X ** 3 + .5 * X ** 2 + X + 3 + np.random.randn(m, 1)

# 2. 데이터 분할 단계
# 전체 20개 데이터를 학습용(Train)과 검증용(Test)으로 나눔
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 3. 데이터 뻥튀기(다항 특성 추가) 단계
# degree=20: x를 x²⁰까지 확장함. 모델이 엄청나게 복잡한 곡선을 그릴 수 있게 됨
poly_features = PolynomialFeatures(degree = 20, include_bias = False)
# 학습용 데이터와 테스트용 데이터를 각각 20차항으로 변환
X_train_pl = poly_features.fit_transform(X_train)
X_test_pl = poly_features.fit_transform(X_test)

# 4. 모델 생성 및 학습 단계
regr = LinearRegression()
regr.fit(X_train_pl, y_train) # 20차항 데이터를 가지고 학습용 데이터의 정답을 외우기 시작함

# 5. 성능 평가 및 출력
# 학습한 데이터에 대한 점수 (얼마나 잘 외웠나?)
print('훈련 데이터의 점수 =', regr.score(X_train_pl, y_train))
# 한 번도 본 적 없는 데이터에 대한 점수 (얼마나 실력이 좋나?)
print('테스트 데이터의 점수 =', regr.score(X_test_pl, y_test))

 

 

'과대적합(폭주하는 AI)' 문제를 해결하기 위해 AI에게 제동을 거는 과정

  • 규제(Regularization): AI가 데이터를 너무 똑같이 외우려고 공식의 숫자(계수)를 비정상적으로 키우는 것을 막는 '벌금' 시스템.
  • 스케일링(StandardScaler): 여러 항목의 숫자 단위가 다르면 AI가 혼란을 느낌. 이를 평균 0, 표준편차 1이라는 일정한 기준으로 통일하는 '신체검사 기준 맞추기'와 같음. 규제 모델을 쓸 때는 이 과정이 필수.
  • 릿지(Ridge): 규제가 들어간 회귀 모델 중 하나. 공식의 숫자들을 전체적으로 작게 만들어 그래프가 너무 요동치지 않게 '부드럽게' 펴주는 역할을 함.
  • 알파(alpha): 벌금의 세기. 숫자가 클수록 AI에게 더 강한 제약을 걸어 모델을 단순하게 만든다.
### 1. 데이터 표준화(Scaling) ###
from sklearn.preprocessing import StandardScaler
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 1. 데이터 생성 단계
m = 20      # 데이터 개수를 20개로 매우 적게 설정 (과대적합이 잘 일어나게 함)
X = 8 * np.random.rand(m, 1) - 4  # -4에서 4 사이의 무작위 X값 생성
# 실제 정답 공식: 0.5x³ + 0.5x² + x + 3 에 약간의 오차(noise)를 더함
y = .5 * X ** 3 + .5 * X ** 2 + X + 3 + np.random.randn(m, 1)

# 2. 데이터 분할 단계
# 전체 20개 데이터를 학습용(Train)과 검증용(Test)으로 나눔
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 3. 데이터 뻥튀기(다항 특성 추가) 단계
# degree=20: x를 x²⁰까지 확장함. 모델이 엄청나게 복잡한 곡선을 그릴 수 있게 됨
poly_features = PolynomialFeatures(degree = 20, include_bias = False)
# 학습용 데이터와 테스트용 데이터를 각각 20차항으로 변환
X_train_pl = poly_features.fit_transform(X_train)
X_test_pl = poly_features.fit_transform(X_test)

# 4. 모델 생성 및 학습 단계
regr = LinearRegression()
regr.fit(X_train_pl, y_train) # 20차항 데이터를 가지고 학습용 데이터의 정답을 외우기 시작함

# 5. 성능 평가 및 출력
# 학습한 데이터에 대한 점수 (얼마나 잘 외웠나?)
print('훈련 데이터의 점수 =', regr.score(X_train_pl, y_train))
# 한 번도 본 적 없는 데이터에 대한 점수 (얼마나 실력이 좋나?)
print('테스트 데이터의 점수 =', regr.score(X_test_pl, y_test))

ss = StandardScaler()
# 학습 데이터의 평균과 표준편차를 계산함
ss.fit(X_train_pl)
# 계산된 기준을 바탕으로 학습/테스트 데이터를 일정한 규격으로 변환함
train_scaled = ss.transform(X_train_pl)
test_scaled = ss.transform(X_test_pl)

### 2. 릿지 회귀 적용 ###
from sklearn.linear_model import Ridge
# alpha=10: 벌금의 강도를 10으로 설정 (적당한 제동)
ridge = Ridge(alpha = 10)
ridge.fit(train_scaled, y_train)

# 점수 확인: 훈련 점수는 낮아질 수 있지만, 테스트 점수는 올라가는 것이 목표임
print('훈련 데이터의 점수 =', ridge.score(train_scaled, y_train))
print('테스트 데이터의 점수 =', ridge.score(test_scaled, y_test))

### 3. 최적의 벌금 강도(Alpha) 찾기 ###
import matplotlib.pyplot as plt

train_scores = []
test_scores = []
# 벌금의 강도를 0.001(약함)부터 1000(강함)까지 바꿔가며 실험함
alpha_values = [0.001, 0.01, 1, 10, 100, 1000]

for a in alpha_values:
  ridge = Ridge(alpha = a)
  ridge.fit(train_scaled, y_train)
  # 각 알파값에 따른 점수를 리스트에 저장함
  train_scores.append(ridge.score(train_scaled, y_train))
  test_scores.append(ridge.score(test_scaled, y_test))

# 그래프 그리기: x축은 알파값에 로그를 씌워 간격을 일정하게 만듦 (10의 거듭제곱 형태)
plt.plot(np.log10(alpha_values), train_scores, 'r-', label='Train score')
plt.plot(np.log10(alpha_values), test_scores, 'b:', label='Test score')
plt.xlabel('Alpha value : 10^x scaled')
plt.ylabel('Ridge Regression Score')
plt.legend(loc="lower left")

 

**"AI에게 벌금을 너무 적게 주면 암기만 하고(왼쪽), 너무 많이 주면 공부를 포기해버린다(오른쪽)"**는 것을 보여준다. 따라서 우리는 파란 선이 떨어지기 직전인 알파 1 수준에서 타협을 보는 것이 가장 현명하다.

'머신러닝' 카테고리의 다른 글

크롤링 해서 시각화  (0) 2026.01.12
머신러닝 - 분류, 군집, SVM  (0) 2026.01.12
머신러닝 - 선형 회귀 기초  (0) 2026.01.09
PANDAS  (0) 2026.01.08
SEABORN  (0) 2026.01.07