Был у меня недавно проект: в нем нужно было реализовать поиск похожих цветов в базе автоэмалей. База, к слову, не мелкая — около 60 тысяч записей. Пользователи — сотрудники, и им важно, чтобы всё работало шустро, без долгих загрузок и всяких «подождите, ищем».
Первая мысль, как у любого — нужен векторный поиск. Типа «найди мне ближайшие цвета по RGB или LAB пространству». Ну и естественно сразу в голове всплывает pgvector — оно на слуху, популярно, куча гайдов. Уже почти начал устанавливать, но в какой-то момент остановился и задал себе простой вопрос: «А оно мне точно надо?»
Контекст у меня был такой: база у меня на PostgreSQL, но всё приложение — внутреннее, без публичного API, и работает оно под капотом почти всё в памяти. Таблицы, с которыми постоянно работают, я подгружаю в оперативку через posix_ipc, и использую их в виде pandas DF. И вот тут я подумал: если данные уже в памяти, зачем мне вообще плодить сущности?
pgvector — это красиво, модно, но это лишняя прослойка. Это нужно ставить расширение, тащить данные обратно из базы, писать SQL-запросы, потом обрабатывать. Даже если обёртку сделать удобную — это будет ощутимо медленнее. И не просто медленнее — нужно будет юзеру показать лоадер, заставить его ждать. А я вот терпеть не могу, когда система тормозит и юзер ждёт, когда она «сообразит».
Я решил сделать проще. Раз таблица уже в DataFrame, почему бы не сделать обычный евклидов поиск по RGB координатам напрямую с помощью numpy? LAB можно тоже использовать — но чтобы не усложнять, если юзер вводит LAB, я просто прогоняю его через colorspacious в RGB. лоя металликов и перламутров берем только средний цвет - 45 градусов спектрофотометра.
Нам не нужно суперточности в духе "серый слегка серее", нам нужно — визуально похоже, и по ТЗ этого достаточно.
Сел, написал функцию. Получилось буквально на коленке, без всяких зависимостей кроме numpy и pandas. Работает за 0.01 секунды — и результат юзер видит моментально, как будто система заранее всё знала. Ни одного «ожидайте» на экране. Всё просто и по делу.
def vector_search(df, r, g, b, count=30):
'''
descrip: Ищет count ближайших по цвету записей в df
param:
df — df с колонками r, g, b
r, g, b — координаты цвета
count — сколько ближайших цветов вернуть
return: df с колонками distance и similarity
'''
# Вычисляем евклидово расстояние между цветами
distances = np.sqrt((df['r'] - r) ** 2 + (df['g'] - g) ** 2 + (df['b'] - b) ** 2)
# Максимально возможное расстояние между цветами в RGB
max_distance = np.sqrt(255 ** 2 * 3)
# Вычисляем "похожесть" в процентах
similarity = 100 * (1 - distances / max_distance)
df['distance'] = distances
df['similarity'] = round(similarity, 2)# Для юзера процент совпадения
# Возвращаем ближайшие count записей по расстоянию
return df.nsmallest(count, 'distance')
Работает быстро, просто, понятно. Никаких зависимостей, никаких расширений к БД. Если честно, получаю моральное удовлетворение, когда делаю вот такие решения — без «enterprise боли», просто сработало и всё.
иногда лучше сесть, подумать и не городить велосипед, даже если он модный и с титановой рамой. Если можно обойтись наипростейшим решением — обходись.
Таким же способом можно организовать векторный полнотекстовый поиск. К примеру:
Берётся текст (название цвета, описание и т.п.) и прогоняется через векторизатор.
Самые простые:
• TfidfVectorizer из sklearn — быстрый, работает оффлайн.
• Или что-то вроде SentenceTransformer / fastText — они понимают смысл фразы.
Далее получаешь вектор запроса, сравниваешь его с векторами из базы через cosine similarity или ту же евклидову метрику. Это как расстояние между точками в многомерном пространстве.
Но такой задачи не было. Однако может кому-то пригодится.