#!/usr/bin/env python # coding: utf-8 # In[41]: import numpy as np import pandas as pd import tensorflow as tf import tensorflow.keras.optimizers as tf_optim tf.get_logger().setLevel('ERROR') import matplotlib.pyplot as plt import matplotlib as mpl import seaborn as sns import random import pickle import json import math import datetime import os import random from sklearn.model_selection import train_test_split fig_dpi = 200 # # Neural Network Training # # ## Load Dataset # # Read CSVs dumped from MatLab and parse into Pandas DataFrames # In[42]: data = pd.read_csv('features.csv', header=None).T data.columns = ['Clump thickness', 'Uniformity of cell size', 'Uniformity of cell shape', 'Marginal adhesion', 'Single epithelial cell size', 'Bare nuclei', 'Bland chomatin', 'Normal nucleoli', 'Mitoses'] labels = pd.read_csv('targets.csv', header=None).T labels.columns = ['Benign', 'Malignant'] data.describe() # In[31]: labels.head() # ### Explore Dataset # # The classes are uneven in their occurences, stratify when splitting later on # In[5]: labels.astype(bool).sum(axis=0) # ## Split Dataset # # Using a 50/50 split # In[43]: data_train, data_test, labels_train, labels_test = train_test_split(data, labels, test_size=0.5 # , stratify=labels ) # ## Generate & Retrieve Model # # Get a shallow model with a single hidden layer of varying nodes # In[44]: def get_model(hidden_nodes=9, activation=lambda: 'sigmoid', weight_init=lambda: 'glorot_uniform'): layers = [tf.keras.layers.InputLayer(input_shape=(9,), name='Input'), tf.keras.layers.Dense(hidden_nodes, activation=activation(), kernel_initializer=weight_init(), name='Hidden'), tf.keras.layers.Dense(2, activation='softmax', kernel_initializer=weight_init(), name='Output')] model = tf.keras.models.Sequential(layers) return model # Get a Keras Tensorboard callback for dumping data for later analysis # In[45]: def tensorboard_callback(path='tensorboard-logs', prefix=''): return tf.keras.callbacks.TensorBoard( log_dir=os.path.normpath(os.path.join(path, prefix + datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))), histogram_freq=1 ) # # Example Training # ## Define Model # # Variable number of hidden nodes. All using 9D outputs except the last layer which is 2D for binary classification # In[60]: model = get_model(9) model.compile('sgd', loss='categorical_crossentropy', metrics=['accuracy']) model.summary() # ## Train Model # # Example 10 epochs # In[61]: model.fit(data_train.to_numpy(), labels_train.to_numpy(), epochs=5) # In[62]: model.metrics_names # In[63]: model.metrics[1].result() # # Experiment 1 # # The below function runs an iteration of layer/epoch investigations. # Returns the amount of layers/epochs used as well as the results and the model. # # Using cancer dataset (as in E2) and 'trainscg' or an optimiser of your choice, vary nodes and epochs (that is using early stopping for epochs) over suitable range, to find optimal choice in terms of classification test error rate of node/epochs for 50/50% random train/test split (no validation set). It is suggested that you initially try epochs = [ 1 2 4 8 16 32 64], nodes = [2 8 32], so there would be 21 node/epoch combinations. # # (Hint1: from the 'advanced script' in E2, nodes can be changed to xx, with hiddenLayerSize = xx; and epochs changed to xx by addingnet. trainParam.epochs = xx; placed afternet = patternnet(hiddenLayerSize, trainFcn); --see 'trainscg' help documentation for changing epochs). # # Repeat each of the 21 node/epoch combinations at least thirty times, with different 50/50 split and take average and report classification error rate and standard deviation (std). Graph classification train and test error rate and std as node-epoch changes, that is plot error rate vs epochs for different number of nodes. Report the optimal value for test error rate and associated node/epoch values. # # (Hint2: as epochs increases you can expect the test error rate to reach a minimum and then start increasing, you may need to set the stopping criteria to achieve the desired number of epochs - Hint 3: to find classification error rates for train and test set, you need to check the code from E2, to determine how you may obtain the train and test set patterns) # # In[46]: # hidden_nodes = [2, 8, 16, 24, 32] # epochs = [1, 2, 4, 8, 16, 32, 64, 100, 150, 200] hidden_nodes = [2, 8, 16] epochs = [1, 2, 4, 8] def evaluate_parameters(hidden_nodes=hidden_nodes, epochs=epochs, batch_size=128, optimizer=lambda: 'sgd', weight_init=lambda: 'glorot_uniform', loss=lambda: 'categorical_crossentropy', metrics=['accuracy'], callbacks=None, validation_split=None, verbose=0, print_params=True, return_model=True, run_eagerly=False, tboard=True, dtrain=data_train, dtest=data_test, ltrain=labels_train, ltest=labels_test): for idx1, hn in enumerate(hidden_nodes): for idx2, e in enumerate(epochs): if print_params: print(f"Nodes: {hn}, Epochs: {e}") model = get_model(hn, weight_init=weight_init) model.compile( optimizer=optimizer(), loss=loss(), metrics=metrics, run_eagerly=run_eagerly ) if tboard: if callbacks is not None: cb = [i() for i in callbacks] + [tensorboard_callback(prefix=f'exp1-{hn}-{e}-')] else: cb = [tensorboard_callback(prefix=f'exp1-{hn}-{e}-')] response = {"nodes": hn, "epochs": e, ############## ## TRAIN ############## "history": model.fit(dtrain.to_numpy(), ltrain.to_numpy(), epochs=e, verbose=verbose, callbacks=cb, validation_split=validation_split).history, ############## ## TEST ############## "results": model.evaluate(dtest.to_numpy(), ltest.to_numpy(), callbacks=cb, batch_size=batch_size, verbose=verbose), "optimizer": model.optimizer.get_config(), "loss": model.loss, "model_config": json.loads(model.to_json()) } if return_model: response["model"] = model yield response # ## Single Iteration # Run a single iteration of epoch/layer investigations # In[17]: # es = tf.keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience = 5) single_results = list(evaluate_parameters(return_model=False, validation_split=0.2 , optimizer = lambda: tf.keras.optimizers.SGD(learning_rate=0.5, momentum=0.5) # , callbacks=[es] )) # ### Train/Test Curves # # For a single test from the set # In[68]: single_result = random.choice([i for i in single_results if i["epochs"] > 1]) single_history = single_result["history"] fig, axes = plt.subplots(1, 2, figsize=(15,7)) fig.set_dpi(fig_dpi) ################ ## LOSS ################ ax = axes[0] ax.set_title("Training vs Validation Loss") ax.plot(single_history['loss'], label="train", lw=2) ax.plot(single_history['val_loss'], label="validation", lw=2, c=(1,0,0)) ax.set_xlabel("Epochs") ax.grid() ax.legend() ################ ## ACCURACY ################ ax = axes[1] ax.set_title("Training vs Validation Accuracy") ax.plot(single_history['accuracy'], label="train", lw=2) ax.plot(single_history['val_accuracy'], label="validation", lw=2, c=(1,0,0)) ax.set_xlabel("Epochs") # ax.set_ylim(0, 1) ax.grid() ax.legend() print(f"Nodes: {single_result['nodes']}, Epochs: {single_result['epochs']}") # plt.tight_layout() # plt.savefig('fig.png') plt.show() # ### Accuracy Surface # In[69]: X, Y = np.meshgrid(epochs, hidden_nodes) shaped_result = np.reshape([r["results"][1] for r in single_results], (len(hidden_nodes), len(epochs))) fig = plt.figure(figsize=(8, 5)) fig.set_dpi(fig_dpi) ax = plt.axes(projection='3d') surf = ax.plot_surface(X, Y, shaped_result, cmap='viridis') ax.set_title('Model test accuracy over different training periods with different numbers of nodes') ax.set_xlabel('Epochs') ax.set_ylabel('Hidden Nodes') ax.set_zlabel('Accuracy') ax.view_init(30, -110) # ax.set_zlim([0, 1]) fig.colorbar(surf, shrink=0.3, aspect=6) plt.tight_layout() # plt.savefig('fig.png') plt.show() # ### Error Rate Curves # In[70]: fig = plt.figure(figsize=(8, 5)) fig.set_dpi(fig_dpi) for layer in hidden_nodes: plt.plot(epochs, 1 - np.array([i["results"][1] for i in single_results if i["nodes"] == layer]), label=f'{layer} Nodes') plt.legend() plt.grid() plt.title("Test error rates for a single iteration of different epochs and hidden node training") plt.xlabel("Epochs") plt.ylabel("Error Rate") plt.ylim(0) # plt.savefig('fig.png') plt.show() # ## Multiple Iterations # # Run multiple iterations of the epoch/layer investigations and average # # ### CSV Results # # | test | learning rate | momentum | batch size | hidden nodes | epochs | # | --- | --- | --- | --- | --- | --- | # |1|0.01|0|128|2, 8, 12, 16, 24, 32, 64, 128, 256|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| # |2|0.5|0.1|128|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| # |3|0.2|0.05|128|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| # |4|0.08|0.04|128|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| # |5|0.08|0|128|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| # |6|0.06|0|128|1, 2, 3, 4, 5, 6, 7, 8|1, 2, 4, 8, 16, 32, 64, 100| # |7|0.06|0|35|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| # # ### Pickle Results # # | test | learning rate | momentum | batch size | hidden nodes | epochs | statified | # | --- | --- | --- | --- | --- | --- | --- | # |1|0.01|0|128|2, 8, 12, 16, 24, 32, 64, 128, 256|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| | # |2|0.5|0.1|128|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| | # |3|1|0.3|20|2, 8, 12, 16, 24, 32, 64, 128|1, 2, 4, 8, 16, 32, 64, 100| | # |4|0.6|0.1|20|2, 8, 16, 24, 32|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| | # |5|0.05|0.01|20|2, 8, 16, 24, 32|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| | # |6|1.5|0.5|20|2, 8, 16, 24, 32|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| | # |2-1|0.01|0|35|2, 8, 16, 24, 32|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| n | # |2-2|0.1|0|35|2, 16, 32|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-3|0.15|0|35|2, 16, 32|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-4|0.08|0.9|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-5|0.08|0.2|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-6|0.01|0.1|35|2, 8, 16, 24, 32|1, 2, 4, 8, 16, 32, 64, 100, 150, 200| n | # |2-7|0.01|0.9|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-8|0.01|0.5|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-9|0.01|0.3|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-10|0.01|0.7|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-11|0.01|0.0|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| n | # |2-12|0.1|0.0|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| y | # |2-13|0.5|0.0|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| y | # |2-14|0.05|0.0|35|1, 2, 8, 16, 32, 64|1, 2, 4, 8, 16, 32, 64, 100| y | # In[214]: multi_param_results = list() multi_iterations = 30 for i in range(multi_iterations): print(f"Iteration {i+1}/{multi_iterations}") data_train, data_test, labels_train, labels_test = train_test_split(data, labels, test_size=0.5 # , stratify=labels ) multi_param_results.append(list(evaluate_parameters(dtrain=data_train, dtest=data_test, ltrain=labels_train, ltest=labels_test, hidden_nodes=[2, 16, 32], epochs=[1, 2, 4, 8, 16, 32, 64, 100], optimizer=lambda: tf.keras.optimizers.SGD(learning_rate=0.15, momentum=0.0), weight_init=lambda: 'random_uniform', return_model=False, print_params=False, batch_size=35))) # ### Accuracy Tensor # # Create a tensor for holding the accuracy results # # (Iterations x [Test/Train] x Number of nodes x Number of epochs) # In[268]: multi_param_epochs = sorted(list({i["epochs"] for i in multi_param_results[0]})) multi_param_nodes = sorted(list({i["nodes"] for i in multi_param_results[0]})) multi_param_iter = len(multi_param_results) accuracy_tensor = np.zeros((multi_param_iter, 2, len(multi_param_nodes), len(multi_param_epochs))) for iter_idx, iteration in enumerate(multi_param_results): for single_test in iteration: accuracy_tensor[iter_idx, :, multi_param_nodes.index(single_test['nodes']), multi_param_epochs.index(single_test['epochs'])] = [single_test["results"][1], single_test["history"]["accuracy"][-1]] mean_param_accuracy = np.mean(accuracy_tensor, axis=0) std_param_accuracy = np.std(accuracy_tensor, axis=0) print(f'{multi_param_iter} Tests') print(f'Nodes: {multi_param_nodes}') print(f'Epochs: {multi_param_epochs}') print() print(f'Loss: {multi_param_results[0][0]["loss"]}') print(f'LR: {multi_param_results[0][0]["optimizer"]["learning_rate"]:.3}') print(f'Momentum: {multi_param_results[0][0]["optimizer"]["momentum"]:.3}') # #### Export/Import Test Sets # # Export mean and standard deviations for retrieval and visualisation # In[215]: pickle.dump(multi_param_results, open("results/exp1-test2-3.p", "wb")) # In[267]: exp1_testname = 'exp1-test2-14' multi_param_results = pickle.load(open(f"results/{exp1_testname}.p", "rb")) np.savetxt("exp1-mean.csv", mean_param_accuracy, delimiter=',') np.savetxt("exp1-std.csv", std_param_accuracy, delimiter=',')mean_param_accuracy = np.loadtxt("results/test1-exp1-mean.csv", delimiter=',') std_param_accuracy = np.loadtxt("results/test1-exp1-std.csv", delimiter=',') # multi_iterations = 30 # ### Best Results # In[166]: best_param_accuracy_idx = np.unravel_index(np.argmax(mean_param_accuracy[0, :, :]), mean_param_accuracy.shape) best_param_accuracy = mean_param_accuracy[best_param_accuracy_idx] best_param_accuracy_nodes = multi_param_nodes[best_param_accuracy_idx[1]] best_param_accuracy_epochs = multi_param_epochs[best_param_accuracy_idx[2]] print(f'Nodes: {best_param_accuracy_nodes}, Epochs: {best_param_accuracy_epochs}, {best_param_accuracy * 100:.3}% Accurate') # ### Test Accuracy Surface # In[269]: X, Y = np.meshgrid(multi_param_epochs, multi_param_nodes) # fig = plt.figure(figsize=(10, 5)) fig = plt.figure() fig.set_dpi(fig_dpi) ax = plt.axes(projection='3d') surf = ax.plot_surface(X, Y, mean_param_accuracy[0, :, :], cmap='coolwarm') ax.set_title(f'Average Accuracy') ax.set_xlabel('Epochs') ax.set_ylabel('Hidden Nodes') ax.set_zlabel('Accuracy') ax.view_init(30, -110) # ax.set_zlim([0, 1]) fig.colorbar(surf, shrink=0.3, aspect=6) plt.tight_layout() # plt.savefig(f'graphs/{exp1_testname}-acc-surf.png') plt.show() # ### Test Error Rate Curves # In[270]: fig = plt.figure(figsize=(5, 4)) # fig = plt.figure() fig.set_dpi(fig_dpi) for idx, (layer, std) in enumerate(zip(mean_param_accuracy[0, :, :], std_param_accuracy[0, :, :])): # plt.errorbar(multi_param_epochs, 1 - layer, yerr=std, capsize=4, label=f'{multi_param_nodes[idx]} Nodes') plt.plot(multi_param_epochs, 1 - layer, '-', label=f'{multi_param_nodes[idx]} Nodes', lw=2) plt.legend() plt.grid() plt.title(f"Test error rates over hidden nodes") plt.xlabel("Epochs") plt.ylabel("Error Rate") plt.ylim(0, 0.6) plt.tight_layout() # plt.savefig(f'graphs/{exp1_testname}-error-rate-curves.png') plt.show() # In[271]: fig = plt.figure(figsize=(5, 4)) # fig = plt.figure() fig.set_dpi(fig_dpi) for idx, (layer, std) in enumerate(zip(mean_param_accuracy[0, :, :], std_param_accuracy[0, :, :])): # plt.errorbar(multi_param_epochs, 1 - layer, yerr=std, capsize=4, label=f'{multi_param_nodes[idx]} Nodes') plt.plot(multi_param_epochs, std, 'x-', label=f'{multi_param_nodes[idx]} Nodes', lw=2) plt.legend() plt.grid() plt.title(f"Test error rate std. dev over hidden nodes") plt.xlabel("Epochs") plt.ylabel("Standard Deviation") plt.ylim(0, 0.1) plt.tight_layout() # plt.savefig(f'graphs/{exp1_testname}-error-rate-std.png') plt.show() # ### Test/Train Error Over Nodes # In[272]: fig, axes = plt.subplots(math.ceil(len(multi_param_nodes) / 2), 2, figsize=(6, 6*math.ceil(len(multi_param_nodes) / 2)/3)) fig.set_dpi(fig_dpi) for idx, (nodes, ax) in enumerate(zip(multi_param_nodes, axes.flatten())): ax.set_title(f'Error Rates For {nodes} Nodes') # ax.errorbar(multi_param_epochs, 1 - mean_param_accuracy[0, idx, :], fmt='x', ls='-', yerr=std_param_accuracy[0, idx, :], markersize=4, lw=1, label='Test', capsize=4, c=(0, 0, 1), ecolor=(0, 0, 1, 0.5)) # ax.errorbar(multi_param_epochs, 1 - mean_param_accuracy[1, idx, :], fmt='x', ls='-', yerr=std_param_accuracy[1, idx, :], markersize=4, lw=1, label='Train', capsize=4, c=(1, 0, 0), ecolor=(1, 0, 0, 0.5)) ax.plot(multi_param_epochs, 1 - mean_param_accuracy[0, idx, :], 'x', ls='-', lw=1, label='Test', c=(0, 0, 1)) ax.plot(multi_param_epochs, 1 - mean_param_accuracy[1, idx, :], 'x', ls='-', lw=1, label='Train', c=(1, 0, 0)) ax.set_ylim(0, np.round(np.max(1 - mean_param_accuracy + std_param_accuracy) + 0.05, 1)) ax.legend() ax.grid() fig.tight_layout() # fig.savefig(f'graphs/{exp1_testname}-test-train-error-rate.png') # In[273]: fig, axes = plt.subplots(math.ceil(len(multi_param_nodes) / 2), 2, figsize=(6, 6*math.ceil(len(multi_param_nodes) / 2)/3)) fig.set_dpi(fig_dpi) for idx, (nodes, ax) in enumerate(zip(multi_param_nodes, axes.flatten())): ax.set_title(f'Error Rate Std Dev. For {nodes} Nodes') ax.plot(multi_param_epochs, std_param_accuracy[0, idx, :], 'x', ls='-', lw=1, label='Test', c=(0, 0, 1)) ax.plot(multi_param_epochs, std_param_accuracy[1, idx, :], 'x', ls='-', lw=1, label='Train', c=(1, 0, 0)) ax.set_ylim(0, np.round(np.max(std_param_accuracy) + 0.05, 1)) ax.legend() ax.grid() fig.tight_layout() # fig.savefig(f'graphs/{exp1_testname}-test-train-error-rate-std.png') # # Experiment 2 # # For cancer dataset, choose an appropriate value of node and epochs, based on Exp 1) and use ensemble of individual (base) classifiers with random starting weights and Majority Vote to see if performance improves - repeat the majority vote ensemble at least thirty times with different 50/50 split and average and graph (Each classifier in the ensemble sees the same training patterns). Repeat for a different odd number (prevents tied vote) of individual classifiers between 3 and 25, and comment on the result of individualclassifier accuracy vs ensemble accuracy as number of base classifiers varies. Consider changing the number of nodes/epochs (both less complex and more complex) to see if you obtain better performance, and comment on the result with respect to why the optimal node/epoch combination may be different for an ensemble compared with the base classifier, as in Exp 1). # # (Hint4: to implement majority vote you need to determine the predicted class labels -probably easier to implement yourself rather than use the ensemble matlab functions) # # In[249]: num_models=[1, 3, 9, 15, 25] def evaluate_ensemble_vote(hidden_nodes=16, epochs=50, batch_size=128, learning_rates=None, rand_ranges=False, optimizer=lambda: 'sgd', weight_init=lambda: 'glorot_uniform', loss=lambda: 'categorical_crossentropy', metrics=['accuracy'], callbacks=None, validation_split=None, round_predictions=True, nmodels=num_models, tboard=True, exp='2', verbose=0, print_params=True, return_model=True, dtrain=data_train, dtest=data_test, ltrain=labels_train, ltest=labels_test): for m in nmodels: # iterate over different ensemble sizes if print_params: print(f"Models: {m}") # response dict object for test stats response = {"epochs": list(), "num_models": m} ################### ## GET MODELS ################### if isinstance(hidden_nodes, tuple): # for range of hidden nodes, calculate value per model if m == 1: if not rand_ranges: # just average provided range models = [get_model(int(np.mean(hidden_nodes)), weight_init=weight_init)] response["nodes"] = [int(np.mean(hidden_nodes))] else: # get random val node_val = random.randint(*hidden_nodes) models = [get_model(node_val, weight_init=weight_init)] response["nodes"] = [node_val] else: if not rand_ranges: # use linspace to generate equally spaced nodes throughout range models = [get_model(int(i), weight_init=weight_init) for i in np.linspace(*hidden_nodes, num=m)] response["nodes"] = [int(i) for i in np.linspace(*hidden_nodes, num=m)] else: # use random to generate nodes throughout range node_val = [random.randint(*hidden_nodes) for _ in range(m)] models = [get_model(i, weight_init=weight_init) for i in node_val] response["nodes"] = node_val elif hidden_nodes == 'm': # incrementing mode, number of nodes ranges from 1 to m # more nodes in larger ensembles models = [get_model(i+1, weight_init=weight_init) for i in range(m)] response["nodes"] = [i+1 for i in range(m)] else: # not a range of epochs, just set to given value models = [get_model(hidden_nodes, weight_init=weight_init) for _ in range(m)] response["nodes"] = hidden_nodes ###################### ## COMPILE MODELS ###################### if learning_rates is None: # default, just load optimiser for model in models: model.compile( optimizer=optimizer(), loss=loss(), metrics=metrics ) else: for idx, model in enumerate(models): optim = optimizer() # generate learning rate either randomly or linearly if isinstance(learning_rates, tuple): if not rand_ranges: # get equal spaced learning rates optim.learning_rate = np.linspace(*learning_rates, num=m)[idx] else: # get random learning rate optim.learning_rate = random.uniform(*learning_rates) elif learning_rates == '+': # incrementing mode, scale with size of ensemble optim.learning_rate = 0.01 * (idx + 1) model.compile( optimizer=optim, loss=loss(), metrics=metrics ) if tboard: # include a tensorboard callback to dump stats for later analysis if callbacks is not None: cb = [i() for i in callbacks] + [tensorboard_callback(prefix=f'exp{exp}-{m}-')] else: cb = [tensorboard_callback(prefix=f'exp{exp}-{m}-')] ################### ## TRAIN MODELS ################### histories = list() for idx, model in enumerate(models): if isinstance(epochs, tuple): # for range of epochs, calculate value per model if not rand_ranges: if m == 1: e = np.mean(epochs) # average, not lower bound if single model else: e = np.linspace(*epochs, num=m)[idx] e = int(e) else: e = random.randint(*epochs) else: # not a range of epochs, just set to given value e = epochs # print(m, e) # debug history = model.fit(dtrain.to_numpy(), ltrain.to_numpy(), epochs=e, verbose=verbose, callbacks=cb, validation_split=validation_split) histories.append(history.history) response["epochs"].append(e) ############################ ## FEEDFORWARD TEST DATA ############################ # TEST DATA PREDICTIONS response["predictions"] = [model(dtest.to_numpy()) for model in models] # TEST LABEL TENSOR ltest_tensor = tf.constant(ltest.to_numpy()) ######################## ## ENSEMBLE ACCURACY ######################## ensem_sum_rounded = sum(tf.math.round(pred) for pred in response["predictions"]) ensem_sum = sum(response["predictions"]) # round predictions to onehot vectors and sum over all ensemble models # take argmax for ensemble predicted class correct = 0 # number of correct ensemble predictions correct_num_models = 0 # when correctly predicted ensembley, number of models correctly classifying individual_accuracy = 0 # proportion of models correctly classifying # pc = predicted class, pcr = rounded predicted class, gt = ground truth for pc, pcr, gt in zip(ensem_sum, ensem_sum_rounded, ltest_tensor): gt_argmax = tf.math.argmax(gt) if round_predictions: pred_val = pcr else: pred_val = pc correct_models = pcr[gt_argmax] / m # use rounded value so will divide nicely individual_accuracy += correct_models if tf.math.argmax(pred_val) == gt_argmax: # ENSEMBLE EVALUATE HERE correct += 1 correct_num_models += correct_models # print(pc.numpy(), pcr.numpy(), gt.numpy(), (pcr[gt_argmax] / m).numpy(), True) # debug # else: # print(pc.numpy(), pcr.numpy(), gt.numpy(), (pcr[gt_argmax] / m).numpy(), False) ######################## ## RESULTS ######################## response.update({ "history": histories, "optimizer": model.optimizer.get_config(), "model_config": json.loads(model.to_json()), "loss": model.loss, "round_predictions": round_predictions, "accuracy": correct / len(ltest), # average number of correct ensemble predictions "agreement": correct_num_models / correct, # when correctly predicted ensembley, average proportion of models correctly classifying "individual_accuracy": individual_accuracy / len(ltest) # average proportion of individual models correctly classifying }) if return_model: response["models"] = models yield response # ## Single Iteration # Run a single iteration of ensemble model investigations # In[250]: single_ensem_results = list() # for test in evaluate_ensemble_vote(epochs=(5, 300), optimizer=lambda: tf.keras.optimizers.SGD(learning_rate=0.02)): for test in evaluate_ensemble_vote(hidden_nodes=(1, 20), epochs=(1, 20), rand_ranges=True, learning_rates=(0.01, 0.5), optimizer=lambda: tf.keras.optimizers.SGD(learning_rate=0.02)): single_ensem_results.append(test) print(test["nodes"], test["epochs"]) # In[251]: fig = plt.figure(figsize=(8, 5)) fig.set_dpi(fig_dpi) ensem_x = [i["num_models"] for i in single_ensem_results] plt.plot(ensem_x, 1 - np.array([i["accuracy"] for i in single_ensem_results]), 'x-', label='Ensemble Test') plt.plot(ensem_x, 1 - np.array([i["individual_accuracy"] for i in single_ensem_results]), 'x-', label='Individual Test') plt.plot(ensem_x, 1 - np.array([i["agreement"] for i in single_ensem_results]), 'x-', label='Disagreement') plt.title("Test Error Rates for Horizontal Model Ensembles") plt.ylim(0) plt.grid() plt.legend() plt.ylabel("Error Rate") plt.xlabel("Number of Models") plt.show() # ## Multiple Iterations # Run multiple iterations of the ensemble model investigations and average # # ### CSV Results # # | test | learning rate | momentum | batch size | hidden nodes | epochs | models | # | --- | --- | --- | --- | --- | --- | --- | # |1|0.06|0|128|16|50|1, 3, 9, 15, 25| # |2|0.06|0|35|16|1 - 100|1, 3, 9, 15, 25| # # ### Pickle Results # # | test | learning rate | momentum | batch size | hidden nodes | epochs | models | stratify | # | --- | --- | --- | --- | --- | --- | --- | --- | # |3|0.06|0.05|35|16|1 - 300|1, 3, 9, 15, 25| | # |4|0.06|0.05|35|1 - 50|50|1, 3, 9, 15, 25| | # |5|0.06|0.05|35|1 - 300|50|1, 3, 9, 15, 25| | # |6|0.001|0.01|35|1 - 400|50|1, 3, 9, 15, 25| | # |7|0.01|0.01|35|1 - 400|30 - 150|1, 3, 9, 15, 25| | # |8|0.03|0.01|35|1 - 400|5 - 100|1, 3, 9, 15, 25| | # |9|0.1|0.01|35|1 - 400|20|1, 3, 9, 15, 25| | # |10|0.15|0.01|35|1 - 400|20|1, 3, 9, 15, 25, 35, 45| | # |11|0.15|0.01|35|1 - 400|10|1, 3, 9, 15, 25, 35, 45| | # |12|0.02|0.01|35|m|50|1, 3, 9, 15, 25, 35, 45| | # |13|0.01 exp 0.98, 1|0.01|35|1 - 200|50|1, 3, 9, 15, 25, 35, 45| n | # |14|0.01|0.01|35|1 - 200|50|1, 3, 9, 15, 25, 35, 45| n | # |15|0.01|0.9|35|50 - 100|50|1, 3, 5, 7, 9, 15, 25, 35, 45| n | # |16|0.01|0.1|35|50 - 100|50|1, 3, 5, 7, 9, 15, 25, 35, 45| n | # |17|0.1|0.1|35|50 - 100|50 - 100|1, 3, 5, 7, 9, 15, 25, 35, 45| n | # |18 (r)|0.01 - 1|0.0|35|1 - 50|20 - 70|1, 3, 5, 7, 9, 15, 25, 35| n | # |19 (r)|0.01 - 1|0.0|35|1 - 100|10 - 70|1, 3, 5, 7, 9, 15, 25| n | # In[335]: batch_size=35 test_size=0.5 epochs=50 lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(0.01, decay_steps=1, decay_rate=0.98) plt.plot(range(epochs+1), [lr_schedule(i) for i in range(epochs+1)]) plt.grid() plt.ylim(0) plt.xlabel('Epochs') plt.ylabel('Learning Rate') plt.show() # In[357]: multi_ensem_results = list() multi_ensem_iterations = 30 for i in range(multi_ensem_iterations): print(f"Iteration {i+1}/{multi_ensem_iterations}") data_train, data_test, labels_train, labels_test = train_test_split(data, labels, test_size=test_size, # stratify=labels ) multi_ensem_results.append(list(evaluate_ensemble_vote(epochs=(50, 100), hidden_nodes=(50, 100), nmodels=[1, 3, 5, 7, 9, 15, 25, 35, 45], optimizer=lambda: tf.keras.optimizers.SGD(learning_rate=0.1, momentum=0.1), weight_init=lambda: 'random_uniform', batch_size=batch_size, dtrain=data_train, dtest=data_test, ltrain=labels_train, ltest=labels_test, return_model=False, print_params=False))) # ### Accuracy Tensor # # Create a tensor for holding the accuracy results # # (Iterations x Param x Number of models) # # #### Params # 0. Test Accuracy # 1. Train Accuracy # 2. Individual Accuracy # 3. Agreement # In[253]: def test_tensor_data(test): return [test["accuracy"], np.mean([i["accuracy"][-1] for i in test["history"]]), # avg train acc test["individual_accuracy"], test["agreement"]] # In[354]: multi_ensem_models = sorted(list({i["num_models"] for i in multi_ensem_results[0]})) multi_ensem_iter = len(multi_ensem_results) accuracy_ensem_tensor = np.zeros((multi_ensem_iter, 4, len(multi_ensem_models))) for iter_idx, iteration in enumerate(multi_ensem_results): for single_test in iteration: ensem_models_idx = multi_ensem_models.index(single_test['num_models']) accuracy_ensem_tensor[iter_idx, :, ensem_models_idx] = test_tensor_data(single_test) mean_ensem_accuracy = np.mean(accuracy_ensem_tensor, axis=0) std_ensem_accuracy = np.std(accuracy_ensem_tensor, axis=0) print(f'{multi_ensem_iter} Tests') print(f'Models: {multi_ensem_models}') print() print(f'Loss: {multi_ensem_results[0][0]["loss"]}') print(f'LR: {multi_ensem_results[0][0]["optimizer"]["learning_rate"]:.3}') print(f'Momentum: {multi_ensem_results[0][0]["optimizer"]["momentum"]:.3}') # #### Export/Import Test Sets # # Export mean and standard deviations for retrieval and visualisation # In[358]: exp2_testname = 'exp2-test17' pickle.dump(multi_ensem_results, open(f"results/{exp2_testname}.p", "wb")) # In[353]: exp2_testname = 'exp2-test19' multi_ensem_results = pickle.load(open(f"results/{exp2_testname}.p", "rb")) np.savetxt("exp2-mean.csv", mean_ensem_accuracy, delimiter=',') np.savetxt("exp2-std.csv", std_ensem_accuracy, delimiter=',')mean_ensem_accuracy = np.loadtxt("results/test1-exp2-mean.csv", delimiter=',') std_ensem_accuracy = np.loadtxt("results/test1-exp2-std.csv", delimiter=',') # ### Best Results # In[355]: best_ensem_accuracy_idx = np.unravel_index(np.argmax(mean_ensem_accuracy[0, :]), mean_ensem_accuracy.shape) best_ensem_accuracy = mean_ensem_accuracy[best_ensem_accuracy_idx] best_ensem_accuracy_models = multi_ensem_models[best_ensem_accuracy_idx[1]] print(f'Models: {best_ensem_accuracy_models}, {best_ensem_accuracy * 100:.3}% Accurate') # ### Test/Train Error Over Model Numbers # In[356]: fig = plt.figure(figsize=(5, 4)) fig.set_dpi(fig_dpi) plt.plot(multi_ensem_models, 1 - mean_ensem_accuracy[0, :], 'x-', label='Ensemble Test') plt.plot(multi_ensem_models, 1 - mean_ensem_accuracy[2, :], 'x-', label='Individual Test') plt.plot(multi_ensem_models, 1 - mean_ensem_accuracy[1, :], 'x-', label='Individual Train') plt.plot(multi_ensem_models, 1 - mean_ensem_accuracy[3, :], 'x-', label='Disagreement') # plt.errorbar(multi_ensem_models, 1 - mean_ensem_accuracy[0, :], yerr=std_ensem_accuracy[0, :], capsize=4, label='Ensemble Test') # plt.errorbar(multi_ensem_models, 1 - mean_ensem_accuracy[2, :], yerr=std_ensem_accuracy[2, :], capsize=4, label='Individual Test') # plt.errorbar(multi_ensem_models, 1 - mean_ensem_accuracy[1, :], yerr=std_ensem_accuracy[1, :], capsize=4, label='Individual Train') # plt.errorbar(multi_ensem_models, 1 - mean_ensem_accuracy[3, :], yerr=std_ensem_accuracy[3, :], capsize=4, label='Disagreement') plt.title(f"Error Rate for Horizontal Ensemble Models") plt.ylim(0, 0.1) # plt.ylim(0, np.max(1 - mean_ensem_accuracy + std_ensem_accuracy) + 0.05) plt.grid() plt.legend() plt.xlabel("Number of Models") plt.ylabel("Error Rate") plt.tight_layout() # plt.savefig(f'graphs/{exp2_testname}-error-rate-curves.png') plt.show() # In[305]: fig = plt.figure(figsize=(5, 4)) # fig = plt.figure() fig.set_dpi(fig_dpi) plt.plot(multi_ensem_models, std_ensem_accuracy[0, :], 'x-', label='Ensemble Test', lw=2) plt.plot(multi_ensem_models, std_ensem_accuracy[1, :], 'x-', label='Individual Train', lw=2) plt.plot(multi_ensem_models, std_ensem_accuracy[2, :], 'x-', label='Individual Test', lw=2) plt.plot(multi_ensem_models, std_ensem_accuracy[3, :], 'x-', label='Agreement', lw=2) plt.legend() plt.grid() plt.title(f"Test error rate std. dev over ensemble models") plt.xlabel("Number of Models") plt.ylabel("Standard Deviation") plt.ylim(0, 0.08) plt.tight_layout() # plt.savefig(f'graphs/{exp2_testname}-error-rate-std.png') plt.show() # # Experiment 3 # # Repeat Exp 2) for cancer dataset with two different optimisers of your choice e.g. 'trainlm' and 'trainrp'. Comment and discuss the result and decide which is more appropriate training algorithm for the problem. In your discussion, include in your description a detailed account of how the training algorithms (optimisations) work. # In[127]: def evaluate_optimisers(optimizers=[(lambda: 'sgd', 'sgd'), (lambda: 'adam', 'adam'), (lambda: 'rmsprop', 'rmsprop')], weight_init=lambda: 'glorot_uniform', print_params=True, **kwargs ): for o in optimizers: if print_params: print(f'Optimiser: {o[1]}') yield list(evaluate_ensemble_vote(optimizer=o[0], weight_init=weight_init, exp=f'3-{o[1]}', print_params=print_params, **kwargs )) # ## Single Iteration # In[13]: single_optim_results = list() for test in evaluate_optimisers(epochs=(5, 300), nmodels=[1, 3, 5]): single_optim_results.append(test) # ## Multiple Iterations # # ### Pickle Results # # | test | optim1 | optim2 | optim3 | lr | momentum | epsilon | batch size | hidden nodes | epochs | models | stratified | # | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | # | 1 | SGD | Adam | RMSprop | 0.1 | 0.0 | 1e7 | 35 | 16 | 1 - 100 | 1, 3, 9, 15, 25 | y | # | 2 | SGD | Adam | RMSprop | 0.05 | 0.01 | 1e7 | 35 | 16 | 1 - 100 | 1, 3, 9, 15, 25 | y | # | 3 | SGD | Adam | RMSprop | 0.1 | 0.01 | 1e7 | 35 | 1 - 400 | 20 | 1, 3, 9, 15, 25, 35, 45 | y | # | 4 | SGD | Adam | RMSprop | 0.075 | 0.01 | 1e7 | 35 | 1 - 400 | 20 | 1, 3, 9, 15, 25, 35, 45 | y | # | 5 | SGD | Adam | RMSprop | 0.05 | 0.01 | 1e7 | 35 | 1 - 400 | 20 | 1, 3, 9, 15, 25, 35, 45 | n | # | 6 | SGD | Adam | RMSprop | 0.02 | 0.01 | 1e7 | 35 | m | 50 | 1, 3, 9, 15, 25, 35, 45 | n | # | 7 | SGD | Adam | RMSprop | 0.1 | 0.9 | 1e-8 | 35 | 1 - 400 | 50 - 100 | 1, 3, 5, 7, 9, 15, 25 | n | # | 8 | SGD | Adam | RMSprop | 0.05 | 0.9 | 1e-8 | 35 | 1 - 400 | 50 - 100 | 1, 3, 5, 7, 9, 15, 25 | n | # | 9 (r) | SGD | Adam | RMSprop | 0.01 - 1 | 0.0 | 1e-7 | 35 | 1 - 100 | 10 - 70 | 1, 5, 9, 15, 25 | n | # | 10 (r) | SGD | Adam | RMSprop | 0.01 - 1 | 0.0 | 1e-7 | 35 | 1 - 100 | 1 - 70 | 1, 5, 9, 15, 25 | n | # In[27]: multi_optim_results = list() multi_optim_iterations = 30 multi_optim_lr = 0.05 multi_optim_mom = 0.01 multi_optim_eps = 1e-07 multi_optims = [(lambda: tf_optim.SGD(learning_rate=multi_optim_lr, momentum=multi_optim_mom), 'sgd'), (lambda: tf_optim.Adam(learning_rate=multi_optim_lr, epsilon=multi_optim_eps), 'adam'), (lambda: tf_optim.RMSprop(learning_rate=multi_optim_lr, momentum=multi_optim_mom, epsilon=multi_optim_eps), 'rmsprop')] for i in range(multi_optim_iterations): print(f"Iteration {i+1}/{multi_optim_iterations}") data_train, data_test, labels_train, labels_test = train_test_split(data, labels, test_size=0.5, # stratify=labels ) multi_optim_results.append(list(evaluate_optimisers(epochs=(50, 100), hidden_nodes=(1, 400), nmodels=[1, 3, 9, 15, 25], optimizers=multi_optims, weight_init=lambda: 'random_uniform', batch_size=35, dtrain=data_train, dtest=data_test, ltrain=labels_train, ltest=labels_test, return_model=False, print_params=False))) # ### Accuracy Tensor # # Create a tensor for holding the accuracy results # # (Iterations x Param x Number of models) # # #### Params # 0. Test Accuracy # 1. Train Accuracy # 2. Individual Accuracy # 3. Agreement # In[339]: multi_optim_results_dict = dict() # indexed by optimiser name multi_optim_iter = len(multi_optim_results) # number of iterations (30) ##################################### ## INDIVIDUAL RESULTS TO DICTIONARY ##################################### for iter_idx, iteration in enumerate(multi_optim_results): # of 30 iterations for model_idx, model_test in enumerate(iteration): # of 3 optimisers for single_optim_test in model_test: # single tests for each optimisers single_optim_name = single_optim_test["optimizer"]["name"] if single_optim_name not in multi_optim_results_dict: multi_optim_results_dict[single_optim_name] = list(list() for _ in range(multi_optim_iter)) multi_optim_results_dict[single_optim_name][iter_idx].append(single_optim_test) # list of numbers of models used in test multi_optim_models = sorted(list({i["num_models"] for i in multi_optim_results[0][0]})) ################################## ## DICTIONARY TO RESULTS TENSORS ################################## optim_tensors = dict() for optim, optim_results in multi_optim_results_dict.items(): accuracy_optim_tensor = np.zeros((multi_optim_iter, 4, len(multi_optim_models))) for iter_idx, iteration in enumerate(optim_results): for single_test in iteration: optim_models_idx = multi_optim_models.index(single_test['num_models']) accuracy_optim_tensor[iter_idx, :, optim_models_idx] = test_tensor_data(single_test) optim_tensors[optim] = { "accuracy": accuracy_optim_tensor, "mean": np.mean(accuracy_optim_tensor, axis=0), "std": np.std(accuracy_optim_tensor, axis=0) } print(f'{multi_optim_iter} Tests') print(f'Optimisers: {list(multi_optim_results_dict.keys())}') print(f'Models: {multi_optim_models}') print() print(f'Loss: {multi_optim_results[0][0][0]["loss"]}') # #### Export/Import Test Sets # # Export mean and standard deviations for retrieval and visualisation # In[28]: pickle.dump(multi_optim_results, open("results/exp3-test5.p", "wb")) # In[338]: exp3_testname = 'exp3-test10' multi_optim_results = pickle.load(open(f"results/{exp3_testname}.p", "rb")) # ### Best Results # In[340]: for optim, optim_results in optim_tensors.items(): best_optim_accuracy_idx = np.unravel_index(np.argmax(optim_results["mean"][0, :]), optim_results["mean"].shape) best_optim_accuracy = optim_results["mean"][best_optim_accuracy_idx] best_optim_accuracy_models = multi_optim_models[best_optim_accuracy_idx[1]] print(f'{optim}: {best_optim_accuracy_models} Models, {best_optim_accuracy * 100:.3}% Accurate') # ### Optimiser Error Rates # In[343]: fig, axes = plt.subplots(1, 3, figsize=(12, 3)) fig.set_dpi(fig_dpi) for idx, ((optimiser_name, tensors_dict), ax) in enumerate(zip(optim_tensors.items(), axes.flatten())): ax.plot(multi_optim_models, 1 - tensors_dict["mean"][0, :], 'x-', label='Ensemble Test') ax.plot(multi_optim_models, 1 - tensors_dict["mean"][2, :], 'x-', label='Individual Test') ax.plot(multi_optim_models, 1 - tensors_dict["mean"][1, :], 'x-', label='Individual Train') ax.plot(multi_optim_models, 1 - tensors_dict["mean"][3, :], 'x-', label='Disagreement') # ax.errorbar(multi_optim_models, 1 - tensors_dict["mean"][0, :], yerr=tensors_dict["std"][0, :], capsize=4, label='Ensemble Test') # ax.errorbar(multi_optim_models, 1 - tensors_dict["mean"][2, :], yerr=tensors_dict["std"][2, :], capsize=4, label='Individual Test') # ax.errorbar(multi_optim_models, 1 - tensors_dict["mean"][1, :], yerr=tensors_dict["std"][1, :], capsize=4, label='Individual Train') # ax.errorbar(multi_optim_models, 1 - tensors_dict["mean"][3, :], yerr=tensors_dict["std"][3, :], capsize=4, label='Disagreement') ax.set_title(f"{optimiser_name} Error Rate for Ensemble Models") ax.set_ylim(0, 0.15) # ax.set_ylim(0, np.max([np.max(1 - i["mean"] + i["std"]) for i in optim_tensors.values()]) + 0.03) ax.grid() # if idx > 0: # ax.legend() ax.set_xlabel("Number of Models") ax.set_ylabel("Error Rate") # axes[0].set_ylim(0, 0.4) axes[1].legend() axes[2].legend() plt.tight_layout() # plt.savefig(f'graphs/{exp3_testname}-error-rate-curves.png') plt.show() # In[345]: # fig = plt.figure(figsize=(5, 4)) # fig = plt.figure() # fig.set_dpi(fig_dpi) fig, axes = plt.subplots(1, 3, figsize=(12, 3)) fig.set_dpi(fig_dpi) for idx, ((optimiser_name, tensors_dict), ax) in enumerate(zip(optim_tensors.items(), axes.flatten())): ax.plot(multi_optim_models, tensors_dict["std"][0, :], 'x-', label='Ensemble Test', lw=2) ax.plot(multi_optim_models, tensors_dict["std"][1, :], 'x-', label='Individual Train', lw=2) ax.plot(multi_optim_models, tensors_dict["std"][2, :], 'x-', label='Individual Test', lw=2) ax.plot(multi_optim_models, tensors_dict["std"][3, :], 'x-', label='Agreement', lw=2) ax.legend() ax.grid() ax.set_title(f"{optimiser_name} ensemble test std. dev") ax.set_xlabel("Number of Models") ax.set_ylabel("Standard Deviation") ax.set_ylim(0, 0.15) plt.tight_layout() # plt.savefig(f'graphs/{exp3_testname}-errors-rate-std.png') plt.show() # In[ ]: