Sample usage of Boyle module

Preparing environment


In [1]:
# Create new environment
Boyle.mk("matrex_samples")


All dependencies up to date
Out[1]:
{:ok, ["lala1", "matrex_samples", "test1"]}

In [2]:
# Activate new environment and load modules available in that environment
Boyle.activate("matrex_samples")


All dependencies up to date
Out[2]:
:ok

In [3]:
# Install new dependency
Boyle.install({:matrex, "~> 0.6"})


Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  elixir_make 0.4.2
  matrex 0.6.7
All dependencies up to date
==> matrex
make: Nothing to be done for 'build'.
Out[3]:
:ok

In [4]:
m = Matrex.magic(3)


Out[4]:
#Matrex[3×3]
┌                         ┐
│     8.0     1.0     6.0 │
│     3.0     5.0     7.0 │
│     4.0     9.0     2.0 │
└                         ┘

In [5]:
m[2][3]


Out[5]:
7.0

In [6]:
m[1..2]


Out[6]:
#Matrex[2×3]
┌                         ┐
│     8.0     1.0     6.0 │
│     3.0     5.0     7.0 │
└                         ┘

In [7]:
m[:rows]


Out[7]:
3

In [8]:
m[:size]


Out[8]:
{3, 3}

In [9]:
m[:max]


Out[9]:
9.0

In [10]:
m[2][:max]


Out[10]:
7.0

In [11]:
m[:argmax]


Out[11]:
8

In [12]:
m[2][:argmax]


Out[12]:
3

Math operators overloading and logistic regression implementation


In [13]:
import Matrex

defmodule LinearRegression do
  def lr_cost_fun(%Matrex{} = theta, {%Matrex{} = x, %Matrex{} = y, lambda} = _params, iteration \\ 0)
      when is_number(lambda) do
    m = y[:rows]

    h = Matrex.dot_and_apply(x, theta, :sigmoid)
    l = Matrex.ones(theta[:rows], theta[:cols]) |> Matrex.set(1, 1, 0)

    regularization =
      Matrex.dot_tn(l, Matrex.square(theta))
      |> Matrex.scalar()
      |> Kernel.*(lambda / (2 * m))

    # Compute the cost and add regularization parameter
    j =
      y
      |> Matrex.dot_tn(Matrex.apply(h, :log), -1)
      |> Matrex.subtract(
        Matrex.dot_tn(
          Matrex.subtract(1, y),
          Matrex.apply(Matrex.subtract(1, h), :log)
        )
      )
      |> Matrex.scalar()
      |> (fn
            :nan -> :nan
            x -> x / m + regularization
          end).()

    # Compute gradient
    grad =
      x
      |> Matrex.dot_tn(Matrex.subtract(h, y))
      |> Matrex.add(Matrex.multiply(theta, l), 1.0, lambda)
      |> Matrex.divide(m)

    {j, grad}
  end
  
  # The same cost function, implemented with  operators from `Matrex.Operators` module.
  # Works 2 times slower, than standard implementation. But it's a way more readable.
  # It is here for demonstrating possibilites of the library.
  def lr_cost_fun_ops(%Matrex{} = theta, {%Matrex{} = x, %Matrex{} = y, lambda} = _params)
      when is_number(lambda) do
    # Turn off original operators. Use this with caution!
    import Kernel, except: [-: 1, +: 2, -: 2, *: 2, /: 2, <|>: 2]
    import Matrex
    import Matrex.Operators
    
    # This line is needed only when used from iex, to remove ambiguity of t/1 function.
    import IEx.Helpers, except: [t: 1]

    m = y[:rows]

    h = sigmoid(x * theta)
    l = ones(size(theta)) |> set(1, 1, 0.0)

    j = (-t(y) * log(h) - t(1 - y) * log(1 - h) + lambda / 2 * t(l) * pow2(theta)) / m

    grad = (t(x) * (h - y) + (theta <|> l) * lambda) / m

    {scalar(j), grad}
  end
end


Out[13]:
{:module, LinearRegression, <<70, 79, 82, 49, 0, 0, 15, 240, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 1, 124, 0, 0, 0, 45, 23, 69, 108, 105, 120, 105, 114, 46, 76, 105, 110, 101, 97, 114, 82, 101, 103, 114, 101, 115, 115, 105, 111, ...>>, {:lr_cost_fun_ops, 2}}

In [14]:
System.cmd("git", ["clone", "https://github.com/versilov/matrex", "resources/matrex"])


Out[14]:
{"", 0}

In [15]:
ls "resources/matrex/test/data"


X.mtx.gz                          Xtest.mtx.gz                      Xtrain.mtx.gz                     
Y.mtx                             Ytest.mtx                         Ytest.mtx.gz                      
Ytrain.mtx                        matrex.csv                        nn_theta1.mtx                     
nn_theta2.mtx                     t10k-images-idx3-ubyte.idx.gz     t10k-labels-idx1-ubyte.idx        

In [16]:
x = Matrex.load("resources/matrex/test/data/X.mtx.gz")


Out[16]:
#Matrex[5000×400]
┌                                                                             ┐
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     ⋮       ⋮       ⋮       ⋮       ⋮    …      ⋮       ⋮       ⋮       ⋮   │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
└                                                                             ┘

In [17]:
x[1100..1115]
|> list_of_rows()
|> Enum.map(&(reshape(&1, 20, 20)
|> transpose()))
|> reshape(4, 4)
|> heatmap()


#Matrex[80×80]
┌                                                                                ┐
│         ▄▄▄                                                                    │
│               │
│                                   │
│                                                       │
│                      ▄▄▄▄▄▄                       │
│    ▄▄▄▄▄▄▄                                      │
│  ▄▄ ▄▄ ▄▄                 ▄▄│
│   ▄▄▄                          ▄▄│
│  ▄▄▄                                                        │
│                                                                                │
│                            ▄▄                                                  │
│     ▄▄▄▄▄▄▄▄│
│     ▄▄             │
│                                       │
│                                                      │
│                                            │
│                    │
│                                          │
│                    ▄▄▄                                │
│                                                                     ▄▄        │
│                              │
│                    │
│                                  │
│                                                    │
│                            ▄▄  ▄▄         ▄▄    │
│                          ▄▄▄       │
│                     ▄▄▄▄▄               │
│     ▄▄                  ▄▄│
│                                                             │
│                                                                               │
│                                                  │
│                                      ▄▄│
│      ▄▄▄▄▄▄▄▄▄               │
│                                       │
│     ▄▄                          ▄▄    │
│                                       │
│      ▄▄▄                           │
│              ▄▄▄▄        ▄▄▄      │
│      ▄▄                                ▄▄                        │
│                                                                               │
└                                                                                ┘
Out[17]:
#Matrex[80×80]
┌                                                                             ┐
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0  8.0e-5     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0 -0.0007     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0-0.00269     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0-0.00279     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0-0.00278     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0-0.00278     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0-0.00113     0.0     0.0     0.0 │
│     ⋮       ⋮       ⋮       ⋮       ⋮    …      ⋮       ⋮       ⋮       ⋮   │
│     0.0     0.0     0.0-0.00371  0.0085  9.0e-5     0.0     0.0     0.0 │
│     0.0     0.0     0.0-0.00487 0.02283     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0-0.00278-0.00287     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0-0.00177-0.01581     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0 -1.6e-4-0.02008     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0  3.0e-5-0.00158     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0  1.7e-4     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
│     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0     0.0 │
└                                                                             ┘

In [18]:
y = Matrex.load("resources/matrex/test/data/Y.mtx")


Out[18]:
#Matrex[5000×1]
┌         ┐
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│    10.0 │
│     ⋮   │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
│     9.0 │
└         ┘

In [19]:
theta = Matrex.zeros(x[:cols], 1)


Out[19]:
#Matrex[400×1]
┌         ┐
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     ⋮   │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
│     0.0 │
└         ┘

In [20]:
lambda = 0.01
iterations = 100


Out[20]:
100

In [21]:
solutions =
      1..10  # Our ten digits, we wish to recognize
      |> Task.async_stream(
        fn digit ->
          # Prepare labels matrix with only current digit labeled with 1.0
          y3 = Matrex.apply(y, fn val -> if(val == digit, do: 1.0, else: 0.0) end)

          # Use fmincg() optimizer (ported to Elixir with Matrex functions) with previously defined cost function.
          {sX, fX, _i} =
            Matrex.Algorithms.fmincg(&LinearRegression.lr_cost_fun/3, theta, {x, y3, lambda}, iterations)
        
          # Return the digit itself and the best found solution, which is a column matrix 401x1
          {digit, List.last(fX), sX}
        end,
        max_concurrency: 4
      ) # Merge all 10 found solution column matrices into one 10x401 solutions matrix
      |> Enum.map(fn {:ok, {_d, _l, theta}} -> Matrex.to_list(theta) end)
      |> Matrex.new()


Out[21]:
#Matrex[10×400]
┌                                                                             ┐
│     0.0     0.0  1.2e-4-0.00107-0.00117-0.07428 0.00924     0.0     0.0 │
│     0.0     0.0 -9.0e-5 0.00102 -6.2e-4 0.02237 0.00611 -0.0007     0.0 │
│     0.0     0.0 -5.0e-5-0.00108 0.02117 0.00117 -4.0e-5     0.0     0.0 │
│     0.0     0.0 -1.0e-5  6.0e-5  0.0013-0.00308  2.6e-4  1.0e-5     0.0 │
│     0.0     0.0     0.0  2.3e-4-0.00268 0.01584-0.00147     0.0     0.0 │
│     0.0     0.0     0.0  4.0e-5 -3.1e-4  0.0044 -4.7e-4  1.0e-5     0.0 │
│     0.0     0.0 -3.0e-5  0.0002   0.001-0.00276 -6.6e-4  8.0e-5     0.0 │
│     0.0     0.0 -6.0e-5  5.2e-4  5.9e-4-0.00499 -0.0035  3.7e-4     0.0 │
│     0.0     0.0     0.0  5.0e-5  1.5e-4  2.1e-4-0.00525  4.2e-4     0.0 │
│     0.0     0.0     0.0 -1.0e-5  1.6e-4 0.00163 -3.7e-4  1.0e-5     0.0 │
└                                                                             ┘

In [22]:
predictions =
  x
  |> Matrex.dot_nt(solutions)
  |> Matrex.apply(:sigmoid)


Out[22]:
#Matrex[5000×10]
┌                                                                             ┐
│     0.0  1.8e-4  8.0e-5     0.0  1.4e-4     0.0  1.5e-4 0.00148 0.99992 │
│     0.0  1.0e-5  7.0e-5     0.0 0.00637     0.0     0.0  1.0e-5 0.99995 │
│     0.0  1.1e-4  3.0e-5     0.0  1.9e-4     0.0 0.00972 0.00116  0.9976 │
│     0.0  1.2e-4  1.0e-5     0.0     0.0     0.0  6.0e-5  1.7e-4 0.99986 │
│     0.0     0.0  1.0e-5     0.0 0.00167     0.0  1.0e-5     0.0 0.98872 │
│     0.0  5.0e-5     0.0     0.0  1.0e-5     0.0 0.00696  1.0e-5     1.0 │
│     0.0     0.0 0.01038     0.0     0.0     0.0  1.0e-5  1.0e-5  0.9493 │
│     0.0 0.37895 0.00361     0.0 0.11678     0.0  0.0175     0.0  0.9214 │
│     0.0  3.0e-5  1.1e-4     0.0  3.1e-4     0.0 0.00435  5.0e-5 0.99452 │
│     0.0     0.0 0.23053     0.0 0.00105     0.0  1.0e-5 0.00154 0.99776 │
│     0.0     0.0  0.0005     0.0 0.15892     0.0 0.00191  2.8e-4 0.98208 │
│     ⋮       ⋮       ⋮       ⋮       ⋮    …      ⋮       ⋮       ⋮       ⋮   │
│     0.0  1.0e-5 0.74765     0.0 0.00116  2.0e-5 0.00113 0.00966  1.0e-5 │
│     0.0  6.0e-5  2.9e-4 0.01534  4.4e-4  0.0075 0.02059 0.98074     0.0 │
│     0.0  2.0e-5  1.0e-5  4.5e-4     0.0 0.00804 0.01399 0.80344     0.0 │
│     0.0  1.0e-5     0.0  8.9e-4     0.0 0.00173 0.32175 0.94479     0.0 │
│     0.0  4.0e-5     0.0 0.00114     0.0 0.03529 0.01958 0.89826 0.00213 │
│  1.0e-5  5.0e-5  1.7e-4 0.02086 0.01135 0.00577 0.00132 0.94976  0.0001 │
│     0.0     0.0     0.0     0.0     0.0  8.6e-4 0.04835 0.96422     0.0 │
│ 0.00496 0.00175 0.26107  1.1e-4 0.07533  5.0e-5 0.00839 0.88033     0.0 │
│     0.0     0.0     0.0 0.03696  6.9e-4  1.5e-4 0.00449 0.81857     0.0 │
│     0.0     0.0     0.0  1.0e-5     0.0 0.29215 0.10323 0.29345  0.0049 │
└                                                                             ┘

In [23]:
accuracy =
  1..predictions[:rows]
  |> Enum.reduce(0, fn row, acc ->
    if y[row] == predictions[row][:argmax], do: acc + 1, else: acc
  end)
  |> Kernel./(predictions[:rows])
  |> Kernel.*(100)


Out[23]:
95.5

Enumerable protocol


In [24]:
Enum.member?(m, 2.0)


Out[24]:
true

In [25]:
Enum.count(m)


Out[25]:
9

In [26]:
Enum.sum(m)


Out[26]:
45.0

Saving and loading matrix


In [27]:
Matrex.random(5) |> Matrex.save("rand.mtx")


Out[27]:
:ok

In [28]:
Matrex.load("rand.mtx")


Out[28]:
#Matrex[5×5]
┌                                         ┐
│ 0.87862 0.04598 0.51931  0.1271 0.54683 │
│ 0.92596 0.90734 0.77617 0.67619 0.76672 │
│ 0.52224 0.68494 0.52633 0.46003 0.84459 │
│ 0.96764 0.77753 0.35702 0.55002  0.7374 │
│ 0.21511 0.77983 0.40448 0.74108 0.58493 │
└                                         ┘

In [29]:
Matrex.magic(5) |> Matrex.divide(Matrex.eye(5)) |> Matrex.save("nan.csv")


Out[29]:
:ok

In [30]:
Matrex.load("nan.csv")


Out[30]:
#Matrex[5×5]
┌                                         ┐
│    16.0 │
│     4.0 │
│    12.0 │
│    25.0 │
│     8.0 │
└                                         ┘

NaN and Infinity


In [31]:
m = Matrex.eye(3)


Out[31]:
#Matrex[3×3]
┌                         ┐
│     1.0     0.0     0.0 │
│     0.0     1.0     0.0 │
│     0.0     0.0     1.0 │
└                         ┘

In [32]:
n = Matrex.divide(m, Matrex.zeros(3))


Out[32]:
#Matrex[3×3]
┌                         ┐
│    NaN     NaN  │
│    NaN     NaN  │
│    NaN     NaN  │
└                         ┘

In [33]:
n[1][1]


Out[33]:
:inf

In [34]:
n[1][2]


Out[34]:
:nan