이론만 공부하면 지치니 실습 코드 리뷰를 하나 진행하자.

손글씨 Dataset으로 유명한 mnist 데이터 셋이다.

 

1. Module Import

import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms, datasets

 

필요한 모듈들 import한다.

- numpy, plt 는 수치 계산과 시각화를 위하여.

- nn은 신경망 모델을 위해, F는 함수들 따오기 위해 등

 

2. 딥러닝 모델을 설계할 때 활용되는 장비 확인

if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')

print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)

 

- cuda를 지원하는 gpu가 사용가능한지 확인, 만일 가능하면 gpu를, 그렇지 않다면 cpu를 사용하도록 설정.

 

BATCH_SIZE = 32
EPOCHS = 10

- hyperparameter 설정. 이전에 확인했듯 batch size는 2의 거듭승이 좋음.

 

3. MNIST 데이터 다운로드

train_dataset = datasets.MNIST(root = "../data/MNIST",
                               train = True,
                               download = True,
                               transform = transforms.ToTensor())

test_dataset = datasets.MNIST(root = "../data/MNIST",
                              train = False,
                              transform = transforms.ToTensor())

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False)

 

- train에 대한 param 설정을 통해 train set / test set 구분하여 다운

- transforms.ToTensor(): PIL 이미지나 넘파이 배열을 PyTorch의 FloatTensor로 변환하고, 픽셀 값을 [0, 1] 범위로 스케일링합니다. 

 

- 그 후 받아진 dataset을 dataloader로 변환(변환이 맞는 표현인진 모르겠다).

- 이전에 설정한 BATCH_SIZE (하이퍼파라미터)를 받아오며, train data에 대해선 shuffle을 사용하여 bias를 방지.

 

4.  데이터 확인하기 (1)

for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break

결과는 다음과 같다.

 

for-loop를 바로 break 때리는 것을 보아 하나의 배치만 확인하고자 하는 용도로 사용한듯.

-> 데이터 형태와 타입이 제대로 들어오는 지 단순 확인 용도.

 

X train set에 대해서는

 

  • 배치 크기(batch size): 32
  • 채널 수(channels): 1 (MNIST는 흑백 이미지이므로 채널이 1개)
  • 이미지 높이(height): 28 픽셀
  • 이미지 너비(width): 28 픽셀

 

와 같이 이루어지며, float tensor로 지정되게 됨.

반면 y train 데이터는 1차원 텐서로 label 값인 '정수'를 표현하게 됨.

 

5.  데이터 확인하기 (2)

pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(28, 28), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))

- figsize (10,1)로 설정.

- 위에서 for loop을 돌려서 X_train과 y_train에 첫번째 batch에 대한 데이터가 할당됨.

- 그 중 1~10번째 데이터들만 가져오고 y_train[i]를 통해 같이 표기하여 붙여줌.

 

--------------------- 여기까진 기본적인 data load 및 세팅 ---------------------

6. Multi Layer Perceptron (MLP) 모델 설계하기

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(28 * 28, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 10)

    def forward(self, x):
        x = x.view(-1, 28 * 28)
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)
        x = F.sigmoid(x)
        x = self.fc3(x)
        x = F.log_softmax(x, dim = 1)
        return x

- 여기서 다층 퍼셉트론에 대한 모델 정의

- MLP는 입력층, 하나 이상의 은닉층, 출력층으로 구성된 feed-forward 신경망

- MNIST 데이터셋의 이미지(28x28 픽셀)를 입력으로 받아서, 10개의 클래스(숫자 0~9)에 대한 예측을 출력

 

여기서 PyTorch의 장점 -> feed-forward function만 정의를 하여도 backward function에 대해서는 자동으로 정의가 됨.

(물론 function들은 nn.functional에서 제공해주는 함수들을 사용하여하여함.)

 

Net 클래스는 nn.Module을 상속하여 모델 정의. 부모 클래스인 nn.Module의 생성자를 호출하여 초기화.

 

fc1, fc2, fc3은 레이어 정의.

fc1은 28*28 픽셀 데이터를 1차원 벡터로 펼침. (MNIST 이미지 사이즈가 28*28 임)

fc3의 출력 노드수는 당연히 10으로 맞춰준다. (0~9까지의 10가지 숫자)

 

forward 메서드를 정의하며

입력 이미지를 flatten한 후 first fully-connected layer를 통과 시킨 후 sigmoid적용. 이하 반복.

그리고 마지막에 fc3까지 적용된 출력 값에 log softmax 함수를 적용하여 클래스별 로그 확률 계산

 

 

 

7. Optimizer, Objective Function 설정하기

model = Net().to(DEVICE)
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.5)
criterion = nn.CrossEntropyLoss()

print(model)

- 위에서 생성한 Net class의 객체 생성.

- 옵티마이저 설정. SGD 사용하며 learning rate은 0.01.

- momentum은 처음 봤는데 '이전 업데이트의 방향을 일정 부분 유지하여 학습 속도를 향상시키는 역할'이라고 한다.

- loss function (criterion) 설정. 크로스 엔트로피 손실 함수 사용.

 

print한 결과값은 다음과 같다.

 

8. MLP 모델 학습을 진행하며 학습 데이터에 대한 모델 성능을 확인하는 함수 정의

def train(model, train_loader, optimizer, log_interval):
    model.train()
    for batch_idx, (image, label) in enumerate(train_loader):
        image = image.to(DEVICE)
        label = label.to(DEVICE)
        optimizer.zero_grad()
        output = model(image)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()

        if batch_idx % log_interval == 0:
            print("Train Epoch: {} [{}/{} ({:.0f}%)]\tTrain Loss: {:.6f}".format(
                epoch, batch_idx * len(image), 
                len(train_loader.dataset), 100. * batch_idx / len(train_loader), 
                loss.item()))

 

- train이라는 function 정의. 뒤에 10에서 보면 알겠지만 한 epoch에 대해 학습 / 학습에 대한 성능 확인하는 함수.

- 큰 틀에서 함수는 parameter로 모델과 훈련 데이터(엄밀하게 말하면 배치 단위로 제공되는), 옵티마이저, 학습 상태를 출력할 간격 총 4개를 받는다.

 

- model.train() : 모델의 학습 모드를 설정하는 메소드.

- for loop에서 enumerate을 통해 batch index를 출력하며 돌림.

- image와 label로 가져오며 이는 X(우리가 학습시킬 이미지), y(label) 정답을 의미한다.

- pytorch의 옵티마이저는 gradient가 누적되므로 계속 초기화 해준다.

- 모델에 image를 집어넣어 output을 계산해주고 이를 loss function에 넣어 오차를 구한다.

- 역전파가 계산되며 각 파라미터에 gradient가 저장된다.

- 이를 optimizer.step()을 통해 모델의 파라미터들을 업데이트 시킨다.

 

그 아래는 batch를 돌리며 모델이 어느 정도 성능을 내는지, 배치가 얼마나 돌아갔는지 등을 표기한다.

 

9. 학습되는 과정 속에서 검증 데이터에 대한 모델 성능을 확인하는 함수 정의

def evaluate(model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0

    with torch.no_grad():
        for image, label in test_loader:
            image = image.to(DEVICE)
            label = label.to(DEVICE)
            output = model(image)
            test_loss += criterion(output, label).item()
            prediction = output.max(1, keepdim = True)[1]
            correct += prediction.eq(label.view_as(prediction)).sum().item()
    
    test_loss /= (len(test_loader.dataset) / BATCH_SIZE)
    test_accuracy = 100. * correct / len(test_loader.dataset)
    return test_loss, test_accuracy

모델의 성능을 평가할 evaluate이라는 function을 정의하자.

model.eval()을 호출하면 모델이 평가 모드로 전환됨.

- 평가 모드로 전환되면 드롭아웃 비활성화, 배치 정규화의 이동 평균과 이동 분산이 업데이트 되지 않음.

- test_loss와 correct을 0으로 초기화.

- test_loss : 평가할 데이터의 총 loss 수치. correct : 모델이 정확하게 맞춘 숫자.

- with torch.no_grad(): 이 블럭 내에서는 자동 미분 비활성화. 불필요한 계산 안하니 메모리 할당(사용량?) 이점.

- 이전 train function과 대략적 비슷한 흐름. test_loader를 인자로 받아 for문을 돌리며, loss값을 계속 test_loss에 누적시켜 더함.

 

- output의 가장 큰 값을의 Index를 출력하여 prediction과 label의 동일한지 여부 확인. 맞으면 더하고 아님말고.

 

10. MLP 학습 실행하며 Train, Test set의 Loss 및 Test set Accuracy 확인하기

for epoch in range(1, EPOCHS + 1):
    train(model, train_loader, optimizer, log_interval = 200)
    test_loss, test_accuracy = evaluate(model, test_loader)
    print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
        epoch, test_loss, test_accuracy))

맨 마지막 부분이다.

위에서 정의한 함수들을 사용하며 맨 앞에서 정의한 epoch만큼 for문을 돌리며 train, evaluate을 실행한다.

 

Notice

모든 코드는 하기 링크에서 가져왔으며 내 이해를 위한 글임.

https://github.com/Justin-A/DeepLearning101/blob/master/2-1_MNIST_MLP.ipynb

 

DeepLearning101/2-1_MNIST_MLP.ipynb at master · Justin-A/DeepLearning101

Code about DeepLearning101 Book. Contribute to Justin-A/DeepLearning101 development by creating an account on GitHub.

github.com

 

위 코드를 설명한 https://velog.io/@gr8alex/PyTorch%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-MNIST-%EB%B6%84%EB%A5%98-%EB%AA%A8%EB%8D%B8-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0 도 참고함.

 

 

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

이제 모델을 만들고 학습을 했으면 실제 상황에 써봐야하지 않겠는가.

 

1. 테스트 할 데이터 생성

파워포인트로 직접 그렸다. 물론 데이터는 하나하나 저장해주어야 한다.

 

2. Preprocessing

기존의 이미지

우리가 이전에 데이터셋으로 쓴 이미지는 다음과 같이

1. 검은색 배경에 흰색 글씨

2. 28*28 pixel의 데이터이다.

 

def preprocess_image(image_path):
    image = Image.open(image_path).convert('L')
    image = image.resize((28, 28), Image.LANCZOS)
    image = ImageOps.invert(image)
    transform = transforms.Compose([
        transforms.ToTensor()
    ])
    image = transform(image)
    image = image.unsqueeze(0)
    print(image)
    return image

따라서     image = ImageOps.invert(image) 를 통해 반전을 시켜주며 Resize로 사이즈를 맞춰준다.

그럼 다음과 같은 이미지가 나온다.

 

3. 실제 테스트

image_path = '/test_data_5.png'
input_image = preprocess_image(image_path).to(DEVICE)

with torch.no_grad():
    output = model(input_image)
    _, predicted = torch.max(output.data, 1)
    print('예측된 숫자:', predicted.item())

이를 예측한다.

(근데 생각보다 많이 부정확하다. 5개 중 3개만 맞췄다. 원인 파악을 해봐야겠다.)

 

원문 자료 : 딥러닝을 이용한 자연어 처리 입문 (링크: https://wikidocs.net/22886)

 

서문

앞서 배운 피드 포워드 신경망은 입력의 길이가 고정되어 있어 자연어 처리를 위한 신경망으로는 한계가 있었습니다. 결국 다양한 길이의 입력 시퀀스를 처리할 수 있는 인공 신경망이 필요하게 되었는데, 자연어 처리에 대표적으로 사용되는 인공 신경망이 바로 순환 신경망(Recurrent Neural Network, RNN)입니다. 이번 챕터에서는 가장 기본적인 순환 신경망인 바닐라 RNN, 이를 개선한 LSTM, GRU에 대해서 학습해봅시다. LSTM과 GRU를 이해한다면 텍스트 분류나 기계 번역과 같은 다양한 자연어 처리 문제들을 풀 수 있습니다.

 

다양한 길이의 입력 시퀀스를 처리하기 위해 RNN고안.

하지만 RNN에서도 단점을 보완한 점이 LSTM 및 GRU.

 

내용

RNN(Recurrent Neural Network)은 순환 신경망이라고도 하며, 시퀀스 데이터를 처리하기 위해 설계된 딥러닝 모델이다.

(이름에서 알 수 있듯이 'recurrent'는 '반복되는'이라는 의미.)

 

RNN의 핵심 특징은 입력 데이터의 순서를 고려할 수 있다는 점.

=> 이는 시계열 데이터, 텍스트, 음성 인식 등 시간에 따라 변화하는 데이터를 다루는 데 유리함

 

입력과 출력을 시퀀스 단위로 처리

(*시퀀스는 문장과 같은 단어가 나열된 것을 의미)

 

이러한 시퀀스를 처리하기 위해 만든 시퀀스 모델 중 딥러닝의 가장 기본적인 시퀀스 모델.

TMI(or TIP) : 용어는 비슷하지만 순환 신경망과 재귀 신경망(Recursive Neural Network)은 전혀 다른 개념

 

 

앞서 배운 것들은 은닉층에서 활성화 함수(activation function)를 지닌 값은 오직 출력층 방향으로만 향함.

-> 이와 같은 애들을 피드 포워드 신경망(Feed Forward). 개인적인 생각으론 앞방향으로 '먹인다'는 것이 잘 표현한듯.

=> 하지마 RNN은 은닉층 노드에서 활성화 함수를 통해 나온 result를 출력층 방향으로도 보내지만 다시 은닉층 노드의 입력으로도 보냄

 

출처 : https://wikidocs.net/22886

위 그림서 x는 입력층의 벡터, y는 출력층의 출력 벡터이다. (bias 또한 입력으로 존재할 수 있지만 위 그림에선 생략)

- 셀(cell) : RNN에서 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드

이 셀은 이전 값을 기억하려고 하는 (일종의 메모리 역할) -> 이를 메모리 셀 or RNN 셀 이라고 표현

은닉 층의 메모리 셀은 각각의 시점에서 바로 이전 시점에서의 은닉층의 메모리 셀에서 나온 값을 자신의 입력으로 사용하는 재귀적 활동을 함. 현재 시점 t에서 메모리 셀이 다음 시점인 t+1의 자신에게 보내는 값을 은닉 상태(hidden state)라고 함.

해당 내용을 그림으로 표현하면 다음과 같음.

(좌측 사진은 화살표로 사이클을 그려 재귀 표현, 우측은 여러 시점으로 펼쳐서 표현)

Feed Forward 신경망에서는 뉴런이라는 단위를 사용했지만, RNN에서는 입력 벡터, 출력 벡터, 은닉층에서는 은닉 상태라는 표현을 주로 더 사용.

이를 뉴런 단위로 시각화 한다면 다음과 같고, 뉴런 단위로 해석 시 입력층의 뉴런 수는 4, 은닉 층의 뉴런 수는 2, 출력층의 뉴런 수는 2. 

RNN은 입력과 출력의 길이를 다르게 설계 가능. (입, 출력의 단위는 정의하기 나름이지만 가장 보편적인 단위는 '단어 벡터')

--- 24년 10월 5일. (08-01 보충 필요)

 

바닐라 RNN의 한계

(앞서 배운 가장 단순한 형태의 RNN을 Vanila RNN이라 한다. 바닐라 RNN의 한계를 극복하기 위해 나온 다양한 RNN의 변형 중 LSTM에 대한 소개.)

 

바닐라 RNN은 출력 결과가 이전의 계산 결과에 의존. 하지만 비교적 짧은 시퀀스에 대해서만 효과를 보임.

(어쩌면 가장 중요한 정보가 시점의 앞 쪽에 위치할 수 있음.)

=> 이 문제를 장기 의존성 문제(The problem of Long-Term Dependencies)라고 함.

 

바닐라 RNN의 내부 (bias는 생략)

바닐라 RNN은 x_t와 h_t-1이라는 두 입력이 (가중치가 곱해져) 메모리 셀의 입력이 됨.

-> 이를 hyperbolic tangent의 입력으로 사용. -> 이 값은 은닉층의 출력인 은닉 상태가 됨.

 

LSTM의 내부

 

--- 24년 10월 6일. (08-02 추가 필요)

주요 특징

  • 순환 구조: RNN은 시퀀스의 각 항목을 처리할 때, 이전의 항목에서 얻은 정보를 현재 항목의 계산에 활용할 수 있도록 설계되어 있습니다. 이 때문에 RNN은 현재 시점의 입력뿐만 아니라 이전 시점의 입력도 기억하고 활용할 수 있습니다.
  • 상태 유지: RNN의 각 노드는 현재 시점의 입력과 이전 시점의 은닉 상태(hidden state)를 결합하여 새로운 은닉 상태를 만듭니다. 이 과정에서 이전의 정보가 지속적으로 전달되므로, 모델이 순차적인 정보를 처리하고 학습하는 데 도움을 줍니다.

한계와 문제점

  • 기울기 소실/폭발 문제: RNN은 긴 시퀀스를 처리할 때, 역전파 과정에서 기울기(gradient)가 소실되거나 폭발하는 문제가 발생할 수 있습니다. 이는 RNN이 긴 시퀀스에서 장기 의존성을 학습하는 데 어려움을 겪게 만듭니다.
  • 단기 기억 문제: RNN은 상대적으로 짧은 시퀀스에서 잘 동작하지만, 시퀀스가 길어질수록 이전 정보가 사라지기 쉬워 장기 의존성(long-term dependency)을 학습하는 데 어려움이 있습니다.

LSTM과 GRU

RNN의 위 한계를 극복하기 위해 RNN의 변형 모델인 LSTM(Long Short-Term Memory)과 GRU(Gated Recurrent Unit) 등이 개발되었으며 이들 모델은 내부 게이트 구조를 통해 기억과 잊기의 과정을 조절함으로써 장기 의존성을 더 잘 학습할 수 있음.

RNN은 자연어 처리, 음성 인식, 기계 번역 등 다양한 시퀀스 처리 작업에 사용되었으며, 여전히 일부 응용 분야에서 중요한 역할을 하고 있습니다. 하지만, 현재는 트랜스포머(Transformer)와 같은 모델이 더 널리 사용되고 있습니다.

 

 

 

 

참고

https://casa-de-feel.tistory.com/39

https://wikidocs.net/22886

 

 

선형 회귀와 자동 미분

  • 데이터에 대한 이해(Data Definition) : 학습할 데이터에 대해서 알아봅니다.
  • 가설(Hypothesis) 수립 : 가설을 수립하는 방법에 대해서 알아봅니다.
  • 손실 계산하기(Compute loss) : 학습 데이터를 이용해서 연속적으로 모델을 개선시키는데 이 때 손실(loss)를 이용합니다.
  • 경사 하강법(Gradient Descent) : 학습을 위한 핵심 알고리즘인 경사 하강법(Gradient Descent)에 대해서 이해합니다.

 

데이터에 대한 이해

- 훈련 데이터셋과 테스트 데이터 셋 : 훈련에 사용되는 데이터를 train dataset, 학습이 끝난 후 이 모델이 얼마나 잘 작동하는지 판별하는 데이터셋을 test dataset.

- x_train과 y_train은 맵핑되어야함.

 

가설 수립

- 머신러닝에서 식을 세울때 이를 가설이라고함. 이는 추정일수도, 경험에서 나오는 것일 수도 있음. 맞는 가설이 아니라면 계속 수정해나가면 됨.

- y = Wx + b 와 같은 형식 혹은 가설(hypothesis)의 h를 따서 H(x) = Wx + b라고도 표현.

- W를 가중치(Weight), b를 편향(bias)이라고함.

 

비용 함수(Cost function)에 대한 이해

- 비용 함수(cost function) = 손실 함수(loss function) = 오차 함수(error function) = 목적 함수(objective function)

 

출처 : https://wikidocs.net/53560

- 다음과 같은 그림에서 점 4개를 가장 잘 표현하는 직선이 무엇인지 수학적 근거가 필요. -> '오차'라는 개념 도입

- 단순 '실제값 - 예측값'으로 정의한다면 여러 문제가 생김 -> 그럼 오차는 어떻게 정의할 것인가.

-> 각 오차들을 전부 제곱해준 후 더하는 방식(MSE) 사용. (물론 다른 방법도 많지만 여기선 평균 제곱 오차 설명)

- 제곱을 하여 더해주면 음수의 데이터에 영향을 받지 않고, 각 오차가 클수록 더 심각하게 좋지 않다는 가중치의 의미도 부여.

Cost(W,b)를 MSE로 위와 같이 정의한다면 훈련 데이터를 가장 잘 나타내는 직선은 Cost(W,b)를 최소로 만드는 직선이다.

 

옵티마이저 - 경사 하강법(Gradient Descent)

- 위에서 어떤 경우가 가장 훈련 데이터를 잘 나타내는 경우인지 정의함 (cost function을 정의)

- 그럼 이제 그 최적의 cost function을 어떻게 찾을 것인가? => 옵티마이저(Optimizer) 알고리즘

 

가장 기본적인 옵티마이저 알고리즘인 경사 하강법

우선 b를 고려하지 않고 y = Wx와 같은 기준으로 진행. 해당 선형 방정식에서 W는 기울기임.

위 그림서 주황색선은 W=20, 초록색선은 W=1일 때의 case.

-> 기울기가 너무 커도 오차가 커지고, 너무 작아도 오차가 커짐.

x축 : Weight, y축 : cost

=> 그럼 그 중간 어딘가에서 오차가 제일 작아지는 지점이 있을 것임. (위 사진 파란선 참조)

=> 함수의 극소점의 향해 가도록 해야함. -> 이것이 경사 하강법의 아이디어.

 

경사 하강법의 아이디어는 비용 함수를 미분하여 현재 W에서 접선의 기울기를 구하고, 접선의 기울기가 낮은 방향으로 W값을 변경하는 작업을 반복하는 것. 이 반복 작업서 특정 숫자 알파를 곱해 새로운 W로 사용하는 방식.

 

Case 1. 만일 기울기가 음수라면

=> W값이 증가하여 기울기가 0인 방향으로 조정 됨.

 

Case 2. 만일 기울기가 음수라면 

=> W값이 감소하여 기울기가 0인 방향으로 조정 됨.

 

여기서 '알파'라고 하는 곱해지는 특정 숫자는 학습률(learning rate)라고 한다. 이를 먼저 생각해보면 W에 gradient 값을 빼가며 반복 작업을 할 것인데, 어느 정도의 scale로써 점차 맞춰 나갈지에 대한 변수이다. 만일 너무 크다면 수렴하지 않고 발산하거나, 추후 특정 구간 내에서 반복한다고 하여도 이 구간이 충분히 작지 못할 수 있다. 만일 learning rate가 너무 작다면 수렴을 하는 것에 있어선 안정적일 수 있지만 학습에 시간이 많이 소요되고 불필요하게 리소스를 낭비할 수 있다. 이런 적당한 learning rate을 찾아내는 것도 중요할 것 같다.

 

파이토치로 선형 회귀 구현하기

필요한 라이브러리들을 import 한 후 추후 동일하게 실습을 하더라도 같은 결과 출력을 위해 random seed를 준다.

(110은 내 생일이다.)

 

Float type tensor에 train dataset을 만든 후 shape을 print해보자.

x train set과 y train set의 size는 모두 3X1임을 알 수 있다.

 

가중치와 편향의 초기화

선형 회귀는 학습 데이터와 가장 잘 맞는 하나의 직선을 찾는 일

그리고 가장 잘 맞는 직선을 정의하는 것이 바로 W와 b이다.

(여기서 하나 빠뜨린 점이 있는데 '가장 잘 맞는'이라는 것 또한 정의가 되어야 한다. 맥락상으론 MSE가 최소일때를 말한다.)

 

가중치 W를 0으로 초기화 하고 requires_grad=True가 인자를 통해 학습을 통해 값이 변경되는 변수임을 명시.

마찬가지로 편향 b도 0으로 동일하게 세팅.

현재 세팅에선 다음과 같은 직선이 표현되고 있으며 x에 어떤 값이 들어가도 0을 예측하게 됨.

 

파이토치 코드 상으로 직선의 방정식에 해당하는 가설 선언

 

가설(직선의 방정식)을 선언하였으니 비용 함수 또한 선언 필요.

다음과 같은 MSE를 나타내는 cost function을 선언.

(여기서 좋은 점. Summation의 과정이 필요 없이 각 torch에 대한 element-wise하게 뺄셈이 수행되고 각 element에 대해 square값이 적용된다. 또한 그렇게 만들어진 하나의 tensor(사실 아직도 array로 생각하는게 편하긴 하다)에 대해 mean값을 구해주게 되어 cost function의 value가 print out 되는 듯. 만일 이게 아니라면 for-loop를 통해 n에 대한 loop를 돌려주며 각 개체에 value를 update하는 식으로 했어야되지 않을까 ... 그렇지만 torch가 없었다면 np를 사용하면 torch와 사실상 동일하긴 했을듯)

 

지금까지 한 일.

x, y train set 정의, hypothesis 선언, cost function 선언.

train set의 dataset에 대해 현재의 방정식은 이정도의 loss를 가짐. 까지 왔다.

그럼 이제 이 크나큰 loss를 점차 이런 방법으로 개선하여 hypothesis를 수정하는 방식을 반복 필요.

 

경사 하강법 구현

SGD는 경사 하강법의 일종.

(찾아보니 BGD(배치 경사 하강법), SGD(확률적 경사 하강법), MGD(미니배치 경사 하강법)이 있다. 이 부분도 추가적 공부 필요)

출처 : https://bruders.tistory.com/91

 

학습 대상인 W와 b가 SGD의 입력이 되며 lr은 Learning Rate를 의미.

 

zero_grad()를 실행하여 미분을 통해 얻은 기울기를 0으로 초기화 한다는데 이부분은 뒤에서 추가 설명.

cost.backward() 함수 호출을 통해 가중치 W와 b에 대한 기울기가 계산 된다는데 어떤 의미인지 모르겠음.

(이 부분 추가 공부 필요. 참고 1. : https://hongl.tistory.com/158#google_vignette , 참고 2. https://deepdata.tistory.com/1166#google_vignette 이해한 바로는 computational graph에서 역전파(backpropagation) 관한 내용인 것 같은데 CS231n에서 나와서 이해가 조금 편했던 것 같다. 근데 우선은 가볍게 이정도가 있다 ~ 로만 알고 넘어가도록 하자. 알고보니 뒤쪽에서 별도 정리가 되어있다고한다.)

 

전체 코드

  1. 데이터 train set을 정의하고
  2. 기본 hypothesis를 W=0, b=0으로 세팅을 해두고
  3. optimizer에 대해 SGD의 method를 사용할 것이며, W와 b에 대해 최적화를 이룰 것이다. lr은 0.01로 할 것이다.
  4. 그 후 epoch를 설정해주며 얼마나 반복할 것인지를 정한다.
  5. for문에서 해당 횟수만큼 반복을 하며 점차 W와 b의 값을 변화시켜 나간다.

좌측부터 순서대로 lr = 0.01일때, 0.0001일때, 0.5일때

동일한 횟수의 epoch를 기준으로 lr을 다르게 설정함에 따라 최적화 되는 정도가 다르거나 아예 발산해버림을 알 수 있다.

이는 learning rate의 적당한 수치 조절이 중요함을 알 수 있다.

 

실 정답은 H(x) = 2x이므로 0.01일때 가장 근접한 정답을 알 수 있다.

(혹은 epoch를 1만회 이상으로 증가시키면 더 근접한 정답을 얻을 수 있다.)

 

optimizer.zero_grad()가 필요한 이유

미분값인 2가 계속 누적되는 것을 볼 수 있음.

따라서 zero_grad()를 통해 계속 0으로 초기화 시켜주어야함.

 

torch.manual_seed()를 하는 이유

 
좌측부터 순서대로 실행한 코드.
이는 random하게 난수를 발생시켜도 seed 값을 동일하게 유지하면 이전과 동일한 값을 얻을 수 있음.
 
 
 
 

자동 미분(Autograd) 실습

requires_grad = True가 적용된 텐서에 연산을 하면 계산 그래프가 생성된다 !

backward 함수를 호출하면 그래프로부터 자동으로 미분 계산

 

원래는 8이 나오는 과정은 다음과 같다.

각 변수에 대한 미분과 Chain Rule을 이용하면 다음의 순서를 따른다.

(이전에 CS231n 강의에서 다음과 비슷한 compuatational Graph가 있을 때 그냥 수학적 계산을 하면 안되는 것인가 물어본 학생이 있었는데 각 node끼리 연결된 과정에서 multiple or addition만 적용된 경우 단순 연산의 차원을 쭉 내려 연산하는 것이 수행 능력이 더 좋았다고 들었던 것 같다.)

 

 

다중 선형 회귀(Multivariable Linear Regression)

위에서 진행한 것은 모두 1개의 x에 의해 y를 추론하는 단순 선형 회귀 였음.

하지만 x가 1개가 아닌 여러개가 될 경우 사용하게 될 다중 선형 회귀에 대해 이해해보자.

다음과 같이 3개의 퀴즈 점수로부터 최종 점수를 예측하는 모델을 만들어보자.

hypothesis는 다음과 같을 것이다.

 

다음과 같이 필요한 것들을 import 한 후 train data들을 정의해준다. 그 후 가중치와 bias에 대해 0으로 초기화 해준다.

(TMI : 일상생활에서 초기화는 어떤 존재하는 값을 없애는 느낌이 크지만, CS에서는 '무언인가를 초기에 준비시키는 것'이라는 의미로 쓴다. 따라서 초깃값 세팅의 의미로 생각하면 된다.)

 

다음과 같이 선언한 후 1만회 반복을 통해 최적값을 찾는다.

 

(Q. 여기서부터 갑자기 궁금한 점이 생겼다. zero_grad()를 통해 누적값이 안 생기도록 계속 0으로 초기화까진 이해 되었다. 다만, backward()를 통해 어떤 작업을 하는 것인가 ? GPT한테 물어보니 각 변수 w1, w1에 grad 속성에 기울기 값을 저장한다고 한다. 그 후 step()단계에서 파라미터 업데이트를 진행한다고 한다. 여기까지도 순서에 대한 이해 자체는 되었다. 다만, Single Variable이 아닌 Multi variable일 때 과연 Cost Function의 Optimize는 어떻게 진행되는가. 각 cost function에 대한 편미분으로 grad값이 진행되어 각 파라미터들은 자신의 grad값에만 영향을 받는가 ? => GPT피셜 Yes. (만일 그렇다면 learning rate를 변수마다 다르게 지정하여 더 최적화 할 수 있지 않을까). 또한 자신의 grad값으로만 update가 된다면 과연 그것이 정말 옳은 방향으로서 업데이트가 된다고 생각할 수 있을까. 이는 다시 gradient의 정의까지 가게 되는 것 같아서 나중에 다시 공부해봐야될듯.
참고자료 : https://www.humanunsupervised.com/post/linear-regression-multivariate-cost-function-hypothesis-gradient

=> 크게 각 Weight에 대한 update 방향을 1. 그래프로, 2. computational graph로, 3. Cost function의 정의로 이해하였지만 1, 2는 이해 완료. 3은 나중에 다시 생각해보자.)

 

 

벡터와 행렬 연산으로 바꾸기

 

여기서 issue 발생. 현재는 3개라는 매우 적은 controlable한 개수이지만 이가 만일 100만개와 같이 늘어나게 된다면 우리는 하나하나 변수 선언과 다 해줄 것인가 ? no. 또한 컴퓨터적 연산 계산에 있어서도 비효율적.

 

1) 벡터 연산으로 이해

의 가설은

와 같이 이해할 수 있고 각 벡터를 X와 W로 표현한다면 H(X) = XW로 나타낼 수 있다.

 

2) 행렬 연산으로 이해

와 같이 나타낼 수 있다.

 

 

파이토치로 재구현

다음과 같이 train set 작성 및 출력.

Weight Matrix는 결국 최종적으로 5X1이 나와야되니 3X1 size여야하고

b도 5X1이어야되는거 아닌가 ? 라고 생각했지만 어차피 브로드캐스팅이 잘 되겠구나 싶었다.

 

실제로 b의 크기를 정의 해주지 않았을 때와 정의를 해주었을 때 다음과 같이 차이가 난다. 왜 ?

 

기존의 hypothesis에서는 단순 상수의 b이다. 이를 Matirx연산을 위해 사이즈를 맞춰준 것이지 사실은 동일한 하나의 상수이다.

만일 b를 (5,1)로 두게 된다면 각 equation에서 b가 각각 optimized 되게 된다.

그래서 그냥 size를 두지 않는 것이 의도에 부합한 방법인듯.

 

이제 train된 model을 사용하여 어떤 Input이 들어왔을 때 output을 출력해보자.

 

사실 python에서 with문을 처음 보았다.

(물론 항상 쓰는 건 SQL이도 내용이 완전히 다르지만 ,,)

 

GPT피셜

 

  • 컨텍스트 관리자(Context Manager): with 문은 파이썬의 컨텍스트 관리자를 사용할 때 쓰입니다.
  • 목적: 특정 블록 내에서 리소스의 초기화 및 해제를 자동으로 관리합니다.

구문

 

with 컨텍스트_관리자:
    코드_블록

 

예시

with open('file.txt', 'r') as f:
    data = f.read()

와 같다고 한다.

 

with torch.no_grad(): 이 블록 안에서 수행되는 모든 연산에 대해 역전파(즉, 기울기 계산)를 비활성화

예측을 할 때는 가중치를 업데이트할 필요가 없기 때문에, 메모리와 계산 자원을 절약하기 위해 torch.no_grad()를 사용하는 것이 좋음.

 

nn.Module과 클래스로 구현하기

pytorch에서는 이미 구현되어져 제공되는 함수들이 많이 존재. 해당 제공되는 함수들을 불러와 구현해보자.

 

단순 선형 회귀

2개의 값이 출력되는데 첫번째가 W값이고 두번째가 b에 해당된다.

두 값 모두 현재는 랜덤 초기화가 되어 있다. 또한, 두 값 모두 학습의 대상이기에 requires_grad=True가 되어있음.

 

그 후, 옵티마이저를 정의. model.parameters()를 사용하여 W와 b를 옵티마이저에 전달.

W값이 2에 가깝고, b값이 0에 가까우며 학습이 잘 된 것을 확인 가능.

 

forward vs backward 연산

- H(x)식에 입력 x로부터 예측된 y를 얻는 것 -> forward 연산

- 학습 전, prediction 은 x_train으로부터 예측값을 리턴하므로 forward 연산

- 학습 후, pred_y 는 임의의 값 new_var로부터 예측값을 리턴하므로 forward 연산

- 학습 과정에서 cost function을 미분하여 기울기를 구하는 것 backward 연산

- cost.backward()는 비용함수로부터 기울기를 구하라는 의미이며 backward 연산

 

다중 선형 회귀 구현

train dataset 정의 및 model 선언.

model.parameters를 list로 변환했을 때 length는 2개인데 앞에 것에 들어있는 3개의 수는 w1~w3이며, 뒤의 것은 b이다.

모두 랜덤 초기화가 되어있으며 학습값이기에 requires_grad=True로 되어있음.

 

(learning rate은 0.00001로 정의. 만일 0.01로 하게 된다면 발산함)

-> 그럼 이 발산의 기준을 미리 파악할 수는 없을까 ? 적당한 learning rate을 설정하는 것의 기준이 있으려나.

 

또한 이하의 코드는 단순 선형 회귀와 동일.

x에 임의의 입력 [73, 80, 75]를 넣어 모델이 예측하는 y값 출력.

사실 이 데이터는 학습 데이터에 포함되어있는 값임. 실제 값은 152였는데 예측값이 151에 가까운 값이 나온 것을 통해 어느 정도 잘 최적화 된 것으로 보임.

 

모델을 클래스로 구현하기

 

단순 선형 회귀 모델

대부분의 파이토치 구현체에서 위와 같이 클래스를 사용한 모델 구현 형식은 많이 사용하기에 꼭 숙지해두기 !

 

위 코드 설명

class형태의 모델은 torch.nn.Module을 상속 받음.

__int__() : 클래스의 생성자. 객체가 생성될 때 호출됨.

super().__init__() : 부모(torch.nn.Module)의 생성자를 호출하여 그 기능을 상속 받음.

self.linear = nn.Linear(1,1) : 선형 변환 정의.

forward() : 입력 데이터를 받아 모델의 연산을 수행.

 

다중 선형 회귀 모델

다음과 같이 구현.

 

다중 선형 회귀 클래스로 구현

기본 세팅 및 class로 다중 선형 회귀 구현.

 

MultivariateLinearRegressionModel라는 클래스는 pytorch의 nn.Module을 상속 받음.

클래스 초기화 메서드(__init__)에서 super()를 통해 부모 클래스의 nn.Module을 상속받으며 nn.Linear 객체를 생성하여 모델의 선형층을 설정함. 이는 input dim = 3, output dim=1로 설정되어있음.

 

코드는 이전과 다른 바가 없음.

 

옵티마이저 설정을 해주며, 여러 경사 하강법 중 확률적 경사 하강법인 SGD로 설정. 학습률 또한 적당히 작은 수로 설정.

 

에포크를 2,000으로 설정하며 그만큼 for-loop를 돌게 됨.

학습은 x_train을 사용하여 예측 값을 계산하는 것으로 시작 됨.

 

모델이 처음 만들어 질때 초기화 된 값들은 임의의 값들이지만 어차피 학습을 하며 점차 나아진다.

계속 똑같은 문제를 가지고 돌리며 더 근접한 prediction으로 수렴하게 되는 것이다.

 

코드 입장에선 이러한 임의의 수로 맨 처음 prediction을 진행하게 되고 실제 목표값인 y_train과 얼마나 차이가 나는지를 계산하게 된다. 이 오차는 MSE로 계산하게 된다.

모델은 cost function 기준으로 각 파라미터에 대한 기울기를 계산하여 파라미터 값들을 점차 업데이트 해나간다.

이를 통해 더 정확한 예측을 하게 된다.

 

더 정확한 예측을 위해선 epoch값을 늘리거나, Learning rate을 더 작게 설정해주며 (이 때 epoch값은 늘어나야한다.)하면 될듯.

 

미니 배치와 데이터 로더

이 부분은 선형 회귀에 한정되는 부분은 아니고 데이터를 로드하는 방법과 미니 배치 경사 하강법에 대해서 공부.

 

이전에 다중 선형 회귀에서 사용한 데이터의 샘플의 개수는 5개이다.

이를 하나의 행렬로 선언하여 전체 데이터에 대해 경사 하강법을 수행함.

 

근데 만일 학습 데이터가 엄청 커지게 된다면 이를 하나의 행렬로 만들어 경사 하강법을 진행할 수 있을까 ?

-> 계산량이 많아지며 매우 느리고, 메모리의 한계로 계산이 불가할 수 있다.

출처 :  https://wikidocs.net/55580

 

이러한 문제를 대체하기 위해 더 작은 단위로 쪼개 학습하는 개념 출현.

이 단위를 미니 배치(Mini Batch)라고 함.

 

러프하게 생각해봤을 때 미니 배치 : 작은 단위로 쪼개어 학습하기 때문에 메모리 할당에 이슈는 없을 듯. 만일 존재한다면 더 작은 단위로 쪼개면 되기 때문.

 

미니 배치 학습에서는 미니 배치의 개수만큼 경사 하강법을 수행해야 전체 데이터가 한 번 전부 사용되어 1 에포크가 됨.

미니 배치의 크기를 배치 크기(batch size)라고 함.

 

 

- 전체 데이터에 대해 한 번에 경사 하강법 수행 : 배치 경사 하강법

- 미니 배치 단위로 경사 하강법 수행 : 미니 배치 경사 하강법

+ 배치 크기는 보통 2 제곱수를 사용. CPU와 GPU의 메모리가 2의 배수이므로 배치 크기가 2의 제곱수일 때 데이터 송수신의 효율을 높일 수 있기 때문.

 

이터레이션(Iteration)

출처 : https://wikidocs.net/55580

위 그림은 에포크와 batch size와 iteration의 관계를 보여줌.

이터레이션은 한 번의 에포크 내에서 이루어지는 매개변수인 Weight W와 bias b의 업데이트 횟수

전체 size가 2,000인데 batch size를 200으로한다면 iteration의 수는 10개.

-> 이는 한 번의 에포크 당 매개변수 업데이트가 10번 이루어짐을 의미함.

(사실 Mini batch로 쪼개며 update를 어느 시점에 하는가에 대한 의문이 있었는데 위 내용으로 풀림.)

 

데이터 로드하기(Data Load)

파이토치에서는 데이터셋(Dataset)과 데이터 로더(Data Loader)를 제공.

-> 이를 통해 미니 배치 학습, 데이터 셔플(shuffle), 병렬 처리까지 간단히 수행 가능.

TensorDataset은 텐서를 입력으로 받음. 이를 입력으로 정의 후 dataset으로 저장.

 

DataLoader는 기본적으로 2개의 인자를 입력 받음. 하나는 데이터셋, 하나는 미니 배치의 크기.

+  추가로 많이 사용되는 인자는 shuffle. shuffle=True로 사용하면 Epoch마다 데이터셋을 섞어 학습되는 순서를 바꿈.

모델과 옵티마이저를 정의 후 훈련 실행.

Cost 값이 점차 작아지긴 하지만 충분히 작지 않다 생각하여 epoch값을 늘려 다시 실행.

(이때 주의해야할 점이 모델과 옵티마이저를 다시 정의 해주고 가야지 아니면 이전 모델에 추가로 학습하는 꼴이다.)

 

epoch를 2,000까지 늘린 후 실행했을 때의 결과. 그 후 임의의 값을 넣어 예측.

 

커스텀 데이터셋(Custom Dataset) & 커스텀 데이터셋으로 선형 회귀 구현

torch.utils.data.Dataset을 상속받아 직접 커스텀 데이터셋(Custom Dataset)을 만드는 경우도 있음.

Dataset을 상속받아 메소드들을 오버라이드 하여 커스텀 데이터셋을 만들어보자.

가장 기본적인 뼈대

위 코드는 Pytorch의 Dataset 클래스를 상속받아 사용자 정의 데이터셋 클래스를 만드는 방법.

해당 클래스는 두 가지 데이터 포함(x_data, y_data).

 

클래스가 초기화될 때, 이 두 데이터를 내부 변수로 저장. 데이터 셋의 길이를 반환하는 메서드가 정의되어 있음, 이는 해당 개수를 반환.

(이는 x_data에서 sample의 개수를 의미함.)

 

가장 중요한 메서드는 (__getitem__) 인덱스를 입력으로 받아 해당 인덱스에 맵핑된 데이터를 반환하는 것.

이 메서드는 x_data와 y_data의 특정 인덱스에 해당하는 데이터를 torch.FloatTensor 형식으로 변환하여 반환.

(기존에 __init__에서 넣은 것은 단순한 list type이었음.)

 

위 코드는 Customdataset 클래스를 인스턴스화 하여 데이터셋 객체(dataset)를 만듦.

이 데이터셋으로 Pytorch의 DataLoader 객체를 생성하며 기타 param 결정.

 

그 후, input dim = 3, output dim = 1인 선형 회귀 모델을 정의.

(갑자기 든 생각인데 output dimension이 2 이상이면 어떨까. 또한 어떤 예시가 있을까)

또한, 옵티마이저 설정. 항상 하던대로 SGD 사용.

nb_epochs = 20
for epoch in range(nb_epochs + 1):
  for batch_idx, samples in enumerate(dataloader):
    # print(batch_idx)
    # print(samples)
    x_train, y_train = samples
    # H(x) 계산
    prediction = model(x_train)

    # cost 계산
    cost = F.mse_loss(prediction, y_train)

    # cost로 H(x) 계산
    optimizer.zero_grad()
    cost.backward()
    optimizer.step()

    print('Epoch {:4d}/{} Batch {}/{} Cost: {:.6f}'.format(
        epoch, nb_epochs, batch_idx+1, len(dataloader),
        cost.item()
        ))

그 후 이전과 동일한 코드로 학습.

 

nb_epochs : 전체 학습 횟수.

해당 횟수만큼 반복문을 돌리며, 그 내무에서 enumerate(dataloader):에서는 미니 배치 단위로 데이터를 가져와 모델을 학습 시킴.

데이터 로더에서 가져온 각 배치는 samples라는 변수에 저장되고 이를 x_train과 y_train으로 분리한다. samples는 길이가 2인 list type을 갖기에 다음과 같이 잘 찢어줄 수 있다.

 

모델은 prediction을 생성하고 해당 값의 MSE로 cost를 계산한다. 계산된 cost 값으 기반으로 모델의 가중치를 업데이트 하기 위해 기울기를 초기화 하고 (zero_grad), 손실에 대한 역전파를 수행하고(backward), 파라미터를 업데이트 한다.(step).

 

해당 for-loop 돌아가는 내부 데이터를 좀 더 확인해보자.

 

(enumerate() 함수는 기본적으로 인덱스와 원소로 이루어진 튜플(tuple)을 만들어줌. batch_idx는 torch나 기타 세팅에 의해 생성되는 것이 아닌 enumerate에서 tuple의 앞단 값을 찢어 만든 python 내장 함수 값임. 또한, samples가 shuffle=True로 했기에 잘 섞여들어가는 것을 볼 수 있다. 이 부분 False로 해보고 실행도 해보길..)

 

 

벡터와 행렬 연산 복습하기

이 단원도 써야할까 생각을 해보았지만, 항상 배우는 자세로 겸손하자.라 생각하여 쓴다.

 

배운 것 : 기본적인 epoch, mini batch, shuffle, customdataset class, dataload 등

 

벡터와 행렬과 텐서

추후 머신 러닝의 입, 출력이 복잡해지며 3차원 텐서에 대한 이해가 필수로 요구됨. (ex. RNN)

 

numpy로 텐서 설명.

 

0차원, 1차원 텐서

스칼라 : 0차원 텐서, 0D tensor

ndim의 값에 주목. 이때 나오는 값이 축의 개수 or tensor의 차원이라고 부름. (이 내용 중요하답니다.)

혼동 주의할 부분. 위 벡터는 4차원이지만, tensor의 차원에선 1차원임. (텐서의 차원으론 [] 개수로 세는 것이 편할듯. 혹은 데이터 구조를 그냥 그대로 받아들이는 정도..)

주의 : 벡터의 차원은 하나의 축에 놓인 원소의 개수를 의미하는 것, 텐서의 차원은 축의 개수를 의미

 

2차원 텐서(행렬)

'행과 열'의 두 축이 존재하니 해당 텐서의 차원은 2차원.

shape(텐서의 크기)란 각 축을 따라서 얼마나 많은 차원지를 나타냄.

(큰 방향에서 가보면 3개의 list내에 4개의 데이터로 이루어져있기에 -> 3X4)

(만일 주로 3차원(텐서 dim기준)까지만 쓰인다면 matrix로 뻐팅겨도 괜찮을듯.)

 

3차원 텐서

! 이 부분 중요하니 잘 이해하고 넘어갈 것

자연어 처리(NLP)에서 자주 보게 되는 것이 다음의 3D tensor. 이는 시퀀스 데이터 표현서 자주 사용되기 때문.

예시로 3D는 (samples, timesteps, word_dim)이 된다.

추후 배치 개념에 대해서도 배울 때 (batch_size, timesteps, word_dim)과 같이 볼 수도 있다.

 

그 이상의 텐서

다음과 같다.

 

 

벡터와 행렬의 연산

다음과 같은 두 벡터가 (혹은 3X1 행렬이) 있다고 생각해보자.

해당 벡터에 대한 덧셈과 뺄셈 연산은 다음과 같이 element-wise하게 이루어진다.

 

행렬 또한 마찬가지다.

 

벡터의 내적과 행렬의 곱셈

내적은 dot product 혹은 inner product라고도 한다.

벡터 내적의 결과값으로는 스칼라 값이 나온다.

 

행렬의 곱셉은 딥 러닝을 이해하는 데에 필수적인 개념임으로 반드시 숙지 필요 !

추가로 다음의 두 가지 조건 또한 기억

  • 두 행렬의 곱 A × B이 성립되기 위해서는 행렬 A의 열의 개수와 행렬 B의 행의 개수는 같아야 한다.
  • 두 행렬의 곱 A × B의 결과로 나온 행렬 AB의 크기는 A의 행의 개수와 B의 열의 개수를 가진다.

(위 내용은 matrix의 dimension을 보며 생각하면 되겠다.)

 

다중 선형 회귀 행렬 연산으로 이해하기

다중 선형 회귀는 곧 2개 이상의 독립변수로부터 창출되는 종속 변수 1개를 예측하는 문제.

이를 행렬 연산으로 표현한다면 ?

다음과 같이 weight vector와 input vector에 대한 inner product + bias의 덧셈으로 이해할 수 있다.

 

이는 하나의 test case에 대한 얘기이고 만일 input(test set)이 많다면 ?

 

다음과 같이 얘기할 수 있다.

 

샘플(Sample)과 특성(Feature)

출처 : https://wikidocs.net/217269

Feature은 '특성'이라 생각하며 되고 독립 변수의 개수이다.

Sample의 수는 sample의 개수이다. (너무 당연한가)

 

가중치와 편향 행렬의 크기 결정

Weight Matrix와 input Matrix이 각각 몇 by 몇 이어야 하는 지에 대한 얘기이다.

너무 당연한 부분이라 넘어가도록..

 

추가로

입력 행렬과 출력 행렬의 크기로부터 가중치 행렬과 편향 행렬의 크기를 추정할 수 있다면, 딥 러닝 모델을 구현하였을 때 해당 모델에 존재하는 총 매개변수의 개수를 계산하기 쉽습니다. 어떤 딥 러닝 모델의 총 매개변수의 개수는 해당 모델에 존재하는 가중치 행렬과 편향 행렬의 모든 원소의 수이기 때문입니다.

보통 딥 러닝 모델의 매개변수 수로 AI 모델의 크기가 얼마나 크냐를 의미하고, 성능 또한 대략적으로 알 수 있다.

0. Pytorch(파이토치)란

파이토치는 2017년 초에 공개된 딥러닝 프레임워크로 개발자들과 연구자들이 쉽게 GPU를 활용하여 인공 신경망 모델을 만들고 학습시킬 수 있게 도와준다. 파이토치의 전신이라고 할 수 있는 토치(torch)는 루아 프로그래밍 언어로 되어 있었지만, 파이토치는 파이썬으로 작성되어 파이썬의 언어 특징을 많이 가지고 있다. 

 

파이토치는 페이스북의 인공지능 연구팀 멤버들이 주로 관리하며, 독자적으로 운영되는 파이토치 포럼은 사람들이 질문을 올리면 프레임워크 개발자를 비롯한 많은 사람이 답을 해주는 등 활발히 교류가 일어나고 있다.

 

참고 : https://jfun.tistory.com/238

 

Pytorch란?

1. 파이토치란 무엇일까? 출처 : 파이토치 첫걸음 - 최건호 파이토치는 2017년 초에 공개된 딥러닝 프레임워크로 개발자들과 연구자들이 쉽게 GPU를 활용하여 인공 신경망 모델을 만들고 학습시킬

jfun.tistory.com

 

1. 파이토치 패키지의 기본 구성

  • torch : 메인 네임스페이스입니다. 텐서 등의 다양한 수학 함수가 포함되어져 있으며 Numpy와 유사한 구조를 가집니다.
  • torch.autograd :자동 미분을 위한 함수들이 포함되어져 있습니다. 자동 미분의 on/off를 제어하는 콘텍스트 매니저(enable_grad/no_grad)나 자체 미분 가능 함수를 정의할 때 사용하는 기반 클래스인 'Function' 등이 포함되어져 있습니다.
  • torch.nn : 신경망을 구축하기 위한 다양한 데이터 구조나 레이어 등이 정의되어져 있습니다. 예를 들어 RNN, LSTM과 같은 레이어, ReLU와 같은 활성화 함수, MSELoss와 같은 손실 함수들이 있습니다.
  • torch.optim : 확률적 경사 하강법(Stochastic Gradient Descent, SGD)를 중심으로 한 파라미터 최적화 알고리즘이 구현되어져 있습니다.
  • torch.utils.data : SGD의 반복 연산을 실행할 때 사용하는 미니 배치용 유틸리티 함수가 포함되어져 있습니다.
  • torch.onnx : ONNX(Open Neural Network Exchange)의 포맷으로 모델을 익스포트(export)할 때 사용합니다. ONNX는 서로 다른 딥 러닝 프레임워크 간에 모델을 공유할 때 사용하는 포맷입니다.

 

2. 텐서 조작하기(Tensor Manipulation)

벡터, 행렬 그리고 텐서

기본적인 개념인 vector(벡터), matrix(행렬), tensor(텐서)에 관해 알아보자.

사실 본인 전공은 수학과(수리과학부)라 벡터와 행렬은 정말 익숙하다. 또한 차원 개념에 대해서도 편안하게 받아들여진다. 다만 딥러닝에서는 이를 '차원' 개념에서의 n-th dimension의 입장이 아닌 nd-tensor로 표기하며 이해한다. 사실 이는 다차원 행렬 혹은 다차원 배열과 동일하다.

 

(관련 도움이 될 포스트: https://coding-kindergarten.tistory.com/147)

 

출처 : https://wikidocs.net/231831

Tip

- [1,2,3]과 같은 1차원 tensor의 size는 3이다.

- [[1,2,3],[4,5,6]]과 같은 2차원 tensor의 사이즈는 (2,3)인지 (3,2)인지 헷갈리기 시작한다.

   => 항상 size를 확인할때는 바깥쪽의 괄호부터 카운팅한다. 결국 (2,3) 이다.

 

Tensor Example

출처 : https://wikidocs.net/52460

읽다보니 batch_size라는 것이 있다. 이 batch가 data 작업서 사용하는 batch처리와 일관된 맥락인 것 같아 이해가 편했다.

 

*NLP 분야의 3D tensor 이해

[[나는 사과를 좋아해], [나는 바나나를 좋아해], [나는 사과를 싫어해], [나는 바나나를 싫어해]]

다음과 같은 4개의 문장으로 구성된 훈련 데이터가 있다.

컴퓨터 입력을 위해 각 문장을 단어 단위로 쪼갠다.

[['나는', '사과를', '좋아해'], ['나는', '바나나를', '좋아해'], ['나는', '사과를', '싫어해'], ['나는', '바나나를', '싫어해']]

이제 위 데이터는 4X3의 크기를 갖는 2D tensor이다. 컴퓨터에 입력을 위해 각 단어를 벡터로 변환한다.

예시로 각 단어를 다음과 같은 벡터로 변환한다하자.

'나는' = [0.1, 0.2, 0.9]
'사과를' = [0.3, 0.5, 0.1]
'바나나를' = [0.3, 0.5, 0.2]
'좋아해' = [0.7, 0.6, 0.5]
'싫어해' = [0.5, 0.6, 0.7]

이 내용을 바탕으로 맨 위의 훈련 데이터를 재구성 하면 다음과 같다.

[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.5, 0.6, 0.7]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.5, 0.6, 0.7]]]

이 훈련 데이터는 4X3X3의 size를 갖는 3d tensor이다. 

 

만일 Batch size를 2로 한다면

첫번째 배치 #1
[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.7, 0.6, 0.5]]]

두번째 배치 #2
[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.5, 0.6, 0.7]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.5, 0.6, 0.7]]]

다음과 같이 두 번의 연산으로 수행할 것이다.

 

각 배치의 훈련 데이터 tensor의 크기는 2X3X3이다.

 

3. Numpy로 텐서 만들기

이미 어느 정도 아는 내용이라 따라만 쳐보고 넘어간다.

- ndim은 몇차원인지를 출력

- shape은 크기를 출력. (7,)은 1X7의 size를 의미.

 

인덱스는 0부터 시작이며 (python이니까..), list와 동일한 문법.

 

당연히 slicing도 마찬가지.

 

2차원의 경우도 동일하다.

2차원부턴 shape이 (4,3)인지 (3,4)인지 헷갈리지 말도록 하자.

 

4. 파이토치 텐서 선언하기

위에서 선언한 t는 1차원이며 print 했을때 list나 array와는 다르게 tensor라는 표기가 같이 나온다.

 

2차원의 경우 Size가 [4,3]으로 표기된다. 이 맥락에서 1차원에 대해 (7,)과 같이 표기되었던 np.array에 비해 좀 더 직관적인 표현이 아닌가 싶다. (1차원 한정이긴하지만..)

 

:를 통해 첫번째 차원에 대해 전체를 선택하며 두번째 차원에 대해 1번 인덱스 (즉, 두번째 값)을 가져온다.

 

 

브로드캐스팅

같은 크기의 행렬들에 대해 덧셈이나 뺄셈을 할 때는 우리가 생각하는 대로 계산하면 된다.

같은 크기의 행렬에 대한 계산

다만, 현실세계는 녹록치 않다. 불가피하게 크기가 다른 두 행렬에 대해서도 계산을 해야하는 상황이 온다.

파이토치에서는 알아서(자동으로) 크기를 맞춰 연산을 수행하게 하는 브로드캐스팅이라는 기능을 제공한다.

 

다음과 같이 크기가 다른 두 행렬에 대해 연산이 원래는 되면 안되지만 m2가 자연스레 [10,10]으로 바뀌며 연산이 되었다.

 

다음과 같이 2 by 1 matrix와 1 by 2 matrix에 대한 연산도 하기의 수정본과 같이 브로드캐스팅 된 이후 이루어진다.

=> 이는 굉장히 편리하지만 주의해서 이용해야한다. (python의 eval()과 비슷한 향기..)

 

자주 사용되는 기능들

행렬 곱셈 vs 곱셈

matmul : 이는 선형대수학에서 아는 평범한 행렬 곱셈을 의미한다.

 

mul : element-wise한 곱셈이다.

이는 m2가 브로드캐스팅 된 이후 각 동일한 위치의 element끼리 곱해졌다.

 

(관련하여 numpy의 dot과 matmul에 대해서도 공부하면 좋을듯

관련 링크 : https://jimmy-ai.tistory.com/104)

 

 

 

mean : 평균 계산

다만 1차원 tensor에서는 각 element에 대한 mean value가 나오지만

 

2차원에서 dimension에 대한 인자를 설정함에 따라 값이 소폭 다르게 나온다.

설명 : dimension을 인자로 주면 해당 차원을 제거한다는 의미라고 한다.

 

sum : mean과 동일하게 작동하지만 평균이 아닌 합을 구하는 것 뿐. 이하 동일.

 

max : 원소의 최댓값을 리턴

argmax : 최댓값을 가진 인덱스를 리턴

 

dimension을 설정하지 않은 상태에서 max를 쓰면 전체 element 중 maximum이 나온다.

하지만 dim=0을 설정하면 위에서 말했듯이 1번째 차원(행)을 삭제하며 (1,2)행렬이 되고 이에 대한 값의 계산은 [3,4]가 된다.

 

뒤에 딸려나오는 indices(index의 복수형)은 함께 리턴되는 argmax 값이다.

 

사실 다음과 같이 type을 확인해보니 t.max(dim~)에 대한 값이 tuple이나 다른걸로 나올줄 알았는데 torch관련 class로 정의된 것이 조금 신기했다.

 

뷰(View) - 원소의 수를 유지하면서 텐서의 크기 변경.

pytorch에서의 view는 numpy에서의 reshape과 같은 역할. -> 텐선의 크기(shape)을 변경해주는 역할.

 

위 텐서의 크기는 (2,2,3)

 

3차원 텐서 -> 2차원 텐서로 변경

-1의 경우 pytorch에게 맡기는 의미이며 두번째 차운의 길이는 3이 되도록 한다는 것이다.

reshape처럼 일렬로 배열 후 다시 합친다 생각하면 좋을 것 같다.

 

만일 [-1,7]의 값을 넣는 경우 

RuntimeError: shape '[-1, 7]' is invalid for input of size 12

와 같은 에러가 발생한다. 12의 약수를 넣어야 변환되기 때문이다.

 

 

사이즈를 3차원 -> 3차원도 된다.

-1로 두 자리 이상을 pytorch에게 맡기는 것은 안된다.

 

 

스퀴즈(Squeeze) & 언스퀴즈(Unsqueeze)

[3,1] 사이즈의 텐서 ft를 정의한 후 squeeze를 사용하면 [3]의 크기를 갖는 텐서로 변경된다.

 

궁금한 점 : 꼭 맨뒤의 차원을 내리는 것인지.

 

결론 (예시에 매몰되지말며 글을 잘 읽자)

- 맨 뒤의 차원은 아님. 1인 차원을 없애는 것임.

- 그리고 1인 차원을 제거하는 것임 (글을 잘 읽기.)

- 1인 차원이 없다면 이전과 동일한 텐서 출력

- 1인 차원이 여러개 있다면 다 제거함.

 

unsqueeze : 특정 위치에 1인 차원 추가

이는 view를 통해 만든 것과 동일한 결과를 만들 수 있음

 

=> view(), squeeze(), unsqueeze()는 텐서의 원소 수를 그대로 유지하면서 모양과 차원을 조절.

 

타입 캐스팅 (Type casting)

텐서에는 자료형 존재. CPU 연산이 아닌 GPU 연산을 위한 자료형 또한 존재.

 

이런 자료형 변환을 하는 것 => 타입 캐스팅

 

long type => 64bit 정수형

float type => 32bit 실수형 (각 element에 . 붙어있음)

 

연결(Concatenate)

pandas dataframe concat과 큰 차이가 없는듯.

 

스택킹(stacking)

처음에 생각했을때는 concatenate과 무슨 차이가 있지 ? 했지만

print된 shape을 보았을때 놀랐다.

 

이는 각 tensor끼리 쌓으며 하나의 dimension을 추가로 구성한 것이고

concat의 경우 list의 append마냥 그저 붙이기에 불과한 것이었다.

 

이 또한 dim=0 or 1을 통해 stacking할 방향을 정해줄 수 있다.

 

ones_like와 zeros_like

이는 numpy array에 zeros나 ones와 동일한 기능을 하는 것 같다.

 

In-place Operation (덮어쓰기 연산)

[2,2] tensor 하나 만들고 mul(2.)를 통해 element-wise하게 곱한 결과 및 기존 값 출력

 

다만, mul이 아닌 mul_ 사용시 원래의 값에 덮어쓰기 하며 출력

(이는 dataframe에 대한 연산을 할 때 inplace=True 와 같은 기능인듯하다.)

 

 

파이썬 클래스(class)

pytorch의 구현체들은 대부분 class 개념을 애용하고 있어 설명한다고 한다.

다만 본인도 class를 건너건너 배웠기에 이번 기회에 다시 쌓아나가보자.

 

함수로 덧셈기 구현

(result를 전역 변수로 선언. 다만 실제 사용한적은 정말 오랜만인듯)

함수 외부에서 정의된 result가 함수 내에서도 전역변수로서 정의가 되었고 이 객체에 그대로 더해지는 모습.

만약 두 개의 객체에 대해 계속하여 덧샘기를 구현하려면 다음과 같이 별도의 함수를 계속 독립적으로 만들어주어야함.

 

클래스(class)로 덧셈기 구현

클래스 생성 후 cal1, cal2라는 각각의 객체 생성.

동일한 method로도 각 객체에 대해 개별적으로 계산되는 모습.

 

 

참고 자료

https://wikidocs.net/book/2788

 

Pytorch로 시작하는 딥 러닝 입문

이 책은 딥 러닝 프레임워크 `PyTorch`와 허깅페이스의 `Transformers`를 사용하여 딥 러닝에 입문하는 것을 목표로 합니다. **AI의 완전 쌩 입문자**…

wikidocs.net

본 카테고리는 해당 책의 wikidocs 기준으로 만들어졌다.

 

기존에 알고 있던 Pytorch에 대해 다시 한 번 기초를 재정립할 겸 이 책을 기준으로 정리하고자 한다.

 

깃허브 주소: https://github.com/ukairia777/pytorch-nlp-tutorial

 

GitHub - ukairia777/pytorch-nlp-tutorial: pytorch를 사용하여 텍스트 전처리부터, BERT, GPT와 같은 모델의 다

pytorch를 사용하여 텍스트 전처리부터, BERT, GPT와 같은 모델의 다운스트림 태스크들을 정리한 Deep Learning NLP 저장소입니다. - ukairia777/pytorch-nlp-tutorial

github.com

 

 

01-01 코랩(Colab)과 아나콘다

 

우선 아나콘다부터 설치를 해준다. 다만, 본 책은 Window기준으로 설치를 진행하고 있기에 Mac(M2)의 설치 방법을 찾아 그대로 따라준다.

 

참고 링크: https://applecoconut.tistory.com/entry/M1-M2-ARM-Mac%EC%97%90%EC%84%9C-%EC%95%84%EB%82%98%EC%BD%98%EB%8B%A4-%EC%84%A4%EC%B9%98%EC%99%80-%EC%A0%9C%EA%B1%B0-%EB%B0%A9%EB%B2%95

 

아나콘다를 설치하는 주요 이유는 다음과 같다. (GPT Thank you)

  • 패키지 관리 및 의존성 해결: 아나콘다는 Conda라는 패키지 관리 도구를 제공하여 파이썬 패키지의 설치 및 의존성을 쉽게 관리할 수 있습니다. 특히, 여러 패키지가 서로 다른 버전을 요구하는 상황에서 환경을 분리하여 문제를 방지할 수 있습니다.
  • 가상 환경 관리: Conda는 가상 환경을 쉽게 만들고 관리할 수 있는 도구로, 프로젝트마다 별도의 환경을 설정하여 서로 다른 버전의 파이썬이나 패키지를 사용할 수 있게 해줍니다.
  • 다양한 과학 및 데이터 분석 패키지 기본 제공: 아나콘다는 데이터 과학, 기계 학습, 통계 분석에 자주 사용되는 패키지들(예: NumPy, Pandas, Matplotlib, Scikit-learn, TensorFlow 등)을 기본적으로 포함하고 있어 따로 설치할 필요가 없습니다.
  • 편리한 설치: 아나콘다는 파이썬과 다양한 패키지를 한 번에 설치할 수 있는 통합 환경을 제공합니다. 이를 통해 초보자들도 번거로움 없이 필요한 도구를 빠르게 설치하고 사용할 수 있습니다.
  • Jupyter Notebook 통합: 아나콘다에는 Jupyter Notebook이 포함되어 있어 데이터 분석 및 학습 과정에서 매우 유용합니다. 이를 통해 코드를 실시간으로 실행하고 시각화할 수 있습니다.

 

설치 완료 후 terminal 오픈 시 다음과 같이 (base)가 앞에 붙어있으며 conda에 대한 command를 확인하였을 때 잘 나오는 것을 볼 수 있다.

> conda update -n base conda
> conda update --all

의 명령어로 모든 파이썬 패키지들을 업데이트 해준다. Proceed [Y/N] 나오는 경우는 Y 후 엔터를 쳐주자.

 

Colaboratory

Colab 주소 : https://colab.research.google.com/

-> 학교 이메일 계정으로 만들었다가 별도의 이점은 없는 것 같아 개인 계정으로 재생성.

 

 

CPU에서 GPU로 런타임 유형 변경을 하려는데 TPU가 존재하여 찾아보니 TPU가 더 맞을 것 같아 TPU로 설정하였다.

(추후 여러 코드를 번갈아 돌려가보며 테스트 진행해보고자한다.)

참고 링크 : https://8terabyte.com/7#google_vignette

 

 

머신 러닝 워크 플로우(Machine Learning Workflow)

출처 : https://wikidocs.net/217160

 

1. 수집

데이터 수집에 있어 어떤 데이터를 수집할 것인지, 어느 범위까지 수집할 것인지 생각해야한다.

DSP 입장에서는 어떤 소스를 통해 (ex. from DMP인지 어느 bundle들로부터 데이터를 가져올 것인지, 그 외의 검색 데이터, 혹은 행동 데이터를 가져올 것인지) 데이터를 수집할 지 결정하여야한다.

개인적인 생각으론 수집 단계에선 최대한 많은 데이터를 가져오는 것이 중요하지 않을까 생각한다. 어차피 후의 전처리 및 정제 단계에서 무의미한 데이터들이라면 줄이면 되기 때문이고, 모델링 단계에서 feature 및 factor의 수를 줄이며 연산을 최적화 하고자 하려하기 때문이다.

 

2. 점검 및 탐색

데이터들의 어떤 구조를 띄고 있는지, 데이터의 특징 파악 단계이다.

 

3. 전처리 및 정제

DA로 일을 하며 이 부분이 가장 어렵지 않나 라고 생각된다. 주관적이면서도 객관적이게 데이터를 정제하며 남들을 설득시킬 수 있어야하고 이 부분에서 얼마나 전처리가 잘 되었는 지가 추후 Model의 성능을 결정하기도 하기 때문이다.

또한 Outlier들에 대한 처리를 무조건적으로 제거하는 것이 아닌 각 상황에 따라 알맞게 통제해야 더 좋은 insight를 얻을 수 있다.

 

4. 모델링 및 훈련

모델을 만든 후 Train / Test set을 구분하여 훈련시키거나 hyperparameter에 대한 조정이 필요하면 Validation set 또한 만들어 훈련을 진행하곤 한다. 항상 Overfitting과 Bias에 주의하자.

 

5. 평가

완성된 모델을 평가한다.

 

6. 배포

모델이 성공적이라 판단되면 배포한다.

 

Pandas, Numpy, Matplotlib

pandas

주로 사용하는 데이터 타입은 Dataframe과 Series이다.

numpy

list와 비슷한 구조를 가지지만 더 빠른 연산과 차이점이 조금 있다.

matplotlib

시각화 툴이다. seaborn까지 같이 이용하면 더 아름다운 시각화를 할 수 있다.

 

위 내용들은 이미 잘 알고 있는 부분이라 넘어간다.

 

 

01-05 데이터의 분리(Splitting Data)

출처 : https://wendys.tistory.com/169

학습의 종류에는 크게 세 종류가 있다.

1. 지도 학습(Supervised Learning), 2. 비지도 학습(Unsupervised Learning), 3. 강화 학습(Reinforcement Learning)

 

다만, 이 책에서는 대부분 지도 학습(Supervised Learning)에 대해 배울 예정이다.

지도 학습은 라벨링된 정답이 있는 데이터를 학습 시킨다.

 

그럼 정답이 있는 데이터들을 모두 학습 시키는 것이 아닌 일부는 훈련(train)에 사용, 일부는 테스트(test)에 사용한다.

보통 7:3이나 8:2로 진행하는 것 같다.

 

위 작업을 진행하기 위해서는 데이터를 나눠야하는데 pandas의 Dataframe을 이용할 수도 있고, numpy를 이용할 수도 있다.

다만, scikit-learn을 사용하는 것이 조금 더 편해보인다.

 

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state=1234)

 

다음과 같은 코드로 분리를 할 수 있다.

 

test_size는 해당 데이터 중 test set의 비율을 얼마나 할 것인지 정하는 param이다.

위 0.2는 20%를 test size로 사용하겠다는 의미이다.

 

random_state는 랜덤으로 데이터를 분할할 때의 시드값이다.

이를 지정하지 않으면 위 코드를 돌릴때마다 다르게 분리가 된다. 하지만 특정 값을 사용하면 계속하여 동일하게 분리된 set들을 얻을 수 있다.

+ Recent posts