Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/api/solvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ SolverRecipe

## Solver Structures
```@docs
IHS

IHSRecipe
```

## Exported Functions
Expand Down
49 changes: 47 additions & 2 deletions docs/src/refs.bib
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,30 @@ @misc{martinsson2020randomized
year = {2020},
publisher = {arXiv},
doi = {10.48550/ARXIV.2002.01387},
copyright = {arXiv.org perpetual, non-exclusive license},
keywords = {FOS: Mathematics,Numerical Analysis (math.NA)}
}

@article{needell2014paved,
title = {Paved with Good Intentions: {{Analysis}} of a Randomized Block {{Kaczmarz}} Method},
shorttitle = {Paved with Good Intentions},
author = {Needell, Deanna and Tropp, Joel A.},
year = {2014},
month = jan,
journal = {Linear Algebra and its Applications},
volume = {441},
pages = {199--221},
issn = {00243795},
doi = {10.1016/j.laa.2012.12.022},
langid = {english},
}

@misc{pilanci2014iterative,
title = {Iterative {{Hessian}} Sketch: {{Fast}} and Accurate Solution Approximation for Constrained Least-Squares},
shorttitle = {Iterative {{Hessian}} Sketch},
author = {Pilanci, Mert and Wainwright, Martin J.},
year = {2014},
publisher = {arXiv},
doi = {10.48550/ARXIV.1411.0347},
keywords = {FOS: Computer and information sciences,FOS: Mathematics,Information Theory (cs.IT),Machine Learning (cs.LG),Machine Learning (stat.ML),Optimization and Control (math.OC)}
}

@article{pritchard2023practical,
Expand Down Expand Up @@ -64,3 +86,26 @@ @article{pritchard2024solving
issn = {0266-5611, 1361-6420},
doi = {10.1088/1361-6420/ad5583},
}

@article{strohmer2009randomized,
title = {A {{Randomized Kaczmarz Algorithm}} with {{Exponential Convergence}}},
author = {Strohmer, Thomas and Vershynin, Roman},
year = {2009},
month = apr,
journal = {Journal of Fourier Analysis and Applications},
volume = {15},
number = {2},
pages = {262--278},
issn = {1069-5869, 1531-5851},
doi = {10.1007/s00041-008-9030-4},
langid = {english}
}
@misc{pilanci2014iterative,
title = {Iterative {{Hessian}} Sketch: {{Fast}} and Accurate Solution Approximation for Constrained Least-Squares},
shorttitle = {Iterative {{Hessian}} Sketch},
author = {Pilanci, Mert and Wainwright, Martin J.},
year = {2014},
publisher = {arXiv},
doi = {10.48550/ARXIV.1411.0347},
}
Comment on lines +103 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this reference is listed twice. I think the JMLR version (linked in the PR description) should be the appropriate reference to use:

@article{pilanci2016iterative,
  title={Iterative Hessian sketch: Fast and accurate solution approximation for constrained least-squares},
  author={Pilanci, Mert and Wainwright, Martin J},
  journal={Journal of Machine Learning Research},
  volume={17},
  number={53},
  pages={1--38},
  year={2016}
}


4 changes: 3 additions & 1 deletion src/RLinearAlgebra.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module RLinearAlgebra
import Base.:*
import Base: transpose, adjoint
import LinearAlgebra: Adjoint, axpby!, dot, ldiv!, lmul!, lq!, lq, LQ, mul!, norm, qr!
import LinearAlgebra: Adjoint, axpby!, axpy!, dot, ldiv!, lmul!, lq!, lq, LQ, mul!, norm
import LinearAlgebra: qr!, UpperTriangular
import StatsBase: sample!, ProbabilityWeights, wsample!
import Random: bitrand, rand!, randn!
import SparseArrays: SparseMatrixCSC, sprandn
Expand Down Expand Up @@ -33,6 +34,7 @@ export Uniform, UniformRecipe
# Export Solver types and functions
export Solver, SolverRecipe
export complete_solver, update_solver!, rsolve!
export IHS, IHSRecipe

# Export Logger types and functions
export Logger, LoggerRecipe
Expand Down
1 change: 1 addition & 0 deletions src/Solvers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ include("Solvers/ErrorMethods.jl")
#############################
# The Solver Routine Files
############################
include("Solvers/ihs.jl")
234 changes: 234 additions & 0 deletions src/Solvers/ihs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
"""
IHS <: Solver
An implementation of the Iterative Hessian Sketch solver for solving over determined
least squares problems (@cite)[pilanci2014iterative].
# Mathematical Description
Let ``A \\in \\mathbb{R}^{m \\times n}`` and consider the least square problem ``\\min_x
\\|Ax - b \\|_2^2``. If we let ``S \\in \\mathbb{R}^{s \\times m}`` be a compression matrix, then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment m >> n

Iterative Hessian Sketch iteratively finds a solution to this problem
by repeatedly updating ``x_{k+1} = x_k + \\alpha u_k``where ``u_k`` is the solution to the
convex optimization problem,
``u_k = \\min_u \\{\\|S_k Au\\|_2^2 - \\langle A, b - Ax_k \\rangle \\}.`` This method
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, we write u_k \\in \\argmin_u...

has been to shown to converge geometrically at a rate ``\\rho \\in (0, 1/2]``, typically the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a period before typically instead of a comma. Then typically should be capitalized.

required compression dimension needs to be 4-8 times the size of n for the algorithm to
perform successfully.
# Fields
- `alpha::Float64`, a step size parameter.
- `compressor::Compressor`, a technique for forming the compressed linear system.
- `log::Logger`, a technique for logging the progress of the solver.
- `error::SolverError`, a method for estimating the progress of the solver.
# Constructor
function IHS(;
compressor::Compressor = SparseSign(cardinality = Left()),
log::Logger = BasicLogger(),
error::SolverError = FullResidual(),
alpha::Float64 = 1.0
)
## Keywords
- `compressor::Compressor`, a technique for forming the compressed linear system.
- `log::Logger`, a technique for logging the progress of the solver.
- `error::SolverError', a method for estimating the progress of the solver.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `error::SolverError', a method for estimating the progress of the solver.
- `error::SolverError`, a method for estimating the progress of the solver.

The single quote should have been whatever this character is: `

- `alpha::Float64`, a step size parameter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw in a warning or an information admonition with some guidance about the value of alpha

# Returns
- A `IHS` object.
"""
mutable struct IHS <: Solver
alpha::Float64
log::Logger
compressor::Compressor
error::SolverError
function IHS(alpha, log, compressor, error)
if typeof(compressor.cardinality) != Left
@warn "Compressor has cardinality `Right` but IHS compresses from the `Left`."
end

new(alpha, log, compressor, error)
end

end

function IHS(;
compressor::Compressor = SparseSign(cardinality = Left()),
log::Logger = BasicLogger(),
error::SolverError = FullResidual(),
alpha::Float64 = 1.0
)
return IHS(
alpha,
log,
compressor,
error
)
end

"""
IHSRecipe{
Type<:Number,
LR<:LoggerRecipe,
CR<:CompressorRecipe,
ER<:ErrorRecipe,
M<:AbstractArray,
MV<:SubArray,
V<:AbstractVector
} <: SolverRecip
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SolverRecipe (missing the e at the end)

A mutable structure containing all information relevant to the Iterative Hessian Sketch
solver. It is formed by calling the function `complete_solver` on a `IHS` solver, which
includes all the user controlled parameters, the linear system `A`, and the constant
vector `b`.
# Fields
- `compressor::CompressorRecipe`, a technique for compressing the matrix ``A``.
- `logger::LoggerRecipe`, a technique for logging the progress of the solver.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log comes first

- `error::SolverErrorRecipe`, a technique for estimating the progress of the solver.
- `compressed_mat::AbstractMatrix`, a buffer for storing the compressed matrix.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing alpha

- `mat_view::SubArray`, a container for storing a view of the compressed matrix buffer.
- `residual_vec::AbstractVector`, a vector that contains the residual of the linear system
``Ax-b``.
- `gradient_vec::AbstractVector`, a vector that contains the gradient of the least squares
problem, ``A^\\top(b-Ax)``.
- `buffer_vec::AbstractVector`, a buffer vector for storing intermediate linear system solves.
- `solution_vec::AbstractVector`, a vector storing the current IHS solution.
- `R::UpperTriangular`, a container for storing the upper triangular portion of the R
factor from a QR factorization of `mat_view`. This is used to solve the IHS sub-problem.
"""
mutable struct IHSRecipe{
Type<:Number,
LR<:LoggerRecipe,
CR<:CompressorRecipe,
ER<:SolverErrorRecipe,
M<:AbstractArray,
MV<:SubArray,
V<:AbstractVector
} <: SolverRecipe
log::LR
compressor::CR
error::ER
alpha::Float64
compressed_mat::M
mat_view::MV
residual_vec::V
gradient_vec::V
buffer_vec::V
solution_vec::V
R::UpperTriangular{Type, M}
end

function complete_solver(
ingredients::IHS,
x::AbstractVector,
A::AbstractMatrix,
b::AbstractVector
)
compressor = complete_compressor(ingredients.compressor, x, A, b)
logger = complete_logger(ingredients.log)
error = complete_error(ingredients.error, ingredients, A, b)
sample_size::Int64 = compressor.n_rows
rows_a, cols_a = size(A)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add check if rows_a <= cols_a and throw an error. Make sure to test for this error

# Check that required fields are in the types
if !isdefined(error, :residual)
throw(
ArgumentError(
"ErrorRecipe $(typeof(error)) does not contain the \
field 'residual' and is not valid for an IHS solver."
)
)
end

if !isdefined(logger, :converged)
throw(
ArgumentError(
"LoggerRecipe $(typeof(logger)) does not contain \
the field 'converged' and is not valid for an IHS solver."
)
)
end

# Check that the sketch size is larger than the column dimension and return a warning
# otherwise
if cols_a > sample_size
throw(
ArgumentError(
"Compression dimension not larger than column dimension this will lead to \
singular QR decompositions, which cannot be inverted."
)
)
end

compressed_mat = zeros(eltype(A), sample_size, cols_a)
res = zeros(eltype(A), rows_a)
grad = zeros(eltype(A), cols_a)
buffer_vec = zeros(eltype(A), cols_a)
solution_vec = x
mat_view = view(compressed_mat, 1:sample_size, :)
R = UpperTriangular(mat_view[1:cols_a, :])

return IHSRecipe{
eltype(compressed_mat),
typeof(logger),
typeof(compressor),
typeof(error),
typeof(compressed_mat),
typeof(mat_view),
typeof(buffer_vec)
}(
logger,
compressor,
error,
ingredients.alpha,
compressed_mat,
mat_view,
res,
grad,
buffer_vec,
solution_vec,
R
)
end

function rsolve!(solver::IHSRecipe, x::AbstractVector, A::AbstractMatrix, b::AbstractVector)
reset_logger!(solver.log)
solver.solution_vec = x
err = 0.0
copyto!(solver.residual_vec, b)
# compute the initial residual r = b - Ax
mul!(solver.residual_vec, A, solver.solution_vec, -1.0, 1.0)
for i in 1:solver.log.max_it
# compute the gradient A'r
mul!(solver.gradient_vec, A', solver.residual_vec)
err = compute_error(solver.error, solver, A, b)
update_logger!(solver.log, err, i)
if solver.log.converged
return nothing
end

# generate a new compressor
update_compressor!(solver.compressor, x, A, b)
# Based on the size of the compressor update views of the matrix
rows_s, cols_s = size(solver.compressor)
solver.mat_view = view(solver.compressed_mat, 1:rows_s, :)
# Compress the matrix
mul!(solver.mat_view, solver.compressor, A)
# Update the subsolver
# This is the only piece of allocating code
solver.R = UpperTriangular(qr!(solver.mat_view).R)
# Compute first R' solver R'R x = g
ldiv!(solver.buffer_vec, solver.R', solver.gradient_vec)
# Compute second R Solve Rx = (R')^(-1)g will be stored in gradient_vec
ldiv!(solver.gradient_vec, solver.R, solver.buffer_vec)
# update the solution
# solver.solution_vec = solver.solution_vec + alpha * solver.gradient_vec
Comment on lines +219 to +225
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add "Iterative Hessian Sketch" to the wiki page (either v0.2 or v0.3, your choice). And indicate that we want to allow alternative subsolvers in the inner loop.

axpy!(solver.alpha, solver.gradient_vec, solver.solution_vec)
# compute the fast update of r = r - A * gradient_vec
# note: in this case gradient vec stores the update
mul!(solver.residual_vec, A, solver.gradient_vec, -1.0, 1.0)
end

return nothing

end
Loading
Loading