Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2898: copied implementation from branch to trunk.

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