如何创建自定义管道?
原始文本:
huggingface.co/docs/transformers/v4.37.2/en/add_new_pipeline
在本指南中,我们将看到如何创建自定义管道并在Hub上共享它或将其添加到🤗 Transformers 库中。
首先,您需要决定管道将能够接受的原始条目。它可以是字符串、原始字节、字典或任何看起来最有可能的期望输入。尽量保持这些输入尽可能纯粹的 Python,因为这样可以使兼容性更容易(甚至通过 JSON 通过其他语言)。这些将是管道的inputs
(preprocess
)。
然后定义outputs
。与inputs
相同的策略。越简单越好。这些将是postprocess
方法的输出。
首先通过继承基类Pipeline
,具有实现preprocess
、_forward
、postprocess
和_sanitize_parameters
所需的 4 个方法。
from transformers import Pipeline
class MyPipeline(Pipeline):
def _sanitize_parameters(self, **kwargs):
preprocess_kwargs = {}
if "maybe_arg" in kwargs:
preprocess_kwargs["maybe_arg"] = kwargs["maybe_arg"]
return preprocess_kwargs, {}, {}
def preprocess(self, inputs, maybe_arg=2):
model_input = Tensor(inputs["input_ids"])
return {"model_input": model_input}
def _forward(self, model_inputs):
# model_inputs == {"model_input": model_input}
outputs = self.model(**model_inputs)
# Maybe {"logits": Tensor(...)}
return outputs
def postprocess(self, model_outputs):
best_class = model_outputs["logits"].softmax(-1)
return best_class
这种分解的结构支持相对无缝地支持 CPU/GPU,同时支持在不同线程上在 CPU 上进行预处理/后处理
preprocess
将获取最初定义的输入,并将其转换为可供模型使用的内容。它可能包含更多信息,通常是一个Dict
。
_forward
是实现细节,不应直接调用。forward
是首选的调用方法,因为它包含了确保一切在预期设备上工作的保障措施。如果任何内容与真实模型相关,则应该放在_forward
方法中,其他内容应该放在预处理/后处理中。
postprocess
方法将获取_forward
的输出并将其转换为之前决定的最终输出。
_sanitize_parameters
存在是为了允许用户在任何时候传递任何参数,无论是在初始化时pipeline(...., maybe_arg=4)
还是在调用时pipe = pipeline(...); output = pipe(...., maybe_arg=4)
。
_sanitize_parameters
的返回值是将直接传递给preprocess
、_forward
和postprocess
的 3 个 kwargs 字典。如果调用者没有使用任何额外参数,则不要填写任何内容。这样可以保持函数定义中的默认参数,这总是更“自然”的。
一个经典的例子是在分类任务的后处理中添加一个top_k
参数。
>>> pipe = pipeline("my-new-task")
>>> pipe("This is a test")
[{"label": "1-star", "score": 0.8}, {"label": "2-star", "score": 0.1}, {"label": "3-star", "score": 0.05}
{"label": "4-star", "score": 0.025}, {"label": "5-star", "score": 0.025}]
>>> pipe("This is a test", top_k=2)
[{"label": "1-star", "score": 0.8}, {"label": "2-star", "score": 0.1}]
为了实现这一点,我们将使用一个默认参数5
来更新我们的postprocess
方法,并编辑_sanitize_parameters
以允许这个新参数。
def postprocess(self, model_outputs, top_k=5):
best_class = model_outputs["logits"].softmax(-1)
# Add logic to handle top_k
return best_class
def _sanitize_parameters(self, **kwargs):
preprocess_kwargs = {}
if "maybe_arg" in kwargs:
preprocess_kwargs["maybe_arg"] = kwargs["maybe_arg"]
postprocess_kwargs = {}
if "top_k" in kwargs:
postprocess_kwargs["top_k"] = kwargs["top_k"]
return preprocess_kwargs, {}, postprocess_kwargs
尽量保持输入/输出非常简单,最好是可 JSON 序列化的,因为这样可以使管道的使用非常简单,而不需要用户理解新类型的对象。通常也支持许多不同类型的参数,以便于使用(例如音频文件,可以是文件名、URL 或纯字节)
将其添加到支持任务列表中
要将您的new-task
注册到支持任务列表中,您必须将其添加到PIPELINE_REGISTRY
中:
from transformers.pipelines import PIPELINE_REGISTRY
PIPELINE_REGISTRY.register_pipeline(
"new-task",
pipeline_class=MyPipeline,
pt_model=AutoModelForSequenceClassification,
)
如果需要,您可以指定一个默认模型,此时应该附带一个特定的修订版(可以是分支名称或提交哈希,这里我们取"abcdef"
)以及类型:
PIPELINE_REGISTRY.register_pipeline(
"new-task",
pipeline_class=MyPipeline,
pt_model=AutoModelForSequenceClassification,
default={"pt": ("user/awesome_model", "abcdef")},
type="text", # current support type: text, audio, image, multimodal
)
在 Hub 上共享您的管道
要在 Hub 上共享您的自定义管道,只需将Pipeline
子类的自定义代码保存在一个 python 文件中。例如,假设我们想要像这样为句对分类使用自定义管道:
import numpy as np
from transformers import Pipeline
def softmax(outputs):
maxes = np.max(outputs, axis=-1, keepdims=True)
shifted_exp = np.exp(outputs - maxes)
return shifted_exp / shifted_exp.sum(axis=-1, keepdims=True)
class PairClassificationPipeline(Pipeline):
def _sanitize_parameters(self, **kwargs):
preprocess_kwargs = {}
if "second_text" in kwargs:
preprocess_kwargs["second_text"] = kwargs["second_text"]
return preprocess_kwargs, {}, {}
def preprocess(self, text, second_text=None):
return self.tokenizer(text, text_pair=second_text, return_tensors=self.framework)
def _forward(self, model_inputs):
return self.model(**model_inputs)
def postprocess(self, model_outputs):
logits = model_outputs.logits[0].numpy()
probabilities = softmax(logits)
best_class = np.argmax(probabilities)
label = self.model.config.id2label[best_class]
score = probabilities[best_class].item()
logits = logits.tolist()
return {"label": label, "score": score, "logits": logits}
这个实现是与框架无关的,将适用于 PyTorch 和 TensorFlow 模型。如果我们将其保存在一个名为pair_classification.py
的文件中,然后可以像这样导入并注册它:
from pair_classification import PairClassificationPipeline
from transformers.pipelines import PIPELINE_REGISTRY
from transformers import AutoModelForSequenceClassification, TFAutoModelForSequenceClassification
PIPELINE_REGISTRY.register_pipeline(
"pair-classification",
pipeline_class=PairClassificationPipeline,
pt_model=AutoModelForSequenceClassification,
tf_model=TFAutoModelForSequenceClassification,
)
完成后,我们可以使用预训练模型。例如sgugger/finetuned-bert-mrpc
已在 MRPC 数据集上进行了微调,用于将句子对分类为释义或非释义。
from transformers import pipeline
classifier = pipeline("pair-classification", model="sgugger/finetuned-bert-mrpc")
然后我们可以通过在Repository
中使用save_pretrained
方法在 Hub 上共享它:
from huggingface_hub import Repository
repo = Repository("test-dynamic-pipeline", clone_from="{your_username}/test-dynamic-pipeline")
classifier.save_pretrained("test-dynamic-pipeline")
repo.push_to_hub()
这将复制您在文件中定义PairClassificationPipeline
的文件夹"test-dynamic-pipeline"
中,同时保存管道的模型和分词器,然后将所有内容推送到存储库{your_username}/test-dynamic-pipeline
中。之后,任何人只要提供选项trust_remote_code=True
就可以使用它:
from transformers import pipeline
classifier = pipeline(model="{your_username}/test-dynamic-pipeline", trust_remote_code=True)
将管道添加到🤗 Transformers
如果您想将您的管道贡献给🤗 Transformers,您需要在pipelines
子模块中添加一个新模块,其中包含您的管道的代码,然后将其添加到pipelines/__init__.py
中定义的任务列表中。
然后,您需要添加测试。创建一个新文件tests/test_pipelines_MY_PIPELINE.py
,其中包含其他测试的示例。
run_pipeline_test
函数将非常通用,并在由model_mapping
和tf_model_mapping
定义的每种可能的架构上运行小型随机模型。
这对于测试未来的兼容性非常重要,这意味着如果有人为XXXForQuestionAnswering
添加了一个新模型,那么管道测试将尝试在其上运行。由于模型是随机的,无法检查实际值,这就是为什么有一个辅助ANY
,它将简单地尝试匹配管道类型的输出。
您还需要实现 2(理想情况下 4)个测试。
-
test_small_model_pt
:为这个管道定义一个小模型(结果是否有意义并不重要),并测试管道的输出。结果应该与test_small_model_tf
相同。 -
test_small_model_tf
:为这个管道定义一个小模型(结果是否有意义并不重要),并测试管道的输出。结果应该与test_small_model_pt
相同。 -
test_large_model_pt
(optional
): 在一个真实的管道上测试管道,结果应该是有意义的。这些测试很慢,应该标记为这样。这里的目标是展示管道,并确保将来的发布中没有漂移。 -
test_large_model_tf
(optional
): 在一个真实的管道上测试管道,结果应该是有意义的。这些测试很慢,应该标记为这样。这里的目标是展示管道,并确保将来的发布中没有漂移。
测试
让我们看看🤗 Transformers 模型是如何测试的,以及您如何编写新测试并改进现有测试。
存储库中有 2 个测试套件:
-
tests
— 用于一般 API 的测试 -
examples
— 主要用于不属于 API 的各种应用的测试
如何测试 transformers
-
一旦提交了 PR,它将通过 9 个 CircleCi 作业进行测试。对该 PR 的每个新提交都会重新测试。这些作业在此配置文件中定义,因此如果需要,您可以在您的机器上重现相同的环境。
这些 CI 作业不运行
@slow
测试。 -
由github actions运行 3 个作业:
-
torch hub 集成:检查 torch hub 集成是否正常工作。
-
自托管(推送):仅在
main
上的提交上在 GPU 上运行快速测试。仅在main
上的提交更新了以下文件夹中的代码时才运行:src
,tests
,.github
(以防止在添加模型卡、笔记本等时运行)。 -
自托管的运行器:在
tests
和examples
中的 GPU 上运行正常和慢速测试:
-
RUN_SLOW=1 pytest tests/
RUN_SLOW=1 pytest examples/
结果可以在此处观察。
运行测试
选择要运行的测试
本文详细介绍了如何运行测试。如果阅读完所有内容后,您需要更多细节,您可以在此处找到它们。
以下是运行测试的一些最有用的方法。
运行所有:
pytest
或:
make test
请注意,后者被定义为:
python -m pytest -n auto --dist=loadfile -s -v ./tests/
告诉 pytest:
-
运行与 CPU 核心数量相同的测试进程(如果 RAM 不足可能会太多!)
-
确保同一文件中的所有测试将由同一个测试进程运行
-
不捕获输出
-
以详细模式运行
获取所有测试的列表
测试套件的所有测试:
pytest --collect-only -q
给定测试文件的所有测试:
pytest tests/test_optimization.py --collect-only -q
运行特定测试模块
要运行单个测试模块:
pytest tests/utils/test_logging.py
运行特定测试
由于大多数测试中使用了 unittest,要运行特定的子测试,您需要知道包含这些测试的 unittest 类的名称。例如,可能是:
pytest tests/test_optimization.py::OptimizationTest::test_adam_w
这里:
-
tests/test_optimization.py
- 具有测试的文件 -
OptimizationTest
- 类的名称 -
test_adam_w
- 特定测试函数的名称
如果文件包含多个类,您可以选择仅运行给定类的测试。例如:
pytest tests/test_optimization.py::OptimizationTest
将运行该类中的所有测试。
如前所述,您可以通过运行以下内容查看OptimizationTest
类中包含的所有测试:
pytest tests/test_optimization.py::OptimizationTest --collect-only -q
您可以通过关键字表达式运行测试。
仅运行包含adam
名称的测试:
pytest -k adam tests/test_optimization.py
逻辑and
和or
可用于指示是否应匹配所有关键字或任一关键字。not
可用于否定。
要运行除包含adam
的名称的测试之外的所有测试:
pytest -k "not adam" tests/test_optimization.py
您可以将这两种模式结合在一起:
pytest -k "ada and not adam" tests/test_optimization.py
例如,要同时运行test_adafactor
和test_adam_w
,您可以使用:
pytest -k "test_adam_w or test_adam_w" tests/test_optimization.py
请注意,我们在这里使用or
,因为我们希望关键字中的任何一个匹配以包括两者。
如果要仅包含包含两种模式的测试,应使用and
:
pytest -k "test and ada" tests/test_optimization.py
运行加速测试
有时您需要在模型上运行accelerate
测试。为此,您只需将-m accelerate_tests
添加到您的命令中,例如,如果您想在OPT
上运行这些测试:
RUN_SLOW=1 pytest -m accelerate_tests tests/models/opt/test_modeling_opt.py
运行文档测试
为了测试文档示例是否正确,您应该检查doctests
是否通过。例如,让我们使用WhisperModel.forward
的文档字符串:
r"""
Returns:
Example:
```python
>>> import torch
>>> from transformers import WhisperModel, WhisperFeatureExtractor
从数据集导入数据集
>>> model = WhisperModel.from_pretrained("openai/whisper-base")
>>> feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-base")
>>> ds = load_dataset("hf-internal-testing/librispeech_asr_dummy", "clean", split="validation")
>>> inputs = feature_extractor(ds[0]["audio"]["array"], return_tensors="pt")
>>> input_features = inputs.input_features
>>> decoder_input_ids = torch.tensor([[1, 1]]) * model.config.decoder_start_token_id
>>> last_hidden_state = model(input_features, decoder_input_ids=decoder_input_ids).last_hidden_state
>>> list(last_hidden_state.shape)
[1, 2, 512]
```py"""
只需运行以下行,自动测试所需文件中的每个文档字符串示例:
pytest --doctest-modules <path_to_file_or_dir>
如果文件具有 markdown 扩展名,应添加--doctest-glob="*.md"
参数。
仅运行修改后的测试
您可以通过使用pytest-picked来运行与未暂存文件或当前分支(根据 Git)相关的测试。这是一个快速测试您的更改是否破坏了任何内容的好方法,因为它不会运行与您未触及的文件相关的测试。
pip install pytest-picked
pytest --picked
将从已修改但尚未提交的文件和文件夹中运行所有测试。
在源代码修改时自动重新运行失败的测试
pytest-xdist提供了一个非常有用的功能,可以检测所有失败的测试,然后等待您修改文件并持续重新运行这些失败的测试,直到它们通过,同时您修复它们。这将重复进行,直到所有测试通过,之后再次执行完整运行。
pip install pytest-xdist
要进入模式:pytest -f
或pytest --looponfail
通过查看looponfailroots
根目录及其所有内容(递归)来检测文件更改。如果此值的默认值对您不起作用,您可以通过在setup.cfg
中设置配置选项来更改项目中的值:
[tool:pytest]
looponfailroots = transformers tests
或pytest.ini
/tox.ini
文件:
[pytest]
looponfailroots = transformers tests
这将导致仅查找相对于 ini 文件目录指定的相应目录中的文件更改。
pytest-watch是此功能的另一种实现。
跳过一个测试模块
如果您想运行所有测试模块,除了一些,您可以通过提供一个明确的测试运行列表来排除它们。例如,要运行除了test_modeling_*.py
测试之外的所有测试:
pytest *ls -1 tests/*py | grep -v test_modeling*
清除状态
CI 构建和隔离重要时(针对速度),应清除缓存:
pytest --cache-clear tests
并行运行测试
如前所述,make test
通过pytest-xdist
插件(-n X
参数,例如-n 2
以运行 2 个并行作业)并行运行测试。
pytest-xdist
的--dist=
选项允许控制如何对测试进行分组。--dist=loadfile
将位于一个文件中的测试放在同一个进程中。
由于执行测试的顺序不同且不可预测,如果使用pytest-xdist
运行测试套件会产生失败(意味着我们有一些未检测到的耦合测试),请使用pytest-replay以相同顺序重放测试,这应该有助于以某种方式将失败序列减少到最小。
测试顺序和重复
最好多次重复测试,按顺序、随机或成组进行,以检测任何潜在的相互依赖和与状态相关的错误(拆除)。直接的多次重复只是用来检测由于 DL 的随机性而暴露出的一些问题。
重复测试
pip install pytest-flakefinder
然后多次运行每个测试(默认为 50 次):
pytest --flake-finder --flake-runs=5 tests/test_failing_test.py
此插件不与pytest-xdist
的-n
标志一起使用。
还有另一个插件pytest-repeat
,但它与unittest
不兼容。
以随机顺序运行测试
pip install pytest-random-order
重要提示:存在pytest-random-order
将自动随机化测试,无需更改配置或命令行选项。
正如前面所解释的,这允许检测耦合测试 - 一个测试的状态影响另一个测试的状态。当安装了pytest-random-order
时,它将打印用于该会话的随机种子,例如:
pytest tests
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
因此,如果给定的特定顺序失败,您可以通过添加确切的种子来重现它,例如:
pytest --random-order-seed=573663
[...]
Using --random-order-bucket=module
Using --random-order-seed=573663
只有在使用完全相同的测试列表(或根本没有列表)时,它才会重现确切的顺序。一旦开始手动缩小列表,就不能再依赖种子,而必须按照它们失败的确切顺序手动列出它们,并告诉 pytest 不要随机化它们,而是使用--random-order-bucket=none
,例如:
pytest --random-order-bucket=none tests/test_a.py tests/test_c.py tests/test_b.py
要禁用所有测试的洗牌:
pytest --random-order-bucket=none
默认情况下,隐含--random-order-bucket=module
,这将在模块级别对文件进行洗牌。它还可以在class
、package
、global
和none
级别进行洗牌。有关完整详情,请参阅其文档。
另一个随机化的替代方案是:pytest-randomly
。这个模块具有非常相似的功能/接口,但它没有pytest-random-order
中可用的桶模式。一旦安装,它也会强制自身。
外观和感觉变化
pytest-sugar
pytest-sugar 是一个插件,可以改善外观和感觉,添加进度条,并立即显示失败的测试和断言。安装后会自动激活。
pip install pytest-sugar
要在没有它的情况下运行测试,请运行:
pytest -p no:sugar
或者卸载它。
报告每个子测试的名称及其进度
通过pytest
运行单个或一组测试(在pip install pytest-pspec
之后):
pytest --pspec tests/test_optimization.py
立即显示失败的测试
pytest-instafail 立即显示失败和错误,而不是等到测试会话结束。
pip install pytest-instafail
pytest --instafail
要 GPU 还是不要 GPU
在启用 GPU 的设置中,要在仅 CPU 模式下测试,请添加CUDA_VISIBLE_DEVICES=""
:
CUDA_VISIBLE_DEVICES="" pytest tests/utils/test_logging.py
或者如果您有多个 GPU,可以指定pytest
要使用的 GPU。例如,如果您有 GPU 0
和 1
,则可以仅使用第二个 GPU 运行:
CUDA_VISIBLE_DEVICES="1" pytest tests/utils/test_logging.py
这在您想要在不同的 GPU 上运行不同任务时非常方便。
一些测试必须在仅 CPU 上运行,其他测试可以在 CPU 或 GPU 或 TPU 上运行,另一些测试可以在多个 GPU 上运行。以下跳过装饰器用于设置测试的 CPU/GPU/TPU 要求:
-
require_torch
- 此测试仅在 torch 下运行 -
require_torch_gpu
- 与require_torch
相同,至少需要 1 个 GPU -
require_torch_multi_gpu
- 与require_torch
相同,至少需要 2 个 GPU -
require_torch_non_multi_gpu
- 与require_torch
相同,需要 0 个或 1 个 GPU -
require_torch_up_to_2_gpus
- 与require_torch
相同,需要 0 个或 1 个或 2 个 GPU -
require_torch_tpu
- 与require_torch
相同,至少需要 1 个 TPU
让我们在下表中描述 GPU 要求:
n 个 GPU | 装饰器 | --------±------------------------------- | >= 0 | @require_torch | >= 1 | @require_torch_gpu | >= 2 | @require_torch_multi_gpu | < 2 | @require_torch_non_multi_gpu | < 3 | @require_torch_up_to_2_gpus |
---|
例如,这是一个必须在有 2 个或更多个 GPU 可用且已安装 pytorch 时运行的测试:
@require_torch_multi_gpu
def test_example_with_multi_gpu():
如果一个测试需要tensorflow
,请使用require_tf
装饰器。例如:
@require_tf
def test_tf_thing_with_tensorflow():
这些装饰器可以叠加。例如,如果一个测试很慢并且在 pytorch 下至少需要一个 GPU,这是如何设置的:
@require_torch_gpu
@slow
def test_example_slow_on_gpu():
一些装饰器如@parametrized
会重写测试名称,因此@require_*
跳过装饰器必须在最后列出才能正常工作。以下是正确使用的示例:
@parameterized.expand(...)
@require_torch_multi_gpu
def test_integration_foo():
这个顺序问题在@pytest.mark.parametrize
中不存在,您可以将其放在最前面或最后面,它仍然有效。但它只适用于非单元测试。
在测试中:
- 有多少个 GPU 可用:
from transformers.testing_utils import get_gpu_count
n_gpu = get_gpu_count() # works with torch and tf
使用特定的 PyTorch 后端或设备进行测试
要在特定的 torch 设备上运行测试套件,请添加TRANSFORMERS_TEST_DEVICE="$device"
,其中$device
是目标后端。例如,要仅在 CPU 上测试:
TRANSFORMERS_TEST_DEVICE="cpu" pytest tests/utils/test_logging.py
此变量对于测试自定义或不太常见的 PyTorch 后端(如mps
)很有用。它还可以用于通过定位特定 GPU 或在仅 CPU 模式下进行测试来实现与CUDA_VISIBLE_DEVICES
相同的效果。
在第一次导入torch
后,某些设备将需要额外的导入。这可以使用环境变量TRANSFORMERS_TEST_BACKEND
指定:
TRANSFORMERS_TEST_BACKEND="torch_npu" pytest tests/utils/test_logging.py
替代后端可能还需要替换特定于设备的函数。例如,torch.cuda.manual_seed
可能需要替换为特定于设备的种子设置器,如torch.npu.manual_seed
,以正确设置设备上的随机种子。在运行测试套件时指定新的后端和后端特定设备函数时,创建一个 Python 设备规范文件,格式如下:
import torch
import torch_npu
# !! Further additional imports can be added here !!
# Specify the device name (eg. 'cuda', 'cpu', 'npu')
DEVICE_NAME = 'npu'
# Specify device-specific backends to dispatch to.
# If not specified, will fallback to 'default' in 'testing_utils.py`
MANUAL_SEED_FN = torch.npu.manual_seed
EMPTY_CACHE_FN = torch.npu.empty_cache
DEVICE_COUNT_FN = torch.npu.device_count
此格式还允许指定所需的任何其他导入。要使用此文件替换测试套件中的等效方法,请将环境变量TRANSFORMERS_TEST_DEVICE_SPEC
设置为规范文件的路径。
目前,只支持MANUAL_SEED_FN
、EMPTY_CACHE_FN
和DEVICE_COUNT_FN
用于特定设备的分发。
分布式训练
pytest
不能直接处理分布式训练。如果尝试这样做-子进程不会做正确的事情,最终会认为它们是pytest
并开始循环运行测试套件。但是,如果生成一个正常进程,然后生成多个工作进程并管理 IO 管道,则可以正常工作。
以下是一些使用它的测试:
要直接跳转到执行点,请在这些测试中搜索execute_subprocess_async
调用。
您至少需要 2 个 GPU 才能看到这些测试的运行情况:
CUDA_VISIBLE_DEVICES=0,1 RUN_SLOW=1 pytest -sv tests/test_trainer_distributed.py
输出捕获
在测试执行期间,发送到stdout
和stderr
的任何输出都将被捕获。如果测试或设置方法失败,则通常会显示其相应的捕获输出以及失败的回溯。
要禁用输出捕获并正常获取stdout
和stderr
,请使用-s
或--capture=no
:
pytest -s tests/utils/test_logging.py
将测试结果发送到 JUnit 格式的输出:
py.test tests --junitxml=result.xml
颜色控制
要没有颜色(例如,白色背景上的黄色不可读):
pytest --color=no tests/utils/test_logging.py
将测试报告发送到在线粘贴服务
为每个测试失败创建一个 URL:
pytest --pastebin=failed tests/utils/test_logging.py
这将向远程粘贴服务提交测试运行信息,并为每个失败提供一个 URL。您可以像往常一样选择测试,或者例如添加-x
,如果您只想发送一个特定的失败。
为整个测试会话日志创建一个 URL:
pytest --pastebin=all tests/utils/test_logging.py
编写测试
🤗 transformers 测试基于unittest
,但由pytest
运行,因此大多数情况下可以使用两个系统的功能。
您可以在这里阅读支持的功能,但要记住的重要事情是大多数pytest
固定装置不起作用。也不是参数化,但我们使用模块parameterized
以类似的方式工作。
参数化
经常需要多次运行相同的测试,但使用不同的参数。可以从测试内部完成,但是那样就无法仅为一个参数集运行该测试。
# test_this1.py
import unittest
from parameterized import parameterized
class TestMathUnitTest(unittest.TestCase):
@parameterized.expand( [
("negative", -1.5, -2.0),
("integer", 1, 1.0),
("large fraction", 1.6, 1),
] )
def test_floor(self, name, input, expected):
assert_equal(math.floor(input), expected)
现在,默认情况下,此测试将运行 3 次,每次将test_floor
的最后 3 个参数分配给参数列表中的相应参数。
您可以仅运行negative
和integer
参数集:
pytest -k "negative and integer" tests/test_mytest.py
或除了negative
子测试外的所有子测试,使用:
pytest -k "not negative" tests/test_mytest.py
除了使用刚提到的-k
过滤器,您可以找出每个子测试的确切名称,并使用其确切名称运行任何或所有子测试。
pytest test_this1.py --collect-only -q
它将列出:
test_this1.py::TestMathUnitTest::test_floor_0_negative
test_this1.py::TestMathUnitTest::test_floor_1_integer
test_this1.py::TestMathUnitTest::test_floor_2_large_fraction
现在您可以仅运行 2 个特定的子测试:
pytest test_this1.py::TestMathUnitTest::test_floor_0_negative test_this1.py::TestMathUnitTest::test_floor_1_integer
模块parameterized已经是transformers
的开发依赖项,适用于unittests
和pytest
测试。
但是,如果测试不是unittest
,则可以使用pytest.mark.parametrize
(或者您可能会看到它在一些现有测试中使用,主要在examples
下)。
以下是相同的示例,这次使用pytest
的parametrize
标记:
# test_this2.py
import pytest
@pytest.mark.parametrize( "name, input, expected",
[
("negative", -1.5, -2.0),
("integer", 1, 1.0),
("large fraction", 1.6, 1),
], )
def test_floor(name, input, expected):
assert_equal(math.floor(input), expected)
与parameterized
一样,使用pytest.mark.parametrize
可以对要运行的子测试进行精细控制,如果-k
过滤器无法完成任务。除此之外,此参数化函数为子测试创建了一组略有不同的名称。以下是它们的样子:
pytest test_this2.py --collect-only -q
它将列出:
test_this2.py::test_floor[integer-1-1.0]
test_this2.py::test_floor[negative--1.5--2.0]
test_this2.py::test_floor[large fraction-1.6-1]
现在您可以仅运行特定的测试:
pytest test_this2.py::test_floor[negative--1.5--2.0] test_this2.py::test_floor[integer-1-1.0]
与前面的示例相同。
文件和目录
在测试中,我们经常需要知道事物相对于当前测试文件的位置,这并不是微不足道的,因为测试可能会从多个目录调用,或者可能位于具有不同深度的子目录中。一个辅助类transformers.test_utils.TestCasePlus
通过整理所有基本路径并提供易于访问的访问器来解决这个问题:
-
pathlib
对象(全部完全解析):-
test_file_path
- 当前测试文件路径,即__file__
-
test_file_dir
- 包含当前测试文件的目录 -
tests_dir
-tests
测试套件的目录 -
examples_dir
-examples
测试套件的目录 -
repo_root_dir
- 仓库的目录 -
src_dir
-src
的目录(即transformers
子目录所在的地方)
-
-
字符串化路径—与上述相同,但这些返回路径作为字符串,而不是
pathlib
对象:-
test_file_path_str
-
test_file_dir_str
-
tests_dir_str
-
examples_dir_str
-
repo_root_dir_str
-
src_dir_str
-
要开始使用这些,您只需要确保测试位于transformers.test_utils.TestCasePlus
的子类中。例如:
from transformers.testing_utils import TestCasePlus
class PathExampleTest(TestCasePlus):
def test_something_involving_local_locations(self):
data_dir = self.tests_dir / "fixtures/tests_samples/wmt_en_ro"
如果您不需要通过pathlib
操纵路径,或者只需要路径作为字符串,您可以始终在pathlib
对象上调用str()
或使用以_str
结尾的访问器。例如:
from transformers.testing_utils import TestCasePlus
class PathExampleTest(TestCasePlus):
def test_something_involving_stringified_locations(self):
examples_dir = self.examples_dir_str
临时文件和目录
使用唯一的临时文件和目录对于并行测试运行至关重要,以便测试不会覆盖彼此的数据。此外,我们希望在每个创建它们的测试结束时删除临时文件和目录。因此,使用像tempfile
这样满足这些需求的软件包是至关重要的。
然而,在调试测试时,您需要能够看到临时文件或目录中的内容,并且希望知道其确切路径,而不是在每次测试重新运行时随机化。
辅助类transformers.test_utils.TestCasePlus
最适合用于这些目的。它是unittest.TestCase
的子类,因此我们可以在测试模块中轻松继承它。
以下是其用法示例:
from transformers.testing_utils import TestCasePlus
class ExamplesTests(TestCasePlus):
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
此代码创建一个唯一的临时目录,并将tmp_dir
设置为其位置。
- 创建一个唯一的临时目录:
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir()
tmp_dir
将包含创建的临时目录的路径。它将在测试结束时自动删除。
- 创建我选择的临时目录,在测试开始之前确保它为空,并在测试结束后不清空它。
def test_whatever(self):
tmp_dir = self.get_auto_remove_tmp_dir("./xxx")
当您想要监视特定目录并确保之前的测试没有在其中留下任何数据时,这很有用。
-
您可以通过直接覆盖
before
和after
参数来覆盖默认行为,从而导致以下行为之一:-
before=True
:临时目录将始终在测试开始时清除。 -
before=False
:如果临时目录已经存在,则任何现有文件将保留在那里。 -
after=True
:临时目录将始终在测试结束时被删除。 -
after=False
:临时目录将始终在测试结束时保持不变。
-
为了安全运行等效于rm -r
的操作,只允许项目存储库检出的子目录,如果使用了显式的tmp_dir
,则不会错误地删除任何/tmp
或类似的文件系统重要部分。即请始终传递以./
开头的路径。
每个测试可以注册多个临时目录,它们都将自动删除,除非另有要求。
临时 sys.path 覆盖
如果需要临时覆盖sys.path
以从另一个测试中导入,例如,可以使用ExtendSysPath
上下文管理器。示例:
import os
from transformers.testing_utils import ExtendSysPath
bindir = os.path.abspath(os.path.dirname(__file__))
with ExtendSysPath(f"{bindir}/.."):
from test_trainer import TrainerIntegrationCommon # noqa
跳过测试
当发现错误并编写新测试但尚未修复错误时,这很有用。为了能够将其提交到主存储库,我们需要确保在make test
期间跳过它。
方法:
-
skip表示您期望测试仅在满足某些条件时才通过,否则 pytest 应该跳过运行测试。常见示例是在非 Windows 平台上跳过仅适用于 Windows 的测试,或者跳过依赖于当前不可用的外部资源的测试(例如数据库)。
-
xfail表示您期望由于某种原因测试失败。一个常见示例是测试尚未实现的功能,或者尚未修复的错误。当一个测试尽管预期失败(标记为 pytest.mark.xfail)仍然通过时,它是一个 xpass,并将在测试摘要中报告。
两者之间的一个重要区别是,skip
不运行测试,而xfail
会运行。因此,如果导致一些糟糕状态的有缺陷代码会影响其他测试,请不要使用xfail
。
实施
- 以下是如何无条件跳过整个测试:
@unittest.skip("this bug needs to be fixed")
def test_feature_x():
或通过 pytest:
@pytest.mark.skip(reason="this bug needs to be fixed")
或xfail
方式:
@pytest.mark.xfail
def test_feature_x():
以下是如何根据测试内部检查跳过测试的方法:
def test_feature_x():
if not has_something():
pytest.skip("unsupported configuration")
或整个模块:
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
或xfail
方式:
def test_feature_x():
pytest.xfail("expected to fail until bug XYZ is fixed")
- 以下是如何跳过模块中的所有测试,如果某个导入丢失:
docutils = pytest.importorskip("docutils", minversion="0.3")
- 根据条件跳过测试:
@pytest.mark.skipif(sys.version_info < (3,6), reason="requires python3.6 or higher")
def test_feature_x():
或:
@unittest.skipIf(torch_device == "cpu", "Can't do half precision")
def test_feature_x():
或跳过整个模块:
@pytest.mark.skipif(sys.platform == 'win32', reason="does not run on windows")
class TestClass():
def test_feature_x(self):
更多详细信息、示例和方法请参见这里。
慢速测试
测试库不断增长,一些测试需要几分钟才能运行,因此我们无法等待一个小时才能完成 CI 的测试套件。因此,除了一些必要测试的例外,慢速测试应该标记为下面的示例:
from transformers.testing_utils import slow
@slow
def test_integration_foo():
一旦将测试标记为@slow
,要运行这些测试,请设置RUN_SLOW=1
环境变量,例如:
RUN_SLOW=1 pytest tests
一些装饰器如@parameterized
会重写测试名称,因此@slow
和其他跳过装饰器@require_*
必须在最后列出才能正常工作。以下是正确使用的示例:
@parameteriz ed.expand(...)
@slow
def test_integration_foo():
正如本文档开头所解释的,慢速测试会定期运行,而不是在 PR 的 CI 检查中运行。因此,可能会在提交 PR 时错过一些问题并合并。这些问题将在下一个定期 CI 作业中被捕获。但这也意味着在提交 PR 之前在您的计算机上运行慢速测试非常重要。
以下是选择哪些测试应标记为慢速的大致决策机制:
如果测试侧重于库的内部组件(例如,建模文件、标记文件、流水线),那么我们应该在非慢速测试套件中运行该测试。如果侧重于库的其他方面,例如文档或示例,则应在慢速测试套件中运行这些测试。然后,为了完善这种方法,我们应该有例外情况:
-
所有需要下载一组大量权重或大于~50MB 的数据集(例如,模型或分词器集成测试,管道集成测试)的测试都应该设置为慢速。如果要添加新模型,应该创建并上传到 hub 一个其微型版本(具有随机权重)用于集成测试。这将在以下段落中讨论。
-
所有需要进行训练但没有专门优化为快速的测试都应该设置为慢速。
-
如果其中一些应该是非慢速测试的测试非常慢,可以引入异常,并将它们设置为
@slow
。自动建模测试,将大文件保存和加载到磁盘上,是一个被标记为@slow
的测试的很好的例子。 -
如果一个测试在 CI 上完成时间不到 1 秒(包括下载),那么它应该是一个正常的测试。
总的来说,所有非慢速测试都需要完全覆盖不同的内部功能,同时保持快速。例如,通过使用具有随机权重的特别创建的微型模型进行测试,可以实现显著的覆盖范围。这些模型具有最小数量的层(例如 2),词汇量(例如 1000)等。然后@slow
测试可以使用大型慢速模型进行定性测试。要查看这些的用法,只需搜索带有tiny的模型:
grep tiny tests examples
这里是一个脚本的示例,创建了微型模型stas/tiny-wmt19-en-de。您可以轻松调整它以适应您特定模型的架构。
如果例如下载一个巨大模型的开销很大,那么很容易错误地测量运行时间,但如果您在本地测试它,下载的文件将被缓存,因此下载时间不会被测量。因此,应该查看 CI 日志中的执行速度报告(pytest --durations=0 tests
的输出)。
该报告还有助于找到未标记为慢的慢异常值,或者需要重新编写以提高速度的测试。如果您注意到 CI 上的测试套件开始变慢,此报告的顶部列表将显示最慢的测试。
测试 stdout/stderr 输出
为了测试写入stdout
和/或stderr
的函数,测试可以使用pytest
的capsys 系统来访问这些流。以下是实现这一目标的方法:
import sys
def print_to_stdout(s):
print(s)
def print_to_stderr(s):
sys.stderr.write(s)
def test_result_and_stdout(capsys):
msg = "Hello"
print_to_stdout(msg)
print_to_stderr(msg)
out, err = capsys.readouterr() # consume the captured output streams
# optional: if you want to replay the consumed streams:
sys.stdout.write(out)
sys.stderr.write(err)
# test:
assert msg in out
assert msg in err
当然,大多数情况下,stderr
将作为异常的一部分出现,因此在这种情况下必须使用 try/except:
def raise_exception(msg):
raise ValueError(msg)
def test_something_exception():
msg = "Not a good value"
error = ""
try:
raise_exception(msg)
except Exception as e:
error = str(e)
assert msg in error, f"{msg} is in the exception:\n{error}"
另一种捕获 stdout 的方法是通过contextlib.redirect_stdout
:
from io import StringIO
from contextlib import redirect_stdout
def print_to_stdout(s):
print(s)
def test_result_and_stdout():
msg = "Hello"
buffer = StringIO()
with redirect_stdout(buffer):
print_to_stdout(msg)
out = buffer.getvalue()
# optional: if you want to replay the consumed streams:
sys.stdout.write(out)
# test:
assert msg in out
捕获 stdout 的一个重要潜在问题是它可能包含\r
字符,这些字符在正常的print
中会重置到目前为止已经打印的所有内容。对于pytest
没有问题,但对于pytest -s
,这些字符会包含在缓冲区中,因此为了能够在有和没有-s
的情况下运行测试,必须对捕获的输出进行额外的清理,使用re.sub(r'~.*\r', '', buf, 0, re.M)
。
但是,我们有一个辅助上下文管理器包装器,可以自动处理所有这些,无论它是否包含一些\r
,所以这很简单:
from transformers.testing_utils import CaptureStdout
with CaptureStdout() as cs:
function_that_writes_to_stdout()
print(cs.out)
这里是一个完整的测试示例:
from transformers.testing_utils import CaptureStdout
msg = "Secret message\r"
final = "Hello World"
with CaptureStdout() as cs:
print(msg + final)
assert cs.out == final + "\n", f"captured: {cs.out}, expecting {final}"
如果您想捕获stderr
,请改用CaptureStderr
类:
from transformers.testing_utils import CaptureStderr
with CaptureStderr() as cs:
function_that_writes_to_stderr()
print(cs.err)
如果需要同时捕获两个流,请使用父类CaptureStd
:
from transformers.testing_utils import CaptureStd
with CaptureStd() as cs:
function_that_writes_to_stdout_and_stderr()
print(cs.err, cs.out)
此外,为了帮助调试测试问题,默认情况下这些上下文管理器会在退出上下文时自动重放捕获的流。
捕获记录器流
如果您需要验证记录器的输出,可以使用CaptureLogger
:
from transformers import logging
from transformers.testing_utils import CaptureLogger
msg = "Testing 1, 2, 3"
logging.set_verbosity_info()
logger = logging.get_logger("transformers.models.bart.tokenization_bart")
with CaptureLogger(logger) as cl:
logger.info(msg)
assert cl.out, msg + "\n"
使用环境变量进行测试
如果您想测试特定测试的环境变量的影响,可以使用辅助装饰器transformers.testing_utils.mockenv
from transformers.testing_utils import mockenv
class HfArgumentParserTest(unittest.TestCase):
@mockenv(TRANSFORMERS_VERBOSITY="error")
def test_env_override(self):
env_level_str = os.getenv("TRANSFORMERS_VERBOSITY", None)
有时需要调用外部程序,这需要在os.environ
中设置PYTHONPATH
以包括多个本地路径。一个辅助类transformers.test_utils.TestCasePlus
来帮助:
from transformers.testing_utils import TestCasePlus
class EnvExampleTest(TestCasePlus):
def test_external_prog(self):
env = self.get_env()
# now call the external program, passing `env` to it
根据测试文件是在tests
测试套件还是examples
下,它将正确设置env[PYTHONPATH]
以包括这两个目录之一,并且还将src
目录设置为确保针对当前存储库进行测试,最后还将设置env[PYTHONPATH]
在调用测试之前已经设置的任何内容。
这个辅助方法创建了os.environ
对象的副本,因此原始对象保持不变。
获得可重现的结果
在某些情况下,您可能希望为测试去除随机性。要获得相同的可重现结果集,您需要修复种子:
seed = 42
# python RNG
import random
random.seed(seed)
# pytorch RNGs
import torch
torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
# numpy RNG
import numpy as np
np.random.seed(seed)
# tf RNG
tf.random.set_seed(seed)
调试测试
要在警告点启动调试器,请执行以下操作:
pytest tests/utils/test_logging.py -W error::UserWarning --pdb
使用 github actions 工作流
要触发自动推送工作流 CI 作业,必须:
-
在
transformers
源上创建一个新分支(不是分叉!)。 -
分支名称必须以
ci_
或ci-
开头(main
也会触发它,但我们不能在main
上创建 PR)。它还仅针对特定路径触发 - 如果自从编写本文以来发生了更改,您可以在此处找到最新的定义,位于*push:*下。 -
从此分支创建一个 PR。
-
然后您可以在这里看到该作业。如果有积压,它可能不会立即运行。
测试实验性 CI 功能
测试 CI 功能可能会有潜在问题,因为它可能会干扰正常的 CI 功能。因此,如果要添加新的 CI 功能,应按以下步骤进行。
-
创建一个新的专用作业,测试需要测试的内容
-
新作业必须始终成功,以便为我们提供绿色的 ✓(下面有详细信息)。
-
让它运行几天,看看各种不同类型的 PR 是否可以运行在上面(用户分支,非分叉分支,源自 github.com UI 直接文件编辑的分支,各种强制推送等等 - 有很多),同时监视实验性作业的日志(而不是整体作业绿色,因为它故意总是绿色)
-
当一切都很稳定时,然后将新更改合并到现有作业中。
这样,对 CI 功能本身的实验就不会干扰正常的工作流程。
现在我们如何确保工作始终成功,同时新的 CI 功能正在开发中?
一些 CI,如 TravisCI 支持 ignore-step-failure,并将整体作业报告为成功,但截至目前,CircleCI 和 Github Actions 不支持该功能。
因此,可以使用以下解决方法:
-
在运行命令的开头使用
set +euo pipefail
来抑制 bash 脚本中的大多数潜在故障。 -
最后一个命令必须成功:
echo "done"
或只需true
即可
这是一个例子:
- run:
name: run CI experiment
command: |
set +euo pipefail
echo "setting run-all-despite-any-errors-mode"
this_command_will_fail
echo "but bash continues to run"
# emulate another failure
false
# but the last command must be a success
echo "during experiment do not remove: reporting success to CI, even if there were failures"
对于简单的命令,您也可以这样做:
cmd_that_may_fail || true
当结果令人满意时,将实验步骤或作业与其余正常作业集成在一起,同时删除set +euo pipefail
或您可能添加的任何其他内容,以确保实验性作业不会干扰正常的 CI 功能。
如果只能为实验步骤设置类似于allow-failure
的内容,并且让它失败而不影响 PR 的整体状态,那么整个过程将变得更加容易。但正如前面提到的,CircleCI 和 Github Actions 目前不支持这一点。
您可以为此功能投票,并查看 CI 特定线程的进展:
拉取请求上的检查
当您在🤗 Transformers 上打开拉取请求时,将运行相当多的检查,以确保您添加的补丁不会破坏任何现有内容。这些检查有四种类型:
-
常规测试
-
文档构建
-
代码和文档样式
-
一般存储库一致性
在这份文档中,我们将尝试解释这些各种检查是什么,以及背后的原因,以及如果其中一个在您的 PR 上失败时如何在本地调试它们。
请注意,理想情况下,它们要求您进行开发安装:
pip install transformers[dev]
或者进行可编辑安装:
pip install -e .[dev]
在 Transformers 存储库内。由于 Transformers 的可选依赖项数量大大增加,您可能无法获得所有依赖项。如果开发安装失败,请确保安装您正在使用的深度学习框架(PyTorch、TensorFlow 和/或 Flax),然后执行以下操作:
pip install transformers[quality]
或者进行可编辑安装:
pip install -e .[quality]
测试
所有以ci/circleci: run_tests_
开头的作业都运行 Transformers 测试套件的部分。这些作业中的每一个都专注于库的某个部分在特定环境中运行:例如,ci/circleci: run_tests_pipelines_tf
在仅安装 TensorFlow 的环境中运行 pipelines 测试。
请注意,为了避免在测试的模块中没有真正更改时运行测试,每次只运行测试套件的一部分:运行一个实用程序来确定库中的差异在 PR 之前和之后(GitHub 在“文件更改”选项卡中显示给您的内容),并选择受该差异影响的测试。该实用程序可以在本地运行:
python utils/tests_fetcher.py
从 Transformers 存储库的根目录开始。它将:
-
检查差异中的每个文件,看看更改是在代码中还是仅在注释或文档字符串中。只保留具有真实代码更改的文件。
-
构建一个内部映射,为库源代码的每个文件提供递归影响的所有文件列表。如果模块 B 导入模块 A,则模块 A 被认为影响模块 B。对于递归影响,我们需要一个从模块 A 到模块 B 的模块链,其中每个模块导入前一个模块。
-
将此映射应用于第 1 步中收集的文件,这给出了 PR 影响的模型文件列表。
-
将这些文件映射到它们对应的测试文件,并获取要运行的测试列表。
在本地执行脚本时,您应该得到步骤 1、3 和 4 的结果打印出来,从而知道运行哪些测试。该脚本还将创建一个名为test_list.txt
的文件,其中包含要运行的测试列表,您可以使用以下命令在本地运行它们:
python -m pytest -n 8 --dist=loadfile -rA -s $(cat test_list.txt)
以防有任何遗漏,完整的测试套件也会每天运行。
文档构建
build_pr_documentation
作业构建并生成文档预览,以确保一切在合并您的 PR 后看起来都没问题。机器人将在您的 PR 中添加一个预览文档的链接。您对 PR 所做的任何更改都会自动更新到预览中。如果文档构建失败,请点击失败作业旁边的详细信息,查看出了什么问题。通常,错误可能只是toctree
中缺少文件。
如果您有兴趣在本地构建或预览文档,请查看文档文件夹中的README.md
。
代码和文档样式
对所有源文件、示例和测试应用代码格式化,使用black
和ruff
。我们还有一个自定义工具负责格式化文档字符串和rst
文件(utils/style_doc.py
),以及 Transformers __init__.py
文件中执行的延迟导入顺序(utils/custom_init_isort.py
)。所有这些都可以通过执行来启动
make style
CI 检查这些已应用于ci/circleci: check_code_quality
检查中。它还运行ruff
,它将基本查看您的代码,并在找到未定义的变量或未使用的变量时发出投诉。要在本地运行该检查,请使用
make quality
这可能需要很长时间,因此要仅对当前分支中修改的文件运行相同的操作,请运行
make fixup
这个最后的命令也将运行所有额外的检查,以确保存储库的一致性。让我们来看看它们。
存储库的一致性
这将汇总所有测试,以确保您的 PR 使存储库保持良好状态,并由ci/circleci: check_repository_consistency
检查执行。您可以通过执行以下操作在本地运行该检查:
make repo-consistency
这些检查包括:
-
所有添加到 init 中的对象都有文档记录(由
utils/check_repo.py
执行) -
所有
__init__.py
文件在其两个部分中具有相同的内容(由utils/check_inits.py
执行) -
所有被识别为从另一个模块复制的代码与原始代码一致(由
utils/check_copies.py
执行) -
所有配置类在其 docstrings 中至少提到一个有效的检查点(由
utils/check_config_docstrings.py
执行) -
所有配置类只包含在相应建模文件中使用的属性(由
utils/check_config_attributes.py
执行) -
README 和文档索引的翻译与主 README 中的模型列表相同(由
utils/check_copies.py
执行) -
文档中的自动生成表是最新的(由
utils/check_table.py
执行) -
即使没有安装所有可选依赖项,库中仍有所有对象可用(由
utils/check_dummies.py
执行) -
所有 docstrings 都正确记录了对象签名中的参数(由
utils/check_docstrings.py
执行)
如果此检查失败,前两项需要手动修复,最后四项可以通过运行以下命令自动修复
make fix-copies
额外的检查涉及添加新模型的 PR,主要是:
-
所有添加的模型都在 Auto-mapping 中(由
utils/check_repo.py
执行) -
所有模型都经过适当的测试(由
utils/check_repo.py
执行)
检查副本
由于 Transformers 库在模型代码方面非常有主见,每个模型应该完全在单个文件中实现,而不依赖于其他模型,因此我们添加了一个机制,检查给定模型的层的代码副本是否与原始代码保持一致。这样,当有 bug 修复时,我们可以看到所有其他受影响的模型,并选择将修改传递下去或打破副本。
如果一个文件是另一个文件的完全副本,您应该在utils/check_copies.py
的常量FULL_COPIES
中注册它。
这个机制依赖于形式为# Copied from xxx
的注释。xxx
应包含被复制的类或函数的完整路径,该类或函数被复制在下面。例如,RobertaSelfOutput
是BertSelfOutput
类的直接副本,因此您可以在这里看到它有一个注释:
# Copied from transformers.models.bert.modeling_bert.BertSelfOutput
请注意,您可以将此应用于整个类,也可以将其应用于从中复制的相关方法。例如,在这里中,您可以看到RobertaPreTrainedModel._init_weights
是从BertPreTrainedModel
中的相同方法复制的,并带有注释:
# Copied from transformers.models.bert.modeling_bert.BertPreTrainedModel._init_weights
有时副本完全相同,除了名称:例如,在RobertaAttention
中,我们使用RobertaSelfAttention
而不是BertSelfAttention
,但除此之外,代码完全相同。这就是为什么# Copied from
支持简单的字符串替换,语法如下:Copied from xxx with foo->bar
。这意味着代码被复制,所有foo
的实例都被替换为bar
。您可以在RobertaAttention
中看到它是如何使用的这里。
# Copied from transformers.models.bert.modeling_bert.BertAttention with Bert->Roberta
请注意箭头周围不应该有任何空格(除非该空格是要替换的模式的一部分)。
您可以添加几个用逗号分隔的模式。例如,在这里CamemberForMaskedLM
是RobertaForMaskedLM
的直接副本,有两个替换:Roberta
替换为Camembert
和ROBERTA
替换为CAMEMBERT
。您可以在这里看到这是如何完成的,带有注释:
# Copied from transformers.models.roberta.modeling_roberta.RobertaForMaskedLM with Roberta->Camembert, ROBERTA->CAMEMBERT
如果顺序很重要(因为其中一个替换可能与先前的替换冲突),则替换从左到右执行。
如果替换改变了格式(例如,如果您用一个非常长的名称替换一个短名称),则在应用自动格式化程序后会检查副本。
当模式只是相同替换的不同大小写形式(具有大写和小写变体)时,另一种方法就是添加选项all-casing
。这里是MobileBertForSequenceClassification
中的一个示例,带有注释:
# Copied from transformers.models.bert.modeling_bert.BertForSequenceClassification with Bert->MobileBert all-casing
在这种情况下,代码是通过替换BertForSequenceClassification
复制的:
-
Bert
替换为MobileBert
(例如,在 init 中使用MobileBertModel
时) -
bert
替换为mobilebert
(例如在定义self.mobilebert
时) -
BERT
替换为MOBILEBERT
(在常量MOBILEBERT_INPUTS_DOCSTRING
中)
概念指南
哲学
🤗 Transformers 是一个为以下人群构建的主观库:
-
希望使用、研究或扩展大规模 Transformer 模型的机器学习研究人员和教育工作者。
-
希望微调这些模型或在生产中使用它们的实践者。
-
只想下载预训练模型并用于解决特定机器学习任务的工程师。
该库设计时有两个强烈的目标:
- 尽可能简单快捷地使用:
-
我们强烈限制了用户接触的抽象数量,事实上,几乎没有抽象,只需要三个标准类来使用每个模型:配置、模型和一个预处理类(NLP 的分词器、视觉的图像处理器、音频的特征提取器以及多模态输入的处理器)。
-
所有这些类都可以通过使用通用的
from_pretrained()
方法从预训练实例中简单统一地初始化,该方法会从Hugging Face Hub提供的预训练检查点或您自己保存的检查点下载(如果需要),缓存并加载相关的类实例和相关数据(配置的超参数、分词器的词汇和模型的权重)。 -
除了这三个基本类之外,该库还提供两个 API:pipeline()用于快速在给定任务上使用模型进行推断,以及 Trainer 用于快速训练或微调 PyTorch 模型(所有 TensorFlow 模型都兼容
Keras.fit
)。 -
因此,该库不是神经网络的模块化工具箱。如果您想扩展或构建该库,只需使用常规的 Python、PyTorch、TensorFlow、Keras 模块,并从库的基类继承以重用模型加载和保存等功能。如果您想了解更多关于我们模型编码哲学的信息,请查看我们的Repeat Yourself博客文章。
- 提供性能尽可能接近原始模型的最新模型:
-
我们至少为每种架构提供一个示例,该示例重现了该架构的官方作者提供的结果。
-
代码通常尽可能接近原始代码库,这意味着一些 PyTorch 代码可能不像pytorchic那样,因为它可能是转换为 TensorFlow 代码,反之亦然。
另外几个目标:
-
尽可能一致地暴露模型的内部:
-
我们使用单个 API 访问完整的隐藏状态和注意力权重。
-
预处理类和基础模型 API 被标准化,以便轻松在模型之间切换。
-
-
整合一组有前途的工具,用于微调和研究这些模型:
-
一种简单而一致的方法,用于向词汇表和嵌入中添加新的标记以进行微调。
-
简单的方法来屏蔽和修剪 Transformer 头。
-
-
轻松在 PyTorch、TensorFlow 2.0 和 Flax 之间切换,允许使用一个框架进行训练,使用另一个框架进行推断。
主要概念
该库围绕每个模型的三种类型的类构建:
-
模型类可以是 PyTorch 模型(torch.nn.Module)、Keras 模型(tf.keras.Model)或 JAX/Flax 模型(flax.linen.Module),可以使用库中提供的预训练权重。
-
配置类存储构建模型所需的超参数(例如层数和隐藏大小)。您不总是需要自己实例化这些。特别是,如果您使用预训练模型而没有任何修改,创建模型将自动处理实例化配置(这是模型的一部分)。
-
预处理类将原始数据转换为模型接受的格式。一个 tokenizer 存储每个模型的词汇表,并提供编码和解码字符串的方法,以便将其转换为要馈送给模型的标记嵌入索引列表。图像处理器预处理视觉输入,特征提取器预处理音频输入,以及一个处理器处理多模态输入。
所有这些类都可以从预训练实例实例化,保存在本地,并通过三种方法在 Hub 上共享:
-
from_pretrained()
允许您从库本身提供的预训练版本(支持的模型可以在Model Hub上找到)或用户本地(或服务器上)存储的模型、配置和预处理类实例化。 -
save_pretrained()
允许您将模型、配置和预处理类保存在本地,以便可以使用from_pretrained()
重新加载。 -
push_to_hub()
允许您将模型、配置和预处理类共享到 Hub,以便所有人都可以轻松访问。
术语表
这个术语表定义了一般的机器学习和🤗 Transformers 术语,以帮助您更好地理解文档。
A
注意力掩码
注意力掩码是一个可选参数,用于将序列批处理在一起时使用。
www.youtube-nocookie.com/embed/M6adb1j2jPI
这个参数指示模型应该关注哪些令牌,哪些不应该。
例如,考虑这两个序列:
>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
>>> sequence_a = "This is a short sequence."
>>> sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."
>>> encoded_sequence_a = tokenizer(sequence_a)["input_ids"]
>>> encoded_sequence_b = tokenizer(sequence_b)["input_ids"]
编码版本的长度不同:
>>> len(encoded_sequence_a), len(encoded_sequence_b)
(8, 19)
因此,我们不能将它们直接放在同一个张量中。第一个序列需要填充到第二个序列的长度,或者第二个序列需要截断到第一个序列的长度。
在第一种情况下,ID 列表将通过填充索引进行扩展。我们可以将列表传递给分词器,并要求它这样填充:
>>> padded_sequences = tokenizer([sequence_a, sequence_b], padding=True)
我们可以看到,在第一句的右侧添加了 0,使其与第二句长度相同:
>>> padded_sequences["input_ids"]
[[101, 1188, 1110, 170, 1603, 4954, 119, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [101, 1188, 1110, 170, 1897, 1263, 4954, 119, 1135, 1110, 1120, 1655, 2039, 1190, 1103, 4954, 138, 119, 102]]
然后可以在 PyTorch 或 TensorFlow 中将其转换为张量。注意力掩码是一个二进制张量,指示填充索引的位置,以便模型不会关注它们。对于 BertTokenizer,1
表示应该关注的值,而0
表示填充值。这个注意力掩码在令牌化器返回的字典中,键为“attention_mask”:
>>> padded_sequences["attention_mask"]
[[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 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]]
自编码模型
参见编码器模型和掩码语言建模
自回归模型
参见因果语言建模和解码器模型
B
骨干
骨干是输出原始隐藏状态或特征的网络(嵌入和层)。通常与一个头连接,该头接受特征作为其输入以进行预测。例如,ViTModel 是一个没有特定头部的骨干。其他模型也可以使用VitModel
作为骨干,如 DPT。
C
因果语言建模
一个预训练任务,其中模型按顺序阅读文本,并预测下一个单词。通常通过阅读整个句子来完成,但在模型内部使用掩码来隐藏某个时间步长的未来令牌。
通道
彩色图像由三个通道中的值的某种组合组成:红色、绿色和蓝色(RGB),而灰度图像只有一个通道。在🤗 Transformers 中,通道可以是图像张量的第一个或最后一个维度:[n_channels
,height
,width
]或[height
,width
,n_channels
]。
连接主义时间分类(CTC)
一种允许模型学习而不知道输入和输出如何对齐的算法;CTC 计算给定输入的所有可能输出的分布,并从中选择最可能的输出。CTC 通常用于语音识别任务,因为语音不总是与文本干净地对齐,原因有很多,比如说话者的不同语速。
卷积
神经网络中的一种层类型,其中输入矩阵与较小的矩阵(卷积核或滤波器)逐元素相乘,然后在新矩阵中求和。这被称为卷积操作,它在整个输入矩阵上重复。每个操作应用于输入矩阵的不同部分。卷积神经网络(CNNs)通常用于计算机视觉。
D
DataParallel(DP)
在多个 GPU 上进行训练的并行技术,其中相同的设置被复制多次,每个实例接收一个不同的数据切片。处理是并行进行的,并且所有设置在每个训练步骤结束时都是同步的。
了解有关 DataParallel 如何工作的更多信息在这里。
解码器输入 ID
这个输入是特定于编码器-解码器模型的,包含将馈送给解码器的输入 ID。这些输入应该用于序列到序列任务,例如翻译或摘要,并且通常以每个模型特定的方式构建。
大多数编码器-解码器模型(BART,T5)会自行从labels
创建它们的decoder_input_ids
。在这种模型中,传递labels
是处理训练的首选方式。
请查看每个模型的文档,了解它们如何处理这些输入 ID 以进行序列到序列训练。
解码器模型
解码器模型,也称为自回归模型,涉及一个预训练任务(称为因果语言建模),其中模型按顺序阅读文本,并预测下一个单词。通常通过在某个时间步骤隐藏未来标记的掩码来读取整个句子来完成。
www.youtube-nocookie.com/embed/d_ixlCubqQw
深度学习(DL)
使用多层神经网络的机器学习算法。
E
编码器模型
也称为自编码模型,编码器模型接受输入(如文本或图像)并将它们转换为称为嵌入的简化数值表示。通常,编码器模型使用诸如 masked language modeling 之类的技术进行预训练,该技术对输入序列的部分进行掩码,并迫使模型创建更有意义的表示。
www.youtube-nocookie.com/embed/H39Z_720T5s
F
特征提取
将原始数据选择和转换为一组更具信息性和有用的特征的过程,以供机器学习算法使用。特征提取的一些示例包括将原始文本转换为词嵌入,从图像/视频数据中提取重要特征,如边缘或形状。
前馈分块
在 transformers 中的每个残差注意力块中,自注意力层通常后面跟着 2 个前馈层。前馈层的中间嵌入大小通常大于模型的隐藏大小(例如,对于bert-base-uncased
)。
对于大小为[batch_size, sequence_length]
的输入,存储中间前馈嵌入[batch_size, sequence_length, config.intermediate_size]
所需的内存可能占据了大部分内存使用量。Reformer: The Efficient Transformer的作者注意到,由于计算与sequence_length
维度无关,数学上等价于分别计算两个前馈层的输出嵌入[batch_size, config.hidden_size]_0, ..., [batch_size, config.hidden_size]_n
,然后将它们连接到[batch_size, sequence_length, config.hidden_size]
,其中n = sequence_length
,这会增加计算时间,减少内存使用,但产生数学上等价的结果。
对于使用函数 apply_chunking_to_forward()的模型,chunk_size
定义了并行计算的输出嵌入数量,从而定义了内存和时间复杂度之间的权衡。如果将chunk_size
设置为 0,则不会进行前馈分块。
微调模型
微调是一种迁移学习形式,涉及采用预训练模型,冻结其权重,并用新添加的 model head 替换输出层。模型头在目标数据集上进行训练。
查看Fine-tune a pretrained model教程以获取更多详细信息,并了解如何使用🤗 Transformers 微调模型。
H
头
模型头指的是神经网络的最后一层,它接受原始隐藏状态并将其投影到不同的维度。每个任务都有一个不同的模型头。例如:
-
GPT2ForSequenceClassification 是一个序列分类头 - 一个线性层 - 位于基础 GPT2Model 之上。
-
ViTForImageClassification 是一个图像分类头 - 位于基础 ViTModel 的
CLS
令牌的最终隐藏状态之上的线性层。 -
Wav2Vec2ForCTC 是一个带有 CTC)的语言建模头,位于基础 Wav2Vec2Model 之上。
我
图像补丁
基于视觉的 Transformer 模型将图像分成较小的补丁,这些补丁被线性嵌入,然后作为序列传递给模型。您可以在配置中找到模型的patch_size
- 或分辨率。
推理
推理是在训练完成后对新数据评估模型的过程。请参阅用于推理的 Pipeline教程,了解如何使用🤗 Transformers 执行推理。
输入 ID
输入 ID 通常是传递给模型的唯一必需参数。它们是标记索引,是构建序列的标记的数值表示,这些序列将作为模型的输入使用。
www.youtube-nocookie.com/embed/VFp38yj8h3A
每个分词器的工作方式不同,但基本机制保持不变。以下是使用 BERT 分词器的示例,它是一个WordPiece分词器:
>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
>>> sequence = "A Titan RTX has 24GB of VRAM"
分词器负责将序列拆分为分词器词汇表中可用的标记。
>>> tokenized_sequence = tokenizer.tokenize(sequence)
这些标记可以是单词或子词。例如,在这里,“VRAM”不在模型词汇表中,因此它被分割为“V”、“RA”和“M”。为了指示这些标记不是单独的单词而是同一个单词的部分,为“RA”和“M”添加了双哈希前缀:
>>> print(tokenized_sequence)
['A', 'Titan', 'R', '##T', '##X', 'has', '24', '##GB', 'of', 'V', '##RA', '##M']
然后这些标记可以转换为模型可理解的 ID。这可以通过直接将句子提供给分词器来完成,分词器利用Rust 实现的🤗 Tokenizers以获得最佳性能。
>>> inputs = tokenizer(sequence)
分词器返回一个包含其对应模型正常工作所需的所有参数的字典。标记索引位于键input_ids
下:
>>> encoded_sequence = inputs["input_ids"]
>>> print(encoded_sequence)
[101, 138, 18696, 155, 1942, 3190, 1144, 1572, 13745, 1104, 159, 9664, 2107, 102]
请注意,分词器会自动添加“特殊标记”(如果相关模型依赖于它们),这些特殊标记是模型有时使用的特殊 ID。
如果我们解码先前的 ID 序列,
>>> decoded_sequence = tokenizer.decode(encoded_sequence)
我们将看到
>>> print(decoded_sequence)
[CLS] A Titan RTX has 24GB of VRAM [SEP]
因为这是 BertModel 预期其输入的方式。
L
标签
标签是一个可选参数,可以传递给模型以便计算损失。这些标签应该是模型的预期预测:它将使用标准损失来计算其预测值和预期值(标签)之间的损失。
这些标签根据模型头不同而不同,例如:
-
对于序列分类模型(BertForSequenceClassification),模型期望一个维度为
(batch_size)
的张量,其中批次的每个值对应于整个序列的预期标签。 -
对于标记分类模型,(BertForTokenClassification),模型期望一个维度为
(batch_size, seq_length)
的张量,每个值对应于每个单独标记的预期标签。 -
对于掩码语言建模,(BertForMaskedLM),模型期望一个维度为
(batch_size, seq_length)
的张量,每个值对应于每个单独标记的预期标签:标签是被掩码标记的标记 ID,其余标记的值将被忽略(通常为-100)。 -
对于序列到序列的任务,(BartForConditionalGeneration, MBartForConditionalGeneration),模型期望一个维度为
(batch_size, tgt_seq_length)
的张量,每个值对应于与每个输入序列相关联的目标序列。在训练期间,BART 和 T5 都会在内部生成适当的decoder_input_ids
和解码器注意力掩码。通常不需要提供它们。这不适用于利用编码器-解码器框架的模型。 -
对于图像分类模型,(ViTForImageClassification),模型期望一个维度为
(batch_size)
的张量,批次中的每个值对应于每个单独图像的预期标签。 -
对于语义分割模型,(SegformerForSemanticSegmentation),模型期望一个维度为
(batch_size, height, width)
的张量,批次中的每个值对应于每个单独像素的预期标签。 -
对于目标检测模型,(DetrForObjectDetection),模型期望一个带有
class_labels
和boxes
键的字典列表,批次中的每个值对应于每个单独图像的预期标签和边界框数量。 -
对于自动语音识别模型,(Wav2Vec2ForCTC),模型期望一个维度为
(batch_size, target_length)
的张量,每个值对应于每个单独标记的预期标签。
每个模型的标签可能不同,因此请务必始终查看每个模型的文档以获取有关其特定标签的更多信息!
基础模型(BertModel)不接受标签,因为它们是基础变压器模型,只输出特征。
大型语言模型(LLM)
一个泛指,指的是在大量数据上训练的变压器语言模型(GPT-3、BLOOM、OPT)。这些模型通常也具有大量可学习参数(例如 GPT-3 的 1750 亿)。
M
掩码语言建模(MLM)
一个模型看到文本的损坏版本,通常是通过随机屏蔽一些标记来完成,并且必须预测原始文本的预训练任务。
多模态
将文本与其他类型的输入(例如图像)结合的任务。
N
自然语言生成(NLG)
所有与生成文本相关的任务(例如,使用 Transformer 写作,翻译)。
自然语言处理(NLP)
一个通用的说法是“处理文本”。
自然语言理解(NLU)
所有与理解文本内容相关的任务(例如对整个文本进行分类,对单词进行分类)。
P
管道
在🤗 Transformers 中,管道是一个抽象,指的是按特定顺序执行的一系列步骤,用于预处理和转换数据,并从模型返回预测。管道中可能包含的一些示例阶段可能是数据预处理、特征提取和归一化。
有关更多详细信息,请参阅用于推断的管道。
PipelineParallel(PP)
一种并行技术,其中模型在多个 GPU 上垂直分割(层级),以便将模型的一个或多个层放置在单个 GPU 上。每个 GPU 并行处理管道的不同阶段,并处理一小批次的数据。了解有关 PipelineParallel 如何工作的更多信息,请查看这里。
像素值
传递给模型的图像的数值表示的张量。像素值的形状为[batch_size
, num_channels
, height
, width
],并且是从图像处理器生成的。
池化
将矩阵缩小为较小矩阵的操作,可以通过取池化维度的最大值或平均值来实现。池化层通常位于卷积层之间,用于降采样特征表示。
位置 ID
与 RNN 不同,RNN 的每个标记的位置都嵌入在其中,transformers 不知道每个标记的位置。因此,模型使用位置 ID(position_ids
)来识别列表中每个标记的位置。
它们是一个可选参数。如果没有将position_ids
传递给模型,那么这些 ID 将自动创建为绝对位置嵌入。
绝对位置嵌入被选择在范围[0, config.max_position_embeddings - 1]
内。一些模型使用其他类型的位置嵌入,如正弦位置嵌入或相对位置嵌入。
预处理
将原始数据准备成机器学习模型可以轻松消化的格式的任务。例如,文本通常通过标记化进行预处理。要了解其他输入类型的预处理是什么样子,可以查看预处理教程。
预训练模型
一个在某些数据上进行了预训练的模型(例如整个维基百科)。预训练方法涉及自监督目标,可以是阅读文本并尝试预测下一个单词(参见因果语言建模),或者遮蔽一些单词并尝试预测它们(参见遮蔽语言建模)。
语音和视觉模型有自己的预训练目标。例如,Wav2Vec2 是一个语音模型,它在对比任务上进行了预训练,要求模型从一组“假”语音表示中识别“真实”语音表示。另一方面,BEiT 是一个视觉模型,它在一个遮蔽图像建模任务上进行了预训练,遮蔽了一些图像块,并要求模型预测被遮蔽的块(类似于遮蔽语言建模目标)。
R
循环神经网络(RNN)
一种使用循环处理文本的模型类型。
表示学习
一种机器学习的子领域,专注于学习原始数据的有意义表示。一些表示学习技术的例子包括词嵌入、自编码器和生成对抗网络(GANs)。
S
采样率
每秒采样的样本数(音频信号)。采样率是将连续信号(如语音)离散化的结果。
自注意力
输入的每个元素找出它们应该关注的其他输入元素。
自监督学习
一类机器学习技术,其中模型从未标记数据中创建自己的学习目标。它与无监督学习和监督学习不同,学习过程是受监督的,但不是明确来自用户。
自监督学习的一个例子是掩码语言建模,其中模型传递带有一定比例标记的句子,并学习预测缺失的标记。
半监督学习
一种广泛的机器学习训练技术类别,利用少量标记数据和大量未标记数据来提高模型的准确性,与监督学习和无监督学习不同。
半监督学习方法的一个例子是“自训练”,其中模型在标记数据上进行训练,然后用于对未标记数据进行预测。模型以最大置信度预测的未标记数据部分被添加到标记数据集中,并用于重新训练模型。
序列到序列(seq2seq)
从输入生成新序列的模型,比如翻译模型或总结模型(比如 Bart 或 T5)。
分片 DDP
作为 ZeRO 概念的另一个名称,被各种其他 ZeRO 实现所使用。
步幅
在卷积或池化中,步幅指的是核在矩阵上移动的距离。步幅为 1 表示核每次移动一个像素,步幅为 2 表示核每次移动两个像素。
监督学习
一种直接使用标记数据来纠正和指导模型性能的模型训练形式。数据被馈送到正在训练的模型中,其预测与已知标签进行比较。模型根据其预测的不正确程度更新其权重,并重复该过程以优化模型性能。
T
张量并行性(TP)
在多个 GPU 上进行训练的并行技术,其中每个张量被分割成多个块,因此每个张量的碎片都驻留在其指定的 GPU 上,而不是整个张量驻留在单个 GPU 上。碎片在不同 GPU 上分别并行处理,并在处理步骤结束时进行同步。这有时被称为水平并行,因为分割发生在水平级别。在这里了解更多关于张量并行性的信息。
标记
句子的一部分,通常是一个词,但也可以是一个子词(不常见的词通常被分割为子词)或标点符号。
标记类型 ID
一些模型的目的是对句子对或问题回答进行分类。
www.youtube-nocookie.com/embed/0u3ioSwev3s
这些需要将两个不同序列连接在一个“input_ids”条目中,通常使用特殊标记的帮助来执行,例如分类器([CLS]
)和分隔符([SEP]
)标记。例如,BERT 模型构建其两个序列输入如下:
>>> # [CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP]
我们可以使用我们的分词器通过将两个序列作为两个参数(而不是像以前那样作为列表)传递给tokenizer
来自动生成这样一个句子,就像这样:
>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
>>> sequence_a = "HuggingFace is based in NYC"
>>> sequence_b = "Where is HuggingFace based?"
>>> encoded_dict = tokenizer(sequence_a, sequence_b)
>>> decoded = tokenizer.decode(encoded_dict["input_ids"])
将返回:
>>> print(decoded)
[CLS] HuggingFace is based in NYC [SEP] Where is HuggingFace based? [SEP]
这对于一些模型来说足够了解一个序列的结束和另一个序列的开始。然而,其他模型,如 BERT,还部署了标记类型 ID(也称为段 ID)。它们表示模型中两种序列的二进制掩码。
分词器将此掩码返回为“token_type_ids”条目:
>>> encoded_dict["token_type_ids"]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]
第一个序列,用于问题的“上下文”,其所有标记都表示为0
,而第二个序列,对应于“问题”,其所有标记都表示为1
。
一些模型,比如 XLNetModel 使用一个额外的标记,表示为2
。
迁移学习
一种技术,涉及采用预训练模型并将其调整为特定于您任务的数据集。您可以利用从现有模型获得的知识作为起点,而不是从头开始训练模型。这加快了学习过程并减少了所需的训练数据量。
变压器
基于自注意力的深度学习模型架构。
U
无监督学习
一种模型训练形式,其中提供给模型的数据没有标记。无监督学习技术利用数据分布的统计信息来找到对当前任务有用的模式。
Z
零冗余优化器(ZeRO)
一种并行技术,类似于 TensorParallel 对张量进行分片,除了整个张量在前向或后向计算时会重新构建,因此模型不需要被修改。这种方法还支持各种卸载技术,以弥补有限的 GPU 内存。在这里了解更多关于 ZeRO 的信息(perf_train_gpu_many#zero-data-parallelism)。