anhtld commited on
Commit
ac41a23
·
verified ·
1 Parent(s): 5d5bbcd

Auto-sync: 2026-06-28 20:44:54

Browse files
dovla_cil/eval/maniskill_policy_rollout.py CHANGED
@@ -99,9 +99,10 @@ def evaluate_maniskill_policy_rollout(
99
 
100
  When ``selection_mode == 'retrieval_residual'`` the evaluator retrieves counterfactual
101
  action residuals (candidate minus expert action) from the nearest training-split state(s),
102
- adds those residuals around the current policy mean, scores the resulting local proposal
103
- lattice with the learned field, and executes the best chunk. This keeps the proposal
104
- geometry counterfactual while avoiding same-state validation candidates.
 
105
 
106
  When ``selection_mode == 'field_optim'`` the evaluator starts from the policy mean plus
107
  optional Gaussian multi-start proposals, performs projected gradient ascent on the learned
@@ -155,9 +156,15 @@ def evaluate_maniskill_policy_rollout(
155
  raise ValueError("retrieval_metric must be 'raw' or 'zscore'")
156
  if retrieval_residual_anchor not in {"expert", "policy"}:
157
  raise ValueError("retrieval_residual_anchor must be 'expert' or 'policy'")
158
- if retrieval_residual_reduce not in {"none", "mean_by_type", "median_by_type"}:
 
 
 
 
 
159
  raise ValueError(
160
- "retrieval_residual_reduce must be 'none', 'mean_by_type', or 'median_by_type'"
 
161
  )
162
  if not 0.0 <= retrieval_type_min_success <= 1.0:
163
  raise ValueError("retrieval_type_min_success must be in [0, 1]")
@@ -217,7 +224,6 @@ def evaluate_maniskill_policy_rollout(
217
  observation_mode=model_config.observation_mode,
218
  retrieval_neighbors=retrieval_neighbors,
219
  retrieval_metric=retrieval_metric,
220
- retrieval_type_min_success=retrieval_type_min_success,
221
  )
222
  else:
223
  cases = _attach_retrieved_residual_candidates(
@@ -557,7 +563,7 @@ def _attach_retrieved_residual_candidates(
557
  vectorize_toy_observation(case.observation, obs_dim=obs_dim),
558
  dtype=np.float32,
559
  )
560
- nearest = _nearest_retrieval_entries(
561
  candidates,
562
  query,
563
  retrieval_neighbors=retrieval_neighbors,
@@ -566,15 +572,22 @@ def _attach_retrieved_residual_candidates(
566
  source_group_ids: list[str] = []
567
  residuals: list[list[list[float]]] = []
568
  candidate_types: list[str] = []
569
- for source_group_id, _feature, source_residuals, source_candidate_types in nearest:
 
 
 
 
 
570
  source_group_ids.append(source_group_id)
571
  residuals.extend(source_residuals)
572
  candidate_types.extend(source_candidate_types)
 
573
  if retrieval_residual_reduce != "none":
574
  residuals, candidate_types = _reduce_residual_candidates_by_type(
575
  residuals,
576
  candidate_types,
577
  mode=retrieval_residual_reduce,
 
578
  )
579
  output.append(
580
  replace(
@@ -592,26 +605,48 @@ def _reduce_residual_candidates_by_type(
592
  candidate_types: list[str],
593
  *,
594
  mode: str,
 
595
  ) -> tuple[list[list[list[float]]], list[str]]:
596
- if mode not in {"mean_by_type", "median_by_type"}:
597
- raise ValueError("mode must be 'mean_by_type' or 'median_by_type'")
 
 
598
  if len(residuals) != len(candidate_types):
599
  raise ValueError("residuals and candidate_types must have the same length")
 
 
600
 
601
  ordered_types = list(dict.fromkeys(candidate_types))
602
  reduced_residuals: list[list[list[float]]] = []
603
  reduced_types: list[str] = []
604
  for candidate_type in ordered_types:
605
- values = [
606
- np.asarray(residual, dtype=np.float32)
607
- for residual, residual_type in zip(residuals, candidate_types)
608
- if residual_type == candidate_type
609
- ]
 
 
 
610
  if not values:
611
  continue
612
  stack = np.stack(values, axis=0)
613
  if mode == "mean_by_type":
614
  reduced = np.mean(stack, axis=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
  else:
616
  reduced = np.median(stack, axis=0)
617
  reduced_residuals.append(reduced.astype(np.float32).tolist())
@@ -647,11 +682,30 @@ def _nearest_retrieval_entries(
647
  retrieval_neighbors: int,
648
  retrieval_metric: str,
649
  ) -> list[tuple[Any, np.ndarray, Any, Any]]:
650
- if retrieval_metric == "raw":
651
- return sorted(
 
652
  candidates,
653
- key=lambda item: float(np.mean((item[1] - query) ** 2)),
654
- )[:retrieval_neighbors]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  if retrieval_metric != "zscore":
656
  raise ValueError("retrieval_metric must be 'raw' or 'zscore'")
657
  features = np.stack([np.asarray(item[1], dtype=np.float32) for item in candidates], axis=0)
@@ -660,8 +714,35 @@ def _nearest_retrieval_entries(
660
  scale = np.where(std > 1e-6, std, 1.0).astype(np.float32)
661
  normalized_features = (features - mean) / scale
662
  normalized_query = (np.asarray(query, dtype=np.float32) - mean) / scale
663
- order = np.argsort(np.mean((normalized_features - normalized_query) ** 2, axis=1))
664
- return [candidates[int(index)] for index in order[:retrieval_neighbors]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
 
667
  def _evaluate_task_cases(
 
99
 
100
  When ``selection_mode == 'retrieval_residual'`` the evaluator retrieves counterfactual
101
  action residuals (candidate minus expert action) from the nearest training-split state(s),
102
+ optionally reduces them into type-wise tangent consensus proposals, adds those residuals
103
+ around the current policy mean, scores the resulting local proposal lattice with the learned
104
+ field, and executes the best chunk. This keeps the proposal geometry counterfactual while
105
+ avoiding same-state validation candidates.
106
 
107
  When ``selection_mode == 'field_optim'`` the evaluator starts from the policy mean plus
108
  optional Gaussian multi-start proposals, performs projected gradient ascent on the learned
 
156
  raise ValueError("retrieval_metric must be 'raw' or 'zscore'")
157
  if retrieval_residual_anchor not in {"expert", "policy"}:
158
  raise ValueError("retrieval_residual_anchor must be 'expert' or 'policy'")
159
+ if retrieval_residual_reduce not in {
160
+ "none",
161
+ "mean_by_type",
162
+ "median_by_type",
163
+ "kernel_mean_by_type",
164
+ }:
165
  raise ValueError(
166
+ "retrieval_residual_reduce must be 'none', 'mean_by_type', "
167
+ "'median_by_type', or 'kernel_mean_by_type'"
168
  )
169
  if not 0.0 <= retrieval_type_min_success <= 1.0:
170
  raise ValueError("retrieval_type_min_success must be in [0, 1]")
 
224
  observation_mode=model_config.observation_mode,
225
  retrieval_neighbors=retrieval_neighbors,
226
  retrieval_metric=retrieval_metric,
 
227
  )
228
  else:
229
  cases = _attach_retrieved_residual_candidates(
 
563
  vectorize_toy_observation(case.observation, obs_dim=obs_dim),
564
  dtype=np.float32,
565
  )
566
+ nearest = _nearest_retrieval_entries_with_distances(
567
  candidates,
568
  query,
569
  retrieval_neighbors=retrieval_neighbors,
 
572
  source_group_ids: list[str] = []
573
  residuals: list[list[list[float]]] = []
574
  candidate_types: list[str] = []
575
+ residual_weights: list[float] = []
576
+ source_weights = _kernel_weights_from_distances(
577
+ [distance for _entry, distance in nearest]
578
+ )
579
+ for (entry, _distance), source_weight in zip(nearest, source_weights):
580
+ source_group_id, _feature, source_residuals, source_candidate_types = entry
581
  source_group_ids.append(source_group_id)
582
  residuals.extend(source_residuals)
583
  candidate_types.extend(source_candidate_types)
584
+ residual_weights.extend([float(source_weight)] * len(source_residuals))
585
  if retrieval_residual_reduce != "none":
586
  residuals, candidate_types = _reduce_residual_candidates_by_type(
587
  residuals,
588
  candidate_types,
589
  mode=retrieval_residual_reduce,
590
+ weights=residual_weights,
591
  )
592
  output.append(
593
  replace(
 
605
  candidate_types: list[str],
606
  *,
607
  mode: str,
608
+ weights: list[float] | None = None,
609
  ) -> tuple[list[list[list[float]]], list[str]]:
610
+ if mode not in {"mean_by_type", "median_by_type", "kernel_mean_by_type"}:
611
+ raise ValueError(
612
+ "mode must be 'mean_by_type', 'median_by_type', or 'kernel_mean_by_type'"
613
+ )
614
  if len(residuals) != len(candidate_types):
615
  raise ValueError("residuals and candidate_types must have the same length")
616
+ if weights is not None and len(weights) != len(residuals):
617
+ raise ValueError("weights and residuals must have the same length")
618
 
619
  ordered_types = list(dict.fromkeys(candidate_types))
620
  reduced_residuals: list[list[list[float]]] = []
621
  reduced_types: list[str] = []
622
  for candidate_type in ordered_types:
623
+ values: list[np.ndarray] = []
624
+ value_weights: list[float] = []
625
+ for index, (residual, residual_type) in enumerate(zip(residuals, candidate_types)):
626
+ if residual_type != candidate_type:
627
+ continue
628
+ values.append(np.asarray(residual, dtype=np.float32))
629
+ if weights is not None:
630
+ value_weights.append(float(weights[index]))
631
  if not values:
632
  continue
633
  stack = np.stack(values, axis=0)
634
  if mode == "mean_by_type":
635
  reduced = np.mean(stack, axis=0)
636
+ elif mode == "kernel_mean_by_type":
637
+ if value_weights:
638
+ np_weights = np.asarray(value_weights, dtype=np.float32)
639
+ weight_sum = float(np.sum(np_weights))
640
+ if weight_sum > 1e-12:
641
+ np_weights = np_weights / weight_sum
642
+ reduced = np.sum(
643
+ stack * np_weights.reshape((-1,) + (1,) * (stack.ndim - 1)),
644
+ axis=0,
645
+ )
646
+ else:
647
+ reduced = np.mean(stack, axis=0)
648
+ else:
649
+ reduced = np.mean(stack, axis=0)
650
  else:
651
  reduced = np.median(stack, axis=0)
652
  reduced_residuals.append(reduced.astype(np.float32).tolist())
 
682
  retrieval_neighbors: int,
683
  retrieval_metric: str,
684
  ) -> list[tuple[Any, np.ndarray, Any, Any]]:
685
+ return [
686
+ entry
687
+ for entry, _distance in _nearest_retrieval_entries_with_distances(
688
  candidates,
689
+ query,
690
+ retrieval_neighbors=retrieval_neighbors,
691
+ retrieval_metric=retrieval_metric,
692
+ )
693
+ ]
694
+
695
+
696
+ def _nearest_retrieval_entries_with_distances(
697
+ candidates: list[tuple[Any, np.ndarray, Any, Any]],
698
+ query: np.ndarray,
699
+ *,
700
+ retrieval_neighbors: int,
701
+ retrieval_metric: str,
702
+ ) -> list[tuple[tuple[Any, np.ndarray, Any, Any], float]]:
703
+ if retrieval_metric == "raw":
704
+ scored = [
705
+ (item, float(np.mean((item[1] - query) ** 2)))
706
+ for item in candidates
707
+ ]
708
+ return sorted(scored, key=lambda item: item[1])[:retrieval_neighbors]
709
  if retrieval_metric != "zscore":
710
  raise ValueError("retrieval_metric must be 'raw' or 'zscore'")
711
  features = np.stack([np.asarray(item[1], dtype=np.float32) for item in candidates], axis=0)
 
714
  scale = np.where(std > 1e-6, std, 1.0).astype(np.float32)
715
  normalized_features = (features - mean) / scale
716
  normalized_query = (np.asarray(query, dtype=np.float32) - mean) / scale
717
+ distances = np.mean((normalized_features - normalized_query) ** 2, axis=1)
718
+ order = np.argsort(distances)
719
+ return [
720
+ (candidates[int(index)], float(distances[int(index)]))
721
+ for index in order[:retrieval_neighbors]
722
+ ]
723
+
724
+
725
+ def _kernel_weights_from_distances(distances: list[float]) -> list[float]:
726
+ if not distances:
727
+ return []
728
+ values = np.asarray(distances, dtype=np.float32)
729
+ values = np.maximum(np.nan_to_num(values, nan=np.inf, posinf=np.inf, neginf=0.0), 0.0)
730
+ finite = values[np.isfinite(values)]
731
+ if finite.size == 0:
732
+ return [1.0] * len(distances)
733
+ positive = finite[finite > 1e-12]
734
+ if positive.size == 0:
735
+ return [1.0] * len(distances)
736
+ bandwidth = float(np.median(positive))
737
+ if bandwidth <= 1e-12:
738
+ bandwidth = float(np.max(positive))
739
+ if bandwidth <= 1e-12:
740
+ return [1.0] * len(distances)
741
+ weights = np.exp(-np.minimum(values, bandwidth * 50.0) / bandwidth)
742
+ weights = np.where(np.isfinite(weights), weights, 0.0)
743
+ if float(np.sum(weights)) <= 1e-12:
744
+ return [1.0] * len(distances)
745
+ return weights.astype(np.float32).tolist()
746
 
747
 
748
  def _evaluate_task_cases(