   {:n-layers (count layer-sizes)
    :layer-sizes layer-sizes
    :biases (map rand-vector (next layer-sizes))
-   :weights (map rand-matrix (rest layer-sizes) (drop-last layer-sizes) )})
+   :weights (map rand-matrix (rest layer-sizes) (drop-last layer-sizes))})
 
 (defn feed-forward
   [{:keys [biases weights] :as net} input]

  [net test-data test-labels]
  (let [results (map (fn [data label]
                       (let [res (feed-forward net data)]
                         (argmax res)))
                     (mat/rows test-data) (mat/rows test-labels))
        score (count (filter #(= (first %) (second %)) (map vector results test-labels)))]
    score))


      activation)))

;; Gradient descent
;; 1) forward propagate
;;  * sum((weights * inputs) + biases) for each neuron, layer by layer
;; 2) back propagate deltas
;;  * After propagating forward, we need to figure out the error (often called the
;;    delta) for each neuron.  For the outputs this is just the
;;    (output - expected_output) * activation-fn-prime, but for the hidden units the output error has to
;;    be propagated back across the weights to distributed the error according to
;;    which hidden units were responsible for it.
;; 3) compute gradients
;;  * Then for each weight you multiply its output delta by its input activation to get
;;    its gradient.  This will correspond to the direction and magnitude of the
;;    error for that weight, so any update should happen in the opposite direction.
;; 4) update weights
;;  * multiply gradient by the learning-rate to smooth out jitter in updates.
(defn backprop
  [{:keys [biases weights layer-sizes] :as net} input expected-output]
  ;(println "\n--------------- backprop ---------------------")
  (let [bias-gradients (map #(mat/zero-array (mat/shape %)) biases)
        weight-gradients (map #(mat/zero-array (mat/shape %)) weights)
        [activations zs] (reduce
