Loading [MathJax]/jax/element/mml/optable/MathOperators.js

로지스틱 회귀(Logistic Regression)

시그모이드 함수(sigmoid function)

  • 퍼셉트론 모델에서 뉴런이 발화하는지(1) 발화하지 않는지(0)는 계단함수를 가지고 판별하여 이진 분류(binary classification)를 할 수 있다
  • 일상적인 문제에서는 이진분류로만 해결할 수 없는 일들이 많다. 예를 들어 01이 아닌 어중간한 것들을 판별해야하는 일들이 많다
  •  이런 경우에는 01 사이의 값에 대응시켜야 하는데, 이런 경우 확률로 대응시킬 수 있어 몇 %의 확률로 예측이 가능하다고 할 수 있다
  • 확률을 출력하기 위해서서는 01 사이의 실수에 대응시켜줄 수 있는 함수가 필요한데 이런 성질을 만족하는 함수 중의 하나가 식 (4.1)이며 시그모이드 함수(sigmoid function) 또는 로지스틱 함수(logistic function)이라고 한다

(4.1)σ(x)=11+ex


  • 계단함수와 시그모이드 함수의 비교


시그모이드 함수와 확률 밀도 함수, 누적 분포 함수와의 관계

  • 확률 밀도 함수(PDF; Probability Density Function) : 확률 변수의 분포를 나타내는 함수로 확률 변수 Xa 이상 b 이하가 될 확률을 식 (4.2)와 같이 표현할 때 f(x)를 확률밀도함수라고 한다

(4.2)Pr(aXb)=abf(x)dx

  • f(x)는 확률이기 때문에 다음 두 조건 식 (4.3)과 식 (4.4)를 만족해야 한다
    1. 모든 실수 x에 대하여(4.3)f(x)0
    2. 실수 범위를 가지는 확률 변수 X에 대하여(4.4)Pr(X)=f(x)dx=1
  • 누적 분포 함수(CDF; Cumulative Distribution Function) : 어떤 확률 분포에서 확률 변수가 특정값보다 작거나 같은 확률로 실수 범위를 가지는 확률 변수 X의 누적 분포 함수를 F(x)=P(Xx)이라고 할 때, 식 (4.5)와 같이 표현된다

(4.5)F(x)=xf(t)dt

  • 식 (4.2)와 식 (4.5)로부터 확률 밀도 함수와 누적 분포 함수 사이에는 식 (4.6)과 같은 관계가 있는 것을 알 수 있다

(4.5)ddxF(x)=F(x)=f(x)


정규분포의 확률 밀도 함수와 누적 분포 함수

  • 확률을 표현하는 함수로는 확률 밀도 함수와 누적 분포 함수가 있는데, 누적 분포 함수는 값이 0부터 1까지의 범위에 있으므로 함수값이 확률을 나타내는 함수로 적절
  • 그러나 특정 값만 취하는 함수를 사용한다면 확률 변수 x의 분포에 편향이 생길 수 있기 때문에 확률 변수의 분포가 가장 일반적인 정규분포를 따르는 함수를 생각해보자
  • 평균이 μ이고 표준편차가 σ인 정규분포의 확률 밀도 함수는 식 (4.6)와 같으며, 그림과 같이 정규분포의 누적 분포 함수의 값도 0에서 1 사이에 있다

(4.6)f(x)=12πσe(xμ)22σ2

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-10, 11, 100000)

fig = plt.figure()
ax1 = fig.add_subplot(1, 2, 1)

def pdf(x, mu=0, sigma=1):
    return (np.exp(-(x-mu)**2/2/(sigma**2))) / (np.sqrt(2*math.pi)*sigma)

for sigma in range(1, 4):
    ax1.plot(x, pdf(x, sigma=sigma), label=f'$\sigma$={sigma}')

ax1.legend()
ax1.grid(linestyle=':')
ax1.set_title("PDF of Normal Distribution")
ax1.set_xticks([0])

ax2 = fig.add_subplot(1, 2, 2)

from scipy.special import erf

def cdf(x, mu=0, sigma=1):
    return (1 + erf((x - mu) / np.sqrt(2) / sigma)) / 2

for sigma in range(1, 4):
    ax2.plot(x,cdf(x, sigma=sigma),label=f'$\sigma$={sigma}')

ax2.legend(loc=5)
ax2.grid(linestyle=':')
ax2.set_title('CDF of Normal Distribution')
ax2.set_xticks([0])
ax2.set_yticks([0, 1])
plt.show()


  • 로지스틱 회귀는 활성화 함수로 시그모이드 함수를 사용하는데, 시그모이드 함수 대신 정규분포의 누적 분포 함수를 사용하는 모델을 프로빗 회귀(probit regression)이라고 한다
    • 정규분포의 확률 누적 함수가 확률을 출력하는 함수로 시그모이드 함수보다 더 적절하지만 신경망 모델에서는 거의 사용되지 않음


정규분포의 확률 누적 분포 함수가 사용되지 않는 이유

  • 표준정규분포의 누적 분포 함수 p(x)를 이용해 뉴런을 모델화하는 것을 생각해보면 가설 모델은 식 (4.7)과 같이 표현할 수 있다

H(x)=Pr(wx+b)(4.7)=wx+b12πe(wt+b)22dt

  • 학습을 위해서는 실제의 값과 뉴런 모델의 출력값의 차가 최소가 되도록 웨이트와 바이어스를 조절해야하는데, 식 (4.7)를 사용하는 경우에는 이를 계산하는 것이 쉬운 일이 아니다
  • 오차가 최소가 되도록 매개변수의 값을 업데이트하는 것이 쉬워야하기 때문에 다소 오차는 있지만 누적 분포 함수와 모양의 거의 흡사하면서도 계산은 매우 쉬운 시그모이드 함수를 사용하는 것이 편리하다. 아래 그림은 σ=1σ=2일 때의 누적 분포 함수와 시그모이드 함수의 그래프를 비교한 것이다
    • 모든 일에서 이론도 매우 중요하지만 '현실적으로 계산이 가능한가?'라는 생각을 가지고 공학적으로 접근한 것도 중요하다

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erf

x = np.linspace(-10, 11, 100000)

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

def cdf(x, mu=0, sigma=1):
    return (1 + erf((x - mu) / np.sqrt(2) / sigma)) / 2

plt.plot(x, sigmoid(x), label='sigmoid function')
plt.plot(x, cdf(x, sigma=1), label='CDF where $\sigma$=1')
plt.plot(x, cdf(x, sigma=2), label='CDF where $\sigma$=2')
plt.grid(linestyle=":")
plt.xticks([0])
plt.yticks([0, 1])
plt.legend()
plt.show()


로지스틱 회귀 모델화

  • 로지스틱 회귀는 단순 퍼셉트론과는 달리 01 사이의 값을 갖는 확률적인 분류 모델이기 때문에 단순 신경망 모델과는 다른 모델링 방법을 사용해야 한다
  • 어떤 입력 x에 대하여 뉴런이 발화할지 여부를 나타내는 확률변수를 C라고 하면 C는 뉴런이 발화할 경우 C=1이 되고 발화하지 않을 경우 C=0이 되는 확률변수이기 때문에 단순 신경망 모델식을 고려해보면 뉴런이 발화할 확률은 식 (4.8)과 같다

(4.8)Pr(C=1|x)=σ(wx+b)

  • 뉴런이 발화하는 않을 확률은 식 (4.9)와 같다
    • 뉴런이 발화하거나 발화하지 않은 확률은 둘 중의 하나이기 때문에 1이다

(4.9)Pr(C=0|x)=1Pr(C=1|x)

  • C1(발화)이거나 0(발화하지 않음)의 두 가지 값만 갖기 때문에 가설 모델을 H(x)=σ(wx+b)라고 하면, y{0,1}일 때 식 (4.8)과 식 (4.9)는 식 (4.10)으로 표현할 수 있다

(4.10)Pr(C=y|x)=(H(x))y(1H(x))1y

    • y=0인 경우, 

Pr(C=0|x)=(H(x))0(1H(x))10=1(1H(x))1=1H(x)=1σ(wx+b)=1Pr(C=1|x)

    • y=1인 경우,

Pr(C=1|x)=(H(x))1(1H(x))11=y(1H(x))0=y1=H(x)=σ(wx+b)=Pr(C=1|x)

  • N개의 입력 데이터 xn(n=1,2,,N)과 이 입력 데이터와 쌍을 이루는 출력 데이터 yn이 주어졌을 때 신경망의 매개변수인 w와 바이어스 b의 가장 좋은 값을 찾기 위하여 가능도 또는 우도 함수(likelihood function)는 표준정규분포의 누적 분포 함수 대신에 시그모이드 함수를 사용한 가설식 (4.11)를 사용해 식 (4.12)과 같이 나타낼 수 있다

 (4.11)H(x)=σ(wx+b)

    • 각각의 xn에 대하여 신경망 모델의 출력값이 yn이 될 확률 Pr(C=yn|xn)이 높아야 좋은 모델이다

L(w,b)=n=1NPr(C=yn|xn)(4.12)=n=1N(Hn(x))yn(1Hn(x))1yn

  • 식 (4.12)의 가능도 함수 또는 우도 함수의 값이 최대가 되도록 매개변수를 업데이트하면 신경망 모델이 학습을 잘한 것이라고 볼 수 있다
    • 함수의 최대나 최소가 되는 상태를 구하는 문제를 최적화 문제(optimization problem)라고 한다
    • 함수의 최대화는 부호를 바꾸면 최소화가 되기 때문에 일반적으로 함수를 최적화한다고 하는 것은 함수를 최소로 만드는 매개변수를 구하는 것을 의미한다


함수 최적화와 미분 관계

  • 함수 최적화 문제는 함수를 최대나 최소로 만드는 매개변수를 구하는 것이기 때문에 함수의 최대와 최소는 미분(differentiation)과 관계가 있기 때문에 우리는 식 (4.12)를 매개변수들로 편미분(partial differentiation)하는 문제를 생각해야 한다
  • 식 (4.12)는 함수가 곱의 형태로 되어 있어 편미분을 계산하는 것이 쉽지 않기 때문에 편미분 계산이 쉽도록 식 (4.11)에 로그를 취해 덧셈 형태로 변환하고 최소가 되는 최적화 문제가 되도록 음수의 형태로 변환하면 식 (4.13)이 된다

E(w,b)=logL(w,b)=logn=1N(Hn(x))yn(1Hn(x))1yn(4.13)=n=1N(ynlogHn(x)+(1yn)log(1Hn(x))

  • 식 (4.13)과 같은 형태의 함수를 교차 엔트로피 오차 함수(cross-entropy error fuction 또는 crosss-entropy cost function)이라고 하며, 이 함수를 최소화하는 것이 식 (4.12) 가능도 또는 우도 함수를 최대화하는 것이기 때문에 식 (4.12)는 최적의 상태에서 오차가 어느 정도 있는지를 알 수 있는 식이라고 할 수 있기 때문에 식 (4.13)를 오차 함수(cost function) 또는 손실 함수(loss function)이라고 한다


경사하강법

  • 식 (4.13) 오차 함수에서 매개변수는 wb이기  때문에 wb로 편미분해서 0이 되는 값이 오차 함수가 최소가 되기 때문에 이 식을 직접 해석적으로 식을 풀어 해를 구하는 것은 쉬운 문제가 아니다
  • 해석적으로 식을 푸는 방법보다는 단순 신경망 모델에서 처럼 오차를 계산하여 줄여나가는 방법으로 매개변수를 갱신하는 방법을 사용해야 하는데 매개변수를 업데이트하며 오차를 최소하는 방법 중 대표적인 것이 경사하강법(GDA; Gradient Descent Algorithm)이다
  • 가중치 w와 바이어스 b는 식 (3.9)와 식 (3.10)과 같은 방법으로 업데이트된다

(3.9)wk+1=wk+Δwk

(3.10)bk+1=bk+Δbk

  • 경사하강법에서 가중치와 바이어스는 식 (4.14)와 식 (4.15)와 같은 방법으로 계산
    • α(>0)는 학습률(learning rate)이라는 하이퍼파라미터(hyperparameter)로 알고리즘 내부 설정값인데 이는 가설 모델의 매개변수가 업데이트 또는 최소로 수렴하는 정도를 조절하는 매개변수
    • 보통 0.1이나 0.01과 같은 적당히 작은 값을 사용

(4.14)wk+1=wkαE(w,b)w

(4.15)bk+1=bkαE(w,b)b

  • 가중치 w에 대한 기울기를 편미분을 통해 구하기 위해 n번째 오차 함수 En을 식 (4.16)로 정의한다

(4.16)En=(ynlogHn(x)+(1yn)log(1Hn(x))

  • 가중치 w에 대한 기울기는 식 (4.17)과 같이 구할 수 있다

E(w,b)w=n=1NEnHn(x)Hn(x)w=n=1N(ynHn(x)1yn1Hn(x))Hn(x)w(H(x)=σ(wx+b)(4.11))=n=1N(ynHn(x)1yn1Hn(x))Hn(x)(1Hn(x))xn((4.18))=n=1N(yn(1Hn(x))Hn(x)(1yn))xn(4.17)=n=1N(ynHn(x))xn

ddxσ(x)=ddx(1+ex)1=(1)(1+e1)11ddx(1+ex)=1(1+ex)2(ddx1+ddxex)=1(1+ex)2(0+exddx(x))=1(1+ex)2ex(1)=ex(1+e2)2=1+ex1(1+e2)2=1+ex(1+ex)21(1+ex)2=11+ex1(1+ex)2=11+ex(111+ex)(4.18)=σ(x)(1σ(x))

  • 바이어스 b에 대한 기울기는 식 (4.19)과 같이 구할 수 있다

E(w,b)b=n=1NEnHn(x)Hn(x)b=n=1N(ynHn(x)1yn1Hn(x))Hn(x)b(H(x)=σ(wx+b)(4.11))=n=1N(ynHn(x)1yn1Hn(x))Hn(x)(1Hn(x))((4.18))=n=1N(yn(1Hn(x))Hn(x)(1yn))(4.19)=n=1N(ynHn(x))


  • 경사하강법을 통해 우리가 구하고자 하는 가중치 식 (4.14)와 바이어스 식 (4.15)는 각각 식 (4.20) 및 식 (4.21)과 같이 구할 수 있다

wk+1=wkαE(w,b)w(4.20)=wk+αn=1N(ynHn(x))xn

bk+1=bkαE(w,b)b(4.21)=bk+αn=1N(ynHn(x))


확률적 경사하강법과 미니배치 경사하강법

  • 식 (4.20)와 식 (4.21)의 경사하강법을 통해 로지스틱 회귀 문제를 해결할 수 있지만 실제 구현에 있어서는 한번 학습을 할 때마다 학습 데이터의 개수만큼 N번의 합을 구해야만 하는 문제가 발생하는데, N이 작으면 큰 문제가 없지만 N이 큰 경우에는 구현상에 문제가 발생
    • 메모리에 적재할 때 용량의 문제로 메모리에 다 올려놓지 못하는 경우가 발생하는 문제
    • 계산 시간이 증가하는 문제
  • 이 문제를 해결하기 위한 대책으로 나오는 것이 확률적 경사하강법(SGD; Stochastic Gradient Descent Algorithm)
    • 경사하강법 : 모든 입력데이터의 합을 구한 후 매개변수 업데이트

wk+1=wk+αn=1N(ynHn(x))xnbk+1=bk+αn=1N(ynHn(x))

    • 확률적 경사하강법 : N개의 데이터 중에서 무작위로 1개를 선택해 계산 후 매개변수 업데이트

(4.22)wk+1=wk+α(ynHn(x))xn(4.23)bk+1=bk+α(ynHn(x))

      • 식 (4.22)와 식 (4.23)을 N개의 데이터에 대해 계산
      • 장점 : 경사하강법 1번의 계산량으로 확률적 경사하강법을 N번 계산할 수 있기 때문에 효율적으로 최적의 해를 찾을 수 있음
      • 단점 : 경사가 0으로 수렴하는 일이 거의 없기 때문에 최소해를 찾기가 힘들 뿐만 아니라 N개의 데이터 전체에 대하여 반복해서 학습을 해야함
        • 에포크(epoch) : N개의 데이터 전체에 대한 반복 학습 횟수
        • 각 에포크마다 데이터를 무작위로 섞어서(shuffle) 학습을 하기 때문에 학습을 할 때 편향되지 않은 최적의 해를 찾기 쉬워짐
for epoch in range(epochs):
    shuffle(data)       # 에포크마다 데이터를 섞는다
    for datum in data:  # 데이터를 1개씩 사용해 매개변수를 업데이트한다
        parameters_gradient = evaluate_gradient(cost_function, parameters, datum)
        parameters -= learning_rate * parameters_gradient 


    • 미니배치 경사하강법(Minibatch Gradient Descent Algorithm) : 경사하강법과 확률적 경사하강법의 중간에 위치한 방법으로 N개의 데이터를 M(개씩으로 나눠서(미니배치) 학습하는 방법이며, M는 대략 50 \leqq M \leqq 500정도의 값을 사용하며, 이와 대비하여 일반 경사하강법을 배치 경사하강법이라고도 한다
      • 미니배치 경사하강법을 사용하면 메모리의 부족함없이 선형대수를 사용하여 연산할 수 있기 때문에 1개씩 반복 계산하는 것보다 빠르게 연산이 가능
      • 일반적으로 '확률적 경사하강법'이라고 하면 '미니배치 경사하강법'을 말하는데, M=1인 경우가 정확한 확률적 경사하강법에 해당함
for epoch in range(epochs):
    shuffle(data)           # 에포크마다 데이터를 섞는다
    batches = get_batches(data, batch_size=M)
    for batch in batches:   # 각 미니배치마다 매개변수를 업데이트한다
        parameters_gradient = evaluate_gradient(cost_function, parameters, datum)
        parameters -= learning_rate * parameters_gradient 


OR 게이트 로지스틱 회귀를 TensorFlow로 구현

  • 파이썬 코드
import numpy as np
import tensorflow as tf

X = np.array([[0, 0],
              [0, 1],
              [1, 0],
              [1, 1]])

Y = np.array([[0],
              [1],
              [1],
              [1]])

W = tf.Variable(tf.zeros([2, 1]))
b = tf.Variable(tf.zeros([1]))

x = tf.placeholder(tf.float32, shape=[None, 2])
y = tf.placeholder(tf.float32, shape=[None, 1])

hypothesis = tf.nn.sigmoid(tf.matmul(x, W) + b)
cost = -tf.reduce_sum(y * tf.log(hypothesis) + (1-y)*tf.log(1-hypothesis))
train = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)

correct_predict = tf.equal(tf.to_float(tf.greater(hypothesis, 0.5)), y)

with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)

    for epoch in range(200):
        sess.run(train, feed_dict={x: X, y: Y})

    classified = correct_predict.eval(session=sess, feed_dict={x: X, y: Y}) # OR 게이트 예측값
    print(f'OR 게이트 예측 결과: \n{classified}')

    prob = hypothesis.eval(session=sess, feed_dict={x: X, y: Y})            # OR 게이트의 출력 확률
    print(f'OR 게이트 출력 확률: \n{prob})
  • 실행 결과
OR 게이트 예측 결과 :
[[ True]
 [ True]
 [ True]
 [ True]]

OR 게이트 출력 확률 :
[[0.22355038]
 [0.9142595 ]
 [0.9142595 ]
 [0.99747425]]


OR 게이트 로지스틱 회귀를 Keras로 구현

  • 파이썬 코드
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD

# 가설식 모델 정의 : 입력(input_dim) 2개, 출력(units) 1개
# 발화를 결정하는 활성화 함수는 누적 분포 함수가 아닌 sigmoid를 사용
model = Sequential()
model.add(Dense(input_dim=2, units=1))
model.add(Activation('sigmoid'))

# 가설식의 오차 함수는 cross-entropy 함수를 사용
model.compile(loss='binary_crossentropy', optimizer=SGD(learning_rate=0.1))

X = np.array([[0, 0],
              [0, 1],
              [1, 0],
              [1, 1]])

Y = np.array([[0],
              [1],
              [1],
              [1]])

# 가설식 모델 학습시키기
model.fit(X, Y, epochs=200, batch_size=1)

# 학습 결과 확인하기
classes = model.predict_classes(X, batch_size=1)
prob = model.predict_proba(X, batch_size=1)

print(f'\nOR 게이트 예측 결과 :\n{Y==classes})')
print()
print(f'OR 게이트 출력값 확률:\n{prob}')
  • 실행 결과
1/4 [======>.......................] - ETA: 0s - loss: 0.2746
4/4 [==============================] - 0s 500us/step - loss: 0.1184
Epoch 197/200

1/4 [======>.......................] - ETA: 0s - loss: 0.0911
4/4 [==============================] - 0s 751us/step - loss: 0.1179
Epoch 198/200

1/4 [======>.......................] - ETA: 0s - loss: 0.0031
4/4 [==============================] - 0s 501us/step - loss: 0.1174
Epoch 199/200

1/4 [======>.......................] - ETA: 0s - loss: 0.0905
4/4 [==============================] - 0s 750us/step - loss: 0.1169
Epoch 200/200

1/4 [======>.......................] - ETA: 0s - loss: 0.2696
4/4 [==============================] - 0s 750us/step - loss: 0.1163

OR 게이트 예측 결과 :
[[ True]
 [ True]
 [ True]
 [ True]]

OR 게이트 출력값 확률:
[[0.23539683]
 [0.9141111 ]
 [0.906056  ]
 [0.99700963]]



출처 : 정석으로 배우는 딥러닝

'신경망(Neural Network) 스터디' 카테고리의 다른 글

6. 다층 퍼셉트론 모델링  (0) 2017.12.28
5. 다중 클래스 로지스틱 회귀  (0) 2017.12.27
3. 단순 신경망 모델의 확장  (0) 2017.12.22
2. 논리 회로  (0) 2017.12.22
1. 신경망의 단순 모델  (0) 2017.12.21

+ Recent posts