Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Algorithms.DataAnalysis.Glmnet/3.4/ElasticNetLinearRegression.cs @ 13927

Last change on this file since 13927 was 13927, checked in by gkronber, 8 years ago

#745: import of first implementation of elastic-net LR

File size: 20.5 KB
Line 
1using System;
2using System.Linq;
3using System.Runtime.InteropServices;
4using HeuristicLab.Algorithms.DataAnalysis;
5using HeuristicLab.Analysis;
6using HeuristicLab.Common;
7using HeuristicLab.Core;
8using HeuristicLab.Data;
9using HeuristicLab.Optimization;
10using HeuristicLab.Parameters;
11using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
12using HeuristicLab.Problems.DataAnalysis;
13
14namespace HeuristicLab.LibGlmNet {
15  [Item("Elastic-net Linear Regression (LR)", "Linear regression with elastic-net regularization (wrapper for glmnet)")]
16  [Creatable(CreatableAttribute.Categories.DataAnalysisRegression, Priority = 110)]
17  [StorableClass]
18  public sealed class ElasticNetLinearRegression : FixedDataAnalysisAlgorithm<IRegressionProblem> {
19    private const string PenalityParameterName = "Penality";
20    #region parameters
21    public IFixedValueParameter<DoubleValue> PenalityParameter {
22      get { return (IFixedValueParameter<DoubleValue>)Parameters[PenalityParameterName]; }
23    }
24    #endregion
25    #region properties
26    public double Penality {
27      get { return PenalityParameter.Value.Value; }
28      set { PenalityParameter.Value.Value = value; }
29    }
30    #endregion
31
32    [StorableConstructor]
33    private ElasticNetLinearRegression(bool deserializing) : base(deserializing) { }
34    private ElasticNetLinearRegression(ElasticNetLinearRegression original, Cloner cloner)
35      : base(original, cloner) {
36    }
37    public ElasticNetLinearRegression() : base() {
38      Problem = new RegressionProblem();
39      Parameters.Add(new FixedValueParameter<DoubleValue>(PenalityParameterName, "Penalty factor for balancing between ridge (0.0) and lasso (1.0) regression", new DoubleValue(0.5)));
40    }
41
42    [StorableHook(HookType.AfterDeserialization)]
43    private void AfterDeserialization() { }
44
45    public override IDeepCloneable Clone(Cloner cloner) {
46      return new ElasticNetLinearRegression(this, cloner);
47    }
48
49    protected override void Run() {
50      double[] lambda;
51      double[] rsq;
52      double[,] coeff;
53      double[] intercept;
54      RunElasticNetLinearRegression(Problem.ProblemData, Penality, out lambda, out rsq, out coeff, out intercept);
55
56      var coeffTable = new DataTable("Coefficient Paths", "The paths of coefficient values over different lambda values");
57      var nLambdas = lambda.Length;
58      var nCoeff = coeff.GetLength(1);
59      var dataRows = new DataRow[nCoeff];
60      for (int i = 0; i < nCoeff; i++) {
61        var coeffId = string.Format("c_{0}", i);
62        dataRows[i] = new DataRow(coeffId, coeffId, Enumerable.Range(0, nLambdas).Select(r => coeff[r, i]).ToArray());
63        coeffTable.Rows.Add(dataRows[i]);
64      }
65
66      Results.Add(new Result(coeffTable.Name, coeffTable.Description, coeffTable));
67
68      var rsqTable = new DataTable("R-Squared", "Path of R² values over different lambda values");
69      rsqTable.Rows.Add(new DataRow("R-Squared", "Path of R² values over different lambda values", rsq));
70      Results.Add(new Result(rsqTable.Name, rsqTable.Description, coeffTable));
71    }
72
73    //     private static void TestTower() {
74    //       var prov = new HeuristicLab.Problems.Instances.DataAnalysis.RegressionRealWorldInstanceProvider();
75    //       var towerDesc = prov.GetDataDescriptors().Where(dd => dd.Name.Contains("Tower")).Single();
76    //       try {
77    //         var problemData = prov.LoadData(towerDesc);
78    //
79    //         double[] lambda;
80    //         double[] rsq;
81    //         double[,] coeff;
82    //         double[] intercept;
83    //         RunElasticNetLinearRegression(problemData, 0.5, out lambda, out rsq, out coeff, out intercept);
84    //
85    //         for (int i = 0; i < rsq.Length; i++) {
86    //           Console.WriteLine(rsq[i]);
87    //         }
88    //
89    //         double rsq0;
90    //         CreateElasticNetLinearRegressionSolution(problemData, 0.5, lambda.Skip(20).First(), out rsq0);
91    //
92    //         CreateElasticNetLinearRegressionSolution(problemData, 0.5, lambda.Skip(20).Take(10).ToArray(), out rsq);
93    //
94    //
95    //       }
96    //       catch (Exception e) {
97    //       }
98    //     }
99
100    public static double[] CreateElasticNetLinearRegressionSolution(IRegressionProblemData problemData, double penalty, double lambda,
101            out double rsq,
102            double coeffLowerBound = double.NegativeInfinity, double coeffUpperBound = double.PositiveInfinity) {
103      double[] rsqs;
104      // run for exactly one lambda
105      var coeffs = CreateElasticNetLinearRegressionSolution(problemData, penalty, new double[] { lambda }, out rsqs, coeffLowerBound, coeffUpperBound);
106      rsq = rsqs[0];
107      return coeffs[0];
108    }
109    public static double[][] CreateElasticNetLinearRegressionSolution(IRegressionProblemData problemData, double penalty, double[] lambda,
110            out double[] rsq,
111            double coeffLowerBound = double.NegativeInfinity, double coeffUpperBound = double.PositiveInfinity,
112            int maxVars = -1) {
113      // run for multiple user-supplied lambdas
114      double[,] coeff;
115      double[] intercept;
116      RunElasticNetLinearRegression(problemData, penalty, lambda.Length, 1.0, lambda, out lambda, out rsq, out coeff, out intercept, coeffLowerBound, coeffUpperBound, maxVars);
117
118      int nRows = intercept.Length;
119      int nCols = coeff.GetLength(1) + 1;
120      double[][] sols = new double[nRows][];
121      for (int solIdx = 0; solIdx < nRows; solIdx++) {
122        sols[solIdx] = new double[nCols];
123        for (int cIdx = 0; cIdx < nCols - 1; cIdx++) {
124          sols[solIdx][cIdx] = coeff[solIdx, cIdx];
125        }
126        sols[solIdx][nCols - 1] = intercept[solIdx];
127      }
128      return sols;
129    }
130
131    public static void RunElasticNetLinearRegression(IRegressionProblemData problemData, double penalty,
132      out double[] lambda, out double[] rsq, out double[,] coeff, out double[] intercept,
133      double coeffLowerBound = double.NegativeInfinity, double coeffUpperBound = double.PositiveInfinity,
134      int maxVars = -1
135      ) {
136      double[] userLambda = new double[0];
137      // automatically determine lambda values (maximum 100 different lambda values)
138      RunElasticNetLinearRegression(problemData, penalty, 100, 0.0, userLambda, out lambda, out rsq, out coeff, out intercept, coeffLowerBound, coeffUpperBound, maxVars);
139    }
140
141    /// <summary>
142    /// Elastic net with squared-error-loss for dense predictor matrix, runs the full path of all lambdas
143    /// </summary>
144    /// <param name="problemData">Predictor target matrix x and target vector y</param>
145    /// <param name="penalty">Penalty for balance between ridge (0.0) and lasso (1.0) regression</param>
146    /// <param name="nlam">Maximum number of lambda values (default 100)</param>
147    /// <param name="flmin">User control of lambda values (&lt;1.0 => minimum lambda = flmin * (largest lambda value), >= 1.0 => use supplied lambda values</param>
148    /// <param name="ulam">User supplied lambda values</param>
149    /// <param name="lambda">Output lambda values</param>
150    /// <param name="rsq">Vector of R² values for each set of coefficients along the path</param>
151    /// <param name="coeff">Vector of coefficient vectors for each solution along the path</param>
152    /// <param name="intercept">Vector of intercepts for each solution along the path</param>
153    /// <param name="coeffLowerBound">Optional lower bound for all coefficients</param>
154    /// <param name="coeffUpperBound">Optional upper bound for all coefficients</param>
155    /// <param name="maxVars">Maximum allowed number of variables in each solution along the path (-1 => all variables are allowed)</param>
156    private static void RunElasticNetLinearRegression(IRegressionProblemData problemData, double penalty,
157  int nlam, double flmin, double[] ulam, out double[] lambda, out double[] rsq, out double[,] coeff, out double[] intercept,
158  double coeffLowerBound = double.NegativeInfinity, double coeffUpperBound = double.PositiveInfinity,
159  int maxVars = -1
160  ) {
161      if (penalty < 0.0 || penalty > 1.0) throw new ArgumentException("0 <= penalty <= 1", "penalty");
162
163      double[,] x;
164      double[] y;
165      int numObs;
166      int numVars;
167      PrepareData(problemData, out x, out y, out numObs, out numVars);
168
169      int ka = 1; // => covariance updating algorithm
170      double parm = penalty;
171      double[] w = Enumerable.Repeat(1.0, numObs).ToArray(); // all observations have the same weight
172      int[] jd = new int[1]; // do not force to use any of the variables
173      double[] vp = Enumerable.Repeat(1.0, numVars).ToArray(); // all predictor variables are unpenalized
174      double[,] cl = new double[numVars, 2]; // use the same bounds for all coefficients
175      for (int i = 0; i < numVars; i++) {
176        cl[i, 0] = coeffLowerBound;
177        cl[i, 1] = coeffUpperBound;
178      }
179
180      int ne = maxVars > 0 ? maxVars : numVars;
181      int nx = numVars;
182      double thr = 1.0e-5; // default value as recommended in glmnet
183      int isd = 0; //  => regression on original predictor variables
184      int intr = 1;  // => do include intercept in model
185      int maxit = 100000; // default value as recommended in glmnet
186      // outputs
187      int lmu = -1;
188      double[,] ca;
189      int[] ia;
190      int[] nin;
191      int nlp = -99;
192      int jerr = -99;
193
194      elnet(ka, parm, numObs, numVars, x, y, w, jd, vp, cl, ne, nx, nlam, flmin, ulam, thr, isd, intr, maxit, out lmu, out intercept, out ca, out ia, out nin, out rsq, out lambda, out nlp, out jerr);
195
196      coeff = new double[lmu, numVars];
197      for (int solIdx = 0; solIdx < lmu; solIdx++) {
198        // uncompress coefficients of solution
199        int selectedNin = nin[solIdx];
200        double[] coefficients;
201        double[] selectedCa = new double[nx];
202        for (int i = 0; i < nx; i++) selectedCa[i] = ca[solIdx, i];
203
204        uncomp(numVars, selectedCa, ia, selectedNin, out coefficients);
205        for (int i = 0; i < coefficients.Length; i++) {
206          coeff[solIdx, i] = coefficients[i];
207        }
208      }
209    }
210
211    private static void PrepareData(IRegressionProblemData problemData, out double[,] x, out double[] y, out int numObs, out int numVars) {
212      numVars = problemData.AllowedInputVariables.Count();
213      numObs = problemData.TrainingIndices.Count();
214
215      x = new double[numVars, numObs];
216      y = new double[numObs];
217      var ds = problemData.Dataset;
218      var targetVar = problemData.TargetVariable;
219      int rIdx = 0;
220      foreach (var row in problemData.TrainingIndices) {
221        int cIdx = 0;
222        foreach (var var in problemData.AllowedInputVariables) {
223          x[cIdx, rIdx] = ds.GetDoubleValue(var, row);
224          cIdx++;
225        }
226        y[rIdx] = ds.GetDoubleValue(targetVar, row);
227        rIdx++;
228      }
229    }
230
231
232    #region dllimport
233    /// <summary>Wrapper for elnet procedure in glmnet library</summary>
234    /// (see: https://cran.r-project.org/web/packages/glmnet/index.html)
235    ///
236    ///  ka = algorithm flag
237    ///       ka=1 => covariance updating algorithm
238    ///       ka=2 => naive algorithm
239    ///  parm = penalty member index(0&lt;= parm &lt;= 1)
240    ///         = 0.0 => ridge
241    ///  = 1.0 => lasso
242    ///    no = number of observations
243    ///    ni = number of predictor variables
244    ///  y(no) = response vector(overwritten)
245    ///  w(no)= observation weights(overwritten)
246    ///  jd(jd(1)+1) = predictor variable deletion flag
247    ///  jd(1) = 0  => use all variables
248    ///       jd(1) != 0 => do not use variables jd(2)...jd(jd(1)+1)
249    ///  vp(ni) = relative penalties for each predictor variable
250    ///       vp(j) = 0 => jth variable unpenalized
251    ///    cl(2, ni) = interval constraints on coefficient values(overwritten)
252    ///  cl(1, j) = lower bound for jth coefficient value(&lt;= 0.0)
253    ///  cl(2, j) = upper bound for jth coefficient value(>= 0.0)
254    ///  ne = maximum number of variables allowed to enter largest model
255    /// (stopping criterion)
256    ///  nx = maximum number of variables allowed to enter all modesl
257    ///  along path(memory allocation, nx > ne).
258    ///  nlam = (maximum)number of lamda values
259    ///    flmin = user control of lamda values(>=0)
260    ///  flmin&lt; 1.0 => minimum lamda = flmin * (largest lamda value)
261    ///  flmin >= 1.0 => use supplied lamda values(see below)
262    ///  ulam(nlam) = user supplied lamda values(ignored if flmin&lt; 1.0)
263    ///  thr = convergence threshold for each lamda solution.
264    ///  iterations stop when the maximum reduction in the criterion value
265    ///       as a result of each parameter update over a single pass
266    ///       is less than thr times the null criterion value.
267    /// (suggested value, thr= 1.0e-5)
268    ///  isd = predictor variable standarization flag:
269    ///  isd = 0 => regression on original predictor variables
270    ///       isd = 1 => regression on standardized predictor variables
271    ///       Note: output solutions always reference original
272    ///             variables locations and scales.
273    ///    intr = intercept flag
274    ///       intr = 0 / 1 => don't/do include intercept in model
275    ///  maxit = maximum allowed number of passes over the data for all lambda
276    ///  values (suggested values, maxit = 100000)
277    ///
278    ///  output:
279    ///
280    ///    lmu = actual number of lamda values(solutions)
281    ///  a0(lmu) = intercept values for each solution
282    ///  ca(nx, lmu) = compressed coefficient values for each solution
283    ///  ia(nx) = pointers to compressed coefficients
284    ///  nin(lmu) = number of compressed coefficients for each solution
285    ///  rsq(lmu) = R**2 values for each solution
286    ///  alm(lmu) = lamda values corresponding to each solution
287    ///  nlp = actual number of passes over the data for all lamda values
288    ///    jerr = error flag:
289    ///  jerr = 0 => no error
290    ///  jerr > 0 => fatal error - no output returned
291    ///          jerr&lt; 7777 => memory allocation error
292    ///          jerr = 7777 => all used predictors have zero variance
293    ///          jerr = 10000 => maxval(vp) &lt;= 0.0
294    ///  jerr&lt; 0 => non fatal error - partial output:
295    ///  Solutions for larger lamdas (1:(k-1)) returned.
296    ///  jerr = -k => convergence for kth lamda value not reached
297    ///             after maxit(see above) iterations.
298    ///  jerr = -10000 - k => number of non zero coefficients along path
299    ///             exceeds nx(see above) at kth lamda value.
300    ///
301    private static void elnet(
302      int ka,
303      double parm,
304      int no,
305      int ni,
306      double[,] x,
307      double[] y,
308      double[] w,
309      int[] jd,
310      double[] vp,
311      double[,] cl,
312      int ne,
313      int nx,
314      int nlam,
315      double flmin,
316      double[] ulam,
317      double thr,
318      int isd,
319      int intr,
320      int maxit,
321      // outputs
322      out int lmu,
323      out double[] a0,
324      out double[,] ca,
325      out int[] ia,
326      out int[] nin,
327      out double[] rsq,
328      out double[] alm,
329      out int nlp,
330      out int jerr
331      ) {
332      // initialize output values and allocate arrays big enough
333      a0 = new double[nlam];
334      ca = new double[nlam, nx];
335      ia = new int[nx];
336      nin = new int[nlam];
337      rsq = new double[nlam];
338      alm = new double[nlam];
339      nlp = -1;
340      jerr = -1;
341      lmu = -1;
342
343      // load correct version of native dll based on process (x86/x64)
344      if (Environment.Is64BitProcess) {
345        elnet_x64(ref ka, ref parm, ref no, ref ni, x, y, w, jd, vp, cl, ref ne, ref ni, ref nlam, ref flmin, ulam, ref thr, ref isd, ref intr, ref maxit, ref lmu, a0, ca, ia, nin, rsq, alm, ref nlp, ref jerr);
346      } else {
347        elnet_x86(ref ka, ref parm, ref no, ref ni, x, y, w, jd, vp, cl, ref ne, ref ni, ref nlam, ref flmin, ulam, ref thr, ref isd, ref intr, ref maxit, ref lmu, a0, ca, ia, nin, rsq, alm, ref nlp, ref jerr);
348      }
349      //  jerr = error flag:
350      //  jerr = 0 => no error
351      //  jerr > 0 => fatal error -no output returned
352      //  jerr < 7777 => memory allocation error
353      //          jerr = 7777 => all used predictors have zero variance
354      //  jerr = 10000 => maxval(vp) <= 0.0
355      //  jerr < 0 => non fatal error - partial output:
356      //      c Solutions for larger lamdas (1:(k - 1)) returned.
357      //  jerr = -k => convergence for kth lamda value not reached
358      //             after maxit(see above) iterations.
359      //          jerr = -10000 - k => number of non zero coefficients along path
360      //             exceeds nx(see above) at kth lamda value.
361      if (jerr != 0) {
362        if (jerr > 0 && jerr < 7777) throw new InvalidOperationException("glmnet: memory allocation error");
363        else if (jerr == 7777) throw new InvalidOperationException("glmnet: all used predictors have zero variance");
364        else if (jerr == 10000) throw new InvalidOperationException("glmnet: maxval(vp) <= 0.0");
365        else if (jerr < 0 && jerr > -1000) throw new InvalidOperationException(string.Format("glmnet: convergence for {0}th lamda value not reached after maxit iterations ", -jerr));
366        else if (jerr <= -10000) throw new InvalidOperationException(string.Format("glmnet: number of non zero coefficients along path exceeds number of maximally allowed variables (nx) at {0}th lamda value", -jerr - 10000));
367        else throw new InvalidOperationException(string.Format("glmnet: error {0}", jerr));
368      }
369
370
371      // resize arrays to the capacity that is acutally necessary for the results
372      Array.Resize(ref a0, lmu);
373      Array.Resize(ref nin, lmu);
374      Array.Resize(ref rsq, lmu);
375      Array.Resize(ref alm, lmu);
376    }
377
378    [DllImport("glmnet-x86.dll", EntryPoint = "elnet_", CallingConvention = CallingConvention.Cdecl)]
379    private static extern void elnet_x86(
380      ref int ka,
381      ref double parm,
382      ref int no,
383      ref int ni,
384      double[,] x,
385      double[] y,
386      double[] w,
387      int[] jd,
388      double[] vp,
389      double[,] cl,
390      ref int ne,
391      ref int nx,
392      ref int nlam,
393      ref double flmin,
394      double[] ulam,
395      ref double thr,
396      ref int isd,
397      ref int intr,
398      ref int maxit,
399      // outputs:
400      ref int lmu,
401      [Out] double[] a0,
402      [Out] double[,] ca,
403      [Out] int[] ia,
404      [Out] int[] nin,
405      [Out] double[] rsq,
406      [Out] double[] alm,
407      ref int nlp,
408      ref int jerr
409      );
410    [DllImport("glmnet-x64.dll", EntryPoint = "elnet_", CallingConvention = CallingConvention.Cdecl)]
411    private static extern void elnet_x64(
412      ref int ka,
413      ref double parm,
414      ref int no,
415      ref int ni,
416      double[,] x,
417      double[] y,
418      double[] w,
419      int[] jd,
420      double[] vp,
421      double[,] cl,
422      ref int ne,
423      ref int nx,
424      ref int nlam,
425      ref double flmin,
426      double[] ulam,
427      ref double thr,
428      ref int isd,
429      ref int intr,
430      ref int maxit,
431      // outputs:
432      ref int lmu,
433      [Out] double[] a0,
434      [Out] double[,] ca,
435      [Out] int[] ia,
436      [Out] int[] nin,
437      [Out] double[] rsq,
438      [Out] double[] alm,
439      ref int nlp,
440      ref int jerr
441      );
442
443
444    /// <summary>Wrapper for uncompress coefficient vector for particular solution in glmnet</summary>
445    /// (see: https://cran.r-project.org/web/packages/glmnet/index.html)
446    ///
447    /// call uncomp(ni, ca, ia, nin, a)
448    ///
449    /// input:
450    ///
451    ///    ni = total number of predictor variables
452    ///    ca(nx) = compressed coefficient values for the solution
453    /// ia(nx) = pointers to compressed coefficients
454    /// nin = number of compressed coefficients for the solution
455    ///
456    /// output:
457    ///
458    ///    a(ni) =  uncompressed coefficient vector
459    ///             referencing original variables
460    ///
461    private static void uncomp(int numVars, double[] ca, int[] ia, int nin, out double[] a) {
462      a = new double[numVars];
463      // load correct version of native dll based on process (x86/x64)
464      if (Environment.Is64BitProcess) {
465        uncomp_x64(ref numVars, ca, ia, ref nin, a);
466      } else {
467        uncomp_x86(ref numVars, ca, ia, ref nin, a);
468      }
469    }
470
471    [DllImport("glmnet-x86.dll", EntryPoint = "uncomp_", CallingConvention = CallingConvention.Cdecl)]
472    private static extern void uncomp_x86(ref int numVars, double[] ca, int[] ia, ref int nin, double[] a);
473    [DllImport("glmnet-x64.dll", EntryPoint = "uncomp_", CallingConvention = CallingConvention.Cdecl)]
474    private static extern void uncomp_x64(ref int numVars, double[] ca, int[] ia, ref int nin, double[] a);
475
476    #endregion
477  }
478}
Note: See TracBrowser for help on using the repository browser.