BART 요약 모델 & 인터뷰 데이터 수집 & 복습 정리

syeom·2026년 6월 21일

멀티캠퍼스 데이터분석 6월 19일 수업 내용 — BART 이론, KoBART 요약 모델 Seq2Seq 학습, 인터뷰 JSON 데이터 수집, 복습 문제


📌 목차

BART 요약 모델
1. BART 이론
2. 데이터 준비 — DatasetDict & 토큰화
3. ROUGE 평가 지표
4. Seq2SeqTrainer 학습
5. 문장 생성 — generate() 매개변수

인터뷰 데이터 수집

  1. JSON 파일 수집 & DataFrame 변환
  2. 복습 문제 — KoBART 파인튜닝

BART 요약 모델

1. BART 이론

BART(Bidirectional and Auto-Regressive Transformers) 는 Transformer 기반의 Encoder-Decoder 혼합 모델입니다.

구성역할참고 모델
Encoder문장을 이해BERT 방식
Decoder문장을 생성GPT 방식
  • 번역, 요약, 오탈자 교정 등 새로운 텍스트를 생성하는 작업에 사용
  • 평가는 n-gram 기반으로 정답과 같은 단어를 얼마나 사용했는지 측정 (로그 스케일 수치)
  • 입력용 tokenizer와 출력용 tokenizer를 별도로 사용
  • Transformer 계열 모델은 일반적으로 SentencePiece 기반 토큰화 사용

특수 토큰 정리

토큰의미
<PAD>빈 칸 채우기 (패딩)
<UNK>OOV (미등록 단어)
<SEP>두 번째 문장 구분
</s> (EOS)전체 문장의 끝 — 생성이 언제 끝나는지 표시
# !pip install lighteval
# !pip install rouge_score evaluate
import evaluate
import numpy as np
from datasets import Dataset, DatasetDict
from transformers import (AutoTokenizer, AutoModelForSeq2SeqLM,
                          DataCollatorForSeq2Seq, Seq2SeqTrainer,
                          Seq2SeqTrainingArguments)

model_name = 'digit82/kobart-summarization'

tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
model     = AutoModelForSeq2SeqLM.from_pretrained(model_name)

2. 데이터 준비 — DatasetDict & 토큰화

샘플 데이터 & DatasetDict 생성

train_docs = [
    '정부는 중소기업 세제 혜택과 R&D 세액 공제를 확대한다고 밝혔다',
    '해당 기업은 분기 실적에서 매출 성장을 기록했으며 신제품 출시를 예고했다'
]
train_sums = [
    '정부가 중소기업 지원을 확대한다.',
    '기업이 실적 개선과 신제품 출시를 예고했다.'
]

valid_docs = ['교육부가 디지털 교과서 도입을 추진한다고 발표했다']
valid_sums = ['교육부가 디지털 교과서 도입을 추진한다.']

# DatasetDict — train/validation을 하나의 객체로 관리
raw_ds = DatasetDict({
    'train': Dataset.from_dict({
        'document': train_docs, 'summary': train_sums
    }),
    'validation': Dataset.from_dict({
        'document': valid_docs, 'summary': valid_sums
    })
})

Seq2Seq 토큰화 함수 — labels의 -100 처리

max_input_len  = 512
max_target_len = 128

def token_fn(batch):
    # 입력 문장 인코딩
    inputs = tokenizer(
        batch['document'],
        max_length = max_input_len,
        padding    = 'max_length',
        truncation = True
    )

    # 출력(요약) 문장 인코딩
    labels = tokenizer(
        batch['summary'],
        max_length = max_target_len,
        padding    = True,
        truncation = True
    )

    # 패딩 토큰을 -100으로 변환
    labels_ids = np.array(labels['input_ids'])
    labels_ids[labels_ids == tokenizer.pad_token_id] = -100

    inputs['labels'] = labels_ids.tolist()
    return inputs

tokenized_ds = raw_ds.map(
    token_fn, batched=True,
    remove_columns=['document', 'summary']
)

💡 labels의 패딩 토큰을 -100으로 바꾸는 이유
nn.CrossEntropyLoss() 는 기본적으로 -100 값을 무시하도록 설계되어 있습니다.
패딩 위치까지 손실 계산에 포함되면 모델이 "패딩을 생성하는 법"을 학습하게 되므로,
패딩 부분을 -100으로 마스킹하여 실제 단어 부분만 학습되도록 합니다.

DataCollatorForSeq2Seq

data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)

💡 Seq2Seq 모델 전용 데이터 콜레이터로, 배치마다 입력과 라벨을 적절히 패딩하고
디코더 입력(decoder_input_ids)도 자동으로 생성합니다.


3. ROUGE 평가 지표

ROUGE 는 생성된 문장과 정답 문장 사이에 공통으로 등장하는 단어의 비율을 측정합니다.

지표측정 방식
ROUGE-1개별 단어(1-gram)의 겹침 정도
ROUGE-2연속된 2단어(2-gram)의 겹침 정도
ROUGE-L가장 길게 공통으로 이어지는 부분 문자열(LCS) 기반
from konlpy.tag import Komoran

rouge   = evaluate.load('rouge')
komoran = Komoran()

def metrics(eval_pred):
    preds, labels = eval_pred

    # -100을 다시 패딩 토큰 ID로 복원 (디코딩을 위해)
    labels = np.where(labels != -100, labels, tokenizer.pad_token_id)

    # 토큰 ID → 텍스트 디코딩
    pred_str  = tokenizer.batch_decode(preds,  skip_special_tokens=True)
    label_str = tokenizer.batch_decode(labels, skip_special_tokens=True)

    # 좌우 공백 제거 (다르면 다른 문장으로 측정됨)
    pred_str  = [doc.strip() for doc in pred_str]
    label_str = [doc.strip() for doc in label_str]

    result = rouge.compute(
        predictions = pred_str,
        references  = label_str,
        tokenizer   = lambda x: komoran.morphs(x)   # 한국어 형태소 기준 비교
    )

    return {k: round(v * 100, 2) for k, v in result.items()}

💡 np.where(labels != -100, labels, tokenizer.pad_token_id)
학습용으로 -100 처리했던 라벨을 평가 시에는 다시 패딩 토큰 ID로 되돌려야
batch_decode()로 정상적인 텍스트 디코딩이 가능합니다.


4. Seq2SeqTrainer 학습

args = Seq2SeqTrainingArguments(
    output_dir       = './kobart',
    eval_strategy    = 'epoch',
    save_strategy    = 'epoch',
    learning_rate    = 5e-5,
    num_train_epochs = 5,
    logging_steps    = 2,

    load_best_model_at_end = True,
    metric_for_best_model  = 'rougeL',
    greater_is_better      = True,

    # 생성 관련 설정
    predict_with_generate = True,   # 평가 시 직접 문장 생성
    generation_max_length  = 64,    # 생성 문장 최대 토큰 길이
    generation_num_beams   = 4      # 빔 서치 후보 개수
)

trainer = Seq2SeqTrainer(
    model            = model,
    args             = args,
    train_dataset    = tokenized_ds['train'],
    eval_dataset     = tokenized_ds['validation'],
    processing_class = tokenizer,
    data_collator    = data_collator,
    compute_metrics  = metrics
)

trainer.train()

Seq2SeqTrainingArguments 핵심 매개변수

매개변수설명
predict_with_generate평가 시 실제 텍스트를 생성하여 평가 (분류와 다른 점)
generation_max_length생성 문장의 최대 토큰 길이
generation_num_beams빔 서치 시 탐색할 후보 문장 개수

5. 문장 생성 — generate() 매개변수

test_text = """과학기술정보통신부는 초거대 AI 연구 인프라 지원을 강화한다고 밝혔다.
스타트업 대상으로 GPU 리소스를 확대 제공할 계획이다."""

inputs = tokenizer(
    test_text, return_tensors='pt',
    truncation=True, max_length=max_input_len
)

generate_ids = model.generate(
    **inputs,
    max_new_tokens = 64,    # 생성 토큰 최대 길이
    min_new_tokens = 5,     # 생성 토큰 최소 길이
    num_beams      = 4,     # 빔 서치 후보 개수

    do_sample = False,      # False: 빔서치(결정적) / True: 확률 기반 샘플링(무작위)

    length_penalty = 0.6,   # 길이에 대한 패널티

    no_repeat_ngram_size = 3,   # 동일한 n-gram 반복 방지
    repetition_penalty   = 1.1, # 토큰 반복 패턴에 패널티

    bos_token_id = tokenizer.bos_token_id,
    eos_token_id = tokenizer.eos_token_id,
    pad_token_id = tokenizer.pad_token_id
)

print(tokenizer.decode(generate_ids[0], skip_special_tokens=True))

generate() 주요 매개변수 정리

매개변수설명
num_beams여러 후보 문장을 동시에 탐색 후 가장 가능성 높은 문장 선택
do_sampleFalse: 빔서치(일관된 결과) / True: 확률적 샘플링(다양한 결과)
length_penalty<1: 짧은 문장에 가산점(간결한 요약) / >1: 긴 문장에 가산점
no_repeat_ngram_size지정 크기의 n-gram이 반복되지 않도록 방지
repetition_penalty이미 생성된 토큰의 재등장에 패널티 부여

💡 length_penalty 설정 가이드

  • 요약 작업: 0.6 ~ 0.8 (짧고 압축된 문장 선호)
  • 번역/생성 작업: 1.0 ~ 1.5 (자연스럽고 풍부한 문장 선호)

인터뷰 데이터 수집

6. JSON 파일 수집 & DataFrame 변환

파일 목록 조회 & 구조 확인

from glob import glob
import pandas as pd
import json

file_list = glob('../data/인터뷰/*.json')

# pandas로 바로 읽기 (중첩 구조 확인용)
df = pd.read_json(file_list[0])

# json 라이브러리로 직접 읽기 (딕셔너리 구조 탐색)
with open(file_list[0], 'r', encoding='utf-8') as f:
    data = json.load(f)

from pprint import pprint
pprint(data['dataSet']['answer']['summary']['text'])

💡 with open(...) as f: 패턴
파일을 열고 작업이 끝나면 자동으로 닫아줍니다.
f.close() 를 명시적으로 호출할 필요가 없어 안전합니다.

전체 파일에서 answer/summary 추출

answers, summarys = [], []

for file in file_list:
    try:
        with open(file, 'r', encoding='utf-8') as f:
            dict_data = json.load(f)
            answer  = dict_data['dataSet']['answer']['raw']['text']
            summary = dict_data['dataSet']['answer']['summary']['text']
            answers.append(answer)
            summarys.append(summary)
    except Exception as e:
        print(e)   # 구조가 다른 파일은 건너뛰고 에러 출력

print(len(summarys))

💡 try/except로 일부 파일 오류 방지
대량의 JSON 파일을 처리할 때 일부 파일의 구조가 다르거나 깨져 있을 수 있습니다.
try/except 로 감싸면 한 파일의 오류 때문에 전체 작업이 중단되지 않습니다.

DataFrame 생성 & 저장

df = pd.DataFrame(zip(answers, summarys), columns=['answer', 'summary'])
df.to_csv('인터뷰.csv', index=False)

7. 복습 문제 — KoBART 파인튜닝

문제 조건

  • answers, summarys 데이터 중 상위 100개 → train
  • 하위 20개 → validation
  • DatasetDict 생성
  • 모델: digit82/kobart-summarization
  • 토큰화: input 최대 길이 512, output 최대 길이 256
  • Seq2SeqTrainer 로 파인튜닝

풀이

import pandas as pd
from datasets import Dataset, DatasetDict
from transformers import (AutoTokenizer, AutoModelForSeq2SeqLM,
                          DataCollatorForSeq2Seq, Seq2SeqTrainer,
                          Seq2SeqTrainingArguments)
import numpy as np

df = pd.read_csv('인터뷰.csv')

train_df = df.head(100)
valid_df = df.tail(20)

raw_ds = DatasetDict({
    'train': Dataset.from_dict({
        'document': train_df['answer'].tolist(),
        'summary' : train_df['summary'].tolist()
    }),
    'validation': Dataset.from_dict({
        'document': valid_df['answer'].tolist(),
        'summary' : valid_df['summary'].tolist()
    })
})

model_name = 'digit82/kobart-summarization'
tokenizer  = AutoTokenizer.from_pretrained(model_name, use_fast=True)
model      = AutoModelForSeq2SeqLM.from_pretrained(model_name)

max_input_len  = 512
max_target_len = 256

def token_fn(batch):
    inputs = tokenizer(
        batch['document'], max_length=max_input_len,
        padding='max_length', truncation=True
    )
    labels = tokenizer(
        batch['summary'], max_length=max_target_len,
        padding=True, truncation=True
    )

    labels_ids = np.array(labels['input_ids'])
    labels_ids[labels_ids == tokenizer.pad_token_id] = -100
    inputs['labels'] = labels_ids.tolist()
    return inputs

tokenized_ds = raw_ds.map(
    token_fn, batched=True,
    remove_columns=['document', 'summary']
)

data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)

args = Seq2SeqTrainingArguments(
    output_dir       = './kobart_interview',
    eval_strategy    = 'epoch',
    save_strategy    = 'epoch',
    learning_rate    = 5e-5,
    num_train_epochs = 5,
    logging_steps    = 2,

    predict_with_generate  = True,
    generation_max_length   = 64,
    generation_num_beams    = 4
)

trainer = Seq2SeqTrainer(
    model            = model,
    args             = args,
    train_dataset    = tokenized_ds['train'],
    eval_dataset     = tokenized_ds['validation'],
    processing_class = tokenizer,
    data_collator    = data_collator
)

trainer.train()

💡 인터뷰 데이터의 특징
인터뷰 답변(answer)은 일반적으로 뉴스 기사보다 길기 때문에
max_target_len(출력 최대 길이)을 128 → 256 으로 늘려 더 풍부한 요약을 생성하도록 합니다.


📎 핵심 개념 요약

개념설명
BARTEncoder(BERT) + Decoder(GPT) 혼합 Seq2Seq 모델
<PAD>/<UNK>/<SEP>/</s>패딩/미등록/문장구분/종료 특수 토큰
DatasetDicttrain/validation 등을 하나의 객체로 관리
labels의 -100CrossEntropyLoss가 무시하는 마스킹 값 (패딩 손실 제외)
DataCollatorForSeq2SeqSeq2Seq 전용 동적 패딩 + 디코더 입력 생성
ROUGE-1/2/Ln-gram 겹침 / 최장 공통 부분열 기반 평가
rouge.compute(tokenizer=...)형태소 분석기 기준으로 ROUGE 계산
predict_with_generate평가 시 실제 문장 생성 후 비교
num_beams빔 서치 후보 문장 개수
do_sample=False빔서치(결정적) / True: 확률 샘플링(다양성)
length_penalty짧은/긴 문장 생성 선호도 조절
no_repeat_ngram_sizen-gram 반복 방지
repetition_penalty토큰 반복에 패널티
json.load(f)JSON 파일을 dict로 파싱
with open(...) as f:파일 자동 닫기 컨텍스트 매니저
try/except일부 파일 오류가 전체 중단을 막음
to_csv(index=False)인덱스 제외하고 CSV 저장
profile
공부 기록

0개의 댓글