def __call__(self, inp: to.Tensor) -> to.Tensor: """ Evaluate the features and normalize them. .. note:: Only processing of 1-dim input (e.g., no images)! The input can be batched along the first dimension. :param inp: input i.e. observations in the RL setting :return: 1-dim vector of all feature values given the observations """ if inp.ndimension() > 2: raise pyrado.ShapeErr( msg='RBF class can only handle 1-dim or 2-dim input!') inp = atleast_2D( inp ) # first dim is the batch size, the second dim it the actual input dimension inp = inp.reshape(inp.shape[0], 1, inp.shape[1]).repeat(1, self.centers.shape[0], 1) # reshape explicitly exp_sq_dist = to.exp(-self.scale * to.pow(inp - self.centers, 2)) feat_val = to.empty(inp.shape[0], self.num_feat) for i, sample in enumerate(exp_sq_dist): if self._state_wise_norm: # Normalize the features such that the activation for every state dimension sums up to one feat_val[i, :] = normalize(sample, axis=0, order=1).t().reshape(-1, ) else: # Turn the features into a vector and normalize over all of them feat_val[i, :] = normalize(sample.t().reshape(-1, ), axis=-1, order=1) return feat_val
def _normalize_and_reshape(self, inp: to.Tensor) -> to.Tensor: """ Normalize (depending on `state_wise_norm`) and reshape the input. :param inp: input tensor of exponentiated squared distances :return: feature value """ if self._state_wise_norm: # Normalize the features such that the activation for every state dimension sums up to one return normalize(inp, axis=0, order=1).t().reshape(-1) else: # Turn the features into a vector and normalize over all of them return normalize(inp.t().reshape(-1), axis=-1, order=1)
def test_normalize(dtype, axis): for _ in range(10): x = to.rand(5, 3) if dtype == 'torch' else np.random.rand(5, 3) x_norm = normalize(x, axis=axis, order=1) if isinstance(x_norm, to.Tensor): x_norm = x_norm.numpy() # for easier checking with pytest.approx assert np.sum(x_norm, axis=axis) == pytest.approx(1.)
def cosine_similarity(x: to.Tensor, y: to.Tensor) -> to.Tensor: r""" Compute the cosine similarity between two tensors $D_cos(x,y) = \frac{x^T y}{|x| \cdot |y|}$. :param x: input tensor :param y: input tensor :return: cosine similarity value """ if not isinstance(x, to.Tensor): raise pyrado.TypeErr(given=x, expected_type=to.Tensor) if not isinstance(y, to.Tensor): raise pyrado.TypeErr(given=y, expected_type=to.Tensor) x_normed = normalize(x, order=2) y_normed = x_normed if y is x else normalize(y, order=2) return x_normed.dot(y_normed)
def derivative(self, inp: to.Tensor) -> to.Tensor: """ Compute the derivative of the features w.r.t. the inputs. .. note:: Only processing of 1-dim input (e.g., no images)! The input can be batched along the first dimension. :param inp: input i.e. observations in the RL setting :return: value of all features derivatives given the observations """ if inp.ndimension() > 2: raise pyrado.ShapeErr( msg="RBF class can only handle 1-dim or 2-dim input!") inp = to.atleast_2d( inp ) # first dim is the batch size, the second dim it the actual input dimension inp = inp.reshape(inp.shape[0], 1, inp.shape[1]).repeat(1, self.centers.shape[0], 1) # reshape explicitly exp_sq_dist = to.exp(-self.scale * to.pow(inp - self.centers, 2)) exp_sq_dist_d = -2 * self.scale * (inp - self.centers) feat_val = to.empty(inp.shape[0], self.num_feat) feat_val_dot = to.empty(inp.shape[0], self.num_feat) for i, (sample, sample_d) in enumerate(zip(exp_sq_dist, exp_sq_dist_d)): if self._state_wise_norm: # Normalize the features such that the activation for every state dimension sums up to one feat_val[i, :] = normalize(sample, axis=0, order=1).reshape(-1) else: # Turn the features into a vector and normalize over all of them feat_val[i, :] = normalize( sample.t().reshape(-1), axis=-1, order=1, ) feat_val_dot[ i, :] = sample_d.reshape(-1) * feat_val[i, :] - feat_val[ i, :] * sum(sample_d.reshape(-1) * feat_val[i, :]) return feat_val_dot