首页 > 解决方案 > 为什么 for 循环优化和通过 keras.Model 的个性化 .fit() 方法进行优化之间存在差异?

问题描述

我一直在尝试创建一个 keras.Model,它可以基于 GPD 的样本或其分位数拟合广义帕累托分布 (GPD)。拟合是通过最小化观测值和估计 GPD 的分位数之间的差异来完成的。

进口

import tensorflow as tf
from tensorflow import keras
import tensorflow_probability as tfp

GPD 层

我创建了一个基本的 GPD 层,方法是子类keras.layers.Layer化并将参数保留在层之外以获得更好的性能。我也在使用tensorflow_probability.distributions.GeneralizedPareto见文档)中实现的 GPD。GPD 的两个参数是gammasigma

class GPD_layer(keras.layers.Layer):
    def __init__(self):
        super(GPD_layer, self).__init__()
    def call(self, gamma,sigma,num):
        pareto = tfp.distributions.GeneralizedPareto(
            loc=0.0,
            scale=sigma,
            concentration=gamma
            )
        return pareto.quantile(tf.cast(tf.range(start=1/(num+1),limit=1.0,delta=1/(num+1),dtype=tf.float64),dtype=tf.float32))

GPD 模型

我创建了一个keras.Model子类来拥有一个合适的模型,并且能够像普通的 keras.Model 一样训练它。遵循Keras 团队提供的指南。我做了以下子类:

class GPD_model(keras.Model):
    def __init__(self):
        super(GPD_model, self).__init__()
        self.gpd_layer = GPD_layer()
        self.gamma = tf.Variable(
            initial_value=tf.random_normal_initializer(mean=0.2)(shape=(1,),dtype="float32"),
            trainable=True,
            name="gamma")
        self.sigma = tf.Variable(
            initial_value=tf.random_normal_initializer(mean=1.0)(shape=(1,),dtype="float32"),
            trainable=True,
            name="sigma")
        
    def call(self, num, training=None, *args, **kwargs):
        if training:
            return self.gpd_layer(gamma=self.gamma,sigma=self.sigma, num=num)
        return self.gpd_layer(gamma=self.gamma, sigma=self.sigma, num=num)
    
    def train_step(self,data):
        num = data.shape[0]
        with tf.GradientTape() as tape:
            X_gpd = self(num = num,training=True)
            loss = tf.norm(data-X_gpd,ord=1)
        gradients = tape.gradient(loss, [self.sigma, self.gamma])

        self.optimizer.apply_gradients(zip(gradients, [self.sigma, self.gamma]))
        return {"loss":loss}

训练模型

N=1000
gamma_th = 0.4
sigma_th = 2.0

pareto = tfp.distributions.GeneralizedPareto(loc=0,scale=sigma_th,concentration=gamma_th)
X_train = pareto.quantile(tf.cast(tf.range(start=1/(N+1),limit=1.0,delta=1/(N+1),dtype=tf.float64),dtype=tf.float32))

model = GPD_model()
model.compile(
    optimizer=keras.optimizers.SGD(learning_rate=1e-2),
    loss="mae",
    run_eagerly=True
    )
history = model.fit(X_train, epochs=200, batch_size=X_train.shape[0])

这种训练导致 gamma 值变为负值,并且似乎缓慢发散,并对损失产生以下影响: 个性化合身的演变

创建其他方法

这是类的一种方法,GPD_model类似于外部训练循环。

def manual_fit(self,data, epochs):
        history = {"loss":[],"sigma":[],"gamma":[]}
        num = data.shape[0]
        for step in range(epochs):
            with tf.GradientTape() as tape:
                Y = self(num = num)
                loss = tf.norm(data-Y,ord=1)

            gradients = tape.gradient(loss, [self.sigma,self.gamma])
            self.optimizer.apply_gradients(zip(gradients, [self.sigma, self.gamma]))

            history["loss"].append(loss)
            history["sigma"].append(tf.constant(self.sigma))
            history["gamma"].append(tf.constant(gamma))
            print("\n",str(step+1)+"/"+str(epochs))
            print([f"{k}: {history[k][-1]}" for k in history.keys()])
        return history

这种方法导致损失、伽马和西格玛的预期行为。

外环的演变

请注意,这两种优化方法是在相同的时期数、相同的学习率和相同的优化器下执行的。最后的振荡是由于学习率不是最好的。

问题

标签: pythontensorflowkeraskeras-layermodel-fitting

解决方案


推荐阅读