Cerramos el módulo 6 con una observación: hemos escrito seis módulos de código y, sin decirlo, hemos tomado decisiones de herramienta en cada uno. Python puro para los grafos y las reglas, scikit-learn y PyTorch para los modelos, y menciones de pasada a Prolog, Lisp, CLIPS o Drools que nunca instalamos. Esta lección ordena esa primera decisión, la del lenguaje, que es la que condiciona todas las demás. Veremos qué le pide la inteligencia artificial a un lenguaje de programación, por qué Python se ha convertido en su lengua franca a pesar de ser lento (y cómo se compensa esa lentitud, con un micro-benchmark que ejecutarás), qué aportaron y siguen aportando los lenguajes históricos de la IA simbólica (Lisp y Prolog), qué papel tienen R y Julia en el mundo científico, y qué lenguajes aparecen cuando un modelo sale del portátil y entra en producción: Java y Scala, C++, JavaScript, Rust, Go y, sobre todo, SQL, porque los datos de NovaMarket no viven en un CSV sino en una base de datos. Terminaremos con la idea que articula la industria, "entrenar en Python, servir en X", con los formatos de intercambio que la hacen posible, y con la decisión concreta para el taller de Marta. Es importante porque elegir mal el lenguaje no se nota en el prototipo, sino seis meses después, cuando el equipo intenta mantenerlo, integrarlo o acelerarlo. Las librerías concretas se tratan en 07-03 y los entornos en 07-04; aquí solo hablamos de lenguajes.
Contenido
- Qué le pide la IA a un lenguaje
- Python: por qué domina
- El punto débil de Python y cómo se compensa: micro-benchmark
- Los lenguajes históricos de la IA simbólica: Lisp y Prolog
- R y Julia: los lenguajes del mundo científico
- Lenguajes de producción y despliegue
- SQL: los datos viven en bases de datos
- "Entrenar en Python, servir en X": formatos de intercambio
- Tabla comparativa de lenguajes
- La decisión para NovaMarket
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- Qué le pide la IA a un lenguaje
Un programa de IA no es un programa cualquiera. Mira lo que hemos hecho a lo largo del curso: explorar grafos y árboles de juego (módulo 3), transformar tablas de miles de filas y entrenar modelos que multiplican matrices millones de veces (módulos 4 y 5), representar reglas y hechos y razonar sobre ellos (módulo 6), y en todos los casos experimentar: probar, medir, cambiar un parámetro y volver a probar. De ahí salen cinco exigencias:
| Exigencia | Qué significa | Dónde la hemos sentido |
|---|---|---|
| Expresividad | Escribir la idea con poco código y leerla después; estructuras de datos ricas (listas, diccionarios, tuplas) integradas en el lenguaje | El motor de reglas de 06-02 cabía en 60 líneas; heapq y deque en 03-02 |
| Ecosistema numérico | Arrays, álgebra lineal, estadística, optimización, gráficos, ya hechos y probados | NumPy, scikit-learn, PyTorch en los módulos 4 y 5 |
| Rendimiento (vía librerías nativas) | Que el cálculo pesado corra a velocidad de C o de GPU aunque el lenguaje sea lento | El MLP de 05-03 entrenó 300 épocas en segundos porque PyTorch está escrito en C++ |
| Interoperabilidad | Hablar con bases de datos, APIs web, ficheros de todo tipo, otros lenguajes | El informe con csv de 02-03; en 07-03 veremos FastAPI |
| Comunidad y talento | Documentación, ejemplos, respuestas en foros, gente a la que contratar | Cada error que buscaste en Internet mientras seguías el curso |
Ningún lenguaje gana en todo. La historia de los lenguajes en IA es la historia de cuál de estas exigencias pesaba más en cada época: en los años 60 y 70 la expresividad simbólica (Lisp, Prolog); en los 2000 la estadística (R); hoy el ecosistema numérico y la comunidad (Python), con el rendimiento delegado en librerías nativas y con lenguajes de sistemas para servir en producción.
- Python: por qué domina
Python nació en 1991 como lenguaje de propósito general, no pensado para IA. Su dominio actual se explica por la suma de cuatro factores:
- Sintaxis legible y dinámica. Se escribe casi como pseudocódigo (compara
encadenar_adelantede 06-01 con su descripción en prosa), no exige declarar tipos ni compilar, y el bucle "escribo, ejecuto, miro" dura segundos. Para experimentar es lo que importa. - Ecosistema. Sobre NumPy (2006, heredero de Numeric de 1995) se construyó todo lo demás: SciPy, pandas, Matplotlib, scikit-learn, y después Theano, TensorFlow y PyTorch. Cuando el deep learning despegó en 2012, Python ya tenía las herramientas y la comunidad científica que R no tenía en redes ni Lisp en números.
- Es un lenguaje "pegamento". Python es fácil de extender con código en C, C++ o CUDA (el lenguaje de las GPU de NVIDIA). NumPy, PyTorch, scikit-learn son en realidad envoltorios en Python de bibliotecas nativas: tú escribes
a @ by quien multiplica es una rutina BLAS en C o Fortran; escribesred(xb)y quien calcula es el núcleo C++ de PyTorch. Python pone la interfaz cómoda; el metal lo pone otro. - Notebooks. Jupyter (07-04) hizo de Python el entorno natural para el análisis exploratorio: código, resultados y gráficos en el mismo documento.
Además, Python es el lenguaje que ya saben los ingenieros de datos, los administradores de sistemas y los desarrolladores web, lo que reduce la fricción para que un equipo como el de NovaMarket incorpore IA sin cambiar de lenguaje.
- El punto débil de Python y cómo se compensa: micro-benchmark
Python es lento para el cálculo numérico escrito en Python. Dos razones:
- Es interpretado y dinámico. Cada
total += xen un bucle implica comprobar el tipo detotaly dex, buscar la operación de suma para esos tipos, crear un objeto float nuevo... decenas de instrucciones de máquina por cada suma que en C sería una. - El GIL (Global Interpreter Lock): en el intérprete estándar solo un hilo ejecuta código Python a la vez, así que los hilos no aceleran cálculo puro (sí la espera de red o disco). Existen desde hace poco versiones experimentales del intérprete sin GIL, pero la práctica habitual sigue siendo otra: sacar el cálculo del intérprete.
La compensación se llama vectorización: en lugar de un bucle Python que suma un millón de números uno a uno, se llama una función nativa que recorre los datos en C. Compruébalo:
import time, random
import numpy as np
N = 1_000_000
random.seed(42)
lista_a = [random.random() for _ in range(N)] # un millón de floats en una lista Python
lista_b = [random.random() for _ in range(N)]
def cronometrar(nombre, funcion, repeticiones=3):
"""Ejecuta funcion() varias veces y muestra el mejor tiempo en milisegundos."""
mejor = float("inf")
for _ in range(repeticiones):
inicio = time.perf_counter()
resultado = funcion()
mejor = min(mejor, time.perf_counter() - inicio)
print(f"{nombre:38s} {mejor*1000:9.2f} ms resultado={resultado:.4f}")
return mejor
# 1) Suma de un millón de números
def suma_bucle():
total = 0.0
for x in lista_a:
total += x
return total
def suma_builtin():
return sum(lista_a)
arr_a = np.array(lista_a) # copia a un ndarray de float64
arr_b = np.array(lista_b)
def suma_numpy():
return arr_a.sum()
# 2) Producto escalar: sum(a[i] * b[i])
def producto_bucle():
total = 0.0
for x, y in zip(lista_a, lista_b):
total += x * y
return total
def producto_numpy():
return float(arr_a @ arr_b)
t1 = cronometrar("suma: bucle for en Python", suma_bucle)
t2 = cronometrar("suma: sum() de Python", suma_builtin)
t3 = cronometrar("suma: numpy .sum()", suma_numpy)
t4 = cronometrar("producto escalar: bucle for + zip", producto_bucle)
t5 = cronometrar("producto escalar: numpy a @ b", producto_numpy)
print(f"aceleracion suma: x{t1/t3:.0f} aceleracion producto: x{t4/t5:.0f}")Salida en el equipo del curso (los tiempos absolutos variarán en el tuyo; las proporciones, poco):
suma: bucle for en Python 15.35 ms resultado=500014.5439 suma: sum() de Python 5.69 ms resultado=500014.5439 suma: numpy .sum() 0.33 ms resultado=500014.5439 producto escalar: bucle for + zip 29.34 ms resultado=249910.5634 producto escalar: numpy a @ b 0.39 ms resultado=249910.5634 aceleracion suma: x47 aceleracion producto: x75
Explicación, línea a línea de lo que importa:
time.perf_counter()es el reloj de alta resolución adecuado para medir intervalos; tomamos el mejor de tres para reducir el ruido del sistema operativo.suma_buclees Python puro: unas 15 ms para un millón de sumas.sum()es una función incorporada escrita en C, pero sigue recorriendo objetos Python uno a uno: 3 veces más rápida.arr_a.sum()recorre un bloque contiguo de números de 8 bytes con instrucciones vectoriales del procesador: unas 45 veces más rápida que el bucle.- El producto escalar amplía la brecha (unas 70 veces): el bucle Python hace dos operaciones por elemento y
@delega en BLAS, la biblioteca de álgebra lineal optimizada que también usan scikit-learn y PyTorch. - El resultado es idéntico en todas las variantes; solo cambia quién hace el trabajo. Con matrices en lugar de vectores, o en GPU, la diferencia llega a miles de veces.
De aquí sale la regla de oro del Python científico, que desarrollaremos en 07-02: no escribas bucles sobre números; escribe operaciones sobre arrays. Y cuando algo no se pueda vectorizar, existen las extensiones nativas (Cython, Numba, o directamente C++ enlazado con pybind11) y los compiladores JIT; forman parte del mismo patrón: Python organiza, el código nativo calcula.
- Los lenguajes históricos de la IA simbólica: Lisp y Prolog
En 01-01 situamos el nacimiento de la IA en los años 50 con John McCarthy, y en 06-01 vimos que la lógica fue su primer lenguaje formal. Cada una de esas dos tradiciones tuvo su lenguaje de programación.
4.1 Lisp: listas y código como datos
Lisp (McCarthy, 1958) es el segundo lenguaje de alto nivel más antiguo que sigue en uso. Su idea central: el programa se escribe con la misma estructura de datos que manipula, la lista entre paréntesis. Una función es una lista, un dato es una lista, y una función puede construir otra función y ejecutarla. Eso lo hizo ideal para la IA simbólica de los años 60-80: procesamiento de símbolos, árboles de búsqueda, reglas que se generan y transforman. Ejemplo ilustrativo (no se ejecuta en el entorno del curso) de una función que decide si un pedido está dentro del plazo de desistimiento y de cómo el código es un dato:
;; Definición: (defun nombre (parámetros) cuerpo). Todo son listas entre paréntesis.
(defun dentro-plazo (dias) (<= dias 14))
(dentro-plazo 9) ; => T (verdadero)
(mapcar #'dentro-plazo '(9 20 3)) ; => (T NIL T): aplica la función a cada elemento
;; Código como datos: una lista que ES una expresión, evaluada después con eval
(defvar regla '(<= 9 14)) ; la comilla evita evaluarla ahora
(eval regla) ; => Tdefun define funciones, mapcar aplica una función a una lista (nuestro map de Python), y eval ejecuta una lista como si fuera código: por eso los sistemas expertos de los 80 se escribían en Lisp, y de Lisp heredan Python y JavaScript ideas como las funciones de primera clase, lambda, la recolección de basura y el bucle interactivo leer-evaluar-imprimir (REPL) que hoy es la consola de Python. Sus dialectos actuales (Common Lisp, Scheme, Clojure sobre la JVM) tienen nichos, pero para IA su papel hoy es histórico.
4.2 Prolog: hechos, reglas y consultas
Prolog (Colmerauer y Kowalski, 1972) es la lógica de primer orden convertida en lenguaje: el programa son hechos y reglas en forma de cláusulas de Horn (06-01), y ejecutar es consultar: el intérprete busca una demostración por encadenamiento hacia atrás con unificación y retroceso, exactamente el algoritmo demostrar que escribimos en 06-01. Fue el lenguaje del proyecto japonés de la Quinta Generación (01-01). Las siete reglas de devolución de 06-01, ilustrativas en Prolog:
% Hechos del pedido 48213 (cafetera NovaBrew): entregado hace 9 días, con etiqueta, sin usar
dias_desde_entrega(p48213, 9).
etiqueta_original(p48213).
sin_usar(p48213).
% Hechos del pedido 48377 (auriculares NovaSound): 400 días, defectuoso
dias_desde_entrega(p48377, 400).
defectuoso(p48377).
% Reglas R1-R7 de 06-01. ":-" se lee "si"; la coma es "y"; las mayúsculas son variables
dentro_plazo(P) :- dias_desde_entrega(P, D), D =< 14. % R1
en_garantia(P) :- dias_desde_entrega(P, D), D =< 1095. % R2
desistimiento_ok(P) :- dentro_plazo(P), etiqueta_original(P), sin_usar(P). % R3
garantia_ok(P) :- defectuoso(P), en_garantia(P). % R4
procede_reembolso(P) :- desistimiento_ok(P). % R5
procede_reembolso(P) :- garantia_ok(P). % R6
envio_devolucion_gratis(P) :- garantia_ok(P). % R7
% Consultas (lo que se teclea en el intérprete y lo que responde):
% ?- procede_reembolso(p48213). -> true. (por R1, R3, R5)
% ?- procede_reembolso(p48377). -> true. (por R2, R4, R6)
% ?- envio_devolucion_gratis(p48213). -> false. (no es defectuoso)
% ?- procede_reembolso(X). -> X = p48213 ; X = p48377.Fíjate en la última consulta: X es una variable y Prolog devuelve todos los pedidos para los que puede demostrar el objetivo; en Python tuvimos que escribir el bucle nosotros. Prolog sigue vivo en nichos (motores de reglas, análisis de lenguaje, verificación, planificación) y su descendencia son los motores de reglas de 06-02/06-04 (CLIPS, Drools, experta) y las bases de datos deductivas (Datalog). Lo que no tiene es ecosistema numérico ni comunidad de ML: no entrenarías la regresión logística de 04-04 en Prolog.
- R y Julia: los lenguajes del mundo científico
5.1 R: la estadística como lenguaje
R (1993) nació de S, el lenguaje de los estadísticos de Bell Labs, y sigue siendo el lenguaje de referencia en estadística académica, bioestadística y ciencia de datos "clásica": tiene el modelo lineal, los tests, los gráficos y miles de paquetes estadísticos (CRAN) que en Python a veces no existen o llegan tarde. El tidyverse (dplyr, ggplot2) es la referencia de manipulación de datos y gráficos por gramática. Ejemplo ilustrativo (no ejecutable en el entorno del curso): la regresión que Diego querría para saber si el importe y la entrega explican la devolución.
# Ilustrativo: R lee pedidos.csv y ajusta una regresión logística con la fórmula
pedidos <- read.csv("pedidos.csv")
modelo <- glm(devuelto ~ importe + dias_entrega + cliente_nuevo + categoria,
data = pedidos, family = binomial)
summary(modelo) # coeficientes, errores estándar, p-valores, en una tablaLa sintaxis de fórmula (y ~ x1 + x2) y el summary con inferencia estadística son lo que R hace mejor que nadie: en scikit-learn (04-04) tuvimos coeficientes pero no p-valores, porque scikit-learn está pensado para predecir, no para explicar estadísticamente. R pierde frente a Python en deep learning, en despliegue y en integración con sistemas; los equipos mixtos suelen tener a los estadísticos en R y a los ingenieros en Python, y se hablan a través de CSV, bases de datos o paquetes puente.
5.2 Julia: velocidad de C con sintaxis científica
Julia (2012) se diseñó para resolver el "problema de los dos lenguajes": prototipar en Python y reescribir en C++ para producción. Compila justo a tiempo (JIT) a código nativo, así que un bucle escrito en Julia corre a velocidad de C sin vectorizar, y su sintaxis está pensada para matemáticas (índices desde 1, operadores unicode, broadcasting con punto). Ilustrativo:
# Ilustrativo: la suma y el producto escalar del benchmark, en Julia. Un bucle es rápido.
a = rand(1_000_000); b = rand(1_000_000)
function producto(a, b)
total = 0.0
for i in eachindex(a)
total += a[i] * b[i]
end
return total
end
producto(a, b) # el bucle compilado corre a velocidad nativa
sum(a .* b) # versión vectorizada: el punto aplica * elemento a elementoJulia tiene comunidad fuerte en computación científica, ecuaciones diferenciales y optimización, y librerías de ML (Flux, MLJ), pero un ecosistema mucho menor que Python y menos demanda en empresas. Es la opción a vigilar si el cuello de botella de tu proyecto es un algoritmo numérico propio que no cabe en NumPy ni en PyTorch.
- Lenguajes de producción y despliegue
Un modelo entrenado tiene que servirse: responder en milisegundos dentro de la web de NovaMarket, ejecutarse en el móvil del repartidor o correr en un cluster sobre terabytes de pedidos. Ahí aparecen otros lenguajes, cada uno con su papel:
- Java y Scala. El ecosistema empresarial (bancos, telecomunicaciones, grandes tiendas) corre en la JVM. Apache Spark, el motor de datos a escala, está escrito en Scala y se usa desde Scala, Java o Python (PySpark). Un modelo entrenado en Python puede ejecutarse dentro de una aplicación Java a través de ONNX (sección 8) o de librerías Java de ML (Deeplearning4j, Tribuo), o simplemente llamando a un servicio Python por HTTP.
- C++. El lenguaje de los motores: PyTorch, TensorFlow, ONNX Runtime, OpenCV y los núcleos de NumPy están escritos en C++ (y CUDA). Cuando la inferencia tiene que ir a máxima velocidad o embeberse en un dispositivo (una cámara del almacén de Zaragoza contando cajas), se carga el modelo desde C++ con libtorch (la API C++ de PyTorch) u ONNX Runtime. Casi nadie entrena en C++; muchos sirven en C++ sin saberlo.
- JavaScript / TypeScript. El lenguaje del navegador. TensorFlow.js y ONNX Runtime Web ejecutan modelos en el cliente (sin enviar datos al servidor: útil para privacidad, 02-04) y Node.js sirve APIs. Un recomendador ligero de NovaMarket podría reordenar productos en la propia página.
- Rust y Go. Lenguajes de sistemas modernos, seguros y rápidos, para servicios: servidores de inferencia, herramientas de datos (Polars, el DataFrame rápido que veremos en 07-03, está escrito en Rust; muchos tokenizadores de LLM también). Go domina en infraestructura (Docker y Kubernetes están en Go). No se entrena en ellos; se sirve y se orquesta.
- Kotlin/Swift para móviles con modelos exportados (TensorFlow Lite, Core ML), y CUDA/Triton para quien escribe núcleos de GPU. Menciones para el mapa; fuera del alcance del curso.
- SQL: los datos viven en bases de datos
El lenguaje que sí tiene que saber todo el equipo de Marta, además de Python, es SQL. Los pedidos reales de NovaMarket no están en pedidos.csv: están en la base de datos transaccional de la tienda y en el almacén de datos (data warehouse). El primer paso de casi cualquier proyecto es una consulta SQL que extrae, filtra y agrega. Y muchas agregaciones son más rápidas y más claras en SQL, en la base de datos, que trayendo millones de filas a pandas.
Ejemplo ejecutable: cargamos los 3.000 pedidos sintéticos de 04-01 en una base de datos SQLite en memoria (Python la trae de serie) y calculamos la tasa de devolución por categoría, la consulta que Diego pediría el primer día:
import sqlite3
from novamarket_ml import generar_pedidos_ml # el generador del módulo 4
pedidos = generar_pedidos_ml(3000, 42) # DataFrame con importe, categoria, devuelto...
con = sqlite3.connect(":memory:") # base de datos SQLite temporal en RAM
pedidos.to_sql("pedidos", con, index=False) # pandas crea la tabla y la rellena
sql = """
SELECT categoria,
COUNT(*) AS pedidos,
SUM(devuelto) AS devueltos,
ROUND(100.0 * AVG(devuelto), 1) AS tasa_devolucion_pct,
ROUND(AVG(importe), 2) AS importe_medio
FROM pedidos
WHERE importe > 20 -- descartamos pedidos minúsculos
GROUP BY categoria
ORDER BY tasa_devolucion_pct DESC;
"""
for fila in con.execute(sql):
print(fila)Salida:
('electronica', 1006, 213, 21.2, 126.94)
('informatica', 596, 97, 16.3, 129.18)
('hogar', 887, 122, 13.8, 128.16)
('accesorios', 430, 56, 13.0, 130.97)Lectura: SELECT elige columnas y expresiones; COUNT(*) cuenta filas del grupo; SUM(devuelto) cuenta devueltos porque la columna vale 0/1; AVG(devuelto) es directamente la tasa (la media de un 0/1 es una proporción, el mismo truco que usamos en pandas en 04-01); WHERE filtra antes de agrupar; GROUP BY categoria forma un grupo por categoría; ORDER BY ... DESC ordena de mayor a menor. La electrónica devuelve el 21 % y los accesorios el 13 %, coherente con la "verdad oculta" del generador (el término +1.0 * (categoria == "electronica")). En 07-02 haremos exactamente esta operación con groupby de pandas para comparar.
SQL es declarativo (dices qué quieres, no cómo calcularlo, como Prolog), es el mismo en PostgreSQL, MySQL, SQL Server, BigQuery o Snowflake con pequeñas variantes, y es la frontera natural entre el equipo de datos y el de IA: la mayoría de las características de 04-03 (pedidos_previos, tasa_devolucion_previa) se pueden calcular con SQL en el almacén de datos y servir ya listas al modelo.
- "Entrenar en Python, servir en X": formatos de intercambio
La consecuencia de las secciones 6 y 7 es una arquitectura que se repite en casi todas las empresas:
flowchart LR
BD[(Base de datos<br/>SQL)] -->|consulta / extracción| PY["Python<br/>pandas + scikit-learn / PyTorch<br/>exploración y entrenamiento"]
PY -->|joblib / pickle| SRV1["Servicio Python<br/>API HTTP FastAPI"]
PY -->|exportación ONNX| ONNX[Fichero .onnx]
PY -->|state_dict / TorchScript| SRV1
ONNX --> JAVA["Aplicación Java<br/>ONNX Runtime"]
ONNX --> CPP["Motor C++ / dispositivo<br/>ONNX Runtime, libtorch"]
ONNX --> WEB["Navegador / móvil<br/>ONNX Runtime Web, TF.js"]
SRV1 --> TIENDA[Web de NovaMarket]
JAVA --> TIENDA
CPP --> ALM["Cámaras / almacén"]
WEB --> TIENDA
Los formatos de intercambio son la pieza que permite cambiar de lenguaje sin reentrenar:
| Formato | Qué guarda | Quién lo lee | Riesgos y límites |
|---|---|---|---|
| pickle / joblib | Cualquier objeto Python serializado (el Pipeline completo de 04-03, con su ColumnTransformer) |
Solo Python, con las mismas versiones de scikit-learn y dependencias | Ejecuta código al cargarse: nunca cargues un pickle de origen no fiable; se rompe al cambiar de versión; no lo lee otro lenguaje |
state_dict de PyTorch (torch.save) |
Los tensores de parámetros de una red (los 497 números del MLP de 05-02), no la arquitectura | Python con PyTorch; hay que reconstruir la red con el mismo código antes de cargar | Es un pickle por debajo: mismas cautelas de seguridad |
| ONNX (Open Neural Network Exchange) | El grafo de cálculo del modelo en un formato estándar abierto | ONNX Runtime desde Python, C++, Java, C#, JavaScript, móviles; conversores desde PyTorch, scikit-learn (skl2onnx), TensorFlow |
No todas las operaciones se convierten (los pasos de preprocesado exóticos suelen dar guerra); versiones de operadores |
| SavedModel / TFLite / Core ML | Modelos TensorFlow, para móvil (TFLite) o Apple (Core ML) | Sus ecosistemas | Ligados a un proveedor |
| PMML | Modelos estadísticos clásicos en XML | Herramientas empresariales antiguas, Java | Anticuado, poco expresivo |
En 07-03 ejecutaremos el guardado y recarga con joblib y state_dict y comprobaremos que el modelo recargado predice lo mismo. Aquí basta con retener la regla: guardar un modelo es guardar código + pesos + versiones; el formato que elijas determina desde qué lenguaje podrás usarlo y cuánto tiempo seguirá cargando.
- Tabla comparativa de lenguajes
| Lenguaje | Paradigma | Fortaleza en IA | Ecosistema IA | Cuándo elegirlo |
|---|---|---|---|---|
| Python | Multiparadigma, dinámico, interpretado | Prototipado, entrenamiento, ciencia de datos, LLM | El mayor con diferencia (NumPy, pandas, scikit-learn, PyTorch, Hugging Face...) | Por defecto para casi todo lo que hemos hecho en el curso |
| Lisp | Funcional, simbólico, código = datos | IA simbólica histórica, metaprogramación | Reducido hoy | Interés histórico; nichos en Clojure/Common Lisp |
| Prolog | Lógico, declarativo | Reglas, unificación, búsqueda con retroceso | Pequeño, especializado | Cuando el problema es lógica pura (06-01); hoy suele preferirse un motor de reglas embebido |
| R | Funcional/vectorial, estadístico | Estadística, inferencia, gráficos, informes | Muy grande en estadística (CRAN, tidyverse); menor en DL | Análisis estadístico riguroso, equipos de estadísticos, bioestadística |
| Julia | Multiparadigma, compilado JIT | Cálculo científico rápido sin dos lenguajes | Mediano y creciente | Algoritmos numéricos propios donde el bucle es el cuello de botella |
| Java / Scala | Orientado a objetos / funcional, JVM | Integración empresarial, Spark, servicios | Grande en datos (Spark, Kafka), medio en ML | Servir modelos dentro de sistemas Java existentes; datos a escala |
| C++ | Imperativo/OO, compilado | Motores, inferencia de máxima velocidad, embebido | Es el núcleo de casi todo (PyTorch, ONNX Runtime, OpenCV) | Rendimiento extremo, dispositivos, escribir el propio motor |
| JavaScript / TS | Multiparadigma, dinámico | Inferencia en navegador y Node, interfaces | TF.js, ONNX Runtime Web | Modelos en el cliente, demos web, privacidad |
| Rust / Go | Sistemas, compilados, seguros | Servicios de inferencia, herramientas de datos, infraestructura | Creciente (Polars, tokenizadores) / infraestructura | Servicios de alto rendimiento y fiabilidad |
| SQL | Declarativo, relacional | Extracción, agregación, características en el almacén de datos | Universal en bases de datos | Siempre que los datos estén en una base de datos, es decir, casi siempre |
- La decisión para NovaMarket
Marta reúne al equipo (dos analistas, un desarrollador backend en Java, ella misma) y Diego pregunta lo de siempre: "¿cuánto cuesta y en qué nos metemos?". La decisión que sale, y que sostendrá el módulo 8:
- Python para explorar, prototipar y entrenar. Es lo que sabe el equipo de datos, lo que hemos usado en los módulos 3-6 y donde están las librerías (07-03). Con reglas de estilo: vectorizar (07-02), y no reescribir en otro lenguaje hasta que un perfil demuestre que hace falta.
- SQL para extraer y agregar. Las consultas de la sección 7 se hacen en el almacén de datos; a Python llega solo lo necesario, ya agregado cuando se pueda. Las características que se puedan calcular en SQL (historial del cliente) se calculan allí, para que entrenamiento y producción usen la misma definición.
- Servir en Python con una API HTTP (FastAPI, 07-03) que la web de NovaMarket llama: el predictor de devoluciones, el recomendador y el asistente empiezan así, porque es lo más barato de construir y mantener con el equipo actual.
- ONNX en la recámara si el desarrollador Java necesita ejecutar un modelo dentro de la plataforma de la tienda sin depender de un servicio Python, o si algún día una cámara del almacén debe ejecutar el clasificador de 05-04 en local.
- Nada de Prolog, Lisp, R ni Julia en producción: se conocen, se respetan, y las ideas de Prolog viven en el motor de reglas Python de 06-02 (o en un motor de reglas empresarial si crece).
Con esta decisión, lo que queda por aprender es cómo se usa bien Python para datos: NumPy, pandas y Matplotlib, las librerías que en el módulo 4 utilizamos sin explicarlas. Es la próxima lección.
Errores Comunes y Consejos
- "Python es lento, así que reescribo en C++". Casi nunca. El 95 % de las veces el problema es un bucle Python sobre números que se vectoriza en una línea de NumPy (sección 3) o una consulta que debería haberse hecho en SQL. Mide antes (perfilador o
time.perf_counter) y reescribe solo el 5 % caliente. - Confundir el lenguaje con la librería. "PyTorch es rápido" no significa "Python es rápido"; significa que estás llamando a C++ desde Python. Si metes un bucle Python dentro del paso de entrenamiento, vuelves a la lentitud.
- Cargar pickles de cualquier sitio. Un
.pklo un.ptdescargado puede ejecutar código arbitrario al abrirse. Solo modelos de origen fiable; para compartir con terceros, prefiere formatos que no ejecuten código (ONNX, safetensors). - Elegir el lenguaje por moda o por currículum. Julia o Rust son excelentes, pero si el equipo sabe Python y el cuello de botella no existe, introducirlos añade coste de mantenimiento sin beneficio: es la pregunta de Diego.
- Olvidar SQL. El error más frecuente en perfiles que vienen de la IA académica: cargar toda la tabla en pandas y filtrar allí lo que la base de datos habría filtrado en milisegundos. Aprende
SELECT,WHERE,GROUP BY,JOINcon la misma seriedad quefit/predict. - Ignorar las versiones. El pickle del pipeline entrenado hoy puede no cargar con la próxima versión de scikit-learn. Apunta las versiones (07-04) junto al modelo.
Ejercicios
Ejercicio 1. Amplía el micro-benchmark de la sección 3 con una tercera operación: la media de los cuadrados, sum(x*x for x in lista) / N frente a (arr ** 2).mean(). Antes de ejecutar, predice si la aceleración será mayor o menor que la de la suma simple, y por qué. Ejecuta y compara.
Ejercicio 2. Escribe (sin ejecutar, es Prolog ilustrativo) las dos reglas que faltaban del ejercicio 3 de 06-01: la regla R8 "producto de higiene abierto no es devolvible" y un hecho para un cepillo NovaSmile abierto. Añade una consulta y su respuesta esperada. Después responde: ¿qué problema de 06-01 (conflicto entre R8 y R3/R5) sigue existiendo en la versión Prolog y por qué el lenguaje no lo resuelve solo?
Ejercicio 3. Modifica la consulta SQL de la sección 7 para obtener la tasa de devolución por categoría y por si el cliente es nuevo, ordenada por categoría y luego por tasa, y solo para grupos con al menos 100 pedidos (pista: HAVING, que filtra después de agrupar). Ejecútala e interpreta la fila con mayor tasa a la luz de la "verdad oculta" del generador de 04-01.
Soluciones
Solución 1. Añadiendo al script:
def cuadrados_bucle():
return sum(x * x for x in lista_a) / N
def cuadrados_numpy():
return float((arr_a ** 2).mean())
t6 = cronometrar("media de cuadrados: generador Python", cuadrados_bucle)
t7 = cronometrar("media de cuadrados: numpy (a**2).mean()", cuadrados_numpy)
print(f"aceleracion cuadrados: x{t6/t7:.0f}")En el equipo del curso: unos 28,6 ms para el generador frente a unos 0,87 ms para NumPy (aceleración de unas 33 veces, resultado 0,3332 en ambos, que es la media teórica de x² para x uniforme en [0, 1], 1/3). La predicción razonable era "del mismo orden que la suma, quizá algo menor": el bucle Python hace ahora dos operaciones por elemento (multiplicar y sumar), pero NumPy también hace dos pasadas (** 2 crea un array temporal de un millón de floats y .mean() lo recorre), así que la ventaja se reparte y queda algo por debajo de la de la suma simple. Aun así, decenas de veces. La lección: la brecha no depende de la operación concreta sino de quién ejecuta el bucle; y que las expresiones NumPy encadenadas crean arrays temporales, un coste que en 07-02 aprenderemos a tener en cuenta.
Solución 2.
higiene(p50001).
abierto(p50001).
dias_desde_entrega(p50001, 5).
etiqueta_original(p50001).
sin_usar(p50001).
no_devolvible(P) :- higiene(P), abierto(P). % R8
% ?- no_devolvible(p50001). -> true.
% ?- procede_reembolso(p50001). -> true. (por R1, R3, R5: ¡sigue derivándose!)Prolog deriva ambas cosas, igual que nuestro motor de 06-01: las cláusulas de Horn no tienen prioridad ni negación en las premisas, y el intérprete se limita a demostrar lo que se le pregunta. Para que R8 bloquee el reembolso habría que reescribir R3 con la negación por fallo de Prolog (\+ no_devolvible(P)), que es justamente la resolución de conflictos por especificidad que construimos a mano en 06-02. El lenguaje da el motor; la política de conflictos la sigue poniendo el ingeniero.
Solución 3.
sql = """
SELECT categoria, cliente_nuevo,
COUNT(*) AS pedidos,
ROUND(100.0 * AVG(devuelto), 1) AS tasa_pct
FROM pedidos
GROUP BY categoria, cliente_nuevo
HAVING COUNT(*) >= 100
ORDER BY categoria, tasa_pct DESC;
"""
for fila in con.execute(sql):
print(fila)Salida:
('accesorios', 1, 143, 25.9)
('accesorios', 0, 296, 6.8)
('electronica', 1, 316, 41.5)
('electronica', 0, 722, 11.6)
('hogar', 1, 266, 29.7)
('hogar', 0, 642, 6.9)
('informatica', 1, 181, 30.4)
('informatica', 0, 434, 9.9)(Hemos quitado el filtro WHERE importe > 20 para contar todos los pedidos; por eso los totales por categoría no coinciden con los de la sección 7.) La fila con mayor tasa es electrónica + cliente nuevo (41,5 %): en el generador de 04-01 la electrónica suma +1,0 al logit y el cliente nuevo +1,6, además del término cruzado nuevo × importe. Todos los grupos superan los 100 pedidos, así que HAVING no elimina ninguno aquí; con un filtro de 300 se quedarían solo los recurrentes de electrónica, hogar e informática y los nuevos de electrónica. Observa que en cada categoría los clientes nuevos devuelven entre 3 y 4 veces más: la misma señal que la regresión logística de 04-04 aprendió sola.
Conclusión
Hemos ordenado la primera decisión del taller de Marta. Un lenguaje para IA debe ser expresivo, tener ecosistema numérico, ofrecer rendimiento a través de librerías nativas, interoperar con datos y sistemas y contar con comunidad. Python gana en cuatro de las cinco y compensa la quinta, su lentitud como intérprete y el GIL, con la vectorización (nuestro benchmark: 45-75 veces más rápido con NumPy que con un bucle) y las extensiones nativas: Python organiza, C/C++/CUDA calculan. Hemos visto de dónde venimos, Lisp con su código como datos y Prolog con las reglas de devolución de 06-01 escritas como hechos, reglas y consultas; qué aportan R (estadística e inferencia) y Julia (velocidad sin dos lenguajes); qué lenguajes esperan al modelo cuando sale del portátil (Java/Scala y Spark, C++ con libtorch y ONNX Runtime, JavaScript en el navegador, Rust y Go en los servicios) y por qué SQL es la segunda lengua obligatoria del equipo, con la tasa de devolución por categoría calculada en la base de datos. La arquitectura "entrenar en Python, servir en X" se apoya en formatos de intercambio (pickle/joblib con sus riesgos, state_dict, ONNX) y la decisión de NovaMarket es clara: Python para prototipar y entrenar, SQL para extraer, servicio Python para servir y ONNX en la recámara.
Ahora hay que aprender a usar bien ese Python. En los módulos 4 y 5 escribimos generar_pedidos_ml, groupby, to_datetime, máscaras booleanas y curvas con Matplotlib sin detenernos a explicarlos. En 07-02, Python Científico: NumPy, pandas y Matplotlib, abriremos esas tres librerías con los pedidos y la demanda semanal de NovaMarket: arrays y broadcasting, DataFrames, agrupaciones, uniones y fechas, y gráficos que se describen antes de dibujarse.
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
