All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.iheart.thomas.analysis.bayesian.ModelEvaluator.scala Maven / Gradle / Ivy

The newest version!
package com.iheart.thomas
package analysis
package bayesian

import cats.Monad
import cats.data.NonEmptyList
import com.stripe.rainier.sampler.{RNG, SamplerConfig}
import cats.implicits._

trait ModelEvaluator[F[_], Model, Measurement] {
  def evaluate(
      model: Model,
      measurements: Map[ArmName, Measurement],
      benchmark: Option[(ArmName, Measurement)]
    ): F[List[Evaluation]]

  /** Measure optimal arm probability based on their model and measurement
    * @return
    */
  def evaluate(
      measurementsAndModel: Map[ArmName, (Measurement, Model)]
    ): F[Map[ArmName, Probability]]

  def compare(
      baseline: (Measurement, Model),
      results: (Measurement, Model)
    ): F[Samples[Diff]]

  def compare(
      model: Model,
      baseline: Measurement,
      results: Measurement
    ): F[Samples[Diff]] = compare((baseline, model), (results, model))
}

object ModelEvaluator {

  def apply[F[_], Model, Measurement](
      implicit inst: ModelEvaluator[F, Model, Measurement]
    ): ModelEvaluator[F, Model, Measurement] = inst

  implicit def rainierKPIEvaluator[F[_], Model, Measurement](
      implicit
      F: Monad[F],
      M: Posterior[Model, Measurement],
      K: KPIIndicator[Model]
    ): ModelEvaluator[F, Model, Measurement] =
    new ModelEvaluator[F, Model, Measurement] {

      implicit val rng = RNG.default
      implicit val sc = SamplerConfig.default

      private def posteriorIndicator(
          model: Model,
          measurement: Measurement
        ): Indicator =
        K(M(model, measurement))

      def evaluate(
          model: Model,
          measurements: Map[ArmName, Measurement],
          benchmarkO: Option[(ArmName, Measurement)]
        ): F[List[Evaluation]] =
        for {
          probabilities <- evaluate(measurements.map { case (k, v) =>
            (k, (v, model))
          })
          r <-
            probabilities.toList
              .traverse { case (armName, probability) =>
                benchmarkO
                  .traverseFilter {
                    case (benchmarkName, benchmarkMeasurement)
                        if benchmarkName != armName =>
                      compare(
                        (benchmarkMeasurement, model),
                        (measurements.get(armName).get, model)
                      ).map(r => Option(bayesian.BenchmarkResult(r, benchmarkName)))
                    case _ => none[BenchmarkResult].pure[F]
                  }
                  .map(brO => Evaluation(armName, probability, brO))
              }
        } yield r

      def compare(
          baseline: (Measurement, Model),
          results: (Measurement, Model)
        ): F[Samples[Diff]] = {

        (
          posteriorIndicator(results._2, results._1),
          posteriorIndicator(
            baseline._2,
            baseline._1
          )
        ).mapN(_ - _).predict().pure[F]
      }

      def evaluate(
          allMeasurement: Map[GroupName, (Measurement, Model)]
        ): F[Map[GroupName, Probability]] = {
        NonEmptyList
          .fromList(allMeasurement.toList)
          .map {
            _.nonEmptyTraverse { case (gn, (ms, k)) =>
              posteriorIndicator(k, ms).map((gn, _))
            }
          }
          .fold(F.pure(Map.empty[GroupName, Probability])) { rvGroupResults =>
            val numericGroupResult =
              rvGroupResults
                .map(_.toList.toMap)
                .predict()

            val initCounts = allMeasurement.map { case (gn, _) => (gn, 0L) }

            val winnerCounts = numericGroupResult.foldLeft(initCounts) {
              (counts, groupResult) =>
                val winnerGroup = groupResult.maxBy(_._2)._1
                counts.updated(winnerGroup, counts(winnerGroup) + 1)
            }

            val total = winnerCounts.toList.map(_._2).sum
            F.pure(winnerCounts.map { case (gn, c) =>
              (gn, Probability(c.toDouble / total.toDouble))
            })
          }
      }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy