Cerramos el módulo 5 con una constatación incómoda: las redes neuronales y los grandes modelos de lenguaje son subsimbólicos y opacos; nadie puede leer sus pesos ni pedirles cuentas de una decisión. Y NovaMarket tiene decisiones que exigen poder leerse y justificarse: si procede o no un reembolso (caso 8) o cuál es la causa probable de una incidencia (caso 9). Esta lección abre la otra mitad de la inteligencia artificial, la simbólica de las primeras décadas de 01-01, empezando por su cimiento: la lógica, el lenguaje formal para representar conocimiento de forma explícita y para razonar con él de forma verificable. Veremos la lógica proposicional (proposiciones, conectivos y tablas de verdad, equivalencias, reglas de inferencia, bases de conocimiento y la noción de implicación lógica), la lógica de primer orden (objetos, predicados, cuantificadores) que permite escribir las políticas de devolución de NovaMarket con precisión, y el mecanismo de trabajo de casi todos los sistemas basados en reglas: el encadenamiento hacia delante y hacia atrás con cláusulas de Horn. Todo con código en Python puro que puedes ejecutar y leer, línea a línea. Es importante porque la lógica es el vocabulario que compartirán las tres lecciones siguientes: los sistemas expertos (06-02) son lógica más ingeniería, las redes bayesianas (06-03) son la respuesta a lo que la lógica no sabe hacer, y las aplicaciones de 06-04 son lógica en producción.
Contenido
- Por qué la lógica: conocimiento explícito y verificable
- Lógica proposicional: proposiciones y conectivos
- Tautologías, contradicciones, satisfacibilidad y equivalencias útiles
- Reglas de inferencia: modus ponens, modus tollens y resolución
- Base de conocimiento y consulta: KB ⊨ α por enumeración de modelos
- Lógica de primer orden: objetos, predicados, funciones y cuantificadores
- Cláusulas de Horn: encadenamiento hacia delante y hacia atrás
- Prolog, el lenguaje de la lógica
- Limitaciones: mundo cerrado, monotonía e incertidumbre
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Por qué la lógica: conocimiento explícito y verificable
En 02-02 distinguimos la IA simbólica (símbolos y reglas que un humano puede leer) de la subsimbólica (números y pesos que nadie lee). Los módulos 4 y 5 han sido subsimbólicos: la regresión logística de 04-04 y el MLP de 05-03 aprenden pesos de pedidos.csv, y el LLM de 05-05 genera texto sin poder explicar por qué. Para muchas tareas es lo adecuado. Pero considera lo que Diego necesita para las devoluciones:
- La regla existe antes que los datos. El plazo de desistimiento de 14 días o la garantía legal de 3 años no se aprenden de un histórico: los fija la normativa y la política comercial de NovaMarket. Un modelo que "aprendiera" 13 o 16 días estaría sencillamente equivocado.
- La decisión debe poder justificarse ante el cliente, ante un auditor y, como vimos en 02-04, ante el AI Act y el RGPD (decisiones automatizadas con efectos significativos). "El modelo dio 0,71" no es una justificación; "no procede porque el producto está usado y han pasado 20 días, regla R3" sí lo es.
- Debe ser verificable: Diego tiene que poder leer las reglas, detectar que dos se contradicen y corregirlas, sin reentrenar nada.
- Hay pocos datos y muchos casos límite (un producto de higiene abierto, una garantía ampliada), justo donde el ML rinde peor.
La lógica ofrece exactamente eso: un lenguaje formal con sintaxis precisa (qué frases están bien formadas), semántica precisa (qué significa que una frase sea verdadera) y reglas de inferencia que derivan conclusiones nuevas de forma mecánica y correcta. Es la herramienta que Aristóteles esbozó, Boole y Frege formalizaron y el Logic Theorist de 1956 llevó al ordenador (01-01). Empezamos por la versión más simple.
- Lógica proposicional: proposiciones y conectivos
Una proposición es una afirmación que es verdadera o falsa, sin término medio: "el producto está usado", "han pasado más de 14 días desde la entrega", "procede el reembolso". Las representamos con letras (P, Q, R) o con nombres legibles (usado, mas14, reembolso). Un modelo (o interpretación) es una asignación de verdadero/falso a cada proposición: describe un "mundo posible". Con 3 proposiciones hay 2³ = 8 mundos posibles.
Los conectivos combinan proposiciones en fórmulas compuestas. Su significado se define con tablas de verdad, que dicen el valor de la fórmula en cada combinación de valores de sus partes:
| P | Q | ¬P (no P) | P ∧ Q (y) | P ∨ Q (o) | P → Q (si P entonces Q) | P ↔ Q (si y solo si) |
|---|---|---|---|---|---|---|
| V | V | F | V | V | V | V |
| V | F | F | F | V | F | F |
| F | V | V | F | V | V | F |
| F | F | V | F | F | V | V |
Dos observaciones importantes para principiantes:
- El o lógico es inclusivo: "P ∨ Q" es verdadera también cuando ambas lo son. "Producto dañado o equivocado" admite que sea las dos cosas.
- La implicación P → Q solo es falsa cuando P es verdadera y Q falsa. Si P es falsa, la implicación es verdadera sea cual sea Q: la regla "si está usado y han pasado 14 días, no procede reembolso" no dice nada de los productos sin usar; no se incumple con ellos. Esto sorprende al principio, pero es exactamente lo que queremos de una regla: solo la viola el caso que cumple la condición y contradice la conclusión.
La regla de Diego con la que cerró el módulo 5 se escribe (usado ∧ mas14) → ¬reembolso. Su tabla de verdad tiene 8 filas y solo una es falsa: la del mundo en que el producto está usado, han pasado más de 14 días y aun así se reembolsa. Ese mundo es el que la regla prohíbe. Vamos a generar tablas como esta en código.
2.1 Código: evaluar fórmulas y generar tablas de verdad
Representamos una fórmula como un nombre de variable (cadena) o una tupla cuyo primer elemento es el operador: ("y", "P", "Q"), ("no", "R"), ("->", ("y", "P", "Q"), ("no", "R")). Un evaluador recursivo baja por la tupla y aplica la tabla de verdad de cada conectivo; itertools.product genera todas las combinaciones de V/F.
from itertools import product
def evaluar(formula, modelo):
"""Evalúa una fórmula proposicional dado un modelo (dict variable -> bool).
Una fórmula es un nombre de variable (str) o una tupla (operador, operandos...)."""
if isinstance(formula, str):
return modelo[formula] # una proposición: su valor en el modelo
op = formula[0]
if op == "no":
return not evaluar(formula[1], modelo)
if op == "y":
return evaluar(formula[1], modelo) and evaluar(formula[2], modelo)
if op == "o":
return evaluar(formula[1], modelo) or evaluar(formula[2], modelo)
if op == "->": # P -> Q equivale a (no P) o Q
return (not evaluar(formula[1], modelo)) or evaluar(formula[2], modelo)
if op == "<->":
return evaluar(formula[1], modelo) == evaluar(formula[2], modelo)
raise ValueError(f"operador desconocido: {op}")
def variables_de(formula, acc=None):
"""Recoge el conjunto de proposiciones que aparecen en la fórmula."""
acc = set() if acc is None else acc
if isinstance(formula, str):
acc.add(formula)
else:
for sub in formula[1:]:
variables_de(sub, acc)
return acc
def tabla_verdad(formula, nombre="fórmula"):
vars_ = sorted(variables_de(formula))
print(" | ".join(f"{v:^5}" for v in vars_) + " | " + nombre)
resultados = []
for valores in product([True, False], repeat=len(vars_)): # todas las combinaciones V/F
modelo = dict(zip(vars_, valores))
r = evaluar(formula, modelo)
resultados.append(r)
print(" | ".join(f"{'V' if x else 'F':^5}" for x in valores) + " | " + ("V" if r else "F"))
if all(resultados):
print("-> tautología")
elif not any(resultados):
print("-> contradicción")
else:
print(f"-> satisfacible ({sum(resultados)} de {len(resultados)} modelos)")
# P: "el producto está usado", Q: "han pasado más de 14 días", R: "procede el reembolso"
regla = ("->", ("y", "P", "Q"), ("no", "R"))
tabla_verdad(regla, "(P ∧ Q) → ¬R")Salida:
P | Q | R | (P ∧ Q) → ¬R V | V | V | F V | V | F | V V | F | V | V V | F | F | V F | V | V | V F | V | F | V F | F | V | V F | F | F | V -> satisfacible (7 de 8 modelos)
Explicación: evaluar es una función recursiva: si la fórmula es una cadena devuelve su valor en el modelo; si es una tupla, evalúa los operandos y combina los resultados con la tabla del conectivo. Fíjate en la línea de "->": implementamos la implicación como (no P) o Q, que es su definición. product([True, False], repeat=3) genera las 8 filas. La única fila falsa es la primera (usado, más de 14 días y reembolso), como habíamos razonado.
- Tautologías, contradicciones, satisfacibilidad y equivalencias útiles
Según cómo se comporte una fórmula en todos los modelos, la clasificamos:
| Tipo | Definición | Ejemplo | Uso práctico |
|---|---|---|---|
| Tautología | Verdadera en todos los modelos | P ∨ ¬P | Las equivalencias y las leyes lógicas son tautologías |
| Contradicción | Falsa en todos los modelos | P ∧ ¬P | Una base de reglas que la implica es inconsistente |
| Satisfacible | Verdadera en al menos un modelo | (P ∧ Q) → ¬R | La mayoría de las reglas de negocio |
Dos fórmulas son equivalentes (≡) si tienen la misma tabla de verdad, es decir, si su bicondicional es una tautología. Las equivalencias permiten reescribir reglas sin cambiar su significado, algo que hará el motor de 06-02 y que tú harás al depurar reglas de Diego:
| Equivalencia | Fórmula | Lectura con NovaMarket |
|---|---|---|
| De Morgan | ¬(P ∧ Q) ≡ ¬P ∨ ¬Q | "No es cierto que esté usado y fuera de plazo" = "no está usado o está en plazo" |
| De Morgan | ¬(P ∨ Q) ≡ ¬P ∧ ¬Q | "No está ni dañado ni equivocado" = "no dañado y no equivocado" |
| Contrapositiva | P → Q ≡ ¬Q → ¬P | "Si es defectuoso, reembolso" = "si no hay reembolso, no era defectuoso" |
| Implicación | P → Q ≡ ¬P ∨ Q | Es lo que programamos en evaluar |
| Doble negación | ¬¬P ≡ P | |
| Distributiva | P ∧ (Q ∨ R) ≡ (P ∧ Q) ∨ (P ∧ R) | Desdoblar una regla con "o" en dos reglas |
Cuidado con la conversa: P → Q no equivale a Q → P. "Si es defectuoso, procede reembolso" no significa "si procede reembolso, es defectuoso" (puede proceder por desistimiento). Es el error lógico más frecuente en reglas de negocio mal escritas.
Comprobamos De Morgan y una contradicción con el código anterior:
tabla_verdad(("<->", ("no", ("y", "P", "Q")), ("o", ("no", "P"), ("no", "Q"))), "¬(P∧Q) ↔ (¬P∨¬Q)")
tabla_verdad(("y", "P", ("no", "P")), "P ∧ ¬P")Salida (resumida):
P | Q | ¬(P∧Q) ↔ (¬P∨¬Q) V | V | V V | F | V F | V | V F | F | V -> tautología P | P ∧ ¬P V | F F | F -> contradicción
- Reglas de inferencia: modus ponens, modus tollens y resolución
Las tablas de verdad deciden cualquier pregunta, pero con n proposiciones tienen 2ⁿ filas: con las 40 proposiciones que puede manejar un sistema de devoluciones real son un billón de filas. Las reglas de inferencia evitan enumerar: son patrones que, aplicados a fórmulas verdaderas, producen fórmulas verdaderas (son correctas, sound).
| Regla | Premisas | Conclusión | Ejemplo NovaMarket |
|---|---|---|---|
| Modus ponens | P → Q, P | Q | "Si es defectuoso y está en garantía, envío gratis"; es defectuoso y está en garantía; luego envío gratis |
| Modus tollens | P → Q, ¬Q | ¬P | "Si el pedido salió de Getafe, el albarán lleva la letra G"; no lleva G; luego no salió de Getafe |
| Eliminación de ∧ | P ∧ Q | P | De "dañado y con retraso" se sigue "dañado" |
| Introducción de ∧ | P, Q | P ∧ Q | |
| Silogismo hipotético | P → Q, Q → R | P → R | Encadenar reglas |
| Resolución | P ∨ Q, ¬P ∨ R | Q ∨ R | Ver abajo |
El modus ponens es la regla estrella de los sistemas expertos: dado "si condiciones entonces conclusión" y las condiciones, añade la conclusión. Es literalmente lo que hará el motor de la sección 7.
La resolución merece una intuición: si sabemos que "el paquete llegó dañado o el producto era el equivocado" (P ∨ Q) y también que "no llegó dañado o la causa es transporte" (¬P ∨ R), entonces, sea cual sea el valor de P, algo se salva: si P es cierta, la segunda cláusula obliga a R; si es falsa, la primera obliga a Q. Luego Q ∨ R. La resolución es una única regla que, aplicada sobre fórmulas convertidas a una forma normal de cláusulas, es completa: si algo se sigue lógicamente, la resolución lo encuentra (a menudo demostrando que la negación lleva a la cláusula vacía, una contradicción). Es el motor interno de Prolog (sección 8) y de los demostradores automáticos; aquí basta con la idea.
- Base de conocimiento y consulta: KB ⊨ α por enumeración de modelos
Una base de conocimiento (KB) es un conjunto de fórmulas que damos por verdaderas: reglas generales más hechos del caso. Una consulta α es una fórmula sobre la que preguntamos. Decimos que la KB implica lógicamente α, escrito KB ⊨ α, cuando α es verdadera en todos los modelos en que la KB es verdadera. Es la definición precisa de "se sigue de lo que sabemos": no queda ningún mundo compatible con lo que sabemos en el que α sea falsa.
La forma más directa de comprobarlo es la enumeración de modelos: recorrer todas las asignaciones, quedarse con las que satisfacen la KB y mirar si α es cierta en todas. Es exponencial, pero para bases pequeñas es perfecta y, sobre todo, es la definición hecha código.
def implica(kb, alfa):
"""Devuelve True si KB ⊨ alfa: en todo modelo donde todas las fórmulas de KB
son verdaderas, alfa también lo es. Enumera todos los modelos."""
vars_ = set()
for f in kb + [alfa]:
variables_de(f, vars_)
vars_ = sorted(vars_)
modelos_kb = 0
for valores in product([True, False], repeat=len(vars_)):
modelo = dict(zip(vars_, valores))
if all(evaluar(f, modelo) for f in kb): # ¿el modelo satisface la KB?
modelos_kb += 1
if not evaluar(alfa, modelo): # ...y en él alfa es falsa: contraejemplo
print(f" contraejemplo: {modelo}")
return False
print(f" ({modelos_kb} modelos de la KB, alfa cierta en todos)")
return True
# Pedido 48377: auriculares NovaSound, devueltos usados a los 20 días
KB = [
("->", ("y", "usado", "mas14"), ("no", "reembolso")), # regla de Diego
("->", "defectuoso", "reembolso"), # si es defectuoso, reembolso (garantía)
"usado", # hecho
"mas14", # hecho
]
print("KB ⊨ ¬reembolso ?", implica(KB, ("no", "reembolso")))
print("KB ⊨ ¬defectuoso ?", implica(KB, ("no", "defectuoso")))
print("KB ⊨ reembolso ?", implica(KB, "reembolso"))
KB2 = KB[:2] + ["usado"] # ya no sabemos si han pasado 14 días
print("KB2 ⊨ ¬reembolso ?", implica(KB2, ("no", "reembolso")))Salida:
(1 modelos de la KB, alfa cierta en todos)
KB ⊨ ¬reembolso ? True
(1 modelos de la KB, alfa cierta en todos)
KB ⊨ ¬defectuoso ? True
contraejemplo: {'defectuoso': False, 'mas14': True, 'reembolso': False, 'usado': True}
KB ⊨ reembolso ? False
contraejemplo: {'defectuoso': True, 'mas14': False, 'reembolso': True, 'usado': True}
KB2 ⊨ ¬reembolso ? FalseExplicación, línea a línea del resultado:
- KB ⊨ ¬reembolso: con
usadoymas14como hechos, la regla de Diego obliga a¬reembolsoen el único modelo compatible. Modus ponens hecho por fuerza bruta. - KB ⊨ ¬defectuoso: esto no lo habíamos escrito, y sin embargo se sigue: si fuera defectuoso habría reembolso (regla 2), pero no lo hay (regla 1); luego no es defectuoso. Es modus tollens, y es una alarma útil: la KB tal como está escrita afirma que un producto usado devuelto a los 20 días no puede ser defectuoso, lo cual es absurdo. La lógica ha detectado que las dos reglas, juntas, son incompatibles con un caso real (usado, tardío y defectuoso: la KB sería inconsistente, sin ningún modelo, y de una KB inconsistente se sigue cualquier cosa). En 06-02 lo resolveremos con prioridades entre reglas; aquí basta con notar que la verificación formal ha encontrado un error de diseño que un modelo opaco jamás mostraría.
- KB ⊨ reembolso es falso, con un contraejemplo explícito: el mundo en que no es defectuoso y no hay reembolso es compatible con la KB.
- KB2: al quitar el hecho
mas14, ya no se puede concluir¬reembolso; el contraejemplo muestra un mundo conmas14falso, defectuoso y reembolsado. La consulta no está decidida: hace falta más información. Esa "pregunta que falta" será el motor de la interfaz de preguntas de 06-02.
- Lógica de primer orden: objetos, predicados, funciones y cuantificadores
La lógica proposicional trata "el producto está usado" como un átomo indivisible. Pero NovaMarket tiene 12.000 productos y 3.000 pedidos al día: no vamos a escribir una proposición usado_pedido_48377, usado_pedido_48378... La lógica de primer orden (LPO) añade estructura interna a las afirmaciones:
- Objetos: cosas del mundo: el pedido 48377, el aspirador NovaClean, el cliente Ana López, el almacén de Getafe. Se nombran con constantes (
p48377,getafe). - Predicados: propiedades y relaciones entre objetos, verdaderas o falsas:
Producto(x),Precintado(x),Defectuoso(x),EntregadoDesde(p, almacen),Cliente(c, p). - Funciones: devuelven un objeto a partir de otros:
DiasDesdeEntrega(p)devuelve un número,Almacen(p)devuelve un almacén. - Variables y cuantificadores: ∀x ("para todo x") y ∃x ("existe algún x").
- Los mismos conectivos ¬, ∧, ∨, →, ↔ de antes.
Ahora la política de devoluciones de NovaMarket se puede escribir de una vez para todos los productos (los datos de negocio que fijamos y que reaparecerán en todo el módulo: desistimiento 14 días, garantía legal 3 años para producto nuevo, precintados/higiene no devolvibles si se abren, etiqueta original, envío de devolución gratuito si es defectuoso; la normativa concreta de consumo debe revisarla siempre un profesional legal, aquí es un ejemplo docente):
∀p Producto(p) ∧ ¬Precintado(p) ∧ EtiquetaOriginal(p) ∧ DiasDesdeEntrega(p) ≤ 14 → Devolvible(p)
∀p Producto(p) ∧ Higiene(p) ∧ Abierto(p) → ¬Devolvible(p)
∀p Producto(p) ∧ Nuevo(p) ∧ Defectuoso(p) ∧ DiasDesdeEntrega(p) ≤ 1095 → EnGarantia(p)
∀p EnGarantia(p) → EnvioDevolucionGratis(p)
∃p Producto(p) ∧ Defectuoso(p) ∧ Almacen(p) = getafe ("algún producto defectuoso salió de Getafe")La primera regla es la que anuncia el enunciado de esta lección: Devolvible(p) ← Producto(p) ∧ ¬Precintado(p) ∧ DiasDesdeEntrega(p) ≤ 14, escrita con la flecha hacia la izquierda ("Devolvible si..."), la notación habitual de las reglas. Dos precisiones de lectura:
- ∀ va con →: "para todo p, si p es un producto no precintado dentro de plazo, entonces es devolvible". Escribir ∀p (Producto(p) ∧ Devolvible(p)) diría que todo objeto es un producto devolvible: falso.
- ∃ va con ∧: "existe p tal que p es producto y defectuoso y de Getafe". Escribir ∃p (Producto(p) → Defectuoso(p)) sería trivialmente cierto en cuanto exista algo que no sea producto (una implicación con antecedente falso es verdadera).
Las dos afirmaciones se relacionan por De Morgan cuantificado: ¬∀x P(x) ≡ ∃x ¬P(x) ("no todos los paquetes llegan bien" = "algún paquete no llega bien").
6.1 Unificación e inferencia, a nivel intuitivo
Para aplicar modus ponens a una regla con variables hace falta emparejarla con hechos concretos: unificar Producto(p) con Producto(p48377) sustituyendo p = p48377, comprobar ¬Precintado(p48377), EtiquetaOriginal(p48377) y DiasDesdeEntrega(p48377) ≤ 14 con la misma sustitución, y concluir Devolvible(p48377). La unificación es el algoritmo que encuentra la sustitución de variables que hace iguales dos expresiones (Cliente(c, p48377) unifica con Cliente(ana, p48377) con c = ana; Cliente(ana, p) no unifica con Cliente(luis, p48377)). Es la pieza que hace que una regla general se aplique a miles de casos, y el corazón de Prolog. En nuestro código nos ahorraremos la unificación con un truco habitual en la práctica: instanciar las reglas para un pedido concreto, es decir, convertir los predicados sobre el pedido en proposiciones (dentro_plazo, sin_usar), con lo que volvemos a la lógica proposicional, donde el modus ponens es un simple all(...).
- Cláusulas de Horn: encadenamiento hacia delante y hacia atrás
La inferencia general en LPO es cara (incluso indecidible en general). Los sistemas basados en reglas restringen las fórmulas a cláusulas de Horn: implicaciones con una conjunción de premisas positivas y una sola conclusión positiva, P₁ ∧ P₂ ∧ ... ∧ Pₙ → Q (un hecho es el caso n = 0). Con esa restricción existen dos algoritmos eficientes y correctos:
- Encadenamiento hacia delante (forward chaining, dirigido por los datos): partes de los hechos conocidos, aplicas modus ponens a toda regla cuyas premisas ya estén satisfechas, añades la conclusión y repites hasta que no aparezca nada nuevo. Deriva todo lo derivable. Adecuado cuando llegan datos y quieres todas las consecuencias (un pedido llega devuelto y el sistema calcula reembolso, envío gratuito, aviso al proveedor...).
- Encadenamiento hacia atrás (backward chaining, dirigido por el objetivo): partes de la pregunta ("¿procede reembolso?"), buscas reglas cuya conclusión sea ese objetivo, y conviertes sus premisas en nuevos subobjetivos, recursivamente, hasta llegar a hechos. Solo explora lo relevante para la pregunta y, si un hecho no está, puede preguntarlo al usuario: así funcionan los sistemas de diagnóstico interactivos de 06-02.
Reglas de devolución de NovaMarket como cláusulas de Horn (versión pequeña, sin conflictos entre reglas; los conflictos los trata 06-02):
| Regla | Premisas | Conclusión |
|---|---|---|
| R1 | dias_le_14 | dentro_plazo |
| R2 | dias_le_1095 | en_garantia (3 años ≈ 1095 días, producto nuevo) |
| R3 | dentro_plazo ∧ etiqueta_original ∧ sin_usar | desistimiento_ok |
| R4 | defectuoso ∧ en_garantia | garantia_ok |
| R5 | desistimiento_ok | procede_reembolso |
| R6 | garantia_ok | procede_reembolso |
| R7 | garantia_ok | envio_devolucion_gratis |
Traza hacia delante para el pedido 48213 (cafetera NovaBrew entregada hace 9 días, con etiqueta, sin usar, no defectuosa). Hechos iniciales: dias_le_14, dias_le_1095, etiqueta_original, sin_usar.
| Paso | Regla disparada | Premisas satisfechas | Hecho nuevo |
|---|---|---|---|
| 1 | R1 | dias_le_14 | dentro_plazo |
| 2 | R2 | dias_le_1095 | en_garantia |
| 3 | R3 | dentro_plazo, etiqueta_original, sin_usar | desistimiento_ok |
| 4 | R5 | desistimiento_ok | procede_reembolso |
| 5 | (R4, R6, R7 no se disparan: falta defectuoso) |
fin |
Traza hacia atrás para el pedido 47102 (aspirador NovaClean, 40 días, usado, sin etiqueta, defectuoso), objetivo procede_reembolso:
| Objetivo | Regla probada | Subobjetivos | Resultado |
|---|---|---|---|
| procede_reembolso | R5 | desistimiento_ok | → |
| desistimiento_ok | R3 | dentro_plazo, etiqueta_original, sin_usar | → |
| dentro_plazo | R1 | dias_le_14 | falla (no es hecho ni tiene regla) → R3 falla → R5 falla |
| procede_reembolso | R6 | garantia_ok | → |
| garantia_ok | R4 | defectuoso, en_garantia | defectuoso es hecho; → |
| en_garantia | R2 | dias_le_1095 | es hecho → R2 ok → R4 ok → R6 ok → demostrado |
Observa cómo el encadenamiento hacia atrás retrocede (backtracking) cuando una rama falla y prueba la siguiente regla con la misma conclusión.
7.1 Código: motor de encadenamiento hacia delante y hacia atrás
# Reglas de Horn: (nombre, [premisas], conclusión)
REGLAS = [
("R1", ["dias_le_14"], "dentro_plazo"),
("R2", ["dias_le_1095"], "en_garantia"), # 3 años ≈ 1095 días
("R3", ["dentro_plazo", "etiqueta_original", "sin_usar"], "desistimiento_ok"),
("R4", ["defectuoso", "en_garantia"], "garantia_ok"),
("R5", ["desistimiento_ok"], "procede_reembolso"),
("R6", ["garantia_ok"], "procede_reembolso"),
("R7", ["garantia_ok"], "envio_devolucion_gratis"),
]
def hechos_de(pedido):
"""Traduce los datos del pedido a hechos (átomos verdaderos). Lo que no aparece
se considera falso (suposición de mundo cerrado)."""
h = set()
if pedido["dias_desde_entrega"] <= 14:
h.add("dias_le_14")
if pedido["dias_desde_entrega"] <= 1095:
h.add("dias_le_1095")
if pedido["etiqueta_original"]:
h.add("etiqueta_original")
if not pedido["usado"]:
h.add("sin_usar")
if pedido["defectuoso"]:
h.add("defectuoso")
return h
def encadenar_adelante(hechos, reglas, verbose=True):
hechos = set(hechos)
aplicadas = []
cambio = True
while cambio: # repetir hasta que no se derive nada nuevo
cambio = False
for nombre, premisas, conclusion in reglas:
if conclusion not in hechos and all(p in hechos for p in premisas): # modus ponens
hechos.add(conclusion)
aplicadas.append(nombre)
cambio = True
if verbose:
print(f" {nombre}: {' ∧ '.join(premisas)} → {conclusion}")
return hechos, aplicadas
pedidos = {
48213: dict(producto="Cafetera NovaBrew", dias_desde_entrega=9, etiqueta_original=True, usado=False, defectuoso=False),
48377: dict(producto="Auriculares NovaSound", dias_desde_entrega=20, etiqueta_original=True, usado=True, defectuoso=False),
47102: dict(producto="Aspirador NovaClean", dias_desde_entrega=40, etiqueta_original=False, usado=True, defectuoso=True),
}
for id_pedido, datos in pedidos.items():
print(f"Pedido {id_pedido} ({datos['producto']}):")
hechos = hechos_de(datos)
print(f" hechos iniciales: {sorted(hechos)}")
finales, aplicadas = encadenar_adelante(hechos, REGLAS)
print(f" procede_reembolso: {'procede_reembolso' in finales} reglas: {aplicadas}\n")
def demostrar(objetivo, hechos, reglas, nivel=0):
"""Encadenamiento hacia atrás: ¿se puede demostrar el objetivo?"""
sangria = " " * nivel
if objetivo in hechos:
print(f"{sangria}{objetivo}: es un hecho")
return True
for nombre, premisas, conclusion in reglas:
if conclusion == objetivo: # regla que concluye el objetivo
print(f"{sangria}{objetivo}: pruebo {nombre} (necesito {premisas})")
if all(demostrar(p, hechos, reglas, nivel + 1) for p in premisas):
return True # todas las premisas demostradas
print(f"{sangria}{objetivo}: no demostrable")
return False
print("Hacia atrás, pedido 47102:")
print(demostrar("procede_reembolso", hechos_de(pedidos[47102]), REGLAS))Salida:
Pedido 48213 (Cafetera NovaBrew):
hechos iniciales: ['dias_le_1095', 'dias_le_14', 'etiqueta_original', 'sin_usar']
R1: dias_le_14 → dentro_plazo
R2: dias_le_1095 → en_garantia
R3: dentro_plazo ∧ etiqueta_original ∧ sin_usar → desistimiento_ok
R5: desistimiento_ok → procede_reembolso
procede_reembolso: True reglas: ['R1', 'R2', 'R3', 'R5']
Pedido 48377 (Auriculares NovaSound):
hechos iniciales: ['dias_le_1095', 'etiqueta_original']
R2: dias_le_1095 → en_garantia
procede_reembolso: False reglas: ['R2']
Pedido 47102 (Aspirador NovaClean):
hechos iniciales: ['defectuoso', 'dias_le_1095']
R2: dias_le_1095 → en_garantia
R4: defectuoso ∧ en_garantia → garantia_ok
R6: garantia_ok → procede_reembolso
R7: garantia_ok → envio_devolucion_gratis
procede_reembolso: True reglas: ['R2', 'R4', 'R6', 'R7']
Hacia atrás, pedido 47102:
procede_reembolso: pruebo R5 (necesito ['desistimiento_ok'])
desistimiento_ok: pruebo R3 (necesito ['dentro_plazo', 'etiqueta_original', 'sin_usar'])
dentro_plazo: pruebo R1 (necesito ['dias_le_14'])
dias_le_14: no demostrable
dentro_plazo: no demostrable
desistimiento_ok: no demostrable
procede_reembolso: pruebo R6 (necesito ['garantia_ok'])
garantia_ok: pruebo R4 (necesito ['defectuoso', 'en_garantia'])
defectuoso: es un hecho
en_garantia: pruebo R2 (necesito ['dias_le_1095'])
dias_le_1095: es un hecho
TrueExplicación:
REGLASes una lista de tuplas: nombre, lista de premisas y conclusión. Es toda la "base de conocimiento" de reglas; los hechos de cada pedido los producehechos_de, que instancia los predicados de primer orden (DiasDesdeEntrega(p) ≤ 14) en proposiciones (dias_le_14) para ese pedido concreto, como anunciamos en 6.1.encadenar_adelantees un bucle "hasta que nada cambie": en cada vuelta recorre las reglas y dispara (modus ponens) las que tienen todas las premisas en el conjunto de hechos y cuya conclusión aún no está. Devuelve los hechos finales y la lista de reglas aplicadas, que ya es una explicación rudimentaria ("procede reembolso porque R1, R3, R5"). En 06-02 la enriqueceremos.- Compara los tres pedidos: la cafetera se reembolsa por desistimiento; los auriculares usados y tardíos no derivan
procede_reembolso(nada lo demuestra, y con mundo cerrado eso significa "no"); el aspirador defectuoso a los 40 días se reembolsa por garantía y además tiene envío gratuito (R7), aunque no lleve etiqueta ni esté sin usar: la garantía es otra vía distinta del desistimiento, exactamente como en la política real. demostrares el encadenamiento hacia atrás en diez líneas: si el objetivo es un hecho, listo; si no, prueba cada regla que lo concluye y demuestra recursivamente sus premisas;all(...)se detiene en la primera premisa que falla (retroceso). La traza impresa reproduce la tabla de la sección 7 y muestra que solo se ha explorado lo relevante para la pregunta.
- Prolog, el lenguaje de la lógica
Todo lo anterior es tan regular que existe un lenguaje de programación cuyo intérprete es un motor de encadenamiento hacia atrás con unificación y resolución sobre cláusulas de Horn: Prolog (1972). Las mismas reglas se escriben casi igual que en la tabla, y la consulta es una pregunta:
% Hechos y reglas (una cláusula por línea; ":-" se lee "si")
dias_desde_entrega(p48213, 9).
etiqueta_original(p48213).
sin_usar(p48213).
dentro_plazo(P) :- dias_desde_entrega(P, D), D =< 14.
desistimiento_ok(P) :- dentro_plazo(P), etiqueta_original(P), sin_usar(P).
procede_reembolso(P) :- desistimiento_ok(P).
% Consulta interactiva:
% ?- procede_reembolso(p48213). -> true.
% ?- procede_reembolso(X). -> X = p48213.No lo ejecutaremos aquí (no forma parte del entorno del curso); lo mencionamos porque es el ejemplo puro de programación declarativa: describes qué es cierto y el intérprete busca cómo demostrarlo. En 07-01 compararemos Prolog con Python y con Lisp, y en 06-02 y 06-04 veremos las herramientas modernas (CLIPS, Drools, experta) que heredan estas ideas.
- Limitaciones: mundo cerrado, monotonía e incertidumbre
La lógica es precisa, pero esa precisión tiene un precio. Conviene conocer sus tres límites principales antes de construir un sistema con ella:
- Suposición de mundo cerrado: en nuestro motor, lo que no está en los hechos se trata como falso. Para el pedido 48377 concluimos "no procede reembolso" porque no pudimos demostrar que procediera, no porque demostráramos lo contrario. En bases de datos y reglas de negocio la suposición es cómoda (si un producto no consta como defectuoso, no lo es), pero puede ser peligrosa: "no consta que sea defectuoso" y "sabemos que no es defectuoso" son cosas distintas. La lógica pura (secciones 2-5) es de mundo abierto: si no se sabe, no se concluye nada; recuerda
KB2en la sección 5. - Monotonía: en lógica clásica, añadir conocimiento nunca elimina conclusiones. Pero el sentido común no funciona así: "los productos electrónicos tienen 3 años de garantía" y luego "salvo si son reacondicionados (1 año)" nos obliga a retirar una conclusión al saber más. Los sistemas expertos lo resuelven de forma pragmática con excepciones y prioridades entre reglas (06-02); la investigación en lógicas no monótonas es amplia y queda fuera del curso.
- Incertidumbre: la lógica es binaria. "El paquete llega abollado" no implica con certeza "daño en transporte": también puede ser un error de picking con embalaje deficiente. Los síntomas de una incidencia son ambiguos, y forzarlos a verdadero/falso pierde información. Precisamente el diagnóstico de incidencias del caso 9 necesita grados de creencia, y ese es el tema de 06-03: probabilidad, teorema de Bayes y redes bayesianas.
Y una limitación práctica que no es de la lógica sino del proceso: alguien tiene que escribir las reglas correctamente, mantenerlas y validarlas con casos. Ese trabajo se llama ingeniería del conocimiento y es el corazón de la próxima lección.
Errores Comunes y Consejos
- Confundir la implicación con la conversa. "Si defectuoso entonces reembolso" no autoriza a deducir "defectuoso" de "reembolso". Cuando escribas una regla, lee también su conversa en voz alta y comprueba que no la estás asumiendo.
- Pensar que P → Q es falsa cuando P es falsa. Una regla no se incumple en los casos que no cumplen su condición. La única fila falsa de la implicación es V → F.
- Escribir ∀ con ∧ o ∃ con →. Es el error de sintaxis más común en primer orden: "para todo p, si Producto(p) entonces..." y "existe p tal que Producto(p) y...".
- Usar tablas de verdad para todo. Son perfectas para entender y para verificar bases pequeñas (nuestro
implica), pero crecen como 2ⁿ. Para bases reales, encadenamiento con Horn o resolución. - Olvidar la suposición de mundo cerrado. Si tu motor devuelve "no procede", pregúntate si es "demostrado que no" o "no demostrado". Para decisiones con efectos sobre personas (02-04), la segunda debería llevar a "preguntar más" o a revisión humana, no a denegar.
- No comprobar la consistencia. Nuestra KB de la sección 5 implicaba
¬defectuoso, señal de que dos reglas chocaban en un caso real. Ejecuta consultas "absurdas" (¿implica la KB que ningún producto es defectuoso?) para cazar estos choques antes de que los cace un cliente. - Nombres de proposiciones ambiguos.
plazono dice si es "dentro" o "fuera". Usa nombres que sean afirmaciones completas y positivas (dentro_plazo,sin_usar): las cláusulas de Horn no admiten negaciones en las premisas y agradecerás la claridad.
Ejercicios
Ejercicio 1. Con tabla_verdad, comprueba si son tautologías: (a) la contrapositiva (P → Q) ↔ (¬Q → ¬P); (b) la conversa (P → Q) ↔ (Q → P); (c) el silogismo hipotético ((P → Q) ∧ (Q → R)) → (P → R). Interpreta cada resultado con proposiciones de NovaMarket (P: defectuoso, Q: procede reembolso, R: envío gratuito).
Ejercicio 2. Amplía la base KB de la sección 5 con la regla de la garantía tal como debería ser: si es defectuoso y está en garantía, procede reembolso (en lugar de "si es defectuoso, procede reembolso"), y añade el hecho en_garantia. Con implica, comprueba de nuevo si KB ⊨ ¬defectuoso. ¿Ha cambiado algo? ¿Qué te dice eso sobre la incompatibilidad detectada, y qué haría falta para resolverla de verdad? (No hace falta programar la solución; basta con razonarla: se hará en 06-02.)
Ejercicio 3. Añade a REGLAS de la sección 7 la política de productos de higiene: R8 higiene ∧ abierto → no_devolvible, y haz que hechos_de genere higiene y abierto a partir de dos nuevas claves del pedido. Ejecuta el encadenamiento hacia delante con un cepillo eléctrico NovaSmile abierto y devuelto a los 5 días con etiqueta. ¿Qué deriva el motor? ¿Es coherente? Explica por qué el motor de esta lección no puede resolver lo que ves y qué mecanismo hará falta.
Soluciones
Solución 1. (a) Tautología: las 4 filas son V; "si es defectuoso procede reembolso" equivale a "si no procede reembolso, no era defectuoso". (b) No es tautología (satisfacible: falla cuando P y Q difieren): que proceda el reembolso no implica que sea defectuoso (puede ser desistimiento). (c) Tautología (8 filas, todas V): las reglas se encadenan; es la base de que el encadenamiento hacia delante sea correcto: si defectuoso → reembolso y reembolso → aviso, entonces defectuoso → aviso.
Solución 2. Con ("->", ("y", "defectuoso", "en_garantia"), "reembolso") y el hecho en_garantia, implica(KB, ("no", "defectuoso")) sigue devolviendo True (un único modelo de la KB, en el que defectuoso es falso). No ha cambiado nada, porque el conflicto de fondo no era la formulación de la garantía sino que las dos reglas concluyen cosas contrarias (¬reembolso y reembolso) sobre un mismo pedido usado, tardío, en garantía y defectuoso; la lógica clásica no puede tener ambas y "sacrifica" la posibilidad de que sea defectuoso. Resolverlo de verdad exige que una regla prevalezca sobre la otra (la garantía sobre el desistimiento) o reescribir la regla de Diego con una excepción (usado ∧ mas14 ∧ ¬defectuoso → ¬reembolso), lo que en cláusulas de Horn no se puede expresar directamente (premisa negada). Los sistemas expertos lo hacen con prioridades y resolución de conflictos, tema de 06-02.
Solución 3. Con ("R8", ["higiene", "abierto"], "no_devolvible") y el pedido dict(dias_desde_entrega=5, etiqueta_original=True, usado=False, defectuoso=False, higiene=True, abierto=True) (y hechos_de añadiendo higiene/abierto cuando las claves sean verdaderas), el motor deriva a la vez procede_reembolso (por R1, R3, R5: está en plazo, con etiqueta y "sin usar") y no_devolvible (por R8). Es incoherente: un producto de higiene abierto no debe reembolsarse por desistimiento. El motor de esta lección solo añade hechos y no tiene forma de decir que R8 bloquea a R3/R5: las cláusulas de Horn no tienen negación en las premisas ni noción de prioridad. Hará falta un motor con resolución de conflictos (prioridad o especificidad: R8 es más específica que R3) y una memoria de trabajo que registre una única decision, que es lo que construiremos en 06-02.
Conclusión
En esta lección hemos abierto la mitad simbólica de la IA con su herramienta fundamental. Hemos visto por qué NovaMarket necesita conocimiento explícito y verificable para las devoluciones (las reglas preceden a los datos, deben justificarse y auditarse); la lógica proposicional con sus cinco conectivos y sus tablas de verdad, generadas con itertools.product; las nociones de tautología, contradicción y satisfacibilidad, y las equivalencias (De Morgan, contrapositiva) que permiten reescribir reglas sin cambiar su significado; las reglas de inferencia (modus ponens, modus tollens, resolución) que evitan enumerar; la implicación lógica KB ⊨ α, comprobada por enumeración de modelos, que además nos ha destapado un choque entre la regla de Diego y la de la garantía; la lógica de primer orden, con la que la política de devoluciones se escribe una vez para los 12.000 productos, y la unificación que la aplica a cada pedido; y los dos algoritmos de trabajo sobre cláusulas de Horn, el encadenamiento hacia delante (dirigido por los datos, encadenar_adelante) y hacia atrás (dirigido por el objetivo, demostrar), que han decidido el reembolso de la cafetera NovaBrew, los auriculares NovaSound y el aspirador NovaClean con una traza legible. Hemos cerrado con Prolog y con los límites de la lógica: mundo cerrado, monotonía e incertidumbre.
Ya tenemos el motor mínimo. Lo que falta es todo lo que convierte un motor en un sistema que Diego pueda usar: reglas con prioridad que resuelvan los conflictos que hemos dejado abiertos (la garantía frente al desistimiento, el producto de higiene abierto), una memoria de trabajo, un módulo que explique "por qué" y "cómo", una interfaz que pregunte al usuario los hechos que faltan y un proceso para extraer las reglas de la cabeza del experto y validarlas. Eso es un sistema experto, la tecnología que en los años 80 sacó la IA de los laboratorios (MYCIN, XCON, 01-01), y lo construiremos en Python puro para las devoluciones y garantías de NovaMarket en la próxima lección, 06-02.
Fundamentos de Inteligencia Artificial (IA)
Módulo 1: Introducción a la Inteligencia Artificial
Módulo 2: Principios Básicos de la IA
- Conceptos Fundamentales: Agentes, Entornos y Racionalidad
- Tipos de Inteligencia Artificial
- Los Datos como Materia Prima de la IA
- Ética y Consideraciones en IA
Módulo 3: Algoritmos en IA
- Introducción a los Algoritmos
- Algoritmos de Búsqueda
- Búsqueda con Adversario: Juegos y Minimax
- Algoritmos de Optimización
Módulo 4: Aprendizaje Automático (Machine Learning)
- Conceptos Básicos de Machine Learning
- Tipos de Aprendizaje Automático
- Preparación de Datos y Características
- Algoritmos de Machine Learning
- Evaluación y Validación de Modelos
- Sobreajuste, Regularización y Ajuste de Hiperparámetros
Módulo 5: Redes Neuronales y Deep Learning
- Introducción a las Redes Neuronales
- Arquitectura de Redes Neuronales
- Cómo Aprende una Red: Descenso del Gradiente y Retropropagación
- Deep Learning y sus Aplicaciones
- Transformers, Grandes Modelos de Lenguaje e IA Generativa
Módulo 6: Lógica y Sistemas Expertos
- Lógica en IA
- Sistemas Expertos
- Razonamiento con Incertidumbre: Probabilidad y Redes Bayesianas
- Aplicaciones de Sistemas Expertos
Módulo 7: Herramientas y Lenguajes de Programación en IA
- Lenguajes de Programación para IA
- Python Científico: NumPy, pandas y Matplotlib
- Herramientas y Librerías Populares
- Entornos de Desarrollo
Módulo 8: Proyectos y Casos de Estudio
Módulo 9: Ejercicios y Prácticas
- Ejercicios de Algoritmos
- Prácticas de Machine Learning
- Proyectos de Redes Neuronales
- Proyecto Integrador: de la Idea al Prototipo
