pytorch 기초 및 MLP(다중 퍼셉트론)

텐서

텐서 생성하기

import torch

ts = torch.tensor([ ], device='cuda/cpu')

  • ts.shape
  • ts.size() #shpae과 동일
  • ts.dim() #차원

  • ts1 = ts1.to('cuda')
  • ts2 = ts2.to('cpu')

  • ts2 = ts2.numpy() #numpy는 cpu에 있을때에만 가능
  • ts2 = torch.from_numpy(ts2)

텐서 인덱스

ts3 = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

인덱스

ts3[0, 0] = ts3[0][0] # 1


슬라이스

ts3[0][0:2] = ts3[0, 0:2]  # 1, 2


인덱스 여러개

차원 갯수만큼 [ ]를 만들고 ,로 구분

[x1, x2, x3], [y1, y2, y3], [z1, z2, z3]

ts3[[0],[0]]  # 1

ts3[[0,1],[0,1]]  # 1, 5

ts3[[0,1,2],[2,1,0]]  # 3, 5, 7


텐서 차원 변경

ts4.reshape(, ) 

ts4 = torch.arange(6)

reshaped_ts4 = ts4.reshape(2, 3)


ts4.view(, )

viewed_ts4 = ts4.view(2, 3)


reshpae는 새로운 객체가 생성되는 것이고
view는 기존 객체의 위치값을 참조한다. (모양 정보만 가짐)

non-contiguous tensor는
reshape 하거나
contiguous하게 바꾸고, view해야 한다.
ex) contiguous().view(6)


ts5.squeeze(차원)

해당 차원 제거, 단 갯수가 1개인 경우에만


ts5.unsqueeze(차원)

해당 위치에 차원 추가, 단 갯수가 1개인 상태로 


ts6.transpose(바꿀차원a, 바꿀차원b)

transpose도 view처럼 기존 객체의 위치값을 참조한다. (모양 정보만 가짐)

transpose는 요소 배치를 예상하기 복잡한데, 

우선 바뀌 차원의 shape를 생각하고,

가장 안쪽 차원부터 생각해 보자.


텐서 붙이기

cat(, dim=0/1)  

# 참고로 pandas는 concat(, axis=)

ts7 = torch.cat((ts1, ts2), dim=0)

# 쌓는 부분의 차원은 변하고, 안 쌓는 부분은 안 변함
(그래서 안 쌓는 부분은 일치해야 함)


stack(, dim=0/1) 

# 모양 재구성 없이 그대로 붙여서 한 번 더 감싼다.

ts7 = torch.stack((ts1, ts2), dim=0)

# stack은 모양이 완전히 일치해야 한다.


PyTorch 데이터 타입 정리



MLP 다층 퍼셉트론 데이터셋

데이터셋 클래스 만들기

  • '생성자 함수'로 값 초기화
    • __init__(self, x, y)
  • 길이 확인 함수
    • __len__(self)
  • 인덱스 접근 함수
    • __getitem__(self, idx)

from torch.utils.data import Dataset

class CustomDataset(Dataset): #상위 클래스 Dataset 상속
    def __init__(self, x_data, y_data):
        self.x_data = x_data
        self.y_data = y_data

    def __len__(self):
        return len(self.x_data)

    def __getitem__(self, idx):
        x = self.x_data[idx]
        y = self.y_data[idx]
        return x, y

값을 리스트로 전달한다.

x = [i for i in range(10)]
y = [2*i for i in x]
dataset = CustomDataset(x, y)

self 변수 

dataset.x

인덱스 함수를 사용하면 리턴 값이 나온다.

sample_x, sample_y = dataset[2]

DataLoader 클래스

from torch.utils.data import DataLoader

data_loader = DataLoader(dataset=dataset, batch_size=3, shuffle=False)

  • dataset = 클래스로 선언한 객체
  • batch_size = 밖에 있는 차원을 다시 batch_size 만큼 묶음 


MNIST 데이터

from torchvision import datasets

mnist = datasets.MNIST(root='./data', train=True, download=True, transform=None)

# ①숫자 이미지(0~9, 10개 카테고리)와 ②라벨이 쌍을 이룬 데이터 타입

image, label = mnist[0]

plt.imshow(image, cmap='gray')

plt.show()


전처리

▽ 전처리 딕셔너리 생성

transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=1), # p는 확률
    transforms.ToTensor(), # 이미지를 텐서 형태로 바꿔주다.
    transforms.Normalize((0.1307,), (0.3081,))
])

▽ 데이터셋 클래스 생성

  • __init__ 함수를 보면, transform을 갖고 있는다.
  • 인덱스 접근 함수를 사용할 때, transform 클래스를 사용해서 image를 바꿔준다.

class CustomDataset(Dataset):
    def __init__(self, mnist, transform=None):
        self.mnist = mnist
        self.transform = transform

    def __getitem__(self, idx):
        image, label = self.mnist[idx]
        if self.transform:  # 만약 주어진 transform이 있다면
            image = self.transform(image)  # 이미지에 변환을 적용합니다.
        return image, label

    def __len__(self):
        return len(self.mnist)

▽ mnist 데이터 사용

mnist = datasets.MNIST(root='./data', train=True, download=True)

▽ 데이터셋 객체 생성

custom_dataset = CustomDataset(mnist, transform=transform)

▽ 데이터로더 객체 생성

  • 64개씩 다시 포장함.

data_loader = DataLoader(custom_dataset, batch_size=64, shuffle=False)

▽ 데이터 출력

  • for문을 돌릴때, data_loaser는 64개씩 포장된 상태다.
  • 즉, for문이 1번 돌 때, images, labels는 0~63 까지의 값을 갖는다. 
    • ex) 기존 for문이 image 하나의 변수였다면, 데이터로더 객체는 image[0:64] 형태임

# DataLoader를 통해 데이터를 불러옵니다.
for idx, (images, labels) in enumerate(data_loader):
    sample_img, sample_lb = images[0], labels[0]
    print(f'Batch {idx+1}')
    print('Images shape:', images.shape, ', Labels shape:', labels.shape)
    break  # 예제이므로 한 배치만 출력하고 중단합니다.


데이터 나눠서 목적에 맞게 사용하기

from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
from torch.utils.data import Dataset, DataLoader

▽ mnist 사용

mnist = datasets.MNIST(root='./data', train=True, download=True)

▽ mnist 를 슬라이드 하기 위해 리스트에 담음

mnist_list = [mnist[i] for i in range(len(mnist))]

▽ transform에서 태그에 따라 이미지가 다르게 변형되도록 딕셔너리 생성

transform = {"train" :
             transforms.Compose([
                 transforms.RandomCrop(20),
                 transforms.RandomHorizontalFlip(p=0.99),
                 transforms.ToTensor(),
                 transforms.Normalize((0.1307,), (0.3081,))
                 ]),
             "val" :
             transforms.Compose([
                 transforms.RandomCrop(20),
                 transforms.RandomHorizontalFlip(p=0.5),
                 transforms.ToTensor(),
                 transforms.Normalize((0.1307,), (0.3081,))
                 ]),
             "test" :
             transforms.Compose([
                 transforms.RandomCrop(28),
                 transforms.RandomHorizontalFlip(p=0.01),
                 transforms.ToTensor(),
                 transforms.Normalize((0.1307,), (0.3081,))
                 ])}

▽ phase 변수에 따라, 데이터를 슬라이스 해서 나눠서 사용. transform도 나눠서 사용.

# 커스텀 데이터셋 클래스를 생성합니다.
class CustomDataset(Dataset):
    def __init__(self, data, transform=None, phase='train'):
        if phase == 'train':
            self.data = data[:int(0.6*len(data))]
        elif phase == 'val':
            self.data = data[int(0.6*len(data)):int(0.8*len(data))]
        else:  # phase == 'test'
            self.data = data[int(0.8*len(data)):]
        self.transform = transform[phase]

    def __getitem__(self, idx):
        image, label = self.data[idx]
        if self.transform:
            image = self.transform(image)
        return image, label

    def __len__(self):
        return len(self.data)

# 커스텀 데이터셋 객체를 생성합니다.
train_dataset = CustomDataset(mnist_list, transform=transform, phase='train')
valid_dataset = CustomDataset(mnist_list, transform=transform, phase='val')
test_dataset = CustomDataset(mnist_list, transform=transform, phase='test')

출력 부분은 동일하므로 생략


모델

  • init: 클래스 호출 시 실행 (인스턴스 생성시 실행)
  • call: (인스턴스 생성 후) 인스턴스 호출시 실행
    • 모델 클래스에서는 call 대신 -> forward
  • forward: (인스턴스 생성 후)인스턴스 호출시 실행

기본 형태

# 필요한 모듈 불러오기
import torch
import torch.nn as nn

# nn.Module을 상속받는 MyModel 클래스를 정의
class MyModel(nn.Module):
    def __init__(self): # 실행 : 인스턴스 처음 선언 시 (클래스 호출 시)
        super(MyModel, self).__init__() # nn.Module의 초기화 메소드를 호출
        self.fc1 = nn.Linear(10, 1) # 레이어 정의 : WX = Linear(n_in, n_out)
        self.activation = nn.Sigmoid() # 활성화 함수 정의

    # 순전파 정의
    # def __call__() : 생성된 인스턴스가 호출될 때 실행되는 함수
    def forward(self, inputs): # 실행 : 인스턴스 호출 시
        x = self.fc1(inputs) # 입력 데이터 레이어 통과
        outputs = self.activation(x) # 입력 데이터 활성화 함수 통과
        return outputs # 결과값


model = MyModel() # 모델 인스턴스 생성 = init 실행
print(model) # 모델 구조 확인


Sequential 모듈 사용해 모델 만들기

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

        # Sequntial을 이용해 block1 정의

        self.layer1 = nn.Sequential(
            nn.Linear(10, 10),  # 10차원 입력을 받아 10차원 출력, 파라미터 100개 + 10개
            nn.ReLU(),  # ReLU 활성화 함수
        )

        self.layer2 = nn.Sequential(
            nn.Linear(10, 5),  # 10차원 입력을 받아 10차원 출력, 파라미터 50개 + 5개
            nn.ReLU(),  # ReLU 활성화 함수
        )

        self.layer3 = nn.Sequential(
            nn.Linear(5, 1),
            nn.Sigmoid()
        )

    # 순전파 연산
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        return x


# 모델 인스턴스 생성
model = MyModel()
# 샘플 데이터 생성
sample_data = torch.randn(5, 10)  # 배치크기 5, 입력피처 10인 임의 데이터
# 모델을 통과시키기
output = model(sample_data)

print("output :", output)  # 모델의 출력 확인

output : tensor([[0.4786], [0.4833], [0.4913], [0.4951], [0.4858]], grad_fn=<SigmoidBackward0>)


torch.randn(배치크기, 입력피처)  

torch.randn(5, 10) 
# 배치크기 5, 입력피처 10 인 임의의 데이터


학습된 모델 불러오기

import torch
import torch.nn as nn

import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torch.nn.functional as F

ResNet은 마이크로소프트에서 개발한 알고리즘으로 원 논문명은 "Deep Residual Learning for Image Recognition"

# 사전 학습된 파라미터를 사용하여 ResNet 모델을 불러오기
resnet = models.resnet50(pretrained=True) # 50층

# 모델의 구조를 출력
print(resnet)

Conv2d: ResNet 레이어 구조

ResNet( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False) (layer1): Sequential( (0): Bottleneck( (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (downsample): Sequential( (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): Bottleneck(

.....

(relu): ReLU(inplace=True) ) ) (avgpool): AdaptiveAvgPool2d(output_size=(1, 1)) (fc): Linear(in_features=2048, out_features=1000, bias=True) )

(fc): FullyConnected - 완전 연결 구조

sigmoid는 오차함수 안에 포함되어 있기 때문에 여기에는 빠져있다.


전이 학습

기존 모델을 우리 모델과 연결한다. 출력 값이 원하는 카테고리가 나오게 한다.

resnet.fc

>> Linear(in_features=2048, out_features=1000, bias=True)

resnet.fc = nn.Linear(resnet.fc.in_features, 10)
resnet.fc

fc.in_features는 그대로, out_features = 10으로 변경


전이학습으로 출력 해보기

  • Tensor로 변환
    • transforms.ToTensor()
  • Conv2d 레이어의 이미지 사이즈(원래 224 * 224)는 크게 고려하지 않는다.
    단, 채널은 고려해야 한다. RGB채널로 바꿔줌. (그런데, Mnist는 애초에 흑백임)
    • transforms.Lambda(lambda x: x.repeat(3, 1, 1))
  • 학습된 것을 사용하는 경우, 값을 참고하여 넣는다.
    • transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

  • 데이터 폴더로 MNIST 다운
    • mnist = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
  • dataloasder는 이터레이터 타입
    • images, labels = next(iter(dataloader)) # 한 배치만 가져온다.
# transform 정의
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Lambda(lambda x: x.repeat(3, 1, 1)),  # 1채널을 3채널로 변환
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 이미지 정규화
])

# MNIST 데이터셋을 다운로드하고 변환
mnist = datasets.MNIST(root='./data', train=True, download=True, transform=transform)

# 데이터로더 생성
dataloader = torch.utils.data.DataLoader(mnist, batch_size=8, shuffle=True)

# 샘플 데이터를 가져오기
images, labels = next(iter(dataloader))
images.shape

>> torch.Size([8, 3, 28, 28])          # 배치, 채널, width, height


오차함수가 없으므로 활성화 함수 대신 softmax사용

  • outputs = F.softmax(outputs, dim=1)
    • import torch.nn.functional as F
  • softmax는 아래 *참고내용*의 다중 분류의 방식을 간소화 했다.
    • 시그모이드 함수를 간략화 해서, e^-wx 중 wx만 보자.
    • wx가 0을 기준으로 1/2보다 크거나 작거나를 알 수 있다.
    • wx는 음수가 될수도 있으니, y가 양수만 존재하는 e^wx를 사용한다. 

* 참고 내용 *
여러(3개) 카테고리로 분류해야 하는 문제
  • 2개 카테고리: 이진 분류 -> 출력(sigmoid): 1개
  • 3개 이상: 다중 분류 -> 출력(sigmoid): 3개
    • ex) 출력 값이 3개면 강아지, 고양이, 토끼를 선별하는데, 호랑이 이미지는? 셋 중 가장 확률이 큰 걸로 결정한다. 그런데, 만약에 출력 값을 2개로 하면, 배제하는 방법을 선택하기 때문에, 확률이 높은 것을 비교할 수 없다. 지도학습은 카테고리 중 1개는 무조건 결정해야 하기 때문이다. 각, 카테고리가 나올 확률이 0.1, 0.01, 0.001 일때, 각 확률은 0.1/.111 = 0.9, ... 0.09, .... 0.009

# ResNet 모델에 샘플 데이터를 통과
# 결과로는 각 클래스에 대한 로짓 값 출력
outputs = resnet(images)

# 로짓을 softmax에 통과시켜서 각 클래스에 대한 확률값으로 변환
outputs = F.softmax(outputs, dim=1)

print(outputs)
print(outputs.shape)

>> tensor([[0.0941, 0.1050, 0.1113, 0.1138, 0.1061, 0.1201, 0.0634, 0.1265, 0.0993, 0.0605], [0.0577, 0.1213, 0.0937, 0.1504, 0.0763, 0.1225, 0.0582, 0.0571, 0.0774, 0.1854], [0.0697, 0.0761, 0.0924, 0.0762, 0.0926, 0.1009, 0.1026, 0.1180, 0.0882, 0.1833], [0.0659, 0.1515, 0.1034, 0.1346, 0.0638, 0.0985, 0.0812, 0.1063, 0.0833, 0.1115], [0.0546, 0.2004, 0.0760, 0.0599, 0.0676, 0.0828, 0.1376, 0.1061, 0.0453, 0.1696], [0.0997, 0.1162, 0.0758, 0.0684, 0.0902, 0.1016, 0.1338, 0.1328, 0.0922, 0.0893], [0.0615, 0.0268, 0.1383, 0.2084, 0.0844, 0.0812, 0.0976, 0.0629, 0.1535, 0.0855], [0.1073, 0.0364, 0.0643, 0.1131, 0.0863, 0.0849, 0.1322, 0.0958, 0.0764, 0.2034]], grad_fn=<SoftmaxBackward0>) torch.Size([8, 10])



댓글 쓰기

다음 이전