我试图自己实现 class_weight 因为我以通常的方式(model.fit 方法中的 class_weight)看到了一些行为,这让我看到了一个非常令人困惑的行为,经过一段时间的思考后我无法理解正在发生的事情。
整篇文章很长,所以我先简要说明一下,然后再为感兴趣的人提供详细信息
简要描述;简介
简而言之,我定义了两个自定义损失函数,它们仅在 tf.function 装饰器中有所不同。第一个没有装饰器,而第二个有。使用 keras model.fit,训练不会与具有装饰器的训练收敛。当我对自定义训练循环进行相同尝试时,行为会逆转。
通过在 Eager 模式下运行所有内容,我可以明显看出哪种行为是正确的,因此我可以看到带有装饰函数的自定义循环是正确的。它的结果也与通常的 keras 模型拟合方法与 class_weight 匹配。所有其他三种方法都会给出错误的结果,但我不明白为什么。
详细说明
def customloss1(y_true,y_pred,sample_weight=None):
weights=tf.constant([1.,1.,.1])[tf.newaxis,...]
y_true_one_hot=tf.one_hot(tf.cast(y_true,tf.uint8),3)
return tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_true_one_hot*weights,
y_pred,
from_logits=False))
@tf.function
def customloss2(y_true,y_pred,sample_weight=None):
weights=tf.constant([1.,1.,.1])[tf.newaxis,...]
y_true_one_hot=tf.one_hot(tf.cast(y_true,tf.uint8),3)
return tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_true_one_hot*weights,
y_pred,
from_logits=False))
Run Code Online (Sandbox Code Playgroud)
我有一个功能可以在相同的状态下创建初始模型,并且模型非常简单
def make_model():
tf.random.set_seed(42)
np.random.seed(42)
model=tf.keras.Sequential([
tf.keras.layers.Dense(3,'softmax',input_shape=[1024,])
])
return model
Run Code Online (Sandbox Code Playgroud)
我实例化一个 RMSProp 优化器(代码未显示)并将其称为优化器,然后训练这两个模型
model1=make_model()
model2=make_model()
model1.compile(loss=customloss1,optimizer=optimizer)
model2.compile(loss=customloss2,optimizer=optimizer)
history1 = model1.fit(x,y,epochs=100,batch_size=50, verbose=0)
history2 = model2.fit(x,y,epochs=100,batch_size=50, verbose=0)
Run Code Online (Sandbox Code Playgroud)
结果如下图所示
可以看出,使用带有装饰器的 customloss 的模型在几个时期后没有收敛。我不明白发生了什么。为了深入研究这个问题,我决定手动进行训练循环,所以我制作了两个训练步骤函数,它们使用模型的两个副本,不同之处仅在于它们使用的自定义损失函数
model1=make_model()
model2=make_model()
@tf.function
def train_step1(x,y):
with tf.GradientTape() as tape:
predictions = model1(x) …Run Code Online (Sandbox Code Playgroud) 我发现计算的梯度取决于 tf.function 装饰器的相互作用,如下所示。
首先,我为二元分类创建一些合成数据
tf.random.set_seed(42)
np.random.seed(42)
x=tf.random.normal((2,1))
y=tf.constant(np.random.choice([0,1],2))
Run Code Online (Sandbox Code Playgroud)
然后我定义两个仅在 tf.function 装饰器中不同的损失函数
weights=tf.constant([1.,.1])[tf.newaxis,...]
def customloss1(y_true,y_pred,sample_weight=None):
y_true_one_hot=tf.one_hot(tf.cast(y_true,tf.uint8),2)
y_true_scale=tf.multiply(weights,y_true_one_hot)
return tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_true_scale,y_pred))
@tf.function
def customloss2(y_true,y_pred,sample_weight=None):
y_true_one_hot=tf.one_hot(tf.cast(y_true,tf.uint8),2)
y_true_scale=tf.multiply(weights,y_true_one_hot)
return tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_true_scale,y_pred))
Run Code Online (Sandbox Code Playgroud)
然后我制作了一个非常简单的逻辑回归模型,删除了所有花哨的东西以保持简单
tf.random.set_seed(42)
np.random.seed(42)
model=tf.keras.Sequential([
tf.keras.layers.Dense(2,use_bias=False,activation='softmax',input_shape=[1,])
])
Run Code Online (Sandbox Code Playgroud)
最后定义两个函数来计算上述损失函数的梯度,一个被tf.function修饰,另一个不被tf.function修饰
def get_gradients1(x,y):
with tf.GradientTape() as tape1:
p1=model(x)
l1=customloss1(y,p1)
with tf.GradientTape() as tape2:
p2=model(x)
l2=customloss2(y,p2)
gradients1=tape1.gradient(l1,model.trainable_variables)
gradients2=tape2.gradient(l2,model.trainable_variables)
return gradients1, gradients2
@tf.function
def get_gradients2(x,y):
with tf.GradientTape() as tape1:
p1=model(x)
l1=customloss1(y,p1)
with tf.GradientTape() as tape2:
p2=model(x)
l2=customloss2(y,p2)
gradients1=tape1.gradient(l1,model.trainable_variables)
gradients2=tape2.gradient(l2,model.trainable_variables)
return gradients1, gradients2
Run Code Online (Sandbox Code Playgroud)
现在当我跑步时
get_gradients1(x,y)
Run Code Online (Sandbox Code Playgroud)
我明白了
([<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.11473544, …Run Code Online (Sandbox Code Playgroud) 在使用 model.fit_on_batch 方法和自定义训练循环时,我意识到在自定义训练循环代码中,损失和梯度不考虑任何 l1-l2 正则化器,因此 optimizer.apply_gradients 方法不考虑正则化器。您可以在下面找到显示这一点的代码,但这个想法非常简单。所以我的问题是,是否有一种方法可以在优化器细节不可知的方式中使用所有这些优化器来考虑正则化器。它在 Keras 中是如何实现的?在相关说明中,model.fit_on_batch 返回的值不是损失(如文档字符串中所述)而是其他值。我想知道这里是否有人知道它返回什么。
代码
要查看此效果,请先创建一些数据
x=tf.constant([[1]])
y=tf.constant([[1]])
Run Code Online (Sandbox Code Playgroud)
并创建一个函数来制作可重现的模型
def make_model(l1=.01,l2=.01):
tf.random.set_seed(42)
np.random.seed(42)
model=tf.keras.models.Sequential([
tf.keras.layers.Dense(2,'softmax',
use_bias=False,
kernel_regularizer=tf.keras.regularizers.l1_l2(l1=l1,l2=l2),
input_shape=(1,))
])
return model
Run Code Online (Sandbox Code Playgroud)
现在运行 Keras train_on_batch
model=make_model()
loss_object=tf.keras.losses.SparseCategoricalCrossentropy()
optimizer=tf.keras.optimizers.RMSprop()
model.compile(loss=loss_object,optimizer=optimizer)
model.train_on_batch(x,y)
Run Code Online (Sandbox Code Playgroud)
并将输出与自定义训练循环进行比较,如上述链接和此处所述
model=make_model()
loss_object=tf.keras.losses.SparseCategoricalCrossentropy()
optimizer=tf.keras.optimizers.RMSprop()
@tf.function
def train_step(x,y):
with tf.GradientTape() as tape:
predictions = model(x)
loss = loss_object(y, predictions)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
return loss
train_step(x,y).numpy()
Run Code Online (Sandbox Code Playgroud)
你会看到两个结果是不同的,除非 l1==0 和 l2==0。
我的问题是一个非常广泛的问题。既然可以使用对象,为什么还要使用协程呢?我可以轻松实现一个迭代器,该迭代器仅在 next() 下运行一次,之后您必须调用 o.send(x)。除了可以通过 OOP 实现状态持久性之外,协程还有其他作用吗?它们更轻吗?它只是语法糖吗?我实际上可以对生成器与迭代器提出同样的问题,但我想我已经读到生成器只是语法糖。
如果确实如此,为什么协程如此重要?我确信我错过了一些关于他们的事情,但我不知道是什么。
Fast Ai 使用了一种非常规的风格from fastai import *。
我个人不喜欢它,所以在 fastai 书的第 2 章中煞费苦心地确定了每个导入,但遇到了错误
AttributeError: 'Learner' object has no attribute 'fine_tune'
Run Code Online (Sandbox Code Playgroud)
然而,当我然后去做
from fastbook import *
Run Code Online (Sandbox Code Playgroud)
有用。这是一个非常奇怪的行为,因为如果上述导入完成,则对 cnn_learner 类或包含它的模块进行了一些操作,使其具有 Fine_tune 方法。
我想避免这种编码风格,那么我应该怎么做才能加载正确版本的 Learner?