Recall-Oriented Understudy for Gisting Evaluation
ROUGE는 Recall-Oriented Understudy for Gisting Evaluation 의 약자로, 모델이 생성한 텍스트가 정답과 얼마나 겹치는지 측정하는 지표다. 원래 요약(summarization) 평가용으로 만들어졌지만, QA나 번역 등 텍스트 생성 전반에서 쓰인다.
핵심 아이디어는 단순하다: 정답에 있는 단어가 예측에 얼마나 등장하는가?
모든 ROUGE 변형은 동일한 프레임워크를 따른다. 겹침(overlap)을 세는 단위만 달라질 뿐이다.
\[\text{Recall} = \frac{|\text{overlap}|}{|\text{Reference}|}\] \[\text{Precision} = \frac{|\text{overlap}|}{|\text{Prediction}|}\] \[F_1 = \frac{2 \times P \times R}{P + R}\]evaluate가 기본 리포트하는 값이 이것이다.아래 예시로 모든 ROUGE를 계산해본다.
정답(Reference): "서울의 인구는 약 천만 명이고 대한민국의 수도이다"
예측(Prediction): "서울은 대한민국의 수도이며 인구는 약 구백만 명이다"
토큰 분리 (공백 기준):
R = [서울의, 인구는, 약, 천만, 명이고, 대한민국의, 수도이다] → m = 7
P = [서울은, 대한민국의, 수도이며, 인구는, 약, 구백만, 명이다] → n = 7
각 unigram $w$에 대해, 정답에서의 출현 횟수와 예측에서의 출현 횟수 중 작은 값을 취해 합산한다.
| 정답 토큰 | 예측에 있나? |
|---|---|
| 서울의 | ❌ (“서울은”과 다른 토큰) |
| 인구는 | ✅ |
| 약 | ✅ |
| 천만 | ❌ (“구백만”과 다른 토큰) |
| 명이고 | ❌ (“명이다”와 다른 토큰) |
| 대한민국의 | ✅ |
| 수도이다 | ❌ (“수도이며”와 다른 토큰) |
겹치는 토큰: {인구는, 약, 대한민국의} → $|\text{overlap}| = 3$
unigram 대신 연속 2개 단어 쌍(bigram)으로 겹침을 센다.
정답 bigrams (6개):
(서울의, 인구는) (인구는, 약) (약, 천만)
(천만, 명이고) (명이고, 대한민국의) (대한민국의, 수도이다)
예측 bigrams (6개):
(서울은, 대한민국의) (대한민국의, 수도이며) (수도이며, 인구는)
(인구는, 약) (약, 구백만) (구백만, 명이다)
겹치는 bigram:
(인구는, 약) ✅
나머지 전부 ❌
$|\text{overlap}| = 1$
ROUGE-2가 ROUGE-1보다 낮은 이유: “인구는”과 “약”이 각각 겹쳐도, 이 둘이 연속으로 나타나야만 bigram으로 잡힌다. “대한민국의”가 겹치지만 앞뒤 토큰이 달라서 어떤 bigram에도 기여하지 못한다.
LCS(Longest Common Subsequence)의 길이를 이용한다.
\[R_{lcs} = \frac{\text{LCS}(R, P)}{m}\] \[P_{lcs} = \frac{\text{LCS}(R, P)}{n}\] \[F_{lcs} = \frac{(1 + \beta^2) \times R_{lcs} \times P_{lcs}}{R_{lcs} + \beta^2 \times P_{lcs}}\]$m$ = 정답 길이, $n$ = 예측 길이, $\beta = P_{lcs} / R_{lcs}$ (이렇게 설정하면 $F_{lcs} = F_1$과 동일해진다)
핵심: subsequence는 연속일 필요 없다. 원래 순서만 유지하면 건너뛸 수 있다.
R = [서울의, 인구는, 약, 천만, 명이고, 대한민국의, 수도이다]
P = [서울은, 대한민국의, 수도이며, 인구는, 약, 구백만, 명이다]
DP 테이블 $L[i][j]$ = $R[0..i]$와 $P[0..j]$의 LCS 길이:
| 서울은 | 대한민국의 | 수도이며 | 인구는 | 약 | 구백만 | 명이다 | |
|---|---|---|---|---|---|---|---|
| 서울의 | 0 | 0 | 0 | 0 | 0 | ||
| 인구는 | 0 | 0 | 0 | 1 | 1 | ||
| 약 | 0 | 0 | 0 | 1 | 2 | ||
| 천만 | 0 | 0 | 0 | 1 | 2 | ||
| 명이고 | 0 | 0 | 0 | 1 | 2 | ||
| 대한민국의 | 0 | 1 | 1 | 1 | 2 | ||
| 수도이다 | 0 | 1 | 1 | 1 | 2 |
$\text{LCS}(R, P) = 2$ → [인구는, 약]
“대한민국의”가 양쪽에 존재하지만, 정답에서는 6번째(인구는, 약 뒤), 예측에서는 2번째(인구는, 약 앞)이므로 [인구는, 약]과 동시에 LCS에 포함될 수 없다.
이 예시에서 ROUGE-L < ROUGE-1이다. ROUGE-1은 순서를 무시하고 {인구는, 약, 대한민국의} 3개를 세지만, ROUGE-L은 순서 제약 때문에 2개만 센다.
R = [A, B, C]
P = [C, B, A]
ROUGE-1: overlap = {A, B, C} → 3/3 = 1.0
ROUGE-L: LCS = [A] 또는 [B] 또는 [C] → 1/3 = 0.33
모든 단어가 있지만 순서가 완전히 뒤집혀서 ROUGE-L은 크게 떨어진다. 이것이 ROUGE-L의 존재 이유다.
다중 문장에서 문장별로 LCS를 따로 구한 뒤 합산한다.
정답이 문장 $r_1, r_2, \ldots, r_k$로 구성될 때:
\[R_{lsum} = \frac{\sum_{i=1}^{k} \text{LCS}(r_i, P)}{m}\]정답:
r1: "서울의 인구는 약 천만 명이다" (5토큰)
r2: "서울은 대한민국의 수도이다" (3토큰)
예측:
"서울은 대한민국의 수도이며 인구는 약 구백만 명이다"
LCS(r1, P) = [인구는, 약] → 2
LCS(r2, P) = [서울은, 대한민국의] → 2
Recall = (2 + 2) / (5 + 3) = 4/8 = 0.5
ROUGE-L을 전체에 한 번 적용하면 문장 경계를 무시해서 한 문장의 매칭이 다른 문장의 점수를 방해할 수 있다. ROUGE-Lsum은 문장별로 독립 계산하므로 요약처럼 여러 문장이 있는 태스크에서 더 공정하다.
단일 문장이면 ROUGE-L = ROUGE-Lsum.
| 지표 | 수식의 overlap 단위 | 예시 F1 | 특징 |
|---|---|---|---|
| ROUGE-1 | unigram | 0.4286 | 가장 관대. 순서 무시 |
| ROUGE-2 | bigram | 0.1667 | 구문 유사도. 연속 매칭 필요 |
| ROUGE-L | LCS | 0.2857 | 순서 고려. 비연속 허용 |
| ROUGE-Lsum | 문장별 LCS 합 | - | 다중 문장에서 ROUGE-L 개선 |
일반적으로: ROUGE-1 >= ROUGE-L >= ROUGE-2
정답: "기쁘다"
예측: "행복하다"
→ ROUGE = 0 (의미는 같지만 토큰이 다름)
정답: "교향곡" → m = 1
예측: "바그너는 교향곡을 쓰려 했다" → n = 4
Recall = 1/1 = 1.0 (정답 토큰을 다 잡음)
Precision = 1/4 = 0.25 (예측의 75%가 불필요)
F1 = 2 × (1.0 × 0.25) / (1.0 + 0.25) = 0.4
정답: "2023년"
예측: "2024년"
→ ROUGE-1 = 0이지만, 사실상 거의 맞춘 것
이런 한계 때문에 실무에서는 ROUGE 단독이 아니라 BERTScore(임베딩 유사도), Exact Match, Human Evaluation 등을 함께 쓴다.
import evaluate
rouge = evaluate.load("rouge")
predictions = ["서울은 대한민국의 수도이며 인구는 약 구백만 명이다"]
references = ["서울의 인구는 약 천만 명이고 대한민국의 수도이다"]
results = rouge.compute(predictions=predictions, references=references)
print(results)
# {'rouge1': 0.4286, 'rouge2': 0.1667, 'rougeL': 0.2857, 'rougeLsum': 0.2857}