# Machine Learning in Elixir - Interactive Tutorial (Latest Versions) ```elixir Mix.install([ {:nx, "~> 0.11"}, {:axon, "~> 0.8"}, {:exla, "~> 0.11"}, {:kino, "~> 0.13"} ]) ``` ## Chapter 1: Introduction to ML in Elixir 🚀 ### Welcome to Machine Learning with Elixir! **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.** ### What Makes Elixir Special for ML? 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 ### Core Concepts in Elixir ML **1. Tensors** 🔢 Tensors are the fundamental building blocks - think of them as multi-dimensional arrays: ```elixir import Nx # Creating a tensor tensor = Nx.tensor([[1, 2, 3], [4, 5, 6]]) tensor ``` **2. Numerical Operations** ➕ Perform mathematical operations efficiently: ```elixir # Element-wise operations a = Nx.tensor([1, 2, 3]) b = Nx.tensor([4, 5, 6]) result = Nx.add(a, b) result ``` ### Your First ML Program 💻 Let's create a simple linear regression example compatible with Nx 0.11: ```elixir defmodule SimpleML do import Nx def predict(x, weights) do # Simple linear prediction: y = mx + b Nx.multiply(x, weights[0]) + weights[1] end def train(x_data, y_data, learning_rate \\ 0.01, epochs \\ 1000) do # Initialize random weights weights = [ Nx.random_normal({}, 0.0, 1.0), # m weight Nx.random_normal({}, 0.0, 1.0) # b weight ] Enum.reduce(1..epochs, weights, fn _epoch, [m, b] -> # Forward pass predictions = Nx.multiply(x_data, m) + b # Calculate loss (mean squared error) _loss = Nx.mean(Nx.pow(Nx.subtract(predictions, y_data), 2)) # Backward pass (gradients) grad_m = Nx.mean(2 * Nx.multiply(Nx.subtract(predictions, y_data), x_data)) grad_b = Nx.mean(2 * Nx.subtract(predictions, y_data)) # Update weights [ Nx.subtract(m, Nx.multiply(learning_rate, grad_m)), Nx.subtract(b, Nx.multiply(learning_rate, grad_b)) ] end) end end ``` **Try it out:** ```elixir # Generate simple linear data: y = 2x + 3 + noise x_data = Nx.tensor(Enum.map(0..100, &(&1 / 10.0))) # 0 to 10 in steps of 0.1 noise = Nx.random_normal({101}, 0.0, 0.1) y_data = Nx.add(Nx.add(Nx.multiply(x_data, 2), 3), noise) # Train the model weights = SimpleML.train(x_data, y_data, 0.01, 500) # Test prediction 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)}" ``` ## Chapter 2: Nx and Numerical Computing 🔢 ### Understanding Nx Tensors 📊 Tensors are multi-dimensional arrays that power all ML computations: ```elixir import Nx # Different tensor types scalar = Nx.tensor(42) # 0-dimensional vector = Nx.tensor([1, 2, 3]) # 1-dimensional matrix = Nx.tensor([[1, 2], [3, 4]]) # 2-dimensional # Tensor properties IO.puts("Vector shape: #{inspect(Nx.shape(vector))}") IO.puts("Vector type: #{inspect(Nx.type(vector))}") IO.puts("Vector size: #{inspect(Nx.size(vector))}") ``` ### Essential Tensor Operations ⚡ **Mathematical Operations:** ```elixir # Basic arithmetic 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.pow(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} ``` ### Broadcasting Magic ✨ Nx automatically broadcasts tensors to compatible shapes: ```elixir # Broadcasting examples vector = Nx.tensor([1, 2, 3]) scalar = Nx.tensor(10) # Add scalar to each element result1 = Nx.add(vector, scalar) # Broadcasting with different dimensions matrix = Nx.tensor([[1, 2, 3], [4, 5, 6]]) result2 = Nx.add(matrix, vector) {result1, result2} ``` ### Hands-On Exercise 💻 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) # Normalize: (x - mean) / std Nx.divide(Nx.subtract(tensor, mean), std) end def min_max_scale(tensor) do min = Nx.reduce_min(tensor) max = Nx.reduce_max(tensor) # Scale to [0, 1] range Nx.divide(Nx.subtract(tensor, min), Nx.subtract(max, min)) end end # Test with sample data data = Nx.tensor([10, 20, 30, 40, 50]) normalized = DataPreprocessing.normalize(data) scaled = DataPreprocessing.min_max_scale(data) {data, normalized, scaled} ``` ## Chapter 3: Building ML Models with Axon 🧠 ### Understanding Axon Architecture 🏗️ Axon provides a functional API for building neural networks: ```elixir import Axon # Simple neural network 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) ``` ### Building Your First Neural Network 🚀 **MNIST Digit Classification:** ```elixir defmodule MNISTClassifier do import Axon def build_model() do # Input: 28x28 grayscale images (flattened to 784) Axon.input("input", shape: {nil, 784}) # Hidden layers |> Axon.dense(128, activation: :relu) |> Axon.dropout(rate: 0.3) |> Axon.dense(64, activation: :relu) |> Axon.dropout(rate: 0.3) # Output: 10 classes (digits 0-9) |> Axon.dense(10, activation: :softmax) end end # Create a sample model model = MNISTClassifier.build_model() model ``` ### Common Layer Types 🧩 **Dense (Fully Connected) Layers:** ```elixir model1 = Axon.input("input", shape: {nil, 100}) |> Axon.dense(50) # 50 neurons |> Axon.dense(25) # 25 neurons |> Axon.dense(1) # Output neuron model1 ``` **Convolutional Layers (for images):** ```elixir model2 = Axon.input("input", shape: {nil, 28, 28, 1}) # Grayscale images |> 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 ``` ### Activation Functions 🔥 ```elixir model3 = Axon.input("input", shape: {nil, 100}) |> Axon.dense(50, activation: :relu) # ReLU - most common |> Axon.dense(25, activation: :sigmoid) # Sigmoid - for probabilities |> Axon.dense(10, activation: :softmax) # Softmax - for classification |> Axon.dense(1, activation: :tanh) # Tanh - for outputs in [-1, 1] model3 ``` ## Chapter 4: Real-world Applications 🚀 ### Complete ML Pipeline Example 🔄 **End-to-End Fraud Detection System:** ```elixir defmodule FraudDetection do import Axon def build_pipeline() do # Data preprocessing -> Model training -> Prediction # 1. Model definition model = build_fraud_model() %{ model: model } end defp build_fraud_model() do Axon.input("input", shape: {nil, 20}) # 20 features |> 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) # Probability of fraud end end # Create the fraud detection pipeline pipeline = FraudDetection.build_pipeline() pipeline.model ``` ## Interactive Exercises 🎯 ### Exercise 1: Tensor Operations Create a function that calculates the dot product of two matrices: ```elixir defmodule TensorExercises do import Nx def dot_product(a, b) do # Your code here # Hint: Use Nx.dot/2 Nx.dot(a, b) end def elementwise_multiply(a, b) do # Your code here Nx.multiply(a, b) end end # Test your functions 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} ``` ### Exercise 2: Build a Custom Model 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 # Your code here 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 # Build and display the model model = CustomModel.build_custom_model() model ``` ## Version Compatibility Notes 📝 **Nx 0.11 changes:** - `Nx.random_normal/1` → `Nx.random_normal(shape, mean, std)` - `Nx.power/2` → `Nx.pow/2` - Use explicit `Nx.add()`, `Nx.subtract()`, `Nx.multiply()` for clarity **Axon 0.8 changes:** - Improved performance - Better API consistency - Enhanced training loops ## Next Steps 🚀 1. **Update your project dependencies:** ```bash mise exec -- mix deps.update --all mise exec -- mix deps.compile ``` 2. **Experiment** with the latest Nx/Axon features 3. **Try GPU acceleration** with EXLA 4. **Build real projects** with your new ML skills Happy learning with the latest versions! 🎉✨ --- *This notebook uses Nx 0.11 and Axon 0.8 - the latest stable versions.* *Save this notebook (Ctrl+S) to keep your progress!*