DataLoader와 스토캐스틱 경사하강법¶
딥러닝은 하나의 중요한 알고리즘으로 많이 발전하게 되었다. 바로 스토캐스틱 경사하강법이다. 기존의 경사하강법은 한 epoch에 모든 데이터셋을 한번에 학습시킨다. 스토캐스틱 경사하강법은 이 큰 데이터셋을 여러개로 나누고, 나눠진 데이터의 서브셋 마다 학습을 시킨다. 이것을 기존 경사하강법의 근사치로 보면 될것이다. 이 하나의 서브셋을 배치 또는 미니배치라고 부른다.
import torch
from torch import autograd, nn, optim
import torch.nn.functional as F
import numpy as np
def lin(a,b,x): return a*x+b
# 가짜 데이터 생성
def gen_fake_data(n, a, b):
x = np.random.uniform(0,1,n)
y = lin(a,b,x) + 0.1 * np.random.normal(0,3,n)
return x.astype(np.float32), y.astype(np.float32)
Dataset클래스는 X와 Y 데이터를 엮어주는 역할을 한다. 입력과 출력을 각각 관리하면, 실수하기 쉽기 때문에, Dataset클래스를 사용하면 그런 위험성을 줄일 수 있게 된다.
from torch.utils.data import Dataset, DataLoader
# 데이터셋 클래스를 만들어준다
class RegressionDataset(Dataset):
def __init__(self, a=3, b=8, n=10000):
x, y = gen_fake_data(n, a, b)
x = torch.from_numpy(x).unsqueeze(1)
y = torch.from_numpy(y)
self.x, self.y = x, y
def __len__(self):
return len(self.y)
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
dataset_train = RegressionDataset()
dataset_test = RegressionDataset(n=3000)
DataLoader는 다음과같은 세가지 기능을 제공한다:
- 데이터를 배치로 나눠준다
- 데이터를 셔플링 해준다
- 데이터를 병렬처리를 사용해 패러럴하게 로딩해준다
# 데이터로더에 데이터셋을 로딩하고,
# 배치사이즈는 100개로 정하고,
# 셔플링을 사용한다.
dataloader_train = DataLoader(dataset_train, batch_size=1000, shuffle=True)
x, y = next(iter(dataloader_train))
x[:5], y[:5]
데이터로더로 배치와 셔플링을 사용해 선형회귀를 하려면 다음과같이 코드를 작성하면 된다:
(편의상 선형모델은 클래스를 만들지 않았다.)
linearModel = torch.nn.Sequential(
torch.nn.Linear(1, 1),
)
learning_rate = 0.1
optimizer = torch.optim.Adam(linearModel.parameters(), lr=learning_rate)
model.train()
for t in range(1000):
for i, (x, y) in enumerate(dataloader_train):
y_hat = linearModel(x)
loss = F.mse_loss(y_hat, y.unsqueeze(1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if t % 100 == 0: print(loss.item())
print([p for p in linearModel.parameters()])
이제 모델을 평가해보겠다. 회귀문제이기 때문에 loss의 결과를 출력한다.
from sklearn.metrics import accuracy_score
dataloader_test = DataLoader(dataset_test, batch_size=300, shuffle=True)
with torch.no_grad():
model.eval()
losses = []
for i, (x, y) in enumerate(dataloader_test):
outputs = linearModel(x)
loss = F.mse_loss(outputs, y.unsqueeze(1))
losses.append(loss.item())
print(sum(losses) / len(losses))
레이어 두개의 신경망¶
레이어 두개짜리의 신경망을 만들어보겠다.
def sigmoid(x):
return 1/(1 + np.exp(-x))
# 딥러닝용 가짜 데이터를 만든다
def gen_nn_fake_data(n):
x = np.random.uniform(0,10, (n, 2))
x1 = x[:,0]
x2 = x[:,1]
score1 = sigmoid(-x1 - 8* x2 + 50)
score2 = sigmoid(-7*x1 - 2* x2 + 50)
score3 = 2* score1 + 3*score2 - 0.1
y = score3 < 0
return x, y.astype(int)
x, y = gen_nn_fake_data(500)
import matplotlib.pyplot as plt
%matplotlib inline
plt.scatter(x[:,0],x[:,1],c=y, s=8);
plt.xlabel("x1"); plt.ylabel("x2");
학습데이터는 위와 같다. 보다시피 선형회귀로는 곡선적으로 생긴 두 데이터의 경계를 분류할 수 없다. 따라서 레이어 두개짜리의 신경망을 사용해 분류하는 모델을 만든다.
class TwoLayerNN(nn.Module):
def __init__(self):
super().__init__()
self.lin1 = nn.Linear(2, 2)
self.sig1 = nn.Sigmoid()
self.lin2 = nn.Linear(2, 1)
self.sig2 = nn.Sigmoid()
def forward(self, x):
x = self.lin1(x)
x = self.sig1(x)
x = self.lin2(x)
x = self.sig2(x)
return x
model = TwoLayerNN()
model
그리고 새로운 학습 데이터를 텐서에 로드한 뒤 모델을 학습시킨다.
from sklearn.model_selection import train_test_split
x, y = gen_nn_fake_data(10000)
X_train, X_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)
x_train = torch.tensor(X_train).float()
y_train = torch.tensor(y_train).float()
x_test = torch.tensor(X_test).float()
y_test = torch.tensor(y_test).float()
x_train, y_train, x_test, y_test
이제 모델을 학습시킨다. loss함수는 분류모델이기 때문에 cross entropy를 사용하고, 최적화함수는 Adam을 사용한다.
learning_rate = 0.01
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
model.train()
for t in range(10000):
y_hat = model(x_train)
loss = F.binary_cross_entropy(y_hat, y_train.unsqueeze(1))
if t % 1000 == 0:
print(loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
print([p for p in model.parameters()])
모델을 평가해 보면 역시 높은 점수가 나오는 것을 볼 수 있다.
with torch.no_grad():
model.eval()
output = model(x_test)
x_test_unsqueezed = x_test.unsqueeze(1)
outputs = model(x_test_unsqueezed)
print(accuracy_score(y_test, outputs.squeeze().round()))
참고자료
아래의 원본 소스를 기반으로 번역 및 개인 의견을 덧붙인 코드입니다.
'데이터사이언스 > PyTorch' 카테고리의 다른 글
PyTorch 5 - 딥러닝 기반 협업필터링 (0) | 2019.07.22 |
---|---|
PyTorch 4 - 협업필터링 (0) | 2019.07.14 |
PyTorch 2 - 로지스틱 회귀 (0) | 2019.07.11 |
PyTorch 1 - 텐서 (0) | 2019.07.09 |