신경망 모델 코드로 구현하기

  • 딥러닝 모델 구현 시, 1개의 층을 코드로 작성하면 다음과 같다.
    • TensorFlow
W = tf.Variable(tf.truncated_normal([n_in, n_hidden], stddev=0.01))
b = tf.Variable(tf.zeros([n_hidden]))
h = tf.nn.relu(tf.matmul(x, W) + b)
h_drop = tf.nn.dropout(h, keep_prob)
    • Keras
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(Activation('relu'))
model.add(Dropout(0.5))
  • 딥러닝 모델에서 층을 늘려갈 떄에는 위의 코드를 반복적으로 사용하여 구현하였다.


효율적으로 신경망 모델 구현하기

  • 층의 개수를 더 늘리고 싶거나 모델의 일부분을 변경(예를 들어 활성화 함수를 다른 것으로 변경)하고 싶을 때 유연하게 적용할 수 있도록 신경망 모델을 설계해보자

TensorFlow 설계 방법

  • 신경망 모델을 정의하는 전체적인 과정은 그림 10.1과 같다.

그림 10.1 드롭아웃이 적용된 신경망의 예

  • TensorFlow에서는 위의 과정을 단계별로 함수화할 것을 권장하고 있으며 각각 inference(), loss(), training() 함수로 정의해 사용해야 한다.
    • inference() : 모델 전체를 설정하고 모델의 출력(예측 결과)를 반환하는 함수
    • loss() : 모델의 오차 함수를 정의하고 오차를 반환하는 함수
    • training() : 모델을 학습시키고 학습시킨 결과(진행 상황)을 반환하는 함수
  • 전체적인 코드의 구성은 다음과 같다.
def inference(x):
    # 모델을 정의

def loss(hypothesis, y):
    # 오차함수를 정의

def training(loss):
    # 학습 알고리즘을 정의

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    # 2. 모델을 설정한다
        hypothesis = inference(x)
        loss = loss(hypothesis, y)

    # 3. 모델을 학습시킨다
        train = training(loss)

    # 4. 모델을 평가한다


  • hypohesis = inference(x) 함수 설계
    • 지금까지 각 층을 h0, h1 등으로 함수를 정의했지만 이제는 함수들을 한꺼번에 정의하기 위해 각 층에 있는 뉴런의 개수를 인수로 받게 만들어야 한다.
    • 드롭아웃을 지원하기 위해 x 이외에 keep_prob 변수를 인수로 받아야 하기 때문에 다음과 같이 코드를 작성한다.
def inference(x, keep_prob, n_in, n_hiddens, n_out):
    # 모델을 정의

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    # 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)

    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    # 2. 모델을 설정한다
    # 입출력 데이터와 은닉층의 뉴런 개수 설정
    n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
    n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
    n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
    p_keep = 0.5        # 드롭아웃 확률의 비율

    x = tf.placeholder(tf.float32, shape=[None, n_in])
    y = tf.placeholder(tf.float32, shape=[None, n_out])

    keep_prob = tf.placeholder(tf.float32)

    # 가설식을 그래프에 추가
    hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)
    • 입력층에서 출력층의 바로 앞 층까지는 모든 출력을 같은 식으로 나타낼 수 있지만 출력층만은 활성화 함수가 소프트맥스 함수(또는 시그모이드 함수)로 지정돼 있어 드음과 같이 코드를 작성한다.
      • weight_variable()bias_variable()을 정의해, 웨이트와 바이어스를 초기화 처리를 한다.
      • for 문의 내부에서 '입력층 - 은닉층'과 '은닉층-은닉층'은 각각 처리가 다르지만 기본적으로 이전 층의 output이 다음 층의 input이 되는 구조를 구현
def inference(x, keep_prob, n_in, n_hiddens, n_out):
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    # 입력층-은닉층과 은닉층-은닉층 설계
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:  # 입력층-은닉층
            input = x
            input_dim = n_in
        else:   # 은닉층-은닉층
            input = output  # 출력층이 다음 입력층이 된다
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])  # weight의 shape은 입력층 차원(784) x 뉴런의 개수
        b = bias_variable([n_hidden])               # bias의 shape은 뉴런의 개수

        hidden_layer = tf.nn.relu(tf.matmul(input, W) + b)  # 은닉층의 활성화 함수로 ReLU를 사용
        output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob)   # 출력은 드롭아웃을 적용

    # 은닉층-출력층 설계
    W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개)
    b_out = bias_variable([n_out])                  # bias의 shape은 출력층의 차원

    hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out)    # 출력 활성화 함수는 softmax를 사용
    
    return hypothesis


  • loss(hypothesis, y) 함수와 training(loss) 함수 설계
def loss(hypothesis, y):
    cross_entropy = tf.reduce_sum(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))

    return cross_entropy

def training(loss):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)

    return train


  • 모델 평가를 위한 accuracy(hypothesis, y) 함수 설계
def accuracy(hypothesis, y):
    correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    
    return accuracy


  • 전체 코드
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
import numpy as np
import tensorflow as tf

def inference(x, keep_prob, n_in, n_hiddens, n_out):
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    # 입력층-은닉층과 은닉층-은닉층 설계
    for i, n_hidden in enumerate(n_hiddens):
        if i == 0:  # 입력층-은닉층
            input = x
            input_dim = n_in
        else:   # 은닉층-은닉층
            input = output  # 출력층이 다음 입력층이 된다
            input_dim = n_hiddens[i-1]

        W = weight_variable([input_dim, n_hidden])  # weight의 shape은 입력층 차원(784) x 뉴런의 개수
        b = bias_variable([n_hidden])               # bias의 shape은 뉴런의 개수

        hidden_layer = tf.nn.relu(tf.matmul(input, W) + b)  # 은닉층의 활성화 함수로 ReLU를 사용
        output = tf.nn.dropout(hidden_layer, keep_prob=keep_prob)   # 출력은 드롭아웃을 적용

    # 은닉층-출력층 설계
    W_out = weight_variable([n_hiddens[-1], n_out]) # 출력층의 weight shape은 뉴런의 개수 x 출력층의 차원(10개)
    b_out = bias_variable([n_out])                  # bias의 shape은 출력층의 차원

    hypothesis = tf.nn.softmax(tf.matmul(output, W_out) + b_out)    # 출력 활성화 함수는 softmax를 사용

    return hypothesis

def loss(hypothesis, y):
    # cross entropy
    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))

    return cross_entropy

def train(loss):
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
    train = optimizer.minimize(loss)

    return train

def accuracy(hypothesis, y):
    correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return accuracy

if __name__ == '__main__':
    # 1. 데이터를 준비한다
    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    # 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

    # 학습 데이터와 테스트 데이터를 80:20으로 나눈다
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)

    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    # 2. 모델을 설정한다
    # 입출력 데이터와 은닉층의 뉴런 개수 설정
    n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
    n_hiddens = [200, 200, 200]  # 각 은닉층의 뉴런 개수
    n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
    p_keep = 0.5        # 드롭아웃 확률의 비율

    x = tf.placeholder(tf.float32, shape=[None, n_in])
    y = tf.placeholder(tf.float32, shape=[None, n_out])

    keep_prob = tf.placeholder(tf.float32)

    # 가설식을 그래프에 추가
    hypothesis = inference(x, keep_prob, n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)

    # 오차 함수를 그래프에 추가
    loss = loss(hypothesis, y)

    # 학습 함수를 그래프에 추가
    train = train(loss)

    # 정확도 예측 함수 그래프에 추가
    accuracy = accuracy(hypothesis, y)

    # 3. 모델을 학습시키면서 그래프를 실행
    epochs = 50
    batch_size = 200

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)

    n_batches = N_train // batch_size

    for epoch in range(epochs):
        X_, Y_ = shuffle(X_train, Y_train)

        for i in range(n_batches):

            start = i * batch_size
            end = start + batch_size

            sess.run(train, feed_dict={x: X_[start: end], y: Y_[start: end], keep_prob: p_keep})

        accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_val, y: Y_val, keep_prob: p_keep})
        print(f'Validation accuracy rate per epoch: {accuracy_rate}')

    # 4. 모델을 평가한다
    accuracy_rate = accuracy.eval(session=sess, feed_dict={x: X_test, y: Y_test, keep_prob: 1.0})
    print(f'Test Accuracy : {accuracy_rate}')


Keras 설계 방법

  • Keras는 단순하게 구현하려고 사용하는 것이기 때문에 TensorFlow에서 했던 것처럼 구현 방침같은 것이 정해져 있지 않다.
  • Keras에서는 각 층을 한꺼번에 정의할 수 있기 때문에 모델을 설정하는 전체적인 코드는 다음과 같다.
    • for 문의 내부에서 ([n_in] + n_hiddens)[:-1]을 반복하여 출력층 바로 앞까지의 각 층의 입력과 출력의 차원을 Dense()에 넘겨준다.
    • '입력층 - 은닉층'과 '은닉층-은닉층'은 각각 처리가 다르지만 기본적으로 이전 층의 output이 다음 층의 input이 되는 구조를 구현
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.optimizers import SGD

MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

n = len(MNIST.data)
N = int(n * 1.00)  # 70,000개의 데이터를 모두 사용
N_train = int(n * 0.2)
N_val = int(N_train * 0.2)

# 학습 데이터와 테스트 데이터를 나누기 전에 입력 데이터를 무작위로 섞는다
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]  # 1-of-K hot coding 표현으로 변환

# 학습 데이터와 테스트 데이터를 80:20으로 나눈다
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)


n_in = len(X[0])  # 입력 데이터 이미지의 크기 28x28(=784)
n_hiddens = [200, 200]  # 각 은닉층의 뉴런 개수
n_out = len(Y[0])  # 출력 데이터의 개수 (0~9)
activation = 'relu'
p_keep = 0.5        # 드롭아웃 확률의 비율

model = Sequential()

for i, input_dim in enumerate(([n_in] + n_hiddens)[:-1]):
    model.add(Dense(input_dim=input_dim, units=n_hiddens[i]))
    model.add(Activation(activation))
    model.add(Dropout(p_keep))

model.add(Dense(units=n_out))
model.add(Activation(activation))

model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy'])

epochs = 50
batch_size = 200

model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size)
loss_and_metrics = model.evaluate(X_test, Y_test)
print(f'\nLoss : {loss_and_metrics[0]:6}')
print(f'Accuracy : {loss_and_metrics[1]*100:.7}%')


TensorFlow에서 신경망 모델을 클래스로 설계하기

  • TensorFlow에서는 각각의 과정을 inference(), loss(), training()이라는 함수 3개로 분리했기 떄문에 구현하기가 쉬워졌다.
  • 모델을 실제로 학습을 시키는 부분은 이 3개의 함수에 포함되어 있지 않기 때문에 main 함수 부분에 코드로 작성할 내용이 많아질 수 있기 때문에 학습시키는 부분도 포함해 모든 것을 하나의 클래스로 정리해놓으면 아래 코드처럼 Keras와 비슷하게 코딩을 할 수 있다.
model = DNN()
model.fit(x_train, y_train)
model.evaluate(x_test, y_test)


  • 클래스를 정의하는 전체적인 코드는 다음과 같다.
class DNN(objec):
    def __init__(self):
        # 초기화 처리

    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    def bias_variable(self, shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    def inference(self, x, keep_prob):
        # 모델을 정의한다
        return hypothesis

    def loss(self, hypothesis, y):
        cross_entropy = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))
        return cross_entropy
    
    def training(self, loss):
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
        train = optimizer.minimize(loss)
        return train
    
    def accuracy(self, hypothesis, y):
        correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        return accuracy
    
    def fit(self, x_train, y_train):
        # 학습 처리

    def evaluate(self, x_test, y_test):
        # 예측 결과 처리


신경망 모델 초기화

  • 신경망 모델 초기화 부분에서는 모델의 구성을 결정하는 것이 바람직하므로 각 층의 차원을 매개변수로 받아야 한다.
  • 모델의 구성도 결정해야 하기 때문에 각 층에 관련된 weight와 bias로 정의한다.
class DNN(objec):
    def __init__(self, n_in, n_hiddens, n_out):
        self.n_in = n_in
        self.n_hiddens = n_hiddens
        self.n_out = n_out
        self.weight = []
        self.bias = []


inference() 함수 정의

  • 초기화 부분에 따라 inference() 함수는 다음과 같이 구현할 수 있다. 기존 코드에서는 각 층의 차원 수를 매개변수를 통해 받아들였지만 self.n_in 등을 통해 대체할 수 있게 되었다.
class DNN(objec):
    def inference(self, x, keep_prob):
        # 입력층 - 은닉층, 은닉층 - 입력층
        for i, n_hidden in enumerate(self.n_hiddens):
            if i == 0:
                input = x
                input_dim = self.n_in
            else:
                input = output
                input_dim = self.n_hiddens[i-1]

        self.weight.append(self.weight_variable([input_dim, n_hidden]))
        self.bias.append(self.bias_variable([n_hidden]))

        h = tf.nn.relu(tf.matmul(input, self.weight[-1]) + self.bias[-1])
        output = tf.nn.dropout(hypothesis, keep_prob)

        # 은닉층 - 출력층
        self.weight.append(self.weight_variable([self.n_hiddens[-1], self.n_out]))
        self.bias.append(self.bias_variable([self.n_out]))

        hypothesis = tf.nn.softmax(tf.matmul(output, self.weight[-1]) + self.bias[-1])

        return hypothesis


fit() 함수 정의

  • Keras처럼 학습 데이터와 epoch 수, batch size 등 매개변수를 받아들일 수 있어야 한다. 또한 dropout 변수도 사용할 수 있어야 한다.
class DNN(objec):
    def fit(self, X_train, Y_train, epochs=100, batch_size=100, p_keep=0.5, verbose=1):
        # 학습처리
        x = tf.placeholder(tf.float32, shape=[None, self.n_in])
        y = tf.placeholder(tf.float32, shape=[None, self.n_out])
        keep_prob = tf.placeholder(tf.float32)

        # 예측을 위한 evauate() 함수용으로 작성해둔다
        self._x = x
        self._y = y
        self._keep_prob = keep_prob

        # 학습을 위한 오차 함수 및 예측 정확도 함수 그래프 생성
        hypothesis = self.inference(x, keep_prob)
        loss = self.loss(hypothesis, y)
        train = self.train(loss)
        # evaluate() 함수에서 참조할 수 있도록 함
        self.accuracy = self.accuracy(hypothesis, y)

        # 그래프 초기화
        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

        # 예측을 위한 evaluate() 함수용으로 작성해둔다
        self._sess = sess

        N_train = len(X_train)
        n_batches = N_train // batch_size

        for epoch in range(epochs):
            X_, Y_ = shuffle(X_train, Y_train)

            for i in range(n_batches):
                start = i * batch_size
                end = start + batch_size

                sess.run(train, feed_dict={x: X_[start:end],
                                           y: Y_[start: end],
                                           keep_prob: p_keep})

            loss_ = loss.eval(session=sess, feed_dict={x: X_train,
                                                       y: Y_train,
                                                       keep_prob: p_keep})
            
            # evaluate() 함수에서 참조할 수 있도록 함
            accuracy_ = self.accuracy.eval(session=sess, feed_dict={x: X_train,
                                                               y: Y_train,
                                                               keep_prob: p_keep})

            # 오차와 정확도를 그래프로 그리기 위해 값을 기록한다
            self._history['loss'].append(loss_)
            self._history['accuracy'].append(accuracy_)

            if verbose:
                print(f'epoch : {epoch:03}, loss : {loss_:.7}, accuracy : {accuracy_*100:.6} %')

        return self._history


  • 초기화 부분 다시 정리
    • 테스트할 때 사용되는 변수는 evaluate() 과정에도 필요하기 때문에 클래스 내부에 둬야 한다.
    • 학습 진행 상황을 클래서에서 파악하고 있으면 학습 종료 후에 데이터를 처리하기 쉬워지무로 self.history를 정의해 사용한다.
class DNN(objec):
    def __init__(self, n_in, n_hiddens, n_out):
        self.n_in = n_in
        self.n_hiddens = n_hiddens
        self.n_out = n_out
        self.weight = []
        self.bias = []

        # 학습 진행 상황을 확인하기 위하여 오차와 정확도를 _history 변수에 저장하고 한다
        self._x = None
        self._y = None
        self._keep_prob = None
        self._sess = None
        self._history = {'accuracy': [],
                         'loss': []}


evaluate() 함수 정의

  • 앞에서 정의한 self._sess 등을 사용해 다음과 같이 구현한다.
class DNN(objec):
    def evaluate(self, X_test, Y_test):
        # 모델 예측 결과 처리
        return self.accuracy(session=self._sess, feed_dict={self._x: X_test,
                                                            self._y: Y_test,
                                                            self._keep_prob: 1.0})


정의한 class를 사용하여 모델 설계하기앞서

  • 앞서 정의한 class를 사용한다면 main 함수에서 다음과 같이 코딩하여 사용할 수 있다.
if __name__ == '__main__':

    MNIST = datasets.fetch_mldata('MNIST original', data_home='.')

    n = len(MNIST.data)
    N = int(n * 1.00)
    N_train = int(n * 0.2)
    N_val = int(N_train * 0.2)
    indices = np.random.permutation(range(n))[:N]
    X = MNIST.data[indices]
    Y = MNIST.target[indices]
    Y = np.eye(10)[Y.astype(int)]

    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=N_train)
    X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=N_val)

    n_in = len(X[0])
    n_hiddens = [200, 200, 200]
    n_out = len(Y[0])

    # 신경망 모델링 정의
    model = DNN(n_in=n_in, n_hiddens=n_hiddens, n_out=n_out)

    # 신경망 모델 학습
    model.fit(X_train, Y_train, epochs=50, batch_size=200, p_keep=0.5)

    accuracy = model.evaluate(X_test, Y_test)
    print(f'accuracy : {accuracy*100:.6} %')


  • 위의 예제에서는 fit() 함수가 수행하는 처리 내용을 단순한 형태로 코딩을 했는데 내부에 있는 처리 내용을 다시 분할해서 코딩하면 더욱 범용으로 편리하게 사용할 수 있는 API가 된다.
  • TensorFlow에서는 이러한 높은 수준의 API도 제공하고 있는데, tf.contrib.learn을 통해 이용할 수 있다.


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



+ Recent posts