Sparki ALS-i soovituse algoritmi PlatoBlockchain Data Intelligence uurimine. Vertikaalne otsing. Ai.

Sparki ALS-i soovituse algoritmi uurimine

ALS-i algoritm, mille tutvustas Hu jt., on väga populaarne tehnika, mida kasutatakse Recommender Systemi probleemide korral, eriti kui meil on kaudseid andmekogumeid (nt klikid, meeldimised jne). See saab suhteliselt hästi hakkama suurte andmemahtudega ja erinevatest masinõppe raamistikest leiame palju häid rakendusi. Spark sisaldab MLlib-komponendis algoritmi, mis on hiljuti ümber kujundatud, et parandada koodi loetavust ja arhitektuuri.

Sparki juurutamine nõuab, et üksus ja kasutaja ID oleksid täisarvude vahemikus (kas täisarvu tüüp või pikk täisarvu vahemikus), mis on mõistlik, kuna see võib toiminguid kiirendada ja mälukasutust vähendada. Üks asi, mida ma koodi lugedes märkasin, on see, et need ID-veerud kantakse sobitus-/ennustusmeetodite alguses kahekordseteks ja seejärel täisarvudeks. See tundub natuke hakitav ja olen näinud, et see on prügikoristajale asjatult koormanud. Siin on jooned ALS-kood mis panevad ID-d kahekordseks:
Sparki ALS-i soovituse algoritmi PlatoBlockchain Data Intelligence uurimine. Vertikaalne otsing. Ai.
Sparki ALS-i soovituse algoritmi PlatoBlockchain Data Intelligence uurimine. Vertikaalne otsing. Ai.

Et mõista, miks seda tehakse, tuleb lugeda checkedCast():
Sparki ALS-i soovituse algoritmi PlatoBlockchain Data Intelligence uurimine. Vertikaalne otsing. Ai.

See UDF võtab vastu Double ja kontrollib selle vahemikku ning seejärel heidab selle täisarvuks. Seda UDF-i kasutatakse skeemi valideerimiseks. Küsimus on selles, kas me saame seda saavutada ilma koledaid topeltvalandeid kasutamata? Usun jah:

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

Ülaltoodud kood näitab muudetud checkedCast(), mis võtab vastu sisendi, kontrollib, kas väärtus on numbriline, ja esitab muul juhul erandeid. Kuna sisend on suvaline, saame ülejäänud koodist ohutult eemaldada kõik Double-laused. Lisaks on mõistlik eeldada, et kuna ALS nõuab täisarvude vahemikus olevaid ID-sid, kasutab enamik inimesi tegelikult täisarvude tüüpe. Selle tulemusel käsitleb see meetod real 3 selgesõnaliselt täisarve, et vältida ülekandmist. Kõigi muude arvväärtuste puhul kontrollib see, kas sisend on täisarvude vahemikus. See kontroll toimub real 7.

Selle võiks kirjutada teisiti ja käsitleda selgelt kõiki lubatud tüüpe. Kahjuks tooks see kaasa koodi dubleerimise. Selle asemel teisen ma arvu täisarvuks ja võrdlen seda algse numbriga. Kui väärtused on identsed, on tõene üks järgmistest:

  1. Väärtus on bait või lühike.
  2. Väärtus on pikk, kuid jääb täisarvude vahemikku.
  3. Väärtus on Double või Float, kuid ilma murdosata.

Koodi hea toimimise tagamiseks testisin seda Sparki standardsete ühikutestidega ja käsitsi, kontrollides meetodi käitumist erinevate seaduslike ja ebaseaduslike väärtuste suhtes. Tagamaks, et lahendus oleks vähemalt sama kiire kui originaal, katsetasin mitmeid kordi, kasutades allolevat väljavõtet. Selle saab paigutada ALSSuite klass Sparkis:

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

  }

Pärast mõnda katset näeme, et uus parandus on veidi kiirem kui originaal:

kood

Jooksude arv

Täitmise koguaeg

Keskmine täitmisaeg jooksu kohta

Originaal 100 588.458s 5.88458s
Fikseeritud 100 566.722s 5.66722s

Kordasin katseid mitu korda, et kinnitada ja tulemused on järjepidevad. Siit leiate ühe katse üksikasjaliku väljundi originaalkood ja määrata. Erinevus on väikese andmestiku puhul väike, kuid varem on mul õnnestunud seda parandust kasutades GC üldkulusid märgatavalt vähendada. Seda saame kinnitada, käivitades Sparki kohapeal ja lisades Sparki eksemplarile Java-profiili. Avasin a pilet ja Tõmba-taotlus ametlikul Spark repos kuid kuna pole kindel, kas see liidetakse, mõtlesin seda siin teiega jagada ja see on nüüd osa Spark 2.2-st.

Kõik mõtted, kommentaarid või kriitika on teretulnud! 🙂

Ajatempel:

Veel alates Datumbox