Към съдържанието
wannacode

Границите на модела

Накратко: Езиковият модел халюцинира: уверено пише правдоподобни, но неверни неща, защото предсказва вероятен текст, а не проверява истината; затова отговорът се сверява с източника.

Проблем

Тридесет дни, които ги няма

Асистентът на поддръжката отговаря на клиент: „Можете да върнете якето до 30 дни от получаването.“ Отговорът е учтив, гладък и уверен. Политиката на магазина е 14 дни.

На двадесетия ден клиентът праща якето обратно. Складът го връща, а клиентът пише отзив с една звезда и цитира асистента.

Никой не е програмирал „30 дни“. Откъде се взе числото и как да се пазиш от следващото?

Идеята

Моделът пише вероятен текст, не проверени факти

Езиковият модел е научен да продължава текст с вероятна следваща дума. Той не пази таблица с факти и не проверява отговора си. Ако в текста, на който е учен, „до“ често е следвано от число, той ще сложи число. Кое число, решава вероятността, а не политиката на магазина.

Гладък и уверен отговор, който е грешен или измислен, се казва халюцинация (hallucination). Тя не е бъг, който ще се поправи с една кръпка. Тя идва от самия начин, по който моделът пише: за него вярното и грешното изречение са просто по-вероятен и по-малко вероятен текст.

Виж го

Малък модел, който обещава грешно

Моделът от урока за следващия токен, в най-малкия си вид: за всяка дума пази думите, които са я следвали в текста. Повторенията остават, затова думата, която се среща по-често, се тегли по-често.

// за всяка дума: думите, които са я следвали в текста
static Map<String, List<String>> train(String[] corpus) {
Map<String, List<String>> follows = new HashMap<>();
for (String sentence : corpus) {
String[] words = (sentence + " .").split(" ");
for (int i = 0; i + 1 < words.length; i++) {
follows.computeIfAbsent(words[i], k -> new ArrayList<>()).add(words[i + 1]);
}
}
return follows;
}

Текстът са десет изречения от поддръжката на магазина:

String[] corpus = {
"връщането е до 14 дни от получаването",
"доставката до софия е до 2 дни",
"доставката до пловдив е до 2 дни",
"доставката до варна е до 3 дни",
"парите се връщат до 5 работни дни",
"гаранцията е 24 месеца",
"плащането с карта е сигурно",
"куриерът се обажда преди доставката",
"поръчка над 50 евро е с безплатна доставка",
"отказаната пратка се връща до 7 дни",
};

Най-вероятната дума всеки път:

// най-честата (при равенство първата в текста)
static String mostCommon(List<String> options) {
String best = options.get(0);
for (String o : options) {
int count = Collections.frequency(options, o);
if (count > Collections.frequency(options, best)) {
best = o;
}
}
return best;
}
static String greedy(Map<String, List<String>> follows,
String start) {
List<String> words =
new ArrayList<>(List.of(start.split(" ")));
while (words.size() < 12) {
String last = words.get(words.size() - 1);
String word = mostCommon(follows.get(last));
if (word.equals(".")) break;
words.add(word);
}
return String.join(" ", words);
}

И с избор по вероятност, с генератор със семе, за да е изходът еднакъв при всяко пускане:

static String sample(Map<String, List<String>> follows, String start, Rng rng) {
List<String> words = new ArrayList<>(List.of(start.split(" ")));
while (words.size() < 12) {
List<String> options = follows.get(words.get(words.size() - 1));
String word = options.get((int) (rng.next() * options.size()));
if (word.equals(".")) break;
words.add(word);
}
return String.join(" ", words);
}
Map<String, List<String>> follows = train(corpus);
System.out.println("най-вероятната дума всеки път:");
System.out.println(" " + greedy(follows, "връщането"));
System.out.println("шест пускания, думата се тегли по вероятност:");
Rng rng = new Rng(3876);
List<String> text = List.of(corpus);
for (int i = 1; i <= 6; i++) {
String answer = sample(follows, "връщането", rng);
String mark = text.contains(answer) ? "има го в текста" : "няма го в текста";
System.out.println(" " + i + ". " + answer + " (" + mark + ")");
}
Изход
най-вероятната дума всеки път:
връщането е до 2 дни
шест пускания, думата се тегли по вероятност:
1. връщането е 24 месеца (няма го в текста)
2. връщането е до 14 дни от получаването (има го в текста)
3. връщането е сигурно (няма го в текста)
4. връщането е до 5 работни дни (няма го в текста)
5. връщането е до 2 дни (няма го в текста)
6. връщането е до варна е 24 месеца (няма го в текста)

Най-вероятният отговор е „връщането е до 2 дни“, а в текста пише 14. От шестте изтеглени отговора само един е изречение от текста. Останалите са гладки и грешни: „връщането е 24 месеца“ е взето от гаранцията, а „до 5 работни дни“ от връщането на парите.

Виж го

Откъде дойде числото

// най-честата (при равенство първата в текста)
static String mostCommon(List<String> options) {
String best = options.get(0);
for (String o : options) {
int count = Collections.frequency(options, o);
if (count > Collections.frequency(options, best)) {
best = o;
}
}
return best;
}
static String greedy(Map<String, List<String>> follows,
String start) {
List<String> words =
new ArrayList<>(List.of(start.split(" ")));
while (words.size() < 12) {
String last = words.get(words.size() - 1);
String word = mostCommon(follows.get(last));
if (word.equals(".")) break;
words.add(word);
}
return String.join(" ", words);
}
Стъпка по стъпка, с думи
  1. Моделът продължава „връщането“, всеки път с най-честата следваща дума.
  2. „е до“ е в 4 изречения, едното от тях е за връщането. Дотук е вярно.
  3. След „до“ най-често идва „2“: от двете изречения за доставката.
  4. После точка. Гладко изречение, което го няма в текста: „връщането е до 2 дни“.
  5. Проверката взима числата от отговора и от политиката за връщане.
  6. 2 го няма в политиката. Там пише 14 дни.
  7. Всяка двойка думи е от текста на магазина. Цялото изречение е грешно, а звучи толкова уверено, колкото вярното.

Всяка двойка думи в отговора е вярна някъде в текста. Цялото изречение не е вярно никъде. Големите модели гледат много повече от една дума назад и грешат много по-рядко, но принципът е същият: те пишат вероятно, а не проверено.

Пробвай

Предвиди изхода

Моделът е научен на три изречения. Какво ще напише с най-вероятната дума всеки път, ако започне от думата за доставката?

String[] corpus = {
"доставката до софия е 2 дни",
"доставката до варна е 3 дни",
"смяната на размер е 3 дни",
};
System.out.println(greedy(train(corpus), "доставката"));
Отговор

Идеята

Същият въпрос, друг отговор

Когато думата се тегли по вероятност, всяко пускане може да даде друг отговор. В програмата шестте пускания дадоха пет различни изречения. Това е недетерминизъм: един и същ вход, различен изход. Тестовете, логовете и оплакванията на клиентите стават по-трудни, защото „при мен работи“ вече не значи нищо.

Температура 0 (най-вероятната дума всеки път) прави отговора повторим, но не и верен. Програмата даде „до 2 дни“ при всяко пускане. При истинските доставчици дори температура 0 не обещава напълно еднакъв отговор: сметките на сървъра не вървят винаги в същия ред.

Идеята

Датата на знанията

Моделът знае само текста, на който е учен. Той е събран до определена дата, датата на знанията (knowledge cutoff). Каквото е станало след нея, моделът не го знае: новата политика за връщане, новият продукт, цената от днес.

И не знае нищо, което никога не е било в текста му: статусът на поръчка 1042 е в базата на магазина, не в модела. По подразбиране моделът не казва „не знам“. Той пише вероятен отговор и тук се ражда „пристига утре“, което никой не е проверил.

Затова фактите влизат в заявката: политиката като текст в промпта, статусът на поръчката от програмата. Как моделът сам поисква статуса, е в урока за инструментите.

Идеята

Провери отговора срещу източника

Щом отговорът трябва да стъпва на документ, програмата може да провери поне част от него. Най-грубата проверка: всяко число в отговора трябва да го има в източника.

// всяко число в отговора трябва да го има в източника
static String check(String answer, String source) {
List<String> known = numbers(source);
List<String> claimed = numbers(answer);
if (claimed.isEmpty()) {
return "? няма число за проверка";
}
for (String n : claimed) {
if (!known.contains(n)) {
return "✗ " + n + " го няма в източника";
}
}
return "✓ числата са в източника";
}
static List<String> numbers(String text) {
List<String> found = new ArrayList<>();
for (String word : text.split(" ")) {
String w = word.replace(".", "").replace(",", "");
boolean digits = !w.isEmpty()
&& w.chars().allMatch(Character::isDigit);
if (digits) {
found.add(w);
}
}
return found;
}

Шестте отговора от програмата горе срещу политиката за връщане:

String source = "Връщането е до 14 дни от получаването. Парите се връщат до 5 работни дни.";
String[] answers = {
"връщането е 24 месеца",
"връщането е до 14 дни от получаването",
"връщането е сигурно",
"връщането е до 5 работни дни",
"връщането е до 2 дни",
"връщането е до варна е 24 месеца",
};
for (String a : answers) {
System.out.println(a + " → " + check(a, source));
}
Изход
връщането е 24 месеца → ✗ 24 го няма в източника
връщането е до 14 дни от получаването → ✓ числата са в източника
връщането е сигурно → ? няма число за проверка
връщането е до 5 работни дни → ✓ числата са в източника
връщането е до 2 дни → ✗ 2 го няма в източника
връщането е до варна е 24 месеца → ✗ 24 го няма в източника

Проверката хвана три грешни отговора. Но пусна „връщането е до 5 работни дни“: 5 го има в политиката, само че за връщането на парите. И не може да каже нищо за „връщането е сигурно“, защото там няма число. Груба проверка намалява риска, без да го маха. Затова в истински системи проверките са няколко, а където грешката е скъпа, гледа човек.

Пробвай

Намери грешката

Колега опростява проверката. Сега отговорът „връщането е до 4 дни“ минава като верен, макар че в политиката пише 14. На кой ред е грешката?

Избери реда с грешката.

Пробвай

Попълни

Моделът спира изречението, когато най-вероятната следваща дума е краят. Как изглежда краят в текста за обучение?

static String greedy(Map<String, List<String>> follows,
                     String start) {
    List<String> words =
            new ArrayList<>(List.of(start.split(" ")));
    while (words.size() < 12) {
        String last = words.get(words.size() - 1);
        String word = mostCommon(follows.get(last));
        if (word.equals()) break;
        words.add(word);
    }
    return String.join(" ", words);
}

Идеята

Кога езиковият модел е грешният инструмент

  • Има точен отговор, който се смята. Сумата на поръчката, ДДС, остатъкът по сметка. Един ред код не греши, моделът може да сбърка цифра.
  • Данните са живи. Статус на поръчка, наличност, днешна цена. Те са в базата. Моделът може да ги поиска чрез инструмент, но не бива да ги „помни“.
  • Правилото е твърдо. „Връщане до 14 дни“ е if, не промпт. Каквото трябва да е винаги еднакво, се пише в кода.
  • Решението трябва да се обясни и провери после. Отказано плащане или кредит иска причина, която се повтаря. За такива решения има модели като логистичната регресия, а правилата се пишат явно.
  • Грешката струва повече от мълчанието. Медицински, правни, финансови отговори към клиент. Тогава моделът пише чернова, а човек решава.

Къде моделът е силен: чернови, резюмета, превод, подреждане на свободен текст (за кой отдел е писмото), извличане на данни от текст, отговори по документ, които се проверяват. Задачи, в които малката неточност се хваща лесно и не струва скъпо.

По-точно, за любопитните

Защо и големите модели халюцинират. Те са научени на огромен текст, а после още малко да следват инструкции и да казват „не знам“. Това намалява грешките, но не ги маха: моделът пак избира вероятен текст. Особено опасни са редките факти (номер на член от закон, цитат, име на функция в малка библиотека), където в текста за обучение има малко примери.

Какво помага, на слоеве. Фактите в заявката (политиката като текст, модулът за търсене по смисъл и RAG по-нататък в курса прави точно това). Изход по схема и проверка в кода (структуриран изход). Тестов набор с въпроси и правилни отговори, който се пуска при всяка смяна (оценяване). Отговор „не знам, предавам на колега“, когато проверката не мине. И човек там, където грешката е скъпа.

Колко е малък моделът тук. Биграмният модел помни една дума назад и е научен на десет изречения. Голям модел гледа хиляди токени назад и е научен на трилиони. Затова той рядко лепи „връщането е до 2 дни“ от две изречения. Но когато в контекста няма отговор, той пак пише най-вероятното продължение, а не „не знам“.

Измерването на халюцинации. Процентът грешни отговори зависи от задачата и от данните. Числата от чужди тестове не казват нищо за твоя асистент. Мериш ги ти, на твоите въпроси, както в урока за оценяването.

Упражнение

Отговорите за доставката

Реши задачата в редактора си.

Асистентът отговаря за доставката, а източникът е: „Доставката до София е до 2 работни дни. До Варна е до 3 работни дни. Над 50 евро доставката е безплатна.“ Провери пет отговора със същото правило (всяко число в отговора трябва да го има в източника) и отпечатай присъдата за всеки:

  • Доставката до София е до 2 дни.
  • Доставката до Варна е до 4 дни.
  • Доставката е безплатна над 30 евро.
  • Доставката до Пловдив е до 1 ден.
  • Доставката е безплатна за всички поръчки.
Изход
Доставката до София е до 2 дни. → ✓ числата са в източника
Доставката до Варна е до 4 дни. → ✗ 4 го няма в източника
Доставката е безплатна над 30 евро. → ✗ 30 го няма в източника
Доставката до Пловдив е до 1 ден. → ✗ 1 го няма в източника
Доставката е безплатна за всички поръчки. → ? няма число за проверка

Последният отговор е грешен, а проверката не може да го хване: в него няма число.

Още задачи

  • Число на място. Проверявай не само дали числото го има, а и думата преди него: „до 2“ за София. Кои отговори хваща сега?
  • Отказ. Когато проверката не мине, програмата да връща „Ще ви свържа с колега.“ вместо отговора на модела.

Тест

Провери се

1. Какво е халюцинация на езиков модел?
2. Слагаш температура 0 и асистентът дава един и същ отговор всеки път. Значи ли това, че отговорът е верен?
3. Политиката за връщане се промени вчера. Защо моделът още казва старата?
4. Проверката „числата в отговора ги има ли в източника“ пусна „връщането е до 5 работни дни“. Защо?
5. За коя задача езиковият модел е грешният инструмент?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·