본문 바로가기

Data Science/Machine Learning 구현

[ML] Tensorflow를 이용해 Deep Neural Network 구현하기

반응형

🔉해당 자료 내용은 순천향대학교 빅데이터공학과 김정현 교수님의 수업자료에 기반하였으며 수업자료의 저작권 문제로 인해 수업자료를 직접 이용하지 않고 수업자료의 내용을 참고하여 본인이 직접 작성하였으므로 저작권 문제가 발생하지 않음을 필히 알려드립니다. 

 

이번 포스팅에서는 그동안 Tensorflow에서 구현해보았던 단층 퍼셉트론이 아닌 2개 이상의 층(layer)으로 구성된 다층 퍼셉트론, 이른바 '딥러닝'이라고 불리는 모델을 구현해보려 한다. 물론 여기서 딥(Deep)하다는 것이 특정 몇 개 이상의 층으로 구성되어야 딥러닝이라고 할 수 있는 절대적 기준이 있는 것은 아니다. 하지만 아직 텐서플로우를 활용해 딥러닝을 구현해보는 기초적인 과정이므로 layer가 2개일 경우만을 소개한다는 점 참고해 주길 바란다. 

 

딥한(깊은) 정도는 상대적인 척도가 아닐까?

 

DNN(Deep Neural Network) 구현은 텐서플로우에서 자체적으로 제공하는 손글씨 데이터 MNIST 데이터를 활용하려고 한다. 본격적인 실습코드로 가기 전에 단층 퍼셉트론을 두고 굳이 다층 퍼셉트론, 딥러닝을 사용하는 이유가 무엇일까? 그 이유는 바로 단층 퍼셉트론은 XOR 문제를 해결할 수 없기 때문이다. 

 

XOR 문제

 

위 그림 처럼 1번째, 2번째와 같이 데이터가 주어져 있을 경우, AND, OR 연산으로 두 클래스(까만 동그라미, 흰 동그라미)를 분류할 수 있는 분류 경계선(Decision boundary)을 만들 수 있다. 하지만 가장 오른쪽 처럼 데이터가 주어져 있을 경우, AND, OR 연산으로만 해결 할 수 없는 문제가 발생한다. 이 때가 바로 XOR 문제라고 정의한다.

 

포인트는 우리 일상에서 발생하는 데이터의 분포 형태가 XOR 문제처럼 주어져 있는 경우가 빈번하다는 것이다. "AND 또는 OR 연산 한 번을 계산하여 XOR 문제를 해결할 수 없다면 AND, OR 연산을 2개, 또는 3개 이상으로 결합해서 해결할 수 있지 않을까?" 에서 다층 퍼셉트론 아이디어가 기원한 것이라고 생각한다.

 

우선 간단한 실습으로 XOR 문제를 직접 정의해서 층이 2개인 다층 퍼셉트론으로 XOR 문제를 직접 해결해보자.

 

import tensorflow as tf
import numpy as np

x_data = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=np.float32)
y_data = np.array([[0],[1],[1],[0]], dtype=np.float32)

X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)
# 1-layer : feature 2개를 입력으로 받아서 output size를 2개로 출력
W1 = tf.Variable(tf.random_normal([2,2]), name='weight1')
# output size가 2개이므로 bias값도 2개의 요소로 이루어져 있음!
b1 = tf.Variable(tf.random_normal([2]), name='bias1')
# 1-layer에 활성함수 적용
layer1 = tf.sigmoid(tf.matmul(X, W1) + b1)

# 2-layer : 1-layer의 Output size가 2였기 때문에 2-layer input은 2개, Output은 1개로!
W2 = tf.Variable(tf.random_normal([2,1]), name='weight2')
# 2-layer의 output size가 1이기 때문에 1개의 요소로 이루어지도록!
b2 = tf.Variable(tf.random_normal([1]), name='bias2')
# 1-layer을 거쳐 활성함수 적용한 layer1변수와 W2를 내적하여 활성함수 씌우기!
hypothesis = tf.sigmoid(tf.matmul(layer1, W2) + b2)

# cost function으로 binary cross-enropy텐서 생성
cost = -tf.reduce_mean(Y * tf.log(hypothesis) + (1-Y) * tf.log(1-hypothesis))
# cost값을 SGD방식으로 minimize시키는 텐서 생성
train = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)

# 예측값 반환하는 텐서 생성 - hypothesis가 float형인데, 0.5값보다 크면 1로 작으면 0으로 반환
predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
# 정확도 측정하는 텐서 생성 - equal을 사용해 예측,실제값 비교 후 Boolean값(0,1)을 float형으로 casting함. 그러고 평균값을 취하기
accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, Y), dtype=tf.float32))

# 생성한 텐서 run 시키기
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for step in range(10001):
        # 데이터를 feed해서 SGD사용해 cost값 최소화시키기
        sess.run(train, feed_dict={X:x_data, Y:y_data})
        if step % 100 == 0:
            # 100번 수행할 때마다 step과 cost값 계산해 출력
            print(step, sess.run(cost, feed_dict={X:x_data, Y:y_data}))
            # 업데이트되는 파라미터 W1, W2값도 출력해보기
            print(sess.run(W1))
            print(sess.run(W2))

    # 모든 학습과정을 마친 후 최종 예측값(0.5이상으로 cast하기전), cast한 최종예측값, 실제값, 정확도 출력
    y_, y_pred, acc = sess.run([hypothesis, predicted, accuracy], feed_dict={X:x_data, Y:y_data})
    print("# cast되기전 예측값:\n", y_)
    print("# cast한 최종예측값:\n", y_pred)
    print("# 실제값:\n", y_data)
    print("# 정확도: ", acc)

 

결과값에서 중간에 step과 파라미터값(W1, W2)을 출력하는 화면을 제외하고 최종 결과값만을 살펴보자.

 

층이 2개인 다층퍼셉트론의 학습 결과

 

위 그림의 'cast되기전 예측값'은 입력 데이터가 다층 퍼셉트론으로 들어가 나오게 된 결과값이며 그래서 실수(float) 형태를 띄고 있다. 이 실수형의 데이터를 0.5라는 임계치를 기준으로 분류한 최종 예측값이 바로 'cast한 최종예측값'이다. 이제 이 'cast한 최종예측값'과 원본 데이터의 종속변수인 '실제값'과 비교를 해보자. 두 개의 값이 서로 일치하며 다층 퍼셉트론의 예측 정확도가 100%인 것을 알 수 있다.

 

이제 그렇다면 MNIST 손글씨 데이터를 활용해서 2층으로 이루어진 다층 퍼셉트론을 사용해 예측 결과값을 출력해보자. 참고로 위 XOR문제를 해결하기 위해서는 이진 분류이기 때문에 최종 활성함수를 Sigmoid로 사용했지만, MNIST 데이터는 다중 클래스 분류 문제이기 때문에 최종 활성함수를 Softmax를 이용했다는 것을 잊지말자.

 

from tensorflow.examples.tutorials.mnist import input_data
# one_hot=True인자로 데이터를 원-핫 인코딩된 상태로 불러오기
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

import tensorflow as tf

# 난수 시드 생성
tf.set_random_seed(42)
# 다중 클래스 개수 
num_classes = 10

X = tf.placeholder(tf.float32, shape=[None, 28*28])
Y = tf.placeholder(tf.float32, shape=[None, num_classes])

# 1-layer: 28*28개의 input size가 들어와서 250개의 output개수가 나감
W1 = tf.Variable(tf.random_normal([28*28, 250]), name='weight1')
# 1-layer의 output size개수에 맞추어서 설정!
b1 = tf.Variable(tf.random_normal([250]), name='bias1')
# 1-layer에 sigmoid활성함수 적용
layer1 = tf.sigmoid(tf.matmul(X, W1) + b1)

# 2-layer: 1-layer의 output개수를 input size로 넣고 output size는 클래스 개수로 설정해 최종 output size정의
W2 = tf.Variable(tf.random_normal([250, num_classes], name='weight2'))
# 2-layer의 output개수인 클래스 개수로 설정
b2 = tf.Variable(tf.random_normal([num_classes]), name='bias2')
# 2-layer의 활성함수 적용 - 다중클래스이기 떄문에 softmax function 적용
hypothesis = tf.nn.softmax(tf.matmul(layer1, W2) + b2)

# cost function 텐서정의 - multi-cross-entropy
cost = -tf.reduce_mean(tf.reduce_sum(Y * tf.log(hypothesis), axis=1))
# SGD로 cost값 minimize하는 텐서정의
train = tf.train.GradientDescentOptimizer(learning_rate=0.01).minimize(cost)

# argmax로 예측값 부여 후 실제값이랑 일치하는지 equal로 boolean값 반환
is_correct = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(Y, 1))
# 정확도 측정
accuracy = tf.reduce_mean(tf.cast(is_correct, dtype=tf.float32))

# Epoch와 Batch size설정
num_epoch = 15
batch_size = 100
num_iter = int(mnist.train.num_examples / batch_size)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # 처음 반복문 : epoch
    for epoch in range(num_epoch):
        # 모든 epoch 수행 후 평균 cost값 생성하기 위해
        avg_cost = 0
        # 두번째 반복문 : batch_size를 갖고 반복하는 횟수
        for i in range(num_iter):
            # batch_size만큼 MNIST데이터 분할
            batch_x, batch_y = mnist.train.next_batch(batch_size)
            # SGD 텐서 수행
            _, cost_val = sess.run([train, cost], feed_dict={X:batch_x, Y:batch_y})
            # Epoch 1번 수행한 후 평균 cost값
            avg_cost += cost_val / num_iter
        print(f"## Epoch횟수: {epoch+1}, 1번 Epoch한 후 평균 cost값: {avg_cost:.3f}")
        print()
    print('-'*10, "훈련 데이터로 학습 종료 후 테스트 데이터로 평가 후 정확도", '-'*10)
    acc = sess.run(accuracy, feed_dict={X:mnist.test.images, Y:mnist.test.labels})
    print(f"테스트 데이터에 대한 정확도: {acc:.3f}")

 

XOR 실습과 달리 추가적으로 적용한 점은 바로 Epoch와 Batch size를 적용한 것이다. Epcoh와 Batch size에 대한 개념은 여기를 참고하자. 간단한 내용이니 모른다면 꼭 읽기를 추천한다.

 

결과값은 다음과 같다.

 

MNIST 데이터 다층 퍼셉트론 예측 결과

 

예측 정확도가 81.9%로 도출됬다. 물론 하이퍼 파라미터 튜닝을 하기 전이며 Epoch, Batch size횟수, 1번째 층에서 적용된 활성함수의 종류(위에선 Sigmoid를 사용했지만 Relu 함수, 탄젠트 하이퍼폴릭, 등 기타 함수)를 다르게 사용하거나 아니면 아예 층(layer) 개수를 늘린다거나 하는 추가적인 모델링을 적용해 예측 성능을 높일 수 있을 것이다.

 

반응형