본문 바로가기

컴퓨터/머신러닝

[혼자 공부하는 머신러닝] 4. 다양한 분류 알고리즘

다중 분류

타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류라고 한다.

물고기 데이터를 이용해서 다중 분류 문제를 볼 때 먼저 데이터를 아래와 같이 불러올 수 있다.

import pandas as pd

fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()

데이터의 첫 5개의 행

첫 번째 열에서 물고기 종 데이터가 나오고 뒤의 5개의 열은 해당 물고기 데이터에 대한 특성을 나타낸다.

따라서 특성에 따라 물고기의 종을 예측하기 위해 종에 해당하는 특성을 target으로 설정하고 나머지를 train데이터로 분류하여 학습을 진행하면 된다.

로지스틱 회귀

샘플이 특정 클래스에 속할 확률을 추정하는데 사용되는 분류 모델로 입력 특성의 가중치 핪을 계산하고 편향을 더한다. 

로지스틱은 선형회귀처럼 값을 바로 출력하지 않고 결괏값인 로지스틱을 출력한다.
로지스틱은 0과 1의 사이 값을 출력하는 시그모이드 함수인데 $\phi = \frac{1}{1 + e^{-z}}$로 표기된다.
이때 $\phi$값이 0.5 이상이면 1을 출력하고, 0.5 미만이면 0으로 출력하는 모델을 만든다.

이를 활용해 이진 분류를 해보면 먼저 물고기 데이터에서 도미와 빙어데이터만 추출하면 아래와 같이 할 수 있다.

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]

lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
print(lr.score(train_bream_smelt, target_bream_smelt))
# 결과: 1

이렇게 하면 모든 데이터가 잘 예측 된 것을 볼 수 있다.

로지스틱 회귀로 다중 분류

로지스틱 회귀의 경우 기본적으로 반복적인 알고리즘을 사용한다.

로지스틱의 규제는 기본적으로 계수의 제곱을 규제하는 릿지 회귀와 같은 L2규제를 사용한다.

LogisticRegression에서 규제의 양을 조절하는 alpha의 값이 C의 역수이기 때문에 C의 값을 통해서 규제의 양을 조절할 수 있다.

 

lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)

print(lr.score(train_scaled, train_target)) # 결과: 0.9327731092436975
print(lr.score(test_scaled, test_target)) # 결과: 0.925

 

- 소프트맥스 회귀

다중 분류를 할 경우 이진 분류와 다르게 두 값만 비교하는게 아니여서 시그모이드 함수를 사용해 변환할 수 없다.

따라서 소프트맥스함수를 사용하여 각각의 클래스에 대해 확률을 변환해야한다.이는 샘플X가 주어지면 각 클래스K에 대한 점수를 계산하고 그 점수에 소프트맥스함수를 적용하여 각 클래스의 확률을 추정한다.
$$ 
(소프트맥스 함수) = \frac {e^{s_k(x)}}{ \sum\limits_{j=1}^{K} {e^{s_k(x)}} } , \quad s_k(x) = (\Theta^{(k)})^T x
$$

 

실제 softmax를 사용하여 예측을 하면 아래처럼 각각의 데이터가 어떤 군집에 속하는지 확률적으로 결과가 나온다.

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

확률적 경사 하강법

모든 데이터 세트를 활용하여 학습을 진행하면 훈련을 하기 위해 매우 많은 시간과 컴퓨팅 자원이 필요해진다.

따라서 이러한 문제를 막기위해 일부의 데이터를 활용하여 학습을 진행행하고 학습된 결과에 새로운 데이터를 사용해 점진적인 학습을 진행하면 된다.

확률적 경사 하강법에서는 매 스텝 한 개의 샘플을 무작위로 선택해 그레디언트를 계산하여 처리한다.
이는 매 스텝 한 개의 샘플만 계산을 하기 때문에 큰 훈련셋에도 훈련이 가능하고 시간이 적게 걸린다.

다만, 이는 확률적이므로 배치 경사하강법보다 불안정하다.
즉, 알고리즘이 멈출 때 대체적으로 준수한 파라미터가 나오지만 이것이 최적의 값이 아닐 수도 있다.

하지만 오히려 무작위성 때문에 지역최솟값에서 값이 수렴하는 것을 탈출시켜 전역최솟값을 찾게 할수도 있다.

손실 함수(loss function)

손실 함수는 샘플 하나에 대해서 손실을 정의한다.

비용 함수는 모든 샘플에 대한 손실 함수의 합을 의미하는데 보통 둘을 동일하게 생각한다.

경사 하강법을 이용해서 학습을 하기 위해서는 손실함수가 연속적이여서 조금씩 하강할 수 있어야 한다.

그리고 샘플의 예측이 정확하면 조금 하강하고 손실이 크면 많이 하강해야 한다.

 

- 로지스틱 손실 함수

로지스틱 모델의 경우 확률이 0에서 1사의의 실수이기 때문에 연속적이고 예측 확률이 높을수록 값이 크기 때문에 이를 음수로 하여 손실 함수로 사용하면 된다. 

이때 로그를 사용하면 로그는 0에서 1사이에서 음수가 되고 0에 가까울수록 아주 큰 음수가 되어 손실을 아주 크게 만들어 모델에 큰 영향을 줄 수 있다.
따라서 y = 1인 양성 샘플에 대해서는 높은 확률 추정하고, y = 0인 음성샘플에 대해서는 낮은 확률 추정하는 모델을 로그 손실이라하고 다음과 같다.
$$J(\Theta) = - {1 \over m} \sum_{i=1}^n {y^{(i)} log(\hat p^{(i)}) + (1 - y^{(i)}) log(1 - \hat p^{(i)})}$$

에포크에 따른 과대/과소적합

에포크의 횟수가 너무 적다면 훈련 세테가 덜 학습되어 훈련 세트와 테스트 세트가 잘 맞지 않는 과소적합된 모델이 나올 가능성이 높다.

반면 에포크가 너무 크면 훈련 세트에 너무 과도하게 학습되어 과대적합이 발생할 가능성이 높다.

따라서 과대적합이 발생하기 전에 조기 종료조건을 통해 훈련을 멈추는 방법을 사용하여 적절한 학습 수를 결정해야한다.

 

sc = SGDClassifier(loss='log_loss', 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))
    
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

 

에포크 수에 따른 정확도 차이

위의 결과를 보면 epoch가 대략 60정도가 넘어가면 테스트 세트에 정확도는 향상이 없고 훈련 세트만 과대적합 되기 때문에 epoch를 60정도로 설정해주는 것이 가장 좋다.