domingo, 30 de agosto de 2026

IA. Modelos

Perceptrón III.

IA. Modelos y paradigmas. Machine Learning

En las entradas anteriores hemos utilizado el problema del AND lógico para introducir el perceptrón y analizar su proceso de aprendizaje. El empleo de Calc como soporte nos ha permitido visualizar los datos, los cálculos y la evolución de los parámetros durante el aprendizaje.

Este planteamiento resulta apropiado desde el punto de vista didáctico, pero presenta dos limitaciones evidentes. Por una parte, el AND es un problema muy sencillo y completamente conocido, por lo que permite comprobar fácilmente el comportamiento del perceptrón, pero dice poco sobre su utilidad práctica. Por otra, aunque Calc resulta un excelente soporte para visualizar y seguir el funcionam iento de la arquitectura matemática del preceptrón, no constituye la forma más funcional de implementarlo.

Por tanto, el siguiente paso consiste en afrontar ambas limitaciones: utilizar un conjunto de datos que permita evaluar realmente el comportamiento del perceptrón y dotarlo de un soporte de ejecución que permita utilizarlo como un sistema de aprendizaje propiamente dicho. Antes de hacerlo, sin embargo, resulta necesario preguntarnos para qué podría ser útil un modelo de estas características en un contexto profesional concreto.

En nuestro caso, centraremos inicialmente la atención en algunos posibles ámbitos de aplicación del perceptrón en la intervención profesional de los orientadores educativos. Este planteamiento nos permitirá valorar sus posibilidades, pero también poner de manifiesto una cuestión fundamental: el perceptrón trabaja con una representación deliberadamente simplificada de la realidad, basada en una combinación lineal de variables.

El uso del problema AND como ejemplo puede llevar a pensar que esta representación está limitada a dos variables nominales y, por tanto, a una representación bidimensional. Sin embargo, esta limitación pertenece al ejemplo que hemos utilizado, no a la arquitectura del perceptrón. El modelo puede incorporar tantas variables de entrada como sea necesario, siempre que estas puedan representarse numéricamente.

Lo que sí es constitutivo del perceptrón clásico es la naturaleza de su salida: se trata de una clasificación binaria, es decir, la respuesta del modelo solo puede situarse en una de dos categorías, habitualmente representadas mediante los valores 0 y 1. Esta característica afecta también a las etiquetas del conjunto de datos, que deben expresar esas dos posibles clases.

Esta característica no constituye necesariamente una limitación para la intervención educativa, ya que existen numerosas situaciones en las que la decisión profesional puede formularse en términos dicotómicos: pertenencia o no pertenencia a una determinada categoría, presencia o ausencia de una situación de riesgo, necesidad o no de un determinado tipo de apoyo, etc. El problema radica en la dificultad de determinar qué variables son relevantes para establecer esa pertenencia y si la relación entre dichas variables y la categoría de destino puede representarse adecuadamente mediante una combinación lineal.

En otras palabras, el reto no consiste tanto en que la realidad educativa sea necesariamente incompatible con una clasificación binaria, sino en conseguir una representación de esa realidad en la que las variables seleccionadas permitan discriminar coherentemente entre las dos categorías. Cuanto más compleja sea la realidad que pretendemos categorizar, mayor será también la posibilidad de encontrar casos excepcionales que no encajen en la distribución general y que, por tanto, dificulten o impidan que una única frontera lineal separe correctamente ambas categorías.

Esta limitación puede contemplarse, sin embargo, desde otra perspectiva. Los casos en los que el perceptrón no consigue reproducir correctamente la etiqueta establecida constituyen precisamente aquellos en los que la regla aprendida por el modelo no resulta suficiente para explicar la categorización realizada. El perceptrón, por sí mismo, no puede determinar la causa de esa discrepancia: puede deberse a una variable no considerada, a una interacción entre variables que la combinación lineal no recoge o a las particularidades de un determinado caso. Pero sí puede señalar de forma objetiva dónde se produce esa discrepancia.

Desde esta perspectiva, el «fallo» del perceptrón puede adquirir un valor práctico. En lugar de considerar necesariamente los errores de clasificación como un resultado que debe ser eliminado, podemos utilizarlos como una señal para dirigir la atención del profesional hacia aquellos casos que merecen un análisis específico. El modelo establecería así una regla de clasificación a partir de los datos disponibles y, simultáneamente, pondría de manifiesto aquellos casos que se apartan de ella, sin pretender explicar por sí mismo las razones de esa discrepancia. Considero que esta posibilidad permite contemplar la utilidad del perceptrón desde una perspectiva diferente: no solo puede resultar útil cuando acierta en su clasificación, sino también —y potencialmente de forma especialmente interesante para la intervención profesional— cuando «falla», al señalar los casos en los que la regla aprendida no resulta suficiente y que, por ello, pueden requerir una valoración más detenida por parte del profesional.

Desde esta perspectiva, plantearemos el diseño y uso del perceptrón para abordar una situación potencialmente real en el ámbito de la intervención profesional de los Servicios de Orientación.

En proceso..............................................................

viernes, 14 de agosto de 2026

IA. Modelos

Introducción

IA. Modelos y paradigmas

Cuando hablamos de Inteligencia Artificial es común utilizar expresiones como IA basada en reglas, IA estadística, Machine Learning o Deep Learning como si fueran etapas delimitadas de una evolución tecnológica. Sin embargo, la relación entre estos conceptos es más compleja.

Desde una perspectiva histórica, la IA ha recorrido un camino que va desde sistemas cuyo comportamiento se especificaba mediante reglas hasta sistemas capaces de aprender modelos a partir de los datos. Y, posteriormente, hacia sistemas capaces de aprender también representaciones cada vez más complejas de esos datos.

La diferencia fundamental entre estos enfoques está en cómo se consigue que el sistema sepa qué debe hacer. En una IA basada en reglas, ese conocimiento se incorpora directamente al sistema mediante las reglas que el programador establece. En Machine Learning, en cambio, el sistema aprende un modelo a partir de los datos mediante un algoritmo de aprendizaje.

La historia de la IA puede contemplarse, en buena medida, como la historia de diferentes paradigmas desarrollados para abordar problemas similares mediante estrategias diferentes: desde la especificación explícita del conocimiento mediante reglas hasta la construcción de modelos a partir de los datos. Dentro de ese campo de la IA han coexistido y siguen coexistiendo diferentes paradigmas, entendiendo por paradigma una forma general de plantear cómo abordar un determinado tipo de problema y cómo construir el procedimiento necesario para resolverlo.

La diferencia entre estos paradigmas no reside necesariamente en los problemas que pretenden resolver, sino en la forma en que plantean su resolución y, especialmente, en cómo incorporan el conocimiento necesario para obtener un resultado.

Veamos a continuación cuáles son esos paradigmas y cuales sus características

El paradigma heurístico o cómo programar el conocimiento. Las primeras aproximaciones a la IA estuvieron fuertemente vinculadas a la IA simbólica y a los sistemas basados en reglas. La idea fundamental era relativamente directa: si somos capaces de representar explícitamente el conocimiento necesario y establecer las reglas de razonamiento correspondientes, podemos diseñar un algoritmo que aplique esas reglas para obtener una respuesta.

Un ejemplo elemental sería: SI A implica B ENTONCES C.. En este paradigma, el experto cuyo conocimiento se pretende representar, determina las reglas que utilizará el sistema. lo que prima son las reglas que se establecen a priori y los datos son aquello sobre lo que éstas se aplican. Por ello, el proceso se puede representar esquemáticamente así:

  • Reglas + datos → inferencia → resultado

Esta concepción dio lugar a los sistemas expertos, que tuvieron un importante desarrollo especialmente durante las décadas de 1970 y 1980. Su fortaleza era precisamente aquello que posteriormente se convertiría en una de sus limitaciones: el conocimiento podía representarse de forma explícita y, en determinadas condiciones, resultar interpretable. Sin embargo, el mundo real difícilmente puede reducirse a un conjunto completo de reglas.

¿Qué ocurre cuando existen miles de situaciones diferentes? ¿Qué sucede cuando las reglas son demasiado numerosas? ¿Y cuando ni siquiera sabemos formular explícitamente las reglas que utiliza una persona para reconocer una imagen, interpretar un texto o distinguir un patrón?

Aquí aparece el problema fundamental: podemos saber qué resultado queremos obtener sin saber formular explícitamente todas las reglas necesarias para obtenerlo. Una alternativa consiste entonces en analizar los datos para identificar en ellos regularidades, relaciones o patrones que puedan utilizarse para obtener el resultado buscado sin intentar especificar de antemano todas las reglas. Este planteamiento está en la base del enfoque estadístico de la IA.

La estadística ofrece una forma de abordar determinados problemas que es alternativa a la heurística. En lugar de intentar especificar todas las reglas, observamos los datos para buscar regularidades, relaciones y distribuciones que permitan describirlos o realizar inferencias. Por ello el esquema cambia: Datos → análisis estadístico → regularidades/inferencia → resultado

Esto introduce una transformación importante, ya que el conocimiento necesario para resolver el problema no tiene por qué estar completamente expresado mediante reglas diseñadas previamente por el programador. Puede inferirse matemáticamente a partir de los propios datos.

Dentro de este paradigma encontramos técnicas como la regresión, determinados métodos probabilísticos y otros procedimientos matemáticos que serán utilizados posteriormente dentro de sistemas de aprendizaje automático. Pero es necesario evitar identificar ambas, ya que estadística ≠ Machine Learning.. Un análisis estadístico puede limitarse a describir una población, estimar parámetros o estudiar relaciones entre variables. El Machine Learning añade una finalidad específica: construir mediante un procedimiento de aprendizaje un modelo capaz de realizar una tarea sobre datos, incluidos datos que no se utilizaron durante su entrenamiento. Podemos decir que la estadística constituye uno de los fundamentos de ML, pero no todo procedimiento estadístico es Machine Learning.

Con el Machine Learning (ML) se consolida una nueva forma de plantear muchos problemas de IA. Su característica diferencial no consiste en utilizar matemáticas o estadísticas; consiste en que el sistema aprende un modelo a partir de datos mediante un algoritmo de aprendizaje. La representación del proceso general sería: Datos → algoritmo de aprendizaje → modelo → nuevos datos → resultado

En el ML se distinguen tres elementos:

  • El algoritmo de aprendizaje establece cómo se aprende.
  • Los datos de entrenamiento proporcionan la información a partir de la cual se aprende.
  • Y el modelo es el resultado de ese proceso: una estructura matemática cuyos parámetros han sido ajustados para realizar una determinada tarea.

Lo que cambia respecto a la IA heurística es que el comportamiento concreto del modelo deja de estar completamente especificado de antemano mediante reglas explícitas. Mientras que en la IA basada en reglas, el conocimiento que determina el comportamiento del sistema se incorpora explícitamente a éste, en la IA Machine Learning este conocimiento se obtiene mediante un proceso de aprendizaje a partir de los datos.

Dentro del ML diferenciamos entre el llamado aprendizaje supervisado y el aprendizaje no supervisado.

  • En el aprendizaje supervisado disponemos de ejemplos de los que conocemos el resultado esperado (entrada → resultado conocido) y el algoritmo aprende la relación entre ambos.
  • En el aprendizane no supervisado desconocemos el resultado o la respuesta correcta de cada observación y el objetivo consiste en encontrar estructuras o agrupaciones presentes en los propios datos. Así funcionan, por ejemplo, algoritmos de clustering como K-means. Su esquema de funcionamiento sería el siguiente: datos → algoritmo → estructura descubierta

Finalmente, la IA Deep Learning (DL) es el Machine Learning llevado a otro nivel. No constituye un paradigma independiente situado fuera del Machine Learning. Es fundamentalmente una familia de métodos de ML basada en redes neuronales profundas.

La importancia histórica del Deep Learning procede, entre otras cosas, de su capacidad para aprender automáticamente representaciones de gran complejidad a partir de grandes cantidades de datos.

Mientras que en los sistemas tradicionales de ML era frecuente que una parte importante de la representación de los datos tuviera que ser diseñada previamente, en el Deep Learning es la propia red la que puede aprender progresivamente representaciones útiles para la tarea. Esto resulta especialmente importante en los siguientes campos:

  • reconocimiento de imágenes
  • reconocimiento de voz
  • procesamiento del lenguaje
  • traducción automática
  • generación de texto
  • generación de imágenes

La evolución conceptual puede expresarse entonces como una progresiva transferencia de decisiones desde el diseñador hacia el proceso de aprendizaje: Reglas explícitas → modelos aprendidos → representaciones aprendidas. Pero no debemos interpretar estas secuencia como una sucesión de paradigmas, ya que podemos constatar que los sistemas basados en reglas siguen siendo útiles cuando el conocimiento puede expresarse de manera explícita y necesitamos comportamientos deterministas o fácilmente verificables. Los métodos estadísticos continúan siendo fundamentales y el Machine Learning incorpora técnicas estadísticas, matemáticas y computacionales muy diversas. Por su parte, el Deep Learning convive con otros métodos de ML que no utilizan redes neuronales profundas.

Dicho de otro modo, no estamos ante una sucesión de tecnologías que se reemplazan unas a otras. Estamos ante diferentes formas de resolver problemas, algunas de las cuales se han mostrado especialmente adecuadas para determinados tipos de problemas y determinadas condiciones tecnológicas.

viernes, 7 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (II)

Colecciones de datos

En un diccionario complejo el value (par clave:valor) de un posible "campo" puede estar formado por una lista de datos ([]), de modo que el tratamiento de esos datos haga encesario implementar estrategias específicas. Sin pretender ser exhaustivos, ese tratamiento incluye operaciones como conteo, visualización y consulta (de toda la lista o de un elemento), añadido, supresión o modificación... En esta entrada estudiaremos cómo realizar estas operaciones.

Ya que por algún punto hay que empezar, nos situaremos en el siguiente contexto: hemos definido la estructura de diccionario...



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


... ya sabemos cómo añadir elementos a los "campos" simples nombre - curso - nee...



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    

... pero todavía no sabemos cómo introducir datos en el "campo" "medidas_apoyo": [] cuyo value es una lista, como podemos identificar por el uso corchetes [] como delimitadores. El script que sigue cubre esta necesidad, aunque tiene una funcionalidad limitada.



import json
import os


def solicitar_booleano(mensaje):
    """Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n."""
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ["s", "si", "sí"]:
            return True
        elif respuesta in ["n", "no"]:
            return False
        print(
            " [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No."
        )


# --- FUNCIÓN Cumplimentar datos de medidas_apoyo ---------------------------------------

def capturar_medidas_apoyo():
    """Captura de forma interactiva la lista de medidas de apoyo."""
    medidas = []
    quiere_anadir = solicitar_booleano(
        "¿Desea registrar alguna medida de apoyo?"
    )

    contador = 1
    while quiere_anadir:
        medida = input(f" -> Ingrese la medida de apoyo #{contador}: ").strip()
        if medida:
            medidas.append(medida)
            contador += 1
        quiere_anadir = solicitar_booleano("¿Desea añadir otra medida de apoyo?")

    return medidas


# --- FUNCIÓN PRINCIPAL de ejecución. -----------------------------------------------

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")

    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print(
        "  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)"
    )
    print(
        "  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)"
    )
    print("-------------------------------------\n")

    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano(
        "¿Presenta Necesidades Educativas Especiales (NEE)?"
    )

    # 1. Llamamos a la nueva utilidad para generar la lista
    medidas_apoyo = capturar_medidas_apoyo()

    # 2. Retornamos el diccionario completo con el nuevo campo
    registro = {
        "codigo_alumno": cod_alumno,
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": medidas_apoyo,
    }

    return registro

# ==============================================================================
# BLOQUE DE EJECUCIÓN PRINCIPAL
# ==============================================================================
if __name__ == "__main__":
    # Solicitamos o asignamos el número identificador para el código de alumno
    num_expediente = 1

    # Llamada a la función principal para capturar los datos interactivos
    expediente_alumno = capturar_ficha(num_expediente)

    # Mostramos el resultado por pantalla con formato JSON legible
    print("\n==========================================")
    print("      REGISTRO GENERADO CON ÉXITO")
    print("==========================================")
    print(json.dumps(expediente_alumno, indent=4, ensure_ascii=False))


Este script complementa el de creación de registro añadiendo, a partir de def capturar_medidas_apoyo(): le procedimiento para obtener los primeros posibles datos de medidas_apoyo. Esta función es llamada desde la función principal def capturar_ficha(codigo_num): y se basa, en lo fundamental, en el bucle while quiere_anadir: que permite al usuario introducir cuantas medidas de apoyo desee.

Resuelto el procedimiento de creación de datos, nos situamos ahora en el segundo contexto: ya disponemos de un archivo JSON que contiene datos suficientes como para que sea pertinente realizar diferentes operaciones con la información de medidas_apoyo de los distintos registros-diccionarios.

En este contexto empezamos por acceder al archivo JSON para trabajar con él desde el script (en memoria) como diccionario complejo. Este procedimiento ya nos es conocido.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    

De las entradas anteriores sabemos crear el listado las claves principales, acceder al contenido de un archivo concreto o a todos ellos. También sabemos manipular el contenido de deteminados campos. Ahora nos detendremos en el acceso y las posibles modificaciones del contenido del campo cuyo value es una lista. Empezaremos por obtener el listado completo de registros mostrando cuántas y cuales son las medidas educativas en cada uno de ellos.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

try:
    # 1. Cargar el contenido del archivo JSON en memoria
    with open(ruta_archivo, "r", encoding="utf-8") as archivo:
        expedientes = json.load(archivo)

    print("\n==================================================")
    print("    LISTADO COMPLETO DE EXPEDIENTES Y MEDIDAS     ")
    print("==================================================\n")

    # Adaptación dinámica según si el JSON es una lista [...] o un diccionario {...}
    registros = (
        expedientes.items()
        if isinstance(expedientes, dict)
        else enumerate(expedientes, 1)
    )

    for id_registro, datos in registros:
        # Obtenemos la clave principal (clave del dict o campo 'codigo_alumno')
        if isinstance(datos, dict):
            clave_principal = datos.get("codigo_alumno", f"Registro #{id_registro}")
            medidas = datos.get("medidas_apoyo", [])
        else:
            clave_principal = str(id_registro)
            medidas = datos if isinstance(datos, list) else []

        # Contamos la cantidad total de elementos en la lista
        total_medidas = len(medidas)

        # Imprimimos la salida formateada en CMD
        print(f"📌 Clave Principal: {clave_principal}")
        print(f"   Total de medidas educativas aplicadas: {total_medidas}")

        if total_medidas > 0:
            print("   Listado de medidas:")
            for i, medida in enumerate(medidas, 1):
                print(f"      {i}. {medida}")
        else:
            print("   Listado de medidas: (Sin medidas registradas)")

        print("-" * 50)

except FileNotFoundError:
    print(f" [!] Error: No se encontró el archivo en la ruta '{ruta_archivo}'.")
except json.JSONDecodeError:
    print(
        " [!] Error: El archivo JSON no tiene un formato válido o está corrupto."
    )


Podríamos dirigir nuestra atención al análisis del contenido de este "campo", pero dejaremos estos procedimientos para una tercera fase del estudio de los diccionarios y nos atendremos a explicar los procedimientos de manejo y modificación del value lista, si bien no es muy diferente del del que ya estudiamos en el tratamiento de estas colecciones de datos.

Al tratarse de un diccionario ya desarrollado en cuanto a su contenido, lo primero que deberemos hacer es identificar y acceder al registro de nuestro interés. Para ello debemos usar la clave principal /(vg. "ALU-2024-01") y, a partir de ahí a la clave "medidas_apoyo".

Disponemos de dos procedimientos para acceder a los datos de la lista. Bien directamente identificando al alumno por su clave:



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Identificamos directamente al alumno por su clave

id_alumno = "ALU-2024-01"

# 3. Verificar existencia y mostrar las medidas elemento por elemento
if id_alumno in expedientes:
    print(f"Medidas de apoyo para {id_alumno}:")
    for medida in expedientes[id_alumno]["medidas_apoyo"]:
        print(f"- {medida}")



... o bien hacerlo de forma interactiva introducciendo por input() el identificador de su clave, suponiendo que tenemos acceso a un listado de claves y nombres que nos facilitan la identificación del sujeto deseado.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)
    
# 2. Listado de claves-sujetos

listado = [f"{clave} - {datos['nombre']}" for clave, datos in expedientes.items()]
for al in listado:
    print(al)

# 3. Solicitar la clave directamente al usuario
clave = input("Introduce la clave del expediente: ")

# 4. Acceder al valor del campo 'medidas_apoyo' del registro indicado
medidas = expedientes[clave]["medidas_apoyo"]

# 5. Mostrar el contenido actual
print("Medidas de apoyo actuales:", medidas)


Una vez que accedemos al listado de medidas, podemos limitarnos a mostrarlo, pero también podemos actuar sobre él. Veamos algunas acciones posibles. Se escriben a continuación del script anterior).



print("\n--- ACCIONES SOBRE LAS MEDIDAS ---")

# (1) Conocer el número de medidas
print(f"\n4. Número total de medidas registradas: {len(medidas)}")

# (2) Añadir una nueva medida
nueva = input("1. Introduce la nueva medida a añadir: ").strip()
medidas.append(nueva)
print("   Lista actualizada:", medidas)

# (3) Borrar una medida
a_borrar = input("\n2. Introduce la medida a eliminar: ").strip()
medidas.remove(a_borrar)
print("   Lista tras borrar:", medidas)

# (4) Ordenar alfabéticamente las medidas
medidas.sort()
print("\n3. Medidas ordenadas alfabéticamente:", medidas)

# (5) Saber si existe una determinada medida
buscar = input("\n5. Consulta si existe una medida concreta: ").strip()
print(f"   -> ¿Existe '{buscar}' en la lista?: {buscar in medidas}")


Lo suyo sería actualizar el archivo JSON, pero por ahora no es necesario ya que simplmente estamos aprendiendo a trabajar con los componentes del diccionario.

martes, 4 de agosto de 2026

LENGUAJES. Python

Diccionarios complejos (I)

Colecciones de datos

En las dos entradas previas en las que hemos trabajado con diccionarios hemos empleado de forma intencional formulaciones simples de esta colección de datos. Se trataba de comprender en qué consistía y cómo se podía trabajar con ella, así que la simplicidad se imponía como necesidad. Pero la realidad es que un diccionario "normal" no suele ser tan sencillo ni su virtud o interés requiere de que los sea.

En esta entrada idearemos una estructura compleja (y más realista, incluso) de un diccionario y trabajaremos con ella. Se trata de comprobar la realidad y la utilidad real de un diccionario. Para ello vamos a plantearlo como colección de datos potencialmente aplicable a un proyecto real de trabajo (como SEO) aunque ajustado a las necesidades didácticas del momento.

Imaginemos que sobre una estructura de diccionario queremos construir un recurso de seguimiento del alumnado. Se trata de una forma de expediente, muy parecido a lo que se podría dar soporte con una base de datos, pero también registrar y mantener como un documento JSON. Algo como esto:



 "ALU-cod-n": {
        "nombre": "al_nombre",
        "curso": "al_curso",
        "nee": true,
        "medidas_apoyo": [
            "mad_1"
        ],
        "intervenciones": [
            {
                "fecha": "int_aa_mm_dd",
                "motivo": "int_mot",
                "acuerdos": "int_acuerdos"
            }
        ]
    },


Muestro aquí la estructura de un elemento del documento CSV sin contenidos ni reales ni supuestos (eso viene después) para que podemos observar en qué consiste la complejidad de la que estamos hablando cuando lo trasladamos a diccionario (ya sabemos que estructura y contenido se mantienen): ahora cada elemento del diccionario - aquí "ALU-cod-n": - es la clave de un value que es a su vez un diccionario, de ahí que el conjunto se pueda considerar como un diccionario de diccionarios.

Ese value además de ser un diccionario (de ahí que todo él se encuentre delimitado por {}, es un diccionario complejo ya que sus diferentes elementos-campos lo son de diferente tipología:

  • Tenemos contenidos-campos clave:valor simples (y de distinto tipo de datos), como "nombre": "al_nombre"
  • Pero también un contenido-campo cuyo value es una lista "medidas_apoyo": ["mad_1"],
  • Y más aun, un contenido-campo cuyo value es una lista de diccionarios (de la que ahora sólo muestro su primer componente clave:valor por simplificar: "intervenciones": [{"fecha": "int_aa_mm_dd",}]

Sobra decir que debermos conocer cómo tratar a cada subestructura o tipo de componente, tanto como con el conjunto. Empezaremos con ello a continuación.



import json
import os

def solicitar_booleano(mensaje):
    """
    Solicita una respuesta booleana al usuario garantizando que la entrada sea s/n.
    """
    while True:
        respuesta = input(f"{mensaje} (s/n): ").strip().lower()
        if respuesta in ['s', 'si', 'sí']:
            return True
        elif respuesta in ['n', 'no']:
            return False
        print(" [!] Entrada no válida. Por favor, introduzca 's' para Sí o 'n' para No.")

def capturar_ficha(codigo_num):
    cod_alumno = f"ALU-cod-{codigo_num}"
    print(f"\n==========================================")
    print(f"  NUEVA FICHA DE EXPEDIENTE: {cod_alumno}")
    print(f"==========================================")
    
    print("\n--- AVISO DE FORMATO DE ENTRADA ---")
    print("  * Nombre del alumno: Apellidos, Nombre (ejemplo: García López, María)")
    print("  * Curso: Etapa y grupo (ejemplo: 2º ESO B  o  1º Bachillerato A)")
    print("-------------------------------------\n")
    
    nombre = input("Nombre completo del alumno (ej: García López, María): ").strip()
    curso = input("Curso y grupo (ej: 2º ESO B): ").strip()
    nee = solicitar_booleano("¿Presenta Necesidades Educativas Especiales (NEE)?")
    
    # Se mantienen las estructuras de listas para su posterior implementación
    return cod_alumno, {
        "nombre": nombre,
        "curso": curso,
        "nee": nee,
        "medidas_apoyo": [],
        "intervenciones": []
    }

def guardar_en_json(nuevos_expedientes, ruta_salida):
    # Limpieza de comillas iniciales o finales introducidas por el usuario
    ruta_salida = ruta_salida.strip('\"\'')
    ruta_salida = os.path.normpath(ruta_salida)
    
    # 1. Comprobar si el directorio existe y crearlo si no
    directorio = os.path.dirname(ruta_salida)
    if directorio and not os.path.exists(directorio):
        os.makedirs(directorio, exist_ok=True)
        print(f" [+] Directorio creado: {os.path.abspath(directorio)}")
        
    datos_acumulados = {}
    
    # 2. Comprobar si el archivo JSON existe para cargar su contenido previo
    if os.path.exists(ruta_salida):
        try:
            with open(ruta_salida, mode="r", encoding="utf-8") as archivo:
                contenido = archivo.read().strip()
                if contenido:
                    datos_acumulados = json.loads(contenido)
            print(f" [+] Archivo existente detectado. Se añadirán los nuevos registros a los {len(datos_acumulados)} existentes.")
        except Exception as e:
            print(f" [!] Advertencia: No se pudo leer el archivo existente ({e}). Se iniciará un JSON nuevo.")
            datos_acumulados = {}
    else:
        print(" [+] El archivo no existe. Se creará un nuevo archivo JSON.")

    # 3. Anexar/actualizar los registros nuevos sobre el objeto cargado
    datos_acumulados.update(nuevos_expedientes)
    
    # 4. Sobrescribir el archivo con el diccionario actualizado en formato JSON legible
    with open(ruta_salida, mode="w", encoding="utf-8") as archivo:
        json.dump(datos_acumulados, archivo, ensure_ascii=False, indent=4)
        
    return os.path.abspath(ruta_salida)

def main():
    expedientes_sesion = {}
    contador = 1
    
    print("----------------------------------------------------------------")
    print(" SISTEMA DE REGISTRO DE EXPEDIENTES ACADÉMICOS")
    print("----------------------------------------------------------------")
    
    # Bucle de captura continua de fichas
    while True:
        codigo, ficha = capturar_ficha(contador)
        expedientes_sesion[codigo] = ficha
        contador += 1
        
        if not solicitar_booleano("\n¿Desea incorporar la ficha de otro alumno?"):
            break
            
    # Guardado de datos en JSON
    if expedientes_sesion and solicitar_booleano("\n¿Desea guardar los expedientes recopilados en un archivo JSON local?"):
        print("\n=========================================================================")
        print(" INSTRUCCIONES PARA LA RUTA DEL ARCHIVO JSON:")
        print(" 1. NO use comillas al principio ni al final (ejemplo: NO use \"C:\\datos\\alumnos.json\")")
        print(" 2. Puede usar barras inclinadas normales ( / ) o barras invertidas ( \\ ).")
        print(" 3. Asegúrese de incluir la extensión .json al final del nombre.")
        print(" 4. Ejemplos de rutas válidas:")
        print("    - Windows (ruta absoluta): C:/MisDatos/expedientes.json  ó  C:\\MisDatos\\expedientes.json")
        print("    - Carpeta actual (ruta relativa): ./expedientes.json  ó  expedientes.json")
        print("=========================================================================\n")
        
        ruta_archivo = input("Introduzca la ruta y nombre del archivo JSON: ").strip()
        try:
            ruta_final = guardar_en_json(expedientes_sesion, ruta_archivo)
            print(f"\n[OK] Registros guardados con éxito en:\n     {ruta_final}")
        except Exception as e:
            print(f"\n[ERROR] No se pudo guardar el archivo JSON: {e}")
            
    print("\nCierre de sesión del sistema.")

if __name__ == "__main__":
    main()



Aunque el script resultante sea mejorable, ya resulta suficientemente complejo (además de funcional) como para que nos hagamos una idea de qué estamos planteando cuando implementamos un diccionario de este nivel de complejidad. Lo que hemos pretendido es ejemplificar lo que sería la primera y básica implementación de una utilidad de este tipo: obtener del usuario los datos básicos (y simples, se podría añadir) que componen la estructura del diccionario y archivar esa información partiendo desde cero y en formato JSON. Cualquier proyecto de este tipo debe contener estas funcionalidades y ajustarse a la estructura del JSON deseado.

Pero si el contexto primero es correcto (y realista), el que planteo a continuación también lo es. Dando por supuesto el exito del anterior, creamos ahora una utilidad de acceso a los datos. Partimos de que ya tenemos algunos creados y deseamos acceder a ellos, entendiendo que ya hemos recopilado cierto volumen de información, disponible como archivo JSON. Evidentemente los datos que incluímos son inventados (sintéticos, que se dice) y no nos vamos a detener en valorar su ajuste a la realidad ni su interés pedagógico real. Para que lo que aquí te presento te sea de ayuda, proporcionaré acceso al documento "inventado" al final de esta entrada.

Dicho lo anterior, lo que ahora necesitamos es un script que nos permita acceder al documento JSON para trabajar con él en memoria como diccionario.



import json

# Ruta absoluta al archivo JSON
ruta_archivo = r"C:\MisDatosPropios\exped_orienta.JSON"

# 1. Cargar el contenido del archivo JSON en memoria
with open(ruta_archivo, "r", encoding="utf-8") as archivo:
    expedientes = json.load(archivo)

# 2. Obtener la clave y los datos del primer registro
primer_id = list(expedientes.keys())[0]
primer_registro = expedientes[primer_id]

# 3. Mostrar el resultado por pantalla (CMD)
print(f"--- PRIMER REGISTRO: {primer_id} ---")
print(json.dumps(primer_registro, ensure_ascii=False, indent=4))


Dado que estamos trabajando con archivos JSON, lo primero que haremos será importar la biblioteca correspondiente import json para cargar en memoria el archivo expedientes = json.load(archivo) desde su ubicación local ruta_archivo y accedemos a él with open() as archivo. Después obtenemos las claves de todos los archivos en formato lista list(expedientes.keys()) y el contenido del primer registro primer_registro = expedientes[primer_id] y lo mostramos por CMD print(json.dumps(primer_registro, ensure_ascii=False, indent=4)).

Mediante este par de script abordamos las dos maniobras básicas que deberemos realizar con diccionarios, simples o complejos. Pero como hay tarea para rato, parece conveniente finalizar ahora esta entrada.

Archivo de datos (formato JSON). Acceso para descarga desde este enlace. Deberás ajustar la dirección ruta_archivo a la que tú elijas como directorio del archivo que has descargado.

jueves, 30 de julio de 2026

LENGUAJES. Python

Diccionarios (II)

Colecciones de datos

Damos continuidad en esta entrada a la temática iniciada en la anterior sobre los diccionarios Python y sus usos. Ahora trataremos cuestiones básicas de manejo de los diccionarios y su contenido.



#A. Procedimientos directos de creación de diccionarios simples ========================

# 1. Creación estándar con llaves

usuario_ana = {
    "nombre": "Ana",
    "edad": 28,
    "lenguajes": ["Python", "JavaScript"],
    "activo": True
}

# 2. Creación con la función dict()

usuario_carlos= dict(nombre="Carlos", edad=35,lenguajes=["OOo Basic","Python"],activo=True)

#B. Acceso a datos =================================================

# 1. Acceso directo con llaves

print(usuario_ana["nombre"])  # Salida: Ana
print(usuario_carlos["edad"])  #Salida: 35


# 2. Acceso seguro con .get() (evita que el programa falle si la clave no existe)

print(usuario_ana.get("edad"))  # Salida: True
print(usuario_carlos.get("salario", "No especificado"))  # Salida: No especificada

#3. Acceso cuando el valor es una lista 

# Cuando el valor es una lista, sólo con la clave obtenemos como valor la propia lista

print(usuario_ana.get("lenguajes"))  # Salida: ["Python", "JavaScript"]

# 3.1. Usando corchetes. Obtener el primer elemento de la lista (índice 0)

primer_lenguaje = usuario_ana["lenguajes"][0]
print(primer_lenguaje)  # Salida: Python

# 3.2. Usando get(). Obtener el primer elemento con .get()

primer_lenguaje = usuario_carlos.get("lenguajes")[0]
print(primer_lenguaje)  # Salida: OOo Basic

# C. Modificar datos: cambiar, borrar y añadir datos ===============================

# 1. Modificar un valor existente
usuario_ana["edad"] = 29

# 2. Agregar una nueva pareja clave-valor
usuario_ana["ciudad"] = "Madrid"

# 3. Agregar elemento a un valor-lista (ej. lenguajes:[lista_lenguajes]

# 3.1. Agregar un nuevo elemento a la lista 'lenguajes' con append()
usuario_ana["lenguajes"].append("C++")

# (3.1.b) Si no estás seguro de que exista la clave 'lenguajes' usa setdefault() para crearlo y luego añade el elemento con append()
usuario_ana.setdefault("lenguajes", []).append("LUA")

# 3.2. Agregar un nuevo elemento a la lista 'lenguajes' usando .extend() (varios elementos)
usuario_carlos["lenguajes"].extend(["JavaScript", "C++", "PHP"])

# (3.2.b) ...o usando +=
usuario_ana["lenguajes"] += ["OOo Basic"]

print(usuario_carlos["lenguajes"])
print(usuario_ana["lenguajes"])

# 4. Borrar

# 4.1 Eliminar una clave específica con 'del'
del usuario_ana["activo"] #Elimina el par activo:True

# Extraer y eliminar un valor con .pop()
edad = usuario_ana.pop("edad") # Elimina el valor (29) del par "edad":29

# Limpiar todo el diccionario
usuario_carlos.clear() # Elimina todo el diccionario (usuario_carlos)

print(usuario_ana.get("activo", "No especificado")) #con get() evitamos mensaje de error. Devuelve mensaje ("No especificado")
print(edad) #Devuelve el valor del par 'edad':29
print(usuario_ana.get('edad')) #Devuelve None por defecto (ya que no se espifica mensaje)
print (usuario_carlos) # Devuelve un diccionario vacío ({})


Reproduzco en este script la creación de dos diccionarios simples para desarrollar la lógica de posibles manipulaciones sobre un diccionario y sus contenidos. La división del script en cuatro partes y la explicitación como comentarios de cada una de las posibles acciones facilita la comprensión del contenido y agiliza esta explicación complementaria.

Podemos diferenciar tres tipos de actuaciones sobre un diccionario:

  • Los procedimientos de acceso a los datos (sección B)
    • mediante el uso de llaves print(usuario_ana["nombre"]) - Salida: Ana
    • mediante la función get() para controlar el posible error de acceso
    • y el acceso en el caso específico de valores-lista, bien para visualizar la lista completa de valores print(usuario_ana.get("lenguajes")), bien para acceder a uno de ellos en concreto, por ejemplo, print(primer_lenguaje = usuario_ana["lenguajes"][0]), mediante el uso de corchetes
  • La modificación de datos (cambiar o añadir) (sección C del script)
    • mediante procedimientos simples usuario_ana["edad"] = 29 de cambio
    • o usuario_ana["ciudad"] = "Madrid" para añadir clave:valor, ambos basados en el uso de corchetes
    • o más complejos como es el caso de la modificación de valores-listas, que requieren el uso de funciones como append(), (sóla en combinación con setdefault()) para añadir un elemento-valor o la función extend() para añadir varios.
  • Finalmente, en la sección D tratamos el borrado de datos: clave: valor del usuario_ana["activo"], sólo valor edad = usuario_ana.pop("edad") y del diccionario completo usuario_carlos.clear()

Finalizo aquí esta segunda entrada sobre los diccionarios, pero aun hay materia para más...

miércoles, 29 de julio de 2026

LENGUAJES. Python

Diccionarios (I)

Colecciones de datos

Aunque ya sabemos algo sobre diccionarios en Python, lo cierto es que en esta subsección de LENGUAJES hemos hablado mucho de listas (y de tuplas) y muy poco de diccionarios, al menos mucho menos de lo que se merece.

Cuando uno comienza a programar en OOo Basic deberá trabajar con listas o arrays. Si después programa en Python lo "lógico" es que traslade su interés al uso las listas. Además las listas en Python son sencillas e intuitivas de usar y muy útiles.

Pero Python es mucho más rico y complejo que OOo Basic también es esto de las colecciones de datos y los diccionarios son también expresión de esa superioridad: un diccionario mejora la organización de los datos y también la velocidad y precisión del acceso, que está en función del significado y no de la posición como en las listas.

Un diccionario es una colección de elementos organizados mediante pares clavevalor (key:value) en la que cada dato-valor (que sí se puede modificar y duplicar) se almacena asociado a una clave única (por lo que es inmutable y única).



persona = {
    "nombre": "Ana",
    "edad": 34,
    "profesion": "Ingeniera"
}


En este caso el diccionario persona consta de tres claves y cada clave contiene un dato o valor. v.g. la clave "nombre" contiene el valor "Ana"

Si estuviéramos trabajando con listas persona = ["Ana", 34, "Ingeniera"] (1), para obtener la profesión deberíamos expresarlo como persona[2], mientras que como diccionario diremos persona["profesion"]. En el caso de las listas, cualquier modificación del contenido implica un cambio en el procedimiento de acceso que deberemos conocer de antemano; en el caso de los diccionarios es suficiente con conocer el nombre de la clave.

Si lo anterior no fuera suficiente motivo para entender las ventajas de los diccionarios sobre las listas, piensa en la cantidad de "objetos" del mundo real que se pueden expresar como diccionarios:

  • ejemplo: alumno - identificador - nombre - curso - etapa - centro - resultados académicos - MAD - ...
  • Pero también archivo - ruta - tamaño - fecha...

Estos "objetos" y otros muchos se pueden expresar en estructuras clave - valor. Además, Python implementa internamente los diccionarios mediante tablas hash (2), lo que hace que la búsqueda de un elemento por su clave sea, en condiciones normales, extremadamente rápida. En la práctica, acceder a un valor suele realizarse en tiempo constante, independientemente del tamaño del diccionario.

El método más habitual para crear un diccionario consiste en utilizar llaves, como en provincias = {}, instrucción con la que creamos un diccionario vacío que después podemos dotar de contenido, aunque también es posible (y muy frecuente) crear directamente el diccionario definiendo su estructura y contenido, como en este caso...



provincias = {
    "Asturias": "Oviedo",
    "León": "Leon",
    "Cantabria": "Santander"
}


... aunque también se puede crear un diccionario mediante la función dict() - provincias=dict().



# Bien...

provincias = dict(
    Asturias="Oviedo",
    Leon="León",
    Cantabria="Santander"
)

# O también...

provincias = dict([
    ("Asturias", "Oviedo"),
    ("León", "León"),
    ("Cantabria", "Santander")
])


Aunque esta es la forma simple de expresar un diccionario, lo normal es que su contenido sea más complejo. Pensemos, por ejemplo, que creamos el diccionario provincias pero queremos que contenga una colección de datos (capital - población_2025 - peso_sector_primario , peso_sector_secundario, peso_sector_terciario. Ahora ya no hay un ínico dato asociado a la clave, por lo que debemos expresar esta complejidad de otro modo. Y caben dos posibilidades:

  • Como lista provincia:[capital - población_2025 - peso_sector_primario , peso_sector_secundario]
  • o Como diccionarios anidados

Veamos el desarrollo de ambas posibilidades en un script.



# Valor como lista de valores

provincias = {
    "Asturias": ["Oviedo", 1004326, 4.1, 22.7, 73.2],
    "León": ["León", 442000, 8.5, 24.3, 67.2],
    "Cantabria": ["Santander", 593000, 3.8, 20.6, 75.6]
}


# Valor como diccionario anidado lista:valor

provincias = {
    "Asturias": {
        "capital": "Oviedo",
        "poblacion_2025": 1004326,
        "sector_primario": 4.1,
        "sector_secundario": 22.7,
        "sector_terciario": 73.2
    },

    "León": {
        "capital": "León",
        "poblacion_2025": 442000,
        "sector_primario": 8.5,
        "sector_secundario": 24.3,
        "sector_terciario": 67.2
    },

    "Cantabria": {
        "capital": "Santander",
        "poblacion_2025": 593000,
        "sector_primario": 3.8,
        "sector_secundario": 20.6,
        "sector_terciario": 75.6
    }
}


Aunque la primera formulación parece más sencilla de crear, en realidad su manejo resulta más complicado, por lo que es preferible recurrir a los diccionarios anidados por varias razones que no vamos a analizar aquí. Además, los diccionarios anidados constituyen la representación natural en Python de la estructura de un documento JSON. Esta correspondencia casi directa explica que los diccionarios anidados sean la estructura de datos preferida para trabajar con información procedente de diferentes fuentes, espcialmente con las que se expresan como archivos JSON.

Entre otras ventajas, esto hace posible que...



import json

with open("provincias.json", "w", encoding="utf-8") as archivo:
    json.dump(provincias, archivo, ensure_ascii=False, indent=4)


... nos permita almacenar los datos en un documento en formato JSON, el cual podremos visualizar desde bloc de notas.

{ "Asturias": { "capital": "Oviedo", "poblacion_2025": 1004326, "sector_primario": 4.1, "sector_secundario": 22.7, "sector_terciario": 73.2 }, "León": { "capital": "León", "poblacion_2025": 442000, "sector_primario": 8.5, "sector_secundario": 24.3, "sector_terciario": 67.2 }, "Cantabria": { "capital": "Santander", "poblacion_2025": 593000, "sector_primario": 3.8, "sector_secundario": 20.6, "sector_terciario": 75.6 } }

Como puedes comprobar, el archivo JSON reproduce prácticamente la misma estructura jerárquica que nuestro diccionario. Esta correspondencia es precisamente una de las razones por las que los diccionarios tienen un papel tan importante en Python cuando se trabaja con datos estructurados.

Para realizar el proceso inverso (cargar en Python un archivo JSON) deberemos ejecutar este script:



import json

with open("provincias.json", "r", encoding="utf-8") as archivo:
    provincias = json.load(archivo)


Llegados a este punto parece evidente que los diccionarios dan para algo más que una única entrada. Este es un buen momento para reconocerlo y dar por finalizada la actual. Continuará...

NOTAS
1 Esta sería una de las formas en que se podría expresar lo mismo como lista. Pero no es la única. Personalmente, para casos como ese, yo previero usar un procedimiento multilista (una por "campo") y trabajar después con procedimientos de acceso basados precisamente en las posiciones de los elementos de cada una de ellas.
2 Sin entrar en explicaciones que poco nos aportan aquí, una tabla hash es una estructura de datos que utiliza una función hash para transformar una clave arbitraria en un índice numérico que determina dónde se almacenará o buscará el dato asociado dentro de una tabla. Esto hace que se puedan localizar los datos igual de rápido con independencia del tamaño del diccionario (tiempo constante o O(1)), ya que evita la búsqueda secuencial.

miércoles, 15 de julio de 2026

IA. Modelos

Perceptrón (II)

IA. Modelos y paradigmas. Machine Learning

Damos continuidad aquí a la entrada precedente. Una vez expuesta en ella el esquema de la arquitectura del perceptrón, vamos a desarrollar la forma en que se concreta cada una de sus partes. Empezaremos por cómo obtenemos z a partir de X₁ y X₂; de dónde procede esa operación, por qué se define del modo en que se define y cuál es el efecto esperado.

Cuando Rosenblatt ideo el perceptrón planteó como primer elemento de su arquitectura la operación siguiente: z=(X1*w1) + (X2*w2) + b. La elección de esta operación no fue casual. El perceptrón se desarrolla dentro de una tradición que buscaba construir modelos matemáticos simplificados de la actividad neuronal, iniciada por McCulloch y Pitts y desarrollada posteriormente por Rosenblatt. En este planteamiento, una unidad recibe diferentes señales, las integra y determina su activación a partir del resultado. Rosenblatt incorpora a esta estructura la posibilidad de modificar los pesos de las conexiones mediante aprendizaje.

Esta aproximación permitió convertir una determinada hipótesis sobre el procesamiento neuronal en un modelo matemático susceptible de aprendizaje, pero también implicó una considerable simplificación de la actividad de una neurona biológica. Conviene, por tanto, entender el perceptrón como un modelo computacional inspirado en la neurona, y no como una representación de su funcionamiento biológico.

En nuestro caso, la operación que integra las entradas puede expresarse como una suma ponderada: z=(X1*w1) + (X2*w2) + b, pero los dos términos que aparecen en esta expresión corresponden a las dos variables de entrada de la tabla AND. La formulación matemática del perceptrón permite, no obstante, ser ampliada a tantas entradas como requiera el problema: z=i=1∑nXiwi+b. Por tanto, AND determina nuestro ejemplo, no la estructura general del perceptrón. Las entradas tampoco tienen por qué ser necesariamente variables binarias: lo que importa para esta operación es que puedan representarse numéricamente y participar en la suma ponderada.

La utilización de pesos en la operación responde a una necesidad básica: las distintas entradas no tienen por qué ejercer la misma influencia sobre el resultado. Cada wᵢ permite ponderar la contribución de su correspondiente entrada Xᵢ, incluso en sentido positivo o negativo. El término bias (b) cumple una función diferente: permite desplazar el resultado de la suma ponderada y ajustar el nivel a partir del cual las entradas pueden conducir a una determinada respuesta. Ambos son, por tanto, parámetros de la función que el perceptrón deberá ajustar mediante aprendizaje y que veremos más adelante. De momento, nos interesa dejar clara su función en la formulación de esta suma ponderada.

Concretando lo anterior en el primer registro de nuestra colección de datos, la fórmula anterior modifica la composición de la tabla al implicar la creación de espacios (columnas) para la ubicación de los pesos y del resultado de la suma ponderada, lo que nos obliga a reformular la original en la hoja de Calc que hemos elegido como soporte del siguiente modo: De la tabla original...

X₁ X₂ y
0 0 0

... pasamos a...

X₀ X₁ X₂ w₀ (b) w₁ w₂ z y
1 0 0 0 0 0 0 0

Como punto de partida debemos entender que el sumatorio original (X1*w1) + (X2*w2) + b es ahora equivalente a (x0*w0) + (X1*w1) + (X2*w2) (1). Ello se debe a la introducción de la variable X₀ y la reformulación o equivalencia de b como w₀. Ambas transformaciones obedecen al interés por facilitar la expresión de la operación (de la suma ponderada) como el producto escalar de vectores (2). Pero para que la operación X₀*w₀ sea siempre igual a w₀, esto es, a b,que es como debe ser según se deduce de la equivalencia entre las dos fórmulas de la suma ponderada, X₀ debe tener un valor constante e igual a 1 (3).

Se debe observar, además, que estamos manteniendo el valor de la etiqueta (y) diferenciado de z, que resulta de la suma ponderada. Esto implica que no existe equivalencia entre el resultado de esa operación y el resultado esperado y (la etiqueta), debido a que todavía falta aplicar una segunda función que convierta el valor z en un resultado comparable con y, al que llamaremos y'. Será entonces y' el que podamos comparar con el valor esperado y. Esta función es la llamada función escalón.

La función escalón convierte el resultado cuantitativo z, obtenido mediante la suma ponderada, en una salida binaria y'. Para ello, evalúa una condición lógica que, en términos de programación, se puede expresarse mediante una estructura if: if z >= 0 then y' = 1 else y' = 0.

A nivel operativo, en cuanto a su expresión en nuestra hoja de cálculo, estas operaciones obligan de nuevo a modificar la estructura de nuestra tabla. Ahora..

X₀ X₁ X₂ w₀ (b) w₁ w₂ z y' y
1 0 0 0 0 0 0 0 0

... entendiendo que en cada una de las celdas situadas bajo el encabezado y' de nuestra hoja Calc está la función built-in equivalente =SI(D2>=0;1;0)

Una vez obtenida la salida y' mediante la función escalón, ya podemos compararla con el valor esperado y, es decir, con la etiqueta de cada observación. Esta comparación permite determinar si la respuesta obtenida (y') coincide con la esperada (y), cuestión que resolvemos mediante una simple resta (y-y'), cuyo resultado es 0, si se produce tal coincidencia (concordancia) o diferente de 0 en caso contrario (divergencia).

En términos de nuestro soporte, esto implica que deberemos añadir una nueva columna donde quede recogido el resultado de esta resta...

X₀ X₁ X₂ w₀ (b) w₁ w₂ z y' y y-y'
1 0 0 0 0 0 0 0 0 0

... ubicándose la fórmula correspondiente a y-y' en cada una de las celdas que se sitúan en la columna encabezada por la expresión y-y'. En nuestro caso, como valor primero obtenemos que y-y'= 0, lo que implica que el resultado es el esperado.

Cuando se da esta circunstancia, y-y'=0, no es necesario modificar los pesos, ya que los parámetros actuales han producido la respuesta correcta. Traducimos esto en que no es necesario activar el mecanismo de aprendizaje.

Esta situación puede repetirse con la entrada siguiente y con otras posteriores, pero no podemos concluir por ello que el aprendizaje haya finalizado. Para comprobarlo, debemos completar una época, que consiste en procesar una vez todos los ejemplos que forman parte de los datos de entrenamiento. En el caso de AND, por ejemplo, una época comprende las cuatro combinaciones posibles de x1 y x2. Al finalizarla podemos comprobar si se han producido errores y, en función de ello, determinar si el proceso de aprendizaje ha convergido.

Tenemos, por tanto, identificado que el llamado "aprendizaje" algorítmicamente requiere ser concretado dentro de una estructura condicional if, que consta necesarimente de dos términos; el primero de ellos implica la condición y-y'=0 y se concreta como respuesta como que los valores siguientes para la matriz de pesos wi, son los mismos que los precedentes. Dicho de otro modo, para nuestro ejemplo actual y siendo r2 el registro que sigue al precedente r1...



IF (y - y') = 0 THEN
    w0(r2) = w0(r1)
    w1(r2) = w1(r1)
    w2(r2) = w2(r1)
ELSE


En términos de concreción en nuetro soporte Calc necesitamos añadir tres nuevas columnas, una para cada nuevo valor wi' y establecer mediante asignación de contenido el valor de las originales celdas de las columnas wi la relación entre estas celdas y las que contienen los valores wi'. Cada celda wi' deberá contener la fórmula built-in que expresamos antes, una vez esté plenamente desarrollada (que no es ahora el caso).

A pesar de ello, por ir avanzando, implementaremos la nueva modificación de nuestro soporte calc para dar cabida a la estructura que será plenamente funcional en breve. Observese que los nuevos valores de los pesos (incluido w0 o bias se identifican como wi', aunque su sóla ubicación diferenciada de los wi operativos lo hace innecesario.

X₀ X₁ X₂ w₀ (b) w₁ w₂ z y' y y-y' w₀' w₁' w₂'
1 0 0 0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0

Y no lo es porque aun nos falta por saber cómo se concreta el procedimiento de aprendizaje del perceptrón cuando no existe coincidencia entre y e y', dicho de otro modo, cuando es necesario activar el procedimiento de modificación de los valores que son modificables (los pesos y el bias).

Obsérvese también que en la nueva reformulación de la tabla hemos incluído lo que sería el inicio del segundo registro, incluyendo los valores conocidos de sus Xi, pero también los valores Wi que derivan, por asociación de los precedentes Wi' del registro que precede. En este caso, esos valores Wi se generan automáticamente mediante copia o mediante asociación, tomando, por ejemplo, W0(r2) su valor del contenido de la celda W0'(r1) (4).

Aun tenemos pendiente explicar como se expresa la regla de aprendizaje del perceptrón cuando no se produce la igualdad entre y e y', esto es, cómo se concrea la segunda rama (Else) del condicional anterior.



IF (y - y') = 0 THEN
    w0(siguiente) = w0(actual)
    w1(siguiente) = w1(actual)
    w2(siguiente) = w2(actual)
ELSE
    w0(siguiente) = w0(actual) + η * (y - y') * X0
    w1(siguiente) = w1(actual) + η * (y - y') * X1
    w2(siguiente) = w2(actual) + η * (y - y') * X2
    

Analicemos ahora la fórmula del llamado "aprendizaje" generalizándola a partir de una de sus concreciones. Para W0, por ejemplo, tenemos que W0'= W0 + η * (y - y') * X0, luego podemos decir que Wi' = Wi + η * (y - y') * X0, siendo...

  • Wi' el valor que adquirirá el peso en la siguiente etapa del cálculo de z
  • Wi el valor precedente del peso. Obsérvese que es el único elemento de la fórmula que se suma al producto de los restantes
  • η (eta) es una constante (es la misma en cada una de las expresiones y a lo largo del todo el entrenamiento) que introducimos en la fórmula y que identificamos como tasa de aprendizaje (learning rate), que determina el tamaño del ajuste cuando el perceptrón "se equivoca". Como constante no resulta del propio "aprendizaje", siendo introducida por el programador como parte del algoritmo. Como tal elemento requiere su espacio en nuestra configuración Calc, lo que nos obliga a realizar una última modificación de nuestra hoja, con la peculiaridad que implica su carácter de constante.
X₀ X₁ X₂ w₀ (b) w₁ w₂ z y' y y-y' w₀' w₁' w₂' η
1 0 0 0 0 0 0 0 0 0 0 0 0 0,2
1 1 0 0 0 0
  • (y - y') es, como ya sabemos, el resultado de la diferencia entre y e y' que, como vemos participa de nuevo en la arquitectura del preceptrón. Ahora determinando la dirección del producto η * (y - y') * Xi, esto es, del ajuste de los sesgos
  • Finalmente, Xi es el valor que tiene la variable de entrada correspondiente al peso que estamos ajustando. Su participación en la fórmula hace que la corrección de cada peso tenga en cuenta la presencia y el valor de esa variable en el ejemplo que produjo el error. De este modo, no todos los pesos tienen necesariamente que modificarse en la misma medida: el ajuste de cada uno depende también del valor que tenía su correspondiente Xi.

Para finalizar, diremos que se considera lograda la convergencia cuando, tras aplicar el procedimiento de aprendizaje las veces que sea necesario (siempre en términos de épocas), persiste la igualdad y' = y para todos los registros de una época, lo que se traduce en que todos los Wi' = 0.

Los valores Wi que deberemos tomar como referencia para la fase de aplicación serán los correspondientes a W0, W1 y W2 del primer registro a partir del cual se mantiene la igualdad y' = y y, por tanto, la diferencia y − y' = 0 y los pesos permanecen inalterados. Estos serán los valores definitivos de los diferentes pesos, incluida la bias o W0 (5).

Documento. Desde este enlace puedes descargar el archivo Calc que contiene la versión heurístico y perceptrón del abordaje de AND.

NOTAS
1 Que es la fórmula que contiene la celda situada debajo de la b>cabecera z
2 Esta reformulación permite expresar la suma ponderada como un producto escalar, proporcionando una formulación matemática compacta y general que no depende del número de entradas y que facilita tanto su implementación computacional como la formulación del mecanismo de aprendizaje.
3 Estamos hablando de la propiedad del elemento neutro de la multiplicación. El 1 es el elemento neutro multiplicativo, porque cualquier número multiplicado por 1 permanece inalterado.
4 Realmente este procedimiento de asociación que genera los nuevos valores Wi resulta más difícil de explicar que de ejecutar. Analizar el funcionamiento del soporte Calc te va a permitir comprender la sencillez con la que se realiza con mucha mayor facilidad que lo que esta enrevesada explicación da a entender.
5 Debemos tener en cuenta que esa posición, el momento en que se produce la convergencia, va a depender de varios factores, incluyendo el tamaño de la variable que implementemos como tasa de aprendizaje. Ello obliga a adaptar el procedimiento de uso del perceptrón de nuestro soporte Calc a la realidad concreta que resulte del proceso de aprendizaje.

martes, 14 de julio de 2026

IA. Modelos

Perceptrón (I)

IA. Modelos y paradigmas. Machine Learning

Cuando hablamos de Machine Learning en la introducción a esta subsección lo consideramos como un paradigma radicalmente diferenciado de la IA basada en la heurística, en tanto que, mientras que para esta última lo nuclear es la expresión explícita de la regla (o reglas), en el ML el objetivo es lograr que el algoritmo «aprenda» a partir de los datos.

El perceptrón, propuesto por Frank Rosenblatt en 1957, constituye una de las propuestas ML más sencillas y, al mismo tiempo, más coherente con esa diferenciación. Aunque hoy existen modelos mucho más potentes, el perceptrón conserva un extraordinario valor didáctico y práctico. Su comportamiento resulta lo suficientemente transparente como para comprender cómo aprende un modelo de Machine Learning y, al mismo tiempo, convertir el propio proceso de construcción y entrenamiento en una oportunidad para revisar críticamente la forma en que hemos definido el problema que intentamos resolver.

El perceptrón es un modelo de clasificación binaria que se entrena utilizando ejemplos (registros de datos) previamente etiquetados, por lo que pertenece al ámbito del aprendizaje supervisado. Su objetivo es aprender, a partir de esos ejemplos, una forma de asignar cada nuevo caso a una de dos categorías en función de los datos que lo describen. En su forma clásica, esa clasificación es binaria: sí o no, verdadero o falso, clase A o clase B.

Esta clasificación binaria resulta fundamental para comprender el funcionamiento del perceptrón, ya que el modelo de Rosenblatt determina la clase de un ejemplo mediante una frontera de decisión lineal. Por ello, para que un perceptrón simple pueda aprender correctamente una clasificación, las dos categorías deben ser linealmente separables: debe existir una recta, en el caso de dos variables de entrada, o un hiperplano en dimensiones superiores, capaz de separar los ejemplos pertenecientes a ambas clases.

Para entender mejor qué significa esto, una representación gráfica de lo que estamos diciendo bien podría ser la siguiente. En ella se diferencian dos categorías, representadas mediante puntos azules y rojos, entre las que podría trazarse una recta capaz de separarlas. Aunque la frontera de decisión no aparece dibujada, su posible existencia resulta intuitivamente apreciable a partir de la distribución de los datos.

Existen algunos problemas que, por su sencillez y valor didáctico, resultan especialmente adecuados para comprender el funcionamiento del perceptrón. Uno de los más habituales es la función lógica AND. Su estructura permite utilizar un mismo problema para mostrar dos formas radicalmente diferentes de abordarlo: podemos expresar explícitamente la regla que determina su resultado, como haríamos desde una aproximación heurística, o proporcionar al perceptrón los ejemplos correspondientes para que aprenda a reproducir esa clasificación a partir de los datos.

Empecemos por el paradigma heurístico, que ya nos es conocido de tratamientos anteriores.

Como bien sabemos, la función lógica AND solo resulta V/1 cuando las dos condiciones se cumplen simultáneamente. Si ninguna de ellas se cumple o solo se cumple una, el resultado es invariablemente F/0. Esta sencilla regla puede trasladarse fácilmente a un procedimiento algorítmico y, desde un punto de vista práctico, implementarse también en una hoja de cálculo como LibreOffice Calc, incluso mediante el uso de sus funciones incorporadas (built-in).

Procedamos, pues, a implementar nuestra Puerta lógica AND en Calc, por aquello de demostrar la teoría en la práctica, como corresponde a una buena lógica didáctica. El rsultado es algo tan sencillo como lo que muestra esta imágen que explico a continuación

Podemos apreciar dos regiones diferenciadas dentro de la hoja de cálculo, aunque sólo una de ellas tiene interés desde el punto de vista de la implementación del algoritmo. La primera, correspondiente a las celdas de Entrada de datos, cumple únicamente una función de input: nuestro algoritmo se basa en solicitar al usuario los valores (V-1 o F-0) de cada una de las variables (X1 y X2).

La segunda región, identificada como Solución, es la que contiene la implementación de la regla. En ella, sobre Condición se evalúa ésta mediante la fórmula =Y(E5=1;F5=1), que determina si ambas variables tienen simultáneamente el valor 1. En realidad, la celda Solución se limita a facilitar la visualización del resultado mediante la fórmula =SI(G5=1;1;0), transformando el resultado lógico de la condición en el valor numérico 1 o 0.

Falta, no obstante, una tercera región en nuestra hoja de cálculo: aquella destinada a recoger los resultados obtenidos tras la ejecución sucesiva de nuestro algoritmo. Podemos imaginar, por ejemplo, que ejecutamos el algoritmo 24 veces, proporcionando en cada ejecución una combinación diferente de valores para X1 y X2. En este caso, el resultado podría organizarse en forma de una tabla de doble entrada como la que se muestra a continuación, de la que hemos capturado únicamente los seis primeros casos o registros.

Podemos concluir este sencillo experimento heurístico señalando que, mediante la aplicación de la fórmula o función =Y(E5=1;F5=1), hemos obtenido un conjunto de resultados que podemos almacenar como registros en una tabla. El funcionamiento seguido responde al esquema habitual de un programa informático: definimos previamente el procedimiento que debe aplicarse y obtenemos los resultados como consecuencia de su ejecución: Algoritmo → procedimiento heurístico → resultados

Es este orden el que nos interesa retener, porque el aprendizaje automático (ML) va a introducir un cambio procedimental sustancial: proporcionar al algoritmo ejemplos de entradas y resultados para que, a partir de ellos, determine los parámetros de un modelo capaz de relacionarlos. De este modo, el ML, que concretaremos ahora en el perceptrón, no necesita explicitar la regla que resuelve el problema. En reumen: Datos → aprendizaje → modelo → resultados.

Empezaremos retomando los datos de la tabla AND, que ahora tomaremos como punto de partida para explicar la arquitectura de nuestro perceptrón, sin detenernos en su procedencia ya que, para nuestro propósito actual, su origen carece de interés.

Lo que sí nos interesa es que cada registro contiene dos valores de entrada, X₁ y X₂, asociados a una etiqueta o resultado y. Esta última es fundamental, ya que constituye la salida esperada que el modelo deberá aprender a producir a partir de las entradas. Esto sitúa al perceptrón dentro del ML de aprendizaje supervisado, característica que determina un aspecto esencial de su arquitectura: durante el entrenamiento, el modelo podrá comparar la salida que produce con la etiqueta conocida y, utilizando esa diferencia para ajustar sus parámetros.

Podríamos expresar esta estructura (arquitectura) como sigue:

  • X1 , X2 → z → comparación (z,y)
  • Si z <> y → ajuste (mecanismo de aprendizaje)

Con este esquema podemos identificar ya los elementos fundamentales del funcionamiento de un perceptrón: recibe unos valores de entrada, obtiene a partir de ellos un resultado, lo compara con la etiqueta conocida y, cuando ambos no coinciden, modifica sus parámetros. Hemos descrito así la lógica básica de su arquitectura, sin entrar todavía en la forma concreta en que se realizan estas operaciones. Próximamente nos ocuparemos de explicar todo lo que esto implica.

jueves, 9 de julio de 2026

IA

LLM y el conocimiento especializado

Los límites de la IA

Las dos entradas anteriores abordaban dos límites esenciales de los modelos de lenguaje de gran tamaño (LLM) desde la perspectiva del ejercicio profesional. En primer lugar, la necesidad de preservar la confidencialidad de la información introducida en estos sistemas. En segundo término, la imposibilidad de atribuirles una auténtica capacidad de fundamentación jurídica, técnica o científica. Ambos límites conducen a una misma conclusión: el juicio profesional se refuerza como el criterio que dirige la actuación en el ejercicio de la orientación educativa.

Existe, sin embargo, un tercer límite de los LLM que suele pasar más desapercibido y que resulta igualmente determinante: la relación entre el conocimiento con el que ha sido entrenado un modelo y el ámbito profesional en el que se utiliza.

No todos los problemas pueden resolverse con una herramienta de propósito general. En ocasiones, utilizar un modelo no adaptado al dominio específico de conocimiento constituye una decisión técnicamente inadecuada y, por ello mismo, potencialmente incompatible con el deber de diligencia exigible al profesional.

Un modelo generalista no es un especialista

Uno de los equívocos más frecuentes consiste en identificar la enorme capacidad lingüística de un LLM con un conocimiento especializado de cualquier materia. Sin embargo, ambos aspectos son conceptualmente distintos.

Los modelos de lenguaje transforman los textos en representaciones numéricas y ajustan sus parámetros para modelizar las relaciones estadísticas existentes entre esas representaciones.De ese proceso no emerge un conocimiento sobre el tema consultado, sino una secuencia de tokens obtenida mediante cálculo probabilístico. Cuando esa secuencia resulta coherente y se ajusta a nuestras expectativas lingüísticas y contextuales, tendemos a interpretarla como una respuesta fundada sobre la materia de que se trate. Pero esa apariencia no equivale en ningún momento al dominio experto de una disciplina.

Precisamente porque el entrenamiento del modelo persigue modelizar estadísticamente el lenguaje en toda su diversidad, y no adquirir conocimientos especializados, su capacidad es necesariamente generalista. No se trata de una limitación accidental, sino de una consecuencia directa de la forma en que ha sido entrenado.

De ahí que ese modelo de lenguaje pueda producir respuestas extraordinariamente convincentes sobre prácticamente cualquier ámbito del conocimiento. Esa amplitud temática es consecuencia de un entrenamiento generalista sobre cantidades ingentes de textos y no de una especialización en un determinado dominio profesional o institucional. Por ello, sus respuestas reproducen los patrones generales presentes en los datos de entrenamiento, pero no incorporan, por sí mismas, los criterios conceptuales, técnicos, organizativos o normativos propios de un contexto profesional o institucional específico.

La incorporación del contexto especializado.

Precisamente porque los modelos de lenguaje son herramientas de propósito general, su utilización en contextos profesionales específicos suele requerir mecanismos adicionales que incorporen el conocimiento propio del dominio o de la organización en la que van a emplearse.

Uno de ellos es el denominado ajuste fino (fine-tuning), mediante el cual un modelo previamente entrenado continúa su entrenamiento utilizando datos específicos de un determinado dominio para adaptar parcialmente su comportamiento.

Otra estrategia, cada vez más extendida, consiste en incorporar sistemas de recuperación de información (Retrieval-Augmented Generation o RAG), que permiten al modelo utilizar documentación específica durante la generación de la respuesta.

En ambos casos, el objetivo es el mismo: aproximar un modelo de propósito general a las necesidades concretas de un determinado ámbito profesional o institucional.

Adaptar el modelo no elimina la responsabilidad

La existencia de estas técnicas podría inducir a pensar que un modelo adaptado ofrece respuestas objetivamente correctas y que, por tanto, disminuye la responsabilidad del profesional que las utiliza.

Esa conclusión sería errónea. El ajuste fino y los sistemas RAG pueden mejorar la adecuación de las respuestas a un determinado contexto profesional o institucional, pero no modifican la naturaleza del modelo. Éste continúa generando respuestas mediante cálculo probabilístico y permanece ajeno a cualquier criterio propio de verdad, corrección o validez.

Por ello, la incorporación de contexto especializado no desplaza el juicio profesional ni elimina la necesidad de verificar la corrección técnica, jurídica o metodológica de las respuestas obtenidas.

La adaptación del modelo mejora las condiciones en las que se genera la respuesta, pero no modifica la distribución de responsabilidades: la decisión de aceptarla, descartarla o integrarla en la actuación profesional continúa correspondiendo exclusivamente al profesional.

En definitiva, la especialización de un modelo de lenguaje no consiste en transformar su naturaleza, sino en incorporar el contexto necesario para aproximar sus respuestas a las exigencias de un determinado ámbito profesional o institucional. Precisamente por ello, la adaptación del modelo mejora su utilidad, pero no elimina la necesidad del conocimiento ni del juicio profesional.

IA

LLM y la motivación de las decisiones

Los límites de la IA

En la entrada anterior analicé uno de los principales condicionantes jurídicos del uso profesional de los modelos de inteligencia artificial generativa: la confidencialidad de la información que se introduce en ellos y la responsabilidad compartida de los diferentes actores implicados en su utilización.

Existe, sin embargo, un segundo límite, menos conocido pero probablemente más profundo, que afecta al modo en que el sistema genera sus respuestas. Se trata de la imposibilidad de explicar realmente por qué produce una determinada respuesta y no otra.

No es una limitación circunstancial ni una carencia que pueda corregirse mediante una simple actualización del sistema. Es una consecuencia directa de la arquitectura de los grandes modelos de lenguaje (Large Language Models o LLM) basados en técnicas de aprendizaje profundo (Deep Learning). Precisamente por ello, constituye un factor tanto técnico como jurídico que condiciona su utilización en cualquier ámbito en el que las decisiones deban ser motivadas.

La motivación de las decisiones. Una exigencia profesional y una obligación jurídica

Antes de constituir una obligación jurídica, la motivación de las decisiones es una exigencia inherente al ejercicio profesional del orientador educativo. Toda actuación técnica que pueda afectar a otras personas debe poder explicarse mediante razones objetivas, comprensibles y vinculadas al conocimiento especializado del que deriva. Motivar una decisión significa hacer explícitos los datos considerados, los criterios aplicados y el razonamiento que conduce desde unos a la conclusión adoptada.

Esta exigencia cumple varias funciones esenciales. Permite revisar la solidez del propio razonamiento, facilita el trabajo cooperativo entre iguales —por ejemplo, entre orientadores—, así como el intercambio y contraste de información con profesionales de otras especialidades; favorece la coherencia entre casos similares y hace posible que las personas afectadas comprendan el fundamento de las decisiones que les conciernen.

Sin esta capacidad de justificar y someter a revisión las decisiones adoptadas, el juicio profesional pierde transparencia y se debilitan las garantías de calidad, responsabilidad y control que caracterizan el ejercicio de cualquier profesión técnica, especialmente cuando sus actuaciones producen efectos sobre otras personas.

Esta exigencia, inherente al ejercicio responsable de cualquier profesión, adquiere una especial relevancia cuando las decisiones se adoptan en el marco de procedimientos sujetos al Derecho. En estos casos, la motivación deja de ser únicamente un criterio de calidad técnica para convertirse también en una exigencia jurídica destinada a garantizar la legalidad de la actuación, la protección de los derechos de las personas afectadas y la posibilidad de su control y revisión.

En el ámbito del Derecho no basta con decidir correctamente; es necesario poder explicar por qué se ha decidido de una determinada manera.

La exigencia de motivación constituye uno de los pilares del Estado de Derecho. En el ámbito administrativo encuentra una manifestación expresa en el artículo 35 de la Ley 39/2015, de 1 de octubre, del Procedimiento Administrativo Común de las Administraciones Públicas, que impone la obligación de motivar determinados actos administrativos mediante una sucinta referencia a los hechos y a los fundamentos de Derecho que los sustentan.

La motivación permite comprobar que la actuación administrativa responde a criterios jurídicos y racionales, facilita el ejercicio efectivo del derecho de defensa y hace posible su control por los órganos administrativos y judiciales. Constituye, además, una garantía esencial frente a la arbitrariedad de los poderes públicos, cuya interdicción proclama el artículo 9.3 de la Constitución Española.

Esta exigencia trasciende el Derecho español. En el ámbito de la Unión Europea, el deber de motivar las decisiones constituye una manifestación del derecho a una buena administración reconocido por el artículo 41 de la Carta de los Derechos Fundamentales de la Unión Europea, que exige que las actuaciones de las instituciones públicas puedan justificarse mediante razones comprensibles y susceptibles de control.

La misma lógica inspira la normativa europea sobre protección de datos personales. El Reglamento General de Protección de Datos (RGPD) impone obligaciones de transparencia en el tratamiento de la información y reconoce garantías específicas frente a determinadas decisiones automatizadas, incluyendo el derecho de la persona afectada a obtener información significativa sobre la lógica aplicada cuando concurren los presupuestos del artículo 22. Aunque el alcance de este denominado «derecho a la explicación» continúa siendo objeto de debate doctrinal, resulta indiscutible que el Reglamento refuerza las exigencias de transparencia y control sobre los procesos automatizados.

En esta misma línea, el Reglamento (UE) 2024/1689, de Inteligencia Artificial, establece obligaciones de documentación técnica, trazabilidad, transparencia y supervisión humana para los sistemas de IA de alto riesgo. Estas medidas pretenden garantizar que quienes los utilizan comprendan sus capacidades y limitaciones, puedan interpretar adecuadamente sus resultados y mantengan en todo momento el control sobre las decisiones adoptadas. El Reglamento no presupone que estos sistemas sean capaces de explicar por sí mismos el proceso interno que conduce a sus resultados; por ello, desplaza las garantías hacia la documentación, la trazabilidad y la supervisión humana, sin sustituir la obligación de motivar las decisiones cuando esta resulta exigible conforme al ordenamiento jurídico.

En el ámbito educativo, estas exigencias se proyectan sobre numerosas decisiones que afectan directamente a los alumnos: evaluaciones, promociones de curso, medidas disciplinarias, adaptaciones curriculares, identificación de necesidades específicas de apoyo educativo o adopción de medidas organizativas individualizadas. En todos estos supuestos no basta con alcanzar una conclusión; resulta imprescindible justificarla mediante criterios objetivos, verificables y susceptibles de revisión.

En definitiva, tanto el Derecho español como el europeo parten de un mismo principio: la legitimidad de una decisión no depende únicamente de su corrección material, sino también de la posibilidad de explicar las razones que la fundamentan y de someterlas al escrutinio de quienes puedan revisarlas o verse afectados por ellas.

Lo que hace realmente un modelo de lenguaje

Aquí aparece la principal dificultad.

Es precisamente en este punto donde aparecen los límites de los grandes modelos de lenguaje. Si el ordenamiento exige que determinadas decisiones puedan justificarse mediante razones objetivas y revisables, resulta necesario preguntarse si un sistema de IA generativa puede proporcionar realmente esa justificación.

Con frecuencia se afirma que un chatbot «explica» sus respuestas. En realidad, lo que hace es algo muy distinto.

Los modelos de lenguaje actuales no razonan reconstruyendo una cadena lógica de inferencias semejante a la que seguiría un jurista, un médico o un docente. Tampoco almacenan una relación explícita entre premisas y conclusiones que pueda recuperarse posteriormente para explicar cómo alcanzaron un determinado resultado.

Su funcionamiento responde a una lógica completamente diferente.

Durante el entrenamiento, el modelo ajusta miles de millones —e incluso billones— de parámetros mediante técnicas de optimización matemática destinadas a minimizar el error en la predicción del siguiente elemento de una secuencia lingüística. Ese conocimiento no queda almacenado como reglas identificables ni como argumentos jurídicos o científicos, sino distribuido estadísticamente a través del conjunto de los parámetros de la red neuronal.

Cuando el usuario formula una consulta, el modelo no recupera un razonamiento previamente elaborado ni una cadena de inferencias almacenada. Genera la respuesta calculando, paso a paso, el token con mayor probabilidad de aparecer en cada posición del texto a partir del contexto recibido. Cada predicción condiciona la siguiente hasta completar una respuesta que puede resultar coherente y convincente, pero cuyo proceso de generación no equivale a un razonamiento explícito susceptible de reconstrucción posterior.

El resultado puede ser útil. Puede ser correcto. Puede incluso presentar una argumentación perfectamente estructurada. Sin embargo, esa argumentación no constituye el razonamiento interno que condujo al modelo hasta la respuesta, porque ese razonamiento no existe como una secuencia explícita de inferencias. Cuando se le pide que explique por qué respondió de una determinada manera, el modelo no recupera el proceso que siguió para llegar a esa conclusión; genera una nueva explicación mediante el mismo mecanismo probabilístico con el que produjo la respuesta original.

Esta limitación no significa que los modelos de lenguaje sean completamente opacos para la investigación científica. Existen diversas técnicas de interpretabilidad que permiten analizar determinados aspectos de su funcionamiento interno o estimar la influencia de distintos elementos en la generación de las respuestas. Sin embargo, estos métodos no reconstruyen una cadena de razonamientos comparable a la motivación exigida en el ámbito profesional o jurídico. Ofrecen información sobre el comportamiento estadístico del modelo, no sobre las razones que justificarían una decisión.

Una limitación técnica con consecuencias jurídicas

Esta limitación técnica no constituye un simple inconveniente funcional. Delimita el ámbito en el que los modelos de lenguaje pueden utilizarse legítimamente cuando las decisiones deben ser motivadas.

Si un procedimiento exige que las decisiones puedan justificarse mediante razones objetivas, verificables y revisables, la utilización de una herramienta incapaz de ofrecer esa justificación no impide necesariamente su empleo, pero sí condiciona el modo en que puede integrarse en el proceso de decisión y las garantías que deben acompañar su utilización.]

Ello afecta, en primer lugar, a las empresas que desarrollan y comercializan estos sistemas. Quienes ofrecen soluciones basadas en modelos generativos para sectores regulados no pueden presentar estos sistemas como capaces de sustituir el juicio profesional cuando el ordenamiento exige decisiones motivadas y susceptibles de revisión. Las obligaciones de transparencia, gestión del riesgo e información previstas por el Reglamento europeo de Inteligencia Artificial adquieren precisamente sentido frente a estas limitaciones estructurales.

Pero la consecuencia más importante recae sobre el profesional.

El juicio que no puede delegarse

Al igual que ocurría con la confidencialidad, el problema ya no reside únicamente en las limitaciones del modelo. Desde la perspectiva del juicio profesional —y, por ello, también desde la jurídica— la cuestión decisiva es la decisión de utilizar una herramienta cuyas limitaciones el profesional conoce, o debe conocer, y de integrar sus resultados en el propio proceso de decisión.

Si un docente, un inspector, un orientador, un abogado o cualquier otro profesional adopta una recomendación elaborada por un modelo generativo sin reconstruir y verificar por sí mismo las razones que justifican su utilización en el caso concreto, el problema deja de ser una limitación tecnológica para convertirse en una deficiencia del propio juicio profesional.

En consecuencia, la motivación de una evaluación, un informe psicopedagógico, una medida disciplinaria o una resolución administrativa no puede descansar únicamente en una argumentación generada por un modelo de inteligencia artificial. Corresponde al profesional comprobar su corrección, asumirla como propia y poder justificarla mediante razones objetivas, verificables y susceptibles de revisión.

La IA puede servir como instrumento de apoyo para localizar información, sugerir argumentos, identificar normativa aplicable o explorar hipótesis. Lo que no puede hacer es sustituir la responsabilidad intelectual de quien debe seleccionar esos argumentos, verificar su corrección y asumirlos como propios.

La utilización de modelos de lenguaje puede constituir un valioso instrumento de apoyo para el ejercicio profesional. Sin embargo, allí donde el ordenamiento exige decisiones motivadas, objetivamente justificables y susceptibles de revisión, la responsabilidad de aportar esa motivación continúa correspondiendo íntegramente al profesional. Precisamente porque el modelo no puede ofrecerla por sí mismo, el juicio profesional deja de ser un elemento complementario para convertirse en una garantía jurídica insustituible.

La motivación no constituye un requisito adicional que deba añadirse al resultado generado por un modelo de lenguaje. Constituye un acto propio del juicio profesional y, precisamente por ello, una responsabilidad que no puede ser objeto de delegación. En ello reside uno de los criterios fundamentales para el uso profesional de la inteligencia artificial: cuanto mayor sea la exigencia de motivar una decisión, más irrenunciable resulta el juicio del profesional que la adopta.