Демо: похожие слова рядом

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

Главная мысль урока: если выдать каждому слову координаты-эмбеддинг, то «похоже по смыслу» превращается в «близко по расстоянию». А расстояние между точками — это уже не философия, а пара строк кода, которые ты сейчас запустишь.

Зачем тебе это нужно

Представь поиск по музыке. Ты вбиваешь «грустный медленный трек на вечер», а сервис достаёт песни, в названии которых нет ни одного из этих слов — но по настроению они подходят идеально. Или рекомендации в соцсети: ты лайкнул пару мемов про котов, и лента тут же подсовывает ещё десяток похожих, хотя картинки разные. Как машина понимает, что одно «похоже» на другое?

Секрет в том, что внутри у каждого такого сервиса слова и объекты живут не как буквы, а как точки с координатами. И «похожи» для компьютера буквально означает «лежат близко друг к другу». В прошлом уроке про эмбеддинги — слова как точки мы разобрали эту идею на пальцах. Сегодня доведём её до конца: ты запустишь код, который возьмёт координаты слов и сам посчитает, кто кому ближе.

Вот результат, к которому мы придём за этот урок:

кошка ↔ собака: 0.15
кошка ↔ дом:    1.08
Кто ближе к кошке? собака

Два числа и один вывод — но за ними стоит ровно та идея, на которой работает поиск, рекомендации и сам ChatGPT. Разберём, откуда они берутся.

Эмбеддинг — представление слова или объекта в виде точки в многомерном пространстве, где похожие смыслы лежат рядом.

Метафора: слова как точки на карте

Вспомни карту в телефоне. Каждое место на ней — это пара чисел: широта и долгота. Твоя школа и ближайший магазин имеют близкие координаты, потому что они рядом. А школа и другой город — далёкие координаты, потому что между ними сотни километров. Зная только цифры координат, ты можешь посчитать расстояние, ни разу не выходя из дома.

Эмбеддинги — это такая же карта, только не для городов, а для смыслов. Каждому слову выдаётся набор чисел-координат. И главная хитрость: слова раскладывают так, чтобы близкие по смыслу оказались на карте рядом. «Кошка» и «котёнок» — соседние дворы. «Кошка» и «собака» — соседние улицы (оба животные, оба домашние). А «кошка» и «дом» — разные районы города.

На настоящей карте координат всего две — широта и долгота. У эмбеддингов их сотни: смыслов слишком много, чтобы уложить их на плоский лист. Но идея не меняется от количества чисел. Поэтому в нашем демо мы возьмём всего три координаты на слово — этого хватит, чтобы прочувствовать механику, и при этом числа останутся понятными глазу.

На карте городовВ пространстве смыслов
Место = широта, долготаСлово = набор координат (эмбеддинг)
Рядом = близко географическиРядом = похоже по смыслу
Расстояние считаем по формулеРасстояние считаем точно так же

Откуда эта карта берётся? Её никто не рисует вручную. Модель строит координаты сама, пока читает гигантские объёмы текста, и пользуется простым наблюдением: слова, которые встречаются в похожих местах, скорее всего похожи по смыслу. «Кошка» и «собака» постоянно стоят рядом с «гладить», «корм», «питомец», «лапы» — вот их и тянет в один угол карты. А «дом» окружают совсем другие слова, поэтому он оседает в другом районе. Подробно этот механизм мы трогали в прошлом уроке; сейчас нам важно лишь, что готовая карта уже есть, и мы можем с ней работать как с обычными числами.

И ещё одна важная мелочь. Координаты эмбеддингов — это не «настоящие» числа из реального мира, как рост или вес. Это удобная выдумка, которую модель подобрала так, чтобы расстояния отражали смысловую близость. Поэтому бессмысленно спрашивать «а почему у кошки именно 0.9, а не 0.91» — конкретное значение не важно само по себе, важно лишь, насколько оно близко к значениям других слов. Карта работает как система относительных расстояний, а не как линейка с абсолютным нулём.

Как меряют расстояние между точками

Между двумя точками расстояние считают так же, как ты, возможно, делал на уроке геометрии. Берём разницу по каждой координате, возводим в квадрат, складываем и извлекаем корень. На плоскости это знаменитая теорема Пифагора. В пространстве с тремя (или тремя сотнями) координат — всё то же самое, просто слагаемых больше.

Звучит как матан, но в коде это буквально цикл: пробежался по координатам, накопил сумму квадратов разностей, взял корень. Сейчас увидишь — это короче, чем кажется.

Демо 1: кто ближе к кошке — собака или дом

Заведём игрушечную карту смыслов на пять слов. Каждому слову — три координаты. Числа я подобрал руками, по-честному, как если бы их выучила модель: первое число грубо отвечает за «животное ли это», второе — за «съедобное/питьевое», третье — за «живое/домашнее существо». Не вчитывайся в точные значения, важна только их близость.

// Игрушечные эмбеддинги: каждому слову — 3 координаты.
// Похожие по смыслу слова имеют похожие числа.
const emb = {
  "кошка":   [0.9, 0.1, 0.8],
  "котёнок": [0.85, 0.15, 0.75],
  "собака":  [0.8, 0.2, 0.85],
  "молоко":  [0.2, 0.9, 0.3],
  "дом":     [0.1, 0.3, 0.1]
};

// Расстояние между двумя точками (теорема Пифагора).
function dist(a, b) {
  let s = 0;
  for (let i = 0; i < a.length; i++) {
    const d = a[i] - b[i];
    s += d * d;       // копим квадраты разностей
  }
  return Math.sqrt(s); // и берём корень
}

const d1 = dist(emb["кошка"], emb["собака"]);
const d2 = dist(emb["кошка"], emb["дом"]);

console.log("кошка ↔ собака:", d1.toFixed(2));
console.log("кошка ↔ дом:   ", d2.toFixed(2));
console.log("Кто ближе к кошке?", d1 < d2 ? "собака" : "дом");

Вывод:

кошка ↔ собака: 0.15
кошка ↔ дом:    1.08
Кто ближе к кошке? собака

Разберём по шагам, что произошло.

  1. Завели карту. Объект emb — это наш мини-словарь: ключ — слово, значение — три его координаты. Это и есть эмбеддинги в самом наглядном виде.
  2. Написали линейку. Функция dist бежит по координатам двух точек, на каждом шаге берёт разницу, возводит в квадрат и копит сумму в s. В конце — Math.sqrt, корень. Это та самая формула расстояния, без всякой магии.
  3. Замерили две пары. «Кошка ↔ собака» дали 0.15 — точки почти слиплись. «Кошка ↔ дом» дали 1.08 — в семь раз дальше.
  4. Сделали вывод. Простое сравнение d1 < d2 — и код сам говорит: собака ближе.

Заметь: код нигде не «знает», что кошка и собака — животные. Он не понимает смысла слов вообще. Он просто сложил и сравнил числа. Вся «понятливость» спрятана в том, как заранее подобрали координаты. Это и есть фокус эмбеддингов: смысл превращается в числа, а дальше работает обычная арифметика.

Полезно остановиться на секунду и оценить, насколько это удобно. Раньше, чтобы научить программу понятию «похоже», пришлось бы вручную прописывать тысячи правил: «кошка похожа на собаку, потому что обе домашние, четвероногие, мяукают или лают...». Это бесконечная и хрупкая работа — на каждое исключение придётся новое правило. Эмбеддинги убирают всю эту возню: вместо правил у нас просто координаты, а вместо «похоже» — одно число-расстояние. Любые два слова можно сравнить одной и той же функцией dist, не придумывая для них ничего особенного. Именно поэтому подход с эмбеддингами оказался таким сильным и лёг в основу почти всего современного ИИ, который работает с текстом.

Демо 2: выстроим всех соседей кошки по близости

Сравнивать по две точки скучно. Давай сделаем то, что делает настоящий поиск: возьмём слово «кошка» и отсортируем все остальные слова по близости к нему — от самого похожего к самому далёкому.

const emb = {
  "кошка":   [0.9, 0.1, 0.8],
  "котёнок": [0.85, 0.15, 0.75],
  "собака":  [0.8, 0.2, 0.85],
  "молоко":  [0.2, 0.9, 0.3],
  "дом":     [0.1, 0.3, 0.1]
};

function dist(a, b) {
  let s = 0;
  for (let i = 0; i < a.length; i++) {
    const d = a[i] - b[i];
    s += d * d;
  }
  return Math.sqrt(s);
}

const target = "кошка";
// все слова, кроме самой кошки
const others = Object.keys(emb).filter(w => w !== target);

// посчитать расстояние до каждого и отсортировать
const ranked = others
  .map(w => ({ word: w, d: dist(emb[target], emb[w]) }))
  .sort((a, b) => a.d - b.d);

console.log("Слова по близости к «" + target + "»:");
for (const r of ranked) {
  console.log("  " + r.word + " → " + r.d.toFixed(2));
}
console.log("Самое близкое:", ranked[0].word);

Вывод:

Слова по близости к «кошка»:
  котёнок → 0.09
  собака → 0.15
  дом → 1.08
  молоко → 1.17
Самое близкое: котёнок

Что мы сделали:

  • others — все слова, кроме самой кошки (сравнивать слово с собой бессмысленно — расстояние было бы ноль).
  • .map(...) превращает каждое слово в пару «слово + расстояние до кошки».
  • .sort(...) выстраивает эти пары от меньшего расстояния к большему.

И смотри, какой получился порядок: котёнок (это почти кошка), потом собака (другое, но тоже животное), и только потом далёкие дом и молоко. Ровно так интуитивно расставил бы их и ты. Только мы не вкладывали в код никакого здравого смысла — он сам выстроился из чисел. Вот так же, по сути, работает «похожие треки» в музыке и «похожие товары» в магазине: у всего есть координаты, а сервис просто ищет ближайшие точки.

Демо 3: возвращаемся к «Кошка пьёт ...»

Пора вернуть второй сквозной пример курса — фразу, которую модель должна продолжить: «Кошка пьёт ...». В уроке про токены мы видели, что модель подбирает следующий токен. Теперь покажем это через близость точек, чтобы связать обе наши сквозные истории.

Идея такая: у фразы «Кошка пьёт ...» есть свой смысл — «дальше должно идти что-то жидкое, что пьют». Представим этот смысл тоже точкой в нашем пространстве — назовём её context. А дальше поступим как в демо 2: найдём слово-кандидата, чья точка ближе всего к этому смыслу.

// Кандидаты на продолжение фразы и их координаты
const emb = {
  "молоко": [0.2, 0.9, 0.3],
  "воду":   [0.25, 0.85, 0.35],
  "сок":    [0.3, 0.8, 0.4],
  "камень": [0.1, 0.2, 0.9]
};

// Смысл фразы «Кошка пьёт ...»: что-то жидкое и питьевое.
// Это тоже точка в нашем пространстве.
const context = [0.22, 0.88, 0.32];

function dist(a, b) {
  let s = 0;
  for (let i = 0; i < a.length; i++) {
    const d = a[i] - b[i];
    s += d * d;
  }
  return Math.sqrt(s);
}

const ranked = Object.keys(emb)
  .map(w => ({ word: w, d: dist(context, emb[w]) }))
  .sort((a, b) => a.d - b.d);

console.log("Кошка пьёт ... — кандидаты:");
for (const r of ranked) {
  console.log("  " + r.word + " → " + r.d.toFixed(2));
}
console.log("Модель допишет:", ranked[0].word);

Вывод:

Кошка пьёт ... — кандидаты:
  молоко → 0.03
  воду → 0.05
  сок → 0.14
  камень → 0.90
Модель допишет: молоко

Смотри, что вышло. Питьё — молоко, вода, сок — столпилось рядом с точкой смысла фразы, расстояния крошечные. А «камень» — то, что не пьют — улетел далеко. Код выбрал ближайшее: «молоко». Получилось то же самое продолжение «Кошка пьёт молоко», что и в уроке про токены, но теперь ты видишь механику: предсказание следующего слова — это во многом поиск ближайшей точки в пространстве смыслов.

Конечно, у настоящего ChatGPT точку context не задают руками — её вычисляет нейросеть, разглядывая всю фразу целиком. Как именно она это делает, мы разберём дальше в курсе, когда дойдём до трансформера и внимания. Но фундамент ты уже потрогал руками: смысл — это точка, а «подходит» — это «близко».

Частые ошибки и подводные камни

На этом демо новички спотыкаются предсказуемо. Разберём, чтобы ты не попался.

1. Думать, что меньшее расстояние — это «хуже»

Тут всё наоборот, чем с оценками в школе. Чем меньше расстояние, тем больше похожи слова. Ноль означал бы «это одна и та же точка». Поэтому в сортировке мы ставим ближайших первыми: a.d - b.d выстраивает от маленьких чисел к большим, то есть от самых похожих к самым далёким.

2. Искать смысл в самих числах координат

В нашем демо я подписал, за что «отвечает» каждая из трёх координат, чтобы было нагляднее. У настоящих моделей так нельзя: там сотни координат, и ни одна не значит понятную человеку вещь вроде «животное ли это». Смысл живёт не в отдельном числе, а в том, как все координаты вместе располагают точки друг относительно друга. Не пытайся прочитать одну координату как ярлык.

3. Путать эмбеддинг с номером токена

Из прошлых уроков легко всё смешать. Напомню разницу: номер токена — это просто адрес кусочка в словаре, как номер дома, в нём нет смысла. Эмбеддинг — это координаты точки, и вот в них смысл уже зашит: близкие точки — близкие смыслы. Сначала текст превращают в номера токенов, и только потом каждому номеру сопоставляют его эмбеддинг.

4. Забывать, что код не понимает слов

Очень соблазнительно сказать «программа поняла, что кошка похожа на собаку». Нет. Программа сложила квадраты разностей и взяла корень — чистая арифметика. Вся «понятливость» заранее упакована в координаты. Эмбеддинги не делают компьютер умным; они переводят смысл на язык чисел, с которым он умеет работать.

5. Сравнивать точки разной длины

Если у одного слова три координаты, а у другого вдруг две, формула расстояния сломается или даст бессмыслицу — цикл пробежит только по общим координатам. В настоящих моделях у всех слов одинаковое число координат, иначе их нельзя было бы сравнивать. В нашем демо это тоже строго соблюдено: ровно три числа у каждого.

Мини-практика: построй свою карту смыслов

Теперь твоя очередь. Возьми за основу код из демо 2 и поэкспериментируй:

  1. Добавь в emb слово щенок с координатами [0.78, 0.22, 0.82] (это «маленькая собака»). Прогони сортировку по близости к кошке. На каком месте оказался щенок и почему именно там?
  2. Поменяй target с "кошка" на "молоко". Кто теперь окажется самым близким соседом? Совпало с твоей интуицией?
  3. Придумай и добавь своё слово из мира игр или музыки — например тигр или гитара — и сам прикинь ему три координаты по той же логике (животное? съедобное? живое?). Проверь, встало ли оно на ожидаемое место.
  4. В демо 3 поменяй context на [0.15, 0.25, 0.85] (смысл сместился к «твёрдому, неживому»). Какое слово модель допишет теперь? Объясни, почему изменился ответ.

Лучший способ почувствовать эмбеддинги — не прочитать про них, а самому подвигать координаты и увидеть, как меняется порядок соседей.

Итоги

Сегодня ты не просто прочитал про эмбеддинги — ты запустил код, который превратил смысл в числа и посчитал близость.

  • Эмбеддинг — это координаты слова в пространстве смыслов. Похожие по смыслу слова имеют похожие координаты и лежат на карте рядом.
  • «Похоже» для компьютера = «близко». Близость считают обычной формулой расстояния (та самая теорема Пифагора), и в коде это короткий цикл с корнем в конце.
  • Чем меньше расстояние, тем больше сходство. Поиск похожего — это поиск ближайших точек, как «похожие треки» в музыке.
  • Наши сквозные примеры сошлись: «собака» оказалась ближе к «кошке», чем «дом», а фраза «Кошка пьёт ...» дописалась до «молоко» — потому что точка «молоко» ближе всего к смыслу фразы.
  • Код не понимает слов — он считает числа. Вся «понятливость» спрятана в том, как заранее подобраны координаты.

Теперь у тебя есть мощная картинка: смысл — это точка, сходство — это расстояние, а предсказание слова — поиск ближайшей точки. Но мы пока сами, руками, задавали точку смысла фразы context. Как настоящая нейросеть сама вычисляет её, глядя на все слова сразу и решая, на какие из них смотреть внимательнее? Это и есть главный секрет ChatGPT — механизм внимания внутри трансформера. К нему мы и направимся в следующих разделах курса.

Проверьте себя
1. Что в коде демо означает эмбеддинг слова?
AПорядковый номер слова в словаре
BНабор чисел-координат, задающих точку слова в пространстве смыслов
CКоличество букв в слове
DПароль, которым шифруется слово
2. В выводе «кошка ↔ собака: 0.15» и «кошка ↔ дом: 1.08» — какой вывод верен?
AДом ближе к кошке, потому что у него число больше
BСобака ближе к кошке, потому что у неё расстояние меньше
CОба одинаково близки
DЧисла ничего не говорят о сходстве
3. Что на самом деле делает функция dist в наших демо?
AПонимает смысл слов и решает, похожи ли они
BБерёт разницу по каждой координате, копит квадраты и извлекает корень — считает расстояние
CИщет слово в интернете
DПереводит слово на английский
4. В демо 2 сортировка по близости к «кошке» выстроила: котёнок, собака, дом, молоко. Почему котёнок оказался первым?
AПотому что слово «котёнок» короче остальных
BПотому что его координаты ближе всего к координатам «кошки»
CПотому что он стоит первым в алфавите
DПотому что код специально любит котят
5. В демо 3 фраза «Кошка пьёт ...» дописалась до «молоко». Как код это выбрал?
AУгадал случайно из четырёх слов
BНашёл слово, чья точка ближе всего к точке смысла фразы (context)
CВыбрал самое короткое слово
DСпросил у настоящего ChatGPT
6. Чем эмбеддинг отличается от номера токена?
AЭто одно и то же, просто разные названия
BНомер токена — адрес в словаре без смысла, а эмбеддинг — координаты, в которых сходство закодировано близостью точек
CЭмбеддинг — это номер, а токен — это буква
DНомер токена всегда больше, чем эмбеддинг