numpy генерация векторизованного значения из усеченного гауссовского распределения - PullRequest
0 голосов
/ 08 марта 2020

У меня есть функция, которая генерирует значение из усеченного нормального распределения со значением l oop, которое гарантирует, что любое сгенерированное значение, которое находится за пределами усечения, будет отброшено и заменено другим поколением, пока оно не окажется в пределах диапазона.

def gen_truncated(minimum, maximum, ave, sigma):
    # min=0.9, max=1, 
    x = 0.
    while x < minimum or x > maximum:
        x = np.random.normal(0,1)*sigma+ave

    return x

Как я могу векторизовать эту функцию таким образом, чтобы x был теперь массивом многих x значений, сгенерированных таким образом, что всегда есть время l oop, гарантирующее, что элементы массива восстанавливаются, пока выполняются условия x < minimum и x > maximum? Есть ли векторизованный способ сравнения каждого элемента из x с числом, например minimum или maximum?

Редактировать: Что, если у меня будет больше ограничений, которые должны быть выполнены? В конечном счете, я стремлюсь векторизовать генерацию матрицы 4x4, которая генерируется через несколько ограничений, ограничение в gen_truncated() является лишь одним из многих. У меня есть gen_sigma(), который сначала генерирует 3 значения lambda1, lambda2, lambda3, теперь lambda3 снова должен удовлетворять нескольким условиям относительно значений lambda1 и lambda2, в противном случае они перерисовываются. Как только они верны, все три значения вводятся в get_tau() для генерации 3 значений. Опять же, эти значения тау должны удовлетворять еще нескольким ограничениям, в противном случае они отбрасываются и генерируются снова, пока не станут правильными. В конечном итоге они образуют матрицу 4x4, называемую sigma_gen, которая умножается влево и вправо на create_rotation() - gen_channel, создавая единственную матрицу 4x4 channel.

import numpy as np
from numpy.linalg import norm

def gen_sigma(minimum, maximum, ave, sigma):
    lambda1 = gen_truncated(minimum, maximum, ave, sigma)
    lambda2 = gen_truncated(minimum, maximum, ave, sigma)
    lambda3 = gen_truncated(minimum, maximum, ave, sigma)

    while 1+lambda3 < abs(lambda1+lambda2) or 1-lambda3 < abs(lambda2-lambda1):
        lambda3 = gen_truncated(minimum, maximum, ave, sigma)

    tau = get_tau(lambda1, lambda2, lambda3)
    lambdas = [lambda1, lambda2, lambda3]
    while (norm(tau)**2 >
           1-sum([x**2 for x in [lambda1, lambda2, lambda3]]) +
           2*lambda1*lambda2*lambda3) or (z_eta(tau, lambdas) < 0):
        tau = get_tau(lambda1, lambda2, lambda3)

    sigma_gen = np.array([[     1,       0, 0, 0],
                          [tau[0], lambda1, 0, 0],
                          [tau[1], 0, lambda2, 0],
                          [tau[2], 0, 0, lambda3]])

    return sigma_gen

def get_tau(einval1, einval2, einval3):
    max_tau1 = 1 - abs(einval1)
    max_tau2 = 1 - abs(einval2)
    max_tau3 = 1 - abs(einval3)
    tau1 = max_tau1*(2*np.random.uniform(0,1)-1)
    tau2 = max_tau2*(2*np.random.uniform(0,1)-1)
    tau3 = max_tau3*(2*np.random.uniform(0,1)-1)

    return [tau1, tau2, tau3]

def z_eta(t: np.ndarray, l: np.ndarray):
    condition = (norm(t)**4 - 2*norm(t)**2 -
                 2*sum([(l[i]**2)*(2*(t[i]**2-norm(t)**2)) for i in range(3)])+
                 q(l))
    return condition

def q(e: np.ndarray):
    # e are the eigenvalues
    return (1+e[0]+e[1]+e[2])*(1+e[0]-e[1]-e[2])*(1-e[0]+e[1]-e[2])*(1-e[0]-e[1]+e[2])

def create_rotation(angles: np.ndarray) -> np.ndarray:
    "random rotation in PL form"
    # input np.random.normal(0,1,3)*0.06
    rotation = np.eye(4, dtype=complex)
    left = np.array([[ np.cos(angles[0]), np.sin(angles[0]), 0],
                     [-np.sin(angles[0]), np.cos(angles[0]), 0],
                     [                 0,                 0, 1]])
    mid = np.array([[1,                 0,                 0],
                    [0, np.cos(angles[1]), np.sin(angles[1])],
                    [0, -np.sin(angles[1]), np.cos(angles[1])]])
    right = np.array([[ np.cos(angles[2]), np.sin(angles[2]), 0],
                      [-np.sin(angles[2]), np.cos(angles[2]), 0],
                      [                 0,                 0, 1]])
    rotation[1:4,1:4] = left@mid@right

    return rotation

def gen_channel(r1, r2, ave, sigma):
    rand1 = np.random.normal(0,1,3)
    rand2 = np.random.normal(0,1,3)
    channel = create_rotation(rand1*r1)@gen_sigma(0.9, 1, ave, sigma)@\
              create_rotation(rand2*r2)
    return channel

Пример прогона канал

gen_channel(0.05, 0.05, 0.98, 0.15)

даст, например,

Out[140]: 
array([[ 1.        +0.j,  0.        +0.j,  0.        +0.j,
         0.        +0.j],
       [-0.05828008+0.j,  0.91805971+0.j,  0.14291751+0.j,
        -0.00946994+0.j],
       [-0.00509449+0.j, -0.14170308+0.j,  0.90034613+0.j,
        -0.11548884+0.j],
       [ 0.0467522 +0.j, -0.00851749+0.j,  0.11450963+0.j,
         0.90259637+0.j]])

Теперь, если я захочу создать, скажем, 100 из этих 4x4 матриц, мне придется использовать понимание списка, т.е.

np.array([gen_channel(0.05, 0.05, 0.98, 0.15) for i in range(100)])

, который будет проходить через все сравнения ограничений и создавать матрицы 4x4 одну за другой. Теперь мой очень оригинальный вопрос был мотивирован тем фактом, что я хочу векторизовать их, поэтому вместо сравнения одного значения за раз просто сгенерируйте массив значений, используя numpy broadcast, и проверьте ограничения, чтобы у меня была векторизованная версия gen_channel, который генерирует 100 таких матриц 4x4 без необходимости понимания списка. Способ понимания списка содержит многократное использование генерации одного случайного числа, что приводит к узкому месту в его скорости выполнения. Что я хочу сделать, так это просто создать массивы случайных чисел, выполнить эти проверки, а затем сгенерировать массив каналов 4х4, чтобы уменьшить узкое место.

1 Ответ

0 голосов
/ 08 марта 2020

Вы можете нарисовать большую выборку из исходного распределения, затем определить, какие записи l ie в правильном диапазоне, а затем извлечь из этого:

# parameters
ave, sigma = 0,1
minimum, maximum = 0.9, 1

# draw sample and specify which entries are ok
a = np.random.normal(ave, sigma, 100000)
index = (a > minimum) & (a < maximum)

# draw from subset
np.random.choice(a[index], 1000, replace=False)

Используя timeit

по вышеуказанному коду:

%%timeit -r 10 -n 10 
2.51 ms ± 87.5 µs per loop (mean ± std. dev. of 10 runs, 10 loops each)

по оригиналу в al oop:

%%timeit -r 10 -n 10

for i in range(1000):
    gen_truncated(0.9,1, 0, 1)

88.5 ms ± 1.24 ms per loop (mean ± std. dev. of 10 runs, 10 loops each)
...