@hao peng提供了完全正确的答案(没有警告),但没有清楚地解释解决方案。这对于评论来说太长了,所以我会寻求答案。
让我们从分析几个答案开始(numpy
仅限纯答案):
这个在数学上是正确的,但仍然给我们一个警告。让我们看一下代码:
def sigmoid(x):
return np.where(
x >= 0, # condition
1 / (1 + np.exp(-x)), # For positive values
np.exp(x) / (1 + np.exp(x)) # For negative values
)
当两个分支都被评估时(它们是参数,它们必须是),第一个分支会给我们一个负值警告,第二个是正值。
虽然会发出警告,但不会合并溢出的结果,因此结果是正确的。
缺点
- 对两个分支进行不必要的评估(根据需要进行两倍的操作)
- 抛出警告
这几乎是正确的,但仅适用于浮点值,见下文:
def sigmoid(x):
return np.piecewise(
x,
[x > 0],
[lambda i: 1 / (1 + np.exp(-i)), lambda i: np.exp(i) / (1 + np.exp(i))],
)
sigmoid(np.array([0.0, 1.0])) # [0.5 0.73105858] correct
sigmoid(np.array([0, 1])) # [0, 0] incorrect
为什么?
@mhawke在另一个线程中提供了更长的答案,但要点是:
似乎 piecewise() 将返回值转换为与输入相同的类型,因此,当输入整数时,会对结果执行整数转换,然后返回。
缺点
稳定 sigmoid 的想法来自以下事实:
如果编码正确(一次exp
评估就足够了),这两个版本在操作方面同样有效。现在:
因此,我们必须在x
等于零时进行分支。使用numpy
's masking 我们可以仅转换数组中特定的 sigmoid 实现为正或负的部分。
有关其他要点,请参见代码注释:
def _positive_sigmoid(x):
return 1 / (1 + np.exp(-x))
def _negative_sigmoid(x):
# Cache exp so you won't have to calculate it twice
exp = np.exp(x)
return exp / (exp + 1)
def sigmoid(x):
positive = x >= 0
# Boolean array inversion is faster than another comparison
negative = ~positive
# empty contains junk hence will be faster to allocate
# Zeros has to zero-out the array after allocation, no need for that
result = np.empty_like(x)
result[positive] = _positive_sigmoid(x[positive])
result[negative] = _negative_sigmoid(x[negative])
return result
时间测量
结果(来自 50 次案例测试ynn
):
289.5070939064026 #DYZ
222.49267292022705 #ynn
230.81086134910583 #this
确实,分段似乎更快(不确定原因,也许掩蔽和额外的掩蔽操作使其更慢)。
使用了以下代码:
import time
import numpy as np
def _positive_sigmoid(x):
return 1 / (1 + np.exp(-x))
def _negative_sigmoid(x):
# Cache exp so you won't have to calculate it twice
exp = np.exp(x)
return exp / (exp + 1)
def sigmoid(x):
positive = x >= 0
# Boolean array inversion is faster than another comparison
negative = ~positive
# empty contains juke hence will be faster to allocate than zeros
result = np.empty_like(x)
result[positive] = _positive_sigmoid(x[positive])
result[negative] = _negative_sigmoid(x[negative])
return result
N = int(1e4)
x = np.random.uniform(size=(N, N))
start: float = time.time()
for _ in range(50):
y1 = np.where(x > 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
y1 += 1
end: float = time.time()
print(end - start)
start: float = time.time()
for _ in range(50):
y2 = np.piecewise(
x,
[x > 0],
[lambda i: 1 / (1 + np.exp(-i)), lambda i: np.exp(i) / (1 + np.exp(i))],
)
y2 += 1
end: float = time.time()
print(end - start)
start: float = time.time()
for _ in range(50):
y2 = sigmoid(x)
y2 += 1
end: float = time.time()
print(end - start)