본문 바로가기

2024 - 2학기/머신러닝 & 딥러닝

04 다양한 분류 알고리즘

728x90
728x90
Reporting Date: October. 2, 2024

로지스틱 회귀와 확률적 경사 하강법과 같은 분류 알고리즘을 배우고,
이진 분류와 다중 분류의 차이를 이해하며, 각 클래스에 대한 확률 예측에 대해 다루고자 한다.


목차

04-1. 로지스틱 회귀

04-2. 확률적 경사 하강법


 

 

 

04 - 1 .  로지스틱 회귀

 

4-1 로지스틱 회귀.ipynb

Run, share, and edit Python notebooks

colab.research.google.com

import pandas as pd
# 데이터 준비하기
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()

Species를 타겟으로 하고 나머지를 입력 데이터로 사용한다.

 

 

어떤 종류의 생선이 있는지 확인한다.

print(pd.unique(fish['Species']))

7가지의 생선 종이 있음을 확인.

 

 

Species를 제외한 새로운 데이터프레임 만들기.

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
print(fish_input[:5])

새로운 데이터 프레임이 만들어 졌음을 확인.

 

 

모델 만들기.

더보기
# 'Species' 열의 데이터를 타겟 변수로 설정
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() # StandardScaler 객체 생성
ss.fit(train_input)  # 표준화 전처리 작업
train_scaled = ss.transform(train_input)  # 훈련 데이터 변환
test_scaled = ss.transform(test_input)  # 테스트 데이터 변환


from sklearn.neighbors import KNeighborsClassifier
# k-NN 분류기 객체 생성 (k=3)
kn = KNeighborsClassifier(n_neighbors=3)
# 훈련된 데이터를 사용하여 모델 학습
kn.fit(train_scaled, train_target)


# 훈련 데이터 정확도 출력
print(kn.score(train_scaled, train_target))
# 테스트 데이터 정확도 출력
print(kn.score(test_scaled, test_target))

fish['Species']는 여러 종류의 물고기 종(species)을 대상으로 하므로,
각 샘플이 둘 이상의 클래스 중 하나로 분류된다.

이처럼 클래스가 3개 이상일 때,
이를 다중 분류라고 한다.

 

k-NN 분류기는 다중 분류 문제에서도 잘 동작하며,

각 데이터 포인트에 대해 가장 가까운 이웃들 중
다수의 클래스를 선택하는 방식으로 클래스를 예측한다.

 

 

scikit-learn의 장점

분류 알고리즘에서 문자열로 된 타겟값도 자동으로 처리할 수 있도록 설계되어 있다.
내부적으로는 문자열을 고유한 정수 값으로 인코딩하여 사용되며
이때, 사용자는 별도의 인코딩을 하지 않아도 된다.

 

문자열 타겟값을 내부적으로 처리할 때에는
알파벳 순서대로 고유한 정수로 인코딩한다.

예를 들어, 타겟값이 [ 'Tuna', 'Salmon', 'Bass' ] 일 때
다음과 같이 인코딩된다.


- Bass          ⇨   0

- Salmon       1
- Tuna           2

그러므로, 분류 모델에서 예측된 클래스가
어떤 값인지 해석할 때 이를 염두 해야 한다.

특히 모델의 성능 평가나 결과 해석 시,
클래스 레이블이 자동으로 정렬된다는 점에 주의해야 한다.

 

인코딩 순서를 직접 지정하고 싶다면,

`LabelEncoder` 또는 `pandas.Categorical`을 사용해
명시적으로 타겟값을 숫자로 변환할 수 있다.

 

 

 

정렬된 순서 확인하기.

print(kn.classes_)

알파벳 순서로 정렬된 것을 확인.

 

 

테스트 세트에 있는 처음 5개의 샘플을 예측해본다.

print(kn.predict(test_scaled[:5]))

 

 

5개의 샘플에 대한 예측이 어떤 확률을 갖는지 확인하기.

import numpy as np

proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals = 4)) # 소수점 네 번째 자리 표시

위 숫자는 각각 [ Bream ~ Whitefish ] 순서에 해당된다.

 

 

네 번째 샘플의 최근접 이웃의 클래스 확인하기.

distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])

1개의 Roach = 0.3333 + 2개의 Perch = 0.6667

클래스 확률을 정확히 예측하였다.

 

그러나, k = 3일 경우 이웃이 포함될 확률이
0/3,  1/3,  2/3,  3/3
과 같은 제한된 확률만을 가진다.

이는 확률적으로 다소 불안정한 결과를 초래할 수 있으며,
분류를 좀 더 세밀하게 조정하는 데 한계가 있다.

 

그러므로, 더 나은 성능을 얻기 위해 다른 방법이 필요할 수 있다. 

 

 

로지스틱 회귀

(Logistic Regression)

이진, 다중 분류 문제에서 자주 사용되는 통계적 기법.

선형 회귀와는 달리 예측값이 0 ~ 1 사이의 확률값으로
제한되어 분류 문제에 적합하다.

 

 

이 모델은 본질적으로 선형 모델지만,

예측값이
( ∞  ~  + ) 범위를
가지므로,
그 값을 그대로 사용하지 않는다.

 

이는 분류 문제로 만들기 위한 과정으로
시그모이드 함수를 이용하여 예측값을 0 ~ 1 사이의 확률로 변환할 수 있다.

피(Φ, φ: 그리스어 알파벳의 21번째 글자)

 

–5  ~  +5 사이에 0.1 간격으로 배열 z를 만든 뒤,
z 위치마다 시그모이드 함수를 계산한다.

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')
더보기
0 ~ 1의 범위를 갖는 것을 확인.

 

 

 

(1) 로지스틱 회귀로 이진 분류 수행하기.

# 도미와 빙어의 행만 골라내기. 
# 도미&빙어 = 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]

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

 

 

처음 5개의 샘플 예측하기.

print(lr.predict(train_bream_smelt[:5]))

두 번째 샘플을 제외한 나머지를 도미로 예측.

 

 

처음 5개의 샘플에 대해 예측 확률 확인하기.

print(lr.predict_proba(train_bream_smelt[:5]))

[ 왼쪽: 음성 클래스(0) / 오른쪽: 양성 클래스(1) ]

샘플마다 2개의 확률이 출력되었다.

 

 

어떤 것이 양성 클래스인지 알기 위해,
알파벳으로 정렬하기.

print(lr.classes_)

오른쪽에 있는 빙어(Smelt)가 양성 클래스인 것을 확인.

따라서, 로지스틱 회귀를 통해 성공적으로 이진 분류를 수행하였다.

 

 

로지스틱 회귀가 학습한 계수 확인하기.

print(lr.coef_, lr.intercept_)

 

 

처음 5개의 샘플의 z값을 출력.

decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)

 

 

이 값은 시그모이드 함수를 적용하여
해당 클래스에 속할 확률로 변환하기.

from scipy.special import expit
print(expit(decisions))

두 번째 열의 값이 양성 클래스임을 확인.

 

 

(2) 로지스틱 회귀로 다중 분류 수행하기.

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))

훈련 세트와 테스트 세트 모두에서 높은 점수를 기록했으며,
과대적합이나 과소적합의 문제가 나타나지 않았다.

 

 

테스트 세트의 처음 5개의 샘플 예측하기.

print(lr.predict(test_scaled[:5]))

 

 

테스트 세트의 처음 5개의 샘플에 대한 예측 확률 출력하기.

proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))

5개의 행과 7개의 열을 통해, 다중 분류임을 확인.

 

 

클래스 정보 확인하기.

print(lr.classes_)

위 예측 확률 중 가장 큰 값이 예측된 클래스가 된다.

 

 

다중 분류의 선형 방정식 알아보기.

print(lr.coef_.shape, lr.intercept_.shape)

이 로지스틱 회귀 모델은 7개의 클래스(다중 분류)를 예측하며,
각 클래스에 대해 5개의 특성을 고려한 선형 결정을 내리고 있음을 알 수 있다.

 

 

소프트맥스 함수

(Softmax)

다중 클래스 분류 문제에서
각 클래스에 대한 확률을 계산하기 위해 사용되는 함수.

주어진 입력 값(로지스틱 회귀에서의 결정값)을
확률로 변환하여, 각 클래스에 속할 확률을 반환한다.

결과적으로, 출력된 확률들의 총합이 1이 되도록 정규화한다.

 

 

소프트맥스 함수 사용하여, 확률 변환하기.

decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals = 2))

 

 

샘플에 대한 예측 확률 출력하기.

from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, 
               # 소수점 세 번째자리
               decimals = 3))

이전에 구한 배열과 결과가 일치한다.

 

결과적으로, 7개의 생선 종류에 대한 확률을
예측하는 모델을 성공적으로 훈련하였다.


 

 

 

04 - 2 . 확률적 경사 하강법

 

4-2 확률적 경사 하강법.ipynb

Run, share, and edit Python notebooks

colab.research.google.com

 

 

확률적 경사 하강법

(Stochastic Gradient Descent, SGD)

기계 학습에서 최적화를 위해 널리 사용되는 알고리즘 중 하나.
경사 하강법의 변형 중 하나로, 특히 큰 데이터셋을 처리할 때 효과적이다.

 

이 알고리즘은 매 반복마다
손실 함수의 기울기를 계산한 뒤,
그 방향으로 파라미터를 업데이트한다.

이를 통해 손실을 줄이도록 학습하는 과정이다.

 

 

에포크

(Epoch)

전체 훈련 데이터셋을 모델이 한 번 완전히 통과한 주기.

한 번의 에포크는, 모델이 전체 데이터셋을 사용하여
파라미터를 업데이트하는 과정을 한 번 수행한 것이다.

 

 

미니 배치 경사 하강법

(Mini-Batch Gradient Descent)

전체 데이터셋을 작은 배치(mini-batch)로 나누어
각각의 배치에 대해 파라미터를 업데이트하는 방법.

 

한 번의 업데이트에 하나의 데이터 포인트인
단일 샘플만 사용하는 SGD와 달리

여러 샘플(미니 배치)로 구성된
소규모 데이터 세트를 사용한다.

 

 

배치 경사 하강법

(Batch Gradient Descent)

전체 데이터셋을 사용하여 파라미터를 업데이트하는 방법.
모든 샘플을 사용해 손실 함수의 기울기를 계산한다.

 

 

 

손실 함수

(loss function)

기계 학습 및 통계에서 모델의 예측 성능 평가용 함수.

모델이 예측한 값과 실제 값 간의 차이를 수치적으로 나타내며,
이 값을 최소화하는 것이 학습의 목표이다.

 

 

이진 교차 엔트로피 손실

(Binary Cross-Entropy Loss)

로지스틱 회귀에서의 손실 함수.

모델의 예측 확률과 실제 클래스 레이블 간의
차이측정하여,
모델을 최적화하는 데 사용된다.

 

 

이 손실 함수는 모델의 예측이
실제 클래스와 얼마나 일치하는지를 측정한다.

예측이 정확할수록 손실 값이 0에 가까워지고,
예측이 틀릴 경우 손실 값은 크게 증가하며, 
이론적으로 무한대까지 커질 수 있다.

 

 

 

특성값의 스케일을 맞춘 두 Numpy 배열 준비하기.

더보기
# 이전과 동잉한 코드
import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')

# Species ⇨ 타겟 데이터, 나머지 ⇨ 입력 데이터
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)

 

 

SGD를 사용하여 로지스틱 회귀 모델을 학습하기.

from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss = 'log_loss', 
                   max_iter = 10, 
                   random_state = 42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

두 데이터 세트 모두 점수가 낮아서 개선이 필요하다.

 

 

매 호출 시 1 에포크씩 이어서 진행하는 점진적 학습법을 시도한다.

sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))

정확도가 향상된 것을 확인.

이로 인해 에포크를 늘리면 정확도가 향상된다는 것을 알았지만,
계속해서 에포크를 늘리기만 하면 과대적합의 위험이 있다.

따라서, 과대적합되지 않으면서도 높은 정확도를 유지할 수 있는
최적의 에포크 횟수를 찾아야 한다.

 

 

과대적합되기 전, 조기 종료를 통해 최적의 에포크 횟수를 알아내기.

import numpy as np
sc = SGDClassifier(loss='log_loss', 
                   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.ylabel('accuracy')
더보기
약 100번째 에포크부터 두 데이터 세트의 점수 차이가 증가하기 시작한다.

 

 

위 그래프를 참고하여 반복 횟수를 100으로 맞추어 훈련을 진행한다.

sc = SGDClassifier(loss = 'log_loss', 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))

정확도 점수가 매우 높게 나온 것을 확인.


확률적 경사 하강법을 사용하여

생선 분류 문제를 성공적으로 수행하였다.

 

 

힌지 손실

(Hinge Loss)

loss의 매개변수의 기본값이다.

주로 SVM(Support Vector Machine)과 같은
분류 모델에서 사용되는 손실 함수이다.

 

 

힌지 손실을 기반으로 한 추가적인 분류 모델을 학습한다.

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))
print(sc.score(test_scaled, test_target))

결과적으로 loss를 log_loss로 설정했을 때, 가장 우수한 성능을 보인다.


Mapo금빛나루 | | 공유 마당 (copyright.or.kr)

교제: 혼자 공부하는 머신러닝 + 딥러닝


 

728x90
반응형

'2024 - 2학기 > 머신러닝 & 딥러닝' 카테고리의 다른 글

03 회귀 알고리즘 & 모델 규제  (4) 2024.09.24
02 데이터 다루기  (8) 2024.09.22