【问题标题】:log-sum-exp trick why not recursivelog-sum-exp 技巧为什么不递归
【发布时间】:2013-02-22 22:05:18
【问题描述】:

我一直在研究 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)

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

【问题讨论】:

标签: math numerical-methods


【解决方案1】:

递归地做这件事并没有更好的办法。问题只是你想确保你的有限精度算术不会淹没答案。通过自己处理最大值,您可以确保最终答案中的任何垃圾都保持较小,因为它最重要的组成部分可以保证通过。

对于华而不实的解释表示歉意。自己尝试一些数字(一个合理的列表可能是 [1E-5,1E25,1E-5]),看看会发生什么来感受一下。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 是的,正如我写的那样,它并没有结束,而且两次迭代的结果与第一次迭代相同,只是将 maxlog 替换为第二大的。
    【解决方案3】:

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

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

    你会遇到两种问题:

    • exp(x_i) 会溢出(x_i 太大),导致无法相加的数字
    • exp(x_i) 可以下溢(x_i 太小),导致一堆零

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

    OP 的问题是,为什么我们选择max(x_i) 作为这个常量而不是任何其他值?为什么我们不递归地做这个计算,从每个子集中挑出最大值并重复计算对数?

    答案:因为没关系

    原因?假设x_1 = 10 很大,x_2 = -10 很小。 (这些数字甚至不是很大,对吧?)表达式

    ln( exp(10) + exp(-10) ) 
    

    会给你一个非常接近 10 的值。如果你不相信我,去试试吧。事实上,一般来说,如果某个特定的x_i 比其他所有的大得多,ln( exp(x_1) + exp(x_2) + ... ) 的值将非常接近max(x_i)。 (顺便说一句,这种函数形式实际上可以让您在数学上从一组数字中选择最大值。)

    因此,我们选择最大值而不是任何其他值的原因是因为较小的值几乎不会影响结果。如果它们下溢,它们将太小而无法影响总和,因为它将由最大的数字和任何接近它的数字主导。在计算方面,计算ln后,小数的贡献将小于ulp。因此,如果较小的值无论如何都会在最终结果中丢失,那么没有理由浪费时间递归地计算表达式。

    如果您想在实现这一点上非常挑剔,您可以除以 exp(max(x_i) - some_constant) 左右以将结果值“居中”在 1 左右以避免上溢和下溢,这可能会给您一些额外的数字结果的精度。但是避免上溢对于避免下溢更为重要,因为前者决定了结果,而后者不决定,所以这样做就简单多了。

    【讨论】:

      猜你喜欢
      • 2014-07-27
      • 1970-01-01
      • 1970-01-01
      • 2021-02-25
      • 2013-08-06
      • 2010-09-30
      • 2016-03-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多