Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2521_ProblemRefactoring/HeuristicLab.Algorithms.DataAnalysis/3.4/GaussianProcess/GaussianProcessCovarianceOptimizationProblem.cs @ 16947

Last change on this file since 16947 was 16947, checked in by mkommend, 5 years ago

#2521: Adapted GP basic problems to use read-only value parameters.

File size: 19.1 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2019 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.Linq;
24using HEAL.Attic;
25using HeuristicLab.Common;
26using HeuristicLab.Core;
27using HeuristicLab.Data;
28using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
29using HeuristicLab.Optimization;
30using HeuristicLab.Parameters;
31using HeuristicLab.Problems.DataAnalysis;
32using HeuristicLab.Problems.Instances;
33
34
35namespace HeuristicLab.Algorithms.DataAnalysis {
36  [Item("Gaussian Process Covariance Optimization Problem", "")]
37  [Creatable(CreatableAttribute.Categories.GeneticProgrammingProblems, Priority = 300)]
38  [StorableType("A3EA7CE7-78FA-48FF-9DD5-FBE5AB770A99")]
39  public sealed class GaussianProcessCovarianceOptimizationProblem : SymbolicExpressionTreeProblem, IStatefulItem, IRegressionProblem, IProblemInstanceConsumer<IRegressionProblemData>, IProblemInstanceExporter<IRegressionProblemData> {
40    #region static variables and ctor
41    private static readonly CovarianceMaternIso maternIso1;
42    private static readonly CovarianceMaternIso maternIso3;
43    private static readonly CovarianceMaternIso maternIso5;
44    private static readonly CovariancePiecewisePolynomial piecewisePoly0;
45    private static readonly CovariancePiecewisePolynomial piecewisePoly1;
46    private static readonly CovariancePiecewisePolynomial piecewisePoly2;
47    private static readonly CovariancePiecewisePolynomial piecewisePoly3;
48    private static readonly CovariancePolynomial poly2;
49    private static readonly CovariancePolynomial poly3;
50    private static readonly CovarianceSpectralMixture spectralMixture1;
51    private static readonly CovarianceSpectralMixture spectralMixture3;
52    private static readonly CovarianceSpectralMixture spectralMixture5;
53    private static readonly CovarianceLinear linear;
54    private static readonly CovarianceLinearArd linearArd;
55    private static readonly CovarianceNeuralNetwork neuralNetwork;
56    private static readonly CovariancePeriodic periodic;
57    private static readonly CovarianceRationalQuadraticIso ratQuadraticIso;
58    private static readonly CovarianceRationalQuadraticArd ratQuadraticArd;
59    private static readonly CovarianceSquaredExponentialArd sqrExpArd;
60    private static readonly CovarianceSquaredExponentialIso sqrExpIso;
61
62    static GaussianProcessCovarianceOptimizationProblem() {
63      // cumbersome initialization because of ConstrainedValueParameters
64      maternIso1 = new CovarianceMaternIso(); SetConstrainedValueParameter(maternIso1.DParameter, 1);
65      maternIso3 = new CovarianceMaternIso(); SetConstrainedValueParameter(maternIso3.DParameter, 3);
66      maternIso5 = new CovarianceMaternIso(); SetConstrainedValueParameter(maternIso5.DParameter, 5);
67
68      piecewisePoly0 = new CovariancePiecewisePolynomial(); SetConstrainedValueParameter(piecewisePoly0.VParameter, 0);
69      piecewisePoly1 = new CovariancePiecewisePolynomial(); SetConstrainedValueParameter(piecewisePoly1.VParameter, 1);
70      piecewisePoly2 = new CovariancePiecewisePolynomial(); SetConstrainedValueParameter(piecewisePoly2.VParameter, 2);
71      piecewisePoly3 = new CovariancePiecewisePolynomial(); SetConstrainedValueParameter(piecewisePoly3.VParameter, 3);
72
73      poly2 = new CovariancePolynomial(); poly2.DegreeParameter.Value.Value = 2;
74      poly3 = new CovariancePolynomial(); poly3.DegreeParameter.Value.Value = 3;
75
76      spectralMixture1 = new CovarianceSpectralMixture(); spectralMixture1.QParameter.Value.Value = 1;
77      spectralMixture3 = new CovarianceSpectralMixture(); spectralMixture3.QParameter.Value.Value = 3;
78      spectralMixture5 = new CovarianceSpectralMixture(); spectralMixture5.QParameter.Value.Value = 5;
79
80      linear = new CovarianceLinear();
81      linearArd = new CovarianceLinearArd();
82      neuralNetwork = new CovarianceNeuralNetwork();
83      periodic = new CovariancePeriodic();
84      ratQuadraticArd = new CovarianceRationalQuadraticArd();
85      ratQuadraticIso = new CovarianceRationalQuadraticIso();
86      sqrExpArd = new CovarianceSquaredExponentialArd();
87      sqrExpIso = new CovarianceSquaredExponentialIso();
88    }
89
90    private static void SetConstrainedValueParameter(IConstrainedValueParameter<IntValue> param, int val) {
91      param.Value = param.ValidValues.Single(v => v.Value == val);
92    }
93
94    #endregion
95
96    #region parameter names
97
98    private const string ProblemDataParameterName = "ProblemData";
99    private const string ConstantOptIterationsParameterName = "Constant optimization steps";
100    private const string RestartsParameterName = "Restarts";
101
102    #endregion
103
104    #region Parameter Properties
105    IParameter IDataAnalysisProblem.ProblemDataParameter { get { return ProblemDataParameter; } }
106
107    public IValueParameter<IRegressionProblemData> ProblemDataParameter {
108      get { return (IValueParameter<IRegressionProblemData>)Parameters[ProblemDataParameterName]; }
109    }
110    public IFixedValueParameter<IntValue> ConstantOptIterationsParameter {
111      get { return (IFixedValueParameter<IntValue>)Parameters[ConstantOptIterationsParameterName]; }
112    }
113    public IFixedValueParameter<IntValue> RestartsParameter {
114      get { return (IFixedValueParameter<IntValue>)Parameters[RestartsParameterName]; }
115    }
116    #endregion
117
118    #region Properties
119
120    public IRegressionProblemData ProblemData {
121      get { return ProblemDataParameter.Value; }
122      set { ProblemDataParameter.Value = value; }
123    }
124    IDataAnalysisProblemData IDataAnalysisProblem.ProblemData { get { return ProblemData; } }
125
126    public int ConstantOptIterations {
127      get { return ConstantOptIterationsParameter.Value.Value; }
128      set { ConstantOptIterationsParameter.Value.Value = value; }
129    }
130
131    public int Restarts {
132      get { return RestartsParameter.Value.Value; }
133      set { RestartsParameter.Value.Value = value; }
134    }
135    #endregion
136
137    public override bool Maximization {
138      get { return true; } // return log likelihood (instead of negative log likelihood as in GPR
139    }
140
141    // problem stores a few variables for information exchange from Evaluate() to Analyze()
142    private readonly object problemStateLocker = new object();
143    [Storable]
144    private double bestQ;
145    [Storable]
146    private double[] bestHyperParameters;
147    [Storable]
148    private IMeanFunction meanFunc;
149    [Storable]
150    private ICovarianceFunction covFunc;
151
152    public GaussianProcessCovarianceOptimizationProblem() : base(new SymbolicExpressionTreeEncoding()) {
153      Parameters.Add(new ValueParameter<IRegressionProblemData>(ProblemDataParameterName, "The data for the regression problem", new RegressionProblemData()));
154      Parameters.Add(new FixedValueParameter<IntValue>(ConstantOptIterationsParameterName, "Number of optimization steps for hyperparameter values", new IntValue(50)));
155      Parameters.Add(new FixedValueParameter<IntValue>(RestartsParameterName, "The number of random restarts for constant optimization.", new IntValue(10)));
156      Parameters["Restarts"].Hidden = true;
157
158
159      var g = new SimpleSymbolicExpressionGrammar();
160      g.AddSymbols(new string[] { "Sum", "Product" }, 2, 2);
161      g.AddTerminalSymbols(new string[]
162      {
163        "Linear",
164        "LinearArd",
165        "MaternIso1",
166        "MaternIso3",
167        "MaternIso5",
168        "NeuralNetwork",
169        "Periodic",
170        "PiecewisePolynomial0",
171        "PiecewisePolynomial1",
172        "PiecewisePolynomial2",
173        "PiecewisePolynomial3",
174        "Polynomial2",
175        "Polynomial3",
176        "RationalQuadraticArd",
177        "RationalQuadraticIso",
178        "SpectralMixture1",
179        "SpectralMixture3",
180        "SpectralMixture5",
181        "SquaredExponentialArd",
182        "SquaredExponentialIso"
183      });
184
185      Encoding.TreeLength = 10;
186      Encoding.TreeDepth = 5;
187      Encoding.GrammarParameter.ReadOnly = false;
188      Encoding.Grammar = g;
189      Encoding.GrammarParameter.ReadOnly = true;
190    }
191
192    public void InitializeState() { ClearState(); }
193    public void ClearState() {
194      meanFunc = null;
195      covFunc = null;
196      bestQ = double.NegativeInfinity;
197      bestHyperParameters = null;
198    }
199
200    private readonly object syncRoot = new object();
201    // Does not produce the same result for the same seed when using parallel engine (see below)!
202    public override double Evaluate(ISymbolicExpressionTree tree, IRandom random) {
203      var meanFunction = new MeanConst();
204      var problemData = ProblemData;
205      var ds = problemData.Dataset;
206      var targetVariable = problemData.TargetVariable;
207      var allowedInputVariables = problemData.AllowedInputVariables.ToArray();
208      var nVars = allowedInputVariables.Length;
209      var trainingRows = problemData.TrainingIndices.ToArray();
210
211      // use the same covariance function for each restart
212      var covarianceFunction = TreeToCovarianceFunction(tree);
213
214      // allocate hyperparameters
215      var hyperParameters = new double[meanFunction.GetNumberOfParameters(nVars) + covarianceFunction.GetNumberOfParameters(nVars) + 1]; // mean + cov + noise
216      double[] bestHyperParameters = new double[hyperParameters.Length];
217      var bestObjValue = new double[1] { double.MinValue };
218
219      // data that is necessary for the objective function
220      var data = Tuple.Create(ds, targetVariable, allowedInputVariables, trainingRows, (IMeanFunction)meanFunction, covarianceFunction, bestObjValue);
221
222      for (int t = 0; t < Restarts; t++) {
223        var prevBest = bestObjValue[0];
224        var prevBestHyperParameters = new double[hyperParameters.Length];
225        Array.Copy(bestHyperParameters, prevBestHyperParameters, bestHyperParameters.Length);
226
227        // initialize hyperparameters
228        hyperParameters[0] = ds.GetDoubleValues(targetVariable).Average(); // mean const
229
230        // Evaluate might be called concurrently therefore access to random has to be synchronized.
231        // However, results of multiple runs with the same seed will be different when using the parallel engine.
232        lock (syncRoot) {
233          for (int i = 0; i < covarianceFunction.GetNumberOfParameters(nVars); i++) {
234            hyperParameters[1 + i] = random.NextDouble() * 2.0 - 1.0;
235          }
236        }
237        hyperParameters[hyperParameters.Length - 1] = 1.0; // s² = exp(2), TODO: other inits better?
238
239        // use alglib.bfgs for hyper-parameter optimization ...
240        double epsg = 0;
241        double epsf = 0.00001;
242        double epsx = 0;
243        double stpmax = 1;
244        int maxits = ConstantOptIterations;
245        alglib.mincgstate state;
246        alglib.mincgreport rep;
247
248        alglib.mincgcreate(hyperParameters, out state);
249        alglib.mincgsetcond(state, epsg, epsf, epsx, maxits);
250        alglib.mincgsetstpmax(state, stpmax);
251        alglib.mincgoptimize(state, ObjectiveFunction, null, data);
252
253        alglib.mincgresults(state, out bestHyperParameters, out rep);
254
255        if (rep.terminationtype < 0) {
256          // error -> restore previous best quality
257          bestObjValue[0] = prevBest;
258          Array.Copy(prevBestHyperParameters, bestHyperParameters, prevBestHyperParameters.Length);
259        }
260      }
261
262      UpdateBestSoFar(bestObjValue[0], bestHyperParameters, meanFunction, covarianceFunction);
263
264      return bestObjValue[0];
265    }
266
267    // updates the overall best quality and overall best model for Analyze()
268    private void UpdateBestSoFar(double bestQ, double[] bestHyperParameters, IMeanFunction meanFunc, ICovarianceFunction covFunc) {
269      lock (problemStateLocker) {
270        if (bestQ > this.bestQ) {
271          this.bestQ = bestQ;
272          this.bestHyperParameters = new double[bestHyperParameters.Length];
273          Array.Copy(bestHyperParameters, this.bestHyperParameters, this.bestHyperParameters.Length);
274          this.meanFunc = meanFunc;
275          this.covFunc = covFunc;
276        }
277      }
278    }
279
280    public override void Analyze(ISymbolicExpressionTree[] trees, double[] qualities, ResultCollection results, IRandom random) {
281      if (!results.ContainsKey("Best Solution Quality")) {
282        results.Add(new Result("Best Solution Quality", typeof(DoubleValue)));
283      }
284      if (!results.ContainsKey("Best Tree")) {
285        results.Add(new Result("Best Tree", typeof(ISymbolicExpressionTree)));
286      }
287      if (!results.ContainsKey("Best Solution")) {
288        results.Add(new Result("Best Solution", typeof(GaussianProcessRegressionSolution)));
289      }
290
291      var bestQuality = qualities.Max();
292
293      if (results["Best Solution Quality"].Value == null || bestQuality > ((DoubleValue)results["Best Solution Quality"].Value).Value) {
294        var bestIdx = Array.IndexOf(qualities, bestQuality);
295        var bestClone = (ISymbolicExpressionTree)trees[bestIdx].Clone();
296        results["Best Tree"].Value = bestClone;
297        results["Best Solution Quality"].Value = new DoubleValue(bestQuality);
298        results["Best Solution"].Value = CreateSolution();
299      }
300    }
301
302    private IItem CreateSolution() {
303      var problemData = ProblemData;
304      var ds = problemData.Dataset;
305      var targetVariable = problemData.TargetVariable;
306      var allowedInputVariables = problemData.AllowedInputVariables.ToArray();
307      var trainingRows = problemData.TrainingIndices.ToArray();
308
309      lock (problemStateLocker) {
310        var model = new GaussianProcessModel(ds, targetVariable, allowedInputVariables, trainingRows, bestHyperParameters, (IMeanFunction)meanFunc.Clone(), (ICovarianceFunction)covFunc.Clone());
311        model.FixParameters();
312        return model.CreateRegressionSolution((IRegressionProblemData)ProblemData.Clone());
313      }
314    }
315
316    private void ObjectiveFunction(double[] x, ref double func, double[] grad, object obj) {
317      // we want to optimize the model likelihood by changing the hyperparameters and also return the gradient for each hyperparameter
318      var data = (Tuple<IDataset, string, string[], int[], IMeanFunction, ICovarianceFunction, double[]>)obj;
319      var ds = data.Item1;
320      var targetVariable = data.Item2;
321      var allowedInputVariables = data.Item3;
322      var trainingRows = data.Item4;
323      var meanFunction = data.Item5;
324      var covarianceFunction = data.Item6;
325      var bestObjValue = data.Item7;
326      var hyperParameters = x; // the decision variable vector
327
328      try {
329        var model = new GaussianProcessModel(ds, targetVariable, allowedInputVariables, trainingRows, hyperParameters, meanFunction, covarianceFunction);
330
331        func = model.NegativeLogLikelihood; // mincgoptimize, so we return negative likelihood
332        bestObjValue[0] = Math.Max(bestObjValue[0], -func); // problem itself is a maximization problem
333        var gradients = model.HyperparameterGradients;
334        Array.Copy(gradients, grad, gradients.Length);
335      } catch (ArgumentException) {
336        // building the GaussianProcessModel might fail, in this case we return the worst possible objective value
337        func = 1.0E+300;
338        Array.Clear(grad, 0, grad.Length);
339      }
340    }
341
342    private ICovarianceFunction TreeToCovarianceFunction(ISymbolicExpressionTree tree) {
343      return TreeToCovarianceFunction(tree.Root.GetSubtree(0).GetSubtree(0)); // skip programroot and startsymbol
344    }
345
346    private ICovarianceFunction TreeToCovarianceFunction(ISymbolicExpressionTreeNode node) {
347      switch (node.Symbol.Name) {
348        case "Sum": {
349            var sum = new CovarianceSum();
350            sum.Terms.Add(TreeToCovarianceFunction(node.GetSubtree(0)));
351            sum.Terms.Add(TreeToCovarianceFunction(node.GetSubtree(1)));
352            return sum;
353          }
354        case "Product": {
355            var prod = new CovarianceProduct();
356            prod.Factors.Add(TreeToCovarianceFunction(node.GetSubtree(0)));
357            prod.Factors.Add(TreeToCovarianceFunction(node.GetSubtree(1)));
358            return prod;
359          }
360        // covFunction is cloned by the model so we can reuse instances of terminal covariance functions
361        case "Linear": return linear;
362        case "LinearArd": return linearArd;
363        case "MaternIso1": return maternIso1;
364        case "MaternIso3": return maternIso3;
365        case "MaternIso5": return maternIso5;
366        case "NeuralNetwork": return neuralNetwork;
367        case "Periodic": return periodic;
368        case "PiecewisePolynomial0": return piecewisePoly0;
369        case "PiecewisePolynomial1": return piecewisePoly1;
370        case "PiecewisePolynomial2": return piecewisePoly2;
371        case "PiecewisePolynomial3": return piecewisePoly3;
372        case "Polynomial2": return poly2;
373        case "Polynomial3": return poly3;
374        case "RationalQuadraticArd": return ratQuadraticArd;
375        case "RationalQuadraticIso": return ratQuadraticIso;
376        case "SpectralMixture1": return spectralMixture1;
377        case "SpectralMixture3": return spectralMixture3;
378        case "SpectralMixture5": return spectralMixture5;
379        case "SquaredExponentialArd": return sqrExpArd;
380        case "SquaredExponentialIso": return sqrExpIso;
381        default: throw new InvalidProgramException(string.Format("Found invalid symbol {0}", node.Symbol.Name));
382      }
383    }
384
385
386    // persistence
387    [StorableConstructor]
388    private GaussianProcessCovarianceOptimizationProblem(StorableConstructorFlag _) : base(_) { }
389    [StorableHook(HookType.AfterDeserialization)]
390    private void AfterDeserialization() {
391    }
392
393    // cloning
394    private GaussianProcessCovarianceOptimizationProblem(GaussianProcessCovarianceOptimizationProblem original, Cloner cloner)
395      : base(original, cloner) {
396      bestQ = original.bestQ;
397      meanFunc = cloner.Clone(original.meanFunc);
398      covFunc = cloner.Clone(original.covFunc);
399      if (bestHyperParameters != null) {
400        bestHyperParameters = new double[original.bestHyperParameters.Length];
401        Array.Copy(original.bestHyperParameters, bestHyperParameters, bestHyperParameters.Length);
402      }
403    }
404    public override IDeepCloneable Clone(Cloner cloner) {
405      return new GaussianProcessCovarianceOptimizationProblem(this, cloner);
406    }
407
408    public void Load(IRegressionProblemData data) {
409      this.ProblemData = data;
410      OnProblemDataChanged();
411    }
412
413    public IRegressionProblemData Export() {
414      return ProblemData;
415    }
416
417    #region events
418    public event EventHandler ProblemDataChanged;
419
420
421    private void OnProblemDataChanged() {
422      var handler = ProblemDataChanged;
423      if (handler != null)
424        handler(this, EventArgs.Empty);
425    }
426    #endregion
427
428  }
429}
Note: See TracBrowser for help on using the repository browser.