Для поиска и удаления нецензурных выражений в комментариях и сообщениях в соцсетях используются глубокие нейронные сети. Их обучение и применение требует серьезных вычислительных затрат.
Крупные компании могут это себе позволить, но как быть нам, «простым смертным», владельцам простейших сайтов и рядовым интернет - пользователям?
Как вариант, мы можем использовать JavaScript на клиентской стороне, чтобы процессор пользователя брал на себя часть нагрузки. Но сложный алгоритм сильно загрузит браузер, он зависнет или даже упадет. Надо искать баланс.
По интернету бродит пара-тройка JS и PHP скриптов с регулярными выражениями. Они частично решают проблему, но являются статичными алгоритмами. Русские пользователи, как известно, очень креативны в «этом вопросе». Каждый раз менять алгоритм не каждый осилит, да и временные затраты будут серьезные.
Читать такие комментарии по многу раз при формировании фильтра и думать о них не очень приятно. Попробуем воспользоваться data science.
Идея
За основу возьмем очень простую идею – фильтр мата в виде массива строк длиной от 2х до 7ми символов. Применение фильтра по принципу text.contains определяет с некоторой точностью (70 – 80%), что текст сообщения токсичен.
Именно токсичен, а не с матом, потому что в данный фильтр можно внести слова вроде «лох» или «чмо» (надеюсь, что меня не забанят за них) и другие неугодные нам слова.
Например, фамилии президентов и названия национальностей (особенно разговорные) с высокой долей вероятности удалят политические комментарии.
Как вы понимаете, текстовые паттерны «плохих» слов, могут отфильтровать и «хорошие».
Поэтому нам надо сформировать фильтр так, чтобы максимально уменьшить фильтрацию обычных слов.
Рецепт
Для генерации подобного фильтра нам понадобятся 2 текстовых файла: bad_words.txt и good_words.txt, базовые знаний Python и генетический алгоритм – библиотека deap.
В качестве good_words я использовал 5000 наиболее используемых слов русского языка. Этот список можно расширить, ведь словарный запас интернет - жителей, значительно больше.
Список bad_words формируйте под вашу задачу. В интернете существует значительное количество различных баз слов с классификацией. Я не выкладывал свой bad_words.txt по причине политкорректности.
Устанавливаем deap: pip install deap.
Создаем файл toxic_filter_create.py или jupyter notebook. На ваш выбор.
Загрузка данных
При загрузке убираем строки длиной менее 2х символов, так как длина паттерна начинается с 2х. Этот параметр можно изменить.
После загрузки получаем два списка good_words и bad_words. Функция load_file().
Формируем паттерны
Минимальную длину паттерна min_letter_count = 2 и максимальную длину паттерна max_letter_count = 7 можно регулировать. Обычно 2 и 5 достаточно.
Так как генетический алгоритм оперирует бинарным вектором - геном, то функция decode() преобразует его в список из строк. Причем там где 0, строка просто не вставляется в список. Таким образом происходит сжатие данных и размера конечного фильтра в итоге.
Функция percent_in_text() находит, какую часть из набора фраз (bad или good) «видит» фильтр.
Выбор лучшего
Длина гена равна размеру списка паттернов. One-Hot кодирование.
Строка creator.create("FitnessCompound", base.Fitness, weights=(1.0, -1.0, -0.6)) говорит о том, что целевая (фитнес) функция возвращает 3 значения.
Коэффициенты (1.0, -1.0, -0.6) показывают значимость каждого из параметров. Значения с минусом говорит о том, что параметр должен минимизироваться, если плюс - максимизироваться.
Целевая функция получает на вход бинарный вектор (ген), декодирует его в текстовые паттерны и проверяет percent_in_text по bad_words и good_words.
Первый параметр должен быть ближе к 1.0, а второй к 0, потому что мы не хотим фильтровать хорошие слова.
Третий параметр - суммарная длина конечного фильтра. Минимизируется.
Описывать подробно работу ГА нет смысла - на эту тему есть множество статей. Сам код с минимальными модификациями взят с сайта модуля deap.
Остановлюсь лишь на некоторых моментах:
- Чем больше num_generations, тем точнее будет фильтр.
- Так как паттерны формируются только по bad_words, то первый параметр быстро максимизируется.
- Процент вхождения в good_words оптимизируется значительно дольше. При 1000 эпохах у меня второй параметр уменьшился до 0,12. Другими словами, с вероятностью в 12% фильтр уберет и хорошее сообщение.
- По bad_words фильтр 100% отрабатывает.
Что с этим делать?
Пополняя bad_words.txt новыми «всплесками бешеного креатива» и good_words.txt "классикой", вы можете улучшать свой фильтр.
В итоге алгоритм выдает в консоль массив вида (тут оставлена самая цензурная часть - русские «слова» начинаются с 3х букв), отсортированный в порядке убывания длины.
['поте', 'эрек', 'пида', 'осущ', 'деби', 'уро', 'ид', 'ху', 'чм', 'жо', 'ду', 'уе', 'йл']
Сортировка сделана для того, чтобы вначале срабатывали более точные паттерны, хотя для определения мата иногда достаточно и 2х букв. Это можно отрегулировать параметром min_letter_count. Суммарная длина строк фильтра у меня составила 244 символа.
Берете этот массив, вставляется в JS, PHP или иной код и, по принципу text.contains, идете циклом по фильтру до первого совпадения. Если оно есть - значит идет выбраковка слова или текста.
Для увеличения точности фильтра можно вычислять процент сработавших паттернов и, если он выше некоторого порога (скажем 20%), то текст считаем токсичным. Этот подход чуть больше нагрузит CPU.
Так стоит подумать над усовершенствованием функцией normalize(). Различные замены символов приведут текст в более удобный для машины вид, уменьшат количество символов и паттернов в итоге. Не забудьте добавить ее аналог в код фильтрации на сайте.
Имеется возможность установить плагин для Chrome, который будет фильтровать сообщения в VK и OK.
Полный исходник алгоритма и good_words.txt вы можете найти на моем github.