ScratchLLMStepByStep:训练自己的Tokenizer
1. 引言
分词器是每个大语言模型必不可少的组件,但每个大语言模型的分词器几乎都不相同。如果要训练自己的分词器,可以使用huggingface的tokenizers框架,tokenizers包含以下主要组件:
- Tokenizer: 分词器的核心组件,定义了分词的整个流程,包括标准化、预分词、模型分词、后处理等
- Normalizers:可选,负责将文本标准化,包括unicode归一化、大写转小写、去重音等操作
- Pre-tokenizers:负责将文本分割成更小的片段(如单词等),为模型分词做准备。常见的预分词器有按空格分词(Whitespace)、正则表达式分词(Regex)等
- Models:是实际的分词算法,负责将文本片段转换为子词,常见的有BPE、WordPiece、Unigram等。
- Post-Processors:负责对分词结果进行后处理,如添加特殊标记(CLS、SEP)。
- Decoders:负责将分词结果转换回原始文本,常见的解码器有 ByteLevel、WordPiece 等。
- Trainers:用于训练分词模型,不同的模型对应不同的训练器,如 BpeTrainer、WordPieceTrainer、UnigramTrainer 等。
在开始之前,先导入对应的包。
import json
import re
import os
from tokenizers import (decoders,models,normalizers,pre_tokenizers,processors,trainers,Tokenizer,
)
2. 加载语料库
我们准备好的语料库是一个jsonl文件,大概有736MB,每一行是一条json格式的文本数据,既有中文,也有英文。
!ls -n /data2/minigpt/dataset/tokenize/tokenizer_train.jsonl
-rw-rw-r-- 1 736729803 Nov 4 22:04 /data2/minigpt/dataset/tokenize/tokenizer_train.jsonl
定义一个函数用于从JSONL文件中读取文本数据,考虑到语料库会比较大,所以采用yield
生成器来延迟到访问时再加载数据。
def read_texts_from_jsonl(file_path):with open(file_path, 'r', encoding='utf-8') as f:for i, line in enumerate(f):data = json.loads(line)yield data['text']
data_path = '/data2/minigpt/dataset/tokenize/tokenizer_train.jsonl'
texts = read_texts_from_jsonl(data_path)
type(texts)
generator
可以看到,函数read_texts_from_jsonl
返回的并不是真正的数据,而是一个生成器generator
。可以通过next
函数像访问iterator
一样访问迭代数据。
next(texts)
'好的。现在请你将这个文本中的所有的逗号都替换成空格。 好的,请稍等一下,现在我会将文本中的所有逗号替换为空格。处理后文本为:"这是一个句子 目的是看看是否可以正确地从这个句子中删除关键词。"。处理结果如何?'
3. 训练过程
3.1 模型选择
使用BPE模型来初始化Tokenizer实例。
tokenizer = Tokenizer(models.BPE())
BPE是一种基于子词的分词方法,例如:
- cats -> cat + s
- helpful -> help + ful
- congratulation -> con + gr + at + ulation
这种基于子词的分词方法,相比基于完整单词和基于单个字符有以下好处:
- 子词相比于单词(可以认为多个子词的组合)数量要可控,这能避免词表过大,并且能避免生僻词带来的未知令牌问题。
- 子词相比于字符语义性更强,像单个字符
f
是没有语义的,但子词ful
可以表达满的
,比较像英语里的词根词缀。
3.2 预分词器选择
为tokenizer设置预分词器,预分词器有以下几类:
- Whitespace:按空格分隔,粒度为单词,适用于空格分隔的语言,例如英语。
- Regex:按自定义正则表达式分隔,适用于需要自定义复杂分词规则的场景。
- ByteLevel:按字节分割,适用于特殊字符、非英语场景(例如中文)。
由于我们主要面向中文,所以这里采用ByteLevel的pre_tokenizer。
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
tokenizer.pre_tokenizer
<tokenizers.pre_tokenizers.ByteLevel at 0x7f41641266f0>
预分词器的方法列表
dir(tokenizer.pre_tokenizer)
[……'add_prefix_space','alphabet','custom','pre_tokenize','pre_tokenize_str','use_regex']
那pre_tokenizer具体对文本作了什么处理呢?可以通过下面两个例子来观察下。
- 测试英文文本处理。
tokenizer.pre_tokenizer.pre_tokenize_str("Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place")
[('Pre', (0, 3)),('-', (3, 4)),('tokenize', (4, 12)),('Ġa', (12, 14)),('Ġ:', (14, 16)),('class', (16, 21)),(':`~', (21, 24)),('tokenizers', (24, 34)),('.', (34, 35)),('PyPreTokenizedString', (35, 55)),('`', (55, 56)),('Ġin', (56, 59)),('-', (59, 60)),('place', (60, 65))]
可以看到,pre_tokenizer将文本按照空格和特殊字符作了初步分词,空格处理成了特殊字符Ġ
,并记录了每个词的起始和结束位置。
- 测试中文文本处理。
zh_sentence = "在查处虚开增值税专用发票案件中,常常涉及进项留抵税额和税款损失的认定和处理。"
tokenizer.pre_tokenizer.pre_tokenize_str(zh_sentence)
[('åľ¨æŁ¥å¤ĦèĻļå¼Ģå¢ŀå̼ç¨İä¸ĵç͍åıij票æ¡Īä»¶ä¸Ń', (0, 15)),('ï¼Į', (15, 16)),('常常æ¶īåıĬè¿Ľé¡¹çķĻæĬµç¨İé¢ĿåĴĮç¨İ款æįŁå¤±çļĦ认å®ļåĴĮå¤ĦçIJĨ', (16, 37)),('ãĢĤ', (37, 38))]
中文基本也是按照特殊符号,
和。
进行了分词,但分词的结果是一堆不认识的字符,这些字符是如何产生的呢?
3.3 预分词原理探究
预分词常常使用类似下面一样的正则表达式先对文本进行分隔。
import regex as rePRETOKENIZE_REGEX = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}\p{P}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"""
pat = re.compile(PRETOKENIZE_REGEX)
tokens = re.findall(pat, zh_sentence)
tokens
['在查处虚开增值税专用发票案件中', ',', '常常涉及进项留抵税额和税款损失的认定和处理', '。']
其中,各部分正则表达式的作用如下:
- (?i:'s|'t|'re|'ve|'m|'ll|'d): 匹配常见的英文缩略形式,例如:'s(is 或 has),'t(not),'re(are),'ve(have),'m(am),'ll(will),'d(would 或 had)。
- [^\r\n\p{L}\p{N}\p{P}]?\p{L}+:匹配一个或多个 Unicode 字母
\p{L}
,这里的unicode字母包括英文、中文、拉丁等所有语言中的字母,允许前面有一个非换行符\r\n
、非字母\p{L}
、非数字\p{N}
和非标点\p{P}
的字符,相当于是匹配空格、制表符等空白字符。 - \p{N}:匹配任何 Unicode 数字字符。
- ?[^\s\p{L}\p{N}]+[\r\n]*:匹配非空白、非字母、非数字的字符,允许前面有一个空格,后面跟随换行符,相当于是匹配标点符号后面跟换行符。
- \p{L}:匹配任何 Unicode 字母字符,包括拉丁字母、希腊字母、汉字等所有语言中的字母。
- \p{N}:匹配任何 Unicode 数字字符,涵盖阿拉伯数字、罗马数字等所有形式的数字字符。
- \p{P}:匹配任何 Unicode 标点字符,涵盖句号、逗号、引号、括号等所有形式、所有语言中的标点符号。
对于英文以外的其它语言(例如中文),需要进行utf-8编码,将字符编码为字节。
utf-8编码的目的是解决英文、中文、日文、俄文等多语言的问题,因为世界上所有语言的字符都可以用一个或多个utf-8字节的组合来表示。
tokens_utf8 = [token.encode("utf-8") for token in tokens]
tokens_utf8
[b'\xe5\x9c\xa8\xe6\x9f\xa5\xe5\xa4\x84\xe8\x99\x9a\xe5\xbc\x80\xe5\xa2\x9e\xe5\x80\xbc\xe7\xa8\x8e\xe4\xb8\x93\xe7\x94\xa8\xe5\x8f\x91\xe7\xa5\xa8\xe6\xa1\x88\xe4\xbb\xb6\xe4\xb8\xad',b'\xef\xbc\x8c',b'\xe5\xb8\xb8\xe5\xb8\xb8\xe6\xb6\x89\xe5\x8f\x8a\xe8\xbf\x9b\xe9\xa1\xb9\xe7\x95\x99\xe6\x8a\xb5\xe7\xa8\x8e\xe9\xa2\x9d\xe5\x92\x8c\xe7\xa8\x8e\xe6\xac\xbe\xe6\x8d\x9f\xe5\xa4\xb1\xe7\x9a\x84\xe8\xae\xa4\xe5\xae\x9a\xe5\x92\x8c\xe5\xa4\x84\xe7\x90\x86',b'\xe3\x80\x82']
但有个问题是:Ascii码中是会包含回车、制表、换行等控制字符的,同样utf-8编码中也会有。而我们最终构造的词表必须是可显示的文本,所以还要做一个工作是把控制字符都转换为可显示字符,为此需要制作一个unicode字节编码表,用于将单字节(256以内)都编码为可显示字符。
0-255范围内的可显示字符分为三段:
- 从 !(ASCII 33)到 ~(ASCII 126)。
- 从 ¡(Unicode 161)到 ¬(Unicode 172)。
- 从 ®(Unicode 174)到 ÿ(Unicode 255)。
这三段以外的ASCII码均无法正常显示,需要用可显示字符来填充替代。
def bytes_to_unicode():# 收集0-255范围内的可显示字符对应的数字值,ord函数用于将字符编码为数字bs = (list(range(ord("!"), ord("~") + 1)) + list(range(ord("¡"), ord("¬") + 1)) + list(range(ord("®"), ord("ÿ") + 1)))cs = bs[:]n = 0# 补充0-255范围内不可显示字符对应的数字,并转换为256以上可显示字符对应的数字值for b in range(2**8):if b not in bs:bs.append(b)cs.append(2**8 + n)n += 1# chr函数用于将数字转换回unicode字符,并创建一个字节值到字符值的映射表。cs = [chr(n) for n in cs]return dict(zip(bs, cs))byte_encoder = bytes_to_unicode()
json.dumps(byte_encoder, ensure_ascii=False)
'{"33": "!", "34": "\\"", "35": "#", "36": "$", "37": "%", "38": "&", "39": "\'", "40": "(", "41": ")", "42": "*", "43": "+", "44": ",", "45": "-", "46": ".", "47": "/", "48": "0", "49": "1", "50": "2", "51": "3", "52": "4", "53": "5", "54": "6", "55": "7", "56": "8", "57": "9", "58": ":", "59": ";", "60": "<", "61": "=", "62": ">", "63": "?", "64": "@", "65": "A", "66": "B", "67": "C", "68": "D", "69": "E", "70": "F", "71": "G", "72": "H", "73": "I", "74": "J", "75": "K", "76": "L", "77": "M", "78": "N", "79": "O", "80": "P", "81": "Q", "82": "R", "83": "S", "84": "T", "85": "U", "86": "V", "87": "W", "88": "X", "89": "Y", "90": "Z", "91": "[", "92": "\\\\", "93": "]", "94": "^", "95": "_", "96": "`", "97": "a", "98": "b", "99": "c", "100": "d", "101": "e", "102": "f", "103": "g", "104": "h", "105": "i", "106": "j", "107": "k", "108": "l", "109": "m", "110": "n", "111": "o", "112": "p", "113": "q", "114": "r", "115": "s", "116": "t", "117": "u", "118": "v", "119": "w", "120": "x", "121": "y", "122": "z", "123": "{", "124": "|", "125": "}", "126": "~", "161": "¡", "162": "¢", "163": "£", "164": "¤", "165": "¥", "166": "¦", "167": "§", "168": "¨", "169": "©", "170": "ª", "171": "«", "172": "¬", "174": "®", "175": "¯", "176": "°", "177": "±", "178": "²", "179": "³", "180": "´", "181": "µ", "182": "¶", "183": "·", "184": "¸", "185": "¹", "186": "º", "187": "»", "188": "¼", "189": "½", "190": "¾", "191": "¿", "192": "À", "193": "Á", "194": "Â", "195": "Ã", "196": "Ä", "197": "Å", "198": "Æ", "199": "Ç", "200": "È", "201": "É", "202": "Ê", "203": "Ë", "204": "Ì", "205": "Í", "206": "Î", "207": "Ï", "208": "Ð", "209": "Ñ", "210": "Ò", "211": "Ó", "212": "Ô", "213": "Õ", "214": "Ö", "215": "×", "216": "Ø", "217": "Ù", "218": "Ú", "219": "Û", "220": "Ü", "221": "Ý", "222": "Þ", "223": "ß", "224": "à", "225": "á", "226": "â", "227": "ã", "228": "ä", "229": "å", "230": "æ", "231": "ç", "232": "è", "233": "é", "234": "ê", "235": "ë", "236": "ì", "237": "í", "238": "î", "239": "ï", "240": "ð", "241": "ñ", "242": "ò", "243": "ó", "244": "ô", "245": "õ", "246": "ö", "247": "÷", "248": "ø", "249": "ù", "250": "ú", "251": "û", "252": "ü", "253": "ý", "254": "þ", "255": "ÿ", "0": "Ā", "1": "ā", "2": "Ă", "3": "ă", "4": "Ą", "5": "ą", "6": "Ć", "7": "ć", "8": "Ĉ", "9": "ĉ", "10": "Ċ", "11": "ċ", "12": "Č", "13": "č", "14": "Ď", "15": "ď", "16": "Đ", "17": "đ", "18": "Ē", "19": "ē", "20": "Ĕ", "21": "ĕ", "22": "Ė", "23": "ė", "24": "Ę", "25": "ę", "26": "Ě", "27": "ě", "28": "Ĝ", "29": "ĝ", "30": "Ğ", "31": "ğ", "32": "Ġ", "127": "ġ", "128": "Ģ", "129": "ģ", "130": "Ĥ", "131": "ĥ", "132": "Ħ", "133": "ħ", "134": "Ĩ", "135": "ĩ", "136": "Ī", "137": "ī", "138": "Ĭ", "139": "ĭ", "140": "Į", "141": "į", "142": "İ", "143": "ı", "144": "IJ", "145": "ij", "146": "Ĵ", "147": "ĵ", "148": "Ķ", "149": "ķ", "150": "ĸ", "151": "Ĺ", "152": "ĺ", "153": "Ļ", "154": "ļ", "155": "Ľ", "156": "ľ", "157": "Ŀ", "158": "ŀ", "159": "Ł", "160": "ł", "173": "Ń"}'
这样,每个字节值都从 Unicode 表的开头获得一个分配给它的
可见
字符。这一点非常重要,因为每个utf-8字符都是由一到多个字节组成的,将这个长度为256的编码表中的字节进行组合,理论上就能对世界上所有语言中的字符进行编码,并且还不会出现未知
标记。
使用这个unicode字节编码表将前面utf-8编码后的文本序列进行ByteLevel级的编码。
tokens_unicode = ["".join(byte_encoder[b] for b in token) for token in tokens_utf8]
tokens_unicode
['åľ¨æŁ¥å¤ĦèĻļå¼Ģå¢ŀå̼ç¨İä¸ĵç͍åıij票æ¡Īä»¶ä¸Ń','ï¼Į','常常æ¶īåıĬè¿Ľé¡¹çķĻæĬµç¨İé¢ĿåĴĮç¨İ款æįŁå¤±çļĦ认å®ļåĴĮå¤ĦçIJĨ','ãĢĤ']
可以看到,结果与使用pre_tokenizer预分词的结果完全相同。
3.4 构建训练器
BPE训练器中需要指定几个参数:
- vocab_size:训练后词表中的词条数量,BPE是一个从短词到长词的组合过程,达到词表大小后就会停止训练。
- special_tokens:特殊token,和语言模型的特殊token相同,例如开始、结束、填充标记。
- initial_alphabet:初始字符表,使用上面长度为256的unicode字节编码表作为初始字符表。
通过pre_tokenizers.ByteLevel.alphabet()
可以获得初始字符编码表。
json.dumps(pre_tokenizers.ByteLevel.alphabet(), ensure_ascii=False)
'["ø", "\\\\", "ľ", "v", "ć", "¬", "ł", "°", "ġ", "ķ", "ĕ", "»", "]", "Q", "ģ", "G", "ñ", "¶", "é", "H", "9", ")", "×", "Í", "Ó", "º", "£", "~", "Ā", "s", "Ô", "2", "Ý", "í", "â", "·", "Ą", "ý", "ĭ", "²", "4", "Ù", "ĺ", "ă", "ĸ", "Ī", "z", "K", "IJ", "N", "Ø", "1", "n", "b", "ó", "¼", "õ", "V", "Ö", "6", "©", "ė", "O", "đ", "j", "h", "İ", "į", "¨", "¯", "Ğ", "I", "0", "Ă", "=", "ß", "Û", "Ć", "Å", "ĩ", "Ê", "B", "ª", "W", "_", "S", "Ĥ", "Ł", "q", "ë", "Ķ", "Ò", "Ĉ", "Ċ", "L", "«", "U", "#", "ļ", "Ė", "å", "´", "Î", "M", "&", "D", "¤", "ô", "ç", "Y", "R", "ð", "Ĺ", "ij", ">", "Ŀ", "ę", "d", "É", "à", "ŀ", "<", "Ĩ", "¹", "Ã", "/", "¸", "Ģ", "X", "ê", "u", "ğ", "m", "w", "Ì", "¢", "Æ", "C", "t", "Ļ", "ì", "ě", "Ď", "l", "ē", "Ħ", "Ñ", "3", "÷", "{", "$", "y", "Ç", "¡", "Ë", "ĉ", "ĵ", "Ĵ", "Č", "a", "T", ";", "Ń", "Ú", "f", "§", "Z", "+", "\'", "Ä", "A", "ÿ", "Ę", "Õ", "Ġ", "c", "%", "Ē", "ï", "ī", "Ĕ", "?", "(", "Đ", "Ï", "ö", "^", "P", "±", "x", ",", "i", "æ", "®", "-", "³", "î", "*", "û", "ú", "¾", "ä", "Į", "5", "ò", "Ĭ", "Â", "þ", "J", "ċ", ":", "ü", "¥", "`", "è", "È", "ĝ", "ą", "}", "!", "r", "Þ", "g", "\\"", "[", "À", "ā", "ď", "7", "¿", "ħ", "F", "ı", "Á", "á", "Ð", ".", "@", "E", "č", "½", "e", "ã", "ù", "Ľ", "Ĝ", "ĥ", "¦", "Ě", "p", "Ü", "8", "|", "k", "o", "µ"]'
定义特殊token,分别为填充、开始、结束标记。
special_tokens = ["<|endoftext|>", "<|im_start|>", "<|im_end|>"]
构建训练器,词条数量设置为32000。
trainer = trainers.BpeTrainer(vocab_size=32000,special_tokens=special_tokens, # 确保这三个token被包含show_progress=True,initial_alphabet=pre_tokenizers.ByteLevel.alphabet()
)
使用上面的texts生成器作为语料库,使用trainer开始训练
分词器。
tokenizer.train_from_iterator(texts, trainer=trainer)
注:这个训练过程的用时长短与文本数据大小有关,我们前面加载的文本数据有700多MB, 大概需要十几分钟。
3.5 保存训练结果
在保存结果之前,需要先设置相匹配的解码器,否则ASCII以外的字符可能无法正常解码。
上面编码阶段使用了ByteLevel的预分词器,相对应的解码阶段也需要使用ByteLevel,表示将token id转换为token后,还需要进行一次unicode字节级别的解码,才能正常显示中文等多语言字符。
tokenizer.decoder = decoders.ByteLevel()
将训练的分词器保存到指定目录。
tokenizer_dir = "/data2/minigpt/models/tokenizer_v3"
os.makedirs(tokenizer_dir, exist_ok=True)
tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json"))
tokenizer.model.save(tokenizer_dir)
['/data2/minigpt/models/tokenizer_v3/vocab.json','/data2/minigpt/models/tokenizer_v3/merges.txt']
还需要一个分词器配置文件,包括模型类型、是否使用小写字母等。
config = {"add_bos_token": False,"add_eos_token": False,"add_prefix_space": True,"added_tokens_decoder": {"0": {"content": "<|endoftext|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"1": {"content": "<|im_start|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"2": {"content": "<|im_end|>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True}},"additional_special_tokens": [],"bos_token": "<|im_start|>","clean_up_tokenization_spaces": False,"eos_token": "<|im_end|>","legacy": True,"model_max_length": 1000000000000000019884624838656,"pad_token": None,"sp_model_kwargs": {},"spaces_between_special_tokens": False,"tokenizer_class": "PreTrainedTokenizerFast","unk_token": "<|endoftext|>","use_default_system_prompt": False,"chat_template": "{% if messages[0]['role'] == 'system' %}{% set system_message = messages[0]['content'] %}{% endif %}{% if system_message is defined %}{{ system_message }}{% endif %}{% for message in messages %}{% set content = message['content'] %}{% if message['role'] == 'user' %}{{ '<|im_start|>user\\n' + content + '<|im_end|>\\n<|im_start|>assistant\\n' }}{% elif message['role'] == 'assistant' %}{{ content + '<|im_end|>' + '\\n' }}{% endif %}{% endfor %}"
}
保存分词器配置
with open(os.path.join(tokenizer_dir, "tokenizer_config.json"), "w", encoding="utf-8") as config_file:json.dump(config, config_file, ensure_ascii=False, indent=4)print("Tokenizer training completed and saved.")
Tokenizer training completed and saved.
查看磁盘上的词表文件。
!ls -l /data2/minigpt/models/tokenizer_v3
total 2548
-rw-rw-r-- 1 golfxiao golfxiao 407951 Oct 10 21:45 merges.txt
-rw-rw-r-- 1 golfxiao golfxiao 1686 Oct 10 21:45 tokenizer_config.json
-rw-rw-r-- 1 golfxiao golfxiao 1572840 Oct 10 21:45 tokenizer.json
-rw-rw-r-- 1 golfxiao golfxiao 621912 Oct 10 21:45 vocab.json
- vocab.json:词汇表文件,包含词条和对应的索引。
- merges.txt: 合并表文件,定义了子词的合并规则。
- tokenizer.json: 完整的分词器文件,它包含了分词器的所有信息,包括词汇表、合并规则、特殊标记等。
- tokenizer_config.json: 分词器配置文件,包括了起始token、结束token的定义,以及提示词模板。
4. 测试分词器
from transformers import AutoTokenizer# 加载刚训练的tokenizer
tokenizer_dir = "/data2/minigpt/models/tokenizer_v3"
tokenizer_trained = AutoTokenizer.from_pretrained(tokenizer_dir)
4.1 英文文本测试
先测试英文文本的分词。
text_en = "Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place"
tokenized = tokenizer_trained.tokenize(text_en)
tokenized
['Pre','-','token','ize','Ġa','Ġ:','class',':','`','~','token','izers','.','Py','Pre','T','oken','ized','String','`','Ġin','-','place']
tokenize方法只负责分词,就是将一串文本切分为token列表。我们在给模型输入时一般需要的是token_id,这时就需要使用encode方法,同时完成token切分和文本到数字的序列化。
token_ids_en = tokenizer_trained.encode(text_en)
token_ids_en
[19714,15,24535,1038,260,6938,9939,28,66,96,24535,11344,16,22966,19714,54,9071,1228,13863,66,295,15,2383]
对上面的token_id进行反序列化,以测试解码功能。
tokenizer_trained.decode(token_ids_en)
'Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place'
可以看到,解码的结果与原始英文串完全相同。
4.2 中文文本测试
下面测试下中文文本的序列化和反序列化。
text_zh = "在查处虚开增值税专用发票案件中,常常涉及进项留抵税额和税款损失的认定和处理。"
token_ids_zh = tokenizer_trained.encode(text_zh)
token_ids_zh
[368,1698,1319,4304,953,30571,2147,411,646,3917,6723,413,270,6679,4743,631,1467,3692,9083,3534,2676,315,3534,1805,8576,269,1374,627,12769,286]
tokenizer_trained.decode(token_ids_zh)
'在查处虚开增值税专用发票案件中,常常涉及进项留抵税额和税款损失的认定和处理。'
我们刚训练的分词器在中文和英文上都能正常进行的文本的序列化和反序列化操作。
小结:本文借助huggingface提供的tokenizers框架,以一个真实的语料库为案例,演示了分词器训练的过程,并最终得到了一个切实可用的分词器。但tokenizers框架封装的比较多,所以在训练过程中对多语言的编码和解码部分作了内部实现的剖析和讲解,如果你还对其它部分(如BPE算法)感兴趣,下面的参考内容或许能为你提供进一步的帮助。
参考阅读
- 带你从零认识语言模型
- 手搓BPE算法
- 什么是tokenizer?
相关文章:
ScratchLLMStepByStep:训练自己的Tokenizer
1. 引言 分词器是每个大语言模型必不可少的组件,但每个大语言模型的分词器几乎都不相同。如果要训练自己的分词器,可以使用huggingface的tokenizers框架,tokenizers包含以下主要组件: Tokenizer: 分词器的核心组件,定…...
【Linux】Socket编程-TCP构建自己的C++服务器
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 Socket 编程 TCP 🦋 TCP socket API 详解🦋 多线程远程命令执行🦋 网络版计算器(应用层自定义协议与序列化…...
数据结构——线性表和顺序表
1、线性表的基本概念 1.1 定义 线性结构是简单且常用的数据结构,而线性表则是一种典型的线性结构 存储数据,最简单,最有效的方法是吧它们存储在一个线性表中 一个线性表是n个元素的有限序列。每个元素在不同的情况下有不同的含义,…...
FunASR 在Linux/Unix 平台编译
第一步拉取镜像并生成容器: ### 镜像启动 通过下述命令拉取并启动FunASR软件包的docker镜像: shell sudo docker pull \ registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.12 mkdir -p ./funasr-runtime-…...
AIP-200 先例
编号200原文链接AIP-200: Precedent状态批准创建日期2018-06-28更新日期2018-06-28 很多时候,API的编写方式会违反新的指导原则。此外,有时出于特定原因也需要打破标准,例如与现有系统保持一致、满足严格的性能要求或其他因素。最后…...
SAP五大核心模块:塑造企业数字化未来
在数字化转型的浪潮中,SAP(Systems, Applications and Products in Data Processing)以其强大的企业资源规划(ERP)系统,成为众多企业信赖的伙伴。SAP系统通过五大核心模块,即财务管理࿰…...
【UE5.3】fix DONET报错
新的机器 4070 gpu 运行ue项目, 可能是epic 启动器是vs安装的, vs安装的epic 启动器自己更新了一波,导致了.NET的问题? ue项目是拷贝远程的windows的电脑里面的,应该不会导致ue源码里的cs出问题? 【UE5.3】UnrealLink 安装:fix Detected compiler newer than Visual Stu…...
【0393】Postgres内核 checkpointer process ③ 构建 WAL records 工作缓存区
1. 初始化 ThisTimeLineID、RedoRecPtr 函数 InitXLOGAccess() 内部会初始化 ThisTimeLineID、wal_segment_size、doPageWrites 和 RedoRecPtr 等全局变量。 下面是这四个变量初始化前的值: (gdb) p ThisTimeLineID $125 = 0 (gdb) p wal_segment_size $126 = 16777216 (gdb…...
pc 端 TensorRT API 实现 YOLOv11 的 C++ 小白部署经验
标题1 模型转化 python 先下载项目 https://github.com/ultralytics/ultralytics 同时下载模型 https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt pythonconda虚拟环境,主要是以下三个,其余缺什么直接pip anaconda…...
LLM - 大模型 ScallingLaws 的 C=6ND 公式推导 教程(1)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/145185794 Scaling Laws (缩放法则) 是大模型领域中,用于描述 模型性能(Loss) 与 模型规模N、数据量D、计算资源C 之间关系的经验规律…...
【机器学习实战】kaggle 欺诈检测---使用生成对抗网络(GAN)解决欺诈数据中正负样本极度不平衡问题
【机器学习实战】kaggle 欺诈检测---如何解决欺诈数据中正负样本极度不平衡问题https://blog.csdn.net/2302_79308082/article/details/145177242 本篇文章是基于上次文章中提到的对抗生成网络,通过对抗生成网络生成少数类样本,平衡欺诈数据中正类样本极…...
C++ 之多线程相关总结
C 之多线程相关总结 1.多线程相关基础知识 1.1 线程的创建和管理 1. std::thread 类: 用于创建和管理线程。通过将可调用对象(如函数、函数对象、lambda 表达式)作为参数传递给 std::thread 的构造函数,可以创建一个新的线程。…...
基于机器学习随机森林算法的个人职业预测研究
1.背景调研 随着信息技术的飞速发展,特别是大数据和云计算技术的广泛应用,各行各业都积累了大量的数据。这些数据中蕴含着丰富的信息和模式,为利用机器学习进行职业预测提供了可能。机器学习算法的不断进步,如深度学习、强化学习等…...
性能测试 - Locust WebSocket client
Max.Bai 2024.10 0. 背景 Locust 是性能测试工具,但是默认只支持http协议,就是默认只有http的client,需要其他协议的测试必须自己扩展对于的client,比如下面的WebSocket client。 1. WebSocket test Client “”“ Max.Bai W…...
量子计算将彻底改变商业分析
虽然量子计算听起来颇具未来感,但这项技术正迅速走向成熟 —— 就如同 ChatGPT 这类人工智能(AI)工具一样。我相信,量子计算技术所产生的连锁反应很快就会对业务分析领域产生巨大影响。 什么是量子计算? 尽管名字听起…...
爬山算法与模拟退火算法的全方面比较
一、基本概念与原理 1. 爬山算法 爬山算法是一种基于启发式的局部搜索算法,通过不断地向当前解的邻域中搜索更优解来逼近全局最优解。它的核心思想是,从当前解出发,在邻域内找到一个使目标函数值更大(或更小)的解作为新的当前解,直到找不到更优的解为止。 2.模拟退火算…...
【深度学习】用RML2018训练好模型去识别RML2016的数据会遇到输入维度不匹配的问题,如何解决?
文章目录 问题解决办法1. 调整输入数据长度2. 修改模型结构(我个人比较推崇的方法)3. 迁移学习4. 重新训练模型5. 数据增强6. 其他差异问题 经常会有人问的一个问题: 我用RML2018跑的调制识别模型,用RML2016数据集能直接识别吗?(2018数据集信号样本的长度是1024,2016数据集…...
2025年1月17日(点亮一个 LED)
系统信息: Raspberry Pi Zero 2W 系统版本: 2024-10-22-raspios-bullseye-armhf Python 版本:Python 3.9.2 已安装 pip3 支持拍摄 1080p 30 (1092*1080), 720p 60 (1280*720), 60/90 (640*480) 已安装 vim 已安装 git 学习目标:…...
商用车电子电气零部件电磁兼容条件和试验(8)—辐射抗干扰(ALSE)和便携式发射机抗干扰(HPT)
写在前面 本系列文章主要讲解商用车电子/电气零部件或系统的传导抗干扰、传导发射和辐射抗干扰、电场辐射发射以及静电放电等试验内容及要求,高压试验项目内容及要求。 若有相关问题,欢迎评论沟通,共同进步。(*^▽^*) 目录 商用车电子电气零部件电磁兼容条件和试验—目录…...
NumPy;NumPy在数据分析中的应用;NumPy与其他库的搭配使用
NumPy;NumPy在数据分析中的应用;NumPy与其他库的搭配使用 NumPy:Python 数据分析的核心工具什么是 NumPy?NumPy 的主要优势 NumPy 在数据分析中的应用1. 数据处理与清洗2. 数学和统计分析3. 数组变换与矩阵运算 NumPy 与其他库的搭…...
机器学习经典无监督算法——聚类K-Means算法
目录 算法原理 算法步骤 算法API 算法导入 API参数理解 算法实现 算法原理 Kmeans 算法是一种无监督的聚类算法,目的是将数据集中的样本划分到 K 个不同的簇中。 聚类:将数据集中相似的数据点归为一组或一个簇的过程。 数据集:一组相…...
网络变压器的分类
网络变压器是局域网(LAN)中各级网络设备中必备的元件。它们的主要功能是传输数据,增强信号,并提供电气隔离,以防雷保护和匹配阻抗。网络变压器也被称为数据泵或网络隔离变压器。它们广泛应用于网络交换机、路由器、网卡、集线器等设备中。 网…...
【MySQL】复合查询+表的内外连接
复合查询表的内外连接 1.基本查询回顾2.多表查询3.自连接4.子查询4.1单列子查询4.2多列子查询 5.在from子句中使用子查询6.合并查询7.表的内连和外连7.1内连接7.2外连接7.2.1左外连接7.2.2右外连接 点赞👍👍收藏🌟🌟关注…...
创建模式、结构模式及行为模式
谁在什么地方提供什么功能? 要设计几个类?这些类各个是什么功能?相互间的关系是什么? 创建模式指的是对象那么多,怎么把它"生"出来?生几个?从这个角度上来说数组就是一种另类的创建模式。主要…...
警惕IDEA 2024版重大Bug问题:LomBok失效、Gradle冲突、Spring Boot启动错误
一直以来我认为工具类的软件是越新越好,因为工具代表着一定的先进性;但是IDEA 2024好好的给我上了一课,比如lombok 不起作用、比如Spring Boot 3.4.x 启动报错、再比如MyBatis log plus冲突、再比如Gradle插件冲突. 一、Lombok 失效问题 请不…...
C语言中char str和char str[]的区别
char* str和char* str[]的区别:C语言中char *str[] 和char *str有什么区别-CSDN博客 char str 和 char str[] 在 C 语言中也有不同的含义和用途,以下是它们的区别: 1. char str 类型:这是一个单一的字符变量。 用途:…...
(学习总结20)C++11 可变参数模版、lambda表达式、包装器与部分新内容添加
C11 可变参数模版、lambda表达式、包装器与部分新内容添加 一、可变参数模版基本语法及原理包扩展emplace系列接口 二、lambda表达式lambda表达式语法捕捉列表lambda的原理lambda的应用 三、包装器bindfunction 四、部分新内容添加新的类功能1.默认的移动构造和移动赋值2.声明时…...
备份和容灾之区别(The Difference between Backup and Disaster Recovery)
备份和容灾之区别 备份和容灾都是数据安全常见的保障手段,但是一般在正常业务运行时是无需用到这两个技术手段的。只有在业务已经崩溃,需要进行业务恢复时,这两种技术的价值才能真正体现。所以,备份和容灾可以说是数据安全最后两…...
Go语言之路————数组、切片、map
Go语言之路————数组、切片、map 前言一、数组二、切片三、map 前言 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触Go,到后面能正常完成工作上的业务开发的过程ÿ…...
Kotlin Bytedeco OpenCV 图像图像57 图像ROI
Kotlin Bytedeco OpenCV 图像图像57 图像ROI 1 添加依赖2 测试代码3 测试结果 1 添加依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns"http://maven.apache.o…...
字符编码通过字节数组向16进制的互转【终端设备通讯案例篇】:微信小程序 JavaScript字符串转gb2312 字符编码,以16进制字符串传输。(接收蓝牙设备的信息,发送北斗终端消息)
文章目录 引言I 原理II 案例一:微信小程序字符串转gb2312 字符编码,以16进制字符串传输。字节数组 转16进制字符串微信小程序字符串转gb2312 字节数组(utf8ToGb2312Bytes)demo:gb2312 字符编码->字节数组->16进制字符串III 案例二: 小程序接收gb2312 的16进制字符串…...
LabVIEW与WPS文件格式的兼容性
LabVIEW 本身并不原生支持将文件直接保存为 WPS 格式(如 WPS 文档或表格)。然而,可以通过几种间接的方式实现这一目标,确保您能将 LabVIEW 中的数据或报告转换为 WPS 可兼容的格式。以下是几种常见的解决方案: 导出…...
协程(还需要输入3个字)
昨天我偶然听到协程这个概念,不禁好奇地了解了一下,做些笔记如下: 一、什么是协程 协程是在线程内部,由程序自己控制逻辑, 显式地让出控制权(yield)来实现任务切换,而不是由操作系…...
系统架构设计师-第1章-计算机系统知识要点
【本章学习建议】 根据考试大纲,本章主要考查系统架构设计师单选题,预计考1分左右。第二版教材2.2节增加了本块内容,但较为简略,需要课程补充,属于非重点内容。 1.1 计算机硬件组成 计算机的基本硬件系统由运算器、控…...
Linux -- 初识HTTP协议
目录 什么是HTTP协议 什么是 URL ? 理解 URL 初识HTTP 请求与响应格式 代码验证 gitee HTTP.hpp 结果 什么是HTTP协议 HTTP(HyperText Transfer Protocol,超文本传输协议)主要用于客户端(通常是浏览器&#…...
【已解决】我和ollama运行的qwen2.5大模型通信,总是返回GGGG?
起因: 和大模型通信的时候,总是返回 GGG 之类的乱码。 curl查询返回到结果: 既然curl通信不行,直接在控制台聊天呢? 话都说不明白,我火了,结果一查,我熄火了,是ollama…...
在Mac mini上实现本地话部署AI和知识库
在Mac mini上实现本地话部署AI和知识库 硬件要求:大模型AI,也叫LLM,需要硬件支持,常见的方式有2种:一种是采用英伟达之类支持CUDA库的GPU芯片或者专用AI芯片;第二种是采用苹果M系列芯片架构的支持统一内存架…...
SQL和MySQL以及DAX的日期表生成?数字型日期?将生成的日期表插入到临时表或者实体表中
几种生成日期表的方法 如何用SQL语句生成日期表呢? 如何用MySQL语句生成日期表呢? 如何用DAX语句生成日期表呢? 1. MySQL生成日期表 1.1 日期格式:yyyy-MM-dd 字符型 2024-01-02 -- 生成日期表 WITH RECURSIVE temp_dateTable …...
win32汇编环境,窗口程序中基础列表框的应用举例
;运行效果 ;win32汇编环境,窗口程序中基础列表框的应用举例 ;比如在窗口程序中生成列表框,增加子项,删除某项,取得指定项内容等 ;直接抄进RadAsm可编译运行。重点部分加备注。 ;以下是ASM文件 ;>>>>>>>>>>>…...
Sentinel配置流控规则详解
前言 在微服务架构中,流量控制(Flow Control)是保障服务稳定性的重要手段之一。Sentinel作为一款开源的流量控制、熔断降级Java库,以其丰富的应用场景和完善的监控能力,在微服务保护中扮演了重要角色。本文将详细介绍…...
opencv图像基础学习
2.3图像的加密解密 源码如下: import cv2 import numpy as np import matplotlib.pyplot as plt def passImg():imgcv2.imread(./image/cat.jpg,0)h,wimg.shape#生成一个密码,加密key_imgnp.random.randint(0,256,size(h,w),dtypenp.uint8)img_addmcv2…...
递归40题!再见递归
简介:40个问题,有难有易,均使用递归完成,需要C/C的指针、字符串、数组、链表等基础知识作为基础。 1、数字出现的次数 由键盘录入一个正整数,求该整数中每个数字出现的次数。 输入:19931003 输出…...
javadoc使用dos命令生成api文档演示
新建一个文本后缀改java,名字改为类名 文本内容: /** * author ZZJ * version jdk23.0.1 */ public class Test{/*** 求输入两个参数范围以内整数的和* param n 接收的第一个参数,范围起点* param m 接收的第二个参数,范围终点*…...
OpenAI推出首个AI Agent!日常事项自动化处理!
2025 年1月15日,OpenAI 正式宣布推出一项名为Tasks的测试版功能 。 该功能可以根据你的需求内容和时间实现自动化处理。比方说,你可以设置每天早晨 7 点获取天气预报,或定时提醒遛狗等日常事项。 看到这里,有没有一种熟悉的感觉&a…...
uniapp实现“到这儿去”、拨打电话功能
"到这儿去" 在 UniApp 中实现“到这儿去”的功能,即调起地图导航至指定位置,对于不同的平台(小程序、H5、App)有不同的处理方式。下面将简单介绍如何在这些平台上实现该功能,并讨论位置信息的获取。后面需求会用到,先来找一些相关资料,并不一定很准确,但也来…...
T-SQL语言的计算机基础
T-SQL语言的计算机基础 引言 在当今信息技术迅猛发展的时代,数据已成为企业和组织决策的重要基础。而处理和管理数据的工具和语言也日益成为IT专业人员必备的技能之一。T-SQL(Transact-SQL)作为微软SQL Server数据库的扩展,是一…...
SpringBoot + Websocket实现系统用户消息通知
1、引入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.7</version><relativePath/> </parent> <dependencies><dependency>…...
基于 HTML5 Canvas 制作一个精美的 2048 小游戏--day 1
基于 HTML5 Canvas 制作一个精美的 2048 小游戏 在这个快节奏的生活中,简单而富有挑战性的游戏总能给我们带来乐趣。2048 是一款受欢迎的益智游戏,不仅考验智力,还能让人回味无穷。今天,我带领大家将一起学习如何使用 HTML5 Canv…...
macos arm 本地/docker/本地k8s 安装jupyterhub 并登陆
概述 很多文章写的启动官方docker镜像后,新建linux用户即可直接登录,不知道是否版本原因,总之目前最新版我亲测不可以,踩坑两天,这里记录下解决过程,以及各种细节在文档中的位置.以及为什么官方镜像不能直接使用的原因. part1 本地安装jupyterhub https://jupyterhub.readth…...
go采集注册表
package mainimport ("fmt""golang.org/x/sys/windows/registry""log""os""strconv""strings" )func USBSTOR_Enum() {// 打开注册表键keyPath : SYSTEM\CurrentControlSet\Services\USBSTOR\Enumk, err : regist…...