Статьи

PredictionIO и Lumen: создание приложения с рекомендациями для фильмов

Во введении мы рассмотрели основы PredictionIO и установили его зависимости. В этой части мы собираемся создать приложение с рекомендациями для фильма.

Финальный скриншот приложения

Файл конфигурации среды

Внутри каталога приложения создайте файл .env и добавьте следующую конфигурацию:

 APP_ENV = local APP_DEBUG = true APP_KEY = some - random - key PIO_KEY = your - pio - app - key TMDB_KEY = your - tmdb - api - key CACHE_DRIVER = file SESSION_DRIVER = file QUEUE_DRIVER = database 

Обязательно замените значение APP_KEY уникальным случайным ключом. Поскольку мы используем Lumen, вы можете сгенерировать его, выполнив php artisan key:generate . Также замените значение для PIO_KEY ключом созданного вами приложения PredictionIO, а TMDB_KEY — ключом API, предоставленным веб-сайтом TMDB.

Импорт данных из TMDB

Мы будем импортировать данные с помощью PredictionIO SDK, поэтому сначала нужно указать Lumen, чтобы они использовались. Создайте каталог Classes в lumen/app . Затем внутри него создайте файл Pio.php и добавьте следующий код.

 <?php namespace App \ Classes ; use predictionio \ EventClient ; use predictionio \ EngineClient ; class Pio { public function eventClient ( ) { $pio_accesskey = env ( 'PIO_KEY' ) ; $pio_eventserver = 'http://127.0.0.1:7070' ; return new EventClient ( $pio_accesskey , $pio_eventserver ) ; } public function predictionClient ( ) { $pio_predictionserver = 'http://127.0.0.1:8192' ; return new EngineClient ( $pio_predictionserver ) ; } } 

Этот класс будет служить контейнером для клиента событий PredictionIO и клиента механизма. Таким образом, нам не нужно создавать экземпляры этих классов каждый раз, когда нам нужно их использовать.

Клиент событий используется для общения с сервером событий, который отвечает за сбор данных для нашего приложения. Клиенту нужен ключ приложения и URL-адрес, на котором работает сервер событий. По умолчанию он работает на порту 7070.

Клиент двигателя, с другой стороны, используется для общения с двигателем, который отвечает за обслуживание рекомендаций.

Затем откройте файл bootstrap/app.php и раскомментируйте промежуточное программное обеспечение, отвечающее за обработку сеансов. Это позволяет нам сохранить уникальный идентификатор пользователя, который в настоящее время оценивает некоторые фильмы.

 $app - > middleware ( [ //Illuminate\Cookie\Middleware\EncryptCookies::class, //Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, Illuminate\ Session \ Middleware \ StartSession : : class , //Illuminate\View\Middleware\ShareErrorsFromSession::class, //Laravel\Lumen\Http\Middleware\VerifyCsrfToken::class, ] ) ; 

Создайте новый контроллер и назовите его AdminController.php . Контроллеры хранятся в каталоге app/Http/Controllers . Установите его для использования класса Pio который мы создали ранее.

 <?php namespace App \ Http \ Controllers ; use Laravel \ Lumen \ Routing \ Controller as BaseController ; use App \ Repos \ Pio ; class AdminController extends BaseController { } 

Создайте метод importMovies . Мы будем использовать этот метод для импорта фильмов из API TMDB:

 public function importMovies ( Pio $pio ) { $index = 1 ; $pio_eventclient = $pio - > eventClient ( ) ; $http_client = new \ GuzzleHttp \ Client ( ) ; $es_client = new \ Elasticsearch \ Client ( ) ; for ( $x = 1 ; $x <= 100 ; $x ++ ) { $movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=' . env ( 'TMDB_KEY' ) . '&page=' . $x ; $movies_response = $http_client - > 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=' . env ( 'TMDB_KEY' ) ; $moviedetails_response = $http_client - > get ( $moviedetails_url ) ; $movie_details_body = $moviedetails_response - > getBody ( ) ; $movie = json_decode ( $movie_details_body , true ) ; $overview = $movie [ 'overview' ] ; $release_date = $movie [ 'release_date' ] ; $genre = '' ; if ( ! empty ( $movie [ 'genres' ] [ 0 ] ) ) { $genre = $movie [ 'genres' ] [ 0 ] [ 'name' ] ; } $popularity = $movie [ 'popularity' ] ; $movie_data = [ 'itypes' = > 1 , 'tmdb_id' = > $id , 'title' = > $title , 'poster_path' = > $poster_path , 'overview' = > $overview , 'release_date' = > $release_date , 'genre' = > $genre , 'popularity' = > $popularity , ] ; $pio_response = $pio_eventclient - > setItem ( $index , $movie_data ) ;  //create elasticsearch index $params = [ ] ; $params [ 'body' ] = $movie_data ; $params [ 'index' ] = 'movierecommendation_app' ; $params [ 'type' ] = 'movie' ; $params [ 'id' ] = $index ; $es_res = $es_client - > index ( $params ) ; $index ++ ; } } } } 

Разбивая это:

  1. Мы importMovies класс Pio в метод importMovies

  2. Инициализируйте индекс до 1. Это будет служить уникальным идентификатором для фильмов, которые мы собираемся импортировать.

  3. Вызовите метод eventClient в классе Pio . Это инициализирует клиент событий PredictionIO, который мы можем использовать для сохранения данных фильма.

  4. Создайте новый экземпляр клиента Guzzle HTTP, клиента событий PredictionIO и клиента ElasticSearch.

     $index = 1 ; $pio_eventclient = $pio - > eventClient ( ) ; $http_client = new \ GuzzleHttp \ Client ( ) ; $es_client = new \ Elasticsearch \ Client ( ) ; 
  5. Создайте цикл, который будет выполняться 100 раз. Это позволяет нам получить около 2000 фильмов, потому что каждый запрос к API TMDB возвращает 20 фильмов. Каждая итерация цикла меняет значение на $x которое мы используем для доступа к следующей странице для каждой итерации.

     for ( $x = 1 ; $x <= 100 ; $x ++ ) { . . . } 
  6. Внутри цикла мы делаем запрос к API TMDB, используя Guzzle. Поскольку мы хотим получить как можно больше лайков, мы делаем запрос на самые популярные фильмы всех времен. Мы получаем значение для api_key из файла .env . Затем мы используем метод get предоставленный Guzzle, для выполнения запроса. И затем мы получаем тело ответа, используя метод getBody . Это в основном строка JSON, содержащая все 20 фильмов и их детали. Мы конвертируем это в массив с помощью json_decode и извлекаем результаты.

     $movies_url = 'https://api.themoviedb.org/3/movie/popular?api_key=' . env ( 'TMDB_KEY' ) . '&page=' . $x ; $movies_response = $http_client - > get ( $movies_url ) ; $movies_body = $movies_response - > getBody ( ) ; $movies_result = json_decode ( $movies_body , true ) ; $movies = $movies_result [ 'results' ] ; 
  7. Проверьте, содержит ли оно что-нибудь. Если это произойдет, мы продолжим цикл по всем возвращенным фильмам, чтобы получить дополнительную информацию об этом, сделав еще один запрос к API TMDB. После того, как мы сделали запрос, мы извлекаем необходимые нам данные

     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=' . env ( 'TMDB_KEY' ) ; $moviedetails_response = $http_client - > get ( $moviedetails_url ) ; $movie_details_body = $moviedetails_response - > getBody ( ) ; $movie = json_decode ( $movie_details_body , true ) ; $overview = $movie [ 'overview' ] ; $release_date = $movie [ 'release_date' ] ; $genre = '' ; if ( ! empty ( $movie [ 'genres' ] [ 0 ] [ 'name' ] ) ) { $genre = $movie [ 'genres' ] [ 0 ] [ 'name' ] ; } $popularity = $movie [ 'popularity' ] ; } } 
  8. Создайте массив, содержащий подробности, которые мы хотим предоставить в PredictionIO, а затем вызовите метод setItem чтобы сохранить его. Этот метод принимает уникальный идентификатор, который мы хотим присвоить элементу, и фактические данные в качестве второго аргумента.

     $movie_data = array ( 'itypes' = > 1 , 'tmdb_id' = > $id , 'title' = > $title , 'poster_path' = > $poster_path , 'overview' = > $overview , 'release_date' = > $release_date , 'genre' = > $genre , 'popularity' = > $popularity ) ; $pio_response = $pio_eventclient - > setItem ( $index , $movie_data ) ; 
  9. Индексируйте данные фильма на сервере ElasticSearch. Мы будем использовать это позже, чтобы показать подробную информацию о случайных фильмах, которые мы будем рекомендовать пользователю, а также фактическую рекомендацию, которую вернет PredictionIO. Обратите внимание, что мы используем $index в качестве идентификатора, поэтому нам также нужно увеличивать его для каждой итерации цикла.

     $params = array ( ) ; $params [ 'body' ] = $movie_data ; $params [ 'index' ] = 'movierecommendation_app' ; $params [ 'type' ] = 'movie' ; $params [ 'id' ] = $index ; $es_res = $es_client - > index ( $params ) ; $index ++ ; 
  10. Наконец, мы можем добавить маршрут, к которому мы будем обращаться, чтобы начать импорт некоторых фильмов. Откройте файл app/Http/routes.php и добавьте следующий маршрут:

     $app - > get ( '/movies/import' , 'AdminController@importMovies' ) ; 

После этого путь /movies/import становится доступным в браузере, чтобы начать импорт некоторых фильмов из TMDB. Если вы не хотите делать это таким образом, вы также можете использовать команды .

Это может занять некоторое время, чтобы пойти выпить чашку кофе или посмотреть эпизод вашего любимого шоу.

Выбор случайных фильмов

Теперь, когда у нас есть несколько фильмов, мы готовы показать некоторые случайные для пользователя. Сначала создайте новый контроллер, назовите его HomeController.php затем добавьте следующий код:

 <?php namespace App \ Http \ Controllers ; use Illuminate \ Http \ Request ; use Laravel \ Lumen \ Routing \ Controller as BaseController ; use App \ Repos \ Pio ; class HomeController extends BaseController { public function index ( Pio $pio ) { } } 

Внутри метода index uniqid уникальный идентификатор, используя встроенный в uniqid метод uniqid , а затем присвойте его user_id сеанса user_id . Кроме того, инициализируйте movies_viewed со значением 0 . Это будет представлять количество фильмов, которые мы показали текущему пользователю. Мы будем увеличивать его позже по мере появления случайных фильмов. Затем мы используем клиент событий, чтобы сохранить пользователя в базе данных. Мы можем сделать это, вызвав метод setUser который принимает идентификатор пользователя в качестве аргумента. Наконец, мы визуализируем страницу index .

 $user_id = uniqid ( ) ; session ( array ( 'user_id' = > $user_id , 'movies_viewed' = > 0 ) ) ; $pio_eventclient = $pio - > eventClient ( ) ; $pio_eventclient - > setUser ( $user_id ) ; return view ( 'index' ) ; 

Не забудьте добавить соответствующий маршрут в файл маршрутов:

 $app - > get ( '/' , 'HomeController@index' ) ; 

Вот код для index страницы, которую мы рендерим:

 <!DOCTYPE html> < html lang = " en " > < head > < meta charset = " UTF-8 " > < title > </ title > < link rel = " stylesheet " href = " /assets/css/bootstrap.min.css " > < link rel = " stylesheet " href = " /assets/css/style.css " > </ head > < body > < div id = " wrapper " > < div class = " navbar navbar-default " > < div class = " navbar-header " > < button type = " button " class = " navbar-toggle " data-toggle = " collapse " data-target = " .navbar-responsive-collapse " > < span class = " icon-bar " > </ span > < span class = " icon-bar " > </ span > < span class = " icon-bar " > </ span > </ button > < a class = " navbar-brand " href = " " > Movie Recommender </ a > </ div > < div class = " navbar-collapse collapse navbar-responsive-collapse " > < ul class = " nav navbar-nav " > < li > < a href = " / " > Home </ a > </ li > </ ul > </ div > </ div > < div class = " container " > < div class = " row " > < div id = " movie-container " class = " col-md-10 col-centered " > </ div > </ div > < script id = " movie-template " type = " text/x-handlebars-template " > < div class = "col-md-8" > < img src = "http://image.tmdb.org/t/p/w500{{_source.poster_path}}" > < / div > < div class = "col-md-4" > < h3 > { { _source . title } } < / h3 > < div class = "release-date" > { { _source . release_date } } < / div > < div class = "genre" > Genre : { { _source . genre } } < / div > < div class = "overview" > { { _source . overview } } < / div > < div class = "button-container" > < button class = "btn btn-success btn-block btn-next" data - id = "{{_id}}" data - action = "like" > Like < / button > < button class = "btn btn-danger btn-block btn-next" data - id = "{{_id}}" data - action = "dislike" > Dislike < / button > < a href = "/movies/recommended" class = "show-recommendations" > Show Recommendations < / a > < / div > < / div > </ script > < span class = " label label-success " > </ span > </ div > </ div > < script src = " /assets/js/jquery.min.js " > </ script > < script src = " /assets/js/bootstrap.min.js " > </ script > < script src = " /assets/js/handlebars.min.js " > </ script > < script src = " /assets/js/main.js " > </ script > </ body > </ html > 

Как видно из приведенного выше кода, мы в основном используем клиентские шаблоны для рендеринга деталей фильма. Для этого приложения мы используем рули . Мы загружаем детали каждого фильма, используя ajax.

Из приведенного выше кода вы можете видеть, что мы используем Bootstrap для стилизации. У нас также есть базовый стиль для всего приложения, который добавлен в файл style.css :

 .col-centered { float : none ; margin : 0 auto ; } .button-container { margin-top : 20 px ; } .show-recommendations { display : none ; } #recommended-movies > div { height : 1000 px ; } 

Для сценариев мы используем jQuery , файл JavaScript Bootstrap, Handlebars и основной файл JavaScript для приложения.

Для основного 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 } ) ; } ) ; 

Разбивая его, мы сначала скомпилируем шаблон Handlebars, который хранится в div с идентификатором movie-template :

 var movie_src = $ ( "#movie-template" ) . html ( ) ; var movie_template = Handlebars . compile ( movie_src ) ; 

Затем мы объявляем метод getRandomMovie . Это принимает request_data в качестве необязательного параметра. Внутри функции мы используем метод post jQuery для выдачи POST запроса к movie/random пути. Это возвращает случайные данные фильма с сервера в формате JSON. Затем мы преобразуем его в объект, который может использоваться JavaScript с JSON.parse метода JSON.parse . Как только это будет сделано, мы добавим его в шаблон Handlebars, который мы скомпилировали ранее, а затем обновим содержимое элемента movie-container . Если в возвращаемых данных есть элемент has_recommended , мы показываем ссылку, которая приведет пользователя на страницу, где отображаются фильмы, рекомендованные PredictionIO.

 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 ( ) ; 

Затем мы прослушиваем событие click для кнопки с btn-next . Если вы помните обзор приложения ранее, у нас есть две кнопки: нравится и не нравится . Эти кнопки имеют btn-next . Поэтому каждый раз, когда на них нажимают, выполняется код ниже. Он вызывает функцию 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 } ) ; } ) ; 

Возвращаясь к серверной части, теперь мы готовы написать код для получения случайного фильма из базы данных. Сначала объявите новый маршрут, который отвечает на запросы POST к movie/random пути:

 $app - > post ( '/movie/random' , 'HomeController@randomMovie' ) ; 

В приведенном выше коде мы используем тот же контроллер, который мы использовали ранее для рендеринга домашней страницы приложения. Но на этот раз мы используем метод randomMovie . Так что продолжайте и объявите это в вашем файле app/Http/controllers/HomeController.php . Мы собираемся использовать класс Request в этом методе, поэтому мы передадим его в качестве параметра. Это позволяет нам получить пользовательский ввод, который был передан в HTTP-запросе. И не забудьте также перейти в класс Pio .

 public function randomMovie ( Request $request , Pio $pio ) { . . . } 

Внутри метода randomMovie :

  1. Мы получаем детали запроса и затем проверяем, был ли установлен сеанс пользователя. Если есть сеанс пользователя, мы получаем количество фильмов, которые были просмотрены текущим пользователем.

  2. Объявите новый экземпляр клиента ElasticSearch, затем мы получим случайный фильм, сгенерировав случайное значение от 1 до 1000, используя функцию PHP mt_rand . Если вы помните ранее, когда мы импортировали фильмы в ElasticSearch, в качестве значения идентификатора мы использовали индекс, который мы увеличивали на 1 для каждой итерации цикла. Вот почему это работает. Получив ответ, мы просто извлекаем детали, которые нам нужны.

  3. Проверьте, есть ли в movie_id значение movie_id . Если есть, то это означает, что пользователь оценивает фильм.

  4. Вызовите метод recordUserActionOnItem в клиенте событий PredictionIO. Это принимает действие в качестве первого аргумента. Если вы помните, ранее мы настраивали движок так, чтобы он мог принимать « нравится» или « не нравиться» как форму рейтинга. Это действие, о котором мы говорим. Второй аргумент — это идентификатор пользователя, выполняющего действие, а третий — идентификатор оцениваемого фильма.

  5. Увеличьте количество просматриваемых фильмов и проверьте, просмотрено ли уже 20 фильмов. Если это так, то мы передаем дополнительное поле has_recommended для деталей фильма. Существование этого поля будет затем проверено на стороне клиента, чтобы показать ссылку на страницу рекомендаций. Затем мы сохраняем просмотренные фильмы в сеанс, а затем возвращаем детали фильма.

 if ( session ( 'user_id' ) ) { $movies_viewed = session ( 'movies_viewed' ) ; $es_client = new \ Elasticsearch \ Client ( ) ; $search_params [ 'index' ] = 'movierecommendation_app' ; $search_params [ 'type' ] = 'movie' ; $search_params [ 'id' ] = mt_rand ( 1 , 1000 ) ; $movie = $es_client - > get ( $search_params ) ; if ( ! empty ( $request - > input ( 'movie_id' ) ) ) { $user_id = session ( 'user_id' ) ; $movie_id = $request - > input ( 'movie_id' ) ; $action = $request - > input ( 'action' ) ; $pio_eventclient = $pio - > eventClient ( ) ; $pio_eventclient - > recordUserActionOnItem ( $action , $user_id , $movie_id ) ; $movies_viewed + = 1 ; if ( $movies_viewed == 20 ) { $movie [ 'has_recommended' ] = true ; } $movie [ 'movies_viewed' ] = $movies_viewed ; session ( [ 'movies_viewed' = > $movies_viewed ] ) ; } return $movie ; } 

Рекомендовать фильмы

Теперь, когда мы закончили с фазой обучения, пришло время приступить к написанию кода для фазы рекомендации.

Сначала создайте новый маршрут, который будет отвечать на запросы GET по пути /movies/recommended путь:

 $app - > get ( '/movies/recommended' , 'HomeController@recommendedMovies' ) ; 

Внутри recommendedMovies метода фильмов:

  1. Создайте новый экземпляр клиента PredictionIO Engine. Обратите внимание, что это отличается от клиента событий, который мы использовали, поскольку он используется для фактического получения результатов прогнозирования от движка. Имея это в виду, теперь мы делаем запрос, используя метод sendQuery . Это принимает массив в качестве аргумента. Массив должен содержать user и num качестве его элементов. user — идентификатор пользователя, а num — количество фильмов, которые мы хотим вернуть движку.

  2. Если запрос выполнен успешно, мы используем метод array_map для извлечения только идентификаторов фильмов. Метод array_map принимает функцию, которая возвращает нужный нам элемент, и массив, которым мы хотим манипулировать, в качестве второго аргумента. Это возвращает массив идентификаторов фильма.

  3. Создайте новый экземпляр клиента ElasticSearch и выполните запрос к индексу movierecommendation_app . Затем мы можем передать идентификаторы фильмов в качестве поискового запроса. Далее мы используем метод search и передаем параметры поиска. Это возвращает детали фильмов, которые были возвращены клиентом PredictionIO Engine.

  4. Сбросьте количество просмотров фильмов и установите для идентификатора пользователя значение null чтобы в следующий раз, когда кто-то использует приложение, оно создало нового пользователя. Наконец, мы визуализируем recommended_movies Recommended_movies и передаем детали фильма.

 public function recommendedMovies ( Pio $pio ) { $recommended_movies = array ( ) ; try { $user_id = session ( 'user_id' ) ; $pio_predictionclient = $pio - > predictionClient ( ) ; $recommended_movies_raw = $pio_predictionclient - > sendQuery ( array ( 'user' = > $user_id , 'num' = > 9 ) ) ; $movie_ids = array_map ( function ( $item ) { return $item [ 'item' ] ; } , $recommended_movies_raw [ 'itemScores' ] ) ; $es_client = new \ Elasticsearch \ Client ( ) ; $search_params [ 'index' ] = 'movierecommendation_app' ; $search_params [ 'type' ] = 'movie' ; $search_params [ 'body' ] [ 'query' ] [ 'bool' ] [ 'must' ] [ 'terms' ] [ '_id' ] = $movie_ids ; $es_response = $es_client - > search ( $search_params ) ; $recommended_movies = $es_response [ 'hits' ] [ 'hits' ] ; } catch ( Exception $e ) { echo 'Caught exception: ' , $e - > getMessage ( ) , "\n" ; } session ( array ( 'movies_viewed' = > 0 , 'user_id' = > null ) ) ; return view ( 'recommended_movies' , array ( 'recommended_movies' = > $recommended_movies ) ) ; } 

Вот HTML-код для рекомендуемой страницы фильмов:

 <!DOCTYPE html> < html lang = " en " > < head > < meta charset = " UTF-8 " > < title > </ title > < link rel = " stylesheet " href = " /assets/css/bootstrap.min.css " > < link rel = " stylesheet " href = " /assets/css/style.css " > </ head > < body > < div id = " wrapper " > < div class = " navbar navbar-default " > < div class = " navbar-header " > < button type = " button " class = " navbar-toggle " data-toggle = " collapse " data-target = " .navbar-responsive-collapse " > < span class = " icon-bar " > </ span > < span class = " icon-bar " > </ span > < span class = " icon-bar " > </ span > </ button > < a class = " navbar-brand " href = " " > Movie Recommender </ a > </ div > < div class = " navbar-collapse collapse navbar-responsive-collapse " > < ul class = " nav navbar-nav " > < li > < a href = " / " > Home </ a > </ li > </ ul > </ div > </ div > < div class = " container " > < div class = " row " > < h1 > Recommended Movies </ h1 > < div id = " recommended-movies " class = " col-md-12 " > <?php foreach($recommended_movies as $rm){ ?> < div class = " col-md-6 " > <img src="http://image.tmdb.org/t/p/w500 <?php echo $rm['_source']['poster_path'] ?> " alt=" <?php echo $rm['_source']['title'] ?> "> < h4 > <?php echo $rm['_source']['title']; ?> </ h4 > < div class = " release-date " > <?php echo $rm['_source']['release_date']; ?> </ div > < div class = " genre " > <?php echo $rm['_source']['genre']; ?> </ div > < div class = " overview " > <?php echo $rm['_source']['overview']; ?> </ div > </ div > <?php } ?> </ div > </ div > </ div > </ div > </ body > </ html > 

То, что мы делаем выше, это циклически перебирать массив $recommended_movies а затем выводить значения для соответствующих полей: заголовок, дата выпуска, жанр, обзор и изображение.

Развертывание двигателя

На данный момент мы готовы развернуть движок — нам нужно назначить ему приложение. Для этого перейдите в каталог, где хранится ваш движок, и откройте файл engine.json . Это должно выглядеть примерно так:

 { "id" : "default" , "description" : "Default settings" , "engineFactory" : "wern.RecommendationEngine" , "datasource" : { "params" : { "appName" : "INVALID_APP_NAME" } } , "algorithms" : [ { "name" : "als" , "params" : { "rank" : 10 , "numIterations" : 20 , "lambda" : 0.01 , "seed" : 3 } } ] } 

Нам нужно изменить две вещи: appName в объекте appId и appId в этом же объекте. Если вы не уверены, что такое идентификатор приложения, вы можете выполнить команду pio app list в своем терминале. Это должно выглядеть примерно так:

список приложений PIO

Просто скопируйте значение в столбце ID.

После обновления ваш файл engine.json должен выглядеть примерно так:

 { "id" : "default" , "description" : "Default settings" , "engineFactory" : "wern.RecommendationEngine" , "datasource" : { "params" : { "appId" : 1 , "appName" : "MovieRecommendationApp" } } , "algorithms" : [ { "name" : "als" , "params" : { "rank" : 10 , "numIterations" : 20 , "lambda" : 0.01 , "seed" : 3 } } ] } 

Далее нам нужно собрать движок, выполнив команду pio build в корневом каталоге вашего движка. Это загружает все файлы, которые нужны движку, и запекает их в движок. Это может занять некоторое время в зависимости от скорости вашего компьютера и интернет-соединения. Я рекомендую добавить опцию --verbose чтобы вы могли точно видеть, что происходит.

Как только это будет сделано, он должен показать что-то похожее на следующее:

 [INFO] [Console$] Your engine is ready for training. 

Как только вы это увидите, вы можете получить доступ к приложению в своем браузере и начать любить и не любить фильмы, пока не появится ссылка, ведущая на страницу с рекомендациями к фильмам. Чтобы обучить данные, вам нужно выполнить команду pio train . Убедитесь, что вы все еще находитесь в каталоге движка, когда выполняете это.

Если тренировка прошла успешно, она должна показать что-то вроде этого:

 [ INFO ] [ CoreWorkflow$ ] Training completed successfully . 

Если нет, вы, вероятно, получили что-то вроде следующего:

 [ ERROR ] [ Executor ] Exception in task 0.0 in stage 42.0 

Если это так, вы можете попробовать изменить numIterations в свойстве algorithms вашего файла engine.json . Для меня изменение его на 10 сработало.

 "algorithms" : [ { "name" : "als" , "params" : { "rank" : 10 , "numIterations" : 10 , "lambda" : 0.01 , "seed" : 3 } } ] 

Ошибка связана с входными данными и доступной памятью на компьютере: проблема в том, что Apache Spark требуется много памяти, чтобы подготовить данные для определенного числа итераций. Чем меньше итераций требуется, тем меньше памяти потребуется. Если вашей системе не хватает памяти, вы получите эту ошибку. Вот почему уменьшение количества итераций работает.

Далее вы можете развернуть движок с помощью команды pio deploy --port 8192 . Это развернет механизм рекомендаций, и он будет доступен через порт 8192 вашего компьютера. Он должен показывать некоторую информацию о движке и сервере при доступе к http://IP:8192 в браузере, где IP — это IP-адрес вашего локального компьютера ( 192.168.10.10 в случае Homestead Improved ). Вы также можете вернуться в браузер и перейти на страницу рекомендаций к фильму. Теперь он должен содержать некоторые рекомендации фильма.

Теперь вы можете добавить команды pio train и pio deploy в ваш crontab, чтобы они выполнялись каждые 5 минут. Таким образом, он постоянно работает в фоновом режиме для обучения новых данных и развертывается, когда будет готов.

 */5 * * * * cd /path/to/engine; pio train */5 * * * * cd /path/to/engine; pio deploy 

Вывод

Из этого руководства вы узнали, как использовать PredictionIO для предоставления возможностей машинного обучения вашему PHP-приложению. Вы можете проверить репозиторий Github этого проекта, чтобы увидеть полный исходный код.

В этой серии мы только немного поцарапали поверхность, и с помощью PredictionIO вы можете сделать гораздо больше. Я рекомендую вам проверить официальные документы, если вы хотите узнать больше.

Если вам известны какие-либо альтернативы PredictionIO или некоторые интересные варианты использования, сообщите нам об этом в комментариях!