728x90

파이토치에서 모델을 정의하기 위해서는 Module을 상속한 클래스를 사용한다. 모델과 모듈의 차이는 무엇일까?

  • 계층(layer) : 모듈 또는 모듈을 구성하는 한 개의 계층으로 합성곱층, 선형계층 등이 있다.
  • 모듈(module) : 한 개 이상의 계층이 모여서 구성된 것으로, 모듈이 모여 새로운 모듈을 만들 수도 있다.
  • 모델(model) : 최종적으로 원하는 네트워크로, 한 개의 모듈이 모델이 될 수도 있다.

1. 단순 신경망을 정의하는 방법

nn.Module을 상속받지 않는 매우 단순한 모델을 만들 때 사용한다. 구현이 쉽고 단순하다는 장점이 있다.

model = nn.Linear(in_features=1, out_features=1, bias=True)

2. nn.Module()을 상속하여 정의하는 방법

파이토치에서 nn.Module을 상속받는 모델은 기본적으로 __init__()과 forward() 함수를 포함한다. __init__()에서는 모델에서 사용될 모듈, 활성화 함수 등을 정의하고, forward() 함수에서는 모델에서 실행되어야 하는 연산을 정의한다.

다음은 파이토치에서 모델을 정의하는 코드이다.

class MLP(Module):
    def __init__(self, inputs):
        super(MLP, self).__init__()
        self.layer = Linear(inputs, 1) # 계층 정의
        self.activation = Sigmoid() # 활성화 함수 정의
        
    def forward(self, X):
        X = self.layer(X)
        X = self.activation(X)
        return X

3. Sequential 신경망을 정의하는 방법

nn.Sequential을 사용하면 __init__()에서 사용할 네트워크 모델들을 정의해 줄 뿐만 아니라 forward() 함수에서는 모델에서 실행되어야 할 계산을 좀 더 가독성이 뛰어나게 코드로 작성할 수 있다.

또한, Sequential 객체는 그 안에 포함된 각 모듈을 순차적으로 실행해 주는데 다음과 같이 코드를 작성할 수 있다.

import torch.nn as nn

class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=5),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2))
        
        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=64, 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 = MLP()

print('printing children \n-------------------------')
print(list(model.children()))
print('\n\nprinting Modules\n-------------------------')
print(list(model.modules()))
printing children 
-------------------------
[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(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): Linear(in_features=750, out_features=10, bias=True)
  (1): ReLU(inplace=True)
)]


printing Modules
-------------------------
[MLP(
  (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(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)
  )
  (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(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): 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)]

 

Note. model.modules() & model.children()

model.modules()는 모델의 네트워크에 대한 모든 노드를 반환하며, model.children()은 같은 수준의 하위 노드를 반환한다.

728x90

'딥러닝 > Pytorch' 카테고리의 다른 글

Pytorch 기본 문법 - 모델 훈련  (0) 2022.05.23
Pytorch 기본 문법 - 파라미터 정의  (0) 2022.05.23
Pytorch 기본 문법 - 데이터 준비  (1) 2022.05.18
Pytorch 기본 문법 - 텐서  (0) 2022.05.18
Pytorch 개요  (0) 2022.05.18
728x90

1. 커스텀 데이터셋을 만들어서 이용

딥러닝은 기본적으로 대량의 데이터를 이용하여 모델을 학습시킨다. 하지만 데이터를 한 번에 메모리에 불러와서 훈련시키면 시간과 비용 측면에서 비효율적이다. 따라서 데이터를 한 번에 다 부르지 않고 조금씩 나누어 불러서 사용하는 방식이 커스텀 데이터셋(custom dataset)이다.

먼저 CustomDataset 클래스를 구현하기 위해서는 다음 형태를 취해야 한다.

class CustomDataset(torch.utils.data.Dataset):
    def __init__(self): # 필요한 변수를 선언하고, 데이터셋의 전처리를 해 주는 함수
    def __len__(self): # 데이터셋의 길이. 즉, 총 샘플의 수를 가져오는 함수
    def __getitem__(self, index): # 데이터셋에서 특정 데이터를 가져오는 함수 (index번째 데이터를 반환하는 함수이며, 이때 반환되는 값은 텐서의 형태를 취해야 함)

커스텀 데이터셋 구현 방법에 대해 예제를 통해 구체적으로 알아보자.

import pandas as pd
import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader

class CustomDataset(Dataset):
    def __init__(self, csv_file): # csv_file 파라미터를 통해 데이터셋을 불러온다.
        self.label = pd.read_csv(csv_file)
        
    def __len__(self): # 전체 데이터셋의 크기를 반환한다.
        return len(self.label)
    
    def __getitem__(self, idx): # 전체 x와 y 데이터 중에 해당 idx번째의 데이터를 가져온다.
        sample = torch.tensor(self.label.iloc[idx, 0:3]).int()
        label = torch.tensor(self.label.iloc[idx, 3]).int()
        return sample, label
    
tensor_dataset = CustomDataset('../covtype.csv') # 데이터셋으로 covtype.csv를 사용한다.
dataset = DataLoader(tensor_dataset, batch_size=4, shuffle=True)

Note. torch.utils.data.DataLoader

DataLoader 객체는 학습에 사용될 데이터 전체를 보관했다가 모델 학습을 할 때 배치 크기만큼 데이터를 꺼내서 사용한다. 이때 주의할 것은 데이터를 미리 잘라 놓는 것이 아니라 내부적으로 iterator에 포함된 인덱스를 이용하여 배치 크기만큼 데이터를 반환한다는 것이다.

출처 :http://bit.ly/PyTorchZeroAll

 

 

 

728x90

'딥러닝 > Pytorch' 카테고리의 다른 글

Pytorch 기본 문법 - 모델 훈련  (0) 2022.05.23
Pytorch 기본 문법 - 파라미터 정의  (0) 2022.05.23
Pytorch 기본 문법 - 모델 정의  (0) 2022.05.23
Pytorch 기본 문법 - 텐서  (0) 2022.05.18
Pytorch 개요  (0) 2022.05.18
728x90

1. 텐서 다루기

1.1 텐서 생성 및 변환

텐서는 파이토치의 가장 기본의 되는 데이터 구조이다. 넘파이의 ndarray와 비슷하며 GPU에서의 연산도 가능하다.

텐서 생성은 다음과 같은 코드를 이용한다.

print(torch.tensor([[1, 2], [3, 4]])) # 2차원 형태의 텐서 생성
print('=======================')
print(torch.tensor([[1, 2], [3, 4]], device='cuda:0')) # GPU에 텐서 생성
print('=======================')
print(torch.tensor([[1, 2], [3, 4]], dtype=torch.float64)) # dtype을 이용하여 텐서 생성
tensor([[1, 2],
        [3, 4]])
=======================
tensor([[1, 2],
        [3, 4]], device='cuda:0')
=======================
tensor([[1., 2.],
        [3., 4.]], dtype=torch.float64)

텐서를 ndarray로 변환해보자.

temp = torch.tensor([[1, 2], [3, 4]])
temp.numpy() # 텐서를 ndarray로 변환

temp = torch.tensor([[1, 2], [3, 4]], device='cuda:0')
temp.to('cpu').numpy() # gpu상의 텐서를 cpu텐서로 변환한 후 ndarray로 변환
array([[1, 2],
       [3, 4]])
       
array([[1, 2],
       [3, 4]])

 

1.2 텐서의 인덱스 조작

텐서는 넘파이의 ndarray를 조작하는 것과 유사하게 동작하기 때문에 배열처럼 인덱스를 바로 지정하거나 슬라이스 등을 사용할 수 있다.

temp = torch.FloatTensor([1, 2, 3, 4, 5, 6, 7])
print(temp[0], temp[1]) # 인덱스로 접근
print(temp[0:3]) # 슬라이스로 접근
tensor(1.) tensor(2.)
tensor([1., 2., 3.])

 

1.3 텐서 차원 조작

텐서는 넘파이의 ndarray처럼 다양한 수학 연산이 가능하며, GPU를 사용하면 더 빠르게 연산할 수 있따. 참고로 텐서 간의 타입이 다르면 연산이 불가능하다.

텐서의 차원을 변경하는 가장 대표적인 방법은 view를 이용하는 것이다(하지만 reshape을 더 권장한다고 한다. 나도 reshape이 더 익숙하기도 하고). 이외에도 텐서를 결합하는 stack, cat과 차원을 교환하는 t, transpose도 사용된다. 

temp = torch.tensor([[1, 2], [3, 4]])
print(temp.shape)
print('=======================')
print(temp.view(4, 1)) # 2x2 행렬을 4x1 행렬로 변형
print('=======================')
print(temp.view(-1)) # 2x2 행렬을 1차원 벡터로 변형
print('=======================')
print(temp.view(1, -1)) # 2x2 행렬을 1x4 행렬로 변형
print('=======================')
torch.Size([2, 2])
=======================
tensor([[1],
        [2],
        [3],
        [4]])
=======================
tensor([1, 2, 3, 4])
=======================
tensor([[1, 2, 3, 4]])
=======================
tensor([[1],
        [2],
        [3],
        [4]])

 

 

728x90

'딥러닝 > Pytorch' 카테고리의 다른 글

Pytorch 기본 문법 - 모델 훈련  (0) 2022.05.23
Pytorch 기본 문법 - 파라미터 정의  (0) 2022.05.23
Pytorch 기본 문법 - 모델 정의  (0) 2022.05.23
Pytorch 기본 문법 - 데이터 준비  (1) 2022.05.18
Pytorch 개요  (0) 2022.05.18
728x90

파이토치를 공부하기 전에 파이토치 개요에 대해 알아보고자 한다.

 

파이토치 공식 튜토리얼에서는 파이토치를 다음과 같이 언급하고 있다.

  • 넘파이를 대체하면서 GPU를 이용한 연산이 필요한 경우
  • 최대한의 유연성과 속도를 제공하는 딥러닝 연구 플랫폼이 필요한 경우

하지만 무엇보다 주목받는 이유는 간결하고 빠른 구현성에 있다고 한다. 사실 위의 이유는 와닿지는 않고 최근 논문들의 코드가 대부분 파이토치로 구현되어 있기 때문에 공부의 필요성을 느꼈다.

 

1. 파이토치 특징 및 장점

한마디로 말하면 GPU에서 텐서 조작 및 동적 신경망 구축이 가능한 프레임워크이다.

여기서 말하는 동적 신경망이란 훈련을 반복할 때마다 네트워크 변경이 가능한 신경망을 의미한다고 한다. 연산 그래프를 정의하는 것과 동시에 값도 초기화되는 'Define by Run' 방식을 사용하기 때문에 연산 그래프와 연산을 분리해서 생각할 필요가 없고 때문에 코드를 이해하기 쉽다.

텐서플로우와 파이토치의 차이 (출처 - https://www.popit.kr/딥러닝-프레임워크-조사와-몇가지-홍보/)

 

2. 파이토치의 아키텍쳐

파이토치의 아키텍처는 간단하다. 크게 세 개의 계층으로 나누어있다고 한다. 가장 상위 계층은 파이토치 API가 위치해 있으며 그 아래는 파이토치 엔진이 있다. 파이토치 엔진에서는 다차원 텐서 및 자동 미분을 처리한다. 그리고 마지막으로 가장 아래에는 텐서에 대한 연산을 처리한다.

파이토치 아키텍처 (출처-딥러닝 파이토치 교과서)

3. 텐서를 메모리에 저장하기

텐서는 1차원이든 N차원이든 메모리에 저장할 때는 1차원 배열 형태가 된다. 즉, 1차원 배열 형태여야만 메모리에 저장할 수 있다.

그리고 변환된 1차원 배열을 스토리지라고 한다. 스토리지를 이해하기 위해서는 오프셋과 스트라이드 개념을 알아야 한다고 한다.

  • 오프셋(offset) : 텐서에서 첫 번째 요소가 스토리지에 저장된 인덱스
  • 스트라이드(stride) : 각 차원에 따라 다음 요소를 얻기 위해 건너뛰기가 필요한 스토리지의 요소 개수이다. 즉, 스트라이드는 메모리에서의 텐서 레이아웃을 표현하는 것으로 이해하면 된다. 요소가 연속적으로 저장되기 때문에 행 준심으로 스트라이드는 항상 1이다.

 

728x90

+ Recent posts