diff --git a/.gitignore b/.gitignore index cb18546..b7e4df3 100644 --- a/.gitignore +++ b/.gitignore @@ -150,4 +150,10 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ +dataset/agg_metablockse_jbhi.csv +dataset/arquivo_de_teste_pad-25.csv +dataset/agg_metablockse_jbhi_pad-25.csv +a_topsis.png +images/* +dataset/* diff --git a/README.md b/README.md index ffb9777..1259083 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ inside the folder :test_tube: All methods are documented using the docstring format. However, to understand how the algorithms work, you must refer to their paper linked in the [references section](#references). +# Running an automatic A-TOPSIS test + +Now, you can export your benchmarck's results from `deep-hub-pipelines` + +Add your aggregated results inside the folder 'dataset', change the configuration of the a-topsis in the script 'scripts/atopsis_from_file.py' and then run the command: `python3 scripts/atopsis_from_file.py` + ## References All implementations follow the standard approach described in the paper. However, for TODIM algorithm, we included the diff --git a/scripts/atopsis_from_file.py b/scripts/atopsis_from_file.py new file mode 100644 index 0000000..edf8731 --- /dev/null +++ b/scripts/atopsis_from_file.py @@ -0,0 +1,79 @@ +import os +import pandas as pd +import numpy as np +import sys +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) +from decision_making import ATOPSIS + +def load_dataset(file_folder_path): + try: + dataset = pd.read_csv(file_folder_path, sep=",") + return dataset + except Exception as e: + print(f"Erro ao carregar os dados! Error:{e}\n") + return None + + +if __name__ == "__main__": + file_folder_path = "./dataset/agg_metablockse_jbhi_pad-25.csv" + dataset = load_dataset(file_folder_path) + + if dataset is None: + sys.exit("Dataset could not be loaded. Exiting...") + + # Group the dataset by the 'comb_method' + grouped_data = dataset.groupby('comb_method') + + # Initialize an empty list to store the ordered groups + ordered_dataset = [] + + # Iterate through each group (i.e., each unique comb_method) + for comb_method, group in grouped_data: + ordered_dataset.append(group) # Append each group to the list + + # Concatenate all the groups into a single ordered DataFrame + ordered_dataset = pd.concat(ordered_dataset) + + # ordered_dataset = ordered_dataset[ordered_dataset["metric"]] + print(f"Dataset reordenado:\n{ordered_dataset}\n") + + # Filtrar os dados do + # Get the unique 'alg_names' for the ordered dataset + alg_names = ordered_dataset['comb_method'].unique() + + # Prepare the matrices for A-TOPSIS + avg_mat = np.array(ordered_dataset['AVG']).reshape(len(alg_names), -1) + std_mat = np.array(ordered_dataset['STD']).reshape(len(alg_names), -1) + + # Weights for decision-making + weights = [0.5, 0.5] + + try: + atop = ATOPSIS(avg_mat, std_mat, avg_cost_ben="benefit", std_cost_ben="cost", weights=weights, normalize=False) + atop.get_ranking(True) + # atop.plot_ranking(alg_names=alg_names) + except Exception as e: + print(f"An error occurred during A-TOPSIS processing: {e}") + + # 2) Extrai scores do atributo final_ranking + scores = atop.final_ranking + + # 3) Monta o DataFrame de resultado + result = pd.DataFrame({ + "attention_mecanism": alg_names, + "atopsis_score": scores + }) + + # 4) Calcula o rank (maior score = melhor posição) + result["rank"] = ( + result["atopsis_score"] + .rank(ascending=True, method="min") + .astype(int) + ) + + print("\n=== Ranking Final ===") + print(result.sort_values("rank")) + + # 6) Plota com os próprios nomes + atop.plot_ranking(save_path="./images/a_topsis_PAD_20_extended.png", alg_names=alg_names, show=True, font_size=25, title="", y_axis_title="Scores", x_axis_title="", ascending=True, fig_size=(21, 14)) + diff --git a/scripts/gated-cross-attention-models-atopsis-test.py b/scripts/gated-cross-attention-models-atopsis-test.py new file mode 100644 index 0000000..eeab82b --- /dev/null +++ b/scripts/gated-cross-attention-models-atopsis-test.py @@ -0,0 +1,88 @@ +import os +import sys +import pandas as pd +import numpy as np + +# 1) Ajuste do path para importar o ATOPSIS +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) +from decision_making import ATOPSIS + + +def main(file_path): + # 2) Carrega o CSV e limpa nomes de coluna + df = pd.read_csv(file_path, sep=",") + df.columns = df.columns.str.strip() + print("Column names:", df.columns.tolist()) + + # 3) Métricas consideradas + metrics = ["accuracy", "balanced_accuracy", "auc"] + + + # wanted_model_name = "densenet169" + + # Obter as informações referentes ao modelo desejado + # df=df[df['model_name'] == wanted_model_name] + # df = df[df['attention_mecanism']==["no-metadata", "att-intramodal+residual+cross-attention-metadados"]] + print(df) + # 4) Extrai média e desvio padrão + for m in metrics: + df[[f"{m}_mean", f"{m}_std"]] = ( + df[m] + .str.extract(r"([\d\.]+)\s*±\s*([\d\.]+)") + .astype(float) + ) + + # 5) Agrupa por attention_mecanism e computa média de cada grupo + grouped = df.groupby("attention_mecanism").agg( + **{f"{m}_mean": (f"{m}_mean", "mean") for m in metrics}, + **{f"{m}_std": (f"{m}_std", "mean") for m in metrics}, + ).reset_index() + + # 6) Prepara as matrizes em memória + avg_mat = grouped[[f"{m}_mean" for m in metrics]].values.tolist() + std_mat = grouped[[f"{m}_std" for m in metrics]].values.tolist() + alg_names = grouped["attention_mecanism"].tolist() + # alg_names = grouped["model_name"].tolist() + # 7) Executa A‑TOPSIS (listas de listas) + # avg_cost_ben="benefit" + # std_cost_ben="cost" + weights_presetted = [0.7, 0.3] + + # Executa A‑TOPSIS em memória (listas de listas) + atop = ATOPSIS( + avg_mat, + std_mat, + avg_cost_ben="benefit", + std_cost_ben="cost", + weights=weights_presetted, + normalize=True + ) + # 1) Gera o ranking (printa se verbose=True) + atop.get_ranking(verbose=True) + + # 2) Extrai scores do atributo final_ranking + scores = atop.final_ranking + + # 3) Monta o DataFrame de resultado + result = pd.DataFrame({ + "attention_mecanism": alg_names, + "atopsis_score": scores + }) + + # 4) Calcula o rank (maior score = melhor posição) + result["rank"] = ( + result["atopsis_score"] + .rank(ascending=False, method="min") + .astype(int) + ) + + print("\n=== Ranking Final ===") + print(result.sort_values("rank")) + + # 6) Plota com os próprios nomes + atop.plot_ranking(save_path="./images/a_topsis_PAD_UFES_20.png", alg_names=alg_names, show=True, font_size=16, title="", y_axis_title="Scores", x_axis_title="Methods", ascending=True) + +if __name__=="__main__": + file_path = "dataset/agg_residual-block-pad-ufes-20.csv" + # Função principal + main(file_path=file_path) \ No newline at end of file diff --git a/src/decision_making/a_topsis.py b/src/decision_making/a_topsis.py index 1a4871b..04f51e3 100644 --- a/src/decision_making/a_topsis.py +++ b/src/decision_making/a_topsis.py @@ -104,7 +104,7 @@ def get_ranking(self, verbose=True): self.final_topsis.get_closeness_coefficient(verbose) self.final_ranking = self.final_topsis.clos_coefficient - def plot_ranking(self, alg_names=None, save_path=None, show=True): + def plot_ranking(self, alg_names=None, save_path=None, show=True, font_size=16, title="A-TOPSIS test", y_axis_title="Scores", x_axis_title="Methods", ascending=False, fig_size=(6,4)): """ This method plots the ranking, according to the final_ranking, in a bar plot. @@ -125,7 +125,7 @@ def plot_ranking(self, alg_names=None, save_path=None, show=True): """ if alg_names is None: alg_names = self.final_topsis.alternatives - self.final_topsis.plot_ranking(alg_names, save_path, show) + self.final_topsis.plot_ranking(alg_names, save_path, show, font_size, title, y_axis_title, x_axis_title, ascending=ascending, fig_size=fig_size) diff --git a/src/decision_making/topsis.py b/src/decision_making/topsis.py index 366171a..49173aa 100644 --- a/src/decision_making/topsis.py +++ b/src/decision_making/topsis.py @@ -214,43 +214,74 @@ def get_closeness_coefficient(self, verbose=False, compute_distance_ideal=True): if verbose: print (self.clos_coefficient) - def plot_ranking(self, alt_names=None, save_path=None, show=True): + def plot_ranking(self, alt_names=None, save_path=None, show=True, font_size=16, + title="A-TOPSIS Test", y_axis_title="Scores", x_axis_title="Methods", + ascending=True, fig_size=(6, 4)): """ - This method plots the ranking, according to the closeness coefficient, in a bar plot. + Plots a bar chart representing the ranking based on the closeness coefficient. - Parameters: - ----------- - alt_names: (list), optional - This is a list of names for each alternative within the decision matrix. If you're using a DataFrame, you have - already defined when you set the alt_col_name. So, you may let it as None. However, if you're using a matrix - list or a numpy array, you may pass the alternatives name here. If you let it as None, there will be a default - alternatives name in the plot (ex: A1, A2, etc). Default is None - - save_path: (str), optional - It's the full path (including the figure name and extension) to save the plot. If you let it None, the plot - won't be saved. Default is None. - - show: (boolean), optional - Set is as True if you want to show the plot on the screen. Default is False. + Parameters + ---------- + alt_names : list, optional + Names for each alternative. If None and using a DataFrame, the names from `self.alternatives` are used. + Otherwise, defaults to ["A1", "A2", ...]. + + save_path : str, optional + Full path (including filename and extension) to save the plot. If None, the plot is not saved. + + show : bool, optional + If True, displays the plot. + + font_size : int, optional + Base font size for labels and ticks. + + title : str, optional + Title of the plot. + + y_axis_title : str, optional + Label for the Y-axis. + + x_axis_title : str, optional + Label for the X-axis. + + ascending : bool, optional + If True, sorts the alternatives by score in ascending order. Default is False (descending). """ sns.set_style("whitegrid") - if self.alternatives is not None: - alt_names = self.alternatives - if alt_names is not None: - a = sns.barplot(alt_names, self.clos_coefficient, palette="BuGn_d") - else: - temp = [f"A{n}" for n in range(1, len(self.clos_coefficient)+1, 1)] - a = sns.barplot(temp, self.clos_coefficient, palette="BuGn_d") - a.set_ylabel("Closeness Coefficient") - a.set_xlabel('Alternatives') - fig = a.get_figure() + # Determine alternative names + if alt_names is None: + if hasattr(self, 'alternatives') and self.alternatives is not None: + alt_names = self.alternatives + else: + alt_names = [f"A{i+1}" for i in range(len(self.clos_coefficient))] + + # Sort based on closeness coefficient + sorted_data = sorted(zip(alt_names, self.clos_coefficient), key=lambda x: x[1], reverse=not ascending) + sorted_names, sorted_scores = zip(*sorted_data) + + # Criar figura com tamanho específico + fig, ax = plt.subplots(figsize=fig_size) + + # Plot + sns.barplot(x=list(sorted_names), y=list(sorted_scores), palette="Blues", ax=ax) + ax.set_title(title, fontsize=font_size) + ax.set_ylabel(y_axis_title, fontsize=font_size) + ax.set_xlabel(x_axis_title, fontsize=font_size) + ax.tick_params(labelsize=font_size) + # plt.xticks(rotation=30, ha='right') + + if show: plt.show() + # Save or show + if save_path: + fig.savefig(save_path, dpi=400, bbox_inches='tight') + else: + plt.close(fig) - if save_path is not None: - fig.savefig(save_path) + return fig ######################################################################################################################## # Static methods