Python中的日志计算

时间:2014-11-14 21:07:59

标签: python scipy large-data

我想要计算类似的东西:

formula

其中f(i)是一个函数,可以[-1,1]i中的任何{1,2,...,5000}返回实数。

显然,总和的结果在[-1,1]的某处,但是当我似乎无法使用直接编码在Python中计算它时,0.55000变为0 }和comb(5000,2000)变为inf,这导致计算的总和变为NaN

所需的解决方案是使用双面登录。

这是使用身份a × b = 2log(a) + log(b),如果我可以计算log(a)log(b)我可以计算总和,即使a很大且b差不多是0

所以我想我要问的是,如果有一种简单的计算方法

log2(scipy.misc.comb(5000,2000))

所以我可以简单地通过

计算我的总和
sum([2**(log2comb(5000,i)-5000) * f(i) for i in range(1,5000) ])

@ abarnert的解决方案,在处理5000图时,通过提高计算梳子的精度来解决问题。这适用于这个例子,但不能扩展,因为如果不是5000,我们需要的内存会大大增加,例如我们有1e7。

目前,我正在使用一种丑陋的解决方法,但会降低内存消耗:

log2(comb(5000,2000)) = sum([log2 (x) for x in 1:5000])-sum([log2 (x) for x in 1:2000])-sum([log2 (x) for x in 1:3000])

有可能以可读的表达方式这样做吗?

3 个答案:

答案 0 :(得分:8)

总和

formula

fn = 5000p = 0.5的{​​{3}}的期望。

您可以使用binomial distribution来计算:

import scipy.stats as stats

def f(i):
    return i
n, p = 5000, 0.5
print(stats.binom.expect(f, (n, p), lb=0, ub=n))
# 2499.99999997

另请注意,当n变为无穷大且p已修复时,二项分布scipy.stats.binom.expect的平均值为np和方差np*(1-p)。因此,对于较大的n,您可以改为计算:

import math
print(stats.norm.expect(f, loc=n*p, scale=math.sqrt((n*p*(1-p))), lb=0, ub=n))
# 2500.0

答案 1 :(得分:3)

默认情况下,comb会为您提供float64,它会溢出并为您提供inf

但是如果你传递exact=True,它会给你一个Python变量大小的int,它不会溢出(除非你的内存耗尽这么荒谬)。

而且,虽然您无法在np.log2上使用int,但您可以使用Python的math.log2

所以:

math.log2(scipy.misc.comb(5000, 2000, exact=True))

作为替代方案,您选择k的亲戚定义为n!k / k!,对吗?您可以将其减少到∏(i=1...k)((n+1-i)/i),这很容易计算。

或者,如果您想避免溢出,可以通过交替* (n-i)/ (k-i)来实现。

当然,您还可以减少添加和减去日志。我认为在Python中循环并计算4000个对数比在C中循环并计算4000次乘法要慢,但我们总是可以对其进行矢量化,然后,它可能会更快。让我们写下来并测试:

In [1327]: n, k = 5000, 2000
In [1328]: %timeit math.log2(scipy.misc.comb(5000, 2000, exact=True))
100 loops, best of 3: 1.6 ms per loop
In [1329]: %timeit np.log2(np.arange(n-k+1, n+1)).sum() - np.log2(np.arange(1, k+1)).sum()
10000 loops, best of 3: 91.1 µs per loop

当然,如果你更关心记忆而不是时间......好吧,这显然会让情况变得更糟。我们一次有2000个8字节浮点数而不是一个608字节整数。如果你达到100000,20000,你得到20000个8字节浮点数而不是一个9K整数。在1000000,200000,它是200000个8字节浮点数与一个720K整数。

我不确定为什么这两种方式对你来说都是一个问题。特别是考虑到你使用的是listcomp而不是genexpr,因此创建一个不必要的5000,100000或1000000 Python浮动列表--24MB不是问题,但是720K是?但如果是这样,我们显然可以迭代地做同样的事情,代价是速度:

r = sum(math.log2(n-i) - math.log2(k-i) for i in range(n-k))

这不是scipy解决方案慢得多,而且它永远不会使用超过一小块常量字节(少数Python浮点数)。 (除非你使用的是Python 2,在这种情况下......只需使用xrange代替range,它就会恢复为常量。)


作为旁注,为什么 你使用列表推导而不是带矢量化操作的NumPy数组(速度,还有一点紧凑性)或生成器表达式而不是列表理解(根本没有内存使用,无需加速)?

答案 2 :(得分:3)

编辑:@unutbu已经回答了真正的问题,但是我会留在这里,以防log2comb(n, k)对任何人都有用。


comb(n, k)是n! /((n-k)!k!)和n!可以使用Gamma function gamma(n+1)计算。 Scipy提供函数scipy.special.gamma。 Scipy还提供gammaln,这是Gamma函数的日志(自然日志)。

因此log(comb(n, k))可以计算为gammaln(n+1) - gammaln(n-k+1) - gammaln(k+1)

例如,log(comb(100,8))(执行from scipy.special import gammaln之后):

In [26]: log(comb(100, 8))
Out[26]: 25.949484949043022

In [27]: gammaln(101) - gammaln(93) - gammaln(9)
Out[27]: 25.949484949042962

和log(comb(5000,2000)):

In [28]: log(comb(5000, 2000))  # Overflow!
Out[28]: inf

In [29]: gammaln(5001) - gammaln(3001) - gammaln(2001)
Out[29]: 3360.5943053174142

(当然,要获得基数为2的对数,只需除以log(2)。)

为方便起见,您可以定义:

from math import log
from scipy.special import gammaln

def log2comb(n, k):
    return (gammaln(n+1) - gammaln(n-k+1) - gammaln(k+1)) / log(2)