Как я учил компьютер понимать 122 000 фотографий — и почему сложностью оказались не нейронки, а слова. clip.. clip. computer vision.. clip. computer vision. embeddings.. clip. computer vision. embeddings. machine learning.. clip. computer vision. embeddings. machine learning. onnx.. clip. computer vision. embeddings. machine learning. onnx. tensorflow.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение. Обработка изображений.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение. Обработка изображений. Поисковые технологии.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение. Обработка изображений. Поисковые технологии. продуктовая разработка.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение. Обработка изображений. Поисковые технологии. продуктовая разработка. уменьшение размерности данных.. clip. computer vision. embeddings. machine learning. onnx. tensorflow. zero-shot learning. классификация изображений. Машинное обучение. Обработка изображений. Поисковые технологии. продуктовая разработка. уменьшение размерности данных. Фриланс.

Как я вообще туда попал

Я крайне редко на фрилансе получал заказы связанные с DS/ML, специалистов для таких задач обычно ищут не там. Причины разные: они требуют долгой интеграции, заказчик сам не понимает задачу, DS более конфиденциален, DS часто возникают внутри продукта, да и в последнее время этот сегмент на фрилансе съедается при помощи LLM: AI integration, RAG боты например. По отдельности эти факторы не страшны, но их совокупность уменьшает количество таких проектов на российском фрилансе почти до 0.

Но, внезапно, мне в личку постучались с таким проектом.

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

Датасет надо делать, в этом и смысл задачи
Целевые проценты даны для ориентира, никто не знает что на самом деле достижимо. Готов принять и худшие результаты, если они объективно обоснованы

Вот это порадовало! Человек хочет не красивых цифр, а честный результат.
Предложил 140к, три этапа, поэтапная оплата – сделали этап, сдали, оплатили. Если что-то не устроит – можно уйти к другому разрабу с результатами. Так риски меньше для обеих сторон. Он принял:

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

Ну и поехали.

Требования, от которых голова кругом

На бумаге всё выглядело чётко:

  • Минимум 200 тегов

  • 99%+ фото должны получить хотя бы один тег

  • Не более 3% фото с одинаковым набором тегов

  • Precision и recall — ориентир 90%

  • Работает локально, на CPU

  • Теги должны быть понятны обычному человеку без инструкции

И вот тут – штука, которая определила половину инженерных решений. Заказчик сразу обозначил:

Продукт включает только dlib для распознавания лиц. Он добавляет всего 5-10 МБ. Все модели из OpenCLIP потребуют torch, а он сам что-то вроде 300+ МБ

То есть нельзя просто взять SOTA-модель и сказать «вот, короче, работает». Она может не влезть в продукт. Это не академическая задача, а продуктовая. Ну и разница между ними – как между «решить уравнение на доске» и «построить мост, который не упадет».

Почему это не «ещё одна классификация картинок»

Я поначалу думал – ну, классификации, embedding, cosine similarity, дело техники)
Но..! ошибался.

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

Люди ищут не объекты, а воспоминания. Когда человеку нужно найти фото, он не думает «покажи объекты класса nature». Он думает: «где фото с той прогулки» или «найди фотки с моря», «убери все скрины». Это совершенно другая задача.

Теги должны работать как фильтры. Не «вот 50 тегов на фото, разбирайся сам» – а «вот 2-3 точных тега, которые позволяют сузить архив из 100 000 фото до 20». Заказчик сам привёл пример:

Желание найти фото “женщина в красном платье на фоне моря” может быть описано тремя тегами “портрет женщины”, “море”, “яркая одежда”. Этого достаточно чтобы сузить архив из 100 000 фотографий до двух десятков и выбрать нужную глазами.

Красивая постановка. Осталось реализовать)

Как выяснилось, что в интернете нет ответа на главный вопрос

Вот здесь начались отхождения от начального ТЗ.

Заказчик был убежден, что можно пойти в интернет и найти то, как люди ищут свои фотографии. Что где-то на Reddit, в Google Photos, в обзорах – люди обсуждают, какие теги они ставят, как классифицируют свой архив. Он даже скинул мне свой диалог с DeepSeek на эту тему.

Я сел изучать. И выяснил, что этого не существует.

К Google Photos чужим – нет доступа. Нельзя посмотреть, как другие люди организуют свои фотки, какие альбомы создают, по каким словам ищут. Это все закрыто. Да и в России не много кто намеренно разбирает фотки в Google Photos. Скорее они по дефолту иногда туда загружаются. Reddit – просто никто об этом не говорит. Гипотеза о том, что люди в интернете обсуждают, как они классифицируют свои фотки, – оказалась неверна. Может, лет двадцать назад это было актуально. Сейчас – УВЫ! Люди не думают про организацию архива, пока у них не накопится 50 000 фото и они не захотят найти конкретное.

На стоках типа Shutterstock, Unsplash, Pexels – теги есть, но они коммерческие. Они нужны для SEO, чтобы фото находили в интернете. «Happy», «business», «success» – это маркетинг, а не память. Огромная разница между тем, как интернет по алгоритму классифицирует фотографии, и тем, как человек для себя их обозначает. И вот тут мы с заказчиком пришли к выводу, что нельзя найти в интернете готовый ответ на вопрос «как люди для себя классифицируют фотки». Либо данных нет, либо их дистилляция заняла бы столько времени, что это был бы отдельный проект.

Недолго думая, я решил: сам придумаю. Я же человек. Пообщался с окружающими, с женой, сам с собой подумал – на какие сцены можно разделить фотографии. Не абстрактно, а конкретно: вот я открываю галерею, мне нужно найти фото – о чём я думаю в этот момент? И понял одну важную вещь. Люди мыслят не объектами. Не «красивый лес» и не «два человека». Люди определяют фотографии по месту, по ощущениям, по времени и настроению. «Это было летом на даче». «Это когда мы тусовались в парке». «Это тот вечер в центре, когда шел дождь». Память работает через контекст, а не через перечисление объектов.

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

Оси памяти

Получилось шесть основных координат:

  1. Тип контента – фото, скриншот, мем, документ, коллаж

  2. Место / сцена – дом, улица, парк, пляж, кафе, горы

  3. Активность – прогулка, спорт, еда, шоппинг, путешествие

  4. Событие – день рождения, свадьба, Новый год

  5. Люди —- мужчина, женщина, ребенок (не конкретные лица — это уже было в продукте)

  6. Качество и условия – темное, размытое, засвеченное, дождь, снег

Каждая ось – координата, по которой человек может сузить поиск. Комбинация 2-3 осей даёт достаточно узкую выборку, чтобы найти нужное фото глазами.

Как я собирал 122 000 фотографий

Заказчик сразу сказал: нужно минимум 100 000 фото для тестирования. Свои он давать не готов – они пойдут на валидацию результатов. Предложил: “Возьмите свои фотки и фотки знакомых/родных. Плюс можно поскачивать из интернета”

Я начал с идеи собрать по знакомым. Быстро понял – никто не хочет делиться своими фотками. Это личный архив, там все – от нелепых селфи до документов. Люди не готовы это отдавать. И даже если бы согласились – это не набрало бы 100 000. То есть суммарно мне скинули около 400 фоток.

Тогда я пошел в Telegram. Написал парсер на Telethon (userbot на Python), который проходил по каналам, проверял последние 20 000 сообщений и выгружал фотографии. Только открытые каналы, только публичный контент.

Но не случайные каналы. Я уже имел черновое древо тегов и для каждого тега искал тематический канал. ��обаки – каналы про собак. Дети — родительские чаты. Тусовки, концерты, еда, путешествия, горы, дача – на каждый тег свой источник. Плюс обязательно – каналы, где люди просто делятся своими фото, без тематики. Потому что типичный архив – это не тематическая подборка, а хаос. Много рандомных фото нашел в источниках типа “фото для фейка”, “фото на аву”. Были классные каналы фотографов – прям удовольствие местами получил

Потребовалось около 70 каналов. На выходе – 122 000 изображений: фотографии разного качества, скриншоты, мемы, документы, дубли, случайный мусор.
К концу, правда, понял, что для некоторых тегов эталонных фото не хватало – не все можно найти в открытых каналах. Но для MVP хватило.

Этап 1 — “Мы не оптимизируем. Мы выясняем, где сигнал”

Принцип первого этапа я зафиксировал жестко: никакой оптимизации. Только проверка – есть ли вообще сигнал?.. Потому что соблазн “а давайте ещё вот это попробуем” — он на каждом шагу. И если не ставить четких границ – будет страшно.

Максимально тупой pipeline

Для первого прохода – простой подход. Без обучения, без fine-tuning. Чистый zero-shot:

image → CLIP embedding (ViT-B-32)
tag → набор текстовых промптов
score = cosine_similarity(image, prompt)
if score >= threshold → assign tag

Каждый тег описывался несколькими текстовыми промптами. Один глобальный порог. 39 тегов. 122 000 фото. Один вопрос: работает или нет?

Результат удивил

Я взял top-30 фотографий по каждому тегу и глазами проверил.

21 тег – точность 90-100%. Пляж, парк, дом, селфи, еда, прогулка, пикник, транспорт. Без единого обучающего примера. Просто по текстовому описанию.

Ещt 9 тегов – 70-89%. И 9 провалились ниже 70%.

Думал будет больше шума, а оно работает)
На бытовых категориях zero-shot CLIP дает адекватный результат.

Но тут же вылезла системная проблема

Текстовые промпты оказались слабым звеном.

birthdayweddingnew_year — для CLIP это почти одно и то же. Люди, еда, украшения, помещение. Модель не виновата – она видит визуально похожие сцены, и никакая формулировка промпта не помогает их различить.

document путался с фотографиями книг. screenshot – с селфи в зеркало. meme – со скриншотами переписок.

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

Что сказал заказчик

Заказчик подтвердил приемку первого этапа и сразу обозначил направление:

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

Заказчик сам предложил ключевой поворот проекта. Не потому что ML-инженер – а потому что продуктовый разработчик, который думал на шаги вперед. Если мы завязаны на текстовый энкодер CLIP – мы завязаны на весь стек! А это, так то, 300+ МБ в дистрибутиве.

Вывод первого этапа уместился в одну фразу: сигнал есть, текст -узкое место.

Ключевой поворот: от слов к изображениям

Самый важный архитектурный момент проекта. Он изменил всё.

Было: класс = набор текстовых описаний.
Стало: класс = набор эталонных фотографий.

Вместо того чтобы объяснять модели текстом, что такое “пляж” – мы показываем ей 120 фотографий пляжей. Модель считает средний эмбеддинг – “центроид класса”. Новое фото сравнивается не с текстом, а с этим вот центроидом.

Что это даёт:

Нет зависимости от текстового энкодера. Можно использовать любую модель, которая превращает картинку в вектор: CLIP, ResNet, MobileNet, dlib – да что угодно!
Нет лексической ловушки. “День рождения” и “свадьба” различаются не по словам, а по визуальным паттернам.
Объяснимость. Можно посмотреть, из каких фото состоит класс. Понять, почему модель ошиблась. Улучшить – добавив или убрав примеры, например.
Масштабируемость. Новый тег = новая папка с фотографиями. Никакого переобучения.

Провал с hard negatives

Помимо положительного центроида нужен был отрицательный — чтобы штрафовать за «похож на общий фон». Рабочая формула:

score(tag) = cos(image, centroid_positive) - cos(image, centroid_negative

Это сильно снизило false positives. Но дальше я попробовал hard negatives – вручную подобранные «похожие, но не то». Если beach путается с pool – добавим фото бассейнов как negative.

Результат жесть. Слишком узкие negatives сломали шкалу скоров – модель начала видеть “пляж” в совершенно случайных фото. Массовый over-tagging.
Ну надо запомнить: negative нужен, но нейтральный фон датасета работает лучше ручного подбора. А для конфликтующих классов – нужен отдельный фильтр постфактум.

Этап 2 — шесть моделей и момент, когда заказчик сказал “это отстой”

Зачем сравнивать, если CLIP работает

Потому что продуктовые ограничения. PyTorch – 300+ МБ. У заказчика уже есть dlib – 5-10 МБ. Если эмбеддинги можно считать через dlib или TFLite – продукт распухнет на 50-80 МБ вместо 700. Это не академический интерес – это натуральное бизнес-требование.

Шесть кандидатов

  • CLIP RN50 – основной кандидат

  • CLIP ViT-L/14 – потолок качества (для контекста, не для прода)

  • MobileNet v3 Small – компактный

  • ResNet50 (ImageNet)EfficientNet-B0

  • dlib модели – ResNet34, ResNet50, ViT-S-16

Первый прогон: красивые цифры, которые ничего не значили

Первую версию сравнения я построил на coverage – какой процент фото получает тег. MobileNet показал лучший coverage. Я был доволен.

Но не заказчик:

Мы померяли охваты моделей, но это не то же самое что recall. У нас нет числовых показателей precision/recall. Сложно так принимать решение о выборе.

И далее:

Честно говоря, цифры немного расстраивают… RN50 конечно лучше, но тоже отстой.

Ну тут важно сказать одну вещь: когда заказчик говорит “отстой” – я не воспринимаю это как претензию к своей работе, я ж не изобретаю нейросеть с нуля. Я делаю research – беру готовые предобученные модели, делаю эмбеддинги, показываю результат. Если результат слабый – это факт о модели, а не результат моей работы. Это отчётность о чем-то фактическом. Я выявил результат, и точка.

Но пинок был правильный. Потому что coverage – действительно не recall. И без честного P/R/F1 нельзя принимать решение.

Как пришлось перестроить всё

  1. Собрал GT-датасет – 5500 фото, размеченных вручную по 15 тегам

  2. Посчитал честный Precision / Recall / F1 для каждой модели

  3. Добавил режимы сравнения: target P ≥ 0.9, target R ≥ 0.8

  4. Добавил метаданные: размер, лицензии, runtime, CPU latency

  5. Прогнал SOTA-ceiling – ViT-L/14

Заказчик еще отдельно ткнул в мою ошибку с dlib:

Почему вы из dlib взяли dlib_face? Это же для распознавания лиц, а не картинок. Там же есть resnet50_1000_imagenet_classifier, vit-s-16. Почему не попробовали их?

Действительно. Я взял face-модель как feature backend – проверить принцип работы dlib. Но нужно было брать classifier-модели. Протестировал дополнительно.

Результат

На GT (5500 фото, 15 тегов):

Модель

Precision

Recall

F1

RN50 (CLIP)

0.466

0.853

0.557

ViT-L/14 (ceiling)

0.644

0.565

0.538

MobileNet v3

0.457

0.712

0.514

dlib ResNet34

0.442

0.450

0.339

dlib ViT-S-16

0.432

0.200

0.203

Три наблюдения:

Первое. Разрыв между RN50 и потолком (ViT-L/14) – небольшой. При этом ViT-L/14 в три с лишним раза медленнее (153 vs 45 мс/фото). Потолок качества подтверждён – но он не настолько далеко, чтобы оправдывать тяжёлую модель.

Второе. dlib провалился. ImageNet-обученные модели дают эмбеддинги под 1000 фиксированных классов, а у нас – multilabel, пользовательские сцены, абстрактные теги типа walk и travel. Заказчик сам это резюмировал:

Они же на 1000 классах учили, а не на контрасте как CLIP

Другая постановка – другие требования к пространству эмбеддингов.

Третье. MobileNet был быстрее и компактнее, но ручная валидация показала: на сложных тегах больше borderline-случаев, однотипные результаты. Когда RN50 путает день рождения со свадьбой – это все таки визуально близко. Когда MobileNet путает день рождения с любым столом с едой – это уже раздражает.

Финальное решение: RN50 (CLIP) через ONNX Runtime. Без PyTorch в продакшене. ~45 мс на фото на CPU. ~77 МБ весов.

Этап 3 – “Меньше экспериментов, больше фиксации”

Третий этап – не про исследования, а про то, чтобы зафиксировать и упаковать все, что выжило после первых двух.

81 тег

Система выросла с 39 до 81 тега по всем шести осям. Места, активности, события, типы контента, люди, качество и условия – от beach до moon, от selfie до stairwell.

Индивидуальные пороги

На первом этапе – один глобальный порог. На третьем – каждый тег получил свой, подобранный по precision-recall кривой. Для beach – высокий (модель уверена). Для birthday – компромисс между “хоть что-то” и “не врать”.

Conflict-фильтр

screenshot и meme – скриншот переписки часто содержит мем. birthday и wedding – модель может поставить оба. Если два конфликтующих тега прошли порог – остается тот, у которого score выше.

Fallback для покрытия

Без fallback – 52.73% фото с тегами. С fallback — 99.07%.

Если ни один тег не прошёл порог – берется лучший тег с минимальным порогом. Это не “точный тег”, а “лучшее предположение”. Но для навигации – лучше, чем ничего. В отчетах я всегда считал эти цифры отдельно. Потому что coverage с fallback – продуктовая метрика. Coverage без – качественная. То есть – не путать их!.

Дополнительный этап – уменьшение размерности

После того как RN50+CLIP через ONNX стал финальным выбором, заказчик вернулся к вопросу, который поднял ещё на этапе 2:

Снижение размерности. 1024-мерный вектор – избыточен для 81 тега. Можно ужать?

Вопрос не праздный: меньше размерность – быстрее retrieval, меньше памяти. Договорились начать с 512 и смотреть, что теряется.
Проверяли два метода: PCA 512d и Random Projection 512d.

Ловушка красивых чисел

Автоматические метрики выдали неожиданную картину

Режим

Precision

Recall

F1

overlap@10

Baseline 1024d

0.823

0.474

0.542

PCA 512d

0.344

0.998

0.484

0.810

RP 512d

0.828

0.440

0.514

0.735

PCA показывает recall 0.998 – почти идеальный. Выглядит как победа. Но precision 0.344 – печаль. Модель начала ставить теги буквально всему. Та же история с coverage: загрубил – получил красивую цифру. Только здесь не намеренно – пространство схлопнулось, все эмбеддинги стали ближе друг к другу, и старые пороги поехали.

RP ведёт себя честнее по агрегатным метрикам, но хуже держит retrieval: overlap@10 = 0.735 против 0.810 у PCA.

По числам непонятно, нужна ручная проверка.

Что показала ручная валидация

Подготовил артефакты для визуального сравнения: для 30+ query-изображений – top-10 соседей в каждом из трех режимов, для сложных тегов (birthdayweddingscreenshotwalk и других) – top-30 кандидатов по score. Около 2000 примеров прошли ручную оценку.

Результат по retrieval – доля визуально осмысленных соседей:

  • baseline: 0.940

  • pca_512: 0.947

  • rp_512: 0.927

PCA не только не деградировал – он чуть лучше держит retrieval, чем baseline. По classification на сложных тегах деградации, заметной для пользователя, не выявлено.

Вывод простой: агрегатные метрики врали. Precision 0.344 – артефакт порогов, которые нужно перекалибровать под новое пространство. Визуальное поведение системы осталось рабочим.

Финал: PCA 512d в продакшене. Вдвое меньше размерность, быстрее retrieval, меньше памяти – без практической потери качества.

Итоговые цифры

GT-оценка (7000 фото, 30 тегов)

  • Precision: 0.654

  • Recall: 0.634

  • F1: 0.566

Полный прогон (122 263 фото)

  • Coverage с fallback: 99.07%

  • Coverage без fallback: 52.73%

  • Среднее тегов на фото: 1.558

  • Уникальных наборов тегов: 2023

Финальная конфигурация

RN50 (CLIP) → ONNX Runtime → PCA 512d. Размер модели ~77 МБ, ~45 мс на фото на CPU, 512-мерный эмбеддинг вместо 1024.

Разбор

F1 = 0.566 – это не 90% из ориентира. Но давайте разберём.

Macro-усреднение считает каждый тег одинаково. Сильные теги (beach, selfie, park) дают 95-100%. Слабые (birthday, eating) тянут среднее вниз. Ищешь “фото с пляжа” – работает отлично. “Фото с дня рождения” – шумнее.
Потолок подтверждён. SOTA-модель (ViT-L/14) на том же GT — F1 = 0.538. Не лучше RN50. Ограничение не в модели, а в сложности самой задачи.
52.73% без fallback – не “модель работает наполовину”, на половине фото модель уверена для жесткого порога. Остальные получают тег через fallback – менее уверенно, но полезно для навигации.

Про LLM в этом проекте

Весь код написан через LLM. Скрипты эмбеддинга, классификации, прогонов, валидации, экспорта в ONNX – все сгенерировано. Я работал в основном с Codex, там пока лимиты повышены на версию 5.3.

Конечно, были тупежи. Самый показательный – на третьем этапе мы с заказчиком четко зафиксировали: работаем с RN50. Все к этому подготовили и я смотрю – а Codex мне генерирует скрипт эмбеддинга на совершенно другую модель. ViT-S-16, вроде как – короче, не RN50. Я ему: “В спецификации конкретно написано, что мы работаем только с RN50”. А он просто проигнорировал контекст и взял что ему показалось подходящим.

Галлюцинации немного удлиняли процесс. Но в целом – все равно это намного быстрее, чем писать все скрипты руками. А их было достаточно много: парсер для сбора фото, эмбеддинги, прогоны по моделям, валидация, визуальные сэмплы, GT-оценка, экспорт ONNX, conflict-фильтр, fallback-логика. Руками это было бы в разы дольше.

Но – и это важно – архитектура, таксономия тегов, выбор модели, интерпретация результатов – это инженерная работа. Нейросеть не скажет тебе, что coverage – не recall. Не скажет, что дни рождения путаются со свадьбами потому что это контекстно разные, но визуально похожие события. И уж точно не придумает за тебя оси памяти, основанные на том, как реальные люди вспоминают свои фотографии.

Это должен понять человек.

Пять вещей, которые я вынес

Сначала таксономия, потом модель. beach работает на 100% не из-за модели, а потому что пляж – визуально однозначная категория. birthday не работает не из-за модели, а потому что день рождения – контекст, а не визуальный паттерн. Если не зафиксирован язык тегов – сравнение моделей бессмысленно.

Zero-shot – лучший разведчик, худший продакшен. Для первого прохода – гениально. За день можно проверить 50 гипотез. Строить на нём продукт – ловушка.

Coverage – опасная метрика. Можно загрубить пороги и показать 95%. А потом пользователь увидит, что на его борще стоит тег beach – и удалит приложение.

Продуктовые ограничения формируют архитектуру. Размер дистрибутива, CPU latency, лицензии – это не “потом разберемся”. Это часть архитектурного решения с первого дня.

Заказчик, который говорит “отстой” – хороший заказчик. Каждый его пинок заставлял меня перестроить что-то конкретное. Каждый раз результат становился лучше. Проект, где заказчик только говорит “ок, супер” — это проект, где никто не проверяет качество.

Что дальше

Проект завершён, развитие – отдельные треки:

Расширение до 200 тегов. Черновая иерархия на 190 готова. Нужен пересбор прототипов и калибровка.

Квантизация. FP16 работает без заметных потерь. INT8 — нужна отдельная валидация.

Снижение размерности. 1024-мерный вектор — избыточен для 81 тега. Заказчик сам предложил гипотезу — можно ужать до 10-20 параметров. Но это отдельный scope, потому что затрагивает ещё и поиск похожих фото.

Усиление сложных тегов. Событийные классы (birthdaytraveleating) требуют не смены модели, а расширения и доочистки прототипов.

Вместо заключения

Фотографии – это не изображения. Это фрагменты памяти. И когда строишь систему навигации по ним – самый важный вопрос не “какая модель лучше”, а “как человек будет это искать”.

Если ответ правильный – даже простой cosine similarity с прототипами дает результат, которым можно пользоваться. Если неправильный – никакой ViT-L/14 не поможет.

Инструмент – усилитель. Но усиливает он то, что ты в него вложил.

Проект выполнен как фриланс-заказ для десктопного приложения каталогизации фотоархивов. Все данные – из реальных отчетов и переписки. Немного пиара моего канала. Если есть вопросы по техничке – пишите в комментариях.

Автор: okoloboga

Источник

Rambler's Top100