딥러닝 모델들은 block or layer의 박복이다.
이러한 layer을 구현할 수 있도록 PyTorch에서는 torch.nn.Module을 지원한다.
torch.nn.Module은 딥러닝을 구성하는 layer의 base class
이다.
따라서 torch.nn.Module 안에는 input_feature, output_feature, forward, backward
가 정의되어 있어야하고, 학습의 대상이 되는 parameter(tensor)
가 정의되어 있어야한다.
torch.nn.Module안에 parameter를 정의하기 위해 torch.nn.Parameter class가 사용된다.
torch.nn.Parameter는 torch.Tensor 객체의 상속 객체로, torch.nn.Moudle 내에 attribute가 될 때는 requires_grad=True
로 지정되어 학습 대상이 된다.
대부분의 layer에는 weights 값들이 지정되어 있어, 직접 지정할 일은 잘 없다.
class MyClass(torch.nn.Module):
def __init__(self, input_feature, output_feature, bias = True):
super(MyClass, self).__init__()
self.input_feature = input_feature
self.output_feature = output_feature
self.weight = torch.nn.Parameter(torch.randn(input_feature, output_feature))
self.bias = torch.nn.Parameter(torch.randn(output_feature))
# forward는 torch.nn.Module의 __call__에 등록되어 있다.
def forward(self, x: Tensor):
return x @ self.weights + self.bias
backward는 예측값과 실제값의 차이(loss)에 대해 layer에 있는 각 parameter들의 미분을 수행 한다.
그리고 parameter들의 미분값으로 parameter들을 업데이트 한다.
criterion = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learningRate)
for epoch in range(epochs):
# 1. gradient buffer 초기화
# 이전 epoch에서 사용한 gradient를 사용하지 않는다.
optimizer.zero_grad()
# 2. 주어진 데이터를 model에 넣고 예측된 결과값 얻는다.
y_hat = model(inputs)
# 3. 예측한 결과값과 실제값으로 loss 값을 얻는다.
# criterion은 cross entropy, RMSE 등으로 정의할 수 있다.
loss = criterion(y_hat, y)
# 4. 각 parameter에 관한 gradient 값을 얻는다.
loss.backward()
# parameters를 한꺼번에 업데이트 한다.
optimizer.step()
backward는 nn.Module에서 직접 지정이 가능하다.
PyTorch는 AutoGrad를 지원하기 때문에, nn.Parameter로 parameter를 선언하면 자동으로 계산해주므로 굳이 설정할 필요가 없다.
backward를 직접 지정하려면, nn.Module의 backward 함수와 optimizer 함수를 오버라이딩 해줘야 한다.
backward를 직정 지정하면, 미분식을 입력해줘야 하는 부담감이 있다.
backward를 쓸일은 없지만, 안에서 일어나는 순서는 파악해두자.
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
class LogisticRegression(nn.Module):
def __init__(self, dim, lr=torch.scalar_tensor(0.01)):
super(LogisticRegression, self).__init__()
# intialize parameters
self.w = ~~.to(device)
self.b = ~~.to(device)
self.grads = {"weight": ~~.to(device),
"bias": ~~.to(device)}
self.lr = lr.to(device)
def forward(self, x):
## compute forward
z = torch.mm(self.w.T, x) + self.b
a = self.sigmoid(z)
return a
def sigmoid(self, z):
return 1/(1 + torch.exp(-z))
def backward(self, x, yhat, y):
## compute backward
self.grads["weight"] = (1/x.shape[1]) * torch.mm(x, (yhat - y).T)
self.grads["bias"] = (1/x.shape[1]) * torch.sum(yhat - y)
def optimize(self):
## optimization step
self.w = self.w - self.lr * self.grads["weight"]
self.b = self.b - self.lr * self.grads["bias"]
PyTorch에서는 대용량 데이터를 잘 넣어줄 수 있는 Dataset API를 제공한다.
분야마다 데이터 셋을 넣는 구조가 정해져 있다.
모델에 데이터를 feed(먹이다)는 방법은 아래 사진과 같다.
자료수집 → transforms(전처리, tensor로 변환) → Dataset → DataLoader → Model
Dataset
으로 전처리된 데이터 하나를 가져오는 방법을 정의한다.
DataLoader의 argument로는 Dataset과 batch_size 등이 있다
DataLoader
는 Dataset에 정의된 방법으로 불러온 하나의 데이터를 batch_size만큼 모아 model에 feeding한다.
class CustomDataset(Dataset):
# 변수 초기화
def __init__(self, text, labels):
self.labels = labels
self.data = text
# len() 함수는 __len__ 함수를 호출한다.
#
# len()는 __len__과 다르게 정수인지,
# __len__이 존재하는 지 등을 확인한다.
def __len__(self):
return len(self.labels)
# DataLoader가 iterater가 되고,
# next()로 데이터를 뽑을 때,
# __getitem__에 정의된 방식대로
# 데이터를 가져온다.
def __getitem__(self, idx):
label = self.labels[idx]
text = self.data[idx]
sample = {"Text": text, "Class": label}
return sample
Image, Text, Audio 등 데이터에 따라 입력 형태를 정의해야 한다. 즉, 데이터 형태에 따라 각 함수를 다르게 정의해야 한다.
모든 것을 데이터 생성 시점에 처리할 필요는 없다.
학습하는 시점
에 Dataset에 넘겨준 transform의 정의에 따라 __getitem__ 함수 안에서 data를 tensor 등으로 바꾼다.
CPU에선 data를 tensor로 변환하고, GPU에서 변환된 data를 학습하는 등의 병렬 처리
가 가능하기 때문에 충분한 시간이 있다.
최근에는 HuggingFace 등 표준화된 라이브러리를 사용한다.
Data의 Batch를 생헝해주는 클래스로 학습 직전(GPU feed 직전) 데이터의 변환를 담당한다.
Tensor로 변환
과 Batch 처리
가 메인 업무이며, 병렬적인 데이터 전처리
코드의 고민이 필요하다.
MyDataLoader = DataLoader(MyDataset, batch_size=2, shuffle=True)
# next로 뽑는 시점에 batch_size만큼 memory(GPU)에 올라가서 처리된다.
next(iter(MyDataLoader))
DataLoader의 parameter에 대한 자세한 내용은 여기를 참고하자.