Math log sum exp技巧为什么不是递归的
我一直在研究log sum exp问题。我有一个以对数形式存储的数字列表,我想求和并存储在对数中 朴素的算法是Math log sum exp技巧为什么不是递归的,math,numerical-methods,Math,Numerical Methods,我一直在研究log sum exp问题。我有一个以对数形式存储的数字列表,我想求和并存储在对数中 朴素的算法是 def naive(listOfLogs): return math.log10(sum(10**x for x in listOfLogs)) 许多网站包括: 和 推荐使用 def recommend(listOfLogs): maxLog = max(listOfLogs) return maxLog + math.log10(sum(10**(x-max
def naive(listOfLogs):
return math.log10(sum(10**x for x in listOfLogs))
许多网站包括:
和
推荐使用
def recommend(listOfLogs):
maxLog = max(listOfLogs)
return maxLog + math.log10(sum(10**(x-maxLog) for x in listOfLogs))
阿卡
我不明白的是,如果推荐的算法更好,为什么我们应该递归地调用它?
这会带来更多的好处吗
def recursive(listOfLogs):
maxLog = max(listOfLogs)
return maxLog + recursive((x-maxLog) for x in listOfLogs)
当我问这个问题的时候,有没有其他的技巧使这个计算在数值上更稳定?递归地进行并不是更好。问题只是你想确保你的有限精度算法不会把答案淹没在噪音中。通过单独处理最大值,您可以确保在最终答案中任何垃圾都保持在较小的范围内,因为它的最重要部分都保证通过
我为那些胡言乱语的解释道歉。用一些数字自己尝试一下(一个合理的列表可能是[1E-5,1E25,1E-5]),看看会发生什么事情来感受它。正如您所定义的那样,您的
递归
函数永远不会终止。这是因为((x-maxlog)对于listOfLogs中的x)
仍然具有与listOfLogs
相同的元素数
我认为这也不容易修复,不会显著影响性能或精度(与非递归版本相比)。其他版本的一些背景:当您直接计算以下类型的表达式时
ln( exp(x_1) + exp(x_2) + ... )
您可能会遇到两种问题:
会溢出(exp(x_i)
太大),导致无法相加的数字x_i
会下溢(exp(x_i)
太小),导致一堆零x_i
exp(const)
,然后将const
添加到ln
的外部以获得相同的值。因此,如果我们能够选择正确的常量
,我们就可以将值转移到某个范围内,以防止溢出/下溢
OP的问题是,为什么我们为这个常数选择max(xi)
,而不是任何其他值?我们为什么不递归地进行计算,从每个子集中选取最大值,然后重复计算对数呢
答案是:因为这无关紧要
原因是什么?假设x_1=10
很大,而x_2=-10
很小。(这些数字的大小都不是很大,对吗?)
ln( exp(10) + exp(-10) )
将给您一个非常接近10的值。如果你不相信我,去试试看。事实上,一般来说,ln(exp(x_1)+exp(x_2)+……)
将给出非常接近max(x_i)
的值,如果某个特定的x_i
比所有其他的都大。(顺便说一句,这种函数形式实际上是渐进的,可以让您从一组数字中从数学上选取最大值。)
因此,我们选择最大值而不是任何其他值的原因是较小的值几乎不会影响结果。如果它们下溢,它们将太小,无论如何都不会影响总数,因为它将被最大的数字和任何接近它的数字所支配。在计算方面,在计算ln
之后,小数字的贡献将小于an。因此,如果较小的值在最终结果中丢失,就没有理由浪费时间递归地计算表达式
如果你想在实现这一点上真的很挑剔,你可以除以
exp(max(x_i)-some_常量)
左右,将结果值“居中”在1左右,以避免溢出和下溢,这可能会给你结果中额外的几位精度。但是避免溢出对于避免下溢更为重要,因为前者决定结果,后者不决定结果,所以这样做更简单。是的,正如我写的那样,它没有结束,此外,两次迭代的结果与第一次迭代相同,只是maxlog被第二次最大的替换。我刚刚发现scipy.misc.logsumexp:
ln( exp(10) + exp(-10) )