У нас в проекте четыреста пул-реквестов в месяц. Половина комментариев — «поправь отступ», ещё четверть — «LGTM». Я хотел научить машину отличать полезное от шуУ нас в проекте четыреста пул-реквестов в месяц. Половина комментариев — «поправь отступ», ещё четверть — «LGTM». Я хотел научить машину отличать полезное от шу

Я обучил модель на 10 000 код-ревью, чтобы отсеять мусор. Она начала предсказывать увольнения

2026/02/18 12:15
10м. чтение

У нас в проекте четыреста пул-реквестов в месяц. Половина комментариев — «поправь отступ», ещё четверть — «LGTM». Я хотел научить машину отличать полезное от шума. Машина научилась. А потом я полез смотреть, где она ошибается — и три недели думал об этом перед сном.

Вы наверняка знаете это состояние: открываешь чужой PR на ревью, а там сорок два комментария. Глаза разбегаются. Первый — отступ, второй — пробел, третий — «ок», четвёртый — «ок», пятый — переименуй переменную. Где-то между ними спрятано замечание про гонку данных, которая уложит продакшен через неделю, но ты его благополучно пропустил, потому что к двадцатому «ок» мозг уже не здесь.

Код-ревью давно превратилось в ритуал. Галочка в Jira. Запятые обсуждаем час, архитектуру — никогда. Я решил это починить.

Датасет и первые результаты

Задача выглядела обманчиво простой: берём комментарий, классифицируем — сигнал или шум.

  • «Тут будет падение, если массив пустой» — сигнал.

  • «LGTM» — шум.

  • «Переименуй в getUserData» — шум.

  • «А что если пользователь отправит null?» — сигнал.

Я собрал сорок семь открытых репозиториев с публичной историей — почти одиннадцать тысяч комментариев. Три тысячи разметил вручную, остальное полуавтоматом с выборочной проверкой. Модель обучилась за вечер и угадывала правильно в восьмидесяти семи случаях из ста.

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

EMPTY_APPROVALS = {"lgtm", "ok", "ок", "+1", "👍", "норм", "ладно", "looks good", "принято", "ship it"} def extract(comment: str) -> dict: words = comment.split() return { "length": len(comment), "has_question": "?" in comment, "has_code_ref": bool(re.search(r'`[^`]+`|\.py|\.js|line\s*\d+', comment)), "has_suggestion": bool(re.search(r'а если|лучше|consider|what if', comment, re.I)), "is_empty_approval": comment.strip().lower() in EMPTY_APPROVALS, "specificity": len(re.findall(r'\w+\.\w+|\w+\(', comment)) / (len(words) + 1), }

Есть ли в комментарии вопрос. Ссылается ли на конкретный код. Предлагает ли альтернативу. Или это просто «ок» — одно слово, галочка, дальше. Последний признак — specificity — считает, как часто человек упоминает конкретные функции и файлы относительно длины текста. Чем конкретнее комментарий, тем выше шанс, что в нём есть сигнал.

Классификатор — логистическая регрессия. Пробовал деревья, пробовал бустинг — разница полтора процента, а регрессию можно открыть и посмотреть глазами, какие признаки тянут. has_code_ref и specificity оказались самыми сильными. Длина сама по себе — почти бесполезна: бывают длинные пустые комментарии и короткие точные попадания в баг.
Всё! Можно было писать туториал, собирать лайки и закрывать задачу.
Но я сделал то, что делает любой нормальный инженер — открыл список ошибок.

Паттерн, которого я не искал

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

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

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

Полез смотреть, кто эти авторы. Поднял историю коммитов. И нашёл общее: все они ушли из проекта. Через месяц, два, три после тех самых комментариев — последний коммит, и тишина.

Три часа ночи, экран светится

Вот конкретный человек. Полтора года в проекте, сто сорок пул-реквестов, активный, въедливый, спорит на каждом ревью. А вот его последние восемь недель: комментарии становятся короче, вопросов всё меньше, «ок» вместо развёрнутых абзацев. И потом — тишина.

Модель не ошибалась. Она видела то, чего я не искал: угасание. Задолго до того, как человек написал заявление. Возможно, задолго до того, как он сам понял, что уходит.

Пять признаков «тихого ухода»

Следующие две недели, по вечерам, я этим и занимался. Выделил восемьдесят девять человек, которые ушли и не вернулись. Ушли — значит последний коммит, потом тишина минимум на год. Не знаю, уволились ли они, перешли в другой проект или просто потеряли интерес — из публичных данных это не видно. Но паттерн угасания перед уходом одинаковый, независимо от причины. Потом сравнил их последние комментарии с тем, что они писали раньше. Паттерны оказались пугающе одинаковыми — у всех.

1. Длина комментариев сжимается

Не резко — постепенно, как громкость, которую убавляют по миллиметру в день. Полгода назад человек пишет: «Тут лучше вынести в отдельный сервис, потому что эта логика будет расти — я уже видел такое в модуле биллинга, потом три месяца распутывали». За месяц до ухода: «Ок». Среднее падение — до сорока процентов от прежней длины. Стабильно, у всех.

2. Вопросы исчезают

Человек перестаёт спрашивать «почему так?», перестаёт предлагать «а если иначе?». Принимает всё, кивает, не спорит. Вопрос — это инвестиция: ты тратишь энергию, потому что тебе важен ответ. Когда перестаёт быть важно — вопросы заканчиваются. Падение на семьдесят процентов за шесть недель.

3. Пустые одобрения

«LGTM» без единого замечания на код в пятьсот строк. Раньше такого не было — раньше этот человек всегда находил хоть что-то. Теперь — галочка и дальше. Доля таких «пустых» ревью подскакивает с пятнадцати процентов до шестидесяти.

4. Время реакции растёт

Время от назначения ревью до первого комментария увеличивается вдвое. Не потому что человек стал объективно занятее — нагрузка в команде у всех примерно одинаковая. А потому что задача переместилась в конец внутреннего списка приоритетов. Проще говоря, стало всё равно.

5. Тон выравнивается

Исчезают восклицательные знаки, «круто!», «ого», «спасибо», эмодзи. Раньше: «Блин, точно! Не подумал. Спасибо что поймал». Теперь: «Исправлю». Ровный, стерильный текст — как кардиограмма, которая выпрямляется в линию.


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

Модель-предсказатель

Я должен был проверить. Построил простую модель — не для продукта, для себя. Пять признаков, скользящее окно в две недели. Для каждого автора — его собственная базовая линия за предыдущие полгода. Не «длинный ли комментарий», а «стал ли он короче, чем обычно у этого человека». Не «быстро ли ответил», а «медленнее ли, чем он сам отвечал раньше».

def attrition_signal(author_id, window_days=14): recent = get_comments(author_id, last_n_days=window_days) baseline = get_comments(author_id, last_n_days=180) if len(baseline) < 10 or len(recent) < 3: return None # мало данных — лучше промолчать return { "length_ratio": mean_len(recent) / mean_len(baseline), "question_rate_delta": question_rate(recent) - question_rate(baseline), "empty_approval_rate": empty_rate(recent), "response_time_ratio": median_hours(recent) / median_hours(baseline), "sentiment_delta": mean_sentiment(recent) - mean_sentiment(baseline), } # вспомогательные функции считают среднее/медиану по окну, логика тривиальная

Каждая строчка — один из пяти признаков. length_ratio ниже единицы — комментарии сжимаются. question_rate_delta уходит в минус — вопросы исчезают. empty_approval_rate растёт — всё больше «ок» без единого замечания. response_time_ratio выше единицы — человек отвечает медленнее. sentiment_delta падает — тон выравнивается, эмоции уходят.

Тут важный момент. Классы несбалансированы: ушли восемьдесят девять человек из шестисот с лишним — примерно пятнадцать процентов. Если модель каждому скажет «останется», она угадает в восьмидесяти пяти случаях из ста и не поймает ни одного ухода. Красивая цифра, бесполезная модель. Поэтому простая точность тут врёт — нужно смотреть, сколько уходов модель нашла и сколько раз подняла ложную тревогу.

Что измеряем

Результат

Из тех, на кого модель указала — действительно ушли (precision)

52%

Из тех, кто реально ушёл — модель поймала (recall)

73%

Общее качество ранжирования (ROC AUC)

0.84

Половина ложных тревог — много. Из десяти, на кого модель показала пальцем, пятеро действительно ушли, пятеро — нет: передумали, получили повышение, влюбились, или паттерн объяснялся чем-то, о чём мы не знаем и не должны знать. Зато из тех, кто реально ушёл, модель заметила почти три четверти. За шесть недель. По комментариям к коду.

Для сравнения: если просто смотреть, стал ли человек писать короче, — качество ранжирования около 0.65. Пять признаков вместе — 0.84. Случайное угадывание — 0.50. Проверял на пятикратной кросс-валидации, стратифицированной — чтобы не обманывать самого себя.

Модель не видит причину. Только симптом. Но симптом — видит увереннее, чем я ожидал. Данные — GitHub API, тональность считал через TextBlob/dostoevsky, классификатор — логрег. Пайплайн воспроизводим за вечер на любых 40+ публичных репозиториях.

Три этических тупика

Тут я не мог понять, что с этим делать. Потому что есть три способа смотреть на эту штуку — и все три одновременно правда.

Инструмент заботы

Менеджер видит сигнал заранее. Подходит, спрашивает: «Эй, всё нормально? Чем помочь?» — не «почему ты уходишь», а именно «чем помочь». Человек хотел уйти, потому что его не слышали, а его услышали — и он остался. Выгорание, пойманное за шесть недель, это выгорание, которое ещё можно вылечить. За шесть часов до заявления — уже нет.

Инструмент слежки

Человек имеет право уходить молча. Не объяснять, не сигнализировать, не отчитываться о своих эмоциях через длину комментариев. Если каждый твой «ок» анализируется на предмет лояльности — ты живёшь в паноптикуме. Тюрьма, которую два века назад придумал Бентам: надзиратель видит всех, его не видит никто. Ты не знаешь, следят ли за тобой — и ведёшь себя так, будто следят всегда. HR-отдел с моделью предсказания увольнений — это паноптикум, только с нейросетями вместо вертухаев.

Неизбежность

Данные уже существуют. История коммитов, время ответа, длина сообщений — всё это логируется годами в каждой компании. Вопрос не в том, можно ли это анализировать, а кто сделает это первым. Если не вы — то стартап, который продаст это вашему начальству как «платформу аналитики персонала». Красивые дашборды, метрики удержания, прогнозы оттока. Без вашего участия и без этических вопросов.


Что с этим делать (без модели)

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

Если коллега перестал задавать вопросы на ревью — задайте ему вопрос. Не «ты уходишь?» — а «что думаешь про эту архитектуру?». Верните его в разговор.

Если комментарии стали короче — не списывайте это на эффективность. Это потеря интереса. «Ок» от человека, который вам доверяет, и «ок» от человека, которому всё равно, выглядят одинаково, но значат противоположное.

Если время ответа растёт — не дёргайте. Спросите, что случилось. Не в рабочем чате — лично.

Модель увидела паттерн за шесть недель. Вам достаточно одной, чтобы заметить. Разница в том, что вы можете не просто предсказать уход — вы можете сказать: «Эй, ты как?»

Почему мы не замечаем

Я искал мусор в комментариях, а нашёл людей, которым плохо. Модель оказалась внимательнее меня — не умнее, именно внимательнее. Потому что у неё нет дедлайнов, нет усталости, нет привычки не замечать медленные изменения.

В этом и штука: мы адаптируемся. Коллега стал тише — привыкли. Реже спорит — привыкли. Не шутит на созвонах — привыкли. Каждый день почти как вчера, разница в полтона, а через месяц мы уже не слышим. Модель не привыкает — для неё каждый комментарий это точка на графике, и она видит тренд просто потому, что не устаёт смотреть.

Миф про лягушку в кипятке — что она не выпрыгнет, если нагревать медленно — неправда, лягушка выпрыгнет. А мы нет. Сидим в воде, которая теплеет по градусу в неделю, и не замечаем, что кто-то рядом уже сварился.


У Лема в «Голосе Неба» учёные думали, что расшифровывают сигнал из космоса. Строили гипотезы, спорили о значении. А потом поняли: сигнал изучал их — показывал, кто они такие, по тому, как они его интерпретируют.

Я думал, что классифицирую комментарии к коду. А изучил — как мы перестаём замечать друг друга.

Паттерн всегда был в данных. Мы просто не искали. Может, не хотели.


В канале токены на ветер раскрываю вопрос: модель видит, когда человек уходит. А когда выгорает, но остаётся?

Спойлер: видит. И это страшнее.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу service@support.mexc.com для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.