Предсказване на следващия токен
Накратко: Езиковият модел пише текст, като смята вероятностите на следващия токен и избира един; температурата и top-k решават колко случаен е изборът.
Проблем
Едно начало, три отговора
Ботът на поддръжката трябва да дописва отговори. Колега пуска един и същ начален текст, „поръчката“, три пъти, с различно семе на генератора. Ето трите отговора:
table = count_next(TEXT)for seed in [2026, 34567, 12345]: print(f"семе {seed}:", generate(table, "поръчката", 1, 100, Rng(seed)))семе 2026: поръчката ще пристигне до три днисеме 34567: поръчката пристигна в офиса на куриерсеме 12345: поръчката ще ви се връщат по картата ви утреПървият звучи разумно. Вторият е почти изречение. Третият е безсмислица, макар че всяка две съседни думи са от текста на магазина. И трите са от една и съща програма, а програмата не търси отговор никъде: тя пише дума по дума.
Откъде знае коя дума да сложи? И защо всеки път е различно?
Идеята
Вероятността на следващата дума
Езиковият модел получава текст и дава вероятност за всеки възможен следващ токен. Нищо повече: не знае какво е поръчка, знае само какво обикновено идва след какво.
Текстът се пише с цикъл:
- Моделът дава вероятностите на следващия токен.
- Един токен се избира според тях.
- Добавя се към текста и всичко се повтаря, докато дойде краят или лимитът.
Изборът е отделна стъпка, а не част от модела. Затова един и същ модел може да дава различни отговори.
Моделът в урока е най-малкият възможен: таблица колко пъти коя дума е идвала точно след коя. Гледа само последната дума и брои цели думи, а не токени. Истинските модели са невронни мрежи, които гледат целия текст, но дават същото: вероятностите на следващия токен.
Виж го
Таблицата: коя дума след коя
Текстът на магазина е същият като в урока за токените. За всяко изречение броим всяка двойка съседни думи. Точката „.“ отбелязва края на изречението, за да знае моделът кога да спре.
# колко пъти коя дума идва точно след коя; „.“ е краят на изречениетоdef count_next(text): table = {} for line in text: w = (line + " .").split(" ") for word, after in zip(w, w[1:]): counts = table.setdefault(word, {}) counts[after] = counts.get(after, 0) + 1 return tableВероятността на следващата дума е броят ѝ, делен на сбора. Засега гледай само t = 1 и „всички думи“: тогава теглото е самият брой. Температурата t и k идват по-долу.
# k-те най-чести думи, тегло брой^(1/t) / сбораdef next_words(counts, t, k): top = sorted(counts, key=counts.get, reverse=True)[:k] total = sum(counts[w] ** (1 / t) for w in top) return [(w, counts[w] ** (1 / t) / total) for w in top]table = count_next(TEXT)counts = table["ще"]print('след "ще":')for word, p in next_words(counts, 1, 100): print(f"{word:<11}{counts[word]:2} {p * 100:5.1f}%")след "ще":пристигне 4 40.0%бъде 2 20.0%дойде 1 10.0%ви 1 10.0%бъдат 1 10.0%получите 1 10.0%След „ще“ в текста идват шест различни думи, общо 10 пъти. „пристигне“ е 4 от тях: 40,0%. Ето това е езиковият модел: за „ще“ той казва „пристигне“ с 40,0%, „бъде“ с 20,0% и всяка от другите с 10,0%.
Виж го
Изборът: число от 0 до 1
Как се избира дума с вероятност 40,0%? Генераторът със семе дава число r от 0 до 1. Лентите на думите застават една до друга: „пристигне“ от 0 до 0,4, „бъде“ от 0,4 до 0,6 и така нататък. Думата, в чиято лента пада r, е избраната.
class Rng: def __init__(self, seed): self.state = seed
# следващото число между 0 и 1; едно семе дава едни и същи числа def next(self): self.state = self.state * 48271 % 2147483647 return self.state / 2147483647# число от 0 до 1 пада в една от лентитеdef pick(choices, rng): r = rng.next() total = 0.0 for word, p in choices: total += p if r < total: return word return choices[-1][0]Генерирането повтаря това дума по дума от началото, докато дойде „.“ или 12 думи:
# продължи текста дума по дума до „.“ или до 12 думиdef generate(table, start, t, k, rng): text = start.split(" ") word = text[-1] while len(text) < 12: word = pick(next_words(table[word], t, k), rng) if word == ".": break text.append(word) return " ".join(text)Ако винаги вземаш най-вероятната дума (k = 1), генераторът няма какво да избира и отговорът е един и същ при всяко семе:
table = count_next(TEXT)for seed in [2026, 34567, 12345]: print(f"семе {seed}:", generate(table, "поръчката", 1, 1, Rng(seed)))семе 2026: поръчката ще пристигне до два днисеме 34567: поръчката ще пристигне до два днисеме 12345: поръчката ще пристигне до два дниСъс случаен избор отговорът зависи от семето. Едно и също семе обаче дава едни и същи числа, значи и един и същ отговор:
table = count_next(TEXT)for seed in [2026, 2026, 34567]: print(f"семе {seed}:", generate(table, "поръчката", 1, 100, Rng(seed)))семе 2026: поръчката ще пристигне до три днисеме 2026: поръчката ще пристигне до три днисеме 34567: поръчката пристигна в офиса на куриерЗатова в началото трите отговора бяха различни: всяко семе дава други числа, а оттам други думи.
Пробвай
Предвиди изхода
След „доставката“ в текста има пет продължения, едното е краят „.“. Какво печата програмата с k = 1?
table = count_next(TEXT)for word, p in next_words(table["доставката"], 1, 1): print(f"{word} {p * 100:.1f}%")k = 1 оставя само най-честата дума, „до“. Сборът на теглата вече е само нейното тегло, а теглото, делено на себе си, е 1: 100,0%. 33,3% е вероятността ѝ сред всички думи, но тях k = 1 ги е изрязало.
Идеята
Температура и top-k
Изборът има две ръчки, които имат и истинските модели:
- температура
t: теглото е брой^(1/t). Приtпод 1 честите думи стават още по-вероятни и отговорът е по-предвидим. Приtнад 1 вероятностите се изравняват и редките думи излизат по-често. - top-k: остават само
k-те най-чести думи, а другите не могат да се изберат. Сборът се смята само по тях, затова пак е 100%.
table = count_next(TEXT)counts = table["ще"]temps = [0.5, 1.0, 2.0]rows = [next_words(counts, t, 100) for t in temps]print(" " * 11 + "".join(f"t={t:g}".rjust(7) for t in temps))for i in range(len(counts)): print(f"{rows[0][i][0]:<11}" + "".join(f"{row[i][1] * 100:6.1f}%" for row in rows)) t=0.5 t=1 t=2пристигне 66.7% 40.0% 27.0%бъде 16.7% 20.0% 19.1%дойде 4.2% 10.0% 13.5%ви 4.2% 10.0% 13.5%бъдат 4.2% 10.0% 13.5%получите 4.2% 10.0% 13.5%При t = 0,5 „пристигне“ е 66,7%, а при t = 2 само 27,0%. Думите, видени веднъж, са 4,2%, 10,0% и 13,5%: колкото по-висока е температурата, толкова по-често моделът ги избира.
Ето лентите след „ще“ при трите температури, после с k = 3 и изборът с едно число от генератора:
# k-те най-чести думи, тегло брой^(1/t) / сбораdef next_words(counts, t, k): top = sorted(counts, key=counts.get, reverse=True)[:k] total = sum(counts[w] ** (1 / t) for w in top) return [(w, counts[w] ** (1 / t) / total) for w in top]
# число от 0 до 1 пада в една от лентитеdef pick(choices, rng): r = rng.next() total = 0.0 for word, p in choices: total += p if r < total: return word return choices[-1][0]Стъпка по стъпка, с думи
- След „ще“ в текста идват 6 думи. Лентата е броят, делен на всички 10: „пристигне“ е 4 от 10, 40,0%.
- t = 0,5: теглото е брой². Честата дума става още по-вероятна: „пристигне“ 66,7%.
- t = 2: теглото е √брой. Лентите се изравняват: „пристигне“ 27,0%, „получите“ 13,5%.
- t = 1, k = 3: остават само трите най-чести и сборът им пак е 100%: 57,1%, 28,6%, 14,3%.
- Трите ленти застават една до друга от 0 до 1. Генераторът със семе 31337 дава 0,7044.
- 0,7044 пада в лентата на „бъде“ (от 57,1% до 85,7%): следващата дума е „бъде“, не най-честата.
table = count_next(TEXT)choices = next_words(table["ще"], 1, 3)print("k=3:")for word, p in choices: print(f"{word:<11}{p * 100:5.1f}%")rng = Rng(31337)copy = Rng(31337)print(f"число от генератора: {copy.next():.4f}")print(f"избрана: {pick(choices, rng)}")k=3:пристигне 57.1%бъде 28.6%дойде 14.3%число от генератора: 0.7044избрана: бъдеС k = 3 остават „пристигне“, „бъде“ и „дойде“, а вероятностите им са 4, 2 и 1 от 7. Генераторът със семе 31337 дава 0,7044, а то пада в лентата на „бъде“, от 57,1% до 85,7%. Изборът е по вероятностите, но не е винаги най-вероятната дума.
Пробвай
Попълни
Кое число стои на мястото на ___ в теглото на думата?
total = sum(counts[w] ** (1 / ) for w in top)Температурата t: теглото е брой^(1/t). При t = 1 това е самият брой, при t = 0,5 е брой², а при t = 2 е корен от броя.
Пробвай
Намери грешката
Колега иска top-k с трите най-вероятни думи, но моделът му пише само редки думи. На кой ред е грешката?
Избери реда с грешката.
Подреждането е от най-рядката към най-честата, затова първите k са трите най-редки думи. Трябва да е обратното: reverse=True.
Идеята
Кога боли
- Вероятното не е вярно. Моделът пише „до три дни“ толкова лесно, колкото „до два дни“: и двете са в текста. Той не знае кога пристига точно тази поръчка. Гладкият текст звучи уверено и когато е грешен. За това е урокът за границите на модела.
- Всяка двойка е вярна, изречението не е. „поръчката ще ви се връщат по картата ви утре“ е от съседни думи, които съществуват. Нашият модел гледа само една дума назад. Истинските гледат целия текст и пишат много по-добре, но пак могат да слепят нещо гладко и безсмислено.
- Висока температура, повече грешки. Редките продължения излизат по-често. За отговори на клиенти, където трябва точност, температурата е ниска. За идеи и варианти може да е по-висока.
- Едно семе не е обещание. В урока едно семе дава един отговор. При истински модел през API една и съща заявка понякога дава различен отговор и при едни и същи настройки, затова не разчитай на точно същия текст, а проверявай отговора.
По-точно, за любопитните
Softmax и температурата. Истинският модел е невронна мрежа: за всеки токен от речника тя дава число (logit), а softmax ги прави вероятности. Температурата дели числата на t преди softmax. Теглото брой^(1/t) от урока е точно това: softmax от log(брой) / t.
top-p. Освен top-k има и top-p: остават най-честите токени, докато сборът им стигне p (например 0,9). Така при уверен модел остават малко токени, а при неуверен повече.
Токени, не думи. Истинският модел избира токени от речника си, а думата се получава от няколко избора поред. Температурата, top-k и семето се задават в заявката към модела (урокът за извикването).
Колко назад гледа. Нашият модел помни една дума. Истинският вижда целия разговор до лимита си, и това е прозорецът на контекста.
В Python. sorted(..., reverse=True) пази реда на равните елементи, като Java, C# и JavaScript. Затова думите с еднакъв брой са в реда на първата си поява на всичките четири езика.
Упражнение
Колко вероятно е изречението
Реши в редактора си.
Вероятността на цяло изречение е произведението на вероятностите на всяка следваща дума, включително „.“ накрая. Напиши функция, която я смята с таблицата от урока, и я отпечатай в проценти за три изречения. Ако двойка думи никога не е виждана, вероятността ѝ е 0.
пратката ще пристигне до два дни: 2.86%пратката ще пристигне до 14 дни: 1.43%пратката пристигна утре: 0.00%Първото изречение е 2,86%, второто 1,43%, макар че и двете не са в текста буква по буква: всички двойки в тях са виждани. Третото е 0,00%: „пратката“ и „пристигна“ са в текста, но никога една след друга. Така моделът казва колко „естествен“ му изглежда текстът.
Още задачи
- Най-вероятното изречение. Започни от „пратката“ и всеки път вземи най-вероятната дума. Каква е вероятността на изречението, което се получава?
- Две думи назад. Брой коя дума идва след двойка думи, не след една. Как се променят отговорите от началото на урока?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: dataclass, match и шаблони · Какво е машинно учене · Цифрите от касовата бележка · Текстът на парчета: токени
- Продължава в: Ембединги: смисълът като вектори · Прозорецът на контекста · Границите на модела · Извикване на модел през API
- Задачи по темата: Как работи езиков модел