Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2847_M5Regression/HeuristicLab.Algorithms.DataAnalysis/3.4/GBM/GradientBoostingRegressionAlgorithm.cs @ 16842

Last change on this file since 16842 was 16842, checked in by gkronber, 6 years ago

#2847: merged r16565:16796 from trunk/HeuristicLab.Algorithms.DataAnalysis to branch

File size: 22.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 * and the BEACON Center for the Study of Evolution in Action.
5 *
6 * This file is part of HeuristicLab.
7 *
8 * HeuristicLab is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * HeuristicLab is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
20 */
21#endregion
22
23using System;
24using System.Collections.Generic;
25using System.Linq;
26using System.Threading;
27using HeuristicLab.Analysis;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Data;
31using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
32using HeuristicLab.Optimization;
33using HeuristicLab.Parameters;
34using HEAL.Attic;
35using HeuristicLab.Problems.DataAnalysis;
36using HeuristicLab.Problems.DataAnalysis.Symbolic;
37using HeuristicLab.Problems.DataAnalysis.Symbolic.Regression;
38using HeuristicLab.Random;
39using HeuristicLab.Selection;
40
41namespace HeuristicLab.Algorithms.DataAnalysis.MctsSymbolicRegression {
42  [Item("Gradient Boosting Machine Regression (GBM)",
43    "Gradient boosting for any regression base learner (e.g. MCTS symbolic regression)")]
44  [StorableType("98B340D7-DB23-40F9-A9CC-C3E652E92671")]
45  [Creatable(CreatableAttribute.Categories.DataAnalysisRegression, Priority = 350)]
46  public class GradientBoostingRegressionAlgorithm : FixedDataAnalysisAlgorithm<IRegressionProblem> {
47    #region ParameterNames
48    private const string IterationsParameterName = "Iterations";
49    private const string NuParameterName = "Nu";
50    private const string MParameterName = "M";
51    private const string RParameterName = "R";
52    private const string RegressionAlgorithmParameterName = "RegressionAlgorithm";
53    private const string SeedParameterName = "Seed";
54    private const string SetSeedRandomlyParameterName = "SetSeedRandomly";
55    private const string CreateSolutionParameterName = "CreateSolution";
56    private const string StoreRunsParameterName = "StoreRuns";
57    private const string RegressionAlgorithmSolutionResultParameterName = "RegressionAlgorithmResult";
58    #endregion
59
60    #region ParameterProperties
61    public IFixedValueParameter<IntValue> IterationsParameter {
62      get { return (IFixedValueParameter<IntValue>)Parameters[IterationsParameterName]; }
63    }
64
65    public IFixedValueParameter<DoubleValue> NuParameter {
66      get { return (IFixedValueParameter<DoubleValue>)Parameters[NuParameterName]; }
67    }
68
69    public IFixedValueParameter<DoubleValue> RParameter {
70      get { return (IFixedValueParameter<DoubleValue>)Parameters[RParameterName]; }
71    }
72
73    public IFixedValueParameter<DoubleValue> MParameter {
74      get { return (IFixedValueParameter<DoubleValue>)Parameters[MParameterName]; }
75    }
76
77    // regression algorithms are currently: DataAnalysisAlgorithms, BasicAlgorithms and engine algorithms with no common interface
78    public IConstrainedValueParameter<IAlgorithm> RegressionAlgorithmParameter {
79      get { return (IConstrainedValueParameter<IAlgorithm>)Parameters[RegressionAlgorithmParameterName]; }
80    }
81
82    public IFixedValueParameter<StringValue> RegressionAlgorithmSolutionResultParameter {
83      get { return (IFixedValueParameter<StringValue>)Parameters[RegressionAlgorithmSolutionResultParameterName]; }
84    }
85
86    public IFixedValueParameter<IntValue> SeedParameter {
87      get { return (IFixedValueParameter<IntValue>)Parameters[SeedParameterName]; }
88    }
89
90    public FixedValueParameter<BoolValue> SetSeedRandomlyParameter {
91      get { return (FixedValueParameter<BoolValue>)Parameters[SetSeedRandomlyParameterName]; }
92    }
93
94    public IFixedValueParameter<BoolValue> CreateSolutionParameter {
95      get { return (IFixedValueParameter<BoolValue>)Parameters[CreateSolutionParameterName]; }
96    }
97    public IFixedValueParameter<BoolValue> StoreRunsParameter {
98      get { return (IFixedValueParameter<BoolValue>)Parameters[StoreRunsParameterName]; }
99    }
100    #endregion
101
102    #region Properties
103    public int Iterations {
104      get { return IterationsParameter.Value.Value; }
105      set { IterationsParameter.Value.Value = value; }
106    }
107
108    public int Seed {
109      get { return SeedParameter.Value.Value; }
110      set { SeedParameter.Value.Value = value; }
111    }
112
113    public bool SetSeedRandomly {
114      get { return SetSeedRandomlyParameter.Value.Value; }
115      set { SetSeedRandomlyParameter.Value.Value = value; }
116    }
117
118    public double Nu {
119      get { return NuParameter.Value.Value; }
120      set { NuParameter.Value.Value = value; }
121    }
122
123    public double R {
124      get { return RParameter.Value.Value; }
125      set { RParameter.Value.Value = value; }
126    }
127
128    public double M {
129      get { return MParameter.Value.Value; }
130      set { MParameter.Value.Value = value; }
131    }
132
133    public bool CreateSolution {
134      get { return CreateSolutionParameter.Value.Value; }
135      set { CreateSolutionParameter.Value.Value = value; }
136    }
137
138    public bool StoreRuns {
139      get { return StoreRunsParameter.Value.Value; }
140      set { StoreRunsParameter.Value.Value = value; }
141    }
142
143    public IAlgorithm RegressionAlgorithm {
144      get { return RegressionAlgorithmParameter.Value; }
145    }
146
147    public string RegressionAlgorithmResult {
148      get { return RegressionAlgorithmSolutionResultParameter.Value.Value; }
149      set { RegressionAlgorithmSolutionResultParameter.Value.Value = value; }
150    }
151    #endregion
152
153    [StorableConstructor]
154    protected GradientBoostingRegressionAlgorithm(StorableConstructorFlag _) : base(_) {
155    }
156
157    protected GradientBoostingRegressionAlgorithm(GradientBoostingRegressionAlgorithm original, Cloner cloner)
158      : base(original, cloner) { }
159
160    public override IDeepCloneable Clone(Cloner cloner) {
161      return new GradientBoostingRegressionAlgorithm(this, cloner);
162    }
163
164    public GradientBoostingRegressionAlgorithm() {
165      Problem = new RegressionProblem(); // default problem
166      var osgp = CreateOSGP();
167      var regressionAlgs = new ItemSet<IAlgorithm>(new IAlgorithm[] {
168        new RandomForestRegression(),
169        osgp,
170      });
171      foreach (var alg in regressionAlgs) alg.Prepare();
172
173
174      Parameters.Add(new FixedValueParameter<IntValue>(IterationsParameterName,
175        "Number of iterations", new IntValue(100)));
176      Parameters.Add(new FixedValueParameter<IntValue>(SeedParameterName,
177        "The random seed used to initialize the new pseudo random number generator.", new IntValue(0)));
178      Parameters.Add(new FixedValueParameter<BoolValue>(SetSeedRandomlyParameterName,
179        "True if the random seed should be set to a random value, otherwise false.", new BoolValue(true)));
180      Parameters.Add(new FixedValueParameter<DoubleValue>(NuParameterName,
181        "The learning rate nu when updating predictions in GBM (0 < nu <= 1)", new DoubleValue(0.5)));
182      Parameters.Add(new FixedValueParameter<DoubleValue>(RParameterName,
183        "The fraction of rows that are sampled randomly for the base learner in each iteration (0 < r <= 1)",
184        new DoubleValue(1)));
185      Parameters.Add(new FixedValueParameter<DoubleValue>(MParameterName,
186        "The fraction of variables that are sampled randomly for the base learner in each iteration (0 < m <= 1)",
187        new DoubleValue(0.5)));
188      Parameters.Add(new ConstrainedValueParameter<IAlgorithm>(RegressionAlgorithmParameterName,
189        "The regression algorithm to use as a base learner", regressionAlgs, osgp));
190      Parameters.Add(new FixedValueParameter<StringValue>(RegressionAlgorithmSolutionResultParameterName,
191        "The name of the solution produced by the regression algorithm", new StringValue("Solution")));
192      Parameters[RegressionAlgorithmSolutionResultParameterName].Hidden = true;
193      Parameters.Add(new FixedValueParameter<BoolValue>(CreateSolutionParameterName,
194        "Flag that indicates if a solution should be produced at the end of the run", new BoolValue(true)));
195      Parameters[CreateSolutionParameterName].Hidden = true;
196      Parameters.Add(new FixedValueParameter<BoolValue>(StoreRunsParameterName,
197        "Flag that indicates if the results of the individual runs should be stored for detailed analysis", new BoolValue(false)));
198      Parameters[StoreRunsParameterName].Hidden = true;
199    }
200
201    protected override void Run(CancellationToken cancellationToken) {
202      // Set up the algorithm
203      if (SetSeedRandomly) Seed = RandomSeedGenerator.GetSeed();
204      var rand = new MersenneTwister((uint)Seed);
205
206      // Set up the results display
207      var iterations = new IntValue(0);
208      Results.Add(new Result("Iterations", iterations));
209
210      var table = new DataTable("Qualities");
211      table.Rows.Add(new DataRow("R² (train)"));
212      table.Rows.Add(new DataRow("R² (test)"));
213      Results.Add(new Result("Qualities", table));
214      var curLoss = new DoubleValue();
215      var curTestLoss = new DoubleValue();
216      Results.Add(new Result("R² (train)", curLoss));
217      Results.Add(new Result("R² (test)", curTestLoss));
218      var runCollection = new RunCollection();
219      if (StoreRuns)
220        Results.Add(new Result("Runs", runCollection));
221
222      // init
223      var problemData = Problem.ProblemData;
224      var targetVarName = problemData.TargetVariable;
225      var activeVariables = problemData.AllowedInputVariables.Concat(new string[] {problemData.TargetVariable});
226      var modifiableDataset = new ModifiableDataset(
227        activeVariables,
228        activeVariables.Select(v => problemData.Dataset.GetDoubleValues(v).ToList()));
229
230      var trainingRows = problemData.TrainingIndices;
231      var testRows = problemData.TestIndices;
232      var yPred = new double[trainingRows.Count()];
233      var yPredTest = new double[testRows.Count()];
234      var y = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices).ToArray();
235      var curY = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices).ToArray();
236
237      var yTest = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TestIndices).ToArray();
238      var curYTest = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TestIndices).ToArray();
239      var nu = Nu;
240      var mVars = (int)Math.Ceiling(M * problemData.AllowedInputVariables.Count());
241      var rRows = (int)Math.Ceiling(R * problemData.TrainingIndices.Count());
242      var alg = RegressionAlgorithm;
243      List<IRegressionModel> models = new List<IRegressionModel>();
244      try {
245        // Loop until iteration limit reached or canceled.
246        for (int i = 0; i < Iterations; i++) {
247          cancellationToken.ThrowIfCancellationRequested();
248
249          modifiableDataset.RemoveVariable(targetVarName);
250          modifiableDataset.AddVariable(targetVarName, curY.Concat(curYTest).ToList());
251
252          SampleTrainingData(rand, modifiableDataset, rRows, problemData.Dataset, curY, problemData.TargetVariable, problemData.TrainingIndices); // all training indices from the original problem data are allowed
253          var modifiableProblemData = new RegressionProblemData(modifiableDataset,
254            problemData.AllowedInputVariables.SampleRandomWithoutRepetition(rand, mVars),
255            problemData.TargetVariable);
256          modifiableProblemData.TrainingPartition.Start = 0;
257          modifiableProblemData.TrainingPartition.End = rRows;
258          modifiableProblemData.TestPartition.Start = problemData.TestPartition.Start;
259          modifiableProblemData.TestPartition.End = problemData.TestPartition.End;
260
261          if (!TrySetProblemData(alg, modifiableProblemData))
262            throw new NotSupportedException("The algorithm cannot be used with GBM.");
263
264          IRegressionModel model;
265          IRun run;
266
267          // try to find a model. The algorithm might fail to produce a model. In this case we just retry until the iterations are exhausted
268          if (TryExecute(alg, rand.Next(), RegressionAlgorithmResult, out model, out run)) {
269            int row = 0;
270            // update predictions for training and test
271            // update new targets (in the case of squared error loss we simply use negative residuals)
272            foreach (var pred in model.GetEstimatedValues(problemData.Dataset, trainingRows)) {
273              yPred[row] = yPred[row] + nu * pred;
274              curY[row] = y[row] - yPred[row];
275              row++;
276            }
277            row = 0;
278            foreach (var pred in model.GetEstimatedValues(problemData.Dataset, testRows)) {
279              yPredTest[row] = yPredTest[row] + nu * pred;
280              curYTest[row] = yTest[row] - yPredTest[row];
281              row++;
282            }
283            // determine quality
284            OnlineCalculatorError error;
285            var trainR = OnlinePearsonsRCalculator.Calculate(yPred, y, out error);
286            var testR = OnlinePearsonsRCalculator.Calculate(yPredTest, yTest, out error);
287
288            // iteration results
289            curLoss.Value = error == OnlineCalculatorError.None ? trainR * trainR : 0.0;
290            curTestLoss.Value = error == OnlineCalculatorError.None ? testR * testR : 0.0;
291
292            models.Add(model);
293          }
294
295          if (StoreRuns)
296            runCollection.Add(run);
297          table.Rows["R² (train)"].Values.Add(curLoss.Value);
298          table.Rows["R² (test)"].Values.Add(curTestLoss.Value);
299          iterations.Value = i + 1;
300        }
301
302        // produce solution
303        if (CreateSolution) {
304          // when all our models are symbolic models we can easily combine them to a single model
305          if (models.All(m => m is ISymbolicRegressionModel)) {
306            Results.Add(new Result("Solution", CreateSymbolicSolution(models, Nu, (IRegressionProblemData)problemData.Clone())));
307          }
308          // just produce an ensemble solution for now (TODO: correct scaling or linear regression for ensemble model weights)
309
310          var ensembleSolution = CreateEnsembleSolution(models, (IRegressionProblemData)problemData.Clone());
311          Results.Add(new Result("EnsembleSolution", ensembleSolution));
312        }
313      }
314      finally {
315        // reset everything
316        alg.Prepare(true);
317      }
318    }
319
320    private static IRegressionEnsembleSolution CreateEnsembleSolution(List<IRegressionModel> models,
321      IRegressionProblemData problemData) {
322      var rows = problemData.TrainingPartition.Size;
323      var features = models.Count;
324      double[,] inputMatrix = new double[rows, features + 1];
325      //add model estimates
326      for (int m = 0; m < models.Count; m++) {
327        var model = models[m];
328        var estimates = model.GetEstimatedValues(problemData.Dataset, problemData.TrainingIndices);
329        int estimatesCounter = 0;
330        foreach (var estimate in estimates) {
331          inputMatrix[estimatesCounter, m] = estimate;
332          estimatesCounter++;
333        }
334      }
335
336      //add target
337      var targets = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
338      int targetCounter = 0;
339      foreach (var target in targets) {
340        inputMatrix[targetCounter, models.Count] = target;
341        targetCounter++;
342      }
343
344      alglib.linearmodel lm = new alglib.linearmodel();
345      alglib.lrreport ar = new alglib.lrreport();
346      double[] coefficients;
347      int retVal = 1;
348      alglib.lrbuildz(inputMatrix, rows, features, out retVal, out lm, out ar);
349      if (retVal != 1) throw new ArgumentException("Error in calculation of linear regression solution");
350
351      alglib.lrunpack(lm, out coefficients, out features);
352
353      var ensembleModel = new RegressionEnsembleModel(models, coefficients.Take(models.Count)) {AverageModelEstimates = false};
354      var ensembleSolution = (IRegressionEnsembleSolution)ensembleModel.CreateRegressionSolution(problemData);
355      return ensembleSolution;
356    }
357
358
359    private IAlgorithm CreateOSGP() {
360      // configure strict osgp
361      var alg = new OffspringSelectionGeneticAlgorithm.OffspringSelectionGeneticAlgorithm();
362      var prob = new SymbolicRegressionSingleObjectiveProblem();
363      prob.MaximumSymbolicExpressionTreeDepth.Value = 7;
364      prob.MaximumSymbolicExpressionTreeLength.Value = 15;
365      alg.Problem = prob;
366      alg.SuccessRatio.Value = 1.0;
367      alg.ComparisonFactorLowerBound.Value = 1.0;
368      alg.ComparisonFactorUpperBound.Value = 1.0;
369      alg.MutationProbability.Value = 0.15;
370      alg.PopulationSize.Value = 200;
371      alg.MaximumSelectionPressure.Value = 100;
372      alg.MaximumEvaluatedSolutions.Value = 20000;
373      alg.SelectorParameter.Value = alg.SelectorParameter.ValidValues.OfType<GenderSpecificSelector>().First();
374      alg.MutatorParameter.Value = alg.MutatorParameter.ValidValues.OfType<MultiSymbolicExpressionTreeManipulator>().First();
375      alg.StoreAlgorithmInEachRun = false;
376      return alg;
377    }
378
379    private void SampleTrainingData(MersenneTwister rand, ModifiableDataset ds, int rRows,
380      IDataset sourceDs, double[] curTarget, string targetVarName, IEnumerable<int> trainingIndices) {
381      var selectedRows = trainingIndices.SampleRandomWithoutRepetition(rand, rRows).ToArray();
382      int t = 0;
383      object[] srcRow = new object[ds.Columns];
384      var varNames = ds.DoubleVariables.ToArray();
385      foreach (var r in selectedRows) {
386        // take all values from the original dataset
387        for (int c = 0; c < srcRow.Length; c++) {
388          var col = sourceDs.GetReadOnlyDoubleValues(varNames[c]);
389          srcRow[c] = col[r];
390        }
391        ds.ReplaceRow(t, srcRow);
392        // but use the updated target values
393        ds.SetVariableValue(curTarget[r], targetVarName, t);
394        t++;
395      }
396    }
397
398    private static ISymbolicRegressionSolution CreateSymbolicSolution(List<IRegressionModel> models, double nu, IRegressionProblemData problemData) {
399      var symbModels = models.OfType<ISymbolicRegressionModel>();
400      var lowerLimit = symbModels.Min(m => m.LowerEstimationLimit);
401      var upperLimit = symbModels.Max(m => m.UpperEstimationLimit);
402      var interpreter = new SymbolicDataAnalysisExpressionTreeLinearInterpreter();
403      var progRootNode = new ProgramRootSymbol().CreateTreeNode();
404      var startNode = new StartSymbol().CreateTreeNode();
405
406      var addNode = new Addition().CreateTreeNode();
407      var mulNode = new Multiplication().CreateTreeNode();
408      var scaleNode = (ConstantTreeNode)new Constant().CreateTreeNode(); // all models are scaled using the same nu
409      scaleNode.Value = nu;
410
411      foreach (var m in symbModels) {
412        var relevantPart = m.SymbolicExpressionTree.Root.GetSubtree(0).GetSubtree(0); // skip root and start
413        addNode.AddSubtree((ISymbolicExpressionTreeNode)relevantPart.Clone());
414      }
415
416      mulNode.AddSubtree(addNode);
417      mulNode.AddSubtree(scaleNode);
418      startNode.AddSubtree(mulNode);
419      progRootNode.AddSubtree(startNode);
420      var t = new SymbolicExpressionTree(progRootNode);
421      var combinedModel = new SymbolicRegressionModel(problemData.TargetVariable, t, interpreter, lowerLimit, upperLimit);
422      var sol = new SymbolicRegressionSolution(combinedModel, problemData);
423      return sol;
424    }
425
426    private static bool TrySetProblemData(IAlgorithm alg, IRegressionProblemData problemData) {
427      var prob = alg.Problem as IRegressionProblem;
428      // there is already a problem and it is compatible -> just set problem data
429      if (prob != null) {
430        prob.ProblemDataParameter.Value = problemData;
431        return true;
432      }
433      else return false;
434    }
435
436    private static bool TryExecute(IAlgorithm alg, int seed, string regressionAlgorithmResultName, out IRegressionModel model, out IRun run) {
437      model = null;
438      SetSeed(alg, seed);
439      using (var wh = new AutoResetEvent(false)) {
440        Exception ex = null;
441        EventHandler<EventArgs<Exception>> handler = (sender, args) => {
442          ex = args.Value;
443          wh.Set();
444        };
445        EventHandler handler2 = (sender, args) => wh.Set();
446        alg.ExceptionOccurred += handler;
447        alg.Stopped += handler2;
448        try {
449          alg.Prepare();
450          alg.Start();
451          wh.WaitOne();
452
453          if (ex != null) throw new AggregateException(ex);
454          run = alg.Runs.Last();
455          alg.Runs.Clear();
456          var sols = alg.Results.Select(r => r.Value).OfType<IRegressionSolution>();
457          if (!sols.Any()) return false;
458          var sol = sols.First();
459          if (sols.Skip(1).Any()) {
460            // more than one solution => use regressionAlgorithmResult
461            if (alg.Results.ContainsKey(regressionAlgorithmResultName)) {
462              sol = (IRegressionSolution)alg.Results[regressionAlgorithmResultName].Value;
463            }
464          }
465          var symbRegSol = sol as SymbolicRegressionSolution;
466          // only accept symb reg solutions that do not hit the estimation limits
467          // NaN evaluations would not be critical but are problematic if we want to combine all symbolic models into a single symbolic model
468          if (symbRegSol == null ||
469              (symbRegSol.TrainingLowerEstimationLimitHits == 0 && symbRegSol.TrainingUpperEstimationLimitHits == 0 &&
470               symbRegSol.TestLowerEstimationLimitHits == 0 && symbRegSol.TestUpperEstimationLimitHits == 0) &&
471              symbRegSol.TrainingNaNEvaluations == 0 && symbRegSol.TestNaNEvaluations == 0) {
472            model = sol.Model;
473          }
474        }
475        finally {
476          alg.ExceptionOccurred -= handler;
477          alg.Stopped -= handler2;
478        }
479      }
480      return model != null;
481    }
482
483    private static void SetSeed(IAlgorithm alg, int seed) {
484      // no common interface for algs that use a PRNG -> use naming convention to set seed
485      var paramItem = alg as IParameterizedItem;
486
487      if (paramItem.Parameters.ContainsKey("SetSeedRandomly")) {
488        ((BoolValue)paramItem.Parameters["SetSeedRandomly"].ActualValue).Value = false;
489        ((IntValue)paramItem.Parameters["Seed"].ActualValue).Value = seed;
490      }
491      else {
492        throw new ArgumentException("Base learner does not have a seed parameter (algorithm {0})", alg.Name);
493      }
494    }
495  }
496}
Note: See TracBrowser for help on using the repository browser.