Эта статья была рецензирована Хайдаром Кюлекси . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
В последние годы с ростом числа сервисов машинного обучения разработчикам стало легче, чем когда-либо, создавать «умные приложения». В этой статье я познакомлю вас с предложением Microsoft по предоставлению приложениям возможностей машинного обучения. В частности, вы узнаете об API-интерфейсе Text Analytics и создадите приложение, которое улучшит понимание онлайн-продавцов, когда дело доходит до их клиентов.
Microsoft Text Analytics API
API Text Analytics является частью Microsoft Cognitive Services , набора API-интерфейсов, цель которого — дать приложениям человеческую сторону. API Text Analytics включает в себя следующие функции:
- Анализ настроений — возвращает оценку от 0 до 1, чтобы определить и извлечь субъективное мнение о данном тексте. Числа, которые являются самыми близкими к 0, указывают отрицательное чувство, и числа, которые являются самыми близкими к 1, указывают положительное чувство.
- Извлечение ключевой фразы (извлечение терминологии) — возвращает список строк, обозначающих ключевые точки разговора в предоставленном тексте.
- Определение темы — обнаруживает тему в группе текстовых записей. Для этого требуется минимум 100 текстовых записей.
- Обнаружение языка — возвращает обнаруженные языки вместе с их оценкой достоверности, обозначающей степень уверенности движка в обнаружении языка.
Получаете ключ API?
Службы Microsoft Cognitive предлагаются как часть платформы Microsoft Azure, поэтому вам необходимо зарегистрировать учетную запись на веб-сайте Azure, если у вас ее еще нет. Прежде чем сделать это, вы должны зарегистрировать учетную запись Microsoft Live, которую затем будете использовать для регистрации в Azure.
- Создать учетную запись Microsoft Live
- Веб-сайт Microsoft Azure — нажмите кнопку « Попробовать сейчас» или любую кнопку регистрации.
После того, как вы предоставили всю необходимую информацию, вам также может потребоваться предоставить информацию о вашей кредитной карте. Вы получите бесплатный кредит в течение месяца после регистрации. API-интерфейс Text Analytics также должен быть бесплатным для 5000 транзакций в месяц . Этого должно быть более чем достаточно для тестирования.
После создания учетной записи вы будете перенаправлены на портал Azure . Оказавшись там, перейдите на панель поиска и введите когнитивные услуги . Нажмите на тот, который говорит учетные записи Cognitive Services (превью) .
Это должно показать вам интерфейс, похожий на приведенный ниже:
Нажмите на кнопку « Добавить» и заполните следующие поля:
- Имя учетной записи — вы можете выбрать любое имя, которое вы хотите.
- Тип API — выберите Text Analytics.
- Уровень цен — выберите бесплатный, который составляет 5000 звонков в месяц.
- Подписка — бесплатная пробная версия.
- Группа ресурсов — выберите существующую, если она у вас уже есть, в противном случае создайте новую группу ресурсов, выбрав новую опцию, а затем введите нужное имя.
- Местоположение — выберите Запад США.
После принятия юридических условий нажмите кнопку « Создать» . Дайте ему несколько секунд для развертывания и нажмите кнопку обновления , как только вы получите уведомление о том, что служба развернута. Это должно перечислить новый сервис. Нажмите на него, чтобы просмотреть общие сведения. Оттуда вы можете нажать на клавиши для просмотра ключей API, которые вы можете использовать при отправке запросов.
Игра с API
Теперь, когда у вас есть ключ API, вы можете играть с API на каждой из этих страниц:
Вот пример:
Все, что вам нужно сделать, это указать свой ключ API в качестве значения для заголовка Ocp-Apim-Subscription-Key . Вы также можете нажать Добавить заголовок, если есть какие-либо дополнительные заголовки, которые требуются конечной точке. Затем добавьте следующее для тела запроса:
{ "documents": [ { "id": 1, "text": "this is so very nice for getting good quality sleep" } ] }
Это общая структура для тела запроса. Просто предоставьте объект, содержащий свойство под названием documents
и он будет иметь массив объектов в качестве значения. Объект должен содержать только два свойства: id
и text
. id
должен быть уникальным, чтобы каждое предоставленное вами text
значение было уникальным образом идентифицировано.
Нажмите на кнопку Отправить , чтобы отправить запрос. Он должен дать вам следующий ответ:
Как видите, он дает ту же структуру, которую вы указали в теле запроса. Только на этот раз у него уже есть массив keyPhrases
вместо text
.
Существует также API статуса операции , который вы можете использовать только в том случае, если у вас есть ожидающая операция из любого из запросов, которые вы отправили в API. Операция считается ожидающей, когда вы не получаете ожидаемые данные в теле ответа на ваш запрос. В этих случаях API возвращает 202 Принятый для кода состояния. Вот где приходит конечная точка состояния операции. Эта конечная точка отвечает на запросы GET
на этот URL:
https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/operations/{operationId}
operationId
— это идентификатор запроса, который возвратил код состояния 202. Вы можете найти его в заголовке ответа x-aml-ta-request-id
.
Настройка проекта
Как упоминалось во введении ранее, вы создадите приложение, которое улучшит понимание онлайн-продавцов, когда дело доходит до их клиентов. Вы собираетесь построить небольшую часть бэк-энда интернет-магазина, где продавцы смогут понять, что их клиенты думают о своих продуктах. Здесь вступает в игру API текстовой аналитики. Вы получите отзывы клиентов об этом продукте Amazon и отправите их в API для анализа. Затем вы отобразите результаты в интерфейсе веб-сайта. Вот несколько скриншотов того, как будет выглядеть приложение.
Вкладка «Настроения» показывает среднее настроение, которое клиенты имеют к определенному продукту.
На вкладке ключевых фраз выделены ключевые слова и фразы в конкретном тексте отзыва.
А на вкладке «Темы» показана таблица из десяти самых обсуждаемых тем.
Установка зависимостей
Вы будете использовать скелет Слима для этого проекта. Вы можете установить его с помощью следующей команды:
composer create-project -n -s dev akrabat/slim3-skeleton sp_store
Это создаст папку sp_store
. Перейдите в эту папку и используйте Composer для установки следующих библиотек:
-
slim/pdo
— библиотека базы данных PDO для платформы Slim. -
vlucas/phpdotenv
— загружает переменные окружения для приложения. -
guzzlehttp/guzzle
— используется для выполнения запросов к API. -
maximebf/consolekit
— используется для создания команд, которые будут получать результаты от API.
База данных
Приложение также будет использовать базу данных. Вы можете найти дамп SQL схемы базы данных в этом списке . Вот что делает каждая таблица:
- запросы — хранит операции, которые еще не вернули ответ.
- отзывы — хранит отзывы для каждого товара.
- review_key_phrases — хранит ключевые фразы, найденные для каждого текста отзыва.
- review_sentiments — сохраняет оценку для каждого отзыва.
- themes — хранит темы, определенные из группы отзывов.
- review_topics — хранит темы, определенные для каждого обзора, и их соответствующее расстояние.
Если вы не хотите получать обзоры продуктов для отправки в API, вот дамп данных для таблицы отзывов, которую я использовал для тестирования. Все кредиты идут на Amazon и всех клиентов, которые оставили отзыв об этом продукте .
Конфигурирование проекта
В корне каталога проекта создайте файл .env
и добавьте следующее:
APP_NAME="SP Store" APP_BASE_URL=http://spstore.dev DB_HOST=localhost DB_NAME=sp_store DB_USER=user DB_PASS=secret CS_KEY="YOUR API KEY"
Обязательно замените APP_BASE_URL
на URL-адрес, назначенный вашему приложению, на DB_
конфигурацию DB_
с вашими учетными данными базы данных, а на CS_KEY
на ключ API, полученный от портала Microsoft Azure.
Перейдите в каталог app
и отредактируйте файл settings.php
и установите для displayErrorDetails
значение true
чтобы вы точно знали, что происходит, если что-то пойдет не так.
'displayErrorDetails' => true,
Измените разрешение папки cache
и log
на 755
. Это позволяет Slim писать в эти каталоги.
sudo chmod -R 755 cache log
Строим проект
Теперь вы готовы построить проект. Сначала вы будете работать с частью, где запросы к API.
Утилиты
Создайте следующие файлы в каталоге app/src/Lib
:
-
HttpRequest.php
— вспомогательный класс для простого выполнения http-запросов с помощью Guzzle. -
Reviews.php
— используется для взаимодействия с базой данных. -
TextAnalyzer.php
— используется дляTextAnalyzer.php
запросов к API.
Откройте файл HttpRequest.php
и добавьте следующий код:
<?php namespace App\Lib; class HttpRequest { private $headers; private $client; public function __construct() { $this->headers = [ 'Ocp-Apim-Subscription-Key' => getenv('CS_KEY'), 'Content-Type' => 'application/json', 'Accept' => 'application/json' ]; $this->client = new \GuzzleHttp\Client( ['base_uri' => 'https://westus.api.cognitive.microsoft.com'] ); } public function make($type, $endpoint, $body) { try{ $response = $this->client->request( $type, $endpoint, [ 'headers' => $this->headers, 'body' => $body ] ); $response_body = json_decode($response->getBody()->getContents(), true); if($response->getStatusCode() == 202){ $operation_id = $response->getHeaderLine('x-aml-ta-request-id'); return [ 'operation_id' => $operation_id ]; } return $response_body; } catch (RequestException $e) { if($e->hasReponse()){ $error_data = json_decode($e->getResponse()->getBody()->getContents(), true); return ['error' => $error_data]; } } } }
Разбивая приведенный выше код внутри конструктора, предоставьте данные, требуемые API, в заголовке. Это включает в себя Ocp-Apim-Subscription-Key
который является ключом API, который у вас есть. Заголовки Content-Type
и Accept
установлены в json, что означает, что тело запроса должно быть в формате json.
$this->headers = [ 'Ocp-Apim-Subscription-Key' => getenv('CS_KEY'), 'Content-Type' => 'application/json', 'Accept' => 'application/json' ]; $this->client = new \GuzzleHttp\Client( ['base_uri' => 'https://westus.api.cognitive.microsoft.com'] );
Метод make
принимает метод HTTP-запроса ( $type
), конечную точку ( $endpoint
) в API, в которой выполняется запрос, и данные, которые вы хотите отправить ( $body
).
public function make($type, $endpoint, $body) { ... }
Установите те в запросе:
$response = $this->client->request( $type, $endpoint, [ 'headers' => $this->headers, 'body' => $body ] );
Как только ответ возвращается, вызовите несколько методов из объекта $response
, чтобы получить нужные данные. API возвращает строку json, поэтому вы должны использовать json_decode
для преобразования ее в массив.
$response_body = json_decode($response->getBody()->getContents(), true);
Проверьте код статуса «Принят» (202). Если это код состояния ответа, это означает, что запрошенная вами операция еще не завершена. Поэтому вместо возврата $response_body
извлеките из заголовка x-aml-ta-request-id
. Это идентификатор запрашиваемой вами операции. Затем вы можете получить данные с этим идентификатором позже, вызвав конечную точку статуса операции get .
if ($response->getStatusCode() == 202) { $operation_id = $response->getHeaderLine('x-aml-ta-request-id'); return [ 'operation_id' => $operation_id ]; } return $response_body;
Затем откройте файл TextAnalyzer.php
и добавьте следующий код:
<?php namespace App\Lib; class TextAnalyzer { private $HttpRequest; public function __construct() { $this->HttpRequest = new HttpRequest(); } public function formatDocs($docs) { $body = [ 'documents' => $docs ]; return json_encode($body); } public function requestSentiments($docs) { $body = $this->formatDocs($docs); return $this->HttpRequest->make('POST', '/text/analytics/v2.0/sentiment', $body); } public function requestKeyPhrases($docs) { $body = $this->formatDocs($docs); return $this->HttpRequest->make('POST', '/text/analytics/v2.0/keyPhrases', $body); } public function requestTopics($docs) { $body = $this->formatDocs($docs); return $this->HttpRequest->make('POST', '/text/analytics/v2.0/topics', $body); } public function getAnalysis($request_id) { return $this->HttpRequest->make('GET', "/text/analytics/v2.0/operations/{$request_id}"); } }
Приведенный выше код говорит сам за себя, поэтому я не буду вдаваться в подробности того, что делает каждая строка. Просто знайте, что каждый метод выполняет запрос к другой конечной точке в API, используя класс HttpRequest
который вы создали ранее. Метод formatDocs
отвечает за форматирование текстовых документов так, как этого требует API. Этот метод getAnalysis
в каждом из методов, кроме метода getAnalysis
поскольку для тела запроса ничего не требуется.
Откройте файл Reviews.php
и добавьте следующее:
<?php namespace App\Lib; class Reviews { private $db; public function __construct() { $db_host = getenv('DB_HOST'); $db_name = getenv('DB_NAME'); $dsn = "mysql:host={$db_host};dbname={$db_name};charset=utf8"; $pdo = new \Slim\PDO\Database($dsn, getenv('DB_USER'), getenv('DB_PASS')); $this->db = $pdo; } public function getReviews() { $select_statement = $this->db->select(['id', 'review AS text']) ->from('reviews') ->where('analyzed', '=', 0) ->limit(100); $stmt = $select_statement->execute(); $data = $stmt->fetchAll(); return $data; } public function getSentiments() { //gets sentiments from DB $select_statement = $this->db->select() ->from('review_sentiments'); $stmt = $select_statement->execute(); $data = $stmt->fetchAll(); return $data; } public function getTopics() { $select_statement = $this->db->select(['topic', 'score']) ->from('topics') ->orderBy('score', 'DESC') ->limit(10); $stmt = $select_statement->execute(); $data = $stmt->fetchAll(); return $data; } public function getKeyPhrases() { $select_statement = $this->db->select(['review', 'key_phrases']) ->from('review_key_phrases') ->join('reviews', 'review_key_phrases.review_id', '=', 'reviews.id') ->where('analyzed', '=', 1) ->limit(10); $stmt = $select_statement->execute(); $data = $stmt->fetchAll(); return $data; } public function saveSentiments($sentiments) { foreach ($sentiments as $row) { $review_id = $row['id']; $score = $row['score']; $insert_statement = $this->db->insert(['review_id', 'score']) ->into('review_sentiments') ->values([$review_id, $score]); $insert_statement->execute(); } } public function saveRequest($request_id, $request_type) { $insert_statement = $this->db->insert(['request_id', 'request_type', 'done']) ->into('requests') ->values([$request_id, $request_type, 0]); $insert_statement->execute(); } public function updateRequest($request_id) { $update_statement = $this->db->update(['done' => 1]) ->table('requests') ->where('request_id', '=', $request_id); $update_statement->execute(); } public function saveTopics($topics_data) { $topics = $topics_data['topics']; foreach ($topics as $row) { $topic_id = $row['id']; $topic = $row['keyPhrase']; $score = $row['score']; $insert_statement = $this->db->insert(['topic_id', 'topic', 'score']) ->into('topics') ->values([$topic_id, $topic, $score]); $insert_statement->execute(); } $review_topics = $topics_data['review_topics']; foreach ($review_topics as $row) { $review_id = $row['documentId']; $topic_id = $row['topicId']; $distance = $row['distance']; $insert_statement = $this->db->insert(['review_id', 'topic_id', 'distance']) ->into('review_topics') ->values([$review_id, $topic_id, $distance]); $insert_statement->execute(); } } public function saveKeyPhrases($key_phrases) { foreach ($key_phrases as $row) { $review_id = $row['id']; $phrases = json_encode($row['keyPhrases']); $insert_statement = $this->db->insert(['review_id', 'key_phrases']) ->into('review_key_phrases') ->values([$review_id, $phrases]); $insert_statement->execute(); } } public function getPendingRequests() { $select_statement = $this->db->select() ->from('requests') ->where('done', '=', 0); $stmt = $select_statement->execute(); $data = $stmt->fetchAll(); return $data; } public function setDone($from_id, $to_id) { $update_statement = $this->db->update(['analyzed' => 1]) ->table('reviews') ->whereBetween('id', [$from_id, $to_id]); $update_statement->execute(); } public function getAverageSentiment() { $select_statement = $this->db->select() ->from('review_sentiments') ->avg('score', 'avg_sentiment'); $stmt = $select_statement->execute(); $data = $stmt->fetch(); return $data['avg_sentiment']; } }
Опять же, это довольно очевидно. Внутри конструктора вы подключаетесь к базе данных. Каждый метод в классе выполняет или выбор, обновление или вставку запроса к определенной таблице в базе данных.
Командный класс
В этом разделе вы создадите класс, который расширяет библиотеку Console Kit. Это позволяет вам выполнять запросы к API в определенное время, используя cron.
Начните с создания файла Commands/Analyze.php
каталоге app/src
и добавьте следующий код:
<?php require 'vendor/autoload.php'; use \App\Lib\TextAnalyzer; use \App\Lib\Reviews; class AnalyzeCommand extends ConsoleKit\Command { public function execute(array $args, array $options = array()) { $dotenv = new \Dotenv\Dotenv(__DIR__ . '/../../..'); $dotenv->load(); $reviews = new Reviews(); $text_analyzer = new TextAnalyzer(); //check if there are pending requests $pending_requests = $reviews->getPendingRequests(); foreach ($pending_requests as $request) { $request_id = $request['request_id']; $from_id = $request['from_review']; $to_id = $request['to_review']; $response = $text_analyzer->getAnalysis($request_id); if (strtolower($response['status']) == 'succeeded') { $result = $response['operationProcessingResult']; $topics = $result['topics']; $review_topics = $result['topicAssignments']; $reviews->saveTopics([ 'topics' => $topics, 'review_topics' => $review_topics ]); $reviews->setDone($from_id, $to_id); $reviews->updateRequest($request_id); } } $docs = $reviews->getReviews(); $total_docs = count($docs); if ($total_docs == 100) { $from_review = $docs[0]['id']; $to_review = $docs[$total_docs - 1]['id']; $sentiments_response = $text_analyzer->requestSentiments($docs); $reviews->saveSentiments($sentiments_response['documents']); $this->writeln('saved sentiments!'); $key_phrases_response = $text_analyzer->requestKeyPhrases($docs); $reviews->saveKeyPhrases($key_phrases_response['documents']); $this->writeln('saved key phrases!'); $topics_request_id = $text_analyzer->requestTopics($docs); $reviews->saveRequest($topics_request_id, 'topics', $from_review, $to_review); $this->writeln('topics requested! request ID: ' . $topics_request_id); } $this->writeln('Done!', ConsoleKit\Colors::GREEN); } } $console = new ConsoleKit\Console(); $console->addCommand('AnalyzeCommand'); $console->run();
Разбивка кода выше. Во-первых, требуется файл vendor/autoload.php
чтобы вы могли использовать все библиотеки, а также служебные классы, которые вы создали ранее.
require 'vendor/autoload.php'; use \App\Lib\TextAnalyzer; use \App\Lib\Reviews;
Внутри метода execute
инициализируйте библиотеку dotenv, чтобы вы могли получить переменные конфигурации.
$dotenv = new \Dotenv\Dotenv(__DIR__ . '/../../..'); $dotenv->load();
Инициализируйте два служебных класса:
$reviews = new Reviews(); $text_analyzer = new TextAnalyzer();
Получить все операции, которые еще не были завершены. В базе данных они хранятся в таблице запросов . Все строки, которые имеют значение 0
в done
столбце, возвращаются.
$pending_requests = $reviews->getPendingRequests();
getAnalysis
цикл по всем ожидающим запросам и запросите анализ, вызвав метод getAnalysis
из объекта $text_analyzer
. Обратите внимание, что $request_id
— это идентификатор операции, возвращаемый API, когда вы сделали запрос к определенной конечной точке API. Вы продолжите сохранение результатов только в том случае, если статус был успешным . Это означает, что запрос был обработан и данные анализа готовы к получению. Ниже вы принимаете во внимание только результат конечной точки обнаружения темы. Это связано с тем, что конечные точки настроений и ключевых фраз возвращают данные сразу же после выполнения запроса. topicAssignments
ключе topicAssignments
и topicAssignments
поэтому извлеките их и сохраните в базе данных, вызвав метод saveTopics
из объекта $reviews
. После этого вы вызываете метод setDone
чтобы щелкнуть переключателем « setDone
для всех проанализированных проверок, чтобы они не были выбраны для анализа при следующем setDone
команды. То же самое верно и для операции, метод updateRequest
устанавливает выполнение операции, чтобы вы не делали запрос к той же операции позже.
foreach ($pending_requests as $request) { $request_id = $request['request_id']; $from_id = $request['from_review']; $to_id = $request['to_review']; $response = $text_analyzer->getAnalysis($request_id); if (strtolower($response['status']) == 'succeeded') { $result = $response['operationProcessingResult']; $topics = $result['topics']; $review_topics = $result['topicAssignments']; $reviews->saveTopics([ 'topics' => $topics, 'review_topics' => $review_topics ]); $reviews->setDone($from_id, $to_id); $reviews->updateRequest($request_id); } }
Получить обзоры продуктов из базы данных. Метод getReviews
ограничивает результат до 100 строк. Это связано с тем, что для работы конечной точки темы требуется минимум 100 записей. Вот почему вы также проверяете, является ли общее количество возвращенных документов 100, прежде чем продолжить. Если условие возвращает true
, определите идентификатор первой и последней строк, которые были возвращены. Сохраните эту информацию в таблице запросов , вызвав метод saveRequest
. Это те же идентификаторы, которые вы использовали ранее в коде для обработки ожидающих операций.
Затем запросите данные из конечной точки настроений, вызвав метод requestSentiments
. Как упоминалось ранее, эта конечная точка немедленно возвращает данные анализа, чтобы вы могли сохранить их в таблице review_sentiments , вызвав метод saveSentiments
. Вы также делаете то же самое с конечной точкой ключевых фраз. Что касается конечной точки тем, вы ожидаете получить идентификатор операции только при вызове метода requestTopics
, поэтому сохраните его в переменной $topics_request_id
и сохраните операцию в базе данных. Таким образом, он будет выбран для обработки при следующем запуске команды.
$docs = $reviews->getReviews(); $total_docs = count($docs); if ($total_docs == 100) { $from_review = $docs[0]['id']; $to_review = $docs[$total_docs - 1]['id']; $sentiments_response = $text_analyzer->requestSentiments($docs); $reviews->saveSentiments($sentiments_response['documents']); $this->writeln('saved sentiments!'); $key_phrases_response = $text_analyzer->requestKeyPhrases($docs); $reviews->saveKeyPhrases($key_phrases_response['documents']); $this->writeln('saved key phrases!'); $topics_request_id = $text_analyzer->requestTopics($docs); $reviews->saveRequest($topics_request_id, 'topics', $from_review, $to_review); $this->writeln('topics requested! request ID: ' . $topics_request_id); }
Как только вы закончите с этим, сохраните файл и выполните следующую команду в корневом каталоге вашего проекта:
php app/src/Commands/Analyze.php analyze
Убедитесь, что у вас есть не менее 100 записей в таблице обзоров, и вы .env
действительный ключ API в файле .env
когда вы это сделаете.
Маршруты
Откройте файл public/index.php
и инициализируйте библиотеку dotenv непосредственно перед вызовом $app->run
.
$dotenv = new Dotenv\Dotenv('../'); $dotenv->load(); // Run! $app->run();
Откройте файл app/routes.php
. Он должен содержать следующий код:
<?php // Routes $app->get('/', App\Action\HomeAction::class) ->setName('homepage');
Маршрут по умолчанию использует файл HomeAction.php
каталоге app/src/Action
. Откройте его и добавьте следующий код:
<?php namespace App\Action; use Slim\Views\Twig; use Psr\Log\LoggerInterface; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use \App\Lib\Reviews; use \App\Lib\TextAnalyzer; use \App\Lib\TextFormatter; final class HomeAction { private $view; private $logger; public function __construct(Twig $view, LoggerInterface $logger) { $this->view = $view; $this->logger = $logger; $filter = new \Twig_SimpleFilter('highlight', function ($item) { $key_phrases = json_decode($item['key_phrases'], true); $highlighted_key_phrases = array_map(function($value){ return "<span class='highlight'>{$value}</span>"; }, $key_phrases); return str_replace($key_phrases, $highlighted_key_phrases, $item['review']); }); $this->view->getEnvironment()->addFilter($filter); } public function __invoke(Request $request, Response $response, $args) { $reviews = new Reviews(); $text_analyzer = new TextAnalyzer(); $avg_sentiment = $reviews->getAverageSentiment(); $key_phrases = $reviews->getKeyPhrases(); $topics = $reviews->getTopics(); $labels = ['Good', 'Bad']; $colors = ['#46BFBD', '#F7464A']; $highlights = ['#5AD3D1', '#FF5A5E']; $first_value = $avg_sentiment; $second_value = 1 - $avg_sentiment; if($second_value > $first_value){ $labels = array_reverse($labels); $colors = array_reverse($colors); $highlights = array_reverse($highlights); } $sentiments_data = [ [ 'value' => $first_value, 'label' => $labels[0], 'color' => $colors[0], 'highlight' => $highlights[0] ], [ 'value' => $second_value, 'label' => $labels[1], 'color' => $colors[1], 'highlight' => $colors[1] ] ]; $page_data = [ 'app_name' => getenv('APP_NAME'), 'sentiments_data' => json_encode($sentiments_data), 'key_phrases' => $key_phrases, 'topics' => $topics ]; $this->view->render($response, 'home.twig', $page_data); } }
Разбирая код, приведенный выше, сначала потребуйте все библиотеки, которые вам нужны:
use Slim\Views\Twig; use Psr\Log\LoggerInterface; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use \App\Lib\Reviews; use \App\Lib\TextAnalyzer; use \App\Lib\TextFormatter;
Внутри конструктора добавьте пользовательский фильтр веток, который позволяет выделять определенные слова или фразы внутри фрагмента текста. Этот пользовательский фильтр принимает каждый $item
содержащий ключевые фразы и текст отзыва.
Значение для $key_phrases
берется из таблицы review_key_phrases в базе данных, которая, если вы помните, является строкой json. Вот почему вам нужно преобразовать его обратно в массив, используя json_decode
. Затем, используйте array_map
чтобы обернуть элементы в массиве в класс с подсветкой. Затем вы нацеливаетесь на это позже, используя CSS для выделения текста Наконец, используйте str_replace
чтобы заменить все вхождения $key_phrases
на $key_phrases
в тексте обзора.
$filter = new \Twig_SimpleFilter('highlight', function ($item) { $key_phrases = json_decode($item['key_phrases'], true); $highlighted_key_phrases = array_map(function($value){ return "<span class='highlight'>{$value}</span>"; }, $key_phrases); return str_replace($key_phrases, $highlighted_key_phrases, $item['review']); });
Внутри метода __invoke
находится код, который вы хотите выполнить при доступе к домашней странице. Здесь вы выбираете и форматируете все данные, которые вам нужны для страницы.
$reviews = new Reviews(); $text_analyzer = new TextAnalyzer(); $avg_sentiment = $reviews->getAverageSentiment(); $key_phrases = $reviews->getKeyPhrases(); $topics = $reviews->getTopics(); $labels = ['Good', 'Bad']; $colors = ['#46BFBD', '#F7464A']; $highlights = ['#5AD3D1', '#FF5A5E']; $first_value = $avg_sentiment; $second_value = 1 - $avg_sentiment; if ($second_value > $first_value) { $labels = array_reverse($labels); $colors = array_reverse($colors); $highlights = array_reverse($highlights); } $sentiments_data = [ [ 'value' => $first_value, 'label' => $labels[0], 'color' => $colors[0], 'highlight' => $highlights[0] ], [ 'value' => $second_value, 'label' => $labels[1], 'color' => $colors[1], 'highlight' => $colors[1] ] ]; $page_data = [ 'app_name' => getenv('APP_NAME'), 'sentiments_data' => json_encode($sentiments_data), 'key_phrases' => $key_phrases, 'topics' => $topics ]; $this->view->render($response, 'home.twig', $page_data);
Разбивая вышеприведенный код, сначала запросите среднее настроение, ключевые фразы и темы, которые в данный момент хранятся в базе данных.
$avg_sentiment = $reviews->getAverageSentiment(); $key_phrases = $reviews->getKeyPhrases(); $topics = $reviews->getTopics();
Объявите данные, которые будут использоваться диаграммой на странице. Вы будете использовать круговую диаграмму для представления настроений покупателей в обзоре. Ниже представлены три массива по два элемента в каждом. Это потому, что для продукта может быть только два возможных чувства: хорошее или плохое. Здесь вы предполагаете, что среднее настроение, которое вы получили из базы данных, представляет собой хорошую сторону.
$labels = ['Good', 'Bad']; $colors = ['#46BFBD', '#F7464A']; $highlights = ['#5AD3D1', '#FF5A5E'];
Рассчитайте разницу между 1 и средним настроением, которое вы получили. Это даст вам процент для другой половины пирога (плохая сторона).
$first_value = $avg_sentiment; $second_value = 1 - $avg_sentiment;
Если другая половина пирога больше среднего настроения, поменяйте местами все массивы, которые вы объявили ранее. Это потому, что данные по умолчанию предполагают, что среднее настроение — это хорошая сторона.
if ($second_value > $first_value) { $labels = array_reverse($labels); $colors = array_reverse($colors); $highlights = array_reverse($highlights); }
Отформатируйте данные таким образом, чтобы они могли быть легко использованы клиентским скриптом.
$sentiments_data = [ [ 'value' => $first_value, 'label' => $labels[0], 'color' => $colors[0], 'highlight' => $highlights[0] ], [ 'value' => $second_value, 'label' => $labels[1], 'color' => $colors[1], 'highlight' => $colors[1] ] ];
Создайте данные, которые будут предоставлены странице, а затем визуализируйте страницу. Обратите внимание, что вы конвертируете $sentiments_data
в json, чтобы вы могли отобразить его на странице как значение для переменной JavaScript.
$page_data = [ 'app_name' => getenv('APP_NAME'), 'sentiments_data' => json_encode($sentiments_data), 'key_phrases' => $key_phrases, 'topics' => $topics ]; $this->view->render($response, 'home.twig', $page_data);
Внешний интерфейс
Откройте файл app/templates/home.twig
и добавьте следующее:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ app_name }}</title> <link rel="stylesheet" href="/lib/mui/packages/cdn/css/mui.min.css"> <link rel="stylesheet" href="/css/style.css"> <script src="/lib/mui/packages/cdn/js/mui.min.js"></script> <script src="/lib/Chart.min.js"></script> <script> var sentiments_data = {{ sentiments_data|raw }} </script> </head> <body> <header class="mui-appbar mui--z1"> <strong id="app-name">{{ app_name }}</strong> </header> <div id="content-wrapper" class="mui--text-center"> <ul class="mui-tabs__bar"> <li class="mui--is-active"> <a data-mui-toggle="tab" data-mui-controls="pane-default-1">Sentiments</a> </li> <li> <a data-mui-toggle="tab" data-mui-controls="pane-default-2">Key Phrases</a> </li> <li> <a data-mui-toggle="tab" data-mui-controls="pane-default-3">Topics</a> </li> </ul> <div class="mui-tabs__pane mui--is-active" id="pane-default-1"> <canvas id="sentiments_chart" width="400" height="400"></canvas> </div> <div class="mui-tabs__pane" id="pane-default-2"> <ul class="align-left"> {% for row in key_phrases %} <li>{{ row | highlight|raw }}</li> {% endfor %} </ul> </div> <div class="mui-tabs__pane" id="pane-default-3"> <table class="mui-table mui-table--bordered"> <thead> <tr> <th>Topic</th> <th>Score</th> </tr> </thead> <tbody> {% for row in topics %} <tr> <td>{{ row.topic }}</td> <td>{{ row.score }}</td> </tr> {% endfor %} </tbody> </table> </div> </div> <script src="/js/main.js"></script> </body> </html>
Пользовательский интерфейс Материал используется для оформления приложения:
<link rel="stylesheet" href="/lib/mui/packages/cdn/css/mui.min.css">
Chart.js используется для круговой диаграммы. Вы можете скачать Chart.js из cloudflare . Вы также можете приобрести Chart.js через bower или npm, но учтите, что в данном руководстве используется версия 1.1.1. Также обратите внимание, что есть некоторые изменения API с более новой версией, которая на момент написания находится в стадии бета-тестирования Если вы хотите использовать это, вам придется обновить код для файла main.js
<script src="/lib/Chart.min.js"></script>
Это единственные зависимости для внешнего интерфейса.
Внутри внутреннего скрипта присвойте значение переменной sentiments_data
строке json, которую вы передали из контроллера ранее. Обратите внимание на использование raw
фильтра Twig. Это позволяет визуализировать строку json как есть.
<script> var sentiments_data = {{ sentiments_data|raw }} </script>
Для основного содержимого страницы у вас есть три вкладки: одна для круговой диаграммы настроений, одна для ключевых фраз и одна для тем.
Для круговой диаграммы настроений у нас есть холст с предопределенной шириной и высотой.
<div class="mui-tabs__pane mui--is-active" id="pane-default-1"> <canvas id="sentiments_chart" width="400" height="400"></canvas> </div>
На вкладке ключевых фраз просматривайте результаты, возвращаемые базой данных. Затем внутри цикла примените фильтр highlight
и raw
текста. Вы уже видели, как работает фильтр highlight
поэтому объяснять это не нужно. Что касается raw
фильтра, он вам нужен, потому что фильтр highlight
выводит html, поэтому вы используете его для предотвращения выхода html.
<div class="mui-tabs__pane" id="pane-default-2"> <ul class="align-left"> {% for row in key_phrases %} <li>{{ row | highlight|raw }}</li> {% endfor %} </ul> </div>
Для вкладки тем используйте таблицу, чтобы отобразить десятку лучших тем вместе с каждым баллом.
<div class="mui-tabs__pane" id="pane-default-3"> <table class="mui-table mui-table--bordered"> <thead> <tr> <th>Topic</th> <th>Score</th> </tr> </thead> <tbody> {% for row in topics %} <tr> <td>{{ row.topic }}</td> <td>{{ row.score }}</td> </tr> {% endfor %} </tbody> </table> </div>
Создайте файл public/js/main.js
и добавьте следующее:
var sentiments_ctx = document.getElementById('sentiments_chart').getContext("2d"); var sentiments_chart = new Chart(sentiments_ctx).Pie(sentiments_data);
Это код для создания круговой диаграммы на основе данных, хранящихся в переменной sentiments_data
.
Наконец, создайте файл public/css/main.css
. Он содержит следующий код:
#content-wrapper { width: 500px; margin: 0 auto; } li { margin-bottom: 20px; } .mui-table { text-align: left; } #app-name { font-size: 30px; } header { padding: 10px; } .mui-tabs__pane { padding-top: 40px; } .align-left { text-align: left; } span.highlight { background-color: #FAFA22; padding: 5px; }
Вывод
Это оно! Из этого руководства вы узнали, как использовать API-интерфейс Text Analytics от Microsoft, чтобы дать онлайн-продавцам лучшее представление о том, насколько хорошо работают их продукты. В частности, вы использовали чувства, ключевые фразы и функции определения тем API.
Вы можете найти исходный код проекта в репозитории Github .
Я рекомендую вам зайти на веб-сайт Microsoft Cognitive Services, чтобы узнать, какие еще хорошие качества машинного обучения они предлагают. Вы использовали какие-либо из этих когнитивных услуг? Ваше мнение? Как мы могли бы улучшить наше приложение? Дайте нам знать об этом в комментариях!