모델/데이터세트 분리
- 파이토치의 모델은 인공 신경망 모듈을 활용해 구현
- 데이터에 대한 연산 수행, 계층 정의, 순방향 연산 수행
- 신경망 패키지의 모듈 클래스 활용
- 새로운 모델 클래스를 생성하려면 모듈 클래스를 상속받아 임의의 서브 클래스를 생성
- 이 클래스는 다른 모듈 클래스를 포함할 수 있으며 트리 구조로 중첩 가능
모듈 클래스
class Model(nn.Module):
def __init__(self):
super().__init__()
self.conv1=nn.Conv2d(1, 20, 5)
self.conv2=nn.Conv2d(20, 20, 5)
def forward(self, x):
x=F.relu(self.conv1(x))
x=F.relu(self.conv2(x))
return x
- 초기화 메서드 init 와 순방향 메서드 forward를 재정의해 활용
- 초기화 메서드 init
- 신경망에 사용될 계층을 정의하기 전에 super 함수로 모듈 클래스의 속성을 초기화
- super 함수로 부모 클래스를 초기화하면 서브 클레스인 모델에서 부모 클래스의 속성 사용 가능
- 모델 클래스 초기화 이후, 학습에 사용되는 계층을 초기화 메서드에 선언
- self.conv1이나 self.conv2와 같은 인스턴스가 모델의 매개변수가 됨
- 순방향 메서드 forward
- 초기화 메서드에서 선언한 모델 매개변수를 활용해 신경망 구조를 설계
- 모델이 데이터 x를 입력받아 학습을 진행하는 일련의 과정을 정의하는 영역
- 모듈 클래스는 호출 가능한 형식 Callable Type 으로 모델의 인스턴스를 호출하는 순간 호출 메서드 call가 순방향 메서드를 실행
- 초기화 메서드에서 super 함수로 부모 클래스를 초기화했으므로 역방향 backward 연산을 정의하지 않아도 됨
- 파이토치의 자동 미분 기능인 Autograd 에서 모델의 매개변수를 역으로 전파해 자동으로 기울기 또는 변화도를 계산해줌 → 별도의 메서드로 역전파 기능 구성 필요X
비선형 회귀
- 생략
import torch
import pandas as pd
from torch import nn
from torch import optim
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, file_path):
df = pd.read_csv(file_path)
self.x = df.iloc[:, 0].values
self.y = df.iloc[:, 1].values
self.length = len(df)
def __getitem__(self, index):
x = torch.FloatTensor([self.x[index] ** 2, self.x[index]])
y = torch.FloatTensor([self.y[index]])
return x, y
def __len__(self):
return self.length
class CustomModel(nn.Module):
def __init__(self):
super().__init__()
self.layer = nn.Linear(2, 1)
def forward(self, x):
x = self.layer(x)
return x
train_dataset = CustomDataset("datasets/non_linear.csv")
train_dataloader = DataLoader(train_dataset, batch_size=128, shuffle=True, drop_last=True)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CustomModel().to(device)
criterion = nn.MSELoss().to(device)
optimizer = optim.SGD(model.parameters(), lr=0.0001)
for epoch in range(10000):
cost = 0.0
for x, y in train_dataloader:
x = x.to(device)
y = y.to(device)
output = model(x)
loss = criterion(output, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
cost += loss
cost = cost / len(train_dataloader)
if (epoch + 1) % 1000 == 0:
print(f"Epoch : {epoch+1:4d}, Model : {list(model.parameters())}, Cost : {cost:.3f}")
모델 평가
- 비선형 회귀에서 학습시킨 모델 사용
with torch.no_grad(): model.eval() inputs = torch.FloatTensor( [ [1 ** 2, 1], [5 ** 2, 5], [11 ** 2, 11] ] ).to(device) outputs = model(inputs) print(outputs)
- torch.no_grad 클래스 활용
- 기울기 계산을 비활성화하는 클래스. 자동 미분 기능을 사용하지 않도록 설정. 메모리 사용량 줄여 추론에 적합한 상태로 변경
- eval 메서드
- 모델 평가 모드
- 모델을 평가모드로 변경하지 않으면 일관성 없는 추론 결과를 반환하기 때문에 평가 시 평가모드 선언 필요
- 다시 학습을 진행하려는 경우, 다시 학습 모드 변경 필요
- 모델 저장
torch.save( model, "../models/model.pt" )
torch.save( model.state_dict(), "../models/model_state_dict.pt" )
데이터세트 분리
- 머신러닝 데이터세트는 훈련용 데이터 Training Data, 테스트 데이터 Testing Data, 검증용 데이터 Validation Data 분리해 활용
- 훈련용 데이터
- 모델 학습하는 데 사용되는 데이터세트
- 검증용 데이터
- 학습이 완료된 모델을 검증하기 위해 사용되는 데이터세트
- 구조가 다른 모델의 성능 비교를 위해 사용되는 데이터세트를 의미
- 계층이나 하이퍼파라미터 차이 등으로 인한 성능 비교를 위해 전체 데이터세트를 분리
- 테스트 데이터
- 검증용 데이터를 통해 결정된 성능이 가장 우수한 모델을 최종 테스트하기 위한 목적으로 사용되는 데이터세트
- 검증용 데이터에 의해 선택된 모델은 검증용 데이터에 과대적합된 모델이거나 검증용 데이터가 해당 모델에 적합한 형태의데이터만 모여 있을 수도 있음
- 기존 과정에서 평가해 보지 않은 새로운 데이터인 테스트 데이터로 최종 모델 성능 평가
- 무작위 분리 함수 random_split
subset=torch.utils.data.random_split( dataset, lengths, generator ) # 사용 예시 dataset = CustomDataset("../datasets/non_linear.csv") dataset_size = len(dataset) train_size = int(dataset_size * 0.8) validation_size = int(dataset_size * 0.1) test_size = dataset_size - train_size - validation_size train_dataset, validation_dataset, test_dataset = random_split(dataset, [train_size, validation_size, test_size]) print(f"Training Data Size : {len(train_dataset)}") print(f"Validation Data Size : {len(validation_dataset)}") print(f"Testing Data Size : {len(test_dataset)}") train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True, drop_last=True) validation_dataloader = DataLoader(validation_dataset, batch_size=4, shuffle=True, drop_last=True) test_dataloader = DataLoader(test_dataset, batch_size=4, shuffle=True, drop_last=True)_size])
- 분리 길이 lengths 만큼 데이터세트 dataset의 서브셋 subset을 생성
- 입력한 데이터세트의 개수와 분리 길이의 총합이 같아야 함
- 생성자 generator는 서브셋에 포함될 무작위 데이터들의 난수 생성 시드를 의미
- 검증용 데이터세트를 통한 평가
with torch.no_grad(): model.eval() for x, y in validation_dataloader: x = x.to(device) y = y.to(device) outputs = model(x) print(f"X : {x}") print(f"Y : {y}") print(f"Outputs : {outputs}") print("--------------------")
모델 저장 및 불러오기
- 파이토치 모델은 직렬화 Serialize와 역직렬화 Deserialize를 통해 객체를 저장하고 불러올 수 있음
- 파이썬의 피클 Pickle을 활용해 파이썬 객체 구조를 바이너리 프로토콜 Binary Protocols로 직렬화함
- 모델에 사용된 텐서나 매개변수를 저장함
- 모델을 불러오려면 저장된 객체 파일을 역직렬화해 현재 프로세스의 메모리에 업로드
- 주로 모델 학습이 모두 완료된 이후에 모델을 저장하거나, 특정 에폭이 끝날 때마다 저장함
- 모델 파일 확장자는 주로 .pt 나 .pth로 저장함
모델 전체 저장/불러오기
- 모델 저장 함수
- 저장하려는 모델 인스턴스 model 와 학습 결과 파일이 생성될 경로 path를 설정해 학습된 모델을 저장
torch.save( model, path )
- 모델 불러오기 함수
- 모델이 저장된 경로 path를 불러와 모델의 매개변수와 메타데이터를 적용해 인스턴스를 생성
- map_location 매개변수: 모델을 불러올 때 적용하려는 장치 상태를 의미
- 모델 학습 상태가 GPU인지 CPU인지 확인하지 않고 모델을 활용할 수 있게, map_location 매개변수로 장치 설정
- 모델을 불러오는 경우에도 동일한 형태의 클래스 선언 필수
- 이 코드처럼 그냥 단순히 load 하면 AttributeError 오류 발생, 모델 로드 불가
import torch device = "cuda" if torch.cuda.is_available() else "cpu" model = torch.load("model/model.pt", map_location=device) print(model)
- 이 코드처럼 그냥 단순히 load 하면 AttributeError 오류 발생, 모델 로드 불가
- 아래와 같이 CustomModel을 동일하게 선언하거나, 다음 모델 구조 확인을 통해 할 수도 있음
# 1) 아예 CustomModel 동일하게 선언 import torch from torch import nn class CustomModel(nn.Module): def __init__(self): super().__init__() self.layer = nn.Linear(2, 1) def forward(self, x): x = self.layer(x) return x device = "cuda" if torch.cuda.is_available() else "cpu" model = torch.load("model/model.pt", map_location=device) print(model) with torch.no_grad(): model.eval() inputs = torch.FloatTensor( [ [1 ** 2, 1], [5 ** 2, 5], [11 ** 2, 11] ] ).to(device) outputs = model(inputs) print(outputs)
model=torch.load( path, map_location )
- 모델 구조 확인
- 모델 전체 파일은 가지고 있으나, 모델 구조를 알 수 없는 경우
# 2) 모델 구조 출력 import torch from torch import nn class CustomModel(nn.Module): pass device = "cuda" if torch.cuda.is_available() else "cpu" model = torch.load("model/model.pt", map_location=device) print(model)
- 모델 전체 파일은 가지고 있으나, 모델 구조를 알 수 없는 경우
모델 상태 저장/불러오기
- 모델 전체를 저장하고 불러오는 방법이 아닌, 모델 상태 값만 저장해 불러오는 방법
- 모델 전체를 저장하면 모델의 모든 정보를 저장 → 많은 저장 공간이 필요
- 모델 상태 저장
torch.save( model.state_dict(), "../models/model_state_dict.pt" )
- model 변수가 아닌 state_dict 메서드로 모델 상태를 저장
- 모델 상태 torch.state_dict: 모델에서 학습이 가능한 매개변수를 순서가 있는 딕셔너리 OrderedDict 형식으로 반환
- 모델 상태 불러오기
import torch from torch import nn class CustomModel(nn.Module): def __init__(self): super().__init__() self.layer = nn.Linear(2, 1) def forward(self, x): x = self.layer(x) return x device = "cuda" if torch.cuda.is_available() else "cpu" model = CustomModel().to(device) model_state_dict = torch.load("model/model_state_dict.pt", map_location=device) model.load_state_dict(model_state_dict) with torch.no_grad(): model.eval() inputs = torch.FloatTensor( [ [1 ** 2, 1], [5 ** 2, 5], [11 ** 2, 11] ] ).to(device) outputs = model(inputs)
- 모델의 상태만 저장했으므로 CustomModel에 학습 결과 반영
- 모델 상태만 불러오면 모델 구조를 알 수 없으므로 CustomModel 클래스가 동일하게 구현돼 있어야 함
- 모델 상태 파일 pt도 torch.load 함수를 통해 불러옴
- 모델의 가중치와 편향을 모델에 적용해야 하기 때문에 model 인스턴스의 load_state_dict 메서드 사용해 모델 상태를 반영함
체크포인트 저장/불러오기
- 체크포인트 Checkpoint: 학습 과정의 특정 지점마다 저장하는 것을 의미
- 데이터의 개수가 많고 깊은 구조의 모델을 학습하면 오랜 시간 소요됨
- → 학습 과정에서 한 번에 전체 에폭을 반복하기 어렵거나, 모종의 이유로 학습 중단 가능성 존재
- → 일정 에폭마다 학습된 결과를 저장, 나중에 이어서 학습
- 체크포인트마다 모델 상태를 저장해 가장 최적화된 모델 상태로 추론 진행 가능
- 체크포인트 저장
# 중략 checkpoint = 1 for epoch in range(10000): cost = 0.0 for x, y in train_dataloader: x = x.to(device) y = y.to(device) output = model(x) loss = criterion(output, y) optimizer.zero_grad() loss.backward() optimizer.step() cost += loss cost = cost / len(train_dataloader) if (epoch + 1) % 1000 == 0: torch.save( { "model": "CustomModel", "epoch": epoch, "model_state_dict": model.state_dict(), "optimizer_state_dict": optimizer.state_dict(), "cost": cost, "description": f"CustomModel 체크포인트-{checkpoint}", }, f"../models/checkpoint-{checkpoint}.pt", ) checkpoint += 1
- 학습을 이어서 진행하기 위한 목적이기 때문에
- 에폭 epoch, 모델 상태 model.state_dict, 최적화 상태 optimizer.state_dict 등은 필수로 포함 필요
- 학습을 이어서 진행하기 위한 목적이기 때문에
- 체크포인트 불러오기
# 중략 checkpoint = torch.load("../models/checkpoint-6.pt") model.load_state_dict(checkpoint["model_state_dict"]) optimizer.load_state_dict(checkpoint["optimizer_state_dict"]) checkpoint_epoch = checkpoint["epoch"] checkpoint_description = checkpoint["description"] print(checkpoint_description) for epoch in range(checkpoint_epoch + 1, 10000): cost = 0.0 for x, y in train_dataloader: x = x.to(device) y = y.to(device) output = model(x) loss = criterion(output, y) optimizer.zero_grad() loss.backward() optimizer.step() cost += loss if (epoch + 1) % 1000 == 0: print(f"Epoch : {epoch+1:4d}, Model : {list(model.parameters())}, Cost : {cost:.3f}")
'Theory > Pytorch' 카테고리의 다른 글
[파이토치 트랜스포머 #6] 3장 파이토치 기초 - 6) 순전파와 역전파, 퍼셉트론 (1) | 2024.04.03 |
---|---|
[파이토치 트랜스포머 #6] 3장 파이토치 기초 - 5) 활성화 함수 (1) | 2024.03.17 |
[파이토치 트랜스포머 #4] 3장 파이토치 기초 - 3) 데이터세트와 데이터로더 (0) | 2024.03.06 |
[파이토치 트랜스포머 #3] 3장 파이토치 기초 - 2) 가설, 손실함수, 최적화 (2) | 2024.03.04 |
[파이토치 트랜스포머 #2] 3장 파이토치 기초 - 1) 텐서 (1) | 2024.02.24 |