在 Python 中组合 reduce 和 map 的最简洁方法
Cleanest way to combine reduce and map in Python
我正在做一点深度学习,我想抓取所有隐藏层的值。所以我最终写了这样的函数:
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)
砰。全部一行,漂亮而紧凑。但是我无法保留任何中间值。
那么,有没有什么办法既能吃我的蛋糕(漂亮的紧凑型单层蛋糕)又能吃(return 中间值)?
一般来说,itertools.accumulate() will do what 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]
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])
然后我需要向 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
有一个 accumulate
方法,但是使用自定义 Python 函数并不容易。所以有一个 np.maximum.accumulate
,但我不确定在这种情况下如何使用它。 (也是 np.cumsum
即 np.add.accumulate
)。
在Python2.x中,没有干净的单行代码。
在 Python 3 中,有 itertools.accumulate,但它仍然不是很干净,因为它不像 reduce 那样接受 "initial" 输入。
这是一个函数,虽然不如内置的理解语法那么好,但可以完成工作。
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 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+:
Python3.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)]
我正在做一点深度学习,我想抓取所有隐藏层的值。所以我最终写了这样的函数:
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)
砰。全部一行,漂亮而紧凑。但是我无法保留任何中间值。
那么,有没有什么办法既能吃我的蛋糕(漂亮的紧凑型单层蛋糕)又能吃(return 中间值)?
一般来说,itertools.accumulate() will do what 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]
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])
然后我需要向 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
有一个 accumulate
方法,但是使用自定义 Python 函数并不容易。所以有一个 np.maximum.accumulate
,但我不确定在这种情况下如何使用它。 (也是 np.cumsum
即 np.add.accumulate
)。
在Python2.x中,没有干净的单行代码。
在 Python 3 中,有 itertools.accumulate,但它仍然不是很干净,因为它不像 reduce 那样接受 "initial" 输入。
这是一个函数,虽然不如内置的理解语法那么好,但可以完成工作。
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 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+:
Python3.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)]