
Полная версия:
Вектор смысла: новый квадрант. Навигация в пространстве векторного поиска
И это число имеет смысл. Не произвольный, не случайный. Если модель эмбеддингов хорошая, близкие по смыслу объекты получают близкие векторы. Тексты про кофе оказываются рядом друг с другом. Тексты про кофе и тексты про чай – чуть дальше, но всё ещё в одной области, в области напитков. Тексты про кофе и тексты про квантовую физику – далеко друг от друга, в разных концах пространства.
Пространство смыслов обретает структуру. Похожее – рядом. Разное – далеко. Это не метафора. Это буквально так: близкие расстояния в пространстве векторов соответствуют близости смыслов в нашем понимании.
Можно представить это как карту. Только не географическую, а семантическую. На этой карте есть области: область текстов про технологии, область текстов про кулинарию, область текстов про политику. Внутри области технологий – подобласти: машинное обучение, веб-разработка, базы данных. И так далее, вглубь. Похожие тексты собираются в кластеры, разные – расходятся в стороны. Структура возникает сама, из расстояний между векторами. Никто её не программировал. Она проявилась.
Поиск похожего теперь превращается в геометрическую задачу. Есть запрос – текст, изображение, что угодно. Превращаем в вектор – помещаем на карту. Теперь нужно найти в базе векторы, которые ближе всего к этому. Ближайшие соседи. Самые маленькие расстояния. Это уже не философия, это алгоритм. Это код, который можно написать и запустить.
Звучит просто. Есть миллион векторов в базе, есть один запрос. Вычисляем расстояние от запроса до каждого из миллиона, находим минимальные. Сортируем. Берём первые десять. Готово.
Алгоритм прямолинейный, честный, правильный. И совершенно непрактичный на больших масштабах.
На тысяче векторов – мгновенно. На миллионе – терпимо, секунда-другая. На миллиарде – минуты на каждый запрос. Неприемлемо. Но это проблема следующих глав. Там мы узнаем, как люди научились искать быстро, пожертвовав кое-чем другим.
Пока важно другое. Расстояние между векторами – это мера похожести между объектами. Близко – значит похоже. Далеко – значит разное. У нас есть способ измерить смысл. Превратить интуитивное «это похоже на то» в конкретное число. Этого достаточно, чтобы построить поиск.
Однако здесь нужно предупреждение. Всё, что мы обсуждали, прекрасно работает в двух, трёх, даже десяти измерениях. Но эмбеддинги живут в сотнях и тысячах измерений. И там происходят странные вещи.
2.3. Проклятие размерности
Мы привыкли к трёхмерному миру. Верх и низ, лево и право, вперёд и назад – три направления, всё под контролем. Интуиция работает.
А теперь – тысяча измерений. Тысяча независимых направлений. Тысяча осей, перпендикулярных друг другу. Не укладывается в голове? Ни у кого не укладывается. И это не просто ограничение воображения – в высоких измерениях математика ведёт себя иначе. Интуиция, отточенная миллионами лет эволюции, начинает врать.
Вот первая странность. Возьмём куб. В двух измерениях – квадрат. Его площадь равна стороне в квадрате. Сторона 2 – площадь 4. В трёх измерениях – обычный куб. Его объём – сторона в кубе. Сторона 2 – объём 8. В тысяче измерений – гиперкуб, объект, который невозможно представить. Его «объём» – сторона в тысячной степени. Если сторона равна 2, то объём двумерного квадрата – 4, трёхмерного куба – 8, а тысячемерного гиперкуба – 2 в тысячной степени. Число с тремя сотнями нулей. Больше, чем атомов во Вселенной.
Это кажется абстракцией. Игрой с числами. Но следствия – практические и неизбежные.
Теперь возьмём шар внутри этого куба. Шар, который касается всех сторон куба изнутри. В двух измерениях – круг, вписанный в квадрат. Он занимает большую часть площади квадрата, примерно 78 процентов. В трёх измерениях шар, вписанный в куб, занимает уже только 52 процента объёма. Чем больше измерений – тем меньше доля.
В тысяче измерений шар, вписанный в гиперкуб, занимает исчезающе малую долю его объёма. Практически ноль. Число настолько маленькое, что калькулятор покажет просто 0. Весь «объём» гиперкуба сконцентрирован в его углах, далеко от центра. Шар в центре – почти пуст по сравнению с углами. Центр, который в трёхмерном мире кажется главным местом, в тысячемерном – пустыня.
Какое это имеет отношение к поиску похожего?
Прямое. Когда мы ищем ближайших соседей в высокомерном пространстве, мы сталкиваемся с парадоксом: все точки оказываются примерно одинаково далеко друг от друга.
Это называется «проклятие размерности». Термин ввёл математик Ричард Беллман в 1961 году, но проблема никуда не делась. В пространстве высокой размерности расстояния между случайными точками сходятся к одному значению. Разница между «близко» и «далеко» размывается. Ближайший сосед оказывается лишь чуть ближе, чем самый дальний. Само понятие «ближайший» теряет остроту.
Представьте: вы ищете документ, похожий на ваш запрос. В базе миллион документов. В низкоразмерном пространстве – скажем, в двух измерениях – ближайший документ может быть на расстоянии 0.1, а самый дальний – на расстоянии 100. Разница в тысячу раз. Понятно, кто близок, кто далёк. Выбор очевиден.
В тысячемерном пространстве картина другая. Ближайший документ может быть на расстоянии 45.2, а самый дальний – на расстоянии 47.8. Разница – пять процентов. Все примерно одинаково далеко. Как выбрать «похожих», если похожи все одинаково? Или одинаково непохожи?
Это реальная проблема. Не теоретическая, не надуманная. Не пугало из учебника, а ежедневная реальность. Современные модели эмбеддингов выдают векторы в сотнях и тысячах измерений. BERT – одна из таких моделей для текста – даёт 768 измерений. Некоторые модели – 1536 и больше. Проклятие размерности присутствует всегда, в каждой векторной базе, в каждом семантическом поиске.
И всё же – векторный поиск работает. Миллионы систем используют его каждый день. Поисковики, рекомендательные системы, чат-боты – все они опираются на сравнение векторов в высокоразмерном пространстве. Как это возможно, если расстояния теряют смысл? Если проклятие размерности всё портит?
Ответ в том, что настоящие данные – не случайные точки.
Случайные точки, равномерно разбросанные по гиперкубу, действительно схлопываются в облако, где все одинаково далеки друг от друга. Математика неумолима. Но тексты, изображения, объекты из нашего мира – не случайны. Они имеют структуру. Глубокую, многослойную структуру, порождённую реальностью.
Тексты про кофе похожи друг на друга не случайно – они описывают один и тот же феномен, используют похожую лексику, обращаются к похожему опыту. Фотографии котов похожи друг на друга не случайно – на них изображены существа с четырьмя лапами, усами, определёнными пропорциями тела. Эта похожесть – не артефакт алгоритма, а свойство мира. Мир структурирован, и данные о мире наследуют эту структуру.
Хорошие модели эмбеддингов обучены улавливать эту структуру. Они не разбрасывают векторы случайно по всему пространству. Они группируют похожее рядом, раздвигают разное. Данные из реального мира занимают не всё пространство, а некое подмногообразие меньшей размерности. Грубо говоря – поверхность внутри пространства, а не всё пространство целиком. Тонкая плёнка смысла в океане возможных, но бессмысленных комбинаций.
Это можно представить так. Возьмите лист бумаги – двумерный объект. Сверните его в трубочку и поместите в комнату. Технически бумага теперь в трёхмерном пространстве. Любая точка на ней имеет три координаты – x, y, z. Но точки на бумаге по-прежнему живут на двумерной поверхности. Расстояния между ними осмысленны, если измерять вдоль поверхности, а не напрямую сквозь воздух. Муравей, ползущий по бумаге, не знает о третьем измерении – для него мир двумерен, и расстояния в этом мире реальны.
С эмбеддингами похожая история. Да, они живут в пространстве из тысячи измерений. Формально у каждого вектора тысяча координат. Но подлинные данные – тексты, написанные людьми, фотографии настоящих объектов – кластеризуются на поверхностях гораздо меньшей размерности. Они не заполняют всё доступное пространство, они занимают его малую часть. Поэтому расстояния между ними остаются осмысленными. Поэтому поиск работает.
Есть и другие способы борьбы с проклятием. Инструменты, которые разработали исследователи за десятилетия работы.
Нормализация векторов. Если все векторы привести к единичной длине, они окажутся на поверхности гиперсферы. Это уже не весь объём пространства, а только его граница – тонкая оболочка. Косинусное сходство, о котором мы говорили, фактически работает именно так – сравнивает направления, игнорируя длину. На поверхности сферы проклятие размерности проявляется слабее.
Снижение размерности. Иногда тысяча измерений – избыточно. Многие координаты коррелируют друг с другом, несут дублирующую информацию. Можно спроецировать векторы в пространство меньшей размерности, сохранив структуру расстояний. Методы вроде PCA находят главные направления в данных и отбрасывают остальное – как если бы вы смотрели на статую и рисовали её силуэт на стене. Объём теряется, но форма узнаваема. UMAP идёт дальше, сохраняя локальные соседства. Теряется часть информации, но основные отношения между точками остаются.
Умные алгоритмы поиска. Вместо того чтобы сравнивать запрос со всеми точками подряд, можно построить структуру данных, которая позволяет быстро находить кандидатов. HNSW, о котором речь пойдёт позже, – один из таких алгоритмов. Он не страдает от проклятия размерности так сильно, потому что использует локальную структуру графа, а не глобальные расстояния. Ищет не везде, а там, где вероятнее всего найти.
Важно понимать: проклятие размерности – это не приговор. Это предупреждение. Оно говорит: не верь интуиции слепо. Не думай, что высокоразмерное пространство ведёт себя как трёхмерное. Не экстраполируй опыт трёхмерного мира на тысячу измерений. Но при правильном подходе – с хорошими данными, с правильной метрикой, с умными алгоритмами – работать можно. И нужно.
И люди научились работать. Десятилетия исследований, проб и ошибок привели к инструментам, которые справляются. Не идеально – идеального решения нет и быть не может. Но достаточно хорошо для практических задач. Достаточно хорошо, чтобы искать среди миллиардов векторов за миллисекунды. Достаточно хорошо, чтобы находить похожее там, где наивный подход захлебнулся бы.
Векторный поиск – это искусство компромиссов. Между точностью и скоростью. Между размерностью и вычислительной стоимостью. Между теоретическими ограничениями и практическими потребностями. Проклятие размерности – одно из таких ограничений. Оно не исчезает. Но его можно обойти, если знать, что делаешь. Если понимать, с чем имеешь дело.
Итак, у нас есть ментальная модель. Объекты – тексты, изображения, любые данные – превращаются в векторы. Точки в многомерном пространстве. Адреса в мире смыслов. Расстояние между точками измеряет похожесть между объектами. Близко – значит похоже, далеко – значит разное. Простая идея с мощными следствиями.
Мы узнали, что высокая размерность создаёт проблемы. Проклятие размерности – реальное явление, не выдумка теоретиков. Но мы также узнали, что эти проблемы не непреодолимы. Подлинные данные имеют структуру, и эта структура спасает. Умные алгоритмы и правильные метрики делают своё дело.
Теперь у нас есть язык для разговора о похожести. Есть способ измерить смысл – пусть не идеально, но достаточно хорошо. Есть понимание, почему это работает и где может сломаться.
Осталось понять одно. Мы говорили о векторах как о данности. Текст становится вектором. Изображение становится вектором. Но как именно это происходит? Кто или что превращает слова и пиксели в набор чисел? Откуда берутся эти координаты, которые так точно отражают смысл?
Это история следующих глав. История о том, как люди научились векторизовать мир. От первых попыток – примитивных, неуклюжих, теряющих смысл на каждом шагу – до современных моделей, которые понимают язык и изображения так, будто действительно их понимают. История поворотных моментов и неожиданных открытий. История о том, как математика поймала смысл – и что из этого вышло.
Часть II: Эволюция
Откуда это взялось? Векторные базы данных не возникли из ниоткуда. За ними – десятилетия исследований, ошибок, озарений и снова ошибок. История идей, не хронология продуктов.
Мы привыкли к тому, что технологии появляются готовыми. Открываешь документацию, копируешь код, получаешь результат. Но за каждой строкой API – годы работы людей, которые искали способ превратить смысл в числа. Они ошибались, начинали заново, спорили о направлениях. Иногда целые исследовательские программы заходили в тупик. Иногда случайная идея оказывалась поворотной. И в какой-то момент что-то сработало – так хорошо, что изменило всю область.
Эта часть – о том, как люди научились делать то, что казалось невозможным: измерять похожесть смыслов. Не похожесть букв, не совпадение слов – похожесть того, что стоит за ними. Путь оказался неочевидным. Первые попытки выглядели разумно, но упирались в стену. Прорыв пришёл оттуда, откуда не ждали – не от лингвистов и не от философов, а от инженера, который заставил нейронную сеть предсказывать соседние слова. А потом – цепная реакция: от поиска к генерации, от текста к изображениям и звуку.
Глава 3. Как научились векторизовать
Векторы не падают с неба. Кто-то должен превратить текст, изображение, звук в набор чисел – так, чтобы похожие объекты оказались рядом в пространстве. Это не тривиальная задача. Это центральная задача всей области.
Предыдущая глава дала нам инструмент: если есть векторы, можно измерить расстояние между ними. Близко – значит похоже. Но откуда взять эти векторы? Как превратить фотографию кота в тысячу чисел так, чтобы фотография другого кота оказалась рядом, а фотография собаки – дальше?
Эта глава – история о том, как люди научились это делать. От первых наивных попыток, которые работали, но теряли главное, до момента, когда математика поймала смысл. Путь занял десятилетия. Препятствия казались непреодолимыми. А потом – одна идея перевернула правила игры.
3.1. Тупик ключевых слов
Первая идея была простой и казалась разумной. Текст состоит из слов. Значит, чтобы сравнить два текста, нужно сравнить их слова. Посчитать, какие слова встречаются в обоих, – и вот тебе мера похожести.
Этот подход получил название «мешок слов» – Bag of Words. Название точное: текст буквально превращается в мешок, куда ссыпаны все его слова. Порядок не важен. «Собака укусила человека» и «человек укусил собаку» – один и тот же мешок. Структура предложения исчезает. Контекст исчезает. Остаётся только набор.
Технически это работало так: составляется словарь всех слов, которые встречаются в коллекции документов. Каждому слову присваивается номер – позиция в словаре. Документ превращается в вектор, где на каждой позиции стоит число – сколько раз соответствующее слово встретилось в тексте. Если словарь содержит десять тысяч слов, каждый документ становится вектором из десяти тысяч чисел. Большинство из них – нули, потому что в конкретном тексте используется лишь малая часть всех возможных слов. Такие разреженные векторы легко хранить и быстро сравнивать.
Два документа можно сравнить, сравнив их векторы. Чем больше общих ненулевых позиций, тем больше общих слов, тем документы «похожее». Математика простая, вычисления быстрые, результат понятный. Вектор становится числовым отпечатком документа.
Для некоторых задач этого хватало. Если нужно понять, о чём документ в целом – о медицине, о спорте, о политике, – мешок слов справлялся. Слова «диагноз», «пациент», «лечение» достаточно надёжно указывают на медицинскую тематику, даже если мы не знаем, в каком порядке они стоят.
Но для поиска похожего этот подход быстро упёрся в стену.
Представим интернет-магазин. Пользователь загружает фотографию платья и хочет найти похожие. Но у нас нет фотографий – только текстовые описания товаров. Пользователь не знает точных слов, которые использовал копирайтер. «Элегантное вечернее платье в пол» или «длинный нарядный наряд для торжеств» – как угадать?
Или служба поддержки. Клиент пишет: «Не могу зайти в личный кабинет». В базе знаний есть статья «Проблемы с авторизацией в персональном разделе». Смысл тот же, слова разные. Мешок слов не найдёт.
Первая проблема – синонимы. Пользователь ищет «автомобиль», а в документе написано «машина». Мешок слов не видит связи. Для него это разные слова, разные элементы, нулевое пересечение. Документ о машинах не найдётся по запросу об автомобилях – хотя речь об одном и том же.
Это не редкий случай. Язык избыточен по своей природе. «Дом» и «жилище», «идти» и «шагать», «большой» и «крупный» – пары множатся бесконечно. Профессиональные тексты используют одну терминологию, разговорные – другую. Научная статья о «кардиологических патологиях» не найдётся по запросу «болезни сердца», хотя говорит о том же самом.
Можно попробовать решить это словарём синонимов. Но словарь нужно составлять вручную. Он никогда не будет полным. И он не учитывает контекст: «крутая тачка» и «крутой поворот» используют слово «крутой» в совершенно разных значениях. «Тяжёлый чемодан» и «тяжёлый характер» – разные «тяжёлые».
Вторая проблема – омонимы. «Банк» может означать финансовую организацию или берег реки. «Ключ» – инструмент, источник воды или музыкальный знак. «Лук» – оружие, овощ или модный образ. «Мир» – вселенная, отсутствие войны или община. Мешок слов не различает эти значения. Для него «ключ от квартиры» и «ключ бьёт из-под земли» – одинаково релевантны запросу «ключ».
Появилась идея улучшить подход. Не просто считать слова, а взвешивать их. Так в семидесятых годах родился TF-IDF – Term Frequency—Inverse Document Frequency. Идея элегантная: слово важно, если оно часто встречается в данном документе, но редко – в остальных. «Диагноз» в медицинской статье важнее, чем «и» или «в». Редкие термины получают больший вес, частые служебные слова – почти нулевой.
Формула выглядела изящно. Частота слова в документе умножается на логарифм от общего числа документов, делённого на число документов с этим словом. Чем реже слово в коллекции и чем чаще в конкретном тексте – тем выше его вес. Служебные слова вроде «и», «в», «на» встречаются везде и получают почти нулевой вес – они не помогают различать документы. Специфические термины, характерные именно для данного документа, получают высокий вес – они и есть его «подпись».
TF-IDF работал лучше. Поиск стал точнее. На нём построены классические поисковые системы – те, что индексировали интернет в девяностые и двухтысячные. Он до сих пор используется там, где нужна простота и скорость: в поиске по документации, в фильтрации спама, в начальном ранжировании результатов. Библиотеки для работы с TF-IDF есть в любом языке программирования, их можно поднять за час.
Для своего времени это был шаг вперёд. Пользователь вводил запрос, система находила документы с редкими ключевыми словами из запроса, ранжировала их по весам – и результат был приемлемым. Не идеальным, но работающим. Достаточно хорошим, чтобы построить на этом индустрию.
Но фундаментальные проблемы никуда не делись.
Синонимы по-прежнему невидимы. TF-IDF не знает, что «врач» и «доктор» – одно и то же. Что «быстрый» и «скорый» близки по смыслу. Что «недорогой» и «бюджетный» описывают похожее свойство. Каждое слово существует в изоляции, как будто язык – это просто список несвязанных ярлыков.
Омонимы по-прежнему неразличимы. Алгоритм не понимает, что «лист бумаги» и «лист дерева» – разные вещи. Что «операция на сердце» и «военная операция» не имеют ничего общего, кроме случайного совпадения букв.
И самое главное – смысл по-прежнему теряется. «Мне грустно» и «я в печали» – идентичны по значению, но не имеют ни одного общего слова. TF-IDF покажет нулевое сходство. «Счастлив» и «несчастлив» – противоположны по смыслу, но очень похожи по написанию. Алгоритм решит, что они близки.
Отрицание вообще невидимо для мешка слов. «Этот ресторан хороший» и «этот ресторан нехороший» – почти идентичные мешки. Одно маленькое «не» меняет смысл на противоположный, но статистически это шум. «Фильм понравился» и «фильм не понравился» будут считаться похожими документами.
Порядок слов, который несёт в себе половину смысла, отбрасывается полностью. «Человек кусает собаку» – новость. «Собака кусает человека» – рутина. Для мешка слов это одно и то же. «Я должен тебе сто рублей» и «ты должен мне сто рублей» – идентичные мешки, противоположные ситуации.
Проблема была глубже, чем казалось. Дело не в том, что алгоритмы плохие. Дело в том, что слова – это не смысл. Слова – это ярлыки, которые люди навешивают на смыслы. Один смысл может иметь много ярлыков. Один ярлык может указывать на разные смыслы. Пока мы работаем с ярлыками, мы работаем с поверхностью, не с глубиной.
Исследователи понимали это. В конце восьмидесятых появился латентно-семантический анализ – LSA. Идея была интересной: если слова «врач» и «доктор» встречаются в похожих документах, значит, они как-то связаны. Можно попробовать найти эти скрытые связи математически, разложив огромную матрицу «слова × документы» на компоненты. Под поверхностью буквальных совпадений должна скрываться латентная – скрытая – семантика.
LSA действительно находил некоторые связи. Синонимы иногда оказывались ближе друг к другу. Тематически связанные слова группировались. Результаты были обнадёживающими – казалось, ещё чуть-чуть, и проблема будет решена.
Но метод был громоздким, требовал огромных вычислений, а результаты оставались нестабильными и трудно интерпретируемыми. Главное – он по-прежнему не понимал смысл. Он находил статистические корреляции, но не значения. «Хороший» и «плохой» часто встречаются в одних и тех же контекстах – обзорах, оценках, отзывах – и LSA мог посчитать их близкими. Хотя они противоположны по значению.
Нужен был другой подход. Не сравнивать слова – сравнивать то, что за ними стоит. Но как измерить то, что стоит за словом? Как превратить значение в число?
Десятилетиями это казалось философским вопросом, а не инженерной задачей. Смысл – категория человеческая, субъективная, неуловимая. Лингвисты спорили о природе значения. Философы писали трактаты о референции и денотации. А инженерам нужно было что-то, что можно посчитать.
Можно ли формализовать смысл? Можно ли научить машину понимать, что «король» ближе к «королеве», чем к «карандашу»? Что «бежать» и «мчаться» – почти одно и то же, а «бежать» и «лежать» – нет, несмотря на рифму? Что «не люблю» – противоположность «люблю», хотя отличается всего на два символа?
Интуитивно казалось, что нельзя. Смысл – это то, что понимает человек. Машина оперирует символами, не значениями. Она может сравнить строки, посчитать совпадения, применить статистику – но понять? Это из другой области.
Оказалось – можно. Но решение пришло не из лингвистики и не из философии. Оно пришло из наблюдения за тем, как слова ведут себя в тексте. Из простой идеи, оказавшейся ключом: слово определяется своим окружением.
Лингвист Джон Фёрс сформулировал это ещё в пятидесятых: «Ты узнаешь слово по компании, которую оно водит». Слово «кофе» появляется рядом с «чашка», «утро», «аромат», «бодрость». Слово «чай» – рядом с похожими словами. Значит, «кофе» и «чай» в чём-то близки, хотя сами по себе не похожи. Контекст определяет значение.
Идея красивая. Но от красивой фразы до работающего алгоритма – пропасть. Преодолеть её удалось только через полвека, когда появились нейронные сети, большие данные и достаточно мощные компьютеры. И человек, который соединил всё это вместе.
3.2. Word2Vec
Две тысячи тринадцатый год. Томас Миколов, исследователь из Google, публикует статью с неброским названием. Никаких громких обещаний, никакого маркетинга. Просто описание метода, который он назвал Word2Vec – «слово в вектор».
Идея выглядела почти наивно. Берём огромный массив текстов – всю Википедию, миллионы книг, весь доступный интернет. Строим простую нейронную сеть с одной задачей: по слову предсказать его соседей. Или наоборот – по соседям угадать пропущенное слово. Сеть ошибается, корректирует свои веса, снова ошибается, снова корректирует. Миллиарды раз.
Сама по себе задача предсказания соседних слов не очень интересна. Интересно другое. В процессе обучения сеть вынуждена как-то представлять слова внутри себя. Каждое слово получает внутреннее представление – набор чисел, которые сеть использует для своих вычислений. Эти числа и есть вектор слова.
Миколов обнаружил, что эти векторы – не просто технический артефакт. Они несут смысл.

