Spaces:
Running
Running
import argparse | |
import datetime | |
import math | |
import os | |
from collections import defaultdict | |
from glob import glob | |
import numpy as np | |
import pandas as pd | |
import plotly.express as px | |
from sklearn.linear_model import LogisticRegression | |
from tqdm import tqdm | |
from utils import load_model_answers | |
def compute_mle_elo(df, SCALE=400, BASE=10, INIT_RATING=1000): | |
models = pd.concat([df["model_a"], df["model_b"]]).unique() | |
models = pd.Series(np.arange(len(models)), index=models) | |
# duplicate battles | |
df = pd.concat([df, df], ignore_index=True) | |
p = len(models.index) | |
n = df.shape[0] | |
X = np.zeros([n, p]) | |
X[np.arange(n), models[df["model_a"]]] = +math.log(BASE) | |
X[np.arange(n), models[df["model_b"]]] = -math.log(BASE) | |
# one A win => two A win | |
Y = np.zeros(n) | |
Y[df["winner"] == "model_a"] = 1.0 | |
# one tie => one A win + one B win | |
# find tie + tie (both bad) index | |
tie_idx = (df["winner"] == "tie") | (df["winner"] == "tie (bothbad)") | |
tie_idx[len(tie_idx) // 2 :] = False | |
Y[tie_idx] = 1.0 | |
lr = LogisticRegression(fit_intercept=False, penalty=None, tol=1e-8) | |
lr.fit(X, Y) | |
elo_scores = SCALE * lr.coef_[0] + INIT_RATING | |
# set anchor as gpt-3.5-turbo-0125 = 1000 | |
if "gpt-3.5-turbo-0125" in models.index: | |
elo_scores += 1000 - elo_scores[models["gpt-3.5-turbo-0125"]] | |
return pd.Series(elo_scores, index=models.index).sort_values(ascending=False) | |
def get_bootstrap_result(battles, func_compute_elo, num_round): | |
rows = [] | |
for i in tqdm(range(num_round), desc="bootstrap"): | |
rows.append(func_compute_elo(battles.sample(frac=1.0, replace=True))) | |
df = pd.DataFrame(rows) | |
return df[df.median().sort_values(ascending=False).index] | |
def preety_print_two_ratings(ratings_1, ratings_2, column_names): | |
df = ( | |
pd.DataFrame( | |
[[n, ratings_1[n], ratings_2[n]] for n in ratings_1.keys()], | |
columns=["Model", column_names[0], column_names[1]], | |
) | |
.sort_values(column_names[0], ascending=False) | |
.reset_index(drop=True) | |
) | |
df[column_names[0]] = (df[column_names[0]] + 0.5).astype(int) | |
df[column_names[1]] = (df[column_names[1]] + 0.5).astype(int) | |
df.index = df.index + 1 | |
return df | |
def visualize_bootstrap_scores(df, title): | |
bars = ( | |
pd.DataFrame(dict(lower=df.quantile(0.025), rating=df.quantile(0.5), upper=df.quantile(0.975))) | |
.reset_index(names="model") | |
.sort_values("rating", ascending=False) | |
) | |
bars["error_y"] = bars["upper"] - bars["rating"] | |
bars["error_y_minus"] = bars["rating"] - bars["lower"] | |
bars["rating_rounded"] = np.round(bars["rating"], 2) | |
fig = px.scatter( | |
bars, | |
x="model", | |
y="rating", | |
error_y="error_y", | |
error_y_minus="error_y_minus", | |
text="rating_rounded", | |
title=title, | |
) | |
fig.update_layout(xaxis_title="Model", yaxis_title="Rating", height=600) | |
return fig | |
def predict_win_rate(elo_ratings, SCALE=400, BASE=10, INIT_RATING=1000): | |
names = sorted(list(elo_ratings.keys())) | |
wins = defaultdict(lambda: defaultdict(lambda: 0)) | |
for a in names: | |
for b in names: | |
ea = 1 / (1 + BASE ** ((elo_ratings[b] - elo_ratings[a]) / SCALE)) | |
wins[a][b] = ea | |
wins[b][a] = 1 - ea | |
data = {a: [wins[a][b] if a != b else np.NAN for b in names] for a in names} | |
df = pd.DataFrame(data, index=names) | |
df.index.name = "model_a" | |
df.columns.name = "model_b" | |
return df.T | |
def get_win_rate_column(df, column, baseline="gpt-3.5-turbo-0125"): | |
to_dict = df[["model", column]].set_index("model").to_dict()[column] | |
win_rate_table = predict_win_rate(to_dict) | |
return win_rate_table[baseline].fillna(0.5).apply(lambda x: round(x * 100, 2)) | |
def get_battles_from_judgment(judge_name, first_game_only=False, WEIGHT=3): | |
arena_hard_battles = pd.DataFrame() | |
print("Turning judgment results into battles...") | |
directory = f"data/arena-hard-v0.1/model_judgement/{judge_name}" | |
assert os.path.exists(directory) | |
for file in tqdm(glob(f"{directory}/*jsonl")): | |
df = pd.read_json(file, lines=True) | |
for _, row in df.iterrows(): | |
# game 1 | |
output = {"question_id": row["question_id"], "model_a": "gpt-3.5-turbo-0125", "model_b": row["model"]} | |
game = row["games"][0] | |
weight = 1 | |
if game["score"] == "A=B": | |
output["winner"] = "tie" | |
elif game["score"] == "A>B": | |
output["winner"] = "model_a" | |
elif game["score"] == "A>>B": | |
output["winner"] = "model_a" | |
weight = WEIGHT | |
elif game["score"] == "B>A": | |
output["winner"] = "model_b" | |
elif game["score"] == "B>>A": | |
output["winner"] = "model_b" | |
weight = WEIGHT | |
else: | |
weight = 0 | |
if weight: | |
arena_hard_battles = pd.concat([arena_hard_battles, pd.DataFrame([output] * weight)]) | |
if not first_game_only: | |
# game 2 | |
output = {"question_id": row["question_id"], "model_a": "gpt-3.5-turbo-0125", "model_b": row["model"]} | |
game = row["games"][1] | |
weight = 1 | |
if game["score"] == "A=B": | |
output["winner"] = "tie" | |
elif game["score"] == "A>B": | |
output["winner"] = "model_b" | |
elif game["score"] == "A>>B": | |
output["winner"] = "model_b" | |
weight = WEIGHT | |
elif game["score"] == "B>A": | |
output["winner"] = "model_a" | |
elif game["score"] == "B>>A": | |
output["winner"] = "model_a" | |
weight = WEIGHT | |
else: | |
weight = 0 | |
if weight: | |
arena_hard_battles = pd.concat([arena_hard_battles, pd.DataFrame([output] * weight)]) | |
arena_hard_battles.to_json("data/arena_hard_battles.jsonl", lines=True, orient="records") | |
return arena_hard_battles | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("--bench-name", type=str, default="arena-hard-v0.1") | |
parser.add_argument("--judge-name", type=str, default="gpt-4-1106-preview") | |
parser.add_argument("--baseline", type=str, default="gpt-3.5-turbo-0125") | |
parser.add_argument("--load-battles", action="store_true") | |
parser.add_argument("--load-bootstrap", action="store_true") | |
parser.add_argument("--show-elo", action="store_true") | |
parser.add_argument("--weight", type=int, default=3) | |
parser.add_argument("--num-rounds", type=int, default=100) | |
parser.add_argument("--output", action="store_true") | |
parser.add_argument("--first-game-only", action="store_true") | |
args = parser.parse_args() | |
print(args) | |
assert not args.load_bootstrap or ( | |
args.load_battles and args.load_bootstrap | |
), "If loading prexisting bootstrapping data, you must also load preexisting battles." | |
answer_dir = os.path.join("data", args.bench_name, "model_answer/external") | |
model_answers = load_model_answers(answer_dir) | |
if args.load_battles: | |
assert os.path.exists("data/arena_hard_battles.jsonl") | |
battles = pd.read_json("data/arena_hard_battles.jsonl", lines=True) | |
else: | |
battles = get_battles_from_judgment(args.judge_name, args.first_game_only, args.weight) | |
bootstrap_online_elo = compute_mle_elo(battles) | |
if args.load_bootstrap: | |
bootstrap_elo_lu = pd.read_json("data/bootstrapping_results.jsonl", lines=True) | |
else: | |
np.random.seed(42) | |
bootstrap_elo_lu = get_bootstrap_result(battles, compute_mle_elo, args.num_rounds) | |
bootstrap_elo_lu.to_json("data/bootstrapping_results.jsonl", lines=True, orient="records") | |
stats = pd.DataFrame() | |
stats["results"] = None | |
stats["results"] = stats["results"].astype("object") | |
for i, model in enumerate(bootstrap_online_elo.index): | |
assert model in bootstrap_elo_lu.columns | |
stats.at[i, "model"] = model | |
stats.at[i, "score"] = bootstrap_online_elo[model] | |
stats.at[i, "lower"] = np.percentile(bootstrap_elo_lu[model], 2.5) | |
stats.at[i, "upper"] = np.percentile(bootstrap_elo_lu[model], 97.5) | |
length = 0 | |
if model in model_answers: | |
for _, row in model_answers[model].items(): | |
turn = row["choices"][0]["turns"][0] | |
length += turn["token_len"] | |
length /= len(model_answers[model]) | |
stats.at[i, "avg_tokens"] = int(length) | |
stats.at[i, "results"] = bootstrap_elo_lu[model].tolist() | |
if not args.show_elo: | |
stats.sort_values(by="model", inplace=True) | |
stats["score"] = get_win_rate_column(stats, "score", args.baseline).tolist() | |
stats["lower"] = get_win_rate_column(stats, "lower", args.baseline).tolist() | |
stats["upper"] = get_win_rate_column(stats, "upper", args.baseline).tolist() | |
decimal = 1 | |
else: | |
decimal = 0 | |
stats = stats.astype({"score": int, "lower": int, "upper": int}) | |
stats.sort_values(by="score", ascending=False, inplace=True) | |
for _, row in stats.iterrows(): | |
interval = str((round(row["lower"] - row["score"], decimal), round(row["upper"] - row["score"], decimal))) | |
print( | |
f"{row['model'] : <30} | score: {round(row['score'], decimal) : ^5} | 95% CI: {interval : ^12} | average #tokens: {int(row['avg_tokens'])}" | |
) | |
if args.output: | |
cur_date = datetime.datetime.now() | |
date_str = cur_date.strftime("%Y%m%d") | |
json_file_name = f"arena_hard_leaderboard_{date_str}.json" | |
stats.to_json(json_file_name, orient="records", indent=4) | |
import huggingface_hub | |
huggingface_hub.HfApi().upload_file( | |
path_or_fileobj=json_file_name, | |
path_in_repo="data/leaderboard.json", | |
repo_id="Vikhrmodels/leaderboard", | |
repo_type="space", | |
) | |
huggingface_hub.HfApi().upload_file( | |
path_or_fileobj=json_file_name, | |
path_in_repo=f"leaderboard_logs/{json_file_name}", | |
repo_id="Vikhrmodels/openbench-eval", | |
repo_type="dataset", | |
) | |