1. Qu'est-ce que l'IA ?

L'intelligence artificielle, c'est quoi au juste ?

Imagine que tu veux apprendre a un enfant a reconnaitre des fruits. Tu lui montres 1000 pommes, 1000 oranges, 1000 bananes. Au debut, il se trompe. Tu corriges. Petit a petit, il apprend les motifs : la pomme est ronde et rouge, l'orange est orange et pele, la banane est longue et jaune.

L'IA fait exactement la meme chose. Sauf qu'au lieu d'un enfant, c'est un programme informatique. Et au lieu de "voir" des fruits, il "voit" des nombres.

APPRENTISSAGE HUMAIN                     APPRENTISSAGE MACHINE
       ┌─────────────┐                           ┌─────────────┐
       │ Regarde     │                           │ Lit donnees │
       │ 1000 pommes │                           │ (images)    │
       └──────┬──────┘                           └──────┬──────┘
              │                                         │
              ▼                                         ▼
       ┌─────────────┐                           ┌─────────────┐
       │ Se trompe   │                           │ Predit mal  │
       │ au debut    │                           │ au debut    │
       └──────┬──────┘                           └──────┬──────┘
              │                                         │
              ▼                                         ▼
       ┌─────────────┐                           ┌─────────────┐
       │ Quelqu'un   │                           │ Algo calcule│
       │ corrige     │                           │ l'erreur    │
       └──────┬──────┘                           └──────┬──────┘
              │                                         │
              ▼                                         ▼
       ┌─────────────┐                           ┌─────────────┐
       │ Apprend les │                           │ Ajuste les  │
       │ motifs      │                           │ poids       │
       └──────┬──────┘                           └──────┬──────┘
              │                                         │
              ▼                                         ▼
       ┌─────────────┐                           ┌─────────────┐
       │ Reconnait   │                           │ Reconnait   │
       │ les fruits  │                           │ les images  │
       └─────────────┘                           └─────────────┘

IA, Machine Learning, Deep Learning : c'est quoi la difference ?

Ces trois termes sont souvent utilises de maniere interchangeable, mais ils sont differents :

Terme Definition Exemple
Intelligence Artificielle Tout programme qui simule une intelligence humaine Chatbot, pathfinding jeu video, recommandation
Machine Learning Sous-ensemble de l'IA qui apprend a partir de donnees Regression lineaire, arbre de decision, SVM
Deep Learning Sous-ensemble du ML avec reseaux de neurones profonds ChatGPT, recon. d'images, traduction auto
┌─────────────────────────────────────────────────────────────┐
│                    INTELLIGENCE ARTIFICIELLE                  │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │                  MACHINE LEARNING                        │  │
│  │  ┌───────────────────────────────────────────────────┐  │  │
│  │  │               DEEP LEARNING                        │  │  │
│  │  │                                                    │  │  │
│  │  │    Reseau de neurones                             │  │  │
│  │  │    CNN, RNN, Transformers                         │  │  │
│  │  │    ChatGPT, GPT-4, Claude...                      │  │  │
│  │  └───────────────────────────────────────────────────┘  │  │
│  │                                                          │  │
│  │   Regression, Decision Trees, Random Forest             │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   Regles explicites, if/then, systemes experts                 │
└─────────────────────────────────────────────────────────────────┘

Ce que l'IA peut faire

  • Reconnaissance d'images — Identifier des objets, des visages, des ecritures
  • Traitement du langage — Traduire, resumer, generer du texte
  • Recommandation — Netflix, Spotify, YouTube te suggerent des contenus
  • Prediction — Prix immo, meteo, risques
  • Classification — Spam vs non-spam, sentiment positif/negatif
  • Generation — Créer des images, du texte, de la musique

Ce que l'IA NE peut PAS faire

  • Comprendre vraiment — L'IA manipule des symboles, elle ne "comprend" pas
  • Avoir une conscience — Pas de sentiments, pas de conscience
  • Raisonner comme un humain — Pas de bon sens, pas d'intuition
  • Apprendre sans donnees — Il faut des masses de donnees
Mythe : "L'IA va devenir consciente et nous dominer."

Realite : L'IA est un outil mathematique. Elle calcule des probabilites et ajuste des nombres. Pas de conscience, pas de volonte propre. Les scenarios "Terminator" sont de la fiction.

Pourquoi ce tutoriel ?

Dans ce tutoriel, on va construire une IA de A a Z. Pas avec des boites noires, mais en comprenant chaque brique :

  1. Le neurone — L'unite de base
  2. Le reseau — Comment les neurones se connectent
  3. L'entrainement — Comment la machine apprend
  4. Les embeddings — Comment representer du texte
  5. Les LLM — Comment fonctionne ChatGPT

Tu vas voir, c'est pas de la magie. C'est des maths. Et les maths, ca se comprend.

2. Neurone biologique vs artificiel

Accessible Avant de coder, comprenons comment ca marche dans le cerveau.

Le neurone biologique

Dans ton cerveau, y'a environ 86 milliards de neurones. Chaque neurone est une cellule qui traite l'information.

                    NEURONE BIOLOGIQUE
                    
           Dendrites (entrees)         Corps cellulaire       Axone (sortie)
               │                           │                      │
    ──────────┼───────────┐               │                      │
              │           │               │                      │
    ──────────┼───────────┤               │                      │
              │           │    ┌──────────┴──────────┐           │
    ──────────┼───────────┼───▶│                     │───────────┼───▶
              │           │    │  Traitement         │           │
              │           │    │  (activation)       │           │
              │           │    └─────────────────────┘           │
              │           │               │                      │
              │           │               ▼                      │
              │           │         Noyau cell.                  │
              │           │                                      │
              │           └──────────────────────────────────────┘
              │                          Synapses (connexions)
              │
              └─── Signaux electriques provenant d'autres neurones

Comment ca marche :

  1. Dendrites : Recoivent des signaux electriques d'autres neurones (entrees)
  2. Corps cellulaire : Combine tous les signaux, decide s'il faut s'activer
  3. Axone : Transmet le signal aux autres neurones (sortie)
  4. Synapses : Points de connexion entre neurones, avec differentes "forces"

Le neurone artificiel

En 1943, McCulloch et Pitts ont propose un modele mathematique du neurone. C'est la base de tout le deep learning moderne.

                    NEURONE ARTIFICIEL
                    
   x1 ────────┬──▶  w1           ┌─────────────────────┐
              │                   │                     │    y
   x2 ────────┼──▶  w2 ──▶  Σ ──▶│  Activation f()    │───▶ (sortie)
              │           │      │                     │
   x3 ────────┼──▶  w3    │      └─────────────────────┘
              │           │
   b  ────────┴───────────┘ (biais)
   
   
   x1, x2, x3 = entrees (nombres)
   w1, w2, w3 = poids (importance de chaque entree)
   b  = biais (seuil d'activation)
   Σ  = somme ponderee
   f  = fonction d'activation
   y  = sortie

La formule mathematique

y = f(x₁·w₁ + x₂·w₂ + x₃·w₃ + b)

En anglais :

  • x sont les entrees (input)
  • w sont les poids (weights) — c'est ce que le reseau apprend
  • b est le biais (bias) — permet de decaler la decision
  • f est la fonction d'activation — decide si le neurone "s'active"
  • y est la sortie (output)

Comparaison cote a cote

Biologique Artificiel Description
Dendrites Entrees x Signal entrant
Force synaptique Poids w Importance de chaque connexion
Seuil d'activation Biais b Niveau necessaire pour s'activer
Corps cellulaire Somme Σ + Activation f Traitement
Axone Sortie y Signal sortant
La grande idee : Les poids w sont ce que le reseau APPREND. Au debut, ils sont aleatoires. Pendant l'entrainement, on les ajuste pour que la sortie soit correcte. C'est ca, le "machine learning" : ajuster des nombres.

3. Le perceptron

Debutant Construisons notre premier neurone.

Le perceptron simple

Un perceptron est un neurone unique qui prend plusieurs entrees et produit une sortie binaire (0 ou 1).

Exemple : classifier des fruits

On veut distinguer pommes et oranges avec deux caracteristiques :

  • x1 : Poids en grammes
  • x2 : Couleur (1 = rouge, 0 = orange)
   Pomme : x1 = 150g, x2 = 1 (rouge)  ──▶  y = 1 (pomme)
   Orange: x1 = 200g, x2 = 0 (orange) ──▶  y = 0 (orange)
   
        x1 (poids) ────▶ w1 = 0.1
                                │
                                ├──▶ Σ ──▶ f(Σ) ──▶ y
                                │
        x2 (couleur) ──▶ w2 = 0.2
                        
        b (biais) ─────▶ -15

Le neurone calcule :

Σ = x₁·w₁ + x₂·w₂ + b = 150×0.1 + 1×0.2 + (-15) = 15 + 0.2 - 15 = 0.2

Si Σ > 0, alors y = 1 (pomme). Sinon y = 0 (orange).

Code en JavaScript

// Perceptron simple en JavaScript

// Entrees : [poids (g), couleur (1=rouge, 0=orange)]
const pomme1  = [150, 1];
const pomme2  = [130, 1];
const orange1 = [200, 0];
const orange2 = [180, 0];

// Poids et biais (au debut, on les choisit nous-memes)
const w1 = 0.1;   // Poids pour le poids du fruit
const w2 = 0.2;   // Poids pour la couleur
const b  = -15;   // Biais

// Fonction d'activation : step function
function activation(sum) {
  return sum > 0 ? 1 : 0;
}

// Prediction d'un perceptron
function predict(x1, x2) {
  const sum = x1 * w1 + x2 * w2 + b;
  return activation(sum);
}

// Testons !
console.log('Pomme 1:', predict(pomme1[0], pomme1[1]));   // 1 (pomme)
console.log('Pomme 2:', predict(pomme2[0], pomme2[1]));   // 1 (pomme)
console.log('Orange 1:', predict(orange1[0], orange1[1])); // 0 (orange)
console.log('Orange 2:', predict(orange2[0], orange2[1])); // 0 (orange)
# Perceptron simple en Python

# Entrees : [poids (g), couleur (1=rouge, 0=orange)]
pomme1  = [150, 1]
pomme2  = [130, 1]
orange1 = [200, 0]
orange2 = [180, 0]

# Poids et biais
w1 = 0.1   # Poids pour le poids du fruit
w2 = 0.2   # Poids pour la couleur
b  = -15   # Biais

# Fonction d'activation : step function
def activation(somme):
    return 1 if somme > 0 else 0

# Prediction d'un perceptron
def predict(x1, x2):
    somme = x1 * w1 + x2 * w2 + b
    return activation(somme)

# Testons !
print('Pomme 1:', predict(*pomme1))   # 1 (pomme)
print('Pomme 2:', predict(*pomme2))   # 1 (pomme)
print('Orange 1:', predict(*orange1)) # 0 (orange)
print('Orange 2:', predict(*orange2)) # 0 (orange)

Les fonctions d'activation

La fonction d'activation decide si le neurone "s'active" ou non. Voici les plus courantes :

Fonction Formule Utilisation
Step 1 si x > 0, sinon 0 Perceptron binaire
Sigmoide f(x) = 1 / (1 + e^(-x)) Probabilites (0 a 1)
Tanh f(x) = (e^x - e^(-x)) / (e^x + e^(-x)) Probabilites (-1 a 1)
ReLU f(x) = max(0, x) Deep learning (le plus utilise)
Implémenter les differentes fonctions d'activation
// JavaScript
function step(x) { return x > 0 ? 1 : 0; }
function sigmoid(x) { return 1 / (1 + Math.exp(-x)); }
function tanh(x) { return Math.tanh(x); }
function relu(x) { return Math.max(0, x); }
# Python
import math

def step(x): return 1 if x > 0 else 0
def sigmoid(x): return 1 / (1 + math.exp(-x))
def tanh_act(x): return math.tanh(x)
def relu(x): return max(0, x)

4. Entrainement d'un perceptron

Debutant+ Comment le perceptron apprend tout seul.

Le probleme

Dans l'exemple precedent, on a choisi les poids nous-memes. Mais en pratique, on veut que la machine les trouve elle-meme.

C'est ca, l'entrainement.

L'algorithme d'apprentissage

  1. Initialisation : On commence avec des poids aleatoires
  2. Prediction : Pour chaque exemple, on predit
  3. Erreur : On compare avec la vraie reponse
  4. Correction : On ajuste les poids pour reduire l'erreur
  5. Repetition : On repete 1000, 10000, 100000 fois...
┌─────────────────────────────────────────────────────────────┐
│                    BOUCLE D'ENTRAINEMENT                     │
│                                                              │
│   ┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐
│   │ Entree  │      │Prediction│      │  Erreur │      │ Ajuster │
│   │  x      │─────▶│   y_    │─────▶│ e = y-y_│─────▶│  poids  │
│   └─────────┘      └─────────┘      └─────────┘      └─────────┘
│                                                            │
│                      Repondre pour chaque exemple          │
│                                                            │
└─────────────────────────────────────────────────────────────┘
                         │
                         ▼
                   Repeter N fois

La fonction d'erreur

On veut mesurer a quel point notre prediction est mauvaise. La formule la plus simple :

Erreur = y - ŷ  ou y est la vraie valeur, ŷ est la prediction

Si la prediction est correcte, erreur = 0.

Si la prediction est fausse, erreur = 1 ou -1.

La regle d'apprentissage

Pour ajuster les poids, on utilise cette formule :

w_nouveau = w_ancien + α × erreur × entree

Ou α (alpha) est le learning rate (taux d'apprentissage), un nombre entre 0 et 1.

Learning rate (α) : Un petit nombre (ex: 0.01, 0.1) qui controle la vitesse d'apprentissage.
  • Trop petit = apprentissage tres lent
  • Trop grand = on risque de "sauter" la solution

Code en JavaScript

// Entrainement d'un perceptron en JavaScript

// Donnees d'entrainement : [poids, couleur, label]
// label: 1 = pomme, 0 = orange
const data = [
  { x: [150, 1], y: 1 },  // pomme
  { x: [130, 1], y: 1 },  // pomme
  { x: [170, 1], y: 1 },  // pomme
  { x: [200, 0], y: 0 },  // orange
  { x: [180, 0], y: 0 },  // orange
  { x: [220, 0], y: 0 },  // orange
];

// Initialisation aleatoire des poids
let w = [Math.random(), Math.random()];
let b = Math.random();
const learningRate = 0.01;

// Fonction d'activation (step)
function activation(sum) {
  return sum > 0 ? 1 : 0;
}

// Prediction
function predict(x) {
  const sum = x[0] * w[0] + x[1] * w[1] + b;
  return activation(sum);
}

// Entrainement sur une epoch
function trainEpoch() {
  let totalError = 0;
  
  for (const example of data) {
    const x = example.x;
    const target = example.y;
    
    // Prediction
    const sum = x[0] * w[0] + x[1] * w[1] + b;
    const prediction = activation(sum);
    
    // Erreur
    const error = target - prediction;
    totalError += Math.abs(error);
    
    // Ajustement des poids
    w[0] += learningRate * error * x[0];
    w[1] += learningRate * error * x[1];
    b    += learningRate * error;
  }
  
  return totalError;
}

// Entrainement sur plusieurs epochs
console.log('Entrainement en cours...\n');
for (let epoch = 0; epoch < 100; epoch++) {
  const error = trainEpoch();
  if (epoch % 10 === 0) {
    console.log(`Epoch ${epoch}: Erreur totale = ${error}`);
  }
}

// Test final
console.log('\nResultats apres entrainement:');
console.log('Pomme 150g rouge:', predict([150, 1]));   // 1
console.log('Orange 200g orange:', predict([200, 0])); // 0
console.log('Poids appris:', w, 'Biais:', b);
# Entrainement d'un perceptron en Python
import random

# Donnees d'entrainement : [poids, couleur, label]
data = [
    {'x': [150, 1], 'y': 1},  # pomme
    {'x': [130, 1], 'y': 1},  # pomme
    {'x': [170, 1], 'y': 1},  # pomme
    {'x': [200, 0], 'y': 0},  # orange
    {'x': [180, 0], 'y': 0},  # orange
    {'x': [220, 0], 'y': 0},  # orange
]

# Initialisation aleatoire des poids
w = [random.random(), random.random()]
b = random.random()
learning_rate = 0.01

# Fonction d'activation (step)
def activation(somme):
    return 1 if somme > 0 else 0

# Prediction
def predict(x):
    somme = x[0] * w[0] + x[1] * w[1] + b
    return activation(somme)

# Entrainement sur une epoch
def train_epoch():
    global w, b
    total_error = 0
    
    for example in data:
        x = example['x']
        target = example['y']
        
        # Prediction
        somme = x[0] * w[0] + x[1] * w[1] + b
        prediction = activation(somme)
        
        # Erreur
        error = target - prediction
        total_error += abs(error)
        
        # Ajustement des poids
        w[0] += learning_rate * error * x[0]
        w[1] += learning_rate * error * x[1]
        b    += learning_rate * error
    
    return total_error

# Entrainement sur plusieurs epochs
print('Entrainement en cours...\n')
for epoch in range(100):
    error = train_epoch()
    if epoch % 10 == 0:
        print(f'Epoch {epoch}: Erreur totale = {error}')

# Test final
print('\nResultats apres entrainement:')
print('Pomme 150g rouge:', predict([150, 1]))   # 1
print('Orange 200g orange:', predict([200, 0])) # 0
print(f'Poids appris: {w}, Biais: {b}')

Ce qui se passe

Pendant l'entrainement, le perceptron calcule l'erreur pour chaque exemple, puis ajuste les poids pour reduire cette erreur. Au fil des epochs (iterations), les poids convergent vers des valeurs qui permettent de bien classifier les exemples.

Epoch 0  : Poids aleatoires  ──▶  Erreur = 6  ──▶  Beaucoup de mauvaises predictions
Epoch 10 : Poids s'ajustent  ──▶  Erreur = 2  ──▶  Moins d'erreurs
Epoch 50 : Poids convergent  ──▶  Erreur = 0  ──▶  Toutes les predictions sont correctes !
Attention : Le perceptron simple ne peut resoudre que des problemes lineairement separables. Si tu essaies de faire du XOR (a XOR b), ca ne marchera pas. Il faut un reseau multi-couches (voir section suivante).

5. Reseau multi-couches (MLP)

Intermediaire Passons d'un neurone a un vrai reseau.

Le probleme du perceptron

Un perceptron simple ne peut tracer qu'une ligne droite pour separer les classes. Pour des problemes plus complexes (comme XOR), il faut plusieurs neurones connectes en couches.

Architecture d'un MLP

                    RESEAU MULTI-COUCHES (MLP)
                    
    COUCHE         COUCHE          COUCHE
    D'ENTREE       CACHEE          DE SORTIE
    
      x1 ───┬────▶  h1 ───┬────▶  y1
            │       │       │
            │    ┌──┴──┐   │
            └───▶│  h2 │───┴───▶  y1
            │    └──┬──┘
            │       │
      x2 ───┴────▶  h3 ───┘
      
      
    x1, x2 = entrees (2 neurones d'entree)
    h1, h2, h3 = neurones caches (3 neurones)
    y1 = sortie (1 neurone de sortie)
    
    Chaque fleche = un poids (w)
    Ce reseau a: 2×3 + 3×1 = 9 poids + biais

Pourquoi plusieurs couches ?

Chaque couche transforme les donnees de maniere non-lineaire. Avec plusieurs couches, on peut approximer n'importe quelle fonction. C'est le theoreme d'approximation universelle.

Exemple : le probleme XOR

XOR (ou exclusif) est un probleme mythique que le perceptron simple ne peut pas resoudre :

x1 x2 XOR
000
011
101
110

Impossible de separer les 0 et les 1 avec une seule ligne ! Il faut un MLP.

Code MLP en JavaScript

// Reseau multi-couches (MLP) pour XOR en JavaScript

// Fonctions d'activation
function sigmoid(x) { return 1 / (1 + Math.exp(-x)); }
function sigmoidDerivative(x) { return x * (1 - x); }

// Donnees XOR
const X = [
  [0, 0],
  [0, 1],
  [1, 0],
  [1, 1]
];
const Y = [[0], [1], [1], [0]];

// Initialisation des poids (aleatoires)
const inputSize = 2;
const hiddenSize = 4;
const outputSize = 1;

// Poids entree -> cachee
const W1 = [];
for (let i = 0; i < inputSize; i++) {
  W1.push([]);
  for (let j = 0; j < hiddenSize; j++) {
    W1[i].push(Math.random() * 2 - 1);
  }
}
const b1 = Array(hiddenSize).fill(0).map(() => Math.random() * 2 - 1);

// Poids cachee -> sortie
const W2 = [];
for (let i = 0; i < hiddenSize; i++) {
  W2.push([]);
  for (let j = 0; j < outputSize; j++) {
    W2[i].push(Math.random() * 2 - 1);
  }
}
const b2 = Array(outputSize).fill(0).map(() => Math.random() * 2 - 1);

// Forward pass
function forward(x) {
  // Couche cachee
  const hidden = [];
  for (let j = 0; j < hiddenSize; j++) {
    let sum = b1[j];
    for (let i = 0; i < inputSize; i++) {
      sum += x[i] * W1[i][j];
    }
    hidden.push(sigmoid(sum));
  }
  
  // Couche de sortie
  const output = [];
  for (let j = 0; j < outputSize; j++) {
    let sum = b2[j];
    for (let i = 0; i < hiddenSize; i++) {
      sum += hidden[i] * W2[i][j];
    }
    output.push(sigmoid(sum));
  }
  
  return { hidden, output };
}

// Entrainement
const learningRate = 0.5;
const epochs = 10000;

console.log('Entrainement du MLP pour XOR...\n');

for (let epoch = 0; epoch < epochs; epoch++) {
  let totalError = 0;
  
  for (let s = 0; s < X.length; s++) {
    const x = X[s];
    const y = Y[s];
    
    // Forward
    const { hidden, output } = forward(x);
    
    // Erreur
    const error = y[0] - output[0];
    totalError += error * error;
    
    // Backward (retropropagation simplifiee)
    const deltaOutput = error * sigmoidDerivative(output[0]);
    
    // Mise a jour des poids cachee -> sortie
    for (let i = 0; i < hiddenSize; i++) {
      W2[i][0] += learningRate * deltaOutput * hidden[i];
    }
    b2[0] += learningRate * deltaOutput;
    
    // Erreur cachee
    for (let i = 0; i < hiddenSize; i++) {
      const deltaHidden = deltaOutput * W2[i][0] * sigmoidDerivative(hidden[i]);
      for (let j = 0; j < inputSize; j++) {
        W1[j][i] += learningRate * deltaHidden * x[j];
      }
      b1[i] += learningRate * deltaHidden;
    }
  }
  
  if (epoch % 1000 === 0) {
    console.log(`Epoch ${epoch}: Erreur = ${totalError.toFixed(6)}`);
  }
}

// Test final
console.log('\nResultats apres entrainement:');
for (let s = 0; s < X.length; s++) {
  const { output } = forward(X[s]);
  console.log(`XOR(${X[s][0]}, ${X[s][1]}) = ${output[0].toFixed(4)} (attendu: ${Y[s][0]})`);
}
# Reseau multi-couches (MLP) pour XOR en Python
import random
import math

# Fonctions d'activation
def sigmoid(x):
    return 1 / (1 + math.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)

# Donnees XOR
X = [[0, 0], [0, 1], [1, 0], [1, 1]]
Y = [[0], [1], [1], [0]]

# Dimensions
input_size = 2
hidden_size = 4
output_size = 1

# Initialisation des poids
W1 = [[random.uniform(-1, 1) for _ in range(hidden_size)] for _ in range(input_size)]
b1 = [random.uniform(-1, 1) for _ in range(hidden_size)]

W2 = [[random.uniform(-1, 1) for _ in range(output_size)] for _ in range(hidden_size)]
b2 = [random.uniform(-1, 1) for _ in range(output_size)]

# Forward pass
def forward(x):
    # Couche cachee
    hidden = []
    for j in range(hidden_size):
        s = b1[j]
        for i in range(input_size):
            s += x[i] * W1[i][j]
        hidden.append(sigmoid(s))
    
    # Couche de sortie
    output = []
    for j in range(output_size):
        s = b2[j]
        for i in range(hidden_size):
            s += hidden[i] * W2[i][j]
        output.append(sigmoid(s))
    
    return hidden, output

# Entrainement
learning_rate = 0.5
epochs = 10000

print('Entrainement du MLP pour XOR...\n')

for epoch in range(epochs):
    total_error = 0
    
    for s in range(len(X)):
        x = X[s]
        y = Y[s]
        
        # Forward
        hidden, output = forward(x)
        
        # Erreur
        error = y[0] - output[0]
        total_error += error * error
        
        # Backward
        delta_output = error * sigmoid_derivative(output[0])
        
        # Mise a jour W2, b2
        for i in range(hidden_size):
            W2[i][0] += learning_rate * delta_output * hidden[i]
        b2[0] += learning_rate * delta_output
        
        # Erreur cachee
        for i in range(hidden_size):
            delta_hidden = delta_output * W2[i][0] * sigmoid_derivative(hidden[i])
            for j in range(input_size):
                W1[j][i] += learning_rate * delta_hidden * x[j]
            b1[i] += learning_rate * delta_hidden
    
    if epoch % 1000 == 0:
        print(f'Epoch {epoch}: Erreur = {total_error:.6f}')

# Test final
print('\nResultats apres entrainement:')
for s in range(len(X)):
    _, output = forward(X[s])
    print(f'XOR({X[s][0]}, {X[s][1]}) = {output[0]:.4f} (attendu: {Y[s][0]})')

Apres 10000 epochs, le reseau devrait resoudre XOR correctement. C'est la base du deep learning : empiler des couches, entrainer avec la backpropagation, et le reseau apprend des representations complexes.

6. Backpropagation (detail avec maths)

Avance L'algorithme fondamental du deep learning, avec les maths detaillees.

Le probleme

On a un reseau avec plusieurs couches. Chaque couche a des poids. Comment ajuster les poids des couches cachees ?

C'est la question qu'a resolu la backpropagation (retropropagation du gradient).

L'intuition

Imagine que tu veux minimiser l'erreur a la sortie. L'erreur depend des poids de la derniere couche, qui depend des poids de la couche precedente, etc.

Utilisant la regle de la chaine (chain rule) du calcul differentiel, on peut propager l'erreur vers l'arriere et calculer le gradient pour chaque poids.

La regle de la chaine

Si y = f(g(x)), alors :

dy/dx = dy/dg × dg/dx

On peut decomposer une fonction complexe en fonctions simples et multiplier les derivees.

Notation

Pour un reseau simple (entree → cachee → sortie), on a :

  • x : entree
  • W1, b1 : poids et biais de la couche cachee
  • h : activation cachee : h = σ(W1·x + b1)
  • W2, b2 : poids et biais de la couche de sortie
  • y : sortie : y = σ(W2·h + b2)
  • L : fonction de perte (loss)

Forward pass

z₁ = W₁·x + b₁  │  h = σ(z₁)  │  z₂ = W₂·h + b₂  │  ŷ = σ(z₂)

Fonction de perte

On utilise souvent la MSE (Mean Squared Error) :

L = ½(y - ŷ)²

Backward pass (derivees)

On veut calculer ∂L/∂W₁ et ∂L/∂W₂ pour mettre a jour les poids.

Pour W₂ :

En appliquant la regle de la chaine :

∂L/∂W₂ = ∂L/∂ŷ × ∂ŷ/∂z₂ × ∂z₂/∂W₂

Calculons chaque terme :

  • ∂L/∂ŷ = -(y - ŷ) = ŷ - y
  • ∂ŷ/∂z₂ = σ'(z₂) = ŷ(1 - ŷ)
  • ∂z₂/∂W₂ = h

Donc :

∂L/∂W₂ = (ŷ - y) × ŷ(1 - ŷ) × h = δ₂ × h

Ou δ₂ = (ŷ - y) × ŷ(1 - ŷ) est l'erreur de la couche de sortie.

Pour W₁ :

C'est plus complexe car l'erreur doit traverser la deuxieme couche :

∂L/∂W₁ = ∂L/∂ŷ × ∂ŷ/∂z₂ × ∂z₂/∂h × ∂h/∂z₁ × ∂z₁/∂W₁

Les nouveaux termes :

  • ∂z₂/∂h = W₂
  • ∂h/∂z₁ = σ'(z₁) = h(1 - h)
  • ∂z₁/∂W₁ = x

Donc :

∂L/∂W₁ = δ₂ × W₂ × h(1 - h) × x = δ₁ × x

Ou δ₁ = δ₂ × W₂ × h(1 - h) est l'erreur propagee vers la couche cachee.

Algorithme complet

La backpropagation suit ces etapes :

  1. Forward pass : Calculer z₁, h, z₂, ŷ
  2. Erreur de sortie : δ₂ = (ŷ - y) × σ'(z₂)
  3. Erreur cachee : δ₁ = δ₂ × W₂ × σ'(z₁)
  4. Gradients : ∂L/∂W₂ = δ₂ × h, ∂L/∂W₁ = δ₁ × x
  5. Mise a jour : W = W - α × ∂L/∂W
FORWARD:  x ──▶ z1 ──▶ h ──▶ z2 ──▶ ŷ ──▶ L
           │      │      │      │      │      │
           │      │      │      │      │      │
BACKWARD:  │◀─────│◀─────│◀─────│◀─────│◀─────│
           │      │      │      │      │      │
        ∂L/∂W1   δ1    ∂L/∂W2   δ2    ∂L/∂ŷ   (erreur)

Implementation detaillee

Code complet avec commentaires mathematiques
// Backpropagation detaillee avec commentaires
// Reseau: entree(2) -> cachee(3) -> sortie(1)

const sigmoid = x => 1 / (1 + Math.exp(-x));
const sigmoidDeriv = y => y * (1 - y);

// Donnees
const X = [[0,0], [0,1], [1,0], [1,1]];
const Y = [[0], [1], [1], [0]];

// Dimensions
const n_input = 2, n_hidden = 3, n_output = 1;

// Poids (initialises aleatoirement)
let W1 = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]];  // 2x3
let b1 = [0.1, 0.2, 0.3];                      // 3
let W2 = [[0.7], [0.8], [0.9]];                // 3x1
let b2 = [0.1];                                // 1

// FORWARD PASS pour un exemple
function forward(x) {
  // z1 = W1·x + b1
  const z1 = [];
  for (let j = 0; j < n_hidden; j++) {
    let sum = b1[j];
    for (let i = 0; i < n_input; i++) {
      sum += W1[i][j] * x[i];
    }
    z1.push(sum);
  }
  
  // h = sigmoid(z1)
  const h = z1.map(sigmoid);
  
  // z2 = W2·h + b2
  const z2 = [];
  for (let j = 0; j < n_output; j++) {
    let sum = b2[j];
    for (let i = 0; i < n_hidden; i++) {
      sum += W2[i][j] * h[i];
    }
    z2.push(sum);
  }
  
  // y_pred = sigmoid(z2)
  const y_pred = z2.map(sigmoid);
  
  return { z1, h, z2, y_pred };
}

// BACKWARD PASS avec tout le detail
function backward(x, y_true, forward_result, lr = 0.5) {
  const { z1, h, z2, y_pred } = forward_result;
  
  // 1. Erreur a la sortie: delta2 = (y_pred - y_true) * sigmoid'(z2)
  const delta2 = [];
  for (let j = 0; j < n_output; j++) {
    const error = y_pred[j] - y_true[j];
    delta2.push(error * sigmoidDeriv(y_pred[j])); // delta2 = erreur * derivee
  }
  
  // 2. Gradient pour W2: dL/dW2 = delta2 * h
  const dW2 = [];
  for (let i = 0; i < n_hidden; i++) {
    dW2.push([]);
    for (let j = 0; j < n_output; j++) {
      dW2[i].push(delta2[j] * h[i]); // gradient = erreur * activation precedente
    }
  }
  
  // 3. Gradient pour b2: dL/db2 = delta2
  const db2 = [...delta2];
  
  // 4. Erreur de la couche cachee: delta1 = delta2 * W2 * sigmoid'(h)
  const delta1 = [];
  for (let i = 0; i < n_hidden; i++) {
    let sum = 0;
    for (let j = 0; j < n_output; j++) {
      sum += delta2[j] * W2[i][j]; // propagation de l'erreur
    }
    delta1.push(sum * sigmoidDeriv(h[i])); // fois la derivee
  }
  
  // 5. Gradient pour W1: dL/dW1 = delta1 * x
  const dW1 = [];
  for (let i = 0; i < n_input; i++) {
    dW1.push([]);
    for (let j = 0; j < n_hidden; j++) {
      dW1[i].push(delta1[j] * x[i]); // gradient = erreur * entree
    }
  }
  
  // 6. Gradient pour b1: dL/db1 = delta1
  const db1 = [...delta1];
  
  // 7. Mise a jour des poids: W = W - lr * dW
  for (let i = 0; i < n_hidden; i++) {
    for (let j = 0; j < n_output; j++) {
      W2[i][j] -= lr * dW2[i][j];
    }
  }
  for (let j = 0; j < n_output; j++) {
    b2[j] -= lr * db2[j];
  }
  for (let i = 0; i < n_input; i++) {
    for (let j = 0; j < n_hidden; j++) {
      W1[i][j] -= lr * dW1[i][j];
    }
  }
  for (let j = 0; j < n_hidden; j++) {
    b1[j] -= lr * db1[j];
  }
}

// Entrainement
console.log('Entrainement avec backpropagation detaillee...\n');
for (let epoch = 0; epoch < 10000; epoch++) {
  let totalLoss = 0;
  for (let s = 0; s < X.length; s++) {
    const result = forward(X[s]);
    const loss = 0.5 * Math.pow(Y[s][0] - result.y_pred[0], 2);
    totalLoss += loss;
    backward(X[s], Y[s], result);
  }
  if (epoch % 1000 === 0) console.log(`Epoch ${epoch}: Loss = ${totalLoss.toFixed(6)}`);
}

// Test
console.log('\nPredictions:');
for (let s = 0; s < X.length; s++) {
  const { y_pred } = forward(X[s]);
  console.log(`XOR(${X[s][0]}, ${X[s][1]}) = ${y_pred[0].toFixed(4)}`);
}
Resume : La backpropagation utilise la regle de la chaine pour calculer le gradient de l'erreur par rapport a chaque poids. On propage l'erreur de la sortie vers l'entree, d'ou le nom "retropropagation". Sans cet algorithme, le deep learning n'existerait pas.

7. Frameworks vs from scratch

Pourquoi avoir code from scratch ?

En codant chaque etape nous-memes, tu as compris :

  • Comment fonctionne un neurone
  • Comment les donnees circulent (forward)
  • Comment les erreurs se propagent (backward)
  • Comment les poids se mettent a jour

Maintenant, tu sais ce que fait PyTorch/TensorFlow sous le capot.

Pourquoi utiliser un framework en production ?

From scratch Framework (PyTorch/TensorFlow)
Comprendre Etre productif
Implémenter chaque detail Utiliser des fonctions optimisees
CPU uniquement (en general) GPU acceleration automatique
Derivees a la main Autograd (derivees automatiques)
Pas de parallelisation Parallelisation sur GPU
Numeros de lignes de code Quelques lignes

Comparaison de code

Le meme reseau XOR en PyTorch:

import torch
import torch.nn as nn

# Definition du modele (3 lignes au lieu de 100+)
model = nn.Sequential(
    nn.Linear(2, 4),   # entree -> cachee
    nn.Sigmoid(),
    nn.Linear(4, 1),   # cachee -> sortie
    nn.Sigmoid()
)

# Fonction de perte et optimiseur
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)

# Donnees
X = torch.tensor([[0,0], [0,1], [1,0], [1,1]], dtype=torch.float32)
Y = torch.tensor([[0], [1], [1], [0]], dtype=torch.float32)

# Entrainement (la backprop est automatique !)
for epoch in range(10000):
    optimizer.zero_grad()
    outputs = model(X)
    loss = criterion(outputs, Y)
    loss.backward()  # Backprop automatique
    optimizer.step()

# Test
print(model(X))
Moralite : Apprendre from scratch t'aide a comprendre. Mais pour de vrais projets, utilise PyTorch ou TensorFlow. Ils gerent les GPU, les gradients automatiques, et sont beaucoup plus rapides.

8. Embeddings avec Ollama

Debutant+ Transformer du texte en vecteurs numeriques.

Qu'est-ce qu'un embedding ?

Un embedding est une representation numerique d'un texte (ou d'une image, d'un son...). C'est un vecteur de nombres qui capture le "sens" du texte.

Par exemple, les phrases "Le chat dort" et "Le chat fait une sieste" auront des embeddings similaires parce qu'elles ont le meme sens.

TEXTE                           EMBEDDING
                               
"Le chat dort"       ──▶  [0.12, -0.34, 0.56, 0.23, ...] (768 nombres)
"Le chat fait une"  ──▶  [0.11, -0.32, 0.55, 0.22, ...] (tres similaire)
"sieste"
                               
"La voiture roule"   ──▶  [-0.45, 0.23, 0.12, -0.67, ...] (different)

Pourquoi les embeddings sont utiles ?

  • Recherche semantique : Trouver des documents similaires
  • Classification : Trier des textes automatiquement
  • Clustering : Grouper des textes par theme
  • RAG : Retrieval Augmented Generation pour les LLM

Similarite cosinus

Pour comparer deux embeddings, on utilise la similarite cosinus :

cos(A, B) = (A · B) / (||A|| × ||B||)

Le resultat est entre -1 et 1. Plus c'est proche de 1, plus les vecteurs sont similaires.

Utiliser Ollama pour les embeddings

Ollama fournit une API d'embeddings. Tu peux l'utiliser localement ou avec ton abonnement cloud.

// Embeddings avec Ollama en JavaScript

const OLLAMA_URL = 'http://localhost:11434';  // Ou ton URL Ollama Cloud
const MODEL = 'nomic-embed-text';  // Modele d'embeddings

// Obtenir un embedding
async function getEmbedding(text) {
  const response = await fetch(`${OLLAMA_URL}/api/embeddings`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: MODEL,
      prompt: text
    })
  });
  const data = await response.json();
  return data.embedding;
}

// Similarite cosinus
function cosineSimilarity(a, b) {
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

// Test
async function main() {
  const texts = [
    'Le chat dort sur le canape',
    'Le chat fait une sieste',
    'La voiture roule sur la route',
    'JavaScript est un langage de programmation'
  ];
  
  console.log('Generation des embeddings...\n');
  const embeddings = [];
  for (const text of texts) {
    const emb = await getEmbedding(text);
    embeddings.push(emb);
    console.log(`"${text}" -> [${emb.length} valeurs]`);
  }
  
  console.log('\n Similarites cosinus:\n');
  for (let i = 0; i < texts.length; i++) {
    for (let j = i + 1; j < texts.length; j++) {
      const sim = cosineSimilarity(embeddings[i], embeddings[j]);
      console.log(`"${texts[i]}"`);
      console.log(`  vs "${texts[j]}"`);
      console.log(`  -> Similarite: ${(sim * 100).toFixed(1)}%\n`);
    }
  }
}

main();
# Embeddings avec Ollama en Python
import requests
import math

OLLAMA_URL = 'http://localhost:11434'  # Ou ton URL Ollama Cloud
MODEL = 'nomic-embed-text'  # Modele d'embeddings

# Obtenir un embedding
def get_embedding(text):
    response = requests.post(f'{OLLAMA_URL}/api/embeddings', json={
        'model': MODEL,
        'prompt': text
    })
    return response.json()['embedding']

# Similarite cosinus
def cosine_similarity(a, b):
    dot_product = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x ** 2 for x in a))
    norm_b = math.sqrt(sum(x ** 2 for x in b))
    return dot_product / (norm_a * norm_b)

# Test
texts = [
    'Le chat dort sur le canape',
    'Le chat fait une sieste',
    'La voiture roule sur la route',
    'JavaScript est un langage de programmation'
]

print('Generation des embeddings...\n')
embeddings = []
for text in texts:
    emb = get_embedding(text)
    embeddings.append(emb)
    print(f'"{text}" -> [{len(emb)} valeurs]')

print('\nSimilarites cosinus:\n')
for i in range(len(texts)):
    for j in range(i + 1, len(texts)):
        sim = cosine_similarity(embeddings[i], embeddings[j])
        print(f'"{texts[i]}"')
        print(f'  vs "{texts[j]}"')
        print(f'  -> Similarite: {sim * 100:.1f}%\n')
Les embeddings sont la base du RAG. RAG = Retrieval Augmented Generation. Au lieu de demander directement a un LLM, on cherche d'abord les documents pertinents avec les embeddings, puis on les donne au LLM. C'est comme ca que fonctionne ChatGPT avec tes fichiers.

9. Classification de texte

Intermediaire Utiliser les embeddings pour classifier des textes.

Objectif

Classifier automatiquement des avis clients en "positif" ou "negatif".

Approche

  1. Generer les embeddings des avis d'entrainement
  2. Calculer l'embedding moyen de chaque classe (positif/negatif)
  3. Pour un nouvel avis, comparer son embedding avec les moyennes
  4. La classe la plus similaire gagne

Code

// Classification de texte avec embeddings

const OLLAMA_URL = 'http://localhost:11434';
const MODEL = 'nomic-embed-text';

// Donnees d'entrainement
const trainingData = [
  { text: 'Super produit, je recommande !', label: 'positif' },
  { text: 'Excellent, tres satisfaite', label: 'positif' },
  { text: 'Parfait, livraison rapide', label: 'positif' },
  { text: 'Tres deçu, mauvaise qualité', label: 'negatif' },
  { text: 'Nul, ne fonctionne pas', label: 'negatif' },
  { text: 'Arnaque, a eviter', label: 'negatif' },
];

async function getEmbedding(text) {
  const response = await fetch(`${OLLAMA_URL}/api/embeddings`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: MODEL, prompt: text })
  });
  return (await response.json()).embedding;
}

function cosineSimilarity(a, b) {
  let dotProduct = 0, normA = 0, normB = 0;
  for (let i = 0; i < a.length; i++) {
    dotProduct += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

// Calculer l'embedding moyen d'une classe
function meanEmbedding(embeddings) {
  const n = embeddings[0].length;
  const mean = new Array(n).fill(0);
  for (const emb of embeddings) {
    for (let i = 0; i < n; i++) {
      mean[i] += emb[i];
    }
  }
  return mean.map(x => x / embeddings.length);
}

async function trainClassifier(data) {
  console.log('Entrainement du classificateur...\n');
  
  const positifEmbeddings = [];
  const negatifEmbeddings = [];
  
  for (const item of data) {
    const emb = await getEmbedding(item.text);
    if (item.label === 'positif') {
      positifEmbeddings.push(emb);
    } else {
      negatifEmbeddings.push(emb);
    }
    console.log(`  "${item.text}" -> ${item.label}`);
  }
  
  return {
    positif: meanEmbedding(positifEmbeddings),
    negatif: meanEmbedding(negatifEmbeddings)
  };
}

function predict(text, centroids) {
  return getEmbedding(text).then(emb => {
    const simPos = cosineSimilarity(emb, centroids.positif);
    const simNeg = cosineSimilarity(emb, centroids.negatif);
    return {
      label: simPos > simNeg ? 'positif' : 'negatif',
      confidence: Math.max(simPos, simNeg) / (simPos + simNeg)
    };
  });
}

// Main
async function main() {
  const centroids = await trainClassifier(trainingData);
  
  console.log('\nPredictions:');
  
  const testTexts = [
    'Genial, tres content',
    'Horrible, perte d\'argent',
    'Correct mais sans plus'
  ];
  
  for (const text of testTexts) {
    const result = await predict(text, centroids);
    console.log(`"${text}"`);
    console.log(`  -> ${result.label} (confiance: ${(result.confidence * 100).toFixed(1)}%)\n`);
  }
}

main();
# Classification de texte avec embeddings
import requests
import math

OLLAMA_URL = 'http://localhost:11434'
MODEL = 'nomic-embed-text'

# Donnees d'entrainement
training_data = [
    {'text': 'Super produit, je recommande !', 'label': 'positif'},
    {'text': 'Excellent, tres satisfaite', 'label': 'positif'},
    {'text': 'Parfait, livraison rapide', 'label': 'positif'},
    {'text': 'Tres decu, mauvaise qualite', 'label': 'negatif'},
    {'text': 'Nul, ne fonctionne pas', 'label': 'negatif'},
    {'text': 'Arnaque, a eviter', 'label': 'negatif'},
]

def get_embedding(text):
    response = requests.post(f'{OLLAMA_URL}/api/embeddings', json={
        'model': MODEL,
        'prompt': text
    })
    return response.json()['embedding']

def cosine_similarity(a, b):
    dot_product = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x ** 2 for x in a))
    norm_b = math.sqrt(sum(x ** 2 for x in b))
    return dot_product / (norm_a * norm_b)

def mean_embedding(embeddings):
    n = len(embeddings[0])
    mean = [0] * n
    for emb in embeddings:
        for i in range(n):
            mean[i] += emb[i]
    return [x / len(embeddings) for x in mean]

def train_classifier(data):
    print('Entrainement du classificateur...\n')
    positif_embeddings = []
    negatif_embeddings = []
    
    for item in data:
        emb = get_embedding(item['text'])
        if item['label'] == 'positif':
            positif_embeddings.append(emb)
        else:
            negatif_embeddings.append(emb)
        print(f"  \"{item['text']}\" -> {item['label']}")
    
    return {
        'positif': mean_embedding(positif_embeddings),
        'negatif': mean_embedding(negatif_embeddings)
    }

def predict(text, centroids):
    emb = get_embedding(text)
    sim_pos = cosine_similarity(emb, centroids['positif'])
    sim_neg = cosine_similarity(emb, centroids['negatif'])
    return {
        'label': 'positif' if sim_pos > sim_neg else 'negatif',
        'confidence': max(sim_pos, sim_neg) / (sim_pos + sim_neg)
    }

# Main
centroids = train_classifier(training_data)

print('\nPredictions:')
test_texts = [
    'Genial, tres content',
    'Horrible, perte d\'argent',
    'Correct mais sans plus'
]

for text in test_texts:
    result = predict(text, centroids)
    print(f'"{text}"')
    print(f'  -> {result["label"]} (confiance: {result["confidence"] * 100:.1f}%)\n')

10. Introduction aux LLM

Accessible Comment fonctionne un modele comme GPT ?

Qu'est-ce qu'un LLM ?

LLM = Large Language Model. C'est un modele de langage entraine sur des milliards de textes pour predire le mot suivant.

Les 3 etapes

  1. Tokenisation : Le texte est decoupe en "tokens" (morceaux de mots)
  2. Embeddings : Chaque token devient un vecteur de nombres
  3. Attention : Le modele calcule quel token est important pour quel autre

Tokenisation

Un token est une unite de texte. Ca peut etre un mot, une partie de mot, ou meme un caractere.

TEXTE:  "Bonjour le monde"

TOKENISATION:
  "Bonjour" -> [1234]
  " le"     -> [567]
  " monde"  -> [890]

EMBEDDINGS:
  [1234] -> [0.12, -0.34, ...] (768 nombres)
  [567]  -> [0.45, 0.23, ...]
  [890]  -> [-0.11, 0.67, ...]

Mecanisme d'Attention

L'attention permet au modele de "se souvenir" du contexte. Quand le modele predit un mot, il regarde tous les mots precedents et calcule leur importance.

Par exemple, dans "Le chat mange la souris car il a ..."

  • "il" fait reference a "chat"
  • "mange" donne un contexte d'action
  • "souris" donne un contexte d'objet

Le modele apprend ces relations pendant l'entrainement.

Prediction du mot suivant

Un LLM predit le mot suivant en calculant une probabilite pour chaque mot du vocabulaire :

P(mot | contexte) = softmax(logits)

Le mot avec la plus haute probabilite est choisi (ou on echantillonne parmi les plus probables).

Pourquoi les LLM sont "gros"

Modele Parametres Taille
GPT-2 1.5 milliards ~6 Go
GPT-3 175 milliards ~350 Go
LLaMA-70B 70 milliards ~140 Go
GPT-4 ~1 trillion (estimation) ?

Ces parametres sont les poids du reseau (comme les W1, W2 qu'on a vus), mais en beaucoup plus grand.

Ce qu'on a appris s'applique aux LLM

  • Neurones : Les LLM sont des reseaux de neurones geants
  • Couches : Un LLM a des dizaines de couches
  • Forward pass : Pour chaque token, on fait une forward pass
  • Backpropagation : Pendant l'entrainement, la backprop ajuste les poids
  • Embeddings : Chaque token a un embedding
Moralite : Les LLM sont de simples reseaux de neurones, mais enormes. Les concepts qu'on a appris (neurone, couche, forward, backprop, embeddings) sont les memes. Ce qui change : l'echelle, l'architecture (transformers), et les donnees d'entrainement.

11. Deploiement

Creer une API pour ton modele

Voila un exemple simple d'API Node.js qui sert un modele d'embeddings :

// api-embeddings.js - API simple pour les embeddings
const http = require('http');
const url = require('url');

const OLLAMA_URL = 'http://localhost:11434';
const PORT = 3000;

async function getEmbedding(text) {
  const response = await fetch(`${OLLAMA_URL}/api/embeddings`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'nomic-embed-text',
      prompt: text
    })
  });
  return (await response.json()).embedding;
}

const server = http.createServer(async (req, res) => {
  const parsedUrl = url.parse(req.url, true);
  
  res.setHeader('Content-Type', 'application/json');
  
  if (parsedUrl.pathname === '/embed' && req.method === 'POST') {
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', async () => {
      try {
        const { text } = JSON.parse(body);
        const embedding = await getEmbedding(text);
        res.writeHead(200);
        res.end(JSON.stringify({ success: true, embedding }));
      } catch (error) {
        res.writeHead(400);
        res.end(JSON.stringify({ success: false, error: error.message }));
      }
    });
  } else if (parsedUrl.pathname === '/health') {
    res.writeHead(200);
    res.end(JSON.stringify({ status: 'ok' }));
  } else {
    res.writeHead(404);
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

server.listen(PORT, () => {
  console.log(`API d'embeddings sur http://localhost:${PORT}`);
  console.log('POST /embed { "text": "votre texte" }');
});

Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production || true

COPY . .

EXPOSE 3000

CMD ["node", "api-embeddings.js"]

docker-compose.yml

services:
  api-embeddings:
    build: .
    container_name: api-embeddings
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - OLLAMA_URL=http://host.docker.internal:11434
    extra_hosts:
      - "host.docker.internal:host-gateway"

12. Pour aller plus loin

Cours en ligne

Frameworks

Projets a faire

  • Reconnaissance d'images (MNIST, CIFAR-10)
  • Classification de sentiment (IMDB reviews)
  • Generation de texte (tiny Shakespeare)
  • RAG sur tes propres documents
  • Chatbot simple avec Ollama

Livres

  • "Deep Learning" par Ian Goodfellow
  • "Hands-On Machine Learning" par Aurelion Geron
  • "The Little Book of Deep Learning" (gratuit en PDF)
Bravo ! Tu as appris les bases de l'intelligence artificielle. Tu comprends maintenant comment fonctionne un neurone, comment un reseau apprend, ce que sont les embeddings, et meme comment marchent les LLM comme ChatGPT. C'est pas de la magie, c'est des maths. Et tu sais les coder.
Retour aux tutoriels