Как эффективно проходить функцию через?
Мотивация
Взгляните на следующую картинку.
![введите описание изображения здесь]()
Учитывая кривую красного, синего и зеленого. Я бы хотел найти в каждой точке оси x
доминирующую кривую. Это показано как черная графа на картинке. Из свойств красной, зеленой и синей кривой (возрастающей и постоянной через некоторое время) это сводится к тому, чтобы найти доминирующую кривую с правой стороны, а затем двигаться в сторону левой стороны, находить все точки пересечения и обновлять доминирующие кривая.
Эта общая задача должна быть решена T
раза. В этой проблеме есть один последний поворот. Синяя, зеленая и красная кривые следующей итерации строятся через доминирующее решение из предыдущей итерации плюс некоторые различные параметры. В качестве примера на рисунке выше: Решение - черная функция. Эта функция используется для создания новой синей, зеленой и красной кривой. Затем проблема снова начнется, чтобы найти доминирующую для этих новых кривых и т.д.
Вопрос в двух словах
На каждой итерации я начинаю с фиксированной правой стороны и оцениваю все три функции, чтобы увидеть, какая из них является доминирующей. Эти оценки занимают больше времени и больше по сравнению с итерацией. Мое ощущение состоит в том, что я не передаю оптимально старой доминирующей функции, чтобы построить новую синюю, зеленую и красную кривые. Причина. Я получил более раннюю версию ошибки максимальной глубины рекурсии. Другие части кода, в которых требуется значение текущей доминирующей функции (которая является существенной или зеленой, красной или синей кривой), также занимают больше времени и больше с итерацией.
В течение 5 итераций возрастает только оценка функций в одной точке с правой стороны:
Результаты были получены через
test = A(5, 120000, 100000)
И затем запустите
test.find_all_intersections()
>>> test.find_all_intersections()
iteration 4
to compute function values it took
0.0102479457855
iteration 3
to compute function values it took
0.0134601593018
iteration 2
to compute function values it took
0.0294270515442
iteration 1
to compute function values it took
0.109843969345
iteration 0
to compute function values it took
0.823768854141
Я хотел бы знать, почему это так, и если можно запрограммировать его более эффективно.
Подробное описание кода
Я быстро обобщаю наиболее важные функции. Полный код можно найти ниже. Если есть какие-либо другие вопросы относительно кода, я более чем счастлив разработать/уточнить.
-
Метод u
: для повторяющейся задачи создания новой партии
зеленой, красной и синей кривой выше, нам нужен старый доминирующий.
u
- это инициализация, которая будет использоваться в самой первой итерации.
-
Метод _function_template
: функция генерирует версии
зеленой, синей и красной кривой, используя разные параметры. Он возвращает
функция одного входа.
-
Метод eval
: Это основная функция для генерации синей, зеленой и красной версий каждый раз. Для каждой итерации требуется три различных параметра: vfunction
, который является доминирующей функцией предыдущего шага, m
и s
, которые представляют собой два параметра (флеоты), влияющие на форму результирующей кривой. Остальные параметры одинаковы для каждой итерации. В коде есть значения выборки для m
и s
для каждой итерации. Для более вызывающих: он должен аппроксимировать интеграл, где m
и s
- ожидаемое среднее и стандартное отклонение основного нормального распределения. Аппроксимация выполняется через узлы Гаусса-Эрмита/веса.
-
Метод find_all_intersections
: это основной метод нахождения в
каждая итерация является доминирующей. Он строит доминирующую
функции через кусочную мудрую конкатенацию синего, зеленого и красного
кривая. Это достигается с помощью функции piecewise
.
Вот полный код
import numpy as np
import pandas as pd
from scipy.optimize import brentq
import multiprocessing as mp
import pathos as pt
import timeit
import math
class A(object):
def u(self, w):
_w = np.asarray(w).copy()
_w[_w >= 120000] = 120000
_p = np.maximum(0, 100000 - _w)
return _w - 1000*_p**2
def __init__(self, T, upper_bound, lower_bound):
self.T = T
self.upper_bound = upper_bound
self.lower_bound = lower_bound
def _function_template(self, *args):
def _f(x):
return self.evalv(x, *args)
return _f
def evalv(self, w, c, vfunction, g, m, s, gauss_weights, gauss_nodes):
_A = np.tile(1 + m + math.sqrt(2) * s * gauss_nodes, (np.size(w), 1))
_W = (_A.T * w).T
_W = gauss_weights * vfunction(np.ravel(_W)).reshape(np.size(w),
len(gauss_nodes))
evalue = g*1/math.sqrt(math.pi)*np.sum(_W, axis=1)
return c + evalue
def find_all_intersections(self):
# the hermite gauss weights and nodes for integration
# and additional paramters used for function generation
gauss = np.polynomial.hermite.hermgauss(10)
gauss_nodes = gauss[0]
gauss_weights = gauss[1]
r = np.asarray([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1.])
m = [[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.03836174909668277, 0.08543620707856969, 0.15548297423808233],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.03836174909668277, 0.08543620707856969, 0.15548297423808233],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.03836174909668277, 0.08543620707856969, 0.15548297423808233],
[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.038063407778193614, 0.08475713587463352, 0.15420895520972322],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.03836174909668277, 0.08543620707856969, 0.15548297423808233],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624],
[0.038212567720998125, 0.08509661835487026, 0.15484578903763624]]
s = [[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019529101011406914, 0.04708607140891122, 0.20089341636351565],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.019529101011406914, 0.04708607140891122, 0.20089341636351565],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019529101011406914, 0.04708607140891122, 0.20089341636351565],
[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.01945441966324046, 0.04690600929081242, 0.200125178687699],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019529101011406914, 0.04708607140891122, 0.20089341636351565],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142],
[0.019491796104351332, 0.04699612658674578, 0.20050966545654142]]
self.solution = []
n_cpu = mp.cpu_count()
pool = pt.multiprocessing.ProcessPool(n_cpu)
# this function is used for multiprocessing
def call_f(f, x):
return f(x)
# this function takes differences for getting cross points
def _diff(f_dom, f_other):
def h(x):
return f_dom(x) - f_other(x)
return h
# finds the root of two function
def find_roots(F, u_bound, l_bound):
try:
sol = brentq(F, a=l_bound,
b=u_bound)
if np.absolute(sol - u_bound) > 1:
return sol
else:
return l_bound
except ValueError:
return l_bound
# piecewise function
def piecewise(l_comp, l_f):
def f(x):
_ind_f = np.digitize(x, l_comp) - 1
if np.isscalar(x):
return l_f[_ind_f](x)
else:
return np.asarray([l_f[_ind_f[i]](x[i])
for i in range(0, len(x))]).ravel()
return f
_u = self.u
for t in range(self.T-1, -1, -1):
print('iteration' + ' ' + str(t))
l_bound, u_bound = 0.5*self.lower_bound, self.upper_bound
l_ordered_functions = []
l_roots = []
l_solution = []
# build all function variations
l_functions = [self._function_template(0, _u, r[t], m[t][i], s[t][i],
gauss_weights, gauss_nodes)
for i in range(0, len(m[t]))]
# get the best solution for the upper bound on the very
# right hand side of wealth interval
array_functions = np.asarray(l_functions)
start_time = timeit.default_timer()
functions_values = pool.map(call_f, array_functions.tolist(),
len(m[t]) * [u_bound])
elapsed = timeit.default_timer() - start_time
print('to compute function values it took')
print(elapsed)
ind = np.argmax(functions_values)
cross_points = len(m[t]) * [u_bound]
l_roots.insert(0, u_bound)
max_m = m[t][ind]
l_solution.insert(0, max_m)
# move from the upper bound twoards the lower bound
# and find the dominating solution by exploring all cross
# points.
test = True
while test:
l_ordered_functions.insert(0, array_functions[ind])
current_max = l_ordered_functions[0]
l_c_max = len(m[t]) * [current_max]
l_u_cross = len(m[t]) * [cross_points[ind]]
# Find new cross points on the smaller interval
diff = pool.map(_diff, l_c_max, array_functions.tolist())
cross_points = pool.map(find_roots, diff,
l_u_cross, len(m[t]) * [l_bound])
# update the solution, cross points and current
# dominating function.
ind = np.argmax(cross_points)
l_roots.insert(0, cross_points[ind])
max_m = m[t][ind]
l_solution.insert(0, max_m)
if cross_points[ind] <= l_bound:
test = False
l_ordered_functions.insert(0, l_functions[0])
l_roots.insert(0, 0)
l_roots[-1] = np.inf
l_comp = l_roots[:]
l_f = l_ordered_functions[:]
# build piecewise function which is used for next
# iteration.
_u = piecewise(l_comp, l_f)
_sol = pd.DataFrame(data=l_solution,
index=np.asarray(l_roots)[0:-1])
self.solution.insert(0, _sol)
return self.solution
Ответы
Ответ 1
Ваш код действительно слишком сложный, чтобы объяснить вашу проблему. Стремитесь к чему-то более простому. Иногда вам приходится писать код, чтобы продемонстрировать проблему.
Я принимаю удар, основанный исключительно на вашем описании, а не на вашем коде (хотя я запустил код и проверил). Вот ваша проблема:
метод eval: это основная функция генерации синего, зеленого и красные версии каждый раз. Требуется три различных параметра: каждый итерация: vfunction, которая является доминирующей функцией из предыдущий шаг, m и s, которые являются двумя параметрами (флеотами), влияющими на формы результирующей кривой.
Ваш параметр vfunction
более сложный на каждой итерации. Вы передаете вложенную функцию, созданную поверх предыдущих итераций, что вызывает рекурсивное выполнение. Каждая итерация увеличивает глубину рекурсивного вызова.
Как вы можете избежать этого? Там нет простого или встроенного способа. Самый простой ответ - при условии, что входные данные для этих функций согласованы - сохранить функциональный результат (т.е. Числа), а не сами функции. Вы можете сделать это, пока у вас есть конечное число известных входов.
Если входы базовых функций не согласованы, то нет ярлыка. Вам нужно многократно оценивать эти основные функции. Я вижу, что вы выполняете кусочно сплайсинг базовых функций - вы можете проверить, стоит ли затратить на это расходы, просто беря max
каждой из основных функций.
Тест, который я выполнил (10 итераций), занял несколько секунд. Я не вижу в этом проблемы.
Ответ 2
Давайте начнем с изменения кода для вывода текущей итерации:
_u = self.u
for t in range(0, self.T):
print(t)
lparams = np.random.randint(self.a, self.b, 6).reshape(3, 2).tolist()
functions = [self._function_template(_u, *lparams[i])
for i in range(0, 3)]
# evaluate functions
pairs = list(itertools.combinations(functions, 2))
fval = [F(diff(*pairs[i]), self.a, self.b) for i in range(0, 3)]
ind = np.sort(np.unique(np.random.randint(self.a, self.b, 10)))
_u = _temp(ind, np.asarray(functions)[ind % 3])
Заглядывая в строку, вызывающую поведение,
fval = [F(diff(*pairs[i]), self.a, self.b) for i in range(0, 3)]
представляющие интерес функции будут F
и diff
. Последнее простое, первое:
def F(f, a, b):
try:
brentq(f, a=a, b=b)
except ValueError:
pass
Хмм, проглатывая исключения, посмотрим, что получится, если мы:
def F(f, a, b):
brentq(f, a=a, b=b)
Сразу же для первой функции и первой итерации возникает ошибка:
ValueError: f (a) и f (b) должны иметь разные знаки
Глядя на docs, это является обязательным условием функции поиска корня brentq
. Позвольте изменить определение еще раз, чтобы контролировать это условие на каждой итерации.
def F(f, a, b):
try:
brentq(f, a=a, b=b)
except ValueError as e:
print(e)
Выходной сигнал
i
f(a) and f(b) must have different signs
f(a) and f(b) must have different signs
f(a) and f(b) must have different signs
для i
в диапазоне от 0 до 57. Значение, когда функция F
всегда выполняет какую-либо реальную работу для i=58
. И он продолжает делать это для более высоких значений i
.
Заключение: для этих более высоких значений требуется больше времени, потому что:
- корень никогда не вычисляется для более низких значений
- число вычислений растет линейно для
i>58