tensorflow笔记(二十五)——MultiLabelHead.loss代码走读

1.多标签和多分类区分

怎么定义多标签问题?——多标签和多分类问题,神经网络结构其实可以一摸一样,最后的输出层就是N个节点,每个节点表示一个类别。在estimator.head中有两个子类分别是MultiClassHead和MultiLebelHead,分别对应多分类和多标签问题,这俩最大的区别其实是默认的损失函数不同。多分类用softmax_cross_entropy,多标签用sigmoid_cross_entropy,也就是输出层分别对应softmax和sigmoid。softmax相当于对多个类目进行归一化,最后概率和是1;sigmoid则没有这一处理,所以你会看到多个输出节点概率值可能同时大于0.5——定性解释,多分类问题要求类别之间互斥非此即彼,多标签问题可以允许类目共存。

2.MultiLabelHead.loss代码走读

MuiltLabelHead是tf.estimator.Head的子类,用于多标签问题,默认损失函数为sigmoid_cross_entropy(也可以自定义loss或者train_op,初始化的时候传入)。具体的调用关系如下(version=tensorflow 1.15,其他版本可能稍有不同):
在这里插入图片描述
关键函数:
_unweighted_loss_and_weights:返回loss和weights。

def _unweighted_loss_and_weights(self, logits, processed_labels, features):
    """Computes loss spec."""
    # 如果自定义了loss_fn就用loss_fn,注意expected_loss_dim这里希望这个loss_fn返回值最后一维是1(这里后面会说到)
    if self._loss_fn:
      unweighted_loss = base_head.call_loss_fn(
          loss_fn=self._loss_fn,
          labels=processed_labels,
          logits=logits,
          features=features,
          expected_loss_dim=1)
    # 默认用sigmoid_cross_entropy,并在classes维度上求平均,这样unweighted_loss最后一维就是1
    else:
      unweighted_loss = losses.sigmoid_cross_entropy(
          multi_class_labels=processed_labels,
          logits=logits,
          reduction=losses.Reduction.NONE)
      # Averages loss over classes.
      unweighted_loss = math_ops.reduce_mean(
          unweighted_loss, axis=-1, keepdims=True)
    # 从features里面获取_weight_column中定义的权重,_weight_column是特征名
    weights = base_head.get_weights_and_check_match_logits(
        features=features,
        weight_column=self._weight_column,
        logits=logits)
    return unweighted_loss, weights
  • loss默认就是sigmoid_cross_entropy,且默认loss reduction是None,这样loss的shape就和logits一样,但返回前有一个loss=reduce_mean(loss, axis=-1),loss在类目维度进行求均值,如果原来的shape是[batch_size, logits]会变为[batch_size,1];
  • weights是model_fn传入的features中读到的,样本粒度的权重,默认是1。一般用于样本类目不均衡时候调整权重,有点类似于focal_loss,这个权重值可以落训练数据的时候当作一列特征读入,也可以在input_fn中根据特定label训练时生成。该权重对应MultiLabelHead初始化的weight_column参数。
  • 这里的loss和weights分别返回,此时还没有加权。

losses_utils.compute_weighted_loss:使用weights对loss加权,返回training_loss。

def compute_weighted_loss(losses,
                          sample_weight=None,
                          reduction=ReductionV2.SUM_OVER_BATCH_SIZE,
                          name=None):
	ReductionV2.validate(reduction)

    # If this function is called directly, then we just default 'AUTO' to
	# 'SUM_OVER_BATCH_SIZE'. Eg. Canned estimator use cases.
	if reduction == ReductionV2.AUTO:
	    reduction = ReductionV2.SUM_OVER_BATCH_SIZE
	if sample_weight is None:
	    sample_weight = 1.0
	with K.name_scope(name or 'weighted_loss'):
		# Save the `reduction` argument for loss normalization when distributing
		# to multiple replicas. Used only for estimator + v1 optimizer flow.
		ops.get_default_graph()._last_loss_reduction = reduction  # pylint: disable=protected-access
		
		losses = ops.convert_to_tensor(losses)
		input_dtype = losses.dtype
		weighted_losses = tf_losses_utils.scale_losses_by_sample_weight(
		    losses, sample_weight)
		# Apply reduction function to the individual weighted losses.
		loss = reduce_weighted_loss(weighted_losses, reduction)
		# Convert the result back to the input type.
		loss = math_ops.cast(loss, input_dtype)
		return loss
  • weigths可以是一个标量,也可以跟loss的形状一样,比如loss形状是[batch_size,1],那么weighs可以rank=0或者形状跟loss一样。如果形状跟loss一样,相当于在纵向样本粒度上进行加权,每一个样本都有一个自己的权重,当然前面说了默认值都是1.

3 能否对每一个类目进行加权/掩码?

我们在实际应用中,有时候不仅需要样本粒度的权重,也希望每一个类目都有权重,MultiHeadLabel默认的损失函数没有支持,需要我们自定义loss_fn,在_unweighted_loss_and_weights中首先会检查是否有自定义的loss_fn,如果没有才会用默认的loss.

from tensorflow.python.ops.losses import losses
def build_loss_fn(weights_per_logits):
	def loss_fn(labels, logits):
		loss = losses.sigmoid_cross_entropy(
                multi_class_labels=labels,
                logits=logits,
                weights=weights_per_logits,
                reduction=losses.Reduction.SUM_OVER_NONZERO_WEIGHTS)
        return loss
    return loss_fn

这里定义一个闭包函数,返回loss_fn,具体到loss_fn,我们传入weight_per_logits,该参数是每一个类别的权重,形状和logits一致,在losses.sigmoid_cross_entropy内部调用weighted_losses = math_ops.multiply(losses, weights)实现加权。注意这里我们用了reduction=losses.Reduction.SUM_OVER_NONZERO_WEIGHTS来对loss进行处理,主要原因是我们的weights其实是0/1组成的掩码,每条样本只有一个类目是1,其他地方都是0不贡献损失,所以最后计算loss将非0的元素求平均更合理,当然你也可以换一个其他的reduction。
定义好了loss_fn,可以初始化MultiLabelHead类的时候作为参数传入:

loss_fn = build_loss_fn(weights_per_logits)
head = MyMultiLabelHead( n_classes= K, loss_fn=loss_fn)

不过这里还有个坑,因为在原始MultiLabelHead的_unweighted_loss_and_weights函数中,希望返回的loss是一个最后一维是1的tensor,比如shape=[batch_size, 1]的tensor。但我们刚刚定义的loss_fn的reduction用了SUM_OVER_NONZERO_WEIGHTS,已经直接把loss整成一个标量,最后一维不是1了。这里我们要么改我们的loss_fn,和默认loss处理方式一样reduction用None再在classed维度求平均后返回,如果没有特殊要求这种方案应该是比较不错的。而我就是想用SUM_OVER_NONZERO_WEIGHTS,所以最终采用了另一种方法,直接继承MultiLabelHead,重写_unweighted_loss_and_weights函数,把else后面的默认流程改成我想要的了,构建子类还有一个好处能改几乎所有的重要地方,比如直接在__init__函数中加入weights_per_logits成员变量、重写preditions加自己想要的输出(比如label),等等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值