--07.회귀(regession).ipynb--
KNN regession 사용
K-nearest neighbor regression
"""
'k-최근접 이웃 회귀' 도 비슷하다.
'분류' 와 똑같이 예측하려는 샘플에 가장 가까운 샘플 k개를 선택합니다.
하지만 회귀이기 때문에 이웃한 샘플의 타깃은 어떤 클래스가 아니라 '임의의 수치' 가 된다.
이웃 샘플의 수치를 사용해 새로운 샘플 X 의 타킷을 예측하는 방법은 ?
=> 바로 이 수치들의 평균값을 구하면 된다.
아래 그림에서 이웃한 샘플의 타킷값이 각각 100, 80, 60 이고, 이를 평균하면 X의 예측 타킷값은 80이 된다.
결국 k-최근접 이웃 분류 알고리즘과 비숫하되, 타깃값을 결정할 때만 조금 다르다.
"""
None
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
base_path = r'/content/drive/MyDrive/dataset'
file_path = os.path.join(base_path, 'fish.csv')
fish_df = pd.read_csv(file_path)
fish_df
농어의 길이(feature)가 주어졌을 때 무게(target)를 예측해보자
perch_length = fish_df[fish_df.Species == 'Perch']['Length'].values
print(perch_length)
perch_weight = fish_df[fish_df.Species == 'Perch']['Weight'].values
print(perch_weight)
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)
train_input.shape, test_input.shape
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)
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
knr.fit(train_input, train_target)
knr.score(test_input, test_target)
"""
매우 높은 점수가 나왔다? 이 점수는 무엇일까?
'분류' 문제의 경우 test 세트의 샘플을 정확하게 분류한 개수의 비율이다. (accuracy: 정확도)
'회귀' 문제는 정확한 숫자를 맞힌다는것이 거의 불가능하다. 예측하는 값이나 타킷 모두 임의의 수치이기 때문이다.
'회귀' 의 경우 평가방식이 조금 다르다
이 점수를 '결정계수' (coeffiencit of determination) 이라고 부름.
간단히 R² 라고도 부름
이 값은 다음과 같은 식으로 계산됨
(target - 예측)² 의 합
R² = 1 - ──────────────────
(target - 평균)² 의 합
"""
None
from sklearn.metrics import mean_absolute_error
test_prediction = knr.predict(test_input)
test_prediction
test_target
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
knr.score(train_input, train_target)
knr.n_neighbors = 3
knr.fit(train_input, train_target)
knr.score(train_input, train_target)
knr.score(test_input, test_target)
"""
↑ 예상대로, test 세트의 점수는 train 세트때보다 낮아졌으므로 과소적합 문제를 해결한것 같습니다.
또한, 두 점수의 차이가 크지 않으므로 이 모델이 과대 적합 된거 같지도 않습니다.
이제 이 학습된 모델이 test 세트와 추가될 데이터에도 '일반화' 를 잘 하리라 기대할수 있다.
"""
None
knr = KNeighborsRegressor()
x = np.arange(5,45).reshape(-1,1) # feature vector 형태로 만들어야 predict사용가능.
for n in [1, 5, 10] :
knr.n_neighbors = n
knr.fit(train_input, train_target)
prediction = knr.predict(x)
plt.scatter(train_input, train_target)
plt.plot(x, prediction)
plt.title(f'n_neighbors = {n}')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
train_input, test_input, train_targer, test_target = train_test_split(
perch_length, perch_weight, random_state=42
)
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
train_input.shape, test_input.shape
knr = KNeighborsRegressor(n_neighbors = 3)
knr.fit(train_input, train_target)
knr.predict([[50]])
"""
모델은 1,033g으로 예측함.
그런데, 실제 측정해보니 농어의 무게가 훨~씬 더 많이 나간다고 해보자.
무엇이 문제일까?
"""
None
distance, indexes = knr.kneighbors([[50]])
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker = 'D')
plt.scatter(50, 1033, marker = '^')
plt.xlabel('length')
plt.xlabel('weight')
plt.show()
np.mean(train_target[indexes])
distance, indexes = knr.kneighbors([[100]])
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker = 'D')
plt.scatter(100, 1033, marker = '^')
plt.xlabel('length')
plt.xlabel('weight')
plt.show()
특정(feature)가 한개인 경우 어떤 '직선'을 학습하는 알고리즘
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_input, train_target)
lr.predict([[50]])
"""
농어의 무게 = a x length + b
LinearRegression 이 찾은 a, b는 coef와 intercept 속성에 저장됨.
"""
None
lr.coef_
lr.intercept_
"""
coef 와 intercept 를 머신러닝 알고리즘이 찾은 값이 라는 의미로 모델 파라미터 (model parameter) 라고 부릅니다.
대부분의 많은 머신러닝 알고리즘의 훈련과정은 최적의 모델 파라미터를 찾는 것과 같다.
이를 '모델기반 학습(model based learning)' 이라 합니다. 앞서 사용한 KNN 에는 모델파라미터가 없습니다.
훈련세트를 저장하는 것이 훈련의 전부였습니다. 이를 '사례기반 학습(instance based learning)' 이라 합니다.
"""
None
plt.scatter(train_input, train_target)
plt.plot([15, 50], [15 lr.coef + lr.intercept, 50 lr.coef + lr.intercept])
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
lr.score(train_input, train_target)
lr.score(test_input, test_target)
"""
train 세트와 test 세트의 점수가 조금 차이 납니다.
이 모델이 train 세트에 overfit (과대 적합) 된걸까요?
그런데 train 세트의 점수도 그닥 높지도 않습니다.
오히려 전체적으로 underfit (과소 적합) 되었다고 볼수 있습니다.
"""
None
"""
좌 하단이 이상하다
농어의 길이가 15 라고 한다면 무게가 음수가 나올 것이다.
"""
None
"""
이러한 2차 방정식 그래프를 그리려면 길이를 제곱한 항이 훈련세트에 추가되어야 합니다.
numpy 를 사용하면 손쉽게 만들수 있다.
무게 = a x 길이² - b x 길이 + c <= 길이의 제곱과 원래길이 두가지 모두 필요하다.
length² ← length
[[ 384.16 19.6 ][ 484. 22. ]
[ 349.69 18.7 ][ 302.76 17.4 ]
[1296. 36. ]
...
[ 655.36 25.6 ][1764. 42. ]
[1190.25 34.5 ]]
"""
None
train_poly = np.column_stack((train_input 2, train_input))
test_poly = np.column_stack((test_input 2, test_input))
train_poly.shape, test_poly.shape
lr = LinearRegression()
lr.fit(train_poly, train_target)
lr.predict([[50 ** 2, 50]])
lr.coef_ # a, b 값
lr.intercept_ # c 값
"""
이러한 방정식을 다항식 (polynomial) 이라 부르며 다항식을 사용한 선형 회귀를 다항회귀 (polynomizal regression) 이라 한다
"""
None
plt.scatter(train_input, train_target)
point = np.arange(15,50)
plt.plot(point, 1.01 point ** 2 - 21.6 point + 116.05)
plt.scatter([50], [1574], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
lr.score(train_poly, train_target)
lr.score(test_poly, test_target)
"""
train 세트와 test 세트에 대한 점수가 크게 높아졌다
그럼에도 여전히 test 세트의 점수가 조금 더 높다
underfit 이 남아 있는 것으로 보입니다. => 조금더 복잡한 모델이 필요할수도 있다고 생각해야한다.
"""
None
fish_df
fish_df.columns
"""
그렇다! 무게 예측에 영향을 주는건 Length 만이 아니라, 다른 특징들도 영향을 주고 있는 것이다.
선형회귀는 영향을 주는 특성이 많을수록 효과가 크다.
"""
None
여러개의 특성을 사용한 선형회귀
perch_df = fish_df[fish_df.Species == 'Perch']['Length', 'Height', 'Width']]
perch_df
perch_full = perch_df.to_numpy()
perch_full
perch_weight # target 값
train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures()
poly.fit([[2,3]]) # fit() 새롭게 만든 특성들의 조합을 찾음
poly.transform([[2,3]]) # transform() 실제로 데이터를 변환함.
"""
↑ fit() 은 새롭게 만들 특성 조합을 찾고
transform() 은 실제로 데이터를 변환함
fit() 을 잘 보시면, target 을 필요로 하지 않음을 알수 있다. (model 의 fit() 과는 다르다!)
입력데이터만 전달되면 됩니다.
여기서는 2개의 특성(원소)을 가진 샘플[2, 3] 이 6개의 특성을 가진 샘플 [1. 2. 3. 4. 6. 9.] 로 바뀌었습니다.
PolynomialFeatures 클래슨 기본적으로
각 특성을 제곱한 항을 추가하고 특성끼리 서로 곱한 항을 추가합니다.
2 와 3을 각기 제곱한 4와 9가 추가되었고, 2 와 3을 곱한 6이 추가되었습니다.
앞의 1은 왜 추가 되었을까요? 다음식을 봅니다.
무게 = a x 길이 + b x 높이 + c x 두께 + d x 1
↑ 사실, 선형방정식의 절편을 항상 값이 1인 특성과 곱해지는 계수라고 볼수 있습니다.
이렇게 놓고 보면 특성은 (길이, 높이, 두께, 1) 이 됩니다.
하지만 사이킷 럿의 선형 모델은 자동으로 절편을 추가하므로 굳이 이렇게 특성을 만들 필요가 없습니다.
그래서 include_bias=False 로 지정하여 다시 특성을 변환하겠습니다.
"""
None
poly = PolynomialFeatures(include_bias = False)
poly.fit([[2,3]])
poly.transform([[2,3]])
poly = PolynomialFeatures(include_bias = False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
train_poly.shape
train_input.shape
poly.get_feature_names_out()
"""
'x0' 는 첫번째 특성
'x0^2' 는 첫번째 특성의 제곱
'x0 x1' 은 첫번째 특성과 두번째 특성의 곱
이와 같은 것을 나타냅니다.
"""
None
test_poly = poly.transform(test_input)
test_poly.shape
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
lr.score(test_poly, test_target)
poly = PolynomialFeatures(degree=5 , include_bias = False) # degree = 5 5제곱까지의 특성을 만듦
poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)
print(train_poly.shape)
print(test_poly.shape)
lr.fit(train_poly, train_target)
lr.score(train_poly, train_target)
lr.score(test_poly, test_target) # overfit 증상중 하나. 학습모델에서 너무 정확도가 높으면 test 에서 망할 수 있다.

규제 (regularization) 은 머신러닝 모델이 train 세트를 너무 과도하게 학습하지 못하도록 훼방하는 것
즉, 모델이 훈련세트에 과대적합 (overfit) 되지 않도록 만드는 거다.
선형 회귀 모델의 경우 특성에 곱해지는 계수 (또는 기울기) 의 크기를 작게 만드는 일입니다.
아래 그림을 살펴봅니다

오른쪽의 그림은 train 세트를 과도하게 학습했고, 가운데는 기울기를 줄여서 보다 보편적인 (general) 패턴을 학습하고 있습니다
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
ss.mean, ss.scale
"""
선형 회귀 모델에 규제를 추가한 모델을 릿지 (ridge) 와 라쏘(lasso) 라고 부릅니다.
두 모델은 규제를 가하는 방법에 차이가 있습니다.
릿지: 계수를 제곱한 값을 기준으로 규제를 적용. (일반적으로 릿지 선호)
라쏘: 계수의 절댓값을 기준으로 규제를 적용.
두 알고리즘 모두 계수의 크기를 줄이지만, 라쏘는 아예 0으로 만들 수도 있습니다.
사이킷런은 두 알고리즘 모두 제공합.
"""
None
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)
ridge.score(train_scaled, train_target)
ridge.score(test_scaled, test_target)
train_score = []
test_score = []
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))
print(train_score)
print(test_score)
np.log10(alpha_list)
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()
"""
↑ 위의 파란색이 train 세트 그래프
아래 주황색이 test 세트 그래프
왼쪽을 보면 train 세트와 test 세트의 점수차이가 아주 큽니다.
=> train 세트에는 잘 맞고 test 세트에는 형편없는 과대적합(overfit)의 전형적인 모습
반대로 오른쪽 편은 train 세트와 test 셑트긔 점수가 모두 낮아지는 경향을 보이는 과소적합 (underfit) 으로 가는 모습
적절한 alpha 값은 두 그래프가 가장 가깝고 테스트 점수가 가장 높은 -1, 즉 0.1 입니다.
alpha 값을 0.1 로 세팅하여 최종 모델을 훈련해봅니다.
"""
None
ridge = Ridge(alpha = 0.1) # 최적의 alpha 값 세팅
ridge.fit(train_scaled, train_target)
ridge.score(train_scaled, train_target)
ridge.score(test_scaled, test_target)
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
lasso.score(train_scaled, train_target)
lasso.score(test_scaled, test_target)
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) # 모델 생성 # max_iter : 최적의 계수를 찾기 위한 반복 계산 횟수
lasso.fit(train_scaled, train_target) # 모델 훈련
train_score.append(lasso.score(train_scaled, train_target))
test_score.append(lasso.score(test_scaled, test_target))
""" ConvergenceWarning
↑ warning 이 뜬다. 뭐지?
라쏘 모델을 훈련할때 ConvergenceWarning 경고가 뜰수 있다.
사이킨 것의 라쏘 모델은 최적의 계수를 찾기 위해 반복적인 계산을 수행하는데, 지정한 반복횟수가 부족할 때 이런 경고가 발생합니다
이 반복 횟수를 충분히 늘리기 위해 max_iter 매개변수의 값을 10000 으로 지정했습니다.
필요하면 더 늘릴 수 있지만, 이 문제에서는 큰 영향을 끼치지 않습니다.
"""
None
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()
lasso = Lasso(alpha = 10)
lasso.fit(train_scaled, train_target)
lasso.score(train_scaled, train_target)
lasso.score(test_scaled, test_target)
"""
라쏘 모델은 계수 값을 아예 0으로 만들수도 있습니다.
라쏘 모델의 계수는 coef_ 속성에 저장되어 있습니다. 이 중에 0 인것을 헤아려 보겠습니다.
"""
None
np.sum(lasso.coef_ == 0) # 0 인것은 총 40개 따라서 55개가 아닌 15개면 충분했다.
"""
↑ 정말 많은 계수가 0이 되었습니다. 55개의 특성을 모델에 주입했지만 라쏘 모델이 사용한 특성은 15개 밖에 되지 않습니다.
이러한 특징 때문에 라쏘 모델은 유용한 특성을 골라내는 용도로도 사용할수 있답니다.
"""
None