Tutoriale how-to
Lekki agent wizyjno-językowy: jak zbudować go w weekend bez GPU
W zeszłym miesiącu zespół z firmy [Tooploox](https://tooploox.com/) z Wrocławia zbudował prototyp agenta do sortowania paczek w magazynie. Zamiast drogiego s…
# Lekki agent wizyjno-językowy: jak zbudować go w weekend bez GPU
W zeszłym miesiącu zespół z firmy [Tooploox](https://tooploox.com/) z Wrocławia zbudował prototyp agenta do sortowania paczek w magazynie. Zamiast drogiego symulatora 3D, użyli NumPy i renderowali środowisko jako prostą siatkę pikseli. Efekt? Pierwsze testy na prawdziwych zdjęciach z kamer magazynowych zajęły im 48 godzin, a nie 4 tygodnie. Oto jak powtórzyć ich eksperyment — krok po kroku, bez frameworków za milion dolarów.
## Dlaczego proste symulacje grid world mogą przyspieszyć rozwój agentów embodied AI?
Grid world to nie tylko zabawa dla studentów. Gdy OpenAI trenowało swoje roboty do manipulacji obiektami, pierwsze 1000 iteracji algorytmu DQN przeprowadziło właśnie w takim środowisku [4]. Dlaczego? Bo renderowanie klatek RGB w NumPy zamiast w Unreal Engine zmniejsza zapotrzebowanie na moc obliczeniową **o 95%** — z 32 GB VRAM do 1,5 GB RAM na zwykłym laptopie [1].
### Przykład: Jak NumPy-renderowany grid world redukuje złożoność obliczeniową
W typowym symulatorze fizyki, jak MuJoCo, każda klatka wymaga obliczenia kolizji, oświetlenia i tekstur. W grid world wystarczy tablica NumPy o wymiarach 64x64x3 (szerokość x wysokość x RGB). To **49 152 wartości** zamiast milionów trójkątów. Przy 30 FPS, symulacja 10-minutowego zadania generuje 18 000 klatek — czyli zaledwie 843 MB danych. Dla porównania: jedna sekunda wideo 4K to już 1 GB.
### Czym różni się obserwacja RGB od symbolicznych stanów?
Symboliczne stany to abstrakcje: "agent jest w polu (3,4), cel w (7,2)". Problem? W prawdziwym świecie nie ma etykiet. Kamera w magazynie widzi tylko piksele — i to z nich agent musi wywnioskować, gdzie jest paczka. Trenowanie na surowych pikselach od początku **zwiększa odporność modelu na zakłócenia** (np. zmiany oświetlenia) [4]. W testach OpenAI, agenci trenowani na RGB radzili sobie **o 30% lepiej** w nieznanych środowiskach niż ci uczący się na symbolicznych stanach.
### Case study: Uproszczony pipeline Vision-Language-Action w akcji
W tutorialu [MarkTechPost](https://www.marktechpost.com/) [1] pokazano agenta, który:
1. Otrzymuje klatkę RGB 64x64 z grid world (NumPy).
2. Przetwarza ją przez encoder do 32-wymiarowego latent space.
3. Przewiduje kolejne 5 stanów w latent space (model świata).
4. Używa MPC do wyboru akcji (np. "idź w prawo").
Cały pipeline działa na CPU i generuje **12 klatek na sekundę** na Intel i5 — wystarczająco, by trenować agenta w czasie rzeczywistym.
## Jak przygotować środowisko grid world z obserwacjami RGB w NumPy?
Zaczynamy od zera. Potrzebujesz tylko NumPy, OpenCV (do wyświetlania) i PyTorch (do modelu). Żadnych Unity, ROS czy CUDA.
### Krok po kroku: Tworzenie siatki i renderowanie klatek RGB
1. **Definiujemy siatkę**:
```python
import numpy as np
grid_size = 10 # 10x10 pól
grid = np.zeros((grid_size, grid_size, 3), dtype=np.uint8) # RGB
```
2. **Rysujemy agenta i cel**:
```python
agent_pos = (2, 3)
goal_pos = (8, 7)
grid[agent_pos] = [255, 0, 0] # Czerwony agent
grid[goal_pos] = [0, 255, 0] # Zielony cel
```
3. **Dodajemy przeszkody** (opcjonalnie):
```python
obstacles = [(4, 5), (5, 5), (6, 5)]
for pos in obstacles:
grid[pos] = [128, 128, 128] # Szare przeszkody
```
4. **Renderujemy klatkę** (upscale do 64x64 dla modelu):
```python
import cv2
frame = cv2.resize(grid, (64, 64), interpolation=cv2.INTER_NEAREST)
cv2.imshow("Grid World", frame)
cv2.waitKey(100) # 10 FPS
```
### Dlaczego warto unikać symbolicznych stanów na rzecz surowych pikseli?
Symboliczne stany wymagają ręcznego projektowania: "jeśli agent jest w (x,y) i widzi przeszkodę w (x+1,y), to...". W grid world 10x10 to 100 możliwych stanów. Ale co, jeśli dodamy kolory? Albo dynamiczne przeszkody? Liczba stanów rośnie wykładniczo. Piksele rozwiązują ten problem — model sam uczy się reprezentacji. W eksperymencie z [Towards Data Science](https://towardsdatascience.com/) [5], agent trenowany na pikselach nauczył się omijać przeszkody **po 2 godzinach**, podczas gdy ten na symbolicznych stanach potrzebował **12 godzin**.
### Narzędzia i biblioteki: Co będzie potrzebne
- **NumPy**: Do renderowania i manipulacji klatkami. Wersja 1.24+ wystarczy.
- **OpenCV**: Tylko do wyświetlania (opcjonalnie). Można zastąpić matplotlib.
- **PyTorch**: Do budowy modelu świata i MPC. Wersja 2.0+ (dla lepszej optymalizacji).
- **Środowisko**: Python 3.10+ na dowolnym systemie. Nie potrzebujesz GPU.
## Jak zbudować i wytrenować lekki model świata w latent space?
Model świata to serce agenta. Zamiast przewidywać przyszłe klatki RGB (co jest kosztowne), przewiduje je w **latent space** — niskowymiarowej przestrzeni, gdzie każdy punkt reprezentuje stan środowiska.
### Architektura modelu: Encoder, latent dynamics i decoder
1. **Encoder** (CNN):
- Wejście: Klatka RGB 64x64x3.
- Wyjście: Wektor latent o wymiarze 32.
- Przykład architektury:
```python
import torch.nn as nn
encoder = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, stride=2), # 64x64 -> 31x31
nn.ReLU(),
nn.Conv2d(16, 32, kernel_size=3, stride=2), # 31x31 -> 15x15
nn.ReLU(),
nn.Flatten(),
nn.Linear(32 * 15 * 15, 32) # Latent space
)
```
2. **Latent dynamics** (RNN lub MLP):
- Wejście: Aktualny latent + akcja (np. "idź w prawo").
- Wyjście: Przewidywany latent dla kolejnego stanu.
- Przykład (MLP):
```python
dynamics = nn.Sequential(
nn.Linear(32 + 4, 64), # 32 (latent) + 4 (one-hot akcja)
nn.ReLU(),
nn.Linear(64, 32)
)
```
3. **Decoder** (CNN):
- Wejście: Latent.
- Wyjście: Rekonstrukcja klatki RGB 64x64x3.
- Przykład:
```python
decoder = nn.Sequential(
nn.Linear(32, 32 * 15 * 15),
nn.Unflatten(1, (32, 15, 15)),
nn.ConvTranspose2d(32, 16, kernel_size=3, stride=2), # 15x15 -> 31x31
nn.ReLU(),
nn.ConvTranspose2d(16, 3, kernel_size=3, stride=2), # 31x31 -> 64x64
nn.Sigmoid() # RGB w [0,1]
)
```
### Dlaczego latent world modeling redukuje wymagania obliczeniowe?
Przewidywanie przyszłych klatek RGB wymagałoby generowania 64x64x3 = **12 288 wartości** na klatkę. W latent space wystarczy 32 wartości. To **384 razy mniej danych**. W testach z [arXiv](https://arxiv.org/abs/2301.04104) [2], agenci z latent world models trenowali się **5 razy szybciej** niż te przewidujące surowy RGB, przy porównywalnej skuteczności.
### Przykładowy kod: Implementacja modelu w PyTorch
Pełny trening modelu świata (encoder + dynamics + decoder) wygląda tak:
import torch.optim as optim
model = nn.ModuleDict({
"encoder": encoder,
"dynamics": dynamics,
"decoder": decoder
})
optimizer = optim.Adam(model.parameters(), lr=1e-3)
for epoch in range(100):
for batch in dataloader: # Batch klatek RGB i akcji
# Forward pass
latent = model"encoder"
next_latent_pred = model"dynamics")
frame_recon = model"decoder"
# Loss: rekonstrukcja + przewidywanie
loss = nn.MSELoss()(frame_recon, batch["next_frame"])
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
AN