Привет! Меня зовут Владислав Козлов, я тимлид аналитиков 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, показали снижение силы связи. Это говорит о том, что наш подход более устойчив к выбросам, и, следовательно, менее требователен к предварительной обработке данных.

Слева — абсолютные значения метрики до и после зашумления, справа — изменение ARI
Слева — абсолютные значения метрики до и после зашумления, справа — изменение ARI

К чему это я

Не воспринимайте эту статью как рекламу алгоритма или библиотеки, я прекрасно понимаю, что и алгоритм имеет аналоги, и библиотека сырая. Это только иллюстрация того, что имея пару часов времени и LLM, можно между делом проверить идею, не написав ни строчки кода.

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

Призываю вас не держать идеи в себе и смело экспериментировать!

Если хотите знать больше о работе со сложными продуктами, подписывайтесь на телеграм-канал «Коммуналка аналитиков». Там интересно!