Keras 中填充输出的 F1 分数
F1 score for padded outputs in Keras
我在 Keras 中有 LSTM 序列标注器,用于处理高度不平衡的数据。因此,我想使用(多类)F1 分数作为模型的主要指标。我有 2 个问题:
1) 我在数据中使用了零填充(因此在我的嵌入中使用了 mask_zero=True
),并且所有损失都是针对屏蔽数据自动计算的。但是,我想屏蔽必须手动完成自定义指标计算?是否有有效的矢量化解决方案?
2) 是否可以将 sklearn's f1_score implementation 传递到模型的 compile
中(也许在以某种方式包装之后)?马上,它没有工作,因为显然一个占位符被传递给它而不是一个 numpy 数组(我使用 tensorflow 后端..)
[UPD] 鉴于我的实施,现在有这个问题:我不确定是否也可以屏蔽模型的输出。
因为如果我们不关心 'pad' 输入位置的模型输出(无论如何它们都不会造成损失),输出中也可能存在一些随机垃圾,这会影响 F1 指标。最好也只有零。
好的,这是我的尝试。非常欢迎评论!
主要的F1评分逻辑取自。对于 y_pred
和 y_true
作为形状 (batch_size, sequence_length, classes_number)
的 3D 张量,我们在其相应的切片上计算单个 class F1,然后对结果进行平均。 Class 0 保留用于填充,不计入分数。
from keras import backend as K
def precision(y_true, y_pred):
"""Precision metric.
Only computes a batch-wise average of precision.
Computes the precision, a metric for multi-label classification of
how many selected items are relevant.
"""
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
precision = true_positives / (predicted_positives + K.epsilon())
return precision
def recall(y_true, y_pred):
"""Recall metric.
Only computes a batch-wise average of recall.
Computes the recall, a metric for multi-label classification of
how many relevant items are selected.
"""
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
recall = true_positives / (possible_positives + K.epsilon())
return recall
def f1_binary(y_true, y_pred):
p = precision(y_true, y_pred)
r = recall(y_true, y_pred)
return 2 * ((p * r) / (p + r + K.epsilon()))
def f1(classes_number, y_true, y_pred):
result = 0.0
for class_id in xrange(1, classes_number + 1):
y_true_single_class = y_true[:,:,class_id]
y_pred_single_class = y_pred[:,:,class_id]
f1_single = f1_binary(y_true_single_class, y_pred_single_class)
result += f1_single / float(classes_number)
return result
下面是如何将它与 Keras 模型一起使用(classes_number 参数通过 wrapped_partial 绑定):
model.compile(optimizer=opt,
loss='categorical_crossentropy',
metrics=[wrapped_partial(f1, classes_number)])
切换到以下内容(基于 this code):
import numpy as np
from keras.callbacks import Callback
from sklearn.metrics import f1_score
class ZeroPaddedF1Score(Callback):
def on_train_begin(self, logs={}):
self.val_f1s = []
def on_epoch_end(self, epoch, logs={}):
y_true = np.argmax(self.validation_data[1], axis=-1)
y_pred = np.argmax(self.model.predict(self.validation_data[0]), axis=-1)
val_f1 = zero_padded_f1(y_true, y_pred)
self.val_f1s.append(val_f1)
print ' - val_f1: %f' % (val_f1)
def zero_padded_f1(y_true, y_pred):
y_pred_flat, y_true_flat = [], []
for y_pred_i, y_true_i in zip(y_pred.flatten(), y_true.flatten()):
if y_true_i != 0:
y_pred_flat.append(y_pred_i)
y_true_flat.append(y_true_i)
result = f1_score(y_true_flat, y_pred_flat, average='macro')
return result
它可能无法与 model.compile
一起使用(因为它与 numpy 数组一起运行,因此是一个已经编译的模型),但它可以作为回调完成工作。
我在 Keras 中有 LSTM 序列标注器,用于处理高度不平衡的数据。因此,我想使用(多类)F1 分数作为模型的主要指标。我有 2 个问题:
1) 我在数据中使用了零填充(因此在我的嵌入中使用了 mask_zero=True
),并且所有损失都是针对屏蔽数据自动计算的。但是,我想屏蔽必须手动完成自定义指标计算?是否有有效的矢量化解决方案?
2) 是否可以将 sklearn's f1_score implementation 传递到模型的 compile
中(也许在以某种方式包装之后)?马上,它没有工作,因为显然一个占位符被传递给它而不是一个 numpy 数组(我使用 tensorflow 后端..)
[UPD] 鉴于我的实施,现在有这个问题:我不确定是否也可以屏蔽模型的输出。 因为如果我们不关心 'pad' 输入位置的模型输出(无论如何它们都不会造成损失),输出中也可能存在一些随机垃圾,这会影响 F1 指标。最好也只有零。
好的,这是我的尝试。非常欢迎评论!
主要的F1评分逻辑取自y_pred
和 y_true
作为形状 (batch_size, sequence_length, classes_number)
的 3D 张量,我们在其相应的切片上计算单个 class F1,然后对结果进行平均。 Class 0 保留用于填充,不计入分数。
from keras import backend as K
def precision(y_true, y_pred):
"""Precision metric.
Only computes a batch-wise average of precision.
Computes the precision, a metric for multi-label classification of
how many selected items are relevant.
"""
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
precision = true_positives / (predicted_positives + K.epsilon())
return precision
def recall(y_true, y_pred):
"""Recall metric.
Only computes a batch-wise average of recall.
Computes the recall, a metric for multi-label classification of
how many relevant items are selected.
"""
true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
recall = true_positives / (possible_positives + K.epsilon())
return recall
def f1_binary(y_true, y_pred):
p = precision(y_true, y_pred)
r = recall(y_true, y_pred)
return 2 * ((p * r) / (p + r + K.epsilon()))
def f1(classes_number, y_true, y_pred):
result = 0.0
for class_id in xrange(1, classes_number + 1):
y_true_single_class = y_true[:,:,class_id]
y_pred_single_class = y_pred[:,:,class_id]
f1_single = f1_binary(y_true_single_class, y_pred_single_class)
result += f1_single / float(classes_number)
return result
下面是如何将它与 Keras 模型一起使用(classes_number 参数通过 wrapped_partial 绑定):
model.compile(optimizer=opt,
loss='categorical_crossentropy',
metrics=[wrapped_partial(f1, classes_number)])
切换到以下内容(基于 this code):
import numpy as np
from keras.callbacks import Callback
from sklearn.metrics import f1_score
class ZeroPaddedF1Score(Callback):
def on_train_begin(self, logs={}):
self.val_f1s = []
def on_epoch_end(self, epoch, logs={}):
y_true = np.argmax(self.validation_data[1], axis=-1)
y_pred = np.argmax(self.model.predict(self.validation_data[0]), axis=-1)
val_f1 = zero_padded_f1(y_true, y_pred)
self.val_f1s.append(val_f1)
print ' - val_f1: %f' % (val_f1)
def zero_padded_f1(y_true, y_pred):
y_pred_flat, y_true_flat = [], []
for y_pred_i, y_true_i in zip(y_pred.flatten(), y_true.flatten()):
if y_true_i != 0:
y_pred_flat.append(y_pred_i)
y_true_flat.append(y_true_i)
result = f1_score(y_true_flat, y_pred_flat, average='macro')
return result
它可能无法与 model.compile
一起使用(因为它与 numpy 数组一起运行,因此是一个已经编译的模型),但它可以作为回调完成工作。