我有一个Java应用程序,它使用由double形成的高维向量。它通过将矢量分量乘以欧几里德范数的倒数来对这些矢量进行归一化。有时,结果向量的范数不等于1到机器精度。发生这种情况并不让我感到惊讶。
我的问题是:如何对矢量进行标准化,使得结果矢量的单位长度达到机器精度?
这些是我的Vector类计算范数和规范化矢量的方法:
public double getFrobeniusNorm() {
return Math.sqrt(getFrobeniusNormSquared());
}
public double getFrobeniusNormSquared() {
double normSquared = 0.0;
int numberOfRows = getRowDimension();
int numberOfColumns = getColumnDimension();
for(int i = 0; i < numberOfRows; ++i) {
for(int j = 0; j < numberOfColumns; ++j) {
double matrixElement = get(i,j);
normSquared += matrixElement*matrixElement;
}
}
return normSquared;
}
public void normalize() {
double norm = getFrobeniusNorm();
if (norm == 0) {
throw new ArithmeticException("Cannot get a unit vector from the zero vector.");
} else {
double oneOverNorm = 1.0 / norm;
multiplyEquals(oneOverNorm);
}
}
由于这是Java,我不能使用特定于操作系统和处理器的技术,否则这似乎是标准的浮点算法问题。
我可以使用Kahan求和和/或除去最大分量来改进范数计算,但归一化和计算范数之间的一致性是真正的问题。范数比方向更重要,所以我认为这是找到最接近原始向量的浮点向量,其约束条件是规范是机器精度的1。就我的目的而言,原始矢量是准确的。
假设原始向量是 u 。我打电话给u.normalize()
。然后,如果我计算Math.abs(u.getFrobeniusNorm()-1d
,在某些情况下,结果是数百个ulps。这就是问题。我可以接受向量规范有错误。我只是想对矢量进行标准化,使得 u.getFrobeniusNorm()
计算的范数为1到最小可能的ulps。改进u.getFrobeniusNorm()
是有道理的,但我认为这不能解决一致性问题。
答案 0 :(得分:1)
简单:您的 p>
你可以合理地接近1.0,在大多数情况下这应该足够好了(它应该已经在你的代码中)。
如果事实证明准确性对于您的情况来说太小,则需要执行错误分析(因为您首先提出问题,让有经验的人为您做错误分析 > - 这将花钱。)
这里解释了浮点精度背后的基础知识:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html(每个计算机科学家应该知道浮点数)
答案 1 :(得分:1)
你可以通过做一些尴尬的贪婪舍入来重新阐述你所说的问题。 (你也可以把它描述成一个更加尴尬的网络流问题。)我不认为你可以保证这里的“好”舍入所有四舍五入的部分比所有舍入的部分都要大
支持一点,我不知道你为什么要让自己进入一个你需要向量的范数正好为1的位置,而不是在n *机器的epsilon为1之内。这可能是一个更好的主意重新考虑使用规范化向量的代码而不是重新考虑规范化本身。
(你也这样说:“对于统一性问题,单位向量精确地具有范数1,并且我的所有方程式都使用了这个事实。我希望浮点表示最接近该单位向量(通过内积)。 “这完全改变了游戏;欧几里德范数中与精确归一化向量最接近的向量将是舍入的归一化向量。”
答案 2 :(得分:1)
阅读你的问题,让我思考错误的根源。我发现问题始于计算getFrobeniusNormSquared()返回的值的sqrt()。我担心机器精度的准确性确实不可能像其他人所说的那样。尽管如此,以下实现通过推迟sqrt()来改进它:
double norm = X * X + Y * Y + Z * Z;
if (norm > 0.0)
{
norm = 1.0 / norm;
return new vector
{
X = Math.Sign(X) * Math.Sqrt(X * X * norm),
Y = Math.Sign(Y) * Math.Sqrt(Y * Y * norm),
Z = Math.Sign(Z) * Math.Sqrt(Z * Z * norm),
};
}
else
throw new ArithmeticException("Cannot get a unit vector from the zero vector.");
当然,这会花费你一些CPU周期,结果仍然不能达到机器精度,但它有所改善。
答案 3 :(得分:0)
我一直在考虑这个问题,但没有时间研究数学。我正在考虑这些问题:
假设您计算范数并发现它是1加上一个小数 d (可能是正数或负数)。您可以考虑少量更改向量的元素,直到计算出的范数为1.
假设您要在值 x 的某个元素中进行 e 更改。 成本是它将向量的方向改变为大约arccos的角度((1 + xe )/ sqrt(1 + 2
因此,如果您要对元素进行一次更改,请更改具有最大幅度的元素。
我还没有考虑对更改进行分区:假设我们将两个或更多个更改应用于不同的元素而不是一个更改。这会增加成本效益比吗?
另一个考虑因素是我们可以对元素进行量化,因此,不是将 e 更改为更大的元素,我们可能更喜欢更改 e / 2如果它将计算的范数改变相同的量,则更小的元素。
答案 4 :(得分:0)
虽然最初的问题仍然很有趣,但最近我在代码中找到了问题的根源。在另一个代码中,我通过实现Kahan Summation的变体来改进求和。我重新访问了单位向量代码,发现规范化不是问题。
规范化方法包括三个步骤:
为了改进归一化方法,我用改进的求和计算了范数,用更准确的范数的倒数来计算分量,并使用改进的求和来计算单位向量的范数,以检查它的归一化程度。果然,单位矢量被归一化到一个低得多的公差,即机器精度*尺寸。我将改进的归一化方法与以前的方法进行了比较,结果更好。让我感到惊讶的是,如果第二个向量范数计算使用了改进的求和,那么旧的归一化方法就像一样准确。
因此,导致问题的不是规范化本身,而是规范化的检查。对于接近1的总和,对于许多其他值而言,天真求和似乎不太准确(即使在相对意义上)。我说“许多其他值”在实践中对于所有幅度的矢量都发生了原始问题,但我怀疑一些矢量,因此一些总和,具有与单位矢量相同的不良行为(总和接近1)。然而,问题范数值可能稀疏地分布在实数上,如2的幂。
在原始方法中,问题是两个向量范数计算具有不同的相对精度。如果你从一个接近1的范数的向量开始,这两个计算将具有几乎相同的相对精度,并且标准化本身将是不准确的。
所以现在,我不计算单位向量的向量范数作为检查。