blob: 9b7625bbc1ae249a97d420a10b0ae0b7f63a7f7f [file] [log] [blame] [view]
---
title: Customizing Serving Component (Recommendation)
---
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
Serving component is where post-processing occurs. For example, if you are
recommending items to users, you may want to remove items that are not
currently in stock from the list of recommendation.
This section is based on the [Recommendation Engine Template](/templates/recommendation/quickstart/).
A full end-to-end example can be found on
[GitHub](https://github.com/apache/predictionio/tree/develop/examples/scala-parallel-recommendation/customize-serving).
## The Serving Component
Recall [the DASE Architecture](/customize/), a PredictionIO engine has
4 main components: Data Source, Data Preparator, Algorithm, and Serving
components. When a Query comes in, it is passed to the Algorithm component for
making Predictions.
The Engine's serving component can be found in `src/main/scala/Serving.scala` in
the *MyRecommendation* directory. By default, it looks like the following:
```scala
class Serving
extends LServing[Query, PredictedResult] {
override
def serve(query: Query,
predictedResults: Seq[PredictedResult]): PredictedResult = {
predictedResults.head
}
}
```
We will customize the Serving component to remove temporarily disabled items
from the Prediction made by Algorithms.
## Modify the Serving Interface
We will use a file to specify a list of disabled items. When the `serve` method
is called, it loads the file and removes items in the disabled list from
`PredictedResult`. The following code snippet illustrates the logic:
```scala
import scala.io.Source // ADDED
class Serving
extends LServing[Query, PredictedResult] {
override
def serve(query: Query,
predictedResults: Seq[PredictedResult]): PredictedResult = {
// MODIFIED HERE
// Read the disabled item from file.
val disabledProducts: Set[String] = Source
.fromFile("./data/sample_disabled_items.txt")
.getLines
.toSet
val itemScores = predictedResults.head.itemScores
// Remove items from the original predictedResult
PredictedResult(itemScores.filter(ps => !disabledProducts(ps.item)))
}
}
```
INFO:We will show you how not to hardcode the path
`./data/sample_disabled_items.txt` soon.
WARNING: This example code uses a local relative path. For remote deployment, it is
recommended to use a globally accessible absolute path.
DANGER: This example is only for demonstration purpose. Reading from disk for every
query leads to terrible system performance. Use a more efficient
implementation for production deployment.
## Deploy the Modified Engine
Now you can deploy the modified engine as described in [Quick
Start](quickstart.html).
Make sure the `appName` defined in the file `engine.json` matches your *App Name*:
```
...
"datasource": {
"params" : {
"appName": "YourAppName"
}
},
...
```
To build *MyRecommendation* and deploy it as a service:
```
$ pio build
$ pio train
$ pio deploy
```
This will deploy an engine that binds to http://localhost:8000. You can visit
that page in your web browser to check its status.
Now, you can try to retrieve predicted results. To recommend 4 movies to user
whose ID is 1, send this JSON `{ "user": "1", "num": 4 }` to the deployed
engine
```
$ curl -H "Content-Type: application/json" -d '{ "user": "1", "num": 4 }' \
http://localhost:8000/queries.json
```
and it will return a JSON of recommended movies.
```json
{
"itemScores": [
{"item": "65", "score": 6.537168137254073},
{"item": "69", "score": 6.391430405762495},
{"item": "38", "score": 5.829957095096519},
{"item": "11", "score": 5.5991291456974}
]
}
```
Now, to verify the blacklisting logic, we add the item 69 (the second item)
to the blacklisting file `data/sample_disabled_items.txt`. Rerun the `curl`
query, and the change should take effect immediately as the disabled item
list is reloaded every time the `serve` method is called.
```
$ echo "69" >> ./data/sample_disabled_items.txt
$ curl -H "Content-Type: application/json" -d '{ "user": "1", "num": 4 }' \
http://localhost:8000/queries.json
```
```json
{
"itemScores": [
{"item": "65", "score": 6.537168137254073},
{"item": "38", "score": 5.829957095096519},
{"item": "11", "score": 5.5991291456974}
]
}
```
Congratulations! You have learned how to add customized realtime blacklisting
logic to your Serving component!
## Adding Serving Parameters
Optionally, you may want to take the hardcoded path
(`./data/sample_disabled_items.txt`) away from the source code.
PredictionIO offers serving params so you can read variable values from
`engine.json` instead. PredictionIO transforms the JSON object specified in
`engine.json`'s `serving` field into the `ServingParams` class.
Modify `src/main/scala/Serving.scala` again in the *MyRecommendation*
directory to:
```scala
import scala.io.Source
import org.apache.predictionio.controller.Params // ADDED
// ADDED ServingParams to specify the blacklisting file location.
case class ServingParams(filepath: String) extends Params
class Serving(val params: ServingParams)
extends LServing[Query, PredictedResult] {
override
def serve(query: Query, predictedResults: Seq[PredictedResult])
: PredictedResult = {
val disabledProducts: Set[String] = Source
.fromFile(params.filepath)
.getLines
.toSet
val itemScores = predictedResults.head.itemScores
PredictedResult(itemScores.filter(ps => !disabledProducts(ps.item)))
}
}
```
In `engine.json`, you define the parameters `serving` for the Serving component:
```json
{
...
"serving": {
"params": {
"filepath": "./data/sample_disabled_items.txt"
}
},
...
}
```
Try to build *MyRecommendation* and deploy it again:
```
$ pio build
$ pio train
$ pio deploy
```
You can change the `filepath` value without re-building the code next time.
#### [Next: Training with Implicit Preference](training-with-implicit-preference.html)