Yo en el Premio de AI principalmente y ganó los desafíos de datos planos y secuenciales. La competencia fue una experiencia de aprendizaje fantástica, y en esta publicación, quiero proporcionar algunas ideas sobre mi solución ganadora.
La competencia
El objetivo de la competencia era generar un conjunto de datos sintético con las mismas propiedades estadísticas que un conjunto de datos de origen, sin copiar los datos.
La competencia se dividió en dos desafíos independientes:
- Desafío de datos planos: Genere 100,000 registros con 80 columnas.
- Desafío de datos secuenciales: Genere 20,000 secuencias (grupos) de registros.
Para medir la calidad de los datos sintéticos, la competencia utilizó un Precisión general métrico. Esta puntuación mide la similitud entre las distribuciones sintéticas y de fuente para columnas individuales (univariables), pares de columnas (bivariables) y triples de columnas (trivariables) utilizando la distancia L1. Además, métricas de privacidad como DCR (distancia al registro más cercano) y NNDR (relación de distancia del vecino más cercano) se utilizaron para garantizar que los envíos no solo estaban sobrecargando o copiando los datos de capacitación.
Diseño de soluciones
Inicialmente, mi objetivo era crear un conjunto de múltiples modelos de última generación y combinar sus datos generados. Experimenté mucho con diferentes modelos, pero los resultados no mejoraron tanto como esperaba.
Pivoté mi enfoque y me concentré en postprocesamiento. Primero, entrené un solo modelo generativo del Sobre todo ai sdky en lugar de generar el número requerido de muestras para la presentación, yo sobremuestrado Para crear un gran grupo de muestras candidatas. A partir de este grupo, seleccioné la salida final de una manera que coincida con las propiedades estadísticas del conjunto de datos de origen mucho más de cerca.
Este enfoque condujo a un salto sustancial en el puntaje de la tabla de clasificación. Para el desafío de datos planos, los datos sintéticos en bruto del modelo obtuvieron alrededor de 0.96, pero después del procesamiento posterior, el puntaje saltó a 0.992. Utilicé una versión modificada de este enfoque para el desafío de datos secuenciales, que produjo una mejora similar.
Mi tubería final para el desafío plano consistió en tres pasos principales:
- Ajuste proporcional iterativo (IPF) para seleccionar un subconjunto de gran calidad de gran tamaño.
- Recorte codicioso Para reducir el subconjunto al tamaño del objetivo eliminando las muestras de peor ajuste.
- Refinamiento iterativo Para pulir el conjunto de datos final intercambiando muestras para obtener un mejor ajuste.
Paso 1: Ajuste proporcional iterativo (IPF)
El primer paso en mi tubería posterior al procesamiento fue obtener un subconjunto inicial fuerte del grupo sobremuestreado (2.5 millones de filas generadas). Para esto, usé Ajuste proporcional iterativo (IPF).
IPF es un algoritmo estadístico clásico utilizado para ajustar una distribución de muestra para que coincida con un conjunto conocido de marginales. En este caso, quería que las distribuciones bivariadas de los datos sintéticos (2 columnas) coincidieran con las de los datos originales. También probé distribuciones uni y trivariadas, pero descubrí que centrarse en las relaciones bivariadas arrojó el mejor rendimiento al tiempo que era computacionalmente rápido.
Así es como funcionó:
- Identifiqué el 5,000 pares de columnas más correlacionados En los datos de capacitación utilizando información mutua. Estas son las relaciones más importantes para preservar.
- IPF luego calculó pesos fraccionados para cada una de las 2.5 millones de filas sintéticas. Los pesos se ajustaron de manera iterativa para que las sumas ponderadas de las distribuciones bivariadas en la piscina sintética coincidieran con las distribuciones objetivo de los datos de entrenamiento.
- Finalmente, utilicé un enfoque de reducción de expectativas para convertir estos pesos fraccionados en un recuento de enteros de cuántas veces se debe seleccionar cada fila. Esto dio como resultado un subconjunto de gran tamaño de 125,000 filas (1.25x el tamaño requerido) que ya tenía una precisión bivariada muy fuerte.
El paso de IPF proporcionó un punto de partida de alta calidad para la siguiente fase.
Paso 2: recorte
Generar un subconjunto de gran tamaño de 125,000 filas de IPF fue una elección deliberada que permitió este paso de recorte adicional para eliminar muestras que no encajaban bien.
Utilicé un enfoque codicioso que calcula iterativamente la “contribución de error” de cada fila en el subconjunto actual. Las filas que más contribuyen a la distancia estadística desde la distribución objetivo se identifican y eliminan. Este proceso se repite hasta que quedan solo 100,000 filas, asegurando que se descarten las peores 25,000 filas.
Paso 3: Refinamiento (intercambio)
El paso final fue un proceso de refinamiento iterativo para intercambiar filas del subconjunto con mejores filas del grupo de datos mucho más grande y no utilizado (las 2.4 millones restantes de filas).
En cada iteración, el algoritmo:
- Identifica las peores filas dentro del subconjunto de 100k actual (las que más contribuyen al error L1).
- Búsqueda de los mejores candidatos de reemplazo del grupo exterior que reduciría el error L1 si se intercambia.
- Realiza el intercambio si resulta en una mejor puntuación general.
Como la precisión de la muestra sintética ya es bastante alta, la ganancia adicional de este proceso es bastante pequeña.
Adaptarse para el desafío secuencial
El desafío secuencial requirió un enfoque similar, pero con dos cambios. Primero, una muestra consta de varias filas, conectadas por la identificación del grupo. En segundo lugar, la métrica de la competencia agrega una medida para coherencia. Esto significa que no solo las distribuciones estadísticas necesitan coincidir, sino que las secuencias de eventos también deben ser similares al conjunto de datos de origen.
Mi tubería de postprocesamiento se adaptó para manejar grupos y también optimizar para coherencia:
- Preselección basada en coherencia: Antes de optimizar la precisión estadística, ejecuté un paso de refinamiento especializado. Este algoritmo cambió de forma iterativa (secuencias) para que coincidan específicamente con las métricas de coherencia de los datos originales, como la distribución de “categorías únicas por secuencia” y “secuencias por categoría”. Esto aseguró que continuamos el postprocesamiento con una estructura secuencial sólida.
- Refinamiento (intercambio): Los 20,000 grupos seleccionados para la coherencia luego pasaron por el mismo proceso de refinamiento estadístico que los datos planos. El algoritmo intercambió grupos enteros con mejores de la piscina para minimizar el error L1 de las distribuciones uni, bi y trivariadas. Un ingrediente secreto debía incluir la “longitud de secuencia” como característica, por lo que las longitudes del grupo también se consideran en el intercambio.
Este enfoque de dos etapas aseguró que el conjunto de datos final fuera fuerte tanto en la precisión estadística como en la coherencia secuencial. Curiosamente, el enfoque basado en IPF que funcionó tan bien para los datos planos fue menos efectivo para el desafío secuencial. Por lo tanto, lo eliminé para enfocar el tiempo de computación en los algoritmos de coherencia y intercambio, lo que arrojó mejores resultados.
Haciéndolo rápido: optimizaciones clave
La estrategia de postprocesamiento por sí misma era computacionalmente costosa, y hacerla funcionar dentro del límite de tiempo de competencia fue un desafío en sí mismo. Para tener éxito, confié en algunas optimizaciones clave.
Primero, yo Redujo los tipos de datos Siempre que sea posible para manejar el grupo de datos de muestra masivo sin quedarse sin memoria. Cambiar el tipo numérico de una matriz grande de 64 bits a 32 o 16 bits reduce en gran medida la huella de la memoria.
En segundo lugar, cuando cambiar el tipo de datos no era suficiente, utilicé matrices escasas de Scipy. Esta técnica me permitió almacenar las contribuciones estadísticas de cada muestra de una manera increíblemente eficiente en la memoria.
Por último, el bucle de refinamiento central implicó muchos cálculos especializados, algunos de los cuales fueron muy lentos con numpy. Para superar esto, usé <strong>numba</strong>. Extrayendo los cuellos de botella en mi código en funciones especializadas con el @numba.njit Decorador, Numba los tradujo automáticamente en un código de máquina altamente optimizado que se ejecuta a velocidades comparables a C.
Aquí hay un ejemplo de cómo necesitaba acelerar el resumen de filas en matrices dispersas, que era un cuello de botella importante en la versión original de Numpy.
import numpy as np
import numba
# This can make the logic run hundreds of times faster.
@numba.njit
def _rows_sum_csr_int32(data, indices, indptr, rows, K):
"""
Sum CSR rows into a dense 1-D vector without creating
intermediate scipy / numpy objects.
"""
out = np.zeros(K, dtype=np.int32)
for r in rows:
start = indptr[r]
end = indptr[r + 1]
for p in range(start, end):
out[indices[p]] += data[p]
return out
Sin embargo, Numba no es una bala de plata; Es útil para el código numérico y pesado en bucle, pero para la mayoría de los cálculos, es más rápido y más fácil mantener a las operaciones numpy vectorizadas. Te aconsejo que solo lo pruebes cuando un enfoque numpy no alcanza la velocidad requerida.
Pensamientos finales
Aunque los modelos ML se están volviendo cada vez más fuertes, creo que para la mayoría de los problemas que los científicos de datos están tratando de resolver, el ingrediente secreto a menudo no está en el modelo. Por supuesto, un modelo fuerte es una parte integral de una solución, pero el preprocesamiento previo y posterior es igualmente importante. Para estos desafíos, una tubería posterior al procesamiento dirigida específicamente a la métrica de evaluación me llevó a la solución ganadora, sin ningún ML adicional.
Aprendí mucho en este desafío y quiero agradecer Sobre todo AI y el jurado por su gran trabajo en la organización de esta fantástica competencia.
Mi código y mis soluciones para ambos desafíos son de código abierto y se pueden encontrar aquí: