Download pipelines/Tr006_V2/project/scripts/evaluate_segmentqa_predictions.py from Gbone3176/interval: direct link, hf CLI and curl.
- Browser
- Download file 5.71 kB
-
https://huggingface.co/Gbone3176/interval/resolve/main/pipelines/Tr006_V2/project/scripts/evaluate_segmentqa_predictions.py
- Command line
-
hf download hf://Gbone3176/interval/pipelines/Tr006_V2/project/scripts/evaluate_segmentqa_predictions.py
-
curl -L -o evaluate_segmentqa_predictions.py https://huggingface.co/Gbone3176/interval/resolve/main/pipelines/Tr006_V2/project/scripts/evaluate_segmentqa_predictions.py
5.71 kB
| #!/usr/bin/env python | |
| import argparse | |
| import json | |
| import re | |
| from collections import defaultdict | |
| from pathlib import Path | |
| import pyarrow.parquet as pq | |
| from eval_segmentqa_vllm import correct | |
| BUCKETS = {'1': 'or', '2': 'tg', '3': 'agg', '4': 'eu', '5': 'cr'} | |
| TIME_RE = re.compile(r'\d{2}:\d{2}:\d{2}(?:\s*,\s*\d{2}:\d{2}:\d{2})*') | |
| def seconds(value): | |
| hours, minutes, secs = map(int, value.split(':')) | |
| if minutes >= 60 or secs >= 60: | |
| raise ValueError(value) | |
| return hours * 3600 + minutes * 60 + secs | |
| def parse_times(value): | |
| value = re.sub(r'<think>.*?</think>', '', str(value or ''), flags=re.S).strip() | |
| if not TIME_RE.fullmatch(value): | |
| return None | |
| try: | |
| return sorted(seconds(item.strip()) for item in value.split(',')) | |
| except ValueError: | |
| return None | |
| def summarize(decisions, total): | |
| by_bucket = {} | |
| for bucket in ('agg', 'tg', 'or', 'eu', 'cr'): | |
| values = decisions[bucket] | |
| passed = sum(values) | |
| by_bucket[bucket] = {'correct': passed, 'total': len(values), | |
| 'accuracy': passed / len(values) if values else 0.0} | |
| passed = sum(sum(values) for values in decisions.values()) | |
| return {'correct': passed, 'total': total, 'accuracy': passed / total if total else 0.0, | |
| 'by_bucket': by_bucket} | |
| def summarize_formats(decisions): | |
| by_format = {} | |
| for fmt, values in decisions.items(): | |
| passed = sum(values) | |
| by_format[fmt] = {'correct': passed, 'total': len(values), | |
| 'accuracy': passed / len(values) if values else 0.0} | |
| return by_format | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--predictions', required=True) | |
| parser.add_argument('--references', required=True) | |
| parser.add_argument('--parquet', action='append', required=True) | |
| parser.add_argument('--local-output', required=True) | |
| parser.add_argument('--time-output', required=True) | |
| args = parser.parse_args() | |
| predictions = [json.loads(line) for line in Path(args.predictions).read_text().splitlines() if line.strip()] | |
| references = {str(row['id']): row for row in | |
| (json.loads(line) for line in Path(args.references).read_text().splitlines() if line.strip())} | |
| metadata = {} | |
| for path in args.parquet: | |
| metadata.update({str(row['id']): row for row in pq.read_table(path).to_pylist()}) | |
| ids = [str(row['id']) for row in predictions] | |
| expected = set(references) | |
| actual = set(ids) | |
| completeness = { | |
| 'predictions': len(predictions), | |
| 'unique_ids': len(actual), | |
| 'missing': len(expected - actual), | |
| 'duplicate': len(ids) - len(actual), | |
| 'unexpected': len(actual - expected), | |
| 'failed': sum(bool(row.get('error') or row.get('failed')) for row in predictions), | |
| 'skipped': sum(bool(row.get('skipped')) for row in predictions), | |
| 'empty_predictions': sum(not str(row.get('prediction', '')).strip() for row in predictions), | |
| } | |
| if completeness != {'predictions': len(expected), 'unique_ids': len(expected), 'missing': 0, | |
| 'duplicate': 0, 'unexpected': 0, 'failed': 0, 'skipped': 0, | |
| 'empty_predictions': 0}: | |
| raise RuntimeError(f'incomplete predictions: {completeness}') | |
| if set(metadata) != expected: | |
| raise RuntimeError(f'official parquet/reference ID mismatch: parquet={len(metadata)} refs={len(expected)}') | |
| local, tolerant = defaultdict(list), defaultdict(list) | |
| local_formats, tolerant_formats = defaultdict(list), defaultdict(list) | |
| time_parse_errors = 0 | |
| for row in predictions: | |
| qid = str(row['id']) | |
| reference, meta = references[qid], metadata[qid] | |
| bucket = BUCKETS[str(meta['primary_capability'])[0]] | |
| local_result = correct(row['prediction'], reference['answer'], reference['answer_format']) | |
| local[bucket].append(local_result) | |
| local_formats[reference['answer_format']].append(local_result) | |
| tolerant_result = local_result | |
| if reference['answer_format'] == 'time': | |
| pred_times, gold_times = parse_times(row['prediction']), parse_times(reference['answer']) | |
| if pred_times is None: | |
| time_parse_errors += 1 | |
| tolerant_result = False | |
| else: | |
| duration = seconds(meta['timestamp_end']) - seconds(meta['timestamp_start']) | |
| tolerance = min(5.0, 1.0 + duration * 4.0 / 360.0) | |
| tolerant_result = len(pred_times) == len(gold_times) and all( | |
| abs(pred - gold) <= tolerance for pred, gold in zip(pred_times, gold_times)) | |
| tolerant[bucket].append(tolerant_result) | |
| tolerant_formats[reference['answer_format']].append(tolerant_result) | |
| local_metrics = {'protocol': 'scripts/eval_segmentqa_vllm.py::correct', | |
| **completeness, **summarize(local, len(predictions)), | |
| 'by_format': summarize_formats(local_formats)} | |
| tolerant_metrics = { | |
| 'protocol': 'time-tolerance-only', | |
| 'time_tolerance_seconds': 'min(5, 1 + segment_duration * 4 / 360)', | |
| 'non_time_protocol': 'scripts/eval_segmentqa_vllm.py::correct', | |
| 'time_parse_errors': time_parse_errors, | |
| **completeness, | |
| **summarize(tolerant, len(predictions)), | |
| 'by_format': summarize_formats(tolerant_formats), | |
| } | |
| Path(args.local_output).write_text(json.dumps(local_metrics, indent=2)) | |
| Path(args.time_output).write_text(json.dumps(tolerant_metrics, indent=2)) | |
| print(json.dumps({'local': local_metrics, 'time_tolerance': tolerant_metrics}, indent=2)) | |
| if __name__ == '__main__': | |
| main() | |