PyTorch의 Dataset과 DataLoader를 이용하여 학습 효율성 향상시키기

PyTorch의 Dataset과 DataLoader를 이용하면 학습을 위한 방대한 데이터를 미니배치 단위로 처리할 수 있고, 데이터를 무작위로 섞음으로써 학습의 효율성을 향상시킬 수 있다. 또한 데이터를 여러개의 GPU를 사용해 병렬처리로 학습할 수도 있다. 아래의 코드는 Dataset과 DataLoader를 사용하지 않고 매 에폭마다 학습 데이터 전체를 입력해 학습하는 코드이다.

import torch
from torch import nn, optim
from sklearn.datasets import load_iris
from torch.utils.data import  TensorDataset, DataLoader
 
iris = load_iris()
 
X = iris.data[:100]
y = iris.target[:100]
 
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)
 
net = nn.Linear(4, 1)
loss_fn = nn.BCEWithLogitsLoss()
optimizer = optim.SGD(net.parameters(), lr=0.25)
 
losses = []
 
for epoc in range(100):
    batch_loss = 0.0

    optimizer.zero_grad()
    y_pred = net(X)
    loss = loss_fn(y_pred.view_as(y), y)
    loss.backward()
    optimizer.step()
    batch_loss += loss.item()
    
    losses.append(batch_loss)
 
from matplotlib import pyplot as plt
plt.plot(losses)
plt.show()

위의 코드에 대한 손실 그래프는 다음과 같다.

다음 코드는 위의 코드에 대해서 Dataset과 DataLoader를 적용한 코드이다. 앞 코드의 하이퍼 파라메터 등에 대한 모든 조건은 동일하고 단지 미니배치를 10로 하여 학습시킨다.

import torch
from torch import nn, optim
from sklearn.datasets import load_iris
from torch.utils.data import  TensorDataset, DataLoader

iris = load_iris()

X = iris.data[:100]
y = iris.target[:100]

X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)

ds = TensorDataset(X, y)
loader = DataLoader(ds, batch_size=10, shuffle=True)

net = nn.Linear(4, 1)
loss_fn = nn.BCEWithLogitsLoss()
optimizer = optim.SGD(net.parameters(), lr=0.25)

losses = []

for epoc in range(100):
    batch_loss = 0.0
    for xx, yy in loader:
        optimizer.zero_grad()
        y_pred = net(xx)
        loss = loss_fn(y_pred.view_as(yy), yy)
        loss.backward()
        optimizer.step()
        batch_loss += loss.item()
    losses.append(batch_loss)

from matplotlib import pyplot as plt
plt.plot(losses)
plt.show()

위의 코드에 대한 손실 그래프는 다음과 같다.

손실 그래프를 보면 미니배치를 사용한 것이 더 안정적으로 학습이 진행 되는 것으로 확인할 수 있다.

선형회귀모델에 대한 PyTorch를 이용한 두가지 접근

아래와 같은 식을 회귀하는 모델을 구하는 두가지 접근을 PyTorch로 살펴본다.

    $$y=1+5a+7b$$

즉, 입력값(a, b)에 대한 출력값 y가 100개 주어지고, 이 데이터를 통해 상수항인 1과 계수 5, 7을 구하는 것이 문제다. 물론 y에는 오차가 반영되어 있다. 첫번째 접근은 다음과 같다. 손실함수는 평균최소제곱을, 역전파를 통한 최적값 수렴을 위한 기울기를 구해 반영한 학습률은 0.01을 사용했다. 아래의 코드의 경우 기울기를 구하기 위한 방법을 PyTorch의 역전파를 이용한 것이다.

import torch
from matplotlib import pyplot as plt

weight_true = torch.Tensor([1,5,7]) # y = 1 + 5a + 7b
X = torch.cat([torch.ones(100,1),torch.randn(100,2)], 1)
y = torch.mv(X, weight_true) + torch.randn(100)
weight = torch.randn(3, requires_grad=True)

lr = 0.01

losses = []

for epoch in range(1000):
    weight.grad = None

    y_pred = torch.mv(X, weight)
    loss = torch.mean((y - y_pred)**2)
    loss.backward()

    weight.data = weight.data - lr*weight.grad.data

    losses.append(loss.item())

print(weight)

plt.plot(losses)
plt.show()

두번째 접근은 다음과 같다. 앞서 직접 하나 하나 개발자가 지정했던 것들에 대한 모듈을 사용한 경우이다.

import torch
from torch import nn, optim
from matplotlib import pyplot as plt

weight_true = torch.Tensor([1,5,7]) # y = 1 + 5a + 7b
X = torch.cat([torch.ones(100,1),torch.randn(100,2)], 1)
y = torch.mv(X, weight_true) + torch.randn(100)

net = nn.Linear(in_features=3, out_features=1, bias=False)
optimizer = optim.SGD(net.parameters(), lr=0.01)
loss_fn = nn.MSELoss()

losses = []

for epoch in range(1000):
    optimizer.zero_grad()

    y_pred = net(X)
    loss = loss_fn(y_pred.view_as(y), y)
    loss.backward()

    optimizer.step()

    losses.append(loss.item())

print(net.weight)

plt.plot(losses)
plt.show()

두 경우 모두 실행하면 아래와 같은 손실값에 대한 그래프와 추론된 상수와 두계수 값이 콘솔에 출력된다.

tensor([0.9295, 4.9402, 7.0627], requires_grad=True)