Я написал random_walk
симуляцию, используя numpy
для выделения данных и генераторы для выполнения шагов симуляции. Это random_walk
- это просто MWE из исходного кода (которое вообще не связано со случайными блужданиями, а математическая модель стохасти c, слишком большая и сложная, чтобы использовать ее в качестве примера. Тем не менее random_walk
MWE моделирует основные компоненты .
Причина, по которой я использую генераторы, связана с симуляцией. Я буду запускать симуляцию в течение бесконечного времени и дам только данные в некоторых угловых случаях. Поэтому я могу измерить вероятность возникновения угловых случаев Я могу заранее выделить массив numpy с высокой степенью точности (никогда не ошибаясь), но это верхняя граница, поэтому мне нужно подсчитать, сколько раз происходили угловые случаи, а затем разрезать набор данных (эмулируемый) "в симуляции).
Для сравнения я также написал аналогичный наивный подход, использующий обычный append
для списков для хранения данных симуляции, куда я добавляю только когда происходят угловые случаи.
Важно знать, что угловые случаи будут происходить миллиард раз (займет огромную часть ry), но финальная симуляция будет выполняться в течение «бесконечного времени», то есть ОЧЕНЬ большого количества шагов. Угловые случаи похожи на случай 1e-10.
И в конечном коде есть условие остановки, которое я эмулировал здесь, используя distance
и классический simulation time
.
К моему удивлению Я заметил, что append
подход имеет лучшую производительность, чем numpy+generators
. Как мы можем видеть в выходных данных ниже
Для небольших наборов данных:
%timeit random_walk_naive(max_distance=1e5, simul_time=1e4)
5.35 ms ± 190 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit random_walk_simul(max_distance=1e5, simul_time=1e4)
16.3 ms ± 567 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Для больших наборов данных
%timeit random_walk_naive(max_distance=1e12, simul_time=1e7)
12.2 s ± 760 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit random_walk_simul(max_distance=1e12, simul_time=1e7)
36 s ± 102 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Выполнение cProfile
по вызовам, я заметил генератор Время выполнения вызовов аналогично наивному подходу, и все дополнительное время было потрачено на random_walk_simul
самостоятельно. Оценивая np.empty
и операцию нарезки, я заметил, что время создания пустого набора данных и его нарезки в ответ минимально. Не влияет на время, потраченное на операции. Кроме того, код почти такой же, за исключением того, что в подходе generator
я сначала выделяю данные локальным переменным, а затем "flu sh" их в numpy.array
, что было показано быстрее, чем flu sh напрямую, так как я будет использовать значения в while
l oop для оценки условия остановки.
Мне нужно понять, почему это поведение проявляется и ожидается ли оно; если не как это исправить?
Полный исходный код вставлен ниже
import numpy as np
from random import random
def clip(value, lower, upper):
return lower if value < lower else upper if value > upper else value
def random_walk(s_0, a_0, pa, pb):
"""Initial position (often 0), acceleration, 0 < pa < pb < 1"""
# Time, x-position, Velocity, Acceleration
t, x, v, a = 0, s_0, 0, a_0
yield (t, x, v, a)
while True:
# Roll the dices
god_wishes = random()
if god_wishes <= pa:
# Increase acceleration
a += .005
elif god_wishes <= pb:
# Reduce acceleration
a -= .005
# Lets avoid too much acceleration
a = clip(a, -.2, .2)
# How much time has passed, since last update?
dt = random()
v += dt*a
x += dt*v
t += dt
yield (t, x, v, a)
def random_walk_simul(initial_position = 0, acceleration = 0,
prob_increase=0.005, prob_decrease=0.005,
max_distance=10000, simul_time=1000):
"""Runs a random walk simulation given parameters
Particle initial state (initial position and acceleration)
State change probability (prob_increase, prob_decrease)
Stop criteria (max_distance, simul_time)
Returns a random_walk particle data
"""
assert (0 < prob_increase+prob_decrease < 1), "Total probability should be in range [0, 1]"
rw = random_walk(initial_position, acceleration, prob_increase, prob_decrease+prob_increase)
# Over estimated given by law of large numbers expected value of a
# uniform distribution
estimated_N = int(simul_time * 2.2)
data = np.empty((estimated_N, 4))
# Runs the first iteraction
n = 0
(t, x, v, a) = rw.__next__()
data[n] = (t, x, v, a)
# While there is simulation time or not too far away
while (t < simul_time) and (np.abs(x) < max_distance):
n += 1
(t, x, v, a) = rw.__next__()
data[n] = (t, x, v, a)
return data[:n]
def random_walk_naive(initial_position = 0, acceleration = 0,
prob_increase=0.005, prob_decrease=0.005,
max_distance=10000, simul_time=1000):
"""Emulates same behavior as random_walk_simul, but use append instead numpy and generators"""
T = []
X = []
V = []
A = []
T.append(0)
X.append(initial_position)
V.append(0)
A.append(acceleration)
a = A[-1]
t = T[-1]
v = V[-1]
x = X[-1]
while (T[-1] < simul_time) and (abs(X[-1]) < max_distance):
god_wishes = random()
if god_wishes <= prob_increase:
# Increase acceleration
a += .005
elif god_wishes <= prob_increase+prob_decrease:
# Reduce acceleration
a -= .005
# Lets avoid too much acceleration
a = clip(a, -.2, .2)
dt = random()
t += dt
v += dt*a
x += dt*v
# Storing next simulation step
T.append(t)
X.append(x)
V.append(v)
A.append(a)
return np.array((T, X, V, A)).transpose()
def main():
random_walk_naive(max_distance=1e9, simul_time=1e6)
random_walk_simul(max_distance=1e9, simul_time=1e6)
if __name__ == '__main__':
main()