Project Predictionio has retired. For details please refer to its Attic page.

This examples demonstrates how to modify E-Commerce Recommendation template to further adjust score.

By default, items have a weight of 1.0. Giving an item a weight greater than 1.0 will make them appear more often and can be useful for i.e. promoted products. An item can also be given a weight smaller than 1.0 (but bigger than 0), in which case it will be recommended less often than originally. Weight values smaller than 0.0 are invalid.

You can find the complete modified source code here.

Modification

ECommAlgorithm.scala

Add a case class to represent each group items which are given the same weight.

1
2
3
4
5
// ADDED
case class WeightGroup(
  items: Set[String],
  weight: Double
)

In ECommAlgorithm, add weightedItems function to extract the sequence of WeightGroup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  // ADDED
  /** Get the latest constraint weightedItems */
  def weightedItems: Seq[WeightGroup] = {
    try {
      val constr = LEventStore.findByEntity(
        appName = ap.appName,
        entityType = "constraint",
        entityId = "weightedItems",
        eventNames = Some(Seq("$set")),
        limit = Some(1),
        latest = true,
        timeout = Duration(200, "millis")
      )
      if (constr.hasNext) {
        constr.next.properties.get[Seq[WeightGroup]]("weights")
      } else {
        Nil
      }
    } catch {
      case e: scala.concurrent.TimeoutException =>
        logger.error(s"Timeout when read set weightedItems event." +
          s" Empty list is used. ${e}")
        Nil
      case e: Exception =>
        logger.error(s"Error when read set weightedItems event: ${e}")
        throw e
    }
  }

Modify the predictKnownUser(), predictDefault() and predictSimilar():

  • add the weights: Map[Int, Double] parameter
  • adjust score according to item weights
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  def predictKnownUser(
    userFeature: Array[Double],
    productModels: Map[Int, ProductModel],
    query: Query,
    whiteList: Option[Set[Int]],
    blackList: Set[Int],
    weights: Map[Int, Double] // ADDED
  ): Array[(Int, Double)] = {

      ...
      .map { case (i, pm) =>
        // NOTE: features must be defined, so can call .get
        val s = dotProduct(userFeature, pm.features.get)
        // may customize here to further adjust score
        // ADDED
        val adjustedScore = s * weights(i)
        (i, adjustedScore)
      }
      ...

  }

Lastly, modify the predict() method. The sequence of WeightGroup transforms into a Map[Int, Double] that we can easily query to extract the weight given to an item, using its Int index.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  def predict(model: ECommModel, query: Query): PredictedResult = {

    ...

    // ADDED
    val weights: Map[Int, Double] = (for {
      group <- weightedItems
      item <- group.items
      index <- model.itemStringIntMap.get(item)
    } yield (index, group.weight))
      .toMap
      .withDefaultValue(1.0)

    ...

    val topScores: Array[(Int, Double)] = if (userFeature.isDefined) {
      // the user has feature vector
      predictKnownUser(
        userFeature = userFeature.get,
        productModels = productModels,
        query = query,
        whiteList = whiteList,
        blackList = finalBlackList,
        weights = weights // ADDED
      )
    } else {
      ...

      if (recentFeatures.isEmpty) {
        logger.info(s"No features vector for recent items ${recentItems}.")
        predictDefault(
          productModels = productModels,
          query = query,
          whiteList = whiteList,
          blackList = finalBlackList,
          weights = weights // ADDED
        )
      } else {
        predictSimilar(
          recentFeatures = recentFeatures,
          productModels = productModels,
          query = query,
          whiteList = whiteList,
          blackList = finalBlackList,
          weights = weights // ADDED
        )
      }
    }

    ...
  }

Now, to send an event to Event Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ curl -i -X POST http://localhost:7070/events.json?accessKey=$ACCESS_KEY \
-H "Content-Type: application/json" \
-d '{
  "event" : "$set",
  "entityType" : "constraint",
  "entityId" : "weightedItems",
  "properties" : {
    "weights": [
      {
        "items": ["i4", "i14"],
        "weight": 1.2
      },
      {
        "items": ["i11"],
        "weight": 1.5
      }
    ]
  },
  "eventTime" : "2014-11-02T09:39:45.618-08:00"
}'

That's it! Now your engine can predict with adjusted scores.