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

[자연어 개인 프로젝트] 제주도 사투리 번역기 중간발표

by 머킹 2023. 10. 11.
728x90

Transformer 제주도 사투리 번역기 중간발표

안녕하세요 머킹입니다.

이 글을 쓰다가 날아가서 조금.. 조심하면서 쓰겠습니다...

많이 안 써서 참 다행이네요.

 

저는 오늘 학원에서 발표를 했습니다.

모델이 돌아가지 않아서 아쉽지만

저는 이 프로젝트 발표 뒤에도 최종 목표로 계속할 거니까요!


저번 글에서 돌아갔던 코드의 결과입니다.

데이터를 1000000개로 쪼개니까 그래도 하루종일 토큰화 하지는 않았습니다.

저는 왜 항상 토큰화에서 뒤로 갔다가 앞으로 갔다가 할까요?

 

어제 이런 논문을 발견하게 되었습니다.

https://koreascience.kr/article/CFKO201930060758842.page

 

Parallel Corpus Filtering and Korean-Optimized Subword Tokenization for Machine Translation -Annual Conference on Human and

Abstract 딥러닝을 이용한 Neural Machine Translation(NMT)의 등장으로 기계번역 분야에서 기존의 규칙 기반,통계기반 방식을 압도하는 좋은 성능을 보이고 있다. 본 논문은 기계번역 모델도 중요하지만

koreascience.kr

논문의 결론은 이렇습니다.

 

한국어 관련 기계번역을 만들 때는 BPE나 Sentencepiece를 사용하는 것보다 형태소 단위분리가 더 좋은 성능을 보임

 

실험결과 제안하는 방식인 형태소 분석기를 통한 조사 분리 후 Unigram 정보를 이용한 Sentencepiece Unigram 정보를 이용한 Tokenization을 사용하니 BLEU 점수가 향상됨을 확인하였다.

 

즉 형태소 분석기만 단독으로 사용하는 게 아니라

형태소 분석기 + Unigram을 사용하는게 훨씬 더 좋은 결과를 나타냈다는 건데요.

 

근데 그게 제 데이터에도 그럴까 고민됐습니다.

그래서 직접 코드를 돌려보기로 했습니다.

import os
import csv
from konlpy.tag import Okt
from collections import Counter
import random
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from tqdm import tqdm

# 나눔고딕 폰트 경로 설정
font_path = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf'

# 나눔고딕 폰트 설정
font_name = fm.FontProperties(fname=font_path, size=10).get_name()
plt.rc('font', family=font_name)

# Levenshtein 거리 계산 함수
def levenshtein_distance(s1, s2):
    if len(s1) < len(s2):
        return levenshtein_distance(s2, s1)

    if len(s2) == 0:
        return len(s1)

    previous_row = range(len(s2) + 1)
    for i, c1 in enumerate(s1):
        current_row = [i + 1]
        for j, c2 in enumerate(s2):
            insertions = previous_row[j + 1] + 1
            deletions = current_row[j] + 1
            substitutions = previous_row[j] + (c1 != c2)
            current_row.append(min(insertions, deletions, substitutions))
        previous_row = current_row

    return previous_row[-1]

# 형태소 분석 결과를 얻기 위한 함수
def get_morph_tokens(text, use_unigram=False):
    if use_unigram:
        word_counts = Counter(text.split())
        tokens = [token for token in text.split() if word_counts[token] > 1]
    else:
        tokens = okt.morphs(text)
    return tokens

# `Okt`와 `Okt` + `Unigram` 결과 비교하는 함수
def compare_results(text1, text2, use_unigram=False):
    tokens1 = get_morph_tokens(text1, use_unigram)
    tokens2 = get_morph_tokens(text2, use_unigram)
    
    # 형태소 개수가 다른 경우 0 반환
    if len(tokens1) != len(tokens2):
        return 0
    
    # 정확도 계산
    total_tokens = len(tokens2)
    correct_tokens = sum(1 for d, s in zip(tokens1, tokens2) if d == s)
    
    accuracy = correct_tokens / total_tokens if total_tokens > 0 else 0
    
    return accuracy

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

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

# CSV 파일로부터 데이터를 읽어옵니다.
parallel_data = []

# Define accuracy lists
okt_accuracies = []
okt_unigram_accuracies = []

# Number of records to read (adjust as needed)
num_records = 100

with open(csv_file, 'r', encoding='utf-8') as csvfile:
    reader = csv.DictReader(csvfile)
    
    # Use tqdm to show a progress bar
    for row in tqdm(reader, total=num_records):
        dialect_form = row['Original']
        standard_form = row['Translation']

        # 형태소 분석 후 토큰화
        dialect_tokens = dialect_form.split()
        standard_tokens = standard_form.split()

        # 형태소 분석 결과가 비어 있는 경우, 정확도를 0으로 처리
        if not dialect_tokens or not standard_tokens:
            okt_accuracy = 0
            okt_unigram_accuracy = 0
        else:
            # 형태소 분석 결과를 비교하여 정확도 계산 (Okt)
            okt_accuracy = compare_results(dialect_form, standard_form)

            # 형태소 분석 결과를 비교하여 정확도 계산 (Okt+Unigram)
            okt_unigram_accuracy = compare_results(' '.join(dialect_tokens), ' '.join(standard_tokens))
        
        okt_accuracies.append(okt_accuracy)
        okt_unigram_accuracies.append(okt_unigram_accuracy)

# 정확도 출력
average_okt_accuracy = sum(okt_accuracies) / len(okt_accuracies) if len(okt_accuracies) > 0 else 0
average_okt_unigram_accuracy = sum(okt_unigram_accuracies) / len(okt_unigram_accuracies) if len(okt_unigram_accuracies) > 0 else 0

print(f'Okt 정확도: {average_okt_accuracy}')
print(f'Okt + Unigram 정확도: {average_okt_unigram_accuracy}')

# 정확도 시각화
labels = ['Okt', 'Okt+Unigram']
accuracies = [average_okt_accuracy, average_okt_unigram_accuracy]

plt.bar(labels, accuracies)
plt.xlabel('형태소 분석 방법')
plt.ylabel('정확도')
plt.title('형태소 분석 방법별 정확도 비교')
plt.show()

결과를 보여드리고 싶은데 아직도 돌아가는 중입니다..

대신 다른 흥미로운 결과들이 이었습니다.

데이터의 길이가 다르지만 그래도 엄청나게 많이 차이 나지는 않는다는 결과를 보였습니다.

이게 겹친 결과인데 사투리가 조금 더 긴 문장이 많다는 것을 확인했습니다.

그리고 저는 주제별로 길이가 궁금했습니다.

import os
import json
import matplotlib.pyplot as plt

# JSON 파일이 있는 디렉토리 경로
data_dir = '/content/drive/MyDrive/jeju'

# 주제별로 표준어와 사투리의 길이를 저장할 딕셔너리 초기화
topic_lengths = {}

# 디렉토리 내의 JSON 파일을 읽어옵니다.
for folder in ['Training', 'Validation']:
    folder_path = os.path.join(data_dir, folder)
    json_files = os.listdir(folder_path)
    json_files = json_files[:100]  # 최대 1000000개의 파일만 읽습니다.
    for filename in json_files:
        if filename.endswith('.json'):
            with open(os.path.join(folder_path, filename), 'r', encoding='utf-8') as json_file:
                data = json.load(json_file)
                topic = data.get("topic", "Unknown")
                std_length = len(data.get("std_tokens", []))
                jeju_length = len(data.get("jeju_tokens", []))
                
                # 주제별로 길이를 딕셔너리에 추가
                if topic not in topic_lengths:
                    topic_lengths[topic] = {'std_lengths': [], 'jeju_lengths': []}
                topic_lengths[topic]['std_lengths'].append(std_length)
                topic_lengths[topic]['jeju_lengths'].append(jeju_length)

# 주제별로 표준어 길이 히스토그램 그리기
plt.figure(figsize=(10, 6))
for topic in topic_lengths:
    plt.hist(topic_lengths[topic]['std_lengths'], bins=20, alpha=0.5, label=topic)

plt.xlabel('표준어 길이')
plt.ylabel('빈도')
plt.title('주제별 표준어 길이 히스토그램')
plt.legend()
plt.show()

# 주제별로 사투리 길이 히스토그램 그리기
plt.figure(figsize=(10, 6))
for topic in topic_lengths:
    plt.hist(topic_lengths[topic]['jeju_lengths'], bins=20, alpha=0.5, label=topic)

plt.xlabel('사투리 길이')
plt.ylabel('빈도')
plt.title('주제별 사투리 길이 히스토그램')
plt.legend()
plt.show()

그렇지만.. 이런 결과가 나와서 보여드릴 수 없었습니다.

이 코드도 빨리 고치고 싶은데 위에 코드가 안 끝나요ㅎㅎㅠ

아 그리고 불용어를 제거하는 코드는 끝나서 결과를 보여드릴 수 있습니다.

불용어가 포함된 원문:
[SOS] 그런 걸 이제 그렇게 많이 뽑는 그런 일자리 같은 경우 는 공고 를 계속 띄워주거든 그 걸 로 하면 되는거 [EOS]
불용어가 포함된 번역문:
[SOS] 그런 걸 이제 그렇게 많이 뽑는 그런 일자리 같은 경우 는 공고 를 계속 띄워주거든 그 걸 로 하면 되는거 [EOS]
불용어 제거된 원문:
[ SOS ] 그런 걸 이제 그렇게 많이 뽑는 그런 일자리 같은 경우 는 공고 계속 띄워주거든 걸 하면 되는거 [ EOS ]
불용어 제거된 번역문:
[ SOS ] 그런 걸 이제 그렇게 많이 뽑는 그런 일자리 같은 경우 는 공고 계속 띄워주거든 걸 하면 되는거 [ EOS ]

짠 이렇게 결과가 나왔습니다.

처음에 제대로 적용되지 않은 줄 알고 오잉? 했는데 잘 됐더라고요 

 

계속해서 저는 모델을 돌리기 위해 코드를 열심히 수정하고 있습니다.

근데 모델에서 오류가 나서 머리를 짚게 됐지만..

또 어떻게든 해결해 보겠습니다.

 

마지막으로 제가 만든 제주도 캐릭터입니다.