SNA Hackathon 2019

I februari-mars 2019 hölls en tävling för att rangordna det sociala nätverksflödet SNA Hackathon 2019, där vårt lag tog förstaplatsen. I artikeln kommer jag att prata om organisationen av tävlingen, metoderna vi provade och catboost-inställningarna för träning på big data.

SNA Hackathon 2019

SNA Hackathon

Det är tredje gången som ett hackathon under detta namn genomförs. Det organiseras av det sociala nätverket ok.ru, respektive uppgift och data är direkt relaterade till detta sociala nätverk.
SNA (social nätverksanalys) i detta fall förstås mer korrekt inte som en analys av en social graf, utan snarare som en analys av ett socialt nätverk.

  • Under 2014 var uppgiften att förutse hur många likes ett inlägg skulle få.
  • Under 2016 - VVZ-uppgiften (kanske du är bekant), närmare analysen av den sociala grafen.
  • Under 2019 rangordnas användarens flöde baserat på sannolikheten att användaren kommer att gilla inlägget.

Jag kan inte säga något om 2014, men under 2016 och 2019 krävdes, förutom förmågan att analysera data, även färdigheter i att arbeta med big data. Jag tror att det var kombinationen av maskininlärning och problem med big databehandling som lockade mig till dessa tävlingar, och min erfarenhet inom dessa områden hjälpte mig att vinna.

mlbootcamp

Under 2019 anordnades tävlingen på plattformen https://mlbootcamp.ru.

Tävlingen började online den 7 februari och bestod av 3 uppgifter. Vem som helst kunde registrera sig på sidan, ladda ner baslinje och lasta din bil i några timmar. I slutet av online-scenen den 15 mars bjöds de 15 bästa av varje hoppning in till Mail.ru-kontoret för offline-scenen, som ägde rum från 30 mars till 1 april.

Uppgift

Källdata tillhandahåller användar-ID:n (userId) och post-ID:n (objectId). Om användaren visades ett inlägg så innehåller informationen en rad som innehåller userId, objectId, användarens reaktioner på detta inlägg (feedback) och en uppsättning olika funktioner eller länkar till bilder och texter.

användar ID objectId ägare-ID återkoppling bilder
3555 22 5677 [gillade, klickade] [hash1]
12842 55 32144 [gillade inte] [hash2, hash3]
13145 35 5677 [klickade, delade] [hash2]

Testdatauppsättningen innehåller en liknande struktur, men feedbackfältet saknas. Uppgiften är att förutsäga närvaron av den "gillade"-reaktionen i feedbackfältet.
Inlämningsfilen har följande struktur:

användar ID SortedList[objectId]
123 78,13,54,22
128 35,61,55
131 35,68,129,11

Mätvärdet är den genomsnittliga ROC AUC för användare.

En mer detaljerad beskrivning av uppgifterna finns på fullmäktiges hemsida. Där kan du också ladda ner data, inklusive tester och bilder.

Online scen

På onlinestadiet var uppgiften uppdelad i 3 delar

Offline scen

I offline-stadiet inkluderade uppgifterna alla funktioner, medan texter och bilder var sparsamma. Det fanns 1,5 gånger fler rader i datasetet, av vilka det redan fanns många.

Lösningen på problemet

Eftersom jag gör CV på jobbet började jag min resa i den här tävlingen med uppgiften "Bilder". Datan som lämnades var userId, objectId, ownerId (gruppen i vilken inlägget publicerades), tidsstämplar för att skapa och visa inlägget och, naturligtvis, bilden för detta inlägg.
Efter att ha genererat flera funktioner baserade på tidsstämplar, var nästa idé att ta det näst sista lagret av neuronen som är förtränat på imagenet och skicka dessa inbäddningar till boosting.

SNA Hackathon 2019

Resultaten var inte imponerande. Inbäddningar från bildnätsneuronen är irrelevanta, tänkte jag, jag måste göra min egen autokodare.

SNA Hackathon 2019

Det tog mycket tid och resultatet blev inte bättre.

Funktionsgenerering

Att arbeta med bilder tar mycket tid, så jag bestämde mig för att göra något enklare.
Som du direkt kan se finns det flera kategoriska funktioner i datasetet, och för att inte bry mig för mycket tog jag bara catboost. Lösningen var utmärkt, utan några inställningar kom jag direkt till första raden på topplistan.

Det finns ganska mycket data och den är upplagd i parkettformat, så utan att tänka två gånger tog jag scala och började skriva allt i gnista.

De enklaste funktionerna som gav mer tillväxt än bildinbäddningar:

  • hur många gånger objectId, userId och ownerId förekom i data (bör korrelera med popularitet);
  • hur många inlägg som userId har sett från ownerId (bör korrelera med användarens intresse för gruppen);
  • hur många unika användar-ID som visade inlägg från ownerId (speglar storleken på gruppens målgrupp).

Från tidsstämplar var det möjligt att få fram den tid på dygnet då användaren tittade på flödet (morgon/eftermiddag/kväll/natt). Genom att kombinera dessa kategorier kan du fortsätta att skapa funktioner:

  • hur många gånger userId loggat in på kvällen;
  • vid vilken tidpunkt visas detta inlägg oftast (objectId) och så vidare.

Allt detta förbättrade gradvis mätvärdena. Men storleken på träningsdatauppsättningen är cirka 20 miljoner rekord, så att lägga till funktioner saktade ner träningen avsevärt.

Jag har funderat om mitt sätt att använda data. Även om data är tidsberoende, såg jag inga uppenbara informationsläckor "i framtiden", men jag bröt upp det så här, för säkerhets skull:

SNA Hackathon 2019

Utbildningssetet som vi fick (februari och 2 veckor i mars) var uppdelat i 2 delar.
Modellen tränades på data från de senaste N dagarna. Aggregeringarna som beskrivs ovan byggdes på all data, inklusive testet. Samtidigt har det dykt upp data som det går att bygga olika kodningar av målvariabeln på. Det enklaste tillvägagångssättet är att återanvända kod som redan skapar nya funktioner, och helt enkelt mata den med data som den inte kommer att tränas på och mål = 1.

Således har vi liknande funktioner:

  • Hur många gånger har userId sett ett inlägg i gruppen ownerId;
  • Hur många gånger userId gillade inlägget i group ownerId;
  • Procentandelen av inlägg som userId gillade från ownerId.

Det vill säga, visade det sig genomsnittlig målkodning på en del av datamängden för olika kombinationer av kategoriska egenskaper. Catboost bygger i princip även målkodning och ur denna synvinkel är det ingen fördel, men det blev till exempel möjligt att räkna antalet unika användare som gillade inlägg i denna grupp. Samtidigt uppnåddes huvudmålet - min datauppsättning reducerades flera gånger, och det var möjligt att fortsätta generera funktioner.

Medan catboost kan bygga kodning endast baserat på den gillade reaktionen, har feedback andra reaktioner: delad, ogillad, ogillad, klickad, ignorerad, kodningar som kan göras manuellt. Jag räknade om alla typer av aggregat och eliminerade funktioner med låg betydelse för att inte blåsa upp datasetet.

Vid det laget var jag på första plats med bred marginal. Det enda som var förvirrande var att bildinbäddningar nästan inte visade någon tillväxt. Idén kom att ge allt till catboost. Vi grupperar Kmeans-bilder och får en ny kategorisk funktion imageCat.

Här är några klasser efter manuell filtrering och sammanslagning av kluster erhållna från KMeans.

SNA Hackathon 2019

Baserat på imageCat genererar vi:

  • Nya kategoriska funktioner:
    • Vilken imageCat sågs oftast av userId;
    • Vilken imageCat visar oftast ownerId;
    • Vilken imageCat gillades oftast av användar-ID;
  • Olika diskar:
    • Hur många unika imageCat tittade på användar-ID;
    • Cirka 15 liknande funktioner plus målkodning enligt beskrivningen ovan.

texter

Resultaten i bildtävlingen passade mig och jag bestämde mig för att prova mig fram med texter. Jag har inte jobbat så mycket med texter tidigare och dumt nog slog jag ihjäl dagen på tf-idf och svd. Sedan såg jag baseline med doc2vec, som gör precis vad jag behöver. Efter att ha justerat doc2vec-parametrarna något fick jag textinbäddningar.

Och sedan återanvände jag helt enkelt koden för bilderna, där jag ersatte bildinbäddningarna med textinbäddningar. Det gjorde att jag tog en 2:a plats i texttävlingen.

Samverkanssystem

Det fanns en tävling kvar som jag ännu inte "petat" med en pinne, och att döma av AUC på resultattavlan borde resultaten från just denna tävling ha haft störst inverkan på offlinestadiet.
Jag tog alla särdrag som fanns i källdata, valde ut kategoriska och beräknade samma aggregat som för bilder, förutom särdrag baserade på själva bilderna. Bara att sätta detta i catboost fick mig till 2:a plats.

De första stegen av catboost-optimering

En första- och två andraplatser gladde mig, men det fanns en förståelse för att jag inte hade gjort något speciellt, vilket gör att jag kunde förvänta mig en förlust av positioner.

Målet med tävlingen är att ranka inlägg inom användaren, och hela den här tiden löste jag klassificeringsproblemet, det vill säga att optimera fel mått.

Jag ska ge ett enkelt exempel:

användar ID objectId förutsägelse marken sanning
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

Låt oss göra en liten omarrangering

användar ID objectId förutsägelse marken sanning
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

Vi får följande resultat:

Modell AUC Användare1 AUC Användare2 AUC medel AUC
Alternativ 1 0,8 1,0 0,0 0,5
Alternativ 2 0,7 0,75 1,0 0,875

Som du kan se betyder att förbättra det övergripande AUC-måttet inte att förbättra det genomsnittliga AUC-måttet inom en användare.

Catboost vet hur man optimerar rankningsmått från lådan. Jag läste om rankningsmått, framgångshistorier när du använder catboost och ställ in YetiRankPairwise att träna över natten. Resultatet var inte imponerande. När jag bestämde mig för att jag var undertränad ändrade jag felfunktionen till QueryRMSE, som, att döma av catboost-dokumentationen, konvergerar snabbare. I slutändan fick jag samma resultat som när jag tränade för klassificering, men ensemblerna av dessa två modeller gav en bra ökning, vilket förde mig till första plats i alla tre tävlingarna.

5 minuter före stängningen av onlinesteget i tävlingen "Collaborative Systems" flyttade Sergey Shalnov mig till andra plats. Vi gick den vidare vägen tillsammans.

Förbereder för offlinestadiet

Vi var garanterade seger i online-stadiet med ett RTX 2080 TI-grafikkort, men huvudpriset på 300 000 rubel och, med största sannolikhet, även den sista förstaplatsen tvingade oss att arbeta under dessa 2 veckor.

Som det visade sig använde Sergey också catboost. Vi utbytte idéer och funktioner, och jag lärde mig om rapport av Anna Veronica Dorogush som innehöll svar på många av mina frågor, och även de som jag ännu inte hade fått vid den tiden.

Att titta på rapporten ledde mig till tanken att vi måste återställa alla parametrar till standardvärdet och göra inställningarna mycket noggrant och först efter att ha fixat en uppsättning funktioner. Nu tog en träning cirka 15 timmar, men en modell lyckades få en bättre hastighet än den som erhölls i ensemblen med ranking.

Funktionsgenerering

I tävlingen Collaborative Systems bedöms ett stort antal funktioner som viktiga för modellen. Till exempel, auditweights_spark_svd - det viktigaste tecknet, men det finns ingen information om vad det betyder. Jag tänkte att det skulle vara värt att räkna de olika aggregaten utifrån viktiga egenskaper. Till exempel, genomsnittlig auditweights_spark_svd per användare, efter grupp, per objekt. Detsamma kan beräknas med hjälp av data som ingen träning utförs på och mål = 1, det vill säga genomsnitt auditweights_spark_svd av användare efter objekt han gillade. Viktiga tecken dessutom auditweights_spark_svd, det var flera. Här är några av dem:

  • auditweightsCtrGender
  • auditweightsCtrHigh
  • userOwnerCounterCreateLikes

Till exempel genomsnittet auditweightsCtrGender enligt userId visade det sig vara en viktig funktion, precis som medelvärdet userOwnerCounterCreateLikes av userId+ownerId. Detta borde redan få dig att tänka att du måste förstå innebörden av fälten.

Viktiga funktioner var också auditweightsLikesCount и auditweightsShowsCount. Genom att dela den ena efter den andra erhölls en ännu viktigare egenskap.

Dataläckor

Konkurrens och produktionsmodellering är väldigt olika uppgifter. När man förbereder data är det mycket svårt att ta hänsyn till alla detaljer och inte förmedla någon icke-trivial information om målvariabeln i testet. Om vi ​​skapar en produktionslösning kommer vi att försöka undvika att använda dataläckor när vi tränar modellen. Men om vi vill vinna tävlingen så är dataläckor de bästa funktionerna.

Efter att ha studerat data kan du se det enligt objectId-värdena auditweightsLikesCount и auditweightsShowsCount förändring, vilket innebär att förhållandet mellan de maximala värdena för dessa funktioner kommer att återspegla efterkonverteringen mycket bättre än förhållandet vid visningstillfället.

Den första läckan vi hittade är auditweightsLikesCountMax/auditweightsShowsCountMax.
Men vad händer om vi tittar på uppgifterna närmare? Låt oss sortera efter visningsdatum och få:

objectId användar ID auditweightsShowsCount auditweightsLikesCount mål (gillas)
1 1 12 3 antagligen inte
1 2 15 3 kanske ja
1 3 16 4

Det var förvånande när jag hittade det första sådana exemplet och det visade sig att min förutsägelse inte gick i uppfyllelse. Men med hänsyn till det faktum att de maximala värdena för dessa egenskaper inom objektet gav en ökning, var vi inte lata och bestämde oss för att hitta auditweightsShowsCountNext и revisionsvikterGillarRäknaNästa, det vill säga värdena i nästa ögonblick. Genom att lägga till en funktion
(auditweightsShowsCountNext-auditweightsShowsCount)/(auditweightsLikesCount-auditweightsLikesCountNext) vi gjorde ett kraftigt hopp snabbt.
Liknande läckor kan användas genom att hitta följande värden för userOwnerCounterCreateLikes inom userId+ownerId och t.ex. auditweightsCtrGender inom objectId+userGender. Vi hittade 6 liknande fält med läckor och extraherade så mycket information som möjligt från dem.

Vid det laget hade vi klämt ut så mycket information som möjligt från samarbetsfunktioner, men återgick inte till bild- och texttävlingar. Jag hade en bra idé att kolla: hur mycket ger funktioner direkt baserade på bilder eller texter i relevanta tävlingar?

Det fanns inga läckor i bild- och texttävlingarna, men vid det laget hade jag returnerat standardparametrarna för catboost, rensat upp koden och lagt till några funktioner. Summan var:

beslutet snart
Max med bilder 0.6411
Max inga bilder 0.6297
Resultat på andra plats 0.6295

beslutet snart
Max med texter 0.666
Max utan texter 0.660
Resultat på andra plats 0.656

beslutet snart
Maximalt i samarbete 0.745
Resultat på andra plats 0.723

Det blev uppenbart att det var osannolikt att vi skulle kunna pressa mycket ur texter och bilder, och efter att ha provat ett par av de mest intressanta idéerna slutade vi arbeta med dem.

Ytterligare generering av funktioner i kollaborativa system gav ingen ökning, och vi började rangordna. På onlinestadiet gav klassificerings- och rankningsensemblen mig en liten ökning, vilket visade sig eftersom jag undertränade klassificeringen. Ingen av felfunktionerna, inklusive YetiRanlPairwise, gav någonstans i närheten av det resultat som LogLoss gjorde (0,745 vs. 0,725). Det fanns fortfarande hopp för QueryCrossEntropy, som inte kunde lanseras.

Offline scen

På offlinestadiet förblev datastrukturen densamma, men det fanns mindre ändringar:

  • identifierare userId, objectId, ownerId randomiserades om;
  • flera skyltar togs bort och flera döptes om;
  • uppgifterna har ökat cirka 1,5 gånger.

Utöver de angivna svårigheterna fanns det ett stort plus: teamet tilldelades en stor server med en RTX 2080TI. Jag har njutit av htop länge.
SNA Hackathon 2019

Det fanns bara en idé - att helt enkelt reproducera det som redan finns. Efter att ha tillbringat ett par timmar med att sätta upp miljön på servern började vi gradvis verifiera att resultaten var reproducerbara. Det största problemet vi står inför är ökningen av datavolymen. Vi bestämde oss för att minska belastningen lite och ställa in catboost-parametern ctr_complexity=1. Detta sänker hastigheten lite, men min modell började fungera, resultatet blev bra - 0,733. Sergey, till skillnad från mig, delade inte upp data i 2 delar och tränade på all data, även om detta gav de bästa resultaten på online-stadiet, på offline-stadiet fanns det många svårigheter. Om vi ​​tog alla funktioner som vi genererade och försökte skjuta in dem i catboost, så skulle ingenting fungera på online-stadiet. Sergey gjorde typoptimering, till exempel, konverterade float64-typer till float32. I den här artikeln, Du kan hitta information om minnesoptimering i pandor. Som ett resultat tränade Sergey på CPU:n med all data och fick cirka 0,735.

Dessa resultat räckte för att vinna, men vi dolde vår sanna fart och kunde inte vara säkra på att andra lag inte gjorde samma sak.

Kämpa in i det sista

Catboost tuning

Vår lösning reproducerades helt, vi lade till funktionerna för textdata och bilder, så allt som återstod var att justera catboost-parametrarna. Sergey tränade på CPU med ett litet antal iterationer, och jag tränade på den med ctr_complexity=1. Det var en dag kvar, och om du bara lade till iterationer eller ökade ctr_complexity, så kunde du på morgonen få en ännu bättre hastighet och gå hela dagen.

I offline-stadiet kan hastigheter mycket enkelt döljas genom att helt enkelt välja inte den bästa lösningen på webbplatsen. Vi förväntade oss drastiska förändringar i resultattavlan de sista minuterna innan bidragen stängdes och beslutade att inte sluta.

Från Annas video lärde jag mig att för att förbättra kvaliteten på modellen är det bäst att välja följande parametrar:

  • learning_rate — Standardvärdet beräknas baserat på datauppsättningens storlek. Att öka learning_rate kräver att antalet iterationer ökar.
  • l2_leaf_reg — Regulariseringskoefficient, standardvärde 3, välj helst från 2 till 30. Minskning av värdet leder till en ökning av överpassningen.
  • bagging_temperatur — lägger till randomisering till vikten av objekt i urvalet. Standardvärdet är 1, där vikterna dras från en exponentiell fördelning. Att sänka värdet leder till en ökning av överfittning.
  • random_styrka — Påverkar valet av delningar vid en specifik iteration. Ju högre slumpstyrka, desto större är chansen att en lågviktsdelning väljs. Vid varje efterföljande iteration minskar slumpen. Att sänka värdet leder till en ökning av överfittning.

Andra parametrar har en mycket mindre effekt på slutresultatet, så jag försökte inte välja dem. En iteration av träning på min GPU-datauppsättning med ctr_complexity=1 tog 20 minuter, och de valda parametrarna på den reducerade datauppsättningen skilde sig något från de optimala på hela datauppsättningen. Till slut gjorde jag cirka 30 iterationer på 10 % av datan och sedan cirka 10 iterationer till på all data. Det blev ungefär så här:

  • learning_rate Jag ökade med 40% från standard;
  • l2_leaf_reg lämnade det samma;
  • bagging_temperatur и random_styrka reduceras till 0,8.

Vi kan dra slutsatsen att modellen var undertränad med standardparametrarna.

Jag blev mycket förvånad när jag såg resultatet på resultattavlan:

Modell modell 1 modell 2 modell 3 ensemble
Utan stämning 0.7403 0.7404 0.7404 0.7407
Med tuning 0.7406 0.7405 0.7406 0.7408

Jag drog slutsatsen för mig själv att om snabb tillämpning av modellen inte behövs, är det bättre att ersätta valet av parametrar med en ensemble av flera modeller som använder icke-optimerade parametrar.

Sergey optimerade storleken på datamängden för att köra den på GPU:n. Det enklaste alternativet är att skära av en del av data, men detta kan göras på flera sätt:

  • gradvis ta bort de äldsta data (början av februari) tills datauppsättningen börjar passa in i minnet;
  • ta bort funktioner med den lägsta betydelsen;
  • ta bort användar-ID för vilka det bara finns en post;
  • lämna bara de användar-ID som finns i testet.

Och i slutändan, gör en ensemble av alla alternativ.

Den sista ensemblen

Sen på kvällen den sista dagen hade vi lagt ut en ensemble av våra modeller som gav 0,742. Över natten lanserade jag min modell med ctr_complexity=2 och istället för 30 minuter tränade den i 5 timmar. Först klockan 4 räknades det, och jag gjorde den sista ensemblen, som gav 0,7433 på den offentliga topplistan.

På grund av olika tillvägagångssätt för att lösa problemet var våra förutsägelser inte starkt korrelerade, vilket gav en bra ökning av ensemblen. För att få en bra ensemble är det bättre att använda råmodellen predictions predict(prediction_type='RawFormulaVal') och sätta scale_pos_weight=neg_count/pos_count.

SNA Hackathon 2019

På hemsidan kan du se slutresultat på den privata topplistan.

Andra lösningar

Många team följde kanonerna för algoritmer för rekommenderade system. Jag, som inte är expert på detta område, kan inte utvärdera dem, men jag kommer ihåg 2 intressanta lösningar.

  • Nikolay Anokhins lösning. Nikolay, som är anställd på Mail.ru, ansökte inte om priser, så hans mål var inte att uppnå maximal hastighet, utan att få en lätt skalbar lösning.
  • Juryprisvinnande lags beslut baserat på denna artikel från facebook, tillåter mycket bra bildkluster utan manuellt arbete.

Slutsats

Det som fastnade mest i mitt minne:

  • Om det finns kategoriska funktioner i data, och du vet hur man gör målkodning korrekt, är det fortfarande bättre att prova catboost.
  • Om du deltar i en tävling bör du inte slösa tid på att välja andra parametrar än learning_rate och iterationer. En snabbare lösning är att göra en ensemble av flera modeller.
  • Boosting kan lära sig på GPU:n. Catboost kan lära sig väldigt snabbt på GPU:n, men det äter upp mycket minne.
  • Under utveckling och testning av idéer är det bättre att ställa in en liten rsm~=0.2 (endast CPU) och ctr_complexity=1.
  • Till skillnad från andra team gav ensemblen av våra modeller en stor ökning. Vi utbytte bara idéer och skrev på olika språk. Vi hade ett annat tillvägagångssätt för att dela upp data och jag tror att var och en hade sina egna buggar.
  • Det är inte klart varför rankningsoptimering fungerade sämre än klassificeringsoptimering.
  • Jag fick lite erfarenhet av att arbeta med texter och en förståelse för hur rekommendatorsystem görs.

SNA Hackathon 2019

Tack till arrangörerna för känslorna, kunskapen och priserna.

Källa: will.com

Lägg en kommentar