Spaces:
Runtime error
Runtime error
| import torch; torch.manual_seed(0) | |
| import torch.utils | |
| from torch.utils.data import DataLoader | |
| import torch.distributions | |
| import torch.nn as nn | |
| import matplotlib.pyplot as plt; plt.rcParams['figure.dpi'] = 200 | |
| from src.cocktails.representation_learning.dataset import MyDataset, get_representation_from_ingredient, get_max_n_ingredients | |
| import json | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| from src.cocktails.representation_learning.simple_model import SimpleNet | |
| from src.cocktails.config import COCKTAILS_CSV_DATA, FULL_COCKTAIL_REP_PATH, EXPERIMENT_PATH | |
| from src.cocktails.utilities.cocktail_utilities import get_bunch_of_rep_keys | |
| from src.cocktails.utilities.ingredients_utilities import ingredient_profiles | |
| from resource import getrusage | |
| from resource import RUSAGE_SELF | |
| import gc | |
| gc.collect(2) | |
| device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| def get_params(): | |
| data = pd.read_csv(COCKTAILS_CSV_DATA) | |
| max_ingredients, ingredient_set, liquor_set, liqueur_set = get_max_n_ingredients(data) | |
| num_ingredients = len(ingredient_set) | |
| rep_keys = get_bunch_of_rep_keys()['custom'] | |
| ing_keys = [k.split(' ')[1] for k in rep_keys] | |
| ing_keys.remove('volume') | |
| nb_ing_categories = len(set(ingredient_profiles['type'])) | |
| category_encodings = dict(zip(sorted(set(ingredient_profiles['type'])), np.eye(nb_ing_categories))) | |
| params = dict(trial_id='test', | |
| save_path=EXPERIMENT_PATH + "/simple_net/", | |
| nb_epochs=100, | |
| print_every=50, | |
| plot_every=50, | |
| batch_size=128, | |
| lr=0.001, | |
| dropout=0.15, | |
| output_keyword='glasses', | |
| ing_keys=ing_keys, | |
| nb_ingredients=len(ingredient_set), | |
| hidden_dims=[16], | |
| activation='sigmoid', | |
| auxiliaries_dict=dict(categories=dict(weight=0, type='classif', final_activ=None, dim_output=len(set(data['subcategory']))), | |
| glasses=dict(weight=0, type='classif', final_activ=None, dim_output=len(set(data['glass']))), | |
| prep_type=dict(weight=0, type='classif', final_activ=None, dim_output=len(set(data['category']))), | |
| cocktail_reps=dict(weight=0, type='regression', final_activ=None, dim_output=13), | |
| volume=dict(weight=0, type='regression', final_activ='relu', dim_output=1), | |
| taste_reps=dict(weight=0, type='regression', final_activ='relu', dim_output=2), | |
| ingredients_presence=dict(weight=0, type='multiclassif', final_activ=None, dim_output=num_ingredients), | |
| ingredients_quantities=dict(weight=0, type='regression', final_activ=None, dim_output=num_ingredients)), | |
| category_encodings=category_encodings | |
| ) | |
| params['output_dim'] = params['auxiliaries_dict'][params['output_keyword']]['dim_output'] | |
| water_rep, indexes_to_normalize = get_representation_from_ingredient(ingredients=['water'], quantities=[1], | |
| max_q_per_ing=dict(zip(ingredient_set, [1] * num_ingredients)), index=0, | |
| params=params) | |
| dim_rep_ingredient = water_rep.size | |
| params['indexes_ing_to_normalize'] = indexes_to_normalize | |
| params['deepset_latent_dim'] = dim_rep_ingredient * max_ingredients | |
| params['dim_rep_ingredient'] = dim_rep_ingredient | |
| params['input_dim'] = params['nb_ingredients'] | |
| params = compute_expe_name_and_save_path(params) | |
| del params['category_encodings'] # to dump | |
| with open(params['save_path'] + 'params.json', 'w') as f: | |
| json.dump(params, f) | |
| params = complete_params(params) | |
| return params | |
| def complete_params(params): | |
| data = pd.read_csv(COCKTAILS_CSV_DATA) | |
| cocktail_reps = np.loadtxt(FULL_COCKTAIL_REP_PATH) | |
| nb_ing_categories = len(set(ingredient_profiles['type'])) | |
| category_encodings = dict(zip(sorted(set(ingredient_profiles['type'])), np.eye(nb_ing_categories))) | |
| params['cocktail_reps'] = cocktail_reps | |
| params['raw_data'] = data | |
| params['category_encodings'] = category_encodings | |
| return params | |
| def compute_confusion_matrix_and_accuracy(predictions, ground_truth): | |
| bs, n_options = predictions.shape | |
| predicted = predictions.argmax(dim=1).detach().numpy() | |
| true = ground_truth.int().detach().numpy() | |
| confusion_matrix = np.zeros([n_options, n_options]) | |
| for i in range(bs): | |
| confusion_matrix[true[i], predicted[i]] += 1 | |
| acc = confusion_matrix.diagonal().sum() / bs | |
| for i in range(n_options): | |
| if confusion_matrix[i].sum() != 0: | |
| confusion_matrix[i] /= confusion_matrix[i].sum() | |
| acc2 = np.mean(predicted == true) | |
| assert (acc - acc2) < 1e-5 | |
| return confusion_matrix, acc | |
| def run_epoch(opt, train, model, data, loss_function, params): | |
| if train: | |
| model.train() | |
| else: | |
| model.eval() | |
| # prepare logging of losses | |
| losses = [] | |
| accuracies = [] | |
| cf_matrices = [] | |
| if train: opt.zero_grad() | |
| for d in data: | |
| nb_ingredients = d[0] | |
| batch_size = nb_ingredients.shape[0] | |
| x_ingredients = d[1].float() | |
| ingredient_quantities = d[2].float() | |
| cocktail_reps = d[3].float() | |
| auxiliaries = d[4] | |
| for k in auxiliaries.keys(): | |
| if auxiliaries[k].dtype == torch.float64: auxiliaries[k] = auxiliaries[k].float() | |
| taste_valid = d[-1] | |
| predictions = model(ingredient_quantities) | |
| loss = loss_function(predictions, auxiliaries[params['output_keyword']].long()).float() | |
| cf_matrix, accuracy = compute_confusion_matrix_and_accuracy(predictions, auxiliaries[params['output_keyword']]) | |
| if train: | |
| loss.backward() | |
| opt.step() | |
| opt.zero_grad() | |
| losses.append(float(loss)) | |
| cf_matrices.append(cf_matrix) | |
| accuracies.append(accuracy) | |
| return model, np.mean(losses), np.mean(accuracies), np.mean(cf_matrices, axis=0) | |
| def prepare_data_and_loss(params): | |
| train_data = MyDataset(split='train', params=params) | |
| test_data = MyDataset(split='test', params=params) | |
| train_data_loader = DataLoader(train_data, batch_size=params['batch_size'], shuffle=True) | |
| test_data_loader = DataLoader(test_data, batch_size=params['batch_size'], shuffle=True) | |
| if params['auxiliaries_dict'][params['output_keyword']]['type'] == 'classif': | |
| if params['output_keyword'] == 'glasses': | |
| classif_weights = train_data.glasses_weights | |
| elif params['output_keyword'] == 'prep_type': | |
| classif_weights = train_data.prep_types_weights | |
| elif params['output_keyword'] == 'categories': | |
| classif_weights = train_data.categories_weights | |
| else: | |
| raise ValueError | |
| # classif_weights = (np.array(classif_weights) * 2 + np.ones(len(classif_weights))) / 3 | |
| loss_function = nn.CrossEntropyLoss(torch.FloatTensor(classif_weights)) | |
| # loss_function = nn.CrossEntropyLoss() | |
| elif params['auxiliaries_dict'][params['output_keyword']]['type'] == 'multiclassif': | |
| loss_function = nn.BCEWithLogitsLoss() | |
| elif params['auxiliaries_dict'][params['output_keyword']]['type'] == 'regression': | |
| loss_function = nn.MSELoss() | |
| else: | |
| raise ValueError | |
| return loss_function, train_data_loader, test_data_loader | |
| def print_losses(train, loss, accuracy): | |
| keyword = 'Train' if train else 'Eval' | |
| print(f'\t{keyword} logs:') | |
| print(f'\t\t Loss: {loss:.2f}, Acc: {accuracy:.2f}') | |
| def run_experiment(params, verbose=True): | |
| loss_function, train_data_loader, test_data_loader = prepare_data_and_loss(params) | |
| model = SimpleNet(params['input_dim'], params['hidden_dims'], params['output_dim'], params['activation'], params['dropout']) | |
| opt = torch.optim.AdamW(model.parameters(), lr=params['lr']) | |
| all_train_losses = [] | |
| all_eval_losses = [] | |
| all_eval_cf_matrices = [] | |
| all_train_accuracies = [] | |
| all_eval_accuracies = [] | |
| all_train_cf_matrices = [] | |
| best_loss = np.inf | |
| model, eval_loss, eval_accuracy, eval_cf_matrix = run_epoch(opt=opt, train=False, model=model, data=test_data_loader, loss_function=loss_function, params=params) | |
| all_eval_losses.append(eval_loss) | |
| all_eval_accuracies.append(eval_accuracy) | |
| if verbose: print(f'\n--------\nEpoch #0') | |
| if verbose: print_losses(train=False, accuracy=eval_accuracy, loss=eval_loss) | |
| for epoch in range(params['nb_epochs']): | |
| if verbose and (epoch + 1) % params['print_every'] == 0: print(f'\n--------\nEpoch #{epoch+1}') | |
| model, train_loss, train_accuracy, train_cf_matrix = run_epoch(opt=opt, train=True, model=model, data=train_data_loader, loss_function=loss_function, params=params) | |
| if verbose and (epoch + 1) % params['print_every'] == 0: print_losses(train=True, accuracy=train_accuracy, loss=train_loss) | |
| model, eval_loss, eval_accuracy, eval_cf_matrix = run_epoch(opt=opt, train=False, model=model, data=test_data_loader, loss_function=loss_function, params=params) | |
| if verbose and (epoch + 1) % params['print_every'] == 0: print_losses(train=False, accuracy=eval_accuracy, loss=eval_loss) | |
| if eval_loss < best_loss: | |
| best_loss = eval_loss | |
| if verbose: print(f'Saving new best model with loss {best_loss:.2f}') | |
| torch.save(model.state_dict(), params['save_path'] + f'checkpoint_best.save') | |
| # log | |
| all_train_losses.append(train_loss) | |
| all_train_accuracies.append(train_accuracy) | |
| all_eval_losses.append(eval_loss) | |
| all_eval_accuracies.append(eval_accuracy) | |
| all_eval_cf_matrices.append(eval_cf_matrix) | |
| all_train_cf_matrices.append(train_cf_matrix) | |
| if (epoch + 1) % params['plot_every'] == 0: | |
| plot_results(all_train_losses, all_train_accuracies, all_train_cf_matrices, | |
| all_eval_losses, all_eval_accuracies, all_eval_cf_matrices, params['plot_path']) | |
| return model | |
| def plot_results(all_train_losses, all_train_accuracies, all_train_cf_matrices, | |
| all_eval_losses, all_eval_accuracies, all_eval_cf_matrices, plot_path): | |
| steps = np.arange(len(all_eval_accuracies)) | |
| plt.figure() | |
| plt.title('Losses') | |
| plt.plot(steps[1:], all_train_losses, label='train') | |
| plt.plot(steps, all_eval_losses, label='eval') | |
| plt.legend() | |
| plt.ylim([0, 4]) | |
| plt.savefig(plot_path + 'losses.png', dpi=200) | |
| fig = plt.gcf() | |
| plt.close(fig) | |
| plt.figure() | |
| plt.title('Accuracies') | |
| plt.plot(steps[1:], all_train_accuracies, label='train') | |
| plt.plot(steps, all_eval_accuracies, label='eval') | |
| plt.legend() | |
| plt.ylim([0, 1]) | |
| plt.savefig(plot_path + 'accs.png', dpi=200) | |
| fig = plt.gcf() | |
| plt.close(fig) | |
| plt.figure() | |
| plt.title('Train confusion matrix') | |
| plt.ylabel('True') | |
| plt.xlabel('Predicted') | |
| plt.imshow(all_train_cf_matrices[-1], vmin=0, vmax=1) | |
| plt.colorbar() | |
| plt.savefig(plot_path + f'train_confusion_matrix.png', dpi=200) | |
| fig = plt.gcf() | |
| plt.close(fig) | |
| plt.figure() | |
| plt.title('Eval confusion matrix') | |
| plt.ylabel('True') | |
| plt.xlabel('Predicted') | |
| plt.imshow(all_eval_cf_matrices[-1], vmin=0, vmax=1) | |
| plt.colorbar() | |
| plt.savefig(plot_path + f'eval_confusion_matrix.png', dpi=200) | |
| fig = plt.gcf() | |
| plt.close(fig) | |
| plt.close('all') | |
| def get_model(model_path): | |
| with open(model_path + 'params.json', 'r') as f: | |
| params = json.load(f) | |
| params['save_path'] = model_path | |
| model_chkpt = model_path + "checkpoint_best.save" | |
| model = SimpleNet(params['input_dim'], params['hidden_dims'], params['output_dim'], params['activation'], params['dropout']) | |
| model.load_state_dict(torch.load(model_chkpt)) | |
| model.eval() | |
| return model, params | |
| def compute_expe_name_and_save_path(params): | |
| weights_str = '[' | |
| for aux in params['auxiliaries_dict'].keys(): | |
| weights_str += f'{params["auxiliaries_dict"][aux]["weight"]}, ' | |
| weights_str = weights_str[:-2] + ']' | |
| save_path = params['save_path'] + params["trial_id"] | |
| save_path += f'_lr{params["lr"]}' | |
| save_path += f'_bs{params["batch_size"]}' | |
| save_path += f'_hd{params["hidden_dims"]}' | |
| save_path += f'_activ{params["activation"]}' | |
| save_path += f'_w{weights_str}' | |
| counter = 0 | |
| while os.path.exists(save_path + f"_{counter}"): | |
| counter += 1 | |
| save_path = save_path + f"_{counter}" + '/' | |
| params["save_path"] = save_path | |
| os.makedirs(save_path) | |
| os.makedirs(save_path + 'plots/') | |
| params['plot_path'] = save_path + 'plots/' | |
| print(f'logging to {save_path}') | |
| return params | |
| if __name__ == '__main__': | |
| params = get_params() | |
| run_experiment(params) | |