728x90
반응형
이제 Pupbani는 회귀 문제를 다룰 수 있고 특성값을 전처리하거나 특성을 조합해 새로운 특성을 만들 수 있게 되었다.
어느날 마케팅 팀에서 Pupbani에게 다음과 같은 요청을 했다.
- "7개의 생선이 랜덤하게 들어 있는 럭키백 이벤트를 진행할 것인데 이 럭키백에 있는 생선들이 나올 확률을 구해주세요!"
- "생선의 무게 ,길이, 높이, 두께, 대각선 길이 데이터도 같이 드릴게요!"
Pupbani는 새로운 과제에 대해 생각에 잠겼다.
갑자기 번뜩이는 아이디어가 떠올랐다.
K-최근접 이웃은 주변 이웃을 찾아주니까 이웃의 클래스 비율을 확률이라고 출력하면 되지 않을까?
- 사각형이 나올 확률 30%
- 원이 나올 확률 20%
- 삼각형이 나올 확룰 50%
- 사이킷런의 K-최근접 이웃 분류기로 하면 될 것 같다.
데이터 준비하기
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv')
fish.head()
- pandas 라이브러리의 read_csv() 함수를 사용해서 생선 데이터 csv 파일을 불러왔다.
- head() 메서드로 데이터프레임의 앞의 5개의 데이터를 살펴본다.
- 데이터 프레임에 어떤 생선이 있는지 확인해 본다.
print(pd.unique(fish['Species']))
- fish에서 Species열을 추출하여 unique() 함수를 통해 중복값을 제거한 고유한 값만 추출한다.
- 총 7개의 종류의 생선이 존재한다.
- 이제 훈련 데이터와 타겟 데이터를 나눠보겠다,
- 훈련 : Weight, Length, Diagonal, Height, Width
- 타겟 : Species
- 데이터프레임에서 여러 열을 선택하면 그 열로 이뤄진 새로운 데이터 프레임이 반환 된다.
- sklearn에서 사용하기 위해 넘파이 배열로 변환(to_numpy())한다.
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
- 훈련 세트와 테스트 세트로 분할 한다.
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)
- 이제 데이터가 완성 되었다.
- 모델을 학습 시켜보자
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(f'훈련세트 정확도: {kn.score(train_scaled, train_target)}%')
print(f'테스트 세트 정확도: {kn.score(test_scaled, test_target)}%')
- 정확도가 나쁘지만 확률을 배우는 목적이므로 점수는 잠시 잊도록 한다.
- 이 모델의 클래스를 확인해보자.
- classes_ 속성을 사용해 알 수 있다.
print(kn.classes_)
- 타깃 데이터의 고유한 값들이 순서는 틀리지만 전부 들어 있다.
- 타깃 값을 사이킷런 모델에 전달하면 순서가 자동으로 알파벳 순으로 정렬되어 classes_ 속성에 저장된다.
- 이렇게 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류(Multi-Class Classification)라고 부른다.
다중분류(Multi-Class Classification)
- 사이킷런의 predict() 메서드는 친절하게 타깃값으로 예측을 출력해 준다.
- 테스트 세트의 상위 5개의 샘플 데이터의 타깃값을 예측해 보겠다.
print(f"예측 결과 : {kn.predict(test_scaled[:5])}")
- 예측된 결과들은 어떻게 만들어질까?
- 사이킷런의 분류 모델은 predict_proba() 메서드로 클래스별 확률 값을 반환한다.
- 소수점 자리가 너무 클 수 있으므로 numpy의 round() 함수를 사용한다.
- decimals 매개변수로 유지할 소수점 아래 자리수를 정한다.
import numpy as np
proba = kn.predict_proba(test_scaled[:5])*100
df = pd.DataFrame(np.round(proba,decimals = 2), columns=kn.classes_)
print(df)
- 이 모델의 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인해보자.
- 네 번째 샘플의 최근접 이웃의 클래스를 확인한다.
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
- 이 샘플의 이웃은 'Roach'가 1개, 'Pearch'가 2개이다.
- 클래스에 대한 확률을 계산한면
- 'Roach'는 1/3 = 0.3333 = 33.33%
- 'Pearch'는 2/3 = 0.6667 = 66.67%
- 샘플의 클래스 확률값과 같다.
하지만 3개의 이웃을 사용하기 때문에 가능한 확률이 0/3, 1/3, 2/3, 3/3이 전부다.
다른 방법을 사용해야 할 것 같다.
로지스틱 회귀(Logistic Regression)
- 이름은 회귀이지만 분류 모델이다.
- 이 알고리즘은 선형 방정식을 학습한다.
- 예를 들면 다음과 같다.
- z는 어떤 값도 가능하지만 확률이 되려면 0~1(또는 0~100%)가 되야한다.
- z가 아주 큰 음수일 때 0, z가 아주 큰 양수일 때 1이 되도록 바꾸는 방법을 찾아야한다.
- 시그모이드 함수(Sigmoid Function 또는 로지스틱 함수; Logistic Function)를 사용하면 가능하다.선형 방정식의 출력 z의 음수를 사용해 자연 상수 e를 거듭제곱하고 1을 더한 값의 역수를 취한다.
시그모이드 함수를 이해하기 위해 파이썬을 통해 그려보자.
- 조건들
- z가 무한하게 큰 음수일 경우 함수는 0에 가까워 지고 무한하게 큰 양수일 경우 1에 가까워진다.
- z가 0일때는 0.5가 된다.
- ⏀는 절대로 0~1 사이의 범위를 벗어날 수 없다. -> 0~100%까지 확률로 해석할 수 있다.
- Numpy의 arange()를 통해 -5와 5사이에 0.1 간격으로 배열 z를 만든다.
- Numpy의 exp() 함수를 통해 지수함수를 계산한다.
import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
Logistic Regression 모델을 사용해보자 - 이진분류
- Numpy 배열은 True, False 값을 전달하여 행을 선택할 수 있다.
- 이를 불리언 인덱싱(Boolean Indexing)이라고 부른다.
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]])
- 불리언 인덱싱을 이용해 훈련 세트에서 도미와 빙어의 행만 골라낸다.
- 도미와 빙어의 행을 모두 True로 만든다.
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 클래스는 sklearn.linear_model 패키지 아래에 있다.
print("로지스틱 회귀 모델로 예측한 상위 5개의 생선 샘플 예측 결과\n")
prcnt = lr.predict_proba(train_bream_smelt[:5]).round(4) * 100
a = lr.predict(train_bream_smelt[:5]).reshape(5,-1)
con = np.column_stack((prcnt,a))
col = lr.classes_.tolist()+["예측 결과"]
df = pd.DataFrame(con, columns =col)
print(df)
- prodict_proba() 함수가 반환한 배열값에 첫번째 열이 음성 클래스(0), 두번째 열이 양성 클래스(1)에 대한 확률이다.
- Bream - 음성
- Smelt - 양성
- 예측 결과를 보면 두번째 샘플만 양성 클래스인 빙어의 확률이 높고 나머진 도미가 높다.
- 학습한 로지스틱 회귀 모델의 계수들을 통해 z 값을 구해보자.
for i,j in zip(['a','b','c','d'],lr.coef_[0]):
print(f"{i} = {j}")
print(f"f = {lr.intercept_[0]}")
- LogisticRegression 클래스는 decision_function() 메서드로 z값을 쉽게 구할 수 있다.
decisions = lr.decision_function(train_bream_smelt[:5])
for idx, z in enumerate(decisions):
print(f"{idx}번째 샘플의 z값 = {z}")
- 이 z값을 시그모이드 함수에 통과 시키면 확률값을 얻을 수 있다.
- 파이썬의 싸이파이(Scipy) 라이브러리에 시그모이드 함수가 있다.
- expit() : 시그모이드 함수
from scipy.special import expit
for idx, z in enumerate(expit(decisions)):
print(f"{idx}번째 샘플의 확률값 = {z*100:.2f}%")
Logistic Regression 모델을 사용해보자 - 다중분류
- LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용한다.
- max_iter 매개변수에서 반복 회수를 결정한다. (기본값 = 100)
- 그리고 기본적으로 규제(L2 Norm)를 한다.
- 릿지 처럼 계수의 제곱을 규제한다.
- C 매개변수의 값에 따라 규제가 커지거나 작아진다. (기본값 = 1)
- 학습을 진행해보자.
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(f'훈련세트 정확도: {lr.score(train_scaled, train_target)*100:.2f}%')
print(f'테스트 세트 정확도: {lr.score(test_scaled, test_target)*100:.2f}%')
- 과대적합, 과소적합 없이 적절히 잘나온 것 같다.
- 처음 5개 샘플에 대한 예측과 예측확률을 출력해보자
print("로지스틱 회귀 모델로 예측한 상위 5개의 생선 샘플 예측 결과\n")
prcnt = lr.predict_proba(test_scaled[:5]).round(4) * 100
a = lr.predict(test_scaled[:5]).reshape(5,-1)
con = np.column_stack((prcnt,a))
col = lr.classes_.tolist()+["예측 결과"]
df = pd.DataFrame(con, columns =col)
print(df)
- 선형 방정식 확인을 위해 coef_와 intercept_의 크기를 출력해봤다.
print(lr.coef_.shape, lr.intercept_.shape)
- 이 데이터는 5개의 특성을 사용한다.
- coef는 5개
- intercept는 7개 - z를 7번 계산한다.
- 다중 분류는 z값을 하나씩 계산한다.
- 그렇다면 z로 확률은 어떻게 계산한 것일까?
- 이진 분류에서는 시그모이드 함수를 사용했지만 다중 분류는 소프트맥스 함수(Softmax Function)을 사용하여 7개의 z 값을 확률 값으로 변환한다.
- 7개의 z값이 있고 z1 ~ z7라고 이름을 가정
- z1~z7을 사용해 e^z1 ~ e^z7을 계산하여 모두 더한 값을 e_sum이라고 한다.
- e^z1 ~ e^z7을 각각 e_sum으로 나누어준 것들이 각각의 확률이다.
- 이렇게 나온 확률들은 모두 더하면 1이 된다.
- 먼저 decision_function() 으로 z1 ~ z7의 값을 구한다.
decision = lr.decision_function(test_scaled[:5])
print("5개 샘플에 대한 z1 ~ z7의 값\n")
for idx,z in enumerate(decision):
print(f"{idx}번째 샘플의 z값\n{z}\n")
- 그 다음은 Scipy 라이브러리의 소프트맥스 함수를 사용해 확률값을 구한다.
- scipy.special 아래의 softmax() : axis는 계산할 축을 설정(0: 열, 1: 행)
from scipy.special import softmax
print("소프트맥스 함수로 예측 확률 구하기\n")
class_ = lr.classes_.tolist() + ["예측 결과"]
prd = lr.predict(test_scaled[:5]).reshape(5,-1)
sm = softmax(decision, axis=1).round(2) * 100
con = np.column_stack((sm,prd))
print(pd.DataFrame(con,columns=class_))
- 앞에서 구한 proba 배열과 일치한다.
Pupbani는 이제 7개의 생선에 대한 확률을 예측하는 모델을 훈련했다.
마케팅 팀이 만족하겠네요.
728x90
반응형
'AI > 기계학습(Machine Learning)' 카테고리의 다른 글
[기계학습/ML]10. 트리알고리즘 - 결정트리 (0) | 2022.10.23 |
---|---|
[기계학습/ML]9. 확률적 경사 하강법 - 손실 함수, 에포크 (0) | 2022.10.23 |
[기계학습/ML]7. 회귀 알고리즘(2) - 다중 회귀, 릿지(Ridge), 라쏘(Lasso) (0) | 2022.10.23 |
[기계학습/ML]6. 회귀 알고리즘(1) - K-최근접 이웃 회귀, 선형회귀 (0) | 2022.10.23 |
[기계학습/ML]5. 데이터 전처리 - 표준점수, 브로드캐스팅 (0) | 2022.10.22 |