Cédric Colas
initial commit
e775f6d
raw
history blame
13 kB
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)