MinPakke.jl/src/TR.jl
2023-05-20 08:36:15 +02:00

436 lines
No EOL
13 KiB
Julia

using Optimization # For numerical minimization of PRESS statistic
using OptimizationOptimJL # For numerical minimization of PRESS statistic
struct TRSVD
U::Matrix{Float64}
s::Vector{Float64}
V::Matrix{Float64}
mX::Matrix{Float64}
regType::String
regParam1::Float64
regMat::Matrix{Float64}
n::Int64
p::Int64
end
struct TRBidiag
W::Matrix{Float64}
B::Bidiagonal{Float64, Vector{Float64}}
T::Matrix{Float64}
y::Vector{Float64}
mX::Matrix{Float64}
my::Float64
regType::String
regParam1::Float64
regMat::Matrix{Float64}
n::Int64
p::Int64
end
"""
### TO DO: ADD FRACTIONAL DERIVATIVE REGULARIZATION ###
regularizationMatrix(X; regType="L2", regParam1=0, regParam2=1e-14)
regularizationMatrix(p::Int64; regType="L2", regParam1=0, regParam2=1e-14)
Calculates and returns square regularization matrix.
Inputs:
- X/p : Size of regularization matrix or data matrix (size of reg. mat. will then be size(X,2)
- regType : "L2" (returns identity matrix)
"bc" (boundary condition, forces zero on right endpoint for derivative regularization) or
"legendre" (no boundary condition, but fills out reg. mat. with lower order polynomial trends to get square matrix)
"std" (standardization, FILL OUT WHEN DONE)
- regParam1 : Int64, Indicates degree of derivative regularization (0 gives L\\_2)
- regParam2 : For regType=="plegendre" added polynomials are multiplied by sqrt(regParam2)
Output: Square regularization matrix
"""
function regularizationMatrix(X; regType="L2", regParam1=0, regParam2=1e-14)
if regType == "std"
regParam2 = Diagonal(vec(std(X, dims=1)));
end
regMat = regularizationMatrix(size(X,2); regType, regParam1, regParam2)
return regMat
end
function regularizationMatrix(p::Int64; regType="L2", regParam1=0, regParam2=1e-14)
if regType == "bc" # Discrete derivative with boundary conditions
regMat = [I(p); zeros(regParam1,p)]; for i = 1:regParam1 regMat = diff(regMat, dims = 1); end
elseif regType == "legendre" # Fill in polynomials in bottom row(s) to get square matrix
regMat = [I(p); zeros(regParam1,p)]; for i = 1:regParam1 regMat = diff(regMat, dims = 1); end
P, _ = plegendre(regParam1-1, p);
regMat[end-regParam1+1:end,:] = sqrt(regParam2) * P;
elseif regType == "L2"
regMat = I(p);
elseif regType == "std" # Standardization
regMat = regParam2;
elseif regType == "GL" # Grünwald-Letnikov fractional derivative regulariztion
# regParam1 is alpha (order of fractional derivative)
C = ones(p)*1.0;
for k in 2:p
C[k] = (1-(regParam1+1)/(k-1)) * C[k-1];
end
regMat = zeros(p,p);
for i in 1:p
regMat[i:end, i] = C[1:end-i+1];
end
end
return regMat
end
"""
function TRBidiagDecomp(X, y, A=(minimum(size(X))-1), regType="L2", regParam1=0, regParam2=1e-14)
Calculates regularization matrix (using function "RegularizationMatrix"),
and centres and transforms data matrix according to "X / regMat".
Output is an object of type "TRBidiag" and is used as input to other TR functions.
"""
function TRBidiagDecomp(X, y, A=(minimum(size(X))-1), regType="L2", regParam1=0, regParam2=1e-14)
n, p = size(X);
mX = mean(X, dims=1);
X = X .- mX;
my = mean(y);
y = vec(y .- my);
regMat = regularizationMatrix(X; regType, regParam1, regParam2);
X = X / regMat;
_, W, B, T = bidiag2(X, y, A);
TRObj = TRBidiag(W, B, T, y, mX, my, regType, regParam1, regMat, n, p);
return TRObj
end
"""
function TRSVDDecomp(X, regType="L2", regParam1=0, regParam2=1e-14)
Calculates regularization matrix (using function "RegularizationMatrix"),
and centres and transforms data matrix according to "X / regMat".
Output is an object of type "TRSVD" and is used as input to other TR functions.
"""
function TRSVDDecomp(X, regType="L2", regParam1=0, regParam2=1e-14)
n, p = size(X);
mX = mean(X, dims=1);
X = X .- mX;
regMat = regularizationMatrix(X; regType, regParam1, regParam2);
X = X / regMat;
U, s, V = svd(X, full=false);
TRObj = TRSVD(U, s, V, mX, regType, regParam1, regMat, n, p);
return TRObj
end
function TRSegmentOrth(X, segments)
n = size(X,1);
n_segments = maximum(segments);
U = zeros(n,n);
for seg in 1:n_segments
inds = vec(seg .== segments)
U[inds, inds], _, _ = svd(X[inds,:], full=false);
end
return U
end
"""
function TRVirCV(X, y, segments, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
Segmented virtual cross-validation (VirCV) for TR models.
Outputs: b, press, lambda_min, lambda_min_ind, GCV
b are (virtual) press-minimal regression coefficients.
"""
function TRVirCV(X, y, segments, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
U_segments = TRSegmentOrth(X, segments);
bs = vec(sum(U_segments, dims=1).^2);
n, p = size(X);
mX = mean(X, dims=1);
X = X .- mX;
my = mean(y);
y = vec(y .- my);
X = U_segments' * X;
y = U_segments' * y;
regMat = regularizationMatrix(p; regType, regParam1, regParam2);
X = X / regMat;
U, s, V = svd(X, full=false);
denom = broadcast(.+, broadcast(./, lambdas, s'), s')';
H = broadcast(.+, U.^2 * broadcast(./, s, denom), bs./n);
resid = broadcast(.-, y, U * broadcast(./, s .* (U'*y), denom));
rescv = broadcast(./, resid, broadcast(.-, 1, H));
press = vec(sum(rescv.^2, dims=1));
#rmsecv = sqrt.(1/n .* press);
GCV = vec(broadcast(./, sum(resid.^2, dims=1), mean(broadcast(.-, 1, H), dims=1).^2));
lambda_min, lambda_min_ind = findmin(press);
lambda_min_ind = lambda_min_ind[1];
denom2 = broadcast(.+, lambda_min ./ s', s')';
b = V * broadcast(./, (U' * y), denom2);
b = regMat \ b;
b = [my .- mX*b; b];
return b, press, lambda_min, lambda_min_ind, GCV
end
"""
function TRSegCV(X, y, lambdas, cv, regType="L2", regParam1=0, regParam2=1e-14)
Segmented cross-validation based on the Sherman-Morrison-Woodbury updating formula.
Inputs:
- X : Data matrix
- y : Response vector
- lambdas : Vector of regularization parameter values
- cv : Vector of length n indicating segment membership for each sample
- regType, regParam1, regParam2 : Inputs to regularizationMatrix function
Outputs: rmsecv, b, lambda_min, lambda_min_ind.
b are regression coefficients corresponding to the lambda value minimising the CV-error.
"""
function TRSegCV(X, y, lambdas, cv, regType="L2", regParam1=0, regParam2=1e-14)
TR = TRSVDDecomp(X, regType, regParam1, regParam2);
n_seg = maximum(cv);
n_lambdas = length(lambdas);
my = mean(y);
y = y .- my;
denom = broadcast(.+, broadcast(./, lambdas, TR.s'), TR.s')';
resid = broadcast(.-, y, TR.U * broadcast(./, TR.s .* (TR.U'*y), denom));
rescv = zeros(TR.n, n_lambdas);
sdenom = sqrt.(broadcast(./, TR.s, denom))';
for seg in 1:n_seg
Useg = TR.U[vec(cv .== seg),:];
Id = 1.0 * I(size(Useg,1)) .- 1/TR.n;
for k in 1:n_lambdas
Uk = Useg .* sdenom[k,:]';
rescv[vec(cv .== seg), k] = (Id - Uk * Uk') \ resid[vec(cv .== seg), k];
end
end
press = sum(rescv.^2, dims=1)';
rmsecv = sqrt.(1/TR.n .* press);
lambda_min, lambda_min_ind = findmin(rmsecv)
lambda_min_ind = lambda_min_ind[1]
b = TRRegCoeffs(TR, y, lambda_min, my)
return b, rmsecv, lambda_min, lambda_min_ind
end
"""
TRRegCoeffs(X, y, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
TRRegCoeffs(TR::TRSVD, y, lambdas, my=0)
Calculates regression coefficients for TR model.
First function returns "bcoeffs, TR::TRSVD",
second function returns "bcoeffs".
"""
function TRRegCoeffs(X, y, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
TR = TRSVDDecomp(X, regType, regParam1, regParam2);
my = mean(y);
y = y .- my;
@inline bcoeffs = TRRegCoeffs(TR, y, lambdas, my);
return bcoeffs, TR
end
function TRRegCoeffs(TR::TRSVD, y, lambdas, my=0)
# Don't forget about centering (both X and y) - Maybe do it outside of this function?
denom = broadcast(.+, broadcast(./, lambdas, TR.s'), TR.s')';
bcoeffs = TR.V * broadcast(./, (TR.U' * y), denom);
bcoeffs = TR.regMat \ bcoeffs;
if my != 0
bcoeffs = [my .- TR.mX*bcoeffs; bcoeffs];
end
return bcoeffs
end
"""
TRPress(TR::TRSVD, y, lambdas)
TRPress(TR::TRSVD, y, lambdas, H, resid)
Calculates and returns press-values (as vector) for lambda values given as input.
"""
function TRPress(TR::TRSVD, y, lambdas)
denom = broadcast(.+, broadcast(./, lambdas, TR.s'), TR.s')';
resid = broadcast(.-, y, TR.U * broadcast(./, TR.s .* (TR.U'*y), denom));
H = broadcast(.+, TR.U.^2 * broadcast(./, TR.s, denom), 1/TR.n);
@inline press = TRPress(TR, y, lambdas, H, resid);
return press
end
function TRPress(TR::TRSVD, y, lambdas, H, resid)
rescv = broadcast(./, resid, broadcast(.-, 1, H));
press = vec(sum(rescv.^2, dims=1));
return press
end
"""
function TRGCV(TR::TRSVD, y, lambdas)
function TRGCV(TR::TRSVD, y, lambdas, H, resid)
Calculates and returns GCV-values (as vector) for lambda values given as input.
"""
function TRGCV(TR::TRSVD, y, lambdas)
denom = broadcast(.+, broadcast(./, lambdas, TR.s'), TR.s')';
resid = broadcast(.-, y, TR.U * broadcast(./, TR.s .* (TR.U'*y), denom));
H = broadcast(.+, TR.U.^2 * broadcast(./, TR.s, denom), 1/TR.n);
@inline GCV = TRGCV(TR, y, lambdas, H, resid);
return GCV
end
function TRGCV(TR::TRSVD, y, lambdas, H, resid)
GCV = vec(broadcast(./, sum(resid.^2, dims=1), mean(broadcast(.-, 1, H), dims=1).^2));
return GCV;
end
"""
function TRLooCV(X, y, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
function TRLooCV(TR::TRSVD, y, lambdas)
Calculates PRESS- and GCV-minimal regression coefficients from the reg. param values in lambdas.
Outputs: BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV
"""
function TRLooCV(X, y, lambdas, regType="L2", regParam1=0, regParam2=1e-14)
TR = TRSVDDecomp(X, regType, regParam1, regParam2);
BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV = TRLooCV(TR, y, lambdas);
return BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV
end
function TRLooCV(TR::TRSVD, y, lambdas)
my = mean(y);
y = y .- my;
denom = broadcast(.+, broadcast(./, lambdas, TR.s'), TR.s')';
H = broadcast(.+, TR.U.^2 * broadcast(./, TR.s, denom), 1/TR.n);
resid = broadcast(.-, y, TR.U * broadcast(./, TR.s .* (TR.U'*y), denom));
@inline press = TRPress(TR, y, lambdas, H, resid);
@inline GCV = TRGCV(TR, y, lambdas, H, resid);
idminPRESS = findmin(press)[2][1]; # First index selects coordinates, second selects '1st coordinate'
idminGCV = findmin(GCV)[2][1]; # First index selects coordinates, second selects '1st coordinate'
lambdaPRESS = lambdas[idminPRESS];
lambdaGCV = lambdas[idminGCV];
BPRESS = TRRegCoeffs(TR, y, lambdaPRESS, my);
BGCV = TRRegCoeffs(TR, y, lambdaGCV, my);
return BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV
end
"""
function PlotTRLooCV(lambdas, BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV)
The function uses lambdas (vector) + the output from TRLooCV to plot data, PRESS- and GCV-curves, as well as
PRESS- and GCV-minimal regression coefficients.
"""
function PlotTRLooCV(lambdas, BPRESS, BGCV, TR, press, GCV, idminPRESS, lambdaPRESS, idminGCV, lambdaGCV)
plta = plot((TR.U * diagm(TR.s) * TR.V' .+ TR.mX)', legend=false)
pltb = plot(log10.(lambdas), xlabel="log10(lambda)", press, label="PRESS");
plot!(log10.(lambdas), GCV, label="GCV")
#plot!(log10(lambdaPRESS), press[idminPRESS])
pltc = plot(BPRESS[2:end], label="B-press")
plot!(BGCV[2:end], label="B-GCV")
#pltd = plot(X', legend=false)
plt = plot(plta, pltb, pltc, layout=(2,2))
display(plt)
end
"""
function TRLooCVNum(TR::TRSVD, y, lambdaInit=1)
TRLooCVNum(X, y, lambdaInit=1, regType="L2", regParam1=0, regParam2=1e-14)
Finds regularization paramter value minimising the PRESS-statistic
and returns "b, lambda_min".
"""
function TRLooCVNum(TR::TRSVD, y, lambdaInit=1)
function pressfunc(lambdaval)
@inline pressval = TRPress(TR, y, lambdaval[1])
return pressval
end
my = mean(y);
y = y .- my;
prob = OptimizationProblem((x, p) -> pressfunc(x), [1.0], [])
sol = solve(prob, NelderMead())[1];
b = TRRegCoeffs(TR, y, sol, my);
return b, sol[1]
end
function TRLooCVNum(X, y, lambdaInit=1, regType="L2", regParam1=0, regParam2=1e-14)
TR = TRSVDDecomp(X, regType, regParam1, regParam2);
@inline b, lambda_min = TRLooCVNum(TR, y, lambdaInit)
end
"""
function TRGCVNum(TR::TRSVD, y, lambdaInit=1)
TRGCVNum(X, y, lambdaInit=1, regType="L2", regParam1=0, regParam2=1e-14)
Finds regularization paramter value minimising the PRESS-statistic
and returns "b, lambda_min".
"""
function TRGCVNum(TR::TRSVD, y, lambdaInit=1)
function gcvfunc(lambdaval)
@inline gcvval = TRGCV(TR, y, lambdaval[1]);
return gcvval
end
my = mean(y);
y = y .- my;
prob = OptimizationProblem((x, p) -> gcvfunc(x), [1.0], [])
sol = solve(prob, NelderMead())[1];
b = TRRegCoeffs(TR, y, sol, my);
return b, sol[1]
end
function TRGCVNum(X, y, lambdaInit=1, regType="L2", regParam1=0, regParam2=1e-14)
TR = TRSVDDecomp(X, regType, regParam1, regParam2);
@inline b, lambda_min = TRGCVNum(TR, y, lambdaInit)
end