본문 바로가기

Machine Learning

[CS231n] 8. Deep Learning Software(2) - Frameworks

이번 포스팅에서는 실제 딥러닝 코드 구현에서 사용되는 딥러닝 프레임워크들에 대해 소개합니다.

* 본 포스팅은 2017년 상반기를 기준으로 작성되었습니다.

딥러닝 코드 구현에 도움을 주는 프레임워크는 그동안 정말 많이 출시되었습니다. 그 중 현재 널리 쓰이고 있고 이번 포스팅에서 소개할 프레임워크는 3개입니다.

  • TensorFlow
  • PyTorch
  • Caffe2

이전의 포스팅에서 파이썬의 Numpy 모듈을 이용해서 신경망을 구현하는 방법을 보인 적이 있습니다. 물론 Numpy 모듈을 이용한 행렬 연산으로 딥러닝 네트워크를 구현할 수도 있습니다. 하지만 이 경우에는 GPU의 사용이 제한되며 그래디언트 값을 수동으로 계산해줘야 한다는 단점이 있습니다. 이에 반해 TensorFlow나 PyTorch와 같은 프레임워크를 사용하면 계산은 Numpy 모듈을 이용할 때와 비슷하게 구현하면 되지만 그래디언트 계산도 간단한 코드로 해결되고 GPU 사용을 설정하는 방법도 매우 간단하다는 장점이 있습니다.

TensorFlow

먼저 간단한 코드를 하나 보시겠습니다.

import numpy as np
import tensorflow as tf

N, D, H = 64, 1000, 100
x = tf.placeholder(tf.float32, shape=(N, D))
y = tf.placeholder(tf.float32, shape=(N, D))
w1 = tf.placeholder(tf.float32, shape=(D, H))
w2 = tf.placeholder(tf.float32, shape=(H, D))

h = tf.maximum(tf.matmul(x, w1), 0)
y_pred = tf.matmul(h, w2)
diff = y_pred - y
loss = tf.reduce_mean(tf.reduce_sum(diff ** 2, axis=1))
grad_w1, grad_w2 = tf.gradient(loss, [w1, w2])

with tf.Session() as sess:
    values = {x: np.random.randn(N, D),
              w1: np.random.randn(D, H),
              w2: np.random.randn(H, D),
              y: np.random.randn(N, D),}
    learning rate = 1e-5
    for t in range(50):
        out = sess.run([loss, grad_w1, grad_w2],
                        feed_dict=values)
        loss_val, grad_w1_val, grad_w2_val = out
        values[w1] -= learning_rate * grad_w1_val
        values[w2] -= learning_rate * grad_w2_val

TensorFlow로 구현한 코드는 네트워크를 정의하는 부분과 그 네트워크를 실행하고 loss를 계산해 새로운 weight 값으로 갱신하는 부분 두 파트로 나뉘어집니다. 특히 위 코드에서 for문을 보시면, 가중치 w1과 w2가 어떻게 갱신되는지 확인할 수 있습니다.

이 코드에는 작은 문제점이 존재합니다. for문을 수행할 때, weight 값이 매번 CPU에서 GPU로 이동된 다음 연산이 수행된다는 것입니다. 이를 보완하고 몇가지 기능을 더 추가한 것이 아래의 코드입니다.

import numpy as np
import tensorflow as tf

N, D, H = 64, 1000, 100
x = tf.placeholder(tf.float32, shape=(N, D))
y = tf.placeholder(tf.float32, shape=(N, D))
w1 = tf.Variable(tf.random_normal((D, H)))
w2 = tf.Variable(tf.random_normal((D, H)))

h = tf.maximum(tf.matmul(x, w1), 0)
y_pred = tf.matmul(h, w2)
loss = tf.losses.mean_squared_error(y_pred, y)

optimizer = tf.train.GradientDescentOptimizer(1e-3)
updates = optimizer.minimum(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    values = {x: np.random.randn(N, D),
              y: np.random.randn(N, D),}
    for t in range(50):
        loss_val, _ = sess.run([loss, updates], 
                feed_dict=values)

바뀐 점을 눈치채셨나요?

우선 가중치에 해당하는 w1와 w2를 placeholder가 아닌 Variable로 설정했습니다. placeholder의 경우에는 해당 변수가 사용될 때마다 CPU에서 그 값을 가져오게 되는 반면 Variable의 경우에는 해당 변수를 네트워크 구조와 같이 GPU의 메모리 상에 저장하여 변수 값에 빠른 속도로 접근하여 사용할 수 있습니다.

loss를 계산하는 방식도 수정하였습니다. 기존의 코드와 같이 사용자가 직접 loss의 계산을 코드로 구현할 수도 있지만 이미 프레임워크에 만들어져 있는 loss 계산 함수를 사용할 수도 있습니다.

optimizer도 따로 설정해준 모습입니다. 단순히 loss를 최소화 하는 방식이 아닌 GD를 사용한 모습입니다.

TensorFlow를 기반으로 한 상위 레벨 API도 많으니 유용하게 사용할 수 있을 것입니다.

Keras
TFLearn
TensorLayer
tf.layers
TF-Slim
tf.contrib.learn
Pretty Tensor
Sonnet

PyTorch

PyTorch는 Keras와 비슷하게 nn package를 사용합니다. nn 모듈에는 딥러닝 네트워크에 사용되는 각종 구성요소가 클래스로 구현되어 있으며 각 클래스 안에는 대표적인 두개의 함수가 있습니다. 하나는 네트워크의 수치 계산을 담당하는 forward 함수이고 나머지 하나는 오차역전파 과정에서 실행되어야 하는 backward 함수입니다. 물론 사용자가 직접 클래스를 구현하여 사용하는 것도 가능합니다. 다음은 PyTorch로 구현한 딥러닝 네트워크의 코드입니다.

import torch
from torch.autograd import Variable

N, D_in, H, D_out = 64, 1000, 100, 10
x = Variable(torch.randn(N, D_in), requires_grad=False)
y = Variable(torch.randn(N, D_out), requires_grad=False)
w1 = Variable(torch.randn(D_in, H), requires_grad=True)
w2 = Variable(torch.randn(H, D_out), requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    y_pred = x.mm(w1).clamp(min=0).mm(w2)
    loss = (y_pred - y).pow(2).sum()

    if w1.grad: w1.grad.data.zero_()
    if w2.grad: w2.grad.data.zero_()
    loss.backward()

    w1.data -= learning_rate * w1.grad.data
    w2.data -= learning_rate * w2.grad.data

weight 변수는 오차역전파 과정에서 그래디언트를 구할 필요가 있기 때문에 requires_grad 인자를 True로 설정해줍니다. for문의 처음 두 줄은 네트워크를 한번 실행 시키고 loss를 계산하는 과정입니다. 그 다음 세줄은 w1과 w2의 그래디언트를 구하는 과정입니다.

다음은 몇가지 유용한 기능을 추가한 코드입니다.

import torch
from torch.autograd import Variable
from torch.utils.data import TensorDaterset, DataLoader

class ReLU(torch.autograd.Function):
    def forward(self, x):
        self.save_for_backward(x)
        return x.clamp(min=0)

    def backward(self, grad_y):
        x, = self.saved_tensors
        grad_input = grad_y.clone()
        grad_input[x < 0] = 0
        return grad_input

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

loader = DataLoader(TensorDataset(x, y), batch_size=8)

model = torch.nn.Sequential(
        torch.nn.Linear(D_in, H),
        torch.nn.ReLU(),
        torch.nn.Linear(H, D_out))

criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)

for epoch in range(10):
    for x_batch, y_batch in loader:
        x_var, y_var = Variable(x_batch), Variable(y_batch)
        y_pred = model(x_var)
        loss = criterion(y_pred, y_var)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

ReLU 클래스는 딥러닝 네트워크의 한 층으로 사용하기 위해 사용자가 직접 만들어준 클래스입니다. forward 함수와 backward 함수의 내용을 보면 각각이 수행하는 역할을 짐작할 수 있습니다. loss를 구하기 위해 nn 모듈에 있는 이미 구현된 함수를 사용했습니다. model을 정의해서 사용했고 optimizer도 설정했습니다. DataLoader를 이용하면 데이터셋에 대해 minibatch, shuffling, multithreading 등의 기능도 사용할 수 있습니다.

Caffe2

Caffe는 TensorFlow나 PyTorch와는 사용법이 사뭇 다릅니다.
간략히 소개해보자면, TensorFlow와 PyTorch는 코드를 구현할 때 사용할 수 있는 API와 같은 개념이었다면 Caffe는 다양한 플랫폼에서 사용할 수 있는 이미 구현된 어플리케이션입니다. 우선 Caffe를 사용하기 위해서는 학습하고자 하는 데이터 셋을 특정 포맷에 맞춰 변환해야 됩니다. 그리고 config 파일과 같은 텍스트 파일을 손봐야됩니다. 총 두개의 텍스트 파일을 수정해야 하는데 첫번째 파일은 네트워크의 구조를 설정하는 역할입니다. 정해진 양식에 따라 딥러닝 네트워크의 구조를 사용자가 직접 입력해주어야 합니다. 두번째 파일은 여러 인자들을 설정하는 용도입니다. learning rate, 최대 반복 횟수 등의 여러가지 학습인자를 설정해줍니다. 그러고 난 후 Caffe 바이너리를 실행하여 학습을 진행할 수 있습니다.

마무리

TensorFlow와 PyTorch, Caffe2의 사용법에 대해 알아보았습니다. 각각의 프레임워크는 장단점이 있기 때문에 사용자의 구현환경과 구현하고자 하는 딥러닝 네트워크의 특성에 따라 유동적으로 사용할 프레임워크를 선택하면 될 것입니다. TensorFlow의 경우에는 여러가지 high-level API가 존재하여 사용하기 편리하고 PyTorch는 연구에 적합합니다. 실제 어플리케이션을 만들어서 구동시키고 싶은 경우 Caffe가 바람직한 선택이 될 것입니다.