| |
|
|
| <!-- livebook:{"app":"embedded"} --> |
|
|
| ```elixir |
| Mix.install([ |
| {:nx, "~> 0.11"}, |
| {:axon, "~> 0.8"}, |
| {:exla, "~> 0.11"}, |
| {:kino, "~> 0.13"} |
| ]) |
| ``` |
|
|
| |
|
|
| |
|
|
| **I'm an intermediate Elixir developer and I want to learn Machine Learning in Elixir so I can build intelligent applications and understand ML concepts using functional programming patterns.** |
|
|
| |
|
|
| Elixir brings some unique advantages to machine learning: |
|
|
| - **ποΈ Functional Programming Foundation**: Pure functions and immutability naturally align with ML workflows |
| - **β‘ Concurrency & Distribution**: Handle large datasets and parallel training efficiently |
| - **π§ Erlang VM Benefits**: Fault tolerance and hot code reloading for production ML systems |
| - **π Nx Library**: Numerical computing with GPU acceleration |
|
|
| |
|
|
| **1. Tensors** π’ |
| Tensors are the fundamental building blocks - think of them as multi-dimensional arrays: |
|
|
| ```elixir |
| import Nx |
|
|
| |
| tensor = Nx.tensor([[1, 2, 3], [4, 5, 6]]) |
| tensor |
| ``` |
|
|
| **2. Numerical Operations** β |
| Perform mathematical operations efficiently: |
|
|
| ```elixir |
| |
| a = Nx.tensor([1, 2, 3]) |
| b = Nx.tensor([4, 5, 6]) |
| result = Nx.add(a, b) |
| result |
| ``` |
|
|
| |
|
|
| Let's create a simple linear regression example: |
|
|
| ```elixir |
| defmodule SimpleML do |
| import Nx |
| |
| def predict(x, weights) do |
| |
| multiply(x, weights[0]) + weights[1] |
| end |
| |
| def train(x_data, y_data, learning_rate \\ 0.01, epochs \\ 1000) do |
| |
| weights = [Nx.random_normal({}), Nx.random_normal({})] |
| |
| Enum.reduce(1..epochs, weights, fn epoch, [m, b] -> |
| |
| predictions = multiply(x_data, m) + b |
| |
| |
| loss = mean(power(predictions - y_data, 2)) |
| |
| |
| grad_m = mean(2 * (predictions - y_data) * x_data) |
| grad_b = mean(2 * (predictions - y_data)) |
| |
| |
| [m - learning_rate * grad_m, b - learning_rate * grad_b] |
| end) |
| end |
| end |
| ``` |
|
|
| **Try it out:** |
|
|
| ```elixir |
| |
| x_data = Nx.tensor(Enum.map(0..100, &(&1 / 10.0))) |
| noise = Nx.random_normal({101}) |> Nx.multiply(0.1) |
| y_data = Nx.multiply(x_data, 2) |> Nx.add(3) |> Nx.add(noise) |
|
|
| |
| weights = SimpleML.train(x_data, y_data, 0.01, 500) |
|
|
| |
| test_x = Nx.tensor([0.5, 1.0, 1.5, 2.0]) |
| predictions = SimpleML.predict(test_x, weights) |
|
|
| "Trained weights: #{inspect(weights)}, Predictions: #{inspect(predictions)}" |
| ``` |
|
|
| |
|
|
| |
|
|
| Tensors are multi-dimensional arrays that power all ML computations: |
|
|
| ```elixir |
| import Nx |
|
|
| |
| scalar = Nx.tensor(42) |
| vector = Nx.tensor([1, 2, 3]) |
| matrix = Nx.tensor([[1, 2], [3, 4]]) |
|
|
| |
| IO.puts("Vector shape: #{inspect(Nx.shape(vector))}") |
| IO.puts("Vector type: #{inspect(Nx.type(vector))}") |
| IO.puts("Vector size: #{inspect(Nx.size(vector))}") |
| ``` |
|
|
| |
|
|
| **Mathematical Operations:** |
|
|
| ```elixir |
| |
| a = Nx.tensor([1.0, 2.0, 3.0]) |
| b = Nx.tensor([4.0, 5.0, 6.0]) |
|
|
| add_result = Nx.add(a, b) |
| mult_result = Nx.multiply(a, b) |
| pow_result = Nx.power(a, 2) |
|
|
| {add_result, mult_result, pow_result} |
| ``` |
|
|
| **Aggregation Operations:** |
|
|
| ```elixir |
| matrix = Nx.tensor([[1, 2, 3], [4, 5, 6]]) |
|
|
| sum_all = Nx.sum(matrix) |
| sum_rows = Nx.sum(matrix, axes: [1]) |
| mean_all = Nx.mean(matrix) |
|
|
| {sum_all, sum_rows, mean_all} |
| ``` |
|
|
| |
|
|
| Nx automatically broadcasts tensors to compatible shapes: |
|
|
| ```elixir |
| |
| vector = Nx.tensor([1, 2, 3]) |
| scalar = Nx.tensor(10) |
|
|
| |
| result1 = Nx.add(vector, scalar) |
|
|
| |
| matrix = Nx.tensor([[1, 2, 3], [4, 5, 6]]) |
| result2 = Nx.add(matrix, vector) |
|
|
| {result1, result2} |
| ``` |
|
|
| |
|
|
| Create a function that normalizes a dataset: |
|
|
| ```elixir |
| defmodule DataPreprocessing do |
| import Nx |
| |
| def normalize(tensor) do |
| mean = Nx.mean(tensor) |
| std = Nx.standard_deviation(tensor) |
| |
| |
| Nx.divide(Nx.subtract(tensor, mean), std) |
| end |
| |
| def min_max_scale(tensor) do |
| min = Nx.reduce_min(tensor) |
| max = Nx.reduce_max(tensor) |
| |
| |
| Nx.divide(Nx.subtract(tensor, min), Nx.subtract(max, min)) |
| end |
| end |
|
|
| |
| data = Nx.tensor([10, 20, 30, 40, 50]) |
| normalized = DataPreprocessing.normalize(data) |
| scaled = DataPreprocessing.min_max_scale(data) |
|
|
| {data, normalized, scaled} |
| ``` |
|
|
| |
|
|
| |
|
|
| Axon provides a functional API for building neural networks: |
|
|
| ```elixir |
| import Axon |
|
|
| |
| model = Axon.input("input", shape: {nil, 784}) |
| |> Axon.dense(128, activation: :relu) |
| |> Axon.dense(64, activation: :relu) |
| |> Axon.dense(10, activation: :softmax) |
|
|
| IO.puts("Model structure:") |
| IO.inspect(model) |
| ``` |
|
|
| |
|
|
| **MNIST Digit Classification:** |
|
|
| ```elixir |
| defmodule MNISTClassifier do |
| import Axon |
| |
| def build_model() do |
| |
| Axon.input("input", shape: {nil, 784}) |
| |
| |> Axon.dense(128, activation: :relu) |
| |> Axon.dropout(rate: 0.3) |
| |> Axon.dense(64, activation: :relu) |
| |> Axon.dropout(rate: 0.3) |
| |
| |> Axon.dense(10, activation: :softmax) |
| end |
| |
| def train_model(model, train_data, validation_data) do |
| |
| Axon.Loop.trainer(model, :categorical_cross_entropy, :adam) |
| |> Axon.Loop.validate(model, validation_data) |
| |> Axon.Loop.run(train_data, epochs: 10) |
| end |
| end |
|
|
| |
| model = MNISTClassifier.build_model() |
| model |
| ``` |
|
|
| |
|
|
| **Dense (Fully Connected) Layers:** |
|
|
| ```elixir |
| model1 = Axon.input("input", shape: {nil, 100}) |
| |> Axon.dense(50) |
| |> Axon.dense(25) |
| |> Axon.dense(1) |
|
|
| model1 |
| ``` |
|
|
| **Convolutional Layers (for images):** |
|
|
| ```elixir |
| model2 = Axon.input("input", shape: {nil, 28, 28, 1}) |
| |> Axon.conv(32, kernel_size: {3, 3}, activation: :relu) |
| |> Axon.max_pool(kernel_size: {2, 2}) |
| |> Axon.conv(64, kernel_size: {3, 3}, activation: :relu) |
| |> Axon.max_pool(kernel_size: {2, 2}) |
| |> Axon.flatten() |
| |> Axon.dense(128, activation: :relu) |
| |> Axon.dense(10, activation: :softmax) |
|
|
| model2 |
| ``` |
|
|
| |
|
|
| ```elixir |
| model3 = Axon.input("input", shape: {nil, 100}) |
| |> Axon.dense(50, activation: :relu) |
| |> Axon.dense(25, activation: :sigmoid) |
| |> Axon.dense(10, activation: :softmax) |
| |> Axon.dense(1, activation: :tanh) |
|
|
| model3 |
| ``` |
|
|
| |
|
|
| |
|
|
| **End-to-End Fraud Detection System:** |
|
|
| ```elixir |
| defmodule FraudDetection do |
| import Axon |
| |
| def build_pipeline() do |
| |
| |
| |
| model = build_fraud_model() |
| |
| %{ |
| model: model |
| } |
| end |
| |
| defp build_fraud_model() do |
| Axon.input("input", shape: {nil, 20}) |
| |> Axon.dense(64, activation: :relu) |
| |> Axon.dropout(rate: 0.3) |
| |> Axon.dense(32, activation: :relu) |
| |> Axon.dropout(rate: 0.3) |
| |> Axon.dense(1, activation: :sigmoid) |
| end |
| end |
|
|
| |
| pipeline = FraudDetection.build_pipeline() |
| pipeline.model |
| ``` |
|
|
| |
|
|
| ```elixir |
| defmodule ChurnPredictor do |
| import Axon |
| |
| def build_churn_model() do |
| |
| Axon.input("input", shape: {nil, 15}) |
| |> Axon.dense(32, activation: :relu) |
| |> Axon.batch_norm() |
| |> Axon.dense(16, activation: :relu) |
| |> Axon.dropout(rate: 0.2) |
| |> Axon.dense(1, activation: :sigmoid) |
| end |
| |
| def predict_churn_risk(customer_features) do |
| |
| %{ |
| probability: 0.65, |
| risk_level: :medium, |
| recommendation: "Offer loyalty discount" |
| } |
| end |
| end |
|
|
| |
| churn_model = ChurnPredictor.build_churn_model() |
| churn_model |
| ``` |
|
|
| |
|
|
| |
| Create a function that calculates the dot product of two matrices: |
|
|
| ```elixir |
| defmodule TensorExercises do |
| import Nx |
| |
| def dot_product(a, b) do |
| |
| |
| Nx.dot(a, b) |
| end |
| |
| def elementwise_multiply(a, b) do |
| |
| Nx.multiply(a, b) |
| end |
| end |
|
|
| |
| a = Nx.tensor([[1, 2], [3, 4]]) |
| b = Nx.tensor([[5, 6], [7, 8]]) |
|
|
| dot_result = TensorExercises.dot_product(a, b) |
| mult_result = TensorExercises.elementwise_multiply(a, b) |
|
|
| {dot_result, mult_result} |
| ``` |
|
|
| |
| Create a neural network with 3 hidden layers (128, 64, 32 neurons) and ReLU activations: |
|
|
| ```elixir |
| defmodule CustomModel do |
| import Axon |
| |
| def build_custom_model(input_shape \\ {nil, 10}) do |
| |
| Axon.input("input", shape: input_shape) |
| |> Axon.dense(128, activation: :relu) |
| |> Axon.dense(64, activation: :relu) |
| |> Axon.dense(32, activation: :relu) |
| |> Axon.dense(1, activation: :sigmoid) |
| end |
| end |
|
|
| |
| model = CustomModel.build_custom_model() |
| model |
| ``` |
|
|
| |
|
|
| 1. **Experiment** with different tensor operations |
| 2. **Modify** the models with different architectures |
| 3. **Try** training on real datasets |
| 4. **Explore** Livebook's visualization features |
|
|
| Happy learning! πβ¨ |
|
|
| --- |
|
|
| *This notebook was created as a companion to the "Machine Learning in Elixir" tutorial.* |
| *Save this notebook (Ctrl+S) to keep your progress!* |