Открытые метрики детектора персональных данных

2026-10-07 · Андрей Цымбалюк · метрики152-ФЗнейросетиобезличиваниекачество

Метрики нашего детектора лежат в открытом доступе: maskgate.ru/quality. Замер от 10 октября 2026 года: recall 99,78 %, precision 99,92 %, 1 473 записи в тестовом корпусе, 40 категорий данных. Выглядит как цифры из презентации для инвестора. Дальше — откуда они взялись, где врут и почему рядом с ними нет ни одного числа про скорость.

Сразу оговорюсь: я Андрей Цымбалюк, разработчик maskgate.ru. Это разбор замера моего же сервиса, написанный заинтересованным лицом. Читайте с этой поправкой.

Что мы выложили

Вот итог замера целиком.

ПоказательЗначение
recall (доля размеченных значений, которые детектор нашёл)99,78 %
precision (доля находок, подтверждённых разметкой)99,92 %
записей в тестовом корпусе1 473
категорий данных измерено40
TP / FP / FN1385 / 1 / 3
версия корпуса4.2
дата замера (UTC)2026-10-10

Источник тот же, maskgate.ru/quality. Там же лежат отпечаток замера sha256 e30922ac… и ревизия кода на момент прогона 8785b3e1…: по ним читатель сверяет свой прогон с нашим.

Одна ложная находка на 1386 срабатываний. Три пропуска на 1388 размеченных значений. Если вы видели такое в пресс-релизе вендора, вы уже решили, что цифры нарисованы. Реакция нормальная. Поэтому дальше про метод.

Как считаются recall и precision

Каждое размеченное значение корпуса сверяется с находками детектора по пересечению отрезков текста, отдельно по каждой категории. Дальше три счётчика:

recall = TP / (TP + FN), precision = TP / (TP + FP). В корпусе размечено 1388 значений, детектор нашёл 1385. Считаем precision: 1385 / 1386 = 99,9278… %, на странице стоит 99,92 %.

Проценты усечены вниз, а не округлены. Это принципиально: округление вверх завышало бы публичное число, а мне не нужна метрика, которую придётся защищать в комментариях. Рядом с процентами лежат TP, FP и FN, поэтому любой читатель пересчитывает их сам и ловит меня на арифметике, если я где-то сжульничал.

Ещё две вещи, без которых цифры читаются неверно.

Первая. Метрика считается по значениям и категориям. Документ как единица измерения здесь не участвует. «100 %» в таблице по отдельной категории — это результат на десятках размеченных значений, а не гарантия на любом тексте. Когда вам говорят «точность 99 %», первый вопрос — на чём именно.

Вторая. Precision здесь нижняя граница. Разметка перечисляет не все персональные данные страницы, поэтому неподтверждённая находка не обязательно ложная: детектор мог найти реальный паспорт, которого в разметке просто нет. Читать 99,92 % как «мы ошибаемся в 0,08 % случаев» нельзя. Честнее так: в 0,08 % случаев мы не смогли доказать, что не ошиблись.

И про профиль. Замер идёт на конфигурации без NER (enable_ner=false), то есть без модели для имён, организаций и локаций. NER меряется другим прогоном, python -m eval.run_eval --ner, и в этих числах его нет.

Почему прежние 95 % и 92 % больше не действуют

До этой недели на сайте стояли recall 95 % и precision 92 %. Их снимали на корпусе из 404 записей версии 3.2, отчёт от 10 сентября 2026 года. Нынешние проценты сняты на корпусе версии 4.2 из 1 473 записей.

Соблазн написать «точность выросла с 92 % до 99,92 %» был. Это была бы неправда. Корпус вырос больше чем втрое, разметка стала плотнее, часть категорий появилась уже после старого замера. Проценты с разных корпусов нельзя вычитать друг из друга и выдавать за прогресс. Поэтому старые числа я просто выбросил: на странице стоит только тот замер, который читатель может воспроизвести сегодня.

Заодно про ловушку в именах файлов, на которую я сам наступил. Один из корпусов называется corpus_256.jsonl, а записей в нём 178. Число в имени — идентификатор задачи, а не количество кейсов. Сначала я написал в отчёте «256 кейсов», потом пересчитал строки и переписал. С тех пор размер корпуса беру из файла, а не из его названия.

Порог в CI: метрика как условие сборки

Число на странице кто-то должен обновлять руками. Я не хотел, чтобы это делал человек, поэтому метрика стала частью сборки. Тот же прогон, python -m eval.run_eval, стоит гейтом в CI. Сборка падает, если порог нарушен. Пороги такие:

ТребованиеПорог
Общий recall (structured-RU)0,85
Tier 1 (паспорт, СНИЛС, ИНН, карта, ФИО, телефон, почта): recall0,97
Tier 1: precision0,85
Tier 2 (реквизиты, адрес, дата рождения): recall0,95
Tier 3 (шумные и контекстные категории): recall0,75 (отчётный, сборку не блокирует)
Precision для всех категорий0,8
NER-профиль: recall / precision0,75 / 0,7

Посмотрите на разрыв. Измеренный общий recall 99,78 % при пороге 85 %. Tier 1 измерен выше 99 % при пороге 97 %. Пороги намеренно ниже измеренного. Планка стоит там, где ловит деградацию, и поэтому её трудно заподозрить в подгонке под результат.

Артефакт замера лежит в api/quality_report.json. Из него читают числа и страница /quality, и главная; расхождение ловит тест. Отдельно проверяется свежесть: python scripts/quality_report.py --check-digests сверяет дайджесты корпуса и детектирующих источников за секунды, --check делает полную сверку со свежим прогоном. Правка детектора без перегенерации артефакта расходится с дайджестами и видна сразу. У замера есть sha256-отпечаток: читатель повторяет прогон и сравнивает результат, два прогона подряд дают побайтово одинаковый вывод.

Про Tier 3 отдельно. Это шумные и контекстные категории, где цена ложного срабатывания почти такая же, как цена пропуска. Если детектор начнёт блокировать каждое упоминание слова «договор», пользователь уйдёт и перестанет чем-либо пользоваться. Для Tier 3 порог поэтому отчётный: сборку он не роняет, но цифру мы всё равно показываем. Слабое место, о котором честнее сказать вслух, чем спрятать между строк.

Чего эти числа не значат

Теперь самая полезная часть. Вот что в этом замере устроено не в мою пользу, и я специально не стал это прятать.

Корпус синтетический. Записи генерирует код, eval/corpus/build_corpus.py, с фиксированным seed. Значения собираются так, чтобы проходить собственные валидаторы детектора. Прочитайте это ещё раз: корпус построен по тем же правилам, по которым работает проверка. Это смещает замер в пользу сервиса, и никакая честная подача смещения не отменяет. Понимать это важнее, чем запоминать проценты.

Отдельной цифры по состязательным примерам нет. В корпусе есть блок из 52 записей с обфускацией и кодированием: гомоглифы, percent-encoding, разбивка разделителями. Он измеряется вместе с остальными. Значит, общий recall нельзя читать как устойчивость к обфускации: сказать «на состязательных примерах у нас столько-то» я не могу и не буду.

Реальные документы клиентов в замер не входят. Пилота на настоящих договорах, реестрах и резюме ещё нет. На живых сканах, сложных таблицах и нетиповых форматах числа будут другими, и увидеть их можно только этим пилотом. Пока его нет, замер описывает корпус. Про «любой документ» он не говорит ничего.

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

NER в этих числах нет. Имена, организации и локации идут отдельным профилем с моделью, и порог там другой: 0,75 по полноте и 0,7 по точности против 0,85 общего. Смешивать два профиля в одну цифру я не стал: среднее по разным вещам всегда выглядит лучше, чем работает.

Латентность не публикуем. В прошлой версии статьи у меня стояли красивые «~0,1 мс на значение» и «~5 мс на сообщение в 1000 знаков». Я их убрал: воспроизводимого замера времени ответа нет, а число, зависящее от железа замеряющего, читатель повторить не может. Красивая цифра без возможности проверки — ровно то, за что справедливо прилетает в комментариях. Поэтому вместо числа на /quality стоит явный отказ.

100 % защиты не бывает. Мотивированный инсайдер, стеганография и паспорт, пересказанный своими словами, остаются вне игры. Сервис снижает риск при типовых утечках и не заменяет юридическую оценку вашей ситуации.

Специальные категории облако не обрабатывает. Документы со сведениями о здоровье (ст. 10 152-ФЗ) не принимаются: сервис отказывает в обработке до того, как содержимое куда-либо уйдёт. В журнал попадают только факт отказа, его причина и код категории, ни текста, ни совпавшего значения. Биометрию (ст. 11) детектор не выявляет вовсе, поэтому маскирование её не защитит и загружать такие документы нельзя. Для тех и других есть self-hosted развёртывание, где оператором остаётесь вы.

Сколько это стоит

Цены беру из машиночитаемого источника, maskgate.ru/config/pricing, чтобы статья не разошлась с реальностью через месяц.

ОперацияЦена
Проверка текста в интерфейсе20 ₽
Проверка документа (DOCX, XLSX, PDF, скан)100 ₽
Запрос к API1 ₽
Запрос через LLM-прокси1 ₽
Восстановление своих данных из vault0 ₽
Минимальное пополнение100 ₽

Бесплатно: 10 проверок текста в сутки без регистрации, 20 в сутки после входа. Сверх — по цене из таблицы.

Лимиты на файл: до 10 файлов за одну операцию, 20 МБ на файл, до 500 страниц в PDF, до 50 страниц распознавания сканов на документ. Готовый документ живёт в памяти сервера, не на диске, и доступен по ссылке со случайным токеном 5 минут; исходный файл пишется во временный и удаляется сразу после разбора. Форматов одиннадцать: DOCX, XLSX, PDF, TXT, CSV, PNG, JPG, JPEG, TIFF, TIF и BMP. Анализ идёт на серверах в России. Маскированные значения лежат в хранилище под AES-256-GCM, а в аудит вместо значений попадают HMAC-токены.

Для компаний с требованием локализации есть self-hosted развёртывание целиком в своём контуре, это страница /business.

Чего мне не хватает

Не звёзд на GitHub и не ещё одной статьи. Мне не хватает пилота на реальных документах и людей, которые принесут сценарии, до которых я не додумался. Замер на синтетике я уже сделал и показал; следующий честный шаг — живые договоры, реестры и сканы, где числа почти наверняка окажутся хуже.

Пилот нужен не для красивой цифры в новой статье. Мне важно увидеть, сколько находок детектор пропустит на живых сканах и сколько ложных срабатываний даст на настоящих таблицах. И где человек всё равно обойдёт фильтр руками. Последнее важнее первых двух: если сотрудник перепишет паспорт своими словами, никакая точность на корпусе его не остановит.

Поэтому предложение простое. Попробуйте сломать сервис: пришлите текст, на котором он, по-вашему, ошибётся, пропустит чувствительные данные или сработает зря. Форма /report собирает только категории и структурные признаки: тип документа, категорию ошибки, категорию данных и ожидаемое поведение. Поля для свободного текста там нет, присылать сам документ не нужно. В аудит-журнал попадают метаданные: ни текста, ни файла, ни IP.

Частые вопросы

99,78 % — это про мои документы? Нет. Это синтетический корпус из 1 473 записей. Пилота на реальных документах пока нет, поэтому замер описывает корпус, а не «любой документ».

Почему precision не 100 %? Одна находка из 1386 не подтверждена разметкой. И это нижняя граница: разметка перечисляет не все персональные данные страницы, так что находка может оказаться настоящей.

Можно проверить замер самому? Да. python scripts/quality_report.py --check-digests сверяет дайджесты за секунды, --check прогоняет эвал заново. Отпечаток замера опубликован на maskgate.ru/quality.

Вы гарантируете соответствие 152-ФЗ? Нет. Сервис снижает риск утечки и не заменяет юридическую оценку вашей ситуации.

Автор: Андрей Цымбалюк, создатель maskgate.ru. Оператор персональных данных: ИП Цымбалюк А.В., запись в реестре операторов персональных данных № 77-25-184814.

Не является юридической консультацией. Проверяйте актуальную редакцию 152-ФЗ.

Проверить свой текст · Метрики и методика · Сообщить об ошибке детекции

Проверьте свой текст перед отправкой в нейросеть — бесплатно на maskgate.ru.

Не является юридической консультацией. Проверяйте актуальную редакцию 152-ФЗ.