KSVD.jl

Highly optimized K-SVD implementation in Julia, with several parallelization techniques available.
Popularity
0 Stars
Updated Last
5 Months Ago
Started In
July 2024

KSVD.jl

K-SVD is an algorithm for creating overcomplete dictionaries for sparse representations.

This package implements:

In particular, substantial effort has been put in speeding up this implementation. This includes:

  • Custom parallel dense-sparse matrix multiplication via ThreadedDenseSparseMul.jl
  • Custom pipelined GPU offloading for matching pursuit (CUDAAcceleratedMatchingPursuit)
  • Threaded matching pursuit (ParallelMatchingPursuit) (mostly "embarassingly parallel")
  • Threaded ksvd implementation (ParallelKSVD) (not "embarassingly parallel")
  • Threaded and batched ksvd implementation (BatchedParallelKSVD) (not "embarassingly parallel")
  • Extensive efforts removing allocations by preallocating buffers
  • Extensive benchmark-driven optimizations utilizing ProfileView.jl
  • Many other modification experiments.

Usage

Assume that each column of Y represents a feature vector (or an input signal from some system).
D is a dictionary. Each column of D represents an atom.
K-SVD derives D and X such that DX ≈ Y from only Y.

(; D, X) = ksvd(Y, 256)

# we can control the matching pursuit stage and ksvd stage through method structs
ksvd_update_method = BatchedParallelKSVD{false, Float64}(shuffle_indices=true, batch_size_per_thread=1)
sparse_coding_method = ParallelMatchingPursuit(max_nnz=25, rtol=5e-2)
result = ksvd(Y, 256;
              ksvd_update_method,
              sparse_coding_method,
              maxiters=100,
              abstol=1e-6,
              reltol=1e-6,
              show_trace=true)

# Access additional information
println("Termination condition: ", result.termination_condition)
println("Norm results: ", result.norm_results)
println("NNZ per column results: ", result.nnz_per_col_results)
println("Timing information: ", result.timer)

Of course we can also just run one step of matching pursuit/sparse coding, or one step of the ksvd update:

basis = KSVD.init_dictionary(size(Y, 1), 2*size(Y,2))
X = sparse_coding(OrthogonalMatchingPursuit(max_nnz=25), Y, basis)

(; D, X) = ksvd_update(ksvd_update_method, Y, basis, X)

Matching Pursuit derives X from D and Y such that DX = Y in constraint that X be as sparse as possible.

Performance improvements

Here is an overview of the performance improvements in the ksvd_update provided in this package, broken down by computation type. The data is computed using different commits on the experiments branch. More details will be added later.

benchmark results