Как я вообще туда попал
Я крайне редко на фрилансе получал заказы связанные с 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» – это маркетинг, а не память. Огромная разница между тем, как интернет по алгоритму классифицирует фотографии, и тем, как человек для себя их обозначает. И вот тут мы с заказчиком пришли к выводу, что нельзя найти в интернете готовый ответ на вопрос «как люди для себя классифицируют фотки». Либо данных нет, либо их дистилляция заняла бы столько времени, что это был бы отдельный проект.
Недолго думая, я решил: сам придумаю. Я же человек. Пообщался с окружающими, с женой, сам с собой подумал – на какие сцены можно разделить фотографии. Не абстрактно, а конкретно: вот я открываю галерею, мне нужно найти фото – о чём я думаю в этот момент? И понял одну важную вещь. Люди мыслят не объектами. Не «красивый лес» и не «два человека». Люди определяют фотографии по месту, по ощущениям, по времени и настроению. «Это было летом на даче». «Это когда мы тусовались в парке». «Это тот вечер в центре, когда шел дождь». Память работает через контекст, а не через перечисление объектов.
Вот так я начал выращивать первое древо тегов – не сверху вниз от возможностей модели, а от того, как устроена человеческая память.
Оси памяти
Получилось шесть основных координат:
-
Тип контента – фото, скриншот, мем, документ, коллаж
-
Место / сцена – дом, улица, парк, пляж, кафе, горы
-
Активность – прогулка, спорт, еда, шоппинг, путешествие
-
Событие – день рождения, свадьба, Новый год
-
Люди —- мужчина, женщина, ребенок (не конкретные лица — это уже было в продукте)
-
Качество и условия – темное, размытое, засвеченное, дождь, снег
Каждая ось – координата, по которой человек может сузить поиск. Комбинация 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 дает адекватный результат.
Но тут же вылезла системная проблема
Текстовые промпты оказались слабым звеном.
birthday, wedding, new_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 нельзя принимать решение.
Как пришлось перестроить всё
-
Собрал GT-датасет – 5500 фото, размеченных вручную по 15 тегам
-
Посчитал честный Precision / Recall / F1 для каждой модели
-
Добавил режимы сравнения: target P ≥ 0.9, target R ≥ 0.8
-
Добавил метаданные: размер, лицензии, runtime, CPU latency
-
Прогнал 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 соседей в каждом из трех режимов, для сложных тегов (birthday, wedding, screenshot, walk и других) – 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, потому что затрагивает ещё и поиск похожих фото.
Усиление сложных тегов. Событийные классы (birthday, travel, eating) требуют не смены модели, а расширения и доочистки прототипов.
Вместо заключения
Фотографии – это не изображения. Это фрагменты памяти. И когда строишь систему навигации по ним – самый важный вопрос не “какая модель лучше”, а “как человек будет это искать”.
Если ответ правильный – даже простой cosine similarity с прототипами дает результат, которым можно пользоваться. Если неправильный – никакой ViT-L/14 не поможет.
Инструмент – усилитель. Но усиливает он то, что ты в него вложил.
Проект выполнен как фриланс-заказ для десктопного приложения каталогизации фотоархивов. Все данные – из реальных отчетов и переписки. Немного пиара моего канала. Если есть вопросы по техничке – пишите в комментариях.
Автор: okoloboga
- Запись добавлена: 16.03.2026 в 14:20
- Оставлено в
Советуем прочесть:
- Упражнение 21. Запечатление лиц и имен
- Как запомнить место, вы положили какую-то вещь
- Компания «Элрон» представила полностью российский одноплатный компьютер на процессоре «СКИФ»
- Amber.Page представил прототип чехла AmberDeck, превращающий iPhone в карманный компьютер
- «Биологический компьютер» на основе клеток мозга научили играть в Doom
- Смартфоны Pixel 10 получили встроенную систему цифровой подписи фотографий
- Microsoft начала тестировать ИИ-распознавание фото в OneDrive
- Наша мыслительная система
- Мобилка, море и нейронки: зовём студентов на интенсив по iOS и Flutter
- Читать, писать, считать


