PyTorch에서는 텐서를 사용하여 모델의 입력(input)과 출력(output), 매개변수들을 encode한다.
1.데이터로부터 직접(directly) 생성
2.NumPy 배열로부터 생성
3.다른 텐서로부터 생성
4.무작위(random) 또는 상수(constant)값 사용
텐서의 모양(shape), 자료형(datatype) 및 어느 장치에 저장되는지를 나타낸다.
torch.cat: 주어진 차원에 따라 일련의 텐서를 연결할 수 있다.
item(): 텐서의 모든 값을 하나로 집계(aggregate)하여 요소가 하나인 텐서의 경우, Python 숫자 값으로 변환할 수 있다.
x.copy_(y)``x.t_() : x 를 변경한다.
CPU 상의 텐서와 NumPy 배열은 메모리 공간을 공유하기 때문에, 하나를 변경하면 다른 하나도 변경된다.
텐서의 변경 사항이 NumPy 배열에 반영된다.
NumPy 배열의 변경 사항이 텐서에 반영된다.
pre-loaded 데이터셋 뿐만 아니라 custom 데이터를 사용할 수 있다.
Dataset: 샘플과 정답(label)을 저장한다.
DataLoader:Dataset을 샘플에 쉽게 접근할 수 있도록 iterable로 감싼다.
PyTorch가 제공하는 두 가지 데이터 기본 요소
torch.utils.data.DataLoader
torch.utils.data.Dataset: 데이터셋은 torch.utils.data.Dataset의 하위 클래스로 개별 데이터를 특정하는 함수가 구현되어 있다. 이러한 데이터셋은 모델을 만들어보고(prototype) 성능을 측정(benchmark)하는데 사용할 수 있다.
매개변수
root: 학습/테스트 데이터가 저장되는 경로다.
train: 학습용 또는 테스트용 데이터셋 여부를 지정한다.
download=True:root에 데이터가 없는 경우 인터넷에서 다운로드한다.
transform,target_transform: feature과 label의 transform을 지정한다.
training_data[index], matplotlib 을 사용하여 학습 데이터를 시각화할 수 있다.
Custom Dataset 클래스는 반드시 3개 함수를 구현해야 한다.
__init__, __len__, __getitem__
Dataset 은 데이터셋의 feature를 가져오고 하나의 샘플에 label을 지정하는 일을 한 번에 한다.
모델을 학습할 때, 일반적으로 샘플들을 minibatch로 전달하고, 매 epoch마다 데이터를 다시 섞어서 과적합(overfit)을 막고, Python의 multiprocessing 을 사용하여 데이터 검색 속도를 높인다.
__init__Dataset 객체가 생성될 때 한 번만 실행된다.
이미지와 주석 파일(annotation_file)이 포함된 디렉토리와 두가지 transform을 초기화한다.
__len__데이터셋의 샘플 개수를 반환한다.
__getitem__주어진 인덱스 idx 에 해당하는 샘플을 데이터셋에서 불러오고 반환한다.
인덱스를 기반으로, 디스크에서 이미지의 위치를 식별한다.
read_image 를 사용하여 이미지를 텐서로 변환한다.
self.img_labels 의 csv 데이터로부터 해당하는 정답(label)을 가져온다.
transform 함수들을 호출한 뒤, 텐서 이미지와 라벨을 Python dict형으로 반환한다.
DataLoader는 간단한 API로 복잡한 과정들을 추상화한 iterable이다.
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
DataLoader에 데이터셋을 불러온 뒤에는 필요에 따라 데이터셋을 iterate한다.
각 iteration은 (각각 batch_size=64의 feature과 label을 포함) train_features 와 train_labels의 batch를 반환한다.
shuffle=True 로 지정했으므로, 모든 배치를 순회한 뒤 데이터가 섞인다.
transform을 통해 데이터를 조작하고 학습에 적합하게 만든다.
모든 TorchVision 데이터셋들은 변형 로직을 갖는, 호출 가능한 객체를 받는 매개변수 두개를 갖는다.
transform: feature를 변경한다.
target_transform: label을 변경한다.
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
FashionMNIST feature은 PIL Image 형식이다.
정답(label)은 integer이다.
학습을 하려면 정규화(normalize)된 텐서 형태의 feature와 one-hot encoding된 텐서 형태의 정답(label)이 필요하다. 이러한 변형(transformation)을 하기 위해 ToTensor와 Lambda를 사용한다.
PIL Image나 NumPy ndarray를 FloatTensor 로 변환한다.
이미지의 픽셀의 intensity 값을 [0., 1.] 범위로 비례하여 조정한다.
사용자 정의 람다(lambda) 함수를 적용한다.
정수를 원-핫으로 부호화된 텐서로 바꾸는 함수를 정의한다.
target_transform = Lambda(lambda y: torch.zeros(
10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))
dataset label 크기 10 zero tensor를 만들고, scatter_ 를 호출하여 주어진 정답 y 에 해당하는 인덱스에 value=1 을 할당한다.
신경망은 데이터에 대한 연산을 수행하는 계층(layer)/모듈(module)로 구성되어 있다.
torch.nn 네임스페이스는 신경망을 구성하는데 필요한 모든 구성 요소를 제공한다. PyTorch의 모든 모듈은 nn.Module 의 하위 클래스(subclass) 이다. 신경망은 다른 모듈로 구성된 모듈이다. 이러한 중첩된 구조는 복잡한 아키텍처를 쉽게 구축하고 관리할 수 있게 한다.
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
torch.cuda 또는 torch.backends.mps 가 사용 가능한지 확인해보고, 그렇지 않으면 CPU를 계속 사용한다.
신경망 모델을 nn.Module의 하위클래스로 정의한다.
__init__ 에서 신경망 계층들을 초기화한다.
nn.Module을 상속받은 모든 클래스는 forward 메소드에 입력 데이터에 대한 연산들을 구현한다.
model = NeuralNetwork().to(device)
신경망 모델(NeuralNetwork)의 인스턴스(instance)를 생성하고 이를 device 로 이동한다.
X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
2차원 텐서의 dim=0은 각 분류(class)에 대한 원시(raw) 예측값 10개가, dim=1에는 각 출력의 개별 값들이 해당한다.
원시 예측값을 nn.Softmax 모듈의 인스턴스에 통과시켜 예측 확률을 얻는다.
(예시)FashionMNIST 모델의 계층들
input_image = torch.rand(3,28,28)
28x28 크기의 이미지 3개로 구성된 미니배치
nn.Flatten 계층을 초기화하여 각 28x28의 2D 이미지를 784 픽셀 값을 갖는 연속된 배열로 변환한다.
flatten = nn.Flatten()
flat_image = flatten(input_image)
weight와 bias를 사용하여 입력에 선형 변환(linear transformation)을 적용하는 모듈이다.
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
비선형 활성화(activation)는 모델의 입력과 출력 사이에 복잡한 관계(mapping)를 만든다.
비선형 활성화는 선형 변환 후에 적용되어 비선형성(nonlinearity) 을 도입하고, 신경망이 다양한 현상을 학습할 수 있도록 돕는다.
hidden1 = nn.ReLU()(hidden1)
nn.Sequential은 순서를 갖는 모듈의 컨테이너이다.
데이터는 정의된 것과 같은 순서로 모든 모듈들을 통해 전달된다.
seq_modules = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
순차 컨테이너(sequential container)를 사용하여 seq_modules 와 같은 신경망을 빠르게 만들 수 있다.
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
신경망의 마지막 선형 계층은 nn.Softmax 모듈에 전달될 ([-infty, infty] 범위의 원시 값(raw value)인) logits 를 반환한다. logits는 모델의 각 분류(class)에 대한 예측 확률을 나타내도록 [0, 1] 범위로 비례하여 조정(scale)된다. dim 매개변수는 값의 합이 1이 되는 차원을 나타낸다.
신경망 내부의 많은 계층들은 매개변수화(parameterize) 된다.
학습 중에 최적화되는 가중치와 편향과 연관지어진다.
nn.Module 을 상속하면 모델 객체 내부의 모든 필드들이 자동으로 추적되며, 모델의 parameters() 및 named_parameters() 메소드로 모든 매개변수에 접근할 수 있게 된다.
torch.autograd automatic differentiation신경망을 학습할 때 가장 자주 사용되는 알고리즘은 backpropagation이다. 이 알고리즘에서, 매개변수(모델 가중치)는 주어진 매개변수에 대한 손실 함수의 gradient에 따라 조정된다.
gradient 계산
PyTorch에는 torch.autograd라고 불리는 자동 미분 엔진이 내장되어 있다.
모든 Computational graph에 대한 gradient의 automatic differentiation을 지원한다.
입력 x, 매개변수 w와 b , 그리고 일부 손실 함수가 있는 가장 간단한 단일 계층 신경망을 가정하면,
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
PyTorch에서 gradient를 계산할 수 있다.
w와 b는 최적화를 해야 하는 매개변수이다.
변수들에 대한 손실 함수의 변화도를 계산할 수 있어야 한다.
해당 텐서에 requires_grad 속성을 설정한다.
연산 그래프를 구성하기 위해 텐서에 적용하는 함수는 Function 클래스의 객체이다. 이 객체는 순전파 방향으로 함수를 계산하는 방법과, 역방향 전파 단계에서 도함수(derivative)를 계산하는 방법을 알고 있다.
역방향 전파 함수에 대한 참조(reference)는 텐서의 grad_fn 속성에 저장된다.
매개변수의 가중치를 최적화하려면 매개변수에 대한 손실함수의 도함수(derivative)를 계산해야 한다.
x와 y의 일부 고정값에서 와 $$\frac{∂𝑙𝑜𝑠𝑠}{∂𝑏}가 필요하다.
이러한 도함수를 계산하기 위해, loss.backward()를 호출한 다음 w.grad와 b.grad에서 값을 가져온다.
loss.backward()
노드들 중 requires_grad=True로 설정된 노드들의 grad 속성만 구할 수 있다.
z = torch.matmul(x, w)+b
print(z.requires_grad)
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
requires_grad=True인 모든 텐서들은 연산 기록을 추적하고 변화도 계산을 지원한다.
그러나 모델을 학습한 뒤 입력 데이터를 단순히 적용하기만 하는 경우와 같이 순전파 연산만 필요한 경우에는, 이러한 추적이나 지원이 필요 없다.
torch.no_grad()로 연산 추적을 멈출 수 있다.
z = torch.matmul(x, w)+b
z_det = z.detach()
다른 방법은 텐서에 detach() 메소드를 사용하는 것이다.
효과
신경망의 일부 매개변수를 고정된 매개변수(frozen parameter)로 표시한다.
변화도를 추적하지 않는 텐서의 연산이 더 효율적이기 때문에, 순전파 단계만 수행할 때 연산 속도가 향상된다.
autograd
: 데이터(텐서)의 연산 및 실행된 모든 연산들(및 연산 결과가 새로운 텐서인 경우도 포함하여)의 기록을 Function 객체로 구성된 방향성 비순환 그래프(DAG; Directed Acyclic Graph)에 저장한다.
PyTorch에서 DAG들은 동적(dynamic)이다.
이 방향성 비순환 그래프(DAG)의 잎(leave)은 입력 텐서이고, 뿌리(root)는 결과 텐서이다.
연쇄 법칙(chain rule)에 따라 변화도를 자동으로 계산할 수 있다.
순전파 단계에서, autograd는 다음 두 가지 작업을 동시에 수행합니다:
1. 요청된 연산을 수행하여 결과 텐서를 계산한다.
2. DAG에 연산의 변화도 기능gradient function 를 유지한다.
역전파 단계는 DAG 뿌리(root)에서 .backward() 가 호출될 때 시작된다.
autograd는 이 때:
각 .grad_fn 으로부터 변화도를 계산한다.
각 텐서의 .grad 속성에 계산 결과를 쌓는다.
연쇄 법칙을 사용하여, 모든 잎(leaf) 텐서들까지 전파(propagate)한다.
스칼라 손실 함수를 가지고 일부 매개변수와 관련한 변화도를 계산해야 한다.
그러나 출력 함수가 임의의 텐서인 경우가 있다.
이럴 때, PyTorch는 실제 변화도가 아닌 야코비안 곱(Jacobian product)을 계산한다.
벡터x에 대한 벡터 y의 변화도는 야코비안 행렬Jacobian matrix로 주어진다.
야코비안 행렬 자체를 계산하는 대신, PyTorch는 주어진 입력 벡터에 대한 야코비안 곱(Jacobian Product) 을 계산한다. v를 인자로 backward를 호출하면 이뤄진다.
backpropagation을 수행할 때, PyTorch가 변화도를 누적해주기 때문에 동일한 인자로 backward를 두차례 호출하면 변화도 값이 달라진다. 제대로 된 변화도를 계산하기 위해서는 grad 속성을 먼저 0으로 만들어야 한다. 실제 학습 과정에서는 옵티마이저(optimizer)가 이 과정을 도와준다.
데이터에 매개변수를 최적화하여 모델을 학습하고, 검증하고, 테스트한다.
모델을 학습하는 과정은 반복적인 과정을 거친다.
각 반복 단계에서 모델은 출력을 추측하고, 추측과 정답 사이의 오류(손실(loss))를 계산하고,매개변수에 대한 오류의 도함수(derivative)를 수집한 뒤, 경사하강법을 사용하여 이 파라미터들을 최적화한다.
Dataset & DataLoader
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
Hyperparameter는 모델 최적화 과정을 제어할 수 있는 조절 가능한 매개변수이다.
하이퍼파라미터 값은 모델 학습과 수렴율(convergence rate)에 영향을 미친다.
에폭(epoch) 수: 데이터셋을 반복하는 횟수
배치 크기(batch size): 매개변수가 갱신되기 전 신경망을 통해 전파된 데이터 샘플의 수다.
학습률(learning rate): 각 배치/에폭에서 모델의 매개변수를 조절하는 비율이다. 값이 작을수록 학습 속도가 느려지고, 값이 크면 학습 중 예측할 수 없는 동작이 발생할 수 있다.
하이퍼파라미터를 설정한 뒤에는 최적화 단계를 통해 모델을 학습하고 최적화할 수 있다. 최적화 단계의 각 iteration을 epoch라고 한다.
epoch 구성
train loop: 학습용 데이터셋을 반복(iterate)하고 최적의 매개변수로 수렴한다.
validation/test loop: 모델 성능이 개선되고 있는지를 확인하기 위해 테스트 데이터셋을 반복(iterate)한다.
손실 함수(loss function)는 획득한 결과와 실제 값 사이의 틀린 정도를 측정한다.
학습 과정에서 이 값을 최소화하려고 한다.
주어진 데이터 샘플을 입력으로 계산한 예측과 label을 비교하여 손실(loss)을 계산한다.
일반적인 손실함수에는 regression에 사용하는 nn.MSELoss(MSE; Mean Square Error)나 분류에 사용하는 nn.NLLLoss (Negative Log Likelihood), 그리고 nn.LogSoftmax와 nn.NLLLoss를 합친 nn.CrossEntropyLoss 등이 있다.
loss_fn = nn.CrossEntropyLoss()
출력 logit을 nn.CrossEntropyLoss에 전달하여 logit을 정규화하고 예측 오류를 계산한다.
최적화는 각 학습 단계에서 모델의 오류를 줄이기 위해 모델 매개변수를 조정하는 과정이다. 최적화 알고리즘은 이 과정이 수행되는 방식을 정의한다. 모든 최적화 logic은 optimizer 객체에 캡슐화(encapsulate)된다.
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
여기서는 SGD 옵티마이저를 사용하고 있으며, PyTorch에는 ADAM이나 RMSProp과 같은 다른 종류의 모델과 데이터에서 더 잘 동작하는 다양한 옵티마이저가 있다.
최적화 단계
1.optimizer.zero_grad(): 모델 매개변수의 변화도를 재설정한다. 기본적으로 변화도는 더해지기(add up) 때문에 중복 계산을 막기 위해 반복할 때마다 명시적으로 0으로 설정한다.
2.loss.backwards(): 예측 손실(prediction loss)을 역전파한다. PyTorch는 각 매개변수에 대한 손실의 변화도를 저장한다.
3. 변화도를 계산한 뒤에는optimizer.step()을 호출하여 역전파 단계에서 수집된 변화도로 매개변수를 조정한다.
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
# 모델을 학습(train) 모드로 설정합니다 - 배치 정규화(Batch Normalization) 및 드롭아웃(Dropout) 레이어들에 중요합니다.
# 이 예시에서는 없어도 되지만, 모범 사례를 위해 추가해두었습니다.
model.train()
for batch, (X, y) in enumerate(dataloader):
# 예측(prediction)과 손실(loss) 계산
pred = model(X)
loss = loss_fn(pred, y)
# 역전파
loss.backward()
optimizer.step()
optimizer.zero_grad()
if batch % 100 == 0:
loss, current = loss.item(), batch * batch_size + len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test_loop(dataloader, model, loss_fn):
# 모델을 평가(eval) 모드로 설정합니다 - 배치 정규화(Batch Normalization) 및 드롭아웃(Dropout) 레이어들에 중요합니다.
# 이 예시에서는 없어도 되지만, 모범 사례를 위해 추가해두었습니다.
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
# torch.no_grad()를 사용하여 테스트 시 변화도(gradient)를 계산하지 않도록 합니다.
# 이는 requires_grad=True로 설정된 텐서들의 불필요한 변화도 연산 및 메모리 사용량 또한 줄여줍니다.
with torch.no_grad():
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
epochs = 10
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train_loop(train_dataloader, model, loss_fn, optimizer)
test_loop(test_dataloader, model, loss_fn)
print("Finish!")
import torch
import torchvision.models as models
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')
PyTorch 모델은 학습한 매개변수를 state_dict라고 불리는 내부 상태 사전(internal state dictionary)에 저장한다. 이 상태 값들은 torch.save 메소드를 사용하여 저장할 수 있다.
model = models.vgg16()
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()
동일한 모델의 인스턴스(instance)를 생성한 다음에 load_state_dict() 메소드를 사용하여 매개변수들을 불러온다.
inference 하기 전에 model.eval() 메소드를 호출하여 dropout과 배치 정규화batch normalization를 evaluation mode로 설정해야 한다.
torch.save(model, 'model.pth')
model을 저장 함수에 전달한다.
model = torch.load('model.pth')
model을 불러온다.