Sparse principal components
Sparse principal component analysis by Fantope projection and selection.
maximize ⟨S, X⟩ - λ ‖X‖₁ subject to X ∈ Fᵏ = {X : 0 ⪯ X ⪯ I, tr(X) = k}The Fantope Fᵏ is the convex hull of the projections onto k-dimensional subspaces, so without the penalty the maximizer is the projection onto the leading k eigenvectors of the sample covariance S. The ℓ1 penalty makes the estimate of the subspace sparse (Vu, Cho, Lei and Rohe, 2013).
The indicator of Fᵏ is Spectral(Constrained(IndBox(0, 1), IndHyperplane(1, k))): its projection projects the eigenvalues onto the capped simplex. ADMM splits the problem as h(X) + g(Y) with X = Y, for h = Tilt(indicator of Fᵏ, -S), whose proximal operator is the projection of X + S/ρ, and g = λ‖⋅‖₁.
using ProximalBase, LinearAlgebra, Random
rng = Xoshiro(17)Random.Xoshiro(0x86e4c19d9405fc84, 0x43b6af16d34e2a65, 0x5914b730eb37944e, 0x02667705420b1d4a, 0x203da3fb74542e3f)Data: a spiked covariance with a sparse leading eigenvector
p, n, k = 50, 200, 1
v = zeros(p)
v[1:5] .= [0.6, 0.5, 0.4, 0.35, 0.3]
v ./= norm(v)
Σ = I + 2.0 * v * v' # the leading eigenvector is v
X = randn(rng, n, p) * cholesky(Σ).U # rows from N(0, Σ)
S = Symmetric(X' * X / n)50×50 LinearAlgebra.Symmetric{Float64, Matrix{Float64}}:
1.67348 0.717647 0.515158 … -0.0318799 -0.0200226
0.717647 1.58155 0.597751 -0.103707 -0.02732
0.515158 0.597751 1.23933 -0.0576458 -0.0601376
0.513567 0.35081 0.296302 0.10167 -0.0235139
0.341465 0.329261 0.168689 0.0508923 0.0222485
0.123804 -0.0507365 0.00977792 … -0.109161 0.109117
-0.102047 -0.0807422 -0.0474026 -0.00839483 0.0561052
-0.0485354 0.122492 0.0557524 -0.00560918 -0.104943
0.0591334 -0.0670024 -0.0298017 0.0506705 0.0493601
-0.0955585 -0.11947 -0.0292286 0.0212479 -0.0887009
⋮ ⋱
-0.217358 -0.0265814 -0.107644 0.137814 -0.0116244
0.0984075 0.106963 -0.0833812 -0.0812493 -0.0449529
-0.0170181 -0.0804936 -0.0909464 -0.0518178 0.035773
0.013062 0.0420517 -0.01591 0.0293147 -0.169642
0.010331 0.0217644 0.0645598 … -0.0540304 -0.147969
-0.0985846 -0.0307143 0.0188641 0.0217222 0.0148886
-0.0860568 -0.0527603 0.0492549 0.120862 -0.050211
-0.0318799 -0.103707 -0.0576458 1.02265 0.0466268
-0.0200226 -0.02732 -0.0601376 0.0466268 0.901578Plain PCA
u = eigen(S).vectors[:, end]
println("PCA: ", count(x -> abs(x) > 1e-3, u), " nonzero loadings, |⟨u, v⟩| = ", round(abs(dot(u, v)), digits = 3))PCA: 50 nonzero loadings, |⟨u, v⟩| = 0.934Fantope projection and selection by ADMM
λ = 0.15
fantope = Spectral(Constrained(IndBox(0, 1), IndHyperplane(1, k)))
h = Tilt(fantope, -Matrix(S)) # -⟨S, X⟩ on the Fantope
g = NormL1(λ)
ρ = 1.0
Xk, Y, U = zeros(p, p), zeros(p, p), zeros(p, p)
iters = 0
for it in 1:2000
global iters = it
prox!(Xk, h, Y .- U, 1 / ρ) # the projection of Y - U + S/ρ onto Fᵏ
Yold = copy(Y)
prox!(Y, g, Xk .+ U, 1 / ρ) # soft thresholding
U .+= Xk .- Y
r, s = norm(Xk - Y), ρ * norm(Y - Yold) # the primal and dual residuals
max(r, s) < 1e-9 && break
end
println("ADMM: ", iters, " iterations, ‖X - Y‖ = ", norm(Xk - Y))
μ = eigvals(Symmetric(Xk))
@assert all(-1e-9 .<= μ .<= 1 + 1e-9) && tr(Xk) ≈ k # X is in the Fantope
@assert norm(Xk - Y) < 1e-8ADMM: 103 iterations, ‖X - Y‖ = 9.490710993897126e-10the leading eigenvector of the estimate: sparse, and nearer to v than PCA's
w = eigen(Symmetric(Y)).vectors[:, end]
support = findall(x -> abs(x) > 1e-6, w)
println("Fantope projection and selection: loadings on ", support, ", |⟨w, v⟩| = ", round(abs(dot(w, v)), digits = 3))
@assert support == 1:5
@assert abs(dot(w, v)) > abs(dot(u, v))Fantope projection and selection: loadings on [1, 2, 3, 4, 5], |⟨w, v⟩| = 0.985the optimality conditions, through the scaled dual variable U: ρU is a subgradient of λ‖⋅‖₁ at Y, and S - ρU is normal to Fᵏ at X, so that the projection of X + S - ρU onto Fᵏ is X
@assert all(abs(ρ * U[i] - λ * sign(Y[i])) < 1e-6 for i in eachindex(Y) if !iszero(Y[i]))
@assert all(abs(ρ * U[i]) <= λ + 1e-6 for i in eachindex(Y) if iszero(Y[i]))
@assert norm(prox(fantope, Xk .+ (Matrix(S) .- ρ .* U)) - Xk) < 1e-6