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 没有进行一些安全检查,因此您应该确保 vectorsdtype 足以足够准确地存储幅度的平方.

【讨论】:

有趣的计时结果(我分别得到了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 范数?

关于用matlab进行向量归一化的问题

numpy按行归一化,numpy按列归一化

Unity如何理解Vector3.normalized 归一化向量 以及 向量方向计算

归一化输入向量

动态归一化二维 numpy 数组