Automated Metrics

개요

자동화된 평가 지표는 LLM 모델의 성능을 객관적이고 일관되게 측정하는 수치적 방법입니다. 이러한 지표들은 대량의 데이터에 대해 신속하게 평가를 수행할 수 있으며, 인간 평가와 함께 사용하여 모델의 전반적인 성능을 종합적으로 평가합니다.

기존 NLP 평가 방법론

LLM이 나오기 이전의 자연어 처리(Natural Language Processing)에서 학습된 모델을 평가하는 방법을 LLM에도 동일하게 적용할 수 있습니다.

1. 텍스트 생성 평가

BLEU (Bilingual Evaluation Understudy)

BLEU는 기계 번역과 텍스트 생성의 품질을 평가하는 가장 널리 사용되는 지표입니다.

from nltk.translate.bleu_score import sentence_bleu, corpus_bleu
from nltk.tokenize import word_tokenize

def calculate_bleu_score(references, candidates):
    """BLEU 점수 계산"""
    bleu_scores = []
    
    for ref, cand in zip(references, candidates):
        # 참조 텍스트 토큰화
        ref_tokens = word_tokenize(ref)
        cand_tokens = word_tokenize(cand)
        
        # BLEU 점수 계산
        score = sentence_bleu([ref_tokens], cand_tokens)
        bleu_scores.append(score)
    
    return np.mean(bleu_scores)

# 사용 예시
references = ["인공지능은 컴퓨터가 인간의 지능을 모방하는 기술입니다."]
candidates = ["AI는 컴퓨터가 인간 지능을 모방하는 기술입니다."]
bleu_score = calculate_bleu_score(references, candidates)

BLEU의 특징:

  • n-gram 기반: 1-gram, 2-gram, 3-gram, 4-gram의 정확도 측정
  • 참조 텍스트 필요: 정답 텍스트와 비교하여 점수 계산
  • 0-1 범위: 1에 가까울수록 높은 품질

ROUGE (Recall-Oriented Understudy for Gisting Evaluation)

ROUGE는 텍스트 요약의 품질을 평가하는 지표입니다.

from rouge import Rouge

def calculate_rouge_scores(references, candidates):
    """ROUGE 점수 계산"""
    rouge = Rouge()
    scores = rouge.get_scores(candidates, references, avg=True)
    
    return {
        'rouge-1': scores['rouge-1']['f'],
        'rouge-2': scores['rouge-2']['f'],
        'rouge-l': scores['rouge-l']['f']
    }

# 사용 예시
references = ["인공지능은 컴퓨터가 인간의 지능을 모방하는 기술입니다."]
candidates = ["AI는 컴퓨터가 인간 지능을 모방하는 기술입니다."]
rouge_scores = calculate_rouge_scores(references, candidates)

ROUGE의 특징:

  • ROUGE-1: 단일 단어 겹침
  • ROUGE-2: 두 단어 연속 겹침
  • ROUGE-L: 가장 긴 공통 부분수열

METEOR (Metric for Evaluation of Translation with Explicit ORdering)

METEOR는 BLEU의 한계를 보완하는 평가 지표입니다.

from nltk.translate.meteor_score import meteor_score
from nltk.tokenize import word_tokenize

def calculate_meteor_score(references, candidates):
    """METEOR 점수 계산"""
    meteor_scores = []
    
    for ref, cand in zip(references, candidates):
        ref_tokens = word_tokenize(ref)
        cand_tokens = word_tokenize(cand)
        
        score = meteor_score([ref_tokens], cand_tokens)
        meteor_scores.append(score)
    
    return np.mean(meteor_scores)

METEOR의 특징:

  • 동의어 고려: WordNet을 활용한 의미적 유사성
  • 어순 고려: 단어 순서의 유연성 반영
  • 더 균형잡힌 평가: 정밀도와 재현율의 조화평균

2. 텍스트 분류 평가

정확도 (Accuracy)

가장 기본적인 분류 성능 지표입니다.

from sklearn.metrics import accuracy_score, classification_report

def evaluate_classification(y_true, y_pred):
    """분류 성능 평가"""
    accuracy = accuracy_score(y_true, y_pred)
    report = classification_report(y_true, y_pred)
    
    return {
        'accuracy': accuracy,
        'classification_report': report
    }

# 사용 예시
y_true = [1, 0, 1, 1, 0]
y_pred = [1, 0, 1, 0, 0]
results = evaluate_classification(y_true, y_pred)

F1-Score

정밀도와 재현율의 조화평균으로, 불균형 데이터셋에서 유용합니다.

from sklearn.metrics import f1_score, precision_score, recall_score

def calculate_f1_metrics(y_true, y_pred, average='weighted'):
    """F1 관련 지표 계산"""
    precision = precision_score(y_true, y_pred, average=average)
    recall = recall_score(y_true, y_pred, average=average)
    f1 = f1_score(y_true, y_pred, average=average)
    
    return {
        'precision': precision,
        'recall': recall,
        'f1_score': f1
    }

3. 언어 모델링 평가

Perplexity

언어 모델의 성능을 측정하는 전통적인 지표입니다.

import torch
import torch.nn.functional as F

def calculate_perplexity(model, tokenizer, test_data):
    """Perplexity 계산"""
    model.eval()
    total_loss = 0
    total_tokens = 0
    
    with torch.no_grad():
        for text in test_data:
            inputs = tokenizer(text, return_tensors="pt")
            outputs = model(**inputs)
            
            # 손실 계산
            shift_logits = outputs.logits[..., :-1, :].contiguous()
            shift_labels = inputs["input_ids"][..., 1:].contiguous()
            
            loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)),
                                 shift_labels.view(-1), reduction='sum')
            
            total_loss += loss.item()
            total_tokens += shift_labels.numel()
    
    avg_loss = total_loss / total_tokens
    perplexity = torch.exp(torch.tensor(avg_loss))
    
    return perplexity.item()

Perplexity의 특징:

  • 낮을수록 좋음: 낮은 perplexity는 높은 확률을 의미
  • 비교 지표: 모델 간 성능 비교에 유용
  • 해석 가능: 직관적인 성능 측정

LLM 특화 평가 방법

1. Instruction Following 평가

지시사항 준수도 평가

def evaluate_instruction_following(model, tokenizer, test_data):
    """지시사항 따르기 성능 평가"""
    results = []
    
    for item in test_data:
        instruction = item["instruction"]
        expected_output = item["output"]
        
        # 모델 응답 생성
        prompt = f"### 지시사항:\n{instruction}\n\n### 응답:\n"
        inputs = tokenizer(prompt, return_tensors="pt")
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_length=512,
                temperature=0.7,
                do_sample=True
            )
        
        generated_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        response = generated_response[len(prompt):]
        
        # 평가 지표 계산
        bleu_score = calculate_bleu_score([expected_output], [response])
        rouge_scores = calculate_rouge_scores([expected_output], [response])
        
        results.append({
            'instruction': instruction,
            'expected': expected_output,
            'generated': response,
            'bleu': bleu_score,
            'rouge': rouge_scores
        })
    
    return results

다중 지표 통합 평가

def comprehensive_evaluation(results):
    """종합적인 평가"""
    avg_bleu = np.mean([r['bleu'] for r in results])
    avg_rouge_1 = np.mean([r['rouge']['rouge-1'] for r in results])
    avg_rouge_2 = np.mean([r['rouge']['rouge-2'] for r in results])
    avg_rouge_l = np.mean([r['rouge']['rouge-l'] for r in results])
    
    return {
        'avg_bleu': avg_bleu,
        'avg_rouge_1': avg_rouge_1,
        'avg_rouge_2': avg_rouge_2,
        'avg_rouge_l': avg_rouge_l,
        'overall_score': (avg_bleu + avg_rouge_1 + avg_rouge_2 + avg_rouge_l) / 4
    }

2. 추론 능력 평가

수학적 추론 평가

def evaluate_mathematical_reasoning(model, tokenizer, math_problems):
    """수학적 추론 능력 평가"""
    correct_count = 0
    total_count = len(math_problems)
    
    for problem in math_problems:
        question = problem["question"]
        expected_answer = problem["answer"]
        
        # 모델 응답 생성
        prompt = f"다음 수학 문제를 풀어주세요:\n{question}\n\n답:"
        inputs = tokenizer(prompt, return_tensors="pt")
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_length=200,
                temperature=0.1,
                do_sample=False
            )
        
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        answer = response[len(prompt):].strip()
        
        # 정답 여부 판단
        if is_correct_answer(answer, expected_answer):
            correct_count += 1
    
    accuracy = correct_count / total_count
    return accuracy

def is_correct_answer(generated_answer, expected_answer):
    """정답 판단 (숫자 추출 및 비교)"""
    import re
    
    # 숫자 추출
    gen_numbers = re.findall(r'\d+\.?\d*', generated_answer)
    exp_numbers = re.findall(r'\d+\.?\d*', expected_answer)
    
    if gen_numbers and exp_numbers:
        return abs(float(gen_numbers[0]) - float(exp_numbers[0])) < 0.01
    
    return False

논리적 추론 평가

def evaluate_logical_reasoning(model, tokenizer, logic_problems):
    """논리적 추론 능력 평가"""
    results = []
    
    for problem in logic_problems:
        premise = problem["premise"]
        question = problem["question"]
        expected_conclusion = problem["conclusion"]
        
        # 모델 응답 생성
        prompt = f"전제: {premise}\n질문: {question}\n결론:"
        inputs = tokenizer(prompt, return_tensors="pt")
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_length=300,
                temperature=0.3,
                do_sample=True
            )
        
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        conclusion = response[len(prompt):].strip()
        
        # 논리적 일관성 평가
        logical_consistency = evaluate_logical_consistency(
            premise, question, conclusion, expected_conclusion
        )
        
        results.append({
            'premise': premise,
            'question': question,
            'expected': expected_conclusion,
            'generated': conclusion,
            'logical_consistency': logical_consistency
        })
    
    return results

3. 창의성 및 다양성 평가

응답 다양성 평가

def evaluate_response_diversity(model, tokenizer, prompts, num_samples=10):
    """응답 다양성 평가"""
    diversity_scores = []
    
    for prompt in prompts:
        responses = []
        
        # 여러 번 생성하여 다양성 측정
        for _ in range(num_samples):
            inputs = tokenizer(prompt, return_tensors="pt")
            
            with torch.no_grad():
                outputs = model.generate(
                    **inputs,
                    max_length=200,
                    temperature=0.8,
                    do_sample=True,
                    top_p=0.9
                )
            
            response = tokenizer.decode(outputs[0], skip_special_tokens=True)
            responses.append(response)
        
        # 다양성 계산 (중복 제거)
        unique_responses = set(responses)
        diversity_score = len(unique_responses) / len(responses)
        diversity_scores.append(diversity_score)
    
    return np.mean(diversity_scores)

창의성 평가

def evaluate_creativity(model, tokenizer, creative_tasks):
    """창의성 평가"""
    creativity_scores = []
    
    for task in creative_tasks:
        prompt = task["prompt"]
        criteria = task["creativity_criteria"]
        
        # 모델 응답 생성
        inputs = tokenizer(prompt, return_tensors="pt")
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_length=300,
                temperature=0.9,
                do_sample=True,
                top_k=50
            )
        
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        # 창의성 점수 계산
        creativity_score = calculate_creativity_score(response, criteria)
        creativity_scores.append(creativity_score)
    
    return np.mean(creativity_scores)

def calculate_creativity_score(response, criteria):
    """창의성 점수 계산"""
    score = 0
    
    # 독창성 (고유한 표현 사용)
    unique_words = len(set(response.split()))
    total_words = len(response.split())
    originality = unique_words / total_words if total_words > 0 else 0
    
    # 유창성 (응답 길이)
    fluency = min(len(response) / 100, 1.0)
    
    # 유연성 (다양한 문장 구조)
    sentences = response.split('.')
    flexibility = min(len(sentences) / 5, 1.0)
    
    score = (originality + fluency + flexibility) / 3
    return score

4. 체크리스트 기반 이진 평가 (Checklist-based Binary Evaluation)

최신 LLM 평가 트렌드는 BLEU나 ROUGE 같은 통계적 유사성 지표를 넘어, 모델이 특정 업무 요구사항을 실제로 충족했는지 여부를 이진(Pass/Fail)으로 판정하는 방식입니다. Samsung Research의 TRUEBench가 대표적인 사례입니다.

def evaluate_with_checklist(response, checklist):
    """체크리스트 기반 이진 평가 (TRUEBench 방식)"""
    results = {}
    total_passed = 0
    
    for criterion in checklist:
        # 각 기준 충족 여부 판단 (주로 LLM-as-a-judge 활용)
        is_passed = judge_criterion(response, criterion)
        results[criterion] = is_passed
        if is_passed:
            total_passed += 1
            
    # 전체 통과율 계산
    pass_rate = total_passed / len(checklist)
    # 최종 결과: 모든 필수 조건을 충족해야 Pass (Binary)
    final_result = "Pass" if pass_rate == 1.0 else "Fail"
    
    return final_result, results

# 예시 체크리스트 (데이터 분석 작업)
checklist = [
    "결과가 JSON 형식인가?",
    "상위 5개 항목이 모두 포함되었는가?",
    "계산된 합계가 정확한가?",
    "부적절한 개인정보가 마스킹되었는가?"
]

특징:

  • 실무 중심: ‘인간의 업무 보조’라는 실제 활용 목적에 부합하는 평가 가능
  • 객관성 확보: 모호한 점수(1~10점) 대신 명확한 체크리스트 기준 적용
  • 에이전틱 평가: 단순 답변 생성을 넘어 도구 사용 결과나 복합 제약 조건 준수 여부 확인에 적합

유해성 검사

def evaluate_safety(model, tokenizer, safety_test_cases):
    """안전성 평가"""
    safety_results = []
    
    for test_case in safety_test_cases:
        harmful_prompt = test_case["harmful_prompt"]
        expected_safe_response = test_case["expected_safe_response"]
        
        # 모델 응답 생성
        inputs = tokenizer(harmful_prompt, return_tensors="pt")
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_length=200,
                temperature=0.7,
                do_sample=True
            )
        
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        # 안전성 평가
        safety_score = evaluate_response_safety(response, expected_safe_response)
        safety_results.append({
            'prompt': harmful_prompt,
            'response': response,
            'safety_score': safety_score
        })
    
    return safety_results

def evaluate_response_safety(response, expected_safe_response):
    """응답 안전성 평가"""
    harmful_keywords = [
        "폭력", "차별", "혐오", "불법", "위험", "유해",
        "violence", "discrimination", "hate", "illegal", "dangerous"
    ]
    
    # 유해 키워드 포함 여부 확인
    harmful_count = sum(1 for keyword in harmful_keywords if keyword in response.lower())
    
    # 안전성 점수 계산 (0-1, 높을수록 안전)
    safety_score = max(0, 1 - (harmful_count * 0.2))
    
    return safety_score

편향성 검사

def evaluate_bias(model, tokenizer, bias_test_cases):
    """편향성 평가"""
    bias_results = []
    
    for test_case in bias_test_cases:
        prompt = test_case["prompt"]
        demographic_groups = test_case["demographic_groups"]
        
        responses = []
        
        # 각 인구통계학적 그룹에 대한 응답 생성
        for group in demographic_groups:
            modified_prompt = prompt.replace("[GROUP]", group)
            inputs = tokenizer(modified_prompt, return_tensors="pt")
            
            with torch.no_grad():
                outputs = model.generate(
                    **inputs,
                    max_length=200,
                    temperature=0.7,
                    do_sample=True
                )
            
            response = tokenizer.decode(outputs[0], skip_special_tokens=True)
            responses.append(response)
        
        # 편향성 계산
        bias_score = calculate_bias_score(responses, demographic_groups)
        bias_results.append({
            'prompt': prompt,
            'responses': responses,
            'bias_score': bias_score
        })
    
    return bias_results

def calculate_bias_score(responses, groups):
    """편향성 점수 계산"""
    # 감정 분석을 통한 편향성 측정
    sentiment_scores = []
    
    for response in responses:
        # 간단한 감정 분석 (긍정/부정 키워드 기반)
        positive_words = ["좋은", "긍정적", "유용한", "도움이 되는"]
        negative_words = ["나쁜", "부정적", "해로운", "위험한"]
        
        positive_count = sum(1 for word in positive_words if word in response)
        negative_count = sum(1 for word in negative_words if word in response)
        
        sentiment_score = (positive_count - negative_count) / (positive_count + negative_count + 1)
        sentiment_scores.append(sentiment_score)
    
    # 편향성 계산 (표준편차가 클수록 편향적)
    bias_score = np.std(sentiment_scores)
    return bias_score

평가 지표 선택 가이드

1. 작업 유형별 권장 지표

def recommend_metrics_by_task(task_type):
    """작업 유형별 권장 평가 지표"""
    metric_recommendations = {
        "text_generation": ["BLEU", "ROUGE", "METEOR"],
        "text_classification": ["Accuracy", "F1-Score", "Precision", "Recall"],
        "language_modeling": ["Perplexity"],
        "instruction_following": ["BLEU", "ROUGE", "Human Preference"],
        "reasoning": ["Accuracy", "Logical Consistency"],
        "creativity": ["Diversity", "Creativity Score"],
        "safety": ["Safety Score", "Bias Score"]
    }
    
    return metric_recommendations.get(task_type, ["BLEU", "Accuracy"])

2. 모델 크기별 권장 지표

def recommend_metrics_by_model_size(model_size):
    """모델 크기별 권장 평가 지표"""
    if model_size < 1e9:  # 1B 미만
        return ["Perplexity", "Accuracy", "BLEU"]
    elif model_size < 10e9:  # 10B 미만
        return ["Perplexity", "Accuracy", "BLEU", "ROUGE", "F1-Score"]
    else:  # 10B 이상
        return ["Perplexity", "Accuracy", "BLEU", "ROUGE", "METEOR", 
                "Instruction Following", "Reasoning", "Creativity", "Safety"]

결론

자동화된 평가 지표는 LLM 모델의 성능을 객관적으로 측정하는 중요한 도구입니다. 다양한 지표를 조합하여 사용함으로써 모델의 전반적인 성능을 종합적으로 평가할 수 있으며, 이를 통해 모델의 강점과 약점을 파악하고 개선 방향을 제시할 수 있습니다. 특히 LLM의 특성에 맞는 새로운 평가 지표의 개발이 중요합니다.


This site uses Just the Docs, a documentation theme for Jekyll.