Tantangan Rekko 2019: bagaimana itu



Belum lama ini, kontes sistem rekomendasi dari bioskop online Okko - Rekko Challenge 2019 diadakan di platform Boosters . Bagi saya itu adalah pengalaman pertama berpartisipasi dalam kompetisi dengan leaderboard (sebelumnya saya mencoba kekuatan hanya dalam hackathon). Tugas ini menarik dan akrab bagi saya dari latihan, ada dana hadiah, yang berarti masuk akal untuk berpartisipasi. Sebagai hasilnya, saya mengambil tempat ke-14, di mana panitia mengeluarkan kaos peringatan. Bagus Terima kasih

Dalam artikel ini, saya akan secara singkat membenamkan Anda dalam tugas, berbicara tentang hipotesis yang diajukan oleh saya, serta bagaimana menyeret kompetisi dalam sistem rekomendasi dan masuk ke 15 besar tanpa pengalaman susun, yang akan sangat berguna bagi mereka yang hanya akan berpartisipasi dalam kontes.

Sistem Rekomendasi


Tujuan utama dari sistem rekomendasi adalah untuk memberikan kepada pengguna apa yang ingin ia beli (sayangnya, pandangan hipertrofi seperti itu dikenakan pada kami oleh aplikasi komersial).
Ada berbagai pernyataan tugas (peringkat, cari yang serupa, prediksi elemen tertentu), dan, dengan demikian, cara untuk menyelesaikannya. Kita semua menyukai variabilitas dalam pilihan, yang disediakan oleh serangkaian solusi potensial untuk setiap masalah. Berbagai pendekatan dijelaskan dengan baik dalam artikel Anatomi Sistem Rekomendasi . Tentu saja, tidak ada yang membatalkan teorema NFL , yang berarti bahwa dalam masalah kompetitif kita dapat mencoba algoritma yang berbeda.

Pernyataan masalah


Baca lebih lanjut tentang tugas dan data dalam artikel oleh penyelenggara. TL; DR di sini saya akan menjelaskan minimum yang diperlukan untuk memahami konteksnya.

Dataset berisi lebih dari sepuluh ribu film dengan atribut anonim. Opsi berikut tersedia sebagai matriks interaksi item-pengguna:

  • transaksi - berisi fakta-fakta pengguna membeli konten / menyewa / melihat dengan berlangganan;
  • peringkat - peringkat film oleh pengguna;
  • bookmark - acara menambahkan film ke bookmark.

Semua informasi diambil selama periode waktu tertentu, yang disajikan dalam unit sewenang-wenang yang terhubung dengan nyata.

ts=f(tsreal)


Konten memiliki serangkaian atribut berikut:



Anda dapat membacanya secara detail di artikel oleh panitia, tetapi saya ingin segera memperhatikan apa yang menarik perhatian saya: parameter "atribut". Isinya sekantong atribut kategorikal dengan kardinalitas ~ 36 ribu. Ada rata-rata 15 nilai per film. Sekilas, hanya atribut paling dasar yang menggambarkan konten dienkripsi dalam nilai-nilai ini: aktor, sutradara, negara, langganan atau koleksi di mana film tersebut berada.

Diperlukan untuk memprediksi 20 film yang akan ditonton pengguna pengujian dalam dua bulan ke depan. Pengguna uji adalah 50 ribu dari semua 500 ribu pengguna. Di leaderboard, mereka terbagi dua: 25 ribu masing-masing di publik / swasta.

Metrik


Panitia memilih Mean Normalize Average Precision pada 20 elemen (MNAP @ 20) sebagai metrik. Perbedaan utama dari MAP biasa adalah bahwa untuk pengguna yang belum menonton 20 film dalam periode pengujian, penjatahan tidak terjadi pada k, tetapi pada nilai aktual dari film yang ditonton.



Baca lebih lanjut dan lihat kode dalam Cython di sini.

Validasi


Mendapatkan solusi masalah. Pertama-tama, perlu memutuskan apa yang divalidasi. Karena kami perlu memprediksi film di masa mendatang, kami tidak dapat melakukan gangguan sederhana oleh pengguna. Karena fakta bahwa waktu dianonimkan, saya harus setidaknya mulai menguraikannya setidaknya sekitar. Untuk melakukan ini, saya mengambil beberapa pengguna, membuat jadwal untuk transaksi dan mengungkapkan musiman tertentu. Diasumsikan bahwa itu harian, dan mengetahui perbedaan waktu antara hari-hari, kita dapat menghitung untuk periode berapa data diunggah. Ternyata ini adalah transaksi selama 6 bulan. Ini kemudian dikonfirmasi di saluran telegram di mana kontes itu dibahas.



Bagan di atas menunjukkan frekuensi transaksi per jam menggunakan data selama satu bulan sebagai contoh. Tiga puncak terkemuka setiap minggu mirip dengan Jumat malam, Sabtu, dan Minggu.

Akibatnya, kami memiliki enam bulan menonton, dan film-film perlu diprediksi untuk dua berikutnya. Kami akan menggunakan sepertiga terakhir waktu sampel pelatihan sebagai dataset validasi.



Beberapa kiriman berikutnya menunjukkan bahwa pemisahan dipilih dengan baik dan kecepatan validasi lokal berkorelasi sangat baik dengan leaderboard.

Mencoba mendanonimkan data


Untuk mulai dengan, saya memutuskan untuk mencoba mendanonimisasi semua film, sehingga:

  • menghasilkan banyak tanda dengan meta-informasi konten. Setidaknya, orang-orang berikut ini muncul dalam pikiran: genre, pemeran, masuk ke langganan, deskripsi teks, dll.
  • melemparkan tungku interaksi dari samping untuk mengurangi kekenyangan matriks. Ya, aturan kompetisi tidak melarang penggunaan data eksternal. Tentu saja, tidak ada harapan kecocokan dengan dataset terbuka, tetapi tidak ada yang membatalkan penguraian portal Rusia.

Tampaknya menjadi motivasi logis, yang, sesuai dengan harapan saya, adalah menjadi solusi fitur-pembunuh.

Pertama-tama, saya memutuskan untuk mengurai situs web Okko dan mengeluarkan semua film beserta propertinya (peringkat, durasi, batasan umur, dan lainnya). Nah, bagaimana menguraikan - semuanya ternyata cukup sederhana, dalam hal ini, Anda bisa menggunakan API:



Setelah masuk ke katalog dan memilih genre atau langganan tertentu, Anda hanya perlu masuk ke salah satu elemen. Menanggapi permintaan di atas, seluruh array film dalam genre / berlangganan dengan semua atribut jatuh. Sangat nyaman :)

Jadi atribut dari satu elemen dalam struktur terlihat
"element": { "id": "c2f98ef4-2eb5-4bfd-b765-b96589d4c470", "type": "SERIAL", "name": " ", "originalName": " ", "covers": {...}, "basicCovers": {...}, "description": "      ,    ...", "title": null, "worldReleaseDate": 1558731600000, "ageAccessType": "16", "ageAccessDescription": "16+    16 ", "duration": null, "trailers": {...}, "kinopoiskRating": 6, "okkoRating": 4, "imdbRating": null, "alias": "staraja-gvardija", "has3d": false, "hasHd": true, "hasFullHd": true, "hasUltraHd": false, "hasDolby": false, "hasSound51": false, "hasMultiAudio": false, "hasSubtitles": false, "inSubscription": true, "inNovelty": true, "earlyWindow": false, "releaseType": "RELEASE", "playbackStartDate": null, "playbackTimeMark": null, "products": { "items": [ { "type": "PURCHASE", "consumptionMode": "SUBSCRIPTION", "fromConsumptionMode": null, "qualities": [ "Q_FULL_HD" ], "fromQuality": null, "price": { "value": 0, "currencyCode": "RUB" }, "priceCategory": "679", "startDate": 1554670800000, "endDate": null, "description": null, "subscription": { "element": { "id": "bc682dc6-c0f7-498e-9064-7d6cafd8ca66", "type": "SUBSCRIPTION", "alias": "119228" } }, "offer": null, "originalPrice": null }, ... ], "emptyReason": null }, "licenses": null, "assets": {...}, "genres": { "items": [ { "element": { "id": "Detective", "type": "GENRE", "name": "", "alias": "Detective" } }, ... ], "totalSize": 2 }, "countries": { "items": [ { "element": { "id": "3b9706f4-a681-47fb-918e-182ea9dfef0b", "type": "COUNTRY", "name": "", "alias": "russia" } } ], "totalSize": 1 }, "subscriptions": { "items": [ { "element": { "id": "bc682dc6-c0f7-498e-9064-7d6cafd8ca66", "type": "SUBSCRIPTION", "name": "   ", "alias": "119228" } }, ... ], "totalSize": 7 }, "promoText": null, "innerColor": null, "updateRateDescription": null, "contentCountDescription": null, "copyright": null, "subscriptionStartDate": null, "subscriptionEndDate": null, "subscriptionActivateDate": null, "stickerText": null, "fullSeasonPriceText": null, "purchaseDate": null, "expireDate": null, "lastWatchedChildId": null, "bookmarkDate": null, "userRating": null, "consumeDate": null, "lastStartingDate": null, "watchDate": null, "startingDate": null, "earlyWatchDate": null } 


Masih harus melalui semua genre, parse JSON, dan kemudian memungkinkan duplikat, karena satu film dapat menjadi milik beberapa genre / langganan.

Ya, di sini saya beruntung dan saya menghemat banyak waktu. Jika ini bukan kasus Anda, dan Anda perlu mengurai konten html, maka ada artikel di hub yang dapat membantu dengan ini, misalnya, di sini .

"Masalahnya adalah topinya," pikirku, "kita hanya bisa menahannya." "Intinya adalah topinya," aku menyadari keesokan harinya: data tidak sepenuhnya cocok. Tentang itu di bawah ini.

Pertama, ukuran katalog secara signifikan berbeda: dalam dataset - 10.200, dikumpulkan dari situs - 8870. Ini mengikuti dari historisitas dataset: itu hanya diunduh apa yang ada di situs sekarang, dan data kompetisi untuk 2018. Beberapa film menjadi tidak tersedia. Ups

Kedua, dari atribut potensial untuk pertandingan adalah intuisi gigih hanya tentang hal-hal berikut:

feature5 - batas usia. Cukup mudah dimengerti. Kardinalitas atribut adalah 5 nilai float unik dan “-1”. Di antara data yang dikumpulkan, atribut "ageAccessType" ditemukan hanya dengan kardinalitas 5. Pemetaan tampak seperti ini:

 catalogue.age_rating = catalogue.age_rating.map({0: 0, 0.4496666915: 6 0.5927161087: 12 0.6547073468: 16 0.6804096966000001: 18}) 

feature2 - nilai film yang dikonversi dari pencarian film. Awalnya, pada tahap EDA, gagasan bahwa kita berurusan dengan peringkat diajukan oleh korelasi parameter dengan jumlah total tampilan. Selanjutnya, keyakinan bahwa peringkat ini berasal dari pencarian film mengkonfirmasi keberadaan parameter “kinopoiskRating” di data situs.

Selangkah lebih dekat ke pertandingan! Sekarang tinggal mencari cara untuk membalikkan konversi untuk parameter fitur2 yang disajikan dalam bentuk anonim.

Beginilah distribusi nilai-nilai terlihat di feature2 :



Dan distribusi nilai parameter kinopoiskRating :



Ketika saya menunjukkan gambar-gambar ini kepada rekan saya Sasha, dia segera melihat bahwa ini adalah tingkat tiga. Tiga ahli matematika tidak dihormati, tetapi angka Pi bahkan genap. Hasilnya, ternyata seperti ini:



Tampaknya semuanya, tetapi tidak cukup. Kami melihat distribusi yang identik, tetapi nilai nominal dan kuantitasnya masih belum bertemu. Jika kita tahu sejumlah contoh perbandingan, itu hanya akan tetap mendekati fungsi linear untuk menemukan faktornya. Tetapi kami tidak memilikinya.

Ngomong-ngomong, perkiraan bukanlah kata yang paling cocok. Kami membutuhkan solusi dengan kesalahan yang hampir sama dengan nol. Akurasi dalam data yang dikumpulkan adalah 2 karakter setelah pemisah. Jika Anda menganggap bahwa ada banyak film dengan peringkat 6.xx dan ada film dengan peringkat yang sama, maka Anda harus memperjuangkan ketepatan di sini.

Apa lagi yang bisa Anda coba? Anda dapat mengandalkan nilai minimum dan maksimum dan menggunakan MinMaxScaler, tetapi tidak dapat diandalkannya metode ini segera menimbulkan keraguan. Biarkan saya mengingatkan Anda bahwa jumlah film awalnya tidak bersamaan, dan dataset kami adalah historis, dan keadaan saat ini di situs. Yaitu tidak ada jaminan bahwa film-film dengan peringkat minimum dan maksimum di kedua kelompok adalah identik (ternyata mereka memiliki batasan usia yang berbeda, dan durasinya tidak menyatu dari kata "sepenuhnya"), serta tidak ada pemahaman tentang seberapa sering pembaruan OKKO di API setiap hari mengubah peringkat pencarian film.

Jadi menjadi jelas bahwa saya membutuhkan lebih banyak kandidat atribut untuk pencocokan.

Apa lagi yang menarik?

feature1 adalah semacam tanggal. Secara teori, untuk tanggal, penyelenggara menjanjikan pelestarian pemisahan negara, yang menyiratkan fungsi linier. Secara umum, transformasi seharusnya identik dengan atribut ts untuk matriks interaksi. Jika Anda melihat distribusi film oleh feature_1 ...



... maka hipotesis tanggal rilis film segera tersapu. Intuition menunjukkan bahwa jumlah film yang diproduksi oleh industri harus meningkat dari waktu ke waktu. Ini dikonfirmasi di bawah ini.

Ada 14 atribut dalam data yang kami terima dari situs. Bahkan, sayangnya, nilai-nilai itu hanya berisi untuk yang berikut:







Tidak ada contoh di atas yang mirip dengan fitur_1 . Yah, tidak ada lagi ide untuk perbandingan, dan tampaknya semua kerepotan dengan tugas ini sia-sia. Tentu saja, saya mendengar tentang kontes, di mana orang-orang menandai data secara manual, tetapi tidak ketika datang ke ratusan dan ribuan salinan.

Solusi


1. Model sederhana

Menyadari bahwa tidak semuanya sederhana, saya mulai bekerja dengan rendah hati dengan apa yang ada. Sebagai permulaan, saya ingin memulai dengan yang sederhana. Saya mencoba beberapa solusi yang sering digunakan dalam industri ini, yaitu: pemfilteran kolaboratif (berdasarkan item kasus kami) dan faktorisasi matriks .

Komunitas menggunakan banyak pustaka python yang cocok untuk tugas-tugas ini: implisit dan LightFM . Yang pertama mampu memfaktorkan berdasarkan ALS, serta Penyaringan Kolaboratif Tetangga Terdekat dengan beberapa opsi untuk memproses ulang item-item matriks. Yang kedua memiliki dua faktor khas:

  • Factorisasi didasarkan pada SGD, yang memungkinkan untuk menggunakan fungsi kerugian berbasis pengambilan sampel, termasuk WARP .
  • Ini menggunakan pendekatan hybrid, menggabungkan informasi tentang atribut pengguna dan item dalam model sedemikian rupa sehingga vektor laten pengguna adalah jumlah dari vektor laten atributnya. Dan juga untuk item. Pendekatan ini menjadi sangat nyaman ketika ada masalah mulai dingin untuk pengguna / item.

Kami tidak memiliki atribut pengguna (selain dari kemampuan untuk menampilkannya berdasarkan interaksi dengan film), jadi saya hanya menggunakan atribut item.

Secara total, 6 pengaturan pergi untuk menghitung parameter. Kombinasi tiga matriks digunakan sebagai matriks interaksi, di mana peringkat diubah menjadi biner. Hasil komparatif dengan hyperparameter terbaik untuk setiap pengaturan pada tabel di bawah ini.
ModelTes MNAP @ 20
ALS implisit0,02646
Cosinus tersirat kNN CF0,03170
TFIDF kNN CF Implisit0,03113
LightFM (tanpa fitur item), kehilangan BPR0,02567
LightFM (tanpa fitur item), kehilangan WARP0,02632
LightFM dengan fitur item, kehilangan WARP0,02635

Seperti yang Anda lihat, pemfilteran kolaboratif klasik telah terbukti jauh lebih baik daripada model lain. Tidak sempurna, tetapi banyak yang tidak diperlukan dari garis dasar. Kirim dengan konfigurasi ini memberi 0,03048 di leaderboard publik. Saya tidak ingat posisi pada waktu itu, tetapi pada saat penutupan kompetisi, pengajuan ini pasti akan mencapai puncak 80 dan memberikan medali perunggu.

2. Hello Boosting

Apa yang bisa lebih baik dari satu model? Benar: beberapa model.

Oleh karena itu, opsi berikutnya adalah ansambel atau, dalam konteks rekomendasi, menentukan peringkat model tingkat kedua. Sebagai pendekatan, saya mengambil artikel ini dari orang-orang dari Avito. Tampaknya memasak ketat sesuai resep, diaduk secara berkala dan dibumbui dengan atribut film. Satu-satunya penyimpangan adalah jumlah kandidat: saya mengambil 200 teratas dari LightFM, karena dengan 500.000 pengguna, lebih tepatnya tidak sesuai dengan memori.

Akibatnya, kecepatan yang saya peroleh pada validasi lebih buruk daripada pada satu model.

Setelah beberapa hari bereksperimen, muncul kesadaran bahwa tidak ada yang berhasil dan tidak ada yang berhasil sendiri. Atau saya tidak tahu cara memasaknya (spoiler: jawaban kedua yang benar). Apa yang saya lakukan salah? Ada dua alasan yang muncul:

  • Di satu sisi, mengambil 200 teratas dari model tingkat pertama masuk akal dari sudut pandang menghasilkan sampel "negatif keras", yaitu film-film yang juga relevan bagi pengguna, tetapi tidak ditonton olehnya. Di sisi lain, beberapa film ini dapat ditonton selama periode pengujian, dan kami menyajikan contoh-contoh ini sebagai negatif. Selanjutnya, saya memutuskan untuk mengurangi risiko dari fakta ini, memeriksa ulang hipotesis dengan eksperimen berikut: Saya mengambil semua contoh positif + acak untuk sampel pelatihan. Kecepatan pada sampel uji tidak meningkat. Di sini perlu untuk mengklarifikasi bahwa dalam pengambilan sampel pada tes ada juga prediksi teratas dari model tingkat pertama, karena pada leaderboard tidak ada yang akan memberi tahu saya semua contoh positif.
  • Dari 10.200 film yang tersedia dalam katalog, hanya 8.296 film yang melakukan interaksi. Hampir 2.000 film kehilangan perhatian pengguna, sebagian karena tidak tersedia untuk pembelian / sewa / sebagai bagian dari berlangganan. Orang-orang dalam obrolan bertanya apakah film yang tidak dapat diakses dapat tersedia pada periode pengujian. Jawabannya adalah ya. Jelas tidak mungkin untuk membuang mereka. Jadi, saya menyarankan bahwa hampir 2.000 film lagi akan tersedia dalam 2 bulan ke depan. Kalau tidak, mengapa membuangnya ke dalam dataset?

3. Neuron

Dari paragraf sebelumnya, muncul pertanyaan: bagaimana kita bisa bekerja dengan film yang tidak ada interaksi sama sekali? Ya, kami mengingat fitur item di LightFM, tetapi seperti yang kami ingat, mereka tidak masuk. Apa lagi Neuron!

Arsenal open source memiliki beberapa perpustakaan tingkat tinggi yang cukup populer untuk bekerja dengan sistem rekomendasi: Spotlight dari Maciej Kula (penulis LightFM) dan TensorRec . Yang pertama di bawah tenda PyTorch, yang kedua - Tensorflow.

Spotlight dapat memfaktisasi pada dataset implisit / eksplisit dengan neuron dan urutan model. Pada saat yang sama, dalam faktorisasi "di luar kotak" tidak ada cara untuk menambahkan fitur pengguna / item, jadi jatuhkan.

TensorRec, sebaliknya, hanya tahu cara memfaktorkan dan merupakan kerangka kerja yang menarik:

  • grafik representasi - metode transformasi (dapat diatur berbeda untuk pengguna / item) dari data input dalam penyematan, berdasarkan pada perhitungan mana dalam grafik prediksi akan terjadi. Pilihan terdiri dari lapisan dengan opsi aktivasi berbeda. Dimungkinkan juga untuk menggunakan kelas abstrak dan menempel pada transformasi kustom, yang terdiri dari urutan lapisan keras.
  • grafik prediksi memungkinkan Anda untuk memilih operasi di akhir: titik produk favorit Anda, jarak euclidean dan kosinus.
  • Kerugian - ada juga banyak untuk dipilih. Kami senang dengan implementasi WMRB (pada dasarnya WARP yang sama, hanya tahu cara belajar batch dan didistribusikan)

Yang paling penting, TensorRec dapat bekerja dengan fitur kontekstual, dan memang penulis mengakui bahwa ia awalnya terinspirasi oleh ide LightFM. Baiklah, mari kita lihat. Kami mengambil interaksi (hanya transaksi) dan fitur barang.

Kami mengirim untuk mencari berbagai konfigurasi dan menunggu. Dibandingkan dengan LightFM, pelatihan dan validasi membutuhkan waktu lama.

Selain itu, ada beberapa ketidaknyamanan yang harus dihadapi:

  1. Dari mengubah flag verbose, metode fit tidak mengubah apa pun dan tidak ada callback yang disediakan untuk Anda. Saya harus menulis fungsi yang secara internal melatih satu era menggunakan metode fit_partial , dan kemudian menjalankan validasi untuk kereta dan tes (dalam kedua kasus, sampel digunakan untuk mempercepat proses).
  2. Secara umum, penulis kerangka kerja adalah orang hebat dan memanfaatkan tf.SparseTensor di mana-mana. Namun, perlu dipahami bahwa sebagai prediksi, termasuk untuk validasi, Anda mendapatkan hasil yang padat sebagai vektor dengan panjang n_item untuk setiap pengguna. Dua tips berikut dari ini: membuat siklus untuk menghasilkan prediksi dengan batch (metode perpustakaan tidak memiliki parameter seperti itu) dengan penyaringan top-k dan menyiapkan strip dengan RAM.

Pada akhirnya, pada opsi konfigurasi terbaik, saya berhasil memeras 0,02869 pada sampel pengujian saya. Ada sesuatu yang mirip dengan LB.

Nah, apa yang saya harapkan? Bahwa penambahan fitur non-linearitas ke item akan memberikan peningkatan dua kali lipat dalam metrik? Itu naif.

4. Beck tugas itu

Jadi tunggu sebentar. Tampaknya saya kembali bertemu neuron juggling. Hipotesis apa yang ingin saya uji ketika saya memulai bisnis ini? Hipotesisnya adalah: “Dalam 2 bulan berikutnya dari pemilihan yang tertunda, hampir 2.000 film baru akan terlihat di papan peringkat. Beberapa dari mereka akan memiliki banyak pandangan. "

Jadi Anda bisa memeriksanya dalam 2 langkah:

  1. Akan menyenangkan untuk melihat berapa banyak film yang kami tambahkan dalam pemisahan yang jujur ​​oleh kami tentang masa kereta. Jika kita hanya menonton, maka film "baru" hanya 240 (!). Hipotesis segera bergetar. Tampaknya pembelian konten baru tidak dapat berbeda dengan jumlah itu dari periode ke periode.
  2. Kita selesai. Kami memiliki kesempatan untuk melatih model agar hanya menggunakan presentasi berdasarkan fitur-fitur item (dalam LightFM, misalnya, ini dilakukan secara default, jika kami tidak melakukan pra-populasi matriks atribut dengan matriks identitas). Lebih lanjut, untuk infeness, kami dapat mengirimkan ke model ini saja (!) Film-film kami yang tidak dapat diakses dan tidak pernah dilihat. Dari hasil ini, kami kirim dan dapatkan 0,0000136.

Bingo! Ini berarti Anda dapat berhenti menekan semantik dari atribut film. Ngomong-ngomong, kemudian, di DataFest, orang-orang dari OKKO mengatakan bahwa sebagian besar konten yang tidak dapat diakses hanyalah beberapa film lama.

Anda perlu mencoba sesuatu yang baru, dan baru - lama terlupakan. Dalam kasus kami, tidak sepenuhnya dilupakan, tetapi apa yang terjadi beberapa hari yang lalu. Penyaringan kolaboratif?

5. Setel garis dasar

Bagaimana saya bisa membantu baseline CF?

Ide nomor 1

Di Internet, saya menemukan presentasi tentang menggunakan uji rasio kemungkinan untuk menyaring film-film kecil.

Di bawah ini saya akan meninggalkan kode python saya untuk menghitung skor LLR, yang harus saya tulis di atas lutut saya untuk menguji ide ini.

Perhitungan LLR
 import numpy as np from scipy.sparse import csr_matrix from tqdm import tqdm class LLR: def __init__(self, interaction_matrix, interaction_matrix_2=None): interactions, lack_of_interactions = self.make_two_tables(interaction_matrix) if interaction_matrix_2 is not None: interactions_2, lack_of_interactions_2 = self.make_two_tables(interaction_matrix_2) else: interactions_2, lack_of_interactions_2 = interactions, lack_of_interactions self.num_items = interaction_matrix.shape[1] self.llr_matrix = np.zeros((self.num_items, self.num_items)) # k11 - item-item co-occurrence self.k_11 = np.dot(interactions, interactions_2.T) # k12 - how many times row elements was bought without column elements self.k_12 = np.dot(interactions, lack_of_interactions_2.T) # k21 - how many times column elements was bought without row elements self.k_21 = np.dot(lack_of_interactions, interactions_2.T) # k22 - how many times elements was not bought together self.k_22 = np.dot(lack_of_interactions, lack_of_interactions_2.T) def make_two_tables(self, interaction_matrix): interactions = interaction_matrix if type(interactions) == csr_matrix: interactions = interactions.todense() interactions = np.array(interactions.astype(bool).T) lack_of_interactions = ~interactions interactions = np.array(interactions, dtype=np.float32) lack_of_interactions = np.array(lack_of_interactions, dtype=np.float32) return interactions, lack_of_interactions def entropy(self, k): N = np.sum(k) return np.nansum((k / N + (k == 0)) * np.log(k / N)) def get_LLR(self, item_1, item_2): k = np.array([[self.k_11[item_1, item_2], self.k_12[item_1, item_2]], [self.k_21[item_1, item_2], self.k_22[item_1, item_2]]]) LLR = 2 * np.sum(k) * (self.entropy(k) - self.entropy(np.sum(k, axis=0)) - self.entropy(np.sum(k, axis=1))) return LLR def compute_llr_matrix(self): for item_1 in range(self.num_items): for item_2 in range(item_1, self.num_items): self.llr_matrix[item_1, item_2] = self.get_LLR(item_1, item_2) if item_1 != item_2: self.llr_matrix[item_2, item_1] = self.llr_matrix[item_1, item_2] def get_top_n(self, n=100, mask=False): filtered_matrix = self.llr_matrix.copy() for i in tqdm(range(filtered_matrix.shape[0])): ind = np.argpartition(filtered_matrix[i], -n)[-n:] filtered_matrix[i][[x for x in range(filtered_matrix.shape[0]) if x not in ind]] = 0 if mask: return filtered_matrix != 0 else: return filtered_matrix 


Akibatnya, matriks yang dihasilkan dapat digunakan sebagai masker untuk hanya meninggalkan interaksi yang paling signifikan dan ada dua opsi: gunakan ambang batas atau biarkan elemen top-k dengan nilai tertinggi. Dengan cara yang sama, kombinasi beberapa pengaruh pada pembelian dalam satu kecepatan digunakan untuk memberi peringkat item, dengan kata lain, tes menunjukkan betapa pentingnya, misalnya, menambahkan ke favorit tentang kemungkinan konversi ke pembelian. Tampaknya menjanjikan, tetapi penggunaan menunjukkan bahwa pemfilteran menggunakan skor LLR memberikan peningkatan yang sangat mini, dan menggabungkan beberapa skor hanya memperburuk hasilnya. Tampaknya, metode ini bukan untuk data ini. Dari plus, saya hanya dapat mencatat bahwa, sementara mencari tahu bagaimana menerapkan ide ini, saya harus menggali yang tersirat di bawah tenda.

Contoh penerapan logika khusus ini secara implisit akan ditinggalkan di bawah kucing.

Modifikasi matriks secara implisit
 #   LLR scores.     n_items * n_items. llr = LLR(train_csr, train_csr) llr.compute_llr_matrix() #     id, ,      llr_based_mask = llr.get_top_n(n=500, mask=True) #     ,  - CosineRecommender. model = CosineRecommender(K=10200) model.fit(train_csr.T) # model.similarity -   co-occurrence (  Cosine - ).        . masked_matrix = np.array(model.similarity.todense()) * llr_based_mask #  fit()  scorer     model.similarity. #     . model.scorer = NearestNeighboursScorer(csr_matrix(masked_matrix)) #      :   recommend   . test_predict = {} for id_ in tqdm(np.unique(test_csr.nonzero()[0])): test_predict[id_] = model.recommend(id_, train_csr, filter_already_liked_items=True, N=20) #   tuples (item_id, score),   id  . test_predict_ids = {k: [x[0] for x in v] for k, v in test_predict.items()} #       / ,     ,   Cython. 


Ide nomor 2

Gagasan lain muncul yang dapat meningkatkan prediksi tentang penyaringan kolaboratif sederhana. Industri sering menggunakan semacam fungsi redaman untuk perkiraan lama, tetapi kami memiliki kasus yang tidak begitu sederhana. Mungkin, Anda perlu mempertimbangkan 2 kemungkinan kasus:

  1. Pengguna yang menonton layanan ini kebanyakan baru
  2. "Penguji." Artinya, mereka yang datang relatif baru dan dapat menonton film-film yang sebelumnya populer.

Dengan demikian, dimungkinkan untuk membagi komponen kolaboratif menjadi dua kelompok yang berbeda secara otomatis. Untuk melakukan ini, saya menemukan fungsi yang alih-alih nilai implisit diatur ke "1" atau "0" di persimpangan pengguna dan film, nilai yang menunjukkan betapa pentingnya film ini dalam riwayat menonton pengguna.

confidence(film)=αStartTime+βΔWatchTime


di mana StartTime adalah tanggal rilis film, dan ΔWatchTime adalah perbedaan antara tanggal rilis dan tanggal pengguna menonton, dan α dan β adalah hiperparameter.

Di sini, istilah pertama bertanggung jawab untuk meningkatkan kecepatan film yang baru saja dirilis, dan yang kedua adalah untuk memperhitungkan kecanduan pengguna terhadap film-film lama. Jika sebuah film dirilis sejak lama, dan pengguna langsung membelinya, maka hari ini, fakta ini seharusnya tidak diperhitungkan banyak. Jika ini semacam hal baru, maka kami harus merekomendasikannya kepada lebih banyak pengguna yang juga menonton item baru. Jika filmnya cukup kuno, dan pengguna hanya menontonnya sekarang, maka ini bisa juga penting bagi mereka yang menyukainya.

Ada sedikit kiri - untuk memilah koefisien α dan β . Tinggalkan malam itu, dan pekerjaan selesai. Tidak ada asumsi tentang rentang sama sekali, sehingga awalnya besar, dan di bawah ini adalah hasil pencarian optimal lokal.



Menggunakan ide ini, model sederhana pada satu matriks memberikan kecepatan 0,03627 pada validasi lokal dan 0,03685 pada LB publik, yang segera terlihat seperti dorongan yang baik dibandingkan dengan hasil sebelumnya. Pada saat itu, itu membawa saya ke 20 besar.

№3

, , , . CF . :



, 25 .

, , 0.040312 , 0.03870 0.03990 public/private 14- .

Acknowledgments


jupyter notebook — . , . output . . cookiecutter-data-science — Ocean. . . .

( )


private , . , . :



, , , :



, 2 . . , .



Kesimpulan


— . , . ? , — feature importance , , «» . , .

, . ; , , .

Source: https://habr.com/ru/post/id461055/


All Articles