当前位置: 首页 > news >正文

Transformer简述和实现

Transformer

1、概述

(一)、诞生

自从2017年此文《Attention is All You Need》提出来Transformer后,便开启了大规模预训练的新时代,也在历史的长河中一举催生出了GPT、BERT这样的里程碑模型。

(二)、优势

相比之前占领市场的LSTM和GRU模型,Transformer有两个显著的优势:

  • Transformer能够利用分布式GPU进行并行训练,提升模型训练效率.
  • 在分析预测更长的文本时, 捕捉间隔较长的语义关联效果更好.

2、架构

(一)、模型作用

  • 基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
  • 在接下来的架构分析中, 我们将假设使用Transformer模型架构处理从一种语言文本到另一种语言文本的翻译工作, 因此很多命名方式遵循NLP中的规则. 比如: Embeddding层将称作文本嵌入层, Embedding层产生的张量称为词嵌入张量, 它的最后一维将称作词向量等.

(二)、总体架构图

在这里插入图片描述

transformer总体架构:输入部分,输出部分,编码器部分,解码器部分

3、输入部分

在这里插入图片描述

(一)、文本嵌入式作用

无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系

# 导入必备的工具包
import torch
# 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层, 
# 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
import torch.nn as nn
# 数学计算工具包
import math
# torch中变量封装函数Variable.
from torch.autograd import Variable# Embeddings类 实现思路分析
# 1 init函数 (self, d_model, vocab)# 设置类属性 定义词嵌入层 self.lut层
# 2 forward(x)函数# self.lut(x) * math.sqrt(self.d_model)
# 词嵌入类
class Embedding(nn.Module):def __init__(self, d_model, vocab_size):'''初始化embedding编码模型和词嵌入维度:param d_model: 词嵌入维度:param vocab_size: 词表大小'''super().__init__()# 初始化词嵌入对象self.lcut = nn.Embedding(vocab_size, d_model)# 初始化词嵌入维度self.d_model = d_modeldef forward(self, x):'''前向传播,对词表进行编码和放大:param x: 输入的原始信息:return: 放大后的编码'''# 对token进行编码并放大return self.lcut(x) * math.sqrt(self.d_model)

(二)、位置编码器的作用

因为在Transformer的编码器结构中, 所有输入的词语并行训练,并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失。

为什么选择使用sin和cos来作为位置编码?

  • 绝对位置编码(0-512):冲淡文本嵌入信息,模型外推性差,当超出设置长度,无法表示位置。(ROPE alibi)
  • 对绝对位置编码进行归一化:512/512=1 511/512约=1,相邻token位置信息区别不大
  • 为什么不单独用一个三角函数:三角函数的周期,周期大,相邻token位置信息区别不大,周期小,间隔的token可能位置信息相似甚至相等
# 位置编码器类PositionalEncoding 实现思路分析
# 1 init函数  (self, d_model, dropout, max_len=5000)
#   super()函数 定义层self.dropout
#   定义位置编码矩阵pe  定义位置列-矩阵position 定义变化矩阵div_term
#   套公式div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0)/d_model))
#   位置列-矩阵 * 变化矩阵 哈达码积my_matmulres
#   给pe矩阵偶数列奇数列赋值 pe[:, 0::2] pe[:, 1::2]
#   pe矩阵注册到模型缓冲区 pe.unsqueeze(0)三维 self.register_buffer('pe', pe)
# 2 forward(self, x) 返回self.dropout(x)
#   给x数据添加位置特征信息 x = x + Variable( self.pe[:,:x.size()[1]], requires_grad=False)class PositionalEncoding(nn.Module):def __init__(self, d_model, dropout, max_len=5000):# 参数d_model 词嵌入维度 eg: 512个特征# 参数max_len 单词token个数 eg: 60个单词super(PositionalEncoding, self).__init__()# 定义dropout层self.dropout = nn.Dropout(p=dropout)# 思路:位置编码矩阵 + 特征矩阵 相当于给特征增加了位置信息# 定义位置编码矩阵PE eg pe[60, 512], 位置编码矩阵和特征矩阵形状是一样的pe = torch.zeros(max_len, d_model)# 定义位置列-矩阵position  数据形状[max_len,1] eg: [0,1,2,3,4...60]^Tposition = torch.arange(0, max_len).unsqueeze(1)# 保存绝对位置信息# print('position--->', position.shape, position)# 定义变化矩阵div_term [1,256]# torch.arange(start=1, end=512, 2)结果并不包含end。在start和end之间做一个等差数组 [0, 2, 4, 6 ... 510]div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) # 位置列-矩阵 @ 变化矩阵 做矩阵运算 [60*1]@ [1*256] ==> 60 *256# 矩阵相乘也就是行列对应位置相乘再相加,其含义,给每一个列属性(列特征)增加位置编码信息my_matmulres = position * div_term# print('my_matmulres--->', my_matmulres.shape, my_matmulres)# 给位置编码矩阵奇数列,赋值sin曲线特征pe[:, 0::2] = torch.sin(my_matmulres)# 给位置编码矩阵偶数列,赋值cos曲线特征pe[:, 1::2] = torch.cos(my_matmulres)# 形状变化 [60,512]-->[1,60,512]pe = pe.unsqueeze(0)# 把pe位置编码矩阵 注册成模型的持久缓冲区buffer; 模型保存再加载时,可以根模型参数一样,一同被加载# 什么是buffer: 对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不参与模型训练self.register_buffer('pe', pe)def forward(self, x):# 注意:输入的x形状2*4*512  pe是1*60*512 形状 如何进行相加# 只需按照x的单词个数 给特征增加位置信息x = x + Variable( self.pe[:,:x.size()[1]], requires_grad=False)return self.dropout(x)

4、编码器部分*

在这里插入图片描述

由N个编码器层堆叠而成

每个编码器层由两个子层连接结构组成

第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接

第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

(一)、注意力机制

(1)、注意力计算

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{Q·K^T}{\sqrt{d_k}}·V) Attention(Q,K,V)=Softmax(dk QKTV)

为什么除以根号下dk?

假设:输入x (2,4,512),分别经过三个线性层,Q、K、V (2,4,512)—> Q@K^T (2,4,4)
词嵌入维度 dk=512
前提假设 Q、K 满足标准正态分布,(均值)期望0,方差1, E(QK)=0, Var(QK)=dk, 把方差dk拉回到1,除以标准差(根号下dk)
可以解决:

  • 内部协变量偏移问题 输入输出最好是同分布的,好处是: 需要的data更少,训练时间更短
  • f = softmax(xi),f‘ = f(1-f)(i=j);f’ = -fi*fj (j!=i),dk越大,方差越大,数据越分散,大的很大,小的很小,再经过softmax,大的变成1,小的变成了0,类似onehot编码形式,把这些值带入softmax导数公式,梯度接近为0,梯度消失问题,模型可训练性非常差
(2)、实现
# 实现单头attn
def attention(query, key, value, dropout=None, mask=None):'''该函数实现单头注意力机制计算:param query:Q:param key:K:param value:V:param dropout:随机失活函数对象:param mask:掩码矩阵padding mask(遮蔽填充的0);subsquent mask(解码并行):return:处理后的数据,权重概率矩阵'''# 拿到d_k,是一个头词嵌入的维度d_k = query.size(-1)# 先将Q·K^T,再除以sqrt(d_k)scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)# 使用掩码矩阵对原始信息进行遮掩。masked_fill->将目标数字替换为第二个参数。# 此处替换是因为如果是原始0,在softmax转换后分子为1,替换为一个负值后,分子e^i极小,几乎不对结果概率产生影响if mask is not None:scores = scores.masked_fill(mask == 0, -1e9)# 将上述的结果经过softmax得到权重概率矩阵p_attn = F.softmax(scores, dim=-1)# 对权重概率矩阵做随机失活if dropout is not None:p_attn = dropout(p_attn)# 返回处理后的数据和权重概率矩阵return torch.matmul(p_attn, value), p_attn

(二)、多头注意力机制

(1)、作用

多头注意力机制的理解:1、代码;2、原理
a、注意力计算公式,(2,4,512)—》(2,4,8,64)—》(2,8,4,64)
b、512,划分成了8个细分子空间,每个子空间就是一个观察角度,特征抽取会更丰富更全面,盲人摸象
c、降低了计算复杂度,4*512*4*512,变成了 8 个 4*64*4*64

(2)、实现
# 克隆函数
def clones(module, N):'''复制N个目标对象module:param module: 目标module:param N: 复制N个:return: 返回一个内部有N个module对象列表'''# 对目标对象进行深拷贝,拷贝N份,再将放到到一个列表中输出return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])# 多头注意力机制类
class MutiHeadAttn(nn.Module):def __init__(self, head, embedding_dim, dropout=0.1):'''初始化多头注意力机制参数:param head: 多少头:param embedding_dim: 词向量长度:param dropout: 随机失活比例'''super().__init__()# 因为多头必须均分词向量,所以不能整除就不继续往下走assert embedding_dim % head == 0# 计算每个头的d_kself.d_k = embedding_dim // head# 复制头数self.head = head# 初始化注意力self.attn = None# 初始化随机失活对象self.dropout = nn.Dropout(dropout)# 初始化4个线性层,计算QKV使用3个,最后的x需要再经过1个self.linears = clones(nn.Linear(embedding_dim, embedding_dim), 4)def forward(self, query, key, value, mask=None):'''实现多头注意力机制,通过输入的QKV控制是自注意力机制还是一般注意力机制:param query: Q:param key: K:param value: V:param mask: 掩码矩阵:return: 返回多头处理后的x'''# 对掩码矩阵进行升维,为了和多头注意力矩阵处理if mask is not None:mask = mask.unsqueeze(0)# 获得batch_sizebatch_size = query.size(0)'''q,k,v 三者的维度都是 (batch_size, head, seq_len, d_k)  d_k就是分为多个头之后的维度 64transpose(1, 2) 是为了让 seq-len(代表的token)与 d_k 相邻 才有意义,同时方便计算此处的-1是为了动态适应句子长度,因为句子长度都不一样,用-1自适应更加灵活'''query, key, value = [model(x).view(batch_size, -1, self.head, self.d_k).transpose(1, 2)for model, x in zip(self.linears, (query, key, value))]# 利用单头注意力函数计算处理后的数据x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)# 先(batch_size, head, seq_len, d_k)-》(batch_size, seq_len, head, d_k)# 将处理后的内存合并,再将数据降维回三维x = x.transpose(1, 2).contiguous().view(batch_size, -1, self.head * self.d_k)# 使用第4个线性层处理原始数据return self.linears[-1](x)
(3)、小结
  • 多头注意力机制:
    • 每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头.将每个头的获得的输入送到注意力机制中, 就形成了多头注意力机制.
  • 多头注意力机制的作用:
    • 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.
  • 实现多头注意力机制的类: MultiHeadedAttention
    • 因为多头注意力机制中需要使用多个相同的线性层, 首先实现了克隆函数clones.
    • clones函数的输入是module,N,分别代表克隆的目标层,和克隆个数.
    • clones函数的输出是装有N个克隆层的Module列表.
    • 接着实现MultiHeadedAttention类, 它的初始化函数输入是h, d_model, dropout分别代表头数,词嵌入维度和置零比率.
    • 它的实例化对象输入是Q, K, V以及掩码张量mask.
    • 它的实例化对象输出是通过多头注意力机制处理的Q的注意力表示

(三)、前馈全连接层

(1)、作用

作用:考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力

(2)、实现
# feed_forward前馈全连接层
class PositionwiseFeedForward(nn.Module):def __init__(self, d_model, d_ff, dropout=0.1):'''增加两层线性层来增加模型的拟合能力:param d_model: 词嵌入维度:param d_ff: 中间过度维度:param dropout: 随机失活率'''super().__init__()# 第一层线性层,将词向量维度转为中间过渡维度self.w1 = nn.Linear(d_model, d_ff)# 第二层线性层,将中间过度维度转为词向量维度self.w2 = nn.Linear(d_ff, d_model)# 随机失活率self.dropout = nn.Dropout(dropout)def forward(self, x):'''将数据经过两层线性层、relu激活函数和随机失活:param x: 上一层输入的数据:return: 经过线性层处理的数据'''# 线性层-》relu激活-》随机失活-》线性层return self.w2(self.dropout(F.relu(self.w1(x))))
(3)、小结
  • 前馈全连接层:
    • 在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
  • 前馈全连接层的作用:
    • 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
  • 实现前馈全连接层的类: PositionwiseFeedForward
    • 它的实例化参数为d_model, d_ff, dropout, 分别代表词嵌入维度, 线性变换维度, 和置零比率.
    • 它的输入参数x, 表示上层的输出.
    • 它的输出是经过2层线性网络变换的特征表示

(四)、规划化层

(1)、作用

它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.

norm的作用:

1、解决内部协变量偏移问题;

2、把数据分布拉回到激活函数的非饱和区

norm会不会丢失网络学到的参数:不会。

1、norm改变的是数据的空间分布,不改变数据内部的相对大小

2、增加了两个可学习参数:缩放系数、偏移系数,分别决定方差、期望,让模型自行决定缩放、偏移多少

(2)、实现
# 规范化层
class LayerNorm(nn.Module):def __init__(self, features, esp=1e-6):'''初始化规划化层的数据:param features: 词向量维度:param esp: 很小的值,防止在计算时数据违法'''super().__init__()# 词向量维度self.size = features# 将a2定义为一个随着模型更新的参数self.a2 = nn.Parameter(torch.ones(features))# 将b2定义为一个随着模型更新的参数self.b2 = nn.Parameter(torch.zeros(features))# 很小的值self.esp = espdef forward(self, x):'''规范化/标准化,相当于BN层:param x::return:'''# 对数据的最后一维做均值计算mean = x.mean(-1, keepdim=True)# 对数据做标准差计算std = x.std(-1, keepdim=True)# 用均值和标准差对数据做标准化,再用反向传播的a2和b2做学习的比例和偏移return self.a2 * (x - mean) / (std + self.esp) + self.b2
(3)、小结
  • 规范化层的作用:
    • 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
  • 实现规范化层的类: LayerNorm
    • 它的实例化参数有两个, features和eps,分别表示词嵌入特征大小,和一个足够小的数.
    • 它的输入参数x代表来自上一层的输出.
    • 它的输出就是经过规范化的特征表示

(五)、子层连接结构

(1)、概述

输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构),在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构

(2)、实现
# 子层连接结构 子层(前馈全连接层 或者 注意力机制层)+ norm层 + 残差连接
# SublayerConnection实现思路分析
# 1 init函数  (self, size, dropout=0.1):# 定义self.norm层 self.dropout层, 其中LayerNorm(size)
# 2 forward(self, x, sublayer) 返回+以后的结果# 数据self.norm() -> sublayer()->self.dropout() + x# 子层连接结构
class SublayerConnection(nn.Module):def __init__(self, size, dropout=0.1):'''初始化归一化对象,初始化随机失活比例:param size: 词嵌入维度:param dropout: 随机失活参数'''super().__init__()# 初始化归一化对象self.norm = LayerNorm(size)# 初始化随机失活对象self.dropout = nn.Dropout(dropout)def forward(self, x, sublayer):'''将原始数据和:param x: 原始数据(刚经过位置编码):param sublayer: 处理数据对象,如多头注意力层:return: 处理后的数据'''# 残差连接,将原始数据与处理后的数据相加res = x + self.dropout(sublayer(self.norm(x)))# 方法二# res = x + self.dropout(self.norm(x.subtype(x)))# 返回处理后的数据return res
(3)、小结
  • 子层连接结构:
    • 如图所示,输入到每个子层以及规范化层的过程中,还使用了残差链接(跳跃连接),因此我们把这一部分结构整体叫做子层连接(代表子层及其链接结构), 在每个编码器层中,都有两个子层,这两个子层加上周围的链接结构就形成了两个子层连接结构.
  • 实现子层连接结构的类: SublayerConnection
    • 类的初始化函数输入参数是size, dropout, 分别代表词嵌入大小和置零比率.
    • 它的实例化对象输入参数是x, sublayer, 分别代表上一层输出以及子层的函数表示.
    • 它的输出就是通过子层连接结构处理的输出.

(六)、编码器层

(1)、作用

编码器的组成单元,每个编码器层完成一次对输入特征的提取过程,即编码过程

(2)、实现
# 编码器层
class EncoderLayer(nn.Module):def __init__(self, size, self_attn, ffn, dropout=0.1):'''初始化各类对象:param size: 词嵌入维度:param self_attn: 注意力层对象:param ffn: 前馈全连接层对象:param dropout: 随机失活参数'''super().__init__()# 初始化词嵌入维度self.size = size# 初始化注意力层对象self.self_attn = self_attn# 初始化前馈全连接层对象self.ffn = ffn# 复制两个子层连接结构self.sublayer = clones(SublayerConnection(size), 2)def forward(self, x, mask=None):'''构建一个完整的编码器层:param x: 经过位置编码后的数据:param mask: 掩码矩阵:return: 处理后的数据'''# 计算自注意力机制后(因为qkv原始数据相等),再经过一次残差连接后输出数据x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))# 计算前馈全连接层后,经过一次残差连接后输出数据x = self.sublayer[1](x, self.ffn)return x
(3)、小结
  • 编码器层的作用:
    • 作为编码器的组成单元, 每个编码器层完成一次对输入的特征提取过程, 即编码过程.
  • 实现编码器层的类: EncoderLayer
    • 类的初始化函数共有4个, 别是size,其实就是我们词嵌入维度的大小. 第二个self_attn,之后我们将传入多头自注意力子层实例化对象, 并且是自注意力机制. 第三个是feed_froward, 之后我们将传入前馈全连接层实例化对象. 最后一个是置0比率dropout.
    • 实例化对象的输入参数有2个,x代表来自上一层的输出, mask代表掩码张量.
    • 它的输出代表经过整个编码层的特征表示

(七)、编码器

(1)、作用

对于输入进行指定的特征提取过程,也称为编码,由多个编码器层堆叠构成

(2)、实现
# 编码器
class Encoder(nn.Module):def __init__(self, layer, N):'''复制N份编码器模型:param layer: 编码器对象:param N: 复制多少个'''super().__init__()# 复制N个编码器对象,并放到一个列表中self.layers = clones(layer, N)# 定义规范化对象self.norm = LayerNorm(layer.size)def forward(self, x, mask):'''构建多个编码器层构成的编码器:param x: 经过位置编码后的数据:param mask: 掩码矩阵:return: 经过多层编码器层处理,再经过一层规范化的数据'''# 将数据在N个编码器层中串行处理for layer in self.layers:x = layer(x, mask)# 将上面处理的数据进行规范化return self.norm(x)
(3)、小结
  • 编码器的作用:
    • 编码器用于对输入进行指定的特征提取过程, 也称为编码, 由N个编码器层堆叠而成.
  • 实现编码器的类: Encoder
    • 类的初始化函数参数有两个,分别是layer和N,代表编码器层和编码器层的个数.
    • forward函数的输入参数也有两个, 和编码器层的forward相同, x代表上一层的输出, mask代码掩码张量.
    • 编码器类的输出就是Transformer中编码器的特征提取表示, 它将成为解码器的输入的一部分.

5、解码器部分

  • 由N个解码器层堆叠而成
  • 每个解码器层由三个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
  • 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

(一)、掩码机制

(1)、概述

掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量

(2)、作用

在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩

(3)、实现
# 下三角矩阵作用: 生成字符时,希望模型不要使用当前字符和后面的字符。# 使用遮掩mask,防止未来的信息可能被提前利用# 实现方法: 1 - 上三角矩阵
# 函数 subsequent_mask 实现分析
# 产生上三角矩阵 np.triu(m=np.ones((1, size, size)), k=1).astype('uint8')
# 返回下三角矩阵 torch.from_numpy(1 - my_mask )
# 实现掩码张量
def subsquent_mask(seq_len):'''构造掩码矩阵,目的是在解码器预测时掩盖未来信息:param seq_len: 句子长,因为掩码步骤在Q*K^T后,所以矩阵大小就是句子长:return: 返回目标掩码矩阵'''# 定义掩码矩阵大小,1是因为可以广播,seq_len是Q*K^T后,矩阵大小就是句子长attn_shape = (1, seq_len, seq_len)# triu函数是保留右上半区# subsquent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')# return torch.from_numpy(1 - subsquent_mask)# tril函数是保留左下半区subsquent_mask = np.tril(np.ones(attn_shape), k=0).astype('uint8')return torch.from_numpy(subsquent_mask)
(4)、小结
  • 掩码张量:
    • 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩, 也可以说被替换, 它的表现形式是一个张量.
  • 掩码张量的作用:
    • 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attetion张量中的值计算有可能已知量未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.
  • 实现生成向后遮掩的掩码张量函数: subsequent_mask
    • 它的输入是size, 代表掩码张量的大小.
    • 它的输出是一个最后两维形成1方阵的下三角阵.
    • 最后对生成的掩码张量进行了可视化分析, 更深一步理解了它的用途.

(二)、解码器层

(1)、作用

解码器的组成单元,每个解码器层根据给定的输入向目标进行特征提取操作,即解码过程。

(2)、实现
# 解码器层
class DecoderLayer(nn.Module):def __init__(self, size, self_attn, src_attn, ffn, dropout=0.1):'''构建出一个完整的解码器层所需的数据和类:param size: 词向量维度:param self_attn: 自注意力机制对象:param src_attn: 一般注意力机制对象:param ffn: 前馈全连接层对象:param dropout: 随机失活率'''super().__init__()# 初始化词向量维度self.size = size# 自注意力机制对象self.self_attn = self_attn# 一般注意力机制对象self.src_attn = src_attn# 前馈全连接层对象self.ffn = ffn# 克隆三份自连接结构层self.sublayer = clones(SublayerConnection(size), 3)def forward(self, x, memory, source_mask, target_mask):'''构建出一个完整的解码器层:param x: 目标语言经过位置编码后的数据:param memory: 中间语义张量c:param source_mask: 自有语言的掩码矩阵:param target_mask: 目标语言的掩码矩阵:return: 解码数据'''# 中间语义张量,也就是编码器最后输出的值m = memory# 输入目标语言带位置编码的数据,经过自注意力层,规范化层,残差连接x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, target_mask))# 将上面输出的数据作为Q输入,KV为编码器输出,计算一般注意力权重,在过规范化层,残差连接x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, source_mask))# 计算前馈全连接层后,经过一次残差连接后输出数据x = self.sublayer[2](x, self.ffn)return x
(3)、小结
  • 解码器层的作用:
    • 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
  • 实现解码器层的类: DecoderLayer
    • 类的初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
    • forward函数的参数有4个,分别是来自上一层的输入x,来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
    • 最终输出了由编码器输入和目标数据一同作用的特征提取结果.

(三)、解码器

(1)、作用

编码器的结果以及上一次预测的结果, 对下一次可能出现的’值’进行特征表示

(2)、实现
# 解码器
class Decoder(nn.Module):def __init__(self, layer, N):'''初始化出N个解码器层:param layer: 解码器层对象:param N: 需要几个解码器层'''super().__init__()# 复制出N个解码器层对象self.layers = clones(layer, N)# 规范化层对象self.norm = LayerNorm(layer.size)def forward(self, x, memory, source_mask, target_mask):'''构建多个解码器层构成的解码器:param x: 经过位置编码后的数据:param memory: 中间语义张量c,也就是编码器的输出:param source_mask: 源语言的掩码矩阵:param target_mask: 目标语言的掩码矩阵:return: 规划化后的预测矩阵'''# 经过N个解码器层,对数据进行解码for layer in self.layers:x = layer(x, memory, source_mask, target_mask)# 对数据进行规范化return self.norm(x)
(3)、小结
  • 解码器的作用:
    • 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的’值’进行特征表示.
  • 实现解码器的类: Decoder
    • 类的初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N.
    • forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,src_mask, tgt_mask代表源数据和目标数据的掩码张量.
    • 输出解码过程的最终特征表示

6、输出部分

  • 线性层
  • softmax层
# 输出部分
class Generator(nn.Module):def __init__(self, d_model, vocab_size):'''初始化线性层:param d_model: 输入词嵌入维度:param vocab_size: 输出词嵌入维度'''super().__init__()# 初始化线性层self.project = nn.Linear(d_model, vocab_size)def forward(self, x):'''输出目标数据:param x: 编码器的输出数据:return: 最终输出数据'''# 将编码器输出的数据经过线性层后,对最后一维做softmaxreturn F.log_softmax(self.project(x), dim=-1)

实现线性层和softmax层的类: Generator

  • 初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小.
  • forward函数接受上一层的输出.
  • 最终获得经过线性层和softmax层处理的结果.

7、模型构建

(一)、实现编码器和解码器

# 编码器-解码器联合
class EncoderDecoder(nn.Module):def __init__(self, encoder, decoder, source_embed, target_embed, generator):'''初始化解码器、编码器、源输入、目标输入、输出对象:param encoder: 编码器对象:param decoder: 解码器对象:param source_embed: 源输入对象:param target_embed: 目标输入对象:param generator: 输出对象'''super().__init__()# 初始化编码器对象self.encoder = encoder# 初始化解码器对象self.decoder = decoder# 初始化源输入对象(包含位置编码)self.src_embed = source_embed# 初始化目标输入对象(包含位置编码)self.tgt_embed = target_embed# 初始化输出对象self.generator = generatordef forward(self, source, target, source_mask, target_mask):'''将源数据和目标数据输入后解码:param source: 源数据:param target: 目标数据:param source_mask: 源数据掩码矩阵:param target_mask: 目标数据掩码矩阵:return: 解码后的数据'''# 编码器的输出作为解码器的QK,其他的和解码器一样,再将解码器输出给输出层,最终返回最终结果return self.generator(self.decode(self.encode(source, source_mask), source_mask, target, target_mask))def encode(self, source, source_mask):'''构造编码器:param source: 源数据:param source_mask: 源数据掩码矩阵:return: 编码后的数据'''# 先将原数据进行编码(包括位置编码和掩码),再进行编码return self.encoder(self.src_embed(source), source_mask)def decode(self, memory, source_mask, target, target_mask):'''构造解码器:param memory: 中间语义张量c,解码器的输出:param source_mask: 源数据掩码矩阵:param target: 目标数据:param target_mask: 目标数据掩码矩阵:return: 解码后的数据'''# 先将目标数据进行编码(包括位置编码和掩码),再进行解码return self.decoder(self.tgt_embed(target), memory, source_mask, target_mask)

(二)、实现组件模型

# 组建模型
def make_model(source_vocab, target_vocab, d_model=512, d_ff=2048, head=8, dropout=0.1, N=6):'''组建完整的transformer:param source_vocab: 原数据:param target_vocab: 目标数据:param d_model: 词嵌入维度:param d_ff: 中间过度维度:param head: 多头数量:param dropout: 失活比率:param N: 组成编码器和解码器的编码器层和解码器层个数:return: 模型'''# 深拷贝对象c = copy.deepcopy# 定义位置编码对象position = PositionalEncoding(d_model)# 定义多头注意力对象attn = MutiHeadAttn(head, d_model)# 定义前馈层对象ff = PositionwiseFeedForward(d_model, d_ff, dropout)# 定义解码编码器对象model = EncoderDecoder(# 输入编码器对象,由N个编码器层对象构成Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),# 输入解码器对象,由N个解码器层对象构成Decoder(DecoderLayer(d_model, c(attn), c(ff), dropout), N),# 源数据enbedding对象和位置编码对象nn.Sequential(Embedding(d_model, source_vocab), c(position)),# 目标数据enbedding对象和位置编码对象nn.Sequential(Embedding(d_model, target_vocab), c(position)),# 输出对象Generator(d_model, target_vocab))# 遍历model中所有的迭代器,只要维度大于1的就进行xavier_uniform初始化,后面的_表示在原地址修改for p in model.parameters():if p.dim() > 1:nn.init.xavier_uniform_(p)# 返回模型return model

8、执行

if __name__ == '__main__':# 词嵌入维度d_model = 512# 中间过度维度d_ff = 1024# 源词表维度source_vocab = 1000# 目标词表维度target_vocab = 1000# 解码后词嵌入维度vocab_size = 5000# 多头注意力头数head = 8# 编码器/解码器内部编码器层/解码器层个数N = 6# 深拷贝对象c = copy.deepcopy# 多头一般注意力和多头自注意力对象src_attn = self_attn = MutiHeadAttn(head, d_model)# 前馈全连接层对象ffn = PositionwiseFeedForward(d_model, d_ff)# 编码器层对象el = EncoderLayer(d_model, c(self_attn), c(ffn))# 解码器层对象dl = DecoderLayer(d_model, self_attn, src_attn, ffn)# 编码器对象encoder = Encoder(el, N)# 解码器对象decoder = Decoder(dl, N)# 源数据词嵌入对象source_embed = nn.Embedding(source_vocab, d_model)# 目标数据词嵌入对象target_embed = nn.Embedding(target_vocab, d_model)# 输出对象gen = Generator(d_model, vocab_size)# 源数据和目标数据掩码矩阵source_mask = target_mask = subsquent_mask(4)# 源数据和目标数据source = target = torch.LongTensor([[1, 2, 3, 4], [6, 7, 8, 9]])  # (2,4)# 定义解码编码器对象ed = EncoderDecoder(encoder, decoder, source_embed, target_embed, gen)# 调用解码编码器对象ed_res = ed(source, target, source_mask, target_mask)print(ed_res)print(ed_res.shape)# 构建模型# model = make_model(source_vocab, target_vocab)# print(model)

相关文章:

Transformer简述和实现

Transformer 1、概述 (一)、诞生 自从2017年此文《Attention is All You Need》提出来Transformer后,便开启了大规模预训练的新时代,也在历史的长河中一举催生出了GPT、BERT这样的里程碑模型。 (二)、优势 相比之前占领市场的LSTM和GRU模型&#xf…...

使用Python3 连接操作 OceanBase数据库

注:使用Python3 连接 OceanBase数据库,可通过安装 PyMySQL驱动包来实现。 本次测试是在一台安装部署OBD的OceanBase 测试linux服务器上,通过python来远程操作OceanBase数据库。 一、Linux服务器通过Python3连接OceanBase数据库 1.1 安装pyth…...

vue3-hooks

hooks 把模块化 发挥到极致 命名规则: useDog.ts/useDog.js useXxx(和xxx相关的所有内容) 具体内容: export function que(){} 或者 export default function () { let dogList []; const getDog () > {} //向外…...

网络安全:构建数字世界的坚固防线

在当今数字化飞速发展的时代,网络已经渗透到我们生活的方方面面。从日常的社交娱乐、在线购物,到工作中的远程协作、数据存储与传输,网络无处不在。然而,随着网络的普及和应用的深入,网络安全问题也日益凸显&#xff0…...

Vision Transformer (ViT) 基本原理

Vision Transformer (ViT) 基本原理 flyfish Vision Transformer (ViT) 是一种基于 Transformer 架构的计算机视觉模型 一、ViT 的基本原理 ViT 的核心思想是将一张图像视为一组序列,将其嵌入到 Transformer 的输入中,通过自注意力机制捕获全局上下文…...

【青牛科技】拥有两个独立的、高增益、内部相位补偿的双运算放大器,可适用于单电源或双电源工作——D4558

概述: D4558内部包括有两个独立的、高增益、内部相位补偿的双运算放大器,可适用于单电源或双电源工作。该电路具有电压增益高、噪声低等特点。主要应用于音频信号放大,有源滤波器等场合。 D4558采用DIP8、SOP8的封装形式 主要特点&#xff…...

LCD与lvgl

LCD与lvgl 目录 LCD与lvgl 回顾 LCD 的驱动层讲解 1、LCD 的常见接口 2、我们的 LCD 的参数 3、LCD 的设备树说明 4、LCD 的设备树说明 5、如何移植 LCD 的驱动(重点) LCD 的应用层开发 1:LCD 应用开发->界面开发的方法 2:LVGL 模拟器安装 3:LVGL 工程创建和…...

大语言模型(2)--GPT-1

GPT-1是由OpenAI在2018年推出的第一代生成式预训练模型(《Improving Language Understanding by Generative Pre-Training》),它采用了无监督预训练和有监督微调相结合的方法,以增强模型的通用任务求解能力。在此之前,…...

openstack内部rpc消息通信源码分析

我们知道openstack内部消息队列基于AMQP协议,默认使用的rabbitmq 消息队列。谈到rabbitmq,大家或许并不陌生,但或许会对oslo message有些陌生。openstack内部并不是直接使用rabbitmq,而是使用了oslo.message 。oslo.message 后端的…...

单端和差分信号的接线法

内容来源:【单端信号 差分信号与数据采集卡的【RSE】【 NRES】【 DIFF】 模式的连接】 此篇文章仅作笔记分享。 单端输入 单端信号指的是输入信号由一个参考端和一个信号端构成,参考端一般是地端,信号就是通过计算信号端口和地端的差值所得…...

服务器被ping的风险,如何开启和禁止ping?

允许服务器被ping(即响应ICMP回显请求)有其风险和好处。允许ping的主要好处是它可以帮助网络管理员快速检查服务器的连通性。然而,这也可能带来一些安全风险,例如: 暴露信息:响应ping请求可以让攻击者知道…...

pushgateway HA高可用方案

未经本人同意不得转载,若引用请附上原文链接。 项目使用flink来处理kafka中的无界流数据,采用的是flink on yarn的模式部署flink任务。最近做flink任务的监控过程中,踩了一些坑。下面是过程,只想看最终方案的直接拉到最后。 先说…...

在 Ubuntu Server 22.04 上安装 Docker 的详细步骤

本文档详细记录了在 Ubuntu Server 22.04 上安装 Docker 的完整过程,包括解决过程中遇到的问题。希望能对读者有所帮助。 安装过程,重点需要看官方文档。https://docs.docker.com/engine/install/ubuntu/ 步骤 1:卸载冲突的软件包 在安装 D…...

锻造船用发动机动力系统,铸强船舶“心脏”

船舶是海洋、湖泊及河流中重要的水上交通工具,不仅能够促进海上经济的发展,还能够保卫国家的制海权。船舶动力装置,也就是船舶的核心动力源——船用发动机动力系统对船舶的重要作用不言自明,关系到船舶的性能质量,能够…...

string类函数的手动实现

在上一篇文章中,我们讲解了一些string类的函数,但是对于我们要熟练掌握c是远远不够的,今天,我将手动实现一下这些函数~ 注意:本篇文章中会大量应用复用,这是一种很巧妙的方法 和以往一样,还是…...

前端工程化面试题(二)

前端模块化标准 CJS、ESM 和 UMD 的区别 CJS(CommonJS)、ESM(ESModule)和UMD(Universal Module Definition)是前端模块化标准的三种主要形式,它们各自有不同的特点和使用场景: CJS&…...

优化 LabVIEW 系统内存使用

在 LabVIEW 中,内存使用管理是确保高效系统性能的关键因素,尤其是在进行复杂的数据采集、信号处理和控制任务时。LabVIEW 程序的内存消耗可能会随着项目的规模和复杂度增加,导致性能下降,甚至出现内存溢出或程序崩溃。通过合理优化…...

pyqt6事件概要

例子: 利用qtdesigner建立闹钟 python代码 # 导入所需要的文件 from PyQt6.QtGui import QIcon, QPixmap from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QListWidgetItem from PyQt6 import uic from PyQt6.QtCore import Qt, QTime imp…...

鸿蒙分享(一):添加模块,修改app名称图标

码仓库:https://gitee.com/linguanzhong/share_harmonyos 鸿蒙api:12 新建公共模块common 在entry的oh-package.json5添加dependencies,引入common模块 "dependencies": {"common": "file:../common" } 修改app名称&…...

记忆泡沫垫市场:解锁舒适睡眠的黄金钥匙与增长潜力的深度剖析

在当今快节奏、高压力的生活中,优质睡眠已成为现代人追求健康生活的重要组成部分。记忆泡沫垫,作为床垫和枕头领域的一次革命性创新,凭借其独特的材质特性和对人体工学的完美贴合,正逐步成为改善睡眠质量的首选解决方案。本文将从…...

AI+电影特效产品化:开启电影人物年轻化新时代

随着人工智能技术的不断进步,它正在改变着我们生活的方方面面,包括娱乐产业。在电影制作领域,AI技术的应用尤其引人注目,尤其是在实现演员年轻化或老化效果方面。本文将介绍一款名为MyTimeMach...

探索 Python 应用的分层依赖:解决 UOS 环境中的 libvirt-python 安装问题

探索 Python 应用的分层依赖:解决 UOS 环境中的 libvirt-python 安装问题 背景Python 版本升级 问题描述原因分析与解决方案 Python 应用的分层依赖:安装与部署的视角libvirt-python的分层依赖尝试的解决方案 使用编译好的 .whl 文件"嫁接"整个…...

【MySQL 进阶之路】表级锁、行级锁详解

1. 表级锁和行级锁的概念及区别 表级锁(Table Lock) 表锁是一种较为粗粒度的锁,锁定的是整个表。当某个事务加锁表时,其他事务对该表的任何读写操作都会被阻塞,直到锁被释放。因此,表锁具有较高的冲突概率…...

FPGA系列,文章目录

前言 FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种集成电路,其内部结构可以通过软件重新配置来实现不同的逻辑功能。与传统的ASIC(Application-Specific Integrated Circuit,专用集成电路…...

离谱的梯形滤波器——增加过渡点

增加过渡点 频率采样法(Frequency Sampling Method)是一种设计FIR滤波器的方法,通过在频域中指定希望的频率响应,然后利用逆离散傅里叶变换(IDFT)来获得滤波器的脉冲响应。然而,这种方法容易导…...

容积卡尔曼滤波(CKF)仿真抛物线运动

容积卡尔曼滤波(CKF)仿真抛物线运动 容积卡尔曼滤波(Cubature Kalman Filter, CKF)的MATLAB实现。CKF是一种用于非线性系统状态估计的算法,它通过在状态空间中采样点(容积点)来近似非线性函数的…...

FlightGear+MATLAB+飞行手柄实现实时飞控视景系统

文章目录 一、软件配置二、FlightGearMATLAB联合仿真第一步 复制文件第二步 新建文件夹第三步 打开demo第四步 demo说明第五步 打开Simulink第六步 连接FlightGear第七步 设置FlightGear第八步 生成FlightGear连接文件FlightGear的设置Network的设置File的设置生成.bat文件 第九…...

Oracle 11g Data Guard 环境中的 GAP 处理办法

概述 在Data Guard 环境中,当主库的某些日志没有成功传送到备库时,就会发生归档裂缝(Archive Gap)。目前,Oracle 提供了两种日志 GAP 的检测和处理机制:自动 GAP 处理(Automatic Gap Resolutio…...

自建服务器,数据安全有保障

在远程桌面工具的选择上,向日葵和TeamViewer功能强大,但都存在收费昂贵、依赖第三方服务器、数据隐私难以完全掌控等问题。相比之下,RustDesk 凭借开源免费、自建服务的特性脱颖而出!用户可以在自己的服务器上部署RustDesk服务端&…...

华为HarmonyOS 快速构建各种文本识别应用 -- 通用文字识别

适用场景 通用文字识别,是通过拍照、扫描等光学输入方式,将各种票据、卡证、表格、报刊、书籍等印刷品文字转化为图像信息,再利用文字识别技术将图像信息转化为计算机等设备可以使用的字符信息的技术。 可以对文档翻拍、街景翻拍等图片进行…...

shell作业

计算器 #!/bin/bash num1$1 num2$3 op$2 case $op in"")echo $((num1 num2));;"-")echo $((num1 - num2));;"*")echo $((num1 * num2));;"/")if [ $num2 -ne 0 ]; thenecho $((num1 / num2))elseecho "除数不能为0"fi;;*)…...

css部分

前面我们学习了HTML,但是HTML仅仅只是做数据的显示,页面的样式比较简陋,用户体验度不高,所以需要通过CSS来完成对页面的修饰,CSS就是页面的装饰者,给页面化妆,让它更好看。 1 层叠样式表&#…...

nginx 配置 跨域、压缩、文件下载 、静态文件、防盗链

1.跨域配置 在server模块下 访问如:http://127.0.0.1:8080/static/a.txt #跨域server {listen 8080;server_name localhost;#允许跨域请求的域, *代表所有add_header Access-Control-Allow-Origin *;#允许带上cookie请求add_header Access-Contro…...

SQL教程(1):什么是SQL?有什么用?

如果你是刚接触用户研究的新手,可能会听说过一个词叫做 SQL(Structured Query Language,结构化查询语言),但你可能还不太清楚它是什么,如何使用它,或者为什么它会对你的用户研究有帮助。别担心&…...

c++笔记2

14、c的对象 对象和结构的区别; 结构:包含各种类型的变量; 对象:包含各种函数、和变量; 设计对象的时候引用class关键字创建类,和结构形状差不多; 将变量称之为属性,函数称之为方…...

Xlsxwriter生成Excel文件时TypeError异常处理

在使用 XlsxWriter 生成 Excel 文件时,如果遇到 TypeError,通常是因为尝试写入的值或格式与 XlsxWriter 的限制或要求不兼容。 1、问题背景 在使用 Xlsxwriter 库生成 Excel 文件时,出现 TypeError: “expected string or buffer” 异常。此…...

应用层协议/传输层协议(UDP)

目录 应用层 如何自定义应用层协议? 序列化方式 1.基于行文本的方式来传输 2.基于xml的方式 3.基于json的方式 4.yml的形式 5.protobuffer(pb)形式 传输层 端口号 协议 UDP 校验和 CRC TCP TCP/IP五层协议 应用层 -- 传输层 -- 网络层 -- 数据链路层…...

【Linux】应用层协议—HTTP

一、HTTP协议介绍 请求-响应模型:HTTP (Hyper Text Transfer Protocol) 协议是基于请求和响应的。客户端(如Web浏览器)发送一个HTTP请求到服务器,服务器处理请求后返回一个HTTP响应。 无状态,无连接协议:H…...

使用Vue3+Echarts实现加载中国地图,点击省份地图下钻(完整教程)

一. 前言 在众多 ECharts 图表类型中,开发者始终绕不开的有各种各样的地图开发,关于地图开发,可能比其他图表相对繁琐一些,其实说简单也简单,说复杂也复杂,其中不乏有层级地图、3D 地图等,感觉…...

双目相机的标定,视差图,深度图,点云生成思路与实现。

该文档记录从双目相机标定到点云生成的所有过程,同时会附上代码。 代码直接能跑。https://github.com/stu-yzZ/stereoCamera 目录 大致思路如下: 一、相机标定 1、相机参数介绍 2、单目相机标定 3、双目相机标定 二、图片畸变矫正 三、极线矫正…...

解决 minio上传文件Service: S3, Status Code: 403

错误信息 [software.amazon.awssdk.services.s3.model.S3Exception: (Service: S3, Status Code: 403, Request ID: 180E9BC04F11312E, Extended Request ID: 81aefed089495c5faf6270c59bea93c9783926f74ef647fe6b17908f0976b557)]分析过程 4XX一般是客户端错误。403表示禁止…...

SpringBoot实战——个人博客项目

目录 一、项目简介 ?二、项目整体架构 数据库模块 后端模块 前端模块 ?三、项目具体展示 ?四、项目的具体实现 1、一些准备工作 ??数据库、数据表的创建 ??设置数据库和MyBatis的配置 ??将前端项目引入到当前项目中 2、登录注册模块 ??实体类的创建 ?…...

OpenCV-平滑图像

二维卷积(图像滤波) 与一维信号一样,图像也可以通过各种低通滤波器(LPF)、高通滤波器(HPF)等进行过滤。LPF 有助于消除噪音、模糊图像等。HPF 滤波器有助于在图像中找到边缘。 opencv 提供了函数 **cv.filter2D()**&…...

nodejs官方文档学习-笔记-1

一、异步工作 process.nextTick(): 回调会在当前操作完成后立即执行,但在事件循环进入下一个阶段之前。它是最先执行的。 Promise.then(): 回调会在 microtask 队列中执行,通常是在当前操作完成后,但在事件循环进入…...

YOLOv7

YOLOv7 是 YOLO 系列的一个重要版本,由 Chien-Yao Wang 和 Alexey Bochkovskiy 等研究人员和开发者提出,继续改进了 YOLOv4 和 YOLOv5 的设计,主要在模型性能、训练效率以及推理速度上进行优化。YOLOv7 进一步优化了 YOLO 系列在速度和精度之…...

汉语唤醒词的模糊判断(Python)

汉语唤醒词的模糊判断【Python】 说明安装库代码Demo其他 说明 这是一个简单的汉语模糊唤醒词的判断器,汉语发音中前后舌以及声母韵母的区别,如果进行精准判断,很容易误判。需要一个模糊判断的逻辑! 安装库 pip install pypinyin代码Demo …...

C语言实例_26之根据输入计算星期几

1. 问题 请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续判断第二个字母。 2. 实现思路 思路一:嵌套if-else语句实现 首先接收用户输入的第一个字母,使用if-else语句进行判断。如果输入的是M&#xff0…...

包管理器npm,cnpm,yarn和pnpm

npm (Node Package Manager) 核心技术与工作原理 依赖解析: 广度优先搜索(BFS):npm 使用 BFS 算法来解析依赖树,尽量扁平化 node_modules 目录以减少重复的依赖项。冲突处理:如果两个包需要同一个依赖的不…...

docker-compose部署skywalking 8.1.0

一、下载镜像 #注意 skywalking-oap-server和skywalking java agent版本强关联,版本需要保持一致性 docker pull elasticsearch:7.9.0 docker pull apache/skywalking-oap-server:8.1.0-es7 docker pull apache/skywalking-ui:8.1.0二、部署文件docker-compose.yam…...

Android -- [SelfView] 自定义多行歌词滚动显示器

Android – [SelfView] 自定义多行歌词滚动显示器 流畅、丝滑的滚动歌词控件* 1. 背景透明;* 2. 外部可控制进度变化;* 3. 支持屏幕拖动调节进度(回调给外部);效果 歌词文件(.lrc) 一. 使用…...