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

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

За много лет развития искусственного интеллекта для решения подобных задач было придумано множество инструментов. Работу с одним из них мы рассмотрим на примере простой задачи.

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

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

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

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

  • Поиск в новостях имен людей, названий организаций или мест
  • Автоматическая классификация результатов поиска по категориям
  • Предложение правильного написания поисковых запросов
  • И прочие

Для решения задачи классификации были собраны 40-50 статей по каждой из предложенных тематик: охота, рыбалка, путешествия, отдых на природе:

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

import com.aliasi.classify.Classification;

import com.aliasi.classify.Classified;

import com.aliasi.classify.ConfusionMatrix;

import com.aliasi.classify.DynamicLMClassifier;

import com.aliasi.classify.JointClassification;

import com.aliasi.classify.JointClassifier;

import com.aliasi.classify.JointClassifierEvaluator;

import com.aliasi.classify.LMClassifier;

import com.aliasi.lm.NGramProcessLM;

import com.aliasi.util.AbstractExternalizable;

import java.io.File;

import java.io.IOException;

import com.aliasi.util.Files;

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

private static File TRAINING_DIR = new File("path/to/train/files");
    private static File TESTING_DIR  =  new File("path/to/test/files");
    private static String[] CATEGORIES
        = { "Travelling",
            "Hunting",
            "Fishing",
            "Camping" };

    private static int NGRAM_SIZE = 8;

Параметр NGRAM_SIZE отвечает за разбивку текста в файлах на последовательности из слов для их дальнейшего участия в алгоритмах классификации. Параметр может принимать значение от 1 до 16, с увеличением значения увеличивается как точность классификации, так и время необходимое на обучение модели. Для нашей задачи было выбрано значение 8, как оптимальное в отношении скорость/качество для обучения модели.

Фреймворк предоставляет несколько вариантов построения классификаторов для моделей ограниченного символьного языка, токенизированных языковых моделей и для  моделей натурального языка. Последний мы будем использовать в нашем примере. Для создания классификатора достаточно использовать статический метод createNGramProcess() класса DynamicLMClassifier:

DynamicLMClassifier<NGramProcessLM> classifier
            = DynamicLMClassifier.createNGramProcess(CATEGORIES,NGRAM_SIZE);

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

После создания классификатора можно приступать к созданию и обучению модели.

Обучение происходит вызовом метода handle() классификатора

File file = new File(classDir,trainingFiles[j]);
                String text = Files.readFromFile(file,"ISO-8859-1");
                System.out.println("Training on " + CATEGORIES[i] + "/" + trainingFiles[j]);
                Classification classification
                    = new Classification(CATEGORIES[i]);
                Classified<CharSequence> classified
                    = new Classified<CharSequence>(text,classification);
                classifier.handle(classified);

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

        JointClassifier<CharSequence> compiledClassifier
            = (JointClassifier<CharSequence>)
            AbstractExternalizable.compile(classifier);

Для валидации обученной модели воспользуемся классом JointClassifierEvaluator:

JointClassifierEvaluator<CharSequence> evaluator
            = new JointClassifierEvaluator<CharSequence>(compiledClassifier,
                                                         CATEGORIES,
                                                         storeCategories);

Валидация происходит по аналогии с классификатором — через метод handle() на который подается объект Classified<CharSequence>. Также существует скомпилированная версия валидатора для ускорения вычислений :

                System.out.print("Testing on " + CATEGORIES[i] + "/" + testingFiles[j] + " ");
                Classification classification = new Classification(CATEGORIES[i]);
                Classified<CharSequence> classified = new Classified<CharSequence>(text,classification);
                evaluator.handle(classified);
                JointClassification jc =
                    compiledClassifier.classify(text);
                System.out.println("Got best category of: " + jc.bestCategory());
                System.out.println(jc.toString());
            }
        }
    }

Результат валидации на тестовой выборке приведен на скрине:

Мы получили работающую модель для классификации текстов и приобрели навыки работы с новым для нас инструментом в области машинного обучения.

Из плюсов LingPipe для себя я выделил:

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

Из минусов:

  • Невозможность управлять параметрами алгоритма обучения модели, что снижает ее универсальность.

Помимо алгоритма классификации LingPipe также имеет другие алгоритмы машинного обучения для NLP, советую всем обратить внимание на этот инструмент и делиться своими результатами. Спасибо за внимание!