一 数据预处理
HMEAE中数据预处理很有特色,特别是论元数据预处理。
样本数据
1.HMEAE中样本和DMCNN中一样,根据候选触发词的思路来划分样本,也就是对于一个句子,将每个词逐个视为候选触发词,每次对应一个样本。
2.另一点比较重要的是,每个样本中记录中句子中的所有命名实体,并且为实体加上论元标签。如果,当前样本中的候选触发词是真正的触发词,那么实体的论元标签就是各实体在该触发词下的论元角色。如果,样本中候选触发词不是触发词,那么实体的论元标签都为None。
3.上述的原始数据可以直接作为ED的输入,AE的输入要进一步处理。先用ED筛选触发词标签正确的样本,然后依次将样本中的实体作为候选论元,因此衍生出多个样本。
这里有两个问题:
1.一个词在多个触发词中充当不同的角色;
2.一个词在一个触发词中充当多个角色;
在这两种情况在ACE2005中有标注吗?
第一个情况,按照文章的说法是能够覆盖的,样本中还没专门去找看看,如果ACE2005本身标注了,那么不成问题。
第二个情况,文章是没有处理的。
利用ED预测结果筛选AE数据集
# 训练ED模型;并生成AE的dev、test数据集
def train_trigger(self):
train, dev, test = self.t_train, self.t_dev, self.t_test
...
with tf.Session() as sess:
... 训练并保存得到的ED模型
# 生成AE的数据集:a_data_process = (train,dev,test)
# train直接来源于原始数据
# dev、test是基于ED模型,将ED预测的事件类型加入到数据集中,构成新的dev、test
# dev、test比train多了预测事件类型这一列
a_data_process = self.predict_trigger(sess)
return a_data_process
# 加载ED模型,预测dev、test中的事件类型
def predict_trigger(self, sess):
print('--Predict Trigger For Argument Stage--')
# 注意:这里的输入不是t_data,而是a_data了
# 从更前面的数据预处理可以看到,这里train、dev、test的产生方式是不一样的
# train是根据gold标签过滤了事件类型为None的样本;dev、test并没有过滤,产生方式和ED部分完全一样
# dev、test是在这里根据ED预测标签来过滤事件类型为None的样本
# 那么有个疑问,为啥train使用gold过滤,而dev、test使用ED预测结果过滤?
# 个人理解:这完全是为了和之前的DMCNN具有可比性,或者说与ED/AE联合模型具有可比性,不过话说回来,DMCNN是联合模型吗?AE部分是用ED预测值作为trigger标签的吗?
# 如果是的话,就说得通;还是说是流水线模型,训练的时候用的是gold值,那也不会dev、test用ED预测值吧,搞不懂,再看看吧
# 不过也说明了,可比性这玩意很扯啊,这里作者实现了DMCNN的ED部分,却没有直接拿触发词标签真实值单独测一下DMCNN的AE部分,然后比较
# 因为DMCNN没有官方开源的代码,你自己实现的可能说服力不够吧
train, dev, test = self.a_train, self.a_dev, self.a_test
# tf 加载模型
saver = tf.train.Saver()
saver.restore(sess, "saved_models/trigger.ckpt")
# dev、test中的事件类型
dev_pred_event_types = []
test_pred_event_types = []
# dev_pred_event_types、test_pred_event_types 记录ED预测的事件类型,注意这里面包括None的
for batch in get_batch(dev, constant.t_batch_size, False):
pred_label = list(sess.run(self.pred_label, feed_dict=get_argument_feeddict(self, batch, False, 'trigger')))
dev_pred_event_types.extend(pred_label)
for batch in get_batch(test, constant.t_batch_size, False):
pred_label = list(sess.run(self.pred_label, feed_dict=get_argument_feeddict(self, batch, False, 'trigger')))
test_pred_event_types.extend(pred_label)
return self.process_data_for_argument(np.array(dev_pred_event_types, np.int32),
np.array(test_pred_event_types, np.int32))
# 使用ED预测结果过滤掉dev、test中事件类型为None的样本,得到新的dev、test
def process_data_for_argument(self, dev_pred_event_types, test_pred_event_types):
print('--Preprocess Data For Argument Stage--')
# 这里train、dev、test还是原始数据
train, dev, test = list(self.a_train), list(self.a_dev), list(self.a_test)
# 下面的两个操作导致,dev、test比train多了一列;ED预测的事件类型
# 注意啦:这里往dev中多加了一列dev_pred_event_types;这个表示ED预测的事件类型(还是很别扭,预测的不是触发词对应的事件类型,而是事件类型;因为逐个词作为候选触发词);
dev = list(dev)
dev.append(dev_pred_event_types)
dev = tuple(dev)
# 注意啦:同上,这里往test中多加了一列test_pred_event_types;
test = list(test)
test.append(test_pred_event_types)
test = tuple(test)
dev_slices = []
# 这里删除掉预测事件类型为None的样本
for idx, event_type in enumerate(list(dev_pred_event_types)):
if event_type != 0:
dev_slices.append(idx)
test_slices = []
# 这里删除掉预测事件类型为None的样本
for idx, event_type in enumerate(list(test_pred_event_types)):
if event_type != 0:
test_slices.append(idx)
# 这个意思是说,只把ED预测的事件类型不为None的样本筛选出来,作为新的dev、test
# 有个问题啊,gold不为None但是ED预测为None的样本也被过滤了,有影响吗?
# 没有影响,因为这里筛选只是减少了dev、test样本的数量,也就是减少了数据集的数量,
# 而不是改变AE模型预测的结果,所以没有不影响
dev = [np.take(d, dev_slices, axis=0) for d in dev]
test = [np.take(d, test_slices, axis=0) for d in test]
return train, dev, test
返回AE数据集
# tf中这里才是模型的输入,将batch中数据和计算图中变量名对应上
def get_argument_feeddict(model,batch,is_train=True,stage='trigger'):
if stage=='trigger':
sents,event_types,roles,maskl,maskm,maskr,\
trigger_lexical,argument_lexical,trigger_maskl,trigger_maskr,trigger_posis,argument_posis = batch
return get_trigger_feeddict(model,(trigger_posis,sents,trigger_maskl,trigger_maskr,event_types,trigger_lexical),False)
elif stage=="argument":
# 注意看:预测时比训练时多了一个pred_event_types,也就是说batch不一样了;
if is_train:
sents,event_types,roles,maskl,maskm,maskr,\
trigger_lexical,argument_lexical,trigger_maskl,trigger_maskr,trigger_posis,argument_posis = batch
# 对应模型中的变量名:
# sents、trigger_posis、argument_posis、maskls、maskms、maskrs、trigger_lexical、argument_lexical、
# _labels、is_train、event_types
# 需要注意的是:数据中的roles在模型中对应_labels
return {model.sents:sents,model.trigger_posis:trigger_posis,model.argument_posis:argument_posis,
model.maskls:maskl,model.maskms:maskm,model.maskrs:maskr,
model.trigger_lexical:trigger_lexical,model.argument_lexical:argument_lexical,
model._labels:roles,model.is_train:is_train,model.event_types:event_types}
else:
sents,event_types,roles,maskl,maskm,maskr,\
trigger_lexical,argument_lexical,trigger_maskl,trigger_maskr,trigger_posis,argument_posis,pred_event_types = batch
# 这个也是因垂丝汀啊:把pred_event_types单独提出来了
return pred_event_types,{model.sents:sents,model.trigger_posis:trigger_posis,model.argument_posis:argument_posis,
model.maskls:maskl,model.maskms:maskm,model.maskrs:maskr,
model.trigger_lexical:trigger_lexical,model.argument_lexical:argument_lexical,
model._labels:roles,model.is_train:is_train,model.event_types:pred_event_types}
else:
raise ValueError("stage could only be trigger or argument")
X 其他
注意:这个代码里面没有使用BIO标签,DMCNN里面用了吗?论文没看到说明,代码没开源;
名词作为触发词的例子:
'CNN_CF_20030303.1900.00'
['He', 'lost', 'an', 'election', 'to', 'a', 'dead', 'man']
从这个代码里面反思:
1.word2idx什么时候执行?
可以在产生dataset时、从dataloader中获取batch时、forward输入时;放在这些地方都是可以的,到底放在哪里更好?这份代码放在了,dataloader中;一般来说,对数据的预处理也确实也是放在dataloader中,比如pad;JMEE放在dataset中了;之所以放在dataloader之中或者之前,是为了方便后面pad。