Classifier


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

Классификация может быть бинарной или многоклассовой.  

Использование моделей классификации упрощает процесс прохождения бизнес-сценария в смартапе и делает его более гибким.

Пример. В сценарии есть слот «тип банковской карты». Он должен быть заполнен одним из значений: «виртуальная карта», «золотая», «платиновая», «классическая», «моментум» и др.

Предобученная модель классификации (нейросетевая, модель на основе правил или любая другая) поможет определить, к какому конкретно классу, т. е. типу банковской карты, можно отнести ответ пользователя на вопрос про тип карты.  Эту модель классификации можно вызвать в сущности для слот-филлинга (Filler с типом classifier), и слот будет заполнен наиболее вероятным вариантом ответа, который выдаст модель.  

Где применяются классификаторы

Классификаторы применяются для работы сущностей Filler и Requirement с типом classifier

Пример использования классификатора для слот-филлинга можно увидеть в форме сценария hello. 

Конфигурация классификатора и хранение моделей

Все классификаторы должны быть описаны в файле classifiers.json, который находится в директории static/references/classifiers.

Вызывать классификатор нужно по его названию с помощью external classifier

Пример конфигурации:  

{
  "binary_classifier_name": {
    "type": "scikit",
    "threshold": 0.6,
    "path": "pretrained_binary_classifier_model.pkl",
    "intents": ["да", "нет"]
  },
  "hello_scenario_classifier": {
    "type": "external",
    "timeout": 0.2,
    "classifier": "binary_classifier_name"
  }
}

Чекпоинты предобученных моделей в pkl-файлах должны храниться в директории static/references/classifiers_data. Там же должны находиться сохраненные pkl-файлы с кастомными слоями модели, если они есть.

Если ваша предобученная модель должна принимать на вход векторное представление реплики (embedding), и вы использовали плагин SAF Vectorizers во время обучения модели, добавьте поле vectorizer в конфигурацию классификатора, в котором укажите название модели-векторизатора, например:

{
    "type": "scikit",
    "threshold": 0.7,
    "path": "pretrained_model.pkl",
    "intents": ["intent_1", "intent_2" ... "intent_n"],
    "vectorizer": "sbert"
}

Типы классификаторов

Параметр Описание
scikit

Использование обученного классификатора, который лежит в директории static/references/classifiers_data.

Возвращает intents, уверенность в которых не ниже значения threshold по результатам вызова метода predict_proba из классификатора.

Порядок выдачи метода predict_proba должен соответствовать порядку intents.

{
"type": "scikit",
"threshold": 0.6,
"path": "pretrained_binary_classifier_model.pkl",
"intents": ["да", "нет"],
"vectorizer": "sbert"

}

Значения:

  • threshold

    Float.

    Порог вероятности класса или ответа.

    Значения: 0 до 1.

  • path

    Строка.

    Наименование чекпоинта модели pkl-файла.

    Модель может быть реализована с помощью библиотеки sklearn или любого нейросетевого фреймворка и должна иметь метод predict_proba. Это pkl-файл, упакованный с помощью библиотеки dill.

    На вход модели подается tokenized_elements_list.

  • intents

    Список строк.

    Возможные классы или ответы

  • vectorizer

    Строка

    Опциональный параметр. Название модели-векторизатора из плагина SAF Vectorizers.

external

Вызов описанного в classifiers.json классификатора по имени.

{ 
 "type": "external",
"timeout": 0.2,
"classifier": "binary_classifier_name"
}

Значения:

  • timeout

    Float.

    Время, за которое должен прийти ответ от классификатора во время инференса, иначе возвращается пустой ответ. По умолчанию — 200 мс.

  • classifier

    Строка.

    Название вызываемого классификатора. Он должен быть описан в classifiers.json.

skip

Применяется, когда необходимо по формату указать классификатор, но использовать конкретное значение-результат (список intents). 

Возвращает список intents со значениями score=0, other=false

{ 
	"type": "skip",
	"intents": ["intent_name_1", "intent_name_2", ..., "intent_name_n"]
}

Формат ответа

Любой классификатор должен возвращать список наиболее вероятных вариантов ответов или классов (intents) из заданного множества, преодолевших определенный порог уверенности (threshold). Этот список должен быть отсортирован в порядке уменьшения уверенности (score).

Каждый вариант из списка должен соответствовать общему шаблону, в котором поле answer равно классу или интенту, score — величине уверенности в ответе, а other указывает на принадлежность класса к other

[
  {
    "answer": "intent_name_1",
    "score": 0.6,
    "other": false
 },
  {
    "answer": "intent_name_2",
    "score": 0.3,
    "other": false
 }
]

Имплементация классификатора

Фреймворк работает с классификаторами, созданными при помощи основных Python-библиотек scikit-learn, Tensorflow, Keras, Torch. Также можно внедрять классификаторы, построенные на основе правил.  

Любой классификатор на вход получает объект tokenized_elements_list (один из атрибутов сущности text_preprocessing_result) — список токенов с информацией о них: оригинальная и начальная формы слова, род, число, зависимые слова, тип связи между словами и т. д. Список токенов — это по сути словарь.

import pickle

pkl_loader = pickle.loads

def predict_proba(tokenized_elements_list):
	list_of_tokens = pkl_loader(tokenized_elements_list)
  1. Трансформируйте список словарей в строку, чтобы получить на выходе аналоги тех строк, на которых обучен классификатор.
  2. Векторизируйте строку, полученную после предобработки. Если вы пользовались своим векторизатором, используйте его при инференсе точно так же, как при обучении.
  

Пример функции, которая берет леммы всех токенов, кроме пунктуации:

def tokenized_elements_list_to_str(tokenized_elements_list):
    return ' '.join([token['lemma'] for token in tokenized_elements_list if 'dependency_type' in token and token['dependency_type']!='punct'])

Сохранение чекпоинта готового классификатора

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

Этот метод должен принимать на вход tokenized_elements_list, сериализованный в pkl-объект, полученный с помощью метода pickle.dumps():

class ReminderClassifier:
         
    def __init__(self):
        self.pkl_loader = pickle.loads
        self.white_list_verbs = set(["оповестить", "оповещать", "пингануть", "ткнуть", "напомнить", "напоминать", "сообщить", "сообщать"])
 
 
    def predict_proba(self, tokenized_elements_list_pkl):
        list_of_tokens = self.pkl_loader(tokenized_elements_list_pkl)
        to_return = numpy.array([[1, 0]])
        for token in list_of_tokens:
            lemma = token.get('lemma', '')
            if lemma in self.white_list_verbs:
                to_return = numpy.array([[0, 1]])
                break
        return to_return

Метод должен возвращать numpy array со списком вероятностей принадлежности каждому классу.   

Нужно извлечь из дампа этот классификатор с помощью библиотеки dill.

Если модель была построена без кастомных слоев, дамп делается следующим образом:  

import dill
 
with open("pretrained_binary_classifier_model.pkl", "wb") as out:
    dill.dump(MyBinaryClassifier(), out)

Если в модели есть кастомные слои Keras, которые нельзя напрямую импортировать из Keras, их нужно извлечь из дампа с помощью dill отдельно:   

import dill
from keras.engine.topology import Layer

class CustomLayer(Layer):
	pass

dill.settings['recurse'] = True
dill.dump(CustomLayer, open("CustomLayer.pkl", "wb"))

Если кастомных слоев несколько, процедуру нужно повторить для каждого из них. В итоге на выходе должно быть n+1 pkl-файлов, где n – число кастомных слоев.

После это нужно извлечь из дампа сам классификатор — конкретный инстанс. Обращение к кастомным слоям происходит при помощи CustomObjectScope. В файле с расширением h5 должен лежать выход функции keras.save, примененный к основному классификатору.

import pickle
import dill

from keras.models import load_model
from keras.utils import CustomObjectScope


class MyModel:

    def __init__(self):
        from CustomLayer import CustomLayer
        self.pkl_loader = pickle.loads
        with CustomObjectScope({'CustomLayer': CustomLayer}):
            self.model = load_model('my_model_cpu.h5', compile=False)
      
    def fit(self):
        pass
 
    def predict_proba(self, tokenized_elements_list_pkl):
        list_tokens = self.pkl_loader(tokenized_elements_list_pkl)
        return self.model.predict(list_tokens)
dill.settings['recurse'] = True
dill.dump(MyModel(), open("my_model.pkl", "wb"))

Ваш векторайзер также можно поместить вместе с классификатором в один pkl-файл. 

Пример конфигурации классификатора с кастомными слоями

{
    "type": "scikit",
    "threshold": 0.2,
	"is_gpu": false,
    "path": "pretrained_classifier_model.pkl",
    "intents": ["class_1", "class_2", ... , "class_n"],
  	"custom_layers": [
		{
			"layer_name": "CustomLayer",
      		"path": "CustomLayer.pkl"
    	}
  ]
}

Заметили ошибку?

Выделите текст и нажмите Ctrl + Enter, чтобы сообщить нам о ней