【NLP 17、NLP的基础——分词】

我始终相信,世间所有的安排都有它的道理;失之东隅,收之桑榆

                                                                                        —— 24.12.20

一、中文分词的介绍

1.为什么讲分词?

① 分词是一个被长期研究的任务,通过了解分词算法的发展,可以看到NLP的研究历程

② 分词是NLP中一类问题的代表

③ 分词很常用,很多NLP任务建立在分词之上

2.中文分词的难点

歧义切分

① 南京市长江大桥     ② 欢迎新老师生前来就餐        ③ 无线电法国别研究        ④ 乒乓球拍卖完了

新词/专用名词/改造词等

① 九漏鱼      ② 活性位点、受体剪切位点      ③ 虽迟但到、十动然拒


二、中文分词的方法

1.正向最大匹配

分词步骤:

① 收集一个词表

② 对于一个待分词的字符串,从前向后寻找最长的,在此表中出现的词,在词边界做切分

③ 从切分处重复步骤②,直到字符串末尾


实现方式一

1.找出词表中最大词长度

2.从字符串开头开始选取最大词长度的窗口,检查窗口内的词是否在词表中

3.如果在词表中,在词边界处进行切分,之后移动到词边界处,重复步骤2

4.如果不在词表中,窗口右边界回退一个字符,之后检查窗口词是否在词表中

#分词方法:最大正向切分的第一种实现方式import re
import time#加载词典
def load_word_dict(path):max_word_length = 0word_dict = {}  #用set也是可以的。用list会很慢with open(path, encoding="utf8") as f:for line in f:word = line.split()[0]word_dict[word] = 0# 记录词的最大长度max_word_length = max(max_word_length, len(word))return word_dict, max_word_length#先确定最大词长度
#从长向短查找是否有匹配的词
#找到后移动窗口
def cut_method1(string, word_dict, max_len):words = []while string != '':lens = min(max_len, len(string))word = string[:lens]while word not in word_dict:if len(word) == 1:break#     词右边位置回退一位word = word[:len(word) - 1]words.append(word)string = string[len(word):]return words#cut_method是切割函数
#output_path是输出路径
def main(cut_method, input_path, output_path):word_dict, max_word_length = load_word_dict("dict.txt")writer = open(output_path, "w", encoding="utf8")start_time = time.time()with open(input_path, encoding="utf8") as f:for line in f:words = cut_method(line.strip(), word_dict, max_word_length)writer.write(" / ".join(words) + "\n")writer.close()print("耗时:", time.time() - start_time)returnstring = "测试字符串"
string1 = "王羲之草书《平安帖》共有九行"
string2 = "你到很多有钱人家里去看"
string3 = "金鹏期货北京海鹰路营业部总经理陈旭指出"
string4 = "伴随着优雅的西洋乐"
string5 = "非常的幸运"
word_dict, max_len = load_word_dict("dict.txt")
print(cut_method1(string, word_dict, max_len))
print(cut_method1(string1, word_dict, max_len))
print(cut_method1(string2, word_dict, max_len))
print(cut_method1(string3, word_dict, max_len))
print(cut_method1(string4, word_dict, max_len))
print(cut_method1(string5, word_dict, max_len))main(cut_method1, "corpus.txt", "cut_method1_output.txt")


实现方式二        利用前缀字典

1.从前向后进行查找

2.如果窗口内的词是一个词前缀则继续扩大窗口

3.如果窗口内的词不是一个词前缀,则记录已发现的词,并将窗口移动到词边界

4.相较于第一种方式,查找次数少很多,这就是其效率要大于第一种方式的原因,本质上是采取用更多的内存存储的前缀词典,来减少查询的次数,本质上采用空间换时间的思想

利用前缀词典,对遍历到的每一个词都进行判断,判断是否是一个词或是词的前缀,记录为1 / 0,如果遍历到的既不是一个词也不是一个词的前缀,则在当前这个词之前停下来,进行分词

#分词方法最大正向切分的第二种实现方式import re
import time
import json#加载词前缀词典
#用0和1来区分是前缀还是真词
#需要注意有的词的前缀也是真词,在记录时不要互相覆盖
def load_prefix_word_dict(path):prefix_dict = {}with open(path, encoding="utf8") as f:for line in f:word = line.split()[0]for i in range(1, len(word)):if word[:i] not in prefix_dict: #不能用前缀覆盖词prefix_dict[word[:i]] = 0  #前缀prefix_dict[word] = 1  #词return prefix_dict#输入字符串和字典,返回词的列表
def cut_method2(string, prefix_dict):if string == "":return []words = []  # 准备用于放入切好的词start_index, end_index = 0, 1  #记录窗口的起始位置window = string[start_index:end_index] #从第一个字开始find_word = window  # 将第一个字先当做默认词while start_index < len(string):#窗口没有在词典里出现if window not in prefix_dict or end_index > len(string):words.append(find_word)  #记录找到的词start_index += len(find_word)  #更新起点的位置end_index = start_index + 1window = string[start_index:end_index]  #从新的位置开始一个字一个字向后找find_word = window#窗口是一个词elif prefix_dict[window] == 1:find_word = window  #查找到了一个词,还要在看有没有比他更长的词end_index += 1window = string[start_index:end_index]#窗口是一个前缀elif prefix_dict[window] == 0:end_index += 1window = string[start_index:end_index]#最后找到的window如果不在词典里,把单独的字加入切词结果if prefix_dict.get(window) != 1:words += list(window)else:words.append(window)return words#cut_method是切割函数
#output_path是输出路径
def main(cut_method, input_path, output_path):word_dict = load_prefix_word_dict("dict.txt")writer = open(output_path, "w", encoding="utf8")start_time = time.time()with open(input_path, encoding="utf8") as f:for line in f:words = cut_method(line.strip(), word_dict)writer.write(" / ".join(words) + "\n")writer.close()print("耗时:", time.time() - start_time)returnstring = "测试字符串"
string1 = "王羲之草书《平安帖》共有九行"
string2 = "你到很多有钱人家里去看"
string3 = "金鹏期货北京海鹰路营业部总经理陈旭指出"
string4 = "伴随着优雅的西洋乐"
string5 = "非常的幸运"
prefix_dict = load_prefix_word_dict("dict.txt")
print(cut_method2(string, prefix_dict))
print(cut_method2(string1, prefix_dict))
print(cut_method2(string2, prefix_dict))
print(cut_method2(string3, prefix_dict))
print(cut_method2(string4, prefix_dict))
print(cut_method2(string5, prefix_dict))
# print(json.dumps(prefix_dict, ensure_ascii=False, indent=2))
main(cut_method2, "corpus.txt", "cut_method2_output.txt")

​​​​​


3.反向最大匹配

从右向左进行,基于相同的词表切分出不同的结果

两者都依赖词表,都有多种实现方式


4.双向最大匹配

同时进行正向最大切分,和负向最大切分,之后比较两者结果,决定切分方式。

如何比较?

1.单字词:词表中可以有单字,从分词的角度,我们也会把它称为一个词

2.非字典词:未在词表中出现过的词,一般都会被分成单字

3.词总量:不同切分方法得到的词数可能不同

分析:我们一般认为,把词语切的很碎,分词后词的总量越少,单字字典词的数量越少,分词的效果越好

我们在野生动物园玩

词表:我们、在野、生动、动物、野生动物园、野生、动物园、在、玩、园

正向最大匹配法: “我们 / 在野 / 生动 / 物 / 园 / 玩”

        词典词3个,单字字典词为2,非词典词为1。

逆向最大匹配法: “我们 / 在 / 野生动物园 / 玩”

        词典词2个,单字字典词为2,非词典词为0。


5.jieba分词

jieba全切分词表

计算哪种切分方式总词频最高 词频事先根据分词后语料(语言材料)统计出来

依赖于一个全切分方式的词表,将基于词表的所有可能切分方式列出,计算哪种切分方式的总词频较高,把每个词的词频通过一份大的语料(语言材料)事先统计在一个文件中,然后计算哪种切分方式下的总词频最高


6.正向最大切分,负向最大切分,双向最大切分共同的缺点:

对词表极为依赖,如果没有词表,则无法进行;如果词表中缺少需要的词,结果也不会正确

② 切分过程中不会关注整个句子表达的意思,只会将句子看成一个个片段

③ 如果文本中出现一定的错别字,会造成一连串影响

④ 对于人名等的无法枚举实体词无法有效的处理


三、基于机器学习的中文分词

1.分词任务转化为机器学习任务

如果想要对一句话进行分词,我们需要对于每一个字,知道它是不是一个词的边界,用一个0 / 1 序列来表示某一文字是不是词边界

建立一个模型,通过这个模型对一句话中每一个字进行判断是否是词边界,并用0 / 1 表示,这种规律可以用神经网络学习

问题转化为:

对于句子中的每一个字,进行二分类判断正类表示这句话中,它是词边界负类表示它不是词边界

标注数据、训练模型,使模型可以完成上述判断,那么这个模型,可以称为一个分词模型

神经网络训练完成分词任务可以看作是一个序列标注问题,能够使分词任务脱离对于词表的依赖


2.构建模型

embedding嵌入层 + rnn + 线性层 + 交叉熵损失函数

① 继承父类

调用父类nn.Module的初始化方法。

② 嵌入层

创建一个嵌入层初始化nn.Embedding,将词汇表中的每个词映射为固定维度的向量。

③ RNN层

创建一个多层RNN层初始化nn.RNN,用于处理序列数据。

④ 线性层

创建一个线性层初始化nn.Linear,将RNN输出映射到二维概率分布。

⑤ 损失函数

定义交叉熵损失函数初始化nn.CrossEntropyLoss,并设置忽略padding位置的标签。

ignore_index:配合padding使用,设置padding位置值的标签label。

class TorchModel(nn.Module):def __init__(self, input_dim, hidden_size, num_rnn_layers, vocab):super(TorchModel, self).__init__()# embedding + rnn + 线性层 + 交叉熵self.embedding = nn.Embedding(len(vocab) + 1, input_dim, padding_idx=0) #shape=(vocab_size, dim)self.rnn_layer = nn.RNN(input_size=input_dim,hidden_size=hidden_size,batch_first=True,# 设置rnn的层数,两层rnnnum_layers=num_rnn_layers,)# 线性层,把每个 hidden_size 映射到二维的概率分布self.classify = nn.Linear(hidden_size, 2)  # w = hidden_size * 2# ignore_index:配合padding使用,padding位置值的标签label是-100self.loss_func = nn.CrossEntropyLoss(ignore_index=-100)

3.前向计算

① 输入嵌入

x = self.embedding(x):输入 x 通过嵌入层转换为嵌入向量

输入形状:(batch_size, sen_len)

输出形状:(batch_size, sen_len, input_dim)

② RNN层处理:

x, _ = self.rnn_layer(x):嵌入后的输入通过RNN层处理

输出形状:(batch_size, sen_len, hidden_size)

③ 分类

y_pred = self.classify(x):RNN层的输出通过分类层进行分类

输出形状:(batch_size, sen_len, 2),重塑为:(batch_size * sen_len, 2)

④ 损失计算

if y is not None:如果提供了标签 y,则计算交叉熵损失

计算公式:self.loss_func(y_pred.reshape(-1, 2), y.view(-1)),返回损失值。

⑤ 返回预测结果:

else:如果没有提供标签 y,则直接返回预测结果 y_pred

view(): view 函数主要用于改变张量(Tensor)的维度,也就是对张量进行形状重塑(reshape),但不会改变张量中元素的存储顺序和数量

    def forward(self, x, y=None):x = self.embedding(x)  #input shape: (batch_size, sen_len), output shape:(batch_size, sen_len, input_dim)x, _ = self.rnn_layer(x)  #output shape:(batch_size, sen_len, hidden_size)  y_pred = self.classify(x)   #output shape:(batch_size, sen_len, 2) -> y_pred.view(-1, 2) (batch_size*sen_len, 2)if y is not None:#cross entropy#y_pred : n, class_num    [[1,2,3], [3,2,1]]#y      : n               [0,       1      ]#y:batch_size, sen_len  = 2 * 5#[[0,0,1,0,1],[0,1,0, -100, -100]]  y#[0,0,1,0,1,  0,1,0,-100.-100]    y.view(-1) shape= n = batch_size*sen_lenreturn self.loss_func(y_pred.reshape(-1, 2), y.view(-1))else:return y_pred

4.加载和处理数据集

① 初始化

__init__:初始化类的属性,包括词汇表 (vocab)、数据集路径 (corpus_path) 和最大序列长度 (max_length)。

调用 load 方法加载数据。

② 加载数据

load:从指定路径读取文本文件。

对每行文本进行处理:

        sentence_to_sequence:将句子转换为字符序列

        sequence_to_label:生成标签序列

        padding:对序列和标签进行填充或截断

        将处理后的序列和标签转换为 PyTorch 张量

        将处理后的数据存储在 self.data 列表中

为了减少训练时间,只加载前 10000 条数据。

③ 填充或截断序列         

padding():将序列和标签截断或补齐到固定长度 max_length。

序列不足的部分用 0 填充,标签不足的部分用 -100 填充。

④ 获取数据集长度

__len__:返回数据集的长度,即 self.data 列表的长度。

⑤ 获取数据项

__getitem__:根据索引返回数据集中的一个数据项。

class Dataset:def __init__(self, corpus_path, vocab, max_length):self.vocab = vocabself.corpus_path = corpus_pathself.max_length = max_lengthself.load()def load(self):self.data = []with open(self.corpus_path, encoding="utf8") as f:for line in f:sequence = sentence_to_sequence(line, self.vocab)label = sequence_to_label(line)sequence, label = self.padding(sequence, label)sequence = torch.LongTensor(sequence)label = torch.LongTensor(label)self.data.append([sequence, label])#使用部分数据做展示,使用全部数据训练时间会相应变长if len(self.data) > 10000:break#将文本截断或补齐到固定长度def padding(self, sequence, label):sequence = sequence[:self.max_length]sequence += [0] * (self.max_length - len(sequence))label = label[:self.max_length]label += [-100] * (self.max_length - len(label))return sequence, labeldef __len__(self):return len(self.data)def __getitem__(self, item):return self.data[item]

5.数据集处理

将一个文本句子转换为字符串

① 遍历句子

遍历输入的句子中的每个字符。

② 设定每个字的索引值

对于每个字符,使用字典 vocab 查找对应的索引值。如果字符不在字典中,则使用 vocab['unk'] 作为默认值。

③ 返回结果

将所有查找结果组成一个列表并返回

#文本转化为数字序列,为embedding做准备
def sentence_to_sequence(sentence, vocab):sequence = [vocab.get(char, vocab['unk']) for char in sentence]return sequence

④ 设置分词结果标注

1.分词得到词语列表

使用 jieba.lcut 对句子进行分词,得到词语列表

2.初始化标签列表

初始化一个与句子长度相同的全零标签列表

3.更新标签列表

遍历词语列表,更新标签列表中每个词语最后一个字符的位置为1

4.返回标签列表

返回最终的标签列表。

#基于jieba词表生成分级结果的标注
def sequence_to_label(sentence):words = jieba.lcut(sentence)label = [0] * len(sentence)pointer = 0for word in words:pointer += len(word)label[pointer - 1] = 1return label

⑤ 加载字表

enumerate(): 是 Python 内置的一个函数,它的主要作用是在遍历可迭代对象(如列表、元组、字符串等)时,同时获取元素的索引和对应的值,通常用于需要知道元素在序列中的位置信息的情况。这可以让代码在迭代过程中更加简洁和方便,避免了手动去维护一个索引变量来记录位置。

strip():是字符串的一个方法,用于去除字符串开头和结尾的空白字符。空白字符包括空格、制表符(\t)、换行符(\n)等。

#加载字表
def build_vocab(vocab_path):vocab = {}with open(vocab_path, "r", encoding="utf8") as f:for index, line in enumerate(f):char = line.strip()vocab[char] = index + 1   #每个字对应一个序号vocab['unk'] = len(vocab) + 1return vocab

⑥ 建立数据集

1.创建数据集对象

DataSet 类:通常作为一个抽象基类(在一些库中实现了基本功能框架,但常需继承扩展使用),用于对数据集进行封装和管理,将数据以及对应的标签(在有监督学习场景下)等信息以一种规范、便于操作的方式组织起来,方便后续传递给数据加载器(如 DataLoader),进而提供给模型进行训练、验证或测试等操作。

2.创建数据加载器

DataLoader类:是一个非常重要的数据加载工具。它的主要作用是将数据集(通常是继承自DataSet类的自定义数据集)按照指定的方式(如批次大小、是否打乱数据等)加载数据,使得数据能够以合适的格式和顺序高效地提供给模型进行训练、验证或测试。

3.返回数据加载器

返回搭建好的data_loader

#建立数据集
def build_dataset(corpus_path, vocab, max_length, batch_size):dataset = Dataset(corpus_path, vocab, max_length) #diy __len__ __getitem__# DataLoader:python专门用于读取数据的类,用指定的batch_size为一组对样本数据进行打乱data_loader = DataLoader(dataset, shuffle=True, batch_size=batch_size) #torchreturn data_loader

6.模型训练

① 初始化参数

设置训练相关的超参数:训练轮数、每次训练的样本个数、每个字的维度、隐含层的维度、rnn的层数、样本最大长度、学习率、子表文件路径和语料文件路径 

② 建立字表和数据集

调用build_vocab函数生成字符到索引的映射

调用build_dataset函数构建数据加载器

③ 创建模型和优化器

实例化第2步中构建的模型

实例化Adam优化器

④ 训练模型

在指定轮数内进行训练,每轮按批次处理数据,计算损失并更新模型参数

⑤ 保存模型

训练结束后保存模型参数

def main():epoch_num = 5        #训练轮数batch_size = 20       #每次训练样本个数char_dim = 50         #每个字的维度hidden_size = 100     #隐含层维度num_rnn_layers = 1    #rnn层数max_length = 20       #样本最大长度learning_rate = 1e-3  #学习率vocab_path = "chars.txt"  #字表文件路径corpus_path = "../corpus.txt"  #语料文件路径vocab = build_vocab(vocab_path)       #建立字表data_loader = build_dataset(corpus_path, vocab, max_length, batch_size)  #建立数据集model = TorchModel(char_dim, hidden_size, num_rnn_layers, vocab)   #建立模型optim = torch.optim.Adam(model.parameters(), lr=learning_rate)     #建立优化器#训练开始for epoch in range(epoch_num):model.train()watch_loss = []# 按照batch_size数量的数据为一组,按组进行训练for x, y in data_loader:optim.zero_grad()    #梯度归零loss = model.forward(x, y)   #计算lossloss.backward()      #计算梯度optim.step()         #更新权重watch_loss.append(loss.item())print("=========\n第%d轮平均loss:%f" % (epoch + 1, np.mean(watch_loss)))#保存模型torch.save(model.state_dict(), "model.pth")return
if __name__ == "__main__":main()


7.用模型进行预测

① 初始化配置

设置字符维度、隐含层维度和RNN层数

② 建立词汇表

调用 build_vocab 函数读取词汇表文件并生成字典

③ 加载模型

创建模型实例并加载训练好的权重

load_state_dict(): 是在深度学习框架(如 PyTorch)中常用的一个方法,它主要用于加载模型的参数状态字典(state dictionary),使得模型能够恢复到之前保存的某个状态(例如之前训练好的状态,或者训练过程中某个特定阶段的状态),便于继续训练、进行模型评估或者在实际应用中使用已训练好的模型进行推理(预测)等操作。

④ 逐条预测

model.eval():将模型设置为评估模式禁用 dropout 等训练时的行为

with torch.no_grad():不计算梯度,提高推理速度并减少内存消耗,然后对生成的测试数据进行预测

⑤ 输出预测结果

对每个输入字符串进行编码,通过模型预测,并根据预测结果切分字符串后输出

#最终预测
def predict(model_path, vocab_path, input_strings):#配置保持和训练时一致char_dim = 50  # 每个字的维度hidden_size = 100  # 隐含层维度num_rnn_layers = 1  # rnn层数vocab = build_vocab(vocab_path)       #建立字表model = TorchModel(char_dim, hidden_size, num_rnn_layers, vocab)   #建立模型model.load_state_dict(torch.load(model_path, weights_only=True))   #加载训练好的模型权重model.eval()for input_string in input_strings:#逐条预测x = sentence_to_sequence(input_string, vocab)with torch.no_grad():result = model.forward(torch.LongTensor([x]))[0]result = torch.argmax(result, dim=-1)  #预测出的01序列#在预测为1的地方切分,将切分后文本打印出来for index, p in enumerate(result):if p == 1:print(input_string[index], end=" ")else:print(input_string[index], end="")print()if __name__ == "__main__":input_strings = ["同时国内有望出台新汽车刺激方案","沪胶后市有望延续强势","经过两个交易日的强势调整后","昨日上海天然橡胶期货价格再度大幅上扬","妈妈,我想你了"]predict("model.pth", "chars.txt", input_strings)

 

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

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

相关文章

Rust 在前端基建中的使用

摘要 随着前端技术的不断发展&#xff0c;前端基础设施&#xff08;前端基建&#xff09;的建设已成为提升开发效率、保障产品质量的关键环节。然而&#xff0c;在应对复杂业务场景与高性能需求时&#xff0c;传统的前端技术栈逐渐暴露出诸多不足。近年来&#xff0c;Rust语言…

谷歌浏览器的网络连接问题解决方案

在数字化时代&#xff0c;网络浏览器已成为日常工作和生活中不可或缺的工具。谷歌浏览器以其快速、稳定和丰富的功能深受用户喜爱。然而&#xff0c;就像其他软件一样&#xff0c;谷歌浏览器也可能遇到网络连接问题&#xff0c;这可能由多种因素引起。本文将为您提供一系列解决…

【Unity3D】Particle粒子特效或3D物体显示在UGUI上的方案

目录 一、RawImage Camera RenderTexture方式 &#xff08;1&#xff09;扩展知识&#xff1a;实现射线检测RawImage内的3D物体 &#xff08;2&#xff09;扩展知识&#xff1a;实现粒子特效显示RawImage上 二、UI摄像机 Canvas(Screen Space - Camera模式)方式 &#…

14-zookeeper环境搭建

0、环境 java&#xff1a;1.8zookeeper&#xff1a;3.5.6 1、下载 zookeeper下载点击这里。 2、安装 下载完成后解压&#xff0c;放到你想放的目录里。先看一下zookeeper的目录结构&#xff0c;如下图&#xff1a; 进入conf目录&#xff0c;复制zoo_sample.cfg&#xff0…

精准提升:从94.5%到99.4%——目标检测调优全纪录

&#x1f680; 目标检测模型调优过程记录 在进行目标检测模型的训练过程中&#xff0c;我们面对了许多挑战与迭代。从初始模型的训练结果到最终的调优优化&#xff0c;每一步的实验和调整都有其独特的思路和收获。本文记录了我在优化目标检测模型的过程中进行的几次尝试&#…

贪心算法(三)

目录 一、k次取反后最大化的数组和 二、优势洗牌 三、最长回文串 四、增减字符串匹配 一、k次取反后最大化的数组和 k次取反后最大化的数组和 贪心策略&#xff1a; 解题代码&#xff1a; class Solution { public:int largestSumAfterKNegations(vector<int>&am…

基于Springboot的在线问卷调查系统【附源码】

基于Springboot的在线问卷调查系统 效果如下&#xff1a; 系统主页面 问卷列表页面 个人中心页面 系统登陆页面 管理员主页面 问卷管理页面 研究背景 随着互联网技术的飞速发展&#xff0c;传统的问卷调查方式因其时间和地点的限制&#xff0c;难以高效地收集到足够的数据。…

Python选择题训练工具:高效学习、答题回顾与音频朗读一站式体验

一、引言 随着人工智能技术的不断进步&#xff0c;传统的教学方式已经逐渐向智能化、互动化转变。在众多英语测试题型中&#xff0c;选择题作为一种高效的方式被广泛应用于各类培训与考试中。为了帮助学生高效学习与自测&#xff0c;本篇文章将采用Python编写一款基于 Python …

《三角洲行动》游戏运行时提示“缺失kernel32.dll”:问题解析与解决方案

《三角洲行动》游戏运行时提示“缺失kernel32.dll”&#xff1a;问题解析与解决方案 作为软件开发领域的一名从业者&#xff0c;我深知电脑游戏运行过程中可能遇到的各种挑战&#xff0c;尤其是文件丢失、文件损坏以及系统报错等问题。今天&#xff0c;我将以经典游戏《三角洲…

【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理

文章目录 一、软件下载安装1、Unity官网2、下载Unity Hub 二、修改Unity Hub配置1、设置Unity Hub中文语言2、修改默认存储目录 三、安装unity编辑器1、点击安装编辑器2、版本选择3、关于版本号4、安装模块选择5、等待下载完成自动安装即可6、追加unity和模块 四、许可证管理专…

AtCoder Beginner Contest 385(A~F)题解

A - Equally 思路&#xff1a;由题可知最多只能分成三组&#xff0c;我们只需要判断是否三个数都相等&#xff0c;或者两个数相加等于另外一个数即可 #include<bits/stdc.h> using namespace std; #define int long long int n; string s; int a,b,c; signed main() {ci…

STM32串口第一次接收数据时第一个字节丢失的问题

解决方法&#xff1a;开启中断之前&#xff0c;先清除标志位【1】。 串口清除标志位&#xff1a; __HAL_UART_CLEAR_PEFLAG(&huart1); HAL_UART_Receive_IT(&huart1,&RxUart, 1); 定时器清除标志位&#xff1a; __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);…

Unity3d 基于UGUI和VideoPlayer 实现一个多功能视频播放器功能(含源码)

前言 随着Unity3d引擎在数字沙盘、智慧工厂、数字孪生等场景的广泛应用&#xff0c;视频已成为系统程序中展示时&#xff0c;不可或缺的一部分。在 Unity3d 中&#xff0c;我们可以通过强大的 VideoPlayer 组件和灵活的 UGUI 系统&#xff0c;将视频播放功能无缝集成到用户界面…

第22天:信息收集-Web应用各语言框架安全组件联动系统数据特征人工分析识别项目

#知识点 1、信息收集-Web应用-开发框架-识别安全 2、信息收集-Web应用-安全组件-特征分析 一、ICO图标&#xff1a; 1、某个应用系统的标示&#xff0c;如若依系统有自己特点的图标&#xff1b;一旦该系统出问题&#xff0c;使用该系统的网站都会受到影响&#xff1b; 2、某个公…

我的 2024 年终总结

2024 年&#xff0c;我离开了待了两年的互联网公司&#xff0c;来到了一家聚焦教育机器人和激光切割机的公司&#xff0c;没错&#xff0c;是一家硬件公司&#xff0c;从未接触过的领域&#xff0c;但这还不是我今年最重要的里程碑事件 5 月份的时候&#xff0c;正式提出了离职…

acme ssl证书自动续签 nginx

参考 github 官方操作 &#xff0c;acme操作说明 说下我的操作 安装 acme.sh curl https://get.acme.sh | sh source ~/.bashrc 2.注册 acme.sh --register-account -m 123qq.com 如果你在配置 acme.sh 时选择了其他 CA&#xff08;如 Let’s Encrypt&#xff09;&#xff…

sentinel学习笔记6-限流降级(上)

本文属于sentinel学习笔记系列。网上看到吴就业老师的专栏&#xff0c;写的好值得推荐&#xff0c;我整理的有所删减&#xff0c;推荐看原文。 https://blog.csdn.net/baidu_28523317/category_10400605.html sentinel 实现限流降级、熔断降级、黑白名单限流降级、系统自适应…

简单了解函数递归

函数递归 一 了解函数递归二 深入理解函数递归的思想三 函数递归的优缺点 一 了解函数递归 首先&#xff0c;我们通过一个简单的代码来理解函数递归。 #include<stdio.h> int Func() {return Func(n1); } int main() {int n 5;Func(n);return 0; }这个就是函数递归&am…

QT的前景与互联网岗位发展

qt是用来干什么的 --》桌面应用开发&#xff08;做电脑的应用程序&#xff0c;面对客户端&#xff09;。 主要用于开发跨平台的应用程序和用户界面&#xff08;UI&#xff09;。它是一个全面的C库集合&#xff0c;提供了构建软件应用所需的各种工具和功能。 客户端开发的重…

重温设计模式--2、设计模式七大原则

文章目录 1、开闭原则&#xff08;Open - Closed Principle&#xff0c;OCP&#xff09;定义&#xff1a;示例&#xff1a;好处&#xff1a; 2、里氏替换原则&#xff08;Liskov Substitution Principle&#xff0c;LSP&#xff09;定义&#xff1a;示例&#xff1a;好处&#…