对于标量变量x,我们知道如何在python中写下数值稳定的Sigmoid函数:
def sigmoid(x):
if x >= 0:
return 1. / ( 1. + np.exp(-x) )
else:
return exp(x) / ( 1. + np.exp(x) )
对于一个标量列表,假设z = [x_1,x_2,x_3,...],并且假设我们事先不知道每个$ x_i $的符号,我们可以将上面的定义归纳并尝试: / p>
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
这似乎有效。但是,我觉得这可能不是最有效的方法。我应该如何改善“清洁度”的定义?说,有没有办法使用理解来缩短函数定义?
很抱歉,如果有人提出这个问题,因为我找不到类似的问题。非常感谢您的时间和帮助!
答案 0 :(得分:3)
您是对的,可以使用np.where
(相当于if
的numpy来做得更好:
def sigmoid(x):
return np.where(x >= 0,
1 / (1 + np.exp(-x)),
np.exp(x) / (1 + np.exp(x)))
此函数接受一个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])
答案 1 :(得分:0)
代码的另一种替代方法如下:
def sigmoid(z):
return [(1. / (1. + np.exp(-x)) if x >= 0 else (np.exp(x) / (1. + np.exp(x))) for x in z]
答案 2 :(得分:0)
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)
这段代码来自cs231n的assignment3,我不太明白为什么要用这种方式计算它,但是我知道这可能是您正在寻找的代码。希望能有所帮助。
答案 3 :(得分:0)
The accepted answer是正确的,但正如this comment所指出的那样,它计算两个分支,因此存在问题。
相反,您可能要使用np.piecewise()
。这更快,更有意义(np.where
不是旨在定义分段函数),并且不会因同时进入两个分支而引起误导性警告。
源代码
import numpy as np
import time
N: int = int(1e+4)
np.random.seed(0)
x: np.ndarray = np.random.random((N, N))
x *= 1e+3
start: float = time.time()
y1 = np.where(x > 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
end: float = time.time()
print()
print(end - start)
start: float = time.time()
y2 = np.piecewise(x, [x > 0], [lambda i: 1 / (1 + np.exp(-i)), lambda i: np.exp(i) / (1 + np.exp(i))])
end: float = time.time()
print(end - start)
assert (np.array_equal(y1, y2))
结果
np.piecewise()
保持沉默,速度提高了两倍!
test.py:12: RuntimeWarning: overflow encountered in exp
y1 = np.where(x > 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
test.py:12: RuntimeWarning: invalid value encountered in true_divide
y1 = np.where(x > 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
6.32736349105835
3.138420343399048
答案 4 :(得分:0)
我写了一个技巧,我猜np.where或torch.where的实现方式与处理二进制条件相同:
std::vector<std::pair<std::string, Team>> orderTeams2( const std::map<std::string, Team>& map) {
std::vector<std::pair<std::string, Team>> vect = { map.begin(), map.end() };
std::sort( vect.begin(), vect.end(), []( auto &left, auto &right ) {
return left.second.points > right.second.points;
});
return vect;
}
答案 5 :(得分:0)
@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()将返回值转换为相同类型 作为输入,当输入整数时,整数转换为 对结果执行,然后返回。
稳定的乙状结肠的想法来自以下事实:
如果正确编码,则两个版本在操作方面都同样有效(一个exp
评估就足够了)。现在:
e^x
将在x
为正值时溢出e^-x
在x
为负值时将溢出因此,当x
等于零时,我们必须分支。使用numpy
的遮罩,我们只能通过特定的S形变换来变换正或负数组的一部分。
请参见代码注释以获取其他要点:
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
结果(来自ynn
的50次案例测试):
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)