/*
* 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.ann.activation.ActivationFunction;
import com.heatonresearch.aifh.ann.train.GradientCalc;
/**
* Base class for all layers (used with BasicNetwork) that have weights.
*/
public abstract class WeightedLayer implements Layer {
/**
* The layer index.
*/
private int layerIndex;
/**
* The network that owns this layer.
*/
private BasicNetwork owner;
/**
* The index to this layer's weights.
*/
private int weightIndex;
/**
* The index to this layer's neurons.
*/
private int neuronIndex;
/**
* The activation function.
*/
private ActivationFunction activation;
/**
* {@inheritDoc}
*/
@Override
public void finalizeStructure(BasicNetwork theOwner, int theLayerIndex, TempStructureCounts counts) {
this.owner = theOwner;
this.layerIndex = theLayerIndex;
Layer prevLayer = (this.layerIndex>0) ? this.owner.getLayers().get(this.layerIndex-1) : null;
Layer nextLayer = (this.layerIndex<this.owner.getLayers().size()-1) ? this.owner.getLayers().get(this.layerIndex+1) : null;
int tc = getTotalCount();
counts.addNeuronCount(tc);
if (prevLayer != null) {
counts.addWeightCount(getCount() * prevLayer.getTotalCount());
}
int weightIndex, layerIndex;
if (theLayerIndex == this.owner.getLayers().size()-1 ) {
weightIndex = 0;
layerIndex = 0;
} else {
weightIndex = nextLayer.getWeightIndex()
+ (getTotalCount() * nextLayer.getCount());
layerIndex = nextLayer.getNeuronIndex() + nextLayer.getTotalCount();
//layerIndex = nextLayer.getLayerIndexReverse()
// + nextLayer.getTotalCount();
}
this.neuronIndex = layerIndex;
this.weightIndex = weightIndex;
}
/**
* Compute a layer.
* @param inputOffset The offset to the input for this layer.
* @param outputOffset The offset to the output from this layer.
* @param fromCount The count of from neurons.
* @param toCount The count of to neurons.
*/
public void computeLayer(int inputOffset, int outputOffset, int fromCount, int toCount) {
Layer prev = getOwner().getPreviousLayer(this);
final double[] weights = getOwner().getWeights();
int index = getWeightIndex();
// weight values
for (int ix = 0; ix < toCount; ix++) {
int x = getNeuronIndex()+ix;
double sum = 0;
for (int y = 0; y < fromCount; y++) {
sum += weights[index] * getOwner().getLayerOutput()[prev.getNeuronIndex()+y];
index++;
}
getOwner().getLayerSums()[x] += sum;
getOwner().getLayerOutput()[x] += sum;
}
getActivation().activationFunction(
getOwner().getLayerOutput(), getNeuronIndex(), toCount);
}
/**
* Compute gradients for this layer.
* @param calc The gradient calculator.
* @param inputOffset The input offset.
* @param outputOffset The output offset.
* @param fromLayerSize The from layer size.
* @param toLayerSize The to layer size.
*/
public void computeGradient(GradientCalc calc, int inputOffset, int outputOffset, int fromLayerSize, int toLayerSize) {
Layer prev = getOwner().getPreviousLayer(this);
final int fromLayerIndex = prev.getNeuronIndex();
final int toLayerIndex = getNeuronIndex();
final int index = getWeightIndex();
final ActivationFunction activation = getActivation();
// handle weights
// array references are made method local to avoid one indirection
final double[] layerDelta = calc.getLayerDelta();
final double[] weights = this.getOwner().getWeights();
final double[] layerOutput = getOwner().getLayerOutput();
final double[] layerSums = getOwner().getLayerSums();
int y = fromLayerIndex;
for (int yi = 0; yi < fromLayerSize; yi++) {
final double output = layerOutput[y];
double sum = 0;
int wi = index + yi;
for (int xi = 0; xi < toLayerSize; xi++, wi += fromLayerSize) {
int x = xi + toLayerIndex;
calc.getGradients()[wi] += -(output * layerDelta[x]);
sum += weights[wi] * layerDelta[x];
}
layerDelta[y] = sum
* (activation.derivativeFunction(layerSums[y], layerOutput[y]));
y++;
}
}
/**
* {@inheritDoc}
*/
@Override
public int getWeightIndex() {
return this.weightIndex;
}
/**
* {@inheritDoc}
*/
@Override
public int getNeuronIndex() {
return this.neuronIndex;
}
/**
* {@inheritDoc}
*/
@Override
public BasicNetwork getOwner() {
return this.owner;
}
/**
* @param activation
* the activation to set
*/
public void setActivation(final ActivationFunction activation) {
this.activation = activation;
}
/**
* {@inheritDoc}
*/
@Override
public ActivationFunction getActivation() {
return this.activation;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
result.append("[");
result.append(this.getClass().getSimpleName());
result.append(",count=").append(getCount());
result.append(",weightIndex=").append(getWeightIndex());
result.append(",neuronIndex=").append(getNeuronIndex());
result.append("]");
return result.toString();
}
}