本文共 31487 字,大约阅读时间需要 104 分钟。
In part one, we covered the basics of Prediction IO and installed its dependencies. In this part, we’re going to build the app.
在第一部分中,我们介绍了Prediction IO的基础知识并安装了其依赖项。 在这一部分中,我们将构建应用程序。
We will be importing the data using the Prediction IO SDK, so we first need to tell Flight to use it. In the beginning of your index.php
file add the following code:
我们将使用Prediction IO SDK导入数据,因此我们首先需要告诉Flight使用它。 在index.php
文件的开头,添加以下代码:
Next, register the Prediction IO Client to Flight so that we can use it throughout our app:
接下来,将Prediction IO Client注册为Flight,以便我们可以在整个应用中使用它:
Flight::register('prediction', 'PredictionIO\PredictionIOClient');
While we’re here let’s also register the MongoDB class so that we can query MongoDB later on:
在这里,我们还要注册MongoDB类,以便稍后可以查询MongoDB:
Flight::register('mdb', 'Mongo', array('mongodb://localhost'));
Next, we map the factory method to a Flight method and call it prediction_client
. We will be using this later on to make calls with the Prediction IO client.
接下来,我们将工厂方法映射到Flight方法,并将其命名为prediction_client
。 我们稍后将使用它与Prediction IO客户端进行呼叫。
Flight::map('prediction_client', function(){ $client = Flight::prediction()->factory(array("appkey" => "YOUR_PREDICTION_IO_APP_KEY")); return $client;});
Lastly, we also let Flight know of Guzzle. This way we don’t need to initialize Guzzle every time we need to use it. We can just call Flight::guzzle()->some_guzzle_method()
and be done with it.
最后,我们还让Flight知道了Guzzle。 这样,我们不需要每次使用Guzzle时都进行初始化。 我们可以只调用Flight::guzzle()->some_guzzle_method()
并完成它。
Flight::register('guzzle', 'GuzzleHttp\Client');
We can now start writing the code for importing data from TMDB. First, declare a route to the movies/import
path. We will access this from the browser later on to begin importing.
现在,我们可以开始编写用于从TMDB导入数据的代码。 首先,声明到movies/import
路径的路线。 我们稍后将通过浏览器访问它,以开始导入。
Flight::route('GET /movies/import', array('Admin', 'import'));
In the code above, we’re specifying the controller and the method that Flight will use inside an array. Go ahead and create a controllers
directory inside the root of your project folder. Then create an admin.php
file, which will serve as the controller. Open up the file and declare a class to be the same name as the name of the file:
在上面的代码中,我们指定了Flight将在数组内使用的控制器和方法。 继续,在项目文件夹的根目录下创建一个controllers
目录。 然后创建一个admin.php
文件,它将用作控制器。 打开文件,并声明一个与文件名相同的类:
Inside the import
method, initialize the Prediction IO client. Then create a for
loop that would loop 100 times. Inside the loop, we call the TMDB API to return the data on the most popular movies for us. Each API call returns 20 movies so if we loop for 100 times we get a total of 2000 movies.
在import
方法内部,初始化Prediction IO客户端。 然后创建一个for
循环,该循环将循环100次。 在循环内部,我们调用TMDB API为我们返回最流行电影的数据。 每个API调用返回20部电影,因此,如果我们循环播放100次,则总共将获得2000部电影。
$client = PredictionIOClient::factory(array("appkey" => "YOUR_PREDICTIONIO_APP_KEY")); $index = 0; for($x = 3; $x <= 100; $x++){ $movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=YOUR_TMDB_API_KEY&page=' . $x; $movies_response = Flight::guzzle()->get($movies_url); //get most popular movies $movies_body = $movies_response->getBody(); //get response body }
In the code above, we’re using Guzzle to fetch some movies from the TMDB API for us. We then convert the returned data which is in JSON format into an array using json_decode
:
在上面的代码中,我们正在使用Guzzle从TMDB API中获取一些电影。 然后,我们使用json_decode
将返回的JSON格式数据转换为数组:
$movies_result = json_decode($movies_body, true);$movies = $movies_result['results'];
Once that’s done, we can loop through all of the movies and extract the fields that we want. In this case we want the id, title and the path to the movie poster:
完成后,我们可以遍历所有电影并提取所需的字段。 在这种情况下,我们需要ID,标题和电影海报的路径:
if(!empty($movies)){ //loop through all the movies foreach($res as $row){ $id = $row['id']; $title = $row['title']; $poster_path = ''; if(!empty($row['poster_path'])){ $poster_path = $row['poster_path']; } }
To get more details we need to make a separate call for each movie by using its id. We then convert the returned JSON data to an array the same way we did earlier:
要获取更多详细信息,我们需要使用其ID对每个电影进行单独调用。 然后,我们以与之前相同的方式将返回的JSON数据转换为数组:
$moviedetails_url = 'https://api.themoviedb.org/3/movie/' . $id . '?api_key=YOUR_TMDB_API_KEY';$moviedetails_response = Flight::guzzle()->get($moviedetails_url);$movie_details_body = $moviedetails_response->getBody();$movie = json_decode($movie_details_body, true);
The call to the movie resource returns a whole bunch of data about the movie, but for this app we’re only going to use the overview and release date:
对电影资源的调用返回了有关电影的一堆数据,但是对于这个应用程序,我们将仅使用概述和发行日期:
$overview = $movie['overview'];$release_date = $movie['release_date'];
Now that we have all the data we need, we can save it to the database using the create_item
method from the Prediction IO SDK. This call accepts 2 arguments: pio_iid
and pio_itypes
. pio_iid
is the ID of the item; in this case, we’re simply going to use $movie_id
, the variable that we declared earlier. We’ll just increment this variable for each iteration of the loop so we have a unique id for each movie. The other required argument is the pio_itypes
. This is where we specify the type of the item. You can use any descriptive name for this like movie
. But for this app, we’re just going to set the pio_itypes
to 1. Next, we set the movie details using the set
method. Once that’s done we just call the execute
method to add the movie into the database. Then we use print_r
to print out the response that we get to make sure the operation was successful.
现在我们有了所需的所有数据,我们可以使用Prediction IO SDK中的create_item
方法将其保存到数据库中。 该调用接受2个参数: pio_iid
和pio_itypes
。 pio_iid
是商品的ID; 在这种情况下,我们将仅使用$movie_id
,这是我们之前声明的变量。 我们将为循环的每次迭代增加此变量,以便为每部电影都有唯一的ID。 另一个必需的参数是pio_itypes
。 这是我们指定项目类型的地方。 您可以为此movie
使用任何描述性名称,例如movie
。 但是对于这个应用程序,我们只是将pio_itypes
设置为1。接下来,我们使用set
方法设置电影细节。 完成后,我们只需调用execute
方法将影片添加到数据库中即可。 然后,我们使用print_r
打印出获得的响应,以确保操作成功。
$command = $client->getCommand('create_item', array('pio_iid' => $movie_id, 'pio_itypes' => 1));$command->set('tmdb_id', $id);$command->set('title', $title);$command->set('poster_path', $poster_path);$command->set('overview', $overview);$command->set('release_date', $release_date);$client_response = $client->execute($command);print_r($client_response);
Lastly, we increment the $movie_id
by 1.
最后,我们将$movie_id
递增1。
$movie_id += 1;
Putting everything together, we have the following code:
将所有内容放在一起,我们有以下代码:
$client = PredictionIOClient::factory(array("appkey" => "YOUR_PREDICTIONIO_APP_KEY"));$index = 41;for($x = 3; $x <= 100; $x++){ $movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=YOUR_TMDB_API_KEY&page=' . $x; $movies_response = Flight::guzzle()->get($movies_url); $movies_body = $movies_response->getBody(); $movies_result = json_decode($movies_body, true); $movies = $movies_result['results']; if(!empty($movies)){ foreach($movies as $row){ $id = $row['id']; $title = $row['title']; $poster_path = ''; if(!empty($row['poster_path'])){ $poster_path = $row['poster_path']; } $moviedetails_url = 'https://api.themoviedb.org/3/movie/' . $id . '?api_key=YOUR_TMDB_API_KEY'; $moviedetails_response = Flight::guzzle()->get($moviedetails_url); $movie_details_body = $moviedetails_response->getBody(); $movie = json_decode($movie_details_body, true); $overview = $movie['overview']; $release_date = $movie['release_date']; $command = $client->getCommand('create_item', array('pio_iid' => $index, 'pio_itypes' => 1)); $command->set('tmdb_id', $id); $command->set('title', $title); $command->set('poster_path', $poster_path); $command->set('overview', $overview); $command->set('release_date', $release_date); $client_response = $client->execute($command); print_r($client_response); echo ""; $index++; } }}
Once that’s done, open upindex.php
at the root of the project directory and put in the following on the last line. This will start the Flight framework. As a convention, this should always be located at the last line of the file:
完成后,在项目目录的根目录下打开index.php
并将其放在最后一行。 这将启动Flight框架。 按照惯例,它应始终位于文件的最后一行:
Flight::start();
After that, you can now access the /movies/import
path in the browser to begin importing some movies from TMDB. This might take a while to complete so go grab a coffee or watch an episode of your favorite show.
之后,您现在可以在浏览器中访问/movies/import
路径,以开始从TMDB导入一些电影。 这可能需要一段时间才能完成,因此可以去喝咖啡或观看您喜欢的节目的一集。
Now that we have some movies, we’re ready to show some random ones to the user. First, create a route for the index page:
现在我们有了一些电影,我们准备向用户展示一些随机的电影。 首先,为索引页面创建路由:
Flight::route('GET /', array('Home', 'index'));
This makes use of the Home
controller so go ahead and create a home.php
inside the controllers
directory with an index
method.
这将利用Home
控制器,因此继续使用index
方法在controllers
目录内创建一个home.php
。
Inside the index
method, generate a unique id using the uniqid
method, then assign it to the user_id
session item. Also, initialize movies_viewed
with a value of 0
. This will represent the number of movies that we have shown the user so far. We will increment it later on as random movies get suggested to the user. Next, we use the prediction client to save the user into the database. The Prediction IO SDK provides us with the create_user
method which will interact with the . The create_user
method needs the pio_uid
as its argument. This is pretty much all the information we need so we just call the execute
method once we’ve added the user id. If you want to add more user information you can just use the set
method to set custom user information.
在index
方法内部,使用uniqid
方法生成唯一ID,然后将其分配给user_id
会话项。 另外,将movies_viewed
初始化为0
。 这将代表我们到目前为止向用户展示的电影数量。 我们会在以后向用户建议随机电影时增加它。 接下来,我们使用预测客户端将用户保存到数据库中。 Prediction IO SDK为我们提供了create_user
方法,该方法将与交互。 create_user
方法需要使用pio_uid
作为其参数。 这几乎是我们需要的所有信息,因此添加用户ID后,我们只需调用execute
方法即可。 如果要添加更多用户信息,则可以使用set
方法设置自定义用户信息。
$user_id = uniqid();$_SESSION['user_id'] = $user_id;$_SESSION['movies_viewed'] = 0;$client = Flight::prediction_client();$command = $client->getCommand('create_user', array('pio_uid' => $user_id));$client->execute($command);
Once the new user is added into the database we can render the index page using the render
method provided by Flight. Here we’re rendering two views, the first one is the actual page and the second one is the layout. We need to call render
on the actual page first because the layout depends on the content
variable that we’re setting. In the render
call for the layout, we’re setting the title of the page and the base path for the CSS and JS files that we’re linking in the page:
将新用户添加到数据库后,我们可以使用Flight提供的render
方法来渲染索引页。 在这里,我们呈现两个视图,第一个是实际页面,第二个是布局。 我们首先需要在实际页面上调用render
,因为布局取决于我们所设置的content
变量。 在布局的render
调用中,我们设置页面的标题以及在页面中链接CSS和JS文件的基本路径:
Flight::render('index', array(), 'content');Flight::render('layout', array('title' => 'Home', 'base_path' => '/movie_recommender'));
The render
method expects the name of the view as its first argument. Views in Flight are expected to be in the views
directory relative to the root directory of the project. So in the example above, the file name used for the view is index.php
and it will contain the following HTML:
render
方法将视图名称作为其第一个参数。 飞行中的views
应该位于相对于项目根目录的views
目录中。 因此,在上面的示例中,用于视图的文件名为index.php
,它将包含以下HTML:
As you can see from the above code, we’re mainly using client-side templating to render the details for the movie. For this app, we’re using . Each subsequent movie will be loaded via AJAX so we’re also going to load the first movie via AJAX once the page has loaded.
从上面的代码中可以看到,我们主要是使用客户端模板来呈现电影的细节。 对于此应用程序,我们使用的是 。 随后的每部电影都将通过AJAX加载,因此一旦页面加载完毕,我们还将通过AJAX加载第一部电影。
For the layout, the file name would be layout.php
. The layout file contains the following:
对于布局,文件名将为layout.php
。 布局文件包含以下内容:
<?= $title; ?>
From the above code you can see that we’re using as a framework for the styling. We also have basic styling for the whole app which is added in the style.css
file:
从上面的代码中,您可以看到我们正在使用作为样式框架。 我们还为整个应用程序提供了基本样式,这些样式已添加到style.css
文件中:
.col-centered { float: none; margin: 0 auto;}.button-container { margin-top: 20px;}.show-recommendations { display: none;}#recommended-movies > div { height: 1000px;}
For the scripts, we use , Bootstrap’s JavaScript file, and the main JavaScript file for the app.
对于脚本,我们使用 ,BootstrapJavaScript文件, 和应用程序的主JavaScript文件。
For the main JavaScript we add the following code:
对于主要JavaScript,我们添加以下代码:
var movie_src = $("#movie-template").html();var movie_template = Handlebars.compile(movie_src);function getRandomMovie(request_data){ request_data = typeof request_data !== 'undefined' ? request_data : {}; $.post('movie/random', request_data, function(response){ var data = JSON.parse(response); var movie_html = movie_template(data); $('#movie-container').html(movie_html); if(data.has_recommended){ $('.show-recommendations').show(); } });}getRandomMovie();$('#movie-container').on('click', '.btn-next', function(){ var self = $(this); var id = self.data('id'); var action = self.data('action'); getRandomMovie({'movie_id' : id, 'action' : action});});
Breaking it down, we first compile the Handlebars template which is stored in the div with the ID of movie-template
:
分解它,我们首先编译存储在div中的ID为movie-template
的Handlebars movie-template
:
var movie_src = $("#movie-template").html();var movie_template = Handlebars.compile(movie_src);
We then declare the getRandomMovie
method. This takes the request_data
as an optional parameter. Inside the function we use jQuery’s post
method to issue a POST
request to the movie/random
path. This returns random movie data from the server in JSON format. We then convert it to an object that can be used by JavaScript using the JSON.parse
method. Once that’s done we just supply it to the Handlebars template that we have compiled earlier and then update the contents of movie-container
div. If the returned data has the has_recommended
item we show the link which will lead the user to the page where the movies recommended by Prediction IO are displayed:
然后,我们声明getRandomMovie
方法。 这将request_data
作为可选参数。 在函数内部,我们使用jQuery的post
方法向movie/random
路径发出POST
请求。 这将从服务器以JSON格式返回随机电影数据。 然后,我们使用JSON.parse
方法将其转换为JavaScript可以使用的对象。 完成后,我们将其提供给我们先前编译的Handlebars模板,然后更新movie-container
div的内容。 如果返回的数据包含has_recommended
项,我们将显示链接,该链接将引导用户到显示Prediction IO推荐的电影的页面:
function getRandomMovie(request_data){ request_data = typeof request_data !== 'undefined' ? request_data : {}; $.post('movie/random', request_data, function(response){ var data = JSON.parse(response); var movie_html = movie_template(data); $('#movie-container').html(movie_html); if(data.has_recommended){ $('.show-recommendations').show(); } });}
Once the script is loaded we execute the function to load the first random movie:
加载脚本后,我们将执行函数以加载第一个随机电影:
getRandomMovie();
We then listen for the click
event on the button with the btn-next
class. If you remember the overview of the app earlier we have two buttons: like and dislike. Those buttons have the btn-next
class. So every time those are clicked, the code below is executed. What it does is call the getRandomMovie
function and supply the movie id and the action. The action can have a value of either like or dislike:
然后,我们使用btn-next
类监听按钮上的click
事件。 如果您还记得应用程序的概述,那么我们有两个按钮:“喜欢”和“不喜欢”。 这些按钮具有btn-next
类。 因此,每次单击它们时,都会执行以下代码。 它的作用是调用getRandomMovie
函数,并提供影片ID和动作。 该动作可以具有“喜欢”或“不喜欢”的值:
$('#movie-container').on('click', '.btn-next', function(){ var self = $(this); var id = self.data('id'); var action = self.data('action'); getRandomMovie({'movie_id' : id, 'action' : action});});
Going back to the server side, we’re now ready to write the code for getting a random movie from the database. First, declare a new route that responds to POST
requests to the movie/random
path:
回到服务器端,我们现在准备编写代码以从数据库中获取随机电影。 首先,声明一个新路由,该新路由响应对movie/random
路径的POST
请求:
Flight::route('POST /movie/random', array('Home', 'random'));
In the above code, we’re using the the same controller that we used earlier for rendering the home page of the app. But this time we’re using the random
method. So go ahead and declare it in your controllers/Home.php
file:
在上面的代码中,我们使用的控制器与我们之前渲染应用程序主页所使用的控制器相同。 但是这次我们使用random
方法。 因此,继续在您的controllers/Home.php
文件中声明它:
public static function random() {}
Inside the random
method, we get the details of the request and then check if a user session has been set. If there’s a user session we get the movies that have been viewed by the current user. Next, we connect to the MongoDB database using the mdb
variable that we assigned to Flight earlier. We then generate a random number from 1 to 2000. 1 represents the initial movie id that we used earlier, 2000 is the total number of movies that we have imported. I have just hard coded it in there since we already know the total number of movies and we won’t really be increasing it. After that, we just make a query to MongoDB and tell it to find all the items which has an itypes
value of 1
. Then we use the random number that we generated as an offset and tell it to limit the result to 1. Executing the query returns an iterator object, so we still need to convert the iterator to an array using the iterator_to_array
method. Once that’s done, we call the array_values
method to convert the associative array to a numeric one so we can get to the data we need by accessing the first index of the array.
在random
方法内部,我们获取请求的详细信息,然后检查是否已设置用户会话。 如果有用户会话,我们将获取当前用户已观看的电影。 接下来,我们使用之前分配给Flight的mdb
变量连接到MongoDB数据库。 然后,我们生成一个从1到2000的随机数。1代表我们之前使用的初始电影ID,2000是我们导入的电影总数。 我已经在其中进行了硬编码,因为我们已经知道了电影的总数,并且我们实际上不会增加它。 之后,我们只查询MongoDB并告诉它查找所有itypes
值为1
。 然后,我们使用生成的随机数作为偏移量,并告诉它将结果限制为1。执行查询将返回迭代器对象,因此,我们仍然需要使用iterator_to_array
方法将迭代器转换为数组。 完成此操作后,我们将调用array_values
方法将关联数组转换为数字数组,以便通过访问数组的第一个索引来获取所需的数据。
$request = Flight::request();if(!empty($_SESSION['user_id'])){ $movies_viewed = $_SESSION['movies_viewed']; $dbname = 'predictionio_appdata'; $mdb = Flight::mdb(); $db = $mdb->$dbname; $first_movie_id = 1; $last_movie_id = 2000; $skip = mt_rand($first_movie_id, $last_movie_id); //generate a random number that is between the first and last movie id $items = $db->items; //offset using the random number $cursor = $items->find(array('itypes' => '1'))->skip($skip)->limit(1); $data = array_values(iterator_to_array($cursor)); //convert iterator object to an array then convert associative array to numeric $movie = $data[0];
Next, we check if the request data contains a movie_id
. If you remember from the main JavaScript file earlier, we did not supply any data to the getRandomMovie
function on the first time the page is loaded. But when the user starts interacting with the app with the like and dislike button we pass on the movie_id
and the action. And that’s what we’re checking here. Ifmovie_id
exists, then we use the Prediction IO client to save that user interaction in the database. But first we have to extract the movie id as Prediction IO adds a prefix to the movie id that we have supplied when we imported some movies earlier. Movie ids are prefixed with the app id followed by an underscore. So we use the substr
method to extract the movie id. We do this by getting the position of the underscore and then adding 1 to it.
接下来,我们检查请求数据是否包含movie_id
。 如果您还记得之前的主JavaScript文件,则在第一次加载页面时,我们没有向getRandomMovie
函数提供任何数据。 但是,当用户开始使用“喜欢”和“不喜欢”按钮与应用进行交互时,我们会传递movie_id
和操作。 这就是我们在这里检查的内容。 如果movie_id
存在,则我们使用Prediction IO客户端将该用户交互保存在数据库中。 但是首先,我们必须提取影片ID,因为Prediction IO会在我们之前导入一些影片时提供的影片ID之前添加前缀。 电影ID前面带有应用ID,后跟一个下划线。 因此,我们使用substr
方法提取影片ID。 我们通过获取下划线的位置然后将其加1来实现。
if(!empty($request->data['movie_id'])){ $params = $request->data; $client = Flight::prediction_client(); $user_id = $_SESSION['user_id']; $movie_id = substr($params['movie_id'], strpos($params['movie_id'], '_') + 1); $action = $params['action'];
Once that’s done, call the identify
method from the Prediction IO SDK. This tells Prediction IO to assign a specific user to the actions that we’re going to perform. Next, we use the getCommand
method to create a command that will execute the record_action_on_item
method in the . What this does is just exactly as it sounds: record a user action. Valid values for this include: like, dislike, rate, view, and conversion. The record_action_on_item
method requires the pio_action
which is the user action and the pio_iid
which is the movie id. Once we have assigned those, all that’s needed is to call the execute
method to commit the user action.
完成后,从Prediction IO SDK调用identify
方法。 这告诉Prediction IO将特定用户分配给我们将要执行的操作。 接下来,我们使用getCommand
方法创建一个命令,该命令将在执行record_action_on_item
方法。 它的作用完全像听起来那样:记录用户操作。 有效值包括:喜欢,不喜欢,评价,观看和转化。 该record_action_on_item
方法需要pio_action
这是用户动作和pio_iid
这是电影的ID。 分配完这些之后,所需要做的就是调用execute
方法来提交用户操作。
$client->identify($user_id);$user_action = $client->getCommand('record_action_on_item', array('pio_action' => $action, 'pio_iid' => $movie_id))$res = $client->execute($user_action);
Next, we increment the movies viewed by 1. Then we check for the current movies viewed total. If 20 have already been viewed, we set the has_recommended
item to true
. If you remember from the main JavaScript file earlier, we are checking for the existence of this item. If it exists then we show the link to the recommended movies page. After that, we just update the movies_viewed
session to store the incremented movies viewed.
接下来,我们将观看的电影增加1。然后检查当前观看的电影总数。 如果已经查看了20个,则将has_recommended
项设置为true
。 如果您还记得以前的主JavaScript文件,我们正在检查此项是否存在。 如果存在,那么我们将显示指向推荐电影页面的链接。 在那之后,我们只是更新了movies_viewed
会话以存储观看的增量电影。
$movies_viewed += 1;if($movies_viewed == 20){ $movie['has_recommended'] = true; } $_SESSION['movies_viewed'] = $movies_viewed;
Outside the condition for checking the existence of the movie_id
, we just echo out the JSON string representation of the movie data using Flight’s json
method. This is pretty much the same as the json_encode
method that’s available for PHP:
在检查movie_id
是否存在的条件movie_id
,我们只是使用Flight的json
方法回显电影数据的JSON字符串表示形式。 这与可用于PHP的json_encode
方法几乎相同:
if(!empty($request->data['movie_id'])){ ...}Flight::json($movie);
Putting everything together we get the following:
将所有内容放在一起,我们得到以下信息:
public static function random() { $request = Flight::request(); if(!empty($_SESSION['user_id'])){ $movies_viewed = $_SESSION['movies_viewed']; $dbname = 'predictionio_appdata'; $mdb = Flight::mdb(); $db = $mdb->$dbname; $skip = mt_rand(1, 2000); $items = $db->items; $cursor = $items->find(array('itypes' => '1'))->skip($skip)->limit(1); $data = array_values(iterator_to_array($cursor)); $movie = $data[0]; if(!empty($request->data['movie_id'])){ $params = $request->data; $client = Flight::prediction_client(); $user_id = $_SESSION['user_id']; $movie_id = substr($params['movie_id'], strpos($params['movie_id'], '_') + 1); $action = $params['action']; $client->identify($user_id); $user_action = $client->getCommand('record_action_on_item', array('pio_action' => $action, 'pio_iid' => $movie_id)); $client->execute($user_action); $movies_viewed += 1; if($movies_viewed == 20){ $movie['has_recommended'] = true; } $_SESSION['movies_viewed'] = $movies_viewed; } Flight::json($movie); }}
Now that we’re done with the learning phase, it’s time to proceed with writing the code for the recommendation phase. The part that actually recommends relevant movies to the user. Note that the relevance of the results that Prediction IO returns depends on the settings that we have supplied to the movie recommendation engine earlier and the actual data that has been collected. The more data, the better the results will be. But we can’t really ask the user to rate a whole bunch of movies just to get there. I believe 20 is an ideal number.
现在我们已经完成了学习阶段,是时候为推荐阶段编写代码了。 实际上向用户推荐相关电影的部分。 请注意,Prediction IO返回的结果的相关性取决于我们之前提供给电影推荐引擎的设置以及已收集的实际数据。 数据越多,结果越好。 但是我们不能真正要求用户仅仅为了达到目标就对整部电影进行评分。 我相信20是一个理想的数字。
Create a new route that will respond to GET requests on the /movies/recommended
path:
在/movies/recommended
路径上创建一个新路由以响应GET请求:
Flight::route('GET /movies/recommended', array('Home', 'recommended'));
This route uses the recommended
method in the Home
controller. Inside the method, initialize a connection to the database and then get the items collection. Also, initialize the Prediction IO client then assign an empty array to the $recommended_movies
array. This is where we will store the data for the recommended movies later on.
此路由使用Home
控制器中的recommended
方法。 在该方法内部,初始化与数据库的连接,然后获取items集合。 另外,初始化Prediction IO客户端,然后将空数组分配给$recommended_movies
数组。 稍后我们将在这里存储推荐电影的数据。
$dbname = 'predictionio_appdata';$mdb = Flight::mdb();$db = $mdb->$dbname;$items = $db->items; //get the items collection$client = Flight::prediction_client();$recommended_movies = array();
Next, create a try catch
statement. Inside the try
block, get the current user id from the session and then use the identify
method to assign the current user. Then, use the getCommand
method to create a command that will call the itemrec_get_top_n
method in the Prediction IO API. This method returns the top recommended movies for the user that we identified it with. This method accepts the pio_engine
and pio_n
as its parameters. The pio_engine
is the name we assigned to the engine that we created earlier. pio_n
is the number of results that you want to return. In this case we’re just going to recommend nine movies. Once that’s done we just call the execute
method to make the actual request.
接下来,创建一个try catch
语句。 在try
块内,从会话中获取当前用户ID,然后使用identify
方法分配当前用户。 然后,使用getCommand
方法创建一个命令,该命令将在Prediction IO API中调用itemrec_get_top_n
方法。 此方法返回与我们一起标识的用户的最佳推荐电影。 此方法接受pio_engine
和pio_n
作为其参数。 pio_engine
是我们分配给我们先前创建的引擎的名称。 pio_n
是您要返回的结果数。 在这种情况下,我们将只推荐九部电影。 完成后,我们只需调用execute
方法发出实际请求即可。
If the call is successful it returns an array with the pio_iids
item. This contains the IDs of the recommended movies. Since we specified 9 for the pio_n
, we should get 9 movie IDs. We then use array_walk
to prefix the movie IDs with the app id. We need to prefix the movie IDs because Prediction IO only returns the actual movie IDs. This is not good because what actually gets saved in the database as the value for the movie ID is prefixed with the app id followed by an underscore. That’s why we need to add the prefix before making a query to MongoDB. If you don’t know what the app ID is, access the Prediction IO web admin interface from your browser, then select the app that you created earlier and click on any of the engines that you have. Once on the engine page the URL will look something like this:
如果调用成功,则返回带有pio_iids
项的数组。 其中包含推荐电影的ID。 因为我们为pio_n
指定了9,所以我们应该获得9个电影ID。 然后,我们使用array_walk
为影片ID加上应用ID前缀。 我们需要给影片ID加上前缀,因为Prediction IO仅返回实际的影片ID。 这不好,因为作为电影ID的值实际存储在数据库中的内容以应用程序ID为前缀,后跟一个下划线。 这就是为什么我们需要在对MongoDB进行查询之前添加前缀。 如果您不知道应用程序ID是什么,请从浏览器访问Prediction IO Web管理员界面,然后选择您先前创建的应用程序,然后单击您拥有的任何引擎。 进入引擎页面后,URL将如下所示:
http://localhost:9000/web/?appid=4&engineid=4&engineinfoid=itemrec#engine
The value of the appid
in the query parameters is the id of your app. You can use that as the prefix. In this case the appid
is 4
.
查询参数中的appid
的值是您的应用的ID。 您可以将其用作前缀。 在这个例子中, appid
是4
。
Once we have prefixed each of the array items with the app id, we can query MongoDB to get the data for each of those movie ID’s. We do that using the $in
operator. The $in
operator expects an array of items that we want to match. Next, we convert the iterator to an array. If we somehow find ourselves under the catch
block we just echo out that there’s a problem:
在为每个数组项添加了应用程序ID前缀之后,我们可以查询MongoDB以获得每个电影ID的数据。 我们使用$in
运算符来实现。 $in
运算符期望我们要匹配的项目数组。 接下来,我们将迭代器转换为数组。 如果我们以某种方式发现自己在catch
块下,我们就会回显出问题所在:
try{ $user_id = $_SESSION['user_id']; $client->identify($user_id); $command = $client->getCommand('itemrec_get_top_n', array('pio_engine' => 'movie-recommender', 'pio_n' => 9)); $recommended_movies_raw = $client->execute($command); $movie_iids = $recommended_movies_raw['pio_iids']; array_walk($movie_iids, function(&$movie_iid){ $movie_iid = '4_' . $movie_iid; }); $cursor = $items->find(array('itypes' => '1', '_id' => array('$in' => $movie_iids))); $recommended_movies = array_values(iterator_to_array($cursor)); }catch(Exception $e){ echo "Sorry there's a problem";}
If you find yourself inside the catch
block that means that the data we provided hasn’t been ‘trained’ yet. This means that Prediction IO still hasn’t done the number crunching when the user finished rating the movies. There are two solutions to this. The first is increasing the number of movies that the user has to rate. 60 is pretty safe since there are 60 seconds in a minute. We have set the training schedule to execute every minute so this is a pretty good number, not unless the user immediately clicks a random button when a movie is shown. The second method is to manually tell Prediction IO to train the data model. You can do that by clicking on the ‘Algorithms’ tab on your engine page. And on the default algorithm used, click on the ‘running’ button. A drop-down will show up and all you have to do is to click on the ‘train data model now’. This will tell Prediction IO to train the data model immediately.
如果您发现自己位于catch
块中,则意味着我们提供的数据尚未经过“培训”。 这意味着当用户完成对电影的评分时,Prediction IO仍未进行数字处理。 有两种解决方案。 首先是增加用户必须评分的电影数量。 60分钟非常安全,因为一分钟有60秒。 我们将培训计划设置为每分钟执行一次,因此这是一个不错的数字,除非在电影放映时用户立即单击随机按钮,否则这不是一个好数字。 第二种方法是手动告诉Prediction IO训练数据模型。 您可以通过点击引擎页面上的“算法”标签来做到这一点。 然后在使用的默认算法上,单击“运行”按钮。 将显示一个下拉列表,您需要做的就是单击“立即训练数据模型”。 这将告诉Prediction IO立即训练数据模型。
Lastly, we reset the values for the movies_viewed
and the user_id
in the session and then render the page for the recommended movies passing along the data that we got from the database:
最后,我们重置会话中的movies_viewed
和user_id
的值,然后呈现推荐电影的页面以及从数据库中获取的数据:
$_SESSION['movies_viewed'] = 0;$_SESSION['user_id'] = '';Flight::render('recommended', array('recommended_movies' => $recommended_movies), 'body_content');
Here’s the HTML for the recommended movies page:
这是推荐电影页面HTML:
Recommended Movies
What we’re doing is looping through the $recommended_movies
array and then echoing out the values for the relevant fields; namely the title, release date, overview and the image.
我们正在做的是遍历$recommended_movies
数组,然后回显相关字段的值。 即标题,发行日期,概述和图像。
That’s it! In this tutorial you have learned how to use Prediction IO to provide machine learning capabilities to your app. You can check this project out . We have barely scratched the surface in this series and there’s much more that you can do with it. I recommend that you check out the if you want to learn more.
而已! 在本教程中,您学习了如何使用Prediction IO为您的应用程序提供机器学习功能。 您可以检出此项目。 在本系列文章中,我们几乎没有涉及任何内容,您可以做很多事情。 如果您想了解更多信息,我建议您查看的 。
If you know of any alternatives to Prediction IO or some interesting use cases, let us know in the comments!
如果您知道Prediction IO的替代方案或一些有趣的用例,请在评论中告诉我们!
翻译自:
转载地址:http://xxrgb.baihongyu.com/