본 포스팅은 저를 포함한 책을 구입한 분들의 학습 정리를 위해 쓰여졌습니다.
03-1 k-최근접 이웃 회귀
이전에는 bream과 smelt의 2가지 특성 값(길이, 무게)을 가지고 신규 데이터를 분류하는 문제를 풀었다.
이번에는 신규 데이터의 길이 값 만을 가지고 무게를 예측하는 데이터 예측 모델을 만들어 본다. (이제 진짜 예측 관련 공부 시작!)
예측 모델을 만들기 위해선 일단 '회귀regression'에 대해 알아야한다. 참고로 이전에 학습한 방법은 '분류classification'이다. 회귀는 클래스 중 하나로 분류하는 것이 아니라 임의의 어떤 숫자를 예측하는 문제이다. 이 용어는 19세기 통계학자였던 프랜시스 골턴이 제창한 용어로, 키가 큰 사람의 아이가 부모보다 더 크지 않는다는 사실을 관찰하고 이를 평균으로 회귀한다고 표현을 하며 정립된 용어이다. 이후 회귀는 '두 변수 사이의 상관관계를 분석하는 방법'으로 쓰인다. 예를 들어, 내년도 경제성장률을 예측하거나, 배달이 도착할 시간을 예측하는 것도 회귀 문제다! 회귀는 정해진 클래스가 없고 임의의 수치를 출력한다.
그런데 사실 우리가 사용했던 k-최근접 이웃 알고리즘은 회귀에도 작동한다. 이 모델의 분류 알고리즘은 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한 뒤 샘플들의 '클래스class(ex. bream, smelt)'를 확인하여 다수 클래스(민주주의!)를 새로운 샘플의 클래스로 예측한다. 회귀는 샘플 k개를 선택하는 것은 동일하나, 클래스가 아닌 '임의의 수치'를 샘플의 타깃으로 설정한다. k개의 이웃 샘플의 수치를 사용해 평균 값을 구하면 샘플의 예측 타깃값이 되는 형태이다.
정리하자면, K-최근접 이웃 회귀 모델은 샘플 x의 주변에 이웃한 샘플 k개의 평균 값을 타깃 값(예측 값)으로 넣어주는 것이다. 이를 이용해 새로운 샘플의 타깃 값을 예측해보자
데이터를 입력하고 이를 산점도로 그려 확인한다.
#K-최근접 이웃 회귀
#지도 학습 알고리즘은 크게 분류(Classification)와 회귀(Regression)로 나뉨
#회귀: 두 변수사이의 상관관계를 분석하는 방법, 임의의 어떤 숫자를 예측하는 문제. 정해진 클래스가 없음. 즉 클래스별로 나누는 것이 아닌 수치로 판단
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()
길이에 따라 무게가 늘어나는 형태가 잘 이루어져 있다. 이제 훈련세트와 테스트 세트로 나눈다.
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)
단, 사이킷런에 사용할 훈련세트는 2차원이라는 것을 기억해야 한다. 그런데 우리가 나눈 배열은 1차원이었기 때문에 이를 2차원으로 바꾸어주어야 한다. 파이썬에서 1차원 배열의 크기는 원소가 1개인 튜플로 나타낸다. 즉, 1차원 배열인 [1, 2, 3]의 크기는 (3, )이 된다. 2차원 배열로 만들기 위해서는 하나의 열을 추가해주면 되는데 크기가 (3, 1)로 변경된다. 바꾸는 방법은 넘파이의 reshape() 메서드를 활용하는 것이다.
예시를 보면 이해가 쉽다.
#데이터가 1차원이기 때문에 2차원으로 변환 필요
test_array = np.array([1,2,3,4])
print(test_array.shape)
#결과값: (4,)
test_array = test_array.reshape(2, 2)
print(test_array.shape)
#결과값: (2,2)
reshape() 메서드는 바꾸려는 배열의 크기를 지정할 수 있는데, 지정한 크기와 원본 배열의 원소 개수가 다르면 에러가 나니 주의해야 한다. 예를 들어 (4, ) 크기의 배열을 (2, 3)으로 바꾸려고 하면 에러가 발생한다. 원본 배열의 원소는 4개 밖에 없는데 6개 크기의 배열로 바꾸었기 때문! 또한 배열 크기에 -1을 지정하면 지정한 배열 값 외의 나머지 원소 개수를 자동으로 모두 채워준다.
이런 특성을 이용해 첫 번째 크기를 나머지 원소 개수로 채워버리고 두번 째 크기를 1로 만들면 2차원 배열로 만들어진다.
#배열의 크기를 자동으로 지정해서 간단하게 변환하자
#크기에 -1을 지정하면 나머지 원소 개수로 모두 채우라는 의미
#두번째 크기를 1로 하려면 (-1,1)로 사용
train_input = train_input.reshape(-1,1)
test_input = test_input.reshape(-1,1)
print(train_input.shape, test_input.shape)
#결과값: (42, 1) (14, 1)
이렇게 만들면 배열의 전체 원소 개수를 굳이 알 필요가 없다. 마치 all과 같은 의미의 -1의 활용은 다른 곳에서도 자주 활용된다.
사이킷런에서 k-최근접 이웃 회귀 알고리즘을 구현한 클래스는 KNeighborsRegressor이다.
#결정계수 R^2
#K최근접이웃 회귀 알고리즘 임포트
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
#K-최근접 이웃 회귀 모델을 훈련
knr.fit(train_input, train_target)
#결정계수(coefficient of determination, R^2)를 가지고 평가
print(knr.score(test_input, test_target))
#결과값: 0.992809406101064
분류 문제와는 달리 회귀는 정확한 숫자를 맞히지 못한다. 예측하는 값이나 타깃 모두 임의의 수치이기 때문이다. 또한 분류 문제에서는 결과로 나온 점수가 샘플을 정확하게 분류한 개수의 비율 즉, 정확도였지만 회귀는 다른 값으로 평가한다.
회귀의 경우에는 평가를 결정하는 점수를 결정계수coefficient of determination 또는 R²이라고 부른다.
간만에 수식이 하나 나왔는데 그리 어렵지 않다. 여기서 y는 타깃값, y'는 예측값, y̅'은 타깃의 평균값이다. 각 샘플의 타깃값과 예측값의 차이를 제곱하여 더한 뒤, 타깃값과 타깃 평균값의 차이를 제곱하여 더한 값으로 나눈다는 의미이다(제곱을 하는 이유는 값의 차이가 음수 나오는 경우를 방지하기 위함이다). 만약 타깃값의 평균 정도를 예측하는 수준이라면(분자와 분모가 거의 같다면) 결정계수는 0에 가까워지고, 예측이 타깃에 아주 가까워지면 (분자가 0에 가까워지면) 1에 가까운 값이 된다. 결론적으로 예측값이 타깃 값에 가까울 수록 결정계수는 100%에 가까워 지는 것이다.
타깃과 예측한 값 사이의 차이를 구해보면 예측이 얼마나 벗어나 있는지 가늠할 수 있다. sklearns.metrics 패키지는 여러 가지 측정 도구를 제공하는데 이 중에서 mean_absolute_error는 타깃과 예측의 절대값 오차를 평균하여 반환한다.
from sklearn.metrics import mean_absolute_error
#테스트 세트에 대한 예측을 만듦
test_prediction = knr.predict(test_input)
#테스트 세트에 대한 평균 절댓값 오차를 계산
#mean_absolute_error() 함수는 타깃과 예측의 절댓값 오차를 평균하여 반환
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
#결과값: 19.157142857142862
예측이 평균적으로 19g 정도 차이 난다는 것을 알 수 있다.
지금까지 우리는 훈련세트를 사용해 모델을 훈련하고 테스트 세트로 모델을 평가하는 방식을 사용해 왔다. 그렇다면 테스트 세트 대신 훈련 세트로 모델을 평가한다면 어떻게 될까? score() 메서드에 훈련 세트를 전달하여 점수를 출력해 보자
print(knr.score(train_input, train_target))
#결과값: 0.9698823289099254
뭔가 이상하다. 우리는 훈련세트로 모델을 만들었는데, 해당 모델을 훈련 세트에 적용한 결과가 테스트 세트에 적용한 결과보다 낮다. 이런 경우 모델이 훈련세트에 과소적합underfitting되었다고 한다. 즉 모델이 너무 단순하여 훈련세트에 적절히 훈련되지 않은 경우이다. 반대로 과대적합overfitting은 훈련세트에 너무 잘 맞게 만들어져 테스트 세트나 다른 샘플을 예측할 때 제대로 작동하지 않는 경우를 말한다.
과소적합의 해결방안은 모델을 조금 더 복잡하게 만드는 것이다. 즉 훈련세트에 더 잘 맞게 만들어낸다. k-최근접 이웃 알고리즘으로 모델을 더욱 복잡하게 만드는 방법은 이웃의 개수를 줄이는 것이다. 이웃이 줄어들면 훈련 세트에 있는 국지적인 패턴에 더욱 민감해지고, 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따르게 된다.
사이킷런의 이웃 기본값은 5개로 지정되어 있다. 이를 3개로 바꾸어 준다. 이 때 n_neighbors의 속성값을 바꿔주면 된다.
#훈련세트와 테스트 세트의 점수를 비교했을 때, 훈련세트가 너무 높으면 과대적합, 그 반대이거나 두 점수가 모두 낮으면 과소적합
#훈련세트보다 테스트 세트의 점수가 높으면 과소적합임
#이웃의 개수를 3으로 설정 -> K-최근접 이웃 알고리증므로 모델을 더 복잡하게 만드는 방법은 이웃의 개수를 줄이는 것
#이웃의 개수를 줄이면 훈련세트에 있는 국지적인 패턴에 민감해지고, 늘리면 일반적인 패턴을 따름
knr.n_neighbors =3
#모델을 다시 훈련
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
#결과값: 0.9804899950518966
k 값을 줄였더니 훈련세트 결정계수의 점수가 높아졌다! 테스트 세트의 점수도 확인해보자.
print(knr.score(test_input, test_target))
#결과값: 0.9746459963987609
테스트세트의 점수는 약간 낮아졌지만 훈련세트에 과소적합 문제는 해결되었다. 점수차이도 크지 않은걸 보면 과대적합이라 보기도 어렵다.
03-2 선형회귀
만들어 놓은 모델에 50cm 크기의 bream을 가져다 놓아봤다.
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])
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)
#훈련세트와 테스트 세트를 2차원 배열로 변환
train_input = train_input.reshape(-1,1)
test_input = test_input.reshape(-1,1)
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor(n_neighbors=3)
#K-최근접 이웃 회귀 모델을 훈련
knr.fit(train_input, train_target)
print(knr.predict([[50]]))
#결과값: [1033.333333]
기존 훈련세트의 최대 값이었던 45cm 보다 큰 bream을 가져다 놓았는데, 결과는 훈련세트의 최대 값보다 작은 수치가 튀어나왔다. 산점도를 그려 무엇이 문제인지 확인해보자. kneighbors() 메서드를 이용해 가장 가까운 이웃까지의 거리와 이웃 샘플의 인덱스를 얻는다.
#훈련세트와 5cm 농어와 최근접 이웃을 산점도 표시
import matplotlib.pyplot as plt
#50cm 농어의 이웃을 구함
distances, indexes = knr.kneighbors([[50]])
#훈련세트의 산점도를 구함
plt.scatter(train_input, train_target)
#훈련세트 중에서 이웃 샘플만 다시 그림
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
#50cm 농어 데이터
plt.scatter(50, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
기존 데이터는 길이가 커질 수록 무게가 증가하는 '경향'을 가지고 있었는데, 신규 데이터인 50cm는 경향에서 벗어나 있다. 이유는 당연하다. 50cm에 해당하는 샘플의 가장 가까운 이웃이 45cm 대에 몰려있고, 이 이웃의 평균 값으로 측정하기 때문이다.
print(np.mean(train_target[indexes]))
print(knr.predict([[100]]))
#결과값: 1033.333333
#결과값: 1033.333333
100cm의 농어를 넣어도 마찬가지다. 이런 식으로 계산을 하면 bream이 아무리 커도 무게가 늘어나지 않는다. 따라서 이웃회귀 모형은 주기적으로 데이터를 갱신하여 훈련해야 한다.
이러한 문제를 해결하기 위해 '선형 회귀linear regression'을 사용한다. 이 알고리즘은 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘이다. 사이킷런에서는 sklearn.linear_model 패키지 아래에 LinearRegression 클래스로 선형 회귀 알고리즘이 구현되어 있다.
#선형 회귀모델
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
#선형 회귀모델 훈련
lr.fit(train_input, train_target)
#50cm 농어에 대해 예측
print(lr.predict([[50]]))
#결과값: [1241.83860323]
그나마 무게 예측값이 올라갔다. 선형 회귀는 직선을 그려 학습한다. y= ax+b와 같은 1차방정식의 형태로 나타낼 수 있고, x는 bream의 길이 y는 bream의 무게로 바꾸어서 생각하면 편하다. LinearRegression 클래스는 최적의 a, b의 값을 찾아내는 데 이건 lr 객체의 coef_와 intercept_속성에 저장되어 있다.
#기울기(coefficeint)와 상수 절편(intercept)을 구함
#모델 파라미터(model parameter) -> 모델기반학습 *K최근접이웃은 사례기반학습으로 모델파라미터가 없음
print(lr.coef_, lr.intercept_)
#결과값: [39.01714496] -709.0186449535477
coef_는 coefficient의 약자로 '계수' 또는 가중치(weight)이라 부른다. 좌표평면 상에서 불릴 때는 기울기로 표현한다. 그리고 절편은 그냥 영어로 intercept다. (역시 영어를 잘 이해해야....)
coef_와 intercept_는 머신러닝 알고리즘이 찾은 값이라는 의미로 모델 파라미터model parameter라고 부른다. 머신러닝 알고리즘의 훈련 과정은 최적의 모델 파라미터를 찾는 것과 동일한 의미다. 그리고 이를 모델 기반 학습이라고 부른다. 앞서 사용한 k-최근접 이웃에는 모델 파라미터가 없다. 훈련세트를 저장하는 것이 훈련의 전부였는데 이는 사례 기반 학습이라고 한다.
이 값을 가지고 bream의 길이 15에서 50까지의 직선 그래프를 그려보자. (15, 15 * coef_ - intercept_)와 (50, 50 * coef_ - intercept_)의 두 점을 이으면 끝이다. 훈련세트와 함께 그려 비교한다.
#훈련세트의 산점도를 그림
plt.scatter(train_input, train_target)
#15에서 50까지 1차방정식 그래프를 그림
plt.plot([15,50],[15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
#50cm 농어데이터
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
이 직선이 선형 회귀 알고리즘이 이 데이터셋에서 찾은 최적의 직선이다. 이제 훈련세트와 테스트 세트에 대한 결정계수 점수를 확인해보자.
#훈련세트와 테스트세트에 대한 결정계수 점수 확인
print(lr.score(train_input, train_target)) #훈련세트
print(lr.score(test_input, test_target)) #테스트세트
"""
결과값:
0.939846333997604
0.8247503123313558
"""
훈련세트와 테스트세트의 점수 차가 많이 벌어져 있다. 그러나 이 모델을 과대적합 되었다고 보기엔 훈련세트의 점수도 많이 높지는 않다. 이 비밀은 앞서 본 산점도 안에 있는데, 직선 그래프의 문제점이다. 직선대로 예측을 할 경우 bream의 무게가 0g 이하인 경우가 생길 수 있고, 산점도 자체가 직선보다는 구부러진 곡선에 가깝다. 우리가 중고등학교때 배운 지수 함수를 생각하면 편하다.
곡선을 나타내기 위해서는 2차 방정식의 그래프를 그려야 한다. 그러려면 길이를 제곱한 항(x²에 해당하는!)이 훈련세트에 추가되어야 하는데 역시 여기서도 넘파이를 이용한다. bream의 길이를 제곱하여 원래 데이터 앞에 붙여본다. 열을 쌓아주는 column_stack() 함수를 사용해보자.
#제곱을 통해 곡선함수로 만들기
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np. column_stack((test_input ** 2, test_input))
print(train_poly.shape, test_poly.shape)
#결과값: (42, 2) (14, 2)
아름답게도 넘파이 브로드캐스팅이 자동으로 적용된다. 이 train_poly를 사용해 선형회귀 모델을 다시 훈련한다. 다만, 타깃값은 그대로 사용한다. 목표하는 값은 어떤 그래프를 훈련하던지 바꿀 필요가 없다. 다만 샘플을 테스트 할 때는 세트의 형식에 맞추어 제곱과 원래 길이를 함께 넣어준다.
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.predict([[50**2, 50]]))
#결과값: [1573.98423528]
예측 값이 앞서 훈련한 모델보다 훨씬 낫다! 계수와 절편을 출력해보자.
print(lr.coef_, lr.intercept_)
#결과값: [ 1.01433211 -21.55792498] 116.0502107827827
이 결과를 가지고 생각해보면
무게 = 1.01 * 길이² - 21.6 * 길이 + 116.05
라는 그래프를 학습했단 것을 알 수 있다. 이런 방정식을 다항식이라 부르며, 다항식을 사용한 선형 회귀를 다항 회귀polynominal regression라 부른다. 2차 방정식의 계수와 정편 a, b, c를 알았으니 이전과 동일하게 훈련 세트의 산점도에 그래프로 그려 본다. 곡선을 그리는 방법은 짧은 직선을 이어서 그리는 것인데, 1씩 짧게 끊어서 그리자.
#구간별 직선을 그리기 위해 15에서 49까지 정수배열을 만듦
point = np.arange(15, 50)
#훈련세트의 산점도를 그림
plt.scatter(train_input, train_target)
#15에서 49까지 2차 방정식 그래프를 그림
plt.plot(point, 1.01*point**2 - 21.6*point +116.05)
#50cm 농어데이터
plt.scatter(50, 1574, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
단순 선형 그래프 보다 훨씬 낫다! 이 모델로 훈련세트와 테스트 세트의 결정계수 점수를 평가해보면
#훈련세트와 테스트세트의 결정계수를 평가
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
"""
결과값:
0.9706807451768623
0.9775935108325122
"""
이전 점수보다 훨~씬 좋아졌다. 테스트점수가 약간 높은걸 보니 약간 과소적합이 남아있다.
이럴땐? 조금 더 복잡한 모델로 만든다!
03-3 특성공학과 규제
이번에는 조금 더 복잡한 모델을 만들기 위해 높이와 두께 특성을 함께 이용한다. 여러 특성을 사용한 선형 회귀는 다중 회귀multiple regression이라 부른다. 특성이 한 개면 '직선'을 학습하지만, 두 개면 '평면 입체'를, 3개 이상은 '고차원'을 학습한다. 특성이 두개면 타깃값과 함께 3차원 공간을 형성하고 선형 회귀 방정식인
타깃 = a * 특성1 + b x 특성2 + 절편
이 된다. 3개 이상일 경우는 공간을 그리거나 상상해 볼 순 없지만 식을 이용해 매우 복잡한 모델을 표현할 수는 있다.
이번에는 bream의 길이, 높이, 두께를 모두 함께 사용하며, 각각의 특성을 제곱하여 추가한다. 여기에 각 특성을 서롭 곱해서 또 다른 특성을 만들어 낸다. 예를 들어, 'bream 길이 * bream 높이'라는 특성도 만드는 것이다. 이렇게 기존의 특성을 이용해 새로운 특성을 뽑아내는 작업을 특성 공학feature engineering이라 한다.
이걸 우리가 직접하느냐? 사이킷런은 모든걸 제공한다! 특성공학 작업을 위한 도구를 사용해보자
이번엔 판다스pandas라는 데이터 분석 라이브러리를 이용해 데이터를 인터넷에서 바로 다운로드하여 사용하려 한다. (언제까지 데이터 복붙에 의존할 수는 없다...) 판다스에서 제공하는 기능 중 데이터프레임dataframe은 판다스의 핵심 데이터 구조로 넘파이 배열과 비슷하게 다차원 배열을 다루지만 훨씬 많은 기능을 제공한다. 더불어 넘파이 배열로도 쉽게 바꿀 수 있다는 장점이 있다. 인터넷에 올려진 데이터를 읽는 방법은 판다스의 read_csv()함수에 주소를 넣어 읽어오기만 하면 된다. 그리고 이를 to_numpy() 메서드를 사용해 넘파이 배열로 바꿔준다. 타깃값은 어차피 특성이 하나니까 이전과 동일하게 준비한다.
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(perch_full)
"""
결과값
[[ 8.4 2.11 1.41]
[13.7 3.53 2. ]
[15. 3.82 2.43]
[16.2 4.59 2.63]
...
"""
import numpy as np
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])
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)
훈련과 테스트 세트로 나누는 과정까지 끝냈다. 이 데이터를 이용해서 새로운 특성을 만든다.
사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공하는데 이런 클래스를 변환기transformer라고 부른다. 변환기 클래스는 모두 fit(), transform() 메서드를 제공한다. 이 장에서 사용할 변환기는 PolynominalFeatures 클래스로 sklearn.preprocessing 패키지에 포함되어 있다.
#변환기(transformer): 특성을 만들거나 전처리하기 위한 클래스
from sklearn.preprocessing import PolynomialFeatures
#2개의 특성 2와 3으로 이루어진 샘플을 하나 적용
poly = PolynomialFeatures()
poly.fit([[2,3]])
print(poly.transform([[2,3]]))
#결과값: [[1. 2. 3. 4. 6. 9.]]
fit() 메서드로 특성을 훈련하고 transform()메서드로 특성을 새롭게 만들었다. fit()메서드는 새롭게 만들 특성 조합을 찾고 transform()메서드는 실제로 데이터를 변환한다. 그런데 뭔가 이상하다. 한방에 transform으로 특성을 변환해주면 될 것을 꼭 fit()메서드를 써야한다는게 마음에 들지 않는다. 책에서는 사이킷런의 일관된 api 때문이라고는 하는데.... 어쨌든 두 메서드를 하나로 붙인 fit_transform()메서드도 존재하니 이걸 사용하는 것도 방법이다.
참고로 변환기는 타깃 데이터 없이 입력데이터를 변환한다. 여기서 나온 값은 2와 3을 요리조리 계산해서 나온 값이다. 그.런.데. 1은.... 왜 나왔는가?
무게 = a * 길이 + b * 높이 + c * 두께 + d * 1
선형 방정식의 절편은 항상 값이 1인 특성과 곱해지는 계수라고 볼 수 있다. 이렇게 보면 특성은 길이, 높이 두께, 1이 되지만 어차피 사이킷런의 선형 모델은 자동으로 절편intercept을 추가하므로 굳이 이렇게 특성을 만들 필요는 없다. 1을 없애기 위해서는 include_bias=False로 지정해 다시 특성을 변환해준다.
#1이 추가되는 이유는 절편이 항상 1인 특성과 곱해지는 계수로 여겨지기 때문, 사이킷런의 선형모델은 자동으로 절편을 추가함으로 굳이 특성을 이렇게 만들 필요 없음
#include_bias=False로 특성변환 -> 사실 꼭 지정할 필요는 없음
#절편을 위한 항이 제거되고 특성의 제곱과 특성끼리 곱한 항만 추가
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2,3]])
print(poly.transform([[2,3]]))
#결과값: [[2. 3. 4. 6. 9.]]
1이 사라졌다. 사실 include_bias = False를 명시적으로 지정하지 않아도 사이킷 런 모델은 자동으로 특성에 추가된 절편의 항을 무시한다. 그래도 우리는 학습 과정이니까 지정해줬다.
이 방식으로 train_input에 적용하고 train_input을 변환한 데이터를 train_poly에 저장하고 이 배열의 크기를 확인해 본다.
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
#결과값: (42, 9)
9개의 특성(열)이 나왔다. 이 특성이 어떻게 만들어졌는지 확인 할 수도 있다. PolynominalFeatures 클래스는 9개의 특성이 어떻게 만들어졌는지 확인하는 방법을 제공한다. get_feature_names() 메서드를 호출하면 9개의 특성이 각각 어떤 입력의 조합으로 만들어졌는지 알려준다.
poly.get_feature_names()
#결과값: ['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2']
여기서 x0은 첫 번째 특성을 의미하고, x0^2은 첫 번째 특성의 제곱, x0 x1은 첫 번째 특성과 두 번째 특성의 곱을 나타내는 식이다.
테스트세트도 변환한다. 물론 훈련세트에 적용했던 변환기로 테스트 세트를 변환해야 한다. 사실 PolynominalFeatures 클래스는 별도의 통계값을 구하지 않기 때문에 테스트 세트를 따로 변환해도 되지만 그런 습관이 들면 안된다. 무조건 훈련세트를 기준으로 테스트 세트를 변환하는 습관을 들여야 한다.
#테스트세트 변환
test_poly=poly.transform(test_input)
다중 회귀 모델이나 선형 회귀 모델을 훈련하는 것은 같다. LinearRegression 클래스를 임포트하고 앞에서 만든 train_poly를 사용해 모델을 훈련시킨다.
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
"""
결과값: 0.9903183436982124
결과값: 0.9714559911594134
"""
점수가 아주 만족스럽게 높다. 테스트 세트는 거의 차이가 없지만 특성을 여러 개 사용하니 과소적합 문제는 사라졌다. 특성을 더 많이 추가해볼까? 3제곱, 4제곱을 넣어보는 것이다. PolynominalFeatures 클래스의 degree 매개변수를 사용해 필요한 고차항의 최대 차수를 지정할 수 있다. 5제곱을 시전해보자.
#필요한 고차항의 최대 차수 지정, 5제곱
poly = PolynomialFeatures(degree=5, include_bias = False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
print(train_poly.shape)
#결과값: (42, 55)
열이 55개로 늘어났다. 여기서 특성은 열로 표현된다. 이걸로 선형회귀모델을 다시 훈련해보면
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
#결과값: 0.9999999999991097
무려 1에 가까운 결정계수가 나온다! 테스트 세트로 평가한 점수도 보자
print(lr.score(test_poly, test_target))
#결과값: -144.40579242684848
어...? 음수가 나올 수도 있구나... 결정계수의 분자 값이던 타깃 값과 예측값의 차이가 엄청나게 벌어진 것이다. 과대적합의 끝이라 할 수 있다. 사실 지금 단계는 말이 안된다. bream 샘플의 개수는 42개인데 특성은 55개로 더 많다. 그렇기 때문에 과대적합이 심각하게 발생하는 것이다. 특성을 줄이는 방법도 과대적합을 막을 수 있겠지만, 다른 방법을 학습해보자
규제regularization은 머신러닝 모델이 훈련세트를 너무 과도하게 학습하지 못하도록 훼방하는 것을 말한다. 선형 회귀 모델의 경우에는 특성에 곱해지는 계수(기울기)의 크기를 작게 만들어 보편적인 패턴을 만드는 것과 같다. 계수를 규제하여 훈련세트의 점수를 낮춰 보자
지금까지는 회귀 모델에 있어 특성의 스케일을 고려하지 않았지만, 규제를 할 때는 특성 간 스케일을 고려해주어야 한다. 특성의 스케일이 정규화되지 않으면 곱해지는 계수값이 차이가 나버린다. 이 때 규제를 통해 제어를 해버리면 공정하게 결과가 나오지 않는다. 규제 전에는 정규화를 해주자. 예전엔 직접 평균과 표준편차를 구해서 특성을 표준점수로 바꾸었지만 이번엔 사이킷런의 StandardScaler 클래스를 이용한다. 이것도 변환기transformer의 하나다.
#엄청나게 과대적합되었음을 확인
#이럴 때 과도한 학습을 방해하는 '규제(regularization)' 적용
#그런데 규제를 적용할 때 계수의 값이 많이 다르면 공정하게 제어가 되지 않음
#정규화 필요
#표준점수로 바꾸거나 Standardscaler 클래스 사용
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
StandardScaler 클래스의 객체 ss를 초기화하여 넣어주고, PolynominalFeatures 클래스로 만든 train_poly를 사용해 이 객체를 훈련한다. 훈련세트에서 학습한 평균과 표준편차는 StandardScaler 클래스 객체의 mean_, scale_ 속성에 저장되어 있고 특성마다 계산하므로 55개 특성 모두의 평균과 표준편차가 들어 있다.
선형 회귀 모델에 규제를 추가한 모델을 릿지ridge와 라쏘lasso라고 부른다. 규제 방식에 따라 모델이 나뉘는데 릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 둘 다 표준화된 계수가 음수가 나오는 것을 방지하기 위함이다. 수학에선 일반적으로 제곱을 한 값을 많이 사용하므로 릿지가 더 선호된다. 두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0으로 만드는 것도 가능하다.
릿지와 라쏘 모두 sklearn_model 패키지 안에 있는데, 모두 사용법은 같다.
모델 객체 만들기 → fit() 메서드로 훈련 → score() 메서드 평가
릿지 먼저 훈련세트 평가와 테스트세트 평가를 해보자
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
"""
결과값:
0.9896101671037343
0.9790693977615397
"""
만족스런 결과다. 특성을 많이 이용했지만 과대적합 되지 않았다. 규제의 양을 임의로 조절할 수 있는데, 모델 객체를 만들 때 alpha 값을 크게 만들면 규제강도가 커지고 계수 값을 더욱 줄여 과소적합 되도록 유도한다. alpha값이 작으면 반대로 계수를 줄이는 역할이 줄고 선형 회귀 모델과 유사해져 과대적합 가능성이 커진다. 이건 릿지 모델이 학습하는 값이 아니라 사전에 사람이 직접 지정해야 하는 값인데, 사람이 알려줘야하는 매개변수parameter를 하이퍼파라미터hyperparameter라고 부른다.
적절한 alpha값을 찾는 방법은 alpha값에 대한 결정계수 값의 그래프를 그려보는 것이다. 훈련세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha 값이 된다. 맷플롯립을 임포트해 alpha값을 바꿀 때마다 score 메서드의 결과를 저장할 리스트를 만든다음 alpha값을 0.001에서 100까지 10배씩 늘려가며 릿지회귀 모델을 훈련한 다음 훈련세트와 테스트 세트의 점수를 파이썬 리스트에 저장한다.
#모델 객체를 만들 때 alpha 매개변수로 규제의 강도 조절 가능
#alpha 값이 크면 규제의 강도가 세지므로 계수의 값을 더 줄여 조금 더 과소적합되도록 유도
#alpha의 값이 작으면 계수를 줄이는 역할이 줄어들고 선형회귀모델과 유사해지므로 과대적합될 가능성이 큼
#alpha값은 릿지모델이 학습하는 값이 아닌 사전에 지정해야하는 값
#이렇게 머신러닝 모델이 학습할 수 없고 사람이 알려줘야하는 파라미터를 하이퍼파라미터(hyperparameter)라고 부름
#머신러닝 라이브러리에서 하이퍼파라미터는 클래스와 메서드의 매개변수로 표현
#적절한 alpha값을 찾는 한 가지 방법은 alpha값에 대한 결정계수 값의 그래프를 그려보는 것임
#훈련세트와 테스트세트의 점수가 가장 가까운 지점이 최적의 alpha값
#맷플롯립을 임포트하고 alpha 값을 바꿀 때마다 score() 메서드의 결과를 저장할 리스트를 만듦
import matplotlib.pyplot as plt
train_score = []
test_score = []
#alpha값을 0.001부터 100까지 10배씩 늘려가며 릿지회귀 모델을 훈련한 다음 점수를 리스트에 저장
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
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))
이제 그래프를 그려 확인해보는데 값이 0.001부터 10배씩이니까 이대로 그래프 그리면 큰 일 난다. 범위가 모두 달라 그래프 왼쪽만 촘촘해지는 상황이 발생한다. alpha_list에 있는 6개의 값을 동일한 간격으로 나타내려면 로그함수로 바꿔 값을 지수로 표현해야 한다. 즉 0.001은 -3, 0.01은 -2가 되는 식이다.
넘파이 로그 함수는 np.log()와 np.log10()이 있다. 전자는 자연 상수 e를 밑으로하는 자연로그이고 후자는 10을 밑으로 하는 상용로그이다. 이제 그래프를 그려보자
#그래프로 그리기: alpha_list에 있는 6개의 값을 동일한 간격으로 나타내기 위해 로그함수로 바꾸어 지수로 표현
#즉, 0.001은 -3, 0.01은 -2가 되는 식
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()
위는 훈련세트 그래프 아래는 테스트 세트 그래프다. 그래프 왼쪽은 과대적합의 모습이 보이고 오른쪽은 모든 결정계수의 값이 줄어드는 과소적합의 모습이 보인다. 여기서 알 수 있는건 적절한 alpha값이 -1이라는 것이다. alpha값을 -1에 해당하는 0.1로 바꾸어 (아까 지수 값으로 변환했다는 것을 기억하자) 규제해본다.
#그래프 확인 결과 적합한 alpha값은 두 그래프가 가장 가깝고 테스트 세트의 점수가 가장 높은 -1, 즉 0.1임
#0.1를 alpha값으로 하여 최종 모델 훈련
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
"""
결과값:
0.9903815817570366
0.9827976465386926
"""
알파값을 지정해주지 않았던 때보다 점수가 더 올랐다. 과소나 과대적합도 없다.
라쏘도 릿지와 훈련방식은 같다.
#라쏘회귀모델 사용
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
"""
결과값:
0.989789897208096
0.9800593698421883
"""
라쏘도 결과값이 괜찮다. 여기서도 앞과 같이 alpha값을 바꾸어 가며 훈련 세트와 테스트 세트에 대한 점수를 계산한다. 이번엔 그래프까지 한번에 그린다.
#라쏘모델도 alpha 매개변수로 규제강도 조절 가능
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)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()
이 그래프도 왼쪽은 과대적합이고 오른쪽에서 갑자기 훈련세트와 테스트 세트의 점수가 좁혀지고 크게 점수가 떨어진다. 오른쪽은 과소적합이다! 최적의 alpha값은 1, 즉 10이다(잊지말자 로그함수). 이 값으로 다시 모델을 훈련한다.
#그래프 확인결과 최적의 alpha값은 1, 즉 10임. 이값으로 다시 모델 훈련
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
"""
결과값:
0.9888067471131867
0.9824470598706695
"""
아까보다 테스트세트의 점수가 약간 더 올랐다. 라쏘 모델은 계수 값을 아예 0으로 만들 수 있다고 했다. 라쏘모델의 계수는 coef_에 저장되어 있을테니 여기서 0인 것을 찾아보자. np.sum()은 함수의 배열을 모두 더한 값을 반환한다. 넘파이 배열에 비교 연산자를 사용했을 때 각 원소는 True 또는 False가 되는데, np.sum() 함수는 True를 1로, False를 0으로 인식하여 덧셈을 할 수 있기 때문에 비교 연산자에 맞는 원소 개수를 헤아리는 효과를 낸다.
#라쏘 모델의 계수 중 0인 것은 몇 개일까?
print(np.sum(lasso.coef_==0))
#결과값: 40
#라쏘는 이렇게 쓰지 않는 특성도 있기 때문에 유용한 특성을 골라내는 용도로 사용 가능
0이 된 계수가 40개나 된다는 말은 라쏘 모델은 유용한 특성으로 15개만 쓰고 나머진 유용하지 않다고 판단한 것이다. 이렇게 선형 회귀 모델의 규제까지 알아보았다.
결론
주어진 데이터의 특성(입력값)을 이용해 비어 있는 타깃값(예측값)을 도출하기 위한 지식을 학습했다. 사례기반 학습을 활용하는 k-최근접이웃 회귀 모델을 활용하여 회귀모델과 결정계수R²의 개념, 과대-과소 적합 해결 방안을 살펴보고, 사례기반 학습모델이 나타내는 문제점을 해결할 수 있는 선형 회귀 모델과 직선 형태에 비해 예측성을 높일 수 있는 다항회귀 방식의 활용 방안을 알아봤다. 선형모델이란 결국 최적의 기울기와 절편을 구하는 것으로 이 값은 선형 회귀 모델의 coef_와 intercept_ 속성에 저장되어 있다. 특성을 변환하여 새로운 특성을 만든 다음 더욱 다양해진 특성 종류를 활용해 다중 회귀 모델을 훈련하였으며, 지나친 과대적합을 방지하기 위한 규제의 방법론 중 라쏘 회귀와 릿지 회귀 또한 이용하였다.
여기서 지금까지 배운 머신러닝 과정에 대해 살짝 정리해본다. 각 항목의 하위 항목은 단순 결과나 optional한 과정으로 정리하였다.
머신러닝 모델링 순서
1. 학습을 위한 데이터를 준비한다:
- 이 책에선 데이터 수집 과정은 따로 다루지 않았다
2. 데이터를 불러 온다:
- pandas의 to_read() 메서드를 활용한다
3. 데이터를 훈련세트와 테스트세트로 나눈다:
- sklearn에 있는 model_selection 모듈의 train_test_split() 메서드를 활용한다
4. 훈련세트와 테스트세트를 모델의 학습이 가능하도록 2차원 배열로 만든다:
- 넘파이의 reshape() 메서드를 활용한다.
5. 구현할 알고리즘 클래스를 객체를 생성해 담아 준다:
- ex) knr = KneighborsRegressor()
6. 객체에 fit()메서드를 활용해 훈련데이터(입력값, 타깃값)를 넣고 훈련한다:
- fit(입력값, 타깃값)
7. 훈련한 객체를 가지고 훈련세트와 테스트 세트에 한번씩 넣어 점수를 출력한다. 사례기반 학습(분류)의 경우 정확도를 도출하고 모델기반학습(회귀)의 경우에는 결정계수R²을 도출한다:
- score(입력값, 타깃값)
7-1. (optional) 테스트 세트에 대한 예측을 만들어 테스트 세트에 대한 평균 절댓값 오차를 계산한다.
이를 통해 결과에서 예측이 평균 얼마나 다른지 확인 가능하다:
- 새로운 객체에 predict() 메서드로 예측값을 담고 mean_absolute_error(타깃값, 예측값)함수로
평균 오차를 계산한다.
----
1차 모델링 끝
결과 값 확인 뒤 n차 모델링 과정
1. 과대적합 과소적합을 판별한 뒤 과소적합이 일어날 경우 훈련세트를 더 복잡하게 만들고 과대적합이 일어날 경우 훈련을 더 일반화 시킬 수 있도록 복잡성을 줄인다:
- k-최근접 이웃모델의 경우 이웃의 개수가 적을수록 국지적 패턴에 민감해져 과소적합을 방지한다.
선형 회귀 모델의 경우 특성 공학을 활용해 다항 또는 다중 회귀 모델로 제작하여 선형 그래프의 모양을
학습 데이터의 모습과 더욱 가깝게 그려낸다. sklearn의 preprocessing 모듈에서 fit_transform() 메서드를
활용해 기존 특성을 변환시킨 뒤 모델링을 진행한다.
2. 특성의 수가 변환기에 의해 과도하게 많아져 과대적합이 된다면 규제를 위해 릿지 혹은 라쏘 규제를 활용한다. 이 때 계수 값을 균일하게 맞춰주기 위해 특성의 스케일을 변환해준다:
- sklearn의 preprocessing 모듈에서 표준점수를 구하는 StandardScaler 클래스를 객체에 담아 주고,
훈련세트로 fit_transform()메서드를 이용해 학습 및 변환작업을 해준다. 여기서 Ridge()나 Lasso() 클래스
를 사용하여 학습(fit)과 점수 도출(score)를 진행한다.
2-2. (optional) alpha값의 조정을 통해 규제 강도를 조절하여 적합성을 높힌다:
릿지 예시 ex) alpha 매개변수를 for 문을 이용해(for alpha in alpha_list:)
0.001에서 100까지 10배씩 늘려가며 (alpha_list=[0.001, 0.01, 0.1...])
모델을 만든 뒤(ridge = Ridge(alpha=alpha))
각각 훈련하고(ridge.fit()) 점수를 저장한다.(train_score.append(ridge.score())
이를 로그함수 그래프로 그려 가장 테스트와 훈련세트가 가장 가깝게 닿아 있는 값을
alpha값으로 사용한다. (라쏘도 동일하다)
if 문제가 생겼다면? 산점도 그래프를 그려 눈으로 확인한다!
1. 산점도 표시하기
1) matplotlib을 임포트하여 pyplot 모듈을 불러온다: import matplotlib.pyplot as plt
2) scatter() 메소드로 산점도를 그린다.
3) 특정 데이터는 값을 넣어주고 'marker=' 파라미터를 이용해 따로 표시해 준다.
4) xlabel(), ylabel(), legend() 등으로 축과 데이터의 타이틀을 달아준다.
Key Word
회귀(regression): 임의의 수치를 예측하는 문제. 타깃 값도 임의의 수치가 된다. 이 용어는 19세기 통계학자였던 프랜시스 골턴이 제창한 용어로, 키가 큰 사람의 아이가 부모보다 더 크지 않는다는 사실을 관찰하고 이를 평균으로 회귀한다고 표현을 하며 정립된 용어이다. 이후 회귀는 '두 변수 사이의 상관관계를 분석하는 방법'으로 쓰인다. 예를 들어, 내년도 경제성장률을 예측하거나, 배달이 도착할 시간을 예측하는 것도 회귀 문제다! 회귀는 정해진 클래스가 없고 임의의 수치를 출력한다.
결정계수(coefficient of determination 또는 R²): 대표적인 회귀 문제의 성능 측정 도구. 1에 가까울수록 좋고, 0에 가깝다면 성능이 나쁜 모델이다. y는 타깃값, y'는 예측값, y̅'은 타깃의 평균값이다. 각 샘플의 타깃값과 예측값의 차이를 제곱하여 더한 뒤, 타깃값과 타깃 평균값의 차이를 제곱하여 더한 값으로 나눈다는 의미이다(제곱을 하는 이유는 값의 차이가 음수 나오는 경우를 방지하기 위함이다). 만약 타깃값의 평균 정도를 예측하는 수준이라면(분자와 분모가 거의 같다면) 결정계수는 0에 가까워지고, 예측이 타깃에 아주 가까워지면 (분자가 0에 가까워지면) 1에 가까운 값이 된다. 결론적으로 예측값이 타깃 값에 가까울 수록 결정계수는 100%에 가까워 지는 것이다.
과대적합(ovefitting): 모델의 훈련세트 성능이 테스트 세트 성능보다 훨씬 높은 경우 발생하는 형상. 모델이 훈련세트의 수치에 너무 집착해서 데이터에 내재된 거시적인 패턴을 감지하지 못한다.
과소적합(underfitting): 과대적합과 반대로 훈련세트와 테스트 세트의 성능이 모두 동일하게 낮거나 테스트 세트의 성능이 오히려 높을 때 일어하는 현상 이런 경우 더 복잡한 모델을 사용해 훈련세트에 잘 맞는 모델을 만들어야 한다.
선형 회귀(linear regression): 특성과 타깃 사이의 관계를 잘 나타내는 선형 방정식(계수, 절편)을 찾는다. 특성이 하나면 직선 방정식이 된다. 선형 회귀가 찾은 특성과 타깃 사이의 관계는 선형 방정식의 계수 또는 가중치에 저장된다. 머신러닝에서 종종 가중치는 방정식의 기울기와 절편 모두를 의미하기도 한다.
다항 회귀(polynominal regression): 다항식을 사용하여 특성과 타깃 사이의 관계를 나타내며, 이 함수는 비선형일 수 있지만 여전히 선형 회귀로 표현된다.
모델 파라미터(model parameter): 선형 회귀가 찾은 가중치처럼 머신러닝 모델이 특성에서 학습한 파라미터를 말한다. 참고로 사람이 직접 찾는 파라미터는 하이퍼 파라미터hyper parameter라 한다. 머신러닝 알고리즘의 훈련 과정은 최적의 모델 파라미터를 찾는 것과 동일한 의미다. 그리고 이를 모델 기반 학습이라고 부른다.
하이퍼 파라미터(hyper parameter): 머신러닝 알고리즘이 학습하지 않는 파라미터, 사람이 사전에 지정해야한다. 대표적으로 ridge와 lasso의 규제강도를 정하는 alpha값이 있다.
릿지(ridge): 규제가 있는 선형 회귀 모델 중 하나로 선형 모델의 계수를 작게 만들어 과대적합을 완화시킨다. 릿지는 비교적 효과가 좋아 널리 사용하는 규제 방법이다.
라쏘(lasso): 규제가 있는 선형 회귀 모델로 릿지와 달리 계수를 아예 0으로 만들 수도 있다.
다중 회귀(multiple regression): 여러 특성을 사용한 선형 회귀. 특성이 한 개면 '직선'을 학습하지만, 두 개면 '평면 입체'를, 3개 이상은 '고차원'을 학습한다. 특성이 두개면 타깃값과 함께 3차원 공간을 형성하고 선형 회귀 방정식인 [타깃 = a * 특성1 + b x 특성2 + 절편]이 된다. 3개 이상일 경우는 공간을 그리거나 상상해 볼 순 없지만 식을 이용해 매우 복잡한 모델을 표현할 수는 있다.
특성 공학(feature engineering): 기존의 특성을 이용해 새로운 특성을 뽑아내는 일련의 작업 과정. 사이킷런에서 변환기transformer를 이용해 실행한다.
패키지와 함수
[넘파이 라이브러리numpy]
reshape(): 구조를 재배열하는 넘파이 메서드. reshape(행의 수, 열의 개수), reshape(열의 개수) 형태로 사용한다. 활용하려는 데이터가 1차원인 경우 reshape(-1,1)나 reshape(1)로 열을 하나 만들어주어야 한다. 여기서 착각하지 말아야 할 점은 차원의 값이 1인 경우가 2차원이라는 것이다!
데이터가 1차원 배열이라는 말은 배열에 '열'이 존재하지 않고 '행'만 존재한다는 것이다. 즉, 행과 열이 각각 하나의 차원이라는 얘기다. reshape으로 여러 차원을 만들어 줄 수 있는데 숫자를 하나 더 써주면 된다. 예를 들어 3차원인 경우 reshape(1, 1, 1)로 만들어 주면 된다.
[판다스pandas]
read_csv(): csv파일을 로컬 컴퓨터나 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 함수
- sep=: CSV 파일의 구분자 지정, 기본값은 '콤마(,)'
- header=: 데이터프레임의 열 이름으로 사용할 CSV 파일의 행 번호를 지정한다. 기본값은 첫 번째 행
- skiprows=: 파일을 읽기 전에 건너 뛸 행의 개수 지정
- nrows=: 파일에서 읽을 행의 개수를 지정
[사이킷런 라이브러리scikit-learn]
KNeighborsRegressor: k-최근접 이웃 회귀 모델을 만드는 사이킷런 클래스. n_neighbors 매개변수로 이웃의 개수를 지정한다. 기본값은 5이다. (sklearns.neighbors)
mean_absolute_error(): 회귀 모델의 평균 절댓값 오차를 계산한다. 첫 번째 매개변수는 타깃, 두 번째 매개변수는 예측값을 전달한다. (sklearns.metrics)
mean_squared_error(): 평균 제곱 오차를 계산한다. 있다. 이 함수는 타깃과 예측을 뺀 값을 제곱한 다음 전체 샘플에 대해 평균한 값을 반환한다. (sklearns.metrics)
LinearRegression: 선형회귀 클래스. intercept_속성에는 절편이 저장되어 있지만, fit_intercept 매개변수를 false로 지정하면 절편을 학습하지 않는다.(쓸 일이 있나...?) 학습된 모델의 coef_ 속성은 특성에 대한 계수를 포함한 배열이다. 즉, 이 배열의 크기는 특성의 개수와 같다. (sklearns.neighbors)
PolynominalFeatures: 주어진 특성을 조합하여 새로운 특성을 만든다.
- degree=: 최고차수를 지정, 기본값은 2
- interaction_only=: True이면 거듭제곱 항은 제외되고 특성 간 곱셈 항만 추가된다. 기본값은 False이다.
- include_bias=: False이면 절편을 위한 특성을 추가하지 않는다. 기본값은 True이다.
Ridge: 규제가 있는 회귀 알고리즘인 릿지 회귀 모델을 훈련한다.
- alpha=: 규제의 강도 조절, alpha 값이 클수록 규제가 세진다. 기본값은 1이다.
- solver=: 최적의 모델을 찾기 위한 방법 지정. 확률적 평균 경사 하강법 알고리즘으로 특성과 샘플 수가 많을 때 성능이 빠르고 좋다. 기본값은 auto
- random_state=: solver가 sag나 saga일 때 넘파이 난수 시드값을 지정할 수 있다.
Lasso: 규제가 있는 회귀 알고리즘인 라쏘 회귀 모델을 훈련한다. 이 클래스는 최적의 모델을 찾기 위해 좌표축을 따라 최적화를 수행해가는 좌표 하강법coordinate descent을 사용한다.
- alpha=: 규제의 강도 조절, alpha 값이 클수록 규제가 세진다. 기본값은 1이다.
- max_iter=: 알고리즘의 수행 반복 횟수를 지정한다. 기본값은 1,000이다.
- random_state=: solver가 sag나 saga일 때 넘파이 난수 시드값을 지정할 수 있다.
'데이터 분석 학습' 카테고리의 다른 글
[혼자공부하는머신러닝+딥러닝] Ch.04 다양한 분류 알고리즘 / 로지스틱 회귀, 확률적 경사 하강법, 이진분류, 다중 분류 (0) | 2022.04.20 |
---|---|
[혼자공부하는머신러닝+딥러닝] Ch.02 데이터 다루기 / 훈련-테스트 세트 분리, 전처리 (0) | 2022.04.15 |
[혼자공부하는머신러닝+딥러닝] Ch.01 나의 첫 머신러닝 리뷰 / K-최근접 이웃 모델 (0) | 2022.04.05 |
[모두의 데이터분석 With 파이썬] Unit.08 리뷰 / 항아리모양 그래프 그리기 (0) | 2022.03.25 |
[모두의 데이터분석 With 파이썬] Unit.07 리뷰 / 데이터에 맞는 시각화 (0) | 2022.03.24 |