Free cookie consent management tool by TermsFeed Policy Generator

source: branches/RBFRegression/HeuristicLab.Algorithms.DataAnalysis/3.4/RadialBasisFunctions/RadialBasisFunctionModel.cs @ 14872

Last change on this file since 14872 was 14872, checked in by gkronber, 7 years ago

#2699: made a number of changes mainly to RBF regression model

File size: 9.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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.Diagnostics;
25using System.Linq;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
29using HeuristicLab.Problems.DataAnalysis;
30
31namespace HeuristicLab.Algorithms.DataAnalysis {
32  /// <summary>
33  /// Represents an RBF regression model.
34  /// </summary>
35  [StorableClass]
36  [Item("RBFModel", "An RBF regression model")]
37  public sealed class RadialBasisFunctionModel : RegressionModel, IConfidenceRegressionModel {
38    public override IEnumerable<string> VariablesUsedForPrediction {
39      get { return allowedInputVariables; }
40    }
41
42    [Storable]
43    private readonly string[] allowedInputVariables;
44    public string[] AllowedInputVariables {
45      get { return allowedInputVariables; }
46    }
47
48    [Storable]
49    private readonly double[] alpha;
50
51    [Storable]
52    private readonly double[,] trainX; // it is better to store the original training dataset completely because this is more efficient in persistence
53
54    [Storable]
55    private readonly ITransformation<double>[] scaling;
56
57    [Storable]
58    private readonly ICovarianceFunction kernel;
59
60    private double[,] gramInvert; // not storable as it can be large (recreate after deserialization as required)
61
62    [Storable]
63    private readonly double meanOffset; // implementation works for zero-mean target variables
64
65    [StorableConstructor]
66    private RadialBasisFunctionModel(bool deserializing) : base(deserializing) { }
67    private RadialBasisFunctionModel(RadialBasisFunctionModel original, Cloner cloner)
68      : base(original, cloner) {
69      // shallow copies of arrays because they cannot be modified
70      allowedInputVariables = original.allowedInputVariables;
71      alpha = original.alpha;
72      trainX = original.trainX;
73      gramInvert = original.gramInvert;
74      scaling = original.scaling;
75
76      meanOffset = original.meanOffset;
77      if (original.kernel != null)
78        kernel = cloner.Clone(original.kernel);
79    }
80    public RadialBasisFunctionModel(IDataset dataset, string targetVariable, IEnumerable<string> allowedInputVariables, IEnumerable<int> rows,
81      bool scaleInputs, ICovarianceFunction kernel)
82      : base(targetVariable) {
83      if (kernel.GetNumberOfParameters(allowedInputVariables.Count()) > 0) throw new ArgumentException("All parameters in the kernel function must be specified.");
84      name = ItemName;
85      description = ItemDescription;
86      this.allowedInputVariables = allowedInputVariables.ToArray();
87      var trainingRows = rows.ToArray();
88      this.kernel = (ICovarianceFunction)kernel.Clone();
89      try {
90        if (scaleInputs)
91          scaling = CreateScaling(dataset, trainingRows);
92        trainX = ExtractData(dataset, trainingRows, scaling);
93        var y = dataset.GetDoubleValues(targetVariable, trainingRows).ToArray();
94        meanOffset = y.Average();
95        for (int i = 0; i < y.Length; i++) y[i] -= meanOffset;
96        int info;
97        // TODO: improve efficiency by decomposing matrix once instead of solving the system and then inverting the matrix
98        alglib.densesolverlsreport denseSolveRep;
99        gramInvert = BuildGramMatrix(trainX);
100        int n = trainX.GetLength(0);
101        alglib.rmatrixsolvels(gramInvert, n, n, y, 0.0, out info, out denseSolveRep, out alpha);
102        if (info != 1) throw new ArgumentException("Could not create model.");
103
104        alglib.matinvreport report;
105        alglib.rmatrixinverse(ref gramInvert, out info, out report);
106        if (info != 1) throw new ArgumentException("Could not invert matrix. Is it quadratic symmetric positive definite?");
107
108      } catch (alglib.alglibexception ae) {
109        // wrap exception so that calling code doesn't have to know about alglib implementation
110        throw new ArgumentException("There was a problem in the calculation of the RBF model", ae);
111      }
112    }
113
114    private ITransformation<double>[] CreateScaling(IDataset dataset, int[] rows) {
115      var trans = new ITransformation<double>[allowedInputVariables.Length];
116      int i = 0;
117      foreach (var variable in allowedInputVariables) {
118        var lin = new LinearTransformation(allowedInputVariables);
119        var max = dataset.GetDoubleValues(variable, rows).Max();
120        var min = dataset.GetDoubleValues(variable, rows).Min();
121        lin.Multiplier = 1.0 / (max - min);
122        lin.Addend = -min / (max - min);
123        trans[i] = lin;
124        i++;
125      }
126      return trans;
127    }
128
129    private double[,] ExtractData(IDataset dataset, IEnumerable<int> rows, ITransformation<double>[] scaling = null) {
130      double[][] variables;
131      if (scaling != null) {
132        variables =
133          allowedInputVariables.Select((var, i) => scaling[i].Apply(dataset.GetDoubleValues(var, rows)).ToArray())
134            .ToArray();
135      } else {
136        variables =
137        allowedInputVariables.Select(var => dataset.GetDoubleValues(var, rows).ToArray()).ToArray();
138      }
139      int n = variables.First().Length;
140      var res = new double[n, variables.Length];
141      for (int r = 0; r < n; r++)
142        for (int c = 0; c < variables.Length; c++) {
143          res[r, c] = variables[c][r];
144        }
145      return res;
146    }
147
148    public override IDeepCloneable Clone(Cloner cloner) {
149      return new RadialBasisFunctionModel(this, cloner);
150    }
151
152    #region IRegressionModel Members
153    public override IEnumerable<double> GetEstimatedValues(IDataset dataset, IEnumerable<int> rows) {
154      var newX = ExtractData(dataset, rows, scaling);
155      var dim = newX.GetLength(1);
156      var cov = kernel.GetParameterizedCovarianceFunction(new double[0], Enumerable.Range(0, dim).ToArray());
157
158      var pred = new double[newX.GetLength(0)];
159      for (int i = 0; i < pred.Length; i++) {
160        double sum = meanOffset;
161        for (int j = 0; j < alpha.Length; j++) {
162          sum += alpha[j] * cov.CrossCovariance(trainX, newX, j, i);
163        }
164        pred[i] = sum;
165      }
166      return pred;
167    }
168    public override IRegressionSolution CreateRegressionSolution(IRegressionProblemData problemData) {
169      return new ConfidenceRegressionSolution(this, new RegressionProblemData(problemData));
170    }
171    #endregion
172
173    public IEnumerable<double> GetEstimatedVariances(IDataset dataset, IEnumerable<int> rows) {
174      if (gramInvert == null) CreateAndInvertGramMatrix();
175      int n = gramInvert.GetLength(0);
176      var newData = ExtractData(dataset, rows, scaling);
177      var dim = newData.GetLength(1);
178      var cov = kernel.GetParameterizedCovarianceFunction(new double[0], Enumerable.Range(0, dim).ToArray());
179
180      // TODO perf (matrix matrix multiplication)
181      for (int i = 0; i < newData.GetLength(0); i++) {
182        double[] p = new double[n];
183
184        for (int j = 0; j < trainX.GetLength(0); j++) {
185          p[j] = cov.CrossCovariance(trainX, newData, j, i);
186        }
187
188        var Ap = new double[n];
189        alglib.ablas.rmatrixmv(n, n, gramInvert, 0, 0, 0, p, 0, ref Ap, 0);
190        var res = 0.0;
191        // dot product
192        for (int j = 0; j < p.Length; j++) res += p[j] * Ap[j];
193        yield return res > 0 ? res : 0;
194      }
195    }
196    public double LeaveOneOutCrossValidationRootMeanSquaredError() {
197      if (gramInvert == null) CreateAndInvertGramMatrix();
198      var n = gramInvert.GetLength(0);
199      var s = 1.0 / n;
200
201      var sum = 0.0;
202      for (int i = 0; i < alpha.Length; i++) {
203        var x = alpha[i] / gramInvert[i, i];
204        sum += x * x;
205      }
206      sum *= s;
207      return Math.Sqrt(sum);
208    }
209
210    private void CreateAndInvertGramMatrix() {
211      try {
212        gramInvert = BuildGramMatrix(trainX);
213        int info = 0;
214        alglib.matinvreport report;
215        alglib.rmatrixinverse(ref gramInvert, out info, out report);
216        if (info != 1)
217          throw new ArgumentException("Could not invert matrix. Is it quadratic symmetric positive definite?");
218      } catch (alglib.alglibexception) {
219        // wrap exception so that calling code doesn't have to know about alglib implementation
220        throw new ArgumentException("Could not invert matrix. Is it quadratic symmetric positive definite?");
221      }
222    }
223    #region helpers
224    private double[,] BuildGramMatrix(double[,] data) {
225      var n = data.GetLength(0);
226      var dim = data.GetLength(1);
227      var cov = kernel.GetParameterizedCovarianceFunction(new double[0], Enumerable.Range(0, dim).ToArray());
228      var gram = new double[n, n];
229      for (var i = 0; i < n; i++)
230        for (var j = i; j < n; j++) {
231          gram[j, i] = gram[i, j] = cov.Covariance(data, i, j); // symmetric matrix --> half of the work
232        }
233      return gram;
234    }
235
236    #endregion
237  }
238}
Note: See TracBrowser for help on using the repository browser.