
Imagínate pedirle a un robot: "Oye, recoge la taza roja de la cocina y tráela aquí".
Suena simple, ¿no? Pero para la IA esto implica comprender el lenguaje, navegar en un espacio, reconocer objetos y brindar retroalimentación, todo en tiempo real.
Esto es exactamente lo que abordé en el Alexa Prize SimBot Challenge, donde construimos un agente conversacional encarnado que podía entender instrucciones, moverse a través de su entorno, interactuar con objetos y comunicarse.
Así es como lo hicimos funcionar con BERT, aprendizaje de refuerzo y aprendizaje automático multimodal. Repasemos los diferentes problemas y cómo abordamos cada uno de ellos.
El lenguaje natural es confuso y puede volverse muy complicado. Los humanos decimos Ve al frigorífico, pero también podríamos decir Encuentra el frigorífico y ábrelo. Un robot debe extraer el significado de diferentes frases.
Para ello, utilizamos BERT (Bidirectional Encoder Representations from Transformers) para convertir instrucciones de texto en comandos estructurados, de modo que sea más fácil ejecutarlos de forma secuencial.
Cómo funciona
A continuación se muestra el núcleo de nuestro analizador de instrucciones basado en BERT:
import torch import torch.nn as nn import torch.optim as optim from transformers import BertTokenizer, BertModel class InstructionEncoder(nn.Module): """ Fine-tunes BERT on domain-specific instructions, outputs a command distribution. """ def __init__(self, num_commands=10, dropout=0.1): super(InstructionEncoder, self).__init__() self.bert = BertModel.from_pretrained("bert-base-uncased") self.dropout = nn.Dropout(dropout) self.classifier = nn.Linear(self.bert.config.hidden_size, num_commands) def forward(self, input_ids, attention_mask): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) pooled_output = outputs.pooler_output pooled_output = self.dropout(pooled_output) logits = self.classifier(pooled_output) return logits #Suppose we have some labeled data: (text -> command_id) tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") model = InstructionEncoder(num_commands=12) model.train() instructions = ["Go to the fridge", "Pick up the red cup", "Turn left"] labels = [2, 5, 1] input_encodings = tokenizer(instructions, padding=True, truncation=True, return_tensors="pt") labels_tensor = torch.tensor(labels) optimizer = optim.AdamW(model.parameters(), lr=1e-5) criterion = nn.CrossEntropyLoss()
Una vez que el robot entiende a dónde ir, necesita una manera de llegar allí. Usamos la búsqueda A * para entornos estructurados (como mapas) y el aprendizaje por refuerzo (RL) para espacios dinámicos .
Así es como implementamos nuestra implementación de búsqueda A* para la búsqueda de rutas.
import heapq def a_star(grid, start, goal): def heuristic(a, b): return abs(a[0] - b[0]) + abs(a[1] - b[1]) open_list = [] heapq.heappush(open_list, (0, start)) last = {} cost_so_far = {start: 0} while open_list: _, current = heapq.heappop(open_list) if current == goal: break for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: #4 directions neighbor = (current[0] + dx, current[1] + dy) if neighbor in grid: #Check if it's a valid position new_cost = cost_so_far[current] + 1 if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: cost_so_far[neighbor] = new_cost priority = new_cost + heuristic(goal, neighbor) heapq.heappush(open_list, (priority, neighbor)) last[neighbor] = current return last
Y esta es la implementación de cómo usamos RL para el movimiento dinámico.
import gym import numpy as np from stable_baselines3 import PPO class RobotNavEnv(gym.Env): """ A simplified environment mixing a partial grid with dynamic obstacles. Observations might include LiDAR scans or collision sensors. """ def __init__(self): super(RobotNavEnv, self).__init__() self.observation_space = gym.spaces.Box(low=0, high=1, shape=(360,), dtype=np.float32) self.action_space = gym.spaces.Discrete(3) self.state = np.zeros((360,), dtype=np.float32) def reset(self): self.state = np.random.rand(360).astype(np.float32) return self.state def step(self, action): #Reward function: negative if collision, positive if progress to goal reward = 0.0 done = False if action == 2 and np.random.rand() < 0.1: reward = -5.0 done = True else: reward = 1.0 self.state = np.random.rand(360).astype(np.float32) return self.state, reward, done, {} env = RobotNavEnv() model = PPO("MlpPolicy", env, verbose=1).learn(total_timesteps=5000)
Una vez en el destino, el robot debe ver objetos e interactuar con ellos, lo que requiere visión artificial para la localización de los objetos.
Entrenamos un modelo YOLOv8 para reconocer objetos como tazas, puertas y electrodomésticos.
import torch from ultralytics import YOLO import numpy as np #load a base YOLOv8 model model = YOLO("yolov8s.pt") #embeddings object_categories = { "cup": np.array([0.22, 0.88, 0.53]), "mug": np.array([0.21, 0.85, 0.50]), "bottle": np.array([0.75, 0.10, 0.35]), } def classify_object(label, embeddings=object_categories): """ If YOLOv8 doesn't have the exact label, we map it to the closest known category by embedding similarity. """ if label in embeddings: return label else: best_label = None best_sim = -1 for cat, emb in embeddings.items(): sim = np.random.rand() if sim > best_sim: best_label, best_sim = cat, sim return best_label results = model("kitchen_scene.jpg") for r in results: for box, cls_id in zip(r.boxes.xyxy, r.boxes.cls): label = r.names[int(cls_id)] mapped_label = classify_object(label)
Ahora que el robot:
Debe comprender cómo responder al usuario. Este ciclo de retroalimentación también ayuda en la experiencia del usuario; para lograrlo, utilizamos una generación de texto basada en GPT para respuestas dinámicas.
from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("EleutherAI/gpt-j-6B") model_gpt = AutoModelForCausalLM.from_pretrained("EleutherAI/gpt-j-6B").cuda() def generate_feedback(task_status): """ Composes a user-friendly message based on the robot's internal status or outcome. """ prompt = (f"You are a helpful home robot. A user gave you a task. Current status: {task_status}.\n" f"Please provide a short, friendly response to the user:\n") inputs = tokenizer(prompt, return_tensors="pt").to("cuda") outputs = model_gpt.generate(**inputs, max_length=60, do_sample=True, temperature=0.7) response_text = tokenizer.decode(outputs[0], skip_special_tokens=True) return response_text.split("\n")[-1] print(generate_feedback("I have arrived at the kitchen. I see a red cup."))
La sinergia del procesamiento del lenguaje natural avanzado, la planificación de rutas sólida, la detección de objetos en tiempo real y el lenguaje generativo ha abierto una nueva frontera en la robótica colaborativa. Nuestros agentes pueden interpretar comandos con matices, navegar en entornos dinámicos, identificar objetos con una precisión notable y brindar respuestas que parecen naturales.
Más allá de la simple ejecución de tareas, estos robots mantienen una auténtica comunicación de ida y vuelta, haciendo preguntas aclaratorias, explicando acciones y adaptándose sobre la marcha. Es un atisbo de un futuro en el que las máquinas hacen más que servir: colaboran, aprenden y conversan como verdaderos socios en nuestras rutinas diarias.