Files
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

243 lines
8.9 KiB
Python

"""
Main script for Model Leaderboard Calculation
Experiment 7-7: Building Model Leaderboard from Pairwise Comparison Data
Supports two methods (following official Chatbot Arena):
1. Online Elo (K=4) - Simple but order-dependent
2. Bradley-Terry MLE - Official leaderboard method (more stable)
"""
import os
import sys
import pandas as pd
from bradley_terry import (
compute_bradley_terry_leaderboard,
predict_win_rate,
)
from data_loader import download_arena_data, filter_data, load_arena_data
from elo_rating import EloRatingSystem
from parallel_processing import optimize_dataframe
from visualization import plot_leaderboard, plot_rating_distribution, plot_win_rate_matrix
def compute_online_elo_leaderboard(df: pd.DataFrame) -> pd.DataFrame:
"""
Compute leaderboard using online Elo updates (K=4, official value).
This method updates ratings sequentially as matches are processed.
It's simpler but can be unstable and order-dependent.
Args:
df: DataFrame with columns 'model_a', 'model_b', 'winner'
Returns:
DataFrame with model ratings
"""
from tqdm import tqdm
print("Computing online Elo ratings (K=4)...")
elo = EloRatingSystem(initial_rating=1000.0, k_factor=4.0)
# Process matches sequentially
for _, row in tqdm(df.iterrows(), total=len(df), desc="Processing matches"):
elo.update_ratings(row['model_a'], row['model_b'], row['winner'])
# Get leaderboard
leaderboard = elo.get_leaderboard()
# Convert to DataFrame
result = pd.DataFrame(leaderboard, columns=['model', 'rating', 'matches', 'wins'])
return result
def main(method: str = 'bradley-terry'):
"""
Run model leaderboard calculation.
Args:
method: 'bradley-terry' (default, official) or 'online-elo' (simple)
"""
print("="*80)
print("Experiment 7-7: Building Model Leaderboard from Pairwise Comparisons")
if method == 'bradley-terry':
print("Method: Bradley-Terry Model with MLE (Official Chatbot Arena)")
else:
print("Method: Online Elo Updates (K=4)")
print("="*80)
print()
# Step 1: Download and load data
print("Step 1: Loading Chatbot Arena voting data...")
print("-" * 80)
data_file = "arena_data.json"
try:
# Download data if not exists
if not os.path.exists(data_file):
data_file = download_arena_data(data_file)
# Load data
df = load_arena_data(data_file)
# Optimize memory usage
df = optimize_dataframe(df)
except Exception as e: # noqa: BLE001 - surface loader/provider diagnostics to CLI users
print(f"Error loading data: {e}")
print("\nNote: If the data download fails, you can manually download the file from:")
print("https://storage.googleapis.com/arena_external_data/public/clean_battle_20240814_public.json")
print("and save it as 'arena_data.json' in the current directory.")
return
print()
# Step 2: Filter data (official Chatbot Arena method)
print("Step 2: Filtering data (following official Arena method)...")
print("-" * 80)
df_filtered = filter_data(
df,
anony_only=True, # Only anonymous/blind votes
use_dedup=True, # Apply deduplication (removes top 0.1% redundant prompts)
min_turn=1
)
print()
# Step 3: Compute ratings using selected method
print(f"Step 3: Computing ratings using {method} method...")
print("-" * 80)
if method == 'bradley-terry':
print("Note: Bradley-Terry model uses sklearn LogisticRegression for MLE.")
print("This is the official Chatbot Arena method - more stable than online Elo.")
print()
# Compute ratings with bootstrap for confidence intervals
leaderboard_df = compute_bradley_terry_leaderboard(df_filtered, bootstrap_rounds=100)
print("\nTop 20 models by Bradley-Terry rating:")
print("-" * 80)
print(f"{'Rank':<6}{'Model':<35}{'Rating':<10}{'95% CI':<20}")
print("-" * 80)
for idx, row in leaderboard_df.head(20).iterrows():
if 'lower_ci' in row and 'upper_ci' in row:
ci_str = f"[{row['lower_ci']:.1f}, {row['upper_ci']:.1f}]"
else:
ci_str = "N/A"
print(f"{idx+1:<6}{row['model']:<35}{row['rating']:7.1f} {ci_str:<20}")
else: # online-elo
print("Note: Online Elo uses K=4 (official value) for stable ratings.")
print("Processes matches sequentially - simpler but can be order-dependent.")
print()
# Compute online Elo ratings
leaderboard_df = compute_online_elo_leaderboard(df_filtered)
print("\nTop 20 models by Online Elo rating:")
print("-" * 80)
print(f"{'Rank':<6}{'Model':<35}{'Rating':<10}{'Matches':<10}{'Win Rate':<10}")
print("-" * 80)
for idx, row in leaderboard_df.head(20).iterrows():
win_rate = row['wins'] / row['matches'] * 100 if row['matches'] > 0 else 0
print(f"{idx+1:<6}{row['model']:<35}{row['rating']:7.1f} {row['matches']:<10}{win_rate:6.1f}%")
print()
# Step 4: Predict win rates using Bradley-Terry model
print("Step 4: Calculating predicted win rates...")
print("-" * 80)
# Get ratings as dictionary
ratings_dict = dict(zip(leaderboard_df['model'], leaderboard_df['rating']))
# Predict win rates
predicted_win_rates = predict_win_rate(ratings_dict)
print(f"Calculated predicted win rates for {len(ratings_dict)} models")
print()
# Step 5: Create visualizations
print("Step 5: Creating visualizations...")
print("-" * 80)
# Convert leaderboard_df to format expected by visualization functions
leaderboard_tuples = [(row['model'], row['rating'], 0, 0) for _, row in leaderboard_df.iterrows()]
plot_leaderboard(leaderboard_tuples, top_n=20, save_path="leaderboard.png")
plot_rating_distribution(leaderboard_tuples, save_path="rating_distribution.png")
# Plot win rate matrix
top_30_models = leaderboard_df.head(30)['model'].tolist()
plot_win_rate_matrix(predicted_win_rates.loc[top_30_models, top_30_models],
top_n=30, save_path="win_rate_matrix.png")
print()
# Summary
print("="*80)
print("Analysis complete!")
print("="*80)
print("\nGenerated files:")
print(" - leaderboard.png : Top 20 models by Bradley-Terry rating")
print(" - rating_distribution.png : Distribution of ratings")
print(" - win_rate_matrix.png : Predicted win rate matrix (top 30 models)")
print()
# Method summary
print("Method:")
print("-" * 80)
if method == 'bradley-terry':
print(" ✓ Bradley-Terry model with Maximum Likelihood Estimation")
print(" ✓ sklearn LogisticRegression for stable rating computation")
print(" ✓ Bootstrap confidence intervals (100 samples)")
else:
print(" ✓ Online Elo with K=4 (official value)")
print(" ✓ Sequential match processing")
print(" ✓ Simple but order-dependent")
print(" ✓ Deduplication filter (removes top 0.1% redundant prompts)")
print(" ✓ Anonymous votes only (blind evaluation)")
print()
# Key insights
print("Key Insights:")
print("-" * 80)
print(f" • Total models analyzed: {len(leaderboard_df)}")
print(f" • Total battles: {len(df_filtered):,}")
print(f" • Rating range: {leaderboard_df['rating'].min():.1f} - {leaderboard_df['rating'].max():.1f}")
print(f" • Top model: {leaderboard_df.iloc[0]['model']} ({leaderboard_df.iloc[0]['rating']:.1f})")
if 'lower_ci' in leaderboard_df.columns:
avg_ci_width = (leaderboard_df['upper_ci'] - leaderboard_df['lower_ci']).mean()
print(f" • Average confidence interval width: {avg_ci_width:.1f} rating points")
print()
print("This implementation matches the official Chatbot Arena leaderboard calculation!")
print("Source: https://colab.research.google.com/drive/1KdwokPjirkTmpO_P1WByFNFiqxWQquwH")
print()
if __name__ == "__main__":
# Allow method selection via command line argument
import sys
method = 'bradley-terry' # Default to official method
if len(sys.argv) > 1:
if sys.argv[1] in ['bradley-terry', 'bt', 'mle']:
method = 'bradley-terry'
elif sys.argv[1] in ['online-elo', 'elo', 'online']:
method = 'online-elo'
else:
print(f"Unknown method: {sys.argv[1]}")
print("Usage: python main.py [bradley-terry|online-elo]")
print(" bradley-terry (default): Official Arena method, more stable")
print(" online-elo: Simple Elo updates with K=4")
sys.exit(1)
main(method)