keras中是否有基于精度或召回率而不是损失的优化器?
Is there an optimizer in keras based on precision or recall instead of loss?
我正在开发一个只有两个 类、0 和 1 的分割神经网络(0 是背景,1 是我想在图像上找到的对象)。在每张图片上,大约有 80% 的 1 和 20% 的 0。如您所见,数据集不平衡,导致结果错误。我的准确率是 85%,我的损失很低,但这只是因为我的模型擅长寻找背景!
我想将优化器基于另一个指标,例如在这种情况下更有用的精度或召回率。
有人知道如何实现吗?
没有。要执行 'gradient descent',您需要计算梯度。为此,功能需要以某种方式平滑。 Precision/recall 或者 accuracy 不是一个光滑的函数,它只有梯度为无穷大的锐边和梯度为零的平坦区域。因此,您不能使用任何一种数值方法来找到这样一个函数的最小值——您将不得不使用某种组合优化,这将是 NP 难的。
您没有使用精度或召回率来进行优化。您只需将它们作为有效分数进行跟踪以获得最佳权重。不要混合损失、优化器、指标和其他。它们的用途不同。
THRESHOLD = 0.5
def precision(y_true, y_pred, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fp = K.sum(K.round(K.clip(y_pred_bin - y_true, 0, 1)))
precision = tp / (tp + fp)
return precision
def recall(y_true, y_pred, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fn = K.sum(K.round(K.clip(y_true - y_pred_bin, 0, 1)))
recall = tp / (tp + fn)
return recall
def fbeta(y_true, y_pred, beta = 2, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fp = K.sum(K.round(K.clip(y_pred_bin - y_true, 0, 1)))
fn = K.sum(K.round(K.clip(y_true - y_pred, 0, 1)))
precision = tp / (tp + fp)
recall = tp / (tp + fn)
beta_squared = beta ** 2
return (beta_squared + 1) * (precision * recall) / (beta_squared * precision + recall)
def model_fit(X,y,X_test,y_test):
class_weight={
1: 1/(np.sum(y) / len(y)),
0:1}
np.random.seed(47)
model = Sequential()
model.add(Dense(1000, input_shape=(X.shape[1],)))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(500))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(250))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adamax',metrics=[fbeta,precision,recall])
model.fit(X, y,validation_data=(X_test,y_test), epochs=200, batch_size=50, verbose=2,class_weight = class_weight)
return model
正如其他人所说,precision/recall 不能直接用作损失函数。然而,已经发现更好的代理损失函数可以帮助整个 precision/recall 相关函数家族(例如 ROC AUC、固定召回精度等)
研究论文 Scalable Learning of Non-Decomposable Objectives covers this with a method to sidestep the combinatorial optimization by the use of certain calculated bounds, and some Tensorflow code by the authors is available at the tensorflow/models repository. Additionally, there is a followup question on Whosebug 的答案将其改编成可用的 Keras 损失函数。
特别感谢 Francois Chollet 和 Keras issue thread here 的其他参与者,他们提出了该研究论文。您可能还会发现该线程提供了对手头问题的其他有用见解。
我对二进制分类的不平衡数据集有同样的问题,我也想提高召回灵敏度。我发现 tf.keras 中有一个 built-in 函数用于调用,可以在编译语句中使用,如下所示:
from tensorflow.keras.metrics import Recall, Accuracy
model.compile(loss='binary_crossentropy' , optimizer=opt, metrics=[Accuracy(),Recall()])
对于不平衡的数据集有同样的问题,我建议您使用 F1 分数 作为优化器的指标。
Andrew Ng 教导说,为模型设置一个指标是训练模型最简单(最好?)的方法。如果你有 2 个指标,比如精确率和召回率——不清楚哪个更重要。试图对一个指标设置限制显然会影响另一个指标...
F1 分数是召回率和精确率的奇迹 - 这是它们的调和平均值。
不幸的是,我正在使用的 Keras 没有实现 F1 分数作为指标,就像准确率或许多其他 Keras 指标一样 https://keras.io/api/metrics/。
我找到了 F1 分数作为 Keras 指标的实现,在每个时期使用:
https://medium.com/@aakashgoel12/how-to-add-user-defined-function-get-f1-score-in-keras-metrics-3013f979ce0d
我已经实现了上述文章中的简单函数,模型现在以 F1 分数作为其 Keras 优化器指标进行训练。测试结果:准确率下降了一点,F1分数上升了很多。
处理像您这样的不平衡数据集的推荐方法是使用 class_weights 或 sample_weights。详见型号fitAPI。
引用:
class_weight: Optional dictionary mapping class indices (integers) to a weight (float) value, used for weighting the loss function (during training only). This can be useful to tell the model to "pay more attention" to samples from an under-represented class.
使用与 class 频率成反比的权重,损失将避免仅预测背景 class。
我知道这不是您提出问题的方式,但恕我直言,这是解决您所面临问题的最实用方法。
我认为回调和提前停止机制提供了一种技术,可以引导您尽可能接近您想要实现的目标。请阅读 Jason Brownlee 撰写的以下关于提前停止的文章(读到底!):
我正在开发一个只有两个 类、0 和 1 的分割神经网络(0 是背景,1 是我想在图像上找到的对象)。在每张图片上,大约有 80% 的 1 和 20% 的 0。如您所见,数据集不平衡,导致结果错误。我的准确率是 85%,我的损失很低,但这只是因为我的模型擅长寻找背景!
我想将优化器基于另一个指标,例如在这种情况下更有用的精度或召回率。
有人知道如何实现吗?
没有。要执行 'gradient descent',您需要计算梯度。为此,功能需要以某种方式平滑。 Precision/recall 或者 accuracy 不是一个光滑的函数,它只有梯度为无穷大的锐边和梯度为零的平坦区域。因此,您不能使用任何一种数值方法来找到这样一个函数的最小值——您将不得不使用某种组合优化,这将是 NP 难的。
您没有使用精度或召回率来进行优化。您只需将它们作为有效分数进行跟踪以获得最佳权重。不要混合损失、优化器、指标和其他。它们的用途不同。
THRESHOLD = 0.5
def precision(y_true, y_pred, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fp = K.sum(K.round(K.clip(y_pred_bin - y_true, 0, 1)))
precision = tp / (tp + fp)
return precision
def recall(y_true, y_pred, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fn = K.sum(K.round(K.clip(y_true - y_pred_bin, 0, 1)))
recall = tp / (tp + fn)
return recall
def fbeta(y_true, y_pred, beta = 2, threshold_shift=0.5-THRESHOLD):
# just in case
y_pred = K.clip(y_pred, 0, 1)
# shifting the prediction threshold from .5 if needed
y_pred_bin = K.round(y_pred + threshold_shift)
tp = K.sum(K.round(y_true * y_pred_bin)) + K.epsilon()
fp = K.sum(K.round(K.clip(y_pred_bin - y_true, 0, 1)))
fn = K.sum(K.round(K.clip(y_true - y_pred, 0, 1)))
precision = tp / (tp + fp)
recall = tp / (tp + fn)
beta_squared = beta ** 2
return (beta_squared + 1) * (precision * recall) / (beta_squared * precision + recall)
def model_fit(X,y,X_test,y_test):
class_weight={
1: 1/(np.sum(y) / len(y)),
0:1}
np.random.seed(47)
model = Sequential()
model.add(Dense(1000, input_shape=(X.shape[1],)))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(500))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(250))
model.add(Activation('relu'))
model.add(Dropout(0.35))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adamax',metrics=[fbeta,precision,recall])
model.fit(X, y,validation_data=(X_test,y_test), epochs=200, batch_size=50, verbose=2,class_weight = class_weight)
return model
正如其他人所说,precision/recall 不能直接用作损失函数。然而,已经发现更好的代理损失函数可以帮助整个 precision/recall 相关函数家族(例如 ROC AUC、固定召回精度等)
研究论文 Scalable Learning of Non-Decomposable Objectives covers this with a method to sidestep the combinatorial optimization by the use of certain calculated bounds, and some Tensorflow code by the authors is available at the tensorflow/models repository. Additionally, there is a followup question on Whosebug 的答案将其改编成可用的 Keras 损失函数。
特别感谢 Francois Chollet 和 Keras issue thread here 的其他参与者,他们提出了该研究论文。您可能还会发现该线程提供了对手头问题的其他有用见解。
我对二进制分类的不平衡数据集有同样的问题,我也想提高召回灵敏度。我发现 tf.keras 中有一个 built-in 函数用于调用,可以在编译语句中使用,如下所示:
from tensorflow.keras.metrics import Recall, Accuracy
model.compile(loss='binary_crossentropy' , optimizer=opt, metrics=[Accuracy(),Recall()])
对于不平衡的数据集有同样的问题,我建议您使用 F1 分数 作为优化器的指标。 Andrew Ng 教导说,为模型设置一个指标是训练模型最简单(最好?)的方法。如果你有 2 个指标,比如精确率和召回率——不清楚哪个更重要。试图对一个指标设置限制显然会影响另一个指标...
F1 分数是召回率和精确率的奇迹 - 这是它们的调和平均值。
不幸的是,我正在使用的 Keras 没有实现 F1 分数作为指标,就像准确率或许多其他 Keras 指标一样 https://keras.io/api/metrics/。
我找到了 F1 分数作为 Keras 指标的实现,在每个时期使用: https://medium.com/@aakashgoel12/how-to-add-user-defined-function-get-f1-score-in-keras-metrics-3013f979ce0d
我已经实现了上述文章中的简单函数,模型现在以 F1 分数作为其 Keras 优化器指标进行训练。测试结果:准确率下降了一点,F1分数上升了很多。
处理像您这样的不平衡数据集的推荐方法是使用 class_weights 或 sample_weights。详见型号fitAPI。
引用:
class_weight: Optional dictionary mapping class indices (integers) to a weight (float) value, used for weighting the loss function (during training only). This can be useful to tell the model to "pay more attention" to samples from an under-represented class.
使用与 class 频率成反比的权重,损失将避免仅预测背景 class。
我知道这不是您提出问题的方式,但恕我直言,这是解决您所面临问题的最实用方法。
我认为回调和提前停止机制提供了一种技术,可以引导您尽可能接近您想要实现的目标。请阅读 Jason Brownlee 撰写的以下关于提前停止的文章(读到底!):