Всем привет, меня зовут Олег, я занимаюсь компьютерным зрением в команде Видеоаналитики МТС и сегодня расскажу вам, как мы защищаем от небезопасного контента стриминговую платформу WASD.tv, в частности про детектирование порнографии в постановке задачи action recognition.



Наш кейс — это стриминговый сайт для геймеров, киберспортсменов и любителей живых трансляций формата Twitch.tv. Одни пользователи транслируют развлекательный контент, а другие его смотрят. Контент может быть самый разный: игры, живая музыка, интерактивы, мукбанг, ASMR, готовка, подкасты, прочее — и в принципе не ограничен ничем, кроме воображения стримера.

И правил платформы, за соблюдением которых следят модераторы.


Зачем нужно модерировать небезопасный контент? На это есть две причины. Первая — это действующее российское законодательство, по которому распространение порнографии незаконно. Вторая причина — user experience. Платформа ориентирована на людей всех возрастов, и мы не можем себе позволить взрослый контент на главной странице.

Когда перед нами встала задача отслеживания небезопасного контента, сразу стало очевидно, что отличить безопасный контент от небезопасного не так-то просто. Первое, что было важно понять — порно и нагота не тождественные понятия.

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

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

Исходя из этих соображений, мы начали смотреть, как можно решить эту задачу. Из интересных открытых решений уже несколько лет существует обученная на закрытых данных модель Open NSFW от Yahoo (имплементация на TF). Ещё есть классный открытый репозиторий Александра Кима nsfw data scraper, из которого можно получить несколько сотен тысяч изображений с реддита, imgur и вроде бы каких-то других сайтов. Изображения разбиты на пять классов: порно, хентай, эротика, нейтральный и рисунки. На основе этих данных появилось много моделей, например раз, два
Опенсорсные решения страдают от нескольких проблем — в целом невысокое качество некоторых моделей, некорректное срабатывание на вышеупомянутых сложных кейсах и безопасных изображениях вроде тверкающих девушек и мемов с Рикардо Милосом, а также проблематичность доработки, потому что либо модели устаревшие и обучены на закрытых данных, либо данные очень шумные и с непредсказуемым распределением.



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

Распознавание действий


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

Как вообще решают эту задачу? В восемнадцатом году вышёл отличный обзор от qure.ai, и кажется, что с тех пор радикального прогресса в области не произошло, так что рекомендую. Более интересный ресерч на тему видео перешёл в более сложную задачу понимания и пересказа видео. Там и графовые сетки, и self-supervised learning — этому даже был полностью посвящен второй день на последнем Machines Can See.

Так вот, классификация действий. История прогресса в нейросетевых моделях примерно следующая: сначала проводили обучение трехмерных сверточных сетей с нуля (С3D), затем стали пробовать свёртки с какой-нибудь рекуррентной архитектурой или механизмом внимания; в какой-то момент Андрей Карпатый предложил разными способами мержить представления с разных кадров, еще позже стандартом стало делать двуглавые модели, где на один вход подается последовательность кадров в BGR/RGB, а на другой — посчитанный на них плотный оптический поток. Еще были приколы с использованием дополнительных признаков и специальных слоёв вроде NetVLAD. В итоге мы смотрели на модели, лучше всего показавшие себя на бенчмарке UCF101, где видео разбиты по 101 классу действий. Такой моделью оказалась архитектура I3D от DeepMind, она зашла лучше всего и у нас, поэтому расскажу о ней подробнее.

DeepMind I3D


Как бейзлайны мы пробовали обучать C3D и CNN-LSTM — обе модели долго обучаются и медленно сходятся. Затем мы взяли I3D, и жизнь стала лучше. Это две трёхмерные сверточные сети для BGR и оптического потока, но есть особенность — в отличие от предыдущих моделей, эта предобучена на ImageNet и собственном датасете от Deepmind Kinetics-700, в котором 650 тысяч клипов и 700 классов. Это обеспечивает крайне быструю сходимость модели в несколько часов к хорошему качеству.

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

Мы подаем в модель 16 кадров, а не 64. Раньше у нас был квадратный вход, но, учитывая специфику платформы, мы поменяли соотношение сторон входа на 16:9. Задача — бинарная классификация, где нулевой класс — это не порно, а единичный — порно. Обучали с помощью SGD с моментумом, он показал себя чуть лучше Адама. Аугментации минимальные — горизонтальные флипы и JPEG-компрессия. Тут ничего особенного.

Завершая тему моделей — после I3D еще выходили модели EVANet — Neural Architecture Search для последовательности кадров, SlowFast Networks — сеть с двумя каналами с разным фреймрейтом, и статья Google AI — Temporal Cycle-Consistency Learning , но мы их не исследовали.

На чём обучали-то?


Как я уже писал выше, с данными туго. Никто их публиковать не хочет, это сложно с юридической и этической точек зрения — начиная от лицензий и заканчивая согласием каждого причастного к контенту лица. Датасеты, их лицензии и публикация — это вообще весело. Если кто-то хочет написать об этом статью, я с удовольствием почитаю. Из значимых академических датасетов есть только бразильский NPDI, и он, к сожалению, маленький по объему, его распределение данных недостаточно разнообразно, он состоит из ключевых кадров, и процедура его получения не самая простая. А мы ведь еще и датасет из видео хотим! Пришлось собирать самостоятельно.

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

Скрапинг обеспечит нам потенциально гораздо большее разнообразие в данных, и мы можем получить разметку достаточно дёшево, сказав, что все кадры всех видео с условного порнхаба — небезопасные, а все кадры всех видео с ютуба — безопасные. Есть минусы — это всё надо где-то хранить, из этого надо как-то собирать датасет, и самое существенное — в наивной разметке порнороликов есть шум. Это как прямые ошибки: интро, сцены, где все одеты, крупные планы без половых признаков, меню хентай-игр — так и элементы, на которые модель может переобучиться: логотипы, черные экраны, монтажные склейки. Такого шума несколько процентов, и в случае терабайтов видео избавиться от него дорого. Об этом мы ещё поговорим.

Второй подход — это ручная сборка. Ее плюсы заключаются в том, что мы можем моделировать любое желаемое распределение данных, данные более предсказуемы, и их проще размечать просто потому, что их меньше. Но есть и минусы. Очевидно, данных при таком подходе получается меньше, и помимо этого они могут страдать от bias’а сборщиков, так как он моделирует распределение и может что-то упустить.
Мы выбрали второй подход. Составили список того, что потенциально могло бы оказаться на стриминговой платформе: самые разные игры, анимация, аниме, игра на музыкальных инструментах, реакции, мемы, хайлайты стримов — и попытались покрыть самые разные возможные типы небезопасного контента — от чего-то обычного до трэша в духе порно с птеродактилями. Отдельно упомянули компьютерные игры, по которым часто делают 3д-хентай — Overwatch, например. И начали собирать. В итоге могу выделить два инсайта.

Фетишисты — неплохие сборщики данных


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

Да и ютуберы тоже


Пример раз: на ютубе есть компиляции хайлайтов стримеров, иногда они покрывают отдельный год, длятся часами и содержат под тысячу монтажных склеек, т.е. сцен. Пример два: топы игр/аниме/сериалов. Скажем, вам надо внятно объяснить нейросети, что такое аниме. При этом в Японии огромное количество студий, стиль которых прогрессирует с каждым годом. Решение — скачать видео с топами аниме за отдельные годы от известного ютубера. Или вам нужно покрыть разнообразие сцен из популярной игры. Идете и качаете ролик например videogamedunkey по этой игре.

Итерации данных


У нас было несколько итераций данных. Сначала это было около ста видео хронометражом около 70 часов с наивной разметкой “все кадры с порносайтов — порно, всё с ютуба — непорно”, из которых мы более-менее равномерно сэмплировали последовательности кадров для датасета.

Обученная таким образом модель работала неплохо, но из-за шума в данных первые модели выдавали ошибки на разного рода логотипах, черных экранах и одетых девушках на черном кожаном диване ( ?° ?? ?°). Особенно сбивали с толку черные экраны со скором 0.817, но оказалось, что в данных была ошибка — в одной из компиляций порно автор случайно отрендерил видео на десять минут дольше нужного, в итоге в трейне было много “опасных” черных экранов.

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

Как размечали — почти для всех роликов использовали инструмент от OpenCV CVAT.

Пять копеек про CVAT
Расшифровывается как Computer Vision Annotation Tool. Разрабатывается в Нижнем Новгороде. Запускается в докере, можно сделать свою мини-Толоку. Проблема — он предназначен для сегментации и детекции, но не для классификации. Пришлось парсить их XML. Потом написали для разметчика свой простенький инструмент.


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

Отлично, у нас есть сырые размеченные данные! Как нам из них получить хороший датасет? Ролики разной длины, для каждой категории собраны видео разного хронометража и степени разнообразия — как это всё связать воедино? Сколько сэмплов мы можем взять из датасета? Его разнообразие как-то фундаментально ограничено (как максимум количеством кадров видео), как нам понять, что мы берем лишнего?

В начале работы мы не особо заморачивались над этими вопросами и просто брали из каждого видео отдельного класса столько сэмплов, чтобы порно и непорно в датасете было примерно поровну, а количество сэмплов определялось интуитивно (“ну вроде бы несколько раз в минуту почти во всех видео что-то радикально разное происходит, будем брать 10000 сэмплов”), а затем эмпирически по метрикам обученных моделей.

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

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

Давайте будем искать монтажные склейки

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

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

Собрали датасет из 20000 сэмплов в трейне, 2000 в валидации и 2000 в тесте, обучили модель, нам понравились метрики на тесте, отправили в продакшн.

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

Это и есть данные со звездочкой. Они позволили нам заточиться на разнообразный контент платформы и снизить нагрузку на модераторов. Теперь в основном ложные срабатывания происходят на новых играх — так, мы одно время чаще ловили Death Stranding и Valorant.

Текущий датасет состоит из 30000/5000/3000 сэмплов train/val/test.

Эволюция наших метрик на нашем тесте, разбитым по категориям, и сравнение с открытыми решениями (кликабельно)


В качестве метрики мы используем f1-меру с подвохом. Мы стараемся делать так, чтобы precision наших моделей стремился к единице, и в таком случае f1-мера становится прокси полноты.



Благодаря нашим детекторам время проверки всей платформы модераторами снижается в несколько раз. Помимо порнографии мы отлавливаем наготу, логотипы телеканалов и спортивные трансляции, но это истории для другого раза.

Fin.



Видеоверсию материала можно увидеть здесь