首页 > 解决方案 > 使用具有固定词汇表的 Hugginface Transformers 和 Tokenizers?

问题描述

我有一个使用固定词汇表的特殊非语言用例,即一组相对较小的生成标记,它们代表我们“语言”的整个​​词汇表。我希望能够将它与任何不同的模型一起使用,我想知道最好的方法是什么?它只是一个vocab.txt短字符串文件,我认为它不适用于任何 BPE 标记器。我的假设是否正确?此外,有没有办法将词汇“强制”到任何标记器上?


澄清一下,我们的“语言”使用前缀来标识某些types标记,这些标记在整体语法中具有某些功能。我们希望能够type在推理过程中进行屏蔽,无论是在输入上还是作为选择过程的一部分,例如,通过限制top-ktop-p采样到给定type。使用固定/手动调整的词汇表,我们可以非常具体地确定我们需要哪些ids多少 ids;即,我们知道每个 使用哪些标记type,因此我们可以相应地屏蔽/过滤。但是,通过 BPE 标记化,type可以使用任意数量的标记对给定的标记进行标记,从而使该过程变得不那么简单。

动机只是通过更好地融入 Huggingface 宇宙来让生活更轻松,这样我们就可以更流畅地试验现成的模型。我们已经使用BertTokenizerGPT2 和 RoBERTa 的标准进行了这项工作,但是如果能够“开箱即用”地试验不同的 Huggingface 模型,可以说(使用 Trainers、Pipelines 等),那就太好了。我们只需BertTokenizer加载我们的 vocab.txt 就完成了,所以我想知道是否有其他方法可以使用其他标记器(实际上,此时 BPE 是唯一的问题)。

在我看来,能够为任何分词器指定一个词汇比让我们的分词器与其他模型一起工作更简单。尽管也许更好的方法是考虑简化过程?我想我可以分叉和修改AutoTokenizer......??

非常感谢任何帮助。

标签: huggingface-transformershuggingface-tokenizers

解决方案


据我了解,下面的解决方案可能会对您有所帮助,因为您可以使用此标记器,就像使用其他预训练的标记器一样。

由于我并不真正了解标记器的所有内部工作原理,因此我很可能会使用此解决方案,但希望它可以帮助某人。

主要思想是PreTrainedTokenizer. 这样,您应该只覆盖一些关键方法,如_tokenize,_convert_token_to_id等...,这比实现一个全新的标记器更简单。

import json
from pathlib import Path
from typing import Optional, Tuple, Dict

from transformers import PreTrainedTokenizer


class FixedVocabTokenizer(PreTrainedTokenizer):
    def __init__(self, vocab: Dict[str, int], max_len: int = None):
        super().__init__(max_len=max_len)
        self.__token_ids = vocab
        self.__id_tokens: Dict[int, str] = {value: key for key, value in vocab.items()}

    def _tokenize(self, text: str, **kwargs):
        return text.split(' ')

    def _convert_token_to_id(self, token: str) -> int:
        return self.__token_ids[token] if token in self.__token_ids else self.unk_token_id

    def _convert_id_to_token(self, index: int) -> str:
        return self.__id_tokens[index] if index in self.__id_tokens else self.unk_token

    def get_vocab(self) -> Dict[str, int]:
        return self.__token_ids.copy()

    def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
        if filename_prefix is None:
            filename_prefix = ''
        vocab_path = Path(save_directory, filename_prefix + 'vocab.json')
        json.dump(self.__token_ids, open(vocab_path, 'w'))
        return str(vocab_path),

    @property
    def vocab_size(self) -> int:
        return len(self.__token_ids)


if __name__ == '__main__':
    # your custom, fixed vocabulary
    custom_vocab = {
        '[UNK]': 0,
        'word0': 1,
        'word1': 2,
        'word2': 3,
        'word3': 4,
        'word4': 5,
        'word5': 6,
        '[CLS]': 7,
        '[SEP]': 8,
        '[PAD]': 9
    }
    model_max_len = 8
    tokenizer = FixedVocabTokenizer(custom_vocab, max_len=model_max_len)
    # tell your tokenizer about your special tokens
    tokenizer.add_special_tokens({
        'unk_token': '[UNK]',
        'pad_token': '[PAD]',
        'cls_token': '[CLS]',
        'sep_token': '[SEP]'
    })

    res = tokenizer(
        [
            'word1 word2 word word1 word3',
            'word2 word0 word0 word3 word5 word4 word2 word1 word0'
        ],
        padding=True,
        truncation=True
    )
    # the result should look like something like this
    # res -> BatchEncoding(
    #     data: {
    #         'input_ids': [[2, 3, 0, 2, 4, 9, 9, 9], [3, 1, 1, 4, 6, 5, 3, 2]],
    #         'attention_mask': [[1, 1, 1, 1, 1, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1]],
    #         ...
    #     },
    #     ...
    # )

这是我能想出的解决方案,但是我不知道你是否可以用PreTrainedTokenizerFast. 所以还有一点需要注意的是,您只能使用这种方法使用慢速标记器。


推荐阅读