tf.nn.softmax_cross_entropy_with_logits 和 tf.contrib.legacy_seq2seq.sequence_loss_by_example 的联系与区别


Author: Cao Shengming
Email: caoshengming@trio.ai ? checkmate.ming@gmail.com
Company: Trio 北京(三角兽)科技有限公司


0.函数介绍

这两个函数是在 model 中非常常用的两个损失函数,不管是序列标注还是语言模型中都会见到他们两个的身影,总的来说tf.nn.softmax_cross_entropy_with_logits 是 tf.contrib.legacy_seq2seq.sequence_loss_by_example 的特殊情况,而且在代码处理中也有一定的技巧。
(注:查东西的时候先看 api 和源码再去翻各种乱七八糟的博客,效率会更高。)

1.区别联系

1.1 tf.nn.softmax_cross_entropy_with_logits

**函数实现:**传统实现不赘述
函数输入:

logits: [batch_size, num_classes]
labels: [batch_size, num_classes]
logits和 labels 拥有相同的shape

代码示例:

import tensorflow as tf
import numpy as np
y = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0]])  # onestep vector
logits = np.array([[12, 3, 2], [3, 10, 1], [1, 2, 5], [4, 6.5, 1.2], [3, 6, 1]])
y_ = tf.nn.softmax(logits)
e1 = -np.sum(y * np.log(y_), -1)  # reduce_sum 所有样本 loss 求和
 
sess = tf.Session()
y = np.array(y).astype(np.float64)
e2 = sess.run(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=logits))  # labels 和 logtis shape 相同
 
print("公式计算的结果:\n", e1)
print("tf api 计算的结果:\n", e2)

1.2 tf.nn.sparse_softmax_cross_entropy_with_logits

主要区别:与上边函数不同,输入 labels 不是 one-hot 格式所以会少一维
函数输入:

logits: [batch_size, num_classes]
labels: [batch_size]
logits和 labels 拥有相同的shape

代码示例:

import tensorflow as tf
labels = [0,1,2] #只需给类的编号,从 0 开始
 
logits = [[2,0.5,1],
          [0.1,1,3],
          [3.1,4,2]]
 
logits_scaled = tf.nn.softmax(logits)
result = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=labels, logits=logits)
 
with tf.Session() as sess:
    print(sess.run(result))

1.3 tf.contrib.legacy_seq2seq.sequence_loss_by_example

函数实现:

def sequence_loss_by_example(logits, targets, weights,
                             average_across_timesteps=True,
                             softmax_loss_function=None, name=None):
#logits: List of 2D Tensors of shape [batch_size x num_decoder_symbols].
#targets: List of 1D batch-sized int32 Tensors of the same length as logits.
#weights: List of 1D batch-sized float-Tensors of the same length as logits.
#return:log_pers 形状是 [batch_size].
   for logit, target, weight in zip(logits, targets, weights):
      if softmax_loss_function is None:
        # TODO(irving,ebrevdo): This reshape is needed because
        # sequence_loss_by_example is called with scalars sometimes, which
        # violates our general scalar strictness policy.
        target = array_ops.reshape(target, [-1])
        crossent = nn_ops.sparse_softmax_cross_entropy_with_logits(logit, target)
      else:
        crossent = softmax_loss_function(logit, target)
      log_perp_list.append(crossent * weight)
    log_perps = math_ops.add_n(log_perp_list)
    if average_across_timesteps:
      total_size = math_ops.add_n(weights) 
      total_size += 1e-12  # Just to avoid division by 0 for all-0 weights.
      log_perps /= total_size

函数说明: 可以发现通过 zip 操作对 list 的每个元素执行一次 sparse 操作,其他的都与 sparse 是相同的,所以使用这个函数的关键在于如何进行输入的 list 的构造。最容易想到的是安 sequence_length进行 unstack,但是这样会给输入的构造带来很多额外的工作量。具体代码使用时是由一定技巧的,请参见下一部分代码呈现

2.代码呈现

此处我们将展示在训练 char-level语言模型的时候,两种损失函数的处理,就可以搞清这两个函数到底是怎么使用的。

case1:使用 softmax…的情况

def build_loss(self):
	with tf.name_scope('loss'):
		y_one_hot = tf.one_hot(self.targets, self.num_classes)
		y_reshaped = tf.reshape(y_one_hot, self.logits.get_shape())
		loss =tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=y_reshaped)
		self.loss = tf.reduce_mean(loss)

case1:使用 sequece…的情况

with tf.name_scope('loss'):
	output = tf.reshape(outputs, [-1, args.state_size])
	self.logits = tf.matmul(output, w) + b
	self.probs = tf.nn.softmax(self.logits)
	self.last_state = last_state

	targets = tf.reshape(self.target_data, [-1])
	loss = seq2seq.sequence_loss_by_example([self.logits],[targets],[tf.ones_like(targets, dtype=tf.float32)])
	self.cost = tf.reduce_sum(loss) / args.batch_size
	tf.summary.scalar('loss', self.cost)

上述代码总结:
我们可以清晰地看到两者接受的原始的输入竟然都是一样的,所以这两者在处理时都用到了一定的技巧,前者的技巧是是将 [B*T] 作为 [B] ,后者的技巧是在外边包一层 “[ ]” 留给函数内部的 zip 来使用,当然同样是将 [B*T] 作为 [B] ,这些操作都要考虑清楚,才能搞清楚函数的差异到底在哪。
另外需要注意的一点是 sequence 函数中的 weight 参数,可以替代手工的 loss mask 对不需要的 padding 位置不进行 loss 的计算。

3.References

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值