对于标量变量x,我们知道如何在python中写出数值稳定的sigmoid函数:
def sigmoid(x):
if x >= 0:
return 1. / ( 1. + np.exp(-x) )
else:
return exp(x) / ( 1. + np.exp(x) )
Run Code Online (Sandbox Code Playgroud)
对于标量列表z = [x_1, x_2, x_3, ...],假设我们x_i事先不知道每个标量的符号,我们可以概括上面的定义并尝试:
def sigmoid(z):
result = []
for x in z:
if x >= 0:
result.append(1. / ( 1. + np.exp(-x) ) )
else:
result.append( exp(x) / ( 1. + np.exp(x) ) )
return result
Run Code Online (Sandbox Code Playgroud)
这似乎有效。但是,我觉得这可能不是最 Pythonic 的方式。我应该如何改进“清洁”方面的定义?说,有没有办法使用理解来缩短函数定义?
如果有人问过这个,我很抱歉,因为我在 SO 上找不到类似的问题。非常感谢您的时间和帮助!
Szy*_*zke 11
@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
)
Run Code Online (Sandbox Code Playgroud)
由于两个分支都被评估(它们是参数,它们必须是),第一个分支将向我们发出负值警告,第二个分支将向我们发出正值警告。
尽管会发出警告,但不会合并溢出结果,因此结果是正确的。
这几乎是正确的,但仅适用于浮点值,请参见下文:
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
Run Code Online (Sandbox Code Playgroud)
为什么? @mhawke在另一个线程中提供了更长的答案,但要点是:
看来,piecewise() 将返回值转换为与输入相同的类型,因此,当输入整数时,会对结果执行整数转换,然后返回结果。
稳定 sigmoid 的想法来自于以下事实:
exp如果编码正确(一次评估就足够了),这两个版本在操作方面同样有效。现在:
e^xx当为正数时会溢出e^-xx为负数时会溢出因此我们必须在x等于 0 的地方分支。使用numpy的掩码,我们可以通过特定的 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
# See comment to the answer when it comes to dtype
result = np.empty_like(x, dtype=np.float)
result[positive] = _positive_sigmoid(x[positive])
result[negative] = _negative_sigmoid(x[negative])
return result
Run Code Online (Sandbox Code Playgroud)
结果(50次案例测试来自ynn):
289.5070939064026 #DYZ
222.49267292022705 #ynn
230.81086134910583 #this
Run Code Online (Sandbox Code Playgroud)
事实上,分段似乎更快(不确定原因,也许屏蔽和额外的屏蔽操作使它更慢)。
使用以下代码:
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)
Run Code Online (Sandbox Code Playgroud)
DYZ*_*DYZ 10
你是对的,你可以通过使用np.wherenumpy 等价物做得更好if:
def sigmoid(x):
return np.where(x >= 0,
1 / (1 + np.exp(-x)),
np.exp(x) / (1 + np.exp(x)))
Run Code Online (Sandbox Code Playgroud)
这个函数接受一个 numpy 数组x并返回一个 numpy 数组:
data = np.arange(-5,5)
sigmoid(data)
#array([0.00669285, 0.01798621, 0.04742587, 0.11920292, 0.26894142,
# 0.5 , 0.73105858, 0.88079708, 0.95257413, 0.98201379])
Run Code Online (Sandbox Code Playgroud)
小智 5
def sigmoid(x):
"""
A numerically stable version of the logistic sigmoid function.
"""
pos_mask = (x >= 0)
neg_mask = (x < 0)
z = np.zeros_like(x)
z[pos_mask] = np.exp(-x[pos_mask])
z[neg_mask] = np.exp(x[neg_mask])
top = np.ones_like(x)
top[neg_mask] = z[neg_mask]
return top / (1 + z)
Run Code Online (Sandbox Code Playgroud)
这段代码来自cs231n的assignment3,我不太明白为什么要这样计算,但我知道这可能就是你要找的代码。希望有所帮助。