# Imports nécessaires
import random
import time
from typing import List, Tuple, Dict, Any
from collections import dequeTP Séance 1 : Qu’est-ce que l’Intelligence Artificielle ?
Introduction à l’Intelligence Artificielle — M1 IABD
Dr. El Hadji Bassirou TOURÉ
Université Cheikh Anta Diop de Dakar
Objectifs
- Explorer concrètement la distinction entre IA symbolique et IA connexionniste
- Implémenter un agent réflexe simple
- Expérimenter avec différents types d’environnements
- Comprendre les limites de chaque approche
Prérequis
- Python de base (fonctions, classes, boucles)
- Avoir lu le CM de la séance 1
Partie 1 : IA Symbolique vs Machine Learning
Cette partie illustre la différence fondamentale entre l’IA symbolique (règles programmées) et le Machine Learning (apprentissage des données).
1.1 Problème : Classification de nombres
Objectif : Déterminer si un nombre est “petit” (< 50), “moyen” (50-100), ou “grand” (> 100).
# Approche IA symbolique : règles explicites
def classifier_symbolique(n: int) -> str:
"""
Classification par règles explicites.
Les seuils sont codés en dur par le programmeur.
"""
if n < 50:
return "petit"
elif n <= 100:
return "moyen"
else:
return "grand"
# Test
test_numbers = [10, 49, 50, 75, 100, 101, 500]
print("Classification symbolique (règles) :")
for n in test_numbers:
print(f" {n} -> {classifier_symbolique(n)}")# Approche ML : apprendre des données
def classifier_ml_simple(n: int, donnees_entrainement: List[Tuple[int, str]]) -> str:
"""
Classification par k-NN simplifié (k=1).
Le système apprend des exemples au lieu d'avoir des règles codées.
"""
# Trouver l'exemple le plus proche
plus_proche = min(donnees_entrainement, key=lambda x: abs(x[0] - n))
return plus_proche[1]
# Données d'entraînement (exemples étiquetés)
donnees = [
(5, "petit"), (20, "petit"), (40, "petit"),
(55, "moyen"), (70, "moyen"), (90, "moyen"),
(120, "grand"), (200, "grand"), (500, "grand")
]
print("\nClassification ML (apprentissage des exemples) :")
for n in test_numbers:
print(f" {n} -> {classifier_ml_simple(n, donnees)}")1.2 Questions
- Que se passe-t-il si on veut changer les seuils ?
- Approche symbolique : modifier le code
- Approche ML : fournir de nouveaux exemples
- Exécutez la cellule ci-dessous pour voir ce qui se passe avec des données différentes :
# Nouvelles données avec des seuils différents (30 et 80)
nouvelles_donnees = [
(5, "petit"), (15, "petit"), (25, "petit"),
(35, "moyen"), (50, "moyen"), (70, "moyen"),
(90, "grand"), (150, "grand"), (300, "grand")
]
print("Comparaison avec nouveaux seuils (implicites dans les données) :")
print(f"{'Nombre':>6} | {'Symbolique':>12} | {'ML':>12}")
print("-" * 35)
for n in test_numbers:
sym = classifier_symbolique(n)
ml = classifier_ml_simple(n, nouvelles_donnees)
match = "✓" if sym == ml else "✗"
print(f"{n:>6} | {sym:>12} | {ml:>12} {match}")Observation clé : Le système ML s’adapte automatiquement aux nouveaux seuils via les données, alors que le système symbolique nécessite une modification du code.
Partie 2 : Implémentation d’un Agent Réflexe Simple
On implémente un agent aspirateur dans un monde simple (2 cases : A et B).
2.1 L’environnement
class EnvironnementAspirateur:
"""
Environnement simple avec deux cases (A et B).
Chaque case peut être propre ou sale.
"""
def __init__(self):
# État initial aléatoire
self.cases = {
'A': random.choice(['propre', 'sale']),
'B': random.choice(['propre', 'sale'])
}
self.position_agent = random.choice(['A', 'B'])
self.score = 0
def percevoir(self) -> Tuple[str, str]:
"""Renvoie (position, état_case)"""
return (self.position_agent, self.cases[self.position_agent])
def executer_action(self, action: str):
"""Exécute une action et met à jour le score."""
if action == 'aspirer':
if self.cases[self.position_agent] == 'sale':
self.cases[self.position_agent] = 'propre'
self.score += 10 # Récompense pour avoir nettoyé
elif action == 'gauche':
self.position_agent = 'A'
self.score -= 1 # Coût du déplacement
elif action == 'droite':
self.position_agent = 'B'
self.score -= 1
def afficher(self):
"""Affiche l'état de l'environnement."""
agent_A = "🤖" if self.position_agent == 'A' else " "
agent_B = "🤖" if self.position_agent == 'B' else " "
etat_A = "💩" if self.cases['A'] == 'sale' else "✨"
etat_B = "💩" if self.cases['B'] == 'sale' else "✨"
print(f"┌────────┬────────┐")
print(f"│ A │ B │")
print(f"│ {etat_A} {agent_A}│ {etat_B} {agent_B}│")
print(f"└────────┴────────┘")
print(f"Score: {self.score}")
# Test de l'environnement
env = EnvironnementAspirateur()
print("État initial de l'environnement :")
env.afficher()
print(f"Perception de l'agent : {env.percevoir()}")2.2 Agent réflexe simple
class AgentReflexeSimple:
"""
Agent réflexe simple basé sur des règles condition-action.
Ne mémorise rien, décide uniquement sur la perception actuelle.
"""
def choisir_action(self, perception: Tuple[str, str]) -> str:
"""
Règles :
- Si la case est sale -> aspirer
- Si en A et propre -> aller à droite
- Si en B et propre -> aller à gauche
"""
position, etat = perception
if etat == 'sale':
return 'aspirer'
elif position == 'A':
return 'droite'
else: # position == 'B'
return 'gauche'
def simuler_agent(agent, environnement, n_etapes: int = 10):
"""Simule un agent dans un environnement pendant n étapes."""
print("\n" + "="*50)
print("SIMULATION")
print("="*50)
for etape in range(n_etapes):
print(f"\n--- Étape {etape + 1} ---")
# L'agent perçoit
perception = environnement.percevoir()
print(f"Perception: {perception}")
# L'agent décide
action = agent.choisir_action(perception)
print(f"Action choisie: {action}")
# L'environnement exécute
environnement.executer_action(action)
environnement.afficher()
# Vérifier si tout est propre
if all(v == 'propre' for v in environnement.cases.values()):
print("\n✅ Tout est propre !")
break
print(f"\n{'='*50}")
print(f"Score final: {environnement.score}")
print(f"{'='*50}")
# Simulation
env = EnvironnementAspirateur()
agent = AgentReflexeSimple()
simuler_agent(agent, env)2.3 Exercice : Améliorer l’agent
L’agent réflexe simple a un problème : il continue à se déplacer même quand tout est propre.
Exercice : Créez un AgentReflexeAvecMemoire qui se souvient des cases déjà visitées et propres.
class AgentReflexeAvecMemoire:
"""
Agent basé sur modèle : maintient un état interne
pour se souvenir de l'état des cases visitées.
"""
def __init__(self):
# État interne : mémoire des cases connues comme propres
self.cases_propres = set()
def choisir_action(self, perception: Tuple[str, str]) -> str:
"""
À COMPLÉTER
Règles suggérées :
1. Si la case actuelle est sale -> aspirer
2. Sinon, mémoriser que cette case est propre
3. Si toutes les cases connues sont propres -> ne rien faire
4. Sinon, se déplacer vers une case non visitée/sale
"""
position, etat = perception
# VOTRE CODE ICI
pass
# Test de votre agent
# env = EnvironnementAspirateur()
# agent_ameliore = AgentReflexeAvecMemoire()
# simuler_agent(agent_ameliore, env, n_etapes=15)2.4 Solution
class AgentReflexeAvecMemoireSolution:
"""
Solution : Agent basé sur modèle avec mémoire.
"""
def __init__(self):
self.cases_propres = set()
self.toutes_cases = {'A', 'B'}
def choisir_action(self, perception: Tuple[str, str]) -> str:
position, etat = perception
# Si sale, aspirer
if etat == 'sale':
self.cases_propres.discard(position) # Au cas où on pensait qu'elle était propre
return 'aspirer'
# Mémoriser que cette case est propre
self.cases_propres.add(position)
# Si on connaît toutes les cases comme propres, ne rien faire
if self.cases_propres == self.toutes_cases:
return 'rien' # Nouvelle action : ne rien faire
# Sinon, aller vers l'autre case
if position == 'A':
return 'droite'
else:
return 'gauche'
# Modification de l'environnement pour accepter 'rien'
class EnvironnementAmelioré(EnvironnementAspirateur):
def executer_action(self, action: str):
if action == 'rien':
pass # Ne rien faire ne coûte rien
else:
super().executer_action(action)
# Simulation
print("Agent AVEC mémoire :")
env = EnvironnementAmelioré()
agent = AgentReflexeAvecMemoireSolution()
simuler_agent(agent, env, n_etapes=10)Partie 3 : Exploration des Types d’Environnements
On va modifier l’environnement pour explorer les différentes propriétés.
3.1 Environnement stochastique
class EnvironnementStochastique(EnvironnementAspirateur):
"""
Environnement où :
- Les cases peuvent se salir spontanément
- L'aspirateur peut échouer avec une certaine probabilité
"""
def __init__(self, proba_salir=0.1, proba_echec=0.2):
super().__init__()
self.proba_salir = proba_salir
self.proba_echec = proba_echec
def executer_action(self, action: str):
# Les cases peuvent se salir spontanément
for case in self.cases:
if self.cases[case] == 'propre' and random.random() < self.proba_salir:
self.cases[case] = 'sale'
print(f" [!] La case {case} s'est salie !")
# L'aspiration peut échouer
if action == 'aspirer' and random.random() < self.proba_echec:
print(" [!] L'aspiration a échoué !")
self.score -= 1
return
super().executer_action(action)
# Simulation avec environnement stochastique
print("Environnement STOCHASTIQUE :")
env = EnvironnementStochastique(proba_salir=0.2, proba_echec=0.1)
agent = AgentReflexeSimple()
simuler_agent(agent, env, n_etapes=15)3.2 Questions d’analyse
Exécutez plusieurs fois les simulations et répondez :
- L’agent réflexe simple fonctionne-t-il bien dans l’environnement stochastique ?
- Pourquoi l’agent avec mémoire peut-il avoir des problèmes dans l’environnement stochastique ?
- Comment pourrait-on améliorer l’agent pour gérer l’incertitude ?
Partie 4 : Comparaison Finale — Quel Agent pour Quel Environnement ?
4.1 Benchmark
def benchmark_agent(AgentClass, EnvClass, n_simulations=100, n_etapes=20, **env_kwargs):
"""
Évalue un agent sur plusieurs simulations.
Retourne le score moyen et la proportion de succès.
"""
scores = []
succes = 0
for _ in range(n_simulations):
env = EnvClass(**env_kwargs)
agent = AgentClass()
for _ in range(n_etapes):
perception = env.percevoir()
action = agent.choisir_action(perception)
if action:
env.executer_action(action)
# Vérifier si tout est propre
if all(v == 'propre' for v in env.cases.values()):
succes += 1
break
scores.append(env.score)
return {
'score_moyen': sum(scores) / len(scores),
'taux_succes': succes / n_simulations * 100
}
# Benchmark dans différents environnements
print("BENCHMARK DES AGENTS")
print("=" * 60)
# Environnement déterministe
print("\n1. Environnement DÉTERMINISTE")
print("-" * 40)
res = benchmark_agent(AgentReflexeSimple, EnvironnementAspirateur)
print(f"Agent réflexe simple : Score={res['score_moyen']:.1f}, Succès={res['taux_succes']:.0f}%")
res = benchmark_agent(AgentReflexeAvecMemoireSolution, EnvironnementAmelioré)
print(f"Agent avec mémoire : Score={res['score_moyen']:.1f}, Succès={res['taux_succes']:.0f}%")
# Environnement stochastique
print("\n2. Environnement STOCHASTIQUE (proba_salir=0.1)")
print("-" * 40)
res = benchmark_agent(AgentReflexeSimple, EnvironnementStochastique,
proba_salir=0.1, proba_echec=0.1)
print(f"Agent réflexe simple : Score={res['score_moyen']:.1f}, Succès={res['taux_succes']:.0f}%")Pour aller plus loin (optionnel)
Défi 1 : Environnement à 4 cases
Modifiez l’environnement pour avoir 4 cases (grille 2x2). L’agent doit pouvoir se déplacer dans 4 directions.
# VOTRE CODE ICI - Environnement 2x2
# Indice : utilisez des coordonnées (x, y) au lieu de 'A' et 'B'Défi 2 : Agent basé sur buts
Créez un agent qui a un but explicite : “toutes les cases doivent être propres”. Au lieu de règles réflexes, il doit planifier une séquence d’actions.
# VOTRE CODE ICI - Agent basé sur buts
# Indice : utilisez BFS pour trouver la séquence d'actions optimaleRésumé du TP
Dans ce TP, nous avons :
- Comparé IA symbolique et ML : Les règles explicites vs l’apprentissage des données
- Implémenté un agent réflexe simple : Décisions basées uniquement sur la perception actuelle
- Amélioré vers un agent avec mémoire : Maintien d’un état interne pour de meilleures décisions
- Exploré différents types d’environnements : Déterministe vs stochastique
- Mesuré les performances : Benchmark pour comparer les approches
Points clés : - Un agent réflexe simple suffit pour des environnements simples et déterministes - La mémoire (agent basé sur modèle) devient nécessaire quand l’observabilité est partielle - L’incertitude (environnement stochastique) pose des défis que nous aborderons dans les séances suivantes (probabilités, apprentissage)