Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Algorithms.DataAnalysis/3.4/GAM/GeneralizedAdditiveModelAlgorithm.cs @ 17815

Last change on this file since 17815 was 17815, checked in by gkronber, 3 years ago

#2898: fix header

File size: 10.3 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using System.Threading;
26using HEAL.Attic;
27using HeuristicLab.Analysis;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.Data;
31using HeuristicLab.Optimization;
32using HeuristicLab.Parameters;
33using HeuristicLab.Problems.DataAnalysis;
34using HeuristicLab.Random;
35
36namespace HeuristicLab.Algorithms.DataAnalysis {
37  [Item("Generalized Additive Model (GAM)", "Generalized additive model using uni-variate penalized regression splines as base learner.")]
38  [StorableType("98A887E7-73DD-4602-BD6C-2F6B9E6FBBC5")]
39  [Creatable(CreatableAttribute.Categories.DataAnalysisRegression, Priority = 600)]
40  public sealed class GeneralizedAdditiveModelAlgorithm : FixedDataAnalysisAlgorithm<IRegressionProblem> {
41    #region ParameterNames
42
43    private const string IterationsParameterName = "Iterations";
44    private const string LambdaParameterName = "Lambda";
45    private const string SeedParameterName = "Seed";
46    private const string SetSeedRandomlyParameterName = "SetSeedRandomly";
47    private const string CreateSolutionParameterName = "CreateSolution";
48    #endregion
49
50    #region ParameterProperties
51
52    public IFixedValueParameter<IntValue> IterationsParameter {
53      get { return (IFixedValueParameter<IntValue>)Parameters[IterationsParameterName]; }
54    }
55
56    public IFixedValueParameter<DoubleValue> LambdaParameter {
57      get { return (IFixedValueParameter<DoubleValue>)Parameters[LambdaParameterName]; }
58    }
59
60    public IFixedValueParameter<IntValue> SeedParameter {
61      get { return (IFixedValueParameter<IntValue>)Parameters[SeedParameterName]; }
62    }
63
64    public FixedValueParameter<BoolValue> SetSeedRandomlyParameter {
65      get { return (FixedValueParameter<BoolValue>)Parameters[SetSeedRandomlyParameterName]; }
66    }
67
68    public IFixedValueParameter<BoolValue> CreateSolutionParameter {
69      get { return (IFixedValueParameter<BoolValue>)Parameters[CreateSolutionParameterName]; }
70    }
71
72    #endregion
73
74    #region Properties
75
76    public int Iterations {
77      get { return IterationsParameter.Value.Value; }
78      set { IterationsParameter.Value.Value = value; }
79    }
80
81    public double Lambda {
82      get { return LambdaParameter.Value.Value; }
83      set { LambdaParameter.Value.Value = value; }
84    }
85
86    public int Seed {
87      get { return SeedParameter.Value.Value; }
88      set { SeedParameter.Value.Value = value; }
89    }
90
91    public bool SetSeedRandomly {
92      get { return SetSeedRandomlyParameter.Value.Value; }
93      set { SetSeedRandomlyParameter.Value.Value = value; }
94    }
95
96    public bool CreateSolution {
97      get { return CreateSolutionParameter.Value.Value; }
98      set { CreateSolutionParameter.Value.Value = value; }
99    }
100
101    #endregion
102
103    [StorableConstructor]
104    private GeneralizedAdditiveModelAlgorithm(StorableConstructorFlag deserializing)
105      : base(deserializing) {
106    }
107
108    private GeneralizedAdditiveModelAlgorithm(GeneralizedAdditiveModelAlgorithm original, Cloner cloner)
109      : base(original, cloner) {
110    }
111
112    public override IDeepCloneable Clone(Cloner cloner) {
113      return new GeneralizedAdditiveModelAlgorithm(this, cloner);
114    }
115
116    public GeneralizedAdditiveModelAlgorithm() {
117      Problem = new RegressionProblem(); // default problem
118
119      Parameters.Add(new FixedValueParameter<IntValue>(IterationsParameterName,
120        "Number of iterations. Try a large value and check convergence of the error over iterations. Usually, only a few iterations (e.g. 10) are needed for convergence.", new IntValue(10)));
121      Parameters.Add(new FixedValueParameter<DoubleValue>(LambdaParameterName,
122        "The penalty parameter for the penalized regression splines. Set to a value between -8 (weak smoothing) and 8 (strong smooting). Usually, a value between -4 and 4 should be fine", new DoubleValue(3)));
123      Parameters.Add(new FixedValueParameter<IntValue>(SeedParameterName,
124        "The random seed used to initialize the new pseudo random number generator.", new IntValue(0)));
125      Parameters.Add(new FixedValueParameter<BoolValue>(SetSeedRandomlyParameterName,
126        "True if the random seed should be set to a random value, otherwise false.", new BoolValue(true)));
127      Parameters.Add(new FixedValueParameter<BoolValue>(CreateSolutionParameterName,
128        "Flag that indicates if a solution should be produced at the end of the run", new BoolValue(true)));
129      Parameters[CreateSolutionParameterName].Hidden = true;
130    }
131
132    protected override void Run(CancellationToken cancellationToken) {
133      // Set up the algorithm
134      if (SetSeedRandomly) Seed = new System.Random().Next();
135      var rand = new MersenneTwister((uint)Seed);
136
137      // calculates a GAM model using univariate non-linear functions
138      // using backfitting algorithm (see The Elements of Statistical Learning page 298)
139
140      // init
141      var problemData = Problem.ProblemData;
142      var ds = problemData.Dataset;
143      var trainRows = problemData.TrainingIndices;
144      var testRows = problemData.TestIndices;
145      var avgY = problemData.TargetVariableTrainingValues.Average();
146      var inputVars = problemData.AllowedInputVariables.ToArray();
147
148      int nTerms = inputVars.Length;
149
150      #region init results
151      // Set up the results display
152      var iterations = new IntValue(0);
153      Results.Add(new Result("Iterations", iterations));
154
155      var table = new DataTable("Qualities");
156      var rmseRow = new DataRow("RMSE (train)");
157      var rmseRowTest = new DataRow("RMSE (test)");
158      table.Rows.Add(rmseRow);
159      table.Rows.Add(rmseRowTest);
160      Results.Add(new Result("Qualities", table));
161      var curRMSE = new DoubleValue();
162      var curRMSETest = new DoubleValue();
163      Results.Add(new Result("RMSE (train)", curRMSE));
164      Results.Add(new Result("RMSE (test)", curRMSETest));
165
166      // calculate table with residual contributions of each term
167      var rssTable = new DoubleMatrix(nTerms, 1, new string[] { "RSS" }, inputVars);
168      Results.Add(new Result("RSS Values", rssTable));
169      #endregion
170
171      // start with a set of constant models = 0
172      IRegressionModel[] f = new IRegressionModel[nTerms];
173      for (int i = 0; i < f.Length; i++) {
174        f[i] = new ConstantModel(0.0, problemData.TargetVariable);
175      }
176      // init res which contains the current residual vector
177      double[] res = problemData.TargetVariableTrainingValues.Select(yi => yi - avgY).ToArray();
178      double[] resTest = problemData.TargetVariableTestValues.Select(yi => yi - avgY).ToArray();
179
180      curRMSE.Value = res.StandardDeviation();
181      curRMSETest.Value = resTest.StandardDeviation();
182      rmseRow.Values.Add(res.StandardDeviation());
183      rmseRowTest.Values.Add(resTest.StandardDeviation());
184
185
186      double lambda = Lambda;
187      var idx = Enumerable.Range(0, nTerms).ToArray();
188
189      // Loop until iteration limit reached or canceled.
190      for (int i = 0; i < Iterations && !cancellationToken.IsCancellationRequested; i++) {
191        // shuffle order of terms in each iteration to remove bias on earlier terms
192        idx.ShuffleInPlace(rand);
193        foreach (var inputIdx in idx) {
194          var inputVar = inputVars[inputIdx];
195          // first remove the effect of the previous model for the inputIdx (by adding the output of the current model to the residual)
196          AddInPlace(res, f[inputIdx].GetEstimatedValues(ds, trainRows));
197          AddInPlace(resTest, f[inputIdx].GetEstimatedValues(ds, testRows));
198
199          rssTable[inputIdx, 0] = res.Variance();
200          f[inputIdx] = RegressSpline(problemData, inputVar, res, lambda);
201
202          SubtractInPlace(res, f[inputIdx].GetEstimatedValues(ds, trainRows));
203          SubtractInPlace(resTest, f[inputIdx].GetEstimatedValues(ds, testRows));
204        }
205
206        curRMSE.Value = res.StandardDeviation();
207        curRMSETest.Value = resTest.StandardDeviation();
208        rmseRow.Values.Add(curRMSE.Value);
209        rmseRowTest.Values.Add(curRMSETest.Value);
210        iterations.Value = i;
211      }
212
213      // produce solution
214      if (CreateSolution) {
215        var model = new RegressionEnsembleModel(f.Concat(new[] { new ConstantModel(avgY, problemData.TargetVariable) }));
216        model.AverageModelEstimates = false;
217        var solution = model.CreateRegressionSolution((IRegressionProblemData)problemData.Clone());
218        Results.Add(new Result("Ensemble solution", solution));
219      }
220    }
221
222    private IRegressionModel RegressSpline(IRegressionProblemData problemData, string inputVar, double[] target, double lambda) {
223      var x = problemData.Dataset.GetDoubleValues(inputVar, problemData.TrainingIndices).ToArray();
224      var y = (double[])target.Clone();
225      int info;
226      alglib.spline1dinterpolant s;
227      alglib.spline1dfitreport rep;
228      int numKnots = (int)Math.Min(50, 3 * Math.Sqrt(x.Length)); // heuristic for number of knots  (Elements of Statistical Learning)
229
230      alglib.spline1dfitpenalized(x, y, numKnots, lambda, out info, out s, out rep);
231
232      return new Spline1dModel(s.innerobj, problemData.TargetVariable, inputVar);
233    }
234
235
236    private static void AddInPlace(double[] a, IEnumerable<double> enumerable) {
237      int i = 0;
238      foreach (var elem in enumerable) {
239        a[i] += elem;
240        i++;
241      }
242    }
243
244    private static void SubtractInPlace(double[] a, IEnumerable<double> enumerable) {
245      int i = 0;
246      foreach (var elem in enumerable) {
247        a[i] -= elem;
248        i++;
249      }
250    }
251  }
252}
Note: See TracBrowser for help on using the repository browser.