본문 바로가기
데이터&AI/LLM

LLM의 요약을 잘했는지 평가하는 방법! ROUGE 점수! (with python code)

by 일등박사 2024. 11. 9.
728x90

점수의 타입!!

 

 

 

chatgpt이후로!

LLM을 통한 요약, 변역에 정말 효과적이어서 

많은 분들이 사용하고 있는데요~~

이때 정말 요약을 잘하는걸까? 에 대하여

어떤 모델이 더 요약을 잘할까? 를 평가하는 지표가 있어 오늘 소개하고자합니다!!

 

 

 

바로 ROUGE (Recall-Oriented Understudy for Gisting Evaluation) 점수!!! 인데요!!

 

이 ROUGE 점수가 어떻게 계산되는지,

python 코드로 이 점수를 구하는 방법을 오늘 알아보겠습니다!!


ROUGE 점수의 타입 및 평가방법!

ROUGE 점수는 요약 성능을 평가하는 주요 지표입니다.

요약 결과 내의 텍스트가 기준 텍스트와 얼마나 일치하는지를 측정합니다.

 

ROUGE는 주로 n-그램(연속된 단어의 집합) 일치를 기반으로 하는 평가 지표인데,

ROUGE 점수에는 다양한 변형방식이 존제합니다!!

 

점수의 타입!!

 

1. ROUGE-N: 기준 텍스트와 생성 텍스트 간의 n-그램(예: 1-그램, 2-그램 등) 일치를 측정  

            ex) ROUGE -1 은 단어 하나 하나의 일치여부를 ( 단어 단위의 정확도),

                  ROUGE-2는 두 단어씩 묶어 평가 ( 구 또는 짧은 문장 단위의 일치도 평가)

 

2. ROUGE-L: 가장 긴 공통 부분 (Longest Common Subsequence, LCS)를 사용, 요약의 순서를 고려한 일치도 측정!!

        >> 생성 텍스트와 기준 텍스트 간에 얼마나 비슷한 순서로 내용이 나타나는지를 평가

        =  요약의 순서와 연속성을 반영

 

3. ROUGE-W: W가 weighted 의 약자로서!! ROUGE-L과 유사하지만, 연속적인 일치에 가중치를 부여하여 더 긴 연속 일치 구간에 더 높은 점수 부여

 

4.ROUGE-S: Skip-Bigram을 기반으로, 단어 간의 거리가 다소 멀어도 일치하는 경우를 인정다. 단어 사이에 다른 단어들이 있어도 일치로 간주하는 융통성을 가진 평가 방식

 

점수의 해석(평가방법)!!

 

ROUGE 점수는 Precision, Recall, F1-Score로 나누어 평가됩니다:

  • Recall: 기준 텍스트에 있는 중요한 n-그램을 얼마나 많이 포함했는지 평가 
  • Precision: 생성된 요약 텍스트에서 기준 텍스트와 일치하는 n-그램의 비율을
  • F1-Score: Precision과 Recall의 조화 평균으로, 두 점수의 균형

ROUGE의 장점과 한계

ROUGE 점수는 대량의 텍스트 평가에서 빠르게 성능을 비교할 수 있다는 점에서 매우 유용합니다. 특히, 여러 모델을 비교할 때 상대적인 성능 차이를 직관적으로 파악할 수 있습니다. 그러나 텍스트의 의미와 문맥적 유사성을 완벽히 반영하지 못한다는 한계가 있습니다. 같은 의미의 동의어를 사용한 경우에도 ROUGE 점수가 낮을 수 있고, 단순히 n-그램을 맞추는 것이 요약의 진정한 품질을 보장하지는 않기 때문에 사람의 평가와 함께 보조적으로 사용하는 것이 이상적입니다.


예시 코드: ROUGE 점수 계산하기

Python의 rouge-score 라이브러리를 사용하여 간단히 ROUGE 점수를 계산할 수 있습니다.

 

1. 라이브러리 설치 : pip로 간단하게 설치합닏!

pip install rouge_score

 

2. rouge 점수 계산하기!

 > 저는 rouge1 / 2 / L 점수를 계산 해보겠습니다!

from rouge_score import rouge_scorer

# 기준 텍스트와 생성된 요약 텍스트
reference_text = """
일등박사는 대한민국 출신의 LLM, 프롬프트 엔지니어링 블로거로서 다양한 글들을 연재하고있습니다. 
그의 주된 관심사는 생성형 AI모델을 자체의 기술과 그 기술을 여러 분야에 적용시켜보는것 입니다.
이 외에도 금융시장, 블록체인에 대하여 관심이 많은 블로거로서 많은글들을 사용하며 네티즌들에게 사람받고있습니다
한편으로는 블록체인에 대하여
"""
generated_summary = "일등박사는 대한민국 출신의 LLM 및 프롬프트 엔지니어링 블로거입니다"

# ROUGE 점수 계산
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(reference_text, generated_summary)

print(scores)

 

그결과!!

{'rouge1': Score(precision=1.0, recall=0.5, fmeasure=0.6666666666666666)
, 'rouge2': Score(precision=0.0, recall=0.0, fmeasure=0.0)
, 'rougeL': Score(precision=1.0, recall=0.5, fmeasure=0.6666666666666666)}

 

간단하계 rouge 의 타입별 스코어를 볼 수 있습니다. 

해석해보면 단어단위 (rouge1) 및 요약의 연속성 (rougeL)에서는 괜찮은 점수 (0.6)을 받았지만, 

문장단위 일치도(roung2) 에서는 낮은 점스를 받았습니다!

 

 

이번엔!

패키지가 아니라 직접 코드를 통해서 구해보겠습니다!!

 

1, rouge1 !!

각각의 문장을 쪼갠 뒤 precision과 recall을 구해서, 잘 가공하여 f1_score를 만듧니다!!

def rouge_1_score(reference, candidate):
    # 기준(reference)과 생성된 텍스트(candidate)를 단어로 분리
    ref_words = reference.split()
    cand_words = candidate.split()
    
    # 각 텍스트의 단어 집합 생성
    ref_word_count = len(ref_words)
    cand_word_count = len(cand_words)
    
    # 단어 일치 수 계산
    matches = sum(1 for word in cand_words if word in ref_words)
    
    # Precision, Recall 계산
    precision = matches / cand_word_count if cand_word_count > 0 else 0
    recall = matches / ref_word_count if ref_word_count > 0 else 0
    
    # F1-Score 계산
    if precision + recall == 0:
        f1_score = 0
    else:
        f1_score = 2 * (precision * recall) / (precision + recall)
    
    return {
        "ROUGE-1 Precision": precision,
        "ROUGE-1 Recall": recall,
        "ROUGE-1 F1-Score": f1_score
    }



# ROUGE-1 점수 계산
rouge_1_result = rouge_1_score(reference_text, generated_summary)
print(rouge_1_result)

 

2, rouge2 !!

ngram을 만들어서 복잡한계산!! rouge1과는 많이 다르지요~!?

from collections import Counter

def get_ngrams(text, n=2):
    words = text.split()
    ngrams = zip(*[words[i:] for i in range(n)])
    return [" ".join(ngram) for ngram in ngrams]

def rouge_2_score(reference, candidate):
    # 기준(reference)과 생성된 텍스트(candidate)의 2-그램을 생성
    ref_bigrams = get_ngrams(reference, n=2)
    cand_bigrams = get_ngrams(candidate, n=2)
    
    # 2-그램 카운트
    ref_bigram_count = Counter(ref_bigrams)
    cand_bigram_count = Counter(cand_bigrams)
    
    # 일치하는 2-그램 수 계산
    overlap = sum((cand_bigram_count & ref_bigram_count).values())
    
    # Precision, Recall 계산
    precision = overlap / sum(cand_bigram_count.values()) if sum(cand_bigram_count.values()) > 0 else 0
    recall = overlap / sum(ref_bigram_count.values()) if sum(ref_bigram_count.values()) > 0 else 0
    
    # F1-Score 계산
    if precision + recall == 0:
        f1_score = 0
    else:
        f1_score = 2 * (precision * recall) / (precision + recall)
    
    return {
        "ROUGE-2 Precision": precision,
        "ROUGE-2 Recall": recall,
        "ROUGE-2 F1-Score": f1_score
    }


# ROUGE-2 점수 계산
rouge_2_result = rouge_2_score(reference_text, generated_summary)
print(rouge_2_result)

 

 

 

 

이처럼 직접 rouge를 계싼할 수도 있지만!! 처음 나온 패키기를 통해 계산하는게 간단하고 빠릅니다!!^^

 

+ 그런데!! 두개의 점수가 다른것은 왜일까요!?

 

  1. 어근(stemming) 처리: rouge_scorer.RougeScorer는 use_stemmer=True 설정을 통해 단어의 어근을 추출하여 일치 여부를 비교합니다. 예를 들어, "블로거입니다"와 "블로거"가 같은 어근으로 처리되므로 일치율이 높아질 수 있습니다. 반면 scratch로 계산한 코드에는 어근 처리 기능이 없으므로 정확히 일치하는 단어만 계산합니다.
  2. 토큰화 방식의 차이: rouge_score 라이브러리는 일반적으로 단어 경계, 구두점 등 텍스트 토큰화를 보다 정교하게 처리하여 ROUGE 점수를 계산합니다. 반면 scratch 구현에서는 단순히 공백으로만 단어를 분리했기 때문에, 구두점이 포함된 단어들은 다르게 인식될 수 있습니다.
  3. 정확한 2-그램 계산 방식의 차이: rouge_score 라이브러리에서는 2-그램 생성과 비교 과정이 더욱 정밀하게 처리될 수 있습니다. 예를 들어, 일부 라이브러리는 중복된 2-그램을 다루는 방식이 다를 수 있으며, 공백이나 구두점 처리 등에서도 차이가 발생할 수 있습니다.
  4. 길이 패널티: rouge_score 라이브러리는 일부 변형된 ROUGE 메트릭에 대해 길이 패널티를 적용하여 짧은 텍스트의 점수를 조정할 수도 있습니다.

따라서, scratch 구현과 rouge_score 라이브러리의 계산 결과는 어근 처리, 토큰화, 길이 패널티 등 세부적인 구현 차이로 인해 다를 수 있습니다.

 

일관된 점수를 원할 경우 라이브러리 사용이 권장됩니다.

728x90

댓글