NLP中常见的分词算法(BPE、WordPiece、Unigram、SentencePiece)
文章目录
- 一、基本概念
- 二、传统分词方法
- 2.1 古典分词方法
- 2.2 拆分为单个字符
- 三、基于子词的分词方法(Subword Tokenization)
- 3.1 主要思想
- 3.2 主流的 Subword 算法
- 3.3 Subword 与 传统分词方法的比较
- 四、Byte Pair Encoding (BPE)
- 4.1 主要思想
- 4.2 算法过程
- 4.3 完整例子
- 4.4 BPE 的优缺点
- 4.5 BPE 的适用范围
- 4.6 BPE 的实现
- 4.7 编码与解码
- 4.8 调包使用 BPE
- 五、BBPE (Byte-level BPE)
- 5.1 基础知识
- 5.2 基本思想
- 5.3 代码实现
- 六、WordPiece
- 6.1 基本思想
- 6.2 优势与劣势
- 七、Unigram Language Model (ULM)
- 7.1 基本思想
- 7.2 优势与劣势
- 八、SentencePiece
- 8.1 基本思想
- 8.2 使用说明
- 九、LLM中的分词器
- 9.1 BERT的分词器
- 9.2 LLaMA的分词器
- 9.3 GLM的分词器
- 参考资料
一、基本概念
机器无法直接理解自然语言的文本,我们需要进行文本预处理 ,而最重要的一步就是分词(Tokenize) 。
其中,执行分词的算法模型称为 分词器(Tokenizer) ,划分好的一个个词称为 Token(词元) ,这个过程称为 Tokenization(分词)。
分词的目的是将输入文本分成一个个token(词元),保证各个token(词元)拥有相对完整和独立的语义,以供后续任务(比如学习embedding或者作为高级模型的输入)使用。
由于一篇文本的词往往太多了,为了方便算法模型训练,我们会选取出频率 (也可能是其它的权重)最高的若干个词组成一个词表(Vocabulary) 。
二、传统分词方法
2.1 古典分词方法
分词,顾名思义,就是把一句话分词一个个词,这还不简单?直接把词与词直接加一个空格不就行了?那如果真这么简单我们也不用讨论了,还有什么办法呢,再想一想?或许还能按标点符号分词 ,或者按语法规则分词 。
上面提到的这些方法,统称为古典分词方法 ,区别不是很大。
古典分词方法的缺点
可见,一个句子,使用不同的规则,将有许多种不同的分词结果。古典分词方法的缺点非常明显:
- 对于 未在词表中出现的词(Out Of Vocabulary, OOV ),模型将无法处理(未知符号标记为 [UNK])。
- 词表中的低频词/稀疏词在模型训无法得到训练(因为词表大小有限,太大的话会影响效率)。
- 很多语言难以用空格进行分词,例如英语单词的多形态,
look
衍生出的looks, looking, looked
,其实都是一个意思,但是在词表中却被当作不同的词处理,模型也无法通过old, older, oldest
之间的关系学到smart, smarter, smartest
之间的关系。这一方面增加了训练冗余,另一方面也造成了大词汇量问题。
2.2 拆分为单个字符
这种方法称为 Character embedding,是一种更为极端的分词方法,直接把一个词分成一个一个的字母和特殊符号。虽然能解决 OOV 问题,也避免了大词汇量问题,但缺点也太明显了,粒度太细,训练花费的成本太高。而且单个字符无法承载丰富的语义。
三、基于子词的分词方法(Subword Tokenization)
如何结合word和char粒度各自的优势呢?基于子词的分词方法(Subword Tokenization) 应运而生,顾名思义,粒度介于word和char之间,基本思想为常用词应该保持原状,生僻词应该拆分成子词以共享token压缩空间,所以可以较好的平衡词表大小与语义表达能力,比如OOV问题可以通过subword的组合来解决。
总结一下,文本的分词粒度:
- word:
优点:词的边界和含义得到保留;
缺点:1)词表大,稀有词学不好;2)OOV;3)无法处理单词形态关系和词缀关系; - char:
优点:词表极小,比如26个英文字母几乎可以组合出所有词,5000多个中文常用字基本也能组合出足够的词汇;
缺点:1)无法承载丰富的语义;2)序列长度大幅增长; - subword:可以较好的平衡词表大小与语义表达能力;
3.1 主要思想
基于子词的分词方法的目的是:通过一个有限的词表 来解决所有单词的分词问题,同时尽可能将结果中 token 的数目降到最低。
例如,可以用更小的词片段来组成更大的词,例如:
"unfortunately" = "un" + "for" + "tun" + "ate" + "ly"
可以看到,有点类似英语中的词根词缀拼词法,其中的这些小片段又可以用来构造其他词。可见这样做,既可以降低词表的大小,同时对相近词也能更好地处理。
3.2 主流的 Subword 算法
目前几种主流的 Subword 算法,包括:
- BPE:即字节对编码。其核心思想是从字母开始,不断找词频最高、且连续的两个token合并,直到达到目标词数。
- BBPE:BBPE核心思想将BPE的从字符级别扩展到子节(Byte)级别。BPE的一个问题是如果遇到了unicode编码,基本字符集可能会很大。BBPE就是以一个字节为一种“字符”,不管实际字符集用了几个字节来表示一个字符。这样的话,基础字符集的大小就锁定在了256(2^8)。采用BBPE的好处是可以跨语言共用词表,显著压缩词表的大小。而坏处就是,对于类似中文这样的语言,一段文字的序列长度会显著增长。因此,BBPE based模型可能比BPE based模型表现的更好。然而,BBPE sequence比起BPE来说略长,这也导致了更长的训练/推理时间。BBPE其实与BPE在实现上并无大的不同,只不过基础词表使用256的字节集。
- WordPiece:WordPiece算法可以看作是BPE的变种。不同的是,WordPiece基于概率生成新的subword而不是下一最高频字节对。WordPiece算法也是每次从词表中选出两个子词合并成新的子词。BPE选择频数最高的相邻子词合并,而WordPiece选择使得语言模型概率最大的相邻子词加入词表。
- Unigram:它和 BPE 以及 WordPiece 从表面上看一个大的不同是,前两者都是初始化一个小词表,然后一个个增加到限定的词汇量,而 Unigram Language Model 却是先初始一个大词表,接着通过语言模型评估不断减少词表,直到限定词汇量。
- SentencePiece:SentencePiece它是谷歌推出的子词开源工具包,它是把一个句子看作一个整体,再拆成片段,而没有保留天然的词语的概念。一般地,它把空格也当作一种特殊字符来处理,再用BPE或者Unigram算法来构造词汇表。SentencePiece除了集成了BPE、ULM子词算法之外,SentencePiece还能支持字符和词级别的分词。
下图是一些主流模型使用的分词算法:
从上表可以看出:
- GPT-1 使用的 BPE 实现分词
- LLaMA / BLOOM / GPT2 / ChatGLM 使用 BBPE 实现分词
- BERT / DistilBERT / Electra 使用 WordPiece 进行分词
- XLNet 则采用了 SentencePiece 进行分词
3.3 Subword 与 传统分词方法的比较
Subword 与 传统分词方法的区别主要在于:
- 传统词表示方法无法很好的处理未知或罕见的词汇(OOV 问题)。
- 传统词 tokenization 方法不利于模型学习词缀之间的关系,例如模型学到的“old”, “older”, and “oldest”之间的关系无法泛化到“smart”, “smarter”, and “smartest”。
- Character embedding 作为 OOV 的解决方法粒度太细。
- Subword 粒度在词与字符之间,能够较好的平衡 OOV 问题。
四、Byte Pair Encoding (BPE)
【参考资料】:BPE 算法原理及使用指南【深入浅出】
4.1 主要思想
字节对编码(BPE, Byte Pair Encoder),是一种数据压缩 算法,用来在固定大小的词表中实现可变⻓度的子词。该算法简单有效,因而目前它是最流行的方法。
BPE 首先将词分成单个字符,然后依次用另一个字符替换频率最高的一对字符 ,直到循环次数结束。
4.2 算法过程
接下来详细介绍 BPE 在分词中的算法过程:
- 准备语料库,确定期望的 subword 词表大小等参数。
- 通常在每个单词末尾添加后缀
</w>
,统计每个单词出现的频率,例如,low
的频率为 5,那么我们将其改写为"low</ w>": 5
注:停止符
</w>
的意义在于标明 subword 是词后缀。举例来说:st 不加 可以出现在词首,如star
;加了</w>
表明该子词位于词尾,如west</w>
,二者意义截然不同。
- 将语料库中所有单词拆分为单个字符,用所有单个字符建立最初的词典,并统计每个字符的频率,本阶段的 subword 的粒度是字符。
- 挑出频次最高的符号对 ,比如说
t
和h
组成的th
,将新字符加入词表,然后将语料中所有该字符对融合(merge),即所有t
和h
都变为th
。
注:新字符依然可以参与后续的 merge,有点类似哈夫曼树,BPE 实际上就是一种贪心算法 。
- 重复遍历 2 和 3 操作,直到词表中单词数达到设定量 或下一个最高频数为 1 ,如果已经打到设定量,其余的词汇直接丢弃
注:看似我们要维护两张表,一个词表,一个字符表,实际上只有一张,词表只是为了我们方便理解。
4.3 完整例子
我们举一个完整的例子,来直观地看一下这个过程:
- 假设有语料集经过统计后表示为:
{'low':5, 'lower':2, 'newest':6, 'widest':3}
其中数字代表的是对应单词在语料中的频数。
-
拆分单词成最小单元,加后缀。初始化词表,这里词表的最小单元为字符。
-
在语料上统计相邻单元的频数。这里,最高频连续子词对"e"和"s"出现了6+3=9次,将其合并成"es",有:
由于语料中不存在’s’子词了,因此将其从词表中删除。同时加入新的子词’es’。一增一减,词表大小保持不变。
- 继续统计相邻子词的频数。此时,最高频连续子词对"es"和"t"出现了6+3=9次, 将其合并成"est",有:
- 接着,最高频连续子词对为"est"和"",有:
6. 继续上述迭代直到达到预设的Subword词表大小或下一个最高频的字节对出现频率为1。
这样我们就得到了更加合适的词表,这个词表可能会出现一些不是单词的组合,但是其本身有意义的一种形式。
4.4 BPE 的优缺点
(1)优点
上面例子中的语料库很小,知识为了方便我们理解 BPE 的过程,但实际中语料库往往非常非常大,无法给每个词(token)都放在词表中。
BPE 的优点就在于:可以很有效地平衡词典大小和编码步骤数(将语料编码所需要的 token 数量)。
随着合并的次数增加,词表大小通常先增加后减小。迭代次数太小,大部分还是字母,没什么意义;迭代次数多,又重新变回了原来那几个词。所以词表大小要取一个中间值。
(2)缺点
-
对于同一个句子, 例如 Hello world,如图所示,可能会有不同的 Subword 序列。不同的 Subword 序列会产生完全不同的 id 序列表示,这种歧义可能在解码阶段无法解决。在翻译任务中,不同的 id 序列可能翻译出不同的句子,这显然是错误的。
-
在训练任务中,如果能对不同的 Subword 进行训练的话,将增加模型的健壮性,能够容忍更多的噪声,而 BPE 的贪心算法无法对随机分布进行学习。
4.5 BPE 的适用范围
BPE 一般适用在欧美语言拉丁语系中,因为欧美语言大多是字符形式,涉及前缀、后缀的单词比较多。而中文的汉字一般不用 BPE 进行编码,因为中文是字无法进行拆分。对中文的处理通常只有分词和分字两种。理论上分词效果更好,更好的区别语义。分字效率高、简洁,因为常用的字不过 3000 字,词表更加简短。
4.6 BPE 的实现
实现代码如下:
import re,collectionsdef get_vocab(filename):vocab = collections.defaultdict(int)with open(filename, 'r', encoding='utf-8') as fhand:for line in fhand:words = line.strip().split()for word in words:vocab[' '.join(list(word)) + ' </w>'] += 1return vocabdef get_stats(vocab):pairs = collections.defaultdict(int)for word, freq in vocab.items():symbols = word.split()for i in range(len(symbols)-1):pairs[symbols[i],symbols[i+1]] += freqreturn pairsdef merge_vocab(pair, v_in):v_out = {}bigram = re.escape(' '.join(pair))p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')for word in v_in:w_out = p.sub(''.join(pair), word)v_out[w_out] = v_in[word]return v_outdef get_tokens(vocab):tokens = collections.defaultdict(int)for word, freq in vocab.items():word_tokens = word.split()for token in word_tokens:tokens[token] += freqreturn tokens
跑一个例子试一下,这里已经对原句子进行了预处理:
if __name__ == "__main__":vocab = {'l o w </w>': 5, 'l o w e r </w>': 2, 'n e w e s t </w>': 6, 'w i d e s t </w>': 3}print('==========')print('Tokens Before BPE')tokens = get_tokens(vocab)print('Tokens: {}'.format(tokens))print('Number of tokens: {}'.format(len(tokens)))print('==========')num_merges = 5for i in range(num_merges):pairs = get_stats(vocab)if not pairs:breakbest = max(pairs, key=pairs.get)vocab = merge_vocab(best, vocab)print('Iter: {}'.format(i))print('Best pair: {}'.format(best))tokens = get_tokens(vocab)print('Tokens: {}'.format(tokens))print('Number of tokens: {}'.format(len(tokens)))
结果:
==========
Tokens Before BPE
Tokens: defaultdict(<class 'int'>, {'l': 7, 'o': 7, 'w': 16, '</w>': 16, 'e': 17, 'r': 2, 'n': 6, 's': 9, 't': 9, 'i': 3, 'd': 3})
Number of tokens: 11
==========
Iter: 0
Best pair: ('e', 's')
Tokens: defaultdict(<class 'int'>, {'l': 7, 'o': 7, 'w': 16, '</w>': 16, 'e': 8, 'r': 2, 'n': 6, 'es': 9, 't': 9, 'i': 3, 'd': 3})
Number of tokens: 11
==========
Iter: 1
Best pair: ('es', 't')
Tokens: defaultdict(<class 'int'>, {'l': 7, 'o': 7, 'w': 16, '</w>': 16, 'e': 8, 'r': 2, 'n': 6, 'est': 9, 'i': 3, 'd': 3})
Number of tokens: 10
==========
Iter: 2
Best pair: ('est', '</w>')
Tokens: defaultdict(<class 'int'>, {'l': 7, 'o': 7, 'w': 16, '</w>': 7, 'e': 8, 'r': 2, 'n': 6, 'est</w>': 9, 'i': 3, 'd': 3})
Number of tokens: 10
==========
Iter: 3
Best pair: ('l', 'o')
Tokens: defaultdict(<class 'int'>, {'lo': 7, 'w': 16, '</w>': 7, 'e': 8, 'r': 2, 'n': 6, 'est</w>': 9, 'i': 3, 'd': 3})
Number of tokens: 9
==========
Iter: 4
Best pair: ('lo', 'w')
Tokens: defaultdict(<class 'int'>, {'low': 7, '</w>': 7, 'e': 8, 'r': 2, 'n': 6, 'w': 9, 'est</w>': 9, 'i': 3, 'd': 3})
Number of tokens: 9
==========
4.7 编码与解码
上面的过程称为编码。解码过程比较简单,如果相邻子词间没有中止符,则将两子词直接拼接,否则两子词之间添加分隔符。 如果仍然有子字符串没被替换但所有 token 都已迭代完毕,则将剩余的子词替换为特殊 token,如 。例如:
# 编码序列
["the</w>", "high", "est</w>", "moun", "tain</w>"]
# 解码序列
"the</w> highest</w> mountain</w>"
4.8 调包使用 BPE
BPE 可以直接用最经典的 subword-nmt 包,不需要自己实现。
五、BBPE (Byte-level BPE)
5.1 基础知识
(1)Unicode
Unicode 是一种字符集,旨在涵盖地球上几乎所有的书写系统和字符。它为每个字符分配了一个唯一的代码点(code point)用于标识字符。Unicode 不关注字符在计算机内部的具体表示方式,而只是提供了一种字符到代码点的映射。Unicode 的出现解决了字符集的碎片化问题,使得不同的语言和字符能够在一个共同的标准下共存。然而,Unicode 并没有规定如何在计算机内存中存储和传输这些字符。
(2)UTF-8
UTF-8(Unicode Transformation Format-8)是一种变长的字符编码方案,它将 Unicode 中的代码点转换为字节序列。UTF-8 的一个重要特点是它是向后兼容 ASCII 的,这意味着标准的 ASCII 字符在 UTF-8 中使用相同的字节表示,从而确保现有的 ASCII 文本可以无缝地与 UTF-8 共存。在 UTF-8 编码中,字符的表示长度可以是1到4个字节,不同范围的 Unicode 代码点使用不同长度的字节序列表示,这样可以高效地表示整个 Unicode 字符集。UTF-8 的编码规则是:
- 单字节字符(ASCII 范围内的字符)使用一个字节表示,保持与 ASCII 编码的兼容性。
- 带有更高代码点的字符使用多个字节表示。UTF-8 使用特定的字节序列来指示一个字符所需的字节数,以及字符的实际数据。
例如,英文字母 “A” 的 Unicode 代码点是U+0041,在 UTF-8 中表示为 0x41(与 ASCII 编码相同);而中文汉字 “你” 的 Unicode 代码点是U+4F60,在 UTF-8 中表示为0xE4 0xBD 0xA0三个字节的序列。
所以简单的来说:
- Unicode 是字符集,为每个字符分配唯一的代码点。
- UTF-8 是一种基于 Unicode 的字符编码方式,用于在计算机中存储和传输字符。
(3)Byte(字节)
计算机存储和数据处理时,字节是最小的单位。一个字节包含8个(Bit)二进制位,每个位可以是0或1,每位的不同排列和组合可以表示不同的数据,所以一个字节能表示的范围是256个。
5.2 基本思想
Byte-level BPE(BBPE) 和 Byte-Pair Encoding (BPE) 区别就是:
- BPE是最小词汇是字符级别
- 而BBPE是字节级别的,通过UTF-8的编码方式这一个字节的256的范围,理论上可以表示这个世界上的所有字符。
所以实现的步骤和BPE就是实现的粒度不一样,其他的都是一样的。具体步骤为:
- 初始词表:构建初始词表,包含一个字节的所有表示(256)。
- 构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。
- 合并频率最高的子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。
- 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
- 分词:使用最终得到的词汇表对文本进行分词。
5.3 代码实现
from collections import defaultdict# 构建频率统计
def build_stats(sentences):stats = defaultdict(int)for sentence in sentences:symbols = sentence.split()for symbol in symbols:stats[symbol.encode("utf-8")] += 1return statsdef compute_pair_freqs(splits):pair_freqs = defaultdict(int)for word, freq in stats.items():split = splits[word]if len(split) == 1:continuefor i in range(len(split) - 1):pair = (split[i], split[i + 1])pair_freqs[pair] += freqreturn pair_freqsdef merge_pair(pair, splits):merged_byte = bytes(pair)for word in stats:split = splits[word]if len(split) == 1:continuei = 0while i < len(split) - 1:if split[i:i+2] == pair: # 检查分割中是否有这对字节split = split[:i] + [merged_byte] + split[i + 2 :]else:i += 1splits[word] = splitreturn splitsif __name__ == "__main__":sentences = ["我","喜欢","吃","苹果","他","不","喜欢","吃","苹果派","I like to eat apples","She has a cute cat","you are very cute","give you a hug",]# 构建初始词汇表,包含一个字节的256个表示initial_vocab = [bytes([byte]) for byte in range(256)]vocab = initial_vocab.copy()print("initial_vocab:", initial_vocab)stats = build_stats(sentences)splits = {word: [byte for byte in word] for word in stats.keys()}pair_freqs = compute_pair_freqs(splits)vocab_size = 50while len(vocab) < vocab_size:pair_freqs = compute_pair_freqs(splits)best_pair = ()max_freq = Nonefor pair, freq in pair_freqs.items():if max_freq is None or max_freq < freq:best_pair = pairmax_freq = freqsplits = merge_pair(best_pair, splits)merged_byte = bytes(best_pair)print("vocab:", vocab)
解释一下为什么Byte-level BPE(BBPE)不会出现OOV问题,初始的词表里有256个表示如下:
initial_vocab: [b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\t', b'\n', b'\x0b', b'\x0c', b'\r', b'\x0e', b'\x0f', b'\x10', b'\x11', b'\x12', b'\x13', b'\x14', b'\x15', b'\x16', b'\x17', b'\x18', b'\x19', b'\x1a', b'\x1b', b'\x1c', b'\x1d', b'\x1e', b'\x1f', b' ', b'!', b'"', b'#', b'$', b'%', b'&', b"'", b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|', b'}', b'~', b'\x7f', b'\x80', b'\x81', b'\x82', b'\x83', b'\x84', b'\x85', b'\x86', b'\x87', b'\x88', b'\x89', b'\x8a', b'\x8b', b'\x8c', b'\x8d', b'\x8e', b'\x8f', b'\x90', b'\x91', b'\x92', b'\x93', b'\x94', b'\x95', b'\x96', b'\x97', b'\x98', b'\x99', b'\x9a', b'\x9b', b'\x9c', b'\x9d', b'\x9e', b'\x9f', b'\xa0', b'\xa1', b'\xa2', b'\xa3', b'\xa4', b'\xa5', b'\xa6', b'\xa7', b'\xa8', b'\xa9', b'\xaa', b'\xab', b'\xac', b'\xad', b'\xae', b'\xaf', b'\xb0', b'\xb1', b'\xb2', b'\xb3', b'\xb4', b'\xb5', b'\xb6', b'\xb7', b'\xb8', b'\xb9', b'\xba', b'\xbb', b'\xbc', b'\xbd', b'\xbe', b'\xbf', b'\xc0', b'\xc1', b'\xc2', b'\xc3', b'\xc4', b'\xc5', b'\xc6', b'\xc7', b'\xc8', b'\xc9', b'\xca', b'\xcb', b'\xcc', b'\xcd', b'\xce', b'\xcf', b'\xd0', b'\xd1', b'\xd2', b'\xd3', b'\xd4', b'\xd5', b'\xd6', b'\xd7', b'\xd8', b'\xd9', b'\xda', b'\xdb', b'\xdc', b'\xdd', b'\xde', b'\xdf', b'\xe0', b'\xe1', b'\xe2', b'\xe3', b'\xe4', b'\xe5', b'\xe6', b'\xe7', b'\xe8', b'\xe9', b'\xea', b'\xeb', b'\xec', b'\xed', b'\xee', b'\xef', b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4', b'\xf5', b'\xf6', b'\xf7', b'\xf8', b'\xf9', b'\xfa', b'\xfb', b'\xfc', b'\xfd', b'\xfe', b'\xff']
通过上述的方式其实是在一直根据训练语料循环迭代合成子词或者词,最后形成词表,比如 “苹果” 通过UTF-8进行编码后为 \xe8\x8b\xb9\xe6\x9e\x9c
,如果词表里面有,那“苹果” 就通过词表映射成了1个表示,准确来说是1个token;如果词表里没有,那就用256中的 \xe8+\x8b+\xb9+\xe6+\x9e+\x9c
来表示“苹果”这个词,那就是6个token。
在先前的各种分词方法中,如果词典里没有”苹果“这个词,也没有”苹“,”果“这样的子词的话,那就变成了[UNK]
。所以在现在的大模型中,以 Byte-level BPE(BBPE) 这种方式进行分词是不会出现OOV,但词表中如果没有word级别的词的话,一些中英文就会分词分的很细碎,比如Llama在中文上就会把一些词分成多个token其实就是UTF-8后的中文编码,对编码效率以及语义会有影响,于是出现了一些扩充Llama中文词表的工作。
六、WordPiece
6.1 基本思想
Google的Bert模型在分词的时候使用的是WordPiece算法。与BPE算法类似,WordPiece算法也是每次从词表中选出两个子词合并成新的子词。
与BPE的最大区别在于,如何选择两个子词进行合并:
- BPE选择频数最高的相邻子词合并;
- WordPiece选择能够提升语言模型概率最大的相邻子词加入词表。
看到这里,你可能不清楚WordPiece是如何选取子词的。这里,通过形式化方法,能够清楚地理解WordPiece在合并这一步是如何作出选择的。假设句子 S = ( t 1 , t 2 , . . . , t n ) S = (t_1, t_2,...,t_n) S=(t1,t2,...,tn) 由n个子词组成, t i t_i ti表示子词,且假设各个子词之间是独立存在的,则句子 S S S 的语言模型似然值等价于所有子词概率的乘积:
假设把相邻位置的 x x x 和 y y y 两个子词进行合并,合并后产生的子词记为 z z z ,此时句子 S S S 似然值的变化可表示为:
从上面的公式,很容易发现,似然值的变化就是两个子词之间的互信息。简而言之,WordPiece每次选择合并的两个子词,他们具有最大的互信息值,也就是两子词在语言模型上具有较强的关联性,它们经常在语料中以相邻方式同时出现。
6.2 优势与劣势
-
优势:可以较好的平衡词表大小和OOV问题;
-
劣势:可能会产生一些不太合理的子词或者说错误的切分;对拼写错误非常敏感;对前缀的支持不够好;
七、Unigram Language Model (ULM)
7.1 基本思想
与WordPiece一样,Unigram Language Model(ULM)同样使用语言模型来挑选子词。不同之处在于,BPE和WordPiece算法的词表大小都是从小到大变化,属于增量法。 而Unigram Language Model则是减量法,即先初始化一个大词表,根据评估准则不断丢弃词表,直到满足限定条件。 ULM算法考虑了句子的不同分词可能,因而能够输出带概率的多个子词分段。
我们接下来看看ULM是如何操作的。对于句子S, x ⃗ = ( x 1 , x 2 , . . . , x m ) \vec x = (x_1, x_2,...,x_m) x=(x1,x2,...,xm) 为句子的一个分词结果,由m个子词组成。所以,当前分词下句子S的似然值可以表示为:
对于句子S,挑选似然值最大的作为分词结果,则可以表示为:
这里 U ( x ) U(x) U(x) 包含了句子的所有分词结果。在实际应用中,词表大小有上万个,直接罗列所有可能的分词组合不具有操作性。针对这个问题,可通过维特比算法得到 x ∗ {x^*} x∗ 来解决。
那怎么求解每个子词的概率 P ( x i ) P(x_i) P(xi) 呢?ULM通过EM算法来估计。假设当前词表 V V V, 则 M M M 步最大化的对象是如下似然函数:
其中,|D|是语料库中语料数量。上述公式的一个直观理解是,将语料库中所有句子的所有分词组合形成的概率相加。
但是,初始时,词表V并不存在。因而,ULM算法采用不断迭代的方法来构造词表以及求解分词概率:
- 初始时,建立一个足够大的词表。一般,可用语料中的所有字符加上常见的子字符串初始化词表,也可以通过BPE算法初始化。
- 针对当前词表,用EM算法求解每个子词在语料上的概率。
- 对于每个子词,计算当该子词被从词表中移除时,总的loss降低了多少,记为该子词的loss。
- 将子词按照loss大小进行排序,丢弃一定比例loss最小的子词(比如20%),保留下来的子词生成新的词表。这里需要注意的是,单字符不能被丢弃,这是为了避免OOV情况。
- 重复步骤2到4,直到词表大小减少到设定范围。
可以看出,ULM会保留那些以较高频率出现在很多句子的分词结果中的子词,因为这些子词如果被丢弃,其损失会很大。
7.2 优势与劣势
-
优势:
- 使用的训练算法可以利用所有可能的分词结果,这是通过data sampling算法实现的;
- 提出一种基于语言模型的分词算法,这种语言模型可以给多种分词结果赋予概率,从而可以学到其中的噪声;
- 使用时也可以给出带概率的多个分词结果。
-
劣势:
- 效果与初始词表息息相关,初始的大词表要足够好,比如可以通过BPE来初始化;
- 略显复杂。
八、SentencePiece
8.1 基本思想
SentencePiece 是谷歌推出的子词开源工具包,其中集成了BPE、ULM子词算法。除此之外,SentencePiece还能支持字符和词级别的分词。更进一步,为了能够处理多语言问题,sentencePiece将句子视为Unicode编码序列,从而子词算法不用依赖于语言的表示。
SentencePiece 的主要特性包括:
- 多分词粒度:支持BPE、ULM子词算法,也支持char, word分词;
- 多语言:以unicode方式编码字符,将所有的输入(英文、中文等不同语言)都转化为unicode字符,解决了多语言编码方式不同的问题;
- 编解码的可逆性:之前几种分词算法对空格的处理略显粗暴,有时是无法还原的。Sentencepiece显式地将空白作为基本标记来处理,用一个元符号 “▁”( U+2581 )转义空白,这样就可以实现简单且可逆的编解码;
- 无须Pre-tokenization:Sentencepiece可以直接从raw text/setences进行训练,无须Pre-tokenization;
- 快速、轻量化。
8.2 使用说明
(1) 安装依赖包
pip install sentencepiece
(2) 训练模型和使用模型
SentencePiece分为两部分:训练模型和使用模型。训练结束后生成一个model文件和一个词典vocab文件。
spm.SentencePieceTrainer.train('--input=train.txt --model_prefix=m --vocab_size=1000 --character_coverage=0.9995 --model_type=bpe')
参数说明:
- input: 训练语料文件,可以传递以逗号分隔的文件列表。文件格式为每行一个句子。 无需运行tokenizer、normalizer或preprocessor。 默认情况下,SentencePiece 使用 Unicode NFKC 规范化输入。
- model_prefix:输出模型名称前缀。 训练完成后将生成 <model_name>.model 和 <model_name>.vocab 文件。
- vocab_size:训练后的词表大小,例如:8000、16000 或 32000
- character_coverage:模型覆盖的字符数量,对于字符集丰富的语言(如日语或中文)推荐默认值为 0.9995,对于其他字符集较小的语言推荐默认值为 1.0。
- model_type:模型类型。 可选值:unigram(默认)、bpe、char 或 word 。 使用word类型时,必须对输入句子进行pretokenized。
(3) 代码示例
以下是一个使用 SentencePiece 进行文本符号化的代码示例:
import sentencepiece as spm# 训练 SentencePiece 模型
spm.SentencePieceTrainer.train('--input=train.txt --model_prefix=m --vocab_size=1000 --character_coverage=0.9995 --model_type=bpe')# 加载训练好的模型
sp = spm.SentencePieceProcessor()
sp.load('m.model')# 文本符号化
text = "Hello, world!"
tokens = sp.encode_as_pieces(text)# 输出结果
print(tokens)
(3) 代码解读
- 训练模型:使用
SentencePieceTrainer.train
方法训练 SentencePiece 模型,指定输入文件、模型前缀和词汇表大小。 - 加载模型:使用
SentencePieceProcessor
加载训练好的模型。 - 文本符号化:使用
encode_as_pieces
方法对文本进行符号化处理。 - 输出结果:打印符号化后的结果。
九、LLM中的分词器
9.1 BERT的分词器
- 代码:https://github.com/huggingface/transformers/blob/main/src/transformers/models/bert/tokenization_bert.py
- 文档:https://huggingface.co/docs/transformers/v4.28.1/en/model_doc/bert#transformers.BertTokenizer
BERT的分词器由两个部分组成:
-
BasicTokenizer:
转成 unicode:Python3,输入为str时,可以省略这一步
_clean_text:去除各种奇怪字符
_tokenize_chinese_chars:中文按字拆开
whitespace_tokenize:空格分词
_run_strip_accents:去掉变音符号
_run_split_on_punc:标点分词
再次空格分词:whitespace_tokenize(" ".join(split_tokens)),先用空格join再按空白分词,可以去掉连续空格 -
WordpieceTokenizer:
贪心最大匹配:用双指针实现;
9.2 LLaMA的分词器
-
代码:https://github.com/huggingface/transformers/blob/main/src/transformers/models/llama/tokenization_llama.py
-
文档:https://huggingface.co/docs/transformers/v4.28.1/en/model_doc/llama#transformers.LlamaTokenizer
LLaMA的分词器基于BBPE实现;这也是LLaMA支持多语言的原因。
9.3 GLM的分词器
- 代码:
- https://github.com/THUDM/GLM
- https://github.com/THUDM/GLM/blob/main/data_utils/tokenization.py
参考资料
- BPE 算法原理及使用指南【深入浅出】
- NLP三大Subword模型详解:BPE、WordPiece、ULM
- 【OpenLLM 008】大模型基础组件之分词器-万字长文全面解读LLM中的分词算法与分词器(tokenization & tokenizers):BPE/WordPiece/ULM & beyond
- python库 - sentencepiece
相关文章:
NLP中常见的分词算法(BPE、WordPiece、Unigram、SentencePiece)
文章目录 一、基本概念二、传统分词方法2.1 古典分词方法2.2 拆分为单个字符 三、基于子词的分词方法(Subword Tokenization)3.1 主要思想3.2 主流的 Subword 算法3.3 Subword 与 传统分词方法的比较 四、Byte Pair Encoding (BPE)4.1 主要思想4.2 算法过…...
HTTP/HTTPS ②-Cookie || Session || HTTP报头
这里是Themberfue 上篇文章介绍了HTTP报头的首行信息 本篇我们将更进一步讲解HTTP报头键值对的含义~~~ ❤️❤️❤️❤️ 报头Header ✨再上一篇的学习中,我们了解了HTTP的报头主要是通过键值对的结构存储和表达信息的;我们已经了解了首行的HTTP方法和UR…...
前端 动图方案
1、vue3lottie 解析使用Bodymovin导出为json格式的Adobe After Effects动画 vue3lottie的使用神器:vue3-lottie (electron也适用)-CSDN博客 2、MP4文件 //template部分 <video class"header-bg" src"../../assets/images/screen/layout/heade…...
C#语言的字符串处理
C#语言的字符串处理 引言 在现代编程中,字符串处理是一项重要的技能,几乎在所有编程语言中都有应用。C#语言作为一种强类型的、面向对象的编程语言,提供了丰富的字符串处理功能。这使得开发人员能够方便地进行文本操作,比如字符…...
【shell编程】报错信息:bash: bad file descriptor(包含6种解决方法)
大家好,我是摇光~ 在运行 Shell 脚本时,遇到 bash: bad file descriptor 错误通常意味着脚本尝试对一个无效或不可用的文件描述符(file descriptor)执行了读写操作。 以下是一些可能导致这个问题的原因、详细案例以及相应的解决…...
vscode 配置c/c++环境 中文乱码
D:\MIscrobingDownload\mingw64\binmingw配置到环境变量中 测试一下,按winr输入cmd打开终端 gcc -v g -v安装插件 一 二 run code 因为run code 插件配置实质上是用它提供的指令进行编译执行,因此无法直接使用断点调试功能,需要对配置进行…...
leetcode 面试经典 150 题:两数之和
链接两数之和题序号1题型数组解题方法1. 哈希表,2. 暴力法难度简单熟练度✅✅✅✅✅ 题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输…...
【Unity报错】error Cs0103: The name ‘keyCode‘ does not exist in the current context
报错提示: 解决方法: KeyCode K大写...
家用万兆网络实践:紧凑型家用服务器静音化改造(二)
大家好,这篇文章我们继续分享家里网络设备的万兆升级和静音改造经验,希望对有类似需求的朋友有所帮助。 写在前面 在上一篇《家用网络升级实践:低成本实现局部万兆(一)》中,我们留下了一些待解决的问题。…...
“AI智能实训系统:让学习更高效、更轻松!
大家好,作为一名资深产品经理,今天我来跟大家聊聊一款备受瞩目的产品——AI智能实训系统。在这个人工智能技术飞速发展的时代,AI智能实训系统应运而生,为广大学习者提供了全新的学习体验。那么,这款产品究竟有哪些亮点…...
【Linux 之一 】Linux常用命令汇总
Linux常用命令 ./catcd 命令chmodclearcphistoryhtoplnmkdirmvpwdrmtailunamewcwhoami 我从2021年4月份开始才开始真正意义上接触Linux,最初学习时是一脸蒙圈,啥也不会,啥也不懂,做了很多乱七八糟,没有条理的笔记。不知…...
Git 从入门到精通
一、环境配置 下载地址:https://git-scm.com/downloads/ 二、用户配置 找到git bash git --version 查看当前版本 git config --global user.name szhipeng625 设置用户名 git config --global user.email szhipeng625gmail.com 设置邮箱 git config --global …...
【Uniapp-Vue3】创建自定义页面模板
大多数情况下我们都使用的是默认模板,但是默认模板是Vue2格式的,如果我们想要定义一个Vue3模板的页面就需要自定义。 一、我们先复制下面的模板代码(可根据自身需要进行修改): <template><view class"…...
Ansible之批量管理服务器
文章目录 背景第一步、安装第二步、配置免密登录2.1 生成密钥2.2 分发公钥2.3 测试无密连接 背景 Ansible是Python强大的服务器批量管理 第一步、安装 首先要拉取epel数据源,执行以下命令 yum -y install epel-release安装完毕如下所示。 使用 yum 命令安装 an…...
android compose 串口通信
1.添加依赖 implementation("io.github.xmaihh:serialport:2.1.1") 2.添加SerialHelper派生类 class SerialPortHelper(portName:String,baudRate:Int): SerialHelper(portName,baudRate) {var receivedDataBuffer mutableListOf<Byte>()override fun onDa…...
ios脚本巨魔商店多巴胺越狱基本操作教程
准备工作 确认设备兼容性:A9-A11(iPhone6s-X):iOS15.0-16.6.1;A12-A14(iPhoneXR-12PM):iOS15.0-16.5.1;A15-A16(iPhone13-…...
NLP项目实战——基于Bert模型的多情感评论分类(附数据集和源码)
在当今数字化的时代,分析用户评论中的情感倾向对于了解产品、服务的口碑等方面有着重要意义。而基于强大的预训练语言模型如 Bert 来进行评论情感分析,能够取得较好的效果。 在本次项目中,我们将展示如何利用 Python 语言结合transformers库&…...
.NET framework、Core和Standard都是什么?
对于这些概念一直没有深入去理解,以至于经过.net这几年的发展进化,概念越来越多,越来越梳理不容易理解了。内心深处存在思想上的懒惰,以为自己专注于Unity开发就好,这些并不属于核心范畴,所以对这些概念总是…...
Mybatis原理简介
看到Mybatis的框架图,可以清晰的看到Mybatis的整体核心对象,我更喜欢用自己的图来表达Mybatis的整个的执行流程。如下图所示: 原理详解: MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在根…...
腾讯云AI代码助手-公司职位分析AI助手
作品简介 腾讯云AI代码助手是一款智能工具,专注于为公司提供职位分析服务。通过自然语言处理和机器学习技术,它能快速解析职位描述,提取关键信息,并提供数据驱动的洞察,帮助公司优化招聘流程和职位设计。 技术架构 …...
腾讯云AI代码助手编程挑战赛-解忧助手
作品简介 何以解忧,唯有杜康。而随着Ai的发展,解忧不再只有杜康还有Ai,使用的是腾讯云AI代码助手来生成的所有代码,使用方便,快捷,高效。 技术架构 采用了全后端分离的架构,前端使用Vue.js,腾讯云的AI服务处理自然语…...
Sentinel服务保护 + Seata分布式事务
服务保护 【雪崩问题】微服务调用链路中某个服务,引起整个链路中所有微服务都不可用。 【原因】: 微服务相互调用,服务提供者出现故障。服务调用这没有做好异常处理,导致自身故障。调用链中所有服务级联失败,导致整个…...
【Leetcode·中等·数组】59. 螺旋矩阵 II(spiral matrix ii)
题目描述 英文版描述 Given a positive integer n, generate an n x n matrix filled with elements from 1 to n(2) in spiral order. Example 1: Input: n 3 Output: [[1,2,3],[8,9,4],[7,6,5]] 提示: 1 < n < 20 英文版地址 https://leetcode.com…...
WebSocket 扩展生态:协议与框架
在前七篇文章中,我们深入探讨了 WebSocket 的基础原理、开发实践和实战案例。今天,让我们把视野扩展到 WebSocket 的生态系统,看看有哪些扩展协议和框架可以帮助我们更好地开发 WebSocket 应用。我曾在一个大型即时通讯项目中,通过合理使用这些工具,将开发效率提升了 50%。 扩…...
MySQL —— 在CentOS9下安装MySQL
MySQL —— 在CentOS9下安装MySQL 1.查看自己操作系统的版本2.找到对应的安装源3.上传我们在windows下,下载的文件,解压4.执行rpm命令,启用MySQL8仓库5.执行dnf install -y mysql-community-server6.设置开机自启动7.获得初始密码8.登录MySQL…...
用VS C#构建Windows服务【纯操作版,附带项目地址】
1.点击“创建新项目”,选择“Windows 服务(.NET Framework)” 2、给项目命名 3、双击“Service1.cs”,右键,选择“添加安装程序”,就会生成一个“ProjectInstaller.cs”文件 4、双击“ProjectInstaller.cs”文件,右键“serviceProcessInstaller1”,选择“属性…...
1.UGUI相关
1.这一种UIcanvas下的组件,会显示在3d物体之前 2.可以设置3d物体在UI界面之前。选中第二个模式。这时候会指定一个摄像机。一般情况下,不用主摄像机。需要新建一个专门给UI的摄像机。相当于设置距离摄像机的远近。两个layer 可以理解成 章节,关卡。相同…...
大语言模型训练的数据集从哪里来?
继续上篇文章的内容说说大语言模型预训练的数据集从哪里来以及为什么互联网上的数据已经被耗尽这个说法并不专业,再谈谈大语言模型预训练数据集的优化思路。 1. GPT2使用的数据集是WebText,该数据集大概40GB,由OpenAI创建,主要内…...
Android 来电白名单 只允许联系人呼入电话
客户需求只允许通讯录中联系人可以呼入电话。参考自带的黑名单实现 CallsManager.java类中的onSuccessfulIncomingCall方法有一些过滤器,可以仿照黑名单的方式添加自己的过滤器。 packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java …...
StarRocks Awards 2024 年度贡献人物
在过去一年,StarRocks 在 Lakehouse 与 AI 等关键领域取得了显著进步,其卓越的产品功能极大地简化和提升了数据分析的效率,使得"One Data,All Analytics" 的愿景变得更加触手可及。 虽然实现这一目标的道路充满挑战且漫…...
plane开源的自托管项目
Plane 是一个开源的自托管项目规划解决方案,专注于问题管理、里程碑跟踪以及产品路线图的设计。作为一款开源软件,Plane 的代码托管在 GitHub 平台上,允许任何人查看和贡献代码。它为用户提供了便捷的项目创建与管理手段,并配备了…...
WebLogic安全基线
WebLogic安全基线 一、 用户权限1 、检查weblogic 的启动用户2 、用户权限整改3 、使用普通用户重启weblogic 二、账户共用1 、检查weblogic 控制台的账户2 、账户共用整改3 、测试登录weblogic 控制台新账户 三、 账户清理1 、检查weblogic 控制台的账户2 、帐户清理整改 四、…...
复杂园区网基本分支的构建
目录 1、各主机进行网络配置。2、交换机配置。3、配置路由交换,进行测试。4、配置路由器接口和静态路由,进行测试。5、最后测试任意两台主机通信情况 模拟环境链接 拓扑结构 说明: VLAN标签在上面的一定是GigabitEthernet接口的,…...
设计模式-结构型-组合模式
1. 什么是组合模式? 组合模式(Composite Pattern) 是一种结构型设计模式,它允许将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。换句话说,组合模式允…...
32单片机从入门到精通之用户界面——用户界面(十四)
不论你现在处于什么样的困境和挑战,不要放弃希望和努力。成功之路不会一帆风顺,但是只要你坚定信念,勇敢面对困难,努力奋斗,就一定能够战胜困难,迈向成功的道路。困难和挫折只是暂时的,而坚持和…...
Redis 优化秒杀(异步秒杀)
目录 为什么需要异步秒杀 异步优化的核心逻辑是什么? 阻塞队列的特点是什么? Lua脚本在这里的作用是什么? 异步调用创建订单的具体逻辑是什么? 为什么要用代理对象proxy调用createVoucherOrder方法? 对于代码的详细…...
NFS 组件容器化部署实战指南
文章目录 前言部署NFS服务器K8S部署NFS问题记录 前言 使用nfs-client-provisioner这个应用,利用nfs server给kubernets提供作为持久化后端,并且动态提供pv。所有节点需要安装nfs-utils组件,并且nfs服务器与kubernets worker节点都能网络连通…...
LCE(Local Cascade Ensemble)预测模型和LSTM(Long Short-Term Memory)模型在效果和特点上存在显著差异
LCE(Local Cascade Ensemble)预测模型和LSTM(Long Short-Term Memory)模型在效果和特点上存在显著差异。以下是对两者的比较: 一、效果比较 LCE模型: 优势:LCE结合了随机森林和XGBoost的优势&a…...
rk3568平台Buildroot编译实践:内核rootfs定制 及常见编译问题
目录 编译前准备常规编译流程定制内核修改内核 参数并增量 保存修改rootfs并增量 保存修改rootfs包下载源rootfs软件包增删refBuildroot 是一个用于自动化构建嵌入式 Linux 系统的工具。它通过使用简单的配置文件和 Makefile,能够从源代码开始交叉编译出一个完整的、可以运行在…...
头歌python实验:网络安全应用实践-恶意流量检测
第1关:re 库的使用 本关任务:编写一个能正则匹配出 ip 地址的小程序。 re 的主要功能函数 常用的功能函数包括: compile、search、match、split、findall(finditer)、sub(subn)。 re.search 函数 re.search 扫描整个字符串并返回第一个成功的匹配。 函数语法: re…...
Linux内核编程(二十一)USB应用及驱动开发
一、基础知识 1. USB接口是什么? USB接口(Universal Serial Bus)是一种通用串行总线,广泛使用的接口标准,主要用于连接计算机与外围设备(如键盘、鼠标、打印机、存储设备等)之间的数据传输和电…...
机器学习实战——决策树:从原理到应用的深度解析
✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ 决策树(Decision Tree)是一种简单而直观的分类与回归模型,在机器学习中广泛应用。它的…...
计算机网络的定义与发展历程
计算机网络的定义 计算机网络是指通过通信设备和传输介质将分布在不同地点的计算机及其相关设备(如打印机、服务器等)连接起来,按照一定的通信协议进行数据交换与资源共享的系统。计算机网络的基本功能包括:信息的传输、资源共享…...
什么是Kafka?有什么主要用途?
大家好,我是锋哥。今天分享关于【什么是Kafka?有什么主要用途?】面试题。希望对大家有帮助; 什么是Kafka?有什么主要用途? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka 是一个分布式流…...
Ubuntu 24.04 LTS系统安装Docker踩的坑
一开始我跟着Docker给出的官网文档 Ubuntu | Docker Docs 流程走,倒腾了两个多小时,遇到了各种坑,最后放弃了。在我们使用脚本安装Docker命令前,我们先把已经安装的Docker全部卸载掉。 卸载Docker 1.删除docker及安装时自动安装…...
算法基础 - 冒泡排序
文章目录 1、原理演示2、示例一 冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。重复进行直到没有再需要交换的元素,这意味着数列已经排序完成。 冒泡排序法采用…...
为答疑机器人扩展问题分类与路由功能
1.意图识别 2. 构建路由模块 简单的意图识别 from chatbot import llmfrom config.load_key import load_key load_key()prompt 【角色背景】 你是一个问题分类路由器,需要识别问题的类型。 --- 【任务要求】 问题的类型目前有:公司内部文档查询、内…...
如何解决 /proc/sys/net/bridge/bridge-nf-call-iptables 文件缺失的问题
在使用 Linux 系统,尤其是在容器化环境(如 Docker、Kubernetes)中时,我们可能会遇到如下错误信息: [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptabl…...
道品科技智慧农业与云平台:未来农业的变革之路
随着全球人口的不断增长,农业面临着前所未有的挑战。如何在有限的土地和资源上提高农业生产效率,成为了各国政府和农业从业者亟待解决的问题。智慧农业的兴起,结合云平台的应用,为农业的可持续发展提供了新的解决方案。 ## 一、智…...
芯片详细讲解,从而区分CPU、MPU、DSP、GPU、FPGA、MCU、SOC、ECU
目录 芯片的概念结构 芯片的派系划分 通用芯片(CPU,MPU,GPU,DSP) 定制芯片(FPGA,ASIC) 芯片之上的集成(MCU,SOC,ECU) 软硬件的匹…...