train_features = convert_examples_to_features(opt.task_type, train_examples, opt.bert_dir,
opt.max_seq_len, **feature_para)
数据预处理后变这样:
输入:单个样本如下
# token_ids= [101, 1333, 3403,.....,1469, 3856, 102]
# attention_masks = [1, 1, 1,...,1, 1] 有字的为1
# token_type_ids = [0, 0,.......,0, 0] 对单个句子而言全是0 不存在奇偶句
labels = [[0, 0], [0, 0],[1, 0], [0, 1],..,[0, 0], [0, 0]] ,其中[1, 0], [0, 1]是触发词位置 分起始和结束位置
#[0, 0, ....1, 1, ....0, 0] 其中1,1是distant_triggers位置,不分起始和结束位置。
这5个作为输入。
数据预处理:
processor.get_train_examples(train_aux_raw_examples):
set_type = train/dev (训练时train,评估模型是dev)
examples.append(TriggerExample(set_type=set_type,
text=text,
label=tmp_triggers,
distant_triggers=_ex['distant_triggers']))
callback_info.append((text, tmp_triggers, _ex['distant_triggers']))
# (callback_info和examples 的区别为 一个是元组 一个是 对象)
# [('原标题:2019年反腐败“成绩单”亮眼●从数据上看,2019年的反腐败成绩单引人注目,反映了当前反腐败力度仍然没有放松、'
# '反腐败工作仍然按照既定目标推进、高压反腐态势仍然保持,由此查处的中管干部、省管干部这些“关键少数”干部数量仍然保持高位'
# '并呈现增长态势●2019年反腐败主要呈现三个方面的特点:一是精准反腐;二是强化标本兼治;三是深化基层反腐败●'
# '中央针对全面从严治党和党风廉政建设领域,出台了一系列具有代表性的党内法规制度,为整个监察体制改革提供了制度保障,'
# '同时着力建立健全一个具有中国特色的监察法律体系,推动制度反腐、法治防腐制图/李晓军2019年12月26日14时30分,'
# '中央纪委国家监委网站发布了年度最新一条执纪审查消息,“呼和浩特职业学院原党委副书记、院长李怀柱接受纪律审查和监察调查”',
# [('发布', 298)], ['接受', '发布', '推进', '提供', '建立', '推动', '出台', '建设', '调查', '改革'])]
训练时只返回训练集的examples 列表
评估模型时 返回验证集的examples 和 callback_info
数据转化为输入特征:
tokens = fine_grade_tokenize(raw_text, tokenizer)
# tokens = ['原', '标', '题', ':', '2', '0',........'查', '”']
tokenizer = BertTokenizer.from_pretrained(bert_dir)
for i, example in enumerate(tqdm(examples, desc=f'convert examples')):
feature = convert_trigger_example(
ex_idx=i,
example=example,
max_seq_len=max_seq_len,
tokenizer=tokenizer,
)
encode_dict = tokenizer.encode_plus(text=tokens,
max_length=max_seq_len,
pad_to_max_length=True,
is_pretokenized=True,
return_token_type_ids=True,
return_attention_mask=True)
token_ids = encode_dict['input_ids']
attention_masks = encode_dict['attention_mask']
token_type_ids = encode_dict['token_type_ids']
# token_ids= [101, 1333, 3403,.....,1469, 3856, 102]
# attention_masks = [1, 1, 1,...,1, 1] 有字的为1
# token_type_ids = [0, 0,.......,0, 0] 对单个句子而言全是0 不存在奇偶句
# labels = [[0, 0], [0, 0],[1, 0], [0, 1],..,[0, 0], [0, 0]] ,其中[1, 0], [0, 1]是触发词起始结束位置
# [0, 0, ....1, 1, ....0, 0] 其中1,1是distant_triggers位置 训练时该项设置False
feature = TriggerFeature(token_ids=token_ids,
attention_masks=attention_masks,
token_type_ids=token_type_ids,
distant_trigger_label=distant_trigger_label,
labels=labels)
class BaseFeature:
def __init__(self,
token_ids,
attention_masks,
token_type_ids,
labels=None):
self.token_ids = token_ids
self.attention_masks = attention_masks
self.token_type_ids = token_type_ids
self.labels = labels
class TriggerFeature(BaseFeature):
def __init__(self,
token_ids,
attention_masks,
token_type_ids,
distant_trigger_label=None,
labels=None):
super(TriggerFeature, self).__init__(token_ids=token_ids,
attention_masks=attention_masks,
token_type_ids=token_type_ids,
labels=labels)
self.distant_trigger_label = distant_trigger_label
features.append(feature) # 每个样例变成一个对象,然后添加到列表
其中 self.token_ids = token_ids= [101, 1333, 3403,.....,1469, 3856, 102]
self.attention_masks = [1, 1, 1,...,1, 1] 有字的为1
self.token_type_ids = [0, 0,.......,0, 0] 对单个句子而言全是0 不存在奇偶句
self.labels = [[0, 0], [0, 0],[1, 0], [0, 1],..,[0, 0], [0, 0]] ,其中[1, 0], [0, 1]是触发词起始结束位置
self.distant_trigger_label = [0, 0, ....1, 1, ....0, 0] 其中1,1是distant_triggers位置 训练时该项设置False
转化成自动迭代的有bach_size格式:
train_dataset = build_dataset(opt.task_type, train_features, 'train', **dataset_para)
# opt.task_type = trigger
class TriggerDataset(BaseDataset):
def __init__(self,
features,
mode,
use_distant_trigger=False):
super(TriggerDataset, self).__init__(features, mode)
self.distant_trigger = None
if use_distant_trigger:
self.distant_trigger = [torch.tensor(example.distant_trigger_label).long()
for example in features]
def __getitem__(self, index):
data = {'token_ids': self.token_ids[index],
'attention_masks': self.attention_masks[index],
'token_type_ids': self.token_type_ids[index]}
if self.distant_trigger is not None:
data['distant_trigger'] = self.distant_trigger[index]
if self.labels is not None:
data['labels'] = self.labels[index]
return data
train_loader = DataLoader(dataset=train_dataset,
batch_size=opt.train_batch_size,
sampler=train_sampler,
num_workers=8)
for epoch in range(opt.train_epochs):
for step, batch_data in enumerate(train_loader):
model.train()
for key in batch_data.keys():
#key = token_ids / attention_masks / tokem_yype_ids / labels
batch_data[key] = batch_data[key].to(device)
#len(batch_data)=4 分别是token_ids / attention_masks / tokem_yype_ids / labels的值 batch_data[token_ids].shape = [4,320] bsz和句子长度
loss = model(**batch_data)[0] #[0]为loss [1]为预测的logits
模型构建
model = build_model(opt.task_type, opt.bert_dir, **model_para)
self.bert_module = BertModel.from_pretrained(bert_dir)
bert_outputs = self.bert_module(
input_ids=token_ids,
attention_mask=attention_masks,
token_type_ids=token_type_ids
)
seq_out = bert_outputs[0] #seq_out.shape = bert_outputs[0].shape = (4, 320, 768)
# bert_outputs[1].shape = (4, 768)
self.mid_linear = nn.Sequential(
nn.Linear(out_dims, mid_linear_dims),
nn.ReLU(),
nn.Dropout(dropout_prob)
)
# Sequential(
# (0): Linear(in_features=768, out_features=128, bias=True)
# (1): ReLU()
# (2): Dropout(p=0.1, inplace=False)
# )
self.classifier = nn.Linear(mid_linear_dims, 2)
self.criterion = nn.BCELoss()
seq_out = self.mid_linear(seq_out) # [4, 320, 128]
logits = self.activation(self.classifier(seq_out)) # [4, 320, 2]
if labels is not None: #True
loss = self.criterion(logits, labels.float()) #labels.shape = [4, 320, 2] loss = 0.6610
out = (loss,) + out #相当于列表的添加操作
#out 为元组 (loss的值,out的输出)
return out
训练完成到指定步数存储模型,并评估模型:
验证集 也转换为输入特征,最后 pred_logits.shape = (369, 320, 2) #369是全部验证集的总量
设置阈值kwargs = {'start_threshold': 0.5, 'end_threshold': 0.5}
start_ids = np.argwhere(logits[:, 0] > start_threshold)[:, 0] #[:, 0]之前如果是 [[1] 之后变为 [1 3],作用就是变成一行
#[3]]
end_ids = np.argwhere(logits[:, 1] > end_threshold)[:, 0]
# _start 和 _end 自由组合,选出小于 长度为3的组合 放入候选
for _start in start_ids:
for _end in end_ids:
# 限定 trigger 长度不能超过 3
if _end >= _start and _end - _start <= 2:
# (start, end, start_logits + end_logits)
candidate_entities.append((raw_text[_start: _end + 1], _start, logits[_start][0] + logits[_end][1]))
break
情况1:_start 和 _end 自由组合,选出小于 长度为3的组合 放入候选,然后选出一个最大的(起始logits和结束logits的和)。
以下是没有大于阈值或者不满足自由组合的条件即candidate_entities列表为空
情况2 if not len(candidate_entities):
没有的话 就选出 distant_triggers中的标签,每个标签有个起始logits和结束logits的和,然后选出一个最大的(起始logits和结束logits的和)。
情况3:连distant_triggers都没有的话,选取最大的(起始logits和结束logits的和)作为 trigger。
计算F1
gt, predict = [('投资', 31)],[('投资', 31)] -------------> if '投资' == '投资' and 31 == 31 ------------------->预测正确。然后计算P和R 然后计算F1