#region License Information /* HeuristicLab * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding; namespace HeuristicLab.Problems.DataAnalysis.Symbolic.ConstantsOptimization { public class LMConstantsOptimizer { private LMConstantsOptimizer() { } /// /// Method to determine whether the numeric constants of the tree can be optimized. This depends primarily on the symbols occuring in the tree. /// /// The tree that should be analyzed /// A flag indicating whether the numeric constants of the tree can be optimized public static bool CanOptimizeConstants(ISymbolicExpressionTree tree) { return AutoDiffConverter.IsCompatible(tree); } /// /// Optimizes the numeric constants in a symbolic expression tree in place. /// /// The tree for which the constants should be optimized /// The dataset containing the data. /// The target variable name. /// The rows for which the data should be extracted. /// A flag to determine whether linear scaling should be applied during the optimization /// The maximum number of iterations of the Levenberg-Marquardt algorithm. /// public static double OptimizeConstants(ISymbolicExpressionTree tree, IDataset dataset, string targetVariable, IEnumerable rows, bool applyLinearScaling, int maxIterations = 10) { if (tree == null) throw new ArgumentNullException("tree"); if (dataset == null) throw new ArgumentNullException("dataset"); if (!dataset.ContainsVariable(targetVariable)) throw new ArgumentException("The dataset does not contain the provided target variable."); var allVariables = Util.ExtractVariables(tree); var numericNodes = Util.ExtractNumericNodes(tree); AutoDiff.IParametricCompiledTerm term; if (!AutoDiffConverter.TryConvertToAutoDiff(tree, applyLinearScaling, numericNodes, allVariables, out term)) throw new NotSupportedException("Could not convert symbolic expression tree to an AutoDiff term due to not supported symbols used in the tree."); // Variables of the symbolic expression tree correspond to parameters in the term. // Hence if no parameters are present we can't do anything and R² stays the same. if (term.Parameters.Count == 0) return 0.0; var initialConstants = Util.ExtractConstants(numericNodes, applyLinearScaling); double[] constants; double[,] x = Util.ExtractData(dataset, allVariables, rows); double[] y = dataset.GetDoubleValues(targetVariable, rows).ToArray(); var result = OptimizeConstants(term, initialConstants, x, y, maxIterations, out constants); if (result > 0.0 && constants.Length != 0) Util.UpdateConstants(numericNodes, constants); return result; } /// /// Optimizes the numeric coefficents of an AutoDiff Term using the Levenberg-Marquardt algorithm. /// /// The AutoDiff term for which the numeric coefficients should be optimized. /// The starting values for the numeric coefficients. /// The input data for the optimization. /// The target values for the optimization. /// The maximum number of iterations of the Levenberg-Marquardt /// The optimized constants. /// An optional callback for detailed analysis that is called in each algorithm iteration. /// The R² of the term evaluated on the input data x and the target data y using the optimized constants public static double OptimizeConstants(AutoDiff.IParametricCompiledTerm term, double[] initialConstants, double[,] x, double[] y, int maxIterations, out double[] constants, Action LM_IterationCallback = null) { if (term.Parameters.Count == 0) { constants = new double[0]; return 0.0; } var optimizedConstants = (double[])initialConstants.Clone(); int numberOfRows = x.GetLength(0); int numberOfColumns = x.GetLength(1); int numberOfConstants = optimizedConstants.Length; alglib.minlmstate state; alglib.minlmreport rep; alglib.ndimensional_rep xrep = (p, f, obj) => LM_IterationCallback(p, f, obj); try { alglib.minlmcreatevj(numberOfRows, optimizedConstants, state: out state); alglib.minlmsetcond(state, 0.0, 0.0, 0.0, maxIterations); alglib.minlmsetxrep(state, LM_IterationCallback != null); alglib.minlmoptimize(state, Evaluate, EvaluateGradient, xrep, new object[] { term, x, y }); alglib.minlmresults(state, out optimizedConstants, out rep); } catch (ArithmeticException) { constants = new double[0]; return double.NaN; } catch (alglib.alglibexception) { constants = new double[0]; return double.NaN; } // error if (rep.terminationtype < 0) { constants = initialConstants; return 0; } constants = optimizedConstants; // calculate prediction with optimized constants to calculate R² double[] pred = new double[numberOfRows]; double[] zeros = new double[numberOfRows]; Evaluate(constants, pred, new object[] { term, x, zeros }); var r = OnlinePearsonsRCalculator.Calculate(pred, y, out OnlineCalculatorError error); if (error != OnlineCalculatorError.None) r = 0; return r * r; } private static void Evaluate(double[] c, double[] fi, object o) { var objs = (object[])o; AutoDiff.IParametricCompiledTerm term = (AutoDiff.IParametricCompiledTerm)objs[0]; var x = (double[,])objs[1]; var y = (double[])objs[2]; double[] xi = new double[x.GetLength(1)]; for (int i = 0; i < fi.Length; i++) { // copy data row for (int j = 0; j < xi.Length; j++) xi[j] = x[i, j]; fi[i] = term.Evaluate(c, xi) - y[i]; } } private static void EvaluateGradient(double[] c, double[] fi, double[,] jac, object o) { var objs = (object[])o; AutoDiff.IParametricCompiledTerm term = (AutoDiff.IParametricCompiledTerm)objs[0]; var x = (double[,])objs[1]; var y = (double[])objs[2]; double[] xi = new double[x.GetLength(1)]; for (int i = 0; i < fi.Length; i++) { // copy data row for (int j = 0; j < xi.Length; j++) xi[j] = x[i, j]; Tuple result = term.Differentiate(c, xi); fi[i] = result.Item2 - y[i]; var g = result.Item1; // copy gradient to Jacobian for (int j = 0; j < c.Length; j++) { jac[i, j] = g[j]; } } } } }