From d5c57497b3c1b6b8f8f7fc93c3f9cb22d35b1e48 Mon Sep 17 00:00:00 2001 From: Harry Slatyer Date: Fri, 2 Dec 2016 15:02:12 +1100 Subject: [PATCH] Basic NN learner implementation I've pulled the actual network logic out into a new class, to keep the TF stuff separate from everything else and to keep a clear separation between what's modelling the landscape and what's doing prediction. --- mloop/learners.py | 61 ++-------------------------- mloop/nnlearner.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 58 deletions(-) create mode 100644 mloop/nnlearner.py diff --git a/mloop/learners.py b/mloop/learners.py index 714c3fa..92b5e44 100644 --- a/mloop/learners.py +++ b/mloop/learners.py @@ -1583,18 +1583,11 @@ def __init__(self, self.cost_scaler = skp.StandardScaler() - - #--- FAKE NN CONSTRUCTOR START ---# - self.length_scale = 1 self.cost_has_noise = True self.noise_level = 1 - self.create_neural_net() - - - - #--- FAKE NN CONSTRUCTOR END ---# + self.neural_net_impl = NeuralNetImpl(self.num_params) self.archive_dict.update({'archive_type':'neural_net_learner', 'bad_run_indexs':self.bad_run_indexs, @@ -1611,23 +1604,6 @@ def __init__(self, #Remove logger so gaussian process can be safely picked for multiprocessing on Windows self.log = None - - #--- FAKE NN METHODS START ---# - - - def create_neural_net(self): - ''' - Create the neural net. - - ''' - #TODO: Do. - gp_kernel = skk.RBF(length_scale=self.length_scale) + skk.WhiteKernel(noise_level=self.noise_level) - - if self.update_hyperparameters: - self.gaussian_process = skg.GaussianProcessRegressor(kernel=gp_kernel,n_restarts_optimizer=self.hyperparameter_searches) - else: - self.gaussian_process = skg.GaussianProcessRegressor(kernel=gp_kernel,optimizer=None) - def fit_neural_net(self): ''' Determine the appropriate number of layers for the NN given the data. @@ -1635,34 +1611,7 @@ def fit_neural_net(self): Fit the Neural Net with the appropriate topology to the data ''' - #TODO: Do. - self.log.debug('Fitting Gaussian process.') - if self.all_params.size==0 or self.all_costs.size==0 or self.all_uncers.size==0: - self.log.error('Asked to fit GP but no data is in all_costs, all_params or all_uncers.') - raise ValueError - - self.scaled_costs = self.cost_scaler.fit_transform(self.all_costs[:,np.newaxis])[:,0] - self.scaled_uncers = self.all_uncers * self.cost_scaler.scale_ - self.gaussian_process.alpha_ = self.scaled_uncers - self.gaussian_process.fit(self.all_params,self.scaled_costs) - - if self.update_hyperparameters: - - self.fit_count += 1 - self.gaussian_process.kernel = self.gaussian_process.kernel_ - - last_hyperparameters = self.gaussian_process.kernel.get_params() - - if self.cost_has_noise: - self.length_scale = last_hyperparameters['k1__length_scale'] - if isinstance(self.length_scale, float): - self.length_scale = np.array([self.length_scale]) - self.length_scale_history.append(self.length_scale) - self.noise_level = last_hyperparameters['k2__noise_level'] - self.noise_level_history.append(self.noise_level) - else: - self.length_scale = last_hyperparameters['length_scale'] - self.length_scale_history.append(self.length_scale) + self.neural_net_impl.fit_neural_net(self.all_params, self.all_costs) def predict_cost(self,params): ''' @@ -1671,11 +1620,7 @@ def predict_cost(self,params): Returns: float : Predicted cost at paramters ''' - #TODO: Do. - return self.gaussian_process.predict(params[np.newaxis,:]) - - #--- FAKE NN METHODS END ---# - + return self.neural_net_impl.predict_cost(params) def wait_for_new_params_event(self): ''' diff --git a/mloop/nnlearner.py b/mloop/nnlearner.py new file mode 100644 index 0000000..bbf5a76 --- /dev/null +++ b/mloop/nnlearner.py @@ -0,0 +1,116 @@ +import logging +import math +import tensorflow as tf +import numpy as np + +class NeuralNetImpl(): + ''' + Neural network implementation. + + Args: + num_params (int): The number of params. + + + Attributes: + TODO + ''' + + def __init__(self, + num_params = None): + + self.log = logging.getLogger(__name__) + if num_params is None: + self.log.error("num_params must be provided") + raise ValueError + self.num_params = num_params + + self.tf_session = tf.InteractiveSession() + + # Initial hyperparameters + self.num_layers = 1 + self.layer_dim = 128 + self.train_epochs = 300 + self.batch_size = 64 + + # Inputs + self.input_placeholder = tf.placeholder(tf.float32, shape=[None, self.num_params]) + self.output_placeholder = tf.placeholder(tf.float32, shape=[None, 1]) + self.keep_prob = tf.placeholder_with_default(1., shape=[]) + self.regularisation_coefficient = tf.placeholder_with_default(0., shape=[]) + + self._create_neural_net() + + def _create_neural_net(self): + ''' + Creates the neural net with topology specified by the current hyperparameters. + + ''' + # Forget about any old weights/biases + self.weights = [] + self.biases = [] + + # Input + internal nodes + # TODO: Use length scale for setting initial weights? + prev_layer_dim = self.num_params + prev_h = self.input_placeholder + for dim in [self.layer_dim] * self.num_layers: + self.weights.append(tf.Variable(tf.random_normal([prev_layer_dim, dim], stddev=0.1))) + self.biases.append(tf.Variable(tf.random_normal([dim]))) + prev_layer_dim = dim + prev_h = tf.nn.dropout( + tf.nn.sigmoid(tf.matmul(prev_h, self.weights[-1]) + self.biases[-1]), + keep_prob=self.keep_prob) + + # Output node + self.weights.append(tf.Variable(tf.random_normal([prev_layer_dim, 1]))) + self.biases.append(tf.Variable(tf.random_normal([1]))) + self.output_var = tf.matmul(prev_h, self.weights[-1]) + self.biases[-1] + + # Loss function and training + loss_func = ( + tf.reduce_mean(tf.reduce_sum(tf.square(self.output_var - self.output_placeholder), + reduction_indices=[1])) + + self.regularisation_coefficient * sum([tf.nn.l2_loss(W) for W in self.weights])) + self.train_step = tf.train.AdamOptimizer().minimize(loss_func) + + self.tf_session.run(tf.initialize_all_variables()) + + def fit_neural_net(self, all_params, all_costs): + ''' + Determine the appropriate number of layers for the NN given the data. + + Fit the Neural Net with the appropriate topology to the data + + Args: + all_params (array): array of all parameter arrays + all_costs (array): array of costs (associated with the corresponding parameters) + ''' + self.log.debug('Fitting neural network') + if len(all_params) == 0: + self.log.error('No data provided.') + raise ValueError + if not len(all_params) == len(all_costs): + self.log.error("Params and costs must have the same length") + raise ValueError + + # TODO: Fit hyperparameters. + + for i in range(self.train_epochs): + # Split the data into random batches, and train on each batch + all_indices = np.random.permutation(len(all_params)) + for j in range(math.ceil(len(all_params) / self.batch_size)): + batch_indices = all_indices[j * self.batch_size : (j + 1) * self.batch_size] + batch_input = [all_params[index] for index in batch_indices] + batch_output = [[all_costs[index]] for index in batch_indices] + self.tf_session.run(self.train_step, + feed_dict={self.input_placeholder: batch_input, + self.output_placeholder: batch_output}) + + def predict_cost(self,params): + ''' + Produces a prediction of cost from the neural net at params. + + Returns: + float : Predicted cost at parameters + ''' + return self.tf_session.run(self.output_var, feed_dict={self.input_placeholder: [params]})[0][0]