Краткий обзор обработки естественного языка

Вы когда-нибудь злились на друга за то, что он долгое время не отвечал? Возможно, вы просто ждали подтверждения или просто хотели отвлечься от других вещей. Какой бы ни была причина, благодаря достижениям в обработке естественного языка или, для краткости, НЛП, как разработчик вы можете внести изменения.

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

Превратив друга в чат-бота, вы всегда будете рядом с ним, несмотря ни на что. Он не будет таким умным, как ваш друг, но все равно будет чат-ботом. Означает ли это, что тебе не одиноко? Может быть, это так. Это действительно хорошая идея? Возможно, это не так, но мы определенно сможем узнать интересные вещи о НЛП по пути.

Что нам понадобится?

Для начала нам понадобятся некоторые данные для работы, а именно ваша история сообщений с другом, которого вы хотите превратить в чат-бота. Несмотря на изображение героя, в этой статье предполагается, что вы используете Messenger от Facebook. Но если вы используете другую платформу и можете получить оттуда необработанные данные, это тоже нормально.

Мы также собираемся использовать natural, надежный инструментарий для обработки естественного языка в JavaScript. Единственное, что нам понадобится, это встроенный fs модуль npm для чтения и записи наборов данных.

Если у вас все готово, давайте перейдем к следующему шагу и посмотрим, как это на самом деле будет работать.

Как это работает?

Итак, как это работает? В первую очередь нам нужно будет получить данные из Facebook. Чем больше сообщений у нас будет, тем лучше будет конечный результат. После получения сообщений нам нужно будет проанализировать их, чтобы удалить всю ненужную информацию, метаданные, такие как временные метки, и нерелевантные сообщения, такие как общие стикеры или вложения. Нас интересует только текст.

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

Вот почему так важно иметь достаточно данных - чем шире диапазон данных, тем лучше конечные результаты. Для справки, я буду работать с историей сообщений, состоящей примерно из 18 тыс. Строк данных.

Получение набора данных

Чтобы экспортировать сообщения, перейдите в настройки своей учетной записи Facebook и щелкните третью вкладку слева: «Ваша информация Facebook».

Вы увидите вариант с надписью «Загрузите вашу информацию». Нажав на «Просмотр», вы попадете на новую страницу. Здесь вы можете запросить копию собранной вами информации Facebook. Вы можете экспортировать множество вещей, но сейчас нас интересуют сообщения. Итак, нажмите «Отменить выбор всех», чтобы убрать флажки из каждой категории, и прокрутите вниз, чтобы выбрать только сообщения.

Также важно изменить формат с HTML на JSON, так как с ним легче работать. Все остальное может остаться прежним. Как только вы нажмете «Создать файл», он начнет создавать для вас архив, что может занять некоторое время, но как только он будет готов, вы получите уведомление и сможете скачать подготовил для вас zip файл.

Настройка проекта

Когда у вас будут готовы данные, мы можем приступить к настройке проекта. Я создал папку проекта, а внутри у меня есть подпапка с именем data. Здесь мы будем хранить все данные, необходимые для классификации.

В загруженном zip-файле у вас должна быть папка «Входящие», содержащая все ваши сообщения с именами людей, которые будут именами папок для каждого разговора. Откройте тот, с которым хотите работать, и скопируйте файл message.json в папку data, которую мы только что создали.

А также здесь, давайте получим единственную зависимость, которую мы собираемся использовать, которая является естественной. Вы можете сбросить его, запустив npm i natural после вашего npm init.

Анализ данных

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

Если вы откроете файл message.json, вы заметите, что у нас есть куча нерелевантной информации, которую можно рассматривать как шум для нашего алгоритма обучения. В самом начале у нас есть массив participants. Вы можете избавиться от него сразу, в результате чего останется только массив messages.

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

Для этого я создал файл с именем parser.js в корне моего проекта, а также добавил сценарий с именем parse в файл package.json, который запускается parser.js с node parser.js.

Чтобы полностью понять, что мы собираемся реализовать, давайте посмотрим на конечный результат:

Для обучающих данных, которые будут использоваться для обучения нашего классификатора, нам нужны метки. Внутри каждой метки у нас будет массив inputs и outputs, содержащий сообщения и их ответы из исходной истории Messenger. Это то, что наш классификатор будет использовать для самообучения. Какой ответ дать на каждый ввод. Ему даже удастся дать нам хорошие ответы на вводимые данные, которых он никогда раньше не видел.

Перед тем, как приступить к самому синтаксическому анализу, нам нужно определить некоторые переменные и вспомогательные функции:

В самой первой строке мы извлекаем модуль fs, который поможет нам прочитать историю сообщений и записать окончательные данные обучения, которые будут использоваться для обучения нашего классификатора. Затем мы определяем объект message, который будет содержать все наши входы и выходы с соответствующими метками, а также определяем схему для каждого отдельного сообщения, используя переменную messageObject.

Затем мы устанавливаем источник ввода для сообщений, которые мы читаем, и источник вывода для данных обучения.

Я также определил две вспомогательные функции. Сообщения не хранятся в кодировке UTF-8, поэтому нам нужен декодер. Это то, что вы видите в строке: 14 и еще одну функцию, которая помогает определить, было ли сообщение отправлено вами (в этом случае это будет ввод) или вашим другом. (в этом случае это будет выход)

Это поможет нам поместить отдельные последовательные сообщения в один и тот же массив. То же самое касается двух переменных в строке: 24 и 25, которые будут действовать как флаги.

Перед тем, как начать фактический синтаксический анализ, мы также можем перевернуть массив, чтобы последние сообщения располагались вверху, а самые старые - внизу:

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

Для каждого сообщения мы хотим определить, кто был отправителем. Я или вы? Если это я, то это будет вход. Если это вы, то это будет выход. Мы помещаем сообщение в соответствующий массив и устанавливаем флаг input или output в значение true соответственно.

И если они оба установлены на true, а отправитель следующего сообщения отличается от текущего, мы можем создать новую метку, содержащую массивы как inputs, так и outputs. Затем мы возвращаем все к исходному значению и начинаем заново. Обратите внимание, что нам нужно использовать Object.assign для создания копий исходного объекта, иначе мы начали бы заполнять объект messages пустыми массивами.

Когда мы все закончили, мы записываем созданный объект в output файл, используя UTF-8 в качестве кодировки.

Запуск npm run parse выполнит файл JavaScript, и вы должны увидеть trainingData.json файл, созданный в папке данных.

Классификация

Классификация будет намного проще, чем анализ данных. Мы можем сделать это, используя всего пару строчек. Для обучения я снова создал отдельный файл с именем train.js и новый скрипт в package.json, чтобы мы могли выполнить npm run train для выполнения train.js файла.

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

Начнем с импорта FileSystem API и Natural. Поскольку мы хотим работать с обучающими данными, они нам нужны в качестве входных, и наши сгенерированные выходные данные будут файлом classifier.json.

Мы храним данные обучения внутри переменной trainingData, а также определяем тип классификатора, который хотим использовать. В Natural мы можем выбирать между различными классификаторами. Мы используем здесь классификатор логистической регрессии.

Чтобы обучить классификатор, мы перебираем обучающие данные. Для каждой метки мы перебираем входные данные внутри них и вызываем classifier.addDocument, передавая inputs индивидуально, а также делая все в нижнем регистре - это сделает классификатор более точным - и label для их использования.

addDocument также принимает массив, поэтому мы могли бы просто сделать: classifier.addDocument(traininData[label].inputs, label);, но таким образом у нас будет больше детализированных выборок, что даст нам большую точность.

И если мы достигаем конца обучающих данных, мы вызываем classifier.train, чтобы обучить его. Чтобы также сделать обученный классификатор многоразовым, мы сохраняем его в месте вывода, которое мы определили в строке: 5, с classifier.save.

Окончательные результаты

Чтобы проверить все, что мы сделали, мы можем создать окончательный сценарий для обработки входного текста и создания для него выходных данных. Еще раз я создал отдельный скрипт с отдельной командой, сделав файл package.json таким:

Я добавил тестовый сценарий, который запускает test.js. И для этого мы оживляем нашего чат-бота примерно с 20 строками кода:

Сначала мы импортируем все зависимости, а также собираемся использовать созданные нами обучающие данные. Затем мы определяем ввод, который можно передать прямо из командной строки. Итак, мы можем сделать npm run test "Hey". Здесь переданная строка будет входом.

Затем мы загружаем classifier.json, который мы сгенерировали ранее, и в качестве функции обратного вызова мы выполняем classifier.getClassifications, чтобы получить классификацию для ввода и сохранить ее в переменной guesses. Это даст нам массив с метками и значениями вероятности. Чем выше value, тем больше мы можем быть уверены, что у нас есть совпадение. Помните, мы установили порог в 90%.

Затем мы выбираем сообщение с наивысшей оценкой с reduce, и если вероятность этого меньше 90%, мы возвращаемся к общему сообщению и возвращаемся. В противном случае мы читаем outputs из нашего trainingData.json с меткой наиболее вероятного предположения и выбираем случайный ответ из массива outputs.

Классификатор может обрабатывать предложения, которых он никогда раньше не видел, и по-прежнему может выдавать правильные результаты.

Больше никаких «seen» и никаких сцен, так вы превращаете своего друга в чат-бота, который ждет только ваших сообщений. 🤖