Джулия Флюкс: написание регуляризатора в зависимости от предоставленных коэффициентов регуляризации
Я пишу скрипт, конвертирующий 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
слоев в цепочке моделей), то вы тоже можете это сделать. Дайте мне знать, если вам нужно это решение, но я пока оставлю его.