def __init__( self, *args, subrtn_sbi_class: Type[PosteriorEstimator], subrtn_sbi_sampling_hparam: Optional[dict] = None, **kwargs, ): """ Constructor forwarding everything to the superclass :param subrtn_sbi_class: sbi algorithm calls for executing the LFI, e.g. SNPE-C :param subrtn_sbi_sampling_hparam: keyword arguments forwarded to sbi's `DirectPosterior.sample()` function like `sample_with_mcmc`, ect. """ if not issubclass(subrtn_sbi_class, PosteriorEstimator): raise pyrado.TypeErr( msg=f"The given subrtn_sbi_class must be a subclass of PosteriorEstimator, but is {subrtn_sbi_class}!" ) # Call SBIBase's constructor super().__init__(*args, num_checkpoints=3, init_checkpoint=-1, **kwargs) # Set the sampling parameters used by the sbi subroutine default_sampling_hparam = dict( mcmc_method="slice_np_vectorized", mcmc_parameters=dict(warmup_steps=50, num_chains=100, init_strategy="sir"), # default: slice_np, 20 ) self.subrtn_sbi_sampling_hparam = merge_dicts([default_sampling_hparam, subrtn_sbi_sampling_hparam or dict()]) # Create algorithm instance self._subrtn_sbi_class = subrtn_sbi_class self._initialize_subrtn_sbi(subrtn_sbi_class=self._subrtn_sbi_class)
def draw_categorical( plot_type: str, ax: plt.Axes, data: Union[list, np.ndarray, to.Tensor, pd.DataFrame], x_label: Optional[Union[str, Sequence[str]]], y_label: Optional[str], vline_level: float = None, vline_label: str = "approx. solved", palette=None, title: str = None, show_legend: bool = True, legend_kwargs: dict = None, plot_kwargs: dict = None, ) -> plt.Figure: """ Create a box or violin plot for a list of data arrays or a pandas DataFrame. The plot is neither shown nor saved. If you want to order the 4th element to the 2nd position in terms of colors use .. code-block:: python palette.insert(1, palette.pop(3)) .. note:: If you want to have a tight layout, it is best to pass axes of a figure with `tight_layout=True` or `constrained_layout=True`. :param plot_type: tye of categorical plot, pass box or violin :param ax: axis of the figure to plot on :param data: list of data sets to plot as separate boxes :param x_label: labels for the categories on the x-axis, if `data` is not given as a `DataFrame` :param y_label: label for the y-axis, pass `None` to set no label :param vline_level: if not `None` (default) add a vertical line at the given level :param vline_label: label for the vertical line :param palette: seaborn color palette, pass `None` to use the default palette :param show_legend: if `True` the legend is shown, useful when handling multiple subplots :param title: title displayed above the figure, set to None to suppress the title :param legend_kwargs: keyword arguments forwarded to pyplot's `legend()` function, e.g. `loc='best'` :param plot_kwargs: keyword arguments forwarded to seaborn's `boxplot()` or `violinplot()` function :return: handle to the resulting figure """ plot_type = plot_type.lower() if plot_type not in ["box", "violin"]: raise pyrado.ValueErr(given=plot_type, eq_constraint="box or violin") if not isinstance(data, (list, to.Tensor, np.ndarray, pd.DataFrame)): raise pyrado.TypeErr( given=data, expected_type=[list, to.Tensor, np.ndarray, pd.DataFrame]) # Set defaults which can be overwritten plot_kwargs = merge_dicts([dict(alpha=1), plot_kwargs]) # by default no transparency alpha = plot_kwargs.pop( "alpha") # can't pass the to the seaborn plotting functions legend_kwargs = dict() if legend_kwargs is None else legend_kwargs palette = sns.color_palette() if palette is None else palette # Preprocess if isinstance(data, pd.DataFrame): df = data else: if isinstance(data, list): data = np.array(data) elif isinstance(data, to.Tensor): data = data.detach().cpu().numpy() if x_label is not None and not len(x_label) == data.shape[1]: raise pyrado.ShapeErr(given=data, expected_match=x_label) df = pd.DataFrame(data, columns=x_label) if data.shape[0] < data.shape[1]: print_cbt( f"Less data samples {data.shape[0]} then data dimensions {data.shape[1]}", "y", bright=True) # Plot if plot_type == "box": ax = sns.boxplot(data=df, ax=ax, **plot_kwargs) elif plot_type == "violin": plot_kwargs = merge_dicts([ dict(alpha=0.3, scale="count", inner="box", bw=0.3, cut=0), plot_kwargs ]) ax = sns.violinplot(data=df, ax=ax, palette=palette, **plot_kwargs) # Plot larger circles for medians (need to memorize the limits) medians = df.median().to_numpy() left, right = ax.get_xlim() locs = ax.get_xticks() ax.scatter(locs, medians, marker="o", s=30, zorder=3, color="white", edgecolors="black") ax.set_xlim((left, right)) # Postprocess if alpha < 1 and plot_type == "box": for patch in ax.artists: r, g, b, a = patch.get_facecolor() patch.set_facecolor((r, g, b, alpha)) elif alpha < 1 and plot_type == "violin": for violin in ax.collections[::2]: violin.set_alpha(alpha) if vline_level is not None: # Add dashed line to mark a threshold ax.axhline(vline_level, c="k", ls="--", lw=1.0, label=vline_label) if x_label is None: ax.get_xaxis().set_ticks([]) if y_label is not None: ax.set_ylabel(y_label) if show_legend: ax.legend(**legend_kwargs) if title is not None: ax.set_title(title) return plt.gcf()
def eval_posterior( posterior: DirectPosterior, data_real: to.Tensor, num_samples: int, calculate_log_probs: bool = True, normalize_posterior: bool = True, subrtn_sbi_sampling_hparam: Optional[dict] = None, ) -> Tuple[to.Tensor, Optional[to.Tensor]]: r""" Evaluates the posterior by computing parameter samples given observed data, its log probability and the simulated trajectory. :param posterior: posterior to evaluate, e.g. a normalizing flow, that samples domain parameters conditioned on the provided data :param data_real: data from the real-world rollouts a.k.a. set of $x_o$ of shape [num_iter, num_rollouts_per_iter * dim_feat] :param num_samples: number of samples to draw from the posterior :param calculate_log_probs: if `True`, the log-probabilities are computed, else `None` is returned :param normalize_posterior: if `True`, the normalization of the posterior density is enforced by sbi :param subrtn_sbi_sampling_hparam: keyword arguments forwarded to sbi's `DirectPosterior.sample()` function :return: domain parameters sampled form the posterior of shape [batch_size, num_samples, dim_domain_param], as well as the log-probabilities of these domain parameters """ if not isinstance(data_real, to.Tensor) or data_real.ndim != 2: raise pyrado.ShapeErr( msg= f"The data must be a 2-dim PyTorch tensor, but is of shape {data_real.shape}!" ) batch_size, _ = data_real.shape # Sample domain parameters for all batches and stack them default_sampling_hparam = dict( mcmc_method="slice_np_vectorized", mcmc_parameters=dict(warmup_steps=50, num_chains=100, init_strategy="sir"), # default: slice_np, 20 ) if subrtn_sbi_sampling_hparam is None: subrtn_sbi_sampling_hparam = dict() elif isinstance(subrtn_sbi_sampling_hparam, dict): subrtn_sbi_sampling_hparam = merge_dicts( [default_sampling_hparam, subrtn_sbi_sampling_hparam]) else: raise pyrado.TypeErr(given=subrtn_sbi_sampling_hparam, expected_type=dict) # Sample domain parameters from the posterior domain_params = to.stack( [ posterior.sample( (num_samples, ), x=x_o, **subrtn_sbi_sampling_hparam) for x_o in data_real ], dim=0, ) # Check shape if not domain_params.ndim == 3 or domain_params.shape[:2] != ( batch_size, num_samples): raise pyrado.ShapeErr( msg= f"The sampled domain parameters must be a 3-dim tensor where the 1st dimension is {batch_size} and " f"the 2nd dimension is {num_samples}, but it is of shape {domain_params.shape}!" ) # Compute the log probability if desired if calculate_log_probs: # Batch-wise computation and stacking with completion_context("Evaluating posterior", color="w"): log_probs = to.stack( [ posterior.log_prob( dp, x=x_o, norm_posterior=normalize_posterior) for dp, x_o in zip(domain_params, data_real) ], dim=0, ) # Check shape if log_probs.shape != (batch_size, num_samples): raise pyrado.ShapeErr(given=log_probs, expected_match=(batch_size, num_samples)) else: log_probs = None return domain_params, log_probs
def draw_surface(x: np.ndarray, y: np.ndarray, z_fcn: Union[Callable[[np.ndarray], np.ndarray], nn.Module], x_label: str, y_label: str, z_label: str, data_format='numpy', fig: plt.Figure = None, title: str = None, plot_kwargs: dict = None) -> plt.Figure: """ Render a 3-dim surface plot by providing a 1-dim array of x and y points and a function to calculate the z values. .. note:: If you want to have a tight layout, it is best to pass axes of a figure with `tight_layout=True` or `constrained_layout=True`. :param x: x-axis 1-dim grid for constructing the 2-dim mesh grid :param y: y-axis 1-dim grid for constructing the 2-dim mesh grid :param z_fcn: function that defines the surface, takes a 2-dim vector as input :param x_label: label for the x-axis :param y_label: label for the y-axis :param z_label: label for the z-axis :param data_format: data format, 'numpy' or 'torch' :param fig: handle to figure, pass None to create a new figure :param title: title displayed above the figure, set to None to suppress the title :param plot_kwargs: keyword arguments forwarded to pyplot's `plot_surface()` function :return: handle to figure """ plot_kwargs = merge_dicts( [dict(cmap=mpl.rcParams['image.cmap']), plot_kwargs]) if fig is None: fig = plt.figure() ax = Axes3D(fig) # Create mesh grid matrices from x and y vectors xx, yy = np.meshgrid(x, y) # Check which version to use based on the output of the function if data_format == 'numpy': # Operate on ndarrays zz = np.array( [z_fcn(np.stack((x, y), axis=0)) for x, y in zip(xx, yy)]) elif data_format == 'torch': # Operate on Tensors xx_tensor = to.from_numpy(xx) yy_tensor = to.from_numpy(yy) if hasattr(z_fcn, '_fcn'): # Passed function was wrapped (e.g. by functools) check_fcn = z_fcn._fcn else: check_fcn = z_fcn if isinstance(check_fcn, nn.Module): # Adapt for batch-first behavior of NN-based policies zz = to.stack([ z_fcn( to.stack((x, y), dim=1).view(-1, 1, 2).to(to.get_default_dtype())) for x, y in zip(xx_tensor, yy_tensor) ]) else: zz = to.stack([ z_fcn( to.stack((x, y), dim=1).transpose(0, 1).to(to.get_default_dtype())) for x, y in zip(xx_tensor, yy_tensor) ]) zz = zz.squeeze().detach().cpu().numpy() else: raise pyrado.ValueErr(given=data_format, eq_constraint="'numpy' or 'torch'") # Generate the plot ax.plot_surface(xx, yy, zz, **plot_kwargs) # Add labels ax.set_xlabel(x_label) ax.set_ylabel(y_label) ax.set_zlabel(z_label) if title is not None: ax.set_title(title) return fig
def draw_curve(plot_type: str, ax: plt.Axes, data: pd.DataFrame, x_grid: Union[list, np.ndarray, to.Tensor], x_label: Optional[Union[str, Sequence[str]]] = None, y_label: Optional[str] = None, curve_label: Optional[str] = None, area_label: Optional[str] = None, vline_level: Optional[float] = None, vline_label: str = 'approx. solved', title: Optional[str] = None, show_legend: bool = True, legend_kwargs: dict = None, plot_kwargs: dict = None) -> plt.Figure: """ Create a box or violin plot for a list of data arrays or a pandas DataFrame. The plot is neither shown nor saved. .. note:: If you want to have a tight layout, it is best to pass axes of a figure with `tight_layout=True` or `constrained_layout=True`. If you want to order the 4th element to the 2nd position in terms of colors use .. code-block:: python palette.insert(1, palette.pop(3)) :param plot_type: tye of categorical plot, pass box or violin :param ax: axis of the figure to plot on :param data: pandas DataFrame containing the columns `mean`, `std`, `min`, and `max` depending on the `plot_type` :param x_grid: values to plot the data over, e.g. time :param x_label: labels for the categories on the x-axis, if `data` is not given as a `DataFrame` :param y_label: label for the y-axis, pass `None` to set no label :param curve_label: label of the (1-dim) curve :param area_label: label of the (transparent) area :param vline_level: if not `None` (default) add a vertical line at the given level :param vline_label: label for the vertical line :param show_legend: if `True` the legend is shown, useful when handling multiple subplots :param title: title displayed above the figure, set to None to suppress the title :param legend_kwargs: keyword arguments forwarded to pyplot's `legend()` function, e.g. `loc='best'` :param plot_kwargs: keyword arguments forwarded to seaborn's `boxplot()` or `violinplot()` function :return: handle to the resulting figure """ plot_type = plot_type.lower() if plot_type not in ['mean_std', 'min_mean_max']: raise pyrado.ValueErr(given=plot_type, eq_constraint='mean_std or min_mean_max') if not isinstance(data, pd.DataFrame): raise pyrado.TypeErr(given=data, expected_type=pd.DataFrame) if x_label is not None and not isinstance(x_label, str): raise pyrado.TypeErr(given=x_label, expected_type=str) if y_label is not None and not isinstance(y_label, str): raise pyrado.TypeErr(given=y_label, expected_type=str) # Set defaults which can be overwritten by passing plot_kwargs plot_kwargs = merge_dicts([dict(alpha=0.3), plot_kwargs]) legend_kwargs = dict() if legend_kwargs is None else legend_kwargs # palette = sns.color_palette() if palette is None else palette # Preprocess if isinstance(x_grid, list): x_grid = np.array(x_grid) elif isinstance(x_grid, to.Tensor): x_grid = x_grid.detach().cpu().numpy() # Plot if plot_type == 'mean_std': if not ('mean' in data.columns and 'std' in data.columns): raise pyrado.KeyErr(keys="'mean' and 'std'", container=data) num_stds = 2 if area_label is None: area_label = rf'$\pm {num_stds}$ std' ax.fill_between(x_grid, data['mean'] - num_stds * data['std'], data['mean'] + num_stds * data['std'], label=area_label, **plot_kwargs) elif plot_type == 'min_mean_max': if not ('mean' in data.columns and 'min' in data.columns and 'max' in data.columns): raise pyrado.KeyErr(keys="'mean' and 'min' and 'max'", container=data) if area_label is None: area_label = r'min \& max' ax.fill_between(x_grid, data['min'], data['max'], label=area_label, **plot_kwargs) # plot mean last for proper z-ordering plot_kwargs['alpha'] = 1 ax.plot(x_grid, data['mean'], label=curve_label, **plot_kwargs) # Postprocess if vline_level is not None: # Add dashed line to mark a threshold ax.axhline(vline_level, c='k', ls='--', lw=1., label=vline_label) if x_label is None: ax.get_xaxis().set_ticks([]) if y_label is not None: ax.set_ylabel(y_label) if show_legend: ax.legend(**legend_kwargs) if title is not None: ax.set_title(title) return plt.gcf()
def __init__( self, spec: EnvSpec, dim_data: int, hidden_size: int, num_recurrent_layers: int, output_size: int, recurrent_network_type: type = nn.RNN, only_last_output: bool = False, len_rollouts: int = None, output_nonlin: Callable = None, dropout: float = 0.0, init_param_kwargs: Optional[dict] = None, downsampling_factor: int = 1, state_mask_labels: Optional[Union[Tuple[Union[int, str]], List[Union[int, str]]]] = None, act_mask_labels: Optional[Union[Tuple[Union[int, str]], List[Union[int, str]]]] = None, use_cuda: bool = False, **recurrent_net_kwargs, ): """ Constructor :param spec: environment specification :param dim_data: number of dimensions of one data sample, i.e. one time step. By default, this is the sum of the state and action spaces' flat dimensions. This number is doubled if the embedding target domain data. :param hidden_size: size of the hidden layers (all equal) :param num_recurrent_layers: number of equally sized hidden layers :param recurrent_network_type: PyTorch recurrent network class, e.g. `nn.RNN`, `nn.LSTM`, or `nn.GRU` :param output_size: size of the features at every time step, which are eventually reshaped into a vector :param only_last_output: if `True`, only the last output of the network is used as a feature for sbi, else there will be an output every `downsampling_factor` time steps. Moreover, if `True` the constructor does not need to know how long the rollouts are. :param len_rollouts: number of time steps per rollout without considering a potential downsampling later (must be the same for all rollouts) :param output_nonlin: nonlinearity for output layer :param dropout: dropout probability, default = 0 deactivates dropout :param init_param_kwargs: additional keyword arguments for the policy parameter initialization :param recurrent_net_kwargs: any extra kwargs are passed to the recurrent net's constructor :param downsampling_factor: skip evey `downsampling_factor` time series sample, the downsampling is done in the base class before calling `summary_statistic()` :param state_mask_labels: list or tuple of integers or stings to select specific states from their space. By default `None` all states are passed to sbi. :param act_mask_labels: list or tuple of integers or stings to select specific actions from their space. By default `None` all actions are passed to sbi. :param use_cuda: `True` to move the policy to the GPU, `False` (default) to use the CPU """ super().__init__(spec, dim_data, downsampling_factor, state_mask_labels, act_mask_labels, use_cuda) # Check the time sequence length if necessary if not only_last_output: if not isinstance(len_rollouts, int) or len_rollouts < 0: raise pyrado.ValueErr(given=len_rollouts, eq_constraint="1 (int)") self._len_rollouts = len_rollouts // downsampling_factor else: self._len_rollouts = None # use to signal only_last_output == True if recurrent_network_type == nn.RNN: recurrent_net_kwargs = merge_dicts( [dict(nonlinearity="tanh"), recurrent_net_kwargs]) # Create the RNN layers self.rnn_layers = recurrent_network_type( input_size=dim_data if self.data_mask is None else np.count_nonzero(self.data_mask), # includes actions hidden_size=hidden_size, num_layers=num_recurrent_layers, bias=True, batch_first=False, dropout=dropout, bidirectional=False, **recurrent_net_kwargs, ) # Create the output layer self.output_layer = nn.Linear(hidden_size, output_size) self.output_nonlin = output_nonlin # Initialize parameter values init_param_kwargs = init_param_kwargs if init_param_kwargs is not None else dict( ) self.init_param(None, **init_param_kwargs) self.to(self.device) # Detach the complete network, i.e. use it with the random initialization for p in self.parameters(): p.requires_grad = False
def __init__( self, save_dir: pyrado.PathLike, inputs: to.Tensor, targets: to.Tensor, policy: Policy, max_iter: int, max_iter_no_improvement: int = 30, optim_class=optim.Adam, optim_hparam: dict = None, loss_fcn=nn.MSELoss(), batch_size: int = 256, ratio_train: float = 0.8, max_grad_norm: Optional[float] = None, lr_scheduler=None, lr_scheduler_hparam: Optional[dict] = None, logger: StepLogger = None, ): """ Constructor :param save_dir: directory to save the snapshots i.e. the results in :param inputs: input data set, where the samples are along the first dimension :param targets: target data set, where the samples are along the first dimension :param policy: Pyrado policy (subclass of PyTorch's Module) to train :param max_iter: maximum number of iterations :param max_iter_no_improvement: if the performance on the validation set did not improve for this many iterations, the policy is considered to have converged, i.e. training stops :param optim_class: PyTorch optimizer class :param optim_hparam: hyper-parameters for the PyTorch optimizer :param loss_fcn: loss function for training, by default `torch.nn.MSELoss()` :param batch_size: number of samples per policy update batch :param ratio_train: ratio of the training samples w.r.t. the total sample count :param max_grad_norm: maximum L2 norm of the gradients for clipping, set to `None` to disable gradient clipping :param lr_scheduler: learning rate scheduler that does one step per epoch (pass through the whole data set) :param lr_scheduler_hparam: hyper-parameters for the learning rate scheduler :param logger: logger for every step of the algorithm, if `None` the default logger will be created """ if not isinstance(inputs, to.Tensor): raise pyrado.TypeErr(given=inputs, expected_type=to.Tensor) if not isinstance(targets, to.Tensor): raise pyrado.TypeErr(given=targets, expected_type=to.Tensor) if not isinstance(ratio_train, float): raise pyrado.TypeErr(given=ratio_train, expected_type=float) if not (0 < ratio_train < 1): raise pyrado.ValueErr(given=ratio_train, g_constraint="0", l_constraint="1") # Call Algorithm's constructor super().__init__(save_dir, max_iter, policy, logger) # Construct the dataset (samples along rows) inputs = to.atleast_2d( inputs).T if inputs.ndimension() == 1 else inputs targets = to.atleast_2d( targets).T if targets.ndimension() == 1 else targets if inputs.shape[0] != targets.shape[0]: raise pyrado.ShapeErr(given=targets, expected_match=inputs) num_samples_all = inputs.shape[0] dataset = TensorDataset( inputs, targets) # shared for training and validation loaders # Create training and validation loader idcs_all = to.randperm(num_samples_all) num_samples_trn = int(ratio_train * num_samples_all) num_samples_val = num_samples_all - num_samples_trn idcs_trn, idcs_val = idcs_all[:num_samples_trn], idcs_all[ num_samples_trn:] self.loader_trn = DataLoader( dataset, batch_size=min(batch_size, num_samples_trn), drop_last=True, sampler=SubsetRandomSampler(idcs_trn), ) self.loader_val = DataLoader( dataset, batch_size=min(batch_size, num_samples_val), drop_last=True, sampler=SubsetRandomSampler(idcs_val), ) # Set defaults which can be overwritten by passing optim_hparam, and create the optimizer optim_hparam = merge_dicts( [dict(lr=5e-3, eps=1e-8, weight_decay=1e-4), optim_hparam]) self.optim = optim_class([{ "params": self._policy.parameters() }], **optim_hparam) self.batch_size = batch_size self.ratio_train = ratio_train self.loss_fcn = loss_fcn self.max_grad_norm = max_grad_norm self._lr_scheduler = lr_scheduler self._lr_scheduler_hparam = lr_scheduler_hparam if lr_scheduler is not None and lr_scheduler_hparam is not None: self._lr_scheduler = lr_scheduler(self.optim, **lr_scheduler_hparam) # Stopping criterion self._curr_loss_val = pyrado.inf self._best_loss_val = pyrado.inf self._cnt_iter_no_improvement = 0 self._max_iter_no_improvement = max_iter_no_improvement self.stopping_criterion = self.stopping_criterion | CustomStoppingCriterion( self._custom_stopping_criterion)
def compute_traj_distance_metrics( states_real: np.ndarray, states_ml: np.ndarray, states_nom: np.ndarray, num_rollouts_real: int, normalize: bool = True, dtw_config: Optional[dict] = None, save: bool = True, ): """ Compute the DTW distance and the RMSE for 2 trajectories w.r.t. a ground truth trajectory, and store it in a table. :param states_real: numpy array of states from the real system of shape [num_rollouts, len_time_series, dim_state] :param states_ml: numpy array of states from the most likely system [num_rollouts, len_time_series, dim_state] :param states_nom: numpy array of states from the nominal system [num_rollouts, len_time_series, dim_state] :param num_rollouts_real: number of rollouts :param normalize: it `True`, normalize all trajectories by the max. abs. values of the ground truth trajectory for each dimension before computing the metrics :param dtw_config: dictionary with options for the `dtw.dtw()` command, e.g. `dict(step_pattern=dtw.rabinerJuangStepPattern(6, "c"))` :param save: it `True`, save table as tex-file """ # Configure the metric computations default_dtw_config = dict(open_end=True, step_pattern="symmetric2", distance_only=True) dtw_config = merge_dicts([default_dtw_config, dtw_config or dict()]) # Iterate over all rollouts and compute the performance metrics table = [] dtw_dist_ml_avg, dtw_dist_nom_avg, rmse_ml_avg, rmse_nom_avg = 0, 0, 0, 0 for idx_r in range(num_rollouts_real): if normalize: # Normalize all trajectories by the max. abs. values of the ground truth trajectory for each dimension max_abs_state = np.max(np.abs(states_real[idx_r]), axis=0) states_real[idx_r] /= max_abs_state states_ml[idx_r] /= max_abs_state states_nom[idx_r] /= max_abs_state # DTW dtw_dist_ml = dtw.dtw(states_real[idx_r], states_ml[idx_r], **dtw_config).distance dtw_dist_nom = dtw.dtw(states_real[idx_r], states_nom[idx_r], **dtw_config).distance dtw_dist_ml_avg += dtw_dist_ml / num_rollouts_real dtw_dist_nom_avg += dtw_dist_nom / num_rollouts_real # RMSE averaged over the states rmse_ml = np.mean(rmse(states_real[idx_r], states_ml[idx_r], dim=0)) rmse_nom = np.mean(rmse(states_real[idx_r], states_nom[idx_r], dim=0)) rmse_ml_avg += rmse_ml / num_rollouts_real rmse_nom_avg += rmse_nom / num_rollouts_real table.append([idx_r, dtw_dist_ml, dtw_dist_nom, rmse_ml, rmse_nom]) raw_data = np.array(table) # Add last row separately table.append(["average", dtw_dist_ml_avg, dtw_dist_nom_avg, rmse_ml_avg, rmse_nom_avg]) # Print the tabulated data headers = ("rollout", "DTW dist. ml", "DTW dist. nom", "mean RMSE ml", "mean RMSE nom") print(tabulate(table, headers)) if save: # Save the table for LaTeX table_latex_str = tabulate(table, headers, tablefmt="latex") str_iter = f"_iter_{args.iter}" str_round = f"_round_{args.round}" use_rec_str = "_use_rec" if args.use_rec else "" file_name = f"distance_metrics{str_iter}{str_round}{use_rec_str}" with open(osp.join(ex_dir, f"{file_name}.tex"), "w") as tab_file: print(table_latex_str, file=tab_file) with open(osp.join(ex_dir, f"{file_name}.npy"), "wb") as np_file: np.save(np_file, raw_data)
def draw_curve_from_data( plot_type: str, ax: plt.Axes, data: Union[list, np.ndarray, to.Tensor, pd.DataFrame], x_grid: Union[list, np.ndarray, to.Tensor], ax_calc: int, x_label: Optional[Union[str, Sequence[str]]] = None, y_label: Optional[str] = None, curve_label: Optional[str] = None, area_label: Optional[str] = "", vline_level: Optional[float] = None, vline_label: str = "approx. solved", title: Optional[str] = None, show_legend: bool = True, cmp_kwargs: dict = None, plot_kwargs: dict = None, legend_kwargs: dict = None, ) -> plt.Figure: """ Create a box or violin plot for a list of data arrays or a pandas DataFrame. The plot is neither shown nor saved. .. note:: If you want to have a tight layout, it is best to pass axes of a figure with `tight_layout=True` or `constrained_layout=True`. If you want to order the 4th element to the 2nd position in terms of colors use .. code-block:: python palette.insert(1, palette.pop(3)) :param plot_type: tye of 1-dim plot: `mean_std`, `min_mean_max`, or `ci_on_mean` :param ax: axis of the figure to plot on :param data: data to plot,me.g. a time series :param x_grid: values to plot the data over, e.g. time :param ax_calc: axis of the data array to calculate the mean, min and max, or std over :param x_label: labels for the categories on the x-axis, if `data` is not given as a `DataFrame` :param y_label: label for the y-axis, pass `None` to set no label :param curve_label: label of the (1-dim) curve, pass `None` for no label :param area_label: label of the (transparent) area, pass `None` for no label and "" for the default label :param vline_level: if not `None` (default) add a vertical line at the given level :param vline_label: label for the vertical line :param show_legend: if `True` the legend is shown, useful when handling multiple subplots :param title: title displayed above the figure, set to None to suppress the title :param cmp_kwargs: keyword arguments forwarded to functions computing the statistics of interest :param plot_kwargs: keyword arguments forwarded to the plotting` functions :param legend_kwargs: keyword arguments forwarded to pyplot's `legend()` function, e.g. `loc='best'` :return: handle to the resulting figure """ plot_type = plot_type.lower() if plot_type not in ["mean_std", "min_mean_max", "ci_on_mean"]: raise pyrado.ValueErr( given=plot_type, eq_constraint="mean_std, min_mean_max, ci_on_mean") if not isinstance(data, (list, to.Tensor, np.ndarray, pd.DataFrame)): raise pyrado.TypeErr( given=data, expected_type=[list, to.Tensor, np.ndarray, pd.DataFrame]) # Set defaults which can be overwritten by passing plot_kwargs cmp_kwargs = merge_dicts([ dict(num_reps=1000, confidence_level=0.9, bias_correction=False, studentized=False), cmp_kwargs ]) if isinstance(data, pd.DataFrame): data = data.to_numpy() elif isinstance(data, list): data = np.array(data) elif isinstance(data, to.Tensor): data = data.detach().cpu().numpy() # Extract features from data data_mean = np.mean(data, axis=ax_calc) df = pd.DataFrame() df = df.assign(mean=data_mean) if plot_type == "mean_std": data_std = np.std(data, axis=ax_calc) df = df.assign(std=data_std) elif plot_type == "min_mean_max": data_min = np.min(data, axis=ax_calc) data_max = np.max(data, axis=ax_calc) df = df.assign(min=data_min) df = df.assign(max=data_max) elif plot_type == "ci_on_mean": _, data_lo, data_up = bootstrap_ci( data.T if ax_calc == 1 else data, stat_fcn=np.mean, num_reps=cmp_kwargs["num_reps"], alpha=cmp_kwargs["confidence_level"], ci_sides=2, bias_correction=cmp_kwargs["bias_correction"], studentized=cmp_kwargs["studentized"], seed=0, ) df = df.assign(ci_lo=data_lo) df = df.assign(ci_up=data_up) # Forward the actual plotting return draw_curve( plot_type, ax, df, x_grid, x_label, y_label, curve_label, area_label, vline_level, vline_label, title, show_legend, plot_kwargs, legend_kwargs, )