Я хочу вычислить что-то вроде:
Где 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])
Есть ли способ сделать это в читаемом выражении?
Сумма
- это ожидание f
относительно биномиального распределения с n = 5000
и p = 0.5
.
Вы можете вычислить это с помощью scipy.stats.binom.expect:
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
биномиальное распределение приближается к нормальному распределению со средним значением 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
EDIT: @unutbu ответил на реальный вопрос, но я оставлю это здесь, если log2comb(n, k)
полезен для всех.
comb(n, k)
является n!/((n-k)! k!) и n! может быть вычислен с помощью функции Gamma gamma(n+1)
. Scipy предоставляет функцию scipy.special.gamma
. Scipy также предоставляет gammaln
, который является журналом (естественным логом, то есть) функции Гамма.
Итак, log(comb(n, k))
можно вычислить как gammaln(n+1) - gammaln(n-k+1) - gammaln(k+1)
Например, log (гребень (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 (гребень (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)
По умолчанию comb
дает float64
, который переполняется и дает вам inf
.
Но если вы пройдете exact=True
, вместо этого вы получите Python с переменным размером int
, который не может переполняться (если вы не получите настолько смехотворно огромным, что у вас не хватает памяти).
И, хотя вы не можете использовать np.log2
в int
, вы можете использовать Python math.log2
.
Итак:
math.log2(scipy.misc.comb(5000, 2000, exact=True))
В качестве альтернативы вы относитесь к тому, что n выбирает 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 вместо genxpr, и поэтому создание ненужного списка 5000, 100000 или 1000000 Python-float-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 с векторизованными операциями (для скорости, а также бит компактности) или выражение генератора вместо понимания списка (без использования памяти вообще, бесплатно для скорости)?
comb(1000000, 200000)
составляет около2**721918
, что означает, что для точного хранения требуется около 720К. И, так как каждый из них вам нужен только временно, кого это волнует?