인공지능 & Pytorch_입문(Tensor, Backpropagation, Data Loader)

  • python을 기본적으로 사용합니다

  • Gpu을 사용하기 위하여 Goole에서 이용할 수 있는 Colab을 이용해서 실행

인공지능을 위한 pytorch 배우기

1.Tensor(텐서) : pytorch 의 기본단위이며 GPU 계산 을 할 수 있게 해준다 numpy와 상당히 유사하다.

1.1 Tensor 생성 : 라이브러리 불러오기

import torch # Pytorch를 사용하기 위한 기본 라이브러리
import numpy as np #Numpy를 사용하기 위한 기본 라이브러리 np로 줄여서 사용

X = torch.empty(5,4) # 5X4 행렬 생성
print(X)
X = torch.ones(3,3) # 원소가 모두 1인 3X3 행렬 생성
print(X)
X = torch.zeros(2) # 원소가 모두 0인 2행 Vector
print(X)
X = torch.rand(5,6) # 표준정규분포를 따르는 무작위 원소의 5X6 행렬 생성
print(X) 

1.2 List, Numpy 배열을 Tensor로 만들기

리스트 = [3,4] # list 생성
넘파이 = np.array([4,50,7]) # numpy 배열 생성
torch.tensor(리스트) # List → Tensor
torch.tensor(넘파이) # Numpy → Tensor

1.3 Tensor의 크기와 타입 확인하기

X.size()  #.size()는 Tensor의 크기를 확인할 수 있다.
type(X) # type은 Python에서 사용되는 모든 것들을 종류로 보여준다.

1.4 Tensor의 연산

# 크기가 같아야 한다.
X = torch.rand(2,2) # 2X2 랜덤 행렬
Y = torch.rand(2,2) # 2X2 랜덤 행렬
print("X:",X)
print("Y:",Y)
X+Y # 첫번째 표현
torch.add(X,Y) # 두번째 표현
Y.add_(X) #세번째 표현이며 Y = X+Y 와 같은의미이며 inplace 방식이라고 한다.
print("기존 Y:",Y)  # 기존  Y값
Y.add_(X)  # Y = X+Y 연산
print("inplace Y:",Y)  # 대체된 Y값

1.5 Tensor의 크기 변환

# view 함수를 사용하여 크기를 변환

X = torch.rand(8,8) # 8X8랜덤 행렬
print("size of X:",X.size()) # size 확인 
A = X.view(64) #  8X8 행렬을 64개의 Vector로 바꿔준다 
print("size of A:",A.size()) # size 확인
B = X.view(-1,4,4) 
''' -1은 다른 인자를 우선으로 크기로 맞춘 뒤 남는 부분을 자동으로 맞춰달라는 의미 
→ 8X8 행렬을 -1X4X4로 표현하려면 -1자리에는 4가 들어가서 4X4X4 행렬이 되어야 한다. 따라서 -1자리에는 4가 자동으로 맞춰짐
'''
print("size of B:",B.size()) #size확인

1.6 Tensor를 Numpy 만들기

X = torch.rand(4,4) # 4X4 랜덤 행렬 
Y = X.numpy() # Tensor → Numpy
print("Y :",Y) # Y값 확인
type(Y) # Y의 타입 확인

1.7 단일 Tensor에서 값 추출

X = torch.ones(1) # 원소가 1인 Vecotr하나 생성
print("X:",X)
print(X.item()) # 원소가 하나일 때만 사용가능하며 tensor안에 하나의 스칼라 값을 가져온다. 텐서(행렬)가 아님
#  loss 값을 저장하기 위함

2.Backpropagation(역전파) : 인공 신경망을 최적화 하는 과정에서 미분은 필수적인 요소이다 pytorch는 최적화 과정인 역전파(Backpropagation)를 쉽게 할 수 있도록 자동 미분 계산을 제공한다.

2.1 자동 미분 준비

#라이브러리 불러오기
import torch
# requires_grad=True는 해당 텐서를 기준으로 모든 연산들을 추적할 수 있게 하는 옵션이다.
x = torch.ones(2,2, requires_grad=True) # 2x2 tensor 생성 자동미분
print(x)
y = x+1 # y는 x에 관한 식
z = 2*y**2 # z는 y에 관한 식
result = z.mean() # result는 z에 관한 식이다.
print("y:", y)
print("z:", z)
print("Result:", result)
# grad_fn=..은 추적이 잘 되고 있다는 뜻

2.2 역전파

result.backward() # result를 기준으로 역전파 진행
# 위에 식을 역으로 써내려 가보면
# result = (z_1 +... z_4)/4 → x는 2X2행렬이므로 총 4개의 원소이므로 
# z_i = 2 y_i **2 
# z_i = 2(x_i+1) **2
# d(result)/dx_i = x_i + 1

print(x)
print(x.grad)  
# backward()가 선언 된 변수를 기준으로 미분 d(result)/dx를 계산
# d(result)/dx_i = x_i + 1

d(result)/dx 이미지 그림을 보면 코드를 이해하기 쉽다!

3 데이터 불러오기 : 머신러닝의 근원은 데이터이다. 따라서 데이터의 수집,가공,사용 방법에 따라 모델 성능이 크게 달라질 수 있으며 데이터의 형태는 매우 다양하기 때문에 데이터 전처리를 가장 중요한 단계 중 하나이다.

import torch # 기본 라이브러리
import torchvision # 이미지 관련 라이브러리
import torchvision.transforms as tr # 이미지 전처리 기능들을 제공하는 라이브러리
from torch.utils.data import DataLoader, dataset # 데이터를 모델에 사용할 수 있도록 정리해 주는 라이브러리
import numpy as np # 넘파이 기본 라이브러리

3.1 Pytorch 제공 Data 사용

# https://pytorch.org/vision/stable/transforms.html 에서 여러 전처리 방법 확인 가능.
# tr.Compose 내에 원하는 전처리를 넣어준다.

transf = tr.Compose([tr.Resize(16), tr.ToTensor()]) # 16x16으로 resize 이후 Tensor 타입으로 변환

# Transforms on PIL Image
# Pad, Grayscale, RandomCrop, Normalize ..
# Transfroms on torch. *Tensor - tensor image
# torchvision.transforms.ToPILImage(mode=None)...
# ...

# https://pytorch.org/vision/stable/datasets.html 에서 다양한 이미지 데이터셋을 확인할 수 있다.
# torchvision.datasets에서 제공하는 CIFAR10 데이터를 불러온다.
# root에는 다운로드 받을 경로를 입력→ 자신의 폴더 경로를 선택해야 함
# train=Ture이면 학습 데이터를 불러오고 train=False이면 테스트 데이터를 불러온다.
# 미리 선언한 전처리를 사용하기 위해 transform=transf을 작성.

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transf)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transf) 

# 일반적으로 데이터셋은 이미지와 라벨이 동시에 들어있는 튜플(tuple) 형태다. (이미지, 라벨)
# trainset[0]은 학습 데이터의 첫 번째 데이터로 이미지 한 장과 라벨 숫자 하나가 저장되어 있다.
# trainset[0][0]은 이미지이며 trainset[0][1]은 라벨이다.

print(trainset[0][0].size()) 

# 현재 이미지 사이즈는 3x16x16이다. 여기서 3은 채널 수를 말하고 16x16은 이미지의 너비와 높이를 의미한다.
# 일반적인 컬러 사진은 RGB 이미지이기 때문에 채널이 3개 이고 (너비)x(높이)x(채널 수)로 크기가 표현된다.
# 하지만 파이토치에서는 이미지 한 장이 (채널 수)x(너비)x(높이)으로 표현되니 유의하도록 한다.

# DataLoader는 데이터를 미니 배치 형태로 만들어 준다.
# 따라서 배치 사이즈 및 셔플 여부 등을 선택할 수 있다.
trainloader = DataLoader(trainset, batch_size=50, shuffle=True)
testloader = DataLoader(testset, batch_size=50, shuffle=False)

len(trainloader)
# CIFAR10의 학습 이미지는 50,000장이고 배치 사이즈가 50장이므로 1,000은 배치의 개수가 된다.
# 즉 trainloader가 잘 만들어졌다는 것을 단편적으로 알 수 있다.

# iter, next를 이용해 일부 데이터를 확인할 수 있다.
dataiter = iter(trainloader)
images, labels = dataiter.next()

print(images.size())
# 일반적으로 학습 데이터는 4차원 형태로 모델에서 사용된다.
# (배치 크기)x(채널 수)x(너비)x(높이)

3.2 같은 클래스 별로 폴더를 정리한 경우

우선 colab에 마운트 하는 과정이 필요하다.

from google.colab import drive
drive.mount('/content/gdrive')

cd/content/gdrive/My Drive/deeplearningbro/pytorch

# 데이터가 같은 클래스 별로 미리 폴더를 정리 된 경우, ImageFolder의 1줄 선언으로 개인 데이터를 사용할 수 있다.
# 별도의 라벨링이 필요 없으며 폴더 별로 자동으로 라벨링을 한다.
# 예를 들어 class 폴더에 tiger, lion 폴더(./class/tiger와 ./class/lion)를 미리 만든다.
# 다음으로 ImageFolder에 상위 폴더 ./class를 입력하면 이미지와 라벨이 정리 되어 데이터를 불러온다.

transf = tr.Compose([tr.Resize((128, 128)),tr.ToTensor()]) # 128x128 이미지 크기 변환 후 텐서로 만든다.
trainset = torchvision.datasets.ImageFolder(root='./class', transform=transf) # 커스텀 데이터 불러온다.
trainloader = DataLoader(trainset, batch_size=2, shuffle=False) # 데이터를 미니 배치 형태로 만들어 준다.

dataiter = iter(trainloader)
images, labels = dataiter.next()

print(images.size(), labels)

3.3 정형화되지 않은 커스텀 데이터 불러오기 (3.2를 사용하지 못할 때)

  1. 라벨 별로 아름답게 폴더 정리가 되어 있으면 매우 좋겠지만 그렇지 않은 경우가 매우 많다.
  2. 다른 작업들과 공유 된 데이터인 경우 폴더를 함부로 정리할 수 없다.
  3. 이미지 데이터라도 이미지가 아닌 텍스트, 리스트, 배열 등으로 저장 되어 있는 경우도 있다.
 #32x32 컬러 이미지와 라벨이 각각 100장이 있다고 가정하다.

train_images = np.random.randint(256,size=(100,32,32,3)) # (이미지 수)x(너비)x(높이)x(채널 수)
train_labels = np.random.randint(2,size=(100,1)) # 라벨 수

# 이미지 전처리 작업이 필요할 경우 openCV와 같은 라이브러리를 이용하여 이 곳에서 작업할 수도 있다.
# 필자는 이 단계에서 전처리하는 것을 선호한다. 그 이유는 torchvision.transforms 라이브러리 보다
# OpenCV, SciPy와 같은 라이브러리가 더 많은 전처리 기술을 제공하며 이미지를 미리 처리해 놓고 전처리 된 이미지를 살펴보면서 
# 작업하는 것을 좋아하기 때문이다. 따라서 사용 목적과 편의성에 맞게 본인이 전처리를 어디서 할 지 정하면 될 것이다.

#......
#......
#......
#train_images, train_labels = preprocessing(train_images, train_labels)
#......
#......
#......

print(train_images.shape, train_labels.shape)

"""
from torch.utils.data import Dataset

class MyDataset(Dataset):
    
    def __init__(self):
    
    def __getitem__(self, index):
    
    def __len__(self):

이 양식을 통으로 가지고 다니자!!
"""

class TensorData(Dataset):

    def __init__(self, x_data, y_data):
        self.x_data = torch.FloatTensor(x_data) # 이미지 데이터를 FloatTensor로 변형
        self.x_data = self.x_data.permute(0,3,1,2) # (이미지 수)x(너비)x(높이)x(채널 수) -> (배치 크기)x(채널 수)x(너비)x(높이)
        self.y_data = torch.LongTensor(y_data) # 라벨 데이터를 LongTensor로 변형
        self.len = self.y_data.shape[0] # 클래스 내의 들어 온 데이터 개수 

    def __getitem__(self, index):
        return self.x_data[index], self.y_data[index] # 뽑아 낼 데이터를 적어준다.

    def __len__(self):
        return self.len # 클래스 내의 들어 온 데이터 개수 

# 파이토치에서는 (배치 크기)x(채널 수)x(너비)x(높이) 데이터가 사용 되므로 원래 데이터 (이미지 수)x(너비)x(높이)x(채널 수)를 변경해야만 한다. 
# permute에서 0(이미지 수), 1(너비),2 (높이), 3(채널 수)을 0(이미지 수), 3(채널 수), 1(너비),2 (높이)로 바꿔주는 것이기 때문에
# .permute(0,3,1,2)을 사용하는 것이다.

train_data = TensorData(train_images,train_labels) # 텐서 데이터 불러오기 
train_loader = DataLoader(train_data, batch_size=10, shuffle=True) # 미니 배치 형태로 데이터 갖추기

Commnets