본문 바로가기

머신러닝

[파이썬] 분류 알고리즘( 로지스틱 회귀, 시그모이드)

728x90

 

 

1. 분류 알고리즘

 

머신러닝에서 데이터 분류 알고리즘은 트레이닝 데이터의 특성과 상관관계 등을 분석한 후에

임의의 입력 데이터에 대해서 그 결과를 어떤 종류의 값으로 나눌 수 있는지 ,

즉 분류할 수 있는지를 예측합니다

 

분류에 대한 구체적인 예

 

아래 이미지는 분류 알고리즘의 도식화입니다.

 

 

위 이미지처럼 데이터를 2개 집단 이상으로 나누어 분류하는 알고리즘을

분류 알고리즘이라 합니다.

 

2. 로지스틱 회귀

 

데이터의 특성과 분포를 가장 잘 나타내는 직선을 먼저 찾고 , 이직선을 기준으로 데이터를 위아래 또는

왼쪽이나 오른쪽 등으로 분류하는 방법을 로지스틱회귀라고 합니

 

이러한 로지스틱 회귀는 분류 알고리즘 중에서도 정확도가 상당히 높은 것으로 알려져 있어서 신경망 ,

즉 딥러닝에서도 기본적인 컴포넌트로 사용됩니다.

 

컴포넌트는 기존의 코딩 방식에 의한 개발에서 벗어나 소프트웨어 구성단위 (module) 를 미리 만든 뒤

필요한 응용기술을 개발할 때 이 모듈을 조립하는 기술을 의미합니

 

로지스틱 회귀 알고리즘을 정리해보면 아래의 그림과 같이 트레이닝 데이터를 입력으로 받아서

회귀 부분에서는 최적의 직선을 찾고 , 그 직선을 마탕으로 분류 부분에서는 위 아래 등으로 나누어서

최종 출력값으로 True (1) 또는 False(0) 등을 갖도록 분류해 줍니

 

예를 들어 성적에 따른 Pass, Fail여부를 예측하는 등의 경우에서 사용할 수 있습니다.

 

 

728x90

 

3. 시그모이드 함수

 

시그모이드 함수에 대한 정의입니다.
아래의 그림과 같이 입력값 z=0 에서 함수값은 0.5, 입력 값 z>0 인 경우 함수값은 1 에 가까워지며 ,

입력값 z<0 인 경우에는 함수값이 0 에 수렴합니다. 즉, 모든 입력값에 대한 함수의 출력이

0 과 1 사이의 값이 되는 함수를 시그모이드 함수라고하며 y = sigmoid(z) = 1/1+e^-z(z=Wx+b) 형태로 나타납니

 

시그모이드 함수에 대한 해석입니다.

그모이드 함수는 0 과 1 사이 값으로 계산되기 때문에 시그모이드 함수의 결과를 확률로해석하기도 합니다 .

예를 들어 시그모이드 함수값을 0.75 라고 할 때 , True 상태로 정의할 수도 있지만 상황에 따라서

결과가 나올 확률이 75% 라고 해석하기도 합니

 

출처 :&nbsp;https://reniew.github.io/12/

 

분류에서의 손실함수에 대한 대수적 표현입니다.
최종 출력값은 시그모이드 함수에 의해서 0 과 1 사이의 값으로 표시되며 ,

이러한 출력값은 확률로 해석할 수 있기 때문에 분류 시스템의 오차값을 나타내는 손실함수는

아래의 식과 같은 로그를 포함한 대수식으로 표현되며 , 크로스 엔트로피라고 부릅니다

 

$$y=\dfrac{1}{1+e^{-\left( wx+b\right) }},t_{i}=0or1$$

 

$$\begin{aligned}l \left( W,b\right) =-\sum ^{n}_{i=1}\{ t_{i}\log y_{i}+\left( 1-t_{i}\right) \ log\left( 1-y_{i}\right) \} \end{aligned}$$

 

$$W=W-\alpha \dfrac{\partial l(W,b)}{\partial W},b=b-\alpha \dfrac{\partial l( W,b) }{\partial b}$$

 

여기서 알파 값은 학습률을 뜻합니다.

 

이 손실함수의 유도를 알아보겠습니다.

분류의 최종 출력값 y 는 sigmoid 함수에 의해 0~1 사이의 값을 가지는 확률적인 분류 모델이므로 ,

다음과 같이 확률변수 C 를 이용해 출력값을 나타낼 수 있습니다

 

p(c=1|x) = y = sigmoid(Wx+b)이고, (c=0|x)=1-p(c=1|x)=1-y라고 가정하겠습니다.

여기서 p(c=1|x)는 x가 1로 분리될 확률을 의미하고 given이라 부릅니다.

아래 식은 손실함수의 유도 식입니다.

 

$$p( c= t|x) =y^{t}\left( 1-y\right) ^{1-t}$$

$$\begin{aligned}L\left( W,b\right) =\prod ^{n}_{i=1}p\left( c=t_{i}| x\right) =\ \prod ^{n}_{i=1}yt_i\left( 1-y_{i}\right) ^{1-t_{i}}\end{aligned}$$

$$\begin{aligned}-\ln L\left( W,b\right) =-\Sigma _{i=1}^{n}\{ t_{i}\log y_{i}+\left( 1-t_{i}\right) \ \log \left( 1-y_{i}\right) \} \end{aligned}$$

 

4. 로지스틱 회귀 파이썬 구현

 

import numpy as np

# 학습데이터 준비
x_data = np.array([2,4,6,8,10,12,14,16,18,20]).reshape(10,1)
t_data = np.array([0,0,0,0,0,0,1,1,1,1]).reshape(10,1)

 

학습 데이터 입니다.

reshape를 사용해 10행 1열의 행렬로 변경합니다.

 

# 임의의 직선 z= Wx+b 정의
W = np.random.rand(1,1)
b = np.random.rand(1)

print(x_data.shape,t_data.shape)
print(W,W.shape,b,b.shape)

 

임의의 직선인 z = Wx+b를 정의합니다.

W와 b는 랜덤 값을 가지며 W는 1행1열의 행렬, b는 스칼라입니다.

 

# 크로스 엔트로피 정의
def sigmoid(z):
    return 1/(1+np.exp(-z))

def loss_func(x,t):
    delta = 1e-7 # log 무한대 발산 방지 log0이 되면 안되기 때문임
    z = np.dot(x,W)+b
    y = sigmoid(z)

    return -np.sum(t*np.log(y+delta)+(1-t)*np.log((1-y)+delta))

 

크로스 엔트로피를 정의하고 손실률 함수를 정의합니다.

임의의 직선 z를 입력받으면 1/(1+e^-z)를 리턴합니다.

손실률 함수는 log0가 되는것을 방지하기 위해 delta 변수를 생성하고 

z는 W와x의 행렬 곱을 한것과 스칼라 b를 더한 값입니다.

y는 크로스 엔트로피 함수에 z를 넣은 값입니다.

 

# 수치미분 numerical_derivative 및 utility 함수 정의 
def numerical_derivative(f,x): # f는 미분하고자 하는 다변수 함수, x는 모든 변수를 포함하고 있는 numpy 객체(배열, 행렬)등
    delta_x = 1e-5 #lim에 해당되는 작은 값
    grad = np.zeros_like(x) # 계산된 수치미분 값 저장 변수

    it = np.nditer(x, flags =['multi_index'],op_flags=['readwrite'])# 모든 입력변수에 대해 편미분하기 위해 사용
    while not it.finished:
        idx = it.multi_index  # x에대한 편미분 후 y에 대한 편미분 실행  [1.0,2.0]이라면 1.0 편미분 후 2.0 편미분
        
        tmp_val = x[idx] # numpy 타입은 mutable이므로 원래 값 보관
        x[idx] = float(tmp_val)+delta_x #하나의 변수에 대해 수치미분 계산
        fx1 = f(x) # f(x+delta_x) 전체에 대해 계산해야 하기 때문에 x[idx]가 아닌 x를 넣어줌

        x[idx] = tmp_val - delta_x
        fx2=f(x) # f(x-delta_x)
        grad[idx] = (fx1-fx2)/(2*delta_x)

        x[idx] = tmp_val
        it.iternext()

    return grad

def loss_val(x,t):
    delta = 1e-7 # log 무한대 발산 방지 log0이 되면 안되기 때문임
    z = np.dot(x,W)+b
    y = sigmoid(z)

    return -np.sum(t*np.log(y+delta)+(1-t)*np.log((1-y)+delta))

def predict(test_data):
    z = np.dot(test_data,W)+b
    y = sigmoid(z)

    if y>=0.5:
        result = 1
    else:
        result = 0
        
    return y,result

 

수치미분에 대한 함수를 불러오고

값이 바뀔 수 있으므로 다시 손실 함수를 재정의합니다.

다음 예측 함수를 정의합니다.

만약 예측률이 0.5이상이면 1을 반환하고

0.5 미만이면 0을 반환합니다.

 

# 학습률 초기화 및 손실함수가 최소가 될 때까지 W,b 업데이트
learning_rate = 1e-2 

f = lambda x: loss_func(x_data,t_data) # loss_func에 x_data,t_data가 들어감
print("Initial loss value = ",loss_val(x_data,t_data))

for step in range(50001):
    W-= learning_rate * numerical_derivative(f,W) # w에 대한 수치미분
    b-= learning_rate * numerical_derivative(f,b) # b에 대한 수치미분

    if(step % 5000==0):
        print("step = ",step,"loss value = ",loss_val(x_data,t_data))

 

학습률을 변수로 설정하고

f변수에 lambda를 이용해 loss_func함수에 데이터를 전달합니다.

 

반복문에서 W값과 b값을 구한 후 step에 따른 손실률을 출력합니다.

step이 반복될 수록 손실률이 떨어지는 모습을 볼 수 있습니다.

 

# 3과 17에 대한 미래 값 예측
test_data = np.array([3.0])
(real_val_1,logical_val_1) = predict(test_data)
print(real_val_1,logical_val_1)

test_data = np.array([17.0])
(real_val_1,logical_val_1) = predict(test_data)
print(real_val_1,logical_val_1)

 

3과 17에 대한 예측 값입니다.

0과 1로 잘 예측한 모습을 볼 수 있습니다.

1.416...e-09는 지수표현식으로 0의 근사치입니다.

728x90
반응형