/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 Heaton Research, Inc. * * 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 org.encog.ml.svm; import org.encog.EncogError; import org.encog.mathutil.libsvm.svm; import org.encog.mathutil.libsvm.svm_model; import org.encog.mathutil.libsvm.svm_node; import org.encog.mathutil.libsvm.svm_parameter; import org.encog.ml.BasicML; import org.encog.ml.MLClassification; import org.encog.ml.MLError; import org.encog.ml.MLRegression; import org.encog.ml.data.MLData; import org.encog.ml.data.MLDataSet; import org.encog.ml.data.basic.BasicMLData; import org.encog.neural.NeuralNetworkError; import org.encog.util.simple.EncogUtility; /** * This is a network that is backed by one or more Support Vector Machines * (SVM). It is designed to function very similarly to an Encog neural network, * and is largely interchangeable with an Encog neural network. * * The support vector machine supports several types. Regression is used when * you want the network to predict a value, given the input. Function * approximation is a good example of regression. Classification is used when * you want the SVM to group the input data into one or more classes. * * Support Vector Machines typically have a single output. Neural networks can * have multiple output neurons. To get around this issue, this class will * create multiple SVM's if there is more than one output specified. * * Because a SVM is trained quite differently from a neural network, none of the * neural network training classes will work. This class must be trained using * SVMTrain. */ public class SVM extends BasicML implements MLRegression, MLClassification, MLError { /** * The default degree. */ public static final int DEFAULT_DEGREE = 3; /** * The default COEF0. */ public static final int DEFAULT_COEF0 = 0; /** * The default NU. */ public static final double DEFAULT_NU = 0.5; /** * The default cache size. */ public static final int DEFAULT_CACHE_SIZE = 100; /** * The default C. */ public static final int DEFAULT_C = 1; /** * The default EPS. */ public static final double DEFAULT_EPS = 1e-3; /** * The default P. */ public static final double DEFAULT_P = 0.1; /** * Serial id. */ private static final long serialVersionUID = 1L; /** * The SVM model to use. */ private svm_model model; /** * The params for the model. */ private final svm_parameter params; /** * The input count. */ private int inputCount; /** * Construct the SVM. */ public SVM() { this.params = new svm_parameter(); } /** * Construct an SVM network. For regression it will use an epsilon support * vector. Both types will use an RBF kernel. * * @param theInputCount * The input count. * @param regression * True if this network is used for regression. */ public SVM(final int theInputCount, final boolean regression) { this(theInputCount, regression ? SVMType.EpsilonSupportVectorRegression : SVMType.SupportVectorClassification, KernelType.RadialBasisFunction); } /** * Construct a SVM network. * * @param theInputCount * The input count. * @param svmType * The type of SVM. * @param kernelType * The SVM kernal type. */ public SVM(final int theInputCount, final SVMType svmType, final KernelType kernelType) { this.inputCount = theInputCount; this.params = new svm_parameter(); switch (svmType) { case SupportVectorClassification: this.params.svm_type = svm_parameter.C_SVC; break; case NewSupportVectorClassification: this.params.svm_type = svm_parameter.NU_SVC; break; case SupportVectorOneClass: this.params.svm_type = svm_parameter.ONE_CLASS; break; case EpsilonSupportVectorRegression: this.params.svm_type = svm_parameter.EPSILON_SVR; break; case NewSupportVectorRegression: this.params.svm_type = svm_parameter.NU_SVR; break; default: throw new NeuralNetworkError("Invalid svm type"); } switch (kernelType) { case Linear: this.params.kernel_type = svm_parameter.LINEAR; break; case Poly: this.params.kernel_type = svm_parameter.POLY; break; case RadialBasisFunction: this.params.kernel_type = svm_parameter.RBF; break; case Sigmoid: this.params.kernel_type = svm_parameter.SIGMOID; break; case Precomputed: this.params.kernel_type = svm_parameter.PRECOMPUTED; break; default: throw new NeuralNetworkError("Invalid kernel type"); } // params[i].kernel_type = svm_parameter.RBF; this.params.degree = DEFAULT_DEGREE; this.params.coef0 = 0; this.params.nu = DEFAULT_NU; this.params.cache_size = DEFAULT_CACHE_SIZE; this.params.C = 1; this.params.eps = DEFAULT_EPS; this.params.p = DEFAULT_P; this.params.shrinking = 1; this.params.probability = 0; this.params.nr_weight = 0; this.params.weight_label = new int[0]; this.params.weight = new double[0]; this.params.gamma = 1.0 / inputCount; } /** * Construct a SVM from a model. * @param theModel The model. */ public SVM(final svm_model theModel) { this.model = theModel; this.params = model.param; this.inputCount = 0; // determine the input count for (final svm_node[] element : this.model.SV) { for (int col = 0; col < element.length; col++) { this.inputCount = Math.max(element[col].index, this.inputCount); } } // } /** * Calculate the error for this SVM. * * @param data * The training set. * @return The error percentage. */ @Override public double calculateError(final MLDataSet data) { switch (getSVMType()) { case SupportVectorClassification: case NewSupportVectorClassification: case SupportVectorOneClass: return EncogUtility.calculateClassificationError(this, data); case EpsilonSupportVectorRegression: case NewSupportVectorRegression: return EncogUtility.calculateRegressionError(this, data); default: return EncogUtility.calculateRegressionError(this, data); } } /** * {@inheritDoc} */ @Override public int classify(final MLData input) { if (this.model == null) { throw new EncogError( "Can't use the SVM yet, it has not been trained, " + "and no model exists."); } final svm_node[] formattedInput = makeSparse(input); return (int) svm.svm_predict(this.model, formattedInput); } /** * Compute the output for the given input. * * @param input * The input to the SVM. * @return The results from the SVM. */ @Override public MLData compute(final MLData input) { if (this.model == null) { throw new EncogError( "Can't use the SVM yet, it has not been trained, " + "and no model exists."); } final MLData result = new BasicMLData(1); final svm_node[] formattedInput = makeSparse(input); final double d = svm.svm_predict(this.model, formattedInput); result.setData(0, d); return result; } /** * @return The input count. */ @Override public int getInputCount() { return this.inputCount; } /** * @return The kernel type. */ public KernelType getKernelType() { switch (this.params.kernel_type) { case svm_parameter.LINEAR: return KernelType.Linear; case svm_parameter.POLY: return KernelType.Poly; case svm_parameter.RBF: return KernelType.RadialBasisFunction; case svm_parameter.SIGMOID: return KernelType.Sigmoid; case svm_parameter.PRECOMPUTED: return KernelType.Precomputed; default: return null; } } /** * @return The SVM models for each output. */ public svm_model getModel() { return this.model; } /** * @return For a SVM, the output count is always one. */ @Override public int getOutputCount() { return 1; } /** * @return The SVM params for each of the outputs. */ public final svm_parameter getParams() { return this.params; } /** * @return The SVM type. */ public SVMType getSVMType() { switch (this.params.svm_type) { case svm_parameter.C_SVC: return SVMType.SupportVectorClassification; case svm_parameter.NU_SVC: return SVMType.NewSupportVectorClassification; case svm_parameter.ONE_CLASS: return SVMType.SupportVectorOneClass; case svm_parameter.EPSILON_SVR: return SVMType.EpsilonSupportVectorRegression; case svm_parameter.NU_SVR: return SVMType.NewSupportVectorRegression; default: return null; } } /** * Convert regular Encog MLData into the "sparse" data needed by an SVM. * * @param data * The data to convert. * @return The SVM sparse data. */ public svm_node[] makeSparse(final MLData data) { final svm_node[] result = new svm_node[data.size()]; for (int i = 0; i < data.size(); i++) { result[i] = new svm_node(); result[i].index = i + 1; result[i].value = data.getData(i); } return result; } /** * Set the input count. * @param i The new input count. */ public void setInputCount(final int i) { this.inputCount = i; } /** * Set the model. * @param theModel The model. */ public void setModel(final svm_model theModel) { this.model = theModel; } /** * Not needed, no properties to update. */ @Override public void updateProperties() { // unneeded } }