เจาะลึกอัลกอริทึมการแนะนำ ALS ของ Spark PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

เจาะลึกอัลกอริทึม ALS Recommendation ของ Spark

อัลกอริทึม ALS ที่แนะนำโดย หูและคณะเป็นเทคนิคที่นิยมใช้กันมากในปัญหาระบบผู้แนะนำ โดยเฉพาะอย่างยิ่งเมื่อเรามีชุดข้อมูลโดยปริยาย (เช่น การคลิก การชอบ ฯลฯ) สามารถจัดการกับข้อมูลปริมาณมากได้อย่างสมเหตุสมผล และเราสามารถพบการใช้งานที่ดีมากมายในเฟรมเวิร์กการเรียนรู้ของเครื่องต่างๆ Spark รวมอัลกอริธึมในองค์ประกอบ MLlib ซึ่งเพิ่งได้รับการปรับโครงสร้างใหม่เพื่อปรับปรุงความสามารถในการอ่านและสถาปัตยกรรมของโค้ด

การใช้งานของ Spark ต้องการให้ Item และ User id เป็นตัวเลขที่อยู่ในช่วงจำนวนเต็ม (ไม่ว่าจะเป็นประเภท Integer หรือ Long ภายในช่วงจำนวนเต็ม) ซึ่งสมเหตุสมผลเพราะสามารถช่วยเพิ่มความเร็วในการดำเนินการและลดการใช้หน่วยความจำได้ สิ่งหนึ่งที่ฉันสังเกตเห็นในขณะที่อ่านโค้ดคือคอลัมน์ id เหล่านั้นกำลังถูกแคสต์เป็น Doubles แล้วจึงเปลี่ยนเป็น Integers ที่จุดเริ่มต้นของวิธี fit/predict ดูเหมือนว่าแฮ็คเล็กน้อยและฉันเห็นว่ามันทำให้เกิดความเครียดที่ไม่จำเป็นกับตัวรวบรวมขยะ นี่คือบรรทัดบน รหัส ALS ที่หล่อไอดีเป็นสองเท่า:
เจาะลึกอัลกอริทึมการแนะนำ ALS ของ Spark PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.
เจาะลึกอัลกอริทึมการแนะนำ ALS ของ Spark PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

เพื่อให้เข้าใจว่าเหตุใดจึงเสร็จสิ้น เราต้องอ่านเช็คแคสต์ ():
เจาะลึกอัลกอริทึมการแนะนำ ALS ของ Spark PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

UDF นี้ได้รับ Double และตรวจสอบช่วงแล้วเปลี่ยนเป็นจำนวนเต็ม UDF นี้ใช้สำหรับการตรวจสอบความถูกต้องของสคีมา คำถามคือเราสามารถทำสิ่งนี้ให้สำเร็จได้โดยไม่ต้องใช้การหล่อสองครั้งที่น่าเกลียดหรือไม่? ฉันเชื่อว่าใช่:

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

โค้ดด้านบนแสดงcheckedCast() ที่แก้ไขแล้วซึ่งรับอินพุต การตรวจสอบยืนยันว่าค่าเป็นตัวเลขและทำให้เกิดข้อยกเว้นเป็นอย่างอื่น เนื่องจากอินพุตเป็น Any เราจึงสามารถลบคำสั่ง cast to Double ทั้งหมดออกจากโค้ดที่เหลือได้อย่างปลอดภัย นอกจากนี้ มีเหตุผลที่จะคาดหวังว่าเนื่องจาก ALS ต้องการรหัสภายในช่วงจำนวนเต็ม คนส่วนใหญ่จึงใช้ประเภทจำนวนเต็มจริงๆ ด้วยเหตุนี้ในบรรทัดที่ 3 วิธีการนี้จะจัดการกับจำนวนเต็มอย่างชัดเจนเพื่อหลีกเลี่ยงการแคสต์ใดๆ สำหรับค่าตัวเลขอื่นๆ ทั้งหมด จะตรวจสอบว่าอินพุตอยู่ในช่วงจำนวนเต็มหรือไม่ การตรวจสอบนี้เกิดขึ้นในบรรทัดที่ 7

เราสามารถเขียนสิ่งนี้ได้แตกต่างและชัดเจนในการจัดการทุกประเภทที่ได้รับอนุญาต น่าเสียดายที่สิ่งนี้จะนำไปสู่รหัสที่ซ้ำกัน สิ่งที่ฉันทำคือแปลงตัวเลขเป็นจำนวนเต็มและเปรียบเทียบกับตัวเลขเดิม หากค่าเหมือนกัน ข้อใดข้อหนึ่งต่อไปนี้เป็นจริง:

  1. ค่าคือ Byte หรือ Short
  2. ค่ายาวแต่อยู่ในช่วงจำนวนเต็ม
  3. ค่าเป็น Double หรือ Float แต่ไม่มีเศษส่วนใดๆ

เพื่อให้แน่ใจว่ารหัสทำงานได้ดี ฉันได้ทดสอบด้วยการทดสอบหน่วยมาตรฐานของ Spark และด้วยตนเองโดยตรวจสอบพฤติกรรมของวิธีการเพื่อหาค่าทางกฎหมายและค่าที่ผิดกฎหมายต่างๆ เพื่อให้แน่ใจว่าวิธีแก้ปัญหาอย่างน้อยก็เร็วเท่ากับต้นฉบับ ฉันทดสอบหลายครั้งโดยใช้ตัวอย่างด้านล่าง สามารถวางไว้ใน คลาส ALSSuite ในสปาร์ค:

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

  }

หลังจากการทดสอบเล็กน้อย เราจะเห็นได้ว่าการแก้ไขใหม่นั้นเร็วกว่าของเดิมเล็กน้อย:

รหัส

จำนวนการวิ่ง

เวลาดำเนินการทั้งหมด

เวลาดำเนินการเฉลี่ยต่อรัน

Original 100 588.458s 5.88458s
คงที่ 100 566.722s 5.66722s

ฉันทำการทดลองซ้ำหลายครั้งเพื่อยืนยันและผลลัพธ์ก็สม่ำเสมอ คุณจะพบผลลัพธ์โดยละเอียดของการทดสอบหนึ่งรายการสำหรับ รหัสเดิม และ แก้ไขปัญหา. ความแตกต่างนั้นเล็กน้อยสำหรับชุดข้อมูลเล็กๆ แต่ในอดีต ฉันสามารถจัดการเพื่อลดค่าใช้จ่าย GC ได้อย่างเห็นได้ชัดโดยใช้การแก้ไขนี้ เราสามารถยืนยันได้โดยเรียกใช้ Spark ในเครื่องและแนบ Java Profiler บนอินสแตนซ์ Spark ฉันเปิด ตั๋ว และ ดึงคำขอ บน Spark repo อย่างเป็นทางการ แต่เพราะไม่แน่ใจว่าจะควบรวมหรือเปล่า เลยเอามาแชร์ที่นี่ค่ะ และตอนนี้เป็นส่วนหนึ่งของ Spark 2.2

ยินดีต้อนรับความคิดความคิดเห็นหรือคำติชม! 🙂

ประทับเวลา:

เพิ่มเติมจาก กล่องข้อมูล