# B5 — Un neurone sous la loupe

## Avant de commencer

Ce carnet facultatif reprend les modèles et le gradient étudiés en AP5. Il ne constitue pas un devoir supplémentaire. Si je le retiens comme bonus pour un cycle, je préciserai en cours les exercices attendus, le dossier de dépôt et l’échéance. Les anciennes dates de remise ne s’appliquent plus.

## Le sujet

Avant de demander à Nx de calculer nos gradients, vérifions-les à la main. Nous cherchons la droite `y = weight * x + bias` qui minimise l'erreur quadratique moyenne sur une liste de couples `{x, y}`.

Écrivez `Neuron.loss(samples, weight, bias)`, `Neuron.gradient(samples, weight, bias)` (un couple `{dw, db}`) et `Neuron.train(samples, weight, bias, rate, steps)`. Chaque étape utilise les deux gradients calculés sur les **mêmes** anciens paramètres. Avec zéro étape, les paramètres restent inchangés. Les échantillons de ce sujet sont non vides ; refusez néanmoins une liste vide avec `ArgumentError`.

Comparez le gradient analytique à `(loss(w + epsilon) - loss(w - epsilon)) / (2 * epsilon)`, puis entraînez le neurone sur `y = 2x + 1`. Expliquez ce qui arrive quand le pas d'apprentissage devient trop grand. Le sujet ne nécessite aucune dépendance ; vous pourrez ensuite refaire les calculs avec Nx.

## À vous

```elixir
defmodule Neuron do
  def loss(samples, weight, bias), do: nil
  def gradient(samples, weight, bias), do: nil
  def train(samples, weight, bias, rate, steps), do: nil
end
```

## Vérifications

```elixir
samples = for x <- -2..2, do: {x, 2 * x + 1}
{dw, db} = Neuron.gradient(samples, 0.3, -0.4)
epsilon = 1.0e-5
numeric_w = (Neuron.loss(samples, 0.3 + epsilon, -0.4) - Neuron.loss(samples, 0.3 - epsilon, -0.4)) / (2 * epsilon)
numeric_b = (Neuron.loss(samples, 0.3, -0.4 + epsilon) - Neuron.loss(samples, 0.3, -0.4 - epsilon)) / (2 * epsilon)
true = abs(dw - numeric_w) < 1.0e-6
true = abs(db - numeric_b) < 1.0e-6
{0.3, -0.4} = Neuron.train(samples, 0.3, -0.4, 0.1, 0)
{w, b} = Neuron.train(samples, 0.0, 0.0, 0.1, 100)
true = abs(w - 2) < 1.0e-6 and abs(b - 1) < 1.0e-6
true = Neuron.loss(samples, w, b) < Neuron.loss(samples, 0.0, 0.0)
IO.inspect({w, b}, label: "Paramètres appris")
```

## Votre explication

Ajoutez une cellule Markdown pour expliquer votre démarche, les cas limites vérifiés et ce que vous avez observé. Pour toute question : aurelien.noce@minesparis.psl.eu, avec aurelien.noce@imagine-app.fr en copie.
