모델 저장
- 학습한 모델을 저장장치에 파일로 저장하고 나중에 불러와 사용(추가 학습, 예측 서비스) 할 수 있도록 한다.
- 파이토치는 모델의 파라미터만 저장하는 방법과 모델 구조와 파라미터 모두를 저장하는 두가지 방식을 제공한다.
- 저장 함수
- torch.save(저장할 객체, 저장경로) - 보통 저장파일의 확장자는 pt 나 pth 를 지정한다.
모델 전체 저장하기 및 불러오기
- 저장하기
- torch.save(model, 저장경로) - 불러오기
- load_model = torch.load(저장경로) - 저장시 pickle을 이용해 직렬화하기 때문에 불어오는 실행환경에도 모델을 저장할 때 사용한 클래스가 있어야 한다.
모델의 파라미터만 저장
- 모델을 구성하는 파라미터만 저장한다.
- 모델의 구조는 저장하지 않기 때문에 불러올 때 모델을 먼저 생성하고 생성한 모델에 불러온 파라미터를 덮어씌운다.
- 모델의 파라미터는 state_dict 형식으로 저장한다.
state_dict
- 모델의 파라미터 Tensor들을 레이어 단위별로 나누어 저장한 Ordered Dictionary (OrderedDict)
- 모델객체.state_dict() 메소드를 이용해 조회한다.
- 모델의 state_dict을 조회 후 저장한다.
- torch.save(model.state_dict(), "저장경로") - 생성된 모델에 읽어온 state_dict를 덮어씌운다.
- new_model.load_state_dict(torch.load("state_dict저장경로"))
Checkpoint를 저장 및 불러오기
- 학습이 끝나지 않은 모델을 저장 후 나중에 이어서 학습시킬 경우에는 모델의 구조, 파라미터 뿐만 아니라 optimizer, loss 함수등 학습에 필요한 객체들을 저장해야 한다.
- Dictionary에 필요한 요소들을 key-value 쌍으로 저장후 torch.save()를 이용해 저장한다.
model의 기본적 흐름
# 저장
torch.save({
'epoch':epoch,
'model_state_dict':model.state_dict(),
'optimizer_state_dict':optimizer.state_dict(),
'loss':train_loss
}, "저장경로")
# 불러오기
model = MyModel()
optimizer = optim.Adam(model.parameter())
checkpoint = torch.load("저장경로")
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
#### 이어학습
model.train()
#### 추론
model.eval()
model 생성 class
- nn.Flatten()은 PyTorch의 텐서를 1차원으로 평탄화(flatten)하는 클래스. 이는 다차원 텐서를 1차원으로 변환하여 다층 퍼셉트론(MLP) 등의 신경망 레이어에 입력으로 제공할 수 있게 해준다.
import torch
import torch.nn as nn
class Network(nn.Module):
def __init__(self):
super().__init__()
self.lr = nn.Linear(784, 64) # (input, output) 784 -> 64
self.out = nn.Linear(64, 10) # 64 -> 10
self.relu = nn.ReLU() # 활성화함수
def forward(self, X):
X = torch.flatten(X, start_dim=1) # PyTorch의 텐서를 1차원으로 평탄화
X = self.lr(X)
X = relu(X)
X = out(X)
return X
모델 생성
sample_model = Network() # 모델 생성
모델명.state_dict(): 파라미터들만 조회
## 모델의 파라미터만 저장 -> model.state_dict(): 파라미터들만 조회
state_dict = sample_model.state_dict()
print(type(state_dict)) # OrderedDict: 순서를 유지하는 dictionary
state_dict.keys() # 키값들만 조회
### 레이어객체_변수.weight, 변수.bias
두 코드의 값 비교
lr_weight = state_dict['lr.weight']
lr_bias = state_dict['lr.bias']
print(lr_weight.shape, lr_bias.shape)
lr_weight = state_dict['out.weight']
lr_bias = state_dict['out.bias']
print(lr_weight.shape, lr_bias.shape)
파라미터 저장
### 파라미터 저장
import os
os.makedirs('models/sample')
torch.save(state_dict, "models/sample/sample_state_dict.pth")
파라미터 로드.
### 파라미터 로드.
load_state_dict = torch.load("models/sample/sample_state_dict.pth")
load_state_dict.keys()
새로 모델객체를 생성 -> 읽어온 파라미터로 변환.
# 새로 모델객체를 생성 -> 읽어온 파라미터로 변환.
new_model = Network()
new_model.load_state_dict(load_state_dict)
문제 유형별 MLP 네트워크
- MLP(Multi Layer Perceptron)
- Fully Connected Layer로 구성된 네트워크 - Regression(회귀)
- 예측할 값이 정해져 있지 않는 경우. => 연속형값(실수)을 추론
Boston Housing Dataset
- 보스턴 주택가격 dataset은 다음과 같은 속성을 바탕으로 해당 타운 주택 가격의 중앙값을 예측하는 문제.
- CRIM: 범죄율
- ZN: 25,000 평방피트당 주거지역 비율
- INDUS: 비소매 상업지구 비율
- CHAS: 찰스강에 인접해 있는지 여부(인접:1, 아니면:0)
- NOX: 일산화질소 농도(단위: 0.1ppm)
- RM: 주택당 방의 수
- AGE: 1940년 이전에 건설된 주택의 비율
- DIS: 5개의 보스턴 직업고용센터와의 거리(가중 평균)
- RAD: 고속도로 접근성
- TAX: 재산세율
- PTRATIO: 학생/교사 비율
- B: 흑인 비율
- LSTAT: 하위 계층 비율
- Target
- MEDV: 타운의 주택가격 중앙값(단위: 1,000달러)
선행: !pip install scikit-learn torchinfo 실행
import 및 device 설정
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
import torchinfo
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
Dataset, DataLoader 생성
boston csv 파일 읽어오기
boston = pd.read_csv('data/boston_hosing.csv')
print(boston.shape)
boston.info()
결과값이 되는 MEDV column을 y_boston로 나머지 독립변수들이 담겨있는 column들을 X_boston으로 선언
# X(input data, features), y(output data, target, label) 을 분리
## dataframe/servies.values => ndarray로 변환.
X_boston = boston.drop(columns="MEDV").values
y_boston = boston['MEDV'].to_frame().values
X_boston.shape, y_boston.shape
학습셋/테스트셋 분리
## Trainset/Testset을 분리
## X_boston, y_boston을 섞은 뒤에 0.8 : 0.2 의 비율로 나눈다.
X_train, X_test, y_train, y_test = train_test_split(X_boston,
y_boston,
test_size=0.2,
random_state=0 # seed값 설정 -> 섞이는 순서를 동일
)
# 회귀(정답이 연속형-다 다른값) ==> stratify=y 를 설정하지 않는다. (분류는 필수.)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
MEDV의 값을 정하는 컬럼들의 단위가 서로 일정하지 않기 때문에 가중치를 균등하게 분배를 하지 못한다.
-> 그래서 컬럼들의 값들을 균등한 값으로 맞춰준 값, 표준점수를 구한다.
### Feature scaling => 컬럼들의 scaling(척도-단위)를 맞춰주는 작업.
# 단순 값의 크기에 따라 모델링이 되지 않도록 처리.
## StandardScaler => 모든 컬럼의 척도를 평균-0, 표준편차-1 로 맞춘다.
## Feature scaling은 Train set의 평균과 표준편차를 이용해 train set/test set의 값들에 적용한다.
X_train_mean = X_train.mean(axis=0) # 컬럼별 평균
X_train_std = X_train.std(axis=0) # 컬럼별 표준편차
X_train_scaled_raw = (X_train - X_train_mean)/X_train_std # (각원소값 - 평균)/표준편차 = 표준점수(Z score)
값을 확인
- X_train_scaled_raw.mean(axis=0) -> 0에 가까운 값을 가진다.
- X_train_scaled_raw.std(axis=0) -> 1에 가까운 값을 가진다.
X_train_scaled_raw.mean(axis=0)
X_train_scaled_raw.std(axis=0)
fit -> 학습, transform -> 변환
#### Sklearn을 이용해 Standard Scaling 처리
scaler = StandardScaler()
scaler.fit(X_train) # 어떻게 변환할지 학습 -> 평균/표준편차 계산
X_train_scaled = scaler.transform(X_train) # 변환.
X_test_scaled = scaler.transform(X_test)
# X_train의 평균/표준편차 기준으로 testset도 변환 ==> 모델을 좀더 정확하게 평가하기 위해.
- train set: 모델을 학습 ==> 이전(과거) 데이터를 대표하는 샘플
- test set: 모델을 평가 ==> 앞으로 예측할(미래) 데이터를 대표하는 샘플
torch.tensor() -> torch.tensor로 변환
# X, y: ndarray => torch.Tensor 로 변환
X_train_scaled = torch.tensor(X_train_scaled, dtype=torch.float32)
X_test_scaled = torch.tensor(X_test_scaled, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32)
y_test = torch.tensor(y_test, dtype=torch.float32)
tensor로 변환한 데이터들을 Dataset으로 만든다.
# Dataset 생성
boston_trainset = TensorDataset(X_train_scaled, y_train)
boston_testset = TensorDataset(X_test_scaled, y_test)
print("Dataset의 데이터 개수:", len(boston_trainset), len(boston_testset))
boston_trainset[0]
DataLoader 생성
# DataLoader 생성
boston_trainloader = DataLoader(boston_trainset,
batch_size=200,
shuffle=True,
drop_last=True)
boston_testloader = DataLoader(boston_testset,
batch_size=len(boston_testset))
print("epoch당 step수:", len(boston_trainloader), len(boston_testloader))
모델정의
X_boston.shape
y_boston.shape
BostonModel 모델 정의
class BostonModel(nn.Module):
def __init__(self):
# nn.Module의 __init__() 실행 => 초기화.
super().__init__()
# forward propagation(예측) 할때 필요한 Layer들 생성.
## 입력 feature: 13, 출력 feature: 32 => weight: 13(weight수) x 32(unit수)
self.lr1 = nn.Linear(in_features=13, out_features=32)
self.lr2 = nn.Linear(32, 16)
## lr3 -> 출력 Layer: out_features=모델이 출력해야할 값의 개수에 맞춰준다.
self.lr3 = nn.Linear(16, 1) # 집값(중앙값) 하나를 예측해야 하므로 1로 설정.
def forward(self, X):
out = self.lr1(X) # 선형
out = nn.ReLU()(out) # 비선형
out = self.lr2(out) # 선형
out = nn.ReLU()(out) # 비선형
out = self.lr3(out) # 출력 레이어(이 값이 모델의 예측값이 된다.)
# 회귀의 출력결과에는 Activation 함수를 적용하지 않는다.
# 예외: 출력값의 범위가 정해져 있고 그 범위값을 출력하는 함수가 있을경우에는 적용 가능
# 범위: 0 ~ 1 -> logistic (nn.Sigmoid())
# -1 ~ 1 -> tanh (nn.Tanh())
return out
모델 생성 및 구조 확인
# 모델 생성
boston_model = BostonModel()
## 모델 구조 확인
print(boston_model) # attribute로 설정된 Layer들을 확인.
summary
## 모델 구조 확인 - 연산 흐름 + output 의 shape 등등
# (모델, 입력데이터의 shape(batch_size, features))
torchinfo.summary(boston_model, (100, 13))
# lr1 => in: 13, out: 32
# weight 개수: 13 * 32
# bias 개수: 13 (유닛수)
13 * 32 + 32
학습(Train) : 학습 + 검증
하이퍼파라미터, 모델, ... 정의
# 하이퍼파라미터 (우리가 설정하는 파라미터) 정의
N_EPOCH = 1000
LR = 0.001
# 모델 준비
boston_model = boston_model.to(device) # 모델: 1. 생성 2. device를 설정.
# loss 함수 정의 - 회귀: mse
loss_fn = nn.MSELoss()
# optimizer 정의
optimizer = torch.optim.RMSprop(boston_model.parameters(), lr=LR)
# torch.optim 모듈에 최적화알고리즘들이 정의. (모델의 파라미터, 학습률)
에폭(epoch)별 학습 결과를 저장할 리스트
# 에폭별 학습 결과를 저장할 리스트
## train loss와 validation loss 를 저장.
train_loss_list = []
valid_loss_list = []
Train (학습/훈련) 코드
- train mode와 eval mode로 나뉜다.
import time
## Train (학습/훈련)
### 두단계 -> Train + Validation => step별로 train -> epoch 별로 검증.
s = time.time()
for epoch in range(N_EPOCH):
### 한 epoch에 대한 train 코드
######################################
# train - 모델을 train mode로 변경
######################################
boston_model.train() # train 모드로 변경
train_loss = 0.0 # 현재 epoch의 train loss를 저장할 변수
### batch 단위로 학습 => step
for X, y in boston_trainloader:
## 한 STEP에 대한 train 코드
# 1. X, y 를 device로 옮긴다. => 모델과 동일한 device에 위치시킨다.
X, y = X.to(device), y.to(device)
# 2. 모델 추정(예측) => forward propagation
pred = boston_model(X)
# 3. loss 계산
loss = loss_fn(pred, y) # 오차계산 -> grad_fn
# 파라미터 업데이트
# 4. 파라미터 초기화
optimizer.zero_grad()
# 5. back propagation -> 파라미터들의 gradient값들을 계산.
loss.backward() # 모든 weight와 bias 에 대한 loss의 gradient들을 구한다. - 변수의 grad 속성에 저장.
# 6. 파라미터 업데이터
optimizer.step()
# 7. 현 step의 loss를 train_loss에 누적
train_loss += loss.item()
# train_loss의 전체 평균을 계산 (step별 평균loss의 합계 -> step수로 나눠서 전체 평균으로 계산.)
train_loss /= len(boston_trainloader) # step수로 나누기.
############################################
# validation - 모델을 평가(eval) mode로 변경
# - 검증, 평가, 서비스 할때.
# - validation/test dataset으로 모델을 평가.
############################################
boston_model.eval() # 평가 모드로 변경.
# 검증 loss를 저장할 변수
valid_loss = 0.0
# 검증은 gradient 계산할 필요가 없음. forward propagation시 도함수를 구할 필요가 없다.
with torch.no_grad():
for X_valid, y_valid in boston_testloader:
# 1. X, y를 device로 이동.
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
# 2. 모델을 이용해 예측
pred_valid = boston_model(X_valid)
# 3. 평가 - MSE
valid_loss += loss_fn(pred_valid, y_valid).item()
# valid_loss 평균
valid_loss /= len(boston_testloader)
# 현 epoch에 대한 학습 결과 로그를 출력 + list에 추가
print(f"[{epoch+1}/{N_EPOCH}] train loss: {train_loss:.4f}, valid loss: {valid_loss:.4f}")
train_loss_list.append(train_loss)
valid_loss_list.append(valid_loss)
e = time.time()
걸린시간 나타내는 코드
# 회귀 -> loss: mse, 평가: mse, rmse (Root Mean squared error)
print('걸린시간:', (e-s), "초")
train loss, valid loss 의 epoch 별 변화의 흐름 시각화.
## train loss, valid loss 의 epoch 별 변화의 흐름 시각화.
plt.plot(range(1, N_EPOCH+1), train_loss_list, label="Train Loss")
plt.plot(range(1, N_EPOCH+1), valid_loss_list, label="Validation Loss")
plt.xlabel("EPOCH")
plt.ylabel("Loss")
plt.ylim(3, 50)
plt.legend()
plt.show()
모델저장
- 모델 전체 저장 및 불러오기
- 모델구조, 파라미터 저장
저장경로 설정 및 저장
save_path = "models/boston_model.pth"
torch.save(boston_model, save_path)
모델 불러오기 및 정보 확인(summary)
load_boston_model = torch.load(save_path)
torchinfo.summary(load_boston_model, (200, 13))
예측하고 오차 계
# 예측 서비스
new_X = torch.cat([boston_testset[0][0], boston_testset[1][0]])
new_X = new_X.reshape(-1, 13)
new_X.shape #[2: 데이터수, 13: feature수]
boston_testset[0][0] # 첫번째 데이터 => X, y
boston_testset[1][0]
torch.cat([boston_testset[0][0], boston_testset[1][0]]).reshape(-1, 13)
예측값 pred_new 생성
pred_new = load_boston_model(new_X)
print(pred_new)
오차 계산
boston_testset[0][1], boston_testset[1][1]
loss_fn(pred_new[1], boston_testset[1][1]) # loss 계산
state_dict 저장 및 로딩
- 모델 파라미터만 저장
모델의 state_dict 저장
save_path2 = "models/boston_model_statedict.pth"
# 모델에서 state_dict를 조회
model_sd = boston_model.state_dict()
torch.save(model_sd, save_path2)
state_dict 불러오기
# 불러오기
## state_dict를 불러오기
load_sd = torch.load(save_path2)
print(type(load_sd))
## 새로운 모델을 생성한 뒤에 파라미터를 변경.
new_model = BostonModel()
new_model.load_state_dict(load_sd)
pred_new와 값이 같은 것을 확인할 수 있다. (파라미터를 전와 같은것으로 사용했기 떄문이다.)
pred_new2 = new_model(new_X)
pred_new2
분류 (Classification)
- 예측할 값이 정해져 있는 경우. ==> 범주형인 경우.
- 다중분류
- 범주값(class)가 여러인 경우 - 이진분류
- 범주값: 0/1 => 맞는지 틀린지를 추정 문제.
- 맞는것: Posivitve -> 1
- 틀린것: Negative -> 0 - Fashion MNIST Dataset - 다중분류(Multi-Class Classification) 문제
- 10개의 범주(category)와 70,000개의 흑백 이미지로 구성된 패션 MNIST 데이터셋. 이미지는 해상도(28x28 픽셀)가 낮고 다음처럼 개별 의류 품목을 나타낸다.
- 이미지는 28x28 크기이며 Gray scale이다. 레이블(label)은 0에서 9까지의 정수 배열이다. 아래 표는 이미지에 있는 의류의 클래스(class)들이다.

import 및 device 설정
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import torchinfo
device = "cuda" if torch.cuda.is_available() else "cpu"
print(device)
Dataset 생성
### Dataset, DataLoader 생성
#### Built in Dataset
fmnist_trainset = datasets.FashionMNIST(root="datasets", train=True, download=True,
transform=transforms.ToTensor())
fmnist_testset = datasets.FashionMNIST(root="datasets", train=False, download=True,
transform=transforms.ToTensor())
학습셋과 테스트셋의 데이터 개수 및 정보 확인
# 데이터개수
print("train 개수:", len(fmnist_trainset))
print("test 개수:", len(fmnist_testset))
print(fmnist_trainset)
print()
print(fmnist_testset)
index to class, class to index
# index to class
index_to_class = np.array(fmnist_trainset.classes) # fany indexing을 위해서 list->ndarray
index_to_class[[1, 1, 2, 3, 0]]
# class to index
class_to_index = fmnist_trainset.class_to_idx
class_to_index
이미지 확인
# 이미지 확인
idx = 100
x, y = fmnist_trainset[idx] # Dataset[i] : (X, y)
plt.imshow(x[0], cmap='gray') # x: (channel: 1, height, width)
plt.title(index_to_class[y])
plt.show()
DataLoader 생성
### DataLoader
fmnist_trainloader = DataLoader(fmnist_trainset, batch_size=128, shuffle=True,
drop_last=True)
fmnist_testloader = DataLoader(fmnist_testset, batch_size=128)
모델 정의
- 이미지의 픽셀(28*28)이 column이 된다.
- 784 -> 2048, 2048 -> 1024 ...... -> 10(output):클래스 개수
### 모델 정의
class FashionMNISTModel(nn.Module):
def __init__(self):
super().__init__()
# 입력 이미지를 받아서 처리후 리턴
self.lr1 = nn.Linear(28*28, 2048) # 784 -> 2048
self.lr2 = nn.Linear(2048, 1024) # 2048 -> 1024
self.lr3 = nn.Linear(1024, 512) # 1024 -> 512
self.lr4 = nn.Linear(512, 256) # 512 -> 256
self.lr5 = nn.Linear(256, 128) # 256 -> 128
self.lr6 = nn.Linear(128, 64) # 128 -> 64
# output - out_features: 다중분류- class 개수 (fashion mnist: 10)
self.lr7 = nn.Linear(64, 10) # 각 클래스별 확률이 출력되도록 한다.
def forward(self, X):
# X: (batch, channel, height, width) ====> (batch, channel*height*width)
# out = torch.flatten(X, start_dim=1)
out = nn.Flatten()(X)
# lr1 ~ lr7
## forward 처리를 구현. => Linear -> ReLU() (lr7의 출력은 ReLU에 넣지 마세요.)
out = nn.ReLU()(self.lr1(out))
out = nn.ReLU()(self.lr2(out))
out = nn.ReLU()(self.lr3(out))
out = nn.ReLU()(self.lr4(out))
out = nn.ReLU()(self.lr5(out))
out = nn.ReLU()(self.lr6(out))
### output
out = self.lr7(out)
return out
모델 생성
## 모델 생성 및 확인
f_model = FashionMNISTModel()
print(f_model)
summary
torchinfo.summary(f_model, (128, 1, 28, 28))
모델을 이용한 추정결과
### 모델 추정결과 형태를 확인
i = torch.ones((2, 1, 28, 28)) # 1 x 28 x 28 이미지 2장
y_hat = f_model(i)
y_hat[0] # 첫번째 이미지에대한 추론결과
print(y_hat[0].shape)
# 정답 class => 예측결과 10중에서 가장 큰값이 있는 index
print(y_hat.argmax(axis=-1)) # 첫번째: 가장큰값이 있는 index, 두번째: 가장큰값이 있는 index
index_to_class[y_hat.detach().numpy().argmax(axis=-1)]
# requires_grad=True인 Tensor를 ndarray로 변환할 때는
## tensor.detach() 를 한 다음 변환해야 한다.
10개의 인덱스 중에 가장 큰 값의 확률을 가진 인덱스가 정답이 되고, 확률과 그 인덱스의 라벨(종류)를 가져온다
- Softmax: 확률
- argmax: 가장 큰 확률의 종류
# y_hat => 확률값으로 변환 ===> Softmax()
y_hat_probability = nn.Softmax(dim=-1)(y_hat)
y_hat_probability#.sum(dim=-1)
# 정답의 확률, 정답 라벨
y_hat_probability.max(dim=-1).values, y_hat_probability.argmax(dim=-1)
학습
학습을 위한 하이퍼파라미더 설정 및 device
- 모델과 데이터베이스는 항상 같은 곳(device)에 위치해야 한다.
- 다중분류시 loss함수 -> CrossEntropyLoss 사용
#### 학습(Train)
## 하이퍼파라미터
LR = 0.001
N_EPOCH = 20
# 모델을 device 로 이동.
f_model = f_model.to(device)
# loss fn -> 다중분류: nn.CrossEntropyLoss() ==> 다중 분류용 Log loss
loss_fn = nn.CrossEntropyLoss()
# optimizer
optimizer = torch.optim.Adam(f_model.parameters(), lr=LR)
학습 및 검증 코드
# train
import time
## 각 에폭별 학습이 끝나고 모델 평가한 값을 저장.
train_loss_list = []
valid_loss_list = []
valid_acc_list = [] # test set의 정확도 검증 결과 => 전체데이터 중 맞은데이터의 개수
s = time.time()
for epoch in range(N_EPOCH):
######### train
f_model.train() # train 모드로 변경
train_loss = 0.0 # 현재 epoch의 tain set의 loss
for X_train, y_train in fmnist_trainloader:
# 1. device로 옮기기. model과 같은 device로 옮긴다.
X_train, y_train = X_train.to(device), y_train.to(device)
# 2. 예측 - 순전파
pred_train = f_model(X_train)
# 3. Loss 계산
loss = loss_fn(pred_train, y_train) # (예측, 정답)
# 4 모델 파라미터 업데이트
## 4-1 gradient 초기화
optimizer.zero_grad()
## 4-2 grad 계산 - (오차) 역전파
loss.backward()
## 4-3 파라미터 업데이트
optimizer.step()
# train loss를 누적
train_loss += loss.item() # tensor에 들어가 있는값을 파이썬값으로 누적
# 1에폭 학습 종료 => train_loss의 평균을 list에 저장.
train_loss /= len(fmnist_trainloader) # 누적_train_loss/step수
train_loss_list.append(train_loss)
######### validation
f_model.eval() # validation 모드로 변경
valid_loss = 0.0 # 현재 epoch의 validation loss 저장할 변수
valid_acc = 0.0 # 현재 epoch의 validation accuracy(정확도)를 저장할 변수
### 정확도: 맞은것의 개수 / 전체 개수
with torch.no_grad(): # 도함수 구할 필요가 없으므로 no grad context manager에서 실행.
for X_valid, y_valid in fmnist_testloader:
# 1. device로 옮기기
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
# 2. 예측
pred_valid = f_model(X_valid) # class별 정답일 가능성을 출력 (batch, 10)
pred_label = pred_valid.argmax(dim=-1) # 정답 class를 조회. (pred_valid에서 가장 큰값을 가진 index)
# 3. 평가
## 3.1 loss 계산
loss_valid = loss_fn(pred_valid, y_valid) ## loss_fn() batch만큼 평균을 계산.
valid_loss += loss_valid
## 3.2 정확도 계산
valid_acc += torch.sum(pred_label == y_valid).item()
# 한 epoch에 대한 평가 완료 => valid_loss_list, valid_acc_list에 추가
valid_loss /= len(fmnist_testloader) # step수로 나눠서 평균을 계산
valid_acc /= len(fmnist_testloader.dataset) # testset의 총 데이터 개수로 나눔.
valid_loss_list.append(valid_loss)
valid_acc_list.append(valid_acc)
print(f"[{epoch+1:02d}/{N_EPOCH}] train loss: {train_loss} valid loss: {valid_loss} valid acc: {valid_acc}")
e = time.time()
결과시각화
# 결과 시각화
plt.rcParams["font.family"] = "Malgun gothic"
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(train_loss_list, label="train")
plt.plot(valid_loss_list, label="validation")
plt.title("epoch 별 loss 변화")
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(valid_acc_list)
plt.title("Validation Accuracy")
plt.tight_layout()
plt.show()

- train 데이터의 loss는 학습마다 작아지지만, validation의 loss는 작아지다가 커지는 구간이 발생한다.
-> 이 구간을 과대적합이라고 한다. 과대적합이 발생하지 않도록 적당한 학습이 필요하다.
모델 학습이 진행되면 어느 시점부터 성능이 떨어지기 시작한다. (trainset으로 검증한 결과는 계속 좋아지는데
validation set으로 검증한 결과는 성능이 좋아지다 안좋아진다.)
1. 학습 도중 성능 개선될 때마다 저장. (가장 좋은 성능의 모델을 서비스 할 수 있게한다.)
2. 더이상 성능개선이 안되면 학습을 중지(조기종료)
import time
LR = 0.001
N_EPOCH = 1000
# 모델을 device 로 이동.
f_model = FashionMNISTModel()
f_model = f_model.to(device)
# loss fn -> 다중분류: nn.CrossEntropyLoss() ==> 다중 분류용 Log loss
loss_fn = nn.CrossEntropyLoss()
# optimizer
optimizer = torch.optim.Adam(f_model.parameters(), lr=LR)
########################################
# 조기종료 + 모델 저장을 위한 변수 추가
########################################
###### 모델 저장을 위한변수
# 학습 중 가장 좋은 성능 평가지표를 저장. 현 epoch의 지표가 이 변수값보다 좋으면 저장
# 평가지표: validation loss
best_score = torch.inf
save_model_path = "models/fashion_mnist_best_model.pth"
###### 조기 종료를 위한 변수: 특정 epoch동안 성능 개선이 없으면 학습을 중단
patience = 5 # 성능이 개선 될지를 기다릴 epoch 수. patience 번 만큼 개선이 안되면 중단.(보통 10이상 지정)
trigger_cnt = 0 # 성능 개선을 몇번 째 기다리는 지 정할 변수. patience==trigger_cnt : 중단
# train
## 각 에폭별 학습이 끝나고 모델 평가한 값을 저장.
train_loss_list = []
valid_loss_list = []
valid_acc_list = [] # test set의 정확도 검증 결과 => 전체데이터 중 맞은데이터의 개수
s = time.time()
for epoch in range(N_EPOCH):
######### train
f_model.train()
train_loss = 0.0 # 현재 epoch의 tain set의 loss
for X_train, y_train in fmnist_trainloader:
# 1. device로 옮기기. model과 같은 device로 옮긴다.
X_train, y_train = X_train.to(device), y_train.to(device)
# 2. 예측 - 순전파
pred_train = f_model(X_train)
# 3. Loss 계산
loss = loss_fn(pred_train, y_train) # (예측, 정답)
# 4 모델 파라미터 업데이트
## 4-1 gradient 초기화
optimizer.zero_grad()
## 4-2 grad 계산 - (오차) 역전파
loss.backward()
## 4-3 파라미터 업데이트
optimizer.step()
# train loss를 누적
train_loss += loss.item()
# 1에폭 학습 종료 => train_loss의 평균을 list에 저장.
train_loss /= len(fmnist_trainloader) # 누적_train_loss/step수
train_loss_list.append(train_loss)
######### validation
f_model.eval()
valid_loss = 0.0 # 현재 epoch의 validation loss 저장할 변수
valid_acc = 0.0 # 현재 epoch의 validation accuracy(정확도)를 저장할 변수
### 정확도: 맞은것의 개수 / 전체 개수
with torch.no_grad(): # 도함수 구할 필요가 없으므로 no grad context manager에서 실행.
for X_valid, y_valid in fmnist_testloader:
# 1. device로 옮기기
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
# 2. 예측
pred_valid = f_model(X_valid) # class별 정답일 가능성을 출력 (batch, 10)
pred_label = pred_valid.argmax(dim=-1) # 정답 class를 조회. (pred_valid에서 가장 큰값을 가진 index)
# 3. 평가
## 3.1 loss 계산
loss_valid = loss_fn(pred_valid, y_valid) ## loss_fn() batch만큼 평균을 계산.
valid_loss += loss_valid.item()
## 3.2 정확도 계산
valid_acc += torch.sum(pred_label == y_valid).item()
# 한 epoch에 대한 평가 완료 => valid_loss_list, valid_acc_list에 추가
valid_loss /= len(fmnist_testloader) # step수로 나눠서 평균을 계산
valid_acc /= len(fmnist_testloader.dataset) # testset의 총 데이터 개수로 나눔.
valid_loss_list.append(valid_loss)
valid_acc_list.append(valid_acc)
print(f"[{epoch+1:02d}/{N_EPOCH}] train loss: {train_loss} valid loss: {valid_loss} valid acc: {valid_acc}")
##################################
# 조기종료여부, 모델 저장 처리
# 저장: 현 epoch valid_loss 가 best_score 보다 개선된 경우 저장(작으면 개선)
#################################
if valid_loss < best_score: # 성능이 개선된 경우.
#저장 로그 출력
print(f"====> 모델저장: {epoch+1} Epoch - 이전 valid_loss: {best_score}, 현재 valid_loss: {valid_loss}")
# best_score교체
best_score = valid_loss
# 저장
torch.save(f_model, save_model_path)
# trigger_cnt 를 0으로 초기화
trigger_cnt = 0
else: # 성능개선이 안된경우.
# trigger_cnt를 1 증가
trigger_cnt += 1
if patience == trigger_cnt: # patience 만큼 대기 ==> 조기 종료
#로그
print(f"=====> {epoch+1} Epoch에서 조기종료-{best_score}에서 개선 안됨")
break
e = time.time()
저장된 모델 로딩
# 저장된 모델 로딩
best_model = torch.load(save_model_path)
best_model
위에서 학습 및 저장된 모델로 평가
# test_dataloader로 평가
best_model = best_model.to(device)
best_model.eval()
valid_loss = 0.0 # 현재 epoch의 validation loss 저장할 변수
valid_acc = 0.0 # 현재 epoch의 validation accuracy(정확도)를 저장할 변수
### 정확도: 맞은것의 개수 / 전체 개수
with torch.no_grad(): # 도함수 구할 필요가 없으므로 no grad context manager에서 실행.
for X_valid, y_valid in fmnist_testloader:
# 1. device로 옮기기
X_valid, y_valid = X_valid.to(device), y_valid.to(device)
# 2. 예측
pred_valid = best_model(X_valid) # class별 정답일 가능성을 출력 (batch, 10)
pred_label = pred_valid.argmax(dim=-1) # 정답 class를 조회. (pred_valid에서 가장 큰값을 가진 index)
# 3. 평가
## 3.1 loss 계산
loss_valid = loss_fn(pred_valid, y_valid) ## loss_fn() batch만큼 평균을 계산.
valid_loss += loss_valid.item()
## 3.2 정확도 계산
valid_acc += torch.sum(pred_label == y_valid).item()
# 한 epoch에 대한 평가 완료 => valid_loss_list, valid_acc_list에 추가
valid_loss /= len(fmnist_testloader) # step수로 나눠서 평균을 계산
valid_acc /= len(fmnist_testloader.dataset) # testset의 총 데이터 개수로 나눔.
값 확인
print(valid_loss, valid_acc)
'Data_Analysis_Track_33 > Python' 카테고리의 다른 글
| Python_Deeplearning_pytorch_06(딥러닝모델_성능개선) (0) | 2023.10.25 |
|---|---|
| Python_Deeplearning_pytorch_05-2(모델저장_문제 유형별 모델 생성(이진분류), 정리) (0) | 2023.10.24 |
| Python_Deeplearning_pytorch_04(Dataset 과 DataLoader) (0) | 2023.10.19 |
| Python_Deeplearning_pytorch_03(딥러닝-MLP구현) (0) | 2023.10.17 |
| Python_Deeplearning_pytorch_02-2(gradient_decending) (1) | 2023.10.17 |