독학 연구소
공부한 내용을 정리합니다.
머신러닝/딥러닝 (17)
소프트맥스 회귀(Softmax Regression) (1)

 

2020/11/06 - [머신러닝/딥러닝] - 로지스틱 회귀(Logistic Regression)

 

스프트맥스 회귀(Softmax Regression)

3개 이상의 범주 혹은 클래스를 갖는 다중 클래스 분류(multi-class classification)를 위한 알고리즘입니다.

 

이진 분류 문제에서 로지스틱 회귀는 시그모이드 함수를 통과한 확률에 해당하는 출력값에 0.5와 같은 임계값(threshold)을 기준으로 분류합니다.

 

다중 분류 문제에서도 시그모이드 함수를 이용할 수 있을까요?

 

이진 분류의 경우 하나의 출력으로 두 클래스에 대한 확률을 모두 알 수 있습니다. ($A$ 일 확률 $p$, $B$ 일 확률 $1 - p$) 하지만 소프트맥스 회귀에서는 입력된 데이터에 대해 하나의 출력으로 바로 특정 클래스를 예측하는 것이 아니라 각 클래스에 대한 확률을 출력하여 가장 높은 확률을 갖는 클래스로 예측하는 것입니다. 따라서 출력의 합이 1이 되어야 하기 때문에 시그모이드 함수를 사용할 수 없는 것입니다.

 

 

소프트맥스 함수(Softmax Function)

소프트맥스 함수는 활성화 함수(activation function)의 한 종류로 출력의 합을 1로 만드는데 이것을 출력 강도를 정규화한다고 합니다.

 

3개의 출력에 대한 식으로 나타내면 다음과 같습니다.($e$ 는 자연 상수, $z$ 는 입력)

 

$z_1 \rightarrow {e^{z_1} \over e^{z_1}+e^{z_2}+e^{z_3}}$

 

$z_2 \rightarrow {e^{z_1} \over e^{z_1}+e^{z_2}+e^{z_3}}$

 

$z_3 \rightarrow {e^{z_1} \over e^{z_1}+e^{z_2}+e^{z_3}}$

 

$z_1+z_2+z_3=1$

 

import numpy as np

def softmax(array):
    r = []
    for z in sigmoid(array):
        r.append(np.exp(z) / sum(np.exp(sigmoid(array))))
    return r

a = np.array([0.3, 2.6, 4.1])

print(softmax(a))
print(sum(softmax(a)))
[0.25420056710998096, 0.36305075758862626, 0.38274867530139284]
1.0

 

 

원-핫 인코딩(One-Hot Encoding)

사용할 데이터는 붓꽃 데이터셋으로 타겟의 구성값을 확인합니다.

import numpy as np
from sklearn.datasets import load_iris

iris = load_iris()
data = iris.data
target = iris.target

np.unique(target, return_counts=1)
(array([0, 1, 2]), array([50, 50, 50], dtype=int64))

 

3개의 클래스를 가지며 값은 0,1,2  정수 인코딩되어있습니다. 신경망에 입력될 때는 정수 인코딩된 값을 원-핫 인코딩하여 각 클래스간의 관계를 균등하게 합니다.

 

넘파이에서는 단위 행렬을 생성하는 np.eye 를 이용할 수 있습니다. 

np.eye(3, 7)
array([[1., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0.]])

 

다음과 같이 사용하면 입력 데이터에 대해 원-핫 인코딩할 수 있습니다.

label_size = np.unique(target).size

np.eye(label_size)[target]
array([[1., 0., 0.],
       [1., 0., 0.],
       [1., 0., 0.],

...

       [0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.],


...

       [0., 0., 1.],
       [0., 0., 1.],
       [0., 0., 1.]])

 

텐서플로에서 제공하는 tf.keras.utils.to_categorical 을 이용하면 더욱 간단합니다.

import tensorflow as tf

tf.keras.utils.to_categorical(target)
array([[1., 0., 0.],
       [1., 0., 0.],
       [1., 0., 0.],

...

       [0., 1., 0.],
       [0., 1., 0.],
       [0., 1., 0.],


...

       [0., 0., 1.],
       [0., 0., 1.],
       [0., 0., 1.]])

 

또한 신경망 내부적으로 처리할 때는 텐서를 반환하는 tf.one_hot 을 이용할 수 있습니다.

import numpy as np
import tensorflow as tf

label_size = 5

x = tf.placeholder(tf.int32)
onehot = tf.one_hot(x, label_size)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    
    print(sess.run(onehot, {x: [1, 4, 0, 2, 3]}))
[[0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1.]
 [1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]]

 

 

손실 함수(Loss Function)

다중 클래스 분류 문제에서는 손실 함수로 크로스 엔트로피(cross-entropy)가 사용됩니다. 크로스 엔트로피는 정답 분포 $q$ 와 예측 분포 $p$ 사이의 평균 정보량을 의미합니다. 정보량은 해당 사건이 일어날 확률이 낮으면 증가하고 높으면 감소하게 되는데, 경사 하강법 최적화 알고리즘을 통해 그 정보량을 감소시켜 확률을 높이게 되는 것입니다.

 

크로스 엔트로피 손실 함수와 로지스틱 회귀에서의 손실 함수는 매우 유사합니다.

 

$L=-{1 \over n}\displaystyle \sum_{i=1}^n[y_i \text{log}(p_i)+(1-y_i) \text{log}(1-p_i)]$

 

이는 2개의 클래스를 갖는 이진 분류 문제에 대한 식으로 정답값이 0인 경우와 1인 경우를 고려한 것입니다. 다중 클래스 분류 문제에 대한 식도 이와 비슷합니다. 신경망에 입력되는 타겟 벡터는 원-핫 인코딩된 것으로 정답 클래스의 값은 1이고 나머지는 0입니다.

 

다음과 같이 클래스의 개수만큼 더해주어 나타낼 수 있습니다. ($k$는 클래스의 개수, $p$는 해당 클래스의 확률값, $y$는 원-핫 인코딩된 타겟 벡터)

 

$L=-\displaystyle \sum_{j=1}^ky_j \text{log}(p_j)$

 

최종적으로 n개의 데이터에 대한 식으로 나타내면 다음과 같습니다.

 

$L=-{1 \over n}\displaystyle \sum_{i=1}^n \sum_{j=1}^k y_{ij} \text{log}(p_{ij})$

 

텐서플로에서 제공하는 tf.nn.softmax_cross_entropy_logits_v2 를 이용하면 간단합니다.

import tensorflow as tf

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

output = tf.layers.dense(x, 3)

cross_entropy = -tf.reduce_sum(y * tf.log(tf.nn.softmax(output)), 1)
cross_entropy2 = tf.nn.softmax_cross_entropy_with_logits_v2(logits=output, labels=y)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    
    _cross_entropy, _cross_entropy2 = sess.run([cross_entropy, cross_entropy2], {x: [[0.1], [0.2]], y: [[1., 0., 0.], [0., 1., 0.]]})
    
    print(_cross_entropy)
    print(_cross_entropy2)
[1.0372838 1.0633986]
[1.0372838 1.0633986]

 

 

구현

소프트맥스 회귀 모델을 구현합니다.

 

붓꽃 데이터셋을 불러옵니다.

from sklearn.datasets import load_iris

iris = load_iris()
data = iris.data
target = iris.target
feature_names = iris.feature_names
target_names = iris.target_names

print('data shape:', data.shape)
print('data sample:', data[0])
print('feature names:', feature_names)

print('target shape:', target.shape)
print('target sample:', target[0], target[1], target[2])
print('target label:', np.unique(target, return_counts=True))
print('target names:', target_names)
data shape: (150, 4)
data sample: [5.1 3.5 1.4 0.2]
feature names: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
target shape: (150,)
target sample: 0 0 0
target label: (array([0, 1, 2]), array([50, 50, 50], dtype=int64))
target names: ['setosa' 'versicolor' 'virginica']

 

레이블은 3개의 클래스를 가지며 고르게 분포되어 있습니다.

target label: (array([0, 1, 2]), array([50, 50, 50], dtype=int64))

 

8:2 비율로 학습 데이터와 테스트 데이터로 분할합니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

 

신경망을 정의합니다.

import numpy as np
import tensorflow as tf

class Model:
    def __init__(self, lr=1e-2):
        tf.reset_default_graph()
        
        with tf.name_scope('input'):
            self.x = tf.placeholder(tf.float32, [None, 4])
            self.y = tf.placeholder(tf.int64)

        with tf.name_scope('y_onehot'):
            y_onehot = tf.one_hot(self.y, 3)
            
        with tf.name_scope('layer'):
            fc = tf.layers.dense(self.x, 32, tf.nn.relu)
            logits = tf.layers.dense(fc, 3)
            
        with tf.name_scope('output'):
            h = tf.nn.softmax(logits)
            self.predict = tf.argmax(h, 1)

        with tf.name_scope('accuracy'):
            self.accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.to_int64(self.predict), self.y), dtype=tf.float32))    
        
        with tf.name_scope('loss'):
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=y_onehot)
            self.loss = tf.reduce_mean(cross_entropy)
        
        with tf.name_scope('optimizer'):
            self.train_op = tf.train.GradientDescentOptimizer(lr).minimize(self.loss)

        with tf.name_scope('summary'):
            tf.summary.scalar('loss', self.loss)
            tf.summary.scalar('accuracy', self.accuracy)
            
            self.merge = tf.summary.merge_all()

        self.writer = tf.summary.FileWriter('./tmp/softmax-regression_iris', tf.get_default_graph())
        
        self.sess = tf.Session()
        
        self.sess.run(tf.global_variables_initializer())
    
    def train(self, x_train, y_train, epochs):
        for e in range(epochs):
            summary, loss, accuracy, _ = self.sess.run([self.merge, self.loss, self.accuracy, self.train_op], {self.x: x_train, self.y: y_train})
            self.writer.add_summary(summary, e)
            print('epoch:', e + 1, ' / loss:', loss, '/ accuracy:', accuracy)
    
    def score(self, x, y):
        return self.sess.run(self.accuracy, {self.x: x, self.y: y})

 

배치 경사 하강법으로 학습을 수행합니다.

def train(self, x_train, y_train, epochs):
    for e in range(epochs):
        summary, loss, accuracy, _ = self.sess.run([self.merge, self.loss, self.accuracy, self.train_op], {self.x: x_train, self.y: y_train})
        self.writer.add_summary(summary, e)
        print('epoch:', e + 1, ' / loss:', loss, '/ accuracy:', accuracy)

 

모델을 학습하고 테스트합니다.

model = Model()
model.train(x_train, y_train, epochs=500)
model.score(x_test, y_test)
...

epoch: 490  / loss: 0.25155848 / accuracy: 0.9583333
epoch: 491  / loss: 0.25125748 / accuracy: 0.9583333
epoch: 492  / loss: 0.2509577 / accuracy: 0.9583333
epoch: 493  / loss: 0.2506586 / accuracy: 0.9583333
epoch: 494  / loss: 0.25035974 / accuracy: 0.9583333
epoch: 495  / loss: 0.25006163 / accuracy: 0.9583333
epoch: 496  / loss: 0.24976416 / accuracy: 0.9583333
epoch: 497  / loss: 0.24946807 / accuracy: 0.9583333
epoch: 498  / loss: 0.24917193 / accuracy: 0.9583333
epoch: 499  / loss: 0.24887681 / accuracy: 0.9583333
epoch: 500  / loss: 0.24858236 / accuracy: 0.9583333

0.96666664

 

에포크에 대한 정확도와 손실 함수의 그래프는 다음과 같습니다.

 

 

 

  Comments,     Trackbacks
교차 검증(Cross Validation)

 

2020/11/06 - [머신러닝/딥러닝] - 로지스틱 회귀(Logistic Regression)

 

검증 데이터(Validation Data)

데이터는 학습 데이터와 테스트 나누어지는데, 학습 데이터로 학습을 하고 테스트 데이터를 이용해 범용 성능을 평가합니다. 여기서 문제는 과대적합(Overfitting)이 나타날 수 있다는 것인데, 이는 학습 데이터에만 적합한 모델이 될 수 있다는 것입니다. 따라서 학습 데이터의 일부를 검증 데이터로 분할하여 튜닝에 사용할 수 있습니다.

 

 

튜닝한다는 것은 모델의 하이퍼파라미터를 조정하는 것으로 성능 지표가 필요합니다. 이에 대해 테스트 데이터를 사용하여 튜닝하면 테스트 데이터에만 적합하도록 조정되어 과대적합이 발생하며 테스트 데이터의 정보가 알려져 범용 성능이 떨어지게 됩니다.

 

 

Holdout Cross Validation

학습 데이터의 일부를 고정적으로 분할하는 방식으로 전체 데이터가 많을 경우에 사용할 수 있는 방법입니다.

 

전체 데이터를 8:2 비율로 학습 데이터와 테스트 데이터를 분할하고 학습 데이터를 또다시 8:2 비율로 학습 데이터와 검증 데이터로 분할합니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.2)

 

주의해야할 점은 전체 데이터에서 6:2:2과 같이 한 번에 나누는게 아니라는 것입니다.

 

 

K-Fold Cross Validation

전체 데이터가 적을 경우에 사용할 수 있는 방법입니다.

 

k는 학습 데이터를 분할하는 수를 의미하며 분할된 데이터를 폴드(fold)라고 합니다. 모든 폴드는 1개의 검증 폴드와 나머지 학습 폴드로 구성되는데, 모든 폴드가 최소 1번의 검증 폴드가 되도록 반복합니다. 이 과정에서 검증 폴드를 이용하여 성능 평가를 수행하며, 이에 대한 평균을 지표로 이용하는 것입니다.

 

사이킷런에서 제공하는 KFold 를 이용할 수 있습니다.

from sklearn.model_selection import KFold

data = np.arange(20).reshape(10, 2)
print(data)

kf = KFold(n_splits=4)

for train_idx, val_idx in kf.split(data):
    print('\ntrain index:', train_idx)
    print('val index:', val_idx)
    print('val data')
    print(data[val_idx])
[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]
 [14 15]
 [16 17]
 [18 19]]

train index: [3 4 5 6 7 8 9]
val index: [0 1 2]
val data
[[0 1]
 [2 3]
 [4 5]]

train index: [0 1 2 6 7 8 9]
val index: [3 4 5]
val data
[[ 6  7]
 [ 8  9]
 [10 11]]

train index: [0 1 2 3 4 5 8 9]
val index: [6 7]
val data
[[12 13]
 [14 15]]

train index: [0 1 2 3 4 5 6 7]
val index: [8 9]
val data
[[16 17]
 [18 19]]

 

입력 데이터에 대해 k번 반복하여 학습 인덱스와 검증 인덱스를 반환합니다.

 

 

구현

K폴드 교차 검증을 하는 로지스틱 회귀 모델을 구현합니다.

 

유방암 데이터셋을 불러옵니다.

from sklearn.datasets import load_breast_cancer

breast_cancer = load_breast_cancer()

data = breast_cancer.data
target = breast_cancer.target
feature_names = breast_cancer.feature_names

print('data shape:', data.shape)
print('data sample:', data[0])
print('feature_names:', feature_names)

print('target shape:', target.shape)
print('target sample:', target[0], target[1], target[2])

 

타겟 형태를 변형합니다.

target = target.reshape(-1, 1)
target.shape

 

8:2 비율로 학습 데이터와 테스트 데이터를 분할합니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

 

테스트 데이터를 전처리합니다.

x_test = (x_test - x_train.mean(axis=0)) / x_train.std(axis=0)

 

신경망을 정의합니다.

import numpy as np
import tensorflow as tf
from sklearn.model_selection import KFold

class Model:
    def __init__(self, lr=1e-3):
        with tf.name_scope('input'):
            self.x = tf.placeholder(tf.float32, [None, 30])
            self.y = tf.placeholder(tf.float32, [None, 1])

        with tf.name_scope('layer'):
            fc = tf.layers.dense(self.x, 64, tf.nn.relu)
            fc2 = tf.layers.dense(fc, 64, tf.nn.relu)
            fc3 = tf.layers.dense(fc2, 1)
            
        with tf.name_scope('output'):
            self.output = tf.nn.sigmoid(fc3)

        with tf.name_scope('accuracy'):
            self.predict = tf.cast(tf.greater(self.output, tf.constant([0.5])), dtype=tf.float32)
            self.accuracy = tf.reduce_mean(tf.cast(tf.equal(self.y, self.predict), dtype=tf.float32))    
        
        with tf.name_scope('loss'):
            self.loss = -tf.reduce_mean(self.y * tf.log(self.output) + (1-self.y) * tf.log(1-self.output))

        with tf.name_scope('optimizer'):
            self.train_op = tf.train.GradientDescentOptimizer(lr).minimize(self.loss)
      
        self.sess = tf.Session()
        self.sess.run(tf.global_variables_initializer())
   
    def train(self, x_train, y_train, epochs=100):
        train_loss, train_acc = [], []
        for e in range(epochs):
            loss, acc, _ = self.sess.run([self.loss, self.accuracy, self.train_op], {self.x: x_train, self.y: y_train})
            train_loss.append(loss)
            train_acc.append(acc)
        print('train loss:', np.mean(train_loss), '/ train accuracy:', np.mean(train_acc))
    
    def score(self, x, y):
        return self.sess.run(self.accuracy, {self.x: x, self.y: y})

 

배치 경사 하강법으로 학습을 수행합니다.

def train(self, x_train, y_train, epochs=100):
    train_loss, train_acc = [], []
    for e in range(epochs):
        loss, acc, _ = self.sess.run([self.loss, self.accuracy, self.train_op], {self.x: x_train, self.y: y_train})
        train_loss.append(loss)
        train_acc.append(acc)
    print('train loss:', np.mean(train_loss), '/ train accuracy:', np.mean(train_acc))

 

K폴드 교차 검증을 위한 반복을 수행합니다.

model = Model()

k = 5
kf = KFold(n_splits=k, shuffle=True)

val_scores = []

for train_idx, val_idx in kf.split(x_train):
    train_fold, train_target = x_train[train_idx], y_train[train_idx]
    val_fold, val_target = x_train[val_idx], y_train[val_idx]
    
    train_mean = train_fold.mean()
    train_std = train_fold.std()
    
    train_fold = (train_fold - train_mean) / train_std
    val_fold = (val_fold - train_mean) / train_std
    
    model.train(train_fold, train_target, epochs=500)
    acc = model.score(val_fold, val_target)
    val_scores.append(acc)

print('\nk-fold validation accuracy:', np.mean(val_scores))

print('\ntest accuracy:', model.score(x_test, y_test))
train loss: 0.615141 / train accuracy: 0.6809341
train loss: 0.5062132 / train accuracy: 0.91860986
train loss: 0.43708414 / train accuracy: 0.91754395
train loss: 0.37843737 / train accuracy: 0.92094517
train loss: 0.33940402 / train accuracy: 0.9114397

k-fold validation accuracy: 0.9076923

test accuracy: 0.85087717

 

전체 학습 데이터를 5분할합니다.

k = 5
kf = KFold(n_splits=k, shuffle=True)

 

각 반복의 학습 폴드를 이용하여 검증 폴드를 전처리합니다.

_x_train, _y_train = x_train[train_idx], y_train[train_idx]
x_val, y_val = x_train[val_idx], y_train[val_idx]

train_mean = _x_train.mean()
train_std = _x_train.std()

_x_train = (_x_train - train_mean) / train_std
x_val = (x_val - train_mean) / train_std

 

각 반복의 검증 폴드를 이용한 성능 평가에 대한 평균을 구합니다.

val_scores = []

...

acc = model.score(x_val, y_val)
val_scores.append(acc)

...

print('\nk-fold validation accuracy:', np.mean(val_scores))

 

 

  Comments,     Trackbacks
로지스틱 회귀(Logistic Regression)

로지스틱 회귀(Logistic Regression)

로지스틱 회귀는 0 또는 1과 같은 범주를 갖는 이진 분류(binary classification) 문제를 위한 알고리즘으로 해당 범주에 대한 확률을 예측합니다. 

 

 

 

시그모이드 함수(Sigmoid Function)

시그모이드 함수는 출력으로 $0<y<1$ 의 범위를 갖는 활성화 함수(activation function)의 한 종류입니다. 

 

 

시그모이드 함수의 유도되는 과정은 다음과 같습니다.

 

오즈 비(odds ratio) > 로짓 함수(logit function) > 시그모이드 함수(sigmoid function)

 

 

오즈 비는 성공 확률 $p$ 과 실패 확률 $1-p$ 의 비율을 나타냅니다.

 

$OR = {p \over 1-p}$

 

import numpy as np
import matplotlib.pyplot as plt

def odds_ratio(x):
    return x / (1-x)

x = np.arange(0, 1, 0.01)
y = odds_ratio(x)

plt.plot(x, y)
plt.title('Odds ratio')
plt.xlabel('p')
plt.show()

 

$p$ 의 값은 1에 가까워지면 급격히 증가하는 특징을 가집니다.

 

 

로짓 함수는 오즈 비에 로그를 취한 함수입니다.

 

$logit(p)=\text{log}({p \over 1-p})$

 

import numpy as np
import matplotlib.pyplot as plt

def odds_ratio(x):
    return x / (1-x)

def logit(x):
    return np.log(odds_ratio(x))

x = np.arange(0.001, 1, 0.001)
y = logit(x)

plt.plot(x, y)
plt.title('Logit function')
plt.xlabel('p')
plt.show()

 

$p$ 가 0.5일 때 0이 되고 0과 1일 때 각각 음의 무한, 양의 무한이 되는 특징을 가집니다.

 

 

로짓 함수를 출력을 $z$ 로 놓고 $z$ 대하여 다시 정리하면 로지스틱 함수가 됩니다.

 

$\text{log}({p \over 1-p})=z$ 

 

$sigmoid(z)=\frac{1}{ 1+e^{-z} }$

 

import numpy as np
import matplotlib.pyplot as plt

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

x = np.arange(-10, 10, 0.1)
y = sigmoid(x)

plt.plot(x, y)
plt.title('Logistic function')
plt.xlabel('z')
plt.show()

 

로짓 함수를 반대로 뒤집어 놓은 모양으로 S자를 나타내어 로지스틱 함수 시그모이드 함수라고 합니다.

 

이러한 시그모이드 함수를 통해 출력된 값을 0.5와 같은 임계값을 기준으로 나누면 이진 분류할 수 있는 것입니다.

 

 

손실 함수

이진 분류 문제에서 손실 함수는 어떻게 정의할 수 있을까요?

 

이진 분류 문제에서 정답 데이터는 0과 1의 범주를 갖으며 신경망의 예측에 해당하는 시그모이드 함수의 출력은 0과 1 사이의 범위를 갖습니다. 이에 대해 로그 함수의 특징을 이용하여 손실 함수를 정의할 수 있습니다.

 

로그 함수는 입력이 0에 가까지면 출력의 절대값이 커지고, 입력이 1에 가까워지면 출력의 절대값이 작아집니다.

 

 

이 같은 특징을 이용하여 다음과 같이 나타낼 수 있습니다.

 

$\hat y = sigmoid(x)$

 

$\text{if } y=1 \rightarrow -\text{log}(\hat y)$

 

$\text{if } y=0 \rightarrow -\text{log}(1 - \hat y)$

 

정답 $ y $ 가 1인 경우에는 예측 $ \hat y $ 이 0에 가까워진다면 오차는 커지고 1에 가까워진다면 오차는 작아지게 됩니다. $ y $ 가 0인 경우에는 $ \hat y$ 가 0에 가까워진다면 오차는 작아지고 1에 가까워진다면 오차는 커지게 됩니다.

 

이를 통해 손실 함수를 정의하면 다음과 같습니다.

 

$L=-{1 \over n}\displaystyle \sum_{i=1}^n \Big[ y_i \text{log}(\hat y_i))+(1-y_i) \text{log}(1-\hat y_i) \Big]$

 

 

구현

로지스틱 회귀 모델을 구현합니다.

 

유방암 데이터셋을 불러옵니다.

from sklearn.datasets import load_breast_cancer

breast_cancer = load_breast_cancer()

data = breast_cancer.data
target = breast_cancer.target
feature_names = breast_cancer.feature_names

print('data shape:', data.shape)
print('data sample:', data[0])
print('feature_names:', feature_names)

print('target shape:', target.shape)
print('target sample:', target[0], target[1], target[2])
data shape: (569, 30)
data sample: [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]
feature_names: ['mean radius' 'mean texture' 'mean perimeter' 'mean area'
 'mean smoothness' 'mean compactness' 'mean concavity'
 'mean concave points' 'mean symmetry' 'mean fractal dimension'
 'radius error' 'texture error' 'perimeter error' 'area error'
 'smoothness error' 'compactness error' 'concavity error'
 'concave points error' 'symmetry error' 'fractal dimension error'
 'worst radius' 'worst texture' 'worst perimeter' 'worst area'
 'worst smoothness' 'worst compactness' 'worst concavity'
 'worst concave points' 'worst symmetry' 'worst fractal dimension']
target shape: (569,)
target sample: 0 0 0

 

8:2의 비율로 학습 데이터와 테스트 데이터로 분할합니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

 

데이터 스케일을 확인합니다.

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)

for i in range(x_train.shape[1]): # all features
    ax.scatter(x_train[:, i], y_train, s=10)
    
plt.title('Raw')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

 

학습 데이터와 테스트 데이터를 표준화합니다.

train_mean = x_train.mean(axis=0)
train_std = x_train.std(axis=0)

x_train = (x_train - train_mean) / train_std
x_test = (x_test - train_mean) / train_std

 

산점도로 나타나는 x축의 스케일이 조정되었습니다.

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)

for i in range(x_train.shape[1]): # all features
    ax.scatter(x_train[:, i], y_train, s=10)
    
plt.title('Standardization')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

 

신경망을 정의합니다.

import numpy as np
import tensorflow as tf

class Model:
    def __init__(self, lr=1e-3):
        tf.reset_default_graph()
        
        with tf.name_scope('input'):
            self.x = tf.placeholder(tf.float32, [None, 30])
            self.y = tf.placeholder(tf.float32, [None, 1])

        with tf.name_scope('layer'):
            fc = tf.layers.dense(self.x, 128, tf.nn.relu)
            fc2 = tf.layers.dense(fc, 128, tf.nn.relu)
            fc3 = tf.layers.dense(fc2, 1)
            
        with tf.name_scope('output'):
            self.output = tf.nn.sigmoid(fc3)

        with tf.name_scope('accuracy'):
            self.predict = tf.cast(tf.greater(self.output, tf.constant([0.5])), dtype=tf.float32)
            self.accuracy = tf.reduce_mean(tf.cast(tf.equal(self.y, self.predict), dtype=tf.float32))    
        
        with tf.name_scope('loss'):
            self.loss = -tf.reduce_mean(self.y * tf.log(self.output) + (1-self.y) * tf.log(1-self.output))

        with tf.name_scope('optimizer'):
            self.train_op = tf.train.GradientDescentOptimizer(lr).minimize(self.loss)

        with tf.name_scope('summary'):
            self.summary_loss = tf.placeholder(tf.float32)
            self.summary_accuracy = tf.placeholder(tf.float32)
            
            tf.summary.scalar('loss', self.summary_loss)
            tf.summary.scalar('accuracy', self.summary_accuracy)
            
            self.merge = tf.summary.merge_all()

        self.writer = tf.summary.FileWriter('./tmp/logistic-regression_breast_cancer_1', tf.get_default_graph())
        
        self.sess = tf.Session()
        
        self.sess.run(tf.global_variables_initializer())
                               
    def train(self, x, y, epochs, batch_size=32):
        data_size = len(x)
        for e in range(epochs):
            epoch_loss, epoch_acc = [], []
            
            idx = np.random.permutation(np.arange(data_size))
            x_train, y_train = x[idx], y[idx]
            for i in range(0, data_size, batch_size):
                si, ei = i, i + batch_size
                if ei > data_size:
                    ei = data_size

                x_batch, y_batch = x_train[si:ei, :], y_train[si:ei]
                
                loss, acc, _ = self.sess.run([self.loss, self.accuracy, self.train_op], {self.x: x_batch, self.y: y_batch})
                
                epoch_loss.append(loss)
                epoch_acc.append(acc)
            
            summary = self.sess.run(self.merge, {self.summary_loss: np.mean(epoch_loss), self.summary_accuracy: np.mean(epoch_acc)})
            self.writer.add_summary(summary, e)
            
            print('epoch:', e + 1, ' / loss:', np.mean(epoch_loss), '/ accuracy:', np.mean(epoch_acc))
    
    def score(self, x, y):
        return self.sess.run(self.accuracy, {self.x: x, self.y: y})

 

미니 배치 경사 하강법으로 학습을 수행합니다.

def train(self, x, y, epochs, batch_size=32):
    data_size = len(x)
    for e in range(epochs):
        epoch_loss, epoch_acc = [], []

        idx = np.random.permutation(np.arange(data_size))
        x_train, y_train = x[idx], y[idx]
        for i in range(0, data_size, batch_size):
            si, ei = i, i + batch_size
            if ei > data_size:
                ei = data_size

            x_batch, y_batch = x_train[si:ei, :], y_train[si:ei]

            loss, acc, _ = self.sess.run([self.loss, self.accuracy, self.train_op], {self.x: x_batch, self.y: y_batch})

            epoch_loss.append(loss)
            epoch_acc.append(acc)

        summary = self.sess.run(self.merge, {self.summary_loss: np.mean(epoch_loss), self.summary_accuracy: np.mean(epoch_acc)})
        self.writer.add_summary(summary, e)

        print('epoch:', e + 1, ' / loss:', np.mean(epoch_loss), '/ accuracy:', np.mean(epoch_acc))

 

모델을 학습하고 테스트합니다.

model = Model()
model.train(x_train, y_train, epochs=200)
model.score(x_test, y_test)
...

epoch: 190  / loss: 0.1256168 / accuracy: 0.96875
epoch: 191  / loss: 0.12216289 / accuracy: 0.96875
epoch: 192  / loss: 0.1224106 / accuracy: 0.96875
epoch: 193  / loss: 0.12211617 / accuracy: 0.96875
epoch: 194  / loss: 0.12102139 / accuracy: 0.96875
epoch: 195  / loss: 0.12139161 / accuracy: 0.96875
epoch: 196  / loss: 0.12274469 / accuracy: 0.96875
epoch: 197  / loss: 0.12596376 / accuracy: 0.96339285
epoch: 198  / loss: 0.12571438 / accuracy: 0.97083336
epoch: 199  / loss: 0.1223996 / accuracy: 0.97291666
epoch: 200  / loss: 0.12401686 / accuracy: 0.97291666

0.98245615

 

에포크에 대한 정확도와 손실 함수의 그래프는 다음과 같습니다.

 

 

 

'머신러닝 > 딥러닝' 카테고리의 다른 글

소프트맥스 회귀(Softmax Regression) (1)  (0) 2020.11.10
교차 검증(Cross Validation)  (0) 2020.11.09
선형 회귀(Linear Regression)  (0) 2020.11.06
순전파와 역전파  (0) 2020.11.06
신경망 학습  (0) 2020.11.04
  Comments,     Trackbacks
선형 회귀(Linear Regression)

선형 회귀(Linear Regression)

종속 변수 $y$ 와 하나 이상의 독립 변수 $x$ 와의 선형 관계를 모델링하는 통계학 기법입니다. 변수 $x$ 의 값은 독립적으로 변할 수 있으나 $y$ 의 값은 $x$ 의 값에 의해 종속적으로 결정됩니다.

 

1개의 독립 변수를 갖는 단순 선형 회귀(simple linear regression)의 수식은 다음과 같습니다.

 

$y=Wx+b$

 

이는 직선 방정식으로 $W$ 는 기울기, $b$ 는 절편이며 신경망의 파라미터(parameter)로 각각 가중치(weight)편향(bias)을 의미합니다.

 

 

즉 입력 데이터에 대한 예측으로 직선을 구하는 문제라고 할 수 있습니다.

 

 

2개 이상의 독립 변수를 다루면 이를 다중 선형 회귀(multiple linear regression)라고 합니다.

 

$y=W_0x_0+W_1x_1+...W_nx_n+b$

 

이는 데이터가 갖는 다수의 특성(feature) $x_0, ..., x_n$ 을 고려하여 $y$ 를 예측하는 것입니다. 입력 데이터는 n차원을 갖는 벡터로 데이터의 특성의 개수와 가중치의 개수는 같아야 합니다.

 

 

행렬의 곱

다음과 같은 다중 선형 회귀의 수식은 벡터의 내적 또는 행렬의 곱으로 나타낼 수 있습니다.

 

$y=W_0x_0+W_1x_1+...W_nx_n+b$

 

이는 데이터의 개수와 관련이 있는데 1개의 입력 데이터 $ [ x_0\ x_1\ x_2\ \cdots\ x_n  ] $ 에 대한 연산은 벡터의 내적으로 표현할 수 있습니다.

 

$
y =   
\left[  
    \begin{array}{c}  
      x_{0}\ x_{1}\ x_{2}\ \cdots\ x_{n}  
    \end{array}  
  \right]  
\left[  
    \begin{array}{c}  
      W_{0} \\  
      W_{1} \\  
      W_{2} \\  
      \cdot\cdot\cdot \\  
      W_{n}  
    \end{array}  
  \right]  
+  
b
= x_0W_0 + x_1W_1 + x_2W_2 + ... + x_nW_n + b 
$

입력 데이터가 여러 개인 경우 행렬의 곱으로 표현할 수 있습니다.

$\left[   
    \begin{array}{c}   
      x_{11}\ x_{12}\ x_{13}\ x_{14} \\   
      x_{21}\ x_{22}\ x_{23}\ x_{24} \\   
      x_{31}\ x_{32}\ x_{33}\ x_{34} \\   
      x_{41}\ x_{42}\ x_{43}\ x_{44} \\   
      x_{51}\ x_{52}\ x_{53}\ x_{54} \\   
    \end{array}   
  \right]   
\left[   
    \begin{array}{c}   
      w_{1} \\   
      w_{2} \\   
      w_{3} \\   
      w_{4} \\   
    \end{array}   
  \right]  

  +  

\left[   
    \begin{array}{c}   
      b \\   
      b \\   
      b \\   
      b \\

      b \\   
    \end{array}   
  \right]  
  =   
\left[   
    \begin{array}{c}   
      x_{11}w_{1}+ x_{12}w_{2}+ x_{13}w_{3}+ x_{14}w_{4} + b \\   
      x_{21}w_{1}+ x_{22}w_{2}+ x_{23}w_{3}+ x_{24}w_{4} + b \\   
      x_{31}w_{1}+ x_{32}w_{2}+ x_{33}w_{3}+ x_{34}w_{4} + b \\   
      x_{41}w_{1}+ x_{42}w_{2}+ x_{43}w_{3}+ x_{44}w_{4} + b \\   
      x_{51}w_{1}+ x_{52}w_{2}+ x_{53}w_{3}+ x_{54}w_{4} + b \\   
    \end{array}   
  \right]$

 

넘파이의 np.dot 을 이용하면 두 행렬간의 곱 연산을 수행할 수 있습니다.

import numpy as np

a = np.arange(20).reshape(5, 4)
b = np.array([1, -1, 1, -1, 0, 0, 0, 0]).reshape(-1, 2)

r = np.dot(a, b)

print(a)
print(b)
print(r)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
[[ 1 -1]
 [ 1 -1]
 [ 0  0]
 [ 0  0]]
[[  1  -1]
 [  9  -9]
 [ 17 -17]
 [ 25 -25]
 [ 33 -33]]

 

 

데이터 전처리

신경망에 입력되는 데이터가 여러 특성을 갖을 경우에는 각 특성의 데이터 분포를 확인해야 합니다. 주어지는 데이터는 항상 신경망 학습을 위해 특화되어 있지 않으며 특성에 따라 구성된 값의 단위가 다르다는 것입니다.

 

데이터의 스케일을 조정하는 방법에는 표준화(standardization)정규화(normalization)가 있습니다.

 

표준화는 데이터를 평균 $ \mu $ 이 0이고 표준 편차 $ \sigma $ 가 1인 분포로 만들어주는 것입니다.

 

$ X = { X - \mu \over \sigma} $ 

 

정규화는 스케일의 범위를 $ [0, 1] $ 로 만들어 주는 것입니다. 

 

$ X = { X - X_{min} \over X_{max} - X_{min} } $

 

특성의 영향을 균등하게 하는 것이라고 할 수 있습니다.

 

테스트 데이터를 산점도를 통해 비교해보면 기존 분포의 모양을 유지하며 y축의 스케일이 조정된 것을 확인할 수 있습니다.(실제 데이터는 y축이 아닌 x축 데이터를 전처리합니다.)

import numpy as np
import matplotlib.pyplot as plt

x = 500
y = np.random.normal(0, 10, x)

fig = plt.figure(figsize=(16, 4))

plt.subplot(131)
plt.scatter(range(x), y)
plt.title('Raw')

plt.subplot(132)
y_std = (y - y.mean()) / y.std()
plt.scatter(range(x), y_std, color='red')
plt.title('Standardization')

plt.subplot(133)
y_norm = (y - y.min()) / (y.max() - y.min())
plt.scatter(range(x), y_norm, color='green')
plt.title('Normalization')

plt.show()

 

 

손실 함수(Loss Function)

회귀 문제에서 손실 함수는 주로 평균 제곱 오차(mean squared error, MSE)가 사용되며 정답 $y$ 과 예측 $\hat y$ 의 오차 또는 손실을 최소화(minimize)하는 방향으로 파라미터를 학습합니다.

 

$MSE={1 \over n}\displaystyle \sum_{i=1}^n (y_i-\hat y_i)^2$

 

 

구현

다중 선형 회귀 모델을 구현합니다.

 

보스턴 주택 가격 데이터셋을 불러옵니다.

from sklearn.datasets import load_boston

boston = load_boston()

data = boston.data
target = boston.target
feature_names = boston.feature_names

print('data shape:', data.shape)
print('data sample:', data[0])
print('feature_names:', feature_names)

print('target shape:', target.shape)
print('target sample:', target[0], target[1], target[2])
data shape: (506, 13)
data sample: [6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01
 4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969e+02 4.980e+00]
feature_names: ['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
target shape: (506,)
target sample: 24.0 21.6 34.7

 

신경망에 정의한 입력 타겟의 형태는 다음과 같습니다.

self.y = tf.placeholder(tf.float32, [None, 1])

 

따라서 기존 타겟 데이터의 형태 변형이 필요합니다.

target = target.reshape(-1, 1)
target.shape
(506, 1)

 

8:2의 비율로 학습 데이터와 테스트 데이터로 분할합니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

 

데이터 분포를 확인합니다.

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)

for i in range(x_train.shape[1]): # all features
    ax.scatter(x_train[:, i], y_train, s=10)
    
plt.title('Raw')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

 

산점도 상에 나타난 각 특성의 스케일은 0에서 700정도로 넓게 분포합니다. 안정적인 학습을 위해서는 전처리가 필요합니다.

 

표준화를 위해 학습 데이터의 평균과 표준 편차를 구합니다.

train_mean = x_train.mean(axis=0)
train_std = x_train.std(axis=0)

 

평균과 표준 편차를 이용해 학습 데이터와 테스트 데이터를 표준화합니다. 

x_train = (x_train - train_mean) / train_std
x_test = (x_test - train_mean) / train_std

 

주의할 점은 테스트 데이터를 전처리할 때도 학습 데이터의 통계를 이용한다는 것입니다. 테스트 데이터는 최종 성능 평가를 위한 것으로 어떠한 정보도 누출되어서는 안됩니다.

 

산점도로 나타나는 x축의 스케일이 조정되었습니다.

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(12, 6))
ax = fig.add_subplot(111)

for i in range(x_train.shape[1]): # all features
    ax.scatter(x_train[:, i], y_train, s=10)
    
plt.title('Standardization')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

 

신경망을 정의합니다.

import numpy as np
import tensorflow as tf

class Model:
    def __init__(self, lr=1e-3):
        with tf.name_scope('input'):
            self.x = tf.placeholder(tf.float32, [None, 13])
            self.y = tf.placeholder(tf.float32, [None, 1])

        with tf.name_scope('layer'):
            w = tf.Variable(tf.random_normal([13, 64]))
            b = tf.Variable(tf.random_normal([1]))

            z = tf.matmul(self.x, w) + b
            h = tf.nn.relu(z)

            w2 = tf.Variable(tf.random_normal([64, 1]))
            b2 = tf.Variable(tf.random_normal([1]))

        with tf.name_scope('output'):
            pred = tf.matmul(h, w2) + b2

        with tf.name_scope('loss'):
            self.loss = tf.losses.mean_squared_error(self.y, pred)

        with tf.name_scope('optimizer'):
            self.train_op = tf.train.GradientDescentOptimizer(lr).minimize(self.loss)

        with tf.name_scope('summary'):
            tf.summary.scalar('loss', self.loss)
            self.merge = tf.summary.merge_all()

        self.writer = tf.summary.FileWriter('./tmp/linear-regression', tf.get_default_graph())
        
        self.sess = tf.Session()
        
        self.sess.run(tf.global_variables_initializer())
                               
    def train(self, x, y, epochs):
        for e in range(epochs):
            summary, loss, _ = self.sess.run([self.merge, self.loss, self.train_op], {self.x: x, self.y: y})
            self.writer.add_summary(summary, e)
            print('epoch:', e + 1, ' / loss:', loss)
    
    def test(self, x, y):
        loss = self.sess.run(self.loss, {self.x: x, self.y: y})
        return loss

 

배치 경사 하강법으로 학습을 수행합니다.

def train(self, x, y, epochs):
    for e in range(epochs):
        summary, loss, _ = self.sess.run([self.merge, self.loss, self.train_op], {self.x: x, self.y: y})
        self.writer.add_summary(summary, e)
        print('epoch:', e + 1, ' / loss:', loss)

 

모델을 학습하고 테스트합니다.

model = Model()
model.train(x_train, y_train, epochs=1000)
model.test(x_test, y_test)
...

epoch: 990  / loss: 9.514389
epoch: 991  / loss: 9.510468
epoch: 992  / loss: 9.506571
epoch: 993  / loss: 9.502658
epoch: 994  / loss: 9.49877
epoch: 995  / loss: 9.494896
epoch: 996  / loss: 9.490974
epoch: 997  / loss: 9.4871645
epoch: 998  / loss: 9.483273
epoch: 999  / loss: 9.479406
epoch: 1000  / loss: 9.475529

14.222591

 

에포크에 대한 손실 함수의 그래프는 다음과 같습니다.

 

 

 

'머신러닝 > 딥러닝' 카테고리의 다른 글

교차 검증(Cross Validation)  (0) 2020.11.09
로지스틱 회귀(Logistic Regression)  (0) 2020.11.06
순전파와 역전파  (0) 2020.11.06
신경망 학습  (0) 2020.11.04
퍼셉트론(Perceptron)  (0) 2020.11.04
  Comments,     Trackbacks
순전파와 역전파

신경망(Neural Network)

신경망의 내부 동작은 추론 과정에 해당하는 순전파(forward-propagation)학습 과정에 해당하는 역전파(back-propagation)로 나누어집니다.

 

특히 역전파는 다층 퍼셉트론과 같은 은닉층(hidden layer)을 포함하는 신경망에서 경사 하강법(gradient descent)을 이용한 학습 과정으로 미분의 체인 룰(chain rule)을 통해 기울기가 역으로 전파되어 파라미터가 업데이트되는 원리입니다.

 

예제를 통해 순전파와 역전파의 계산 과정을 알아보겠습니다.

 

다음 신경망은 입력층, 은닉층, 출력층으로 구성되며 각 층은 2개의 노드를 가지고 있습니다.

신경망에서 학습 가능한 파라미터는 가중치 $w_1$~$w_8$ 와 편향 $b_1, b_2$ 이며, 은닉층 노드 $h_1, h_2$ 와 출력층 노드 $o_1, o_2$ 에는 시그모이드 활성화 함수가 사용됩니다. 또한 손실 함수는 평균 제곱 오차를 사용하며 경사 하강법의 학습률은 0.5로 지정합니다.

 

초기 파라미터 값은 다음과 같습니다.

 

 

순전파(Forward-Propagation)

먼저 노드 $h_1$ 의 입력 $net_{h_1}$ 에 대해 계산합니다.

 

$ net_{h_1} = w_1 * i_1 + w_2 * i_2 + b_1 = 0.15 * 0.05 + 0.2 * 0.1 + 0.35 = 0.3775 $

 

$ net_{h_1} $ 을 시그모이드 함수를 통한 출력 $ out_{h_1} $ 은 다음과 같습니다.

 

$ out_{h_1} = {1 \over {1 + e^{net_{h_1}}} } = {1 \over {1 + e^{ -0.3775 }} } = 0.593269992 $

 

$ h_2 $ 도 같은 방식으로 계산합니다.

 

$ net_{h_2} = w_3 * i_1 + w_4 * i_2 + b_1 = 0.25 * 0.05 + 0.3 * 0.1 + 0.35 = 0.3925 $

 

$ out_{h_2} = {1 \over {1 + e^{net_{h_2}}} } = {1 \over {1 + e^{ -0.3925 }} } = 0.596884378 $

 

$ h_1, h_2 $ 에 대해 정리하면 다음과 같습니다.

 

$ net_{h_1} = 0.3775 $
$ net_{h_2} = 0.3925 $

$ out_{h_1} = 0.593269992 $

$ out_{h_2} = 0.596884378 $

 

 

위와 같은 방식으로 노드 $ o_1, o_2 $ 를 계산합니다.

 

$\begin{aligned}  
net_{o_1} & = w_5 * out_{h_1} + w_6 * out_{h_2} + b_2 \\\\  
& = 0.4 * 0.593269992 + 0.45 * 0.596884378 + 0.6 \\\\ 
& = 1.1059059669
\end{aligned}$

 

$ out_{o_1} = {1 \over {1 + e^{net_{o_1}}} } = {1 \over {1 + e^{ -1.1059059669 }} } = 0.7513650695224682 $

 

$\begin{aligned}   
net_{o_2} & = w_7 * out_{h_1} + w_8 * out_{h_2} + b_2 \\\\   
& = 0.5 * 0.593269992 + 0.55 * 0.596884378 + 0.6 \\\\  
& = 1.2249214039
\end{aligned}$ 

$ out_{o_2} = {1 \over {1 + e^{net_{o_2}}} } = {1 \over {1 + e^{ -1.2249214039 }} } = 0.772928465286981 $a

 

$o_1, o_2$ 에 대해 정리하면 다음과 같습니다.

 

$net_{o_1} = 1.1059059669$ 
$net_{o_2} = 1.2249214039 $
$out_{o_1} = 0.7513650695224682$
$out_{o_2} = 0.772928465286981 $

 

 

신경망의 출력과 타겟의 평균 제곱 오차를 계산합니다. 계산에 편의를 위해 $ 1 \over 2 $ 를 곱하며 1개의 입력에 대한 것이므로 다음과 같이 나타낼 수 있습니다.

 

$E = { 1\over 2 } (y - \hat y)^2 $

 

 

출력 $out_{o_1}$ 에 대한 타겟값은 $0.01$ 이며, $out_{o_2}$ 에 대한 타겟값은 $0.99$ 입니다.

 

$ target_{o_1} = 0.01 $

$ target_{o_2} = 0.99 $

 

$ E_{o_1} = { 1 \over 2 } ( target_{o_1} - out_{o_1} )^2 = { 1 \over 2 } ( 0.01 - 0.7513650695224682 )^2 = 0.274811083 $ 

 

$ E_{o_2} = { 1 \over 2 } ( target_{o_2} - out_{o_2} )^2 = { 1 \over 2 } ( 0.99 - 0.772928465286981 )^2 = 0.023560026 $ 

 

오차의 합을 계산합니다.

 

$ E_\text{total} = E_{o_1} + E_{o_2} = 0.274811083 + 0.023560026 = 0.298371109 $

 

역전파(Back-Propagation)

신경망에서 학습 가능한 파라미터는 가중치 $w_1$~$w_8$ 와 편향 $b_1, b_2$ 입니다. 이에 대해 경사 하강법을 적용하기 위해서는 먼저 각 파라미터에 대해 기울기를 계산해야 합니다.

 

우선 출력층에 가까운 $w_5$~$w_8$, $b_2$ 에 대해 업데이트를 진행합니다.

$w_5$ 에 대한 오차의 기울기 $\partial E_\text{total} \over \partial w_5$ 는 체인 룰에 의해 다음과 같이 나타낼 수 있습니다.

 

${\partial E_{total} \over \partial w_5} = {\partial E_\text{total} \over \partial out_{o_1}} {\partial out_{o_1} \over \partial net_{o_1}} {\partial net_{o_1} \over \partial w_5 }$

 

다음 항을 계산합니다.

 

${\partial E_\text{total} \over \partial w_5} = \color{red}{\partial E_\text{total} \over \partial out_{o_1}} {\partial out_{o_1} \over \partial net_{o_1}} {\partial net_{o_1} \over \partial w_5 }$

 

$\begin{aligned} 
{\partial E_\text{total} \over \partial out_{o_1}} & = { \partial \over \partial out_{o_1} } \left[ {1 \over 2}(target_{o_1} - out_{o_1} )^2 + {1 \over 2} ( target_{o_2} - out_{o_2} )^2 \right] \\\\
& = 2 * {1 \over 2}(target_{o_1} - out_{o_1}) * -1 + 0 \\\\ 
& = -(target_{o_1} - out_{o_1}) \\\\ 
& = -(0.01 - 0.7513650695224682) \\\\ 
& = 0.741365069 
\end{aligned}$

 

다음 항을 계산합니다.

 

${\partial E_\text{total} \over \partial w_5} = {\partial E_\text{total} \over \partial out_{o_1}} \color{red}{\partial out_{o_1} \over \partial net_{o_1}} {\partial net_{o_1} \over \partial w_5 }$

 

$ out_{o_1} $ 는 시그모이드 함수를 통한 출력입니다.

 

$ out_{o_1} = {1 \over { 1 + e^{-net_{o_1}} }} $

 

시그모이드 함수의 미분은 다음과 같습니다.

 

$ {\partial \over \partial x} \sigma (x) = \sigma (x) (1 - \sigma(x)) $

 

따라서 다음과 같이 계산할 수 있습니다.

 

$\begin{aligned} 
\partial out_{o_1} \over \partial net_{o_1} & = out_{o_1} ( 1 - out_{o_1} ) \\\\ 
& = 0.7513650695224682 ( 1 - 0.7513650695224682 ) \\\\ 
& = 0.186815602 
\end{aligned}$

 

다음 항을 계산합니다.

 

${\partial E_\text{total} \over \partial w_5} = {\partial E_\text{total} \over \partial out_{o_1}} {\partial out_{o_1} \over \partial net_{o_1}} \color{red}{\partial net_{o_1} \over \partial w_5 }$

 

$\begin{aligned} 
\partial net_{o_1} \over \partial w_5 & = { \partial \over \partial w_5 } (w_5 * out_{h_1} + w_6 * out_{h_2} + b_2) \\\\
& = 1 * out_{h_1} \\\\ 
& = 1 * 0.593269992 \\\\ 
& = 0.593269992 
\end{aligned}$

 

최종적으로 $ {\partial E_\text{total} \over \partial w_5} $ 은 다음과 같습니다.

 

$\begin{aligned} 
{\partial E_\text{total} \over \partial w_5} & = {\partial E_\text{total} \over \partial out_{o_1}} {\partial out_{o_1} \over \partial net_{o_1}} {\partial net_{o_1} \over \partial w_5 } \\\\  
& = 0.741365069 * 0.186815602 * 0.593269992 \\\\ 
& = 0.082167041
\end{aligned}$

 

이어서 경사 하강법을 적용하여 $w_5$ 를 업데이트합니다.

 

$\begin{aligned}
w_5^+ & = w_5 - \eta \, { \partial E_\text{total} \over \partial w_5 } \\\\ 
& = 0.4 - 0.5 * 0.082167041 \\\\ 
& = 0.35891648 
\end{aligned}$

 

이렇게 오차가 역방향으로 전파되어 파라미터를 업데이트하는 과정이 바로 오차 역전파인 것입니다.

 

같은 방식으로 나머지 $w_6$ 을 업데이트합니다.

 

$\begin{aligned}    
{\partial E_{total} \over \partial w_6} & = {\partial E_\text{total} \over \partial out_{o_1}} {\partial out_{o_1} \over \partial net_{o_1}} {\partial net_{o_1} \over \partial w_6 } \\\\     
& = 0.741365069 * 0.186815602 * 0.596884378 \\\\   
& = 0.082667628
\end{aligned}$

 

$\begin{aligned}  
w_6^+ & = w_6 - \eta \, { \partial E_\text{total} \over \partial w_6 } \\\\   
& = 0.45 - 0.5 * 0.082667628 \\\\  
& = 0.408666186
\end{aligned}$

 

$w_7, w_8$ 은 출력 노드 $o_2$ 에 영향을 주는 파라미터입니다.

 

${\partial E_\text{total} \over \partial w_7} = {\partial E_\text{total} \over \partial out_{o_2}} {\partial out_{o_2} \over \partial net_{o_2}} {\partial net_{o_2} \over \partial w_7 }$

 

$\begin{aligned}   
{\partial E_\text{total} \over \partial out_{o_2}} & = { \partial \over \partial out_{o_2} } \left[ {1 \over 2}(target_{o_1} - out_{o_1} )^2 + {1 \over 2} ( target_{o_2} - out_{o_2} )^2 \right] \\\\
& = 0 + 2 * {1 \over 2}(target_{o_2} - out_{o_2}) * -1 \\\\  
& = -(target_{o_2} - out_{o_2}) \\\\   
& = -(0.99 - 0.772928465286981) \\\\   
& = -0.217071535 
\end{aligned}$

 

$\begin{aligned}  
\partial out_{o_2} \over \partial net_{o_2} & = out_{o_2} ( 1 - out_{o_2} ) \\\\   
& = 0.772928465286981 ( 1 - 0.772928465286981 ) \\\\   
& = 0.175510053
\end{aligned}$

 

$\begin{aligned}   
\partial net_{o_2} \over \partial w_7 & = { \partial \over \partial w_7 } (w_7 * out_{h_1} + w_8 * out_{h_2} + b_2) \\\\
& = 1 * out_{h_1} \\\\   
& = 1 * 0.593269992 \\\\   
& = 0.593269992   
\end{aligned}$

 

$\begin{aligned}       
{\partial E_\text{total} \over \partial w_7} & = {\partial E_\text{total} \over \partial out_{o_2}} {\partial out_{o_2} \over \partial net_{o_2}} {\partial net_{o_2} \over \partial w_7 } \\\\        
& = -0.217071535 * 0.175510053 * 0.593269992 \\\\      
& = -0.022602541
\end{aligned}$

 

$\begin{aligned} 
w_7^+ & = w_7 - \eta \, { \partial E_\text{total} \over \partial w_7 } \\\\  
& = 0.5 - 0.5 * -0.022602541 \\\\ 
& = 0.511301271
\end{aligned}$

 

 

$\begin{aligned}      
{\partial E_\text{total} \over \partial w_8} & = {\partial E_\text{total} \over \partial out_{o_2}} {\partial out_{o_2} \over \partial net_{o_2}} {\partial net_{o_2} \over \partial w_8 } \\\\       
& = -0.217071535 * 0.175510053 * 0.596884378 \\\\     
& = -0.022740242
\end{aligned}$

 

$\begin{aligned}   
w_8^+ & = w_8 - \eta \, { \partial E_\text{total} \over \partial w_8 } \\\\    
& = 0.55 - 0.5 * -0.022740242 \\\\   
& = 0.561370121
\end{aligned}$

 

 

편향 $b_2$ 은 2개의 출력 노드 $o_1, o_2$ 에 영향을 주는 파라미터입니다.

따라서 $b_2$ 에 대한 전체 오차 $E_\text{total}$ 의 미분을 다음과 같이 나타낼 수 있습니다.

 

${ \partial E_\text{total} \over \partial b_2} = { \partial E_{o_1} \over \partial b_2} + { \partial E_{o_2} \over \partial b_2}$

 

각각 체인 룰을 적용합니다.

 

$\begin{aligned}
{ \partial E_{o_1} \over \partial b_2} & = { \partial E_{o_1} \over \partial out_{o_1} } { \partial out_{o_1} \over \partial net_{o_1} } { \partial net_{o_1} \over \partial b_2 } \\\\ 
& = 0.741365069 * 0.186815602 * 1 \\\\ 
& = 0.138498562
\end{aligned}$

 

$\begin{aligned}
{ \partial E_{o_2} \over \partial b_2} & = { \partial E_{o_2} \over \partial out_{o_2} } { \partial out_{o_2} \over \partial net_{o_2} } { \partial net_{o_2} \over \partial b_2 } \\\\ 
& = -0.217071535 * 0.175510053 * 1 \\\\ 
& = -0.038098237
\end{aligned}$

 

$\begin{aligned}
{ \partial E_\text{total} \over \partial b_2} & = { \partial E_{o_1} \over \partial b_2} + { \partial E_{o_2} \over \partial b_2} \\\\ 
& = 0.138498562 - 0.038098237 \\\\ 
& = 0.100400325
\end{aligned}$ 

 

경사 하강법을 적용해 $b_2$ 를 업데이트합니다.

 

$\begin{aligned}    
b_2^+ & = b_2 - \eta \, { \partial E_\text{total} \over \partial b_2 } \\\\     
& = 0.6 - 0.5 * 0.100400325 \\\\    
& = 0.549799838
\end{aligned}$

 

 

다음은 $w_1$~$w_4$, $b_1$ 에 대한 업데이트입니다.

$w_1$ 에 대한 기울기는 다음과 같이 나타낼 수 있습니다.

 

$ { \partial E_\text{total} \over \partial w_1 } = { \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_1 } $

 

여기서 은닉층의 노드 $h_1$ 의 출력 $out_{h_1}$ 은 2개의 출력 노드 $o_1, o_2$ 에 영향을 줍니다.

따라서 다음과 같이 나누어 줄 수 있습니다.

 

$ { \partial E_\text{total} \over \partial w_1 } = \color{red}{ \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_1 } $

 

${ \partial E_\text{total} \over \partial out_{h_1} } = { \partial E_{o_1} \over \partial out_{h_1} } + { \partial E_{o_2} \over \partial out_{h_1} }$

 

각각 체인 룰을 적용하면 다음과 같습니다. 

 

${ \partial E_{o_1} \over \partial out_{h1} } = { \partial E_{o_1} \over \partial out_{o_1} } { \partial out_{o_1} \over \partial net_{o_1} } { \partial net_{o_1} \over \partial out_{h_1} } $

 

${ \partial E_{o_2} \over \partial out_{h_1} } = { \partial E_{o_2} \over \partial out_{o_2} } { \partial out_{o_2} \over \partial net_{o_2} } { \partial net_{o_2} \over \partial out_{h_1} } $

 

 

위에서 계산한 내용은 다음과 같습니다.

 

${\partial E_{o_1} \over \partial out_{o_1}} = 0.741365069$

${\partial out_{o_1} \over \partial net_{o_1}} = 0.186815602$

${\partial E_{o_2} \over \partial out_{o_2}} = -0.217071535$

${\partial out_{o_2} \over \partial net_{o_2}} = 0.175510053$

 

 

다음 항을 계산합니다.

 

${ \partial E_{o_1} \over \partial out_{h_1} } = { \partial E_{o_1} \over \partial out_{o_1} } { \partial out_{o_1} \over \partial net_{o_1} } \color{red}{ \partial net_{o_1} \over \partial out_{h_1} } $

 

$ { \partial net_{o_1} \over \partial out_{h_1} } = {\partial \over \partial out_{h_1}} (w_5 * out_{h_1} + w_6 * out_{h_2} + b_2) = w_5 = 0.4 $

 

$ { \partial E_{o_1} \over \partial out_{h_1} } $ 을 계산합니다.

 

$\begin{aligned}
{ \partial E_{o_1} \over \partial out_{h_1} } & = { \partial E_{o_1} \over \partial out_{o_1} } { \partial out_{o_1} \over \partial net_{o_1} } { \partial net_{o_1} \over \partial out_{h_1} } \\\\
& = 0.741365069 * 0.186815602 * 0.4 \\\\
& = 0.055399425
\end{aligned}$

 

동일한 방식으로 ${ \partial E_{o_2} \over \partial out_{h_1} }$ 도 계산합니다.

 

$\begin{aligned}  
{ \partial E_{o_2} \over \partial out_{h_1} } & = { \partial E_{o_2} \over \partial out_{o_2} } { \partial out_{o_2} \over \partial net_{o_2} } { \partial net_{o_2} \over \partial out_{h_1} } \\\\  
& = -0.217071535 * 0.175510053 * 0.5 \\\\  
& = -0.019049118
\end{aligned}$

 

최종적으로 ${ \partial E_\text{total} \over \partial out_{h_1} }$ 은 다음과 같습니다.

 

$\begin{aligned}
{ \partial E_\text{total} \over \partial out_{h_1} } & = { \partial E_{o_1} \over \partial out_{h_1} } + { \partial E_{o_2} \over \partial out_{h_1} } \\\\
& = 0.055399425 - 0.019049118 \\\\
& = 0.036350307 
\end{aligned}$

 

다음 항을 계산합니다.

 

$ { \partial E_\text{total} \over \partial w_1 } = { \partial E_\text{total} \over \partial out_{h1} } \color{red}{ \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_1 } $

 

$ { \partial out_{h_1} \over \partial net_{h_1} } = out_{h_1} ( 1 - out_{h_1} ) = 0.593269992 ( 1 - 0.593269992 ) = 0.241300709 $

 

다음 항을 계산합니다.

 

$ { \partial E_\text{total} \over \partial w_1 } = { \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } \color{red}{ \partial net_{h_1} \over \partial w_1 } $

 

$ { \partial net_{h_1} \over \partial w_1 } = { \partial \over \partial w_1 } ( w_1 * i_1 + w_2 * i_2 + b_1 ) = i_1 = 0.05 $

 

최종적으로 $ { \partial E_\text{total} \over \partial w_1 } $ 을 계산합니다.

 

$\begin{aligned} 
{ \partial E_\text{total} \over \partial w_1 } & = { \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_1 } \\\\ 
& = 0.036350307 * 0.241300709 * 0.05 \\\\ 
& = 0.000438568
\end{aligned}$

 

경사 하강법을 적용하여 $w_1$ 을 업데이트합니다.

 

$\begin{aligned} 
w_1^+ & = w_1 - \eta \, { \partial E_\text{total} \over \partial w_1 } \\\\  
& = 0.15 - 0.5 * 0.000438568 \\\\ 
& = 0.149780716
\end{aligned}$

 

동일하게 가중치 $w_2, w_3, w_4$ 도 업데이트합니다.

 

$\begin{aligned}  
{ \partial E_\text{total} \over \partial w_2 } & = { \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_2 } \\\\  
& = 0.036350307 * 0.241300709 * 0.1 \\\\  
& = 0.000877135
\end{aligned}$

 

$\begin{aligned}   
w_2^+ & = w_2 - \eta \, { \partial E_\text{total} \over \partial w_2 } \\\\   
& = 0.2 - 0.5 * 0.000877135 \\\\   
& = 0.199561432
\end{aligned}$

 

$\begin{aligned}    
{ \partial E_\text{total} \over \partial w_3 } & = { \partial E_\text{total} \over \partial out_{h2} } { \partial out_{h_2} \over \partial net_{h_2} } { \partial net_{h_2} \over \partial w_3 } \\\\    
& = 0.041370323 * 0.240613417 * 0.05 \\\\    
& = 0.000497713
\end{aligned}$

 

$\begin{aligned}     
w_3^+ & = w_3 - \eta \, { \partial E_\text{total} \over \partial w_3 } \\\\     
& = 0.25 - 0.5 * 0.000497713 \\\\     
& = 0.249751144
\end{aligned}$

 

$\begin{aligned}    
{ \partial E_\text{total} \over \partial w_4 } & = { \partial E_\text{total} \over \partial out_{h1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial w_4 } \\\\    
& = 0.041370323 * 0.240613417 * 0.1 \\\\    
& = 0.000995426
\end{aligned}$

 

$\begin{aligned}     
w_4^+ & = w_4 - \eta \, { \partial E_\text{total} \over \partial w_4 } \\\\     
& = 0.3 - 0.5 * 0.000995426 \\\\     
& = 0.299502287
\end{aligned}$

 

 

편향 $b_1$ 은 2개의 은닉층 노드 $h1, h2$ 에 영향을 주는 파라미터입니다.

따라서 $b_1$ 에 대한 기울기는 다음과 같이 구할 수 있습니다.

 

$\begin{aligned} 
{ \partial E_\text{total} \over \partial b_1 } & = { \partial E_\text{total} \over \partial out_{h_1} } { \partial out_{h_1} \over \partial net_{h_1} } { \partial net_{h_1} \over \partial b_1 } + { \partial E_\text{total} \over \partial out_{h_2} } { \partial out_{h_2} \over \partial net_{h_2} } { \partial net_{h_2} \over \partial b_1 } \\\\
& = 0.000438568 * 0.241300709 * 1 + 0.041370323 * 0.240613417 * 1 \\\\ 
& = 0.010060082 
\end{aligned}$

 

$\begin{aligned}       
b_1^+ & = b_1 - \eta \, { \partial E_\text{total} \over \partial b_1 } \\\\       
& = 0.35 - 0.5 * 0.010060082 \\\\       
& = 0.344969959
\end{aligned}$

 

 

여기까지 모든 파라미터에 대한 업데이트를 완료했습니다.

 

$\begin{aligned}
w_1^+ = 0.149780716 \\
w_2^+ = 0.199561432 \\
w_3^+ = 0.249751144 \\
w_4^+ = 0.299502287 \\
w_5^+ = 0.358916480 \\
w_6^+ = 0.408666186 \\
w_7^+ = 0.511301271 \\
w_8^+ = 0.561370121 \\
b_1^+ = 0.344969959 \\
b_2^+ = 0.549799838
\end{aligned}$

 

이제 업데이트된 파라미터를 이용해 다시 순전파를 통해 출력값을 계산합니다.

 

$\begin{aligned}
net_{h_1} & = w_1 * i_1 + w_2 * i_2 + b_1 \\\\
& = 0.149780716 * 0.05 + 0.199561432 * 0.1 + 0.344969959 \\\\
& = 0.372415138
\end{aligned}$

 

$ out_{h_1} = {1 \over {1 + e^{net_{h_1}}} } = {1 \over {1 + e^{ -0.372415138 }} } = 0.592042432$

 

$\begin{aligned}
net_{h_2} & = w_3 * i_1 + w_4 * i_2 + b_1 \\\\
& = 0.249751144 * 0.05 + 0.299502287 * 0.1 + 0.344969959 \\\\
& = 0.387407745 
\end{aligned}$

 

$ out_{h_2} = {1 \over {1 + e^{net_{h_2}}} } = {1 \over {1 + e^{ -0.3874077449 }} } = 0.595658511 $

 

$\begin{aligned}     
net_{o_1} & = w_5 * out_{h_1} + w_6 * out_{h_2} + b_2 \\\\     
& = 0.358916480 * 0.592042432+ 0.408666186 * 0.595658511 + 0.549799838 \\\\    
& = 1.005719116
\end{aligned} $

 

$ out_{o_1} = {1 \over {1 + e^{net_{o_1}}} } = {1 \over {1 + e^{ -1.005719116 }} } = 0.732181538 $

 

$ \begin{aligned}     
net_{o_2} & = w_7 * out_{h_1} + w_8 * out_{h_2} + b_2 \\\\     
& = 0.511301271 * 0.592042432 + 0.561370121 * 0.595658511 + 0.549799838 \\\\    
& = 1.186896776
\end{aligned} $

 

$ out_{o_2} = {1 \over {1 + e^{net_{o_2}}} } = {1 \over {1 + e^{ -1.186896776 }} } = 0.766185596 $

 

$ E_{o_1} = { 1 \over 2 } ( target_{o_1} - out_{o_1} )^2 = { 1 \over 2 } ( 0.01 - 0.732181538 )^2 = 0.260773087$

 

$ E_{o_2} = { 1 \over 2 } ( target_{o_2} - out_{o_2} )^2 = { 1 \over 2 } ( 0.99 - 0.766185596 )^2 = 0.025046444$

 

$ E_\text{total} = E_{o_1} + E_{o_2} = 0.260773087 + 0.025046444 = 0.285819531 $

 

 

기존 오차와 비교해 감소되었습니다.

 

$ E_\text{old total} - E_\text{new total} = 0.298371109 - 0.285819531 = 0.012551578$

 

 

 

'머신러닝 > 딥러닝' 카테고리의 다른 글

교차 검증(Cross Validation)  (0) 2020.11.09
로지스틱 회귀(Logistic Regression)  (0) 2020.11.06
선형 회귀(Linear Regression)  (0) 2020.11.06
신경망 학습  (0) 2020.11.04
퍼셉트론(Perceptron)  (0) 2020.11.04
  Comments,     Trackbacks
신경망 학습

퍼셉트론(Perceptron)

다음은 입력 $x_1, x_2$ 에 대해 $y$ 를 출력하는 퍼셉트론입니다.

 

 

이와 같은 신경망을 학습하는 것은 입력으로 받는 $x_1, x_2$ 에 대해 적절한 가중치(weight) $w_1, w_2$ 와 편향(bias) $b$ 을 구하는 것이라고 할 수 있으며 이러한 가중치와 편향을 신경망에서는 파라미터(parameter)라고 합니다.

 

 

활성화 함수(Activation Function)

다음은 퍼셉트론의 동작을 나타냅니다.

 

$
y=   
\begin{cases}   0 \ ( \, b + w_1 x_1 + w_2 x_2 \le 0 \ ) \\\\  
1 \ ( \, b + w_1 x_1 + w_2 x_2 > 0 \ )  
\end{cases}
$

$b + w_1 x_1 + w_2 x_2$ 의 값에 임계값 0을 기준으로 0과 1을 분류하는 것인데 이 같은 임계값을 경계로 출력이 바뀌는 함수를 계단 함수(step function)이라고 합니다. 

 

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

 

또한 계단 함수와 같이 입력 신호에 대해 출력 신호로 변환하는 함수를 활성화 함수(activation function)라고 합니다. 보통 신경망에서는 활성화 함수로 비선형(nonlinear) 함수를 사용하는데, 이는 선형(linear) 함수를 사용하면 층을 깊게 하는 의미가 없기 때문입니다. 또한 활성화 함수로 사용하기 위해서는 미분 가능해야 하는데 이는 파라미터 업데이트 과정에서 미분을 이용하기 때문입니다.

 

 

데이터(Data)

학습의 목적은 주어진 데이터에 대해 적절한 파라미터를 구하는 것입니다. 그 과정에 있어 여러 데이터의 특성(feature)으로부터 패턴과 같은 규칙성을 찾아내고 파라미터를 조정하는 것입니다. 이 같은 데이터는 신경망 학습에서 없어서는 안되는 중요한 요소입니다.

 

다음은 Iris 라는 데이터의 구조입니다.

 

 

행은 샘플의 개수를 나타내며 열은 데이터가 갖는 특성을 의미합니다. 또한 해당 행에 대해 분류하고자하는 어떤 범주 또는 클래스의 값을 가지는 특성을 레이블(label)이라고 하며, 이 값은 신경망의 출력인 예측과 일치하고자하는 타겟(target)인 것입니다.

 

또한 데이터는 학습에 사용하는 학습 데이터(train data)와 테스트에 사용하는 테스트 데이터(test data)로 나누어지는데 이는 모델을 범용적으로 사용할 수 있게 일반화하는 것입니다. 

 

다음과 같이 데이터가 분리되어 있지 않은 경우도 있습니다.

from sklearn.datasets import load_boston

boston = load_boston()

data = boston.data
target = boston.target

print(dir(boston))
['DESCR', 'data', 'feature_names', 'filename', 'target']

 

사이킷런에서 제공하는 train_test_split 을 이용하면 학습 데이터와 테스트 데이터를 나눌 수 있습니다.

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2)

 

여기서 test_size=0.2 의 의미는 데이터의 20% 를 테스트 데이터로 분리하는 것이며 기본적으로 무작위로 섞여서 분리됩니다.

 

 

손실 함수(Loss Function)

신경망 학습을 위한 방향 또는 성능 지표를 의미하며 이를 통해 신경망의 파라미터를 조정하는 것입니다. 이 같은 손실 함수는 예측의 목표가 되는 타겟과의 관계로 정의할 수 있습니다.

 

회귀 문제에서 주로 사용되는 손실 함수인 평균 제곱 오차(mean squared error, MSE)는 다음과 같습니다.

 

$MSE={1 \over n}\displaystyle \sum_{i=1}^n (y_i-\hat y_i)^2$

 

데이터 $n$ 개에 대해 타겟 $y$ 과 예측 $\hat y$ 의 오차(error) 또는 손실(loss)의 평균을 나타내는 것으로 예측이 타겟에 가까울수록 손실값은 0에 가까워지게 됩니다. 

 

이러한 손실 함수는 신경망의 파라미터에 대해 미분이 가능해야 하며 이를 지표로 파라미터를 업데이트할 수 있게 됩니다.

 

 

경사 하강법(Gradient Descent)

경사 하강법은 파라미터를 학습하기 위한 최적화 알고리즘(optimizer)으로 손실 함수에서 계산된 손실값을 최소화(minimize)하는 방향으로 파라미터를 업데이트합니다.

 

손실 함수의 최솟값을 구하기 위해서는 기울기(gradient)를 따라 손실값이 감소하는 방향으로 이동해야 하는데, 위와 같이 기울기가 음수인 지점이라면 $w$ 값이 커져야 손실값이 작아지게 되고 기울기가 양수라면 $w$ 값이 작아져야 손실값이 작아져 최종적으로 최솟값에 수렴하게 되는 것입니다.

 

이러한 기울기는 경사 하강법에서 손실값을 낮추는 방안을 제시하는 지표로서 이용되는 것이며 신경망의 파라미터인 가중치나 편향에 대한 손실 함수의 미분으로 구할 수 있습니다.

 

파라미터에 대한 평균 제곱 오차 손실 함수의 기울기는 다음과 같습니다.

 

$MSE={1 \over n}\displaystyle \sum_{i=1}^n (y_i-\hat y_i)^2$

 

${\partial \over \partial w} MSE(w, b)$

 

${\partial \over \partial b} MSE(w, b)$

 

또한 신경망의 파라미터는 벡터이며 각 파라미터가 손실값에 미치는 영향력을 알아내기 위해 편미분을 이용하는데 이는 대상 변수 외에 다른 변수는 상수로 취급하는 것을 의미합니다. 

 

신경망의 파라미터를 업데이트하는 경사 하강법의 수식은 다음과 같습니다.

 

$\theta= \theta - \eta \, \nabla_\theta L(\theta)$

 

파라미터 $\theta$ 에 대한 손실 함수 $L(\theta)$ 의 기울기를 이용하여 파라미터를 업데이트하는 것으로  $\eta$ 는 학습률(step size, learning rate)로 업데이트하는 양을 의미합니다. 이러한 학습률은 하이퍼파라미터(hyperparameter)로 적절한 크기로 설정해야 안정적인 학습이 가능합니다.

 

신경망의 학습은 이러한 경사 하강법을 이용하여 반복적으로 파라미터를 업데이트하여 최적화(optimization)하는 것입니다.

 

 

추가적으로 배치(batch)와 관련하여 경사 하강법 종류는 다음과 같습니다.

 

- 확률적 경사 하강법(stochastic gradient descent, SGD)

- 배치 경사 하강법(batch gradient descent)

- 미니배치 경사 하강법(minibatch gradient descent)

 

배치란 경사 하강법의 계산에 사용되는 데이터의 단위를 의미합니다. 예를 들어 배치의 크기가 32이면 한 번에 계산되는 데이터의 개수가 32개라는 것입니다. 배치의 크기는 학습에 영향을 미치는 요소로 이와 관련하여 경사 하강법의 종류가 나뉘어 진 것입니다.

 

확률적 경사 하강법은 하나의 데이터(배치 크기 1)마다 계산하는 것으로 학습 전에 데이터를 무작위로 추출하기 때문에 확률적인 것입니다. 특징은 계산 비용이 적은 대신 수렴이 불안정합니다. 이에 반해 배치 경사 하강법은 모든 학습 데이터에 대해 한 번에 계산하는 것입니다. 특징은 계산 비용이 큰 대신 수렴 과정이 안정적입니다. 또한 두 방식을 절충한 것이 미니 배치 경사 하강법입니다.

 

 

또한 경사 하강법에는 단점이 있는데 신경망 파라미터의 무작위 초기화(random initialization) 방식으로 인해 전역 극소값(global minimum)이 아닌 지역 극소값(local minimum)에 수렴할 수 있다는 것입니다.

 

 

 

구현

경사 하강법을 이용해 학습하는 선형 회귀 모델을 구현합니다.

 

학습 데이터를 생성합니다.

import numpy as np
import matplotlib.pyplot as plt

n = 50

x_data = np.random.rand(n, 1)
b_noise = np.random.normal(0, 0.5, size=(n, 1))

w = 5
b = 1

y_data = w * x_data + b + b_noise

plt.scatter(x_data, y_data)
plt.show()

 

모델을 정의합니다.

class Model:
    def __init__(self, lr=0.01, epochs=100):
        self.w = np.random.rand()
        self.b = np.random.rand()
        self.lr = lr
        self.epochs = epochs
    
    def predict(self, x):
        return self.w * x + self.b
    
    def train(self, x_data, y_data):
        for epoch in range(self.epochs):
            for x, y in zip(x_data, y_data):
                y_hat = self.predict(x)
                err = -(y - y_hat)
                w_grad = self.lr * err * x
                b_grad = self.lr * err
                self.w -= w_grad
                self.b -= b_grad

    def draw_graph(self, x_data, y_data):
        plt.scatter(x_data, y_data)
        pt1 = (0.01, self.predict(0.01))
        pt2 = (0.99, self.predict(0.99))
        plt.plot([pt1[0], pt2[0]], [pt1[1], pt2[1]], 'red')
        plt.title('Linear Regression')
        plt.xlabel('x')
        plt.ylabel('y')
        plt.show()

 

학습을 수행합니다.

def train(self, x_data, y_data):
    for epoch in range(self.epochs):
        for x, y in zip(x_data, y_data):
            y_hat = self.predict(x)
            err = -(y - y_hat)
            w_grad = self.lr * err * x
            b_grad = self.lr * err
            self.w -= w_grad
            self.b -= b_grad

 

선형 회귀의 예측 $\hat y$ 을 수식으로 나타내면 다음과 같습니다.

 

$\hat y = wx + b$

 

1개의 데이터로 업데이트를 수행하여 손실 함수는 제곱 오차(squared error)를 이용합니다.

 

$L = {1 \over 2}(y - \hat y)^2$

 

가중치 $w$ 에 대한 손실 함수 $L$ 의 기울기는 다음과 같이 구할 수 있습니다.

 

$\begin{aligned}  
{\partial L \over \partial w} & = {\partial \over \partial w} {1 \over 2}(y - \hat y)^2 \\  
& = 2 * {1 \over 2}(y - \hat y) * - {\partial \over \partial w} \hat y \\ 
& = (y - \hat y) * -x  \\
& = -(y - \hat y) x
\end{aligned}$

 

경사 하강법을 통한 파라미터 업데이트 식으로 나타내면 다음과 같습니다.

 

$\begin{aligned}
w & = w - \eta {\partial L \over \partial w} \\
& = w + \eta (y - \hat y) x
\end{aligned}$

 

 

학습을 진행하고 그래프를 확인합니다.

model = Model()
model.train(x_data, y_data)
model.draw_graph(x_data, y_data)

 

 

'머신러닝 > 딥러닝' 카테고리의 다른 글

교차 검증(Cross Validation)  (0) 2020.11.09
로지스틱 회귀(Logistic Regression)  (0) 2020.11.06
선형 회귀(Linear Regression)  (0) 2020.11.06
순전파와 역전파  (0) 2020.11.06
퍼셉트론(Perceptron)  (0) 2020.11.04
  Comments,     Trackbacks
퍼셉트론(Perceptron)

퍼셉트론(Perceptron)

1957년 Frank Rosenblatt 가 고안한 알고리즘으로 인공 신경망의 기원이 됩니다.

 

뇌의 신경 세포인 뉴런(neuron)의 구조에서 수상돌기(dendrites)는 다른 신경 세포로부터 받은 신호를 축삭돌기(axon)을 통해 다른 뉴런이나 세포에 전달합니다. 이와 비슷한 기능을 하는 퍼셉트론은 다수의 입력 신호로부터 하나의 신호(0 또는 1)를 출력합니다.

 

 

다음은 2개의 입력 신호 $x_1, x_2$ 를 받아 신호 $y$ 를 출력하는 퍼셉트론입니다. 

 

 

여기서 $w_1, w_2$ 는 가중치(weight)를 의미하는데 이는 출력에 대해 각 신호에 주는 영향을 조절하는 요소입니다.

 

 

계산은 다음과 같으며 임계값 $\theta$ 을 기준으로 0과 1을 출력하는데 이를 이진 분류(binary classification)라고 합니다.

 

$y=
\begin{cases}
0 \ ( \, w_1 x_1 + w_2 x_2 \le \theta \ ) \\\\
1 \ ( \, w_1 x_1 + w_2 x_2 > \theta \ )
\end{cases}
$

 

또한 1을 출력하는 것을 뉴런을 활성화(activation)한다고 합니다. 

 

 

이어서 편향 $b$ 를 도입하기 위해 $\theta$ 를 $-b$ 로 치환하고 이항하면 다음과 같습니다. 

 

$y= 
\begin{cases} 
0 \ ( \, b + w_1 x_1 + w_2 x_2 \le 0 \ ) \\\\
1 \ ( \, b + w_1 x_1 + w_2 x_2 > 0 \ )
\end{cases} 
$

 

편향은 뉴런의 활성화를 조절하는 요소입니다. 예를 들어 $b$ 가 -1인 경우, $w_1 x_1 + w_2 x_2$ 의 값이 1을 초과할 때 뉴런이 활성화하고 $b$ 가 -10인 경우, $w_1 x_1 + w_2 x_2$ 의 값이 10을 초과해야 뉴런이 활성화합니다. 즉 얼마나 쉽게 활성화를 하는지를 조절하는 것입니다.

 

 

구현

퍼셉트론으로 논리 회로를 구현합니다.

 

 

AND Gate

 

모든 입력이 참인 경우에만 참을 출력합니다.

 

AND 게이트에 대한 진리표를 참고하여 임의의 가중치와 편향을 설정합니다.

 

import numpy as np

def AND(x1, x2):
    # inputs
    x = np.array([x1, x2])
    
    # weights
    w = np.array([0.4, 0.4])
    
    # bias
    b = -0.5
    
    z = np.sum(x*w) + b
    
    if z <= 0:
        return 0
    else:
        return 1
print(AND(0, 0))
print(AND(0, 1))
print(AND(1, 0))
print(AND(1, 1))
0
0
0
1

 

 

NAND Gate

 

AND 게이트와 반대로 모든 입력이 참인 경우에만 거짓을 출력합니다.

 

NAND 게이트에 대한 진리표를 참고하여 임의의 가중치와 편향을 설정합니다.

 

import numpy as np

def NAND(x1, x2):
    # inputs
    x = np.array([x1, x2])
    
    # weights
    w = np.array([-0.4, -0.4])
    
    # bias
    b = 0.5
    
    z = np.sum(x*w) + b
    
    if z <= 0:
        return 0
    else:
        return 1
print(NAND(0, 0))
print(NAND(0, 1))
print(NAND(1, 0))
print(NAND(1, 1))
1
1
1
0

 

 

OR Gate

 

하나 이상의 입력이 참인 경우에 참을 출력합니다.

 

OR 게이트에 대한 진리표를 참고하여 임의의 가중치와 편향을 설정합니다.

 

import numpy as np

def OR(x1, x2):
    # inputs
    x = np.array([x1, x2])
    
    # weights
    w = np.array([0.4, 0.4])
    
    # bias
    b = -0.3
    
    z = np.sum(x*w) + b
    
    if z <= 0:
        return 0
    else:
        return 1
print(OR(0, 0))
print(OR(0, 1))
print(OR(1, 0))
print(OR(1, 1))
0
1
1
1

 

 

선형과 비선형

계속해서 퍼셉트론으로 XOR 게이트를 구현할 수 있을까요?

 

XOR 게이트는 다음과 같은 진리표를 가지며 두 입력 중 하나의 입력이 참일 경우에만 참을 출력합니다.

 

 

퍼셉트론은 분류하고자 하는 두 영역을 직선으로 나누는 것으로 AND, NAN, OR 의 진리표에 만족합니다.

 

 

하지만 XOR 에서는 선형(linear)적인 직선으로는 입력에 대해 참과 거짓을 분류하지 못하는데 이러한 문제를 비선형(nonlinear) 문제라고 합니다.

 

 

 

 

다층 퍼셉트론(Multilayer Perceptron, MLP)

앞서 다룬 하나의 입력층과 출력층을 갖는 단층의 구조로는 비선형 문제에 해당하는 XOR 게이트를 구현하지 못했습니다. 

 

 

이를 해결하는 것이 다층 퍼셉트론으로 입력층과 출력층 사이에 은닉층(hidden layer)을 갖는 구조입니다. 

 

 

이 같은 구조로 기본 논리 게이트를 조합하여 XOR 을 구현할 수 있게 됩니다.

 

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y
print(XOR(0, 0))
print(XOR(0, 1))
print(XOR(1, 0))
print(XOR(1, 1))
0
1
1
0

 

 

'머신러닝 > 딥러닝' 카테고리의 다른 글

교차 검증(Cross Validation)  (0) 2020.11.09
로지스틱 회귀(Logistic Regression)  (0) 2020.11.06
선형 회귀(Linear Regression)  (0) 2020.11.06
순전파와 역전파  (0) 2020.11.06
신경망 학습  (0) 2020.11.04
  Comments,     Trackbacks