Mengebor ke dalam algoritma Rekomendasi ALS Spark, PlatoBlockchain Data Intelligence. Pencarian Vertikal. Ai.

Mengebor ke dalam algoritma ALS Recommendation Spark

Algoritme ALS diperkenalkan oleh Hu et al., adalah teknik yang sangat populer digunakan dalam masalah Sistem Rekomendasi, terutama ketika kita memiliki kumpulan data implisit (misalnya klik, suka, dll). Ini dapat menangani volume data yang besar dengan cukup baik dan kami dapat menemukan banyak implementasi yang baik di berbagai kerangka kerja Machine Learning. Spark menyertakan algoritme dalam komponen MLlib yang baru-baru ini telah direfraktorisasi untuk meningkatkan keterbacaan dan arsitektur kode.

Implementasi Spark mengharuskan Item dan User id menjadi angka dalam rentang integer (baik tipe Integer atau Long dalam rentang integer), yang wajar karena ini dapat membantu mempercepat operasi dan mengurangi konsumsi memori. Satu hal yang saya perhatikan saat membaca kode adalah bahwa kolom id tersebut dicor menjadi Ganda dan kemudian menjadi Integer pada awal metode fit / prediksi. Ini sepertinya agak hack dan saya telah melihatnya memberikan tekanan yang tidak perlu pada pengumpul sampah. Berikut adalah garis-garis di Kode ALS yang mengubah id menjadi ganda:
Mengebor ke dalam algoritma Rekomendasi ALS Spark, PlatoBlockchain Data Intelligence. Pencarian Vertikal. Ai.
Mengebor ke dalam algoritma Rekomendasi ALS Spark, PlatoBlockchain Data Intelligence. Pencarian Vertikal. Ai.

Untuk memahami mengapa ini dilakukan, seseorang perlu membaca checkCast ():
Mengebor ke dalam algoritma Rekomendasi ALS Spark, PlatoBlockchain Data Intelligence. Pencarian Vertikal. Ai.

UDF ini menerima Double dan memeriksa jangkauannya dan kemudian memasukkannya ke integer. UDF ini digunakan untuk validasi Skema. Pertanyaannya adalah dapatkah kita mencapai ini tanpa menggunakan coran ganda yang jelek? Saya yakin ya:

  protected val checkedCast = udf { (n: Any) =>
    n match {
      case v: Int => v // Avoid unnecessary casting
      case v: Number =>
        val intV = v.intValue()
        // True for Byte/Short, Long within the Int range and Double/Float with no fractional part.
        if (v.doubleValue == intV) {
          intV
        }
        else {
          throw new IllegalArgumentException(s"ALS only supports values in Integer range " +
            s"for columns ${$(userCol)} and ${$(itemCol)}. Value $n was out of Integer range.")
        }
      case _ => throw new IllegalArgumentException(s"ALS only supports values in Integer range " +
        s"for columns ${$(userCol)} and ${$(itemCol)}. Value $n is not numeric.")
    }
  }

Kode di atas menunjukkan checkCast () yang dimodifikasi yang menerima masukan, pemeriksaan menegaskan bahwa nilainya adalah numerik dan memunculkan pengecualian jika tidak. Karena inputnya adalah Any, kami dapat dengan aman menghapus semua cast ke pernyataan Double dari kode lainnya. Selain itu, masuk akal untuk mengharapkan bahwa karena ALS memerlukan id dalam kisaran integer, mayoritas orang benar-benar menggunakan tipe integer. Akibatnya pada baris 3, metode ini menangani Bilangan bulat secara eksplisit untuk menghindari melakukan transmisi apa pun. Untuk semua nilai numerik lainnya, ia memeriksa apakah input berada dalam kisaran integer. Pemeriksaan ini terjadi di baris 7.

Seseorang dapat menulis ini secara berbeda dan secara eksplisit menangani semua jenis yang diizinkan. Sayangnya ini akan menyebabkan kode duplikat. Sebaliknya apa yang saya lakukan di sini adalah mengubah nomor menjadi Integer dan membandingkannya dengan Nomor aslinya. Jika nilainya identik, salah satu dari berikut ini benar:

  1. Nilainya Byte atau Short.
  2. Nilainya Panjang tetapi dalam kisaran Integer.
  3. Nilainya Double atau Float tetapi tanpa bagian pecahan apa pun.

Untuk memastikan bahwa kode berjalan dengan baik, saya mengujinya dengan tes unit standar Spark dan secara manual dengan memeriksa perilaku metode untuk berbagai nilai legal dan ilegal. Untuk memastikan bahwa solusinya setidaknya secepat aslinya, saya menguji berkali-kali menggunakan potongan di bawah ini. Ini dapat ditempatkan di Kelas ALSSuite di Spark:

  test("Speed difference") {
    val (training, test) =
      genExplicitTestData(numUsers = 200, numItems = 400, rank = 2, noiseStd = 0.01)

    val runs = 100
    var totalTime = 0.0
    println("Performing "+runs+" runs")
    for(i <- 0 until runs) {
      val t0 = System.currentTimeMillis
      testALS(training, test, maxIter = 1, rank = 2, regParam = 0.01, targetRMSE = 0.1)
      val secs = (System.currentTimeMillis - t0)/1000.0
      println("Run "+i+" executed in "+secs+"s")
      totalTime += secs
    }
    println("AVG Execution Time: "+(totalTime/runs)+"s")

  }

Setelah beberapa pengujian, kita dapat melihat bahwa perbaikan baru sedikit lebih cepat daripada yang asli:

Kode

Jumlah Proses

Total Waktu Eksekusi

Waktu Eksekusi Rata-rata per Proses

Original 100 588.458s 5.88458s
Tetap 100 566.722s 5.66722s

Saya mengulangi percobaan beberapa kali untuk mengonfirmasi dan hasilnya konsisten. Di sini Anda dapat menemukan hasil mendetail dari satu eksperimen untuk kode asli dan memperbaiki. Perbedaannya kecil untuk kumpulan data kecil tetapi di masa lalu saya telah berhasil mencapai pengurangan yang nyata dalam overhead GC menggunakan perbaikan ini. Kami dapat mengonfirmasi ini dengan menjalankan Spark secara lokal dan melampirkan profiler Java pada instance Spark. Saya membuka tiket dan Tarik-Permintaan di repo resmi Spark tapi karena belum pasti apakah akan digabung, saya pikir akan share disini dengan anda dan sekarang menjadi bagian dari Spark 2.2.

Setiap pemikiran, komentar atau kritik dipersilakan! 🙂

Stempel Waktu:

Lebih dari kotak data