Neural Network Multi-Class Classification From Scratch Using C# (2025 Update)

I will be giving a talk titled “Introduction to Neural Networks Using C#” at the 2025 Visual Studio Live conference, March 10-14, in Las Vegas. A big part of my talk will be demonstrating a multi-class classification system. I figured I’d go over my existing implementation and clean up a few details.

I used one of my standard synthetic datasets where the goal is to predict a person’s political leaning (conservative, moderate, liberal) from sex, age, State, and income. The data looks Like:

 1,  0.24,  1, 0, 0,  0.2950,  2
-1,  0.39,  0, 0, 1,  0.5120,  1
 1,  0.63,  0, 1, 0,  0.7580,  0
. . .

The fields are sex (-1 = male, +1 = female), age (divided by 100), State (Michigan = 100, Nebraska = 010, Oklahoma = 001, income (divided by $100,000), political leaning (conservative = 0, moderate = 1, liberal = 2). There are 200 training items and 40 test items.

For my architecture, I used a single hidden layer (deep neural networks are possible with C#, but using PyTorch is a better option), with tanh() hidden activation, and softmax() output activation. For weight and bias initialization, I used uniform random in [-0.10, +0.10].

For training, I implemented the batch (aka mini-batch) algorithm where I accumulate the gradients in each batch and then divide by the batch size. For the loss function, I used mean cross entropy error (aka negative log likelihood) which simplifies the calculation of the gradients, compared to using mean squared error. For my demo, I used a batch size of 10 with a constant (non-adaptive) learning rate of 0.01 and max epochs = 1000.

My demo computes a simple classification accuracy. I also implemented a simple confusion matrix that shows more detailed information.

In principle, all the parts of a neural network classifier are simple, but there are many different parts to deal with. That said, neural networks from scratch are beautiful to my eye.

It’s not possible to explain the demo program in a single blog post — even a moderate explanation would take roughly 100 pages (I know because I’ve done it before). So, I’ll just say, if you came across this blog post while searching the Internet for an example of a from-scratch neural network classifier, examine the code below.



Neural networks are simultaneously complex and simple and beautiful (to me at least). Sometimes I entertain myself by starting with an image and then doing repeated image searches on the Internet for similar images, just to see how one image can morph to others due to varying levels of abstraction. Here are three attractive portraits (again, to me anyway) that range from complex to simple.


Demo program. Replace “lt”, “gt”, “lte”, “gte” with Boolean operator symbols. (My blog editor often chokes on symbols).

using System;
using System.IO;
using System.Collections.Generic;

namespace NeuralNetworkMultiClassification
{
  internal class NeuralNetworkMultiClassificationProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("\nNeural network " +
        "multi-class classification C# ");

      Console.WriteLine("Predict political leaning (con," +
        " mod, lib) from sex, age, State, income ");

      // ----------------------------------------------------

      string trainFile =
        "..\\..\\..\\Data\\people_train.txt";
      // sex,  age,  State,  income,  politics
      //  1, 0.24, 1, 0, 0, 0.29500, 2
      // -1, 0.39, 0, 0, 1, 0.51200, 1
      //  1, 0.63, 0, 1, 0, 0.75800, 0
      double[][] trainX = Utils.MatLoad(trainFile,
        new int[] { 0, 1, 2, 3, 4, 5 }, ',', "#");
      double[][] trainY = Utils.MatLoad(trainFile, new int[]
        { 6 }, ',', "#");
      trainY = Utils.MatToOneHot(trainY, 3);

      string testFile =
        "..\\..\\..\\Data\\people_test.txt";
      double[][] testX = Utils.MatLoad(testFile,
        new int[] { 0, 1, 2, 3, 4, 5 }, ',', "#");
      double[][] testY = Utils.MatLoad(testFile, new int[]
        { 6 }, ',', "#");
      testY = Utils.MatToOneHot(testY, 3);

      Console.WriteLine("\nFirst three X data: ");
      for (int i = 0; i "lt" 3; ++i)
        Utils.VecShow(trainX[i], 5, 9, true);

      Console.WriteLine("\nFirst three target Y: ");
      for (int i = 0; i "lt" 3; ++i)
        Utils.VecShow(trainY[i], 1, 5, true);

      // ----------------------------------------------------

      Console.WriteLine("\nCreating 6-100-3 tanh()" +
        " softmax() neural network ");
      NeuralNetwork nn =
        new NeuralNetwork(6, 100, 3);  // seed = 0 default

      int maxEpochs = 1000;
      double lrnRate = 0.01;
      int batSize = 10;  // train = 200 items

      Console.WriteLine("\nmaxEpochs = " +
        maxEpochs);
      Console.WriteLine("lrnRate = " +
        lrnRate.ToString("F3"));
      Console.WriteLine("batSize = " + batSize);

      Console.WriteLine("\nStarting (batch) training ");
      nn.TrainBatch(trainX, trainY, lrnRate,
        batSize, maxEpochs);
      Console.WriteLine("Done ");

      double trainAcc = nn.Accuracy(trainX, trainY);
      Console.WriteLine("\nAccuracy on train data = " +
        trainAcc.ToString("F4"));

      double testAcc = nn.Accuracy(testX, testY);
      Console.WriteLine("Accuracy on test data  = " +
        testAcc.ToString("F4"));

      Console.WriteLine("\nConstructing confusion matrix ");
      int[][] cm = nn.ConfusionMatrix(testX, testY);
      nn.ShowConfusion(cm);

      Console.WriteLine("\nPredicting politics (con," +
        " mod, lib) for male 35 Michigan $49,000 ");
      double[] X = new double[] { -1, 0.35, 1, 0, 0, 0.49000 };
      double[] y = nn.ComputeOutput(X);
      Console.WriteLine("pseudo-probs: ");
      Utils.VecShow(y, 4, 9, true);

      Console.WriteLine("\nSaving model wts to file ");
      nn.SaveWeights("..\\..\\..\\Models\\weights.txt");
      Console.WriteLine("Done ");

      Console.WriteLine("\nEnd NN multi-class classification ");
      Console.ReadLine();
    } // Main

  } // Program

  // --------------------------------------------------------

  public class NeuralNetwork
  {
    private int ni; // number input nodes
    private int nh; // hidden
    private int no; // output

    private double[] iNodes;
    private double[][] ihWeights; // input-hidden
    private double[] hBiases;
    private double[] hNodes;

    private double[][] hoWeights; // hidden-output
    private double[] oBiases;
    private double[] oNodes;

    // gradients
    private double[][] ihGrads;
    private double[] hbGrads;
    private double[][] hoGrads;
    private double[] obGrads;

    private Random rnd;  // wt init and train shuffle

    // ------------------------------------------------------

    public NeuralNetwork(int numIn, int numHid,
      int numOut, int seed = 0)
    {
      this.ni = numIn;  // 6 for this demo
      this.nh = numHid; // 
      this.no = numOut; // 3

      this.iNodes = new double[numIn];

      this.ihWeights = Utils.MatCreate(numIn, numHid);
      this.hBiases = new double[numHid];
      this.hNodes = new double[numHid];

      this.hoWeights = Utils.MatCreate(numHid, numOut);
      this.oBiases = new double[numOut];
      this.oNodes = new double[numOut];

      this.ihGrads = Utils.MatCreate(numIn, numHid);
      this.hbGrads = new double[numHid];
      this.hoGrads = Utils.MatCreate(numHid, numOut);
      this.obGrads = new double[numOut];

      this.rnd = new Random(seed);
      this.InitWeights(); // all weights and biases
    } // ctor

    // ------------------------------------------------------

    private void InitWeights() // helper for ctor
    {
      // weights and biases to small random values
      double lo = -0.10; double hi = +0.10;
      int numWts = (this.ni * this.nh) +
        (this.nh * this.no) + this.nh + this.no;
      double[] initialWeights = new double[numWts];
      for (int i = 0; i "lt" initialWeights.Length; ++i)
        initialWeights[i] =
          (hi - lo) * rnd.NextDouble() + lo;
      this.SetWeights(initialWeights);
    }

    // ------------------------------------------------------

    public void SetWeights(double[] wts)
    {
      // copy serialized weights and biases in wts[] 
      // to ih weights, ih biases, ho weights, ho biases
      int numWts = (this.ni * this.nh) +
        (this.nh * this.no) + this.nh + this.no;
      if (wts.Length != numWts)
        throw new Exception("Bad array in SetWeights");

      int k = 0; // points into wts param

      for (int i = 0; i "lt" this.ni; ++i)
        for (int j = 0; j "lt" this.nh; ++j)
          this.ihWeights[i][j] = wts[k++];
      for (int i = 0; i "lt" this.nh; ++i)
        this.hBiases[i] = wts[k++];
      for (int i = 0; i "lt" this.nh; ++i)
        for (int j = 0; j "lt" this.no; ++j)
          this.hoWeights[i][j] = wts[k++];
      for (int i = 0; i "lt" this.no; ++i)
        this.oBiases[i] = wts[k++];
    }

    // ------------------------------------------------------

    public double[] GetWeights()
    {
      int numWts = (this.ni * this.nh) +
        (this.nh * this.no) + this.nh + this.no;
      double[] result = new double[numWts];
      int k = 0;
      for (int i = 0; i "lt" ihWeights.Length; ++i)
        for (int j = 0; j "lt" this.ihWeights[0].Length; ++j)
          result[k++] = this.ihWeights[i][j];
      for (int i = 0; i "lt" this.hBiases.Length; ++i)
        result[k++] = this.hBiases[i];
      for (int i = 0; i "lt" this.hoWeights.Length; ++i)
        for (int j = 0; j "lt" this.hoWeights[0].Length; ++j)
          result[k++] = this.hoWeights[i][j];
      for (int i = 0; i "lt" this.oBiases.Length; ++i)
        result[k++] = this.oBiases[i];
      return result;
    }

    // ------------------------------------------------------

    public double[] ComputeOutput(double[] x)
    {
      double[] hSums = new double[this.nh]; // scratch 
      double[] oSums = new double[this.no]; // out sums

      for (int i = 0; i "lt" x.Length; ++i)
        this.iNodes[i] = x[i];
      // note: no need to copy x-values unless
      // you implement a ToString.
      // more efficient to simply use the x[] directly.

      // 1. compute i-h sum of weights * inputs
      for (int j = 0; j "lt" this.nh; ++j)
        for (int i = 0; i "lt" this.ni; ++i)
          hSums[j] += this.iNodes[i] *
            this.ihWeights[i][j]; // note +=

      // 2. add biases to hidden sums
      for (int i = 0; i "lt" this.nh; ++i)
        hSums[i] += this.hBiases[i];

      // 3. apply hidden activation
      for (int i = 0; i "lt" this.nh; ++i)
        this.hNodes[i] = HyperTan(hSums[i]);

      // 4. compute h-o sum of wts * hOutputs
      for (int j = 0; j "lt" this.no; ++j)
        for (int i = 0; i "lt" this.nh; ++i)
          oSums[j] += this.hNodes[i] *
            this.hoWeights[i][j];  // [1]

      // 5. add biases to output sums
      for (int i = 0; i "lt" this.no; ++i)
        oSums[i] += this.oBiases[i];

      double[] softOut = Softmax(oSums);
      Array.Copy(softOut, this.oNodes, softOut.Length);

      double[] retResult = new double[this.no];
      Array.Copy(this.oNodes, retResult, retResult.Length);
      return retResult;

    }

    // ------------------------------------------------------

    private static double HyperTan(double x)
    {
      if (x "lt" -10.0) return -1.0;
      else if (x "gt" 10.0) return 1.0;
      else return Math.Tanh(x);
    }

    // ------------------------------------------------------

    private static double[] Softmax(double[] logits)
    {
      // determine max logit 
      // does all output nodes at once so scale
      // doesn't have to be re-computed each time
      double max = logits[0];
      for (int i = 0; i "lt" logits.Length; ++i)
        if (logits[i] "gt" max) max = logits[i];

      // scaling factor -- sum of exp(each val - max)
      double scale = 0.0;
      for (int i = 0; i "lt" logits.Length; ++i)
        scale += Math.Exp(logits[i] - max);

      double[] result = new double[logits.Length];
      for (int i = 0; i "lt" logits.Length; ++i)
        result[i] = Math.Exp(logits[i] - max) / scale;

      return result; // now scaled so that xi sum to 1.0
    }

    // ------------------------------------------------------

    private void ZeroOutGrads()
    {
      for (int i = 0; i "lt" this.ni; ++i)
        for (int j = 0; j "lt" this.nh; ++j)
          this.ihGrads[i][j] = 0.0;

      for (int j = 0; j "lt" this.nh; ++j)
        this.hbGrads[j] = 0.0;

      for (int j = 0; j "lt" this.nh; ++j)
        for (int k = 0; k "lt" this.no; ++k)
          this.hoGrads[j][k] = 0.0;

      for (int k = 0; k "lt" this.no; ++k)
        this.obGrads[k] = 0.0;
    }  // ZeroOutGrads()

    private void AccumGrads(double[] y)
    {
      double[] oSignals = new double[this.no];
      double[] hSignals = new double[this.nh];

      // 1. compute output node scratch signals 
      for (int k = 0; k "lt" this.no; ++k)
      {
        double derivative = 1.0;  // CEE
        //double derivative =
        //  this.oNodes[k] * (1 - this.oNodes[k]); // MSE
        oSignals[k] = derivative *
          (this.oNodes[k] - y[k]);  // CEE
      }

      // 2. accum hidden-to-output gradients 
      for (int j = 0; j "lt" this.nh; ++j)
        for (int k = 0; k "lt" this.no; ++k)
          hoGrads[j][k] +=
           oSignals[k] * this.hNodes[j];

      // 3. accum output node bias gradients
      for (int k = 0; k "lt" this.no; ++k)
        obGrads[k] +=
         oSignals[k] * 1.0;  // 1.0 dummy 

      // 4. compute hidden node signals
      for (int j = 0; j "lt" this.nh; ++j)
      {
        double sum = 0.0;
        for (int k = 0; k "lt" this.no; ++k)
          sum += oSignals[k] * this.hoWeights[j][k];

        double derivative =
          (1 - this.hNodes[j]) *
          (1 + this.hNodes[j]);  // assumes tanh
        hSignals[j] = derivative * sum;
      }

      // 5. accum input-to-hidden gradients
      for (int i = 0; i "lt" this.ni; ++i)
        for (int j = 0; j "lt" this.nh; ++j)
          this.ihGrads[i][j] +=
            hSignals[j] * this.iNodes[i];

      // 6. accum hidden node bias gradients
      for (int j = 0; j "lt" this.nh; ++j)
        this.hbGrads[j] +=
          hSignals[j] * 1.0;  // 1.0 dummy
    } // AccumGrads

    // ------------------------------------------------------

    private void UpdateWeights(double lrnRate)
    {
      // assumes all gradients computed
      // 1. update input-to-hidden weights
      for (int i = 0; i "lt" this.ni; ++i)
      {
        for (int j = 0; j "lt" this.nh; ++j)
        {
          double delta = -1.0 * lrnRate *
            this.ihGrads[i][j];
          this.ihWeights[i][j] += delta;
        }
      }

      // 2. update hidden node biases
      for (int j = 0; j "lt" this.nh; ++j)
      {
        double delta = -1.0 * lrnRate *
          this.hbGrads[j];
        this.hBiases[j] += delta;
      }

      // 3. update hidden-to-output weights
      for (int j = 0; j "lt" this.nh; ++j)
      {
        for (int k = 0; k "lt" this.no; ++k)
        {
          double delta = -1.0 * lrnRate *
            this.hoGrads[j][k];
          this.hoWeights[j][k] += delta;
        }
      }

      // 4. update output node biases
      for (int k = 0; k "lt" this.no; ++k)
      {
        double delta = -1.0 * lrnRate *
          this.obGrads[k];
        this.oBiases[k] += delta;
      }
    } // UpdateWeights()

    public void TrainBatch(double[][] trainX,
      double[][] trainY, double lrnRate, int batSize,
      int maxEpochs)
    {
      int n = trainX.Length;  // 200
      int batchesPerEpoch = n / batSize;  // 20
      int freq = maxEpochs / 10;  // to show progress

      int[] indices = new int[n];
      for (int i = 0; i "lt" n; ++i)
        indices[i] = i;

      // ----------------------------------------------------
      //
      // n = 200; bs = 10
      // batches per epoch = 200 / 10 = 20

      // for epoch = 0; epoch "lt" maxEpochs; ++epoch
      //   for batch = 0; batch "lt" bpe; ++batch
      //     for item = 0; item "lt" bs; ++item
      //       compute output
      //       accum grads
      //     end-item
      //     update weights
      //     zero-out grads
      //   end-batches
      //   shuffle indices
      // end-epochs
      //
      // ----------------------------------------------------

      for (int epoch = 0; epoch "lt" maxEpochs; ++epoch)
      {
        Shuffle(indices);
        int ptr = 0;  // points into indices
        for (int batIdx = 0; batIdx "lt" batchesPerEpoch;
          ++batIdx) // 0, 1, . . 19
        {
          for (int i = 0; i "lt" batSize; ++i) // 0 . . 9
          {
            int ii = indices[ptr++];  // compute output
            double[] x = trainX[ii];
            double[] y = trainY[ii];
            this.ComputeOutput(x);  // into this.oNoodes
            this.AccumGrads(y);
          }
          this.UpdateWeights(lrnRate);
          this.ZeroOutGrads(); // prep for next batch
        } // batches

        if (epoch % freq == 0)  // progress every few epochs
        {
          double mcee =
            this.MeanCrossEntError(trainX, trainY);
          double acc =
            this.Accuracy(trainX, trainY);

          string s1 = "epoch: " +
            epoch.ToString().PadLeft(4);
          string s2 = "  MCEE = " +
            mcee.ToString("F4");
          string s3 = "  acc = " +
            acc.ToString("F4");
          Console.WriteLine(s1 + s2 + s3);
        }
      } // epoch
    } // TrainBatch

    // ------------------------------------------------------

    private void Shuffle(int[] sequence)
    {
      // Fisher-Yates
      for (int i = 0; i "lt" sequence.Length; ++i)
      {
        int r = this.rnd.Next(i, sequence.Length);
        int tmp = sequence[r];
        sequence[r] = sequence[i];
        sequence[i] = tmp;
        // sequence[i] = i; // for testing
      }
    } // Shuffle

    // ------------------------------------------------------

    public double MeanSqError(double[][] trainX,
      double[][] trainY)
    {
      // MSE - useful for progress (easier to interpret)
      int n = trainX.Length;
      double sumSquaredError = 0.0;
      for (int i = 0; i "lt" n; ++i)
      {
        double[] predY = this.ComputeOutput(trainX[i]);
        double[] actualY = trainY[i];

        for (int j = 0; j "lt" this.no; ++j)
        {
          sumSquaredError += (predY[j] - actualY[j]) *
            (predY[j] - actualY[j]);
        }
      }
      return sumSquaredError / n;
    } // MSE loss 

    // ------------------------------------------------------

    public double MeanCrossEntError(double[][] trainX,
      double[][] trainY)
    {
      int n = trainX.Length;
      double sum = 0.0;
      for (int i = 0; i "lt" n; ++i)
      {
        double[] predY = this.ComputeOutput(trainX[i]);
        int idx = ArgMax(trainY[i]); // loc of 1.0 target
        sum += -Math.Log(predY[idx]);
      }
      return sum / n;
    } // MCEE loss 

    // ------------------------------------------------------

    private static int ArgMax(double[] v)
    {
      // index of largest value in v[]
      int result = 0;
      double big = v[0];
      for (int i = 0; i "lt" v.Length; ++i)
      {
        if (v[i] "gt" big)
        {
          result = i;
          big = v[i];
        }
      }
      return result;
    }

    // ------------------------------------------------------

    public double Accuracy(double[][] dataX,
      double[][] dataY)
    {
      int n = dataX.Length;
      int nCorrect = 0; int nWrong = 0;
      for (int i = 0; i "lt" n; ++i)
      {
        double[] predY = this.ComputeOutput(dataX[i]);
        double[] actualY = dataY[i];
        if (ArgMax(predY) == ArgMax(actualY))
          ++nCorrect;
        else
          ++nWrong;
      }
      return (nCorrect * 1.0) / (nCorrect + nWrong);
    }

    // ------------------------------------------------------

    public int[][] ConfusionMatrix(double[][] dataX,
      double[][] dataY)
    {
      int n = this.no;
      int[][] result = new int[n][];  // nxn
      for (int i = 0; i "lt" n; ++i)
        result[i] = new int[n];

      for (int i = 0; i "lt" dataX.Length; ++i)
      {
        double[] x = dataX[i];  // inputs
        int targetK = ArgMax(dataY[i]);  // target 
        double[] probs = this.ComputeOutput(x);  // pseudo
        int predK = ArgMax(probs);  // predicted 0, 1, 2
        ++result[targetK][predK];
      }
      return result;
    }

    public void ShowConfusion(int[][] cm)
    {
      int n = cm.Length;
      for (int i = 0; i "lt" n; ++i)
      {
        Console.Write("actual " + i + ": ");
        for (int j = 0; j "lt" n; ++j)
        {
          Console.Write(cm[i][j].ToString().
            PadLeft(4) + " ");
        }
        Console.WriteLine("");
      }
    }

    public void SaveWeights(string fn)
    {
      double[] wts = this.GetWeights(); // wts and biases
      // order: ih weights, ih biases, ho weights, ho biases
      // one value per line
      FileStream ofs = new FileStream(fn, FileMode.Create);
      StreamWriter sw = new StreamWriter(ofs);
      for (int i = 0; i "lt" wts.Length; ++i)
        sw.WriteLine(wts[i].ToString("F8"));
      sw.Close();
      ofs.Close();
    }

    public void LoadWeights(string fn)
    {
      FileStream ifs = new FileStream(fn, FileMode.Open);
      StreamReader sr = new StreamReader(ifs);
      List listWts = new List();
      string line = "";  // one wt per line
      while ((line = sr.ReadLine()) != null)
      {
        // if (line.StartsWith(comment) == true)
        //   continue;
        listWts.Add(double.Parse(line));
      }
      sr.Close();
      ifs.Close();

      double[] wts = listWts.ToArray();
      this.SetWeights(wts);
    }

  } // NeuralNetwork class

  // --------------------------------------------------------

  public class Utils
  {
    public static double[][] VecToMat(double[] vec,
      int rows, int cols)
    {
      // vector to row vec/matrix
      double[][] result = MatCreate(rows, cols);
      int k = 0;
      for (int i = 0; i "lt" rows; ++i)
        for (int j = 0; j "lt" cols; ++j)
          result[i][j] = vec[k++];
      return result;
    }

    // ------------------------------------------------------

    public static double[][] MatCreate(int rows,
      int cols)
    {
      double[][] result = new double[rows][];
      for (int i = 0; i "lt" rows; ++i)
        result[i] = new double[cols];
      return result;
    }

    // ------------------------------------------------------

    public static double[][] MatLoad(string fn, int[] usecols,
      char sep, string comment)
    {
      List"lt"double[]"gt" result = new List"lt"double[]"gt"();
      string line = "";
      FileStream ifs = new FileStream(fn, FileMode.Open);
      StreamReader sr = new StreamReader(ifs);
      while ((line = sr.ReadLine()) != null)
      {
        if (line.StartsWith(comment) == true)
          continue;
        string[] tokens = line.Split(sep);
        List"lt"double"gt" lst = new List"lt"double"gt"();
        for (int j = 0; j "lt" usecols.Length; ++j)
          lst.Add(double.Parse(tokens[usecols[j]]));
        double[] row = lst.ToArray();
        result.Add(row);
      }
      sr.Close(); ifs.Close();
      return result.ToArray();
    }

    // ------------------------------------------------------

    //static int NumNonCommentLines(string fn,
    //  string comment)
    //{
    //  int ct = 0;
    //  string line = "";
    //  FileStream ifs = new FileStream(fn,
    //    FileMode.Open);
    //  StreamReader sr = new StreamReader(ifs);
    //  while ((line = sr.ReadLine()) != null)
    //    if (line.StartsWith(comment) == false)
    //      ++ct;
    //  sr.Close(); ifs.Close();
    //  return ct;
    //}

    // ------------------------------------------------------

    //public static double[][] MatLoad(string fn,
    //  int[] usecols, char sep, string comment)
    //{
    //  // count number of non-comment lines
    //  int nRows = NumNonCommentLines(fn, comment);
    //  int nCols = usecols.Length;
    //  double[][] result = MatCreate(nRows, nCols);
    //  string line = "";
    //  string[] tokens = null;
    //  FileStream ifs = new FileStream(fn, FileMode.Open);
    //  StreamReader sr = new StreamReader(ifs);

    //  int i = 0;
    //  while ((line = sr.ReadLine()) != null)
    //  {
    //    if (line.StartsWith(comment) == true)
    //      continue;
    //    tokens = line.Split(sep);
    //    for (int j = 0; j "lt" nCols; ++j)
    //    {
    //      int k = usecols[j];  // into tokens
    //      result[i][j] = double.Parse(tokens[k]);
    //    }
    //    ++i;
    //  }
    //  sr.Close(); ifs.Close();
    //  return result;
    //}

    // ------------------------------------------------------

    public static double[] MatToVec(double[][] m)
    {
      int rows = m.Length;
      int cols = m[0].Length;
      double[] result = new double[rows * cols];
      int k = 0;
      for (int i = 0; i "lt" rows; ++i)
        for (int j = 0; j "lt" cols; ++j)
          result[k++] = m[i][j];

      return result;
    }

    // ------------------------------------------------------

    public static double[][] MatToOneHot(double[][] m,
      int n)
    {
      // convert ordinal (0,1,2 . .) to one-hot
      int rows = m.Length;
      int cols = m[0].Length;  // assumed 1
      double[][] result = MatCreate(rows, n);
      for (int i = 0; i "lt" rows; ++i)
      {
        int k = (int)m[i][0];  // 0,1,2 . .
        result[i] = new double[n];  // [0.0  0.0  0.0]
        result[i][k] = 1.0;  // [ 0.0  1.0  0.0]
      }

      return result;
    }

    // ------------------------------------------------------

    public static void MatShow(double[][] m,
      int dec, int wid)
    {
      for (int i = 0; i "lt" m.Length; ++i)
      {
        for (int j = 0; j "lt" m[0].Length; ++j)
        {
          double v = m[i][j];
          if (Math.Abs(v) "lt" 1.0e-8) v = 0.0; // hack
          Console.Write(v.ToString("F" +
            dec).PadLeft(wid));
        }
        Console.WriteLine("");
      }
    }

    // ------------------------------------------------------

    public static void VecShow(int[] vec, int wid)
    {
      for (int i = 0; i "lt" vec.Length; ++i)
        Console.Write(vec[i].ToString().PadLeft(wid));
      Console.WriteLine("");
    }

    // ------------------------------------------------------

    public static void VecShow(double[] vec,
      int dec, int wid, bool newLine)
    {
      for (int i = 0; i "lt" vec.Length; ++i)
      {
        double x = vec[i];
        if (Math.Abs(x) "lt" 1.0e-8) x = 0.0;
        Console.Write(x.ToString("F" +
          dec).PadLeft(wid));
      }
      if (newLine == true)
        Console.WriteLine("");
    }

  } // Utils class

} // ns

Training data.

# people_train.txt
# sex (M=-1, F=1)  age  state (michigan, 
# nebraska, oklahoma) income
# politics (consrvative, moderate, liberal)
#
 1, 0.24, 1, 0, 0, 0.29500, 2
-1, 0.39, 0, 0, 1, 0.51200, 1
 1, 0.63, 0, 1, 0, 0.75800, 0
-1, 0.36, 1, 0, 0, 0.44500, 1
 1, 0.27, 0, 1, 0, 0.28600, 2
 1, 0.50, 0, 1, 0, 0.56500, 1
 1, 0.50, 0, 0, 1, 0.55000, 1
-1, 0.19, 0, 0, 1, 0.32700, 0
 1, 0.22, 0, 1, 0, 0.27700, 1
-1, 0.39, 0, 0, 1, 0.47100, 2
 1, 0.34, 1, 0, 0, 0.39400, 1
-1, 0.22, 1, 0, 0, 0.33500, 0
 1, 0.35, 0, 0, 1, 0.35200, 2
-1, 0.33, 0, 1, 0, 0.46400, 1
 1, 0.45, 0, 1, 0, 0.54100, 1
 1, 0.42, 0, 1, 0, 0.50700, 1
-1, 0.33, 0, 1, 0, 0.46800, 1
 1, 0.25, 0, 0, 1, 0.30000, 1
-1, 0.31, 0, 1, 0, 0.46400, 0
 1, 0.27, 1, 0, 0, 0.32500, 2
 1, 0.48, 1, 0, 0, 0.54000, 1
-1, 0.64, 0, 1, 0, 0.71300, 2
 1, 0.61, 0, 1, 0, 0.72400, 0
 1, 0.54, 0, 0, 1, 0.61000, 0
 1, 0.29, 1, 0, 0, 0.36300, 0
 1, 0.50, 0, 0, 1, 0.55000, 1
 1, 0.55, 0, 0, 1, 0.62500, 0
 1, 0.40, 1, 0, 0, 0.52400, 0
 1, 0.22, 1, 0, 0, 0.23600, 2
 1, 0.68, 0, 1, 0, 0.78400, 0
-1, 0.60, 1, 0, 0, 0.71700, 2
-1, 0.34, 0, 0, 1, 0.46500, 1
-1, 0.25, 0, 0, 1, 0.37100, 0
-1, 0.31, 0, 1, 0, 0.48900, 1
 1, 0.43, 0, 0, 1, 0.48000, 1
 1, 0.58, 0, 1, 0, 0.65400, 2
-1, 0.55, 0, 1, 0, 0.60700, 2
-1, 0.43, 0, 1, 0, 0.51100, 1
-1, 0.43, 0, 0, 1, 0.53200, 1
-1, 0.21, 1, 0, 0, 0.37200, 0
 1, 0.55, 0, 0, 1, 0.64600, 0
 1, 0.64, 0, 1, 0, 0.74800, 0
-1, 0.41, 1, 0, 0, 0.58800, 1
 1, 0.64, 0, 0, 1, 0.72700, 0
-1, 0.56, 0, 0, 1, 0.66600, 2
 1, 0.31, 0, 0, 1, 0.36000, 1
-1, 0.65, 0, 0, 1, 0.70100, 2
 1, 0.55, 0, 0, 1, 0.64300, 0
-1, 0.25, 1, 0, 0, 0.40300, 0
 1, 0.46, 0, 0, 1, 0.51000, 1
-1, 0.36, 1, 0, 0, 0.53500, 0
 1, 0.52, 0, 1, 0, 0.58100, 1
 1, 0.61, 0, 0, 1, 0.67900, 0
 1, 0.57, 0, 0, 1, 0.65700, 0
-1, 0.46, 0, 1, 0, 0.52600, 1
-1, 0.62, 1, 0, 0, 0.66800, 2
 1, 0.55, 0, 0, 1, 0.62700, 0
-1, 0.22, 0, 0, 1, 0.27700, 1
-1, 0.50, 1, 0, 0, 0.62900, 0
-1, 0.32, 0, 1, 0, 0.41800, 1
-1, 0.21, 0, 0, 1, 0.35600, 0
 1, 0.44, 0, 1, 0, 0.52000, 1
 1, 0.46, 0, 1, 0, 0.51700, 1
 1, 0.62, 0, 1, 0, 0.69700, 0
 1, 0.57, 0, 1, 0, 0.66400, 0
-1, 0.67, 0, 0, 1, 0.75800, 2
 1, 0.29, 1, 0, 0, 0.34300, 2
 1, 0.53, 1, 0, 0, 0.60100, 0
-1, 0.44, 1, 0, 0, 0.54800, 1
 1, 0.46, 0, 1, 0, 0.52300, 1
-1, 0.20, 0, 1, 0, 0.30100, 1
-1, 0.38, 1, 0, 0, 0.53500, 1
 1, 0.50, 0, 1, 0, 0.58600, 1
 1, 0.33, 0, 1, 0, 0.42500, 1
-1, 0.33, 0, 1, 0, 0.39300, 1
 1, 0.26, 0, 1, 0, 0.40400, 0
 1, 0.58, 1, 0, 0, 0.70700, 0
 1, 0.43, 0, 0, 1, 0.48000, 1
-1, 0.46, 1, 0, 0, 0.64400, 0
 1, 0.60, 1, 0, 0, 0.71700, 0
-1, 0.42, 1, 0, 0, 0.48900, 1
-1, 0.56, 0, 0, 1, 0.56400, 2
-1, 0.62, 0, 1, 0, 0.66300, 2
-1, 0.50, 1, 0, 0, 0.64800, 1
 1, 0.47, 0, 0, 1, 0.52000, 1
-1, 0.67, 0, 1, 0, 0.80400, 2
-1, 0.40, 0, 0, 1, 0.50400, 1
 1, 0.42, 0, 1, 0, 0.48400, 1
 1, 0.64, 1, 0, 0, 0.72000, 0
-1, 0.47, 1, 0, 0, 0.58700, 2
 1, 0.45, 0, 1, 0, 0.52800, 1
-1, 0.25, 0, 0, 1, 0.40900, 0
 1, 0.38, 1, 0, 0, 0.48400, 0
 1, 0.55, 0, 0, 1, 0.60000, 1
-1, 0.44, 1, 0, 0, 0.60600, 1
 1, 0.33, 1, 0, 0, 0.41000, 1
 1, 0.34, 0, 0, 1, 0.39000, 1
 1, 0.27, 0, 1, 0, 0.33700, 2
 1, 0.32, 0, 1, 0, 0.40700, 1
 1, 0.42, 0, 0, 1, 0.47000, 1
-1, 0.24, 0, 0, 1, 0.40300, 0
 1, 0.42, 0, 1, 0, 0.50300, 1
 1, 0.25, 0, 0, 1, 0.28000, 2
 1, 0.51, 0, 1, 0, 0.58000, 1
-1, 0.55, 0, 1, 0, 0.63500, 2
 1, 0.44, 1, 0, 0, 0.47800, 2
-1, 0.18, 1, 0, 0, 0.39800, 0
-1, 0.67, 0, 1, 0, 0.71600, 2
 1, 0.45, 0, 0, 1, 0.50000, 1
 1, 0.48, 1, 0, 0, 0.55800, 1
-1, 0.25, 0, 1, 0, 0.39000, 1
-1, 0.67, 1, 0, 0, 0.78300, 1
 1, 0.37, 0, 0, 1, 0.42000, 1
-1, 0.32, 1, 0, 0, 0.42700, 1
 1, 0.48, 1, 0, 0, 0.57000, 1
-1, 0.66, 0, 0, 1, 0.75000, 2
 1, 0.61, 1, 0, 0, 0.70000, 0
-1, 0.58, 0, 0, 1, 0.68900, 1
 1, 0.19, 1, 0, 0, 0.24000, 2
 1, 0.38, 0, 0, 1, 0.43000, 1
-1, 0.27, 1, 0, 0, 0.36400, 1
 1, 0.42, 1, 0, 0, 0.48000, 1
 1, 0.60, 1, 0, 0, 0.71300, 0
-1, 0.27, 0, 0, 1, 0.34800, 0
 1, 0.29, 0, 1, 0, 0.37100, 0
-1, 0.43, 1, 0, 0, 0.56700, 1
 1, 0.48, 1, 0, 0, 0.56700, 1
 1, 0.27, 0, 0, 1, 0.29400, 2
-1, 0.44, 1, 0, 0, 0.55200, 0
 1, 0.23, 0, 1, 0, 0.26300, 2
-1, 0.36, 0, 1, 0, 0.53000, 2
 1, 0.64, 0, 0, 1, 0.72500, 0
 1, 0.29, 0, 0, 1, 0.30000, 2
-1, 0.33, 1, 0, 0, 0.49300, 1
-1, 0.66, 0, 1, 0, 0.75000, 2
-1, 0.21, 0, 0, 1, 0.34300, 0
 1, 0.27, 1, 0, 0, 0.32700, 2
 1, 0.29, 1, 0, 0, 0.31800, 2
-1, 0.31, 1, 0, 0, 0.48600, 1
 1, 0.36, 0, 0, 1, 0.41000, 1
 1, 0.49, 0, 1, 0, 0.55700, 1
-1, 0.28, 1, 0, 0, 0.38400, 0
-1, 0.43, 0, 0, 1, 0.56600, 1
-1, 0.46, 0, 1, 0, 0.58800, 1
 1, 0.57, 1, 0, 0, 0.69800, 0
-1, 0.52, 0, 0, 1, 0.59400, 1
-1, 0.31, 0, 0, 1, 0.43500, 1
-1, 0.55, 1, 0, 0, 0.62000, 2
 1, 0.50, 1, 0, 0, 0.56400, 1
 1, 0.48, 0, 1, 0, 0.55900, 1
-1, 0.22, 0, 0, 1, 0.34500, 0
 1, 0.59, 0, 0, 1, 0.66700, 0
 1, 0.34, 1, 0, 0, 0.42800, 2
-1, 0.64, 1, 0, 0, 0.77200, 2
 1, 0.29, 0, 0, 1, 0.33500, 2
-1, 0.34, 0, 1, 0, 0.43200, 1
-1, 0.61, 1, 0, 0, 0.75000, 2
 1, 0.64, 0, 0, 1, 0.71100, 0
-1, 0.29, 1, 0, 0, 0.41300, 0
 1, 0.63, 0, 1, 0, 0.70600, 0
-1, 0.29, 0, 1, 0, 0.40000, 0
-1, 0.51, 1, 0, 0, 0.62700, 1
-1, 0.24, 0, 0, 1, 0.37700, 0
 1, 0.48, 0, 1, 0, 0.57500, 1
 1, 0.18, 1, 0, 0, 0.27400, 0
 1, 0.18, 1, 0, 0, 0.20300, 2
 1, 0.33, 0, 1, 0, 0.38200, 2
-1, 0.20, 0, 0, 1, 0.34800, 0
 1, 0.29, 0, 0, 1, 0.33000, 2
-1, 0.44, 0, 0, 1, 0.63000, 0
-1, 0.65, 0, 0, 1, 0.81800, 0
-1, 0.56, 1, 0, 0, 0.63700, 2
-1, 0.52, 0, 0, 1, 0.58400, 1
-1, 0.29, 0, 1, 0, 0.48600, 0
-1, 0.47, 0, 1, 0, 0.58900, 1
 1, 0.68, 1, 0, 0, 0.72600, 2
 1, 0.31, 0, 0, 1, 0.36000, 1
 1, 0.61, 0, 1, 0, 0.62500, 2
 1, 0.19, 0, 1, 0, 0.21500, 2
 1, 0.38, 0, 0, 1, 0.43000, 1
-1, 0.26, 1, 0, 0, 0.42300, 0
 1, 0.61, 0, 1, 0, 0.67400, 0
 1, 0.40, 1, 0, 0, 0.46500, 1
-1, 0.49, 1, 0, 0, 0.65200, 1
 1, 0.56, 1, 0, 0, 0.67500, 0
-1, 0.48, 0, 1, 0, 0.66000, 1
 1, 0.52, 1, 0, 0, 0.56300, 2
-1, 0.18, 1, 0, 0, 0.29800, 0
-1, 0.56, 0, 0, 1, 0.59300, 2
-1, 0.52, 0, 1, 0, 0.64400, 1
-1, 0.18, 0, 1, 0, 0.28600, 1
-1, 0.58, 1, 0, 0, 0.66200, 2
-1, 0.39, 0, 1, 0, 0.55100, 1
-1, 0.46, 1, 0, 0, 0.62900, 1
-1, 0.40, 0, 1, 0, 0.46200, 1
-1, 0.60, 1, 0, 0, 0.72700, 2
 1, 0.36, 0, 1, 0, 0.40700, 2
 1, 0.44, 1, 0, 0, 0.52300, 1
 1, 0.28, 1, 0, 0, 0.31300, 2
 1, 0.54, 0, 0, 1, 0.62600, 0

Test data.

# people_test.txt
#
-1, 0.51, 1, 0, 0, 0.61200, 1
-1, 0.32, 0, 1, 0, 0.46100, 1
 1, 0.55, 1, 0, 0, 0.62700, 0
 1, 0.25, 0, 0, 1, 0.26200, 2
 1, 0.33, 0, 0, 1, 0.37300, 2
-1, 0.29, 0, 1, 0, 0.46200, 0
 1, 0.65, 1, 0, 0, 0.72700, 0
-1, 0.43, 0, 1, 0, 0.51400, 1
-1, 0.54, 0, 1, 0, 0.64800, 2
 1, 0.61, 0, 1, 0, 0.72700, 0
 1, 0.52, 0, 1, 0, 0.63600, 0
 1, 0.30, 0, 1, 0, 0.33500, 2
 1, 0.29, 1, 0, 0, 0.31400, 2
-1, 0.47, 0, 0, 1, 0.59400, 1
 1, 0.39, 0, 1, 0, 0.47800, 1
 1, 0.47, 0, 0, 1, 0.52000, 1
-1, 0.49, 1, 0, 0, 0.58600, 1
-1, 0.63, 0, 0, 1, 0.67400, 2
-1, 0.30, 1, 0, 0, 0.39200, 0
-1, 0.61, 0, 0, 1, 0.69600, 2
-1, 0.47, 0, 0, 1, 0.58700, 1
 1, 0.30, 0, 0, 1, 0.34500, 2
-1, 0.51, 0, 0, 1, 0.58000, 1
-1, 0.24, 1, 0, 0, 0.38800, 1
-1, 0.49, 1, 0, 0, 0.64500, 1
 1, 0.66, 0, 0, 1, 0.74500, 0
-1, 0.65, 1, 0, 0, 0.76900, 0
-1, 0.46, 0, 1, 0, 0.58000, 0
-1, 0.45, 0, 0, 1, 0.51800, 1
-1, 0.47, 1, 0, 0, 0.63600, 0
-1, 0.29, 1, 0, 0, 0.44800, 0
-1, 0.57, 0, 0, 1, 0.69300, 2
-1, 0.20, 1, 0, 0, 0.28700, 2
-1, 0.35, 1, 0, 0, 0.43400, 1
-1, 0.61, 0, 0, 1, 0.67000, 2
-1, 0.31, 0, 0, 1, 0.37300, 1
 1, 0.18, 1, 0, 0, 0.20800, 2
 1, 0.26, 0, 0, 1, 0.29200, 2
-1, 0.28, 1, 0, 0, 0.36400, 2
-1, 0.59, 0, 0, 1, 0.69400, 2
This entry was posted in Machine Learning. Bookmark the permalink.

Leave a Reply