/* multifit/gcv.c * * Copyright (C) 2016 Patrick Alken * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * References: * * [1] P. C. Hansen, "Discrete Inverse Problems: Insight and Algorithms," * SIAM Press, 2010. */ #include #include #include #include #include #include #include #include typedef struct { const gsl_vector * S; const gsl_vector * UTy; double delta0; size_t np; gsl_vector * workp; } gcv_params; static double gcv_func(double lambda, void * params); /* gsl_multifit_linear_gcv_init() Initialize Generalized Cross Validation parameters Inputs: y - right hand side vector reg_param - (output) regularization parameters UTy - (output) U^T y delta0 - (output) delta0 work - workspace */ int gsl_multifit_linear_gcv_init(const gsl_vector * y, gsl_vector * reg_param, gsl_vector * UTy, double * delta0, gsl_multifit_linear_workspace * work) { const size_t n = y->size; if (n != work->n) { GSL_ERROR("y vector does not match workspace", GSL_EBADLEN); } else if (UTy->size != work->p) { GSL_ERROR ("UTy vector does not match workspace", GSL_EBADLEN); } else { const size_t p = work->p; gsl_matrix_view U = gsl_matrix_submatrix(work->A, 0, 0, n, p); gsl_vector_view S = gsl_vector_subvector(work->S, 0, p); const double smax = gsl_vector_get(&S.vector, 0); const double smin = gsl_vector_get(&S.vector, p - 1); double dr; /* residual error from projection */ double normy = gsl_blas_dnrm2(y); double normUTy; /* compute projection UTy = U^T y */ gsl_blas_dgemv (CblasTrans, 1.0, &U.matrix, y, 0.0, UTy); normUTy = gsl_blas_dnrm2(UTy); /* dr = ||y||^2 - ||U^T y||^2 */ dr = (normy + normUTy) * (normy - normUTy); /* calculate regularization parameters */ gsl_multifit_linear_lreg(smin, smax, reg_param); if (n > p && dr > 0.0) *delta0 = dr; else *delta0 = 0.0; return GSL_SUCCESS; } } /* gsl_multifit_linear_gcv_curve() Calculate Generalized Cross Validation curve for a set of regularization parameters Inputs: reg_param - regularization parameters UTy - U^T y vector, size p delta0 - delta0 G - (output) GCV curve values work - workspace */ int gsl_multifit_linear_gcv_curve(const gsl_vector * reg_param, const gsl_vector * UTy, const double delta0, gsl_vector * G, gsl_multifit_linear_workspace * work) { const size_t n = work->n; const size_t p = work->p; const size_t N = reg_param->size; /* number of points on GCV curve */ if (UTy->size != p) { GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN); } else if (G->size != N) { GSL_ERROR ("size of reg_param and G vectors do not match", GSL_EBADLEN); } else { size_t i; gsl_vector_view S = gsl_vector_subvector(work->S, 0, p); gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p); gcv_params params; params.S = &S.vector; params.UTy = UTy; params.delta0 = delta0; params.np = n - p; params.workp = &workp.vector; for (i = 0; i < N; ++i) { double lambdai = gsl_vector_get(reg_param, i); double Gi = gcv_func(lambdai, ¶ms); gsl_vector_set(G, i, Gi); } return GSL_SUCCESS; } } /* gsl_multifit_linear_gcv_min() Find regularization parameter which minimizes GCV curve Inputs: reg_param - regularization parameters UTy - U^T y vector, size p G - GCV curve values delta0 - delta0 lambda - (output) optimal regularization parameter work - workspace */ int gsl_multifit_linear_gcv_min(const gsl_vector * reg_param, const gsl_vector * UTy, const gsl_vector * G, const double delta0, double * lambda, gsl_multifit_linear_workspace * work) { const size_t n = work->n; const size_t p = work->p; const size_t npts = reg_param->size; /* number of points on GCV curve */ if (UTy->size != p) { GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN); } else if (G->size != npts) { GSL_ERROR ("size of reg_param and G vectors do not match", GSL_EBADLEN); } else { int status; const size_t max_iter = 500; const double tol = 1.0e-4; gsl_vector_view S = gsl_vector_subvector(work->S, 0, p); gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p); gcv_params params; int idxG = (int) gsl_vector_min_index(G); double a = gsl_vector_get(reg_param, GSL_MIN(idxG + 1, (int) npts - 1)); double b = gsl_vector_get(reg_param, GSL_MAX(idxG - 1, 0)); double m = gsl_vector_get(reg_param, idxG); size_t iter = 0; gsl_function F; /* XXX FIXME */ gsl_min_fminimizer *min_workspace_p; if (idxG == 0 || idxG == ((int)npts - 1)) { /* the minimum is an endpoint of the curve, no need to search */ *lambda = m; return GSL_SUCCESS; } /* XXX FIXME */ min_workspace_p = gsl_min_fminimizer_alloc(gsl_min_fminimizer_brent); params.S = &S.vector; params.UTy = UTy; params.delta0 = delta0; params.np = n - p; params.workp = &workp.vector; F.function = gcv_func; F.params = ¶ms; gsl_min_fminimizer_set(min_workspace_p, &F, m, a, b); do { iter++; status = gsl_min_fminimizer_iterate(min_workspace_p); a = gsl_min_fminimizer_x_lower(min_workspace_p); b = gsl_min_fminimizer_x_upper(min_workspace_p); status = gsl_min_test_interval(a, b, 0.0, tol); } while (status == GSL_CONTINUE && iter < max_iter); if (status == GSL_SUCCESS) *lambda = gsl_min_fminimizer_minimum(min_workspace_p); else status = GSL_EMAXITER; gsl_min_fminimizer_free(min_workspace_p); return status; } } /* gsl_multifit_linear_gcv_calc() Calculate GCV function G(lambda) for given lambda Inputs: reg_param - regularization parameters UTy - U^T y vector, size p delta0 - delta0 G - (output) GCV curve values work - workspace */ double gsl_multifit_linear_gcv_calc(const double lambda, const gsl_vector * UTy, const double delta0, gsl_multifit_linear_workspace * work) { const size_t n = work->n; const size_t p = work->p; if (UTy->size != p) { GSL_ERROR_VAL("UTy vector does not match workspace", GSL_EBADLEN, 0.0); } else { gsl_vector_view S = gsl_vector_subvector(work->S, 0, p); gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p); gcv_params params; double G; params.S = &S.vector; params.UTy = UTy; params.delta0 = delta0; params.np = n - p; params.workp = &workp.vector; G = gcv_func(lambda, ¶ms); return G; } } /* gsl_multifit_linear_gcv() Calculate Generalized Cross Validation curve for a set of regularization parameters Inputs: y - right hand side vector reg_param - (output) regularization parameters G - (output) GCV curve values lambda - (output) optimal regularization parameter which minimizes GCV curve G_lambda - (output) G(lambda) value at optimal parameter work - workspace */ int gsl_multifit_linear_gcv(const gsl_vector * y, gsl_vector * reg_param, gsl_vector * G, double * lambda, double * G_lambda, gsl_multifit_linear_workspace * work) { const size_t n = y->size; const size_t N = G->size; /* number of points on GCV curve */ if (n != work->n) { GSL_ERROR("y vector does not match workspace", GSL_EBADLEN); } else if (reg_param->size != N) { GSL_ERROR ("size of reg_param and G vectors do not match", GSL_EBADLEN); } else { int status; const size_t p = work->p; gsl_vector_view UTy = gsl_vector_subvector(work->xt, 0, p); double delta0; status = gsl_multifit_linear_gcv_init(y, reg_param, &UTy.vector, &delta0, work); if (status) return status; status = gsl_multifit_linear_gcv_curve(reg_param, &UTy.vector, delta0, G, work); if (status) return status; status = gsl_multifit_linear_gcv_min(reg_param, &UTy.vector, G, delta0, lambda, work); if (status) return status; *G_lambda = gsl_multifit_linear_gcv_calc(*lambda, &UTy.vector, delta0, work); return GSL_SUCCESS; } } static double gcv_func(double lambda, void * params) { gcv_params * par = (gcv_params *) params; const gsl_vector *S = par->S; const gsl_vector *UTy = par->UTy; double delta0 = par->delta0; size_t np = par->np; gsl_vector *workp = par->workp; const size_t p = S->size; size_t i; double lambda_sq = lambda * lambda; double G, d, norm; double sumf = 0.0; /* compute workp = 1 - filter_factors */ for (i = 0; i < p; ++i) { double si = gsl_vector_get(S, i); double fi = lambda_sq / (si * si + lambda_sq); gsl_vector_set(workp, i, fi); sumf += fi; } d = (double)np + sumf; gsl_vector_mul(workp, UTy); norm = gsl_blas_dnrm2(workp); G = (norm*norm + delta0) / (d * d); return G; }