딥러닝 - 이진 분류

사과 톡톡톡·2024년 7월 24일

머신러닝 & 딥러닝

목록 보기
7/10

1. 이진 분류 (Binary Classification)

이진 분류란?

이진 분류(Binary Classification)는 주어진 입력 데이터가 두 개의 클래스 중 하나에 속하는지 예측하는 문제를 말한다.
예를 들어, 이메일이 스팸인지 아닌지, 환자가 질병에 걸렸는지 아닌지 등을 예측하는 경우가 이진 분류에 해당한다.

이진 분류의 주요 개념

1. 클래스 (Classes):

  • 이진 분류에서는 두 개의 클래스가 있다.
    일반적으로 0과 1, 또는 양성(Positive)과 음성(Negative)으로 표현된다.
    • 양성 클래스 (Positive Class): 예측하고자 하는 주요 클래스 (예: 질병에 걸린 경우).
    • 음성 클래스 (Negative Class): 예측하고자 하는 주요 클래스가 아닌 경우 (예: 질병에 걸리지 않은 경우).

2. 모델:

로지스틱 회귀 (Logistic Regression):

  • 입력 데이터의 선형 조합을 바탕으로 0과 1 사이의 값을 출력하는 모델
    이 값은 특정 클래스에 속할 확률을 나타냄

서포트 벡터 머신 (Support Vector Machine, SVM):

  • 두 클래스 간의 경계를 최대화하는 초평면을 찾는 모델

결정 트리 (Decision Tree):

  • 데이터의 특징을 이용해 분류 규칙을 만드는 트리 구조의 모델

랜덤 포레스트 (Random Forest):

  • 여러 개의 결정 트리를 앙상블하여 예측 성능을 향상시키는 모델

신경망 (Neural Networks):

  • 다층 퍼셉트론(MLP)과 같은 신경망 구조를 사용하여 복잡한 패턴을 학습

3. 손실 함수 (Loss Function):

  • 모델의 예측 성능을 평가하기 위해 사용되는 함수

이진 크로스 엔트로피 (Binary Cross-Entropy)

L=1ni=1n[yilog(yi^)+(1yi)log(1yi^)]L = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\hat{y_i}) + (1 - y_i) \log(1 - \hat{y_i})]

  • 여기서 ( y_i )는 실제 클래스 레이블(0 또는 1), ( \hat{y_i} )는 예측된 확률값이다.

2. 퍼셉트론

퍼셉트론이란?

퍼셉트론(Perceptron)은 인공 신경망의 기초가 되는 알고리즘으로, 하나의 뉴런을 모델링한 것이다.
퍼셉트론은 입력 데이터를 받아 가중치와 곱하고, 그 합을 활성화 함수를 통해 출력하여 이진 분류를 수행한다.

퍼셉트론의 구성 요소

1. 입력 (Input):

  • x1,x2,...,xnx_1, x_2, ..., x_n와 같은 입력 값들이 주어진다

2. 가중치 (Weights):

  • 각 입력에 대응하는 가중치 w1,w2,...,wnw_1, w_2, ..., w_n가 있다.

3. 바이어스 (Bias):

  • 모델이 데이터에 더 잘 맞도록 조정하는 추가적인 매개변수 bb가 있다.

4. 가중합 (Weighted Sum):

  • 입력 값과 그에 대응하는 가중치의 곱을 모두 더하고, 바이어스를 더하여 가중합을 계산한다.
  • z=i=1n(wixi)+bz = \sum_{i=1}^{n} (w_i \cdot x_i) + b

5. 활성화 함수 (Activation Function):

  • 가중합을 입력으로 받아 출력 값을 결정한다.
  • 퍼셉트론에서는 주로 단위 계단 함수 (Step Function)를 사용한다.
  • output={1if z00if z<0\text{output} = \begin{cases} 1 & \text{if } z \ge 0 \\0 & \text{if } z < 0 \end{cases}

퍼셉트론의 학습 알고리즘

1. 초기화:

  • 가중치와 바이어스를 임의의 작은 값으로 초기화

2. 예측:

  • 현재 가중치와 바이어스를 사용하여 입력 데이터에 대한 예측을 수행

3. 업데이트:

  • 예측 값과 실제 값의 차이를 이용해 가중치와 바이어스를 업데이트
    • 만약 예측이 잘못되었다면, 다음 식을 사용하여 업데이트
      • wi=wi+Δwib=b+Δbw_i = w_i + \Delta w_i \\b = b + \Delta b
    • 여기서 Δwi\Delta w_iΔb\Delta b는 학습률(learning rate) η\eta와 오차(error) ϵ\epsilon를 이용해 계산
      • Δwi=ηϵxiΔb=ηϵ\Delta w_i = \eta \cdot \epsilon \cdot x_i \\\Delta b = \eta \cdot \epsilon

        ϵ=yy^\epsilon = y - \hat{y}
      • 여기서 ( y )는 실제 값, ( \hat{y} )는 예측 값이다.

4. 반복:

  • 주어진 훈련 데이터에 대해 예측과 업데이트를 반복하여 가중치와 바이어스를 조정

퍼셉트론의 한계

선형 분리 가능성:

  • 퍼셉트론은 선형 분리 가능한 문제에 대해서만 수렴한다.
  • 즉, 입력 데이터가 선형적으로 분리되지 않는다면 퍼셉트론은 적절한 가중치와 바이어스를 찾지 못한다.

복잡한 패턴 학습:

  • 단일 퍼셉트론은 단순한 선형 결정 경계만 학습할 수 있으므로, 복잡한 패턴을 학습하는 데 한계가 있다.

3. 아달린

아달린(ADALINE)이란?

아달린(ADALINE, Adaptive Linear Neuron)은 퍼셉트론과 유사한 선형 분류 모델이지만, 가중치 업데이트 방식에서 차이가 있는 모델이다.
아달린은 학습 과정에서 예측 오류를 기반으로 가중치를 조정하는 방식으로, 퍼셉트론과 달리 실제 출력과 예측 출력 간의 연속적인 차이를 최소화하는 데 중점을 둔다.

주요 개념

1. 구조:

  • 아달린의 기본 구조는 퍼셉트론과 비슷하다.
  • 입력값, 가중치, 바이어스, 가중합, 활성화 함수로 구성된다.

2. 가중합 (Weighted Sum):

  • 입력 값과 가중치의 곱을 모두 더하고 바이어스를 더하여 계산한다.
  • z=i=1n(wixi)+bz = \sum_{i=1}^{n} (w_i \cdot x_i) + b

3. 활성화 함수 (Activation Function):

  • 아달린에서는 순수한 선형 활성화 함수를 사용한다.
  • 즉, 활성화 함수는 단순히 가중합을 그대로 전달힌다.
  • y^=z\hat{y} = z

  • 여기서 y^\hat{y}는 예측된 출력값

학습 알고리즘

1. 초기화:

  • 가중치와 바이어스를 임의의 작은 값으로 초기화

2. 예측:

  • 현재 가중치와 바이어스를 사용하여 입력 데이터에 대한 예측을 수행
  • y^=z\hat{y} = z

3. 오차 계산 (Error Calculation):

  • 예측 값과 실제 값 사이의 오차를 계산
  • ϵ=yy^\epsilon = y - \hat{y}

  • 여기서 yy는 실제 출력값

4. 가중치 업데이트 (Weight Update):

  • 가중치와 바이어스를 다음 식을 통해 업데이트
  • wi=wi+ηϵxiw_i = w_i + \eta \cdot \epsilon \cdot x_i

  • b=b+ηϵb = b + \eta \cdot \epsilon

  • 여기서 η\eta는 학습률(learning rate)

5. 반복:

  • 주어진 훈련 데이터에 대해 예측과 업데이트를 반복하여 가중치와 바이어스를 조정

아달린의 특징

  • 선형 활성화 함수: 아달린은 선형 활성화 함수를 사용하여 출력을 계산. 이는 퍼셉트론의 비선형 활성화 함수와의 주요 차이점입니다.
  • 연속적 오차 최소화: 아달린은 실제 출력과 예측 출력 간의 연속적인 오차를 최소화하는 데 중점을 둠. 이는 퍼셉트론의 이진 오차 최소화 방식과 다릅니다.
  • MSE 손실 함수: 아달린은 평균 제곱 오차(MSE)를 손실 함수로 사용하여 가중치를 업데이트함. 이는 예측 값과 실제 값 간의 차이를 제곱하여 평균을 구하는 방식입니다.

MSE=1ni=1n(yiyi^)2MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y_i})^2

응용 및 한계

아달린은 선형 분류 문제에서 유용하게 사용될 수 있지만, 비선형 데이터에 대해서는 성능이 제한적이다.
비선형 데이터에 대한 성능을 개선하기 위해 다층 퍼셉트론(MLP)과 같은 신경망 구조가 발전하였다.
그러나 아달린은 여전히 중요한 역사적 모델로, 많은 현대 신경망 알고리즘의 기초가 되었다.

4. 계단 함수

계단 함수란?

계단 함수(Step Function)는 특정 임계값을 기준으로 출력을 결정하는 비선형 활성화 함수이다.
이 함수는 입력값이 임계값보다 크거나 같으면 하나의 값을 출력하고, 작으면 다른 값을 출력하는 방식으로 동작한다.
계단 함수는 퍼셉트론에서 주로 사용된다.

계단 함수의 정의

Step(x)={1if x00if x<0\text{Step}(x) = \begin{cases} 1 & \text{if } x \ge 0 \\0 & \text{if } x < 0 \end{cases}

여기서 ( x )는 입력값이다.
이 정의는 입력값이 0 이상일 때 1을 출력하고, 0보다 작을 때 0을 출력한다.
임계값을 다른 값으로 설정할 수도 있으며, 그에 따라 출력이 결정된다.

계단 함수의 특징

1. 비선형성:

  • 계단 함수는 입력값의 연속적인 변화를 출력값의 비연속적인 변화로 변환한다.
  • 이는 비선형성을 도입하여 신경망이 더 복잡한 패턴을 학습할 수 있도록 돕는다.

2. 단순성:

  • 계산이 매우 간단하여 퍼셉트론과 같은 초기 신경망 모델에서 사용되기 적합하다.

3. 이진 분류:

  • 계단 함수는 주로 이진 분류 문제에서 사용된다.
  • 입력값이 특정 임계값을 넘는지 여부에 따라 두 개의 클래스 중 하나로 분류한다.

계단 함수의 한계

1. 미분 불가능성:

  • 계단 함수는 0에서 미분 불가능하다.
  • 이는 경사 하강법(Gradient Descent)과 같은 최적화 알고리즘에서 사용하기 어렵게 만든다.
  • 신경망의 가중치를 업데이트하기 위해서는 활성화 함수의 미분이 필요하기 때문이다.

2. 출력의 갑작스러운 변화:

  • 입력값이 임계값을 살짝 넘거나 못 미칠 때 출력이 갑작스럽게 변화하여, 작은 입력 변화가 큰 출력 변화를 초래할 수 있다.
  • 이는 모델의 안정성에 영향을 줄 수 있습니다.

계단 함수의 대안

1. 시그모이드 함수 (Sigmoid Function):

  • 출력값을 0과 1 사이의 연속적인 값으로 변환
  • p=11+ezp = \frac{1}{1 + e^{-z}}

2. ReLU (Rectified Linear Unit):

  • 입력값이 0보다 크면 그대로 출력하고, 0 이하이면 0을 출력
  • ReLU(x)=max(0,x)\text{ReLU}(x) = \max(0, x)

3. 탄젠트 하이퍼볼릭 함수 (Tanh):

  • 출력값을 -1과 1 사이의 연속적인 값으로 변환
  • tanh(x)=exexex+ex\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}

5. 로지스틱 회귀

로지스틱 회귀란?

로지스틱 회귀(Logistic Regression)는 이진 분류 문제에서 많이 사용되는 통계적 모델이다.
로지스틱 회귀는 입력 변수와 출력 변수 간의 관계를 모델링하여, 주어진 입력 데이터가 특정 클래스에 속할 확률을 예측한다.
이름에는 "회귀"가 포함되어 있지만, 실제로는 분류 문제에 사용되는 모델이다.

주요 개념

1. 로짓 함수 (Logit Function):

  • 로지스틱 회귀는 로짓 함수를 사용하여 선형 결합된 입력 값을 확률 값으로 변환한다.
  • 로짓 함수는 시그모이드 함수라고도 불리며, 다음과 같은 수식을 가진다.
  • σ(z)=11+ez\sigma(z) = \frac{1}{1 + e^{-z}}

  • 여기서 ( z )는 입력 값들의 선형 결합으로 표현된다.
  • z=w0+w1x1+w2x2+...+wnxnz = w_0 + w_1 x_1 + w_2 x_2 + ... + w_n x_n

2. 확률 출력:

  • 로지스틱 회귀 모델은 입력 데이터가 특정 클래스(예: 1 클래스)에 속할 확률을 출력한다.
  • 출력 값은 0과 1 사이의 값을 가지며, 0.5를 기준으로 클래스 0과 클래스 1을 구분할 수 있다.

3. 모델의 학습:

  • 로지스틱 회귀 모델은 최대 우도 추정(Maximum Likelihood Estimation, MLE)을 사용하여 가중치를 학습한다.
  • 이는 주어진 데이터에 대해 모델의 출력이 실제 레이블과 최대한 일치하도록 가중치를 조정하는 과정이다.

수학적 표현

선형 결합:

  • z=w0+w1x1+w2x2+...+wnxnz = w_0 + w_1 x_1 + w_2 x_2 + ... + w_n x_n

시그모이드 함수:

  • σ(z)=11+ez\sigma(z) = \frac{1}{1 + e^{-z}}

예측 확률:

  • P(y=1x)=σ(z)=11+e(w0+w1x1+w2x2+...+wnxn)P(y=1|x) = \sigma(z) = \frac{1}{1 + e^{-(w_0 + w_1 x_1 + w_2 x_2 + ... + w_n x_n)}}

  • 여기서 P(y=1x)P(y=1|x)는 주어진 입력 xx에 대해 출력이 클래스 1일 확률을 의미

손실 함수

로지스틱 회귀는 이진 크로스 엔트로피(Binary Cross-Entropy) 또는 로그 손실(Log Loss)을 손실 함수로 사용한다.
손실 함수는 모델의 예측이 실제 레이블과 얼마나 차이가 나는지를 측정한다.

L=1ni=1n[yilog(yi^)+(1yi)log(1yi^)]L = -\frac{1}{n} \sum_{i=1}^{n} [y_i \log(\hat{y_i}) + (1 - y_i) \log(1 - \hat{y_i})]

여기서 yiy_i 는 실제 레이블, yi^\hat{y_i}는 모델의 예측 확률

6. 시그모이드 함수

probs = np.arange(0, 1, 0.01)
odds = [p/(1-p) for p in probs]
plt.plot(probs, odds)
plt.xlabel('p')
plt.ylabel('p/(1-p)')
plt.show()

### 출력 결과


이 그래프는 ppp1p\frac{p}{1-p} 의 관계를 나타낸다.
이는 오즈 비율(Odds Ratio)이라고 불리며, 확률 p 가 주어졌을 때 해당 사건이 발생할 확률과 발생하지 않을 확률의 비율을 의미한다.

probs  = np.arange(0.001, 0.999, 0.001)
logit = [np.log(p/(1-p)) for p in probs]
plt.plot(probs, logit)
plt.xlabel('p')
plt.ylabel('log(p/(1-p))')
plt.show()

### 출력 결과


이 그래프는 pplog(p1p)\log\left(\frac{p}{1-p}\right) 의 관계를 나타낸다.
이는 로짓 함수(Logit Function)이다.
로지스틱 회귀에서 로짓 함수는 선형 결합된 입력 값을 확률 값으로 변환하기 위해 사용된다.

zs = np.arange(-10., 10., 0.1)
gs = [1/(1+np.exp(-z)) for z in zs]
plt.plot(zs, gs)
plt.xlabel('z')
plt.ylabel('1/(1+e^-z)')
plt.show()

### 출력 결과


이 그래프는 zz11+ez\frac{1}{1 + e^{-z}} 의 관계를 나타낸다.
이는 시그모이드 함수(Sigmoid Function)로, 로지스틱 회귀에서 사용된다.
시그모이드 함수는 입력 값 zz 를 0과 1 사이의 값으로 변환하여 확률로 해석할 수 있게 한다.

로지스틱 손실 함수를 경사 하강법에 적용

분류의 정확도는 미분 가능한 함수가 아니다.
대신 이진 크로스 엔트로피 (binary cross entropy) 또는 로지스틱(logistic) 손실 함수를 사용한다.
L=(ylog(a)+(1y)log(1a))L = -(ylog(a) + (1 - y)log(1-a))

  • yy : 타깃값
  • aa : 활성화 함수의 출력

L
y가 1인 경우(양성 클래스) -log(a)
y가 0인 경우(음성 클래스) -log(1-a)

로지스틱 손실 함수 미분

미분의 연쇄 법칙

미분의 연쇄 법칙이란?

미분의 연쇄 법칙(Chain Rule)은 복합 함수의 미분을 계산할 때 사용하는 중요한 원칙이다.
복합 함수란 두 개 이상의 함수가 합성된 형태의 함수이다.
예를 들어, 함수 f(g(x))f(g(x))에서 g(x)g(x)가 먼저 적용되고, 그 결과에 다시 ff가 적용되는 경우를 생각할 수 있다.

연쇄 법칙을 뉴런 그림에 나타내기

연쇄 법칙의 공식

합성 함수의 도함수를 구하는 방법:

  • y=f(u)y = f(u),    u=g(x)u = g(x)

  • y=f(g(x))y = f(g(x))


  • yx=yuux\frac{\partial y}{\partial x} = \frac{\partial y}{\partial u} \cdot \frac{\partial u}{\partial x}


    연쇄 법칙은 복합 함수의 도함수를 구할 때 매우 유용하며, 수학, 물리학, 공학 등 다양한 분야에서 널리 사용된다.

1] 분류용 데이터셋 준비

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(cancer.data.shape, cancer.target.shape)

### 출력 결과
(569, 30) (569,)

cancer.data[:3]

### 출력 결과
array([[1.799e+01, 1.038e+01, 1.228e+02, 1.001e+03, 1.184e-01, 2.776e-01,
        3.001e-01, 1.471e-01, 2.419e-01, 7.871e-02, 1.095e+00, 9.053e-01,
        8.589e+00, 1.534e+02, 6.399e-03, 4.904e-02, 5.373e-02, 1.587e-02,
        3.003e-02, 6.193e-03, 2.538e+01, 1.733e+01, 1.846e+02, 2.019e+03,
        1.622e-01, 6.656e-01, 7.119e-01, 2.654e-01, 4.601e-01, 1.189e-01],
       [2.057e+01, 1.777e+01, 1.329e+02, 1.326e+03, 8.474e-02, 7.864e-02,
        8.690e-02, 7.017e-02, 1.812e-01, 5.667e-02, 5.435e-01, 7.339e-01,
        3.398e+00, 7.408e+01, 5.225e-03, 1.308e-02, 1.860e-02, 1.340e-02,
        1.389e-02, 3.532e-03, 2.499e+01, 2.341e+01, 1.588e+02, 1.956e+03,
        1.238e-01, 1.866e-01, 2.416e-01, 1.860e-01, 2.750e-01, 8.902e-02],
       [1.969e+01, 2.125e+01, 1.300e+02, 1.203e+03, 1.096e-01, 1.599e-01,
        1.974e-01, 1.279e-01, 2.069e-01, 5.999e-02, 7.456e-01, 7.869e-01,
        4.585e+00, 9.403e+01, 6.150e-03, 4.006e-02, 3.832e-02, 2.058e-02,
        2.250e-02, 4.571e-03, 2.357e+01, 2.553e+01, 1.525e+02, 1.709e+03,
        1.444e-01, 4.245e-01, 4.504e-01, 2.430e-01, 3.613e-01, 8.758e-02]])
        
plt.boxplot(cancer.data)
plt.xlabel('feature')
plt.ylabel('value')
plt.show()

### 출력 결과

cancer.feature_names[[3,13,23]]

### 출력 결과
array(['mean area', 'area error', 'worst area'], dtype='<U23')

np.unique(cancer.target, return_counts=True)

### 출력 결과
(array([0, 1]), array([212, 357]))

x = cancer.data
y = cancer.target

x

### 출력 결과
array([[1.799e+01, 1.038e+01, 1.228e+02, ..., 2.654e-01, 4.601e-01,
        1.189e-01],
       [2.057e+01, 1.777e+01, 1.329e+02, ..., 1.860e-01, 2.750e-01,
        8.902e-02],
       [1.969e+01, 2.125e+01, 1.300e+02, ..., 2.430e-01, 3.613e-01,
        8.758e-02],
       ...,
       [1.660e+01, 2.808e+01, 1.083e+02, ..., 1.418e-01, 2.218e-01,
        7.820e-02],
       [2.060e+01, 2.933e+01, 1.401e+02, ..., 2.650e-01, 4.087e-01,
        1.240e-01],
       [7.760e+00, 2.454e+01, 4.792e+01, ..., 0.000e+00, 2.871e-01,
        7.039e-02]])
        
y

### 출력 결과
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0,
       1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0,
       1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1,
       1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0,
       0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1,
       1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0,
       0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0,
       1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1,
       1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0,
       0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
       0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
       1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
       1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1,
       1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
       1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
       1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1,
       1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1])

2] 로지스틱 회귀 모델

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(x, y, stratify=y, 
                                                    test_size=0.2, random_state=42)
                                                    
print(x_train.shape, x_test.shape)

### 출력 결과
(455, 30) (114, 30)

np.unique(y_train, return_counts=True)

### 출력 결과
(array([0, 1]), array([170, 285]))

np.unique(y_test, return_counts=True)

### 출력 결과
(array([0, 1]), array([42, 72]))
class LogisticNeuron:
    
    def __init__(self):
        self.w = None
        self.b = None

    def forpass(self, x):
        z = np.sum(x * self.w) + self.b  # 직선 방정식을 계산합니다
        return z

    def backprop(self, x, err):
        w_grad = x * err    # 가중치에 대한 그래디언트를 계산합니다
        b_grad = 1 * err    # 절편에 대한 그래디언트를 계산합니다
        return w_grad, b_grad

    def activation(self, z):
        z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
        a = 1 / (1 + np.exp(-z))  # 시그모이드 계산
        return a
        
    def fit(self, x, y, epochs=100):
        self.w = np.ones(x.shape[1])      # 가중치를 초기화합니다.
        self.b = 0                        # 절편을 초기화합니다.
        for i in range(epochs):           # epochs만큼 반복합니다
            for x_i, y_i in zip(x, y):    # 모든 샘플에 대해 반복합니다
                z = self.forpass(x_i)     # 정방향 계산
                a = self.activation(z)    # 활성화 함수 적용
                err = -(y_i - a)          # 오차 계산
                w_grad, b_grad = self.backprop(x_i, err) # 역방향 계산
                self.w -= w_grad          # 가중치 업데이트
                self.b -= b_grad          # 절편 업데이트
    
    def predict(self, x):
        z = [self.forpass(x_i) for x_i in x]    # 정방향 계산
        a = self.activation(np.array(z))        # 활성화 함수 적용
        return a > 0.5
neuron = LogisticNeuron()
neuron.fit(x_train, y_train, 1000)

neuron.w

### 출력 결과
array([ 1.81086044e+04, -1.24739236e+04,  6.15479592e+04,  2.29708006e+03,
       -2.36374580e+02, -1.55925367e+03, -2.29217368e+03, -9.00954111e+02,
       -5.29003854e+02, -6.23364384e+01,  1.95660848e+02,  1.49599975e+03,
       -4.23463727e+03, -1.14675592e+04, -4.85204916e+01, -4.54812331e+02,
       -6.30236381e+02, -1.43944877e+02, -1.32761289e+02, -4.12155554e+01,
        1.72585155e+04, -2.21131568e+04,  1.12605908e+04, -9.86149886e+03,
       -5.55641094e+02, -5.50388907e+03, -6.83560328e+03, -1.89790258e+03,
       -1.70994065e+03, -4.88775942e+02])
       
neuron.b

### 출력 결과
2266.000816493729

neuron.predict(x_test).astype(int)

### 출력 결과
array([0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0,
       1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0,
       0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1,
       1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
       0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0,
       1, 0, 1, 1])
       
y_test

### 출력 결과
array([0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0,
       1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0,
       0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0,
       1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1,
       1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0,
       1, 0, 1, 1])
       
np.mean(neuron.predict(x_test) == y_test)

### 출력 결과
0.8947368421052632

7. 단일층 신경망


사실 우리는 이미 단일층 신경망을 만들었다.
위에서 만든것들이 단일층 신경망이다.

class SingleLayer:
    
    def __init__(self):
        self.w = None
        self.b = None
        self.losses = []

    def forpass(self, x):
        z = np.sum(x * self.w) + self.b  # 직선 방정식을 계산합니다
        return z

    def backprop(self, x, err):
        w_grad = x * err    # 가중치에 대한 그래디언트를 계산합니다
        b_grad = 1 * err    # 절편에 대한 그래디언트를 계산합니다
        return w_grad, b_grad

    def activation(self, z):
        z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
        a = 1 / (1 + np.exp(-z))  # 시그모이드 계산
        return a
        
    def fit(self, x, y, epochs=100):
        self.w = np.ones(x.shape[1])               # 가중치를 초기화합니다.
        self.b = 0                                 # 절편을 초기화합니다.
        for i in range(epochs):                    # epochs만큼 반복합니다
            loss = 0
            # 인덱스를 섞습니다
            indexes = np.random.permutation(np.arange(len(x)))
            for i in indexes:                      # 모든 샘플에 대해 반복합니다
                z = self.forpass(x[i])             # 정방향 계산
                a = self.activation(z)             # 활성화 함수 적용
                err = -(y[i] - a)                  # 오차 계산
                w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
                self.w -= w_grad                   # 가중치 업데이트
                self.b -= b_grad                   # 절편 업데이트
                # 안전한 로그 계산을 위해 클리핑한 후 손실을 누적합니다
                a = np.clip(a, 1e-10, 1-1e-10)
                loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a))
            # 에포크마다 평균 손실을 저장합니다
            self.losses.append(loss/len(y))
    
    def predict(self, x):
        z = [self.forpass(x_i) for x_i in x]     # 정방향 계산
        return np.array(z) > 0                   # 스텝 함수 적용
    
    def score(self, x, y):
        return np.mean(self.predict(x) == y)
layer = SingleLayer()
layer.fit(x_train, y_train)
layer.score(x_test, y_test)

### 출력 결과
0.9298245614035088

plt.plot(layer.losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

### 출력 결과

8. 사이킷런

사실 사이킷런에 이미 로지스틱 회귀가 있다.

from sklearn.linear_model import SGDClassifier

sgd = SGDClassifier(loss='log_loss', max_iter=100, tol=1e-3, random_state=42)
sgd.fit(x_train, y_train)
sgd.score(x_test, y_test)

### 출력 결과
0.8333333333333334

sgd.predict(x_test[0:10])

### 출력 결과
array([0, 1, 0, 0, 0, 0, 1, 0, 0, 0])

loss='' 부분에서 로지스틱 손실 함수를 지정해주었다.
회귀일 경우에는 SGDRegressor 모델을 쓰면 된다.

0개의 댓글