/* * Artificial Intelligence for Humans * Volume 3: Deep Learning and Neural Networks * Java Version * http://www.aifh.org * http://www.jeffheaton.com * * Code repository: * https://github.com/jeffheaton/aifh * * Copyright 2014-2015 by Jeff Heaton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package com.heatonresearch.aifh.ann; import com.heatonresearch.aifh.AIFHError; import com.heatonresearch.aifh.ann.randomize.XaiverRandomizeNetwork; import com.heatonresearch.aifh.learning.ClassificationAlgorithm; import com.heatonresearch.aifh.learning.RegressionAlgorithm; import com.heatonresearch.aifh.util.ArrayUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * The base class for most feedforward networks in this book. This includes deep and convolutional networks. * * Layers must be added to the neural network and then a call to finalizeStructure makes the network ready for use. * After the call to finalizeStructure, layers can no longer be added to the NN. */ public class BasicNetwork implements RegressionAlgorithm, ClassificationAlgorithm { /** * The number of input neurons in this network. */ private int inputCount; /** * The outputs from each of the neurons. */ private double[] layerOutput; /** * The sum of the layer, before the activation function is applied, producing the layerOutput. */ private double[] layerSums; /** * The number of output neurons in this network. */ private int outputCount; /** * The weights for a neural network. */ private double[] weights; /** * The layers of the network. */ private final List<Layer> layers = new ArrayList<>(); /** * True if the network is in training mode. Some layers act differently while training (i.e. dropout). */ private boolean networkTraining; /** * Default constructor. */ public BasicNetwork() { } /** * Calculate the output for the given input. * * @param input * The input. * @param output * Output will be placed here. */ public void compute(final double[] input, final double[] output) { clearOutput(); final int sourceIndex = getNeuronCount() - this.layers.get(0).getTotalCount(); System.arraycopy(input, 0, this.layerOutput, sourceIndex, this.inputCount); for (int i = 1; i<this.layers.size(); i++) { this.layers.get(i).computeLayer(); } System.arraycopy(this.layerOutput, 0, output, 0, this.outputCount); } /** * @return The total number of neurons in the neural network. */ public int getNeuronCount() { int result = 0; for(Layer layer: this.layers) { result+=layer.getTotalCount(); } return result; } /** * @return The length of the array the network would encode to. */ public int getEncodeLength() { return this.weights.length; } /** * @return The number of input neurons. */ public int getInputCount() { return this.inputCount; } /** * @return The output for each layer. */ public double[] getLayerOutput() { return this.layerOutput; } /** * @return The number of output neurons. */ public int getOutputCount() { return this.outputCount; } /** * @return The index of each layer in the weight and threshold array. */ public double[] getWeights() { return this.weights; } /** * Set the input count. * @param inputCount The input count. */ public void setInputCount(final int inputCount) { this.inputCount = inputCount; } /** * Set the output count. * @param outputCount The output count. */ public void setOutputCount(final int outputCount) { this.outputCount = outputCount; } /** * @return the layerSums */ public double[] getLayerSums() { return this.layerSums; } /** * Get the weight between the two layers. * @param fromLayer The from layer. * @param fromNeuron The from neuron. * @param toNeuron The to neuron. * @return The weight value. */ public double getWeight(final int fromLayer, final int fromNeuron, final int toNeuron) { validateNeuron(fromLayer, fromNeuron); validateNeuron(fromLayer + 1, toNeuron); final int fromLayerNumber = this.layers.size() - fromLayer - 1; final int toLayerNumber = fromLayerNumber - 1; if (toLayerNumber < 0) { throw new AIFHError( "The specified layer is not connected to another layer: " + fromLayer); } final int weightBaseIndex = this.layers.get(fromLayer+1).getWeightIndex(); final int count = this.layers.get(fromLayer).getTotalCount(); final int weightIndex = weightBaseIndex + fromNeuron + (toNeuron * count); return this.weights[weightIndex]; } /** * Validate the the specified targetLayer and neuron are valid. * @param targetLayer The target layer. * @param neuron The target neuron. */ public void validateNeuron(final int targetLayer, final int neuron) { if ((targetLayer < 0) || (targetLayer >= this.layers.size())) { throw new AIFHError("Invalid layer count: " + targetLayer); } if ((neuron < 0) || (neuron >= getLayerTotalNeuronCount(targetLayer))) { throw new AIFHError("Invalid neuron number: " + neuron); } } /** * Get the total (including bias and context) neuron cont for a layer. * @param l The layer. * @return The count. */ public int getLayerTotalNeuronCount(final int l) { return this.layers.get(l).getTotalCount(); } /** * Add a layer to the neural network. * @param layer The layer to be added to the neural network. */ public void addLayer(Layer layer) { this.layers.add(layer); } /** * Finalize the structure of the neural network. This must be called before any training or calculation can * be performed. After this method is called layers can no longer be added to the neural network. */ public void finalizeStructure() { final int layerCount = this.layers.size(); this.inputCount = this.layers.get(0).getCount(); this.outputCount = this.layers.get(layerCount - 1).getCount(); TempStructureCounts counts = new TempStructureCounts(); for (int i = this.layers.size() - 1; i >= 0; i--) { final Layer layer = this.layers.get(i); layer.finalizeStructure(this, i, counts); } this.weights = new double[counts.getWeightCount()]; this.layerOutput = new double[counts.getNeuronCount()]; this.layerSums = new double[counts.getNeuronCount()]; clearOutput(); } /** * Clear the outputs of each layer. */ public void clearOutput() { // Clear all outputs to 0 for(int i=0;i<this.layerOutput.length;i++) { this.layerOutput[i] = 0.0; this.layerSums[i] = 0.0; } // Init the output arrays by filling in bias values int index = 0; for (int i = 0; i < this.layers.size(); i++) { Layer layer = this.layers.get(this.layers.size()-1-i); index += layer.getCount(); if (layer.hasBias()) { this.layerOutput[index++] = 1.0; } } } /** * Set the weight between the two specified neurons. The bias neuron is always * the last neuron on a layer. * @param fromLayer The from layer. * @param fromNeuron The from neuron. * @param toNeuron The to neuron. * @param value The to value. */ public void setWeight(final int fromLayer, final int fromNeuron, final int toNeuron, final double value) { final int fromLayerNumber = this.layers.size() - fromLayer - 1; final int toLayerNumber = fromLayerNumber - 1; if (toLayerNumber < 0) { throw new AIFHError( "The specified layer is not connected to another layer: " + fromLayer); } final int weightBaseIndex = this.layers.get(fromLayer+1).getWeightIndex(); final int count = this.layers.get(fromLayer).getTotalCount(); final int weightIndex = weightBaseIndex + fromNeuron + (toNeuron * count); getWeights()[weightIndex] = value; } /** * Randomize the neural network. */ public void reset() { XaiverRandomizeNetwork random = new XaiverRandomizeNetwork(); random.randomize(this); } /** * @return The layers of the neural network. */ public List<Layer> getLayers() { return this.layers; } /** * Compute the output for the specified input. * * @param input The input. * @return The regression output. */ @Override public double[] computeRegression(double[] input) { if( input.length!=getInputCount()) { throw new AIFHError("Invalid input count("+ input.length+"), this network is designed for: " + getInputCount()); } double[] output = new double[getOutputCount()]; compute(input,output); return output; } /** * @return The long term memory for the algorithm. This is usually weights or other coefficients. */ @Override public double[] getLongTermMemory() { return this.weights; } /** * Find the next layer in a neural network, given a layer. * @param layer The reference layer. * @return The next layer in the neural network. */ public Layer getNextLayer(Layer layer) { int idx = this.layers.indexOf(layer); if( idx==-1 ) { throw new AIFHError("Can't find next layer for a layer that is not part of this network."); } if( idx>=this.layers.size() ) { throw new AIFHError("Can't find the next layer for the final layer in a network."); } return this.layers.get(idx+1); } /** * Find the previous layer in a neural network, given a layer. * @param layer The reference layer. * @return The previous layer in the neural network. */ public Layer getPreviousLayer(Layer layer) { int idx = this.layers.indexOf(layer); if( idx==-1 ) { throw new AIFHError("Can't find previous layer for a layer that is not part of this network."); } if( idx==0 ) { throw new AIFHError("Can't find the previous layer for the final layer in a network."); } return this.layers.get(idx-1); } /** * Classify the specified input into a group. * * @param input The input data. * @return The group the data was classified into. */ @Override public int computeClassification(double[] input) { return ArrayUtil.indexOfLargest(computeRegression(input)); } /** * @return True, if the neural network is training. Some layers (e.g. dropout) behave differently. */ public boolean isNetworkTraining() { return this.networkTraining; } /** * Determine if the neural network is training. * @param networkTraining True, if the neural network is training. */ public void setNetworkTraining(boolean networkTraining) { this.networkTraining = networkTraining; } }