[Pytorch]실습

Date:     Updated:

카테고리:

1. Autograd(자동미분) 복습

1) 정의

  • torch.autograd 패키지는 Tensor의 모든 연산에 대해 자동 미분 제공
  • 이는 코드를 어떻게 작성하여 실행하느냐에 따라 역전파가 정의된다는 뜻
  • 역전파를 위해 미분값을 자동으로 계산
  • requires_grad 속성을 True로 설정하면, 해당 텐서에서 이루어지는 모든 연산들을 추적하기 시작
  • 기록을 추적하는 것을 중단하게 하려면, .detach()를 호출하여 연산기록으로부터 분리
import torch
a = torch.rand(3,3)
a = a * 3

print(a)
print(a.requires_grad)
tensor([[0.7729, 2.8212, 1.8116],
        [1.5490, 1.3230, 0.7801],
        [2.7811, 2.3541, 2.1352]])
        
False

2) 기울기(Gradient)

x = torch.ones(3,3,requires_grad = True)
print(x)

y = x + 5
print(y)
print(y.requires_grad)

z = y*y
out = z.mean()
print(z)
print(out)
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
-------------------------------------------------  
tensor([[6., 6., 6.],
        [6., 6., 6.],
        [6., 6., 6.]], grad_fn=<AddBackward0>)
True
-------------------------------------------------
        [36., 36., 36.],
        [36., 36., 36.]], grad_fn=<MulBackward0>)
tensor(36., grad_fn=<MeanBackward0>)
-------------------------------------------------

계산이 완료된 후, backward()를 호출하면 자동으로 역전파 계산이 가능하고, .grad 속성에 누적됨

print(out)
out.backward()
tensor(36., grad_fn=<MeanBackward0>)

grad: data가 거쳐온 layer에 대한 미분값 저장

print(x)
print(x.grad)

print(y)
print(y.grad)
## x, x.grad
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
tensor([[1.3333, 1.3333, 1.3333],
        [1.3333, 1.3333, 1.3333],
        [1.3333, 1.3333, 1.3333]])
        
## y, y.grad
tensor([[6., 6., 6.],
        [6., 6., 6.],
        [6., 6., 6.]], grad_fn=<AddBackward0>)
None

x = torch.randn(3, requires_grad = True)
y = x*2
while y.data.norm() < 1000:
    y = y*2

print(y)

v = torch.tensor([0.1, 1.0, 0.0001], dtype = torch.float)
y.backward(v)

print(x.grad)
tensor([-1502.9976,  -581.3363,   240.9863], grad_fn=<MulBackward0>)
tensor([2.0480e+02, 2.0480e+03, 2.0480e-01])

  • with torch.no_grad()를 사용하여 기울기의 업데이트를 하지 않음
  • 기록을 추적하는 것을 방지하기 위해 코드 블럭을 with torch.no_grad()로 감싸면 기울기 계산은 필요없지만,requires_grad=True로 설정되어 학습 가능한 매개변수를 갖는 모델을 평가(evaluate)할 때 유용
  • 모델을 평가할 때는 모델 자체를 업데이트 하면 안되니 기울기 계산하지 않고, 현재 있는 상태에서 평가할 때는 no_grad()를 넣어줘야 함
import torch

print(x.requires_grad)
print((x**2).requires_grad)

with torch.no_grad():
    print((x**2).requires_grad)  ## 여기서는 False나옴  
True
True
False

detach(): 내용물(content)은 같지만 require_grad가 다른 새로운 Tensor를 가져올 때 사용

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all()) ## eq(): equal, x와 y가 같니?
True
False
tensor(True)

3) 자동 미분 흐름 예제

  • 계산 흐름 $a \rightarrow b \rightarrow c \rightarrow out $
  • 그럼 $\quad \frac{\partial out}{\partial a}$ = ?
  • backward()를 통해 $a \leftarrow b \leftarrow c \leftarrow out $을 계산하면 $\frac{\partial out}{\partial a}$값이 a.grad에 채워짐
a = torch.ones(2,2)
print(a)

a = torch.ones(2,2, requires_grad = True)
print(a)

print(a.data)
print(a.grad)
print(a.grad_fn)
tensor([[1., 1.],
        [1., 1.]])
---------------------------------------
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
---------------------------------------
tensor([[1., 1.],
        [1., 1.]])
None
None

b = a + 2
print(b.data)

c = b**2
print(c.data)

out = c.sum()
print(out)
tensor([[9., 9.],
        [9., 9.]])

tensor(36., grad_fn=<SumBackward0>)
tensor(36., grad_fn=<SumBackward0>)

  • a의 grad_fn이 None인 이유는 직접적으로 계산한 부분이 없었기 때문
print(a.data)
print(a.grad)
print(a.grad_fn)  ## None: a의 미분을 직접적으로 계산한 부분 없음

print(b.data)
print(b.grad)
print(b.grad_fn)

print(c.data)
print(c.grad)
print(c.grad_fn)

print(out.data)
print(out.grad)
print(out.grad_fn)
tensor([[1., 1.],
        [1., 1.]])
tensor([[6., 6.],
        [6., 6.]])
None
-------------------------------------------
tensor([[3., 3.],
        [3., 3.]])
None
<AddBackward0 object at 0x7f6f0d459a60>
-------------------------------------------
tensor([[9., 9.],
        [9., 9.]])
None
<PowBackward0 object at 0x7f6f0d459eb0>
-------------------------------------------
tensor(36.)
None
<SumBackward0 object at 0x7f6f0d459220>

2. 신경망 구성해보기

1) 데이터 준비

파이토치에서는 데이터 준비를 위해 torch.utils.dataDatasetDataLoader 사용 가능하다.

  • Dataset에는 다양한 데이터셋이 존재 (MNIST, FashionMNIST, CIFAR10, …)
    • Vision Dataset: https://pytorch.org/vision/stable/datasets.html
    • Text Dataset: https://pytorch.org/text/stable/datasets.html
    • Audio Dataset: https://pytorch.org/audio/stable/datasets.html
  • DataLoaderDataset을 통해 batch_size, train 여부, transform 등을 인자로 넣어 데이터를 어떻게 load할 것인지 정해줄 수 있다.
import torch
import torchvision
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
from torchvision import datasets

토치비전(torchvision)은 파이토치에서 제공하는 데이터셋들이 모여있는 패키지

  • transforms: 전처리할 때 사용하는 메소드(https://pytorch.org/docs/stable/torchvision/transforms.html)
  • transforms에서 제공하는 클래스 이외는 일반적으로 클래스를 따로 만들어 전처리 단계를 진행
  • DataLoader의 인자로 들어갈 transform을 미리 정의할 수 있고, Compose를 통해 리스트 안에 순서대로 전처리 진행

  • ToTensor()를 하는 이유는 torchvision이 PIL Image 형태로만 입력을 받기 때문에 데이터 처리를 위해서 Tensor형으로 변환 필요
mnist_transform = transforms.Compose([transforms.ToTensor(),   		   										  			
                                      transforms.Normalize(mean = (0.5, ), std = (1.0, ))])
                                      
------------------------------------------------------------------------

trainset = datasets.MNIST(root = '/content/',
                                            download = True,
                                            train = True,
                                            transform =mnist_transform)

testset = datasets.MNIST(root = '/content/',
                                            download = True, 
                                            train = False,
                                            transform =mnist_transform)
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to /content/MNIST/raw/train-images-idx3-ubyte.gz
100%
9912422/9912422 [00:00<00:00, 21731101.17it/s]
Extracting /content/MNIST/raw/train-images-idx3-ubyte.gz to /content/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to /content/MNIST/raw/train-labels-idx1-ubyte.gz
100%
28881/28881 [00:00<00:00, 682477.68it/s]
Extracting /content/MNIST/raw/train-labels-idx1-ubyte.gz to /content/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to /content/MNIST/raw/t10k-images-idx3-ubyte.gz
100%
1648877/1648877 [00:00<00:00, 5918891.39it/s]
Extracting /content/MNIST/raw/t10k-images-idx3-ubyte.gz to /content/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to /content/MNIST/raw/t10k-labels-idx1-ubyte.gz
100%
4542/4542 [00:00<00:00, 223463.99it/s]
Extracting /content/MNIST/raw/t10k-labels-idx1-ubyte.gz to /content/MNIST/raw

  • DataLoader는 데이터 전체를 보관했다가 실제 모델 학습을 할 때 batch_size 크기만큼 데이터를 가져옴
train_loader = DataLoader(trainset, batch_size = 8, shuffle = True, num_workers = 2)
test_loader = DataLoader(testset, batch_size = 8, shuffle = False, num_workers = 2)

dataiter = iter(train_loader)
images, labels = next(dataiter)
print(images.shape, labels.shape)
## [8,1,28,28]: 28x28 이미지인데, 1이면 흑백, 8 = 8개 = batchsize
## 즉, 크기가 28x28인 흑백 이미지가 8개

torch_image = torch.squeeze(images[0])
print(torch_image.shape) # 차원 축소됨. 0번째 차원 날라감 = 1 날라감
(torch.Size([8, 1, 28, 28]), torch.Size([8]))
torch.Size([28, 28])

import matplotlib.pyplot as plt

figure = plt.figure(figsize = (12,6))
columns, rows = 4,2
for i in range(1, columns * rows + 1):
    sample_idx = torch.randint(len(trainset), size = (1,)).item()
    img, label = trainset[sample_idx]
    figure.add_subplot(rows, columns, i)
    plt.title(label)
    plt.axis('off')
    plt.imshow(img.squeeze(), cmap = 'gray')

1


2) 신경망 구성

  • 레이어(layer): 신경망의 핵심 데이터 구조로 하나 이상의 텐서를 입력받아 하나 이상의 텐서를 출력
    • 모듈(module): 한 개 이상의 계층이 모여서 구성
    • 모델(model): 한 개 이상의 모듈이 모여서 구성
  • torch.nn 패키지
    • 주로 가중치(weights), 편향(bias)값들이 내부에서 자동으로 생성되는 레이어들을 사용할 때 사용 (weight값들을 직접 선언 안함)
    • https://pytorch.org/docs/stable/nn.html

Linear Layer

nn.Linear 계층 예제

import torch.nn as nn

input = torch.rand(128,20)
print(input)
print("----------------------------------------------------------------------------")

model = nn.Linear(20,30)  ## (input_feature, output_feature)
print(model)
print("----------------------------------------------------------------------------")

output = model(input)
print("----------------------------------------------------------------------------")
print(output)
print(output.size())
tensor([[0.0741, 0.5181, 0.5499,  ..., 0.8257, 0.8955, 0.0808],
        [0.7822, 0.8137, 0.5497,  ..., 0.0058, 0.9777, 0.0643],
        [0.4558, 0.9138, 0.7020,  ..., 0.1283, 0.5074, 0.8316],
        ...,
        [0.9362, 0.2198, 0.5683,  ..., 0.6776, 0.4641, 0.6829],
        [0.7784, 0.3584, 0.4910,  ..., 0.6288, 0.2116, 0.5324],
        [0.8038, 0.5861, 0.2872,  ..., 0.1152, 0.7582, 0.8966]])
----------------------------------------------------------------------------
Linear(in_features=20, out_features=30, bias=True)
----------------------------------------------------------------------------
----------------------------------------------------------------------------
tensor([[-0.6023,  0.2802, -0.0368,  ...,  0.1032,  0.4914, -0.2984],
        [ 0.0908, -0.0125,  0.0343,  ...,  0.3796,  0.2376, -0.5433],
        [-0.2045, -0.0195, -0.2661,  ...,  0.3265,  0.4362, -0.3936],
        ...,
        [-0.3677, -0.0852, -0.2084,  ...,  0.0845,  0.2574, -0.5173],
        [-0.1548,  0.0879, -0.1322,  ...,  0.2853,  0.1115, -0.4794],
        [ 0.1217,  0.3638, -0.0713,  ...,  0.4999,  0.1304, -0.5946]],
       grad_fn=<AddmmBackward0>)
torch.Size([128, 30])

Convolution Layer

nn.Conv2d 예제

  • in_channels: channel의 갯수
  • out_channels: 출력 채널의 갯수
  • kernel_size: 커널(필터) 사이즈
input = torch.randn(20,16,50,100)
print(input.shape)

torch.Size([20,16,50,100])

m = nn.Conv2d(16,33,3,stride = 2)
m = nn.Conv2d(16,33,(3,5), stride = (2,1), padding = (4,2))
m = nn.Conv2d(16,33,(3,5), stride = (2,1), padding = (4,2), dilation = (3,1))
# nn.Conv2d(in_channels = 16, out_channels = 33, kernel_size = 3, stride = 2)
# nn.Conv2d(in_channels = 16, out_channels = 33, kernel_size = (2,1), padding = (4,2))
print(m)

output = m(input)
print(output.size())
torch.Size([20, 16, 50, 100])
torch.Size([20, 16, 50, 100])

Conv2d(16, 33, kernel_size=(3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))

torch.Size([20, 33, 26, 100])

nn.Conv2d(in_channels = 1, out_channels = 20, kernel_size = 5, stride = 1)

layer = nn.Conv2d(1,20,5,1).to(torch.device('cpu'))
layer
Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))

Weight 확인, weightdetach()를 통해 꺼내줘야 numpy()변환이 가능

weight = layer.weight
print(weight.shape)

weight = weight.detach()
weight = weight.numpy()
print(weight.shape)

plt.imshow(weight[0,0,:,:], 'jet')
plt.colorbar()
plt.show()
print(weight[0,0,:,:])
weight = weight.detach()
(20, 1, 5, 5)

1

[[ 0.14788686 -0.07691999 -0.06958671  0.0327538  -0.12588926]
 [-0.15326653 -0.06465175  0.0382885   0.16026531  0.08553481]
 [ 0.14748965 -0.11287172 -0.01223607 -0.05822499  0.04035332]
 [-0.14469357 -0.16859543  0.00449383  0.08313598 -0.0606976 ]
 [ 0.16758244  0.14283665  0.13446307  0.15948279  0.01430113]]

print(images.shape)
print(images[0].size())

input_image = torch.squeeze(images[0])
print(input_image.size())

input_data = torch.unsqueeze(images[0], dim = 0)
print(input_data.size())

output_data = layer(input_data) 
output = output_data.data # 레이어를 통과시킨 데이터들만 모아서 넘파이 어레이로 바꾸고 모양을 본 것임
output_arr = output.numpy()
print(output_arr.shape)

## Visualization
plt.figure(figsize=  (15,30))
plt.subplot(131)
plt.title('input')
plt.imshow(input_image, 'gray')
plt.subplot(132)
plt.title('Weight')
plt.imshow(weight[0,0,:,:], 'jet')
plt.subplot(133)
plt.title("Output")
plt.imshow(output_arr[0,0,:,:], 'gray')
torch.Size([8, 1, 28, 28])
torch.Size([1, 28, 28])

torch.Size([28, 28])

torch.Size([1, 1, 28, 28])

(1, 20, 24, 24)

1


Pooling Layer

  • F.max_pool2d
    • stride
    • kernel_size
  • torch.nn.MaxPool2d 도 많이 사용
import torch.nn.functional as F
pool = F.max_pool2d(output, 2,2) ## maxpooling()
pool.shape
torch.Size([1, 20, 12, 12])

MaxPool Layer는 weight가 없기 때문에 바로 numpy()이 가능하다!!

pool_arr = pool.numpy()
print(output.shape)
print(pool_arr.shape)
## [1,20,24,24] -> [1,20,12,12] : output에 대해서 2개를 기준으로 max값만 저장
torch.Size([1, 20, 24, 24])
(1, 20, 12, 12)

plt.figure(figsize=  (10,15))

plt.subplot(121)
plt.title('input')
plt.imshow(input_image, 'gray')

plt.subplot(122)
plt.title('Output')
plt.imshow(pool_arr[0,0,:,:], 'gray')

1


신경망 종류

1

1

모델 정의

  • nn.Module 상속 클래스 정의
    • nn.Module을 상속받는 클래스 정의
    • __init__(): 모델에서 사용될 모듈과 활성화 함수 등을 정의
    • forward(): 모델에서 실행되어야 하는 연산을 정의
class Model(nn.Module):
    def __init__(self, inputs):
        super(Model, self).__init__()
        self.layer = nn.Linear(inputs, 1)
        self.activation = nn.Sigmoid()

    def forward(self, x):
        x = self.layer(x)
        x = self.activation(x)
        return x
        
model = Model(1)
print(list(model.children())) ## children으로 call하는 순간 눈으로 확인가능
print()
print(list(model.modules())) ## 모듈에 linea이고, sigmoid가 들어가 있는지 확인 가능
[Linear(in_features=1, out_features=1, bias=True), Sigmoid()]

[Model(
  (layer): Linear(in_features=1, out_features=1, bias=True)
  (activation): Sigmoid()
), Linear(in_features=1, out_features=1, bias=True), Sigmoid()]

  • nn.Sequential을 이용한 신경망 정의
    • nn.Sequential 객체로 그 안에 각 모듈을 순차적으로 실행
    • __init__()에서 사용할 네트워크 모델들을 nn.Sequential로 정의 가능
    • forward()에서 실행되어야 할 계산을 가독성 높게 작성 가능
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels = 3, out_channels = 64, kernel_size = 5),
            nn.ReLU(inplace = True),
            nn.MaxPool2d(2)
        )
## 1번 layer가 sequential이니 순차적으로 실행되는데
## 먼저 convolution layer를 거치고, ReLU라는 Activiation function을 지난다음
## maxpooling을 한다
        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels = 64, out_channels = 30, kernel_size = 5),
            nn.ReLU(inplace = True),
            nn.MaxPool2d(2)
        )
        
        self.layer3 = nn.Sequential(
            nn .Linear(in_features = 30*5*5, out_features = 10, bias = True),
            nn.ReLU(inplace = True),
        )

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = x.view(x.shape[0], -1)
        x = self.layer3(x)
        return x
        
model = Model()
print(list(model.children()))
print('------------------------------------------------------------------------------------------')
print(list(model.modules()))
[Sequential(
  (0): Conv2d(3, 64, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU(inplace=True)
  (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
), Sequential(
  (0): Conv2d(64, 30, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU(inplace=True)
  (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
), Sequential(
  (0): Linear(in_features=750, out_features=10, bias=True)
  (1): ReLU(inplace=True)
)]
------------------------------------------------------------------------------------------
[Model(
  (layer1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer2): Sequential(
    (0): Conv2d(64, 30, kernel_size=(5, 5), stride=(1, 1))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer3): Sequential(
    (0): Linear(in_features=750, out_features=10, bias=True)
    (1): ReLU(inplace=True)
  )
), Sequential(
  (0): Conv2d(3, 64, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU(inplace=True)
  (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
), Conv2d(3, 64, kernel_size=(5, 5), stride=(1, 1)), ReLU(inplace=True), MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), Sequential(
  (0): Conv2d(64, 30, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU(inplace=True)
  (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
), Conv2d(64, 30, kernel_size=(5, 5), stride=(1, 1)), ReLU(inplace=True), MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False), Sequential(
  (0): Linear(in_features=750, out_features=10, bias=True)
  (1): ReLU(inplace=True)
), Linear(in_features=750, out_features=10, bias=True), ReLU(inplace=True)]

파이토치 사전학습 모델

3. 모델 파라미터

1) 손실 함수(Cost Funtion)

  • 예측 값과 실제 값 사이의 오차 측정
  • 학습이 진행되면서 해당 과정이 얼마나 잘 되고 있는지 나타내는 지표
  • 모델이 훈련되는 동안 최소화될 값으로 주어진 문제에 대한 성공 지표
  • 손실 함수에 따른 결과를 통해 학습 파라미터를 조정
  • 최적화 이론에서 최소화 하고자 하는 함수
  • 미분 가능한 함수 사용
  • 파이토치의 주요 손실 함수
    • torch.nn.BCELoss: 이진 분류를 위해 사용
    • torch.nn.CrossEntropyLoss: 다중 클래스 분류를 위해 사용
    • torch.nn.MSELoss: 회귀 모델에서 사용
criterion = nn.MSELoss()
criterion = nn.CrossEntropyLoss()

2) 옵티마이저(Optimizer)

  • 손실 함수를 기반으로 모델이 어떻게 업데이트되어야 하는지 결정 (특정 종류의 확률적 경사 하강법 구현)
  • optimizer는 step()을 통해 전달받은 파라미터를 모델 업데이트
  • 모든 옵티마이저의 기본으로 torch.optim.Optimizer(params, defaults) 클래스 사용
  • zero_grad()를 이용해 옵티마이저에 사용된 파라미터들의 기울기를 0으로 설정
  • torch.optim.lr_scheduler를 이용해 에포크(epochs)에 따라 학습률(learning rate) 조절
  • 파이토치의 주요 옵티마이저: optim.Adadelta, optim.Adagrad, optim.Adam, optim.RMSprop, optim.SGD

1

3) 학습률 스케줄러(Learning rate scheduler)

  • 학습시 특정 조건에 따라 학습률을 조정하여 최적화 진행
  • 일정 횟수 이상이 되면 학습률을 감소(decay)시키거나 전역 최소점(global minimum) 근처에 가면 학습률을 줄이는 등
  • 파이토치의 학습률 스케줄러 종류
    • optim.lr_scheduler.LambdaLR: 람다(lambda) 함수를 이용해 그 결과를 학습률로 설정
    • optim.lr_scheduler.StepLR: 단계(step)마다 학습률을 감마(gamma) 비율만큼 감소
    • optim.lr_scheduler.MultiStepLR: StepLR과 비슷하지만 특정 단계가 아니라 지정된 에포크에만 감마 비율로 감소
    • optim.lr_scheduler.ExponentialLR: 에포크마다 이전 학습률에 감마만큼 곱함
    • optim.lr_scheduler.CosineAnnealingLR: 학습률을 코사인(cosine) 함수의 형태처럼 변화시켜 학습률일 커지기도 하고 작아지기도 함
    • optim.lr_scheduler.ReduceLROnPlateau: 학습이 잘되는지 아닌지에 따라 동적으로 학습률 변화

4) 지표(Metrics)

  • 모델의 학습과 테스트 단계를 모니터링
!pip install torchmetrics
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting torchmetrics
  Downloading torchmetrics-0.11.3-py3-none-any.whl (518 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 518.6/518.6 KB 10.5 MB/s eta 0:00:00
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.8/dist-packages (from torchmetrics) (4.5.0)
Requirement already satisfied: torch>=1.8.1 in /usr/local/lib/python3.8/dist-packages (from torchmetrics) (1.13.1+cu116)
Requirement already satisfied: packaging in /usr/local/lib/python3.8/dist-packages (from torchmetrics) (23.0)
Requirement already satisfied: numpy>=1.17.2 in /usr/local/lib/python3.8/dist-packages (from torchmetrics) (1.22.4)
Installing collected packages: torchmetrics
Successfully installed torchmetrics-0.11.3

import torchmetrics

preds = torch.randn(10,5).softmax(dim  = -1) ## softmax값을 통과한 예측값과
target = torch.randint(5, (10,))                      ## 랜덤하게 만든 값과 얼마나 비슷하니?
print(preds, target)

acc = torchmetrics.functional.accuracy(preds, target, task="multiclass", num_classes=5)
print(acc)
tensor([[0.3291, 0.1527, 0.1217, 0.0663, 0.3301],
        [0.2401, 0.0895, 0.2365, 0.1314, 0.3025],
        [0.1900, 0.0677, 0.3601, 0.2837, 0.0985],
        [0.0551, 0.4757, 0.2383, 0.1886, 0.0423],
        [0.0773, 0.2609, 0.5159, 0.1181, 0.0278],
        [0.4987, 0.1455, 0.0842, 0.0480, 0.2236],
        [0.6345, 0.0376, 0.0616, 0.0420, 0.2242],
        [0.3481, 0.2354, 0.2752, 0.1273, 0.0139],
        [0.2529, 0.1980, 0.0559, 0.2323, 0.2609],
        [0.0468, 0.3254, 0.0636, 0.4104, 0.1538]]) tensor([4, 1, 3, 4, 2, 2, 2, 1, 2, 3])
tensor(0.3000)

metric = torchmetrics.Accuracy(task="multiclass", num_classes=5)

n_batches = 10
for i in range(n_batches):
    preds = torch.randn(10,5).softmax(dim = -1)
    target = torch.randint(5, (10,))

    acc = torchmetrics.functional.accuracy(preds, target, task="multiclass", num_classes=5)
    print(acc)
tensor(0.3000)
tensor(0.1000)
tensor(0.3000)
tensor(0.2000)
tensor(0.1000)
tensor(0.1000)
tensor(0.2000)
tensor(0.1000)
tensor(0.1000)
tensor(0.)

4. 선형 회귀 모델(Linear Regression Model)

1) 데이터 생성

X = torch.randn(200, 1) * 10
y = X + 3*torch.randn(200,1)
plt.scatter(X.numpy(), y.numpy())
plt.xlabel("X")
plt.ylabel("y")
plt.grid()
plt.show()

1

2) 모델 정의 및 파라미터

class LinearRegressionModel(nn.Module):
    def __init__(self):
        super(LinearRegressionModel, self).__init__()
        self.linear = nn.Linear(1,1)

    def forward(self, x):
        pred = self.linear(x)
        return pred
        
model = LinearRegressionModel()
print(model)
print(list(model.parameters()))
LinearRegressionModel(
  (linear): Linear(in_features=1, out_features=1, bias=True)
)
[Parameter containing:
tensor([[0.2249]], requires_grad=True), Parameter containing:
tensor([0.9877], requires_grad=True)]

w, b = model.parameters()

w1,b1 = w[0][0].item(), b[0].item()
x1 = np.array([-30,30])
y1 = w1*x1 +b1

plt.plot(x1,y1, 'r')
plt.scatter(X,y)
plt.grid()
plt.show()

1


3) 손실 함수 및 Optimizer

import torch.optim as optim

criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr = 0.001)

4) 모델 학습

epochs = 100
losses = []

for epoch in range(epochs):
    optimizer.zero_grad()

    y_pred = model(X)
    loss = criterion(y_pred, y)
    losses.append(loss.item())
    loss.backward()

    optimizer.step()
plt.plot(range(epochs), losses)
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.show()

1

w1,b1 = w[0][0].item(), b[0].item()
x1 = np.array([-30,30])
y1 = w1*x1 +b1

plt.plot(x1,y1, 'r')
plt.scatter(X,y)
plt.grid()
plt.show()

1

5. Fashion MNIST 분류 모델

먼저 GPU가 동작하는지 설정부터 확인한다.

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
cuda # GPU ok!!

1) 데이터 로드

transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5,),(0.5,))])
                                                    
trainset = datasets.FashionMNIST("/content/",
                                 train = True, download = True,
                                 transform = transform)

testset = datasets.FashionMNIST("/content/",
                                 train = False, download = True,
                                 transform = transform)
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to /content/FashionMNIST/raw/train-images-idx3-ubyte.gz
100%
26421880/26421880 [00:01<00:00, 24872885.09it/s]
Extracting /content/FashionMNIST/raw/train-images-idx3-ubyte.gz to /content/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to /content/FashionMNIST/raw/train-labels-idx1-ubyte.gz
100%
29515/29515 [00:00<00:00, 273950.97it/s]
Extracting /content/FashionMNIST/raw/train-labels-idx1-ubyte.gz to /content/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to /content/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
100%
4422102/4422102 [00:00<00:00, 7719615.32it/s]
Extracting /content/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to /content/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to /content/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
100%
5148/5148 [00:00<00:00, 285472.41it/s]
Extracting /content/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to /content/FashionMNIST/raw

train_loader = DataLoader(trainset, batch_size = 128, shuffle = True, num_workers = 2)
test_loader = DataLoader(testset, batch_size = 128, shuffle = False, num_workers = 2)

images, labels = next(iter(train_loader))
print(images.shape, labels.shape)
(torch.Size([128, 1, 28, 28]), torch.Size([128]))

labels_map = {
    0: 'T-Shirt',
    1: 'Trouser',
    2: 'Pullover',
    3: 'Dress',
    4: 'Coat',
    5: 'Sandal',
    6: 'Shirt',
    7: 'Sneaker',
    8: 'Bag',
    9: 'Ankle Boot',
}

figure = plt.figure(figsize = (12,12))
columns, rows = 4,4
for i in range(1, columns * rows + 1):
    image = images[i].squeeze()
    label_idx = labels[i].item()
    label = labels_map[label_idx]

    figure.add_subplot(rows, columns, i)
    plt.title(label)
    plt.axis('off')
    plt.imshow(image, cmap = "gray")
plt.show()

1

2) 모델 정의 및 파라미터

class NeuralNet(nn.Module):
    def __init__(self):
        super(NeuralNet, self).__init__()

        self.conv1 = nn.Conv2d(1,20,3)
        self.conv2 = nn.Conv2d(20,40,3)
        self.fc1 = nn.Linear(40*5*5, 240)
        self.fc2 = nn.Linear(240, 120)
        self.fc3 = nn.Linear(120, 84)
        self.fc4 = nn.Linear(84,10)

    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = self.fc4(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]
        num_features =  1
        for s in size:
            num_features *= s

        return num_features

net = NeuralNet()
print(net)
NeuralNet(
  (conv1): Conv2d(1, 20, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(20, 40, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=1000, out_features=240, bias=True)
  (fc2): Linear(in_features=240, out_features=120, bias=True)
  (fc3): Linear(in_features=120, out_features=84, bias=True)
  (fc4): Linear(in_features=84, out_features=10, bias=True)
)

params = list(net.parameters())
print(len(params))
print(params[0].size())

input = torch.randn(1,1,28,28)
output = net(input)
print(output)
12
torch.Size([20, 1, 3, 3])

tensor([[ 0.0967,  0.0541,  0.0478, -0.0208, -0.1028,  0.0862, -0.0289, -0.0761,
         -0.0289,  0.0543]], grad_fn=<AddmmBackward0>)

3) 손실함수와 Optimizer

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr = 0.001, momentum = 0.9) # lr = learning rate

4) 모델 학습

  • 배치수 확인
total_batch = len(train_loader)
print(total_batch) # 469

for epoch in range(5):

    running_loss = 0.0 # running loss 초기화

    for i, data in enumerate(train_loader, 0):
        inputs, labels = data
        optimizer.zero_grad()
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

        if i % 100 ==99:
            print("Epoch: {}, Iter: {}, Loss: {}".format(epoch + 1, i + 1, running_loss/2000))
469

Epoch: 1, Iter: 100, Loss: 0.11515839791297913
Epoch: 1, Iter: 200, Loss: 0.23012689578533171
Epoch: 1, Iter: 300, Loss: 0.34492256307601926
Epoch: 1, Iter: 400, Loss: 0.4594321113824844
Epoch: 2, Iter: 100, Loss: 0.1136441946029663
Epoch: 2, Iter: 200, Loss: 0.22612131297588348
Epoch: 2, Iter: 300, Loss: 0.335495311498642
Epoch: 2, Iter: 400, Loss: 0.4343601332306862
Epoch: 3, Iter: 100, Loss: 0.058922575682401654
Epoch: 3, Iter: 200, Loss: 0.10821666920185088
Epoch: 3, Iter: 300, Loss: 0.15261925032734872
Epoch: 3, Iter: 400, Loss: 0.19333434492349624
Epoch: 4, Iter: 100, Loss: 0.03691924387216568
Epoch: 4, Iter: 200, Loss: 0.07250896961987019
Epoch: 4, Iter: 300, Loss: 0.1064168707728386
Epoch: 4, Iter: 400, Loss: 0.13989421248435974
Epoch: 5, Iter: 100, Loss: 0.03280985563993454
Epoch: 5, Iter: 200, Loss: 0.06475198629498481
Epoch: 5, Iter: 300, Loss: 0.09624279655516148
Epoch: 5, Iter: 400, Loss: 0.12776464824378492

5) 모델의 저장 및 로드

  • torch.save: net.state_dict()를 저장
  • torch.load: load_state_dict로 모델을 로드
PATH = './fashion_mnist.pth'
torch.save(net.state_dict(), PATH)

net = NeuralNet()
print(net.load_state_dict(torch.load(PATH)))

print(net.parameters)
<All keys matched successfully>

<bound method Module.parameters of NeuralNet(
  (conv1): Conv2d(1, 20, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(20, 40, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=1000, out_features=240, bias=True)
  (fc2): Linear(in_features=240, out_features=120, bias=True)
  (fc3): Linear(in_features=120, out_features=84, bias=True)
  (fc4): Linear(in_features=84, out_features=10, bias=True)
)>

6) 모델 테스트

def imshow(image):
    image = image / 2 + 0.5
    npimg = image.numpy()

    fig = plt.figure(figsize = (16, 8))
    plt.imshow(np.transpose(npimg, (1,2,0)))
    plt.show()

import torchvision

dataiter = iter(test_loader)
images, labels = next(dataiter)
imshow(torchvision.utils.make_grid(images[:6]))

1


outputs = net(images)

_, predicted = torch.max(outputs, 1)
print(predicted)

print(''.join('{},'.format(labels_map[int(predicted[j].numpy())]) for j in range(6)))

correct = 0
total = 0
tensor([9, 2, 1, 1, 2, 1, 2, 2, 5, 7, 4, 5, 5, 3, 4, 1, 2, 2, 8, 0, 2, 7, 7, 5,
        1, 2, 2, 3, 9, 4, 8, 8, 3, 6, 8, 0, 7, 5, 7, 9, 0, 1, 3, 9, 2, 5, 2, 1,
        2, 2, 2, 2, 5, 2, 4, 2, 8, 4, 8, 0, 7, 7, 8, 5, 1, 1, 6, 3, 7, 8, 7, 0,
        2, 6, 4, 3, 1, 2, 8, 4, 1, 8, 5, 9, 5, 0, 3, 2, 0, 2, 5, 3, 4, 7, 1, 8,
        0, 1, 4, 2, 3, 4, 7, 6, 7, 8, 5, 9, 9, 4, 6, 5, 7, 0, 5, 2, 8, 4, 7, 2,
        0, 0, 9, 9, 0, 0, 8, 4])
        
Ankle Boot,Pullover,Trouser,Trouser,Pullover,Trouser,

with torch.no_grad(): ## test, 평가하는거지 gradient 업데이트 안함
    for data in test_loader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1) ## 각 열마다 최댓값의 위치를 예측
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(100*correct / total)
76.35

참고로 _, predicted = torch.max(outputs.data, 1) 이 부분에서, 언더바(_,) 이 부분은 출력값을 저장하지 않겠다는 것이다. 즉, torch.max는 최댓값과 최댓값의 위치를 산출해주는데, 여기서 최댓값을 필요 없으므로 받이 않겠다는 것이다. 따라서 언더바로 처리하여 해당 출력ㄱ밧은 저장하지 않겠다는 의미이다. 즉, predictted에 최댓값의 위치만 저장하겠다는 의미이다.

그리고 _, predicted = torch.max(outputs.data, 1) 이 부분에서 마찬가지로 .data를 사용하는 이유는 역전파 계산이 필요없기 때문에 데이터만 사용한다는 의미이다.

Reference

파이토치 튜토리얼
이수안 컴퓨터 연구소 강의

Pytorch 카테고리 내 다른 글 보러가기

댓글 남기기