实例代码
#编码的例子 from transformers import BertTokenizer # 1. 加载本地模型和分词器 model_dir = "D:\\本地模型\\google-bert\\bert-base-chinese" tokenizer = BertTokenizer.from_pretrained(model_dir) #打印特殊符号 print(tokenizer) sents = ["酒店太旧了,大堂感觉象三星级的,房间也就是的好点的三星级的条件,在青岛这样的酒店是绝对算不上四星标准,早餐走了两圈也没有" "已经贴完了,又给小区的妈妈买了一套。最值得推荐", "屏幕大,本本薄,自带数字小键盘,比较少见。声音也还过得去。usb接口多,有四个。独显看高清很好。运行速度也还可以,性价比高!", "酒店环境很好 就是有一点点偏 交通不是很便利 去哪都需要达车 关键是不好打 酒店应该想办法解决一下"] #批量编码句子 out = tokenizer.batch_encode_plus( batch_text_or_text_pairs=[sents[0],sents[1]], add_special_tokens=True, #当句子长度大于max_length时,截断 truncation=True, #一律补零到max_length长度 padding="max_length", max_length=30, #可取tf,pt,np,默认为返回list return_tensors=None, #返回attention_mask return_attention_mask=True, #返回token_type_ids #返回offset_mapping 标识每个词的起止位置,这个参数只BertTokenizerFast使用 # return_offsets_mapping=True, #返回length 标识长度 return_length=True, ) #input_ids 就是编码后的词 #token_type_ids 第一个句子和特殊符号的位置是0,第二个句子的位置是1 #special_tokens_mask 特殊符号的位置是1,其他位置是0 #attention_mask pad的位置是0,其他位置是1 #length 返回句子长度 print(out) for k, v in out.items(): print(k,":",v) print(tokenizer.decode(out["input_ids"][0]),tokenizer.decode(out["input_ids"][1]))输出特殊符号
无法识别的字符,全部定义为unk
实例2 字典操作
# 字典操作中添加新词 from transformers import ( BertTokenizer, ) # 1. 加载本地模型和分词器 model_dir = "D:\\本地模型\\google-bert\\bert-base-chinese" tokenizer = BertTokenizer.from_pretrained(model_dir) #打印特字典 vocab=tokenizer.get_vocab() print(len(vocab)) print("阳" in vocab) print("光" in vocab) #以每一个中文字符为节点划分,所以添加的词不在vocab中 print("阳光" in vocab) #添加新词 tokenizer.add_tokens(new_tokens=["阳光","大地"]) vocab = tokenizer.get_vocab() print("阳光" in vocab) print(tokenizer.tokenize("阳光")) print(len(vocab)) #添加新的特殊符号 tokenizer.add_special_tokens({"eos_token":"[EOS]"}) vocab = tokenizer.get_vocab() print(vocab) print(tokenizer) print("[EOS]" in vocab) out=tokenizer.encode(text="阳光照在大地上[EOS]", text_pair=None, truncation=True, padding="max_length", max_length=10, add_special_tokens=True) print(out) #解码为原来的字符串 print(tokenizer.decode(out))BERT(Bidirectional Encoder Representations from Transformers)使用的是WordPiece分词算法,该算法将单词分解为更小的子词单元。对于中文文本,BERT分词器通常采用字符级分词,将每个汉字视为独立的token。
词汇表(字典)是分词器的核心组成部分,它决定了模型能够理解和处理哪些词汇。当遇到词汇表中不存在的词汇时,分词器会将其拆分为已知的子词,这可能导致语义信息的丢失。
2.1 原始词汇表的局限性
让我们通过代码示例来观察原始词汇表的局限性:
from transformers import BertTokenizer # 加载本地模型和分词器 model_dir = "D:\\本地模型\\google-bert\\bert-base-chinese" tokenizer = BertTokenizer.from_pretrained(model_dir) # 打印词汇表基本信息 vocab = tokenizer.get_vocab() print(f"原始词汇表大小: {len(vocab)}") # 检查单个字符是否在词汇表中 print("'阳'在词汇表中:", "阳" in vocab) # 输出: True print("'光'在词汇表中:", "光" in vocab) # 输出: True # 检查词语是否在词汇表中 print("'阳光'在词汇表中:", "阳光" in vocab) # 输出: False从输出结果可以看出,虽然单个汉字"阳"和"光"都存在于词汇表中,但它们的组合"阳光"却不在其中。这是因为BERT-base-chinese模型采用字符级分词,词汇表主要包含单个汉字和少量常见词语。
2.2 词汇缺失的影响
当词汇表中缺少特定词语时,分词器会将其拆分为单个字符。例如:
"阳光" → ["阳", "光"]
"人工智能" → ["人", "工", "智", "能"]
这种拆分可能导致以下问题:
语义信息丢失:词语的整体含义被分解
序列长度增加:影响模型处理效率
上下文理解不准确:模型难以捕捉词语的整体语义
三、解决方案:动态添加新词
3.1 使用add_tokens方法扩展词汇表
Transformers库提供了便捷的add_tokens方法,允许我们动态地向分词器添加新词:
# 添加新词 tokenizer.add_tokens(new_tokens=["阳光", "大地"]) # 验证添加结果 vocab = tokenizer.get_vocab() print("'阳光'在词汇表中:", "阳光" in vocab) # 输出: True print(f"扩展后词汇表大小: {len(vocab)}") # 增加了2 # 测试分词效果 print("分词结果:", tokenizer.tokenize("阳光")) # 输出: ['阳光']3.2 方法详解
add_tokens方法的主要特性:
支持批量添加:可以一次添加多个新词
保持分词一致性:新词会被作为一个整体进行分词
词汇表动态扩展:不会影响原有词汇表的功能
四、实际应用场景
4.1 专业领域术语处理
在医疗、法律、金融等专业领域,存在大量专业术语。例如,在医疗领域可以添加:
medical_terms = ["冠状动脉", "心电图", "核磁共振", "化疗"] tokenizer.add_tokens(new_tokens=medical_terms)4.2 新兴词汇和网络用语
随着语言的发展,不断涌现出新词汇和网络用语:
new_words = ["元宇宙", "区块链", "内卷", "躺平"] tokenizer.add_tokens(new_tokens=new_words)4.3 特定应用场景定制
针对具体应用场景,可以添加相关词汇:
# 电商场景 ecommerce_words = ["包邮", "满减", "预售", "秒杀"] tokenizer.add_tokens(new_tokens=ecommerce_words)五、注意事项与最佳实践
5.1 模型权重调整
添加新词后,模型的embedding层需要相应扩展。如果继续使用原有模型,新添加的词会被随机初始化。建议对新词进行微调:
# 扩展模型embedding层 model.resize_token_embeddings(len(tokenizer)) # 对新添加的词进行微调 # ... 微调代码 ...5.2 词汇添加策略
必要性评估:只添加高频且重要的词汇
避免冗余:不要添加已存在的子词组合
批量处理:对大量词汇进行批量添加以提高效率
5.3 性能考虑
词汇表大小:过大的词汇表会影响模型推理速度
内存占用:增加词汇表会占用更多内存
训练效率:微调时需要更多计算资源
六、完整示例代码
from transformers import BertTokenizer, BertModel import torch # 1. 加载分词器和模型 model_dir = "D:\\本地模型\\google-bert\\bert-base-chinese" tokenizer = BertTokenizer.from_pretrained(model_dir) model = BertModel.from_pretrained(model_dir) # 2. 原始词汇表分析 original_vocab_size = len(tokenizer) print(f"原始词汇表大小: {original_vocab_size}") # 3. 添加新词 new_tokens = ["阳光", "大地", "人工智能", "机器学习"] tokenizer.add_tokens(new_tokens) print(f"添加{len(new_tokens)}个新词后词汇表大小: {len(tokenizer)}") # 4. 调整模型embedding层 model.resize_token_embeddings(len(tokenizer)) # 5. 测试分词效果 test_text = "今天的阳光很好,适合机器学习实践。" tokens = tokenizer.tokenize(test_text) print(f"分词结果: {tokens}") # 6. 编码测试 inputs = tokenizer(test_text, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) print(f"编码后张量形状: {outputs.last_hidden_state.shape}")七、总结
动态扩展BERT分词器的词汇表是一项实用且重要的技术,能够有效解决实际应用中的词汇覆盖问题。通过本文的详细解析,我们了解到:
问题根源:预训练模型的词汇表难以覆盖所有应用场景
解决方案:使用
add_tokens方法动态添加新词应用价值:提升模型对专业术语、新兴词汇的理解能力
最佳实践:合理选择添加词汇,注意模型权重调整
在实际应用中,建议根据具体需求制定词汇添加策略,平衡模型性能和词汇覆盖范围。通过合理的词汇扩展和模型微调,可以显著提升模型在特定领域的表现。