[ML기초세션]_3주차
세션 전 학습 내용
교재: p.114 ~ p.173
[3-1] k-최근접 이웃 회귀
핵심 키워드: 회귀, k-최근접 이웃 회귀, 결정계수, 과대적합과 과소적합
지도 학습 알고리즘은 크게 분류와 회귀로 나뉜다. 여기서 회귀는, 클래스 중 하나로 분류하는 것이 아니라 임의의 어떤 숫자를 예측하는 문제다. 회귀는 정해진 클래스가 없고 임의의 수치를 출력한다.
- k-nn 회귀: 예측하려는 샘플에 가장 가까운 샘플 k개를 선택한다. 그 뒤 이 샘플들의 수치를 확인하고, 이웃 샘플들의 수치를 이용하여 평균을 구한다.
데이터를 numpy의 array로 준비해두고, 아래의 코드를 실행하면 그래프를 얻을 수 있다.
1 2 3 4 5 import matplotlib.pyplot as plt plt.scatter(perch_length, perch_weight) plt.xlabel('length') plt.ylabel('weight') plt.show()
train_test_split() 이용하여 train/test set 나누기
1
2
from sklearn.model_selection improt train_test_split
train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state=42)
사이킷런에서 사용할 training set은 2차원 배열이어야 하기에, train_input/test_input을 2차원 배열로 바꿔야 한다. 파이썬에서 1차원 배열의 크기는 원소가 1개인 튜플로 나타내는데, 예를 들어 [1,2,3]의 크기는 (3, )이다. 이를 2차원 배열로 만들기 위해 하나의 열을 억지로 추가하면 배열의 크기는 (3, 1)이 된다. 이를 수행하기 위해 reshape() method를 사용한다. 추가적으로 reshape를 이용할 때, 크기에 -1을 입력하면 나머지 원소 개수로 모두 채우라는 의미다.
1
2
3
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
print(train_input.shape, test_input.shape)
사이킷런에서 k-nn 회귀 알고리즘을 구현한 클래스는 ‘KNeighborsRegressor’이다.
1
2
3
4
5
6
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
#k-nn 회귀 모델 훈련
knr.fit(train_input, train_target)
- 회귀에서는 정확도 대신 다른 값으로 모델을 평가하는데, 이것이 결정계수(R^2)다. 만약 타깃의 평균 정도를 예측하는 수준이라면 결정계수는 0에 가까워지고, 타깃이 예측에 가까워지면 결정계수는 1에 가까워진다. +score() 함수에서 반환되는 값도 결정계수다!
- 다른 평가 수치로는 mean_absolute_error가 있다. mean_absolute_error는 타깃과 예측의 절댓값 오차를 평균하여 반환한다.
1
2
3
4
5
6
7
8
from sklearn.metrics import mean_absolute_error
# test set에 대한 예측 만들기
test_prediction = knr.predict(test_input)
# test set에 대한 평균 절댓값 오차 계산
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
training set을 사용하여 모델을 평가하면 어떨까?
over-fitting vs. under-fitting
일반적으로는, 모델을 training set으로 훈련하기에, test set보다 training set으로 모델을 평가할 때 더 높은 점수가 나온다. 그런데 이 차이가 너무 크다면 오버피팅(과대적합)된 것이고, 오히려 test set으로 평가했을 때의 점수가 더 크다면 언더피팅(과소적합) 된 것이다.
- over-fitting: training set score »> test set score
- under-fitting: training set score < test set score
교재의 예제에서는 언더피팅이 발생했기에 더 복잡한 모델을 만들 필요가 있다. k-nn알고리즘으로 모델을 더 복잡하게 만드는 방법은 k의 개수를 줄이는 것이다.
1
2
3
4
knr.n_neighbors = 3
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
이로써 언더피팅 문제를 해결하였다!
핵심 패키지와 함수
scikit-learn
- KNeighborsRegressor: k-nn 회귀 모델을 만드는 클래스. n_neighbors 매개변수로 이웃의 개수를 지정하며 기본값은 5다.
- mean_absolute_error(): 회귀 모델의 평균 절댓값 오차를 계산한다. 첫 번째 매개변수는 타깃, 두 번째 매개변수는 예측값을 전달한다. 비슷한 함수로는 mean_squared_error()가 있다.
numpy
- reshape(): 배열의 크기를 바꾸는 method. 바꾸고자 하는 배열의 크기를 매개변수로 전달하고, 바꾸기 전후의 배열 원소 개수는 동일해야한다. numpy는 종종 배열의 method와 동일한 함수를 별도로 제공한다. (ex. test_array.reshape(2,2) = np.reshape(test_array, (2,2)))
[3-2] 선형 회귀
핵심 키워드: 선형 회귀, 계수 또는 가중치, 모델 파라미터, 다항 회귀
k-nn의 한계
기존 training set에 없던, 거대한 농어의 무게를 예측해보니 모델의 예측값과 실제 무게의 값이 너무 차이가 많이 난다..! k-nn 회귀는 가장 가까운 샘플을 찾아 타깃을 평균한다. 따라서 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다. 길이가 50cm인 농어도, 100cm인 농어도 모두 1033g으로 예측하는 것이다.
선형 회귀
사이킷런은 sklearn.linear_model 패키지 아래에 LinearRegression 클래스로 선형 회귀 알고리즘을 구현해 놓았다. 마찬가지로 fit(), score(), predict() method가 있다.
1
2
3
4
5
6
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_input, train_target)
print(lr.predict([[50]]))
k-nn 회귀를 사용했을 때와 달리 아주 높게 예측했다.
선형 회귀가 학습한 직선은 y = ax + b의 형태를 가지는데, LinearRegression 클래스가 찾은 a와 b는 lr객체의 coef_, intercept_ 속성에 저장되어 있다.
model parameter: coef_, intercept_ 와 같이 ML 알고리즘이 찾은 값. 많은 ML알고리즘의 훈련과정은 최적의 모델 파라미터를 찾는 것과 같은데, 이를 모델 기반 학습이라고 부른다. 한편, k-nn의 경우에는 모델 파라미터가 없는데 training set을 저장하는 것이 훈련의 전부였기 때문이다. 이런 경우를 사례 기반 학습이라고 부른다.
다항 회귀: 앞서 구한 선형 회귀 직선 대로면 농어의 무게가 음수가 나올 수도 있는데, 말이 되지 않는다. 더욱 적합한 모델을 구하려면 직선이 아니라 곡선을 찾아야 한다. 2차 함수를 그리기 위해 길이를 제곱한 항을 training set에 추가해보자.
1
2
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
이제 이 train_poly를 사용하여 2차 함수를 그리는 선형 회귀 모델을 다시 훈련해보자. 여기서 주목할 점은 2차 방정식 그래프를 찾기 위해 training set에 제곱 항을 추가했지만, 타깃값은 그대로 사용한다는 것이다. 목표하는 값은 어떤 그래프를 훈련하든지 바꿀 필요가 없다.
1
2
3
4
5
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.predict([[50**2, 50]]))
print(lr.coef_, lr.intercept_)
위의 코드를 실행하면, 이전 모델보다 더 높은 무게를 예측하였고 모델이 y = 1.01 x^2 -21.6 x + 116.05의 그래프를 학습했다는 것을 알 수 있다.
2차 방정식도 선형 회귀인가요? -> 예 그렇습니다. 2차항을 또 다른 변수 z로 간단하게 치환할 수 있기 때문에, 선형 회귀라고 부를 수 있다.
이런 방정식을 다항식이라 부르며, 다항식을 활용한 선형 회귀를 다항 회귀라고 부른다.
1
2
3
4
5
6
7
8
9
10
# 구간별 직선을 그리기 위해 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)
plt.scatter([50], [1574], marker = '^')
plt.show()
훨씬 나은 모델이 나왔다. 하지만 training set, test set의 R^2 점수를 평가해보면, 여전히 언더피팅이다.
- 다항 회귀는 다항식을 사용한여 특성과 타깃 사이의 관계를 나타낸다. 이 함수는 비선형일 수 있지만 여전히 선형 회귀로 표현할 수 있다.
핵심 패키지와 함수
scikit-learn
- LinearRegression은 사이킷런의 선형 회귀 클래스다. fit_intercept 매개변수를 True로 지정하면 절편을 학습하지 않는다. 이 매개변수의 기본값은 True다. 학습된 모델의 coef_ 속성은 특성에 대한 계수를 포함한 배열이다. 즉 이 배열의 크기는 특성의 개수와 같다. intercept_ 속성에는 절편이 저장되어 있다.
[3-3] 특성 공학과 규제
핵심 키워드: 다중 회귀, 특성 공학, 릿지, 라쏘, 하이퍼파라미터
다중 회귀: 1개의 특성을 사용하면, 선형 회귀 모델은 직선을 학습하지만, 2개의 특성을 사용하면 선형 회귀 모델은 평면을 학습한다.
선형 회귀를 단순한 직선이나 평면으로 생각하여 성능이 무조건 낮다고 오해해서는 안된다. 특성이 많은 고차원에서는 선형 회귀가 매우 복잡한 모델을 표현할 수 있다.
- 특성 공학(feature engineering): 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업
데이터 준비
- pandas는 유명한 데이터 분석 라이브러리다. 데이터프레임(dataframe)은 pandas의 핵심 데이터 구조다. numpy array와 비슷하게 다차원 배열을 다룰 수 있지만 훨씬 더 많은 기능을 제공한다. pandas df를 만들기 위해 많이 사용하는 파일은 csv 파일이다.
1
2
3
4
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv')
perch_full = df.to_numpy()
print(perch_full)
perch_full, perch_weight를 train_test_split을 이용하여 training set, test set으로 나눈다.
사이킷런의 변환기 PolynomialFeatures클래스는 sklearn.preprocessing 패키지에 포함되어 있다.
1
2
3
4
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures()
poly.fit([[2,3]])
print(poly.transform([[2,3]]))
훈련을 해야 변환을 할 수 있다. 사이킷런의 일관된 api 때문에 나뉘어 있지만 항상 fit을 한 뒤 transform을 수행할 것. 두 method를 하나로 붙인 “fit_transform()” method도 존재한다.
fit() method는 새롭게 만들 특성 조합을 찾고 transform() method는 실제로 데이터를 변환한다. +변환기는 입력 데이터를 변환하는 데 타깃 데이터가 필요하지 않다. 따라서 모델 클래스와는 달리 fit() method에 입력 데이터만 전달한다.
PolynomialFeautures 클래스는 각 특성의 제곱 항을 추가하고, 특성끼리 서로 곱한 항을 추가한다. 그래서 샘플 [2,3]을 넣으면 샘플 [1,2,3,4,6,9]로 변환된다.
만약 반환된 샘플에서 1을 배제하고 싶다면 include_bias=False로 지정하여 특성을 변환하면 된다. (include_bias=False로 지정하지 않아도 사이킷런 모델은 자동으로 특성에 추가된 절편 항을 무시한다.)
PolynomialFeatures 클래스는 9개의 특성이 어떻게 만들어졌는지 확인하는 get_feature_names() method를 제공한다.
PolynomialFeatures 클래스는 fit() method에서 특성의 조합을 준비하기만 하고 통계값을 구하지 않아 test set을 따로 변환해도 된다. 하지만 항상 training set을 기준으로 test set을 변환하는 습관을 들이는 것이 좋다.
1
2
3
4
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
- PolynomialFeatures 클래스의 degree 매개변수를 사용하여 필요한 고차항의 최대 차수를 지정할 수 있다.
1
2
3
4
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)
위의 코드를 수행하면 무려 55개의 특성이 만들어진 것을 알 수 있다.
그리고 training set과 test set에 대해서 score()를 사용하면 매우 오버피팅된 모델임을 확인할 수 있다.
규제
- 특성의 스케일이 정규화되지 않으면 여기에 곱해지는 계수 값도 차이가 난다. 다시 말해, 규제를 적용하기 전에 먼저 정규화를 해야 한다.
사이킷런에서 제공하는 StandardScalerer클래스를 사용해보자.
1
2
3
4
5
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
- training set에서 학습한 평균과 표준편차는 StandardScaler 클래스 객체의 mean_, scale_ 속성에 저장된다.
선형 회귀 모델에 규제를 추가한 모델을 릿지와 라쏘라고 부른다. 두 모델은 규제를 가하는 방법이 다른데, 릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 일반적으로 릿지를 조금 더 선호한다. 두 알고리즘 모두 계수의 크기를 줄이지만 라쏘는 아예 0으로 만들 수도 있다.
릿지 회귀
1
2
3
4
5
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))
위의 코드를 실행하면, 오버피팅 되었던 모델이 릿지 규제를 통해 다시 정상으로 돌아온 것을 확인할 수 있다.
alpha 매개변수로 규제의 강도를 조절할 수 있다. alpha 값이 클수록 규제의 강도가 강해진다.
alpha 값은 릿지 모델이 학습하는 값이 아니라 사전에 우리가 지정해야 하는 값이다. 이렇게 모델이 학습할 수 없고 사람이 알려줘야 하는 파라미터를 하이퍼파라미터라고 한다.
적절한 alpha 값을 찾는 한 가지 방법은 alpha값에 대한 R^2 값의 그래프를 그려보는 것이다.
1
2
3
import matplotlib.pyplot as plt
train_score = []
test_score = []
1
2
3
4
5
6
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))
alpha의 값을 0.001부터 10배씩 늘렸기 때문에 로그 스케일로 그래프를 표현하는 것이 더 적합하다. 이를 위해 np.log() 혹은 np.log10()을 사용할 수 있는데 각각 자연로그와 상용로그를 의미한다.
1
2
3
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.show()
위의 그래프를 얻을 수 있고, alpha = 0.1일 때 가장 좋은 모델임을 알 수 있다.
이를 이용해 alpha=0.1로 설정하고 모델을 다시 평가하면 아래와 같다.
1
2
3
4
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))
라쏘 회귀 라쏘 모델을 훈련하는 것은 릿지와 매우 비슷하다. Ridge 클래스를 Lasso 클래스로 바꿔주기만 하면 된다.
1
2
3
4
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target)
1
print(lasso.score(test_scaled, test_target))
릿지에서 했던 것처럼 최적의 alpha값을 찾아보자.
1
2
3
4
5
6
7
8
9
10
11
12
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.show()
- 라쏘 모델을 훈련할 때 ConvergenceWarning이란 경고가 발생할 수 있다. 사이킷런의 라쏘 모델은 최적의 계수를 찾기 위해 반복적인 계산을 수행하는데, 지정 반복 횟수가 부적할 때 이런 경고가 발생한다. max_iter 매개변수가 최대반복횟수를 지정하는 매개변수이고 여기선 10000으로 설정해둔 것이다. 필요하면 더 늘릴 수 있으나 이 문제에선 큰 영향을 끼치지 않는다.
위의 그래프에서 최적의 알파값은 10이므로 아래의 코드를 작성할 수 있다.
1
2
3
4
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으로 만들 수 있다고 했는데, 라쏘 모델의 계수는 coef_에 저장되어 있다. 이 중에서 0인 것을 찾아보자.
1
print(np.sum(lasso.coef_ == 0))
40이라는 값이 반환되었다. 55개의 특성을 모델에 주입했지만 라쏘 모델이 사용한 특성은 15개 뿐인 것이다. 이런 특징 때문에 라쏘 모델은 유용한 특성을 골라내는 용도로도 사용할 수 있다.
핵심 패키지와 함수
pandas
- read_csv(): CSV 파일을 로컬 컴퓨터나 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 함수다. 이 함수는 매우 많은 매개변수를 제공한다. 그중에 자주 사용하는 매개변수는 다음과 같다. 1. sep: CSV파일의 구분자를 지정한다. 기본값은 ‘,’ 2. header는 df의 열 이름으로 사용할 CSV파일의 행 번호를 지정한다. 기본적으로 첫 번째 행을 열 이름으로 사용. 3. skiprows는 파일에서 읽기 전에 건너뛸 행의 개수를 지정한다. 4. nrows는 파일에서 읽을 행의 개수를 지정한다.
scikit-learn
- PolynomialFeatures: 주어진 특성을 조합하여 새로운 특성을 만든다. 1. degree: 최고 차수를 지정한다. 기본값은 2. 2. interaction_only가 True면 거듭제곱 항은 제외되고 특성 간의 곱셈 항만 추가된다. 기본값은 False. 3. include_bias가 False면 절편을 위한 특성을 추가하지 않는다. 기본값은 True.
- Ridge: 규제가 있는 회귀 알고리즘인 릿지 회귀 모델을 훈련한다. 1. alpha: 규제의 강도 조절. 클수록 규제가 세진다. 기본값은 1 2. solver 매개변수에 최적의 모델을 찾기 위한 방법을 지정. 기본값은 ‘auto’이며 데이터에 따라 자동으로 선택됨. ‘sag’는 확률적 평균 경사 하강법 알고리즘으로 특성과 샘플 수가 많을 때에 성능이 빠르고 좋다. 0.19버전에선 개선 버전인 ‘saga’가 추가되었다. 3. random_state는 solver가 ‘sag’나 ‘saga’일 때 넘파이 난수 시드값을 지정가능8.
- Lasso: 규제가 있는 회귀 알고리즘인 라쏘 회귀 모델을 훈련한다. 이 클래스는 최적의 모델을 찾기 위해 좌표축을 따라 최적화를 수행해가는 좌표 하강법을 사용한다. 1. alpha와 random_state는 Ridge와 동일. 2. max_iter는 알고리즘의 수행 반복 횟수 지정. 기본값은 1000.
세션 복습
발표 내용 복습
중요한 변수가 누락되면 모데릐 신뢰성과 정확성이 떨어지게 된다. 그래서 다중회귀분석을 수행할 때는 가능한 모든 중요 변수를 포함하는 것이 중요하다.
부적합한 변수가 추가되면 모델의 효율성이 떨어지지만, 기본적인 추정치와 가설 검정의 유효성은 유지된다. 다중회귀분석을 수행할 때는 적절한 변수 선택이 매우 중요하다.
다중공선성은 입력변수들 간의 상관관계가 존재하므로, 회귀 계수의 분산을 크게 한다.
이는 회귀 분석으로 나온 추정 회귀 계수의 신용을 없앤다. 왼쪽은 변수들간의 독립성이 있는 모습을, 오른쪽은 A, B가 부분적으로 독립성이 훼손되었다고 판단할 수 있다.
다중 공선성을 해결하기 위해 아래와 같은 과정을 거칠 수 있다.
- 제거: 상관관계가 높은 독립변수나 그룹 중의 일부를 제거
- 변형: 변수를 변형하거나 새로운 관측치를 이용
- 이유 파악: 자료를 수집하는 현장의 상황을 보아 상관관계의 이유를 파악한다.
- 변수 규제 or 선택: PCA와 Ridge Regression과 같은 추정 방법을 사용한다.
- 다중공선성 진단법: 분산팽창계수(VIF, Variance Inflation Factor)
Q&A 복습
두 클래스 이상의 분류를 할 때의 다양한 분류 방식
이진 분류 활용
- Ridge Regression vs. Lasso Regression Ridge: 모든 특성들이 중요할 때 사용. 가중치가 0으로 접근하지만 0이 되지는 않는다. Lasso: 0이 됐다가 안됐다가 함.
추가 공부
heatmap: 색상으로 표현할 수 있는 다양한 정보를 일정한 이미지 위에 열분포 형태의 비쥬얼한 그래픽으로 출력.
릿지, 라쏘 분석의 동작 메커니즘에 대한 공부 필요!!