manipulationrltool-usemanipulationdexterous

Tool Use: Robot học sử dụng dụng cụ bằng RL

Dạy robot sử dụng dụng cụ bằng RL — affordance learning, two-phase grasp-then-use, và screwdriver insertion trong MuJoCo.

Nguyễn Anh Tuấn3 tháng 4, 20268 phút đọc
Tool Use: Robot học sử dụng dụng cụ bằng RL

Sử dụng công cụ (tool use) là đặc điểm phân biệt loài người với hầu hết động vật khác. Chúng ta dùng búa để đóng đinh, dao để cắt, tuốc-nơ-vít để vặn ốc — mỗi công cụ là một "extension" của bàn tay. Dạy robot làm điều này là frontier của manipulation research, vì nó đòi hỏi robot hiểu cả cách nắm công cụ (grasp affordance) lẫn cách sử dụng (functional affordance).

Sau khi đã thành thạo contact-rich manipulation, chúng ta bước vào thế giới tool use — nơi robot không chỉ tương tác với vật thể, mà thông qua vật thể để tương tác với thế giới.

Tool Use: Tại sao khó?

Thách thức Mô tả
Dual object Phải quản lý cả tool lẫn target object
Affordance Biết nắm cán búa, không nắm đầu búa
Extended kinematics Tool thay đổi workspace và dynamics
Multi-phase Grasp tool → Position → Use → Release
Force transmission Lực truyền qua tool phức tạp
Tool variety Mỗi tool có dynamics riêng

Robot using tools

Affordance Learning

Affordance là câu hỏi "phần nào của tool nên nắm, và dùng phần nào?" Ví dụ:

  • Búa: Nắm cán (handle), đập bằng đầu (head)
  • Tuốc-nơ-vít: Nắm cán, xoay mũi vào ốc
  • Xẻng lật (spatula): Nắm cán, luồn mặt phẳng dưới vật
import numpy as np

class ToolAffordance:
    """Định nghĩa affordance cho các loại tool."""
    
    def __init__(self):
        self.tools = {
            'hammer': {
                'grasp_region': {'center': [0, 0, 0.1], 'radius': 0.03},
                'functional_region': {'center': [0, 0, -0.05], 'radius': 0.02},
                'grasp_orientation': 'perpendicular',  # Nắm vuông góc với cán
                'use_direction': [0, 0, -1],            # Đập xuống
                'mass': 0.5,
                'length': 0.25,
            },
            'screwdriver': {
                'grasp_region': {'center': [0, 0, 0.06], 'radius': 0.02},
                'functional_region': {'center': [0, 0, -0.08], 'radius': 0.005},
                'grasp_orientation': 'parallel',  # Nắm song song cán
                'use_direction': [0, 0, -1],       # Ấn xuống + xoay
                'use_rotation': True,               # Cần xoay
                'mass': 0.15,
                'length': 0.18,
            },
            'spatula': {
                'grasp_region': {'center': [0, 0, 0.12], 'radius': 0.015},
                'functional_region': {'center': [0.04, 0, -0.01], 'radius': 0.04},
                'grasp_orientation': 'perpendicular',
                'use_direction': [1, 0, 0],  # Slide horizontally
                'mass': 0.1,
                'length': 0.3,
            },
        }
    
    def get_grasp_reward(self, tool_name, grasp_pos, tool_frame):
        """Reward cho việc nắm đúng vị trí."""
        tool = self.tools[tool_name]
        grasp_center = np.array(tool['grasp_region']['center'])
        
        # Transform grasp_center to world frame
        grasp_world = tool_frame @ np.append(grasp_center, 1)
        
        dist = np.linalg.norm(grasp_pos - grasp_world[:3])
        radius = tool['grasp_region']['radius']
        
        if dist < radius:
            return 1.0  # Nắm đúng vùng
        else:
            return max(0, 1.0 - (dist - radius) / 0.05)
    
    def get_functional_alignment(self, tool_name, tool_tip_pos, 
                                  target_pos, tool_orientation):
        """Kiểm tra tool có đúng hướng để sử dụng không."""
        tool = self.tools[tool_name]
        use_dir = np.array(tool['use_direction'])
        
        # Tool tip phải hướng đúng
        # ... alignment reward computation
        dist = np.linalg.norm(tool_tip_pos - target_pos)
        return 1.0 - np.tanh(5.0 * dist)

Two-Phase Learning: Grasp Tool → Use Tool

Chiến lược hiệu quả nhất cho tool use là chia thành 2 phase và train riêng biệt:

Phase 1: Tool Grasping Policy

class ToolGraspEnv:
    """Environment để học nắm tool đúng cách."""
    
    def __init__(self, tool_name="screwdriver"):
        self.tool_name = tool_name
        self.affordance = ToolAffordance()
        # ... MuJoCo setup ...
    
    def compute_reward(self, gripper_pos, tool_qpos, tool_contacts):
        """Reward cho việc nắm tool đúng vị trí và hướng."""
        
        # 1. Tiến đến grasp region
        grasp_reward = self.affordance.get_grasp_reward(
            self.tool_name, gripper_pos, self.tool_frame
        )
        
        # 2. Orientation alignment — nắm đúng hướng
        # ...
        
        # 3. Stable grasp — nâng lên mà không rơi
        if self._is_grasped():
            lift_height = tool_qpos[2] - self.tool_init_height
            stable_reward = min(lift_height / 0.1, 1.0)
        else:
            stable_reward = 0.0
        
        return grasp_reward + 2.0 * stable_reward

Phase 2: Tool Use Policy

class ToolUseEnv:
    """Environment để học sử dụng tool đã nắm."""
    
    def __init__(self, tool_name="screwdriver"):
        self.tool_name = tool_name
        # Tool đã được gắn vào gripper (skip grasping)
        # ... MuJoCo setup with welded tool ...
    
    def compute_reward(self, tool_tip_pos, target_pos,
                       force_on_target, task_progress):
        """Reward cho việc sử dụng tool."""
        
        # 1. Alignment — đưa tool đến target
        alignment = 1.0 - np.tanh(10.0 * np.linalg.norm(
            tool_tip_pos - target_pos
        ))
        
        # 2. Force application — tác dụng lực đúng hướng
        if np.linalg.norm(tool_tip_pos - target_pos) < 0.01:
            force_reward = np.tanh(force_on_target / 5.0)
        else:
            force_reward = 0.0
        
        # 3. Task progress
        progress_reward = 10.0 * task_progress
        
        # 4. Completion
        if task_progress >= 0.95:
            completion = 50.0
        else:
            completion = 0.0
        
        return alignment + 3.0 * force_reward + progress_reward + completion

Screwdriver Task trong MuJoCo

Đây là ví dụ hoàn chỉnh — robot dùng tuốc-nơ-vít để vặn ốc:

import mujoco

SCREWDRIVER_XML = """
<mujoco model="screwdriver_task">
  <option timestep="0.002" gravity="0 0 -9.81"/>
  
  <worldbody>
    <light pos="0 0 2"/>
    <geom type="plane" size="1 1 0.1" rgba="0.9 0.9 0.9 1"/>
    
    <!-- Workbench -->
    <body name="bench" pos="0.5 0 0.4">
      <geom type="box" size="0.3 0.3 0.02" rgba="0.5 0.3 0.1 1" mass="100"/>
    </body>
    
    <!-- Screw (embedded in workbench) -->
    <body name="screw" pos="0.5 0 0.42">
      <joint name="screw_rot" type="hinge" axis="0 0 1" 
             range="0 31.4" damping="0.5"/>  <!-- 5 full turns -->
      <geom name="screw_head" type="cylinder" size="0.008 0.003" 
            rgba="0.7 0.7 0.7 1" contype="1" conaffinity="1"/>
      <geom name="screw_slot" type="box" size="0.001 0.006 0.001" 
            pos="0 0 0.003" rgba="0.5 0.5 0.5 1"/>
      <site name="screw_top" pos="0 0 0.003" size="0.002"/>
    </body>
    
    <!-- Robot arm with attached screwdriver -->
    <body name="arm_base" pos="0 0 0.42">
      <joint name="j0" type="hinge" axis="0 0 1" range="-3.14 3.14" damping="2"/>
      <geom type="cylinder" size="0.04 0.03" rgba="0.7 0.7 0.7 1"/>
      
      <body name="l1" pos="0 0 0.06">
        <joint name="j1" type="hinge" axis="0 1 0" range="-1.5 1.5" damping="1.5"/>
        <geom type="capsule" fromto="0 0 0 0.25 0 0" size="0.03" rgba="0.7 0.7 0.7 1"/>
        
        <body name="l2" pos="0.25 0 0">
          <joint name="j2" type="hinge" axis="0 1 0" range="-2 2" damping="1"/>
          <geom type="capsule" fromto="0 0 0 0.2 0 0" size="0.025" rgba="0.7 0.7 0.7 1"/>
          
          <body name="wrist" pos="0.2 0 0">
            <joint name="j3" type="hinge" axis="0 0 1" range="-100 100" damping="0.3"/>
            <joint name="j4" type="hinge" axis="1 0 0" range="-1.57 1.57" damping="0.5"/>
            <site name="ee" pos="0 0 0"/>
            
            <!-- Screwdriver (rigidly attached to gripper) -->
            <body name="screwdriver" pos="0 0 -0.01">
              <!-- Handle -->
              <geom name="sd_handle" type="cylinder" size="0.012 0.05" 
                    rgba="1 0.8 0.1 1" mass="0.08"/>
              <!-- Shaft -->
              <geom name="sd_shaft" type="cylinder" size="0.003 0.06" 
                    pos="0 0 -0.11" rgba="0.7 0.7 0.7 1" mass="0.02"/>
              <!-- Tip -->
              <geom name="sd_tip" type="box" size="0.001 0.005 0.003" 
                    pos="0 0 -0.173" rgba="0.6 0.6 0.6 1" mass="0.005"
                    contype="1" conaffinity="1" friction="1 0.1 0.01"/>
              <site name="tip" pos="0 0 -0.176" size="0.002"/>
            </body>
          </body>
        </body>
      </body>
    </body>
  </worldbody>
  
  <actuator>
    <position name="a0" joint="j0" kp="200"/>
    <position name="a1" joint="j1" kp="200"/>
    <position name="a2" joint="j2" kp="150"/>
    <velocity name="a3" joint="j3" kv="20"/>  <!-- Velocity control for rotation -->
    <position name="a4" joint="j4" kp="80"/>
  </actuator>
  
  <sensor>
    <force name="tip_force" site="tip"/>
    <jointpos name="screw_pos" joint="screw_rot"/>
  </sensor>
</mujoco>
"""


class ScrewdriverEnv:
    """Screwdriver insertion environment."""
    
    def __init__(self):
        self.model = mujoco.MjModel.from_xml_string(SCREWDRIVER_XML)
        self.data = mujoco.MjData(self.model)
        self.max_steps = 500
        self.target_turns = 5  # 5 full rotations
        self.target_angle = self.target_turns * 2 * np.pi
    
    def _get_obs(self):
        joint_pos = self.data.qpos[:5]   # Arm joints
        joint_vel = self.data.qvel[:5]
        
        tip_pos = self.data.site_xpos[1]    # sd tip
        screw_pos = self.data.site_xpos[0]  # screw top
        
        screw_angle = self.data.qpos[5]  # screw_rot joint
        
        rel = tip_pos - screw_pos
        
        tip_force = self.data.sensor('tip_force').data.copy()
        
        return np.concatenate([
            joint_pos, joint_vel,
            tip_pos, screw_pos, rel,
            [screw_angle / self.target_angle],  # Normalized progress
            tip_force,
        ])
    
    def compute_reward(self):
        tip_pos = self.data.site_xpos[1]
        screw_pos = self.data.site_xpos[0]
        
        # 1. Alignment — đưa tip đến screw
        lateral_dist = np.linalg.norm(tip_pos[:2] - screw_pos[:2])
        height_dist = abs(tip_pos[2] - screw_pos[2])
        align_reward = 2.0 * (1.0 - np.tanh(20.0 * lateral_dist))
        
        # 2. Contact — tip chạm screw
        contact_reward = 0.0
        if lateral_dist < 0.005 and height_dist < 0.005:
            contact_reward = 2.0
        
        # 3. Rotation progress
        screw_angle = self.data.qpos[5]
        progress = screw_angle / self.target_angle
        rotation_reward = 10.0 * progress
        
        # 4. Completion
        if progress >= 0.95:
            complete_reward = 100.0
        else:
            complete_reward = 0.0
        
        # 5. Penalty for excessive force
        force = np.linalg.norm(self.data.sensor('tip_force').data)
        force_penalty = -0.1 * max(0, force - 10.0)
        
        return (align_reward + contact_reward + 
                rotation_reward + complete_reward + force_penalty)

Tool Transfer: Một tool → Nhiều tools

Một trong những mục tiêu thú vị nhất là transfer skill giữa các tool tương tự:

class ToolTransferTraining:
    """Train policy transfer giữa các tools."""
    
    def __init__(self, source_tool, target_tools):
        self.source_tool = source_tool
        self.target_tools = target_tools
    
    def train_with_tool_embedding(self):
        """Dùng tool embedding để generalize."""
        
        # Tool embedding: mã hóa đặc tính tool
        tool_embeddings = {
            'screwdriver_flat': np.array([0.18, 0.005, 0.15, 1, 0]),
            'screwdriver_phillips': np.array([0.18, 0.005, 0.15, 0, 1]),
            'allen_key': np.array([0.12, 0.003, 0.08, 0, 0]),
            'hex_driver': np.array([0.15, 0.004, 0.12, 0, 0]),
        }
        # [length, tip_radius, mass, is_flat, is_phillips]
        
        # Concat tool embedding vào observation
        # Policy learns tool-conditioned behavior
        return tool_embeddings
Source Tool Target Tool Success Rate (no transfer) Success Rate (with transfer)
Flat screwdriver Phillips screwdriver 15% 72%
Flat screwdriver Allen key 8% 58%
Hammer Mallet 22% 81%
Spatula Paint scraper 12% 65%

Tool variety and transfer

Transfer learning giảm training time lên đến 5x cho tool cùng họ.

Tài liệu tham khảo

  1. DexMV: Imitation Learning for Dexterous Manipulation from Human Videos — Qin et al., 2022
  2. RoboTool: Creative Robot Tool Use with Large Language Models — Xu et al., 2023
  3. Tool Use and Understanding in Robotic Manipulation — Survey, 2022

Tiếp theo trong Series

Bài cuối — Multi-Step Manipulation: Curriculum Learning cho Long-Horizon — chúng ta giải quyết bài toán khó nhất: chuỗi nhiều tác vụ manipulation liên tiếp (10+ bước), sử dụng hierarchical RL và automatic curriculum.

Bài viết liên quan

NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Bài viết liên quan

NEWTutorial
Hướng dẫn GigaBrain-0: VLA + World Model + RL
vlaworld-modelreinforcement-learninggigabrainroboticsmanipulation

Hướng dẫn GigaBrain-0: VLA + World Model + RL

Hướng dẫn chi tiết huấn luyện VLA bằng World Model và Reinforcement Learning với framework RAMP từ GigaBrain — open-source, 3.5B params.

12/4/202611 phút đọc
NEWDeep Dive
WholebodyVLA Open-Source: Hướng Dẫn Kiến Trúc & Code
vlahumanoidloco-manipulationiclrrlopen-sourceisaac-lab

WholebodyVLA Open-Source: Hướng Dẫn Kiến Trúc & Code

Deep-dive vào codebase WholebodyVLA — kiến trúc latent action, LMO RL policy, và cách xây dựng pipeline whole-body loco-manipulation cho humanoid.

12/4/202619 phút đọc
NEWTutorial
Sim-to-Real cho Humanoid: Deployment Best Practices
sim2realhumanoiddeploymentrlunitreePhần 10

Sim-to-Real cho Humanoid: Deployment Best Practices

Pipeline hoàn chỉnh deploy RL locomotion policy lên robot humanoid thật — domain randomization, system identification, safety, và Unitree SDK.

9/4/202611 phút đọc