« Professeur, pourquoi on n'utilise pas Python comme tout le monde ? — Parce que vous êtes en cours de programmation fonctionnelle, pas en cours de "taper pip install et prier". — Mais… TensorFlow… — TensorFlow, c'est pour les gens qui ne savent pas faire une multiplication de matrices à la main. Et vous, vous allez la faire à la main. En OCaml. Avec des listes. Comme des vrais. »

Introduction

Rappel du cours 3 : Monade option (gestion d'erreur), Monade Writer (journalisation), Pattern général retour / >>=.

Ce cours : on implémente un mini-réseau de neurones en OCaml pur — neurone, couche, réseau, gradient, descente.

Tous les programmes présentés ici sont disponibles dans le répertoire du cours.

Neurone artificiel

f(x, w, b) = σ(∑ wi xi + b)

type neurone = { w : float list; b : float }

let sigmoid x =
  1.0 /. (1.0 +. exp (-. x))

let forward n x =
  let sum = List.fold_left (+.) 0.0
    (List.map2 ( *. ) n.w x) in
  sigmoid (sum +. n.b)
Exercice

Calculez la sortie du neurone w = [0.5; -0.3], b = 0.1 pour x = [1.0; 2.0]. Vérifiez à la main.

Propagation avant (forward)

Une couche = une liste de neurones.

type layer = { neurones : neurone list }

let forward_layer l x =
  List.map (fun n -> forward n x) l.neurones

Réseau = liste de couches.

type reseau = { couches : layer list }

let forward_network r x =
  List.fold_left (fun x layer ->
    forward_layer layer x) x r.couches

« Un réseau de neurones, c'est comme un buffet à volonté : vous mangez (forward), vous regrettez (backward), et vous ajustez votre stratégie pour le prochain plat (SGD). »

Fonction de coût (loss)

On mesure l'erreur entre prédiction et cible — Erreur quadratique moyenne (MSE) :

let mse pred target =
  List.map2 (fun p t -> (p -. t) ** 2.0) pred target
  |> List.fold_left (+.) 0.0
  |> (fun s -> s /. float_of_int (List.length pred))

Gradient : dérivée de la sigmoïde

Pour corriger les poids, on suit le gradient.

let sigmoid_deriv sigma =
  sigma *. (1.0 -. sigma)

Pourquoi c'est utile : σ'(x) s'exprime facilement à partir de σ(x).

Exercice

Vérifiez : si σ(x) = 0.8, que vaut σ'(x) ? Si σ(x) = 0.5, que vaut σ'(x) ? Que remarquez-vous ?

Gradient pas à pas

Pour un neurone de sortie :

let gradient_sortie n x p t =
  let delta = (p -. t) *. sigmoid_deriv p in
  let dw = List.map (fun xi ->
    -. alpha *. delta *. xi) x in
  let db = -. alpha *. delta in
  (dw, db)

Rétropropagation (backpropagation)

Pour une couche cachée, δj se propage :

let gradient_cache n inputs deltas_next w_next =
  let sum = List.fold_left2 (fun acc d w ->
    acc +. d *. w) 0.0 deltas_next w_next in
  let out = forward n inputs in
  let delta = sum *. sigmoid_deriv out in
  let dw = List.map (fun i ->
    -. alpha *. delta *. i) inputs in
  let db = -. alpha *. delta in
  (dw, db, delta)

Descente de gradient stochastique (SGD)

Algorithme complet :

  1. Forward : calculer toutes les sorties
  2. Backward : calculer les δ de la sortie vers l'entrée
  3. Mettre à jour tous les poids et biais
  4. Recommencer (epoch)
let rec train r data epochs alpha =
  if epochs <= 0 then r
  else
    let r' = List.fold_left (fun r (x, t) ->
      let activations = forward_all r x in
      let gradients =
        backward_all r x activations t alpha in
      update_weights r gradients
    ) r data in
    train r' data (epochs - 1) alpha

« La descente de gradient, c'est comme descendre une montagne dans le brouillard : vous ne voyez pas le sommet, mais vous sentez la pente. SGD, c'est le faire en courant, les yeux fermés, en espérant ne pas tomber dans une rivière. »

Test : XOR avec un réseau 2 → 2 → 1

let data = [
  ([0.0;0.0], [0.0]);  ([0.0;1.0], [1.0]);
  ([1.0;0.0], [1.0]);  ([1.0;1.0], [0.0]);
]

let reseau_xor = init_reseau [2; 2; 1]
let trained = train reseau_xor data 10000 0.5

Après entraînement :

# forward_network trained [1.0;0.0]  -> ~0.95 (proche de 1)
# forward_network trained [0.0;0.0]  -> ~0.05 (proche de 0)

Conclusion

Félicitations ! Vous avez survécu aux 4 cours. Vous êtes capables d'écrire un réseau de neurones en OCaml pur, sans bibliothèque externe, sans boucle while, sans effet de bord.

« Si vous pouvez faire du machine learning en OCaml pur, vous pouvez tout faire en OCaml pur. Maintenant, allez coder. »

Régression Frontière de décision Loss RNN Autoencoder codes