代码获取时间为2019.09.30
基本pipeline
文档检索->句子检索->事实验证
-
文档检索:
- 分析句法树得到潜在实体queries
- 使用Wikipedia API查询,得到文档集合,并使用词条名和claim的word overlap进行过滤
-
句子检索:
- 使用ESIM模型,设计了判断句子相关性的任务,损失函数使用Hinge loss: ∑ m a x ( 0 , 1 + s n − s p ) \sum{max(0, 1 + s_n - s_p)} ∑max(0,1+sn−sp),此损失函数,学习目标是负样本相关分数 s n s_n sn尽可能低,正样本相关分数 s p s_p sp尽可能高,这样整体模型loss才会趋于0
- 使用阈值(数量阈值 6 6 6与相关分数阈值 τ \tau τ)过滤
-
事实验证:
-
特征提取(句子建模):基本思想是使用BERT模型[CLS]位置的隐向量作为输入序列的编码,获取evidence文本编码时,输入中也包含了claim,如下:
e i = B E R T ( e i , c ) c = B E R T ( c ) {\rm{e}}_i = BERT(e_i, c) \\ {\rm{c}} = BERT(c) ei=BERT(ei,c)c=BERT(c)相关代码:
# feature_extractor/extractor:87 # # The convention in BERT is: # (a) For sequence pairs: # tokens: [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP] # type_ids: 0 0 0 0 0 0 0 0 1 1 1 1 1 1 # (b) For single sequences: # tokens: [CLS] the dog is hairy . [SEP] # type_ids: 0 0 0 0 0 0 0
通过设置不同标识来区分单句还是双句,之后将tokens转换为bert中的id,并通过1-0 padding得到mask,并丢给Bert预训练模型获取句子表示(特征向量),字典形式保存到文件
这部分了解到的新内容:
模型并行:torch.nn.parallel.DistributedDataParallel,torch.nn.DataParallel(后者是简单的单机多卡,数据并行?)
数据传递:TensorDataset,沿第一个维度取各个传入tensor的数据-
特征加载:主要是对于候选证据个数做了限制,默认阈值为5。不足5条时,做zero-padding
-
模型:分为推理和整合两个阶段,推理是self-attention,整合是采用claim-aware的attention或者max-pooling或mean—pooling
推理中的每一步都是一个全连接attention,每一步即stacking的一层,论文中结果表明,nlayer=2时实验效果略好。由于每层输入输出尺寸都相同(获取的句子表示tensor shape都一样),因此forward的时候,循环实现步进# gear/models.py:58 self.attentions = [AttentionLayer(nins, nfeat) for _ in range(nlayer)] # gear/models.py:76 # 这里input是每个数据组中evidence的表示,claim是单独传入的 for i in range(self.nlayer): inputs = self.attentions[i](inputs)
对于一个数据实例,其包含nins个证据表示,在特定某层推理时,这nins个向量,都要和全体组内向量做attention操作。SelfAttentionLayer输入向量长度应当为nhid * 2,因为论文中进行权重分数计算的公式中,首先对attention的query和key进行了concat,没有使用Dot、general两种方法
p i j = W 1 t − 1 ( R e L U ( W 0 t − 1 ( h i t − 1 ∣ ∣ h j t − 1 ) ) ) p_{ij} = {\rm{W}_{1}^{t-1}}({\rm{ReLU}}({\rm{W}_{0}^{t-1}}({\rm{h}_{i}^{t-1}}||{\rm{h}_{j}^{t-1}}))) pij=W1t−1(ReLU(W0t−1(hit−1∣∣hjt−1)))
h i {\rm{h_i}} hi、 h j {\rm{h_j}} hj、 p i j p_{ij} pij都是列向量# gear/models.py:8 # self.project对应到上面的公式:线性层、ReLU、线性层,对应矩阵运算 class SelfAttentionLayer(nn.Module): def __init__(self, nhid, nins): super(SelfAttentionLayer, self).__init__() self.nhid = nhid self.nins = nins self.project = nn.Sequential( Linear(nhid, 64), ReLU(True), Linear(64, 1) ) # own.repeat是为了下一步的concat操作 # attention.squeeze 将多个单独的权重值降维成为一个weight向量,又升维和inputs相乘 # 得到input中每个向量的加权贡献,求和得到own的attention表示 def forward(self, inputs, index, claims): tmp = None if index > -1: idx = torch.LongTensor([index]).cuda() own = torch.index_select(inputs, 1, idx) own = own.repeat(1, self.nins, 1) tmp = torch.cat((own, inputs), 2) else: claims = claims.unsqueeze(1) claims = claims.repeat(1, self.nins, 1) tmp = torch.cat((claims, inputs), 2) # before attention = self.project(tmp) weights = F.softmax(attention.squeeze(-1), dim=1) outputs = (inputs * weights.unsqueeze(-1)).sum(dim=1) return outputs # gear/models.py:41 # 对每一个query,声明一个attention结构 self.attentions = [SelfAttentionLayer(nhid=nhid * 2, nins=nins) for _ in range(nins)] # gear/models.py:48 # 所有query的attention outputs = torch.cat([self.attentions[i](inputs, i, None) for i in range(self.nins)], dim=1) outputs = outputs.view(inputs.shape)
推理输入输出shape一致,输出的向量是进行过若干层信息传播的句子编码,用claim当query,使用self-attention,得到一个状态向量(或这里选择不再整合claim信息,直接max-pooling、mean-pooling)
# gear/models:65 self.aggregate = SelfAttentionLayer(nfeat * 2, nins) # gear/models:80 inputs = self.aggregate(inputs, -1, claims)
这部分了解到的新内容:
需要掩码操作的时候,torch.index_select可以选择性地挑出需要的某几个维度,一般维度0是batch选择,维度1就是组内数据选择:torch.index_select(batch, 1, some_indices)- 训练:训练的损失函数是NLL(负对数似然),在这里等价于交叉熵
-