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
243 lines
8.9 KiB
Python
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)
|