| |
| """ |
| EvoRM 全面实验脚本 |
| =================== |
| 覆盖论文全部 5 个 Research Questions: |
| RQ1: 主实验 (ICEWS-WIKI + ICEWS-YAGO + BETA) |
| RQ2: 跨模型分析 (GPT-3.5, GPT-4o-mini, DeepSeek) |
| RQ3: 消融实验 (Full / w/o Stage-1 / w/o Maintenance) |
| RQ4: 效率分析 (Token消耗 + 延迟) |
| RQ5: 冷启动扩展 (不同 warmup 规模) |
| |
| 用法: |
| python run_comprehensive.py --dataset icews_wiki --all |
| python run_comprehensive.py --dataset icews_yago --main |
| python run_comprehensive.py --dataset BETA --ablation |
| python run_comprehensive.py --all_datasets --all |
| """ |
|
|
| import os, sys, json, time, shutil, argparse, subprocess |
| from datetime import datetime |
| from collections import defaultdict |
|
|
| |
| API_CONFIGS = { |
| 'gpt-3.5-turbo': { |
| 'base_url': 'https://hk.xty.app/v1', |
| 'api_key': 'sk-7a7Ev4VcVyysPLT5hqtqIVD6PybzJ1ZlEIVZddIR3NtZvPgK', |
| 'model': 'gpt-3.5-turbo-1106', |
| }, |
| 'gpt-4o-mini': { |
| 'base_url': 'https://hk.xty.app/v1', |
| 'api_key': 'sk-7a7Ev4VcVyysPLT5hqtqIVD6PybzJ1ZlEIVZddIR3NtZvPgK', |
| 'model': 'gpt-4o-mini', |
| }, |
| } |
|
|
| DATA_BASE = "/root/autodl-tmp/AdaCoAgentEA/data" |
| RESULTS_DIR = "/root/autodl-tmp/AdaCoAgentEA/results" |
|
|
| os.makedirs(RESULTS_DIR, exist_ok=True) |
|
|
| |
| def load_ref_pairs(path): |
| pairs = set() |
| if not os.path.exists(path): |
| return pairs |
| with open(path, 'r') as f: |
| for line in f: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| pairs.add((int(parts[0]), int(parts[1]))) |
| except ValueError: |
| pass |
| return pairs |
|
|
| def compute_metrics(aligned_file, ref_pairs_file, ref_subset=None): |
| aligned = set() |
| if os.path.exists(aligned_file): |
| with open(aligned_file, 'r') as f: |
| for line in f: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| aligned.add((int(parts[0]), int(parts[1]))) |
| except: |
| continue |
|
|
| if ref_subset: |
| ref_set = ref_subset |
| else: |
| ref_set = load_ref_pairs(ref_pairs_file) |
|
|
| if not ref_set: |
| return {'error': 'No reference pairs'} |
|
|
| tp = len(aligned & ref_set) |
| fp = len(aligned - ref_set) |
| fn = len(ref_set - aligned) |
|
|
| precision = tp / max(1, tp + fp) |
| recall = tp / max(1, tp + fn) |
| hits1 = tp / len(ref_set) |
|
|
| return { |
| 'Hits@1': round(hits1, 4), |
| 'MRR': round(hits1, 4), |
| 'Precision': round(precision, 4), |
| 'Recall': round(recall, 4), |
| 'TP': tp, 'FP': fp, 'FN': fn, |
| 'Total_Ref': len(ref_set), |
| 'Total_Aligned': len(aligned), |
| } |
|
|
| def clear_state(data_dir): |
| evorm_state = os.path.join(data_dir, "evorm_state") |
| aligned_file = os.path.join(data_dir, "message_pool", "aligned_entities.txt") |
| if os.path.exists(evorm_state): |
| shutil.rmtree(evorm_state) |
| if os.path.exists(aligned_file): |
| os.remove(aligned_file) |
|
|
| def setup_api(model_name): |
| config = API_CONFIGS[model_name] |
| os.environ["OPENAI_API_BASE"] = config['base_url'] |
| os.environ["OPENAI_API_KEY"] = config['api_key'] |
| return config |
|
|
| |
| def run_main_experiment(data_dir, dataset_name, model_name='gpt-3.5-turbo'): |
| print(f"\n{'='*70}") |
| print(f"RQ1: MAIN EXPERIMENT - {dataset_name} ({model_name})") |
| print(f"{'='*70}") |
|
|
| config = setup_api(model_name) |
| ref_pairs_file = os.path.join(data_dir, "ref_pairs") |
| aligned_file = os.path.join(data_dir, "message_pool", "aligned_entities.txt") |
| important_file = os.path.join(data_dir, "message_pool", "important_entities.txt") |
| retriever_file = os.path.join(data_dir, "message_pool", "retriever_outputs.txt") |
|
|
| |
| has_kg = os.path.exists(os.path.join(data_dir, 'triples_1')) |
| has_retriever = os.path.exists(retriever_file) |
|
|
| if not has_retriever and not has_kg: |
| print(f" SKIP: {dataset_name} missing retriever_outputs.txt and triples") |
| return None |
|
|
| |
| if os.path.exists(important_file): |
| bak = important_file + ".bak_evorm" |
| if not os.path.exists(bak): |
| shutil.copy(important_file, bak) |
|
|
| clear_state(data_dir) |
|
|
| |
| if has_retriever: |
| shutil.copy(retriever_file, important_file) |
|
|
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA') |
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA/Area2') |
|
|
| import tokens_cal |
| tokens_cal.global_tokens = 0 |
|
|
| from LLM1_label_selector import align_entities |
|
|
| start_time = time.time() |
| aligned_pairs = align_entities(data_dir, from_m3=False) |
| elapsed = time.time() - start_time |
|
|
| tokens = tokens_cal.global_tokens |
| n_pairs = len(aligned_pairs) |
|
|
| metrics = {} |
| if os.path.exists(ref_pairs_file): |
| metrics = compute_metrics(aligned_file, ref_pairs_file) |
|
|
| evorm_info = {} |
| evorm_state = os.path.join(data_dir, "evorm_state") |
| if os.path.exists(evorm_state): |
| rules_file = os.path.join(evorm_state, "rules.json") |
| if os.path.exists(rules_file): |
| with open(rules_file) as f: |
| evorm_info['rules'] = len(json.load(f)) |
| hedges_file = os.path.join(evorm_state, "hyperedges.json") |
| if os.path.exists(hedges_file): |
| with open(hedges_file) as f: |
| evorm_info['hyperedges'] = len(json.load(f)) |
|
|
| result = { |
| 'dataset': dataset_name, |
| 'model': model_name, |
| 'aligned_pairs': n_pairs, |
| 'time_seconds': round(elapsed, 1), |
| 'tokens': tokens, |
| 'tokens_per_pair': round(tokens / max(1, n_pairs), 1), |
| 'time_per_pair_s': round(elapsed / max(1, n_pairs), 2), |
| 'metrics': metrics, |
| 'evorm_state': evorm_info, |
| } |
|
|
| print(f" Aligned: {n_pairs}, Time: {elapsed:.1f}s, Tokens: {tokens}") |
| if metrics: |
| print(f" Hits@1: {metrics.get('Hits@1', 'N/A')}, Precision: {metrics.get('Precision', 'N/A')}") |
|
|
| return result |
|
|
| |
| def run_cross_model_experiment(data_dir, dataset_name): |
| print(f"\n{'='*70}") |
| print(f"RQ2: CROSS-MODEL ANALYSIS - {dataset_name}") |
| print(f"{'='*70}") |
|
|
| models = ['gpt-3.5-turbo', 'gpt-4o-mini'] |
| results = {} |
|
|
| for model_name in models: |
| result = run_main_experiment(data_dir, dataset_name, model_name) |
| if result: |
| results[model_name] = result |
|
|
| return results |
|
|
| |
| def run_ablation_experiment(data_dir, dataset_name, n_entities=200): |
| print(f"\n{'='*70}") |
| print(f"RQ3: ABLATION STUDY - {dataset_name} ({n_entities} entities)") |
| print(f"{'='*70}") |
|
|
| config = setup_api('gpt-3.5-turbo') |
| ref_pairs_file = os.path.join(data_dir, "ref_pairs") |
| aligned_file = os.path.join(data_dir, "message_pool", "aligned_entities.txt") |
| important_file = os.path.join(data_dir, "message_pool", "important_entities.txt") |
| retriever_file = os.path.join(data_dir, "message_pool", "retriever_outputs.txt") |
|
|
| if not os.path.exists(retriever_file): |
| print(f" SKIP: no retriever_outputs.txt") |
| return None |
|
|
| |
| ref_set = load_ref_pairs(ref_pairs_file) |
| if not ref_set: |
| print(f" SKIP: no ref_pairs") |
| return None |
|
|
| |
| kg1_order = [] |
| with open(retriever_file, 'r') as f: |
| for line in f: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| e1 = int(parts[0]) |
| if e1 not in kg1_order: |
| kg1_order.append(e1) |
| except: |
| pass |
|
|
| |
| ref_kg1 = set(e1 for e1, e2 in ref_set) |
| kg1_order_filtered = [e for e in kg1_order if e in ref_kg1] |
| if len(kg1_order_filtered) < n_entities: |
| n_entities = len(kg1_order_filtered) |
| print(f" WARNING: Only {n_entities} entities with ref pairs available") |
| subset_kg1 = set(kg1_order_filtered[:n_entities]) |
| subset_ref = {e1: ref for e1, ref in ref_set if e1 in subset_kg1} |
| ref_set_limited = set(subset_ref.items()) |
|
|
| |
| subset_lines = [] |
| with open(retriever_file, 'r') as f: |
| for line in f: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| if int(parts[0]) in subset_kg1: |
| subset_lines.append(line) |
| except: |
| pass |
|
|
| |
| bak = important_file + ".bak_evorm" |
| if not os.path.exists(bak): |
| shutil.copy(important_file, bak) |
|
|
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA') |
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA/Area2') |
|
|
| configs = [ |
| ('Full System (EvoRM)', None), |
| ('w/o Stage-1 (Symbolic)', 'no_stage1'), |
| ('w/o Maintenance', 'no_maintenance'), |
| ('w/o MLP Gate', 'no_mlp_gate'), |
| ('w/o Hypergraph', 'no_hypergraph'), |
| ('w/o Mlight', 'no_mlight'), |
| ] |
|
|
| results = {} |
| for config_name, ablation_mode in configs: |
| print(f"\n --- {config_name} ---") |
|
|
| with open(important_file, 'w') as f: |
| f.writelines(subset_lines) |
|
|
| clear_state(data_dir) |
|
|
| import tokens_cal |
| tokens_cal.global_tokens = 0 |
|
|
| from LLM1_label_selector import align_entities |
|
|
| start_time = time.time() |
| aligned_pairs = align_entities(data_dir, from_m3=False, ablation_mode=ablation_mode) |
| elapsed = time.time() - start_time |
|
|
| tokens = tokens_cal.global_tokens |
| n_pairs = len(aligned_pairs) |
| metrics = compute_metrics(aligned_file, ref_pairs_file, ref_subset=ref_set_limited) |
|
|
| results[config_name] = { |
| 'aligned_pairs': n_pairs, |
| 'time_seconds': round(elapsed, 1), |
| 'tokens': tokens, |
| 'tokens_per_pair': round(tokens / max(1, n_pairs), 1), |
| 'metrics': metrics, |
| } |
|
|
| print(f" Aligned: {n_pairs}, Hits@1: {metrics['Hits@1']}, Tokens: {tokens}") |
|
|
| |
| shutil.copy(bak, important_file) |
| return results |
|
|
| |
| def run_efficiency_experiment(data_dir, dataset_name): |
| print(f"\n{'='*70}") |
| print(f"RQ4: EFFICIENCY ANALYSIS - {dataset_name}") |
| print(f"{'='*70}") |
|
|
| config = setup_api('gpt-3.5-turbo') |
| ref_pairs_file = os.path.join(data_dir, "ref_pairs") |
| aligned_file = os.path.join(data_dir, "message_pool", "aligned_entities.txt") |
| important_file = os.path.join(data_dir, "message_pool", "important_entities.txt") |
| retriever_file = os.path.join(data_dir, "message_pool", "retriever_outputs.txt") |
|
|
| if not os.path.exists(retriever_file): |
| print(f" SKIP: no retriever_outputs.txt") |
| return None |
|
|
| |
| with open(retriever_file, 'r') as f: |
| all_lines = f.readlines() |
|
|
| |
| kg1_order = [] |
| for line in all_lines: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| e1 = int(parts[0]) |
| if e1 not in kg1_order: |
| kg1_order.append(e1) |
| except: |
| pass |
|
|
| |
| bak = important_file + ".bak_evorm" |
| if not os.path.exists(bak): |
| shutil.copy(important_file, bak) |
|
|
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA') |
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA/Area2') |
|
|
| |
| ref_pairs = load_ref_pairs(ref_pairs_file) |
| ref_kg1 = set(e1 for e1, e2 in ref_pairs) |
| kg1_order_filt = [e for e in kg1_order if e in ref_kg1] |
|
|
| |
| entity_counts = [100, 300, 500] |
| results = {} |
|
|
| for n_entities in entity_counts: |
| print(f"\n --- N_entities = {n_entities} ---") |
|
|
| actual_n = min(n_entities, len(kg1_order_filt)) |
| subset_kg1 = set(kg1_order_filt[:actual_n]) |
| subset_lines = [l for l in all_lines |
| if len(l.strip().split('\t')) == 2 |
| and int(l.strip().split('\t')[0]) in subset_kg1] |
|
|
| with open(important_file, 'w') as f: |
| f.writelines(subset_lines) |
|
|
| clear_state(data_dir) |
|
|
| import tokens_cal |
| tokens_cal.global_tokens = 0 |
|
|
| from LLM1_label_selector import align_entities |
|
|
| |
| start_time = time.time() |
| aligned_pairs = align_entities(data_dir, from_m3=False) |
| total_time = time.time() - start_time |
|
|
| tokens = tokens_cal.global_tokens |
| n_pairs = len(aligned_pairs) |
|
|
| results[f'n_{n_entities}'] = { |
| 'n_pairs': n_pairs, |
| 'total_time_s': round(total_time, 2), |
| 'time_per_pair_s': round(total_time / max(1, n_pairs), 3), |
| 'tokens': tokens, |
| 'tokens_per_pair': round(tokens / max(1, n_pairs), 1), |
| 'throughput_pairs_per_s': round(n_pairs / max(1, total_time), 2), |
| } |
|
|
| print(f" Pairs: {n_pairs}, Time: {total_time:.1f}s, " |
| f"Time/Pair: {total_time/max(1,n_pairs):.3f}s, " |
| f"Tokens/Pair: {tokens/max(1,n_pairs):.1f}") |
|
|
| |
| shutil.copy(bak, important_file) |
| return results |
|
|
| |
| def run_coldstart_experiment(data_dir, dataset_name): |
| print(f"\n{'='*70}") |
| print(f"RQ5: COLD-START SCALING - {dataset_name}") |
| print(f"{'='*70}") |
|
|
| config = setup_api('gpt-3.5-turbo') |
| ref_pairs_file = os.path.join(data_dir, "ref_pairs") |
| aligned_file = os.path.join(data_dir, "message_pool", "aligned_entities.txt") |
| important_file = os.path.join(data_dir, "message_pool", "important_entities.txt") |
| retriever_file = os.path.join(data_dir, "message_pool", "retriever_outputs.txt") |
|
|
| if not os.path.exists(retriever_file): |
| print(f" SKIP: no retriever_outputs.txt") |
| return None |
|
|
| with open(retriever_file, 'r') as f: |
| all_lines = f.readlines() |
|
|
| kg1_order = [] |
| for line in all_lines: |
| parts = line.strip().split('\t') |
| if len(parts) == 2: |
| try: |
| e1 = int(parts[0]) |
| if e1 not in kg1_order: |
| kg1_order.append(e1) |
| except: |
| pass |
|
|
| ref_pairs = load_ref_pairs(ref_pairs_file) |
| ref_map = {e1: e2 for e1, e2 in ref_pairs} |
|
|
| bak = important_file + ".bak_evorm" |
| if not os.path.exists(bak): |
| shutil.copy(important_file, bak) |
|
|
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA') |
| sys.path.insert(0, '/root/autodl-tmp/AdaCoAgentEA/Area2') |
|
|
| |
| ref_kg1 = set(e1 for e1, e2 in ref_pairs) |
| kg1_order_filt = [e for e in kg1_order if e in ref_kg1] |
|
|
| warmup_sizes = [100, 200, 500, 1000] |
| results = {} |
|
|
| for n_warmup in warmup_sizes: |
| print(f"\n --- N_warmup = {n_warmup} ---") |
|
|
| actual_n = min(n_warmup, len(kg1_order_filt)) |
| subset_kg1 = set(kg1_order_filt[:actual_n]) |
| subset_ref = {e1: ref_map[e1] for e1 in subset_kg1 if e1 in ref_map} |
| ref_set_limited = set(subset_ref.items()) |
|
|
| subset_lines = [l for l in all_lines |
| if len(l.strip().split('\t')) == 2 |
| and int(l.strip().split('\t')[0]) in subset_kg1] |
|
|
| with open(important_file, 'w') as f: |
| f.writelines(subset_lines) |
|
|
| clear_state(data_dir) |
|
|
| import tokens_cal |
| tokens_cal.global_tokens = 0 |
|
|
| from LLM1_label_selector import align_entities |
|
|
| start_time = time.time() |
| aligned_pairs = align_entities(data_dir, from_m3=False) |
| elapsed = time.time() - start_time |
|
|
| tokens = tokens_cal.global_tokens |
| n_pairs = len(aligned_pairs) |
| metrics = compute_metrics(aligned_file, ref_pairs_file, ref_subset=ref_set_limited) |
|
|
| evorm_info = {} |
| evorm_state = os.path.join(data_dir, "evorm_state") |
| if os.path.exists(evorm_state): |
| rules_file = os.path.join(evorm_state, "rules.json") |
| if os.path.exists(rules_file): |
| with open(rules_file) as f: |
| evorm_info['rules'] = len(json.load(f)) |
|
|
| results[f'n_{n_warmup}'] = { |
| 'aligned_pairs': n_pairs, |
| 'time_seconds': round(elapsed, 1), |
| 'tokens': tokens, |
| 'tokens_per_pair': round(tokens / max(1, n_pairs), 1), |
| 'metrics': metrics, |
| 'evorm_state': evorm_info, |
| } |
|
|
| print(f" Aligned: {n_pairs}, Hits@1: {metrics['Hits@1']}, " |
| f"Tokens/Pair: {tokens/max(1,n_pairs):.1f}, Rules: {evorm_info.get('rules', 0)}") |
|
|
| shutil.copy(bak, important_file) |
| return results |
|
|
| |
| def main(): |
| parser = argparse.ArgumentParser(description='EvoRM Comprehensive Experiments') |
| parser.add_argument('--dataset', type=str, default='icews_wiki', |
| choices=['icews_wiki', 'icews_yago', 'BETA'], |
| help='Dataset to run on') |
| parser.add_argument('--all_datasets', action='store_true', |
| help='Run on all available datasets') |
| parser.add_argument('--all', action='store_true', help='Run all experiments') |
| parser.add_argument('--main', action='store_true', help='RQ1: Main experiment') |
| parser.add_argument('--cross_model', action='store_true', help='RQ2: Cross-model') |
| parser.add_argument('--ablation', action='store_true', help='RQ3: Ablation study') |
| parser.add_argument('--efficiency', action='store_true', help='RQ4: Efficiency') |
| parser.add_argument('--coldstart', action='store_true', help='RQ5: Cold-start') |
| args = parser.parse_args() |
|
|
| run_all = args.all or not any([args.main, args.cross_model, args.ablation, |
| args.efficiency, args.coldstart]) |
|
|
| datasets = [args.dataset] |
| if args.all_datasets: |
| datasets = ['icews_wiki', 'icews_yago', 'BETA'] |
|
|
| all_results = {} |
|
|
| for ds_name in datasets: |
| data_dir = os.path.join(DATA_BASE, ds_name) |
| if not os.path.exists(data_dir): |
| print(f"SKIP: {data_dir} not found") |
| continue |
|
|
| print(f"\n{'#'*70}") |
| print(f"# DATASET: {ds_name}") |
| print(f"{'#'*70}") |
|
|
| ds_results = {} |
|
|
| if run_all or args.main: |
| ds_results['RQ1_main'] = run_main_experiment(data_dir, ds_name) |
|
|
| if run_all or args.cross_model: |
| ds_results['RQ2_cross_model'] = run_cross_model_experiment(data_dir, ds_name) |
|
|
| if run_all or args.ablation: |
| ds_results['RQ3_ablation'] = run_ablation_experiment(data_dir, ds_name) |
|
|
| if run_all or args.efficiency: |
| ds_results['RQ4_efficiency'] = run_efficiency_experiment(data_dir, ds_name) |
|
|
| if run_all or args.coldstart: |
| ds_results['RQ5_coldstart'] = run_coldstart_experiment(data_dir, ds_name) |
|
|
| all_results[ds_name] = ds_results |
|
|
| |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
| result_file = os.path.join(RESULTS_DIR, f"comprehensive_{timestamp}.json") |
| with open(result_file, 'w') as f: |
| json.dump(all_results, f, indent=2) |
|
|
| |
| print(f"\n{'='*80}") |
| print("COMPREHENSIVE EXPERIMENT SUMMARY") |
| print(f"{'='*80}") |
|
|
| for ds_name, ds_results in all_results.items(): |
| print(f"\n--- {ds_name} ---") |
| for exp_name, exp_result in ds_results.items(): |
| if exp_result and isinstance(exp_result, dict): |
| if 'metrics' in exp_result and exp_result['metrics']: |
| print(f" {exp_name}: Hits@1={exp_result['metrics'].get('Hits@1','N/A')}, " |
| f"Tokens={exp_result.get('tokens','N/A')}") |
|
|
| print(f"\nResults saved to: {result_file}") |
|
|
| if __name__ == '__main__': |
| main() |