Я работаю программистом, и в том числе занимаюсь машинным обучением применительно к анализу текстов. При обработке естественного языка требуется предварительная подготовка документов, и одним из способов является лемматизация – приведение всех слов текста к их нормальным формам с учетом контекста.

Недавно мы столкнулись с проблемой больших временных затрат на этот процесс. В конкретной задаче было более 100000 документов, средняя длина которых около 1000 символов, и требовалось реализовать обработку на обычном локальном компьютере, а не на нашем сервере для вычислений. Решение на просторах интернета мы найти не смогли, но нашли его сами, и я хотел бы поделиться — продемонстрировать сравнительный анализ двух наиболее популярных библиотек по лемматизации в этой статье.



PyMorphy2


Одной из самых популярных является PyMorphy2 — она встречается почти в каждом решении, которое можно найти в сети. Мы также пользовались этой библиотекой, и она отлично себя показывала, пока не потребовалось сделать лемматизацию для всей базы (как я писал выше, это более 100 тысяч небольших документов). Для того чтобы проанализировать такой объём документов, PyMorphy2 потребовалось бы почти 10 часов, при этом нагрузка на процессор всё это время будет в среднем около 30% (Intel Core i7 7740X).

PyMystem3


В поисках другого решения мы проанализировали библиотеку от Яндекса PyMystem3, но результат оказался чуть ли не вдвое хуже (по времени), чем у PyMorphy2: чтобы обработать 100 тысяч документов, ей бы потребовалось 16ч.

Немного магии


Нам показалось странным, что при этом нагрузка на процессор была почти нулевой. Также странно было то, что для получения результата от одного текста, даже большого (3-4 тысячи символов), PyMystem3 требовалось около 1 секунды. Поэтому мы решили объединить тексты, добавив какой-нибудь разделитель между ними, по которому мы могли бы заново вернуть структуру нашего списка документов, и отдать их на лемматизацию.

Код решения на Python:

def checkExecTimeMystemOneText(texts):
    lol = lambda lst, sz: [lst[i:i+sz] for i in range(0, len(lst), sz)]
    txtpart = lol(texts, 1000)
    res = []
    for txtp in txtpart:
        alltexts = ' '.join([txt + ' br ' for txt in txtp])

        words = mystem.lemmatize(alltexts)
        doc = []
        for txt in words:
            if txt != '\n' and txt.strip() != '':
                if txt == 'br':
                    res.append(doc)
                    doc = []
                else:
                    doc.append(txt)

Мы брали по 1000 документов для объединения, разделитель между ними — «br» (надо отметить, что тексты на русском языке, и латиницу и спецсимволы мы предварительно убирали). Данное решение значительно ускорило лемматизацию: в среднем, получилось около 25 минут на все 100 тысяч документов, нагрузка на процессор 20-40%. Правда это решение больше грузит ОЗУ: в среднем, это около 3-7Гб, но это хороший результат. Если же памяти недостаточно, то можно изменить количество объединяемых документов, это хоть и замедлит обработку, но всё равно будет гораздо быстрее, чем по одному тексту за раз. Также если объём памяти позволяет, то можно увеличить это значение и получить результат ещё быстрее.

В таблице — сравнение библиотек при обработке на локальном компьютере (Intel Core i7 7740X, 32 ГБ ОЗУ):
Метод Время ОЗУ (Мб) Проц
PyMorphy2 ~9,5 час 500 — 600 25 — 30%
PyMystem3 по 1 предложению ~16 час 500 — 700 0 — 1%
PyMystem3 по 1000 предложений 26 минут 2000 — 7000 25 — 40%

Интересно узнать мнение других специалистов. Возможно, кто-то нашёл более эффективный по времени способ лемматизации коротких текстов?

Кабаков Михаил, старший инженер-программист,
Михайлова Анна, начальник отдела интеллектуального анализа данных,
Консорциум «Кодекс»