SNA Hackathon 2019

I februar-mars 2019 ble det holdt en konkurranse for å rangere feeden for sosiale nettverk SNA Hackathon 2019, der laget vårt tok førsteplassen. I artikkelen vil jeg snakke om organiseringen av konkurransen, metodene vi prøvde, og catboost-innstillingene for trening på big data.

SNA Hackathon 2019

SNA Hackathon

Det er tredje gang det arrangeres et hackathon under dette navnet. Det er organisert av henholdsvis det sosiale nettverket ok.ru, oppgaven og dataene er direkte relatert til dette sosiale nettverket.
SNA (sosial nettverksanalyse) er i dette tilfellet mer korrekt forstått ikke som en analyse av en sosial graf, men snarere som en analyse av et sosialt nettverk.

  • I 2014 var oppgaven å forutsi antall likes et innlegg ville få.
  • I 2016 - VVZ-oppgaven (kanskje du er kjent), nærmere analysen av den sosiale grafen.
  • I 2019, rangering av brukerens feed basert på sannsynligheten for at brukeren vil like innlegget.

Jeg kan ikke si noe om 2014, men i 2016 og 2019, i tillegg til dataanalyseevner, var det også nødvendig med ferdigheter i å jobbe med big data. Jeg tror det var kombinasjonen av maskinlæring og store databehandlingsproblemer som tiltrakk meg til disse konkurransene, og min erfaring på disse områdene hjalp meg å vinne.

mlbootcamp

I 2019 ble konkurransen arrangert på plattformen https://mlbootcamp.ru.

Konkurransen startet på nett 7. februar og besto av 3 oppgaver. Hvem som helst kan registrere seg på siden, laste ned baseline og last bilen i noen timer. På slutten av nettscenen 15. mars ble de 15 beste fra hvert hoppbegivenhet invitert til Mail.ru-kontoret for offline-scenen, som fant sted fra 30. mars til 1. april.

Oppgave

Kildedataene gir bruker-ID-er (userId) og post-ID-er (objectId). Hvis brukeren ble vist et innlegg, så inneholder dataene en linje som inneholder userId, objectId, brukerreaksjoner på dette innlegget (tilbakemelding) og et sett med ulike funksjoner eller lenker til bilder og tekster.

bruker-ID objekt-ID eier-ID tilbakemelding bilder
3555 22 5677 [likte, klikket] [hash1]
12842 55 32144 [likte ikke] [hash2, hash3]
13145 35 5677 [klikket, delt] [hash2]

Testdatasettet inneholder en lignende struktur, men tilbakemeldingsfeltet mangler. Oppgaven er å forutsi tilstedeværelsen av "likte"-reaksjonen i tilbakemeldingsfeltet.
Innsendingsfilen har følgende struktur:

bruker-ID SortedList[objectId]
123 78,13,54,22
128 35,61,55
131 35,68,129,11

Beregningen er gjennomsnittlig ROC AUC for brukere.

En mer detaljert beskrivelse av dataene finner du på rådets nettside. Du kan også laste ned data der, inkludert tester og bilder.

Online scene

På nettstadiet var oppgaven delt inn i 3 deler

Frakoblet scene

På offline-stadiet inkluderte dataene alle funksjoner, mens tekster og bilder var sparsomme. Det var 1,5 ganger flere rader i datasettet, som det allerede var mange av.

Løsningen på problemet

Siden jeg tar CV på jobben, startet jeg reisen min i denne konkurransen med «Bilder»-oppgaven. Dataene som ble oppgitt var userId, objectId, ownerId (gruppen der innlegget ble publisert), tidsstempler for å opprette og vise innlegget, og selvfølgelig bildet for dette innlegget.
Etter å ha generert flere funksjoner basert på tidsstempler, var den neste ideen å ta det nest siste laget av nevronet som var forhåndstrent på imagenet og sende disse innebyggingene til boosting.

SNA Hackathon 2019

Resultatene var ikke imponerende. Innebygginger fra bildenettneuronen er irrelevante, tenkte jeg, jeg må lage min egen autokoder.

SNA Hackathon 2019

Det tok mye tid og resultatet ble ikke bedre.

Funksjonsgenerering

Å jobbe med bilder tar mye tid, så jeg bestemte meg for å gjøre noe enklere.
Som du umiddelbart ser er det flere kategoriske trekk i datasettet, og for å ikke bry meg for mye tok jeg bare catboost. Løsningen var utmerket, uten noen innstillinger kom jeg umiddelbart til første linje på ledertavlen.

Det er ganske mye data og det er lagt ut i parkettformat, så uten å tenke to ganger tok jeg scala og begynte å skrive alt i gnist.

De enkleste funksjonene som ga mer vekst enn bildeinnbygging:

  • hvor mange ganger objectId, userId og ownerId dukket opp i dataene (bør korrelere med popularitet);
  • hvor mange innlegg userId har sett fra eierId (bør korrelere med brukerens interesse for gruppen);
  • hvor mange unike userIds som har sett innlegg fra eierId (reflekterer størrelsen på gruppens publikum).

Fra tidsstempler var det mulig å finne tidspunktet på dagen brukeren så feeden (morgen/ettermiddag/kveld/natt). Ved å kombinere disse kategoriene kan du fortsette å generere funksjoner:

  • hvor mange ganger bruker-ID logget på om kvelden;
  • på hvilket tidspunkt dette innlegget oftest vises (objectId) og så videre.

Alt dette forbedret beregningene gradvis. Men størrelsen på treningsdatasettet er omtrent 20 millioner poster, så å legge til funksjoner bremset treningen betydelig.

Jeg har revurdert min tilnærming til bruk av data. Selv om dataene er tidsavhengige, så jeg ikke noen åpenbare informasjonslekkasjer "i fremtiden", men for sikkerhets skyld delte jeg det ned slik:

SNA Hackathon 2019

Opplæringssettet gitt til oss (februar og 2 uker i mars) var delt inn i 2 deler.
Modellen ble trent på data fra de siste N dagene. Aggregeringene beskrevet ovenfor ble bygget på alle data, inkludert testen. Samtidig har det dukket opp data som det er mulig å bygge ulike kodinger av målvariabelen på. Den enkleste tilnærmingen er å gjenbruke kode som allerede skaper nye funksjoner, og ganske enkelt mate den data som den ikke vil bli trent på og mål = 1.

Dermed har vi lignende funksjoner:

  • Hvor mange ganger har userId sett et innlegg i gruppen eierId;
  • Hvor mange ganger userId likte innlegget i group ownerId;
  • Prosentandelen av innlegg som userId likte fra eierId.

Det vil si, viste det seg gjennomsnittlig målkoding på en del av datasettet for ulike kombinasjoner av kategoriske trekk. I prinsippet bygger catboost også målkoding og fra dette synspunktet er det ingen fordel, men for eksempel ble det mulig å telle antall unike brukere som likte innlegg i denne gruppen. Samtidig ble hovedmålet nådd – datasettet mitt ble redusert flere ganger, og det var mulig å fortsette å generere funksjoner.

Mens catboost kan bygge koding bare basert på den likte reaksjonen, har tilbakemeldinger andre reaksjoner: videredelt, ikke likt, ikke likt, klikket, ignorert, kodinger som kan gjøres manuelt. Jeg rekalkulerte alle slags aggregater og eliminerte funksjoner med lav betydning for ikke å blåse opp datasettet.

På den tiden var jeg på førsteplass med god margin. Det eneste som var forvirrende var at bildeinnbygginger nesten ikke viste noen vekst. Ideen kom til å gi alt til catboost. Vi grupperer Kmeans-bilder og får en ny kategorisk funksjon imageCat.

Her er noen klasser etter manuell filtrering og sammenslåing av klynger hentet fra KMeans.

SNA Hackathon 2019

Basert på imageCat genererer vi:

  • Nye kategoriske funksjoner:
    • Hvilken imageCat ble oftest sett av userId;
    • Hvilken imageCat viser oftest eier-ID;
    • Hvilken imageCat ble likt oftest av userId;
  • Ulike tellere:
    • Hvor mange unike imageCat så på bruker-ID;
    • Omtrent 15 lignende funksjoner pluss målkoding som beskrevet ovenfor.

Tekster

Resultatene i bildekonkurransen passet meg og jeg bestemte meg for å prøve meg på tekster. Jeg har ikke jobbet så mye med tekster før, og dumt nok drepte jeg dagen på tf-idf og svd. Så så jeg baseline med doc2vec, som gjør akkurat det jeg trenger. Etter å ha justert doc2vec-parametrene litt, fikk jeg tekstinnbygging.

Og så brukte jeg rett og slett koden til bildene på nytt, der jeg byttet ut bildeinnbyggingene med tekstinnbygginger. Det gjorde at jeg tok 2. plass i tekstkonkurransen.

Samarbeidssystem

Det var én konkurranse igjen som jeg ennå ikke hadde "stikket" med en pinne, og å dømme etter AUC på ledertavlen, burde resultatene av denne konkurransen ha hatt størst innvirkning på offline-stadiet.
Jeg tok alle funksjonene som var i kildedataene, valgte kategoriske og regnet ut de samme aggregatene som for bilder, bortsett fra funksjoner basert på selve bildene. Bare å sette dette i catboost fikk meg til 2. plass.

De første trinnene i catboost-optimalisering

En første- og to andreplasser gledet meg, men det var en forståelse for at jeg ikke hadde gjort noe spesielt, noe som betyr at jeg kunne forvente tap av stillinger.

Konkurransens oppgave er å rangere innlegg i brukeren, og hele denne tiden løste jeg klassifiseringsproblemet, det vil si å optimalisere feil metrikk.

La meg gi deg et enkelt eksempel:

bruker-ID objekt-ID prediksjon bakken sannhet
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

La oss gjøre en liten omorganisering

bruker-ID objekt-ID prediksjon bakken sannhet
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ølgende resultater:

modell AUC Bruker1 AUC Bruker2 AUC betyr 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, betyr ikke å forbedre den generelle AUC-beregningen å forbedre den gjennomsnittlige AUC-beregningen i en bruker.

Catboost vet hvordan man optimerer rangeringsberegninger fra esken. Jeg leste om rangeringsberegninger, suksesshistorier når du bruker catboost og sett YetiRankPairwise til å trene over natten. Resultatet var ikke imponerende. Da jeg bestemte meg for at jeg var undertrent, endret jeg feilfunksjonen til QueryRMSE, som, etter catboost-dokumentasjonen å dømme, konvergerer raskere. Til slutt fikk jeg de samme resultatene som da jeg trente for klassifisering, men ensemblene til disse to modellene ga en god økning, noe som brakte meg til førsteplass i alle tre konkurransene.

5 minutter før avslutningen av nettfasen av "Collaborative Systems"-konkurransen, flyttet Sergey Shalnov meg til andreplass. Vi gikk den videre stien sammen.

Forbereder for offline-stadiet

Vi var garantert seier på nettstadiet med et RTX 2080 TI-skjermkort, men hovedpremien på 300 000 rubler og, mest sannsynlig, til og med den endelige førsteplassen tvang oss til å jobbe i disse 2 ukene.

Som det viste seg, brukte Sergey også catboost. Vi utvekslet ideer og funksjoner, og jeg lærte om rapport av Anna Veronica Dorogush som inneholdt svar på mange av spørsmålene mine, og til og med de jeg ennå ikke hadde fått på den tiden.

Å se rapporten førte meg til ideen om at vi må returnere alle parametere til standardverdien, og gjøre innstillingene veldig nøye og først etter å ha fikset et sett med funksjoner. Nå tok en trening omtrent 15 timer, men en modell klarte å oppnå en bedre hastighet enn den som ble oppnådd i ensemblet med rangering.

Funksjonsgenerering

I konkurransen Collaborative Systems vurderes en lang rekke funksjoner som viktige for modellen. For eksempel, auditweights_spark_svd - det viktigste tegnet, men det er ingen informasjon om hva det betyr. Jeg tenkte at det ville være verdt å telle de ulike aggregatene basert på viktige egenskaper. For eksempel gjennomsnittlig auditweights_spark_svd etter bruker, etter gruppe, etter objekt. Det samme kan beregnes ved hjelp av data som det ikke utføres trening på og mål = 1, det vil si gjennomsnitt auditweights_spark_svd av bruker etter objekter han likte. Viktige tegn i tillegg auditweights_spark_svd, det var flere. Her er noen av dem:

  • revisjonsvekterCtrGender
  • revisjonsvekterCtrHigh
  • userOwnerCounterCreateLikes

For eksempel gjennomsnittet revisjonsvekterCtrGender ifølge userId viste det seg å være en viktig funksjon, akkurat som gjennomsnittsverdien userOwnerCounterCreateLikes av userId+ownerId. Dette burde allerede få deg til å tenke at du må forstå betydningen av feltene.

Også viktige funksjoner var revisjonsvekterLikesTell и revisjonsvekterShowsCount. Ved å dele den ene etter den andre ble en enda viktigere funksjon oppnådd.

Datalekkasjer

Konkurranse- og produksjonsmodellering er svært forskjellige oppgaver. Når du utarbeider data, er det svært vanskelig å ta hensyn til alle detaljene og ikke formidle noe ikke-triviell informasjon om målvariabelen i testen. Hvis vi lager en produksjonsløsning, vil vi prøve å unngå å bruke datalekkasjer ved opplæring av modellen. Men hvis vi ønsker å vinne konkurransen, så er datalekkasjer de beste egenskapene.

Etter å ha studert dataene, kan du se det i henhold til objectId-verdiene revisjonsvekterLikesTell и revisjonsvekterShowsCount endring, noe som betyr at forholdet mellom maksimalverdiene for disse funksjonene vil reflektere etterkonverteringen mye bedre enn forholdet på visningstidspunktet.

Den første lekkasjen vi fant er revisjonsvekterLikesCountMax/revisjonsvekterShowsCountMax.
Men hva om vi ser nærmere på dataene? La oss sortere etter showdato og få:

objekt-ID bruker-ID revisjonsvekterShowsCount revisjonsvekterLikesTell mål (blir likt)
1 1 12 3 sannsynligvis ikke
1 2 15 3 kanskje ja
1 3 16 4

Det var overraskende da jeg fant det første slike eksempel og det viste seg at spådommen min ikke gikk i oppfyllelse. Men med tanke på det faktum at maksimalverdiene av disse egenskapene i objektet ga en økning, var vi ikke late og bestemte oss for å finne revisjonsvekterShowsCountNext и revisjonsvekterLikerTelleNeste, det vil si verdiene i neste øyeblikk. Ved å legge til en funksjon
(auditweightsShowsCountNext-auditweightsShowsCount)/(auditweightsLikesCount-auditweightsLikesCountNext) vi gjorde et skarpt hopp raskt.
Lignende lekkasjer kan brukes ved å finne følgende verdier for userOwnerCounterCreateLikes innenfor userId+ownerId og f.eks. revisjonsvekterCtrGender innenfor objectId+userGender. Vi fant 6 lignende felt med lekkasjer og hentet ut så mye informasjon som mulig fra dem.

På det tidspunktet hadde vi presset ut så mye informasjon som mulig fra samarbeidsfunksjoner, men gikk ikke tilbake til bilde- og tekstkonkurranser. Jeg hadde en god idé å sjekke: hvor mye gir funksjoner direkte basert på bilder eller tekster i relevante konkurranser?

Det var ingen lekkasjer i bilde- og tekstkonkurransene, men på den tiden hadde jeg returnert standard catboost-parametrene, ryddet opp i koden og lagt til noen få funksjoner. Totalen ble:

beslutning snart
Maksimalt med bilder 0.6411
Maks ingen bilder 0.6297
Resultat på andreplass 0.6295

beslutning snart
Maksimalt med tekster 0.666
Maks uten tekster 0.660
Resultat på andreplass 0.656

beslutning snart
Maksimalt i samarbeid 0.745
Resultat på andreplass 0.723

Det ble tydelig at vi neppe ville klare å presse mye ut av tekster og bilder, og etter å ha prøvd et par av de mest interessante ideene, sluttet vi å jobbe med dem.

Ytterligere generering av funksjoner i samarbeidssystemer ga ingen økning, og vi begynte å rangere. På nettstadiet ga klassifiserings- og rangeringsensemblet meg en liten økning, da det viste seg fordi jeg undertrente klassifiseringen. Ingen av feilfunksjonene, inkludert YetiRanlPairwise, ga noe i nærheten av resultatet som LogLoss gjorde (0,745 vs. 0,725). Det var fortsatt håp for QueryCrossEntropy, som ikke kunne lanseres.

Frakoblet scene

På offline-stadiet forble datastrukturen den samme, men det var mindre endringer:

  • identifikatorer userId, objectId, ownerId ble omrandomisert;
  • flere skilt ble fjernet og flere ble omdøpt;
  • dataene har økt ca. 1,5 ganger.

I tillegg til de oppførte vanskelighetene var det ett stort pluss: teamet ble tildelt en stor server med en RTX 2080TI. Jeg har likt htop lenge.
SNA Hackathon 2019

Det var bare én idé - å ganske enkelt reprodusere det som allerede eksisterer. Etter å ha brukt et par timer på å sette opp miljøet på serveren, begynte vi gradvis å verifisere at resultatene var reproduserbare. Hovedproblemet vi står overfor er økningen i datavolumet. Vi bestemte oss for å redusere belastningen litt og sette catboost-parameteren ctr_complexity=1. Dette senker hastigheten litt, men modellen min begynte å fungere, resultatet ble bra - 0,733. Sergey, i motsetning til meg, delte ikke dataene i 2 deler og trente på alle dataene, selv om dette ga de beste resultatene på online-stadiet, på offline-stadiet var det mange vanskeligheter. Hvis vi tok alle funksjonene vi genererte og prøvde å skyve dem inn i catboost, ville ingenting fungere på nettstadiet. Sergey gjorde typeoptimalisering, for eksempel ved å konvertere float64-typer til float32. I denne artikkelen, Du kan finne informasjon om minneoptimalisering i pandaer. Som et resultat trente Sergey på CPU-en ved å bruke alle dataene og fikk omtrent 0,735.

Disse resultatene var nok til å vinne, men vi skjulte vår sanne fart og kunne ikke være sikre på at andre lag ikke gjorde det samme.

Kjemp til det siste

Catboost-tuning

Løsningen vår ble fullstendig reprodusert, vi la til funksjonene til tekstdata og bilder, så alt som gjensto var å justere catboost-parametrene. Sergey trente på CPU med et lite antall iterasjoner, og jeg trente på den med ctr_complexity=1. Det var én dag igjen, og hvis du bare la til iterasjoner eller økte ctr_complexity, så kunne du om morgenen få en enda bedre hastighet og gå hele dagen.

På offline-stadiet kan hastigheter veldig enkelt skjules ved ganske enkelt å velge ikke den beste løsningen på nettstedet. Vi forventet drastiske endringer i ledertavlen de siste minuttene før innleveringene ble avsluttet og bestemte oss for ikke å stoppe.

Fra Annas video lærte jeg at for å forbedre kvaliteten på modellen, er det best å velge følgende parametere:

  • learning_rate — Standardverdien beregnes basert på størrelsen på datasettet. Å øke læringshastigheten krever å øke antall iterasjoner.
  • l2_bladreg — Regulariseringskoeffisient, standardverdi 3, velg helst fra 2 til 30. Redusering av verdien fører til en økning i overfitting.
  • bagging_temperatur — legger til randomisering til vekten av objekter i utvalget. Standardverdien er 1, hvor vektene er trukket fra en eksponentiell fordeling. Å redusere verdien fører til en økning i overfit.
  • tilfeldig_styrke — Påvirker valg av splittelser ved en spesifikk iterasjon. Jo høyere tilfeldig_styrke, desto større er sjansen for at en del av lav betydning velges. Ved hver påfølgende iterasjon avtar tilfeldigheten. Å redusere verdien fører til en økning i overfit.

Andre parametere har mye mindre effekt på det endelige resultatet, så jeg prøvde ikke å velge dem. Én iterasjon av trening på mitt GPU-datasett med ctr_complexity=1 tok 20 minutter, og de valgte parameterne på det reduserte datasettet var litt forskjellige fra de optimale på hele datasettet. Til slutt gjorde jeg omtrent 30 iterasjoner på 10 % av dataene, og deretter omtrent 10 flere iterasjoner på alle dataene. Det ble noe slikt:

  • learning_rate Jeg økte med 40 % fra standard;
  • l2_bladreg lot det være det samme;
  • bagging_temperatur и tilfeldig_styrke redusert til 0,8.

Vi kan konkludere med at modellen ble undertrent med standardparametrene.

Jeg ble veldig overrasket da jeg så resultatet på ledertavlen:

modell modell 1 modell 2 modell 3 ensemble
Uten tuning 0.7403 0.7404 0.7404 0.7407
Med tuning 0.7406 0.7405 0.7406 0.7408

Jeg konkluderte for meg selv at hvis rask anvendelse av modellen ikke er nødvendig, er det bedre å erstatte utvalget av parametere med et ensemble av flere modeller ved å bruke ikke-optimaliserte parametere.

Sergey optimaliserte størrelsen på datasettet for å kjøre det på GPU. Det enkleste alternativet er å kutte av deler av dataene, men dette kan gjøres på flere måter:

  • fjern gradvis de eldste dataene (begynnelsen av februar) til datasettet begynner å passe inn i minnet;
  • fjern funksjoner med lavest betydning;
  • fjerne bruker-IDer som det bare er én oppføring for;
  • la bare bruker-ID-ene som er i testen.

Og til slutt, lag et ensemble ut av alle alternativene.

Det siste ensemblet

Sent på kvelden den siste dagen hadde vi lagt ut et ensemble av modellene våre som ga 0,742. Over natten lanserte jeg modellen min med ctr_complexity=2 og i stedet for 30 minutter trente den i 5 timer. Først klokken 4 ble det talt, og jeg laget det siste ensemblet, som ga 0,7433 på den offentlige ledertavlen.

På grunn av ulike tilnærminger til å løse problemet, var våre spådommer ikke sterkt korrelert, noe som ga en god økning i ensemblet. For å få et godt ensemble er det bedre å bruke råmodellprediksjonene predict(prediction_type='RawFormulaVal') og sette scale_pos_weight=neg_count/pos_count.

SNA Hackathon 2019

På nettsiden kan du se endelige resultater på den private ledertavlen.

Andre løsninger

Mange team fulgte kanonene til anbefalingssystemalgoritmer. Jeg, som ikke er ekspert på dette feltet, kan ikke evaluere dem, men jeg husker 2 interessante løsninger.

  • Nikolay Anokhins løsning. Nikolay, som er ansatt i Mail.ru, søkte ikke om premier, så målet hans var ikke å oppnå maksimal hastighet, men å få en lett skalerbar løsning.
  • Juryprisvinnende lags avgjørelse basert på denne artikkelen fra facebook, muliggjør veldig god bildeklynger uten manuelt arbeid.

Konklusjon

Det som satt seg mest i hukommelsen:

  • Hvis det er kategoriske funksjoner i dataene, og du vet hvordan du gjør målkoding riktig, er det fortsatt bedre å prøve catboost.
  • Hvis du deltar i en konkurranse, bør du ikke kaste bort tid på å velge andre parametere enn learning_rate og iterasjoner. En raskere løsning er å lage et ensemble av flere modeller.
  • Forsterkninger kan lære på GPU. Catboost kan lære veldig raskt på GPU, men det spiser opp mye minne.
  • Under utvikling og testing av ideer er det bedre å sette en liten rsm~=0.2 (kun CPU) og ctr_complexity=1.
  • I motsetning til andre lag ga ensemblet av modellene våre en stor økning. Vi utvekslet bare ideer og skrev på forskjellige språk. Vi hadde en annen tilnærming til å dele dataene, og jeg tror hver hadde sine egne feil.
  • Det er ikke klart hvorfor rangeringsoptimalisering presterte dårligere enn klassifiseringsoptimalisering.
  • Jeg fikk litt erfaring med å jobbe med tekster og forståelse for hvordan anbefalingssystemer lages.

SNA Hackathon 2019

Takk til arrangørene for mottatte følelser, kunnskap og premier.

Kilde: www.habr.com

Legg til en kommentar