This repository was archived by the owner on Jul 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Return ed.RandomVariables in initializers and operate on them in regu… #1182
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,8 +19,11 @@ | |
| from __future__ import division | ||
| from __future__ import print_function | ||
|
|
||
| import functools | ||
| import tensorflow as tf | ||
|
|
||
| from tensorflow_probability import edward2 as ed | ||
|
|
||
|
|
||
| class Softplus(tf.keras.constraints.Constraint): | ||
| """Softplus constraint.""" | ||
|
|
@@ -110,11 +113,7 @@ def __call__(self, shape=None, dtype=None, partition_info=None): | |
| raise ValueError('A TrainableInitializer must be built by a layer before ' | ||
| 'usage, and is currently only compatible with Bayesian ' | ||
| 'layers.') | ||
| noise = tf.random_normal(self.shape, dtype=self.dtype, seed=self.seed) | ||
| output = self.mean + self.stddev * noise | ||
| # TODO(trandustin): Hack to store parameters so KL reg. can operate on them. | ||
| output._parameters = (self.mean, self.stddev) # pylint: disable=protected-access | ||
| return output | ||
| return ed.Normal(loc=self.mean, scale=self.stddev) | ||
|
|
||
| def get_config(self): | ||
| return { | ||
|
|
@@ -151,12 +150,11 @@ def __init__(self, mean=0., stddev=1.): | |
| self.stddev = stddev | ||
|
|
||
| def __call__(self, x): | ||
| mean, stddev = x._parameters # pylint: disable=protected-access | ||
| variance2 = tf.square(self.stddev) | ||
| variance_ratio = tf.square(stddev) / variance2 | ||
| regularization = tf.square(mean - self.mean) / (2. * variance2) | ||
| regularization += (variance_ratio - 1. - tf.log(variance_ratio)) / 2. | ||
| return regularization | ||
| """Computes regularization given an ed.Normal random variable as input.""" | ||
| if not isinstance(x, ed.RandomVariable): | ||
| raise ValueError('Input must be an ed.RandomVariable.') | ||
| random_variable = ed.Normal(loc=self.mean, scale=self.stddev) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason to not create this object in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Breaks Eager unfortunately if |
||
| return random_variable.distribution.kl_divergence(x.distribution) | ||
|
|
||
| def get_config(self): | ||
| return { | ||
|
|
@@ -276,3 +274,18 @@ def build(self, input_shape): | |
| else: | ||
| self.bias = None | ||
| self.built = True | ||
|
|
||
| # TODO(trandustin): Waiting on T2T to drop dependence on | ||
| # TF<=1.12rc2. A TF commit enables tf.colocate_with to work for | ||
| # Tensor-like inputs. This lets us use the parent method instead of | ||
| # this one. | ||
| def _handle_weight_regularization(self, name, variable, regularizer): | ||
| """Create lambdas which compute regularization losses.""" | ||
|
|
||
| def _loss_for_variable(v): | ||
| """Creates a regularization loss `Tensor` for variable `v`.""" | ||
| with tf.name_scope(name + '/Regularizer'): | ||
| regularization = regularizer(v) | ||
| return regularization | ||
|
|
||
| self.add_loss(functools.partial(_loss_for_variable, variable)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that we could create and store this in the
buildfunction to remove a bit of Python overhead in eager mode, but it looks like Edward2 RVs only sample from the underlying distribution on the first access in eager mode due torv.valueusing a memoization strategy by storing the sample on the first access. It seems like it could potentially simplify some use cases ifrv.valuecalledrv.distribution.sampleon each access torv.value. That being said, you definitely thought much longer on the design of Edward[1|2], so I bet there is a reason that would make this undesirable.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We memoize for usability reasons, so that you operate on RandomVariables as if they were any other Tensor. For example, if you call
x = ed.Normal(...), you would expecty = x + 1andz = x + 1to return the same result.On caching: There's an issue that actually arises before Ed2, which is that during
__init__, Distributions call TF ops on its parameters. This breaks gradients ifDistribution.__init__isn't recorded in the gradient tape. @vdumoulin raised a point about this in Brain Chat.