The 17 Absolutely Essential Matrix and Vector Functions for Machine Learning Using C#

It is possible to implement almost any kind of machine learning algorithm using C# or the Python NumPy numeric library. NumPy has hundreds of built-in functions to do almost anything. But raw C# doesn’t have any and so it’s necessary to implement even the most basic functions.

I was sitting at home one evening and speculated aboout the absolutely essential matrix and functions needed for C# machine learning. I came up with 17 functions: MatMake(), MatLoad(), MatShow(), VecShow(), MatCopy(), MatIdentity(), MatTranspose(), MatProduct(), MatSum(), MatVecProduct(), VecMatProduct(), VecToMat(), MatToVec(), VecNorm(), VecDot(), VecEucDist(), Shuffle().

Of course there are dozens more, but for a beginner I think these are definitely the required ones. And note that I’m not considering sophisticated matrix functions such as matrix inverse, matrix pseudo-inverse, and matrix decomposition.

My matrix functions use a simple array-of-arrays design, as opposed to some sort of program-defined Matrix class definition, which is overkill in my opinion.

Output of a super short demo:

Begin matrix and vector basics

A =
  1.0  2.0  3.0  4.0
  5.0  6.0  7.0  8.0
  9.0  0.0  1.0  2.0

At =
  1.0  5.0  9.0
  2.0  6.0  0.0
  3.0  7.0  1.0
  4.0  8.0  2.0

At * A =
 107.0  32.0  47.0  62.0
  32.0  40.0  48.0  56.0
  47.0  48.0  59.0  70.0
  62.0  56.0  70.0  84.0

v =
   2.0   4.0   6.0   8.0

A * v =
   60.0  140.0   40.0

End demo

The purpose of most of these 17 basic functions should be clear from their names. MatLoad() loads a matrix from values in a text file. The Shuffle() function scrambles the order of indices in an array of integers — this is used to randomize the order in which rows of training data are processed for SGD training.



I enjoy trivia contests. There are a lot of areas that are essential knowledge, such as geography, American history, and classical art. James Tissot (1836-1902) makes occassional appearances in trivia answers. Tissot did a wide range of paintings, illustrations, and caricatures. Here are two of my favorite Tissot paintings. Left: “An Interesting Story” (1872). Right: “The Gallery of HMS Calcutta” (1876).


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

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

namespace MatrixVectorBasics
{
  internal class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("\nBegin matrix and vector basics ");

      //double[][] A = MatLoad("data.txt",
      //  new int[] { 0, 1, 2, 3 }, ',', "#");

      double[][] A = new double[3][];
      A[0] = new double[] { 1, 2, 3, 4 };
      A[1] = new double[] { 5, 6, 7, 8 };
      A[2] = new double[] { 9, 0, 1, 2 };

      Console.WriteLine("\nA = ");
      MatShow(A, 1, 5);

      double[][] At = MatTranspose(A);
      Console.WriteLine("\nAt = ");
      MatShow(At, 1, 5);

      double[][] AtA = MatProduct(At, A);
      Console.WriteLine("\nAt * A = ");
      MatShow(AtA, 1, 6);

      double[] v = new double[] { 2.0, 4.0, 6.0, 8.0 };
      Console.WriteLine("\nv = ");
      VecShow(v, 1, 6);

      double[] Av = MatVecProd(A, v);
      Console.WriteLine("\nA * v = ");
      VecShow(Av, 1, 7);

      Console.WriteLine("\nEnd demo ");
      Console.ReadLine();
    } // Main

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

    static double[][] MatMake(int nRows, int nCols)
    {
      double[][] result = new double[nRows][];
      for (int i = 0; i "lt" nRows; ++i)
        result[i] = new double[nCols];
      return result;
    }

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

    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 void MatShow(double[][] A, int dec, int wid)
    {
      int nRows = A.Length; int nCols = A[0].Length;
      double small = 1.0 / Math.Pow(10, dec);
      for (int i = 0; i "lt" nRows; ++i)
      {
        for (int j = 0; j "lt" nCols; ++j)
        {
          double v = A[i][j];
          if (Math.Abs(v) "lt" small) v = 0.0;
          Console.Write(v.ToString("F" + dec).
            PadLeft(wid));
        }
        Console.WriteLine("");
      }
    }

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

    static void VecShow(double[] v, int dec, int wid)
    {
      int n = v.Length;
      double small = 1.0 / Math.Pow(10, dec);
      for (int i = 0; i "lt" n; ++i)
      {
        double x = v[i];
        if (Math.Abs(x) "lt" small) x = 0.0;
          Console.Write(x.ToString("F" + dec).
            PadLeft(wid));
      }
      Console.WriteLine("");
    }

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

    static double[][] MatCopy(double[][] A)
    {
      int nRows = A.Length; int nCols = A[0].Length;
      double[][] result = MatMake(nRows, nCols);
      for (int i = 0; i "lt" nRows; ++i)
        for (int j = 0; j "lt" nCols; ++j)
          result[i][j] = A[i][j];
      return result;
    }

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

    static double[][] MatIdentity(int n)
    {
      double[][] result = MatMake(n, n);
      for (int i = 0; i "lt" n; ++i)
        result[i][i] = 1.0;
      return result;
    }

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

    static double[][] MatTranspose(double[][] A)
    {
      int nRows = A.Length; int nCols = A[0].Length;
      double[][] result = MatMake(nCols, nRows);  // note
      for (int i = 0; i "lt" nRows; ++i)
        for (int j = 0; j "lt" nCols; ++j)
          result[j][i] = A[i][j];
      return result;
    }

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

    static double[][] MatProduct(double[][] A, double[][] B)
    {
      int aRows = A.Length;
      int aCols = A[0].Length;
      int bRows = B.Length;
      int bCols = B[0].Length;
      if (aCols != bRows)
        throw new Exception("Non-conformable matrices");

      double[][] result = MatMake(aRows, bCols);
      for (int i = 0; i "lt" aRows; ++i) // each row of A
        for (int j = 0; j "lt" bCols; ++j) // each col of B
          for (int k = 0; k "lt" aCols; ++k) // could use bRows
            result[i][j] += A[i][k] * B[k][j];

      return result;
    }

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

    static double[][] MatSum(double[][] A, double[][] B)
    {
      int nRows = A.Length; int nCols = A[0].Length;
      double[][] result = MatMake(nRows, nCols);
      for (int i = 0; i "lt" nRows; ++i)
        for (int j = 0; j "lt" nCols; ++j)
          result[i][j] = A[i][j] + B[i][j];
      return result;

    }
    // --------------------------------------------------------

    static double[] MatVecProd(double[][] A, double[] v)
    {
      // return a regular vector
      int nRows = A.Length; int nCols = A[0].Length;
      int n = v.Length;
      if (nCols != n)
        throw new Exception("non-comform in MatVecProd");

      double[] result = new double[nRows];
      for (int i = 0; i "lt" nRows; ++i)
        for (int k = 0; k "lt" nCols; ++k)
          result[i] += A[i][k] * v[k];

      return result;
    }

    static double[] VecMatProd(double[] v, double[][] A)
    {
      // return a regular vector
      int nRows = A.Length; int nCols = A[0].Length;
      int n = v.Length;

      if (n != nRows)
        throw new Exception("non-comform in VecMatProd");

      double[] result = new double[nCols];
      for (int j = 0; j "lt" nCols; ++j)
        for (int k = 0; k "lt" n; ++k)
          result[j] += v[k] * A[k][j];

      return result;
    }

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

    static double[][] VecToMat(double[] vec,
      int nRows, int nCols)
    {
      double[][] result = MatMake(nRows, nCols);
      int k = 0;
      for (int i = 0; i "lt" nRows; ++i)
        for (int j = 0; j "lt" nCols; ++j)
          result[i][j] = vec[k++];
      return result;
    }

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

    static double[] MatToVec(double[][] A)
    {
      int nRows = A.Length; int nCols = A[0].Length;
      double[] result = new double[nRows * nCols];
      int k = 0;
      for (int i = 0; i "lt" nRows; ++i)
        for (int j = 0; j "lt" nCols; ++j)
        {
          result[k++] = A[i][j];
        }
      return result;
    }

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

    static double VecNorm(double[] vec)
    {
      int n = vec.Length;
      double sum = 0.0;
      for (int i = 0; i "lt" n; ++i)
        sum += vec[i] * vec[i];
      return Math.Sqrt(sum);
    }

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

    static double VecDot(double[] v1, double[] v2)
    {
      double result = 0.0;
      int n = v1.Length;
      for (int i = 0; i "lt" n; ++i)
        result += v1[i] * v2[i];
      return result;
    }

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

    static double VecEucDist(double[] v1, double[] v2)
    {
      // Eucliden distance v1, v2
      double sum = 0.0;
      int n = v1.Length;
      for (int i = 0; i "lt" n; ++i)
        sum += (v1[i] - v2[i]) * (v1[i] - v2[i]);
      return Math.Sqrt(sum);

    }

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

    static void Shuffle(int[] indices, Random rnd)
    {
      // Fisher-Yates. last iteration not necessary
      for (int i = 0; i "lt" indices.Length; ++i)
      {
        int ri = rnd.Next(i, indices.Length);
        int tmp = indices[i];
        indices[i] = indices[ri];
        indices[ri] = tmp;
      }
    } // Shuffle

  } // class Program

} // ns
This entry was posted in Machine Learning. Bookmark the permalink.

Leave a Reply