У меня есть категория функций «детекторы», которые, учитывая видео, параметр пространственной шкалы и параметр шкалы времени, обнаруживают некоторые точки интереса. Я написал функцию «MultiscaleDetector», которая в основном принимает список параметров пространственного масштаба и функцию «детектор» в качестве параметров и выполняет заданный коэффициент детектора для каждого масштаба.
Вот как они выглядят:
def GaborDetector(v, sigma, tau, threshold, num_points):
"""
Gabor Detector
Keyword arguments:
video -- input video (y_len, x_len, frames)
sigma -- Gaussian kernel space standard deviation
tau -- Gaussian kernel time standard deviation
kappa -- Gabor response threshold
"""
# setup video
video = v.copy()
video = video.astype(float)/video.max()
video = video_smoothen_space(video, sigma)
# first define a linspace of width -2tau to 2tau
time = np.linspace(-2*tau, 2*tau, int(4*tau+1))
omega = 4/tau
# define the gabor filters
h_ev = np.exp(-time**2/(2*tau**2)) * np.cos(2*np.pi*omega*time)
h_od = np.exp(-time**2/(2*tau**2)) * np.sin(2*np.pi*omega*time)
# normalize the L1 norm
h_ev /= np.linalg.norm(h_ev, ord=1)
h_od /= np.linalg.norm(h_od, ord=1)
# compute the response
response = (scp.convolve1d(video, h_ev, axis=2) ** 2) + (scp.convolve1d(video, h_od, axis=2) ** 2)
points = interest_points(response, num=num_points, threshold=threshold, scale=sigma)
return points
def MultiscaleDetector(detector, video, sigmas, tau, num_points):
"""
Multiscale Detector
Executes a detector at multiple scales. Detector has to be a function that
takes a video as input, along with other parameters, and returns a list of interest points.
Keyword arguments:
detector -- function that returns interest points
video -- input video (y_len, x_len, frames)
sigmas -- list of scales
"""
# for every scale, compute the response
points = []
for sigm in sigmas:
found = detector(video, sigm, tau)
points.append(found)
# filter the points, currently irrelevant
Как вы, возможно, уже заметили, внутри функции "GaborDetector" выполняются некоторые тяжелые вычисления, которые зависят только от параметра времени. Поэтому функция «MultiscaleDetector» излишне пересчитывает эти переменные при каждом вызове.
Пытаясь избежать рефакторинга кода, я придумал то, что хотел, чтобы было хорошим трюком, но считал, что это будет напрасно:
def MultiscaleDetector(detector, video, sigmas, tau, num_points):
"""
Multiscale Detector
Executes a detector at multiple scales. Detector has to be a function that
takes a video as input, along with other parameters, and returns a list of interest points.
Keyword arguments:
detector -- function that returns interest points
video -- input video (y_len, x_len, frames)
sigmas -- list of scales
"""
optimization_trick = lambda s_param: detector(video, s_param, tau)
# for every scale, compute the response
points = []
for sigm in sigmas:
found = optimization_trick(sigm)
points.append(found)
# filter the points, currently irrelevant
Я надеялся, что переменные, зависящие только от тау, останутся каким-то образом «сохраненными» в «оптимизации_трюке» и не будут пересчитаны. Однако, когда я измерял время для разных реализаций, разница составляла около 0,02 секунды, причем «оптимизированная» функция работала быстрее.
Обновлено:
Фактический вызов, который имеет место, выглядит следующим образом:
# read the video
video = read_video(video_name, num_frames, 0)
# get the interest points
detector = lambda v, s, t: GaborDetector(v, s, t, 0.3, 500)
scales = [3*(1.1)**i for i in range(6)]
start = time.time()
points = MultiscaleDetector(detector, video, scales, 1.5, 500)
end = time.time()
print("Time: {}".format(end-start))
Я попытался поместить все переменные, которых хотел избежать, в другую функцию, например:
def GaborDetectorTrial(v, sigma, tau, threshold, num_points):
"""
Gabor Detector
Keyword arguments:
video -- input video (y_len, x_len, frames)
sigma -- Gaussian kernel space standard deviation
tau -- Gaussian kernel time standard deviation
kappa -- Gabor response threshold
"""
@lru_cache(maxsize=None)
def time_function(tau):
time = np.linspace(-2*tau, 2*tau, int(4*tau+1))
omega = 4/tau
# define the gabor filters
h_ev = np.exp(-time**2/(2*tau**2)) * np.cos(2*np.pi*omega*time)
h_od = np.exp(-time**2/(2*tau**2)) * np.sin(2*np.pi*omega*time)
# normalize the L1 norm
h_ev /= np.linalg.norm(h_ev, ord=1)
h_od /= np.linalg.norm(h_od, ord=1)
return h_ev, h_od
# setup video
video = v.copy()
video = video.astype(float)/video.max()
video = video_smoothen_space(video, sigma)
# compute the response
h_ev, h_od = time_function(tau)
response = (scp.convolve1d(video, h_ev, axis=2) ** 2) + (scp.convolve1d(video, h_od, axis=2) ** 2)
points = interest_points(response, num=num_points, threshold=threshold, scale=sigma)
return points
Переменные, которые я хочу избежать пересчета, это h_ev и h_od. Время выполнения было в основном таким же (на самом деле, на несколько мс медленнее).
@SparklingMarcel звучит как хорошая идея, я не знал, что такие вещи существуют. Я это попробую.
@SparklingMarcel Я немного изучил это, и кажется, что эти декораторы предназначены для кеширования результатов, если я правильно понимаю. В моем примере меня интересует кэширование переменных «время», «омега» и «h_ev», «h_od». Я не перестану изучать это, потому что это кажется интересным, но сейчас у меня мало времени. Спасибо за предложение в любом случае!
@StefanosAnagnostou Разве извлечение вычисления в отдельную функцию, которая принимает параметр времени, не сделает так, чтобы вы могли использовать декоратор кеша?
@Sylwester Я прочитал твой комментарий и только что попробовал. К сожалению, похоже, что это не окупается. Я засекал разные версии функции, и она по крайней мере так же медленна, если не медленнее. Кроме того, я заметил еще кое-что: декоратор, похоже, не работает с ndarrays. Это не имеет отношения к настоящей проблеме, но относится к исходному абстрактному вопросу, который я поставил.
Что-то, чего я не понимаю в вашем коде, это то, что вы никогда не вызываете GaborDetector в своем примере. В любом случае, если некоторые из ваших переменных всегда одинаковы и их нужно вычислить только один раз, поместите их в функцию, верните их, а затем используйте их в качестве параметра для другой функции. Если вы можете обновить свой код с помощью соответствующего вызова функции (GarborDetector), а затем указать нам, какую именно переменную вы не хотите «пересчитывать», я смогу дать удовлетворительный ответ :)
@SparklingMarcel Извините, я думал, что объяснил это, но, возможно, это требует пояснений. Функция "GaborDetector" передается в качестве параметра "MultiscaleDetector". "MultiscaleDetector" предназначен для использования в качестве параметра любой существующей "детекторной" функции (например, "GaborDetector" или других детекторов, которые я не показывал, поскольку они не имеют значения). Я думаю, что ваше последнее предложение - это то, что также предложил Сильвестр. Звучит очень разумно, и я действительно пробовал, но, к сожалению, это не окупается. Я обновлю вопрос.
Как можно передать GaborDetector как функцию, если она принимает 5 параметров?
@quamrana смотрите отредактированный вопрос. я могу использовать лямбда-функцию, чтобы определить другие параметры и превратить ее в функцию, которая принимает всего три параметра. Я знаю, что дизайн далек от хорошего, но я не думаю, что это именно то, что нужно прямо сейчас. Суть в том, как избежать повторного вычисления одних и тех же вещей несколько раз.
Хорошо, теперь я это вижу. Но какой именно бит вы хотите избежать пересчета?
В вашем примере вы вызываете GaborDetectorTrial только один раз, поэтому вы ничего не пересчитываете, вы имеете в виду, что в реальном случае вы вызываете GaborDetectorTrial несколько раз, но «Тау» всегда одно и то же?
@Stefanos Anagnostou — Вы пишете: «На самом деле происходит следующий звонок: … detector = lambda v, s, t: GaborDetector(v, s, t, 0.3, 500)»; по этому вы вообще не звоните GaborDetectorTrial, а скорее GaborDetector.
@SparklingMarcel В моем примере GaborDetector вызывается несколько раз внутри MultiscaleDetector. Внутри MultiscaleDetector есть цикл, который вызывает GaborDetector несколько раз для разных сигм, но для одного и того же тау. Да, "тау" всегда одно и то же.
@Armali Я отсчитываю исходную версию, а затем меняю код на пробную версию. Я не хотел публиковать слишком много кода. Возможно, я должен был быть более ясным. Извините за путаницу.






Разве вы не можете просто разделить несколько функций?
Пример :
def time_function(tau): #To compute hev and hod
time = np.linspace(-2*tau, 2*tau, int(4*tau+1))
omega = 4/tau
# define the gabor filters
h_ev = np.exp(-time**2/(2*tau**2)) * np.cos(2*np.pi*omega*time)
h_od = np.exp(-time**2/(2*tau**2)) * np.sin(2*np.pi*omega*time)
# normalize the L1 norm
h_ev /= np.linalg.norm(h_ev, ord=1)
h_od /= np.linalg.norm(h_od, ord=1)
return h_ev, h_od
а потом
# read the video
video = read_video(video_name, num_frames, 0)
# get the interest points
custom_tau = {}
scales = [3*(1.1)**i for i in range(6)]
start = time.time()
for i in range(0,10): # for loop cause otherwise you call GaborDetector only once so the question is not revelant
#Insert logic here because for your question to be revelant Tau must not always be the same
if tau not in custom_tau.keys(): # Logic to "cache" hev and hod for the same tau value
hev, hod = time_function(tau)
custom_tau[tau] = (hev, hod)
else:
hev, hod = custom_tau[tau]
gabor = GaborDetector(v, s, t, hev, hod, 0.3, 500)
points = MultiscaleDetector(gabor, video, scales, 1.5, 500)
end = time.time()
print("Time: {}".format(end-start))
и, наконец, вы меняете GarborCollector, чтобы не пересчитывать время
def GaborDetectorTrial(v, sigma, tau,hev, hod, threshold, num_points):
video = v.copy()
video = video.astype(float)/video.max()
video = video_smoothen_space(video, sigma)
response = (scp.convolve1d(video, h_ev, axis=2) ** 2) + (scp.convolve1d(video, h_od, axis=2) ** 2)
points = interest_points(response, num=num_points, threshold=threshold, scale=sigma)
return points
Что вы делаете в этом коде, так это «кэширование» (через словарь) hev и hod, так что, если вы уже вычислили его для данного «tau», вы вместо этого ищете результат в словаре, в противном случае вы вычисляете его и помещаете его в словаре
Мне пришлось экстраполировать и угадать, как будет работать ваш код, потому что в приведенном вами случае he_v и ho_d никогда не вычисляются повторно, поскольку вы вызываете garbor только один раз.
Я переформатировал код так, чтобы функция GaborDetector принимала «h_ev» и «h_od» в качестве параметров, предварительно вычисленных «time_function», близко к тому, что вы предлагаете. Есть небольшое, но постоянное ускорение около 200-500 мс. Лично я не нахожу это идеальным (из соображений дизайна, хотя, возможно, это легко исправить), но ускорение есть! Я отмечу ответ как принятый.
Вы должны определить функцию для кэширования (time_function) в соответствующей области, здесь: вне GaborDetectorTrial - как и в вашем коде выше, жизнь time_function заканчивается каждый раз, когда GaborDetectorTrial возвращается, как и его кеш.
Я изменил область, переместив «time_function» за пределы определения «GaborDetector», но, к сожалению, я все еще не вижу ускорения. Я нахожу это действительно странным, потому что то, что вы предлагаете, кажется мне логичным.
Хм - мне тоже это кажется странным, мне кажется, это сработало. (Я проверил, вставив print("time_function(%d)"%tau) в time_function.)
Рассматривали ли вы использование декоратора кеша, чтобы уже вычисленное значение не вычислялось снова, а бралось непосредственно из кеша?