Перейти к содержимому
TelegramGitHub

Machines that Think, глава 2: Теперь мы говорим

Главная мысль: Машина не понимает язык сразу. Она собирает его по частям: сначала считает слова, потом режет текст на токены, потом превращает токены в векторы. Так получается рабочая замена смысла. Но язык всё равно остаётся одной из самых трудных задач для ИИ.

Путь текста в машине: правила, подсчёт n-gram, токенизация, one-hot, статические embeddings, контекстные embeddings

Люди давно считают речь признаком ума. Кто говорит ясно, того считают умным.

  • Язык помогает думать: название выделяет вещь, грамматика связывает идеи.
  • Язык влияет на восприятие. В русском есть «голубой» и «синий», и русские различают эти оттенки быстрее носителей английского.

Когда машина пишет гладкий текст, мы включаем привычку: «говорит хорошо — значит, понимает». Это anthropomorphism.

Но текст модели не пустой. Модель отражает то, как миллионы людей писали и спорили. Автор называет LLM скорее зеркалом, чем мыслителем. Для многих задач этого хватает: резюме, черновик, объяснение.

Что есть у человекаЧто есть у LLM
Пространственное мышлениеЧастично, через текст
Планирование на длинный срокСлабо
Работа с физическим миромНет
Устойчивые причины и следствияЧастично
ЯзыкОчень сильно

Язык делает мысль видимой, но не равен ей. Книга всё равно про языковые модели: через язык видно много умений сразу.

Язык стал данными, когда его записали и оцифровали. Так появился NLP (natural language processing).

Первая идея: лингвисты описали грамматику, значит, компьютер может её применять.

  • Перевод: найти подлежащее и сказуемое, переставить по правилам другого языка. Идиома ломала всё.
  • Правила не масштабируются. Новая область (право, медицина) = новые правила и исключения.

ELIZA (1960-е) играла роль психотерапевта. Пользователь пишет «я устал от работы». ELIZA видит шаблон «я X» и отвечает «почему вы устали от работы?». Пару реплик это выглядит умно. Потом шаблоны кончаются.

Когда собрали большие корпусы текста, подход сменился. Не «как должно быть», а «как люди реально пишут». Вероятностные модели лучше справлялись с шумом и улучшались с ростом данных.

Человек читает «Солнце встаёт на» и ждёт «востоке». Это опыт. Компьютер получает то же через подсчёт.

Language model даёт вероятность последовательности слов. Посчитать все предложения нельзя: их слишком много. Поэтому используют Markov property: следующее слово зависит только от нескольких предыдущих.

n-gram — это n слов подряд.

nНазваниеПример из «загрузи данные в хранилище»
1unigram«данные»
2bigram«загрузи данные»
3trigram«загрузи данные в»

Вероятность считается делением: сколько раз встретилось «в хранилище», разделить на сколько раз встретилось «в».

Модель «говорит» так: берёт слово, выбирает самое вероятное следующее, повторяет. Грамматики внутри нет, только статистика. Но текст выглядит похоже на язык.

Bigram-модель на четырёх фразах и сходство игрушечных векторов. Только стандартная библиотека.

from collections import Counter
import math
corpus = [
"загрузи данные в хранилище",
"загрузи данные в озеро",
"проверь данные в хранилище",
"загрузи логи в хранилище",
]
pairs = Counter()
starts = Counter()
for line in corpus:
words = line.split()
for a, b in zip(words, words[1:]):
pairs[(a, b)] += 1
starts[a] += 1
def p(next_word, prev_word):
return pairs[(prev_word, next_word)] / starts[prev_word]
print("P(данные | загрузи) =", round(p("данные", "загрузи"), 2))
print("P(хранилище | в) =", round(p("хранилище", "в"), 2))
print("P(витрину | в) =", round(p("витрину", "в"), 2))
def cos(u, v):
dot = sum(a * b for a, b in zip(u, v))
return dot / (math.sqrt(sum(a * a for a in u)) * math.sqrt(sum(b * b for b in v)))
vec = {
"postgres": [0.9, 0.8, 0.1],
"snowflake": [0.8, 0.9, 0.2],
"pizza": [0.1, 0.0, 0.9],
}
print("postgres ~ snowflake:", round(cos(vec["postgres"], vec["snowflake"]), 2))
print("postgres ~ pizza:", round(cos(vec["postgres"], vec["pizza"]), 2))

Результат:

P(данные | загрузи) = 0.67
P(хранилище | в) = 0.75
P(витрину | в) = 0.0
postgres ~ snowflake: 0.99
postgres ~ pizza: 0.16

«в витрину» — нормальная фраза, но в корпусе её нет. Модель даёт 0. Это и есть проблема sparsity. Вторая часть примера относится к разделу про embeddings ниже.

Язык огромный. Даже в миллиардах слов большинство сочетаний не встречается ни разу. Модель считает их невозможными.

Решение: smoothing. Не ставить ноль, а дать маленькую вероятность.

МетодИдея
LaplaceДобавить немного к каждому счётчику
BackoffНет trigram — взять bigram, нет bigram — взять слово
InterpolationСмешать оценки разной длины

Чем больше n, тем больше контекста. Но число сочетаний растёт очень быстро, данных не хватает, sparsity сильнее. На практике компромиссом стал trigram.

Для компьютера текст — это байты. Сначала его надо разрезать на единицы. Это tokenization.

Резать по пробелам просто, но плохо. Есть падежи, сокращения, составные слова, языки без пробелов (китайский, японский).

УровеньПлюсМинус
СловоПростоСловарь на миллионы форм. Новое слово неизвестно
СимволСловарь из сотен символовПоследовательности очень длинные. Модель сама учит, что буквы складываются в слово
SubwordКомпромисс. Новые слова собираются из частейНужен алгоритм

Subword — стандарт сейчас. Слово «перезагрузка» можно разбить на «пере» + «загруз» + «ка», даже если целиком модель его не видела.

BPE (Byte Pair Encoding) пришёл из сжатия данных.

  1. Начинаем с отдельных символов.
  2. Находим самую частую пару соседних символов.
  3. Склеиваем её в один токен.
  4. Повторяем, пока словарь не станет нужного размера.

Частые куски становятся одним токеном. Редкие слова остаются разбитыми на части.

ТокенайзерЧем отличается
BPEСклеивает самую частую пару
WordPieceСклеивает пару, которая лучше описывает весь корпус, а не самую частую
SentencePieceРаботает с сырым потоком текста. Пробел — обычный символ. Подходит для любого языка

Раньше все незнакомые слова превращались в один токен «неизвестно». Subword-токенизация это убрала: новое слово разбивается на знакомые куски, и у модели есть хоть какая-то информация.

  • Каждый токен занимает место в context window. Мелкие токены дают детали, крупные экономят место.
  • Сам по себе токен — изолированный символ. Модель не знает, что «кот» и «кошка» похожи.

Токену дают номер: «кот» = 42, «ложка» = 173. Но номер ничего не значит. 42 не ближе к 43, чем к 173.

Вектор длиной в словарь. Одна единица, остальные нули. Две проблемы:

  • Нет сходства. «Кот» одинаково далёк и от «собаки», и от «ложки».
  • Огромные векторы почти из одних нулей.

Embedding — короткий вектор чисел, где каждое число что-то значит. Его учат так, чтобы слова из похожих контекстов получили похожие векторы.

Основа — distributional hypothesis (Фёрт): слово узнают по его соседям. «Кот» и «собака» стоят рядом с «спит» и «ест». «Ложка» — рядом с «тарелка» и «мешать».

МетодГодИдея
word2vec (CBOW, Skip-gram)2013, GoogleПредсказать слово по соседям или соседей по слову. Модель очень простая
GloVeStanfordТаблица: как часто каждое слово стоит рядом с каждым. Глобальная статистика по всему корпусу
ELMoПозжеВектор зависит от контекста. Читает фразу в обе стороны
  • Похожие слова лежат рядом: животные в одной области, инструменты в другой.
  • Расстояние = мера сходства. В примере выше postgres и snowflake дают 0.99, а postgres и pizza — 0.16.
  • Направления тоже значат. «король − мужчина + женщина» ≈ «королева». Переход «идти → шёл» похож на «играть → играл».
  • Реальные embeddings имеют сотни измерений. Отдельное измерение обычно ничего понятного не значит.

У статического embedding слово «ключ» всегда в одной точке. Но «ключ от двери» и «горный ключ» — разные смыслы. Contextual embeddings считают новый вектор каждый раз по окружающим словам. Это убирает главную слабость статических векторов.

Что дали embeddings: язык стал геометрией, текст стал числами для ML (как картинки и звук), появился transfer learning. Минусы: дорого считать и трудно интерпретировать.

ТрудностьСуть
Бесконечность формПочти все возможные фразы модель не видела. «Бесцветные зелёные идеи яростно спят» (Хомский): грамматика верна, смысла нет
Дальние связиМестоимение ссылается на слово из прошлого абзаца. Короткое окно это теряет
НедосказанностьБольшая часть смысла не сказана. Нужен контекст и знание мира
Long tailЗакон Ципфа: частота слова обратно пропорциональна его рангу. Редкие слова (термины, имена) важны, но данных о них мало
Разные языкиПорядок слов, морфология, тон, письменность. Решение для английского не подходит всем
ОценкаНет одной метрики. Текст бывает гладким, но ложным. Перевод, резюме и диалог требуют разного качества
ТерминЗначение
NLPОбласть ИИ про работу с человеческим языком
Language modelМодель, которая даёт вероятность следующего слова или фразы
Markov propertyДопущение: будущее зависит только от короткого куска прошлого
n-gramПоследовательность из n слов подряд
SparsityБольшинство сочетаний не встречается в данных
SmoothingДать маленькую вероятность невиданным сочетаниям вместо нуля
tokenЕдиница текста, с которой работает модель
BPEТокенизация через склейку самых частых пар символов
One-hot encodingВектор длиной в словарь с одной единицей
embeddingПлотный вектор, где похожие по употреблению слова лежат рядом
Contextual embeddingВектор слова, который зависит от окружающих слов
Zipf’s lawМало частых слов и очень много редких
  • Платите за токены, а не за слова. Цена API и лимит context window считаются в токенах. Русский текст обычно даёт больше токенов, чем английский того же смысла. Замерьте токенайзером модели на своих данных.
  • Названия таблиц и колонок тоже токены. Длинная DDL-схема в prompt съедает окно. Давайте модели только нужные таблицы.
  • Редкие термины модель знает хуже. Внутренние сокращения компании, коды продуктов — это long tail. Объясните их в prompt или в документах для RAG.
  • RAG стоит на embeddings. Поиск по близости векторов находит похожий текст, а не верный ответ. Проверяйте, что попало в контекст.
  • Сходство не равно смыслу. У «рост выручки» и «падение выручки» могут быть близкие векторы. Для точных фильтров используйте SQL, а не vector search.
  • Гладкий текст не значит правильный. Оценивайте выход модели по нескольким критериям: факты, формат, полнота. Одной метрики мало.
  1. Почему n-gram модель даёт вероятность 0 нормальной фразе? Как это исправляет smoothing?
  2. Чем subword-токенизация лучше токенизации по словам и по символам?
  3. Почему номер токена и one-hot вектор не дают модели понятия сходства, а embedding даёт?
  4. Зачем нужны contextual embeddings, если уже есть word2vec?

Предыдущая: Старые вопросы, новые машины. Следующая: Как устроены нейросети.