/** * @brief Low level fitting routine for a Gaussian trend filtering problem. * Function used by tf_admm to fit a Gaussian ADMM trendfilter, or as a * subproblem by tf_admm_glm when using logistic or poisson losses. Fits * the solution for a single value of lambda. Most users will want to call * tf_admm, rather than tf_admm_gauss directly. * * @param y a vector of responses * @param x a vector of response locations; must be in increasing order * @param w a vector of sample weights * @param n the length of y, x, and w * @param k degree of the trendfilter; i.e., k=1 linear * @param max_iter maximum number of ADMM interations; ignored for k=0 * @param lam the value of lambda * @param beta allocated space for output coefficents; must pre-fill as it is used in warm start * @param alpha allocated space for ADMM alpha covariates; must pre-fill as it is used in warm start * @param u allocated space for ADMM u covariates; must pre-fill as it is used in warm start * @param obj allocated space to store the objective; will fill at most max_iter elements * @param iter allocated space to store the number of iterations; will fill just one element * @param rho tuning parameter for the ADMM algorithm; set to 1 for default * @param obj_tol stopping criteria tolerance; set to 1e-10 for default * @param DktDk pointer to the inner product of DktDk * @param verbose 0/1 flag for printing progress * @return void * @see tf_admm */ void tf_admm_gauss (double * y, double * x, double * w, int n, int k, int max_iter, double lam, double * beta, double * alpha, double * u, double * obj, int * iter, double rho, double obj_tol, cs * DktDk, int verbose) { int i; int it; double *v; double *z; double *db; double loss; double pen; cs * kernmat; gqr * kernmat_qr; /* Special case for k=0: skip the ADMM algorithm */ if (k==0) { /* Use Nick's DP algorithm, weighted version */ tf_dp_weight(n,y,w,lam,beta); db = (double *) malloc(n*sizeof(double)); /* Compute objective */ loss = 0; pen = 0; for (i=0; i<n; i++) loss += w[i]*(y[i]-beta[i])*(y[i]-beta[i]); loss = loss/2; tf_dx(x,n,k+1,beta,db); /* IMPORTANT: use k+1 here! */ for (i=0; i<n-k-1; i++) pen += fabs(db[i]); obj[0] = loss+lam*pen; free(db); return; } /* Otherwise we run our ADMM routine */ /* Construct the kernel matrix and its QR decomposition */ kernmat = scalar_plus_diag(DktDk, rho, w); kernmat_qr = glmgen_qr(kernmat); /* Other variables that will be useful during our iterations */ v = (double*) malloc(n*sizeof(double)); z = (double*) malloc(n*sizeof(double)); if (verbose) printf("\nlambda=%0.3e\n",lam); if (verbose) printf("Iteration\tObjective\tLoss\tPenalty\n"); for(it=0; it < max_iter; it++) { /* Update beta: banded linear system (kernel matrix) */ for (i=0; i < n-k; i++) v[i] = alpha[i] + u[i]; tf_dtxtil(x,n,k,v,z); for (i=0; i<n; i++) beta[i] = w[i]*y[i] + rho*z[i]; /* Solve the least squares problem with sparse QR */ glmgen_qrsol(kernmat_qr, beta); /* Update alpha: 1d fused lasso * Build the response vector */ tf_dxtil(x,n,k,beta,v); for (i=0; i<n-k; i++) { z[i] = v[i]-u[i]; } /* Use Nick's DP algorithm */ tf_dp(n-k,z,lam/rho,alpha); /* Update u: dual update */ for (i=0; i<n-k; i++) { u[i] = u[i]+alpha[i]-v[i]; } /* Compute loss */ loss = 0; for (i=0; i<n; i++) loss += w[i]*(y[i]-beta[i])*(y[i]-beta[i]); loss = loss/2; /* Compute penalty */ tf_dx(x,n,k+1,beta,z); /* IMPORTANT: use k+1 here! */ pen = 0; for (i=0; i<n-k-1; i++) pen += fabs(z[i]); obj[it] = loss+lam*pen; if (verbose) printf("%i\t%0.3e\t%0.3e\t%0.3e\n",it+1,obj[it],loss,lam*pen); /* Stop if relative difference of objective values <= obj_tol */ if(it > 0) { if( fabs(obj[it] - obj[it-1]) < fabs(obj[it]) * obj_tol ) break; } } *iter = it; cs_spfree(kernmat); glmgen_gqr_free(kernmat_qr); free(v); free(z); }
/** * @brief Low level fitting routine for a Gaussian trend filtering problem. * Function used by tf_admm to fit a Gaussian ADMM trendfilter, or as a * subproblem by tf_admm_glm when using logistic or poisson losses. Fits * the solution for a single value of lambda. Most users will want to call * tf_admm, rather than tf_admm_gauss directly. * * @param x a vector of data locations; must be in increasing order * @param y a vector of responses * @param w a vector of sample weights * @param n the length of x, y, and w * @param k polynomial degree of the fitted trend; i.e., k=1 for linear * @param max_iter maximum number of ADMM interations; ignored for k=0 * @param lam the value of lambda * @param df allocated space for df value at the solution * @param beta allocated space for output coefficents; must pre-fill as it is used in warm start * @param alpha allocated space for ADMM alpha variable; must pre-fill as it is used in warm start * @param u allocated space for ADMM u variable; must pre-fill as it is used in warm start * @param obj allocated space to store the objective; will fill at most max_iter elements * @param iter allocated space to store the number of iterations; will fill just one element * @param rho tuning parameter for the ADMM algorithm; set to 1 for default * @param obj_tol stopping criteria tolerance; set to 1e-10 for default * @param DktDk pointer to the inner product of DktDk * @param verbose 0/1 flag for printing progress * @return void * @see tf_admm */ void tf_admm_gauss (double * x, double * y, double * w, int n, int k, int max_iter, double lam, int * df, double * beta, double * alpha, double * u, double * obj, int * iter, double rho, double obj_tol, cs * DktDk, int verbose) { int i; int d; int it, itbest; double *v; double *z; double *betabest; double *alphabest; double descent; double variation; cs * kernmat; gqr * kernmat_qr; /* Special case for k=0: skip the ADMM algorithm */ if (k==0) { /* Use Nick's DP algorithm, weighted version */ tf_dp_weight(n,y,w,lam,beta); /* Compute df value */ d = 1; for (i=0; i<n-1; i++) if (beta[i] != beta[i+1]) d += 1; *df = d; /* Compute objective */ v = (double *) malloc(n*sizeof(double)); obj[0] = tf_obj_gauss(x,y,w,n,k,lam,beta,v); free(v); return; } /* Otherwise we run our ADMM routine */ /* Construct the kernel matrix and its QR decomposition */ kernmat = scalar_plus_diag(DktDk, rho, w); kernmat_qr = glmgen_qr(kernmat); /* Other variables that will be useful during our iterations */ v = (double*) malloc(n*sizeof(double)); z = (double*) malloc(n*sizeof(double)); betabest = (double*) malloc(n*sizeof(double)); alphabest = (double*) malloc(n*sizeof(double)); if (verbose) printf("\nlambda=%0.3e\n",lam); if (verbose) printf("Iteration\tObjective\n"); itbest = 0; obj[0] = tf_obj_gauss(x,y,w,n,k,lam,beta,v); memcpy(betabest, beta, n * sizeof(double)); memcpy(alphabest, alpha, n * sizeof(double)); for (it=0; it < max_iter; it++) { /* Update beta: banded linear system (kernel matrix) */ for (i=0; i < n-k; i++) v[i] = alpha[i] + u[i]; tf_dtxtil(x,n,k,v,z); for (i=0; i<n; i++) beta[i] = w[i]*y[i] + rho*z[i]; /* Solve the least squares problem with sparse QR */ glmgen_qrsol(kernmat_qr, beta); /* Update alpha: 1d fused lasso * Build the response vector */ tf_dxtil(x,n,k,beta,v); for (i=0; i<n-k; i++) z[i] = v[i]-u[i]; /* Use Nick's DP algorithm */ tf_dp(n-k,z,lam/rho,alpha); /* Update u: dual update */ for (i=0; i<n-k; i++) u[i] = u[i]+alpha[i]-v[i]; /* Compute objective */ obj[it+1] = tf_obj_gauss(x,y,w,n,k,lam,beta,z); if (verbose) printf("%i\t%0.3e\n",it+1,obj[it]); /* Stop if relative difference of objective values < obj_tol */ descent = obj[itbest] - obj[it+1]; if ( descent > 0 ) { memcpy(betabest, beta, n * sizeof(double)); memcpy(alphabest, alpha, n * sizeof(double)); itbest = it+1; } if (it >= 10) { variation = 0; for (i=0; i < 10; i++ ) variation += fabs(obj[it+1-i] - obj[it-i]); //variation = fabs(obj[it+1] - obj[it]) + fabs(obj[it] - obj[it-1]) + fabs(obj[it-1] - obj[it-2]); if (variation < fabs(obj[itbest]) * 10 * obj_tol) break; } } memcpy(beta, betabest, n * sizeof(double)); memcpy(alpha, alphabest, n * sizeof(double)); *iter = it; if (verbose) printf("itbest = %d it = %d obj[0]= %f obj.best = %f\n", itbest, it, obj[0], obj[itbest]); /* Compute final df value, based on alpha */ d = k+1; for (i=0; i<n-k-1; i++) if (alpha[i] != alpha[i+1]) d += 1; *df = d; cs_spfree(kernmat); glmgen_gqr_free(kernmat_qr); free(v); free(z); free(betabest); free(alphabest); }