이전 글에서 다중 회귀 모델을 설계하고 특성을 조합하여 새로운 특성을 만들고 alpha 값을 이용한 규제까지 해보았다.
이번 글에서는 어떤 생선에 대해서 이 생선이 얼마의 확률로 7개의 생선들 중 어떤 종류의 생선 일지 추측하는 모델을 설계해보자.
K - 최근접 이웃 알고리즘
이전부터 매번 등장해주시는 알고리즘이다. 확률을 구한다는 것은 분류 문제일까 회귀 문제일까? 7개의 생선들 중에서 종류를 추측하는 것이니까 분류 문제일까 아니면 확률이라는 수치를 구하는 것이니까 회귀 문제일까? k-최근접 이웃 알고리즘은 가장 가까운 이웃 샘플로 추측을 해주니까 10개의 샘플을 조사해서 개수에 따라 확률을 매기면 될 것 같다.
판다스의 read_csv() 함수로 CSV 파일을 데이터 프레임(Dataframe)으로 변환하고 head() 메서드로 처음 5개 행을 출력해보자.
* 데이터 프레임(Dataframe)이란 판다스에서 제공하는 2차원 표 형식의 주요 데이터 구조이다. 넘파이 배열과 비슷하게 열과 행으로 이루어져 있고 통계와 그래프를 위한 메서드를 풍부하게 제공해준다. 또한 넘파이로의 상호 변환이 쉽고 사이킷런과의 호환성도 뛰어나다.
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head() #5개만 출력
어떤 종류의 생선들이 있는지 확인해보자. 특정 열의 원소들을 확인하고 싶을 때는 unique() 함수를 사용하면 된다.
Species 열을 타깃 데이터로, 나머지 5개 열은 입력 데이터로 사용하여 넘파이 배열로 변환하자. 데이터 프레임에서 열을 선택하는 방법은 원하는 열을 리스트로 나열하면 된다. 변환이 잘 되었는지 처음 5개 행만 출력해보자.
fish_input = fish[['Weight', 'Length', 'Diagonal','Height','Width']].to_numpy() #fish_input 변수에 여러 열들을 선택하여 넘파이 배열로 반환
fish_target = fish['Species'].to_numpy()
변환이 잘 되었으므로 이제 훈련 세트와 테스트 세트로 나누고 StandardScaler 클래스를 사용하여 표준화 전처리하자.
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state = 42)
from sklearn.preprocessing import StandardScaler #표준화 전처리
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
K-최근접 이웃 분류기의 확률 예측
KNeighborsClassifier 클래스 객체를 만들고 훈련 세트로 훈련시킨 뒤 점수를 확인해보자.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
클래스 확률을 배우는 것이 목표이므로 점수에 대해서는 나중에 생각해보자.
Species에는 7개의 생선 종류가 들어있었는데, 타깃 데이터를 만들 때 fish ['Species']를 사용해서 만들었기 때문에 훈련 세트와 테스트 세트의 타깃 데이터에도 7개의 생선 종류가 들어가 있다.
이렇게 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류(Multiclass Classification)라고 부른다.
사이킷런에서는 문자열로 된 타깃값을 그대로 사용할 수 있지만 타깃값을 그대로 사이킷런 모델에 전달하면 순서가 자동으로 알파벳 순으로 매겨지므로 주의해야 한다. 정렬된 타깃 값은 classes_ 속성에 저장되어있다.
위의 출력 값을 보면 우리가 pd.unique(fish ['Species'])로 출력했던 순서와 다른 것을 알 수 있다.
predict() 메서드는 타깃 값으로 예측을 출력해준다. 테스트 세트의 처음 5개 샘플의 타깃 값을 예측해보자.
이 5개의 예측은 얼마의 확률로 만들어졌을까? 사이킷런의 분류 모델은 predict_proba() 메서드로 클래스별 확률 값을 반환한다. 넘 파이의 round() 함수는 소수점 첫째 자리에서 반올림하는데, decimals 매개변수로 유지할 소수점 아래 자릿수를 지정할 수 있다. 테스트 세트에 있는 처음 5개의 샘플에 대한 확률을 출력해보자.
import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals = 4)) #소수점 네번째 자리까지 표기 --> 다섯번째 자리에서 반올림, 원래 round()는 소수점 첫째자리에서 반올림한다.
predict_proba() 메서드의 출력 순서는 앞에서 봤던 classes_ 속성과 같다.
kneighbors() 메서드를 사용해서 이 모델이 계산한 확률이 가장 가까운 이욱의 비율이 맞는지 확인해보자. kneighbors() 메서드의 입력은 2차원 배열이어야 한다. 슬라이싱 연산자는 하나의 샘플만 선택해도 항상 2차원 배열이 만들어지기 때문에 슬라이싱 연산자를 이용해서 네 번째 샘플 하나만 선택했다.
distance, indexes = kn.kneighbors(test_scaled[3:4])
다섯 번째 클래스인 Roach가 1개, 세 번째 클래스인 Perch가 2개이다. 그래서 확률이 1/3으로 0.3333, 2/3으로 0.6667로 나온 것이다. k = 3으로 설정했기 때문에, 확률이 0, 1/3, 2/3, 3/3으로만 나올 것이다. 좀 더 복잡한 모델이 필요할 것 같다.
로지스틱 회귀 (Logistic Regression)
로지스틱 회귀는 이름은 회귀이지만 분류 모델이다. 선형 회귀와 똑같이 다음 식 형태의 선형 방정식을 학습한다.
z = a * (Weight) + b * (Length) + c * (Diagonal) + d * (Height) + e * (Width) + f
위 식에서 z는 어떤 값도 가능한데, 확률이 되려면 0 ~ 1 혹은 0 ~ 100% 사이 값이 되어야 한다. z가 아주 큰 양수일 때 1, 아주 큰 음수일 때 0으로 바꿀 방법이 없을까?
이럴 때 시그모이드 함수(sigmoid function) 혹은 로지스틱 함수(logistic function)를 사용한다.
넘파이를 사용하여 그래프를 그려보자.
import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5,5,0.1) #-5와 5사이에 0.1간격으로 배열 z를 만든다.
phi = 1 / (1 + np.exp(-z)) #시그모이드 함수 형태를 phi에 넣어준다. 1/(1 + e ^ (-z)) 꼴
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
이진 분류일 경우 시그모이드 함수의 크기가 0.5보다 크면 양성 클래스, 0.5보다 작으면 음성 클래스로 판단한다. 도미와 빙어를 사용하여 이진 분류를 시행해보자.
넘파이 배열은 True, False 값을 전달하여 행을 선택할 수 있는데, 이를 불리언 인덱싱 (Boolean Indexing)이라고 한다. 예를 들어 다음처럼 A부터 E까지 5개의 원소로 이루어진 배열이 있다고 하자. 여기서 A와 C만 골라내려면 첫 번째와 세 번째 원소만 True이고 나머지는 모두 False인 배열을 전달하면 된다.
비교 연산자를 사용하면 도미와 빙어의 행만 True로 만들 수 있다. bream_smelt_indexes 배열은 도미와 빙어일 경우 True, 그 외의 경우는 모두 False 값이 들어가 있다. 이 배열을 사용해 train_scaled와 train_target 배열에 불리언 인덱싱을 적용하면 손쉽게 도미와 빙어 데이터만 골라낼 수 있다.
bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
LogisticRegression 클래스를 사용하여 로지스틱 회귀 모델을 훈련하고 처음 5개 샘플의 확률을 확인해보자.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
샘플마다 2개의 확률이 출력되었다. 첫 번째 열이 음성 클래스(0)에 대한 확률이고 두 번째 열이 양성 클래스(1)에 대한 확률이다. Bream과 Smelt 중 어떤 것이 양성 클래스일까? 사이킷런은 타깃 값을 알파벳 순으로 정렬하기 때문에 음성 클래스가 Bream, 양성 클래스가 Smelt이다.
로지스틱 회귀가 학습한 계수를 확인해 보자.
각각의 값들은 z = a * (Weight) + b * (Length) + c * (Diagonal) + d * (Height) + e * (Width) + f에서 a~f의 값이다.
decision_function() 메서드로 처음 5개 샘플의 z값을 출력해보자.
decisions = lr.decision_function(train_bream_smelt[:5])
이 z값을 시그모이드 함수에 대입하면 확률을 얻을 수 있다. 파이썬의 사이파이(scipy) 라이브러리에 expit()이라는 시그모이드 함수가 있다. 이는 np.exp() 함수를 사용해 분수 계산을 하는 것보다 훨씬 편리하고 안전하다. decisions 배열의 값을 확률로 계산해보자.
출력 값을 보면 predict_proba() 메서드 출력의 두 번째 열의 값과 동일하다. 다시 말해 decision_function() 메서드는 양성 클래스에 대한 z값을 반환한다.
다중 분류도 이진 분류와 크게 다르지 않다. 이번엔 7개의 생선을 분류하면서 이진 분류와의 차이점을 알아보자.
LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용한다. max_iter 매개변수는 반복 횟수를 지정해준다. 기본값은 100이지만 준비한 데이터셋을 사용해서 훈련하면 반복 횟수가 부족하다는 경고가 발생하므로 max_iter의 값을 1000으로 늘리겠다.
LogisticRegression 클래스는 기본적으로 릿지 회귀와 같이 계수의 제곱을 규제한다. 이런 규제를 L2 규제라고 부른다. 릿지 회귀에서는 alpha 매개변수로 규제의 양을 조절하지만 LogisticRegression에서는 C로 규제한다. C는 alpha와는 반대로 크기가 작을수록 규제가 커진다. C의 기본값은 1인데, 여기에서는 규제를 조금 완화하기 위해 20으로 늘리겠다.
7개의 생선 데이터가 들어있는 train_scaled와 train_target을 이용한 점에 집중해서 코드를 보자.
훈련 세트와 테스트 세트에 대한 점수가 높고 과대 적합이나 과소 적합으로 치우친 것 같지 않다.
테스트 세트의 처음 5개 샘플에 대한 예측과 예측 확률을 출력해보자.
이진 분류에서는 시그모이드 함수를 사용해 z를 0과 1 사이의 값으로 변환한다. 반면에 다중 분류는 소프트맥스 함수(Softmax)를 사용하여 여러 개의 z값을 확률로 변환해준다. 또한 여러개의 선형 방정식의 출력 값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만든다. 이를 위해서 지수함수를 사용하므로 정규화된 지수 함수라고도 한다.
e_sum = e^z1 + e^z2 + ...
S1 = e^z1 / e_sum, S2 = e^z2 / e_sum, ...
위 식에서 모든 S들의 합은 1이 된다.
시그모이드 함수와 소프트맥스 함수는 나중에 신경망을 배울 때 다시 등장하니 자세히 익혀두자.
decision_function() 메서드로 z1 ~ z7까지의 값을 구한 다음 소프트맥스 함수를 사용해 확률로 바꿔보자.
decision 배열을 softmax() 함수에 전달한다. softmax()의 axis 매개변수는 소프트맥스를 계산할 축을 지정한다. axis = 1로 지정하면 각 행, 즉 각 샘플에 대해 소프트맥스를 계산한다. axis 매개변수를 지정하지 않으면 배열 전체에 대해 소프트맥스를 계산하므로 조심해야 한다.
앞에서 구한 proba 배열과 정확히 일치하는 것을 확인할 수 있다.
이번 글에서는 로지스틱 회귀를 사용하여 7개의 생선에 대한 확률을 예측하는 모델을 훈련했다. 확률을 계산하는 과정에서 이진 분류에서의 시그모이드 함수와 다중 분류에서의 소프트맥스 함수에 대해 공부했다. 시그모이드 함수와 소프트맥스 함수는 신경망 부분에서 다시 다루기 때문에 자세히 볼 필요가 있을 것 같다.
'AI > 머신 러닝(ML)' 카테고리의 다른 글
결정 트리 (Decision Tree) (0) | 2021.08.16 |
---|---|
확률적 경사 하강법 (Stochastic Gradient Descent) (0) | 2021.08.14 |
특성 공학과 규제 (0) | 2021.08.09 |
선형 회귀 (Linear Regression) (0) | 2021.08.09 |
K-최근접 이웃 회귀(K-Neighbors Regression) (0) | 2021.08.08 |