【问题标题】:Cleanest way to combine reduce and map in Python在 Python 中结合 reduce 和 map 的最简洁方法
【发布时间】:2017-02-02 22:18:06
【问题描述】:

我正在做一点深度学习,我想获取所有隐藏层的值。所以我最终写了这样的函数:

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)

巴姆。一条线,美观紧凑。但我不能保留任何中间值。

那么,有什么方法可以让我的蛋糕(漂亮紧凑的单衬里)也吃掉(返回中间值)?

【问题讨论】:

  • 您想保留哪些intermediate values
  • 告诉我们x,ws,bs,尤其是它们的尺寸。甚至可能是一个带有输出的样本集。
  • (w, b) 给了我一个语法错误(在 Py3 中)
  • 我认为这个问题并不重要,但形状是:x_shape:(n_samples, n_dims[0]),w_shapes:[(n_dims[i], n_dims[i+1]) for i in range(len(ws))],b_shapes:[(n_dims[i], ) for i in range(len(ws))]。我正在使用 python 2.7,很惊讶它会导致 Python 3 中的语法错误。

标签: python numpy deep-learning reduce


【解决方案1】:

一般来说,itertools.accumulate() 会做reduce() 做的事情,但也会给你中间值。也就是说,accumulate 不支持 start 值,因此它不适用于您的情况。

例子:

>>> 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]

【讨论】:

    【解决方案2】:

    dot 告诉我您正在使用一个或多个 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 使列表理解变得棘手。

    accumulate 使用第一项作为开始。我可以使用像

    这样的函数来解决这个问题
    def foo(u, wb):
        if u[0] is None: u=x   # x from global
        return np.maximum(0, u.dot(wb[0])+wb[1])
    

    然后我需要给wsbs添加额外的起始值:

    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 有一个accumulate 方法,但是对于自定义 Python 函数来说使用起来并不容易。所以有一个np.maximum.accumulate,但我不确定在这种情况下如何使用它。 (也是np.cumsum,即np.add.accumulate)。

    【讨论】:

    • 感谢您的详尽回答。考虑到 ws 的不同元素可能具有不同的维度,内部 numpy 累加器并不是真正有用。看起来没有干净的解决方案。
    【解决方案3】:

    在 Python 2.x 中,没有明确的单行代码。

    在 Python 3 中,有 itertools.accumulate,但它仍然不是很干净,因为它不像 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]
    

    【讨论】:

      【解决方案4】:

      您实际上可以使用 result = [y for y in [initial] for x in inputs for y in [f(x, y)]] 这个有点奇怪的模式来做到这一点。请注意,第一个和第三个for 并不是真正的循环,而是赋值——我们可以在理解中使用for var in [value]value 分配给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 引入了“海象”运算符:=,这给了我们另一种选择:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-10
        • 2011-03-21
        • 1970-01-01
        • 2011-05-09
        • 1970-01-01
        • 2014-07-29
        • 1970-01-01
        相关资源
        最近更新 更多