log-sum-exp技巧为什么不递归

时间:2013-03-07 15:56:18

标签: math numerical-methods

我一直在研究log-sum-exp问题。我有一个以对数形式存储的数字列表,我希望将它们相加并以对数形式存储。

天真算法是

def naive(listOfLogs):
    return math.log10(sum(10**x for x in listOfLogs))

许多网站包括: logsumexp implementation in C?http://machineintelligence.tumblr.com/post/4998477107/ 建议使用

def recommend(listOfLogs):
    maxLog = max(listOfLogs)
    return maxLog + math.log10(sum(10**(x-maxLog) for x in listOfLogs))

又名

def recommend(listOfLogs):
    maxLog = max(listOfLogs)
    return maxLog + naive((x-maxLog) for x in listOfLogs)

我不明白的是,如果建议的算法更好,为什么我们应该递归调用它? 那会提供更多的好处吗?

def recursive(listOfLogs):
    maxLog = max(listOfLogs)
    return maxLog + recursive((x-maxLog) for x in listOfLogs)

虽然我在问是否有其他技巧可以使这个计算在数值上更稳定?

3 个答案:

答案 0 :(得分:9)

其他人的一些背景:当你直接计算以下类型的表达时

ln( exp(x_1) + exp(x_2) + ... )

你可能遇到两种问题:

  • exp(x_i)可能会溢出(x_i太大),导致数字无法合并
  • exp(x_i)可能下溢(x_i太小),导致一堆零

如果所有值都很大,或者所有值都很小,我们可以除以exp(const)并将const添加到ln的外部以获得相同的值。因此,如果我们可以选择正确的const,我们可以将值移到某个范围内以防止溢出/下溢。

OP的问题是,为什么我们为这个const而不是任何其他值选择max(x_i)?为什么我们不递归地进行这种计算,选择每个子集的最大值并重复计算对数?

答案:因为无关紧要

原因?假设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非常接近exp(max(x_i) - some_constant)。 (顺便说一句,这个函数形式,渐近地,实际上允许你从数学集合中数学选择最大值。)

因此,我们选择max而不是任何其他值的原因是因为较小的值几乎不会影响结果。如果它们下溢,它们本来就太小而无法影响总和,因为它将由最大数量和接近它的任何东西支配。在计算术语中,在计算{{1}}之后,小数字的贡献将小于ulp。因此,没有理由浪费时间递归计算较小值的表达式,如果它们将在最终结果中丢失。

如果你真的想要实现这个,你可以将{{1}}左右除以'中心'得到的值大约为1,以避免溢出和下溢,这可能会给你一些额外的结果中的精度数字。但是避免溢出对于避免下溢更为重要,因为前者决定了结果而后者没有,所以这样做就简单得多了。

答案 1 :(得分:2)

递归执行它并不是更好。问题只是你要确保你的有限精度算术不会在噪声中淹没答案。通过自己处理max,可以确保在最终答案中保留任何垃圾,因为它中最重要的部分可以保证通过。

道歉的解释。自己尝试一些数字(一个明智的列表,可能是[1E-5,1E25,1E-5])并看看会有什么感觉。

答案 2 :(得分:2)

正如您所定义的那样,您的recursive函数永远不会终止。这是因为((x-maxlog) for x in listOfLogs)仍然具有与listOfLogs相同数量的元素。

我认为这也不容易解决,不会显着影响性能或精度(与非递归版本相比)。