
| 구분 | 추출형 요약 (Extractive) | 생성형 요약 (Abstractive) |
|---|---|---|
| 정의 | 원문에서 문장 또는 단어를 그대로 복사해서 요약문을 구성 | 원문 내용을 새로운 표현으로 재구성해서 요약 |
| 방식 | 핵심 문장을 골라내서 붙이기 | 원문 내용을 이해하고 다시 쓰기 |
| 예시 | 대화: "내일 병원 가야 해서 일찍 일어나야 돼." → 요약: "내일 병원 가야 해." (원문 그대로) | 요약: "병원 일정이 있어 아침 일찍 준비함." (새로운 표현 사용) |
| 장점 | 문법 오류 없음, 안전함 | 더 간결하고 자연스러움 |
| 단점 | 부자연스러울 수 있음 | 잘못 요약하거나 사실을 왜곡할 수 있음 |
| 모델 예시 | LexRank, BERTSum (BERT 기반) | BART, T5, GPT 등 |
| 패키지 | 안정 버전 추천 |
|---|---|
numpy | 1.23.5 |
scipy | `1.10.1 |
numba | 0.57.1 |
umap-learn | 0.5.5 |
scikit-learn | 1.2.2 |
import string
from konlpy.tag import Okt
#형태소 분석기
okt = Okt()
# 알파벳 치환용 문자 리스트
ALPHABET = list(string.ascii_uppercase)
stop_words_path = "../data/stopwords-ko.txt"
with open(stop_words_path, "r", encoding="utf-8") as f:
stop_words_ko = set([line.strip() for line in f])
❓❓ stopwords-ko.txt 는 어디서 생성?
#Person1#) → SPECIALA 등의 임시 키로 치환def preprocess_ko(text):
# 1. 소문자 변환 (영어 혼용시 유용)
# 특수 토큰(#Person1# 같은 것)을 따로 추출
special_tokens = set(re.findall(r"#\w+#", text)) # 예: #Person1#
token_map = {f"SPECIAL{ALPHABET[i]}": token for i, token in enumerate(special_tokens)}
# 3. 치환
for key, token in token_map.items():
text = text.replace(token, key)
# 2. 구두점 제거
text = text.translate(str.maketrans('', '', string.punctuation))
# 3. 형태소 분석 및 불용어 제거
words = okt.morphs(text, stem=True)
words = [word for word in words if word not in stop_words_ko and word.strip()]
# 다시 특수 토큰 복원
for key, token in token_map.items():
words = [token if t==key else t for t in words]
return words
(overlap_ratio, nonoverlap_ratio) 튜플 형태overlap_ratio: 추출성(Extractiveness) 지표 nonoverlap_ratio: 생성성(Abstractive tendency)def calculate_coverage_ratio(dialogue: str, summary: str) -> float:
# 전처리된 단어 리스트
dialogue_tokens = set(preprocess_ko(dialogue)) # 중복 제거
summary_tokens = preprocess_ko(summary)
if not summary_tokens:
return 0.0
# 요약문 단어 중 원문에도 존재하는 것만 필터링
overlap = [word for word in summary_tokens if word in dialogue_tokens]
nonoverlap = [word for word in summary_tokens if word not in dialogue_tokens]
overlap_ratio = len(overlap) / len(summary_tokens)
nonoverlap_ratio = len(nonoverlap) / len(summary_tokens)
return overlap_ratio, nonoverlap_ratio
train_df[['coverage_ratio', 'noncoverage_ratio']] = train_df.apply(
lambda row: pd.Series(calculate_coverage_ratio(row['dialogue'], row['summary'])),
axis=1
)

| 항목 | 결과 요약 |
|---|---|
| 전체 분포 경향 | 추출형과 생성형이 혼합된 요약 스타일이 지배적 |
| 데이터 특성 | 대부분은 추출 기반이나, 어느 정도 생성이 포함됨 |
| 모델 전략 시사점 | 복사+생성 혼합형 모델 (예: BART, T5) 또는 coverage-aware 전략 적합 |
bins = [0.0, 0.3, 0.6, 1.0]
labels = ['low', 'mid', 'high']
train_df['coverage_bin'] = pd.cut(train_df['coverage_ratio'], bins=bins, labels=labels)
| 그룹 | coverage 비율 범위 | 의미 |
|---|---|---|
| low | 0.0 ~ 0.3 | 거의 생성형 요약 (원문 단어 거의 안 씀) |
| mid | 0.3 ~ 0.6 | 추출형과 생성형이 혼합된 스타일 |
| high | 0.6 ~ 1.0 | 대부분 추출형 (원문 단어 다수 포함) |
from transformers import BartTokenizer, BartModel
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = BartTokenizer.from_pretrained("facebook/bart-base")
special_tokens_dict = {
"additional_special_tokens": config["tokenizer"]["special_tokens"]
}
tokenizer.add_special_tokens(special_tokens_dict)
model = BartModel.from_pretrained("facebook/bart-base")
model.resize_token_embeddings(len(tokenizer))
model.to(device)
model.eval()
[B, H] 크기의 문장 임베딩 (mean pooling)def get_bart_embeddings_batch(texts, batch_size=32):
all_embeddings = []
for i in tqdm(range(0, len(texts), batch_size), desc="Embedding batches"):
batch_texts = texts[i:i + batch_size]
inputs = tokenizer(
batch_texts,
return_tensors="pt",
padding=True,
truncation=True,
max_length=512
).to(device)
with torch.no_grad():
outputs = model(**inputs)
embeddings = outputs.last_hidden_state.mean(dim=1) # [B, H]
all_embeddings.extend(embeddings.cpu()) # 개별 tensor 저장
return all_embeddings # [CLS] 유사 mean pooling
[batch_size, hidden_dim][CLS] 토큰이 없기 때문에 전체 평균(mean pooling)으로 대체.coverage_bin 기준으로 텍스트를 필터링하고 해당 텍스트들의 임베딩을 얻습니다.
def get_bin_embeddings(bin, df):
df_bin = df[df["coverage_bin"] == bin]
texts = df_bin["dialogue"].tolist()
embeddings = get_bart_embeddings_batch(texts, batch_size=32)
return embeddings
“내 텍스트 임베딩들이 의미적으로 어떤 군집을 형성하고 있는가?”
→ 이를 시각적으로 확인하고, 자동으로 클러스터 라벨링(HDBSCAN)까지 진행하기 위한 목적입니다.
X = np.stack([emb.numpy() if hasattr(emb, "numpy") else emb for emb in embeddings])
embeddings는 SBERT 등의 출력 (벡터 리스트)torch.Tensor 또는 np.array 형태를 통일해서 np.stack() 사용X_umap = umap.UMAP(...).fit_transform(X)
min_dist, n_neighbors, metric은 거리 기준과 클러스터 밀도 조절X_umap = (X_umap - X_umap.min(0)) / (X_umap.max(0) - X_umap.min(0))
clusterer = hdbscan.HDBSCAN(min_cluster_size=min_cluster_size).fit(X_umap) labels = clusterer.labels_
min_cluster_size: 군집으로 인정할 최소 점 개수labels: 군집 ID (-1은 노이즈)plt.scatter(X_umap[:,0], X_umap[:,1], c=labels, cmap='Spectral', s=10)
title 추가

원문만 보고 coverage 자체를 비율 : 이를 test 데이터셋에 예측하여 coverage 예측.
coverage 분석: 요약문의 단어 중에 원문에 존재하는 단어를 사용한 비율(Coverage) 확인

coverage에 따른 요약 난이도 분석: Coverage ratio를 5개의 영역으로 binning하여 각 영역에서 모델 예측 후 rouge score 확인
