Машинное обучение своими руками (часть 2). Сервис для классификации обращений в тех. поддержку

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


В октябре команда облачного сервиса Okdesk приняла участие в пензенском хакатоне, в рамках которого мы разработали "коробочного" Telegram-бота для Okdesk. Бот позволит клиентам сервисных компаний отправлять заявки на обслуживание, переписываться по заявками и ставить оценки выполнению заявок не выходя из любимого мессенджера.

image

Мы планировали написать об этом статью на Хабру, но вовремя остановились. Воистину, кому сегодня интересно читать о том, что на очередном хакатоне был разработан очередной Telegram-бот? Поэтому мы написали продолжение статьи о машинном обучении для классификации заявок в тех. поддержку. В этой статьей рассказываем о том, как после обучения алгоритма сделать работающий сервис, на вход которому передается текст клиентской заявки, а на выходе — категория, к которой относится заявка.

Краткое содержание предыдущей части

Настоятельно рекомендуем прочитать первую часть перед тем, как приступить к чтению данного текста (или хотя бы добавить первую часть статьи в закладки). Ниже изложим краткое содержание.

Итак, сервисные компании оказывают услуги своим клиентам. Клиенты отправляют заявки в службу поддержки клиентов: например, "не работает интернет" или "не проходит проводка в 1С". В сервисной компании разными направлениями занимаются разные люди: проблемы с интернетом лежат в ответственности группы системных администраторов, а проблемы по 1С "падают" на группу сопровождения 1С. Распределение заявок по группам можно поручить диспетчеру, но это дополнительные расходы (зарплата) и потеря времени решения (ко времени решения заявок добавляется реакция диспетчера на распределение заявок). Логично переложить задачу распределения заявок на "умный алгоритм", который по тексту заявки сможет определить, к какому направлению она относится.

Для решения этой задачи была взята обучающая выборка из 1200 текстов заявок с проставленными категориями (14 категорий). По обучающей выборке был составлен словарь (набор имеющих значение для классификации слов), все заявки "проецировались" на словарь (т.е. каждому тексту заявки ставился в соответствие вектор в пространстве словаря), после чего на векторах-проекциях заявок был найден лучший для данной обучающей выборки алгоритм классификации. Для классификации заявок алгоритмом на вход подавался вектор в пространстве словаря, а на выходе алгоритм давал предсказание категории заявки.

Лучший алгоритм показывал на обучающей выборке 74,5% точность классификации (что весьма неплохо для 14 категорий), но благодарные читатели писали в личку, что на их данных примененный алгоритм показывал точность в 92% (а это уже вполне "продакшн" вариант).

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

Выгрузка словаря и обученного алгоритма

Напомним, что классификация новых заявок проводится в 2 этапа:

  1. Для новой заявки определяются координаты заявки в пространстве словаря;
  2. Полученный вектор передается в обученный алгоритм, который возвращает категорию заявки.

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

Далее по тексту будем использовать переменные из первой части статьи.

Выгрузка словаря

С выгрузкой словаря все просто. В первой части мы записали все слова словаря (порядок важен!) в list-переменную words. Теперь необходимо записать слова из переменной words в текстовый файл. Каждое слово будем записывать с новой строки.

# Импортируем библиотеку codecs, так как слова записаны в кодировке utf8 import codecs # Записываем слова из словаря в файл words.txt, каждое слово с новой строки with codecs.open('words.txt', 'w', encoding = 'utf8') as wordfile: wordfile.writelines(i + ' ' for i in words)

Выгрузка дампа обученного алгоритма

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

Python предлагает 2 варианта для сохранения алгоритма.

Встроенный модуль pickle:

import pickle saved = pickle.dumps(classifier) classifier2 = pickle.loads(saved)

Он позволяет сохранить дамп в переменную, а переменную можно сохранить в файл.

Библиотека joblib:

from sklearn.externals import joblib joblib.dump(classifier, 'filename.pkl')  classifier2 = joblib.load('classifier.pkl')

Она не позволяет записывать дамп модели в переменную, но сразу записывает дамп в .pkl файл.

Для нашей задачи необходимо сохранить дамп алгоритма в файл:

from sklearn.externals import joblib joblib.dump(optimazer_tree.best_estimator_, 'model_tree.pkl')

Теперь у нас есть второй файл: дамп обученного алгоритма в файле model_tree.pkl

Скрипт классификации новых заявок

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

  1. Загружать словарь из файла words.txt;
  2. Загружать обученный алгоритм из файла classifier.pkl;
  3. Проецировать в пространство словаря переданный для классификации текст;
  4. Возвращать предсказание категории переданного для классификации текста.

Приступим. Для начала импортируем необходимые библиотеки. С большинством из необходимых для работы скрипта библиотек мы познакомились либо в первой части статьи, либо (с codecs) в этой статье чуть выше. Дополнительно возникает библиотека sys — она нужна нам для работы с параметрами командной строки (из которой мы будем передавать в скрипт текст)

import numpy as np import re import sklearn import codecs Import sys

Теперь загрузим словарь и обученный алгоритм:

#Загружаем словарь из файла words.txt with codecs.open('words.txt','r', encoding = 'utf8') as wordsfile: wds = wordsfile.readlines()  #Удаляем в конце импортируемых слов символ переноса строки words = [] for i in wds:     words.append(i[:-1])  #Загружаем обученные классификатор из файла model_tree.pkl estimator = sklearn.externals.joblib.load('model_kNN.pkl')

Перед тем, как проецировать новый текст на пространство словаря, необходимо разбить текст на слова (предварительно приведя текст к нижнему регистру). Объявим соответствующие функции:

#Объявляем функцию приведения строки к нижнему регистру def lower(str):     return str.lower()  #Определяем функцию, которая разбивает строку по ряду символов и возвращает массив слов def splitstring(str):     words = []     #разбиваем строку по символам из [], символы подбирали опытным путем в первой части     for i in re.split('[;,., ,s,:,-,+,-,(,),=,-,/,«,»,-,@,-,-,d,!,?,"]',str):         words.append(i)     return words

И в завершении объявим функцию, которая принимает на вход новый текст, а на выходе выдает предсказание его категории:

#Объявляем функцию, в которую можно передать текст, а на выходе получить категорию def class_func(new_issue):     #Разбиваем текст на слова, предварительно приведя текст к нижнему регистру     new_issue_words = splitstring(lower(new_issue))     #Создаем нулевой вектор размером len(words)     new_issue_vec = np.zeros((1,len(words)))     #проставляем [j]-ю координату вектора число, равное количеству вхождений j-го слова из словаря в текст new_issue     for j in new_issue_words:         if j in words:             new_issue_vec[0][words.index(j)]+=1     #Предсказываем категорию     return estimator.predict(new_issue_vec)

Напомним, что мы планируем передавать текст новой заявки из командной строки. Для того, чтобы получить в скрипте аргументы командной строки, воспользуемся библиотекой sys. Sys.argv возвращает список аргументов командной строки, при этом нулевой элемент — название скрипта (но нам это не важно, так как названия скрипта нет в словаре; оно само исчезнет при проецировании). Таким образом, для передачи текста новой заявки в скрипт, нам необходимо "склеить" в скрипте переданные параметры командной строки (так как каждое слово из текста новой заявки будет передаваться как один аргумент):

new_issue = u'' for i in sys.argv:     #склеиваем с добавлением пробелов     new_issue += ' ' + i.decode('cp1251') class_func(new_issue)

Важно! В зависимости от используемой в консоли кодировки, в строке new_issue += ' ' + i.decode('cp1251') параметр у decode может быть другим.

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

Далее все три файла необходимо сложить на сервере в одну папку и настроить необходимое окружение (python с библиотеками). Скрипт можно запускать из командной строки любым удобным вам способом (например, написать на любимом вами языке простенький сервис, который будет получать по сети текст новой заявки, передавать его в скрипт, получать ответ и отправлять назад).

Конец! Желаем удачи в решении ваших ML-задач. Ну а мы продолжим разрабатывать Okdesk — самую удобную (по мнению нашей компании :)) helpdesk систему для обслуживания клиентов в сервисных компаниях, параллельно исследуя возможности применения “умных алгоритмов” для решения задач, связанных с обслуживанием.


Источник: habrahabr.ru

Комментарии: