0. 前言

这部分内容还是很重要的,预计会设计常见的 pytorch 模型部署方法,理解框架中,每个模块在做什么。另外,这也是工程上必备的技能。

0.1 模型及下载地址

ModelRepoPaper
ResNet (18: 12M; 50: 26M; 152: 60M)https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py
BERT (110 / 330M)https://github.com/NVIDIA/DeepLearningExamples/tree/master/PyTorch/LanguageModeling/BERThttps://arxiv.org/abs/1810.04805
GPT-2 (1.5B)https://github.com/openai/gpt-2
OPT (125 / 350M; 1.3 / 2.7 / 6.7 / 13 / 30 / 66 / 175B)https://github.com/facebookresearch/metaseqhttps://arxiv.org/pdf/2205.01068.pdf
BLOOM (560M; 1.1 / 1.7 / 3 / 7.1 / 176B)https://huggingface.co/docs/transformers/model_doc/bloom
T5 (60 / 220 / 770M; 3 / 11B)https://github.com/google-research/text-to-text-transfer-transformer#released-model-checkpointshttps://jmlr.org/papers/volume21/20-074/20-074.pdf

0.2 模型下载方法

2023-07-08 20:10:12,重新回顾 22 年关于大模型研究的工作。从 chatgpt 爆火之后,大模型应用的框架变得火热,语言模型的社区也变得火热起来。准备在 ant_ext 工作中加入一些新的模型的评估,但是期智连接 huggingface 的网络老是抽风,导致试图从 huggingface 下载模型时出现错误。现在总结一些其他的下载方法。

Ref: https://zhuanlan.zhihu.com/p/475260268

0.2.1 git lfs

这个方法会下载所有框架的模型文件,flax_model.msgpack、tf_model.h5和pytorch_model.bin,可以看到有 3 种框架的模型文件,比较冗余。

git lfs install
git clone https://huggingface.co/bert-base-chinese

0.2.2 hungging face hub

这种方法其实仍然是通过访问 huggingface 网站来下载

python
>>> from huggingface_hub import snapshot_download
>>> snapshot_download(repo_id="facebook/opt-13b")

# 通过参数可以指定下载的文件,屏蔽不需要的文件即可
>>> snapshot_download(repo_id="facebook/opt-13b", ignore_patterns=["*.h5", "*.ot", "*.msgpack"], local_dir="/localdata_ssd/model", local_dir_use_symlinks=False)

1. Hugging Face

简介:Hugging face 是一家总部位于纽约的聊天机器人初创服务商,开发的应用在青少年中颇受欢迎,相比于其他公司,Hugging Face更加注重产品带来的情感以及环境因素。但更令它广为人知的是Hugging Face专注于NLP技术,拥有大型的开源社区。尤其是在github上开源的自然语言处理,预训练模型库 Transformers,已被下载超过一百万次,github上超过24000个star。Transformers 提供了NLP领域大量state-of-art的 预训练语言模型结构的模型和调用框架。

BERT 模型中,一个 token-wise 就是 768 维的向量。

总的来说,提供了各种易用 transformer 模型,并且有很好的社区和文档。

1.1 简介

1.1.1 用途

理解了一下,感觉是提供预训练好的模型,自己可以用来解决一些任务。

1.1.2 重要概念

1.1.2.1 Pipeline

pipeline 函数:包含 pro-processing, model, post-processing

如果只是想使用训练好的模型的功能,这是最简单的方法。pipeline 函数中应该完成了一套 workflow。下列代码下载了大概 300MB 的预训练模型,给出了对我输入的句子的情感分析。

from transformers import pipeline
classifier = pipeline("sentiment-analysis")
print(classifier("You like a pig"))

output: [{'label': 'NEGATIVE', 'score': 0.9961085915565491}]

1.1.2.2 AutoClass

AutoModelForSequenceClassification 以及 AutoTokenizer 用于支持 pipeline()。AutoClass 是一种快捷方式,可以自动从预训练模型的名称或路径中检索模型架构。 您只需要为您的任务选择适当的 AutoClass 及其关联的预处理类。

在自己使用时,选择 GPT2 模型,需要 from transformers import GPT2Model,如果要引入其他模型也是类似的做法。from transformers import AutoModel,应该可以根据模型名称自己去检索下载。

2022-10-25 21:13:01,当我有可能通过参数载入多种模型时,我意识到了 AutoClass 的作用。

1.1.2.3 Custom Model

主要途径是获取模型的 config,然后手动修改即可。

from transformers import AutoConfig
my_config = AutoConfig.from_pretrained("distilbert-base-uncased", n_heads=12)
my_model = AutoModel.from_config(my_config)

1.1.2.4 Trainer - a PyTorch optimized training loop

目前的很多框架中都有一套训练的 loop,hugging face 也提供了这套流程。设置好几个必备的参数和输入即可。

  1. A PreTrainedModel or a torch.nn.Module;自己定制一个模型应该也是可以的。
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("distilbert-base-uncased")
  1. TrainingArguments:超参数,比如 learning rate, batch size, epoch
from transformers import TrainingArguments
training_args = TrainingArguments(
    output_dir="path/to/save/folder/",
    learning_rate=2e-5,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    num_train_epochs=2,
)
  1. 设置预处理过程:比如 tokenizer, feature extractor, processor
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
  1. 定义预处理的训练数据集和测试数据集;不过这段代码之前得先把 dataset load 进来吧。
train_dataset = dataset["train"]  # doctest: +SKIP
eval_dataset = dataset["eval"]  # doctest: +SKIP
  1. DataCollator() to create a batch of examples from your dataset
from transformers import DefaultDataCollator
data_collator = DefaultDataCollator()

2022-10-24 09:43:20,这个 example 比较陌生

  1. Put it all together
from transformers import Trainer

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    tokenizer=tokenizer,
    data_collator=data_collator,
)  # doctest: +SKIP

# start training
trainer.train()

1.1.2.5 预处理

对于 NLP 模型,一般就是用 tokenizer 来做。经过 tokenizer 的字典有三个重要的 items

  • input_ids, the indices corresponding to each token in the sentence.
  • attention_mask, indicates whether a token should be attended to or not.
  • token_type_ids, identifies which sequence a token belongs to when there is more than one sequence.

padding

每个 sentence 长度不一致,用 padding 来 tensor 对齐。

batch_sentences = [
    "But what about second breakfast?",
    "Don't think he knows about second breakfast, Pip.",
    "What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True)
print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0], 
               [101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102], 
               [101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]], 
 'token_type_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]], 
 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], 
                    [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]]}

Truncation

也是一种对齐的方式

1.1.3 模型 Model

支持的模型很多,比较热门的预训练模型基本都有。学术界的很多模型也有,比如 I-BERT,RoBERTa

1.2 API

1.2.1 from_pretrained

功能:from_pretrained 提供了模型类别判断,模型文件列表映射,模型文件下载及缓存,网络下载稳定性容错等功能。

# Download from huggingface.co and cache
model = BertModel.from_pretrained("bert-base-uncased")
model.save_pretrained('./local_model_directory/')

# Load from local
model = BertModel.from_pretrained('./local_model_directory/')

实例:

if(os.path.exists('./model/'+args.gpt2_model+'pytorch_model.bin')):
    model.GPT2ForSequenceClassification.from_pretrained('./model/'+args.gpt2_model)
else:
    model = GPT2ForSequenceClassification.from_pretrained(args.gpt2_model)
    num_labels = len(model.config.id2label)
    model = GPT2ForSequenceClassification.from_pretrained(args.gpt2_model, num_labels=num_labels)
    model.save_pretrained('./model/'+args.gpt2_model)

1.3 Fine-tune a pretrained model

可以用 1.1.2.4 中的 Trainer() 来实现微调和训练,也可以在 pytorch 框架中实现。下面是步骤。

1.3.1 Prepare a dataset

比如,从 Yelp Reviews 中 load 数据,然后进行预处理。下面的代码用到了 map() 函数,写法比较陌生,不过这个过程是通用的。

# load
from datasets import load_dataset
dataset = load_dataset("yelp_review_full")
dataset["train"][100]
{'label': 0,
 'text': 'My expectations for McDonalds are t rarely high....'}

# tokenizer
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)

1.3.2 Train

Trainer() 训练在 1.1.2.4 中讲过了,讲一下用 native pytorch 训练,Train in native PyTorch

1.3.2.1 DataLoader

为训练数据集和测试数据集创建一个 DataLoader,以便可以遍历数据的 batch。给出 BERT 框架中的示例代码。变量 batch 中就是一个 batch,经过 tokenizer 之后的数据。

from torch.utils.data import (DataLoader, RandomSampler, SequentialSampler, TensorDataset)
train_features = get_train_features(
    args.data_dir,
    args.gpt2_model,
    args.max_seq_length,
    args.do_lower_case,
    args.local_rank,
    args.train_batch_size,
    args.gradient_accumulation_steps,
    args.num_train_epochs,
    tokenizer,
    processor,
)
train_data = gen_tensor_dataset(train_features)
        train_dataloader = DataLoader(
            train_data,
            sampler=train_sampler,
            batch_size=args.train_batch_size,
        )
for e in range(args.num_train_epochs):
    ...
    for step, batch in enumerate(train_dataloader):
        ...

1.3.2.2 Optimizer and learning rate scheduler

优化器根据模型来定义,要传入 learning rate 吗,代码示例:

model, optimizer, scheduler = init_optimizer_and_amp(
    model,
    args.learning_rate,
    args.loss_scale,
    args.warmup_proportion,
    num_train_optimization_steps,
    args.fp16,
)
def init_optimizer_and_amp(model, learning_rate, loss_scale, warmup_proportion,
                           num_train_optimization_steps, use_fp16):
    ...
    optimizer, scheduler = None, None
    logger.info("using fp32")
    if num_train_optimization_steps is not None:
        optimizer = BertAdam(
            optimizer_grouped_parameters,
            lr=learning_rate,
            warmup=warmup_proportion,
            t_total=num_train_optimization_steps,
        )
    return model, optimizer, scheduler

1.3.2.3 Training Loop

这个无需多说。设置好 epoch,每个 epoch 会过完训练数据集中的所有数据。(当然,也有随机抽样的形式)

model.train()
for e in range(args.num_train_epochs):
    ...
    for step, batch in enumerate(train_dataloader):
        batch = tuple(t.to(device) for t in batch)
        input_ids, input_mask, segment_ids, label_ids = batch
        # outputs = model(**batch)
        outputs = model(input_ids=input_ids, token_type_ids=segment_ids, attention_mask=input_mask, labels=label_ids)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

1.3.2.4 Evaluate

model.eval()
for i, (input_ids, input_mask, segment_ids, label_ids) in enumerate(eval_dataloader):
    input_ids = input_ids.to(device)
    input_mask = input_mask.to(device)
    segment_ids = segment_ids.to(device)
    label_ids = label_ids.to(device)
    with torch.no_grad():
        # label_ids = label_ids.to(torch.float)
        outputs = model(input_ids=input_ids, token_type_ids=segment_ids, attention_mask=input_mask, labels=label_ids)
        logits = outputs.logits
    ...

1.4 Accelerate

Easily rain and use PyTorch models with multi-GPU, TPU, mixed-precision

1.4.0 术语

关于分布式的一些概念和术语。

参考:

https://zhuanlan.zhihu.com/p/544273093

https://www.zhihu.com/question/453920336/answer/1828326535

  • Scatter: 把数据分发到其他线程 or 进程
  • Gather: 把数据从其他进程 or 线程中收集到一个主进程中
  • Reduce: 计算在各个进程 or 线程中进行,然后归约到一个主进程中
  • All-Reduce: 同 Reduce 一样,计算再各个进程 or 线程中进行,但是每个节点都一起归约,保持每个节点的结果一致
  • Broadcast: 把数据复制到各个进程 or 线程
  • All-Gather: 与 Gather 不同,每个进程 or 线程中完成数据的手机,保证每个节点的结果一致
  • CLI (Command Line Interface):一种通过命令行来交互的工具或者应用,比如 mkdir, cd, scp, npm 等
  • Rank: 表示进程的编号/序号。(自己观察到一个 GPU 似乎对应一个 rank,不过 rank 和 GPU 没有严格的对应关系。如果是多进程共享 GPU,那么一个 GPU 可以为多个 rank 服务)
  • Node: 物理节点,可以是一台机器或者一个容器,节点内可以有多个 GPU
  • Rank 和 Local Rank: rank是指在整个分布式任务中进程的序号;local_rank是指在一个node上进程的相对序号,local_rank在node之间相互独立。
  • nnodes、node_rank 与 nproc_per_node: nnodes是指物理节点数量,node_rank是物理节点的序号;nproc_per_node是指每个物理节点上面进程的数量。
  • word size : 全局(一个分布式任务)中,rank的数量。
  • csrc: 应该是 C source code
  • NVIDIA Megatron-LM: 针对Transformer类的模型提供半自动的分布式部署。
  • DeepSpeed: 微软提供的一个开源深度学习训练优化库。基于英伟达 Megatron-LM 进行了张量切分式的模型并行。

同步 SGD:每个 Worker 都是同步计算一个批量。

1.4.1 分布式概念 Distributed

2022-10-24 10:43:08,日后填坑。

2022-11-01 09:04:30,开始学习。

nn.parallel.scatter(data, devices),可以将一组数据 split 到多个 devices 上

1.4.1.1 DataParallel

nn.DataParallel(net, devices_ids=devices),DP 模型。单进程,多线程,适于一台机器的情况。

做法是将 model 复制到不同的 GPU 上,各个 GPU 计算不同 batch 的数据。

1.4.1.2 DistributedDataParallel

torch.nn.parallel.DistributedDataParallel,DDP 模型(PyTorch 官网更推荐)。

需要启动 init_process_group

如果想在一个有 N 个 GPU 的设备上面使用 DistributedDataParallel,则需要 spawn up N 个进程,每个进程对应0-N-1 的一个 GPU。这可以通过下面的语句实现

torch.cuda.set_device(i)

# i from 0-N-1,每个进程中都需要:
torch.distributed.init_process_group(
    backend='nccl', world_size=N, init_method='...'
)
model = DistributedDataParallel(model, device_ids=[i], output_device=i)

# 实践
if args.local_rank == -1 or args.no_cuda:
    device = torch.device(
        "cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")
    n_gpu = torch.cuda.device_count()
else:
    torch.cuda.set_device(args.local_rank)
    device = torch.device("cuda", args.local_rank)
    n_gpu = 1
    # Initializes the distributed backend which will take care of
    # sychronizing nodes/GPUs.
    if not torch.distributed.is_initialized():
        torch.distributed.init_process_group(backend='nccl')

多进程,适于多台机器的情况,可以结合模型并行的方法。

使用时建议

  • 使用一个 big dataset
  • 好的 CPU-GPU 和机器-机器带宽
  • 高效的 data load 以及 preprocess
  • 模型需要有好的计算(FLOP)通讯(model size)比
    • Inception > ResNet > AlexNet
  • 使用足够大的 batch size 来获取好的系统性能
  • 使用高效的优化算法对应大批量大小

1.4.2 Hugging face 文档

1.4.2.1 步骤

  • import Accelerator 并且实例化
from accelerator import Accelerator

accelerator = Accelerator()
  • 消去 model 以及 input data 的 .to(device) 或者 .cuda() 操作。accelerator 会帮你完成这件事
  • 把相关的对象(optimizer, model, train_dataloader, lr_scheduler)传递给 prepare() 方法
model, optimizer, train_dataloader, lr_scheduler = accelerator.prepare(
    model, optimizer, train_dataloader, lr_scheduler
)

每个 GPU 会加载 training dataset 中的不同部分(batch),这看起来像是数据并行。

  • Replace the line loss.backward() by accelerator.backward(loss).

1.4.2.2 Distributed evaluation

如果需要分布式评估,同理,把 validation dataloader 发送给 prepare() 方法。

validation_dataloader = accelerator.prepare(validation_dataloader)

这样的话每个 devices 只能看见 evaluation data 中的一部分,所以最后需要把结果 group 到一起,通过 gather_for_metrics() 方法。

for inputs, targets in validation_dataloader:
    predictions = model(inputs)
    # Gather all predictions and targets
    all_predictions, all_targets = accelerator.gather_for_metrics((predictions, targets))
    # Example of use with a *Datasets.Metric*
    metric.add_batch(all_predictions, all_targets)

1.4.2.3 Launch

accelerate 命令整合了在不同平台上发射脚本的命令,你无需自己记住所有命令。如果你自己熟悉 PyTorch 提供的发射脚本,也可以不使用 accelerate

# 单 GPU 执行
CUDA_VISIBLE_DEVICES="0" accelerate launch {script_name.py} --arg1 --arg2 ...

# 默认参数,使用所有的 GPU,不启动混合精度
accelerate launch --multi_gpu {script_name.py} {--arg1} {--arg2} ...

# 当然,也可以指定参数
accelerate launch --multi_gpu --mixed_precision=fp16 --num_processes=2 {script_name.py} {--arg1} {--arg2} ...

# 查看所有参数
accelerate launch -h

accelerate config

当然,更好的方式是配置一个 config 文件。使用了 accelerate config 之后,会在 ~/.cache/huggingface/accelerate 目录下生成 default_config.yaml 文件。也可以自己定制 config 文件然后读取。

accelerate launch --config_file ~/.cache/huggingface/accelerate {script_name.py} {--arg1} {--arg2} ...

1.5 Dataset

1.5.1 load from hugging face

hugging face 下载文件存储的位置:~/.cache/huggingface

找到 dataset 之后如何下载:

# 搜索到数据集 oscar,里面有很多 subset,比如:unshuffled_deduplicated_en, unshuffled_deduplicated_br

# 第一个参数是数据集的名称,第二个参数用于索引子集,一般还有 split 参数:train, test, validation 

from datasets import load_dataset
dataset = load_dataset(path="oscar", name="unshuffled_deduplicated_en")
# 420G,赶紧中断
dataset.save_to_disk("./glue/")

# 尝试下载 wikitext
dataset = load_dataset(path="wikitext", name="wikitext-103-v1", split="train")
# 2022-10-24 14:07:31,是可以的
print(dataset[:3])
{'text': ['', ' = Valkyria Chronicles III = \n', '']}

# 保存
dataset.save_to_disk("./")

1.5.2 数据集格式

1.5.1 的例子中,默认保存为 dataset.arrow 加上 .json 文件。

现在我们想将其保存为 .csv

1.5.3 split 作用

下面是带有 feature 的 dataset 一个示例

from datasets import load_dataset
# 如果用了 split 
dataset = load_dataset("rotten_tomatoes", split="train")
print(dataset)
Dataset({
    features: ['text', 'label'],
    num_rows: 8530
})
# 如果不用 split,
dataset = load_dataset("rotten_tomatoes")
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 8530
    })
    validation: Dataset({
        features: ['text', 'label'],
        num_rows: 1066
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 1066
    })
})

1.6 Github 文档

所有任务的 example 存放在 https://github.com/huggingface/transformers/tree/main/examples/pytorch 里面。

2. 知识补充

补充一些相关的,之前不熟悉的概念。

2.2 Data Set

2.2.1 GLUE

数据集是之前屏蔽的一个部分,但是如果想了解模型是在做什么,并且要动手做实验,必须了解数据集。

GLUE 是学术界非常常用的数据集。

数据集类型描述
MNLI句子对分类任务给定一个前提 (Premise) ,根据这个前提去推断假设 (Hypothesis) 与前提的关系。该任务的关系分为三种,蕴含关系 (Entailment)、矛盾关系 (Contradiction) 以及中立关系 (Neutral)。
QQP句子对分类任务判断 Quora 上的两个问题句是否表示的是一样的意思
SST-2分类任务情感分析
CoLA单句分类任务句子语义判断,是否是可接受的(Acceptable)
QNLI用于判断文本是否包含问题的答案
STS-B预测两个句子的相似性,包括5个级别。
MRPC句子对分类任务也是判断两个句子是否是等价的。
RTE分类类似于MNLI,但是只是对蕴含关系的二分类判断,而且数据集更小。
SWAG从四个句子中选择为可能为前句下文的那个

3. 编程实例

3.1 通用知识

3.1.1 主要的组件

  • Configuration: 编程模型中的参数或是变量。用于配置词表大小,隐藏层维数,Dropout rate 等等。给出一个 BERT_base 配置的示例。
{
  "attention_probs_dropout_prob": 0.1,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "type_vocab_size": 2,
  "vocab_size": 30522
}
  • Tokenizer: 每个模型都有对应的分词器。存储 token 到 index 的映射。
  • Model: 实现模型的计算图和编码过程,实现前向传播。对于 output 层,不同的模型有不一样的封装。比如今天(2022-10-23)debug 观察的 BaseModelOutputWithPoolingAndCrossAttentions类型:SequenceClassifierOutput 等等。

3.1.2 任务分类

语言生成任务,对应模型:GPT,GPT-2,XLNet

语言理解任务,对应模型:BERT,RoBERTa,XLM

3.2 在 GPT-2 上运行 wikitext

README:

pip install transformers
ln -s ../BERT/glue/ ./glue

3.0.1 坑1

坑,和 BERT 区分

BERT forward 和 GPT-2 forward 不一样。之前实现的 model(inputs_id, token_type_ids, attention_mask) 按位置传参,但是 GPT-2 forward 第二个参数是 past_key_values,所以报错,按关键字传参即可解决这个问题。

他们经过前向之后,output 也不一样。

3.0.2 大坑2

2022-10-26 22:31:46,复现 GPT 的结果出了大问题,好在今晚解决了。问题来源于框架中会自动启动分布式 train/eval,但是没有很好地支持分布式(单 GPU 运行 30s,4 GPU 运行 6min),而且 loss 的处理也有问题,导致最终的 metric - PPL 出了问题。用单 GPU 可以解决这个问题。

3.0.3 坑3

2022-10-28 16:03:02,框架K 是不支持分布式的,这导致了 local_rank 索引不到,因此执行 evaluate(),进入 quant 模块时报错。

3.2.1 Prepare Dataset

读取 datasets 并保存到本地

from datasets import load_dataset
test_dataset = load_dataset("json", data_files="test.json", split="train")
test_dataset.save_to_disk("test.hf")

3.2.2 LM 预处理

针对生成类模型的预处理。如何从原始的数据集生成 tokenizer 之后的数据。

row_datasets = load_dataset(data_args.dataset_name, data_args.dataset_config_name, cache_dir=model_args.cache_dir)
# tokenizer
tokenized_datasets = raw_datasets.map(
    tokenize_function,
    batched=True,
    num_proc=data_args.preprocessing_num_workers,
    remove_columns=column_names,
    load_from_cache_file=not data_args.overwrite_cache,
    desc="Running tokenizer on dataset",
)
with training_args.main_process_first(desc="grouping texts together"):
    lm_datasets = tokenized_datasets.map(
        group_texts,
        batched=True,
        num_proc=data_args.preprocessing_num_workers,
        load_from_cache_file=not data_args.overwrite_cache,
        desc=f"Grouping texts in chunks of {block_size}",
    )
# 计算 token 长度
for split, data in lm_datasets.items():
total_eval_tokens = 0        
for chunk in data['labels']:
    total_eval_tokens += len([x for x in chunk[1:] if x != padding_index])
logger.info(f'[{split}] Total eval tokens: {total_eval_tokens}')

# 得到 eval dataset 以及 train dataset
if training_args.do_train:
    if "train" not in tokenized_datasets:
        raise ValueError("--do_train requires a train dataset")
    train_dataset = lm_datasets["train"]
    if data_args.max_train_samples is not None:
        train_dataset = train_dataset.select(range(data_args.max_train_samples))

if training_args.do_eval:
    if "validation" not in tokenized_datasets:
        raise ValueError("--do_eval requires a validation dataset")
    eval_dataset = lm_datasets[data_args.eval_subset]
    if data_args.max_eval_samples is not None:
        eval_dataset = eval_dataset.select(range(data_args.max_eval_samples))

# 这个时候 eval_dataset size (480, 3),3 个维度分别是 input_ids, attention_mask, labels

3.3 在 T5 上运行 WMT

MODEL=t5-small

CUDA_VISIBLE_DEVICES=0 python -u run_translation.py  \
  --model_name_or_path ${MODEL} \
  --dataset_name wmt16 --dataset_config_name ro-en \
  --per_device_eval_batch_size=4 \
  --output_dir checkpoints-translation/${MODEL} \
  --source_lang en --target_lang ro \
  --do_eval \
  --predict_with_generate \
  --source_prefix "translate English to Romanian: "

4. 编程 flow

利用 debug 以及 hugging face 文档,了解整个流程。在 Section 1 介绍 hugging face 指导文档,这部分结合实际编程来看。

4.1 数据读取及预处理

4.2.1 数据读取

通过参数 --data_dir,把本地的数据加载进来,应该包含 .tsv 文件。hugging face 的预训练模型可以下载到本地并保存,那么 Dataset 应该也可以。

4.2.2 DataLoader

经过处理后的 train_features,长度是 8551 examples,装的是经过 tokenizer 后的数据。

原始的 train_example,从 ./glue/CoLA/train.tsv 读入,长度是 8551 examples,每个 example 装的是:

  • guid:train-1 or train=2
  • label:1 or 0
  • text_a:Our friends won't buy this analysis...
  • text_b:None

train_data 就是通过 gen_tensor_dataset()train_features 转换为 tensor;经过 DataLoader(train_data, sampler, batch_size) 方法之后,得到 train_dataloadersampler 就是 8551。

在一个 epoch 中,需要遍历一遍整个 train_dataloader,一个 iteration 处理其中一个 batch,train_loader 遍历完就是一个 epoch。

记得数据需要转移到 device

4.2.X Tokenizer

input_ids 存放的就是 tokenizer 后的数据本身,是一个 (64, 128) 的 tensor。mask, segment_id 也是 (64, 128),label 是 (64),这应该就是最后的分类,因为 batch size 是 64,所以有 64 个输出。

对于 hugging face BERT,经过 tokenizer 打印出来就是一整个 mapping,也就是对应 run_glue.py 中遍历 train_dataloader 的变量 batch。不过 batch 的类型是 list,而下面的 inputs 是 dict。而 hugging face BERT 的 output 也是一个 BaseModelOutputWithPoolingAndCrossAttentions 类型。

from transformers import BertTokenizer, BertModel
import torch

tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")

inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
print(inputs)
{'input_ids': tensor([[  101,  7592,  1010,  2026,  3899,  2003, 10140,   102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1]])}

outputs = model(**inputs)
# output 输出包含 last_hidden_state, pooler_output,其他属性 hidden_states, past_key_values, attentions, cross_attentions 为 None

对于 hugging face BertForSequenceClassification,

  • output 类型:SequenceClassifierOutput(loss=None, logits=tensor([[-2.2095, 2.5760]]), hidden_states=None, attentions=None)

4.3 计算 loss

hugging face 有一个普通 trainer 的实例,关于如何计算 loss。看一下能不能套用到原先 BERT 的框架中。

from torch import nn
from transformers import Trainer


class CustomTrainer(Trainer):
    def compute_loss(self, model, inputs, return_outputs=False):
        labels = inputs.get("labels")
        # forward pass
        outputs = model(**inputs)
        logits = outputs.get("logits")
        # compute custom loss (suppose one has 3 labels with different weights)
        loss_fct = nn.CrossEntropyLoss(weight=torch.tensor([1.0, 2.0, 3.0]))
        loss = loss_fct(logits.view(-1, self.model.config.num_labels), labels.view(-1))
        return (loss, outputs) if return_outputs else loss

2022-10-23 18:32:34,直接用 forward 函数中计算的 loss

label_ids = label_ids.to(torch.float)
outputs = model(input_ids=input_ids, token_type_ids=segment_ids, attention_mask=input_mask, labels=label_ids)
logits = outputs.logits
loss = outputs.loss

注意,对于 single_label_classification 分类任务,不要将 label_ids 转化成 int。

5. 理解

5.1 深度学习研究者在做什么

转载自 https://www.zhihu.com/question/433274875/answer/2240764095

以下阶段层层递进

  • 把已有的开源模型下载下来,换成自己的数据集。这个时候应该是偏向于应用型研究,把动物图像数据集换成自己领域的,医学图像数据集,并且跑出 SOTA 的准确率。

这个过程要求看懂开源的代码架构,loss, optimizer, 基本的前向反向传播应该无需修改,但是需要了解数据的预处理,输入的数据是什么样的,如何进行数据转换,数据集从哪里下载,用哪个函数 load data。

  • 从理论上理解模型的一些算法和思想,知道一些重要的超参数背后的思想。有信心调整它,并符合自己预期的效果

最显然的,learning rate, batch size,又比如量化时的 scale factor 搜索范围

  • 深入了解模型的代码实现和细节,能够为模型添加一些主流的,提升性能的 module 或者 trick;能够分析修改前后,模型的差异;对模型的推理和训练过程了如指掌

比如分析推理时某个 epoch 的激活分布情况,分布出现了什么问题,如何解决,loss 变化有什么问题等等

  • 能够根据自己的业务需求,拓展模型的功能,让模型能做更多的事情

这个可能更多体现在业务需求层面,如何去部署公司需要的模型。不过我认为做了量化框架的拓展,也达到这个水平了。

Reference

https://zhuanlan.zhihu.com/p/120315111