一文搞懂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…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...
ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
