├── .gitignore ├── README.md ├── config └── config.exs ├── lib ├── bagger.ex └── bagger │ ├── grocery_lists │ └── whole_foods.csv │ ├── supervisors │ └── layer_one_supervisor.ex │ ├── template │ ├── bagger_report.eex │ └── paper-bag.jpg │ └── workers │ ├── activations.ex │ ├── neuron.ex │ └── output.ex ├── mix.exs ├── mix.lock └── test ├── bagger_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | .DS_Store 19 | bagger_report.html 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bagger 2 | 3 | ## This Project is for Educational Purposes Only 4 | 5 | It is designed to illustrate the 6 | Single Layer Perceptron in Elixir. It accompanies the Post on 7 | [Automating The Future](http://www.automatingthefuture.com) 8 | called `Training Elixir Processes To Learn Like Neurons`. This repo can be downloaded 9 | and copied as much as you like. The main thing here is to illustrate to the readers 10 | how this type of Neural Network works in a down to earth easy to grasp example. 11 | 12 | ### The Story of The Bagger 13 | 14 | The `Bagger` is an automated system that looks at any grocery list and classifies 15 | the list's items into two distinct categories. The categories are hot items and cold items. 16 | The `Bagger's` job is to put these items in the appropriate bag on its own. 17 | 18 | #### Getting Started 19 | 20 | Getting started is easy the whole point of this project is to give an `Elixir` based 21 | example of the Single Layer Perceptron and how it can be used to Classify items on its own. To 22 | get started just run the following command. 23 | 24 | ```Elixir 25 | #after cloning 26 | mix deps.get 27 | #start console 28 | iex -S mix 29 | ``` 30 | The entry point into the project is... 31 | 32 | ```Elixir 33 | Bagger.bag() 34 | # SEE how bagger automatically packs the groceries in the correct bag 35 | ``` 36 | 37 | For a greater understanding of whats happening here read the post [here](http://www.automatingthefuture.com/blog/2016/11/30/training-elixir-processes-to-learn-like-neurons) 38 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :bagger, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:bagger, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/bagger.ex: -------------------------------------------------------------------------------- 1 | defmodule Bagger do 2 | use Application 3 | 4 | @grocery_list "lib/bagger/grocery_lists/whole_foods.csv" 5 | 6 | def start(_type, _args) do 7 | import Supervisor.Spec, warn: false 8 | 9 | children = [ 10 | supervisor(Bagger.Supervisors.LayerOne, []), 11 | worker(Bagger.Workers.Output, []) 12 | ] 13 | 14 | opts = [strategy: :one_for_one, name: Bagger.Supervisor] 15 | Supervisor.start_link(children, opts) 16 | end 17 | 18 | def bag(list \\ @grocery_list) do 19 | CSVLixir.read(list) 20 | |> Enum.to_list 21 | |> tl 22 | |> parse 23 | |> Enum.map(&Bagger.Workers.Neuron.add_inputs(&1)) 24 | 25 | Bagger.Workers.Output.show() 26 | end 27 | 28 | defp parse(data) when is_list(data) do 29 | Stream.map(data, fn(contents) -> 30 | item = hd(contents) 31 | inputs = tl(contents) 32 | [item, Enum.map(inputs, &String.to_integer/1)] 33 | end) 34 | |> Enum.to_list 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/bagger/grocery_lists/whole_foods.csv: -------------------------------------------------------------------------------- 1 | GroceryName,Temperature,Location,Target 2 | Ice Cream,1,6,0 3 | Popsicles,2,0,0 4 | Bologna,0,3,0 5 | Campbell's Soup,2,1,1 6 | Frozen Pizza,3,5,0 7 | Spicy Wings,2,0,1 8 | Fresh Baked Lasagna,1,1,1 9 | Warm Apple Pie,1,5,1 10 | Pre-made Slow Cooked Pot-Roast,2,2,1 11 | Frozen Chicken,-2,-2,0 12 | Cooked Oatmeal,-4,-3,1 13 | Cod,5,3,0 14 | Tuna,2,-5,0 15 | Basil & Asparagus Soup,2,-1,1 16 | Cooked Kidney Beans,3,-2,1 17 | Eggplant Pasta,-5,-2,1 18 | Carrot Soup,-1,1,1 19 | Cheese,2,5,0 20 | Eggs,1,-5,0 21 | Grapes,-3,1,0 22 | Premade Warm Mashed potatoes & Gravy,-2,1,1 23 | Cooked Rosmary Chicken,2,-5,1 24 | Pumpkin Pie,-2,-3,1 25 | Leafy Romaine Lettuce,-5,3,0 26 | -------------------------------------------------------------------------------- /lib/bagger/supervisors/layer_one_supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule Bagger.Supervisors.LayerOne do 2 | @moduledoc """ 3 | The layer one Supervisor is responsible for all the neurons in the first layer. 4 | As a result when the `Bagger` application starts the supervisor will create 5 | the new Neuron and link itself to the child. 6 | """ 7 | use Supervisor 8 | 9 | @doc """ 10 | Starts the first layer which is a `Supervisor` process. By default it 11 | creates one Neuron. Within the Layer. 12 | """ 13 | def start_link do 14 | Supervisor.start_link(__MODULE__, [], [name: __MODULE__]) 15 | end 16 | 17 | def init([]) do 18 | children = [ 19 | worker(Bagger.Workers.Neuron, [], [function: :new]) 20 | ] 21 | supervise(children, [strategy: :one_for_one]) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/bagger/template/bagger_report.eex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bagger's Report 6 | 7 | 10 | 22 | 23 | 24 |
25 |
26 | Animated Paperbag 27 |

Bagger Report

28 |
29 |
30 | 31 |
32 |

Bagging For Grocery List has Completed.

33 |  
34 |     35 | 36 |   37 | <%= for item <- hot_items do %> 38 | 39 | 40 | 41 | 42 | <% end%> 43 | <%= for item <- cold_items do %> 44 | 45 | 46 | 47 | 48 | <% end%> 49 | 50 |
STORE ITEMBAG CLASSIFICATION
<%= item %>Hot
<%= item %>Cold
51 |
52 |
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/bagger/template/paper-bag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheQuengineer/bagger/12f88f0ed3cc21b6ee96a694ba0818add956630e/lib/bagger/template/paper-bag.jpg -------------------------------------------------------------------------------- /lib/bagger/workers/activations.ex: -------------------------------------------------------------------------------- 1 | defmodule Bagger.Workers.Activations do 2 | @moduledoc """ 3 | Does all the calculations for the neurons. It will adjust the weights 4 | and bias accordingly until it reaches the target that is set. 5 | """ 6 | 7 | #use GenServer 8 | 9 | 10 | ####### 11 | # API # 12 | ####### 13 | 14 | def calculate(:hard_limit, neuron) when is_map(neuron) do 15 | summation(neuron.inputs, neuron.weights) 16 | |> add_bias(neuron) 17 | |> hard_limit 18 | end 19 | 20 | def adjust(neuron, target, item) do 21 | error = calculate_local_error(neuron.output, target) 22 | adjust(error, neuron, item, target) 23 | end 24 | 25 | ################## 26 | # IMPLEMENTATION # 27 | ################## 28 | 29 | defp add_bias(calc, neuron), do: calc + neuron.bias 30 | defp calculate_local_error(actual, target), do: target - actual 31 | defp hard_limit(calc) when calc < 0, do: 0 32 | defp hard_limit(calc) when calc >= 0, do: 1 33 | 34 | defp summation([], []), do: 0 35 | 36 | defp summation(inputs, weights) do 37 | ExMatrix.multiply([inputs], [weights]) 38 | |> List.flatten 39 | |> Enum.sum 40 | end 41 | 42 | defp adjust(0, neuron, item, _) do 43 | Bagger.Workers.Output.classify(neuron.output, item) 44 | end 45 | 46 | defp adjust(error, neuron, item, target) do 47 | new_weights = 48 | ExMatrix.multiply([[error]],[neuron.weights]) 49 | |> ExMatrix.add([neuron.inputs]) 50 | |> List.flatten 51 | 52 | new_bias = neuron.bias +(error) 53 | Bagger.Workers.Neuron.update(new_weights, new_bias, target, item) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/bagger/workers/neuron.ex: -------------------------------------------------------------------------------- 1 | defmodule Bagger.Workers.Neuron do 2 | @moduledoc """ 3 | All Neural Networks need neurons to get their work done. `Bagger` needs 4 | neurons as well in order to bag items in the grocery list. The neruon 5 | will take inputs and generate random weights for each input. Then it will 6 | train itself toward the target until it converges on the right answer. 7 | """ 8 | 9 | alias Bagger.Workers.Activations 10 | 11 | defstruct [ 12 | pid: nil, 13 | bias: nil, 14 | inputs: nil, 15 | weights: nil, 16 | output: nil 17 | ] 18 | 19 | @doc """ 20 | Creates a new neuron for `Bagger` which is essentially represented as an 21 | Agent. 22 | """ 23 | def new do 24 | Agent.start_link(fn() -> 25 | %Bagger.Workers.Neuron{ 26 | pid: self(), 27 | bias: 1 28 | } 29 | end, [name: __MODULE__]) 30 | end 31 | 32 | @doc """ 33 | Shows the current state of the given Neuron. 34 | """ 35 | def get do 36 | Agent.get(__MODULE__, &(&1)) 37 | end 38 | 39 | @doc """ 40 | Add inputs to neuron so that it can classify the item. 41 | takes a list of 42 | """ 43 | def add_inputs(data) when is_list(data) do 44 | [item, input_data] = data 45 | target = List.last(input_data) 46 | inputs = List.delete_at(input_data, -1) 47 | 48 | :sfmt.seed :os.timestamp 49 | 50 | Agent.update(__MODULE__, fn(map) -> 51 | Map.put(map, :inputs, inputs) 52 | |> Map.put(:weights, 1..length(inputs) 53 | |> Enum.map(fn(_) -> :sfmt.uniform() end)) 54 | end) 55 | 56 | calculate_output() 57 | neuron = get() 58 | Activations.adjust(neuron, target, item) 59 | end 60 | 61 | def update(new_weights, new_bias, target, item) do 62 | Agent.update(__MODULE__, fn(map) -> 63 | Map.put(map, :weights, new_weights) 64 | |> Map.put(:bias, new_bias) 65 | end) 66 | calculate_output() 67 | Activations.adjust(get(), target, item) 68 | end 69 | 70 | @doc """ 71 | Calculates the output of the Neuron using the `hard_limit` transfer function 72 | """ 73 | def calculate_output do 74 | new_output = Activations.calculate(:hard_limit, get()) 75 | Agent.update(__MODULE__, fn(map) -> 76 | Map.put(map, :output, new_output) 77 | end) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/bagger/workers/output.ex: -------------------------------------------------------------------------------- 1 | defmodule Bagger.Workers.Output do 2 | @moduledoc """ 3 | This process is responsible for generating the output for the `Bagger`. 4 | This will show the results of a specific test and training cycle. The output 5 | layer is essentialy the communication layer to the outside world of the network. 6 | All the code associated to how `Bagger` generates its output can be listed 7 | here. 8 | 9 | The `Bagger` has two bags for the groceries 10 | - Hot Bag 11 | - Cold Bag 12 | 13 | This it shows the contents of the bags to the user 14 | """ 15 | 16 | use GenServer 17 | 18 | ######### 19 | # API # 20 | ######### 21 | 22 | @doc """ 23 | Starts the Output Process. 24 | """ 25 | def start_link do 26 | GenServer.start_link(__MODULE__, [hot_bag: [], cold_bag: []], [name: __MODULE__]) 27 | end 28 | 29 | @doc """ 30 | Classifies a hot item and adds it to the hot bag 31 | """ 32 | def classify(1, item) do 33 | GenServer.cast(__MODULE__, {:hot, item}) 34 | end 35 | 36 | @doc """ 37 | Classifies a cold item and adds it to the cold bag 38 | """ 39 | def classify(0, item) do 40 | GenServer.cast(__MODULE__, {:cold, item}) 41 | end 42 | 43 | @doc """ 44 | Shows the contents of the bag to the user 45 | """ 46 | def show do 47 | GenServer.call(__MODULE__, :show) 48 | end 49 | 50 | ################## 51 | # IMPLEMENTATION # 52 | ################## 53 | 54 | def init(classifiers) do 55 | {:ok, classifiers} 56 | end 57 | 58 | def handle_call(:show, _from, state) do 59 | make_report(state) 60 | System.cmd("open", ["bagger_report.html"]) 61 | {:reply, "Bagging Completed.", state} 62 | end 63 | 64 | def handle_cast({:hot, item}, state) do 65 | result = Keyword.update!(state, :hot_bag, fn(items) -> [item | items] end) 66 | {:noreply, result} 67 | end 68 | 69 | def handle_cast({:cold, item}, state) do 70 | result = Keyword.update!(state, :cold_bag, fn(items) -> [item | items] end) 71 | {:noreply, result} 72 | end 73 | 74 | defp make_report(state) do 75 | contents = EEx.eval_file("lib/bagger/template/bagger_report.eex", 76 | [ 77 | hot_items: Keyword.fetch!(state, :hot_bag), 78 | cold_items: Keyword.fetch!(state, :cold_bag) 79 | ]) 80 | File.write!("bagger_report.html", contents, [:write]) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Bagger.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :bagger, 6 | version: "0.1.0", 7 | elixir: "~> 1.3", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | def application do 14 | [applications: [:logger, :exmatrix, :csvlixir, :sfmt], 15 | mod: {Bagger, []}] 16 | end 17 | 18 | defp deps do 19 | [ 20 | {:csvlixir, "~> 2.0"}, 21 | {:exmatrix, "~> 0.0.1"}, 22 | {:sfmt, git: "https://github.com/jj1bdx/sfmt-erlang.git"} 23 | ] 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"benchfella": {:hex, :benchfella, "0.2.1", "5c8f427a11c09b7d33d6039cdb5eb942e12ab8d4151829fe1c3df6f6395ad813", [:mix], []}, 2 | "csvlixir": {:hex, :csvlixir, "2.0.3", "297f4217726ef7f0645bd9b3883cdbd6193ac8fe964aeea8eeb4b3406acc46ac", [:mix], []}, 3 | "exmatrix": {:hex, :exmatrix, "0.0.1", "bd91870e87597c890af938e14db6292fc9c0c14870b03b966e7d5e8059c84229", [:mix], [{:benchfella, "~> 0.2.0", [hex: :benchfella, optional: false]}]}, 4 | "exprintf": {:hex, :exprintf, "0.2.0", "6c97364c75ddb848d0d6142ea3d882567369fc60f0b88a009f41470cab068a56", [:mix], []}, 5 | "matrix": {:hex, :matrix, "0.3.2", "9c826bc3a1117bf5e1c5cdcf3a3d95456c93bc2e127a04e363e9fc90b724f784", [:mix], [{:exprintf, "~> 0.1", [hex: :exprintf, optional: false]}]}, 6 | "sfmt": {:git, "https://github.com/jj1bdx/sfmt-erlang.git", "36e86c3ce52d3692aaead104ad8bbd5ee3f685e6", []}} 7 | -------------------------------------------------------------------------------- /test/bagger_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BaggerTest do 2 | use ExUnit.Case 3 | doctest Bagger 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------