모델/데이터세트 분리
파이토치의 모델은 인공 신경망 모듈을 활용해 구현
데이터에 대한 연산 수행, 계층 정의, 순방향 연산 수행
신경망 패키지의 모듈 클래스 활용
새로운 모델 클래스를 생성하려면 모듈 클래스를 상속받아 임의의 서브 클래스를 생성
이 클래스는 다른 모듈 클래스를 포함할 수 있으며 트리 구조로 중첩 가능
모듈 클래스
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 매개변수로 장치 설정
모델을 불러오는 경우에도 동일한 형태의 클래스 선언 필수
아래와 같이 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
)
모델 구조 확인
모델 상태 저장/불러오기
모델 전체를 저장하고 불러오는 방법이 아닌, 모델 상태 값만 저장해 불러오는 방법
모델 전체를 저장하면 모델의 모든 정보를 저장 → 많은 저장 공간이 필요
모델 상태 저장
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}")