损失函数的目的是计算模型在训练期间应寻求最小化的量。
注意,所有损失函数既可以通过类句柄访问,也可以通过函数句柄访问。类句柄使您能够在构造函数中传递配置参数(例如,loss_fn = CategoricalCrossentropy(from_logits=True)
),并且在独立使用时默认执行降维(详见下文)。
compile()
和 fit()
中使用损失函数损失函数是编译 Keras 模型所需的两个参数之一:
import keras
from keras import layers
model = keras.Sequential()
model.add(layers.Dense(64, kernel_initializer='uniform', input_shape=(10,)))
model.add(layers.Activation('softmax'))
loss_fn = keras.losses.SparseCategoricalCrossentropy()
model.compile(loss=loss_fn, optimizer='adam')
所有内置的损失函数也可以通过它们的字符串标识符传递:
# 通过名称传递优化器:将使用默认参数
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')
损失函数通常通过实例化损失类(例如 keras.losses.SparseCategoricalCrossentropy
)创建。所有损失函数也提供为函数句柄(例如 keras.losses.sparse_categorical_crossentropy
)。
使用类使您能够在实例化时传递配置参数,例如:
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)
损失是一个可调用对象,具有参数 loss_fn(y_true, y_pred, sample_weight=None)
:
(batch_size, d0, ... dN)
。对于稀疏损失函数,如稀疏类别交叉熵,形状应为 (batch_size, d0, ... dN-1)
(batch_size, d0, .. dN)
。sample_weight
作为每个样本损失的缩减权重系数。如果提供标量,则损失仅按给定值缩放。如果 sample_weight
是大小为 [batch_size]
的张量,则每个样本的总损失按 sample_weight
向量中相应元素重新缩放。如果 sample_weight
的形状为 (batch_size, d0, ... dN-1)
(或可以广播到此形状),则 y_pred
的每个损失元素按 sample_weight
的相应值缩放。(注意 dN-1
:所有损失函数减少 1 个维度,通常是 axis=-1
。)默认情况下,损失函数对每个输入样本返回一个标量损失值,例如
>>> keras.losses.mean_squared_error(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 1.], dtype=float32)>
然而,损失类实例具有一个 reduction
构造函数参数,默认值为 "sum_over_batch_size"
(即平均)。允许的值为 "sum_over_batch_size"、"sum" 和 "none":
>>> loss_fn = keras.losses.MeanSquaredError(reduction='sum_over_batch_size')
>>> loss_fn(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>
>>> loss_fn = keras.losses.MeanSquaredError(reduction='sum')
>>> loss_fn(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(), dtype=float32, numpy=2.0>
>>> loss_fn = keras.losses.MeanSquaredError(reduction='none')
>>> loss_fn(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 1.], dtype=float32)>
注意,这在于像 keras.losses.mean_squared_error
这样的损失函数与像 keras.losses.MeanSquaredError
这样的默认损失类实例之间的一个重要区别:函数版本不执行降维,但默认情况下类实例会。
>>> loss_fn = keras.losses.mean_squared_error
>>> loss_fn(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([1., 1.], dtype=float32)>
>>> loss_fn = keras.losses.MeanSquaredError()
>>> loss_fn(tf.ones((2, 2,)), tf.zeros((2, 2)))
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>
在使用 fit()
时,这种差异是无关紧要的,因为降维由框架处理。
以下是如何在简单训练循环中使用损失类实例的示例:
loss_fn = keras.losses.CategoricalCrossentropy(from_logits=True)
optimizer = keras.optimizers.Adam()
# 遍历数据集的批次。
for x, y in dataset:
with tf.GradientTape() as tape:
logits = model(x)
# 计算该批次的损失值。
loss_value = loss_fn(y, logits)
# 更新模型的权重以最小化损失值。
gradients = tape.gradient(loss_value, model.trainable_weights)
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
任何具有签名 loss_fn(y_true, y_pred)
的可调用对象,只要返回一个损失数组(每个输入批次中的一个样本),都可以作为损失传递给 compile()
。注意,样本加权对于任何这样的损失是自动支持的。
这是一个简单的例子:
from keras import ops
def my_loss_fn(y_true, y_pred):
squared_difference = ops.square(y_true - y_pred)
return ops.mean(squared_difference, axis=-1) # 注意 `axis=-1`
model.compile(optimizer='adam', loss=my_loss_fn)
add_loss()
API应用于模型输出的损失函数并不是创建损失的唯一方式。
在撰写自定义层或子类模型的 call
方法时,您可能希望计算在训练期间要最小化的标量量(例如,正则化损失)。您可以使用 add_loss()
层方法来跟踪这样的损失项。
这是一个基于输入的 L2 范数添加稀疏性正则化损失的层的示例:
from keras import ops
class MyActivityRegularizer(keras.layers.Layer):
"""创建活动稀疏性正则化损失的层。"""
def __init__(self, rate=1e-2):
super().__init__()
self.rate = rate
def call(self, inputs):
# 我们使用 `add_loss` 来创建一个依赖于输入的正则化损失。
self.add_loss(self.rate * ops.sum(ops.square(inputs)))
return inputs
通过 add_loss
添加的损失值可以在任何 Layer
或 Model
的 .losses
列表属性中检索到(它们是从每个底层层递归检索的):
from keras import layers
from keras import ops
class SparseMLP(layers.Layer):
"""带有稀疏性正则化损失的线性层堆栈。"""
def __init__(self, output_dim):
super().__init__()
self.dense_1 = layers.Dense(32, activation=ops.relu)
self.regularization = MyActivityRegularizer(1e-2)
self.dense_2 = layers.Dense(output_dim)
def call(self, inputs):
x = self.dense_1(inputs)
x = self.regularization(x)
return self.dense_2(x)
mlp = SparseMLP(1)
y = mlp(ops.ones((10, 10)))
print(mlp.losses) # 包含一个 float32 标量的列表
这些损失在每次前向传递开始时会被顶层层清除——它们不会累积。因此 layer.losses
始终只包含在最后一次前向传递中创建的损失。您通常会在编写训练循环时通过在计算梯度之前对这些损失求和来使用它们。
# 损失对应于 *最后* 的前向传递。
mlp = SparseMLP(1)
mlp(ops.ones((10, 10)))
assert len(mlp.losses) == 1
mlp(ops.ones((10, 10)))
assert len(mlp.losses) == 1 # 没有累积。
在使用 model.fit()
时,这些损失项会自动处理。
在编写自定义训练循环时,您应该手动从 model.losses
中检索这些项,如下所示:
loss_fn = keras.losses.CategoricalCrossentropy(from_logits=True)
optimizer = keras.optimizers.Adam()
# 遍历数据集的批次。
for x, y in dataset:
with tf.GradientTape() as tape:
# 前向传递。
logits = model(x)
# 此批次的损失值。
loss_value = loss_fn(y, logits)
# 将额外的损失项添加到损失值中。
loss_value += sum(model.losses)
# 更新模型的权重以最小化损失值。
gradients = tape.gradient(loss_value, model.trainable_weights)
optimizer.apply_gradients(zip(gradients, model.trainable_weights))
有关更多详细信息,请参见 the add_loss()
documentation。