Время прочтения: 6 мин.

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

Важно отметить, что датасет является лишь образцом (в нем не более 80 строк), а в идеале он должен состоять из нескольких тысяч строк, поэтому я буду использовать однотипные вопросы. И тем не менее ничто не мешает вам увеличить его собственноручно или подать на вход свои данные 😊

В силу того, что датасет не является большим, я не буду затрагивать аспекты сохранения модели, с последующей её загрузкой, так как модель обучится очень быстро и нет нужды её сохранять. Не стоит задерживаться на этом этапе, так как загрузка и сохранение моделей заслуживают отдельной истории, скажу лишь то, что если вы планируете использовать её для своих проектов с большим количеством данных, то в этом вам помогут библиотеки pickle или joblib.

Модель будет получать на вход какой-то вопрос, и, в свою очередь, должна его классифицировать. Например, если пользователь спросит: «В каком году отменили крепостное право», модель должна ответить «Дата». Она не будет отвечать на вопрос, а просто классифицирует вопрос в определенную категорию.

Алгоритм выполнения таков:

1. Загружаем датасет

2. Очищаем данные

3. Обучаем модель

4. Делаем выводы 😊

 Итак, давайте посмотрим на часть моего импровизированного датасета:

Откуда готовилось нападение на Беларусь @ Местоположение
Где находится полярный круг @ Местоположение
Сколько стоит доллар @ Количество
Когда началась вторая Мировая @ Дата
В каком году была построена Эйфелева башня @ Дата
Сколько стоят помидоры @ Количество
Какой средний рост у баскетболистов @ Количество
Почему светит солнце @ Информация о предмете
Почему желтеют листья @ Информация о предмете
Как накопить на автомобиль @ Инструкция
Как открыть вино без штопора @ Инструкция
Кто создал радио @ Имя
Кто убил Марка @ Имя

Датасет представляет из себя простой текстовый файл, имеющий название 3.txt и с использованием кодировки UTF-8, где вопрос идет до знака @, а классификация указана после этого знака. Вы можете скопировать эти данные и проделать этот путь вместе со мной для лучшего понимания.

Плавно переходим к практике

Для работы я буду использовать среду google colab и по умолчанию в ней не установлена одна из необходимых библиотек – Pystemmer, сделать это можно командой pip install pystemmer или !pip install pystemmer.

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

Далее импортирую библиотеки:

import numpy as np
import re
from Stemmer import Stemmer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report

Создаю функцию для обработки текста

def cleaner(txt):
  txt = txt.lower() # приведение букв в нижний регистр 
  stemmer = Stemmer('russian')
  txt = ' '.join( stemmer.stemWords( txt.split() ) ) 
  txt = re.sub( r'\b\d+\b', ' digit ', txt ) # заменяем цифры 
  return  txt 

  Далее загружаю данные из файла 3.txt и разделяю этот датасет.

def load_data():   
    data = {'txt':[],'tag':[]}
    for line in open('3.txt'):
        if not('#' in line):
            row = line.split("@") 
            data['txt'] += [row[0]] # вопрос
            data['tag'] += [row[1]] # категория(ответ)
    data['tag'] = [i.replace(’\n’, ’’) for i in data['tag']] # убираем ’\n’ (переходы между строками) для корректного отображения
    return data

Подхожу к завершающей стадии подготовки датасета.

В этом блоке компьютер проходит по циклам и кладет списки в переменные X и Y. Validation_split – процент тренировочный данных, используемый для валидации. Как правило за стандарт берут от 20% (0.2) до 30%(0.3) при достаточном объеме данных, но в случае с малым количеством данных, достаточно будет и 10%(0.1). Используя интервалы, данные возвращаются в виде train и test

def train_test_split(data, validation_split = 0.1):
    sz = len(data['txt'])
    indices = np.arange(sz)
    np.random.shuffle(indices) 

    X = [data['txt'][i] for i in indices]
    Y = [data['tag'][i] for i in indices]
    nb_validation_samples = int( validation_split * sz )

    return { 
        'train': {'x': X[:-nb_validation_samples], 'y': Y[:-nb_validation_samples]},
        'test': {'x': X[-nb_validation_samples:], 'y': Y[-nb_validation_samples:]}}

Теперь пора обучить модель и посмотреть результат. Я не буду вдаваться в глубокие математические вычисления и теоретическую базу, а обучу модель из “коробки”. В этом помогут библиотеки sklearn:TfidVectorizer(ссылка на документацию) для векторизации (так как машина понимает лишь цифры, нам необходимо перевести текст в векторные числа) и SGDClassifier(ссылка на документацию) – непосредственно сам алгоритм обучения. Обернём это в пайплайн (Pipeline выполняет, своего рода, роль класса, принимает настройки/преобразования) для большего удобства

def ai():
    data = load_data()
    D = train_test_split(data)
    text_clf = Pipeline([
                    ('tfidf', TfidfVectorizer(preprocessor = cleaner)),
                    ('clf', SGDClassifier()),
                    ])
    text_clf.fit(D['train']['x'], D['train']['y'])
    y_pred = text_clf.predict(D['test']['x'])
    stats = classification_report(D['test']['y'],y_pred)
    
    
    a = input("Введите интересующий вас вопрос: ")
    aa = []
    aa.append(a)
    y_pred = text_clf.predict(aa) 
    print(y_pred[0])
    print(stats)
ai()

Запущу ячейку с функцией, на что компьютер предлагает ввести вопрос, параллельно выведя метрику данной модели

Вопросом будет — ‘Сколько вешать в граммах’, параллельно выведя показатели метрик, стоит отметить, что не важно будет ли вопрос в конце предложения или нет.

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