Нарязване на документи (chunking)
Накратко: Нарязването (chunking) дели документа на парчета по думи или изречения, със застъпване, така че всяко парче да има свой вектор, а отговорът да не се среже.
Проблем
Цялата страница в един вектор
Магазинът има политика за връщане. Тя е една страница с шест изречения:
// политиката за връщане: шест изреченияstatic final String[] POLICY = { "можете да върнете стоката до 14 дни от доставката", "стоката трябва да е в оригиналната опаковка", "куриер ще я вземе от адреса ви безплатно", "парите се връщат по картата до пет дни", "стоки с ваш надпис не се връщат", "за повреда пишете ни до два дни",};
// въпросът на клиентаstatic final String QUESTION = "кога ще ми върнат парите по картата";Клиентът пита „кога ще ми върнат парите по картата“. Отговорът е четвъртото изречение. Ако цялата страница е един текст в индекса, тя е един вектор:
System.out.println("думи: " + words.length);String doc = String.join(" ", POLICY);System.out.printf("целият документ: %.3f%n", cosine(v, space.ofText(doc)));System.out.printf("изречението с отговора: %.3f%n", cosine(v, space.ofText(POLICY[3])));думи: 46целият документ: 0.710изречението с отговора: 0.874Цялата страница е 0,710, а изречението с отговора само е 0,874. Векторът на страницата е смес от шест теми: срок, опаковка, куриер, пари, изключения, повреди. Колкото по-дълга е страницата, толкова повече се размива. В база с много документи такава страница губи от кратки текстове, които изобщо не отговарят на въпроса.
А ако все пак бъде намерена, в промпта отиват всичките 46 думи заради 8. Истинските политики са хиляди думи, и три такива страници пълнят прозореца на контекста и сметката.
Идеята
Парчета
Документът се реже на парчета (chunks), преди да влезе в индекса. Всяко парче е отделен запис: своят текст, своят вектор и кой документ е. Търсенето от предишния урок намира парчето, а не цялата страница.
Най-простото рязане е по брой думи: първите 10 думи, следващите 10 и така до края. За векторите в урока ползваме същия модел като в урока за векторите, с две разлики: той се учи и от текста на политиката (иначе думите ѝ са непознати), и има 16 оси вместо 8. Всички парчета са за връщане и с 8 оси се различават слабо.
List<String> all = new ArrayList<>(List.of(TEXT));all.addAll(List.of(POLICY));Space space = new Space(all.toArray(new String[0]), STOP, 16);String[] words = String.join(" ", POLICY).split(" ");int[] v = space.ofText(QUESTION);Виж го
Парчета по 10 думи
Функцията реже думите на парчета. Засега застъпването е 0: всяко парче започва там, където свършва предишното.
// парчета по size думи; стъпката е size - overlapstatic List<String> chunk( String[] words, int size, int overlap) { List<String> pieces = new ArrayList<>(); int step = size - overlap; for (int from = 0; from < words.length; from += step) { int end = Math.min(from + size, words.length); var part = Arrays.copyOfRange(words, from, end); pieces.add(String.join(" ", part)); if (end == words.length) break; } return pieces;}Друга функция печата всяко парче с близостта му до въпроса и кое е най-близко:
// всяко парче с близостта му; най-близкото накраяstatic void show(Space space, int[] v, List<String> pieces) { int best = 0; double top = -1; for (int i = 0; i < pieces.size(); i++) { String p = pieces.get(i); double c = cosine(v, space.ofText(p)); System.out.printf("%d %.3f %s%n", i + 1, c, p); if (c > top) { top = c; best = i; } } System.out.println("→ " + (best + 1));}show(space, v, chunk(words, 10, 0));1 0.652 можете да върнете стоката до 14 дни от доставката стоката2 0.174 трябва да е в оригиналната опаковка куриер ще я вземе3 0.815 от адреса ви безплатно парите се връщат по картата до4 0.745 пет дни стоки с ваш надпис не се връщат за5 0.632 повреда пишете ни до два дни→ 3Най-близко е парче 3 (0,815). То свършва на „по картата до“, а „пет дни“ е в парче 4. Моделът ще прочете „парите се връщат по картата до“ и няма да знае колко. Срезът е паднал точно в средата на отговора.
Идеята
Застъпване
Срезът е сляп: не знае къде свършва изречението. Поправката е застъпване (overlap): всяко парче започва малко преди края на предишното. С парчета по 10 думи и застъпване 4 всяко парче започва 6 думи след предишното, а последните му 4 думи са първите 4 на следващото.
Така краищата се повтарят и изречение, срязано в едно парче, е цяло в съседното. Цената е повече парчета и повторени думи.
Виж го
Със застъпване 4
show(space, v, chunk(words, 10, 4));1 0.652 можете да върнете стоката до 14 дни от доставката стоката2 0.436 дни от доставката стоката трябва да е в оригиналната опаковка3 0.147 е в оригиналната опаковка куриер ще я вземе от адреса4 0.531 я вземе от адреса ви безплатно парите се връщат по5 0.874 парите се връщат по картата до пет дни стоки с6 0.745 пет дни стоки с ваш надпис не се връщат за7 0.732 не се връщат за повреда пишете ни до два дни→ 5Сега най-близко е парче 5 (0,874) и то държи цялото изречение: „парите се връщат по картата до пет дни“. Парчетата са 7 вместо 5.
// парчета по size думи; стъпката е size - overlapstatic List<String> chunk( String[] words, int size, int overlap) { List<String> pieces = new ArrayList<>(); int step = size - overlap; for (int from = 0; from < words.length; from += step) { int end = Math.min(from + size, words.length); var part = Arrays.copyOfRange(words, from, end); pieces.add(String.join(" ", part)); if (end == words.length) break; } return pieces;}
int[] v = space.ofText(QUESTION);show(space, v, chunk(words, 10, 0));// парчета по size думи; стъпката е size - overlapstatic List<String> chunk( String[] words, int size, int overlap) { List<String> pieces = new ArrayList<>(); int step = size - overlap; for (int from = 0; from < words.length; from += step) { int end = Math.min(from + size, words.length); var part = Arrays.copyOfRange(words, from, end); pieces.add(String.join(" ", part)); if (end == words.length) break; } return pieces;}
int[] v = space.ofText(QUESTION);show(space, v, chunk(words, 10, 0));// парчета по size думи; стъпката е size - overlapstatic List<String> chunk( String[] words, int size, int overlap) { List<String> pieces = new ArrayList<>(); int step = size - overlap; for (int from = 0; from < words.length; from += step) { int end = Math.min(from + size, words.length); var part = Arrays.copyOfRange(words, from, end); pieces.add(String.join(" ", part)); if (end == words.length) break; } return pieces;}
int[] v = space.ofText(QUESTION);show(space, v, chunk(words, 10, 4));Стъпка по стъпка, с думи
- Политиката за връщане: 46 думи. В оранжево е изречението с отговора: „парите се връщат по картата до пет дни“.
- Парчета по 10 думи без застъпване: 5 парчета, едно след друго.
- Близостта на всяко парче до въпроса. Най-близко е парче 3 (0,815).
- Но парче 3 свършва на „по картата до“: „пет дни“ е в следващото парче. Отговорът е срязан.
- Застъпване 4: всяко парче започва 6 думи след предишното и краищата се повтарят. 7 парчета.
- Най-близко е парче 5 (0,874) и то държи цялото изречение.
- Без застъпване отговорът е срязан между две парчета, а със застъпване едно парче го държи цял. Цената: 7 парчета вместо 5 и повторени думи.
Пробвай
Предвиди изхода
Изречение от 8 думи на парчета по 4 със застъпване 1. Колко парчета излизат?
String[] ws = "доставката до офис на куриер струва 3 евро".split(" ");System.out.println(chunk(ws, 4, 1).size());Стъпката е 4 − 1 = 3. Парчетата започват на думи 0, 3 и 6. Третото е само две думи, „3 евро“, и стига до края, затова цикълът спира. Без застъпване щяха да са две парчета.
Пробвай
Попълни
С колко думи по-късно започва всяко следващо парче? Какво стои на мястото на ___?
// парчета по size думи; стъпката е size - overlap
static List<String> chunk(
String[] words, int size, int overlap) {
List<String> pieces = new ArrayList<>();
int step = size - ;
for (int from = 0; from < words.length; from += step) {
int end = Math.min(from + size, words.length);
var part = Arrays.copyOfRange(words, from, end);
pieces.add(String.join(" ", part));
if (end == words.length)
break;
}
return pieces;
}overlap. Стъпката е размерът минус застъпването: с 10 и 4 следващото парче започва 6 думи по-късно, а 4 думи се повтарят.
Пробвай
Намери грешката
Колега реже със застъпване 4, но част от политиката не е в нито едно парче. На кой ред е грешката?
Избери реда с грешката.
Стъпката е size + overlap: парчетата започват на 0, 14, 28 и 42 и между тях остават по 4 думи, които не влизат никъде. Тези думи никога няма да бъдат намерени. Трябва size - overlap.
Идеята
По изречения
Брой думи е грубо. Документите имат естествени граници: изречения, абзаци, заглавия. Ако парчето е цяло изречение, отговорът не се срязва:
show(space, v, List.of(POLICY));1 0.670 можете да върнете стоката до 14 дни от доставката2 0.312 стоката трябва да е в оригиналната опаковка3 0.031 куриер ще я вземе от адреса ви безплатно4 0.874 парите се връщат по картата до пет дни5 0.837 стоки с ваш надпис не се връщат6 0.632 за повреда пишете ни до два дни→ 4Изречението с отговора печели (0,874). Но виж изречение 3: „куриер ще я вземе от адреса ви безплатно“, 0,031. „я“ е стоката от предишното изречение. Само по себе си парчето не казва нито какво взема куриерът, нито че е за връщане. Ако клиентът пита „идва ли куриер за връщането“, това парче губи част от смисъла си.
Затова истинските системи пазят връзката: слагат заглавието на документа и на раздела пред всяко парче, добавят по едно изречение от съседите или режат по абзаци. И пазят към всяко парче откъде е: документ, раздел, място. По това отговорът цитира източника.
Идеята
Кога боли
- Твърде малки парчета губят смисъла: „до 14 дни“ без думата „връщане“ не казва за какво са 14-те дни.
- Твърде големи парчета се размиват като цялата страница и пълнят промпта.
- Застъпването носи повторения. Парчета 5 и 6 имат едни и същи думи. Ако и двете са сред първите k, в промпта влиза повторен текст. Съседни парчета от един документ се сливат, преди да отидат при модела.
- Таблици, списъци и код не се режат по брой думи. Срязаният ред от таблица с цени е безсмислен: такива документи се режат по структурата си.
- Смениш ли рязането, сменяш индекса. Всички парчета се режат и смятат наново.
Няма размер, който е верен за всички документи. Размерът и застъпването се избират с тестов набор от въпроси, както при k в предишния урок: виж оценяването.
По-точно, за любопитните
Размерът в истинските системи. Парчетата се мерят в токени, не в думи: често са от 200 до 800 токена със застъпване от 10 до 20% (примерни числа). Колко токена е един текст, е в урока за токените. Тук парчетата са по 10 думи, а изреченията са почти толкова дълги, затова и застъпването е голямо.
Кога застъпването пази отговора цял. Изречение от L думи е цяло в някое парче, ако L ≤ overlap + 1. С застъпване 4 това са изречения до 5 думи. По-дълго изречение е цяло само ако някой срез падне точно преди него: в упражнението се вижда, че това зависи от местата на срезовете.
Рязане по смисъл. Някои системи режат там, където векторите на съседните изречения се разминават: смяната на темата е граница. Това струва по едно извикване на модела за вектори на изречение.
Упражнение
Проверка на рязането
Реши в редактора си.
Напиши проверка: за парчета по 10 думи и застъпване от 0 до 4 намери най-близкото парче до въпроса и отпечатай колко са парчетата, близостта и дали парчето държи цялото изречение с отговора.
застъпване 0: 5 парчета, 0.815, отговорът е цял: незастъпване 1: 5 парчета, 0.745, отговорът е цял: незастъпване 2: 6 парчета, 0.874, отговорът е цял: дазастъпване 3: 7 парчета, 0.892, отговорът е цял: незастъпване 4: 7 парчета, 0.874, отговорът е цял: даЗастъпване 2 и 4 държат отговора цял, а 1 и 3 не. При застъпване 3 близостта е най-висока (0,892), но парчето свършва на „до пет“ и думата „дни“ е отрязана. По-голяма близост не значи цял отговор. Дали отговорът е цял, зависи от това къде падат срезовете, затова такава проверка се пуска при всяка смяна на рязането.
Още задачи
- Изречения до 16 думи. Слепвай цели изречения в парче, докато не минат 16 думи. Кое парче е най-близо?
- Без повторения. Ако две от първите k парчета се застъпват, слей ги в едно.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Ембединги: смисълът като вектори · Прозорецът на контекста · Цена и бавност: бюджет, кеш, по-малък модел · Търсене по смисъл: най-близките k
- Продължава в: RAG: отговор от документите · Оценяване на RAG: намира ли и стъпва ли · Асистентът на поддръжката
- Задачи по темата: Търсене по смисъл и RAG