在 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_weights
和class_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_weights
和 sample_weights
:
一切都归一化为单个样本(或时间步) 权重数组。 如果同时提供
sample_weights
和class_weights
, 权重相乘。
【讨论】:
对不起,我可能应该在我的帖子中提到这一点:最初我也理解class_weight
是我想要实现的最合适的参数。在我上面的代码中定义为count = k: count[key] / total for key in count
的count
变量本来是作为class_weight
传递的。但是,当我尝试这样做时,出现以下错误:ValueError: class_weight not supported for 3+ dimensional targets.
在 SO 上环顾四周后,似乎对于 3d+ 输出,您别无选择,只能使用 sample_weight
@darkcygnus 当您使用fit_generator
和class_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