Elixir Perceptron
In order to learn elixir’s numerical libraries and foundational machine learning topics, I implemented a perceptron in elixir. The source code, which uses Livebook, can be found here.
defmodule Perceptron do
@doc """
Returns an initialized weights vector with `length` elements.
## Examples
iex> init_weights(3)
%Nx.Tensor{...}
"""
def init_weights(length) do
Nx.random_normal({length}, 0.0, 0.0)
end
@doc """
Trains `weights` on a list of input vectors, `data`, with given learning rate `r`.
Returns the updated weight vector.
## Examples
iex> train([{input, d}, {input, d}], r, weights)
%Nx.Tensor{...}
"""
def train(data, r, weights \\ nil) do
# base case
if length(data) == 1 do
train_single(hd(data), r, weights)
else
weights = train_single(hd(data), r, weights)
train(tl(data), r, weights)
end
end
@doc """
Trains `weights` on a single input, with given learning rate `r`.
Returns the updated weight vector.
## Examples
iex> train_single({input, d}, r, weights)
%Nx.Tensor{...}
"""
def train_single({%Nx.Tensor{} = input, d}, r, weights \\ nil) do
weights =
case weights do
nil -> init_weights(Nx.size(input))
_ -> weights
end
y = feed_forward(weights, input)
update_weights(weights, input, r, d, y)
end
@doc """
Tests `weights` ability to correctly classify a single `input` vector.
Returns `true` if the output is equal to the desired output, `d`, `false` otherwise
## Examples
iex> test_single({input, d}, weights)
true
"""
def test_single({%Nx.Tensor{} = input, d}, %Nx.Tensor{} = weights) do
y = feed_forward(weights, input)
d == y
end
@doc """
Classifies an `input` vector based on a `weights` vector.
Returns a binary value (0 or 1) representing the the predicted class.
## Examples
iex> feed_forwared(weights, input)
1
"""
def feed_forward(%Nx.Tensor{} = weights, %Nx.Tensor{} = input) do
if Nx.to_number(Nx.dot(weights, input)) > 0, do: 1, else: 0
end
def normalize(value) do
cond do
value > 0 -> 1
value <= 0 -> 0
end
end
def update_weights(%Nx.Tensor{} = weights, %Nx.Tensor{} = input, r, d, y) do
Nx.tensor(update_weight_vector(weights, input, r, d, y))
end
defp update_weight_vector(%Nx.Tensor{} = weights, %Nx.Tensor{} = input, r, d, y) do
if Nx.size(weights) == 1 do
[update_weight(Nx.to_number(weights[0]), Nx.to_number(input[0]), r, d, y)]
else
[update_weight(Nx.to_number(weights[0]), Nx.to_number(input[0]), r, d, y)] ++
update_weight_vector(weights[1..-1//1], input[1..-1//1], r, d, y)
end
end
defp update_weight(w, x, r, d, y) do
w + r * (d - y) * x
# Nx.add(w, Nx.multiply(r, Nx.multiply((Nx.subtract(d, y)), x)))
end
end