*해당 포스팅은 이 원문을 번역, 정리(요약)한 것입니다.

 

 

텐서플로우가 파이토치보다 1년 먼저 나왔지만, 최근 많은 개발자들이 텐서플로우에서 파이토치로 넘어가고 있는 실정이다.

이번 아티클에서는 텐서플로우에서 파이토치로 넘어가는 방법에 대해 이야기하려고 한다. 

먼저 각 딥러닝 프레임워크를 사용하는 이유에 대해 살펴본 후, 간단한 예제를 통해 파이토치와 가까워지는 시간을 가져보려 한다.

 

프레임워크 개요

텐서플로우

2015년 구글에서 발표한 딥러닝 프레임워크. 텐서플로우 2.0로 업데이트되면서 케라스(keras)와 통합되었다.

 

파이토치

2016년 페이스북에서 발표한 딥러닝 프레임워크. 파이썬 언어와 자연스럽게 어우러진다는 장점이 있다.

파이토치를 텐서 단위의 딥러닝 모델을 GPU 위에서 계산할 수 있는 플랫폼으로 이해할 수 있다.

동적인(dynamic) 그래프를 생성하고, 그때그때(on the fly) 모델의 동작 원리를 살펴볼 수 있다.

 

왜 텐서플로우에서 파이토치로?

- 파이토치와 다르게 텐서플로우는 정적인(static) 그래프를 생성한다.

- 파이토치는 특정 그래프 작업에 맞춰 모델을 정의하고 조작할 수 있다. (RNN처럼 입력 길이가 가변적인 상황에서 유용)

- 파이토치는 근본적으로 파이썬을 기반으로 개발되었기 때문에 좀더 직관적이다.

- 텐서플로우가 익히기 좀더 까다로운 편. 

- 신속하게 프로토타입을 개발하고 연구하는 데 파이토치가 가장 적합하다.

 

 

텐서플로우에서 파이토치로 넘어가기

GPU 환경에서 두 프레임워크를 설치하는 과정은 생략하겠습니다.

 

텐서플로우에서 파이토치로 넘어가는 건 그렇게 복잡한 편이 아니다.

파이토치는 문제를 해결하는 데 있어 파이써닉한 접근방식을 취하기 때문이다.

 

 

텐서 다루기

각 프레임워크에서 임의의 텐서를 초기화해보자. 방법은 크게 다르지 않다.

 

텐서플로우

import tensorflow as tf

rank2_tensor = tf.constant([[1, 2],
                            [3, 4],
                            [5, 6]], dtype=tf.int32)

 

파이토치

import torch

rank2_tensor = torch.tensor([[1, 2],
                             [3, 4],
                             [5, 6]], dtype=torch.int32)

 

간단한 텐서 연산을 수행해보자.

 

텐서플로우

a = tf.constant([[1, 2],
                 [3, 4]])
b = tf.constant([[1, 1],
                 [1, 1]])
                 
a = tf.Variable(a)
b = tf.Variable(b)

print(tf.add(a, b), "n")
print(tf.multiply(a, b), "n")
print(tf.matmul(a, b), "n")

 

파이토치

a = torch.tensor([1, 2, 3], dtype=torch.float)
b = torch.tensor([7, 8, 9], dtype=torch.float)

print(torch.add(a, b))
print(torch.subtract(b, a))
print(a.mul(b))
print(a.dot(b))

 

 

작동 메카니즘

딥러닝 프레임워크는 연산 그래프를 활용한다. 연산 그래프는 최선의 결과를 얻기 위해 연산이 이루어져야 하는 순서를 정의한다.

 

딥러닝 모델을 계산하기 위해 크게 두 개의 인터프리터가 사용되는데,

하나는 프로그래밍 언어에 이용되고(대부분의 경우 파이썬) 나머지 하나는 연산 그래프를 뜻대로 조작하는 데 사용된다.

따라서 연산 그래프를 세팅할 때는 파이썬 같은 프로그래밍 언어를 통해, 실행 메카니즘은 다른 언어를 통해 이루어지게 된다.

애초에 효율성 및 최적화 문제 때문에 이런 이상한(?) 세팅이 구성됐다고 볼 수 있다.

 

텐서플로우의 경우 정적인 연산 그래프를 활용한다. 전형적인 "Define and Run" 방식으로 작동한다. 처음에 모든 변수들을 생성, 연결한 다음, 이들을 정적인 세션에 초기화하게 된다.

하지만 이렇게 정적인 그래프에 변수 파라미터를 정의하는 방식이 비효율적이라고 여겨지기도 한다. 특히 RNN 형식의 모델을 쓰는 경우에 그렇다.

 

Define-and-Run : 연산 그래프를 먼저 정의한 후에 데이터를 흘려보내 결과를 얻는 방식

 

정적인 텐서플로우 그래프를 살펴보자.

 

import tensorflow as tf

a = tf.Variable(15)
b = tf.Variable(5)

prod = tf.multiply(a, b)
sum = tf.add(a, b)

result = prod/sum
print(result)

 

 

 

파이토치의 경우, 동적인 그래프를 활용하기 때문에 코드를 입력할 때 연산 그래프가 설계된다.

유저가 변수를 처음 선언할 때 연산 그래프가 바로 만들어지고, 각 훈련 iteration 마다 연산 그래프가 다시 만들어진다.

 

정적인 그래프보다 동적인 그래프를 선호하는 이유는, 다른 요소는 건드리지 않고 필요한 요소들만 수정할 수 있기 때문이다.

이러한 방법의 단점을 꼽자면 그래프를 재설계 하는 데 종종 시간이 좀더 걸린다는 점이다.

 

아래 움짤은 파이토치 작동 방식을 보여준다.

 

 

반복 훈련(training loops)에서의 비교

텐서플로우에서 훈련 루프를 만드는 과정은 다소 복잡하고 직관과는 거리가 멀다.

 

보통은 tf.function을 데코레이터로 써서 정적 그래프 관점에서 모델을 컴파일 한다. 

일반적으로 텐서플로우는 즉시 실행(eager execution)을 활용한다. 이는 디버깅에는 용이하지만 빠른 실행에는 불리하다.

따라서 tf.function을 활용해서 전역적인(global) 퍼포먼스 최적화를 적용한다.

 

즉시 실행 (Eager execution) : 그래프 생성 없이 연산을 즉시 실행하는 명령형 프로그래밍 환경

 

그 다음 학습 과정을 정의할 때는 Gradient Tape 기능을 통해 자동 미분(automatic differentiation)을 수행한다.

그래디언트 값이 적용되고, 역전파 과정이 이루어질 때, 최종적으로 훈련 파라미터가 업데이트 된다.

모델의 훈련을 끝내고 나면 원했던 값들을 리턴 받을 수 있다.

 

@tf.function
def train_step(x, y):
     with tf.GradientTape() as tape:
          logits = model(x, training=True)
          loss_value = loss_fn(y, logits)
     grads = tape.gradient(loss_value, model.trainable_weights)
     optimizer.apply_gradients(zip(grads, model.trainable_weights))
     train_acc_metric.update_state(y, logits)
     return loss_value

 

파이토치에서 학습 루프를 정의하는 건 보다 간단하고 직관적이다. 변수를 생성하고 동적 그래프를 그때그때 정의하면서 모델을 학습할 수 있다. 데이터와 타겟을 디바이스(CPU/GPU)에 할당하고 순전파를 연산한다.

 

모델이 순전파(feed-forward) 연산을 마치고 나면, pytorch의 사전 정의된 개체들을 통해 역전파 과정을 수행할 수 있다. 그래디언트를 계산하고, 역전파 방법론을 적용함으로써 파라미터를 업데이트 한다.

 

for epoch in range(epochs):
     for batch, (data, target) in enumerate(train_loader):
          # cuda 파라미터 가져오기
          data = data.to(device=device)
          target = target.to(device=device)
          # 순전파
          score = model(data)
          loss = criterion(score, target)
          # 역전파
          optimizer.zero_grad()
          loss.backward()
          optimizer.step()

 

 

MNIST로 파이토치 이해하기

필요한 라이브러리를 불러온다

 

import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader

import numpy as np
import matplotlib.pyplot as plt

 

디바이스 파라미터를 설정한다. 

 

device = torch.device('cuda' if torch.cuda.is_available() else cpu)

 

하이퍼파라미터를 세팅한다.

 

num_classes = 10
input_size = 784
batch_size = 64
lr = 0.0001
epochs = 3

 

텐서플로우처럼 파이토치도 MNIST 등 기본 데이터셋을 불러올 수 있다.

 

T = torchvision.transform.Compose([torchvision.transforms.ToTensor()])

X_train = torchvision.datasets.MNIST(root='/datasets', \
                                     train=True, \
                                     download=True, \
                                     transform=T)
train_loader = DataLoader(dataset=X_train, batch_size=batch_size, shuffle=True)

X_test = torchvision.datasets.MNIST(root='/datasets', \
                                     train=False, \
                                     download=True, \
                                     transform=T)
test_loader = DataLoader(dataset=X_test, batch_size=batch_size, shuffle=True)

 

"Linear" function 으로 정의되는 fully connected layer 를 선언할 것이다.

텐서플로우였다면 Dense function을 사용했을 거라는 점을 기억하자.

 

class neural_network(nn.Module):
     def __init__(self, input_size, num_classes):
          super(neural_network, self).__init__()
          self.fc1 = nn.Linear(in_features=input_size, out_features=50)
          self.fc2 = nn.Linear(in_features=50, out_features_num_classes)
          
     def forward(self, x):
          x = self.fc1(x)
          x = F.relu(x)
          x = self.fc2(x)
          return x

 

이제 loss 와 옵티마이저를 선택해보자.

학습에 있어서는, 순전파를 수행한 다음 최적의 가중치를 학습하기 위해 역전파를 적용할 것이다.

 

# 손실함수와 옵티마이저
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)

# 훈련
for epoch in range(epochs):
     for batch, (data, target) in enumerate(train_loader):
          data = data.to(device=device)
          target = target.to(device=device)
          
          data = data.reshape(data.shape[0], -1)
          
          # 순전파
          score = model(data)
          loss = criterion(score, target)
          
          # 역전파
          optimizer.zero_grad()
          loss.backward()
          optimizer.step()

 

모델을 평가해보자.

 

def check_accuracy(loader, model):
    num_correct = 0
    num_samples = 0
    model.eval()

    with torch.no_grad():
        for x, y in loader:
            x = x.to(device=device)
            y = y.to(device=device)
            x = x.reshape(x.shape[0], -1)

            scores = model(x)
            _, predictions = scores.max(1)
            num_correct += (predictions == y).sum()
            num_samples += predictions.size(0)

        if num_samples == 60000:
            print(f"Train accuracy = {float(num_correct) / float(num_samples) * 100:.2f}")
        else:
            print(f"Test accuracy = {float(num_correct) / float(num_samples) * 100:.2f}")

    model.train()
    
check_accuracy(train_loader, model)
check_accuracy(test_loader, model)

 

 

꼭 파이토치여야 할까?

파이토치의 장점

1. 본질적으로 파이써닉하다

파이토치로 된 코드는 파이써닉하다. 즉, 절차적인 코딩 방식이(procedural coding) 파이썬 요소와 유사하다.

텐서플로우로 작업할 때는 코드가 다소 low-level에 있어 이해하기가 어렵다.

(이런 이유로 keras 같은 high-level API가 텐서플로우 2.0으로 통합되고 있다.)

 

파이토치의 기능들은 Numpy, Scipy, Cpython과 같은 다른 라이브러리와 함께 적용하기 좋다.

또 파이토치의 문법이나 응용 방식이 정통 파이썬 프로그래밍 방식과 매우 유사하기 때문에 학습 난이도가 낮다.

 

2. 문서화가 잘 되어 있고 커뮤니티가 잘 형성되어 있다

 

3. 동적 그래프

메모리를 미리 지정하기 어려울 때 동적으로 생성된 그래프를 유용하게 쓸 수 있다.

 

4. 많은 개발자들이 프로젝트에 파이토치를 활용하고 있다

 

파이토치의 단점

1. 시각화 기술이 부족하다

텐서플로우의 경우 텐서보드(Tensorboard)라는 시각화 툴킷을 통해 학습 및 검증 정확도 및 loss, 모델 그래프, 히스토그램, 이미지 등 다양한 요소를 모니터링 할 수 있다. 파이토치에 Tensorboard를 활용하는 것도 방법이다.

 

2. 배포를 위해서는 API 서버가 요구된다

텐서플로우의 장점 중 하나가 프로덕션 툴이 다양하다는 것이다. production-ready 상태로 빌드되기 때문에 텐서플로우의 확장성이 크다.

inference나 학습된 모델 주기(lifetimes) 등을 다룰 수 있다.

 

 

 

오늘은 텐서플로우와 파이토치를 비교하며 대략적으로 파이토치가 어떤 프레임워크인지 살펴보았다.

다음 글에서는 파이토치의 모델 정의, 학습, 추론에 대해 자세하게 살펴보겠다.

'인공지능 > 머신러닝-딥러닝' 카테고리의 다른 글

파이토치(PyTorch) 이해하기 - 자동 미분(Autograd)  (0) 2022.12.02
파이토치(PyTorch) 이해하기 with MNIST  (0) 2022.11.24
ASAM (pending)  (0) 2022.08.10
tensorflow - 함수들  (0) 2022.08.10
Tensorflow  (0) 2022.08.10
복사했습니다!