Онлайн-детекторы ИИ-текста выдают проценты, но не объясняют, что именно исправлять. При этом их точность сомнительна: в работе Liang и соавторов семь детекторов приняли за машинные 61% эссе TOEFL, написанных живыми людьми. OpenAI закрыла собственный классификатор ещё летом 2023 года из-за низкой точности.
Альтернативный подход — не считать вероятность, а называть конкретную фразу и правило. Тогда с каждым срабатыванием можно спорить отдельно.
Зачем вообще писать свой детектор
Если вы редактируете контент с помощью ИИ или проверяете чужие тексты, вам нужен не «процент машинности», а список конкретных мест для правки. Готовые сервисы этого не дают.
Скрипт, который разбирается ниже, ищет следы невычитанного текста — то, что редакторы Википедии называют признаками возможной проблемы. Авторство он не определяет: один признак ничего не доказывает, но три-четыре вместе — повод перечитать абзац.
Что вообще поддаётся автоматической проверке
Из 12 характерных признаков нейрослопа кодом уверенно ловятся пять — те, где есть устойчивая формулировка или конечный словарь:
| Признак | Метод |
|---|---|
| Фразы-анонсы вместо содержания | Регулярки по глаголам-обещаниям + порог длины |
| Пустые вводные обороты | Список из 36 паттернов |
| Монотонный ритм абзацев | Стандартное отклонение длины предложений |
| Кавычки на обычных словах | Регулярка по ёлочкам + исключения |
| Кальки и жаргонные англицизмы | Два словаря замен |
Семь признаков — отсутствие проверяемых деталей, придуманные цифры, позиция «с одной стороны / с другой», тройки ради ритма, рубленые концовки, тире в каждом предложении, схема «тезис + три пункта + вывод» — кодом не ловятся. Поиск по строке не знает, считал ли кто-то упомянутые 37%, и не чувствует синтаксического ритма на уровне абзаца.
Как ловить фразы-анонсы
Скрипт режет абзац на предложения и проверяет только короткие — до 12 слов. Длинное предложение анонсом не бывает.
for sent in re.split(r'(?<=[.!?])\s+', para):
clean = re.sub(r'[*_`]', '', sent).strip()
if not clean:
continue
if any(rx.search(clean) for rx in SELF_TALK_RE):
bad.append(clean[:70])
continue
if len(clean.split()) > 12: # длинное предложение анонсом не бывает
continueКороткое предложение сверяется с шаблонами. Один из них ищет глагол-обещание в самом начале:
r'^(?:разберу|разберём|расскажу|расскажем|покажу|покажем|объясню|объясним|'
r'рассмотрим|приведу|приведём|опишу|опишем|перечислю|начн[уё]м|перейд[уё]м)'Совпадение снимается, если в предложении есть фактический якорь — цифра, код в бэктиках или латинское слово:
FACT_ANCHOR = re.compile(r'\d|`[^`]+`|[A-Za-z]{2,}')Слабое место — символ ^. Фраза «В этой статье мы рассмотрим основные признаки» начинается с обстоятельства, глагол стоит четвёртым словом, и шаблон её не видит. Убрать ^ не получается: тогда под фильтр попадают нормальные фразы вроде «Разберём по шагам». На статьях одного блога такой расширенный шаблон дал девять ложных срабатываний.
Для конкретного оборота «в этой статье» работает узкий паттерн без ложных срабатываний:
r'^в\s+(?:этой|данной)\s+(?:статье|заметке|части)\s+(?:мы\s+|я\s+)?'
r'(?:разберу|рассмотрим|покажу|опишу)\b'Как ловить пустые вводные фразы и кавычки на обычных словах
Пустые вводные ищет список из 36 регулярных выражений. Одно срабатывание даёт 3 балла, два — уже стоп до правки:
LEADIN_PATTERNS = [
r'вывод из этого прост',
r'давайте (?:разберёмся|разберемся|начнём|начнем)',
# ... ещё 34 паттерна
]
score = 10 if n >= 3 else (5 if n == 2 else (3 if n == 1 else 0))Словарь знает только вписанные фразы. «Вывод простой:» с двоеточием он пропустит — в списке есть только оборот «из этого». Добавить вариант нельзя без риска: в четырёх статьях того же блога после «Вывод простой» идёт нормальная мысль, и словарь снял бы рабочее предложение.
С кавычками вся логика в исключениях. Регулярка по ёлочкам простая, а функция ordinary() отсеивает легитимные случаи:
def ordinary(s):
s = s.strip()
if len(s.split()) > 3: # длинное — скорее всего цитата или название
return False
if re.search(r'[A-Za-z+0-9]', s): # латиница, термин, лейбл — легитимно
return False
if s[:1].isupper(): # заглавная — имя или название
return False
return True # всё остальное — подозрительная кавычкаФлаг ставится уже от одной пары подозрительных кавычек — например, модель «знает» бренд.
Как ловить монотонный ритм и кальки
Монотонный ритм скрипт видит косвенно: считает стандартное отклонение длины предложений в словах по всему тексту. Маленький разброс — признак того, что предложения одной длины и текст звучит монотонно. Абзацы целиком он не сравнивает, поэтому классическая схема «тезис — три пункта — вывод» с предложениями разной длины пройдёт без флага.
Кальки ловят два словаря. Первый содержит жаргонные англицизмы с русской заменой: «партия» вместо кальки с batch, «вывод» вместо кальки с insight. Второй — полсотни замен из текстов, которые уже правились вручную. Смысловую кальку, где все слова русские, а оборот английский, словари не найдут.
Где ломается
Ложные срабатывания на нормальных текстах. Широкий шаблон глаголов-обещаний без ^ даёт девять ложных флагов на обычных статьях. Узкие паттерны точнее, но покрывают меньше случаев.
Словари устаревают. Список из 36 вводных фраз и словарь калек нужно обновлять вручную по мере того, как меняется стиль ИИ-генерации.
Смысловые признаки недоступны коду. Фраза «рост на 37%» получает ноль баллов — скрипт не знает, считал ли кто-то эти проценты. Семь из 12 признаков нейрослопа остаются на совести редактора: отсутствие деталей, взвешенность без позиции, синтаксические ритмические приёмы.
Что попробовать дальше
Добавить проверку тире: считать долю предложений с тире и флажить текст, где она выше порога. Это один из семи признаков, которые сейчас остаются редактору, но поддаётся автоматизации.
Попробовать сравнение абзацев по структуре: количество предложений, наличие списка, длина первого и последнего предложения. Если три абзаца подряд устроены одинаково — это уже паттерн.
Открытый список признаков редакторов Википедии — хорошая отправная точка для расширения словарей. Он публичный и обновляется сообществом.
FAQ
Чем этот детектор отличается от онлайн-сервисов?
Онлайн-сервисы выдают один процент вероятности. Этот скрипт называет конкретную фразу и правило, по которому она помечена. С каждым срабатыванием можно спорить отдельно, а не гадать, что именно не так.
Можно ли доверять результату и считать текст машинным, если много флагов?
Нет. Скрипт ищет признаки невычитанного текста, а не определяет авторство. Редакторы Википедии, на чей список опирается подход, прямо пишут: признак указывает на возможную проблему и сам по себе ничего не доказывает.
Почему не ловятся придуманные цифры и отсутствие деталей?
Поиск по строке не знает контекста. Фраза «рост на 37%» выглядит как факт — скрипт не может проверить, считал ли кто-то эти проценты. Такие признаки требуют понимания смысла, а не формы текста.
Как расширить словарь калек и вводных фраз?
Вручную: собирайте фразы, которые правите в реальных текстах, и добавляйте их в списки LEADIN_PATTERNS и словари замен. Перед добавлением проверьте паттерн на нескольких нормальных статьях — иначе получите ложные срабатывания.
Что делать с семью признаками, которые код не ловит?
Оформить их как чеклист для редактора: позиция без взвешенности, проверяемые детали, тире не в каждом предложении, отсутствие троек ради ритма, разнообразные концовки абзацев. Часть из них — тире, рубленые концовки — в принципе поддаётся автоматизации, если добавить соответствующие счётчики.
