更改关于 epoch 的自定义损失参数和 NN 参数

Change custom loss parameter and NN parameter with respect to epoch

我有一个按以下方式定义的 Keras 模型(试图只保留必要的部分):

temperature = 5.0

def knowledge_distillation_loss(y_true, y_pred, lambda_const):    
    y_true, logits = y_true[:, :10], y_true[:, 10:]
    y_soft = K.softmax(logits/temperature)
    y_pred, y_pred_soft = y_pred[:, :10], y_pred[:, 10:]    

    return lambda_const*logloss(y_true, y_pred) + logloss(y_soft, y_pred_soft)

def get_model(num_labels):
    #Some layers for model
    model.add(Dense(num_labels))
    logits = model.layers[-1].output
    probabilities = Activation('softmax')(logits)
    # softed probabilities
    logits_T = Lambda(lambda x: x/temperature)(logits)
    probabilities_T = Activation('softmax')(logits_T)

    output = concatenate([probabilities, probabilities_T])
    model = Model(model.input, output)

    lambda_const = 0.07

    model.compile(
    optimizer=optimizers.SGD(lr=1e-1, momentum=0.9, nesterov=True), 
    loss=lambda y_true, y_pred: knowledge_distillation_loss(y_true, y_pred, lambda_const), 
    metrics=[accuracy])
    return model

我正在关注 this reference

这是在带有 tf 后端的 Keras 上使用 fit generator() 实现的。显然,加载模型时会遇到麻烦,因为 temperature 是硬编码的。 还, 我希望根据损失函数和模型中的纪元号更新 temperature 参数。

如何定义这样的控制信号?

我已将其转化为一个完整示例,说明一种方法。

你可以为损失函数做一个class。

class TemperatureLossFunction:
    def __init__(self, temperature):
        self.temperature = temperature
    def loss_fun(self, y_truth, y_pred):
        return self.temperature*keras.losses.mse(y_truth, y_pred)
    def setTemperature(self, t, session=None):
        if session:
            session.run(self.temperature.assign( t )
        elif tensorflow.get_default_session():
            tensorflow.get_default_session().run(self.temperature.assign( t ))

class TemperatureLossCallback(keras.callbacks.Callback):
    def __init__(self, temp_lf):
        self.temp_lf = temp_lf
    def on_epoch_end(self, epoch, params):
        self.temp_lf.setTemperature(epoch)

我创建了两种方法来处理这个问题,第一种方法创建并保存模型。

def init(session):
    global temperature #global for serialization issues
    temperature = tensorflow.Variable(5.0)
    tlo = TemperatureLossFunction(temperature)

    inp = keras.layers.Input((4,4))

    l1 = keras.layers.Lambda( lambda x: temperature*x )
    op = l1(inp)

    m = keras.models.Model(inputs=[inp], outputs=[op])
    m.compile( optimizer = keras.optimizers.SGD(0.01), loss=tlo.loss_fun)

    #make sure the session is the one your using!
    session.run(temperature.initializer)

第一个测试 运行 确保我们正在更改值。

    m.evaluate( numpy.ones((1, 4, 4)), numpy.zeros((1, 4, 4)) )    
    session.run(temperature.assign(1))
    m.evaluate( numpy.ones((1, 4, 4)), numpy.zeros((1, 4, 4)) )

我的第二个测试 运行 确保我们可以通过回调更改值。

    cb = TemperatureLossCallback(tlo)
    def gen():
        for i in range(10):
            yield numpy.ones((1, 4, 4)), numpy.zeros((1, 4, 4))
    m.fit_generator(
            gen(), steps_per_epoch=1, epochs=10, callbacks=[cb]
        )
    m.save("junk.h5")

最后,演示重新加载文件。

def restart(session):
    global temperature
    temperature = tensorflow.Variable(5.0)
    tlo = TemperatureLossFunction(temperature)
    loss_fun = tlo.loss_fun
    m = keras.models.load_model(
            "junk.h5", 
            custom_objects = {"loss_fun":tlo.loss_fun}
    )
    session.run(temperature.initializer)
    m.evaluate( numpy.ones((1, 4, 4)), numpy.zeros((1, 4, 4)) )
    session.run(temperature.assign(1))
    m.evaluate( numpy.ones( (1, 4, 4) ), numpy.zeros( ( 1, 4, 4) ) )

为了完整起见,这只是我用来启动程序的代码

import sys    
if __name__=="__main__":
    sess = tensorflow.Session()
    with sess.as_default():
        if "restart" in sys.argv:
            restart(sess)
        else:
            init(sess)

此方法的一个缺点是,如果您 运行 这样做,您会看到温度变量未从模型文件中加载。它采用代码中指定的值。

从好的方面来说,损失函数和图层都引用相同的 Variable

我发现保存变量值的一种方法是创建一个新层并将该变量用作新层的权重。

class VLayer(keras.layers.Layer):
    def __init__(self, *args, **kwargs):
        super().__init__(**kwargs)
    def build(self, input_shape):
        self.v1 = self.add_weight(
                       dtype="float32", 
                       shape = (), 
                       trainable=False, 
                       initializer="zeros"
                   )
    def call(self, x):
        return x*self.v1
    def setValue(self, val):
        self.set_weights( numpy.array([val]) )

现在加载模型时,重量也会加载。不幸的是,我找不到一种方法来 link 加载变量的权重。所以会有两个变量,一个是损失函数,一个是层。不过,它们都可以从回调中设置。所以我觉得这个方法走的是一条更稳健的道路。