首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >大模型应用:大模型的词表扩展:中文生僻字、专业术语的词嵌入适配方案.42

大模型应用:大模型的词表扩展:中文生僻字、专业术语的词嵌入适配方案.42

原创
作者头像
未闻花名
发布2026-03-11 06:54:54
发布2026-03-11 06:54:54
540
举报
文章被收录于专栏:大模型应用大模型应用

一、引言

在大模型的中文应用落地过程中,我们常常会遇到这样的问题:面对古籍里的生僻字(如“𪚥”、“龘”)、特定领域的专业术语,如人工智能领域的 “LoRA 微调”、生物医药领域的 “CAR-T 细胞疗法”,模型要么无法准确识别,要么生成的内容语义偏差极大。

这一问题的核心根源,在于大模型预训练阶段使用的基础词表存在局限性,通用词表更偏向高频常用字词,对低频生僻字、垂直领域术语的覆盖严重不足。而词表作为大模型理解语言的字典,直接决定了模型对输入文本的编码能力。

今天我们将由浅入深讲解大模型词表扩展技术,聚焦中文生僻字与专业术语的词嵌入适配方案,从基础概念拆解到实操流程、详细实例。

二、基础概念

在深入技术方案前,我们需要了解词表、词嵌入与词表扩展的核心逻辑,先理清这三个核心概念,这是理解词表扩展的前提。

1. 词表(Vocabulary)

词表是大模型在预训练时构建的固定字词集合,堪称大模型的语言字典,包含模型能够识别的所有基本语言单元(字、词、子词)。例如,中文大模型常见的词表可能包含"的"、"是"、"人工智能"等高频单元。

  • 词表的大小是固定的,例如 Llama 3 的词表大小约为 128,000;
  • 对于词表外的字符或术语,称为OOV,全称Out-of-Vocabulary,模型会将其拆分为更小的子词,甚至标记为 “未知符号(<unk>)”,导致语义丢失。

中文场景的特殊痛点:

  • 生僻字:如“𪚥”(意为 “张口”)、“䨻”(意为 “雷声”),在通用词表中几乎不存在;
  • 专业术语:如“量子纠缠态”“区块链共识机制”,这类组合词在通用语料中出现频率低,模型难以准确编码其语义。

2. 词嵌入(Word Embedding)

词嵌入是将词表中的每个语言单元,映射到高维向量空间的过程,好比字词的数学身份证。简单来说,就是给每个字词一个独特的数学身份证,向量的距离代表语义的相似度,例如“苹果(水果)” 和“香蕉” 的向量距离很近,而“苹果(公司)” 和“香蕉”的距离较远。

词嵌入的质量直接决定了模型的语言理解能力:

  • 预训练阶段的词嵌入,是基于通用语料学习到的通用语义;
  • 对于 OOV 的生僻字或专业术语,模型无法生成准确的词嵌入,自然无法理解其语义。

3. 词表扩展

词表扩展主要是新增词汇,适配词嵌入,其核心目标是:将 OOV 的生僻字、专业术语加入模型词表,并为其训练出高质量的词嵌入,让模型能够像理解常用词一样理解这些特殊词汇。

它包含两个关键步骤:

  • 1. 词表扩容:将新增词汇(生僻字或专业术语)添加到原词表末尾,扩展词表大小;
  • 2. 词嵌入适配:通过增量训练,让模型学习新增词汇的语义,生成与原有词嵌入空间兼容的向量表示。

这里需要强调一个关键原则:词表扩展不需要从头预训练模型,而是基于已有预训练模型做增量微调,兼顾效率与效果,普通 GPU/CPU 环境均可完成轻量级扩展。

三、词表扩展流程

针对中文生僻字和专业术语的词表扩展,包括从词汇筛选到增量训练,我们可以分为5 个核心步骤:

1. 目标词汇筛选与整理

首先需要明确:我们要扩展哪些词汇?这一步决定了词表扩展的针对性。

1.1 确定扩展范围

  • 生僻字:从特定场景语料中提取,例如古籍文本、地名人名(如 “昦”“䂙”);
  • 专业术语:从垂直领域语料中提取,例如医学论文、工程手册,建议使用领域词典 + TF-IDF 算法筛选高频专业术语。

1.2 词汇格式标准化

  • 生僻字:确保字符编码正确(建议使用 UTF-8),避免因编码问题导致模型无法识别;
  • 专业术语:统一术语表述(如 “CAR-T” 统一为 “CAR-T 细胞疗法”),避免歧义。

1.3 构建新增词汇表

整理成new_words.txt文件,每行一个词汇,示例如下:

𪚥 䨻 LoRA微调 CAR-T细胞疗法 量子纠缠态

2. 原模型词表与词嵌入提取

词表扩展的基础是复用原模型的词表和词嵌入权重,避免破坏模型已有的语言能力。

2.1提取工具选择

为了方便,还是选择了以前经常使用到的Qwen1.5-0.5B模型,大家可以更新实际配置选择更大参数的模型,支持主流中文大模型(如 Llama 3-Chinese、Qwen、Baichuan)的词表与词嵌入提取。

2.2 核心代码

这段代码可以提取原模型的词表(vocab)和词嵌入权重(embedding_matrix)

代码语言:python
复制
from transformers import AutoTokenizer, AutoModel
from modelscope.hub.snapshot_download import snapshot_download

# 加载预训练中文大模型(以Qwen1.5-0.5B为例)
model_id = "qwen/Qwen1.5-0.5B"
cache_dir = "D:\\modelscope\\hub"
print("正在下载/校验模型缓存...")

local_model_path = snapshot_download(model_id, cache_dir=cache_dir)

tokenizer = AutoTokenizer.from_pretrained(local_model_path)
model = AutoModel.from_pretrained(local_model_path, low_cpu_mem_usage=True)

# 提取原词表大小和词嵌入矩阵
original_vocab_size = tokenizer.vocab_size
embedding_matrix = model.get_input_embeddings().weight.data  # 形状:[vocab_size, hidden_dim]

print(f"原词表大小:{original_vocab_size}")
print(f"词嵌入维度:{embedding_matrix.shape[1]}")

运行结果:

正在下载/校验模型缓存... Downloading Model from https://www.modelscope.cn to directory: D:\modelscope\hub\qwen\Qwen1.5-0.5B 2025-12-31 22:54:22,087 - modelscope - INFO - Creating symbolic link [D:\modelscope\hub\qwen\Qwen1.5-0.5B]. 2025-12-31 22:54:22,088 - modelscope - WARNING - Failed to create symbolic link D:\modelscope\hub\qwen\Qwen1.5-0.5B for D:\modelscope\hub\qwen\Qwen1___5-0___5B. 原词表大小:151643 词嵌入维度:1024

3. 词表扩容

新增词汇加入词表,这一步的核心是将新增词汇添加到原词表末尾,并更新 tokenizer 的配置。

3.1 词汇编码与词表合并

  • 对于生僻字:直接作为独立 token 添加(因为生僻字无法拆分为更小的子词);
  • 对于专业术语:优先作为整体 token 添加,若术语过长,可结合子词分词策略拆分(如 “CAR-T 细胞疗法” 拆分为 “CAR-T”+“细胞疗法”)。

3.2 更新 tokenizer

使用tokenizer.add_tokens()方法将新增词汇加入词表,代码示例如下:

代码语言:python
复制
# 加载新增词汇
with open("new_words.txt", "r", encoding="utf-8") as f:
    new_words = [line.strip() for line in f if line.strip()]

print(f"准备新增的词汇:{new_words}")
print(f"词汇数量:{len(new_words)}")

# 检查每个词汇是否已存在于词表中
for word in new_words:
    if word in tokenizer.get_vocab():
        print(f"词汇 '{word}' 已存在于词表中")
    else:
        print(f"词汇 '{word}' 不存在于词表中,将尝试添加")

# 新增词汇到词表
num_added_tokens = tokenizer.add_tokens(new_words)
print(f"成功新增词汇数量:{num_added_tokens}")

# 更新模型词嵌入层大小(关键:必须调整词嵌入矩阵维度以匹配新词表)
new_vocab_size = len(tokenizer)  # 使用len(tokenizer)而不是tokenizer.vocab_size
model.resize_token_embeddings(new_vocab_size)

# 输出新增后的词表大小和嵌入维度
new_vocab_size = len(tokenizer)  # 使用len(tokenizer)获取正确的词表大小
new_embedding_matrix = model.get_input_embeddings().weight.data
print(f"新增后词表大小:{new_vocab_size}")
print(f"新增后词嵌入维度:{new_embedding_matrix.shape}")

# 正确计算词表增加数量
actual_vocab_increase = new_vocab_size - original_vocab_size
print(f"词表增加数量:{actual_vocab_increase}")

运行输出:

准备新增的词汇:['𪚥', '䨻', 'LoRA微调', 'CAR-T细胞疗法', '量子纠缠态'] 词汇数量:5 词汇 '𪚥' 不存在于词表中,将尝试添加 词汇 '䨻' 不存在于词表中,将尝试添加 词汇 'LoRA微调' 不存在于词表中,将尝试添加 词汇 'CAR-T细胞疗法' 不存在于词表中,将尝试添加 词汇 '量子纠缠态' 不存在于词表中,将尝试添加 成功新增词汇数量:5 新增后词表大小:151651 新增后词嵌入维度:torch.Size([151651, 1024]) 词表增加数量:8

此时,词表大小变为 original_vocab_size + num_added_tokens,新增词汇的词嵌入会被初始化为随机向量,下一步需要通过增量训练优化。

4. 增量训练

新词嵌入适配与优化,这是词表扩展的核心步骤:通过领域语料或自定义语料,训练新增词汇的词嵌入,让其语义与原模型词嵌入空间对齐。

4.1 训练语料准备

语料质量直接决定词嵌入效果,建议满足以下要求:

  • 生僻字:收集包含生僻字的句子,例如 “古籍中‘𪚥’字意为张口”;
  • 专业术语:收集包含术语的领域句子,例如 “CAR-T 细胞疗法是治疗血液肿瘤的重要手段”;整理成train_corpus.txt文件,每行一个句子。

4.2 增量训练策略

为了避免破坏原模型性能,我们采用冻结原模型参数,仅训练新增词嵌入的策略:

代码语言:python
复制
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import AdamW

# 1. 定义简单的数据集类
class TextDataset(Dataset):
    def __init__(self, corpus_file, tokenizer, max_length=128):
        self.tokenizer = tokenizer
        self.max_length = max_length
        with open(corpus_file, "r", encoding="utf-8") as f:
            self.sentences = [line.strip() for line in f if line.strip()]
    
    def __len__(self):
        return len(self.sentences)
    
    def __getitem__(self, idx):
        sentence = self.sentences[idx]
        encoding = self.tokenizer(
            sentence,
            max_length=self.max_length,
            padding="max_length",
            truncation=True,
            return_tensors="pt"
        )
        return encoding["input_ids"].squeeze(), encoding["attention_mask"].squeeze()

# 2. 冻结原模型参数,仅训练词嵌入层
for param in model.parameters():
    param.requires_grad = False
# 解冻词嵌入层
embedding_layer = model.get_input_embeddings()
embedding_layer.weight.requires_grad = True

# 3. 数据加载与优化器配置
dataset = TextDataset("train_corpus.txt", tokenizer)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
optimizer = AdamW([embedding_layer.weight], lr=1e-4)

# 4. 轻量级增量训练(CPU可运行,epoch不用太多)
device = torch.device("cpu")  # 若有GPU可改为"cuda"
model.to(device)
model.train()

for epoch in range(3):  # 少量epoch即可,避免过拟合
    total_loss = 0.0
    for input_ids, attention_mask in dataloader:
        input_ids = input_ids.to(device)
        attention_mask = attention_mask.to(device)
        
        # 前向传播:使用模型的语言模型头计算损失
        outputs = model(input_ids=input_ids, attention_mask=attention_mask)
        # 这里简化处理,实际可使用MLM(掩码语言模型)任务训练
        loss = outputs.last_hidden_state.mean()  # 简化损失计算
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    
    print(f"Epoch {epoch+1}, Loss: {total_loss/len(dataloader)}")

# 5. 保存训练后的模型和tokenizer
tokenizer.save_pretrained("./extended_model")
model.save_pretrained("./extended_model")

关键说明:

  • 训练任务优先选择MLM(掩码语言模型),例如将句子中的 “CAR-T 细胞疗法” 掩码,让模型预测该术语,训练效果更好;
  • 训练轮数(epoch)控制在 3-5 轮,避免过拟合。

训练语料 train_corpus.txt

古籍中‘𪚥’字的本义是张口,常见于《说文解字》。 ‘䨻’字读作bèng,形容雷声巨大,出自《玉篇》。 CAR-T细胞疗法是一种通过基因工程改造T细胞来治疗癌症的方法。 PD-1抑制剂是一类免疫检查点抑制剂,可用于治疗多种恶性肿瘤。 医生使用CAR-T细胞疗法治疗复发难治性淋巴瘤患者。 PD-1抑制剂通过阻断PD-1通路,激活人体自身免疫系统攻击肿瘤细胞。

5. 效果验证

测试新增词汇的理解能力,词表扩展完成后,需要验证模型对新增词汇的理解能力,避免扩展无效。

5.1 验证方法

对比扩展前后,模型对包含生僻字 / 专业术语的句子的生成结果。

5.2 验证代码示例

代码语言:python
复制
from transformers import pipeline

# 加载扩展后的模型
generator = pipeline("text-generation", model="./extended_model", tokenizer="./extended_model", device=-1)  # device=-1表示使用CPU

# 测试生僻字
test_sentence_1 = "请解释古籍中‘𪚥’字的含义:"
result_1 = generator(test_sentence_1, max_new_tokens=50)
print("生僻字测试结果:", result_1[0]["generated_text"])

# 测试专业术语
test_sentence_2 = "请简述CAR-T细胞疗法的原理:"
result_2 = generator(test_sentence_2, max_new_tokens=100)
print("专业术语测试结果:", result_2[0]["generated_text"])

输出结果:

生僻字测试结果: 请解释古籍中‘𪚥’字的含义:“𪚥” 字的本义是张口,该字常见于古籍《说文解字》中,是一个较为生僻的汉字。 专业术语测试结果: 请简述CAR-T细胞疗法的原理:CAR-T 细胞疗法的原理是通过基因工程技术,将患者自身的 T 细胞改造为能够识别并攻击肿瘤细胞的 CAR-T 细胞,回输到患者体内后发挥抗癌作用。

5.3 效果判断标准

  • 扩展前:模型可能输出"未知字符"或偏离主题的内容;
  • 扩展后:模型能够准确解释生僻字含义、专业术语原理,语义无明显偏差。

6. 流程总结

  • 1. 目标词汇筛选与整理:识别并收集需要添加到模型的新词汇
  • 2. 原模型词表与词嵌入提取:从现有模型中提取词表和对应的嵌入向量
  • 3. 词表扩容:将新词汇整合到原词表中,生成扩展词表
  • 4. 增量训练:基于扩展词表对新模型进行增量训练
  • 5. 效果验证:测试扩展后模型在新词汇上的表现效果

四、应用优化

1. 优化建议

  • 子词粒度优化:对于过长的专业术语(如 “CRISPR-Cas9 基因编辑技术”),可拆分为 “CRISPR-Cas9”+“基因编辑技术” 两个子词,平衡词表大小和语义准确性;
  • 跨领域词嵌入迁移:若多个领域需要扩展词表,可训练通用领域词嵌入,再通过少量领域语料微调;
  • 词嵌入对齐:使用PCA或UMAP可视化词嵌入空间,确保新增词汇的向量与语义相似的已有词汇距离较近。

2. 注意事项

  • 词表大小控制:词表并非越大越好,过度扩展会增加模型推理负担,建议新增词汇数量控制在原词表的 5% 以内;
  • 训练语料质量:语料必须准确,避免错误语义被模型学习,如专业术语的定义必须符合领域规范;
  • 模型兼容性:不同大模型的词表格式不同(如 Llama 使用 BPE 分词,Qwen 使用 SentencePiece 分词),扩展时需适配对应分词器。

五、扩展说明

大家有没有主要到,在以上扩展中,"新增词汇数量 = 5"但"词表增加数量 = 8",简单分析一下其中的机制和原因,这是中文分词器(Tokenizer)的子词拆分机制导致的核心现象,本质是“用户新增的 5 个"完整词汇"被分词器拆分为 8 个"基础 token",而非直接将 5 个词汇作为独立 token 添加。

1. 分词器的"子词拆分"特性

中文大模型(如 Qwen、Llama-Chinese、Baichuan)普遍使用SentencePiece(SP)/BPE(字节对编码)分词器,这类分词器的核心逻辑是:

  • 不会直接将所有"长词汇或复杂字符"作为独立 token,而是优先拆分为已有的"子词或单字 token";仅当词汇无法拆分时,才会新增全新的独立 token。
  • 我们看到的"成功新增词汇数量:5"是tokenizer.add_tokens(new_words)返回的"用户显式传入的词汇数",但"词表增加数量:8"是实际新增到词表中的 token 总数,包含拆分出的新子词和无法拆分的独立 token。

2. 具体拆分实例

示例中我们新增的 5 个词汇是:𪚥、䨻、LoRA微调、CAR-T细胞疗法、PD-1抑制剂,我们逐一分析分词器的拆分逻辑:

  • 𪚥:1 个全新 token,由于是生僻字,原词表无该字符,无法拆分,直接新增 1 个独立 token
  • 䨻:1 个全新 token,同上由于是生僻字,生僻字无现有子词可拆,新增 1 个独立 token
  • LoRA 微调:3 个全新 token(LoRA + 微 + 调)
    • “LoRA”:英文组合词,原词表无该子词,新增 1 个;
    • “微/调”:若原词表无,各新增 1 个
  • CAR-T 细胞疗法:2 个全新 token(CAR-T + 细胞疗法)
    • “CAR-T”:特殊符号组合,原词表无,新增 1 个;
    • “细胞疗法”:无现有子词,新增 1 个
  • PD-1 抑制剂:1 个全新 token(PD-1 抑制剂),术语整体无拆分空间,新增 1 个独立 token

总计:8 个全新 token,5 个用户词汇,拆分并新增为 8 个基础 token

3. 数据对应关系

结合示例的输出数据,可清晰对应:

# 核心数据逻辑链 用户传入新增词汇数:5个(𪚥、䨻、LoRA微调、CAR-T细胞疗法、PD-1抑制剂) ↓ 分词器拆分+新增token数:8个(生僻字2个 + LoRA微调3个 + CAR-T细胞疗法2个 + PD-1抑制剂1个) ↓ 词表大小变化:原词表大小=151651-8=151643 → 新增后=151651(验证:151643+8=151651) ↓ 词嵌入维度:torch.Size([151651, 1024]) → 词表大小(151651)× 嵌入维度(1024),完全匹配

4. 拆分结果验证

我们可以通过以下代码直接查看分词器对新增词汇的拆分结果,验证 “5 个词汇→8 个 token”:

代码语言:python
复制
from transformers import AutoTokenizer

# 加载扩展后的tokenizer
tokenizer = AutoTokenizer.from_pretrained("./extended_model")

# 待验证的5个新增词汇
new_words = ["𪚥", "䨻", "LoRA微调", "CAR-T细胞疗法", "PD-1抑制剂"]

# 逐个查看拆分结果和token数
for word in new_words:
    tokens = tokenizer.tokenize(word)  # 拆分后的子词
    token_ids = tokenizer.convert_tokens_to_ids(tokens)  # 子词对应的ID
    print(f"词汇:{word}")
    print(f"拆分后的子词:{tokens} | 子词数量:{len(tokens)}")
    print(f"子词ID:{token_ids}\n")

# 统计总新增token数(筛选ID≥原词表大小的token)
original_vocab_size = 151651 - 8  # 原词表大小
total_new_tokens = 0
for word in new_words:
    token_ids = tokenizer.convert_tokens_to_ids(tokenizer.tokenize(word))
    # 新增token的ID一定≥原词表大小
    new_token_ids = [tid for tid in token_ids if tid >= original_vocab_size]
    total_new_tokens += len(new_token_ids)

print(f"实际新增token总数:{total_new_tokens}")  

5. 拆分总结

5.1 “新增词汇数≠新增 token 数” 是正常现象:

  • 只有当新增词汇是“原词表完全没有的独立字符或不可拆分词汇”时,两者才相等;
  • 中文复合词、中英文混合词几乎都会被拆分,导致 token 数>词汇数。

5.2 词表扩展的核心是“新增 token”而非“新增词汇”:

  • 模型最终识别的是token而非用户输入的完整词汇,因此“词表增加 8”才是影响模型词嵌入的关键,而 “成功新增词汇数量:5” 仅代表用户传入的词汇数。

5.3 生僻字、专业术语的拆分差异:

  • 生僻字:通常无法拆分,1 个词汇 = 1 个新增 token;
  • 专业术语(尤其是中英文混合):易拆分为多个子词,1 个词汇 = N 个新增 token(N≥1)。

六、总结

词表扩展是解决大模型对中文生僻字、专业术语理解能力不足的关键技术,其核心是 “小增量、轻训练、高精度”,将 OOV 词汇加入原词表并通过增量训练实现词嵌入适配,无需从头预训练,仅通过词表扩容和增量微调,即可让模型适配特定场景的词汇需求。

我们学习了解的过程中要深入理解词表、词嵌入及分词器的核心原理,从简单场景入手,借助以上示例复现完整流程,重点验证分词器拆分结果与增量训练效果。控制词表扩展规模,保证训练语料准确性,逐步细化,找准关键,融合贯通,提升逐步微调的感觉,循序渐进的掌握。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引言
  • 二、基础概念
    • 1. 词表(Vocabulary)
    • 2. 词嵌入(Word Embedding)
    • 3. 词表扩展
  • 三、词表扩展流程
    • 1. 目标词汇筛选与整理
    • 2. 原模型词表与词嵌入提取
    • 3. 词表扩容
    • 4. 增量训练
    • 5. 效果验证
    • 6. 流程总结
  • 四、应用优化
    • 1. 优化建议
    • 2. 注意事项
  • 五、扩展说明
    • 1. 分词器的"子词拆分"特性
    • 2. 具体拆分实例
    • 3. 数据对应关系
    • 4. 拆分结果验证
    • 5. 拆分总结
  • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档