로지스틱 회귀(Logistic Regression)
시그모이드 함수(sigmoid function)
- 퍼셉트론 모델에서 뉴런이 발화하는지(1) 발화하지 않는지(0)는 계단함수를 가지고 판별하여 이진 분류(binary classification)를 할 수 있다
- 일상적인 문제에서는 이진분류로만 해결할 수 없는 일들이 많다. 예를 들어 0과 1이 아닌 어중간한 것들을 판별해야하는 일들이 많다
- 이런 경우에는 0과 1 사이의 값에 대응시켜야 하는데, 이런 경우 확률로 대응시킬 수 있어 몇 %의 확률로 예측이 가능하다고 할 수 있다
- 확률을 출력하기 위해서서는 0과 1 사이의 실수에 대응시켜줄 수 있는 함수가 필요한데 이런 성질을 만족하는 함수 중의 하나가 식 (4.1)이며 시그모이드 함수(sigmoid function) 또는 로지스틱 함수(logistic function)이라고 한다
- 계단함수와 시그모이드 함수의 비교
시그모이드 함수와 확률 밀도 함수, 누적 분포 함수와의 관계
- 확률 밀도 함수(PDF; Probability Density Function) : 확률 변수의 분포를 나타내는 함수로 확률 변수 X가 a 이상 b 이하가 될 확률을 식 (4.2)와 같이 표현할 때 f(x)를 확률밀도함수라고 한다
- f(x)는 확률이기 때문에 다음 두 조건 식 (4.3)과 식 (4.4)를 만족해야 한다
- 모든 실수 x에 대하여
- 실수 범위를 가지는 확률 변수 X에 대하여
- 누적 분포 함수(CDF; Cumulative Distribution Function) : 어떤 확률 분포에서 확률 변수가 특정값보다 작거나 같은 확률로 실수 범위를 가지는 확률 변수 X의 누적 분포 함수를 F(x)=P(X⩽x)이라고 할 때, 식 (4.5)와 같이 표현된다
- 식 (4.2)와 식 (4.5)로부터 확률 밀도 함수와 누적 분포 함수 사이에는 식 (4.6)과 같은 관계가 있는 것을 알 수 있다
정규분포의 확률 밀도 함수와 누적 분포 함수
- 확률을 표현하는 함수로는 확률 밀도 함수와 누적 분포 함수가 있는데, 누적 분포 함수는 값이 0부터 1까지의 범위에 있으므로 함수값이 확률을 나타내는 함수로 적절
- 그러나 특정 값만 취하는 함수를 사용한다면 확률 변수 x의 분포에 편향이 생길 수 있기 때문에 확률 변수의 분포가 가장 일반적인 정규분포를 따르는 함수를 생각해보자
- 평균이 μ이고 표준편차가 σ인 정규분포의 확률 밀도 함수는 식 (4.6)와 같으며, 그림과 같이 정규분포의 누적 분포 함수의 값도 0에서 1 사이에 있다
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)과 같이 표현할 수 있다
- 학습을 위해서는 실제의 값과 뉴런 모델의 출력값의 차가 최소가 되도록 웨이트와 바이어스를 조절해야하는데, 식 (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()
로지스틱 회귀 모델화
- 로지스틱 회귀는 단순 퍼셉트론과는 달리 0과 1 사이의 값을 갖는 확률적인 분류 모델이기 때문에 단순 신경망 모델과는 다른 모델링 방법을 사용해야 한다
- 어떤 입력 x에 대하여 뉴런이 발화할지 여부를 나타내는 확률변수를 C라고 하면 C는 뉴런이 발화할 경우 C=1이 되고 발화하지 않을 경우 C=0이 되는 확률변수이기 때문에 단순 신경망 모델식을 고려해보면 뉴런이 발화할 확률은 식 (4.8)과 같다
- 뉴런이 발화하는 않을 확률은 식 (4.9)와 같다
- 뉴런이 발화하거나 발화하지 않은 확률은 둘 중의 하나이기 때문에 1이다
- C는 1(발화)이거나 0(발화하지 않음)의 두 가지 값만 갖기 때문에 가설 모델을 H(x)=σ(wx+b)라고 하면, y∈{0,1}일 때 식 (4.8)과 식 (4.9)는 식 (4.10)으로 표현할 수 있다
- y=0인 경우,
- y=1인 경우,
- N개의 입력 데이터 xn(n=1,2,…,N)과 이 입력 데이터와 쌍을 이루는 출력 데이터 yn이 주어졌을 때 신경망의 매개변수인 w와 바이어스 b의 가장 좋은 값을 찾기 위하여 가능도 또는 우도 함수(likelihood function)는 표준정규분포의 누적 분포 함수 대신에 시그모이드 함수를 사용한 가설식 (4.11)를 사용해 식 (4.12)과 같이 나타낼 수 있다
- 각각의 xn에 대하여 신경망 모델의 출력값이 yn이 될 확률 Pr(C=yn|xn)이 높아야 좋은 모델이다
- 식 (4.12)의 가능도 함수 또는 우도 함수의 값이 최대가 되도록 매개변수를 업데이트하면 신경망 모델이 학습을 잘한 것이라고 볼 수 있다
- 함수의 최대나 최소가 되는 상태를 구하는 문제를 최적화 문제(optimization problem)라고 한다
- 함수의 최대화는 부호를 바꾸면 최소화가 되기 때문에 일반적으로 함수를 최적화한다고 하는 것은 함수를 최소로 만드는 매개변수를 구하는 것을 의미한다
함수 최적화와 미분 관계
- 함수 최적화 문제는 함수를 최대나 최소로 만드는 매개변수를 구하는 것이기 때문에 함수의 최대와 최소는 미분(differentiation)과 관계가 있기 때문에 우리는 식 (4.12)를 매개변수들로 편미분(partial differentiation)하는 문제를 생각해야 한다
- 식 (4.12)는 함수가 곱의 형태로 되어 있어 편미분을 계산하는 것이 쉽지 않기 때문에 편미분 계산이 쉽도록 식 (4.11)에 로그를 취해 덧셈 형태로 변환하고 최소가 되는 최적화 문제가 되도록 음수의 형태로 변환하면 식 (4.13)이 된다
- 식 (4.13)과 같은 형태의 함수를 교차 엔트로피 오차 함수(cross-entropy error fuction 또는 crosss-entropy cost function)이라고 하며, 이 함수를 최소화하는 것이 식 (4.12) 가능도 또는 우도 함수를 최대화하는 것이기 때문에 식 (4.12)는 최적의 상태에서 오차가 어느 정도 있는지를 알 수 있는 식이라고 할 수 있기 때문에 식 (4.13)를 오차 함수(cost function) 또는 손실 함수(loss function)이라고 한다
경사하강법
- 식 (4.13) 오차 함수에서 매개변수는 w와 b이기 때문에 w와 b로 편미분해서 0이 되는 값이 오차 함수가 최소가 되기 때문에 이 식을 직접 해석적으로 식을 풀어 해를 구하는 것은 쉬운 문제가 아니다
- 해석적으로 식을 푸는 방법보다는 단순 신경망 모델에서 처럼 오차를 계산하여 줄여나가는 방법으로 매개변수를 갱신하는 방법을 사용해야 하는데 매개변수를 업데이트하며 오차를 최소하는 방법 중 대표적인 것이 경사하강법(GDA; Gradient Descent Algorithm)이다
- 가중치 w와 바이어스 b는 식 (3.9)와 식 (3.10)과 같은 방법으로 업데이트된다
- 경사하강법에서 가중치와 바이어스는 식 (4.14)와 식 (4.15)와 같은 방법으로 계산
- α(>0)는 학습률(learning rate)이라는 하이퍼파라미터(hyperparameter)로 알고리즘 내부 설정값인데 이는 가설 모델의 매개변수가 업데이트 또는 최소로 수렴하는 정도를 조절하는 매개변수
- 보통 0.1이나 0.01과 같은 적당히 작은 값을 사용
- 가중치 w에 대한 기울기를 편미분을 통해 구하기 위해 n번째 오차 함수 En을 식 (4.16)로 정의한다
- 가중치 w에 대한 기울기는 식 (4.17)과 같이 구할 수 있다
- 바이어스 b에 대한 기울기는 식 (4.19)과 같이 구할 수 있다
- 경사하강법을 통해 우리가 구하고자 하는 가중치 식 (4.14)와 바이어스 식 (4.15)는 각각 식 (4.20) 및 식 (4.21)과 같이 구할 수 있다
확률적 경사하강법과 미니배치 경사하강법
- 식 (4.20)와 식 (4.21)의 경사하강법을 통해 로지스틱 회귀 문제를 해결할 수 있지만 실제 구현에 있어서는 한번 학습을 할 때마다 학습 데이터의 개수만큼 N번의 합을 구해야만 하는 문제가 발생하는데, N이 작으면 큰 문제가 없지만 N이 큰 경우에는 구현상에 문제가 발생
- 메모리에 적재할 때 용량의 문제로 메모리에 다 올려놓지 못하는 경우가 발생하는 문제
- 계산 시간이 증가하는 문제
- 이 문제를 해결하기 위한 대책으로 나오는 것이 확률적 경사하강법(SGD; Stochastic Gradient Descent Algorithm)
- 경사하강법 : 모든 입력데이터의 합을 구한 후 매개변수 업데이트
- 확률적 경사하강법 : N개의 데이터 중에서 무작위로 1개를 선택해 계산 후 매개변수 업데이트
- 식 (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 |