I did a scala implementation of non-negative matrix decomposition yesterday. Today, I am going to implement it in clojure.
For this post (as I did in my other posts in Clojure) I use boot. We declare our dependencies:
(set-env! :dependencies
'[[net.mikera/vectorz-clj "0.48.0"]
[net.mikera/core.matrix "0.62.0"]])
(ns nonnegative.core
(:gen-class)
(:require [clojure.core.matrix :as cm]))
core.matrix has several backends to perform matrix calculations. Today, we are going to use vectorz.
(clojure.core.matrix.implementations/set-current-implementation :vectorz)
We are going to need two utility functions:
(defn random-matrix [n m]
(as-> (repeatedly rand) $
(take (* n m) $)
(cm/reshape $ [n m])))
(defn cost-fn [A B]
(->> (cm/sub A B)
(cm/to-vector)
(map (fn [x] (* x x)))
(reduce +)))
and here is our implementation:
(defn nnmd [D k epocs tol rate flag]
(let [n (cm/row-count D)
m (cm/column-count D)
s (* n m)]
(loop [W (random-matrix n k)
H (random-matrix k m)
i epocs
c tol]
(if (or (= i 0) (< c tol))
[W H i c]
(let [u (cm/reshape (take s (repeat 1)) [n m])
Wt (cm/transpose W)
Ht (cm/transpose H)
et (cm/mul rate (cm/div W (cm/mmul u Ht)))
mu (cm/mul rate (cm/div H (cm/mmul Wt u)))
temp (cm/sub (cm/div D (cm/mmul W H)) u)]
(if (and flag (= 0 (mod i 100))) (println i c))
(recur (cm/add W (cm/mul et (cm/mmul temp Ht)))
(cm/add H (cm/mul mu (cm/mmul Wt temp)))
(dec i)
(/ (cost-fn D (cm/mmul W H)) s)))))))
Let us test:
(let [D (cm/scale 1e-4 (random-matrix 300 150))
[W H i c] (nnmd D 50 3500 1e-2 1e-2 true)]
(println i c))
3500 0.01
3400 2.967546510137647
3300 0.05326918311432456
3258 0.009844411644088693