智能问答是NLP领域落地最多的场景,其商业价值较高,能有效解决业务问题,降低人力成本。智能问答分为封闭域问答与开放域问答两种。封闭域的问答是指根据用户的问题,从已有问答库中找出最匹配的答案返回给用户。而开放域问答则是根据用户的问题,由模型根据已学会的知识生成相应的答案返回给用户。由于当前开放域问答存在回答不可控的问题,在工业界落地项目较少。而封闭域问答是NLP领域中落地最多的项目之一。封闭域问答最常见的形式是FAQ问答,即常见问题解答。
在NLP中,对FAQ任务的解决方案是,将用户会遇到的常见问题收集起来构建一个问答库,问答库中的每个条目包含问题和最佳答案。用户提问时,智能问答模型能根据用户的问题从问答库中查找最佳答案返回给用户。找最佳答案的方法有两种,一种做法是将用户的问题与问答库中的问题进行相似度计算,将问答库中相似度最高的问题所对应的回答返回给用户。另一种做法是将用户的问题与问答库中的答案进行匹配,找出最佳匹配返回给用户。这两种方法都涉及相似度计算问题,本文将对解决此类问题常用的损失函数做个介绍。此外,对于FAQ任务,本质上也是一个分类问题,所以本文也将介绍二分类交叉熵损失函数。
2 Binary CrossEntropy Loss在FAQ任务中,假设训练样本是由([问题q,答案a],标签y)构成,如果a是问题q的最佳答案,则y=1,如果a不是最佳答案则y=0。对于这样的二分类任务,我们可以采用Binary CrossEntropy Loss来训练模型。
模型的训练过程是在不断最小化交叉熵,从数学角度看,优化交叉熵损失的本质是使模型的分布不断去拟合数据集的分布。在FAQ任务中,可以理解为让模型去学习问答库中问题与答案的某种关系。
Binary CrossEntropy Loss的定义如下方公式所示,
(1)
其中,,为batch_size,是模型预测输出的概率值,即预测样本是正例的概率。为样本标签,若样本为正例,则,若样本为负例,则.
当样本为正例时,我们希望模型预测是正例的概率越大越好。由公式(1)可以看出,当时,
,
越大,损失就越小。
当样本为负例时,我们希望模型预测是正例的概率越小越好。由公式(1),当时,
,
越小,损失就越小。
下面以FAQ问答任务为例,用代码来实现二分类交叉熵损失函数。
loss_fn = nn.BCEWithLogitsLoss()
# x为问题,y为答案,model为一个Encoder
# x_rep是x经过模型Encode后的语义向量表示,x_rep.shape:(batch_size, vec_dim)
# y_rep是y经过模型Encode后的语义向量表示,y_rep.shape:(batch_size, vec_dim)
x_rep, y_rep = model(x, y)
logits = model.linear(torch.cat([x_rep, y_rep], 1))
# target为标签向量,当y是x的最佳答案时,target=1,否则target=0
loss = loss_fn(logits, target)
上述代码直接采用了Pytorch中的BCEWithLogitsLoss损失函数。
这里需要注意的是,二分类交叉熵损失函数公式中的的取值范围必需是在[0,1]区间范围。而上述代码第八行,[x_rep,y_rep]经过模型全接层后的输出 logits的取值范围不在[0,1]区间,为了将logits的值压缩在[0,1]区间,代码中使用nn.BCEWithLogitsLoss()函数,这个函数内部采用了sigmoid函数将变量logits的值压缩到[0,1]区间。
3 Hinge Loss在上例中,是给模型输入一个问题、一个答案,让模型预测问题与答案是否匹配。如果概率值大于等于0.5,则认为问题与答案匹配。如果我们希望问题与最佳答案的匹配度必须比问题与不佳答案的匹配度高,该用什么方法。下面具体说明一下这个问题。
假设给模型输入(问题, 最佳答案, 不佳答案),我们希望问题与最佳答案的打分比问题与不佳答案的打分高出一个margin,即:
Loss(question, pos_reply, neg_reply) = max(0, margin- score(question, pos_reply) + score(question, neg_reply))
上述就是Hinge Loss的定义,其公式如下所示,
(2)
其中,表示问题,表示最佳答案,表示不佳答案。是打分函数。
通俗点说,公式(2)的含义是,给定一个问题,我们希望问题与最佳答案的得分比问题与不佳答案的得分高出一个。
下面用代码来实现Hinge Loss
q = list(batch_df["question"])
pos_reply = list(batch_df["reply"])
neg_reply = list(batch_df["neg_reply"])
q = q + q
reply = pos_reply + neg_reply
x_rep, y_rep = model(q, reply)
# 用余弦相似度作为打分函数
sim = F.cosine_similarity(x_rep, y_rep)
# sim1:问题与最佳答案的相似度
sim1 = sim[:batch_size]
# sim2:问题与不佳答案的相似度
sim2 = sim[batch_size:]
# margin=0.5
hinge_loss = sim2 - sim1 + 0.5
# 如果问题与最佳答案的相似度比问题与不佳答案的相似度高出0.5,则loss=0
hinge_loss[hinge_loss < 0] = 0
hinge_loss = torch.mean(hinge_loss)
4 Cosine Embedding Loss
在FAQ任务中,我们还可以用Cosine Embedding Loss损失来训练模型。
Cosine Embedding Loss的定义如下方公式所示,
(3)
其中,、为问题的向量表示,为两个问题是否相似的标签。
根据数学知识我们知道,两个向量越相似,两向量的夹角越小,于是值就越大。所以,公式(3)中,当时,我们希望两个向量尽可能地相似,也就是两向量的夹角尽可能地小,于是就大,这样损失就小。相反,当时,我们希望两个向量尽可能地不相似,也就是两个向量的夹角尽可能地大,于是就小,这样损失就小。
公式(3)中对的通俗理解是,当两向量的余弦相似度小于等于时,就认为与不是同义问题。
下面我们通过代码来实现这个损失函数。还是以FAQ任务为例,我们采用Pytorch的CosineEmbeddingLoss()函数。
loss_fn = nn.CosineEmbeddingLoss()
# 用model对输入的两个问题x1、x2进行Encode,输出两个问题的向量表示x1_rep、x2_rep
x1_rep, x2_rep = model(x1, x2)
# target为两个问题是否为同义问题的标签,取值为-1或1
loss = loss_fn(x1_rep, x2_rep, target)
这里需要注意的是,Binary CrossEntropy Loss的标签取值是0或1,而Cosine Embedding Loss的标签取值是-1或1.
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)