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 AutoTokenizermodel_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 sqrtQ = 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 Fweights = 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 sqrtdef 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 nnclass 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_sizenum_heads = config.num_attention_headshead_dim = embed_dim // num_headsself.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 AutoTokenizermodel_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, valuehidden_state = self.layer_norm_1(x)# Apply attention with a skip connectionx = x + self.attention(hidden_state, hidden_state, hidden_state, mask=mask)# Apply feed-forward layer with a skip connectionx = 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 sequenceseq_length = input_ids.size(1)position_ids = torch.arange(seq_length, dtype=torch.long).unsqueeze(0)# Create token and position embeddingstoken_embeddings = self.token_embeddings(input_ids)position_embeddings = self.position_embeddings(position_ids)# Combine token and position embeddingsembeddings = token_embeddings + position_embeddingsembeddings = self.layer_norm(embeddings)embeddings = self.dropout(embeddings)return embeddingsembedding_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>)

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

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

相关文章

js通过Object.defineProperty实现数据响应式

目录 数据响应式属性描述符propertyResponsive 依赖收集依赖队列寻找依赖 观察器 派发更新Observer完整代码关于数据响应式关于Object.defineProperty的限制 数据响应式 假设我们现在有这么一个页面 <!DOCTYPE html> <html lang"en"><head><m…

Oracle表空间满清理方案汇总分享

目录 前言思考 一、第一种增加表空间的数据文件数量达到总容量的提升 二、第二种解决方案针对system和sysaux的操作 2.1SYSTEM表空间优化 2.2sysaux表空间回收 2.2.1针对sysaux的表空间爆满还有第二套方案维护 三、第三种解决方案使用alter tablespace resize更改表空间的…

深入浅出 -- 系统架构之微服务架构的新挑战

尽管微服务架构有着高度独立的软件模块、单一的业务职责、可灵活调整的技术栈等优势&#xff0c;但也不能忽略它所带来的弊端。本篇文章&#xff0c;我们从网络、性能、运维、组织架构和集成测试五个方面来聊一下设计微服务架构需要考虑哪些问题&#xff0c;对设计有哪些挑战呢…

Webots常用的执行器(Python版)

文章目录 1. RotationalMotor2. LinearMotor3. Brake4. Propeller5. Pen6. LED 1. RotationalMotor # -*- coding: utf-8 -*- """motor_controller controller."""from controller import Robot# 实例化机器人 robot Robot()# 获取基本仿真步长…

ChatGPT/GPT4科研应用与绘图技术及论文写作

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

c++的学习之路:19、模板

摘要 本章主要是说了一些模板&#xff0c;如非类型模板参数、类模板的特化等等&#xff0c;文章末附上测试代码与导图 目录 摘要 一、非类型模板参数 二、类模板的特化 1、概念 2、函数模板特化 3、类模板特化 三、模板的分离编译 1、什么是分离编译 2、模板的分离编…

ip地址切换器安卓版,保护隐私,自由上网

在移动互联网时代&#xff0c;随着智能手机和平板电脑的普及&#xff0c;移动设备的网络连接变得愈发重要。为了满足用户在不同网络环境下的需求&#xff0c;IP地址切换器安卓版应运而生。本文将以虎观代理为例&#xff0c;为您详细解析IP地址切换器安卓版的功能、应用以及其所…

克服与新一代人工智能部署相关的数据挑战

随着商界领袖逐渐了解该技术的力量和潜力&#xff0c;人们对 ChatGPT 等生成式人工智能工具的潜力的兴趣正在迅速上升。 这些工具能够创建以前属于人类创造力和智力领域的输出&#xff0c;有潜力改变许多业务流程&#xff0c;并成为每个人&#xff08;从作家和创作者到程序员和…

蓝桥杯加训

1.两只塔姆沃斯牛&#xff08;模拟&#xff09; 思路&#xff1a;人和牛都记录三个数据&#xff0c;当前坐标和走的方向&#xff0c;如果人和牛的坐标和方向走重复了&#xff0c;那就说明一直在绕圈圈&#xff0c;无解 #include<iostream> using namespace std; const i…

openstack-认证服务

整个OpenStack是由控制节点&#xff0c;计算节点&#xff0c;网络节点&#xff0c;存储节点四大部分组成。 openstack重要集成组件: Nova-计算服务&#xff1b;Neutron-网络服务&#xff1b;Swift-对象存储服务&#xff1b;Cinder-块存储服务&#xff1b;Glance-镜像服务Keys…

LeetCode-118. 杨辉三角【数组 动态规划】

LeetCode-118. 杨辉三角【数组 动态规划】 题目描述&#xff1a;解题思路一&#xff1a;Python 动态规划解题思路二&#xff1a;解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&…

C语言进阶课程学习记录-第27课 - 数组的本质分析

C语言进阶课程学习记录-第27课 - 数组的本质分析 数组实验-数组元素个数的指定实验-数组地址与数组首元素地址实验-指针与数组地址的区别小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 数组 实验-数…

Hot100【十一】:编辑距离

// 定义dp[i][j]: 表示word1前i个字符转换到word2前j个字符最小操作数 // 初始化dp[m1][n1] class Solution {public int minDistance(String word1, String word2) {int m word1.length();int n word2.length();// 1. dp数组int[][] dp new int[m 1][n 1];// 2. dp数组初…

IO流:将文件从A复制到B,并实现复制过程进度条的实现

private static boolean copyFile(String strFileA, String strFileB) {// 使用try资源块 ,其中创建的流对象可以自动关闭try (FileInputStream inputStream new FileInputStream(strFileA); // 输入流FileOutputStream outputStream new FileOutputStream(strFileB) // 输…

【Linux】进程的状态(运行、阻塞、挂起)详解,揭开孤儿进程和僵尸进程的面纱,一篇文章万字讲透!!!!进程的学习②

目录 1.进程排队 时间片 时间片的分配 结构体内存对齐 偏移量补充 对齐规则 为什么会有对齐 2.操作系统学科层面对进程状态的理解 2.1进程的状态理解 ①我们说所谓的状态就是一个整型变量&#xff0c;是task_struct中的一个整型变量 ②.状态决定了接下来的动作 2.2运行状态 2.…

【闲聊】-网页划词翻译插件

英文之痛 作为程序猿&#xff0c;常常需要接触外文网站&#xff0c;以前很痛苦&#xff0c;现在大模型时代有很多智能工具可以直接翻译&#xff0c;翻译的虽然越来越好&#xff0c;但是还是不如直接看英文能理解本义&#xff0c;相信我&#xff0c;看翻译的理解和看原文的理解…

龙迅LT2611UXC 2 PORT LVDS桥接到HDMI 2.0,内置MCU,颗自行操作

龙迅LT2611UXC描述&#xff1a; LT2611UXC是一个高性能的LVDS到HDMI2.0的转换器&#xff0c;用于STB&#xff0c;DVD应用程序。LVDS输入可以配置为单端口或双端口&#xff0c;有1个高速时钟通道&#xff0c;3~4个高速数据通道&#xff0c;最大运行1.2Gbps/通道&#xff0c;可支…

gpu模拟器总体流程

1、开显存空间&#xff0c;初始化 这里显存就是运行模拟器的机器 2、创建页表&#xff0c;开设备端空间并复制数据 虚拟地址 3、划分形状&#xff0c;传入内核函数&#xff0c;形状参数和设备端数据地址、执行计算 4、复制数据回主机端&#xff0c;释放gpu资源

手写简易操作系统(二十五)--文件系统第三部分

前情提要 一、文件写入 1.1、file的写入 文件写入比较复杂&#xff0c;函数行数相当多 /*** description: 把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1 * param {file*} file 文件* param {void*} buf 缓存* param {uint32_t} count 写入的字节数…

基于Java+SpringBoot+Vue民宿预约管理系统(源码+文档+部署+讲解)

一.系统概述 随着社会的不断进步与发展&#xff0c;人们经济水平也不断的提高&#xff0c;于是对各行各业需求也越来越高。利用计算机网络来处理各行业事务这一概念更深入人心&#xff0c;由于工作繁忙以及其他的原因&#xff0c;到实体店进行预约也是比较难实施的。如果开发一…