Джулия Флюкс: написание регуляризатора в зависимости от предоставленных коэффициентов регуляризации

Я пишу скрипт, конвертирующий Python Keras (v1.1.0) модель к Юлии Fluxмодель, и я пытаюсь реализовать регуляризацию (я прочитал https://fluxml.ai/Flux.jl/stable/models/regularisation/) как способ познакомиться с Джулией.

Итак, в KerasМодель json у меня есть что-то вроде: "W_regularizer": {"l2": 0.0010000000474974513, "name": "WeightRegularizer", "l1": 0.0} для каждого Denseслой. Я хочу использовать эти коэффициенты для создания регуляризации вFluxмодель. Проблема в том, что вFlux он добавляется непосредственно к потерям, а не определяется как свойство самого слоя.

Чтобы не размещать здесь слишком много кода, я добавил его в репо. Вот небольшой скрипт, который использует json и createaFluxс Chain: https://github.com/iegorval/Keras2Flux.jl/blob/master/Keras2Flux/src/Keras2Flux.jl

Теперь я хочу назначить штраф для каждого Dense слой с предопределенным l1/l2коэффициент. Я пробовал сделать это так:

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_regs а также index_modelкак они ПОСЛЕ цикла). Вместо этого я хочу использовать их фактические индексы, которые были у меня при добавлении данного штрафа в список штрафов.

С другой стороны, если бы я делал это не как список функций, а как список значений, это также было бы неверно, потому что я определю потери как:loss(x, y) = binarycrossentropy(model(x), y) + total_penalty(model). Если бы я использовал его как список значений, то у меня был бы статическийtotal_penalty, при этом его следует пересчитывать для каждого Dense слой каждый раз во время обучения модели.

Я был бы благодарен, если бы кто-нибудь с опытом Джулии дал мне совет, потому что я определенно не понимаю, как это работает в Юлии и, в частности, в Flux. Как бы я создалtotal_penalty что будет автоматически пересчитываться во время тренировки?

1 ответ

Решение

В вашем вопросе есть пара частей, и, поскольку вы новичок в Flux (и Джулии?), Я отвечу поэтапно. Но я предлагаю решение в конце как более чистый способ справиться с этим.

Во-первых, это проблема p(m) расчет штрафа с использованием index_regs а также index_modelкак значения после цикла for. Это из-за правил области видимости в Julia. Когда вы определяете закрытие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

я использовал i а также index_modelв блоке let, чтобы понять правила определения объема. Я рекомендую вам заменить анонимную функцию в блоке let наglobal penalty(m) = ... (и удалите назначение penalty перед блоком let), чтобы увидеть разницу между анонимными и именованными функциями.


Но, если мы вернемся к вашей исходной проблеме, вы хотите рассчитать штраф за регуляризацию для вашей модели, используя сохраненные коэффициенты. В идеале они должны храниться с каждымDenseслой как в Керасе. Вы можете воссоздать ту же функциональность в 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 возвращаться w_l1_reg а также w_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)чтобы избежать проблем с производительностью.

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

Если вы предпочитаете хранить регуляризатор и модель отдельно (т.е. иметь стандартные Denseслоев в цепочке моделей), то вы тоже можете это сделать. Дайте мне знать, если вам нужно это решение, но я пока оставлю его.

Другие вопросы по тегам