Привет! Меня зовут Владислав Козлов, я тимлид аналитиков Business Security в Авито. Хочу поделиться с вами интересным опытом взаимодействия с LLM, который позволил мне дёшево и быстро проверить свою идею. А заодно — рассказать об интересном алгоритме кластеризации, который мы с моделью придумали и реализовали в виде библиотеки.
Статья не раскроет вам глубин вайбкодинга, не разверзнет бездну понимания ИИ и не явит Истину. Она не о мощных навыках использования LLM, а скорее о личном опыте дешёвого и быстрого прототипирования идеи. Главная её цель — вдохновить читателя не откладывать свои идеи в долгий ящик, а реализовывать и делиться ими с окружающими.
Надеюсь также, что сделанный нами с нейросетью алгоритм окажется полезным для вас.
• Придумал, посоветовался, реализовал — прелести быстрого прототипирования
• Тестируем на игрушечном наборе данных или как много у вас шансов выжить на Титанике
• К чему это я

Придумал, посоветовался, реализовал — прелести быстрого прототипирования
Вы бывали в ситуации, когда вас посещает очень интересная идея — с одновременным осознанием, что вы никогда её не реализуете? Потому что сейчас на это нет времени, а потом вы её просто забудете.
Именно в такой ситуации я обнаружил себя, когда читал код аналитика из своей команды.
📚 Задача
Нам нужно было выделить кластеры пользователей с характерным поведением и, рассмотрев их внимательнее, сформировать список мошеннических схем. Набор данных — объёмный, с большим количеством самых разных признаков
Есть много способов обрабатывать эти признаки и отбирать те, что стоит использовать для кластеризации. Мысль о том, сколько времени потребует такой анализ, причиняла мне боль.
💡 Идея
Я подумал: «Было бы здорово, если б можно было хотя на первом шаге использовать подход, который работает из коробки с категориальными признаками и не чувствителен к выбросам» — и почувствовал, что это звучит знакомо. Звучит как задача для деревьев.
Мысль о деревьях в алгоритмах обучения без учителя навела мой внутренний взгляд на Isolation Forest — алгоритм поиска аномалий, основанный на очень простой, но изящной идее: разбивать случайным образом набор данных на группы и смотреть, как часто каждое наблюдение оказывается отрезанным от большинства.
Те, что отделяются быстрее, скорее всего, сильно отличаются от остальных. Принцип работы Isolation Forest можно увидеть ниже. Я не очень хорош в рисовании, поэтому использовал Gemini. Простите за немногочисленные проблемы с подписями.

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

👨💻 Решение
С этой идеей наперевес я и отправился за советом к LLM. И модель изрядно развила мою идею, предложив интересный подход к решению проблемы оценки похожести объекта. Она посоветовала после получения случайных разбиений вычислять id ячеек как:
cell_id = Σ bj * Kj,
где:
j — номер фичи
b — номер бина
K — количество бинов, на которые бьются фичи
Таким образом, id никогда не повторяются. Эмбеддинг объекта (E) — это список таких id ячеек, в которые он попадал на разных итерациях.
Расстояние между объектами А и Б считается как Hamming Distance. Или просто доля итераций, в которых два объекта попали в разные ячейки. Чем чаще объекты попадают в одну ячейку, тем меньше будет расстояние.
После чего матрицу расстояний может использовать любой алгоритм кластеризации.
5 минут обсуждений, и вуа-ля — я получаю библиотеку, которая вполне пригодна как MVP.
И она работает! Не сразу: пришлось исправить пару багов и пройти несколько итераций обсуждений — но работает, и я всё ещё не написал ни строчки кода.

Тестируем на игрушечном наборе данных или как много у вас шансов выжить на Титанике
Опробуем подход на датасете, который описывает пассажиров Титаника. Он небольшой и там есть фичи разных типов, что делает его идеальным кандидатом.
Вот какие поля можно анализировать:
PassengerId — уникальный порядковый номер пассажира.
Survived — бинарная целевая переменная: 0 — погиб, 1 — выжил.
Pclass — социально-экономический класс билета: 1-й, 2-й или 3-й. Часто выступает главным индикатором достатка.
Name — полное имя пассажира, включая титул или звание.
Sex — пол пассажира: male / female.
Age — возраст в годах. Для детей младше года указывается дробное значение, например: 0.75 или 1.5. Присутствуют пропуски.
SibSp — количество супругов (husband/wife) и братьев/сестёр (siblings), с которыми путешествует пассажир.
Parch — количество детей (children) и родителей (parents), которые путешествуют с ним.
Ticket — уникальный буквенно-цифровой номер билета.
Fare — стоимость проезда (тариф) в фунтах стерлингов.
Cabin — номер каюты пассажира. Встречается большое количество пропущенных значений.
Embarked — порт, где пассажир сел на корабль. Принимает три значения: C — Шербур (Cherbourg), Q — Куинстаун (Queenstown), S — Саутгемптон (Southampton).
В качестве фич будем использовать только Sex, Age, SibSP, Parch, Fare и Embarked. А Survived и Pclass возьмём, чтобы посмотреть, как они коррелируют с найденными кластерами.
Попробуем просто забросить их в алгоритм без всякой предварительной обработки — прямо рай, легко и удобно:
fc = ForestClusterer( n_iterations=300,/ n_bins=3, quantile_cuts=True, n_clusters=3, corr_threshold=0.9, random_state=42, ) labels = fit_predict(X)
И чудо свершилось, мы получили вполне понятные кластеры:

Кластер 1 состоит почти полностью из женщин, по большей части — с членами семьи. Чем и объясняется высокая доля выживших.
Кластер 2 — с наибольшей концентрацией пассажиров 1 класса. Это, вероятно, объясняет второй результат по доле выживших.
А Кластер 3 — мужчины из 2 и 3 классов, путешествующие в большинстве без близких родственников. У таких пассажиров шансов совсем мало.
Весьма любопытно, что KMeans, обученный на отмасштабированных данных с OneHotEncoding нашёл совсем другие кластеры:

🔵 Кластер 1 — в основном мужчины из 2-го и 3-го классов с ожидаемо малыми шансами на выживание.
🟠 Кластер 2 — более высокая доля женщин, больше пассажиров с членами семьи: более высокие шансы. Но это пассажиры 2-го и 3-го классов, что снижает вероятность выжить.
🟢 Кластер 3 — пассажиры 1 класса, да ещё в основном женщины. Самые высокие шансы.
В целом, датасет не очень хорошо разделён на группы, поэтому внутри относительно плотного облака объекты можно делить по-разному.
Но вот если мы подмешаем немного аномалий, выбросов, сильно отличающихся от остальных наблюдений, то на таком «испорченном» датасете наш алгоритм покажет себя особенно хорошо.
Посмотрим, как Forest Clustering и набор алгоритмов из sklearn выделяют связь кластеров с классом пассажиров.
Используем метрику ARI (Adjusted Rand Index) — она оценивает качество в машинном обучении: насколько алгоритм кластеризации согласуется с истинной разметкой данных — эталонными классами. Метрика устраняет случайные совпадения и позволяет объективно сравнивать разные алгоритмы. Узнать о ней подробнее.

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

К чему это я
Не воспринимайте эту статью как рекламу алгоритма или библиотеки, я прекрасно понимаю, что и алгоритм имеет аналоги, и библиотека сырая. Это только иллюстрация того, что имея пару часов времени и LLM, можно между делом проверить идею, не написав ни строчки кода.
Более того, можно получить пусть и сырой, но полезный инструмент. В рассмотренном случае он позволяет дёшево разбить данные с помощью нескольких алгоритмов, не думая о выбросах, пропусках и категориях.
Призываю вас не держать идеи в себе и смело экспериментировать!
Если хотите знать больше о работе со сложными продуктами, подписывайтесь на телеграм-канал «Коммуналка аналитиков». Там интересно!



