File size: 12,082 Bytes
e35ec21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | """
Evaluation tricks for improving success rates on BEHAVIOR-1K tasks.
This module contains:
1. Correction rules: Task-specific problem resolution (stage reset, gripper recovery)
2. Gripper variation check: Disable compression when grippers have large movement
Enable via --apply-eval-tricks flag (default: False)
"""
import logging
import numpy as np
from typing import Tuple, Optional
logger = logging.getLogger(__name__)
# ============================================================================
# GLOBAL CONSTANTS
# ============================================================================
# Gripper thresholds (normalized [-1, 1] range)
# Grippers are normalized from raw [0, 0.1] to [-1, 1]:
# -1.0 = fully closed (raw 0.0)
# -0.9 = mostly closed (raw 0.005)
# 0.0 = half open (raw 0.05)
# 0.9 = mostly open (raw 0.095)
# 1.0 = fully open (raw 0.1)
OPEN_THRESHOLD = 0.90
CLOSED_THRESHOLD = -0.98 # very strict threshold to avoid false positives
# Gripper variation threshold for compression check
GRIPPER_VARIATION_THRESHOLD = 0.2
# State indices
LEFT_GRIPPER_IDX = 14
RIGHT_GRIPPER_IDX = 22
LEFT_ARM_START_IDX = 7 # Left arm: state[7:14]
RIGHT_ARM_START_IDX = 15 # Right arm: state[15:22]
LEFT_ARM_END_IDX = 14
RIGHT_ARM_END_IDX = 22
# Base positions for arms (7 joints each)
BASE_POSITION_LEFT_ARM = np.array([-0.2, 0.0, 0.1, -1.4, 0.1, 0.7, 0.0])
BASE_POSITION_RIGHT_ARM = np.array([-0.2, 0.0, 0.1, -1.4, 0.1, 0.7, 0.0])
# ============================================================================
# GRIPPER CORRECTION POLICY
# ============================================================================
# Tasks where grippers should ALWAYS be open (any closure is an error)
ALWAYS_OPEN_LEFT_GRIPPER_TASKS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 47, 48}
ALWAYS_OPEN_RIGHT_GRIPPER_TASKS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 34, 35, 36, 37, 42, 43, 44, 47, 48, 49}
# Tasks where closure is not allowed BEFORE specified stage
# Format: {task_id: {'left': min_stage, 'right': min_stage}}
MIN_STAGE_FOR_CLOSURE = {
0: {'left': 2, 'right': 2}, # Radio: allow closing from stage 2+
30: {'right': 6}, # setting_the_fire
31: {'right': 8}, # clean_boxing_gloves
32: {'right': 5}, # wash_a_baseball_cap
33: {'right': 11}, # wash_dog_toys
40: {'right': 4}, # make_microwave_popcorn
41: {'left': 14, 'right': 14}, # cook_cabbage
45: {'right': 10}, # cook_hot_dogs
46: {'left': 8, 'right': 8}, # cook_bacon
49: {'left': 14}, # make_pizza
}
# Tasks where right gripper is ALWAYS allowed to close (exceptions)
RIGHT_GRIPPER_ALWAYS_ALLOWED = {38, 39} # spraying_for_bugs, spraying_fruit_trees
# ============================================================================
# CORRECTION RULES - ADD NEW RULES HERE
# ============================================================================
def task0_stage4_reset_to_stage2(
task_id: int,
stage: int,
state: np.ndarray,
actions: np.ndarray
) -> Optional[Tuple[np.ndarray, int]]:
# Probably not very important for the final solution
"""
Task 0 (Radio), Stage 4: Reset to stage 2 with arm/gripper recovery.
PRIMARY: task_id=0 AND stage=4 β reset to stage 2 (always)
SECONDARY (conditional):
- If both grippers are fully open or fully closed:
β Open closed grippers
Args:
task_id: Current task ID
stage: Current stage
state: Current state (23-dim)
actions: Predicted actions (horizon, 23)
Returns:
(corrected_actions, corrected_stage) if rule applies, None otherwise
"""
# Check primary condition
if task_id != 0 or stage < 2:
return None
# PRIMARY: Always reset stage
if stage == 4:
logger.info(f"π§ CORRECTION RULE: Task 0 (Radio), Stage 4 β Stage 2")
corrected_stage = 2
else:
corrected_stage = stage
# SECONDARY: Check gripper states
left_gripper = state[LEFT_GRIPPER_IDX]
right_gripper = state[RIGHT_GRIPPER_IDX]
left_closed = left_gripper < CLOSED_THRESHOLD
right_closed = right_gripper < CLOSED_THRESHOLD
left_open = left_gripper > OPEN_THRESHOLD
right_open = right_gripper > OPEN_THRESHOLD
# Check if any gripper is in middle position (not fully open or closed)
left_middle = not (left_open or left_closed)
right_middle = not (right_open or right_closed)
change_action = False
# Copy current state to all actions as baseline
corrected_actions = np.tile(state, (actions.shape[0], 1))
if left_closed and not right_middle:
corrected_actions[:, LEFT_GRIPPER_IDX] = 1.0
change_action = True
logger.info(f" - Opening left gripper β 1.0")
if right_closed and not left_middle:
corrected_actions[:, RIGHT_GRIPPER_IDX] = 1.0
change_action = True
logger.info(f" - Opening right gripper β 1.0")
if not change_action:
if stage == corrected_stage:
return None
corrected_actions = actions
return corrected_actions, corrected_stage
def general_gripper_correction(
task_id: int,
stage: int,
state: np.ndarray,
actions: np.ndarray
) -> Optional[Tuple[np.ndarray, int]]:
"""
General gripper correction rule for all tasks.
Applies task-specific policies:
1. Always-open tasks: Open gripper if closed (any stage)
2. Stage-restricted tasks: Open gripper if closed before allowed stage
3. Exceptions: Skip tasks where gripper is intentionally closed (e.g., spray bottles)
Works in conjunction with task-specific rules:
- Task 0: This rule handles stages β€1, task-specific rule handles stage 4
Args:
task_id: Current task ID
stage: Current stage
state: np.ndarray,
actions: Predicted actions (horizon, 23)
Returns:
(corrected_actions, corrected_stage) if rule applies, None otherwise
"""
# Get gripper states
left_gripper = state[LEFT_GRIPPER_IDX]
right_gripper = state[RIGHT_GRIPPER_IDX]
left_closed = left_gripper < CLOSED_THRESHOLD
right_closed = right_gripper < CLOSED_THRESHOLD
# Determine if correction is needed
left_needs_opening = False
right_needs_opening = False
# Check left gripper
if left_closed:
# Always open if task requires it
if task_id in ALWAYS_OPEN_LEFT_GRIPPER_TASKS:
left_needs_opening = True
# Check stage restriction
elif task_id in MIN_STAGE_FOR_CLOSURE:
min_stage_left = MIN_STAGE_FOR_CLOSURE[task_id].get('left')
if min_stage_left is not None and stage < min_stage_left:
left_needs_opening = True
# Check right gripper
if right_closed:
# Exception: Always allow for spray bottle tasks
if task_id in RIGHT_GRIPPER_ALWAYS_ALLOWED:
pass # Never correct these tasks
# Always open if task requires it
elif task_id in ALWAYS_OPEN_RIGHT_GRIPPER_TASKS:
right_needs_opening = True
# Check stage restriction
elif task_id in MIN_STAGE_FOR_CLOSURE:
min_stage_right = MIN_STAGE_FOR_CLOSURE[task_id].get('right')
if min_stage_right is not None and stage < min_stage_right:
right_needs_opening = True
# Apply correction if needed
if left_needs_opening or right_needs_opening:
logger.info(f"π§ GENERAL GRIPPER CORRECTION: Task {task_id}, Stage {stage}")
# Copy current state to all actions
corrected_actions = np.tile(state, (actions.shape[0], 1))
if left_needs_opening:
logger.info(f" Left gripper closed ({left_gripper:.4f}) β Opening to 1.0")
corrected_actions[:, LEFT_GRIPPER_IDX] = 1.0
if right_needs_opening:
logger.info(f" Right gripper closed ({right_gripper:.4f}) β Opening to 1.0")
corrected_actions[:, RIGHT_GRIPPER_IDX] = 1.0
# Keep stage unchanged
return corrected_actions, stage
return None # No correction needed
# ============================================================================
# RULE REGISTRY - ORGANIZE BY TASK_ID
# ============================================================================
# Task-specific rules (these run BEFORE the general rule)
# Dictionary mapping task_id -> list of rule functions
TASK_SPECIFIC_RULES = {
0: [task0_stage4_reset_to_stage2], # Radio has special stage 4 handling
# Add more task-specific rules here as needed
}
# General rules that apply to ALL tasks (run after task-specific rules)
GENERAL_RULES = [
general_gripper_correction, # Applies to all tasks with gripper policies
]
# ============================================================================
# RULE APPLICATION
# ============================================================================
def apply_correction_rules(
task_id: int,
stage: int,
state: np.ndarray,
actions: np.ndarray
) -> Tuple[np.ndarray, int]:
"""
Apply correction rules for the given task_id.
Rules are applied in order until one matches:
1. Task-specific rules (e.g., radio's special stage 4 handling)
2. General rules (e.g., gripper correction policies)
Only the first matching rule is applied per call.
Args:
task_id: Current task ID
stage: Current stage
state: Current state (23-dim)
actions: Predicted actions (horizon, 23)
Returns:
(actions, stage) - corrected or original if no rule applies
"""
# Try task-specific rules first
task_specific_rules = TASK_SPECIFIC_RULES.get(task_id, [])
for rule in task_specific_rules:
result = rule(task_id, stage, state, actions)
if result is not None:
corrected_actions, corrected_stage = result
return corrected_actions, corrected_stage
# Try general rules (apply to all tasks)
for rule in GENERAL_RULES:
result = rule(task_id, stage, state, actions)
if result is not None:
corrected_actions, corrected_stage = result
return corrected_actions, corrected_stage
# No rules matched, return original
return actions, stage
# ============================================================================
# GRIPPER VARIATION CHECK
# ============================================================================
def check_gripper_variation(
actions: np.ndarray,
num_actions_to_check: int
) -> Tuple[bool, float, float]:
"""
Check if gripper actions have high variation.
High variation in gripper positions suggests complex manipulation that
should not be compressed via interpolation.
Args:
actions: Predicted actions (horizon, 23)
num_actions_to_check: Number of actions to check (e.g., actions_to_execute)
Returns:
(has_high_variation, left_variation, right_variation)
"""
# Check only the actions we plan to execute
actions_to_check = actions[:num_actions_to_check]
# Extract gripper actions
left_gripper_actions = actions_to_check[:, LEFT_GRIPPER_IDX]
right_gripper_actions = actions_to_check[:, RIGHT_GRIPPER_IDX]
left_variation = float(np.max(left_gripper_actions) - np.min(left_gripper_actions))
right_variation = float(np.max(right_gripper_actions) - np.min(right_gripper_actions))
has_high_variation = (left_variation > GRIPPER_VARIATION_THRESHOLD or
right_variation > GRIPPER_VARIATION_THRESHOLD)
return has_high_variation, left_variation, right_variation
|