* 모든 코드는 제 깃허브 (cdjs1432/DeepLearningBasic: Deep Learning from scratch)에서 확인할 수 있습니다.

 

이번 시간엔 Logistic Regression을 구현하고, 이를 breast cancer 데이터셋으로 테스트까지 해보도록 하겠습니다.

(30가지 데이터를 토대로, 해당 유방암이 악성인지 양성인지 확인하는 데이터셋)

 

import numpy as np
from sklearn import datasets
import ComputeGrad

일단 필요한 모듈 먼저 import해 줍시다.

원래는 직접 데이터셋을 다운받아서 해도 되겠지만, 편의를 위해 sklearn 모듈을 import하겠습니다.

 

 

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

그리고, Logistic Regression에 필수적인, 시그모이드 함수를 정의해 줍시다.

 

 

cancer = datasets.load_breast_cancer()
x = cancer.data
x /= x.mean()
y = cancer.target
w = np.random.uniform(-1, 1, x.shape[1])
b = 0
learning_rate = 0.0001
epoch = 10000

w, b = ComputeGrad.LogisticGD(x, y, w, b, learning_rate, epoch)

그리고, 이제 필요한 변수들을 모두 불러와 봅시다.

 

아까 import 해왔던 datasets에서 breast_cancer을 불러오고, 각각의 data와 target을 x, y에 집어넣어 줍시다.

(이 때, 당연히 data는 원래 x값에 들어갈 데이터를, target은 y값에 들어갈 데이터를 가지고 있습니다.)

 

그런데 이번 코드에는 저번과는 다르게 x값을 x의 평균으로 나누는 코드까지 같이 들어있습니다.

왜 이렇게 하는걸까요?

 

이유를 잠깐 간단히 설명해 보도록 하겠습니다.

만약 x값의 범위가 위아래로 너무 커버리면, w값과 곱할 때도 더욱 범위가 커지게 됩니다.

그러면, 한번에 너무 크게 dw값이 진동하게 되고, 이는 결국 learning_rate를 아주 큰 값으로 둔 것과 비슷한 결과를 낳게 됩니다.

(참고로, 사실 x를 그냥 평균으로 나눠버리는 것 보다 x값을 표준화 시키는 더욱 좋은 방법들이 많이 있지만, 그것은 나중에 언급하도록 하고 우선 지금은 이정도로 둡시다. 이정도도 충분히 잘 작동하거든요.)

 

 

아무튼, 그 뒤 w값과 b값, learning_rate와 epoch값을 초기화 해 줍니다.

 

이제, LogisticGD 함수를 확인하러 갑시다.

 

 

def LogisticGD(x, y, w, b=0, learning_rate=0.001, epoch=10000):
    for epochs in range(epoch):
        z = x.dot(w) + b
        pred = sigmoid(z)
        err = -np.log(pred) * y - np.log(1 - pred) * (1 - y)
        err = err.mean()
        """
        to minimize err --> differentiate pred
        err = -ln(pred)*y - ln(1-pred)*(1-y)
        --> -y/pred + (1-y) / (1-pred)

        differentiate pred by z
        pred = 1 / (1+e^-z)
        --> e^z / (1+e^-z)^2
        --> e^z / (1+e^-z) * 1 / (1+e^-z)
        --> pred * (1 - pred)

        chain rule : dl/dpred * dpred/dz = dl/dz

        as the chain rule, derivative of the loss function respect of z
        --> (-y/pred + (1-y) / (1-pred)) * (pred * 1-pred)
        --> (1-pred) * -y + pred * (1-y)
        --> -y +y*pred +pred -y*pred
        --> pred - y

         now, let's find dl/dw
         dl/dw = dl/dz * dz/dw
               = (pred - y) * dz/dw
         let's find dz/dw...

         z = wT.x + b
         --> dz/dw = x

         so, dl/dw = (pred - y) * dz/dw
                   = (pred - y) * x


        similarly, find dl/db...
        dl/db = dl/dz * dz/db
              =  (pred - y) * dz/db
        z = wT.x + b
        --> dz/db = 1

        so, dl/db = (pred - y)

        """
        dw = (pred - y).dot(x)
        db = (pred - y).mean()
        w -= dw * learning_rate
        b -= db * learning_rate
        if epochs % 1000 == 0:
            print(err)
    return w, b

 

 

 

주석이 더럽게 깁니다. 계산 과정을 보고싶은 것이 아니라면, 그냥 무시하고 지워버리셔도 됩니다.

 

일단, logistic regression에서의 prediction은 사실 Gradient Descent에다가 sigmoid만 집어넣은 것입니다.

그리고, Logistic Regression의 err도 아래의 loss function을 사용하도록 하겠습니다.

 

$ err = -ln(pred) * y - ln(1 - pred) * (1-y) $

 

이제 저희가 생각해야 할 부분은, 이 err 함수를 각각 w와 b에 대해서 미분하는 것입니다.

 

 

우리는 여기서 chain rule(연쇄 법칙)이라는 것을 사용할 것입니다.

chain rule이란, $ \frac{\partial z}{\partial x} = \frac{\partial z}{\partial y} \frac{\partial y}{\partial x} $인 것을 말합니다.

저희는 이제부터 $ \frac{\partial err}{\partial w} $의 값을 찾을 겁니다.

 

우선, 가장 간단하게 err을 pred에 대해서 미분해 볼까요?

$ err = -ln(pred) * y - ln(1 - pred) * (1-y) $ 이었고, 이를 pred에 미분해보면...

$$ \frac{\partial err}{\partial pred} = \frac{-y}{pred} + \frac{1-y}{1-pred} $$

가 됩니다.

 

또, pred는 $ sigmoid(z) = \frac{1}{1+e^{-z}} $ 였으므로, 이 pred를 z에 대해서 미분하면,

$$ \frac{\partial pred}{\partial z} = \frac{e^{-z}}{(1+e^{-z})^2}$$

이 된다는 것을 알 수 있습니다.

 

그리고, z=wx+b이므로 z를 w에 대해 미분하면 그냥 x, z를 b에 대해 미분하면 그냥 1이 나옵니다.

그리고 지금까지 구한 것들을 chain rule을 사용하여 다시 정리해 봅시다.

 

$$ \frac{\partial err}{\partial w} = \frac{\partial err}{\partial pred} * \frac{\partial pred}{\partial z} * \frac{\partial z}{\partial w} $$

$$ \frac{\partial err}{\partial w} = (\frac{-y}{pred} + \frac{1-y}{1-pred}) * \frac{e^{-z}}{(1+e^{-z})^2} * x$$

 

자! 이제 이 위의 pred와 z에 한번 식을 대입해서 계산해 봅시다!!

 

 

 

는 사실 좀;;;; 너무하죠 그쵸?

 

 

사실 아까 전에, $ \frac{\partial pred}{\partial z} = \frac{e^{-z}}{(1+e^{-z})^2} $ 라고 했던 걸 조금 정리를 해 보면,

$pred = \frac{1}{1+e^{-z}}$라고 했으므로,

 

$$ \frac{e^{-z}}{(1+e^{-z})^2} = \frac{1}{1+e^{-z}} * \frac{e^{-z}}{1+e^{-z}} = pred * (1-pred)$$

로 정리가 됩니다!!

 

 

자, 그럼 이제 다시 위 식으로 돌아가서 하던 계산을 마저 해 봅시다!

 

 

$$ \frac{\partial err}{\partial w} = \frac{\partial err}{\partial pred} * \frac{\partial pred}{\partial z} * \frac{\partial z}{\partial w} $$

$$ = (\frac{-y}{pred} + \frac{1-y}{1-pred}) * pred * (1 - pred) * x$$

$$ = ((pred - 1) * y + pred * (1 - y)) * x$$

$$ = (pred - y) * x$$

 

 

이제 이렇게 두니, 아주 예쁜 식이 나왔군요!

또한,$ \frac{\partial err}{\partial b}$의 값도 구해 보자면, $ \frac{\partial z}{\partial b} = 1 $이었으므로,

$$ \frac{\partial err}{\partial b} = \frac{\partial err}{\partial pred} * \frac{\partial pred}{\partial z} * \frac{\partial z}{\partial b} $$

$$ = pred - y$$

 

라는 식이 도출됩니다!!

 

따라서, dw, db를 위의 코드와 같이 update시켜줄 수 있습니다.

 

 

이제, 제대로 훈련이 되었나 확인해야겠죠?

원래 쓰던 코드에, 다음과 같은 코드를 넣어서 확인해 봅시다.

z = x.dot(w) + b
pred = sigmoid(z)

pred[pred > 0.5] = 1
pred[pred <= 0.5] = 0
print("ACC : ", (pred == y).mean())

pred 값을 직접 계산해서, 0.5보다 크면 1(양성), 0.5보다 작거나 같으면 0(악성)인 종양으로 판별하게 하는 것입니다.

 

그리고, (pred ==y).mean()의 코드로 정확도를 보면, 대충 0.92, 즉 92%의 정확도를 보이네요!

(참고 - (pred == y)를 하면 pred와 y가 같으면 1, 다르면 0을 각각의 인덱스에 따라 출력하니, 그 평균을 맞추면 정확도가 됩니다.)

 

이렇게, Logistic Regression의 구현까지 마쳤습니다. 다음 시간에는 Softmax Classification의 구현을 해보도록 하겠습니다.

+ Recent posts