
지도 학습 알고리즘은 크게 분류/회귀로 나뉜다.
- 분류: 주어진 데이터들을 두고서 몇 개의 클래스 혹은 집단으로 나누는 것.
- 회귀: 주어진 데이터들을 두고서 집단으로 나누는 것이 아니라, 새로운 데이터가 어디에 속할지, 어떤 결과를 도출할지 등을 예측하는 것. 두 변수 사이의 상관관계를 분석하는 방법이라고도 한다.
<회귀(regression)>
1. 데이터 준비
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])
농어의 길이와 무게 데이터를 가져왔다.
2. 훈련 세트와 테스트 세트 분류
from matplotlib import 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)

사이킷런의 train_test_split() 함수를 사용해 훈련세트와 테스트 세트로 나누었다. 훈련세트는 2차원 배열이어야 하기 때문에, 1차원 배열을 reshape 함수를 통해 2차원 배열로 바꿔준준다.
[1, 2, 3]
-->
[[1], [2], [3]]
크기: (3,) (3, 1)
test_array = np.array([1, 2, 3, 4])
print(test_array.shape)
test_array = test_array.reshape(2, 2)
print(test_array.shape)
# -> (2, 2)
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)
3. k-최근접 이웃 모델 훈련
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
knr.fit(train_input, train_target)
print(knr.score(test_input, test_target))
# 0.992809406101064
사이킷런에서 k-최근접 이웃 회귀 알고리즘을 구현한 라이브러리는 KNeighborsRegressor로, 앞선 Classifier와 유사하다.
객체를 생성하고 fit() method로 회귀 모델을 훈련시켰다.
** 0.9928... : 분류와는 값의 의미가 다르다. 분류에서는 정확도라고 명했지만, 회귀에서는 이 점수를 결정계수(R^2)라고 한다.
<해석>
*결정계수

1) 타깃과 예측한 값의 차이를 제곱.
2) 타깃과 타깃 평균의 차이를 제곱한 값으로 나눔.
-> 타깃의 평균 정도를 예측하는 수준이라면, R^2은 0에 가까워짐.
-> 타깃이 예측에 아주 가까워지면, (분자가 0에 가까워지기 때문) 1에 가까워짐.
타깃과 예측값 사이의 차이
from sklearn.metrics import mean_absolute_error
test_prediction = knr.predict(test_input)
mae = mean_absolute_error(test_target, test_prediction)
mae
# 19.157142857142862
*과대적합과 과소적합
print(knr.score(train_input, train_target))
# 0.9698823289099254
테스트 세트의 결정계수와 훈련 세트의 결정계수 값이 다르다. 왜 그럴까? 모델을 훈련 세트를 통해 훈련하면, 훈련 세트에 잘 맞는 모델이 만들어진다. 즉, 테스트 세트보다 높은 값이 나올 수 있다. 훈련 세트에서 점수가 굉장히 좋았는데, 테스트 세트에서 점수가 나쁘다면, 이 모델은 훈련 세트에 '과대적합'되었다고 말한다. 실전에 투입하기엔 너무 훈련 세트에만 적합한 상태라고 볼 수 있다.
반면에, 테스트 세트에서의 점수가 훈련 세트의 점수보다 월등히 높거나, 두 점수가 너무 낮은 경우도 있다. 이런 경우를 모델이 훈련 세트에 '과소적합'되었다고 말하는데, 모델이 너무 단순하여 훈련 세트를 통한 훈련이 제대로 되지 않은 것이다.
*모델의 복잡도
위와 같이 테스트 세트에 대한 점수가 월등히 높은 경우, 모델이 훈련세트에 과소적합되었다고 말할 수 있겠다. 그러면 어떻게 이 문제를 해결할까? 굉장히 간단하게 해결 가능하다. 바로 이웃의 개수를 줄여 모델을 조금 더 복잡하게 만들면 된다.
ex) k-최근접 이웃 알고리즘의 모델을 더 복잡하게 만들기. 이웃의 개수 k를 줄여보자! k = 3.
이웃의 개수를 바꾸기 위해 객체를 다시 만들 필요 없이, n_neighbors의 속성값만 바꾸면 된다.
knr.n_neighbors = 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
이웃의 개수를 감소시키고 모델의 복잡성을 올려서 과소적합 문제를 해결했다.
<선형 회귀(linear regression)>
회귀 모델에서 발생할 수 있는 오류를 방지하고자 50cm 길이 농어의 무게를 예측하는 과정을 거쳤다. 하지만, 농어의 본 무게는 1.5kg이었고, 회귀 모델을 통해 예측된 농어의 무게는 그보다 한참 모자란 [1033.33333333]이 나왔다. 왜 그럴까?
1. 데이터 가져오기
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])
2. 훈련세트와 테스트 세트 나누기
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)
test_input = test_input.reshape(-1, 1)
train_input = train_input.reshape(-1, 1)
print(test_input.shape, train_input.shape)
# (14, 1) (42, 1)
3. fit()으로 회귀 모델 훈련시키기
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor(n_neighbors = 3)
knr.fit(train_input, train_target)
print(knr.predict([[50]]))
# [1033.333333333]
4. 산점도 그려보기
import matplotlib.pyplot as plt
distances, 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.ylabel('weight')
plt.show()

k-최근접 이웃 회귀 알고리즘은 입력받은 데이터와 가장 가까운 데이터(샘플)를 찾아 타깃을 평균값에 맞춘다. 결국 선형적인 형태를 띄고 있는 데이터라 하더라도, 가장 가까운 값들의 평균으로 예측할 수 밖에 없는 것! 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측하기도 한다. 예를 들어 농어의 길이가 100cm일 때도, 회귀 모델은 농어의 무게를1033.3333333으로 예측한다.
선형 회귀 알고리즘
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_input, train_target)
print(lr.predict([[50]]))
# [1241.83860323]

print(lr.coef_, lr.intercept_)
# [39.01714496] -709.0186449535477
""" a: 기울기 or 계수, b: y 절편 """
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.show()

> 선형직선이 생긴다.
하지만! 기존의 데이터보다 아래로 직선이 뻗어나가고 있다. 선형회귀 모델에 입력한 길이의 값은 + 값이지만, 무게가 - 값인 이상치가 나오는 문제가 발생할 수 있다. 어떻게 해결할까? 산점도를 보면, 데이터가 일직선 상에 있지 않고, 곡선 상에 위치한다. 이건 2차함수로 만들면 해결이 가능하다.
다항 회귀(polynomial regression)

길이 제곱의 값이 np 배열에 하나 더 있어야 할 듯하다.
train_poly = np.column_stack((train_input **2, train_input)) #column_stack (()) -> (array1, array2, array3)를 튜플로 전달하기 위함.
test_poly = np.column_stack((test_input ** 2, test_input))
print(train_poly.shape, test_poly.shape)
# (42, 2) (14, 2)
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr. predict([[50**2, 50]]))
# [1573.98423528]
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
# 0.9706807451768623
# 0.9775935108325122
기존의 선형회귀 모델보다 훨씬 더 높은 값을 예측한 모습이다. But, 테스트 세트의 값이 훈련 세트보다 조금 높은 과소적합 현상이 남아있다.
선형 회귀 keyword
- 선형 회귀는 특성과 타깃 사이의 관계를 가장 잘 나타내는 선형 방정식을 찾는다. ex) 특성이 하나라면 직선 방정식이 된다.
- 그 관계는 계수 혹은 가중치에 저장되며, 가중치는 방정식의 기울기와 절편을 모두 의미하는 경우가 많다.
- 다항회귀는 다항식을 사용하여 특성과 타깃 사이의 관계를 나타낸다. 비선형적일 수 있지만 선형 회귀로 표현할 수 있다.
<특성 공학과 규제>
다중회귀
하나의 특성을 갖고서 모델을 학습하는 것은 선형회귀, 2개 이상의 특성을 갖고서 학습한다면 이는 다중회귀라고 한다.

농어의 길이, 높이, 두께 -> '특성'
길이 * 높이, 높이 * 두께 -> '특성 공학'
사이킷런의 변환기(transformer)
- fit()
- transformer()
사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공한다. 사이킷런에서는 이런 클래스를 변환기라고 부르는데, 모델 클래스에 fit(), score(), predict() method가 있는 것처럼, 변환기 클래스는 fit(), transform() method를 제공한다.
PolynomialFeatures
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(include_bias = False)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))
# [[2. 3. 4. 6. 9.]] # 각 특성을 제곱한 항, 특성끼리 곱한 항을 추가한다. 1은? 절편에 자동으로 곱해지는 값. 없어지게 할 수 있다!
# 무게 = a * 길이 + b * 높이 + c * 두께 + d * 1
poly = PolynomialFeatures(include_bias = False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
# (42, 9)
poly.get_feature_names_out()
# array(['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2', 'x2^2'], dtype=object)
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.9903183436982125
# 0.9714559911594111
과소적합 문제는 해결되었다! But, 테스트 세트의 점수는 크게 변하지 않았다. 이를 해결하기 위해서 특성을 더 많이 추가하기도 하는데,,
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개나 된다.
다시 훈련을 하게 되면 train 세트에 대한 점수는 0.999999991098로 1에 굉장히 근접한다. 하지만 test 세트를 score() 했을 때, -144.40...로 굉장히 큰 음수가 나타난다. 이처럼 특성의 개수가 늘어나면 선형 모델은 강력해지지만, 훈련세트에 너무 과대적합되므로 테스트 세트에서 형편없는 점수를 받게 된다. 그렇기 때문에 특성의 개수가 지나치게 많아지지 않도록 degree를 조절해야 한다. 여기서 사용하는 것이 바로 '규제'다
규제 + 정규화(StandardScaler)

특성의 scale에 대해 생각해보자. 특성의 스케일이 정규화되지 않으면, 여기에 곱해지는 계수 값도 차이난다. 규제를 적용하기에 앞서 계수의 정규화 과정이 먼저 이루어져야 한다.
mean() 과 std() 를 사용한 정규화도 있지만, StandardScaler를 활용한 정규화를 활용해보자.
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))
print(ridge.score(test_scaled, test_target))
# 0.9857915060511934
# 0.9835057194929057
- 라쏘: 계수의 절댓값을 기준으로 규제를 적용한다.
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.986591255464559
# 0.9846056618190413
릿지와 라쏘를 사용할 때는 규제의 양을 임의로 조절하는 것이 가능하다. 모델 객체를 만들 때, alpha 매개변수로 규제의 강도를 조절한다. alpha값이 크면 규제의 강도가 세지므로, 계수 값을 줄이고 조금 더 과소적합하도록 유도한다. 반대로 alpha 값이 작으면 계수를 줄이는 역할이 줄어들고 선형회귀 모델과 유사해지므로 과대적합될 가능성이 크다!
alpha의 적절한 값은, 그래프를 통한 비교에서 찾아야 한다. 그렇게 했을 때, '릿지'의 적절한 alpha 값을 먼저 찾아보자.
하이퍼파라미터 alpha
train_score =[]
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for a in alpha_list:
ridge = Ridge(alpha = a)
ridge.fit(train_scaled, train_target)
train_score.append(ridge.score(train_scaled, train_target))
test_score.append(ridge.score(test_scaled, test_target))
# log 함수로 바꾸어 지수로 표현
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.show()

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.9889354346720892
# 0.9856564835209134
모델의 과대적합 제어하기: 규제
다항 특성을 많이 추가해 훈련 세트에서 1에 가까운 점수를 얻었다. 하지만, 특성이 지나치게 많으면(2, 3, 4, 5제곱항) 훈련 세트에 과대적합되고, 테스트 세트에서 좋지 않은 점수를 얻을 확률이 높다. 때문에, 선형 회귀 모델을 제약하기 위한 규제가 필요하다.
다중 회귀 keyword
- 특성 공학: 주어진 특성을 조합해 새로운 특성을 만드는 일련의 작업 과정(특성의 제곱, 특성 간의 곱 등)
- 릿지: 규제가 있는 선형회귀 모델 중 하나이며 선형 모델의 계수를 작게 만들어 과소적합을 완화시키기 위해 들여왔던 과대적합 방식을 다시 완화시킨다.
- 라쏘: 릿지와 비슷하지만 다른 선형회귀 모델. 릿지와는 달리 계수에 절대값을 씌워 계수 값을 아예 0으로 만들어버릴 수도 있따.
- 하이퍼파라미터: 머신러닝 알고리즘이 학습하지 않는 파라미터다. 때문에, 사람이 사전에 지정해야 하며, 릿지와 라쏘의 규제 강도 alpha이다.
이미지 출처)
"https://www.flaticon.com/kr/free-icons/" 아이콘 제작자: Nuricon - Flaticon
'머신러닝(ML), 딥러닝(DL)' 카테고리의 다른 글
인공신경망(ANN)과 활성화 함수 [딥러닝] (0) | 2024.03.13 |
---|---|
퍼셉트론(Perceptron) [딥러닝] (0) | 2024.03.12 |
비지도 학습 [머신러닝] (1) | 2024.01.13 |
트리 알고리즘 [머신러닝] (1) | 2023.12.26 |
로지스틱 회귀(Logistic Regression) [머신러닝] (0) | 2023.12.15 |