Skip to content

cnuernber/kmeans-mnist

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Julia/Clojure KMeans-MNIST

An example of using using Clojure and Julia to implement KMeans to create an MNIST classifier which performs extremely well both in terms of accuracy and in terms of cpu time.

Usage

If you are on Ubuntu and using JDK 8, type source scripts/local-julia-env to setup the julia environment. If you are not on Ubuntu, then see the script and change/enhance it to fit your system.

From a julia prompt, install the StaticArrays package:

julia> import(Pkg)
julia> Pkg.add("StaticArrays")

In the script you will notice that it exports LD_PRELOAD so that a special java library is loaded: libjsig.so. This allows the JVM to forward signals meant for Julia to Julia but still use signals. This is a requirement to run the demonstration.

Assuming that you have the proper environment setup, go into the mnist namespace and type:

kmeans-mnist.mnist> (def data (time (test-kmeans 100)))
...(lots of logging)...
"Elapsed time: 6936.24221 msecs"
#'kmeans-mnist.mnist/data
kmeans-mnist.mnist> data
{:accuracy 0.9577, :confusion-matrix #tech.v3.tensor<int64>[10 10]
[[1932    1    9    2    1    5   12    2    7    3]
 [   1 2262    8    0    3    2    4   13    5    3]
 [   9    8 1964    8    2    2    5   21   19    4]
 [   2    0    8 1904    1   35    0    6   28   20]
 [   1    3    2    1 1864    4    5    9    7   53]
 [   5    2    2   35    4 1684   10    2   28   14]
 [  12    4    5    0    5   10 1884    0    4    1]
 [   2   13   21    6    9    2    0 1948    7   39]
 [   7    5   19   28    7   28    4    7 1824    9]
 [   3    3    4   20   53   14    1   39    9 1888]]}

Some Highlights

The algorithm is a modified kmeans-++ where we use a cumulative summation for the ++ part as opposed to a sort operation. Next we calculate distances inline with finding the centroid index thus reducing algorithm steps. We use Julia to implement a series of small numeric kernels that are the tight loops of the algorithm but we write the outer algorithm purely in Clojure. This allows us to use Julia exactly where it is quite strong and use Clojure to glue the pieces together. Specifically the dense arithmetic is done in Julia while the algorithmic outline and the centroid reduction is done in Clojure.

The kmeans.jl file is somewhat type hinted to allow the Julia compiler as much freedom as possible to specifically optimize the code to the datatypes of the arguments provided as well as the number of columns.

I implemented a Julia threading primitive, indexed-map-reduce that allows me to efficiently declare context local to a thread before iterating through the indexes of the parallelization. This allows a very efficient multithreaded form of reduction where your reduction targets are declared on the stack and the compiler has full visibility of their lifetime allowing it to keep them in CPU registers or to declare transitive per-thread datastructures that are never visible outside the algorithm so for instance a hash map that is used simply inside the per-thread operation.

Copying data between the two languages is optimized; as long as the datatypes match a jvm-heap or native-heap backed tensor will have an optimized pathway to Julia.

Julia resources are never exposed to the client code; all per-kmeans-run julia objects are allocated within the libjulia-clj construct with-stack-context. There is a significant performance penalty upon first call of the code as the Julia compiler has quite a bit of work to do.

Releases

No releases published

Sponsor this project

 

Packages

No packages published