| |
| |
| |
| |
| |
|
|
| import numpy as np |
| import torch |
| from torch import Tensor, nn |
|
|
|
|
| def create_stats_buffers( |
| normalization_type: str, |
| stats = None, |
| ): |
| assert isinstance(normalization_type, str) |
|
|
| shape = tuple(stats["mean"].shape) |
| if normalization_type == "MEAN_STD": |
| mean = torch.ones(shape, dtype=torch.float32) * torch.inf |
| std = torch.ones(shape, dtype=torch.float32) * torch.inf |
| buffer = nn.ParameterDict( |
| { |
| "mean": nn.Parameter(mean, requires_grad=False), |
| "std": nn.Parameter(std, requires_grad=False), |
| } |
| ) |
| elif normalization_type == "MIN_MAX": |
| min = torch.ones(shape, dtype=torch.float32) * torch.inf |
| max = torch.ones(shape, dtype=torch.float32) * torch.inf |
| buffer = nn.ParameterDict( |
| { |
| "min": nn.Parameter(min, requires_grad=False), |
| "max": nn.Parameter(max, requires_grad=False), |
| } |
| ) |
| elif normalization_type == "QUANTILE": |
| q01 = torch.ones(shape, dtype=torch.float32) * torch.inf |
| q99 = torch.ones(shape, dtype=torch.float32) * torch.inf |
| buffer = nn.ParameterDict( |
| { |
| "q01": nn.Parameter(q01, requires_grad=False), |
| "q99": nn.Parameter(q99, requires_grad=False), |
| } |
| ) |
|
|
| if stats: |
| if isinstance(stats["mean"], np.ndarray): |
| if normalization_type == "MEAN_STD": |
| buffer["mean"].data = torch.from_numpy(stats["mean"]).to(dtype=torch.float32) |
| buffer["std"].data = torch.from_numpy(stats["std"]).to(dtype=torch.float32) |
| elif normalization_type == "MIN_MAX": |
| buffer["min"].data = torch.from_numpy(stats["min"]).to(dtype=torch.float32) |
| buffer["max"].data = torch.from_numpy(stats["max"]).to(dtype=torch.float32) |
| elif normalization_type == "QUANTILE": |
| buffer["q01"].data = torch.from_numpy(stats["q01"]).to(dtype=torch.float32) |
| buffer["q99"].data = torch.from_numpy(stats["q99"]).to(dtype=torch.float32) |
|
|
| elif isinstance(stats["mean"], torch.Tensor): |
| if normalization_type == "MEAN_STD": |
| buffer["mean"].data = stats["mean"].clone().to(dtype=torch.float32) |
| buffer["std"].data = stats["std"].clone().to(dtype=torch.float32) |
| elif normalization_type == "MIN_MAX": |
| buffer["min"].data = stats["min"].clone().to(dtype=torch.float32) |
| buffer["max"].data = stats["max"].clone().to(dtype=torch.float32) |
| elif normalization_type == "QUANTILE": |
| buffer["q01"].data = stats["q01"].clone().to(dtype=torch.float32) |
| buffer["q99"].data = stats["q99"].clone().to(dtype=torch.float32) |
| else: |
| type_ = type(stats["mean"]) |
| raise ValueError(f"np.ndarray or torch.Tensor expected, but type is '{type_}' instead.") |
|
|
| return buffer |
|
|
|
|
| def _no_stats_error_str(name: str) -> str: |
| return ( |
| f"`{name}` is infinity. You should either initialize with `stats` as an argument, or use a " |
| "pretrained model." |
| ) |
|
|
|
|
| class Normalize_Action(nn.Module): |
| """Normalizes data (e.g. "observation.image") for more stable and faster convergence during training.""" |
|
|
| def __init__( |
| self, |
| normalization_type: str, |
| stats = None, |
| action_mask = None, |
| ): |
| super().__init__() |
| self.normalization_type = normalization_type |
| self.stats = stats |
| self.action_mask = torch.tensor(action_mask, dtype=torch.bool) if action_mask is not None else None |
| stats_buffers = create_stats_buffers(normalization_type, stats) |
| setattr(self, "buffer_value", stats_buffers) |
|
|
| |
| @torch.no_grad |
| def forward(self, action): |
| self.action_mask = self.action_mask.to(action.device) |
| buffer = getattr(self, "buffer_value") |
|
|
| if self.normalization_type == "MEAN_STD": |
| mean = buffer["mean"].to(action.device, dtype=action.dtype) |
| std = buffer["std"].to(action.device, dtype=action.dtype) |
| assert not torch.isinf(mean).any(), _no_stats_error_str("mean") |
| assert not torch.isinf(std).any(), _no_stats_error_str("std") |
| normalized_actions = torch.where( |
| self.action_mask, |
| (action - mean) / (std + 1e-8), |
| action, |
| ) |
| elif self.normalization_type == "MIN_MAX": |
| min = buffer["min"].to(action.device, dtype=action.dtype) |
| max = buffer["max"].to(action.device, dtype=action.dtype) |
| assert not torch.isinf(min).any(), _no_stats_error_str("min") |
| assert not torch.isinf(max).any(), _no_stats_error_str("max") |
| normalized_actions = torch.where( |
| self.action_mask, |
| torch.clamp(2 * (action - min) / (max - min + 1e-8) - 1, -1, 1), |
| action, |
| ) |
| elif self.normalization_type == "QUANTILE": |
| q01 = buffer["q01"].to(action.device, dtype=action.dtype) |
| q99 = buffer["q99"].to(action.device, dtype=action.dtype) |
| assert not torch.isinf(q01).any(), _no_stats_error_str("q01") |
| assert not torch.isinf(q99).any(), _no_stats_error_str("q99") |
| normalized_actions = torch.where( |
| self.action_mask, |
| torch.clamp(2 * (action - q01) / (q99 - q01 + 1e-8) - 1, -1, 1), |
| action, |
| ) |
|
|
| else: |
| raise ValueError(self.normalization_type) |
| return normalized_actions |
|
|
|
|
| class Unnormalize_Action(nn.Module): |
| def __init__( |
| self, |
| normalization_type: str, |
| stats = None, |
| action_mask = None, |
| ): |
| super().__init__() |
| self.normalization_type = normalization_type |
| self.stats = stats |
| self.action_mask = torch.tensor(action_mask, dtype=torch.bool) if action_mask is not None else None |
| stats_buffers = create_stats_buffers(normalization_type, stats) |
| setattr(self, "buffer_value", stats_buffers) |
|
|
| |
| @torch.no_grad |
| def forward(self, normalized_actions): |
| self.action_mask = self.action_mask.to(normalized_actions.device) |
| buffer = getattr(self, "buffer_value") |
|
|
| if self.normalization_type == "MEAN_STD": |
| mean = buffer["mean"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| std = buffer["std"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| assert not torch.isinf(mean).any(), _no_stats_error_str("mean") |
| assert not torch.isinf(std).any(), _no_stats_error_str("std") |
| action = torch.where( |
| self.action_mask, |
| normalized_actions * std + mean, |
| normalized_actions, |
| ) |
| elif self.normalization_type == "MIN_MAX": |
| min = buffer["min"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| max = buffer["max"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| assert not torch.isinf(min).any(), _no_stats_error_str("min") |
| assert not torch.isinf(max).any(), _no_stats_error_str("max") |
| action = torch.where( |
| self.action_mask, |
| 0.5 * (normalized_actions + 1) * (max - min + 1e-8) + min, |
| normalized_actions, |
| ) |
| elif self.normalization_type == "QUANTILE": |
| q01 = buffer["q01"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| q99 = buffer["q99"].to(normalized_actions.device, dtype=normalized_actions.dtype) |
| assert not torch.isinf(q01).any(), _no_stats_error_str("q01") |
| assert not torch.isinf(q99).any(), _no_stats_error_str("q99") |
| action = torch.where( |
| self.action_mask, |
| 0.5 * (normalized_actions + 1) * (q99 - q01 + 1e-8) + q01, |
| normalized_actions, |
| ) |
| else: |
| raise ValueError(self.normalization_type) |
| return action |
|
|