#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];
}
}
}
}
}