#region License Information
/* HeuristicLab
* Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
* and the BEACON Center for the Study of Evolution in Action.
*
* 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.Linq;
using System.Threading;
using HeuristicLab.Algorithms.DataAnalysis.GradientBoostedTrees;
using HeuristicLab.Analysis;
using HeuristicLab.Common;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Optimization;
using HeuristicLab.Parameters;
using HEAL.Attic;
using HeuristicLab.PluginInfrastructure;
using HeuristicLab.Problems.DataAnalysis;
namespace HeuristicLab.Algorithms.DataAnalysis {
[Item("Gradient Boosted Trees (GBT)", "Gradient boosted trees algorithm. Specific implementation of gradient boosting for regression trees. Friedman, J. \"Greedy Function Approximation: A Gradient Boosting Machine\", IMS 1999 Reitz Lecture.")]
[StorableType("8CCB55BD-4935-4868-855F-D3E5D55127AA")]
[Creatable(CreatableAttribute.Categories.DataAnalysisRegression, Priority = 125)]
public class GradientBoostedTreesAlgorithm : FixedDataAnalysisAlgorithm {
#region ParameterNames
private const string IterationsParameterName = "Iterations";
private const string MaxSizeParameterName = "Maximum Tree Size";
private const string NuParameterName = "Nu";
private const string RParameterName = "R";
private const string MParameterName = "M";
private const string SeedParameterName = "Seed";
private const string SetSeedRandomlyParameterName = "SetSeedRandomly";
private const string LossFunctionParameterName = "LossFunction";
private const string UpdateIntervalParameterName = "UpdateInterval";
private const string ModelCreationParameterName = "ModelCreation";
#endregion
#region ParameterProperties
public IFixedValueParameter IterationsParameter {
get { return (IFixedValueParameter)Parameters[IterationsParameterName]; }
}
public IFixedValueParameter MaxSizeParameter {
get { return (IFixedValueParameter)Parameters[MaxSizeParameterName]; }
}
public IFixedValueParameter NuParameter {
get { return (IFixedValueParameter)Parameters[NuParameterName]; }
}
public IFixedValueParameter RParameter {
get { return (IFixedValueParameter)Parameters[RParameterName]; }
}
public IFixedValueParameter MParameter {
get { return (IFixedValueParameter)Parameters[MParameterName]; }
}
public IFixedValueParameter SeedParameter {
get { return (IFixedValueParameter)Parameters[SeedParameterName]; }
}
public FixedValueParameter SetSeedRandomlyParameter {
get { return (FixedValueParameter)Parameters[SetSeedRandomlyParameterName]; }
}
public IConstrainedValueParameter LossFunctionParameter {
get { return (IConstrainedValueParameter)Parameters[LossFunctionParameterName]; }
}
public IFixedValueParameter UpdateIntervalParameter {
get { return (IFixedValueParameter)Parameters[UpdateIntervalParameterName]; }
}
private IFixedValueParameter> ModelCreationParameter {
get { return (IFixedValueParameter>)Parameters[ModelCreationParameterName]; }
}
#endregion
#region Properties
public int Iterations {
get { return IterationsParameter.Value.Value; }
set { IterationsParameter.Value.Value = value; }
}
public int Seed {
get { return SeedParameter.Value.Value; }
set { SeedParameter.Value.Value = value; }
}
public bool SetSeedRandomly {
get { return SetSeedRandomlyParameter.Value.Value; }
set { SetSeedRandomlyParameter.Value.Value = value; }
}
public int MaxSize {
get { return MaxSizeParameter.Value.Value; }
set { MaxSizeParameter.Value.Value = value; }
}
public double Nu {
get { return NuParameter.Value.Value; }
set { NuParameter.Value.Value = value; }
}
public double R {
get { return RParameter.Value.Value; }
set { RParameter.Value.Value = value; }
}
public double M {
get { return MParameter.Value.Value; }
set { MParameter.Value.Value = value; }
}
public ModelCreation ModelCreation {
get { return ModelCreationParameter.Value.Value; }
set { ModelCreationParameter.Value.Value = value; }
}
#endregion
#region ResultsProperties
private double ResultsBestQuality {
get { return ((DoubleValue)Results["Best Quality"].Value).Value; }
set { ((DoubleValue)Results["Best Quality"].Value).Value = value; }
}
private DataTable ResultsQualities {
get { return ((DataTable)Results["Qualities"].Value); }
}
#endregion
[StorableConstructor]
protected GradientBoostedTreesAlgorithm(StorableConstructorFlag _) : base(_) { }
protected GradientBoostedTreesAlgorithm(GradientBoostedTreesAlgorithm original, Cloner cloner)
: base(original, cloner) {
}
public override IDeepCloneable Clone(Cloner cloner) {
return new GradientBoostedTreesAlgorithm(this, cloner);
}
public GradientBoostedTreesAlgorithm() {
Problem = new RegressionProblem(); // default problem
Parameters.Add(new FixedValueParameter(IterationsParameterName, "Number of iterations (set as high as possible, adjust in combination with nu, when increasing iterations also decrease nu)", new IntValue(1000)));
Parameters.Add(new FixedValueParameter(SeedParameterName, "The random seed used to initialize the new pseudo random number generator.", new IntValue(0)));
Parameters.Add(new FixedValueParameter(SetSeedRandomlyParameterName, "True if the random seed should be set to a random value, otherwise false.", new BoolValue(true)));
Parameters.Add(new FixedValueParameter(MaxSizeParameterName, "Maximal size of the tree learned in each step (prefer smaller sizes (3 to 10) if possible)", new IntValue(10)));
Parameters.Add(new FixedValueParameter(RParameterName, "Ratio of training rows selected randomly in each step (0 < R <= 1)", new DoubleValue(0.5)));
Parameters.Add(new FixedValueParameter(MParameterName, "Ratio of variables selected randomly in each step (0 < M <= 1)", new DoubleValue(0.5)));
Parameters.Add(new FixedValueParameter(NuParameterName, "Learning rate nu (step size for the gradient update, should be small 0 < nu < 0.1)", new DoubleValue(0.002)));
Parameters.Add(new FixedValueParameter(UpdateIntervalParameterName, "", new IntValue(100)));
Parameters[UpdateIntervalParameterName].Hidden = true;
Parameters.Add(new FixedValueParameter>(ModelCreationParameterName, "Defines the results produced at the end of the run (Surrogate => Less disk space, lazy recalculation of model)", new EnumValue(ModelCreation.Model)));
Parameters[ModelCreationParameterName].Hidden = true;
var lossFunctions = ApplicationManager.Manager.GetInstances();
Parameters.Add(new ConstrainedValueParameter(LossFunctionParameterName, "The loss function", new ItemSet(lossFunctions)));
LossFunctionParameter.Value = LossFunctionParameter.ValidValues.First(f => f.ToString().Contains("Squared")); // squared error loss is the default
}
[StorableHook(HookType.AfterDeserialization)]
private void AfterDeserialization() {
// BackwardsCompatibility3.4
#region Backwards compatible code, remove with 3.5
#region LossFunction
// parameter type has been changed
var lossFunctionParam = Parameters[LossFunctionParameterName] as ConstrainedValueParameter;
if (lossFunctionParam != null) {
Parameters.Remove(LossFunctionParameterName);
var selectedValue = lossFunctionParam.Value; // to be restored below
var lossFunctions = ApplicationManager.Manager.GetInstances();
Parameters.Add(new ConstrainedValueParameter(LossFunctionParameterName, "The loss function", new ItemSet(lossFunctions)));
// try to restore selected value
var selectedLossFunction =
LossFunctionParameter.ValidValues.FirstOrDefault(f => f.ToString() == selectedValue.Value);
if (selectedLossFunction != null) {
LossFunctionParameter.Value = selectedLossFunction;
} else {
LossFunctionParameter.Value = LossFunctionParameter.ValidValues.First(f => f.ToString().Contains("Squared")); // default: SE
}
}
#endregion
#region CreateSolution
// parameter type has been changed
if (Parameters.ContainsKey("CreateSolution")) {
var createSolutionParam = Parameters["CreateSolution"] as FixedValueParameter;
Parameters.Remove(createSolutionParam);
ModelCreation value = createSolutionParam.Value.Value ? ModelCreation.Model : ModelCreation.QualityOnly;
Parameters.Add(new FixedValueParameter>(ModelCreationParameterName, "Defines the results produced at the end of the run (Surrogate => Less disk space, lazy recalculation of model)", new EnumValue(value)));
Parameters[ModelCreationParameterName].Hidden = true;
} else if (!Parameters.ContainsKey(ModelCreationParameterName)) {
// very old version contains neither ModelCreationParameter nor CreateSolutionParameter
Parameters.Add(new FixedValueParameter>(ModelCreationParameterName, "Defines the results produced at the end of the run (Surrogate => Less disk space, lazy recalculation of model)", new EnumValue(ModelCreation.Model)));
Parameters[ModelCreationParameterName].Hidden = true;
}
#endregion
#endregion
}
protected override void Run(CancellationToken cancellationToken) {
// Set up the algorithm
if (SetSeedRandomly) Seed = Random.RandomSeedGenerator.GetSeed();
// Set up the results display
var iterations = new IntValue(0);
Results.Add(new Result("Iterations", iterations));
var table = new DataTable("Qualities");
table.Rows.Add(new DataRow("Loss (train)"));
table.Rows.Add(new DataRow("Loss (test)"));
table.Rows["Loss (train)"].VisualProperties.StartIndexZero = true;
table.Rows["Loss (test)"].VisualProperties.StartIndexZero = true;
Results.Add(new Result("Qualities", table));
var curLoss = new DoubleValue();
Results.Add(new Result("Loss (train)", curLoss));
// init
var problemData = (IRegressionProblemData)Problem.ProblemData.Clone();
var lossFunction = LossFunctionParameter.Value;
var state = GradientBoostedTreesAlgorithmStatic.CreateGbmState(problemData, lossFunction, (uint)Seed, MaxSize, R, M, Nu);
var updateInterval = UpdateIntervalParameter.Value.Value;
// Loop until iteration limit reached or canceled.
for (int i = 0; i < Iterations; i++) {
cancellationToken.ThrowIfCancellationRequested();
GradientBoostedTreesAlgorithmStatic.MakeStep(state);
// iteration results
if (i % updateInterval == 0) {
curLoss.Value = state.GetTrainLoss();
table.Rows["Loss (train)"].Values.Add(curLoss.Value);
table.Rows["Loss (test)"].Values.Add(state.GetTestLoss());
iterations.Value = i;
}
}
// final results
iterations.Value = Iterations;
curLoss.Value = state.GetTrainLoss();
table.Rows["Loss (train)"].Values.Add(curLoss.Value);
table.Rows["Loss (test)"].Values.Add(state.GetTestLoss());
// produce variable relevance
var orderedImpacts = state.GetVariableRelevance().Select(t => new { name = t.Key, impact = t.Value }).ToList();
var impacts = new DoubleMatrix();
var matrix = impacts as IStringConvertibleMatrix;
matrix.Rows = orderedImpacts.Count;
matrix.RowNames = orderedImpacts.Select(x => x.name);
matrix.Columns = 1;
matrix.ColumnNames = new string[] { "Relative variable relevance" };
int rowIdx = 0;
foreach (var p in orderedImpacts) {
matrix.SetValue(string.Format("{0:N2}", p.impact), rowIdx++, 0);
}
Results.Add(new Result("Variable relevance", impacts));
Results.Add(new Result("Loss (test)", new DoubleValue(state.GetTestLoss())));
// produce solution
if (ModelCreation == ModelCreation.SurrogateModel || ModelCreation == ModelCreation.Model) {
IRegressionModel model = state.GetModel();
if (ModelCreation == ModelCreation.SurrogateModel) {
model = new GradientBoostedTreesModelSurrogate((GradientBoostedTreesModel)model, problemData, (uint)Seed, lossFunction, Iterations, MaxSize, R, M, Nu);
}
// for logistic regression we produce a classification solution
if (lossFunction is LogisticRegressionLoss) {
var classificationModel = new DiscriminantFunctionClassificationModel(model,
new AccuracyMaximizationThresholdCalculator());
var classificationProblemData = new ClassificationProblemData(problemData.Dataset,
problemData.AllowedInputVariables, problemData.TargetVariable, problemData.Transformations);
classificationProblemData.TrainingPartition.Start = Problem.ProblemData.TrainingPartition.Start;
classificationProblemData.TrainingPartition.End = Problem.ProblemData.TrainingPartition.End;
classificationProblemData.TestPartition.Start = Problem.ProblemData.TestPartition.Start;
classificationProblemData.TestPartition.End = Problem.ProblemData.TestPartition.End;
classificationModel.SetThresholdsAndClassValues(new double[] { double.NegativeInfinity, 0.0 }, new[] { 0.0, 1.0 });
var classificationSolution = new DiscriminantFunctionClassificationSolution(classificationModel, classificationProblemData);
Results.Add(new Result("Solution", classificationSolution));
} else {
// otherwise we produce a regression solution
Results.Add(new Result("Solution", new GradientBoostedTreesSolution(model, problemData)));
}
} else if (ModelCreation == ModelCreation.QualityOnly) {
//Do nothing
} else {
throw new NotImplementedException("Selected parameter for CreateSolution isn't implemented yet");
}
}
}
}