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

Posted

技术标签:

【中文标题】在 Keras 中使用 sample_weight 进行序列标记【英文标题】:Using sample_weight in Keras for sequence labelling 【发布时间】:2018-06-27 03:49:47 【问题描述】:

我正在处理不平衡类的顺序标记问题,我想使用sample_weight 来解决不平衡问题。基本上,如果我训练模型大约 10 个 epoch,我会得到很好的结果。如果我训练更多的时期,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: Found a sample_weight array with shape (7,) for an input with shape (2000, 150, 7). sample_weight cannot be broadcast.

查看文档,看起来我应该传递 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 的正确方法还是我做错了?我不能说我注意到添加权重有任何改进,所以我一定错过了什么。

【问题讨论】:

【参考方案1】:

我认为您混淆了sample_weightsclass_weights。稍微检查一下docs,我们可以看到它们之间的区别:

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

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

class_weight = 0 : 1. , 1: 50.

在这种情况下(一个二元分类问题),与 0 类相比,1 类的样本的权重(或“相关性”)是类的 50 倍。通过这种方式,您可以补偿不平衡的数据集。这是另一个有用的post,详细解释了在处理不平衡数据集时要考虑的这个选项和其他选项。

如果我训练更多 epoch,val_loss 会不断下降,但我会得到更差的结果。

您可能是过度拟合的,而可能导致这种情况的原因是您的数据集具有的不平衡类,正如您正确怀疑的那样。补偿类权重应该有助于缓解这种情况,但可能仍有其他因素可能导致过度拟合,超出此问题/答案的范围(因此请务必在解决此问题后注意这些因素)。


从您的帖子来看,在我看来,您需要使用class_weight 来平衡您的训练数据集,为此您需要传递一个字典,指示您之间的权重比7班。仅当您想为每个样本提供自定义权重以供考虑时,才考虑使用sample_weight

如果您想在这两者之间进行更详细的比较,请考虑查看 this answer 我在相关问题上发布的内容。 剧透:sample_weight 覆盖 class_weight,因此您必须使用其中之一,但不能同时使用两者,因此请注意不要混合使用它们。


更新:截至本次编辑时(2020 年 3 月 27 日),查看 training_utils.standardize_weights() 的 source code 我们可以看到它现在支持两者 class_weightssample_weights:

一切都归一化为单个样本(或时间步) 权重数组。 如果同时提供sample_weightsclass_weights, 权重相乘。

【讨论】:

对不起,我可能应该在我的帖子中提到这一点:最初我也理解class_weight 是我想要实现的最合适的参数。在我上面的代码中定义为count = k: count[key] / total for key in countcount 变量本来是作为class_weight 传递的。但是,当我尝试这样做时,出现以下错误:ValueError: class_weight not supported for 3+ dimensional targets. 在 SO 上环顾四周后,似乎对于 3d+ 输出,您别无选择,只能使用 sample_weight @darkcygnus 当您使用fit_generatorclass_weight 并且验证中的损失函数返回与训练显着不同的数字时,您是否找到了解决方案或解决方法? (github.com/keras-team/keras/issues/4137) @pablo_sci 如果您碰巧发布了它以及一些详细信息和代码示例,请随时联系我,以便我可以查看并可能会帮助您:) 根据您的描述,我想您的生成器应该“足够聪明”,以便能够传递样本它们关联的sample_weight,每个样本1个,不依赖于频率 @989 IIRC,后台发生的情况是,如果一个样本的权重为 X,它将对该样本进行 X 个“副本”,并对其进行训练,这反过来会导致更多梯度使用该示例进行更新。所以,这不像您要修改反向传播计算本身;我们正在做的是对该样本(或类,如果使用类权重)执行更多次反向传播......我们可以说这是数据增强的一种形式。这有助于(常见)不平衡数据集的情况。 不客气 :) FWIW,在我的相关@​​987654326@ 上,我分享了该部分代码的链接。现在检查链接似乎已经发生了一些变化,但似乎您正在寻找的是在_standardize_user_data 方法上的470 行。特别是 625... 行,现在我正在阅读它,这表明样本权重不再覆盖类权重(第 629 行)。【参考方案2】:

我在网上搜索了同样的问题,在我的案例中正确使用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]

【讨论】:

以上是关于在 Keras 中使用 sample_weight 进行序列标记的主要内容,如果未能解决你的问题,请参考以下文章

在 GridSearchCV 中使用 sample_weight

sample_weight 在 SGDClassifier 中是如何工作的?

scikit 随机森林 sample_weights 的使用

Python SkLearn Gradient Boost Classifier Sample_Weight Clarification

PySpark 中是不是有与 scikit-learn 的 sample_weight 等效的参数?

scikit-learn:随机森林 class_weight 和 sample_weight 参数