前面两篇关于文本匹配的博客中,都用到了Sampled-softmax训练方法来加速训练,Sampled-softmax简单点来说,就是通过采样,来减少我们训练计算loss时输出层的运算量。从第一篇博客中的不知其然,到后面看到DSSM代码中Sampled softamax的知其然,这篇博客目的是在知其所以然,从Sampled softmax的数学原理思考,为什么DSSM中的训练代码可以这样写,代码还能怎么改进。
这段时间也一直在思考,如何才能不随波逐流,如何才能成为一名独当一面的算法工程师,我想对于一个问题的浅尝辄止肯定是远远不够的,不仅要知其然还要知其所以然,光是读懂这几篇论文是不够的,进一步的要理解代码工程实现,更进一步,去理解代码背后的数学原理,为什么代码这样做一定能保证结果正确或者收敛,了解了这些,我们才能够根据自己的想法去做优化,我想对于现在日益成熟的深度学习,难的可能不是如何实现,而是对于自己的实际场景去调整优化。
上面有点扯远了,回归正题,这篇博客主要基于Tensorflow官方对于Sampled softmax文档,建议大家有问题不懂的时候多看官方文档,写的非常通俗易懂,下面我就说说自己对Sampled Softmax数学原理的理解。
What is Candidate Sampling Tensorflow 官方文档
当我们做分类问题时,假设我们需要分类的类别数为 ∣ L ∣ |L| ∣L∣,那么我们做法通常如下,假设我们的输入为 x x x:
logits
, 这里的 logits
其实代表的就是各个类别未经归一化的概率分布(也就是加起来不为1),网络就是学习出一个映射 f θ ( x ) = l o g i t s f_{\theta}(x) = logits fθ(x)=logitslogits
作为softmax
的输入进行归一化操作,softmax
的输出则是表示各个类别上的概率分布还是采用之前博客中的Query-Doc Softmax作为说明,从logtis
进行softmax
归一化公式如下:
logits
这个公式的具体解释可以参考之前的两篇博客,下面分析一下上面这个公式,下面是重点:
softmax
归一化logits
加上一个与类别无关的常数,结果将不会变化。这个很好理解,当我们对每个logits
均加上同一个常数K
,那么分子分母可以约去这个常数K
,结果不变partition function
,分母与类别无关,因为分母中对整个类别集合进行了求和,给定输入后,分母归一化因子也就确定了。从上面分析可以知道,我们的关键词是logits
、softmax归一化
。logits
本质上就是未归一化的概率,softmax
目的就是计算归一化因子(分母),对logtis
进行归一化,从而得到一个概率分布。问题就在于需要对整个类别集合 D \mathcal D D计算logtis
并求和,当类别集合比较大时(比如上面的Query-Doc预测,以及语言模型训练),计算量会非常大。
Sampled Softmax
的核心思想就在于 Sampled
,既然类别全集太大,那么能不能采样一个类别子集,然后在计算在子集上的logtis
然后进行softmax
归一化呢?假设我们类别全集为 L L L,输入为 ( x , T i ) (x,T_i) (x,Ti),其中 T i T_i Ti就是我们的输入类别标签,那么我们可以在 L L L上随机采样一个子集 S i ⊂ L S_i \subset L Si⊂L,并且与我们的输入类别 T i T_i Ti,共同组成候选类别子集 C i C_i Ci
C i = T i ∪ S i C_i = T_i \cup S_i Ci=Ti∪Si
我们在训练模型时,只要在这个采样出来的 C i C_i Ci上计算logits
和softmax
就可以了,大大减少了计算量,加快训练过程。现在问题是:
logits
应该如何计算,和使用类别全集时的logtis
有什么对应关系?从上面可以看出,当我们进行采样后,按理来说logtis
计算方法也需要改变,这样才能最后得到正确的概率分布。前方公式预警!!!!
logtis
,这里 F ( ∗ , ∗ ) F(*,*) F(∗,∗)其实表示的就是我们的模型以上符号如果没有特殊说明,都表示是在类别全集上进行计算
其中 K ( x ) K(x) K(x)表示与类别 y y y无关的常数,其实就是softmax
计算出来的分母。推导也很简单:
P ( y ∣ x ) = e x p ( F ( x , y ) ) ∑ y ^ ∈ L e x p ( F ( x , y ^ ) ) P(y|x) =\frac{ exp(F(x,y))}{\sum _{\hat y \in L}exp(F(x,\hat y))} P(y∣x)=∑y^∈Lexp(F(x,y^))exp(F(x,y))
两边同时取 l o g log log,可以得到
l o g ( P ( y ∣ x ) ) = F ( x , y ) − l o g ( ∑ y ^ ∈ L e x p ( F ( x , y ^ ) ) ) = F ( x , y ) − K ( x ) log(P(y|x)) = F(x,y) - log(\sum _{\hat y \in L}exp(F(x,\hat y))) = F(x,y) - K(x) log(P(y∣x))=F(x,y)−log(y^∈L∑exp(F(x,y^)))=F(x,y)−K(x)
最后将 K ( x ) K(x) K(x)移项则可以得到上式。即logits
可以写成“ l o g ( P ( y ∣ x ) ) + c o n s t log(P(y|x) ) + const log(P(y∣x))+const”这种形式。为什么要推导出这个关系呢,且听后面分解~
这里推导也很简单,当 y ∈ S i y \in S_i y∈Si时概率为 Q ( y ∣ x i ) Q(y|x_i) Q(y∣xi),否则为 ( 1 − Q ( y ∣ x i ) ) (1 - Q(y|x_i)) (1−Q(y∣xi)),这里假设每次采样都是独立同分布(iid),所以我们把每个类别概率乘起来就可以了
重点来了!前面都是铺垫,我们最终的目的是计算给定输入 x x x,在采样后的类别子集 C i C_i Ci概率分布表示,也就是
P ( t i = y ∣ x , C i ) P(t_i = y|x,C_i) P(ti=y∣x,Ci)
进一步,由于在2中,logits与概率之间的关系,我们已经得到,所以我们就可以得到采样后logits
的正确表示形式啦~,我们假设 C i C_i Ci为采样子集 S i S_i Si和我们目标类别 t i t_i ti的并集
C i = S i ∪ { t i } C_i = S_i \cup \{t_i\} Ci=Si∪{ti}
那么在给定类别子集 C i C_i Ci,输入 x x x条件下,输入类别 t i = y t_i = y ti=y的概率 P ( t i = y ∣ x , C i ) P(t_i = y|x,C_i) P(ti=y∣x,Ci)计算推导如下,首先使用贝叶斯公式:
上面的推导就是简单的贝叶斯公式。我们分析一下推导结果:
综上,下面 P ( t i = y ∣ x , C i ) P(t_i = y|x,C_i) P(ti=y∣x,Ci)计算结果如下:
其中 K ( x i , C i ) K(x_i,C_i) K(xi,Ci)为与类别 y y y无关的常数,我们对上式两边取 l o g log log,则有:
结果已经跃然纸上, Q ( y ∣ x i ) Q(y|x_i) Q(y∣xi)是我们自己选取的采样函数,通过这个式子我们已经得到了采样后类别子集 C i C_i Ci和类别全集 L L L上概率分布的关系
logits
和原始logits
关系终于要到最后一步了,我们已经知道了采样后类别子集 C i C_i Ci和类别全集 L L L上概率分布的关系,这时我们只需要利用2中的结论,logits与概率之间的关系,就可以得出采样后类别子集 C i C_i Ci上的logits
和原始logits
关系,推导如下:
l o g ( P ( t i = y ∣ x , C i ) ) = F ( x , y ∣ C i ) − K 1 ( x ) log(P(t_i = y|x,C_i)) = F(x,y|C_i) - K_1(x) log(P(ti=y∣x,Ci))=F(x,y∣Ci)−K1(x)
l o g ( P ( y ∣ x ) ) = F ( x , y ) − K 2 ( x ) log(P(y|x)) = F(x,y) - K_2(x) log(P(y∣x))=F(x,y)−K2(x)
带入上面推导出来的公式:
F ( x , y ∣ C i ) − K 1 ( x ) = F ( x , y ) − K 2 ( x ) − l o g ( Q ( y ∣ x ) ) + K ‘ ( x i , C i ) F(x,y|C_i) - K_1(x) = F(x,y) - K_2(x) - log(Q(y|x)) + K^`(x_i,C_i) F(x,y∣Ci)−K1(x)=F(x,y)−K2(x)−log(Q(y∣x))+K‘(xi,Ci)
其中与类别 y y y无关的常数项都可以合并,则有:
F ( x , y ∣ C i ) = F ( x , y ) − l o g ( Q ( y ∣ x ) ) + C o n s t F(x,y|C_i) = F(x,y) - log(Q(y|x)) + Const F(x,y∣Ci)=F(x,y)−log(Q(y∣x))+Const
大功告成!上面的公式就是我们进行采样后的logtis
与原始logits
关系,具体的用法如下:
logits
(这样就不用在类别全集计算logits
了),这里得到的其实是 F ( x , y ) F(x,y) F(x,y)logits
, F ( x , y ∣ C i ) = F ( x , y ) − l o g ( Q ( y ∣ x ) ) F(x,y|C_i) = F(x,y) - log(Q(y|x)) F(x,y∣Ci)=F(x,y)−log(Q(y∣x))softmax
输入,计算概率分布以及loss进行梯度下降从上面分析可以得到:
F ( x , y ∣ C i ) = F ( x , y ) − l o g ( Q ( y ∣ x ) ) + C o n s t F(x,y|C_i) = F(x,y) - log(Q(y|x)) + Const F(x,y∣Ci)=F(x,y)−log(Q(y∣x))+Const
我们选取不同的采样函数 Q ( y ∣ x ) Q(y|x) Q(y∣x),那么结果也会不同,比如Tensorflow中有如下采样方式:
tf.nn.log_uniform_candidate_sampler
,按照 log-uniform (Zipfian) 分布采样。tf.nn.learned_unigram_candidate_sampler
按照训练数据中类别出现分布进行采样。具体实现方式:1)初始化一个 [0, range_max] 的数组, 数组元素初始为1; 2) 在训练过程中碰到一个类别,就将相应数组元素加 1;3) 每次按照数组归一化得到的概率进行采样。上述采样方式都和输入 x x x相关,而如果我们选择随机采样,那么选择每个类别的概率都相等,也就是说 l o g ( Q ( y ∣ x ) ) log(Q(y|x)) log(Q(y∣x))对于每个类别来说都一样,可以看做一个常数,并到后面常数项中,所以有:
F ( x , y ∣ C i ) = F ( x , y ) + C o n s t F(x,y|C_i) = F(x,y) + Const F(x,y∣Ci)=F(x,y)+Const
而上面分析过,logits
加上或者减去一个常数,对softmax
结果并没有影响,所以可以用原始logits
代替采样后的logits
。所以DSSM代码中,构造子集后直接计算logits
然后做softmax
结果也是正确的,代码如下:
with tf.name_scope('Loss'):
# Train Loss
# 转化为softmax概率矩阵。
prob = tf.nn.softmax(cos_sim)
# 只取第一列,即正样本列概率。相当于one-hot标签为[1,0,0,0,.....,0]
hit_prob = tf.slice(prob, [0, 0], [-1, 1])
loss = -tf.reduce_sum(tf.log(hit_prob))
tf.summary.scalar('loss', loss)
理论指导实践,代码中每一步都是有理论依据的,所以只有弄懂其背后的数学原理才能各个算法活学活用。以上也都是我的个人理解,难免有错,欢迎大家和我讨论,一起学习,一起进步~