Skip to content

Commit

Permalink
Add map, foldl, foldr to the backend (keras-team#4461)
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloskath authored and fchollet committed Nov 23, 2016
1 parent 50fdb87 commit 2878f60
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 0 deletions.
49 changes: 49 additions & 0 deletions keras/backend/tensorflow_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1980,3 +1980,52 @@ def ctc_decode(y_pred, input_length, greedy=True, beam_width=100,
for st in decoded]

return (decoded_dense, log_prob)


# HIGH ORDER FUNCTIONS

def map_fn(fn, elems, name=None):
'''Map the function fn over the elements elems and return the outputs.
# Arguments
fn: Callable that will be called upon each element in elems
elems: tensor
name: A string name for the map node in the graph
# Returns
Tensor with first dimension equal to the elems and second depending on
fn
'''
return tf.map_fn(fn, elems, name=name)


def foldl(fn, elems, initializer=None, name=None):
'''Reduce elems using fn to combine them from left to right.
# Arguments
fn: Callable that will be called upon each element in elems and an
accumulator, for instance lambda acc, x: acc + x
elems: tensor
initializer: The first value used (elems[0] in case of None)
name: A string name for the foldl node in the graph
# Returns
Same type and shape as initializer
'''
return tf.foldl(fn, elems, initializer=initializer, name=name)


def foldr(fn, elems, initializer=None, name=None):
'''Reduce elems using fn to combine them from right to left.
# Arguments
fn: Callable that will be called upon each element in elems and an
accumulator, for instance lambda acc, x: acc + x
elems: tensor
initializer: The first value used (elems[-1] in case of None)
name: A string name for the foldr node in the graph
# Returns
Same type and shape as initializer
'''
return tf.foldr(fn, elems, initializer=initializer, name=name)
65 changes: 65 additions & 0 deletions keras/backend/theano_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1851,3 +1851,68 @@ def ctc_step(y_true_step, y_pred_step, input_length_step, label_length_step):

ret = ret.dimshuffle('x', 0)
return ret


# HIGH ORDER FUNCTIONS

def map_fn(fn, elems, name=None):
'''Map the function fn over the elements elems and return the outputs.
# Arguments
fn: Callable that will be called upon each element in elems
elems: tensor, at least 2 dimensional
name: A string name for the map node in the graph
# Returns
Tensor with first dimension equal to the elems and second depending on
fn
'''
return theano.map(fn, elems, name=name)[0]


def foldl(fn, elems, initializer=None, name=None):
'''Reduce elems using fn to combine them from left to right.
# Arguments
fn: Callable that will be called upon each element in elems and an
accumulator, for instance lambda acc, x: acc + x
elems: tensor
initializer: The first value used (elems[0] in case of None)
name: A string name for the foldl node in the graph
# Returns
Same type and shape as initializer
'''
if initializer is None:
initializer = elems[0]
elems = elems[1:]

# We need to change the order of the arguments because theano accepts x as
# first parameter and accumulator as second
fn2 = lambda x, acc: fn(acc, x)

return theano.foldl(fn2, elems, initializer, name=name)[0]


def foldr(fn, elems, initializer=None, name=None):
'''Reduce elems using fn to combine them from right to left.
# Arguments
fn: Callable that will be called upon each element in elems and an
accumulator, for instance lambda acc, x: acc + x
elems: tensor
initializer: The first value used (elems[-1] in case of None)
name: A string name for the foldr node in the graph
# Returns
Same type and shape as initializer
'''
if initializer is None:
initializer = elems[-1]
elems = elems[:-1]

# We need to change the order of the arguments because theano accepts x as
# first parameter and accumulator as second
fn2 = lambda x, acc: fn(acc, x)

return theano.foldr(fn2, elems, initializer, name=name)[0]
29 changes: 29 additions & 0 deletions tests/keras/backend/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,35 @@ def test_sparse_concat(self):
assert k_s_d.shape == k_d.shape
assert_allclose(k_s_d, k_d, atol=1e-05)

def test_map(self):
x = np.random.rand(10, 3).astype(np.float32)
for K in [KTF, KTH]:
kx = K.eval(K.map_fn(K.sum, x))

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

def test_foldl(self):
x = np.random.rand(10, 3).astype(np.float32)
for K in [KTF, KTH]:
kx = K.eval(K.foldl(lambda a, b: a+b, x))

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

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
# cannot be held into a float32 so it causes an underflow while from
# right to left we have no such problem and the result is larger
x = np.array([1e-20, 1e-20, 10, 10, 10], dtype=np.float32)
for K in [KTF, KTH]:
p1 = K.eval(K.foldl(lambda a, b: a*b, x))
p2 = K.eval(K.foldr(lambda a, b: a*b, x))

assert p1 < p2
assert 9e-38 < p2 <= 1e-37


if __name__ == '__main__':
pytest.main([__file__])

0 comments on commit 2878f60

Please sign in to comment.