From 694de4bfcf3ef3bab22324bee8a0e700358c8bb6 Mon Sep 17 00:00:00 2001 From: Yaseen Shady <139421618+yshady-acheev@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:36:38 -0800 Subject: [PATCH 1/9] A library for analysis --- mlos_analyzer/README.md | 16 ++++ mlos_analyzer/mlos_analyzer/__init__.py | 0 mlos_analyzer/mlos_analyzer/api/__init__.py | 0 mlos_analyzer/mlos_analyzer/api/endpoints.py | 29 +++++++ mlos_analyzer/mlos_analyzer/api/models.py | 4 + mlos_analyzer/mlos_analyzer/core/__init__.py | 0 mlos_analyzer/mlos_analyzer/core/storage.py | 6 ++ mlos_analyzer/mlos_analyzer/utils/__init__.py | 0 .../mlos_analyzer/visualization/__init__.py | 0 .../visualization/correlation.py | 28 +++++++ .../visualization/distributions.py | 23 ++++++ .../visualization/failure_metrics.py | 26 ++++++ .../visualization/performance.py | 82 +++++++++++++++++++ .../mlos_analyzer/visualization/plots.py | 22 +++++ .../visualization/statistical.py | 34 ++++++++ .../mlos_analyzer/visualization/timeseries.py | 30 +++++++ mlos_analyzer/requirements.txt | 11 +++ mlos_analyzer/setup.py | 20 +++++ 18 files changed, 331 insertions(+) create mode 100644 mlos_analyzer/README.md create mode 100644 mlos_analyzer/mlos_analyzer/__init__.py create mode 100644 mlos_analyzer/mlos_analyzer/api/__init__.py create mode 100644 mlos_analyzer/mlos_analyzer/api/endpoints.py create mode 100644 mlos_analyzer/mlos_analyzer/api/models.py create mode 100644 mlos_analyzer/mlos_analyzer/core/__init__.py create mode 100644 mlos_analyzer/mlos_analyzer/core/storage.py create mode 100644 mlos_analyzer/mlos_analyzer/utils/__init__.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/__init__.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/correlation.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/distributions.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/performance.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/plots.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/statistical.py create mode 100644 mlos_analyzer/mlos_analyzer/visualization/timeseries.py create mode 100644 mlos_analyzer/requirements.txt create mode 100644 mlos_analyzer/setup.py diff --git a/mlos_analyzer/README.md b/mlos_analyzer/README.md new file mode 100644 index 00000000000..109cd886095 --- /dev/null +++ b/mlos_analyzer/README.md @@ -0,0 +1,16 @@ +# MLOS Analyzer + +A comprehensive library for analyzing and visualizing MLOS experiment results. + +## Installation +```bash +pip install -r requirements.txt +python setup.py install +``` + +## Features +- FastAPI backend for experiment data retrieval +- Correlation analysis +- Failure metrics visualization +- Statistical analysis +- Advanced plotting capabilities diff --git a/mlos_analyzer/mlos_analyzer/__init__.py b/mlos_analyzer/mlos_analyzer/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mlos_analyzer/mlos_analyzer/api/__init__.py b/mlos_analyzer/mlos_analyzer/api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mlos_analyzer/mlos_analyzer/api/endpoints.py b/mlos_analyzer/mlos_analyzer/api/endpoints.py new file mode 100644 index 00000000000..ebd4a393b1a --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/api/endpoints.py @@ -0,0 +1,29 @@ +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from .models import ExperimentExplanationRequest +from ..core.storage import storage +import logging + +app = FastAPI() +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/experiments") +def get_experiments(): + return list(storage.experiments.keys()) + +@app.get("/experiment_results/{experiment_id}") +def get_experiment_results(experiment_id: str): + try: + exp = storage.experiments[experiment_id] + return exp.results_df.to_dict(orient="records") + except KeyError: + raise HTTPException(status_code=404, detail="Experiment not found") diff --git a/mlos_analyzer/mlos_analyzer/api/models.py b/mlos_analyzer/mlos_analyzer/api/models.py new file mode 100644 index 00000000000..bea4426bc8a --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/api/models.py @@ -0,0 +1,4 @@ +from pydantic import BaseModel + +class ExperimentExplanationRequest(BaseModel): + experiment_id: str diff --git a/mlos_analyzer/mlos_analyzer/core/__init__.py b/mlos_analyzer/mlos_analyzer/core/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mlos_analyzer/mlos_analyzer/core/storage.py b/mlos_analyzer/mlos_analyzer/core/storage.py new file mode 100644 index 00000000000..5e36b2481bd --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/core/storage.py @@ -0,0 +1,6 @@ +from mlos_bench.storage import from_config + +try: + storage = from_config(config="storage/sqlite.jsonc") +except Exception as e: + raise Exception(f"Error loading storage configuration: {e}") diff --git a/mlos_analyzer/mlos_analyzer/utils/__init__.py b/mlos_analyzer/mlos_analyzer/utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mlos_analyzer/mlos_analyzer/visualization/__init__.py b/mlos_analyzer/mlos_analyzer/visualization/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mlos_analyzer/mlos_analyzer/visualization/correlation.py b/mlos_analyzer/mlos_analyzer/visualization/correlation.py new file mode 100644 index 00000000000..0b184086809 --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/correlation.py @@ -0,0 +1,28 @@ +import plotly.express as px +import pandas as pd + + +def plot_heatmap(df: pd.DataFrame): + numeric_df = df.select_dtypes(include=["int64", "float64"]) + config_columns = [col for col in numeric_df.columns if col.startswith("config")] + result_columns = [col for col in numeric_df.columns if col.startswith("result")] + + combined_data = numeric_df[config_columns + result_columns] + correlation_matrix = combined_data.corr() + + fig = px.imshow( + correlation_matrix, + title="Configuration vs Results Correlation Heatmap", + color_continuous_scale="RdBu", + ) + return fig + + +def plot_correlation_table_target(df: pd.DataFrame, target_col: str): + numeric_df = df.select_dtypes(include=["int64", "float64"]) + correlations = numeric_df.corrwith(numeric_df[target_col]).sort_values(ascending=False) + + fig = px.bar( + x=correlations.index, y=correlations.values, title=f"Correlations with {target_col}" + ) + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/distributions.py b/mlos_analyzer/mlos_analyzer/visualization/distributions.py new file mode 100644 index 00000000000..f5f813c8704 --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/distributions.py @@ -0,0 +1,23 @@ +# src/mlos_analyzer/visualization/distributions.py +import plotly.express as px +import plotly.figure_factory as ff + + +def plot_metric_distribution(df, metric: str): + fig = ff.create_distplot( + [df[metric].dropna()], [metric], bin_size=(df[metric].max() - df[metric].min()) / 30 + ) + fig.update_layout(title=f"Distribution of {metric}") + return fig + + +def plot_violin_comparison(df, metric: str, group_by: str = "tunable_config_id"): + fig = px.violin( + df, + x=group_by, + y=metric, + box=True, + points="all", + title=f"{metric} Distribution by {group_by}", + ) + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py b/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py new file mode 100644 index 00000000000..002c2b469ad --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py @@ -0,0 +1,26 @@ +import plotly.express as px +import pandas as pd + + +def plot_success_failure_distribution(df: pd.DataFrame): + status_counts = df["status"].value_counts() + return px.pie( + values=status_counts.values, + names=status_counts.index, + title="Success/Failure Distribution", + ) + + +def plot_failure_rate_by_config(df: pd.DataFrame): + failure_rate = ( + df.groupby("tunable_config_id")["status"] + .apply(lambda x: (x == "FAILED").mean()) + .reset_index() + ) + failure_rate.columns = ["tunable_config_id", "failure_rate"] + return px.bar( + failure_rate, + x="tunable_config_id", + y="failure_rate", + title="Failure Rate by Configuration", + ) diff --git a/mlos_analyzer/mlos_analyzer/visualization/performance.py b/mlos_analyzer/mlos_analyzer/visualization/performance.py new file mode 100644 index 00000000000..8a3e245b334 --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/performance.py @@ -0,0 +1,82 @@ +# src/mlos_analyzer/visualization/performance.py +import plotly.express as px +import plotly.graph_objects as go +from typing import List + + +def plot_parallel_coordinates(df, metrics: List[str]): + fig = px.parallel_coordinates( + df, + dimensions=[col for col in df.columns if col.startswith("config") or col in metrics], + title="Parameter and Metric Relationships", + ) + return fig + + +def plot_performance_radar(df, metrics: List[str], top_n: int = 5): + # Normalize metrics + normalized_df = df.copy() + for metric in metrics: + normalized_df[metric] = (df[metric] - df[metric].min()) / ( + df[metric].max() - df[metric].min() + ) + + # Get top configurations + top_configs = ( + normalized_df.groupby("tunable_config_id")[metrics] + .mean() + .mean(axis=1) + .nlargest(top_n) + .index + ) + + fig = go.Figure() + for config in top_configs: + config_data = normalized_df[normalized_df["tunable_config_id"] == config][metrics].mean() + fig.add_trace( + go.Scatterpolar(r=config_data.values, theta=metrics, name=f"Config {config}") + ) + + fig.update_layout(title=f"Top {top_n} Configurations Performance") + return fig # src/mlos_analyzer/visualization/performance.py + + +import plotly.express as px +import plotly.graph_objects as go + + +def plot_parallel_coordinates(df, metrics: List[str]): + fig = px.parallel_coordinates( + df, + dimensions=[col for col in df.columns if col.startswith("config") or col in metrics], + title="Parameter and Metric Relationships", + ) + return fig + + +def plot_performance_radar(df, metrics: List[str], top_n: int = 5): + # Normalize metrics + normalized_df = df.copy() + for metric in metrics: + normalized_df[metric] = (df[metric] - df[metric].min()) / ( + df[metric].max() - df[metric].min() + ) + + # Get top configurations + top_configs = ( + normalized_df.groupby("tunable_config_id")[metrics] + .mean() + .mean(axis=1) + .nlargest(top_n) + .index + ) + + fig = go.Figure() + for config in top_configs: + config_data = normalized_df[normalized_df["tunable_config_id"] == config][metrics].mean() + fig.add_trace( + go.Scatterpolar(r=config_data.values, theta=metrics, name=f"Config {config}") + ) + + fig.update_layout(title=f"Top {top_n} Configurations Performance") + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/plots.py b/mlos_analyzer/mlos_analyzer/visualization/plots.py new file mode 100644 index 00000000000..93e349a7edb --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/plots.py @@ -0,0 +1,22 @@ +import plotly.express as px +import pandas as pd + + +def plot_whisker_plots(df: pd.DataFrame, target_col: str, n: int = 5): + if "tunable_config_id" not in df.columns or target_col not in df.columns: + raise ValueError(f"Required columns missing") + + df[target_col] = pd.to_numeric(df[target_col], errors="coerce") + df = df.dropna(subset=[target_col]) + + config_avg = df.groupby("tunable_config_id")[target_col].mean().reset_index() + top_n_configs = config_avg.nlargest(n, target_col)["tunable_config_id"] + top_configs = df[df["tunable_config_id"].isin(top_n_configs)] + + fig = px.box( + top_configs, + x="tunable_config_id", + y=target_col, + title=f"Top {n} Configurations by {target_col}", + ) + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/statistical.py b/mlos_analyzer/mlos_analyzer/visualization/statistical.py new file mode 100644 index 00000000000..bc5abac4572 --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/statistical.py @@ -0,0 +1,34 @@ +import plotly.graph_objects as go +import pandas as pd +from scipy import stats + +def run_pairwise_stat_tests(df: pd.DataFrame, metric: str, alpha: float = 0.05): + configs = df["tunable_config_id"].unique() + results = [] + + for i in range(len(configs)): + for j in range(i+1, len(configs)): + group1 = df[df["tunable_config_id"] == configs[i]][metric] + group2 = df[df["tunable_config_id"] == configs[j]][metric] + + stat, pval = stats.ttest_ind(group1, group2) + results.append({ + "Config1": configs[i], + "Config2": configs[j], + "p-value": pval, + "Significant": pval < alpha + }) + + return pd.DataFrame(results) + +def compare_score_distributions(df: pd.DataFrame, metric: str, config1: str, config2: str): + group1 = df[df["tunable_config_id"] == config1][metric] + group2 = df[df["tunable_config_id"] == config2][metric] + + fig = go.Figure() + fig.add_trace(go.Histogram(x=group1, name=f"Config {config1}", opacity=0.7)) + fig.add_trace(go.Histogram(x=group2, name=f"Config {config2}", opacity=0.7)) + + fig.update_layout(barmode="overlay", + title=f"Score Distribution Comparison: Config {config1} vs {config2}") + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/timeseries.py b/mlos_analyzer/mlos_analyzer/visualization/timeseries.py new file mode 100644 index 00000000000..4a9da3433cd --- /dev/null +++ b/mlos_analyzer/mlos_analyzer/visualization/timeseries.py @@ -0,0 +1,30 @@ +# src/mlos_analyzer/visualization/timeseries.py +import plotly.express as px +import plotly.graph_objects as go +from typing import List + + +def plot_metric_over_time(df, metric: str, configs: List[str] = None): + if configs: + df = df[df["tunable_config_id"].isin(configs)] + + fig = px.line( + df, + x="ts_start", + y=metric, + color="tunable_config_id", + title=f"{metric} Over Time by Configuration", + ) + return fig + + +def plot_moving_average(df, metric: str, window: int = 5): + df = df.sort_values("ts_start") + df[f"{metric}_ma"] = df[metric].rolling(window=window).mean() + + fig = go.Figure() + fig.add_trace(go.Scatter(x=df["ts_start"], y=df[metric], mode="markers", name="Raw Data")) + fig.add_trace( + go.Scatter(x=df["ts_start"], y=df[f"{metric}_ma"], name=f"{window}-point Moving Average") + ) + return fig diff --git a/mlos_analyzer/requirements.txt b/mlos_analyzer/requirements.txt new file mode 100644 index 00000000000..d27962adc76 --- /dev/null +++ b/mlos_analyzer/requirements.txt @@ -0,0 +1,11 @@ +fastapi==0.68.0 +pandas==1.3.3 +plotly==5.3.1 +streamlit==1.2.0 +seaborn==0.11.2 +matplotlib==3.4.3 +scikit-learn==0.24.2 +scipy==1.7.1 +watchdog==2.1.6 +uvicorn==0.15.0 +azure-identity==1.7.0 diff --git a/mlos_analyzer/setup.py b/mlos_analyzer/setup.py new file mode 100644 index 00000000000..80e3f37dcbf --- /dev/null +++ b/mlos_analyzer/setup.py @@ -0,0 +1,20 @@ +from setuptools import setup, find_packages + +setup( + name="mlos_analyzer", + version="0.1.0", + packages=find_packages(), + install_requires=[ + "fastapi", + "pandas", + "plotly", + "streamlit", + "seaborn", + "matplotlib", + "scikit-learn", + "scipy", + "watchdog", + "uvicorn", + "azure-identity", + ], +) From 64fb198ca6f443c810dc5e6ce1182c2c7882ae1f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:39:31 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mlos_analyzer/README.md | 2 + mlos_analyzer/mlos_analyzer/__init__.py | 4 ++ mlos_analyzer/mlos_analyzer/api/__init__.py | 4 ++ mlos_analyzer/mlos_analyzer/api/endpoints.py | 12 +++- mlos_analyzer/mlos_analyzer/api/models.py | 5 ++ mlos_analyzer/mlos_analyzer/core/__init__.py | 4 ++ mlos_analyzer/mlos_analyzer/core/storage.py | 4 ++ mlos_analyzer/mlos_analyzer/utils/__init__.py | 4 ++ .../mlos_analyzer/visualization/__init__.py | 4 ++ .../visualization/correlation.py | 6 +- .../visualization/distributions.py | 5 ++ .../visualization/failure_metrics.py | 6 +- .../visualization/performance.py | 16 +++-- .../mlos_analyzer/visualization/plots.py | 6 +- .../visualization/statistical.py | 59 +++++++++++-------- .../mlos_analyzer/visualization/timeseries.py | 10 +++- mlos_analyzer/setup.py | 6 +- 17 files changed, 119 insertions(+), 38 deletions(-) diff --git a/mlos_analyzer/README.md b/mlos_analyzer/README.md index 109cd886095..af8bf0e87df 100644 --- a/mlos_analyzer/README.md +++ b/mlos_analyzer/README.md @@ -3,12 +3,14 @@ A comprehensive library for analyzing and visualizing MLOS experiment results. ## Installation + ```bash pip install -r requirements.txt python setup.py install ``` ## Features + - FastAPI backend for experiment data retrieval - Correlation analysis - Failure metrics visualization diff --git a/mlos_analyzer/mlos_analyzer/__init__.py b/mlos_analyzer/mlos_analyzer/__init__.py index e69de29bb2d..8b9f1bdf790 100644 --- a/mlos_analyzer/mlos_analyzer/__init__.py +++ b/mlos_analyzer/mlos_analyzer/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# diff --git a/mlos_analyzer/mlos_analyzer/api/__init__.py b/mlos_analyzer/mlos_analyzer/api/__init__.py index e69de29bb2d..8b9f1bdf790 100644 --- a/mlos_analyzer/mlos_analyzer/api/__init__.py +++ b/mlos_analyzer/mlos_analyzer/api/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# diff --git a/mlos_analyzer/mlos_analyzer/api/endpoints.py b/mlos_analyzer/mlos_analyzer/api/endpoints.py index ebd4a393b1a..3a24c21a31b 100644 --- a/mlos_analyzer/mlos_analyzer/api/endpoints.py +++ b/mlos_analyzer/mlos_analyzer/api/endpoints.py @@ -1,8 +1,14 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +import logging + from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware -from .models import ExperimentExplanationRequest + from ..core.storage import storage -import logging +from .models import ExperimentExplanationRequest app = FastAPI() logging.basicConfig(level=logging.INFO) @@ -16,10 +22,12 @@ allow_headers=["*"], ) + @app.get("/experiments") def get_experiments(): return list(storage.experiments.keys()) + @app.get("/experiment_results/{experiment_id}") def get_experiment_results(experiment_id: str): try: diff --git a/mlos_analyzer/mlos_analyzer/api/models.py b/mlos_analyzer/mlos_analyzer/api/models.py index bea4426bc8a..03d836e5fd1 100644 --- a/mlos_analyzer/mlos_analyzer/api/models.py +++ b/mlos_analyzer/mlos_analyzer/api/models.py @@ -1,4 +1,9 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# from pydantic import BaseModel + class ExperimentExplanationRequest(BaseModel): experiment_id: str diff --git a/mlos_analyzer/mlos_analyzer/core/__init__.py b/mlos_analyzer/mlos_analyzer/core/__init__.py index e69de29bb2d..8b9f1bdf790 100644 --- a/mlos_analyzer/mlos_analyzer/core/__init__.py +++ b/mlos_analyzer/mlos_analyzer/core/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# diff --git a/mlos_analyzer/mlos_analyzer/core/storage.py b/mlos_analyzer/mlos_analyzer/core/storage.py index 5e36b2481bd..9533f464d37 100644 --- a/mlos_analyzer/mlos_analyzer/core/storage.py +++ b/mlos_analyzer/mlos_analyzer/core/storage.py @@ -1,3 +1,7 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# from mlos_bench.storage import from_config try: diff --git a/mlos_analyzer/mlos_analyzer/utils/__init__.py b/mlos_analyzer/mlos_analyzer/utils/__init__.py index e69de29bb2d..8b9f1bdf790 100644 --- a/mlos_analyzer/mlos_analyzer/utils/__init__.py +++ b/mlos_analyzer/mlos_analyzer/utils/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# diff --git a/mlos_analyzer/mlos_analyzer/visualization/__init__.py b/mlos_analyzer/mlos_analyzer/visualization/__init__.py index e69de29bb2d..8b9f1bdf790 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/__init__.py +++ b/mlos_analyzer/mlos_analyzer/visualization/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# diff --git a/mlos_analyzer/mlos_analyzer/visualization/correlation.py b/mlos_analyzer/mlos_analyzer/visualization/correlation.py index 0b184086809..f5864f96929 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/correlation.py +++ b/mlos_analyzer/mlos_analyzer/visualization/correlation.py @@ -1,5 +1,9 @@ -import plotly.express as px +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# import pandas as pd +import plotly.express as px def plot_heatmap(df: pd.DataFrame): diff --git a/mlos_analyzer/mlos_analyzer/visualization/distributions.py b/mlos_analyzer/mlos_analyzer/visualization/distributions.py index f5f813c8704..45b5bd7a138 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/distributions.py +++ b/mlos_analyzer/mlos_analyzer/visualization/distributions.py @@ -1,3 +1,8 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# + # src/mlos_analyzer/visualization/distributions.py import plotly.express as px import plotly.figure_factory as ff diff --git a/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py b/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py index 002c2b469ad..dcb67121ad2 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py +++ b/mlos_analyzer/mlos_analyzer/visualization/failure_metrics.py @@ -1,5 +1,9 @@ -import plotly.express as px +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# import pandas as pd +import plotly.express as px def plot_success_failure_distribution(df: pd.DataFrame): diff --git a/mlos_analyzer/mlos_analyzer/visualization/performance.py b/mlos_analyzer/mlos_analyzer/visualization/performance.py index 8a3e245b334..740a4559ebd 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/performance.py +++ b/mlos_analyzer/mlos_analyzer/visualization/performance.py @@ -1,10 +1,16 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# + +from typing import List + # src/mlos_analyzer/visualization/performance.py import plotly.express as px import plotly.graph_objects as go -from typing import List -def plot_parallel_coordinates(df, metrics: List[str]): +def plot_parallel_coordinates(df, metrics: list[str]): fig = px.parallel_coordinates( df, dimensions=[col for col in df.columns if col.startswith("config") or col in metrics], @@ -13,7 +19,7 @@ def plot_parallel_coordinates(df, metrics: List[str]): return fig -def plot_performance_radar(df, metrics: List[str], top_n: int = 5): +def plot_performance_radar(df, metrics: list[str], top_n: int = 5): # Normalize metrics normalized_df = df.copy() for metric in metrics: @@ -45,7 +51,7 @@ def plot_performance_radar(df, metrics: List[str], top_n: int = 5): import plotly.graph_objects as go -def plot_parallel_coordinates(df, metrics: List[str]): +def plot_parallel_coordinates(df, metrics: list[str]): fig = px.parallel_coordinates( df, dimensions=[col for col in df.columns if col.startswith("config") or col in metrics], @@ -54,7 +60,7 @@ def plot_parallel_coordinates(df, metrics: List[str]): return fig -def plot_performance_radar(df, metrics: List[str], top_n: int = 5): +def plot_performance_radar(df, metrics: list[str], top_n: int = 5): # Normalize metrics normalized_df = df.copy() for metric in metrics: diff --git a/mlos_analyzer/mlos_analyzer/visualization/plots.py b/mlos_analyzer/mlos_analyzer/visualization/plots.py index 93e349a7edb..6d964893bf0 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/plots.py +++ b/mlos_analyzer/mlos_analyzer/visualization/plots.py @@ -1,5 +1,9 @@ -import plotly.express as px +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# import pandas as pd +import plotly.express as px def plot_whisker_plots(df: pd.DataFrame, target_col: str, n: int = 5): diff --git a/mlos_analyzer/mlos_analyzer/visualization/statistical.py b/mlos_analyzer/mlos_analyzer/visualization/statistical.py index bc5abac4572..13b90c748a1 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/statistical.py +++ b/mlos_analyzer/mlos_analyzer/visualization/statistical.py @@ -1,34 +1,43 @@ -import plotly.graph_objects as go +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# import pandas as pd +import plotly.graph_objects as go from scipy import stats + def run_pairwise_stat_tests(df: pd.DataFrame, metric: str, alpha: float = 0.05): - configs = df["tunable_config_id"].unique() - results = [] + configs = df["tunable_config_id"].unique() + results = [] - for i in range(len(configs)): - for j in range(i+1, len(configs)): - group1 = df[df["tunable_config_id"] == configs[i]][metric] - group2 = df[df["tunable_config_id"] == configs[j]][metric] + for i in range(len(configs)): + for j in range(i + 1, len(configs)): + group1 = df[df["tunable_config_id"] == configs[i]][metric] + group2 = df[df["tunable_config_id"] == configs[j]][metric] - stat, pval = stats.ttest_ind(group1, group2) - results.append({ - "Config1": configs[i], - "Config2": configs[j], - "p-value": pval, - "Significant": pval < alpha - }) + stat, pval = stats.ttest_ind(group1, group2) + results.append( + { + "Config1": configs[i], + "Config2": configs[j], + "p-value": pval, + "Significant": pval < alpha, + } + ) + + return pd.DataFrame(results) - return pd.DataFrame(results) def compare_score_distributions(df: pd.DataFrame, metric: str, config1: str, config2: str): - group1 = df[df["tunable_config_id"] == config1][metric] - group2 = df[df["tunable_config_id"] == config2][metric] - - fig = go.Figure() - fig.add_trace(go.Histogram(x=group1, name=f"Config {config1}", opacity=0.7)) - fig.add_trace(go.Histogram(x=group2, name=f"Config {config2}", opacity=0.7)) - - fig.update_layout(barmode="overlay", - title=f"Score Distribution Comparison: Config {config1} vs {config2}") - return fig + group1 = df[df["tunable_config_id"] == config1][metric] + group2 = df[df["tunable_config_id"] == config2][metric] + + fig = go.Figure() + fig.add_trace(go.Histogram(x=group1, name=f"Config {config1}", opacity=0.7)) + fig.add_trace(go.Histogram(x=group2, name=f"Config {config2}", opacity=0.7)) + + fig.update_layout( + barmode="overlay", title=f"Score Distribution Comparison: Config {config1} vs {config2}" + ) + return fig diff --git a/mlos_analyzer/mlos_analyzer/visualization/timeseries.py b/mlos_analyzer/mlos_analyzer/visualization/timeseries.py index 4a9da3433cd..3ced9e0b1f1 100644 --- a/mlos_analyzer/mlos_analyzer/visualization/timeseries.py +++ b/mlos_analyzer/mlos_analyzer/visualization/timeseries.py @@ -1,10 +1,16 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# + +from typing import List + # src/mlos_analyzer/visualization/timeseries.py import plotly.express as px import plotly.graph_objects as go -from typing import List -def plot_metric_over_time(df, metric: str, configs: List[str] = None): +def plot_metric_over_time(df, metric: str, configs: list[str] = None): if configs: df = df[df["tunable_config_id"].isin(configs)] diff --git a/mlos_analyzer/setup.py b/mlos_analyzer/setup.py index 80e3f37dcbf..2b1165e2624 100644 --- a/mlos_analyzer/setup.py +++ b/mlos_analyzer/setup.py @@ -1,4 +1,8 @@ -from setuptools import setup, find_packages +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +from setuptools import find_packages, setup setup( name="mlos_analyzer", From c85cc37fd28a8a4849ccba7d3f2dd04118471e70 Mon Sep 17 00:00:00 2001 From: Yaseen Shady <139421618+yshady-acheev@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:27:02 -0800 Subject: [PATCH 3/9] Update storage.py --- mlos_analyzer/mlos_analyzer/core/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlos_analyzer/mlos_analyzer/core/storage.py b/mlos_analyzer/mlos_analyzer/core/storage.py index 9533f464d37..9c9dbd4d021 100644 --- a/mlos_analyzer/mlos_analyzer/core/storage.py +++ b/mlos_analyzer/mlos_analyzer/core/storage.py @@ -5,6 +5,6 @@ from mlos_bench.storage import from_config try: - storage = from_config(config="storage/sqlite.jsonc") + storage = from_config(config="storage/sqlite.jsonc") # PLACEHOLDER except Exception as e: raise Exception(f"Error loading storage configuration: {e}") From eb1a3db6cbd9eb51d87feac6c50598277a7552fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 22:27:20 +0000 Subject: [PATCH 4/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mlos_analyzer/mlos_analyzer/core/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlos_analyzer/mlos_analyzer/core/storage.py b/mlos_analyzer/mlos_analyzer/core/storage.py index 9c9dbd4d021..df2db7bdd7d 100644 --- a/mlos_analyzer/mlos_analyzer/core/storage.py +++ b/mlos_analyzer/mlos_analyzer/core/storage.py @@ -5,6 +5,6 @@ from mlos_bench.storage import from_config try: - storage = from_config(config="storage/sqlite.jsonc") # PLACEHOLDER + storage = from_config(config="storage/sqlite.jsonc") # PLACEHOLDER except Exception as e: raise Exception(f"Error loading storage configuration: {e}") From 41b271b795c188686aab5395b84ac3f52d91149d Mon Sep 17 00:00:00 2001 From: Yaseen Shady <139421618+yshady-acheev@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:32:42 -0800 Subject: [PATCH 5/9] Create example_usage.py --- mlos_analyzer/example_usage.py | 125 +++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 mlos_analyzer/example_usage.py diff --git a/mlos_analyzer/example_usage.py b/mlos_analyzer/example_usage.py new file mode 100644 index 00000000000..af18700130f --- /dev/null +++ b/mlos_analyzer/example_usage.py @@ -0,0 +1,125 @@ +# Run as "streamlit run example_usage.py" + +import streamlit as st +from mlos_analyzer.core.storage import storage +from mlos_analyzer.visualization.plots import plot_whisker_plots +from mlos_analyzer.visualization.correlation import plot_heatmap, plot_correlation_table_target +from mlos_analyzer.visualization.failure_metrics import ( + plot_success_failure_distribution, + plot_failure_rate_by_config, +) +from mlos_analyzer.visualization.statistical import ( + run_pairwise_stat_tests, + compare_score_distributions, +) +from mlos_analyzer.visualization.timeseries import plot_metric_over_time, plot_moving_average +from mlos_analyzer.visualization.distributions import plot_metric_distribution, plot_violin_comparison +from mlos_analyzer.visualization.performance import plot_parallel_coordinates, plot_performance_radar + + +def main(): + st.set_page_config(page_title="MLOS Analyzer Dashboard", layout="wide") + st.title("MLOS Experiment Analysis Dashboard") + + st.sidebar.header("Settings") + experiment_ids = list(storage.experiments.keys()) + selected_experiment = st.sidebar.selectbox("Select Experiment", experiment_ids) + + if selected_experiment: + df = storage.experiments[selected_experiment].results_df + metrics = [col for col in df.columns if col.startswith("result")] + + tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs( + ["Overview", "Performance", "Time Series", "Distributions", "Failures", "Statistics"] + ) + + with tab1: + st.header("Experiment Overview") + col1, col2 = st.columns(2) + with col1: + st.subheader("Dataset Info") + st.write(df.describe()) + with col2: + st.subheader("Configuration Distribution") + config_counts = df["tunable_config_id"].value_counts() + st.bar_chart(config_counts) + + with tab2: + st.header("Performance Analysis") + selected_metric = st.selectbox("Select Metric", metrics, key="perf_metric") + + col1, col2 = st.columns(2) + with col1: + fig_whisker = plot_whisker_plots(df, selected_metric) + st.plotly_chart(fig_whisker) + with col2: + fig_heatmap = plot_heatmap(df) + st.plotly_chart(fig_heatmap) + + selected_metrics = st.multiselect("Select Metrics for Advanced Analysis", metrics, default=metrics[:3]) + if selected_metrics: + col3, col4 = st.columns(2) + with col3: + fig = plot_parallel_coordinates(df, selected_metrics) + st.plotly_chart(fig) + with col4: + fig = plot_performance_radar(df, selected_metrics) + st.plotly_chart(fig) + + with tab3: + st.header("Time Series Analysis") + metric = st.selectbox("Select Metric", metrics, key="ts_metric") + window = st.slider("Moving Average Window", 2, 20, 5) + + col1, col2 = st.columns(2) + with col1: + fig = plot_metric_over_time(df, metric) + st.plotly_chart(fig) + with col2: + fig = plot_moving_average(df, metric, window) + st.plotly_chart(fig) + + with tab4: + st.header("Distribution Analysis") + metric = st.selectbox("Select Metric", metrics, key="dist_metric") + + col1, col2 = st.columns(2) + with col1: + fig = plot_metric_distribution(df, metric) + st.plotly_chart(fig) + with col2: + fig = plot_violin_comparison(df, metric) + st.plotly_chart(fig) + + with tab5: + st.header("Failure Analysis") + col1, col2 = st.columns(2) + with col1: + fig_dist = plot_success_failure_distribution(df) + st.plotly_chart(fig_dist) + with col2: + fig_rate = plot_failure_rate_by_config(df) + st.plotly_chart(fig_rate) + + with tab6: + st.header("Statistical Analysis") + test_metric = st.selectbox("Select Test Metric", metrics) + alpha = st.slider("Significance Level (α)", 0.01, 0.10, 0.05) + + results = run_pairwise_stat_tests(df, test_metric, alpha=alpha) + st.dataframe(results) + + st.subheader("Configuration Comparison") + config1, config2 = st.columns(2) + with config1: + cfg1 = st.selectbox("First Configuration", df["tunable_config_id"].unique()) + with config2: + cfg2 = st.selectbox("Second Configuration", df["tunable_config_id"].unique()) + + if cfg1 != cfg2: + fig_compare = compare_score_distributions(df, test_metric, cfg1, cfg2) + st.plotly_chart(fig_compare) + + +if __name__ == "__main__": + main() From 56f8692626dfbcb9605af66c2e9be5216cf929be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 04:33:10 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mlos_analyzer/example_usage.py | 41 +++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/mlos_analyzer/example_usage.py b/mlos_analyzer/example_usage.py index af18700130f..e0012c8d64c 100644 --- a/mlos_analyzer/example_usage.py +++ b/mlos_analyzer/example_usage.py @@ -1,20 +1,37 @@ +# +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# + # Run as "streamlit run example_usage.py" import streamlit as st from mlos_analyzer.core.storage import storage -from mlos_analyzer.visualization.plots import plot_whisker_plots -from mlos_analyzer.visualization.correlation import plot_heatmap, plot_correlation_table_target +from mlos_analyzer.visualization.correlation import ( + plot_correlation_table_target, + plot_heatmap, +) +from mlos_analyzer.visualization.distributions import ( + plot_metric_distribution, + plot_violin_comparison, +) from mlos_analyzer.visualization.failure_metrics import ( - plot_success_failure_distribution, plot_failure_rate_by_config, + plot_success_failure_distribution, +) +from mlos_analyzer.visualization.performance import ( + plot_parallel_coordinates, + plot_performance_radar, ) +from mlos_analyzer.visualization.plots import plot_whisker_plots from mlos_analyzer.visualization.statistical import ( - run_pairwise_stat_tests, compare_score_distributions, + run_pairwise_stat_tests, +) +from mlos_analyzer.visualization.timeseries import ( + plot_metric_over_time, + plot_moving_average, ) -from mlos_analyzer.visualization.timeseries import plot_metric_over_time, plot_moving_average -from mlos_analyzer.visualization.distributions import plot_metric_distribution, plot_violin_comparison -from mlos_analyzer.visualization.performance import plot_parallel_coordinates, plot_performance_radar def main(): @@ -47,7 +64,7 @@ def main(): with tab2: st.header("Performance Analysis") selected_metric = st.selectbox("Select Metric", metrics, key="perf_metric") - + col1, col2 = st.columns(2) with col1: fig_whisker = plot_whisker_plots(df, selected_metric) @@ -56,7 +73,9 @@ def main(): fig_heatmap = plot_heatmap(df) st.plotly_chart(fig_heatmap) - selected_metrics = st.multiselect("Select Metrics for Advanced Analysis", metrics, default=metrics[:3]) + selected_metrics = st.multiselect( + "Select Metrics for Advanced Analysis", metrics, default=metrics[:3] + ) if selected_metrics: col3, col4 = st.columns(2) with col3: @@ -70,7 +89,7 @@ def main(): st.header("Time Series Analysis") metric = st.selectbox("Select Metric", metrics, key="ts_metric") window = st.slider("Moving Average Window", 2, 20, 5) - + col1, col2 = st.columns(2) with col1: fig = plot_metric_over_time(df, metric) @@ -82,7 +101,7 @@ def main(): with tab4: st.header("Distribution Analysis") metric = st.selectbox("Select Metric", metrics, key="dist_metric") - + col1, col2 = st.columns(2) with col1: fig = plot_metric_distribution(df, metric) From 8e78ad081cf0653c8cf0704f81ee16cda8f12c59 Mon Sep 17 00:00:00 2001 From: Yaseen Shady <139421618+yshady-acheev@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:36:33 -0800 Subject: [PATCH 7/9] Update README.md --- mlos_analyzer/README.md | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/mlos_analyzer/README.md b/mlos_analyzer/README.md index af8bf0e87df..c87846df1e7 100644 --- a/mlos_analyzer/README.md +++ b/mlos_analyzer/README.md @@ -1,18 +1,36 @@ -# MLOS Analyzer +# MLOS Analyzer Dashboard -A comprehensive library for analyzing and visualizing MLOS experiment results. +This project provides a comprehensive dashboard components for analyzing experiments conducted using MLOS. The dashboard components enables users to visualize experiment results, analyze performance metrics, and conduct statistical analyses interactively. -## Installation +The dashboard components can also be used within a notebook, or streamlit, or any platform which supports plotly. + +## Features + +1. **Experiment Overview**: + - View dataset statistics and configuration distributions. + - Inspect the overall performance of your experiments. + +2. **Performance Analysis**: + - Visualize metrics with whisker plots and heatmaps. + - Perform advanced analysis using parallel coordinates and performance radar plots. + +3. **Time Series Analysis**: + - Analyze metrics over time. + - Apply moving average filters for better trend visualization. + +4. **Distribution Analysis**: + - View metric distributions with histogram and violin plots. +5. **Failure Analysis**: + - Visualize success/failure distributions. + - Analyze failure rates across different configurations. + +6. **Statistical Analysis**: + - Perform pairwise statistical tests for configuration comparison. + - Compare score distributions between different configurations. + +## Installation ```bash pip install -r requirements.txt python setup.py install ``` - -## Features - -- FastAPI backend for experiment data retrieval -- Correlation analysis -- Failure metrics visualization -- Statistical analysis -- Advanced plotting capabilities From e89c8c239f1e670d2c025b4e8ff4d7ebb7cbc0cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 04:36:51 +0000 Subject: [PATCH 8/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mlos_analyzer/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/mlos_analyzer/README.md b/mlos_analyzer/README.md index c87846df1e7..a253c3533d4 100644 --- a/mlos_analyzer/README.md +++ b/mlos_analyzer/README.md @@ -7,29 +7,36 @@ The dashboard components can also be used within a notebook, or streamlit, or an ## Features 1. **Experiment Overview**: + - View dataset statistics and configuration distributions. - Inspect the overall performance of your experiments. -2. **Performance Analysis**: +1. **Performance Analysis**: + - Visualize metrics with whisker plots and heatmaps. - Perform advanced analysis using parallel coordinates and performance radar plots. -3. **Time Series Analysis**: +1. **Time Series Analysis**: + - Analyze metrics over time. - Apply moving average filters for better trend visualization. -4. **Distribution Analysis**: +1. **Distribution Analysis**: + - View metric distributions with histogram and violin plots. -5. **Failure Analysis**: +1. **Failure Analysis**: + - Visualize success/failure distributions. - Analyze failure rates across different configurations. -6. **Statistical Analysis**: +1. **Statistical Analysis**: + - Perform pairwise statistical tests for configuration comparison. - Compare score distributions between different configurations. ## Installation + ```bash pip install -r requirements.txt python setup.py install From ba3b56c872f1e20244e9d189f24754c2809df5e2 Mon Sep 17 00:00:00 2001 From: Yaseen Shady <139421618+yshady-acheev@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:48:48 -0800 Subject: [PATCH 9/9] Update README.md --- mlos_analyzer/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlos_analyzer/README.md b/mlos_analyzer/README.md index a253c3533d4..47ecaffb0e5 100644 --- a/mlos_analyzer/README.md +++ b/mlos_analyzer/README.md @@ -4,6 +4,8 @@ This project provides a comprehensive dashboard components for analyzing experim The dashboard components can also be used within a notebook, or streamlit, or any platform which supports plotly. +Another use would be to automate the process of running statistical significance tests to analyze and identify meaningful differences between configuration sets. It enables users to streamline performance analysis by automatically detecting which configurations yield compelling performance improvements. + ## Features 1. **Experiment Overview**: