在Python中结合reduce和map的最干净方法

在Python中结合reduce和map的最干净方法,python,numpy,deep-learning,reduce,Python,Numpy,Deep Learning,Reduce,我正在做一些深入的学习,我想抓住所有隐藏层的值。因此,我最终编写了如下函数: def forward_pass(x, ws, bs): activations = [] u = x for w, b in zip(ws, bs): u = np.maximum(0, u.dot(w)+b) activations.append(u) return activations 如果不必获取中间值,我会使用更不详细的形式: out = r

我正在做一些深入的学习,我想抓住所有隐藏层的值。因此,我最终编写了如下函数:

def forward_pass(x, ws, bs):
    activations = []
    u = x
    for w, b in zip(ws, bs):
        u = np.maximum(0, u.dot(w)+b)
        activations.append(u)
    return activations
如果不必获取中间值,我会使用更不详细的形式:

out = reduce(lambda u, (w, b): np.maximum(0, u.dot(w)+b), zip(ws, bs), x)
砰。全部为一条线,美观紧凑。但我不能保留任何中间值

那么,有什么办法可以吃我的蛋糕(漂亮的紧凑型单衬里蛋糕)并且也吃它(返回中间值)?

一般来说,会做什么,但也会给你中间值。也就是说,累积不支持起始值,因此它不适用于您的情况

例如:

>>> import operator, functools, itertools
>>> functools.reduce(operator.mul, range(1, 11))
3628800
>>> list(itertools.accumulate(range(1, 11), operator.mul))
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

点告诉我您正在使用一个或多个numpy数组。因此,我将尝试:

In [28]: b=np.array([1,2,3])
In [29]: x=np.arange(9).reshape(3,3)
In [30]: ws=[x,x,x]

In [31]: forward_pass(x,ws,bs)
Out[31]: 
[array([[ 16,  19,  22],
        [ 43,  55,  67],
        [ 70,  91, 112]]), 
 array([[ 191,  248,  305],
        [ 569,  734,  899],
        [ 947, 1220, 1493]]), 
 array([[ 2577,  3321,  4065],
        [ 7599,  9801, 12003],
        [12621, 16281, 19941]])]
在py3中,我必须将
reduce
解决方案编写为:

In [32]: functools.reduce(lambda u, wb: np.maximum(0,
                   u.dot(wb[0])+wb[1]), zip(ws, bs), x)
Out[32]: 
array([[ 2577,  3321,  4065],
       [ 7599,  9801, 12003],
       [12621, 16281, 19941]])
从一个求值传递到下一个求值的中间值
u
,使得列表理解变得棘手

累计
使用第一项作为开始。我可以用这样的函数来解决这个问题

def foo(u, wb):
    if u[0] is None: u=x   # x from global
    return np.maximum(0, u.dot(wb[0])+wb[1])
然后我需要向
ws
bs
添加额外的起始值:

In [56]: list(itertools.accumulate(zip([None,x,x,x], np.array([0,1,2,3])), foo))
Out[56]: 
[(None, 0), 
 array([[ 16,  19,  22],
        [ 43,  55,  67],
        [ 70,  91, 112]]), 
 array([[ 191,  248,  305],
        [ 569,  734,  899],
        [ 947, 1220, 1493]]), 
 array([[ 2577,  3321,  4065],
        [ 7599,  9801, 12003],
        [12621, 16281, 19941]])]
下面是一个列表理解版本,使用外部
u

In [66]: u=x.copy()
In [67]: def foo1(wb):
    ...:     v = np.maximum(0, u.dot(wb[0])+wb[1])
    ...:     u[:]=v
    ...:     return v
    ...: 
In [68]: [foo1(wb) for wb in zip(ws,bs)]
Out[68]: 
[array([[ 16,  19,  22],
        [ 43,  55,  67],
        [ 70,  91, 112]]), 
 array([[ 191,  248,  305],
        [ 569,  734,  899],
        [ 947, 1220, 1493]]), 
 array([[ 2577,  3321,  4065],
        [ 7599,  9801, 12003],
        [12621, 16281, 19941]])]
与原始循环相比,
append
没有真正的优势


numpy.ufunc
有一个
acculate
方法,但这不容易与自定义Python函数一起使用。所以有一个
np.maximum.accumulate
,但我不确定在这种情况下如何使用它。(也是
np.cumsum
,它是
np.add.accumulate
)。

在Python2.x中,没有干净的一行程序

在Python 3中,有itertools.acculate,但它仍然不是真正干净的,因为它不像reduce那样接受“初始”输入

这是一个函数,虽然没有内置的理解语法那么好,但它完成了这项工作

def reducemap(func, sequence, initial=None, include_zeroth = False):
    """
    A version of reduce that also returns the intermediate values.
    :param func: A function of the form x_i_plus_1 = f(x_i, params_i)
        Where:
            x_i is the value passed through the reduce.
            params_i is the i'th element of sequence
            x_i_plus_i is the value that will be passed to the next step
    :param sequence: A list of parameters to feed at each step of the reduce.
    :param initial: Optionally, an initial value (else the first element of the sequence will be taken as the initial)
    :param include_zeroth: Include the initial value in the returned list.
    :return: A list of length: len(sequence), (or len(sequence)+1 if include_zeroth is True) containing the computed result of each iteration.
    """
    if initial is None:
        val = sequence[0]
        sequence = sequence[1:]
    else:
        val = initial
    results = [val] if include_zeroth else []
    for s in sequence:
        val = func(val, s)
        results.append(val)
    return results
测试:

assert reducemap(lambda a, b: a+b, [1, 2, -4, 3, 6, -7], initial=0) == [1, 3, -1, 2, 8, 1]
assert reducemap(lambda a, b: a+b, [1, 2, -4, 3, 6, -7]) == [3, -1, 2, 8, 1]
assert reducemap(lambda a, b: a+b, [1, 2, -4, 3, 6, -7], include_zeroth=True) == [1, 3, -1, 2, 8, 1]

实际上,您可以使用有点奇怪的模式
result=[y for y in[initial]for x in[f(x,y)]]中的y的输入]
来实现这一点。请注意,的第一个和第三个
实际上并不是循环,而是赋值-我们可以在理解中使用[value]
中的
for var将
赋值给
var
。例如:

def forward_pass(x, ws, bs):
    activations = []
    u = x
    for w, b in zip(ws, bs):
        u = np.maximum(0, u.dot(w)+b)
        activations.append(u)
    return activations
相当于:

def forward_pass(x, ws, bs):
    return [u for u in [x] for w, b in zip(ws, bs) for u in [np.maximum(0, u.dot(w)+b)]]
Python 3.8+:
Python 3.8引入了“walrus”操作符
:=
,这为我们提供了另一个选项:

def forward_pass(x, ws, bs):
    u = x
    return [u:=np.maximum(0, u.dot(w)+b) for w, b in zip(ws, bs)]

您希望保留哪些中间值?请告诉我们关于
x
ws
bs
,尤其是它们的尺寸。甚至可能是一个带有输出的样本集。
(w,b)
给了我一个语法错误(在Py3中)。我认为这对这个问题不重要,但是形状是:x_形状:
(n_样本,n_dims[0])
,w_形状:
[(n_dims[I],n_dims[I+1]),对于范围内的I(len而言,
,b_形状:
[(n_dims[I],。我使用的是Python2.7,很惊讶它会导致Python3中的语法错误。感谢您的详细回答。考虑到ws的不同元素可能具有不同的维度,内部numpy累加器并不是真正有用的。看起来没有干净的解决方案。