[NLP] 0-5. Coverage에 따른 EDA 및 전략

Paper2Code·2025년 8월 7일

NLP Project

목록 보기
6/12
post-thumbnail
  • 커버리지 비율 기반 분석 → 토픽 분류 → 모델 선택 전략

🔍 Coverage 정의

📌 1. 데이터 커버리지

  • 정의: 전체 데이터 중 특정 범주, 조건, 특성 등을 얼마나 잘 포함하고 있는지를 나타냅니다.
  • 예시:
    • 대화 주제 커버리지 → 일상, 쇼핑, 업무, 병원 등 다양한 주제를 포함하고 있는지.
    • 발화자 수 커버리지 → 2인 대화부터 7인까지 다양한 경우가 포함되는지

📌 2. 모델 커버리지

  • 정의: 모델이 학습 데이터 또는 실제 데이터 분포의 다양한 양상을 얼마나 잘 다루는지를 의미합니다.
  • 관련 맥락:
    • 특정 주제나 표현 방식에만 과적합(overfitting)되지 않고, 여러 스타일의 대화를 잘 요약할 수 있는 능력을 뜻합니다.
    • 다양한 길이, 말투, 문법 오류 등에도 강건하게 요약을 잘 수행하면 커버리지가 넓다고 평가합니다.

📌 3. 평가 커버리지 (Evaluation Coverage)

  • 정의: 평가 지표나 방식이 다양한 모델의 특성을 얼마나 잘 반영하는지.
  • DialogSum 대회에서는:
    • 하나의 대화에 대해 3개의 요약문(Golden Summary) 이 제공되므로, 다양한 관점에서 모델을 평가함으로써 “커버리지”가 높은 평가를 지향합니다.

📌 4. EDA에서의 커버리지

  • EDA에서 주제, 발화자 수, 길이, 발화 패턴 등을 분석하면서 커버리지 수준을 파악할 수 있습니다.
  • 예: “상위 30개 주제의 분포를 확인하여 전체 데이터가 특정 주제에 쏠려 있지 않은지 확인 → 주제 커버리지 확인”

✅ 1. Extractiveness (추출성) / Copy Rate

  • 정의: 요약문이 원문에서 직접 발췌한 단어(또는 문장) 로 얼마나 구성되었는지를 나타냅니다.
  • 측정 예시:
    • 요약문 단어 중 원문에 실제로 존재하는 단어 비율
    • → 80% 이상이면 “추출적(extractive)” 요약
    • → 낮으면 “생성적(abstractive)” 요약
  • 이것을 커버리지라고 부르는 경우도 있지만, 일반적으로는 Extractiveness 지표 또는 Copy Rate로 따로 구분해서 표현합니다.

✅ 2. Coverage in Attention Mechanism (기계 번역/요약 모델 용어)

  • Transformer나 seq2seq 구조에서는 “coverage vector”라는 개념이 있어요.
    • 정의: 모델이 원문에서 어느 부분을 얼마나 참고했는지를 추적하는 벡터
    • 의의: 중복 요약 방지, 누락 방지 등
    • 이때의 "coverage"는 attention 분포의 누적값으로 계산됩니다.

✅ 3. 요약 적합성 평가에서의 활용

  • ROUGE만으로는 생성 요약의 품질을 완벽히 평가하기 어렵기 때문에,
    • 추출율 (Copy Coverage)
    • 추가율 (Novelty Rate)
    • 등을 함께 봐야 모델의 추론 다양성과 안정성을 판단할 수 있습니다.
구분추출형 요약 (Extractive)생성형 요약 (Abstractive)
정의원문에서 문장 또는 단어를 그대로 복사해서 요약문을 구성원문 내용을 새로운 표현으로 재구성해서 요약
방식핵심 문장을 골라내서 붙이기원문 내용을 이해하고 다시 쓰기
예시대화: "내일 병원 가야 해서 일찍 일어나야 돼." → 요약: "내일 병원 가야 해." (원문 그대로)요약: "병원 일정이 있어 아침 일찍 준비함." (새로운 표현 사용)
장점문법 오류 없음, 안전함더 간결하고 자연스러움
단점부자연스러울 수 있음잘못 요약하거나 사실을 왜곡할 수 있음
모델 예시LexRank, BERTSum (BERT 기반)BART, T5, GPT 등

사전, 라이브러리 버전 호환성
패키지안정 버전 추천
numpy1.23.5
scipy`1.10.1
numba0.57.1
umap-learn0.5.5
scikit-learn1.2.2

1️⃣ 요약문이 원문에서 등장하는(+등장하지 않는) 단어 비율 정도

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 는 어디서 생성?

preprocess_ko(text)

  • 목적: 텍스트를 분석 가능한 형태로 정제
  • 주요 단계:
    1. 특수 토큰(#Person1#) → SPECIALA 등의 임시 키로 치환
    2. 구두점 제거
    3. 형태소 분석 + 불용어 제거
    4. 치환했던 특수 토큰 복원
  • 장점: 화자 정보 보존 + 노이즈 제거
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

calculate_coverage_ratio(dialogue, summary)

  • 목적: 주어진 요약문이 원문에서 얼마나 많은 단어를 그대로 사용했는지 비율 계산
  • 출력: (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
)

✅ 시각화 해석

🎯 1. Coverage Ratio 분포 (왼쪽 그래프)
  • 대부분의 데이터가 0.6 ~ 0.75 구간에 몰려 있음
  • 이는 요약문 대부분이 원문에서 꽤 많은 단어를 복사해 사용하고 있음을 뜻합니다.
  • 하지만 0.9 이상은 드물기 때문에, 완전한 추출형은 아니다는 것도 알 수 있어요.
    👉 의미: 추출형과 생성형 사이의 중간적 요약이 많음 (semi-abstractive)
🎯 2. Non-Coverage Ratio 분포 (오른쪽 그래프)
  • 대부분이 0.2 ~ 0.45 사이에 분포 → 요약문 단어 중 약 20~45%는 원문에 없던 단어
  • 생성형 요약도 어느 정도 존재함을 보여줌
  • 0.6 이상 되는 경우는 거의 없음 → 완전 생성형 요약은 드뭄
    👉 의미: 대부분 요약문은 원문 기반 생성을 하고 있으나, 일부 표현은 새로 만들어진다

🧠 종합 인사이트

항목결과 요약
전체 분포 경향추출형과 생성형이 혼합된 요약 스타일이 지배적
데이터 특성대부분은 추출 기반이나, 어느 정도 생성이 포함됨
모델 전략 시사점복사+생성 혼합형 모델 (예: BART, T5) 또는 coverage-aware 전략 적합

2️⃣ high, mid, low Coverage 분류

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 비율 범위의미
low0.0 ~ 0.3거의 생성형 요약 (원문 단어 거의 안 씀)
mid0.3 ~ 0.6추출형과 생성형이 혼합된 스타일
high0.6 ~ 1.0대부분 추출형 (원문 단어 다수 포함)

BART 모델 로드 및 special token 반영

  • Huggingface BART 모델에 대회 특수 토큰(#Person1#, #Agent#, 등)을 반영하고,
  • 이를 기반으로 대화문을 임베딩할 준비
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)
  • 목적: 각 coverage group에 대해 표현 양상의 차이를 파악할 수 있는 고차원 벡터를 얻는 것
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
  • 전체 토큰의 hidden state를 평균내어 하나의 문장 벡터로 생성 (mean pooling).
  • shape: [batch_size, hidden_dim]
  • 전체 문장들의 임베딩을 리스트 형태로 반환.
  • 각 항목은 텐서이며, BART의 [CLS] 토큰이 없기 때문에 전체 평균(mean pooling)으로 대체.

coverage_bin 기준으로 텍스트 필터링

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

3️⃣ UMAP 차원 축소 -> HDBSCAN 클러스터링

“내 텍스트 임베딩들이 의미적으로 어떤 군집을 형성하고 있는가?”
→ 이를 시각적으로 확인하고, 자동으로 클러스터 라벨링(HDBSCAN)까지 진행하기 위한 목적입니다.

✅ 1. 입력 정리

X = np.stack([emb.numpy() if hasattr(emb, "numpy") else emb for emb in embeddings])
  • embeddings는 SBERT 등의 출력 (벡터 리스트)
  • torch.Tensor 또는 np.array 형태를 통일해서 np.stack() 사용

✅ 2. UMAP 차원 축소

X_umap = umap.UMAP(...).fit_transform(X)
  • 원래 임베딩은 384~768차원 → 시각화 불가
  • UMAP으로 2차원으로 의미 보존하면서 축소
  • min_dist, n_neighbors, metric은 거리 기준과 클러스터 밀도 조절
X_umap = (X_umap - X_umap.min(0)) / (X_umap.max(0) - X_umap.min(0))
  • 축소된 결과를 0~1 범위로 normalize → 그래프 가독성 향상

✅ 3. HDBSCAN 클러스터링

clusterer = hdbscan.HDBSCAN(min_cluster_size=min_cluster_size).fit(X_umap) labels = clusterer.labels_
  • 밀도 기반 클러스터링: KMeans와 달리 cluster 수를 정하지 않아도 됨
  • min_cluster_size: 군집으로 인정할 최소 점 개수
  • labels: 군집 ID (-1은 노이즈)

✅ 4. 시각화

plt.scatter(X_umap[:,0], X_umap[:,1], c=labels, cmap='Spectral', s=10)
  • UMAP 축소 결과를 2D 평면에 뿌림
  • 색깔은 HDBSCAN 결과를 기반으로 자동 군집 라벨링
  • 제목에 title 추가

Coverage 비율별 대화가 임베딩 공간에서 어떻게 군집을 이루는가?

dialogue 로 coverage 그룹 분리

summary로 Coverage 그룹 분리

1️⃣ High Coverage (추출적 요약 성향 ↑)

  • 특징: 대부분 하나의 클러스터에 몰려 있음
  • 해석: 원문 문장 그대로 요약에 많이 사용됨. 구조/어휘의 다양성이 적음.
  • 시사점:
    • 모델이 원문 copy만 잘해도 높은 성능 가능
    • 학습에 용이, 추론 속도 빠름
    • ➕ Oracle 기반 extractive model에 유리한 구간

2️⃣ Mid Coverage (혼합 성향)

  • 특징: 여러 중형 클러스터 + 주변 노이즈
  • 해석: 일부 추출, 일부 생성.
    → 대화 주제나 표현 방식에 따라 요약 스타일이 달라지는 부분.
  • 시사점:
    • 요약 방식 다양성 존재
    • 학습에 상대적으로 어려움
    • ➕ condition-aware decoder나 reinforcement 요약 방식 필요 가능성

3️⃣ Low Coverage (생성적 요약 성향 ↑)

  • 특징: 문장이 흩어져 있고, 클러스터 내 데이터 수 적음
  • 해석: 요약이 거의 새로운 단어, 재구성된 문장으로 이루어짐
  • 시사점:
    • 모델이 원문을 이해하고 새로 표현해야 함 → 추론 난이도 상승
    • 요약 길이도 짧은 경우 많아 extreme summarization일 가능성
    • ➕ 사전 훈련이 잘 된 abstractive model, 특히 T5/BART가 유리

이후 추가 EDA 진행

  • 커버리지 비율에 따라 점수 어떻게 나오는지 확인. (high, middle, low)
    - 이후 토픽들이 어느 커비리지 그룹에 속해있는지 확인해보고 싶음.
    * 경향성 보이면, 원본을 통해 토픽을 예측하는 모델 생성 후, 좀 더 타겟팅.

원문만 보고 coverage 자체를 비율 : 이를 test 데이터셋에 예측하여 coverage 예측.

coverage 분석: 요약문의 단어 중에 원문에 존재하는 단어를 사용한 비율(Coverage) 확인

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

profile
As I Imagine | 이론 정리 사이트 Tistory 링크 참조 ↓ 홈 아이콘 클릭)

0개의 댓글