MLP, MDN, IKFlow, diffusion, multi-modalne odwracanie f: Q → SE(3).
Do tej pory rozwiązywaliśmy IK przez jawne wzory matematyczne, albo zamknięte (moduł 1), albo iteracyjne (moduły 3 i 4). Teraz spróbujemy czegoś zupełnie innego: nauczyć komputer rozwiązywać IK przez pokazywanie mu tysięcy poprawnych przykładów. Zamiast programować algorytm, dostarczamy dane, a komputer sam odkrywa prawidłowości.
Wyobraź sobie, że uczysz dziecko rozpoznawać psy i koty. Nie tłumaczysz definicji („pies ma długi pysk i…"), pokazujesz tysiąc zdjęć z podpisami „pies" i „kot". Po jakimś czasie dziecko samo zaczyna rozpoznawać. Dokładnie tak działają sieci neuronowe: są to programy, które uczą się funkcji „wejście → wyjście" przez przykłady. Dla nas „wejście" = poza T, a „wyjście" = kąty q.
Zanim przejdziemy do skomplikowanych architektur, zobaczmy, jak działa najprostsza sieć neuronowa, wielowarstwowy perceptron (MLP). Animacja niżej przeprowadzi Cię przez jeden forward pass, czyli jedno przejście danych od wejścia do wyjścia. Naciśnij ▶ odtwórz albo klikaj dalej →:
Mała sieć MLP, 3 wejścia, 4 neurony ukryte, 2 wyjścia.
Naciśnij ▶ odtwórz albo dalej → żeby przejść przez forward pass krok po kroku. W każdym kroku zobaczysz, co dokładnie się dzieje.
Zauważ, w środku sieci nie ma żadnej magii. Każdy neuron to po prostu kalkulator, który robi trzy rzeczy:
tanh).Cała tajemnica sieci jest w tym, jakie dokładnie wagi siedzą przy strzałkach. Tych wag może być setki tysięcy. Nikt ich nie wpisuje ręcznie, uczą się same na danych treningowych. To jest właśnie uczenie (training).
Po lewej, wersja specjalna dla naszego zadania (poza T → kąty q). Niżej, kanoniczny schemat z literatury. Obie pokazują to samo: warstwy neuronów połączone wagami.
Trening sieci neuronowej to dokładnie ten sam problem optymalizacyjny co w module 4, tylko że tu zmiennymi są wagi, a kosztem jest średni błąd predykcji na danych treningowych:
Procedura w pseudokodzie:
Po skończonym treningu sieć, przy odrobinie szczęścia, daje sensowne predykcje także dla nowych danych, których nie widziała. To zjawisko nazywa się generalizacją. Jeśli sieć nauczyła się tylko zapamiętać dane treningowe i nie umie odpowiadać na nowe, mówimy o przetrenowaniu (overfitting).
W kroku 6 procedury napisałem „przesuń wagi w tym kierunku (SGD albo Adam)". Co to dokładnie?
SGD, Stochastic Gradient Descent, stochastyczny gradient descent. To po prostu zwykły gradient descent z modułu 4, ale z jedną sprytną modyfikacją: zamiast liczyć gradient na całym datasecie (co dla milionów przykładów zajmuje wieczność), liczymy go na jednym losowo wybranym przykładzie albo małym mini-batchu (zazwyczaj 32, 64 lub 128 przykładów):
Gradient z jednego przykładu jest niedokładnym oszacowaniem prawdziwego gradientu (stąd „stochastic", losowy), ale jest milion razy szybszy do policzenia. W praktyce: zaszumiona ścieżka mimo wszystko prowadzi do dobrego rozwiązania, a często nawet pomaga uciec z płytkich lokalnych minimów. „Wagi chodzą małymi krokami w mniej-więcej dobrym kierunku".
Adam, Adaptive Moment Estimation (Kingma & Ba, 2014). Ulepszony SGD, który adaptuje krok osobno dla każdej wagi. Pomysł:
(z dodatkową korektą obciążenia , ). Domyślne hiperparametry: , , .
Dlaczego Adam wyparł czysty SGD w deep learningu?
Kiedy SGD bywa lepszy? Dla bardzo dużych modeli i długich treningów SGD z momentum daje czasem lepszą generalizację (model „bardziej wygładzony", mniej overfitujący). Stąd np. ResNet'y w ImageNecie często trenuje się SGD-em, a transformerów (BERT, GPT), Adamem (a właściwie Adam-W, wariantem z popraw lepszą regularyzacją).
W naszej aplikacji (sekcja „Laboratorium" niżej) używamy Adama, dzięki temu trening MLP w przeglądarce jest stabilny i kończy się w kilkunastu sekundach bez ręcznego strojenia .
Pomysł: wytrenuj MLP, który dla danej pozy (6 liczb: pozycja + RPY) zwraca 6 kątów . Trenujesz na milionach par wygenerowanych losowo przez FK.
Brzmi sensownie, ale jest jeden zasadniczy problem.
Pamiętasz z modułu 2, że dla Pumy ta sama poza może być osiągnięta na do 8 sposobów (shoulder L/R, elbow U/D, wrist flip)? W datasecie treningowym ta sama poza pojawia się więc kilka razy z różnymi kątami. A funkcja kosztu MSE każe sieci „minimalizuj średni kwadrat błędu", więc sieć uczy się średniej wszystkich poprawnych odpowiedzi.
Tylko że średnia ośmiu różnych prawidłowych konfiguracji nie jest żadną prawidłową konfiguracją. To jak byś zapytał ośmiu osób o najkrótszą drogę do biura, jedna mówi „przez most", druga „przez tunel", ich „średnia" („zjedź do rzeki i zatrzymaj się w połowie") to nonsens.
Czerwona kropka to predykcja MLP, średnia dwóch niebieskich „dzwonków" prawdziwych odpowiedzi. Średnia trafia dokładnie tam, gdzie żadna z odpowiedzi nie była. To nie jest błąd implementacji ani za małej sieci, to fundamentalna pułapka uśredniania.
Mimo wad, naiwny MLP ma jedną zaletę, daje dobry punkt startowy (warm start). Pomysł hybrydy:
Sieć daje przybliżenie (błąd ~5 cm), DLS dopina do precyzji maszynowej w 2–3 iteracjach. Sieć daje szybkość, klasyczny solver, dokładność. Tak właśnie wygląda większość produkcyjnych systemów IK z neural-warmem.
Niżej, laboratorium. Trenujemy MLP od zera (bez zewnętrznych bibliotek ML, ~200 linii TypeScriptu) i porównujemy surową predykcję z hybrydą NN → DLS.
Pomysł w jednym zdaniu: zamiast jednej liczby niech sieć zwraca listę możliwych odpowiedzi z prawdopodobieństwami.
Analogia: wyobraź sobie prognozę pogody. Możliwe podejścia:
Mixture Density Network (Bishop, 1994) to właśnie sieć, która zwraca prognozę probabilistyczną. Konkretnie , parametry kilku „dzwonków" (gaussianów):
Suma wszystkich garbów daje rozkład prawdopodobieństwa , sieć mówi nie „q to 1.5", ale „q to z 50% szansy 1.5, z 50% szansy −1.5":
Po wytrenowaniu sieć z K = 8 komponentami dla Pumy naturalnie odkrywa 8 gałęzi rozwiązania, bo to jest matematycznie optymalna liczba „garbów" dla tych danych.
Jak tego używać:
Wady MDN: trening jest niestabilny (małe potknięcia inicjalizacji powodują, że wszystkie garby zlewają się w jeden); wybór K (ile garbów?) wymaga eksperymentów.
Pomysł w jednym zdaniu: nauczmy sieć przekształcać losowy szum w poprawne odpowiedzi.
Analogia: kreatywny artysta dostaje plamy atramentu z butelki (czyli losowy szum), patrzy na nie i rysuje z nich konkretne obrazy. Każdy losowy układ plam → inny prawidłowy obraz. Im lepszy artysta, tym ciekawsze i bardziej różnorodne wyniki.
IKFlow (Ames et al., 2022) jest takim artystą, siecią neuronową, która zamienia szum w poprawne kąty robota:
Po lewej, chmurka punktów z prostego gaussowskiego szumu (każdy punkt to losowy 6-wymiarowy wektor z rozkładu normalnego). Po prawej, te same punkty po przejściu przez sieć IKFlow: wszystkie spadły do jednego z wyraźnych klastrów, każdy klaster odpowiada jednej gałęzi rozwiązania IK (shoulder R i shoulder L). Sieć „wie", którą gałąź ma trafić każdy konkretny punkt szumu, bo jest warunkowa na pozie .
Praktyczne użycie:
Siła tego podejścia: jedna sieć, dowolnie wiele różnych poprawnych rozwiązań. Wyniki publikowane (KUKA, Baxter, Atlas): próbkowanie w pojedynczych ms, błąd pozycji rzędu mm.
Weźmy najprostszego robota, na którym można zobaczyć IKFlow w akcji: planarne ramię z dwoma ogniwami obrotowymi (dwa segmenty o długości 1, sterowane kątami ). Cel, czerwona kropka, możemy osiągnąć na dwa sposoby: elbow up (łokieć w górę) albo elbow down (łokieć w dół).
Niżej: dwanaście niezależnych zapytań do (symulowanej) sieci IKFlow. Dla każdego losujemy 2-wymiarowy gaussowski szum, sieć przekształca go w parę kątów. Naciśnij wylosuj, zobaczysz, że część sampli daje rozwiązanie „elbow up" (zielone), część, „elbow down" (fioletowe). Wszystkie trafiają końcówką w czerwoną kropkę:
Cel TCP (x, y) = (1.2, 0.7), czerwona kropka. Każdy mały robot poniżej to jeden sample z (uproszczonej) sieci IKFlow: bierzemy losowy gaussowski szum z, „sieć" przekształca go w parę kątów (q₁, q₂). Wszystkie roboty trafiają w ten sam cel, ale używają różnych gałęzi rozwiązania.
⚠ Uwaga: w tym demie „sieć" to skrót, używamy znaku z₁ żeby przypisać gałąź. Prawdziwa wytrenowana sieć IKFlow uczy się tych granic sama z danych. Czytaj wyjaśnienie pod demem.
To jest cała magia normalizing flow w jednym demie: jedna sieć, jedno wywołanie, ale za każdym razem inna poprawna gałąź. Z 8 gałęzi Pumy zrobiłoby się analogicznie 8 klastrów; tu mamy 2 (bo robot 2R ma tylko dwa rozwiązania). Każde losowanie z gaussa to jeden „zaczerpnięty z kapelusza" kandydat.
Praktyczne zastosowanie: w planowaniu ruchu robot sięga po przedmiot. Jeden klaster może być zablokowany kolizją z ścianą, dzięki IKFlow generujemy dziesięć kandydatów, sprawdzamy kolizje i wybieramy ten, który przejdzie. Algorytm nie musi „wiedzieć" o ścianie a priori, wystarczy że po prostu produkuje różnorodne rozwiązania.
Mogłeś zauważyć, że w demie powyżej znak bezpośrednio decyduje o gałęzi: z[0] > 0 ? "up" : "down". Wygląda to jak gdybym z góry „kazał" sieci wybrać konkretne rozwiązanie. Tak, w demie tak jest. Symuluję jedynie efekt już wytrenowanej sieci, bez całego treningu.
Prawdziwa sieć IKFlow działa inaczej:
Inaczej mówiąc: nie programujemy podziału. Podział wyłania się jako efekt uboczny minimalizacji błędu na danych. Sieć „sama odkrywa", że czasem trzeba dwóch rozwiązań, czasem ośmiu, bo dane treningowe ją do tego zmuszają.
Dlaczego nie zrobiłem prawdziwego IKFlow w demie? Wymagałby wytrenowania sieci z coupling layers (~kilkaset linii kodu + kilkanaście minut treningu na GPU + przygotowane dane). W naszym module pokazujemy ideę, efekt, który student powinien zrozumieć, bez angażowania pełnej infrastruktury ML. Naiwny MLP w sekcji „Laboratorium" niżej jest natomiast prawdziwy i trenujemy go od zera w przeglądarce.
Magia matematyczna w środku, żeby sieć była odwracalna i żeby umiała przekształcać dowolny rozkład w inny, używa się specjalnych warstw (coupling layers) z obliczalnym wyznacznikiem jakobianu. Szczegóły są dla zaawansowanych, tu wystarczy intuicja: sieć uczy się gładkiego, odwracalnego mapowania szum ↔ rozwiązania.
Implementacja IKFlow wymaga GPU i bibliotek typu FrEIA albo nflows, w naszej aplikacji pokazujemy ideę, nie pełny model. Literatura: Ames, Limb & Srinivasa, „IKFlow: Generating Diverse Inverse Kinematics Solutions", IROS 2022.
Pomysł w jednym zdaniu: zacznij od czystego szumu i stopniowo go „odszumiaj" w wiele małych kroków, aż wyłoni się prawidłowa odpowiedź.
Analogia: wyobraź sobie zaszumione, prawie nieczytelne zdjęcie. Aplikacja w telefonie powoli usuwa ziarno, krok po kroku obraz staje się wyraźniejszy. Po 50 krokach widać twarz. Diffusion models robią to samo, tylko zamiast obrazu odzyskują kąty robota.
Przesuń poniższy slider od 0 do 50. Zobaczysz, jak losowe punkty (szum) stopniowo przesuwają się ku swoim klastrom (rozwiązania IK):
Krok 0: czysty losowy szum. Sieć dostaje te kropki na wejściu.
Krok 0: czysty szum, kropki rozproszone wszędzie. Krok 50: kropki uformowały dwa wyraźne klastry, to są dwie gałęzie rozwiązania. Sieć diffusion, w każdym z 50 kroków, robi tylko jedno: zgaduje, jak trochę przesunąć każdy punkt, żeby było mniej szumu. Dziesiątki kolejnych takich małych kroków sumują się w pełny ruch z szumu do prawdziwej odpowiedzi.
Trochę bardziej namacalna wersja, sześć egzemplarzy tego samego robota 2R (dwa ogniwa obrotowe). Każdy startuje z innego losowego ułożenia (czysty szum w kątach ). Slider 0 → 50 to kroki dyfuzji. W każdym kroku model robi mały „krok odszumiania", kąty zbliżają się do jednej z dwóch poprawnych konfiguracji.
Krok 0: czysty losowy szum. Każdy z sześciu robotów ma losowe (q₁, q₂), koniec ramienia jest gdzieś w przestrzeni, ale nie na celu.
Przesuń slider od 0 do 50 i obserwuj. Krok 0: każdy robot leży inaczej, końcówki rozproszone losowo, kolory blade (model nie wie jeszcze, w którą stronę pójdzie). Mniej więcej przy kroku 20–30 widać, że pewne roboty już idą w stronę elbow up (zielone), a inne, elbow down (fioletowe). Krok 50: wszystkie sześć trafiło końcówką w czerwoną kropkę.
Kluczowa obserwacja: diffusion nie zna celu z góry, tylko przez wiele małych kroków „szlifuje" odpowiedź. To jest jak rzeźba odsłaniana z bryły kamienia: każde uderzenie dłutem usuwa odrobinę zbędnego materiału. Po dziesiątkach uderzeń wyłania się postać.
Dla Pumy z 8 gałęziami wyglądałoby to identycznie, tylko z sześcioma kątami zamiast dwóch i ośmioma możliwymi „celami" zamiast dwóch. Czas inferencji: 50 forward passów × ~0.3 ms = 15 ms na zapytanie, wolniejsze niż IKFlow, ale wciąż w czasie rzeczywistym.
Plusy:
Minusy:
Literatura: Janner et al. „Planning with Diffusion", ICML 2022; Pearce et al. „Imitating Human Behaviour with Diffusion Models", ICLR 2023.
Trenujemy najprostszą sieć MLP w czystym TypeScripcie, bez zewnętrznych bibliotek ML. Cała implementacja (~200 linii kodu) w src/lib/ml/mlp.ts: forward pass, backpropagation, optymalizator Adam. Po treningu oceniamy predykcję na bieżącej pozie i porównujemy z wynikiem hybrydy NN → DLS.
| Metoda | Idea w jednym zdaniu | Kiedy używać |
|---|---|---|
| Naiwny MLP | Sieć zwraca jedną odpowiedź | Gdy potrzebujesz tylko warm startu (potem dopinanie DLS-em) |
| MDN | Sieć zwraca rozkład gaussowski (kilka „garbów") | Gdy chcesz znać wszystkie warianty + ich prawdopodobieństwa |
| IKFlow | Sieć przekształca szum w poprawne odpowiedzi | Standard nowoczesnego learning-based IK; szybkie, multi-modalne |
| Diffusion | Stopniowe odszumianie szumu w odpowiedź | Planowanie trajektorii; gdy IKFlow nie radzi sobie z trudnymi rozkładami |
Moduł pokazuje trenowalnego MLP w przeglądarce bez GPU. W realnej dydaktyce dodatkowo: