본문 바로가기
  • 머킹이의 머신로그
프로젝트/개인 프로젝트

[자연어 개인 프로젝트] 임베딩과 Transformer 구현

by 머킹 2023. 9. 26.
728x90

제주도 사투리 후처리 로직과 Transformer 구현하기

 

안녕하세요 머킹입니다.

오늘은 한 게 많아서 코드 위주의 글이 될 것 같습니다.

근데 제가 모델을 다시 처음부터 해야 할 것 같다는 생각이 들어서 조금 막막하지만

그래도 오늘 오류를 많이 경험해서 배우는 게 많았습니다.


저번에 병렬 구조 쌍으로 데이터를 저장했고 잘 저장되었습니다.

다음으로 토큰을 추가하는 후처리 로직을 해야 하는데요.

 

각 언어 쌍의 평균 길이와 최대 길이를 계산하고,

PAD 토큰을 추가하여 모든 문장을 최대 길이로 맞추는 작업을 수행합니다.

 

그리고 모든 데이터 셋을 텐서형 데이터로 변환합니다.

### 샘플 50개 수행

import csv
import torch
from torch.utils.data import DataLoader
from konlpy.tag import Okt

# Okt 형태소 분석기 초기화
okt = Okt()

# CSV 파일 경로 설정
csv_file = '/content/drive/MyDrive/jeju/parallel_data.csv'

# 제주도 사투리 토크나이저 초기화
jeju_tokenizer = Okt()

# 데이터를 저장할 리스트
parallel_data = []

# CSV 파일에서 50개의 데이터 읽어오기
with open(csv_file, 'r', encoding='utf-8') as csvfile:
    reader = csv.DictReader(csvfile)
    for idx, row in enumerate(reader):
        if idx >= 50:
            break
        parallel_data.append({
            'Utterance': row['Utterance'],
            'Original': row['Original'],
            'Translation': row['Translation']
        })

# 추가 후처리 작업 (SOS, EOS, PAD 토큰 추가)
max_len = 32  # 문장의 최대 길이를 지정합니다.
for item in parallel_data:
    item['Original'] = '[SOS] ' + item['Original'] + ' [EOS]'
    item['Translation'] = '[SOS] ' + item['Translation'] + ' [EOS]'

# 단어를 정수로 매핑하는 사전을 생성합니다.
word_to_index = {}
index_to_word = {}

# 각 단어에 고유한 정수를 할당합니다.
for item in parallel_data:
    for sentence in [item['Original'], item['Translation']]:
        for word in sentence.split():
            if word not in word_to_index:
                word_to_index[word] = len(word_to_index)
                index_to_word[len(word_to_index) - 1] = word

# 토큰을 정수 인덱스로 매핑하는 함수
def tokens_to_indices(tokens):
    indices = [word_to_index.get(token, word_to_index['[PAD]']) for token in tokens.split()]
    indices += [word_to_index['[PAD]']] * (max_len - len(indices))
    return indices


# GPU 사용 가능한 경우 GPU를 사용하고, 그렇지 않은 경우 CPU를 사용합니다.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 제주도 사투리 데이터를 torch.Tensor로 변환
jeju_tensors = [torch.tensor(tokens_to_indices(item['Original'])).to(device) for item in parallel_data]

# 표준어 데이터를 torch.Tensor로 변환
std_tensors = [torch.tensor(tokens_to_indices(item['Translation'])).to(device) for item in parallel_data]

# DataLoader를 사용하여 데이터를 배치로 만듭니다.
batch_size = 64
jeju_loader = DataLoader(jeju_tensors, batch_size=batch_size, shuffle=True)
std_loader = DataLoader(std_tensors, batch_size=batch_size, shuffle=True)

#예시 문장 (표준어 데이터 중 하나를 선택)
sample_translation = parallel_data[49]['Translation']

# 토큰화
sample_tokens = sample_translation.split()

# 후처리 작업 결과 (SOS, EOS, PAD 토큰 제외)
post_proc_sent = [word_to_index.get(token, word_to_index['[PAD]']) for token in sample_tokens if token not in ['[SOS]', '[EOS]', '[PAD]']]

print(f'후처리 결과: {post_proc_sent}\n')
print(f'후처리 해석: {" ".join(sample_tokens)}')
후처리 결과: [113, 138, 139, 140, 141, 33, 46, 142, 143, 144, 136, 145, 9, 146, 120, 147, 148]

후처리 해석: [SOS] 아 내 개인 적 으로 이제 난 소금 후추 오일이 일단 소스 는 제일 맛있는 것 같고 [EOS]

제대로 된 결과가 나와서 좋습니다 ㅎㅎ 이제 샘플 말고 전체 데이터를 진행해야 합니다.

import csv
import torch
from torch.utils.data import DataLoader
from konlpy.tag import Okt

# Okt 형태소 분석기 초기화
okt = Okt()

# CSV 파일 경로 설정
csv_file = '/content/drive/MyDrive/jeju/parallel_data.csv'

# 데이터를 저장할 리스트
parallel_data = []

# 제주도 사투리 토크나이저 초기화
jeju_tokenizer = Okt()

# 데이터를 저장할 리스트
parallel_data = []

# CSV 파일에서 모든 데이터 읽어오기
with open(csv_file, 'r', encoding='utf-8') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        parallel_data.append({
            'Utterance': row['Utterance'],
            'Original': row['Original'],
            'Translation': row['Translation']
        })

# 추가 후처리 작업 (SOS, EOS, PAD 토큰 추가)
max_len = 32  # 문장의 최대 길이를 지정합니다.
for item in parallel_data:
    item['Original'] = '[SOS] ' + item['Original'] + ' [EOS]'
    item['Translation'] = '[SOS] ' + item['Translation'] + ' [EOS]'

# 단어를 정수로 매핑하는 사전을 생성합니다.
word_to_index = {}
index_to_word = {}

# 각 단어에 고유한 정수를 할당합니다.
for item in parallel_data:
    for sentence in [item['Original'], item['Translation']]:
        for word in sentence.split():
            if word not in word_to_index:
                word_to_index[word] = len(word_to_index)
                index_to_word[len(word_to_index) - 1] = word

# 토큰을 정수 인덱스로 매핑하는 함수
def tokens_to_indices(tokens):
    indices = [word_to_index.get(token, word_to_index['[PAD]']) for token in tokens.split()]
    indices += [word_to_index['[PAD]']] * (max_len - len(indices))
    return indices

# GPU 사용 가능한 경우 GPU를 사용하고, 그렇지 않은 경우 CPU를 사용합니다.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 제주도 사투리 데이터를 torch.Tensor로 변환
jeju_tensors = [torch.tensor(tokens_to_indices(item['Original'])).to(device) for item in parallel_data]

# 표준어 데이터를 torch.Tensor로 변환
std_tensors = [torch.tensor(tokens_to_indices(item['Translation'])).to(device) for item in parallel_data]

# DataLoader를 사용하여 데이터를 배치로 만듭니다.
batch_size = 64
jeju_loader = DataLoader(jeju_tensors, batch_size=batch_size, shuffle=True)
std_loader = DataLoader(std_tensors, batch_size=batch_size, shuffle=True)
# 예시 문장 (표준어 데이터 중 하나를 선택)
sample_translation = parallel_data[31562]['Translation']

# 토큰화
sample_tokens = sample_translation.split()

# 후처리 작업 결과 (SOS, EOS, PAD 토큰 제외)
post_proc_sent = [word_to_index.get(token, word_to_index['[PAD]']) for token in sample_tokens if token not in ['[SOS]', '[EOS]', '[PAD]']]

print(f'후처리 결과: {post_proc_sent}\n')
print(f'후처리 해석: {" ".join(sample_tokens)}')
후처리 결과: [1711, 4523, 23981]

후처리 해석: [SOS] 어차피 붙으면 장거리 [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [EOS]

이것도 잘 마무리되었습니다.

 

현재 데이터 셋의 크기가 토큰화의 크기인가 궁금해서 출력해 봤습니다.

dataset_size = len(parallel_data)
print(f"데이터셋의 크기: {dataset_size}")

데이터셋의 크기: 283929

이제 모델을 생성할 때입니다.

저는 Transformer 모델을 사용할 예정인데요.

시간이 된다면 다른 모델들도 많이 사용해보고 싶습니다.

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

import numpy as np
import matplotlib.pyplot as plt

# Reproducible
torch.manual_seed(32)  # CPU에서의 난수 생성 시드를 설정
torch.cuda.manual_seed(32)  #  GPU에서의 난수 생성 시드를 설정
torch.backends.cudnn.deterministic = True  
# PyTorch에서 CUDA 연산을 수행할 때, cuDNN 라이브러리의 동작을 결정적으로 만들어, 동일한 입력에 대해 항상 동일한 출력을 생성

일단 위에서 불러올 라이브러리들을 정의합니다.

그리고 임베딩을 진행합니다.

import torch
import torch.nn as nn

# 임베딩 크기 설정
embedding_dim = 512  # 임의의 값, 원하는 차원 크기로 선택하세요

# 임베딩 레이어 초기화
embedding = nn.Embedding(len(word_to_index), embedding_dim).to(device)

# 토큰화된 데이터를 인덱스로 변환
sample_tokens = ["[SOS]", "제주도", "사투리", "번역", "[EOS]"]  # 예시 토큰 데이터
sample_indices = torch.tensor([word_to_index.get(token, word_to_index['[PAD]']) for token in sample_tokens]).to(device)

# 임베딩 레이어에 토큰 인덱스 적용
embedded_data = embedding(sample_indices)

# 결과 확인
print(f"입력 토큰: {sample_tokens}")
print(f"임베딩 결과:\n{embedded_data}")

dataset_size = len(parallel_data)
print(f"데이터셋 크기: {dataset_size}개")
입력 토큰: ['[SOS]', '제주도', '사투리', '번역', '[EOS]']
임베딩 결과:
tensor([[-0.1718,  2.1986, -1.3559,  ...,  0.4675, -0.3020,  0.3031],
        [-0.3033,  0.5764,  0.8353,  ...,  0.0862, -0.1007,  0.5910],
        [ 0.4156,  1.6934, -0.1176,  ...,  2.0199, -1.8832, -1.1365],
        [ 1.3505, -0.6583, -1.2172,  ..., -1.3062,  0.5017, -0.4340],
        [-0.8248, -0.0634,  0.0075,  ..., -0.7278, -2.3551, -0.5645]],
       grad_fn=<EmbeddingBackward0>)
데이터셋 크기: 283929개

근데 왜 이게 하나의 결과만 나올까요..?

그래서 다시 루프문으로 코드를 짰습니다.

import csv
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from konlpy.tag import Okt
import random

# Okt 형태소 분석기 초기화
okt = Okt()

# CSV 파일 경로 설정
csv_file = '/content/drive/MyDrive/jeju/parallel_data.csv'

# 데이터를 저장할 리스트
parallel_data = []

# CSV 파일에서 모든 데이터 읽어오기
with open(csv_file, 'r', encoding='utf-8') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        parallel_data.append({
            'Utterance': row['Utterance'],
            'Original': row['Original'],
            'Translation': row['Translation']
        })

# 추가 후처리 작업 (SOS, EOS, PAD 토큰 추가)
max_len = 32  # 문장의 최대 길이를 지정합니다.
for item in parallel_data:
    item['Original'] = '[SOS] ' + item['Original'] + ' [EOS]'
    item['Translation'] = '[SOS] ' + item['Translation'] + ' [EOS]'

# 단어를 정수로 매핑하는 사전을 생성합니다.
word_to_index = {}
index_to_word = {}

# 각 단어에 고유한 정수를 할당합니다.
for item in parallel_data:
    for sentence in [item['Original'], item['Translation']]:
        for word in sentence.split():
            if word not in word_to_index:
                word_to_index[word] = len(word_to_index)
                index_to_word[len(word_to_index) - 1] = word

# 토큰을 정수 인덱스로 매핑하는 함수
def tokens_to_indices(tokens):
    indices = [word_to_index.get(token, word_to_index['[PAD]']) for token in tokens.split()]
    indices += [word_to_index['[PAD]']] * (max_len - len(indices))
    return indices

# GPU 사용 가능한 경우 GPU를 사용하고, 그렇지 않은 경우 CPU를 사용합니다.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 표준어 데이터를 torch.Tensor로 변환
std_tensors = [torch.tensor(tokens_to_indices(item['Translation'])).to(device) for item in parallel_data]

# DataLoader를 사용하여 데이터를 배치로 만듭니다.
batch_size = 64
std_loader = DataLoader(std_tensors, batch_size=batch_size, shuffle=True)

# 예시 문장 (표준어 데이터 중 하나를 선택)
sample_translation = parallel_data[31562]['Translation']

# 토큰화
sample_tokens = sample_translation.split()

# 후처리 작업 결과 (SOS, EOS, PAD 토큰 제외)
post_proc_sent = [word_to_index.get(token, word_to_index['[PAD]']) for token in sample_tokens if token not in ['[SOS]', '[EOS]', '[PAD]']]

# 생성한 타겟 시퀀스를 모델에 입력하여 임베딩 생성
sample_target_sequence = torch.tensor(post_proc_sent).unsqueeze(0).to(device)  # 배치 차원 추가 및 GPU로 이동

# 임베딩 차원 랜덤 설정 (100에서 300 사이의 값)
embedding_dim = random.randint(100, 300)

# 임베딩 모델 정의
embedding_model = nn.Embedding(num_embeddings=len(word_to_index), embedding_dim=embedding_dim).to(device)

# 생성한 타겟 시퀀스에 임베딩 적용
sample_embeddings = embedding_model(sample_target_sequence)

# 결과 출력
print("임베딩 차원 (embedding_dim):", embedding_dim)
print("예시 문장의 임베딩:")
print(sample_embeddings)
임베딩 차원 (embedding_dim): 274
예시 문장의 임베딩:
tensor([[[-3.2013e-01,  8.9300e-01, -1.5642e-01, -1.1617e+00, -9.9936e-01,
          -1.2362e+00, -5.8393e-01,  8.5305e-02, -6.2949e-01,  1.1705e+00,
          -4.5907e-01,  3.7055e-01, -2.5642e+00, -1.9076e+00,  2.0141e-01,
          -5.6503e-01, -2.5445e+00,  7.6639e-01, -1.1969e+00,  3.2083e+00,
          -8.5860e-01,  6.4544e-01, -9.4454e-01, -8.6809e-01, -6.2616e-01,
          -2.0979e+00, -1.6870e+00,  1.9868e-01,  6.0553e-01, -3.5960e-01,
           9.3706e-01, -3.6318e-01, -1.7007e+00,  1.2417e+00,  2.0647e+00,
           5.0176e-01, -1.4038e-01, -6.9572e-01, -2.0611e-01, -1.9721e+00,
          -1.2197e+00, -3.8668e-01,  4.2489e-02,  2.2858e+00, -6.8441e-01,
           1.3245e+00, -2.9854e-01, -1.9532e+00, -2.1559e-01,  1.0040e+00,
           1.4041e+00,  7.5854e-01,  1.2120e+00, -6.9265e-01, -2.6285e+00,
           4.5654e-01, -4.3540e-01,  1.8434e-01, -2.5295e-01, -1.2123e+00,
           1.3356e-01, -4.0755e-01, -5.3499e-01,  6.2490e-01, -2.1108e+00,
          -1.2799e+00, -2.9460e-01, -3.0130e-01,  3.8295e-01,  5.0995e-01,
          -9.7412e-01, -1.2716e+00,  6.7756e-02,  9.0849e-01, -4.2134e-01,
          -5.1248e-01,  1.6009e+00,  1.4864e+00,  1.4728e+00,  2.9776e-01,
          -6.4681e-01, -2.7097e-01,  9.8615e-01, -9.3145e-01,  3.7559e-01,
           1.0129e+00, -9.3753e-01,  1.2375e+00, -3.7421e-01,  7.8030e-01,
          -4.6174e-01, -1.0416e+00, -3.2362e-01, -1.0242e-02,  2.1748e+00,
           1.3396e-02,  9.6336e-02, -1.7605e+00,  6.9758e-01, -8.6163e-01,
          -6.2748e-01, -1.5422e-01,  9.8859e-01,  6.5999e-01, -1.0179e+00,
          -2.4505e-01,  1.0723e-01, -1.1064e+00, -1.7642e+00, -5.2585e-01,
           1.9505e-02, -3.4172e-01, -1.1603e+00,  7.1975e-01, -1.2105e-01,
          -2.7572e-01, -3.4234e+00,  5.1096e-01, -7.2008e-01,  1.3927e+00,
          -6.8067e-01, -2.1993e+00,  8.1406e-01, -3.6469e-01,  6.6249e-02,
          -5.6257e-01, -1.4890e+00,  5.2670e-01,  8.6683e-02, -1.3128e+00,
           1.2244e+00,  3.4080e-01, -1.4879e+00,  1.8772e+00, -5.4685e-01,
          -8.3381e-01, -9.8754e-01, -2.1823e+00,  1.5689e+00,  1.9534e-01,
           1.6287e+00, -9.9462e-01, -5.4653e-01,  3.3966e-01,  1.2837e+00,
          -2.2196e+00, -8.9072e-01, -9.6998e-01, -1.2668e+00,  8.5012e-01,
           1.2659e-01,  1.3515e+00,  2.6429e-01,  4.0000e-01,  1.8585e-01,
           2.4267e+00,  4.9941e-01, -8.3500e-01, -8.2085e-02, -6.0440e-01,
          -1.1392e+00,  2.1220e+00,  2.8596e-01,  5.2889e-01, -1.3340e+00,
          -5.5415e-01,  1.4871e+00,  4.3336e-01,  5.0365e-01,  5.0953e-01,
           1.4089e+00, -1.4863e-01,  9.3505e-01,  6.4719e-01, -6.6731e-01,
          -9.4803e-01,  1.2565e+00, -4.0733e-01, -4.3429e-01, -1.2835e+00,
          -5.7946e-01,  5.8279e-01,  5.6765e-01,  3.0173e-01, -7.1000e-01,
           1.8348e+00,  1.1015e+00, -6.5589e-01, -5.7083e-01, -1.8124e+00,
           6.5931e-01,  4.3257e-01,  4.6625e-01,  6.0108e-01, -9.5370e-02,
          -8.1240e-01,  5.1029e-01, -1.5049e+00,  1.2511e+00, -7.4395e-02,
           7.4078e-01, -1.3789e+00,  9.8704e-01, -4.6152e-01, -1.1496e+00,
           5.7528e-01, -1.4195e+00,  1.2004e+00,  9.8642e-03, -1.8884e-02,
           4.2219e-02, -1.1994e+00, -6.8163e-01,  1.3353e+00, -1.4879e+00,
          -1.9522e-01,  6.4714e-02, -6.8177e-01,  2.1174e+00, -8.7714e-02,
           4.5179e-01, -6.1949e-01,  1.7309e-02,  7.3083e-01,  7.1680e-01,
          -6.7938e-02,  8.9001e-01,  1.2510e+00,  6.6596e-02,  2.1986e-01,
           7.0223e-01, -7.9476e-01,  3.1504e-01, -9.2580e-01,  1.0179e+00,
           2.3394e+00, -5.1022e-01, -1.4389e+00, -9.8074e-01, -5.3781e-01,
           1.9484e+00, -2.0270e+00, -2.4907e-01,  1.2921e+00, -2.0133e+00,
          -7.3969e-01,  1.1344e+00, -1.2300e-01,  1.8912e+00,  1.7167e+00,
          -5.1337e-01,  3.7241e-01, -8.6645e-01, -4.3350e-01,  9.1209e-01,
          -7.0829e-01, -1.3014e+00, -7.6891e-01, -7.4103e-01,  7.8002e-01,
          -1.0996e+00, -8.7705e-02,  7.1006e-01,  8.0093e-01,  3.5955e-01,
           1.1620e+00, -1.7636e+00, -7.5913e-01,  9.9495e-01,  4.0728e-01,
           5.7910e-01, -6.2005e-01, -7.8045e-02,  3.1005e-01],

너무 길어서 조금만 보여드리겠습니다.

이제 진짜 모델을 멀티 헤드 어텐션부터 정의할 것인데요. 

그 코드는 제가 Transformer 직접 짜보기 글에 있으니 참고해 주세요! (5번부터입니다 ㅎㅎ)

 

아무튼 저는 이제 거의 다 마무리를 해서 모델을 돌려볼까!

했는데! 여기서 오류가 없으면 정말 좋겠지만 코드는 오류의 연속 아니겠습니까

위에 정의를 제대로 안 해서 저는 다시 토큰화부터 해야 하더라고요 ㅎㅎ

열심히 하루 동안 코드 돌리고 내일은 꼭.. 모델을 돌리겠습니다..