| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- import os, openai, json, functools, argparse
- try:
- from datasets import load_dataset # optional, only needed by some official flows
- except ImportError:
- load_dataset = None
- try:
- from proactvl.utils.multiprocessor import local_mt
- except ImportError:
- try:
- from proactvl_eval.multiprocessor import local_mt
- except ImportError:
- from multiprocessor import local_mt
- # ========================= Initialize GPT Client =========================
- try:
- from evaluation.gaming.gpt_client_utils import GPTClientFactory
- except ImportError:
- try:
- from proactvl_eval.gpt_client_utils import GPTClientFactory
- except ImportError:
- from gpt_client_utils import GPTClientFactory
- # deployment_name = "qwen/qwen3.6-plus"
- deployment_name = os.getenv("OPENAI_JUDGE_MODEL", "gpt-5.1_2025-11-13")
- try:
- gpt = GPTClientFactory().build()
- except Exception as e:
- raise RuntimeError(f"Error: failed to initialize GPT client. {e}") from e
- def judge_ab(a_id_with_pred, b_id_with_pred, gt_asr):
- a_id, a_pred = a_id_with_pred
- b_id, b_pred = b_id_with_pred
- ab_prompt = (
- 'You are an expert in video commentary. '
- 'Your task is to review two commentaries (Commentary A and Commentary B), and select the one that better aligns with the human commentary. '
- 'You should consider the criteria:\n'
- '1. Semantic Alignment: The commentary should convey the same meaning, details, and key points as the human commentary.\n'
- 'If the above criteria is not enough to judge, then consider:\n'
- '2. Stylistic Consistency: The commentary should maintain a tone, word choice, and structure similar to the human commentary.\n'
- f'\n---Commentary A---\n{a_pred}\n----------\n'
- f'\n---Commentary B---\n{b_pred}\n----------\n'
- f'\n---Human Commentary---\n{gt_asr}\n----------\n'
- '\nYour response should be "Commentary A is better aligned with the human commentary" or "Commentary B is better aligned with the human commentary".\n'
- )
- while True:
- try:
- ab_resp = gpt.chat.completions.create(
- # model='gpt-4o-2024-08-06',
- # model='gpt-4.1',
- model=deployment_name,
- messages=[{"role": "user", "content": [{'type': 'text', 'text': ab_prompt}]}],
- seed=42,
- temperature=0,
- ).choices[0].message.content
- break
- except Exception as e:
- print('Failed to get response...', e)
- if 'Commentary A' in ab_resp:
- ab_winner = a_id
- elif 'Commentary B' in ab_resp:
- ab_winner = b_id
- else:
- ab_winner = 'tie'
- # print(ab_winner)
- return ab_winner
- def judge(item, model_id):
- # print('--------call judge once--------')
- video_event_id, model_pred = item
- gt_asr = video_event_id_to_gt_asr[video_event_id]
- baseline_pred = video_event_id_to_baseline_pred[video_event_id]
- dataset_name = video_event_id_to_dataset_name[video_event_id]
- tag = video_event_id_to_tag[video_event_id]
- return {
- 'video_event_id': video_event_id,
- 'ab_winner': judge_ab([model_id, model_pred], [baseline_id, baseline_pred], gt_asr),
- 'ba_winner': judge_ab([baseline_id, baseline_pred], [model_id, model_pred], gt_asr),
- 'dataset_name': dataset_name,
- 'tag': tag
- }
- def merge_commentary(commentary_dict):
- sorted_seconds = sorted(commentary_dict.keys(), key=int)
- merged_commentary = ' '.join([commentary_dict[sec].replace(' ...', '') for sec in sorted_seconds])
- return merged_commentary.strip()
- def calculate_proactive_metrics(label_dict, pred_dict, video_duration):
- label_events = set(label_dict.keys())
- pred_events = set(pred_dict.keys())
- true_positives = len(label_events.intersection(pred_events))
- false_positives = len(pred_events - label_events)
- false_negatives = len(label_events - pred_events)
- proactive_rate = true_positives / video_duration
- precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0.0
- recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0.0
- return proactive_rate, precision, recall
- def pred2standard(pred):
- if type(pred) == str:
- return pred, pred
- sorted_seconds = sorted(pred.keys(), key=int)
- # print(sorted_seconds)
- asrs = []
- asr_lines = []
- for sec in sorted_seconds:
- line = pred[sec]
- # Trim trailing ' ...'
- if line.endswith(' ...'):
- line = line[:-4]
- if line.strip() == '':
- continue
- else:
- asr_lines.append(f'[t={sec}] {line}')
- asrs.append(line)
- return '\n'.join(asr_lines), ' '.join(asrs)
- def gt2standard(annotations):
- active_speaker = annotations['active_speaker']['name']
- asrs = []
- asr_lines = []
- for item in annotations['annotations']:
- # if item['speaker'] != active_speaker:
- # continue
- sec = item['start']
- line = item['text'] if 'text' in item else item['query']
- if line.strip() == '':
- continue
- else:
- if item['speaker'] == active_speaker:
- asrs.append(line)
- asr_lines.append(f'[t={sec}][ASSISTANT]:{line}')
- elif item['speaker'] == 'user':
- asr_lines.append(f'[t={sec}][USER]:{line}')
- else:
- asr_lines.append(f'[t={sec}][{item["speaker"]}]:{line}')
-
- return '\n'.join(asr_lines), ' '.join(asrs)
- if __name__ == '__main__':
- parser = argparse.ArgumentParser()
- parser.add_argument('--model_id', type=str, required=True, help='Model name to compare against baseline')
- parser.add_argument('--prediction_jsonl', type=str, required=True, help='Path to model predictions in JSONL format')
- parser.add_argument('--output_dir', type=str, default='evaluation/gaming/judges/', help='Directory to save judgment results')
- parser.add_argument('--num_workers', type=int, default=1)
- parser.add_argument('--baseline_id', type=str, default='gpt-4o-3', help='Baseline model name')
- parser.add_argument('--baseline_jsonl', type=str, default='evaluation/gaming/captions/gpt-4o-3-caption.jsonl', help='Path to baseline predictions in JSONL format')
- parser.add_argument('--asr_jsonl', type=str, default=None, help='Path to ASR transcriptions in JSONL format')
- parser.add_argument('--max_video_length', type=int, default=86400, help='Maximum video length to consider for evaluation (in seconds)')
- # parser.add_argument('--dataset_name_list', type=str, nargs='+', default=['lol'], help='List of dataset names to evaluate on')
- args = parser.parse_args()
- model_id = args.model_id
- prediction_jsonl = args.prediction_jsonl
- output_dir = args.output_dir
- baseline_id = args.baseline_id
- baseline_jsonl = args.baseline_jsonl
- os.makedirs(output_dir, exist_ok=True)
- save_path = os.path.join(output_dir, f'{baseline_id}_{model_id}.jsonl')
- print(f'{model_id} vs. {baseline_id}')
- # prediction_jsonl = 'evaluation/gaming/gaming/Qwen2.5-Omni-7B.jsonl'
- prediction_jsonl = args.prediction_jsonl
- asr_jsonl = args.asr_jsonl
- if args.asr_jsonl is None:
- asr_jsonl = prediction_jsonl
- else:
- asr_jsonl = args.asr_jsonl
- video_event_id_to_baseline_pred, video_event_id_to_gt_asr, video_start_end_to_video_event_id, video_event_id_to_model_pred = {}, {}, {}, {}
- video_event_id_to_dataset_name = {}
- video_event_id_to_tag = {}
- for line in open(baseline_jsonl):
- datum = json.loads(line)
- dataset_name = datum['dataset_name']
- if datum['end'] > args.max_video_length:
- continue
- # if datum['dataset_name'] not in args.dataset_name_list:
- # continue
- video_event_id = f'{datum["video_id"]}_{datum["begin"]}_{datum["end"]}_{dataset_name}_{datum["idx"]}'
- video_start_end = (datum['video_id'], datum['begin'], datum['end'])
- video_start_end_to_video_event_id[video_start_end] = video_event_id
- video_event_id_to_baseline_pred[video_event_id] = datum['pred']
- video_event_id_to_dataset_name[video_event_id] = datum['dataset_name']
- video_event_id_to_tag[video_event_id] = datum.get('tag', None)
- for line in open(prediction_jsonl):
- datum = json.loads(line)
- dataset_name = datum['dataset_name']
- if datum['end'] > args.max_video_length:
- continue
- # if datum['tag'] not in ['Solo commentators', "Multiple commentators", 'Guidance']:
- # continue
- # if datum['dataset_name'] not in args.dataset_name_list:
- # continue
- video_event_id = f'{datum["video_id"]}_{datum["begin"]}_{datum["end"]}_{dataset_name}_{datum["idx"]}'
- if video_event_id in video_event_id_to_model_pred:
- print(f'Warning: duplicate video_event_id {video_event_id} in predictions')
- video_event_id_to_model_pred[video_event_id] = pred2standard(datum['pred'])[1]
- # if type(datum['pred']) == str:
- # video_event_id_to_model_pred[video_event_id] = datum['pred']
- # else:
- # video_event_id_to_model_pred[video_event_id] = merge_commentary(datum['pred'])
- for line in open(asr_jsonl):
- datum = json.loads(line)
- if datum['video_end'] > args.max_video_length:
- continue
- video_event_id = f'{os.path.basename(datum["video_path"])}_{datum["video_begin"]}_{datum["video_end"]}_{datum["dataset_name"]}_{datum["idx"]}'
- # video_event_id_to_gt_asr[video_event_id] = merge_commentary(datum['label'])
- video_event_id_to_gt_asr[video_event_id] = gt2standard(datum)[1]
- winner_results = local_mt(
- video_event_id_to_model_pred.items(),
- functools.partial(judge, model_id=model_id),
- desc=f'Call gpt-5.1_2025-11-13 for {model_id} vs. {baseline_id}',
- num_workers=args.num_workers
- )
-
- with open(save_path, 'w') as f:
- for winner_result in winner_results:
- f.write(json.dumps(winner_result) + '\n')
-
- # Report win rates by game, by tag, and overall.
- summary_by_game = {}
- for winner_result in winner_results:
- dataset_name = winner_result['dataset_name']
- if dataset_name not in summary_by_game:
- summary_by_game[dataset_name] = {'win_count': 0, 'count': 0}
- if winner_result['ab_winner'] == model_id:
- summary_by_game[dataset_name]['win_count'] += 1
- if winner_result['ba_winner'] == model_id:
- summary_by_game[dataset_name]['win_count'] += 1
- summary_by_game[dataset_name]['count'] += 2
- for dataset_name, summary in summary_by_game.items():
- win_rate = summary['win_count'] / summary['count'] * 100
- output = f'Winning Rate for {model_id} vs. {baseline_id} on {dataset_name}: {win_rate:.2f}%'
- print(output)
- with open(os.path.join(output_dir, 'log.txt'), 'a') as f:
- f.write(output + '\n')
- summary_by_tag = {}
- for winner_result in winner_results:
- tag = winner_result['tag']
- if tag not in summary_by_tag:
- summary_by_tag[tag] = {'win_count': 0, 'count': 0}
- if winner_result['ab_winner'] == model_id:
- summary_by_tag[tag]['win_count'] += 1
- if winner_result['ba_winner'] == model_id:
- summary_by_tag[tag]['win_count'] += 1
- summary_by_tag[tag]['count'] += 2
- for tag, summary in summary_by_tag.items():
- win_rate = summary['win_count'] / summary['count'] * 100
- output = f'Winning Rate for {model_id} vs. {baseline_id} on tag {tag}: {win_rate:.2f}%'
- print(output)
- with open(os.path.join(output_dir, 'log.txt'), 'a') as f:
- f.write(output + '\n')
- win_count, count = 0, 0
- for winner_result in winner_results:
- if winner_result['ab_winner'] == model_id:
- win_count += 1
- if winner_result['ba_winner'] == model_id:
- win_count += 1
- count += 2
-
- win_rate = win_count / count * 100
- output = f'Winning Rate for {model_id} vs. {baseline_id}: {win_rate:.2f}%'
- print(output)
- with open(os.path.join(output_dir, 'log.txt'), 'a') as f:
- f.write(output + '\n')
- # python -m evaluation.gaming.llm_judge --model_id qwen25omni_7game_s3156_threshold_50.0 --prediction_jsonl evaluation/gaming/gaming/qwen25omni_7game_s3156_threshold_50.0.jsonl --num_workers 4
|