Юлия создает массив функций с совпадающими аргументами для выполнения в цикле - PullRequest
1 голос
/ 23 января 2020
  1. Легко создать массив функций и выполнить их в любом oop.
  2. Легко предоставить аргументы либо в соответствующем массиве одинаковой длины, либо массив может состоять из кортежей (fn, arg).

Для 2 l oop просто

for fn_ar in arr  # arr is [(myfunc, [1,2,3]), (func2, [10,11,12]), ...]
    fn_ar[1](fn_ar[2])
end

Вот проблема: аргументы, которые я использую, являются массивами очень больших массивов , В # 2 аргумент, который будет вызываться с помощью функции, будет текущим значением массива при первоначальном создании записи arg кортежа. Мне нужно предоставить имена массивов в качестве аргумента и отложить оценку аргументов до тех пор, пока соответствующая функция не будет запущена в теле l oop.

Я мог бы предоставить массивы, используемые в качестве входных данных, в качестве выражения и Выразите выражение в l oop, чтобы предоставить необходимые аргументы. Но eval не может быть eval в локальной области.

То, что я сделал, сработало (вроде), заключалось в создании замыкания для каждой функции, которая захватывает массивы (которые на самом деле являются просто ссылкой на хранилище). Это работает, потому что единственным аргументом для каждой функции, которая изменяется в теле l oop, оказывается счетчик l oop. Рассматриваемые функции обновляют массивы на месте. Аргумент массива - это просто ссылка на место хранения, поэтому каждая функция, выполняемая в теле l oop, видит самые последние значения массивов. Это сработало. Это было не сложно сделать. Это очень, очень медленно. Это известная проблема в Юлии.

Я попробовал рекомендуемые советы в разделе производительности руководства. Убедитесь, что захваченные переменные набраны до того, как они будут захвачены, чтобы JIT знал, что они из себя представляют. Не влияет на перф. Другой совет - поместить определение функции карри с данными для замыкания в блок let. Пробовал это. Не влияет на перф. Возможно, я неправильно реализовал подсказки - я могу предоставить фрагмент кода, если это поможет.

Но я бы лучше задал вопрос о том, что я пытаюсь сделать, а не запутал воду своим прошлым усилия, которые не могут идти по правильному пути.

Вот небольшой фрагмент, который более реалистичен c, чем приведенный выше:

Просто пара функций и аргументов:

(affine!, "(dat.z[hl], dat.a[hl-1], nnw.theta[hl], nnw.bias[hl])")
(relu!, "(dat.a[hl], dat.z[hl])")

Конечно, аргументы можно заключить в Meta.parse как выражение. dat.z и dat.a - это матрицы, используемые в машинном обучении. hl индексирует слой модели для линейного результата и нелинейной активации.

Упрощенная версия l oop, где я хочу выполнить стек функций по слоям модели:

function feedfwd!(dat::Union{Batch_view,Model_data}, nnw, hp, ff_execstack)  

    for lr in 1:hp.n_layers
        for f in ff_execstack[lr]
            f(lr)
        end
    end

end

Итак, закрытие массивов происходит слишком медленно. Eval Я не могу приступить к работе.

Есть предложения ...?

Спасибо, Льюис

1 Ответ

1 голос
/ 23 января 2020

Я решил это с красотой композиции функций.

Вот l oop, который выполняет функции прямой связи для всех слоев:

for lr in 1:hp.n_layers
    for f in ff_execstack[lr]
        f(argfilt(dat, nnw, hp, bn, lr, f)...)
    end
end

Параметр внутренней функции f вызвал фильтры argfilt вниз из общего списка c всех входных данных, чтобы вернуть набор аргументов, необходимых для указанной функции c. Это также использует красоту метода отправки. Обратите внимание, что функция f является входом для argfilt. Типы функций являются синглетонами: каждая функция имеет уникальный тип, как, например, в typeof (relu!). Таким образом, без каких-либо сумасшедших ветвлений метод dispatch позволяет argfilt возвращать только необходимые аргументы. Стоимость производительности по сравнению с передачей аргументов непосредственно функции составляет около 1,2 нс. Это происходит в очень жаркое время l oop, которое обычно работает 24 000 раз, что составляет 29 микросекунд за весь тренировочный проход.

Другая замечательная вещь заключается в том, что это происходит менее чем за 1/10 времени версия с использованием замыканий. Я получаю немного лучшую производительность, чем моя оригинальная версия, которая использовала некоторые переменные функции и кучу операторов if в горячем l oop для feedfwd.

Вот как выглядит пара методов для argfilt:

function argfilt(dat::Union{Model_data, Batch_view}, nnw::Wgts, hp::Hyper_parameters, 
    bn::Batch_norm_params, hl::Int, fn::typeof(affine!))
    (dat.z[hl], dat.a[hl-1], nnw.theta[hl], nnw.bias[hl])
end

function argfilt(dat::Union{Model_data, Batch_view}, nnw::Wgts, hp::Hyper_parameters, 
    bn::Batch_norm_params, hl::Int, fn::typeof(relu!))
    (dat.a[hl], dat.z[hl])
end

Предыстория: я пришел сюда, рассуждая, что могу передать один и тот же список аргументов всем функциям: объединение всех возможных аргументов - не так уж и плохо, поскольку всего 9 аргументов. Игнорируемые аргументы тратят некоторое пространство в стеке, но это крохотно, потому что для структур и массивов аргумент является ссылкой на указатель, а не на все данные. Недостатком является то, что каждая из этих функций (около 20 или около того) должна иметь большой список аргументов. Хорошо, но глупо: это не имеет особого смысла, когда вы смотрите на код любой из функций. Но, если бы я мог отфильтровать аргументы только для того, что нужно, сигнатуры функций не нужно менять.

Это своего рода крутая модель. Нет необходимости в самоанализе или оценке; просто функционирует.

...