Skip to content

Commit

Permalink
Implement foldl and foldr for CNTK keras-team#12168 (keras-team#1…
Browse files Browse the repository at this point in the history
…2572)

* Implement functions `K.foldl` and `K.foldr` for CNTK

* Enable `test_foldl` and `test_foldr` backend tests for CNTK
  • Loading branch information
gyjelinek authored and fchollet committed Apr 3, 2019
1 parent b23c9fe commit ad5db10
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 4 deletions.
98 changes: 96 additions & 2 deletions keras/backend/cntk_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2792,8 +2792,102 @@ def map_fn(fn, elems, name=None, dtype=None):


def foldl(fn, elems, initializer=None, name=None):
raise NotImplementedError
"""Reduce `elems` by `fn` combined them from left to right on dimension 0.
# Arguments
fn: Callable that will be called upon each element in `elems`
(and on the optional `initializer`) passed as a second argument.
The first argument passed to `fn` is the accumulator which is the
accumulated value calculated from the preceding invocation of `fn`.
Example For `fn`:
```python
lambda acc, x: acc + x
```
elems: Tensor
initializer: (optional) Tensor, the initial value for the accumulator.
In case of None value is provided during the call
the first value is used (`elems[0]`) as `initializer` from `elems`
name: (optional) String, name for the foldl node in the graph.
# Returns
Same type and shape as `initializer`
# Raises:
TypeError: if `fn` is not callable.
TypeError: if `initializer` is neither a tensor nor None value.
TypeError: if `elems` is not a tensor.
"""
if not callable(fn):
raise TypeError("`fn` must be callable.")
if initializer is not None and not is_tensor(initializer):
raise TypeError("`initializer` must be a tensor or None")
if not is_tensor(elems):
raise TypeError('`elems` must be a tensor')

if initializer is None and shape(elems)[0] > 1:
initializer = elems[0]
elems = elems[1:]
elif initializer is None:
initializer = elems[0]
elems = None

accumulator = initializer
if elems is not None:
for i in range(shape(elems)[0]):
accumulator = fn(accumulator, elems[i])

if name is not None:
accumulator.name = str(name)

return reshape(accumulator, shape(initializer)[1:])


def foldr(fn, elems, initializer=None, name=None):
raise NotImplementedError
"""Reduce `elems` by `fn` combined them from right to left on dimension 0.
# Arguments
fn: Callable that will be called upon each element in `elems`
(and on the optional `initializer`) passed as a second argument.
The first argument passed to `fn` is the accumulator which is the
accumulated value calculated from the preceding invocation of `fn`.
Example For `fn`:
```python
lambda acc, x: acc + x
```
elems: Tensor
initializer: (optional) Tensor, the initial value for the accumulator.
In case of None value is provided during the call
the last value is used (`elems[-1]`) as `initializer` from `elems`
name: (optional) String, name for the foldr node in the graph.
# Returns
Same type and shape as `initializer`
# Raises:
TypeError: if `fn` is not callable.
TypeError: if `initializer` is neither a tensor nor None value.
TypeError: if `elems` is not a tensor.
"""
if not callable(fn):
raise TypeError("`fn` must be callable.")
if initializer is not None and not is_tensor(initializer):
raise TypeError("`initializer` must be a tensor or None")
if not is_tensor(elems):
raise TypeError('`elems` must be a tensor')

if initializer is None and shape(elems)[0] > 1:
initializer = elems[-1]
elems = elems[:-1]
elif initializer is None:
initializer = elems[0]
elems = None

accumulator = initializer
if elems is not None:
for i in range(shape(elems)[0]):
accumulator = fn(accumulator, elems[-i])

if name is not None:
accumulator.name = str(name)

return reshape(accumulator, shape(initializer)[1:])
2 changes: 0 additions & 2 deletions tests/keras/backend/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1948,15 +1948,13 @@ def test_map(self):
assert_allclose(x.sum(axis=1), kx, atol=1e-05)
assert_allclose(kx, kx2, atol=1e-05)

@pytest.mark.skipif(K.backend() == 'cntk', reason='Not supported.')
def test_foldl(self):
x = np.random.rand(10, 3).astype(np.float32)
kx = K.eval(K.foldl(lambda a, b: a + b, K.variable(x)))

assert (3,) == kx.shape
assert_allclose(x.sum(axis=0), kx, atol=1e-05)

@pytest.mark.skipif(K.backend() == 'cntk', reason='Not supported.')
def test_foldr(self):
# This test aims to make sure that we walk the array from right to left
# and checks it in the following way: multiplying left to right 1e-40
Expand Down

0 comments on commit ad5db10

Please sign in to comment.