在Keras中使用sample_weight进行序列标注

18

我正在处理一个带有不平衡类的顺序标记问题,并且希望使用sample_weight来解决不平衡问题。基本上,如果我训练模型大约10个时期,就会获得很好的结果。如果我训练更多时期,val_loss一直在下降,但是结果变得更差。我猜测模型只是检测到更多优势类别,对较小的类别产生了负面影响。

该模型有两个输入,用于词嵌入和字符嵌入,输入是从0到6的7种可能的类之一。

使用填充后,用于单词嵌入的输入层的形状为(3000, 150),用于单词嵌入的输入层的形状为(3000, 150, 15)。我将测试数据和训练数据分割成0.3比例,这意味着用于单词嵌入的X_train的形状是(2000, 150),用于字符嵌入的形状是(2000, 150, 15)y包含每个单词的正确类别,编码为7维的one-hot向量,因此其形状为(3000, 150, 7)。同样,y也被分成训练和测试集。然后,每个输入都被送入双向LSTM中。

输出是一个矩阵,其中对2000个训练样本的每个单词分配了7个类别之一,因此大小为(2000, 150, 7)


起初,我尝试将sample_weight定义为长度为7的np.array,其中包含每个类别的权重:

count = [list(array).index(1) for arrays in y for array in arrays]
count = dict(Counter(count))
count[0] = 0
total = sum([count[key] for key in count])
count = {k: count[key] / total for key in count}
category_weights = np.zeros(7)
for f in count:
    category_weights[f] = count[f]

但是我遇到了以下错误 ValueError: 发现了一个形状为(7,)的样本权重数组,而输入的形状为(2000, 150, 7)。无法广播样本权重。

查看文档,看起来我应该传递一个形状为样本数、序列长度的二维数组(a 2D array with shape (samples, sequence_length))。因此,我创建了一个(3000, 150)的数组,其中包含每个序列中每个单词的权重拼接:

weights = []

for sample in y:
    current_weight = []
    for line in sample:
        current_weight.append(frequency[list(line).index(1)])
    weights.append(current_weight)

weights = np.array(weights)

compile()中添加sample_weight_mode="temporal"选项后,将其通过sample_weight参数传递给fit()函数。

起初我收到了一个维度错误的错误信息,然而在仅为训练样本生成权重之后,我得到了一个(2000, 150)的数组,我可以用它来拟合我的模型。


  • 这是定义sample_weights的正确方式吗,还是我完全错了?我不能说我注意到加入权重会有任何改进,所以我一定是错过了些什么。
2个回答

27

我认为你混淆了sample_weightsclass_weights。查看一下文档,我们可以看到它们之间的区别:

sample_weights用于为每个训练样本提供权重。这意味着您应该传递与您的训练样本数量相同的1D数组(指示每个样本的权重)。如果您使用的是时间数据,则可以传递一个2D数组,使您能够为每个样本的每个时间步骤赋予权重。

class_weights用于为每个输出类别提供权重或偏差。这意味着您应该为您要分类的每个类别传递一个权重。此外,该参数需要传递一个字典(而不是一个数组,这就是为什么您会得到那个错误的原因)。例如,考虑以下情况:

class_weight = {0 : 1. , 1: 50.}

在这个二元分类问题中,你将类别 1 的样本权重(或 "相关性")提高了50倍,相比之下类别 0 的样本。这样可以弥补数据集不平衡的问题。这里有另一个有用的帖子,详细解释了处理不平衡数据集时需要考虑的其他选项。

如果我训练更多的 epochs,val_loss 会持续下降,但结果变得更差。

很可能你正在过拟合,其中一个原因可能是你的数据集存在不平衡的类别,正如你所怀疑的那样。调整类别权重应该有助于缓解这种情况,然而仍可能有其他因素导致过拟合,超出了此问题/答案的范围(所以在解决此问题后务必注意这些因素)。


根据你的帖子,我认为你需要使用 class_weight 来平衡你的数据集进行训练,你需要传递一个字典来指示你的 7 个类别之间的权重比率。如果要为每个样本分配自定义权重,请考虑使用 sample_weight

如果你想获得这两者之间更详细的比较,请查看我在相关问题中发布的答案。 剧透: sample_weight 会覆盖 class_weight,因此您必须使用其中一个,但不要混用。


更新:截至本次编辑(2020年3月27日),查看training_utils.standardize_weights()源代码,我们可以看到它现在同时支持 class_weightssample_weights

一切都会标准化为单个样本权重数组(或时序权重数组)。如果提供了 sample_weightsclass_weights,则将它们相乘。


嘿 @pablo_sci,我还没有遇到这个问题,无法设想出一个“解决方法”,我看到你在github的问题页面上发布了帖子,但是你认为使用sample_weight有什么办法可以解决吗?你的问题是否与那里的原始帖子完全相同?你是否在这里发布过(因此我可以查看您的帖子并在那里回答/帮助)? - DarkCygnus
感谢@DarkCygnus。我还没有在SO上发布。我猜这应该是一个解决方法,因为在使用fit_generator(用于数组)+ sample_weight(高度不平衡的目标)进行预测时存在一个不太奇怪的情况。 唯一的解决方法是使用平衡类别(抽样)来提供网络。 - Pablo Casas
1
@pablo_sci 如果你发帖了,并附上一些细节和代码示例,随时欢迎联系我,这样我就可以看看并帮助你 :) 根据你的描述,我认为你的生成器应该足够“聪明”,能够传递样本以及它们相关的sample_weight,每个样本的权重都是1,不取决于频率。 - DarkCygnus
1
如果我没记错的话,幕后发生的事情是,如果一个样本的权重为X,它将制作X个该样本的“副本”,并对其进行训练,这反过来会导致使用该样本进行更多的梯度更新。因此,这不像是直接修改反向传播计算;我们所做的是对该样本(或类别,如果使用类别权重)执行更多次的反向传播……我们可以说这是一种数据增强形式。这有助于处理(常见的)不平衡数据集的情况。 - DarkCygnus
1
不客气 :) 顺便说一下,在我的一个相关答案中,我分享了代码的那一部分的链接。现在检查链接似乎有点变化,但是你要找的东西在_standardize_user_data方法的第470行上。具体来说,是第625行...现在我正在阅读它,它会建议样本权重不再覆盖类别权重(第629行)。 - DarkCygnus
显示剩余6条评论

0

我在网上搜索了相同的问题,并且在我的情况下,正确使用sample_weight后确实有很好的准确性提升。

我认为你的理解是正确的,步骤也是正确的。你没有在你的情况下看到改进的一个可能原因是,当你传入sample_weight时,较高的值意味着较高的权重。这意味着你不能直接使用词频。你可以考虑使用倒排词频:

total = sum([count[key] for key in count])
count = {k: count[key] / total for key in count}
for f in count:
category_weights = np.zeros(7)
    category_weights[f] = 1 - count[f]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接