Анализ и сравнение профиля из демо
Анализ и сравнение профиля из демо

TL;DR

  • Идея: Люди используют GitHub Stars как закладки - это отличный сигнал, чтобы понять, какие репозитории похожи друг на друга по смыслу.

  • Данные: Обработал 1 ТБ сырых данных из GitHub Archive (BigQuery), получив матрицу интересов 4 миллионов разработчиков.

  • ML: Обучил эмбеддинги для 300к+ репозиториев, используя Metric Learning (EmbeddingBag + MultiSimilarityLoss).

  • Frontend: Сделал client-only демо, которое крутит векторный поиск (KNN) прямо в браузере через WASM, без участия бекенда.

  • Итог: Система находит неочевидные альтернативы библиотек и позволяет сравнивать профили разработчиков.


Персональная мотивация

Доводить идеи до конца обычно сложнее, чем кажется. Часто хватает сил на прототип, но дальше начинается самое трудное: довести качество, написать текст, оформить демо. Этот проект - моя попытка пройти весь путь и закрыть один из таких "висящих хвостов".

А еще я задумался о природе наших GitHub Stars. Мы привыкли относиться к ним просто как к закладкам "на будущее". Но на самом деле - это ценный цифровой актив. Это слепок наших профессиональных интересов и навыков. Мне стало интересно: можно ли заставить этот пассивный актив работать? Использовать накопленные знания миллионов разработчиков, чтобы построить систему рекомендаций репозиториев и дать возможность людям сравнивать их интересы.


Концепт

Cluster hypothesis

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

Мы интуитивно чувствуем эти "скрытые" предпочтения. Вы подошли к новому коллеге, который что-то печатает в Vim и смог самостоятельно из него выйти. Наверняка вы уже построили в голове примерный вектор его интересов: с ним можно обсудить патчинг KDE2 под FreeBSD, но вряд ли стоит спрашивать совета про выбор игровой мыши с RGB-подсветкой.

Repo representation

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

Чтобы упростить, представьте себе 2D пространство с осями:

  • Ось X: Данные (Подготовка & Анализ) vs Модели (Обучение & Inference).

  • Ось Y: Local / Single-node vs Big Data / Cluster.

Пространство
Пространство

В реальности нейросеть сама придумывает эти оси (features), и они не всегда интерпретируемы человеком, но математическая суть сохраняется: похожие инструменты стягиваются в кластеры.

Имея такие вектора, мы можем:

  • Искать похожие репозитории через Cosine Similarity (угол между векторами).

  • Получить вектор интересов пользователя, просто усреднив вектора его лайкнутых репозиториев.

  • Сравнивать профили пользователей между собой.


Источники сигнала (Signal Source)

Чтобы получить такие вектора, я использовал гибридный подход.

1. Текст (README.md) - для инициализации

У многих репозиториев есть README. Это отличный источник для "холодного старта". Я использовал модель Qwen3-Embedding-0.6B, которая поддерживает MRL (Matryoshka Representation Learning), оставив только первые 128D самые важные компоненты. Эти вектора я использовал как начальную инициализацию весов обучаемой модели.
Note: Этот шаг добавляет около 10% к итоговому качеству. Чтобы не усложнять код лишними зависимостями, в публичном репозитории я этот шаг пропустил - модель отлично учится и с нуля

2. Матрица Stars - для основного обучения

Текст - это хорошо, но он не показывает, как инструменты используются вместе. Тут в игру вступает коллаборативная фильтрация.

User

Starred repositories

A

Pandas, Dask, SK-Learn, Numpy

B

Vue, React, TypeScript, Vite

Существует много подходов к обучению на таких данных: графовые алгоритмы (LightGCN) или факторизация матриц. Но я выбрал Metric Learning, так как это требует меньше ресурсов GPU и дает гибкость в управлении пространством.


Подготовка данных

Данные брались из публичного датасета GitHub Archive в BigQuery.
Нам нужно было два запроса:

  1. Stars (WatchEvent): Собрать пользователей, у которых от 10 до 800 лайков (фильтруем малоактивных и ботов). Сохраняем порядок лайков.

  2. Meta (PushEvent): Собрать имена репозиториев, даты коммитов и описание.

Суммарно запросы обрабатывают около 1 ТБ данных и почти укладываются в Free Tier BigQuery. На выходе - Parquet-файл с 4 млн пользователей и 2.5 млн уникальных репозиториев.


Обучение векторов

Выбор модели

Я хотел сделать решение максимально легковесным для браузера, поэтому сразу отсек Трансформеры.
Моя модель - это классический torch.nn.EmbeddingBag. По сути, это просто большая таблица repo_id -> vector[128], которая умеет эффективно агрегировать (усреднять) вектора.

Сэмплирование и Loss Function

Как объяснить нейросети, что Pandas и Numpy - это "близкие" вещи?

Basic sampling method

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

Пользователь

Группа

Репо в группе

Среднее(Embeddings в группе)

A

A1

Numpy, Dask, SciPy

[0.2, -1.1, 0.9]

A

A2

Pandas, SK-Learn

[0.1, -1.3, 0.6]

B

B1

Vue, Vite

[-0.4, 0.6, 0.2]

B

B2

React, TypeScript

[-0.3, 0.7, 0.1]

Мы обучаем эмбеддинги так, чтобы одновременно соблюдались два условия:

  1. Группы одного пользователя были как можно ближе (A1 <-стягивались-> A2).

  2. Группы разных пользователей были как можно дальше (B1 <-отталкивались-> A2).

Очевидно модель не сможет идеально выполнить эти два условия одновременно для всех пользователей. Но балансируя между этими двумя силами градиентный спуск пытается подобрать такие вектора для самих репозиториев чтобы ошибка была минимальной.

Advanced methods vs Simplicity

Логично было предположить, что в последовательности лайков (то, в каком порядке вы ставите звезды) есть сильный сигнал. Я пробовал подходы а-ля Word2Vec (скользящее окно), сложные Hard Negative Miners.
Но, как ни странно, простейшее случайное разбиение сработало лучше всего. Вероятно, данные о времени слишком шумные, либо мне не удалось извлечь из этого пользу. Иногда в ML простые эвристики побеждают сложные архитектуры.

В качестве функции потерь лучше всего себя показала MultiSimilarityLoss из библиотеки pytorch-metric-learning, хотя сравнивались и другие альтернативы.


Оценка качества

У нас есть вектора, но как понять, хорошие ли они?
Можно использовать синтетику от LLM, но я нашел более элегантный Ground Truth - Awesome Lists. На GitHub существуют тысячи репозиториев формата "Awesome Python", "Awesome React". Это уже готовые, модерируемые людьми кластеры похожих библиотек.
Я скачал README этих списков, нашел коллокации (какие репо встречаются вместе), хитро взвесил силу связи и использовал метрику NDCG для оценки ранжирования. Это позволило честно сравнивать разные лоссы, гиперпараметры и методы семплирования.


Frontend: Showcase и AI-разработка

Несмотря на мой 10-летний опыт в Data Science, во фронтенде я, мягко говоря, не эксперт. Поэтому челлендж был таким: сделать сложную клиентскую логику без бекенда, не будучи JS-разработчиком.

Весь код фронтенда и "клея" был написан с помощью Codex.

Архитектура

  1. Данные: Клиент загружает сжатые эмбеддинги (FP16, ~80 MB) и метадату, кешируя их в IndexedDB.

  2. Поиск (WASM): Используется ядро библиотеки USearch, скомпилированное в WebAssembly.

Низкоуровневая магия

Изначально я хотел использовать предрасчитанный HNSW-индекс, но он занимал больше памяти чем чистые эмбединги.
Поэтому я попросил агента попробовать сделать Exact Search (все еще используя WASM).
Агент нашел низкоуровневые методы _usearch_exact_search и сгенерировал воркер (coreWorker.js), который вручную управляет памятью, аллоцирует буферы через _malloc и гоняет указатели.
Браузеры пока плохо умеют в нативный FP16, поэтому агенту так же пришлось написать конвертер FP16 -> FP32 на лету при чтении векторов. Для меня это выглядит как магия, но это работает быстро даже на 300к векторов, даже без HNSW-индексов.

Замыкаем агентный цикл через Chrome MCP

Особую роль сыграл Chrome MCP. Без него использование LLM для веб-разработки выглядит как пинг-понг: "Даешь агенту задание" -> "Проверяешь нормально ли это работает в браузере" -> "Видишь ошибки или недочеты и просишь агента исправить". И логичной идеей выглядит замкнуть цикл чтобы агент сам мог посмотреть на результатысвоей работы (например WASM ошибки памяти).


Результаты: Ожидания vs Реальность

Помимо метрик, я проверял модель "глазами".

Что не получилось:
Я надеялся на красивую векторную арифметику, как в NLP (King - Man + Woman = Queen).
Гипотеза: Pandas - Python + TypeScript = Danfo.js.
Реальность: Это не сработало. Векторное пространство репозиториев оказалось устроено сложнее, и простые линейные операции там не дают такой красивой интерпретации.

Помимо этого я надеялся на красивую кластерную структуру эмбедингов, но к сожалению она тоже не выглядит слишком выраженной.

Что получилось:
Главная цель достигнута - поиск находит альтернативы, о которых я не знал, но семантически релевантны.
В отличие от LLM, которые часто имеют bias в сторону самых популярных решений, этот подход, основанный на поведении IT-специалистов, выкапывает:

  1. Нишевые инструменты: Библиотеки, которые используют профи, но о которых мало пишут в блогах.

  2. Свежие решения: Репозитории, которые набрали популярность недавно и имеют схожий паттерн "звездообразования".

  3. Local-first: Все работает локально на клиентских устройствах.

Final words

После нескольких лет работы с LLM мне хотелось сделать шаг назад, "тряхнуть стариной" и вспомнить классические подходы. Так же хотелось показать что "старые добрые" эмбеддинги все еще могут быть полезными в мире где все в наше время кажется крутиться вокруг GenAI.

Комментарии (1)


  1. cmyser
    06.01.2026 09:50

    Здорово ! Ссылочки бы в итогах продублировать ещё

    Можно ли похожим образом можно сделать локальную web rag систему ?