PyTorch 는 이미지, 텍스트, 오디오에 대한 데이터셋과 DataLoader 를 제공합니다. 

 

(1) torch.utils.data.Dataset: train, test 샘플 데이터와 label 을 저장해 놓은 데이터셋

(2) torch.utils.data.DataLoader: 샘플 데이터에 쉽게 접근 가능하도록 해주는 iterable 을 제공. 

 

 

PyTorch에서 Dataset을 로딩할 때 매개변수는 다음과 같습니다. 

 

- root : train/test 데이터가 저장될 경로
- train : train/test 데이터셋 여부 설정. True 이면 training set, False 이면 test set
- download=True : root 에서 데이터가 사용가능하지 않으면 인터넷에서 데이터를 다운로드함
- transform : Feature 를 변환하는 함수 지정
- target_transform : Label 을 변환하는 함수 지정

 

 

이번 포스팅에서는 torchvision.datasets 에서 FashionMNIST 이미지 데이터셋을 로딩하고 변환 (transformation)하는 예를 들어보겠습니다. 

 

 

1. FashionMNIST 데이터셋 가져와서 변환하기

 

torchvision.datasets.FashionMNIST() 메소드를 사용해서 train, test 데이터셋을 다운로드하여 가져옵니다.

 

이때, 모델 훈련에 적합한 형태가 되도록 Feature와 Label 을 변환할 필요가 생길 수 있는데요, PyTorch에서는 Transforms 를 사용하여 변환을 합니다. Dataset 을 가져올 때 사용자 정의 함수를 정의해서 transfrom, target_transform 매개변수에 넣어주여 Feature와 Lable 을 모델 훈련에 맞게 변환해줍니다. 

 

- Feature 변환: 이미지를 (224, 224) 크기로 조정하고, PyTorch tensor로 변환

- Label 변환: integer list 로 되어있는 것을 one-hot encoding 변환

 

 

transform, target_transform 매개변수 자리에는 Lambda 를 사용해서 사용자 정의 함수를 바로 넣어줘도 됩니다. 

 

## Loading and Transforming Image Dataset
import torch
from torchvision import datasets
import torchvision.transforms as transforms

# Define transformations for the image
image_transforms = transforms.Compose([
    transforms.Resize((224, 224)),  # Resize the image to 224x224 pixels
    transforms.ToTensor(),          # Convert the image to a PyTorch tensor
])

# Define transformations for the target (e.g., converting to a one-hot encoded tensor)
def target_transform(target):
    return torch.eye(10)[target]  # there are 10 classes
    
    
# Load the dataset with transformations
training_data = datasets.FashionMNIST(
    root='data',
    train=True, # training set
    download=True, 
    transform=image_transforms,         # (1) specify how the input data should be preprocessed
    target_transform=target_transform # (2) specifies how the labels should be converted
)

trest_data = datasets.FashionMNIST(
    root='data',
    train=False, # test set
    download=True, 
    transform=image_transforms,         # (1) specify how the input data should be preprocessed
    target_transform=target_transform # (2) specifies how the labels should be converted
)


print(f"Image tensor:\n {training_data[0][0]}")
print("-----------" * 5)
print(f"Label: \n {training_data[0][1]}")

# Image tensor:
#  tensor([[[0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          ...,
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.],
#          [0., 0., 0.,  ..., 0., 0., 0.]]])
# -------------------------------------------------------
# Label: 
#  tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 1.])

 

 

 

FashionMNIST 데이터셋에서 무작위로 이미지를 9개 가져와서 시각화해보면 아래와 같습니다. 

 

## Iterating and Visualizing the Dataset
import matplotlib.pyplot as plt

labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}

figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3

for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[torch.argmax(label).item()])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

 

FashionMNIST

 

 

 

 

2. DataLoader 를 사용해서 Iterate 하기

 

모델 학습을 할 때 보통 mini-batch 만큼의 데이터셋을 가져와서 iteration을 돌면서 학습을 진행합니다. 이때 batch_size 만큼 mini-batch 만큼 데이터셋 가져오기, shuffle=True 를 설정해서 무작위로 데이터 가져오기 설정을 할 수 있습니다.  

 

## Preparing your data for training with DataLoaders
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)


# Display image and label.
train_features, 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.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")

# Feature batch shape: torch.Size([64, 1, 28, 28])
# Labels batch shape: torch.Size([64])
# Label: 9

 

 

[Reference]

(1) PyTorch Datasets & DataLoaders
: https://pytorch.org/tutorials/beginner/basics/data_tutorial.html

 

Datasets & DataLoaders — PyTorch Tutorials 2.2.0+cu121 documentation

Note Click here to download the full example code Learn the Basics || Quickstart || Tensors || Datasets & DataLoaders || Transforms || Build Model || Autograd || Optimization || Save & Load Model Datasets & DataLoaders Code for processing data samples can

pytorch.org

 

(2) PyTorch Transforms
: https://pytorch.org/tutorials/beginner/basics/transforms_tutorial.html

 

Transforms — PyTorch Tutorials 2.2.0+cu121 documentation

Note Click here to download the full example code Learn the Basics || Quickstart || Tensors || Datasets & DataLoaders || Transforms || Build Model || Autograd || Optimization || Save & Load Model Transforms Data does not always come in its final processed

pytorch.org

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 PyTorch 를 사용해서 두 개 Tensor 에 대해 

 

(1) 원소 간 곱 (Element-wise Product)

(2) 행렬 곱 (Matrix Multiplication) 

 

하는 방법을 비교해서 소개하겠습니다. 

 

 

PyTorch: 원소 간 곱 (element-wise product) vs. 행렬 곱 (matrix multiplication)

 

 

먼저, 간단한 예제로 torch.size([2, 2]) 형태를 가지는 x, y 두 개의 Tensor 객체를 만들어보겠습니다. 

그리고 shape, dtype (data type), 저장되어 있는 device 를 확인해보겠습니다. 

 

import torch

x = torch.tensor([[1, 2], [3, 4]])
y = torch.tensor([[0, 10], [20, 30]])

print(f"Tensor x: \n {x}")
print(f"Shape of tensor x: {x.shape}")
print(f"Datatype of tensor x: {x.dtype}")
print(f"Device tensor x is stored: {x.device}")
print("----" * 15)
print(f"Tensor y: \n {y}")
print(f"Shape of tensor y: {y.shape}")
print(f"Datatype of tensor y: {y.dtype}")
print(f"Device tensor y is stored: {y.device}")

# Tensor x: 
#  tensor([[1, 2],
#          [3, 4]])
# Shape of tensor x: torch.Size([2, 2])
# Datatype of tensor x: torch.int64
# Device tensor x is stored: cpu
# ------------------------------------------------------------
# Tensor y: 
#  tensor([[ 0, 10],
#          [20, 30]])
# Shape of tensor y: torch.Size([2, 2])
# Datatype of tensor y: torch.int64
# Device tensor y is stored: cpu

 

 

 

(1) 원소 간 곱 (Element-wise Product)

 

원소 간 곱은 Tensor 내 같은 위치에 있는 원소 간 산술 곱을 해줍니다. 

 

# (1) Element-wise product
# *, mul() is for element-wise multiplication, 
# where each element in the resulting tensor is the product 
# of the corresponding elements in the input tensors.

x * y
# tensor([[  0,  20],
#            [ 60, 120]])


x.mul(y)
# tensor([[  0,  20],
#            [ 60, 120]])

 

 

 

 

(2) 행렬 곱 (Matrix Multiplication)

 

행렬 곱은 선형대수 (Linear Algebra) 의 규칙을 따라서 행렬 곱을 해줍니다. (위의 예제 풀이 그림 참조) 

 

# (2) Matrix Multiplication
# @, matmul() is for matrix or batched matrix multiplication, 
# following the rules of linear algebra. 

x @ y
# tensor([[ 40,  70],
#            [ 80, 150]])


x.matmul(y)
# tensor([[ 40,  70],
#            [ 80, 150]])

 

 

 

행렬 곱을 할 때 두 Tensor 간 shape 에 대해서 조심해야 할 것이 있습니다. 

만약 첫번 째 행렬의 열의 개수와 두번 째 행렬의 행의 개수가 서로 일치하지 않은면 RuntimeError 가 발생합니다. 

 

z = torch.tensor([[5, 6], [7, 8], [9, 10]])
print(f"Shape of tensor z: {z.shape}")
# Shape of tensor z: torch.Size([3, 2])

# RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x2 and 3x2)
# due to a matrix multiplication between two matrices with shapes that are incompatible 
x.matmul(z) # RuntimeError

# ---------------------------------------------------------------------------
# RuntimeError                              Traceback (most recent call last)
# <ipython-input-42-0a2708938308> in <cell line: 2>()
#       1 # RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x2 and 3x2)
# ----> 2 x.matmul(z)

# RuntimeError: mat1 and mat2 shapes cannot be multiplied (2x2 and 3x2)

 

 

 

따라서, 두 Tensor 간 행렬 곱을 할 때는 첫번 째 행렬의 열과 두번 째 행렬의 행의 개수를 일치시켜야 합니다. 

 

행렬 A 가 (m x n), 행렬 B 가 (n x p) 의 형태를 가지고 있다면 행렬 A와 행렬 B 간의 행렬 곱 (matrix multiplication of A and B) 은 (m x p) 형태의 행렬 곱 결과를 반환합니다. 

 

# In matrix multiplication, 
# the number of columns in the first matrix 
# must equal the number of rows in the second matrix. 
# In other words, if the shape of the first matrix is (m x n), 
# the shape of the second matrix must be (n x p) in order to multiply them, 
# resulting in a new matrix of shape (m x p).

A = torch.randn(2, 3)
B = torch.randn(3, 2)
C = torch.matmul(A, B)  # This will result in a matrix of shape 2x2

print(f"Shape of tensor C: {C.shape}")
# Shape of tensor C: torch.Size([2, 2])

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요. :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 심층 신경망을 훈련하는 과정에서 발생하는 문제 중의 하나로 기울기 소실 문제 (Vanishing Gradient Problem) 에 대해서 다루어보겠습니다. 

 

1. 기울기 소실 문제은 무엇인가? 

   (What is the Vanishing Gradient Problem?)

2. 기울기 소실 문제에 기여하는 주요 요인은 무엇인가? 

   (What are the key factors contributing to the vanishing gradient problem?)

3. 기울기 소실 문제를 완화할 수 있는 방법은 무엇인가? 

   (How to mitigate the vanishing gradient problem?)

 

 

 

1. 기울기 소실 문제은 무엇인가? 
(What is the Vanishing Gradient Problem?)

 

기울기 소실 문제 (Vanishing Gradient Problem)은 특히 많은 층을 갖는 심층 신경망을 훈련하는 과정에서 발생하는 어려움입니다. 이는 훈련 과정 중에 네트워크를 통해 그래디언트가 역전파될 때 손실 함수에 대한 가중치와 편향의 그래디언트가 극도로 작아지는 문제를 나타냅니다. 그래디언트가 거의 0에 가까워지면 네트워크의 가중치가 효과적으로 업데이트되지 않을 수 있으며, 네트워크는 데이터에서 학습하는 데 어려움을 겪을 수 있습니다. 


기울기 소실 문제 (Vanishing Gradient Problem)는 특히 순환 신경망 (RNN)이나 심층 피드포워드 신경망(Deep Feedforward Neural Network)과 같은 심층 아키텍처에서 두드러지며, 오류의 역전파는 각 층의 업데이트에서 많은 작은 그래디언트 값을 서로 곱함으로써 기울기 소실로 이어집니다. 이 작은 값들의 곱셈은 기울기 소실을 초래하여 전체적으로 소실된 그래디언트를 초래할 수 있습니다.

 

vanishing gradient problem intuition, 기울기 소실 문제

 

 

 

 

2. 기울기 소실 문제에 기여하는 주요 요인은 무엇인가? 
(What are the key factors contributing to the vanishing gradient problem?)


(1) 활성화 함수 (Activation Functions)


일부 활성화 함수, 특히 시그모이드(Sigmoid) 또는 쌍곡선 탄젠트 (Hyperbolic tangent, tanh)와 같은 함수는 극단적인 입력 값에 대해 포화됩니다. 이러한 영역에서 그래디언트는 거의 0에 가까워져 네트워크가 유용한 그래디언트를 역방향으로 전파하는 것을 어렵게 만듭니다.

 

vanishing gradient problem - sigmoid activation funciton

 

 


(2) 네트워크의 깊이 (Depth of the network)


신경망의 층 수가 증가함에 따라 소실 그래디언트를 만날 확률도 높아집니다. 그래디언트는 여러 층을 통해 역전파되어야 하며, 각 층은 그래디언트가 소실되기에 추가 기회를 제공합니다.

 


(3) 가중치 초기화 (Weight Initialization)


가중치 초기화를 잘못 선택하면 기울기 소실 문제가 악화될 수 있습니다. 가중치가 너무 작게 초기화되면 그래디언트가 층을 통과할 때 소실될 수 있습니다. 

 


(4) 네트워크 아키텍처 (Network Architecture)


순환 신경망(RNN)이나 심층 신경망(Deep Neural Network)과 같이 특정 아키텍처 선택은 소실 그래디언트 문제를 강화할 수 있습니다.

 

 



3. 기울기 소실 문제를 완화할 수 있는 방법은 무엇인가? 
(How to mitigate the vanishing gradient problem?)


(1) 활성화 함수 (Activation Function)


해당 입력 범위에서 포화 문제가 없는 활성화 함수를 사용하십시오. ReLU(Rectified Linear Unit)는 양수 입력 값에 대해 포화되지 않는 일반적으로 사용되는 활성화 함수입니다. 

 

## Use Activation Functions That Do Not Saturate like ReLU, using PyTorch

# define a simple nueral network
class SimpleNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu = nn.ReLU() # Rectified Linear Unit Activation Function
        self.fc2 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)

        return x

# Usage
model = SimpleNN()

 



(2) 배치 정규화 (Batch Normalization)


배치 정규화는 미니 배치의 평균을 빼고 표준 편차로 나누어 신경망 층의 입력을 정규화함으로써 소실 그래디언트 문제를 완화합니다. 다음은 배치 정규화가 소실 그래디언트 문제에 대처하는 데 도움이 되는 방법입니다.

 


정규화 (Normailzation): 배치 정규화는 미니 배치의 평균을 빼고 표준 편차로 나누어 층의 입력을 정규화합니다. 이로써 다음 층의 입력이 대략적으로 0 주변에 집중되고 표준 편차가 약 1이 되도록 보장됩니다.


활성화 함수 안정화(Stablizing Activation Functions): 시그모이드 또는 tanh와 같은 특정 활성화 함수는 입력이 너무 크거나 작을 때 포화됩니다. 배치 정규화는 입력을 정규화하여 입력이 안정된 범위에 유지되도록 돕습니다. 이로써 기울기 소실 문제의 일반적인 원인인 활성화 함수의 포화를 방지합니다.


내부 공변량 변화 감소 (Reducing Internal Covariate Shift): 배치 정규화는 훈련 중에 층의 입력 분포가 변경되는 내부 공변량 변화를 감소시킵니다. 입력을 정규화함으로써 배치 정규화는 입력 분포의 변화를 완화시켜 최적화 과정을 안정화시킵니다. 


더 높은 학습률 허용 (Allowing Higher Learning Rates): 배치 정규화는 더 높은 학습률을 사용할 수 있게 합니다. 높은 학습률은 최적화 알고리즘이 빠르게 수렴하는 데 도움을 줄 수 있습니다. 배치 정규화 없이 높은 학습률을 사용하면 소실 그래디언트 문제로 인해 발산하거나 수렴이 느려질 수 있습니다. 


가중치 초기화에 대한 독립성 (Independence from Weight Initialization): 배치 정규화는 가중치 초기화 선택에 대한 민감성을 줄입니다. 배치 정규화가 없으면 나쁜 가중치 초기화가 소실 그래디언트 문제를 악화시킬 수 있습니다. 배치 정규화는 학습 동적을 초기화로부터 분리하여 훈련을 더 견고하게 만듭니다. 


이러한 문제들을 해결함으로써 배치 정규화는 특히 기울기 소실 문제가 두드러지는 깊은 아키텍처에서 효과적으로 학습하도록 도와줍니다. 

 

## Batch Normalization using PyTorch

## Batch Normalization
import torch
import torch.nn as nn

class SimpleNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(YourModel, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.bn1 = nn.BatchNorm1d(hidden_size) # Batch Normalization
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = self.fc1(x)
        x = self.bn1(x) # Batch Normalization
        x = self.relu(x)
        x= self.fc2(x)

        return x

# Usage
model = SimpleNN()

 

 


(3) 가중치 초기화 (Weight Initialization)


신호 전파를 촉진하는 방식으로 초기 가중치를 설정하는 적절한 가중치 초기화 기술을 사용하십시오. 예를 들면, Xavier/ Glorot 초기화가 있습니다. 

 

## Xavier Initialization using PyTorch

import torch.nn.init as init

class YourModel(nn.Module):
    def __init__(self):
        super(YourModel, self).__init__()
        self.fc1 = nn.Linear(in_features, out_features)
        # Initialize weights using Xavier/Glorot initialization
        init.xavier_uniform_(self.fc1.weight)

# Usage
model = YourModel()

 


(4) 스킵 연결 (Skip Connections)


심층 신경망에서 스킵 연결(Skip Connections) 또는 잔차 연결(Residual Connections)은 정보를 층 간에 직접 흐르게 해 소실 그래디언트를 완화하는 데 도움이 됩니다. ResNet과 같은 아키텍처에서 사용됩니다. 

 


기울기 소실 문제를 해결하는 것은 심층 신경망을 효과적으로 훈련시키기 위해 중요하며, 아키텍처 선택 및 다양한 기술의 조합이 그 영향을 완화하는 데 도움이 될 수 있습니다. 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요.  :-) 

 

728x90
반응형
Posted by Rfriend
,

인공신경망이 인풋 데이터와 정답(label) 데이터를 가지고 학습을 한다고 했을 때, 신경망의 내부에서 무슨 일이 일어나고 있는지, 기계가 데이터를 가지고 학습을 하는 원리가 무엇인지 궁금하실거예요. 이번 포스팅은 바로 이 마법에 대한 소개 글입니다. 

 

"오차역전파(Backpropagation)"은 "오차를 역방향으로 전파하는 방법 (backward propagation of errors)" 의 줄임말로서, 인공 신경망을 훈련시키기 위한 지도 학습 알고리즘으로 자주 사용됩니다. 이는 신경망이 오류에서 학습하고 시간이 지남에 따라 성능을 향상시킬 수 있도록 하는 훈련 과정에서 중요한 구성 요소입니다. 


다음은 오차역전파 알고리즘이 학습하는 단계입니다. 

 

1. 초기화 (Initialization)

2. 순방향 전파 (Forward Pass)

3. 오류 계산 (Calculate Error) 

4. 역방향 전파 (Backward Pass, Backpropagation) 

5. 경사 하강법 (Gradient Descent)

 

 

[ 손실함수와 경사하강법에 의한 최적화를 통한 모델 훈련 ]

model training using loss function and optimization by gradient descent

 

 

 

1. 초기화 (Initialization)

 

신경망에서 초기화(Initialization)는 훈련이 시작되기 전에 네트워크의 가중치(Weights)와 편향(Biases)을 설정하는 과정을 나타냅니다. 올바른 초기화는 신경망의 성공적인 훈련에 중요하며 모델의 수렴 및 성능에 큰 영향을 미칠 수 있습니다.

신경망을 만들 때 그 가중치 및 편향을 일정한 값으로 초기화합니다. 이러한 초기 값의 선택은 네트워크가 얼마나 빨리 학습하며 올바른 해법으로 수렴하는지에 영향을 줄 수 있습니다. 부적절한 초기화는 수렴이 느리게 되거나 그라디언트가 소멸하거나 폭발하는 등의 문제를 초래할 수 있습니다.

가중치 초기화에는 여러 기법이 있으며, 일반적으로 사용되는 몇 가지 방법에는 다음이 포함됩니다.


(1) 랜덤 초기화(Random Initialization): 가중치에 무작위 값 할당하기. 이는 흔한 접근법으로, 가우시안(정규) 분포 또는 균일 분포를 사용하여 무작위 값 생성하는 방법을 사용할 수 있습니다. 


(2) 자비에/글로럿 초기화(Xavier/Glorot Initialization): 이 방법은 시그모이드 및 쌍곡선 탄젠트(tanh) 활성화 함수와 잘 작동하도록 설계되었습니다. 가중치는 평균이 0이고 분산이 (1/ 입력 뉴런 수) 인 가우시안 분포에서 값들을 추출하여 초기화됩니다. 

 

(3) He 초기화(He Initialization): "ReLU 초기화"로도 알려져 있으며, ReLU 활성화 함수를 사용하는 네트워크에 적합합니다. 가중치는 평균이 0이고 분산이 (2/ 입력 뉴런 수) 인 가우시안 분포에서 값들을 추출하여 초기화됩니다. 


적절한 초기화 방법을 선택하는 것은 사용된 활성화 함수에 따라 다르며, 신경망을 효과적으로 훈련시키기 위한 중요한 측면입니다. 적절한 초기화 기술을 사용하면 훈련 중 발생하는 일반적인 문제를 완화하고 더 안정적이고 빠른 수렴에 기여할 수 있습니다. 

 

 

 

2. 순방향 전파 (Forward Pass)

 

신경망에서의 순전파(forward pass)는 입력 데이터를 네트워크를 통과시켜 출력을 생성하는 과정을 나타냅니다. 순전파 중에는 입력 데이터가 신경망의 각 층을 통과하고, 각각의 층에서 변환을 거쳐 최종 출력을 생성합니다. 


순전파의 단계를 설명하면 다음과 같습니다. 


(1) 입력 층(Input Layer): 데이터 입력, 즉 데이터 세트의 특징들이 신경망의 입력 층에 공급됩니다. 


(2) 가중치와 편향(Weights and Biases): 인접한 층 사이의 각 뉴런 간에는 가중치가 할당됩니다. 이러한 가중치는 연결의 강도를 나타냅니다. 또한 각 뉴런은 편향(bias) 항을 갖습니다. 입력 데이터는 가중치로 곱해지고 편향이 결과에 더해집니다. 


(3) 활성화 함수(Activation Function): 입력과 편향의 가중 합은 활성화 함수를 통과합니다. 이는 신경망에 비선형성을 도입하여 데이터의 복잡한 관계를 학습할 수 있도록 합니다. 일반적인 활성화 함수로는 ReLU(렐루), 시그모이드, tanh 등이 있습니다. 


(4) 출력(Output): 활성화 함수의 결과는 해당 층의 출력이 되고, 이는 네트워크의 다음 층에 대한 입력으로 전달됩니다. 


(5) 과정 반복(Repeating the process): 2~4단계는 각각의 이전 층에 대해 반복되어 네트워크의 모든 층을 통과합니다. 최종 출력은 일반적으로 출력 층(Output Layer)에서 생성됩니다. 


순전파는 예측 단계에서 정보가 네트워크를 통과하는 방식입니다. 네트워크는 학습된 가중치와 편향을 사용하여 입력 데이터를 의미 있는 예측으로 변환합니다. 순전파의 출력은 실제 대상 값과 비교되며, 이 오차는 역전파(backpropagation) 단계에서 가중치와 편향을 업데이트하는 데 사용되어 네트워크가 시간이 지남에 따라 학습하고 성능을 향상시킬 수 있도록 합니다. 

 

Feedforward Neural Network

 

 

3. 오류 계산 (Calculate Error) 

 

신경망의 출력은 손실 함수(loss function)를 사용하여 실제 목표 값(실제 값)과 비교됩니다. 이는 예측 값과 실제 출력 간의 차이를 측정합니다. 이 손실 함수를 최소화하는 것이 목표로, 이는 네트워크의 예측이 가능한 한 실제 값에 가까워지도록 합니다. 

 

다양한 유형의 손실 함수가 있으며, 사용할 적절한 손실 함수의 선택은 해결하려는 문제의 성격에 따라 다릅니다. 일반적인 손실 함수에는 다음이 포함됩니다. 


(1) 평균 제곱 오차 (Mean Squared Error, MSE): 회귀 문제(Regression problem)에 적합하며, 예측된 값과 실제 값 간의 평균 제곱 차이를 계산합니다. 


(2) 이진 교차 엔트로피 손실 (Binary Cross-Entropy Loss): 이진 분류 문제(Binary classification problem)에 사용되며, 예측된 확률 분포와 실제 분포 간의 불일치를 측정합니다. 


(3) 다중 클래스 교차 엔트로피 손실 (Categorical Cross-Entropy Loss): 다중 클래스 분류 문제(Multi-class classification problem)에 적합하며, 각 클래스에 대한 예측된 확률 분포와 실제 분포 간의 불일치를 측정합니다. 


(4) 힌지 손실 (Hinge Loss): 서포트 벡터 머신(Support Vector Machine, SVM)에서 흔히 사용되며, 일부 분류 작업에서도 사용됩니다.

 

손실 함수의 선택은 학습 과정에 영향을 미치며, 주어진 작업의 특수한 요구사항과 특성과 일치하는 손실 함수를 선택하는 것이 중요합니다. 최적화 알고리즘은 훈련 중에 계산된 손실을 기반으로 모델 매개변수를 조정하여 시간이 지남에 따라 모델의 성능을 향상시키려고 노력합니다. 

 

 

4. 역방향 전파 (Backward Pass, Backpropagation)

 

역전파(Backward Pass, Backpropagation)는 신경망을 훈련시키는 중요한 단계입니다. 입력 데이터가 네트워크를 통과하여 예측을 생성하는 순전파 이후에는 역전파를 사용하여 계산된 손실에 기초해 모델의 매개변수(가중치와 편향)를 업데이트합니다. 이 과정의 목표는 예측된 출력과 실제 목표 값 사이의 차이를 최소화하는 것입니다.


아래는 역전파 오류 전파 과정의 주요 단계입니다.  


(1) 출력에 대한 손실의 그래디언트 계산 (Compute Gradient of the Loss with Respect to the Output) : 손실 함수의 그래디언트를 모델의 출력에 대해 계산합니다. 이 단계에서는 출력이 조정될 경우 손실이 얼마나 변하는지를 결정합니다. 

 

(2) 네트워크를 통한 그래디언트 역전파 (Compute Gradient of the Loss with Respect to the Output) : 그래디언트를 네트워크의 층을 통해 역방향으로 전파합니다. 이는 미적분의 연쇄 법칙을 적용하여 각 층의 가중치와 편향에 대한 손실의 그래디언트를 계산하는 것을 포함합니다. 

 

(3) 가중치와 편향 업데이트 (Update Weights and Biases) : 계산된 그래디언트를 사용하여 네트워크의 가중치와 편향을 업데이트합니다. 최적화 알고리즘인 확률적 경사 하강법(SGD) 또는 그 변형 중 하나는 손실을 줄이는 방향으로 매개변수를 조정합니다. 

 

(4) 다중 반복에 대해 반복 (Repeat for Multiple Iterations (Epochs)) : 단계 1-3은 여러 번의 반복 또는 에폭에 대해 반복되며 각 반복에서는 훈련 샘플의 배치가 처리됩니다. 이 반복적인 프로세스를 통해 네트워크는 매개변수를 조정하여 시간이 지남에 따라 성능을 향상시킬 수 있습니다. 

 

 

역전파의 수학적 세부 사항은 편미분(partial derivatives)과 연쇄 법칙(the chain rule)을 포함합니다. 구체적으로 그래디언트는 오류를 층을 통해 역방향으로 전파함으로써 계산되며, 가중치와 편향은 그래디언트의 반대 방향으로 업데이트됩니다.

 

 

[ The Chain Rule]

The Chain Rule

 

 

[ example: Partial Derivative of w1 with respect to E1 ]

Partial Derivative of w1 with respect to Error

 


역전파는 신경망을 훈련시키는 데 기본 개념으로, 네트워크가 실수에서 학습하고 매개변수를 계속해서 조정할 수 있게 합니다. 이 반복적인 과정을 통해 모델은 새로운, 보지 않은 데이터에 대한 예측을 더 잘 수행하게 됩니다.

 

 

5. 경사 하강법 (Gradient Descent)

 

계산된 도함수는 손실을 줄이는 방향으로 가중치와 편향을 업데이트하는 데 사용됩니다.
이를 위해 일반적으로 경사 하강 최적화 알고리즘(Optimization by Gradient-Descent)이 사용되며, 학습률에 의해 스케일링된 기울기의 일부를 빼서 가중치와 편향을 조절합니다. 

경사하강법 ( Gradient Descent )



- Eta (η)는 학습율(the learning rate)로서, 학습율은 기계 학습 및 신경망 훈련에서 사용되는 하이퍼파라미터 중 하나로, 각 훈련 단계에서 모델의 가중치를 조정하는 정도를 나타냅니다. 즉, 학습율은 가중치 업데이트의 크기(step size)를 결정하는 매개변수입니다.


높은 학습율은 각 단계에서 더 큰 가중치 업데이트를 의미하며, 이는 모델이 빠르게 학습할 수 있게 할 수 있습니다. 그러나 너무 높은 학습율은 수렴을 방해하고 발산(explosion)할 수 있기 때문에 조심스럽게 선택되어야 합니다.


낮은 학습율은 더 안정적인 학습을 제공하지만, 수렴하는 데 더 많은 시간이 걸릴 수 있습니다. 또한, 지역 최솟값(local optima)에서 빠져나오기 어려울 수 있습니다.


적절한 학습율을 선택하는 것은 모델의 성능과 수렴 속도에 큰 영향을 미칩니다. 학습율은 하이퍼파라미터 튜닝 과정에서 조정되며, 실험을 통해 최적의 값을 찾는 것이 일반적입니다. 일반적으로는 0.1, 0.01, 0.001과 같은 값을 시도하며 Cross-validation을 통해 모델의 성능을 평가하여 최적의 학습율을 결정합니다.

 

 

다음은 오차역전파법을 설명하기 위해 가상으로 만든, 각 레이어 별로 input size = 3, hidden size 1 = 3, hidden size 2 = 3, output size = 1 의 모델 아키텍처를 가지는 아주 간단한 PyTorch 예시가 되겠습니다.  MSE 를 손실함수로 하고, Adam optimizer를 사용하였습니다. 

 

import torch
import torch.nn as nn
import torch.nn.functional as F

# Define the neural network architecture with hyperparameters
class SimpleNet(nn.Module):
    def __init__(self, input_size, hidden_size_1, hidden_size_2, output_size):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size_1)
        self.bn1 = nn.BatchNorm1d(hidden_size_1)
        self.fc2 = nn.Linear(hidden_size_1, hidden_size_2)
        self.bn2 = nn.BatchNorm1d(hidden_size_2)
        self.fc3 = nn.Linear(hidden_size_2, output_size)

    def forward(self, x):
        x = F.relu(self.bn1(self.fc1(x)))
        x = F.relu(self.bn2(self.fc2(x)))
        x = self.fc3(x)

        return x

# Set Hyperparameters
input_size = 3
hidden_size_1 = 6
hidden_size_2 = 3
output_size = 1
epochs = 100
learning_rate = 0.001

# Initiate the network
model = SimpleNet(input_size, hidden_size_1, hidden_size_2, output_size)

print(model)
# SimpleNet(
#   (fc1): Linear(in_features=3, out_features=6, bias=True)
#   (bn1): BatchNorm1d(6, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
#   (fc2): Linear(in_features=6, out_features=3, bias=True)
#   (bn2): BatchNorm1d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
#   (fc3): Linear(in_features=3, out_features=1, bias=True)
# )

 

 

GPU 를 사용할 수 있으면 Device를 "cuda"로, CPU를 사용할 수 있으면 Device를 "cpu"로 설정해줍니다. 아래 예에서는 Tesla V100 16G GPU를 사용하므로 "cuda"로 Device를 설정해주었습니다. 

 

# Check for CUDA availability
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

print(device)
# cuda


#### when you run at Jupyter Notebook, add '!' at the beginning.
! nvidia-smi -L
# GPU 0: Tesla V100-SXM2-16GB (UUID: GPU-d445249d-b63e-5e62-abd1-7646bb409870)

 

 

 

Loss 함수는 MSE (Mean Squared Error) 로 설정하였고, Optimizer는 Adam 으로 설정해주었습니다. 

모델 훈련에 사용할 데이터셋을 난수를 발생시켜서 생성하고, 위에서 지정한 Device 에 모델의 파라미터와 버퍼, 그리고 데이터를 이동시켰습니다 (modeo.to(device), torch.randn((100, input_size)).to(device)). 

 

# Move the model's parameters and buffers to the specified device.
model.to(device)

# Set Loss and Optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)


# Generate dummy input and target data, move the data to the specified device
inputs = torch.randn((100, input_size)).to(device)
targets = torch.randn((100, output_size)).to(device)

 

 

 

Epochs = 100 만큼 for loop 순환문을 사용해서 반복하면서 모델을 훈련 시킵니다. Forward pass, Calculate loss, Backward pass, Update weights (Optimize) 의 과정을 반복합니다. 

 

# Training phase
model.train() # training mode
for epoch in range(epochs):
    # Forward pass
    output = model(inputs)
    loss = criterion(output, targets) # calculate loss

    # Backward pass and optimize
    optimizer.zero_grad() # clear previous gradients
    loss.backward() # compute gradients, backward pass
    optimizer.step() # update weights

    # Print training statistics
    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')

# Epoch [10/100], Loss: 0.9778
# Epoch [20/100], Loss: 0.9476
# Epoch [30/100], Loss: 0.9217
# Epoch [40/100], Loss: 0.9000
# Epoch [50/100], Loss: 0.8826
# Epoch [60/100], Loss: 0.8674
# Epoch [70/100], Loss: 0.8534
# Epoch [80/100], Loss: 0.8423
# Epoch [90/100], Loss: 0.8329
# Epoch [100/100], Loss: 0.8229

 

 

위에서 훈련한 모델에 가상으로 만든 1개의 샘플 데이터를 인풋으로 넣어서 예측을 해보겠습니다. 배치가 아니가 단 1개의 샘플 데이터만 사용하기 때문에 model.eval() 을 해주었습니다. (만약 model.eval() 을 추가해주지 않으면, ValueError: Expected more than 1 value per channel when training, got input size torch.Size([1, 2]) 가 발생합니다.)

 

# Example of using the trained model
test_input = torch.randn((1, input_size)).to(device)

model.eval() # evaluation mode
with torch.no_grad():
    predicted_output = model(test_input)

print("Test Input:", test_input)
print("Predicted Output:", predicted_output.item())

# Test Input: tensor([[ 0.3862,  0.1250, -0.3322]], device='cuda:0')
# Predicted Output: -0.00717635452747345

 

 

위에서 학습이 완료된 인공신경망 모델의 레이어 이름과 파라미터별 가중치, 편향에 접근해서 확인해보도록 하겠습니다. 

 

## (1) Access to the names and parameters in the trained model
for name, param in model.named_parameters():
    print(name)
    print(param)
    print("-----" * 15)

# fc1.weight
# Parameter containing:
# tensor([[-0.0542, -0.5121,  0.3793],
#         [-0.5354, -0.1862,  0.4207],
#         [ 0.3339, -0.0813, -0.2598],
#         [ 0.5527, -0.3505, -0.4357],
#         [-0.0351, -0.0992,  0.1252],
#         [-0.2110,  0.0713, -0.2545]], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# fc1.bias
# Parameter containing:
# tensor([ 0.5726,  0.1125,  0.4138, -0.1896,  0.1353, -0.0372], device='cuda:0',
#        requires_grad=True)
# ---------------------------------------------------------------------------
# bn1.weight
# Parameter containing:
# tensor([0.9044, 0.9735, 1.0557, 0.9440, 1.0983, 1.0414], device='cuda:0',
#        requires_grad=True)
# ---------------------------------------------------------------------------
# bn1.bias
# Parameter containing:
# tensor([-0.1062,  0.0177,  0.0283, -0.0527,  0.0059, -0.0117], device='cuda:0',
#        requires_grad=True)
# ---------------------------------------------------------------------------
# fc2.weight
# Parameter containing:
# tensor([[-0.3057,  0.0730,  0.2711,  0.0770,  0.0497,  0.4484],
#         [-0.3078,  0.0153,  0.1769,  0.2855, -0.4853,  0.2110],
#         [-0.3342, -0.2749,  0.3440, -0.2844,  0.0763, -0.1959]],
#        device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# fc2.bias
# Parameter containing:
# tensor([-0.0536, -0.3275,  0.0371], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# bn2.weight
# Parameter containing:
# tensor([1.0843, 0.9524, 0.9426], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# bn2.bias
# Parameter containing:
# tensor([-0.0564,  0.0576, -0.0848], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# fc3.weight
# Parameter containing:
# tensor([[-0.4607,  0.4394, -0.1971]], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------
# fc3.bias
# Parameter containing:
# tensor([-0.0125], device='cuda:0', requires_grad=True)
# ---------------------------------------------------------------------------

 

 

 

## (2) Access to the weights and biases in the trained model
print("[ Model FC1 Weights ]")
print(model.fc1.weight.cpu().detach().numpy())
print("-----" * 10)
print("[ Model FC2 Weights ]")
print(model.fc2.weight.cpu().detach().numpy())

# [ Model FC1 Weights ]
# [[-0.05423066 -0.51214963  0.37927148]
#  [-0.53538287 -0.18618222  0.42071185]
#  [ 0.33391565 -0.08127454 -0.2597795 ]
#  [ 0.55268896 -0.3505293  -0.4356556 ]
#  [-0.03507916 -0.09922788  0.1251672 ]
#  [-0.21096292  0.07126608 -0.25446963]]
# --------------------------------------------------
# [ Model FC2 Weights ]
# [[-0.30573702  0.07304495  0.27107015  0.07700069  0.04968859  0.44840544]
#  [-0.3078182   0.01533379  0.17685111  0.28549835 -0.485255    0.21100621]
#  [-0.33416617 -0.27492833  0.3440289  -0.28436708  0.07630474 -0.19592032]]

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요.  :-)

 

728x90
반응형
Posted by Rfriend
,

토큰화, 단어 임베딩, 텍스트 임베딩은 자연어 처리(NLP) 및 기계 학습에서 텍스트 데이터를 표현하고 처리하는 데 사용되는 개념입니다. 이러한 개념들은 감정 분석, 기계 번역, 텍스트 분류와 같은 NLP 작업에서 중요하며, 기계 학습 모델이 이해하고 학습할 수 있는 형태로 텍스트 정보를 표현하고 처리하는 데 사용됩니다.

 

이번 포스팅에서는 토근화(Tokenization), 단어 임베딩 (Word Embedding), 텍스트 임베딩 (Text Embedding)에 대해서 소개하겠습니다. 그리고 transformers 와 PyTorch 를 이용하여 실습을 해보겠습니다. 

 

tokenization, word embedding, text embedding

 


1. 토큰화 (Tokenization)

 

- 정의: 토큰화는 텍스트를 토큰이라 불리는 더 작은 단위로 분해하는 과정입니다. 이러한 토큰은 단어, 서브워드 또는 문자일 수 있으며 원하는 정밀도 수준에 따라 다릅니다.

- 목적: 토큰화는 NLP에서 중요한 전처리 단계로, 기계가 텍스트의 개별 요소를 이해하고 처리할 수 있게 합니다. 토큰은 후속 분석을 위한 구성 요소로 작용합니다.

 

-- Install transformers, torch, sentence-transformers at terminal at first 

pip install -q transformers
pip install -q torch
pip install -q -U sentence-transformers

 

# 1. Tokenization 
# : Tokenization is the process of breaking down a text into smaller units called tokens. 
# : These tokens can be words, subwords, or characters, depending on the level of granularity desired.

from transformers import AutoTokenizer

# Load pre-trained DistilBERT tokenizer
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')

# Example text
text = "Love is wanting to be loved"

# Tokenize the text
input_ids = tokenizer.encode(text)
tokens = tokenizer.tokenize(tokenizer.decode(input_ids))

print("Input IDs:", input_ids)
print("-------" * 10)
print("Tokens:", tokens)

# Input IDs: [101, 2293, 2003, 5782, 2000, 2022, 3866, 102]
# ----------------------------------------------------------------------
# Tokens: ['[CLS]', 'love', 'is', 'wanting', 'to', 'be', 'loved', '[SEP]']

 

 


2. 단어 임베딩 (Word Embedding)

 

- 정의: 단어 임베딩은 단어를 연속적인 벡터 공간의 실수로 이루어진 밀도 있는 벡터로 나타내는 기술입니다. 각 단어는 벡터로 매핑되며 이러한 벡터 간의 거리와 방향이 의미를 가집니다.

- 목적: 단어 임베딩은 단어 간의 의미적 관계를 포착하여 기계가 주어진 텍스트의 맥락과 의미를 이해할 수 있게 합니다. 단어가 맥락과 의미가 유사한 경우 단어 임베딩 벡터가 서로 가깝게 위치하는 특성이 있어 semantic search에 유용하게 활용됩니다. 유명한 단어 임베딩 모델로는 Word2Vec, GloVe, FastText가 있습니다. 

 

# 2. Word Embedding
# : Word embedding is a technique that represents words 
#   as dense vectors of real numbers in a continuous vector space. 
# : Each word is mapped to a vector, and the distances and directions 
#   between these vectors carry semantic meaning.

from transformers import AutoTokenizer, AutoModel
import torch

# Load pre-trained DistilBERT model
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
model = AutoModel.from_pretrained('distilbert-base-uncased')

# Encode the tokens to obtain word embeddings
input_ids = tokenizer.encode(text, return_tensors='pt')
with torch.no_grad():
    word_embeddings = model(input_ids)[0]

print("Tensor Size:", word_embeddings.size())
print("------" * 10)
print("Word Embeddings:")
print(word_embeddings)
# Tensor Size: torch.Size([1, 8, 768])
# ------------------------------------------------------------
# Word Embeddings: 
#   tensor([[
#          [-0.1067, -0.0101, -0.0897,  ..., -0.1721,  0.2134,  0.3274],
#          [ 0.8698,  0.1002,  0.2687,  ..., -0.3276,  0.4205,  0.1344],
#          [-0.1877, -0.0777,  0.1905,  ..., -0.0220,  0.0024,  0.5138],
#          ...,
#          [ 0.5998, -0.2141,  0.3774,  ..., -0.3945, -0.1149,  0.1245],
#          [ 1.1550, -0.1050,  0.3570,  ..., -0.4063, -0.0489, -0.0717],
#          [ 1.0036,  0.1886, -0.4508,  ...,  0.0999, -0.5486, -0.3076]]])

 


3. 텍스트 임베딩 (Text Embedding)

 

- 정의: 텍스트 임베딩은 문장(sentences)이나 문서(documents) 전체를 밀도 있는 벡터로 나타내어 전체 텍스트의 전반적인 의미를 캡처하는 기술입니다. 단어 임베딩을 집계하거나 요약하여 전체 텍스트의 표현을 만듭니다. 

- 목적: 텍스트 임베딩은 전체 문서나 문장을 의미적 공간에서 비교하고 분석할 수 있게 합니다. 텍스트 임베딩을 생성하는 방법에는 단어 임베딩의 평균(averaging word embeddings)을 사용하거나 순환 신경망(RNN), 장·단기 기억 네트워크(LSTM), 트랜스포머(Transformers)를 사용하는 방법 등이 있습니다. 

 

텍스트 임베딩은 

 - 텍스트 분류 (Text Classification),

 - 감성분석 (Sentiment Analysis),

 - 검색 엔진 랭킹 (Search Engine Ranking),

 - 군집 및 유사도 분석 (Clustering and Similarity Analysis),

 - 질의 응답 (Question Answering, 질문과 답변을 임베딩으로 표현함으로써 시스템은 쿼리의 의미 콘텐츠와 데이터셋의 관련 정보를 정확하게 검색할 수 있음)

 

등에 활용할 수 있습니다. 

text embedding 을 하는 방법에는 (1) transformers 를 이용한 방법과, (2) sentence-transfomers 모델을 이용하는 방법이 있습니다. sentence-transformers 모델을 이용하는 방법이 코드가 간결합니다. 

 

 

3-1. transformers 를 이용한 text embedding

# 3. Text Embedding
# : Text embedding represents entire sentences or documents as dense vectors, 
#   capturing the overall meaning of the text. 
# : It involves aggregating or summarizing word embeddings 
#   to create a representation for the entire text.

from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F

#Mean Pooling - Take attention mask into account for correct averaging
def mean_pooling(model_output, attention_mask):
    token_embeddings = model_output[0] #First element of model_output contains all token embeddings
    input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
    return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(input_mask_expanded.sum(1), min=1e-9)


# Sentences we want sentence embeddings for
# Example sentence
text = "Love is wanting to be loved"

# Load model from HuggingFace Hub
tokenizer = AutoTokenizer.from_pretrained('distilbert-base-uncased')
model = AutoModel.from_pretrained('distilbert-base-uncased')

# Tokenize sentences
encoded_input = tokenizer(text, padding=True, truncation=True, return_tensors='pt')

# Compute token embeddings
with torch.no_grad():
    model_output = model(**encoded_input)

# Perform pooling
sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])

# Normalize embeddings
sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1)

print("Tensor Size", sentence_embeddings.size())
print("--------" * 10)
print("Sentence embeddings:")
print(sentence_embeddings)

# Tensor Size torch.Size([1, 768])
# --------------------------------------------------------------------------------
# Sentence embeddings:
# tensor([[ 7.1203e-02, -9.0556e-03,  2.0404e-02, -5.4984e-03,  5.3534e-02,
#          -2.7214e-02,  2.7177e-02,  5.1983e-02, -1.1366e-02, -4.1719e-02,
#              :             :           :           :             :
#           2.5733e-02, -2.1918e-02,  5.2480e-03, -5.7470e-04,  1.4644e-02,
#          -1.8896e-02, -4.5600e-03,  1.4625e-02]])

 

 

 

3-2. sentence-transformers 모델을 이용한 text embedding

: 'all-mpnet-base-v2' 모델을 HuggingFace에서 다운로드 하여 사용

 

sentence-transformers 모델에 대한 설명은 https://huggingface.co/sentence-transformers 를 참고하세요. 

 

# ! pip install -q -U sentence-transformers


# Text embedding by sentence-transformers model
# reference: https://huggingface.co/sentence-transformers/all-mpnet-base-v2
from sentence_transformers import SentenceTransformer

text = "Love is wanting to be loved"

model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
text_embedding = model.encode(text)

print("Tensor Shape:", text_embedding.shape)
print("------" * 10)
print("Text Embedding:")
print(text_embedding)

# Tensor Shape: (768,)
# ------------------------------------------------------------
# Text Embedding:
# [ 7.25340098e-02 -3.43943425e-02  3.50002125e-02 -1.46801360e-02
#   1.79491900e-02  5.70273288e-02 -9.77409929e-02  2.65225749e-02
#      :               :               :               :
#   -6.68407883e-03  1.09006204e-02 -1.37606068e-02 -4.93713543e-02
#   3.27182375e-02  5.77081088e-03  4.32791896e-02 -1.55460704e-02]

 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 OpenAI 의 ChatGPT 의 Rest API 를 사용하기 위한 준비 과정으로서 API Key를 생성하는 방법을 소개하겠습니다. 별로 어렵지 않으므로 아래의 내용을 참고해서 순차적으로 따라해 보시기 바랍니다. 

 

 

1. OpenAI 회원가입

 

https://platform.openai.com/overview 에 접속하면 아래와 같이 OpenAI platform 에서 제공하는 애플리케이션 개발 관련 API, Plugin 서비스를 한눈에 살펴볼 수 있습니다. 

 

아직 OpenAI 회원가입을 하지 않았다면, 아래 사이트의 우측 상단에 있는 'Sign up' 메뉴를 선택해서 회원가입을 먼저 하면 됩니다. 

 

OpenAI Platform Overview

 

 

계정을 생성하는 회원 가입 페이지는 아래와 같습니다. Email 주소를 입력하거나, 아니면 Google, Microsoft, Apple 계정을 연동해서 쉽고 빠르게 회원가입을 진행할 수도 있습니다. 

OpenAI Create your account

 

 

 

2. 로그인 후 'View API Keys' 메뉴로 가기

 

우측 상단의 Personal > View API keys 클릭 후 > 본문의 + Create enw secret key 메뉴 클릭

(만약 기존에 발급받았던 API keys 가 존재할 경우 API keys 페이지에 API Keys 리스트만 나오며, API Key 자체를 다 보여주지는 않습니다.). 

 

OpenAI View API keys

 

 

 

3. '+ Create new secret key' 메뉴 선택해서 API key 생성

 

API keys 페이지의 본문 중간에 있는 '+ Create new secret key' 메뉴를 클릭하면 아래에 보이는 바와 같이 'Create new secret key' 팝업 창이 뜹니다. 팝업 창의 가운데 빈칸에 key의 이름을 입력해주고 우측 하단에 'Create secret key' 단추를 클릭하면 API key 가 생성됩니다. 

 

OpenAI API key creation

 

 

 

4. 새로 생성된 OpenAI API Key 복사 후 별도 저장

 

새로 생성된 API key 는 보안 상의 이유로 해서 최초로 생성되는 시점에 딱 한번만 볼 수 있으며, 이후에는 다시 볼 수 없습니다. 따라서 아래 복사를 해서 안전한 곳에 보관을 해서 사용해야 합니다. 만약 API key 를 잃어버렸다면 새로 발급받는 방법밖에 없으므로 보관에 유의하시기 바랍니다. 

 

OpenAI API key copy

 

 

 

5. 생성된 OpenAI API keys 목록 조회

 

위에서 생성한 API key 에 대해서 아래의 화면처럼 "이름(name)-키(key)-생성일(created)-최종사용일(last used)" 의 내용으로 확인할 수 있습니다.   (이때 API key는 보안 이유로 볼 수 없습니다.) 

 

API key 는 다른 사람과 공유할 수 없으며, 브라우저나 클라이언트 쪽의 코드에 노출하면 안됩니다. 각 사용자의 보안을 보호하기 위해서 OpenAI 회사가 공개적으로 노출된 사용자의 API Key를 찾는다면 자동으로 API key를 변경할 수 있다고 하네요. 보안뿐만 아니라 비용 과금과도 관련되었으므로 API key는 노출되지 않게 잘 관리해야 겠지요. 

 

이제 API key 생성이 끝났으므로, 애플리케이션 개발에 API key를 사용하면 됩니다. 

 

OpenAI API keys: generated lists

 

 

 

6. OpenAI API 사용량 확인 

 

좌측의 'Usage' 메뉴를 선택하면 개인이나 조직의 일별 API 사용량 (Daily usage) 을 확인할 수 있습니다. 

계정별로 $18 의 무료 시범 사용 (Free trial usage) 이 가능합니다. 공부하는 개인이라면 $18 로 API 테스트를 해보기에 부족함이 없을 것 같아요. 

 

OpenAI API usage

 

 

 

7. Rate limits 

 

API 를 사용함에 있어 requests-per-minute, tokens-per-minute, images-per-minite 별 제한이 있으므로 애플리케이션 개발에 참고하시기 바랍니다. (좌측의 'Rate limits' 메뉴에서 조회) 

 

 

 

다음번 포스팅에서는 API key를 사용해서 Python으로 ChatGPT 를 실제로 사용해보는 예제를 소개하겠습니다. 

 

* 참고: OpenAI의 API와 Greenplum, PostgreSQL의 pgvector 를 연동해서 Semantic search, Question & Answering 에 활용하는 방법은 https://rfriend.tistory.com/804 를 참고하세요. 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅에서는 OpenAI API Key를 발급하는 방법(https://rfriend.tistory.com/794) 을 소개하였습니다. 

 

이번 포스팅에서는 애플리케이션을 개발할 때 REST API 로 OpenAI의 서비스를 사용할 수 있는 

 

(1) OpenAI API 를 이용해서 GPT-3 Text Embeddings 를 하는 방법

(2) OpenAI API 를 이용해서 ChatGPT 채팅을 하는 방법

 

을 소개하겠습니다. 

 

 

Application using OpenAI's REST API

 

 

 

먼저, openai 모듈이 없으면 터미널에서 openai 파이썬 모듈을 설치해야 합니다 

 

-- terminal 에서 모듈 설치

pip install openai
pip install --force-reinstall charset-normalizer==3.1.0

 

그리고, Billing method 에서 API를 사용한 만큼의 비용을 결제할 때 사용하는 신용카드를 등록해야 합니다.  

 

 

 

(1) OpenAI API 를 이용해서 GPT-3 Text Embeddings 를 하는 방법

 

텍스트 임베팅 (Text Embeddings)은 텍스트 유사성(text similarity), 텍스트 군집화(clustering), 토픽 모델링(topic modeling), 텍스트 검색(text search), 코드 검색(code search) 등에 사용됩니다. 

 

openai.Embedding.create() 함수에 텍스트 문장을 input 으로 넣어주면, 1,536 차원의 리스트(list) 데이터 유형의 embeddings 를 output 으로 반환합니다. 

 

OpenAI API Key는 외부로 노출되지 않도록 보안에 유의하시기 바랍니다. (사용한 만큼 과금이 됩니다!)

 

## OpenAI API key
## how to get key: https://rfriend.tistory.com/794
openai_key = "Your_OpenAI_API_Key_here"


## Getting Embeddings
def get_embedding(content, openai_key):
    import openai
    
    openai.api_key = openai_key
    
    text = content 
    
    response = openai.Embedding.create(
        model="text-embedding-ada-002", 
        input = text.replace("\n", " ")
        )
    
    embedding = response['data'][0]['embedding']
    
    return embedding


## run the UDF above
content = """What is love?"""

text_embs = get_embedding(content, openai_key)


## sentence embeddings in detail
type(text_embs)
# list

len(text_embs)
# 1536

print(text_embs)
# [0.010716659016907215, -0.019867753610014915, 
#. -0.010219654999673367, -0.008119810372591019, 
#  ...... 
#  0.014152202755212784, -0.022837355732917786, 
#. -0.0026341236662119627, -0.03941245377063751]

 

 

 

(2) OpenAI API 를 이용해서 ChatGPT 채팅을 하는 방법

 

messages 에 {"role": "system", "content": "You are an intelligent assistant."} 를 설정해주고, input("User : ") 를 통해 사용자의 질문을 input으로 입력받아 {"role": "user", "content": message} 를 함께 묶어서 (append) OpenAI API 를 통해 ChatGPT 모델에 전달합니다.

 

그러면 OpenAI의 서버에서 openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages) 함수가 GPU를 사용해서 열심히 Text Generation Inference 를 해서 답변(reply)를 API를 통해 사용자에게 반환합니다. 

 

제일 마지막에 있는 messages.append({"role": "assistant", "content": reply}) 는 ChatGPT 가 답변한 내용(reply)를 messages 에 합쳐 놓아서 이후의 이어지는 질문에 이전 답변을 참고하는데 사용됩니다. (ChatGPT는 이전 상태에 대한 기억이 없기 때문에 이렇게 해주는 것임). 

 

#%% AI-assisted Chatting
import openai 

opneai_key = "Your_OpenAI_API_Key_here"
openai.api_key = opneai_key


messages = [ {"role": "system", 
              "content": "You are an intelligent assistant."} ]

message = input("User : ")
if message:
    messages.append(
        {"role": "user", 
         "content": message},
    )
    chat = openai.ChatCompletion.create(
        model="gpt-3.5-turbo", 
        messages=messages
    )

    
reply = chat.choices[0].message.content

print(f"ChatGPT: {reply}")

messages.append({"role": "assistant", "content": reply})


## 챗팅 결과
# User :  What is love?

# ChatGPT: Love is a complex emotion and can mean different things to different people. 
# Generally, love refers to a deep affection, care, and attachment felt towards someone or something. 
# It involves feelings of intense happiness, contentment, and a sense of connection. 
# Love can exist in various forms, such as romantic love, familial love, platonic love, 
# or even love for hobbies and interests. It is often characterized by selflessness, 
# understanding, support, and the desire to nurture and protect the well-being of the loved one. 
# Love can bring joy, fulfillment, and a sense of purpose to our lives, but it can also be challenging 
# and require effort, compromise, and understanding. Ultimately, 
# love is a fundamental aspect of human experience that enriches relationships 
# and contributes to personal growth and happiness.

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 PyTorch 의 Torchvision.datasets 모듈에 내장되어 있는 데이터 중에서 하나를 가져와서 시각화해보는 작업을 소개하겠습니다. 

 

(1) Torchvision.DataSets 모듈 소개

(2) torchvision.datasets 모듈에서 CIFAR10 데이터셋 다운로드하고 압축풀기

(3) torchvision.datasets 모듈에서 가져온 CIFAR10 데이터셋 살펴보기

(4) CIFAR10 이미지 시각화하기

 

 

 

(1) Torchvision.DataSets 모듈 소개

 

PyTorch 패키지의 Torchvision 은 torchvision.datasets 모듈에 다양한 종류의 내장된 데이터셋(built-in datasets)과 편리한 유틸리티 클래스(utility classes)를 제공합니다. 

 

Torchvision 의 DataSets 모듈에 내장되어 있는 데이터셋의 과업 목적 종류별로 몇가지 예를 들어보면요,  

 

- Image Classification: CIFAR10, CIFAR100, MNIST, FashionMNIST, Caltech101, ImageNet 등

- Image Detection or Segmentation: CocoDetection, celebA, Cityscapes, VOCSegmentation 등

- Optical Flow: FlyingChairs, FlyingThings3D, HD1K, KittiFlow, Sintel 등

- Stereo Matching: CarlaStereo, Kitti2015Stereo, CREStereo, SintelStereo 등

- Image Pairs: LFWPairs, PhotoTour

- Image Captioning: CocoCaptions

- Video Classification: HMDB51, Kinetics, UCF101

- Base Classes for Custom Datasets: DatasetFolder, ImageFolder, VisionDataset

 

등이 있습니다. 

 

모든 데이터셋이 PyTorch의 torch.utils.data.Dataset 의 하위클래스이므로 __getitem__ 과 __len__ 메소드를 가지고 있으며, torch.utils.data.DataLoader 메소드를 적용해서 batch size 나 shuffle 여부 등의 매개변수를 적용해서 데이터 로딩을 편리하게 할 수 있습니다. 

 

[참고 사이트] https://pytorch.org/vision/stable/datasets.html

 

 

 

(2) torchvision.datasets 모듈에서 CIFAR10 데이터셋 다운로드하고 압축풀기

 

아래와 같이 CIFAR10 을 다운로드할 경로를 설정해주고, datasets.CIFAR10(directory, download=True, train=True) 처럼 학습에 사용한다는 옵션(train=True) 부여해서 다운로드하면 됩니다. 데이터셋 크기가 꽤 큰 편이어서 시간이 좀 걸려요. 

 

import torch
from torchvision import datasets
import numpy as np

print(torch.__version__)
# 1.10.0


## downloading CIFAR10 data from torchvision.datasets
## reference: https://pytorch.org/vision/stable/datasets.html
img_dir = '~/Downloads/CIFAR10'  # set with yours
cifar10 = datasets.CIFAR10(
    img_dir, download=True, train=True)

# Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to /Users/lhongdon/Downloads/CIFAR10/cifar-10-python.tar.gz
# 170499072/? [09:27<00:00, 300518.28it/s]

# Extracting /Users/lhongdon/Downloads/CIFAR10/cifar-10-python.tar.gz to /Users/lhongdon/Downloads/CIFAR10

 

[참고 사이트] CIFAR10 데이터셋에 대한 자세한 소개는 http://www.cs.toronto.edu/~kriz/cifar.html 를 참고하세요. 

 

 

 

(3) torchvision.datasets 모듈에서 가져온 CIFAR10 데이터셋 살펴보기

 

CIFAR10 데이터셋은 이미지 데이터(data)와 분류 범주 (targets) 의 dictionary 로 되어있습니다. 

 

## getting image data and targets
cifar10_data = cifar10.data
cifar10_targets = cifar10.targets


## information on FMNIST dataset
print('CIFAR10 data shape:', cifar10_data.shape) # array
print('CIFAR10 targets shape:', len(cifar10_targets)) # list

# CIFAR10 data shape: (50000, 32, 32, 3)
# CIFAR10 targets shape: 50000

 

 

이미지 데이터는 32 (폭) x 32 (높이) x 3 (RGB 채널) 크기의 데이터가 50,000 개 들어있는 다차원 배열 (ND array) 입니다. 

 

## ND-array
cifar10_data[0]

# array([[[ 59,  62,  63],
#         [ 43,  46,  45],
#         [ 50,  48,  43],
#         ...,
#         [158, 132, 108],
#         [152, 125, 102],
#         [148, 124, 103]],

#        [[ 16,  20,  20],
#         [  0,   0,   0],
#         [ 18,   8,   0],
#         ...,
#         [118,  84,  50],
#         [120,  84,  50],
#         [109,  73,  42]],
#        ...,
#        [[177, 144, 116],
#         [168, 129,  94],
#         [179, 142,  87],
#         ...,
#         [216, 184, 140],
#         [151, 118,  84],
#         [123,  92,  72]]], dtype=uint8)

 

 

10개의 범주를 가지는 목표 범주(targets)는 0~9 의 정수가 들어있는 리스트(list) 입니다. cicar10.classes 로 각 범주의 이름에 접근할 수 있습니다. 

 

## list
cifar10_targets[:20]

# [6, 9, 9, 4, 1, 1, 2, 7, 8, 3, 4, 7, 7, 2, 9, 9, 9, 3, 2, 6]


## target_labels : target_classes
for i in zip(np.unique(cifar10_targets), cifar10.classes):
    print(i[0], ':', i[1])
    
# 0 : airplane
# 1 : automobile
# 2 : bird
# 3 : cat
# 4 : deer
# 5 : dog
# 6 : frog
# 7 : horse
# 8 : ship
# 9 : truck

 

 

 

(4) CIFAR10 이미지 시각화하기

 

10개 범주의 각 클래스에서 10개의 이미지를 가져와서 10 x 10 grid 에 subplots 을 그려보겠습니다. 

 

import matplotlib.pyplot as plt
import numpy as np

row_num, col_num = len(cifar10.classes), 10
fig, ax = plt.subplots(row_num, col_num, figsize=(10, 10))

for label_class, plot_row in enumerate(ax):
    ## array of index per each target classes
    label_idx = np.where(np.array(cifar10_targets) == label_class)[0]
    for i, plot_cell in enumerate(plot_row):
        if i == 0:
            ## adding class label at ylabel axis
            plot_cell.set_ylabel(cifar10.classes[label_class], 
                                 fontsize=12)
            ## no ticks at x, y axis
            plot_cell.set_yticks([])
            plot_cell.set_xticks([])
            idx = label_idx[i]
            img = cifar10_data[idx]
            plot_cell.imshow(img)
        else:
            # turn off axis
            plot_cell.axis('off')
            # pick the first 10 images from each classes
            idx = label_idx[i]
            img = cifar10_data[idx]
            plot_cell.imshow(img)
            
# Adjust the padding between and around subplots     
plt.tight_layout(pad=0.5)

 

CIFAR10 plots

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

지난 포스팅에서는 두 개의 PyTorch 텐서를 합치기 (concatenating two PyTorch tensors) 에 대해서 다루었습니다.
(바로가기 ==> https://rfriend.tistory.com/781 )

 

이번 포스팅에서는 반대로 한 개의 PyTorch 텐서를 복수 개로 나누기 (splitting a tensor into multiple tensors) 하는 방법을 소개하겠습니다. 

 

(1) 하나의 PyTorch 텐서를 위-아래의 복수 개의 텐서로 나누기
      (splitting a tensor into multiple tensors vertically)

      : torch.vsplit(tensor, indices_or_sections), torch.split(tensor, split_size_or_sections, dim=0)

(2) 하나의 PyTorch 텐서를 좌-우의 복수 개의 텐서로 나누기
     (splitting a tensor into multiple tensors horizontally) 

     : torch.hsplit(tensor, indices_or_sections), torch.split(tensor, split_size_or_sections, dim=1)

 

 

먼저 PyTorch 텐서를 위-아래의 수직으로 복수 개의 텐서로 나누기를 해보겠습니다. 

 

(1) 하나의 PyTorch 텐서를 위-아래의 복수 개의 텐서로 나누기
      (splitting a tensor into multiple tensors vertically)
      : torch.vsplit(tensor, indices_or_sections), torch.split(tensor, split_size_or_sections, dim=0)

 

torch.vsplit() 과 torch.split(dim=0) 의 경우 매개변수의 사용법에 차이가 있습니다. 

 

torch.vsplit(tensor, indices_or_sections) 은 indices_or_sections 매개변수로 input tensor 의 행(row) 을 몇으로 나눌지(indices)를, 그래서 결국 몇 개의 텐서로 나누고 싶은지를 지정해주는 것입니다. 가령, 아래 예의 경우 텐서 z 는 Size[6, 6] 인데요, indices_or_sections=2 로 입력할 경우, 6/2 = 3 으로서 각 3개의 균등한 행을 가지는 2개의 텐서로 나누어줍니다. 

 

반면에, torch.split(tensor, split_size_or_sections, dim=0) 은 (a) "dim=0" 으로 행에 대해서 수직으로 텐서 분리를 수행하라고 지정을 해주어야 하고, (b) split_size_or_sections 에서 지정한 숫자만큼의 크기(split_size)로 행을 가지도록 텐서를 분리해줍니다. 가령, 아래 예의 Size[6, 6] 의 텐서 z에 대해서 torch.split(z, 3, dim=0) 으로서 split_size_or_sections = 3 을 입력해주면 각 행을 3개씩의 크기(split size)로 가지는 텐서로 분리를 해줍니다. (원래 텐서에 행이 6개 있는데, 분리할 텐서는 각 3개씩 행을 가지라고 했으므로 결과적으로 총 2개의 텐서로 분리가 됨). 

 

PyTorch: splitting a tensor into multiple tensors vertically

 

예제로 사용할 Size[6, 6]의 PyTorch 텐서 z 를 만들어보겠습니다. 

 

z = torch.arange(36).reshape(6,6)

print(z)
# tensor([[ 0,  1,  2,  3,  4,  5],
#         [ 6,  7,  8,  9, 10, 11],
#         [12, 13, 14, 15, 16, 17],
#         [18, 19, 20, 21, 22, 23],
#         [24, 25, 26, 27, 28, 29],
#         [30, 31, 32, 33, 34, 35]])

 

 

 

텐서 z 를 torch.vsplit(z, 2) 를 사용해서 2개의 텐서로 분리를 해보겠습니다. (indices_or_sections = 2 로 입력하면 6을 2로 나누어서 3개씩의 행을 가지는 2개의 텐서로 분리해줌)

 

## vsplit(): Splits input, a tensor with two or more dimensions, 
## into multiple tensors vertically according to indices_or_sections.
torch.vsplit(z, 2)

# (tensor([[ 0,  1,  2,  3,  4,  5],
#          [ 6,  7,  8,  9, 10, 11],
#          [12, 13, 14, 15, 16, 17]]),
#  tensor([[18, 19, 20, 21, 22, 23],
#          [24, 25, 26, 27, 28, 29],
#          [30, 31, 32, 33, 34, 35]]))

 

 

이때 반환되는 아웃풋은 두 개의 텐서를 묶어놓은 튜플(tuple)입니다. 

 

type(torch.vsplit(z, 2))
# tuple

 

 

위에서 2개로 분리한 텐서들의 묶음인 튜플에 원하는 부분의 텐서에 접근하기 위해서는 인덱싱(indexing)을 사용하면 됩니다. 아래 예에서는 2개로 분리한 텐서의 각 첫번째와 두번째 튜플에 접근해서 가져와봤습니다.  

 

## accessing a tensor after splitting
torch.vsplit(z, 2)[0]

# tensor([[ 0,  1,  2,  3,  4,  5],
#         [ 6,  7,  8,  9, 10, 11],
#         [12, 13, 14, 15, 16, 17]])


torch.vsplit(z, 2)[1]

# tensor([[18, 19, 20, 21, 22, 23],
#         [24, 25, 26, 27, 28, 29],
#         [30, 31, 32, 33, 34, 35]])

 

 

 

torch.split(z, 3, dim=0) 은 dim=0 으로 지정을 해주면 됩니다. 

 

## Splits the tensor into chunks. 
## Each chunk is a view of the original tensor.
torch.split(z, 3, dim=0)

# (tensor([[ 0,  1,  2,  3,  4,  5],
#          [ 6,  7,  8,  9, 10, 11],
#          [12, 13, 14, 15, 16, 17]]),
#  tensor([[18, 19, 20, 21, 22, 23],
#          [24, 25, 26, 27, 28, 29],
#          [30, 31, 32, 33, 34, 35]]))

 

 

## split_size_or_sections : list of sizes for each chunk
torch.split(z, [1,2,3], dim=0)

# (tensor([[0, 1, 2, 3, 4, 5]]),
#  tensor([[ 6,  7,  8,  9, 10, 11],
#          [12, 13, 14, 15, 16, 17]]),
#  tensor([[18, 19, 20, 21, 22, 23],
#          [24, 25, 26, 27, 28, 29],
#          [30, 31, 32, 33, 34, 35]]))

 

 

 

 

 

(2) 하나의 PyTorch 텐서를 좌-우의 복수 개의 텐서로 나누기
     (splitting a tensor into multiple tensors horizontally) 

     : torch.hsplit(tensor, indices_or_sections), torch.split(tensor, split_size_or_sections, dim=1)

 

이번에는 하나의 PyTorch 텐서를 좌-우 수평으로해서 복수 개의 텐서로 나누어볼텐데요, 역시 torch.hsplit() 과 torch.split(dim=1) 의 경우 매개변수의 사용법에 차이가 있습니다.

 

torch.hsplit(tensor, indices_or_sections) 은 indices_or_sections 매개변수로 input tensor 의 열(column) 을 몇으로 나눌지(indices)를, 그래서 결국 몇 개의 텐서로 나누고 싶은지를 지정해주는 것입니다. 가령, 아래 예의 경우 텐서 z 는 Size[6, 6] 인데요, indices_or_sections=2 로 입력할 경우, 6/2 = 3 으로서 각 3개의 균등한 열을 가지는 2개의 텐서로 좌-우로 나누어줍니다. 

 

반면에, torch.split(tensor, split_size_or_sections, dim=1) 은 (a) "dim=1" 으로 행에 대해서 수평으로 텐서 분리를 수행하라고 지정을 해주어야 하고, (b) split_size_or_sections 에서 지정한 숫자만큼의 크기(split_size)로 행을 가지도록 텐서를 분리해줍니다. 가령, 아래 예의 Size[6, 6] 의 텐서 z에 대해서 torch.split(z, 3, dim=0) 으로서 split_size_or_sections = 3 을 입력해주면 각 열(column)을 3개씩의 크기(split size)로 가지는 텐서로 분리를 해줍니다. (원래 텐서에 열이 6개 있는데, 분리할 텐서는 각 3개씩 열을 가지라고 했으므로 결과적으로 총 2개의 텐서로 분리가 됨). 

 

 

 

PyTorch hsplit(), split(dim=1) : splitting a tensor into multiple tensors horizontally

 

 

## hsplit(): Splits input, a tensor with two or more dimensions, 
## into multiple tensors horizontally according to indices_or_sections.

torch.hsplit(z, 2)

# (tensor([[ 0,  1,  2],
#          [ 6,  7,  8],
#          [12, 13, 14],
#          [18, 19, 20],
#          [24, 25, 26],
#          [30, 31, 32]]),
#  tensor([[ 3,  4,  5],
#          [ 9, 10, 11],
#          [15, 16, 17],
#          [21, 22, 23],
#          [27, 28, 29],
#          [33, 34, 35]]))

 

 

torch.hsplit(tensor, indices_or_sections) 에서 indices_or_sections 매개변수로 indices 넣어줄 때는 정수로 나누어지는 값을 넣어주어야 합니다. 가령, 아래 예에서는 Size[6, 6] 의 텐서에서 dimension 1 의 방향으로 4개 나누라고 지정해었더닌 6을 4로 나눌 수 없다면서 RunTimeError: torch.hsplit attempted to split along dimension 1, but size of the dimension 6 is not divisible by the split_size 4! 라는 에러가 발생했습니다. 

 

## RuntimeError
torch.hsplit(z, 4)

# RuntimeError: torch.hsplit attempted to split along dimension 1, 
# but the size of the dimension 6 is not divisible by the split_size 4!

 

 

 

torch.split(z, 3, dim=1) 에서는 dim=1 로 차원을 지정해주면 됩니다. 

 

torch.split(z, 3, dim=1)

# (tensor([[ 0,  1,  2],
#          [ 6,  7,  8],
#          [12, 13, 14],
#          [18, 19, 20],
#          [24, 25, 26],
#          [30, 31, 32]]),
#  tensor([[ 3,  4,  5],
#          [ 9, 10, 11],
#          [15, 16, 17],
#          [21, 22, 23],
#          [27, 28, 29],
#          [33, 34, 35]]))

 

 

 

torch.split() 함수에서 split_size_or_sections 매개변수에 리스트로 해서 나누고 싶은 텐서의 크기 (split_size_sections) 를 복수개로 지정해줄 수도 있습니다. 아래의 예에서는 Size[6, 6]의 텐서를 열의 개수를 1개, 2개, 3개를 가지는 총 3개의 텐서로 분리(split_size_or_sections = [1, 2, 3])해 본 것입니다. 매우 편리한 기능입니다! 

 

## split_size_or_sections : list of sizes for each chunk
torch.split(z, [1,2,3], dim=1)

# (tensor([[ 0],
#          [ 6],
#          [12],
#          [18],
#          [24],
#          [30]]),
#  tensor([[ 1,  2],
#          [ 7,  8],
#          [13, 14],
#          [19, 20],
#          [25, 26],
#          [31, 32]]),
#  tensor([[ 3,  4,  5],
#          [ 9, 10, 11],
#          [15, 16, 17],
#          [21, 22, 23],
#          [27, 28, 29],
#          [33, 34, 35]]))

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 두 개의 PyTorch 텐서를 하나로 합치는 방법을 소개하겠습니다. 

 

(1) torch.cat((x, y), dim=0), torch.concat(dim=0): 두 개의 텐서를 위-아래로 합치기 (vertically, row wise)

(2) torch.cat((x, y), dim=1), torch.concat(dim=1): 두 개의 텐서를 좌-우로 합치기 (horizontally, column wise)

(3) torch.vstack((x, y)), torch.row_stack(): 두 개의 텐서를 위-아래로 합치기 (vertically, row wise)

(4) torch.hstack((x, y)), torch.column_stack(): 두 개의 텐서를 좌-우로 합치기 (horizontally, column wise)

(5) torch.stack((x, y), dim=0): 두 개의 텐서를 새 차원(new dimension)으로 위-아래로 합치기

(6) torch.stack((x, y)dim=1): 두 개의 텐서를 새 차원(new dimension)으로 좌-우로 합치기

 

 

 

두 개의 텐서를 위-아래로 합치기 (vertically, row wise concatenation) 는 torch.cat(dim=0), torch.concat(dim=0), torch.vstack(), torch.row_stack() 로 수행 가능합니다. 

 

PyTorch: concatenate tensors in sequence vertically, row-wise

 

 

 

두 개의 텐서를 좌-우로 합치기 (horizontally, column wise concatenation) 는 torch.cat(dim=1), torch.concat(dim=1), torch.hstack(), torch.column_stack() 로 수행 가능합니다. 

 

PyTorch: concatenating tensors in sequence horizontally, column-wise

 

 

 

torch.stack((x, y), dim=0) 은 새로운 차원(new dimension)을 추가해서 두 개의 텐서를 위-아래로 합쳐주며, 

torch.stack((x, y), dim=1) 은 새로운 차원을 추가해서 두 개의 텐서를 좌-우로 합쳐주는 차이점이 있습니다. 

 

 

 

 

예제로 사용할 두 개의 PyTorch 텐서를 만들어보겠습니다. 

 

import torch

x = torch.arange(12).reshape(3, 4)
y = torch.arange(12, 24).reshape(3, 4)

print(x)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]])

print(y)
# tensor([[12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 21, 22, 23]])

 

 

 

(1) torch.cat(dim=0), torch.concat(dim=0)
        : 두 개의 텐서를 위-아래로 합치기 (vertically, row wise)

 

## torch.cat(): concatenating in the axis 0
torch.cat((x, y), dim=0) # or torch.concat((x, y), 0)

# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11],
#         [12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 21, 22, 23]])


torch.cat((x, y), dim=0).shape
# torch.Size([6, 4])

 

 

 

(2) torch.cat((x, y), dim=1), torch.concat(dim=1)
        : 두 개의 텐서를 좌-우로 합치기 (horizontally, column wise)

 

## concatenating in the axis 1
torch.cat((x, y), dim=1) # or torch.concat((x, y), 1)

# tensor([[ 0,  1,  2,  3, 12, 13, 14, 15],
#         [ 4,  5,  6,  7, 16, 17, 18, 19],
#         [ 8,  9, 10, 11, 20, 21, 22, 23]])


torch.cat((x, y), dim=1).shape
# torch.Size([3, 8])

 

 

 

(3) torch.vstack((x, y)), torch.row_stack()
        : 
두 개의 텐서를 위-아래로 합치기 (vertically, row wise)

 

## Stack tensors in sequence vertically (row wise).
torch.vstack((x, y))

# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11],
#         [12, 13, 14, 15],
#         [16, 17, 18, 19],
#         [20, 21, 22, 23]])


## or equivalently
torch.row_stack((x, y))


torch.row_stack((x, y)).shape
# torch.Size([6, 4])

 

 

 

(4) torch.hstack((x, y)), torch.column_stack()
        : 두 개의 텐서를 좌-우로 합치기 (horizontally, column wise)

 

## Stack tensors in sequence horizontally (column wise).
torch.hstack((x, y))

# tensor([[ 0,  1,  2,  3, 12, 13, 14, 15],
#         [ 4,  5,  6,  7, 16, 17, 18, 19],
#         [ 8,  9, 10, 11, 20, 21, 22, 23]])


## or equivalently
torch.column_stack((x, y))


torch.column_stack((x, y)).shape
# torch.Size([3, 8])

 

 

 

(5) torch.stack((x, y), dim=0)
        : 두 개의 텐서를 새 차원(new dimension)으로 위-아래로 합치기

 

위의 (1)~(4)번의 메소드 대비해서 torch.stack((x, y), dim=0) 은 새로운 차원(new dimension)을 추가해서 두 개의 텐서를 위-아래로 합쳐주며, torch.stack((x, y), dim=1) 은 새로운 차원을 추가해서 두 개의 텐서를 좌-우로 합쳐주는 차이점이 있습니다. (1)~(4)번의 텐서 합치기를 했을 때의 shape 과 (5)~(6)번의 텐서 합치기 후의 shape 을 유심히 비교해보시기 바랍니다.  

 

## Concatenates a sequence of tensors along a new dimension.
## stack with axis=0 (vertically, row-wise)
torch.stack((x, y), dim=0) # axis=0

# tensor([[[ 0,  1,  2,  3],
#          [ 4,  5,  6,  7],
#          [ 8,  9, 10, 11]],

#         [[12, 13, 14, 15],
#          [16, 17, 18, 19],
#          [20, 21, 22, 23]]])

## new dimension
torch.stack((x, y), dim=0).shape
# torch.Size([2, 3, 4])

 

 

 

(6) torch.stack((x, y)dim=1)
        : 두 개의 텐서를 새 차원(new dimension)으로 좌-우로 합치기

 

## stack with axis=1 (horizontally, column-wise)
torch.stack((x, y), dim=1) # axis=1

# tensor([[[ 0,  1,  2,  3],
#          [12, 13, 14, 15]],

#         [[ 4,  5,  6,  7],
#          [16, 17, 18, 19]],

#         [[ 8,  9, 10, 11],
#          [20, 21, 22, 23]]])


## new dimension
torch.stack((x, y), dim=1).shape
# torch.Size([3, 2, 4])

 

 

다음 포스팅에서는 이어서 '하나의 PyTorch 텐서를 복수개의 텐서로 나누기 (splitting a PyTorch tensor into multiple tensors)' 에 대해서 소개하겠습니다.  

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,