딥러닝(AI학습 43)

이유진·2024년 7월 8일

--25.PyTorch 모델 학습.ipynb--

PyTorch 모델학습

import numpy as np
import matplotlib.pyplot as plt
import os

import torch
from torchvision import datasets, transforms

import torch.nn as nn
import torch.nn.functional as F

Layer 쌓기

class Net(nn.Module) : # nn.Module을 상속받아 클래스 정의

생성자에는 '학습(train)' 이 가능한 것. 즉, weight가 들어있는 레이어

ex) Conv2d, Linear, ...

def init(self) :
super(Net, self).init()
self.conv1 = nn.Conv2d(1, 20, 5, 1) # (in-channel, out-channel, kernel-size, stride)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4 4 50, 500) # (in-feature, out-feature)
self.fc2 = nn.Linear(500, 10)

생성자에 '학습' 이 가능한 레이어들을 만들어 놓았으니

이를 적용한 모델을 작성

def forward(self, x) : # x : 입력

# 1. Feature Extraction
x = F.relu(self.conv1(x))     # Conv 결과는 relu 활성화 함수를 거치게 한다.
x = F.max_pool2d(x, 2, 2)     # (input, kernel, stride)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)

# conv1 입력 (1, 28, 28) => conv1 => (20, 24, 24) => maxpool => (20, 12, 12)
# conv2 입력 (20, 12, 12) => conv2 => (50, 8, 8) => maxpool => (50, 4, 4)

# 2. Classification (Fully connected)
# view(batchsize, ) <= batch size 는 몇개일지 모르기 때문에 -1로 지정
x = x.view(-1, 4 * 4 * 50)
x = F.relu(self.fc1(x))
x = self.fc2(x)

# 3. output은 softmax
return F.log_softmax(x, dim=1)

model = Net() # 모델 생성!

base_path = r'./'

이미지 하나 필요

train_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path,
train = True, download=True,
transform=transforms.Compose([transforms.ToTensor()])
),
batch_size = 1,
)

image, label = next(iter(train_loader))

image.shape, label.shape

result = model.forward(image)

result

레이어 하나하나씩 불러올수도 있다.

model.conv1

model.conv1(image)

Preprocess

import torch.optim as optim # Optimizer

no_cuda = False

use_cuda = not no_cuda and torch.cuda.is_available()
device = torch.device('cuda' if use_cuda else 'cpu')
device

DataLoader() 에서 shuffle = True 를 주어 랜덤으로 데이터 섞을 때

수행 할 때 마다 동일하게 섞일 수 있도록 seed 값 준비

seed = 1

batch_size = 64
test_batch_size = 64

torch.manual_seed(seed)

train_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path, train = True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307, ), (0.3081, ))
])),
batch_size = batch_size, shuffle = True)

test_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path, train = False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307, ), (0.3081, ))
])),
batch_size = test_batch_size, shuffle = True)

Optimization

import torch.optim as optim # Optimizer

model = Net().to(device) # model을 생성하여 device에 컴파일 해준다.

학습 parameter들 확인 가능

params = list(model.parameters())
params

len(params) # 학습 가능 레이어 4개 + 4(각 레이어 마다 bias)

torch.Tensor 객체 8개

for param in params :
print(param.size())

"""
torch.Size([20, 1, 5, 5]) <-- conv1 의 weight size
torch.Size([20]) <-- conv1 의 bias (out channel의 개수만큼)
torch.Size([50, 20, 5, 5]) <-- conv2 의 weight size
torch.Size([50]) <-- conv1 의 bias (out channel의 개수만큼)
torch.Size([500, 800]) <-- fc1 의 weight size (out_channel, in_channel)
torch.Size([500]) <-- fc1 의 bias (out_channel의 개수만큼)
torch.Size([10, 500]) <-- fc2 의 weight size
torch.Size([10]) <-- fc2 의 bias
"""
None

Optimizer 설정

optimizer = optim.SGD(model.parameters(),
lr=0.001, # learning rate
momentum=0.5)

optimizer

학습 전

  • 학습하기 전에 Model 이 Train 할 수 있도록 Train mode 전환
  • Conv. 또는 Linear 분 아닐 Dropout 같은 parameter를 가진 layer들도 준비

train mode 전환

model.train()

train mode (학습) ↔ evaluation mode (테스트, 예측)

train mode 했다가 evalutaion mode 했다가 다시 train mode 로 돌아올때에도 .train() 을 해주어야 합니다

  • 모델에 넣기 위한 첫 Batch 추출

※ 일단 '하나' 씩 넣어서 확인, 나중에는 for로 돌릴 예정

data, target = next(iter(train_loader)) # 첫 batch 꺼내기. (현재 64로 세팅)

data.shape, target.shape

  • 추출한 batch 데이터를 device에 compile

data, target = data.to(device), target.to(device)

data.shape, target.shape

학습하기전에 Optimizer를 clear 해주어야한다.

zero_grad() : gradient를 clear 해서 새로운 최적값을 찾기 위한 준비|

optimizer.zero_grad()

준비한 데이터를 model에 input으로 넣어 output 얻음

output = model(data) # 예측값 아님! (지금은 train mode 다!) 잠시후 loss 값 계산해야한다.

output.shape

  • Model의 output 값을 loss function 에 넣기

Negative Log-likelihood loss 하는 loss function

loss = F.nll_loss(output, target)
loss

  • back propagation 을 통해 gradient 계산

loss.backward() # 기울기 (gradient) 계산

기울기 계산 후에 Optimizer를 통해 parameter 업데이트 해주어야 한다.

optimizer.step()

이상이 '학습' 의 "1 스텝"입니다

  • train 모드 변환
  • 데이터 넣어주고
  • 기울기 clear
  • model 에 데이터 넣고
  • loss 계산하고
  • back propagation 하여 gradient 계산하고
  • parameter 업데이트

Start Training

위의 최적화 과정을 반복하여 학습시작

필요한 hyper parameter들 설정

epochs = 1
log_interval = 100 # 로그를 확인하기 위해 몇 스텝마다 로그 출력할지 결정

for epoch in range(1, epochs + 1) :

1 train 모드 변환

model.train()

for 한번순환 할 때 마다 한번 학습이 이루어짐 (batch 단위로 학습) -> weight 최신화

2. 데이터 넣어주기

for batch_idx, (data, target) in enumerate(train_loader) :

# 데이터를 device에 compile
data, target = data.to(device), target.to(device)
# 3. 기울기 clear
optimizer.zero_grad()
# 4. model에 데이터 넣기
output = model(data)
# 5. loss 계산
loss = F.nll_loss(output, target)
# 6. back propagation 하여 gradient 계산
loss.backward()
# 7, parameter 업데이트
optimizer.step

# 중간중간에 로그 확인
if batch_idx % log_interval == 0 :
  print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
      epoch, batch_idx * len(data), len(train_loader.dataset),
      100 * batch_idx / len(train_loader),
      loss.item() # loss 값   <-- 학습이 진행되면서 loss 값이 내려가는지 관찰
  ))

Evaluation

  • model.eval() 으로 평가모드로 전환

model.eval() # 평가 모드 전환

test_loss = 0
correct = 0

with torch.no_grad() :
data, target = next(iter(test_loader)) # 테스트 데이터에서 batch 하나 꺼내기
data, target = data.to(device), target.to(device)
output = model(data)

loss 계산. 이미 gradient는 껐다. 업데이트 용이 아니라, 계산만 해서 log interval만 보기 위함

test_loss += F.nll_loss(output, target, reduction='sum').item()

        # reduction 을 안하면 batch size 별로, 데이터별로 따로 따로 계산하게 되는데
        # reduction=sum 을 해주면 하나의 스칼라 값으로 리턴해줌
        # 그것을 test_loss 에 누적 더해주는 것

pred = output.argmax(dim = 1, keepdim = True)

  # keepdim=True : output 과 pred 의 dimention 유지

correct = pred.eq(target.view_as(pred)).sum().item() # pred 과 target 이 얼마나 같은지 판정

test_loss

correct # 한개의 batch (64개) 에서 총 맞춘 개수

output.shape

pred.shape

target

target.shape

target.view_as(pred).shape

pred.eq(target.view_as(pred))

pred.eq(target.view_as(pred)).sum()

pred.eq(target.view_as(pred)).sum().item()

pred.eq(target.view_as(pred)).sum().item() / test_batch_size # 한 bacth size 에서 맞춘 확률

test_loss # 누적된 loss 값

PyTorch 모델학습

import numpy as np
import matplotlib.pyplot as plt
import os

import torch
from torchvision import datasets, transforms

import torch.nn as nn
import torch.nn.functional as F

Layer 쌓기

class Net(nn.Module): # nn.Module 을 상속받아 클래스 정의

생성자에는 '학습(train)' 이 가능한 것, 즉 weight 가 들어있는 레이어

ex) Conv2d, Linear ...

def init(self):
super(Net, self).init()
self.conv1 = nn.Conv2d(1, 20, 5, 1) # (in-channel, out-channel, kernel-size, stride)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4 4 50, 500) # (in-feature, out-feature)
self.fc2 = nn.Linear(500, 10)

생성자에 '학습' 이 가능한 레이어들을 만들어 놓았으니

이를 적용한 모델 작성

def forward(self, x): # x: 입력

# 1. Feature Extraction
x = F.relu(self.conv1(x))    # Conv 결과는 relu 활성화 함수를 거치게 한다.
x = F.max_pool2d(x, 2, 2)      # (input, kernel, stride)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)

# conv1 입력 (1, 28, 28) => conv1 => (20, 24, 24) => maxpool => (20, 12, 12)
# conv2 입력 (20, 12, 12) => conv2 => (50, 8, 8) => maxpool => (50, 4, 4)

# 2. Classification (Fully connected)
# view(batchsize, )  <=  batch size 는 몇개일지 모르기 때문에 -1로 지정
x = x.view(-1, 4 * 4 * 50)
x = F.relu(self.fc1(x))
x = self.fc2(x)

# 3. output 은 softmax
return F.log_softmax(x, dim=1)

model = Net() # 모델 생성!

base_path = '.'

이미지 하나 필요

train_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path,
train=True, download=True,
transform=transforms.Compose([transforms.ToTensor()])
),
batch_size=1
)

image, label = next(iter(train_loader))

image.shape, label.shape

result = model.forward(image)

result

레이어 하나하나씩 불러올수도 있다

model.conv1

model.conv1(image) # 각 레이어에 데이터 통과시켜볼수도 있다

Preprocess

no_cuda = False

use_cuda = not no_cuda and torch.cuda.is_available()
device = torch.device('cuda' if use_cuda else 'cpu')
device

DataLoader() 에서 shuffle=True 를 주어 랜덤으로 데이터 섞을때

수행할때마다 동일하게 섞일수 있도록 seed 값 준비

seed = 1

batch_size = 64
test_batch_size = 64

torch.manual_seed(seed)

train_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path,train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)

test_loader = torch.utils.data.DataLoader(
datasets.MNIST(base_path,train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=test_batch_size, shuffle=True)

Optimization

import torch.optim as optim # Optimizer

model = Net().to(device) # model 을 생성하여 device에 컴파일 해줍니다.

학습 parameter 들 확인 가능

params = list(model.parameters())
params

len(params) # 학습 가능 레이어 4개 + 4(각 레이어마다 bias)

torch.Tensor 객체 8개

for param in params:
print(param.size())

"""
torch.Size([20, 1, 5, 5]) <-- conv1 의 weight size
torch.Size([20]) <-- conv1 의 bias (out channel 의 개수만큼)
torch.Size([50, 20, 5, 5]) <-- conv2 의 weight size
torch.Size([50]) <-- conv2 의 bias
torch.Size([500, 800]) <-- fc1 의 weight size(out_channel, in_channel)
torch.Size([500]) <-- fc1 의 bias (out_channel 의 개수만큼)
torch.Size([10, 500]) <-- fc2 의 weight size
torch.Size([10]) <-- fc2 의 bias
"""
None

Optimizer 설정

optimizer = optim.SGD(model.parameters(),
lr=0.001, # learning rate
momentum=0.5)

optimizer

학습 전

  • 학습하기 전에 Model 이 Train 할수 있도록 Train mode 전환
  • Conv. 또는 Linear 분 아닐 Dropout 같은 parameter 를 가진 layer 들도 준비

train mode 전환

model.train()

train mode (학습) ↔ evaluation mode (테스트, 예측)

train mode 했다가 evalutaion mode 했다가 다시 train mode 로 돌아올때에도 .train() 을 해주어야 합니다

  • 모델에 넣기 위한 첫 Batch 추출

※ 일단 '하나' 씩 넣어서 확인, 나중에는 for 로 돌릴 예정

data, target = next(iter(train_loader)) # 첫 batch 꺼내기. (현재 64개로 세팅)

data.shape, target.shape

  • 추출한 batch 데이터를 device 에 compile

data, target = data.to(device), target.to(device)

data.shape, target.shape

학습하기전에 Optimizer 를 clear 해주어야 한다

zero_grad() : gradient 를 clear 해서 새로운 최적값을 찾기 위한 준비

optimizer.zero_grad()

준비한 데이터를 model 에 input 으로 넣어 output 얻음

output = model(data) # 예측값 아님! (지금은 train mode 다!) 잠시후 loss 값 계산해야 한다

output.shape

  • Model 의 output 값을 loss function 에 넣

Negative Log-likelihood loss 하는 loss function

loss = F.nll_loss(output, target)
loss

  • back propagation 을 통해 gradient 계산

loss.backward() # 기울기 (gradient) 계산

기울기 계산후에 Optimizer 를 통해 parameter 업데이트 해주어야 한다.

optimizer.step()

이상이 '학습' 의 "1 스텝"입니다

  • train 모드 변환
  • 데이터 넣어주고
  • 기울기 clear
  • model 에 데이터 넣고
  • loss 계산하고
  • back propagation 하여 gradient 계산하고
  • parameter 업데이트

Start Training

위의 최적화 과정을 반복하여 학습시작

필요한 hyper parameter 들 설정

epochs = 1
log_interval = 100 # 로그를 확인하기 위해 몇 스텝마다 로그 출력할지 결정

for epoch in range(1, epochs + 1):

1 train 모드 변환

model.train()

for 한번 순환할때 마다 한번 학습 이루어진 (batch 단위로 학습)

2. 데이터 넣어주기

for batch_idx, (data, target) in enumerate(train_loader):

# 데이터를 device 에 compile
data, target = data.to(device), target.to(device)
# 3. 기울기 clear
optimizer.zero_grad()
# 4. model 에 데이터 넣기
output = model(data)
# 5. loss 계산
loss = F.nll_loss(output, target)
# 6. back propagation 하여 gradient 계산
loss.backward()
# 7. parameter 업데이트
optimizer.step()

# 중간중간에 로그 확인
if batch_idx % log_interval == 0:
  print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
      epoch, batch_idx * len(data), len(train_loader.dataset),
      100 * batch_idx / len(train_loader),
      loss.item()  # loss 값   <- 학습이 진행되면서 loss 값이 내려가는지 관찰
  ))

Evaluation

  • model.eval() 으로 평가모드로 전환

model.eval() # 평가 모드 전환

test_loss = 0
correct = 0

with torch.no_grad():
data, target = next(iter(test_loader)) # 테스트 데이터에서 batch 하나 꺼내기
data, target = data.to(device), target.to(device)
output = model(data)

loss 계산. 이미 gradient 는 껐다. 업데이트 용이 아니라, 계산만 해서 log interval 보기 위함

test_loss += F.nll_loss(output, target, reduction='sum').item()

        # reduction 을 안하면 batch size 별로, 데이터별로 따로 따로 계산하게 되는데
        # reduction=sum 을 해주면 하나의 스칼라 값으로 리턴해줌
        # 그것을 test_loss 에 누적 더해주는 것

pred = output.argmax(dim=1, keepdim=True)

  # keepdim=True : output 과 pred 의 dimension 유지

correct = pred.eq(target.view_as(pred)).sum().item() # pred 과 target 이 얼마나 같은지 판정

test_loss

correct # 한개의 batch (64개) 에서 총 맞춘 개수

output.shape

pred.shape

target

target.shape

target.view_as(pred).shape

pred.eq(target.view_as(pred))

pred.eq(target.view_as(pred)).sum()

pred.eq(target.view_as(pred)).sum().item()

pred.eq(target.view_as(pred)).sum().item() / test_batch_size # 한 batch size 에서 맞춘 확률

test_loss # 누적된 loss 값

test_loss / test_batch_size

model.eval()

test_loss = 0
correct = 0

with torch.no_grad() :
for data, target in test_loader : # 모든 batch 를 다 돌려 볼거다
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # 얼마나 틀렸는지 모으고
pred = output.argmax(dim = 1, keepdim = True)
correct += pred.eq(target.view_as(pred)).sum().item() # 얼마나 맞혔는지도 모으고

test_loss /= len(test_loader.dataset) # loss의 평균값

print('\nTest set : Average Loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset), 100.0 * correct / len(test_loader.dataset)
))

Train + Eval

epochs = 5

for epoch in range(1, epochs + 1):

1 train 모드 변환

model.train()

for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()

if batch_idx % log_interval == 0:
  print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
      epoch, batch_idx * len(data), len(train_loader.dataset),
      100 * batch_idx / len(train_loader),
      loss.item()
  ))

2. Evaludation mode

model.eval()

test_loss = 0
correct = 0

with torch.no_grad() :
for data, target in test_loader : # 모든 batch 를 다 돌려 볼거다
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # 얼마나 틀렸는지 모으고
pred = output.argmax(dim = 1, keepdim = True)
correct += pred.eq(target.view_as(pred)).sum().item() # 얼마나 맞혔는지도 모으고

test_loss /= len(test_loader.dataset) # loss의 평균값

print('\nTest set : Average Loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset), 100.0 * correct / len(test_loader.dataset)
))

학습모델 저장하기

torch.save(model.state_dict(), os.path.join(base_path, 'MNIST.pth'))

저장했던 모델 불러오기

model = None

model = Net().to(device)
model.load_state_dict(torch.load(os.path.join(base_path, 'MNIST.pth')))

실제 데이터 적용 (예측하기)

import glob

img_paths = glob.glob('digit*.png')
img_paths

from PIL import Image
import PIL.ImageOps as ops

def predict(file_path) :
img = Image.open(file_path)
mono8img = img.convert('L') # gray scale 전환
invImg = ops.invert(mono8img) # 색상 반전
resizeImg = invImg.resize((28, 28)) # resize
data_arr = np.array(resizeImg).reshape(1, 1, 28, 28) # torch 에서 사용하는 입력 shape로 변환
tensor = torch.Tensor(data_arr) # Torch에서 사용하는 Tensor 로 변환
transformed = transforms.Normalize((0.1307,), (0.3081,))(tensor) # 학습에 사용된 것과 동일한 scaling

output = model(transformed.to(device))
pred = output.argmax(dim = 1, keepdim = True)
return pred.item()

for img_path in img_paths :
print(img_path, '->', predict(img_path))

profile
독해지자

0개의 댓글