Путь к оптимизации поиска изображений миллиардного масштаба (2/2)
Эта статья — вторая часть Пути к оптимизации поиска изображений миллиардного масштаба от UPYUN. Если вы пропустили первую часть, нажмите здесь.
Система поиска по изображению второго поколения
Система поиска по изображению второго поколения технически выбирает решение CNN + Milvus. Система основана на векторах признаков и обеспечивает лучшую техническую поддержку.
Извлечение признаков
В области компьютерного зрения использование искусственного интеллекта стало мейнстримом. Аналогично, извлечение признаков в системе поиска по изображению второго поколения использует сверточную нейронную сеть (CNN) в качестве базовой технологии
Термин CNN сложен для понимания. Здесь мы сосредоточимся на ответах на два вопроса:
- Что может делать CNN?
- Почему я могу использовать CNN для поиска изображений?
Фото от memegenerator.net
В области ИИ существует множество соревнований, и классификация изображений — одно из самых важных. Задача классификации изображений — определить, относится ли содержимое картинки к кошке, собаке, яблоку, груше или другим типам объектов.
Что может делать CNN? Она может извлекать признаки и распознавать объекты. Она извлекает признаки по нескольким измерениям и измеряет, насколько признаки изображения близки к признакам кошек или собак. Мы можем выбрать наиболее близкие в качестве результата идентификации, который указывает, относится ли содержимое конкретного изображения к кошке, собаке или чему-то еще.
Какова связь между функцией идентификации объектов CNN и поиском по изображению? Нам нужен не конечный результат идентификации, а вектор признаков, извлеченный по нескольким измерениям. Векторы признаков двух изображений с похожим содержанием должны быть близки.
Какую модель CNN следует использовать?
Ответ — VGG16. Почему выбрать ее? Во-первых, VGG16 обладает хорошей способностью к обобщению, то есть она очень универсальна. Во-вторых, векторы признаков, извлекаемые VGG16, имеют 512 измерений. Если измерений очень мало, точность может пострадать. Если измерений слишком много, стоимость хранения и вычисления этих векторов признаков относительно высока.
Использование CNN для извлечения признаков изображений — это распространенное решение. Мы можем использовать VGG16 в качестве модели и Keras + TensorFlow для технической реализации. Вот официальный пример Keras:
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
model = VGG16(weights=’imagenet’, include_top=False)
img_path = ‘elephant.jpg’
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
features = model.predict(x)
Извлеченные здесь признаки являются векторами признаков.
1. Нормализация
Чтобы облегчить последующие операции, мы часто нормализуем признак:
В дальнейшем также используется нормализованный norm_feat.
2. Описание изображения
Изображение загружается с помощью метода image.load_img из keras.preprocessing:
from keras.preprocessing import image
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
Фактически это метод TensorFlow, вызываемый Keras. Подробности см. в документации TensorFlow. Итоговый объект изображения на самом деле является экземпляром PIL Image (PIL, используемый TensorFlow).
3. Преобразование байтов
На практике содержимое изображений часто передается через сеть. Поэтому вместо загрузки изображений из пути мы предпочитаем напрямую преобразовывать байтовые данные в объекты изображения, то есть PIL Images:
import io
from PIL import Image
# img_bytes: 图片内容 bytes
img = Image.open(io.BytesIO(img_bytes))
img = img.convert('RGB')
img = img.resize((224, 224), Image.NEAREST)
Приведенное выше img совпадает с результатом, полученным методом image.load_img. Нужно обратить внимание на две вещи:
- Необходимо выполнить преобразование в RGB.
- Необходимо изменить размер (resize — это второй параметр
load_img method).
4. Обработка черных рамок
Изображения, такие как скриншоты, иногда могут иметь довольно много черных рамок. Эти черные рамки не имеют практической ценности и сильно мешают. По этой причине удаление черных рамок также является распространенной практикой.
Черная рамка по сути представляет собой строку или столбец пикселей, где все пиксели имеют значение (0, 0, 0) (RGB-изображение). Удалить черную рамку — значит найти эти строки или столбцы и удалить их. На самом деле это 3-D матричное умножение в NumPy.
Пример удаления горизонтальных черных рамок:
# -*- coding: utf-8 -*-
import numpy as np
from keras.preprocessing import image
def RemoveBlackEdge(img):
Args:
img: PIL image instance
Returns:
PIL image instance
"""
width = img.width
img = image.img_to_array(img)
img_without_black = img[~np.all(img == np.zeros((1, width, 3), np.uint8), axis=(1, 2))]
img = image.array_to_img(img_without_black)
return img
Это в целом то, о чем я хотел рассказать в части использования CNN для извлечения признаков изображений и реализации другой обработки изображений. Теперь давайте взглянем на векторные поисковые движки.
Векторный поисковый движок
Проблема извлечения векторов признаков из изображений решена. Тогда остаются следующие проблемы:
- Как хранить векторы признаков?
- Как вычислять сходство векторов признаков, то есть как выполнять поиск? Открытый векторный поисковый движок Milvus может решить эти две проблемы. На данный момент он хорошо работает в нашей производственной среде.
Логотип Milvus.
Milvus, векторный поисковый движок
Извлечения векторов признаков из изображения далеко не достаточно. Нам также нужно динамически управлять этими векторами признаков (добавление, удаление и обновление), вычислять сходство векторов и возвращать векторные данные в диапазоне ближайших соседей. Открытый векторный поисковый движок Milvus довольно хорошо выполняет эти задачи.
Оставшаяся часть этой статьи описывает конкретные практики и моменты, на которые следует обратить внимание.
1. Требования к CPU
Чтобы использовать Milvus, ваш CPU должен поддерживать набор инструкций avx2. Для систем Linux используйте следующую команду, чтобы проверить, какие наборы инструкций поддерживает ваш CPU:
cat /proc/cpuinfo | grep flags</code?
Затем вы получите что-то вроде:
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
То, что следует после flags, — это наборы инструкций, поддерживаемые вашим CPU. Конечно, их гораздо больше, чем мне нужно. Я просто хочу проверить, поддерживается ли конкретный набор инструкций, например avx2. Просто добавьте grep, чтобы отфильтровать его:
cat /proc/cpuinfo | grep flags | grep avx2
Если результат не возвращается, это означает, что данный конкретный набор инструкций не поддерживается. Тогда вам нужно сменить машину.
2. Планирование емкости
Планирование емкости — это первое, что мы учитываем при проектировании системы. Сколько данных нам нужно хранить? Сколько памяти и дискового пространства требуют эти данные?
Давайте быстро посчитаем. Каждое измерение вектора имеет тип float32. Тип float32 занимает 4 Bytes. Тогда вектор размерностью 512 требует 2 KB хранилища. По тому же принципу:
- Для хранения одной тысячи 512-мерных векторов требуется 2 МБ.
- Для хранения одного миллиона 512-мерных векторов требуется 2 ГБ.
- Для хранения 10 миллионов 512-мерных векторов требуется 20 ГБ.
- Для хранения 100 миллионов 512-мерных векторов требуется 200 ГБ.
- Для хранения одного миллиарда 512-мерных векторов требуется 2 ТБ.
Если мы хотим хранить все данные в памяти, системе потребуется как минимум соответствующий объём памяти.
Рекомендуется использовать официальный инструмент расчёта размера: Milvus sizing tool.
На самом деле наша память может быть не такой большой. (Это не имеет особого значения, если у вас недостаточно памяти. Milvus автоматически сбрасывает данные на диск.) Помимо исходных векторных данных, нам также нужно учитывать хранение других данных, таких как журналы.
3. Конфигурация системы
Дополнительную информацию о конфигурации системы см. в документации Milvus:
- Конфигурация сервера Milvus: https://milvus.io/docs/v0.10.1/milvus_config.md
4. Проектирование базы данных
Коллекция и раздел
- Коллекция также известна как таблица.
- Раздел относится к разделам внутри коллекции.
Базовая реализация раздела фактически такая же, как у коллекции, за исключением того, что раздел находится внутри коллекции. Но благодаря разделам организация данных становится более гибкой. Мы также можем выполнять запрос к конкретному разделу в коллекции, чтобы добиться лучших результатов запроса.
Сколько коллекций и разделов у нас может быть? Основная информация о коллекции и разделе хранится в Metadata. Milvus использует либо SQLite (внутренняя интеграция Milvus), либо MySQL (требуется внешнее подключение) для внутреннего управления метаданными. Если вы используете SQLite по умолчанию для управления Metadata, вы столкнётесь с серьёзной потерей производительности, когда количество коллекций и разделов станет слишком большим. Поэтому общее количество коллекций и разделов не должно превышать 50 000 (Milvus 0.8.0 ограничит это число до 4 096). Если вам нужно установить большее число, рекомендуется использовать MySQL через внешнее подключение.
Структура данных, поддерживаемая коллекцией и разделом Milvus, очень проста, а именно ID + vector. Другими словами, в таблице есть только два столбца: ID и векторные данные.
Примечание:
- ID должен быть целым числом.
- Нам нужно убедиться, что ID уникален в пределах коллекции, а не в пределах раздела.
Условная фильтрация
Когда мы используем традиционные базы данных, мы можем указывать значения полей в качестве условий фильтрации. Хотя Milvus не выполняет фильтрацию точно таким же образом, мы можем реализовать простую условную фильтрацию с помощью коллекций и разделов. Например, у нас есть большой объём данных изображений, и данные принадлежат определённым пользователям. Тогда мы можем разделить данные на разделы по пользователям. Следовательно, использование пользователя в качестве условия фильтра фактически означает указание раздела.
Структурированные данные и сопоставление векторов
Milvus поддерживает только структуру данных ID + vector. Но в бизнес-сценариях нам нужны структурированные данные, несущие бизнес-смысл. Другими словами, нам нужно находить структурированные данные через векторы. Соответственно, нам нужно поддерживать отношения сопоставления между структурированными данными и векторами через ID.
ID структурированных данных <--> таблица сопоставления <--> ID Milvus
Выбор индекса
Вы можете обратиться к следующим статьям:
- Типы индексов: https://www.milvus.io/docs/v0.10.1/index.md
- Как выбрать индекс: https://medium.com/@milvusio/how-to-choose-an-index-in-milvus-4f3d15259212
5. Обработка результатов поиска
Результаты поиска Milvus представляют собой набор ID + расстояние:
- ID: ID в коллекции.
- Расстояние: значение расстояния от 0 до 1 указывает уровень сходства; чем меньше значение, тем более похожи два вектора.
Фильтрация данных, чей ID равен -1
Когда количество коллекций слишком мало, результаты поиска могут содержать данные, чей ID равен -1. Нам нужно отфильтровать их самостоятельно.
Пагинация
Поиск векторов довольно сильно отличается. Результаты запроса сортируются в порядке убывания сходства, и выбираются наиболее похожие (topK) результаты (topK указывается пользователем во время запроса).
Milvus не поддерживает пагинацию. Нам нужно самостоятельно реализовать функцию пагинации, если она требуется для бизнеса. Например, если на каждой странице у нас по десять результатов и мы хотим отобразить только третью страницу, нам нужно указать, что topK = 30, и вернуть только последние десять результатов.
Порог сходства для бизнеса
Расстояние между векторами двух изображений находится в диапазоне от 0 до 1. Если мы хотим определить, являются ли два изображения похожими в конкретном бизнес-сценарии, нам нужно указать порог в этом диапазоне. Два изображения считаются похожими, если расстояние меньше порога, или они существенно отличаются друг от друга, если расстояние больше порога. Вам нужно настроить порог в соответствии с вашими бизнес-потребностями.
Эта статья написана rifewang, пользователем Milvus и инженером-программистом UPYUN. Если вам понравилась эта статья, будем рады, если вы зайдёте поздороваться @ https://github.com/rifewang.
Читать далее

Balancing Precision and Performance: How Zilliz Cloud's New Parameters Help You Optimize Vector Search
Optimize vector search with Zilliz Cloud’s level and recall features to tune accuracy, balance performance, and power AI applications.

Bringing AI to Legal Tech: The Role of Vector Databases in Enhancing LLM Guardrails
Discover how vector databases enhance AI reliability in legal tech, ensuring accurate, compliant, and trustworthy AI-powered legal solutions.

Introducing DeepSearcher: A Local Open Source Deep Research
In contrast to OpenAI’s Deep Research, this example ran locally, using only open-source models and tools like Milvus and LangChain.



