Spark'ın ALS Öneri algoritması PlatoBlockchain Veri İstihbaratı'nı detaylandırma. Dikey Arama. Ai.

Spark'ın ALS Öneri algoritmasına giriş

Tarafından sunulan ALS algoritması Hu ve diğ., özellikle örtük veri kümelerimiz olduğunda (örneğin tıklamalar, beğeniler vb.) Öneri Sistemi problemlerinde kullanılan çok popüler bir tekniktir. Büyük hacimli verileri makul derecede iyi işleyebilir ve çeşitli Makine Öğrenimi çerçevelerinde birçok iyi uygulama bulabiliriz. Spark, kodun okunabilirliğini ve mimarisini iyileştirmek için yakın zamanda yeniden düzenlenen MLlib bileşenindeki algoritmayı içerir.

Spark'ın uygulanması, Öğe ve Kullanıcı kimliğinin tamsayı aralığında sayılar olmasını gerektirir (Tamsayı türü veya tamsayı aralığında Uzun), bu, işlemleri hızlandırmaya ve bellek tüketimini azaltmaya yardımcı olabileceği için mantıklıdır. Yine de kodu okurken fark ettiğim bir şey, bu id sütunlarının çiftlere ve ardından sığdırma / tahmin yöntemlerinin başlangıcında Tamsayılara dönüştürülmesiydi. Bu biraz karmaşık görünüyor ve çöp toplayıcısını gereksiz yere zorladığını gördüm. İşte satırlar ALS kodu kimlikleri ikiye katlayanlar:
Spark'ın ALS Öneri algoritması PlatoBlockchain Veri İstihbaratı'nı detaylandırma. Dikey Arama. Ai.
Spark'ın ALS Öneri algoritması PlatoBlockchain Veri İstihbaratı'nı detaylandırma. Dikey Arama. Ai.

Bunun neden yapıldığını anlamak için, checkCast () okunmalıdır:
Spark'ın ALS Öneri algoritması PlatoBlockchain Veri İstihbaratı'nı detaylandırma. Dikey Arama. Ai.

Bu UDF, bir Double alır ve aralığını kontrol eder ve ardından onu tam sayıya çevirir. Bu UDF, Şema doğrulaması için kullanılır. Soru, bunu çirkin çift döküm kullanmadan başarabilir miyiz? Evet inanıyorum:

  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.")
    }
  }

Yukarıdaki kod, girdiyi alan, değerin sayısal olduğunu kontrol eden ve aksi takdirde istisnalar ortaya çıkaran değiştirilmiş bir checkCast () gösterir. Girdi Any olduğundan, tüm cast to Double deyimlerini kodun geri kalanından güvenle kaldırabiliriz. Dahası, ALS tam sayı aralığında kimlik gerektirdiğinden, insanların çoğunluğunun tam sayı türlerini kullanmasını beklemek mantıklıdır. Sonuç olarak, 3. satırda bu yöntem, herhangi bir çevrim yapmaktan kaçınmak için Tamsayıları açıkça kullanır. Diğer tüm sayısal değerler için girdinin tamsayı aralığında olup olmadığını kontrol eder. Bu kontrol 7. satırda gerçekleşir.

Bunu farklı bir şekilde yazabilir ve izin verilen tüm türleri açıkça ele alabiliriz. Maalesef bu, yinelenen koda yol açacaktır. Bunun yerine, burada yaptığım şey sayıyı Tamsayıya dönüştürmek ve orijinal Sayı ile karşılaştırmak. Değerler aynıysa aşağıdakilerden biri doğrudur:

  1. Değer Byte veya Short'tur.
  2. Değer Uzun, ancak Tamsayı aralığı içinde.
  3. Değer Double veya Float şeklindedir ancak kesirli kısım yoktur.

Kodun iyi çalıştığından emin olmak için onu Spark'ın standart birim testleriyle ve çeşitli yasal ve yasadışı değerler için yöntemin davranışını kontrol ederek manuel olarak test ettim. Çözümün en az orijinal kadar hızlı olmasını sağlamak için aşağıdaki pasajı kullanarak birçok kez test ettim. Bu, ALSSuite sınıfı Spark'ta:

  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")

  }

Birkaç testten sonra, yeni düzeltmenin orijinalinden biraz daha hızlı olduğunu görebiliriz:

Kod

Koşu Sayısı

Toplam Uygulama Süresi

Çalıştırma Başına Ortalama Yürütme Süresi

orijinal 100 588.458s 5.88458s
Sabit 100 566.722s 5.66722s

Doğrulamak için deneyleri birçok kez tekrarladım ve sonuçlar tutarlı. Burada bir deneyin ayrıntılı çıktısını bulabilirsiniz. orijinal kod ve düzeltmek. Küçük bir veri kümesi için fark küçüktür, ancak geçmişte bu düzeltmeyi kullanarak GC ek yükünde gözle görülür bir azalma elde etmeyi başardım. Bunu, Spark'ı yerel olarak çalıştırarak ve Spark örneğine bir Java profil oluşturucusu ekleyerek doğrulayabiliriz. Açtım bilet ve Çekme İsteği resmi Spark deposunda ancak birleştirilip birleştirilmeyeceği belirsiz olduğu için burada sizinle paylaşmayı düşündüm ve artık Spark 2.2'nin bir parçası.

Herhangi bir düşünce, yorum veya eleştiri memnuniyetle karşılanır! 🙂

Zaman Damgası:

Den fazla Veri kutusu