회귀
- 클래스 중 하나로 분류하는 것이 아니라 임의의 어떤 “숫자”를 예측하는 문제
- K-NN Regression : 주변의 가장 가까운 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]
)
위의 데이터로 k-최근접 이웃 회귀(k-nn)를 수행해보았다.
먼저 Matplotlib를 사용하여 perch_length(물고기의 길이)와 perch_weight(물고기의 무게) 사이의 관계를 산점도로 나타냈다.
import matplotlib.pyplot as plt
plt.scatter(perch_length, perch_weight)
plt.xlabel("length")
plt.ylabel("weight")
plt.show()
scikit-learn의 train_test_split 함수를 사용하여 데이터를 훈련용과 테스트용으로 나누는 작업을 한다.
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)
print(train_input.shape, test_input.shape)
# (42,) (14,)
사이킷런에 사용할 훈련 세트는 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)
K-최근접 이웃 회귀(K-Nearest Neighbors Regressor, KNN 회귀) 모델을 사용하여 훈련 데이터를 학습한다.
from sklearn.neighbors import KNeighborsRegressor
#knr = KNeighborsRegressor(n_neighbors=5) #default가 5
knr = KNeighborsRegressor()
knr.n_neighbors = 5 # 하이퍼파라미터 튜닝, 1, 3, 5, 7
knr.fit(train_input, train_target)
훈련시킨 모델의 성능을 출력해보았다.
print(knr.score(test_input, test_target)) # 0.992809406101064
print(knr.score(train_input, train_target)) #0.9698823289099254
train 데이터로 학습시킨 모델이기 때문에 train의 점수가 더 높은 것이 정상인데, test 데이터에 대한 점수가 더 높다.
이러한 결과를 모델이 과소적합 되었다고 표현한다.
반대로 과대적합은 과하게 훈련용 데이터에 학습되어 test 데이터의 점수보다 train 데이터의 점수가 매우 큰 것을 말한다.
참고로 t_input의 값은 27.5이다. 길이가 27.5인 물고기의 무게를 예측하는 코드이다.
#### 수동 예측
t_input = test_input[2]
p = knr.predict(t_input.reshape(1, -1))
print(f"예측 사용 무게:{p}")
print(f"실 무게:{test_target[2]}")
# 예측 사용 무게:[248.]
# 실 무게:250.0
k 값에 따른 회귀 결과와 결정계수(R²) 차이를 시각적으로 표현하는 그래프를 그려보았다.
### 그래프로 그려보기
knr = KNeighborsRegressor()
x = np.arange(5,45).reshape(-1,1)
for k in [1,3,5,7,10]:
knr.n_neighbors = k
knr.fit(train_input, train_target)
prediction = knr.predict(x)
#결정R^2
s_train = knr.score(train_input, train_target)
s_test = knr.score(test_input, test_target)
plt.title(f"k={k}, R^2={s_train-s_test}")
plt.scatter(train_input, train_target)
plt.plot(x, prediction)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
보통 R² 값이 클수록 모델이 종속 변수의 변동을 더 잘 설명하는 것으로 간주되어 좋다.
따라서 k=3이 가장 학습이 잘 된 모델이다.
knn 모델의 문제점이 있다.
길이가 43 이상인 물고기에 대해 예측을 수행하면 무게가 모두 같게 나온다.
knr = KNeighborsRegressor()
knr.n_neighbors = 3
knr.fit(train_input, train_target)
print(knr.predict([[37]])) # [719.]
print(knr.predict([[42]])) # [990.]
print(knr.predict([[43]])) # [1010.]
print(knr.predict([[48]])) # [1010.]
print(knr.predict([[49]])) # [1010.]
print(knr.predict([[50]])) # [1010.]
print(knr.predict([[100]])) # [1010.]
### 이웃을 찾아 차트로 그리기
w=50 # 100, 43, 42, 150, 41, 39 ... 값을 바꾸어 그려보기
d,i = knr.kneighbors([[w]])
print(f"d : {d}, i:{i}")
#훈련 데이터의 산점도
plt.title(f"length : {w} ")
plt.scatter(train_input, train_target)
plt.scatter(train_input[i], train_target[i], marker='D') #이웃 3
plt.scatter(w,knr.predict([[w]])[0], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
산점도 그래프에서 주황색 마름모로 표현된 이웃 데이터들이 43 이상부터는 모두 같은 데이터로 구성되어 있어서 예측 결과가 같은 것을 확인할 수 있다. 테스트하고자 하는 샘플에 근접한 훈련 데이터가 없는 경우, 즉 훈련 셋의 범위를 많이 벗어나는 샘플인 경우 정확하게 예측하기 어려운 한계가 있다.
이 문제를 해결 할 방법으로 선형회귀를 수행해보았다.
길이와 무게 데이터는 knn 데이터와 동일합니당
LinearRegression
- 선형 회귀는 종속 변수 y와 하나 이상의 독립 변수 x와의 선형 상관관계를 모델링하는 기법이다
- 만약 독립 변수 x가 1개라면 단순 선형 회귀라고 하고, 2개 이상이면 다중 선형 회귀라고 한다.
1) 단순 선형 회귀 (Simple Linear Regression)
단순 선형 회귀는 y=Wx+b의 식으로 나타난다. 머신러닝에서는 독립 변수 x에 곱해지는 W값을 가중치(weight), 상수항에 해당하는 b를 편향(bias)이라고 부른다.
따라서 단순 선형 회귀 모델을 훈련하는 것은 적절한 W와 b값을 찾는 것이다. 그래프의 형태는 직선으로 나타난다.
2) 다중 선형 회귀 (Multiple Linear Regression)
다중 선형 회귀는 y=W1x1+W2x2+...+Wnxn+b의 식으로 나타난다. 여러 독립 변수에 의해 영향을 받는 경우이다. 만약 2개의 독립 변수면 그래프는 평면으로 나타날 것이다.
출처: https://rebro.kr/185 [Rebro의 코딩 일기장:티스토리]
선형 회귀는 농어의 길이만을 이용해서 무게를 예측하기 때문에, 정확하게는 하나의 독립 변수를 사용하는 단순 선형 회귀(Simple Linear Regression)다. 농어 데이터를 가장 잘 나타낼 수 있는 직선을 찾아야 할 것이다. 먼저 사이킷런에서 제공하는 선형회귀를 수행한다.
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_input, train_target)
50과 100에 대한 예측 결과가 우선 달라진 것은 확인할 수 있다. 길이가 더 긴 농어에 대하여 더 무겁게 예측하고 있는 결과이다.
print(lr.predict([[50]])) # [1241.83860323]
print(lr.predict([[100]])) # [3192.69585141]
길이라는 하나의 특성을 사용했기 때문에 그래프는 직선인 y=ax+by=ax+b의 형태로 나타난다. LinearRegression 클래스가 찾은 a와 b는 각각 lr 객체의 coef_와 intercept_ 변수에 저장되고, 나는 A,B 변수로 저장했다.
구한 직선의 방정식을 함수 y = f(x)라고 하면, 직선은 두 점 (10, f(10))와 (100, f(100))을 연결하는 직선과 동일할 것이다.
따라서 plot 함수를 이용하여 두 점을 연결하는 선을 그린다. 적절한 직선을 찾은 것을 볼 수 있다. 이제, 훈련 셋의 범위를 벗어난 농어의 무게도 예측할 수 있게 되었다.
A = lr.coef_ # 계수 array([39.01714496])
B = lr.intercept_ # 절편, 독립항 np.float64(-709.0186449535477)
plt.scatter(train_input, train_target)
plt.plot([10, 100], [10 * A + B, 100 * A + B])
plt.scatter(50, 1241.83860323, marker='^')
plt.scatter(100, 3192.69585141, marker='*')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
그런데 그래프를 보면 length가 일정 수준 이하가 되면 weight가 음수가 되는 것을 확인할 수 있다.
상식적으로 무게가 음수인 것은 불가능하다. predict를 통해 length==15인 데이터에 대한 결과 음수인 것을 확인했다.
print(lr.predict([[15]])) # [-123.7614705]
훈련한 모델에 대한 점수 또한, 훈련 셋과 테스트 셋의 점수에 차이가 있을뿐더러 훈련 셋의 점수도 그렇게 높지 않다.
그 이유는 실제로 농어의 데이터는 직선보단 곡선에 가깝기 때문이다.
print(lr.score(train_input, train_target)) # 0.9398463339976041
print(lr.score(test_input, test_target)) # 0.8247503123313562
그렇기 때문에 최적의 직선을 찾기보단 최적의 곡선을 찾는다면 더 정확한 예측이 가능할 것이다. 따라서 가장 간단한 곡선인 2차 방정식을 구해보자. 2차 방정식을 구하기 위해서 길이를 제곱한 항을 추가한다. 따라서, 길이의 제곱이 배열에 추가되어야 하는데, 이는 넘파이의 column_stack 함수를 이용하면 간단하게 해결할 수 있다.
train_poly = np.column_stack((train_input**2, train_input))
train_poly
"""
array([[ 384.16, 19.6 ],
[ 484. , 22. ],
[ 349.69, 18.7 ],
[ 302.76, 17.4 ],
[1296. , 36. ],
[ 625. , 25. ],
[1600. , 40. ],
[1521. , 39. ],
[1849. , 43. ],
[ 484. , 22. ],
[ 400. , 20. ],
[ 484. , 22. ],
.
.
.
"""
제곱한 값으로 다시 모델을 학습시킨다. 다항회귀를 수행하는 모습이다.
결과, 대략 y=1.014x^2−21.5579x+116.05y=1.014x2−21.5579x+116.05의 그래프를 학습하였다.
* 다중 회귀는 2개 이상의 독립 변수가 존재하는 형태이고, 다항 회귀는 하나의 독립 변수를 이용하여 차수를 높이는 개념이라고 생각하면 된다.
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.coef_, lr.intercept_) # [ 1.01433211 -21.55792498] 116.05021078278247
모델 학습 결과, 단순 선형 회귀보다 과대적합이 많이 개선된 결과를 확인할 수 있다.
print(lr.score(train_poly, train_target)) # 0.9706807451768623
print(lr.score(test_poly, test_target)) # 0.9775935108325121
predict를 통해 실제 예측도 수행해보고, 산점도도 그려보았다.
음수값을 갖지 않게 적절히 잘 그려졌다고 판단할 수 있을 것 같다.
print(lr.predict([[50**2, 50]])) # [1573.98423528]
print(lr.predict([[100**2, 100]])) # [8103.57880667]
A = lr.coef_[0]
B = lr.coef_[1]
C = lr.intercept_
points = np.arange(0, 101)
plt.scatter(train_input, train_target)
plt.xlabel('length')
plt.ylabel('weight')
plt.plot(points, (A * points**2) + (B * points) + C)
plt.scatter([50], [1574], marker='^')
plt.scatter([100], [8104], marker='^')
plt.show()
'playdata > daily' 카테고리의 다른 글
12주차 : Day 3 (9/25) (1) | 2024.09.30 |
---|---|
12주차 : Day 2 (9/24) (0) | 2024.09.30 |
12주차 : Day 1 (9/23) (2) | 2024.09.24 |
10주차 : Day 4,5 (9/12,13) (3) | 2024.09.23 |
10주차 : Day 2,3 (9/10,11) (0) | 2024.09.23 |