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: