【tokenization分词】WordPiece, Byte-Pair Encoding(BPE), Byte-level BPE(BBPE)的原理和代码

目录

前言

1、word (词粒度)

2、char (字符粒度)

3、subword (子词粒度)

WordPiece

Byte-Pair Encoding (BPE)

Byte-level BPE(BBPE)

总结


前言

Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,将文本处理成一串tokens用于后续的处理,把文本处理成token有一系列的方法,基本思想是构建一个词表,通过词表一一映射进行分词,但如何构建合适的词表呢?以下介绍三种Tokenization方法。


以下分别介绍三种分词粒度,并且重点介绍 subword (子词粒度) 的三种分词方法。

1、word (词粒度)

在英文语系中,word(词)粒度的分词很容易实现,因为有天然的空格作为分隔符。而在中文里面 word (词)粒度,需要一些分词工具比如jieba,以下是中文和英文的例子:

中文句子:我喜欢看电影和读书。
分词结果:我 | 喜欢 | 看 | 电影 | 和 | 读书。


英文句子:I enjoy watching movies and reading books.
分词结果:I | enjoy | watching | movies | and | reading | books.

word (词粒度) 的优点:

  • 词自身的语义明确:以词为单位进行分词可以更好地保留每个词的语义。
  • 词之间的语义关联:以词为粒度进行分词有助于保留词语之间的关联性和上下文信息,从而在语义分析和理解时能够更好地捕捉句子的意图。

缺点:

  • 长尾效应:词表可能变得巨大,包含很多不常见的词汇,增加存储成本;稀有词的训练数据有限,难以获得准确的表示。
  • OOV(Out-of-Vocabulary): 词粒度分词模型只能使用词表中的词来进行处理,无法处理词表之外的词汇,这就是所谓的OOV问题。
  • 形态关系和词缀关系: 无法捕捉同一词的不同形态,比如love和loves在word(词)粒度的词表中会是两个词;也无法学习词缀在不同词汇之间的共通性,限制了模型的语言理解能力。

2、char (字符粒度)

以字符为单位进行分词,即将文本拆分成一个个单独的字符作为最小基本单元,这种字符粒度的分词方法适用于多语言,无论是英文、中文还是其他不同语言,都能够一致地使用字符粒度进行处理,因为英文就26个字母以及其他的一些符号,中文常见字就6000个左右。

中文句子:我喜欢看电影和读书。
分词结果:我 | 喜 | 欢 | 看 | 电 | 影 | 和 | 读 | 书 | 。

英文句子:I enjoy watching movies and reading books.
分词结果:I |   | e | n | j | o | y |   | w | a | t | c | h | i | n | g |   | m | o | v | i | e | s |   | a | n | d |   | r | e | a | d | i | n | g |   | b | o | o | k | s | .

char (字符粒度) 的优点有:

  • 统一处理方式:字符粒度分词方法适用于不同语言,无需针对每种语言设计不同的分词规则或工具,具有通用性。
  • 解决OOV问题:由于字符粒度分词可以处理任何字符,无需维护词表,因此可以很好地处理一些新创词汇、专有名词等问题。

缺点:

  • 语义信息不明确:字符粒度分词无法直接表达词的语义,导致在一些语义分析任务中效果较差。
  • 处理效率低:由于文本被拆分为字符,处理的粒度较小,增加后续处理的计算成本和时间。

3、subword (子词粒度)

在很多情况下,既不希望将文本切分成单独的词(太大),也不想将其切分成单个字符(太小),而是希望得到介于词和字符之间的子词单元。这就引入了 subword(子词)粒度的分词方法。

WordPiece

在BERT时代,WordPiece 分词方法被广泛应用,比如 BERT、DistilBERT等。WordPiece 分词方法属于 subword(子词)粒度的一种方法。 

WordPiece核心思想是将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为子词级别。例如对于单词"word",拆分如下:

w ##o ##r ##d

然后通过合并规则进行合并,从而循环迭代构建出一个词表,以下是核心步骤:

  1. 计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。
  2. 计算合并分数:对训练语料拆分的多个子词单元通过合拼规则计算合并分数。
  3. 合并子词对:选择分数最高的子词对,将它们合并成一个新的子词单元,并更新词表。
  4. 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词表的效益)。
  5. 分词:使用最终得到的词汇表对文本进行分词。

简单举例:

有以下的训练语料中的样例,括号中第2位为在训练语料中出现的频率:

("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

将其拆分为带前缀的形式:

("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)

初始化的词表为:

["b", "h", "p", "##g", "##n", "##s", "##u"]

接下来重要的一步进行计算合并分数,也称作互信息(信息论中衡量两个变量之间的关联程度),简单来说就是以下公式来计算:

分数 = 候选pair的频率 / (第一个元素的频率 × 第二个元素的频率)

对于上述样例中这个pair("##u", "##g")出现的频率是最高的20次,但是"##u"出现的频率是36次, "##g"出现的频率是20次,所以这个pair("##u", "##g")的分数是(20)/(36*20) = 1/36,同理计算这个pair("##g", "##s")的分数为(5)/(20*5) = 1/20,所以最先合并的pair是("##g", "##s")→("##gs")。此时词表和拆分后的的频率将变成以下:

Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]
Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)

重复上述的操作,直到达到你想要的词表的大小:

Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]
Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)

代码实现:

# 用一些包含中英文的文本作为训练语料,因为英文有天然的分隔符,所以在这个例子中,中文已经进行了分词:
sentences = ["我","喜欢","吃","苹果","他","不","喜欢","吃","苹果派","I like to eat apples","She has a cute cat","you are very cute","give you a hug",
]# 初始化词表
from collections import defaultdict
# 构建频率统计
def build_stats(sentences):stats = defaultdict(int)for sentence in sentences:symbols = sentence.split()for symbol in symbols:stats[symbol] += 1return statsstats = build_stats(sentences)
print("stats:", stats)alphabet = []
for word in stats.keys():if word[0] not in alphabet:alphabet.append(word[0])for letter in word[1:]:if f"##{letter}" not in alphabet:alphabet.append(f"##{letter}")alphabet.sort()
# 初始词表
vocab = alphabet.copy()
print("alphabet:", alphabet)# 根据初始词表拆分每个词
splits = {word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]for word in stats.keys()
}
print("splits:", splits)# 计算合并分数
def compute_pair_scores(splits):letter_freqs = defaultdict(int)pair_freqs = defaultdict(int)for word, freq in stats.items():split = splits[word]if len(split) == 1:letter_freqs[split[0]] += freqcontinuefor i in range(len(split) - 1):pair = (split[i], split[i + 1])letter_freqs[split[i]] += freqpair_freqs[pair] += freqletter_freqs[split[-1]] += freqscores = {pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])for pair, freq in pair_freqs.items()}return scorespair_scores = compute_pair_scores(splits)
for i, key in enumerate(pair_scores.keys()):print(f"{key}: {pair_scores[key]}")if i >= 5:break# 看一看分数最高的pair(子词对):
best_pair = ""
max_score = None
for pair, score in pair_scores.items():if max_score is None or max_score < score:best_pair = pairmax_score = scoreprint(best_pair, max_score)# 合并函数
def merge_pair(a, b, splits):for word in stats:split = splits[word]if len(split) == 1:continuei = 0while i < len(split) - 1:if split[i] == a and split[i + 1] == b:merge = a + b[2:] if b.startswith("##") else a + bsplit = split[:i] + [merge] + split[i + 2 :]else:i += 1splits[word] = splitreturn splits# 循环迭代,直到vocab达到想要的数量
vocab_size = 50
while len(vocab) < vocab_size:scores = compute_pair_scores(splits)best_pair, max_score = "", Nonefor pair, score in scores.items():if max_score is None or max_score < score:best_pair = pairmax_score = scoresplits = merge_pair(*best_pair, splits)new_token = (best_pair[0] + best_pair[1][2:]if best_pair[1].startswith("##")else best_pair[0] + best_pair[1])vocab.append(new_token)# 结果
vocab: ['##a', '##e', '##g', '##h', '##i', '##k', '##l', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##y', '##果', '##欢', '##派', 'I', 'S', 'a', 'c', 'e', 'g', 'h', 'l', 't', 'v', 'y', '不', '他', '吃', '喜', '我', '苹', 'Sh', '喜欢', '苹果', '苹果派', 'li', 'lik', 'gi', 'giv', '##pl', '##ppl', '##ry', 'to', 'yo', 'ea', 'eat']

一般来说最后会在词表中加上一些特殊词汇、英文中26个字母、各种符号以及常见中文字符,如果训练语料比较大那么这些应该也已经包括了,只需要添加特殊词汇:

all_vocab = vocab + ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + other_alphabet

Byte-Pair Encoding (BPE)

在大语言模型时代,最常用的分词方法是Byte-Pair Encoding (BPE) 和 Byte-level BPE(BBPE),BPE 最初是一种文本压缩算法,,在15年被引入到NLP用于分词,在训练 GPT 时被 OpenAI 用于tokenization,后续好多模型GPT,RoBERTa等都采用了这种分词方法。BBPE是于19年在BPE的基础上提出以Byte-level(字节)为粒度的分词方法,目前GPT2,BLOOM,Llama,Falcon等采用的是该分词方法。 

Byte-Pair Encoding (BPE)核心思想是逐步合并出现 频率最高 的子词对,而不是像Wordpiece计算合并分数来构建词汇表,以下是核心步骤:

  1. 计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。
  2. 构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。
  3. 合并子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。
  4. 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
  5. 分词:使用最终得到的词汇表对文本进行分词。

代码:

sentences = ["我","喜欢","吃","苹果","他","不","喜欢","吃","苹果派","I like to eat apples","She has a cute cat","you are very cute","give you a hug",
]# 初始化词表:
# 构建频率统计
def build_stats(sentences):stats = defaultdict(int)for sentence in sentences:symbols = sentence.split()for symbol in symbols:stats[symbol] += 1return statsstats = build_stats(sentences)
print("stats:", stats)alphabet = []
for word in stats.keys():for letter in word:if letter not in alphabet:alphabet.append(letter)
alphabet.sort()# 初始词表
vocab = alphabet.copy()
print("alphabet:", alphabet)# 根据初始词表拆分每个词
splits = {word: [c for c in word] for word in stats.keys()}
print("splits:", splits)def 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_freqs
pair_freqs = compute_pair_freqs(splits)for i, key in enumerate(pair_freqs.keys()):print(f"{key}: {pair_freqs[key]}")if i >= 5:break# 找到出现频率最高的pair(子词对):
best_pair = ""
max_freq = None
for pair, freq in pair_freqs.items():if max_freq is None or max_freq < freq:best_pair = pairmax_freq = freqprint(best_pair, max_freq)# 合并函数
def merge_pair(a, b, splits):for word in stats:split = splits[word]if len(split) == 1:continuei = 0while i < len(split) - 1:if split[i] == a and split[i + 1] == b:split = split[:i] + [a + b] + split[i + 2 :]else:i += 1splits[word] = splitreturn splits# 循环直到vocab达到想要的数量:
# 假设我们想要的词典为50
merges = {}
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)merges[best_pair] = best_pair[0] + best_pair[1]vocab.append(best_pair[0] + best_pair[1])print("merges:", merges)
print("vocab:", vocab)# 结果
merges: {('喜', '欢'): '喜欢', ('苹', '果'): '苹果', ('a', 't'): 'at', ('c', 'u'): 'cu', ('cu', 't'): 'cut', ('cut', 'e'): 'cute', ('y', 'o'): 'yo', ('yo', 'u'): 'you', ('v', 'e'): 've', ('苹果', '派'): '苹果派', ('l', 'i'): 'li', ('li', 'k'): 'lik', ('lik', 'e'): 'like', ('t', 'o'): 'to', ('e', 'at'): 'eat', ('a', 'p'): 'ap', ('ap', 'p'): 'app', ('app', 'l'): 'appl', ('appl', 'e'): 'apple', ('apple', 's'): 'apples', ('S', 'h'): 'Sh', ('Sh', 'e'): 'She', ('h', 'a'): 'ha'}
vocab: ['I', 'S', 'a', 'c', 'e', 'g', 'h', 'i', 'k', 'l', 'o', 'p', 'r', 's', 't', 'u', 'v', 'y', '不', '他', '吃', '喜', '我', '果', '欢', '派', '苹', '喜欢', '苹果', 'at', 'cu', 'cut', 'cute', 'yo', 'you', 've', '苹果派', 'li', 'lik', 'like', 'to', 'eat', 'ap', 'app', 'appl', 'apple', 'apples', 'Sh', 'She', 'ha']

再加上一些特殊词汇和其他词汇:

all_vocab = vocab + ["[PAD]", "[UNK]", "[BOS]", "[EOS]"] + other_alphabet

BPE理论上还是会出现OOV的,当词汇表的大小受限时,一些较少出现的子词和没有在训练过程中见过的子词,就会无法进入词汇表出现OOV,而Byte-level BPE(BBPE)理论上是不会出现这个情况的。

Byte-level BPE(BBPE)

基础知识:

Unicode: Unicode 是一种字符集,旨在涵盖地球上几乎所有的书写系统和字符。Unicode 码点的取值范围是 U+0000U+10FFFF,总共覆盖了 1,114,112 个可能的码点。这些码点包括了全球几乎所有的书写系统和符号,可以容纳各种文字、符号、表情符号等。

UTF-8:UTF-8(Unicode Transformation Format-8)是一种变长的字符编码方案,它将 Unicode 中的代码点转换为字节序列。在 UTF-8 编码中,字符的表示长度可以是1到4个字节,不同范围的 Unicode 代码点使用不同长度的字节序列表示,这样可以高效地表示整个 Unicode 字符集。

例如,英文字母 "A" 的 Unicode 代码点是U+0041,在 UTF-8 中表示为 0x41(与 ASCII 编码相同);而中文汉字 "你" 的 Unicode 代码点是U+4F60,在 UTF-8 中表示为0xE4 0xBD 0xA0三个字节的序列。

Byte(字节):计算机存储和数据处理的最小单位。一个字节包含8个(Bit)二进制位,每个位可以是0或1,每位的不同排列和组合可以表示不同的数据。

Byte-level BPE (BBPE) 和Byte-Pair Encoding (BPE) 区别是:BPE的最小词汇是字符级别,而BBPE是字节级别的,通过UTF-8的编码方式,理论上可以表示这个世界上的所有字符。

所以实现步骤和BPE是一样的,只是实现的粒度不一样:

  1. 初始词表:构建初始词表,包含一个字节的所有表示(256)。
  2. 构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。
  3. 合并子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。
  4. 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
  5. 分词:使用最终得到的词汇表对文本进行分词。
from collections import defaultdict
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)# 构建频率统计
def build_stats(sentences):stats = defaultdict(int)for sentence in sentences:symbols = sentence.split()for symbol in symbols:stats[symbol.encode("utf-8")] += 1return stats
stats = build_stats(sentences)splits = {word: [byte for byte in word] for word in stats.keys()}
def 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_freqspair_freqs = compute_pair_freqs(splits)def 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 splitsvocab_size = 50
while 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个表示如下:

[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中文词表的工作。

上述分词算法在工程上实现一般使用sentencpiece工具包,谷歌在这个包中实现了上述的一系列算法,扩充Llama中文词表的工作也都是在此上面实现的。


总结

三种分词方法的不同主要在于 初始化词表 和 合并子词对 的方式,理解每一种分词能够更深刻理解不同分词方法在不同任务和模型中的表现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/60994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析 MySQL 数据库:数据库时区问题

在 MySQL 数据库中&#xff0c;时区管理是一个重要且复杂的主题&#xff0c;尤其是在全球化的应用程序中。以下是关于 MySQL 数据库时区问题的深入解析&#xff1a; 1. 时区的概念 时区是指地球表面被分为若干个区域&#xff0c;每个区域的标准时间相对协调世界时 (UTC) 有所…

技术周总结 11.11~11.17 周日(Js JVM XML)

文章目录 一、11.11 周一1.1&#xff09;问题01&#xff1a;js中的prompt弹窗区分出来用户点击的是 确认还是取消进一步示例 1.2&#xff09;问题02&#xff1a;在 prompt弹窗弹出时默认给弹窗中写入一些内容 二、11.12 周二2.1) 问题02: 详解JVM中的本地方法栈本地方法栈的主要…

模拟实现STL中的list

目录 1.设计list的结点 2.设计list的迭代器 3.list类的设计总览 4.list类的迭代器操作 5.list类的四个特殊的默认成员函数 无参的默认构造函数 拷贝构造函数 赋值运算符重载函数 析构函数 6.list类的插入操作 7.list类的删除操作 8.list.hpp源代码 1.设计list的结点…

spark 设置hive.exec.max.dynamic.partition不生效

spark脚本和程序中设置ive.exec.max.dynamic.partition不生效 正确写法&#xff1a; spark-submit \ --master yarn \ --deploy-mode client \ --driver-memory 1G \ --executor-memory 12G \ --num-executors 8 \ --executor-cores 4 \--conf spark.hadoop.hive.exec.max.dyna…

.NET SDK 各操作系统开发环境搭建

一、Win10&#xff08;推荐&#xff09; 1、VS 2022 社区版 # 下载地址 https://visualstudio.microsoft.com/zh-hans/downloads/ 2、.NET 6 SDK # 下载地址 https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0 3、Hello World 如果需要使用旧程序样式时&#xff0c;则…

centos7.9安装mysql社区版

文章目录 场景安装 场景 今天把家里闲置的笔记本安装了centos&#xff0c;设置内网穿透做个人服务器用&#xff0c; 这里记录下安装mysql的过程 安装 安装mysql源 sudo yum install -y wget wget https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm sudo r…

IDEA怎么定位java类所用maven依赖版本及引用位置

在实际开发中&#xff0c;我们可能会遇到需要搞清楚代码所用依赖版本号及引用位置的场景&#xff0c;便于排查问题&#xff0c;怎么通过IDEA实现呢&#xff1f; 可以在IDEA中打开项目&#xff0c;右键点击maven的pom.xml文件&#xff0c;或者在maven窗口下选中项目&#xff0c;…

CommandLineRunner、ApplicationRunner和@PostConstruct

在Spring Boot中&#xff0c;CommandLineRunner、ApplicationRunner 和 PostConstruct 都是常用的生命周期管理接口或注解&#xff0c;它们有不同的用途和执行时机&#xff0c;帮助开发者在Spring应用启动过程中进行一些初始化操作或执行特定任务。下面分别介绍它们的特点、使用…

【Golang】——Gin 框架中的模板渲染详解

Gin 框架支持动态网页开发&#xff0c;能够通过模板渲染结合数据生成动态页面。在这篇文章中&#xff0c;我们将一步步学习如何在 Gin 框架中配置模板、渲染动态数据&#xff0c;并结合静态资源文件创建一个功能完整的动态网站。 文章目录 1. 什么是模板渲染&#xff1f;1.1 概…

力扣 LeetCode 144. 二叉树的前序遍历(Day6:二叉树)

解题思路&#xff1a; 方法一&#xff1a;递归&#xff08;中左右&#xff09; class Solution {List<Integer> res new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {recur(root);return res;}public void recur(TreeNode roo…

高级 SQL 技巧讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; SQL&#xff08;结构化查询语言&#xff09;是管理和操作数据库的核心工具。从基本的查询语句到复杂的数据处理&#xff0c;掌握高级 SQL 技巧不仅能显著提高数据分析的效率&#xff0c;还能解决业务中的复…

Java Server Pages (JSP):动态网页开发的基石

在Web开发的广阔领域中&#xff0c;Java Server Pages (JSP) 作为一种将Java代码与HTML内容相结合的服务器端技术&#xff0c;始终占据着举足轻重的地位。作为Java Enterprise Edition (Java EE) 的核心组成部分&#xff0c;JSP不仅为开发者提供了强大的动态网页生成能力&#…

pom中无法下载下来的类外部引用只给一个jar的时候

比如jar在桌面上放着,操作步骤如下&#xff1a; 选择桌面&#xff0c;输入cmd ,执行mvn install:install-file -DgroupIdcom -DartifactIdaspose-words -Dversion15.8.0 -Dpackagingjar -Dclassifierjdk11 -Dfilejar包名称 即可把jar包引入成功。

c语言学习21数组

1.1数组介绍 概念&#xff1a;数组就是 相同数据类型 的一组数据的集合 数组中每个数据 元素 用一个名字来命名这个集合 数组名 用编号区分 下表&#xff08;从0开始自动标号&#xff09; 当处理大量…

No Module named pytorchvideo.losses问题解决

问题描述 最近在跑X3D的源码时发现&#xff0c;在conda powershell prompt中安装了pytorchvideo&#xff0c;但是仍然报错&#xff1a;No Module named pytorchvideo.losses 解决方案&#xff1a; 直接去https://gitcode.com/gh_mirrors/py/pytorchvideo/overview?utm_sour…

LLM学习笔记(1).env文件与Anaconda虚拟环境有何区别?

刚入门&#xff0c;我打算跟着这个随笔走&#xff1a; 前言 LLM 应用开发实践笔记 会先从ChatGPT的中文文档开始&#xff1a; 入门 | OpenAI 官方帮助文档中文版 以上笔记和文档里的内容就不会在我的学习笔记里重复了&#xff0c;最后会把注意力转移到Hugging Face和论文上…

【软件测试】设计测试用例的万能公式

文章目录 概念设计测试用例的万能公式常规思考逆向思维发散性思维万能公式水杯测试弱网测试如何进行弱网测试 安装卸载测试 概念 什么是测试用例&#xff1f; 测试⽤例&#xff08;Test Case&#xff09;是为了实施测试⽽向被测试的系统提供的⼀组集合&#xff0c;这组集合包…

MySQL存储过程的详细说明

MySQL存储过程的详细说明 MySQL 存储过程是一种预编译的 SQL 语句集合&#xff0c;可以接受参数并返回结果。存储过程可以提高数据库的性能、可维护性和安全性。本文将详细介绍如何在 MySQL 中创建和使用存储过程&#xff0c;包括多个参数传入、返回输出和事务处理。 1. 存储…

在连通无向图中寻找欧拉回路(Eulerian Circuit)

在连通无向图中寻找欧拉回路(Eulerian Circuit) 问题描述解决方案概述算法步骤伪代码C代码示例如何在迷宫中找出一条路示例:在简单迷宫中应用欧拉回路结论问题描述 给定一个连通无向图 $ G = (V, E) $,我们需要找到一条路径,该路径正向和反向通过 $ E $ 中的每条边恰好一…