- 2017년 2월 25일
- 바실리스 브로 니 오티스
- . 댓글 3 개
에 의해 도입 된 ALS 알고리즘 Huet al., 특히 암시 적 데이터 집합 (예 : 클릭, 좋아요 등)이있는 경우 Recommender 시스템 문제에 사용되는 매우 인기있는 기술입니다. 대량의 데이터를 합리적으로 잘 처리 할 수 있으며 다양한 머신 러닝 프레임 워크에서 많은 훌륭한 구현을 찾을 수 있습니다. Spark에는 코드의 가독성과 아키텍처를 개선하기 위해 최근 리팩토링 된 MLlib 구성 요소의 알고리즘이 포함되어 있습니다.
Spark를 구현하려면 Item 및 User ID가 정수 범위 (정수 유형 또는 정수 범위 내의 Long) 내의 숫자 여야합니다. 이는 연산 속도를 높이고 메모리 소비를 줄이는 데 도움이되므로 합리적입니다. 코드를 읽는 동안 알아 차린 한 가지 사항은 해당 id 열이 적합 / 예측 방법의 시작 부분에서 Doubles로 캐스팅 된 다음 정수로 캐스트된다는 것입니다. 이것은 약간 해키처럼 보이고 가비지 수집기에 불필요한 부담을주는 것을 보았습니다. 여기에 라인이 있습니다 ALS 코드 ID를 두 배로 캐스트합니다.
이것이 왜 이루어지는 지 이해하려면 checkedCast ()를 읽어야합니다.
이 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이므로 나머지 코드에서 모든 캐스트를 Double 문으로 안전하게 제거 할 수 있습니다. 또한 ALS에는 정수 범위 내의 ID가 필요하기 때문에 대부분의 사람들이 실제로 정수 유형을 사용한다고 기대하는 것이 합리적입니다. 3 행의 결과로이 메소드는 캐스팅을 피하기 위해 정수를 명시 적으로 처리합니다. 다른 모든 숫자 값의 경우 입력이 정수 범위 내에 있는지 확인합니다. 이 점검은 7 행에서 이루어집니다.
허용되는 모든 유형을 다르게 작성하고 명시 적으로 처리 할 수 있습니다. 불행히도 이로 인해 코드가 중복 될 수 있습니다. 대신 내가 여기서하는 일은 숫자를 정수로 변환하고 원래 숫자와 비교하는 것입니다. 값이 동일하면 다음 중 하나에 해당합니다.
- 값은 바이트 또는 짧은입니다.
- 값이 Long이지만 정수 범위 내에 있습니다.
- 값은 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") }
몇 번의 테스트 후에 새로운 수정이 원본보다 약간 빠르다는 것을 알 수 있습니다.
암호 |
런 수 |
총 실행 시간 |
실행 당 평균 실행 시간 |
실물 | 100 | 588.458s | 5.88458s |
Fixed | 100 | 566.722s | 5.66722s |
확인을 위해 실험을 여러 번 반복했으며 결과는 일관됩니다. 여기에서 하나의 실험에 대한 자세한 결과를 확인할 수 있습니다. 원래 코드 그리고 고정 된. 작은 데이터 세트의 경우 그 차이는 작지만 과거에는이 수정을 사용하여 GC 오버 헤드를 눈에 띄게 줄였습니다. Spark를 로컬로 실행하고 Spark 인스턴스에 Java 프로파일 러를 연결하여이를 확인할 수 있습니다. 나는 열었다 표 및 풀 요청 공식 Spark Repo에서 병합 될지 확실하지 않기 때문에 여기에서 공유 할 것으로 생각했습니다. 이제 Spark 2.2의 일부입니다.
모든 생각, 의견 또는 비난은 환영합니다! 🙂