Free cookie consent management tool by TermsFeed Policy Generator

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

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

#745: first version of elastic-net alg that runs in HL

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