BPE算法详解:从数据预处理到合并操作的完整分词流程解析
BPE 是一种基于统计的无监督分词算法,在自然语言处理任务中经常被使用,像机器翻译、文本生成等领域都有它的身影。BPE 算法通过逐步把原始文本拆分成子词或者字符,以此来达到分词的目的。

以下是BPE分词算法的详细说明:
数据预处理方面,BPE 算法首先对输入的训练语料展开预处理工作。它把每个词都按照字符的方式切分成序列,并且为这些序列加上特殊的符号,其中包括开始符号和结束符号。
构建词表:BPE 算法会对训练语料中字符或子词的频率进行统计,以此来构建词表。在初始阶段,会把训练语料里的字符或子词当作词表中的初始词汇。
计算频率:对训练语料中的字符或子词进行出现频率的统计,然后按照频率的高低进行排序。
合并操作:选择出现频率最高的一对相邻的字符或者子词,将它们合并成一个新的字符或者子词,同时更新词表以及频率统计。
进行合并操作,一直进行直到达到预设的合并次数或者无法再进行合并。
使用最终的词表来对输入文本进行分词操作。在分词过程中,要优先去匹配较长的子词,要是无法继续进行匹配了,就接着去匹配较短的子词。
去除分词结果中的特殊符号,把字符或子词连接起来,使其恢复为原始的文本形式。
BPE 分词算法的一个优点在于能够自动构建词表。它还能够处理未登录词(Out-of-Vocabulary,OOV)的相关问题。此算法能够灵活地对复杂词组进行识别和生成。并且它适用于不同领域以及不同语种的文本处理任务。
以下是一个使用Python实现BPE分词算法的示例代码:
从集合模块中导入 defaultdict 类。
数据和合并次数被用于学习字节对编码(BPE)。首先获取数据,然后根据给定的合并次数进行相关操作以学习 BPE。
# 初始化词表,将每个字符作为初始词汇
vocab = defaultdict(int)
for word in data:
for char in word:
vocab[char] += 1
# 进行合并操作
merges = []
for _ in range(num_merges):
# 统计词频
pairs = defaultdict(int)
for word in data:
symbols = word.split()
对于 i 而言,i 在 symbols 的长度减 1 的范围内进行遍历。
在 pairs 中,symbols[i] 与 symbols[i+1] 对应的键值对的数量加 1 。
# 找到最频繁的一对相邻字符或子词
在 pairs 中,通过 key=pairs.get 找到最大值,这个最大值被赋值给 best 。
merges.append(best)
# 更新词表
new_vocab = defaultdict(int)
for word in data:
# 合并最频繁的一对相邻字符或子词
new_word 会将 word 中由空格连接的 best 所表示的内容替换为空字符串,即将这些内容去除。具体来说,就是用空字符串替换掉 word 中由空格连接的 best 所对应的部分。
new_vocab[new_word] += 1
vocab = new_vocab
return merges, vocab
对文本进行分割操作,需要给定文本和合并规则。将文本按照合并规则进行处理,以实现文本的分割。通过特定的方式依据合并规则对文本进行操作,从而达到分割文本的目的。
# 恢复分词结果
segments = []
for word in text.split():
for merge in merges:
if merge in word:
word 进行了替换操作,将 merge 替换为了通过空格连接起来的 merge ,即 ' '.join(merge) 。
segments.extend(word.split())
return segments
# 示例使用
data 中有“specials”这个元素。
learn_bpe 函数对数据进行处理后,得到了 merges 和 vocab 两个结果。
print("Merges:", merges)
打印出“词汇:”,然后打印出字典(vocab)的内容。
text = "lowest specials"
通过 segment_text 函数对文本进行处理,并使用 merges 来生成 segments 。
print("Segments:", segments)
c++实现:
#include
#include
#include
#include
std::unordered_map learn_bpe(const std::vector& data, int num_merges) {
std::unordered_map vocab;
对于 data 中的每一个 std::string 类型的 word 而言:
for (char c : word) {
std::string charStr(1, c);
vocab[charStr]++;
}
}
std::unordered_map, int> pairs;
for (const std::string& word : data) {
std::vector symbols;
size_t len = word.length();
for (size_t i = 0; i < len - 1; ++i) {
std::string sym = word.substr(i, 2);
pairs[std::make_pair(sym.substr(0, 1), sym.substr(1, 1))]++;
}
}
std::vector> merges;
for (int i = 0; i < num_merges; ++i) {
auto best = std::max_element(pairs.begin(), pairs.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
std::pair merge = best->first;
merges.push_back(merge);
std::unordered_map new_vocab;
对于 data 中的每一个 std::string 类型的 word 来说:
std::string new_word = word;
size_t index = 0;
当 ( (index = new_word.find(merge.first + merge.second, index)) 不等于 std::string::npos 时 {
new_word 把索引 index 处的 2 个字符替换为 merge.first 与 merge.second 拼接后的内容。
index 的值增加 merge.first 的长度。
}
new_vocab[new_word]++;
}
vocab = new_vocab;
pairs.erase(best);
}
return vocab;
}
std::vector使用给定的字符串 text 和向量 vector 来进行 segment_text 操作。通过这个操作,可以对 text 进行特定的处理或分割等相关操作。具体的处理方式取决于 segment_text 函数的具体实现和设计。>& merges) {
std::vector segments;
std::string word = text;
size_t len = merges.size();
for (size_t i = 0; i < len; ++i) {
const auto& merge = merges[i];
size_t index = 0;
while ((index = word.find(merge.first + merge.second, index)) != std::string::npos) {
word.replace(index, 2, merge.first + " " + merge.second);
index += merge.first.length() + 1;
}
}
size_t startIndex = 0;
size_t endIndex = word.find(' ');
while (endIndex != std::string::npos) {
segments.push_back(word.substr(startIndex, endIndex - startIndex));
startIndex = endIndex + 1;
endIndex = word.find(' ', startIndex);
}
segments.push_back(word.substr(startIndex));
return segments;
}
int main() {
std::vectordata 包含“low”“lower”“newest”“widest”“special”“specials”这些元素。
int num_merges = 5;
std::unordered_map使用数据来学习字节对编码(BPE),得到的结果赋值给 vocab,其中进行的合并操作次数为 num_merges。
std::cout << "Vocabulary:" << std::endl;
for (const auto& entry : vocab) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
std::string text = "lowest specials";
std::vector> merges;
for (int i = 0; i < num_merges; ++i) {
merges.push_back(std::make_pair("", ""));
}
std::vector使用 merges 对 text 进行分割操作后得到 segments 。
std::cout << "Segments:" << std::endl;
for (const std::string& segment : segments) {
std::cout << segment << std::endl;
}
return 0;
}