NumPy:如何快速归一化许多向量?
Posted
技术标签:
【中文标题】NumPy:如何快速归一化许多向量?【英文标题】:NumPy: how to quickly normalize many vectors? 【发布时间】:2011-02-20 11:59:57 【问题描述】:如何在 NumPy 中优雅地标准化向量列表?
这是一个不工作的例子:
from numpy import *
vectors = array([arange(10), arange(10)]) # All x's, then all y's
norms = apply_along_axis(linalg.norm, 0, vectors)
# Now, what I was expecting would work:
print vectors.T / norms # vectors.T has 10 elements, as does norms, but this does not work
最后一个操作产生“形状不匹配:无法将对象广播到单个形状”。
如何使用 NumPy 优雅地完成 vectors
中的二维向量的归一化?
编辑:为什么在向norms
添加维度时上述方法不起作用(根据我下面的回答)?
【问题讨论】:
仅供参考,评论者可能有更快的方法,我更详细地编辑了我的答案。 【参考方案1】:好的:NumPy 的数组形状广播将维度添加到数组形状的左侧,而不是右侧。然而,可以指示 NumPy 在 norms
数组的右侧添加一个维度:
print vectors.T / norms[:, newaxis]
确实有效!
【讨论】:
请注意,我使用norms[..., np.newaxis]
以防矩阵不只是二维。它也适用于 3D(或更多)张量。【参考方案2】:
好吧,除非我错过了什么,这确实有效:
vectors / norms
你建议的问题是广播规则。
vectors # shape 2, 10
norms # shape 10
形状的长度不一样!所以规则是首先在左侧将小形状扩展一个:
norms # shape 1,10
您可以通过调用手动执行此操作:
vectors / norms.reshape(1,-1) # same as vectors/norms
如果要计算vectors.T/norms
,则必须手动进行整形,如下所示:
vectors.T / norms.reshape(-1,1) # this works
【讨论】:
为什么不直接做 (vectors/norms).T 如果 OP 想要这个转置。在我看来,它既简单又优雅。 啊,啊!所以维度扩展是在 left 上完成的:这确实解释了观察到的行为。谢谢!【参考方案3】:计算幅度
我遇到了这个问题,并对您的标准化方法感到好奇。我使用不同的方法来计算幅度。 注意:我通常还计算最后一个索引的范数(在这种情况下是行,而不是列)。
magnitudes = np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis]
不过,通常情况下,我只是这样标准化:
vectors /= np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis]
时间比较
我跑了一个测试比较时间,发现我的方法快了不少,但是Freddie Witherdon的建议更快。
import numpy as np
vectors = np.random.rand(100, 25)
# OP's
%timeit np.apply_along_axis(np.linalg.norm, 1, vectors)
# Output: 100 loops, best of 3: 2.39 ms per loop
# Mine
%timeit np.sqrt((vectors ** 2).sum(-1))[..., np.newaxis]
# Output: 10000 loops, best of 3: 13.8 us per loop
# Freddie's (from comment below)
%timeit np.sqrt(np.einsum('...i,...i', vectors, vectors))
# Output: 10000 loops, best of 3: 6.45 us per loop
但请注意,正如 *** answer 所指出的,einsum
没有进行一些安全检查,因此您应该确保 vectors
的 dtype
足以足够准确地存储幅度的平方.
【讨论】:
有趣的计时结果(我分别得到了0.8 s和1.4 s,IPython更健壮的%timeit函数),谢谢! 我发现np.sqrt(np.einsum('...i,...i', vectors, vectors))
比上面给出的方法 1 快约 4 倍。
@FreddieWitherden - 感谢您的评论,我不知道 einsum
。这里有一个有趣的相关 SO 问题:***.com/questions/18365073/… 它通常会更快,但可能不安全(取决于向量的dtype
)。
@FreddieWitherden,你的方法给了我不同的(但np.allclose
)值。
@EOL - 谢谢,我切换到 ipython,并更新了我的答案。事实证明,我的巨大阵列造成了一些严重的开销。使用较小的(在我的新答案中)速度差异更加明显。【参考方案4】:
scikit learn中已经有一个函数了:
import sklearn.preprocessing as preprocessing
norm =preprocessing.normalize(m, norm='l2')*
更多信息请访问:
http://scikit-learn.org/stable/modules/preprocessing.html
【讨论】:
有趣的信息,但问题明确是关于 NumPy。最好将其放在对原始问题的评论中。【参考方案5】:我首选的向量归一化方法是使用 numpy 的 inner1d 来计算它们的大小。这是迄今为止与 inner1d 相比的建议
import numpy as np
from numpy.core.umath_tests import inner1d
COUNT = 10**6 # 1 million points
points = np.random.random_sample((COUNT,3,))
A = np.sqrt(np.einsum('...i,...i', points, points))
B = np.apply_along_axis(np.linalg.norm, 1, points)
C = np.sqrt((points ** 2).sum(-1))
D = np.sqrt((points*points).sum(axis=1))
E = np.sqrt(inner1d(points,points))
print [np.allclose(E,x) for x in [A,B,C,D]] # [True, True, True, True]
使用 cProfile 测试性能:
import cProfile
cProfile.run("np.sqrt(np.einsum('...i,...i', points, points))**0.5") # 3 function calls in 0.013 seconds
cProfile.run('np.apply_along_axis(np.linalg.norm, 1, points)') # 9000018 function calls in 10.977 seconds
cProfile.run('np.sqrt((points ** 2).sum(-1))') # 5 function calls in 0.028 seconds
cProfile.run('np.sqrt((points*points).sum(axis=1))') # 5 function calls in 0.027 seconds
cProfile.run('np.sqrt(inner1d(points,points))') # 2 function calls in 0.009 seconds
inner1d 计算头发的幅度比 einsum 快。所以使用 inner1d 进行归一化:
n = points/np.sqrt(inner1d(points,points))[:,None]
cProfile.run('points/np.sqrt(inner1d(points,points))[:,None]') # 2 function calls in 0.026 seconds
针对 scikit 进行测试:
import sklearn.preprocessing as preprocessing
n_ = preprocessing.normalize(points, norm='l2')
cProfile.run("preprocessing.normalize(points, norm='l2')") # 47 function calls in 0.047 seconds
np.allclose(n,n_) # True
结论:使用 inner1d 似乎是最好的选择
【讨论】:
作为参考,这个问题实际上要求计算沿 first 维度的范数,而不是第二个维度(请参阅添加到 Geoff 答案中的警告)。这将如何改变结果?由于访问内存的方式,可能会产生影响,特别是如果您有更大的第二维(而不是示例中的 3)。【参考方案6】:对于二维情况,使用 np.hypot(vectors[:,0],vectors[:,1])
计算量级似乎比 Freddie Witherden 的 np.sqrt(np.einsum('...i,...i', vectors, vectors))
更快。 (参考 Geoff 的回答)
import numpy as np
# Generate array of 2D vectors.
vectors = np.random.random((1000,2))
# Using Freddie's
%timeit np.sqrt(np.einsum('...i,...i', vectors, vectors))
# Output: 11.1 µs ± 173 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# Using numpy.hypot()
%timeit np.hypot(vectors[:,0], vectors[:,1])
# Output: 6.81 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
要获得归一化的向量,请执行以下操作:
vectors /= np.hypot(vectors[:,0], vectors[:,1])
【讨论】:
以上是关于NumPy:如何快速归一化许多向量?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C++ 中快速计算向量的归一化 l1 和 l2 范数?