
Привет, Хабр! Меня зовут Макс Траулько, я занимаюсь анализом данных и проработкой новых фич в команде RnD в HFLabs. Прямо сейчас я работаю над нетривиальной задачей — учу наши алгоритмы распознавать в именах и фамилиях русский мат и прочие ругательства.
Как появилась эта задача? В одной крупной компании клиенты могут оставить обращения во фронт-системе. И иногда пишут в полях ФИО, прямо скажем, черт знает что. А у бизнеса риски: если при ответе на обращение автоматически использовать данные из поля «Имя», можно стать героем насмешливых или гневных постов.
Чуть раньше с такой же проблемой к нам пришел другой клиент, из ретейла. У того клиенты вообще большие выдумщики — придумывают составные и сложные имена и фамилии с обсценной лексикой. Даже жаль, что показать эти примеры не можем.
В статье расскажу, что и как мы с этим делаем.
Почему алгоритмы, а не сентимент-анализ?
Одно из наших решений — «Фактор» — занимается стандартизацией и повышением качества данных. Чтобы находить в полях ФИО табуированные в обществе слова и производные для них (с прочим «мусором» уже давно успешно справляемся), решили усовершенствовать фильтр стандартизации ФИО.
В более длинных текстах эта задача решается в контексте определения эмоциональной окраски (тональности) текста (Sentiment Analysis). Мы же имеем дело с короткими значениями, состоящими, как правило, всего лишь из двух-трех или четырех слов — фамилия, имя и отчество. Это создает определенные сложности в оценке ФИО с точки зрения сентимент-анализа.
Например, можно совершенно зря «ругаться» на хорошие компоненты ФИО: Хайитмурод, Хохренова, Чеботько, Солохина, Сосипатровна, Аблязова, Абдурасул, Асретов, Фанус и многие другие. Чтобы минимизировать такие ошибки, мы пошли алгоритмическим путем. Он позволяет гибко настраивать строгость поиска мата в ФИО и дает ожидаемое поведение для несправочных компонентов ФИО.
160 тысяч бранных слов
На первом этапе мы собрали собственный справочник русского мата, обзывательств и жаргонных слов — их набралось почти 160 000. Справочник необходим, чтобы система определенным образом проверяла по нему ФИО на предмет наличия грубых слов и мата и ставила соответствующий маркер SWEAR. На этом наборе данных провели разведочный анализ: как часто и какие подстроки грубых значений встречаются как в самом сформированном справочнике, так и в выгрузках ФИО. По понятным причинам мы не можем представить результаты этого анализа в статье;)

На основе 160 000 значений сделали справочник из ≈7000 значений, которые покрывают все это словесное многообразие. Зачем нужен такой «схлопнутый» справочник? Дело в том, что учесть все приставки и окончания слов невозможно, поэтому мы сфокусировались на корнях и прочих подстроках — основных элементах грубых слов и мата. Включили в этот справочник те значения, которые позволяют находить остальные производные формы слова среди 160 тысяч — через поиск подстроки в строке. Например, в справочник попадает слово «дебил», а все производные значения с такой подстрокой («дебилу», «дебилы», «дебилизм» и другие) не включаются.
Правда, у описанного в предыдущем абзаце правила есть исключение — иногда производное, более длинное, чем пополняемое значение, мы также вносим в «схлопнутый» справочник. На это есть две причины, о которых расскажу дальше.
В статье для краткости справочник матерных и грубых слов, содержащий 7 000 значений, буду называть swear.csv, а исходный справочник из 160 000 значений, предназначенный для тестирования, — swear_test.csv.
З@мена букв и прочие шалости
Теперь расскажу про форматы написания грубых слов и мата в ФИО. В порыве неуемного творчества сограждане пишут мат и грубые слова крайне затейливо: используют случайные и умышленные опечатки, разделители, транслитерацию (в том числе частичную, а не всего слова), трансграфику и замену букв на цифры.
На примере безобидной «жалобы» можно показать, как могут быть искажены буквы:
}|{ало6@, ж@л0ба, )(alоб@ и еще более сотни вариантов только для одного слова!
Чтобы уметь находить ругательства и ненормативную лексику в таких случаях, выполняем поиск значений в ФИО из справочника swear.csv с учетом замен, которые мы учли в карте соответствий символов, и разделителей между символами.
Так, карта соответствий определяет, на какие символы могут заменяться буквы, и учитывает:
-
Схожесть в написании букв с учетом схем транслитерации и визуальной похожести, включая цифры. Для каждой буквы кириллицы мы сформировали варианты ее написания. Например,
-
буква «Ж» может встречаться как: ZH, Z, G, J, }|{, }{, )(
-
буква «В» может встречаться как V, F, W, 8, B (латинская), В (русская);
-
-
Использование названий букв вместо слогов. Например, «си» → «с», «кс» → «x», «ви» → «v», «ый» → «y», «ий» → «y»;
-
Числовое представление звуков. Например, «пи» → «3,14» или «3.14». Допустим, слово «пилот» можно написать как «3,14лот» (а в каких словах в реальности люди подменяют слог «пи» на цифры, вы легко представите).
Если одна и та же буква с вариативными заменами встречается в слове несколько раз, то учитываются все возможные комбинации ее написания. Дополнительно можно учитывать прямой перевод грубого слова на разные языки.
Что касается разделителей между символами, то сюда относятся такие случаи:
-
Дублирование одной или нескольких букв («Дееебил», «Деебиииил»);
-
Запись букв через разделители в одном или нескольких местах. Примеры: «Д.е.б.и.л», «Д!еб-ил», «Л(о)х». Сюда же относятся пробельные разрывы, которые маскируют грубое слово как несколько раздельных компонентов ФИО («Л О Хов», «Иван Ду рак»);
-
Замена букв на небуквенные символы. Популярные замены включают знак звездочки, скобки, точки, тире/дефис, нижнее подчеркивание и восклицательный знак. Примеры: «Д*бил», «Д!бил».
Одна и та же буква для человека с фантазией может выглядеть по-разному. Поэтому система должна находить мат и грубые значения, в которых встречаются комбинации описанных выше разделителей и букв-замен согласно карте соответствий. Например, «Дебил» может быть записано как «Д€бииил», «Д€б*л», «Д€6iл» и прочее творчество, которое исчисляется тысячами вариаций.
Из-за такого разнообразия мы выполняем поиск грубых значений в ФИО до этапа стандартизации — до разбивки и возможного исправления компонентов ФИО, включая вероятное удаление некоторых мусорных символов, которые на деле могут быть частью грубого значения.
И еще один нюанс. ФИО может поступать в «Фактор» как в виде одной строки, которую необходимо разобрать на три поля — фамилию, имя и отчество, так и в гранулярном формате, когда компоненты ФИО уже распределены по этим трем полям. Это создает ситуации, когда грубое слово может быть намеренно разорвано между двумя или тремя полями в исходном гранулярном ФИО.
Для наглядности объединим в примере поля фамилии, имени и отчества, разделяя их типографским символом «•» (буллит): «Иванов де • бил Иван • ». Если такие хитрые формы написания встречаются часто, их тоже нужно обнаруживать. Сейчас мы как раз выясняем, насколько это критичный и частый кейс у бизнеса. Намеренно разорванные значения внутри одного конкретного поля мы уже научились находить.

Важная ремарка. Во время стандартизации в ФИО может быть обнаружена дополнительная информация — наименование компании, признаки «не обслуживается», «приостановлен» и пр. Дополнительную информацию, которую умеем находить в ФИО, на наличие нецензурной лексики не проверяем.
Например, в полях ФИО может встречаться запись: «Иванова Настя (ребенок)». В этом случае слово «ребенок» не нужно проверять на наличие мата. Это та самая дополнительная информация, которую мы исключаем из ФИО и помещаем в отдельное поле. Пример показателен, так как слово «ребенок» содержит подстроку, присутствующую в справочнике swear.csv. Но благодаря нашей системе фильтрации мы можем избежать ложных срабатываний и обеспечить корректную обработку данных.
Как устроен процесс поиска нецензурных слов
Используя справочник swear.csv и описанную карту соответствий, мы берем структуру данных в виде префиксного дерева для оптимизации поиска нецензурных слов. Дерево строится на основе букв ругательств. Так, все слова, начинающиеся с буквы «Х», образуют одну ветку. Затем эта ветка делится на подветки, основанные на следующих буквах, таких как «Е» и «У».
Процесс поиска начинается с того, что мы берем строку ФИО и ищем в ней значение из swear.csv. На первом этапе ищем первую букву грубого слова в строке, и это может привести к нескольким вхождениям. После нахождения первой буквы мы проверяем, что за ней должна следовать либо вторая буква, либо ее заменитель по карте соответствия. Также допускаются символы-разделители, которые могут быть использованы для искажения слова. Такой подход позволяет эффективно находить различные варианты написания ругательств.
Строгость поиска коротких грубых значений в ФИО. Поиск некоторых коротких значений из swear.csv в строке ФИО как простой поиск подстроки — дело довольно рискованное. Поэтому мы внесли изменения в структуру справочника, добавив флаг строгости поиска грубого значения или мата в ФИО — SEARCH_CONSTRAINT — при значении:
-
пусто — значение ищется как подстрока в строке, то есть без ограничений. Этот режим используется по умолчанию;
-
1 — значение ищется только как отдельно стоящее слово (по краям не буквы или начало строки);
-
2 — значение ищется без примыкания букв справа (примыкание букв слева допустимо);
-
3 — значение ищется без примыкания букв слева (примыкание букв справа допустимо);
-
при необходимости можно развивать варианты строгости поиска грубого значения.
Описанные значения SEARCH_CONSTRAINT=1,2,3 планируется использовать точечно — только для тех коротких и «опасных» значений, которые могут достаточно часто встречаться в несправочных ФИО. Вот пример: для «урод» с флагом SEARCH_CONSTRAINT=1 решается проблема присвоения маркера SWEAR таким несправочным значениям как Хайитмурод, Элмурод, Саидмуродович и пр.
Так как мы ограничили поиск грубого короткого значения флагом SEARCH_CONSTRAINT, то, чтобы находить в ФИО производные более длинные грубые слова без наложенных ограничений (в контексте строгости поиска), нужно формировать справочник swear.csv так:
-
Сначала вносим значения с ограничением SEARCH_CONSTRAINT из swear_test.csv в swear.csv;
-
Далее берем оставшиеся значения из swear_test.csv и формируем список таких значений для swear.csv, которые позволяют находить остальные производные формы слов через поиск подстроки в строке.
Пример: захотели поставить флаг SEARCH_CONSTRAINT =3 для значения «урод». Тогда в справочник swear.csv включаем как само значение «урод», так и производные более длинные значения из swear_test.csv (урода, уроды, уроде, уродц*, уроди* и т. д.) с фильтрацией — берем только те из множества более длинных значений для «урод», с помощью которых можно найти производные из этого множества. Такая сборка справочника swear.csv из данных swear_test.csv выполняется в автоматическом режиме.
В этом случае не повлияем на строгость поиска для тех производных более длинных слов, которые находились с помощью значения, на которое наложили ограничение SEARCH_CONSTRAINT.
Это первая причина почему иногда требуется вносить более длинные производные слова в справочник swear.csv, несмотря на то, что они могли быть найдены в виде вхождения более короткого значения.
По результату анализа swear_test.csv и справочных ФИО, мы выработали правило простановки маркера SWEAR.
Небольшое отступление в целом про фильтр ФИО и то, как он работает. Нажмите, чтобы прочитать
Сначала поясню, что означает токен. Токен — это такие части (или гранулы) в исходной строке ФИО, которые выделяются определенными алгоритмами. В самом простом понимании токен — это то, что выделено пробелами (или другими спецсимволами, например, не кириллицей, не латиницей и не цифрами) слева и справа от значения. В реальности все гораздо сложнее, и внутри токена допускается пробелы и т. п., но статья не об этом.
Чтобы определить результирующие значения для полей фамилии, имени и отчества, эти токены ищутся в справочниках ФИО с помощью разных методов: точного сопоставления, алгоритма Левенштейна, справочников замен, транслитерации, трансграфики и даже смены раскладки. Далее те токены, к которым по справочникам ФИО ничего не подобралось, еще раз «разрезаем» уже по границам кириллицы и чисел, латиницы и чисел и т. п.
Более того — реализована функциональность склейки токенов (<Иванов Дми трий> – если к <Дми> и <трий> ничего не подберется, то мы их постараемся склеить как <Дмитрий> ), и разрезания (<ивановдмитрий> — если ничего не подберется по справочникам ФИО, то значение будет разрезано на <иванов> <дмитрий>).
То есть в результирующих полях могут быть использованы исходные символы токена, подобранное справочное значение или, например, исходные символы с учетом транслитерации (когда по справочнику ничего не нашлось или исправление значения запрещено настройками).
Токены используются и в правиле для установления маркера SWEAR.
Грубые слова ищутся именно в исходной строке ФИО, а не в токенах. Затем вычисляется то, к какому токену (каким токенам) относятся использованные символы, чтобы проверить, что к данному токену не подбираются справочные значения с учетом возможных опечаток, а значит можно поставить маркер SWEAR.
А теперь главное. Маркер SWEAR ставится, если (выполнимость любого из вариантов):
-
токен подобран к справочнику ругательств swear.csv целиком, при этом не анализируется то, было ли подобрано какое-то значение по справочникам ФИО с учетом опечаток;
-
по справочнику ругательств swear.csv нашлась часть токена, и при этом НЕ было подобрано справочных значений с учетом возможных опечаток.
Возможность привести значение к справочному проверяется через:
-
Справочники замен (опечаток) для имен, фамилий и отчеств. Проверяем значение по всем справочникам независимо от того, в какой грануле находится значение. Пример: «Георгей» поступило в грануле фамилии, «гей» есть в справочнике swear.csv, но по справочнику опечаток для имен нашли замену «Георгей → Георгий». Значит, маркер мата НЕ ставим.
-
Исправление опечатки по Левенштейну по любому из справочников имен, фамилий, отчеств и никнеймов для имен. Проверяем значение на возможность привести его к справочному значению по всем справочникам. Пример: «ОлегАвна» поступила в грануле имя, слово из двух последних слогов есть в справочнике swear.csv, но значение можно привести к справочному по Левенштейну, используя справочник отчеств. Выходит, маркер мата НЕ ставим.
Как не сделать из ругательств «хорошие» слова
Есть и другая сторона медали: иногда плохое слово из справочника swear_test.csv алгоритмически можно привести к справочному (хорошему) значению. Возьмем слово «конченная»: можно исправить букву и подберется справочная фамилия Корченная. Но человек, конечно, понимает, что это не фамилия с опечатками, а жаргонизм. Или угадайте, к какому грубому значению можно подобрать фамилию Андон или имя Ганджон?..
Решение этой проблемы: нужно добавить в swear.csv все те полные формы ругательств, которые потенциально можно привести к справочному значению. В этом случае токен подберется к справочнику ругательств swear.csv целиком, а значит (согласно пункту №1 в правилах простановки маркера SWEAR) мы поставим маркер SWEAR, хоть и потенциально значение можно привести к справочному. Вот вторая причина, почему нам все-таки пришлось внести некоторые производные полные слова в справочник swear.csv, формирующийся из swear_test.csv.
Используя справочник swear_test.csv из 160 000 значений, предназначенный для тестирования, мы прогоняем значения через фильтр стандартизации ФИО с учетом реализованной доработки поиска мата и грубых значений. И все те ругательства, которые будут распознаваться как «похожие на справочные значения» (то есть которым не проставится маркер SWEAR), будут включены в справочник swear.csv.
Для описанного выше примера «конченная» в справочнике swear.csv указано лишь значение «Конч», с помощью которого мы и находим грубое слово «конченая». Чтобы решить проблему неприсвоения макера SWEAR, мы в справочник мата вносим не только «Конч», но и полный вариант — «конченая».
Еще мы сделали справочник swear-exception.csv на случай тех редких значений, на которые НЕ надо ставить маркер SWEAR, даже если они содержат значение из справочника swear.csv (по формату справочник такой же, как swear.csv). Если на один и тот же токен претендует и значение из swear.csv, и значение из swear-exception.csv, то он не будет считаться ругательным.
Пример: фамилия и имя Аль Шалавкинов Сейсал. Представим, что значение Шалавкинов не входит ни в какие справочники ФИО и получает маркер мата из-за корня «шалав». Если значение Шалавкинов не будет включено в справочники ФИО, мы можем внести его в справочник исключений swear-exception.csv. Тогда маркер SWEAR он не получит.
Или наоборот, представим, что Додик — справочная фамилия, при этом такое значение есть в справочнике swear.csv. Исходя из правила простановки маркера SWEAR значение «додик» подобрано к справочнику ругательств swear.csv целиком, поэтому мы поставим маркер SWEAR. Для изменения поведения мы можем внести это значение в справочник исключений swear-exception.csv. Тогда маркер SWEAR не будет поставлен.
Над чем сейчас еще работаем? Параллельно проводим анализ тестовых массивов ФИО и справочных данных, которые могут содержаться в полях ФИО, чтобы проверить корректность простановки или отсутствия маркера SWEAR. Это поможет выявить новые инсайты, которые могут быть полезны в работе нашей новой фичи.
Вот такая иногда работа в RnD. Честно скажу, что значения далеко не всех слов, особенно из уголовной лексики, я знал. Некоторые слова сначала кажутся безобидными, а потом смотришь, что означает, — и впечатляешься от их значения. В некоторые моменты чувствовал себя как герой Леонова из «Джентльменов удачи», который тоже осваивал жаргон. Но что не сделаешь ради того, чтобы клиенты были довольны и имели дело только с качественными данными;)

Кстати, о клиентских данных мы пишем в одном телеграм-канале, а о жизни и работе в HFLabs — в другом.
ссылка на оригинал статьи https://habr.com/ru/articles/896436/
Добавить комментарий