ProbabilisticTensorDictModule#
- class tensordict.nn.ProbabilisticTensorDictModule(in_keys: NestedKey | List[NestedKey] | Dict[str, NestedKey], out_keys: NestedKey | List[NestedKey] | None = None, *, default_interaction_type: InteractionType = InteractionType.DETERMINISTIC, distribution_class: type = <class 'tensordict.nn.distributions.continuous.Delta'>, distribution_kwargs: dict | None = None, return_log_prob: bool = False, log_prob_keys: List[NestedKey] | None = None, log_prob_key: NestedKey | None = None, cache_dist: bool = False, n_empirical_estimate: int = 1000, num_samples: int | torch.Size | None = None, generator: torch.Generator | int | NestedKey | None = None)#
Bases:
TensorDictModuleBaseA module that builds a distribution from entries of a tensordict and writes a value from it.
The module has no parameters. It reads the distribution parameters at
in_keys, buildsdistribution_class(**params, **distribution_kwargs), and writes one value at each of theout_keys. To compute the parameters with a network, put aTensorDictModulebefore this module in aProbabilisticTensorDictSequential. To get the distribution without a value, callget_dist(). To get the log-probability of a value, calllog_prob(). See Probabilistic modules for a guide.The value depends on the interaction type: the value of
interaction_type()if it is set (seeset_interaction_type),default_interaction_typeotherwise. SeeInteractionTypefor what each type reads from the distribution.The default
distribution_classisDelta, which always returns its parameter. With it, the module copies its input toout_keys.If every key in
out_keysis already in the input andskip_existing()isTrue(seeset_skip_existing), the module returns the input unchanged. Otherwise it computes a new value and overwrites the entry.With
return_log_prob=Trueand theRANDOMinteraction type, the module draws and scores the value in one call if the distribution defines the method:dist.rsample_and_log_prob(sample_shape)ifdist.has_rsample,dist.sample_and_log_prob(sample_shape)otherwise. The method returns a(sample, log_prob)tuple, withlog_probin the layout ofdist.log_prob. Define it whendist.log_prob(sample)cannot recover the draw exactly, for example for a tanh-squashed normal that saturates. Otherwise the module callsdist.log_probon the sample. Thetorch.distributionsclasses do not define these methods;CompositeDistributiondefines both.Log-probability keys
With
return_log_prob=True, the module also writes the log-probability of the value. The key depends oncomposite_lp_aggregate()(Falseby default, seeset_composite_lp_aggregate) and on the number of sampled keys:Case
composite_lp_aggregate()isFalse(default)composite_lp_aggregate()isTrueOne sampled key,
"action""action_log_prob""sample_log_prob"One nested key,
("agent", "action")("agent", "action_log_prob")"sample_log_prob"Several sampled keys from a
CompositeDistribution,"cont"and("nested", "disc")"cont_log_prob"and("nested", "disc_log_prob")The same two entries, and
"sample_log_prob"with their sumSeveral probabilistic modules in a
ProbabilisticTensorDictSequentialOne
<key>_log_probentry per sampled keyEach module writes
"sample_log_prob", so the entry holds the log-probability of the last module only. CallProbabilisticTensorDictSequential.log_prob()to get the sum.The suffix
_log_probgoes on the last element of a nested key. Passlog_prob_key(one sampled key) orlog_prob_keys(several sampled keys) to choose other keys.log_prob()returns a tensor, except for aCompositeDistributionwhilecomposite_lp_aggregate()isFalse: then it returns aTensorDictwith the<key>_log_probentries of the table. WithTrue, the tensor is the sum over the sampled keys and their event dimensions.Set
composite_lp_aggregate()before you build the module. If the value changed since then,log_prob_key,log_prob_keys,log_prob()and a call withreturn_log_prob=Truecan raise aRuntimeError.- Parameters:
in_keys (NestedKey, list of NestedKey, or dict) – the entries that hold the parameters of the distribution. With a key or a list of keys, the last element of each key is the keyword argument of
distribution_classthat it feeds:["loc", "scale"]for aNormal, and("agent", "loc")feedsloc. With a dict, the keys are the keyword arguments and the values are the tensordict keys:{"loc": "mu", "scale": "sigma"}.out_keys (NestedKey or list of NestedKey, optional) – the keys where the values are written, one per value that the distribution returns. If the input already has these keys, the module overwrites them, unless
skip_existing()isTrue. Defaults toNone: for aCompositeDistribution, the values of thename_mapofdistribution_kwargsif it has one, and the keys of itsdistribution_mapotherwise. A component thatname_mapleaves out is written under its own name but is not inout_keys, and the module warns. Starting with TensorDict 0.17, the default will change toout_keysthat include these components. For another distribution,Nonegives the key"_": the module writes the sample there, and warns the first time it does. Starting with TensorDict 0.17, the default will change to not writing the sample at"_", which marks an output that is not written, as inTensorDictModule. Passout_keysto keep the current keys.
- Keyword Arguments:
default_interaction_type (InteractionType or str, optional) – the interaction type that the module uses when
interaction_type()returnsNone, that is outside of aset_interaction_typeblock.MODEandMEDIANraiseNotImplementedErrorif the distribution does not have them.MEANaveragesn_empirical_estimatesamples if the distribution has no mean. TorchRL collectors sample withInteractionType.RANDOMby default (theirexploration_typeargument). Defaults toInteractionType.DETERMINISTIC.distribution_class (type or Callable, optional) – the distribution class, or a callable that takes the parameters as keyword arguments and returns a
torch.distributions.Distribution. For aCompositeDistribution,distribution_kwargsholds itsdistribution_map, andout_keyscan be left out. Defaults toDelta.distribution_kwargs (dict, optional) – extra keyword arguments passed to
distribution_class, for example constants. To move the tensors in it with the module (module.to(device),module.to(dtype)), pass aTensorDictParams. Defaults toNone.return_log_prob (bool, optional) – if
True, the module also writes the log-probability of the value, at the keys of the table above. Defaults toFalse.log_prob_keys (list of NestedKey, optional) – the keys of the log-probabilities, one per sampled key, in place of the
<key>_log_probdefaults, including for aCompositeDistribution. Ifcomposite_lp_aggregate()isTrue, passing it raises aRuntimeError. Defaults toNone.log_prob_key (NestedKey, optional) –
the key of the log-probability: of the one sampled key, or, while
composite_lp_aggregate()isTrue, of their sum. With several sampled keys while it isFalse, passing it raises aRuntimeError. Defaults toNone, which gives the key of the table above.With
return_log_prob=True, a customlog_prob_keyorlog_prob_keysentry equal to a sample key, inside one, or holding one raises aValueError, because the log-probability would overwrite the sample. The check runs whendistribution_classis a class (and, for aCompositeDistribution, whendistribution_kwargsholds itsdistribution_map).cache_dist (bool, optional) – has no effect. Deprecated: passing
Truewarns, and the argument will be removed in TensorDict 0.17. Defaults toFalse.n_empirical_estimate (int, optional) – the number of samples that
MEANaverages when the distribution has no mean. Defaults to1000.num_samples (int or Size, optional) – the number of values per call. The output tensordict is expanded to
num_samples + batch_size, and each value gets these leading dimensions. With theRANDOMinteraction type, each value is a new draw. With another interaction type, the module repeats its one deterministic value. Defaults toNone: one value, no extra dimension.generator (Generator, int or NestedKey, optional) –
the random number generator of the sampling, used by the
RANDOMinteraction type and by the sampled estimate ofMEAN.None(default): the global PyTorch generator.a
torch.Generator: used for every call. Its state advances in place, so consecutive calls draw different values. It must be on the device of the distribution parameters.an
int: the seed of a CPU generator that the constructor creates, then used as above.a
NestedKey: the key of an entry of the input, read at each call. The entry holds atorch.Generator, used as above, or an integer seed (a scalar tensor or anint). A seed gives a new generator, and after sampling the module writes a new seed, drawn from that generator, back at the key. If the input has no such entry, the global generator is used.
Sampling with a generator does not change the state of the global generator, and works with every distribution. Under
torch.compile(), the argument is ignored, without a warning.
Examples
Sample from a normal distribution, and write the log-probability of the sample:
>>> import torch >>> from torch import distributions as d >>> from tensordict import TensorDict >>> from tensordict.nn import InteractionType, ProbabilisticTensorDictModule >>> module = ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], ... out_keys=["action"], ... distribution_class=d.Normal, ... default_interaction_type=InteractionType.RANDOM, ... return_log_prob=True, ... ) >>> _ = torch.manual_seed(0) >>> td = module(TensorDict(loc=torch.zeros(3), scale=torch.ones(3))) >>> td["action"] tensor([ 1.5410, -0.2934, -2.1788]) >>> td["action_log_prob"] tensor([-2.1063, -0.9620, -3.2925])
Compute the parameters with a network, in a
ProbabilisticTensorDictSequential:>>> from tensordict.nn import ( ... ProbabilisticTensorDictModule, ... ProbabilisticTensorDictSequential, ... TensorDictModule, ... ) >>> from tensordict.nn.distributions import NormalParamExtractor >>> from torch.distributions import Normal, Independent >>> td = TensorDict( ... {"input": torch.randn(3, 4), "hidden": torch.randn(3, 8)}, [3] ... ) >>> net = torch.nn.GRUCell(4, 8) >>> module = TensorDictModule( ... net, in_keys=["input", "hidden"], out_keys=["params"] ... ) >>> normal_params = TensorDictModule( ... NormalParamExtractor(), in_keys=["params"], out_keys=["loc", "scale"] ... ) >>> def IndepNormal(**kwargs): ... return Independent(Normal(**kwargs), 1) >>> prob_module = ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], ... out_keys=["action"], ... distribution_class=IndepNormal, ... return_log_prob=True, ... ) >>> td_module = ProbabilisticTensorDictSequential( ... module, normal_params, prob_module ... ) >>> params = TensorDict.from_module(td_module) >>> with params.to_module(td_module): ... _ = td_module(td) >>> print(td) TensorDict(batch_size=[3], fields={ action: f32[4], action_log_prob: f32[], hidden: f32[8], input: f32[4], loc: f32[4], params: f32[8], scale: f32[4]}) >>> with params.to_module(td_module): ... dist = td_module.get_dist(td) >>> print(dist) Independent(Normal(loc: torch.Size([3, 4]), scale: torch.Size([3, 4])), 1) >>> # we can also apply the module to the TensorDict with vmap >>> from torch import vmap >>> params = params.expand(4) >>> def func(td, params): ... with params.to_module(td_module): ... return td_module(td) >>> td_vmap = vmap(func, (None, 0))(td, params) >>> print(td_vmap) TensorDict(batch_size=[4,3], fields={ action: f32[4], action_log_prob: f32[], hidden: f32[8], input: f32[4], loc: f32[4], params: f32[8], scale: f32[4]})
- build_dist_from_params(tensordict: TensorDictBase) Distribution#
Creates a
torch.distributions.Distributioninstance with the parameters provided in the input tensordict.- Parameters:
tensordict (TensorDictBase) – the input, with the distribution parameters at
in_keys.- Returns:
A
torch.distributions.Distributioninstance created from the input tensordict.- Raises:
TypeError – if the entries of the input do not match the keyword arguments of the distribution.
Examples
>>> import torch >>> from torch import distributions as d >>> from tensordict import TensorDict >>> from tensordict.nn import ProbabilisticTensorDictModule >>> module = ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], out_keys=["action"], distribution_class=d.Normal ... ) >>> td = TensorDict(loc=torch.zeros(3), scale=torch.ones(3)) >>> dist = module.get_dist(td) >>> dist Normal(loc: torch.Size([3]), scale: torch.Size([3])) >>> dist.entropy() tensor([1.4189, 1.4189, 1.4189])
- property dist_params_keys: List[NestedKey]#
the
in_keys.- Type:
The keys of the distribution parameters in the input
- property dist_sample_keys: List[NestedKey]#
the
out_keyswithout the log-probability keys.- Type:
The keys of the sampled values
- forward(tensordict: TensorDictBase = None, tensordict_out: TensorDictBase | None = None, _requires_sample: bool = True) TensorDictBase#
Builds the distribution from
tensordictand writes a value from it atout_keys.With
return_log_prob=True, the module also writes the log-probability of the value. You can also call the module with the parameters as keyword arguments,module(loc=..., scale=...), and get the outputs back (seedispatch()).- Parameters:
tensordict (TensorDictBase) – the input, with the distribution parameters at
in_keys.tensordict_out (TensorDictBase, optional) – the tensordict where the outputs are written. Defaults to
None, which writes them intotensordict.
- Returns:
The tensordict that holds the outputs,
tensordict_outortensordict. Withnum_samples, a new tensordict, which is that tensordict expanded tonum_samples + batch_size. The input does not get the outputs then.
- get_dist(tensordict: TensorDictBase) Distribution#
Creates a
torch.distributions.Distributioninstance with the parameters provided in the input tensordict.- Parameters:
tensordict (TensorDictBase) – the input, with the distribution parameters at
in_keys.- Returns:
A
torch.distributions.Distributioninstance created from the input tensordict.- Raises:
TypeError – if the entries of the input do not match the keyword arguments of the distribution.
Examples
>>> import torch >>> from torch import distributions as d >>> from tensordict import TensorDict >>> from tensordict.nn import ProbabilisticTensorDictModule >>> module = ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], out_keys=["action"], distribution_class=d.Normal ... ) >>> td = TensorDict(loc=torch.zeros(3), scale=torch.ones(3)) >>> dist = module.get_dist(td) >>> dist Normal(loc: torch.Size([3]), scale: torch.Size([3])) >>> dist.entropy() tensor([1.4189, 1.4189, 1.4189])
- property in_keys#
The keys that the module reads from the input tensordict.
- static is_tdmodule_compatible(module)#
Returns
Trueifmodulehas both anin_keysand anout_keysattribute.TensorDictSequentialwraps the callables that fail this check in aWrapModule.- Parameters:
module (object) – the object to check.
- log_prob(tensordict, *, dist: Distribution | None = None)#
Computes the log-probability of the value stored in
tensordict.- Parameters:
tensordict (TensorDictBase) – the input, with the distribution parameters at
in_keysand the value atout_keys.- Keyword Arguments:
dist (torch.distributions.Distribution, optional) – the distribution. Defaults to
None, which builds it fromtensordictwithget_dist().- Returns:
A tensor. For a
CompositeDistributionwhilecomposite_lp_aggregate()isFalse, a tensordict with one<key>_log_probentry per sampled key (see the log-probability key table). For a composite distribution while it isTrue, the sum of these entries, which are also written intotensordict.
Examples
>>> import torch >>> from torch import distributions as d >>> from tensordict import TensorDict >>> from tensordict.nn import ProbabilisticTensorDictModule >>> module = ProbabilisticTensorDictModule( ... in_keys=["loc", "scale"], out_keys=["action"], distribution_class=d.Normal ... ) >>> td = module(TensorDict(loc=torch.zeros(3), scale=torch.ones(3))) >>> td["action"] tensor([0., 0., 0.]) >>> module.log_prob(td) tensor([-0.9189, -0.9189, -0.9189])
- property log_prob_key#
The key where the log-probability is written, when there is one such key.
See the log-probability key table.
- Raises:
RuntimeError – if
composite_lp_aggregate()changed since the module was built, or if it isFalseand the module has several sampled keys (uselog_prob_keys).
- property log_prob_keys#
The list of keys where the log-probabilities are written.
One key per sampled key when
composite_lp_aggregate()isFalse, and[log_prob_key]when it isTrue. See the log-probability key table.- Raises:
RuntimeError – if
composite_lp_aggregate()changed since the module was built.
- property out_keys: List[NestedKey]#
the sampled keys, then the log-probability keys if
return_log_prob=True.See the log-probability key table.
- Type:
The keys that the module writes
- property out_keys_source#
All the keys that the module writes, before
select_out_keys().
- reset_out_keys()#
Undoes
select_out_keys(): the module writes all itsout_keysagain.- Returns:
self.
Examples
>>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule >>> mod = TensorDictModule( ... lambda x, y: (x + 2, y + 2), in_keys=["a", "b"], out_keys=["c", "d"] ... ) >>> _ = mod.select_out_keys("d") >>> mod.out_keys ['d'] >>> _ = mod.reset_out_keys() >>> mod.out_keys ['c', 'd'] >>> td = mod(TensorDict(a=torch.zeros(()), b=torch.ones(()))) >>> sorted(td.keys()) ['a', 'b', 'c', 'd']
- reset_parameters_recursive(parameters: TensorDictBase | None = None) TensorDictBase | None#
Resets the parameters of every submodule that has a
reset_parametersmethod.Without
parameters, the parameters of the module are reset in place. Withparameters, the module is left as it is: the values inparametersare reset in place, as if they were loaded into the module (seeto_module()). Use this for parameters kept outside the module, for example in a functional call. Values that share their storage with the module parameters, such as those offrom_module(), reset the module too.- Parameters:
parameters (TensorDictBase, optional) – the parameters to reset, with the structure of
from_module()and an empty batch size. Defaults toNone, which resets the parameters of the module.- Returns:
Nonewithoutparameters. Otherwise a new tensordict of tensors that share their storage withparametersand require gradients.- Raises:
RuntimeError – if
parametershas batch dimensions.
Without
parameters, the call warns if no submodule has areset_parametersmethod.Examples
>>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule >>> from torch import nn >>> net = nn.Sequential(nn.Linear(2, 3), nn.ReLU()) >>> old_weight = net[0].weight.clone() >>> module = TensorDictModule(net, in_keys=["x"], out_keys=["y"]) >>> module.reset_parameters_recursive() >>> torch.equal(old_weight, net[0].weight) False
The parameters can also live outside the module:
>>> params = TensorDict.from_module(module).clone() >>> old_params = params.clone() >>> _ = module.reset_parameters_recursive(params) >>> (old_params == params).any() False >>> torch.equal(net[0].weight, old_params["module", "0", "weight"]) True
- select_out_keys(*out_keys) TensorDictModuleBase#
Keeps only some of the
out_keysin the output of the module, and returns the module.Use it to drop intermediate outputs, for example those of a
TensorDictSequential. The module changes in place:out_keysbecomes the selected keys, andout_keys_sourcekeeps the full list. A call still computes all the outputs, and then removes the ones that are not selected. It removes nothing else, so the input entries stay in the output. When you call the module with tensors (seedispatch()), it returns only the selected outputs.reset_out_keys()undoes the selection.- Parameters:
*out_keys (NestedKey) – the keys to keep. Each one must be in
out_keys.- Returns:
self.- Raises:
ValueError – if a key is not in
out_keys. The error names the missing keys, and is also aRuntimeError.
Examples
>>> import torch >>> from tensordict import TensorDict >>> from tensordict.nn import TensorDictModule >>> mod = TensorDictModule( ... lambda x, y: (x + 2, y + 2), in_keys=["a", "b"], out_keys=["c", "d"] ... ) >>> _ = mod.select_out_keys("d") >>> mod.out_keys, mod.out_keys_source (['d'], ['c', 'd']) >>> td = mod(TensorDict(a=torch.zeros(()), b=torch.ones(()))) >>> sorted(td.keys()) ['a', 'b', 'd'] >>> mod(torch.zeros(()), torch.ones(())) tensor(3.) >>> _ = mod.reset_out_keys() >>> sorted(mod(TensorDict(a=torch.zeros(()), b=torch.ones(()))).keys()) ['a', 'b', 'c', 'd']
It works on a
TensorDictSequentialtoo:>>> from tensordict.nn import TensorDictSequential >>> seq = TensorDictSequential( ... TensorDictModule(lambda x: x + 1, in_keys=["x"], out_keys=["y"]), ... TensorDictModule(lambda y: y + 1, in_keys=["y"], out_keys=["z"]), ... ) >>> _ = seq.select_out_keys("z") >>> sorted(seq(TensorDict(x=torch.zeros(()))).keys()) ['x', 'z']