最后更新于
最后更新于
keras
中的损失函数, 或者自定义损失函数直接在model.compile
中使用的, 都是接受真实标签和类别预测概率两个变量, 计算得到的, 即loss为预测值与真实值的某种误差函数.
但有些任务/模型, 计算损失时比较复杂, 需要别的参数. 这里的参数指的不是超参数, 例如focal loss中的或者, 这种超参数可以使用函数工厂的方法, 返回闭包, 具体方法见一节. 这里的参数, 对应的是模型中某一层的输出, 可能是计算过程中需要引入其他的参数或变量, 也可能对应多输出且损失不是简单的加和情况(有交互).
在实际中常遇到的属于这种情况的有:
seq2seq任务, 或者序列标注任务等. NLP任务基本都需要对样本进行padding或者truncate, 对于这种序列任务, 最后的输出往往都是各个序列上损失的加和
因此对于padding的位置, 计算整体损失时, 就不应当计入
因此对应的损失函数需要引入mask变量
句向量或任何其他形式向量之间的匹配. 例如FAQ问题, 将问题和答案都编码成长度相同的向量, 然后计算它们的余弦相似度, 作为loss, 正确答案loss小, 错误答案反之. .因此可以使用triplet loss:
只要保证正确答案的cos值比错误答案的cos高即可, 大多少不重要, 以这个思路进行训练, 就对应上面的损失函数
可以看到这个损失函数就不是简单的是输入输出的组合事情了.
无论哪种方法都是单独地构造一个损失层, 区别在于:
如何将损失层融入到模型中
如何组织样本喂给模型进行fit
第一种方法以上面的FAQ为例, 参考自中的第二部分.
代码如下:
首先, 我们将损失函数单独地构建为一层:
然后在构建Model时, 指定output
为该loss
:
这就相当于指定模型的输出不再是概率, 而是整体的损失. 因此传入到最后计算损失的函数中, 所用的y_pred
就是模型得到的损失了, 所以compile
方法写为:
这相当于自定义了一个损失函数(符合keras损失函数接受y_true和y_pred的原则), 该函数返回的就是y_pred, 而y_pred就是模型计算得到的样本损失.
再以seq2seq任务常见的mask需求为例, 介绍另一种形式, 但原理与方法一是相同的.
计算损失函数时要屏蔽占位符对应产生的损失, 因此可以写为:
mask是在损失函数中计算的, 当然也可以在compile时直接指定loss
为该参数. 但在seq2seq任务中, decoder的输出为预测值, 其真值也会作为输入, 在解码时在每个step中引导下个step的输出, 作为训练的一部分, 而这个真值是从第二个step开始到最后一个step的序列. 因此如果再单独的使用一个Input作为真实结果的占位符, 就需要对原始序列进行处理, 比较麻烦, 同一个变量两次输入也不够优雅.
使用如下的方法:
仍然是让loss单独作为一层, 并制定为训练模型的output, 但这里指定模型损失时不是在compile
中使用lambda函数作为损失函数, 而是使用了模型的add_loss
方法. 注意这里的loss要用list的形式传入.
这样如果模型的整体损失是由多部分组成, 就可以使用add_loss
函数逐步添加, 而不用构思一个损失函数去做了.
另外需要注意, 由于已经使用了add_loss
为模型添加了损失, 在compile
的时候就不用再为模型指定损失了, 因此loss
参数对应的指定为None
.
另外的隐身为对metrics
的灵活添加. 普通的metrics
对应是在compile
时以列表的形式指定, 可以指定多个. 评价函数也是如同损失函数, 接受真值和预测概率, 如果使用到模型中的层或参数, 也可以如同损失函数一样添加.
添加metrics
方法使用到model
的两个属性, metrics_names
和metrics_tensors
, 分别对应评价方法的名字(供显示时使用)和函数, 这个需要直接计算出评价值, 训练的时候就按名称: 值
的形式显示.
由于以往对于分类任务, output
参数指定的都是输出概率那一层, 因此在fit
的时候对应的喂给output的就是样本真实标签. 而这里, 正确答案和错误答案都已经通过Input
占位符的方式传入, 已经计算得到了损失, 不再需要额外的真值了, 所以代码最后一行中的, 可以是任意符合shape
要求的array
, 整个训练过程都不会使用到这个值(而且在构建模型时都不用给这个值一个占位符Input
).