حفاری در الگوریتم توصیه ALS Spark، هوش داده پلاتو بلاک چین. جستجوی عمودی Ai.

حفاری در الگوریتم توصیه ALS Spark

الگوریتم ALS معرفی شده توسط هو و همکاران، یک تکنیک بسیار محبوب است که در مشکلات سیستم توصیه‌کننده استفاده می‌شود، به‌ویژه زمانی که مجموعه داده‌های ضمنی (مثلاً کلیک‌ها، لایک‌ها و غیره) داریم. این می تواند حجم زیادی از داده ها را به خوبی مدیریت کند و ما می توانیم پیاده سازی های خوب زیادی را در چارچوب های مختلف یادگیری ماشین پیدا کنیم. Spark شامل الگوریتمی در مؤلفه MLlib است که اخیراً برای بهبود خوانایی و معماری کد بازسازی شده است.

اجرای Spark مستلزم آن است که Item و User ID اعدادی در محدوده صحیح (اعم از نوع Integer یا Long در محدوده صحیح) باشند، که منطقی است زیرا این امر می تواند به سرعت بخشیدن به عملیات و کاهش مصرف حافظه کمک کند. یکی از چیزهایی که من هنگام خواندن کد متوجه شدم این است که ستون‌های شناسه در ابتدای روش‌های برازش/پیش‌بینی به Doubles و سپس به اعداد صحیح ریخته می‌شوند. این کمی هک به نظر می رسد و من دیده ام که فشار غیرضروری به جمع کننده زباله وارد می کند. در اینجا خطوط در کد ALS که شناسه ها را دوتایی می کند:
حفاری در الگوریتم توصیه ALS Spark، هوش داده پلاتو بلاک چین. جستجوی عمودی Ai.
حفاری در الگوریتم توصیه ALS Spark، هوش داده پلاتو بلاک چین. جستجوی عمودی Ai.

برای درک اینکه چرا این کار انجام می شود، باید checkedCast():
حفاری در الگوریتم توصیه ALS Spark، هوش داده پلاتو بلاک چین. جستجوی عمودی Ai.

این UDF یک Double دریافت می کند و محدوده آن را بررسی می کند و سپس آن را به عدد صحیح می فرستد. این UDF برای اعتبار سنجی Schema استفاده می شود. سوال این است که آیا می‌توانیم بدون استفاده از ریخته‌گری‌های زشت به این هدف دست پیدا کنیم؟ من معتقدم بله:

  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. مقدار Long است اما در محدوده Integer است.
  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")

  }

پس از چند آزمایش می توانیم ببینیم که تعمیر جدید کمی سریعتر از نسخه اصلی است:

رمز

تعداد اجراها

کل زمان اجرا

میانگین زمان اجرا در هر اجرا

اصلی 100 588.458s 5.88458s
درست شد 100 566.722s 5.66722s

من آزمایش ها را چندین بار تکرار کردم تا تایید کنم و نتایج ثابت باشد. در اینجا می توانید خروجی دقیق یک آزمایش را برای آن بیابید کد اصلی و تعمیر. تفاوت برای یک مجموعه داده کوچک کوچک است، اما در گذشته من موفق به کاهش قابل توجهی در سربار GC با استفاده از این اصلاح شده‌ام. ما می‌توانیم این موضوع را با اجرای Spark به صورت محلی و پیوست کردن یک پروفایل‌کننده جاوا در نمونه Spark تأیید کنیم. a را باز کردم بلیط و یک کشش-درخواست در مخزن رسمی اسپارک اما از آنجایی که ادغام شدن آن مشخص نیست، فکر کردم آن را در اینجا با شما به اشتراک بگذارم و اکنون بخشی از Spark 2.2 است.

هر گونه نظر، نظر یا انتقاد پذیرفته می شود! 🙂

تمبر زمان:

بیشتر از Datumbox