Планування: STRIPS, світ кубиків, евристики з релаксації та планування в AI-агентах
Мова STRIPS і PDDL, пряме та зворотне планування, евристики hmax і hadd, аномалія Зуссмана, ієрархічне планування і перепланування — від робота Шейкі до AI-агентів, які складають собі список завдань.
Серія Основи ШІ Частина 25 з 52
- Мапа штучного інтелекту: що є що і як усе це пов’язано
- Коротка історія штучного інтелекту: від Тюрінга до мовних моделей
- Дані, модель, тренування, інференс: основні поняття на одному прикладі
- Як читати математичний запис: символи, індекси, суми та квантори
- Множини й відношення: конструктор, з якого складаються означення в ШІ
- Функції й відображення: чому кожна модель у ШІ — це функція
- Логіка й доведення: правила, за якими міркують у ШІ
- Графи й дерева: структура, на якій тримаються пошук і нейронні мережі
- Лінійна алгебра для машинного навчання: вектори, матриці й чому ШІ передусім множить
- Похідні, градієнт і правило ланцюжка: як модель дізнається, у якому напрямку вчитися
- Градієнтний спуск: як навчається модель
- Що таке ймовірність: від кидка кубика до аксіом Колмогорова
- Умовна ймовірність і теорема Баєса: як змінити оцінку, коли дізнаєтеся щось нове
- Випадкова величина і функція розподілу: одна функція, яка каже про розподіл усе
- Функція щільності та функція маси ймовірності: чому неперервна величина має в точці ймовірність нуль
- Математичне сподівання, дисперсія і коваріація: де лежить розподіл, наскільки він широкий і що з чим пов’язано
- Огляд розподілів ймовірностей: Бернуллі, біноміальний, Пуассона, рівномірний, нормальний і експоненційний
- Закон великих чисел і центральна гранична теорема: чому середні поводяться так передбачувано
- Статистика й оцінювання параметрів: максимальна правдоподібність, MAP і довірчі інтервали
- Теорія інформації: ентропія, перехресна ентропія і KL-дивергенція — звідки беруться функції втрат
- Пошук у просторі станів: BFS, DFS, UCS і A* з анімаціями
- Ігри та суперник: мінімакс, альфа-бета відсікання і Monte Carlo tree search
- Задоволення обмежень: бектрекінг, поширення обмежень і AC-3 на судоку та мапі чеських країв
- Логіка й системи, засновані на знаннях: пряме і зворотне виведення, резолюція та експертні системи
- Планування: STRIPS, світ кубиків, евристики з релаксації та планування в AI-агентах
- Локальний пошук та імітація відпалу: комівояжер через обласні центри Чехії
- Генетичні алгоритми: популяція, схрещування, мутація і добір на рюкзаку для походу
- Еволюційні стратегії і генетичне програмування: від правила однієї п’ятої до закону Кеплера
- Оптимізація мурашиною колонією: як феромоновий слід знаходить короткий шлях
- Оптимізація роєм частинок: інерція, власний досвід і досвід рою
- Нейроеволюція: еволюція ваг і форми нейронних мереж та алгоритм NEAT
- Нечіткі множини й функції належності: як записати «трохи тепло»
- Нечітка логіка та ймовірність: чому «трохи тепло» не означає «імовірно тепло»
- Нечітка логіка: t-норми, імплікації та правила ЯКЩО–ТО
- Нечітке керування: Мамдані та Суґено від вентилятора до маятника на візку
- Нечітка кластеризація: алгоритм fuzzy c-means і точки між кластерами
- Нейро-нечіткі системи (ANFIS): нечіткі правила, які навчаються з даних
- Марковські ланцюги: матриця переходів, стаціонарний розподіл і PageRank
- Приховані марковські моделі: алгоритм Вітербі й нечесне казино
- Байєсівські мережі: чому трава мокра і що з цього випливає для дощувача
- Фільтр Калмана: як за неточними вимірюваннями оцінити, де авто і як швидко воно їде
- Види машинного навчання: з учителем, без учителя, з частковим залученням учителя та інші
- Промпти, контекст і RAG: як дати моделі правильну інформацію
- Варіаційні автокодувальники (VAE): нові зображення з неперервного латентного простору
- Генеративні змагальні мережі (GAN): фальшувальник проти детектива
- Дифузійні моделі: як із чистого шуму постає зображення
- Марковські процеси ухвалення рішень: стани, дії, винагороди й рівняння Беллмана
- Q-навчання і DQN: як навчитися цінності дій — від таблиці до нейронної мережі
- Policy gradient і PPO: навчання безпосередньо політики
- Як працює ШІ-агент: мовна модель у циклі з інструментами
- Програмування з AI-агентами: завдання, тести й перевірка
- Оцінювання і безпека систем ШІ: як вимірювати і як стежити
Повна програма серії Готово: 52 з 83 тем
- Як записати задачу планування як атоми, дії з передумовами й ефектами та ціль.
- Як планувальник сам обчислює евристику з релаксованої задачі і скільки роботи це заощаджує.
- Чому підцілі не можна виконувати одну за одною і як планують сучасні AI-агенти.
Що варто знати заздалегідь: Пошук у просторі станів: BFS, DFS, UCS і A* з анімаціями, Логіка й системи, засновані на знаннях: пряме і зворотне виведення, резолюція та експертні системи
Робот Шейкі зі Стенфордського дослідницького інституту наприкінці шістдесятих їздив кількома кімнатами, пересував коробки й вимикав світло. Це був перший робот, який міркував про власні дії: отримував ціль («перемісти коробку до сусідньої кімнати») і сам вигадував, які дії і в якому порядку до неї ведуть. Мова, якою він це робив, називається STRIPS — а її ідеї сьогодні ви знайдете в логістиці, у комп’ютерних іграх і в циклі AI-агента, який пише вам код.
Ця частина продовжує частини Пошук у просторі станів і Логіка й системи, засновані на знаннях.
1. Планування: пошук, який бачить усередину
Планування — це пошук послідовності дій, яка веде до цілі, — точнісінько як у частині про пошук. Різниця в тому, як записано задачу. У лабіринті стан був чорною скринькою, і алгоритм знав про нього лише те, куди з нього ведуть дії. Планувальник бачить усередину: стан — це список фактів про світ, а дія каже, які факти їй потрібні і які вона змінить. Завдяки цьому планувальник уміє сам вивести оцінку, наскільки далеко ціль, — жодної евристики вам не доведеться писати вручну.
2. Мова STRIPS
Означення — задача планування STRIPS
Задано скінченну множину атомів F (тверджень про світ). Стан — це підмножина s ⊆ F — атоми, які в ньому істинні; чого в s немає, те хибне.
Дія a — це трійка (pre(a), add(a), del(a)) підмножин F: передумови, додавані та вилучувані атоми. Дія застосовна в s, якщо pre(a) ⊆ s, і результівний стан — це
T(s, a) = (s ∖ del(a)) ∪ add(a)
- T(s, a)
- стан після виконання дії — функція переходу, яку в частині про пошук задавав програміст; тут планувальник виводить її з опису дії сам
- s
- вихідний стан: усе, про що дія не згадує, переходить у новий стан без змін (припущення STRIPS, яке розв’язує проблему фрейму, — не треба перелічувати, що не змінюється)
- del(a)
- атоми, які дія робить хибними; віднімаються різницею множин ∖
- add(a)
- атоми, які дія запроваджує; додаються об’єднанням аж після вилучення, тож атом, що є в обох списках, зрештою істинний
Задача — це четвірка (F, A, s0, G), де G ⊆ F — ціль. План — це послідовність дій a1, …, ak, кожна з яких застосовна в стані після попередніх, і в останньому стані sk виконується G ⊆ sk.
Стан — це список речень, які зараз істинні: «C лежить на A», «на B нічого не лежить». Дія — це рецепт із трьома графами: що має бути істинним, щоб її можна було виконати; що стане істинним; що істинним бути перестане. Ціль не мусить описувати весь світ — досить сказати, що для вас важливо, а решта байдужа.
Приклад — одна дія крок за кроком
Кубик C лежить на A, B стоїть поруч. Дія на стіл(C, A) має pre = {на(C, A), вільний(C)}, add = {стіл(C), вільний(A)}, del = {на(C, A)} (повний опис дій — у наступному розділі).
s = {на(C, A), стіл(A), стіл(B), вільний(B), вільний(C)}
pre ⊆ s? на(C, A) ∈ s, вільний(C) ∈ s → дія застосовна
s ∖ del = {стіл(A), стіл(B), вільний(B), вільний(C)}
(s ∖ del) ∪ add = {стіл(A), стіл(B), стіл(C), вільний(A), вільний(B), вільний(C)} — усі три кубики на столі
STRIPS з’явився 1971 року (Файкс і Нільссон) саме для Шейкі. Його сучасний наступник — мова PDDL, якою з 1998 року задають задачі для міжнародних змагань планувальників. Дію в ній записують як схему зі змінними:
(:action na-stil
:parameters (?a ?b)
:precondition (and (na ?a ?b) (vilnyi ?a))
:effect (and (stil ?a) (vilnyi ?b) (not (na ?a ?b))))
3. Світ кубиків
Класична тестова задача планування: на столі лежать кубики, деякі один на одному. Однією дією ви берете кубик, на якому нічого не лежить, і кладете його на стіл або на інший вільний кубик. Атоми бувають трьох видів: на(A, B) — A лежить на B, стіл(A) і вільний(A).
Дії у світі кубиків
- на стіл(A, B): pre = {на(A, B), вільний(A)}, add = {стіл(A), вільний(B)}, del = {на(A, B)}
- зі столу(A, C): pre = {стіл(A), вільний(A), вільний(C)}, add = {на(A, C)}, del = {стіл(A), вільний(C)}
- перенести(A, B, C): pre = {на(A, B), вільний(A), вільний(C)}, add = {на(A, C), вільний(B)}, del = {на(A, B), вільний(C)}
Коли ви знімаєте A з B на стіл, A опиниться на столі, B звільниться, і A перестане лежати на B. Усе інше залишиться як було — і це важливо: STRIPS припускає, що дія змінює лише те, про що каже сама.
Схему зі змінними планувальник спершу заземлює: підставляє замість змінних усі комбінації кубиків. Для n кубиків є n(n − 1) дій «на стіл», стільки ж «зі столу» і n(n − 1)(n − 2) перенесень, разом n2(n − 1). Для трьох кубиків це 9 · 2 = 18 дій, для шести 36 · 5 = 180. Проте в кожному стані застосовні лише кілька з них — кубиків, на яких нічого не лежить, мало.
Світ виглядає як іграшка, але росте швидко. Три кубики можна розставити 13 способами, чотири — 73, п’ять — 501, а шість — уже 4 051. Звідки ці числа:
кількість станів = ∑k=1n n! · C(n − 1, k − 1) / k!
- n!
- вишикуємо всі кубики в один ряд — кожен порядок є кандидатом; вежі з нього отримаємо розрізанням
- C(n − 1, k − 1)
- ряд розріжемо на k непорожніх веж: виберемо k − 1 з n − 1 проміжків між кубиками. Порядок усередині вежі важливий (A на B — не те саме, що B на A), тому ріжемо порядок, а не множину
- k!
- порядок веж на столі не важливий — дві вежі поруч є тим самим станом, хоч би в якому порядку ми їх виписали. Кожен стан ми, отже, порахували k! разів
n = 3: k = 1: 6 · 1 / 1 = 6, k = 2: 6 · 2 / 2 = 6, k = 3: 6 · 1 / 6 = 1 → 13
n = 4: 24 + 36 + 12 + 1 = 73
n = 6: 720 + 1 800 + 1 200 + 300 + 30 + 1 = 4 051
Планувальник порахував їх інакше — пройшов усі досяжні стани — і отримав те саме. Спробуймо дві задачі. У більшій на столі стоять дві вежі A–B–C і D–E–F (записано знизу), а ціль — єдина вежа F–E–D–C–B–A:
4. Пряме планування
Означення — пряме планування (прогресія)
Пошук у просторі станів зі стану s0: наступники стану s — це стани T(s, a) для всіх дій a, застосовних у s. Перевірка цілі: G ⊆ s.
З вихідної ситуації ви пробуєте всі ходи, з усіх результатів — знову всі ходи, доки не натрапите на ситуацію, в якій ціль виконується. Нічого нового — це BFS або A* з частини про пошук, лише над станами, записаними як множини фактів.
from collections import deque
def proved(stav, akce):
return (stav - akce.odeber) | akce.pridej
def planuj(start, cil, akce):
okraj, rodic = deque([start]), {start: None}
while okraj:
stav = okraj.popleft()
if cil <= stav: # усі цільові атоми істинні
return plan_z(rodic, stav)
for a in akce:
if a.predpoklady <= stav:
dalsi = proved(stav, a)
if dalsi not in rodic:
rodic[dalsi] = (stav, a)
okraj.append(dalsi)
На задачі з вежею пошук у ширину знаходить найкоротший план із 6 дій, але розгортає при цьому 2 132 стани — понад половину з усіх 4 051. Він-бо нічого не знає про те, в якому напрямку лежить ціль.
5. Евристики з релаксації
У частині про пошук евристику для лабіринту ми вигадали самі. Планувальник уміє обчислити її з опису дій — так, що задачу спрощує, розв’язує її і ціну спрощеного розв’язку бере за оцінку.
Означення — релаксація без вилучення
Релаксована задача виникає, коли для всіх дій покладемо del(a) = ∅. Атом, який одного разу став істинним, тоді лишається істинним назавжди.
Ціну атома p зі стану s у релаксованій задачі оцінюємо рекурсивно: c(p) = 0 для p ∈ s, інакше c(p) = мінімум за діями a з p ∈ add(a) від значення 1 + c(pre(a)), де ціна множини атомів —
- для hmax максимум цін її атомів — евристика допустима,
- для hadd сума цін її атомів — точніший орієнтир, але може переоцінити.
Евристика стану s — це ціна цільової множини G.
c(p) = mina : p ∈ add(a) [ 1 + c(pre(a)) ]
- c(p)
- оцінка, скільки дій потрібно, щоб атом p став істинним (для атомів, які вже істинні, це 0)
- min за діями з p ∈ add(a)
- атом можуть запровадити кілька дій; вибираємо найдешевшу, бо шлях планувальник обирає сам
- 1
- ціна самої дії, яка p зрештою додасть
- c(pre(a))
- ціна того, щоб дія взагалі була застосовна, — виконати всі її передумови. Тут hmax і hadd розходяться: максимум припускає, що передумови виконаються «одночасно» і їхні підготовки перекриваються, сума — що кожна коштує окремої роботи
Уявіть світ, у якому нічого не можна зіпсувати: коли кубик одного разу відкриєте, він лишиться відкритим, навіть якщо на нього щось покладете. У такому світі планувати легко, а ціна такого плану — оптимістична оцінка справжньої ціни. hmax зважає лише на найважчу підціль (обережна, ніколи не переоцінить), hadd додає всі (сміливіша, може схибити в більший бік, бо одна дія часом допомагає кільком цілям водночас).
def ceny(stav, akce, agreguj): # agreguj = max (hmax) або sum (hadd)
c = {p: 0 for p in stav}
zmena = True
while zmena: # повторюй, доки якась ціна покращується
zmena = False
for a in akce:
if all(p in c for p in a.predpoklady):
nova = 1 + agreguj(c[p] for p in a.predpoklady)
for p in a.pridej: # a.odeber ігнорується — релаксація
if nova < c.get(p, float("inf")):
c[p], zmena = nova, True
return c
def h(stav, cil, akce, agreguj):
c = ceny(stav, akce, agreguj)
return agreguj(c[g] for g in cil) # недосяжна ціль: h = ∞
Приклад — hmax і hadd для аномалії Зуссмана
Початок: C лежить на A, B стоїть поруч; ціль G = {на(A, B), на(B, C)} (задачу розберемо нижче). Ціни атомів по шарах:
ціна 0 (істинні): стіл(A), стіл(B), на(C, A), вільний(B), вільний(C)
ціна 1: на стіл(C, A) → стіл(C), вільний(A); зі столу(B, C) → на(B, C); перенести(C, A, B) → на(C, B)
ціна 2: зі столу(A, B) потребує стіл(A), вільний(A), вільний(B) з цінами 0, 1, 0
hmax: 1 + max(0, 1, 0) = 2, hadd: 1 + (0 + 1 + 0) = 2 → на(A, B) має ціну 2
hmax(s0) = max(2, 1) = 2, hadd(s0) = 2 + 1 = 3, насправді потрібно: h* = 3
hmax недооцінює, бо враховує лише найдорожчу ціль: на(A, B) коштує дві дії, але на(B, C) потребує ще власної третьої, і максимум її поглинає. Сума її врахує і тут влучає точно. На задачі з вежею це помітніше: зі старту h* = 6, hmax = 3, кількість невиконаних цілей 5, а hadd = 15 — сума враховує спільну підготовку (відкривання кубиків) для кожної цілі знову і переоцінює справжню ціну в два з половиною рази.
Теорема — hmax і кількість невиконаних цілей допустимі
Допустимість із частини про пошук: h(s) ≤ h*(s), де h*(s) — довжина найкоротшого плану з s.
hmax. Візьмімо найкоротший план a1, …, ak справжньої задачі. Він є планом і для релаксованої задачі (без вилучення на кожному кроці істинні щонайменше ті самі атоми, тож передумови лишаються виконаними). Індукцією за i: кожен атом, істинний після i дій, має cmax ≤ i — або він був істинний ще раніше, або його додала дія ai, передумови якої були істинні після i − 1 дій і тому мають ціну ≤ i − 1, отже 1 + max ≤ i. Усі цільові атоми істинні після k дій, тому hmax(s) = maxg ∈ G c(g) ≤ k = h*(s).
Кількість невиконаних цілей. Цілі світу кубиків — це атоми на(·, ·), і кожна дія додає щонайбільше один такий атом. Тож для виконання m відсутніх цілей потрібно щонайменше m дій.
Для hadd доведення не спрацьовує на останньому кроці: сума цін може бути більшою за k, бо ті самі дії плану враховуються для кількох цілей.
Три евристики на задачі з вежею, усі з алгоритмом A*:
| Пошук | Розгорнуто станів | Довжина плану |
|---|---|---|
| У ширину (без евристики) | 2 132 | 6 |
| A* з hmax | 136 | 6 |
| A* з кількістю невиконаних цілей | 19 | 6 |
| A* з hadd | 12 | 7 |
Допустима hmax знаходить оптимум і заощаджує понад 90 % роботи. Кількість невиконаних цілей у нашому світі теж допустима (кожна дія кладе лише один кубик, тож виконує щонайбільше одну ціль) і тут працює ще краще. hadd розгортає найменше, але платить за це: план має на крок більше, ніж потрібно. Жодна евристика не виграє завжди — і справжні планувальники, як-от FF чи Fast Downward, мають цілі набори евристик, які комбінують.
Як працює A* з hmax, найкраще видно на малій задачі — аномалії Зуссмана, яку розберемо двома розділами далі. Кожен рядок — один розгорнутий стан (вежі знизу); A* завжди бере стан із найменшим f = g + h, де g — кількість дій від початку, а за рівності — той, який виявив раніше:
| Крок | Розгорнутий стан | Остання дія | g | hmax | f |
|---|---|---|---|---|---|
| 1 | A–C, B | — (початок) | 0 | 2 | 2 |
| 2 | A, B, C | на стіл(C, A) | 1 | 1 | 2 |
| 3 | A, B–C | перенести(C, A, B) | 1 | 2 | 3 |
| 4 | A, C–B | зі столу(B, C) | 2 | 1 | 3 |
| 5 | C–B–A | зі столу(A, B) | 3 | 0 | 3 — ціль |
Пошук у ширину на тій самій задачі розгортає всі 13 станів, A* з hmax — лише 5. На третьому кроці він звертає не туди — кладе C на B, — бо цей стан має f = 3, так само як правильний шлях, і був виявлений раніше. Уже наступний крок це виправляє. Перший розгорнутий цільовий стан має найкоротший план — це A* гарантує, коли евристика допустима (а hmax навіть узгоджена, тож до закритих станів повертатися не треба).
6. Зворотне планування
Означення — зворотне планування (регресія)
Шукає від цілі: стан пошуку — це множина атомів g, які ще мають бути істинними (на початку g = G). Дія a релевантна для g, якщо add(a) ∩ g ≠ ∅ і del(a) ∩ g = ∅. Регресія цілі через a — це
g′ = (g ∖ add(a)) ∪ pre(a)
- g′
- що має бути істинним перед дією a, щоб після неї виконувалося g, — нова, раніша підціль
- g
- дотеперішня підціль; атоми, про які дія не згадує, мають бути істинними вже раніше, бо дія сама їх не запровадить (а завдяки умові релевантності й не знищить)
- add(a)
- віднімаємо, бо ці атоми забезпечить сама дія — раніше вони істинними бути не мусять
- pre(a)
- додаємо, бо без передумов дію виконати не можна — вони стають новими підцілями
Пошук закінчується, коли g′ ⊆ s0; план — це знайдені дії у зворотному порядку.
Ви питаєте: «Яка дія могла б дати мені те, чого я хочу, і нічого з цього не зруйнувати? І що мало б бути істинним перед нею?» Те саме міркування, що й у зворотному виведенні в попередній частині. Перевага: ви досліджуєте лише дії, пов’язані з ціллю. Недолік: ви працюєте з множинами цілей замість повних станів, а їх важче оцінювати.
Приклад — регресія цілі аномалії Зуссмана
Ціль g = {на(A, B), на(B, C)}, початок s0 = {на(C, A), стіл(A), стіл(B), вільний(B), вільний(C)}. Дії беремо від останньої:
зі столу(A, B): add = {на(A, B)} зачіпає g, del = {стіл(A), вільний(B)} — ні → релевантна
g′ = {на(B, C)} ∪ {стіл(A), вільний(A), вільний(B)}
зі столу(B, C): add = {на(B, C)}, del = {стіл(B), вільний(C)} → релевантна
g″ = {стіл(A), вільний(A), вільний(B)} ∪ {стіл(B), вільний(B), вільний(C)} = {стіл(A), стіл(B), вільний(A), вільний(B), вільний(C)}
на стіл(C, A): add = {стіл(C), вільний(A)}, del = {на(C, A)} → релевантна
g‴ = {стіл(A), стіл(B), вільний(B), вільний(C)} ∪ {на(C, A), вільний(C)} = {на(C, A), стіл(A), стіл(B), вільний(B), вільний(C)} ⊆ s0 ✓
План, прочитаний у зворотному порядку: на стіл(C, A), зі столу(B, C), зі столу(A, B). Але якби останньою дією ми спробували зі столу(B, C), отримали б підціль {на(A, B), стіл(B), вільний(B), вільний(C)} — A лежить на B, і водночас B вільний. Такого стану не існує, але регресія сама цього не розпізнає; тому зворотні планувальники потребують додаткових правил, які неможливі комбінації виявляють.
7. Аномалія Зуссмана
Найпростіша ідея, як планувати: розділити ціль на частини і виконати їх одну за одною. Часто це працює — і зазнає невдачі вже на трьох кубиках. Задача носить ім’я Джеральда Зуссмана, який розібрав її в сімдесятих роках: C лежить на A, B стоїть поруч, ціль — A на B і водночас B на C.
Найкоротший план для «A на B» знімає C і кладе A на B. Але тоді B лежить під A, і щоб покласти B на C, доведеться A знову зняти. Наприкінці виконується лише друга ціль, і це коштувало 4 кроки. Протилежний порядок закінчується так само: покладемо B на C, але цим засиплемо A під C і B — а звільнити його означає знову зруйнувати першу ціль. Цілі заважають одна одній, хоч би з якої ви почали.
Розв’язок вимагає кроки обох цілей перемежувати: спершу звільнити A (крок для першої цілі), потім B на C (друга ціль), і лише потім A на B (перша ціль). Планувальник, який шукає в просторі повних станів, знайде це сам — тут він пройшов усі 13 станів світу трьох кубиків. Планувальник, який складає часткові плани, мусить уміти їх розплутувати і знову з’єднувати. Це тема цілої родини «планування в просторі планів».
8. Ієрархічне планування
Означення — HTN
В ієрархічній мережі задач (HTN) окрім примітивних дій є й складені задачі. Кожна складена задача має один або кілька методів — послідовностей підзадач, на які її можна розкласти. Планування починається з однієї задачі найвищого рівня і розкладає її, доки не залишаться лише примітивні дії.
Ніхто не планує відпустку окремими кроками ніг. Ви розкладаєте її на «дістатися туди», «поселитися» і «повернутися»; «дістатися» — на «купити квиток» і «доїхати до вокзалу», — і лише в самому низу є конкретні дії. Методи — це перевірені рецепти, як розкласти складну річ, і завдяки ним планувальник не пробує нісенітниць.
def rozloz(ukoly, stav): # ukoly: список задач, що залишилися
if not ukoly:
return [] # усе розкладено
u, zbytek = ukoly[0], ukoly[1:]
if u.primitivni: # звичайна дія STRIPS
if not u.predpoklady <= stav:
return None
plan = rozloz(zbytek, proved(stav, u))
return None if plan is None else [u] + plan
for metoda in u.metody: # пробуй рецепти один за одним
if metoda.podminka <= stav:
plan = rozloz(metoda.podukoly + zbytek, stav)
if plan is not None:
return plan
return None # жоден метод не вдався — повернення назад
Шукають тут у просторі розкладів, а не станів: розгалуження відбувається лише за тим, який метод вибрати для складеної задачі. Задачі розкладаються в тому порядку, в якому виконуватимуться, тож планувальник завжди знає поточний стан — так само діє, наприклад, планувальник SHOP2.
Ієрархічне планування використовують там, де фахівці мають перевірені процедури: у логістиці, в керуванні виробництвом і дуже часто в комп’ютерних іграх. Екшн-гра F.E.A.R. (2005) прославилася солдатами, які планували тактику на ходу мовою в стилі STRIPS, — і з тієї самої ідеї виходять планувальники в низці інших ігор.
9. Планування в невизначеному світі
STRIPS припускає, що світ точно такий, як ми думаємо, і дії дають точно той результат, що в описі. Але справжній робот кубик упустить, а справжній сервер не відповість. Найпоширеніших відповідей на це три:
- Моніторинг і перепланування. Після кожної дії перевір, чи світ виглядає так, як очікував план. Якщо ні, сплануй знову з того, що є. Просто й напрочуд дієво (цикл наведено нижче).
- Умовні плани мають розгалуження: «якщо двері відчиняться, іди далі, інакше скористайся другим входом».
- Імовірнісне планування. Коли дія має кілька можливих результатів із заданими ймовірностями, шукаємо не послідовність дій, а стратегію для кожної ситуації — марковські процеси прийняття рішень мають у програмі серії власну частину.
def proved_s_dohledem(cil, akce, svet):
plan = planuj(svet.pozoruj(), cil, akce)
while plan:
a = plan.pop(0)
cekany = proved(svet.pozoruj(), a) # що мало б бути істинним за моделлю
svet.udelej(a)
skutecny = svet.pozoruj()
if cil <= skutecny:
return True
if skutecny != cekany: # світ повівся інакше
plan = planuj(skutecny, cil, akce) # переплануй з того, що є
return cil <= svet.pozoruj()
Причому досить стежити лише за тим, від чого залежить решта плану: коли зміниться атом, який не є передумовою жодної наступної дії і не входить до цілі, план лишається чинним. Цю економію запровадила ще система PLANEX, яка виконувала плани Шейкі (Файкс, Гарт і Нільссон, 1972).
10. Планування в AI-агентах
Мовний агент, який виправляє помилку в коді, теж планує: розписує собі завдання у список кроків, після кожного кроку дивиться на результат (тести пройшли? файл існує?) і відповідно коригує план. Це моніторинг і перепланування, лише дії — це команди в терміналі, а передумови й ефекти ніде формально не записані — модель оцінює їх із тексту.
Саме тут слабке місце. Дослідження на задачах зі світу кубиків (наприклад, набір PlanBench) показали, що мовні моделі в класичному плануванні часто помиляються, особливо коли кубики перейменувати на безглузді слова, тож модель не може спертися на завчені приклади. Новіші моделі з довшим міркуванням дають собі раду значно краще, але гарантії правильності не дають. Тому себе виправдовує поділ праці: модель перекладає задачу в PDDL або пропонує план, а класичний планувальник чи валідатор його перевіряє — кожен робить те, що вміє.
11. Підсумок
- Планування — це пошук послідовності дій до цілі; STRIPS описує стан як множину атомів, а дію — передумовами, додаваними та вилучуваними атомами.
- Пряме планування — це пошук у просторі станів; зворотне регресує ціль через релевантні дії.
- Планувальник сам обчислює евристику з релаксованої задачі без вилучення: hmax допустима, hadd швидша, але може привести до довшого плану.
- Аномалія Зуссмана показує, що підцілі не завжди можна виконувати одну за одною — кроки треба перемежувати.
- Ієрархічне планування розкладає задачі за перевіреними методами; у невизначеному світі за планом стежать і перепланують — саме це роблять сучасні AI-агенти.
Хочете помацати це власноруч? Попросіть AI-агента записати світ кубиків у PDDL і запустити на ньому вільно доступний планувальник Fast Downward — а потім перевірте, зі скількома кубиками модель упорається сама, без нього.
Маєте іншу думку? Напишіть на support@coden-app.com.