机器学习|从0开发大模型之Tokenizer训练
大模型开发/技术交流
- LLM
2024.11.28559看过
机器学习|从0开发大模型之Tokenizer训练
继续写《从0开发大模型》系列文章,本文主要介绍从头快速训练一个Tokenizer。
1、Tokenizer
什么是Tokenizer,作用是什么?对于从0开发大模型的开发者,这里解释一下。Tokenizer(标记器)是 NLP 管道的核心组件之一,它们有一个目的:将文本转换为模型可以处理的数据。
模型只能处理数字,因此Tokenizer需要将我们的文本输入转换为数字数据,比如在NLP的任务中,有如下原始文本:
我是中国人,我爱我的祖国
以上文本我们没法直接丢给模型处理,因此需要将原始文本分词,然后将对应的分词分配对应的ID,从 0 开始一直到词汇表的大小,那么该模型使用这些 ID 来识别每个词,比如上述文本变成:我 | 是 | 中 | 国 | 人 | , | 我 | 爱 | 我 | 的 | 祖 | 国
当然像这种词标记的技术有很多,如:
Byte-level BPE, 用于 GPT-2;
WordPiece, 用于 BERT;
...
2、准备数据
Tokenizer训练之前需要准备数据,其中huggingface上有很多相关的数据(huggingface.co/datasets?so…
def read_texts_from_jsonl(file_path):with open(file_path, 'r', encoding='utf-8') as f:for line in f:data = json.loads(line)yield data['text']data_path = '../datasets/tokenizer_train.jsonl'
3、训练
准备好数据以后,使用
tokenizers
库来训练,如下代码:
tokenizer = Tokenizer(models.BPE())tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
首先创建了一个
BPE
模型的分词器实例,并设置其预处理器为 ByteLevel
,这意味着输入文本将被视为字节流进行处理,add_prefix_space=False
表示在每个字节前不添加空格。
# 定义特殊tokenspecial_tokens = ["<unk>", "<s>", "</s>"]# 设置训练器并添加特殊tokentrainer = trainers.BpeTrainer(vocab_size=2048,special_tokens=special_tokens, # 确保这三个token被包含show_progress=True,initial_alphabet=pre_tokenizers.ByteLevel.alphabet())
然后定义
special_tokens
,指定词汇表大小为2048,其中三个特殊的token:<unk>
(未知词),<s>
(开始标记),</s>
(结束标记)。
texts = read_texts_from_jsonl(data_path)tokenizer.train_from_iterator(texts, trainer=trainer)tokenizer.decoder = decoders.ByteLevel()tokenizer_dir = "./my_tokenizer"os.makedirs(tokenizer_dir, exist_ok=True)tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json"))tokenizer.model.save("./my_tokenizer")
读取预处理的数据,通过
tokenizer.train_from_iterator
训练分词器,设置 tokenizer.decoder
解码,用于将token还原回原始文本,最后将分词模型保存到 my_tokenizer
文件夹中。
这里
HuggingFace
的 Tokenizer
还需要用到 tokenizer_config.json,用于分词模型的配置信息,用于指定分词模型的超参和其他的相关信息,例如分词器的类型、词汇表大小、最大序列长度、特殊标记等,这个一般固定就可以。
4、测试
tokenizer = AutoTokenizer.from_pretrained("./my_tokenizer")new_prompt = '我是中国人,我爱我的祖国 @微博 当前数据为测试文件! 'print(new_prompt)model_inputs = tokenizer(new_prompt)print(model_inputs)print('长度:', len(model_inputs['input_ids']))// 输出我是中国人,我爱我的祖国 @微博 当前数据为测试文件!{'input_ids': [397, 345, 413, 1317, 415, 270, 397, 1699, 397, 667, 101, 247, 1317, 223, 34, 454, 109, 469, 251, 223, 938, 1143, 1327, 428, 1577, 1410, 677, 1223, 264, 226, 223], '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], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}长度: 31
上述代码主要是加载预训练模型,然后打印对应的词的ID,然后通过反序列:
input_ids_ = model_inputs['input_ids']response = tokenizer.decode(input_ids_)print(response, end='')// 输出我是中国人,我爱我的祖国 @微博 当前数据为测试文件!
5、总结
(1)以上就是训练一个
Tokenizer
的流程,当然也可以使用其他的,比如使用gpt2:
from transformers import AutoModelForCausalLM, AutoTokenizermodel = AutoModelForCausalLM.from_pretrained("gpt2")tokenizer = AutoTokenizer.from_pretrained("gpt2")prompt = "GPT2 is a model developed by OpenAI."input_ids = tokenizer(prompt, return_tensors="pt").input_idsgen_tokens = model.generate(input_ids,do_sample=True,temperature=0.9,max_length=100,)gen_text = tokenizer.batch_decode(gen_tokens)[0]print("gen_text: ", gen_text)
(2)完整的
0-train_tokenizer
代码如下:
import randomimport jsonfrom tokenizers import (decoders,models,pre_tokenizers,trainers,Tokenizer,)import osrandom.seed(42)def train_tokenizer():# 读取JSONL文件并提取文本数据def read_texts_from_jsonl(file_path):with open(file_path, 'r', encoding='utf-8') as f:for line in f:data = json.loads(line)yield data['text']data_path = '../datasets/tokenizer_train.jsonl'# 初始化tokenizertokenizer = Tokenizer(models.BPE())tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)# 定义特殊tokenspecial_tokens = ["<unk>", "<s>", "</s>"]# 设置训练器并添加特殊tokentrainer = trainers.BpeTrainer(vocab_size=2048,special_tokens=special_tokens, # 确保这三个token被包含show_progress=True,initial_alphabet=pre_tokenizers.ByteLevel.alphabet())# 读取文本数据texts = read_texts_from_jsonl(data_path)# 训练tokenizertokenizer.train_from_iterator(texts, trainer=trainer)# 设置解码器tokenizer.decoder = decoders.ByteLevel()# 检查特殊token的索引assert tokenizer.token_to_id("<unk>") == 0assert tokenizer.token_to_id("<s>") == 1assert tokenizer.token_to_id("</s>") == 2# 保存tokenizertokenizer_dir = "./my_tokenizer"os.makedirs(tokenizer_dir, exist_ok=True)tokenizer.save(os.path.join(tokenizer_dir, "tokenizer.json"))tokenizer.model.save("./my_tokenizer")# 手动创建配置文件config = {"add_bos_token": False,"add_eos_token": False,"add_prefix_space": True,"added_tokens_decoder": {"0": {"content": "<unk>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"1": {"content": "<s>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True},"2": {"content": "</s>","lstrip": False,"normalized": False,"rstrip": False,"single_word": False,"special": True}},"additional_special_tokens": [],"bos_token": "<s>","clean_up_tokenization_spaces": False,"eos_token": "</s>","legacy": True,"model_max_length": 1000000000000000019884624838656,"pad_token": None,"sp_model_kwargs": {},"spaces_between_special_tokens": False,"tokenizer_class": "PreTrainedTokenizerFast","unk_token": "<unk>","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' %}{{ '<s>user\n' + content + '</s>\n<s>assistant\n' }}{% elif message['role'] == 'assistant' %}{{ content + '</s>' + '\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.")def eval_tokenizer():from transformers import AutoTokenizer# 加载预训练的tokenizertokenizer = AutoTokenizer.from_pretrained("./my_tokenizer")messages = [{"role": "system", "content": "你好,你是中国人,请用中文回答我的问题"},{"role": "user", "content": '123'},]new_prompt = tokenizer.apply_chat_template(messages,tokenize=False)print(new_prompt)# 获取词汇表大小(不包括特殊符号)print('tokenizer词表大小:', tokenizer.vocab_size)# 获取实际词汇表长度(包括特殊符号)actual_vocab_size = len(tokenizer)print('实际词表长度:', actual_vocab_size)new_prompt = '我是中国人,我爱我的祖国 @微博 当前数据为测试文件! 'print(new_prompt)model_inputs = tokenizer(new_prompt)print(model_inputs)print('长度:', len(model_inputs['input_ids']))input_ids_ = model_inputs['input_ids']response = tokenizer.decode(input_ids_)print(response, end='')def main():train_tokenizer()eval_tokenizer()if __name__ == '__main__':main()
参考
(1)
————————————————
版权声明:本文为稀土掘金博主「周末程序猿」的原创文章
原文链接:https://juejin.cn/post/7432964821056159780
如有侵权,请联系千帆社区进行删除
评论
