Dartboard RAG: Чаго робіць, калі Top-K вяртае той самы фрагмент троха разоў
Звычны спосаб працы з top-k можа вярнуць часткі, які ў большай меры супадаюць. Dartboard RAG выбірае элементы як за ўзаемнаю адносоўнасцю, так і за разнавідносцю; хутчэй пазнаём алгорытм, код і супакой, калі яго належыць выкарыстоўваць.
Крыніцы: Dartboard RAG: When Top-K Returns Three Versions of the Same Chunk
Звычны спосабы адзыскання топ-к элементаў не функцыонуюць эфектыва. Калі ў корпусе тэкста адна і тая ж інфармацыя павтараецца ў калькі месцаў, k найбліжэйшых фрагментаў да запіту можа выступіць адзін фрагмент, у яком гэта павтараецца тры разы за дапамогою розных слоў.
Уявіце 100-сторонній атлас пра змяну клімату, у яком пяць раздэлаў прыбліжна аднакова, але з разным формулюванням, распаведаюць пра парніковыя газы і падкрэпляюць адну й тую ж ідэю. Калі хтось запытае: «Што спрычыняе змяну клімату?», індэкс FAISS вярне наступнае:
Top 3:
1. "Greenhouse gases trap heat in the atmosphere, causing warming..." (sim 0.91)
2. "Atmospheric greenhouse gases are the primary driver of climate change..." (sim 0.89)
3. "The trapping of heat by greenhouse gases leads to rising temperatures..." (sim 0.88)
Гэта тры ўрокі, які фактычна адпавядаюць ад однаго факту. Карыстальнік запытаўся пра прычыны, у множыні, і павінаваўся адпаведзь з адной прычынай, якая павтарылася. Урокі пра вырубку лесоў, промышловую сельскадарожчу і змяну выкарыстоўвання землі знаходзіліся на 8, 12 і 17-му месцах, таму ніхто з іх не патрапіў у кантэкст ЛЛМ. Адпаведзь мадэлі падражнае яе вхідным даным: плавны абзац пра парніковыя газы без жадных згадак пра іншыя прычыны.
Вектарны пошук не ёсць прычыной. Ён зрабіў тое, што яму было сказана — вернуў тыповыя рэзультаты за адпаведнасцю. Проблема ў тым, што панявленне адпаведнасці не урахоўвае рэдундацыю. Тры ўрокі можу быць максымальна блізкія да запіту і водночас майжэ ідэнтычныя адна другой, і звычны метод top-k вернуе ўсе тры, выключаючы іншыя, рэлевантныя матэрыялы.
RAG для тарэлкі з дартамі зменяе правіло выбору. Якшча стандартны спосаб знаходжэння дадзеных шукае k частак, якія знаходзяцца найбліжэй да запіту, то этот падход шукае k частак, кожная з якіх ў высокай меры рэлевантна і адночаса знаходзіцца далека ад іншых. Такім чынам отрымаюцься рэлевантныя частыны корпусу, а не калькі ўсёрасовай найкрашчай падходзяшчай дастатку.
Уявіце сабе тарэлку з дартамі
Разглядзіце запіт як цэль і кожную частаку — як дарт, які ужо застраў у тарэлцы. Чым бліжэй частака да центру, тым вышэй яе рэлевантнасць.
Chunks plotted by relevance to query
(closer to center = higher cosine sim)
●●● ← cluster of near-duplicate chunks
●●● (all about "greenhouse gases")
● bull's-eye = QUERY
● ← chunk about deforestation
(relevant but different topic)
● ● ← chunks about agriculture, ocean carbon
● ● ← chunks about historical climate
STANDARD TOP-3 picks: DARTBOARD TOP-3 picks:
3 closest darts 1 closest, then darts that are also
= 3 darts in the same good but spread across the board
spot near bull's-eye = better coverage of relevant content
Звычны спосаб выкарыстоўвання збірае тры найбліжэйшыя дарты. Метод дошкі для дартоў збірае тры дарты, якія знаходзяцца блізка адно да другога, але таксама розташаваны на разных расстояннях.
Аб’ектна структура
З архітэктурнага точка зору гэта стандартны спосаб выкарыстоўвання з дадатковым, болей розумным крокам выбору. Храненне вектараў і эмбеддынгі застаюцца такімі ж, якімі былі. Џедынай новай часткаю ёсць крок жадання, які выбірае, калькі з 15 кандыдатаў потрапляюць у фінальныя 5.
Крок 1: запрашаць больш, чым трэба
Алгорытм не можа выбіраць між кандыдатамі, якія у ўсём не ёсць, таму йому патрэбен набор большый, чым фінальныя k. У стандартнай рэалізацыі гэта досягаецца падвоўкай колькасці за дапамогою кэфіцыента перэпрыборкавання 3:
fetch_k = self.k * self.oversampling # default: 5 × 3 = 15
candidates = vector_store.search(query_embedding, k=fetch_k)
Тады вы бераеце 15 кандыдатаў, а не 5. FAISS вяртае іх, распарадзіўшы па косай сэмабільнасці, чыяму кандыдат №0 знаходзіцца найбліжэй да запіту, а кандыдат №14 — найдалей. Наступны крок выбірае найкрашчы пяцічленны сабсэт, вазаваючы размяроўна значэнне рэлевантнасці і разнообразнасці.
Наступны крок выкарыстоўвае эмбедынгі кожнага кандыдата, а не толькі ўсё яго тэкст, таму калекцыя запрашваецца як цэлыя об’екты Document з прыўязанымі эмбедынгамі.
Крок 2: стварэнне двух матрыцаў аддаленасці
На адваротах эмбедынгаў кандыдатаў вырачваюцца две матрыцы:
# Normalize all vectors so dot product = cosine similarity
query_norm = query_vec / np.linalg.norm(query_vec)
cand_norm = candidate_matrix / np.linalg.norm(candidate_matrix, axis=1, keepdims=True)
# Distance = 1 - cosine_similarity
query_distances = 1.0 - np.dot(query_norm, cand_norm.T) # (1, N)
document_distances = 1.0 - np.dot(cand_norm, cand_norm.T) # (N, N)
query_distancesабсалюеўае расстоянне ад запіту да кожнага кандыдата. Нізкае значэнне означае высокую рэлевантнасць. Яго форма — (1, N).document_distancesабсалюеўае парнае расстоянне між кожнымі двума кандыдатамі. Нізкае значэнне означае, што гэтыя кандыдаты супадаюць адзін з іншым. Яго форма — (N, N).
Рэлевантнасць выкалічваецца на адной з матрыц, а разнараднасць — на другай.
Шаг 3: ператварэнне расстояняў у лог-верыятносці
Чыстыя расстоянні па-свойму не спрацоўваюць разам. Разлік межа 0,3 і 0,4 можа здавацца значымым, але простая абяканне дае 0,1 і недастаткова падчырвоняе гэты факт. Таму алгорытм ператварае расстоянні на лог-верыятносці пад дэйствам гаусавага ядра:
def lognorm(dist, sigma):
return -np.log(sigma) - 0.5 * np.log(2 * np.pi) - dist**2 / (2 * sigma**2)
Гэта лог-графік гаусіянскай ўплотненасці. Як можна зрозумець, мала вялічына абэрэнцыі дае высокую верыятнасць (блізкая абэрэнцыя означае, што элементы, верагатна, адносна супарадні), вялікая абэрэнцыя — нізкую, а sigma задае насколькі острой ёсць крывая. Мала значэння sigma, за значыню 0.1, стварае чыстыя пікі, якія сільна рэагуюць на малыя абэрэнціі. Больша значэння, напрыклад 0.5, спявялівае крывую, так што супарадныя абэрэнціі спрацоўваюць прыбліжна однакова.
Работа ў лог-просторы мае значэнне для чысловай стабільнасці. Пры малых абэрэнціях і малай значэнні sigma чыстыя верыятнасці зменшаюцца да мізерных значэнняў, якія пераходзяць у ноль, тады калі лог-верыятнасці застаюцца ў межах і можна іх пораўняваць.
Шаг 4: цікавы цыкл
Самэ гэтаму месту належыць логіка, якая выкорыстоўваецца для аналізу. Процэс выглядае так:
- Спачатку выберыце найболей рэлевантны фрагмент. Якшто нічога не было выбрана для парадоксу, тады няма прымусов да аплікацыі тэста на разнаобразнасць.
- Сохраняйце значэнне
max_distances, якое фіксуе для кожнага кандыдата яго максімальную адстань у відах логарымету верыятнасці да будзь-каго з выбраных дакументаў. Чым большая цяжкае значэнне, тым болей кандыдат адрозныя ад нынешняй выборкі. - Для кожнага засталога месца расчысляйце бал кожнаму невыбранаму кандыдату, сумаваючы показнік рэлевантнасці (якая яго блізорукасць да запіту, з
query_probs) і показнік разнаобразнасці (якая яго адстань ад найбліжэйшага вялікага кандыдата, зmax_distances). Расчытанне выглядае так:diversity_weight × diversity + relevance_weight × relevance. - Агрэгавайце сумарныя балы за дапамою функціі logsumexp, якая забезпечвае чысловую стабільнасць.
- Выберыце кандыдата з найвышэйшым сумарным балам, дадзіце яго ў выборку і апдэйтавайце значэнне
max_distances.
У кодзе:
# Step 1: pick most relevant first
most_relevant_idx = np.argmax(query_probs)
selected_indices = [most_relevant_idx]
max_distances = doc_probs[most_relevant_idx].copy() # diversity tracker
# Step 2-6: iteratively add diverse + relevant chunks
while len(selected_indices) < num_results:
# For each candidate, compute "diversity from any selected"
updated_distances = np.maximum(max_distances, doc_probs)
# Combine relevance + diversity
combined = (diversity_weight * updated_distances
+ relevance_weight * query_probs[np.newaxis, :])
# Aggregate per candidate (logsumexp for numerical stability)
normalized = logsumexp(combined, axis=1)
# Mask already-selected
for idx in selected_indices:
normalized[idx] = -np.inf
# Pick the best
best_idx = np.argmax(normalized)
max_distances = updated_distances[best_idx]
selected_indices.append(best_idx)
Асалідная ідэя — цэлкавая заўантажанасць. Кожны выбор вазнічае пытанне «Чы гэта рэлевантна?» проты пытання «Чы гэта не аднае з таго, што ужо ў нас ёсць?» Першы выбор максімізуе чыстую рэлевантнасць, а кожны наступны выбор балансуе як між рэлевантнасцю, так і між охопленнем рэлевантнага рэгіону.
Што фактычна рабіць матэматыка, у простых тлумачэннях
На кожным кроцы цыкл фактычна спрашвае: з-поміж кандыдатаў, якія ўсё шчэ не выбраныя, кальвен з іх найкрашча павышае як свою заўантажанасць рэлевантнасці, так і охопленне рэлевантнага рэгіону?
Уявіце сабе 2D-дыяграму, дзе рэлевантнасць прадстаўленая на асі x, а адстань ад выбранага набору — на асі y. Першы выбор — гэта точка, якая знаходзіцца самая права. Пасля чаго кожны іншы кандыдат мае значэнне y, якое ўособлівае адстань ад гэтага першага выбору.
Для другага выбору трэба ўсё, што знаходзіцца далека правая стороне (рэлевантнае), а таксама высока (вядома, не як у першым случае). Калі значэння обох ваг становяць 1,0, баланс ёсць роўным, таму цыкл выберае фрагмент, які ў меньшай ступені рэлевантны, але значна разны.
Relevance →
Low ●◯
● ● ← Standard top-k picks these 3
(highest relevance, regardless of diversity)
● ●
●●●●●● ● ● ● ← Many similar high-relevance chunks
High (cluster)
Diversity ↓
from
selected
↓
↓ ↓ ← Dartboard picks 1 from cluster,
then far-away ones with high relevance still
Метод «Plain top-k» выбірае самыя правыя точкі, незалежна ад таго, насколькі яны згрупаваны разам. Метод «Dartboard» спачатку выбірае самую правую точку, а потым намеравана распрастоюецца на іншыя рэлевантныя рэгіоны.
Два коефіцыенты
relevance_weight = 1.0 # how much we care about chunks being close to query
diversity_weight = 1.0 # how much we care about chunks being different from each other
І тое, і другое за замовчуванням станавяцца на 1.0, што означае роўны акцэнт. Іх пераскок палягае на змяне прыемаў: падвышайце показнік разнаобразнасці, калі продаваліся дуплікаты, і падвышайце показнік рэлевантнасці, калі прасачуецца нерэлевантны матэрыял. Замовчытыя значэння падходзяць для большасці корпусаў.
Прыклад працы з корпусам, насыцаным дуплікатамі
У стандартнай рэалізацыі ёсць параметр duplicate_factor, які павтарае кожны фрагмент N разоў — гэта корыстны спосаб тэставання разнаобразнасці на сінтэтычны, насыцаны дуплікатамі корпусе. Установіце яго на 5 і запытайце: «Што спрычыняе кліматычныя змены?»
Стандартны метод адбору 5 найболей рэлевантных рэзультаатаў дае такі вынік:
1. "Greenhouse gases cause warming..." (sim 0.91)
2. "Greenhouse gases cause warming..." (sim 0.91) ← DUPLICATE
3. "Greenhouse gases cause warming..." (sim 0.91) ← DUPLICATE
4. "Greenhouse gases cause warming..." (sim 0.91) ← DUPLICATE
5. "Greenhouse gases cause warming..." (sim 0.91) ← DUPLICATE
Unique results: 1/5
Пяць рэзультатаў ёсць буквальна аднам цэлым, усе яны на однаковай адстані ад запіту, таму LLM прымеў адну фактую інформацыю, перазказаную пяць разоў.
Рэйтынг топ-5 дае такія рынкі:
1. "Greenhouse gases cause warming..." (highest relevance — wins first pick)
2. "Deforestation reduces the carbon sink..." (different chunk, still relevant)
3. "Industrial agriculture emits methane..." (third unique cause)
4. "Land-use changes alter surface albedo..." (fourth unique cause)
5. "Fossil fuel combustion is the largest CO₂ source..." (related to #1 but different angle)
Unique results: 5/5
Калі выбраны першы фрагмент, паракамеры разнаобразнасці карае кожную яго копію, адколькі кожная з іх мае нулявую разнаобразнасць у стосунку да выбранага фрагмента. Таму цыкл шукае разныя тэксты ў полюсах з два па пяць, і большая мовная мадэль бачыць пяць разных фактов пра прычыны. Гэта найкрашчы варыянт. Рэальныя корпусы рэдка калі маюць точныя дуплікаты, таму эфект ёсць менш выражаным, хоць тая ж дынаміка праўяеся і да майжэ-дуплікатав.
Усё гэта за адносна дваццаць ліній
def dartboard_select(query_emb, candidate_embs, k=5, sigma=0.1):
# 1. Compute distance matrices
query_dist = 1 - cosine(query_emb, candidate_embs) # query→each
doc_dist = 1 - cosine(candidate_embs, candidate_embs) # each→each
# 2. Convert distances to log-probabilities
query_probs = lognorm(query_dist, sigma)
doc_probs = lognorm(doc_dist, sigma)
# 3. Pick most relevant first
selected = [np.argmax(query_probs)]
max_distances = doc_probs[selected[0]].copy()
# 4. Iteratively add diverse + relevant
while len(selected) < k:
updated = np.maximum(max_distances, doc_probs)
combined = updated + query_probs[np.newaxis, :] # equal weights = sum
scores = logsumexp(combined, axis=1)
for idx in selected:
scores[idx] = -np.inf # don't re-select
best = np.argmax(scores)
max_distances = updated[best]
selected.append(best)
return selected
Дзейнае высака ўмерклая. Складнасць крыўціцца у тым, што значаюць матэматычныя вырачункі, а не у колькасці ліній. Калі правіла стануць яснымі (выбраць найболей рэлевантны фрагмент, а потым прабачыць тыя кандыдаты, якія ўзмаўчайна рэлевантныя і не падобныя да таго, што ужо выбрана), код стане простым для розумэння.
Параметры, якія варта налаштаваць
Две настройкі заслуговуюць на увагу.
oversamplingзадае розмер пулу кандыдатаў. Стандартнае значэнне 3 означае 15 кандыдатаў для фінальных 5. Падвоўкай цаго значэння, да 5 або 10, алгорытму дае больш магчымасцей для працы, але сповалняе вычысленне парнага расстояння. У корпусе з сільным дуплікацыям 10× oversampling дазволяе сістэме знаходзіць ўсё болей рэлевантныя фрагменты ў рангаванні.diversity_weightпавінен зростаць, калі падчас аналізу застаюцца дублікаты, які ўсё ж праходзяць незафіксаваныя, і спадаць, калі рэзультаты занадта распыляюцца, а фрагменты пастрэлі выбіраюцца лішчынка таму, што яны „разныя“. Значэнне 1.0 ў стандартным налаштаванні ёсць збалансаваным, а значэння 1.5 чы 2.0 ёсць разумнымі для дужа ўжоўтканых корпусаў.
sigma ёсць болей чутлівым параметрам і рэдка калі трэба яго зменшваць чы падвышваць. Стандартнае значэнне 0.1, як сказвацца, падходзіць для расчытку косайнавых адстаней у мадэлях OpenAI. Калі вы перайдзеце на іншую мадэль ужоўткання з іншымі масштабамі адстаней, можа знадобіцца яго перэнастроўка, хоча большасць людзей можа залішыць яго без змян.
Дзе ён дапамагае, а дзе — ні
Dartboard падходзіць для кальколька сітуацыяў.
- Корпусы з майжэ дублікатамі. Большое колічэство істочнікаў, які розглядаюць адна і тая ж тэму, павтаральныя документы з політыкі, архіўы новасцей з перакрыцанням у контэнце, індэксаваныя паспакі з форумаў. Стандартны метод top-k часта не справляецца з такімі задачамі.
- Обсяг інформацыі такі ж важлівы, як і точнасць. Кантэнт у віглядзе падсумакаў даследжэнняў, развернутых запытань і адпаведзей, а таксама порэйтывных аналізаў, калі корыстнік хочаць побачыць весь спектр актуальной інформацыі, а не толькі яшчэ адна частка з яе.
- Дапаможны вычысловы рэсурсы. Дадатковыя вітраты стаўляюцца з вырахунку арытметыкі матрыц над кандыдатамі, а не з додатковых вызоў LLM. Гэта практычна непазначальна для k=5 з 15 кандыдатамі, але помітна для k=20 з 60 і болей кандыдатамі.
У такіх случаях гэта менш эфектыва.
- Прыродна разнаваротная корпуса. Калі фрагменты ужо ўнізе тэматычна разныя, за дапамогою семантычнага фрагментавання або добра структураваных дакументаў, метод top-k адбірае розныя фрагменты у будзь-якім случае, а дадатак dartboard прыносіць дапэўнюю навантажэння без змены рангавання.
- Дужа спецыфічныя запітанні. Запітанне „Які тэмпература капявання ртуты?“ мае адну правільную адпаведзь. Логіка разнаваротнасці можа выбраць слабейшыя фрагменты зарады разнаваротнасці, што пагубна для адпаведзі.
- Маленькае значэнне k. Калі k становіць 1 або 2, няма многа можлівасцей для разнаваротнасці, таму звычны метод top-k ўсё равна падходзіць.
- Працэсныя ланцюгі, які ужо аб’еднваюць фузыю, перарангаванне і вікнаўцаванне. Кожны з яных рашае супакойленыя проблемы своім спосабам. Дадаўанне дадатку dartboard да ўжо складнага працэснага ланцюга рэдка калі дапамагае і толькі падвышае складнасць.
Ідэя, яку трэба запам’ятаваць
Цікавым аспектам у методэ Дартборд RAG ня ў матэматыцы. Гэта усвядомленне, што сама толькі рэлевантнасць — гэта ня правільная мета для методу адзыскання дакументаў top-k.
Стандартны метод top-k спытаецца, калікі элементаў з групы k ёсць самыя рэлевантныя. Гэты падход адночасна расследжвае кожны элемент окрэма і ігнаруе тое, што вялікі ўжо ёсць у рэзультатнай групе, таму калі існуюць калькі роўна рэлевантных элементаў, ўсе яны будуць включаныя, нават калі яны гавораюць пра тое ж самае.
Крашчая мета базуецца на цэлай групе: калікі элементаў з групы k, разам узятых, найкрашча падходзяць для задоволення патрабавання запыткі? Гэта іншая форма оптымізацыі, у якой рэйтынг элемента k залежыць ад элементаў 1 па k-1, а не толькі ад яго сябеўскай рэлевантнасці. Жаднабытныя методы, такія як Дартборд, ёсць прыемнымі апрыксамі гэтага падходу.
Гэты прыем вельмі часта выкорыстоўваецца не толькі для адзыскання дакументаў:
- Выбіранне праекстаменту для навчання. Тэхнікі акыўнага навчання таксама вікорыстоўваюць выбіранне праекстаменту, уважлівае да разнаобразнасці, з таго жа паводу.
Загальны урок заключаецца ў тым, што калі вы выбіраеце набор рэчэй, трэба оценяць самы набор, а не адзінкавыя елементы. Dartboard — гэта версія гэтага прынцыпа для тэхналогій RAG, і калі вы яго зрозумеце, пабачыце тую ж абманну схему ў багацькох іншых практыках тыпу «ранкаваць, а потым выбраць топ-k».
Есць свая цена: дадзецца додатковая обработка даных, і будзе сложнейша адказа на пытанне, чаму адзін фрагмент паводзіцца лепшая за іншы. Адноакоўшы, гэта дае лепейшы охват, што часта безпосерэдна прыводзіць да крашчых адказоў, особліва калі корпус даных не аранжаваны так, каб кожны факт быў у адным фрагменце.
Спадневаная літэратура
- Іспытанне інструментаў у працэсе вырабоцтва для агентаў: паходжанне, практыка павторных спроб і можлівасці адзорвання — Агент банку некалькі разоў вызваў платную API і выдумаў номер тэлфона. Дазнаецеся пра мінімальныя наборы інструментаў, паходжанне параметраў, трыя рэверсы павторных спроб і показнікі для кожнага інструмента.