【LLM】LLama2模型(RMSNorm、SwiGLU、RoPE位置编码)

note

  • 预训练语言模型除了自回归(Autoregressive)模型GPT,还有自编码模型(Autoencoding)BERT[1]、编-解码(Encoder-Decoder)模型BART[67],以及融合上述三种方法的自回归填空(Autoregressive Blank Infilling)模型GLM(General Language Model)[68]。
  • ChatGPT的出现,使得目前几乎所有大语言模型神经网络结构趋同,采用自回归模型,基础架构与GPT-2相同,但在归一化函数、激活函数及位置编码等细节方面有所不同。归一化函数和激活函数的选择对于大语言模型的收敛性具有一定影响,因此在LLaMA 模型被提出之后,大多数开源模型沿用了RMSNorm 和SwiGLU 的组合方式。
  • 由于LLaMA 模型所采用的位置编码方法RoPE 的外推能力不好,因此后续一些研究采用了ALiBi[69] 等具有更好外推能力的位置编码方法,使模型具有更长的上下文建模能力。
  • 很多博客推导公式rope很复杂,用到了矩阵运算、复数运算、欧拉公式、三角展开式,整体过程很繁琐。如果我们直接利用旋转矩阵的性质,推导会很简洁。假设Ra表示角度为a的旋转矩阵,那么R具有如下性质:
      1. Ra^T = R(-a)
      1. Ra Rb = R(a+b)
    • 回到旋转位置编码,我们可以去证明 <RaX, RbY> = <X, R(b-a)Y> ,证明如下:
      <RaX, RbY>
      = (RaX)^T RbY
      = X^T Ra^T RbY
      = X^T R(b-a) Y
      = <X, R(b-a)Y>

Llama 2 模型

论文:《Llama 2: Open Foundation and Fine-Tuned Chat Models》
链接:https://arxiv.org/pdf/2307.09288.pdf
在这里插入图片描述
亮点:llama2是市面上为数不多的经过RLHF对齐训练后的大模型,记录的如从最开始的有监督微调(SFT-v1) 到最终使用 PPO + RLHF 微调(RLHF-v5) 的演变历程。
在这里插入图片描述

模型结构

和很多LLM类似也是使用自回归的方式构建语言模型,但在位置编码、层归一化位置、激活函数等细节不同。模型结构和GPT2类似:
在这里插入图片描述

主要的不同点:

  • 前置的RMSNorm层
  • Q在与K相乘之前,先使用RoPE进行位置编码
  • K V Cache,并采用Group Query Attention
  • FeedForward层

不同规模的llama模型使用的超参数也有所不同:
在这里插入图片描述

RMSNorm归一化函数

回顾LN层(对同一个样本的不同特征进行归一化)归一化的公式:
LayerNorm  : y = x − E [ x ] Var ⁡ [ x ] + ϵ ∗ γ + β E [ x ] = 1 N ∑ i = 1 N x i Var ⁡ [ x ] = 1 N ∑ i = 1 N ( x i − E [ x ] ) 2 \begin{aligned} \text { LayerNorm }: y & =\frac{x-E[x]}{\sqrt{\operatorname{Var}[x]+\epsilon}} * \gamma+\beta \\ E[x] & =\frac{1}{N} \sum_{i=1}^N x_i \\ \operatorname{Var}[x] & =\frac{1}{N} \sum_{i=1}^N\left(x_i-E[x]\right)^2 \end{aligned}  LayerNorm :yE[x]Var[x]=Var[x]+ϵ xE[x]γ+β=N1i=1Nxi=N1i=1N(xiE[x])2

而RMSNorm就是LayerNorm的变体, RMSNorm省去了求均值的过程,也没有了偏置 β \beta β,(如下),RMSNorm使用均方根进行归一化
RMSNorm  : y = x Mean ⁡ ( x 2 ) + ϵ ∗ γ Mean ⁡ ( x 2 ) = 1 N ∑ i = 1 N x i 2 \begin{aligned} \text { RMSNorm }: y & =\frac{x}{\sqrt{\operatorname{Mean}\left(x^2\right)+\epsilon}} * \gamma \\ \operatorname{Mean}\left(x^2\right) & =\frac{1}{N} \sum_{i=1}^N x_i^2 \end{aligned}  RMSNorm :yMean(x2)=Mean(x2)+ϵ xγ=N1i=1Nxi2

其中 γ \gamma γ β \beta β 为可学习的参数。
RMSNorm的优点:

【换种形式】针对输入向量a,RMSNorm 函数计算公式如下: RMS ⁡ ( a ) = 1 n ∑ i = 1 n a i 2 a ˉ i = a i RMS ⁡ ( a ) \begin{aligned} \operatorname{RMS}(\boldsymbol{a}) & =\sqrt{\frac{1}{n} \sum_{i=1}^n \boldsymbol{a}_i^2} \\ \bar{a}_i & =\frac{a_i}{\operatorname{RMS}(\boldsymbol{a})} \end{aligned} RMS(a)aˉi=n1i=1nai2 =RMS(a)ai
此外,RMSNorm 还可以引入可学习的缩放因子 g i g_i gi 和偏移参数 b i b_i bi ,从而得到 a ˉ i = a i RMS ⁡ ( a ) g i + b i \bar{a}_i=\frac{a_i}{\operatorname{RMS}(\boldsymbol{a})} g_i+b_i aˉi=RMS(a)aigi+bi

# hidden_size是隐藏层大小,比如每个样本有5个特征,则hidden_size=5
class LlamaRMSNorm(nn.Module):def __init__(self, hidden_size, eps=1e-6):"""LlamaRMSNorm is equivalent to T5LayerNorm"""super().__init__()self.weight = nn.Parameter(torch.ones(hidden_size))  # 以hidden_size大小的全1张量初始化self.variance_epsilon = eps  # 给定一个很小的数,防止分母为0def forward(self, hidden_states):input_dtype = hidden_states.dtypehidden_states = hidden_states.to(torch.float32)variance = hidden_states.pow(2).mean(-1, keepdim=True)hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)return self.weight * hidden_states.to(input_dtype)  # to(input_dtype)是为了保持数据类型

SwiGLU 激活函数

SwiGLU 激活函数是Shazeer 在文献[50] 中提出的,在PaLM[14] 等模型中进行了广泛应用,并且取得了不错的效果,相较于ReLU 函数在大部分评测中都有不少提升。

在LLaMA 中,全连接层使用带有SwiGLU 激活函数的FFN (Position-wise Feed-Forward Network,FFN通常由两个线性变化组成,这里中间应用SwiGLU非线性激活函数) 的计算公式如下:
FFN ⁡ SwiGLU  ( x , W , V , W 2 ) = SwiGLU ⁡ ( x , W , V ) W 2 SwiGLU ⁡ ( x , W , V ) = Swish ⁡ β ( x W ) ⊗ x V Swish ⁡ β ( x ) = x σ ( β x ) \begin{array}{r} \operatorname{FFN}_{\text {SwiGLU }}\left(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V}, \boldsymbol{W}_2\right)=\operatorname{SwiGLU}(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V}) \boldsymbol{W}_2 \\ \operatorname{SwiGLU}(\boldsymbol{x}, \boldsymbol{W}, \boldsymbol{V})=\operatorname{Swish}_\beta(\boldsymbol{x} \boldsymbol{W}) \otimes \boldsymbol{x} \boldsymbol{V} \\ \operatorname{Swish}_\beta(\boldsymbol{x})=\boldsymbol{x} \sigma(\boldsymbol{\beta} \boldsymbol{x}) \end{array} FFNSwiGLU (x,W,V,W2)=SwiGLU(x,W,V)W2SwiGLU(x,W,V)=Swishβ(xW)xVSwishβ(x)=xσ(βx)
其中 σ ( x ) \sigma(x) σ(x) 是Sigmoid 函数

下图给出了Swish 激活函数在参数 β \beta β 不同取值下的形状。可以看到:

  • β \beta β 趋近于 0 时,Swish 函数趋近于线性函数 y = x y=x y=x;
  • β \beta β 趋近于无穷大时,Swish 函数趋近于 ReLU 函数;
  • β \beta β 取值为 1 时,Swish 函数是光滑且非单调的。

在HugqingFace 的 transformer库中Swish 函数被SiLU 函数代替。
在这里插入图片描述

使用SwiGLU的目的在于提供一种更有效的激活方式,它可以帮助模型更好地学习数据中的复杂模式和关系。Swish激活函数因其平滑性和非零的梯度对于负值的处理,已被证明在多种任务中优于传统的ReLU函数。将Swish与GLU结合,SwiGLU通过门控机制进一步增强了模型的选择性信息传递能力,这有助于提高模型在特定任务,如自然语言处理和图像识别中的表现。

我们来看具体代码,比较不同激活函数(实际用的时候可以直接使用torch的F.silu函数,SiLU其实就是beta为1时的Swish激活函数):

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import normdef gelu(x):return x * norm.cdf(x)def relu(x):return np.maximum(0, x)def swish(x, beta=1):return x * (1 / (1 + np.exp(-beta * x)))x_values = np.linspace(-5, 5, 500)
gelu_values = gelu(x_values)
relu_values = relu(x_values)
swish_values = swish(x_values)
swish_values2 = swish(x_values, beta=0.5)plt.plot(x_values, gelu_values, label='GELU')
plt.plot(x_values, relu_values, label='ReLU')
plt.plot(x_values, swish_values, label='Swish')
plt.plot(x_values, swish_values2, label='Swish (beta=0.5)')
plt.title("GELU, ReLU, and Swish Activation Functions")
plt.xlabel("x")
plt.ylabel("Activation")
plt.grid()
plt.legend()
save_path = "xxx"
plt.savefig(save_path)
plt.show()

在这里插入图片描述

RoPE位置嵌入

(1)数学基础知识回顾

旋转矩阵是正交矩阵的一种特例, 它们在数学和物理学中都有广泛的应用。下面是关于正交矩阵和旋转矩阵的一些核心特点的概述:

正交矩阵的定义和特点

  • 定义:正交矩阵是方块矩阵, 其元素为实数, 并且其行向量和列向量均为正交的单位向量。这意味着正交矩阵满足 Q T Q = Q Q T = I Q^T Q=Q Q^T=I QTQ=QQT=I, 其中 I I I 是单位矩阵, Q T Q^T QT Q Q Q 的转置矩阵 。
  • 行列式值:正交矩阵的行列式值必须为 +1 或 -1 , 这反映了其保持向量空间体积不变的性质
  • 性质:
  • 作为线性映射, 正交矩阵保持距离不变, 是保距映射。
  • 行列式值为 +1 的正交矩阵称为特殊正交矩阵, 代表纯旋转。
  • 行列式值为 -1 的正交矩阵代表旋转加上镜像, 即瑕旋转 。

旋转矩阵

  • 旋转矩阵:特殊正交矩阵, 其行列式值为 +1 , 专门用于表示空间中的旋转操作。旋转矩阵通过保持向量长度和夹角不变的同时实现空间的旋转变换。
  • 应用:旋转矩阵和正交矩阵在多个领域都有广泛的应用, 比如描述分子的点群对称性、数值线性代数中的QR分解、以及在计算机图形学中处理图形旋转等 。

示例
一些小的正交矩阵的例子可能包括:

  • 单位矩阵 I I I, 表示恒等变换。
  • 特定角度的旋转矩阵, 比如旋转 16.2 6 ∘ 16.26^{\circ} 16.26 的矩阵。
  • 针对某一轴的反射矩阵。
  • 置换坐标轴的矩阵等 。

(2)rope旋转位置编码

核心:通过绝对位置编码的方式实现相对位置编码

在位置编码上,使用旋转位置嵌入[52] 代替原有的绝对位置编码。RoPE 借助复数的思想,出发点是通过绝对位置编码的方式实现相对位置编码。其目标是通过下述运算给 q , k q, k q,k 添加绝对位置信息:
q ~ m = f ( q , m ) , k ~ n = f ( k , n ) \tilde{\boldsymbol{q}}_m=f(\boldsymbol{q}, m), \tilde{\boldsymbol{k}}_n=f(\boldsymbol{k}, n) q~m=f(q,m),k~n=f(k,n)

经过上述操作, q ~ m \tilde{\boldsymbol{q}}_m q~m k ~ n \tilde{\boldsymbol{k}}_n k~n 就带有了位置 m \mathrm{m} m n \mathrm{n} n 的绝对位置信息。

详细的证明和求解过程可参考论文,最终可以得到二维情况下用复数表示的 RoPE:
f ( q , m ) = R f ( q , m ) e i Θ f ( q , m ) = ∥ q ∥ e i ( Θ ( q ) + m θ ) = q e i m θ f(\boldsymbol{q}, m)=R_f(\boldsymbol{q}, m) e^{i \Theta_f(\boldsymbol{q}, m)}=\|\boldsymbol{q}\| e^{i(\Theta(\boldsymbol{q})+m \theta)}=\boldsymbol{q} e^{i m \theta} f(q,m)=Rf(q,m)eiΘf(q,m)=qei(Θ(q)+mθ)=qeimθ

根据复数乘法的几何意义,上述变换实际上是对应向量旋转,所以位置向量称为 “旋转式位置编码" 。还可以使用矩阵形式表示:
f ( q , m ) = ( cos ⁡ m θ − sin ⁡ m θ sin ⁡ m θ cos ⁡ m θ ) ( q 0 q 1 ) f(\boldsymbol{q}, m)=\left(\begin{array}{cc} \cos m \theta & -\sin m \theta \\ \sin m \theta & \cos m \theta \end{array}\right)\left(\begin{array}{l} \boldsymbol{q}_0 \\ \boldsymbol{q}_1 \end{array}\right) f(q,m)=(cosmθsinmθsinmθcosmθ)(q0q1)
在这里插入图片描述

根据内积满足线性叠加的性质,任意偶数维的RoPE 都可以表示为二维情形的拼接,即:
f ( q , m ) = ( cos ⁡ m θ 0 − sin ⁡ m θ 0 0 0 ⋯ 0 0 sin ⁡ m θ 0 cos ⁡ m θ 0 0 0 ⋯ 0 0 0 0 cos ⁡ m θ 1 − sin ⁡ m θ 1 ⋯ 0 0 0 0 sin ⁡ m θ 1 cos ⁡ m θ 1 ⋯ 0 0 ⋯ ⋯ ⋯ ⋯ ⋱ ⋯ ⋯ 0 0 0 0 ⋯ cos ⁡ m θ d / 2 − 1 − sin ⁡ m θ d / 2 − 1 0 0 0 0 ⋯ sin ⁡ m θ d / 2 − 1 cos ⁡ m θ d / 2 − 1 ) ⏟ R d ( q 0 q 1 q 2 q 3 ⋯ q d − 2 q d − 1 ) f(\boldsymbol{q}, m)=\underbrace{\left(\begin{array}{ccccccc} \cos m \theta_0 & -\sin m \theta_0 & 0 & 0 & \cdots & 0 & 0 \\ \sin m \theta_0 & \cos m \theta_0 & 0 & 0 & \cdots & 0 & 0 \\ 0 & 0 & \cos m \theta_1 & -\sin m \theta_1 & \cdots & 0 & 0 \\ 0 & 0 & \sin m \theta_1 & \cos m \theta_1 & \cdots & 0 & 0 \\ \cdots & \cdots & \cdots & \cdots & \ddots & \cdots & \cdots \\ 0 & 0 & 0 & 0 & \cdots & \cos m \theta_{d / 2-1} & -\sin m \theta_{d / 2-1} \\ 0 & 0 & 0 & 0 & \cdots & \sin m \theta_{d / 2-1} & \cos m \theta_{d / 2-1} \end{array}\right)}_{\boldsymbol{R}_d}\left(\begin{array}{c} \boldsymbol{q}_0 \\ \boldsymbol{q}_1 \\ \boldsymbol{q}_2 \\ \boldsymbol{q}_3 \\ \cdots \\ \boldsymbol{q}_{d-2} \\ \boldsymbol{q}_{d-1} \end{array}\right) f(q,m)=Rd cosmθ0sinmθ00000sinmθ0cosmθ0000000cosmθ1sinmθ10000sinmθ1cosmθ1000000cosmθd/21sinmθd/210000sinmθd/21cosmθd/21 q0q1q2q3qd2qd1

由于上述矩阵 R d R_d Rd 具有稀疏性,因此可以使用逐位相乘 ⊗ \otimes 操作进一步提高计算速度。

def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0):# 计算词向量元素两两分组以后,每组元素对应的旋转角度 # arange生成[0,2,4...126]freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))# t = [0,....end]t = torch.arange(end, device=freqs.device)  # type: ignore# t为列向量 freqs为行向量做外积# freqs.shape = (t.len(),freqs.len()) #shape (end,dim//2)freqs = torch.outer(t, freqs).float()  # type: ignore# 生成复数# torch.polar(abs,angle) -> abs*cos(angle) + abs*sin(angle)*jfreqs_cis = torch.polar(torch.ones_like(freqs), freqs)  # complex64# freqs_cis.shape  = (end,dim//2)return freqs_cis
​
def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):# ndim为x的维度数 ,此时应该为4ndim = 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)]# (1,x.shape[1],1,x.shape[-1])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.shape = [bsz, seqlen, self.n_local_heads, self.head_dim]# xq_.shape = [bsz, seqlen, self.n_local_heads, self.head_dim//2 , 2]# torch.view_as_complex用于将二维向量转换为复数域 torch.view_as_complex即([x,y]) -> (x+yj)# 所以经过view_as_complex变换后xq_.shape = [bsz, seqlen, self.n_local_heads, self.head_dim//2]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_) # freqs_cis.shape = (1,x.shape[1],1,x.shape[-1])# xq_ 与freqs_cis广播哈达玛积# [bsz, seqlen, self.n_local_heads, self.head_dim//2] * [1,seqlen,1,self.head_dim//2]# torch.view_as_real用于将复数再转换回实数向量, 再经过flatten展平第4个维度 # [bsz, seqlen, self.n_local_heads, self.head_dim//2] ->[bsz, seqlen, self.n_local_heads, self.head_dim//2,2 ] ->[bsz, seqlen, self.n_local_heads, self.head_dim]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)# 精简版Attention
class Attention(nn.Module):def __init__(self, args: ModelArgs):super().__init__()self.wq = Linear(...)self.wk = Linear(...)self.wv = Linear(...)self.freqs_cis = precompute_freqs_cis(dim, max_seq_len * 2)def forward(self, x: torch.Tensor):bsz, seqlen, _ = x.shapexq, xk, xv = self.wq(x), self.wk(x), self.wv(x)xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)xk = xk.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)xv = xv.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)# attention 操作之前,应用旋转位置编码xq, xk = apply_rotary_emb(xq, xk, freqs_cis=freqs_cis)#...# 进行后续Attention计算scores = torch.matmul(xq, xk.transpose(1, 2)) / math.sqrt(dim)scores = F.softmax(scores.float(), dim=-1)output = torch.matmul(scores, xv)  # (batch_size, seq_len, dim)# ......

注意力机制

Q在与K相乘之前,先使用RoPE进行位置编码:

class Attention(nn.Module):def __init__(self, args: ModelArgs):super().__init__()self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_headsmodel_parallel_size = fs_init.get_model_parallel_world_size()self.n_local_heads = args.n_heads // model_parallel_sizeself.n_local_kv_heads = self.n_kv_heads // model_parallel_sizeself.n_rep = self.n_local_heads // self.n_local_kv_headsself.head_dim = args.dim // args.n_headsself.wq = ColumnParallelLinear(args.dim,args.n_heads * self.head_dim,bias=False,gather_output=False,init_method=lambda x: x,)self.wk = ColumnParallelLinear(args.dim,self.n_kv_heads * self.head_dim,bias=False,gather_output=False,init_method=lambda x: x,)self.wv = ColumnParallelLinear(args.dim,self.n_kv_heads * self.head_dim,bias=False,gather_output=False,init_method=lambda x: x,)self.wo = RowParallelLinear(args.n_heads * self.head_dim,args.dim,bias=False,input_is_parallel=True,init_method=lambda x: x,)self.cache_k = torch.zeros((args.max_batch_size,args.max_seq_len,self.n_local_kv_heads,self.head_dim,)).cuda()self.cache_v = torch.zeros((args.max_batch_size,args.max_seq_len,self.n_local_kv_heads,self.head_dim,)).cuda()def forward(self,x: torch.Tensor,start_pos: int,freqs_cis: torch.Tensor,mask: Optional[torch.Tensor],):bsz, seqlen, _ = x.shapexq, xk, xv = self.wq(x), self.wk(x), self.wv(x)xq = xq.view(bsz, seqlen, self.n_local_heads, self.head_dim)xk = xk.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)xv = xv.view(bsz, seqlen, self.n_local_kv_heads, self.head_dim)xq, xk = apply_rotary_emb(xq, xk, freqs_cis=freqs_cis)self.cache_k = self.cache_k.to(xq)self.cache_v = self.cache_v.to(xq)self.cache_k[:bsz, start_pos : start_pos + seqlen] = xkself.cache_v[:bsz, start_pos : start_pos + seqlen] = xvkeys = self.cache_k[:bsz, : start_pos + seqlen]values = self.cache_v[:bsz, : start_pos + seqlen]# repeat k/v heads if n_kv_heads < n_headskeys = repeat_kv(keys, self.n_rep)  # (bs, seqlen, n_local_heads, head_dim)values = repeat_kv(values, self.n_rep)  # (bs, seqlen, n_local_heads, head_dim)xq = xq.transpose(1, 2)  # (bs, n_local_heads, seqlen, head_dim)keys = keys.transpose(1, 2)values = values.transpose(1, 2)scores = torch.matmul(xq, keys.transpose(2, 3)) / math.sqrt(self.head_dim)if mask is not None:scores = scores + mask  # (bs, n_local_heads, seqlen, cache_len + seqlen)scores = F.softmax(scores.float(), dim=-1).type_as(xq)output = torch.matmul(scores, values)  # (bs, n_local_heads, seqlen, head_dim)output = output.transpose(1, 2).contiguous().view(bsz, seqlen, -1)return self.wo(output)

Reference

[1] 大模型系列:SwiGLU激活函数与GLU门控线性单元原理解析
[2]《GLU Variants Improve Transformer》
[3] 《Swish: a Self-Gated Activation Function》
[4] 大模型基础|激活函数|从ReLU 到SwiGLU
[5] https://github.com/meta-llama/llama/tree/main/llama
[6] LLaMA 中的旋转式位置编码(Rotary Position Embedding)
[7] Llama 2详解
[8] 逐行对比LLaMA2和LLaMA模型源代码

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

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

相关文章

【视频图像取证篇】模糊图像增强技术之深度转化类滤波场景应用小结

【视频图像取证篇】模糊图像增强技术之深度转化类滤波场景应用小结 模糊图像增强技术之深度转化类滤波场景应用小结—【蘇小沐】 &#xff08;一&#xff09;转化类滤波器&#xff08;Convert to filter&#xff09; 1、灰度滤波器&#xff08;Gray filter&#xff09; 灰度…

stm32学习——串口通信中的奇偶校验位

常用的校验算法有奇偶校验、校验和、CRC&#xff0c;还有LRC、BCC等不常用的校验算法。 以串口通讯中的奇校验为例&#xff0c;如果数据中1的个数为奇数&#xff0c;则奇校验位0&#xff0c;否则为1。 例如原始数据为&#xff1a;0001 0011&#xff0c;数据中1的个数&#xf…

STM32-Flash闪存

简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程。 读写Flash的用途 1.利用程序存储器的剩余空间来保存掉电不丢失的用户数据。 2.通过在程序中…

springboot“涛宝”大学生二手物品交易商城

摘 要 二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式对时间、地点的限制太多&#xff0c;而在线管理系统刚好能满足这些需求&#xff0c;在线管理系统突破了传统管理方式的局限性。于是本文针对这一…

SwiftUI的 特性 - ViewModify

SwiftUI的 特性 - ViewModify 记录一下SwiftUI的 特性 - ViewModify的使用方式 可以通过viewModify来管理视图的样式&#xff0c;结合extension来完成封装达到解偶效果 import SwiftUI/// 我们可以通过viewModify来管理视图的样式&#xff0c;来达到解偶效果 struct DefaultB…

5_springboot_shiro_jwt_多端认证鉴权_禁用Cookie

1. Cookie是什么 ​ Cookie是一种在客户端&#xff08;通常是用户的Web浏览器&#xff09;和服务器之间进行状态管理的技术。当用户访问Web服务器时&#xff0c;服务器可以向用户的浏览器发送一个名为Cookie的小数据块。浏览器会将这个Cookie存储在客户端&#xff0c;为这个Co…

都2024年了,你还在用两个手指在电脑键盘上打字吗?

前言 前段时间突然想起来一件很有意思的事情&#xff1a;一个找平面设计岗位的应届生&#xff0c;使用电脑的时候居然还在用两个手指打字。 想起这个事情的时候&#xff0c;并不是想嘲笑谁。 准备步入大学或者准备步入职场的小伙伴们&#xff0c;既然找的工作基本上是要接触电…

初出茅庐的小李博客之串口屏开发一个音乐控制器UI

串口屏介绍 串口屏通常指的是一种带有串口接口的显示屏&#xff0c;可以通过串口与其他设备进行通信和控制。这种屏幕通常具有独立的控制器和显示功能&#xff0c;可以直接接入主控系统&#xff0c;实现信息的显示和交互。 开发步骤 准备UI素材 准备了100张音量的图标&#x…

同城预约上门服务APP小程序开发 打造快捷便利生活

随着移动互联网的快速发展&#xff0c;人们的生活方式正在发生深刻的变化。特别是在城市生活中&#xff0c;人们越来越依赖移动应用来解决日常生活中的各种问题。其中&#xff0c;同城预约上门服务APP正成为一种新型的生活服务平台&#xff0c;为人们提供了更加便利和快捷的服务…

2024043期传足14场胜负前瞻

2024043期售止时间为3月17日&#xff08;周日&#xff09;21点30分&#xff0c;敬请留意&#xff1a; 本期深盘多&#xff0c;1.5以下赔率1场&#xff0c;1.5-2.0赔率7场&#xff0c;其他场次是平半盘、平盘。本期14场整体难度中等偏上。以下为基础盘前瞻&#xff0c;大家可根据…

SwiftUI自定义ButtonStyle

SwiftUI自定义ButtonStyle 记录一下如何通过自定义SwiftUI自定义ButtonStyle&#xff0c;来给按钮设计一个点击样式 import SwiftUI /*本文章 通过创建ButtonStyle&#xff0c;来自定义按钮的点击动画*/struct PressButtonStyle: ButtonStyle {func makeBody(configuration: …

find_package 总结

本文参考&#xff1a;“轻松搞定CMake”系列之find_package用法详解 原理 find_package 即在指定目录CMAKE_MODULE_PATH 或 CMAKE_PREFIX_PATH查找对应的cmake文件。 find 模式 Module模式(默认)&#xff1a;查询Findxxx.cmake配置文件, 在CMAKE_MODULE_PATH 目录Config模式…

什么又是线程呢??

线程&#xff1a; 线程可以并发的执行&#xff0c;但是线程的地址是可以共享的 进程与线程的比较&#xff1a; 进程>线程 线程分三种&#xff1a; 用户线程 只有用户程序的库函数来 用户线程 因为操作系统感知不到 线程&#xff0c;如果有线程在运行&#xff0c;然后不交…

爬虫 某物流

目标地址 url "https://api.jdl.com/aging/feeInquiryNewByJDL" 加密参数 ciphertext和data 搜关键字ciphertext跟着栈走 很明显的DES加密 window globalconst e require(jsencrypt); // const e require(JSEncrypt) // e r(775).JSEncrypt // const t requi…

【js刷题:数据结构数组篇之移除元素】

移除元素 一、题目二、思路三、方法1.暴力解法2.双指针法定义快指针和慢指针代码展示 三、力扣刷题1.删除排序数组中的重复项 一、题目 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额…

四连杆机构运动学仿真 | 【Matlab源码+理论公式文本】

【程序简介】&#x1f4bb;&#x1f50d; 本程序通过matlab实现了四连杆机构的运动学仿真编程&#xff0c;动态展现了四连杆机构的运动动画&#xff0c;同时给出了角位移、角速度和角加速度的时程曲线&#xff0c;除了程序本身&#xff0c;还提供了机构运动学公式推导文档&…

ffmpeg6.0从demux_decode.c源码探索解封装流程

前言 在播放器的播放视频、音视频媒体文件的推流等实际应用中,解封装(demux)这个操作是不可避免的,也是最基础的操作。 拿播放器播放MP4来说,如果想实现播放器视频画面的播放和音频声音的播放都需要经过这个解封装的步骤。因为MP4是一种媒体文件格式,是一种封装格式,M…

释放人工智能的力量:GPU服务器托管和高电机柜托管的关键作用

随着人工智能技术的不断发展&#xff0c;GPU服务器托管和高电机柜托管也变得愈发重要。这些技术在人工智能领域发挥着关键作用&#xff0c;为AI算法的训练和推理提供了强大的计算支持。 GPU服务器托管是指将GPU服务器放置在专门的数据中心中&#xff0c;通过云服务提供商提供的…

java基础入门day01

day01 java的特点 简单&#xff1a;摒弃了c和c的指针&#xff0c;垃圾回收机制&#xff0c;相对而言简单许多 安全&#xff1a;几乎各大银行都会使用java作为其开发平台 跨平台&#xff1a;可以运行在任何的操作系统之上&#xff0c;windows&#xff0c; linux以及mac平台&a…

安装snap再安装flutter再安装localsend@Ubuntu(FreeBSD下未成功)

Localsend介绍 localsend是一个跨平台的文件传送软件&#xff0c;可以在Windows、MacOS、Linux、Android和IOS下互相传送文件&#xff0c;只要在同一个局域网即可。 localsend官网&#xff1a;LocalSend 尝试安装localsend&#xff0c;发现需要使用flutter&#xff0c; 安装f…