【Transformer系列】深入浅出理解Attention和Self-Attention机制

news/2024/7/19 11:47:37 标签: Attention机制, Self-Attention, Transformer

一、参考资料

课件:10_Transformer_1.pdf
视频:Transformer模型(1/2): 剥离RNN,保留Attention

二、Attention without RNN

Attention模型可以看到全局的信息
本章节以 Seq2Seq( (encoder + decoder)) 模型为例,介绍Attention机制

1. Keys&Values&Query定义

  • Encoder’s inputs are vectors x 1 , x 2 , ⋯   , x m \mathbf{x}_1,\mathbf{x}_2,\cdots,\mathbf{x}_m x1,x2,,xm.

  • Decoder’s inputs are vectors x 1 ′ , x 2 ′ , ⋯   , x t ′ \color{red}{\mathbf{x}_1^{\prime}},\color{red}{\mathbf{x}_2^{\prime}},\cdots,\color{red}{\mathbf{x}_t^{\prime}} x1,x2,,xt.
    在这里插入图片描述

  • K e y s \color{red}{Keys} Keys and V a l u e s \color {red}{Values} Values are based on encoder’s inputs x 1 , x 2 , ⋯   , x m \mathbf{x}_1,\mathbf{x}_2,\cdots,\mathbf{x}_m x1,x2,,xm.

  • Q u e r i e s \color {red}{Queries} Queries are based on decoder’s inputs x 1 ′ , x 2 ′ , ⋯   , x t ′ \color{red}{\mathbf{x}_1^{\prime}},\color{red}{\mathbf{x}_2^{\prime}},\cdots,\color{red}{\mathbf{x}_t^{\prime}} x1,x2,,xt.

  • K e y s \color{red}{Keys} Keys k : i = W K x i \mathbf{k}_{:i}=\mathbf{W}_K\mathbf{x}_i k:i=WKxi.

  • V a l u e s \color {red}{Values} Values v : i = W V x i \mathbf{v}_{:i}=\mathbf{W}_V\mathbf{x}_i v:i=WVxi.

  • Q u e r y \color {red}{Query} Query q : j = W Q x j ′ {\mathbf{q}_{:j}=\mathbf{W}_Q}{\mathbf{x}_j^{\prime}} q:j=WQxj.
    在这里插入图片描述
    在这里插入图片描述

2. Attention机制的原理

2.1 Compute weights

α : 1 = S o f t m a x ( K T q : 1 ) ∈ R m {\alpha_{:1}=\mathrm{Softmax}(\mathbb{K}^T{q_{:1}})\in\mathbb{R}^m} α:1=Softmax(KTq:1)Rm
在这里插入图片描述

α : 2 = S o f t m a x ( K T q : 2 ) ∈ R m {\alpha_{:2}=\mathrm{Softmax}(\mathbb{K}^T{q_{:2}})\in\mathbb{R}^m} α:2=Softmax(KTq:2)Rm
在这里插入图片描述

2.2 Compute context vector

c : 1 = α : 1 v : 1 + ⋯ + α : m v : m = V α : 1 {\mathbf{c}_{:1}=\alpha_{:1}\mathbf{v}_{:1}+\cdots+\alpha_{:m}\mathbf{v}_{:m}=\mathbf{V}\mathbf{\alpha}_{:1}} c:1=α:1v:1++α:mv:m=Vα:1
在这里插入图片描述

c : 2 = α 12 v : 1 + ⋯ + α : m v : m = V α : 2 {c_{:2}=\alpha_{12}v_{:1}+\cdots+\alpha_{:m}v_{:m}=V\alpha_{:2}} c:2=α12v:1++α:mv:m=Vα:2
在这里插入图片描述

c : j = α 1 j v : 1 + ⋯ + α m j v : m = V α : j {\mathrm{c}_{:j}}=\alpha_{1j}\mathbf{v}_{:1}+\cdots+\alpha_{mj}\mathbf{v}_{:m}=\mathbf{V}\mathbf{\alpha}_{:j} c:j=α1jv:1++αmjv:m=Vα:j
在这里插入图片描述

2.3 Output of attention layer

  • C = [ c : 1 , c : 2 , c : 3 , ⋯   , c : t ] {C=[c_{:1},c_{:2},c_{:3},\cdots,c_{:t}]} C=[c:1,c:2,c:3,,c:t].

  • c : j = V ⋅ S o f t m a x ( K T q : j ) {\mathrm{c}_{:j}=\mathrm{V}\cdot\mathrm{Softmax}(\mathrm{K}^T {\mathbf{q}_{:j}})} c:j=VSoftmax(KTq:j).

  • c : j \mathrm{c}_{:j} c:j is a function of X j ′ \mathbf{X}_j^{\prime} Xj and [ x 1 , ⋯   , x m ] [\mathbf{x}_1,\cdots,\mathbf{x}_m] [x1,,xm].
    在这里插入图片描述

2.4 Attention Layer

  • Attention layer: C = A t t n ( X , X ′ ) \mathrm{C}=\mathrm{Attn}(\mathbf{X},\mathbf{X}^{\prime}) C=Attn(X,X).
  • Encoder’s inputs: X = [ x 1 , x 2 , ⋯   , x m ] \mathbf{X}=[\mathbf{x}_1,\mathbf{x}_2,\cdots,\mathbf{x}_m] X=[x1,x2,,xm].
  • Decoder’s inputs: X ′ = [ x 1 ′ , x 2 ′ , ⋯   , x t ′ ] \mathbf{X}^{\prime}=[x_1^{\prime},x_2^{\prime},\cdots,x_t^{\prime}] X=[x1,x2,,xt].
  • Parameters: W Q ,   W K ,   W V \mathbf{W}_Q\textbf{, W}_K\textbf{, W}_V WQ, WK, WV.
    在这里插入图片描述

2.5 Machine Translation

本章节介绍Attention机制在Machine Translation机器翻译任务中的应用。将English翻译成German。
在这里插入图片描述
在这里插入图片描述

3. Attention最新研究

比标准Attention快197倍!Meta推出多头注意力机制“九头蛇”
Hydra Attention: Efficient Attention with Many Heads

三、Self-Attention without RNN

The Illustrated Transformer

Attention机制详解(二)——Self-AttentionTransformer

1. 引言

在介绍Self-Attention之前,先举了一个语义处理的例子:

“The animal didn’t cross the street because it was too tired.”

我们人很容易理解,后面的it是指animal,但是要怎么让机器能够把it和animal关联起来呢?
在这里插入图片描述

Self-attention就是在这种需求下产生的,如上图所示,我们应当有一个结构能够表达每个单词和其他每个单词的关系

2. Self-Attention机制的原理

2.0 Keys&Values&Query定义

  • 输入为 x 1 , x 2 , x 3 , . . , x m \color{red}{x_1, x_2, x_3,..,x_m} x1,x2,x3,..,xm

  • Q u e r y \color{red}{Query} Query: q : i = W Q x i \mathbf{q}_{:i}=\mathbf{W}_Q\mathbf{x}_i q:i=WQxi;

  • K e y \color{red}{Key} Key: k : i = W K x i \mathbf{k}_{:i}=\mathbf{W}_K\mathbf{x}_i k:i=WKxi;

  • V a l u e \color{red}{Value} Value: v : i = W V x i \mathbf{v}_{:i}=\mathbf{W}_V\mathbf{x}_i v:i=WVxi;
    在这里插入图片描述

2.1 Compute Weights

α : j = S o f t m a x ( K T q : j ) ∈ R m \alpha_{:j}=\mathrm{Softmax}(\mathbb{K}^T{q}_{:j})\in\mathbb{R}^m α:j=Softmax(KTq:j)Rm
在这里插入图片描述

α : 2 = S o f t m a x ( K T q : 2 ) ∈ R m \alpha_{:2}=\mathrm{Softmax}(\mathbb{K}^T\mathbf{q}_{:2})\in\mathbb{R}^m α:2=Softmax(KTq:2)Rm
在这里插入图片描述

α : j = S o f t m a x ( K T q : j ) ∈ R m \alpha_{:j}=\mathrm{Softmax}(\mathbb{K}^T\mathbf{q}_{:j})\in\mathbb{R}^m α:j=Softmax(KTq:j)Rm
在这里插入图片描述

2.2 Compute Context vector

c : 1 = α 11 v : 1 + ⋯ + α m 1 v : m = V α : 1 \mathbf{c}_{:1}=\alpha_{11}\mathbf{v}_{:1}+\cdots+\alpha_{m1}\mathbf{v}_{:m}=\mathbf{V}\mathbf{\alpha}_{:1} c:1=α11v:1++αm1v:m=Vα:1
在这里插入图片描述

c : 2 = α 12 v : 1 + ⋯ + α m 2 v : m = V α : 2 c_{:2}=\alpha_{12}v_{:1}+\cdots+\alpha_{m2}v_{:m}=V\alpha_{:2} c:2=α12v:1++αm2v:m=Vα:2
在这里插入图片描述

c : j = α 1 j v : 1 + ⋯ + α m j v : m = V α : j \mathrm{c}_{:j}=\alpha_{1j}\mathrm{v}_{:1}+\cdots+\alpha_{mj}\mathrm{v}_{:m}=\mathrm{V}\alpha_{:j} c:j=α1jv:1++αmjv:m=Vα:j
在这里插入图片描述

2.3 Output of self-attention layer

  • c : j = V ⋅ S o f t m a x ( K T q : j ) \mathrm{c}_{:j}=\mathrm{V}\cdot\mathrm{Softmax}(\mathbb{K}^T\mathbf{q}_{:j}) c:j=VSoftmax(KTq:j).
  • c : j \mathrm{c}_{:j} c:j is a function of all the 𝑚 vectors x 1 , ⋯   , X m \mathbf{x}_1,\cdots,\mathbf{X}_m x1,,Xm.
    在这里插入图片描述

2.4 Self-Attention Layer

  • Self-attention layer: C = A t t n ( X , X ) \mathrm{C}=\mathrm{Attn}(\mathbf{X},\mathbf{X}) C=Attn(X,X).
  • Inputs: X = [ x 1 , x 2 , ⋯   , x m ] \mathbf{X}=[\mathbf{x}_1,\mathbf{x}_2,\cdots,\mathbf{x}_m] X=[x1,x2,,xm].
  • Parameters: W Q ,   W K ,   W V \mathbf{W}_Q\textbf{, W}_K\textbf{, W}_V WQ, WK, WV.

在这里插入图片描述

3. Self-Attention的通俗理解

Self-Attention机制,最先在NLP中提出,其核心是利用文本中的其他词来增强目标词特征的表征能力,从而得到一个聚焦重点的句子特征。

如果看代码就会发现,QKV仅仅是对X做了三次线性变换(三个不同的全连接层),然后得到了QKV三个X变换之后的输出。它们三个在计算的时候,任意指定一个为QKV都可以(当然,指定后就不能变了)。得到QKV之后, s o f t m a x ( Q K T d k ) V softmax(\frac{QK^{T}}{\sqrt{d_{k}}})V softmax(dk QKT)V 才是真正的计算注意力的过程。所谓QKV,不过是为了引入可训练的参数,同时对X进行特征空间变换。所以,我们关心得到的三个全连接层的参数矩阵就好了,不用给QKV多么直观的解释,QKV仅仅是线性变换

4. Self-Attention的计算过程

4.1 主要步骤

对于self-attention来说,Q(Query)、K(Key)、 V(Value)三个矩阵均来自同一输入。
在这里插入图片描述

计算Thinking的self-attention(自注意力),主要步骤有:

  1. 首先计算Q向量与K向量之间的点乘;
  2. 然后为了防止其结果过大,会除以一个尺度标度(缩放因子) d k \sqrt{d_{k}} dk ,其中 d k d_{k} dk 为一个query和key向量的维度;
  3. 再利用Softmax操作其结果归一化为概率分布(注意力向量)。比如,[0.88, 0.12]这个向量的意思是,要解释Thinking这个词在这个句子中的意思,应当取0.88份Thinking原本的意思,再取0.12份Machine原本的意思,这样加权就是Thinking在这个句子中的意思;
  4. 然后乘以V向量,得到加权向量(权重求和的表示);

self-attention操作可以表示为:
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{QK^{T}}{\sqrt{d_{k}}})V Attention(Q,K,V)=softmax(dk QKT)V

4.2 举例说明

假如我们要翻译一个词组Thinking Machines,其中Thinking输入的Embedding vector用 X 1 X_1 X1 表示,Machines的Embedding vector用 X 2 X_2 X2 表示。在CV领域,Thinking和Machine可以理解为图片被切分的两个patch
在这里插入图片描述

当我们处理Thinking这个词时,我们需要计算句子中所有词与它的Attention Score。简单理解,就是将当前词当作搜索的query,去和句子中所有词(包含该词本身)的key去匹配,看看相关度有多高。 W Q W^Q WQ 矩阵是 X 1 X_1 X1 的权重矩阵, q 1 = X 1 ∗ W Q q_1 = X1 * W^Q q1=X1WQ,所以我们用 q 1 q_1 q1 代表 Thinking 对应的 query vector, k 1 k_1 k1 k 2 k_2 k2 分别代表 Thinking以及Machines对应的 key vector,则计算 Thinking 的 Attention Score的时候需要计算 q 1 q_1 q1 k 1 , k 2 k_1,k_2 k1,k2 的点乘,同理,我们计算Machines 的 Attention Score的时候需要计算 q 2 q_2 q2 k 1 , k 2 k_1,k_2 k1,k2 的点乘。如下图所示,我们分别得到 q 1 q_1 q1 k 1 , k 2 k_1,k_2 k1,k2 的点乘积,然后进行尺度缩放,再进行softmax归一化。
在这里插入图片描述

虽然,当前单词与其自身的Attention Score一般最大,其他单词根据与当前单词重要程度有相应的Attention Score。然后我们再用这些Attention Score与V向量相乘,得到加权的向量。最后图中Sum之后的结果所表达的就是每个单词在这个句子当中的意思。
在这里插入图片描述

4.3 QKV矩阵的概念

如果将输入的所有向量合并为矩阵形式,则所有QKV向量可以合并为QKV矩阵形式表示:
在这里插入图片描述

其中, W Q , W K , W V W^{Q},W^{K},W^{V} WQ,WK,WV 是模型训练过程学习到的合适的参数。

需要知道一个数学的先验知识,两个向量a和b同向, a ∗ b = ∣ a ∣ ∣ b ∣ a*b=|a||b| ab=a∣∣b;如果a和b垂直,则 a ∗ b = 0 a*b=0 ab=0;如果a和b反向,则 a ∗ b = − ∣ a ∣ ∣ b ∣ a*b=-|a||b| ab=a∣∣b。所以,两个向量的点乘(点积)可以表示两个向量的相似度,越相似则方向越趋于一致,a点乘b数值越大。则Self-Attention计算过程可以简化为:
在这里插入图片描述

上式是Self-Attention的公式,Q和K的点乘表示Q和K矩阵之间的相似程度,但是这个相似度不是归一化的,所以需要一个softmax将Q和K的结果进行归一化,那么softmax后的结果就是一个所有数值为0-1的mask矩阵(可以理解为Attention Score矩阵),而V矩阵表示输入线性变化后的特征,那么将mask矩阵乘上V矩阵就能得到加权后的特征。总结一下,Q和K矩阵的引入是为了得到一个所有数值为0-1的mask矩阵,V矩阵的引入是为了保留输入的特征(原始特征)

QKV来自于同一个句子表征,Q是目标词矩阵,K是关键词矩阵,V是原始特征,通过三步计算:

  1. Q和K进行点积计算,得到相似度矩阵;
  2. softmax归一化相似度矩阵,得到相似度权重;
  3. 将相似度权重和V矩阵加权求和,得到强化表征Z。

4.4 Multihead Attention单元

而multihead就是有不同的Q,K,V表示,最后将其结果结合起来,如下图表示:
在这里插入图片描述

这就是基本的Multihead Attention单元,对于encoder来说,就是利用这些基本单元叠加。其中K,Q,V均来自前一层encoder的输出,即encoder的每个位置都可以注意到之前一层encoder的所有位置

对于decoder来说,有两个与encoder不同的地方。一个是第一级的Masked Multihead,另一个是第二级的Multi-Head Attention不仅接收来自前一级的输出,还要接收encoder的输出。
在这里插入图片描述

第一级decoder的key,query,value均来自前一层decoder的输出,但加入了Mask操作,即我们只能attend到前面已经翻译过的输出的词语,因为当前的翻译过程并不知道下一个输出词语,这是之后才会推测到的。

第二级decoder也被称作encoder-decoder attention layer,即它的Q来自于之前一级的decoder层的输出,但其key和value来自于encoder的输出,这使得decoder的每一个位置都可以attend到输入序列的每一个位置。
在这里插入图片描述

总结一下,key和value的来源总是相同的,q在encoder以及第一级decoder中与key,value来源相同,在encoder-decoder attention layer中与key,value来源不同

5. Self-Attention的缺陷

在self-attention模型中,输入是一整排tokens,对于人类来说,我们很容易知道tokens的位置信息,比如:

  1. 绝对位置信息。a1是第一个token,a2是第二个token…
  2. 相对位置信息。a2在a1的后面一位,a4在a2的后面两位…
  3. 不同位置间的距离。a1和a3相差两个位置,a1和a4相差三个位置…

这些对于self-attention来说,是无法分辨的信息,因为self-attention的运算是无向的

6. Attention与Self-Attention对比

6.1 Attention layer

  • Attention layer: C = A t t n ( X , X ′ ) \mathrm{C}=\mathrm{Attn}(\mathbf{X},\mathbf{X}^{\prime}) C=Attn(X,X).
  • Query: q : j = W Q x j ′ \mathbf{q}_{:j}=\mathbf{W}_Q\mathbf{x}_j^{\prime} q:j=WQxj.
  • Key: k : i = W K x i \mathbf{k}_{:i}=\mathbf{W}_K\mathbf{x}_i k:i=WKxi.
  • Value: v : i = W V x i \mathbf{v}_{:i}=\mathbf{W}_V\mathbf{x}_i v:i=WVxi.
  • Output: c ; j = V ⋅ S o f t m a x ( K T q : j ) \mathrm{c}_{;j}=\mathrm{V}\cdot\mathrm{Softmax}(\mathbb{K}^T\mathbf{q}_{:j}) c;j=VSoftmax(KTq:j).
    在这里插入图片描述

6.2 Self-Attention Layer

  • Attention layer: C = A t t n ( X , X ′ ) \mathcal{C}=\mathrm{Attn}(\mathbf{X},\mathbf{X}^{\prime}) C=Attn(X,X).
  • Self-Attention layer: C = Attn ⁡ ( X , X ) C=\operatorname{Attn}(\mathbf{X},\mathbf{X}) C=Attn(X,X).
    在这里插入图片描述

7. Self-Attention代码实现

这里仅分析核心代码,详细代码请查阅:tensor2tensor/layers/common_attention.py

multihead_attention()

def multihead_attention(query_antecedent,
                        memory_antecedent,
                        ...):
    """Multihead scaled-dot-product attention with input/output transformations.
  Args:
    query_antecedent: a Tensor with shape [batch, length_q, channels]
    memory_antecedent: a Tensor with shape [batch, length_m, channels] or None
    ...
  Returns:
    The result of the attention transformation. The output shape is
        [batch_size, length_q, hidden_dim]  
  """
    #计算q, k, v矩阵
    q, k, v = compute_qkv(query_antecedent, memory_antecedent, ...)
    #计算dot_product的attention
    x = dot_product_attention(q, k, v, ...)
    x = common_layers.dense(x, ...)
    return x

compute_qkv()

def compute_qkv(query_antecedent,
                memory_antecedent,
                ...):
    """Computes query, key and value.
  Args:
    query_antecedent: a Tensor with shape [batch, length_q, channels]
    memory_antecedent: a Tensor with shape [batch, length_m, channels]
    ...
  Returns:
    q, k, v : [batch, length, depth] tensors
  """
    # 注意这里如果memory_antecedent是None,它就会设置成和query_antecedent一样,encoder的
    # self-attention调用时memory_antecedent 传进去的就是None。
    if memory_antecedent is None:
        memory_antecedent = query_antecedent
        q = compute_attention_component(
            query_antecedent,
            ...)
        # 注意这里k,v均来自于memory_antecedent。
        k = compute_attention_component(
            memory_antecedent,
            ...)
        v = compute_attention_component(
            memory_antecedent,
            ...)
        return q, k, v

    def compute_attention_component(antecedent,
                                    ...):
        """Computes attention compoenent (query, key or value).
  Args:
    antecedent: a Tensor with shape [batch, length, channels]
    name: a string specifying scope name.
    ...
  Returns:
    c : [batch, length, depth] tensor
  """
        return common_layers.dense(antecedent, ...)

dot_product_attention()

def dot_product_attention(q,
                          k,
                          v,
                          ...):
    """Dot-product attention.
  Args:
    q: Tensor with shape [..., length_q, depth_k].
    k: Tensor with shape [..., length_kv, depth_k]. Leading dimensions must
      match with q.
    v: Tensor with shape [..., length_kv, depth_v] Leading dimensions must
      match with q.
  Returns:
    Tensor with shape [..., length_q, depth_v].
  """
    # 计算Q, K的矩阵乘积。
    logits = tf.matmul(q, k, transpose_b=True)
    # 利用softmax将结果归一化。
    weights = tf.nn.softmax(logits, name="attention_weights")
    # 与V相乘得到加权表示。
    return tf.matmul(weights, v)

transformer_encoder()

def transformer_encoder(encoder_input,
                        hparams,
                        ...):
    """A stack of transformer layers.
  Args:
    encoder_input: a Tensor
    hparams: hyperparameters for model
    ...
  Returns:
    y: a Tensors
  """
    x = encoder_input
    with tf.variable_scope(name):
        for layer in range(hparams.num_encoder_layers or hparams.num_hidden_layers):
            with tf.variable_scope("layer_%d" % layer):
                with tf.variable_scope("self_attention"):
                    # layer_preprocess及layer_postprocess包含了一些layer normalization
                    # 及residual connection, dropout等操作。
                    y = common_attention.multihead_attention(
                        common_layers.layer_preprocess(x, hparams),
                        #这里注意encoder memory_antecedent设置为None
                        None,
                        ...)
                    x = common_layers.layer_postprocess(x, y, hparams)
                    with tf.variable_scope("ffn"):
                        # 前馈神经网络部分。
                        y = transformer_ffn_layer(
                            common_layers.layer_preprocess(x, hparams),
                            hparams,
                            ...)
                        x = common_layers.layer_postprocess(x, y, hparams)
                        return common_layers.layer_preprocess(x, hparams)

transformer_decoder()

def transformer_decoder(decoder_input,
                        encoder_output,
                        hparams,
                        ...):
    """A stack of transformer layers.
  Args:
    decoder_input: a Tensor
    encoder_output: a Tensor
    hparams: hyperparameters for model
    ...
  Returns:
    y: a Tensors
  """
    x = decoder_input
    with tf.variable_scope(name):
        for layer in range(hparams.num_decoder_layers or hparams.num_hidden_layers):
            layer_name = "layer_%d" % layer
            with tf.variable_scope(layer_name):
                with tf.variable_scope("self_attention"):
                    # decoder一级memory_antecedent设置为None
                    y = common_attention.multihead_attention(
                        common_layers.layer_preprocess(x, hparams),
                        None,
                        ...)
                    x = common_layers.layer_postprocess(x, y, hparams)
                    if encoder_output is not None:
                        with tf.variable_scope("encdec_attention"):
                            # decoder二级memory_antecedent设置为encoder_output
                            y = common_attention.multihead_attention(
                                common_layers.layer_preprocess(x, hparams),
                                encoder_output,
                                ...)
                            x = common_layers.layer_postprocess(x, y, hparams)
                            with tf.variable_scope("ffn"):
                                y = transformer_ffn_layer(
                                    common_layers.layer_preprocess(x, hparams),
                                    hparams,
                                    ...)
                                x = common_layers.layer_postprocess(x, y, hparams)
                                return common_layers.layer_preprocess(x, hparams)

http://www.niftyadmin.cn/n/5023903.html

相关文章

电子邮件-架构真题(二十八)

网络冗余设计叙述中,错误的是()。 网络冗余设计避免网络组件单点失效造成应用失效备用路径与主路径同时投入使用,分担主路径流量负载分担是通过并行链路提供流量分担来提供性能的网络中存在备用链路时,可以考虑加入负…

[密码学入门]凯撒密码(Caesar Cipher)

密码体质五元组:P,C,K,E,D P,plaintext,明文空间 C,ciphertext,密文空间 K,key,密钥空间 E,encrypt,加密算法 D,decrypt,解密算法 单表代换…

5.11.Webrtc接口的设计原理

在上节课中呢,我向你介绍了web rtc的接口宏,那有很多同学会产生疑问啊,那觉得web rtc为什么要把接口设计的这么复杂?还非要通过宏来实现一个代理类,再通过代理类来调用到web rtc内部。 那为什么要这么设计呢&#xf…

【经典小练习】JavaSE—拷贝文件夹

🎊专栏【Java小练习】 🍔喜欢的诗句:天行健,君子以自强不息。 🎆音乐分享【如愿】 🎄欢迎并且感谢大家指出小吉的问题🥰 文章目录 🎄效果🌺代码🛸讲解&#x…

C++面试/笔试准备,资料汇总

文章目录 后端太卷,建议往嵌入式,qt,测试,音视频,C一些细分领域投简历。有任何疑问评论区聊,我看到了回复 C面试/笔试准备,资料汇总自我介绍项目实习尽可能有1.编程语言:一.熟悉C语言…

微信小程序如何在切换页面后原页面状态不变

在微信小程序中,如果要实现在切换页面后原页面状态不变,可以通过以下几种方式来实现: 使用全局数据:可以将需要保持状态的数据存储在小程序的全局数据中,这样无论切换到哪个页面,都可以通过全局数据来获取…

排序(希尔、快速、归并排序)

文章目录 1.排序的概念及其运用 2.插入排序 3.选择排序 文章内容 1.排序的概念及其运用 1.1排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在…

MySQL间隙锁深入分析

概念 什么是间隙锁? MySQL的间隙锁(gap lock)是一种锁定相邻数据间隔的机制。 触发时机? 当使用SELECT…FOR UPDATE或UPDATE语句时,MySQL会获取一个范围锁,包括指定条件内的所有数据行,并且还…