Como comentábamos en el artículo acerca de la Inteligencia Artificial para la contratación pública, uno de nuestros objetivos n Gobierto Contratación es utilizar todos los datos que hay para ayudar a las administraciones a entender, predecir y gobernarla. Para ello, comenzamos con la predicción de ofertas que puede recibir una licitación pública. Pero ¿Cómo hacemos esto? Aquí os vamos a contar cómo hemos creado el modelo que usamos, los problemas que encontramos y las decisiones que hemos tomado para crear el modelo que aplicamos para este fin.
La obtención de datos: un entorno complejo y fragmentado
Lo primero que hay que decir es que tenemos la suerte de vivir en la época de los portales de contratación pública y de la existencia de CODICE. Lo primero nos permite acceder a los datos que necesitamos, mientras que el uso del lenguaje estándar de CODICE nos permite utiulizar los mismos modelos de extracción y tratamiento de datos para todas las licitaciones. En Gobierto Contratación tenemos, en la actualidad, los datos de licitaciones de la Plataforma de Contratación del Sector Público, la de Catalunya y la de Euskadi.
Analizaremos todos los contratos menos de los menores (cuyo número de ofertas no se publica) y de los negociados sin publicidad. Utilizamos Ruby para obtener los datos mezclando la captura de información de los portales con los juegos de datos abiertos que publican. A partir de aquí, la suerte se nos acaba.
En primer lugar hay que tener en cuenta que aunque el contenido y el modelo de datos es compartido, cada plataforma tiene sus prácticas y mecánicas de publicación, lo que nos lleva a crear mecanismos específicos para cada caso.
Pero el auténtico problema que encontramos es que hay una enorme irregularidad y falta de consistencia o validez en muchos datos. Por un lado, no todas las administraciones publican los datos de la misma manera (por ejemplo, la publicación de importes de adjudicación o importes básicos cuando se habla de suministros unitarios se hace de manera muy diferente según quién los crea).
Por otro lado hay licitaciones con información incompleta o incongruente, como por ejemplo, contratos adjudicados sin un número de ofertas informadas, lo que es imposible, porque al menos habría una.
Las variables que usamos
En nuestro modelo, en la actualidad, utilizamos variables explícitas del contrato. Es decir, aunque en un futuro,como veremos, planteamos introducir algunas variables, de momento utilizamos aquellas que pueden tener algún sentido en el número de ofertas y se informan de manera general en la mayoría de las licitaciones. Estas son:
- Identificación del contrato
- Fecha de cierre de recepción de ofertas
- Presupuesto base de licitación con el IVA incluido. En el caso de que haya un sistema de lotes que informe el importe de manera detallada, lo que no ocurre siempre, será el que usemos.
- Tipo de contratante. El tipo de Administración utilizando la estructura de DIR3 que incluye AGE, Entes Locales, CCAA, empresas públicas o UGEP, por poner algunos ejemplos.
- Tipo de contrato
- Tipo de procedimiento
- Categoría, en el que utilizamos los dos primeros caracteres del CPV permitiendo hacer categorías con suficiente volumen para realizar la comparación
Y nuestra variable dependiente que es, lógicamente:
- Número de ofertas recibidas por la licitación
Preparando los datos
Un poco de limpieza
Antes de ponernos a trabajar, tenemos que preparar nuestros datos siguiendo estos pasos:
- Eliminar los contratos que no aplican (menores y negociados sin publicidad)
- Asignar a las licitaciones que no tienen contrato un valor de 0 ofertas
- Completar datos faltantes de columnas asignando un valor específico, que es el máximo que tiene esa categoría, lo que nos facilita luego su codificación
- Convertir las fechas en sellados de tiempo para poder hacer operaciones como extraer el mes, calcular el trimestre, etc.
- Hacer una categoría para entes locales, dado que queremos centrarnos, en principio, en estas.
- Descartar las columnas que no van a tener uso
- Reducimos la variabilidad del presupuesto para hacer más consistentes las estimaciones.
- Creamos una variable para estratificar las muestras en los elementos más importantes, como tipo de contrato, contratante, proceso o número de propuestas. Esto permite descartar casos raros (en los que la combinación e estos 4 elementos no se repite muy a menudo)
- Eliminamos contratos que informan un presupuesto base de 0 y las filas que contienen valores vacíos.
Codificando categorías
De todos estos datos, solo se pueden operar aquellos que son numéricos. Es por ello que tenemos que convertir en código aquellos que son variables categóricas como son:
- Tipo de contratante
- Tipo de contrato
- Tipo de proceso
- Año
- Trimestre
- Mes
- Estación
- Semana
- Dia
Usando un one hot encoder que crea una columna para cada una de las categorías de estas variables dándoles valor 1 a las que son verdaderas y 0 a las falsas.
Normalizando valores
Por último, tenemos que normalizar los valores numéricos. Esto se debe a que una alta dispersión de estos puede “despistar” a nuestro algoritmo. Es decir, presupuestos base de licitación muy altos o licitaciones con un número inusualmente elevado de ofertas que no son representativos se eliminan. Adicionalmente, reducimos la dispersión para que los valores que devuelve el algoritmo sean consistentes. Es decir, si hacemos que haya mucha distancia entre los importes de base más pequeños y los más grandes, será más difícil hacer una predicción fiable. Para ello:
- Asignamos a las columnas sus valores
- Obtenemos un logaritmo natural de todos los datos, lo que los normaliza. A las propuestas que tienen un valor 0 se le asigna un 1. Esto hace que este proceso se realice para el modelo de regresión y no el de clasificación.
- Obtenemos la media y desviación estándar de cada una de las columnas y recortamos los valores por encima de la media más menos la desviación, eliminando valores extremos
- Obtenemos todas estas entradas y las filtramos
En todo caso, antes de empezar, tenemos que acabar de ajustar los valores numéricos
Finalmente, dividimos nuestro juego de datos para entrenarlo y probarlo:
- El 80% de los datos se empleará para entrenar el algoritmo
- El 10% servirá para validar los resultados
- El 10% servirá para comprobar los resultados del algoritmo en casos reales.
Con esto ya estamos casi listos para empezar a predecir.
Eligiendo los modelos
La elección de modelos es la que nos permite asociar una aproximación metodológica al problema que tenemos. Para que nuestro proyecto funcione hemos dividido nuestro problema en dos, lo que nos permite usar dos modelos.
- Predecir si habrá o no ofertas para una licitación. En este caso, tanto por su severidad como por su frecuencia estadística, hemos decidido separarlo del resto del nuestro trabajo. Es decir, aquí no estamos buscando adivinar cuántas ofertas habrá, sino si, con determinados datos, la recepción de ofertas será verdadera o falsa, en cuyo caso se considerará que no tiene ofertas. Se trata de un modelo de clasificación.
- Predecir el número de ofertas que recibirá la licitación. La segunda parte es predecir el número de ofertas que se recibirá a partir de los parámetros detectados. En este caso no estamos clasificando entre verdadero y falso, sino que estamos calculando el número de ofertas esperables y comparándolo con el número real. Por lo tanto, estamos aplicando un modelo de regresión estadística.
Tendrá ofertas una licitación: el modelo de clasificación
Nuestro modelo de clasificación es un clasificador binario que se encarga de determinar si una licitación dada tendrá o no ofertas. Si el modelo retorna 1, sí recibirá al menos 1 oferta, si retorna 0, no recibirá ofertas.
- Arquitectura del modelo
- Ejemplo de red neuronal:

El modelo es una red neuronal con 5 capas (hidden layers), 768 neuronas por cada capa y lo entrenamos en 200 épocas (o fases). Para construir la red neuronal utilizamos TensorFlow version 2. La métrica con la que el modelo de clasificación se evalúa mientras se entrena es binary accuracy, es decir, la razón entre la cantidad de predicciones correctas y todas las predicciones realizadas.
La precisión del modelo es alta, entre 82% y 93% de aciertos sobre el total de casos. Esta oscilación depende de la variabilidad que ofrezca el tipo de contratos. Es decir, un contrato de servicios por procedimiento abierto en una Comunidad Autónoma tiene permite una mayor precisión que uno de obras en un ayuntamiento, por poner un ejemplo, dado que este tipo de contratos y de contratantes tienen una variabilidad mucho más alta.
¿Cuántas ofertas tendrá mi licitación?
La segunda parte es predecir el número de ofertas, lo que facilita tanto la estrategia de cuándo y cómo licitar, como la táctica de promover de manera más activa una licitación. Para ello, recurrimos a una regresión estadística.
El modelo de regresión también es una red neuronal de 5 capas con 768 neuronas por capa y lo entrenamos en 200. La métrica que utilizamos durante el entrenamiento es mean absolute error o MAE, en español error absoluto medio, una medida típicamente usada en estadísticas para comparar dos variables continuas (valores observados y valores calculados), a menor MAE, mejor el modelo (generalmente).
Tras varias pruebas encontramos que una regresión “pura”, es decir, predecir directamente el número de ofertas, ofrecía una mayor variabilidad y error, por lo que tuvimos que buscar un enfoque alternativo. Esto lo hemos hecho utilizando un árbol de decisión que permite acotar el posible número de ofertas. Es decir, primero hacemos una clasificación binaria y, en los casos en los que el número de falsos positivos es alto (es decir, si creemos que es probable que haya, por ejemplo 5 ofertas, pero muchas de las veces que prevemos esto, en realidad no se da), lo pasamos al modelo de regresión.
En muchos de los casos (por ejemplo al pasar por clasificadores de umbral 4, 5, 6 y 7) preferimos obtener el máximo entre el resultado que este modelo retornaría y el modelo de regresión retornaría, ya que las predicciones, en general, tienden a ser más conservadoras.
Esto crea una especie de tope suave a las predicciones del modelo de regresión, donde únicamente retornamos una predicción de más de 7 propuestas cuando el modelo de clasificación con umbral 7 retorna un sí y cuando el de regresión retorna más que 7 luego de redondearlo. La razón de esto es que según nuestros datos, únicamente 1.2 de cada 10 licitaciones recibe más de 7 propuestas.

La capacidad del modelo usando el MAE y los valores residuales, ofrece una cierta variabilidad. En el modelo de entrenamiento tenemos en torno al 80% de casos en los que hay una predicción en la que nos alejamos en menos de 1 oferta de las realmente recibidas. Sin embargo, como ocurre en el caso anterior, hay casos en los que la variabilidad cambia en las siguientes condiciones:
- Licitaciones de construcción y arquitectura (dada su alta variabilidad en número de propuestas, presupuesto, etc)
- Licitaciones cuyo tipo de proceso es Proyecto competición o diálogo competitivo
- Licitaciones de obras públicas
- Algunas licitaciones de ayuntamientos y diputaciones provinciales que coincidan con los casos anteriores.
Lo que está por venir
Con todo esto, nos queda aún un trabajo importante para mejorar el modelo. Esto pasa por:
- Añadir nuevos elementos, como por ejemplo, la especificidad del CPV o la duración del plazo de presentación de ofertas. Nuestro estudio pasado sobre CPVs, determinamos que en muchas ocasiones, mayor especificidad de CPV generalmente representa mayor posibilidad de obtener propuestas.
- Plantear el uso de nuevos modelos, como árboles de decisión con diferentes clasificadores binarios, o de entrada directamente utilizar clasificación multi-clase para evitar sobre-fragmentar el modelo de clasificación.
- Reducir el impacto de la fecha. Actualmente utilizamos muchas variables obtenidas de la fecha, y el modelo tiende a tener mucha variabilidad basada en fechas, cosa que no necesariamente siempre refleja la realidad en términos de números de propuestas, o quizás no lo hace de una manera tan dramática.
- Re-ajustar recortes en relación a presupuesto/número de propuestas diferentes.
A pesar de que los resultados y la capacidad predictiva del modelo son aceptables, siempre hay espacio de mejora y en Populate queremos tener los mejores estándares que nuestras capacidades nos pueden ofrecer. El modelo es útil, pero podría mejorar, y nos mantenemos constantemente cuestionando su validez y capacidad predictiva, además de probar regularmente con diferentes parámetros para ver si logramos romperlo o encontrar errores.