自注意力架构大成者_Transformer(Pytorch 17)

1 模型简介

在上节比较了 卷积神经网络(CNN)循环神经网络(RNN)和 自注意力(self‐attention)。值得注意的是, 自注意力同时具有并行计算和最短的最大路径长度这两个优势。因此,使用自注意力来 设计深度架构 是很有 吸引力的。对比之前仍然依赖循环神经网络实现输入表示的自注意力模型 (Cheng et al., 2016, Lin et al., 2017, Paulus et al., 2017),Transformer模型完全基于注意力机制,没有任何卷积层或循环神经网络层 (Vaswani et al., 2017)。尽管Transformer最初是应用于在文本数据上的序列到序列学习,但 现在已经推广到各种现代的深度学习中,例如 语言、视觉、语音 和 强化学习 领域。

2 模型架构

Transformer作为编码器-解码器架构的一个实例,其整体架构图在下图中展示。正如所见到的,Trans‐ former是由编码器和解码器组成的。与基于Bahdanau注意力实现的序列到序列的学习相比,Trans‐ former的编码器和解码器是基于自注意力的模块叠加而成的,源(输入)序列和目标(输出)序列的嵌入 (embedding)表示将加上位置编码(positional encoding),再分别输入到编码器和解码器中。

上图概述了Transformer的架构。从宏观角度来看,Transformer的编码器是 由多个相同的层叠加而 成的每个层都有两个子层(子层表示为sublayer)。第一个子层是多头自注意力(multi‐head self‐attention) 汇聚;第二个子层是基于位置的前馈网络(positionwise feed‐forward network)。具体来说,在计算编码器 的自注意力时,查询、键和值都来自前一个编码器层的输出。受残差网络的启发,每个子层都采用了残差连接(residual connection)。在Transformer中,对于序列中任何位置的任何输入x ∈ R^d,都要求满 足sublayer(x) ∈ R^d,以便残差连接满足x + sublayer(x) ∈ R^d。在残差连接的加法计算之后,紧接着应用层规范化(layer normalization)(Ba et al., 2016)。因此,输入序列对应的每个位置,Transformer编码器都将输出一个d维表示向量。

Transformer 解码器也是由多个相同的层叠加而成的,并且 层中使用了残差连接和层规范化。除了编码器中 描述的两个子层之外,解码器 还在这两个子层之间插入了第三个子层,称为编码器-解码器注意力(encoder‐ decoder attention)层。在编码器-解码器注意力中,查询来自前一个解码器层的输出,而键和值来自整个 编码器的输出。在解码器自注意力中,查询、键和值都来自上一个解码器层的输出。但是,解码器中的每个位置只能考虑该位置之前的所有位置。这种掩蔽(masked)注意力保留了自回归(auto‐regressive)属性,确保预测仅依赖于已生成的输出词元。

在 此 之 前 已 经 描 述 并 实 现 了 基 于 缩 放 点 积 多 头 注 意 力和 位 置 编 码。 接 下 来 将 实 现Transformer模型的剩余部分。

3 基于位置的前馈网络

基于位置的前馈网络对序列中的所有位置的表示 进行变换时使用的是同一个多层感知机(MLP),这就是称 前馈网络是基于位置的(positionwise)的原因。在下面的实现中,输入X的形状(批量大小,时间步数或序列 长度,隐单元数或特征维度)将被一个两层的感知机转换成形状为(批量大小,时间步数,ffn_num_outputs) 的输出张量。

import math
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l

#@save
class PositionWiseFFN(nn.Module):
    """基于位置的前馈网络"""
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)
        
    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))

下面的例子显示,改变张量的最里层维度的尺寸,会改变成基于位置的前馈网络的输出尺寸。因为用同一个 多层感知机对所有位置上的输入进行变换,所以当所有这些位置的输入相同时,它们的输出也是相同的。

ffn = PositionWiseFFN(4, 4, 8)
ffn.eval()
ffn(torch.ones((2, 3, 4)))[0]

# tensor([[ 0.0885,  0.2403, -0.2861, -0.4473,  0.8829, -0.2787, -0.3253, -1.2090],
#         [ 0.0885,  0.2403, -0.2861, -0.4473,  0.8829, -0.2787, -0.3253, -1.2090],
#         [ 0.0885,  0.2403, -0.2861, -0.4473,  0.8829, -0.2787, -0.3253, -1.2090]],
#        grad_fn=<SelectBackward0>)

 

4 残差连接和层规范化

现在让我们关注 Transformer 的加法和规范化(add&norm)组件。正如在本节开头所述,这是由残差连接和紧随其后的层规范化组成的。两者都是构建有效的深度架构的关键。 之前解释了 在一个小批量的样本内基于批量规范化对数据进行重新中心化和重新缩放的调整。层规范化和批量规范化的目标相同,但层规范化是基于特征维度进行规范化。尽管批量规范化在计算机视觉中被广泛 应用,但在自然语言处理任务中(输入通常是变长序列)批量规范化通常不如层规范化的效果好。 以下代码对比不同维度的层规范化和批量规范化的效果。

ln = nn.LayerNorm(2)
bn = nn.BatchNorm1d(2)
X = torch.tensor([[1, 2], [2, 3]], dtype=torch.float32)
# 在训练模式下计算X的均值和方差
print('layer norm:', ln(X), '\nbatch norm:', bn(X))

# layer norm: tensor([[-1.0000,  1.0000],
#         [-1.0000,  1.0000]], grad_fn=<NativeLayerNormBackward0>) 
# batch norm: tensor([[-1.0000, -1.0000],
#         [ 1.0000,  1.0000]], grad_fn=<NativeBatchNormBackward0>)

现在可以 使用残差连接和层规范化 来实现AddNorm类。暂退法也被作为正则化方法 使用。

#@save
class AddNorm(nn.Module):
    """残差连接后进行层规范化"""
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(normalized_shape)
        
    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)

残差连接要求两个输入的形状相同,以便加法操作后输出张量的形状相同。

add_norm = AddNorm([3, 4], 0.5)
add_norm.eval()
add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape
# torch.Size([2, 3, 4])

5 编码器

有了组成Transformer编码器的基础组件,现在可以先实现编码器中的一个层。下面的EncoderBlock类包含 两个子层:多头自注意力和基于位置的前馈网络,这两个子层都使用了残差连接和紧随的层规范化

#@save
class EncoderBlock(nn.Module):
    """Transformer编码器块"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, use_bias=False, **kwargs):
        super(EncoderBlock, self).__init__(**kwargs)
        self.attention = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout,
            use_bias)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(
            ffn_num_input, ffn_num_hiddens, num_hiddens)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        
    def forward(self, X, valid_lens):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
        return self.addnorm2(Y, self.ffn(Y))

正如从代码中所看到的,Transformer编码器中的任何层都不会改变其输入的形状

X = torch.ones((2, 100, 24))
valid_lens = torch.tensor([3, 2])
encoder_blk = EncoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5)
encoder_blk.eval()
encoder_blk(X, valid_lens).shape  # torch.Size([2, 100, 24])

下面实现的Transformer编码器的代码中,堆叠了num_layers个EncoderBlock类 的实例。由于这里使用的是 值范围在−1和1之间的固定位置编码,因此通过学习得到的输入的嵌入表示的值需要先乘以嵌入维度的平方 根进行重新缩放,然后再与位置编码相加。

#@save
class TransformerEncoder(d2l.Encoder):
    """Transformer编码器"""
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, use_bias=False, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                 EncoderBlock(key_size, query_size, value_size, num_hiddens,
                              norm_shape, ffn_num_input, ffn_num_hiddens,
                              num_heads, dropout, use_bias))
    
    def forward(self, X, valid_lens, *args):
        # 因为位置编码值在-1和1之间,
        # 因此嵌入值乘以嵌入维度的平方根进行缩放,
        # 然后再与位置编码相加。
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self.attention_weights = [None] * len(self.blks)
        for i, blk in enumerate(self.blks):
            X = blk(X, valid_lens)
            self.attention_weights[
                i] = blk.attention.attention.attention_weights
        return X

下面我们指定了超参数来 创建一个两层的Transformer编码器。Transformer编码器输出的形状是(批量大小,时间步数目,num_hiddens)。

encoder = TransformerEncoder(
    200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 2, 0.5)
encoder.eval()
encoder(torch.ones((2, 100), dtype=torch.long), valid_lens).shape
# torch.Size([2, 100, 24])

6 解码器

Transformer解码器也是由多个相同的层组成。在DecoderBlock类中实现的 每个层包含了三 个子层解码器自注意力、“编码器‐解码器”注意力和基于位置的前馈网络。这些子层也都被残差连接和紧随的层规范化围绕。

正如在本节前面所述,在掩蔽多头解码器自注意力层(第一个子层)中,查询、键和值都来自上一个解码器 层的输出。关于序列到序列模型(sequence‐to‐sequence model),在训练阶段,其输出序列的所有位置(时 间步)的词元都是已知的;然而,在预测阶段,其输出序列的词元是逐个生成的。因此,在任何解码器时间 步中,只有生成的词元才能用于解码器的自注意力计算中。为了在解码器中保留自回归的属性,其掩蔽自注意力设定了参数dec_valid_lens,以便任何查询都只会与解码器中所有已经生成词元的位置(即直到该查询 位置为止)进行注意力计算。

class DecoderBlock(nn.Module):
    """解码器中第i个块"""
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
                                   num_hiddens)
        self.addnorm3 = AddNorm(norm_shape, dropout)
    
    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]
        # 训练阶段,输出序列的所有词元都在同一时间处理,
        # 因此state[2][self.i]初始化为None。
        # 预测阶段,输出序列是通过词元一个接着一个解码的,
        # 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示
        if state[2][self.i] is None:
            key_values = X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)
        state[2][self.i] = key_values
        if self.training:
            batch_size, num_steps, _ = X.shape
            # dec_valid_lens的开头:(batch_size,num_steps),
            # 其中每一行是[1,2,...,num_steps]
            dec_valid_lens = torch.arange(
                1, num_steps + 1, device=X.device).repeat(batch_size, 1)
        else:
            dec_valid_lens = None
        # 自注意力
        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
        Y = self.addnorm1(X, X2)
        # 编码器-解码器注意力。
        # enc_outputs的开头:(batch_size,num_steps,num_hiddens)
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
        Z = self.addnorm2(Y, Y2)
        return self.addnorm3(Z, self.ffn(Z)), state

为了便于在 “编码器-解码器” 注意力中进行缩放点积计算和残差连接中进行加法计算,编码器和解码器的 特征维度都是 num_hiddens

decoder_blk = DecoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5, 0)
decoder_blk.eval()
X = torch.ones((2, 100, 24))
state = [encoder_blk(X, valid_lens), valid_lens, [None]]
decoder_blk(X, state)[0].shape
# torch.Size([2, 100, 24])

现在我们构建了 由num_layers个DecoderBlock实例组成的完整的Transformer解码器。最后,通过一个全连接层计算所有vocab_size个可能的输出词元的预测值。解码器的自注意力权重和编码器解码器注意力权重都被存储下来,方便日后可视化的需要。

class TransformerDecoder(d2l.AttentionDecoder):
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                 DecoderBlock(key_size, query_size, value_size, num_hiddens,
                              norm_shape, ffn_num_input, ffn_num_hiddens,
                              num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)
    
    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range (2)]
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            # 解码器自注意力权重
            self._attention_weights[0][
                i] = blk.attention1.attention.attention_weights
            # “编码器-解码器”自注意力权重
            self._attention_weights[1][
                i] = blk.attention2.attention.attention_weights
        return self.dense(X), state

    @property
    def attention_weights(self):
        return self._attention_weights

7 执行训练

依照Transformer架构来实例化编码器-解码器模型。在这里,指定Transformer的编码器和解码器都是2层, 都使用4头注意力。为了进行序列到序列的学习,下面在“英语-法语”机器翻译数据集上 训练Transformer模型。

num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10
lr, num_epochs, device = 0.005, 200, d2l.try_gpu()
ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
key_size, query_size, value_size = 32, 32, 32
norm_shape = [32]

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = TransformerEncoder(
    len(src_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
decoder = TransformerDecoder(
    len(tgt_vocab), key_size, query_size, value_size, num_hiddens,
    norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
    num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

训练结束后,使用Transformer模型将一些英语句子翻译成法语,并且计算它们的BLEU分数。

engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, dec_attention_weight_seq = d2l.predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device, True)
    print(f'{eng} => {translation}, ',
          f'bleu {d2l.bleu(translation, fra, k=2):.3f}')
    
# go . => va !, bleu 1.000
# i lost . => j'ai perdu ., bleu 1.000
# he's calm . => il est calme ., bleu 1.000
# i'm home . => je suis chez moi ., bleu 1.000

当进行最后一个英语到法语的句子翻译工作时,让我们可视化Transformer的注意力权重。编码器自注意力 权重的形状为(编码器层数,注意力头数,num_steps或查询的数目,num_steps或“键-值”对的数目)。

enc_attention_weights = torch.cat(net.encoder.attention_weights, 
                                  0).reshape((num_layers, num_heads,-1, num_steps))
enc_attention_weights.shape  # torch.Size([2, 4, 10, 10])

在编码器的自注意力中,查询和键都来自相同的输入序列。因为填充词元是不携带信息的,因此通过指定输 入序列的有效长度可以避免查询与使用填充词元的位置计算注意力。接下来,将逐行呈现两层多头注意力的 权重。每个注意力头都根据查询、键和值的不同的表示子空间来表示不同的注意力

d2l.show_heatmaps(
    enc_attention_weights.cpu(), xlabel='Key positions',
    ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],
    figsize=(7, 3.5))

为了可视化解码器的自注意力权重和“编码器-解码器”的注意力权重,我们需要完成更多的数据操作工作。 例如用零填充被掩蔽住的注意力权重。值得注意的是,解码器的自注意力权重和“编码器-解码器”的注意 力权重都有相同的查询:即 以序列开始词元(beginning‐of‐sequence,BOS)打头,再与后续输出的词元共同组成序列。

dec_attention_weights_2d = [head[0].tolist()
                            for step in dec_attention_weight_seq
                            for attn in step for blk in attn for head in blk]
dec_attention_weights_filled = torch.tensor(
    pd.DataFrame(dec_attention_weights_2d).fillna(0.0).values)
dec_attention_weights = dec_attention_weights_filled.reshape((-1, 2, num_layers, 
                                                              num_heads, num_steps))
dec_self_attention_weights, dec_inter_attention_weights = \
    dec_attention_weights.permute(1, 2, 3, 0, 4)
dec_self_attention_weights.shape, dec_inter_attention_weights.shape
# (torch.Size([2, 4, 6, 10]), torch.Size([2, 4, 6, 10]))

由于解码器自注意力的自回归属性,查询不会对当前位置之后的“键-值”对进行注意力计算。

# Plusonetoincludethebeginning-of-sequencetoken
d2l.show_heatmaps(
    dec_self_attention_weights[:, :, :, :len(translation.split()) + 1],
    xlabel='Key positions', ylabel='Query positions',
    titles=['Head %d' % i for i in range(1, 5)], figsize=(7, 3.5))

与编码器的自注意力的情况类似,通过指定输入序列的有效长度,输出序列的查询不会与输入序列中填充位置的词元进行注意力计算

d2l.show_heatmaps(
    dec_inter_attention_weights, xlabel='Key positions',
    ylabel='Query positions', titles=['Head %d' % i for i in range(1, 5)],
    figsize=(7, 3.5))

尽管Transformer架构是为了序列到序列的学习而提出的,但正如本书后面将提及的那样,Transformer编码 器或Transformer解码器通常被单独用于不同的深度学习任务中。

小结:

  • Transformer是编码器-解码器架构的一个实践,尽管 在实际情况中编码器或解码器可以单独使用
  • 在Transformer中,多头自注意力用于表示输入序列和输出序列,不过解码器必须通过掩蔽机制来保留 自回归属性
  • Transformer中的 残差连接和层规范化 是训练非常深度模型的重要工具。
  • Transformer模型中 基于位置的前馈网络使用同一个多层感知机,作用是 对所有序列位置的表示进行转换

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

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

相关文章

Llama3本地部署与高效微调入门

前言 为了保持公司在AI&#xff08;人工智能&#xff09;开源大模型领域的地位&#xff0c;社交巨头Meta推出了旗下最新开源模型。当地时间4月18日&#xff0c;Meta在官网上宣布公布了旗下最新大模型Llama 3。目前&#xff0c;Llama 3已经开放了80亿&#xff08;8B&#xff09…

8086 汇编学习 Part 9

端口的读写 CPU 的邻居 CPU 内部的寄存器内存单元端口&#xff08;各种接口卡、网卡&#xff0c;显卡&#xff0c;主板上的接口芯片等&#xff09; 各种芯片工作时&#xff0c;都有一些寄存器由 CPU 读写从 CPU 角度&#xff0c;将各寄存器当端口&#xff0c;并统一编制CPU …

开源im即时通讯app源码系统/php即时聊天im源码/php+uniapp框架【终身使用】

摘要 随着开源文化的蓬勃发展&#xff0c;即时通讯(IM)系统作为现代通信不可或缺的一部分&#xff0c;其开源实现正变得越来越普遍。本文将深入探讨基于PHP的全开源即时通讯源码系统&#xff0c;并结合UniApp开源框架&#xff0c;从理论基础到代码实现&#xff0c;再到实际应用…

探索设计模式的魅力:分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索分布式模式之旅✨ 在数字化时代&#xff0c;企业面临着前所未有的挑战和机遇。…

机器学习-K近邻算法(KNN)

目录 什么是KNN算法 图解KNN基本算法 &#xff08;1&#xff09;k近邻算法中k的选取 &#xff08;2&#xff09;距离函数 &#xff08;3&#xff09;归一化处理 &#xff08;4&#xff09;概率kNN KNN算法的优缺点 优势 缺点 KNN算法总结 什么是KNN算法 k近邻算法&…

vs 2022 Xamarin 生成 Android apk

再保存&#xff0c;如果没有生成apk就重启软件 再试一次

(论文阅读-优化器)Volcano-An Extensible and Parallel Query Evaluation System

目录 摘要 一、简介 三、火山模型系统设计 3.1 文件系统 3.2 查询处理 四、扩展性 五、动态查询评估计划 六、多处理器查询评估 6.1 垂直并行化 6.2 水平并行化Horizontal 6.3 exchange operator的变体 6.4 文件系统修改 七、总结 摘要 火山模型Volcano在数据库查…

详解LLMOps,将DevOps用于大语言模型开发

大家好&#xff0c;在机器学习领域&#xff0c;随着技术的不断发展&#xff0c;将大型语言模型&#xff08;LLMs&#xff09;集成到商业产品中已成为一种趋势&#xff0c;同时也带来了许多挑战。为了有效应对这些挑战&#xff0c;数据科学家们转向了一种新型的DevOps实践LLM-OP…

Maven 在项目的 pom.xml 文件中 指定 阿里云的景象仓库

配置 在 项目的 pom.xml 文件中添加如下配置即可 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&…

Android NDK开发——Android Studio 3.5.2安装与配置踩坑

Android NDK开发——Android Studio 3.5.2安装与配置踩坑 一、Android Studio下载二、配置踩坑报错1&#xff1a;Failed to install the following Android SDK packages as some licences have not been accepted报错2&#xff1a;No toolchains found in the NDK toolchains …

如何修复连接失败出现的错误651?这里提供修复方法

错误651消息在Windows 7到Windows 11上很常见&#xff0c;通常会出现在一个小的弹出窗口中。实际文本略有不同&#xff0c;具体取决于连接问题的原因&#xff0c;但始终包括文本“错误651”。 虽然很烦人&#xff0c;但错误651是一个相对较小的问题&#xff0c;不应该导致计算…

UI组件库和内容文字的中英文切换

同时实现UI组件库(这里以ElementPlus为例)和内容文字的中英文切换 1. 安装vueI18n和element-plus pnpm i vue-i18n element-plus 2. 然后在项目中src目录下新建lang文件夹&#xff0c;里面新建en.ts和zh.ts还有index.ts index.ts import { createI18n } from vue-i18n impor…

【Android】Android应用性能优化总结

AndroidApp应用性能优化总结 最近大半年的时间里&#xff0c;大部分投在了某国内新能源汽车的某款AndroidApp开发上。 由于该App是该款车上&#xff0c;常用重点应用。所以车厂对应用性能的要求比较高。 主要包括&#xff1a; 应用冷启动达到***ms。应用热(温)启动达到***ms应…

【备战软考(嵌入式系统设计师)】07 - 计算机网络模型

七层模型 计算机网络中比较常见的有OSI七层模型和TCP/IP四层模型。 软考中主要考七层模型&#xff0c;但是实际中使用的还是四层模型比较多&#xff0c;我们主要是为了考试&#xff0c;那就主要讲讲七层模型。不过实际上四层模型就是将七层模型压缩了三层&#xff0c;本质上是…

Nginx(参数设置总结)

文章目录 Nginx&#xff08;工作机制&参数设置&#xff09;1.Master&Worker工作机制1.示意图2.解释3.Nginx争抢机制4.accept_mutex解决惊群现象5.多进程结构不用多线程结构的好处6.IO多路复用&#xff0c;实现高并发7.优势 2.参数配置1.work_processes1.基本介绍2.work…

杭电acm2018 母牛的故事 Java解法 经典递归

标准递归题 先模拟 接着找递归出口 再找递归通式 想想看 今天的母牛等于前一天的母牛数加上今天出生的母牛 而三天前的母牛所有母牛都能生一头 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scnew Scanner(System.in);l…

交互中的“互”难以产生的原因

脑机交互技术的目标是通过分析和解读大脑活动&#xff0c;将其与特定的意图、指令或行为连接起来。通过训练和分析&#xff0c;可以建立起大脑活动与特定行为或意图之间的关联模型&#xff0c;从而实现脑机交互的应用&#xff0c;例如控制外部设备、传递信息等。然而&#xff0…

unaipp推荐算法的汽车租赁系统zaxzu 微信小程序hbuiderx

随着现代汽车租赁管理的快速发展&#xff0c;可以说汽车租赁管理已经逐渐成为现代汽车租赁管理过程中最为重要的部分之一。但是一直以来我国传统的汽车租赁管理并没有建立一套完善的行之有效的汽车租赁管理系统&#xff0c;传统的汽车租赁管理已经无法适应高速发展&#xff0c;…

Django中如何让页面之间建立关系

今天给大家讲解两种让页面建立联系的方式 一、重定向 二、表单提交 先看第一种方式&#xff0c;重定向 首先需要了解客户端发起请求的过程 1、客户端向服务端发起请求,比如请求地址是&#xff1a;http://127.0.0.1:8000/lili/submit/ 2、程序根据路由找到视图函数 3、执行视…

从一到无穷大 #26 Velox:Meta用cpp实现的大一统模块化执行引擎

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言业务案例PrestoSparkXStreamDistributed messaging systemData IngestionData Pr…