def __init__(self, distribution, flow): """ Construct a new :class:`FlowDistribution` from the given `distribution`. Args: distribution (Distribution): The distribution to transform from. It must be continuous, flow (Flow): A normalizing flow to transform the `distribution`. """ if not isinstance(flow, Flow): raise TypeError('`flow` is not an instance of `tfsnippet.flows.' 'Flow`: {!r}'.format(flow)) distribution = as_distribution(distribution) if not distribution.is_continuous: raise ValueError('{!r} cannot be transformed by a flow, because ' 'it is not continuous.'.format(distribution)) if not distribution.dtype.is_floating: raise ValueError( '{!r} cannot be transformed by a flow, because ' 'its data type is not float.'.format(distribution)) self._flow = flow self._distribution = distribution
def add(self, name, distribution, n_samples=None, group_ndims=0, is_reparameterized=None, flow=None): """ Add a stochastic node to the network. A :class:`StochasticTensor` will be created for this node. If `name` exists in `observed` dict, its value will be used as the observation of this node. Otherwise samples will be taken from `distribution`. Args: name (str): Name of the stochastic node. distribution (Distribution or zhusuan.distributions.Distribution): Distribution where the samples should be taken from. n_samples (int or tf.Tensor): Number of samples to take. If specified, `n_samples` will be taken, with a dedicated sampling dimension ``[n_samples]`` at the front. If not specified, just one sample will be taken, without the dedicated dimension. group_ndims (int or tf.Tensor): Number of dimensions at the end of ``[n_samples] + batch_shape`` to be considered as events group. (default 0) is_reparameterized: Whether or not the re-parameterization trick should be applied? (default :obj:`None`, following the setting of `distribution`) flow (BaseFlow): If specified, transform `distribution` by `flow`. Returns: StochasticTensor: The sampled stochastic tensor. Raises: TypeError: If `name` is not a str, or `distribution` is a :class:`TransformedDistribution`. KeyError: If :class:`StochasticTensor` with `name` already exists. ValueError: If `transform` cannot be applied. See Also: :meth:`tfsnippet.distributions.Distribution.sample` """ if not isinstance(name, six.string_types): raise TypeError('`name` must be a str') if name in self._stochastic_tensors: raise KeyError('StochasticTensor with name {!r} already exists in ' 'the BayesianNet. Names must be unique.'. format(name)) if flow is not None and name in self._observed and \ not flow.explicitly_invertible: raise TypeError('The observed variable {!r} expects `flow` to be ' 'explicitly invertible, but it is not: {!r}.'. format(name, flow)) distribution = as_distribution(distribution) if flow is not None: distribution = FlowDistribution(distribution, flow) if name in self._observed: t = StochasticTensor( distribution=distribution, tensor=self._observed[name], n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) else: t = distribution.sample( n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) assert(isinstance(t, StochasticTensor)) self._stochastic_tensors[name] = t return t
def add(self, name, distribution, n_samples=None, group_ndims=0, is_reparameterized=None): """ Add a stochastic node to the network. A :class:`StochasticTensor` will be created for this node. If `name` exists in `observed` dict, its value will be used as the observation of this node. Otherwise samples will be taken from `distribution`. Args: name (str): Name of the stochastic node. distribution (Distribution or zhusuan.distributions.Distribution): Distribution where the samples should be taken from. n_samples (int or tf.Tensor): Number of samples to take. If specified, `n_samples` will be taken, with a dedicated sampling dimension ``[n_samples]`` at the front. If not specified, just one sample will be taken, without the dedicated dimension. group_ndims (int or tf.Tensor): Number of dimensions at the end of ``[n_samples] + batch_shape`` to be considered as events group. (default 0) is_reparameterized: If observation is not given for `name`, this argument will be used to determine whether or not re-parameterization trick should be applied when taking samples from `distribution` (if not specified, use `distribution.is_reparameterized`). If observation is given for `name`, and this argument is set to :obj:`True`, it will be used to validate the observation. If this argument is set to :obj:`False`, `tf.stop_gradient` will be applied on the observation. Returns: StochasticTensor: The sampled stochastic tensor. Raises: TypeError: If `name` is not a str, or `distribution` is a :class:`TransformedDistribution`. KeyError: If :class:`StochasticTensor` with `name` already exists. ValueError: If `transform` cannot be applied, or `is_reparameterized = True`, but the observation is not re-parameterized. See Also: :meth:`tfsnippet.distributions.Distribution.sample` """ if not isinstance(name, six.string_types): raise TypeError('`name` must be a str') if name in self._stochastic_tensors: raise KeyError( 'StochasticTensor with name {!r} already exists in ' 'the BayesianNet. Names must be unique.'.format(name)) distribution = as_distribution(distribution) if name in self._observed: ob_tensor = self._observed[name] if isinstance(ob_tensor, StochasticTensor): if is_reparameterized and not ob_tensor.is_reparameterized: raise ValueError( '`is_reparameterized` is True, but the observation ' 'for `{}` is not re-parameterized: {}'.format( name, ob_tensor)) if is_reparameterized is None: is_reparameterized = ob_tensor.is_reparameterized if not is_reparameterized: ob_tensor = tf.stop_gradient(ob_tensor) t = StochasticTensor( distribution=distribution, tensor=ob_tensor, n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) else: t = distribution.sample( n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) assert (isinstance(t, StochasticTensor)) self._stochastic_tensors[name] = t return t
def test_type_error(self): with pytest.raises(TypeError, match='Type `int` cannot be casted into `tfsnippet.' 'distributions.Distribution`'): _ = as_distribution(1)
def test_zs_distribution(self): normal = zd.Normal(mean=0., std=1.) distrib = as_distribution(normal) self.assertIsInstance(distrib, Distribution) self.assertIsInstance(distrib, ZhuSuanDistribution) self.assertIs(distrib._distribution, normal)
def test_distribution(self): d = Distribution() distrib = as_distribution(d) self.assertIs(distrib, d)
def add(self, name, distribution, n_samples=None, group_ndims=0, is_reparameterized=None, transform=None): """ Add a stochastic node to the network. A :class:`StochasticTensor` will be created for this node. If `name` exists in `observed` dict, its value will be used as the observation of this node. Otherwise samples will be taken from `distribution`. Args: name (str): Name of the stochastic node. distribution (Distribution or zhusuan.distributions.Distribution): Distribution where the samples should be taken from. n_samples (int or tf.Tensor): Number of samples to take. If specified, `n_samples` will be taken, with a dedicated sampling dimension ``[n_samples]`` at the front. If not specified, just one sample will be taken, without the dedicated dimension. group_ndims (int or tf.Tensor): Number of dimensions at the end of ``[n_samples] + batch_shape`` to be considered as events group. (default 0) is_reparameterized: Whether or not the re-parameterization trick should be applied? (default :obj:`None`, following the setting of `distribution`) transform ((Tensor, Tensor) -> (tf.Tensor, tf.Tensor)): The function to transform (x, log_p) to (x', log_p'). If specified, a :class:`StochasticTensor` will be sampled, then transformed, then wrapped by a :class:`StochasticTensor` with :class:`TransformedDistribution`. Returns: StochasticTensor: The sampled stochastic tensor. Raises: TypeError: If `name` is not a str, or `distribution` is a :class:`TransformedDistribution`. KeyError: If :class:`StochasticTensor` with `name` already exists. ValueError: If `transform` cannot be applied. See Also: :meth:`tfsnippet.distributions.Distribution.sample` """ if not isinstance(name, six.string_types): raise TypeError('`name` must be a str') if name in self._stochastic_tensors: raise KeyError( 'StochasticTensor with name {!r} already exists in ' 'the BayesianNet. Names must be unique.'.format(name)) if isinstance(distribution, TransformedDistribution): raise TypeError('Cannot add `TransformedDistribution`.') if transform is not None and \ (not distribution.is_continuous or not distribution.is_reparameterized or is_reparameterized is False): raise ValueError('`transform` can only be applied on continuous, ' 're-parameterized variables.') if transform is not None and name in self._observed: raise ValueError('`observed` variable cannot be transformed.') distribution = as_distribution(distribution) if name in self._observed: t = StochasticTensor( distribution=distribution, tensor=self._observed[name], n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) else: t = distribution.sample( n_samples=n_samples, group_ndims=group_ndims, is_reparameterized=is_reparameterized, ) assert (isinstance(t, StochasticTensor)) # do transformation if transform is not None: t_log_p = t.log_prob() ft, ft_log_p = transform(t, t_log_p) ft = tf.convert_to_tensor(ft) ft_log_p = tf.convert_to_tensor(ft_log_p) if not ft.dtype.is_floating: raise ValueError('The transformed samples must be ' 'continuous: got {!r}'.format(ft)) t = StochasticTensor(distribution=TransformedDistribution( origin=t, transformed=ft, transformed_log_p=ft_log_p, is_reparameterized=t.is_reparameterized, is_continuous=True), tensor=ft, n_samples=t.n_samples, group_ndims=t.group_ndims, is_reparameterized=t.is_reparameterized) self._stochastic_tensors[name] = t return t
def test_distribution(self): d = Normal(mean=0., std=1.) distrib = as_distribution(d) self.assertIs(distrib, d)