import streamlit as st import tools st.title('Message moderation lab') st.write( """ Термин «модерация» происходит от латинского «moderor», что значит «умерять, сдерживать». Суть задачи модерации состоит в контроле за выполнением законов, правил, требований и ограничений в любых сообществах и сервисах — будь то простое общение в социальных сетях или деловые переговоры на онлайн площадке. Автоматические системы модерации внедряются в веб-сервисы и приложения, где необходимо обрабатывать большое количество сообщений пользователей. Такие системы позволяют сократить издержки на ручную модерацию, ускорить её и обрабатывать все сообщения пользователей в real-time. Со временем пользователи подстраиваются и учатся обманывать такие системы, например пользователи: - генерируют опечатки: you are stupit asswhol, fack u - заменяют буквенные символы на цифры, похожие по описанию: n1gga, b0ll0cks, - вставляют дополнительные пробелы: i d i o t, - удаляют пробелы между словами: dieyoustupid - указывают контактные данные: восем-906-три единицы-два раза по две единицы и многое другое. Для того, чтобы обучить классификатор устойчивый к таким подменам, нужно поступить так, как поступают пользователи: сгенерировать такие же изменения в сообщениях и добавить их в обучающую выборку к основным данным. В целом, эта борьба неизбежна: пользователи всегда будут пытаться находить уязвимости и хаки, а модераторы реализовывать новые алгоритмы. В примере ниже можно ознакомиться с работой разных алгоритмов по выявлению наличия контактных данных в сообщениях пользователей. Это актуально в первую очередь для торговых площадок и других онлайн площадок по продаже и рекомендации товаров и услуг. Актуально это потому, что пользователи не всегда желают платить комиссию за работу сервиса и пытаются осуществлять сделки напрямую, минуя сервис. В данном примере сообщения пользователей подвергаются проверке тремя алгоритмами по поиску контактных данных: - регулярные выражения (regex) - TF-IDF, на основе частотности слов - нейросеть BERT 1. Регулярные выражения Регулярные выражения представляют собой похожий, но гораздо более сильный инструмент для поиска строк, проверки их на соответствие какому-либо шаблону и другой подобной работы. Англоязычное название этого инструмента — Regular Expressions или просто RegExp. """ ) with st.expander( label='Блок теории про регулярные выражения' ): st.write( """ В самом общем смысле регулярные выражения — это последовательности символов для поиска соответствий шаблону. Они являются экземплярами регулярного языка и широко применяются для парсинга текста или валидации входных строк. Представьте лист картона, в котором вырезаны определенные фигуры. И только фигуры, точно соответствующие вырезам, смогут через них пройти. В данном случае лист картона аналогичен строке регулярного выражения. """ ) st.image( image='images/re.jpeg', caption='Суть работы регулярных выражений', use_column_width=True ) st.write( """ Несколько случаев применения регулярных выражений: - парсинг входных данных, например текста, логов, веб-информации и т.д.; - валидация пользовательского ввода; - тестирование результатов вывода; - точный поиск текста; - реструктуризация данных. Регулярные выражения отлично подходят, когда есть четкий формат и структура данных. В нашем же случае пользователям легко будет обмануть систему модерации сообщений, если она будет построена только на регулярных выражениях. Нужно что-то посложнее. """ ) st.write( """ 2. TF-IDF (TF — term frequency, IDF — inverse document frequency). Мера TF-IDF является произведением двух сомножителей TF и IDF. TF - частота слова - отношение числа вхождений некоторого слова к общему числу слов документа. Таким образом, оценивается важность слова в пределах отдельного документа. IDF - обратная частота документа - инверсия частоты, с которой некоторое слово встречается в документах коллекции. Учёт IDF уменьшает вес широкоупотребительных слов. Для каждого уникального слова в пределах конкретной коллекции документов существует только одно значение IDF. """ ) with st.expander( label='Блок теории про TF-IDF' ): st.image( image='images/tf_idf_formula.jpg', caption='Формула TF-IDF', use_column_width=True ) st.write( """ TF рассчитывается по следующей формуле: """ ) st.image( image='images/tf_formula.jpg' ) st.write( """ где t (от англ. term) — количество употребления слова, а n — общее число слов в тексте. """ ) st.image( image='images/idf_formula.jpg' ) st.write( """ где D - общее число текстов в корпусе, d - количество текстов, в которых это слово встречается. IDF нужна в формуле, чтобы уменьшить вес слов, наиболее распространённых в любом другом тексте заданного корпуса. """ ) st.write( """ TF-IDF оценивает значимость слова в документе, на основе данных о всей коллекции документов. Данная мера определяет вес слова за величину пропорциональную частоте его вхождения в документ и обратно пропорциональную частоте его вхождения во всех документах коллекции. Большая величина TF-IDF говорит об уникальности слова в тексте по отношению к корпусу. Чем чаще оно встречается в конкретном тексте и реже в остальных, тем выше значение TF-IDF. """ ) st.write( """ 3. Нейросеть BERT. BERT — это нейронная сеть от Google, показавшая с большим отрывом state-of-the-art результаты на целом ряде задач. С помощью BERT можно создавать программы с ИИ для обработки естественного языка: отвечать на вопросы, заданные в произвольной форме, создавать чат-ботов, автоматические переводчики, анализировать текст и так далее. """ ) with st.expander( label='Блок теории про BERT' ): st.write( """ Чтобы подавать на вход нейронной сети текст, нужно его как-то представить в виде чисел. Проще всего это делать побуквенно, подавая на каждый вход нейросети по одной букве. Тогда каждая буква будет кодироваться числом от 0 до 32 (плюс какой-то запас на знаки препинания). Это так называемый character-level. Но гораздо лучше результаты получаются, если мы предложения будем представлять не по одной букве, а подавая на каждый вход нейросети сразу по целому слову (или хотя бы слогами). Это уже будет word-level. Самый простой вариант — составить словарь со всеми существующими словами, и скармливать сети номер слова в этом словаре. Например, если слово "собака" стоит в этом словаре на 1678 месте, то на вход нейросети для этого слова подаем число 1678. Вот только в естественном языке при слове "собака" у человека всплывает сразу множество ассоциаций: "пушистая", "злая", "друг человека". Нельзя ли как-то закодировать эту особенность нашего мышления в представлении для нейросети? Оказывается, можно. Для этого достаточно так пересортировать номера слов, чтобы близкие по смыслу слова стояли рядом. Пусть будет, например, для "собака" число 1678, а для слова "пушистая" число 1680. А для слова "чайник" число 9000. Как видите, цифры 1678 и 1680 находятся намного ближе друг к другу, чем цифра 9000. На практике, каждому слову назначают не одно число, а несколько — вектор, скажем, из 32 чисел. И расстояния измеряют как расстояния между точками, на которые указывают эти вектора в пространстве соответствущей размерности (для вектора длиной в 32 числа, это пространство с 32 размерностями, или с 32 осями). Это позволяет сопоставлять одному слову сразу несколько близких по смыслу слов (смотря по какой оси считать). Более того, с векторами можно производить арифметические операции. Классический пример: если из вектора, обозначающего слово "король", вычесть вектор "мужчина" и прибавить вектор для слова "женщина", то получится некий вектор-результат. И он чудесным образом будет соответствовать слову "королева". И действительно, "король — мужчина + женщина = королева". Магия! И это не абстрактный пример, а [реально так происходит](https://blog.acolyer.org/2016/04/21/the-amazing-power-of-word-vectors/). Учитывая, что нейронные сети хорошо приспособлены для математических преобразований над своими входами, видимо это и обеспечивает такую высокую эффективность этого метода. Идея в основе BERT лежит очень простая: давайте на вход нейросети будем подавать фразы, в которых 15% слов заменим на [MASK], и обучим нейронную сеть предсказывать эти закрытые маской слова. Например, если подаем на вход нейросети фразу "Я пришел в [MASK] и купил [MASK]", она должна на выходе показать слова "магазин" и "молоко". Это упрощенный пример с официальной страницы BERT, на более длинных предложениях разброс возможных вариантов становится меньше, а ответ нейросети однозначнее. А для того, чтобы нейросеть научилась понимать соотношения между разными предложениями, дополнительно обучим ее предсказывать, является ли вторая фраза логичным продолжением первой. Или это какая-то случайная фраза, не имеющая никакого отношения к первой. Так, для двух предложений: "Я пошел в магазин." и "И купил там молоко.", нейросеть должна ответить, что это логично. А если вторая фраза будет "Карась небо Плутон", то должна ответить, что это предложение никак не связано с первым. Ниже мы поиграемся с обоими этими режимами работы BERT. Обучив таким образом нейронную сеть на корпусе текстов из Wikipedia и сборнике книг BookCorpus в течении 4 дней на 16 TPU, получили BERT. """ ) if st.checkbox('Сгенерировать рандомное сообщение'): user_text = st.text_area( label='Введите сообщение', height=200, value=tools.get_random_message(), help='Попробуйте указать ссылки на vk, twich, twitter и др. каналы связи а также почту') else: user_text = st.text_area( label='Введите сообщение', height=200, help='Попробуйте указать ссылки на vk, twich, twitter и др. каналы связи а также почту' ) with st.expander( label='Показать примеры сообщений со скрытыми контактными данными' ): st.write( """ Ма8ш9и9н9а6 в 0хо0ро4ш4е2м9 состоянии Новый велосипед Работает всё Звонить на 8 девятьсот восемь 1976829 Беспроводная точка доступа маршрутизатор Моя Почта xopkin317 mailru My Отличный телефон TW практически новый ich хороший экран, без трещин lork не падал ing92 """ ) re_res = tools.get_re_pred(user_text) if 'Есть контактная информация' in re_res: st.success(f'Regex: {re_res}') else: st.error(f'Regex : {re_res}') tf_idf_res = tools.get_tf_idf_pred(user_text) if 'Есть контактная информация' in tf_idf_res: st.success(f'TF_IDF: {tf_idf_res}') else: st.error(f'TF_IDF: {tf_idf_res}') bert_res = tools.get_bert_prediction(user_text) if 'Есть контактная информация' in bert_res: st.success(f'BERT: {bert_res}') else: st.error(f'BERT: {bert_res}') with st.form(key='quiz'): right_answers_count = 0 st.write('QUIZ') answer = st.radio( label='Что такое регулярные выражения?', options=[ 'Модель машинного обучения', 'Аналог TF-IDF', 'Инструмент проверки строк на соответствие какому-либо шаблону', 'Инструмент для классификации сообщений пользователя', 'Выражения, которые регулярно используются разработчиками', 'WEB фреймворк', ] ) if answer == 'Инструмент проверки строк на соответствие какому-либо шаблону': right_answers_count += 1 answer = st.radio( label='Как пользователи обходят правила модерации сервиса?', options=[ 'Пишут в поддержку', 'Изменяют сообщения, маскируя запрещенный контент', 'Записывают голосовые сообщения', 'Пользуются другими сервисами, без модерации' ] ) if answer == 'Изменяют сообщения, маскируя запрещенный контент': right_answers_count += 1 answer = st.radio( label='Что такое TF-IDF?', options=[ 'Вид регулярных выражения', 'Система модерации текстовых сообщений', 'Запчасть автомобиля', 'Мера оценки значимости слова в документе', 'Модель машинного обучения', 'Корпус текстов', ] ) if answer == 'Мера оценки значимости слова в документе': right_answers_count += 1 answer = st.radio( label='Что оценивает TF-IDF?', options=[ 'Нужно ли отправлять сообщение на модерацию или нет', 'Значимость слова в документе', 'Частоту слова', 'Обратную частоту слова в документе' ] ) if answer == 'Значимость слова в документе': right_answers_count += 1 answer = st.radio( label='Что такое BERT?', options=[ 'Персонаж из мультика "Улица Сезам"', 'Нейронная сеть от Google', 'Система модерации сообщений', 'Система оценки соответствия сообщений правилам организации и законам', 'Вид регулярных выражений' ] ) if answer == 'Нейронная сеть от Google': right_answers_count += 1 answer = st.radio( label='Как обучается BERT?', options=[ 'На GPU', 'Никак, Google уже обучила ее, нам остается только пользоваться готовой', 'Маскируя 15% слов символом [MASK] и пытаясь предсказать спрятанные слова' ] ) if answer == 'Маскируя 15% слов символом [MASK] и пытаясь предсказать спрятанные слова': right_answers_count += 1 answer = st.radio( label='В каком виде подается информация на вход нейросети BERT?', options=[ 'Как есть без изменений', 'В виде векторов с числами, обозначающими целевое слово и близких к нему по смыслу из словаря', 'В виде сконкатенированных строк всего обучающего датасета', 'В виде списка текстов' ] ) if answer == 'В виде векторов с числами, обозначающими целевое слово и близких к нему по смыслу из словаря': right_answers_count += 1 answer = st.radio( label='BERT учитывает контекст в предложениях?', options=[ 'Нет', 'Да' ] ) if answer == 'Да': right_answers_count += 1 res = st.form_submit_button() if res: st.info(f'Количество правильных ответов {right_answers_count} из 8.') if right_answers_count <= 6: st.warning('Для прохождения блока необходимо правильно ответить хотя бы на 7 вопросов.') else: st.success('Отлично! Блок пройден.')