【Transformer】Positional Encoding

文章目录

  • 为什么需要位置编码?
  • 预备知识
    • 三角函数求和公式
    • 旋转矩阵
      • 逆时针旋转
      • 顺时针旋转
      • 旋转矩阵的性质
  • 原始Transformer中的位置编码
    • 论文中的介绍
    • 具体计算过程
    • 为什么是线性变换?
  • 大模型常用的旋转位置编码RoPE
    • 基本原理
    • 最简实现形式
    • Llama3中的代码实现
  • 两种位置编码的区别
    • 编码方式
    • 实现方式
  • 参考资料

为什么需要位置编码?

众所周知,老生常谈,Transformer模型的核心是自注意力机制(Self-Attention),这一机制的特点是输入序列中的所有元素都是同时被处理的,而不是像RNN那样按顺序处理。这种并行处理的方式虽然具有很高的效率,但也导致了模型无法自然地获取输入序列中元素的位置信息

比如,自注意力机制在处理 AI 好 难 学难 学 好 AI 这两个元素相同,但是位置不同的序列时,得到的每个元素对应的attention值是相同的,也没办法区分。

因此,Positional Encoding 的作用,就是在把 Word Embedding 送入 Attention 之前,把位置信息给带上,使得模型能够在进行自注意力计算时感知到输入元素的相对和绝对位置。

网络社区中对 Positional Encoding 分类的方法很多,按照不同的分类方法划分,大致可以分为:

  • 绝对位置编码和相对位置编码
    • 绝对位置编码,为输入序列中的每个位置提供一个唯一的表示,通常是通过预定义的方法生成,并直接添加到输入表示中
    • 相对位置编码,是对两个单词之间的相对位置进行建模,并且将相对位置信息加入到Self-Attention结构中,形如Transformer-XL,DeBERTa等采用的就是相对位置编码。Self-Attention的本质是两个单词信息的内积操作,相对位置编码的思想是对内积的计算方式进行改进,在内积中注入两个单词的相对位置因素
  • 固定式位置编码和可学习式位置编码
    • 这种分类方式,说的是 绝对位置编码 的不同实现方式
    • 固定位置编码,主要是 Transformer论文中提出的正弦和余弦位置编码(Sinusoidal Positional Encoding)方法,使用正弦和余弦函数生成不同频率的编码
    • 可学习式位置编码,没有固定的位置编码公式,通过初始化位置向量让模型根据上下文数据自适应地学习出来,Bert和GPT采用的就是可学习式
  • 绝对位置编码添加的位置不同
    • 绝对位置编码加在 Transformer 的输入端,典型代表是绝对位置编码( Sinusoidal 位置编码和可学习位置编码 )
    • 绝对位置编码乘在 q , k , v q, k, v q,k,v,典型代表是 RoPE 位置编码
    • 相对位置编码加在注意力权重 q T k q^{T}k qTk,典型代表是 ALiBi 位置编码

根据本人面试经历,只要是和Positional Encoding相关的问题,基本都是 Transformer论文中提出的正弦和余弦位置编码,以及目前大模型常用的RoPE,这两个方法。因此,本文主要以这两个方法为例来深入讨论。

预备知识

三角函数求和公式

s i n ( α + β ) = s i n α ∗ C o s β + c o s α ∗ s i n β \rm{sin}(\alpha+\beta) = sin\alpha*Cos\beta + cos\alpha * sin\beta sin(α+β)=sinαCosβ+cosαsinβ
s i n ( α − β ) = s i n α ∗ C o s β − c o s α ∗ s i n β \rm{sin}(\alpha-\beta) = sin\alpha*Cos\beta - cos\alpha * sin\beta sin(αβ)=sinαCosβcosαsinβ

c o s ( α + β ) = c o s α ∗ c o s β − s i n α ∗ s i n β \rm{cos}(\alpha+\beta) = cos\alpha*cos\beta - sin\alpha * sin\beta cos(α+β)=cosαcosβsinαsinβ
c o s ( α − β ) = c o s α ∗ c o s β + s i n α ∗ s i n β \rm{cos}(\alpha-\beta) = cos\alpha*cos\beta + sin\alpha * sin\beta cos(αβ)=cosαcosβ+sinαsinβ

旋转矩阵

逆时针旋转

在这里插入图片描述
假设向量 a , b \bold{a}, \bold{b} a,b的长度均为1,将 a \bold{a} a逆时针旋转 θ \theta θ角度,变成 b \bold{b} b的过程如下:
a = [ c o s μ , s i n μ \bold{a} = [\rm{cos}\mu, sin\mu a=[cosμ,sinμ]
b = [ c o s ( μ + θ ) , s i n ( μ + θ ) \bold{b} = [\rm{cos}(\mu+\theta), sin(\mu+\theta) b=[cos(μ+θ),sin(μ+θ)]

根据上面的三角函数求和公式可得:
b = [ c o s θ c o s μ − s i n θ s i n μ , s i n μ c o s θ + c o s μ s i n θ \bold{b} = [\rm{cos}\theta cos\mu - sin\theta sin\mu,sin\mu cos\theta + cos\mu sin\theta b=[cosθcosμsinθsinμ,sinμcosθ+cosμsinθ]

这里我们用矩阵乘来简化计算:
b T = [ c o s ( μ + θ ) s i n ( μ + θ ) ] = [ c o s θ − s i n θ s i n θ c o s θ ] [ c o s μ s i n μ ] = M n a \bold{b}^{\rm{T}} =\begin{bmatrix} \rm{cos}(\mu+\theta) \\ \rm{sin}(\mu+\theta) \end{bmatrix}= \begin{bmatrix} \rm{cos}\theta & -\rm{sin}\theta \\ \rm{sin}\theta & \rm{cos}\theta \end{bmatrix} \begin{bmatrix} \rm{cos}\mu \\ \rm{sin}\mu \end{bmatrix} =\bold{M_{n}}\bold{a} bT=[cos(μ+θ)sin(μ+θ)]=[cosθsinθsinθcosθ][cosμsinμ]=Mna

因此,逆时针的旋转矩阵为 M n = [ c o s θ − s i n θ s i n θ c o s θ ] \bold{M_{n}}=\begin{bmatrix} \rm{cos}\theta & -\rm{sin}\theta \\ \rm{sin}\theta & \rm{cos}\theta \end{bmatrix} Mn=[cosθsinθsinθcosθ]

顺时针旋转

在这里插入图片描述

假设向量 a , b \bold{a}, \bold{b} a,b的长度均为1,将 a \bold{a} a顺时针旋转 θ \theta θ角度,变成 b \bold{b} b的过程如下:
a = [ c o s μ , s i n μ \bold{a} = [\rm{cos}\mu, sin\mu a=[cosμ,sinμ]
b = [ c o s ( μ − θ ) , s i n ( μ − θ ) \bold{b} = [\rm{cos}(\mu-\theta), sin(\mu-\theta) b=[cos(μθ),sin(μθ)]

根据上面的三角函数求和公式可得:
b = [ c o s θ c o s μ + s i n θ s i n μ , s i n μ c o s θ − c o s μ s i n θ \bold{b} = [\rm{cos}\theta cos\mu + sin\theta sin\mu,sin\mu cos\theta - cos\mu sin\theta b=[cosθcosμ+sinθsinμ,sinμcosθcosμsinθ]

这里我们用矩阵乘来简化计算:
b T = [ c o s ( μ − θ ) s i n ( μ − θ ) ] = [ c o s θ s i n θ − s i n θ c o s θ ] [ c o s μ s i n μ ] = M s a \bold{b}^{\rm{T}} =\begin{bmatrix} \rm{cos}(\mu-\theta) \\ \rm{sin}(\mu-\theta) \end{bmatrix} = \begin{bmatrix} \rm{cos}\theta & \rm{sin}\theta \\ -\rm{sin}\theta & \rm{cos}\theta \end{bmatrix} \begin{bmatrix} \rm{cos}\mu \\ \rm{sin}\mu \end{bmatrix} = \bold{M_{s}}\bold{a} bT=[cos(μθ)sin(μθ)]=[cosθsinθsinθcosθ][cosμsinμ]=Msa

因此,顺时针的旋转矩阵为: M s = [ c o s θ s i n θ − s i n θ c o s θ ] \bold{M_{s}}=\begin{bmatrix} \rm{cos}\theta & \rm{sin}\theta \\ -\rm{sin}\theta & \rm{cos}\theta \end{bmatrix} Ms=[cosθsinθsinθcosθ]

旋转矩阵的性质

R ( α ) R ( β ) = R ( α + β ) R(\alpha) R(\beta) =R(\alpha+\beta) R(α)R(β)=R(α+β)
R ( θ ) T = R ( − θ ) R(\theta)^{\rm{T}} =R(-\theta) R(θ)T=R(θ)

原始Transformer中的位置编码

论文中的介绍

首先贴上Transformer论文中,对于Positional Encoding部分的全部介绍:

在这里插入图片描述
我真的服了,这么重要的位置编码,论文里就写了这么一点??现在看来,内容虽然少,但是句句都是关键,每一句都是面试官想要考你的点,蚌埠住了!

回到正题,论文里面对Positional Encoding的描述主要有以下几个点:

  • 位置编码的维度和token的embedding的维度一致,所以可以直接add
  • 位置编码的具体实现方式是:sine and cosine functions of different frequencies,也就是同时使用正弦函数和余弦函数来表示每个token的绝对位置
  • sine and cosine functions of different frequencies中,包括两个关键变量,一个是pos,表示 是哪个token,另一个是i,表示token中不同embedding的位置
  • 使用这种正余弦位置编码的方式,可以在计算attention时,很轻松的学习relative positions,也就是相对位置,理由是, P E p o s + k PE_{pos+k} PEpos+k可以表示为 P E p o s PE_{pos} PEpos的线性变换!!(其实就是旋转矩阵)
  • 选择正余弦位置编码方式,也是因为它可以允许模型外推到,比训练期间遇到的序列长度更长的序列长度,这个特性对于扩大模型推理时的长度非常友好!!

具体计算过程

下面,让我们通过一个具体的示例,来理解Transformer论文的正余弦位置编码,到底是怎么计算的?(参考这篇blog)

假设我们的输入如下,第一行是输入文本,第二行tokenization后的tokens,最后是每个token对应的embedding(维度是5):

在这里插入图片描述
首先,对于pos=0的token5(对应text为When)来说,计算它的 位置编码 方式如下:

在这里插入图片描述
可以看到,token的每个维度,都会计算一个位置编码,维度索引的奇偶性,决定了使用sin还是cos函数来计算。

这里的计算方式,和原论文的公式有出入,原论文应该是维度索引i为偶数时,使用sin函数来计算,为奇数时,则使用cos函数来计算。
但是这里的计算方式却是反过来,奇数时,使用sin函数来计算,偶数时,则使用cos函数来计算
所以大家知道这一点就可以,不影响对Positional Encoding计算过程的理解

同理,对于所有输入tokens,分别计算他们的位置编码:

在这里插入图片描述

这里可以感觉出来,越靠前的token计算的位置编码,他们使用的正余弦函数的频率越大,振荡的越快,相反,越往后的tokne,在embedding维度上振荡越慢,不同的频率也就是论文中说的sine and cosine functions of different frequencies,大概如下图所示:

在这里插入图片描述

为什么是线性变换?

到这里,相信大家对Transformer论文的正余弦位置编码的计算过程,有了一个清晰的理解。现在来思考论文中的一个关键点: P E p o s + k PE_{pos+k} PEpos+k如何表示为 P E p o s PE_{pos} PEpos的线性变换?

我们假设,用 t t t来表示不同token得pos(其实这里是把位置,类比为时间,第i个位置和第i个时刻是一致的),那么论文中的PE计算公式就变成了:
P E ( t , 2 i ) = s i n ( t 1000 0 2 i / d m o d e l ) P E ( t , 2 i + 1 ) = c o s ( t 1000 0 2 i / d m o d e l ) \begin{aligned} PE_{(t, 2i)} &= \rm{sin}(\frac{\it{t}}{10000^{2\it{i}/\it{d}_{model}}}) \\ PE_{(t, 2i+1)} &= \rm{cos}(\frac{\it{t}}{10000^{2\it{i}/\it{d}_{model}}}) \end{aligned} PE(t,2i)PE(t,2i+1)=sin(100002i/dmodelt)=cos(100002i/dmodelt)

可以看到,位置编码的过程,其实是对每个token按照维度方向,两两分组,维度索引为偶数时使用sin函数,奇数时使用cos函数/

所以一共有 d m o d e l / 2 d_{model}/2 dmodel/2个分组,这里用 j j j来表示分组情况,那么,第 t t t个token的第 j j j个分组可以表示为:

P E ( t , j ) = { sin ⁡ ( θ j ⋅ t ) , if  j = 2 i / 2 cos ⁡ ( θ j ⋅ t ) , if  j = ( 2 i + 1 ) / 2 P E_{(t, j)}=\left\{\begin{array}{ll} \sin \left(\theta_{j} \cdot t\right), & \text { if } j=2 i / 2 \\ \cos \left(\theta_{j} \cdot t\right), & \text { if } j=(2 i+1) / 2 \end{array}\right. PE(t,j)={sin(θjt),cos(θjt), if j=2i/2 if j=(2i+1)/2

其中, θ j = 1 1000 0 j / d m o d e l \theta_j=\frac{1}{10000^{j/d_{model}}} θj=10000j/dmodel1

那么,对于第 j j j个分组来说,如果 P E ( t + k , j ) PE_{(t+k, j)} PE(t+k,j) P E ( t , j ) PE_{(t, j)} PE(t,j)的线性变换,则存在一个矩阵 M ∈ R 2 × 2 \bold{M}\in \mathbb R^{2 \times 2} MR2×2,使得 P E ( t + k , j ) = M ∗ P E ( t , j ) PE_{(t+k, j)}=\bold{M} * PE_{(t, j)} PE(t+k,j)=MPE(t,j)成立,也就是:

[ s i n ( θ j ⋅ ( t + k ) ) c o s ( θ j ⋅ ( t + k ) ) ] = M [ s i n ( θ j ⋅ t ) c o s ( θ j ⋅ t ) ] \begin{bmatrix} \rm{sin}(\theta_{\it{j}} \cdot (\it{t} + \it{k})) \\ \rm{cos}(\theta_{\it{j}} \cdot (\it{t} + \it{k})) \end{bmatrix} = \bold{M} \begin{bmatrix} \rm{sin}(\theta_{\it{j}} \cdot \it{t}) \\ \rm{cos}(\theta_{\it{j}} \cdot \it{t}) \end{bmatrix} [sin(θj(t+k))cos(θj(t+k))]=M[sin(θjt)cos(θjt)]

聪明的小伙伴肯定可以看出来,这不就是预备知识中讲的逆时针旋转公式嘛!!!其实不然,大家注意看,这里sin和cos的顺序,和我们之前推导旋转公式的时候,是相反的,所以这里重新计算就可以得到:
M = [ c o s ( θ j ⋅ k ) s i n ( θ j ⋅ k ) − s i n ( θ j ⋅ k ) c o s ( θ j ⋅ k ) ] \bold{M}= \begin{bmatrix} \rm{cos}(\theta_{\it{j}} \cdot \it{k}) & \rm{sin}(\theta_{\it{j}} \cdot \it{k}) \\ -\rm{sin}(\theta_{\it{j}} \cdot \it{k}) & \rm{cos}(\theta_{\it{j}} \cdot \it{k}) \end{bmatrix} M=[cos(θjk)sin(θjk)sin(θjk)cos(θjk)]

因此, P E ( t + k , j ) PE_{(t+k, j)} PE(t+k,j)就是 P E ( t , j ) PE_{(t, j)} PE(t,j)顺时针旋转 ( θ j ⋅ k ) (\theta_j \cdot k) (θjk) 角度得到的,旋转角度是相对位置 k k k的线性关系,所以,论文中才说:for any fixed offset k k k, P E p o s + k PE_{pos+k} PEpos+k can be represented as a linear function of P E p o s PE_{pos} PEpos

对于所有 d m o d e l / 2 d_{model}/2 dmodel/2个分组来说, P E t + k PE_{t+k} PEt+k P E t PE_{t} PEt顺时针旋转得到的,可以表示为:

在这里插入图片描述

大模型常用的旋转位置编码RoPE

RoPE是 Rotary Position Embedding 的缩写,即旋转位置编码,源自 RoFormer 这篇论文(RoFormer: Enhanced Transformer with Rotary Position Embedding),目前已在 Llama 等各类大模型中被广泛用作默认的位置编码方法。

基本原理

核心就一句话:使用旋转矩阵对绝对位置进行编码,同时在自注意中结合了显式的相对位置依赖性

也就是说,RoPE是一种固定式的绝对位置编码策略,但是它的绝对位置编码配合 Transformer 的Self-Attention 内积注意力机制能达到相对位置编码的效果。

RoPE的本质是对两个token形成的Query和Key向量做一个变换,使得变换后的Query和Key带有位置信息,进一步使得Attention的内积操作不需要做任何更改就能自动感知到相对位置信息。换句话说,RoPE的出发点和策略用的相对位置编码思想,但是实现方式用的是绝对位置编码。

下面根据论文中的介绍,尽量直白地讲清楚RoPE的基本原理:

在这里插入图片描述
首先,论文在最开始讲说清楚了,RoPE方法的最终目标,就是找到一种等价的位置编码方法,使得query和key的内积结果,只和输入word embedding也就是 x m , x n \bold{x}_m, \bold{x}_n xm,xn以及他们之间的相对位置 m − n m-n mn有关。

那么,RoPE是怎么做的呢?

在这里插入图片描述
根据论文中的公式14和16,我们可以知道:

  • 首先对query向量逆时针旋转 m θ i m\theta_{i} mθi角度,对key向量逆时针旋转 n θ i n\theta_{i} nθi角度
  • 然后,计算attention,也就是 q m T ⋅ k n = ( R m q ) T ⋅ ( R n k ) = q T R m T ⋅ R n k = q T R − m ⋅ R n k = q T R n − m k \bold{q}^{\rm{T}}_{m} \cdot \bold{k}_{n}=(R_m \bold{q})^{\rm{T}} \cdot (R_n \bold{k})=\bold{q}^{\rm{T}} R_m^{\rm{T}} \cdot R_n \bold{k}=\bold{q}^{\rm{T}} R_{-m} \cdot R_n \bold{k}=\bold{q}^{\rm{T}} R_{n-m} \bold{k} qmTkn=(Rmq)T(Rnk)=qTRmTRnk=qTRmRnk=qTRnmk

因此,可以看到,RoPE的实现方式,是通过对query和key的embedding分别进行逆时针旋转(乘以逆时针旋转矩阵),然后作用在attention内积计算中,自然的实现了相对位置信息的嵌入。

在这里插入图片描述
以上是非常简略的讲解,论文中以及llama3代码中,都是在复数域计算频率因子,从而进行旋转变换(相乘),最后再变换到实数域得到结果。

最简实现形式

https://zhuanlan.zhihu.com/p/684666015


# 1. 参数初始化
bs = 1
seq_len = 10
dim = 128
theta = 10000.0# 2. 初始化输入,并转换为复数
xq = torch.randn(bs, seq_len, dim) # [1, 10, 128]
xq_div_2 = xq.view(*xq.shape[:-1], -1, 2) # [1, 10, 64, 2]
xq_complex = torch.view_as_complex(xq_div_2) # [1, 10, 64]# 3. 计算旋转位置编码的复数因子
theta = 1.0 / (theta ** (torch.arange(0, dim, 2) / dim)) # [64]
m = torch.arange(seq_len)  # [10]
freqs = torch.outer(m, theta)  # 外积函数,两个矩阵中的元素两两相乘,[10, 64]
freqs = torch.polar(torch.one_like(freqs), freqs) # [10, 64]# 4. 在复数空间,对原输入进行旋转位置编码转换
freqs_cis = freqs[:seq_len].view(xq_complex.shape) # 统一维度  # [1, 10, 64]
xq_rope_complex = xq_complex * freqs_cis  # [1, 10, 64]# 5. 复数转换为实数
xq_rope_real = torch.view_as_real(xq_rope_complex)  # [1, 10, 64, 2]
xq_rope_real = xq_rope_real.flatten(-2) # 展平最后两个维度 [1, 10, 128]

Llama3中的代码实现

https://github.com/meta-llama/llama3/blob/main/llama/model.py#L65

def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):
"""
这里预先计算每个序列中位置旋转的角度,以复数形式表达。
dim:为attention输入tensor的维度,简单来讲可以理解成embedding的维度
end:为序列的最大长度,即只计算0-end的旋转角度
theta:为超参数,原论文推荐值为10000
return:输出的是一个复数向量,向量shape为(end,dim//2),第一维是是序列长度,第二维为输入tensor维度的一半,因为旋转是以2维矩阵为单位的,即每两个数有一个旋转角度。
"""freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))t = torch.arange(end, device=freqs.device)  # type: ignorefreqs = torch.outer(t, freqs).float()  # type: ignorefreqs_cis = torch.polar(torch.ones_like(freqs), freqs)  # complex64return freqs_cisdef reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):
"""
将预先计算好的旋转角度freqs_cis的shape与输入shape统一。
freqs_cis的维数会变为和x一致,其中第二维是freqs_cis.shape[0],最后一维是freqs_cis.shape[1],其余维度为1
"""ndim = x.ndimassert 0 <= 1 < ndimassert freqs_cis.shape == (x.shape[1], x.shape[-1])shape = [d if i == 1 or i == ndim - 1 else 1 for i, d in enumerate(x.shape)]return freqs_cis.view(*shape)def apply_rotary_emb(xq: torch.Tensor,xk: torch.Tensor,freqs_cis: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
用预先计算的角度来旋转输入的xq和xk,做法是利用复数乘法的性质来完成旋转
"""xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))freqs_cis = reshape_for_broadcast(freqs_cis, xq_)xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3)xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3)return xq_out.type_as(xq), xk_out.type_as(xk)

两种位置编码的区别

简单来说,绝对位置编码是一个顺时针旋转的钟表系统,这个信息会与输入相加。但是旋转位置编码是一个逆时针旋转的时钟系统,并没有采用相加的方式,而是直接将输入(query和key)进行了旋转。

编码方式

  1. 正余弦位置编码:
  • 使用固定的正弦和余弦函数来为每个token的每个维度生成位置编码
  • 这种编码的每个维度使用不同的频率,使得编码具有周期性和可区分性,从而允许模型推断序列中元素的相对位置
  1. RoPE:
  • 使用旋转变换的方式来对位置进行编码。
  • RoPE通过在自注意力机制的点积计算过程中引入旋转变换来实现位置编码,而不是直接添加到输入嵌入上。
  • 提供了一种位置编码与内容编码更紧密结合的方法,使得模型能够在不丢失相对位置关系的情况下处理长序列。

实现方式

  1. 正余弦位置编码:
  • 直接将位置编码加到输入的词嵌入上,影响模型的输入表示。
  1. RoPE:
  • 在注意力计算过程中,使用旋转操作影响注意力得分。具体而言,通过将query和key中的位置进行旋转变换,使得位置编码在自注意力的计算中以更自然的方式呈现。
  • 这种方法通常不直接影响输入嵌入,而是调整注意力机制本身。

参考资料

  • [1] https://note.mowen.cn/note/detail?noteUuid=Q2_oDhFEqD2pD8Iv4uSzn
  • [2] https://note.mowen.cn/note/detail?noteUuid=waAeRtCgZXLO62f9RhUWa
  • [3] https://www.bilibili.com/video/BV1F1421B7iv/?share_source=copy_web&vd_source=79b1ab42a5b1cccc2807bc14de489fa7
  • [4] https://www.jianshu.com/p/e8be3dbfb4c5
  • [5] https://blog.csdn.net/BIT_Legend/article/details/137042032
  • [6] https://medium.com/@fareedkhandev/understanding-transformers-a-step-by-step-math-example-part-1-a7809015150a
  • [7] https://zhuanlan.zhihu.com/p/684666015
  • [8] https://kexue.fm/archives/8265

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

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

相关文章

智汇云舟受邀参加2024第四届国产水科学数值模型开发创新与技术应用研讨会,并成为“科技智水产业联盟”创始成员

在数字化浪潮的推动下&#xff0c;智慧水利作为国家战略的重要组成部分&#xff0c;正迎来前所未有的发展机遇。8月27-29日&#xff0c;由浙江贵仁信息科技股份有限公司主办、浙江省水利学会协办的“2024第四届国产水科学数值模型开发创新与技术应用研讨会”在杭州白马湖建国饭…

ML19_GMM高斯混合模型详解

1. 中心极限定理 中心极限定理&#xff08;Central Limit Theorem, CLT&#xff09;是概率论中的一个重要定理&#xff0c;它描述了在一定条件下&#xff0c;独立同分布的随机变量序列的标准化和的分布趋向于正态分布的性质。这个定理在统计学中有着广泛的应用&#xff0c;尤其…

maven配置文件常用模板

注释很详细&#xff0c;直接上代码 项目结构 内容 父项目 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…

context canceled 到底谁在作祟?

一、背景 在工作中&#xff0c;因报警治理标准提高&#xff0c;在报警治理的过程中&#xff0c;有一类context cancel报警渐渐凸显出来。 目前context cancel日志报警大致可以分为两类。 context deadline exceeded 耗时长有明确报错原因 context canceled 耗时短无明确报错…

Android13_SystemUI下拉框新增音量控制条

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Android13_SystemUI下拉框新增音量控制条 一、必备知识二、源码分析对比1.brightness模块分析对比2.statusbar/phone 对应模块对比对比初始化类声明对比构造方法 三、源码修改…

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆&#xff0c;该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使…

树莓派3B串口通信

树莓派3B串口通信 文章目录 树莓派3B串口通信一、串口的基本认知1.1 关于电器标准和协议&#xff1a;RS232RS422RS485 1.2 关于串口的电平&#xff1a;UARTRS232电平TTL电平 1.3 串口通信引脚接线&#xff1a;1.4 串口的通信协议&#xff1a; 二、树莓派串口通信开发2.1 树莓派…

TensorRT-For-YOLO-Series项目:实现yolov10模型的python-tensorrt推理(对比int8与fp16推理差异)

项目地址&#xff1a;https://github.com/Linaom1214/TensorRT-For-YOLO-Series/tree/cuda-python 算法支持状态&#xff1a; 2024.6.16 Support YOLOv9, YOLOv10, changing the TensorRT version to 10.0 2023.8.15 Support cuda-python 2023.5.12 Update 2023.1.7 support YO…

观趋势 谋发展 2024 SSHT上海智能家居展有哪些创新呈现?

引言&#xff1a;大数跨境发布的《2024全球智能家居市场洞察报告》显示&#xff0c;智能家居市场正迎来快速增长&#xff0c;预计从2024年的1215.9亿美元增长至2032年的6332.0亿美元&#xff0c;复合年增长率为22.9%。 近年来&#xff0c;随着物联网、AI等底层技术的飞速进步&…

kubernetes微服务之ingress-nginx

目录 1 ingress-nginx 介绍 2 Ingress-nginx 的工作原理 3 使用 Ingress-nginx 的步骤 4 部署 ingress &#xff1a; 4.1 开启ipvs 模式 4.2 下载部署文件 4.3 上传镜像到harbor 4.4 修改文件中镜像地址,与harbor仓库路径保持一致 4.5 检查是否running 4.6 将ingress的命名…

轻松上手,高效产出:音频剪辑工具年度精选

不知道你有没有拍vlog记录生活的习惯&#xff0c;有时候视频里穿插进自己的声音能让视频更加丰富贴上自己的标签。这次我们一起探讨当下有哪些好用的在线音频剪辑工具。 1.FOXIT音频剪辑 链接直达>>https://www.foxitsoftware.cn/audio-clip/ 这个工具是一款专业的音…

Java 数据类型详解:基本数据类型与引用数据类型

在 Java 编程语言中&#xff0c;数据类型主要分为两大类&#xff1a;基本数据类型和引用数据类型。理解这两种类型的区别、使用场景及其转换方式是学习 Java 的基础。本文将深入探讨这两类数据类型的特点&#xff0c;并展示自动类型转换、强制类型转换以及自动拆箱和封箱的使用…

虚拟现实辅助工程技术助力多学科协同评估

在当今高速发展的经济环境中&#xff0c;制造业面临着多重挑战&#xff0c;包括提高产品性能、压缩设计周期、实现轻量化设计和降低成本。为了有效应对这些挑战&#xff0c;多学科协同评估成为缩短研发周期和提升研制质量的关键手段。 传统的多学科评估面临着数据孤立与融合困难…

《‌黑神话:‌悟空》‌游戏攻略‌

时光荏苒&#xff0c;岁月如梭&#xff0c;不知不觉已经来到了2024年的9月份了。 ‌突然想写一篇关于《‌黑神话&#xff1a;‌悟空》‌的游戏攻略‌。 在《‌黑神话&#xff1a;‌悟空》‌这款以中国古代名著《‌西游记》‌为背景的动作角色扮演游戏中&#xff0c;‌玩家将扮…

LeetCode 热题 100 回顾9

干货分享&#xff0c;感谢您的阅读&#xff01;原文见&#xff1a;LeetCode 热题 100 回顾_力code热题100-CSDN博客 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标…

【redis】redis的特性和主要应用场景

文章目录 redis 的特性在内存中存储数据可编程的扩展能力持久化集群高可用快 redis 的应用场景实时数据存储缓存消息队列 redis 的特性 redis 的一些特性&#xff08;优点&#xff09;成就了它 在内存中存储数据 In-memory data structures MySQL 主要是通过“表”的方式来…

JavaEE-HTTPHTTPS

目录 HTTP协议 一、概念 二、http协议格式 http请求报文 http响应报文 URL格式 三、认识方法 四、认识报头 HTTP响应中的信息 HTTPS协议 对称加密 非对称加密 中间人攻击 解决中间人攻击 HTTP协议 一、概念 HTTP (全称为 "超⽂本传输协议") 是⼀种应⽤…

Mysql中的锁机制详解

一、概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。 在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决…

一文讲懂Spring Event事件通知机制

目录 一 什么是spring event 二 怎么实现spring event 一 什么是spring event 我不会按照官方的解释来说什么是spring event&#xff0c;我只是按照自己的理解来解释&#xff0c;可能原理上会和官方有偏差&#xff0c;但是它的作用和功能就是这个&#xff0c;我更加偏向于从他…

详解React setState调用原理和批量更新的过程

1. React setState 调用的原理 setState目录 1. React setState 调用的原理2. React setState 调用之后发生了什么&#xff1f;是同步还是异步&#xff1f;3. React中的setState批量更新的过程是什么&#xff1f; 具体的执行过程如下&#xff08;源码级解析&#xff09;&#x…