TL;DR
ACT — это дерево решений для текста, где каждый узел задает вопрос на естественном языке, а LLM отвечает yes/no и направляет данные по ветвям. Вместо split'ов по числовым признакам ("возраст > 30") дерево задает смысловые вопросы ("упоминается ли кашель с кровью?"). Вопросы автоматически находятся через итерационное улучшение: модель анализирует где ошибается, получает feedback и переформулирует вопрос, минимизируя Gini impurity — как в классическом CART, но для неструктурированных данных.
Ключевая находка: Одиночный промпт (Chain-of-Thought, DSPy, TextGrad) делает всё решение за один проход — модель может упустить нюансы, запутаться в комплексных критериях, или галлюцинировать. Нет явной структуры рассуждения, которую можно проверить. Когда решение сложное (медицинский диагноз по симптомам, определение спама по тексту письма, детекция jailbreak-промптов), одношаговый вывод часто проваливается.
Суть метода: Иерархия простых yes/no вопросов работает лучше одного сложного запроса. ACT строит дерево рекурсивно: на каждом уровне ищет бинарный вопрос, который лучше всего разделяет классы. Для каждого узла сравнивает правильно и неправильно классифицированные примеры, находит semantic patterns (что их различает), и улучшает вопрос. Результат — прозрачный decision path: для каждого предсказания видно через какие вопросы прошли данные и почему получили этот ответ. На трех датасетах (медицина, спам, jailbreak) ACT превосходит промптинг-методы на +4.7-5.2% accuracy.
Схема метода
Для каждого узла дерева (от корня к листьям):
ШАГ 1: Начать с generic вопроса
→ "Does this example belong to the positive class?"
ШАГ 2 (цикл, k раз): Улучшить вопрос
→ Разделить данные по текущему вопросу (LLM отвечает yes/no)
→ Для каждой ветви: найти что различает правильные и неправильные примеры
→ LLM дает semantic feedback (какие признаки добавить/убрать из вопроса)
→ Через TextGrad сгенерировать улучшенную версию вопроса
→ Повторить до минимизации Gini impurity
ШАГ 3: Зафиксировать лучший вопрос для узла
→ Выбрать вопрос с минимальной impurity среди всех итераций
ШАГ 4: Рекурсивно построить поддеревья
→ Для каждой ветви (yes/no) повторить шаги 1-3
→ Остановиться при достижении чистоты узла или max глубины
Итого: Дерево вопросов глубины d, где каждый узел — бинарный вопрос на естественном языке.
Пример применения
⚠️ Ограничения метода: ACT работает для бинарной классификации с относительно четкими критериями. Плох для субъективных оценок без явных признаков, для задач где классы сильно перекрываются семантически, для простых одношаговых решений (overkill).
Задача: Отбор резюме на позицию product manager в российский SaaS-стартап. Входят 200 откликов, нужно быстро отсеять неподходящих кандидатов. Критерии размыты: "опыт в продукте", "понимание технологий", "навыки коммуникации". HR-менеджер хочет прозрачную систему отбора, которую можно объяснить кандидату при отказе.
Промпт:
У меня есть набор резюме кандидатов на позицию product manager в SaaS-стартап.
Часть резюме от подходящих кандидатов (приняли оффер, хорошо работают),
часть от неподходящих (отказали или быстро уволились).
Построй дерево решений глубины 3 для классификации резюме (подходит/не подходит).
Для каждого узла:
1. Предложи бинарный вопрос (yes/no) про резюме
2. Проверь на примерах: разбей резюме по ответу на вопрос
3. Сравни правильно и неправильно классифицированные — что их различает?
4. Улучши вопрос на основе различий
5. Повторяй улучшение 5 раз, выбери вариант с лучшим разделением классов
Начни с корня дерева. Первый generic вопрос: "Упоминается ли опыт в продуктовой разработке?"
Вот примеры резюме:
ПОДХОДЯЩИЕ:
[3-5 примеров резюме сильных кандидатов]
НЕПОДХОДЯЩИЕ:
[3-5 примеров резюме слабых кандидатов]
Результат:
Модель построит дерево из 7 узлов (3 уровня). Каждый узел — конкретный вопрос:
- Корень: "Есть ли опыт управления продуктом в B2B SaaS или аналогичной сфере?"
- Если yes → "Упоминается ли взаимодействие с командой разработки или понимание технологий?"
- Если no → "Есть ли опыт запуска продуктов или фич с нуля?"
Для каждого резюме видно путь по дереву: через какие вопросы прошли, какой ответ на каждом шаге, и почему в итоге "подходит" или "не подходит". Можно объяснить отказ кандидату: "Вы прошли по критериям X и Y, но не прошли по критерию Z — нет опыта работы с командой разработки."
Почему это работает
Слабость LLM: В одном промпте модель держит всё решение "в голове" — множество критериев, их взаимосвязи, граничные случаи. Это как решать сложное уравнение устно: легко что-то упустить, перепутать, или придумать несуществующую закономерность (галлюцинация). Особенно для задач где критериев 5+, и они зависят друг от друга.
Сильная сторона LLM: Модель отлично отвечает на конкретные бинарные вопросы. "Есть ли в тексте X?" — высокая точность. "Упоминается ли признак Y?" — надежно. Это как переход от "реши задачу целиком" к "ответь да/нет на каждый шаг" — резко снижается вероятность ошибки.
Как метод использует это: ACT декомпозирует сложное решение на цепочку простых yes/no вопросов. Каждый вопрос оптимизирован под конкретную подзадачу — разделить оставшиеся примеры на две максимально чистые группы. Модель не думает о всех критериях сразу, а фокусируется на одном аспекте. Это снижает cognitive load и уменьшает галлюцинации.
Рычаги управления:
- Глубина дерева d (2-5) — больше уровней = более тонкое разделение, но риск переобучения. Для простых задач достаточно d=2-3, для комплексных нужно d=4-5.
- Число итераций улучшения k (5-20) — сколько раз переформулировать вопрос на каждом узле. k=10 дает хороший баланс качества и скорости. k=20 выжимает последние проценты accuracy.
- Размер выборки для semantic feedback m (30-100) — сколько примеров показывать LLM при анализе ошибок. Меньше = быстрее, но может упустить паттерны. Больше = точнее feedback, но дороже по токенам.
- Stopping criteria — минимальный размер узла, порог чистоты (Gini < 0.1), или max глубина. Настраивай под размер датасета и сложность задачи.
Шаблон промпта
Построй дерево решений глубины {max_depth} для классификации: {описание задачи}.
Данные:
КЛАСС 1 ({название_класса_1}):
{примеры класса 1}
КЛАСС 2 ({название_класса_2}):
{примеры класса 2}
Для каждого узла дерева (начиная с корня):
1. ТЕКУЩИЙ ВОПРОС: {начальный_вопрос или "Укажи generic стартовый вопрос"}
2. УЛУЧШЕНИЕ (повторить {k} раз):
a) Раздели примеры по ответу на текущий вопрос (yes/no)
b) Для каждой группы (yes и no):
- Найди правильно классифицированные примеры
- Найди неправильно классифицированные примеры
- Что их РАЗЛИЧАЕТ? Какие признаки присутствуют/отсутствуют?
c) На основе различий — улучши вопрос:
- Добавь/убери признаки для лучшего разделения
- Сделай вопрос более конкретным или более широким
- Вопрос должен быть бинарным (yes/no)
- Не больше {max_operators} логических операторов (and/or)
d) Оцени качество: посчитай сколько ошибок после нового вопроса
e) Запомни лучший вариант
3. ЗАФИКСИРУЙ финальный вопрос для этого узла
4. Если узел не чистый и глубина < {max_depth}:
- Построй левое поддерево (для yes)
- Построй правое поддерево (для no)
5. Если узел чистый или достигнута max глубина:
- Листовой узел → класс определяется большинством примеров
Результат: дерево в виде иерархии вопросов с ответами yes/no на каждом уровне.
Что подставить:
{max_depth}— глубина дерева (2-5, обычно 3-4){описание задачи}— что классифицируем и на какие классы{название_класса_1},{название_класса_2}— названия классов{примеры класса N}— 5-10 примеров каждого класса{k}— число итераций улучшения вопроса (5-20, обычно 10){max_operators}— сколько and/or в вопросе (1-2, чтобы вопросы оставались простыми){начальный_вопрос}— для корня дерева можно задать generic ("Относится ли пример к классу 1?") или предложить модели самой
Ограничения
⚠️ Только бинарная классификация: Метод работает для задач с двумя классами (да/нет, подходит/не подходит, спам/не спам). Для multi-class нужна адаптация (one-vs-all или иерархическая декомпозиция).
⚠️ Требует примеры обоих классов: Без training examples модель не может построить discriminative вопросы. Нужно хотя бы 10-20 примеров каждого класса для качественного дерева.
⚠️ Субъективные критерии: Если классы различаются по "вкусу" или "стилю" без явных признаков (например, "креативный текст" vs "скучный текст"), метод работает хуже — LLM сложно формализовать такие различия в бинарные вопросы.
⚠️ Overkill для простых задач: Если решение очевидно с первого взгляда (например, наличие конкретного ключевого слова), построение дерева — избыточная работа. Используй для задач где критериев 3+ и они взаимосвязаны.
⚠️ Computational cost: Каждый узел требует k итераций улучшения × оценка на всех примерах. Для дерева глубины d это ~7 узлов × 10 итераций × 100 примеров = 7000 LLM запросов. В чате без автоматизации это долго и дорого. Применяй для критичных задач где нужна высокая точность и interpretability.
Как исследовали
Команда протестировала ACT на трех датасетах с разной спецификой: DIAGNO (медицинская диагностика: туберкулез vs аллергический синусит, 600 обучающих примеров), SPAM (детекция спама в email, 600 примеров), и JAILBREAK (определение вредоносных промптов для обхода защит LLM, 923 примера). Все датасеты — бинарная классификация текстов.
Сравнивали с четырьмя baseline'ами: Chain-of-Thought (zero-shot), DSPy (8-shot in-context learning с демонстрациями), TextGrad (оптимизация промпта через textual feedback), и классический CART на TF-IDF представлениях текста. Важный момент: все LLM-baseline'ы знали задачу — в промпте было описание ("classify as tuberculosis or not") и названия классов. ACT начинал с generic вопроса без знания задачи и находил критерии сам через анализ примеров.
Тестировали на четырех моделях разного размера: Gemma-4B, GPT-4.1-Nano, GPT-4.1-Mini, Qwen3-4B. Это позволило проверить универсальность метода — работает ли он с разными архитектурами и масштабами.
Результат удивил: ACT показал +4.7-5.2% test accuracy относительно лучших baseline'ов (DSPy и TextGrad) при усреднении по всем датасетам и моделям. На SPAM достиг 98.8% accuracy (vs 97.0% у DSPy) — почти идеальное качество. На JAILBREAK обошел TextGrad на +2.5%. Даже на сложном DIAGNO где симптомы туберкулеза могут пересекаться с аллергией, ACT выиграл +8.8% у baseline'ов за счет иерархической декомпозиции.
Почему работает лучше: Ablation study показал оптимальную глубину d=4 — глубже начинается overfitting (train accuracy растет, test падает). Число итераций улучшения k=10-20 дает plateau — дальше улучшения не помогают. Ключевой insight: semantic feedback (анализ что различает правильные и неправильные примеры) критически важен — без него вопросы остаются generic и не схватывают discriminative признаки.
Качественная проверка: Для медицинского датасета сравнили вопросы ACT с симптомами туберкулеза из источников ВОЗ и NHS. Overlap высокий: кашель с кровью, лихорадка, потеря веса, увеличение лимфоузлов, усталость — ACT нашел эти критерии сам, без медицинского знания. Это подтверждает что метод не галлюцинирует, а действительно извлекает discriminative features из данных.
Оригинал из исследования
Контекст: Исследователи описывают процедуру построения дерева через алгоритм (Algorithm 1 в статье). Вот ключевой фрагмент псевдокода для одного узла:
Function GrowPromptTree(D):
Initialize prompt p^(0) // default initialization
if D is pure or stopping criterion is met then
return Leaf node with majority class label
for k = 0 to K-1 do
D_L ← {(x_i, y_i) ∈ D | f_split(p^(k), x_i) = yes}
D_R ← D \ D_L
δ(p^(k)) ← weighted Gini impurity of split
foreach (g, ŷ_g) ∈ {(D_L, 'yes'), (D_R, 'no')} do
X_correct ← {(x_i, y_i) ∈ g | y_i = ŷ_g}
X_error ← {(x_i, y_i) ∈ g | y_i ≠ ŷ_g}
s_g ← f_purity(p^(k), X_correct, X_error) // Semantic analysis
L(p^(k)) ← f_loss(p^(k), s_D_L, s_D_R, δ(p^(k))) // LLM-evaluated loss
∇_p L(p^(k)) ← TextGrad.feedback(p^(k), L(p^(k)))
p^(k+1) ← TextGrad.step(p^(k), ∇_p L(p^(k)))
p* ← argmin_p^(k) δ(p^(k)) // Prompt with lowest impurity
Split D using p* into D_L and D_R
node ← Create prompt node with final question p*
node.left ← GrowPromptTree(D_L)
node.right ← GrowPromptTree(D_R)
return node
Пример semantic feedback промпта из Appendix:
Below are two groups of samples for which a model answered either "yes" or "no".
Provide feedback about key characteristics that are present or absent in the group
where the model's predicted label matched the true label, and in the group where
the prediction was incorrect.
For the following examples, the model answered "yes":
- Well-classified examples (true label = "yes"):
[List of correctly classified inputs]
- Misclassified examples (true label = "no"):
[List of misclassified inputs]
The feedback you provide must be clear and concise. Focus on the one or two most
important characteristics.
Адаптации и экстраполяции
💡 Адаптация для multi-class задач через one-vs-all:
Для классификации на 3+ класса (например, тип документа: договор/счет/акт) можно построить несколько бинарных деревьев:
- Дерево 1: "договор vs все остальное"
- Дерево 2: "счет vs все остальное"
- Дерево 3: "акт vs все остальное"
Объединить через voting или confidence scores.
Построй 3 дерева решений для классификации документов:
ДЕРЕВО 1 (договор vs остальное):
{примеры договоров} vs {примеры счетов + актов}
ДЕРЕВО 2 (счет vs остальное):
{примеры счетов} vs {примеры договоров + актов}
ДЕРЕВО 3 (акт vs остальное):
{примеры актов} vs {примеры договоров + счетов}
Для каждого дерева следуй процедуре ACT.
При классификации нового документа:
1. Пройди через все 3 дерева
2. Каждое дерево дает yes/no для своего класса
3. Выбери класс с наибольшей уверенностью (если несколько yes — приоритет по чистоте листа)
🔧 Техника: убрать iterative refinement → быстрый результат:
Если нужно быстро и токены дороги, пропусти улучшение вопросов (k=0). Попроси LLM сразу предложить лучший вопрос для узла на основе semantic analysis примеров.
Построй дерево глубины {d}, но для каждого узла НЕ делай 10 итераций улучшения.
Вместо этого:
1. Проанализируй примеры: что различает класс 1 и класс 2?
2. Сразу предложи лучший бинарный вопрос для разделения
3. Проверь качество на примерах
4. Зафиксируй вопрос и переходи к следующему узлу
Это быстрее (1 запрос vs 10 на узел), но качество вопросов может быть ниже.
Применяй когда важна скорость, а не последние проценты accuracy.
💡 Адаптация для scoring вместо hard classification:
Вместо бинарного "да/нет" в листе дерева можно выводить confidence score — долю класса 1 среди training примеров в этом листе.
После построения дерева, для каждого листового узла:
- Посчитай долю класса 1: P(class=1) = N_class1 / (N_class1 + N_class2)
- При классификации нового примера:
- Пройди по дереву до листа
- Верни P(class=1) как уверенность модели
Это полезно когда нужен не просто "да/нет", а оценка вероятности.
Например: "риск оттока клиента 73%" вместо "отток=да".
Ресурсы
ACT: Agentic Classification Tree — Vincent Grari (AXA Group Operations, Sorbonne Université), Tim Arni (EPFL), Thibault Laugel (AXA, Sorbonne), Sylvain Lamprier (Université d'Angers), James Zou (Stanford University), Marcin Detyniecki (AXA, Sorbonne, Polish Academy of Science)
Code: https://github.com/axa-rev-research/ACT
Датасеты: DIAGNO (DDXPlus medical diagnosis), SPAM (email classification), JAILBREAK (adversarial prompts)
Методы-базы: CART (Breiman et al., 1984), C4.5 (Salzberg, 1994), TextGrad (Yuksekgonul et al., 2024), Chain-of-Thought (Wei et al., 2022), DSPy (Khattab et al., 2023)
