4. VLA策略网络生成动作指令
任务说明
本案例实现一个视觉-语言-动作(VLA)策略网络的核心推理路径:输入一张仿真环境图像和一条自然语言指令,网络输出对应的离散动作指令(例如前进、左转、抓取等)。重点在于构建跨模态融合模块与动作解码头,并验证在给定输入下网络能产生与指令语义一致的动作分布。
环境依赖
需要 Python 3.9+、PyTorch 2.0+、torchvision、transformers(仅用于加载预训练CLIP文本编码器)。若本机无预训练权重,代码会自动降级为随机初始化文本编码器,不影响网络结构验证。
核心实现
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import CLIPTextModel, CLIPTokenizer
class VLA_Policy(nn.Module):
"""视觉-语言-动作策略网络:图像+文本 → 离散动作分布"""
def __init__(self, num_actions=6, embed_dim=256, text_dim=512):
super().__init__()
# 视觉编码器(轻量CNN,输入3x64x64)
self.vision_encoder = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=5, stride=2, padding=2), # 32x32x32
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1), # 64x16x16
nn.ReLU(),
nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),# 128x8x8
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)), # 128x1x1
nn.Flatten(), # 128
nn.Linear(128, embed_dim)
)
# 文本编码器(使用预训练CLIP,若加载失败则随机初始化)
try:
self.text_encoder = CLIPTextModel.from_pretrained("/clip-vit-base-patch32")
self.text_proj = nn.Linear(512, embed_dim)
self.use_pretrained = True
except Exception:
self.text_encoder = nn.LSTM(input_size=text_dim, hidden_size=embed_dim,
batch_first=True, bidirectional=True)
self.text_proj = nn.Linear(2*embed_dim, embed_dim)
self.use_pretrained = False
# 跨模态融合 + 动作解码
self.fusion_gate = nn.Linear(embed_dim*2, embed_dim)
self.action_head = nn.Linear(embed_dim, num_actions)
def encode_text(self, text_list, device):
"""将文本列表编码为embedding向量"""
if self.use_pretrained:
tokenizer = CLIPTokenizer.from_pretrained("/clip-vit-base-patch32")
inputs = tokenizer(text_list, return_tensors="pt", padding=True,
truncation=True, max_length=32).to(device)
with torch.no_grad():
last_hidden = self.text_encoder(**inputs).last_hidden_state
# 取[CLS]位置向量
text_feat = last_hidden[:, 0, :]
else:
# 随机初始化LSTM回退方案:将字符映射为向量
char_embed = nn.Embedding(128, text_dim).to(device)
seqs = []
for t in text_list:
seqs.append(torch.tensor([min(ord(c), 127) for c in t], device=device))
padded = nn.utils.rnn.pad_sequence(seqs, batch_first=True)
emb = char_embed(padded)
_, (h_n, _) = self.text_encoder(emb)
text_feat = torch.cat([h_n[0], h_n[1]], dim=1) # 双向拼接
return self.text_proj(text_feat)
def forward(self, image, text_list):
"""
image: (B, 3, 64, 64) 像素值归一化到[0,1]
text_list: 长度为B的字符串列表
返回: (B, num_actions) 动作logits
"""
device = image.device
vis_feat = self.vision_encoder(image) # (B, embed_dim)
txt_feat = self.encode_text(text_list, device) # (B, embed_dim)
# 门控融合
combined = torch.cat([vis_feat, txt_feat], dim=1) # (B, 2*embed_dim)
gate = torch.sigmoid(self.fusion_gate(combined)) # (B, embed_dim)
fused = gate * vis_feat + (1 - gate) * txt_feat # (B, embed_dim)
logits = self.action_head(fused) # (B, num_actions)
return logits
# 动作标签映射(0-5)
ACTION_NAMES = ["前进", "左转", "右转", "抓取", "放下", "停止"]
def generate_action(net, image_tensor, instruction):
"""输入单张图像和指令,返回动作名称"""
net.eval()
with torch.no_grad():
logits = net(image_tensor.unsqueeze(0), [instruction])
action_id = torch.argmax(logits, dim=1).item()
return ACTION_NAMES[action_id]
运行与验证
将上述代码保存为 vla_policy.py,然后运行以下验证脚本:
import torch
from vla_policy import VLA_Policy, generate_action, ACTION_NAMES
# 初始化网络
net = VLA_Policy(num_actions=6)
# 构造模拟输入:3通道64x64随机图像(实际场景中为仿真器渲染帧)
dummy_image = torch.rand(3, 64, 64)
# 测试指令
test_instructions = ["请向前移动", "向左转", "抓取红色物体"]
for instr in test_instructions:
action = generate_action(net, dummy_image, instr)
print(f"指令: {instr} → 动作: {action}")
# 验证网络输出维度正确
batch_images = torch.rand(4, 3, 64, 64)
batch_texts = ["前进", "左转", "右转", "停止"]
logits = net(batch_images, batch_texts)
assert logits.shape == (4, 6), f"输出维度错误: {logits.shape}"
print("维度验证通过: (4, 6)")
通过条件:
- 脚本无异常运行结束,输出4条动作预测结果(由于随机初始化,动作内容不要求语义正确);
- 维度断言通过,即网络对批量输入返回
(batch_size, 6)的logits张量; - 若要验证语义对齐能力,需加载预训练权重并输入真实仿真图像,观察输出动作是否与指令匹配——本案例仅验证网络结构与前向通路正确性。
关键说明
本实现采用门控融合机制,视觉特征与文本特征通过可学习的门控权重动态组合,动作头输出6类离散动作的logits。预训练CLIP文本编码器提供语义理解能力;若网络不可用则自动回退到字符级LSTM,保证代码在任何环境可运行。实际部署时需将图像替换为仿真器真实观测,并配合行为克隆损失进行端到端训练。