| import os |
| import argparse |
|
|
| import pandas as pd |
| import re |
| from datasets import load_dataset |
|
|
| import json |
|
|
| def time_evaluation(result_file): |
| results = [] |
| f = open(result_file, 'r') |
| for line in f: |
| results.append(json.loads(line)) |
| f.close() |
|
|
| total_time, total_token = 0, 0 |
| total = 0 |
| |
| for idx, problem in enumerate(results): |
| total += 1 |
| total_time += problem['time'] |
| total_token += problem['tokens'] |
| |
| return { |
| 'total': total, |
| 'total_time': total_time, |
| 'total_token': total_token, |
| 'token/s': total_token / total_time, |
| } |
|
|
| def draw_length_distribution(length, correct_length, wrong_length, file_name): |
| import matplotlib.pyplot as plt |
| import numpy as np |
| from collections import Counter |
| |
| bin_size = 10 |
|
|
| |
| bins_array0 = [min(val, 1000) // bin_size * bin_size for val in length] |
| bins_array1 = [min(val, 1000) // bin_size * bin_size for val in correct_length] |
| bins_array2 = [min(val, 1000) // bin_size * bin_size for val in wrong_length] |
|
|
| |
| freq_bins_array0 = Counter(bins_array0) |
| freq_bins_array1 = Counter(bins_array1) |
| freq_bins_array2 = Counter(bins_array2) |
|
|
| |
| binned_values0, binned_freqs0 = zip(*sorted(freq_bins_array0.items())) |
| binned_values1, binned_freqs1 = zip(*sorted(freq_bins_array1.items())) |
| binned_values2, binned_freqs2 = zip(*sorted(freq_bins_array2.items())) |
|
|
| |
| plt.figure(figsize=(12, 6)) |
|
|
| plt.bar(binned_values0, binned_freqs0, width=bin_size * 0.8, label='All', alpha=0.7, align='center') |
| plt.bar(binned_values1, binned_freqs1, width=bin_size * 0.8, label='Correct', alpha=0.7, align='center') |
| plt.bar(binned_values2, binned_freqs2, width=bin_size * 0.8, label='Incorrect', alpha=0.7, align='center') |
|
|
| |
| plt.xlabel('Token Length', fontsize=12) |
| plt.ylabel('Frequency', fontsize=12) |
| plt.title(f'Frequency Distribution with Bin Size {bin_size}', fontsize=14) |
| plt.xticks(ticks=np.arange(0, max(max(binned_values1), max(binned_values2)) + bin_size, bin_size)) |
| plt.legend() |
| plt.grid(axis='y', linestyle='--', alpha=0.6) |
| plt.tight_layout() |
|
|
| |
| plt.savefig(file_name) |
|
|
|
|
| _DIGIT_TO_LETTER = {"1": "A", "2": "B", "3": "C", "4": "D"} |
|
|
| def normalize_answer_key(key): |
| """Normalize ARC-C answerKey: '1'/'2'/'3'/'4' → 'A'/'B'/'C'/'D'.""" |
| return _DIGIT_TO_LETTER.get(str(key), str(key)).upper() |
|
|
|
|
| def extract_answer_arc(document): |
| """ |
| Extract predicted answer letter (A/B/C/D) from model output for ARC-C. |
| Returns (prediction, confidence): |
| 'boxed' - \\boxed{A} (matches prompt format) |
| 'answer_tag' - "Answer: A" anywhere |
| 'explicit' - "the answer is A", "correct answer is A", etc. |
| 'pattern' - weaker patterns: "(A)", "Option A", "**A**" |
| 'last_letter' - last standalone A-D letter (lowest confidence) |
| None - no answer found |
| """ |
| document = str(document) |
|
|
| |
| m = re.search(r'\\boxed\{([^}]*)\}', document) |
| if m: |
| cm = re.search(r'\b([ABCD])\b', m.group(1)) |
| if cm: |
| return cm.group(1).upper(), 'boxed' |
|
|
| |
| matches = re.findall(r'(?i)Answer\s*:\s*\**\s*([A-D])\b', document) |
| if matches: |
| return matches[-1].upper(), 'answer_tag' |
|
|
| |
| explicit_patterns = [ |
| r'(?i)(?:the\s+)?correct\s+answer\s+is\s*:?\s*\**([A-D])\b', |
| r'(?i)(?:the\s+)?answer\s+is\s*:?\s*\**([A-D])\b', |
| r'(?i)(?:so|thus|therefore)[,\s]+(?:the\s+)?(?:correct\s+)?answer\s+is\s*:?\s*\**([A-D])\b', |
| r'(?i)I\s+(?:would\s+)?(?:choose|select|pick)\s+:?\s*\**([A-D])\b', |
| ] |
| for pat in explicit_patterns: |
| matches = re.findall(pat, document) |
| if matches: |
| return matches[-1].upper(), 'explicit' |
|
|
| |
| weak_patterns = [ |
| r'(?i)is\s+option\s*:?\s*([A-D])\b', |
| r'(?i)\*\*Answer:\*\*\s*([A-D])\b', |
| r'(?i)Option\s+([A-D])\b', |
| r'\(([A-D])\)', |
| r'(?i)\b([A-D])\s+is\s+(?:correct|right)\b', |
| ] |
| for pat in weak_patterns: |
| matches = re.findall(pat, document) |
| if matches: |
| return matches[-1].upper(), 'pattern' |
|
|
| |
| matches = re.findall(r'\b([A-D])\b', document) |
| if matches: |
| return matches[-1].upper(), 'last_letter' |
|
|
| return None, None |
|
|
|
|
| _STRICT_CONFIDENCE = {'boxed', 'answer_tag', 'explicit'} |
| _FLEXIBLE_CONFIDENCE = {'boxed', 'answer_tag', 'explicit', 'pattern', 'last_letter'} |
|
|
|
|
| def extract_boxed_text(document): |
| """Legacy wrapper — returns (strict_list, flexible_list) for backward compat.""" |
| prediction, confidence = extract_answer_arc(document) |
| if confidence in _STRICT_CONFIDENCE: |
| return [prediction], [prediction] |
| elif confidence in _FLEXIBLE_CONFIDENCE: |
| return [], [prediction] |
| return [], None |
|
|
|
|
|
|
| def extract_boxed_text_most_relaxed(document): |
| |
| pattern = r"\\boxed\{([^}]*)\}" |
| |
| matches = re.findall(pattern, document) |
| |
| |
| |
| numbers = re.findall(r'\b\d+[\.\,]?\d*\b', document) |
| |
| if len(numbers) >= 2: |
| f_matches = [''.join(numbers[-1].split(',')), ''.join(numbers[-2].split(','))] if numbers else None |
| else: |
| f_matches = [''.join(numbers[-1].split(',')), None] if numbers else None |
| if len(matches) == 1: |
| s_matches = re.findall(r"[\d.]+", matches[-1]) |
| f_matches = s_matches |
| elif len(matches) > 1: |
| s_matches = re.findall(r"[\d.]+", matches[-1]) |
| else: |
| s_matches = matches |
| return s_matches, f_matches |
|
|
| def process_target(target, dataset): |
| if dataset == 'gsm8k': |
| target = int(target.split('#### ')[-1].replace(',','')) |
| return target |
| elif dataset == 'aime': |
| return target |
| else: |
| raise ValueError('Unknown dataset') |
| |
| def evaluation(result_file): |
| results = [] |
| with open(result_file, 'r') as f: |
| for line in f: |
| results.append(json.loads(line)) |
|
|
| total, s_correct, f_correct = 0, 0, 0 |
| no_answer = 0 |
| total_time, total_token = 0, 0 |
| total_useful_token = 0 |
| total_steps = 0 |
| total_experts = 0 |
| confidence_counts = {} |
|
|
| for problem in results: |
| answer = problem['answer'][0] |
| target = normalize_answer_key(problem['task']['answerKey']) |
|
|
| prediction, confidence = extract_answer_arc(answer) |
|
|
| total += 1 |
| total_time += problem['time'] |
| total_token += problem['tokens'] |
| total_useful_token += problem.get('useful_tokens', 0) |
| total_steps += problem.get('steps', 0) |
| total_experts += problem.get('unique_experts_count', 0) |
|
|
| confidence_counts[confidence] = confidence_counts.get(confidence, 0) + 1 |
|
|
| if prediction is None: |
| no_answer += 1 |
| continue |
|
|
| correct = prediction.upper() == target |
| if correct: |
| if confidence in _STRICT_CONFIDENCE: |
| s_correct += 1 |
| f_correct += 1 |
| else: |
| f_correct += 1 |
|
|
| print(f"Strict Accuracy = {s_correct}/{total} = {s_correct/total:.4f} (boxed/answer_tag/explicit)") |
| print(f"Flexible Accuracy = {f_correct}/{total} = {f_correct/total:.4f} (all patterns)") |
| print(f"No answer found = {no_answer}/{total}") |
| print(f"Confidence breakdown: {confidence_counts}") |
|
|
| return { |
| 'strict_accuracy': s_correct / total, |
| 'soft_accuracy': f_correct / total, |
| 'strict_match': s_correct, |
| 'soft_match': f_correct, |
| 'total': total, |
| 'no_answer': no_answer, |
| 'total_time': total_time, |
| 'total_token': total_token, |
| 'avg_token': total_token / total, |
| 'avg_useful_token': total_useful_token / total if total_useful_token else 0, |
| 'token/s': total_token / total_time, |
| 'avg_steps': total_steps / total if total > 0 else -1, |
| 'unique_experts': total_experts / total if total > 0 else -1, |
| 'confidence_breakdown': confidence_counts, |
| } |
|
|
| def passk_evaluation(result_files): |
| """ |
| Pass@k evaluation for multiple-choice tasks using answerKey. |
| A question is counted as correct if ANY of the result files answers it correctly. |
| |
| result_files: list of paths to JSONL result files. |
| """ |
|
|
| |
| all_results = [] |
| for file in result_files: |
| with open(file, "r") as f: |
| results = [json.loads(line) for line in f] |
| all_results.append(results) |
|
|
| num_files = len(all_results) |
| num_questions = len(all_results[0]) |
|
|
| strict_correct = 0 |
| soft_correct = 0 |
|
|
| for qid in range(num_questions): |
|
|
| target = normalize_answer_key(all_results[0][qid]['task']['answerKey']) |
|
|
| strict_hit = False |
| soft_hit = False |
|
|
| for fidx in range(num_files): |
|
|
| answer = all_results[fidx][qid]['answer'][0] |
|
|
| try: |
| prediction, confidence = extract_answer_arc(answer) |
| if prediction is None: |
| continue |
| correct = prediction.upper() == target |
| if correct: |
| if confidence in _STRICT_CONFIDENCE: |
| strict_hit = True |
| soft_hit = True |
| break |
| else: |
| soft_hit = True |
| break |
| except Exception: |
| continue |
|
|
| |
| if strict_hit: |
| strict_correct += 1 |
| soft_correct += 1 |
| elif soft_hit: |
| soft_correct += 1 |
|
|
| |
| return { |
| 'strict_accuracy': strict_correct / num_questions, |
| 'soft_accuracy': soft_correct / num_questions, |
| 'strict_match': strict_correct, |
| 'soft_match': soft_correct, |
| 'total': num_questions, |
| } |
|
|
|
|
| if __name__ == '__main__': |
| args = argparse.ArgumentParser() |
| args.add_argument('--result_file', type=str, default='results.csv') |
| args.add_argument('--dataset', type=str, default='gsm8k') |
| args.add_argument('--split', type=str, default='test') |
| args.add_argument('--num-samples', type=int, default=None) |
| args = args.parse_args() |
|
|
| result_file = args.result_file |
| if args.dataset == 'gsm8k': |
| dataset = load_dataset('openai/gsm8k', 'main') |
| dataset_type = args.split |
| elif args.dataset == 'aime': |
| dataset = load_dataset('AI-MO/aimo-validation-aime') |
| dataset_type = 'train' |
| else: |
| raise ValueError('Unknown dataset') |
| |
| results = json.load(open(result_file, 'r')) |
|
|
| total, s_correct, f_correct = 0, 0, 0 |
| correct_length, wrong_length, all_length = [], [], [] |
| max_length, min_length = 0, 100000 |
| for idx, (ans, prob) in enumerate(zip(results, dataset[dataset_type])): |
| if args.num_samples is not None and idx >= args.num_samples: |
| break |
| |
| if args.dataset == 'aime' and idx < 60: |
| continue |
|
|
| strict_predict_ans, flexible_predict_ans = extract_boxed_text(ans['answer']) |
| target = prob['answer'] |
|
|
| target = process_target(target, args.dataset) |
|
|
| total += 1 |
| |
| if len(strict_predict_ans) > 0 or (flexible_predict_ans is not None and len(flexible_predict_ans) > 0): |
| try: |
| correct_flag = False |
| if len(strict_predict_ans) > 0 and float(strict_predict_ans[0]) == float(target): |
| s_correct += 1 |
| f_correct += 1 |
| correct_flag = True |
| elif float(flexible_predict_ans[0]) == float(target): |
| f_correct += 1 |
| correct_flag = True |
| elif args.relax and flexible_predict_ans[1] is not None and float(flexible_predict_ans[1]) == float(target): |
| f_correct += 1 |
| correct_flag = True |
|
|
| |
| except: |
| |
| pass |
| else: |
| |
| pass |
|
|
| |
| |
|
|
| print(f"Strict Match Accuracy = {s_correct}/{total} = {s_correct/total}") |
| print(f"Soft Match Accuracy = {f_correct}/{total} = {f_correct/total}") |
|
|
|
|