NLP_Seq2Seq编码器-解码器架构

文章目录

  • Seq2Seq架构
  • 构建简单Seq2Seq架构
    • 1.构建实验语料库和词汇表
    • 2.生成Seq2Seq训练数据
    • 3. 定义编码器和解码器类
    • 4.定义Seq2Seq架构
    • 5. 训练Seq2Seq架构
    • 6.测试Seq2Seq架构
  • 归纳
  • Seq2Seq编码器-解码器架构小结


Seq2Seq架构

起初,人们尝试使用一个独立的RNN来解决这种序列到序列的NLP任务,但发现效果并不理想。这是因为RNN 在同时处理输入和输出序列(既负责编码又负责解码)时,容易出现信息损失。而Seq2Seq架构通过编码器(Encoder)和解码器(Decoder)来分离对输入和输出序列的处理,即在编码器和解码器中,分别嵌入相互独立的 RNN(见下图),这样就有效地解决了编解码过程中的信息损失问题。
在这里插入图片描述

Seq2Seq架构,就是将编码器的输入序列转换成一个固定大小的向量表示,然后将该向量表示转换成解码器的输出序列。

注:编码器的输入序列和解码器的输出序列的长度可以是不同的。
在这里插入图片描述
图中,模型读取了一个输入的句子“咖哥很喜欢小冰”,并生成“Kage very likes XiaoBing”作为输出的句子。在输出句子的结束标记后,模型停止输出。编码器将输入序列编码成一个固定大小的向量表示,解码器再将这个向量表示解码成输出序列。在解码阶段,解码器在每个时间步生成一个输出符号,并将其作为下一个时间步的输入。这个过程实际上是自回归的,因为解码器在生成输出序列时依赖于先前生成的符号。

Seq2Seq架构的本质是一种对输入序列的压缩和对输出序列的解压缩过程。而这个压缩和解压缩的过程,可以通过RNN、LSTM或GRU等序列建模方法来实现。编码器使用RNN、LSTM或GRU等来处理输入序列,生成向量表示;解码器也使用RNN、LSTM或GRU等来处理向量表示,生成输出序列。

构建简单Seq2Seq架构

我们会在一个小型语料库上训练Seq2Seq架构,学习如何将一个中文句子翻译成对应的英文句子。

翻译架构的程序结构如下
在这里插入图片描述

1.构建实验语料库和词汇表

# 构建语料库,每行包含中文、英文(解码器输入)和翻译成英文后的目标输出 3 个句子
sentences = [
    ['哒哥 喜欢 爬山', '<sos> DaGe likes climb', 'DaGe likes climb <eos>'],
    ['我 爱 学习 人工智能', '<sos> I love studying AI', 'I love studying AI <eos>'],
    ['深度学习 改变 世界', '<sos> DL changed the world', 'DL changed the world <eos>'],
    ['自然 语言 处理 很 强大', '<sos> NLP is so powerful', 'NLP is so powerful <eos>'],
    ['神经网络 非常 复杂', '<sos> Neural-Nets are complex', 'Neural-Nets are complex <eos>']]
word_list_cn, word_list_en = [], []  # 初始化中英文词汇表
# 遍历每一个句子并将单词添加到词汇表中
for s in sentences:
    word_list_cn.extend(s[0].split())
    word_list_en.extend(s[1].split())
    word_list_en.extend(s[2].split())
# 去重,得到没有重复单词的词汇表
word_list_cn = list(set(word_list_cn))
word_list_en = list(set(word_list_en))
# 构建单词到索引的映射
word2idx_cn = {w: i for i, w in enumerate(word_list_cn)}
word2idx_en = {w: i for i, w in enumerate(word_list_en)}
# 构建索引到单词的映射
idx2word_cn = {i: w for i, w in enumerate(word_list_cn)}
idx2word_en = {i: w for i, w in enumerate(word_list_en)}
# 计算词汇表的大小
voc_size_cn = len(word_list_cn)
voc_size_en = len(word_list_en)
print(" 句子数量:", len(sentences)) # 打印句子数
print(" 中文词汇表大小:", voc_size_cn) # 打印中文词汇表大小
print(" 英文词汇表大小:", voc_size_en) # 打印英文词汇表大小
print(" 中文词汇到索引的字典:", word2idx_cn) # 打印中文词汇到索引的字典
print(" 英文词汇到索引的字典:", word2idx_en) # 打印英文词汇到索引的字典

在这里插入图片描述
这个语料库是专门为学习Seq2Seq 模型而创建的,每行包含3个句子。

  • 第一句(源语言):中文句子,作为输入序列提供给编码器。
  • 第二句(+目标语言):英文句子, 作为解码器的输入序列。句子以特殊的开始符号开头,表示句子的开始。符号有助于解码器学会在何时开始生成目标句子。
  • 第三句(目标语言+):也是英文句子,作为解码器的目标输出序列。句子以特殊的结束符号结尾,表示句子的结束。符号有助于解码器学会在何时结束目标句子的生成。

2.生成Seq2Seq训练数据

import numpy as np # 导入 numpy
import torch # 导入 torch
import random # 导入 random 库
# 定义一个函数,随机选择一个句子和词汇表生成输入、输出和目标数据
def make_data(sentences):
    # 随机选择一个句子进行训练
    random_sentence = random.choice(sentences)
    # 将输入句子中的单词转换为对应的索引
    encoder_input = np.array([[word2idx_cn[n] for n in random_sentence[0].split()]])
    # 将输出句子中的单词转换为对应的索引
    decoder_input = np.array([[word2idx_en[n] for n in random_sentence[1].split()]])
    # 将目标句子中的单词转换为对应的索引
    target = np.array([[word2idx_en[n] for n in random_sentence[2].split()]])
    # 将输入、输出和目标批次转换为 LongTensor
    encoder_input = torch.LongTensor(encoder_input)
    decoder_input = torch.LongTensor(decoder_input)
    target = torch.LongTensor(target)
    return encoder_input, decoder_input, target 
# 使用 make_data 函数生成输入、输出和目标张量
encoder_input, decoder_input, target = make_data(sentences)
for s in sentences: # 获取原始句子
    if all([word2idx_cn[w] in encoder_input[0] for w in s[0].split()]):
        original_sentence = s
        break
print(" 原始句子:", original_sentence) # 打印原始句子
print(" 编码器输入张量的形状:", encoder_input.shape)  # 打印输入张量形状
print(" 解码器输入张量的形状:", decoder_input.shape) # 打印输出张量形状
print(" 目标张量的形状:", target.shape) # 打印目标张量形状
print(" 编码器输入张量:", encoder_input) # 打印输入张量
print(" 解码器输入张量:", decoder_input) # 打印输出张量
print(" 目标张量:", target) # 打印目标张量

在这里插入图片描述

3. 定义编码器和解码器类

import torch.nn as nn # 导入 torch.nn 库
# 定义编码器类,继承自 nn.Module
class Encoder(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(Encoder, self).__init__()       
        self.hidden_size = hidden_size # 设置隐藏层大小       
        self.embedding = nn.Embedding(input_size, hidden_size) # 创建词嵌入层       
        self.rnn = nn.RNN(hidden_size, hidden_size, batch_first=True) # 创建 RNN 层    
    def forward(self, inputs, hidden): # 前向传播函数
        embedded = self.embedding(inputs) # 将输入转换为嵌入向量       
        output, hidden = self.rnn(embedded, hidden) # 将嵌入向量输入 RNN 层并获取输出
        return output, hidden
# 定义解码器类,继承自 nn.Module
class Decoder(nn.Module):
    def __init__(self, hidden_size, output_size):
        super(Decoder, self).__init__()       
        self.hidden_size = hidden_size # 设置隐藏层大小       
        self.embedding = nn.Embedding(output_size, hidden_size) # 创建词嵌入层
        self.rnn = nn.RNN(hidden_size, hidden_size, batch_first=True)  # 创建 RNN 层       
        self.out = nn.Linear(hidden_size, output_size) # 创建线性输出层    
    def forward(self, inputs, hidden):  # 前向传播函数     
        embedded = self.embedding(inputs) # 将输入转换为嵌入向量       
        output, hidden = self.rnn(embedded, hidden) # 将嵌入向量输入 RNN 层并获取输出       
        output = self.out(output) # 使用线性层生成最终输出
        return output, hidden
n_hidden = 128 # 设置隐藏层数量
# 创建编码器和解码器
encoder = Encoder(voc_size_cn, n_hidden)
decoder = Decoder(n_hidden, voc_size_en)
print(' 编码器结构:', encoder)  # 打印编码器的结构
print(' 解码器结构:', decoder)  # 打印解码器的结构

在这里插入图片描述
专注于编码器和解码器各自的功能,同时使代码更具可读性和可维护性。此外,替换不同的编码器和解码器也很方便,从而能够实现更多样化的模型结构。

可以调整的地方很多,比如通过调整input_size和output_size参数(即输入输出维度),可以使它们适应不同的源语言和目标语言的词汇表大小。又如RNN层的个数,当前我们只使用了一个RNN层,但根据需要,可以在RNN的构造函数中设置 num_layers参数,堆叠多个RNN层,增加模型的复杂性和容量。还可以使用其他类型的RNN层,如LSTM 层或GRU层,来替换RNN层,帮助模型更好地捕获长距离依赖关系。此外,也可以在RNN的构造函数中设置bidirectional=True参数,来使用双向 RNN 捕获输入序列中的前后信息。当然,使用双向RNN 时需要调整隐藏状态的形态以适应双向结构。

编码器和解码器有output、hidden两个输出的作用是什么?

  • output:每个时间步(每个输入的序列元素)的输出。对于一般的RNN来说,output通常就是hidden;但对于某些更复杂的模型,如LSTM来说, output可能会与 hidden有所不同。在你的代码中,output可以被视为对输入序列中每个元素的编码。
  • hidden:RNN的隐藏状态,保存了至当前步骤的所有历史信息。在标准的 RNN中,hidden状态由前一个时间步的hidden状态和当前时间步的输入共同决定。这种机制使得hidden状态能够捕获和记住序列的时间依赖性。

在Seq2Seq架构中,编码器的作用是将源语言句子编码成一个向量,而解码器则以此向量为输入,生成目标语言的句子。在这个过程中,编码器的hidden 状态被用作解码器的初始hidden 状态,作为对整个源语言句子的总结,被解码器用来生成第一个目标语言单词。

编码器的 output通常用于刚才我提到的注意力机制,这是一种在解码器生成每个单词时选择性地查看输入句子的不同部分的技术,可以帮助模型更好地处理长句子和复杂的语言结构。这个简单的Seq2Seq架构并没有真正意义上地使用到编码器的 output。

4.定义Seq2Seq架构

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder):
        super(Seq2Seq, self).__init__()
        # 初始化编码器和解码器
        self.encoder = encoder
        self.decoder = decoder
    def forward(self, enc_input, hidden, dec_input):    # 定义前向传播函数
        # 使输入序列通过编码器并获取输出和隐藏状态
        encoder_output, encoder_hidden = self.encoder(enc_input, hidden)
        # 将编码器的隐藏状态传递给解码器作为初始隐藏状态
        decoder_hidden = encoder_hidden
        # 使解码器输入(目标序列)通过解码器并获取输出
        decoder_output, _ = self.decoder(dec_input, decoder_hidden)
        return decoder_output
# 创建 Seq2Seq 架构
model = Seq2Seq(encoder, decoder)
print('S2S 模型结构:', model)  # 打印模型的结构

在这里插入图片描述
这段代码定义了一个类,用于处理输入序列并生成输出序列。这个类继承自PyTorch的nn.Module,使其成为一个自定义的深度学习模型。在这个类中,主要完成了以下操作。

  • ___init__方法:这是类的构造函数,用于初始化Seq2Seq架构。传入已经定义好的编码器和解码器对象。然后将这两个对象分别赋值给类的实例变量self encoder和 self.decoder。这样,我们可以在类的其他方法中使用这两个子模型。

  • forward 方法:forward是类的前向传播函数,它定义了如何将输入序列enc_input传递给编码器和解码器以生成输出序列。这个函数接收3个参数:编码器输入序列 enc_input、初始隐藏状态hidden和解码器输入序列dec_input,具体操作如下。

  • (1)将输入序列传递给编码器,并获得编码器的输出和隐藏状态(encoder output, encoder_hidden )。

  • (2)将编码器的隐藏状态作为解码器的初始隐藏状态(decoder_hidden= encoder_hidden )。

  • (3)将解码器输入序列,也就是目标序列和解码器的初始隐藏状态传递给解码器,以获取解码器的输出(decoder_output,_)。这里的下划线表示我们不关心解码器返回的隐藏状态,因为我们只需要输出序列。

  • (4)返回解码器的输出decoder_output。这个输出可以用来计算损失,优化模型,并生成翻译后的句子。

在定义前向传播函数的代码def forward(self,enc_input, hidden, dec_input)中,参数dec_input接收的实际上是目标序列的信息。可是通常来说,我们是不会在前向传播部分把目标值输入网络的呀。只有在反向传播,计算损失的时候,才需要目标值嘛,这是“教师强制”。

教师强制是训练Seq2Seq架构的一种常用技术。使用该技术,要向解码器提供真实的目标序列中的词作为输入,而不是使用解码器自身生成的词。这样可以帮助模型更快地收敛,并在训练时获得更好的性能。

5. 训练Seq2Seq架构

# 定义训练函数
def train_seq2seq(model, criterion, optimizer, epochs):
    for epoch in range(epochs):
        encoder_input, decoder_input, target = make_data(sentences) # 训练数据的创建
        hidden = torch.zeros(1, encoder_input.size(0), n_hidden) # 初始化隐藏状态      
        optimizer.zero_grad()# 梯度清零        
        output = model(encoder_input, hidden, decoder_input) # 获取模型输出        
        loss = criterion(output.view(-1, voc_size_en), target.view(-1)) # 计算损失        
        if (epoch + 1) % 1000 == 0: # 打印损失
            print(f"Epoch: {epoch + 1:04d} cost = {loss:.6f}")         
        loss.backward()# 反向传播        
        optimizer.step()# 更新参数
# 训练模型
epochs = 5000 # 训练轮次
criterion = nn.CrossEntropyLoss() # 损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 优化器
train_seq2seq(model, criterion, optimizer, epochs) # 调用函数训练模型

在这里插入图片描述
这是训练模型的标准过程,与你之前看到的大同小异。在train_seq2seq函数中,每个epoch 都会随机选择一个句子进行训练。首先,为这个句子创建输入批次、输出批次和目标批次。然后初始化模型的隐藏状态,并将梯度清零。接着,将输入批次、隐藏状态和输出批次传递给模型,获取模型的输出。计算模型输出和目标批次之间的损失,并在每100个epoch后打印损失值。最后,执行反向传播及参数更新。

代码中的 encoder_input是模型的输入数据,hidden用于初始化 RNN,这两个张量我们已经十分了解。而decoder_input是专属于 Seq2Seq架构的训练数据。刚才我已经说明了,这些数据用于进行教师强制,是在训练时故意暴露给解码器的内容。

6.测试Seq2Seq架构

# 定义测试函数
def test_seq2seq(model, source_sentence):
    # 将输入的句子转换为索引
    encoder_input = np.array([[word2idx_cn[n] for n in source_sentence.split()]])
    # 构建输出的句子的索引,以 '<sos>' 开始,后面跟 '<eos>',长度与输入句子相同
    decoder_input = np.array([word2idx_en['<sos>']] + [word2idx_en['<eos>']]*(len(encoder_input[0])-1))
    # 转换为 LongTensor 类型
    encoder_input = torch.LongTensor(encoder_input)
    decoder_input = torch.LongTensor(decoder_input).unsqueeze(0) # 增加一维    
    hidden = torch.zeros(1, encoder_input.size(0), n_hidden) # 初始化隐藏状态    
    predict = model(encoder_input, hidden, decoder_input) # 获取模型输出    
    predict = predict.data.max(2, keepdim=True)[1] # 获取概率最大的索引
    # 打印输入的句子和预测的句子
    print(source_sentence, '->', [idx2word_en[n.item()] for n in predict.squeeze()])
# 测试模型
test_seq2seq(model, '哒哥 喜欢 爬山')  
test_seq2seq(model, '我 爱 学习 人工智能')

在这里插入图片描述
在test_seq2seq函数中,首先将输入的句子转换为索引,并构建输出的句子的索引,以开始,后面跟,长度与输入句子相同。

为什么在这里,使用一个和一系列符号来构建 decoder_input呢?

进行教师强制是因为要在训练的时候,使模型更快地跟“老师”学会要翻译的内容。现在已经进入测试阶段,我就是要看看模型能否脱离“老师”自己翻译,当然不能再“喂”它真实的目标输出了,那不就完全体现不出模型的翻译能力了嘛。

通过不断的探索和改进,Seq2Seq架构也在不断地发展和完善,即将出现的编码器-解码器注意力机制,将进一步优化基于Seq2Seq架构的模型的性能。

归纳

从NPLM到Seq2Seq,NLP研究人员不断探索更有效的建模方法来捕捉自然语言的复杂性。

NPLM使用神经网络来学习词嵌入表示,并预测给定上文的下一个词。NPLM用连续向量表示词,捕捉到了单词之间的语义和语法关系。尽管NPLM性能有所提高,但仍然存在一些局限性,例如上下文窗口的大小是固定的。

为了解决上下文窗口大小固定的问题,研究人员开始使用RNN来处理可变长度的序列。RNN 可以在处理序列时保持内部状态,从而捕捉长距离依赖关系。然而,RNN在训练中容易出现梯度消失和梯度爆炸问题。

为了解决梯度消失和梯度爆炸问题,LSTM和 GRU等门控循环单元被提出。它们引入了门控机制,可以学习长距离依赖关系,同时缓解梯度消失和梯度爆炸问题。

在此之后,又出现了Seq2Seq这种序列到序列的编码器-解码器架构,渐渐取代了单一的 RNN。编码器将输入序列编码成一个固定大小的向量,解码器则解码该向量,从而生成输出序列。Seq2Seq架构可以处理不等长的输入和输出序列,因此在机器翻译、文本摘要等任务中表现出色。

基于RNN的Seq2Seq架构存在一些缺点,例如难以处理长序列(长输入序列可能导致信息损失)和复杂的上下文相关性等。具体来讲,在一个长序列中,一些重要的上下文信息可能会被编码成一个固定长度的向量,因此在解码过程中,模型难以正确地关注到所有重要信息。为了提高Seq2Seq架构的性能,研究人员发现向编码器一解码器架构间引入注意力机制,可以帮助Seq2Seq架构更好地处理长序列和上下文相关性。通过给予不同时间步(也就是token)的输入不同的注意力权重,可以让模型更加关注与当前时间步(也就是当前token)相关的信息。

Seq2Seq编码器-解码器架构小结

优势:
编码器将输入序列编码成一个固定大小的向量,解码器则解码该向量,从而生成输出序列。Seq2Seq架构可以处理不等长的输入和输出序列,因此在机器翻译、文本摘要等任务中表现出色。

劣势:
难以处理长序列(长输入序列可能导致信息损失)和复杂的上下文相关性。


学习的参考资料:
(1)书籍
利用Python进行数据分析
西瓜书
百面机器学习
机器学习实战
阿里云天池大赛赛题解析(机器学习篇)
白话机器学习中的数学
零基础学机器学习
图解机器学习算法

动手学深度学习(pytorch)

(2)机构
光环大数据
开课吧
极客时间
七月在线
深度之眼
贪心学院
拉勾教育
博学谷
慕课网
海贼宝藏

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——算法的时间复杂度和空间复杂度

1、算法效率 1.1如何衡量一个算法的好坏&#xff1f; 比如我们最熟悉的斐波那契数列 long long Fib(int N) {if(N < 3)return 1;return Fib(N-1) Fib(N-2); } 上面的斐波那契数列使用递归实现&#xff0c;看起来非常的简洁&#xff0c;那么代码一定是越简洁越好么&…

仰暮计划|“​爷爷说这些话的时候眼睛都红着,他那变形的脊柱和瘸拐的双腿都证明他曾为这个家付出了血汗拼尽了全力”

赴一场拾光之旅&#xff0c;集往年回忆碎片 爷爷生于1952年&#xff0c;今年已有七十一了&#xff0c;是河南焦作沁阳北金村的一位地道农民&#xff0c;劳苦一生&#xff0c;如今终于得以颐养天年。许是早年经历过于难忘&#xff0c;爷爷如今与我讲起仍是记忆犹新&#xff0c;…

kafka客户端生产者消费者kafka可视化工具(可生产和消费消息)

点击下载《kafka客户端生产者消费者kafka可视化工具&#xff08;可生产和消费消息&#xff09;》 1. 前言 因在工作中经常有用到kafka做消息的收发&#xff0c;每次调试过程中&#xff0c;经常需要查看接收的消息内容以及人为发送消息&#xff0c;从网上搜寻了一下&#xff0…

航道大数据应用专项研究报告(附下载)

总体目标 充分认识航道大数据对行业治理的重要性和必要性&#xff0c;航道大数据的开发和利用是建设智慧航道的基础。基于大数据的航道管理体系&#xff0c;实现了现有数据的梳理和汇聚&#xff0c;跨部门数据的交换和整合&#xff0c;建立了数据关联和深度学习的模型机制&…

【win】vscode无法使用ctrl+shift+p快捷键的解决方案

本文首发于 ❄️慕雪的寒舍 今天使用vscode的时候遇到的这个问题&#xff0c;明明快捷键设置的是ctrlshiftp&#xff0c;但是在电脑上怎么敲都敲不出来&#xff0c;因为用这个快捷键打开命令面板都习惯了&#xff0c;也不想换&#xff0c;就在找原因。 同时百度的时候还遇到了…

C++ 搜索二叉树的删除

首先查找元素是否在二叉搜索树中&#xff0c;如果不存在&#xff0c;则返回 要删除的结点可能分下面四种情况&#xff1a; a. 要删除的结点无孩子结点 b. 要删除的结点只有左孩子结点 c. 要删除的结点只有右孩子结点 d. 要删除的结点有左、右孩子结点 看起来有待删除节点有4中…

宠物空气净化器哪个牌子好?养猫家庭如何挑选宠物空气净化器?

养猫的朋友都知道&#xff0c;猫咪掉毛是一个令人头痛的问题。猫毛和皮屑会漂浮在空气中&#xff0c;不仅遍布全屋的各个角落&#xff0c;而且清理起来也非常麻烦&#xff0c;特别是那些难以清除的猫毛。更糟糕的是&#xff0c;这些猫毛还可能引发人们的过敏反应&#xff0c;如…

Excel——分类汇总

1.一级分类汇总 Q&#xff1a;请根据各销售地区统计销售额总数。 第一步&#xff1a;排序&#xff0c;我们需要根据销售地区汇总数据&#xff0c;我们就要对【销售地区】的内容进行排序。点击【销售地区】列中任意一个单元格&#xff0c;选择【数据】——【排序】&#xff0c…

parse库,一个优雅的python库

前言 在Python中&#xff0c;format方法和f-strings是两种常用的字符串插值方法。 name "Haige" age "18" print(f"{name} is {age} years old.")# Haige is 18 years old.而如果是要从字符串中提取期望的值呢&#xff1f;相信很多人的第一或…

代码随想录 Leetcode46. 全排列

题目&#xff1a; 代码&#xff08;首刷自解 2024年2月6日&#xff09;&#xff1a; class Solution { private:vector<vector<int>> res;vector<int> path; public:void backtracking(vector<int>& nums, int depth, vector<bool>& us…

CTF-PWN-堆-【chunk extend/overlapping-2】(hack.lu ctf 2015 bookstore)

文章目录 hack.lu ctf 2015 bookstore检查IDA源码main函数edit_notedelete_notesubmit .fini_array段劫持(回到main函数的方法) 思路格式化字符串是啥呢0x开头或者没有0x开头的十六进制的字符串或字节的转换为整数构造格式化字符串的其他方法 exp 佛系getshell 常规getshell ha…

【maven相关问题】Could not transfer artifact ...与Transfer failed for ...报错及解决办法

一、问题描述 拉取一个新项目后&#xff0c;maven解析下载文件时出现如下报错信息&#xff1a; 二、错误原因分析 提示信息是传输失败&#xff0c;无法传输的原因应该是出现错误的包文件夹已经缓存在本地存储库中&#xff0c;然后maven在经过发布的更新间隔或强制进行更新之前…

深入了解Spring Expression Language(SpEL)

深入了解Spring Expression Language&#xff08;SpEL&#xff09; Spring Expression Language&#xff08;SpEL&#xff09;是Spring框架中强大的表达式语言&#xff0c;它在运行时提供了一种灵活的方式来评估字符串表达式。SpEL的设计目标是在各种Spring配置和编程场景中提供…

Go语言每日一练链表篇(二)

传送门 牛客面试笔试必刷101题 ---------------- 链表内指定区间反转 题目以及解析 题目 解题代码及解析 package main import _"fmt" import . "nc_tools" /** type ListNode struct{* Val int* Next *ListNode* }*//*** 代码中的类名、方法名、参…

PyTorch 2.2 中文官方教程(七)

使用 torchtext 库进行文本分类 原文&#xff1a;pytorch.org/tutorials/beginner/text_sentiment_ngrams_tutorial.html 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 注意 点击这里下载完整示例代码 在本教程中&#xff0c;我们将展示如何使用 torchtext 库构建文…

识别CMS指纹与WAF识别

目录 识别CMS指纹 1 什么是CMS指纹&#xff1f; 2 常见的CMS指纹 3 识别CMS指纹的方法有哪些&#xff1f; &#xff08;1&#xff09;分析HTTP响应头&#xff0c;识别CMS的特定标头。 &#xff08;2&#xff09;通过配置文件/特殊文件 &#xff08;3&#xff09;分析网站…

【Linux】命令行解释器脚本编写

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.简单了解命令行解…

设计师常用的软件有哪些?推荐5款设计工具

设计软件的使用对设计师来说非常重要。设计工具的使用是否直接影响到最终结果的质量&#xff0c;然后有人会问&#xff1a;设计需要使用什么软件&#xff1f;这里有一些设计师和那些对设计感兴趣的朋友列出了五个有用的设计工具。 1、即时设计 即时设计操作简单&#xff0c;内…

Unity制作随风摇摆的植物

今天记录一下如何实现随风摇摆的植物&#xff0c;之前项目里面的植物摇摆实现是使用骨骼动画实现的&#xff0c;这种方式太消耗性能&#xff0c;植物这种东西没必要&#xff0c;直接使用顶点动画即可。 准备 植物不需要使用标准的PBR流程&#xff0c;基础的颜色贴图加上法向贴…

leetcode 算法 69.x的平方根(python版)

需求 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#…