1. 로지스틱 회귀
로지스틱 회귀/ 다중 분류/ 시그모이드 함수/ 소프트맥스 함수
러키백의 확률
구성품을 모르는 채 먼저 구매하고, 배송받은 다음 구성품을 알 수 있는 상품
-> 러키백에 포함된 생선의 확률 알려주기
러키백에 들어갈 수 있는 생선: 7개
생선의 크기, 무게 등 -> 7개 생선에 대한 확률
길이, 높이, 두께 + 대각선의 길이, 무게 추가
=> 확률은 숫자 -> 회귀?/ 7개 생선 -> 분류?
k-최근접 이웃: 주변 이웃을 찾아줌 -> 이웃의 클래스 비율 = 확률
데이터 준비
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
데이터프레임(dataframe)
- 판다스에서 제공하는 2차원 표 형식의 주요 데이터 구조
- 넘파이 배열과 비슷하게 열과 행으로 이뤄짐
Species 열: 타깃/ 나머지 5개 열: 입력 데이터
# 고유한 값 추출
print(pd.unique(fish['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()
훈련 세트와 테스트 세트 나누기
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-최근접 이웃 분류기의 확률 예측
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_traget)) # 0.890756~
print(kn.score(test_scaled, test_target)) # 0.85
=> fish 데이터프레임: 7개의 생선 -> 훈련 세트, 테스트 세트의 타깃 데이터: 7개의 생선 종류
= 다중 분류(multi-class classification)
- 타깃 데이터에 2개 이상의 클래스가 포함된 문제
- 타깃값 그대로 사이킷런 모델에 전달 -> 순서가 자동으로 알파벳 순으로 매겨짐
-> KNeighborsClassifier에서 정렬된 타깃값: classes_ 속성에 저장돼 있음
print(kn.classes_) # ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
print(kn.predict(test_scaled[:5])) # ['Perch' 'Smelt' 'Pike' 'Perch' 'Perch']
=> Bream이 첫 번째 클래스, Parkki가 두 번째 클래스...
/ predict(): 타깃값으로 예측 출력
import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4)) # 소수점 4번째 자리까지 표기, 5번째 자리에서 반올림
=> predict_proba(): 클래스별 확률값 반환
4번째 샘플에 최근접 이웃의 클래스 확인
distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes]) # [['Roach' 'Perch' 'Perch']]
- 다섯 번째 클래스: Roach 1개, 확률: 1/3 = 0.3333
- 세 번째 클래스: Perch 2개, 확률: 2/3 = 0.6667
=> 3개의 최근접 이웃 사용 -> 가능한 확률: 0/3, 1/3, 2/3, 3/3 밖에
로지스틱 회귀(logistic regression)
- 선형 방정식을 사용한 분류 알고리즘
- 선형 방정식 학습(= 선형 회귀)
- 시그모이드 함수나 소프트맥스 함수를 사용해 클래스 확률 출력할 수 있음(!= 선형 회귀)
$$ z = a * (Weight) + b * (Length) + c * (Diagonal) + d * (Height) + e * (Width) + f (a, b, c, d, e: 가중치 or 계수) $$
-> 확률이 되려면 0~1 (or 0~100%) 사이 값이 되어야 함
z가 음수 -> 0/ z가 아주 큰 양수 -> 1로 바꾸는 방법
= 시그모이드 함수(sigmoid function) = 로지스틱 함수(logistic function)
- 선형 방정식의 출력을 0과 1 사이의 값으로 압축
- 이진 분류를 위해 사용
-5 ~ 5 사이에 0.1 간격으로 배열 z 만들고 z 위치 마다 시그모이드 함수 계산
0.5 보다 크면 양성 클래스/ 0.5 보다 작으면 음성 클래스
np.exp(): 지니 함수 계산
import numpy as np
import matplotlib.pyplot as plt
z = np.arrange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z, phi)
plt.xlabel('z')
plt.ylbael('phi')
plt.show()
로지스틱 회귀로 이진 분류 수행하기
불리언 인덱싱(boolean indexing): 넘파일 배열 -> True, False 값을 전달하여 행을 선택
char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]]) # ['A', 'C']
도미(Bream)와 빙어(Smelt) 행 골라내기
bream_smelt_indexs = (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 패키지 아래 있음
- solver 매개변수에서 사용할 알고리즘 선택할 수 있음 (기본값: lbfgs)
- 사이킷런 0.17 버전: sag 추가
- 확률적 평균 경사 하강법 알고리즘 특성과 샘플 수 ↑ -> 성능 빠르고 좋음
- 사이킷런 0.19 버전: 개선 버전 saga 추가
- 사이킷런 0.17 버전: sag 추가
- penalty 매개변수: L2 규제(릿지 방식), L1 규제(라쏘 방식) 선택할 수 있음 (기본값: l2)
- C 매개변수: 규제의 강도 제어 (기본값: 1.0)
- 값 ↓ -> 규제 ↑
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, traget_bream_smelt)
예측 확률: 음성 클래스(0)에 대한 확률/ 양성 클래스(1)에 대한 확률
# 예측
print(lr.predict(train_bream_smelt[:5])) # ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
# 예측 확률
print(lr.predict_proba(train_bream_smelt[:5]]))
# 타깃값 알파벳순으로 정렬하여 사용 -> Smelt가 양성 클래스
print(lr.classes_) # ['Bream' 'Smelt']
# 계수 확인
print(lr.coef_, lr.intercept_)
# [[-0.4037798 -0.67620209 -0.6628~ -1.01290~ -0.73168~ ]] [-2.16155132]
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions) # [-6.029277~ 3.571239~ -5.265689~ -4.24321~ -6.0607117 ]
from scipy.special import expit
print(expit(decisions))
# [0.00240145 0.97726~ 0.00513~ 0.01415~ 0.00232~]
로지스틱 회귀로 다중 분류 수행하기
- 반복적인 알고리즘 사용
- -> max_iter 매개변수: 반복 횟수 지정 (기본값: 100) -> 1,000으로 설정
- 계수의 제곱 규제: L2 규제
- 규제 제어 매개변수: C (기본값: 1) -> 20으로 설정
- C ↓ -> 규제 ↑ (alpha와 반대)
다중 분류
- 타깃 클래스 2개 이상인 분류 문제
- 로지스틱 회귀) 다중 분류 위해 소프트맥스 함수 사용하여 클래스 예측
lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) # 0.932773
print(lr.score(test_scaled, test_target)) # 0.925
테스트 세트의 처음 5개 샘플
# 예측
print(lr.predict(test_scaled[:5])) # ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
# 예측 확률
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
=> 세 번째 열의 확률이 가장 높음 (84.1%): Perch
- 이진 분류) 샘플마다 2개의 확률 출력
- 다중 분류) 샘플마다 클래스 개수만큼 확률 출력/ 가장 높은 확률 -> 예측 클래스
print(lr.classes_)
# ['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
print(lr.coef_.shape, lr.intercept_.shape) # (7, 5) (7, )
=> z를 7개 계산 -> 클래스마다 z 값 하니씩 계산
가장 높은 z 값 출력하는 클래스 -> 예측 클래스
- 이진 분류) 시그모이드 함수: z -> 0과 1 사이의 값
- 다중 분류) 소프트맥스 함수: z -> 확률로 변환 (전체 합: 1)
-> 지수 함수 사용 = 정규화된 지수 함수
$$ e_{sum} = e^{z1} + e^{z2} + e^{z3} + e^{z4} + e^{z5} + e^{z6} + e^{z7} $$
$$ s1 = \frac{e^{z1}}{e_{sum}}, s1 = \frac{e^{z2}}{e_{sum}}, ..., s7 = \frac{e^{z7}}{e_{sum}} $$
s1에서 s7까지 더하면 1
테스트 세트의 처음 5개 샘플에 대한 z1~z7의 값
decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
softmax()
- axis 매개변수: 소프트맥스 계산할 축 지정
- axis=1: 각 샘플(행)에 대해 소프트맥스 계산
- axis 매개변수 지정 X -> 배열 전체에 대해 소프트맥스 계산
from scipy.special import softmax
proba = softmax(decision, axis=1)
print(np.round(proba, decimals=3))
predict_proba()
- 예측 확률 반환
- 이진 분류) 샘플마다 음성 클래스와 양성 클래스에 대한 확률 반환
- 다중 분류) 샘플마다 모든 클래스에 대한 확률 반환
decision_function()
- 모델이 학습한 선형 방정식의 출력 반환
- 이진 분류) 양성 클래스의 확률 반환/ 0보다 크면 양성, 작거나 같으면 음성 클래스로 예측
- 다중 분류) 각 클래스마다 선형 방정식 계산/ 가장 큰 값의 클래스 -> 예측 클래스
[문제해결 과정] 로지스틱 회귀로 확률 예측
러키백에 담긴 생선이 어떤 생선인지 확률 예측
분류 모델 -> 예측, 예측의 근거가 되는 확률 출력
- 확률: 분류 모델이 얼마나 예측을 확신하는지
- 확률 ↑ -> 강하게 예측
k-최근접 이웃 모델
- 확률 출력O
- (-) 이웃한 샘플의 클래스 비율 -> 항상 정해진 확률만 출력
=> (해결) 로지스틱 회귀
- 회귀 X 분류 모델 O
- 선형 방정식(= 선형 회귀)
- 이진 분류) 하나의 선형 방정식 훈련
- 출력값 -시그모이드 함수-> 0~1 사이의 값 만듦 = 양성 클래스에 대한 확률
- 음성 클래스의 확률: 1 - 양성 클래스의 확률
- 다중 분류) 클래스 개수만큼 방정식 훈련
- 출력값 -소프트맥스 함수-> 전체 클래스에 대한 합 항상 1 = 각 클래스에 대한 확률
- 이진 분류) 하나의 선형 방정식 훈련
- 계산한 값 그대로 출력(= 선형 회귀) X 0~1 사이로 압축
-> 0~100% 사이의 확률로 이해
2. 확률적 경사 하강법
확률적 경사 하강법/ 손실 함수/ 에포크
점진적인 학습 = 온라인 학습
훈련 데이터 조금씩 추가, 데이터 쌓일 때까지 기다릴 수 X
- 기존 훈련 데이터에 새로운 데이터 추가 -> 매일 다시 훈련
=> (-) 시간 -> 데이터 ↑ -> 서버 ↑ 필요 - 새로운 데이터 추가 시 이전 데이터 버림 -> 데이터 크기 일정하게 유지
=> (-) 데이터 버릴 때 다른 데이터에 없는 중요한 데이터 포함되어 있을 수 있음
=> 이전에 훈련한 모델 버리지 않고 새로운 데이터에 대해 조금씩 더 훈련
확률적 경사 하강법
무작위하게, 랜덤하게/ 기울기/ 내려가는 방법 = 경사를 따라 내려가는 방법
- 훈련 세트에서 샘플 하나씩 꺼내 손실 함수의 경사을 따라 최적의 모델을 찾는 알고리즘
- 에포크(epoch)
- 훈련 세트를 한 번 모두 사용하는 과정
- 전체 샘플을 모두 사용하는 한 번 반복 의미
- (default) 수십 ~ 수백 번의 에포크 반복
- 미니배치 경사 하강법(minibatch gradient descent)
- 무작위로 몇 개 샘플 선택해서 경사 따라 내려가는 방법
- 샘플 하나씩 사용 X 여러 개 사용
- 배치 경사 하강법(batch gradient descent)
- 극단적으로 한 번 경사로를 따라 이동하기 위해 전체 샘플 사용
- 한 번에 전체 샘플을 사용
- (+) 전체 데이터 사용 -> 안정적
- (-) 컴퓨터 자원 ↑
손실 함수(loss function)
- 확률적 경사 하강법이 최적화할 대상
- 작을수록 좋음 -> But, 어떤 값이 최솟값인지 알지 X
- 대부분의 문제에 잘 맞는 손실 함수 이미 정의되어 있음
- 이진 분류) 로지스틱 회귀(or 이진 크로스엔트로피)
- 다진 분류) 크로스엔로피
- 회귀 문제) 평균 제곱 오차
- 대부분의 문제에 잘 맞는 손실 함수 이미 정의되어 있음
비용 함수(cost function): 훈련 세트에 있는 모든 샘플에 대한 손실 함수의 합
손실 함수: 샘플 하나에 대한 손실 정의
=> 비용 함수 != 손실 함수 But, 엄격히 구분 X 섞어서 사용
로지스틱 손실 함수(logistic loss function) = 이진 크로스엔트로피 손실 함수(binary cross-entropy loss function)
타깃이 음성 클래스라 0일 경우, 예측 확률과 곱하면 무조건 0
-> 타깃을 양성 클래스처럼 바꿔 1로 만듦 + 예측값도 양성 클래스에 대한 예측으로 바꿈(1 - 예측값)
로그 함수: 0에 가까울수록 아주 큰 음수가 됨
- 양성 클래스(타깃 = 1)
- 손실: -log(예측 확률)
- 확률이 1에서 멀어져 0에 가까워질수록 손실은 아주 큰 양수가 됨
- 음성 클래스(타깃 = 0)
- 손실: -log(1 - 예측 확률)
- 확률이 0에서 멀어져 1에 가까워질수록 손실은 아주 큰 양수가 됨
크로스엔트로피 손실 함수(cross-entropy loss function): 다중 분류에서 사용하는 손실 함수
SGDClassifier
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)
tarin_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
SGDClassifier
- 확률적 경사 하강법 사용한 분류 모델 만듦
- loss 매개변수: 확률적 경사 하강법으로 최적화할 손실 함수 지정
- 기본값: 서포트 벡터 머신) hinge 손실 함수/ 로지스틱 회귀) log로 지정
- penalty 매개변수: 규제의 종류 지정 (기본값: L2 규제를 위한 l2)
- L1 규제 적용 -> l1로 지정
- 규제 강도: alpha 매개변수에서 지정 (기본값: 0.0001)
- max_iter 매개변수: 에포크 횟수 지정 (기본값: 1000)
- tol 매개변수: 반복을 멈출 조건 (기본값: 0.001)
- no_iter_no_change 매개변수: 에포크 동안 손실 지정 (기본값: 5)
- 에포크 동안 손실이 tol 만큼 줄어들지 않으면 알고리즘 중단
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.773109~
print(sc.score(test_scaled, test_target)) # 0.775
=> 훈련 세트와 테스트 세트 정확도가 낮음
-> 지정한 반복 횟수(10번) 부족
sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target) # 0.815126~
print(sc.score(tets_scaled, test_target) # 0.825
=> 에포크 한 번 더 실행 -> 정확도 향상
=> 무작정 많이 반복 X 얼마나 더 훈련?
SGDRegreesor
- 확률적 경사 하강법을 사용한 회귀 모델 만듦
- loss 매개변수: 손실함수 지정 (기본값: 제곱 오차 나타내는 squared_loss)
- SGDClassifier에서 사용하는 매개변수 동일하게 사용
에포크와 과대/과소적합
확률적 경사 하강법을 사용한 모델) 에포크 횟수 -> 과소적합 or 과대적합
- 에포크 횟수 ↓
- 모델이 훈련 세트 덜 학습 = 산 다 내려오지 못하고 훈련 마침
- 훈련 세트와 테스트 세트에 잘 맞지 않는 과소적합된 모델일 가능성 ↑
- 에포크 횟수 ↑
- 훈련 세트 완전히 학습 = 훈련 세트에 아주 잘 맞는 모델 만들어짐
- 훈련 세트에 너무 잘 맞아 테스트 세트에 점수가 나쁜 과대적합된 모델일 가능성 ↑
조기 종료(early stopping): 과대적합이 시작하기 전에 훈련을 멈추는 것
fit() 메서드 사용 X partial_fit() 메서드만 사용
-> 훈련 세트에 있는 전체 클래스의 레이블을 partial_fit() 메서드에 전달해 주어야 함
import numpy as np
sc = SGDClassifier(loss='log', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)
for _ in range(0, 300): # 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.ylbael('accuracy')
plt.show()
=> 백 번째 에포크 이후 훈련 세트와 테스트 세트의 점수 조금씩 벌어짐
에포크 초기: 과소적합 -> 훈련 세트와 테스트 세트의 점수 ↓
SGDClassifier의 반복 횟수 100에 맞추기
sc = SGDClassifier(loss='log', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, tarin_target)) # 0.95798~
print(sc.score(test_scaled, test_target)) # 0.925
=> 일정 에포크 동안 성능 향상 X -> 더 이상 훈련 X 자동으로 멈춤
- tol 매개변수: 향상될 최솟값 지정
- tol=None: 자동으로 멈춤 X, max_iter=100 만큼 무조건 반복
- loss 매개변수 (기본값: hinge)
- 힌지 손실(hinge loss): 서포트 벡터 머신(support vecotr machine)을 위한 손실 함수
ex. 힌지 손실 사용해 같은 반복 횟수 동안 모델 훈련
sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.94957~
print(sc.score(test_scaled, test_target)) # 0.925
[문제해결 과정] 점진적 학습을 위한 경사 하강법
생선을 실시간으로 학습하기 위한 새로운 머신러닝 모델
-> 확률적 경사 하강법 => 점진적으로 학습하는 로지스틱 회귀 모델
- 손실 함수(ex. 산) 정의, 가장 가파른 경사를 따라 조금씩 내려오는 알고리즘
- 충분히 반복하여 훈련 -> 훈련 세트에서 ↑ 점수 얻는 모델
- But, 훈련 반복 -> 모델이 훈련 세트에 점점 더 잘 맞음 => 과대 적합, 테스트 세트 정확도 ↓
출처
'Artificial Intelligence' 카테고리의 다른 글
[혼자 공부하는 머신러닝+딥러닝] Chapter 03 회귀 알고리즘과 모델 규제 (0) | 2024.07.25 |
---|---|
[밑바닥부터 시작하는 딥러닝] CHAPTER 2 퍼셉트론 (0) | 2023.03.30 |
[밑바닥부터 시작하는 딥러닝] CHAPTER 1 헬로 파이썬 (0) | 2023.03.28 |
[머신러닝 교과서 with 파이썬, 사이킷런, 텐서플로] 2장 간단한 분류 알고리즘 훈련 (0) | 2023.03.16 |
[머신러닝 교과서 with 파이썬, 사이킷런, 텐서플로] 1장 컴퓨터는 데이터에서 배운다 (1) | 2023.03.16 |