Юлия РеверсДифф: как взять градиент w.r.t. только подмножество входов? - PullRequest
0 голосов
/ 04 мая 2018

В моем потоке данных я запрашиваю небольшое подмножество базы данных, используя эти результаты для построения около десятка массивов, а затем, учитывая некоторые значения параметров, вычисляю значение вероятности. Затем повторите для подмножества базы данных. Я хочу вычислить градиент функции правдоподобия по отношению к параметрам, но не к данным. Но ReverseDiff вычисляет градиент относительно всех входных данных. Как я могу обойти это? В частности, как я могу построить объект ReverseDiff.Tape

TL; DR: Как объединить стохастический градиентный спуск и ReverseDiff? (Я не привязан к использованию ReverseDiff. Это просто показалось мне подходящим инструментом для работы.)

Кажется, это должен быть общий шаблон кодирования. Это используется все время в моей области. Но я что-то упустил. Правила области видимости Джулии, кажется, подрывают подход к ограниченным / анонимным функциям, и ReverseDiff сохраняет исходные значения данных в сгенерированной ленте вместо использования мутированных значений.

пример кода вещей, которые не работают

using ReverseDiff
using Base.Test


mutable struct data
    X::Array{Float64, 2}
end

const D = data(zeros(Float64, 2, 2))

# baseline known data to compare against
function f1(params)
    X = float.([1 2; 3 4])
    f2(params, X)
end

# X is data, want derivative wrt to params only
function f2(params, X)
    sum(params[1]' * X[:, 1] - (params[1] .* params[2])' * X[:, 2].^2)
end

# store data of interest in D.X so that we can call just f2(params) and get our
# gradient
f2(params) = f2(params, D.X)

# use an inner function and swap out Z's data
function scope_test()
    function f2_only_params(params)
        f2(params, Z)
    end
    Z = float.([6 7; 1 3])
    f2_tape = ReverseDiff.GradientTape(f2_only_params, [1, 2])

    Z[:] = float.([1 2; 3 4])
    grad = ReverseDiff.gradient!(f2_tape, [3,4])
    return grad
end

function struct_test()
    D.X[:] = float.([6 7; 1 3])
    f2_tape = ReverseDiff.GradientTape(f2, [1., 2.])
    D.X[:] = float.([1 2; 3 4])
    grad = ReverseDiff.gradient!(f2_tape, [3., 4.])
    return grad
end

function struct_test2()
    D.X[:] = float.([1 2; 3 4])
    f2_tape = ReverseDiff.GradientTape(f2, [3., 4.])
    D.X[:] = float.([1 2; 3 4])
    grad = ReverseDiff.gradient!(f2_tape, [3., 4.])
    return grad
end

D.X[:] = float.([1 2; 3 4])

@test f1([3., 4.]) == f2([3., 4.], D.X)
@test f1([3., 4.]) == f2([3., 4.])

f1_tape = ReverseDiff.GradientTape(f1, [3,4])
f1_grad = ReverseDiff.gradient!(f1_tape, [3,4])
# fails! uses line 33 values
@test scope_test() == f1_grad
# fails, uses line 42 values
@test struct_test() == f1_grad
# succeeds, so, not completely random
@test struct_test2() == f1_grad

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

Спасибо, Алекс, твой ответ прошел 90% пути. AutoGrad (то, что Knet использует на момент написания статьи) действительно обеспечивает очень приятный интерфейс, который, я думаю, является естественным для большинства пользователей. Однако оказывается, что использование анонимных функций с ReverseDiff быстрее, чем подход AutoGrad, по причинам, которые я не совсем понимаю.

Если вы следуете цепочке проблем, на которые ссылается то, что вы связали, то похоже, что люди ReverseDiff / ForwardDiff хотят, чтобы люди делали:

ReverseDiff.gradient(p -> f(p, non_differentiated_data), params)

Конечно, разочаровывает, что мы не можем получить предварительно скомпилированную ленту с этим невероятно распространенным сценарием использования, и, возможно, будущая работа изменит ситуацию. Но, похоже, сейчас все так.

Некоторые ссылки для тех, кто заинтересован в дальнейшем чтении:

0 голосов
/ 06 мая 2018

В настоящее время это невозможно (к сожалению). И есть проблема GitHub с двумя обходными путями: https://github.com/JuliaDiff/ReverseDiff.jl/issues/36

  • либо не используйте предварительно записанную ленту
  • или дифференцируйте относительно всех аргументов и игнорируйте градиент для некоторых входных параметров.

У меня была такая же проблема, и я использовал вместо нее функцию grad. Я поддерживаю только дифференцирование относительно одного аргумента, но этот аргумент может быть достаточно гибким (например, массив массивов, или dict, или массивы).

...