Pràctica PR5071/02: Sistema Expert en Python amb Docker
Objectius
En acabar aquesta pràctica sereu capaços de:
- Dissenyar una base de coneixement estructurada per a un domini real
- Implementar un motor d'inferència de forward chaining en Python
- Crear un sistema expert amb mínim 20 regles i gestió de confiança
- Construir una interfície d'usuari interactiva per consola
- Integrar explicabilitat en el raonament del sistema
- Executar el sistema dins un contenidor Docker personalitzat
- (Opcional) Ampliar el sistema amb lògica difusa
Prerequisits
| Requisit | Detalls |
|---|---|
| Temps estimat | 6 hores (3 sessions de 2h) |
| Docker Desktop | Versió 24.x o superior instal·lada i en execució |
| RAM mínima | 2 GB disponibles per a Docker |
| Espai en disc | 1 GB lliure (imatge Python + dependències) |
| Coneixements | Python bàsic (classes, llistes, diccionaris), conceptes de sistemes experts |
| Prerequisit teòric | Haver llegit el tema Sistemes Experts del mòdul 5071 |
Sense IDE especial
Podeu editar els fitxers Python amb qualsevol editor: VS Code, PyCharm o fins i tot el Bloc de Notes. El que importa és que el fitxer es guardi amb codificació UTF-8 per suportar correctament els accents del català.
Introducció
Per que els sistemes experts segueixen sent rellevants el 2025?
En l'era dels LLMs, podria semblar que els sistemes experts han quedat obsolets. La realitat és més matiçada. El 2025, els sistemes experts (i la seva evolució, els motors de regles empresarials) segueixen gestionant decisions crítiques en:
- Banca i assegurances: Drools i IBM Operational Decision Manager prenen milions de decisions de crèdit i sinistres cada dia, amb auditabilitat completa.
- Medicina: sistemes de suport al diagnòstic com Isabel DDx o UpToDate Differential Diagnosis combinen bases de coneixement expert amb ML.
- Indústria: diagnòstic d'averies en maquinari complex (aeronàutica, energia) on calen explicacions comprensibles per als tècnics humans.
- Dret i regulació: classificació automàtica de documents legals i detecció de non-compliances.
La virtut dels sistemes experts és precisament el que els LLMs no garanteixen: traçabilitat i explicabilitat completes. Per a aplicacions d'alt risc regulades per l'AI Act (diagnòstic mèdic, crèdit, selecció de personal), un sistema expert és sovint la millor opció perquè cada decisió pot ser auditada regla per regla.
El forward chaining explicat
El forward chaining (encadenament cap endavant) és el mecanisme d'inferència que implementarem:
graph LR
F[Fets coneguts] --> M[Motor inferencia<br/>compara fets amb condicions]
M --> R{Alguna regla<br/>s aplica?}
R -->|Si| N[Afegir nova conclusio<br/>als fets coneguts]
N --> M
R -->|No| C[Conclusions finals]
style F fill:#1565c0,color:#fff
style M fill:#6a1b9a,color:#fff
style C fill:#2e7d32,color:#fff
El sistema parteix d'un conjunt de fets (observacions, símptomes, respostes de l'usuari), aplica repetidament les regles de la base de coneixement, i dedueix noves conclusions fins que no pot derivar res nou (punt fix).
Part 1: Disseny del Sistema Expert
1.1. Tria del domini
Escolliu un dels quatre dominis proposats. Cada domini té les seves particularitats, però tots segueixen la mateixa arquitectura:
Opció A: Diagnòstic Informàtic
L'usuari descriu problemes amb el seu ordinador (no arranca, va lent, no es connecta a internet...) i el sistema diagnòstica la causa probable i recomana solucions.
Opció B: Recomanació de Carrera Professional en IA
L'usuari respon preguntes sobre les seves habilitats, interessos i objectius professionals, i el sistema recomana quin perfil d'especialista en IA s'ajusta millor (Data Scientist, MLOps Engineer, AI Ethics Officer, NLP Engineer...).
Opció C: Diagnòstic de Plantes (domini més visual i engrescador per a presentar)
L'usuari descriu els símptomes de la seva planta (fulles grogues, tiges febles, terra humida...) i el sistema diagnòstica la malaltia o condició i proposa un tractament.
Opció D: Recomanació de Tecnologies Big Data
L'usuari descriu les necessitats del seu projecte (volum de dades, velocitat, tipus de consultes...) i el sistema recomana quines tecnologies Big Data s'hi adapten millor.
1.2. Disseny de la base de coneixement
Abans de programar, dissenyeu la vostra base de coneixement en paper o en un document:
- Llista de fets possibles (variables booleans o de text)
- Llista de regles (condicions → conclusió, amb confiança 0.0 a 1.0)
- Llista de preguntes que fareu a l'usuari per obtenir els fets inicials
- Llista de conclusions possibles i les seves recomanacions
Consell de disseny
Penseu en les regles com a sentències "SI...LLAVORS": "SI la pantalla no s'encén I el llum d'alimentació tampoc s'encén, LLAVORS el problema és probablement l'alimentació (confiança 0.9)". Intenteu que les vostres regles reflecteixin coneixement real d'expert, no sentit comú genèric.
Part 2: Implementació — Base de Coneixement
2.1. Estructura base del fitxer principal
Creeu un fitxer sistema_expert_joan_garcia.py (substituïu joan_garcia pel vostre nom real):
# sistema_expert_joan_garcia.py
# Sistema Expert: Diagnòstic Informàtic
# Alumne: Joan Garcia - Institut Sa Palomera - Mòdul 5071
# Data: Març 2025
"""
Sistema expert de diagnòstic informàtic implementat amb forward chaining.
Diagnostica problemes comuns d'ordinador basant-se en símptomes descrits per l'usuari.
Cada regla inclou un factor de confiança (0.0 a 1.0) i una explicació per a l'usuari.
"""
from typing import Dict, List, Tuple, Optional
import datetime
class Regla:
"""
Representa una regla de la base de coneixement.
Atributs:
id: Identificador únic de la regla
condicions: Diccionari {fet: valor_esperat} que han de complir-se
conclusio: Fet nou que es deriva si les condicions es compleixen
confiança: Factor de confiança de la regla (0.0 a 1.0)
explicacio: Text explicatiu per mostrar a l'usuari
"""
def __init__(self, id: str, condicions: Dict, conclusio: str,
confiança: float, explicacio: str):
self.id = id
self.condicions = condicions
self.conclusio = conclusio
self.confiança = confiança
self.explicacio = explicacio
def __repr__(self) -> str:
return f"Regla({self.id}: {self.condicions} => {self.conclusio} [{self.confiança}])"
class SistemaExpert:
"""
Motor d'inferència amb forward chaining per a diagnòstic informàtic.
Atributs:
nom_expert: Nom identificador del sistema (inclou el nom de l'alumne)
fets: Diccionari de fets coneguts {fet: valor}
regles: Llista de regles de la base de coneixement
historial_inferencies: Llista d'inferències realitzades (per a explicabilitat)
"""
def __init__(self):
self.nom_expert = "SistemaExpert_Joan_Garcia"
self.versio = "1.0.0"
self.data_creacio = "2025-03-14"
self.domini = "Diagnòstic Informàtic"
self.fets: Dict[str, any] = {}
self.regles: List[Regla] = self._carregar_regles()
self.historial_inferencies: List[Dict] = []
self.conclusions_finals: List[Dict] = []
def _carregar_regles(self) -> List[Regla]:
"""
Defineix la base de coneixement del sistema expert.
Conté 25 regles de diagnòstic informàtic amb factors de confiança.
"""
return [
# ── GRUP 1: Problemes d'alimentació ────────────────────────────
Regla(
id="R01",
condicions={"pantalla_no_encen": True, "llum_alimentacio_off": True},
conclusio="problema_alimentacio_greu",
confiança=0.95,
explicacio="Ni la pantalla ni el llum d'alimentació funcionen: "
"probable fallada de la font d'alimentació o del cable."
),
Regla(
id="R02",
condicions={"pantalla_no_encen": True, "llum_alimentacio_on": True},
conclusio="problema_pantalla_o_cable_video",
confiança=0.85,
explicacio="L'equip té alimentació però la pantalla no mostra res: "
"cable de vídeo desconnectat o pantalla defectuosa."
),
Regla(
id="R03",
condicions={"problema_alimentacio_greu": True, "cable_connectat": False},
conclusio="solucio_connectar_cable",
confiança=0.99,
explicacio="Solució simple: connecteu el cable d'alimentació al endoll."
),
Regla(
id="R04",
condicions={"problema_alimentacio_greu": True, "cable_connectat": True},
conclusio="font_alimentacio_defectuosa",
confiança=0.80,
explicacio="El cable és connectat però no hi ha alimentació: "
"la font d'alimentació pot estar cremada. Porteu-lo al tècnic."
),
# ── GRUP 2: Problemes d'arrancada ──────────────────────────────
Regla(
id="R05",
condicions={"no_arranca": True, "pitets_bios": True},
conclusio="problema_hardware_bios",
confiança=0.90,
explicacio="Els pitets de la BIOS indiquen un error de hardware. "
"Comproveu la memòria RAM i la targeta gràfica."
),
Regla(
id="R06",
condicions={"no_arranca": True, "pitets_bios": False, "pantalla_blava": True},
conclusio="error_bsod_windows",
confiança=0.88,
explicacio="Pantalla blava de la mort (BSOD). Possible corrupció del sistema "
"operatiu, controladors incompatibles o RAM defectuosa."
),
Regla(
id="R07",
condicions={"no_arranca": True, "pitets_bios": False, "missatge_disc": True},
conclusio="problema_disc_dur_o_boot",
confiança=0.85,
explicacio="Missatge d'error sobre el disc en arrencar: "
"possible disc dur defectuós o MBR/GPT corromput."
),
Regla(
id="R08",
condicions={"error_bsod_windows": True},
conclusio="recomanacio_reparar_windows",
confiança=0.80,
explicacio="Executeu 'sfc /scannow' en mode segur per reparar fitxers corruptes "
"o feu una restauració del sistema a un punt anterior."
),
# ── GRUP 3: Rendiment lent ─────────────────────────────────────
Regla(
id="R09",
condicions={"rendiment_lent": True, "cpu_alta": True, "ram_plena": False},
conclusio="proces_consumint_cpu",
confiança=0.85,
explicacio="La CPU és al 100% però la RAM no és plena: "
"un procés específic (virus, actualització, malware) consumeix la CPU."
),
Regla(
id="R10",
condicions={"rendiment_lent": True, "ram_plena": True},
conclusio="ram_insuficient",
confiança=0.90,
explicacio="La RAM és al límit. Tanqueu aplicacions innecessàries "
"o considereu ampliar la memòria RAM."
),
Regla(
id="R11",
condicions={"rendiment_lent": True, "disc_dur_ple": True},
conclusio="disc_dur_ple_rendiment",
confiança=0.80,
explicacio="El disc dur gairebé ple afecta el rendiment global. "
"Allibereu espai eliminant arxius temporals i programes no usats."
),
Regla(
id="R12",
condicions={"rendiment_lent": True, "edat_equip_anys": True, "ram_plena": False},
conclusio="equip_obsolet",
confiança=0.70,
explicacio="L'equip té més de 6 anys. El hardware pot ser insuficient "
"per al programari actual. Considereu actualitzar o substituir l'equip."
),
Regla(
id="R13",
condicions={"proces_consumint_cpu": True},
conclusio="recomanacio_antivirus",
confiança=0.75,
explicacio="Executeu un escaneig complet amb Windows Defender o Malwarebytes "
"per descartar malware com a causa del consum de CPU."
),
# ── GRUP 4: Problemes de xarxa ─────────────────────────────────
Regla(
id="R14",
condicions={"sense_internet": True, "wifi_connectat": False},
conclusio="no_connectat_xarxa",
confiança=0.95,
explicacio="L'equip no està connectat a cap xarxa Wi-Fi o cable."
),
Regla(
id="R15",
condicions={"sense_internet": True, "wifi_connectat": True, "ping_router": False},
conclusio="problema_router_o_lan",
confiança=0.88,
explicacio="Connectat al Wi-Fi però sense accés al router: "
"reinicieu el router o comproveu la configuració de xarxa."
),
Regla(
id="R16",
condicions={"sense_internet": True, "wifi_connectat": True, "ping_router": True},
conclusio="problema_proveidor_internet",
confiança=0.85,
explicacio="El router funciona però no hi ha connexió a internet: "
"el problema és al costat del proveïdor. Truqueu al servei tècnic."
),
Regla(
id="R17",
condicions={"no_connectat_xarxa": True},
conclusio="recomanacio_connectar_xarxa",
confiança=0.99,
explicacio="Feu clic a la icona de xarxa a la barra de tasques "
"i seleccioneu la vostra xarxa Wi-Fi, o connecteu el cable Ethernet."
),
# ── GRUP 5: Problemes de so ────────────────────────────────────
Regla(
id="R18",
condicions={"sense_so": True, "altaveus_connectats": False},
conclusio="altaveus_desconnectats",
confiança=0.98,
explicacio="Els altaveus o auriculars no estan connectats al port d'àudio."
),
Regla(
id="R19",
condicions={"sense_so": True, "altaveus_connectats": True, "volum_silenciat": True},
conclusio="volum_silenciat_sistemu",
confiança=0.99,
explicacio="El volum del sistema és silenciat. "
"Feu clic a la icona de so i augmenteu el volum."
),
Regla(
id="R20",
condicions={"sense_so": True, "altaveus_connectats": True,
"volum_silenciat": False},
conclusio="problema_controlador_audio",
confiança=0.82,
explicacio="Altaveus connectats i volum activat però sense so: "
"el controlador d'àudio pot estar corromput o desactualitzat."
),
# ── GRUP 6: Problemes de virus i seguretat ─────────────────────
Regla(
id="R21",
condicions={"comportament_estrany": True, "finestres_popup": True},
conclusio="possible_malware",
confiança=0.88,
explicacio="Finestres emergents aleatòries i comportament inusual: "
"possible infecció per adware o malware."
),
Regla(
id="R22",
condicions={"possible_malware": True},
conclusio="recomanacio_escaneig_malware",
confiança=0.95,
explicacio="Descarregueu i executeu Malwarebytes Free en mode escaneig complet. "
"Reinicieu en mode segur si l'escaneig normal no pot completar-se."
),
Regla(
id="R23",
condicions={"comportament_estrany": True, "cpu_alta": True,
"finestres_popup": False},
conclusio="possible_cryptominer",
confiança=0.75,
explicacio="CPU alta sense raó aparent i comportament inusual: "
"possible cryptominer instal·lat sense consentiment."
),
# ── GRUP 7: Problemes de sobreescalfament ──────────────────────
Regla(
id="R24",
condicions={"equip_s_apaga_sol": True, "soroll_ventilador": True},
conclusio="sobreescalfament",
confiança=0.90,
explicacio="El ventilador fa molt soroll i l'equip s'apaga sol: "
"sobreescalfament. Neteja de pols interior i revisió del sistema de refrigeració."
),
Regla(
id="R25",
condicions={"sobreescalfament": True},
conclusio="recomanacio_neteja_pols",
confiança=0.92,
explicacio="Apagueu l'equip, desconnecteu-lo de l'endoll i netegeu l'interior "
"amb aire comprimit (especialment els ventiladors i el dissipador de la CPU)."
),
]
Part 3: Motor d'Inferència
3.1. Implementació del forward chaining
Afegiu els mètodes següents a la classe SistemaExpert:
def afegir_fet(self, clau: str, valor) -> None:
"""
Afegeix un fet a la base de fets actual.
Args:
clau: Nom del fet
valor: Valor del fet (bool, str, int, float)
"""
self.fets[clau] = valor
def _condicions_complides(self, regla: Regla) -> bool:
"""
Comprova si totes les condicions d'una regla es compleixen
amb els fets actuals.
Args:
regla: La regla a comprovar
Returns:
True si totes les condicions es compleixen, False en cas contrari
"""
for fet, valor_esperat in regla.condicions.items():
if self.fets.get(fet) != valor_esperat:
return False
return True
def inferir(self) -> List[Dict]:
"""
Motor d'inferència forward chaining.
Aplica les regles repetidament fins que no es pot derivar cap
nou fet (punt fix). Registra totes les inferències per a explicabilitat.
Returns:
Llista de totes les conclusions derivades amb els seus factors de confiança
"""
canvis = True
iteracio = 0
conclusions = []
while canvis:
canvis = False
iteracio += 1
for regla in self.regles:
# Saltar si la conclusio ja esta als fets
if regla.conclusio in self.fets:
continue
# Comprovar si les condicions es compleixen
if self._condicions_complides(regla):
# Nova inferencia!
self.fets[regla.conclusio] = True
canvis = True
inferencia = {
"iteracio": iteracio,
"regla_id": regla.id,
"condicions": dict(regla.condicions),
"conclusio": regla.conclusio,
"confiança": regla.confiança,
"explicacio": regla.explicacio,
"timestamp": datetime.datetime.now().isoformat()
}
self.historial_inferencies.append(inferencia)
conclusions.append(inferencia)
return conclusions
def obtenir_conclusions_finals(self) -> List[Dict]:
"""
Filtra l'historial d'inferències per retornar només les conclusions
que no han estat usades com a condicions intermèdies.
Returns:
Llista de conclusions finals (recomanacions per a l'usuari)
"""
# Identificar quins fets inferits son condicions d'altres regles
condicions_intermedies = set()
for regla in self.regles:
for condicio in regla.condicions.keys():
condicions_intermedies.add(condicio)
# Retornar les conclusions que no son condicions d'altres regles
conclusions_finals = []
for inferencia in self.historial_inferencies:
if inferencia["conclusio"] not in condicions_intermedies:
conclusions_finals.append(inferencia)
self.conclusions_finals = conclusions_finals
return conclusions_finals
def generar_explicacio(self) -> str:
"""
Genera una explicació textual del procés de raonament.
Returns:
Text multi-línia amb l'explicació del raonament pas a pas
"""
if not self.historial_inferencies:
return "No s'han derivat conclusions. Afegiu fets al sistema primer."
linies = [
f"\n{'='*60}",
f"EXPLICACIÓ DEL RAONAMENT — {self.nom_expert}",
f"{'='*60}",
f"Fets inicials introduïts per l'usuari: {len(self.fets)}",
f"Regles aplicades: {len(self.historial_inferencies)}",
f"{'─'*60}"
]
for i, inf in enumerate(self.historial_inferencies, 1):
linies.append(
f"\nPas {i} (Iteració {inf['iteracio']}, Regla {inf['regla_id']}):"
)
linies.append(f" Condicions: {inf['condicions']}")
linies.append(f" Conclusió: {inf['conclusio']}")
linies.append(f" Confiança: {inf['confiança']*100:.0f}%")
linies.append(f" Explicació: {inf['explicacio']}")
linies.append(f"\n{'='*60}")
return "\n".join(linies)
def reiniciar(self) -> None:
"""Reinicia el sistema per a una nova sessió de diagnòstic."""
self.fets = {}
self.historial_inferencies = []
self.conclusions_finals = []
Part 4: Interfície d'Usuari Interactiva
4.1. Preguntes del sistema de diagnòstic
Afegiu la funció preguntar_simptomes fora de la classe:
def preguntar_simptomes(expert: SistemaExpert) -> None:
"""
Guia l'usuari per un conjunt de preguntes per obtenir els fets inicials.
Les preguntes s'adapten dinàmicament segons les respostes anteriors.
Args:
expert: Instància del SistemaExpert on s'afegiran els fets
"""
print("\n" + "="*60)
print("PREGUNTES DE DIAGNÒSTIC")
print("Responeu S (Sí) o N (No) a cada pregunta.")
print("="*60 + "\n")
def fer_pregunta(text: str, clau: str) -> bool:
"""Fa una pregunta binaria i guarda el resultat als fets."""
while True:
resposta = input(f" {text} [S/N]: ").strip().upper()
if resposta in ("S", "SI", "SÍ", "Y", "YES"):
expert.afegir_fet(clau, True)
return True
elif resposta in ("N", "NO"):
expert.afegir_fet(clau, False)
return False
else:
print(" Resposta no vàlida. Escriviu S o N.")
# Símptomes principals
print("SÍMPTOMES GENERALS:")
arranca = not fer_pregunta("L'ordinador s'encén i arranca normalment?", "arranca_normal")
if arranca:
expert.afegir_fet("no_arranca", True)
pantalla_encesa = fer_pregunta("La pantalla s'encén?", "pantalla_encesa")
if not pantalla_encesa:
expert.afegir_fet("pantalla_no_encen", True)
llum_alimentacio = fer_pregunta("El llum d'alimentació de l'equip s'encén?", "llum_alimentacio_on")
if not llum_alimentacio:
expert.afegir_fet("llum_alimentacio_off", True)
cable_connectat = fer_pregunta("El cable d'alimentació és connectat correctament?", "cable_connectat")
pitets_bios = fer_pregunta("Escolteu pitets de la BIOS en arrencar?", "pitets_bios")
pantalla_blava = fer_pregunta("Apareix una pantalla blava (BSOD)?", "pantalla_blava")
missatge_disc = fer_pregunta("Hi ha missatges d'error sobre el disc?", "missatge_disc")
else:
expert.afegir_fet("arranca_normal", True)
expert.afegir_fet("no_arranca", False)
print("\nPROBLEMES DE RENDIMENT:")
rendiment_lent = fer_pregunta("L'ordinador va molt lent?", "rendiment_lent")
if rendiment_lent:
fer_pregunta("La CPU és al 100% (Administrador de tasques)?", "cpu_alta")
fer_pregunta("La memòria RAM és gairebé plena?", "ram_plena")
fer_pregunta("El disc dur té menys del 10% lliure?", "disc_dur_ple")
fer_pregunta("L'equip té més de 6 anys?", "edat_equip_anys")
print("\nPROBLEMES DE XARXA:")
sense_internet = fer_pregunta("No teniu connexió a internet?", "sense_internet")
if sense_internet:
wifi_connectat = fer_pregunta("L'equip apareix connectat al Wi-Fi?", "wifi_connectat")
if wifi_connectat:
fer_pregunta("Podeu fer ping al router (192.168.1.1)?", "ping_router")
print("\nPROBLEMES DE SO:")
sense_so = fer_pregunta("No teniu so?", "sense_so")
if sense_so:
fer_pregunta("Els altaveus o auriculars estan connectats?", "altaveus_connectats")
fer_pregunta("El volum del sistema és silenciat?", "volum_silenciat")
print("\nCOMPORTAMENT ESTRANY:")
comportament = fer_pregunta("L'ordinador té un comportament estrany o inusual?", "comportament_estrany")
if comportament:
fer_pregunta("Apareixen finestres emergents (pop-ups) aleatòries?", "finestres_popup")
print("\nSOBREESCALFAMENT:")
s_apaga = fer_pregunta("L'equip s'apaga sol de manera inesperada?", "equip_s_apaga_sol")
if s_apaga:
fer_pregunta("El ventilador fa molt soroll?", "soroll_ventilador")
4.2. Funció principal main
def main():
"""Funció principal: bucle d'interacció del sistema expert."""
# Capçalera del sistema
print("╔══════════════════════════════════════════════════════════╗")
print("║ SISTEMA EXPERT DE DIAGNÒSTIC INFORMÀTIC ║")
print("║ Institut Sa Palomera — Mòdul 5071 — RA5 ║")
print("╚══════════════════════════════════════════════════════════╝")
expert = SistemaExpert()
print(f"\nBenvingut al {expert.nom_expert} v{expert.versio}")
print(f"Domini: {expert.domini}")
print(f"Base de coneixement: {len(expert.regles)} regles carregades")
continuar = True
while continuar:
print("\n" + "─"*60)
print("OPCIONS:")
print(" 1. Iniciar nou diagnòstic")
print(" 2. Sortir")
opcio = input("\nTrieu una opció [1/2]: ").strip()
if opcio == "2":
print(f"\nGràcies per usar {expert.nom_expert}. Fins aviat!")
break
if opcio != "1":
print("Opció no vàlida.")
continue
# Reiniciar per a nova sessió
expert.reiniciar()
# Recollir símptomes
preguntar_simptomes(expert)
print(f"\n[{expert.nom_expert}] Processant el diagnòstic...")
print(f"Fets recopilats: {len(expert.fets)}")
# Inferència
conclusions = expert.inferir()
if not conclusions:
print("\n⚠️ No s'ha pogut derivar cap diagnòstic amb la informació proporcionada.")
print(" Reviseu les vostres respostes o consulteu un tècnic.")
else:
# Mostrar conclusions finals
conclusions_finals = expert.obtenir_conclusions_finals()
print("\n" + "="*60)
print("DIAGNÒSTIC I RECOMANACIONS")
print("="*60)
if conclusions_finals:
for i, c in enumerate(conclusions_finals, 1):
print(f"\n{i}. {c['conclusio'].upper().replace('_', ' ')}")
print(f" Confiança: {c['confiança']*100:.0f}%")
print(f" {c['explicacio']}")
else:
# Si totes les conclusions son intermedies, mostrar les ultimes
for i, c in enumerate(conclusions[-3:], 1):
print(f"\n{i}. {c['conclusio'].upper().replace('_', ' ')}")
print(f" Confiança: {c['confiança']*100:.0f}%")
print(f" {c['explicacio']}")
# Mostrar explicació detallada si l'usuari ho vol
veure_explicacio = input(
"\nVoleu veure l'explicació detallada del raonament? [S/N]: "
).strip().upper()
if veure_explicacio in ("S", "SI", "SÍ", "Y"):
print(expert.generar_explicacio())
continuar_resposta = input("\nVoleu fer un altre diagnòstic? [S/N]: ").strip().upper()
continuar = continuar_resposta in ("S", "SI", "SÍ", "Y")
# Punt d'entrada del programa
if __name__ == "__main__":
main()
Part 5: Execució amb Docker
5.1. Preparació de l'entorn
Creeu un fitxer requirements.txt al directori del projecte:
5.2. Execució del sistema expert
# Executar el sistema expert en un contenidor Docker
# Nota: -it per a interaccio amb l'usuari, --rm per eliminar el contenidor en acabar
docker run --name expert-joan-garcia \
-v $(pwd):/app \
-w /app \
-it \
--rm \
python:3.11-slim \
python sistema_expert_joan_garcia.py
5.3. Execució amb dependències (scikit-fuzzy)
Si heu implementat la part opcional de lògica difusa:
# Instal·lar dependencies i executar en un sol pas
docker run --name expert-fuzzy-joan-garcia \
-v $(pwd):/app \
-w /app \
-it \
--rm \
python:3.11-slim \
bash -c "pip install -r requirements.txt -q && python sistema_expert_joan_garcia.py"
5.4. Provar el sistema sense interacció (per a automatització)
Per a proves automatitzades, podeu redirigir l'entrada:
# Crear un fitxer de respostes de prova
cat > respostes_prova.txt << EOF
S
S
S
N
N
N
N
N
N
N
N
N
N
N
N
N
N
N
N
N
N
EOF
# Executar amb les respostes predefinides
docker run --name expert-test-joan-garcia \
-v $(pwd):/app \
-w /app \
--rm \
python:3.11-slim \
bash -c "python sistema_expert_joan_garcia.py < respostes_prova.txt"
Part 6 (Opcional): Lògica Difusa amb scikit-fuzzy
La lògica difusa permet treballar amb valors intermedis (no tot és vertader o fals) i és especialment útil per modelar conceptes com "rendiment lleugerament lent" o "temperatura moderadament alta".
# extensio_logica_difusa.py
# Extensió opcional del sistema expert amb lògica difusa
# Avalua el nivel de risc de sobreescalfament
import numpy as np
import skfuzzy as fuzz
from skfuzzy import control as ctrl
import matplotlib
matplotlib.use('Agg') # Per a Docker sense entorn grafic
import matplotlib.pyplot as plt
def crear_sistema_difus_temperatura():
"""
Crea un sistema difús per avaluar el risc de sobreescalfament
basant-se en la temperatura de la CPU i la velocitat del ventilador.
"""
# Variables d'entrada (universos de discurs)
temperatura = ctrl.Antecedent(np.arange(0, 101, 1), 'temperatura_cpu')
velocitat_ventilador = ctrl.Antecedent(np.arange(0, 101, 1), 'velocitat_ventilador')
# Variable de sortida
risc = ctrl.Consequent(np.arange(0, 101, 1), 'risc_sobreescalfament')
# Funcions de pertinença per a temperatura de CPU
temperatura['baixa'] = fuzz.trimf(temperatura.universe, [0, 0, 45])
temperatura['normal'] = fuzz.trimf(temperatura.universe, [35, 55, 75])
temperatura['alta'] = fuzz.trimf(temperatura.universe, [65, 80, 90])
temperatura['critica'] = fuzz.trimf(temperatura.universe, [80, 95, 100])
# Funcions de pertinença per a velocitat del ventilador (%)
velocitat_ventilador['lenta'] = fuzz.trimf(velocitat_ventilador.universe, [0, 0, 40])
velocitat_ventilador['normal'] = fuzz.trimf(velocitat_ventilador.universe, [30, 60, 80])
velocitat_ventilador['alta'] = fuzz.trimf(velocitat_ventilador.universe, [70, 90, 100])
# Funcions de pertinença per al risc
risc['baix'] = fuzz.trimf(risc.universe, [0, 0, 40])
risc['moderat'] = fuzz.trimf(risc.universe, [20, 50, 80])
risc['alt'] = fuzz.trimf(risc.universe, [60, 80, 100])
risc['critic'] = fuzz.trimf(risc.universe, [80, 100, 100])
# Regles difuses
regles_difuses = [
ctrl.Rule(temperatura['baixa'] & velocitat_ventilador['lenta'], risc['baix']),
ctrl.Rule(temperatura['normal'] & velocitat_ventilador['normal'], risc['baix']),
ctrl.Rule(temperatura['alta'] & velocitat_ventilador['lenta'], risc['alt']),
ctrl.Rule(temperatura['alta'] & velocitat_ventilador['normal'], risc['moderat']),
ctrl.Rule(temperatura['alta'] & velocitat_ventilador['alta'], risc['moderat']),
ctrl.Rule(temperatura['critica'] & velocitat_ventilador['lenta'], risc['critic']),
ctrl.Rule(temperatura['critica'] & velocitat_ventilador['normal'], risc['alt']),
ctrl.Rule(temperatura['critica'] & velocitat_ventilador['alta'], risc['alt']),
]
# Crear i simular el sistema de control
sistema_control = ctrl.ControlSystem(regles_difuses)
simulador = ctrl.ControlSystemSimulation(sistema_control)
return simulador, temperatura, velocitat_ventilador, risc
def avaluar_sobreescalfament(temp_cpu: float, vel_ventilador: float) -> dict:
"""
Avalua el risc de sobreescalfament amb lògica difusa.
Args:
temp_cpu: Temperatura de la CPU en graus Celsius (0-100)
vel_ventilador: Velocitat del ventilador en percentatge (0-100)
Returns:
Diccionari amb el risc numèric i la categoria lingüística
"""
simulador, _, _, _ = crear_sistema_difus_temperatura()
simulador.input['temperatura_cpu'] = temp_cpu
simulador.input['velocitat_ventilador'] = vel_ventilador
simulador.compute()
risc_numeric = simulador.output['risc_sobreescalfament']
if risc_numeric < 25:
categoria = "BAIX"
recomanacio = "El sistema funciona dins els paràmetres normals."
elif risc_numeric < 50:
categoria = "MODERAT"
recomanacio = "Considereu millorar la ventilació del gabinet."
elif risc_numeric < 75:
categoria = "ALT"
recomanacio = "Netegeu els ventiladors i apliqueu pasta tèrmica nova."
else:
categoria = "CRÍTIC"
recomanacio = "ATUREU l'equip immediatament per evitar danys permanents."
return {
"temperatura_cpu": temp_cpu,
"velocitat_ventilador": vel_ventilador,
"risc_numeric": round(risc_numeric, 1),
"categoria": categoria,
"recomanacio": recomanacio
}
if __name__ == "__main__":
# Demostració del sistema difús
print("EXTENSIÓ DE LÒGICA DIFUSA — Avaluació de sobreescalfament")
print("="*60)
casos_prova = [
(45, 30), # Temperatura i ventilador moderats
(80, 20), # Temperatura alta, ventilador lent
(95, 90), # Temperatura critica, ventilador rapid
(60, 60), # Cas intermedi
]
for temp, vel in casos_prova:
resultat = avaluar_sobreescalfament(temp, vel)
print(f"\nCPU: {resultat['temperatura_cpu']}°C | "
f"Ventilador: {resultat['velocitat_ventilador']}%")
print(f" Risc: {resultat['risc_numeric']}/100 ({resultat['categoria']})")
print(f" {resultat['recomanacio']}")
Preguntes de Reflexió
Reflexió final
-
Avantatges de l'explicabilitat: En quin aspecte és el vostre sistema expert més transparent que un model de ML com Random Forest o una xarxa neuronal? Doneu un exemple concret de com el vostre sistema pot justificar la seva recomanació.
-
Limitacions del forward chaining: Quina situació podria fer que el motor d'inferència no convergís o trigués molt a convergir? Com podríeu optimitzar el codi per evitar-ho?
-
Manteniment de la base de coneixement: Qui hauria de mantenir i actualitzar les regles del sistema expert en un context professional real? Quin procés s'hauria de seguir per afegir una nova regla i garantir que no contradigui les existents?
-
Comparativa amb ML: Per a quin dels següents escenaris usaríeu un sistema expert vs. un model de ML, i per quin motiu? (a) Detecció de frau bancari, (b) Classificació d'imatges mèdiques, (c) Aprovació/denegació de crèdits hipotecaris, (d) Recomanació de pel·lícules.
-
Factors de confiança: El vostre sistema usa factors de confiança simples (números entre 0 i 1). Com es podrien combinar les confiances de múltiples regles encadenades per obtenir un factor de confiança global de la conclusió final? Implementeu una proposta de millora.
Lliurament
Estructura del fitxer a lliurar
# Comprimir el projecte
zip -r PR5071/02_joan_garcia.zip \
sistema_expert_joan_garcia.py \
extensio_logica_difusa.py \
requirements.txt \
README_joan_garcia.md \
captures/
El fitxer comprimit ha de dir-se PR5071/02_[nom]_[cognoms].zip (exemple: PR5071/02_joan_garcia.zip).
Contingut mínim
- Fitxer Python principal amb el nom de l'alumne (
sistema_expert_joan_garcia.py) - Mínim 20 regles a la base de coneixement, amb factors de confiança i explicacions
- Motor d'inferència forward chaining implementat i funcional
- Interfície d'usuari interactiva per consola
- Mètode
generar_explicacio()que mostra el raonament pas a pas - Evidència de l'execució amb Docker (captures de pantalla)
-
README_joan_garcia.mdamb instruccions d'execució i respostes a les preguntes de reflexió
Requisits de personalització
- El nom de la classe ha de ser
SistemaExpertperò l'atributnom_expertha de contenir el vostre nom:"SistemaExpert_Joan_Garcia" - El contenidor Docker ha d'incloure el vostre nom (
--name expert-joan-garcia) - El fitxer Python ha de portar el vostre nom (
sistema_expert_joan_garcia.py)
Rúbrica
Consulteu la rúbrica completa a Rúbrica PR5071/02 per als criteris detallats d'avaluació.