怎么让英文大语言模型支持中文?(一)—— 构建中文 tokenization
📝通俗解释:就像学英语需要背单词一样,让电脑处理中文也需要"词汇表"。这篇文章教我们如何给原本只懂英文的 LLaMA 模型添加中文字典,这样它就能理解和处理中文了。
来源:AiGC面试宝典
作者:宁静致远
日期:2023年09月29日
一、为什么需要构建中文 tokenization?
大语言模型呈现百家争鸣的趋势,但从根本来看,主要以基于 LLaMA 家族的模型和基于 GLM 家族的模型为主。不过目前大多数 LLMs 还是以基于 LLaMA 家族的模型为主,然而原始 LLaMA 家族的模型主要训练语料为英文语料,中文语料占比较少,直接导致基于 LLaMA 家族的模型对于中文的支持不太友好。
📝通俗解释:LLaMA 是 Meta 公司开发的大语言模型,最初是为了处理英文设计的。它就像一个从小只学英语的人,突然让它学中文,会很吃力。我们需要给它准备一个"中文字典",让它能理解中文。
那么有什么办法能够解决基于 LLaMA 家族的模型对于中文支持不太友好的问题呢?
本文利用《斗破苍穹》作为语料,介绍如何扩充 vocab 里面的词以对中文进行 token 化。
📝通俗解释:《斗破苍穹》是一本非常流行的网络小说,用它来训练中文分词模型,可以学到很多常见的中文词汇和表达方式。
二、如何对原始数据预处理?
《斗破苍穹》原始数据
《斗破苍穹》来自:
===上架感言===
又一次上架了,这次比上次还激动,甚至激动到了上传了章节却不知道发出来的地步。
尴尬,关于新书,上架前成绩好得有些出乎土豆的意料,对于这份厚硕的成绩,土豆心里还真有几分惶恐与忐忑,虽说曾经有人说土豆是刷出来的数据,对于这些留言,我也并未太过在意,别的我不知道,我唯一能知道的,就是人在做,天在看!
究竟刷没刷,自己心中有杆秤就能衡量,问心无愧,何惧留言?
呵呵,在这里很感谢赐予土豆这种厚硕成绩的诸位书友,真的,很感谢你们。
...
上文摘取了部分《斗破苍穹》原始数据,从数据中可以看出,数据中包含大量换行和无效内容,所以需要对《斗破苍穹》原始数据进行预处理,将每一行转化为一句或多句话,同时过滤掉换行和无效内容。
📝通俗解释:原始数据就像一堆杂乱的草稿纸,需要整理成干净的文本。去掉那些乱码、换行符、无意义的符号,只保留有用的文字内容。
代码讲解
# step 1: 《斗破苍穹》原始数据加载
with open("data/《斗破苍穹》.txt", "r", encoding="utf-8") as fp:
data = fp.read().strip().split("\n")
# step 2: 将每一行转化为一句或多句话,同时过滤掉换行和无效内容
sentences = []
for d in data:
d = d.strip()
# 跳过包含分隔符、空白行或标题的行
if "===" in d or len(d) == 0 or d == "《斗破苍穹》来自:":
continue
sentences.append(d)
# step 3: 数据写入
with open("data/corpus.txt", "w", encoding="utf-8") as fp:
fp.write("\n".join(sentences))预处理之后的 corpus.txt
又一次上架了,这次比上次还激动,甚至激动到了上传了章节却不知道发出来的地步。 尴尬,关于新书,上架前成绩好得有些出乎土豆的意料,对于这份厚硕的成绩,土豆心里还真有几分惶恐与忐忑,虽说曾经有人说土豆是刷出来的数据,对于这些留言,我也并未太过在意,别的我不知道,我唯一能知道的,就是人在做,天在看!
三、如何构建中文的词库?
得到语料库 corpus.txt 之后,我们需要利用该 corpus.txt 构建中文的词库。这里采用 SentencePiece 训练中文词库。
📝通俗解释:SentencePiece 是一种分词工具,它会把文本切成一小块一小块的"词片"(token)。就像把一句话拆成单词一样,但中文没有空格,所以需要用算法来智能分词。
1. SentencePiece 安装
$ pip install sentencepiece2. 训练中文词库
import sentencepiece as spm
spm.SentencePieceTrainer.train(
input='data/corpus.txt',
model_prefix='tokenizer',
vocab_size=50000,
user_defined_symbols=['foo', 'bar'],
character_coverage=1.0,
model_type="bpe",
)参数介绍:
| 参数 | 说明 |
|---|---|
| input | 指定输入文本文件的路径或者是一个目录,可以指定多个输入文件或目录。其中每一行可以是一句话或者多句话 |
| model_prefix | 保存的模型的名称前缀 |
| vocab_size | 设置的词表大小 |
| user_defined_symbols | 用于指定用户自定义的符号。这些符号将会被视为单独的 Token,不会被拆分成子词。这里我们简单进行了测试 |
| model_type | 指定模型的类型,有三种可选参数:unigram, bpe, char, word |
| character_coverage | 指定覆盖字符的数量,可以理解为限制字符集的大小。默认值为 1.0,即覆盖全部字符 |
| unk_id | 指定未登录词的 ID 号,即在词表中为未登录词分配一个整数 ID。默认值为 0 |
| bos_id | 指定句子开头符号的 ID 号,即在词表中为句子开头符号分配一个整数 ID。默认值为 1 |
| eos_id | 指定句子结束符号的 ID 号,即在词表中为句子结束符号分配一个整数 ID。默认值为 2 |
| pad_id | 指定填充符号的 ID 号,即在词表中为填充符号分配一个整数 ID。默认值为 -1,即不使用填充符号 |
📝通俗解释:这些参数就像做菜的配方,vocab_size 决定我们要准备多少个"词片",model_type 决定我们用什么方法来切分文字。
运行上述代码之后会得到 tokenizer.model 和 tokenizer.vocab 两个文件。
tokenizer.vocab 词表
除了一些特殊符号外,还有我们自定义的 foo 和 bar,其余的一些词是 BPE 训练得到的:
<unk> 0
<s> 0
</s> 0
foo 0
bar 0
萧炎 -0
.. -1
_ “ -2
也是 -3
便是 -4
了一 -5
...四、如何使用 Transformers 库加载 SentencePiece 模型?
chinese_bpe.py
import os
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
from transformers import LlamaTokenizer
from sentencepiece import sentencepiece_model_pb2 as sp_pb2_model
import sentencepiece as spm
from tokenization import ChineseTokenizer
chinese_sp_model_file = "sentencepiece_tokenizer/tokenizer.model"
# 加载模型
chinese_sp_model = spm.SentencePieceProcessor()
chinese_sp_model.Load(chinese_sp_model_file)
chinese_spm = sp_pb2_model.ModelProto()
chinese_spm.ParseFromString(chinese_sp_model.serialized_model_proto())
# 保存
output_dir = './transformers_tokenizer/chinese/'
os.makedirs(output_dir, exist_ok=True)
with open(output_dir + 'chinese.model', 'wb') as f:
f.write(chinese_spm.SerializeToString())
tokenizer = ChineseTokenizer(vocab_file=output_dir + 'chinese.model')
tokenizer.save_pretrained(output_dir)
print(f"Chinese tokenizer has been saved to {output_dir}")
# 测试
chinese_tokenizer = ChineseTokenizer.from_pretrained(output_dir)
print(tokenizer.all_special_tokens)
print(tokenizer.all_special_ids)
print(tokenizer.special_tokens_map)
text = '''白日依山尽,黄河入海流。欲穷千里目,更上一层楼。
The primary use of LLaMA is research on large language models, including'''
print("Test text:\n", text)
print(f"Tokenized by Chinese tokenizer: {chinese_tokenizer.tokenize(text)}")运行结果
Chinese tokenizer has been saved to ./transformers_tokenizer/chinese/
['<s>', '</s>', '<unk>']
[1, 2, 0]
{'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>'}
Test text:
白日依山尽,黄河入海流。欲穷千里目,更上一层楼。
The primary use of LLaMA is research on large language models, including
Tokenized by Chinese-LLaMA tokenizer: ['▁', '白日', '依', '山', '尽', ',', '黄', '河', '入', '海', '流', '.', '欲', '穷', '千里', '目', ',', '更', '上一层', '楼', '.', '▁', 'T', 'h', 'e', '▁', 'p', 'r', 'i', 'm', 'a', 'r', 'y', '▁', 'u', 's', 'e', '▁', 'o', 'f', '▁', 'LL', 'a', 'MA', '▁i', 's', '▁', 'r', 'e', 's', 'e', 'a', 'r', 'ch', '▁', 'o', 'n', '▁', 'l', 'a', 'r', 'g', 'e', '▁', 'l', 'an', 'g', 'u', 'a', 'g', 'e', '▁', 'm', 'o', 'd', 'e', 'l', 's', ',', '▁i', 'n', 'c', 'lu', 'd', 'i', 'ng']📝通俗解释:可以看到分词结果中,"千里"、"上一层"这样的词被作为一个整体保留了下来,而不是被拆成单个的字。这样既保留了中文的语义,又能让模型处理。
ChineseTokenizer 实现代码
ChineseTokenizer 参考了 LLaMA 模型里面的使用方法,并稍作修改:
# coding=utf-8
# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
#
# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
# and OPT implementations in this library. It has been modified from its
# original forms to accommodate minor architectural differences compared
# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tokenization classes for LLaMA."""
import os
from shutil import copyfile
from typing import Any, Dict, List, Optional, Tuple
import sentencepiece as spm
from transformers.tokenization_utils import AddedToken, PreTrainedTokenizer
from transformers.utils import logging
logger = logging.get_logger(__name__)
VOCAB_FILES_NAMES = {"vocab_file": "tokenizer.model"}
class ChineseTokenizer(PreTrainedTokenizer):
"""
Construct a Llama tokenizer. Based on byte-level Byte-Pair-Encoding.
Args:
vocab_file (`str`):
Path to the vocabulary file.
"""
vocab_files_names = VOCAB_FILES_NAMES
model_input_names = ["input_ids", "attention_mask"]
def __init__(
self,
vocab_file,
unk_token="<unk>",
bos_token="<s>",
eos_token="</s>",
pad_token=None,
sp_model_kwargs: Optional[Dict[str, Any]] = None,
add_bos_token=True,
add_eos_token=False,
clean_up_tokenization_spaces=False,
**kwargs,
):
self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs
# 将字符串 token 转换为 AddedToken 对象
bos_token = AddedToken(bos_token, lstrip=False, rstrip=False) if isinstance(bos_token, str) else bos_token
eos_token = AddedToken(eos_token, lstrip=False, rstrip=False) if isinstance(eos_token, str) else eos_token
unk_token = AddedToken(unk_token, lstrip=False, rstrip=False) if isinstance(unk_token, str) else unk_token
pad_token = AddedToken(pad_token, lstrip=False, rstrip=False) if isinstance(pad_token, str) else pad_token
super().__init__(
bos_token=bos_token,
eos_token=eos_token,
unk_token=unk_token,
pad_token=pad_token,
add_bos_token=add_bos_token,
add_eos_token=add_eos_token,
sp_model_kwargs=self.sp_model_kwargs,
clean_up_tokenization_spaces=clean_up_tokenization_spaces,
**kwargs,
)
self.vocab_file = vocab_file
self.add_bos_token = add_bos_token
self.add_eos_token = add_eos_token
self.sp_model = spm.SentencePieceProcessor(**self.sp_model_kwargs)
self.sp_model.Load(vocab_file)
def __getstate__(self):
state = self.__dict__.copy()
state["sp_model"] = None
return state
def __setstate__(self, d):
self.__dict__ = d
self.sp_model = spm.SentencePieceProcessor(**self.sp_model_kwargs)
self.sp_model.Load(self.vocab_file)
@property
def vocab_size(self):
"""Returns vocab size"""
return self.sp_model.get_piece_size()
def get_vocab(self):
"""Returns vocab as a dict"""
vocab = {self.convert_ids_to_tokens(i): i for i in range(self.vocab_size)}
vocab.update(self.added_tokens_encoder)
return vocab
def _tokenize(self, text):
"""Returns a tokenized string."""
return self.sp_model.encode(text, out_type=str)
def _convert_token_to_id(self, token):
"""Converts a token (str) in an id using the vocab."""
return self.sp_model.piece_to_id(token)
def _convert_id_to_token(self, index):
"""Converts an index (integer) in a token (str) using the vocab."""
token = self.sp_model.IdToPiece(index)
return token
def convert_tokens_to_string(self, tokens):
"""Converts a sequence of tokens (string) in a single string."""
current_sub_tokens = []
out_string = ""
prev_is_special = False
for i, token in enumerate(tokens):
# 确保特殊字符不会用 sentencepiece 模型解码
if token in self.all_special_tokens:
if not prev_is_special and i != 0:
out_string += " "
out_string += self.sp_model.decode(current_sub_tokens) + token
prev_is_special = True
current_sub_tokens = []
else:
current_sub_tokens.append(token)
prev_is_special = False
out_string += self.sp_model.decode(current_sub_tokens)
return out_string
def save_vocabulary(self, save_directory, filename_prefix: Optional[str] = None) -> Tuple[str]:
"""
Save the vocabulary and special tokens file to a directory.
Args:
save_directory (`str`):
The directory in which to save the vocabulary.
Returns:
`Tuple(str)`: Paths to the files saved.
"""
if not os.path.isdir(save_directory):
logger.error(f"Vocabulary path ({save_directory}) should be a directory")
return
out_vocab_file = os.path.join(
save_directory,
(filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]
)
if os.path.abspath(self.vocab_file) != os.path.abspath(out_vocab_file) and os.path.isfile(self.vocab_file):
copyfile(self.vocab_file, out_vocab_file)
elif not os.path.isfile(self.vocab_file):
with open(out_vocab_file, "wb") as fi:
content_spiece_model = self.sp_model.serialized_model_proto()
fi.write(content_spiece_model)
return (out_vocab_file,)
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
bos_token_id = [self.bos_token_id] if self.add_bos_token else []
eos_token_id = [self.eos_token_id] if self.add_eos_token else []
output = bos_token_id + token_ids_0 + eos_token_id
if token_ids_1 is not None:
output = output + bos_token_id + token_ids_1 + eos_token_id
return output
def get_special_tokens_mask(
self,
token_ids_0: List[int],
token_ids_1: Optional[List[int]] = None,
already_has_special_tokens: bool = False
) -> List[int]:
"""
Retrieve sequence ids from a token list that has no special tokens added. This method is
called when adding special tokens using the tokenizer `prepare_for_model` method.
Args:
token_ids_0 (`List[int]`):
List of IDs.
token_ids_1 (`List[int]`, *optional*):
Optional second list of IDs for sequence pairs.
already_has_special_tokens (`bool`):
Whether or not the token list is already formatted with special tokens for the model.
Returns:
`List[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.
"""
if already_has_special_tokens:
return super().get_special_tokens_mask(
token_ids_0=token_ids_0,
token_ids_1=token_ids_1,
already_has_special_tokens=True
)
bos_token_id = [1] if self.add_bos_token else []
eos_token_id = [1] if self.add_eos_token else []
if token_ids_1 is None:
return bos_token_id + ([0] * len(token_ids_0)) + eos_token_id
return (
bos_token_id
+ ([0] * len(token_ids_0))
+ eos_token_id
+ bos_token_id
+ ([0] * len(token_ids_1))
+ eos_token_id
)
def create_token_type_ids_from_sequences(
self,
token_ids_0: List[int],
token_ids_1: Optional[List[int]] = None
) -> List[int]:
"""
Creates a mask from the two sequences passed to be used in a sequence-pair classification task.
An ALBERT sequence pair mask has the following format:
```
0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
| first sequence | second sequence |
```
If token_ids_1 is None, only returns the first portion of the mask (0s).
Args:
token_ids_0 (`List[int]`):
List of ids.
token_ids_1 (`List[int]`, *optional*):
Optional second list of IDs for sequence pairs.
Returns:
`List[int]`: List of token type IDs according to the given sequence(s).
"""
bos_token_id = [self.bos_token_id] if self.add_bos_token else []
eos_token_id = [self.eos_token_id] if self.add_eos_token else []
output = [0] * len(bos_token_id + token_ids_0 + eos_token_id)
if token_ids_1 is not None:
output += [1] * len(bos_token_id + token_ids_1 + eos_token_id)
return output📝通俗解释:这段代码实现了一个自定义的中文分词器,继承自 HuggingFace 的 PreTrainedTokenizer。它底层使用 SentencePiece 库来进行分词,同时兼容 LLaMA 的特殊 token 格式。
五、如何合并英文词表和中文词表?
chinese_llama_bpe.py
import os
os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
from transformers import LlamaTokenizer
from sentencepiece import sentencepiece_model_pb2 as sp_pb2_model
import sentencepiece as spm
llama_tokenizer_dir = "transformers_tokenizer/llama/tokenizer.model"
chinese_sp_model_file = "sentencepiece_tokenizer/tokenizer.model"
# 加载模型
llama_tokenizer = LlamaTokenizer.from_pretrained(llama_tokenizer_dir)
llama_spm = sp_pb2_model.ModelProto()
llama_spm.ParseFromString(llama_tokenizer.sp_model.serialized_model_proto())
chinese_spm = sp_pb2_model.ModelProto()
chinese_sp_model = spm.SentencePieceProcessor()
chinese_sp_model.Load(chinese_sp_model_file)
chinese_spm.ParseFromString(chinese_sp_model.serialized_model_proto())
# 打印 token 数量
print(len(llama_tokenizer), len(chinese_sp_model))
print(llama_tokenizer.all_special_tokens)
print(llama_tokenizer.all_special_ids)
print(llama_tokenizer.special_tokens_map)
# 将中文 tokens 添加到 LLaMA tokenizer
llama_spm_tokens_set = set(p.piece for p in llama_spm.pieces)
print(f"Before: {len(llama_spm_tokens_set)}")
for p in chinese_spm.pieces:
piece = p.piece
if piece not in llama_spm_tokens_set:
new_p = sp_pb2_model.ModelProto().SentencePiece()
new_p.piece = piece
new_p.score = 0
llama_spm.pieces.append(new_p)
print(f"New model pieces: {len(llama_spm.pieces)}")
# 保存
output_sp_dir = 'transformers_tokenizer/llama_chinese'
output_hf_dir = 'transformers_tokenizer/llama_chinese'
os.makedirs(output_sp_dir, exist_ok=True)
with open(output_sp_dir + '/chinese_llama.model', 'wb') as f:
f.write(llama_spm.SerializeToString())
tokenizer = LlamaTokenizer(vocab_file=output_sp_dir + '/chinese_llama.model')
tokenizer.save_pretrained(output_hf_dir)
print(f"Chinese-LLaMA tokenizer has been saved to {output_hf_dir}")
# 测试
llama_tokenizer = LlamaTokenizer.from_pretrained(llama_tokenizer_dir)
chinese_llama_tokenizer = LlamaTokenizer.from_pretrained(output_hf_dir)
print(tokenizer.all_special_tokens)
print(tokenizer.all_special_ids)
print(tokenizer.special_tokens_map)
text = '''白日依山尽,黄河入海流。欲穷千里目,更上一层楼。
The primary use of LLaMA is research on large language models, including'''
print("Test text:\n", text)
print(f"Tokenized by LLaMA tokenizer: {llama_tokenizer.tokenize(text)}")
print(f"Tokenized by Chinese-LLaMA tokenizer: {chinese_llama_tokenizer.tokenize(text)}")核心代码解析
以下代码的作用是将原始词表中没有的新词加入词表:
for p in chinese_spm.pieces:
piece = p.piece
if piece not in llama_spm_tokens_set:
new_p = sp_pb2_model.ModelProto().SentencePiece()
new_p.piece = piece
new_p.score = 0
llama_spm.pieces.append(new_p)📝通俗解释:这就像把中文词典里的新词添加到英文字典里。如果这个词已经在英文词典里了,就不需要重复添加;如果没有,就把它加进去。这样两个语言就能共享一个大的词表了。
运行结果
32000 50000
['<s>', '</s>', '<unk>']
[1, 2, 0]
{'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>'}
Before: 32000
New model pieces: 81163
Chinese-LLaMA tokenizer has been saved to transformers_tokenizer/llama_chinese
['<s>', '</s>', '<unk>']
[1, 2, 0]
{'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>'}
Test text:
白日依山尽,黄河入海流。欲穷千里目,更上一层楼。
The primary use of LLaMA is research on large language models, including
Tokenized by LLaMA tokenizer: ['', '白', '日', '<0xE4>', '<0xBE>', '<0x9D>', '山', '<0xE5>', '<0xB0>', '<0xBD>', ',', '黄', '河', '入', '海', '流', '。', '<0xE6>', '<0xAC>', '<0xB2>', '<0xE7>', '<0xA9>', '<0xB7>', '千', '里', '目', ',', '更', '上', '一', '<0xE5>', '<0xB1>', '<0x82>', '<0xE6>', '<0xA5>', '<0xBC>', '。', '<0x0A>', 'The', '__primary', '__use', '__of', '__L', 'La', 'MA', '__is', '__research', '__on', '__large', '__language', '__models', ',', '__including']
Tokenized by Chinese-LLaMA tokenizer: ['▁白', '日', '依', '山', '尽', ',', '黄', '河', '入', '海', '流', '。', '欲', '穷', '千里', '目', ',', '更', '上一层', '楼', '。', '<0x0A>', 'The', '__primary', '__use', '__of', '__L', 'La', 'MA', '__is', '__research', '__on', '__large', '__language', '__models', ',', '__including']📝通俗解释:对比两个分词结果可以发现:
- 原始 LLaMA 分词器把中文拆成了一个个字节(
<0xE4>这种是 UTF-8 编码的字节),每个汉字被拆成3个字节- 合并后的分词器能正确识别"千里"、"上一层"这样的完整词语,分词效果更好
六、怎么使用修改后的词表?
场景一:重新从头开始训练
如果重新从头开始训练,使用起来很简单:
config = AutoConfig.from_pretrained(...)
tokenizer = LlamaTokenizer.from_pretrained(...)
model = LlamaForCausalLM.from_pretrained(..., config=config)
# 调整词表大小以匹配分词器
model_vocab_size = model.get_output_embeddings().weight.size(0)
model.resize_token_embeddings(len(tokenizer))📝通俗解释:这就相当于给模型换了一个更大的"词汇本",需要让模型的词嵌入层也相应变大。
场景二:保留原始模型 embedding 参数
如果想要保留原始模型 embedding 的参数,可以按照以下步骤操作:
- 找到新词表和旧词表 ID 之间的映射关系
- 将模型里面新词表里面包含的旧词表用原始模型的 embedding 替换
- 如果新词在旧词表里面没有出现,就进行相应的初始化
例如 Transformers 库中的 LLaMA 是这么进行初始化的:
def _init_weights(self, module):
std = self.config.initializer_range
if isinstance(module, nn.Linear):
module.weight.data.normal_(mean=0.0, std=std)
if module.bias is not None:
module.bias.data.zero_()
elif isinstance(module, nn.Embedding):
module.weight.data.normal_(mean=0.0, std=std)
if module.padding_idx is not None:
module.weight.data[module.padding_idx].zero_()📝通俗解释:这个初始化方法确保新添加的词向量有合理的初始值。对于新词,我们用随机初始化(正态分布);对于已有的词,我们直接用原来的参数,这样可以保留模型学到的知识。
📝更详细的实现可以参考:https://github.com/yangjianxin1/LLMPruner
总结
到这里为止,我们已经学会了:
使用 SentencePiece 训练一个中文的词表
📝通俗解释:准备一本中文字典
使用 Transformers 加载 SentencePiece 模型
📝通俗解释:让程序能读懂这本字典
合并中英文的词表,并使用 Transformers 使用合并后的词表
📝通俗解释:把中英文词典合并成一本大词典
在模型中怎么使用新词表
📝通俗解释:让模型学会用这本新词典
通过以上步骤,我们就可以让原本只支持英文的 LLaMA 模型支持中文了!