在 Keras/Tensorflow 自定义损失函数中使用额外的 *可训练* 变量
Use additional *trainable* variables in Keras/Tensorflow custom loss function
我知道如何使用额外的输入在 Keras 中编写自定义损失函数,而不是标准的 y_true
、y_pred
对,请参见下文。我的问题是用 trainable 变量(其中一些)输入损失函数,这是损失梯度的一部分,因此应该更新。
我的解决方法是:
- 为网络输入一个大小为
N
XV
的虚拟输入,其中 N
是观测值的数量,V
是附加变量的数量
- 添加
Dense()
层 dummy_output
以便 Keras 跟踪我的 V
“权重”
- 在我的真实输出层的自定义损失函数中使用该层的
V
权重
- 为此
dummy_output
层使用虚拟损失函数(简单地 returns 0.0 and/or 具有权重 0.0)所以我的 V
“权重”仅通过我更新自定义损失函数
我的问题是: 是否有更自然的 Keras/TF-like 方式来做到这一点?因为感觉很做作更不用说容易出现错误。
我的解决方法示例:
(是的,我知道这是一个非常愚蠢的自定义损失函数,实际上事情要复杂得多)
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Dense
from tensorflow.keras.callbacks import EarlyStopping
import tensorflow.keras.backend as K
from tensorflow.keras.layers import Input
from tensorflow.keras import Model
n_col = 10
n_row = 1000
X = np.random.normal(size=(n_row, n_col))
beta = np.arange(10)
y = X @ beta
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# my custom loss function accepting my dummy layer with 2 variables
def custom_loss_builder(dummy_layer):
def custom_loss(y_true, y_pred):
var1 = dummy_layer.trainable_weights[0][0]
var2 = dummy_layer.trainable_weights[0][1]
return var1 * K.mean(K.square(y_true-y_pred)) + var2 ** 2 # so var2 should get to zero, var1 should get to minus infinity?
return custom_loss
# my dummy loss function
def dummy_loss(y_true, y_pred):
return 0.0
# my dummy input, N X V, where V is 2 for 2 vars
dummy_x_train = np.random.normal(size=(X_train.shape[0], 2))
# model
inputs = Input(shape=(X_train.shape[1],))
dummy_input = Input(shape=(dummy_x_train.shape[1],))
hidden1 = Dense(10)(inputs) # here only 1 hidden layer in the "real" network, assume whatever network is built here
output = Dense(1)(hidden1)
dummy_output = Dense(1, use_bias=False)(dummy_input)
model = Model(inputs=[inputs, dummy_input], outputs=[output, dummy_output])
# compilation, notice zero loss for the dummy_output layer
model.compile(
loss=[custom_loss_builder(model.layers[-1]), dummy_loss],
loss_weights=[1.0, 0.0], optimizer= 'adam')
# run, notice y_train repeating for dummy_output layer, it will not be used, could have created dummy_y_train as well
history = model.fit([X_train, dummy_x_train], [y_train, y_train],
batch_size=32, epochs=100, validation_split=0.1, verbose=0,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)])
似乎确实可以正常工作,无论 var1
和 var2
(dummy_output
层的初始化)的起始值是多少,它们都希望减去 inf
和 0
分别为:
(此图来自 运行 模型迭代并保存这两个权重,如下所示)
var1_list = []
var2_list = []
for i in range(100):
if i % 10 == 0:
print('step %d' % i)
model.fit([X_train, dummy_x_train], [y_train, y_train],
batch_size=32, epochs=1, validation_split=0.1, verbose=0)
var1, var2 = model.layers[-1].get_weights()[0]
var1_list.append(var1.item())
var2_list.append(var2.item())
plt.plot(var1_list, label='var1')
plt.plot(var2_list, 'r', label='var2')
plt.legend()
plt.show()
在这里回答我自己的问题,经过几天的努力,我让它在没有虚拟输入的情况下工作,我认为这要好得多,应该是“规范”的方式,直到 Keras/TF 简化流程。 Keras/TF 文档就是这样做的 here.
使用具有外部 trainable 变量的损失函数的关键是通过使用自定义 loss/output Layer self.add_loss(...)
在其 call()
实现中,像这样:
class MyLoss(Layer):
def __init__(self, var1, var2):
super(MyLoss, self).__init__()
self.var1 = K.variable(var1) # or tf.Variable(var1) etc.
self.var2 = K.variable(var2)
def get_vars(self):
return self.var1, self.var2
def custom_loss(self, y_true, y_pred):
return self.var1 * K.mean(K.square(y_true-y_pred)) + self.var2 ** 2
def call(self, y_true, y_pred):
self.add_loss(self.custom_loss(y_true, y_pred))
return y_pred
现在注意 MyLoss
层需要 两个 输入,实际的 y_true
和预测的 y
直到那个点:
inputs = Input(shape=(X_train.shape[1],))
y_input = Input(shape=(1,))
hidden1 = Dense(10)(inputs)
output = Dense(1)(hidden1)
my_loss = MyLoss(0.5, 0.5)(y_input, output) # here can also initialize those var1, var2
model = Model(inputs=[inputs, y_input], outputs=my_loss)
model.compile(optimizer= 'adam')
最后,如 TF 文档所述,在这种情况下,您不必在 fit()
函数中指定 loss
或 y
:
history = model.fit([X_train, y_train], None,
batch_size=32, epochs=100, validation_split=0.1, verbose=0,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)])
再次注意 y_train
作为输入之一进入 fit()
。
现在可以使用了:
var1_list = []
var2_list = []
for i in range(100):
if i % 10 == 0:
print('step %d' % i)
model.fit([X_train, y_train], None,
batch_size=32, epochs=1, validation_split=0.1, verbose=0)
var1, var2 = model.layers[-1].get_vars()
var1_list.append(var1.numpy())
var2_list.append(var2.numpy())
plt.plot(var1_list, label='var1')
plt.plot(var2_list, 'r', label='var2')
plt.legend()
plt.show()
(我还要提一下var1
这个具体的模式,var2
高度依赖于它们的初始值,如果var1
的初始值大于1它就不会事实减少直到负 inf
)
我知道如何使用额外的输入在 Keras 中编写自定义损失函数,而不是标准的 y_true
、y_pred
对,请参见下文。我的问题是用 trainable 变量(其中一些)输入损失函数,这是损失梯度的一部分,因此应该更新。
我的解决方法是:
- 为网络输入一个大小为
N
XV
的虚拟输入,其中N
是观测值的数量,V
是附加变量的数量 - 添加
Dense()
层dummy_output
以便 Keras 跟踪我的V
“权重” - 在我的真实输出层的自定义损失函数中使用该层的
V
权重 - 为此
dummy_output
层使用虚拟损失函数(简单地 returns 0.0 and/or 具有权重 0.0)所以我的V
“权重”仅通过我更新自定义损失函数
我的问题是: 是否有更自然的 Keras/TF-like 方式来做到这一点?因为感觉很做作更不用说容易出现错误。
我的解决方法示例:
(是的,我知道这是一个非常愚蠢的自定义损失函数,实际上事情要复杂得多)
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Dense
from tensorflow.keras.callbacks import EarlyStopping
import tensorflow.keras.backend as K
from tensorflow.keras.layers import Input
from tensorflow.keras import Model
n_col = 10
n_row = 1000
X = np.random.normal(size=(n_row, n_col))
beta = np.arange(10)
y = X @ beta
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# my custom loss function accepting my dummy layer with 2 variables
def custom_loss_builder(dummy_layer):
def custom_loss(y_true, y_pred):
var1 = dummy_layer.trainable_weights[0][0]
var2 = dummy_layer.trainable_weights[0][1]
return var1 * K.mean(K.square(y_true-y_pred)) + var2 ** 2 # so var2 should get to zero, var1 should get to minus infinity?
return custom_loss
# my dummy loss function
def dummy_loss(y_true, y_pred):
return 0.0
# my dummy input, N X V, where V is 2 for 2 vars
dummy_x_train = np.random.normal(size=(X_train.shape[0], 2))
# model
inputs = Input(shape=(X_train.shape[1],))
dummy_input = Input(shape=(dummy_x_train.shape[1],))
hidden1 = Dense(10)(inputs) # here only 1 hidden layer in the "real" network, assume whatever network is built here
output = Dense(1)(hidden1)
dummy_output = Dense(1, use_bias=False)(dummy_input)
model = Model(inputs=[inputs, dummy_input], outputs=[output, dummy_output])
# compilation, notice zero loss for the dummy_output layer
model.compile(
loss=[custom_loss_builder(model.layers[-1]), dummy_loss],
loss_weights=[1.0, 0.0], optimizer= 'adam')
# run, notice y_train repeating for dummy_output layer, it will not be used, could have created dummy_y_train as well
history = model.fit([X_train, dummy_x_train], [y_train, y_train],
batch_size=32, epochs=100, validation_split=0.1, verbose=0,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)])
似乎确实可以正常工作,无论 var1
和 var2
(dummy_output
层的初始化)的起始值是多少,它们都希望减去 inf
和 0
分别为:
(此图来自 运行 模型迭代并保存这两个权重,如下所示)
var1_list = []
var2_list = []
for i in range(100):
if i % 10 == 0:
print('step %d' % i)
model.fit([X_train, dummy_x_train], [y_train, y_train],
batch_size=32, epochs=1, validation_split=0.1, verbose=0)
var1, var2 = model.layers[-1].get_weights()[0]
var1_list.append(var1.item())
var2_list.append(var2.item())
plt.plot(var1_list, label='var1')
plt.plot(var2_list, 'r', label='var2')
plt.legend()
plt.show()
在这里回答我自己的问题,经过几天的努力,我让它在没有虚拟输入的情况下工作,我认为这要好得多,应该是“规范”的方式,直到 Keras/TF 简化流程。 Keras/TF 文档就是这样做的 here.
使用具有外部 trainable 变量的损失函数的关键是通过使用自定义 loss/output Layer self.add_loss(...)
在其 call()
实现中,像这样:
class MyLoss(Layer):
def __init__(self, var1, var2):
super(MyLoss, self).__init__()
self.var1 = K.variable(var1) # or tf.Variable(var1) etc.
self.var2 = K.variable(var2)
def get_vars(self):
return self.var1, self.var2
def custom_loss(self, y_true, y_pred):
return self.var1 * K.mean(K.square(y_true-y_pred)) + self.var2 ** 2
def call(self, y_true, y_pred):
self.add_loss(self.custom_loss(y_true, y_pred))
return y_pred
现在注意 MyLoss
层需要 两个 输入,实际的 y_true
和预测的 y
直到那个点:
inputs = Input(shape=(X_train.shape[1],))
y_input = Input(shape=(1,))
hidden1 = Dense(10)(inputs)
output = Dense(1)(hidden1)
my_loss = MyLoss(0.5, 0.5)(y_input, output) # here can also initialize those var1, var2
model = Model(inputs=[inputs, y_input], outputs=my_loss)
model.compile(optimizer= 'adam')
最后,如 TF 文档所述,在这种情况下,您不必在 fit()
函数中指定 loss
或 y
:
history = model.fit([X_train, y_train], None,
batch_size=32, epochs=100, validation_split=0.1, verbose=0,
callbacks=[EarlyStopping(monitor='val_loss', patience=5)])
再次注意 y_train
作为输入之一进入 fit()
。
现在可以使用了:
var1_list = []
var2_list = []
for i in range(100):
if i % 10 == 0:
print('step %d' % i)
model.fit([X_train, y_train], None,
batch_size=32, epochs=1, validation_split=0.1, verbose=0)
var1, var2 = model.layers[-1].get_vars()
var1_list.append(var1.numpy())
var2_list.append(var2.numpy())
plt.plot(var1_list, label='var1')
plt.plot(var2_list, 'r', label='var2')
plt.legend()
plt.show()
(我还要提一下var1
这个具体的模式,var2
高度依赖于它们的初始值,如果var1
的初始值大于1它就不会事实减少直到负 inf
)