Index: trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 40318)
+++ trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 40319)
@@ -99,5 +99,9 @@
 $(SRC)/medimage_commands.$(ARCH).o \
 $(SRC)/mset.$(ARCH).o		\
-$(SRC)/needles.$(ARCH).o		\
+$(SRC)/needles.$(ARCH).o	\
+$(SRC)/nnet.$(ARCH).o		\
+$(SRC)/nnet_commands.$(ARCH).o	\
+$(SRC)/nnet_train.$(ARCH).o	\
+$(SRC)/nnet_apply.$(ARCH).o	\
 $(SRC)/peak.$(ARCH).o		\
 $(SRC)/periodogram.$(ARCH).o	\
@@ -189,4 +193,5 @@
 $(INC)/external.h \
 $(INC)/shell.h \
+$(INC)/data.h \
 $(INC)/dvomath.h \
 $(INC)/display.h 
Index: trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/init.c	(revision 40318)
+++ trunk/Ohana/src/opihi/cmd.data/init.c	(revision 40319)
@@ -89,4 +89,5 @@
 int mset             PROTO((int, char **));
 int needles          PROTO((int, char **));
+int nnet_command     PROTO((int, char **));
 int peak             PROTO((int, char **));
 int periodogram      PROTO((int, char **));
@@ -270,4 +271,5 @@
   {1, "imset",        mset,             "insert a vector in an image"},
   {1, "needles",      needles,          "plot vectors needles"},
+  {1, "nnet",         nnet_command,     "Neural Network commands"},
   {1, "parity",       parity,           "set image parity"},
   {1, "peak",         peak,             "find vector peak in range"},
Index: trunk/Ohana/src/opihi/cmd.data/nnet.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/nnet.c	(revision 40318)
+++ trunk/Ohana/src/opihi/cmd.data/nnet.c	(revision 40319)
@@ -1,17 +1,11 @@
 # include "data.h"
-
-int nnet_list (int argc, char **argv);
-int nnet_create (int argc, char **argv);
-int nnet_delete (int argc, char **argv);
-int nnet_train (int argc, char **argv);
-int nnet_apply (int argc, char **argv);
-int nnet_show (int argc, char **argv);
 
 static Command nnet_commands[] = {
   {1, "list",     nnet_list,     "list nnets"},
-  {1, "init",     nnet_init,     "initialize a nnet"},
   {1, "delete",   nnet_delete,   "delete a nnet"},
   {1, "show",     nnet_show,     "display nnet values"},
   {1, "create",   nnet_create,   "create a nnet"},
+  {1, "set",      nnet_set,      "set nnet node values"},
+  {1, "get",      nnet_get,      "get nnet node values"},
   {1, "train",    nnet_train,    "train nnet on a set of data"},
   {1, "apply",    nnet_apply,    "apply nnet to a set of data"},
@@ -25,8 +19,9 @@
     gprint (GP_ERR, "USAGE: nnet (command)\n");
     gprint (GP_ERR, "    nnet list          : list nnets\n");
-    gprint (GP_ERR, "    nnet init   (nnet) : removes all pages from nnet\n");
     gprint (GP_ERR, "    nnet delete (nnet) : delete a nnet\n");
     gprint (GP_ERR, "    nnet show   (nnet) : show values for a nnet\n");
     gprint (GP_ERR, "    nnet create (nnet) (Ninput) [Nnodes] [Nnodes] ... (Noutput) : create a nnet\n");
+    gprint (GP_ERR, "    nnet set    (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    gprint (GP_ERR, "    nnet get    (nnet) [weights] [biases] ... [weights] [biases] : get nnet weights (images) and biases (vectors)\n");
     gprint (GP_ERR, "    nnet train  (nnet) [input] [input] ... [output] [output] ... : train nnet on data from a set of vectors\n");
     gprint (GP_ERR, "    nnet apply  (nnet) [input] [input] ... [output] [output] ... : apply nnet to input data and generate output\n");
Index: trunk/Ohana/src/opihi/cmd.data/nnet_commands.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/nnet_commands.c	(revision 40318)
+++ trunk/Ohana/src/opihi/cmd.data/nnet_commands.c	(revision 40319)
@@ -12,31 +12,11 @@
 }
 
-/** Fix These ...
-int nnet_init (int argc, char **argv) {
-
+int nnet_delete (int argc, char **argv) {
+
+# if (0)
   int status;
   Nnet *nnet;
 
   if (argc != 2) {
-    gprint (GP_ERR, "USAGE: nnet init (nnet)\n");
-    return FALSE;
-  }
-
-  nnet = FindNnet (argv[1]);
-  if (nnet != NULL) {
-      status = DeleteNnet (nnet);
-      if (!status) abort ();
-  }
-
-  CreateNnet (argv[1]);
-  return TRUE;
-}
-
-int nnet_delete (int argc, char **argv) {
-
-  int status;
-  Nnet *nnet;
-
-  if (argc != 2) {
     gprint (GP_ERR, "USAGE: nnet delete (nnet)\n");
     return FALSE;
@@ -51,4 +31,6 @@
   status = DeleteNnet (nnet);
   if (!status) abort ();
+# endif
+
   return TRUE;
 }
@@ -56,4 +38,5 @@
 int nnet_show (int argc, char **argv) {
 
+# if (0)
   Nnet *nnet;
 
@@ -70,7 +53,8 @@
 
   ListPages (nnet);
-  return TRUE;
-}
-**/
+# endif
+
+  return TRUE;
+}
 
 int nnet_create (int argc, char **argv) {
@@ -97,2 +81,145 @@
 }
 
+int nnet_set (int argc, char **argv) {
+
+  Buffer *matrix;
+  Vector *vector;
+
+  if (argc < 5) {
+    gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    return FALSE;
+  }
+
+  Nnet *nnet = FindNnet (argv[1]);
+  if (nnet == NULL) {
+    gprint (GP_ERR, "nnet %s not found, create it first\n", argv[1]);
+    return FALSE;
+  }
+
+  // compare argc and nnet[0].Nlayer 
+  if (argc - 2 != nnet[0].Nlayer * 2) {
+    gprint (GP_ERR, "ERROR: invalid number of arguments\n");
+    gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    return FALSE;
+  }
+
+  // check for existence of each matrix & vector (and check sizes)
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+    int Narg = 2*(L - 1) + 2;
+    if ((matrix = SelectBuffer (argv[Narg + 0], OLDBUFFER, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown input image %s\n", argv[Narg + 0]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+      return (FALSE);    
+    }
+    if ((vector = SelectVector (argv[Narg + 1], OLDVECTOR, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown input vector %s\n", argv[Narg + 1]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+      return (FALSE);    
+    }
+
+    if (vector->Nelements != nnet[0].Nnodes[L]) {
+      gprint (GP_ERR, "invalid bias vector length %s (%d vs %d)\n", argv[Narg + 1], vector->Nelements, nnet[0].Nnodes[L]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    }
+
+    if (matrix->matrix.Naxis[0] != nnet[0].Nnodes[L-1]) {
+      gprint (GP_ERR, "invalid weight image size for %s (Nx = %d vs %d)\n", argv[Narg + 0], (int) matrix->matrix.Naxis[0], nnet[0].Nnodes[L-1]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    }
+
+    if (matrix->matrix.Naxis[1] != nnet[0].Nnodes[L]) {
+      gprint (GP_ERR, "invalid weight image size for %s (Ny = %d vs %d)\n", argv[Narg + 0], (int) matrix->matrix.Naxis[1], nnet[0].Nnodes[L]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    }
+
+  }
+
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+
+    int Narg = 2*(L - 1) + 2;
+    matrix = SelectBuffer (argv[Narg + 0], OLDBUFFER, FALSE); // I've checked the argv entry above so I do not need to check again...
+    vector = SelectVector (argv[Narg + 1], OLDVECTOR, FALSE);
+
+    opihi_flt *vecvalue = vector[0].elements.Flt;
+    for (int i = 0; i < nnet[0].Nnodes[L]; i++) {
+      nnet[0].biases[L][i] = vecvalue[i];
+    }
+
+    float *matvalue = (float *) matrix[0].matrix.buffer;
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[L-1];
+	nnet[0].weight[L][k] = matvalue[k];
+      }
+    }
+  
+  }
+
+  return TRUE;
+}
+
+int nnet_get (int argc, char **argv) {
+
+  Buffer *matrix;
+  Vector *vector;
+
+  if (argc < 5) {
+    gprint (GP_ERR, "USAGE: nnet get (nnet) [weights] [biases] ... [weights] [biases] : get nnet weights (images) and biases (vectors)\n");
+    return FALSE;
+  }
+
+  Nnet *nnet = FindNnet (argv[1]);
+  if (nnet == NULL) {
+    gprint (GP_ERR, "nnet %s not found, create it first\n", argv[1]);
+    return FALSE;
+  }
+
+  // compare argc and nnet[0].Nlayer 
+  if (argc - 2 != nnet[0].Nlayer * 2) {
+    gprint (GP_ERR, "ERROR: invalid number of arguments\n");
+    gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+    return FALSE;
+  }
+
+  // check for existence of each matrix & vector (and check sizes)
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+    int Narg = 2*(L - 1) + 2;
+    if ((matrix = SelectBuffer (argv[Narg + 0], ANYBUFFER, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown input image %s\n", argv[Narg + 0]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+      return (FALSE);    
+    }
+    if ((vector = SelectVector (argv[Narg + 1], ANYVECTOR, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown input vector %s\n", argv[Narg + 1]);
+      gprint (GP_ERR, "USAGE: nnet set (nnet) [weights] [biases] ... [weights] [biases] : set nnet weights (images) and biases (vectors)\n");
+      return (FALSE);    
+    }
+  }
+
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+
+    int Narg = 2*(L - 1) + 2;
+    matrix = SelectBuffer (argv[Narg + 0], OLDBUFFER, FALSE); // I've checked the argv entry above so I do not need to check again...
+    vector = SelectVector (argv[Narg + 1], OLDVECTOR, FALSE);
+
+    ResetVector (vector, OPIHI_FLT, nnet[0].Nnodes[L]);
+    ResetBuffer (matrix, nnet[0].Nnodes[L-1], nnet[0].Nnodes[L], -32, 0.0, 1.0);
+
+    opihi_flt *vecvalue = vector[0].elements.Flt;
+    for (int i = 0; i < nnet[0].Nnodes[L]; i++) {
+      vecvalue[i] = nnet[0].biases[L][i];
+    }
+
+    float *matvalue = (float *) matrix[0].matrix.buffer;
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[L-1];
+	matvalue[k] = nnet[0].weight[L][k];
+      }
+    }
+  
+  }
+
+  return TRUE;
+}
+
Index: trunk/Ohana/src/opihi/cmd.data/nnet_train.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/nnet_train.c	(revision 40318)
+++ trunk/Ohana/src/opihi/cmd.data/nnet_train.c	(revision 40319)
@@ -1,6 +1,17 @@
 # include "data.h"
-void sortseq (float *X, int *Y, int N);
+
+// these are local only
+float nnet_sigmoid (float value);
+void  nnet_sortseq (float *X, int *Y, int N);
+int   nnet_onelayer (Nnet *nnet, int L);
+void  nnet_reset_Nabla (Nnet *nnet);
+void  nnet_update_Nabla (Nnet *nnet);
+void  nnet_apply_Nabla (Nnet *nnet, int Nmini, float eta);
+void  nnet_backprop (Nnet *nnet, Vector **inVec, Vector **outVec, int N);
+void  nnet_descent_step (Nnet *nnet, Vector **inVec, Vector **outVec, int *seq, int pass, int Nmini, float eta);
 
 int nnet_train (int argc, char **argv) {
+
+  int N;
 
   int Nepoch = 10;
@@ -18,4 +29,6 @@
   }
   
+  float eta = 1.0; // XXX how do I set this? does it need to update?
+
   if (argc < 4) {
     gprint (GP_ERR, "USAGE: nnet train (nnet) [input] [input] ... [output] [output] ...\n");
@@ -97,5 +110,5 @@
     // generate a random sequence : used to select random mini batches
     for (int i = 0; i < Ntrial; i++) { seq[i] = i; rnd[i] = drand48(); }
-    sortseq (rnd, seq, Ntrial);
+    nnet_sortseq (rnd, seq, Ntrial);
 
     int Npass = Ntrial / Nmini;
@@ -116,5 +129,5 @@
 
 // this recreates 'update_mini_batch' from http://neuralnetworksanddeeplearning.com/chap1.html
-void nnet_descent_step (NNet *nnet, Vector **inVec, Vector **outVec, int *seq, int pass, int Nmini, float eta) {
+void nnet_descent_step (Nnet *nnet, Vector **inVec, Vector **outVec, int *seq, int pass, int Nmini, float eta) {
 
   int Ntrial = inVec[0][0].Nelements;
@@ -122,8 +135,8 @@
   nnet_reset_Nabla (nnet);
 
-  for (i = 0; (i < Nmini) && (pass*Nmini + i < Ntrial); i++) {
+  for (int i = 0; (i < Nmini) && (pass*Nmini + i < Ntrial); i++) {
 
     // N is the element of the mini batch on which we are currently operating
-    N = seq[pass*Nmini + i];
+    int N = seq[pass*Nmini + i];
 
     // backprop generates a dNabla_b, dNabla_w pair for the element N of the input and output vectors
@@ -136,14 +149,11 @@
 }
 
-void nnet_backprop (NNet *nnet, Vector **inVec, Vector **outVec, int N) {
+void nnet_backprop (Nnet *nnet, Vector **inVec, Vector **outVec, int N) {
 
   // start with the input values
-
-  // store the input values for this row (trial) in the input value vector
   int Nlayer   = nnet[0].Nlayer;
-  int Ninput   = nnet[0].Nnodes[0];
-  int Noutput  = nnet[0].Nnodes[Nlayer - 1];  // number of input + output nodes
-
-  for (int j = 0; j < Ninput; j++) {
+
+  // store the input values for this row (trial) in the input vector "svalue[0]"
+  for (int j = 0; j < nnet[0].Nnodes[0]; j++) {
     nnet[0].svalue[0][j] = inVec[j][0].elements.Flt[N];
   }
@@ -153,25 +163,28 @@
   // activation = sigmoid (z)
 
+  // feedforward operates on the vector saved in svalue[0]
+  // feedforward saves the zvalues [w * input + bias], svalues [sigmoid(z)], sprimes [sigmoid'(z)] as it runs
   nnet_feedforward (nnet);
 
-  // backward pass (note for now these are vector operations:
-  for (L = Nlayer - 1; L > 0; L--) {
-
-    // sp is array of same size as zvalue for each layer
-    // sp = sigmoid_prime (nnet[0].zvalue[L]); sprime is precalculated in feedforward
+  // backward pass
+  for (int L = Nlayer - 1; L > 0; L--) {
 
     if (L == Nlayer - 1) {
       // starting point uses cost_derivative to compare last svalue set with truth output
-      // delta = cost_derivative(nnet[0].svalue[L], outVec, N) * sprime[L];
+      // delta = cost_derivative(svalue[L], output) * sprime[L];
       for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
-	nnet[0].delta[L][j] = cost_derivative(nnet[0].svalue[L][j], outVec[j][0].elements.Flt[N]) * nnet[0].sprime[L][j];
+	// cost_derivative (svalue[L][j], output[j]) = svalue[L][j] - output[j]
+	// (because cost = (1/2)(svalue[L][j] - output[j])^2)
+	// NOTE: the (1/Npt) factor is pushed to the apply_Nabla step
+	// nnet[0].delta[L][j] = cost_derivative(nnet[0].svalue[L][j], outVec[j][0].elements.Flt[N]) * nnet[0].sprime[L][j];
+	nnet[0].delta[L][j] = (nnet[0].svalue[L][j] - outVec[j][0].elements.Flt[N]) * nnet[0].sprime[L][j];
       }
     } else {
-      // delta = (delta DOT transpose(weights[L+1])) * sp;
+      // delta = DOT(delta, transpose(weight[L+1])) * sprime;
       for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
-	tmpdelta = 0;
+	float tmpdelta = 0.0;
 	for (int i = 0; i < nnet[0].Nnodes[L+1]; i++) {
-	  int k = j + i*nnet[0].Nnodes[L];
-	  tmpdelta += nnet[0].weight[L][k] * nnet[0].delta[L+1][i]; // XXX check on the index values
+	  int k = j + i*nnet[0].Nnodes[L]; // note order of (i,j) : j is [L+1] direction 
+	  tmpdelta += nnet[0].weight[L][k] * nnet[0].delta[L+1][i];
 	}
 	nnet[0].delta[L][j] = tmpdelta * nnet[0].sprime[L][j];
@@ -179,57 +192,63 @@
     }					       
 
-    // UPDATE
-    Nabla_b[L] = delta;
+    // NOTE on weight array: for a given layer, L, the matrix weight[L] maps the nodes in
+    // the previous layer (L-1) to those in L: Nnodes[L-1] -> Nnodes[L].  This is a matrix
+    // with dimensions (Nnodes[L-1] x Nnodes[L]).  The Nnodes[L-1] is the fast dimension,
+    // so for an element (i,j), the index k = i + j*Nnodes[L-1].   
+
+    // Nabla_b[L] = delta;
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      nnet[0]. Nabla_b[L][j] = nnet[0].delta[L][j];
+    }
     
-    // Nabla_w[L] = delta DOT transpose(nnet[0].svalue[L-1]);
+    // Nabla_w[L] = DOT(delta, transpose(svalue[L-1]));
     for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
       for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
-	int k = i + j*nnet[0].Nnodes[n-1];
-	nnet[0]. Nabla_w[n][k] = nnet[0].svalue[L-1][i] * nnet[0].delta[L][j];
-      }
-    }
-
+	int k = i + j*nnet[0].Nnodes[L-1];
+	nnet[0]. Nabla_w[L][k] = nnet[0].svalue[L-1][i] * nnet[0].delta[L][j];
+      }
+    }
   }
 }
 
 // support functions to loop over the Nabla entries
-void nnet_apply_Nabla (NNet *nnet, int Nmini, float eta) {
-  for (int n = 1; n < nnet[0].Nlayer; n++) {
-
-    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
-      nnet[0].biases[n][j] -= (eta / Nmini) * nnet[0].Nabla_b[n][j];
-
-      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
-	int k = i + j*nnet[0].Nnodes[n-1];
-	nnet[0].weights[n][k] -= (eta / Nmini) * nnet[0].Nabla_w[n][k];
-      }
-    }
-  }
-}
-void nnet_update_Nabla (NNet *nnet) {
-  for (int n = 1; n < nnet[0].Nlayer; n++) {
-
-    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
-      nnet[0]. Nabla_b[n][j] += nnet[0].dNabla_b[n][j];
-
-      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
-	int k = i + j*nnet[0].Nnodes[n-1];
-	nnet[0]. Nabla_w[n][k] += nnet[0].dNabla_w[n][k];
-      }
-    }
-  }
-}
-void nnet_reset_Nabla (NNet *nnet) {
-  for (int n = 1; n < nnet[0].Nlayer; n++) {
-
-    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
-
-      nnet[0]. Nabla_b[n][j] = 0;
-      nnet[0].dNabla_b[n][j] = 0;
-
-      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
-	int k = i + j*nnet[0].Nnodes[n-1];
-	nnet[0]. Nabla_w[n][k] = 0;
-	nnet[0].dNabla_w[n][k] = 0;
+void nnet_apply_Nabla (Nnet *nnet, int Nmini, float eta) {
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      nnet[0].biases[L][j] -= (eta / Nmini) * nnet[0].Nabla_b[L][j];
+
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[L-1];
+	nnet[0].weight[L][k] -= (eta / Nmini) * nnet[0].Nabla_w[L][k];
+      }
+    }
+  }
+}
+void nnet_update_Nabla (Nnet *nnet) {
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      nnet[0]. Nabla_b[L][j] += nnet[0].dNabla_b[L][j];
+
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[L-1];
+	nnet[0]. Nabla_w[L][k] += nnet[0].dNabla_w[L][k];
+      }
+    }
+  }
+}
+void nnet_reset_Nabla (Nnet *nnet) {
+  for (int L = 1; L < nnet[0].Nlayer; L++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+
+      nnet[0]. Nabla_b[L][j] = 0;
+      nnet[0].dNabla_b[L][j] = 0;
+
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[L-1];
+	nnet[0]. Nabla_w[L][k] = 0;
+	nnet[0].dNabla_w[L][k] = 0;
       }
     }
@@ -238,5 +257,5 @@
 
 // the input values must already be copied to the input layer svalue[]
-void nnet_feedforward (NNet *nnet) {
+void nnet_feedforward (Nnet *nnet) {
 
   for (int i = 1; i < nnet[0].Nlayer; i++) {
@@ -246,15 +265,15 @@
 }
 
-// calcularte z, sigmoid(z) for each layer (z = w*value + bias)
-int nnet_onelayer (NNet *nnet, int n) {
-
-  if (n < 1) return FALSE; // abort here?
-  if (n >= nnet[0].Nlayer) return FALSE; // abort here?
-
-  // evaluating a single layer [n], n > 0, n < Nlayer:
-  int Ninput   = nnet[0].Nnodes[n - 1];
-  int Noutput  = nnet[0].Nnodes[n];
-
-  // input layer is [n-1], output layer is [n]
+// calculate z, sigmoid(z) for each layer (z = w*value + bias)
+int nnet_onelayer (Nnet *nnet, int L) {
+
+  if (L < 1) return FALSE; // abort here?
+  if (L >= nnet[0].Nlayer) return FALSE; // abort here?
+
+  // evaluating a single layer [L], n > 0, n < Nlayer:
+  int Ninput   = nnet[0].Nnodes[L - 1];
+  int Noutput  = nnet[0].Nnodes[L];
+
+  // input layer is [L-1], output layer is [L]
   for (int j = 0; j < Noutput; j ++) {
     float sum = 0;
@@ -262,10 +281,10 @@
       // weight matrix order is (0, 1, ... Ninput-1, Ninput, Ninput + 1, ... Ninput * Noutput - 1)
       int k = j * Ninput + i;
-      sum += nnet[0].weight[n][k]*nnet[0].svalue[n-1][i];
-    }
-    sum += nnet[0].biases[n][j];
-    nnet[0].zvalue[n][j] = sum;
-    nnet[0].svalue[n][j] = nnet_sigmoid(sum);
-    nnet[0].sprime[n][j] = nnet[0].svalue[n][j] * (1 - nnet[0].svalue[n][j]);
+      sum += nnet[0].weight[L][k]*nnet[0].svalue[L-1][i];
+    }
+    sum += nnet[0].biases[L][j];
+    nnet[0].zvalue[L][j] = sum;
+    nnet[0].svalue[L][j] = nnet_sigmoid(sum);
+    nnet[0].sprime[L][j] = nnet[0].svalue[L][j] * (1 - nnet[0].svalue[L][j]);
     // note that d sigmoid / dz = sigmoid * (1 - sigmoid)
   }
@@ -274,8 +293,8 @@
 
 float nnet_sigmoid (float value) {
-  return 1.0 / (1.0 + exp(-sum));
-}
-
-void sortseq (float *X, int *Y, int N) {
+  return 1.0 / (1.0 + exp(-value));
+}
+
+void nnet_sortseq (float *X, int *Y, int N) {
 
 # define SWAPFUNC(A,B){ float ftmp; int itmp; \
Index: trunk/Ohana/src/opihi/cmd.data/nnet_train.save.c
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/nnet_train.save.c	(revision 40319)
+++ trunk/Ohana/src/opihi/cmd.data/nnet_train.save.c	(revision 40319)
@@ -0,0 +1,293 @@
+# include "data.h"
+void sortseq (float *X, int *Y, int N);
+
+int nnet_train (int argc, char **argv) {
+
+  int Nepoch = 10;
+  if ((N = get_argument (argc, argv, "-Nepoch"))) {
+    remove_argument (N, &argc, argv);
+    Nepoch = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  
+  int Nmini = 100;
+  if ((N = get_argument (argc, argv, "-Nmini"))) {
+    remove_argument (N, &argc, argv);
+    Nmini = atoi (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+  
+  if (argc < 4) {
+    gprint (GP_ERR, "USAGE: nnet train (nnet) [input] [input] ... [output] [output] ...\n");
+    gprint (GP_ERR, "OPTIONS: -Nepoch [N] -Nmini [N]\n");
+    return FALSE;
+  }
+
+  Nnet *nnet = FindNnet (argv[1]);
+  if (nnet == NULL) {
+    gprint (GP_ERR, "nnet %s not found, create it first\n", argv[1]);
+    return FALSE;
+  }
+
+  // save the number of input and output nodes; these need to match the supplied vectors
+  int Nlayer   = nnet[0].Nlayer;
+  int Ninput   = nnet[0].Nnodes[0];
+  int Noutput  = nnet[0].Nnodes[Nlayer - 1];  // number of input + output nodes
+
+  // we need to have the right number of input and output vectors
+  if (argc != Ninput + Noutput + 2) {
+    gprint (GP_ERR, "need %d input and %d output vectors, but we have %d total\n", nnet[0].Nnodes[0], nnet[0].Nnodes[Nlayer - 1], argc - 2);
+    return FALSE;
+  }
+
+  // grab the input and output vectors from the argument list
+  ALLOCATE_PTR (inVec, Vector *, Ninput);
+  ALLOCATE_PTR (outVec, Vector *, Noutput);
+
+  // check that the vectors exist and that lengths match
+  int Ntrial = 0;
+  for (int i = 0; i < Ninput; i++) {
+    if ((inVec[i] = SelectVector (argv[i + 2], OLDVECTOR, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown input vector %s\n", argv[i+2]);
+      gprint (GP_ERR, "USAGE: nnet train (nnet) [input] [input] ... [output] [output] ...\n");
+      free (inVec);
+      free (outVec);
+      return (FALSE);    
+    }
+    if (Ntrial && (inVec[i][0].Nelements != Ntrial)) {
+      gprint (GP_ERR, "input vectors have inconsistent lengths: %d vs %d for %s\n", Ntrial, inVec[i][0].Nelements, inVec[i][0].name);
+      free (inVec);
+      free (outVec);
+      return (FALSE);    
+    }
+    if (!Ntrial) Ntrial = inVec[i][0].Nelements;
+    if (!Ntrial) {
+      gprint (GP_ERR, "trial vectors must be non-zero length\n");
+      free (inVec);
+      free (outVec);
+      return (FALSE);    
+    }
+  }    
+  for (int i = 0; i < Noutput; i++) {
+    if ((outVec[i] = SelectVector (argv[i + 2 + Ninput], OLDVECTOR, FALSE)) == NULL) {
+      gprint (GP_ERR, "unknown output vector %s\n", argv[i+2+Ninput]);
+      gprint (GP_ERR, "USAGE: nnet train (nnet) [input] [input] ... [output] [output] ...\n");
+      free (inVec);
+      free (outVec);
+      return (FALSE);    
+    }
+    if (outVec[i][0].Nelements != Ntrial) {
+      gprint (GP_ERR, "output and input vectors have inconsistent lengths: %d vs %d for %s\n", Ntrial, outVec[i][0].Nelements, outVec[i][0].name);
+      free (inVec);
+      free (outVec);
+      return (FALSE);    
+    }
+  }    
+
+  // core of the training process:
+
+  // index to hold a random sequence
+  ALLOCATE_PTR (seq, int,   Ntrial);
+  ALLOCATE_PTR (rnd, float, Ntrial);
+
+  // train for Nepochs
+  // this recreates 'SGD' from http://neuralnetworksanddeeplearning.com/chap1.html
+  for (int epoch = 0; epoch < Nepoch; epoch ++) {
+    
+    // generate a random sequence : used to select random mini batches
+    for (int i = 0; i < Ntrial; i++) { seq[i] = i; rnd[i] = drand48(); }
+    sortseq (rnd, seq, Ntrial);
+
+    int Npass = Ntrial / Nmini;
+    for (int pass = 0; pass < Npass; pass ++) {
+      // for a given pass select seq elements pass*Nmini to pass*Nmini + Nmini - 1
+      // update the weights and biases using the mini batch subset
+      nnet_descent_step (nnet, inVec, outVec, seq, pass, Nmini, eta);
+      gprint (GP_ERR, "epoch %d of %d, pass %d of %d\n", epoch, Nepoch, pass, Npass);
+    }
+  }  
+
+  free (seq);
+  free (rnd);
+  free (inVec);
+  free (outVec);
+  return TRUE;
+}
+
+// this recreates 'update_mini_batch' from http://neuralnetworksanddeeplearning.com/chap1.html
+void nnet_descent_step (NNet *nnet, Vector **inVec, Vector **outVec, int *seq, int pass, int Nmini, float eta) {
+
+  int Ntrial = inVec[0][0].Nelements;
+
+  nnet_reset_Nabla (nnet);
+
+  for (i = 0; (i < Nmini) && (pass*Nmini + i < Ntrial); i++) {
+
+    // N is the element of the mini batch on which we are currently operating
+    N = seq[pass*Nmini + i];
+
+    // backprop generates a dNabla_b, dNabla_w pair for the element N of the input and output vectors
+    nnet_backprop (nnet, inVec, outVec, N);
+
+    nnet_update_Nabla (nnet);
+  }
+
+  nnet_apply_Nabla (nnet, Nmini, eta);
+}
+
+void nnet_backprop (NNet *nnet, Vector **inVec, Vector **outVec, int N) {
+
+  // start with the input values
+
+  // store the input values for this row (trial) in the input value vector
+  int Nlayer   = nnet[0].Nlayer;
+  int Ninput   = nnet[0].Nnodes[0];
+  int Noutput  = nnet[0].Nnodes[Nlayer - 1];  // number of input + output nodes
+
+  for (int j = 0; j < Ninput; j++) {
+    nnet[0].svalue[0][j] = inVec[j][0].elements.Flt[N];
+  }
+
+  // z = w * activation + bias 
+  // save z [one per non-input layer]
+  // activation = sigmoid (z)
+
+  nnet_feedforward (nnet);
+
+  // backward pass (note for now these are vector operations:
+  for (L = Nlayer - 1; L > 0; L--) {
+
+    // sp is array of same size as zvalue for each layer
+    // sp = sigmoid_prime (nnet[0].zvalue[L]); sprime is precalculated in feedforward
+
+    if (L == Nlayer - 1) {
+      // starting point uses cost_derivative to compare last svalue set with truth output
+      // delta = cost_derivative(nnet[0].svalue[L], outVec, N) * sprime[L];
+      for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+	nnet[0].delta[L][j] = cost_derivative(nnet[0].svalue[L][j], outVec[j][0].elements.Flt[N]) * nnet[0].sprime[L][j];
+      }
+    } else {
+      // delta = (delta DOT transpose(weights[L+1])) * sp;
+      for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+	tmpdelta[L][j] = 0;
+	for (int i = 0; i < nnet[0].Nnodes[L+1]; i++) {
+	  int k = j + i*nnet[0].Nnodes[L];
+	  tmpdelta[L][j] += nnet[0].weight[L][k] * nnet[0].sprime[L][i]; // XXX check on the index values
+	}
+	tmpdelta[L][j] * nnet[0].sprime[L][j];
+      }
+    }					       
+
+    // UPDATE
+    Nabla_b[L] = delta;
+    
+    // Nabla_w[L] = delta DOT transpose(nnet[0].svalue[L-1]);
+    for (int j = 0; j < nnet[0].Nnodes[L]; j++) {
+      for (int i = 0; i < nnet[0].Nnodes[L-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[n-1];
+	nnet[0]. Nabla_w[n][k] = nnet[0].svalue[L-1][i] * nnet[0].delta[L][j];
+      }
+    }
+
+  }
+}
+
+// support functions to loop over the Nabla entries
+void nnet_apply_Nabla (NNet *nnet, int Nmini, float eta) {
+  for (int n = 1; n < nnet[0].Nlayer; n++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
+      nnet[0].biases[n][j] -= (eta / Nmini) * nnet[0].Nabla_b[n][j];
+
+      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[n-1];
+	nnet[0].weights[n][k] -= (eta / Nmini) * nnet[0].Nabla_w[n][k];
+      }
+    }
+  }
+}
+void nnet_update_Nabla (NNet *nnet) {
+  for (int n = 1; n < nnet[0].Nlayer; n++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
+      nnet[0]. Nabla_b[n][j] += nnet[0].dNabla_b[n][j];
+
+      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[n-1];
+	nnet[0]. Nabla_w[n][k] += nnet[0].dNabla_w[n][k];
+      }
+    }
+  }
+}
+void nnet_reset_Nabla (NNet *nnet) {
+  for (int n = 1; n < nnet[0].Nlayer; n++) {
+
+    for (int j = 0; j < nnet[0].Nnodes[n]; j++) {
+
+      nnet[0]. Nabla_b[n][j] = 0;
+      nnet[0].dNabla_b[n][j] = 0;
+
+      for (int i = 0; i < nnet[0].Nnodes[n-1]; i++) {
+	int k = i + j*nnet[0].Nnodes[n-1];
+	nnet[0]. Nabla_w[n][k] = 0;
+	nnet[0].dNabla_w[n][k] = 0;
+      }
+    }
+  }
+}
+
+// the input values must already be copied to the input layer svalue[]
+void nnet_feedforward (NNet *nnet) {
+
+  for (int i = 1; i < nnet[0].Nlayer; i++) {
+    nnet_onelayer (nnet, i);
+  }
+  return;
+}
+
+// calcularte z, sigmoid(z) for each layer (z = w*value + bias)
+int nnet_onelayer (NNet *nnet, int n) {
+
+  if (n < 1) return FALSE; // abort here?
+  if (n >= nnet[0].Nlayer) return FALSE; // abort here?
+
+  // evaluating a single layer [n], n > 0, n < Nlayer:
+  int Ninput   = nnet[0].Nnodes[n - 1];
+  int Noutput  = nnet[0].Nnodes[n];
+
+  // input layer is [n-1], output layer is [n]
+  for (int j = 0; j < Noutput; j ++) {
+    float sum = 0;
+    for (int i = 0; i < Ninput; i++) {
+      // weight matrix order is (0, 1, ... Ninput-1, Ninput, Ninput + 1, ... Ninput * Noutput - 1)
+      int k = j * Ninput + i;
+      sum += nnet[0].weight[n][k]*nnet[0].svalue[n-1][i];
+    }
+    sum += nnet[0].biases[n][j];
+    nnet[0].zvalue[n][j] = sum;
+    nnet[0].svalue[n][j] = nnet_sigmoid(sum);
+    nnet[0].sprime[n][j] = nnet[0].svalue[n][j] * (1 - nnet[0].svalue[n][j]);
+    // note that d sigmoid / dz = sigmoid * (1 - sigmoid)
+  }
+  return TRUE;
+}
+
+float nnet_sigmoid (float value) {
+  return 1.0 / (1.0 + exp(-sum));
+}
+
+void sortseq (float *X, int *Y, int N) {
+
+# define SWAPFUNC(A,B){ float ftmp; int itmp; \
+    ftmp = X[A]; X[A] = X[B]; X[B] = ftmp;      \
+    itmp = Y[A]; Y[A] = Y[B]; Y[B] = itmp;       \
+}
+# define COMPARE(A,B)(X[A] < X[B])
+
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+
+# undef SWAPFUNC
+# undef COMPARE
+
+}
+
Index: trunk/Ohana/src/opihi/cmd.data/test/nnet.sh
===================================================================
--- trunk/Ohana/src/opihi/cmd.data/test/nnet.sh	(revision 40319)
+++ trunk/Ohana/src/opihi/cmd.data/test/nnet.sh	(revision 40319)
@@ -0,0 +1,6 @@
+
+macro test
+
+  nnet create t0 5 10 15 3
+  nnet list
+end
Index: trunk/Ohana/src/opihi/include/data.h
===================================================================
--- trunk/Ohana/src/opihi/include/data.h	(revision 40318)
+++ trunk/Ohana/src/opihi/include/data.h	(revision 40319)
@@ -50,4 +50,24 @@
   float **buffers;
 } MedImageType;
+
+/*** typedef structs used by the Neural Network functions (nnet_*) ***/
+typedef struct {
+  char *name;
+  int Nlayer; // Nlayers = input layer + output layer + hidden layers
+  int *Nnodes; // number of nodes per layer
+  float **weight; // a matrix between each layer
+  float **biases; // a vector for each layer
+
+  float **zvalue; // a vector of z values for each layer (= w*input + b)
+  float **svalue; // a vector of s values for each layer (= sigmoid(z))
+  float **sprime; // 
+  float **delta;  // 
+
+  float ** Nabla_b; // a vector of Nabla_b values for each layer
+  float **dNabla_b; // a vector of Nabla_b values for each layer
+
+  float ** Nabla_w; // a matrix of Nabla_w values for each layer
+  float **dNabla_w; // a matrix of Nabla_w values for each layer
+} Nnet;
 
 void InitData (void);
@@ -229,3 +249,39 @@
 int GetKapaChannelFromString (char *string);
 
+/*** Neural Network functions (nnet_*) ***/
+
+// in lib.data/nnet.c:
+void InitNnets ();
+void InitNnetData (Nnet *nnet, char *name);
+
+void FreeNnets ();
+void FreeNnetData (Nnet *nnet);
+
+Nnet *GetNnet (int where);
+Nnet *FindNnet (char *name);
+Nnet *CreateNnet (char *name, int Nlayer);
+void CreateNnetData (Nnet *nnet);
+
+int DeleteNnet (Nnet *nnet);
+void ListNnets ();
+
+// in cmd.data/nnet.c:
+int nnet_command (int argc, char **argv);
+
+// in cmd.data/nnet_commands.c:
+int nnet_init (int argc, char **argv);
+int nnet_list (int argc, char **argv);
+int nnet_delete (int argc, char **argv);
+int nnet_show (int argc, char **argv);
+int nnet_create (int argc, char **argv);
+int nnet_set (int argc, char **argv);
+int nnet_get (int argc, char **argv);
+
+// in cmd.data/nnet_train.c:
+int nnet_train (int argc, char **argv);
+void nnet_feedforward (Nnet *nnet);
+
+// in cmd.data/nnet_apply.c:
+int nnet_apply (int argc, char **argv);
+
 # endif
Index: trunk/Ohana/src/opihi/lib.data/Makefile
===================================================================
--- trunk/Ohana/src/opihi/lib.data/Makefile	(revision 40318)
+++ trunk/Ohana/src/opihi/lib.data/Makefile	(revision 40319)
@@ -20,5 +20,5 @@
 $(SDIR)/fft.$(ARCH).o			\
 $(SDIR)/svdcmp.$(ARCH).o		\
-$(SDIR)/svdcmp_bond_new.$(ARCH).o		\
+$(SDIR)/svdcmp_bond_new.$(ARCH).o	\
 $(SDIR)/bracket.$(ARCH).o		\
 $(SDIR)/spline.$(ARCH).o		\
@@ -27,4 +27,5 @@
 $(SDIR)/mrqmin.$(ARCH).o		\
 $(SDIR)/mrq2dmin.$(ARCH).o		\
+$(SDIR)/nnet.$(ARCH).o			\
 $(SDIR)/precess.$(ARCH).o		\
 $(SDIR)/starfuncs.$(ARCH).o		\
Index: trunk/Ohana/src/opihi/lib.data/nnet.c
===================================================================
--- trunk/Ohana/src/opihi/lib.data/nnet.c	(revision 40318)
+++ trunk/Ohana/src/opihi/lib.data/nnet.c	(revision 40319)
@@ -1,22 +1,3 @@
 # include "data.h"
-
-typedef struct {
-  char *name;
-  int Nlayers; // Nlayers = input layer + output layer + hidden layers
-  int *Nnodes; // number of nodes per layer
-  float **weights; // a matrix between each layer
-  float **biases; // a vector for each layer
-
-  float **zvalue; // a vector of z values for each layer (= w*input + b)
-  float **svalue; // a vector of s values for each layer (= sigmoid(z))
-  float **sprime; // 
-  float **delta;  // 
-
-  float ** Nabla_b; // a vector of Nabla_b values for each layer
-  float **dNabla_b; // a vector of Nabla_b values for each layer
-
-  float ** Nabla_w; // a matrix of Nabla_w values for each layer
-  float **dNabla_w; // a matrix of Nabla_w values for each layer
-} NNet;
 
 static Nnet **nnets  = NULL; /* nnet to store the list of all nnets */
@@ -34,6 +15,9 @@
   int i;
 
-  for (i = 0; i < Nnnets; i++) {
-    FreeNnet (nnets[i]);
+  if (!nnets) InitNnets();
+
+  for (i = 0; i < Nnnets; i++) {
+    FreeNnetData (nnets[i]);
+    free (nnets[i]);
   }
   free (nnets);
@@ -46,10 +30,12 @@
   nnet[0].name = strcreate (name);
 
-  nnet[0].Nlayers = 0;
+  nnet[0].Nlayer  = 0;
   nnet[0].Nnodes  = NULL;
-  nnet[0].weights = NULL;
+  nnet[0].weight  = NULL;
   nnet[0].biases  = NULL;
-  nnet[0].avalue  = NULL;
+  nnet[0].svalue  = NULL;
   nnet[0].zvalue  = NULL;
+  nnet[0].sprime  = NULL;
+  nnet[0].delta   = NULL;
 
   nnet[0]. Nabla_b  = NULL;
@@ -64,6 +50,6 @@
 
     free (nnet[0].name);
-    for (i = 0; i < nnet[0].Nlayers; i++) {
-      free (nnet[0].weights[i]);
+    for (i = 0; i < nnet[0].Nlayer; i++) {
+      free (nnet[0].weight[i]);
       free (nnet[0].biases[i]);
       free (nnet[0].svalue[i]);
@@ -78,5 +64,5 @@
     }
     free (nnet[0].Nnodes);
-    free (nnet[0].weights);
+    free (nnet[0].weight);
     free (nnet[0].biases);
     free (nnet[0].svalue);
@@ -94,4 +80,6 @@
 Nnet *GetNnet (int where) {
 
+  if (!nnets) InitNnets();
+
   if (where < 0) where += Nnnets;
   if (where < 0) return NULL;
@@ -105,4 +93,6 @@
   int i;
 
+  if (!nnets) InitNnets();
+
   for (i = 0; i < Nnnets; i++) {
     if (!strcmp (nnets[i][0].name, name)) {
@@ -113,14 +103,16 @@
 }
 
-/* make a new named nnet with Nlayers */
+/* make a new named nnet with Nlayer */
 Nnet *CreateNnet (char *name, int Nlayer) {
 
   int N;
   Nnet *nnet;
+
+  if (!nnets) InitNnets();
 
   nnet = FindNnet (name);
   if (nnet != NULL) {
     FreeNnetData (nnet);
-    InitNnetData (nnet);
+    InitNnetData (nnet, name);
     return (nnet);
   }
@@ -134,5 +126,5 @@
   nnet[0].Nlayer = Nlayer;
   ALLOCATE (nnet[0].Nnodes, int, Nlayer);
-  ALLOCATE (nnet[0].weights, float *, Nlayer);
+  ALLOCATE (nnet[0].weight, float *, Nlayer);
   ALLOCATE (nnet[0].biases, float *, Nlayer);
   ALLOCATE (nnet[0].svalue, float *, Nlayer);
@@ -155,5 +147,20 @@
   ohana_gaussdev_init ();
 
-  for (int i = 1; i < Nlayer; i++) {
+  // NOTE : none of these elements are used for the first layer (the input layer)
+  // EXCEPT svalue[0]
+  
+  nnet[0].weight[0] = NULL;
+  nnet[0].biases[0] = NULL;
+  nnet[0].zvalue[0] = NULL;
+  nnet[0].sprime[0] = NULL;
+  nnet[0].delta [0] = NULL;
+  nnet[0]. Nabla_b[0] = NULL;
+  nnet[0].dNabla_b[0] = NULL;
+  nnet[0]. Nabla_w[0] = NULL;
+  nnet[0].dNabla_w[0] = NULL;
+
+  ALLOCATE (nnet[0].svalue[0], float, nnet[0].Nnodes[0]);
+
+  for (int i = 1; i < nnet[0].Nlayer; i++) {
     ALLOCATE (nnet[0].biases[i], float, nnet[0].Nnodes[i]);  // biases for each node in the hidden and output layers only
     for (int j = 0; j < nnet[0].Nnodes[i]; j++) {
@@ -161,7 +168,7 @@
     }
 
-    ALLOCATE (nnet[0].weights[i], float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);  // weights connected each node in the previous layer to the current layer (excludes input layer)
+    ALLOCATE (nnet[0].weight[i], float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);  // weight connected each node in the previous layer to the current layer (excludes input layer)
     for (int j = 0; j < nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]; j++) {
-      nnet[0].weights[i][j] = ohana_gaussdev_rnd (0.0, 1.0);
+      nnet[0].weight[i][j] = ohana_gaussdev_rnd (0.0, 1.0);
     }
 
@@ -171,9 +178,8 @@
     ALLOCATE (nnet[0].delta [i], float, nnet[0].Nnodes[i]);  // vectors for holding results / values for each node in the input, hidden, output layers
 
-    ALLOCATE (nnet[0]. Nabla_b, float, nnet[0].Nnodes[i]);
-    ALLOCATE (nnet[0].dNabla_b, float, nnet[0].Nnodes[i]);
-
-    ALLOCATE (nnet[0]. Nabla_w, float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);
-    ALLOCATE (nnet[0].dNabla_w, float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);
+    ALLOCATE (nnet[0]. Nabla_b[i], float, nnet[0].Nnodes[i]);
+    ALLOCATE (nnet[0].dNabla_b[i], float, nnet[0].Nnodes[i]);
+    ALLOCATE (nnet[0]. Nabla_w[i], float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);
+    ALLOCATE (nnet[0].dNabla_w[i], float, nnet[0].Nnodes[i-1]*nnet[0].Nnodes[i]);
   }
 }
@@ -183,4 +189,6 @@
 
   int i, N, NNNETS_2;
+
+  if (!nnets) InitNnets();
 
   /* find nnet in nnet list */
@@ -204,5 +212,6 @@
   }
 
-  FreeNnet (nnet);
+  FreeNnetData (nnet);
+  free (nnet);
   return (TRUE);
 }
@@ -213,7 +222,9 @@
   int i, j;
 
+  if (!nnets) InitNnets();
+
   for (i = 0; i < Nnnets; i++) {
     gprint (GP_ERR, "%-15s :", nnets[i][0].name);
-    for (j = 0; j < nnets[i][0].Nlayers; i++) {
+    for (j = 0; j < nnets[i][0].Nlayer; j++) {
       gprint (GP_ERR, " %3d", nnets[i][0].Nnodes[j]);
     }
