/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.operator.features.transformation; import java.util.Iterator; import java.util.List; import java.util.Random; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.example.Statistics; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.Model; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.UserError; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeDouble; import com.rapidminer.parameter.ParameterTypeInt; import com.rapidminer.tools.RandomGenerator; import com.rapidminer.tools.math.matrix.CovarianceMatrix; import Jama.Matrix; /** * Generalized Hebbian Algorithm (GHA) is an iterative method to compute * principal components. From a computational point of view, it can be * advantageous to solve the eigenvalue problem by iterative methods which do * not need to compute the covariance matrix directly. This is useful when the * ExampleSet contains many Attributes (hundreds, thousands). The operator * outputs a <code>GHAModel</code>. With the <code>ModelApplier</code> you * can transform the features. * * @author Daniel Hakenjos, Ingo Mierswa * @version $Id: GHA.java,v 1.9 2008/07/07 07:06:44 ingomierswa Exp $ */ public class GHA extends Operator { /** The parameter name for "Number of components to compute. If '-1' nr of attributes is taken.'" */ public static final String PARAMETER_NUMBER_OF_COMPONENTS = "number_of_components"; /** The parameter name for "Number of Iterations to apply the update rule." */ public static final String PARAMETER_NUMBER_OF_ITERATIONS = "number_of_iterations"; /** The parameter name for "The learning rate for GHA (small)" */ public static final String PARAMETER_LEARNING_RATE = "learning_rate"; /** The parameter name for "The local random seed for this operator, uses global random number generator if -1." */ public static final String PARAMETER_LOCAL_RANDOM_SEED = "local_random_seed"; public GHA(OperatorDescription description) { super(description); } public IOObject[] apply() throws OperatorException { ExampleSet exampleSet = getInput(ExampleSet.class); Random random = RandomGenerator.getRandomGenerator(getParameterAsInt(PARAMETER_LOCAL_RANDOM_SEED)); exampleSet.recalculateAllAttributeStatistics(); // 1) check whether all attributes are numerical double[] means = new double[exampleSet.getAttributes().size()]; int a = 0; for (Attribute attribute : exampleSet.getAttributes()) { if (!attribute.isNumerical()) { throw new UserError(this, 104, "GHA", attribute.getName()); } means[a] = exampleSet.getStatistics(attribute, Statistics.AVERAGE); a++; } // 2) create data and subtract the mean log("Initialising the weight matrix..."); double[][] data = new double[exampleSet.size()][exampleSet.getAttributes().size()]; Iterator<Example> reader = exampleSet.iterator(); Example example; for (int sample = 0; sample < exampleSet.size(); sample++) { example = reader.next(); int d = 0; for (Attribute attribute : exampleSet.getAttributes()) { data[sample][d] = example.getValue(attribute) - means[d]; d++; } checkForStop(); } // init double learningRate = getParameterAsDouble(PARAMETER_LEARNING_RATE); int numberOfComponents = getParameterAsInt(PARAMETER_NUMBER_OF_COMPONENTS); if (numberOfComponents < 0) numberOfComponents = exampleSet.getAttributes().size(); int numberOfIterations = getParameterAsInt(PARAMETER_NUMBER_OF_ITERATIONS); double[][] randomMatrix = new double[numberOfComponents][exampleSet.getAttributes().size()]; for (int i = 0; i < randomMatrix.length; i++) { for (int j = 0; j < randomMatrix[i].length; j++) { randomMatrix[i][j] = random.nextDouble(); } } Matrix W = new Matrix(randomMatrix); W.timesEquals(0.1d); log("Training with learning rate: " + learningRate); train(data, W, numberOfIterations, learningRate, random); log("Creating the model..."); // compute eigenvalues // --> create covariancematrix // --> multiply with eigenvector // --> calculate eigenvalue Matrix covarianceMatrix = CovarianceMatrix.getCovarianceMatrix(exampleSet); Matrix tmp = W.times(covarianceMatrix); double[][] weights = W.getArray(); double[][] tmparray = tmp.getArray(); double[] eigenvalues = new double[numberOfComponents]; for (int i = 0; i < weights.length; i++) { double nr = 0; eigenvalues[i] = 0.0d; for (int j = 0; j < weights[0].length; j++) { tmparray[i][j] = tmparray[i][j] / weights[i][j]; if (tmparray[i][j] > 0.0d) { nr += 1.0d; eigenvalues[i] += tmparray[i][j]; } } nr = Math.max(nr, 1.0d); eigenvalues[i] = eigenvalues[i] / nr; } GHAModel model = new GHAModel(exampleSet, eigenvalues, W.getArray(), means); return new IOObject[] { exampleSet, model }; } private void train(double[][] data, Matrix W, int numberOfIterations, double learningRate, Random random) throws OperatorException { int sample; Matrix x; Matrix y; int iterlog = 1; while ((numberOfIterations / iterlog > 10) && (numberOfIterations / (iterlog * 10) >= 3)) { iterlog *= 10; } for (int iter = 1; iter <= numberOfIterations; iter++) { if (iter % iterlog == 0) { log("Iteration " + iter); } sample = (int) (random.nextDouble() * data.length); // sample as matrix x = new Matrix(data[sample], data[sample].length); // create output y // y = W'*x; y = W.times(x); // double[rows][columns] double[][] yyT = y.times(y.transpose()).getArray(); // lower triangular for (int row = 0; row < yyT.length; row++) { for (int col = row + 1; col < yyT.length; col++) { yyT[row][col] = 0.0d; } } // the lower triangular matrix Matrix LT = new Matrix(yyT); // W = W + beta*(x*y' - W*tril(y*y')); // beta = options.rate*options.annealfunc(iter); Matrix tmp1 = y.times(x.transpose()); Matrix tmp2 = LT.times(W); tmp1 = tmp1.minus(tmp2); tmp1.timesEquals(learningRate); W.plusEquals(tmp1); double[][] w = W.getArray(); for (int i = 0; i < w.length; i++) { for (int j = 0; j < w[0].length; j++) { if ((Double.isInfinite(w[i][j])) || (Double.isNaN(w[i][j]))) { throw new OperatorException("Lost convergence at iterator " + (iter + 1) + ". Lower learning rate?"); } } } checkForStop(); // all ok continue iteration } } public Class<?>[] getInputClasses() { return new Class[] { ExampleSet.class }; } public Class<?>[] getOutputClasses() { return new Class[] { ExampleSet.class, Model.class }; } public List<ParameterType> getParameterTypes() { List<ParameterType> list = super.getParameterTypes(); ParameterType type = new ParameterTypeInt(PARAMETER_NUMBER_OF_COMPONENTS, "Number of components to compute. If \'-1\' nr of attributes is taken.'", -1, Integer.MAX_VALUE, -1); type.setExpert(false); list.add(type); type = new ParameterTypeInt(PARAMETER_NUMBER_OF_ITERATIONS, "Number of Iterations to apply the update rule.", 0, Integer.MAX_VALUE, 10); list.add(type); type = new ParameterTypeDouble(PARAMETER_LEARNING_RATE, "The learning rate for GHA (small)", 0.0d, Double.POSITIVE_INFINITY, 0.01d); list.add(type); list.add(new ParameterTypeInt(PARAMETER_LOCAL_RANDOM_SEED, "The local random seed for this operator, uses global random number generator if -1.", -1, Integer.MAX_VALUE, -1)); return list; } }