Flux.jl 中的自定义损失函数
Custom Loss Function in Flux.jl
我正在尝试在 Flux.jl 包中实现一个带有自定义损失函数的模型。我包含了简化模型的代码,但错误保持不变。
我有一个插值器,它采用标量值和 returns 一个 2x2 矩阵。我的模型的目标是使用 3 个观察来找到评估插值器的最佳点。为此,我编写了一个自定义损失函数来计算建议的 evalutation_point 并在此时评估插值器。然后将插值结果与数据集中的真实解进行比较。
using Flux, Zygote
using LinearAlgebra
using Interpolations
##
# create interpolator
x = LinRange(0,1,10)
y = [rand(2,2) for i in 1:10]
itp = interpolate(y, BSpline(Linear())) |> i -> scale(i, x)
# create training set
training_set = [(rand(3), rand(2,2)) for i in 0:0.2:1]
#build the model
model = Chain(Dense(3,1),i-> clamp(i[1],0,1))
opt = Descent()
ps = Flux.params(model)
function loss(evaluation_point, solution)
interpolated = itp(model(evaluation_point))
return norm(interpolated - solution)
end
# training NOK
n_epochs = 100
for epoch in 1:n_epochs
Flux.train!(loss, ps, training_set, opt)
println(sum([loss_fnc(i[1],i[2]) for i in training_set]))
end
此returns以下错误:
ERROR: DimensionMismatch("matrix A has dimensions (2,2), vector B has length 1")
Stacktrace:
[1] generic_matvecmul!(C::Vector{Matrix{Float64}}, tA::Char, A::Matrix{Float64}, B::StaticArrays.SVector{1, Matrix{Float64}}, _add::LinearAlgebra.MulAddMul{true, true, Bool, Bool})
@ LinearAlgebra C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:713
[2] mul!
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:81 [inlined]
[3] mul!
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:275 [inlined]
[4] *
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:51 [inlined]
[5] interpolate_pullback
@ C:\Users\thega\.julia\packages\Interpolations\Glp9h\src\chainrules\chainrules.jl:13 [inlined]
[6] ZBack
@ C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\chainrules.jl:204 [inlined]
[7] Pullback
@ c:\Users\thega\Desktop\Question\main.jl:21 [inlined]
[8] (::typeof(∂(loss)))(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface2.jl:0
[9] #212
@ C:\Users\thega\.julia\packages\Zygote\H6vD3\src\lib\lib.jl:203 [inlined]
[10] #1750#back
@ C:\Users\thega\.julia\packages\ZygoteRules\AIbCs\src\adjoint.jl:67 [inlined]
[11] Pullback
@ C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:102 [inlined]
[12] (::typeof(∂(λ)))(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface2.jl:0
[13] (::Zygote.var"#93#94"{Params, typeof(∂(λ)), Zygote.Context})(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface.jl:357
[14] gradient(f::Function, args::Params)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface.jl:76
[15] macro expansion
@ C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:101 [inlined]
[16] macro expansion
@ C:\Users\thega\.julia\packages\Juno\n6wyj\src\progress.jl:134 [inlined]
[17] train!(loss::Function, ps::Params, data::Vector{Tuple{Vector{Float64}, Matrix{Float64}}}, opt::Descent; cb::Flux.Optimise.var"#40#46")
@ Flux.Optimise C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:99
[18] train!(loss::Function, ps::Params, data::Vector{Tuple{Vector{Float64}, Matrix{Float64}}}, opt::Descent)
@ Flux.Optimise C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:97
[19] top-level scope
@ c:\Users\thega\Desktop\Question\main.jl:28
关于维度不匹配的问题,但损失函数的评估工作正常。
loss(training_set[1][1], training_set[1][2])
我玩了一下,发现问题出在梯度计算上:
gradient(loss , training_set[1][1], training_set[1][2])
在训练集中可以找到问题,用你提供的例子,检查一下:
julia> training_set[1][1]
3-element Vector{Float64}:
0.5093876656425886
0.05272770864628318
0.7651982428671759
julia> training_set[1][2]
2×2 Matrix{Float64}:
0.0691616 0.55414
0.5153 0.654379
对于模型,输入是:作为 x
一个 2 元素向量,模型应该学习 return 一个 2x2 矩阵作为 y
。但是,您的模型不会这样做:
julia> model(training_set[1][1])
0.6585413f0 (tracked)
它只有 return 一个实例,由于模型的定义,在这种情况下 model = Chain(Dense(3,1),i-> clamp(i[1],0,1))
变成只有一个 Chain(Dense(3, 1), #7)
,这意味着有一个 3元素向量作为输入,并且 returns 1(并且只有 1 个)实例。
解决方案:
- 将您的
y
重新定义为每个 x
的 1 个元素输出
- 重新定义你的模型(它会更复杂,因为你想要 return 一个 2x2 矩阵)。下面的模型就是一个例子:
julia> model = Chain(
Dense(3, 4),
x -> reshape(x, (2, 2))
)
但是,你应该弄清楚如何调整你的插值代码来工作
我无法解决问题。我的猜测是 Interpolations.jl 与 Zygote.jl 不兼容。我发现的一种可能的解决方法是编写自定义插值 class 和函数。如果有人感兴趣,我会提供一个工作示例:
using Flux, Zygote
using LinearAlgebra
using Interpolations
# create a custom linear splines class
struct CustomInterpolator
x::Vector
y::Vector
function CustomInterpolator(x,y)
@assert issorted(x)
return new(x,y)
end
end
function custom_interpolate(citp::CustomInterpolator, x::Number)
left_value, right_value = 0, 0
left_index, right_index = 1, 1
# check bound
if x > citp.x[end] || x < citp.x[1]
@error "Out of bounds"
throw(DomainError(x))
end
#find the right indices
for (i,v) in enumerate(citp.x)
if left_value > x
right_value = v
right_index = i
break
end
left_value = v
left_index = i
end
# do a linear inter interpolation between the two selected indices
interpolated_value = (1 - (x - left_value)/(right_value - left_value)) * citp.y[left_index] + (x - left_value)/(right_value - left_value) * citp.y[right_index]
return interpolated_value
end
##
# create custom interpolator
x = LinRange(0,1,2)
y = [zeros(2,2), ones(2,2)]
citp = CustomInterpolator(x,y)
# create training set
training_set = [(ones(3)*i, ones(2,2) - i*ones(2,2)) for i in 0:0.2:1]
#build the model
model = Chain(Dense(3,3), Dense(3,1), i-> clamp(i[1],0,1), i->custom_interpolate(citp,i))
opt = ADAM()
ps = Flux.params(model)
loss(x,y) = Flux.mse(model(x), y)
# training
n_epochs = 1000
for epoch in 1:n_epochs
Flux.train!(loss, ps, training_set, opt)
println(sum([loss(i[1],i[2]) for i in training_set]))
end
我正在尝试在 Flux.jl 包中实现一个带有自定义损失函数的模型。我包含了简化模型的代码,但错误保持不变。
我有一个插值器,它采用标量值和 returns 一个 2x2 矩阵。我的模型的目标是使用 3 个观察来找到评估插值器的最佳点。为此,我编写了一个自定义损失函数来计算建议的 evalutation_point 并在此时评估插值器。然后将插值结果与数据集中的真实解进行比较。
using Flux, Zygote
using LinearAlgebra
using Interpolations
##
# create interpolator
x = LinRange(0,1,10)
y = [rand(2,2) for i in 1:10]
itp = interpolate(y, BSpline(Linear())) |> i -> scale(i, x)
# create training set
training_set = [(rand(3), rand(2,2)) for i in 0:0.2:1]
#build the model
model = Chain(Dense(3,1),i-> clamp(i[1],0,1))
opt = Descent()
ps = Flux.params(model)
function loss(evaluation_point, solution)
interpolated = itp(model(evaluation_point))
return norm(interpolated - solution)
end
# training NOK
n_epochs = 100
for epoch in 1:n_epochs
Flux.train!(loss, ps, training_set, opt)
println(sum([loss_fnc(i[1],i[2]) for i in training_set]))
end
此returns以下错误:
ERROR: DimensionMismatch("matrix A has dimensions (2,2), vector B has length 1")
Stacktrace:
[1] generic_matvecmul!(C::Vector{Matrix{Float64}}, tA::Char, A::Matrix{Float64}, B::StaticArrays.SVector{1, Matrix{Float64}}, _add::LinearAlgebra.MulAddMul{true, true, Bool, Bool})
@ LinearAlgebra C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:713
[2] mul!
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:81 [inlined]
[3] mul!
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:275 [inlined]
[4] *
@ C:\Users\thega\AppData\Local\Programs\Julia-1.7.2\share\julia\stdlib\v1.7\LinearAlgebra\src\matmul.jl:51 [inlined]
[5] interpolate_pullback
@ C:\Users\thega\.julia\packages\Interpolations\Glp9h\src\chainrules\chainrules.jl:13 [inlined]
[6] ZBack
@ C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\chainrules.jl:204 [inlined]
[7] Pullback
@ c:\Users\thega\Desktop\Question\main.jl:21 [inlined]
[8] (::typeof(∂(loss)))(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface2.jl:0
[9] #212
@ C:\Users\thega\.julia\packages\Zygote\H6vD3\src\lib\lib.jl:203 [inlined]
[10] #1750#back
@ C:\Users\thega\.julia\packages\ZygoteRules\AIbCs\src\adjoint.jl:67 [inlined]
[11] Pullback
@ C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:102 [inlined]
[12] (::typeof(∂(λ)))(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface2.jl:0
[13] (::Zygote.var"#93#94"{Params, typeof(∂(λ)), Zygote.Context})(Δ::Float64)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface.jl:357
[14] gradient(f::Function, args::Params)
@ Zygote C:\Users\thega\.julia\packages\Zygote\H6vD3\src\compiler\interface.jl:76
[15] macro expansion
@ C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:101 [inlined]
[16] macro expansion
@ C:\Users\thega\.julia\packages\Juno\n6wyj\src\progress.jl:134 [inlined]
[17] train!(loss::Function, ps::Params, data::Vector{Tuple{Vector{Float64}, Matrix{Float64}}}, opt::Descent; cb::Flux.Optimise.var"#40#46")
@ Flux.Optimise C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:99
[18] train!(loss::Function, ps::Params, data::Vector{Tuple{Vector{Float64}, Matrix{Float64}}}, opt::Descent)
@ Flux.Optimise C:\Users\thega\.julia\packages\Flux[=12=]c9kI\src\optimise\train.jl:97
[19] top-level scope
@ c:\Users\thega\Desktop\Question\main.jl:28
关于维度不匹配的问题,但损失函数的评估工作正常。
loss(training_set[1][1], training_set[1][2])
我玩了一下,发现问题出在梯度计算上:
gradient(loss , training_set[1][1], training_set[1][2])
在训练集中可以找到问题,用你提供的例子,检查一下:
julia> training_set[1][1]
3-element Vector{Float64}:
0.5093876656425886
0.05272770864628318
0.7651982428671759
julia> training_set[1][2]
2×2 Matrix{Float64}:
0.0691616 0.55414
0.5153 0.654379
对于模型,输入是:作为 x
一个 2 元素向量,模型应该学习 return 一个 2x2 矩阵作为 y
。但是,您的模型不会这样做:
julia> model(training_set[1][1])
0.6585413f0 (tracked)
它只有 return 一个实例,由于模型的定义,在这种情况下 model = Chain(Dense(3,1),i-> clamp(i[1],0,1))
变成只有一个 Chain(Dense(3, 1), #7)
,这意味着有一个 3元素向量作为输入,并且 returns 1(并且只有 1 个)实例。
解决方案:
- 将您的
y
重新定义为每个x
的 1 个元素输出
- 重新定义你的模型(它会更复杂,因为你想要 return 一个 2x2 矩阵)。下面的模型就是一个例子:
julia> model = Chain(
Dense(3, 4),
x -> reshape(x, (2, 2))
)
但是,你应该弄清楚如何调整你的插值代码来工作
我无法解决问题。我的猜测是 Interpolations.jl 与 Zygote.jl 不兼容。我发现的一种可能的解决方法是编写自定义插值 class 和函数。如果有人感兴趣,我会提供一个工作示例:
using Flux, Zygote
using LinearAlgebra
using Interpolations
# create a custom linear splines class
struct CustomInterpolator
x::Vector
y::Vector
function CustomInterpolator(x,y)
@assert issorted(x)
return new(x,y)
end
end
function custom_interpolate(citp::CustomInterpolator, x::Number)
left_value, right_value = 0, 0
left_index, right_index = 1, 1
# check bound
if x > citp.x[end] || x < citp.x[1]
@error "Out of bounds"
throw(DomainError(x))
end
#find the right indices
for (i,v) in enumerate(citp.x)
if left_value > x
right_value = v
right_index = i
break
end
left_value = v
left_index = i
end
# do a linear inter interpolation between the two selected indices
interpolated_value = (1 - (x - left_value)/(right_value - left_value)) * citp.y[left_index] + (x - left_value)/(right_value - left_value) * citp.y[right_index]
return interpolated_value
end
##
# create custom interpolator
x = LinRange(0,1,2)
y = [zeros(2,2), ones(2,2)]
citp = CustomInterpolator(x,y)
# create training set
training_set = [(ones(3)*i, ones(2,2) - i*ones(2,2)) for i in 0:0.2:1]
#build the model
model = Chain(Dense(3,3), Dense(3,1), i-> clamp(i[1],0,1), i->custom_interpolate(citp,i))
opt = ADAM()
ps = Flux.params(model)
loss(x,y) = Flux.mse(model(x), y)
# training
n_epochs = 1000
for epoch in 1:n_epochs
Flux.train!(loss, ps, training_set, opt)
println(sum([loss(i[1],i[2]) for i in training_set]))
end