A 06-01 vam construir un motor d'encadenament de deu línies i vam deixar dos problemes oberts: dues regles que xoquen (la garantia davant de "usat i més de 14 dies") i un raspall d'higiene obert que el motor reemborsava i denegava alhora. Resoldre'ls exigeix més que lògica: cal decidir quina regla guanya, recordar què se sap del cas, explicar cada conclusió, preguntar a l'usuari el que falta i, abans de tot això, treure les regles del cap del Diego i comprovar-les. Aquest conjunt de peces és un sistema expert, la tecnologia que als anys 80 va portar la IA a la indústria (01-01) i que avui sobreviu, rebatejada, en els motors de regles de negoci de bancs, asseguradores i botigues en línia. En aquesta lliçó veurem què és un sistema expert i d'on ve, la seva arquitectura peça a peça (base de coneixement, motor d'inferència amb resolució de conflictes, memòria de treball, explicació, adquisició del coneixement i interfície), com se'n construeix un (enginyeria del coneixement amb l'expert, que a NovaMarket és el Diego), els seus avantatges i límits, i com es compara amb un model d'ML. I construirem en Python pur un sistema expert de devolucions i garanties (cas 8) amb prioritats, traça d'explicació i una interfície que pregunta el que necessita. És important perquè és el patró que faràs servir cada vegada que una decisió hagi de ser explicable, auditable i modificable sense reentrenar.
Contingut
- Què és un sistema expert i d'on ve
- Arquitectura d'un sistema expert
- El motor d'inferència: encadenament i resolució de conflictes
- Explicació: per què? i com?
- Construir un sistema expert: enginyeria del coneixement
- Avantatges i desavantatges; comparació amb un model d'ML
- Codi: sistema expert de devolucions i garanties de NovaMarket
- Eines reals
- Errors Comuns i Consells
- Exercicis
- Conclusió
- Què és un sistema expert i d'on ve
Un sistema expert és un programa que resol problemes d'un domini concret al nivell d'un expert humà, fent servir coneixement explícit (fets i regles escrits per persones) i un mecanisme de raonament separat d'aquest coneixement. La separació és la idea clau: el motor no sap res de devolucions; les regles no saben res de com s'executen. Canviar la política és canviar regles, no codi.
Les tres fites històriques que ja vam citar a 01-01, amb el que cadascuna va aportar:
- DENDRAL (Stanford, 1965-): identificava estructures moleculars a partir d'espectres de masses. Va demostrar que el coneixement específic del domini, i no un raonador general, era el que donava resultats: "en el coneixement hi ha el poder" (Feigenbaum).
- MYCIN (Stanford, anys 70): diagnòstic d'infeccions bacterianes i recomanació d'antibiòtics amb unes 600 regles SI-ALESHORES, encadenament cap enrere que preguntava al metge, un mòdul que explicava "per què" preguntava i "com" havia arribat a la conclusió, i factors de certesa per a la incertesa (06-03). El seu motor buit, EMYCIN, va ser la primera shell reutilitzable.
- XCON / R1 (Digital Equipment Corporation, 1980): configurava comandes d'ordinadors VAX amb milers de regles d'encadenament cap endavant; va estalviar desenes de milions de dòlars l'any i va disparar l'auge comercial. Els seus problemes de manteniment (més de 10.000 regles cap al 1988, amb equips sencers dedicats a mantenir-les) van anticipar l'hivern.
El que NovaMarket vol per a les devolucions és més a prop de XCON (regles de negoci aplicades a cada comanda) que de MYCIN, però prendrà de MYCIN l'explicació i les preguntes a l'usuari.
- Arquitectura d'un sistema expert
flowchart LR
subgraph SE[Sistema expert de devolucions]
BC[(Base de coneixement<br/>fets generals + regles R1..R8)]
MT[(Memòria de treball<br/>fets del cas: comanda 47102)]
MI[Motor d'inferència<br/>encadenament + resolució de conflictes]
EX[Mòdul d'explicació<br/>per què? / com?]
AQ[Mòdul d'adquisició<br/>del coneixement]
end
UI[Interfície d'usuari<br/>agent d'atenció al client]
D[Diego, expert<br/>en devolucions]
IC[Enginyer/a del coneixement<br/>Marta]
UI <--> MI
UI <--> EX
MI <--> BC
MI <--> MT
EX --> MT
EX --> BC
D --> IC --> AQ --> BC
Les peces, una a una:
| Component | Què conté / fa | A NovaMarket |
|---|---|---|
| Base de coneixement | Coneixement permanent del domini: regles SI-ALESHORES i fets generals. No canvia d'un cas a l'altre | Les regles de devolucions i garanties: termini 14 dies, garantia 3 anys, excepcions d'higiene/precintats, enviament gratuït si és defectuós |
| Memòria de treball | Fets del cas actual: les dades d'entrada, les respostes de l'usuari i les conclusions intermèdies. Es buida a cada consulta | Comanda 47102: 40 dies, usat, defectuós; després decisio = garantia |
| Motor d'inferència | Aplica les regles a la memòria de treball (encadenament cap endavant o enrere, 06-01) i decideix quina regla disparar quan diverses són aplicables (resolució de conflictes) | La nostra classe MotorInferencia |
| Mòdul d'explicació | Respon "per què em preguntes això?" (quina regla intenta aplicar) i "com has arribat a aquesta conclusió?" (cadena de regles i fets) | La traça "s'ha aplicat R3 perquè..." |
| Mòdul d'adquisició del coneixement | Eines per introduir, editar i validar regles sense programar (editors, comprovació de consistència, proves amb casos) | Un full de càlcul o formulari on el Diego edita regles i veu quins casos de prova canvien |
| Interfície d'usuari | Recull les dades, formula preguntes, mostra la decisió i l'explicació | La pantalla de l'agent d'atenció al client o l'assistent del cas 7 |
Fixa't en la línia que separa la base de coneixement de la memòria de treball: és la mateixa que a 06-01 separava REGLES (general) de fets_de(comanda) (particular). I en el fet que l'expert (el Diego) no toca directament la base: ho fa a través de l'enginyera del coneixement (la Marta) i del mòdul d'adquisició.
- El motor d'inferència: encadenament i resolució de conflictes
El motor repeteix un cicle de reconeixement-acció:
- Aparellar: trobar totes les regles les condicions de les quals es compleixen amb la memòria de treball actual. El resultat és el conjunt de conflicte.
- Resoldre el conflicte: triar una regla del conjunt (si n'hi ha diverses).
- Actuar: disparar la regla triada: afegir les seves conclusions a la memòria de treball (o preguntar, o executar una acció).
- Tornar a 1 fins que no hi hagi regles aplicables o s'assoleixi l'objectiu.
A 06-01 el pas 2 no existia: les disparàvem totes en ordre d'escriptura, i d'aquí venien les incoherències. Les estratègies clàssiques de resolució de conflictes, que se solen combinar en aquest ordre:
| Estratègia | Criteri | Exemple en devolucions |
|---|---|---|
| Refractarietat | Una regla no es dispara dues vegades sobre els mateixos fets | Evita bucles infinits |
| Prioritat (salience) | Guanya la regla amb més prioritat, assignada per l'expert | La garantia (R1, prioritat 10) guanya la regla del Diego (R8, prioritat 2): un producte defectuós s'atén encara que hagin passat 14 dies |
| Especificitat | Guanya la regla amb més condicions (més específica): les excepcions són més específiques que la regla general | "Dins del termini, sense usar, amb etiqueta i amb 5+ devolucions prèvies → revisió humana" (R5) guanya "dins del termini, sense usar, amb etiqueta → reemborsament" (R4) |
| Recència | Guanya la regla que fa servir els fets més recents: segueix el fil del raonament | Després que l'usuari respongui una pregunta, es prefereixen les regles que la fan servir |
| Ordre | La primera escrita | Últim recurs; fràgil |
A més hi ha una decisió de disseny sobre quan aturar-se: derivar tot el que és derivable (útil per calcular totes les conseqüències d'una comanda) o aturar-se en fixar l'objectiu (decisio), que és el que farem: un cop la regla guanyadora ha fixat la decisió, les de menys prioritat ja no la poden contradir. És la manera més simple que "guanyi" la regla correcta sense negacions a les premisses.
- Explicació: per què? i com?
És la funcionalitat que distingeix un sistema expert d'un simple if gegant i la raó per la qual aquest mòdul existeix després de 05-05. MYCIN responia a dues preguntes, i nosaltres implementarem totes dues:
- Per què? (durant la consulta): "per què em preguntes si el producte mostra senyals d'ús?" → "perquè intento aplicar R5, que necessita saber si està usat". Dona context a l'usuari i li permet detectar preguntes absurdes (símptoma de regles mal escrites).
- Com? (al final): "com has decidit que procedeix garantia?" → "s'ha aplicat R1 perquè defectuós = True, producte nou = True i 40 dies ≤ 1095; R1 diu: garantia legal de 3 anys; l'enviament el paga NovaMarket". És la justificació que exigeix el client, l'auditor i, a 02-04, el RGPD per a decisions automatitzades.
Tècnicament, l'explicació s'obté gairebé gratis si el motor registra per a cada fet de la memòria de treball qui l'hi va posar (dada d'entrada, resposta de l'usuari o nom de regla) i desa una traça de les regles disparades amb els valors concrets que en satisfeien les condicions. Amb això, "per què?" és seguir la cadena d'orígens cap enrere.
- Construir un sistema expert: enginyeria del coneixement
Un sistema expert no s'entrena: es construeix, i el procés s'anomena enginyeria del coneixement. Amb NovaMarket com a exemple:
- Triar el domini i l'abast. Estret, ben definit, amb un expert disponible i decisions repetitives: les devolucions i garanties (cas 8) ho compleixen; "atenció al client en general" no.
- Adquisició del coneixement: entrevistes a l'expert. La Marta s'asseu amb el Diego. Tècniques: preguntes obertes ("com decideixes si procedeix?"), casos concrets ("i aquesta comanda de la setmana passada?"), think aloud (el Diego resol casos en veu alta), revisió de documents (política de devolucions, normativa de consum, la qual, hi insistim, ha de revisar un professional legal). És la fase més lenta: els experts saben més del que saben dir (coll d'ampolla de l'adquisició), obliden les excepcions fins que veuen un cas, i de vegades es contradiuen entre sessions.
- Formalitzar: extreure regles. Convertir el que s'ha dit en condicions i conclusions precises, amb noms d'atributs definits en un diccionari ("usat" vol dir "amb senyals d'ús" o "desprecintat"? El Diego distingeix les dues coses i el sistema també ho ha de fer). Assignar prioritats i justificacions. Detectar regles incompletes ("i si està usat però és defectuós?").
- Validar amb casos. Un conjunt de casos de prova amb la decisió que el Diego considera correcta (20-50 casos reals de l'històric de devolucions), que s'executa cada vegada que canvia una regla: l'equivalent del conjunt de test de 04-05. Buscar regles que no es disparen mai, casos sense decisió, i decisions que el Diego no signaria.
- Mantenir. Les polítiques canvien (una campanya amb 30 dies de devolució per Nadal, una categoria nova), la normativa canvia i apareixen casos nous. Cada canvi passa pel mòdul d'adquisició i per la bateria de casos. XCON va morir d'això: milers de regles sense propietari clar. La recepta: regles poques, anomenades, amb justificació escrita i propietari (el Diego és el propietari de les regles de devolucions, com vam acordar a 02-03 per a les dades).
Una observació que connecta amb el mòdul 4: el pas 4 s'assembla a avaluar un model, i el pas 3 s'assembla al que fa un arbre de decisió, però a l'inrevés: allà l'algorisme troba les regles a comandes.csv; aquí les dicta l'expert. I hi ha un pont evident: l'export_text de l'arbre de 04-04 produeix regles llegibles que poden servir d'esborrany per a l'entrevista amb el Diego ("l'arbre diu que les devolucions amb més de 3 prèvies i menys de 5 dies són sospitoses; et quadra?").
- Avantatges i desavantatges; comparació amb un model d'ML
| Avantatges | Desavantatges |
|---|---|
| Explicable per construcció: cada decisió té la seva cadena de regles | Coll d'ampolla de l'adquisició: extreure i formalitzar el coneixement és lent i car |
| Modificable sense reentrenar: canviar una regla és un canvi de política, no de codi | Fràgil fora del domini: no sap res del que no és a les regles; no degrada amb elegància |
| No necessita dades històriques: funciona des del primer dia i amb casos rars | Manteniment difícil en créixer: interaccions entre centenars de regles, prioritats embolicades |
| Consistent: la mateixa entrada, la mateixa sortida, sempre | Incertesa mal suportada en la seva forma pura (06-03) |
| Auditable i conforme amb la normativa (AI Act, RGPD, 02-04) | No aprèn de l'experiència llevat que algú reescrigui regles |
| Conserva i difon el coneixement de l'expert (si el Diego se'n va, les regles es queden) | Codifica també els biaixos i errors de l'expert |
Davant d'un model d'ML (l'arbre o la logística de 04-04):
| Aspecte | Sistema expert (regles escrites) | Model d'ML (regles apreses) |
|---|---|---|
| Origen del coneixement | Un expert i la normativa | Dades històriques etiquetades |
| Necessita dades | No (sí casos de prova) | Sí, moltes i representatives |
| Explicabilitat | Total | De l'arbre sí; de la xarxa no |
| Canviar la política | Editar una regla | Recollir dades noves i reentrenar |
| Casos rars i excepcions | S'escriuen una vegada | Mal coberts si són rars a les dades |
| Patrons subtils en moltes dades | No els veu | El seu punt fort |
| Incertesa | Afegida (06-03) | Nativa: probabilitats |
| Exemple NovaMarket | Devolucions i garanties (cas 8), polítiques fixades per normativa | Risc de frau (cas 3), demanda (cas 2), ressenyes (cas 4) |
Quan cadascun: regles quan la decisió està definida per normativa o política, s'ha de justificar una a una, hi ha poques dades o els casos límit importen; ML quan la relació entrada-sortida no la sap escriure ningú però és a les dades. I molt sovint tots dos: l'ML estima (probabilitat de frau, de defecte) i les regles decideixen amb aquesta estimació com un fet més. És la combinació neurosimbòlica que va anunciar 05-05 i que tancarà el mòdul a 06-04.
- Codi: sistema expert de devolucions i garanties de NovaMarket
Construïm les peces de la secció 2 en Python pur. Els fets de la memòria de treball són parells atribut-valor (dies_des_lliurament = 40, usat = True), i les condicions d'una regla són tuples (atribut, operador, valor), llegibles i per tant explicables. Dades de negoci: desistiment 14 dies; garantia legal 3 anys (1095 dies) per a producte nou; higiene i precintats no retornables si s'obren; etiqueta original; enviament de devolució gratuït si és defectuós.
7.1 Base de coneixement, regles i memòria de treball
import operator
OPERADORS = {"==": operator.eq, "!=": operator.ne, "<=": operator.le, "<": operator.lt,
">=": operator.ge, ">": operator.gt, "in": lambda a, b: a in b}
class Regla:
"""Regla SI condicions ALESHORES conclusions, amb prioritat i text de justificació."""
def __init__(self, nom, condicions, conclusions, prioritat=0, justificacio=""):
self.nom = nom
self.condicions = condicions # llista de (atribut, operador, valor)
self.conclusions = conclusions # dict atribut -> valor
self.prioritat = prioritat
self.justificacio = justificacio
def avaluar(self, memoria):
"""True si totes les condicions es compleixen, False si alguna falla,
None si falta algun fet per decidir."""
resultat = True
for atribut, op, valor in self.condicions:
if atribut not in memoria.fets:
resultat = None # encara no ho sabem: continuem per si una altra falla
continue
if not OPERADORS[op](memoria.fets[atribut], valor):
return False
return resultat
def fets_que_falten(self, memoria):
return [a for a, _, _ in self.condicions if a not in memoria.fets]
def __repr__(self):
cond = " ∧ ".join(f"{a} {op} {v!r}" for a, op, v in self.condicions)
return f"{self.nom} [prio {self.prioritat}]: SI {cond} ALESHORES {self.conclusions}"
class BaseConeixement:
def __init__(self):
self.regles = []
def afegir(self, regla):
self.regles.append(regla)
def regles_que_conclouen(self, atribut):
return [r for r in self.regles if atribut in r.conclusions]
class MemoriaTreball:
"""Fets del cas actual + registre de qui els ha posat i quan."""
def __init__(self, fets_inicials=None):
self.fets = {}
self.origen = {} # atribut -> "entrada" | "usuari" | nom de regla
self.rellotge = 0
self.temps = {} # atribut -> instant en què s'ha afirmat (per a la recència)
for k, v in (fets_inicials or {}).items():
self.afirmar(k, v, "entrada")
def afirmar(self, atribut, valor, origen):
self.rellotge += 1
self.fets[atribut] = valor
self.origen[atribut] = origen
self.temps[atribut] = self.rellotge
def recencia(self, regla):
return max((self.temps.get(a, 0) for a, _, _ in regla.condicions), default=0)Explicació: Regla.avaluar distingeix tres resultats, no dos: vertader, fals i "no se sap" (None) quan falta un fet; aquest tercer valor és el que permetrà a la interfície preguntar exactament el que falta. MemoriaTreball desa, a més del valor de cada fet, el seu origen (dada d'entrada, resposta de l'usuari o regla que l'ha deduït) i l'instant en què s'ha afirmat: l'origen alimenta l'explicació i l'instant, la recència.
7.2 Motor d'inferència amb resolució de conflictes i explicació
class MotorInferencia:
def __init__(self, base, memoria):
self.base = base
self.memoria = memoria
self.disparades = [] # noms de regles ja aplicades (refractarietat)
self.traca = [] # explicació pas a pas
# ---- encadenament cap endavant amb resolució de conflictes ----
def conjunt_conflicte(self):
return [r for r in self.base.regles
if r.nom not in self.disparades and r.avaluar(self.memoria) is True]
def triar(self, candidates):
"""Prioritat > especificitat (nre. de condicions) > recència dels fets usats."""
return max(candidates, key=lambda r: (r.prioritat, len(r.condicions),
self.memoria.recencia(r)))
def disparar(self, regla):
detall = ", ".join(f"{a}={self.memoria.fets[a]!r} ({op} {v!r})"
for a, op, v in regla.condicions)
for atribut, valor in regla.conclusions.items():
self.memoria.afirmar(atribut, valor, regla.nom)
self.disparades.append(regla.nom)
self.traca.append(f"S'ha aplicat {regla.nom} perquè {detall} "
f"→ {regla.conclusions}. Justificació: {regla.justificacio}")
def executar(self, objectiu="decisio"):
while objectiu not in self.memoria.fets:
candidates = self.conjunt_conflicte()
if not candidates:
self.traca.append(f"Cap regla aplicable i '{objectiu}' sense fixar")
return None
if len(candidates) > 1:
self.traca.append("Conflicte entre " + ", ".join(r.nom for r in candidates)
+ f"; guanya {self.triar(candidates).nom}")
self.disparar(self.triar(candidates))
return self.memoria.fets[objectiu]
# ---- mòdul d'explicació ----
def per_que(self, atribut):
origen = self.memoria.origen.get(atribut)
if origen is None:
return f"'{atribut}' no és a la memòria de treball"
if origen in ("entrada", "usuari"):
return f"'{atribut}' = {self.memoria.fets[atribut]!r} és una dada d'{origen}"
regla = next(r for r in self.base.regles if r.nom == origen)
return (f"'{atribut}' = {self.memoria.fets[atribut]!r} l'ha fixat {regla.nom} "
f"({regla.justificacio}); les seves condicions: "
+ "; ".join(self.per_que(a) for a, _, _ in regla.condicions))
def com(self):
return "\n".join(f" {i+1}. {pas}" for i, pas in enumerate(self.traca))
# ---- interfície de consulta dirigida per objectiu (cap enrere simplificat) ----
def consultar(self, objectiu, preguntes, respondre):
"""Recorre les regles que conclouen l'objectiu per prioritat; si a una li
falten fets preguntables, els demana; dispara la primera que es compleix."""
for regla in sorted(self.base.regles_que_conclouen(objectiu),
key=lambda r: (-r.prioritat, -len(r.condicions))):
for atribut in regla.fets_que_falten(self.memoria):
if regla.avaluar(self.memoria) is False:
break # ja sabem que no aplica: no preguntar més
if atribut in preguntes:
valor = respondre(preguntes[atribut], regla)
self.memoria.afirmar(atribut, valor, "usuari")
if regla.avaluar(self.memoria) is True:
self.disparar(regla)
return self.memoria.fets[objectiu]
return NoneExplicació:
conjunt_conflicteés el pas "aparellar" del cicle: regles no disparades encara (refractarietat) les condicions de les quals són totes certes.triarés la resolució de conflictes en una línia:maxamb una clau composta (prioritat, nombre de condicions, recència), que aplica els criteris en aquest ordre.dispararafirma les conclusions a la memòria (amb la regla com a origen) i escriu a la traça una frase completa amb els valors reals que satisfeien cada condició: la matèria primera del "com?".executarrepeteix el cicle fins a fixar l'objectiu (decisio) o quedar-se sense regles. Quan hi ha més d'una candidata, ho deixa anotat a la traça: veurem "Conflicte entre R3, R4; guanya R3".per_quesegueix la cadena d'orígens: si el fet l'ha fixat una regla, explica la regla i, recursivament, cadascuna de les seves condicions.comretorna la traça numerada.consultarés l'encadenament cap enrere simplificat dirigit per objectiu: pren les regles que conclouendecisioordenades com les ordenaria el motor (prioritat, especificitat), i per a cadascuna pregunta només els fets que li falten, aturant-se així que una condició ja coneguda falla (per no preguntar coses inútils). La primera regla que resulta certa es dispara.respondre(pregunta, regla)és una funció que se li passa: en producció llegirà del teclat o d'un formulari; a la lliçó la simularem per poder executar sense teclat, i li passem la regla perquè pugui contestar "per què?".
7.3 Les regles de devolucions i garanties
def base_devolucions():
kb = BaseConeixement()
kb.afegir(Regla("R1", [("defectuos", "==", True), ("producte_nou", "==", True),
("dies_des_lliurament", "<=", 1095)],
{"decisio": "garantia", "enviament_devolucio": "gratuit"}, prioritat=10,
justificacio="garantia legal de 3 anys del producte nou; l'enviament el paga NovaMarket"))
kb.afegir(Regla("R2", [("defectuos", "==", True), ("dies_des_lliurament", ">", 1095)],
{"decisio": "denegada", "motiu": "fora del termini de garantia legal"}, prioritat=9,
justificacio="passats 3 anys s'ofereix servei tècnic de pagament"))
kb.afegir(Regla("R3", [("categoria", "in", ("higiene", "precintat")), ("obert", "==", True)],
{"decisio": "denegada", "motiu": "producte d'higiene o precintat obert"}, prioritat=8,
justificacio="excepció al desistiment: precintats i higiene no es retornen oberts"))
kb.afegir(Regla("R4", [("dies_des_lliurament", "<=", 14), ("usat", "==", False),
("etiqueta_original", "==", True)],
{"decisio": "reemborsament_desistiment", "enviament_devolucio": "a carrec del client"}, prioritat=5,
justificacio="dret de desistiment de 14 dies amb producte sense usar i etiquetat"))
kb.afegir(Regla("R5", [("dies_des_lliurament", "<=", 14), ("usat", "==", False),
("etiqueta_original", "==", True), ("devolucions_previes", ">=", 5)],
{"decisio": "revisio_humana", "motiu": "patró de devolucions freqüents"}, prioritat=5,
justificacio="més específica que R4: el cas 3 (frau) demana una mirada humana"))
kb.afegir(Regla("R6", [("dies_des_lliurament", "<=", 14), ("usat", "==", True)],
{"decisio": "revisio_humana", "motiu": "producte usat dins del termini: valorar depreciació"},
prioritat=4, justificacio="una persona decideix si procedeix reemborsament total o parcial"))
kb.afegir(Regla("R7", [("dies_des_lliurament", "<=", 14), ("etiqueta_original", "==", False)],
{"decisio": "revisio_humana", "motiu": "falta l'etiqueta original"}, prioritat=4,
justificacio="la política exigeix etiqueta; una persona valora l'excepció"))
kb.afegir(Regla("R8", [("dies_des_lliurament", ">", 14), ("defectuos", "==", False)],
{"decisio": "denegada", "motiu": "fora del termini de desistiment i sense defecte"}, prioritat=2,
justificacio="la regla del Diego: usat o no, passats 14 dies sense defecte no procedeix"))
return kbLlegeix les prioritats com la jerarquia de la política: primer la garantia (R1, R2: un defecte s'atén amb independència del desistiment, i així es resol el xoc de 06-01), després les excepcions al desistiment (R3), després el desistiment ordinari (R4) i les seves variants que demanen revisió humana (R5-R7, en línia amb el human-in-the-loop de 02-04: la denegació o el reemborsament parcial d'un producte usat no s'automatitzen), i per últim la regla del Diego (R8). Nota que R5 i R4 tenen la mateixa prioritat: la decidirà l'especificitat.
7.4 Casos de prova
CASOS = {
"A cafetera NovaBrew": dict(dies_des_lliurament=9, usat=False, etiqueta_original=True, defectuos=False, producte_nou=True, categoria="general", obert=True, devolucions_previes=0),
"B aspirador NovaClean": dict(dies_des_lliurament=40, usat=True, etiqueta_original=False, defectuos=True, producte_nou=True, categoria="general", obert=True, devolucions_previes=1),
"C raspall NovaSmile": dict(dies_des_lliurament=5, usat=False, etiqueta_original=True, defectuos=False, producte_nou=True, categoria="higiene", obert=True, devolucions_previes=0),
"D auriculars NovaSound": dict(dies_des_lliurament=20, usat=True, etiqueta_original=True, defectuos=False, producte_nou=True, categoria="general", obert=True, devolucions_previes=2),
"E monitor NovaView": dict(dies_des_lliurament=3, usat=False, etiqueta_original=True, defectuos=False, producte_nou=True, categoria="general", obert=False, devolucions_previes=7),
}
for nom, dades in CASOS.items():
motor = MotorInferencia(base_devolucions(), MemoriaTreball(dades))
decisio = motor.executar("decisio")
print(f"{nom}: decisio = {decisio}, {motor.memoria.fets.get('enviament_devolucio') or motor.memoria.fets.get('motiu')}")
print(motor.com())
motor = MotorInferencia(base_devolucions(), MemoriaTreball(CASOS["C raspall NovaSmile"]))
motor.executar("decisio")
print("Per què decisio?\n ", motor.per_que("decisio"))Sortida:
A cafetera NovaBrew: decisio = reemborsament_desistiment, a carrec del client
1. S'ha aplicat R4 perquè dies_des_lliurament=9 (<= 14), usat=False (== False), etiqueta_original=True (== True) → {'decisio': 'reemborsament_desistiment', 'enviament_devolucio': 'a carrec del client'}. Justificació: dret de desistiment de 14 dies amb producte sense usar i etiquetat
B aspirador NovaClean: decisio = garantia, gratuit
1. S'ha aplicat R1 perquè defectuos=True (== True), producte_nou=True (== True), dies_des_lliurament=40 (<= 1095) → {'decisio': 'garantia', 'enviament_devolucio': 'gratuit'}. Justificació: garantia legal de 3 anys del producte nou; l'enviament el paga NovaMarket
C raspall NovaSmile: decisio = denegada, producte d'higiene o precintat obert
1. Conflicte entre R3, R4; guanya R3
2. S'ha aplicat R3 perquè categoria='higiene' (in ('higiene', 'precintat')), obert=True (== True) → {'decisio': 'denegada', 'motiu': "producte d'higiene o precintat obert"}. Justificació: excepció al desistiment: precintats i higiene no es retornen oberts
D auriculars NovaSound: decisio = denegada, fora del termini de desistiment i sense defecte
1. S'ha aplicat R8 perquè dies_des_lliurament=20 (> 14), defectuos=False (== False) → {'decisio': 'denegada', 'motiu': 'fora del termini de desistiment i sense defecte'}. Justificació: la regla del Diego: usat o no, passats 14 dies sense defecte no procedeix
E monitor NovaView: decisio = revisio_humana, patró de devolucions freqüents
1. Conflicte entre R4, R5; guanya R5
2. S'ha aplicat R5 perquè dies_des_lliurament=3 (<= 14), usat=False (== False), etiqueta_original=True (== True), devolucions_previes=7 (>= 5) → {'decisio': 'revisio_humana', 'motiu': 'patró de devolucions freqüents'}. Justificació: més específica que R4: el cas 3 (frau) demana una mirada humana
Per què decisio?
'decisio' = 'denegada' l'ha fixat R3 (excepció al desistiment: precintats i higiene no es retornen oberts); les seves condicions: 'categoria' = 'higiene' és una dada d'entrada; 'obert' = True és una dada d'entradaEls cinc casos cobreixen la política: A, desistiment net (el client paga l'enviament de tornada); B, l'aspirador defectuós al cap de 40 dies, usat i sense etiqueta, va per garantia amb enviament gratuït: R1 guanya tota la resta, i el xoc de 06-01 ha desaparegut per prioritat; C, el raspall d'higiene obert: R3 i R4 són totes dues aplicables (conflicte) i guanya R3 per prioritat, amb la qual cosa la incoherència de l'exercici 3 de 06-01 queda resolta i documentada a la traça; D, la regla del Diego; E, el mateix cas que A però amb 7 devolucions prèvies: R4 i R5 empaten en prioritat i guanya R5 per especificitat, derivant a revisió humana. La crida a per_que mostra l'explicació encadenada fins a les dades d'entrada.
7.5 Interfície de preguntes: consulta dirigida per l'objectiu
PREGUNTES = {
"defectuos": "El producte presenta un defecte de fabricació o funcionament? (s/n)",
"producte_nou": "Es va vendre com a producte nou (no recondicionat)? (s/n)",
"dies_des_lliurament": "Quants dies han passat des del lliurament?",
"categoria": "Categoria del producte (general/higiene/precintat)",
"obert": "Està obert o desprecintat? (s/n)",
"usat": "Mostra senyals d'ús? (s/n)",
"etiqueta_original": "Conserva l'etiqueta original? (s/n)",
"devolucions_previes": "Quantes devolucions prèvies té el client?",
}
def convertir(text):
t = text.strip().lower()
if t in ("s", "si", "sí"): return True
if t in ("n", "no"): return False
try: return int(t)
except ValueError: return t
def respondre_simulat(respostes):
"""Retorna una funció 'respondre' que llegeix d'un dict en lloc d'input()
(per poder executar la sessió sense teclat). Amb '?' explica per què pregunta."""
def respondre(pregunta, regla):
atribut = next(k for k, v in PREGUNTES.items() if v == pregunta)
print(f" Sistema: {pregunta}")
if respostes.get(atribut + "?"):
print(f" Usuari: per què?\n Sistema: perquè intento aplicar {regla.nom}: {regla}")
text = respostes[atribut]
print(f" Usuari: {text}")
return convertir(text)
return respondre
# Portàtil NovaBook retornat amb dades parcials: el sistema pregunta el que necessita
sessio = {"defectuos": "n", "dies_des_lliurament": "10", "categoria": "general", "usat": "s", "usat?": True}
motor = MotorInferencia(base_devolucions(), MemoriaTreball({}))
decisio = motor.consultar("decisio", PREGUNTES, respondre_simulat(sessio))
print(f" Decisió: {decisio} ({motor.memoria.fets.get('motiu', '')})")
print(" Com?\n" + motor.com())Sortida:
Sistema: El producte presenta un defecte de fabricació o funcionament? (s/n)
Usuari: n
Sistema: Categoria del producte (general/higiene/precintat)
Usuari: general
Sistema: Quants dies han passat des del lliurament?
Usuari: 10
Sistema: Mostra senyals d'ús? (s/n)
Usuari: per què?
Sistema: perquè intento aplicar R5: R5 [prio 5]: SI dies_des_lliurament <= 14 ∧ usat == False ∧ etiqueta_original == True ∧ devolucions_previes >= 5 ALESHORES {'decisio': 'revisio_humana', 'motiu': 'patró de devolucions freqüents'}
Usuari: s
Decisió: revisio_humana (producte usat dins del termini: valorar depreciació)
Com?
1. S'ha aplicat R6 perquè dies_des_lliurament=10 (<= 14), usat=True (== True) → {'decisio': 'revisio_humana', 'motiu': 'producte usat dins del termini: valorar depreciació'}. Justificació: una persona decideix si procedeix reemborsament total o parcialSegueix el fil: el sistema comença per la regla de més prioritat (R1) i pregunta si és defectuós; amb "no", R1 i R2 queden descartades sense més preguntes. Passa a R3 i pregunta la categoria; "general" la descarta, i no pregunta si està obert (la condició ja coneguda falla). Passa a R5 (la mateixa prioritat que R4 però més específica): pregunta els dies i si està usat; l'usuari demana "per què?" i el mòdul d'explicació respon amb la regla que està intentant; "sí, usat" descarta R5 i R4, i R6 es compleix: revisió humana per producte usat dins del termini. En total 4 preguntes de 8 possibles: l'encadenament dirigit per objectiu pregunta només el que és rellevant, com MYCIN. Per fer-lo servir amb teclat n'hi ha prou de passar respondre=lambda pregunta, regla: convertir(input(pregunta + " ")).
- Eines reals
No cal escriure el motor cada vegada. En producció es fan servir:
- CLIPS (NASA, anys 80, en C): la shell de regles clàssica amb encadenament cap endavant i l'algorisme Rete, que aparella regles i fets de manera eficient quan n'hi ha milers de totes dues; sintaxi tipus Lisp.
- Drools (Java, codi obert): motor de regles de negoci (BRMS) molt usat en banca i assegurances, amb taules de decisió i l'estàndard DMN que veurem a 06-04.
experta(Python, hereva de PyKnow): regles com a mètodes decorats amb@Rule, fets com a objectesFact, motor amb salience (prioritat). No és a l'entorn d'aquest curs, però el nostre codi es tradueix gairebé línia a línia als seus conceptes:Regla↔@Rule,MemoriaTreball↔declare,prioritat↔salience.
Amb el que has après aquí, la documentació de qualsevol d'elles et resultarà familiar; 07-03 les situa en l'ecosistema.
Errors Comuns i Consells
- Regles sense prioritat ni ordre pensat. És l'error de 06-01: dues regles aplicables i el resultat depèn de l'ordre d'escriptura. Decideix explícitament la jerarquia (garantia > excepcions > desistiment > regla general) i escriu-la a la justificació.
- Regles que "saben" massa. Una regla que barreja vuit condicions és il·legible i fràgil. Descompon-la en fets intermedis (
en_garantia,dins_termini) com vam fer a 06-01, i en regles de dues o tres condicions. - Atributs ambigus.
obertés "desprecintat" o "usat"? El Diego distingeix; el diccionari de dades ha de distingir. Els sistemes experts moren de vocabulari imprecís abans que de lògica. - Oblidar el cas "cap regla aplicable". El nostre motor retorna
None; en producció això s'ha de traduir en revisió humana, no en denegació per defecte (món tancat, 06-01) ni en un error. - No mantenir la bateria de casos. Cada regla nova pot canviar decisions antigues. Els casos A-E són el conjunt de test; afegeix-hi cada cas real conflictiu que aparegui.
- Confiar la política a la màquina. El sistema aplica regles; el responsable de les regles és el Diego, i de la normativa, un professional legal. Que la traça digui sempre quina regla i quina versió han decidit.
- Automatitzar el que exigeix una persona. Reemborsaments parcials, denegacions dubtoses i patrons de frau van a
revisio_humana; és disseny, no covardia (02-04).
Exercicis
Exercici 1. El Diego recorda una excepció: durant la campanya de Nadal el termini de desistiment s'amplia a 30 dies per a les comandes lliurades al desembre. Afegeix a la memòria de treball un atribut campanya_nadal (True/False) i escriu la regla o regles necessàries sense trencar les existents. Quina prioritat els dones i per què? Comprova-ho amb el cas D (auriculars, 20 dies) amb campanya_nadal=True i amb False.
Exercici 2. Executa consultar amb la sessió {"defectuos": "s", "producte_nou": "s", "dies_des_lliurament": "400"} i anota quantes preguntes fa i què decideix. Després canvia dies_des_lliurament a "1200". Explica, mirant l'ordre de les regles a consultar, per què en tots dos casos el sistema no pregunta res sobre categoria, ús ni etiqueta.
Exercici 3. Dissenya, sense codi, la sessió d'adquisició del coneixement amb el Diego per afegir al sistema les garanties comercials ampliades que NovaMarket ven amb alguns productes (per exemple, 2 anys addicionals per a portàtils). Indica: (a) tres preguntes que li faries, (b) quins atributs nous necessitaria la memòria de treball, (c) quins casos de prova afegiries a la bateria A-E i (d) què podria sortir malament en la interacció amb R1 i R2.
Solucions
Solució 1. Una manera neta: R4n, prioritat 5, condicions campanya_nadal == True, dies_des_lliurament <= 30, usat == False, etiqueta_original == True → reemborsament_desistiment; i perquè la regla del Diego (R8) no denegui entre els dies 15 i 30 en campanya, o bé se li afegeix la condició campanya_nadal == False (i una R8n amb campanya_nadal == True i dies_des_lliurament > 30), o bé, més simple, es puja R4n per sobre de R8, cosa que ja passa (prioritat 5 davant de 2), perquè el motor s'atura en fixar decisio. Amb campanya_nadal=True el cas D (usat) continua sense entrar a R4n (exigeix sense usar) i necessita una R6n anàloga a R6 amb 30 dies (revisió humana); amb False, R8 denega com abans. La lliçó: cada excepció temporal toca diverses regles; convé agrupar-les i provar-les juntes.
Solució 2. Amb 400 dies: pregunta defectuos (sí), producte_nou (sí) i dies_des_lliurament (400): tres preguntes, R1 es compleix → garantia, enviament gratuït. Amb 1200 dies: les mateixes tres preguntes; R1 falla en la tercera condició, passa a R2 (defectuos == True, dies > 1095), que ja té tots els fets i es compleix → denegada, "fora del termini de garantia legal". consultar recorre les regles per prioritat descendent i dispara la primera que es compleix: com que R1 i R2 (prioritats 10 i 9) es resolen amb aquests tres fets, mai no arriba a R3-R8, les preguntes de les quals (categoria, ús, etiqueta) són irrellevants per a un producte defectuós.
Solució 3. (a) "Quins productes porten garantia ampliada i com se sap a la comanda?"; "l'ampliada cobreix el mateix que la legal (defectes) o també accidents?"; "qui paga l'enviament i qui repara, NovaMarket o el fabricant?". (b) garantia_ampliada_mesos (o fi_garantia_ampliada), tipus_cobertura, potser causa_defecte (fabricació/accident). (c) Portàtil NovaBook defectuós al cap de 4 anys amb ampliada de 2 anys → garantia (ampliada); el mateix sense ampliada → denegada per R2; un dany accidental amb ampliada que no el cobreix → denegada o revisió humana. (d) R2 denega a partir de 1095 dies abans de considerar l'ampliada: caldria donar a la nova regla una prioritat entre R1 i R2 (per exemple 9,5) o afegir a R2 la condició que no hi hagi ampliada vigent; i cal aclarir al diccionari que producte_nou i garantia_ampliada són atributs diferents.
Conclusió
En aquesta lliçó hem convertit el motor de 06-01 en un sistema expert. Hem vist què és (coneixement explícit separat del raonament) i d'on ve (DENDRAL, MYCIN, XCON), i n'hem recorregut l'arquitectura: base de coneixement, memòria de treball, motor d'inferència amb resolució de conflictes (refractarietat, prioritat, especificitat, recència), mòdul d'explicació (per què? i com?), adquisició del coneixement i interfície. Hem descrit el procés d'enginyeria del coneixement (entrevistes al Diego, extracció i formalització de regles, validació amb casos, manteniment amb propietari), i hem comparat avantatges i inconvenients i el sistema expert amb un model d'ML: regles escrites davant de regles apreses, amb l'arbre de 04-04 com a pont i la combinació de tots dos com a destí. En codi, Regla, BaseConeixement, MemoriaTreball i MotorInferencia han decidit cinc casos de devolucions i garanties de NovaMarket amb traça d'explicació (la garantia del NovaClean per prioritat, el raspall NovaSmile per prioritat sobre el desistiment, el monitor NovaView per especificitat), i consultar ha preguntat només el que calia, amb "per què?" inclòs.
Però fixa't en com hem tractat la incertesa: no l'hem tractat. defectuos és vertader o fals; revisio_humana és la sortida d'escapament quan no volem decidir. En les devolucions això és acceptable, perquè la política és nítida. En el diagnòstic d'incidències del cas 9 no ho és: un paquet abonyegat suggereix dany en transport però també un error de picking amb mal embalatge; un retard apunta a Getafe però també a la missatgeria; i incidencies.csv té la meitat de les causes sense emplenar. Aquí les regles nítides fallen i cal raonar amb graus de creença: els factors de certesa que va inventar MYCIN, la lògica difusa i, sobretot, la probabilitat, el teorema de Bayes i les xarxes bayesianes que van rellançar la IA als 90 (01-01). És el tema de la propera lliçó, 06-03.
Fonaments d'Intel·ligència Artificial (IA)
Mòdul 1: Introducció a la Intel·ligència Artificial
Mòdul 2: Principis Bàsics de la IA
- Conceptes Fonamentals: Agents, Entorns i Racionalitat
- Tipus d'Intel·ligència Artificial
- Les Dades com a Matèria Primera de la IA
- Ètica i Consideracions en IA
Mòdul 3: Algorismes en IA
- Introducció als Algorismes
- Algorismes de Cerca
- Cerca amb Adversari: Jocs i Minimax
- Algorismes d'Optimització
Mòdul 4: Aprenentatge Automàtic (Machine Learning)
- Conceptes Bàsics de Machine Learning
- Tipus d'Aprenentatge Automàtic
- Preparació de Dades i Característiques
- Algorismes de Machine Learning
- Avaluació i Validació de Models
- Sobreajust, Regularització i Ajust d'Hiperparàmetres
Mòdul 5: Xarxes Neuronals i Deep Learning
- Introducció a les Xarxes Neuronals
- Arquitectura de Xarxes Neuronals
- Com Aprèn una Xarxa: Descens del Gradient i Retropropagació
- Deep Learning i les seves Aplicacions
- Transformers, Grans Models de Llenguatge i IA Generativa
Mòdul 6: Lògica i Sistemes Experts
- Lògica en IA
- Sistemes Experts
- Raonament amb Incertesa: Probabilitat i Xarxes Bayesianes
- Aplicacions dels Sistemes Experts
Mòdul 7: Eines i Llenguatges de Programació en IA
- Llenguatges de Programació per a IA
- Python Científic: NumPy, pandas i Matplotlib
- Eines i Llibreries Populars
- Entorns de Desenvolupament
Mòdul 8: Projectes i Casos d'Estudi
Mòdul 9: Exercicis i Pràctiques
- Exercicis d'Algorismes
- Pràctiques de Machine Learning
- Projectes de Xarxes Neuronals
- Projecte Integrador: de la Idea al Prototip
