Accueil » Uncategorized

Catégorie : Uncategorized

Réseau de neurones artificiel

Présentation

Dans cet article nous allons apprendre au robot MR25 à éviter les obstacles en utilisant un réseau de neurones. Nous allons coder un réseau à 2 couches, une seule couche (le perceptron) suffit quand le problème est linéairement séparable, c’est-à-dire qu’on peut tracer une ligne droite entre les cas « oui » et les cas « non ».

Éviter un obstacle unique devant soi, c’est ce cas. Simple, rapide, mais très limité. Deux couches de neurones permet de résoudre des problèmes avec des frontières courbes ou des zones multiples. C’est exactement notre cas : il faut distinguer 4 situations (libre, gauche, droite, obstacle) qui ne sont pas séparables par une seule ligne dans l’espace des 5 capteurs.

La couche 1 construit des « concepts intermédiaires » (obstacle gauche, obstacle devant, obstacle droite), et la couche 2 combine ces concepts pour décider (action).

Voici le réseau de neurone utiliser avec le robot MR25 :

Le Programme

Ce programme implémente un petit réseau de neurones à deux couches (apprentissage supervisé ) qui apprend à piloter un robot MR25 à partir de ses 5 capteurs de proximité. Il comporte deux phases :

  1. Phase d’apprentissage  : le réseau apprend à associer des situations à des actions.
  2. Phase de pilotage en temps réel  : le robot utilise le réseau entraîné pour décider de ses mouvements.

Les 5 capteurs sont regroupés en 3 zones :

  • Neurone gauche : il mesure l’état de la zone gauche.
  • Neurone devant : il représente l’état de la zone centrale.
  • Neurone droit : il représente l’état de la zone droite.

Le réseau apprend grâce à la rétropropagation du gradient, puis pilote le robot en temps réel en transformant les mesures des capteurs en actions de navigation et d’évitement d’obstacles. La rétropropagation du gradient (backpropagation en anglais) est l’algorithme qui permet à un réseau de neurones d’apprendre de ses erreur, on commence par corriger la couche de sortie, puis on remonte vers la couche 1.

L’idée est simple :

  1. Le réseau fait une prédiction.
  2. On compare cette prédiction à la réponse attendue.
  3. On mesure l’erreur.
  4. On remonte cette erreur de la sortie vers les couches précédentes.
  5. On modifie les poids pour réduire l’erreur lors du prochain essai.

Le gradient indique dans quelle direction modifier les poids pour diminuer l’erreur le plus rapidement. Le gradient indique la pente. Le réseau suit la pente descendante pour atteindre le minimum d’erreur.

 

#!/usr/bin/python3
import MR25
import time
import math

# =============================================================================
# MR25 — Réseau de neurones à deux couches
# =============================================================================
#
# Couche 1 : 3 neurones de perception (5 entrées → 3 sorties continues)
#
# Neurone 1 (gauche) : capteurs 1 et 2
# Neurone 2 (devant) : capteurs 2, 3 et 4
# Neurone 3 (droite) : capteurs 4 et 5
#
# Couche 2 : 4 neurones de décision (3 entrées → 4 sorties)
#
# Sortie 0 : AVANCE
# Sortie 1 : TOURNE GAUCHE
# Sortie 2 : TOURNE DROITE
# Sortie 3 : STOP
#
# =============================================================================

# -----------------------------
# Fonctions d'activation
# -----------------------------

def sigmoid(x):
"""
Fonction sigmoïde — sortie continue dans ]0, 1[.
Utilisée dans la couche 1 pour produire un 'niveau d'obstacle' graduel.
"""
return 1.0 / (1.0 + math.exp(-x))

def softmax(vec):
"""
Softmax — transforme un vecteur en distribution de probabilités.
Utilisée dans la couche 2 : la somme des 4 sorties vaut 1.
L'action choisie est celle dont la probabilité est la plus haute.
"""
e = [math.exp(v) for v in vec]
s = sum(e)
return [v / s for v in e]

# -----------------------------
# Architecture du réseau
# -----------------------------
#
# Couche 1 — poids W1[neurone][capteur] et biais B1[neurone]
#
# Chaque neurone de perception combine les capteurs de son côté.
# Valeurs initiales : chaque capteur pèse pareil, biais nul.
#
# neurone 0 (gauche) → capteurs x1, x2
# neurone 1 (devant) → capteurs x2, x3, x4 (x2 et x4 = flancs proches centre)
# neurone 2 (droite) → capteurs x4, x5

W1 = [
# x1 x2 x3 x4 x5
[0.5, 0.5, 0.0, 0.0, 0.0], # neurone 0 : gauche
[0.0, 0.33, 0.33, 0.33, 0.0], # neurone 1 : devant
[0.0, 0.0, 0.0, 0.5, 0.5], # neurone 2 : droite
]
B1 = [0.0, 0.0, 0.0]

# Couche 2 — poids W2[action][neurone] et biais B2[action]
#
# action 0 (AVANCE) → récompenser l'ABSENCE d'obstacle
# action 1 (TOURNE GAUCHE) → obstacle à droite, pas à gauche
# action 2 (TOURNE DROITE) → obstacle à gauche, pas à droite
# action 3 (STOP) → obstacle des deux côtés

W2 = [
# n_gauche n_devant n_droite
[-0.5, -1.0, -0.5], # AVANCE : mauvais score si obstacle
[ 0.0, -0.5, 1.0], # TOURNE GAUCHE: obstacle à droite
[ 1.0, -0.5, 0.0], # TOURNE DROITE: obstacle à gauche
[ 0.5, 1.0, 0.5], # STOP : obstacles partout
]
B2 = [0.0, 0.0, 0.0, 0.0]

# Taux d'apprentissage
eta = 0.1

# -----------------------------
# Jeu d'entraînement
# -----------------------------
#
# Entrées : [x1, x2, x3, x4, x5] normalisées dans [0, 1]
# 0 = obstacle très proche 1 = voie libre
#
# Cibles : indice de l'action correcte
# 0 = AVANCE 1 = TOURNE GAUCHE 2 = TOURNE DROITE 3 = STOP
#
# Répartition des capteurs sur le MR25 (vue de dessus) :
#
# [1] [2] [3] [4] [5]
# \ | (avant) | /
# gauche droite

training_data = [
# Voie complètement libre → AVANCE
([1.0, 1.0, 1.0, 1.0, 1.0], 0),

# Obstacle devant uniquement → STOP (pas d'info sur quel côté éviter)
([0.9, 0.9, 0.1, 0.9, 0.9], 3),

# Obstacle à gauche (capteurs 1-2 faibles) → TOURNE DROITE
([0.1, 0.2, 0.8, 0.9, 0.9], 2),
([0.2, 0.1, 1.0, 1.0, 1.0], 2),

# Obstacle à droite (capteurs 4-5 faibles) → TOURNE GAUCHE
([0.9, 0.9, 0.8, 0.2, 0.1], 1),
([1.0, 1.0, 1.0, 0.1, 0.2], 1),

# Obstacle sur toute la largeur → STOP
([0.1, 0.1, 0.1, 0.1, 0.1], 3),
([0.2, 0.3, 0.2, 0.3, 0.2], 3),

# Obstacle léger devant-gauche, droite libre → TOURNE DROITE
([0.3, 0.4, 0.4, 0.8, 0.9], 2),

# Obstacle léger devant-droite, gauche libre → TOURNE GAUCHE
([0.9, 0.8, 0.4, 0.4, 0.3], 1),
]

# Noms des actions (pour les affichages)
ACTIONS = ["AVANCE", "TOURNE GAUCHE", "TOURNE DROITE", "STOP"]

# -----------------------------
# Propagation avant (forward pass)
# -----------------------------

def forward(inputs):
"""
Calcule la sortie complète du réseau pour un vecteur d'entrées.

Retourne :
h — sorties de la couche 1 (3 valeurs sigmoïdes)
probs — distribution softmax de la couche 2 (4 probabilités)
action — indice de l'action ayant la probabilité la plus haute
"""
# --- Couche 1 : neurones de perception ---
h = []
for n in range(3):
z = sum(W1[n][i] * inputs[i] for i in range(5)) + B1[n]
h.append(sigmoid(z))

# --- Couche 2 : neurones de décision ---
logits = []
for a in range(4):
z = sum(W2[a][n] * h[n] for n in range(3)) + B2[a]
logits.append(z)

probs = softmax(logits)
action = probs.index(max(probs))
return h, probs, action

# -----------------------------
# Mise à jour des poids (rétropropagation simplifiée)
# -----------------------------
#
# On utilise une règle delta adaptée au softmax + entropie croisée :
# delta_couche2[a] = probs[a] - (1 si a == cible, sinon 0)
#
# Pour la couche 1, on rétropropage le gradient à travers la sigmoïde :
# delta_couche1[n] = sigmoid'(h[n]) * somme_a(W2[a][n] * delta_c2[a])
# avec sigmoid'(h) = h * (1 - h)

def train_step(inputs, target, h, probs):
"""
Effectue une mise à jour des poids W1, B1, W2, B2
à partir d'un exemple (inputs, target).
"""
# --- Gradient couche 2 ---
delta2 = [probs[a] - (1.0 if a == target else 0.0) for a in range(4)]

# --- Mise à jour W2, B2 ---
for a in range(4):
for n in range(3):
W2[a][n] -= eta * delta2[a] * h[n]
B2[a] -= eta * delta2[a]

# --- Gradient couche 1 (rétropropagation) ---
delta1 = []
for n in range(3):
grad = sum(W2[a][n] * delta2[a] for a in range(4))
delta1.append(h[n] * (1.0 - h[n]) * grad)

# --- Mise à jour W1, B1 ---
for n in range(3):
for i in range(5):
W1[n][i] -= eta * delta1[n] * inputs[i]
B1[n] -= eta * delta1[n]

# -----------------------------
# Phase d'apprentissage
# -----------------------------

print("=" * 50)
print(" APPRENTISSAGE DU RÉSEAU DE NEURONES")
print("=" * 50)

for epoch in range(500):
nb_erreurs = 0

for inputs, target in training_data:
h, probs, action = forward(inputs)
if action != target:
nb_erreurs += 1
train_step(inputs, target, h, probs)

# Affichage toutes les 50 époques
if (epoch + 1) % 50 == 0:
print(f"Époque {epoch + 1:4d} — erreurs : {nb_erreurs}/{len(training_data)}")

if nb_erreurs == 0:
print(f"\nConvergence atteinte à l'époque {epoch + 1}")
break

print("\nPoids finaux — couche 1 (perception) :")
for n, nom in enumerate(["Gauche", "Devant", "Droite"]):
print(f" Neurone {nom} : w={[f'{v:.3f}' for v in W1[n]]} b={B1[n]:.3f}")

print("\nPoids finaux — couche 2 (décision) :")
for a, nom in enumerate(ACTIONS):
print(f" {nom:15s} : w={[f'{v:.3f}' for v in W2[a]]} b={B2[a]:.3f}")

# Vérification finale sur le jeu d'entraînement
print("\nVérification sur les données d'entraînement :")
for inputs, target in training_data:
_, probs, action = forward(inputs)
statut = "✓" if action == target else "✗"
print(f" {statut} capteurs={inputs} attendu={ACTIONS[target]:15s} obtenu={ACTIONS[action]}")

print("\nDémarrage du robot dans 5 secondes…")
time.sleep(5)

# -----------------------------
# Pilotage en temps réel
# -----------------------------

SEUIL_MAX = 80.0 # mm — distance au-delà de laquelle la voie est considérée libre

print("\n" + "=" * 50)
print(" PILOTAGE EN COURS (Ctrl+C pour stopper)")
print("=" * 50)

try:
while True:
# --- Lecture et normalisation des 5 capteurs ---
raw = [MR25.proxSensor(i) for i in range(1, 6)]
inputs = [min(v, SEUIL_MAX) / SEUIL_MAX for v in raw]

# --- Inférence ---
h, probs, action = forward(inputs)

# --- Affichage diagnostique ---
obstacle = ["⬛" if v < 0.5 else "⬜" for v in inputs]
print(
f"Capteurs {''.join(obstacle)} "
f"G={h[0]:.2f} D={h[1]:.2f} Dr={h[2]:.2f} "
f"→ {ACTIONS[action]}"
)

# --- Commande moteur ---
if action == 0:
MR25.forward(40)
elif action == 1:
MR25.turnLeft(30)
elif action == 2:
MR25.turnRight(30)
else:
MR25.stop()

time.sleep(0.1) # 10 Hz

except KeyboardInterrupt:
MR25.stop()
print("\nArrêt propre.")

# end of file

 

TP1 – Neurone artificiel

Partie 1 : Comprendre le neurone

Un neurone artificiel reçoit plusieurs informations en entrée.

Exemple :

Chaque entrée possède un poids :

  • w₁ = 2
  • w₂ = 1

Le neurone calcule :

Puis compare le résultat à un seuil :

  • Si S ≥ 2 → sortie = 1
  • Sinon → sortie = 0

Question n°1

Compléter le tableau suivant :

Question n°2

  • Que représente la sortie 1 ?
  • Que représente la sortie 0 ?

Partie 2 : Une alarme intelligente

On souhaite déclencher une alarme lorsque :

  • une personne est détectée
  • ET qu’il fait nuit

Variables :

Poids :

  • w₁ = 1
  • w₂ = 1

Seuil :

Question n°1

Compléter ce tableau :

Question n°2

Quel opérateur logique est reproduit ?

  • ET
  • OU
  • NON

Question n°3

Citer deux applications utilisant aujourd’hui des réseaux de neurones.

Exemples :

  • reconnaissance faciale ;
  • traduction automatique ;
  • voitures autonomes ;
  • robots intelligents ;
  • assistants vocaux.

Neurone artificiel (Partie 2)

Mise à jour : 15/06/26

Présentation

Dans cet article nous allons apprendre au robot MR25 à éviter les obstacles en utilisant un neurone perception avec 5 entrées. Avec les 5 capteurs du robot, il est préférable de ne plus simplement décider avance / stop comme dans la partie n°1, mais aussi de choisir une direction lorsque l’on détecte un obstacle.

Voici l’idée :

  • Capteurs 1 et 2 → surveillent la gauche.
  • Capteur 3 → surveille le centre.
  • Capteurs 4 et 5 → surveillent la droite.

  • Distance proche de 0 mm → obstacle très proche.
  • Distance proche de 250 mm → espace libre.

On peut entraîner un perceptron à 5 entrées qui décide si le robot peut avancer, puis utiliser les capteurs pour déterminer le sens de rotation lorsqu’il ne peut pas avancer.

#!/usr/bin/python3

import MR25 # Bibliothèque de contrôle du robot MR25
import time # Pour les pauses dans la boucle principale

# ============================================================
# PERCEPTRON — Neurone artificiel à seuil
# ============================================================
# Un perceptron calcule une somme pondérée de ses entrées,
# puis applique une fonction d'activation pour décider : 0 ou 1.

def activation(x):
"""Fonction d'activation en échelon (step function).
Retourne 1 si x >= 0 (voie libre), 0 sinon (obstacle)."""
return 1 if x >= 0 else 0

# 5 poids — un par capteur de proximité (c1 à c5)
w = [0.0, 0.0, 0.0, 0.0, 0.0]
b = 0.0 # Biais : ajuste le seuil de décision
eta = 0.1 # Taux d'apprentissage (learning rate) : amplitude des corrections

# ============================================================
# DONNÉES D'ENTRAÎNEMENT
# ============================================================
# Chaque exemple est un couple ([c1,c2,c3,c4,c5], étiquette)
# Les valeurs des capteurs sont normalisées entre 0.0 et 1.0 :
# - Proche de 0.0 → obstacle détecté (capteur saturé)
# - Proche de 1.0 → voie dégagée (capteur à portée max)
# Étiquette : 0 = obstacle → s'arrêter/tourner, 1 = avancer

training_data = [
# --- Cas OBSTACLE : capteurs faibles (obstacles proches) ---
([0.1, 0.1, 0.1, 0.1, 0.1], 0), # Obstacles partout
([0.2, 0.2, 0.2, 0.2, 0.2], 0), # Obstacles proches
([0.3, 0.2, 0.1, 0.2, 0.3], 0), # Obstacle central fort
([0.2, 0.2, 0.2, 0.8, 0.8], 0), # Obstacles proches
([0.8, 0.8, 0.2, 0.2, 0.2], 0), # Obstacles proches

# --- Cas AVANCER : capteurs forts (voie libre) ---
([0.8, 0.8, 0.8, 0.8, 0.8], 1), # Voie totalement libre
([0.9, 0.8, 0.9, 0.8, 0.9], 1), # Voie très dégagée
([0.7, 0.9, 0.8, 0.9, 0.7], 1), # Voie dégagée (légère variation)
]

# ============================================================
# PHASE D'APPRENTISSAGE — Règle de Hebb / Perceptron
# ============================================================
# Pour chaque exemple, on calcule la sortie et on corrige les
# poids proportionnellement à l'erreur : Δw = η × erreur × entrée

for epoch in range(100): # Maximum 100 passes sur les données

erreur_totale = 0

for inputs, target in training_data:

# Calcul de la somme pondérée : s = Σ(wi × xi) + b
s = sum(wi * xi for wi, xi in zip(w, inputs)) + b

# Application de la fonction d'activation
y = activation(s)

# Erreur = différence entre valeur attendue et valeur calculée
erreur = target - y # 0, +1 ou -1

# Mise à jour des poids : correction proportionnelle à l'erreur
for i in range(5):
w[i] += eta * erreur * inputs[i]

# Mise à jour du biais (entrée fictive toujours égale à 1)
b += eta * erreur

erreur_totale += abs(erreur)

# Arrêt anticipé si le perceptron classe tout correctement
if erreur_totale == 0:
break

# Affichage des paramètres appris
print("Poids :", w)
print("Biais :", b)

# ============================================================
# PILOTAGE EN TEMPS RÉEL — Boucle principale
# ============================================================

SEUIL_MAX = 250.0 # Distance maximale de détection (en cm ou unité capteur)
# Au-delà, on considère la voie comme libre

try:
while True:

# --- Lecture et normalisation des 5 capteurs de proximité ---
# proxSensor(n) retourne une valeur brute ; on la plafonne à SEUIL_MAX,
# puis on divise pour obtenir un ratio entre 0.0 et 1.0
p1 = MR25.proxSensor(1)
p2 = MR25.proxSensor(2)
p3 = MR25.proxSensor(3)
p4 = MR25.proxSensor(4)
p5 = MR25.proxSensor(5)

capteurs = [p1, p2, p3, p4, p5]
print(capteurs)

c1 = min(p1, SEUIL_MAX) / SEUIL_MAX # Capteur avant-gauche extrême
c2 = min(p2, SEUIL_MAX) / SEUIL_MAX # Capteur avant-gauche
c3 = min(p3, SEUIL_MAX) / SEUIL_MAX # Capteur avant-centre
c4 = min(p4, SEUIL_MAX) / SEUIL_MAX # Capteur avant-droite
c5 = min(p5, SEUIL_MAX) / SEUIL_MAX # Capteur avant-droite extrême

entree = [c1, c2, c3, c4, c5]
print(entree)

# --- Inférence : le perceptron décide d'avancer ou non ---
s = sum(wi * xi for wi, xi in zip(w, entree)) + b
print("s = ", s)
avance = activation(s) # 1 = avancer, 0 = éviter l'obstacle

print("avance = ", avance)

if avance:
# Voie libre → avancer à vitesse modérée
MR25.forward(25)
print("AVANCE")

else:
# Obstacle détecté → choisir le côté le plus dégagé pour tourner

gauche = c1 + c2 # Score d'espace à gauche (somme des 2 capteurs gauches)
droite = c4 + c5 # Score d'espace à droite (somme des 2 capteurs droits)

# Le côté avec le score le plus élevé est le plus dégagé
if gauche > droite:
MR25.turnLeft(40) # Plus d'espace à gauche → tourner à gauche
print("TOURNE GAUCHE")
else:
MR25.turnRight(40) # Plus d'espace à droite (ou égalité) → tourner à droite
print("TOURNE DROITE")

time.sleep(0.2) # Pause

except KeyboardInterrupt:
# Arrêt propre du robot lors d'une interruption clavier (Ctrl+C)
MR25.stop()
# en of file

Le neurone perceptron simple est un modèle de prédiction linéaire. Il est nécessaire d’utiliser plusieurs couches (réseau) pour résoudre des problèmes plus complexes.

Fin !

Faire des formes géométriques

mise à jour : 02/06/26

Dans cet article nous allons réaliser des déplacements sous formes géométriques avec le robot MR25. Nous allons utiliser les fonctions forwardmm() et turnAngle().

Faire un simple carré

Pour tracer un carré de 300 mm de côté, il suffit d’avancer de 300 mm puis de tourner de 90°, et de répéter cette séquence 4 fois.

#!/usr/bin/python3

import MR25
import time

def carre(cote):
for _ in range(4):
MR25.forwardmm(cote)
time.sleep(2)

MR25.turnAngle(90)
time.sleep(1)

carre(300)

MR25.stop()

Faire un triangle

Pour tracer un triangle équilatéral avec les fonctions

1
forwardmm()

et

1
turnAngle()

, il suffit de :

  1. Avancer d’une longueur donnée.
  2. Tourner de 120°.
  3. Répéter 3 fois.

 

Faire un cercle

Pour tracer un cercle de 500mm de diamètre par exemple, il suffit d’avancer de 300 mm puis de tourner de 90°, et de répéter cette séquence 4 fois. On peut approcher le cercle par un polygone régulier composé de petits segments.

Pour un cercle de 500 mm de diamètre, le rayon vaut :

En partant avec par exemple 36 segments de 10°,la circonférence vaut :

Pour un diamètre de 500 mm, la circonférence C = 1570 mm, donc pour 36 segments leurs longueurs doit être de 43 mm.

#!/usr/bin/python3

import MR25
import time

DIAMETRE = 500

circonference = 3.14159 * DIAMETRE
nb_segments = 36

longueur_segment = int(circonference / nb_segments)

for i in range(nb_segments):

MR25.forwardmm(longueur_segment)
time.sleep(0.2)

MR25.turnAngle(10)
time.sleep(0.1)

MR25.stop()

Le robot MR25 avance d’un petit segment puis tourne légèrement. Après de nombreuses répétitions, la trajectoire se rapproche d’un cercle de 500 mm de diamètre.

Si au augment le nombre de segment on peux obtenir une cercle plus prècis.

Une variante paramétrable : 

def cercle(diametre, nb_segments=72):

longueur = int((3.14159 * diametre) / nb_segments)
angle = 360 / nb_segments

for _ in range(nb_segments):
MR25.forwardmm(longueur)
time.sleep(0.1)

MR25.turnAngle(angle)
time.sleep(0.05)

A vous de jouez ! 

 

Neurone artificiel avec le robot MR25

Mise à jour : 22/06/26

Présentation

Dans cet article nous allons apprendre au robot MR25 à éviter les obstacles en utilisant un neurone perception. Le perceptron est le neurone artificiel le plus simple, modèle de neurone biologique, il a été inventé en 1957 par Frank Rosenblatt au laboratoire d’aéronautique de l’université Cornell. Ce neurone reçoit plusieurs entrées, les combine avec des poids, puis prend une décision binaire. 

Voici un exemple simple de neurone perceptron à 2 entrées utilisant les capteurs de proximité n°2 et n°4 du MR25.

Voici le principe de fonctionnement :

Entrées du neurone (capteurs de proximité) :

    • x1 = proxSensor(2)
    • x2 = proxSensor(4)

Sortie du neurone :

      • 0 → arrêt du robot
      • 1 → avance du robot

Le calcul effectué par le neurone est :

Avec :

  • : valeur du capteur 2
  • x2 : valeur du capteur 4
  • w1 : poids associé au capteur 2
  • w2 : poids associé au capteur 4
  • b : biais du neurone
  • s : somme pondérée

Ensuite, une fonction d’activation à seuil décide de la sortie du neurone :

Le perceptron est entraîné avec quelques exemples :

  • obstacle proche → stop le robot
  • espace libre → avancer le robot

-> Si les deux capteurs détectent un espace libre (valeurs élevées), la somme est grande alors le robot avance.

-> Si un obstacle est proche (valeur faible), la somme diminue → le robot MR25 s’arrête.

Le Programme

# Macé Robotics
#
#!/usr/bin/python3
import MR25
import time

# -----------------------------
# Fonctions du perceptron
# -----------------------------

def activation(x):
"""
Fonction d'activation de type seuil (step function).
Retourne 1 si x >= 0 (avancer), sinon 0 (stopper).
"""
if x >= 0:
return 1
return 0

# -----------------------------
# Initialisation des poids et biais
# -----------------------------
# w1 : poids associé au capteur 2 (x1)
# w2 : poids associé au capteur 4 (x2)
# b : biais du perceptron (décalage du seuil de décision)
w1 = 0.0
w2 = 0.0
b = 0.0

# Taux d'apprentissage : contrôle la vitesse de correction des poids
# Une valeur trop grande peut rendre l'apprentissage instable
eta = 0.1

# -----------------------------
# Jeu d'apprentissage (données d'entraînement)
# -----------------------------
#
# x1 = capteur de proximité 2 (distance normalisée entre 0 et 1)
# x2 = capteur de proximité 4 (distance normalisée entre 0 et 1)
#
# Normalisation : 0 = obstacle très proche, 1 = espace libre
#
# Sortie attendue :
# 0 = STOP (obstacle détecté, distances faibles)
# 1 = AVANCE (voie libre, distances élevées)
training_data = [
([0.1, 0.1], 0), # Obstacle très proche sur les deux capteurs → STOP
([0.2, 0.3], 0), # Obstacle proche → STOP
([0.3, 0.2], 0), # Obstacle proche → STOP
([0.8, 0.8], 1), # Espace libre sur les deux capteurs → AVANCE
([0.7, 0.9], 1), # Espace majoritairement libre → AVANCE
([0.9, 0.7], 1), # Espace majoritairement libre → AVANCE
]

# -----------------------------
# Phase d'apprentissage (entraînement du perceptron)
# -----------------------------
# On répète jusqu'à 100 époques (passages complets sur le jeu d'entraînement)
# L'apprentissage s'arrête prématurément si tous les exemples sont bien classés
for epoch in range(100):
erreur_totale = 0 # Compteur d'erreurs pour cette époque

for inputs, target in training_data:
x1, x2 = inputs

# Calcul de la sortie du perceptron : combinaison linéaire + activation
y = activation(w1*x1 + w2*x2 + b)
print("Y = ", y)

# Calcul de l'erreur : différence entre la sortie attendue et la sortie calculée
erreur = target - y

# Mise à jour des poids selon la règle d'apprentissage du perceptron
# Si erreur = 0 : pas de modification
# Si erreur = +1 : poids augmentés (le perceptron devait dire 1)
# Si erreur = -1 : poids diminués (le perceptron devait dire 0)
w1 += eta * erreur * x1
w2 += eta * erreur * x2
b += eta * erreur # Le biais est mis à jour sans facteur d'entrée
print("w1, w2 = ", w1, w2)

# Accumulation de l'erreur absolue sur cette époque
erreur_totale += abs(erreur)
print("Erreur totale = ", erreur_totale)

# Convergence atteinte : tous les exemples sont correctement classés
if erreur_totale == 0:
break

# Affichage des poids finaux après l'apprentissage
print("Apprentissage terminé")
print("w1 =", w1)
print("w2 =", w2)
print("b =", b)

# Pause avant de démarrer le pilotage (laisse le temps de lire les résultats)
time.sleep(5)

# -----------------------------
# Pilotage du robot MR25 en temps réel
# -----------------------------

# Distance maximale prise en compte (en mm)
# Au-delà de SEUIL_MAX, la distance est considérée comme maximale (voie libre)
SEUIL_MAX = 80.0 # mm

try:
while True:
# --- Lecture des capteurs de proximité ---
# proxSensor retourne une distance en mm
x1 = MR25.proxSensor(2) # Capteur 2
x2 = MR25.proxSensor(4) # Capteur 4

# --- Normalisation des distances dans l'intervalle [0, 1] ---
# min(x, SEUIL_MAX) écrête les valeurs supérieures au seuil
# La division ramène la valeur entre 0 et 1
x1 = min(x1, SEUIL_MAX) / SEUIL_MAX
x2 = min(x2, SEUIL_MAX) / SEUIL_MAX

# --- Inférence : décision du perceptron ---
# Utilise les poids appris pour décider d'avancer ou de s'arrêter
sortie = activation(w1*x1 + w2*x2 + b)

# --- Exécution de la commande moteur ---
if sortie == 1:
MR25.forward(40) # Avance à 40% de vitesse
print("AVANCE")
else:
MR25.stop() # Arrêt immédiat
print("STOP")

# Fréquence de rafraîchissement : 10 Hz (une décision toutes les 100 ms)
time.sleep(0.1)

except KeyboardInterrupt:
# Arrêt propre du robot lors d'une interruption clavier (Ctrl+C)
MR25.stop()

# end of file

Le perceptron apprend à partir d’exemples étiquetés :

training_data = [
([0.1, 0.1], 0), # Obstacle très proche sur les deux capteurs → STOP
([0.2, 0.3], 0), # Obstacle proche → STOP
([0.3, 0.2], 0), # Obstacle proche → STOP
([0.8, 0.8], 1), # Espace libre sur les deux capteurs → AVANCE
([0.7, 0.9], 1), # Espace majoritairement libre → AVANCE
([0.9, 0.7], 1), # Espace majoritairement libre → AVANCE
]

À chaque erreur, les poids sont corrigés selon la règle :

w += η × erreur × x

Où η est le taux d’apprentissage. On répète jusqu’à 100 fois ou jusqu’à zéro erreur. Le perceptron apprend par essai-erreur : il fait une prédiction, compare avec la bonne réponse, et corrige ses poids si nécessaire. C’est la règle de Hebb simplifiée.

Une fois entraîné, le robot entre dans une boucle infinie (10 Hz) :

  • Lire les capteurs 2 et 4 (distances en mm)
  • Normaliser entre 0 et 1 (avec un seuil max de 80 mm)
  • Décider via le perceptron
  • Agir 

La vidéo

 

Ce neurone perceptron constitue la base des réseaux de neurones : un réseau plus complexe n’est qu’un assemblage de nombreux perceptrons connectés entre eux.

Les améliorations

Le perceptron à 2 entrées est un bon point de départ, mais pour ce robot il existe plusieurs améliorations possibles. Voici quelques idées :

  • Ajouter les autres capteurs n°1, 3 et 5

Un neurone à 5 entrées permettrait une meilleure perception :

  • Ajouter les déplacements comme tourner à droite ou à gauche.

Par exemple :

  • Si obstacle à droite → Alors tourner à gauche
  • Si obstacle à gauche → Alors tourner à droite
  • Si voie libre → Alors avancer
  • Ajouter la valeur réels des capteurs 

L’exemple précédent utilise un simple seul binaire qui fait perdre beaucoup d’information.

On peux faire dépendre la vitesse du robot de la sortie du neurone :

vitesse = int(sortie * 50)
MR25.forward(vitesse)

Le robot MR25 ralentit lorsqu’il y a un obstacle.

  • Utiliser une fonction sigmoide comme fonction d’activation

La fonction d’activation précédent permet d’avoir une sortie binaire.

Avec cette fonction on obtient une probabilité en sortie du neurone :

Les déplacements du robot devient beaucoup plus fluide.

  • Utiliser un petit réseau de neurone

Au lieu d’utiliser une seule neurone, utiliser :

  • neurone 1 : obstacle à gauche
  • neurone 2 : obstacle devant
  • neurone 3 : obstacle à droite

Puis un neurone de décision choisit l’action :

  • Avancer
  • Tourner gauche
  • Tourner droite
  • Stop

Pour le MR25 équipé de 5 capteurs de proximité, l’amélioration la plus efficace est généralement : 5 entrées + 3 sorties (gauche, avance, droite) + apprentissage automatique des poids. Cela permet déjà d’obtenir un véritable comportement d’évitement d’obstacles basé sur un réseau neuronal simple.

A vous de jouez !