Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 8cfbbd9

Browse files
Guarantee that branch shapes match in the gradient of IfElse
1 parent 482298b commit 8cfbbd9

2 files changed

Lines changed: 82 additions & 20 deletions

File tree

aesara/ifelse.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from aesara.graph.op import _NoPythonOp
2525
from aesara.graph.rewriting.basic import GraphRewriter, in2out, node_rewriter
2626
from aesara.graph.type import HasDataType, HasShape
27-
from aesara.tensor.shape import Reshape, Shape, SpecifyShape, Unbroadcast
27+
from aesara.tensor.shape import Reshape, Shape, SpecifyShape, Unbroadcast, specify_shape
2828

2929

3030
if TYPE_CHECKING:
@@ -254,33 +254,42 @@ def grad(self, ins, grads):
254254
# Since input true/false entries must have the same dtypes, we need to
255255
# cast the zeros to the corresponding `grads` dtypes and not the input
256256
# dtypes.
257-
inputs_true_grad = (
258-
[condition]
259-
+ grads
260-
+ [
261-
at.basic.zeros_like(t, dtype=grads[i].dtype)
262-
for i, t in enumerate(inputs_true_branch)
263-
]
257+
# The `grads` can also have different shapes than the `inputs`, so we
258+
# effectively assert that the shapes are preserved in each branch.
259+
# TODO FIXME: This doesn't seem like a sufficient solution to the
260+
# problem.
261+
inputs_true_grads = if_true_op(
262+
*(
263+
[condition]
264+
+ [specify_shape(g, i.shape) for g, i in zip(grads, inputs_true_branch)]
265+
+ [
266+
at.basic.zeros_like(t, dtype=grads[i].dtype)
267+
for i, t in enumerate(inputs_true_branch)
268+
]
269+
),
270+
return_list=True,
264271
)
265-
inputs_false_grad = (
266-
[condition]
267-
+ [
268-
at.basic.zeros_like(f, dtype=grads[i].dtype)
269-
for i, f in enumerate(inputs_false_branch)
270-
]
271-
+ grads
272+
inputs_false_grads = if_false_op(
273+
*(
274+
[condition]
275+
+ [
276+
at.basic.zeros_like(f, dtype=grads[i].dtype)
277+
for i, f in enumerate(inputs_false_branch)
278+
]
279+
+ [
280+
specify_shape(g, i.shape)
281+
for g, i in zip(grads, inputs_false_branch)
282+
]
283+
),
284+
return_list=True,
272285
)
273286

274287
# `condition` does affect the elements of the output so it is connected.
275288
# For the sake of making the gradient convenient we assume that
276289
# condition + epsilon always triggers the same branch as condition
277290
condition_grad = condition.zeros_like().astype(config.floatX)
278291

279-
return (
280-
[condition_grad]
281-
+ if_true_op(*inputs_true_grad, return_list=True)
282-
+ if_false_op(*inputs_false_grad, return_list=True)
283-
)
292+
return [condition_grad] + inputs_true_grads + inputs_false_grads
284293

285294
def make_thunk(self, node, storage_map, compute_map, no_recycling, impl=None):
286295
cond = node.inputs[0]

tests/test_ifelse.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,56 @@ def test_nested():
718718
linker = aesara.link.vm.VMLinker(lazy=True)
719719
f = function([c1, c2, x1, x2], t4, mode=Mode(linker=linker, optimizer="fast_run"))
720720
assert f(1, 0, np.array(10, dtype=x1.dtype), 0) == 20.5
721+
722+
723+
def test_DimShuffle_drop():
724+
c = scalar("c")
725+
x = scalar("x")
726+
y = vector("y")
727+
728+
cost = ifelse(c, x.dimshuffle("x"), y).sum()
729+
730+
# Sum{acc_dtype=float64} [id A] <TensorType(float64, ())>
731+
# |if{} [id B] <TensorType(float64, (None,))>
732+
# |c [id C] <TensorType(float64, ())>
733+
# |InplaceDimShuffle{x} [id D] <TensorType(float64, (1,))>
734+
# | |x [id E] <TensorType(float64, ())>
735+
# |y [id F] <TensorType(float64, (None,))>
736+
737+
out = aesara.grad(cost, y)
738+
assert out.type.shape == (None,)
739+
740+
out = aesara.grad(cost, x)
741+
742+
#
743+
# `DimShuffle.L_op` `inputs`
744+
#
745+
# x [id A] <TensorType(float64, ())>
746+
747+
#
748+
# `DimShuffle.L_op` `outputs`
749+
#
750+
# InplaceDimShuffle{x} [id B] <TensorType(float64, (1,))>
751+
# |x [id A] <TensorType(float64, ())>
752+
753+
#
754+
# `DimShuffle.L_op` `output_grads`
755+
#
756+
# if{} [id C] <TensorType(float64, (None,))>
757+
# |c [id D] <TensorType(float64, ())>
758+
# |Elemwise{second} [id E] <TensorType(float64, (None,))>
759+
# | |if{} [id F] <TensorType(float64, (None,))>
760+
# | | |c [id D] <TensorType(float64, ())>
761+
# | | |InplaceDimShuffle{x} [id B] <TensorType(float64, (1,))>
762+
# | | |y [id G] <TensorType(float64, (None,))>
763+
# | |InplaceDimShuffle{x} [id H] <TensorType(float64, (1,))>
764+
# | |Elemwise{second,no_inplace} [id I] <TensorType(float64, ())>
765+
# | |Sum{acc_dtype=float64} [id J] <TensorType(float64, ())>
766+
# | | |if{} [id F] <TensorType(float64, (None,))>
767+
# | |TensorConstant{1.0} [id K] <TensorType(float64, ())>
768+
# |Elemwise{second,no_inplace} [id L] <TensorType(float64, (1,))>
769+
# |InplaceDimShuffle{x} [id B] <TensorType(float64, (1,))>
770+
# |InplaceDimShuffle{x} [id M] <TensorType(float64, (1,))>
771+
# |TensorConstant{0.0} [id N] <TensorType(float64, ())>
772+
773+
assert out.type.shape == ()

0 commit comments

Comments
 (0)