import os import json import datetime import numpy as np import matplotlib.pyplot as plt from tqdm import tqdm FONTSIZE = 30 MARKSIZE = 25 COLORS = [ u'#1f77b4', u'#ff7f0e', u'#2ca02c', u'#d62728', u'#9467bd', 'black', u'#8c564b', u'#e377c2', u'#7f7f7f', u'#bcbd22', u'#17becf' ] def logloader(file): log = [] with open(file) as f: file = f.readlines() for line in file: line = json.loads(s=line) log.append(line) return log def ecdf(model, data_list, algo, sample_client=None, key='test_acc'): from fedhpobench.benchmarks import TabularBenchmark # Draw ECDF from target data_list plt.figure(figsize=(10, 7.5)) plt.xticks(fontsize=FONTSIZE) plt.yticks(fontsize=FONTSIZE) plt.xlabel('Normalized regret', size=FONTSIZE) plt.ylabel('P(X <= x)', size=FONTSIZE) # Get target data (tabular only) for data in data_list: benchmark = TabularBenchmark(model, data, algo, device=-1) target = [0] # Init with zero for idx in tqdm(range(len(benchmark.table))): row = benchmark.table.iloc[idx] if sample_client is not None and row[ 'sample_client'] != sample_client: continue result = eval(row['result']) val_loss = result['val_avg_loss'] try: best_round = np.argmin(val_loss) except: continue target.append(result[key][best_round]) norm_regret = np.sort(1 - (np.array(target) / np.max(target))) y = np.arange(len(norm_regret)) / float(len(norm_regret) - 1) plt.plot(norm_regret, y) if data_list[0].endswith('datasets'): legend = ['CoLA', 'SST2'] elif data_list[0].endswith('openml'): legend = [x.split('@')[0] for x in data_list] elif data_list[0].lower == 'cora': legend = ['Cora', 'CiteSeer', 'PubMed'] else: legend = [x.upper() for x in data_list] # Not show legend del legend os.makedirs('figures', exist_ok=True) plt.savefig(f'figures/{model}_{sample_client}_{algo}_cdf.pdf', bbox_inches='tight') plt.close() return target def get_mean_rank(traj_dict): from scipy.stats import rankdata xs = np.logspace(-4, 0, 500) # Convert loss to rank for repeat in traj_dict: logs = traj_dict[repeat] ys = [] for data in logs: tol_time = data[-1]['Consumed'] frac_budget = np.array([i['Consumed'] / tol_time for i in data]) loss = np.array([i['best_value'] for i in data]) ys.append([ loss[np.where(x >= frac_budget)[0][-1]] if len(np.where(x >= frac_budget)[0]) != 0 else np.inf for x in xs ]) if repeat == 0: rank = rankdata(ys, axis=0) rank_bbo = rankdata(ys[:6], axis=0) rank_mf = rankdata(ys[-5:], axis=0) else: rank += rankdata(ys, axis=0) rank_bbo += rankdata(ys[:6], axis=0) rank_mf += rankdata(ys[-5:], axis=0) xs = np.linspace(0, 1, 500) return xs, rank / len(traj_dict), rank_bbo / len(traj_dict), rank_mf / len( traj_dict) def get_mean_loss(traj_dict): xs = np.logspace(-4, 0, 500) for repeat in traj_dict: logs = traj_dict[repeat] ys = [] for data in logs: tol_time = data[-1]['Consumed'] frac_budget = np.array([i['Consumed'] / tol_time for i in data]) loss = np.array([i['best_value'] for i in data]) ys.append([ loss[np.where(x >= frac_budget)[0][-1]] if len(np.where(x >= frac_budget)[0]) != 0 else np.inf for x in xs ]) if repeat == 0: rank = np.array(ys) rank_bbo = np.array(ys[:6]) rank_mf = np.array(ys[-5:]) else: rank += np.array(ys) rank_bbo += np.array(ys[:6]) rank_mf += np.array(ys[-5:]) xs = np.linspace(0, 1, 500) print( str([round(x, 4) for x in (rank / len(traj_dict))[:, -1].tolist() ]).replace(',', ' &')) return xs, rank / len(traj_dict), rank_bbo / len(traj_dict), rank_mf / len( traj_dict) def draw_rank(mean_ranks, mean_ranks_bbo, mean_ranks_mf, xs, opt_all, dataset, family, suffix, Y_label): os.makedirs('figures', exist_ok=True) # BBO + MF plt.figure(figsize=(10, 7.5)) for idx, rank in enumerate(mean_ranks): plt.plot(xs, rank, linewidth=1, color=COLORS[idx], markersize=MARKSIZE) plt.xticks(np.linspace(0, 1, 5), labels=['1e-4', '1e-3', '1e-2', '1e-1', '1'], fontsize=FONTSIZE) if Y_label == 'Mean_rank': plt.yticks(np.linspace(1, 10, 10), ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], fontsize=FONTSIZE) plt.ylabel('Mean rank', size=FONTSIZE) print(dataset, mean_ranks[:, -1].tolist()) else: plt.yticks(fontsize=FONTSIZE) plt.xlabel('Fraction of budget', size=FONTSIZE) plt.savefig( f"figures/{dataset.replace('@', '_')}_{family}_" f"{suffix}_over_time_all_{Y_label}.pdf", bbox_inches='tight') plt.close() # BBO plt.figure(figsize=(10, 7.5)) for idx, rank in enumerate(mean_ranks_bbo): plt.plot(xs, rank, linewidth=1, color=COLORS[idx], markersize=MARKSIZE) plt.xticks(np.linspace(0, 1, 5), labels=['1e-4', '1e-3', '1e-2', '1e-1', '1'], fontsize=FONTSIZE) if Y_label == 'Mean_rank': plt.yticks(np.linspace(1, 5, 5), ['1', '2', '3', '4', '5'], fontsize=FONTSIZE) plt.ylabel('Mean rank', size=FONTSIZE) else: plt.yticks(fontsize=FONTSIZE) plt.ylabel(Y_label, size=FONTSIZE) plt.xlabel('Fraction of budget', size=FONTSIZE) plt.savefig( f"figures/{dataset.replace('@', '_')}_{family}_" f"{suffix}_over_time_bbo_{Y_label}.pdf", bbox_inches='tight') plt.close() # MF plt.figure(figsize=(10, 7.5)) for idx, rank in enumerate(mean_ranks_mf): plt.plot(xs, rank, linewidth=1, color=COLORS[idx + 6], markersize=MARKSIZE) plt.xticks(np.linspace(0, 1, 5), labels=['1e-4', '1e-3', '1e-2', '1e-1', '1'], fontsize=FONTSIZE) if Y_label == 'Mean_rank': plt.yticks(np.linspace(1, 5, 5), ['1', '2', '3', '4', '5'], fontsize=FONTSIZE) plt.ylabel('Mean rank', size=FONTSIZE) else: plt.yticks(fontsize=FONTSIZE) plt.ylabel(Y_label, size=FONTSIZE) plt.xlabel('Fraction of budget', size=FONTSIZE) plt.savefig( f"figures/{dataset.replace('@', '_')}_{family}_" f"{suffix}_over_time_mf_{Y_label}.pdf", bbox_inches='tight') plt.close() def rank_over_time(root, family='all', mode='tabular', algo='avg', repeat=5, loss=False): suffix = f'{mode}_{algo}' if family == 'cnn': data_list = ['femnist'] elif family == 'gcn': data_list = ['cora', 'citeseer', 'pubmed'] elif family == 'bert': data_list = ['sst2@huggingface_datasets', 'cola@huggingface_datasets'] elif family in ['mlp', 'lr']: data_list = [ '10101@openml', '53@openml', '146818@openml', '146821@openml', '146822@openml', '31@openml', '3917@openml' ] else: # All dataset data_list = [ 'femnist', 'cora', 'citeseer', 'pubmed', 'sst2', 'cola', 'mlp', 'lr', '10101@openml', '53@openml', '146818@openml', '146821@openml', '9952@openml', '146822@openml', '31@openml', '3917@openml' ] # Please place these logs to one dir bbo = ['RS', 'BO_GP', 'BO_RF', 'BO_KDE', 'DE', 'grid_search'] mf = ['HB', 'BOHB', 'DEHB', 'TPE_MD', 'TPE_HB'] opt_all = bbo + mf # Load trajectories traj = {} for dataset in data_list: traj[dataset] = {} path = os.path.join(root, f'{dataset}_{family}_{suffix}') files = os.listdir(path) files = [file for file in files if file.endswith('.txt')] for i in range(repeat): files_i = [file for file in files if f'repeat{i}' in file] traj[dataset][i] = [] for opt in opt_all: for file in files_i: if file.startswith(f'{opt.lower()}_'): traj[dataset][i].append( logloader(os.path.join(path, file))) # Draw over dataset family_rank = [] family_rank_bbo = [] family_rank_mf = [] for dataset in traj: if loss: print(dataset) xs, mean_ranks, mean_ranks_bbo, mean_ranks_mf = get_mean_loss( traj[dataset]) Y_label = 'Loss' else: xs, mean_ranks, mean_ranks_bbo, mean_ranks_mf = get_mean_rank( traj[dataset]) Y_label = 'Mean_rank' if len(family_rank): family_rank += mean_ranks family_rank_bbo += mean_ranks_bbo family_rank_mf += mean_ranks_mf else: family_rank, family_rank_bbo, family_rank_mf = mean_ranks, \ mean_ranks_bbo, \ mean_ranks_mf draw_rank(mean_ranks, mean_ranks_bbo, mean_ranks_mf, xs, opt_all, dataset, family, suffix, Y_label) # Draw entire family draw_rank(family_rank / len(traj), family_rank_bbo / len(traj), family_rank_mf / len(traj), xs, opt_all, 'entire', family, suffix, Y_label) def landscape(model='cnn', dname='femnist', algo='avg', sample_client=None, key='test_acc'): import plotly.graph_objects as go from fedhpobench.config import fhb_cfg from fedhpobench.benchmarks import TabularBenchmark z = [] benchmark = TabularBenchmark(model, dname, algo, device=-1) def get_best_config(benchmark): results, config = [], [] for idx in tqdm(range(len(benchmark.table))): row = benchmark.table.iloc[idx] if sample_client is not None and row[ 'sample_client'] != sample_client: continue result = eval(row['result']) val_loss = result['val_avg_loss'] try: best_round = np.argmin(val_loss) except: continue results.append(result[key][best_round]) config.append(row) best_index = np.argmax(results) return config[best_index], results[best_index] # config, _ = get_best_config(benchmark) config = {'wd': 0.0, 'dropout': 0.5, 'step': 1.0} config_space = benchmark.get_configuration_space() X, Y = sorted(list(config_space['batch'])), sorted(list( config_space['lr'])) print(X, Y) for lr in Y: y = [] for batch in X: xy = {'lr': lr, 'batch': batch} print({**config, **xy}) res = benchmark({ **config, **xy }, { 'sample_client': 1.0, 'round': 249 }, fhb_cfg=fhb_cfg, seed=12345) y.append(res['function_value']) z.append(y) Z = np.array(z) fig = go.Figure(data=[go.Surface(z=Z, x=X, y=Y)]) fig.update_layout(title='FEMNIST (FedAvg)', autosize=False, width=900, height=900, margin=dict(l=65, r=50, b=65, t=90), scene=dict( xaxis_title='BS', yaxis_title='LR', zaxis_title='ACC', )) fig.write_image(os.path.join('figures', 'femnist_fedavg_landscape.pdf')) return if __name__ == '__main__': ecdf('gcn', ['cora', 'citeseer', 'pubmed'], sample_client=1.0)