데이터 분석/머신러닝, 딥러닝

딥러닝 텍스트 전처리

fullfish 2025. 12. 1. 17:14

1. 텍스트 정제(Cleaning) 및 토큰화(Tokenization)

이 단계는 텍스트를 기계가 처리하기 쉬운 단위로 쪼개는 과정입니다.

  • 정제: 불필요한 HTML 태그, 특수 문자, 이모지 등을 제거하거나 표준화합니다.
  • 토큰화: 문장을 의미를 가지는 최소 단위(단어, 형태소, 서브 워드 등)로 분리합니다. (예: "나는 학생이다" -> ['나', '는', '학생', '이다'])

2. 단어(또는 서브 워드) 사전(Vocabulary) 생성

토큰화된 결과물을 바탕으로 전체 데이터셋에 존재하는 고유한 단어들의 집합을 만듭니다.

  • 역할: 모델이 인지할 수 있는 모든 단어들의 목록을 정의합니다.
  • 특징: 빈도수가 낮은 단어는 제거하여 사전 크기를 줄이고, 모르는 단어(OOV, Out-Of-Vocabulary)를 처리하기 위해 <unk> (unknown) 토큰을 추가하기도 합니다.

3. 정수 인코딩 (Integer Encoding)

기계가 단어를 숫자로 인식할 수 있도록, 사전에 있는 각 고유한 단어에 고유한 정수(Index)를 부여하는 단계입니다.

  • 작업: 각 문장을 **"정수 리스트"**로 변경합니다.
  • 예시: 단어 '사과'가 100번 인덱스라면, 문장 속의 '사과'는 숫자 '100'으로 대체됩니다.

4. 패딩 (Padding) 및 마스크 (Mask) 생성

딥러닝 모델, 특히 배치(Batch) 단위로 학습하는 모델은 입력 데이터의 길이가 모두 동일해야 하므로, 길이를 맞추는 과정이 필요합니다.

  • 패딩 (Padding): 모든 문장의 길이를 가장 긴 문장이나 정해진 최대 길이에 맞춥니다. 짧은 문장의 부족한 공간은 보통 0(패딩 토큰)으로 채웁니다.
  • 마스크 (Mask): 패딩으로 인해 추가된 가짜 토큰(0)과 원래의 실제 토큰을 구별하기 위한 정보를 생성합니다. 마스크는 모델에게 "어디까지가 실제 의미 있는 데이터이고, 어디부터가 패딩인지"를 알려주어 불필요한 계산을 막습니다.

5. Dataset / DataLoader 구성

최종적으로 준비된 정수 인코딩 및 패딩된 데이터를 효율적으로 모델에 공급하기 위한 파이프라인을 구축합니다.

  • Dataset: 데이터와 레이블을 묶어 모델이 접근할 수 있는 형태로 정리합니다.
  • DataLoader: Dataset을 모델 학습에 필요한 배치 단위로 묶어 모델에 순차적으로 공급하는 역할을 수행합니다. 이는 메모리 효율을 높이고 병렬 처리를 가능하게 합니다.

 

1. Dataset (데이터셋)

Dataset은 전체 데이터의 샘플에 접근하는 방식을 정의합니다. 즉, 파이썬 리스트처럼 인덱스를 통해 개별 데이터를 꺼내올 수 있도록 준비하는 역할을 합니다.

  • 상속: 반드시 torch.utils.data.Dataset 클래스를 상속받아 사용자 정의 클래스를 생성해야 합니다.
  • 필수 구현 메서드:
    • _ _len _ _ (self): 데이터셋에 포함된 전체 샘플의 개수를 정수 형태로 반환해야 합니다.
    • _ _getitem _ _ (self, idx): 주어진 인덱스(idx)에 해당하는 하나의 샘플 데이터를 반환해야 합니다.
  • 반환 형태: 일반적으로 _ _getitem _ _ 메서드에서는 모델 입력에 필요한 데이터(예: input_ids, attention_mask)와 정답 레이블(label)을 묶어서 텐서 형태로 반환하도록 구성합니다.

2. DataLoader (데이터 로더)

DataLoader는 Dataset에서 정의된 개별 샘플들을 가져와서 실제 모델 학습에 적합한 형태로 변환하고 관리하는 역할을 합니다.

  • 주요 기능:
    • Dataset에서 개별 샘플을 꺼냅니다.
    • 꺼낸 샘플들을 지정된 크기만큼 모아서 **배치(batch)**로 묶어줍니다.
    • 필요에 따라 데이터의 순서를 무작위로 섞는 섞기(shuffle) 기능을 수행합니다.
    • 매 에포크(epoch)마다 전체 데이터를 순회하며 **반복(iteration)**을 도와줍니다.

요약하자면, Dataset은 데이터 그 자체의 접근 방식(무엇을 가져올지)을 정의하고, DataLoader는 데이터를 모델에게 효율적으로 전달하는 방법(어떻게 가져올지)을 정의한다고 이해하시면 됩니다

 

 

import re  # 정규 표현식 모듈 임포트 (텍스트 정제용)
from collections import Counter  # 단어 빈도수 계산을 위한 Counter 임포트
import numpy as np  # numpy 임포트 (여기서는 직접 사용되지 않으나, 관례적으로 임포트)
import torch  # PyTorch 라이브러리 임포트 (텐서 및 모델 학습용)
from torch.utils.data import Dataset, DataLoader  # Dataset과 DataLoader 클래스 임포트
from dl_nlp.common.preprocess import get_encoded_data

# 동적 패딩을 위한 pad_sequence 함수 임포트
from torch.nn.utils.rnn import pad_sequence



# 특수 토큰 정의
PAD_TOKEN = "<PAD>"
UNK_TOKEN = "<UNK>"

# encoded_sentences = [encode(tokens, vocab) for tokens in tokenized_sentences]
encoded_sentences, labels, vocab = get_encoded_data(
    "/Users/choimanseon/Documents/multicampus/example/nlp/movie_reviews",
    "csv",
    "document",
    "label",
    PAD_TOKEN,UNK_TOKEN
)



# --- A. 정적 패딩 (Static Padding) 방식 ---
# 4. 패딩 및 마스크 생성 (PyTorch 사용)
def pad_sequences(encoded_list, max_len, pad_value=0):
    padded = []
    masks = []

    for seq in encoded_list:
        # 1. 길이 초과 처리: 문장 길이가 max_len을 넘으면 자르기 (Trimming)
        if len(seq) > max_len:
            seq = seq[:max_len]

        # 2. 패딩 처리
        pad_len = max_len - len(seq)
        padded_seq = seq + [pad_value] * pad_len

        # 3. 마스크 생성
        mask = [1] * len(seq) + [0] * pad_len

        padded.append(padded_seq)
        masks.append(mask)

    return torch.tensor(padded), torch.tensor(masks)


# 최대 길이 설정 (정적 길이: 6)
max_len = 6

# 패딩 및 마스크 적용
padded_inputs, attention_masks = pad_sequences(
    encoded_sentences, max_len, pad_value=vocab[PAD_TOKEN]
)


# 5. PyTorch Dataset 클래스 정의 (정적 패딩 데이터용)
class ReviewDataSet(Dataset):
    def __init__(self, inputs, masks, labels):
        self.inputs = inputs
        self.masks = masks
        self.labels = torch.tensor(labels, dtype=torch.long)

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, index):
        # 이미 패딩이 완료된 텐서를 반환
        return {
            "input_ids": self.inputs[index],
            "attention_mask": self.masks[index],
            "labels": self.labels[index],
        }


# 6. 정적 패딩 DataLoader 생성 및 확인
dataset = ReviewDataSet(padded_inputs, attention_masks, labels)
loader = DataLoader(
    dataset,
    batch_size=2,
    shuffle=True,
)

batch = next(iter(loader))
print("--- 정적 패딩 DataLoader 확인 결과 ---")
print("input_ids batch shape:", batch["input_ids"].shape)
print("attention_mask batch shape:", batch["attention_mask"].shape)
print("labels batch:", batch["labels"])


# --- B. 동적 패딩 (Dynamic Padding) 방식 ---
# 7. Raw Dataset 클래스 정의 (패딩되지 않은 원본 데이터 보관)
class ReviewRawDataset(Dataset):
    # 인코딩된 시퀀스 리스트와 라벨을 받음
    def __init__(self, encoded_sequences, labels):
        super().__init__()
        # encoded_sequences를 torch.tensor 리스트로 변환하여 저장
        self.encoded_sequences = [
            torch.tensor(seq, dtype=torch.long) for seq in encoded_sequences
        ]
        # 라벨은 텐서로 변환
        self.labels = torch.tensor(labels, dtype=torch.long)

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, index):
        # 패딩되지 않은 원본 길이의 시퀀스와 라벨을 반환
        return self.encoded_sequences[index], self.labels[index]


# 8. collate_fn 정의 (배치 생성 시 동적 패딩 수행)
def collate_fn(batch, pad_value=0):
    # batch: list of (seq_tensor, label) -> zip(*batch)로 시퀀스와 라벨을 분리
    seqs, labels = zip(*batch)  # unzip: 시퀀스 튜플, 라벨 튜플로 분리

    # pad_sequence를 사용하여 현재 배치 내에서 가장 긴 길이에 맞춰 패딩
    padded_seqs = pad_sequence(
        seqs,
        batch_first=True,  # (batch, max_len) 형태로 출력
        padding_value=pad_value,  # 패딩 값은 0
    )

    # 마스크 생성: 패딩 값(0)이 아닌 위치(실제 토큰)는 1, 패딩은 0
    attention_mask = (padded_seqs != pad_value).long()

    # 라벨 튜플을 하나의 텐서로 쌓음
    labels = torch.stack(labels)

    return {
        "input_ids": padded_seqs,
        "attention_mask": attention_mask,
        "labels": labels,
    }


# 9. 동적 패딩 DataLoader 생성 및 확인
raw_dataset = ReviewRawDataset(encoded_sentences, labels)

loader2 = DataLoader(
    raw_dataset,
    batch_size=2,
    shuffle=True,
    # collate_fn 인수에 lambda 함수를 사용하여 동적 패딩 함수 지정
    collate_fn=lambda batch: collate_fn(batch, pad_value=vocab[PAD_TOKEN]),
)

# 배치 하나 꺼내서 확인
batch2 = next(iter(loader2))

print("\n--- 동적 패딩 DataLoader 확인 결과 ---")
# 배치마다 길이가 달라질 수 있음 (최대 길이는 7이 될 수 있음)
print("동적 패딩 input_ids shape:", batch2["input_ids"].shape)
print("동적 패딩 attention_mask:\n", batch2["attention_mask"])
import re
import numpy as np
import pandas as pd
from collections import Counter  # 단어 빈도수 계산을 위한 Counter 임포트


def get_encoded_data(
    file_name: str,
    file_type: str,
    text_col: str,
    label_col: str,
    PAD_TOKEN: str,
    UNK_TOKEN: str,
) -> tuple:
    file_path = rf"{file_name}.{file_type}"

    if file_type == "csv":
        df = pd.read_csv(
            file_path,
            encoding="utf-8",
        )
    elif file_type == "json":
        df = pd.read_json(
            file_path,
            encoding="utf-8",
        )
    else:
        raise ValueError(f"지원하지 않는 파일 형식: .{file_type}")

    df = df.dropna()

    sentences = df[text_col].astype(str).tolist()
    labels = df[label_col].tolist()

    def tokenize(text: str):
        # 정제: 한글/숫자/공백을 제외한 모든 문자(특수문자 등)를 공백으로 대체
        text = re.sub(r"[^가-힣0-9\s]", " ", text)
        # 정제: 연속된 공백을 하나의 공백으로 압축하고 양쪽 끝 공백 제거
        text = re.sub(r"\s+", " ", text).strip()
        # 토큰화: 공백 기준으로 문장을 단어 리스트로 분리
        return text.split()

    # 모든 문장에 토큰화 함수를 적용
    tokenized_sentences = [tokenize(s) for s in sentences]

    # --- 3. 단어 사전(Vocabulary) 생성 ---
    counter = Counter()
    for tokens in tokenized_sentences:
        counter.update(tokens)

    vocab = {PAD_TOKEN: 0, UNK_TOKEN: 1}  # 인덱스 0, 1 할당

    # 빈도수가 높은 단어부터 순서대로 Vocab에 정수 인덱스를 부여
    for word, _ in counter.most_common():
        vocab[word] = len(vocab)

    # --- 4. 정수 인코딩 (Integer Encoding) ---
    def encode(tokens, vocab, unk_token=UNK_TOKEN):
        unk_idx = vocab[unk_token]
        # 각 토큰에 대해 Vocab에서 해당 인덱스를 찾고, 없으면 UNK 인덱스를 사용
        return [vocab.get(t, unk_idx) for t in tokens]

    # 모든 토큰화된 문장에 정수 인코딩을 적용
    encoded_sentences = [encode(tokens, vocab) for tokens in tokenized_sentences]

    # 정수 인코딩된 시퀀스와 라벨을 반환
    return encoded_sentences, labels, vocab

'데이터 분석 > 머신러닝, 딥러닝' 카테고리의 다른 글

시퀀스 모델  (0) 2025.12.05
단어 임베딩  (0) 2025.12.03
K-means Document Clustering  (0) 2025.11.28
Cosine 유사도  (0) 2025.11.28
RNN(Recurrent Neural Networks)  (0) 2025.11.27