Hackatón SNA 2019

En febrero-marzo de 2019 se realizó un concurso para clasificar el feed de la red social. Hackatón SNA 2019, en el que nuestro equipo obtuvo el primer lugar. En el artículo hablaré sobre la organización de la competencia, los métodos que probamos y la configuración de catboost para entrenar con big data.

Hackatón SNA 2019

Hackatón SNA

Esta es la tercera vez que se realiza un hackathon con este nombre. Está organizado por la red social ok.ru, respectivamente, la tarea y los datos están directamente relacionados con esta red social.
En este caso, SNA (análisis de redes sociales) se entiende más correctamente no como un análisis de un gráfico social, sino más bien como un análisis de una red social.

  • En 2014, la tarea consistía en predecir la cantidad de me gusta que obtendría una publicación.
  • En 2016, la tarea VVZ (tal vez le resulte familiar), más cercana al análisis del gráfico social.
  • En 2019, clasificar el feed del usuario en función de la probabilidad de que le guste la publicación.

No puedo decir sobre 2014, pero en 2016 y 2019, además de las habilidades de análisis de datos, también se requirieron habilidades para trabajar con big data. Creo que fue la combinación de aprendizaje automático y problemas de procesamiento de big data lo que me atrajo a estas competiciones, y mi experiencia en estas áreas me ayudó a ganar.

mlbootcamp

En 2019, el concurso se organizó en la plataforma. https://mlbootcamp.ru.

La competición comenzó online el 7 de febrero y constaba de 3 tareas. Cualquiera puede registrarse en el sitio, descargar base y carga tu coche durante unas horas. Al final de la etapa en línea el 15 de marzo, los 15 mejores de cada evento de salto fueron invitados a la oficina de Mail.ru para la etapa fuera de línea, que tuvo lugar del 30 de marzo al 1 de abril.

Tarea

Los datos de origen proporcionan ID de usuario (userId) e ID de publicación (objectId). Si al usuario se le mostró una publicación, entonces los datos contienen una línea que contiene el ID de usuario, el ID de objeto, las reacciones del usuario a esta publicación (comentarios) y un conjunto de varias características o enlaces a imágenes y textos.

ID de usuario ID de objeto ID del propietario realimentación imágenes
3555 22 5677 [me gustó, hice clic] [hash1]
12842 55 32144 [no me gusta] [hash2,hash3]
13145 35 5677 [clic, compartido] [hash2]

El conjunto de datos de prueba contiene una estructura similar, pero falta el campo de comentarios. La tarea es predecir la presencia de la reacción "me gusta" en el campo de retroalimentación.
El archivo de envío tiene la siguiente estructura:

ID de usuario Lista Ordenada[objectId]
123 78,13,54,22
128 35,61,55
131 35,68,129,11

La métrica es el AUC ROC promedio para los usuarios.

Una descripción más detallada de los datos se puede encontrar en sitio web del consejo. Allí también puede descargar datos, incluidas pruebas e imágenes.

etapa en línea

En la etapa online, la tarea se dividió en 3 partes.

Etapa fuera de línea

En la etapa fuera de línea, los datos incluían todas las funciones, mientras que los textos y las imágenes eran escasos. Había 1,5 veces más filas en el conjunto de datos, de las cuales ya había muchas.

Resolución de problemas

Como hago CV en el trabajo, comencé mi andadura en esta competencia con la tarea "Imágenes". Los datos que se proporcionaron fueron ID de usuario, ID de objeto, ID de propietario (el grupo en el que se publicó la publicación), marcas de tiempo para crear y mostrar la publicación y, por supuesto, la imagen de esta publicación.
Después de generar varias características basadas en marcas de tiempo, la siguiente idea fue tomar la penúltima capa de la neurona previamente entrenada en imagenet y enviar estas incrustaciones para impulsarlas.

Hackatón SNA 2019

Los resultados no fueron impresionantes. Las incrustaciones de la neurona imagenet son irrelevantes, pensé, necesito crear mi propio codificador automático.

Hackatón SNA 2019

Tomó mucho tiempo y el resultado no mejoró.

Generación de características

Trabajar con imágenes lleva mucho tiempo, así que decidí hacer algo más sencillo.
Como puede ver de inmediato, hay varias características categóricas en el conjunto de datos y, para no molestarme demasiado, simplemente tomé catboost. La solución fue excelente, sin ninguna configuración llegué inmediatamente a la primera línea de la clasificación.

Hay bastantes datos y están dispuestos en formato parquet, así que sin pensarlo dos veces, tomé Scala y comencé a escribir todo en Spark.

Las funciones más simples que dieron más crecimiento que las incrustaciones de imágenes:

  • cuántas veces aparecieron en los datos el ID de objeto, el ID de usuario y el ID de propietario (debe correlacionarse con la popularidad);
  • cuántas publicaciones ha visto UserId desde OwnerId (debe correlacionarse con el interés del usuario en el grupo);
  • Cuántos ID de usuario únicos vieron publicaciones de OwnerId (refleja el tamaño de la audiencia del grupo).

A partir de las marcas de tiempo fue posible obtener la hora del día en que el usuario vio el feed (mañana/tarde/tarde/noche). Al combinar estas categorías, puede continuar generando funciones:

  • cuántas veces UserId inició sesión por la noche;
  • a qué hora se muestra con mayor frecuencia esta publicación (objectId), etc.

Todo esto fue mejorando poco a poco las métricas. Pero el tamaño del conjunto de datos de entrenamiento es de aproximadamente 20 millones de registros, por lo que agregar funciones ralentizó enormemente el entrenamiento.

He repensado mi enfoque sobre el uso de datos. Aunque los datos dependen del tiempo, no vi ninguna filtración de información obvia “en el futuro”, sin embargo, por si acaso, los desglosé así:

Hackatón SNA 2019

El conjunto de formación que nos proporcionaron (febrero y 2 semanas de marzo) se dividió en 2 partes.
El modelo se entrenó con datos de los últimos N días. Las agregaciones descritas anteriormente se basaron en todos los datos, incluida la prueba. Al mismo tiempo, han aparecido datos sobre los cuales es posible construir varias codificaciones de la variable objetivo. El enfoque más simple es reutilizar el código que ya está creando nuevas funciones y simplemente alimentarlo con datos con los que no será entrenado y objetivo = 1.

Así, obtuvimos características similares:

  • ¿Cuántas veces UserId ha visto una publicación en el grupo OwnerId?
  • ¿Cuántas veces le gustó a UserId la publicación en el grupo OwnerId?
  • El porcentaje de publicaciones que le gustaron a UserId de OwnerId.

Es decir, resultó codificación de destino media en parte del conjunto de datos para varias combinaciones de características categóricas. En principio, catboost también crea codificación de destino y, desde este punto de vista, no hay ningún beneficio, pero, por ejemplo, fue posible contar la cantidad de usuarios únicos a quienes les gustaron las publicaciones en este grupo. Al mismo tiempo, se logró el objetivo principal: mi conjunto de datos se redujo varias veces y fue posible continuar generando funciones.

Si bien catboost puede crear codificación solo en función de la reacción que le gusta, la retroalimentación tiene otras reacciones: compartida, no me gusta, no me gusta, se hace clic, se ignora, codificaciones que se pueden realizar manualmente. Recalculé todo tipo de agregados y eliminé características de baja importancia para no inflar el conjunto de datos.

En ese momento ya estaba en primer lugar por un amplio margen. Lo único que resultaba confuso era que las incrustaciones de imágenes casi no mostraban crecimiento. Surgió la idea de darlo todo a catboost. Agrupamos imágenes de Kmeans y obtenemos una nueva característica categórica imageCat.

A continuación se muestran algunas clases después del filtrado manual y la fusión de clústeres obtenidos de KMeans.

Hackatón SNA 2019

Basado en imageCat generamos:

  • Nuevas características categóricas:
    • Qué imageCat fue visto con mayor frecuencia por userId;
    • ¿Qué imageCat muestra con mayor frecuencia el ID de propietario?
    • Qué imageCat le gustó más a userId;
  • Varios contadores:
    • ¿Cuántas imágenes únicas miró el ID de usuario?
    • Aproximadamente 15 funciones similares más codificación de destino como se describe anteriormente.

Textos

Los resultados del concurso de imágenes me convinieron y decidí probar suerte con los textos. No he trabajado mucho con textos antes y, tontamente, maté el día con tf-idf y svd. Luego vi una línea de base con doc2vec, que hace exactamente lo que necesito. Habiendo ajustado ligeramente los parámetros de doc2vec, obtuve incrustaciones de texto.

Y luego simplemente reutilicé el código de las imágenes, en el que reemplacé las incrustaciones de imágenes con incrustaciones de texto. Como resultado, obtuve el segundo lugar en el concurso de textos.

Sistema colaborativo

Quedaba una competencia que aún no había "pinchado" con un palo y, a juzgar por las AUC en la clasificación, los resultados de esta competencia en particular deberían haber tenido el mayor impacto en el escenario fuera de línea.
Tomé todas las características que estaban en los datos de origen, seleccioné las categóricas y calculé los mismos agregados que para las imágenes, excepto las características basadas en las imágenes mismas. Simplemente poner esto en catboost me llevó al segundo lugar.

Primeros pasos de la optimización catboost

Un primer y dos segundos puestos me agradaron, pero entendí que no había hecho nada especial, lo que significaba que podía esperar una pérdida de posición.

La tarea del concurso es clasificar las publicaciones del usuario, y todo este tiempo estuve resolviendo el problema de clasificación, es decir, optimizando la métrica incorrecta.

Daré un ejemplo simple:

ID de usuario ID de objeto predicción verdad fundamental
1 10 0.9 1
1 11 0.8 1
1 12 0.7 1
1 13 0.6 1
1 14 0.5 0
2 15 0.4 0
2 16 0.3 1

Hagamos un pequeño reordenamiento.

ID de usuario ID de objeto predicción verdad fundamental
1 10 0.9 1
1 11 0.8 1
1 12 0.7 1
1 13 0.6 0
2 16 0.5 1
2 15 0.4 0
1 14 0.3 1

Obtenemos los siguientes resultados:

modelo AUC Usuario1 AUC Usuario2 AUC AUC media
1 opción 0,8 1,0 0,0 0,5
2 opción 0,7 0,75 1,0 0,875

Como puede ver, mejorar la métrica AUC general no significa mejorar la métrica AUC promedio dentro de un usuario.

Catboost sabe cómo optimizar las métricas de clasificación de la caja. Leí sobre métricas de clasificación, historias de éxito cuando use catboost y configure YetiRankPairwise para que entrene durante la noche. El resultado no fue impresionante. Al decidir que no estaba suficientemente capacitado, cambié la función de error a QueryRMSE, que, a juzgar por la documentación de catboost, converge más rápido. Al final obtuve los mismos resultados que cuando entrené para la clasificación, pero los conjuntos de estos dos modelos dieron un buen aumento, lo que me llevó al primer lugar en las tres competiciones.

Cinco minutos antes del cierre de la etapa online del concurso “Sistemas colaborativos”, Sergey Shalnov me pasó al segundo lugar. Recorrimos juntos el camino más lejano.

Preparándose para la etapa fuera de línea

Teníamos garantizada la victoria en la etapa online con una tarjeta de vídeo RTX 2080 TI, pero el premio principal de 300 rublos y, muy probablemente, incluso el primer puesto final nos obligaron a trabajar durante estas 000 semanas.

Al final resultó que, Sergey también usó catboost. Intercambiamos ideas y características, y aprendí sobre informe de Anna Verónica Dorogush que contenía respuestas a muchas de mis preguntas, e incluso a aquellas que aún no había tenido en ese momento.

Ver el informe me llevó a la idea de que debemos devolver todos los parámetros al valor predeterminado y realizar la configuración con mucho cuidado y solo después de corregir un conjunto de funciones. Ahora bien, un entrenamiento duró unas 15 horas, pero un modelo logró obtener una velocidad mejor que la obtenida en el conjunto con ranking.

Generación de características

En el concurso de Sistemas Colaborativos, un gran número de características se consideran importantes para el modelo. Por ejemplo, auditoríaweights_spark_svd - el signo más importante, pero no hay información sobre lo que significa. Pensé que valdría la pena contar los distintos agregados en función de características importantes. Por ejemplo, auditweights_spark_svd promedio por usuario, por grupo, por objeto. Lo mismo se puede calcular utilizando datos en los que no se realiza entrenamiento y objetivo = 1, es decir, promedio auditoríaweights_spark_svd por usuario por objetos que le gustaron. Señales importantes además auditoríaweights_spark_svd, fueron varios. Éstos son algunos de ellos:

  • auditoríapesosCtrGénero
  • pesos de auditoríaCtrAlto
  • usuarioPropietarioContadorCrearMe gusta

Por ejemplo, el promedio auditoríapesosCtrGénero según userId resultó ser una característica importante, al igual que el valor promedio usuarioPropietarioContadorCrearMe gusta por ID de usuario + ID de propietario. Esto ya debería hacerte pensar que necesitas comprender el significado de los campos.

También fueron características importantes auditoríapesosMe gustaContar и auditoríapesosShowsCount. Dividiendo uno por otro se obtuvo una característica aún más importante.

Fugas de datos

El modelado de competencia y producción son tareas muy diferentes. Al preparar datos, es muy difícil tener en cuenta todos los detalles y no transmitir información no trivial sobre la variable objetivo en la prueba. Si estamos creando una solución de producción, intentaremos evitar el uso de fugas de datos al entrenar el modelo. Pero si queremos ganar la competencia, la filtración de datos es la mejor característica.

Habiendo estudiado los datos, puede ver que de acuerdo con los valores de objectId auditoríapesosMe gustaContar и auditoríapesosShowsCount cambio, lo que significa que la proporción de los valores máximos de estas características reflejará la conversión posterior mucho mejor que la proporción en el momento de la visualización.

La primera fuga que encontramos es pesos de auditoríaMe gustaCountMax/pesos de auditoríaShowsCountMax.
¿Pero qué pasa si miramos los datos más de cerca? Ordenemos por fecha del programa y obtengamos:

ID de objeto ID de usuario auditoríapesosShowsCount auditoríapesosMe gustaContar objetivo (le gusta)
1 1 12 3 Probablemente no
1 2 15 3 tal vez sí
1 3 16 4

Fue sorprendente cuando encontré el primer ejemplo de este tipo y resultó que mi predicción no se hizo realidad. Pero, teniendo en cuenta que los valores máximos de estas características dentro del objeto dieron un aumento, no fuimos perezosos y decidimos buscar auditoríapesosMuestraContarSiguiente и auditoríapesosMe gustaContarSiguiente, es decir, los valores en el siguiente momento. Al agregar una característica
(auditweightsShowsCountNext-auditweightsShowsCount)/(auditweightsLikesCount-auditweightsLikesCountNext) Dimos un salto brusco rápidamente.
Se podrían utilizar fugas similares encontrando los siguientes valores para usuarioPropietarioContadorCrearMe gusta dentro de userId+ownerId y, por ejemplo, auditoríapesosCtrGénero dentro de objectId+userGender. Encontramos 6 campos similares con fugas y extrajimos de ellos la mayor cantidad de información posible.

En ese momento, habíamos extraído la mayor cantidad de información posible de las funciones colaborativas, pero no volvimos a los concursos de imágenes y texto. Tenía una gran idea: ¿cuánto aportan en concursos relevantes las funciones basadas directamente en imágenes o textos?

No hubo filtraciones en los concursos de imágenes y texto, pero en ese momento ya había devuelto los parámetros predeterminados de catboost, limpié el código y agregué algunas características. El resultado fue:

Solución pronto
Máximo con imágenes 0.6411
Máximo sin imágenes 0.6297
Resultado del segundo lugar 0.6295

Solución pronto
Máximo con textos 0.666
Máximo sin textos 0.660
Resultado del segundo lugar 0.656

Solución pronto
Máximo en colaboración 0.745
Resultado del segundo lugar 0.723

Se hizo evidente que era poco probable que pudiéramos sacar mucho provecho de los textos y las imágenes, y después de probar un par de las ideas más interesantes, dejamos de trabajar con ellas.

La generación adicional de funciones en sistemas colaborativos no dio un aumento y comenzamos a clasificar. En la etapa en línea, la clasificación y el ranking me dieron un pequeño aumento, ya que resultó que no entrené lo suficiente en la clasificación. Ninguna de las funciones de error, incluida YetiRanlPairwise, produjo ni de lejos el resultado que produjo LogLoss (0,745 frente a 0,725). Todavía había esperanzas para QueryCrossEntropy, que no se pudo iniciar.

Etapa fuera de línea

En la etapa fuera de línea, la estructura de datos siguió siendo la misma, pero hubo cambios menores:

  • los identificadores ID de usuario, ID de objeto y ID de propietario se volvieron a aleatorizar;
  • se eliminaron varios carteles y se cambió el nombre de varios;
  • los datos han aumentado aproximadamente 1,5 veces.

Además de las dificultades enumeradas, hubo una gran ventaja: al equipo se le asignó un servidor grande con un RTX 2080TI. He disfrutado htop durante mucho tiempo.
Hackatón SNA 2019

Sólo había una idea: simplemente reproducir lo que ya existe. Después de pasar un par de horas configurando el entorno en el servidor, poco a poco comenzamos a verificar que los resultados eran reproducibles. El principal problema al que nos enfrentamos es el aumento del volumen de datos. Decidimos reducir un poco la carga y configurar el parámetro catboost ctr_complexity=1. Esto reduce un poco la velocidad, pero mi modelo empezó a funcionar, el resultado fue bueno: 0,733. Sergey, a diferencia de mí, no dividió los datos en 2 partes y entrenó con todos los datos, aunque esto dio los mejores resultados en la etapa en línea, en la etapa fuera de línea hubo muchas dificultades. Si tomáramos todas las funciones que generamos y tratáramos de incluirlas en catboost, entonces nada funcionaría en la etapa en línea. Sergey optimizó tipos, por ejemplo, convirtiendo tipos float64 a float32. En este artículo, Puede encontrar información sobre optimización de memoria en pandas. Como resultado, Sergey entrenó en la CPU usando todos los datos y obtuvo aproximadamente 0,735.

Estos resultados fueron suficientes para ganar, pero ocultamos nuestra verdadera velocidad y no podíamos estar seguros de que otros equipos no estuvieran haciendo lo mismo.

Lucha hasta el final

Ajuste de impulso de gato

Nuestra solución se reprodujo completamente, agregamos las características de datos de texto e imágenes, por lo que todo lo que quedaba era ajustar los parámetros de catboost. Sergey entrenó en la CPU con una pequeña cantidad de iteraciones y yo entrené en la que tenía ctr_complexity=1. Quedaba un día, y si simplemente agregaba iteraciones o aumentaba ctr_complexity, por la mañana podría obtener una velocidad aún mejor y caminar todo el día.

En la etapa fuera de línea, las velocidades podrían ocultarse muy fácilmente simplemente eligiendo no la mejor solución en el sitio. Esperábamos cambios drásticos en la clasificación en los últimos minutos antes de que se cerraran las presentaciones y decidimos no parar.

Del video de Anna aprendí que para mejorar la calidad del modelo, es mejor seleccionar los siguientes parámetros:

  • tasa de aprendizaje — El valor predeterminado se calcula en función del tamaño del conjunto de datos. Aumentar la tasa de aprendizaje requiere aumentar el número de iteraciones.
  • l2_leaf_reg — Coeficiente de regularización, valor predeterminado 3, preferiblemente elija entre 2 y 30. La disminución del valor conduce a un aumento del sobreajuste.
  • temperatura_de_embolsado — agrega aleatorización a los pesos de los objetos en la muestra. El valor predeterminado es 1, donde las ponderaciones se extraen de una distribución exponencial. La disminución del valor conduce a un aumento del sobreajuste.
  • fuerza_aleatoria — Afecta la elección de divisiones en una iteración específica. Cuanto mayor sea random_strength, mayor será la probabilidad de que se seleccione una división de baja importancia. En cada iteración posterior, la aleatoriedad disminuye. La disminución del valor conduce a un aumento del sobreajuste.

Otros parámetros tienen un efecto mucho menor en el resultado final, por lo que no intenté seleccionarlos. Una iteración de entrenamiento en mi conjunto de datos de GPU con ctr_complexity=1 tomó 20 minutos y los parámetros seleccionados en el conjunto de datos reducido fueron ligeramente diferentes de los óptimos en el conjunto de datos completo. Al final, hice unas 30 iteraciones con el 10% de los datos y luego unas 10 iteraciones más con todos los datos. Resultó algo como esto:

  • tasa de aprendizaje Aumenté en un 40% respecto del valor predeterminado;
  • l2_leaf_reg lo dejó igual;
  • temperatura_de_embolsado и fuerza_aleatoria reducido a 0,8.

Podemos concluir que el modelo estaba subentrenado con los parámetros predeterminados.

Me sorprendió mucho ver el resultado en la clasificación:

modelo Modelo 1 Modelo 2 Modelo 3 conjunto
sin afinar 0.7403 0.7404 0.7404 0.7407
Con afinación 0.7406 0.7405 0.7406 0.7408

Llegué a la conclusión de que si no se necesita una aplicación rápida del modelo, entonces es mejor reemplazar la selección de parámetros con un conjunto de varios modelos utilizando parámetros no optimizados.

Sergey estaba optimizando el tamaño del conjunto de datos para ejecutarlo en la GPU. La opción más sencilla es cortar parte de los datos, pero esto se puede hacer de varias formas:

  • elimine gradualmente los datos más antiguos (principios de febrero) hasta que el conjunto de datos comience a caber en la memoria;
  • eliminar características con la menor importancia;
  • eliminar los ID de usuario para los que sólo hay una entrada;
  • deje solo los ID de usuario que están en la prueba.

Y, en última instancia, crear un conjunto con todas las opciones.

el ultimo conjunto

A última hora de la tarde del último día, habíamos presentado un conjunto de nuestros modelos que arrojaba 0,742. Durante la noche lancé mi modelo con ctr_complexity=2 y en lugar de 30 minutos entrenó durante 5 horas. Recién a las 4 de la mañana se contó y formé el último conjunto, que dio 0,7433 en la clasificación pública.

Debido a diferentes enfoques para resolver el problema, nuestras predicciones no estaban fuertemente correlacionadas, lo que dio un buen aumento en el conjunto. Para obtener un buen conjunto, es mejor utilizar las predicciones del modelo sin procesar predict(prediction_type='RawFormulaVal') y establecer scale_pos_weight=neg_count/pos_count.

Hackatón SNA 2019

En el sitio web puedes ver resultados finales en la clasificación privada.

Otras soluciones

Muchos equipos siguieron los cánones de los algoritmos del sistema de recomendación. Yo, al no ser un experto en este campo, no puedo evaluarlos, pero recuerdo 2 soluciones interesantes.

  • La solución de Nikolay Anokhin. Nikolay, como empleado de Mail.ru, no solicitó premios, por lo que su objetivo no era alcanzar la máxima velocidad, sino obtener una solución fácilmente escalable.
  • Decisión del equipo ganador del Premio del Jurado basada en este artículo de facebook, permitió una muy buena agrupación de imágenes sin trabajo manual.

Conclusión

Lo que más se me quedó grabado en la memoria:

  • Si hay características categóricas en los datos y sabe cómo realizar la codificación de destino correctamente, es mejor probar catboost.
  • Si participa en una competencia, no debe perder el tiempo seleccionando parámetros distintos de la tasa de aprendizaje y las iteraciones. Una solución más rápida es hacer un conjunto de varios modelos.
  • Los impulsos se pueden aprender en la GPU. Catboost puede aprender muy rápidamente en la GPU, pero consume mucha memoria.
  • Durante el desarrollo y prueba de ideas, es mejor establecer un rsm~=0.2 pequeño (solo CPU) y ctr_complexity=1.
  • A diferencia de otros equipos, el conjunto de nuestros modelos dio un gran aumento. Sólo intercambiábamos ideas y escribíamos en diferentes idiomas. Teníamos un enfoque diferente para dividir los datos y creo que cada uno tenía sus propios errores.
  • No está claro por qué la optimización de la clasificación tuvo peores resultados que la optimización de la clasificación.
  • Adquirí algo de experiencia trabajando con textos y comprendí cómo se crean los sistemas de recomendación.

Hackatón SNA 2019

Gracias a los organizadores por las emociones, conocimientos y premios recibidos.

Fuente: habr.com

Añadir un comentario