Julia ReverseDiff:如何采用梯度 w.r.t。只是输入的一个子集?
Julia ReverseDiff: how to take a gradient w.r.t. only a subset of inputs?
在我的数据流中,我正在查询数据库的一小部分,使用这些结果构建大约十几个数组,然后,给定一些参数值,计算似然值。然后重复数据库的一个子集。我想计算关于参数而不是数据的似然函数的梯度。但是 ReverseDiff 计算关于所有输入的梯度。我该如何解决这个问题?具体来说,我怎样才能构造一个 ReverseDiff.Tape object
TL;DR:如何结合随机梯度下降和 ReverseDiff? (我并不拘泥于使用 ReverseDiff。它似乎是完成这项工作的正确工具。)
看来这一定是一个通用的编码模式。它一直在我的领域使用。但我错过了一些东西。 Julia 的范围规则似乎破坏了 scoped/anonymous 函数方法,并且 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
目前这是不可能的(很遗憾)。这两个解决方法存在 GitHub 问题:
https://github.com/JuliaDiff/ReverseDiff.jl/issues/36
- 要么不使用预录磁带
- 或者相对于所有参数微分并忽略一些输入参数的梯度。
我遇到了同样的问题,我改用了Knet的grad功能。我只支持相对于一个参数的微分,但这个参数可以非常灵活(例如数组数组或字典或数组)。
谢谢 Alex,您的回答已经完成了 90%。 AutoGrad(Knet 在撰写本文时使用的)确实提供了一个非常漂亮的界面,我认为这对大多数用户来说很自然。然而,事实证明,使用 ReverseDiff 的匿名函数比 AutoGrad 所采用的方法更快,原因我不太明白。
如果您遵循所链接内容中引用的问题链,这似乎是 ReverseDiff/ForwardDiff 人们希望人们做的事情:
ReverseDiff.gradient(p -> f(p, non_differentiated_data), params)
当然令人失望的是,我们无法获得具有这种令人难以置信的常见使用场景的预编译磁带,也许未来的工作会有所改变。但这似乎就是现在的情况。
有兴趣进一步阅读的一些参考资料:
在我的数据流中,我正在查询数据库的一小部分,使用这些结果构建大约十几个数组,然后,给定一些参数值,计算似然值。然后重复数据库的一个子集。我想计算关于参数而不是数据的似然函数的梯度。但是 ReverseDiff 计算关于所有输入的梯度。我该如何解决这个问题?具体来说,我怎样才能构造一个 ReverseDiff.Tape object
TL;DR:如何结合随机梯度下降和 ReverseDiff? (我并不拘泥于使用 ReverseDiff。它似乎是完成这项工作的正确工具。)
看来这一定是一个通用的编码模式。它一直在我的领域使用。但我错过了一些东西。 Julia 的范围规则似乎破坏了 scoped/anonymous 函数方法,并且 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
目前这是不可能的(很遗憾)。这两个解决方法存在 GitHub 问题: https://github.com/JuliaDiff/ReverseDiff.jl/issues/36
- 要么不使用预录磁带
- 或者相对于所有参数微分并忽略一些输入参数的梯度。
我遇到了同样的问题,我改用了Knet的grad功能。我只支持相对于一个参数的微分,但这个参数可以非常灵活(例如数组数组或字典或数组)。
谢谢 Alex,您的回答已经完成了 90%。 AutoGrad(Knet 在撰写本文时使用的)确实提供了一个非常漂亮的界面,我认为这对大多数用户来说很自然。然而,事实证明,使用 ReverseDiff 的匿名函数比 AutoGrad 所采用的方法更快,原因我不太明白。
如果您遵循所链接内容中引用的问题链,这似乎是 ReverseDiff/ForwardDiff 人们希望人们做的事情:
ReverseDiff.gradient(p -> f(p, non_differentiated_data), params)
当然令人失望的是,我们无法获得具有这种令人难以置信的常见使用场景的预编译磁带,也许未来的工作会有所改变。但这似乎就是现在的情况。
有兴趣进一步阅读的一些参考资料: