SNA Hackathon 2019

De febrer a març de 2019, es va celebrar un concurs per classificar el canal de xarxes socials SNA Hackathon 2019, en què el nostre equip va ocupar el primer lloc. En l'article parlaré de l'organització de la competició, els mètodes que vam provar i la configuració del catboost per a l'entrenament en big data.

SNA Hackathon 2019

SNA Hackathon

Aquesta és la tercera vegada que es fa un hackathon amb aquest nom. Està organitzat per la xarxa social ok.ru, respectivament, la tasca i les dades estan directament relacionades amb aquesta xarxa social.
SNA (anàlisi de xarxes socials) en aquest cas s'entén més correctament no com una anàlisi d'un gràfic social, sinó més aviat com una anàlisi d'una xarxa social.

  • El 2014, la tasca era predir el nombre de m'agrada que obtindria una publicació.
  • El 2016 - la tasca VVZ (potser ja coneixeu), més propera a l'anàlisi del gràfic social.
  • El 2019, classificació del feed de l'usuari en funció de la probabilitat que a l'usuari li agradi la publicació.

No puc dir del 2014, però el 2016 i el 2019, a més de les habilitats d'anàlisi de dades, també es van requerir habilitats per treballar amb big data. Crec que va ser la combinació d'aprenentatge automàtic i problemes de processament de grans dades el que em va atreure a aquestes competicions, i la meva experiència en aquestes àrees em va ajudar a guanyar.

mlbootcamp

El 2019, el concurs es va organitzar a la plataforma https://mlbootcamp.ru.

El concurs va començar en línia el 7 de febrer i constava de 3 tasques. Qualsevol pot registrar-se al lloc, descarregar línia de base i carrega el cotxe durant unes hores. Al final de l'etapa en línia el 15 de març, els 15 millors de cada esdeveniment de salt d'obstacles van ser convidats a l'oficina de Mail.ru per a l'etapa fora de línia, que va tenir lloc del 30 de març a l'1 d'abril.

Tasca

Les dades d'origen proporcionen identificadors d'usuari (userId) i identificadors de publicació (objectId). Si a l'usuari se li va mostrar una publicació, aleshores les dades contenen una línia que conté userId, objectId, les reaccions dels usuaris a aquesta publicació (feedback) i un conjunt de diverses funcions o enllaços a imatges i textos.

ID d'usuari objectId ID propietari feedback imatges
3555 22 5677 [ha agradat, ha fet clic] [hash1]
12842 55 32144 [no va agradar] [hash2,hash3]
13145 35 5677 [clic, compartit] [hash2]

El conjunt de dades de prova conté una estructura similar, però falta el camp de comentaris. La tasca és predir la presència de la reacció "agradat" al camp de retroalimentació.
El fitxer de presentació té la següent estructura:

ID d'usuari SortedList[objectId]
123 78,13,54,22
128 35,61,55
131 35,68,129,11

La mètrica és l'AUC ROC mitjana per als usuaris.

Es pot trobar una descripció més detallada de les dades a web del consistori. També hi podeu descarregar dades, incloses proves i imatges.

Etapa en línia

A l'etapa en línia, la tasca es va dividir en 3 parts

Etapa fora de línia

En l'etapa fora de línia, les dades incloïen totes les funcions, mentre que els textos i les imatges eren escassos. Hi havia 1,5 vegades més files al conjunt de dades, de les quals ja n'hi havia moltes.

La solució del problema

Com que faig CV a la feina, vaig començar el meu camí en aquest concurs amb la tasca “Imatges”. Les dades que es van proporcionar eren userId, objectId, ownerId (el grup en què es va publicar la publicació), les marques de temps per crear i mostrar la publicació i, per descomptat, la imatge d'aquesta publicació.
Després de generar diverses funcions basades en segells de temps, la següent idea va ser agafar la penúltima capa de la neurona prèviament entrenada a imagenet i enviar aquestes incrustacions a l'impuls.

SNA Hackathon 2019

Els resultats no van ser impressionants. Les incrustacions de la neurona imagenet són irrellevants, vaig pensar, necessito fer el meu propi codificador automàtic.

SNA Hackathon 2019

Va trigar molt de temps i el resultat no va millorar.

Generació de funcions

Treballar amb imatges requereix molt de temps, així que vaig decidir fer una cosa més senzilla.
Com podeu veure immediatament, hi ha diverses característiques categòriques al conjunt de dades i, per no molestar-me massa, només vaig prendre catboost. La solució va ser excel·lent, sense cap configuració vaig arribar immediatament a la primera línia de la classificació.

Hi ha força dades i està distribuïda en format parquet, així que sense pensar-m'ho dues vegades, vaig agafar scala i vaig començar a escriure-ho tot en spark.

Les característiques més senzilles que van donar més creixement que les incrustacions d'imatges:

  • quantes vegades van aparèixer objectId, userId i ownerId a les dades (haurien de correlacionar amb la popularitat);
  • quantes publicacions userId ha vist des de ownerId (haurien de correlacionar amb l'interès de l'usuari en el grup);
  • quants identificadors d'usuari únics han vist publicacions des de l'identificador de propietari (reflecteix la mida del públic del grup).

A partir de les marques de temps es va poder obtenir l'hora del dia en què l'usuari mirava el canal (matí/tarda/nit/nit). En combinar aquestes categories, podeu continuar generant funcions:

  • quantes vegades userId ha iniciat sessió al vespre;
  • a quina hora es mostra més sovint aquesta publicació (objectId) i així successivament.

Tot això va anar millorant progressivament les mètriques. Però la mida del conjunt de dades d'entrenament és d'uns 20 milions de registres, de manera que l'addició de funcions va alentir molt la formació.

He replantejat la meva manera d'utilitzar les dades. Tot i que les dades depenen del temps, no vaig veure cap filtració d'informació evident "en el futur", tanmateix, per si de cas, la vaig desglossar així:

SNA Hackathon 2019

El conjunt de formació que ens van proporcionar (febrer i 2 setmanes de març) es va dividir en 2 parts.
El model es va entrenar amb dades dels darrers N dies. Les agregacions descrites anteriorment es van construir a partir de totes les dades, inclosa la prova. Al mateix temps, han aparegut dades sobre les quals és possible construir diverses codificacions de la variable objectiu. L'enfocament més senzill és reutilitzar el codi que ja està creant noves característiques i simplement alimentar-lo amb dades sobre les quals no s'entrenarà i s'orientarà a 1.

Així, tenim característiques similars:

  • Quantes vegades userId ha vist una publicació al grup ownerId;
  • Quantes vegades a userId li ha agradat la publicació al grup ownerId;
  • El percentatge de publicacions a les quals userId els ha agradat de ownerId.

És a dir, va resultar codificació objectiu mitjana a part del conjunt de dades per a diverses combinacions de característiques categòriques. En principi, catboost també crea codificació objectiu i des d'aquest punt de vista no hi ha cap benefici, però, per exemple, es va poder comptar el nombre d'usuaris únics que els agradaven les publicacions d'aquest grup. Al mateix temps, es va aconseguir l'objectiu principal: el meu conjunt de dades es va reduir diverses vegades i es va poder continuar generant funcions.

Tot i que catboost només pot crear una codificació en funció de la reacció que t'agrada, els comentaris tenen altres reaccions: compartit, no m'agrada, no m'agrada, s'ha fet clic, s'ignora, les codificacions es poden fer manualment. Vaig recalcular tot tipus d'agregats i vaig eliminar funcions amb poca importància per no inflar el conjunt de dades.

En aquell moment jo estava en primer lloc per un ampli marge. L'únic que era confús era que les incrustacions d'imatges gairebé no mostraven creixement. La idea va sorgir per donar-ho tot a catboost. Agrupem imatges Kmeans i obtenim una nova característica categòrica imageCat.

Aquí hi ha algunes classes després del filtratge manual i la fusió de clústers obtinguts de KMeans.

SNA Hackathon 2019

A partir d'imageCat generem:

  • Noves característiques categòriques:
    • Quina imageCat s'ha vist més sovint per userId;
    • Quina imageCat mostra amb més freqüència ownerId;
    • Quina imageCat va agradar més sovint a userId;
  • Diversos comptadors:
    • Quants únics imageCat van mirar userId;
    • Unes 15 funcions similars més la codificació de destinació tal com es descriu anteriorment.

Textos

Els resultats del concurs d'imatge em van bé i vaig decidir provar-me amb els textos. No he treballat gaire amb textos abans i, tontament, vaig matar el dia a tf-idf i svd. Llavors vaig veure la línia de base amb doc2vec, que fa exactament el que necessito. Després d'haver ajustat lleugerament els paràmetres doc2vec, vaig obtenir incrustacions de text.

I després simplement vaig reutilitzar el codi per a les imatges, en què vaig substituir les incrustacions d'imatges per incrustacions de text. Com a resultat, vaig aconseguir el 2n lloc al concurs de textos.

Sistema col·laboratiu

Quedava una competició que encara no havia "picat" amb un pal i, a jutjar per l'AUC a la taula de classificació, els resultats d'aquesta competició en particular haurien d'haver tingut el major impacte a l'etapa fora de línia.
Vaig agafar totes les característiques que hi havia a les dades d'origen, vaig seleccionar les categòriques i vaig calcular els mateixos agregats que per a les imatges, excepte les característiques basades en les imatges. Només posar això a catboost em va portar al 2n lloc.

Primers passos de l'optimització de catboost

Un primer i dos segons em van agradar, però vaig entendre que no havia fet res especial, la qual cosa significa que podia esperar una pèrdua de posició.

La tasca del concurs és classificar les publicacions dins de l'usuari, i durant tot aquest temps he estat resolent el problema de classificació, és a dir, optimitzant la mètrica equivocada.

Permeteu-me que us posi un exemple senzill:

ID d'usuari objectId predicció veritat terrestre
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

Fem una petita reordenació

ID d'usuari objectId predicció veritat terrestre
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

Obtenim els següents resultats:

Model AUC Usuari1 AUC Usuari2 AUC significa AUC
Opció 1 0,8 1,0 0,0 0,5
Opció 2 0,7 0,75 1,0 0,875

Com podeu veure, millorar la mètrica AUC global no vol dir millorar la mètrica AUC mitjana d'un usuari.

Catboost sap com optimitzar les mètriques de classificació de la caixa. He llegit sobre mètriques de classificació, històries d'èxit quan utilitzeu catboost i configureu YetiRankPairwise per entrenar durant la nit. El resultat no va ser impressionant. Decidint que no estava entrenat, vaig canviar la funció d'error a QueryRMSE, que, a jutjar per la documentació de catboost, convergeix més ràpidament. Al final, vaig obtenir els mateixos resultats que a l'entrenament per a la classificació, però els conjunts d'aquests dos models van donar un bon augment, fet que em va portar al primer lloc de les tres competicions.

5 minuts abans del tancament de l'etapa en línia del concurs "Sistemes col·laboratius", Sergey Shalnov em va traslladar al segon lloc. Vam caminar junts pel camí posterior.

Preparant-se per a l'etapa fora de línia

Ens vam garantir la victòria a l'etapa en línia amb una targeta de vídeo RTX 2080 TI, però el premi principal de 300 rubles i, molt probablement, fins i tot el primer lloc final ens van obligar a treballar durant aquestes 000 setmanes.

Com va resultar, Sergey també va utilitzar catboost. Hem intercanviat idees i característiques, i n'he après reportatge d'Anna Veronica Dorogush que contenia respostes a moltes de les meves preguntes, i fins i tot a les que encara no havia tingut en aquell moment.

Veure l'informe em va portar a la idea que hem de tornar tots els paràmetres al valor predeterminat i fer la configuració amb molta cura i només després d'arreglar un conjunt de funcions. Ara un entrenament va durar unes 15 hores, però un model va aconseguir una velocitat millor que l'aconseguida en el conjunt amb rànquing.

Generació de funcions

A la competició de sistemes col·laboratius, un gran nombre de funcions es valoren com a importants per al model. Per exemple, auditweights_spark_svd - el signe més important, però no hi ha informació sobre el que significa. Vaig pensar que valdria la pena comptar els diferents agregats en funció de les característiques importants. Per exemple, auditweights_spark_svd mitjana per usuari, per grup, per objecte. El mateix es pot calcular utilitzant dades sobre les quals no es realitza cap entrenament i objectiu = 1, és a dir, mitjana auditweights_spark_svd per usuari per objectes que li agradaven. A més, senyals importants auditweights_spark_svd, n'hi havia diversos. Aquests són alguns d'ells:

  • auditweightsCtrGender
  • auditorweightsCtrHigh
  • userOwnerCounterCreateLikes

Per exemple, la mitjana auditweightsCtrGender segons userId, va resultar ser una característica important, igual que el valor mitjà userOwnerCounterCreateLikes per userId+ownerId. Això ja hauria de fer pensar que cal entendre el significat dels camps.

També eren característiques importants auditorweightsLikesCount и auditweightsShowsCount. Dividint l'un per l'altre, es va obtenir una característica encara més important.

Fugades de dades

La competència i el modelatge de producció són tasques molt diferents. En preparar les dades, és molt difícil tenir en compte tots els detalls i no transmetre informació no trivial sobre la variable objectiu de la prova. Si estem creant una solució de producció, intentarem evitar l'ús de fuites de dades en entrenar el model. Però si volem guanyar la competició, les filtracions de dades són les millors característiques.

Després d'estudiar les dades, podeu veure-ho segons els valors objectId auditorweightsLikesCount и auditweightsShowsCount canvi, el que significa que la proporció dels valors màxims d'aquestes característiques reflectirà la conversió posterior molt millor que la relació en el moment de la visualització.

La primera filtració que vam trobar és auditweightsLikesCountMax/auditweightsShowsCountMax.
Però, què passa si mirem les dades més de prop? Ordenarem per data de l'espectacle i obtenim:

objectId ID d'usuari auditweightsShowsCount auditorweightsLikesCount objectiu (li agrada)
1 1 12 3 probablement no
1 2 15 3 potser sí
1 3 16 4

Va ser sorprenent quan vaig trobar el primer exemple i va resultar que la meva predicció no es va fer realitat. Però, tenint en compte el fet que els valors màxims d'aquestes característiques dins de l'objecte van augmentar, no vam ser mandrós i vam decidir trobar auditweightsShowsCountNext и auditweightsLikesCountNext, és a dir, els valors en el moment següent. Afegint una característica
(auditweightsShowsCountNext-auditweightsShowsCount)/(auditweightsLikesCount-auditweightsLikesCountNext) vam fer un salt fort ràpidament.
Es podrien utilitzar fuites similars trobant els valors següents per userOwnerCounterCreateLikes dins userId+ownerId i, per exemple, auditweightsCtrGender dins objectId+userGender. Hem trobat 6 camps similars amb filtracions i n'hem extret la màxima informació possible.

En aquell moment, havíem extret la màxima informació possible de les funcions de col·laboració, però no vam tornar als concursos d'imatge i text. Vaig tenir una gran idea per comprovar: quant aporten les funcions basades directament en imatges o textos en competicions rellevants?

No hi havia filtracions a les competicions d'imatge i text, però en aquell moment havia retornat els paràmetres de catboost predeterminats, netejat el codi i afegit algunes funcions. El total va ser:

decisió aviat
Màxim amb imatges 0.6411
Màxim sense imatges 0.6297
Resultat del segon lloc 0.6295

decisió aviat
Màxim amb textos 0.666
Màxim sense textos 0.660
Resultat del segon lloc 0.656

decisió aviat
Màxim en col·laboració 0.745
Resultat del segon lloc 0.723

Es va fer evident que és poc probable que poguéssim extreure molt de textos i imatges, i després de provar un parell de les idees més interessants, vam deixar de treballar-hi.

La generació posterior de funcions en sistemes col·laboratius no va augmentar i vam començar a classificar-nos. A l'etapa en línia, el conjunt de classificació i rànquing em va donar un petit augment, ja que va resultar perquè vaig entrenar poc a la classificació. Cap de les funcions d'error, inclòs YetiRanlPairwise, no va produir el resultat que LogLoss va fer (0,745 vs. 0,725). Encara hi havia esperança per a QueryCrossEntropy, que no es va poder llançar.

Etapa fora de línia

A l'etapa fora de línia, l'estructura de dades es va mantenir igual, però hi va haver canvis menors:

  • els identificadors userId, objectId, ownerId es van tornar a aleatoriar;
  • es van eliminar diversos rètols i es van canviar el nom de diversos;
  • les dades han augmentat aproximadament 1,5 vegades.

A més de les dificultats enumerades, hi havia un gran avantatge: a l'equip se li va assignar un gran servidor amb un RTX 2080TI. He gaudit d'htop durant molt de temps.
SNA Hackathon 2019

Només hi havia una idea: simplement reproduir allò que ja existeix. Després d'haver passat un parell d'hores configurant l'entorn al servidor, a poc a poc vam començar a comprovar que els resultats eren reproduïbles. El principal problema al qual ens enfrontem és l'augment del volum de dades. Vam decidir reduir una mica la càrrega i establir el paràmetre catboost ctr_complexity=1. Això redueix una mica la velocitat, però el meu model va començar a funcionar, el resultat va ser bo: 0,733. Sergey, a diferència de mi, no va dividir les dades en 2 parts i es va entrenar amb totes les dades, tot i que això va donar els millors resultats a l'etapa en línia, a l'etapa fora de línia hi va haver moltes dificultats. Si agafem totes les funcions que hem generat i intentéssim introduir-les a catboost, res funcionaria a l'etapa en línia. Sergey va fer una optimització de tipus, per exemple, convertint els tipus float64 a float32. En aquest article, Podeu trobar informació sobre l'optimització de la memòria a pandas. Com a resultat, Sergey es va entrenar a la CPU utilitzant totes les dades i va obtenir uns 0,735.

Aquests resultats van ser suficients per guanyar, però vam amagar la nostra veritable velocitat i no podíem estar segurs que altres equips no fessin el mateix.

Lluitar fins a l'últim

Sintonització Catboost

La nostra solució es va reproduir íntegrament, vam afegir les característiques de dades de text i imatges, de manera que només quedava ajustar els paràmetres de catboost. En Sergey es va entrenar a la CPU amb un petit nombre d'iteracions, i jo a la que tenia ctr_complexity=1. Quedava un dia, i si només afegiu iteracions o augmenteu ctr_complexity, al matí podríeu aconseguir una velocitat encara millor i caminar tot el dia.

En l'etapa fora de línia, les velocitats es podrien amagar molt fàcilment si escolliu no la millor solució al lloc. Esperàvem canvis dràstics a la classificació en els últims minuts abans que es tanquessin les presentacions i vam decidir no aturar-nos.

Del vídeo de l'Anna, vaig aprendre que per millorar la qualitat del model, el millor és seleccionar els paràmetres següents:

  • ritme_d'aprenentatge — El valor predeterminat es calcula en funció de la mida del conjunt de dades. Augmentar learning_rate requereix augmentar el nombre d'iteracions.
  • l2_leaf_reg — Coeficient de regularització, valor per defecte 3, escolliu preferentment entre 2 i 30. Disminuir el valor comporta un augment del sobreajust.
  • temperatura_bossa — afegeix l'aleatorització als pesos dels objectes de la mostra. El valor per defecte és 1, on els pesos s'extreuen d'una distribució exponencial. Disminuir el valor comporta un augment del sobreajust.
  • força_atzar — Afecta l'elecció de les divisions en una iteració específica. Com més gran sigui random_strength, més probabilitats de seleccionar-se una divisió de poca importància. A cada iteració posterior, l'aleatorietat disminueix. Disminuir el valor comporta un augment del sobreajust.

Altres paràmetres tenen un efecte molt menor en el resultat final, així que no vaig intentar seleccionar-los. Una iteració d'entrenament al meu conjunt de dades GPU amb ctr_complexity=1 va trigar 20 minuts i els paràmetres seleccionats al conjunt de dades reduït eren lleugerament diferents dels òptims del conjunt de dades complet. Al final, vaig fer unes 30 iteracions en el 10% de les dades, i després unes 10 iteracions més en totes les dades. Va resultar una cosa així:

  • ritme_d'aprenentatge Vaig augmentar un 40% respecte al valor predeterminat;
  • l2_leaf_reg ho va deixar igual;
  • temperatura_bossa и força_atzar reduït a 0,8.

Podem concloure que el model estava poc entrenat amb els paràmetres per defecte.

Em va sorprendre molt quan vaig veure el resultat a la classificació:

Model model 1 model 2 model 3 conjunt
Sense afinar 0.7403 0.7404 0.7404 0.7407
Amb afinació 0.7406 0.7405 0.7406 0.7408

Vaig concloure per mi mateix que si no es necessita una aplicació ràpida del model, és millor substituir la selecció de paràmetres per un conjunt de diversos models amb paràmetres no optimitzats.

Sergey estava optimitzant la mida del conjunt de dades per executar-lo a la GPU. L'opció més senzilla és tallar part de les dades, però això es pot fer de diverses maneres:

  • elimina gradualment les dades més antigues (a principis de febrer) fins que el conjunt de dades comenci a cabre a la memòria;
  • eliminar les funcions amb la menor importància;
  • eliminar userIds per als quals només hi ha una entrada;
  • deixeu només els ID d'usuari que es troben a la prova.

I, en definitiva, fer un conjunt de totes les opcions.

L'últim conjunt

A la tarda de l'últim dia, havíem presentat un conjunt dels nostres models que donaven 0,742. Durant la nit vaig llançar el meu model amb ctr_complexity=2 i en comptes de 30 minuts va entrenar durant 5 hores. Només a les 4 del matí es va comptar, i vaig fer l'últim conjunt, que va donar 0,7433 a la classificació pública.

A causa de diferents enfocaments per resoldre el problema, les nostres prediccions no estaven fortament correlacionades, cosa que va donar un bon augment del conjunt. Per obtenir un bon conjunt, és millor utilitzar les prediccions del model brut predict(prediction_type='RawFormulaVal') i establir scale_pos_weight=neg_count/pos_count.

SNA Hackathon 2019

A la web es pot veure resultats finals a la classificació privada.

Altres solucions

Molts equips van seguir els cànons dels algorismes del sistema de recomanació. Jo, al no ser un expert en aquest camp, no puc avaluar-los, però recordo 2 solucions interessants.

  • La solució de Nikolay Anokhin. Nikolay, com a empleat de Mail.ru, no va sol·licitar premis, de manera que el seu objectiu no era aconseguir la màxima velocitat, sinó obtenir una solució fàcilment escalable.
  • La decisió de l'equip guanyador del premi del jurat es basa en aquest article de facebook, permetia una molt bona agrupació d'imatges sense treball manual.

Conclusió

El que més em va quedar en la memòria:

  • Si hi ha característiques categòriques a les dades i sabeu com fer la codificació de destinació correctament, encara és millor provar catboost.
  • Si participeu en una competició, no heu de perdre el temps seleccionant paràmetres que no siguin learning_rate i iteracions. Una solució més ràpida és fer un conjunt de diversos models.
  • Els impulsos poden aprendre a la GPU. Catboost pot aprendre molt ràpidament a la GPU, però consumeix molta memòria.
  • Durant el desenvolupament i la prova d'idees, és millor establir un petit rsm~=0.2 (només CPU) i ctr_complexity=1.
  • A diferència d'altres equips, el conjunt dels nostres models va donar un gran augment. Només hem intercanviat idees i hem escrit en diferents idiomes. Teníem un enfocament diferent per dividir les dades i, crec, cadascun tenia els seus propis errors.
  • No està clar per què l'optimització de la classificació va funcionar pitjor que l'optimització de la classificació.
  • Vaig guanyar una mica d'experiència treballant amb textos i vaig entendre com es fan els sistemes de recomanació.

SNA Hackathon 2019

Gràcies als organitzadors per les emocions, coneixements i premis rebuts.

Font: www.habr.com

Afegeix comentari