   [rows cols]
   (let [weight-scale (Math/sqrt (/ 1.0 (double cols)))]
     (b/array
-     (mapv (fn [_]
-             (vec (repeatedly cols
-                              #(* weight-scale
-                                  (rand-gaussian)))))
-           (range rows)))))
+      (mapv (fn [_]
+              (vec (repeatedly cols
+                               #(* weight-scale
+                                   (rand-gaussian)))))
+            (range rows)))))
 
 (defn random-matrix
   "Constructs an array of the given shape with random normally distributed element values"
   ([shape-vector]
-    (rand-matrix/sample-normal shape-vector)))
-
-(defn empty-array
-  "Constructs a new empty (zero-filled) array of the given shape"
-  ([shape]
-    (m/new-array :vectorz shape)))
-
-(defn mse-gradient-fn
-  "Returns the MSE error gradient for a given output and target value"
-  ([output target]
-    (let [result (m/mutable output)]
-      (m/sub! result target)
-      (m/scale! result 2.0)
-      result)))
-
+   (rand-matrix/sample-normal shape-vector)))
+
+(defn assign-sparse-to-packed!
+  [packed-data sparse-data]
+  (let [packed-data (if packed-data
+                      packed-data
+                      (let [elem-count (reduce + (map m/ecount sparse-data))]
+                        (b/new-array [elem-count])))]
+    (reduce (fn [^long offset next-item]
+              (let [item-vec (m/as-vector next-item)
+                    item-len (long (m/ecount item-vec))]
+                (m/assign! (m/subvector packed-data offset item-len) item-vec)
+                (+ offset item-len)))
+            0
+            sparse-data)
+    packed-data))
+
+(defn assign-packed-to-sparse!
+  [sparse packed]
+  (reduce (fn [^long offset next-item]
+            (let [elem-count (long (m/ecount next-item))]
+              (m/assign! (m/as-vector next-item)
+                         (m/subvector packed offset elem-count))
+              (+ offset elem-count)))
+          0
+          sparse))
+
+(defn zero-sparse!
+  [sparse]
+  (doseq [item sparse]
+    (m/fill! item 0.0)))
+
+
+(defn get-or-new-array
+  [item kywd shape]
+  (or (get item kywd)
+      (b/new-array shape)))
+
+(defn get-or-array
+  [item kywd data]
+  (or (get item kywd)
+      (b/array data)))
+
+;;;; Confusion matrices
+
+(defn confusion-matrix
+  "A confusion matrix shows the predicted classes for each of the actual
+  classes in order to understand performance and commonly confused classes.
+
+                   Predicted
+                 Cat Dog Rabbit
+         | Cat	   5  3  0
+  Actual | Dog	   2  3  1
+         | Rabbit  0  2  11
+
+  Initialize with a set of string labels, and then call add-prediction for
+  each prediction to accumulate into the matrix."
+  [labels]
+  (let [prediction-map (zipmap labels (repeat 0))]
+    (into {} (for [label labels]
+               [label prediction-map]))))
+
+(defn add-prediction
+  [conf-mat prediction label]
+  (update-in conf-mat [label prediction] inc))
+
+(defn print-confusion-matrix
+  [conf-mat]
+  (let [ks (sort (keys conf-mat))
+        label-len (inc (int (apply max (map count ks))))
+        prefix (apply str (repeat label-len " "))
+        s-fmt (str "%" label-len "s")]
+    (apply println prefix ks)
+    (doseq [k ks]
+      (apply println (format s-fmt k) (map #(get-in conf-mat [k %]) ks)))))
+
+;;;; Time
+
+#?(:clj (defn timestamp [] (System/nanoTime))
+   :cljs (defn timestamp [] (.getTime (js/Date.))))
+
+(defn ms-elapsed
+  ([start]
+   (ms-elapsed start (timestamp)))
+  ([start end]
+   (let [start (double start)
+         end (double end)]
+     #?(:clj  (/ (- end start) 1000000.0))
+     :cljs (- end start))))
+
+;;;; Exceptions
 
 #?(:clj
    (defmacro error

  [y]
  (m/emul y (m/sub 1.0 y)))

(defn weight-matrix
  "Creates a randomised weight matrix.


          relative-error))
      (range (m/column-count input)))))

(defn estimate-gradient
  "Computes a numerical approximation of the derivative of the function f at with respectr to input x"
  ([f x]
   (let [x+dx (m/add )])))
