El sistema expert de 06-02 decideix devolucions amb regles nítides perquè la política és nítida: 14 dies són 14 dies. El diagnòstic d'incidències del cas 9 és una altra història. Un paquet que arriba abonyegat suggereix dany en transport, però també pot ser un error de picking amb mal embalatge; un retard apunta al magatzem de Getafe, però la missatgeria també es retarda; i incidencies.csv té la meitat de les causes en blanc. Aquí les regles SI-ALESHORES es trenquen: cada símptoma és compatible amb diverses causes, amb diferent força. Aquesta lliçó ensenya a raonar amb graus de creença. Veurem per què les regles nítides no basten; els dos enfocaments històrics que ho van intentar dins dels sistemes experts (els factors de certesa de MYCIN i la lògica difusa) i per què la probabilitat va acabar imposant-se; un repàs de probabilitat des de zero (conjunta, condicional, independència, regla del producte, marginalització) fins al teorema de Bayes, amb el diagnòstic de NovaMarket calculat a mà i en codi; el Naive Bayes de 04-04 fet a mà; les xarxes bayesianes, amb una xarxa de diagnòstic d'incidències i la inferència per enumeració en Python pur; i com convertir aquestes probabilitats en decisions amb la utilitat esperada de 02-01. És important perquè gairebé tota la IA moderna, del filtre de correu brossa a l'LLM, és probabilitat aplicada; i perquè a NovaMarket la pregunta del Diego no és "quina és la causa?" sinó "què faig amb aquesta incidència sabent el que sé?".
Contingut
- Per què les regles nítides no basten
- Enfocaments històrics: factors de certesa i lògica difusa
- Probabilitat des de zero: conjunta, condicional, independència, producte i marginalització
- El teorema de Bayes, a mà i en codi
- Naive Bayes a mà: la causa d'una incidència a partir de diversos símptomes
- Xarxes bayesianes: nodes, arcs i taules de probabilitat condicional
- Codi: xarxa de diagnòstic d'incidències i inferència per enumeració
- Inferència aproximada i aprenentatge de les CPT
- Decisions sota incertesa: utilitat esperada
- Errors Comuns i Consells
- Exercicis
- Conclusió
- Per què les regles nítides no basten
Intentem escriure el diagnòstic d'incidències com a regles de 06-02. Els tipus d'incidència que registra el Diego són: paquet danyat, producte que no arriba, producte equivocat, producte defectuós i retard; les causes: error de picking al magatzem, dany en transport, adreça incorrecta, stock desactualitzat (sobretot a Getafe, l'inventari del qual se sincronitza amb retard) i fallada del proveïdor. Un primer intent:
- SI paquet_danyat ALESHORES causa = dany_transport
- SI retard ALESHORES causa = stock_desactualitzat
I de seguida els contraexemples: paquets abonyegats per un embalatge mal triat al picking; retards per adreces incorrectes que obliguen a un segon intent; productes equivocats que ja vénen així del proveïdor. Cada regla és "gairebé sempre" certa, i "gairebé sempre" no cap en la lògica de 06-01. Tres raons de fons:
- Mandra: enumerar totes les condicions i excepcions perquè una regla sigui exacta és inviable (caldria incloure l'operari, l'embalatge, la ruta, la missatgeria...).
- Ignorància teòrica: no existeix una teoria completa que digui què causa cada símptoma.
- Ignorància pràctica: encara que existís, no tenim totes les dades del cas (no veiem el paquet fins que el client el fotografia).
El que sí que sabem és que un paquet abonyegat fa més probable el dany en transport, i quant més. Necessitem un llenguatge per a graus de creença que es combinin correctament quan arriben diverses evidències i que es puguin estimar d'incidencies.csv. Aquest llenguatge és la probabilitat; abans d'arribar-hi, vegem les dues dreceres que es van provar.
- Enfocaments històrics: factors de certesa i lògica difusa
2.1 Factors de certesa (MYCIN)
MYCIN (01-01, 06-02) associava a cada regla un factor de certesa CF entre −1 (certesa que és fals) i +1 (certesa que és cert), i combinava els de diverses regles que donaven suport a la mateixa conclusió amb una fórmula senzilla:
- Tots dos positius: CF = CF₁ + CF₂·(1 − CF₁)
- Tots dos negatius: CF = CF₁ + CF₂·(1 + CF₁)
- Signes diferents: CF = (CF₁ + CF₂) / (1 − min(|CF₁|, |CF₂|))
def combinar_cf(cf1, cf2):
if cf1 >= 0 and cf2 >= 0:
return cf1 + cf2 * (1 - cf1)
if cf1 <= 0 and cf2 <= 0:
return cf1 + cf2 * (1 + cf1)
return (cf1 + cf2) / (1 - min(abs(cf1), abs(cf2)))
print("CF abonyegat(0.6) + retard(0.3):", round(combinar_cf(0.6, 0.3), 3))
print("... i evidència en contra (-0.4):", round(combinar_cf(combinar_cf(0.6, 0.3), -0.4), 3))Sortida:
Dues evidències a favor es reforcen (0,6 i 0,3 donen 0,72, mai més d'1); una en contra resta. Era intuïtiu, barat i va funcionar a MYCIN. Es va abandonar perquè no té una semàntica clara: els nombres no són probabilitats ni res de definit, la fórmula tracta totes les evidències com a independents (dos símptomes que en realitat van sempre junts compten doble), no distingeix "no ho sé" d'"evidències contradictòries", i Heckerman va demostrar el 1986 que els CF només són coherents amb la probabilitat sota supòsits molt restrictius. Quan les xarxes bayesianes van fer la probabilitat tractable, els CF van quedar com a peça de museu.
2.2 Lògica difusa
La lògica difusa (Zadeh, 1965) ataca un altre problema: la vaguetat de les paraules. "Retard greu" no és vertader o fals a partir d'una hora exacta; un retard de 36 hores és "bastant lleu" i "una mica greu". Cada terme té una funció de pertinença entre 0 i 1:
def pertinenca_lleu(hores):
if hores <= 0: return 1.0
if hores >= 48: return 0.0
return 1 - hores / 48
def pertinenca_greu(hores):
if hores <= 24: return 0.0
if hores >= 96: return 1.0
return (hores - 24) / 72
for h in (6, 24, 36, 60, 96):
print(f"retard {h:3d} h: lleu={pertinenca_lleu(h):.2f} greu={pertinenca_greu(h):.2f}")Sortida:
retard 6 h: lleu=0.88 greu=0.00 retard 24 h: lleu=0.50 greu=0.00 retard 36 h: lleu=0.25 greu=0.17 retard 60 h: lleu=0.00 greu=0.50 retard 96 h: lleu=0.00 greu=1.00
Sobre aquestes pertinences s'escriuen regles difuses ("SI retard és greu I client és valuós ALESHORES compensació és alta") que es combinen amb mínims i màxims i es desdifuminen en un nombre. És útil en control (rentadores, frens ABS, climatització) i en interfícies on la vaguetat lingüística importa. Però atenció: la pertinença 0,5 de "greu" a les 60 hores no és una probabilitat; el retard és exactament de 60 hores, sense incertesa; el que és difús és la paraula. La incertesa del diagnòstic ("va ser transport o picking?") és una altra cosa: no sabem què va passar, i per a això l'instrument correcte és la probabilitat.
- Probabilitat des de zero: conjunta, condicional, independència, producte i marginalització
Treballarem amb variables aleatòries discretes: Causa (cinc valors) i símptomes binaris com Abonyegat. Una distribució assigna a cada valor un nombre entre 0 i 1 que sumen 1. Els nombres que segueixen són ficticis però versemblants, arrodonits a partir de les ~200 incidències etiquetades de NovaMarket.
Probabilitat a priori (o marginal): el que creiem abans de veure el cas. P(Causa = dany_transport) = 0,25 vol dir que, de les incidències, una de cada quatre es deu al transport.
| Causa | error_picking | dany_transport | adreca_incorrecta | stock_desactualitzat | fallada_proveidor |
|---|---|---|---|---|---|
| P(Causa) | 0,30 | 0,25 | 0,15 | 0,20 | 0,10 |
Probabilitat conjunta P(Causa, Abonyegat): la que passin les dues coses alhora. La manera més intuïtiva de veure-la és una taula de freqüències sobre 1.000 incidències:
| Causa (de 1.000) | Abonyegat | No abonyegat | Total |
|---|---|---|---|
| error_picking | 30 | 270 | 300 |
| dany_transport | 200 | 50 | 250 |
| adreca_incorrecta | 3 | 147 | 150 |
| stock_desactualitzat | 4 | 196 | 200 |
| fallada_proveidor | 15 | 85 | 100 |
| Total | 252 | 748 | 1.000 |
Cada cel·la dividida per 1.000 és la conjunta: P(dany_transport, Abonyegat) = 0,20.
Marginalització: sumar la conjunta sobre la variable que no interessa. P(Abonyegat) = 30 + 200 + 3 + 4 + 15 = 252 de 1.000 = 0,252. És "el total de la columna". En fórmula: P(A) = Σ_c P(A, c).
Probabilitat condicional P(A | B): la d'A sabent que B ha passat: restringim la taula a la fila o columna de B. P(Abonyegat | dany_transport) = 200/250 = 0,80: dels danys en transport, el 80 % arriben abonyegats. I P(dany_transport | Abonyegat) = 200/252 = 0,794: dels abonyegats, el 79 % són danys de transport. No són la mateixa cosa: la primera és una versemblança (què produeix la causa), la segona és el que volem diagnosticar. Confondre-les és l'error més comú de tot aquest camp.
Regla del producte: P(A, B) = P(A | B) · P(B). Amb els nostres nombres: 0,80 · 0,25 = 0,20. És la definició de la condicional reordenada, i encadenada dona la regla de la cadena P(A, B, C) = P(A | B, C) · P(B | C) · P(C), que farem servir a les xarxes bayesianes.
Independència: A i B són independents si P(A | B) = P(A), és a dir, saber B no canvia el que creiem d'A; aleshores P(A, B) = P(A)·P(B). Abonyegat i Causa no són independents (0,80 davant de 0,252). I hi ha una noció més fina que serà la clau de la secció 6: independència condicional: dos símptomes poden dependre l'un de l'altre (els paquets abonyegats vénen més sovint amb producte equivocat que els no abonyegats, perquè tots dos apunten a errors de manipulació) i tanmateix ser independents un cop coneguda la causa: si ja sé que va ser error de picking, saber que està abonyegat no em diu res de nou sobre si el producte és l'equivocat. P(A | B, Causa) = P(A | Causa).
- El teorema de Bayes, a mà i en codi
De la regla del producte escrita en els dos sentits, P(C, A) = P(A | C)·P(C) = P(C | A)·P(A), aïllem:
P(C | A) = P(A | C) · P(C) / P(A)
- P(C): prior, el que crèiem de la causa abans de veure el símptoma.
- P(A | C): versemblança, quant produeix aquella causa aquell símptoma.
- P(A): evidència, la probabilitat total del símptoma, que es calcula marginalitzant: P(A) = Σ_c P(A | c)·P(c). Serveix perquè els posteriors sumin 1.
- P(C | A): posterior, el que creiem després.
És la fórmula que a 04-04 sustentava Naive Bayes i que ara ja podem justificar. Calculem a mà P(Causa | Abonyegat) per a les cinc causes:
| Causa c | Prior P(c) | Versemblança P(Abonyegat | c) | Producte | Posterior = producte / 0,252 |
|---|---|---|---|---|
| error_picking | 0,30 | 0,10 | 0,030 | 0,119 |
| dany_transport | 0,25 | 0,80 | 0,200 | 0,794 |
| adreca_incorrecta | 0,15 | 0,02 | 0,003 | 0,012 |
| stock_desactualitzat | 0,20 | 0,02 | 0,004 | 0,016 |
| fallada_proveidor | 0,10 | 0,15 | 0,015 | 0,060 |
| Suma | 1,00 | 0,252 = P(Abonyegat) | 1,000 |
I en codi, amb fractions perquè no hi hagi arrodoniments:
from fractions import Fraction as F
priors = {"error_picking": F(30,100), "dany_transport": F(25,100), "adreca_incorrecta": F(15,100),
"stock_desactualitzat": F(20,100), "fallada_proveidor": F(10,100)}
veros = {"error_picking": F(10,100), "dany_transport": F(80,100), "adreca_incorrecta": F(2,100),
"stock_desactualitzat": F(2,100), "fallada_proveidor": F(15,100)} # P(abonyegat | causa)
p_abonyegat = sum(priors[c] * veros[c] for c in priors) # marginalització
print("P(abonyegat) =", p_abonyegat, float(p_abonyegat))
for c in priors:
post = priors[c] * veros[c] / p_abonyegat # teorema de Bayes
print(f" P({c} | abonyegat) = {priors[c]*veros[c]} / {p_abonyegat} = {float(post):.3f}")Sortida:
P(abonyegat) = 63/250 0.252 P(error_picking | abonyegat) = 3/100 / 63/250 = 0.119 P(dany_transport | abonyegat) = 1/5 / 63/250 = 0.794 P(adreca_incorrecta | abonyegat) = 3/1000 / 63/250 = 0.012 P(stock_desactualitzat | abonyegat) = 1/250 / 63/250 = 0.016 P(fallada_proveidor | abonyegat) = 3/200 / 63/250 = 0.060
Observa el paper del prior: la fallada del proveïdor produeix paquets abonyegats el 15 % de les vegades, més que l'error de picking (10 %), i tanmateix el picking té més posterior (0,119 davant de 0,060) perquè és tres vegades més freqüent. Ignorar el prior (fixar-se només en "quina causa explica millor el símptoma") és la fal·làcia de la taxa base.
Enllaç amb 04-05. Si tractem "abonyegat" com una prova diagnòstica de "dany en transport", la sensibilitat de la prova és P(Abonyegat | transport) = 0,80 i la seva especificitat és P(No abonyegat | no transport) = 1 − 52/750 = 0,931 (de les 750 incidències que no són de transport, 52 estan abonyegades). Bayes en aquest llenguatge: posterior = sens·prev / (sens·prev + (1 − espec)·(1 − prev)) = 0,80·0,25 / (0,80·0,25 + 0,069·0,75) = 0,794. El valor predictiu positiu de 04-05 és un posterior bayesià, i depèn de la prevalença (el prior) tant com de la qualitat de la prova: la mateixa prova aplicada a Getafe, on el transport pesa menys, donaria un posterior més baix.
- Naive Bayes a mà: la causa d'una incidència a partir de diversos símptomes
Amb un símptoma n'hi ha prou amb una taula. Amb tres (paquet danyat, producte equivocat, retard) la conjunta P(Causa, S₁, S₂, S₃) tindria 5·2·2·2 = 40 cel·les que caldria estimar; amb vint símptomes, milions. Naive Bayes (04-04) se'n surt amb la suposició d'independència condicional de la secció 3: donada la causa, els símptomes són independents, per tant P(S₁, S₂, S₃ | c) = P(S₁ | c)·P(S₂ | c)·P(S₃ | c) i només calen tres taules petites:
| P(símptoma = sí | causa) | error_picking | dany_transport | adreca_incorrecta | stock_desactualitzat | fallada_proveidor |
|---|---|---|---|---|---|
| paquet_danyat | 0,10 | 0,80 | 0,02 | 0,02 | 0,15 |
| producte_equivocat | 0,70 | 0,02 | 0,01 | 0,10 | 0,30 |
| retard | 0,10 | 0,30 | 0,60 | 0,90 | 0,70 |
Incidència 7731: paquet danyat sí, retard sí, producte equivocat no. Per a cada causa multipliquem el prior per les tres versemblances (per a "no" fem servir 1 − p) i normalitzem:
| Causa | Prior | ·P(danyat) | ·P(retard) | ·P(no equivocat) | Numerador | Posterior |
|---|---|---|---|---|---|---|
| error_picking | 0,30 | 0,10 | 0,10 | 0,30 | 0,00090 | 0,012 |
| dany_transport | 0,25 | 0,80 | 0,30 | 0,98 | 0,05880 | 0,816 |
| adreca_incorrecta | 0,15 | 0,02 | 0,60 | 0,99 | 0,00178 | 0,025 |
| stock_desactualitzat | 0,20 | 0,02 | 0,90 | 0,90 | 0,00324 | 0,045 |
| fallada_proveidor | 0,10 | 0,15 | 0,70 | 0,70 | 0,00735 | 0,102 |
| Suma | 0,07207 | 1,000 |
CAUSES = ["error_picking", "dany_transport", "adreca_incorrecta", "stock_desactualitzat", "fallada_proveidor"]
prior = {"error_picking": 0.30, "dany_transport": 0.25, "adreca_incorrecta": 0.15,
"stock_desactualitzat": 0.20, "fallada_proveidor": 0.10}
p_simptoma = { # P(símptoma = True | causa)
"paquet_danyat": {"error_picking": 0.10, "dany_transport": 0.80, "adreca_incorrecta": 0.02, "stock_desactualitzat": 0.02, "fallada_proveidor": 0.15},
"producte_equivocat": {"error_picking": 0.70, "dany_transport": 0.02, "adreca_incorrecta": 0.01, "stock_desactualitzat": 0.10, "fallada_proveidor": 0.30},
"retard": {"error_picking": 0.10, "dany_transport": 0.30, "adreca_incorrecta": 0.60, "stock_desactualitzat": 0.90, "fallada_proveidor": 0.70},
}
def naive_bayes(evidencia):
"""evidencia: dict símptoma -> True/False. Retorna P(causa | evidencia)."""
puntuacions = {}
for c in CAUSES:
p = prior[c]
for s, valor in evidencia.items():
p *= p_simptoma[s][c] if valor else 1 - p_simptoma[s][c]
puntuacions[c] = p # numerador: P(causa) · Π P(símptoma | causa)
total = sum(puntuacions.values()) # P(evidència): normalitzador
return {c: p / total for c, p in puntuacions.items()}, puntuacions, total
ev = {"paquet_danyat": True, "retard": True, "producte_equivocat": False}
post, num, total = naive_bayes(ev)
print(f"P(evidència) = {total:.5f}")
for c in CAUSES:
print(f" {c:22s} numerador {num[c]:.5f} posterior {post[c]:.3f}")Sortida:
P(evidència) = 0.07207 error_picking numerador 0.00090 posterior 0.012 dany_transport numerador 0.05880 posterior 0.816 adreca_incorrecta numerador 0.00178 posterior 0.025 stock_desactualitzat numerador 0.00324 posterior 0.045 fallada_proveidor numerador 0.00735 posterior 0.102
És exactament el que GaussianNB o MultinomialNB de scikit-learn fan per dins (amb les versemblances estimades de les dades, secció 8). El "retard sí" tot sol, sense paquet danyat, donaria stock_desactualitzat 0,51 i adreça incorrecta 0,28: cada símptoma nou mou la creença, i el producte de versemblances és la manera correcta de combinar evidències que faltava als factors de certesa.
- Xarxes bayesianes: nodes, arcs i taules de probabilitat condicional
Naive Bayes és una xarxa bayesiana molt particular: una causa amb fletxes a tots els símptomes. Una xarxa bayesiana general és un graf dirigit sense cicles en què:
- cada node és una variable aleatòria;
- cada arc X → Y expressa una influència directa (sovint causal) de X sobre Y;
- cada node porta una taula de probabilitat condicional (CPT) que dona P(node | pares): per a un node sense pares, el seu prior; per a un amb pares, una fila per cada combinació de valors dels pares.
La xarxa codifica independències condicionals: cada node és independent dels seus no descendents donada la informació dels seus pares. Gràcies a això, la conjunta completa es factoritza amb la regla de la cadena en el producte de les CPT: P(X₁, ..., Xₙ) = Π P(Xᵢ | pares(Xᵢ)). En lloc d'una taula gegant, taules petites i locals que un expert (el Diego) pot emplenar o que s'estimen de les dades.
La nostra xarxa de diagnòstic d'incidències afegeix a Naive Bayes un node pare, el magatzem, perquè la distribució de causes no és la mateixa a Zaragoza que a Getafe (allà l'stock desactualitzat pesa molt més), i un quart símptoma, "no arriba":
flowchart TD
A[magatzem<br/>zaragoza 0,60 / getafe 0,40] --> C[causa<br/>5 valors, CPT per magatzem]
C --> D[paquet_danyat]
C --> E[producte_equivocat]
C --> N[no_arriba]
C --> R[retard]
CPT del node causa (una fila per magatzem; cada fila suma 1):
| P(causa | magatzem) | error_picking | dany_transport | adreca_incorrecta | stock_desactualitzat | fallada_proveidor |
|---|---|---|---|---|---|
| zaragoza | 0,35 | 0,30 | 0,15 | 0,08 | 0,12 |
| getafe | 0,25 | 0,20 | 0,12 | 0,33 | 0,10 |
CPT dels quatre símptomes (P(símptoma = sí | causa); les tres primeres files són les de la secció 5):
| P(sí | causa) | error_picking | dany_transport | adreca_incorrecta | stock_desactualitzat | fallada_proveidor |
|---|---|---|---|---|---|
| paquet_danyat | 0,10 | 0,80 | 0,02 | 0,02 | 0,15 |
| producte_equivocat | 0,70 | 0,02 | 0,01 | 0,10 | 0,30 |
| no_arriba | 0,05 | 0,10 | 0,85 | 0,40 | 0,30 |
| retard | 0,10 | 0,30 | 0,60 | 0,90 | 0,70 |
Compta paràmetres: 1 + 8 + 4·5 = 29 nombres davant dels 2·5·2⁴ − 1 = 159 de la conjunta completa. I llegeix les independències: els símptomes són condicionalment independents entre si donada la causa (per això Naive Bayes és un cas particular), i el magatzem només influeix en els símptomes a través de la causa: P(retard | causa, magatzem) = P(retard | causa).
- Codi: xarxa de diagnòstic d'incidències i inferència per enumeració
Inferència és calcular P(consulta | evidència) per a qualsevol node i qualsevol evidència. L'algorisme més simple és l'enumeració: la conjunta s'obté multiplicant les CPT, i per obtenir P(consulta, evidència) es suma la conjunta sobre totes les combinacions de les variables ocultes (ni consultades ni observades); dividir per la suma total normalitza. És Bayes i marginalització, sense res més.
from itertools import product
CAUSES = ["error_picking", "dany_transport", "adreca_incorrecta", "stock_desactualitzat", "fallada_proveidor"]
def cpt_binaria(p_true_per_causa):
"""Construeix la CPT d'un símptoma binari a partir de P(símptoma=True | causa)."""
return {(c,): {True: p, False: round(1 - p, 4)} for c, p in p_true_per_causa.items()}
# Cada node: (llista de pares, domini, CPT). La CPT és un dict que, per a cada
# combinació de valors dels pares (tupla), dona la distribució sobre el node.
XARXA = {
"magatzem": ([], ["zaragoza", "getafe"], {(): {"zaragoza": 0.60, "getafe": 0.40}}),
"causa": (["magatzem"], CAUSES, {
("zaragoza",): {"error_picking": 0.35, "dany_transport": 0.30, "adreca_incorrecta": 0.15, "stock_desactualitzat": 0.08, "fallada_proveidor": 0.12},
("getafe",): {"error_picking": 0.25, "dany_transport": 0.20, "adreca_incorrecta": 0.12, "stock_desactualitzat": 0.33, "fallada_proveidor": 0.10},
}),
"paquet_danyat": (["causa"], [True, False], cpt_binaria({"error_picking": 0.10, "dany_transport": 0.80, "adreca_incorrecta": 0.02, "stock_desactualitzat": 0.02, "fallada_proveidor": 0.15})),
"producte_equivocat": (["causa"], [True, False], cpt_binaria({"error_picking": 0.70, "dany_transport": 0.02, "adreca_incorrecta": 0.01, "stock_desactualitzat": 0.10, "fallada_proveidor": 0.30})),
"no_arriba": (["causa"], [True, False], cpt_binaria({"error_picking": 0.05, "dany_transport": 0.10, "adreca_incorrecta": 0.85, "stock_desactualitzat": 0.40, "fallada_proveidor": 0.30})),
"retard": (["causa"], [True, False], cpt_binaria({"error_picking": 0.10, "dany_transport": 0.30, "adreca_incorrecta": 0.60, "stock_desactualitzat": 0.90, "fallada_proveidor": 0.70})),
}
ORDRE = ["magatzem", "causa", "paquet_danyat", "producte_equivocat", "no_arriba", "retard"] # pares abans que fills
def prob_local(node, valor, assignacio):
"""P(node = valor | pares) llegint la CPT amb els valors dels pares a 'assignacio'."""
pares, _, cpt = XARXA[node]
clau = tuple(assignacio[p] for p in pares)
return cpt[clau][valor]
def prob_conjunta(assignacio):
"""Regla de la cadena: producte de les probabilitats locals de tots els nodes."""
p = 1.0
for node in ORDRE:
p *= prob_local(node, assignacio[node], assignacio)
return p
def inferir(consulta, evidencia):
"""P(consulta | evidencia) per enumeració: suma la conjunta sobre les variables ocultes."""
ocultes = [n for n in ORDRE if n != consulta and n not in evidencia]
resultat = {}
for valor in XARXA[consulta][1]:
total = 0.0
for combinacio in product(*[XARXA[n][1] for n in ocultes]): # totes les combinacions d'ocultes
assignacio = dict(evidencia, **{consulta: valor}, **dict(zip(ocultes, combinacio)))
total += prob_conjunta(assignacio)
resultat[valor] = total # = P(consulta=valor, evidencia)
z = sum(resultat.values()) # = P(evidencia)
return {v: p / z for v, p in resultat.items()}
def mostrar(titol, dist):
print(titol)
for v, p in sorted(dist.items(), key=lambda kv: -kv[1]):
print(f" {str(v):22s} {p:.3f}")
mostrar("P(causa) sense evidència:", inferir("causa", {}))
mostrar("P(causa | retard):", inferir("causa", {"retard": True}))
mostrar("P(causa | retard, magatzem=getafe):", inferir("causa", {"retard": True, "magatzem": "getafe"}))
mostrar("P(causa | paquet_danyat, retard, no equivocat, sí arriba):",
inferir("causa", {"paquet_danyat": True, "retard": True, "producte_equivocat": False, "no_arriba": False}))
mostrar("P(magatzem | producte_equivocat):", inferir("magatzem", {"producte_equivocat": True}))
mostrar("P(no_arriba | magatzem=getafe, retard):", inferir("no_arriba", {"magatzem": "getafe", "retard": True}))Sortida:
P(causa) sense evidència: error_picking 0.310 dany_transport 0.260 stock_desactualitzat 0.180 adreca_incorrecta 0.138 fallada_proveidor 0.112 P(causa | retard): stock_desactualitzat 0.375 adreca_incorrecta 0.192 fallada_proveidor 0.181 dany_transport 0.180 error_picking 0.072 P(causa | retard, magatzem=getafe): stock_desactualitzat 0.567 adreca_incorrecta 0.137 fallada_proveidor 0.134 dany_transport 0.115 error_picking 0.048 P(causa | paquet_danyat, retard, no equivocat, sí arriba): dany_transport 0.864 fallada_proveidor 0.090 stock_desactualitzat 0.027 error_picking 0.014 adreca_incorrecta 0.004 P(magatzem | producte_equivocat): zaragoza 0.646 getafe 0.354 P(no_arriba | magatzem=getafe, retard): False 0.603 True 0.397
Explicació:
XARXAés la xarxa sencera: per a cada node, els seus pares, els seus valors possibles i la seva CPT com a diccionari de diccionaris;cpt_binariaevita escriure dues vegades cada símptoma.ORDREllista els nodes amb els pares abans que els fills, per poder aplicar la regla de la cadena.prob_localllegeix la CPT: busca la fila corresponent als valors actuals dels pares i retorna la probabilitat del valor del node.prob_conjuntamultiplica les sis probabilitats locals: P(magatzem)·P(causa | magatzem)·P(danyat | causa)·... És la factorització de la secció 6.inferirfa l'enumeració: per a cada valor de la consulta, suma la conjunta sobre totes les combinacions de les variables ocultes (product), cosa que dona P(consulta = valor, evidència); al final divideix per la suma (P(evidència)) per obtenir el posterior. Amb 160 combinacions totals és instantani.- Llegeix les respostes com un diagnosticador: sense evidència, la causa més probable és el picking (0,31); un retard tot sol desplaça la creença a l'stock desactualitzat (0,375) i l'adreça incorrecta; saber que la comanda va sortir de Getafe dispara l'stock a 0,567 (el magatzem influeix en el símptoma a través de la causa); l'evidència completa de la incidència 7731 (danyat, amb retard, producte correcte, ha arribat) dona transport al 0,864, una mica més que el 0,816 de Naive Bayes perquè "sí arriba" descarta adreça incorrecta i stock. La xarxa també raona cap amunt: un producte equivocat fa més probable Zaragoza (0,646 davant del prior 0,60), perquè allà pesa més el picking; i entre símptomes: sabent el magatzem i el retard, prediu si el paquet arribarà (0,397 que no). Tot amb les mateixes 29 xifres.
Aquest últim punt és el que un sistema de regles no pot fer: fer servir el mateix coneixement per diagnosticar (símptomes → causa), predir (causa → símptomes) i explicar (donat un símptoma, quin altre esperar?). I les variables observades s'expliquen amb la traça de la fórmula: "transport 0,864 perquè el prior de transport a Zaragoza/Getafe és 0,26 i el paquet danyat és 8 vegades més probable amb transport que amb picking".
- Inferència aproximada i aprenentatge de les CPT
Dues notes per no crear falses expectatives:
- Inferència aproximada. L'enumeració suma sobre totes les combinacions de variables ocultes: exponencial. Amb sis nodes són 160; amb cinquanta símptomes i causes intermèdies, inabordable. Els algorismes exactes més llestos (eliminació de variables, arbres d'unió) exploten l'estructura del graf, i quan ni això no arriba es fa servir mostreig: generar milers d'"incidències virtuals" seguint les CPT (mostreig directe, amb rebuig, ponderació per versemblança, MCMC) i comptar en quina fracció la causa és cada cosa. És la mateixa idea Monte Carlo del mòdul 3 i de la cerca d'AlphaGo.
- Aprendre les CPT d'
incidencies.csv. Les 29 xifres de la xarxa les hem posat nosaltres; a la pràctica s'estimen comptant: P(paquet_danyat = sí | causa = transport) ≈ (nre. d'incidències de transport amb paquet danyat + 1) / (nre. d'incidències de transport + 2), amb el "+1/+2" del suavitzat de Laplace perquè un símptoma mai vist amb una causa no rebi probabilitat exactament zero (que anul·laria qualsevol producte). És el que faMultinomialNB.fit. I aquí reapareix el problema de 02-03: només ~200 files tenencausa; amb 5 causes i 4 símptomes, algunes cel·les s'estimen amb menys de deu casos, i les 200 es van emplenar els dies tranquils (biaix de mostreig). El Diego va fer bé de fer obligatòria la causa: la qualitat de la xarxa la fixen les dades amb què s'estimen les seves taules; l'estructura (quina fletxa va on) sol posar-la l'expert, tot i que també hi ha algorismes per aprendre-la.
- Decisions sota incertesa: utilitat esperada
Saber que P(transport | evidència) = 0,864 no és encara una acció. A 02-01 vam dir que l'agent racional tria l'acció de més utilitat esperada: per a cada acció, suma sobre els estats possibles de la probabilitat de l'estat per la utilitat del resultat. El Diego té, davant d'una incidència de 60 €, dues accions: reemborsar directament (ràpid, el client content, però es perd la possibilitat de recuperar l'import del transportista o el producte) o obrir investigació (costa temps d'una persona i retarda la solució, però segons la causa es recupera part del cost). Posem utilitats en euros (negatives: cost per a NovaMarket), a partir de l'experiència d'operacions:
| Utilitat (€) | error_picking | dany_transport | adreca_incorrecta | stock_desactualitzat | fallada_proveidor |
|---|---|---|---|---|---|
| reemborsar_directe | −60 | −60 | −60 | −60 | −60 |
| investigar | −45 (es recupera el producte) | −25 (reclama al transportista) | −30 (reenviament a càrrec del client) | −95 (res a recuperar i client més enfadat) | −30 (càrrec al proveïdor) |
UTILITAT = {
"reemborsar_directe": {c: -60 for c in CAUSES},
"investigar": {"error_picking": -45, "dany_transport": -25, "adreca_incorrecta": -30,
"stock_desactualitzat": -95, "fallada_proveidor": -30},
}
def utilitat_esperada(accio, posterior):
return sum(posterior[c] * UTILITAT[accio][c] for c in CAUSES)
def decidir(evidencia):
post = inferir("causa", evidencia)
ues = {a: utilitat_esperada(a, post) for a in UTILITAT}
millor = max(ues, key=ues.get)
print(f"evidència {evidencia}")
for a, u in ues.items():
print(f" UE({a}) = {u:7.2f} €")
print(f" -> {millor}")
decidir({})
decidir({"retard": True})
decidir({"retard": True, "magatzem": "getafe"})
decidir({"paquet_danyat": True})Sortida:
evidència {}
UE(reemborsar_directe) = -60.00 €
UE(investigar) = -45.05 €
-> investigar
evidència {'retard': True}
UE(reemborsar_directe) = -60.00 €
UE(investigar) = -54.54 €
-> investigar
evidència {'retard': True, 'magatzem': 'getafe'}
UE(reemborsar_directe) = -60.00 €
UE(investigar) = -66.98 €
-> reemborsar_directe
evidència {'paquet_danyat': True}
UE(reemborsar_directe) = -60.00 €
UE(investigar) = -28.70 €
-> investigarSense saber res, investigar compensa (−45 davant de −60). Amb un retard, encara, però per poc. Amb retard i Getafe, l'stock desactualitzat és tan probable (0,567) que investigar surt més car que reemborsar: la decisió canvia amb l'evidència. Amb un paquet danyat, investigar és clarament millor perquè gairebé segur que es reclama al transportista. Això és un agent basat en utilitat de 02-01 amb la xarxa com a model del món, i és també l'esquema de la revisió humana de 02-04: es pot afegir una tercera acció, "passar a una persona", amb el seu cost, i deixar que la utilitat esperada digui quan val la pena. La xarxa diagnostica; la utilitat decideix; i les dues parts són llegibles i ajustables pel Diego.
Errors Comuns i Consells
- Confondre P(símptoma | causa) amb P(causa | símptoma). La primera la dona l'expert o les dades ("el 80 % dels danys de transport arriben abonyegats"); la segona és la que busques i necessita el prior. Bayes és el pont; no el creuis mentalment sense ell.
- Ignorar la taxa base. La causa que "explica millor" el símptoma no és la més probable si és rara. Mira sempre la columna del prior.
- Tractar pertinences difuses o factors de certesa com a probabilitats. No ho són; no les combinis amb Bayes ni sumis sobre elles.
- Zeros a les CPT. Un 0 estimat de poques dades aniquila el producte i fa impossible una causa per sempre. Suavitza (Laplace) i reserva el 0 per al que és lògicament impossible.
- Suposar independència condicional sense pensar-la. Naive Bayes funciona sorprenentment bé, però si dos símptomes són gairebé el mateix (retard i "no arriba en data"), comptaran doble. Una xarxa amb l'estructura adequada ho corregeix.
- Oblidar que les probabilitats són d'un context. Les CPT estimades amb incidències de Zaragoza no valen per a Getafe: per això el magatzem és un node.
- Aturar-se en la probabilitat. L'objectiu del Diego és decidir; sense utilitats no hi ha decisió, i una utilitat mal posada (oblidar el cost d'un client enfadat) decideix malament encara que la xarxa sigui perfecta.
Exercicis
Exercici 1. Amb la taula de 1.000 incidències de la secció 3, calcula a mà: (a) P(no abonyegat); (b) P(error_picking | no abonyegat); (c) P(dany_transport | no abonyegat). Comprova que (b) és més gran que el prior de picking i (c) més petit que el de transport, i explica per què en una frase.
Exercici 2. Amb inferir, calcula P(causa | no_arriba = True) per a magatzem = "zaragoza" i per a magatzem = "getafe". Quina és la causa més probable a cada magatzem? Quina acció de la secció 9 triaries en cada cas (calcula les utilitats esperades amb decidir)? Explica el paper del node magatzem.
Exercici 3. Un producte arriba equivocat i a més el paquet està danyat. Calcula P(causa | producte_equivocat) i P(causa | producte_equivocat, paquet_danyat). Puja o baixa error_picking en afegir el dany? I fallada_proveidor? Explica el resultat en termes de les versemblances de la CPT de paquet_danyat.
Solucions
Solució 1. (a) 748/1.000 = 0,748. (b) 270/748 = 0,361 (prior 0,30). (c) 50/748 = 0,067 (prior 0,25). Un paquet que no està abonyegat és evidència en contra del transport (que gairebé sempre abonyega) i, per eliminació, a favor de les altres causes: l'absència d'un símptoma també informa, i tant més com més sensible sigui el símptoma a la causa.
Solució 2. inferir("causa", {"no_arriba": True, "magatzem": "zaragoza"}) dona adreca_incorrecta 0,525, fallada_proveidor 0,148, stock 0,132, transport 0,123, picking 0,072; amb Getafe: stock_desactualitzat 0,445, adreca_incorrecta 0,344, proveïdor 0,101, transport 0,067, picking 0,042. A Zaragoza, la causa més probable d'un "no arriba" és l'adreça incorrecta; a Getafe, l'stock desactualitzat. decidir({"no_arriba": True, "magatzem": "zaragoza"}) dona investigar −39,02 € davant de −60: investigar amb claredat (l'adreça es corregeix i el reenviament es cobra); amb Getafe, investigar −59,23 € davant de −60: empat pràctic, en què n'hi hauria prou amb una mica més de pes de l'stock (o un cost d'investigació una mica més alt) perquè guanyés reemborsar. El node magatzem canvia el prior de les causes, i amb ell tot el diagnòstic i la decisió, sense tocar les CPT dels símptomes.
Solució 3. P(causa | equivocat): picking 0,789, proveïdor 0,122, stock 0,065, transport 0,019, adreça 0,005. P(causa | equivocat, danyat): picking 0,694, proveïdor 0,161, transport 0,133, stock 0,012, adreça 0,001. El picking baixa (0,789 → 0,694) i el proveïdor i el transport pugen: el dany és poc probable amb picking (0,10) i una mica més amb proveïdor (0,15) i molt més amb transport (0,80), així que la nova evidència reparteix creença cap a ells; però el picking continua sent la causa més probable perquè el producte equivocat és 35 vegades més versemblant amb picking que amb transport. Bayes fa el compte exacte que la intuïció fa a ull.
Conclusió
En aquesta lliçó hem après a raonar quan les regles no basten. Hem vist per què el diagnòstic d'incidències no cap en SI-ALESHORES nítids (mandra, ignorància teòrica i pràctica), i hem situat les dues dreceres històriques: els factors de certesa de MYCIN, intuïtius però sense semàntica, i la lògica difusa, que modela la vaguetat de les paraules ("retard greu" amb pertinença 0,5) però no la incertesa sobre els fets. Hem construït la probabilitat des de la taula de 1.000 incidències: conjunta, marginalització, condicional, regla del producte i independència (i la independència condicional), fins al teorema de Bayes, que ha diagnosticat dany en transport amb 0,794 per a un paquet abonyegat, i que hem llegit també en el llenguatge de sensibilitat i especificitat de 04-05. Hem fet Naive Bayes a mà amb tres símptomes (0,816 per a transport a la incidència 7731) i hem generalitzat a una xarxa bayesiana de sis nodes (magatzem → causa → quatre símptomes, 29 paràmetres en lloc de 159) amb inferència per enumeració en Python pur, capaç de diagnosticar, predir i raonar cap amunt amb el mateix coneixement; hem anotat com s'aproxima la inferència quan la xarxa creix i com s'aprenen les CPT d'incidencies.csv amb suavitzat. I hem tancat el cercle amb 02-01: la utilitat esperada converteix el posterior en la decisió d'investigar o reemborsar, i la decisió canvia quan arriba l'evidència de Getafe.
Amb això NovaMarket disposa de les tres eines simbòliques del mòdul: la lògica per representar (06-01), el sistema expert per decidir amb regles (06-02) i la probabilitat per decidir amb incertesa (06-03). Falta veure on es fan servir de debò, avui, fora del nostre laboratori: en la medicina que MYCIN va inaugurar, en la banca i el compliment normatiu, en la indústria, en el dret i en el comerç electrònic; en què s'han convertit els sistemes experts (motors de regles de negoci, taules de decisió, grafs de coneixement) i, sobretot, com es combinen amb l'ML i els LLM dels mòduls 4 i 5 en l'enfocament neurosimbòlic que vam anunciar en tancar 05-05: un model que estima una probabilitat i una regla que decideix amb ella. És el tema de l'última lliçó del mòdul, 06-04.
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
