/* * 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.dbnn; /** * Unsupervised training for DBNN's. This class trains a single layer at a time. */ public class UnsupervisedTrainDBN { /** * The network being trained. */ private final DeepBeliefNetwork network; /** * The level bring trained. */ private final int level; /** * The training cases. */ private final double[][] trainingInput; /** * The learning rate. */ private final double learningRate; /** * The number of cycles per iteration. */ private final int k; /** * Construct a trainer for upsupervised training for the DBNN. * @param theNetwork The DBNN to train. * @param theLevel The level of the DBNN being trained. * @param theTrainingInput The training input cases. * @param theLearningRate The learning rate. * @param theK The number of cycles to use per iteration. */ public UnsupervisedTrainDBN(DeepBeliefNetwork theNetwork, int theLevel, double[][] theTrainingInput, double theLearningRate, int theK) { this.network = theNetwork; this.level = theLevel; this.trainingInput = theTrainingInput; this.learningRate = theLearningRate; this.k = theK; } /** * Perform one iteration of unsupervised training on the DBN. */ public void iteration() { double[] layerInput = new double[0]; int prevLayerInputSize; double[] prevLayerInput; for (final double[] aTrainingInput : this.trainingInput) { // Perform layer-wise sample up to the layer being trained. for (int l = 0; l <= this.level; l++) { if (l == 0) { layerInput = new double[this.network.getInputCount()]; System.arraycopy(aTrainingInput, 0, layerInput, 0, this.network.getInputCount()); } else { // Get the previous input size, if we are on the first layer, this is the input count. // Otherwise it is the input (visible) count from the previous layer. if (l == 1) prevLayerInputSize = this.network.getInputCount(); else prevLayerInputSize = this.network.getLayers()[l - 1].getInputCount(); // Copy the previous layer's input to a new array. prevLayerInput = new double[prevLayerInputSize]; System.arraycopy(layerInput, 0, prevLayerInput, 0, prevLayerInputSize); // Construct an array to hold the current layer's input layerInput = new double[this.network.getLayers()[l].getInputCount()]; // Sample this layer's hidden neuron values (h), given previous layer's input (v). // The output goes into layerInput, which is used in the next layer. this.network.getLayers()[l - 1].sampleHgivenV(prevLayerInput, layerInput); } } // Perform up-down algorithm. contrastiveDivergence(this.network.getRBMLayers()[this.level], layerInput, this.learningRate, this.k); } } /** * Perform contrastive divergence, also known as the up-down algorithm. * @param rbm The RBM to use. * @param input The input training pattern. * @param lr The learning rate. * @param k The number of cycles. */ public void contrastiveDivergence(RestrictedBoltzmannMachine rbm, double[] input, double lr, int k) { // The positive gradient mean & samples (P) - Only for hidden (H) double[] meanPH = new double[rbm.getHiddenCount()]; double[] samplePH = new double[rbm.getHiddenCount()]; // The negative gradient mean & samples (N) - For both visible (V) & hidden (H) double[] meansNV = new double[rbm.getVisibleCount()]; double[] samplesNV = new double[rbm.getVisibleCount()]; double[] meansNH = new double[rbm.getHiddenCount()]; double[] samplesNH = new double[rbm.getHiddenCount()]; // Calculate (sample) meanPH and samplePH sampleHV(rbm, input, meanPH, samplePH); for(int step=0; step<k; step++) { if(step == 0) { gibbsHVH(rbm, samplePH, meansNV, samplesNV, meansNH, samplesNH); } else { gibbsHVH(rbm, samplesNH, meansNV, samplesNV, meansNH, samplesNH); } } // Adjust the weights, based on calculated mean values. // This uses the maximum likelihood learning rule. for(int i=0; i<rbm.getHiddenCount(); i++) { for(int j=0; j<rbm.getVisibleCount(); j++) { rbm.getLayer().getWeights()[i][j] += lr *(meanPH[i] * input[j] - meansNH[i] * samplesNV[j]) / input.length; } rbm.getBiasH()[i] += lr * (samplePH[i] - meansNH[i]) / input.length; } // Adjust the biases for learning. for(int i=0; i<rbm.getVisibleCount(); i++) { rbm.getBiasV()[i] += lr * (input[i] - samplesNV[i]) / input.length; } } /** * Sample the hidden neurons (output), given the visible (input). Return the mean, and a sample, based on that * mean probability. * @param rbm The RBM to use. * @param v0Sample The input to the layer. * @param mean Output: mean value of each hidden neuron. * @param sample Output: sample, based on mean. */ public void sampleHV(RestrictedBoltzmannMachine rbm, double[] v0Sample, double[] mean, double[] sample) { for(int i=0; i<rbm.getHiddenCount(); i++) { // Find the mean. mean[i] = propUp(rbm, v0Sample, rbm.getLayer().getWeights()[i], rbm.getBiasH()[i]); // Sample, based on that mean. sample[i] = rbm.binomial(1, mean[i]); } } /** * Estimate the mean of a hidden neuron in an RBM. Propagate upward part, from visible to hidden. * @param rbm The RBM to use. * @param v The input (v), visible neurons. * @param w The weights. * @param b The bias. * @return The mean. */ public double propUp(RestrictedBoltzmannMachine rbm, double[] v, double[] w, double b) { double sum = 0.0; for(int j=0; j<rbm.getVisibleCount(); j++) { sum += w[j] * v[j]; } sum += b; return RestrictedBoltzmannMachine.sigmoid(sum); } /** * Perform Gibbs sampling. Hidden to visible to hidden. * @param rbm The RBM to use. * @param sampleH0 The hidden samples. * @param meansNV Output: means for the visible (v) neurons. * @param samplesNV Output: samples for the visible (v) neurons. * @param meansNH Output: means for the hidden (h) neurons. * @param samplesNH Output: samples for the hidden (h) neurons. */ public void gibbsHVH(RestrictedBoltzmannMachine rbm, double[] sampleH0, double[] meansNV, double[] samplesNV, double[] meansNH, double[] samplesNH) { sampleVH(rbm, sampleH0, meansNV, samplesNV); sampleHV(rbm, samplesNV, meansNH, samplesNH); } /** * Sample the visible (input), given the hidden neurons (output). Return the mean, and a sample, based on that * mean probability. * @param rbm The RBM to use. * @param sampleH0 Hidden (h) samples. * @param mean Output: Visible (v) mean. * @param sample Output: Visible (v) sample. */ public void sampleVH(RestrictedBoltzmannMachine rbm, double[] sampleH0, double[] mean, double[] sample) { for(int i=0; i<rbm.getVisibleCount(); i++) { mean[i] = propDown(rbm, sampleH0, i, rbm.getBiasV()[i]); sample[i] = rbm.binomial(1, mean[i]); } } /** * Estimate the mean of a visible neuron in an RBM. Propagate downward part, from hidden to visible. * @param rbm The RBM to use. * @param h The hidden neurons. * @param i The visible neuron to use. * @param b Bias value. * @return The estimated mean. */ public double propDown(RestrictedBoltzmannMachine rbm, double[] h, int i, double b) { double sum = 0.0; for(int j=0; j<rbm.getHiddenCount(); j++) { sum += rbm.getLayer().getWeights()[j][i] * h[j]; } sum += b; return RestrictedBoltzmannMachine.sigmoid(sum); } }