ML 세션 - Week3.회귀 알고리즘과 모델 규제

» AI

회귀

임의의 어떤 숫자를 예측하는 것

  • k-최근접 이웃 회귀
    • 샘플에 가장 가까운 샘플 k개 선택, 그러나 여기서 샘플 타깃은 어떤 클래스가 아니라 임의의 수치
    • 예측 타깃값은 보통 평균으로 구함
import numpy as np
perch_length = np.array(
    [8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0,
     21.0, 21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5,
     22.5, 22.7, 23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5,
     27.3, 27.5, 27.5, 27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0,
     36.5, 36.0, 37.0, 37.0, 39.0, 39.0, 39.0, 40.0, 40.0, 40.0,
     40.0, 42.0, 43.0, 43.0, 43.5, 44.0]
     )
perch_weight = np.array(
    [5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0,
     110.0, 115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0,
     130.0, 150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0,
     197.0, 218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0,
     514.0, 556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0,
     820.0, 850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0,
     1000.0, 1000.0]
     )
import matplotlib.pyplot as plt
plt.scatter(perch_length, perch_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

alt text

  • 농어 길이가 커짐에 따라 무게 늘어남 확인
from sklearn.model_selection import train_test_split # 훈련세트랑 테스트세트 나누기
train_input, test_input, train_target, test_target = train_test_split(
    perch_length, perch_weight, random_state=42)
train_input = train_input.reshape(-1, 1) #크기에 -1을 지정하면 나머지 원소 개수로 채우라는 뜻
test_input = test_input.reshape(-1, 1)
# 사이킷런에 사용할 훈련세트는 2차원 배열이어야 함 reshape() 메소드로 바꿔주기
from sklearn.neighbors import KNeighborsRegressor #k-최근접 이웃 회귀 알고리즘
knr = KNeighborsRegressor() #객체 생성
knr.fit(train_input, train_target) # k-최근접 이웃 회귀 모델 훈련
print(knr.score(test_input, test_target)) #0.992809....
  • 회귀에서는 정확한 숫자를 맞추는 것은 거의 불가능함
  • 결정계수(R^2)로 평가
    • 1-(타깃-예측)^2/(타깃-평균)^2
    • 타깃의 평균 정도를 예측하면 0에 가까워지고 예측에 아주 가까워지면 1에 가까워짐
#훈련세트의 결정계수 확인
print(knr.score(train_input, train_target)) #0.96988...
  • 훈련세트 점수가 테스트 세트 점수보다 낮음
  • 훈련세트에서 점수가 매우 좋았는데 테스트세트에서 점수가 매우 나쁘다면 과대적합 되었다고 함
    • 훈련세트에만 잘 맞는 모델이라는 뜻
  • 훈련세트보다 테스트세트 점수가 높거나 두 점수가 모두 매우 낮으면 과소적합 되었다고 함
    • 모델이 너무 단순해 훈련이 적절히 되지 않음
    • 현재 모델이 이에 속함
    • 모델을 복잡하게 만들면 됨 -> k 개수 줄이기(k개수 줄을수록 국지적 패턴에 민감해지고 k개수 많을수록 일반적 패턴 따름)
    •   knr.n_neighbors = 3 # 이웃의 갯수 3으로 설정
        knr.fit(train_input, train_target) # 모델 훈련
        print(knr.score(train_input, train_target)) # 0.9804899950518966
        print(knr.score(test_input, test_target)) # 0.9746459963987609
      
  • k-최근접 이웃의 한계
distances, indexes = knr.kneighbors([[50]]) # 50cm 농어의 이웃 구하기
plt.scatter(train_input, train_target) # 산점도
plt.scatter(train_input[indexes], train_target[indexes], marker='D') # 이웃샘플 표시
plt.scatter(50, 1033, marker='^') # 50cm 농어 데이터
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

alt text

  • 50cm 농어에서 가장 가까운 것은 45cm근방이기 때문에 오차가 큰 것
  • k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 타깃을 평균하기 때문에 새로운 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값 예측

선형 회귀

데이터의 특성을 잘 나타내는 직선을 찾아내는 것

from sklearn.linear_model import LinearRegression # 선형 회귀 알고리즘
lr = LinearRegression()
lr.fit(train_input, train_target) # 선형 회귀 모델 훈련
# 50cm 농어 예측
print(lr.predict([[50]])) #[1241.83860323]
print(lr.coef_, lr.intercept_)# 직선의 기울기와 절편, 흔히 모델 파라미터라고 함
  • 직선이라 한다면 y=ax+b 형태에서 기울기 a와 절편 b를 찾아내는 것
plt.scatter(train_input, train_target)
# 15에서 50까지 1차 방정식 그래프 그리기
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
plt.scatter(50, 1241.8, marker='^') # 50cm 농어 데이터
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

alt text

다항 회귀

  • 이 직선대로라면 농어의 무게가 0g 밑으로 내려가는 사례 발생 -> 1차원 직선이 아니라 2차원 곡선으로 만들어보기
  • 2차 방정식 그래프를 그리려면 길이를 제곱한 항이 훈련세트에 추가되어야 함
    • y= a * (길이)^2 + b * (길이) + c
    •   train_poly = np.column_stack((train_input ** 2, train_input)) #제곱한 항 훈련세트에 추가
        test_poly = np.column_stack((test_input ** 2, test_input))
      
lr = LinearRegression()
lr.fit(train_poly, train_target) # 다시 훈련
#모델이 훈련한 계수와 절편
print(lr.coef_, lr.intercept_) # [1.01...,-21.56...], 116.05
  • 이 모델이 학습한 그래프
    • 무게 = 1.01 * (길이)^2 - 21.6 * (길이) + 116.05
  • 이런 방정식을 다항식이라 부르고 다항식을 사용하나 선형회귀를 다항 회귀 라고 함
point = np.arange(15, 50) # 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만듦
plt.scatter(train_input, train_target) # 훈련 세트 산점도
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05) # 15에서 49까지 2차 방정식 그래프 그리기
plt.scatter([50], [1574], marker='^') # 50cm 농어 데이터
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

alt text

print(lr.score(train_poly, train_target)) # 0.9706807451768623
print(lr.score(test_poly, test_target)) # 0.9775935108325122
# 여전히 과소적합 조금 남아있음

다중 회귀

여러 개의 특성을 사용한 선형 회귀

  • 1개의 특성을 사용할 때 선형 회귀 모델이 학습하는 것은 직선
  • 2개의 특성을 사용할 때 선형 회귀 모델이 학습하는 것은 평면
  • alt text
  • 농어의 길이 뿐만 아니라 높이와 두께, (높이*길이)까지 사용
    • 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업을 특성공학이라고 함
import pandas as pd #판다스 불러오기
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy() #넘파이 배열로 변환
  • 사이킷런에는 특성을 만들거나 전처리 하기 위한 다양한 클래스 제공, 이를 변환기라 부름
from sklearn.preprocessing import PolynomialFeatures
  • PolynomialFeatures 클래스는 기본적으로 각 특성을 제곱한 항을 추가하고 특성끼리 서로 곱한 항을 추가
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input) # train_input 변환 데이터 train_poly에 저장
poly.get_feature_names() # ['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2']
test_poly = poly.transform(test_input) # 테스트세트 변환
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target) # 다중 회귀 모델 훈련
print(lr.score(train_poly, train_target)) # 0.9903183436982125
print(lr.score(test_poly, test_target)) # 0.9714559911594111
  • degree 매개변수를 이용해 특성 더 추가 가능
    poly = PolynomialFeatures(degree=5, include_bias=False) #5제곱까지 특성 추가
    
  • 하지만 이렇게 하고 훈련시킬 경우 훈련세트 점수는 매우 높지만 테스트 세트 점수가 매우 낮은 과대적합이 이루어짐

규제

머신러닝 모델이 훈련세트를 너무 과도하게 학습하지 못하도록 훼방하는 것 즉 과대적합 되지 않도록 만드는 것

  • 규제 적용 전 정규화 필요
    from sklearn.preprocessing import StandardScaler # 특성을 표준점수로 바꾸는 변환기
    ss = StandardScaler()
    ss.fit(train_poly) 
    # 훈련세트로 학습한 변환기를 사용해 테스트세트까지 변환해야함
    train_scaled = ss.transform(train_poly)
    test_scaled = ss.transform(test_poly)
    
  • 릿지 회귀
    • 계수를 제곱한 값을 기준으로 규제 적용
    •   from sklearn.linear_model import Ridge
        ridge = Ridge()
        ridge.fit(train_scaled, train_target)
        print(ridge.score(train_scaled, train_target)) # 0.9903183436982125
        print(ridge.score(test_scaled, test_target)) # 0.9714559911594111
      
    • 많은 특성을 사용했음에도 과대적합 되지 않고 좋은 성능
    • alpha 매개변수로 규제 강도 임의 조절 가능
      • alpha 값이 커지면 규제 강도가 세져 과소적합될 가능성 커짐
      • alpha 값이 작아지면 계수를 줄이는 역할이 줄어들고 과대적합될 가능성 커짐
      • 적절한 alpha 찾는 방법은 alpha 값에 대한 결정계수 그래프 그리기
      • 훈련세트와 테스트세트 점수가 가장 가까운 지점이 최적의 alpha
      •   import matplotlib.pyplot as plt
        
          train_score = []
          test_score = []
        
          alpha_list = [0.001, 0.01, 0.1, 1, 10, 100] # alpha 값 0.001에서 100까지 10배씩 늘리기
          for alpha in alpha_list:
              # 릿지 모델 만들기
              ridge = Ridge(alpha=alpha)
              # 릿지 모델 훈련
              ridge.fit(train_scaled, train_target)
              # 훈련 점수와 테스트 점수를 저장
              train_score.append(ridge.score(train_scaled, train_target))
              test_score.append(ridge.score(test_scaled, test_target))
        
          plt.plot(np.log10(alpha_list), train_score) #x축 로그로
          plt.plot(np.log10(alpha_list), test_score)
          plt.xlabel('alpha')
          plt.ylabel('R^2')
          plt.show()
        
      • alt text
      • 왼쪽은 훈련 세트와 테스트 세트 점수 차이가 큼 -> 과대적합
      • 오른쪽은 훈련 세트와 테스트 세트 점수가 모두 낮아지는 과소적합 보임
      • 적절한 alpha는 -1, 즉 0.1일 때
      •   ridge = Ridge(alpha=0.1)
        
  • 라쏘 회귀
    • 계수의 절댓값을 기준으로 규제 적용
    • 계수를 아예 0으로 만들 수도 있음
    •   from sklearn.linear_model import Lasso
      
        train_score = []
        test_score = []
      
        alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
        for alpha in alpha_list:
            # 라쏘 모델 만들기
            lasso = Lasso(alpha=alpha, max_iter=10000)
            # 라쏘 모델 훈련
            lasso.fit(train_scaled, train_target)
            # 훈련 점수와 테스트 점수를 저장
            train_score.append(lasso.score(train_scaled, train_target))
            test_score.append(lasso.score(test_scaled, test_target))
      
            plt.plot(np.log10(alpha_list), train_score) #x축 로그로
            plt.plot(np.log10(alpha_list), test_score)
            plt.xlabel('alpha')
            plt.ylabel('R^2')
            plt.show()
      
    • alt text
    • 왼쪽은 과대적합, 오른쪽으로 갈수록 점수차 좁혀지다가 가장 오른쪽은 점수 크게 떨어짐
    • 적절한 alpha는 1, 즉 10일 때
    •   lasso = Lasso(alpha=10)
        lasso.fit(train_scaled, train_target)
      
        print(lasso.score(train_scaled, train_target)) # 0.9888067471131867
        print(lasso.score(test_scaled, test_target)) # 0.9824470598706695
      
    • 라쏘 모델 계수 중 0인 것 확인
    •   print(np.sum(lasso.coef_ == 0)) # 40
      
    • 유용한 특성을 골라내는 용도로도 사용 가능