Julia Flux:根据提供的正则化系数编写正则化器

Julia Flux: writing a regularizer depending on the provided regularization coefficients

我正在编写一个脚本,将 Python 的 Keras (v1.1.0) 模型转换为 Julia 的 Flux 模型,我正在努力实现正则化(我已经阅读 https://fluxml.ai/Flux.jl/stable/models/regularisation/) 作为了解 Julia 的一种方式。

所以,在 Keras 的 json 模型中,我有类似的东西: "W_regularizer": {"l2": 0.0010000000474974513, "name": "WeightRegularizer", "l1": 0.0} 每个 Dense 层。我想使用这些系数在 Flux 模型中创建正则化。问题是,在 Flux 中,它直接添加到损失中,而不是被定义为层本身的 属性。

为了避免在此处发布过多代码,我已将其添加到存储库中。这是一个使用 json 并创建 FluxChain 的小脚本:https://github.com/iegorval/Keras2Flux.jl/blob/master/Keras2Flux/src/Keras2Flux.jl

现在,我想用预定义的 l1/l2 系数为每个 Dense 层创建一个惩罚。我试着这样做:

using Pkg
pkg"activate /home/username/.julia/dev/Keras2Flux"

using Flux
using Keras2Flux
using LinearAlgebra

function get_penalty(model::Chain, regs::Array{Any, 1})
    index_model = 1
    index_regs = 1
    penalties = []
    for layer in model
        if layer isa Dense
            println(regs[index_regs](layer.W))   
            penalty(m) = regs[index_regs](m[index_model].W)
            push!(penalties, penalty)
            #println(regs[i])
            index_regs += 1
        end
        index_model += 1
    end
    total_penalty(m) = sum([p(m) for p in penalties])
    println(total_penalty)
    println(total_penalty(model))
    return total_penalty
end

model, regs = convert_keras2flux("examples/keras_1_1_0.json")
penalty = get_penalty(model, regs)

因此,我为每个 Dense 层创建了一个惩罚函数,然后将其加总为总惩罚。但是,它给了我这个错误: ERROR: LoadError: BoundsError: attempt to access 3-element Array{Any,1} at index [4]

我明白这意味着什么,但我真的不明白如何解决它。因此,似乎当我调用 total_penalty(model) 时,它使用 index_regs == 4(因此,index_regsindex_model 的值在 for 循环之后) .相反,我想使用他们在将给定惩罚推入惩罚列表时所拥有的实际索引。

另一方面,如果我不是将其作为函数列表而是作为值列表来做,它也不正确,因为我将损失定义为: loss(x, y) = binarycrossentropy(model(x), y) + total_penalty(model)。如果我只是将它用作值列表,那么我将有一个静态 total_penalty,而在模型训练期间每次都应该为每个 Dense 层重新计算它。

如果有 Julia 经验的人给我一些建议,我将不胜感激,因为我肯定无法理解它在 Julia 中的工作原理,特别是在 Flux 中的工作原理。我如何创建 total_penalty 在训练期间自动重新计算?

您的问题分为几个部分,由于您是 Flux(和 Julia?)的新手,我将分步回答。但我建议最后的解决方案是一种更干净的处理方式。

首先,p(m) 使用 index_regsindex_model 作为 for 循环后的值来计算惩罚的问题。这是因为 Julia 中的 the scoping rules。当您定义闭包 penalty(m) = regs[index_regs](m[index_model].W) 时,index_regs 将绑定到 get_penalty 中定义的变量。因此,随着 index_regs 的变化,p(m) 的输出也会发生变化。另一个问题是函数的命名为 penalty(m)。每次你 运行 这一行,你都在重新定义 penalty 以及你推到 penalties 上的所有对它的引用。相反,您应该更喜欢创建一个匿名函数。以下是我们合并这些更改的方式:

function get_penalty(model::Chain, regs::Array{Any, 1})
    index_model = 1
    index_regs = 1
    penalties = []
    for layer in model
        if layer isa Dense
            println(regs[index_regs](layer.W))   
            penalty = let i = index_regs, index_model = index_model
                m -> regs[i](m[index_model].W)
            end
            push!(penalties, penalty)
            index_regs += 1
        end
        index_model += 1
    end
    total_penalty(m) = sum([p(m) for p in penalties])
    return total_penalty
end

我在 let 块中使用了 iindex_model 来深入了解范围规则。我鼓励您将 let 块中的匿名函数替换为 global penalty(m) = ...(并删除 let 块之前对 penalty 的赋值)以查看使用匿名函数与命名函数的区别。


但是,如果我们回到您最初的问题,您想使用存储的系数计算模型的正则化惩罚。理想情况下,这些将与每个 Dense 层一起存储,就像在 Keras 中一样。您可以在 Flux 中重新创建相同的功能:

using Flux, Functor

struct RegularizedDense{T, LT<:Dense}
    layer::LT
    w_l1::T
    w_l2::T
end

@functor RegularizedDense

(l::RegularizedDense)(x) = l.layer(x)

penalty(l) = 0
penalty(l::RegularizedDense) =
  l.w_l1 * norm(l.layer.W, 1) + l.w_l2 * norm(l.layer.W, 2)
penalty(model::Chain) = sum(penalty(layer) for layer in model)

然后,在您的 Keras2Flux 源代码中,您可以将 get_regularization 重新定义为 return w_l1_regw_l2_reg 而不是函数。在 create_dense 你可以做:

function create_dense(config::Dict{String,Any}, prev_out_dim::Int64=-1)
    # ... code you have already written
    dense = Dense(in, out, activation; initW = init, initb = zeros)
    w_l1, w_l2 = get_regularization(config)
    return RegularizedDense(dense, w_l1, w_l2)
end

最后,您可以像这样计算损失函数:

loss(x, y, m) = binarycrossentropy(m(x), y) + penalty(m)
# ... later for training
train!((x, y) -> loss(x, y, m), training_data, params)

我们将loss定义为(x, y, m)的函数以避免performance issues.

所以,最终,这种方法更清晰,因为在构建模型之后,您不需要传递一组正则化函数并弄清楚如何使用相应的密集层正确索引每个函数。

如果您希望将正则化器和模型分开(即在您的模型链中有标准 Dense 层),那么您也可以这样做。如果您需要该解决方案,请告诉我,但我暂时不谈。