728x90
반응형

 꽤 많은 블로그나 책에서 머신러닝을 다룰 때, 회귀모형, 로지스틱 회귀모형을 꼭 다루고 넘어가는데, 이번 포스트에서는 우리가 통계학에서 흔히 다루는 회귀모형과 딥러닝이 대체 어떤 관계길래 다들 회귀모형부터 다루는지에 대해 알아보고자 한다.

 사실 우리는 이미 이전 포스트에서 회귀모형, 로지스틱 회귀모형을 만들어보았으며, 만약, 통계에 조금 익숙한 사람이라면, 분석된 결과나 그 과정을 보면서, 이게 회귀분석 아닌가? 하는 생각이 들었을지도 모른다(물론, Odd Ratio, $R^2$과 같은 익숙한 지표들이 그대로 등장하진 않았지만, 그것과 유사한 역할을 하는 지표를 이미 봤을 것이다).

 

 

회귀모형(Regression Model)

 통계에 익숙한 사람이라면, 통계의 꽃인 회귀 모형에 대해 이미 잘 알고 있을 것이다. 우리가 기존에 학습했던, "Tensorflow-1.4. 기초(5)-하이퍼 파라미터 튜닝", "Tensorflow-2.4. 타이타닉 생종자 분류 모델(3)-하이퍼 파라미터 튜닝"에서 만들었던 모델이 바로 회귀모형이다.

 

 

 

 

1. 통계학과 딥러닝의 회귀모형

 회귀모형에 대해 잘 모를 수 있으므로, 통계학에서의 회귀모형에 대해 아주 단순하게 설명해보도록 하겠다.

  • 회귀모형은 데이터 간에 어떠한 경향성이 있다는 생각에서 시작된다.
  • 회귀모형은 독립변수와 종속변수가 서로 인과 관계가 있다고 할 때, 독립변수가 변하면 종속변수가 변하게 된다.

회귀식: $ y = b_1x_1 + b_2x_2 + b_3x_3 + c $

  • 회귀모형은 데이터에 가장 적합한 계수($b_1, b_2, b_3, c$)를 구하는 것이 목적이다.
  • 회귀모형은 그 적합한 계수를 찾는 방법으로 평균 제곱 오차(MSE)를 사용한다.
    (실제값과 예측값의 편차 제곱의 평균, 참고: "딥러닝-5.1. 손실함수(2)-평균제곱오차(MSE)")

 회귀식은 퍼셉트론 공식과 아주 똑 닮았다("머신러닝-2.1. 퍼셉트론(2)-논리회로"). 그리고 딥러닝을 통해 우리는 각각의 파라미터(가중치와 편향)를 찾아낼 수 있다.

 독립변수(Dataset이자 상수)의 변화에 따른 종속변수(Label, 상수)의 변화를 가장 잘 설명할 수 있는 계수(weight과 bias)를 찾아내는 것은 회귀분석이며, 이 점이 다층 퍼셉트론을 이용하여, 데이터 자체를 가장 잘 설명할 수 있는 파라미터를 찾아내는 딥러닝(Deep Learning)과 같다고 할 수 있다.

1.1. 이 밖의 회귀모형의 특징

  • 회귀모형은 기본적으로 연속형 데이터를 기반으로 한다.
  • 독립변수, 종속변수가 모두 연속형 데이터여야 한다.
  • 연속형 데이터가 아닌 독립변수 존재 시, 이를 가변수(Dummy variable)로 만든다.

 

 

 

 

2. 데이터셋

 이번 학습에서는 R을 사용해본 사람이라면 아주 친숙한 데이터 중 하나인 자동차 연비 데이터(MPG)를 이용해서 회귀모형을 만들어보도록 하겠다.

# Import Module
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split

import pandas as pd
import numpy as np
>>> dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
Downloading data from http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data
32768/30286 [================================] - 0s 4us/step
  • tf.keras.utils.get_file()을 사용하면, 각종 데이터셋을 쉽게 가져올 수 있다.
  • 데이터셋을 가지고 오는 곳은 UCI 머신러닝 저장소로 인터넷이 안 되는 환경이라면 데이터를 다운로드할 수 없으니, 조심하자.
  • sklearn처럼 데이터셋을 Dictionary에 깔끔하게 저장하여, 데이터를 다운로드하면, 모든 데이터에 대한 정보가 있는 상태가 아니므로, 데이터에 대해 파악하기 위해선 UCI 머신러닝 저장소에서 데이터에 대해 검색하여, 데이터 정보를 찾아봐야 한다.
  • 해당 포스팅에서는 "Tensorflow 공식 홈페이지의 자동차 연비 예측하기: 회귀"를 참고하여 데이터셋을 준비하였다.
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Model Year', 'Origin']
Rawdata = pd.read_csv(dataset_path, names=column_names, na_values = "?",
                      comment='\t', sep=" ", skipinitialspace=True)
Rawdata

 

 

 

 

3. 데이터 전처리

3.1. 결측 값 처리

>>> Rawdata.isna().sum()
MPG             0
Cylinders       0
Displacement    0
Horsepower      6
Weight          0
Acceleration    0
Model Year      0
Origin          0
dtype: int64

>>> print("전체 데이터에서 결측값 행의 비율:", Rawdata.isna().sum(1).sum()/len(Rawdata))
전체 데이터에서 결측값 행의 비율: 0.01507537688442211
  • 위 결과를 보니, Horsepower에서 결측 값이 6개 발생한 것을 알 수 있다.
  • 전체 데이터에서 결측 값 행의 비율을 보니 0.015로 매우 미미한 양이므로, 제거하도록 하자.
Rawdata.dropna(inplace = True)

 

3.2. 범주형 데이터의 원-핫 벡터화

  • 칼럼 Origin은 숫자로 표시되어 있지만, 실제론 문자인 범주형 데이터이므로, 원-핫 벡터화 해주자.
    (이는 회귀 모델에서의 가변수 처리와 매우 유사한 부분이라고 할 수 있다.)
# One-Hot Vector 만들기
def make_One_Hot(data_DF, column):
    
    target_column = data_DF.pop(column)

    for i in sorted(target_column.unique()):

        new_column = column + "_" + str(i)
        data_DF[new_column] = (target_column == i) * 1.0
  • 이전 참고 포스트에서 만들었던, 원-핫 벡터 코드보다 판다스의 성격을 잘 활용하여 만든 코드다
  • DataFrame.pop(column): DataFrame에서 선택된 column을 말 그대로 뽑아낸다. 그로 인해 기존 DataFrame에서 해당 column은 사라지게 된다.
  • (target_column == i) * 1.0: Boolearn의 성질을 사용한 것으로 target_column의 원소를 갖는 위치는 True로, 원소가 없는 곳은 False가 된다. 이에 1.0을 곱하여 int로 바꿔줬다. Python은 동적 언어이므로, 1.0을 곱해주어도 int형으로 변하게 된다.

 

3.3. 데이터셋 분리

  • train set과 test set은 7:3으로 분리하도록 하겠다.
  • validation set은 이 단계에서 뽑지 않고, 학습 과정(fit)에서 뽑도록 하겠다.
  • MPG 변수는 연비를 의미하며 종속변수에 해당하므로, 이를 Label로 사용하겠다.
# Dataset 쪼개기
label = Rawdata.pop("MPG").to_numpy()
dataset = Rawdata.values
X_train, X_test, y_train, y_test = train_test_split(dataset, label, test_size = 0.3)

 

3.4. 특성 스케일 조정

  • 해당 데이터 셋의 각 칼럼의 데이터는 다른 변수에서 나온 것이므로, 각각 변수별 기준으로 정규화해주어야 한다.
  • 표준 정규 분포를 사용하여 특성 스케일 조정을 하도록 하겠다.
  • 원-핫 벡터가 사용된 7:9까지의 열을 제외한 나머지 열에 대해 각각 특성 스케일 조정을 하겠다.
# 특성 스케일 조절
mean_point = X_train[:, :6].mean(axis=0)
std_point = X_train[:, :6].std(axis=0)

X_train[:, :6] = ((X_train[:, :6] - mean_point)/std_point)
X_test[:, :6] = ((X_test[:, :6] - mean_point)/std_point)
  • Numpy의 특징 중 하나인 Broadcasting 덕에 쉽게 정규화할 수 있다.

 

 

 

 

4. 모델 학습 및 평가

  • 모델은 은닉층이 1개 있는 단층 퍼셉트론(Sigle-later Perceptron)으로 만들어보겠다.
    (은닉층을 2개 이상 넣어 다층 퍼셉트론으로 만들어도 상관없다)
  • model은 Sequential 함수 안에 layer를 넣어주는 방법으로 만들어보겠다.
    (추천하지는 않는 방법이지만 이런 방법도 있다는 것을 보여주고자 사용해보았다)
  • 출력층의 활성화 함수는 선형(Linear)으로 설정하였다.
    (activation의 Default는 linear이므로, 따로 설정하지 않아도 된다)
  • 조기 종료(Early stop)를 콜백 함수로 주도록 하겠다.
# model 생성
model = keras.Sequential([
    keras.layers.Dense(60, activation = "relu"),
    keras.layers.Dense(1, activation = "linear")
])

# model compile 설정
opt = keras.optimizers.Adam(learning_rate=0.005)
model.compile(optimizer=opt, loss="mse", metrics = ["accuracy"])

# model 학습
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True)
model.fit(X_train, y_train, epochs=300, validation_split=0.2, callbacks=[early_stop])
Epoch 1/300
7/7 [==============================] - 2s 248ms/step - loss: 572.1581 - accuracy: 0.0000e+00 - val_loss: 497.8600 - val_accuracy: 0.0000e+00
Epoch 2/300
7/7 [==============================] - 0s 19ms/step - loss: 516.3237 - accuracy: 0.0000e+00 - val_loss: 443.4315 - val_accuracy: 0.0000e+00
Epoch 3/300
7/7 [==============================] - 0s 18ms/step - loss: 454.6065 - accuracy: 0.0000e+00 - val_loss: 385.6940 - val_accuracy: 0.0000e+00
Epoch 4/300
7/7 [==============================] - 0s 19ms/step - loss: 390.3198 - accuracy: 0.0000e+00 - val_loss: 321.7114 - val_accuracy: 0.0000e+00

...

Epoch 79/300
7/7 [==============================] - 0s 8ms/step - loss: 5.6932 - accuracy: 0.0000e+00 - val_loss: 7.2688 - val_accuracy: 0.0000e+00
Epoch 80/300
7/7 [==============================] - 0s 8ms/step - loss: 5.9354 - accuracy: 0.0000e+00 - val_loss: 7.3990 - val_accuracy: 0.0000e+00
Epoch 81/300
7/7 [==============================] - 0s 8ms/step - loss: 5.5025 - accuracy: 0.0000e+00 - val_loss: 7.3694 - val_accuracy: 0.0000e+00
Epoch 82/300
7/7 [==============================] - 0s 8ms/step - loss: 5.8313 - accuracy: 0.0000e+00 - val_loss: 7.2677 - val_accuracy: 0.0000e+00
<tensorflow.python.keras.callbacks.History at 0x1e00b62b1f0>
# model 평가
>>> model.evaluate(X_test, y_test)
4/4 [==============================] - 0s 2ms/step - loss: 10.2492 - accuracy: 0.0000e+00
[10.24919319152832, 0.0]

 

 

 

 

5. 정리

  • 출력층에 활성화 함수로 기본값인 선형 함수(Linear)가 사용되었다.
  • 활성화 함수로 선형 함수가 사용되는 경우, 단순하게 이전 층에서 출력된 값에 대하여 선형 함수의 계수 값만큼만 곱하므로, 은닉층에서의 값을 보존한 상태로 출력하게 된다.
  • 참고: "딥러닝-3.0. 활성화함수(1)-계단함수와 선형함수"
  • 손실 함수로 평균 제곱 오차(MSE)를 사용하였다.
  • 참고: "딥러닝-5.1. 손실함수(2)-평균제곱오차(MSE)"
  • 회귀모형은 주어진 데이터가 어떠한 경향성을 갖고 있다는 가정하에 변수들 사이의 경향성을 가장 잘 설명할 수 있는 계수들을 찾아내는 것이다.
  • 우리는 딥러닝 모델을 사용해서 Feature로 독립변수들을 넣었고, Label에 종속변수를 넣었다.
  • 우리는 딥러닝 모델을 이용해 Feature가 Label에 대한 어떠한 경향성, 즉 패턴을 갖고 있다고 보고, 그 패턴을 나타내는 파라미터(weight, bias)를 찾아냈다.
  • 회귀분석은 일반적으로 최소제곱법(OLS)을 통해 최적의 가중치를 찾아내지만, 우리는 최적의 가중치를 경사 하강법과 역전파를 통한 학습으로 찾아내었다.
  • 출력층의 활성화 함수를 Sigmoid 함수로 사용하여 이진 분류 하면, 로지스틱 회귀모형(Logistic Regression model)이 된다.

 

 

 

 지금까지 딥러닝(Deep Learning)을 사용하여, 회귀모델을 만들어보았다. 앞서 "딥러닝을 통해 이런 것도 되는구나!"하고 넘어갔던 부분들이 사실은 회귀모델이고, 회귀모델의 아이디어와 딥러닝의 아이디어의 유사한 부분에서 꽤 재미를 느꼈을 것이다.

 다음 포스트에서는 Keras의 장점인 함수형 API를 이용해 모델을 만들어, 얕은 신경망(Wide model)과 깊은 신경망(Deep model)을 합친 Wide & Deep Learning에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

신경망 학습

 이전 포스트에서 다층 퍼셉트론에서 데이터가 흐르는 것에 대해 학습해보았고, 그 과정에서 석연치 않은 부분이 하나 있었다.

 바로 가중치가 이미 주어졌다는 것인데, 가중치를 저렇게 속 편하게 알고 있는 경우는 있을 수가 없으며, 그렇다고 가중치를 하나하나 찾아내는 것은 불가능에 가깝다.

 한 층의 노드 수는 입력되는 텐서의 크기이기 때문에 한층에 수백 수 천 개에 달하는 노드가 존재할 수 있으며, 그러한 층이 무수히 많이 쌓이게 된다면, 각 노드에서 다음 층의 노드로 연결되는 가중치 엣지의 수가 셀 수 없이 많아지므로, 일일이 이를 구해 입력된 데이터가 내가 전혀 알지 못하는 분류대로 나눠지게 만드는 것이 가능할 리가 없다.

 애초에 딥러닝이라는 기술은 엄청난 양의 데이터만 있고 거기에 숨겨진 함수 즉, 규칙을 모를 때 사용하는 것이며, 이 데이터 속에 막연한 현상이 숨어있을 것이라 추측하고 있는 상황에서, 어떻게 그 규칙을 찾아낼지도 모르고, 수많은 이론을 조합해 만들어낸 알고리즘이 정확할지도 모르기 때문에 사용하는 것이다.

 즉, 딥러닝은 순수하게 데이터만 가지고, 내가 분류하고자 하는 바에 가장 적합한 레이어를 쌓아 만들어낸 머신러닝 알고리즘에 데이터를 학습시켜, 최적의 가중치를 알아서 찾아내 모델을 만들어내고, 여기에 새로운 데이터들을 넣어 분류하는 것이다. 때문에 딥러닝을 데이터 주도 학습이라고도 한다.

 그렇다면, 어떻게 최적의 가중치를 찾을 수 있을까?

 

 

 

 

1. 손실 함수(Loss Function)

  • 자, 당신에게 1억 장에 달하는 고양이 사진과 강아지 사진이 있다고 생각해보자.
  • 당신은 고양이와 강아지를 구분할 수 있지만, 이 사진의 양이 지나치게 많아, 이걸 일일이 고양이와 강아지로 구분하는 것은 불가능하다.
  • 그렇다면, 당신이 만 장의 사진에 대해 고양이는 0, 강아지는 1이라 라벨(Label)을 붙였고(실제 값), 컴퓨터가 사진에서 찾아낸 특징을 기반으로 분류해낸 것(예측값)의 차이가 작다면, 최적의 가중치를 찾았다고 할 수 있지 않을까?
  • 바로 이 실제값과 예측값의 오차가 손실함수(Loss Function)다.
  • 오차가 클수록 손실 함수의 값이 커지고, 오차가 작아질수록 손실 함수의 값이 작아진다.
  • 즉, 이 손실 함수가 0에 가깝게 줄어들게 만드는 것이 학습의 목표라고 할 수 있다.
  • 손실함수는 이 오차를 비용이라고 판단하여, 비용함수(Cost Funtion)라고도 한다.

 

 

 

 

2. 최적화(Optimizer)

  • 자, 당신은 이제 손실 함수의 존재를 알았다. 그리고 손실함수를 이용해서 최적의 가중치를 찾을 수 있다고 했다.
  • 그렇다면, 어떻게 최적의 가중치를 찾아갈 수 있을까?
  • 먼저, 각 층에 임의의 가중치를 설정한다(보통 가중치는 0, 편향은 1로 설정한다.)
  • 학습 데이터셋을 모델에 통과시켜, 출력값을 계산한다.
  • 출력 값과 실제 값이 허용 오차 이내가 되도록 각층의 가중치를 업데이트한다.
  • 이 과정에서 출력 값과 실제값의 차이를 나타내는 지표로 사용되는 것이 손실함수다.
  • 손실함수를 최소화시키기 위해, 가중치의 미분(기울기)을 계산하고, 그 미분 값을 기반으로 가장 적합한 가중치 값을 갱신하는 과정을 반복한다.
  • 기울기를 기반으로 최적의 미분 값을 찾아가는 방식을 최적화(Optimizer)라고 하며, 그 유명한 경사하강법(Gradient Descent)이 여기에 해당한다.
  • 참고로 손실함수와 유사한 정확도(Accuracy)라는 것이 있는데, 손실함수는 연속적으로 변해 미분 가능하지만, 정확도는 가중치의 변화에 둔감하고, 불연속적으로 변해 미분이 불가능하여, 손실함수를 지표로 학습을 해나간다.
    (정확도는 출력된 값과 실제값이 일치하는 비율로, 나중에 텐서플로우로 실제 학습과 예측을 해보는 과정에서 다루도록 하겠다.)

 

 

 

 

3. 역전파(Back Propagation)

  • 당신은 최적화를 통해 최적의 가중치를 찾을 수 있다. 그렇다면, 어떻게 이 것을 모델에 반영해줄 것인가?
  • 역전파는 최적화를 효율적으로 할 수 있게 해주는 알고리즘으로, 순전파와 반대방향으로 실제값과 예측값의 오차를 전파하여, 가중치를 업데이트하고 최적의 학습 결과를 찾아간다.
  • 먼저 순전파를 통해 출력층에서 오차를 계산하고, 이를 다시 입력층으로 역전파시켜 가중치를 업데이트하고, 다시 입력값을 넣어 새로운 오차를 계산하고, 이를 또 역전파해서 가중치를 업데이트하는 것을 반복한다.
  • 즉, "순전파 > 역전파 > 가중치 업데이트 > 순전파 > 역전파 > 가중치 업데이트..."의 과정으로 학습은 이루어진다.

 

 

 

 

4.  정리해보면!

  • 손실함수(Loss Function): 가중치가 얼마나 잘 만들어졌는지를 확인하는 방법
  • 최적화(Optimizer): 손실함수를 기반으로 최적의 가중치를 찾는 방법
  • 역전파(Back Propagation): 가중치를 효율적으로 업데이트 하는 방법
  • 이 3가지 방법이 서로 앙상블을 이뤄 신경망에서 가장 적합한 가중치를 찾아낸다.

 

 

 

 지금까지 인공신경망을 학습시키는 3가지 개념에 대해 학습해보았다. 각 기법은 포스팅 하나로 설명하기엔 그 양이 활성화 함수 때처럼 만만치 않으므로, 하나하나 상세하게 다뤄보도록 하겠다.

 다음 포스트에서는 가장 대표적인 손실함수인 오차제곱합(Sum of Squareds for error, SSE)에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

인공신경망(ANN)과 다층 퍼셉트론(MLP)

 이전 포스트에서 단층 퍼셉트론이 행렬 연산을 통해 다음 노드로 정보를 전달하는 과정을 살펴보았다. 이전에 학습했었던 퍼셉트론과의 차이점은 활성화 함수로 계단 함수가 아닌 시그노이드 함수를 사용한 것이다.

 이렇게 활성화 함수에 정보를 전달하느냐 마느냐인 계단 함수를 넣는 것이 아니라, 시그노이드, 소프트맥스, 하이퍼볼릭 탄젠트, 렐루 등 다양한 활성화 함수를 넣고, 단층 퍼셉트론이 아닌, 다층 퍼셉트론을 만들고, 가중치를 인간이 수동으로 만드는 것이 아닌, 자동으로 가장 적합한 값을 찾아내는 것이 바로 인공 신경망이다.

 

 

 

 

1. 신경망의 구조

  • "입력층 - 출력층"만 존재하는 단층 퍼셉트론(Single Layer Perceptron, SLP)과 달리 "입력층 - m개의 은닉층 - 출력층"이 존재하는 다층 퍼셉트론(Multi Layer Perceptron) "n-층 신경망"이라고도 부르며, 일반적으로 단층 퍼셉트론처럼 입력층을 제외하고 부르거나, 입력층을 0층으로 생각하고 "n = m + 1"로 은닉층의 개수 + 출력층 1개로 명명한다.
  • 즉, 아래 다층 퍼셉트론은 3-층 신경망이라고 부른다.
  • 이렇게 은닉층이 2개 이상인 신경망을 심층 신경망(Deep Neural Network)라 한다.
  • 그러나, 간혹 다른 책에선 입력층까지 포함하여 m+2 층 신경망, 아래 예시에선 4-층 신경망이라 부르는 경우도 있다. 본, 블로그에서는 "은닉층의 수(m) + 출력층 1개"인 m+1층 신경망이라 부르도록 하겠다.

  • 입력층(Input Layer): 
    파란색 노드로, 학습 데이터셋(Train dataset)이 입력되는 곳이다. 학습 데이터의 Feature의 차원 수만큼의 뉴런 개수를 가진다. 입력층은 단 한층만 존재한다.

  • 은닉층(Hidden Layer):
    연두색 노드로, 입력층과 출력층 사이의 모든 층이다. 은닉층이라 불리는 이유는 입력층과 출력층은 Input 되는 Dataset과 Output 된 Dataset을 눈으로 확인할 수 있지만, 은닉층은 보이지 않기 때문이다.

  • 출력층(Output Layer):
    주황색 노드로, 출력하고자 하는 데이터의 형태에 따라 노드의 수가 바뀐다. 예를 들어, 0부터 9까지 10 종류의 숫자가 있다고 할 때, 이를 분류하고자 한다면, 출력층의 노드 수는 10개가 되며, 시그모이드 함수를 사용하여, 이진 분류를 하고자 하는 경우엔 노드의 수가 1개가 된다.

 

 

 

 

2. 다층 퍼셉트론의 연산

  • 다층 퍼셉트론의 연산 방식은 앞서 다뤘던 단층 퍼셉트론의 연산 방식과 동일하나, 더 많이 실시하게 된다.
  • 가중치를 알아서 찾아내는 방식은 뒤에서 다루도록 하고, 이번에는 가중치를 임의로 만들어보자.

$$ X = (x_1, x_2, x_3) = (10, 2) $$

$$ W_1=
\begin{pmatrix}
w_{11}^{(1)} & w_{21}^{(1)} & w_{31}^{(1)}\\ 
w_{12}^{(1)} & w_{22}^{(1)} & w_{32}^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.3 & 0.5\\ 
0.6 & 0.4 & 0.2 
\end{pmatrix} $$

$$W_2=
\begin{pmatrix}
w_{11}^{(2)} & w_{21}^{(2)}\\ 
w_{12}^{(2)} & w_{22}^{(2)}\\ 
w_{13}^{(2)} & w_{23}^{(2)}
\end{pmatrix}
=
\begin{pmatrix}
0.1 & 0.2\\ 
0.2 & 0.4\\
0.3 & 0.6
\end{pmatrix}$$

$$W_3=
\begin{pmatrix}
w_{11}^{(3)} & w_{21}^{(3)}\\ 
w_{12}^{(3)} & w_{22}^{(3)}\\ 
\end{pmatrix}
=
\begin{pmatrix}
0.2 & 0.4\\ 
0.4 & 0.8
\end{pmatrix}$$

$$B_1 = (b_{1}^{(1)}, b_{2}^{(1)}, b_{3}^{(1)})=(0.7, 0.6, 0.5)$$

$$B_2 = (b_{1}^{(2)}, b_{2}^{(2)})=(0.6, 0.4)$$

$$B_3 = (b_{1}^{(3)}, b_{2}^{(3)})=(0.5, 0.6)$$

  • 활성화 함수는 은닉층에서는 렐루 함수를 사용하고, 출력층에서는 시그모이드 함수를 사용해보자.
  • 즉, $f()$는 렐루 함수, $h()$는 시그모이드 함수이다.

 

 

 

 

3. 구현해보자.

  • 먼저 데이터를 생성하고, 데이터의 모양을 봐야 한다.
  • 기계학습에선 행렬곱이 주를 이르므로, 행렬의 모양을 파악하는 것이 최우선이다.
>>> X = np.array([10, 2])

>>> W1 = np.array([[0.1, 0.3, 0.5], [0.6, 0.4, 0.2]])
>>> W2 = np.array([[0.1, 0.2],[0.2, 0.4],[0.3, 0.6]])
>>> W3 = np.array([[0.2, 0.4],[0.4, 0.8]])

>>> B1 = np.array([0.7, 0.6, 0.5])
>>> B2 = np.array([0.6, 0.4])
>>> B3 = np.array([0.5, 0.6])

>>> print("X Shape:", X.shape)
>>> print("----"*20)
>>> print("W1 Shape:", W1.shape)
>>> print("W2 Shape:", W2.shape)
>>> print("W3 Shape:", W3.shape)
>>> print("----"*20)
>>> print("B1 Shape:", B1.shape)
>>> print("B2 Shape:", B2.shape)
>>> print("B3 Shape:", B3.shape)

X Shape: (2,)
--------------------------------------------------------------------------------
W1 Shape: (2, 3)
W2 Shape: (3, 2)
W3 Shape: (2, 2)
--------------------------------------------------------------------------------
B1 Shape: (3,)
B2 Shape: (2,)
B3 Shape: (2,)

  • 중요한 것은 노드와 가중치 엣지의 모양이다.
  • 편향은 위 노드와 가중치 엣지의 모양만 제대로 맞게 이루어져 있다면, 벡터 합이 당연히 되므로, 신경 쓰지 않아도 된다.
>>> def ReLU(x):
>>>     return np.maximum(0, x)

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


# 입력층에서 출력층 방향으로 계산된다.
>>> A1 =  np.dot(X, W1) + B1
>>> Z1 = ReLU(A1)

>>> A2 = np.dot(Z1, W2) + B2
>>> Z2 = ReLU(A2)

>>> A3 = np.dot(Z2, W3) + B3
>>> Y = sigmoid(A2)

>>> Y

array([0.97180471, 0.9981301 ])
  • 위 코드를 보면, 입력층 X에서 출발한 데이터가 "행렬곱 > 활성화 함수 > 행렬곱 > 활성화 함수 > 행렬곱 > 활성화 함수 > 출력"의 형태로 진행된 것을 알 수 있다.
  • 이를 보면, 단층 퍼셉트론을 활성화 함수만 바꾸면서 층을 쌓듯 여러 번 수행된 것을 알 수 있다.

 

 

 

 

4. 순전파(Forward Propagation)

  • 위 신경망에서 데이터의 흐름은 입력층에서 출력층의 방향으로 전달되었는데, 이를 순전파라고 한다.
  • 위에서 인공 신경망은 스스로 가장 적합한 가중치를 찾아간다고 하였는데, 이를 우리는 학습이라고 하며, 이 학습은 역전파(Back Propagation) 과정을 통해 이루어진다.
  • 만약 내가 신경망의 틀을 만들고, 그 신경망에서 가장 적합한 가중치를 찾는 것을, 이는 학습을 한다고 하며, 모델을 만든다라고 한다. 지금 같이 이미 가중치를 알고 있는 상태는 학습이 끝난, 모델이 완성된 상태이며, 이 모델에 데이터를 집어넣어, 그 결과를 확인하는 것은 순전파 되어 실행된다.

 

 

 

 지금까지 이미 가중치가 얻어진 다층 퍼셉트론(MLP)을 이용해, 신경망이 어떻게 연산되는지를 알아보았다. 신경망의 연산은 행렬곱과 활성화 함수 이 두 가지를 통해서 구해지는, 생각보다 단순한 알고리즘인 것을 알 수 있다. 

 다음 포스트에서는 그렇다면 대체 그 학습이라는 과정은 어떻게 이루어지는 지에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

인공신경망(Artificial Neural Network, ANN)

 지금까지 퍼셉트론의 개념과 노드에 전달되어 합쳐진 신호들이 다음 노드로 전달될 때, 값이 어떤 방법으로 전달될지를 결정하는 활성화 함수에 대해 학습해보았다.

 앞서 학습했던, 퍼셉트론은 계단 함수를 이용해서 신호를 전달할지(출력값 = 1), 전달하지 않을지(출력 값 = 0)를 정하였으며, XOR 게이트 실현에서 층을 여러 개 쌓자 단일층으로 해결하지 못했던 문제를 해결할 수 있었다.

 여기서, 활성화 함수의 존재와 층을 여러 개 쌓는다. 이 부분에 초점을 맞추면, 인공신경망을 만들 수 있고, 이 인공신경망을 구현하고, 가장 적합한 가중치를 알아서 찾아내는 것이 바로 딥러닝(Deep Learning)이다.

 본격적으로 신경망을 공부하기 전에 단층 퍼셉트론의 연산이 어떻게 이루어지는지 확인해보도록 하자. 단층 퍼셉트론의 연산 방법을 알게 되면, 다층 퍼셉트론의 구현은 이를 쌓아가기만 하면 된다.

 

 

 

1. m : 1 단층 퍼셉트론의 연산

  • 위 그림에서 정보가 전달되는 방식을 수식으로 적어보면 다음과 같다.

$$ Z = w_1*x_1 + w_2*x_2 +b*1 $$

$$ y = h(Z) $$

  • 여기서, $x_1=2, x_2=5, w_1 = 0.4, w_2 = 0.2, b = 0.7$이라고 가정해보자
  • 위 수식에서 $Z$를 가장 빠르게 출력할 수 있는 방법은 백터 연산이다.
  • 활성화 함수를 시그모이드 함수로 해서 구현해보자.
>>> import numpy as np

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

>>> x = np.array([2, 5, 1])
>>> w = np.array([0.4, 0.2, 0.7])
>>> Z = np.sum(x*w)
>>> sigmoid(Z)

0.9241418199787566
  • 도착점이 1개만 있는 경우, 입력층의 노드 수와 곱해지는 가중치 엣지의 수가 서로 동일하기 때문에, 길이가 동일한 벡터가 2개 나온다.
  • 때문에, 벡터연산으로 쉽게 해결할 수 있다.
    (numpy의 벡터 연산은 길이가 같은 벡터끼리 연산 시, 동일한 위치의 원소끼리 연산이 이루어지는 방식이다.)
  • m : 1 퍼셉트론은 이처럼 쉽게 연산이 가능했다. 그렇다면 입력층 노드의 수와 가중치 엣지의 수가 다른 m : n은 어떨까?

 

 

 

 

2. m : n 단층 퍼셉트론의 연산

  • 참고로 위에서 각 엣지(Edge)별 가중치에 써놓은 숫자들이 무슨 뜻인지 이해가 안 갈 수 있으니, 이를 간략히 설명해보겠다.

  • 가중치에서 위 괄호 안에 들어있는 값은 몇 번째 층(Layer)인지를 의미한다.
  • 아래 숫자는 앞은 다음 층, 뒤는 앞 층을 이야기한다.
  • 입력 노드의 값은 위에서부터 순서대로 1, 3, 5 라고 가정하자.
  • 가중치 엣지의 값은 위에서부터 순서대로 0.3, 0.5, 0.4, 0.2, 0.7, 0.3이라고 가정하자.
  • 편향 엣지의 값은 위에서부터 순서대로 0.2, 0.3이라 가정하자.
  • 이를, 벡터 연산으로 구하고자 한다면, 각 벡터의 길이가 다르고, 이를 잘라서 $y_1$, $y_2$를 따로따로 연산하기엔 시간도 많이 걸리고 공식도 지저분해진다.

 

 

 

 

3. 행렬 곱

  • 위와 같은 m : n 퍼셉트론은 행렬 곱을 사용한다면, 한방에 계산을 할 수 있다.
  • 위 퍼셉트론을 수식으로 간소화하면 다음과 같다.

$$ Y = WX + B$$

$$ X = (x_1, x_2, x_3) = (1, 3, 5)$$

$$ B = (b_{1}^{(1)}, b_{2}^{(1)}) = (0.2, 0.3)$$

$$ W=
\begin{pmatrix}
w_{11}^{(1)} & w_{21}^{(1)}\\ 
w_{12}^{(1)} & w_{22}^{(1)}\\ 
w_{13}^{(1)} & w_{23}^{(1)}
\end{pmatrix}
=
\begin{pmatrix}
0.3 & 0.5\\ 
0.4 & 0.2\\ 
0.7 & 0.3
\end{pmatrix} $$

  • 행렬 연산을 하기 위해선, 가장 먼저 배열이 어떻게 생겼는지를 확인해야 한다.
>>> X = np.array([1, 3, 5])
>>> B = np.array([0.2, 0.3])
>>> W = np.array([[0.3, 0.5],[0.4, 0.2],[0.7, 0.3]])

>>> print("X shape:", X.shape)
>>> print("B shape:", B.shape)
>>> print("W shape:", W.shape)

X shape: (3,)
B shape: (2,)
W shape: (3, 2)
  • 여기서 우리는 X와 W를 행렬곱할 것이다.
  • 행렬곱을 간단하게 짚고 가자면 다음과 같다.

  • 위 행렬 곱 방법을 보면, 서로 곱해지는 행렬에서 빨간 부분이 일치해야만 곱해지며, 출력되는 행렬의 녹색 부분이 행의 수, 파란색이 열의 수를 결정한다.
  • 여기서 지금까지의 행렬에 대한 인식을 조금만 틀어보자.
  • 행 = 데이터의 수
  • 열 = 변수의 수
  • 앞으로 이 인식을 하고 행렬 곱을 생각하게 된다면, 뒤에서 나올 n-차원 텐서의 연산에 대해서도 쉽게 이해할 수 있을 것이다(이에 대한 상세한 내용은 나중에 이야기하겠다.)
  • 자, 위 수식을 함수로 구현해보자.
# 단층 퍼셉트론 연산
>>> Z = np.dot(X, W) + B
>>> Z
array([5.2, 2.9])
  • np.dot(A,B): 행렬 A와 행렬 B를 행렬 곱한다.
  • 편향인 B는 행렬 X와 W의 곱과 길이가 동일한 벡터이므로(다음 노드의 수와 동일하다), 쉽게 벡터 합이 된다.
  • 신경망에서 신호가 흘러가는 것은 행렬 연산을 통해 진행되며, 때문에 딥러닝에서 행렬 연산에 매우 유리한 GPU가 사용되는 것이다.
  • 각 노드별 합산된 결과를 시그모이드 함수를 통해서 출력해보자.
# 시그모이드 함수를 활성화 함수로 사용
>>> sigmoid(Z)
array([0.9945137 , 0.94784644])

 

 

 

 

 지금까지 단층 퍼셉트론(SLP)을 이용해서 신경망에서 연산이 어떻게 이루어지는지 확인해보았다. 다음 포스트에서는 다층 퍼셉트론(MLP)을 이용해서 신경망 연산을 학습해보도록 하자.

728x90
반응형
728x90
반응형

 지난 포스트의 머신러닝, 딥러닝에 대한 설명이 잘 와 닿지 않았을 수 있다. 그러나 퍼셉트론(Perceptron)에 대해 학습해보면, 어떤 과정을 통해서 머신러닝이 이루어지고, 기계 학습이라는 단어의 학습이 정확히 무엇을 의미하는지 알 수 있을 것이다.

 

 

1. 신경 세포

 머신러닝 공부를 해보면 신경망(Neural Network)라는 단어를 종종 볼 수 있었을 것이다. 그리고 머신러닝에 사용되는 인공신경망이 사람의 신경을 흉내 내어 만들어졌다는 글도 나오는데, 대체 이게 무슨 소리일까?

ZUM 학습백과. 뉴런의 구조(http://study.zum.com/book/11779)

  • 위 그림은 인간의 신경 세포인 뉴런이다.
  • 뉴런은 가지돌기(수상돌기)에 여러 신호가 도착하면 신경 세포체에서 이를 합치고, 합쳐진 신호가 특정 임계 값을 넘으면, 출력 신호가 생성되어 축삭 돌기를 통해 다음 뉴런으로 신호가 전달된다.
  • 여기서 중요한 포인트는 다음과 같다.
    • A. 여러 신호가 신경 세포체로 전달된다.
    • B. 전달된 신호는 하나로 합쳐진다.
    • C. 합쳐진 신호가 특정 임계값을 넘으면, 전달된다.
  • 나중에 다룰 이야기지만, 위 이야기에서 정보가 하나로 합쳐져서 전달되는 과정이 활성화함수(Activation function)를 나타내는 부분이라고 생각하자(이 건 나중에 다시 이야기하니깐 대충 넘기자)

 

 

 

 

2. 퍼셉트론(Perceptron)

  • 퍼셉트론은 위에서 나온 신경 세포를 알고리즘화한 것으로, 딥러닝의 기본이 되는 개념이다.
  • 퍼셉트론은 N개의 입력 받아 1개의 신호를 출력한다.
  • 퍼셉트론은 받아들여 합친 신호가 임계값보다 크면 1을 출력하고, 임계값보다 작으면 0을 출력하여, 정보를 전달하거나 전달하지 않는다.

입력이 2개인 퍼셉트론

  • 위 퍼셉트론 그림에서 원은 노드(Node) 혹은 뉴런(Neuron)이라고 부르며, 선은 엣지(Edge)라고 부른다.
  • 노드에서 다음 노드로 정보가 전해질 때, 가중치($w_1$, $w_2$)가 곱해져 전달된다.
  • 전달되는 값과 가중치가 곱해진 값($w_1x_1$)의 합($w_1x_1 + w_2x_2$)이 임계값 $\theta$(theta)보다 크면 1이, 작으면 0이 출력되는데, 이는 위에서 언급한 신경세포에서 신호가 전달되는 과정을 그대로 따라한 것과 같다.
  • 이를 수식으로 나타내면 다음과 같다.

$$y = \begin{cases}
 0 \ \ \ (w_1x_1 + w_2x_2 \leq \theta) \\ 
 1 \ \ \ (w_1x_1 + w_2x_2 > \theta)
\end{cases}$$

  • 위 함수를 보면, 위 신경세포에서 설명한 신호가 움직이는 과정을 쉽게 표현한 것임을 알 수 있다.

 

 

 

3. 퍼셉트론의 구현

  • 앞서 입력 노드가 2개인 퍼셉트론의 함수를 적어보았다. 만약, 위 함수를 파이썬 코드로 구현한다면, 그것이 바로 퍼셉트론을 구현하는 것이 아니겠는가. 한 번 만들어보도록 하자.
# Perceptron 함수를 코드로 구현해보자
>>> def Perceptron(x1, x2, w1, w2, theta):

>>>     y = w1*x1 + w2*x2
            
>>>     if y <= theta:
>>>         return 0
                
>>>     elif y > theta:
>>>         return 1
  • $x_1$과 $x_2$는 퍼셉트론의 노드이므로, 0과 1의 값만 나올 수 있다.
  • $x_1=0,\ x_2=1,\ w_1=0.1,\ w_2=0.3,\ theta=0.6$으로 함수를 실행해보자 
>>> Perceptron(x1=0, x2=1, w1=0.1, w2=0.3, theta=0.6)
0
  • 위 결과를 보면, 0*0.1 + 1*0.3 <= 0.6으로 나와 0이 반환된 것을 알 수 있다.
  • 이를 보면, 가중치는 해당 노드에서 전달되는 정보의 중요도를 어느 정도나 강하게 줄 것인지를 따지는 것이고, 임계값은 받아들인 신호를 얼마나 타이트하게 평가할 것인가를 판단하는 수단이라는 것을 알 수 있다.

 

 

 지금까지 신경망의 기본이 되는 신경 세포, 퍼셉트론의 기본 개념에 대해 학습해보았다. 다음 학습에선 퍼셉트론을 이용하여 단순한 논리 회로를 적용하는 방법에 대해 학습해보도록 하겠다.

 

 

 해당 포스팅은 사이토 고키의 "밑바닥부터 시작하는 딥러닝"책을 참고하여 작성하였다. 해당 책은 꽤 얇고 내용도 이해하기 쉽게 써놨으므로, 머신러닝을 시작하는 초보가 읽기에 괜찮은 책이다. 시간 날 때, 한 번 읽어보기 바란다(참고로 PPL이 아니다! 내 돈 주고 내가 산 괜찮은 책 소개다!).

728x90
반응형

+ Recent posts