Diff of /src/LiviaNet/LiviaNet.py [000000] .. [e9ece0]

Switch to side-by-side view

--- a
+++ b/src/LiviaNet/LiviaNet.py
@@ -0,0 +1,777 @@
+""" 
+Copyright (c) 2016, Jose Dolz .All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    OTHER DEALINGS IN THE SOFTWARE.
+
+NOTES: There are still some functionalities to be implemented.
+
+    - Add pooling layer in 3D
+    - Add more activation functions
+    - Add more optimizers (ex. Adam)
+
+Jose Dolz. Dec, 2016.
+email: jose.dolz.upv@gmail.com
+LIVIA Department, ETS, Montreal.
+"""
+
+import numpy
+import numpy as np
+
+import theano
+import theano.tensor as T
+from theano.tensor.nnet import conv
+import random
+from math import floor
+from math import ceil
+
+from Modules.General.Utils import computeReceptiveField
+from Modules.General.Utils import extendLearningRateToParams
+from Modules.General.Utils import extractCenterFeatMaps
+from Modules.General.Utils import getCentralVoxels
+from Modules.General.Utils import getWeightsSet
+
+import LiviaNet3DConvLayer
+import LiviaSoftmax
+import pdb
+    
+#####################################################
+# ------------------------------------------------- #
+##  ##  ##  ##  ##   LIVIANET 3D   ##  ##  ##  ##  ##
+# ------------------------------------------------- #
+#####################################################
+
+
+class LiviaNet3D(object):
+    def __init__(self):
+
+        # --- containers for Theano compiled functions ----
+        self.networkModel_Train = ""
+        self.networkModel_Test = ""
+        
+        # --- shared variables will be stored in the following variables ----
+        self.trainingData_x = ""
+        self.testingData_x = ""
+        self.trainingData_y = ""
+
+        self.lastLayer = ""
+        self.networkLayers = []
+        self.intermediate_ConnectedLayers = []
+             
+        self.networkName = ""
+        self.folderName = ""
+        self.cnnLayers = []
+        self.n_classes = -1
+
+        self.sampleSize_Train = []
+        self.sampleSize_Test = []
+        self.kernel_Shapes = []
+
+        self.pooling_scales = []
+        self.dropout_Rates = []
+        self.activationType = -1
+        self.weight_Initialization = -1
+        self.dropoutRates = []
+        self.batch_Size = -1
+        self.receptiveField = 0
+        
+        self.initialLearningRate = "" 
+        self.learning_rate = theano.shared(np.cast["float32"](0.01))
+
+        # Symbolic variables,
+        self.inputNetwork_Train = None  
+        self.inputNetwork_Test = None
+
+        self.L1_reg_C = 0
+        self.L2_reg_C = 0
+        self.costFunction = 0
+        
+        # Params for optimizers
+        self.initialMomentum = "" 
+        self.momentum = theano.shared(np.cast["float32"](0.))
+        self.momentumNormalized = 0
+        self.momentumType = 0
+        self.vel_Momentum = [] 
+        self.rho_RMSProp = 0
+        self.epsilon_RMSProp = 0
+        self.params_RmsProp = [] 
+        self.numberOfEpochsTrained = 0
+        self.applyBatchNorm = ""
+        self.numberEpochToApplyBatchNorm = 0
+        self.softmax_Temp = 1.0
+
+        self.centralVoxelsTrain = ""
+        self.centralVoxelsTest = ""
+        
+    # -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+
+    """ ####### Function to generate the network architecture  ######### """
+    def generateNetworkLayers(self,
+                            cnnLayers,
+                            kernel_Shapes,
+                            maxPooling_Layer,
+                            sampleShape_Train,
+                            sampleShape_Test,
+                            inputSample_Train,
+                            inputSample_Test,
+                            layersToConnect):
+
+        rng = np.random.RandomState(24575)
+     
+        # Define inputs for first layers (which will be re-used for next layers)
+        inputSampleToNextLayer_Train = inputSample_Train
+        inputSampleToNextLayer_Test = inputSample_Test
+        inputSampleToNextLayerShape_Train = sampleShape_Train
+        inputSampleToNextLayerShape_Test = sampleShape_Test
+
+        # Get the convolutional layers
+        numLayers = len(kernel_Shapes)
+        numberCNNLayers = []
+        numberFCLayers = []
+        for l_i in range(1,len(kernel_Shapes)):
+            if len(kernel_Shapes[l_i]) == 3:
+                numberCNNLayers = l_i + 1
+
+        numberFCLayers = numLayers - numberCNNLayers
+        
+        ######### -------------- Generate the convolutional layers --------------   #########
+        # Some checks
+        if self.weight_Initialization_CNN == 2:
+            if len(self.weightsTrainedIdx) <> numberCNNLayers:
+                print(" ... WARNING!!!! Number of indexes specified for trained layers does not correspond with number of conv layers in the created architecture...")
+
+        if self.weight_Initialization_CNN == 2:
+            weightsNames = getWeightsSet(self.weightsFolderName, self.weightsTrainedIdx)
+            
+        for l_i in xrange(0, numberCNNLayers) :
+            
+            # Get properties of this layer
+            # The second element is the number of feature maps of previous layer
+            currentLayerKernelShape = [cnnLayers[l_i], inputSampleToNextLayerShape_Train[1]] +  kernel_Shapes[l_i] 
+
+            # If weights are going to be initialized from other pre-trained network they should be loaded in this stage
+            # Otherwise
+            weights = []
+            if self.weight_Initialization_CNN == 2:
+                weights = np.load(weightsNames[l_i])
+
+            maxPoolingParameters = []
+            dropoutRate = 0.0
+            myLiviaNet3DConvLayer = LiviaNet3DConvLayer.LiviaNet3DConvLayer(rng,
+                                                                            l_i,
+                                                                            inputSampleToNextLayer_Train,
+                                                                            inputSampleToNextLayer_Test,
+                                                                            inputSampleToNextLayerShape_Train,
+                                                                            inputSampleToNextLayerShape_Test,
+                                                                            currentLayerKernelShape,
+                                                                            self.applyBatchNorm,
+                                                                            self.numberEpochToApplyBatchNorm,
+                                                                            maxPoolingParameters,
+                                                                            self.weight_Initialization_CNN,
+                                                                            weights,
+                                                                            self.activationType,
+                                                                            dropoutRate
+                                                                            )
+                                                                            
+            self.networkLayers.append(myLiviaNet3DConvLayer)
+            
+            # Just for printing
+            inputSampleToNextLayer_Train_Old = inputSampleToNextLayerShape_Train
+            inputSampleToNextLayer_Test_Old  = inputSampleToNextLayerShape_Test
+            
+            # Update inputs for next layer
+            inputSampleToNextLayer_Train = myLiviaNet3DConvLayer.outputTrain
+            inputSampleToNextLayer_Test  = myLiviaNet3DConvLayer.outputTest    
+    
+            inputSampleToNextLayerShape_Train = myLiviaNet3DConvLayer.outputShapeTrain
+            inputSampleToNextLayerShape_Test  = myLiviaNet3DConvLayer.outputShapeTest
+            
+            print(" ----- (Training) Input shape: {}  ---> Output shape: {}  ||  kernel shape {}".format(inputSampleToNextLayer_Train_Old,inputSampleToNextLayerShape_Train, currentLayerKernelShape))
+            print(" ----- (Testing) Input shape: {}   ---> Output shape: {}".format(inputSampleToNextLayer_Test_Old,inputSampleToNextLayerShape_Test))
+
+        ######### -------------- Create the intermediate (i.e. multi-scale) connections from conv layers to FCN ----------------- ##################
+        featMapsInFullyCN = inputSampleToNextLayerShape_Train[1]
+
+        [featMapsInFullyCN, 
+        inputToFullyCN_Train,
+        inputToFullyCN_Test] = self.connectIntermediateLayers(layersToConnect,
+                                                              inputSampleToNextLayer_Train,
+                                                              inputSampleToNextLayer_Test,
+                                                              featMapsInFullyCN)
+        
+        
+        ######### --------------  Generate the Fully Connected Layers  ----------------- ##################
+
+        # Define inputs
+        inputFullyCNShape_Train = [self.batch_Size, featMapsInFullyCN] + inputSampleToNextLayerShape_Train[2:5]
+        inputFullyCNShape_Test = [self.batch_Size, featMapsInFullyCN] + inputSampleToNextLayerShape_Test[2:5]
+
+        # Kamnitsas applied padding and mirroring to the images when kernels in FC layers were larger than 1x1x1.
+        # For this current work, we employed kernels of this size (i.e. 1x1x1), so there is no need to apply padding or mirroring.
+        # TODO. Check
+        
+        print(" --- Starting to create the fully connected layers....")
+        for l_i in xrange(numberCNNLayers, numLayers) :
+            numberOfKernels = cnnLayers[l_i]
+            kernel_shape = [kernel_Shapes[l_i][0],kernel_Shapes[l_i][0],kernel_Shapes[l_i][0]]
+            
+            currentLayerKernelShape = [cnnLayers[l_i], inputFullyCNShape_Train[1]] +  kernel_shape
+            
+            # If weights are going to be initialized from other pre-trained network they should be loaded in this stage
+            # Otherwise
+
+            weights = []
+            applyBatchNorm = True
+            epochsToApplyBatchNorm = 60
+            maxPoolingParameters = []
+            dropoutRate = self.dropout_Rates[l_i-numberCNNLayers]
+            
+            myLiviaNet3DFullyConnectedLayer = LiviaNet3DConvLayer.LiviaNet3DConvLayer(rng,
+                                                                            l_i,
+                                                                            inputToFullyCN_Train,
+                                                                            inputToFullyCN_Test,
+                                                                            inputFullyCNShape_Train,
+                                                                            inputFullyCNShape_Test,
+                                                                            currentLayerKernelShape,
+                                                                            self.applyBatchNorm,
+                                                                            self.numberEpochToApplyBatchNorm,
+                                                                            maxPoolingParameters,
+                                                                            self.weight_Initialization_FCN,
+                                                                            weights,
+                                                                            self.activationType,
+                                                                            dropoutRate
+                                                                            )
+
+            self.networkLayers.append(myLiviaNet3DFullyConnectedLayer)
+            
+            # Just for printing
+            inputFullyCNShape_Train_Old = inputFullyCNShape_Train
+            inputFullyCNShape_Test_Old  = inputFullyCNShape_Test
+            
+            # Update inputs for next layer
+            inputToFullyCN_Train = myLiviaNet3DFullyConnectedLayer.outputTrain
+            inputToFullyCN_Test = myLiviaNet3DFullyConnectedLayer.outputTest    
+
+            inputFullyCNShape_Train = myLiviaNet3DFullyConnectedLayer.outputShapeTrain
+            inputFullyCNShape_Test = myLiviaNet3DFullyConnectedLayer.outputShapeTest
+ 
+            # Print
+            print(" ----- (Training) Input shape: {}  ---> Output shape: {}  ||  kernel shape {}".format(inputFullyCNShape_Train_Old,inputFullyCNShape_Train, currentLayerKernelShape))
+            print(" ----- (Testing) Input shape: {}   ---> Output shape: {}".format(inputFullyCNShape_Test_Old,inputFullyCNShape_Test))
+        
+        
+        ######### -------------- Do Classification layer  ----------------- ##################
+
+        # Define kernel shape for classification layer
+        featMaps_LastLayer = self.cnnLayers[-1]
+        filterShape_ClassificationLayer = [self.n_classes, featMaps_LastLayer, 1, 1, 1]
+
+        # Define inputs and shapes for the classification layer
+        inputImageClassificationLayer_Train = inputToFullyCN_Train
+        inputImageClassificationLayer_Test = inputToFullyCN_Test
+
+        inputImageClassificationLayerShape_Train = inputFullyCNShape_Train
+        inputImageClassificationLayerShape_Test = inputFullyCNShape_Test
+        
+        print(" ----- (Classification layer) kernel shape {}".format(filterShape_ClassificationLayer))
+        classification_layer_Index = l_i
+
+        weights = []
+        applyBatchNorm = True
+        epochsToApplyBatchNorm = 60
+        maxPoolingParameters = []
+        dropoutRate = self.dropout_Rates[len(self.dropout_Rates)-1]
+        softmaxTemperature = 1.0
+                                                                              
+        myLiviaNet_ClassificationLayer = LiviaSoftmax.LiviaSoftmax(rng,
+                                                                   classification_layer_Index,
+                                                                   inputImageClassificationLayer_Train,
+                                                                   inputImageClassificationLayer_Test,
+                                                                   inputImageClassificationLayerShape_Train,
+                                                                   inputImageClassificationLayerShape_Test,
+                                                                   filterShape_ClassificationLayer,
+                                                                   self.applyBatchNorm,
+                                                                   self.numberEpochToApplyBatchNorm,
+                                                                   maxPoolingParameters,
+                                                                   self.weight_Initialization_FCN,
+                                                                   weights,
+                                                                   0, #self.activationType,
+                                                                   dropoutRate,
+                                                                   softmaxTemperature
+                                                                   )
+                       
+        self.networkLayers.append(myLiviaNet_ClassificationLayer)
+        self.lastLayer = myLiviaNet_ClassificationLayer
+        
+        print(" ----- (Training) Input shape: {}  ---> Output shape: {}  ||  kernel shape {}".format(inputImageClassificationLayerShape_Train,myLiviaNet_ClassificationLayer.outputShapeTrain, filterShape_ClassificationLayer))
+        print(" ----- (Testing) Input shape:  {}  ---> Output shape: {}".format(inputImageClassificationLayerShape_Test,myLiviaNet_ClassificationLayer.outputShapeTest))
+        
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+
+                        
+    def updateLayersMatricesBatchNorm(self):
+        for l_i in xrange(0, len(self.networkLayers) ) :
+            self.networkLayers[l_i].updateLayerMatricesBatchNorm()
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+   
+    """ Function that connects intermediate layers to the input of the first fully connected layer 
+        This is done for multi-scale features """    
+    def connectIntermediateLayers(self,
+                                  layersToConnect,
+                                  inputSampleInFullyCN_Train,
+                                  inputSampleInFullyCN_Test,
+                                  featMapsInFullyCN):
+
+        centralVoxelsTrain = self.centralVoxelsTrain
+        centralVoxelsTest = self.centralVoxelsTest
+    
+        for l_i in layersToConnect :
+            currentLayer = self.networkLayers[l_i]
+            output_train = currentLayer.outputTrain
+            output_trainShape = currentLayer.outputShapeTrain
+            output_test = currentLayer.outputTest
+            output_testShape = currentLayer.outputShapeTest
+
+            # Get the middle part of feature maps at intermediate levels to make them of the same shape at the beginning of the
+            # first fully connected layer
+            featMapsCenter_Train = extractCenterFeatMaps(output_train, output_trainShape, centralVoxelsTrain)
+            featMapsCenter_Test  = extractCenterFeatMaps(output_test, output_testShape, centralVoxelsTest)
+
+            featMapsInFullyCN = featMapsInFullyCN + currentLayer._numberOfFeatureMaps
+            inputSampleInFullyCN_Train = T.concatenate([inputSampleInFullyCN_Train, featMapsCenter_Train], axis=1)
+            inputSampleInFullyCN_Test = T.concatenate([inputSampleInFullyCN_Test, featMapsCenter_Test], axis=1)
+
+        return [featMapsInFullyCN, inputSampleInFullyCN_Train, inputSampleInFullyCN_Test]
+    
+    
+    #############   Functions for OPTIMIZERS ################# 
+
+    def getUpdatesOfTrainableParameters(self, cost, paramsTraining, numberParamsPerLayer) :
+        # Optimizers
+        def SGD():
+            print (" --- Optimizer: Stochastic gradient descent (SGD)")
+            updates = self.updateParams_SGD(cost, paramsTraining, numberParamsPerLayer)
+            return updates
+        def RMSProp():
+            print (" --- Optimizer: RMS Prop")
+            updates = self.updateParams_RMSProp(cost, paramsTraining, numberParamsPerLayer)
+            return updates
+       
+        # TODO. Include more optimizers here
+        optionsOptimizer = {0 : SGD,
+                            1 : RMSProp}
+
+        updates = optionsOptimizer[self.optimizerType]()
+        
+        return updates
+
+    """ # Optimizers:
+    # More optimizers in : https://github.com/Lasagne/Lasagne/blob/master/lasagne/updates.py """
+    # ========= Update the trainable parameters using Stocastic Gradient Descent ===============
+    def updateParams_SGD(self, cost, paramsTraining, numberParamsPerLayer) :
+        # Create a list of gradients for all model parameters
+        grads = T.grad(cost, paramsTraining)
+
+        # Get learning rates for each param
+        #learning_rates = extendLearningRateToParams(numberParamsPerLayer,self.learning_rate)
+        
+        self.vel_Momentum = []
+        updates = []
+        
+        constantForCurrentGradientUpdate = 1.0 - self.momentum*self.momentumNormalized 
+
+        #for param, grad, lrate  in zip(paramsTraining, grads, learning_rates) :
+        for param, grad  in zip(paramsTraining, grads) :
+            v = theano.shared(param.get_value()*0., broadcastable=param.broadcastable)
+            self.vel_Momentum.append(v)
+
+            stepToGradientDirection = constantForCurrentGradientUpdate*self.learning_rate*grad
+            newVel = self.momentum * v - stepToGradientDirection
+            
+            if self.momentumType == 0 : 
+                updateToParam = newVel
+            else : 
+                updateToParam = self.momentum*newVel - stepToGradientDirection
+                
+            updates.append((v, newVel)) 
+            updates.append((param, param + updateToParam))
+            
+        return updates
+        
+    # ========= Update the trainable parameters using RMSProp ===============
+    def updateParams_RMSProp(self, cost, paramsTraining, numberParamsPerLayer) : 
+        # Original code: https://gist.github.com/Newmu/acb738767acb4788bac3
+        # epsilon=1e-4 in paper.
+        # Kamnitsas reported NaN values in cost function when employing this value.
+        # Worked ok with epsilon=1e-6.
+
+        grads = T.grad(cost, paramsTraining)
+
+        # Get learning rates for each param
+        #learning_rates = extendLearningRateToParams(numberParamsPerLayer,self.learning_rate)
+
+        self.params_RmsProp = []
+        self.vel_Momentum = []
+        updates = []
+        
+        constantForCurrentGradientUpdate = 1.0 - self.momentum*self.momentumNormalized 
+        
+        # Using theano constant to prevent upcasting of float32
+        one = T.constant(1)
+
+        for param, grad in zip(paramsTraining, grads):
+            accu = theano.shared(param.get_value()*0., broadcastable=param.broadcastable)
+            self.params_RmsProp.append(accu) 
+            
+            v = theano.shared(param.get_value()*0., broadcastable=param.broadcastable) 
+            
+            self.vel_Momentum.append(v)
+
+            accu_new = self.rho_RMSProp * accu + (one - self.rho_RMSProp) * T.sqr(grad)
+
+            numGradStep = self.learning_rate * grad
+            denGradStep = T.sqrt(accu_new + self.epsilon_RMSProp)
+            
+            stepToGradientDirection = constantForCurrentGradientUpdate*(numGradStep /denGradStep) 
+            
+            newVel = self.momentum * v - stepToGradientDirection
+            
+            if self.momentumType == 0 : 
+                updateToParam = newVel
+            else : 
+                updateToParam = self.momentum*newVel - stepToGradientDirection
+               
+            updates.append((accu, accu_new))
+            updates.append((v, newVel)) 
+            updates.append((param, param + updateToParam))
+            
+        return updates
+        
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+        
+    """ ------ Get trainable parameters --------- """
+    def getTrainable_Params(_self):
+        trainable_Params = []
+        numberTrain_ParamsLayer = [] 
+        for l_i in xrange(0, len(_self.networkLayers) ) :
+            trainable_Params = trainable_Params + _self.networkLayers[l_i].params
+            numberTrain_ParamsLayer.append(_self.networkLayers[l_i].numberOfTrainableParams) # TODO: Get this directly as len(_self.networkLayers[l_i].params)
+            
+        return trainable_Params,numberTrain_ParamsLayer
+
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+    
+    def initTrainingParameters(self,
+                               costFunction,
+                               L1_reg_C,
+                               L2_reg_C,
+                               learning_rate,
+                               momentumType,
+                               momentumValue,
+                               momentumNormalized,
+                               optimizerType,
+                               rho_RMSProp,
+                               epsilon_RMSProp
+                               ) :
+
+        print(" ------- Initializing network training parameters...........")
+        self.numberOfEpochsTrained = 0
+
+        self.L1_reg_C = L1_reg_C
+        self.L2_reg_C = L2_reg_C
+
+        # Set Learning rate and store the last epoch where it was modified
+        self.initialLearningRate = learning_rate
+
+        # TODO: Check the shared variables from learning rates
+        self.learning_rate.set_value(self.initialLearningRate[0])          
+        
+
+        # Set momentum type and values
+        self.momentumType = momentumType
+        self.initialMomentumValue = momentumValue
+        self.momentumNormalized = momentumNormalized
+        self.momentum.set_value(self.initialMomentumValue)
+        
+        # Optimizers
+        if (optimizerType == 2):
+            optimizerType = 1
+            
+        def SGD():
+            print (" --- Optimizer: Stochastic gradient descent (SGD)")
+            self.optimizerType = optimizerType
+
+        def RMSProp():
+            print (" --- Optimizer: RMS Prop")
+            self.optimizerType = optimizerType
+            self.rho_RMSProp = rho_RMSProp
+            self.epsilon_RMSProp = epsilon_RMSProp
+       
+        # TODO. Include more optimizers here
+        optionsOptimizer = {0 : SGD,
+                            1 : RMSProp}
+        
+        optionsOptimizer[optimizerType]()
+                              
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+        
+    def updateParams_BatchNorm(self) : 
+        updatesForBnRollingAverage = []
+        for l_i in xrange(0, len(self.networkLayers) ) :
+            currentLayer = self.networkLayers[l_i]
+            updatesForBnRollingAverage.extend( currentLayer.getUpdatesForBnRollingAverage() ) 
+        return updatesForBnRollingAverage
+
+    # ------------------------------------------------------------------------------------ #
+    # ---------------------------     Compile the Theano functions     ------------------- #
+    # ------------------------------------------------------------------------------------ #
+    def compileTheanoFunctions(self):
+        print(" ----------------- Starting compilation process ----------------- ")        
+        
+        # ------- Create and initialize sharedVariables needed to compile the training function ------ #
+        # -------------------------------------------------------------------------------------------- #
+        # For training 
+        self.trainingData_x = theano.shared(np.zeros([1,1,1,1,1], dtype="float32"), borrow = True)
+        self.trainingData_y = theano.shared(np.zeros([1,1,1,1], dtype="float32") , borrow = True)  
+        
+        # For testing 
+        self.testingData_x = theano.shared(np.zeros([1,1,1,1,1], dtype="float32"), borrow = True)
+        
+        x_Train = self.inputNetwork_Train
+        x_Test  = self.inputNetwork_Test
+        y_Train = T.itensor4('y')
+        
+        # Allocate symbolic variables for the data
+        index_Train = T.lscalar()
+        index_Test  = T.lscalar()
+        
+        # ------- Needed to compile the training function ------ #
+        # ------------------------------------------------------ #
+        trainingData_y_CastedToInt   = T.cast( self.trainingData_y, 'int32') 
+        
+        # To accomodate the weights in the cost function to account for class imbalance
+        weightsOfClassesInCostFunction = T.fvector()  
+        weightPerClass = T.fvector() 
+        
+        # --------- Get trainable parameters (to be fit by gradient descent) ------- #
+        # -------------------------------------------------------------------------- #
+        
+        [paramsTraining, numberParamsPerLayer] = self.getTrainable_Params()
+        
+        # ------------------ Define the cost function --------------------- #
+        # ----------------------------------------------------------------- #
+        def negLogLikelihood():
+            print (" --- Cost function: negativeLogLikelihood")
+            
+            costInLastLayer = self.lastLayer.negativeLogLikelihoodWeighted(y_Train,weightPerClass)
+            return costInLastLayer
+            
+        def NotDefined():
+            print (" --- Cost function: Not defined!!!!!! WARNING!!!")
+
+        optionsCostFunction = {0 : negLogLikelihood,
+                               1 : NotDefined}
+
+        costInLastLayer = optionsCostFunction[self.costFunction]()
+        
+        # --------------------------- Get costs --------------------------- #
+        # ----------------------------------------------------------------- #
+        # Get L1 and L2 weights regularization
+        costL1 = 0
+        costL2 = 0
+        
+        # Compute the costs
+        for l_i in xrange(0, len(self.networkLayers)) :    
+                costL1 += abs(self.networkLayers[l_i].W).sum()
+                costL2 += (self.networkLayers[l_i].W ** 2).sum()
+        
+        # Add also the cost of the last layer                     
+        cost = (costInLastLayer
+                + self.L1_reg_C * costL1
+                + self.L2_reg_C * costL2)
+
+        # --------------------- Include all trainable parameters in updates (for optimization) ---------------------- #
+        # ----------------------------------------------------------------------------------------------------------- #
+        updates = self.getUpdatesOfTrainableParameters(cost, paramsTraining, numberParamsPerLayer)
+        
+        # --------------------- Include batch normalization params ---------------------- #
+        # ------------------------------------------------------------------------------- #
+        updates = updates + self.updateParams_BatchNorm()
+
+        # For the testing function we need to get the Feature maps activations
+        featMapsActivations = []
+        lower_act = 0
+        upper_act = 9999
+        
+        # TODO: Change to output_Test
+        for l_i in xrange(0,len(self.networkLayers)):
+            featMapsActivations.append(self.networkLayers[l_i].outputTest[:, lower_act : upper_act, :, :, :])
+
+        # For the last layer get the predicted probabilities (p_y_given_x_test)
+        featMapsActivations.append(self.lastLayer.p_y_given_x_test)
+
+        # --------------------- Preparing data to compile the functions ---------------------- #
+        # ------------------------------------------------------------------------------------ #
+        
+        givensDataSet_Train = { x_Train: self.trainingData_x[index_Train * self.batch_Size: (index_Train + 1) * self.batch_Size],
+                                y_Train: trainingData_y_CastedToInt[index_Train * self.batch_Size: (index_Train + 1) * self.batch_Size],
+                                weightPerClass: weightsOfClassesInCostFunction }
+
+       
+        givensDataSet_Test  = { x_Test: self.testingData_x[index_Test * self.batch_Size: (index_Test + 1) * self.batch_Size] }
+        
+        print(" ...Compiling the training function...")
+        
+        self.networkModel_Train = theano.function(
+                                    [index_Train, weightsOfClassesInCostFunction],
+                                    #[cost] + self.lastLayer.doEvaluation(y_Train),
+                                    [cost],
+                                    updates=updates,
+                                    givens = givensDataSet_Train
+                                    )
+                          
+        print(" ...The training function was compiled...")
+
+        #self.getProbabilities = theano.function(
+                         #[index],
+                         #self.lastLayer.p_y_given_x_Train,
+                         #givens={
+                            #x: self.trainingData_x[index * _self.batch_size: (index + 1) * _self.batch_size]
+                         #}
+         #)
+     
+
+        print(" ...Compiling the testing function...")
+        self.networkModel_Test = theano.function(
+                                  [index_Test],
+                                  featMapsActivations,
+                                  givens = givensDataSet_Test
+                                  )
+        print(" ...The testing function was compiled...")
+# -------------------------------------------------------------------- END Function ------------------------------------------------------------------- #
+
+####### Function to generate the CNN #########
+
+    def createNetwork(self,
+                      networkName, 
+                      folderName,
+                      cnnLayers,
+                      kernel_Shapes,
+                      intermediate_ConnectedLayers,
+                      n_classes,
+                      sampleSize_Train,
+                      sampleSize_Test,
+                      batch_Size,
+                      applyBatchNorm,
+                      numberEpochToApplyBatchNorm,
+                      activationType,
+                      dropout_Rates,
+                      pooling_Params,
+                      weights_Initialization_CNN,
+                      weights_Initialization_FCN,
+                      weightsFolderName,
+                      weightsTrainedIdx,
+                      softmax_Temp
+                      ):
+
+        # ============= Model Parameters Passed as arguments ================
+        # Assign parameters:
+        self.networkName = networkName
+        self.folderName = folderName
+        self.cnnLayers = cnnLayers
+        self.n_classes = n_classes
+        self.kernel_Shapes = kernel_Shapes
+        self.intermediate_ConnectedLayers = intermediate_ConnectedLayers
+        self.pooling_scales = pooling_Params
+        self.dropout_Rates = dropout_Rates
+        self.activationType = activationType
+        self.weight_Initialization_CNN = weights_Initialization_CNN
+        self.weight_Initialization_FCN = weights_Initialization_FCN
+        self.weightsFolderName = weightsFolderName
+        self.weightsTrainedIdx = weightsTrainedIdx
+        self.batch_Size = batch_Size
+        self.sampleSize_Train = sampleSize_Train
+        self.sampleSize_Test = sampleSize_Test
+        self.applyBatchNorm = applyBatchNorm
+        self.numberEpochToApplyBatchNorm = numberEpochToApplyBatchNorm
+        self.softmax_Temp = softmax_Temp
+
+        # Compute the CNN receptive field
+        stride = 1;
+        self.receptiveField = computeReceptiveField(self.kernel_Shapes, stride)
+
+        # --- Size of Image samples ---
+        self.sampleSize_Train = sampleSize_Train
+        self.sampleSize_Test = sampleSize_Test
+        
+        ## --- Batch Size ---
+        self.batch_Size = batch_Size
+
+        # ======== Calculated Attributes =========
+        self.centralVoxelsTrain = getCentralVoxels(self.sampleSize_Train, self.receptiveField) 
+        self.centralVoxelsTest = getCentralVoxels(self.sampleSize_Test, self.receptiveField) 
+        
+        #==============================
+        rng = numpy.random.RandomState(23455)
+
+        # Transfer to LIVIA NET
+        self.sampleSize_Train = sampleSize_Train
+        self.sampleSize_Test = sampleSize_Test
+        
+        # --------- Now we build the model -------- #
+
+        print("...[STATUS]: Building the Network model...")
+        
+        # Define the symbolic variables used as input of the CNN
+        # start-snippet-1
+        # Define tensor5
+        tensor5 = T.TensorType(dtype='float32', broadcastable=(False, False, False, False, False))
+        self.inputNetwork_Train = tensor5() 
+        self.inputNetwork_Test = tensor5()
+
+        # Define input shapes to the netwrok
+        inputSampleShape_Train = (self.batch_Size, 1, self.sampleSize_Train[0], self.sampleSize_Train[1], self.sampleSize_Train[2])
+        inputSampleShape_Test = (self.batch_Size, 1, self.sampleSize_Test[0], self.sampleSize_Test[1], self.sampleSize_Test[2])
+
+        print (" - Shape of input subvolume (Training): {}".format(inputSampleShape_Train))
+        print (" - Shape of input subvolume (Testing): {}".format(inputSampleShape_Test))
+
+        inputSample_Train = self.inputNetwork_Train
+        inputSample_Test = self.inputNetwork_Test
+
+        # TODO change cnnLayers name by networkLayers
+        self.generateNetworkLayers(cnnLayers,
+                                   kernel_Shapes,
+                                   self.pooling_scales,
+                                   inputSampleShape_Train,
+                                   inputSampleShape_Test,
+                                   inputSample_Train,
+                                   inputSample_Test,
+                                   intermediate_ConnectedLayers)      
+
+    # Release Data from GPU
+    def releaseGPUData(self) :
+        # GPU NOTE: Remove the input values to avoid copying data to the GPU
+        
+        # Image Data
+        self.trainingData_x.set_value(np.zeros([1,1,1,1,1], dtype="float32"))
+        self.testingData_x.set_value(np.zeros([1,1,1,1,1], dtype="float32"))
+
+        # Labels
+        self.trainingData_y.set_value(np.zeros([1,1,1,1], dtype="float32"))
+