FID(Fusion-in-Decoder models)源码笔记

源码

源码:https://github.com/facebookresearch/FiD

目录

数据集

NaturalQuestions 和 TriviaQA 数据可以使用  get-data.sh  下载。两个数据集都从原始来源获得,维基百科转储是从 DPR存储库下载的。除了问题和答案之外,此脚本还检索用于训练已发布的预训练模型的 Wikipedia 段落。

Dense Passage Retrieval (DPR) – is a set of tools and models for state-of-the-art open-domain Q&A research. It is based on the following paper:

Vladimir Karpukhin, Barlas Oguz, Sewon Min, Patrick Lewis, Ledell Wu, Sergey Edunov, Danqi Chen, Wen-tau Yih. Dense Passage Retrieval for Open-Domain Question Answering. Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing (EMNLP), pages 6769–6781, 2020.

数据格式

预期的数据格式是 list 示例列表,其中每个条目示例是字典包含

  • id:例子的id,可选
  • question:问题文本
  • target:用于模型训练的答案,如果没有给出,则从“answer”列表中随机抽取目标
  • answers:用于评估答案的文本列表,如果没有给出目标,也可以用于训练
  • ctxs:是一个文章列表,其中每一项都是包含   – title:文章标题    – 文本:段落文本

实例:

{
  'id': '0',
  'question': 'What element did Marie Curie name after her native land?',
  'target': 'Polonium',
  'answers': ['Polonium', 'Po (chemical element)', 'Po'],
  'ctxs': [
            {
                "title": "Marie Curie",
                "text": "them on visits to Poland. She named the first chemical element that she discovered in 1898 \"polonium\", after her native country. Marie Curie died in 1934, aged 66, at a sanatorium in Sancellemoz (Haute-Savoie), France, of aplastic anemia from exposure to radiation in the course of her scientific research and in the course of her radiological work at field hospitals during World War I. Maria Sk\u0142odowska was born in Warsaw, in Congress Poland in the Russian Empire, on 7 November 1867, the fifth and youngest child of well-known teachers Bronis\u0142awa, \"n\u00e9e\" Boguska, and W\u0142adys\u0142aw Sk\u0142odowski. The elder siblings of Maria"
            },
            {
                "title": "Marie Curie",
                "text": "was present in such minute quantities that they would eventually have to process tons of the ore. In July 1898, Curie and her husband published a joint paper announcing the existence of an element which they named \"polonium\", in honour of her native Poland, which would for another twenty years remain partitioned among three empires (Russian, Austrian, and Prussian). On 26 December 1898, the Curies announced the existence of a second element, which they named \"radium\", from the Latin word for \"ray\". In the course of their research, they also coined the word \"radioactivity\". To prove their discoveries beyond any"
            }
          ]
}

预训练模型

预训练模型的下载可以用: get-model.sh. 现在可用的模型有 [nq_reader_base, nq_reader_large, nq_retriever, tqa_reader_base, tqa_reader_large, tqa_retriever].

bash get-model.sh -m model_name

预训练模型的性能:

FID(Fusion-in-Decoder models)源码笔记

 Fusion-in-Decoder

Fusion-in-Decoder 训练用 train_reader.py and 评估用 test_reader.py.

训练

train_reader.py 为训练模型的代码. 使用实例如下:

python train_reader.py \
        --train_data train_data.json \
        --eval_data eval_data.json \
        --model_size base \
        --per_gpu_batch_size 1 \
        --n_context 100 \
        --name my_experiment \
        --checkpoint_dir checkpoint \

用 100 个段落训练这些模型是内存密集型的。为了缓解这个问题,使用带有 –use_checkpoint 选项的检查点。可变大小的张量会导致内存开销。编码器输入张量默认具有固定大小,但解码器输入张量没有。解码器端的张量大小可以使用 –answer_maxlength 来固定。大型阅读器已在 64 个 GPU 上接受了以下超参数的训练:

python train_reader.py \
        --use_checkpoint \
        --lr 0.00005 \
        --optim adamw \
        --scheduler linear \
        --weight_decay 0.01 \
        --text_maxlength 250 \
        --per_gpu_batch_size 1 \
        --n_context 100 \
        --total_step 15000 \
        --warmup_step 1000 \

测试

使用 test_reader.py 评估模型或预训练模型。下面提供了该脚本的示例用法。

python test_reader.py \
        --model_path checkpoint_dir/my_experiment/my_model_dir/checkpoint/best_dev \
        --eval_data eval_data.json \
        --per_gpu_batch_size 1 \
        --n_context 100 \
        --name my_test \
        --checkpoint_dir checkpoint \

src

slurm.py 资源调度管理

util.py 配置管理

evaluation.py

data.py  数据管理

model.py

index.py

preprocess.py 对数据集预处理main

options.py 参数管理

slurm.py

资源调度管理

def sig_handler(signum, frame):  # 获取内部环境变量


def term_handler(signum, frame):


def init_signal_handler():
    """
    Handle signals sent by SLURM for time limit / pre-emption.
    处理资源调度管理中发送的时间限制/预先空置
    调用前两个函数
    """

def init_distributed_mode(params): #params有{is_slurm_job, local_rank, is_main, node_id, multi_node, n_nodes, multi_gpu, world_size, global_rank, is_distributed, device}
    """
    Handle single and multi-GPU / multi-node / SLURM jobs.
    处理单个或多个gpu/多节点/资源调度工作
    Initialize the following variables:
    初始化以下变量
        - n_nodes
        - node_id
        - local_rank
        - global_rank
        - world_size
    """

PyTorch分布式DPP的基本概念(并行训练)

node
物理节点,就是一台机器,节点内部可以有多个GPU(一台机器有多卡)。

rank & local_rank
用于表示进程的序号,用于进程间通信。每一个进程对应了一个rank。

rank=0的进程就是master进程。

local_rank: rank是指在整个分布式任务中进程的序号;local_rank是指在一台机器上(一个node上)进程的相对序号,例如机器一上有0,1,2,3,4,5,6,7,机器二上也有0,1,2,3,4,5,6,7。local_rank在node之间相互独立。

nnodes
物理节点数量

node_rank
物理节点的序号

nproc_per_node
每个物理节点上面进程的数量。

group

进程组。默认只有一个组

world size
全局的并行数

全局(一个分布式任务)中,rank的数量。

每个node包含16个GPU,且nproc_per_node=8,nnodes=3,机器的node_rank=5,请问world_size是多少? 答案:world_size = 3*8 = 24
————————————————
版权声明:本文为CSDN博主「hxxjxw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hxxjxw/article/details/119606518

util.py

配置管理

def init_logger(is_main=True, is_distributed=False, filename=None): #返回日志

def get_checkpoint_path(opt):  #opt: {checkpoint_dir, name, is_distributed}
    return checkpoint_path, checkpoint_exists


def symlink_force(target, link_name):  #创建软链接,失败则返回错误

def save(model, optimizer, scheduler, step, best_eval_metric, opt, dir_path, name): #保存模型

def load(model_class, dir_path, opt, reset_params=False): #加载模型
    return model, optimizer, scheduler, opt_checkpoint, step, best_eval_metric


class WarmupLinearScheduler(torch.optim.lr_scheduler.LambdaLR):
    def __init__(self, optimizer, warmup_steps, scheduler_steps, min_ratio, fixed_lr,             
 last_epoch=-1):
        self.warmup_steps = warmup_steps
        self.scheduler_steps = scheduler_steps
        self.min_ratio = min_ratio
        self.fixed_lr = fixed_lr
        super(WarmupLinearScheduler, self).__init__(
            optimizer, self.lr_lambda, last_epoch=last_epoch
        )

    def lr_lambda(self, step):#返回学习率

class FixedScheduler(torch.optim.lr_scheduler.LambdaLR):
    def __init__(self, optimizer, last_epoch=-1):
        super(FixedScheduler, self).__init__(optimizer, self.lr_lambda, last_epoch=last_epoch)
    def lr_lambda(self, step):
        return 1.0

def set_dropout(model, dropout_rate): #设置dropout

def set_optim(opt, model):  #设置优化器
    return optimizer, scheduler

def average_main(x, opt):  #参数opt: {is_distributed, world_size}
    return x

def sum_main(x, opt):
    return x

def weighted_average(x, count, opt): #参数opt: {is_distributed, device, world_size}
    return x, count
    return (t_loss / t_total).item(), t_total.item()

def write_output(glob_path, output_path): #参数glob_path: {glob, rmdir},将output写入txt文件

def save_distributed_dataset(data, opt):  #参数opt: {checkpoint_dir, name, global_rank, is_distributed, is_main},保存数据分数在'dataset_wscores.json'文件中,

def load_passages(path):
    return passages

线性学习率预热,学习率从0线性(也可非线性)增加到优化器中的初始预设lr,之后使其学习率从优化器中的初始lr线性降低到0

1. warmup是什么?

  • Warmup是针对学习率优化的一种方式,Warmup是在ResNet论文中提到的一种学习率预热的方法,它在训练开始的时候先选择使用一个较小的学习率,训练了一些epoches,再修改为预先设置的学习率来进行训练。

2. 为什么要使用 warmup?

  1. 在实际中,由于训练刚开始时,训练数据计算出的梯度 grad 可能与期望方向相反,所以此时采用较小的学习率 learning rate,随着迭代次数增加,学习率 lr 线性增大,增长率为 1/warmup_steps;迭代次数等于 warmup_steps 时,学习率为初始设定的学习率;
  2. 另一种原因是由于刚开始训练时,模型的权重(weights)是随机初始化的,此时若选择一个较大的学习率,可能带来模型的不稳定(振荡),选择Warmup预热学习率的方式,可以使得开始训练的几个epoches内学习率较小,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。
  3. 迭代次数超过warmup_steps时,学习率逐步衰减,衰减率为1/(total-warmup_steps),再进行微调。
  4. 刚开始训练时,学习率以 0.01 ~ 0.001 为宜, 接近训练结束的时候,学习速率的衰减应该在100倍以上

3. 如何实现warmup?

  • num_train_optimization_steps为模型参数的总更新次数
    一般来说:
  • t_total 是参数更新的总次数,首先是如果设置了 梯度累积trick会除 gradient_accumulation_steps ,然后乘上 训练 epoch 得到最终的更新次数
  • 下面俩例子区别是 len(train_dataloader)=int(total_train_examples) / .train_batch_size 实际上是一样的
num_train_optimization_steps = int(total_train_examples / args.train_batch_size / args.gradient_accumulation_steps)
scheduler = WarmupLinearSchedule(optimizer, warmup_steps=args.warmup_steps, t_total=num_train_optimization_steps)
======================================================================================================
t_total = len(train_dataloader) / args.gradient_accumulation_steps * args.num_train_epochs
scheduler = WarmupLinearSchedule(optimizer, warmup_steps=args.warmup_steps, t_total=t_total)

3. warmup 方法的优势:

  • 有助于减缓模型在初始阶段对mini-batch的提前过拟合现象,保持分布的平稳
  • 有助于保持模型深层的稳定性

4. optimizer.step()和scheduler.step()的区别

  • optimizer.step()通常用在每个mini-batch之中,而scheduler.step()通常用在epoch里面,但是不绝对,可以根据具体的需求来做。只有用了optimizer.step(),模型才会更新,而scheduler.step()是对lr进行调整。

作者:三方斜阳
链接:https://www.jianshu.com/p/1c875d25ce78
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

evaluation.py

"""
Evaluation code from DPR: https://github.com/facebookresearch/DPR
"""
class SimpleTokenizer(object):
    return tokens

QAMatchStats = collections.namedtuple('QAMatchStats', ['top_k_hits', 'questions_doc_hits'])
注释如下

def calculate_matches(data: List, workers_num: int):
注释如下


def check_answer(example, tokenizer) -> List[bool]:
    """Search through all the top docs to see if they have any of the answers."""


def has_answer(answers, text, tokenizer) -> bool:
    """Check if a document contains an answer string."""

       由于元组不像字典那样可以为内部的元素命名,因此我们并不知道元组内的元素所表达的意义,在访问元组的时候也只能通过索引访问其中的元素。 于是Python标准库collections引入了namedtuple函数,它可以创建一个和元组类似但更为强大的类型——具名元组(namedtuple),也就是构造一个带字段名的元组。

namedtuple 函数的语法如下所示:

collections.namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)
1
typename:元组名称。可以理解为通过namedtuple创建的类名,通过这样的方式可以初始化各种实例化元组对象。
field_names: 元组中元素的名称。类似于字典的key,在这里定义的元组可以通过这样的key去获取里面对应索引位置的元素值。
rename: 为True时field_names中不能包含有非Python标识符、Python中的关键字以及重复的name,如果有则会默认重命名成‘_index’的样式(index表示该name在field_names中的索引,例:[‘abc’,‘def’, ‘ghi’, ‘abc’]将被转换成[‘abc’, ‘_1’, ‘ghi’, ‘_3’])
创建一个具名元组,需要两个参数,一个是类名,另一个是类的各个字段名。后者可以是有多个字符串组成的可迭代对象,或者是有空格分隔开的字段名组成的字符串。具名元组可以通过字段名或者位置来获取一个字段的信息。

import collections

tupleA = collections.namedtuple(‘User’, [‘name’, ‘age’, ‘id’])
tupleB = collections.namedtuple(‘User’, ‘name age id’)
tuple_a = tupleA(‘Tom’, ’28’, ‘464643123’)
tuple_b = tupleB(‘Jack’, ’22’, ‘464643143’)

print(tuple_a, tuple_b)
# User(name=’Tom’, age=’28’, id=’464643123′)
# User(name=’Jack’, age=’22’, id=’464643143′)
————————————————
版权声明:本文为CSDN博主「xuange01」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xuange01/article/details/103309602

def calculate_matches(data: List, workers_num: int):

评估文档集中存在的答案。此功能应该与大量文档和结果一起使用。它在内部分叉多个子流程进行评估,然后合并结果:param all_docs:整个文档数据库的字典。 doc_id -> (doc_text, title) :param answers:答案列表的列表。每个问题一个列表:参数最接近的文档:最高结果的文档 ID 及其分数:参数workers_num:处理数据的并行线程数量:参数match_type:答案匹配的类型。有关可用选项,请参阅 has_answer 代码 :return: 匹配信息元组。 top_k_hits – 一个列表,其中索引是检索到的顶级文档的数量,值是整个数据集中有效匹配的总数。 questions_doc_hits – 每个问题和每个检索到的文档的答案匹配的更详细信息
参形:
workers_num – 处理数据的并行线程数量
返回值:
匹配信息元组。 top_k_hits – 一个列表,其中索引是检索到的顶级文档的数量,值是整个数据集中有效匹配的总数。 questions_doc_hits – 每个问题和每个检索到的文档的答案匹配的更详细信息

data.py

model.py

index.py

preprocess.py

对数据进行预处理,数据集:TQA,NQ

{
  'id': '0',
  'question': 'What element did Marie Curie name after her native land?',
  'target': 'Polonium',
  'answers': ['Polonium', 'Po (chemical element)', 'Po'],
  'ctxs': [
            {
                "title": "Marie Curie",
                "text": "them on visits to Poland. She named the first chemical element that she discovered in 1898 \"polonium\", after her native country. Marie Curie died in 1934, aged 66, at a sanatorium in Sancellemoz (Haute-Savoie), France, of aplastic anemia from exposure to radiation in the course of her scientific research and in the course of her radiological work at field hospitals during World War I. Maria Sk\u0142odowska was born in Warsaw, in Congress Poland in the Russian Empire, on 7 November 1867, the fifth and youngest child of well-known teachers Bronis\u0142awa, \"n\u00e9e\" Boguska, and W\u0142adys\u0142aw Sk\u0142odowski. The elder siblings of Maria"
            },
            {
                "title": "Marie Curie",
                "text": "was present in such minute quantities that they would eventually have to process tons of the ore. In July 1898, Curie and her husband published a joint paper announcing the existence of an element which they named \"polonium\", in honour of her native Poland, which would for another twenty years remain partitioned among three empires (Russian, Austrian, and Prussian). On 26 December 1898, the Curies announced the existence of a second element, which they named \"radium\", from the Latin word for \"ray\". In the course of their research, they also coined the word \"radioactivity\". To prove their discoveries beyond any"
            }
          ]
}
def select_examples_TQA(data, index, passages, passages_index):
        selected_data.append(
            {
                'question': q,
                'answers': answers,
                'target': target,
                'ctxs': ctxs,
            }
        )
    return selected_data
使用:
TQA_train = select_examples_TQA(originaltrain, TQA_idx['train'], passages, TQA_passages['train'])

def select_examples_NQ(data, index, passages, passages_index):
        dico = {
            'question': data[k]['question'],
            'answers': data[k]['answer'],
            'ctxs': ctxs,
        }
        selected_data.append(dico)

    return selected_data
使用:
NQ_train = select_examples_NQ(originaltrain, NQ_idx['train'], passages, NQ_passages['train'])


if __name__ == "__main__":

options.py

class Options():
    '''
    添加各种参数
    '''
    def add_optim_options(self):  #获取优化的参数如学习率、权重衰退、优化函数等

    def add_eval_options(self):  # 保存结果、保存数据的交叉注意力分数等

    def add_reader_options(self):  # 阅读器的参数如训练、测试数据源、模型大小,检查点、段落长度等

    def add_retriever_options(self):  #检索器的参数同上

    def initialize_parser(self):   #基础参数(模型路径、名称)、数据集参数(gpu上训练的大小)

    def print_options(self, opt):    # 输出选项 

    def get_options(use_reader=False,  #是否使用上述函数
                    use_retriever=False,
                    use_optim=False,
                    use_eval=False):
    



 train_reader.py

主函数

# 训练阅读器过程
def train(model, optimizer, scheduler, step, train_dataset, eval_dataset, opt, collator, best_dev_em, checkpoint_path):


# 评估阅读器过程
def evaluate(model, dataset, tokenizer, collator, opt)

test_reader.py

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年6月15日
下一篇 2022年6月15日

相关推荐