一文搞懂SentencePiece的使用
目录
- 1. 什么是 SentencePiece?
- 2. SentencePiece 基础概念
- 2.1 SentencePiece 的工作原理
- 2.2 SentencePiece 的优点
- 3. SentencePiece 的使用
- 3.1 安装 SentencePiece
- 3.2 训练模型与加载模型
- 3.3 encode(高频)
- 3.4 decode(高频)
- 3.5 设置相关选项(不常用)
- 4. Trainer的使用
- 5. 大小写相关问题
- 6. 一些误区
- Ref
1. 什么是 SentencePiece?
在自然语言处理(NLP)任务中,文本的预处理环节至关重要。无论是机器翻译、语言模型,还是问答系统,如何将原始文本转化为模型能够理解的输入是其中一个关键步骤。词汇表的构建和分词方式的选择,往往会直接影响模型的性能。而 SentencePiece
是谷歌开发的一款用于构建词汇表和分词的工具,它特别适用于那些没有明确分词边界的语言,能够在子词级别上实现无监督的文本分割。
SentencePiece
是一种基于子词单元的分词器,广泛应用于机器翻译和文本生成任务中。与传统分词方法不同,SentencePiece
并不依赖于语言的词汇结构,能够直接处理不带空格的语言(例如中文、日文)。它基于两种主要的算法:Byte-Pair Encoding (BPE) 和 Unigram Language Model,在生成子词单元的同时,提供了灵活的词汇表管理方式。
2. SentencePiece 基础概念
2.1 SentencePiece 的工作原理
SentencePiece
的核心思想是将文本分解为子词单元(subword units)。它不依赖预定义的词汇表,而是通过统计学习自动构建子词单元。无论是空格分隔的语言(如英语),还是无空格分隔的语言(如中文、日文),它都能够处理。这样做的好处是,它可以处理不在词汇表中的未知词,并且有效减少词汇表的大小,从而降低 OOV(Out of Vocabulary)问题。
SentencePiece
提供了两种主要的分词算法:
- Byte-Pair Encoding (BPE):通过反复合并最频繁的子词对,逐渐构建子词词汇表。BPE 是一种贪心算法,它通过合并最频繁的字符或子词对来构建词汇表。
- Unigram Language Model:这是一种概率模型,基于一个语言模型来选择最优的子词分割方式。它从一个完整的子词词典开始,逐步移除低概率的子词,最终保留高概率的子词作为最终的词典。
📝 SentencePiece底层使用C++实现,并通过SWIG暴露接口
2.2 SentencePiece 的优点
- 语言无关性:
SentencePiece
不依赖于语言的词汇结构,可以直接用于无空格分隔的语言(如中文)。 - 子词单元的灵活性:通过子词分割,模型可以处理未知词,避免 OOV 问题。
- 词汇表大小可控:通过设定词汇表大小,可以精确控制子词的数量,平衡模型性能与存储资源。
- 简化文本预处理:传统方法需要对文本进行分词和词汇表生成,而
SentencePiece
将这两个步骤合并,简化了工作流程。
3. SentencePiece 的使用
SentencePiece
提供了易用的 Python API,帮助开发者快速集成到项目中。在这一章节中,我们将详细介绍如何使用 SentencePiece
进行文本分割、词汇表训练、子词编码等操作。
3.1 安装 SentencePiece
在使用 SentencePiece
之前,需要先通过 pip 安装相关库:
pip install sentencepiece
安装完成后,我们就可以开始使用 SentencePiece
的 Python API 进行分词和词汇表构建。
3.2 训练模型与加载模型
SentencePiece
的第一个主要功能是训练分词模型。我们可以使用 SentencePiece
来学习文本中的子词分割模式,并生成一个可用于后续编码的词汇表。训练过程主要分为以下步骤:
- 准备好要训练的文本数据。
- 使用
SentencePieceTrainer
进行模型训练。
import sentencepiece as spm# 假设我们有一个文本文件 'data.txt',其中包含我们希望训练的文本数据
spm.SentencePieceTrainer.train(input='data.txt', model_prefix='mymodel', vocab_size=8000)
上述代码中,train
函数用于训练模型,input
参数指定输入文件,model_prefix
用于指定输出模型的前缀,vocab_size
则是我们希望的词汇表大小。在训练完成后,会生成两个文件:
mymodel.model
:这是分词模型文件,包含了子词分割的规则。mymodel.vocab
:这是词汇表文件,列出了所有生成的子词。
训练完成后,我们可以加载生成的模型进行文本分割和编码操作。首先,我们需要使用 SentencePieceProcessor
来加载模型文件。
import sentencepiece as spm# 加载已训练好的模型
sp = spm.SentencePieceProcessor(model_file='mymodel.model')
加载完成后,sp
对象即是我们用于进行文本处理的分词器。
📝
.model
是二进制文件,.vocab
是文本文件。.model
文件已经包含了词汇表,所以加载模型的时候不需要传入.vocab
文件。.vocab
文件仅仅是用来辅助开发者了解词汇表的。
接下来以LLaMA Tokenizer为例。
查看词表大小:
# 四种方法效果相同
print(sp.vocab_size())
print(sp.get_piece_size())
print(sp.piece_size())
print(len(sp))
# 均是32000
获取整个词表:
for i in range(len(sp)):print(sp.id_to_piece(i))
或者执行
spm_export_vocab --model=mymodel.model --output=mymodel.vocab
获取某一个token的id不仅可以用 piece_to_id
方法,还可以直接调用 __getitem__
:
print(sp['<unk>']) # 0
查看特殊词元:
print(sp.unk_id()) # 0
print(sp.bos_id()) # 1
print(sp.eos_id()) # 2
print(sp.pad_id()) # -1,意味着没有设置pad token
3.3 encode(高频)
加载模型后,我们可以使用 SentencePieceProcessor
对文本进行分词和还原。
encode
和 decode
可以说是用的最多的两个方法了,前者用来分词,后者用来对分词后的结果进行还原。
encode
的函数签名如下:
def encode(self, input: str, out_type: Type[Union[int, str]] = int, add_bos: bool = False, add_eos: bool = False
) -> Union[List[int], List[str]]:
out_type
决定了分词结果是 List[int]
还是 List[str]
。
text = "This is a test."
print(sp.encode(text))
print(sp.encode(text, out_type=str))
print(sp.encode(text, add_bos=True, add_eos=True))
print(sp.encode(text, add_bos=True, out_type=str))
输出分别为:
[910, 338, 263, 1243, 29889]
['▁This', '▁is', '▁a', '▁test', '.']
[1, 910, 338, 263, 1243, 29889, 2]
['<s>', '▁This', '▁is', '▁a', '▁test', '.']
当然我们还可以批量进行分词:
text1 = "This is a test."
text2 = "Another test sentence."
text3 = "SentencePiece is a useful tool for tokenization."
text4 = "Batch encoding is efficient."texts = [text1, text2, text3, text4]print(sp.encode(texts))
print('-' * 15)
print(sp.encode(texts, out_type=str))
输出:
[[910, 338, 263, 1243, 29889], [7280, 1243, 10541, 29889], [28048, 663, 29925, 347, 346, 338, 263, 5407, 5780, 363, 5993, 2133, 29889], [350, 905, 8025, 338, 8543, 29889]]
---------------
[['▁This', '▁is', '▁a', '▁test', '.'], ['▁Another', '▁test', '▁sentence', '.'], ['▁Sent', 'ence', 'P', 'ie', 'ce', '▁is', '▁a', '▁useful', '▁tool', '▁for', '▁token', 'ization', '.'], ['▁B', 'atch', '▁encoding', '▁is', '▁efficient', '.']]
如果你觉得每次都要指定 out_type
略显麻烦,sp
还提供了 encode_as_ids
和 encode_as_pieces
两种接口,它们相当于:
encode_as_ids = lambda input: encode(input, out_type=int)
encode_as_pieces = lambda input: encode(input, out_type=str)
3.4 decode(高频)
decode
就是将分词后的结果还原成字符串。其函数签名如下:
def decode(self, input: Union[List[int], List[str]],
) -> str:
decode
会自动检测输入的类型,如下:
text = "This is a test."
list_int = sp.encode_as_ids(text)
list_str = sp.encode_as_pieces(text)print(sp.decode(list_int))
print(sp.decode(list_str))
均能正确还原原始的 text
。
sp
中还提供了 decode_ids
和 decode_pieces
两种方法,但实际上比较鸡肋,它们其实都指向 decode
方法,所以不如直接用 decode
。
3.5 设置相关选项(不常用)
SentencePiece
提供了许多选项,例如:
# 分词的时候默认加上eos
sp.set_encode_extra_options("eos")
text = "This is a test."
print(sp.encode(text)) # [910, 338, 263, 1243, 29889, 2]
此时即使设置 add_bos=False
也无济于事,所以 set_encode_extra_options
的优先级是最高的,要谨慎设置。
常见的选项有:
eos
:默认在分词结果里加上eos
。bos
:默认在分词结果里加上bos
。reverse
:默认颠倒分词结果。unk
:在分词结果里对未出现的词元设置为unk
。
还可以通过 :
将多个选项组合在一起使用:
sp.set_encode_extra_options("bos:eos:reverse")
text = "This is a test."
print(sp.encode(text, out_type=str))
# ['</s>', '.', '▁test', '▁a', '▁is', '▁This', '<s>']
清空已设置的选项只需执行
sp.set_encode_extra_options('')
此外,set_vocabulary
限制分词器使用指定的词汇,reset_vocabulary
恢复完整词汇表,load_vocabulary
根据频率阈值动态加载词汇表,但这三个方法都不会改变原始模型的词汇表大小。
4. Trainer的使用
以上我们仅讨论了如何使用 Processor
,但还没有讨论如何使用 Trainer
。
spm.SentencePieceTrainer.train
中的参数可通过执行如下命令
spm_train --help
来查看。函数签名如下(仅列举了常用的参数):
def train(input: Optional[Union[str, List[str]]] = None,model_prefix: Optional[str] = None,vocab_size: int = 8000,model_type: str = "unigram",character_coverage: float = 0.9995,max_sentence_length: int = 4192,user_defined_symbols: Optional[Union[str, List[str]]] = None,unk_id: int = 0,bos_id: int = 1,eos_id: int = 2,pad_id: int = -1,unk_piece: str = '<unk>',bos_piece: str = '<s>',eos_piece: str = '</s>',pad_piece: str = '<pad>',
):
input
:输入可以是一个文件也可以是多个文件,多个文件就是List[str]
。文件中的每一行都应当是一个未经处理的原始句子。model_prefix
:训练结束后,生成的两个文件分别为<model_prefix>.model
和<model_prefix>.vocab
。vocab_size
:词汇表大小,默认为8000。注意训练完后不一定会严格等于这个大小,可开启hard_vocab_limit
来强行限制(事实上这个选项默认就是True
)。model_type
:选择哪一个模型。有unigram
(默认)、bpe
、char
、word
四种可选。如果选择word
,那么输入文件中的每一个句子必须是已经预分词的形态。character_coverage
:控制模型覆盖多少比例的字符。对于日文,中文基本字符比较多的语言,0.9995是一个不错的选择。对于英文这种基本字符比较少的语言,设置为1就行。默认为0.9995。max_sentence_length
:训练时每个句子在字节意义下的长度超过这个值就会被阶段。默认是4192。user_defined_symbols
:可以传入用户自定义的token。注意,这里不能包含unk_piece
,否则会报错。但可以包含bos_piece
等特殊词元,只不过会合二为一。- 特殊词元:可通过设置相应的id为 − 1 -1 −1 来关闭这个词元。注意
<unk>
必须存在。默认没有<pad>
词元。
如果将 bos_id
设为 − 1 -1 −1,那么 user_defined_symbols[0]
就会被放置在词汇表中索引为 1 1 1 的地方(因为 bos_id
原先是1),以此类推,从前往后填满所有的空缺位置。如果 bos_id
没有设为 − 1 -1 −1,但是 user_defined_symbols
中又含有 bos_piece
,那么 user_defined_symbols
中的 bos_piece
就会失效。
由此可知,.vocab
文件的前半部分由Special Tokens和User Defined Symbols组成,而后半部分就是模型训练过程中所产生的Subwords了。
我们可以观察词表中的前半部分是怎么排列的:
spm.SentencePieceTrainer.train(input='train.txt',model_prefix='m',vocab_size=16,user_defined_symbols=["<cls>", "<sep>", "<s>", "</s>", "<mask>"],unk_id=1,bos_id=3,eos_id=5,
)
词表:
<cls> 0
<unk> 0
<sep> 0
<s> 0
<mask> 0
</s> 0
▁ -1.4849
i -2.31823
s -2.31823
t -2.31823
. -3.31823
a -3.31823
e -3.31823
h -3.31823
x -3.31823
T -3.31823
5. 大小写相关问题
细心的读者可能已经发现了,明明 __init__.py
文件中定义的方法是按照驼峰命名法来命名的,为什么实际使用的时候可以采用蛇形命名法的方法呢?
如下展示了部分源码:
def EncodeAsPieces(self, input, **kwargs):return self.Encode(input=input, out_type=str, **kwargs)def EncodeAsIds(self, input, **kwargs):return self.Encode(input=input, out_type=int, **kwargs)
这是因为在 __init__.py
文件中的第 1020 行,SentencePiece
通过 _add_snake_case
函数在原有的基础上注入了蛇形命名法的方法:
def _add_snake_case(classname):# 定义一个名为 _add_snake_case 的函数,它接受一个类名 classname 作为参数。# 该函数的作用是将类中的驼峰命名法方法转化为蛇形命名法方法。snake_map = {}# 初始化一个空字典 snake_map,用来存储从驼峰命名法转换为蛇形命名法的键值对。for k, v in classname.__dict__.items():# 遍历类 classname 的 __dict__ 属性,该属性是类的字典,包含类中的所有属性(包括方法)。if re.match(r'^[A-Z]+', k):# 检查属性名 k 是否以大写字母开头,这是驼峰命名法方法的特征。snake = re.sub(r'(?<!^)(?=[A-Z])', '_', k).lower().replace('n_best', 'nbest')# 使用正则表达式将属性名中的大写字母前添加下划线(忽略第一个字母)。# 然后将结果转换为小写,形成蛇形命名法。# replace('n_best', 'nbest') 是特殊处理 n_best 这种命名情况,将其替换为 nbest。snake_map[snake] = v# 将转换后的蛇形命名方法名与对应的原始方法 v 存入 snake_map 字典。for k, v in snake_map.items():# 遍历 snake_map 字典的键值对。setattr(classname, k, v)# 使用 setattr 函数,将新的蛇形命名方法 k 赋值给类 classname,使其指向原始方法 v。_add_snake_case(SentencePieceProcessor)
_add_snake_case(SentencePieceTrainer)
6. 一些误区
SentencePiece并不是一种分词算法,而是一些分词算法的implementation,并在其基础上做了一些优化。还有一些其他的implementations,例如fastBPE,BlingFire等。
from tokenizers import SentencePieceBPETokenizertokenizer = SentencePieceBPETokenizer()
print(tokenizer.pre_tokenizer.pre_tokenize_str("こんにちは世界"))
print(tokenizer.pre_tokenizer.pre_tokenize_str("Hello world."))
输出:
[('▁こんにちは世界', (0, 7))]
[('▁Hello', (0, 5)), ('▁', (5, 6)), ('▁', (6, 7)), ('▁world.', (7, 14))]
对于不含空格的语种,例如日语,SentencePiece不会进行pre tokenize,而是将其视为1个token。
💻
pre_tokenize_str
的实现在 https://github.com/huggingface/tokenizers/blob/main/bindings/python/src/pre_tokenizers.rs#L173
在HuggingFace源码中,SentencePiece的pre_tokenizer是MetaSpace,它的核心逻辑如下:
def pre_tokenize(text: str) -> list:if not text:return []text = text.replace(' ', '▁')if not text.startswith('▁'):text = '▁' + texttokens = re.findall(r'▁[^▁]*|▁', text)return tokens
▁[^▁]*
:表示匹配以▁
开头,后面跟随零个或多个非▁
字符的子串,也就是单词前有▁
标记。|▁
:表示单独匹配一个▁
,即如果有连续的▁
,也会被单独作为一个 token 返回。
我们可以通过构造各种极端测试用例来验证它的正确性:
import re
from tokenizers.pre_tokenizers import Metaspacedef pre_tokenize_1(text: str) -> list:if not text:return []text = text.replace(' ', '▁')if not text.startswith('▁'):text = '▁' + texttokens = re.findall(r'▁[^▁]*|▁', text)return tokensdef pre_tokenize_2(text: str) -> list:global modelres = model.pre_tokenize_str(text)return [token[0] for token in res]model = Metaspace()test_cases = ["hello world","hello world"," leading spaces","trailing spaces ","multiple spaces in between","▁▁hello▁world","▁hello world▁▁","▁hello▁world▁",""," ","▁","▁ "," ▁"," ","▁▁▁▁","hello▁","▁▁hello▁▁world▁▁","a","▁▁","hello, world!","▁▁@hello▁#world$","▁▁(hello)▁[world]","1 2 3▁▁4 5","▁▁h3llo▁w0rld123▁","你好 世界","▁▁こんにちは▁世界","Привет▁мир","안녕하세요▁세계","👋🏽▁🌍","▁▁👋🏽▁🌍","Hello▁123▁!▁你好▁世界","▁▁H3llo▁▁123▁▁你好▁世界▁▁","▁▁▁▁▁▁", " ▁▁ ▁▁", "▁a▁b▁c▁d▁e▁f▁g▁", "▁▁▁▁▁abc", "▁▁▁▁▁▁▁", "你好世界▁▁▁▁▁", "▁▁▁123▁456▁789▁", "This▁▁is▁▁▁▁an▁▁▁▁▁▁▁example", "▁▁▁▁▁▁🌟🌟🌟▁🌟▁",
]def compare_and_validate_tokenizers(test_cases, verbose=False):for i, text in enumerate(test_cases):tokens_1 = pre_tokenize_1(text)tokens_2 = pre_tokenize_2(text)if verbose:print(f"Test case {i+1}: '{text}'")print(f"pre_tokenize_1: {tokens_1}")print(f"pre_tokenize_2: {tokens_2}")if tokens_1 != tokens_2:if verbose:print("❌ Results differ!\n")print(f"Difference: \n pre_tokenize_1: {tokens_1}\n pre_tokenize_2: {tokens_2}\n")raise AssertionError(f"Test case {i+1} failed: '{text}'\n pre_tokenize_1: {tokens_1}\n pre_tokenize_2: {tokens_2}")elif verbose:print("✅ Both methods produce the same result.\n")print("All test cases passed!")compare_and_validate_tokenizers(test_cases, verbose=True)
Ref
[1] https://www.reddit.com/r/MachineLearning/comments/rprmq3/d_sentencepiece_wordpiece_bpe_which_tokenizer_is/
[2] https://arxiv.org/pdf/1808.06226
相关文章:
一文搞懂SentencePiece的使用
目录 1. 什么是 SentencePiece?2. SentencePiece 基础概念2.1 SentencePiece 的工作原理2.2 SentencePiece 的优点 3. SentencePiece 的使用3.1 安装 SentencePiece3.2 训练模型与加载模型3.3 encode(高频)3.4 decode(高频&#x…...
一个简单的摄像头应用程序1
这个Python脚本实现了一个基于OpenCV的简单摄像头应用,我们在原有的基础上增加了录制视频等功能,用户可以通过该应用进行拍照、录制视频,并查看已拍摄的照片。以下是该脚本的主要功能和一些使用时需要注意的事项: 功能 拍照: 用户可以通过点击界面上的“拍照”按钮或按…...

通过PHP获取商品详情
在电子商务的浪潮中,数据的重要性不言而喻。商品详情信息对于电商运营者来说尤为宝贵。PHP,作为一种广泛应用的服务器端脚本语言,为我们提供了获取商品详情的便捷途径。 了解API接口文档 开放平台提供了详细的API接口文档。你需要熟悉商品详…...

【Android】获取备案所需的公钥以及签名MD5值
目录 重要前提 获取签名MD5值 获取公钥 重要前提 生成jks文件以及gradle配置应用该文件。具体步骤请参考我这篇文章:【Android】配置Gradle打包apk的环境_generate signed bundle or apk-CSDN博客 你只需要从头看到该文章的配置build.gradle(app&…...

看480p、720p、1080p、2k、4k、视频一般需要多大带宽呢?
看视频都喜欢看高清,那么一般来说看电影不卡顿需要多大带宽呢? 以4K为例,这里引用一位网友的回答:“视频分辨率4092*2160,每个像素用红蓝绿三个256色(8bit)的数据表示,视频帧数为60fps,那么一秒钟画面的数据量是:4096*2160*3*8*60≈11.9Gbps。此外声音大概是视频数据量…...
解决IDEA中@Autowired红色报错的实用指南:原因与解决方案
前言: 在使用Spring Boot开发时,Autowired注解是实现依赖注入的常用方式。然而,许多开发者在IDEA中使用Autowired时,可能会遇到红色报错,导致代码的可读性降低。本文将探讨导致这种现象的原因,并提供几种解…...
408知识点自检(一)
一、细节题 虚电路是面向连接的吗?虚电路线路上会不会有其他虚电路通过?虚电路适合什么类型的数据交换?虚电路的可靠性靠其他协议还是自己?固态硬盘的优势体现在什么存取方式?中断向量地址是谁的地址?多播…...

负载均衡--相关面试题(六)
在负载均衡的面试中,可能会遇到一系列涉及概念、原理、实践应用以及技术细节的问题。以下是一些常见的负载均衡面试题及其详细解答: 一、什么是负载均衡? 回答:负载均衡是一种将网络请求或数据传输工作分配给多个服务器或网络资源…...

【Unity踩坑】Unity更新Google Play结算库
一、问题描述: 在Google Play上提交了app bundle后,提示如下错误。 我使用的是Unity 2022.01.20f1,看来用的Play结算库版本是4.0 查了一下文档,Google Play结算库的维护周期是两年。现在需要更新到至少6.0。 二、更新过程 1. 下…...

Redis:hash类型
Redis:hash类型 hash命令设置与读取HSETHGETHMGET 哈希操作HEXISTSHDELHKEYSHVALSHGETALLHLENHSETNXHINCRBYHINCRBYFLOAT 内部编码ziplisthashtable 目前主流的编程语言中,几乎都提供了哈希表相关的容器,Redis自然也会支持对应的内容…...
力扣9.30
1749. 任意子数组和的绝对值的最大值 给你一个整数数组 nums 。一个子数组 [numsl, numsl1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl numsl1 ... numsr-1 numsr) 。 请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),…...

kafka下载配置
下载安装 参开kafka社区 zookeeperkafka消息队列群集部署https://apache.csdn.net/66c958fb10164416336632c3.html 下载 kafka_2.12-3.2.0安装包快速下载地址分享 官网下载链接地址: 官网下载地址:https://kafka.apache.org/downloads 官网呢下载慢…...

nlp任务之预测中间词-huggingface
目录 1.加载编码器 1.1编码试算 2.加载数据集 3.数据集处理 3.1 map映射:只对数据集中的sentence数据进行编码 3.2用filter()过滤 单词太少的句子过滤掉 3.3截断句子 4.创建数据加载器Dataloader 5. 下游任务模型 6.测试预测代码 7.训练代码 8.保…...

《程序猿之Redis缓存实战 · Redis 与数据库一致性》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
【无标题】observer: error while loading shared libraries: libmariadb.so.3处理办法
文章目录 1.记录新装的oceanbase,使用observer帮助时,出现lib文件无法找到的处理过程 ./observer --help ./observer: error while loading shared libraries: libmariadb.so.3: cannot open shared object file: No such file or directory2.做一个strace跟踪&…...
极客兔兔Gee-Cache Day1
极客兔兔7Days GeeCache - Day1 interface{}:任意类型 缓存击穿:一个高并发的请求查询一个缓存中不存在的数据项,因此这个请求穿透缓存直接到达后端数据库或数据源来获取数据。如果这种请求非常频繁,就会导致后端系统的负载突然…...

[MAUI]数据绑定和MVVM:MVVM的属性验证
一、MVVM的属性验证案例 Toolkit.Mvvm框架中的ObservableValidator类,提供了属性验证功能,可以使用我们熟悉的验证特性对属性的值进行验证,并将错误属性提取和反馈给UI层。以下案例实现对UI层的姓名和年龄两个输入框,进行表单提交验证。实现效果如下所示 View<ContentP…...
2024年水利水电安全员考试题库及答案
一、判断题 1.采用水下钻孔爆破方案时,侧面应采用预裂爆破,并严格控制单响药量以保护附近建(构)筑物的安全。 答案:正确 2.围堰爆破拆除工程的实施应成立爆破指挥机构,并应按设计确定的安全距离设置警戒。…...
【快速删除 node_modules 】rimraf
目录 1. 什么是node_modules 2. 卸载一个npm包 3. 删除 node_modules 为什么这么慢 4. rimraf 5. 为什么rimraf 这么快 作为前端开发,无论我们关注不关注,每天都能接触到node_modules。通常产生于一个npm install命令,之后就不会多加关注…...

毕业设计选题:基于ssm+vue+uniapp的教学辅助小程序
开发语言:Java框架:ssmuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:M…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...