概述

工具函数

功能 类/函数 描述
计算类别相似度 get_rehearsal_prototype() 根据 memory 和 memory_o 数据集预测的特征和标签计算每个类别的余弦相似度
预测特征 get_token_features_and_labels() 使用上一轮的模型预测特征
预测隐藏状态序列 get_token_encodings_and_labels() 使用上一轮的模型预测最后一层输出端的隐藏状态序列
计算类别的原型 get_exemplar_means() 根据支持集的 encoding 计算每个类别的原型,即均值

准备数据集

功能 类/函数 描述
表示训练/测试样本的对象 InputExample(object) 包含每个样本的编号,单词序列,标签序列
表示样本特征集的对象 InputFeatures(object) 包含每个样本特征的 input_ids,input_mask,segment_ids,label_ids
从文件中读取数据 read_examples_from_file() 返回 InputExample 对象的列表
提取标签集 get_labels_dy() 提取当前任务及之前的所有标签集
提取每个样本的 InputFeatures 对象 convert_examples_to_features 将输入的文本样本(通过 InputExample 对象表示)转换为模型输入的特征表示(通过 InputFeatures 对象表示)
加载并处理数据集样本 load_and_cache_examples() 调用前两个函数从原始数据集中提取特征

训练和评估

功能 类/函数 描述
老师模型函数 teacher_evaluate() 使用老师模型预测 logits 分数和进行重新标记旧实体类
评估及重新标记函数 evaluate() 评估模型性能以及使用原型对评估集进行重新标记
训练函数 train() 训练模型,更新参数
训练和评估函数 train_and_eval() 调用 teacher_evaluate(),evaluate() 和 train() 进行训练和评估

损失函数

功能 类/函数 描述
监督对比损失函数 SupConLoss() 实体的监督对比损失函数
监督对比损失函数 SupConLoss_o() 实体和“O”的联合监督对比损失函数

自定义模型

MySftBertModel()

主函数

main()

工具函数


get_rehearsal_prototype()

参数说明:

get_rehearsal_prototype(args, model, tokenizer, labels, pad_token_label_id, mode, data_dir)

  • labels:当前任务及之前的所有标签集
  • pad_token_label_id:PAD 标记的索引

用法:

  • 加载当前任务的 memory 和 memory_o 数据集,并用当前模型预测特征和标签。
  • 根据预测特征和标签计算当前任务及之前的所有类别的余弦相似度。

测试:对 task0 的 memory 和 memory_o 数据集进行测试:

get_token_features_and_labels()

参数说明:

get_token_features_and_labels(args, model, batch)

  • model:当前加载的上一轮的模型
  • btach:要预测的批次

功能:

使用上一轮的模型预测特征,并提取出原数据集的标签。

get_token_encodings_and_labels()

功能:

同 get_token_features_and_labels(),使用上一轮的模型预测最后一层输出端的隐藏状态序列,并提取出原数据集的标签。

get_exemplar_means()

功能:

将每个 label 的所有样本的 encoding 取均值后归一化作为该类别的原型。

准备数据集


InputExample(object)

表示一个训练或测试的样本的对象,该类包含以下属性:

  • guid:句子的编号,用作句子的唯一标识符。
  • words:句子中的单词序列,以列表形式存储。
  • labels:(可选)序列中每个单词的标签,以列表形式存储。对于测试句子,不需要提供标签列表。

InputFeatures(object)

表示一个样本特征集的对象,该类包含以下属性:

  • input_ids:表示单词在词汇表中的索引,是模型输入的主要部分。
  • input_mask:用于指示哪些部分是真实的输入,哪些部分是填充的。
  • segment_ids:在BERT处理句子对中,用于区分不同句子的标识,0表示属于第一个句子,1表示属于第二个句子。
  • label_ids:标签的索引。

get_labels_dy()

get_labels_dy(path, per_types, step_id)

参数说明:

  • path:标签集的存储路径
  • per_types:每个任务要学习的标签数量
  • step_id:当前任务的索引

功能:

返回包含当前任务及之前的所有标签的列表。

read_examples_from_file()

参数说明:

read_examples_from_file(data_dir, mode)

  • data_dir: 数据文件存储的目录,文件读取的路径格式为”data_dir/{mode}.txt”
  • mode:根据不同的模式读取不同的样本
    • train:读取 train.txt。
    • memory:读取 memory.txt。
    • dev:读取 dev.txt。
    • rehearsal:同时读取 train.txt 和 memory.txt,并将它们合并成一个列表返回。

用法:

  • 使用 os.path.join 构造文件路径,该路径由 data_dir 和 mode 组成。
  • 打开文件并逐行读取数据。
  • 对于每一行:
    • 如果行以 “-DOCSTART-“ 开头或为空行,则表示前一个句子的结束。此时,将累积的words和labels作为一个样本添加到 examples 列表中,并清空 words 和 labels 列表。
    • 否则,将行按制表符分割,获取单词和标签,并将它们添加到 words 和 labels 列表中。
  • 返回 examples 列表,其中每个元素都是一个 InputExample 对象,代表一个句子。

convert_examples_to_features()

参数说明:

1
2
3
convert_examples_to_features(examples, label_list, max_seq_length, tokenizer, cls_token_at_end=False, cls_token="[CLS]", 
cls_token_segment_id=1, sep_token="[SEP]", sep_token_extra=False, pad_on_left=False, pad_token=0,
pad_token_segment_id=0, pad_token_label_id=-1, sequence_a_segment_id=0, mask_padding_with_zero=True):
  • examples:经过 read_example_from_file() 函数处理的 InputExample 对象列表
  • label_list:当前增量学习任务的标签
  • max_seq_length:tokenization 后的最大序列长度,比这个长的会被截断,比这个短的会被填充。
  • cls_token_at_end:是否在序列最后加一个 CLS token:
    • False (Default, BERT/XLM pattern): [CLS] + A + [SEP] + B + [SEP]
    • True (XLNet/GPT pattern): A + [SEP] + B + [SEP] + [CLS]
  • cls_token_segment_id:定义关于 CLS token 的段id (0 for BERT, 2 for XLNet)。
  • sep_token_extra:是否在结尾再添加 SEP token( for RoBERTa pattern)。
  • pad_on_left:是否在句子左边填充 PAD。

用法

  • 将增量学习任务的标签从0编号,构建标签到索引映射 label_map。
  • 遍历每个 example:
    • 使用分词器 tokenizer 对句子的每个单词进行分词,并在 label_ids 列表中添加它们的标签编号。
    • 根据序列的长度和特殊标记的数量,截断序列,使其长度符合 max_seq_length 的要求。
    • 添加特殊标记 [CLS] 和 [SEP],以及相应的标签编号。
    • 构建 segment_ids (全为0) 表示句子的段落编号。
    • 构建 input_ids 列表,将 tokens 序列映射为其对应的 ID。
    • 构建输入掩码 input_mask,1 for real tokens and 0 for padding tokens。
    • 对不足长度的序列进行填充。
    • 将每个 example 的处理结果存储为一个 InputFeatures 对象,并添加到 features 列表中。
  • 返回 features 列表。

load_and_cache_examples()

参数说明:

load_and_cache_examples(args, tokenizer, labels, pad_token_label_id, mode, data_dir)

  • tokenizer:分词器
  • labels: 当前增量学习任务的标签
  • pad_token_label_id:PAD 位置的掩码
  • mode:指定当前的模式,可能为 train,dev,rehears。
  • data_dir: 数据文件存储的目录。

data_dir,mode 传入 read_examples_from_file 函数读取样本。
tokenizer,传入 convert_examples_to_features 函数将样本转换为特征。

功能:

  1. 调用 read_examples_from_file() 和 convert_examples_to_features() 函数,根据不同的模式从 data_dir 中读取样本并转换为特征,如果是’rehearsal’模式,则读取 train.txt 和 memory.txt,如果是开发,训练,测试模式,则读取相应的dev.txt,train.txt,test.txt。
  2. 如果是分布式训练的非主进程,直接从缓存文件中加载特征。
  3. 返回一个 TensorDataset 对象 dataset(all_input_ids, all_input_mask, all_segment_ids, all_label_ids)。

测试

对 task0 的训练集的句子进行测试:
获取标签集

1
2
labels = get_labels_dy(path="../data/labels.txt", per_types=6, step_id=0)
labels

输出

1
2
3
4
5
6
7
['O',
'building-library',
'organization-showorganization',
'other-award',
'building-other',
'organization-religion',
'organization-sportsteam']

运行 load_and_cache_examples() 函数

1
train_dataset = load_and_cache_examples(args, tokenizer, labels, pad_token_label_id, mode=mode, data_dir=data_dir)

输出日志()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
03/28/2024 13:38:28 - INFO - __main__ -   Creating features from dataset file at ../data/tasks/task_0
03/28/2024 13:38:28 - INFO - util.supervised_util - Writing example 0 of 100
03/28/2024 13:38:28 - INFO - util.supervised_util - *** Example ***
03/28/2024 13:38:28 - INFO - util.supervised_util - guid: train-1
03/28/2024 13:38:28 - INFO - util.supervised_util - tokens: [CLS] this rivalry intensified in 1919 when arsenal were unexpectedly promoted to the first division , taking a place that tottenham believed should be theirs . [SEP]
03/28/2024 13:38:28 - INFO - util.supervised_util - input_ids: 101 2023 10685 15767 1999 4529 2043 9433 2020 14153 3755 2000 1996 2034 2407 1010 2635 1037 2173 2008 18127 3373 2323 2022 17156 1012 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - input_mask: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - segment_ids: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - label_ids: -100 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100
03/28/2024 13:38:28 - INFO - util.supervised_util - *** Example ***
03/28/2024 13:38:28 - INFO - util.supervised_util - guid: train-2
03/28/2024 13:38:28 - INFO - util.supervised_util - tokens: [CLS] only one of four entered corvette ##s - gt ##1 c ##6 ##r of luc alpha ##nd ave ##nt ##ures - eventually finished the race , taking second place in class . [SEP]
03/28/2024 13:38:28 - INFO - util.supervised_util - input_ids: 101 2069 2028 1997 2176 3133 22687 2015 1011 14181 2487 1039 2575 2099 1997 12776 6541 4859 13642 3372 14900 1011 2776 2736 1996 2679 1010 2635 2117 2173 1999 2465 1012 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - input_mask: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - segment_ids: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
03/28/2024 13:38:28 - INFO - util.supervised_util - label_ids: -100 0 0 0 0 0 0 -100 0 0 -100 0 -100 -100 0 6 6 -100 6 -100 -100 0 0 0 0 0 0 0 0 0 0 0 0 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100
......仅展示前两个句子
03/28/2024 13:38:28 - INFO - __main__ - Saving features into cached file ../data/tasks/task_0\cached_rehearsal_output_nerd_128

训练和评估

teacher_evaluate()

用法:

  1. 使用老师模型,即上一轮的模型预测训练集的 logits_list 和获取真实标签 out_labels
  2. 调用 evaluate(mode=”rehearsal”) 获取训练集的 preds, emissions, out_label_ids, 以及重新标记阈值 prototype_dists
  3. 重新标记旧实体类:对于训练集的每个样本,如果它的原型的相似度大于重新标记阈值,则将该样本预测为这个旧实体类,否则保持原来的预测标签不变。
  4. 返回 logits_list 和重新标记过的标签列表 out_label_new_list

evaluate()

用法:

  1. 加载数据集:调用 load_and_cache_examples() 根据不同模式加载评估集,support 集(提取实体样本),support_o 集(提取“O”样本),训练集。
  2. 计算类别原型:
  • 使用上一轮模型预测支持集的 encoding 和label。
  • 调用 get_exemplar_means() 根据支持集的 encoding 和label 计算每个类别的原型。
  1. 调用 get_token_encodings_and_labels() 使用上一轮模型预测评估集的 encoding 和 标签 out_label_ids
  2. 如果是’rehearsal’模式,则剔除掉支持集中当前任务标签的样本,并调用 NNClassification 的 nn_classifier_dot_prototype() 计算:
  • preds:评估集每个样本中原型相似度最大值所在的类别索引
  • emissions:评估集每个样本与每个类别的原型相似度的最大值
  • prototype_dists:根据支持集计算的每个旧类别的原型重新标记阈值列表
  1. 如果是’rehearsal’模式,则直接返回 preds, emissions, out_label_ids, prototype_dists
  2. out_label_idspreds 的标签从索引形式转换为真实的字符串形式。
  3. 将评估集的 out_label_list 作为真实标签,将 preds_list 作为预测标签,使用 seqeval 库计算 F1 分数 macro_results,
    micro_results
  4. 返回 macro_resultsmicro_resultspreds_list

train()

参数说明:

train(args, train_dataset, train_dataloader, model, tokenizer, labels, pad_token_label_id, data_dir, output_dir, t_logits, out_new_labels):

  • train_dataset:训练集
  • train_dataloader:训练集加载器
  • labels:当前任务及之前的所有真实标签集
  • pad_token_label_id:PAD 标记的索引
  • data_dir:输入数据集的文件路径
  • output_dir:保存模型的检查点的文件路径
  • t_logits:老师模型预测的 logits 分数
  • out_new_labels:老师模型预测的标签

用法:

参数配置
  1. 设置训练总步数 t_total(用于传入 warmup 中):
  • 通过 args.max_steps 可以指定 t_total,并覆盖 arg.num_train_epochs
  • 否则通过 t_total = len(train_dataloader) // args.gradient_accumulation_steps * args.num_train_epochs 计算 t_total。
  1. 配置优化器:使用AdamW优化器,使用了权重衰减,学习率调节器。
  • args.learning_rate(default=5e-5) 和 args.adam_epsilon(default=1e-8) 可修改AdamW的学习率和模糊因子。
  • args.warmup_steps(default=0) 可修改 warmup 的初始预热步数。
  1. 根据 args.fp16 选择是否启用混合精度训练。
训练模型:

迭代每一轮:

  1. 如果当前轮次 >= args.start_train_o_epoch
  2. 如果是第一个任务,使用原始数据集的标签。
  3. 如果不是第一个任务,使用老师模型 tearch_evaluate() 预测 logits 分数 t_logits 和标签 out_new_labels
  4. 模型前向传播,获取模型输出的损失值。
  5. 模型反向传播。
  6. 更新参数
  7. 调用 evaluate() 的开发模式对开发集进行评估
  8. 达到 args.save_steps 时保存 checkpoint 文件和模型。
  9. 返回训练总步数和平均损失值。

train_and_eval()

参数说明:

train_and_eval(args, labels, num_labels, pad_token_label_id, model_name_or_path, output_dir, data_dir, step_id)

  • labels:当前任务及之前的所有标签集
  • num_labels:labels 的数量
  • pad_token_label_id:PAD 标记的索引
  • model_name_or_path:加载模型的路径
  • output_dir:输出结果的文件路径
  • data_dir:读取数据集的文件路径
  • step_id:当前任务的 id

用法:

  1. 加载上一个任务的模型参数,分词器,模型。
  2. 调用 load_and_cache_examples(mode=”rehearsal”) 加载训练集(由当前任务的 train.txt 和 memory.txt 组成)
  3. 如果不是第一个任务,则调用 teacher_evaluate(mode=”train”) 使用老师模型预测训练集的 logits 分数 t_logitsout_new_labels
  4. 调用 train() 进行训练。
  5. 存储模型的参数,分词器,模型。
  6. 如果 args.do_eval 为 Ture,对每个 checkpoint 调用 evaluate() 的开发模式对开发集进行评估,并存储进 eval_results.txt 文件。
  7. 如果 args.do_predict 为 Ture,调用 evaluate() 的测试模式对测试集进行评估和预测,并保存评估和预测结果。

损失函数

SupConLoss()

init():

参数说明:

init(self, temperature=0.07, contrast_mode=’all’, base_temperature=0.07, topk_th=False)

  • temperature:温度参数,用于调整对比损失函数中余弦相似度的尺度。
  • contrast_mod:
  • base_temperature:基准温度参数,用于调整对比损失函数的基准尺度。
  • topk_th:

forward():

参数说明:

forward(self, features, labels=None, mask=None, entity_topk=None, ignore_index=CrossEntropyLoss(), per_types=6, aug_feature=None)

  • mask:对比损失的掩码,同一标签的样本设为1,不同为0。
  • entity_topk: 实体 Top K,表示每个类别的前 K 个样本在对比中的重要性。
  • ignore_index: 忽略索引,默认为 CrossEntropyLoss(),表示在计算损失时需要忽略的标签索引。
用法:
  1. 根据输入的 labelsmask 构建 mask:不同标签的设为0,与自身的设为0,ignore_index 所对应的行设为0,“O”样本所在行设为0。
  2. 选择 Anchor:根据 contrast_mode 选择 anchor 样本,如果 contrast_mode 为 ‘one’, 则将第一个样本设为 anchor,如果 contrast_mode 为 ‘all’, 则将所有样本设为 anchor。
  3. 计算对比损失的 Logits:计算 anchor 和其他样本之间的余弦相似度。
  4. entity_topk 非空: 将 mask 中所有样本与 entity_topk样本设为1,与当前任务训练集的“O”样本设为 0。
  5. 根据监督对比损失函数公式计算损失值,返回所有 anchor 的平均损失值。

模型

MySftBertModel()

forward():

  1. 使用 BertModel 模型前向传播
  2. 提取特征 features :将 ast_hidden_state 通过一个 mlp(Linear-ReLU-Linear) 层变换并归一化后赋给 features
  3. 提取 logits:将 last_hidden_state 依次通过dropout 层,线性分类器层后赋给 logits
  4. 如果是非训练模式,直接返回 loss, features_enc, features, logits,其中 loss = Nonefeatures_enc = last_hidden_state
  5. 计算损失值 loss。:
    • 根据 loss_name 计算损失函数的值。
    • 如果不是第一个任务,计算当前任务样本的 logits 与真实标签的交叉熵损失,当前模型与老师模型 logits 的KL散度。将交叉熵损失或KL散度添加到 loss 中。
  6. 返回 loss, features_enc, features, logits。

主函数

main()

功能:

  • 创建解释器,添加命令行参数。
  • 迭代每个任务,调用 get_labels_dy() 获取当前任务及之前的所有标签集 labels,然后调用 train_and_eval() 进行训练和评估。