코랩 링크 (코드와 강의 정리 내용이 기록되어 있음)
Chapter 04-1 로지스틱 회귀
MEMO
메소드
- classes_: 레이블의 종류를 반환함(사이킷런에서 개체 속성에 _(언더바)가 있으면 학습한 모델의 속성)ㄴ 레이블링: 사이킷런에서 레이블링을 알파벳 순서로 정렬하여 레이블링을 해주지만, 순서를 사용자 정의에 의해 지정되어야 하는 경우,0:도미, 1:빙어... etc ... 로 지정해줄 수 있음
- predict(): [𝒁값 사용] 0, 1, 1로 반환하지 않고, 문자열로 반환해서 출력함(예측하는 메소드)
- predict_proba(): [시그모이드 함수 사용] 자원이 많이 필요한 경우, 확률을 매개변수로 지정을 해줘야하는 경우가 존재함. 𝒴개의 샘플에 대해 𝒳개 타겟종류의 확률로 표현됨. 행렬 𝒳×𝒴로 구성됨.
- K-최근접 이웃의 다중 분류로는 단순한 값들이 나오므로 믿을 만하지 않은 수치를 보이는 경우가 발생함.ex) 3개의 타겟 레이블이 있는데, 각 각 예측 확률을 1/3, 1/3, 1/3로 예측한 경우
럭키백의 확률
1. 데이터 준비하기
csv파일을 pandas로 읽어와 타깃 데이터, 입력 데이터로 나눈다.import pandas as pd
fish = pd.read_csv("http://bit.ly/fish_csv_data")
print(fish.head())
print(pd.unique(fish['Species'])) # 열에서 고유한 값 출력
'''
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
['Bream' 'Roach' 'Whitefish' 'Parkki' 'Perch' 'Pike' 'Smelt']
'''
#fish의 종류를 타깃 데이터, 나머지 특성을 입력 데이터
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
훈련 세트와 테스트세트로 나눈 후 표준점수로 전처리한다.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
#훈련세트와 테스트세트로 나눔
train_input, test_input, train_target, test_target = train_test_split(
fish_input, fish_target, random_state=42
)
#입력 데이터 전처리
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
2. k-최근접 이웃 분류기로 확률 예측
사이킷런의 KneighborsClassifier 클래스로 모델을 훈련한다.
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(kn.classes_)
'''
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
'''
사이킷런에서는 문자열로 된 타깃값을 그대로 사용할 수 있지만, 순서가 자동으로 알파벳 순서로 매겨진다.
훈련된 모델로 테스트 세트의 5개 샘플의 종류를 예측한다.
print(kn.predict(test_scaled[:5]))
'''
['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
'''
- pridict_proba() 메서드는 클래스별 확률값을 반환한다.
- Numpy의 round()는 반올림 함수이며 decimals 매개변수는 유지할 소수점 아래 자리를 지정할 수 있다.
import numpy as np
# 클래스별 확률값 반환
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4)) #소숫점 4자리까지 반올림해 반환
'''
[[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0. 0. 0. 1. 0. ]
[0. 0. 0. 1. 0. 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]]
'''
4번째 샘플의 경우 Perch일 확률이 2/3, Roach일 확률이 1/3이다.
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])
'''
[['Roach' 'Perch' 'Perch']]
'''
4번째 샘플의 이웃은 Perch가 2개, Roach가 1개로, 구한 확률이 맞음을 보여준다.
단, 이 방법은 3개의 최근접 이웃만을 사용하기에 확률은 0, 1/3, 2/3, 1 뿐이라는 한계가 있다.
- 로지스틱 회귀는 이름은 회귀이지만 분류 모델이다.
- 선형 회귀와 동일하게 선형 방정식을 학습한다.
𝛧값과 시그모이드 함수
𝛧 = a x 무게 + b x 길이 + c x 대각선 + d x 높이 + e x 두께 + f
- a, b, c, d, e는 계수이며 z는 어떤 값도 될 수 있다.
- z 값을 그대로 사용하게 되면 회귀가 된다.
- 하지만 분류를 하기 위해서는 확률로 표현해야하고, 0 ~ 1 사이의 값이 되어야 하기 때문에
- 그대로 사용하게 되면 "-∞ ~ ∞"의 범위가 되는데, 이를 0 ~ 1로 변환해주는 함수가 필요
- 그래서 사용하는 수학적 트릭이 시그모이드 함수
- 대부분 시그모이드 함수를 로지스틱 함수라고 부름
- 𝛧가 아주 큰 음수일때 0이 되고, 아주 큰 양수일 때 1이 되도록 바꾼다.
- 이는 시그모이드 함수를 사용하면 가능하다.
3-1. 시그모이드 함수
시그모이드 함수 : ϕ= 1/(1+e)^(-z)
넘파이를 이용해서 간단하게 그려본다.
- 0과 1사이의 값을 나타내줌
- 이는 0.5이상이면 양성, 아니면 음성클래스를 출력함
- 이는 𝒁값만 고려해도 예측을 할 수 있음. 𝛧값이 0일때, 시그모이드 함수에서의 값은 0.5 이기 때문
- 양성클래스: 𝒁 > 0, 시그모이드 함수 > 0.5
- 음성클래스: 𝒁 ≤ 0, 시그모이드 함수 ≤ 0.5
시그모이드 함수를 사용하는 이유
- 판단만 하는 것이면 𝒁값만 사용해서 0보다 큰지, 작은지만 고려하면 되나,
- 이를 활용하여 예측 확률을 계산하고 싶다면 시그모이드 함수를 사용해야함.
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()
3-2. 로지스틱 회귀로 이진분류 수행하기
불리언 인덱싱: 인덱스에 각각 True or False로 구분하여 해당 값이 True로 되는 원소만 가져오는 것 (해당 원소만 뽑아낼 때 사용)
불리언 인덱싱으로 도미와 빙어 데이터를 골라낸다.
이진 분류를 먼저 수행해 볼 것이다.
이진 분류에서 시그모이드 출력이 0.5보다 크면 양성클래스, 작으면 음성클래스로 판단한다.
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]
# 이 데이터로 로지스틱 회귀 모델을 훈련한다.
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
# 훈련한 모델로, 5개의 테스트 샘플을 예측해본다.
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]
'''
계수들과 절편을 볼 수 있다.
따라서 이 로지스틱 회귀 모델이 학습한 방정식은 다음과 같다.
z = -0.404 x 무게 + -0.576 x 길이 + ··· + -2.161
z값과 시그모이드 함수의 값 또한 볼 수 있다.
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
from scipy.special import expit
print(expit(decisions))
# expit()메소드에 딕션 배열을 매개변수로 주었을때, 시그모이드 함수값이 나오는 것을 확인할 수 있음.
'''
[-6.02927744 3.57123907 -5.26568906 -4.24321775 -6.0607117 ]
[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]
'''
POINT __이진분류
- 로지스틱 회귀는 이진 분류의 경우, 양성클래스의 𝛧값만 계산함.
- 확률을 출력할 때는 음성클래스도 계산할 수 있음. 1에서 빼는 방법으로...
3-3. 로지스틱 회귀로 다중 분류
LogisticRegression 클래스는 반복적인 알고리즘을 사용하며, max_iter 매개변수에서 반복값을 지정, Default = 100
로지스틱 회귀는 기본적으로 L2규제를 적용하는 모델
- 규제가 기본적으로 적용되어 있는 것
- 릿지 회귀와 같이 계수의 제곱을 규제하며, L2 규제라고 불림
규제 매개변수 C(대문자)
- 릿지회귀에서 alpha로 규제의 양을 조절한 것과 달리, C(대문자) 매개변수로 조절함
- Default = 1, 작을수록 규제가 강해짐(단순해짐)
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
print(lr.predict(test_scaled[:5])) # 샘플 5개의 종류 예측
'''
0.9327731092436975
0.925
['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
'''
과대적합이나 과소적합이 되지 않았다.
5개 샘플에 대한 예측 샘플도 볼 수 있다.
print(lr.classes_)
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
'''
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
[[0. 0.014 0.841 0. 0.136 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.935 0.015 0.016 0. ]
[0.011 0.034 0.306 0.007 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
'''
[Code]
print(lr.coef_.shape, lr.intercept_.shape)
[Result]
-> (7, 5) (7,)
- 이는 7개의 클래스와, 5개의 특성, 곱해지는 계수를 나타냄
- 클래스마다 선형 함수가 하나씩 만들어짐
- 7개의 선형함수를 만들어서 나오는 가장 큰 값이 예측 클래스가 됨
- 학습을 할때는 이진 분류처럼 함.
- OvR(One vs Rest) 다중분류: 이진분류를 많이 해서 훈련하는 방법
소프트맥스 함수
- 시그모이드 함수에서 여러개의 𝛧값을 적용하면 각각의 합이 1이 되도록 학습함. 그러나 다중 분류에서 시그모이드 함수를 사용하면
- "1"보다 크거나 작은 엉뚱한 값이 나오기 때문에, 7개의 선형함수 출력물을 그대로 시그모이드 함수에 넣어서는 안됨.
- 다중분류의 경우, 예측 확률은 소프트맥스 함수를 사용하여 7개의 z값을 확률로 변환함.
다중분류에서 확률처럼 보여지기 위해 사용하는 수학적 트릭
여러 개의 선형 방정식의 출력값을 0~1로 압축하고, 전체 합이 1이 되도록 만든다.
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
'''
[[ -6.5 1.03 5.16 -2.73 3.34 0.33 -0.63]
[-10.86 1.93 4.77 -2.4 2.98 7.84 -4.26]
[ -4.34 -6.23 3.17 6.49 2.36 2.42 -3.87]
[ -0.68 0.45 2.65 -1.19 3.26 -5.75 1.26]
[ -6.4 -1.99 5.82 -0.11 3.5 -0.11 -0.71]]
'''
from scipy.special import softmax
proba = softmax(decision, axis=1)
print(np.round(proba, decimals=3))
'''
[[0. 0.014 0.841 0. 0.136 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.935 0.015 0.016 0. ]
[0.011 0.034 0.306 0.007 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
'''
로지스틱회귀
선형함수를 학습해서 결과값을 시그모이드 함수 혹은 소프트맥스 함수를 활용하여 이진분류 혹은 다중분류를 사용함.
이진분류의 경우, 양성클래스에 대해서만 결과를 얻을 수 있음.
C 값은 커질수록 복잡해짐. 5장에서 적절한 C값을 얻을 수 있도록 배울 것임.
[질의응답]
OvR: 여러 클래스의 선형회귀 계수와 같다.
- 클래스 A, B, C에 대하여 예측할 때, 클래스A를 구분할 때는 클래스A를 양성클래스로 두고 판별하고, 클래스B를 구분할 때는 클래스B를 양성클래스로 두고 판별하고, 클래스C를 구분할 때는 클래스C를 양성클래스로 두고 이진분류하여 3개의 선형함수가 나오게 되며, 𝛧값이 소프트맥스을 통과하여 확률값으로 반환하여 가장 높은 확률값이 예측값이 됨.
- 𝛧값만 보고도 예측할 수 있음.
- 선형함수를 여러개 학습하기 때문에 선형함수 계수는 이진분류했을 때의 계수와 같음.
다중분류: 이진분류를 여러번해서 다중분류를 수행함
04-2 확률적 경사 하강법
원래 모델을 유지하면서 처음부터 모델을 훈련하지 않고 업데이트할 수는 없을까?
가중치와 절편은 유지하면서 데이터를 업데이트만 할 수는 없을까?
- 점진적 학습: 학습하는 도중에 업데이트 하는 것
- 온라인 학습: 모델을 서비스하는 도중에 업데이트 하면서 서비스를 제공하는 것새로운 데이터를 훈련하는 방법, 최적화하는 방법
확률적 경사하강법 (SGD)
- 머신러닝 알고리즘이 아님
- 머신러닝, 딥러닝 알고리즘을 훈련하는 방법, 최적화하는 방법
- 확률적: 랜덤
- 경사: 기울기(?)
- 하강법: 내려가는 것
- 무작위하게 경사를 내려가는 방법
- 가장 빨리 내려가려면 가장 가파른 경사를 찾아 내려가되, 조금씩 내려가야함.
- 경사하강법은 경사를 따라 내려가면서 #최적점을 찾는 것
- 가파른 경사로 내려가는 것이 합리적
방법
- 훈련세트와 샘플을 하나씩 꺼내서 훈련(조금씩 경사를 따라 이동)
- 반복
- 훈련세트를 모두 다 사용? Yes -> "1 에포크" 완료
- 훈련세트에 샘플을 모두 채우고 다시 시작
조금씩 내려가야하기 때문에 한번 사용해서는 안되고 여러번 수십번 사용함. 여러번 사용했을때, 합리적으로 최적점을 구할 수 있음.
이외의 경사 하강법
- 미니 배치 경사 하강법: 여러개씩 꺼내기 -> 꺼내는 개수(2의 배수): 하이퍼파라미터
- 배치 경사 하강법: 몽땅꺼내기 -> 메모리가 한정적이기 때문에 활용할 수 없음
손실 함수
- 머신러닝 모델의 나쁜 정도를 측정하는 함수: 낮을 수록 좋음
- 확률적 경사 하강법으로 w(가중치), b(계수) 값이 낮아지는 방향으로 설정하여 모델을 업데이트하는 것
- 경사하강법을 사용하려면 매끄러운 곡선 혹은 끊기지 않은 그래프, 연속적이여야함
- 분류의 정확도는 연속적인 값을 가지지 않음으로 손실함수로 사용할 수 없음
- 즉, 미분 가능하지 않으면 손실함수로 사용할 수 없음
- 분류 모델에서는 "로지스틱 손실 함수", "이진 크로스 엔트로피 함수"를 사용
- 회귀는 "평균 절대값 오차", "평균 제곱 오차" 사용
- 회귀에서는 손실함수를 측정지표로 가져갈 수 있음.
- "평균 제곱 오차"를 통해 회귀모델이 얼마의 성능을 살펴볼수 있음.
- "평균 제곱 오차"를 통해 최적화할 수 있음.
- 딥러닝에서 다루는 회귀문제는 대부분 "평균 제곱 오차"를 사용함.
로지스틱 손실 함수
- 분류에서 모델의 성능은 정확도로, 최적화는 "로지스틱 손실 함수"를 사용함.
- 경사하강법을 사용할 수 없어, 경사하강법을 사용할 수 있는 방법을 찾은 것이 "로지스틱 손실 함수"
- 정답에 가까운 것은 낮은 값이 되고, 정답과 먼 것은 높은 값이 되는 산술적 트릭을 사용하는 것
- log를 사용하는 이유: 예측값이 타깃값과 다를 시, 큰 벌칙을 가할 수 있기 때문
확률적 경사 하강법은 가장 가파른 경사를 따라 내려가야하기 때문에, 각 특성의 스케일이 같아야 공정한 평가를 할 수 있음.
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
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.linear_model import SGDClassifier
# loss : 손실 함수의 종류 지정 ('log' = 로지스틱 손실 함수)
# max_iter : 점진적 학습을 위해 (= 수행할 에포크 횟수)
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
0.773109243697479
print(sc.score(test_scaled, test_target))
0.775
# partial_fit()메소드 : 기존의 가중치와 절편을 가지고 추가 학습을 진행하는 것
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.8151260504201681
0.85
에포크와 과대/과소적합
- 규제가 커지면 과소적합
- 계속 에포크를 증가시키면 과대적합이 되어버리므로,
- 에포크가 모델 성능을 높이는 적정 수준에서 경사하강법을 그만두는 것을 "조기 종료"라고 함.
sc = SGDClassifier(loss='log', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)
for _ in range(0, 300) :
sc.partial_fit(train_scaled, train_target, classes = classes)
train_score.append(sc.score(train_scaled, train_target))
test_score.append(sc.score(test_scaled, test_target))
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.show()
sc = SGDClassifier(loss='log', max_iter=100,
tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))
0.957983193277311
0.925
[결론]
- 로지스틱 회귀모델을 점진적으로 학습하기 위해 "확률적 경사 하강법"를 배움
- 딥러닝에서 반드시 필요함: 왜? 훈련데이터가 너무 많아서 다른 방법으로는 최적의 솔루션을 찾기 어려움
- 확률적 경사 하강법은 손실함수를 정의하고 이 값이 작아지도록 파라미터를 유도함
- 에포크를 늘려갈 수록 과대적합이 발생하므로 중간에 중단하는 것을 조기종료라고함.
[질의응답]
- type(classes): 타깃 배열의 고유값으로 해당 값에 따라 정의됨
'머신러닝(ML) & 딥러닝(DL) > 혼자공부하는 머신러닝 딥러닝' 카테고리의 다른 글
Chapter 03-3. 특성 공학과 규제 (0) | 2022.12.05 |
---|---|
Chapter 03-2. 선형 회귀 (0) | 2022.12.05 |
Chapter 03-1. K-최근접 이웃 회귀 (0) | 2022.12.05 |
Chapter 02-2. 데이터 다루기 (0) | 2022.12.05 |
Chapter 02-1 훈련 세트와 테스트 세트 (0) | 2022.12.05 |