목록으로

프로그래밍 · Python

Python 완전정복 시리즈 #36: Hugging Face Datasets 완벽 이해하기

BeanCon
Python에서 Hugging Face Datasets로 AI 학습 데이터를 불러오고 가공하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 36편입니다. Hugging Face Datasets로 AI 학습 데이터를 불러오고 가공하는 방법을 실습 중심으로 정리했습니다. Dataset과 DatasetDict 구조, Features와 ClassLabel, CSV·JSON·Parquet·Text 파일 로딩, Python 객체와 Pandas 변환, shuffle, select, sort, train_test_split, filter, map, batched map, 멀티프로세싱, 열 추가·삭제·이름 변경, Tokenizer 일괄 적용, PyTorch DataLoader 연결, Streaming, 캐시, 로컬 저장, 오프라인 사용, 버전 고정, 비공개 데이터셋, Hub 공유, 데이터셋 카드와 라이선스, 영화 리뷰 데이터 전처리 프로젝트까지 다룹니다.

목차

메타 설명
AI 모델을 학습할 데이터는 어떻게 준비할까요? Hugging Face Datasets를 이용해 공개 데이터셋과 CSV·JSON·Parquet 파일을 불러오고, Dataset과 DatasetDict 구조, map, filter, shuffle, train_test_split, Tokenizer 전처리, Streaming, 캐시, PyTorch DataLoader 연결, 로컬 저장과 Hub 공유 방법까지 실습합니다.

지난 시간에는 Hugging Face Transformers를 이용해 사전 학습 AI 모델을 Python에서 실행했습니다.

Hugging Face Hub에서 모델 검색

→ Transformers로 모델 로딩

→ Tokenizer로 입력 변환

→ 모델 추론

→ 감정 분석·번역·요약 결과 출력

사전 학습 모델을 그대로 사용하는 것만으로도 다양한 AI 기능을 구현할 수 있었습니다.

그런데 모델을 우리 업무에 맞게 추가 학습하려면 새로운 문제가 등장합니다.

모델은 준비되었습니다.

그런데 학습 데이터는 어디에 있나요?

AI 모델은 데이터 없이 학습할 수 없습니다.

고객 리뷰 감정 분석
→ 긍정·부정 리뷰 데이터 필요

스팸 메일 분류
→ 정상·스팸 메일 데이터 필요

고양이·강아지 분류
→ 고양이·강아지 이미지 필요

음성 인식
→ 음성과 정답 문장 필요

챗봇 학습
→ 질문과 답변 대화 데이터 필요

직접 데이터를 관리하려면 다음 작업도 필요합니다.

파일 다운로드

압축 해제

CSV 읽기

열 이름 통일

결측값 제거

라벨 숫자 변환

학습·평가 데이터 분리

Tokenizer 적용

Tensor 변환

배치 생성

전처리 결과 저장

데이터를 학습시키기도 전에 데이터 준비 과정에서 체력이 먼저 학습됩니다.

개발자:
“AI 모델을 학습하겠습니다.”

데이터:
“저를 먼저 정리해 주세요.”

개발자:
“열 이름이 왜 파일마다 다르죠?”

데이터:
“현실 세계에 오신 것을 환영합니다.”

이 복잡한 데이터 작업을 체계적으로 처리할 수 있도록 도와주는 도구가 Hugging Face Datasets입니다.

Hugging Face Datasets

= AI 학습 데이터를 불러오고
  탐색하고
  정리하고
  변환하고
  전달하는 데이터 물류센터

이번 시간에는 데이터가 모델의 입구까지 안전하게 도착하도록 AI 데이터 파이프라인을 만들어 보겠습니다. 📦🤗

1. Hugging Face Datasets란?

Hugging Face Datasets는 텍스트, 이미지, 오디오, 영상 등 AI 학습에 사용하는 데이터를 쉽게 불러오고 처리할 수 있도록 만든 오픈소스 Python 라이브러리입니다.

공개 데이터셋을 Hub에서 한 줄로 내려받을 수 있으며 CSV, JSON, Parquet 같은 로컬 파일도 동일한 방식으로 처리할 수 있습니다. 내부적으로 Apache Arrow 기반의 열 지향 저장 구조를 활용해 대규모 데이터를 효율적으로 다룹니다.

2026년 8월 2일 기준 PyPI의 최신 안정 버전은 Datasets 5.0.1이며, 2026년 7월 28일 공개되었습니다. 현재 패키지는 Python 3.10 이상을 요구합니다.

버전 확인:

import datasets

print(
    datasets.__version__
)

터미널에서 확인:

python -m pip show datasets

2. Datasets 라이브러리가 필요한 이유

일반적으로 CSV 파일은 Pandas로도 불러올 수 있습니다.

import pandas as pd

dataframe = pd.read_csv(
    "reviews.csv"
)

그렇다면 Datasets는 왜 필요할까요?

AI 데이터 중심의 기능

공개 데이터셋 자동 다운로드

Train·Validation·Test Split 관리

텍스트·이미지·오디오 통합 처리

Tokenizer 일괄 적용

대규모 데이터 Streaming

전처리 결과 자동 캐시

PyTorch·TensorFlow 연결

Hub 업로드와 버전 관리

Pandas와의 역할 차이

구분PandasHugging Face Datasets
주요 목적일반 데이터 분석AI 학습 데이터 처리
표 데이터 분석매우 강력함가능
대규모 데이터메모리 부담 가능Arrow·Streaming 지원
Split 관리직접 구성DatasetDict 제공
Tokenizer 연결직접 반복 처리`map()`으로 일괄 처리
이미지·오디오별도 처리 필요전용 Feature 지원
Hub 연동없음내장
PyTorch 연결별도 변환`with_format()` 지원

두 라이브러리는 경쟁 관계가 아닙니다.

Pandas
→ 통계 분석과 표 데이터 가공

Datasets
→ AI 학습 파이프라인과 데이터 전달

필요하면 서로 변환해 함께 사용하면 됩니다.

3. 설치 및 환경 준비

가상환경을 만든 뒤 설치하겠습니다.

Windows

python -m venv venv
venv\Scripts\activate

python -m pip install --upgrade pip
python -m pip install datasets

macOS·Linux

python3 -m venv venv
source venv/bin/activate

python -m pip install --upgrade pip
python -m pip install datasets

Transformers와 PyTorch까지 함께 설치

python -m pip install \
    datasets \
    "transformers[torch]" \
    accelerate

Windows 명령 프롬프트에서는 한 줄로 입력합니다.

python -m pip install datasets "transformers[torch]" accelerate

이미지·영상 기능을 함께 사용할 경우:

python -m pip install "datasets[vision]"

PyTorch 연동용 선택 의존성을 설치할 수도 있습니다.

python -m pip install "datasets[torch]"

Datasets는 기본 설치 외에도 이미지·오디오·PyTorch·TensorFlow 등의 기능을 위한 선택 의존성 설치 방식을 제공합니다.

4. 첫 번째 데이터셋 불러오기

Hugging Face Datasets의 중심 함수는 `load_dataset()`입니다.

from datasets import load_dataset


dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)

print(dataset)

출력 형태:

DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: ...
    })
    validation: Dataset({
        features: ['text', 'label'],
        num_rows: ...
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: ...
    })
})

`load_dataset()`은 Hub의 데이터셋 저장소뿐 아니라 로컬 CSV, JSON, Text, Parquet 파일과 Python 메모리 객체에서도 데이터를 불러올 수 있습니다.

첫 번째 실행에서는 데이터를 내려받고 변환하므로 시간이 걸릴 수 있습니다.

이후에는 캐시를 재사용할 수 있습니다.

첫 실행:

인터넷 다운로드
→ 압축 해제
→ 데이터 변환
→ 캐시 저장

두 번째 실행:

캐시 확인
→ 기존 데이터 재사용

5. Dataset과 DatasetDict 차이

Datasets 라이브러리에서 가장 자주 만나는 객체는 두 가지입니다.

Dataset

하나의 데이터 집합입니다.

train_dataset = dataset[
    "train"
]

print(
    type(train_dataset)
)

개념:

Dataset

= 하나의 표
= 하나의 Split
= 여러 행과 열

DatasetDict

여러 Dataset을 Split 이름으로 묶은 딕셔너리형 객체입니다.

print(
    type(dataset)
)

print(
    dataset.keys()
)

결과 예:

dict_keys([
    'train',
    'validation',
    'test'
])

구조:

DatasetDict
├─ train      → Dataset
├─ validation → Dataset
└─ test       → Dataset

DatasetDict는 단순한 Python 딕셔너리와 비슷하지만 `map()`, `filter()`, `remove_columns()` 같은 변환을 모든 Split에 적용할 수 있습니다.

6. 데이터셋 내부 구조 살펴보기

train_dataset = dataset[
    "train"
]

print(train_dataset)

다음 정보를 확인할 수 있습니다.

features:
열 이름과 타입

num_rows:
데이터 행 개수

기본 속성을 출력해 보겠습니다.

print(
    train_dataset.column_names
)

print(
    train_dataset.num_rows
)

print(
    train_dataset.features
)

데이터셋 상세 정보:

print(
    train_dataset.info
)

설명이나 인용 정보가 등록되어 있다면 다음처럼 확인할 수 있습니다.

print(
    train_dataset.info.description
)

print(
    train_dataset.info.citation
)

print(
    train_dataset.info.homepage
)

모든 데이터셋이 모든 정보를 완벽하게 제공하는 것은 아닙니다.

데이터셋 설명이 비어 있다면 Hub의 Dataset Card를 함께 확인해야 합니다.

7. 행과 열에 접근하기

첫 번째 행

first_example = train_dataset[
    0
]

print(
    first_example
)

결과 예:

{
    'text': '...',
    'label': 1
}

여러 행

examples = train_dataset[
    0:3
]

print(
    examples
)

결과는 열 중심 딕셔너리입니다.

{
    'text': [
        '첫 번째 리뷰',
        '두 번째 리뷰',
        '세 번째 리뷰'
    ],
    'label': [
        1,
        0,
        1
    ]
}

특정 열

texts = train_dataset[
    "text"
]

print(
    texts[:3]
)

반복문

for example in train_dataset.select(
    range(3)
):
    print(
        example["text"]
    )

전체 데이터를 무작정 출력하면 터미널이 리뷰 폭포에 휩쓸립니다.

처음에는 몇 개만 확인하는 습관이 좋습니다.

8. Dataset Features 이해하기

`Features`는 데이터셋의 열 이름과 자료형을 정의하는 구조입니다.

print(
    train_dataset.features
)

출력 예:

{
    'text': Value('string'),
    'label': ClassLabel(
        names=[
            'neg',
            'pos'
        ]
    )
}

Features는 데이터셋 내부 직렬화 구조를 정의하며 열 이름, 데이터 타입, ClassLabel 같은 고수준 정보를 포함합니다.

대표적인 Feature:

Value
→ 문자열·정수·실수·Boolean

ClassLabel
→ 분류 라벨

Image
→ 이미지

Audio
→ 오디오

Video
→ 영상

Sequence
→ 리스트나 연속 데이터

Features는 데이터셋의 주민등록표와 비슷합니다.

text 열:
문자열입니다.

label 열:
분류 라벨입니다.

image 열:
이미지입니다.

열마다 신분을 확실히 기록해 둡니다.

9. ClassLabel 활용하기

숫자 라벨만 보면 의미를 알기 어렵습니다.

0
1

ClassLabel이 있다면 숫자와 클래스 이름을 변환할 수 있습니다.

label_feature = (
    train_dataset
    .features["label"]
)


print(
    label_feature.names
)

결과 예:

['neg', 'pos']

숫자를 이름으로 변환:

label_name = (
    label_feature
    .int2str(1)
)

print(
    label_name
)

결과:

pos

이름을 숫자로 변환:

label_id = (
    label_feature
    .str2int("neg")
)

print(
    label_id
)

데이터에 라벨 이름 추가

def add_label_name(
    example
):
    return {
        "label_name": (
            label_feature
            .int2str(
                example["label"]
            )
        )
    }


train_with_names = (
    train_dataset.map(
        add_label_name
    )
)

확인:

print(
    train_with_names[0]
)

10. Split 불러오기

전체 DatasetDict가 아니라 특정 Split만 불러올 수 있습니다.

from datasets import load_dataset


train_dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    split="train"
)

이번에는 반환값이 Dataset입니다.

print(
    type(train_dataset)
)
datasets.arrow_dataset.Dataset

Validation Split:

validation_dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    split="validation"
)

특정 Split을 지정하면 필요하지 않은 Split을 코드에서 별도로 선택하지 않아도 됩니다. `split`과 `data_files`를 이용해 원하는 데이터 범위를 지정할 수 있습니다.

11. 데이터 일부만 불러오기

Split의 일부 범위를 지정할 수 있습니다.

처음 100개

sample_dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    split="train[:100]"
)

처음 10%

sample_dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    split="train[:10%]"
)

10%부터 20%

sample_dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    split="train[10%:20%]"
)

프로토타입에서는 전체 데이터를 받을 필요가 없습니다.

기능 검증 단계:
100개

전처리 검증:
1,000개

최종 학습:
전체 데이터

처음부터 거대한 데이터셋을 내려받는 것은 이삿짐 창고를 통째로 빌린 뒤 연필 한 자루를 찾는 것과 비슷합니다.

12. CSV 파일 불러오기

로컬 CSV:

from datasets import load_dataset


dataset = load_dataset(
    "csv",
    data_files="reviews.csv"
)

print(dataset)

Split을 직접 지정하지 않으면 일반적으로 `train` Split으로 구성됩니다.

train_dataset = dataset[
    "train"
]

여러 CSV 파일

dataset = load_dataset(
    "csv",
    data_files=[
        "reviews_01.csv",
        "reviews_02.csv"
    ]
)

Split별 파일 지정

data_files = {
    "train": "train.csv",
    "validation": "validation.csv",
    "test": "test.csv"
}


dataset = load_dataset(
    "csv",
    data_files=data_files
)

Datasets는 Hub 데이터뿐 아니라 로컬과 원격의 CSV, JSON, Text, Parquet 파일도 `load_dataset()`으로 불러올 수 있습니다.

구분자 지정

dataset = load_dataset(
    "csv",
    data_files="reviews.tsv",
    delimiter="\t"
)

13. JSON·JSONL 파일 불러오기

JSON Lines

`reviews.jsonl`:

{"text": "정말 좋은 제품입니다.", "label": 1}
{"text": "다시는 구매하지 않겠습니다.", "label": 0}

불러오기:

dataset = load_dataset(
    "json",
    data_files="reviews.jsonl"
)

JSON 배열

[
    {
        "text": "좋은 제품입니다.",
        "label": 1
    },
    {
        "text": "배송이 너무 느립니다.",
        "label": 0
    }
]

불러오기:

dataset = load_dataset(
    "json",
    data_files="reviews.json"
)

중첩 필드 지정

JSON 내부에 데이터 배열이 특정 필드 아래 있다면 `field`를 지정합니다.

{
    "metadata": {
        "version": "1.0"
    },
    "data": [
        {
            "text": "리뷰 A",
            "label": 1
        },
        {
            "text": "리뷰 B",
            "label": 0
        }
    ]
}
dataset = load_dataset(
    "json",
    data_files="reviews.json",
    field="data"
)

14. Parquet·Text 파일 불러오기

Parquet

dataset = load_dataset(
    "parquet",
    data_files="reviews.parquet"
)

Parquet는 열 지향 형식이므로 대규모 표 데이터를 효율적으로 저장하고 필요한 열을 읽는 데 유리합니다.

Text

텍스트 파일의 각 줄을 하나의 데이터로 불러옵니다.

dataset = load_dataset(
    "text",
    data_files="sentences.txt"
)

결과:

{
    'text': '첫 번째 문장'
}

여러 파일:

dataset = load_dataset(
    "text",
    data_files={
        "train": "train.txt",
        "test": "test.txt"
    }
)

Datasets 5.0.1은 CSV, JSON, JSONL, Parquet, Arrow, XML, Text, WebDataset를 비롯한 여러 데이터 형식과 텍스트·이미지·오디오·영상 등의 멀티모달 데이터를 지원합니다.

15. Python 객체에서 Dataset 만들기

Hub나 파일 없이 Python 데이터로 Dataset을 만들 수 있습니다.

딕셔너리

from datasets import Dataset


data = {
    "text": [
        "Python은 재미있습니다.",
        "오류가 발생했습니다.",
        "모델 학습이 완료되었습니다."
    ],
    "label": [
        1,
        0,
        1
    ]
}


dataset = Dataset.from_dict(
    data
)

print(dataset)

딕셔너리 리스트

records = [
    {
        "text": "좋은 제품",
        "label": 1
    },
    {
        "text": "배송 지연",
        "label": 0
    }
]


dataset = Dataset.from_list(
    records
)

Generator

def generate_examples():
    for index in range(100):
        yield {
            "id": index,
            "text": (
                f"예제 문장 {index}"
            )
        }


dataset = Dataset.from_generator(
    generate_examples
)

Datasets는 딕셔너리, 리스트, Pandas DataFrame, Generator 등 Python 메모리 객체로부터 Dataset을 생성할 수 있습니다.

16. Pandas와 Dataset 변환

Pandas에서 Dataset으로

import pandas as pd

from datasets import Dataset


dataframe = pd.DataFrame({
    "text": [
        "긍정 리뷰",
        "부정 리뷰"
    ],
    "label": [
        1,
        0
    ]
})


dataset = Dataset.from_pandas(
    dataframe,
    preserve_index=False
)

`preserve_index=False`를 사용하면 Pandas 인덱스가 별도 열로 추가되는 것을 방지할 수 있습니다.

Dataset에서 Pandas로

dataframe = dataset.to_pandas()

print(
    dataframe.head()
)

특정 범위만 변환하는 것이 안전할 수 있습니다.

sample_dataframe = (
    dataset
    .select(
        range(
            min(
                1000,
                len(dataset)
            )
        )
    )
    .to_pandas()
)

대형 Dataset 전체를 Pandas로 바꾸면 메모리가 크게 필요할 수 있습니다.

Dataset:
디스크 기반 Arrow 데이터 활용 가능

Pandas DataFrame:
전체 데이터를 메모리에 올릴 수 있음

17. 데이터 순서 섞기

학습 전에 데이터 순서를 무작위로 섞는 경우가 많습니다.

shuffled_dataset = (
    train_dataset.shuffle(
        seed=2026
    )
)

`seed`를 고정하면 같은 환경에서 같은 순서를 재현하기 쉽습니다.

first_shuffle = (
    train_dataset.shuffle(
        seed=2026
    )
)

second_shuffle = (
    train_dataset.shuffle(
        seed=2026
    )
)
같은 seed

→ 같은 순서

다른 seed

→ 다른 순서

데이터를 섞지 않으면 클래스가 순서대로 몰려 있을 수 있습니다.

앞부분:
부정 리뷰 5,000개

뒷부분:
긍정 리뷰 5,000개

이 상태로 앞부분만 학습에 사용하면 모델은 세상이 온통 부정적이라고 배울 수 있습니다.

18. 일부 행 선택하기

`select()`는 행의 인덱스를 기준으로 새 Dataset을 만듭니다.

처음 100개

small_dataset = train_dataset.select(
    range(100)
)

원하는 인덱스

selected_dataset = (
    train_dataset.select(
        [
            0,
            10,
            20,
            30
        ]
    )
)

무작위 표본

sample_dataset = (
    train_dataset
    .shuffle(
        seed=2026
    )
    .select(
        range(100)
    )
)

안전하게 범위 제한

sample_size = min(
    100,
    len(train_dataset)
)


sample_dataset = train_dataset.select(
    range(sample_size)
)

19. 데이터 정렬하기

특정 열 기준으로 정렬합니다.

먼저 리뷰 길이 열을 추가합니다.

def add_text_length(
    example
):
    return {
        "text_length": len(
            example["text"]
        )
    }


dataset_with_length = (
    train_dataset.map(
        add_text_length
    )
)

짧은 순서:

sorted_dataset = (
    dataset_with_length.sort(
        "text_length"
    )
)

긴 순서:

sorted_dataset = (
    dataset_with_length.sort(
        "text_length",
        reverse=True
    )
)

가장 긴 리뷰 확인:

print(
    sorted_dataset[0]
)

20. 학습·평가 데이터 분리

하나의 Dataset을 Train과 Test로 나눌 수 있습니다.

split_dataset = (
    train_dataset
    .train_test_split(
        test_size=0.2,
        seed=2026
    )
)

결과:

DatasetDict({
    train: Dataset(...)
    test: Dataset(...)
})

개수 확인:

print(
    len(
        split_dataset["train"]
    )
)

print(
    len(
        split_dataset["test"]
    )
)

라벨 비율을 유지해 분리

ClassLabel 형식의 라벨 열이라면 계층화 분할을 사용할 수 있습니다.

split_dataset = (
    train_dataset
    .train_test_split(
        test_size=0.2,
        seed=2026,
        stratify_by_column="label"
    )
)
원본:

긍정 50%
부정 50%

분리 후 Train:

긍정 50%
부정 50%

분리 후 Test:

긍정 50%
부정 50%

분류 데이터에서는 라벨 비율이 지나치게 달라지지 않는지 확인해야 합니다.

21. filter로 필요한 데이터만 남기기

`filter()`는 조건을 만족하는 행만 남깁니다.

긍정 리뷰

positive_dataset = (
    train_dataset.filter(
        lambda example: (
            example["label"] == 1
        )
    )
)

길이가 30자 이상인 리뷰

long_review_dataset = (
    train_dataset.filter(
        lambda example: (
            len(
                example["text"]
            ) >= 30
        )
    )
)

빈 문장 제거

clean_dataset = (
    train_dataset.filter(
        lambda example: bool(
            example["text"].strip()
        )
    )
)

여러 조건

filtered_dataset = (
    train_dataset.filter(
        lambda example: (
            bool(
                example["text"].strip()
            )
            and len(
                example["text"]
            ) <= 1000
            and example["label"]
            in {
                0,
                1
            }
        )
    )
)

Datasets의 처리 API는 행 재배치, Split 생성, 열 변경, `map()`과 `filter()`를 이용한 데이터 가공 등을 제공합니다.

22. map으로 데이터 가공하기

`map()`은 각 데이터에 함수를 적용합니다.

새 열 추가

def add_uppercase_text(
    example
):
    return {
        "uppercase_text": (
            example["text"].upper()
        )
    }


processed_dataset = (
    train_dataset.map(
        add_uppercase_text
    )
)

기존 열 수정

def clean_text(
    example
):
    cleaned_text = (
        example["text"]
        .strip()
        .replace(
            "\n",
            " "
        )
    )

    return {
        "text": cleaned_text
    }


cleaned_dataset = (
    train_dataset.map(
        clean_text
    )
)

여러 열 추가

def analyze_text(
    example
):
    text = example["text"]

    return {
        "character_count": len(text),
        "word_count": len(
            text.split()
        )
    }


analyzed_dataset = (
    train_dataset.map(
        analyze_text
    )
)

`map()`은 각 Example 또는 Batch에 전처리 함수를 적용하고 새로운 행이나 열을 생성할 수 있는 핵심 처리 기능입니다.

23. batched map으로 빠르게 처리하기

기본 `map()`은 한 번에 한 행을 함수로 전달합니다.

def process_one(
    example
):
    ...

`batched=True`를 사용하면 여러 행이 열별 리스트로 전달됩니다.

def process_batch(
    batch
):
    texts = batch[
        "text"
    ]

    return {
        "text_length": [
            len(text)
            for text in texts
        ]
    }


processed_dataset = (
    train_dataset.map(
        process_batch,
        batched=True
    )
)

Batch 구조:

{
    "text": [
        "첫 번째 리뷰",
        "두 번째 리뷰",
        "세 번째 리뷰"
    ],
    "label": [
        1,
        0,
        1
    ]
}

Batch 크기 지정

processed_dataset = (
    train_dataset.map(
        process_batch,
        batched=True,
        batch_size=1000
    )
)

Tokenizer처럼 배치 처리를 지원하는 함수는 `batched=True`를 사용하면 효율적인 경우가 많습니다.

한 명씩 택배 포장

보다

1,000개씩 묶음 포장

데이터 물류센터에서도 단체 주문이 빠를 수 있습니다.

24. 여러 CPU 코어 사용하기

`num_proc`를 지정하면 여러 프로세스를 이용해 전처리할 수 있습니다.

processed_dataset = (
    train_dataset.map(
        process_batch,
        batched=True,
        num_proc=4
    )
)

filter에서도 사용할 수 있습니다.

filtered_dataset = (
    train_dataset.filter(
        lambda example: (
            len(
                example["text"]
            ) > 20
        ),
        num_proc=4
    )
)

Datasets는 `map(num_proc=N)` 방식으로 멀티프로세싱 기반 데이터 처리를 지원합니다.

주의할 점

전처리 함수는 멀티프로세스에서 직렬화할 수 있어야 합니다.

권장:

def preprocess(
    batch
):
    ...

피해야 할 가능성이 있는 형태:

processed = dataset.map(
    lambda batch: (
        복잡한_지역_객체를_참조
    ),
    num_proc=4
)

Windows에서는 멀티프로세스 코드가 다음 블록 안에서 실행되도록 구성해야 하는 경우가 있습니다.

if __name__ == "__main__":
    main()

CPU 코어를 많이 지정한다고 무조건 빨라지는 것도 아닙니다.

프로세스 증가

→ 병렬 처리 증가 가능

하지만

→ 메모리 사용 증가
→ 프로세스 통신 비용 증가
→ 저장 장치 병목 가능

25. 열 추가·수정·삭제·이름 변경

열 추가

review_ids = list(
    range(
        len(train_dataset)
    )
)


dataset_with_id = (
    train_dataset.add_column(
        "review_id",
        review_ids
    )
)

열 이름 변경

renamed_dataset = (
    train_dataset.rename_column(
        "text",
        "review_text"
    )
)

여러 열 이름 변경:

renamed_dataset = (
    train_dataset.rename_columns({
        "text": "review_text",
        "label": "sentiment"
    })
)

열 삭제

text_only_dataset = (
    train_dataset.remove_columns(
        [
            "label"
        ]
    )
)

필요한 열만 선택

selected_dataset = (
    train_dataset.select_columns(
        [
            "text",
            "label"
        ]
    )
)

전용 `remove_columns()`, `rename_column()`, `select_columns()`는 열 구조를 직접 변경하며 모든 남은 데이터를 `map()`으로 다시 복사하는 방식보다 효율적일 수 있습니다.

26. 데이터 타입 변경하기

잘못 추론된 열 타입을 변경해야 할 수 있습니다.

from datasets import Value


dataset = dataset.cast_column(
    "label",
    Value(
        "int64"
    )
)

ClassLabel로 변경:

from datasets import ClassLabel


label_feature = ClassLabel(
    names=[
        "negative",
        "positive"
    ]
)


dataset = dataset.cast_column(
    "label",
    label_feature
)

전체 Features를 정의할 수도 있습니다.

from datasets import (
    ClassLabel,
    Features,
    Value
)


features = Features({
    "text": Value(
        "string"
    ),
    "label": ClassLabel(
        names=[
            "negative",
            "positive"
        ]
    )
})


dataset = dataset.cast(
    features
)

원본 데이터가 실제로 변환 가능한 값인지 확인해야 합니다.

"positive"

→ 바로 int64 변환 불가

"1"

→ 상황에 따라 숫자 변환 가능

데이터 타입은 이름표만 바꾼다고 내용까지 자동으로 달라지는 것은 아닙니다.

27. DatasetDict 전체 처리하기

DatasetDict에 `map()`을 적용하면 모든 Split에 같은 처리를 적용할 수 있습니다.

def add_length(
    example
):
    return {
        "text_length": len(
            example["text"]
        )
    }


processed_dataset = dataset.map(
    add_length
)

결과:

train:
text_length 추가

validation:
text_length 추가

test:
text_length 추가

열 삭제도 전체 Split에 적용됩니다.

text_dataset = (
    dataset.remove_columns(
        "label"
    )
)

특정 Split만 처리하려면 명시적으로 선택합니다.

processed_train = (
    dataset["train"].map(
        add_length
    )
)

DatasetDict는 Split 이름과 Dataset 객체의 매핑이며 데이터 변환 메서드를 전체 Split에 적용할 수 있습니다.

28. 데이터셋 합치기

세로 방향 연결

행을 이어 붙입니다.

from datasets import (
    concatenate_datasets
)


combined_dataset = (
    concatenate_datasets([
        dataset_a,
        dataset_b
    ])
)

두 Dataset의 열 구조와 타입이 호환되어야 합니다.

Dataset A:
text, label

Dataset B:
text, label

→ 연결 가능
Dataset A:
text, label

Dataset B:
sentence, category

→ 열 이름 정리 필요

여러 데이터셋 번갈아 섞기

from datasets import (
    interleave_datasets
)


mixed_dataset = interleave_datasets(
    [
        dataset_a,
        dataset_b
    ]
)

비율 지정:

mixed_dataset = interleave_datasets(
    [
        dataset_a,
        dataset_b
    ],
    probabilities=[
        0.7,
        0.3
    ],
    seed=2026
)

여러 데이터 출처를 학습에 섞을 때 활용할 수 있습니다.

단, 데이터 출처별 품질과 라이선스를 함께 관리해야 합니다.

29. Tokenizer 일괄 적용

Transformers 모델을 학습하려면 텍스트를 토큰 ID로 변환해야 합니다.

from transformers import (
    AutoTokenizer
)


MODEL_ID = (
    "distilbert-base-uncased"
)


tokenizer = (
    AutoTokenizer
    .from_pretrained(
        MODEL_ID
    )
)

전처리 함수:

def tokenize_batch(
    batch
):
    return tokenizer(
        batch["text"],
        truncation=True,
        max_length=256
    )

DatasetDict 전체 처리:

tokenized_dataset = dataset.map(
    tokenize_batch,
    batched=True
)

확인:

print(
    tokenized_dataset[
        "train"
    ][0]
)

새로운 열:

text

label

input_ids

attention_mask

Datasets의 `map()`과 Transformers의 Tokenizer를 연결하면 전체 데이터셋을 배치 단위로 토큰화할 수 있습니다.

원본 텍스트 열 삭제

tokenized_dataset = dataset.map(
    tokenize_batch,
    batched=True,
    remove_columns=[
        "text"
    ]
)

원본 문장을 나중에 오류 분석에 사용해야 한다면 삭제하지 않는 편이 좋습니다.

학습 속도와 저장 공간:
원본 열 삭제가 유리할 수 있음

오류 분석과 추적:
원본 열 보존이 유리

30. PyTorch Tensor 형식으로 변환

기본 Dataset은 Python 객체를 반환합니다.

print(
    type(
        tokenized_dataset[
            "train"
        ][0]["input_ids"]
    )
)

PyTorch Tensor로 반환하도록 설정합니다.

torch_dataset = (
    tokenized_dataset[
        "train"
    ].with_format(
        "torch"
    )
)

확인:

example = torch_dataset[
    0
]

print(
    type(
        example["input_ids"]
    )
)

결과:

torch.Tensor

특정 열만 변환:

torch_dataset = (
    tokenized_dataset[
        "train"
    ].with_format(
        "torch",
        columns=[
            "input_ids",
            "attention_mask",
            "label"
        ]
    )
)

Datasets는 `with_format("torch")`를 이용해 반환 데이터를 PyTorch Tensor로 변환할 수 있으며 Arrow 기반 데이터에서 효율적으로 Tensor를 생성합니다.

31. DataLoader와 연결하기

텍스트 길이가 서로 다르므로 Batch마다 동적으로 Padding하는 Data Collator를 사용하겠습니다.

from torch.utils.data import (
    DataLoader
)

from transformers import (
    DataCollatorWithPadding
)


data_collator = (
    DataCollatorWithPadding(
        tokenizer=tokenizer,
        return_tensors="pt"
    )
)

DataLoader:

train_loader = DataLoader(
    tokenized_dataset["train"],
    batch_size=16,
    shuffle=True,
    collate_fn=data_collator
)

첫 Batch 확인:

batch = next(
    iter(
        train_loader
    )
)


for key, value in (
    batch.items()
):
    print(
        key,
        value.shape
    )

출력 예:

input_ids
torch.Size([16, 현재_Batch_최대길이])

attention_mask
torch.Size([16, 현재_Batch_최대길이])

labels
torch.Size([16])

Datasets는 PyTorch DataLoader와 직접 연결할 수 있으며 `with_format()`이나 Data Collator를 활용해 학습용 Tensor Batch를 생성할 수 있습니다.

동적 Padding의 장점

전체 데이터 최대 길이:
512

현재 Batch 최대 길이:
84

동적 Padding:
84까지만 Padding

불필요한 계산과 메모리 사용을 줄일 수 있습니다.

32. Streaming으로 대용량 데이터 처리

매우 큰 데이터셋은 전체 다운로드가 어려울 수 있습니다.

from datasets import load_dataset


streaming_dataset = load_dataset(
    "HuggingFaceFW/fineweb",
    split="train",
    streaming=True
)

일부 데이터만 확인:

for example in (
    streaming_dataset.take(3)
):
    print(
        example
    )

Streaming은 전체 데이터셋을 먼저 내려받지 않고 반복하는 시점에 데이터를 순차적으로 읽습니다. 디스크보다 데이터셋이 크거나 일부 Sample만 빠르게 확인하고 싶을 때 유용합니다.

일반 모드:

전체 다운로드
→ 변환
→ 사용

Streaming 모드:

필요한 데이터를
→ 그때그때 읽음

장점

전체 다운로드 대기 감소

디스크 공간 절약

거대 데이터셋 빠른 탐색

학습과 동시에 데이터 읽기

단점

임의 인덱스 접근 제한

정확한 전체 Shuffle 어려움

네트워크 영향

매 Epoch 다시 읽기 가능

일부 변환 기능 차이

33. IterableDataset 이해하기

Streaming 모드에서 반환되는 객체는 보통 `IterableDataset`입니다.

print(
    type(
        streaming_dataset
    )
)

Map-style Dataset:

dataset[100]

IterableDataset:

for example in dataset:
    ...

차이:

구분DatasetIterableDataset
인덱스 접근가능일반적으로 불가
길이 확인가능모를 수 있음
Shuffle전체 기준 가능Buffer 기반 근사
처리 시점미리 처리·캐시 가능반복 중 실시간 처리
대형 데이터디스크 필요Streaming 가능

기존 Dataset을 IterableDataset으로 변환할 수도 있습니다.

iterable_dataset = (
    train_dataset
    .to_iterable_dataset(
        num_shards=16
    )
)

로컬 Dataset을 `to_iterable_dataset()`으로 변환하면 로컬 파일에서 직접 Streaming하므로 처음부터 원격 Streaming으로 로드하는 것보다 빠를 수 있습니다.

34. Streaming 데이터 섞기

전체 데이터를 메모리에 올리지 않으므로 완전한 Shuffle 대신 Buffer를 사용합니다.

shuffled_dataset = (
    streaming_dataset.shuffle(
        seed=2026,
        buffer_size=10_000
    )
)

동작 개념:

앞에서 10,000개를 Buffer에 보관

→ Buffer 안에서 무작위 선택

→ 선택된 자리를 다음 데이터로 채움

→ 반복

`buffer_size`가 클수록 전체 Shuffle에 가까워질 수 있지만 메모리 사용량도 증가합니다. IterableDataset의 Shuffle은 Shard 순서를 섞고 Buffer에서 Sample을 무작위로 선택하는 근사 방식입니다.

Epoch마다 다른 순서:

for epoch in range(3):
    shuffled_dataset.set_epoch(
        epoch
    )

    for example in (
        shuffled_dataset
    ):
        ...

35. 캐시의 동작 원리

Datasets는 다운로드한 데이터와 전처리 결과를 캐시합니다.

processed_dataset = dataset.map(
    preprocess
)

첫 실행:

전처리 함수 실행

→ 새로운 Arrow 캐시 파일 생성

동일한 데이터와 동일한 함수·옵션으로 다시 실행:

캐시 Fingerprint 확인

→ 기존 처리 결과 재사용

Datasets의 주요 변환 결과는 데이터셋 상태와 함수 인수를 기반으로 Fingerprint를 생성해 캐시에 저장하며 동일한 연산에서는 기존 결과를 재사용할 수 있습니다.

강제로 다시 처리

processed_dataset = dataset.map(
    preprocess,
    load_from_cache_file=False
)

전처리 함수를 수정했는데 예상과 다른 결과가 계속 나온다면 캐시 여부를 확인합니다.

다만 불필요하게 캐시를 끄면 매번 전처리를 다시 실행하게 됩니다.

36. 캐시 위치 변경과 정리

캐시 경로 지정

dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes",
    cache_dir=(
        "D:/huggingface_cache/"
        "datasets"
    )
)

환경변수

Windows PowerShell

$env:HF_HOME="D:\huggingface_cache"

macOS·Linux

export HF_HOME="/data/huggingface_cache"

Dataset 캐시 전용 경로:

Windows PowerShell

$env:HF_DATASETS_CACHE="D:\huggingface_cache\datasets"

macOS·Linux

export HF_DATASETS_CACHE="/data/huggingface_cache/datasets"

사용 중인 캐시 파일 확인

print(
    train_dataset.cache_files
)

오래된 변환 캐시 정리

removed_count = (
    train_dataset
    .cleanup_cache_files()
)

print(
    removed_count
)

다른 프로세스가 캐시를 사용하는 동안 무작정 삭제하면 문제가 발생할 수 있습니다.

캐시 정리 전:

실행 중인 학습 확인

디스크 사용량 확인

필요한 로컬 데이터 확인

백업 여부 확인

37. 데이터셋 로컬 저장

전처리가 끝난 Dataset을 로컬 폴더에 저장할 수 있습니다.

tokenized_dataset.save_to_disk(
    "processed_data/"
    "rotten_tomatoes_tokenized"
)

다시 불러오기:

from datasets import (
    load_from_disk
)


loaded_dataset = (
    load_from_disk(
        "processed_data/"
        "rotten_tomatoes_tokenized"
    )
)

`save_to_disk()`로 저장한 Dataset 또는 DatasetDict는 `load_from_disk()`로 다시 불러올 수 있습니다.

주의

`load_dataset()`과 `load_from_disk()`는 용도가 다릅니다.

CSV·JSON·Hub 데이터셋:
load_dataset()

save_to_disk 결과:
load_from_disk()

잘못된 방식:

load_dataset(
    "processed_data/"
    "rotten_tomatoes_tokenized"
)

올바른 방식:

load_from_disk(
    "processed_data/"
    "rotten_tomatoes_tokenized"
)

38. CSV·JSON·Parquet로 내보내기

CSV

train_dataset.to_csv(
    "train_reviews.csv",
    index=False
)

JSON Lines

train_dataset.to_json(
    "train_reviews.jsonl"
)

Parquet

train_dataset.to_parquet(
    "train_reviews.parquet"
)

Python 딕셔너리

data = train_dataset.to_dict()

Pandas DataFrame

dataframe = (
    train_dataset.to_pandas()
)

Datasets는 처리 결과를 CSV, JSON, Parquet와 Python·Pandas 객체 등으로 내보낼 수 있습니다.

39. 오프라인 환경에서 사용하기

인터넷 환경에서 데이터셋을 준비합니다.

dataset = load_dataset(
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)

dataset.save_to_disk(
    "offline_rotten_tomatoes"
)

저장 폴더를 폐쇄망으로 전달합니다.

오프라인 환경:

from datasets import (
    load_from_disk
)


dataset = load_from_disk(
    "offline_rotten_tomatoes"
)

환경변수:

Windows PowerShell

$env:HF_HUB_OFFLINE="1"

macOS·Linux

export HF_HUB_OFFLINE=1

Tokenizer도 함께 사용한다면 모델과 Tokenizer를 별도로 저장해야 합니다.

데이터셋만 전달

→ 데이터는 사용 가능

하지만

Tokenizer가 Hub에 접속 시도
→ 오프라인 오류

폐쇄망 패키지 설치를 위해 필요한 Wheel 파일도 함께 준비해야 합니다.

40. 데이터셋 버전 고정

Hub의 데이터셋 저장소는 시간이 지나면서 변경될 수 있습니다.

`revision`으로 Branch, Tag 또는 Commit Hash를 지정할 수 있습니다.

dataset = load_dataset(
    "조직명/데이터셋명",
    revision="검증한_Commit_Hash"
)

Hub의 데이터셋은 Git Tag, Branch, Commit으로 여러 버전을 가질 수 있으며 `revision`으로 사용할 버전을 선택할 수 있습니다.

운영 프로젝트에서는 다음 정보를 기록합니다.

데이터셋 ID

Configuration 이름

Split

Revision

Datasets 버전

전처리 코드 버전

Tokenizer ID와 Revision

Random Seed
“지난달과 같은 데이터셋입니다.”

라는 문장만으로는 부족합니다.

정말 같은 Commit인지 확인해야 합니다.

41. 비공개 데이터셋 사용하기

먼저 Hugging Face 계정으로 로그인합니다.

hf auth login

비공개 데이터셋 로딩:

dataset = load_dataset(
    "사용자명/"
    "private_dataset",
    token=True
)

환경변수나 로그인 캐시에 저장된 토큰을 사용할 수 있습니다.

토큰을 코드에 직접 작성하지 않습니다.

# 피해야 할 방식
dataset = load_dataset(
    "private_dataset",
    token="hf_실제토큰"
)

비공개 데이터셋은 소유자와 권한이 있는 조직 구성원만 접근할 수 있습니다.

42. 데이터셋을 Hub에 공유하기

로그인:

hf auth login

업로드:

processed_dataset.push_to_hub(
    "사용자명/"
    "processed_reviews"
)

비공개 저장소:

processed_dataset.push_to_hub(
    "사용자명/"
    "processed_reviews",
    private=True
)

Dataset이나 DatasetDict의 `push_to_hub()`을 이용해 처리한 데이터와 Split을 Hub 저장소에 업로드할 수 있습니다.

업로드 전 확인

개인정보가 포함되어 있는가?

저작권이 있는 데이터인가?

재배포가 허용되는가?

라이선스는 무엇인가?

민감한 내부 정보가 있는가?

데이터 출처를 기록했는가?

익명화가 충분한가?

`push_to_hub()` 버튼은 빠르지만 데이터 유출도 빠를 수 있습니다.

업로드 전 검토가 반드시 필요합니다.

43. 데이터셋 카드와 라이선스

Dataset Card는 데이터에 대한 설명서입니다.

포함해야 할 내용:

데이터셋 목적

데이터 출처

수집 방법

열 구조

라벨 의미

Train·Validation·Test 구성

개인정보 처리

라이선스

알려진 편향

사용 제한

업데이트 기록

Hub에서 데이터셋을 공유하면 Dataset Viewer, 버전 기록, Dataset Card를 이용해 데이터를 문서화하고 탐색할 수 있습니다.

데이터 행 수만 많다고 좋은 데이터셋은 아닙니다.

100만 건

하지만

라벨 기준 불명확

중복 데이터 다수

개인정보 포함

출처 불명

라이선스 미확인

이 데이터는 대형 창고지만 재고표가 없는 상태입니다.

44. 실전 프로젝트 준비

이번에는 영화 리뷰 감정 분석용 데이터를 불러와 Transformers 학습에 사용할 수 있는 형태로 준비하겠습니다.

프로젝트 목표

공개 영화 리뷰 데이터 로딩

→ 데이터 구조 확인

→ 빈 리뷰 제거

→ 텍스트 정제

→ 리뷰 길이 추가

→ Tokenizer 적용

→ PyTorch DataLoader 생성

→ Batch 검사

→ 전처리 데이터 저장

프로젝트 구조

review_dataset_project/
├─ prepare_dataset.py
└─ processed_data/

설치

python -m pip install \
    datasets \
    "transformers[torch]"

45. 영화 리뷰 데이터 불러오기

from datasets import load_dataset


DATASET_ID = (
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)


dataset = load_dataset(
    DATASET_ID
)


print(dataset)

Split 확인:

print(
    dataset.keys()
)

행 개수:

for split_name, split_dataset in (
    dataset.items()
):
    print(
        split_name,
        len(
            split_dataset
        )
    )

열 구조:

print(
    dataset["train"].features
)

46. 데이터 탐색과 품질 검사

Sample 확인

for index in range(3):
    example = dataset[
        "train"
    ][index]

    print(
        f"[{index}]"
    )

    print(
        example
    )

    print()

라벨 이름

label_feature = (
    dataset["train"]
    .features["label"]
)


print(
    label_feature.names
)

라벨 분포

from collections import Counter


label_counts = Counter(
    dataset["train"][
        "label"
    ]
)


print(
    label_counts
)

빈 리뷰 개수

empty_count = sum(
    1
    for text in dataset[
        "train"
    ]["text"]
    if not text.strip()
)


print(
    f"빈 리뷰: {empty_count}개"
)

중복 리뷰 확인

texts = dataset[
    "train"
]["text"]


duplicate_count = (
    len(texts)
    - len(
        set(texts)
    )
)


print(
    f"중복 리뷰: "
    f"{duplicate_count}개"
)

모델 학습 전에 데이터의 라벨 분포, 결측값, 중복과 길이 분포를 확인해야 합니다.

모델이 틀렸다고 생각했는데 데이터가 같은 문장을 서로 다른 라벨로 가르치고 있을 수도 있습니다.

47. 리뷰 정제와 필터링

빈 문장 제거

filtered_dataset = dataset.filter(
    lambda example: bool(
        example["text"].strip()
    )
)

텍스트 정제

import re


def clean_review_batch(
    batch
):
    cleaned_texts = []

    for text in batch["text"]:
        cleaned_text = re.sub(
            r"\s+",
            " ",
            text
        ).strip()

        cleaned_texts.append(
            cleaned_text
        )

    return {
        "text": cleaned_texts
    }


cleaned_dataset = (
    filtered_dataset.map(
        clean_review_batch,
        batched=True
    )
)

리뷰 길이 추가

def add_length_batch(
    batch
):
    return {
        "character_count": [
            len(text)
            for text in batch[
                "text"
            ]
        ],
        "word_count": [
            len(
                text.split()
            )
            for text in batch[
                "text"
            ]
        ]
    }


analyzed_dataset = (
    cleaned_dataset.map(
        add_length_batch,
        batched=True
    )
)

지나치게 긴 리뷰 제거

analyzed_dataset = (
    analyzed_dataset.filter(
        lambda example: (
            example[
                "word_count"
            ] <= 500
        )
    )
)

텍스트를 무조건 소문자로 바꾸거나 특수문자를 삭제하기 전에 사용하는 모델의 Tokenizer와 작업 목적을 고려해야 합니다.

감정 표현:

좋아요!!!

좋아요

두 문장은 느낌이 다를 수 있음

과도한 정제는 신호까지 청소해 버릴 수 있습니다.

48. Tokenizer 전처리

from transformers import (
    AutoTokenizer
)


MODEL_ID = (
    "distilbert-base-uncased"
)


tokenizer = (
    AutoTokenizer
    .from_pretrained(
        MODEL_ID
    )
)

전처리 함수:

def tokenize_batch(
    batch
):
    return tokenizer(
        batch["text"],
        truncation=True,
        max_length=256
    )

전체 Split 처리:

tokenized_dataset = (
    analyzed_dataset.map(
        tokenize_batch,
        batched=True,
        desc="리뷰 토큰화 중"
    )
)

모델 학습 코드와 열 이름을 통일하기 위해 `label`을 `labels`로 변경할 수 있습니다.

tokenized_dataset = (
    tokenized_dataset.rename_column(
        "label",
        "labels"
    )
)

필요한 열만 선택:

model_dataset = (
    tokenized_dataset.select_columns([
        "input_ids",
        "attention_mask",
        "labels"
    ])
)

오류 분석에 원본 텍스트를 사용할 계획이라면 별도 버전으로 보관합니다.

analysis_dataset = (
    tokenized_dataset
)

49. PyTorch DataLoader 생성

from torch.utils.data import (
    DataLoader
)

from transformers import (
    DataCollatorWithPadding
)


data_collator = (
    DataCollatorWithPadding(
        tokenizer=tokenizer,
        return_tensors="pt"
    )
)

DataLoader:

train_loader = DataLoader(
    model_dataset["train"],
    batch_size=16,
    shuffle=True,
    collate_fn=data_collator
)


validation_loader = DataLoader(
    model_dataset[
        "validation"
    ],
    batch_size=32,
    shuffle=False,
    collate_fn=data_collator
)

첫 Batch:

batch = next(
    iter(
        train_loader
    )
)


for key, value in (
    batch.items()
):
    print(
        key,
        value.shape,
        value.dtype
    )

출력 예:

input_ids
torch.Size([16, Batch_최대길이])
torch.int64

attention_mask
torch.Size([16, Batch_최대길이])
torch.int64

labels
torch.Size([16])
torch.int64

모델에 전달:

outputs = model(
    **batch
)

단, Batch Tensor와 모델이 같은 장치에 있어야 합니다.

batch = {
    key: value.to(
        device
    )
    for key, value in (
        batch.items()
    )
}

50. 전처리 데이터 저장

from pathlib import Path


OUTPUT_DIR = Path(
    "processed_data/"
    "rotten_tomatoes_distilbert"
)


OUTPUT_DIR.parent.mkdir(
    parents=True,
    exist_ok=True
)


model_dataset.save_to_disk(
    str(
        OUTPUT_DIR
    )
)

Tokenizer도 저장합니다.

tokenizer.save_pretrained(
    OUTPUT_DIR /
    "tokenizer"
)

다시 불러오기:

from datasets import (
    load_from_disk
)

from transformers import (
    AutoTokenizer
)


loaded_dataset = load_from_disk(
    OUTPUT_DIR
)


loaded_tokenizer = (
    AutoTokenizer
    .from_pretrained(
        OUTPUT_DIR /
        "tokenizer",
        local_files_only=True
    )
)

이제 매번 원본 데이터 정제와 Tokenizer 처리를 반복하지 않아도 됩니다.

51. 전체 프로젝트 코드

다음 내용을 `prepare_dataset.py`로 저장합니다.

from collections import Counter
from pathlib import Path
import re

from datasets import (
    DatasetDict,
    load_dataset,
    load_from_disk
)
from torch.utils.data import (
    DataLoader
)
from transformers import (
    AutoTokenizer,
    DataCollatorWithPadding
)


DATASET_ID = (
    "cornell-movie-review-data/"
    "rotten_tomatoes"
)

MODEL_ID = (
    "distilbert-base-uncased"
)

OUTPUT_DIR = Path(
    "processed_data/"
    "rotten_tomatoes_distilbert"
)


def clean_review_batch(
    batch
):
    """연속된 공백과 줄바꿈을 정리합니다."""
    cleaned_texts = []

    for text in batch["text"]:
        cleaned_text = re.sub(
            r"\s+",
            " ",
            text
        ).strip()

        cleaned_texts.append(
            cleaned_text
        )

    return {
        "text": cleaned_texts
    }


def add_text_statistics(
    batch
):
    """리뷰의 글자 수와 단어 수를 추가합니다."""
    texts = batch["text"]

    return {
        "character_count": [
            len(text)
            for text in texts
        ],
        "word_count": [
            len(
                text.split()
            )
            for text in texts
        ]
    }


def inspect_dataset(
    dataset: DatasetDict
) -> None:
    """데이터셋의 기본 구조와 품질을 확인합니다."""
    print(
        "\n[데이터셋 구조]"
    )

    print(dataset)

    train_dataset = dataset[
        "train"
    ]

    print(
        "\n[열 구조]"
    )

    print(
        train_dataset.features
    )

    label_feature = (
        train_dataset
        .features["label"]
    )

    print(
        "\n[라벨 이름]"
    )

    print(
        label_feature.names
    )

    label_counts = Counter(
        train_dataset[
            "label"
        ]
    )

    print(
        "\n[라벨 분포]"
    )

    print(
        label_counts
    )

    texts = train_dataset[
        "text"
    ]

    empty_count = sum(
        1
        for text in texts
        if not text.strip()
    )

    duplicate_count = (
        len(texts)
        - len(
            set(texts)
        )
    )

    print(
        "\n[품질 검사]"
    )

    print(
        f"빈 리뷰: "
        f"{empty_count}개"
    )

    print(
        f"중복 리뷰: "
        f"{duplicate_count}개"
    )

    print(
        "\n[샘플]"
    )

    for index in range(
        min(
            3,
            len(
                train_dataset
            )
        )
    ):
        print(
            train_dataset[
                index
            ]
        )


def prepare_dataset(
    dataset: DatasetDict,
    tokenizer
) -> DatasetDict:
    """필터링, 정제, 토큰화를 수행합니다."""
    filtered_dataset = (
        dataset.filter(
            lambda example: bool(
                example["text"].strip()
            ),
            desc="빈 리뷰 제거 중"
        )
    )

    cleaned_dataset = (
        filtered_dataset.map(
            clean_review_batch,
            batched=True,
            desc="리뷰 정제 중"
        )
    )

    analyzed_dataset = (
        cleaned_dataset.map(
            add_text_statistics,
            batched=True,
            desc="리뷰 길이 계산 중"
        )
    )

    analyzed_dataset = (
        analyzed_dataset.filter(
            lambda example: (
                0
                < example["word_count"]
                <= 500
            ),
            desc=(
                "길이 조건 적용 중"
            )
        )
    )

    def tokenize_batch(
        batch
    ):
        return tokenizer(
            batch["text"],
            truncation=True,
            max_length=256
        )

    tokenized_dataset = (
        analyzed_dataset.map(
            tokenize_batch,
            batched=True,
            desc="Tokenizer 적용 중"
        )
    )

    tokenized_dataset = (
        tokenized_dataset
        .rename_column(
            "label",
            "labels"
        )
    )

    model_dataset = (
        tokenized_dataset
        .select_columns([
            "input_ids",
            "attention_mask",
            "labels"
        ])
    )

    return model_dataset


def inspect_dataloader(
    dataset: DatasetDict,
    tokenizer
) -> None:
    """PyTorch DataLoader의 첫 Batch를 확인합니다."""
    data_collator = (
        DataCollatorWithPadding(
            tokenizer=tokenizer,
            return_tensors="pt"
        )
    )

    train_loader = DataLoader(
        dataset["train"],
        batch_size=16,
        shuffle=True,
        collate_fn=data_collator
    )

    batch = next(
        iter(
            train_loader
        )
    )

    print(
        "\n[첫 번째 Batch]"
    )

    for key, value in (
        batch.items()
    ):
        print(
            f"{key}: "
            f"shape={tuple(value.shape)}, "
            f"dtype={value.dtype}"
        )


def save_processed_dataset(
    dataset: DatasetDict,
    tokenizer
) -> None:
    """전처리 데이터와 Tokenizer를 저장합니다."""
    OUTPUT_DIR.parent.mkdir(
        parents=True,
        exist_ok=True
    )

    dataset.save_to_disk(
        str(
            OUTPUT_DIR
        )
    )

    tokenizer.save_pretrained(
        OUTPUT_DIR /
        "tokenizer"
    )

    print(
        "\n[저장 완료]"
    )

    print(
        OUTPUT_DIR.resolve()
    )


def verify_saved_dataset() -> None:
    """저장된 Dataset을 다시 불러와 검증합니다."""
    loaded_dataset = (
        load_from_disk(
            str(
                OUTPUT_DIR
            )
        )
    )

    print(
        "\n[저장 데이터 검증]"
    )

    print(
        loaded_dataset
    )

    print(
        loaded_dataset[
            "train"
        ][0]
    )


def main() -> None:
    print(
        "[1/6] 데이터셋 로딩"
    )

    dataset = load_dataset(
        DATASET_ID
    )

    print(
        "[2/6] 데이터 탐색"
    )

    inspect_dataset(
        dataset
    )

    print(
        "\n[3/6] Tokenizer 로딩"
    )

    tokenizer = (
        AutoTokenizer
        .from_pretrained(
            MODEL_ID
        )
    )

    print(
        "[4/6] 데이터 전처리"
    )

    processed_dataset = (
        prepare_dataset(
            dataset,
            tokenizer
        )
    )

    print(
        processed_dataset
    )

    print(
        "[5/6] DataLoader 검사"
    )

    inspect_dataloader(
        processed_dataset,
        tokenizer
    )

    print(
        "[6/6] 처리 결과 저장"
    )

    save_processed_dataset(
        processed_dataset,
        tokenizer
    )

    verify_saved_dataset()


if __name__ == "__main__":
    main()

실행:

python prepare_dataset.py

52. 성능 최적화 방법

작은 Sample로 먼저 검증한다

sample_dataset = (
    dataset["train"]
    .shuffle(
        seed=2026
    )
    .select(
        range(1000)
    )
)

전처리 함수의 오류를 전체 데이터에서 발견하지 마세요.

100개 데이터에서 발견하면 훨씬 덜 슬픕니다.

batched=True 사용

dataset.map(
    tokenize_batch,
    batched=True
)

Tokenizer와 배열 연산은 Batch 처리에서 효율적인 경우가 많습니다.

num_proc 조정

dataset.map(
    preprocess,
    batched=True,
    num_proc=4
)

CPU와 메모리 상황을 측정해 결정합니다.

필요 없는 열 제거

dataset = (
    dataset.select_columns([
        "input_ids",
        "attention_mask",
        "labels"
    ])
)

저장 공간과 Batch 전달량을 줄일 수 있습니다.

동적 Padding 사용

DataCollatorWithPadding(
    tokenizer=tokenizer
)

모든 데이터를 512 Token으로 미리 Padding하지 않습니다.

Streaming 검토

load_dataset(
    DATASET_ID,
    streaming=True
)

전체 다운로드가 어려운 초대형 데이터셋에 적합합니다.

Parquet 활용

CSV보다 타입과 열 구조를 효율적으로 저장하고 읽을 수 있습니다.

전처리 결과 저장

dataset.save_to_disk(
    "processed_dataset"
)

학습할 때마다 Tokenizer를 다시 실행하지 않습니다.

캐시 디스크 확인

캐시가 느린 네트워크 드라이브에 있으면 성능이 떨어질 수 있습니다.

먼저 측정한다

import time


start_time = time.perf_counter()

processed_dataset = dataset.map(
    preprocess,
    batched=True
)

elapsed = (
    time.perf_counter()
    - start_time
)


print(
    f"처리 시간: "
    f"{elapsed:.2f}초"
)
체감상 빠름

보다

처리 시간 32.7초

숫자가 더 정직합니다.

53. 개인정보와 데이터 보안

AI 데이터에는 개인정보와 민감정보가 포함될 수 있습니다.

이름

이메일

전화번호

주소

주민등록번호

계정 정보

위치 정보

건강 정보

회사 내부 문서

보안 로그

확인할 사항

수집 목적이 명확한가?

사용자 동의를 받았는가?

학습에 사용할 권한이 있는가?

보관 기간은 적절한가?

개인정보를 제거했는가?

데이터 접근 권한은 제한되어 있는가?

외부 Hub에 업로드해도 되는가?

모델이 학습 데이터를 노출할 가능성은 없는가?

익명화 예

import re


def mask_email(
    example
):
    masked_text = re.sub(
        r"[\w\.-]+@[\w\.-]+",
        "[EMAIL]",
        example["text"]
    )

    return {
        "text": masked_text
    }

하지만 정규식 하나로 완벽한 익명화가 이루어지는 것은 아닙니다.

이름 + 회사 + 사건 날짜

조합만으로도

특정인을 추정할 수 있음

민감한 데이터는 조직의 보안·법률·개인정보 정책을 함께 검토해야 합니다.

54. 자주 발생하는 오류

오류 1. datasets 모듈을 찾을 수 없음

ModuleNotFoundError:
No module named 'datasets'

설치:

python -m pip install datasets

확인:

python -m pip show datasets

오류 2. load_dataset을 Import할 수 없음

ImportError:
cannot import name 'load_dataset'

프로젝트에 다음 파일이나 폴더가 있는지 확인합니다.

datasets.py

datasets/

Python이 공식 라이브러리 대신 로컬 파일을 Import할 수 있습니다.

확인:

import datasets

print(
    datasets.__file__
)

파일 이름을 변경한 뒤 캐시 파일도 정리합니다.

datasets.py
→ dataset_utils.py

오류 3. 데이터셋을 찾을 수 없음

DatasetNotFoundError

확인:

데이터셋 ID

조직명·사용자명

저장소 이름

대소문자

접근 권한

로그인 상태

Hub의 전체 ID를 사용합니다.

load_dataset(
    "조직명/데이터셋명"
)

오류 4. Split 이름 오류

Unknown split

Split 목록 확인:

dataset = load_dataset(
    DATASET_ID
)

print(
    dataset.keys()
)

데이터셋마다 Split 이름은 다를 수 있습니다.

train

validation

test

dev

unsupervised

오류 5. CSV 열 타입 추론 오류

한 열에 숫자와 문자열이 섞여 있을 수 있습니다.

label 열:

0
1
unknown

CSV를 먼저 정리하거나 Features를 명시합니다.

from datasets import (
    Features,
    Value
)


features = Features({
    "text": Value(
        "string"
    ),
    "label": Value(
        "string"
    )
})


dataset = load_dataset(
    "csv",
    data_files="reviews.csv",
    features=features
)

오류 6. JSON 구조가 맞지 않음

중첩된 배열이라면 `field`를 지정합니다.

dataset = load_dataset(
    "json",
    data_files="data.json",
    field="data"
)

JSON Lines 파일의 각 줄이 유효한 JSON 객체인지 확인합니다.

오류 7. map 함수 반환 형식 오류

잘못된 예:

def preprocess(
    example
):
    return "완료"

`map()` 함수는 일반적으로 열 이름과 값을 가진 딕셔너리를 반환해야 합니다.

def preprocess(
    example
):
    return {
        "status": "완료"
    }

오류 8. batched map 길이가 맞지 않음

입력 Batch가 100개인데 새 열의 값이 50개라면 오류가 발생합니다.

def preprocess(
    batch
):
    return {
        "length": [
            len(text)
            for text in batch[
                "text"
            ]
        ]
    }

반환되는 각 열의 행 개수를 확인합니다.

오류 9. num_proc에서 함수 직렬화 오류

복잡한 지역 함수나 직렬화할 수 없는 객체를 참조했을 수 있습니다.

전처리 함수를 모듈 최상위에 정의합니다.

def preprocess(
    batch
):
    ...

먼저 `num_proc` 없이 정상 작동하는지 확인합니다.

오류 10. stratify_by_column 오류

계층화에 사용하는 열이 ClassLabel 형식이 아닐 수 있습니다.

print(
    dataset.features[
        "label"
    ]
)

필요하면 ClassLabel로 변환합니다.

오류 11. save_to_disk 결과를 load_dataset으로 읽음

잘못된 방식:

load_dataset(
    "saved_dataset"
)

올바른 방식:

from datasets import (
    load_from_disk
)


dataset = load_from_disk(
    "saved_dataset"
)

오류 12. Streaming Dataset에서 인덱스 접근

streaming_dataset[
    0
]

IterableDataset은 반복 방식으로 사용합니다.

first_example = next(
    iter(
        streaming_dataset
    )
)

또는:

for example in (
    streaming_dataset.take(3)
):
    print(example)

오류 13. Streaming Dataset 길이 확인

len(
    streaming_dataset
)

전체 길이를 알 수 없거나 계산할 수 없는 경우가 있습니다.

학습 Step 수를 직접 지정하거나 데이터셋 메타데이터를 확인해야 합니다.

오류 14. 캐시 디스크가 가득 참

증상:

No space left on device

확인:

print(
    dataset.cache_files
)

대응:

불필요한 모델 캐시 정리

Datasets 캐시 정리

캐시 위치 변경

처리 결과 압축·삭제

디스크 용량 확장

오류 15. DataLoader에서 문자열 변환 오류

모든 열을 Tensor로 바꾸려 했지만 `text` 열이 남아 있을 수 있습니다.

dataset = dataset.select_columns([
    "input_ids",
    "attention_mask",
    "labels"
])

또는 Data Collator가 필요한 열만 사용하도록 구성합니다.

오류 16. Batch 길이가 서로 다름

고정 Tensor로 묶으려면 Padding이 필요합니다.

data_collator = (
    DataCollatorWithPadding(
        tokenizer=tokenizer
    )
)

오류 17. 다운로드가 매번 반복됨

확인:

캐시 쓰기 권한

HF_HOME 경로

Docker Volume

임시 디렉터리 사용

데이터셋 Revision 변화

컨테이너에서는 캐시 폴더를 Volume으로 연결할 수 있습니다.

오류 18. 비공개 데이터셋 401·403 오류

로그인:

hf auth login

권한과 토큰 범위를 확인합니다.

dataset = load_dataset(
    "사용자명/"
    "비공개_데이터셋",
    token=True
)

55. 연습 문제

문제 1

Hub에서 원하는 공개 데이터셋을 불러오고 Split 이름을 출력하세요.

문제 2

Train Split의 첫 번째 행과 마지막 행을 출력하세요.

문제 3

데이터셋의 열 이름, 행 수, Features를 출력하세요.

문제 4

CSV 파일을 Train과 Test Split으로 나누어 불러오세요.

data_files = {
    "train": "train.csv",
    "test": "test.csv"
}

문제 5

Python 딕셔너리로 다음 열을 가진 Dataset을 만드세요.

text

label

source

문제 6

리뷰 길이가 20자 이상인 행만 `filter()`로 남기세요.

문제 7

`map()`으로 글자 수와 단어 수 열을 추가하세요.

문제 8

`batched=True`를 사용해 모든 텍스트의 양쪽 공백을 제거하세요.

문제 9

Dataset을 섞은 뒤 처음 500개만 선택하세요.

문제 10

하나의 Dataset을 Train 80%, Test 20%로 분리하세요.

문제 11

`label` 열의 이름을 `labels`로 변경하세요.

문제 12

불필요한 `id`, `source`, `metadata` 열을 삭제하세요.

문제 13

Tokenizer를 `map()`으로 전체 데이터에 적용하세요.

문제 14

Tokenized Dataset을 PyTorch DataLoader와 연결하세요.

문제 15

Streaming 모드로 데이터셋을 불러와 처음 10개 Sample을 출력하세요.

문제 16

Streaming Dataset을 Buffer 크기 5,000으로 Shuffle하세요.

문제 17

처리한 Dataset을 `save_to_disk()`로 저장하고 다시 불러오세요.

문제 18

Dataset을 CSV와 Parquet 파일로 각각 내보내세요.

문제 19

특정 Commit Hash를 `revision`에 지정해 데이터셋 버전을 고정하세요.

문제 20

실전 프로젝트에 다음 기능을 추가하세요.

중복 리뷰 제거

라벨별 데이터 개수 출력

리뷰 길이 히스토그램 생성

최대 토큰 길이 통계

Train DataLoader Batch 확인

전처리 정보 JSON 저장

56. 핵심 요약

설치

python -m pip install datasets

Hub 데이터셋

from datasets import load_dataset


dataset = load_dataset(
    "조직명/데이터셋명"
)

특정 Split

train_dataset = load_dataset(
    DATASET_ID,
    split="train"
)

CSV

dataset = load_dataset(
    "csv",
    data_files="data.csv"
)

Python 딕셔너리

from datasets import Dataset


dataset = Dataset.from_dict({
    "text": [
        "문장 A",
        "문장 B"
    ],
    "label": [
        0,
        1
    ]
})

행 접근

example = dataset[
    0
]

열 접근

texts = dataset[
    "text"
]

Shuffle

dataset = dataset.shuffle(
    seed=2026
)

일부 선택

dataset = dataset.select(
    range(100)
)

Split 분리

dataset = (
    dataset
    .train_test_split(
        test_size=0.2,
        seed=2026
    )
)

Filter

dataset = dataset.filter(
    lambda example: (
        example["label"] == 1
    )
)

Map

dataset = dataset.map(
    preprocess
)

Batch Map

dataset = dataset.map(
    preprocess_batch,
    batched=True
)

멀티프로세싱

dataset = dataset.map(
    preprocess_batch,
    batched=True,
    num_proc=4
)

열 이름 변경

dataset = dataset.rename_column(
    "label",
    "labels"
)

열 삭제

dataset = dataset.remove_columns(
    [
        "unused_column"
    ]
)

Tokenizer 적용

tokenized_dataset = dataset.map(
    tokenize_batch,
    batched=True
)

PyTorch 형식

torch_dataset = dataset.with_format(
    "torch"
)

Streaming

streaming_dataset = load_dataset(
    DATASET_ID,
    split="train",
    streaming=True
)

로컬 저장

dataset.save_to_disk(
    "processed_dataset"
)

다시 불러오기

from datasets import (
    load_from_disk
)


dataset = load_from_disk(
    "processed_dataset"
)

Hub 업로드

dataset.push_to_hub(
    "사용자명/데이터셋명"
)

57. 마무리

이번 시간에는 Hugging Face Datasets를 이용해 AI 학습 데이터를 불러오고 가공하는 방법을 알아보았습니다.

전체 흐름을 다시 정리해 보겠습니다.

데이터셋 검색 또는 파일 준비

→ load_dataset()

→ Dataset·DatasetDict 구조 확인

→ Features와 Label 확인

→ 결측값·중복·길이 검사

→ filter()로 불필요한 행 제거

→ map()으로 데이터 정제

→ Train·Validation·Test Split 구성

→ Tokenizer 일괄 적용

→ PyTorch DataLoader 연결

→ save_to_disk()로 저장

→ 모델 학습

Datasets의 중심에는 몇 가지 핵심 함수가 있습니다.

load_dataset()
→ 데이터 불러오기

select()
→ 일부 행 선택

shuffle()
→ 데이터 섞기

filter()
→ 조건에 맞는 행 선택

map()
→ 데이터 변환

train_test_split()
→ 학습·평가 데이터 분리

with_format()
→ Tensor 형식 변환

save_to_disk()
→ 처리 결과 저장

일반적인 AI 프로젝트에서 모델 구조에만 집중하기 쉽습니다.

BERT를 사용할까?

DistilBERT를 사용할까?

Learning Rate는 얼마로 할까?

Epoch는 몇 번 돌릴까?

하지만 데이터가 잘못되어 있다면 모델 선택만으로 문제를 해결할 수 없습니다.

잘못된 라벨

중복 데이터

학습·평가 데이터 누수

불균형한 클래스

개인정보 포함

지원하지 않는 언어

지나치게 짧은 문장

이런 문제는 GPU를 추가한다고 사라지지 않습니다.

쓰레기 데이터

→ 최신 GPU 8장

→ 매우 빠르게 학습된 쓰레기 모델

AI 프로젝트의 품질은 모델뿐 아니라 데이터가 결정합니다.

Hugging Face Datasets는 데이터를 자동으로 좋은 데이터로 만들어 주는 마법 도구는 아닙니다.

대신 데이터를 체계적으로 관찰하고, 변환하고, 재현하고, 모델에 전달할 수 있는 튼튼한 작업대를 제공합니다.

개발자:
“데이터 정제를 완료했습니다.”

Datasets:
“Fingerprint와 캐시를 저장했습니다.”

Tokenizer:
“문장을 토큰으로 바꾸겠습니다.”

DataLoader:
“Batch로 배달하겠습니다.”

모델:
“이제 학습을 시작하겠습니다.”

AI 데이터 물류센터의 모든 직원이 자리를 잡았습니다. 📦🚚🤖

이제 데이터는 단순한 CSV 파일이 아닙니다.

Split과 Feature를 갖고, 전처리 이력을 남기며, Tokenizer를 거쳐 Tensor Batch로 이동하는 하나의 학습 파이프라인이 되었습니다.

다음 편 예고

[Python 완전정복 시리즈 #37] Hugging Face Trainer 완벽 이해하기 | 사전 학습 모델을 내 데이터로 Fine-tuning하는 방법

다음 시간에는 이번 편에서 준비한 영화 리뷰 데이터를 이용해 사전 학습된 Transformer 모델을 감정 분석 모델로 Fine-tuning합니다.

`TrainingArguments`, `Trainer`, Data Collator, 평가 지표, 학습·검증, 체크포인트, Early Stopping, 모델 저장, Pipeline 추론, Hub 업로드까지 실습해 보겠습니다.

#Python #파이썬 #Python강좌 #파이썬기초 #HuggingFace #허깅페이스 #HuggingFaceDatasets #Datasets #AI데이터 #머신러닝 #딥러닝 #데이터전처리 #데이터셋 #Dataset #DatasetDict #load_dataset #TrainTestSplit #Tokenizer #PyTorch #DataLoader #Streaming #IterableDataset #ApacheArrow #Transformers #FineTuning #인공지능 #코딩공부 #프로그래밍

댓글

0

댓글을 불러오는 중입니다.