От слов к числам: как математически отличить Middle от Senior

от автора

Привет, Хабр! В своей первой статье про анализ вакансий C#/.Net разработчиков на рынке я выделила очень интересное замечание, которое определило тему сегодняшней статьи – «не количество навыков делает из мидла синьора, а образ его мышления». Построить граф связности компетенций для синьора это конечно хорошо, но к сожалению, на практике применить его достаточно сложно.

Сделав упор на навыки в своем исследовании, я получила зашумленный датасет, не поддающийся адекватной кластеризации. Так что пришло время попытаться пересмотреть подход к использованию полученных данных и попытаться вычленить из них тот качественный скачок, который отделит мидла от синьора.

Результаты кластеризации по частоте встречаемости навыков

Результаты кластеризации по частоте встречаемости навыков

Имеющиеся проблемы

Для начала стоит понять, какие параметры мы имеем и что нам с этим делать. Мало того, что только 28% вакансий из моего датасета имеют разметку грейда от HR, так еще и корреляция числовых признаков достаточно низкая.

Матрица корреляции числовых признаков в датасете

Матрица корреляции числовых признаков в датасете

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

Демонстрация зависимостей между признаками

Демонстрация зависимостей между признаками

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

Попытка нечеткой кластеризации навыков

Попытка нечеткой кластеризации навыков

Новая система координат и повторная кластеризация

Технически, мы имеем вес каждого технического навыка для каждого года опыта, представленных в матрице компетенций, флаг наличия архитектурных навыков и уровень ответственности, выявленный LLM при анализе текста вакансии. Используя эти три параметра, мы можем определить новый параметр — Topological Seniority Index (TSI). Он позволит связать между собой все параметры по формуле:

TSI=WSP_t+(2*L_r )+(B_a*WSP_t)

Выглядит страшно, но суть проста: мы повышаем в два раза уровень ответственности Lr, так как он крайне важен при выделении качественного скачка мышления, а сумму частоты встречаемости технических навыков удвоим, если мы имеем и архитектурные навыки (бинарный параметр Ba). Так мы разделяем вакансии с простыми техническими требованиями и вакансиями, где внимание уделяется архитектуре и ответственности за проект.

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

Отношение значимости навыков в вакансии к их количеству

Отношение значимости навыков в вакансии к их количеству

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

Повторная кластеризация радует нас тремя вполне обособленными группами с достаточно приятным индексом силуэта в 0.4.

Результат кластеризации по числовым параметрам: ‘WSP’, ‘L_resp’, ‘B_arch’, ‘B_arch’*‘WSP’

Результат кластеризации по числовым параметрам: ‘WSP’, ‘L_resp’, ‘B_arch’, ‘B_arch’*‘WSP’

С одной стороны, замечательно, мы получили разметку, способную каждой вакансии присвоить «рыночный» грейд. Но будем ли мы способны классифицировать наши вакансии по данной разметке?

От кластеров к классификации. Граничные значения грейдов

Проверить стабильность и предсказуемость наших данных было решено с помощью Bagging с использованием kNN. Таким изощренным образом мы сможем сгладить «шум», мешающий классификации и попытаться повысить точность модели. И результаты превзошли все ожидания.

Результат классификации на тестовой выборке (датасет от января)

Результат классификации на тестовой выборке (датасет от января)

Крайне высокое значение Accuracy вызвано не переобучением, что было проверено на других, незнакомых модели, датасетах.

Результат классификации на датасете от начала апреля

Результат классификации на датасете от начала апреля

Но теперь наблюдается другая проблема: пороги перехода между грейдами плавают, в зависимости от выбранного датасета. На графиках, представленных ниже зеленая область — это TSI характерный для Junior, синяя область — для Middle, оранжевая — для Senior.

Распределение грейдов в датасете от января

Распределение грейдов в датасете от января
Распределение грейдов в датасете от начала апреля

Распределение грейдов в датасете от начала апреля
Распределение грейдов в датасете от середины апреля

Распределение грейдов в датасете от середины апреля

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

Выводы

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

Но все эти смелые заявления работают только при некоторых оговорках:

  • Динамика обучающей выборки: Индекс TSI не является константой. Это «снимок» рыночных ожиданий в конкретный момент времени, требующий регулярной перекалибровки порогов при изменении трендов индустрии.

  • Декларативность данных: Модель анализирует «wish list» работодателей, а не реальные компетенции сотрудников. Это оценка позиционирования грейда рынком, а не финальный вердикт (именно поэтому я провожу так же анализ исходного кода).

  • Доменная специфика: Текущие веса оптимизированы под экосистему C#/.NET. Перенос модели на другие языки (Python, Go) потребует адаптации «архитектурного множителя» под специфику их парадигм.

Поделитесь своим мнением: что вы думаете о таком подходе? Можно ли усмирить хаос и бардак в требованиях на рынке или это босс высшего уровня?

ссылка на оригинал статьи https://habr.com/ru/articles/1026186/