transformer上手(2) —— 注意力机制

自从 2017 年 Google 发布《Attention is All You Need》之后,各种基于 Transformer 的模型和方法层出不穷。尤其是 2018 年,OpenAI 发布的 GPT 和 Google 发布的 BERT 模型在几乎所有 NLP 任务上都取得了远超先前最强基准的性能,将 Transformer 模型的热度推上了新的高峰。

在这里插入图片描述

Transformer 模型之所以如此强大,是因为它抛弃了之前广泛采用的循环网络和卷积网络,而采用了一种特殊的结构——注意力机制 (Attention) 来建模文本。

本文将介绍目前最常见的 Multi-Head Attention,并使用 Pytorch 框架实现一个 Transformer block。

1 attention

NLP 神经网络模型的本质就是对输入文本进行编码,常规的做法是首先对句子进行分词,然后将每个词语 (token) 都转化为对应的词向量 (token embeddings),这样文本就转换为一个由词语向量组成的矩阵 X = ( x 1 , x 2 , . . . , x n ) X = (x_1, x_2, ..., x_n) X=(x1,x2,...,xn),其中 x i x_i xi 表示第 i i i 个词的词向量,维度为 d d d,因此 X ∈ R n × d X \in \Bbb{R}^{n \times d} XRn×d

在 Transformer 模型提出之前,对 token 序列 X X X 的常规编码方式是通过循环网络 (RNNs) 和卷积网络 (CNNs)。

  • RNN(例如 LSTM)的方案很简单,每一个词语 x t x_t xt 对应的编码结果 y t y_t yt 通过递归地计算得到:
    y t = f ( y t − 1 , x t ) y_t=f(y_{t-1}, x_t) yt=f(yt1,xt)
    RNN 的序列建模方式虽然与人类阅读类似,但是递归的结构导致其无法并行计算,因此速度较慢。而且 RNN 本质是一个马尔科夫决策过程,难以学习到全局的结构信息;

  • CNN 则通过滑动窗口基于局部上下文来编码文本,例如核尺寸为 3 的卷积操作就是使用每一个词自身以及前一个和后一个词来生成嵌入式表示:
    y t = f ( x t − 1 , x t , x t + 1 ) y_t=f(x_{t-1}, x_t, x_{t+1}) yt=f(xt1,xt,xt+1)

    CNN 能够并行地计算,因此速度很快,但是由于是通过窗口来进行编码,所以更侧重于捕获局部信息,难以建模长距离的语义依赖。

Google《Attention is All You Need》提供了第三个方案:直接使用 Attention 机制编码整个文本。相比 RNN 要逐步递归才能获得全局信息(因此一般使用双向 RNN),而 CNN 实际只能获取局部信息,需要通过层叠来增大感受野,Attention 机制一步到位获取了全局信息:
y t = f ( x t , A , B ) y_t=f(x_t, A, B) yt=f(xt,A,B)

其中 A , B A, B A,B 是另外的词语序列(矩阵),如果取 A = B = X A=B=X A=B=X 就称为 Self-Attention,即直接将 x t x_t xt 与自身序列中的每个词语进行比较,最后算出 y t y_t yt

1.1 Scaled Dot-product Attention

Attention 有许多种实现方式,但是最常见的还是 Scaled Dot-product Attention。

在这里插入图片描述

Scaled Dot-product Attention 共包含 2 个主要步骤:

  • 计算注意力权重:使用某种相似度函数度量每一个 query 向量和所有 key 向量之间的关联程度。对于长度为 m 的 Query 序列和长度为 n 的 Key 序列,该步骤会生成一个尺寸为 mxn 的注意力分数矩阵。
    特别地,Scaled Dot-product Attention 使用点积作为相似度函数,这样相似的 queries 和 keys 会具有较大的点积。

    由于点积可以产生任意大的数字,这会破坏训练过程的稳定性。因此注意力分数还需要乘以一个缩放因子来标准化它们的方差,然后用一个 softmax 标准化。这样就得到了最终的注意力权重 w i j w_{ij} wij,表示第 i 个 query 向量与第 j 个 key 向量之间的关联程度。

  • 更新 token embeddings:将权重 w i j w_{ij} wij 与对应的 value 向量 v 1 , v 2 , . . . , v n v_1, v_2, ..., v_n v1,v2,...,vn 相乘以获得第 i 个 query 向量更新后的语义表示 x i ′ = ∑ j w i j v j x_i^{'}= \sum _j w_{ij} v_j xi=jwijvj,形式化表示为:
    A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q, K, V)=softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V

其中 Q ∈ R m × d k Q \in \Bbb{R}^{m \times d_k} QRm×dk, K ∈ R n × d k K \in \Bbb{R}^{n \times d_k} KRn×dk, V ∈ R n × d v V \in \Bbb{R}^{n \times d_v} VRn×dv 分别是 query、key、value 向量序列。如果忽略 softmax 激活函数,实际上它就是三个 m × d k , d k × n , n × d v m \times d_k, d_k \times n, n \times d_v m×dk,dk×n,n×dv 的三个矩阵相乘,得到一个 m × d v m \times d_v m×dv 的矩阵,实际上就是把 m × d k m \times d_k m×dk 的序列 Q 编码成了一个新的 m × d v m \times d_v m×dv 的序列。

将上面的公式拆开来看:
A t t e n t i o n ( q t , K , V ) = ∑ s = 1 m 1 Z e x p ( < q t , k s > d k ) v s Attention(q_t, K, V)=\sum _{s=1} ^{m} \frac{1}{Z} exp(\frac{<q_t, k_s>}{\sqrt{d_k}})v_s Attention(qt,K,V)=s=1mZ1exp(dk <qt,ks>)vs

其中 Z 是归一化因子,K,V 是一一对应的 key 和 value 向量序列,Scaled Dot-product Attention 就是通过 q t q_t qt 这个 query 与各个 k s k_s ks 内积并 softmax 的方式来得到 q t q_t qt 与各个 v s v_s vs 的相似度,然后加权求和,得到一个 d v d_v dv 维的向量。其中因子 d k \sqrt{d_k} dk 起到调节作用,使得内积不至于太大。

通过 Pytorch 来手工实现 Scaled Dot-product Attention:

首先需要将文本分词为词语 (token) 序列,然后将每一个词语转换为对应的词向量 (embedding)。Pytorch 提供了 torch.nn.Embedding 层来完成该操作,即构建一个从 token ID 到 token embedding 的映射表:

from torch import nn
from transformers import AutoConfig
from transformers import AutoTokenizer

model_ckpt = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)

text = "time flies like an arrow"
inputs = tokenizer(text, return_tensors="pt", add_special_tokens=False)
print(inputs.input_ids)

config = AutoConfig.from_pretrained(model_ckpt)
token_emb = nn.Embedding(config.vocab_size, config.hidden_size)
print(token_emb)

inputs_embeds = token_emb(inputs.input_ids)
print(inputs_embeds.size())

# 输出
# tensor([[ 2051, 10029,  2066,  2019,  8612]])
# Embedding(30522, 768)
# torch.Size([1, 5, 768])

为了方便,这里我们通过设置 add_special_tokens=False 去除了分词结果中的 [CLS] 和 [SEP]。

可以看到,BERT-base-uncased 模型对应的词表大小为 30522,每个词语的词向量维度为 768。Embedding 层把输入的词语序列映射到了尺寸为[batch_size, seq_len, hidden_dim]的张量。

接下来就是创建 query、key、value 向量序列 Q,K,V,并且使用点积作为相似度函数来计算注意力分数:

import torch
from math import sqrt

Q = K = V = inputs_embeds
dim_k = K.size(-1)
scores = torch.bmm(Q, K.transpose(1,2)) / sqrt(dim_k)
print(scores.size())

# 输出
# torch.Size([1, 5, 5])

这里 Q,K 的序列长度都为 5,因此生成了一个 5x5 的注意力分数矩阵,接下来就是应用 Softmax 标准化注意力权重:

import torch.nn.functional as F

weights = F.softmax(scores, dim=-1)
print(weights.sum(dim=-1))

# 输出
# tensor([[1., 1., 1., 1., 1.]], grad_fn=<SumBackward1>)

最后将注意力权重与 value 序列相乘:

attn_outputs = torch.bmm(weights, V)
print(attn_outputs.shape)

# 输出
# torch.Size([1, 5, 768])

至此就实现了一个简化版的 Scaled Dot-product Attention。可以将上面这些操作封装为函数以方便后续调用:

import torch
import torch.nn.functional as F
from math import sqrt

def scaled_dot_product_attention(query, key, value, query_mask=None, key_mask=None, mask=None):
    dim_k = query.size(-1)
    scores = torch.bmm(query, key.transpose(1, 2)) / sqrt(dim_k)
    if query_mask is not None and key_mask is not None:
        mask = torch.bmm(query_mask.unsqueeze(-1), key_mask.unsqueeze(1))
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -float("inf"))
    weights = F.softmax(scores, dim=-1)
    return torch.bmm(weights, value)

上面的代码还考虑了 Q,K,V 序列的 Mask。填充 (padding) 字符不应该参与计算,因此将对应的注意力分数设置为 − ∞ -\infty ,这样 softmax 之后其对应的注意力权重就为 0 了( e − ∞ = 0 e^{-\infty}=0 e=0)。

上面的做法会带来一个问题:当 Q 和 K 序列相同时,注意力机制会为上下文中的相同单词分配非常大的分数(点积为 1),而在实践中,相关词往往比相同词更重要。例如对于上面的例子,只有关注timearrow才能够确认flies的含义。

1.2 Multi-head Attention

Multi-head Attention 首先通过线性映射将 Q,K,V 序列映射到特征空间,每一组线性投影后的向量表示称为一个头 (head),然后在每组映射后的序列上再应用 Scaled Dot-product Attention:

在这里插入图片描述

每个注意力头负责关注某一方面的语义相似性,多个头就可以让模型同时关注多个方面。因此与简单的 Scaled Dot-product Attention 相比,Multi-head Attention 可以捕获到更加复杂的特征信息。

形式化表示为:

h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 , h e a d 2 , . . . , h e a d n ) head_i=Attention(QW_{i}^{Q},KW_i^K,VW_i^V) \\ MultiHead(Q,K,V)=Concat(head_1, head2, ..., head_n) headi=Attention(QWiQ,KWiK,VWiV)MultiHead(Q,K,V)=Concat(head1,head2,...,headn)

其中 W i Q ∈ R d k × d k ^ , W i K ∈ R d k × d k ^ , W i V ∈ R d v × d v ^ W_{i}^{Q} \in \Bbb{R}^{d_k \times \hat{d_k}}, W_{i}^{K} \in \Bbb{R}^{d_k \times \hat{d_k}}, W_{i}^{V} \in \Bbb{R}^{d_v \times \hat{d_v}} WiQRdk×dk^,WiKRdk×dk^,WiVRdv×dv^ 是映射矩阵,h 是注意力头的数量。最后,将多头的结果拼接起来就得到最终 m × h d k ^ m \times h \hat{d_k} m×hdk^ 的结果序列。所谓的“多头” (Multi-head),其实就是多做几次 Scaled Dot-product Attention,然后把结果拼接。

实现一个注意力头:

from torch import nn

class AttentionHead(nn.Module):
    def __init__(self, embed_dim, head_dim):
        super().__init__()
        self.q = nn.Linear(embed_dim, head_dim)
        self.k = nn.Linear(embed_dim, head_dim)
        self.v = nn.Linear(embed_dim, head_dim)

    def forward(self, query, key, value, query_mask=None, key_mask=None, mask=None):
        attn_outputs = scaled_dot_product_attention(
            self.q(query), self.k(key), self.v(value), query_mask, key_mask, mask)
        return attn_outputs

每个头都会初始化三个独立的线性层,负责将 Q,K,V 序列映射到尺寸为 [batch_size, seq_len, head_dim]的张量,其中 head_dim是映射到的向量维度。

一般将 head_dim 设置为 embed_dim 的因数,这样 token 嵌入式表示的维度就可以保持不变,例如 BERT 有 12 个注意力头,因此每个头的维度被设置为 768/12=64。

最后只需要拼接多个注意力头的输出就可以构建出 Multi-head Attention 层了(这里在拼接后还通过一个线性变换来生成最终的输出张量):

class MultiHeadAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        embed_dim = config.hidden_size
        num_heads = config.num_attention_heads
        head_dim = embed_dim // num_heads
        self.heads = nn.ModuleList(
            [AttentionHead(embed_dim, head_dim) for _ in range(num_heads)]
        )
        self.output_linear = nn.Linear(embed_dim, embed_dim)

    def forward(self, query, key, value, query_mask=None, key_mask=None, mask=None):
        x = torch.cat([
            h(query, key, value, query_mask, key_mask, mask) for h in self.heads
        ], dim=-1)
        x = self.output_linear(x)
        return x

这里使用 BERT-base-uncased 模型的参数初始化 Multi-head Attention 层,并且将之前构建的输入送入模型以验证是否工作正常:

from transformers import AutoConfig
from transformers import AutoTokenizer

model_ckpt = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)

text = "time flies like an arrow"
inputs = tokenizer(text, return_tensors="pt", add_special_tokens=False)
config = AutoConfig.from_pretrained(model_ckpt)
token_emb = nn.Embedding(config.vocab_size, config.hidden_size)
inputs_embeds = token_emb(inputs.input_ids)

multihead_attn = MultiHeadAttention(config)
query = key = value = inputs_embeds
attn_output = multihead_attn(query, key, value)
print(attn_output.size())

# 输出
# torch.Size([1, 5, 768])

2 Transformer Encoder

标准 Transformer 结构,Encoder 负责将输入的词语序列转换为词向量序列,Decoder 则基于 Encoder 的隐状态来迭代地生成词语序列作为输出,每次生成一个词语。

在这里插入图片描述

其中,Encoder 和 Decoder 都各自包含有多个 building blocks。下图展示了一个翻译任务的例子:

在这里插入图片描述

可以看到:

  • 输入的词语首先被转换为词向量。由于注意力机制无法捕获词语之间的位置关系,因此还通过 positional embeddings 向输入中添加位置信息;
  • Encoder 由一堆 encoder layers (blocks) 组成,类似于图像领域中的堆叠卷积层。同样地,在 Decoder 中也包含有堆叠的 decoder layers;
  • Encoder 的输出被送入到 Decoder 层中以预测概率最大的下一个词,然后当前的词语序列又被送回到 Decoder 中以继续生成下一个词,重复直至出现序列结束符 EOS 或者超过最大输出长度。
2.1 The Feed-Forward Layer

Transformer Encoder/Decoder 中的前馈子层实际上就是两层全连接神经网络,它单独地处理序列中的每一个词向量,也被称为 position-wise feed-forward layer。常见做法是让第一层的维度是词向量大小的 4 倍,然后以 GELU 作为激活函数。

下面实现一个简单的 Feed-Forward Layer:

class FeedForward(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.linear_1 = nn.Linear(config.hidden_size, config.intermediate_size)
        self.linear_2 = nn.Linear(config.intermediate_size, config.hidden_size)
        self.gelu = nn.GELU()
        self.dropout = nn.Dropout(config.hidden_dropout_prob)

    def forward(self, x):
        x = self.linear_1(x)
        x = self.gelu(x)
        x = self.linear_2(x)
        x = self.dropout(x)
        return x

将前面注意力层的输出送入到该层中以测试是否符合我们的预期:

feed_forward = FeedForward(config)
ff_outputs = feed_forward(attn_output)
print(ff_outputs.size())

# 输出
# torch.Size([1, 5, 768])

至此创建完整 Transformer Encoder 的所有要素都已齐备,只需要再加上 Skip Connections 和 Layer Normalization 就大功告成了。

2.2 Layer Normalization

Layer Normalization 负责将一批 (batch) 输入中的每一个都标准化为均值为零且具有单位方差;Skip Connections 则是将张量直接传递给模型的下一层而不进行处理,并将其添加到处理后的张量中。

向 Transformer Encoder/Decoder 中添加 Layer Normalization 目前共有两种做法:

在这里插入图片描述

  • Post layer normalization:Transformer 论文中使用的方式,将 Layer normalization 放在 Skip Connections 之间。 但是因为梯度可能会发散,这种做法很难训练,还需要结合学习率预热 (learning rate warm-up) 等技巧;
  • Pre layer normalization:目前主流的做法,将 Layer Normalization 放置于 Skip Connections 的范围内。这种做法通常训练过程会更加稳定,并且不需要任何学习率预热。

用第二种方式来构建 Transformer Encoder 层:

class TransformerEncoderLayer(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.layer_norm_1 = nn.LayerNorm(config.hidden_size)
        self.layer_norm_2 = nn.LayerNorm(config.hidden_size)
        self.attention = MultiHeadAttention(config)
        self.feed_forward = FeedForward(config)

    def forward(self, x, mask=None):
        # Apply layer normalization and then copy input into query, key, value
        hidden_state = self.layer_norm_1(x)
        # Apply attention with a skip connection
        x = x + self.attention(hidden_state, hidden_state, hidden_state, mask=mask)
        # Apply feed-forward layer with a skip connection
        x = x + self.feed_forward(self.layer_norm_2(x))
        return x

将之前构建的输入送入到该层中进行测试:

encoder_layer = TransformerEncoderLayer(config)
print(inputs_embeds.shape)
print(encoder_layer(inputs_embeds).size())

# 输出
# torch.Size([1, 5, 768])
# torch.Size([1, 5, 768])
2.3 Positional Embeddings

由于注意力机制无法捕获词语之间的位置信息,因此 Transformer 模型还使用 Positional Embeddings 添加了词语的位置信息。

Positional Embeddings 基于一个简单但有效的想法:使用与位置相关的值模式来增强词向量

如果预训练数据集足够大,那么最简单的方法就是让模型自动学习位置嵌入。以这种方式创建一个自定义的 Embeddings 模块,它同时将词语和位置映射到嵌入式表示,最终的输出是两个表示之和:

class Embeddings(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.token_embeddings = nn.Embedding(config.vocab_size,
                                             config.hidden_size)
        self.position_embeddings = nn.Embedding(config.max_position_embeddings,
                                                config.hidden_size)
        self.layer_norm = nn.LayerNorm(config.hidden_size, eps=1e-12)
        self.dropout = nn.Dropout()

    def forward(self, input_ids):
        # Create position IDs for input sequence
        seq_length = input_ids.size(1)
        position_ids = torch.arange(seq_length, dtype=torch.long).unsqueeze(0)
        # Create token and position embeddings
        token_embeddings = self.token_embeddings(input_ids)
        position_embeddings = self.position_embeddings(position_ids)
        # Combine token and position embeddings
        embeddings = token_embeddings + position_embeddings
        embeddings = self.layer_norm(embeddings)
        embeddings = self.dropout(embeddings)
        return embeddings

embedding_layer = Embeddings(config)
print(embedding_layer(inputs.input_ids).size())

# 输出
# torch.Size([1, 5, 768])

除此以外,Positional Embeddings 还有一些替代方案:

绝对位置表示:使用由调制的正弦和余弦信号组成的静态模式来编码位置。 当没有大量训练数据可用时,这种方法尤其有效;

相对位置表示:在生成某个词语的词向量时,一般距离它近的词语更为重要,因此也有工作采用相对位置编码。因为每个词语的相对嵌入会根据序列的位置而变化,这需要在模型层面对注意力机制进行修改,而不是通过引入嵌入层来完成,例如 DeBERTa 等模型。

下面将所有这些层结合起来构建完整的 Transformer Encoder:

class TransformerEncoder(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.embeddings = Embeddings(config)
        self.layers = nn.ModuleList([TransformerEncoderLayer(config)
                                     for _ in range(config.num_hidden_layers)])

    def forward(self, x, mask=None):
        x = self.embeddings(x)
        for layer in self.layers:
            x = layer(x, mask=mask)
        return x

对该层进行简单的测试:

encoder = TransformerEncoder(config)
print(encoder(inputs.input_ids).size())

# 输出
# torch.Size([1, 5, 768])

3 Transformer Decoder

Transformer Decoder 与 Encoder 最大的不同在于 Decoder 有两个注意力子层,如下图所示:

在这里插入图片描述

Masked multi-head self-attention layer:确保在每个时间步生成的词语仅基于过去的输出和当前预测的词,否则 Decoder 相当于作弊了;

Encoder-decoder attention layer:以解码器的中间表示作为 queries,对 encoder stack 的输出 key 和 value 向量执行 Multi-head Attention。通过这种方式,Encoder-Decoder Attention Layer 就可以学习到如何关联来自两个不同序列的词语,例如两种不同的语言。 解码器可以访问每个 block 中 Encoder 的 keys 和 values。

与 Encoder 中的 Mask 不同,Decoder 的 Mask 是一个下三角矩阵:

seq_len = inputs.input_ids.size(-1)
mask = torch.tril(torch.ones(seq_len, seq_len)).unsqueeze(0)
print(mask[0])

# 输出
# tensor([[1., 0., 0., 0., 0.],
#         [1., 1., 0., 0., 0.],
#         [1., 1., 1., 0., 0.],
#         [1., 1., 1., 1., 0.],
#         [1., 1., 1., 1., 1.]])

这里使用 PyTorch 自带的 tril() 函数来创建下三角矩阵,然后同样地,通过 Tensor.masked_fill() 将所有零替换为负无穷大来防止注意力头看到未来的词语而造成信息泄露:

scores.masked_fill(mask == 0, -float("inf"))

# 输出
# tensor([[[26.8082,    -inf,    -inf,    -inf,    -inf],
#          [-0.6981, 26.9043,    -inf,    -inf,    -inf],
#          [-2.3190,  1.2928, 27.8710,    -inf,    -inf],
#          [-0.5897,  0.3497, -0.3807, 27.5488,    -inf],
#          [ 0.5275,  2.0493, -0.4869,  1.6100, 29.0893]]],
#        grad_fn=<MaskedFillBackward0>)

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

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

相关文章

苹果手机短信删除了怎么恢复?别急,教你4招恢复技巧

在日常使用中&#xff0c;由于误操作或其他原因&#xff0c;我们可能会不小心删除了重要的短信&#xff0c;这时就需要寻找合适的方法来恢复它们。短信删除了怎么恢复&#xff1f;请接着往下看&#xff01; 首先&#xff0c;不必过于紧张&#xff0c;因为苹果手机提供了一些功…

二:什么是RocketMQ

RocketMQ是阿里开源的消息中间件产品&#xff0c;纯Java开发&#xff0c;具有高吞吐量、高可用性、适合大规模分布式系统应用的特点,性能强劲(零拷贝技术)&#xff0c;支持海量堆积,在阿里内部进行大规模使用&#xff0c;适合在互联网与高并发系统中应用。 官方文档&#xff1a…

深入C语言内存:数据在内存中的存储

一、数据类型 1. unsigned&#xff1a;无符号数类型 当一个数是无符号类型时&#xff0c;那么其最高位的1或0&#xff0c;和其它位一样&#xff0c;用来表示该数的大小。 2.signed&#xff1a;有符号数类型 当一个数是有符号类型时&#xff0c;最高数称为“符号位”。符号位为1…

解决npm install安装node-sass包容易失败的问题

具体问题如下&#xff1a; npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: XXX3.4.0 npm ERR! Found: webpack5.31.2 npm ERR! node_modules/webpack npm ERR! peer webpack”^4.0.0 || ^5.0.0″ from html-…

配置交换机 SSH 管理和端口安全——实验1:配置交换机基本安全和 SSH管理

实验目的 通过本实验可以掌握&#xff1a; 交换机基本安全配置。SSH 的工作原理和 SSH服务端和客户端的配置。 实验拓扑 交换机基本安全和 SSH管理实验拓扑如图所示。 交换机基本安全和 SSH管理实验拓扑 实验步骤 &#xff08;1&#xff09;配置交换机S1 Switch>enab…

RTThread studio 驱动开发

rtthread 驱动开发的两种情况 rtthread studio 自动生成 由 RT Thread Studio 自动生成&#xff0c;无需修改任何文件或者简单定义几个宏即可直接使用的驱动&#xff0c;如 GPIO&#xff0c;UART&#xff0c;I2C&#xff0c;SPI&#xff0c;SDIO 和 ETH 等。 使用 RT-Thread S…

快速入门Kotlin④集合

集合概述 继承关系(List、Set和Map均继承了Collection) List List 是一个接口,用于表示列表(List)数据结构。它是一个有序的集合,允许重复元素。 listOf 创建的列表是不可变的,而 mutableListOf 创建的列表是可变的,允许对其进行添加、删除和更新操作。 fun main()…

JAVA面试八股文之Redis相关

Redis相关 Redis6.0为什么要用多线程&#xff1f;在Redis中存一个list集合怎么实现排序&#xff1f;Redis的5大基本类型的底层原理&#xff1f;缓存穿透&#xff1f;缓存击穿&#xff1f;缓存雪崩&#xff1f;redis做为缓存怎么保持和mysql数据进行同步&#xff1f;&#xff08…

老子云、AMRT3D、眸瑞科技

老子云概述 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。 平台架构 平台特性 1、基…

比特币革命:刚刚开始

作者&#xff1a;Marius Farashi Tasooji 编译&#xff1a;秦晋 要充分理解比特币及其含义&#xff0c;首先必须理解什么是价值&#xff0c;什么是货币。以及是什么赋予资产价值&#xff1f; 这个问题看似愚蠢&#xff0c;但实际上非常有趣。我们的生活是由我们消费或出售的物品…

Redis的三种部署方案

文章目录 单机模式主从复制哨兵模式分片集群 在Redis中提供的集群方案总共有三种&#xff1a;单机模式&#xff0c;主从复制集群、哨兵模式&#xff0c;Redis分片集群 单机模式 Redis 只运行在一台服务器上&#xff0c;并且所有的数据都存储在这一台服务器的内存中。 主从复制…

ATF密钥生成和验签

ATF密钥生成和验签 根密钥生成 密钥的生成依赖openssl库&#xff0c;需要提前安装&#xff1a; sudo apt-get install openssl生成.pem格式的RSA4096私钥 openssl genrsa -out pri_key.pem 4096生成对应的DER格式公钥 openssl rsa -in pri_key.pem -pubout -out pub_key.der …

select详细用法

数据库版本&#xff1a;KingbaseES V008R006C008B0014 简介 SELECT语句是用于从一个或多个表中检索数据的操作&#xff0c;它作为数据库DQL语言&#xff0c;可以通过特定条件从表中检索数据。本篇文章以kingbase为例介绍select的详细用法。 文章目录如下 1. 基本语法 2. 条件…

DSP 技术基本概念及典型系统分析

1、 基本概念与典型的DSP系统 通常 DSP&#xff08;Digital Signal Processing&#xff09;是指数字信号处理。DSP 芯片是专用的数字信号处理器&#xff08;Digital Signal Processor&#xff09;&#xff0c;它采用哈佛结数、流水线作业方式的并行处理技术&#xff0c;有专用…

三、SpringBoot3 整合 SpringMVC

本章概要 实现过程web 相关配置静态资源处理自定义拦截器(SpringMVC 配置) 3.1 实现过程 创建程序引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www…

【算法统治世界】动态规划 个人笔记总结

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

中仕公考:2024山东事业编笔试成绩已出!

2024年山东省事业编笔试成绩查询入口于4约8日已经开放&#xff0c;考生可以登录入口查询笔试成绩。 山东16地市除菏泽以外均有县区参加此次310事业单位统考&#xff0c;成绩查询入口及进面名单等分地市分县区发布&#xff0c;考生可关注报考当地人民政府网站。笔试成绩查询后&…

鸿蒙南向开发:制作【智能儿童手表】

样例简介 本项目是基于BearPi套件开发的智能儿童手表系统&#xff0c;该系统通过与GSM模块&#xff08;型号&#xff1a;SIM808&#xff09;的通信来实现通话和定位功能。 智能儿童手表系统可以通过云和手机建立连接&#xff0c;同步时间和获取天气信息&#xff0c;通过手机下…

【SpringBoot整合系列】SpringBoot 实现大文件分片上传、断点续传及秒传

目录 功能介绍文件上传分片上传秒传断点续传 相关概念相关方法大文件上传流程前端切片处理逻辑后端处理切片的逻辑流程解析 后端代码实现功能目标1.建表SQL2.引入依赖3.实体类4.响应模板5.枚举类6.自定义异常7.工具类8.Controller层9.FileService10.LocalStorageService11.File…

Mac系统Unity团结引擎打包OpenHomeny项目配置

1、团结引擎下载&#xff1a;直接百度下载即可 2、mac版本的DevEco4.0编辑器下载&#xff1a; widthdevice-width,initial-scale1.0https://docs.openharmony.cn/pages/v4.0/zh-cn/release-notes/OpenHarmony-v4.0-release.md/#%E9%85%8D%E5%A5%97%E5%85%B3%E7%B3%BB3、打开D…