테크일기

Bert Tokenizer를 이용한 n-gram 만들기

코드아키택트 2025. 1. 16. 22:00
반응형

이름이 참으로 거창해졌다.

회사에서 모종의 이유로 기존에 번역된 파일을 검수해야 하는 일이 생겼다.

여기서 내가 중점을 둔 것은 단어의 통일성을 보는 것이었다.

가령 "soccer" 라는 단어가 있다면 어딘가엔 "축구", 어딘가엔 "공놀이"라고 쓰여있는 것은 아닌지 현재 상황을 파악하는 것이다.

처음에는 인형 눈깔붙이기라고 표현하는 손수 다 보려 했지만 너무 재미없다.

일은 재밌게. 다 까먹어 버린 NLP를 해보기로 했다.

단계는 다음과 같다.

  •  데이터를 전처리한다
    • 엑셀의 각 탭에 나뉜 데이터를 한 군데 모은다
    • 이러한 엑셀파일이 여러개이기 때문에 반복작업한다
    • 여러 엑셀파일이지만 날짜별로 나뉘어 있어 중복으로 들어간 내용들이 있다. 따라서 중복인 경우 최신 날짜의 파일로 덮는다
  • n-gram을 생성한다
    • 영어의 1-gram단어는 한국어로 2-gram단어가 될 수도 3-gram단어가 될 수도 있다
    • 반대의 경우도 마찬가지다
    • 따라서, 우선은 3-gram까지 만든다
  • n-gram끼리의 동시발생 빈도를 구한다
    • 예를들어 "soccer"가 나올 때 번역본에 "축구", "공놀이"가 각각 1번 이상 나오면 soccer에 대한 번역을 다른 단어로 했을 확률이 높다
  • 위에서 구한 내용에 대해 검수한다

 

이게 전체적인 맥락이다. 음 그럼 갑분 내용을 진행해보면 이렇다. 참고로 나는 패션 개발자이기 때문에 각 라이브러리의 문서를 보지 않고, ChatGPT를 사용했다. 내가 집중해야 하는 영역 외에는 기계에 의존하는 것도 방법이 아닐까라고 요즘엔 생각을 고쳐보려 한다.

 

엑셀에 있는 탭 내용 합치기

import os
import pandas as pd


'''
result moved to 01_tab_merged_excels
'''

def find_excel_files(root_folder):
    """
    Traverse the root folder and its subfolders to find Excel files.

    Args:
        root_folder (str): Path to the root folder to start traversal.

    Returns:
        list: List of paths to Excel files found.
    """
    excel_files = []
    for root, dirs, files in os.walk(root_folder):
        for file in files:
            # Check if the file extension matches Excel formats
            if file.endswith(('.xls', '.xlsx', '.xlsm', '.xlsb')):
                excel_files.append(os.path.join(root, file))
    return excel_files

def process_excel_files(files):
    """
    Process Excel files by merging their tabs into one DataFrame per file.

    Args:
        files (list): List of Excel file paths.

    Returns:
        dict: Dictionary with file names as keys and merged DataFrames as values.
    """
    merged_data_dict = {}
    for file in files:
        excel_data = pd.ExcelFile(file)
        merged_data = pd.DataFrame()
        
        for sheet_name in excel_data.sheet_names:
            sheet_data = pd.read_excel(file, sheet_name=sheet_name)
            sheet_data['SheetName'] = sheet_name  # Optional, to track the origin
            merged_data = pd.concat([merged_data, sheet_data], ignore_index=True)
        
        merged_data_dict[file] = merged_data
    return merged_data_dict

# Example usage
root_folder = './Dataset'  # Replace with the folder path
excel_files = find_excel_files(root_folder)

if excel_files:
    print(f"Found {len(excel_files)} Excel files. Processing...")
    merged_data_dict = process_excel_files(excel_files)

    # Save the merged data for each file
    for file, data in merged_data_dict.items():
        output_file = f"{root_folder}/{os.path.splitext(os.path.basename(file))[0]}_merged.xlsx"
        print(output_file)
        data.to_excel(output_file, index=False)
        print(f"Merged data saved to {output_file}")
else:
    print("No Excel files found.")

이렇게 만들어준다. 내가 약간 바꾼 부분도 잇지만 전체적으로 잘 만들어줬다.

 

2단계 : 위에서 만들어진 엑셀들 합치기

위 엑셀을 만들면 여러 엑셀이 만들어진다. 내 원본파일에는 YYYYMMDD 형태로 날짜가 들어가 있었다. 따라서 해당 날짜를 기반으로 중복 데이터가 있는 경우 가장 최신 내용으로 덮어쓰기를 하면 된다. 데이터가 중복인걸 판별하는 것은, 원본 데이터에 있는 두 개의 Column에 같은 내용이 들어가 있는지를 보는 것이다. 

import os
import pandas as pd

def find_excel_files(root_folder):
    """Find all Excel files in the folder and subfolders."""
    excel_files = []
    for root, dirs, files in os.walk(root_folder):
        for file in files:
            if file.endswith(('.xls', '.xlsx', '.xlsm', '.xlsb')):
                excel_files.append(os.path.join(root, file))
    return excel_files

def merge_and_handle_duplicates(files, id_columns):
    """
    Merge Excel files and handle duplicates based on specified columns.

    Args:
        files (list): List of Excel file paths.
        id_columns (list): List of columns used to identify duplicates.

    Returns:
        DataFrame: Merged DataFrame with duplicates handled.
    """
    all_data = pd.DataFrame()

    for file in files:
        # Read each Excel file and append its data
        data = pd.read_excel(file)
        data['SourceFile'] = os.path.basename(file)  # Add a column for the file name
        all_data = pd.concat([all_data, data], ignore_index=True)

    # Extract the date from the file name for sorting (assuming YYYYMMDD format in the name)
    all_data['FileDate'] = all_data['SourceFile'].str.extract(r'(\d{8})', expand=False)

    # Sort by the date (descending) and remove duplicates
    all_data = all_data.sort_values(by=['FileDate'], ascending=False)
    all_data = all_data.drop_duplicates(subset=id_columns, keep='first')

    return all_data

# Example usage
root_folder = './Dataset/01_tab_merged_excels'  # Replace with your folder path
id_columns = ['Resource', 'Number']  # Replace with the columns to identify duplicates

# Find all Excel files
excel_files = find_excel_files(root_folder)

if excel_files:
    print(f"Found {len(excel_files)} Excel files. Merging and handling duplicates...")
    merged_data = merge_and_handle_duplicates(excel_files, id_columns)

    # Save the merged data to a new Excel file
    output_file = 'merged_deduplicated_data.xlsx'
    merged_data.to_excel(output_file, index=False)
    print(f"Merged data saved to {output_file}")
else:
    print("No Excel files found.")

여기서는 regex를 통해 해당 날짜를 추출하는 것을 볼 수 있다. 엄청난 녀석이다. regex... 공부하닥 이해 안돼서 못했던 그것. 시간을 들여 검증을 제대로 하지 못했기 때문에 몇몇 부분에는 오류가 있을 수도 있다.

Tokenize 하기

Tokenize는 쉽게 말해서 영어를 기준으로 띄어쓰기 기준으로 끊는 것이라고 볼 수 있다. 엄밀한 의미에서는 또 트리긴 하지만. 정확한 의미는 의미를 가지는 단어의 최소단위?? 그 정도 될지도 모른다. 조사, 명사, 동사, 부사 등등이 여기에 속한다. Tokenizer에 따라 이러한 품사들이 원형을 찾아서 표시해 줄 건지 아닌지, 현재 문장에서 얘는 목적어인지 주어인지 그런 것들을 쓸 수 있도록 되어있다. 그게 컴퓨터 링귀스틱이란 영역의 일로 알고 있다.

맨 처음엔 Spacy 등 전통의 강자를 써보려 했는데, BERT가 그래도 Transformer의 맏형이니까 한번 써보기로 했다. 아직 이걸 이용해서 본 내용에 대한 Tokenization은 다 하지 못했지만 현재까지 진행도는 이렇다.

import string
from transformers import BertTokenizer
from nltk.util import ngrams

# Load BERT tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# Text to tokenize
text = "I am playing football, and it's fun!"

# Tokenize the text
tokens = tokenizer.tokenize(text)
print("Tokens:", tokens)

# Function to clean tokens (handle subwords and remove punctuation)
def clean_tokens(tokens):
    clean = []
    for token in tokens:
        # Remove punctuation
        if all(char in string.punctuation for char in token):  # Token is punctuation only
            continue
        # Handle subwords
        if token.startswith("##"):  # Subword continuation
            clean[-1] += token[2:]  # Append to the previous token
        else:
            clean.append(token)
    return clean

# Cleaned tokEns
cleaned_tokens = clean_tokens(tokens)
print("Cleaned Tokens:", cleaned_tokens)

# Generate n-grams
def generate_ngrams(tokens, n):
    return list(ngrams(tokens, n))

# Generate 2-grams and 3-grams
two_grams = generate_ngrams(cleaned_tokens, 2)
three_grams = generate_ngrams(cleaned_tokens, 3)

print("2-grams:", [" ".join(gram) for gram in two_grams])
print("3-grams:", [" ".join(gram) for gram in three_grams])

 

결과는

Tokens: ['i', 'am', 'playing', 'football', ',', 'and', 'it', "'", 's', 'fun', '!']
Cleaned Tokens: ['i', 'am', 'playing', 'football', 'and', 'it', 's', 'fun']
2-grams: ['i am', 'am playing', 'playing football', 'football and', 'and it', 'it s', 's fun']
3-grams: ['i am playing', 'am playing football', 'playing football and', 'football and it', 'and it s', 'it s fun']

subword를 안 나오는 설정을 어찌어찌한 거 같은데 그 덕에 작동하는 것 같다

subword란 가령 playing에서 play + ing인데 여기서 ing가 subword에 해당한다. 그래서 옵션을 켜면 play +##ing 형태로 표시된다

아무튼 패션 개발자의 NLP진행은 여기까지 되었다.

다음 단계는 이제 한글에 대한 Tokenization이다.

KoNLTK를 JAVA까지 깔아가며 돌리긴 했었는데, KoBERT 있으면 돌려봐야겠다.

반응형

'테크일기' 카테고리의 다른 글

파라메트릭 모델링 계속  (0) 2025.01.18
KoBERT 사용후기  (0) 2025.01.17
오랜만에 NLP에 시동을 건다  (0) 2025.01.15
이산수학 공부  (0) 2025.01.14
건강은 무척이나 중요하다  (0) 2025.01.11