让我们将目标分为两个子目标,我们首先了解目的、概念、数学细节,然后总结在 tensorflow 中Weighted Kappa
尝试使用时需要注意的事项WeightedKappaLoss
PS:如果你只关心使用,可以跳过理解部分
加权Kappa详解
由于Weighted Kappa可以看作Cohen 的 kappa + weights,所以我们需要先了解Cohen的 kappa
Cohen 的 kappa 示例
假设我们有两个分类器(A 和 B)试图将 50 个语句分为两类(真和假),它们在列联表中对这些语句进行分类的方式是:
B
True False
A True 20 5 25 statements A think is true
False 10 15 25 statements A think is false
30 statements B think is true
20 statements B think is false
现在假设我们想知道:A 和 B 的预测有多可靠?
我们可以做的就是简单地取 A 和 B 相互一致的分类陈述的百分比,即观察到的一致的比例表示为Po
,所以:
Po = (20 + 15) / 50 = 0.7
但这是有问题的,因为 A 和 B 有随机机会彼此一致的概率,即预期机会一致的比例表示为Pe
,如果我们使用观察到的百分比作为预期概率,则:
Pe = (probability statement A think is true) * (probability statement B think is true) +
(probability statement A think is false) * (probability statement B think is false)
= (25 / 50) * (30 / 50) +
(25 / 50) * (20 / 50)
= 0.5
Cohen 的 kappa 系数表示为K
合并Po
并Pe
为我们提供关于预测 A 和 B 的可靠性的更稳健的预测:
![在此处输入图像描述](https://i.stack.imgur.com/doh4g.png)
K = (Po - Pe) / (1 - Pe) = 1 - (1 - Po) / (1 - Pe) = 1 - (1 - 0.7) / (1 - 0.5) = 0.4
我们可以看到,A 和 B 彼此同意的程度越高(Po
越高),因为偶然性越少(Pe
越低),Cohen 的 kappa “认为”结果就越可靠
现在假设 A 是陈述的标签(基本事实),然后K
告诉我们 B 的预测有多可靠,即在考虑随机机会时预测与标签有多少一致
Cohen 的 kappa 权重
我们用m
类正式定义列联表:
classifier 2
class.1 class.2 class... class.k Sum over row
class.1 n11 n12 ... n1k n1+
class.2 n21 n22 ... n2k n2+
classifier 1 class... ... ... ... ... ...
class.k nk1 nk2 ... nkk nk+
Sum over column n+1 n+2 ... n+k N # total sum of all table cells
表格单元格包含交叉分类类别的计数,分别表示为nij
,i,j
用于行和列索引
考虑那些k
序数类与两个分类类分开,例如1, 0
分成五个1, 0.75, 0.5, 0.25, 0
具有平滑有序过渡的类,我们不能说这些类是独立的,除了第一个和最后一个类,例如very good, good, normal, bad, very bad
和very good
不是good
独立的,应该更good
接近于bad
到very bad
由于相邻类是相互依赖的,因此为了计算与协议相关的数量,我们需要定义这种依赖关系,即权重表示为Wij
,它分配给列联表中的每个单元格,权重值(在 [0, 1] 范围内)取决于关于两个班级的接近程度
现在让我们看一下加权 KappaPo
中的Pe
公式:
![在此处输入图像描述](https://i.stack.imgur.com/vLgVl.png)
和科恩的Po
kappaPe
公式:
![在此处输入图像描述](https://i.stack.imgur.com/OgD0V.png)
我们可以看到Po
,Cohen 的 kappaPe
中的公式是Weighted Kappa中的公式的特殊情况,其中分配给所有对角线单元格,并且在其他位置权重 = 0,当我们使用加权 Kappa中的公式计算(Cohen 的 kappa 系数)时,我们还取相邻类之间的依赖关系考虑到weight = 1
K
Po
Pe
以下是两种常用的加权系统:
- 线性重量:
![在此处输入图像描述](https://i.stack.imgur.com/gTyHq.png)
- 二次权重:
![在此处输入图像描述](https://i.stack.imgur.com/nSX1Y.png)
其中,|i-j|
是类之间的距离,是类k
的数量
加权 Kappa 损失
这个损失用于我们之前提到的一个分类器是标签的情况,这个损失的目的是使模型(另一个分类器)的预测尽可能可靠,即鼓励模型做出更多的预测与标签一致,同时做出更少的预测考虑相邻类之间的依赖关系时的随机猜测
加权 Kappa 损失的公式为:
![在此处输入图像描述](https://i.stack.imgur.com/6p5rr.png)
它只需采用负科恩 kappa 系数的公式并去掉常数,-1
然后对其应用自然对数,其中dij = |i-j|
对于线性权重,dij = (|i-j|)^2
对于二次权重
下面是用tensroflow写的Weighted Kappa Loss源码,可以看到它只是实现了上面的Weighted Kappa Loss公式:
import warnings
from typing import Optional
import tensorflow as tf
from typeguard import typechecked
from tensorflow_addons.utils.types import Number
class WeightedKappaLoss(tf.keras.losses.Loss):
@typechecked
def __init__(
self,
num_classes: int,
weightage: Optional[str] = "quadratic",
name: Optional[str] = "cohen_kappa_loss",
epsilon: Optional[Number] = 1e-6,
dtype: Optional[tf.DType] = tf.float32,
reduction: str = tf.keras.losses.Reduction.NONE,
):
super().__init__(name=name, reduction=reduction)
warnings.warn(
"The data type for `WeightedKappaLoss` defaults to "
"`tf.keras.backend.floatx()`."
"The argument `dtype` will be removed in Addons `0.12`.",
DeprecationWarning,
)
if weightage not in ("linear", "quadratic"):
raise ValueError("Unknown kappa weighting type.")
self.weightage = weightage
self.num_classes = num_classes
self.epsilon = epsilon or tf.keras.backend.epsilon()
label_vec = tf.range(num_classes, dtype=tf.keras.backend.floatx())
self.row_label_vec = tf.reshape(label_vec, [1, num_classes])
self.col_label_vec = tf.reshape(label_vec, [num_classes, 1])
col_mat = tf.tile(self.col_label_vec, [1, num_classes])
row_mat = tf.tile(self.row_label_vec, [num_classes, 1])
if weightage == "linear":
self.weight_mat = tf.abs(col_mat - row_mat)
else:
self.weight_mat = (col_mat - row_mat) ** 2
def call(self, y_true, y_pred):
y_true = tf.cast(y_true, dtype=self.col_label_vec.dtype)
y_pred = tf.cast(y_pred, dtype=self.weight_mat.dtype)
batch_size = tf.shape(y_true)[0]
cat_labels = tf.matmul(y_true, self.col_label_vec)
cat_label_mat = tf.tile(cat_labels, [1, self.num_classes])
row_label_mat = tf.tile(self.row_label_vec, [batch_size, 1])
if self.weightage == "linear":
weight = tf.abs(cat_label_mat - row_label_mat)
else:
weight = (cat_label_mat - row_label_mat) ** 2
numerator = tf.reduce_sum(weight * y_pred)
label_dist = tf.reduce_sum(y_true, axis=0, keepdims=True)
pred_dist = tf.reduce_sum(y_pred, axis=0, keepdims=True)
w_pred_dist = tf.matmul(self.weight_mat, pred_dist, transpose_b=True)
denominator = tf.reduce_sum(tf.matmul(label_dist, w_pred_dist))
denominator /= tf.cast(batch_size, dtype=denominator.dtype)
loss = tf.math.divide_no_nan(numerator, denominator)
return tf.math.log(loss + self.epsilon)
def get_config(self):
config = {
"num_classes": self.num_classes,
"weightage": self.weightage,
"epsilon": self.epsilon,
}
base_config = super().get_config()
return {**base_config, **config}
加权 Kappa 损失的使用
只要我们可以将问题转化为序数分类问题,我们就可以使用加权 Kappa 损失,即类形成平滑的有序转换,相邻类是相互依赖的,就像用 对某物进行排名一样,模型的输出应该类似于结果very good, good, normal, bad, very bad
Softmax
当我们尝试预测分数向量 (0-1) 时,我们不能使用加权 Kappa 损失1
,即使它们可以求和为但询问乘法数是多少,例如:
import tensorflow as tf
from tensorflow_addons.losses import WeightedKappaLoss
y_true = tf.constant([[0.1, 0.2, 0.6, 0.1], [0.1, 0.5, 0.3, 0.1],
[0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])
y_pred_0 = tf.constant([[0.1, 0.2, 0.6, 0.1], [0.1, 0.5, 0.3, 0.1],
[0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])
y_pred_1 = tf.constant([[0.0, 0.1, 0.9, 0.0], [0.1, 0.5, 0.3, 0.1],
[0.8, 0.05, 0.05, 0.1], [0.01, 0.09, 0.1, 0.8]])
kappa_loss = WeightedKappaLoss(weightage='linear', num_classes=4)
loss_0 = kappa_loss(y_true, y_pred_0)
loss_1 = kappa_loss(y_true, y_pred_1)
print('Loss_0: {}, loss_1: {}'.format(loss_0.numpy(), loss_1.numpy()))
输出:
# y_pred_0 equal to y_true yet loss_1 is smaller than loss_0
Loss_0: -0.7053321599960327, loss_1: -0.8015820980072021
您在Colab中的代码在Ordinal Classification Problems的上下文中工作正常,因为您形成的函数X->Y
非常简单(X 的 int 是 Y 索引 + 1),因此模型可以快速准确地学习它,正如我们所看到K
的(Cohen's kappa 系数)最高1.0
和加权 Kappa 损失下降-13.0
(在实践中通常是我们可以预期的最小)
总而言之,您可以使用加权 Kappa 损失,除非您可以将问题形成为具有单热方式标签的序数分类问题,如果可以并尝试解决 LTR(学习排名)问题,那么您可以查看本教程的实现 ListNet和本教程的 tensorflow_ranking以获得更好的结果,否则你不应该使用加权 Kappa 损失,如果你只能将你的问题形成回归问题,那么你应该像原来的解决方案一样做
参考:
维基百科上的科恩卡帕
R 中的加权 Kappa:对于两个序数变量
tensroflow-addons 中 WeightedKappaLoss 的源代码
tfa.losses.WeightedKappaLoss 的文档
分类变量、有序变量和数值变量之间的差异