SNA Hackathon 2019

V únoru až březnu 2019 proběhla soutěž o hodnocení zdroje sociální sítě SNA Hackathon 2019, ve kterém náš tým obsadil první místo. V článku budu mluvit o organizaci soutěže, metodách, které jsme vyzkoušeli, a nastavení catboost pro trénink na velkých datech.

SNA Hackathon 2019

SNA Hackathon

Pod tímto názvem se hackathon koná již potřetí. Organizuje ho sociální síť ok.ru, respektive úkol a data s touto sociální sítí přímo souvisí.
SNA (analýza sociální sítě) je v tomto případě správněji chápána nikoli jako analýza sociálního grafu, ale spíše jako analýza sociální sítě.

  • V roce 2014 bylo úkolem předpovědět počet lajků, které příspěvek získá.
  • V roce 2016 - úkol VVZ (možná znáte), blíže k analýze sociálního grafu.
  • V roce 2019 hodnocení zdroje uživatele na základě pravděpodobnosti, že se uživateli bude příspěvek líbit.

Nemohu říci o roce 2014, ale v letech 2016 a 2019 byly kromě schopnosti analýzy dat vyžadovány také dovednosti v práci s velkými daty. Myslím, že to byla kombinace strojového učení a problémů se zpracováním velkých dat, co mě přilákalo do těchto soutěží, a moje zkušenosti v těchto oblastech mi pomohly vyhrát.

mlbootcamp

V roce 2019 byla soutěž organizována na platformě https://mlbootcamp.ru.

Soutěž začala online 7. února a skládala se ze 3 úkolů. Kdokoli se mohl na stránce zaregistrovat, stáhnout základní a naložit své auto na několik hodin. Na konci online fáze 15. března bylo 15 nejlepších z každého parkuru pozváno do kanceláře Mail.ru na offline fázi, která se konala od 30. března do 1. dubna.

Úkol

Zdrojová data poskytují ID uživatele (userId) a ID příspěvku (objectId). Pokud se uživateli zobrazil příspěvek, pak data obsahují řádek obsahující userId, objectId, reakce uživatelů na tento příspěvek (zpětná vazba) a sadu různých funkcí nebo odkazů na obrázky a texty.

uživatelské ID objectId id vlastníka zpětná vazba obrazy
3555 22 5677 [lajkováno, kliknuto] [hash1]
12842 55 32144 [nelíbí se] [hash2, hash3]
13145 35 5677 [kliknuto, sdíleno] [hash2]

Soubor testovacích dat obsahuje podobnou strukturu, ale chybí pole zpětné vazby. Úkolem je předpovědět přítomnost „líbené“ reakce v poli zpětné vazby.
Odeslaný soubor má následující strukturu:

uživatelské ID SortedList[objectId]
123 78,13,54,22
128 35,61,55
131 35,68,129,11

Metrikou je průměrná ROC AUC pro uživatele.

Podrobnější popis údajů naleznete na webové stránky rady. Můžete si tam také stáhnout data včetně testů a obrázků.

Online jeviště

V online fázi byl úkol rozdělen na 3 části

  • Kolaborativní systém — zahrnuje všechny funkce kromě obrázků a textů;
  • Obraz — obsahuje pouze informace o obrázcích;
  • Texty — obsahuje informace pouze o textech.

Offline fáze

Ve fázi offline data zahrnovala všechny funkce, zatímco texty a obrázky byly řídké. V datasetu bylo 1,5x více řádků, kterých už bylo hodně.

Řešení problému

Jelikož v práci dělám CV, začala jsem svou cestu v této soutěži úkolem „Obrázky“. Údaje, které byly poskytnuty, byly userId, objectId, ownerId (skupina, ve které byl příspěvek publikován), časová razítka pro vytvoření a zobrazení příspěvku a samozřejmě obrázek pro tento příspěvek.
Po vygenerování několika funkcí založených na časových razítkách bylo dalším nápadem vzít předposlední vrstvu neuronu předem trénovanou na imagenet a poslat tato vložení k posílení.

SNA Hackathon 2019

Výsledky nebyly působivé. Vložení z neuronu imagenet je irelevantní, pomyslel jsem si, musím si vytvořit svůj vlastní autokodér.

SNA Hackathon 2019

Zabralo to spoustu času a výsledek se nezlepšil.

Generování funkcí

Práce s obrázky zabere hodně času, tak jsem se rozhodl udělat něco jednoduššího.
Jak můžete okamžitě vidět, v datasetu je několik kategorických funkcí, a abych se příliš neobtěžoval, vzal jsem jen catboost. Řešení bylo výborné, bez jakéhokoliv nastavování jsem se okamžitě dostal na první řádek žebříčku.

Dat je poměrně hodně a jsou uspořádány ve formátu parket, takže jsem bez přemýšlení vzal scala a začal vše psát jiskra.

Nejjednodušší funkce, které přinesly větší růst než vkládání obrázků:

  • kolikrát se objectId, userId a ownerId objevilo v datech (mělo by korelovat s popularitou);
  • kolik příspěvků userId vidělo od ownerId (mělo by korelovat se zájmem uživatele o skupinu);
  • kolik jedinečných ID uživatelů zobrazilo příspěvky od vlastníka (odráží velikost publika skupiny).

Z časových razítek bylo možné získat denní dobu, kdy uživatel sledoval zdroj (ráno/odpoledne/večer/noc). Kombinací těchto kategorií můžete pokračovat ve generování funkcí:

  • kolikrát se userId večer přihlásil;
  • v jakém čase se tento příspěvek nejčastěji zobrazuje (objectId) a podobně.

To vše postupně vylepšovalo metriky. Ale velikost tréninkové datové sady je asi 20 milionů záznamů, takže přidávání funkcí značně zpomalilo trénink.

Přehodnotil jsem svůj přístup k používání dat. Přestože jsou data časově závislá, nezaznamenal jsem žádné zjevné úniky informací „v budoucnu“, přesto jsem je pro jistotu rozebral takto:

SNA Hackathon 2019

Tréninkový set, který nám byl poskytnut (únor a 2 týdny v březnu) byl rozdělen na 2 části.
Model byl trénován na datech z posledních N dnů. Výše popsané agregace byly postaveny na všech datech, včetně testu. Zároveň se objevila data, na kterých je možné stavět různá kódování cílové proměnné. Nejjednodušší přístup je znovu použít kód, který již vytváří nové funkce, a jednoduše do něj vložit data, na kterých nebude trénován a cíl = 1.

Tak jsme dostali podobné vlastnosti:

  • Kolikrát userId viděl příspěvek ve skupině ownerId;
  • Kolikrát se uživateli userId líbil příspěvek ve skupině ownerId;
  • Procento příspěvků, které se uživateli userId líbily od vlastníka.

To znamená, že se ukázalo střední cílové kódování na části datové sady pro různé kombinace kategoriálních znaků. Catboost v zásadě také staví cílové kódování a z tohoto pohledu to nemá žádný přínos, ale například bylo možné spočítat počet unikátních uživatelů, kterým se líbily příspěvky v této skupině. Zároveň byl splněn hlavní cíl - můj datový soubor byl několikrát zredukován a bylo možné pokračovat ve generování funkcí.

Zatímco catboost může vytvářet kódování pouze na základě reakce, která se mi líbí, zpětná vazba má jiné reakce: znovu sdílené, nelíbí se mi, nelíbí se mi, kliknuto, ignorují, kódování lze provést ručně. Přepočítal jsem všechny druhy agregací a eliminoval prvky s nízkou důležitostí, aby nedošlo k nafouknutí datové sady.

V té době jsem byl s velkým náskokem na prvním místě. Jediné, co bylo matoucí, bylo, že vkládání obrázků nevykazovalo téměř žádný růst. Přišel nápad dát všechno do catboostu. Seskupujeme obrázky Kmeans a získáváme novou kategorickou funkci imageCat.

Zde jsou některé třídy po ručním filtrování a sloučení clusterů získaných z KMeans.

SNA Hackathon 2019

Na základě imageCat generujeme:

  • Nové kategorické funkce:
    • Který imageCat byl nejčastěji zobrazen podle userId;
    • Která imageCat nejčastěji zobrazuje ownerId;
    • Který imageCat se nejčastěji líbil podle userId;
  • Různé čítače:
    • Kolik jedinečných imageCat se podíval na userId;
    • Asi 15 podobných funkcí plus cílové kódování, jak je popsáno výše.

Texty

Výsledky v obrázkové soutěži mi vyhovovaly a rozhodl jsem se vyzkoušet texty. Předtím jsem s texty moc nepracoval a bláhově jsem zabil den na tf-idf a svd. Pak jsem viděl základní linii s doc2vec, který dělá přesně to, co potřebuji. Po mírné úpravě parametrů doc2vec jsem získal vkládání textu.

A pak jsem jednoduše znovu použil kód pro obrázky, ve kterých jsem nahradil vložení obrázků vložením textu. Díky tomu jsem v textové soutěži obsadil 2. místo.

Kolaborativní systém

Zbývala jedna soutěž, do které jsem ještě „nešťouchl“ klackem, a soudě podle AUC na žebříčku měly mít výsledky této konkrétní soutěže největší dopad na offline scénu.
Vzal jsem všechny vlastnosti, které byly ve zdrojových datech, vybral kategoriální a vypočítal stejné agregáty jako u obrázků, kromě prvků založených na obrázcích samotných. Právě uvedením tohoto do catboostu jsem se dostal na 2. místo.

První kroky optimalizace catboost

Jedno první a dvě druhá místa mě potěšily, ale došlo k pochopení, že jsem nic zvláštního neudělal, což znamená, že jsem mohl očekávat ztrátu pozice.

Úkolem soutěže je seřadit příspěvky v rámci uživatele a celou tu dobu jsem řešil klasifikační problém, tedy optimalizaci špatné metriky.

Uvedu jednoduchý příklad:

uživatelské ID objectId předpověď pozemní pravda
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

Udělejme malou přestavbu

uživatelské ID objectId předpověď pozemní pravda
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

Dostáváme následující výsledky:

model AUC Uživatel1 AUC Uživatel2 AUC střední AUC
Možnost 1 0,8 1,0 0,0 0,5
Možnost 2 0,7 0,75 1,0 0,875

Jak vidíte, zlepšení celkové metriky AUC neznamená zlepšení průměrné metriky AUC v rámci uživatele.

Catboost ví, jak optimalizovat metriky hodnocení z krabice. Četl jsem o hodnotících metrikách, příběhy úspěchu při použití catboost a nastavte YetiRankPairwise na trénink přes noc. Výsledek nebyl působivý. Rozhodl jsem se, že jsem nedotrénovaný, změnil jsem chybovou funkci na QueryRMSE, která, soudě podle dokumentace catboost, konverguje rychleji. Nakonec jsem dostal stejné výsledky jako při tréninku na klasifikaci, ale soubory těchto dvou modelů daly pořádný nárůst, což mě vyneslo na první místo ve všech třech soutěžích.

5 minut před uzavřením online fáze soutěže „Collaborative Systems“ mě Sergey Shalnov posunul na druhé místo. Společně jsme prošli další cestu.

Příprava na offline fázi

Bylo nám zaručeno vítězství v online fázi s grafickou kartou RTX 2080 TI, ale hlavní cena 300 000 rublů a pravděpodobně i konečné první místo nás donutily tyto 2 týdny pracovat.

Jak se ukázalo, Sergej také používal catboost. Vyměnili jsme si nápady a funkce a já se o tom dozvěděl zpráva Anny Veronicy Dorogush který obsahoval odpovědi na mnoho mých otázek, a dokonce i na ty, které jsem do té doby ještě neměl.

Zobrazení zprávy mě přivedlo na myšlenku, že musíme vrátit všechny parametry na výchozí hodnotu a provést nastavení velmi pečlivě a až po opravě sady funkcí. Nyní jeden trénink trval asi 15 hodin, ale jednomu modelu se podařilo získat rychlost lepší než v souboru s hodnocením.

Generování funkcí

V soutěži Collaborative Systems je velké množství funkcí hodnoceno jako důležité pro model. Například, auditweights_spark_svd - nejdůležitější znak, ale neexistují žádné informace o tom, co to znamená. Myslel jsem, že by stálo za to spočítat různé agregáty na základě důležitých vlastností. Například průměr auditweights_spark_svd podle uživatele, podle skupiny, podle objektu. Totéž lze vypočítat pomocí dat, na kterých se neprovádí žádný trénink a cíl = 1, tedy průměr auditweights_spark_svd uživatelem podle objektů, které se mu líbily. Kromě toho důležitá znamení auditweights_spark_svd, bylo jich několik. Tady jsou některé z nich:

  • auditweightsCtrGender
  • auditweightsCtrHigh
  • userOwnerCounterCreateLikes

Například průměr auditweightsCtrGender podle userId se to ukázalo jako důležitá vlastnost, stejně jako průměrná hodnota userOwnerCounterCreateLikes podle userId+ownerId. Už to by vás mělo přimět k zamyšlení, že musíte rozumět významu polí.

Také důležité funkce byly auditweightsLikesCount и auditweightsShowsCount. Rozdělením jednoho na druhého jsme získali ještě důležitější rys.

Úniky dat

Konkurenční a produkční modelování jsou velmi odlišné úkoly. Při přípravě dat je velmi obtížné zohlednit všechny detaily a nesdělit v testu nějaké netriviální informace o cílové proměnné. Pokud vytváříme produkční řešení, pokusíme se vyhnout použití úniků dat při trénování modelu. Pokud ale chceme soutěž vyhrát, pak jsou úniky dat tou nejlepší funkcí.

Po prostudování dat to můžete vidět podle hodnot objectId auditweightsLikesCount и auditweightsShowsCount změna, což znamená, že poměr maximálních hodnot těchto funkcí bude odrážet post konverzi mnohem lépe než poměr v době zobrazení.

První únik, který jsme našli, je auditweightsLikesCountMax/auditweightsShowsCountMax.
Ale co když se na data podíváme blíže? Seřaďme podle data představení a získáme:

objectId uživatelské ID auditweightsShowsCount auditweightsLikesCount cíl (líbí se)
1 1 12 3 asi ne
1 2 15 3 Možná ano
1 3 16 4

Bylo překvapivé, když jsem našel první takový příklad a ukázalo se, že moje předpověď se nenaplnila. Ale s ohledem na skutečnost, že maximální hodnoty těchto charakteristik v objektu zvýšily, nebyli jsme líní a rozhodli jsme se najít auditweightsShowsCountNext и auditweightsLikesCountNext, tedy hodnoty v příštím časovém okamžiku. Přidáním funkce
(auditweightsShowsCountNext-auditweightsShowsCount)/(auditweightsLikesCount-auditweightsLikesCountNext) rychle jsme udělali ostrý skok.
Podobné úniky lze použít nalezením následujících hodnot pro userOwnerCounterCreateLikes v rámci userId+ownerId a např. auditweightsCtrGender v rámci objectId+userGender. Našli jsme 6 podobných polí s úniky a vytěžili z nich co nejvíce informací.

Do té doby jsme z kolaborativních funkcí vymáčkli co nejvíce informací, ale nevrátili jsme se k obrazovým a textovým soutěžím. Měl jsem skvělý nápad zkontrolovat: kolik dávají funkce přímo založené na obrázcích nebo textech v příslušných soutěžích?

V soutěžích obrázků a textů nebyly žádné úniky, ale do té doby jsem vrátil výchozí parametry catboost, vyčistil kód a přidal pár funkcí. Celkem bylo:

rozhodnutí Rychlost
Maximálně s obrázky 0.6411
Maximálně žádné obrázky 0.6297
Výsledek na druhém místě 0.6295

rozhodnutí Rychlost
Maximálně s texty 0.666
Maximálně bez textů 0.660
Výsledek na druhém místě 0.656

rozhodnutí Rychlost
Maximálně ve spolupráci 0.745
Výsledek na druhém místě 0.723

Bylo zřejmé, že je nepravděpodobné, že bychom byli schopni vymáčknout mnoho z textů a obrázků, a poté, co jsme vyzkoušeli několik nejzajímavějších nápadů, jsme s nimi přestali pracovat.

Další generace funkcí v kolaborativních systémech nepřinesla nárůst a začali jsme hodnotit. V online fázi mi soubor klasifikace a hodnocení přinesl malý nárůst, jak se ukázalo, protože jsem klasifikaci podtrénoval. Žádná z chybových funkcí, včetně YetiRanlPairwise, nepřinesla výsledek, který by se blížil výsledku LogLoss (0,745 vs. 0,725). Stále existovala naděje pro QueryCrossEntropy, kterou se nepodařilo spustit.

Offline fáze

Ve fázi offline zůstala datová struktura stejná, ale došlo k drobným změnám:

  • identifikátory userId, objectId, ownerId byly rerandomizovány;
  • několik znaků bylo odstraněno a několik bylo přejmenováno;
  • údaje se zvýšily přibližně 1,5krát.

Kromě uvedených potíží tu bylo jedno velké plus: týmu byl přidělen velký server s RTX 2080TI. Htop mě baví už dlouho.
SNA Hackathon 2019

Byla tu jen jedna myšlenka – jednoduše reprodukovat to, co již existuje. Po několika hodinách nastavování prostředí na serveru jsme postupně začali ověřovat, zda jsou výsledky reprodukovatelné. Hlavním problémem, se kterým se potýkáme, je nárůst objemu dat. Rozhodli jsme se trochu snížit zátěž a nastavili jsme parametr catboost ctr_complexity=1. Tím se rychlost trochu sníží, ale můj model začal fungovat, výsledek byl dobrý - 0,733. Sergey, na rozdíl ode mě, nerozdělil data na 2 části a trénoval na všech datech, i když to přineslo nejlepší výsledky ve fázi online, ve fázi offline bylo mnoho potíží. Pokud bychom vzali všechny funkce, které jsme vygenerovali, a pokusili se je strčit do catboostu, pak by v online fázi nic nefungovalo. Sergey provedl optimalizaci typů, například převedl typy float64 na float32. V tomto článku, Informace o optimalizaci paměti najdete u pand. Výsledkem bylo, že Sergej trénoval na CPU pomocí všech dat a dostal asi 0,735.

Tyto výsledky stačily na vítězství, ale skryli jsme naši skutečnou rychlost a nemohli jsme si být jisti, že ostatní týmy nedělají totéž.

Bojujte do posledního

Ladění Catboost

Naše řešení bylo plně reprodukováno, přidali jsme funkce textových dat a obrázků, takže zbývalo jen doladit parametry catboost. Sergey trénoval na CPU s malým počtem iterací a já jsem trénoval na tom s ctr_complexity=1. Zbýval jeden den, a pokud jste jen přidali iterace nebo zvýšili ctr_complexity, pak ráno můžete získat ještě lepší rychlost a chodit celý den.

Ve fázi offline lze rychlosti velmi snadno skrýt jednoduchým výběrem ne nejlepšího řešení na webu. Očekávali jsme drastické změny v žebříčku v posledních minutách před uzavřením přihlášek a rozhodli jsme se nepřestat.

Z Annina videa jsem se dozvěděl, že pro zlepšení kvality modelu je nejlepší vybrat následující parametry:

  • rychlost_učení — Výchozí hodnota se vypočítá na základě velikosti datové sady. Zvýšení rychlosti učení vyžaduje zvýšení počtu iterací.
  • l2_leaf_reg — Regularizační koeficient, výchozí hodnota 3, nejlépe volte od 2 do 30. Snížení hodnoty vede ke zvýšení přesazení.
  • teplota_balení — přidává náhodnost k vahám objektů ve vzorku. Výchozí hodnota je 1, přičemž váhy jsou čerpány z exponenciálního rozdělení. Snížení hodnoty vede ke zvýšení overfitu.
  • náhodná_síla — Ovlivňuje výběr rozdělení v konkrétní iteraci. Čím vyšší je random_strength, tím vyšší je šance, že bude vybráno rozdělení s nízkou důležitostí. Při každé následující iteraci se náhodnost snižuje. Snížení hodnoty vede ke zvýšení overfitu.

Ostatní parametry mají na konečný výsledek mnohem menší vliv, proto jsem je nezkoušel vybírat. Jedna iterace trénování na mé datové sadě GPU s ctr_complexity=1 trvala 20 minut a vybrané parametry na redukované datové sadě se mírně lišily od optimálních na úplné datové sadě. Nakonec jsem provedl asi 30 iterací na 10 % dat a pak dalších asi 10 iterací na všech datech. Ukázalo se něco takového:

  • rychlost_učení Zvýšil jsem o 40 % z výchozí hodnoty;
  • l2_leaf_reg nechal to stejné;
  • teplota_balení и náhodná_síla snížena na 0,8.

Můžeme dojít k závěru, že model byl podtrénovaný s výchozími parametry.

Byl jsem velmi překvapen, když jsem viděl výsledek na žebříčku:

model model 1 model 2 model 3 soubor
Bez ladění 0.7403 0.7404 0.7404 0.7407
S laděním 0.7406 0.7405 0.7406 0.7408

Sám jsem usoudil, že pokud není potřeba rychlá aplikace modelu, je lepší nahradit výběr parametrů souborem několika modelů s neoptimalizovanými parametry.

Sergey optimalizoval velikost datové sady, aby ji spouštěl na GPU. Nejjednodušší možností je odříznout část dat, ale to lze provést několika způsoby:

  • postupně odstraňovat nejstarší data (začátek února), dokud se datová sada nezačne vejít do paměti;
  • odstranit prvky s nejnižší důležitostí;
  • odstranit userId, pro která existuje pouze jeden záznam;
  • ponechte pouze ID uživatele, která jsou v testu.

A nakonec ze všech možností vytvořte soubor.

Poslední soubor

Do pozdního večera posledního dne jsme rozložili soubor našich modelů, který dal 0,742. Přes noc jsem spustil svůj model s ctr_complexity=2 a místo 30 minut trénoval 5 hodin. Teprve ve 4 hodiny ráno se to počítalo a já udělal poslední soubor, který dal 0,7433 na veřejném žebříčku.

Vzhledem k různým přístupům k řešení problému nebyly naše předpovědi silně korelovány, což dalo dobrý nárůst v souboru. Chcete-li získat dobrý soubor, je lepší použít předpovědi surového modelu předpovědi (prediction_type='RawFormulaVal') a nastavit scale_pos_weight=neg_count/pos_count.

SNA Hackathon 2019

Na webu můžete vidět konečné výsledky v soukromém žebříčku.

Další řešení

Mnoho týmů se řídilo kánony algoritmů systému doporučení. Já, nejsem odborník v této oblasti, je nemohu hodnotit, ale pamatuji si 2 zajímavá řešení.

  • Řešení Nikolaje Anokhina. Nikolay, zaměstnanec Mail.ru, se o ceny neucházel, takže jeho cílem nebylo dosáhnout maximální rychlosti, ale získat snadno škálovatelné řešení.
  • Rozhodnutí vítězného týmu na základě ceny poroty tento článek z facebooku, umožnil velmi dobré shlukování obrazu bez ruční práce.

Závěr

Co mi nejvíce utkvělo v paměti:

  • Pokud jsou v datech kategorické prvky a víte, jak správně provést cílové kódování, je stále lepší vyzkoušet catboost.
  • Pokud se účastníte soutěže, neměli byste ztrácet čas výběrem jiných parametrů než learning_rate a iterations. Rychlejším řešením je vytvořit soubor několika modelů.
  • Zesílení se může učit na GPU. Catboost se dokáže na GPU učit velmi rychle, ale zabírá hodně paměti.
  • Během vývoje a testování nápadů je lepší nastavit malé rsm~=0.2 (pouze CPU) a ctr_complexity=1.
  • Na rozdíl od jiných týmů soubor našich modelů výrazně narostl. Jen jsme si vyměňovali nápady a psali v různých jazycích. Měli jsme jiný přístup k rozdělení dat a myslím, že každý měl své vlastní chyby.
  • Není jasné, proč optimalizace hodnocení dopadla hůře než optimalizace klasifikace.
  • Získal jsem nějaké zkušenosti s prací s texty a porozuměl tomu, jak se vytvářejí doporučovací systémy.

SNA Hackathon 2019

Děkujeme organizátorům za emoce, znalosti a obdržené ceny.

Zdroj: www.habr.com

Přidat komentář