Đi sâu vào thuật toán Khuyến nghị ALS của Spark PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

Tìm hiểu kỹ thuật toán Đề xuất ALS của Spark

Thuật toán ALS được giới thiệu bởi Hu và cộng sự., là một kỹ thuật rất phổ biến được sử dụng trong các vấn đề của Hệ thống đề xuất, đặc biệt là khi chúng ta có tập dữ liệu ngầm định (ví dụ: số lần nhấp chuột, số lượt thích, v.v.). Nó có thể xử lý khối lượng lớn dữ liệu một cách hợp lý và chúng ta có thể tìm thấy nhiều cách triển khai tốt trong các khuôn khổ Học máy khác nhau. Spark bao gồm thuật toán trong thành phần MLlib gần đây đã được cấu trúc lại để cải thiện khả năng đọc và kiến ​​trúc của mã.

Việc triển khai Spark yêu cầu Item và User id là các số trong phạm vi số nguyên (kiểu Số nguyên hoặc Dài trong phạm vi số nguyên), điều này là hợp lý vì điều này có thể giúp tăng tốc hoạt động và giảm tiêu thụ bộ nhớ. Một điều tôi nhận thấy mặc dù trong khi đọc mã là các cột id đó đang được chuyển thành Đôi và sau đó thành Số nguyên ở đầu các phương thức khớp / dự đoán. Điều này có vẻ hơi khó hiểu và tôi đã thấy nó gây căng thẳng không cần thiết cho bộ thu gom rác. Đây là những dòng về Mã ALS truyền các id thành nhân đôi:
Đi sâu vào thuật toán Khuyến nghị ALS của Spark PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.
Đi sâu vào thuật toán Khuyến nghị ALS của Spark PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

Để hiểu lý do tại sao điều này được thực hiện, người ta cần đọc checkCast ():
Đi sâu vào thuật toán Khuyến nghị ALS của Spark PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

UDF này nhận một Double và kiểm tra phạm vi của nó, sau đó chuyển nó thành số nguyên. UDF này được sử dụng để xác thực lược đồ. Câu hỏi đặt ra là liệu chúng ta có thể đạt được điều này mà không cần sử dụng những vật đúc kép xấu xí? Tôi tin là có:

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

Đoạn mã trên cho thấy một checkCast () đã được sửa đổi nhận đầu vào, kiểm tra xác nhận rằng giá trị là số và đặt ra các ngoại lệ nếu không. Vì đầu vào là Bất kỳ, chúng ta có thể xóa tất cả các câu lệnh truyền sang Double một cách an toàn khỏi phần còn lại của mã. Hơn nữa, điều hợp lý là vì ALS yêu cầu id trong phạm vi số nguyên, phần lớn mọi người thực sự sử dụng kiểu số nguyên. Kết quả là ở dòng 3, phương thức này xử lý Số nguyên một cách rõ ràng để tránh thực hiện bất kỳ quá trình ép kiểu nào. Đối với tất cả các giá trị số khác, nó sẽ kiểm tra xem đầu vào có nằm trong phạm vi số nguyên hay không. Kiểm tra này xảy ra trên dòng 7.

Người ta có thể viết điều này theo cách khác và xử lý rõ ràng tất cả các kiểu được phép. Thật không may, điều này sẽ dẫn đến mã trùng lặp. Thay vào đó, những gì tôi làm ở đây là chuyển đổi số thành Số nguyên và so sánh nó với Số ban đầu. Nếu các giá trị giống nhau, một trong những điều sau là đúng:

  1. Giá trị là Byte hoặc Short.
  2. Giá trị là Dài nhưng nằm trong phạm vi Số nguyên.
  3. Giá trị là Double hoặc Float nhưng không có bất kỳ phần phân số nào.

Để đảm bảo rằng mã chạy tốt, tôi đã kiểm tra nó bằng các bài kiểm tra đơn vị tiêu chuẩn của Spark và theo cách thủ công bằng cách kiểm tra hành vi của phương thức đối với các giá trị pháp lý và bất hợp pháp khác nhau. Để đảm bảo rằng giải pháp ít nhất cũng nhanh như ban đầu, tôi đã thử nghiệm nhiều lần bằng cách sử dụng đoạn mã bên dưới. Điều này có thể được đặt trong Lớp ALSSuite trong 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")

  }

Sau một vài thử nghiệm, chúng tôi có thể thấy rằng bản sửa lỗi mới nhanh hơn một chút so với bản gốc:

Số lần chạy

Tổng thời gian thực hiện

Thời gian thực hiện trung bình mỗi lần chạy

Nguyên 100 588.458s 5.88458s
đã sửa 100 566.722s 5.66722s

Tôi lặp lại các thí nghiệm nhiều lần để xác nhận và kết quả là nhất quán. Tại đây, bạn có thể tìm thấy kết quả chi tiết của một thử nghiệm cho mã gốcsửa chữa. Sự khác biệt là nhỏ đối với một tập dữ liệu nhỏ nhưng trước đây tôi đã cố gắng đạt được mức giảm đáng kể chi phí GC bằng cách sử dụng bản sửa lỗi này. Chúng tôi có thể xác nhận điều này bằng cách chạy Spark cục bộ và đính kèm một hồ sơ Java trên phiên bản Spark. Tôi đã mở một véYêu cầu kéo trên đại diện chính thức của Spark nhưng vì không chắc liệu nó có được hợp nhất hay không, nên tôi muốn chia sẻ nó ở đây với bạn và hiện nó là một phần của Spark 2.2.

Mọi suy nghĩ, nhận xét hoặc phê bình đều được chào đón! 🙂

Dấu thời gian:

Thêm từ Hộp dữ liệu