본문 바로가기

머신러닝 & 딥러닝

[혼자 공부하는 머신러닝] 04-01 로지스틱 회귀

<알고가야하는 키워드>

데이터프레임: 판다스에서 제송하는 2차원 표 형식의 데이터 구조. 사이킷런과 호환 잘됨

 

다중분류(multi-class classification): 타깃데이터에 2개 이상의 클래스가 포함된 문제(맞춰야할 라벨이 여러가지임~)

 

K 최근접 이웃 다중분류를 통한 확률 구하기:  가장 가까운 이웃을 10명이라고 측정하면, 각각 3/10, 2/30, 5/10 이런식으로 같은 특성의 이웃을 구한뒤 0.3, 0.2, 0.5 이렇게 각 클래스별로 확률를 부여할수 있다. 

 

주의할점: 타깃값을 사이킷런 모델에 전달하면, 순서가 자동으로 알파벳순으로 정렬된다.(kn.classes_ 속성에 정렬된 타깃값들이 들어가 있다.)

 

로지스틱 회귀: 회귀라고 정량적 수치를 예측하는게 아닌, 분류를 할때 쓰이는 모델이다. 선형회귀와 동일하게 선형 방정식을 학습한다. (Z = a*(Weight) + b*(Length) + c*(Diagonal) +d*(Height) + e*(Width) + f)

여기서 a,b,c,d,e는 가중치(계수)이다. 다중회귀를 위한 선형 방정식과 같다. 

 

Z는 어떤 값도 가능하지만, 확률이 되려면 0~1 사이의 값으로 만들어 주어야 한다.

Z가 아주 큰 음수이면 0, 아주 큰 양수이면 1 이렇게 바꾸어 주어야 한다.

 

시그모이드 함수: 선형방정식의 출력을 0~1사이의 실수값으로 바꾸어 준다. 1/1+e^(-x) 이다. (scipy라이브러리에서 expit()함수를 쓰면 된다.)

시그모이드 함수의 구조

 

불리언 인덱싱: 넘파이 배열의 행에서 True(1), False(0) 를 활용하여 원하는 요소만 선택할수 있다.

 

활성화함수: 출력값을 변환 해주는것

 

소프트맥스 함수: 결과값을 0~1사이의 실수로 변환해주는것(최종결과 출력을 정규화 할때 사용)

 

L2 규제: 릿지모델처럼 계수의 제곱 까지만 규제를 가하는 형태를 의미

 

로지스틱 회귀: C는 규제강도 이며, 클수록 규제가 약해진다는 특징이 있다. 또한 반복적 알고리즘 이라서 반복횟수를 지정 해줄수 있다. 로지스틱 회귀 또한 선형방정식이 존재 한다!!

 

 

 

<유용한 코드>

# 해당 열에 존재하는 요소들 중복제거해서 알려줌
print(pd.unique(fish['Species']))


# 특성정보만 뽑아서 넘파이배열로 가지고 온다.
fish_input = fish[['Weight',  'Length',  'Diagonal',   'Height',   'Width']].to_numpy()


# 타깃값을 사이킷런 모델에 그대로 전달하면, 알파벳 순서대로 클래스가 정렬이 된다.(정렬된 타깃값)
print(kn.classes_)    # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']


# predict_proba를 사용하면 각 클래스에 대한 확률이 계싼됨
proba = kn.predict_proba(test_scaled[:5])
print(proba)
print(np.round(proba,decimals =4))    # 소수점 아래 네번째 까지만 표기(5번째에서 반올림)


# 불리언 인덱싱
char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True,False,True,False,False]])    # ['A' 'C']


# 불리언인덱싱봐 비트OR 연산자를 활용한 특정 클래스 행만 추출하는법
bream_smelt_indexes = (train_target == 'Bream')|(train_target == 'Smelt')    # True,False로 이루어진 인덱스용 행이 생성됨
train_bream_smelt  = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
print(train_bream_smelt.shape)     #33행 5열 


# 기억하자 로지스틱 회귀 또한 선형방정식을 구한뒤 돌리는 것이다.
decisions = lr.decision_function(train_bream_smelt[:5])    # 선형방정식에 넣고 돌린 결과
print(decisions)    # 구한 선형방정식에 각 행을 넣고 돌린 결과 수치(이걸 시그모이드에 넣은뒤 0.5이하, 초과인지에 따라 음수양수 클래스 분류가 가능 한다.)


#%% 사이파이 라이브러리를 활용한 시그모이드 함수 구현
from scipy.special import expit
print(expit(decisions))    # 0~1사이의 값으로 반환됨 [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]


#%% 로지스틱 회귀를 사용한 다중분류 수행하기
lr = LogisticRegression(C=20, max_iter=1000)     # 로지스틱 회귀에서 C는 규제강도이지만, 클수록 규제가 약해짐(선형회귀 alpha랑은 반대임)
lr.fit(train_scaled, train_target)


from scipy.special import softmax
proba = softmax(decision, axis = 1)    # 모든 결과를 합했을때 1.0이 되게 정규화를 해줌
print(np.round(proba, decimals=3))

 

 

 

 

전체코드는 아래와 같다

#%% 로지스틱회귀(럭키백의 에서 해당 생선이 나올 확률 구하기)
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
'''
  Species  Weight  Length  Diagonal   Height   Width
0   Bream   242.0    25.4      30.0  11.5200  4.0200
1   Bream   290.0    26.3      31.2  12.4800  4.3056
2   Bream   340.0    26.5      31.1  12.3778  4.6961
3   Bream   363.0    29.0      33.5  12.7300  4.4555
4   Bream   430.0    29.0      34.0  12.4440  5.1340
'''

print(pd.unique(fish['Species']))    # Species 항목에 존재하는 요소들(중복제거해서 알려줌)
'''['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']'''

# 특성정보만 뽑아서 넘파이배열로 가지고 온다.
fish_input = fish[['Weight',  'Length',  'Diagonal',   'Height',   'Width']].to_numpy()
print(fish_input[:5])

fish_target = fish['Species'].to_numpy()
#print(fish_target)    




#%% 데이터셋 다 만들었으니 이제 스플릿 해주고 정규화 해준다. 이젠 안보고 해도 할수 있겠지?
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최근접 이웃 분류를 활용해서 7개의 생선타깃중 해당 생선에 속할 확률을 알아 보자
from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_target))    # 0.8907563025210085
print(kn.score(test_scaled, test_target))    # 0.85
print(kn.classes_)    # 타깃값을 사이킷런 모델에 그대로 전달하면, 알파벳 순서대로 클래스가 정렬이 된다.(정렬된 타깃값)
# ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
# Bream이 첫번째 클래스, Parkki가 두번째 클래스가 되는 방식이다.

print(kn.predict(test_scaled[:5]))     # 처음 5개의 샘플이 각각 어떤 생선에 해당하는지 예측
# ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']




# %% 최근접 이웃을 통한 각 클래스별 예측 수행
import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(proba)
print(np.round(proba,decimals =4))    # 소수점 아래 네번째 까지만 표기(5번째에서 반올림)

distances, indexes = kn.kneighbors(test_scaled[3:4])    # 네번째 샘플만 불러와서 이웃이 누군지 추적한다.
print(train_target[indexes])     # [['Roach' 'Perch' 'Perch']]으로 나옴, 따라서 perch가 2/3확률로 측정 된거임

#%% 로지스틱 회귀 구현하기전, 시그모이드 함수 알아 보기
# K 최근접 이웃을 통해서도 어느정도 예측은 할수 있지만, 주변 이웃에 의존하기에 참고하는 ㅣ웃에 영향을 받음
import numpy as np
import matplotlib.pyplot as plt

z = np.arange(-5,5,0.1)    #-5
print(z)
phi = 1/(1+np.exp(-z))    # 시그모이드 함수 수제 구현 
plt.plot(z,phi)
plt.xlabel('z')
plt.xlabel('phi')
plt.savefig('sigmoid.png')    # 활성화 함수 시그모이드의 그래프




#%% 로지스틱회귀를 사용한 이진분류 수행하기
# 넘파이에서의 불리언 인덱싱
char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True,False,True,False,False]])    # ['A' 'C']
# 넘파이 배열은 [[]] 이렇게 두개쓰는거 잊지말자

# 이제 이진 분류를 위해서 스케일되어있는 데이터에서 Bream, Smelt 클래스가 존재하는 행을 뽑아오자
# 비트 OR 연산자와 불리언 인덱싱을 활용하면 된다. 해당 타깃이 True False 인지 불리언 배열을 반환함
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]
print(train_bream_smelt.shape)     #33행 5열




# %% 로지스틱회귀를 활용한 이진분류 구현
from sklearn.linear_model import LogisticRegression
lr= LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.predict(train_bream_smelt[:5]))    # ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
print(lr.predict_proba(train_bream_smelt[:5]))
''' 앞에있는게 음수클래스, 뒤에 있는게 음수 클래스 이다.
[[0.99759855 0.00240145]
 [0.02735183 0.97264817]
 [0.99486072 0.00513928]
 [0.98584202 0.01415798]
 [0.99767269 0.00232731]]
'''
print(lr.classes_)    # ['Bream' 'Smelt'] 사이킷런 모델 전달시 알파벳 순으로 클래스를 정렬한다는걸 알아두자
print(lr.coef_, lr.intercept_)     # [[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132] 5개의 계수, 절편을 구하였다.

# 기억하자 로지스틱 회귀 또한 선형방정식을 구한뒤 돌리는 것이다.
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)    # 구한 선형방정식에 각 행을 넣고 돌린 결과 수치(이걸 시그모이드에 넣은뒤 0.5이하, 초과인지에 따라 음수양수 클래스 분류가 가능 한다.)
# [-6.02927744  3.57123907 -5.26568906 -4.24321775 -6.0607117 ]




#%% 사이파이 라이브러리를 활용한 시그모이드 함수 구현
from scipy.special import expit
print(expit(decisions))    # 0~1사이의 값으로 반환됨 [0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]




#%% 로지스틱 회귀를 사용한 다중분류 수행하기
lr = LogisticRegression(C=20, max_iter=1000)     # 로지스틱 회귀에서 C는 규제강도이지만, 클수록 규제가 약해짐(선형회귀 alpha랑은 반대임)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))    # 0.9327731092436975
print(lr.score(test_scaled, test_target))    # 0.925
print(lr.predict(test_scaled[:5]))    # ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
print(lr.coef_.shape, lr.intercept_.shape)    # 각 7개의 클래스에 대한 선형방정식과 절편이 구해진다.

decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))    # 소수점 2자리 까지만 표기

from scipy.special import softmax
proba = softmax(decision, axis = 1)    # 모든 결과를 합했을때 1.0이 되게 정규화를 해줌
print(np.round(proba, decimals=3))