ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Pytorch 튜토리얼] 1. 파이토치 기본 익히기
    AI/Pytorch 2021. 10. 13. 07:48
    728x90

    0. 텐서

    텐서는 배열이나 행렬과 매우 유사함.

    PyTorch에서는 연산을 할 때 텐서를 이용함.

    텐서는 Numpy의 ndarray와 유사하지만 GPU나 하드웨어 가속기에서 실행 가능하다는 점이 특징.

    (상호 변환도 가능)

     

    1. Dataset과 DataLoader

    데이터 처리 코드가 너무 복잡하기에 가독성과 모듈화를 위해 데이터셋 코드는 따로 빼두어야 함.

    Pytorch에서는 자체적으로 torch.utils.data.DataLoader와 torch.utils.data.Dataset 두 가지의 api를 제공해 미리 준비된 데이터셋 뿐만 아니라 내가 가지고 있는 자체 데이터를 사용하기 쉽게 해준다.

    Dataset은 샘플과 label을 저장하고, DataLoader는 Dataset을 샘플에 쉽게 접근할 수 있도록 iterable 객체로 감싼다.(순회하면서 접근 할 수 있게)

     

    1.1 사용자 정의 데이터셋 만들기

    사용자 정의  Dataset 클래스는 '반드시' 3개 함수(__init__, __len__, __getitem__)을 구현해야 함.

    아래 코드에서 이미지 파일 패스를 img_dir 디렉토리에 저장하고, label값은 annotations_file에 csv파일에 저장되어있는 값으로 저장함.

    import os
    import pandas as pd
    from torchvision.io import read_image

    class CustomImageDataset(Dataset):
        def __init__(self, annotations_file, img_dir, transform=none, target_transform=None):
            self.img_labels = pd.read_csv(annotation_file)
            self.img_dir = img_dir
            self.transform = transform
            self.target_transform = target_transform

        def __len __(self):
            return len(self.img_labels)

        def __getitem__(self, idx):
            img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
            image = read_image(img_path)
            label = self.img_labels.iloc[idx, 1]
            if self.transform:
                image = self.transform(image)
            if self.target_transform:
                label = self.target_transform(label)
            return image, label

     

    1.2 DataLoader로 학습용 데이터 준비

    Dataset은 데이터셋의 feature를 가져오고 하나의 샘플에 label을 지정하는 일을 함. 모델 학습때 샘플들을 미니배치로 전달하고, 매 에폭마다 데이터를 다시 섞어서 overfit을 막고, python의 multiprocessing을 사용해 데이터 검색 속도를 높임.

    DataLoader는 간단한 API로 이러한 복잡한 과정들을 추상화한, 순회 가능한 객체(iterable)임.

    from torch.utils.data import DataLoader

    train_dataloader = DataLoader(training_data, batch_size=64, shuffle= True)
    test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

     

    1.3 DataLoader를 통해 순회하기(iterable)

    DataLoader에 데이터셋을 불러온 뒤에는 필요에 따라 데이터셋을 순회(iterable)할 수 있음.

    아래 각 순회(iteration)는 -feature와 정답(label)을 포함하는- train_features와 train_labels의 묶음(batch)를 반환함.

    shuffle=True로 설정했으니 모든 배치를 순회한 뒤 데이터가 섞임.

    train_fatures, train_labels = next(iter(train_dataloader))
    print(f"Feature batch shape: {train_features.size()}")
    print(f"Labels batch shape: {train_labels.size()}")
    img = train_features[0].squeeze()
    label = train_labels[0]
    plt.imgshow(img, cmap="gray")
    plt.show()
    print(f"Label : {label}")

    1.4 변형(Transform)

    데이터 조작 방법. torchvision.transforms 모듈을 사용해 torchvision.dataset 매개변수 2개(transform, target_transform)에 각각 feature, label을 어떻게 변경할 것인지 전달함.

    import torch
    from torchvision import datasets
    from torchvision.transforms import ToTensor, Lambda

    ds = datasets.FashionMNIST(
        root = "data",
        train=True,
        download=True,
        transform=ToTensor(),
        target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
    )

    FashionMNIST의 feature는 PIL image 형식이며, 정답(label)은 정수(integer)이므로, 학습하려면 정규화된 텐서 feature값과 원-핫(one-hot)인코딩된 정답(label)값이 필요함. 이를 위해 ToTensor와 Lambda 함수를 사용한 것.

    ToTensor : PIL image나 Numpy의 ndarray를 FloatTensor로 변환하고, 이미지 픽셀 크기(intensity)값을 [0,1]범위로 스케일함

    Lambda 함수 : 여기서는 크기 10짜리의 zero tensor를 만들고, scatter_ 함수를 호출해 y에 해당하는 인덱스에 value=1을 할당함.

     

    2. 신경망 모델 구성하기

    신경망은 데이터 연산을 수행하는 layer와 module로 구성됨. torch.nn 네임스페이스는 신경망을 구성하는데 필요한 모든 기능 제공.

    PyTorch의 모든 모듈은  nn.Module을 상속한 하위 클래스임.

    신경망은 다른 모듈(계층;layer)로 구성된 모듈임. 이런 중첩 구조는 복잡한 아키텍처를 쉽게 구축하고 관리 가능.

    import os
    import torch
    from torch import nn
    from torch.utils.data import DataLoader
    from torchvision import datasets, transforms

     

    2.2 학습에 필요한 장비 설정

    가능한 경우 GPU와 같은 하드웨어 가속기에서 모델을 학습하는 게 좋음. 따라서 torch.cuda를 사용할 수 있는지 확인할 필요가 있음.

    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    print('Using {} device'.format(device))

    2.3 클래스 정의하기

    신경망 모델을 nn.Module의 하위클래스로 정의, __init__에서 신경망 계층을 초기화함. nn.Module을 상속받는 모든 클래스는 forward 메소드에 입력 데이터에 대한 연산을 구현함.

    class NeuralNetwork(nn.Module):
        def __init__(self):
            super(NeuralNetwork, self).__init__()
            self.flatten = nn.Flatten()
            self.linear_relu_stack = nn.Sequential(
                nn.Linear(28*28, 512),
                nn.ReLU(),
                nn.Linear(512, 512),
                nn.ReLU(),
                nn.Linear(512, 10),
                nn.ReLU()
            )
        def forward(self, x):
            x = self.flatten(x)
            logits = self.linear_relu_stack(x)
            return logits

    NeuralNetwork의 인스턴스(instance)를 생성하고, 이를 device로 이동한 뒤 structure를 출력하자.

    model = NeuralNetwork().to(device)
    print(model)

    모델을 사용하기 위해 입력 데이터를 전달하면, 백그라운드 연산들과 함께 모델의 forward가 실행됨.

    *model.forward() 직접 호출 금지

    모델에 값을 입력하면 각 class에 대한 raw예측값을 갖는 10차원 텐서가 return됨. raw 예측 데이터를 nn.Softmax 모듈을 사용해 예측 확률을 얻을 수 있음.

    X = torch.rand(1, 28, 28, device=device)
    logits = model(X)
    pred_probab = nn.Softmax(dim=1)(logits)
    y_pred = pred_probab.argmax(1)
    print(f"Predicted class : {y_pred}")

    2.4 모델계층(layer)

    FashionMNIST 모델의 계층들을 살펴보기위해 28x28크기의 이미지 3개로 구성된 미니배치를 가져와 신경망을 통과할 때 어떤 일이 발생하는지 살펴보고자 함.

    input_image = torch.rand(3, 28, 28)

    nn.Flatten계층을 초기화하여 28x28의 2D 이미지를 784 픽셀 갓ㅂ을 갖는 연속된 배열로 변환(dim=0의 미니배치 차원은 유지됨)

    flatten = nn.Flatten()
    flat_image = flatten(input_image)
    print(flat_image.size())

    nn.Linear

    선형 계층은 저장된 가중치(weight)와 편향(bias)을 사용하여 입력에 선형 변환(linear transformation)을 적용하는 모듈임.

    layer1 = nn.Linear(in_features=28*28, out_features=20)
    hidden1 = layer1(flat_image)
    print(hidden1.size())

    nn.ReLU

    비선형 활성화(activation)은 모델의 입력과 출력 사이에 복잡한 관계(mapping)을 만듦. 비선형 활성화는 선형 변환 후에 적용되어 비선형성(nonlinearlity)를 도입하고, 신경망이 다양한 현상을 학습할 수 있도록 도와줌.

    이 모델에서는 nn.ReLU를 선형 계층들 사이에서 사용하지만 모델을 만들 때는 비선형성을 가진 다른 활성화를 도입할 수도 있음.

     print(f"Before ReLU : {hidden1}\n\n")
    hidden1 = nn.ReLU(hidden1)
    print(f"After ReLU: {hidden1}")

     

    nn.Sequential

    nn.Sequential은 순서를 갖는 모듈의 컨테이너임. 입력 데이터는 정의된 순서대로 모든 모듈을 통해 전달됨. 순차 컨테이너(sequential container)를 사용해 아래의 seq_modules와 같은 신경망을 빠르게 만들 수 있음.

    seq_modules = nn.Sequential(
        flatten,
        layer1,
        nn.ReLU()
        nn.Linear(20,10)
    )
    input_image = torch.rand(3, 28, 28)
    logits = seq_modules(input_image)    

     

    nn.Softmax

    신경망의 마지막 선형 계층은 nn.Softmax 모듈에 전달될 ([-INF,INF]범위 raw value인)logits을 반환함. logits는 모델의 각 class에 대한 예측 확률을 나타내도록 [0,1]범위로 비례해 조정(scale)함. dim 매개변수는 값의 합이 1이 되는 차원을 나타냄

    softmax = nn.Softmax(dim=1)
    pred_probab = softmax(logits)

     

    2.5 모델 매개변수

    신경망 내부의 많은 계층들은 매개변수화(parameterize)됨. 즉, 학습 중에 최적화되는 가중치와 편향과 연관지어짐.

    nn.Module을 상속하면 모델 객체 내부의 모든 필드들이 자동으로 추적(track)되며, 모델의 parameters() 및 named_parameters() 메소드로 모든 매개변수에 접근할 수 있게 됨.

    이 예제에서는 각 매개변수들을 순회하며(iterate), 매개변수의 크기와 값을 출력함.

    print("Model structure: ", model, "\n\n")

    for name, parm in model.named_parameters():
        print(f"Layer: {name} | Size: {param.size()} | Values: {param[:2]} \n")

     

    3. Torch.Autograd 자동 미분

    backward할 때 계산하는 gradient를 자동으로 계산해줌. 모든 계산 그래프에 지원됨

    입력 x, 매개변수 w(weight),b(bias) 그리고 loss function(손실 함수)을 가볍게 표현해보겠음.

    z = x*w+b (단일 계층 신경망 함수)

    loss = cross_entropy(z,y) (y는 정답값)

    import torch
    x = torch.ones(5) #input
    y = torch.zeros(3) #expected output
    w = torch.randn(5,3, requires_grad=True) # requires_grad=True시 gradient 추적가능
    b = torch.randn(3, requires_grad=True)
    z = torch.matmul(x,w)+b
    loss = torch.nn.functional.binary_cross_entropy_with_logits(z,y)

    3.1 변화도(Gradient) 계산하는 방법은 간단

    loss.backward()로 역전파를 실행하면 requires_grad 파라미터에 True를 넣었던 텐서인 w와 b의 gradient 값을 볼 수 있음.

    loss.backward()
    print(w.grad)
    print(b.grad)

    3.2 변화도 추적 멈추기

    하지만 위와 같이 변화도를 추적하는 기능을 사용하면 연산 속도가 떨어지기도 하며, 순전파 연산만 필요할 경우 필요없음.

    따라서 torch.no_grad()블록으로 둘러싸서 연산 추적을 멈추는 기능을 제공함

    z = torch.matmul(x,w)+b
    print(z.requires_grad) #True

    with torch.no_grad():
        z = torch.matmul(x,w)+b
    print(z.requires_grad) # False

    detach 메소드를 사용하면 grad 추적기능을 떼어버림

    z = torch.matmul(x,w)+b
    z_det = z.detach()
    print(z_det.requires_grad) #False

    3.3 연산 그래프란

    autograd의 알고리즘은 데이터(텐서)가 실행된 모든 연산들의 기록을 Function 객체로 구성된 DAG(Directed Acyclic Graph)에 저장함. 여기서 그래프의 leaf는 입력 텐서이고 root(뿌리)는 결과 텐서임.

    그래프를 root에서 leaf까지 추적하면 chain rule에 따라 변화도를 자동으로 계산하는 방식임.

    'AI > Pytorch' 카테고리의 다른 글

    [pytorch 튜토리얼] 2. 모델 구현 및 저장  (0) 2021.10.13

    댓글

Designed by Tistory.