Могу ли я ускорить этот базовый код линейной алгебры?

Мне было интересно, можно ли оптимизировать следующее с помощью Numpy или математического обмана.

def f1(g, b, dt, t1, t2):
  p = np.copy(g)
  for i in range(dt):
    p += t1*np.tanh(np.dot(p, b)) + t2*p
  return p

где g - вектор длины n, b - это матрица n x n, dt - число итераций, а t1 и t2 - скаляры.

Я быстро исчерпал идеи о том, как оптимизировать это дальше, потому что p используется в цикле во всех трех терминах уравнения: при добавлении к себе; в точечном продукте; и в скалярном умножении.

Но, возможно, есть другой способ представить эту функцию или есть другие трюки, чтобы повысить ее эффективность. Если возможно, я бы предпочел не использовать Cython и т.д., Но я был бы готов использовать его, если бы улучшения скорости были значительными. Заранее спасибо, и извиняюсь, если вопрос вне сферы видимости.

Обновление:

Представленные ответы до сих пор более сфокусированы на том, какие значения ввода/вывода могут быть во избежание ненужных операций. Теперь я обновил MWE с правильными значениями инициализации для переменных (я не ожидал, что идеи оптимизации придут с этой стороны - извинения). g будет находиться в диапазоне [-1, 1], а b будет находиться в диапазоне [-infinity, infinity]. Аппроксимация вывода не является опцией, потому что возвращенные векторы позже передаются функции оценки - аппроксимация может возвращать один и тот же вектор для довольно аналогичного ввода, поэтому это не вариант.


MWE:

import numpy as np
import timeit

iterations = 10000

setup = """
import numpy as np
n  = 100
g  = np.random.uniform(-1, 1, (n,)) # Updated.
b  = np.random.uniform(-1, 1, (n,n)) # Updated.
dt = 10
t1 = 1
t2 = 1/2

def f1(g, b, dt, t1, t2):
  p = np.copy(g)
  for i in range(dt):
    p += t1*np.tanh(np.dot(p, b)) + t2*p
  return p
"""

functions = [
  """
    p = f1(g, b, dt, t1, t2)
  """
]

if __name__ == '__main__':
  for function in functions:
    print(function)
    print('Time = {}'.format(timeit.timeit(function, setup=setup,
                                           number=iterations)))

Ответ 1

Чтобы ускорить работу кода без cython или jit, некоторые математические обманщики могут быть более легким подходом. Мне кажется, что если мы определяем a k(g, b) = f1(g, b, n+1, t1, t2)/f1(g, b, n, t1, t2) для n в положительном N, то функция k должна иметь предел t1+t2 (пока еще нет твердого доказательства, просто ощущение кишки; - частный случай для E (g) = 0 и E (p) = 0 также.). Для t1=1 и t2=0.5, k(), как представляется, приближается к пределу довольно быстро, для N>100 оно почти константа 1.5.

Итак, я думаю, что подход с численным приближением должен быть самым простым. enter image description here

In [81]:

t2=0.5
data=[f1(g, b, i+2, t1, t2)/f1(g, b, i+1, t1, t2) for i in range(1000)]
In [82]:

plt.figure(figsize=(10,5))
plt.plot(data[0], '.-', label='1')
plt.plot(data[4], '.-', label='5')
plt.plot(data[9], '.-', label='10')
plt.plot(data[49], '.-', label='50')
plt.plot(data[99], '.-', label='100')
plt.plot(data[999], '.-', label='1000')
plt.xlim(xmax=120)
plt.legend()
plt.savefig('limit.png')

In [83]:

data[999]
Out[83]:
array([ 1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,  1.5,
        1.5])

Ответ 2

Я смущаюсь дать это как ответ, поскольку я думаю, что это может быть артефакт входных данных, которые вы нам дали. Тем не менее, обратите внимание, что tanh(x) ~ 1 для x>>1. Ваши входные данные во все времена, когда я его запускал, имеют x = np.dot(p,b) >> 1, поэтому мы можем заменить f1 на f2.

def f1(g, b, dt, t1, t2):
  p = np.copy(g)
  for i in range(dt):
      p += t1*np.tanh(np.dot(p, b)) + t2*p
  return p

def f2(g, b, dt, t1, t2):
  p = np.copy(g)
  for i in range(dt):
      p += t1 + t2*p
  return p

print np.allclose(f1(g,b,dt,t1,t2), f2(g,b,dt,t1,t2))

Что действительно показывает, что две функции численно эквивалентны. Заметим, что f2 является неоднородным линейным рекуррентным отношением, и его можно решить за один шаг, если вы решите это сделать.