解析Transformer模型

news2024/11/18 22:33:09

原文地址:https://zhanghan.xyz/posts/17281/

进入Transformer

RNN很难处理冗长的文本序列,且很容易受到所谓梯度消失/爆炸的问题。RNN是按顺序处理单词的,所以很难并行化。

用一句话总结Transformer:当一个扩展性极佳的模型和一个巨大的数据集邂逅,结果可能会让你大吃一惊。

Transformer是如何工作的?

1.位置编码(Positional Encodings)

RNN中按照顺序处理单词,很难并行化。

位置编码的思路:将输入序列中的所有单词后面加上一个数字,表明它的顺序。

[("Dala",1),("say",1),("hello",1),("world",1)]

原文中使用正弦函数进行位置编码

要点:将语序存储作为数据,而不是靠网络结构,这样你的神经网络就更容易训练了。

2.注意力机制(Attention)

注意力是一种机制,它允许文本模型在决定如何翻译输出句子中的单词时“查看”原始句子中的每个单词。

图源:[1409.0473] Neural Machine Translation by Jointly Learning to Align and Translate (arxiv.org)

模型如何知道在每个时间步长中应该注意哪些单词:就是从训练数据中学到的东西,通过观察成千上万的法语和英语句子,学会了什么类型的单词是相互依赖的。

3.自注意力机制(Self-Attention)

如何不是试图翻译单词,而是建立一个理解语言中的基本含义和模式的模型——一种可以用来做任何数量的语言任务的模型,怎么办?

自注意力帮助神经网络消除单词歧义,做词性标注,命名实体识别,学习语义角色等等。


以上仅为读‍‍【解析 Transformer 模型:理解 GPT-3、BERT 和 T5 背后的模型】

的笔记,以下是我对Transformer进行的详细理解和技术解析。

Transformer技术解析

1.位置编码Positional Encoding

从上面的整体框架图可以看出,Transformer中的Encoder和Decoder都是通过堆叠多头自注意力和全连接层得到的,不管是Encoder还是Decoder的输入,都进行了Positional Encoding,对于位置编码的作用,在原文是这样解释的:

Since our model contains no recurrence and no convolution, in order for the model to make use of the order of the sequence, we must inject some information about the relative or absolute position of the tokens in the sequence. To this end, we add “positional encodings” to the input embeddings at the bottoms of the encoder and decoder stacks. The positional encodings have the same dimension dmodel as the embeddings, so that the two can be summed. There are many choices of positional encodings, learned and fixed

由于我们的模型不包含递归和卷积,为了使模型能够利用序列的顺序,我们必须在序列中注入一些关于token的相对或绝对位置的信息。为此,我们在编码器和解码器堆栈底部的输入嵌入中加入位置编码。位置编码与embeddings具有相同的维数d,因此可以将两者求和。位置编码有多种选择,有学习的,也有固定的。

Positional Encoding就是句子中词语相对位置的编码,让Transformer保留词语的位置信息。

理想状态下,编码方式应该要满足以下几个条件,

  • 对于每个位置的词语,它都能提供一个独一无二的编码
  • 词语之间的间隔对于不同长度的句子来说,含义应该是一致的
  • 能够随意延伸到任意长度的句子

以下是Transformer中使用的位置编码的公式:

PE将正弦函数用于偶数嵌入索引i,余弦函数用于奇数嵌入索引i。

代码实现:

class PositionalEncoding(nn.Module):

    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()     
				# 初始化位置编码矩阵  
        pe = torch.zeros(max_len, d_model)
				# 计算位置编码
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
				# 将位置编码矩阵转换为张量
        pe = pe.unsqueeze(0).transpose(0, 1)
        #pe.requires_grad = False
        self.register_buffer('pe', pe)

    def forward(self, x):
        return x + self.pe[:x.size(0), :]

这里再引申一下为什么位置嵌入会有用:

得出一个结论: P E   p o s + k   PE~pos+k~ PE pos+k 可以被 P E   p o s   PE~pos~ PE pos 线性表示。从这一点来说,位置编码是可以反应一定的相对位置信息。

但是这种相对位置信息会在注意力机制那里消失。

2.多头注意力机制

为什么Transformer 需要进行 Multi-head Attention?

最终的原因可以通过两句话来概括:

①为了解决模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身位置的问题;

②一定程度上ℎ越大整个模型的表达能力越强,越能提高模型对于注意力权重的合理分配。

self-Attention自注意力机制

所谓自注意力机制就是通过某种运算来直接计算得到句子在编码过程中每个位置上的注意力权重;然后再以权重和的形式来计算得到整个句子的隐含向量表示。最终,Transformer架构就是基于这种的自注意力机制而构建的Encoder-Decoder模型。

从上图可以看出,自注意力机制的核心过程就是通过Q和K计算得到注意力权重;然后再作用于V得到整个权重和输出。具体的,对于输入Q、K和V来说,其输出向量的计算公式为:

q i = W q ⋅ a i k i = W k ⋅ a i v i = W v ⋅ a i α i , j = q i ⋅ k j A = softmax ( Q K T d k ) Attention ( Q , K , V ) = A V \\\begin{align*}q^{i} &= W^{q} \cdot a^{i} \\k^{i} &= W^{k} \cdot a^{i} \\v^{i} &= W^{v} \cdot a^{i} \\\alpha_{i,j} &= q^{i} \cdot k^{j} \\A &= \text{softmax}\left(\frac{QK^{T}}{\sqrt{d_k}}\right) \\\text{Attention}(Q, K, V) &= AV\\\end{align*} qikiviαi,jAAttention(Q,K,V)=Wqai=Wkai=Wvai=qikj=softmax(dk QKT)=AV

  1. 首先,对于输入序列中的每个元素,我们计算查询向量(Query)、键向量(Key)和值向量(Value),其中,Wq、Wk和Wv是需要学习的参数矩阵,ai是输入序列的第i个元素。
  2. 然后,我们利用查询向量和键向量来计算注意力得分(Attention Score),这里,αi,j表示第i个元素和第j个元素之间的注意力得分。
  3. 接着,我们对注意力得分进行缩放处理并应用softmax函数,以得到注意力权重。其中,Q和K分别是所有查询向量和键向量构成的矩阵,dk是键向量的维度。
  4. 最后,我们利用注意力权重和值向量来计算自注意力机制的输出。其中,V是所有值向量构成的矩阵,A是注意力权重矩阵。

通过这种自注意力机制的方式确实解决了“传统序列模型在编码过程中都需顺序进行的弊端”的问题,有了自注意力机制后,仅仅只需要对原始输入进行几次矩阵变换便能够得到最终包含有不同位置注意力信息的编码向量。

MultiHeadAttention

模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置,因此提出了通过多头注意力机制来解决这一问题。同时,使用多头注意力机制还能够给予注意力层的输出包含有不同子空间中的编码表示信息,从而增强模型的表达能力。

所谓的多头注意力机制其实就是将原始的输入序列进行多组的自注意力处理过程;然后再将每一组自注意力的结果拼接起来进行一次线性变换得到最终的输出结果。

Q i = W i Q ⋅ X K i = W i K ⋅ X V i = W i V ⋅ X α i , j = Q i ⋅ K j d k A i = softmax ( α i , j ) head i = A i ⋅ V i MultiHead ( Q , K , V ) = Concat ( head 1 , . . . , head H ) ⋅ W O \begin{align*}Q_i &= W_i^Q \cdot X \\K_i &= W_i^K \cdot X \\V_i &= W_i^V \cdot X \\\alpha_{i,j} &= \frac{Q_i \cdot K_j}{\sqrt{d_k}} \\A_i &= \text{softmax}(\alpha_{i,j}) \\\text{head}_i &= A_i \cdot V_i \\\text{MultiHead}(Q, K, V) &= \text{Concat}(\text{head}_1, ..., \text{head}_H) \cdot W^O\end{align*} QiKiViαi,jAiheadiMultiHead(Q,K,V)=WiQX=WiKX=WiVX=dk QiKj=softmax(αi,j)=AiVi=Concat(head1,...,headH)WO

为什么要使用多头

根据下图多头注意力计算方式我们可以发现,在dm固定的情况下,不管是使用单头还是多头的方式,在实际的处理过程中直到进行注意力权重矩阵计算前,两者之前没有任何区别。当进行进行注意力权重矩阵计算时,ℎ越大那么Q,K,V就会被切分得越小,进而得到的注意力权重分配方式越多。图源:

This post is all you need(上卷)——层层剥开Transformer

从上图可以看出,如果ℎ=1,那么最终可能得到的就是一个各个位置只集中于自身位置的注意力权重矩阵;如果ℎ=2,那么就还可能得到另外一个注意力权重稍微分配合理的权重矩阵;ℎ=3同理如此。因而多头这一做法也恰好是论文作者提出用于克服模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置的问题。这里再插入一张真实场景下同一层的不同注意力权重矩阵可视化结果图:

同时,当ℎ不一样时,dk的取值也不一样,进而使得对权重矩阵的scale的程度不一样。例如,如果dm=768,那么当ℎ=12时,则dk=64;当ℎ=1时,则dk=768。

所以,当模型的维度dm确定时,一定程度上ℎ越大整个模型的表达能力越强,越能提高模型对于注意力权重的合理分配。

3.Encoder层

对于Encoder部分来说其内部主要由两部分网络所构成:多头注意力机制和两层前馈神经网络。同时,对于这两部分网络来说,都加入了残差连接,并且在残差连接后还进行了层归一化操作。这样,对于每个部分来说其输出均为LayerNorm(x+Sublayer(x)),并且在都加入了Dropout操作。对于第2部分的两层全连接网络来说,其具体计算过程为:

F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x) = max(0, xW1 + b1)W2 + b2 FFN(x)=max(0,xW1+b1)W2+b2

4.Decoder层

对于Decoder部分来说,其整体上与Encoder类似,只是多了一个用于与Encoder输出进行交互的多头注意力机制。

不同于Encoder部分,在Decoder中一共包含有3个部分的网络结构。最上面的和最下面的部分(暂时忽略Mask)与Encoder相同,只是多了中间这个与Encoder输出(Memory)进行交互的部分,作者称之为“Encoder-Decoder attention”。对于这部分的输入,Q来自于下面多头注意力机制的输出,K和V均是Encoder部分的输出(Memory)经过线性变换后得到

在Decoder对每一个时刻进行解码时,首先需要做的便是通过Q与 K进行交互(query查询),并计算得到注意力权重矩阵;然后再通过注意力权重与V进行计算得到一个权重向量,该权重向量所表示的含义就是在解码时如何将注意力分配到Memory的各个位置上。

Decoder解码预测过程


在这里插入图片描述

Decoder训练解码过程

在介绍完预测时Decoder的解码过程后,下面就继续来看在网络在训练过程中是如何进行解码的。在真实预测时解码器需要将上一个时刻的输出作为下一个时刻解码的输入,然后一个时刻一个时刻的进行解码操作。显然,如果训练时也采用同样的方法那将是十分费时的。因此,在训练过程中,解码器也同编码器一样,一次接收解码时所有时刻的输入进行计算。这样做的好处,一是通过多样本并行计算能够加快网络的训练速度;二是在训练过程中直接喂入解码器正确的结果而不是上一时刻的预测值(因为训练时上一时刻的预测值可能是错误的)能够更好的训练网络。

例如在用平行预料"我 是 谁"<==>"who am i"对网络进行训练时,编码器的输入便是"我 是 谁",而解码器的输入则是"<s> who am i",对应的正确标签则是"who am i <e>"

但是,模型在实际的预测过程中只是将当前时刻之前(包括当前时刻)的所有时刻作为输入来预测下一个时刻,也就是说模型在预测时是看不到当前时刻之后的信息。因此,Transformer中的Decoder通过加入注意力掩码机制来解决了这一问题。

左边依旧是通过Q和K计算得到了注意力权重矩阵(此时还未进行softmax操作),而中间的就是所谓的注意力掩码矩阵,两者在相加之后再乘上矩阵V便得到了整个自注意力机制的输出。

5.残差和LayerNorm

残差

在Transformer中,数据过Attention层和FFN层后,都会经过一个Add & Norm处理。其中,Add为residule block(残差模块),数据在这里进行residule connection(残差连接)

在transformer的encoder和decoder中,各用到了6层的attention模块,每一个attention模块又和一个FeedForward层(简称FFN)相接。对每一层的attention和FFN,都采用了一次残差连接,即把每一个位置的输入数据和输出数据相加,使得Transformer能够有效训练更深的网络。在残差连接过后,再采取Layer Nomalization的方式。具体的操作过程见下图:

LayerNorm

为什么在NLP任务中(Transformer),使用LayerNorm而不是BatchNorm。

首先,BN不适合RNN这种动态文本模型,有一个原因是因为batch中的长度不一致,导致有的靠后的特征的均值和方差不能估算。

但是这个问题不是大问题,可以在处理数据的适合,使句子的长度相近的在一个batch,所以这不是NLP中不用BN的核心原因。

BN是对每个特征的batch_size上求得均值和方差,就是对每个特征,这些特征都有明确得含义,比如身高、体重等。但是想象以下,如果BN应用到NLP任务,就变成了对每个单词,也就是默认了在同一个位置得单词对应的是同一个特征,比如:“我/期待/全新/AGI”和“今天/天气/真/不错“,使用BN代表着”我“和”今天“是对应同一个维度的特征,才能做BN。

LayNorm做的事针对每一个样本,做特征的缩放。也就是,它认为“我/期待/全新/AGI”这四个词在同一个特征下,基于此做归一化。这样做和BN的区别在于,一句话中的每个单词都可以归到这句话的”语义信息“这个特征中。所以这里不再对应batch_size,而是文本长度。

引申-为什么BN在CNN中可以而在NLP中不行

浅浅理解:BN在NLP中是对词向量进行缩放,词向量是我们学习出来的参数来表示词语语义的参数,不是真实存在的。而图像像素是真实存在的,像素中包含固有的信息。

6.问题解析

Transformer的并行化

Decoder不用多说,没有并行,只能一个一个解码,很类似于RNN,这个时刻的输入依赖于上一个时刻的输出。

Encoder:首先,6个大的模块之间是串行的,一个模块的计算结果作为下一个模块的输入,互相之间有依赖关系。对于每个模块,注意力层和前馈神经网络这两个子模块单独看都是可以并行的,不同的单词之间是没有依赖关系的,当然对于注意力层在做attention时会依赖别的时刻的输入。然后注意力层和前馈网络之间时串行的,必须先完成注意力层计算再做前馈网络。

为什么Q和K使用不同的权重矩阵生成,不能使用同一个值进行自身的点乘

简单回答:使用QKV不相同可以保证在不同空间进行投影,增强了表达能力,提高泛化能力。

详细解释:

transformer中为什么使用不同的K 和 Q, 为什么不能使用同一个值? - 知乎

Transformer计算attention时候为什么选择点乘而不是加法,在计算复杂度和效果上有什么区别

实验分析,时间复杂度相似;效果上,和dk相关,dk越大,加法效果越显著。

transformer中的attention为什么scaled

根据上一个问题,在dk(即 attention-dim)较小的时候,两者的效果接近。但是随着dk增大,Add 开始显著超越 Mul。作者分析 Mul 性能不佳的原因,认为是极大的点积值将整个 softmax 推向梯度平缓区,使得收敛困难

这才有了 scaled。所以,Add 是天然地不需要 scaled,Mul 在dk较大的时候必须要做 scaled。个人认为,Add 中的矩阵乘法,和 Mul 中的矩阵乘法不同。前者中只有随机变量 X 和参数矩阵W 相乘,但是后者中包含随机变量 X 和随机变量 X 之间的乘法。

下面附上在知乎看到的代码数值实验就很清楚:

from scipy.special import softmax
import numpy as np

def test_gradient(dim, time_steps=50, scale=1.0):
    # Assume components of the query and keys are drawn from N(0, 1) independently
    q = np.random.randn(dim)
    ks = np.random.randn(time_steps, dim)
    x = np.sum(q * ks, axis=1) / scale  # x.shape = (time_steps,) 
    y = softmax(x)
    grad = np.diag(y) - np.outer(y, y)
    return np.max(np.abs(grad))  # the maximum component of gradients

NUMBER_OF_EXPERIMENTS = 5
# results of 5 random runs without scaling
print([test_gradient(100) for _ in range(NUMBER_OF_EXPERIMENTS)])
print([test_gradient(1000) for _ in range(NUMBER_OF_EXPERIMENTS)])

# results of 5 random runs with scaling
print([test_gradient(100, scale=np.sqrt(100)) for _ in range(NUMBER_OF_EXPERIMENTS)])
print([test_gradient(1000, scale=np.sqrt(1000)) for _ in range(NUMBER_OF_EXPERIMENTS)])
# 不带 scaling 的两组(可以看到 dim=1000 时很容易发生梯度消失):
[1.123056543761436e-06, 0.14117211040878508, 0.24896212285554725, 0.000861296669097178, 0.24717750591081689]
[1.2388028380883043e-09, 3.630249703743676e-18, 1.4210854715202004e-14, 1.1652900866465643e-12, 5.045026175709566e-07]
# 带 scaling 的两组(dim=1000 时梯度流依然稳定):
[0.11292476415310426, 0.08650727907448993, 0.11307320939056421, 0.2039260641245917, 0.11422935801305531]
[0.12367058336991178, 0.15990586501130605, 0.1701746604359328, 0.10761820500367032, 0.07591586878293968]

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

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

相关文章

全链路压力测试:现代软件工程中的重要性

全链路压力测试不仅可以确保系统在高负载下的性能和稳定性&#xff0c;还能帮助企业进行有效的风险管理和性能优化。在快速发展的互联网时代&#xff0c;全链路压力测试已成为确保软件产品质量的关键步骤。 1、测试环境搭建 测试应在与生产环境尽可能相似的环境中进行&#xff…

RabbitMQ 部署与配置[CentOS7]

# RabbitMQ,Erlang 版本包对应 https://rabbitmq.com/which-erlang.html#eol-seriescd /usr/local/src# Erlang下载 # https://github.com/rabbitmq/erlang-rpm/releases https://github.com/rabbitmq/erlang-rpm/releases/download/v23.3.4.5/erlang-23.3.4.5-1.el7.x86_64.rp…

Spring 核心之 IOC 容器学习一

IOC 与 DI IOC(Inversion of Control)控制反转&#xff1a;所谓控制反转&#xff0c;就是把原先我们代码里面需要实现的对象创建、依赖的代码&#xff0c;反转给容器来帮忙实现。那么必然的我们需要创建一个容器&#xff0c;同时需要一种描述来让容器知道需要创建的对象与对象…

【探索C++容器:vector的使用和模拟实现】

【本节目标】 1.vector的介绍及使用 2.vector深度剖析及模拟实现 1.vector的介绍及使用 1.1 vector的介绍 vertor文档介绍 1. vector是表示可变大小数组的序列容器。2. 就像数组一样&#xff0c;vector也采用连续存储空间来存储元素。也就是意味着可以采用下标对vector的元…

时间触发以太网TTE交换机混合流量测试方法

在本世纪初&#xff0c;TTE最早是由维也纳科技大学Hermann Kopetz 赫尔曼科佩茨教授等人提出来的&#xff0c;在国际上比较知名的TTE开发机构主要是以奥地利的TTTech公司为主&#xff0c;尔曼科佩茨教授是该公司的创始人之一&#xff0c;这家公司是将教授的理论进行了产业化应用…

iOS原生应用屏幕适配完整流程

1. 已iPhone 11 布局为设计布局,其他机型已这个来适配 2.变量与控件对应关系 txtViewer: txtAccount txtpwd seg btnOk 3.适配方法实现: //iOS屏幕适配 -(vo

深度求索开源国内首个 MoE 大模型 | DeepSeekMoE:在专家混合语言模型中实现终极专家专业化

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在大语言模型时代&#xff0c;混合专家模型&#xff08;MoE&#xff09;是一种很有前途的架构&#xff0c;用于在扩展模型参数时管理计算成本。然而&a…

【文本到上下文 #5】:RNN、LSTM 和 GRU

一、说明 欢迎来到“完整的 NLP 指南&#xff1a;文本到上下文 #5”&#xff0c;这是我们对自然语言处理 &#xff08;NLP&#xff09; 和深度学习的持续探索。从NLP的基础知识到机器学习应用程序&#xff0c;我们现在深入研究了神经网络的复杂世界及其处理语言的深刻能力。 在…

java springcloud中发布webservice 接口

java springcloud中发布webservice 接口 一、在pom文件中添加依赖&#xff1a; <!--webservice--><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version></dependen…

Hardware-Aware-Transformers开源项目笔记

文章目录 Hardware-Aware-Transformers开源项目笔记开源项目背景知识nas进化算法进化算法代码示例 开源项目Evolutionary Search1 生成延迟的数据集2 训练延迟预测器3 使延时约束运行搜索算法4. 训练搜索得到的subTransformer5. 根据重训练后的submodel 得到BLEU精度值 代码结构…

干货满满!MES系统的功能及实施

万界星空科技MES系统的主要功能&#xff1a; &#xff08;1&#xff09;生产资源分配与监控&#xff1b; &#xff08;2&#xff09;作业计划和排产&#xff1b; &#xff08;3&#xff09;工艺规格标准管理&#xff1b; &#xff08;4&#xff09;数据采集&#xff1b; &…

蓝凌EIS智慧协同平台frm_form_upload.aspx接口存在任意文件上传漏洞

@[toc] 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 蓝凌EIS智慧协同平台frm_form_upload.aspx接…

建筑能耗管理系统的解决方案研究

为什么要做能耗监测&#xff1f; 节约能源、降低成本 可帮助企业准确掌握能源的使用情况&#xff0c;找出能源浪费的地方&#xff0c;进而采取针对性措施减少能源的消耗。从而企业可降低能源成本&#xff0c;提高盈利能力。 提高能源利用效率 可帮助企业掌握那些设备消耗了…

助力工业园区作业违规行为检测预警,基于YOLOv5【n/s/m/l/x】全系列参数模型开发构建工业园区场景下作业人员违规行为检测识别系统

在很多工业园区生产作业场景下保障合规合法进行作业生产操作&#xff0c;对于保护工人生命安全降低安全隐患有着非常重要的作用&#xff0c;但是往往在实际的作业生产中&#xff0c;因为一个安全观念的淡薄或者是粗心大意&#xff0c;对于纪律约束等意思薄弱&#xff0c;导致在…

Python编辑开发---pycharm pro 2023 中文

PyCharm Pro 2023是一款功能强大的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提高Python开发人员的生产力。它提供了智能代码编辑、实时代码分析和调试工具&#xff0c;支持版本控制和数据库工具&#xff0c;以及可扩展的插件系统。PyCharm Pro 2023可在多…

vue2嵌入高德地图选择地址后显示地址和经纬度

以高德地图为里&#xff0c;申请key&#xff0c;选择js api服务&#xff0c;获取key和密钥. vue2项目代码引入相关依赖&#xff1a; npm i amap/amap-jsapi-loader -S 封装成组件: <template><div><el-row :gutter"15" class""><…

Python项目——久坐提醒定时器(PySide6)编写

1、介绍 使用Python编写一个久坐提醒软件。功能&#xff1a; 设置工作时间。设置休息时间。选择休息时是否播放音乐。休息时&#xff0c;软件置顶&#xff0c;且不能关闭。 2、工具 语言&#xff1a;python3.11UI设计工具&#xff1a;Qt designer编译器&#xff1a;PyCharm包…

C#使用DateTime的Now静态属性动态获得系统当前日期和时间

目录 一实例 1.源码 2.生成效果 ​编辑 二、相关知识点 1. Thread类 &#xff08;1&#xff09;Thread.Sleep() &#xff08;2&#xff09;Thread(ThreadStart) &#xff08;3&#xff09;IsBackground &#xff08;4&#xff09;Invoke( &#xff09; 2. Create…

Elastic Stack 8.12:通过对 ES|QL 等的改进增强了向量搜索

作者&#xff1a;来自 Elastic Tyler Perkins, Shani Sagiv, Gilad Gal, Ninoslav Miskovic Elastic Stack 8.12 构建于 Apache Lucene 9.9&#xff08;有史以来最快的 Lucene 版本&#xff09;之上&#xff0c;基于我们对标量量化和搜索并发性的贡献&#xff0c;为文本、向量和…

【Java 设计模式】结构型之桥接模式

文章目录 1. 定义2. 应用场景3. 代码实现结语 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它将抽象部分与实现部分分离&#xff0c;使它们可以独立变化&#xff0c;从而降低它们之间的耦合。桥接模式通过将抽象部分和实现部分分离&#x…