动手学深度学习8.7. 通过时间反向传播-笔记练习(PyTorch)

news2024/9/27 8:47:07

本节课程地址:本节无视频

本节教材地址:8.7. 通过时间反向传播 — 动手学深度学习 2.0.0 documentation (d2l.ai)

本节开源代码:...>d2l-zh>pytorch>chapter_multilayer-perceptrons>bptt.ipynb


通过时间反向传播

到目前为止,我们已经反复提到像梯度爆炸梯度消失, 以及需要对循环神经网络分离梯度。 例如,在 8.5节 中, 我们在序列上调用了detach函数。 为了能够快速构建模型并了解其工作原理, 上面所说的这些概念都没有得到充分的解释。 本节将更深入地探讨序列模型反向传播的细节, 以及相关的数学原理。

当我们首次实现循环神经网络( 8.5节)时, 遇到了梯度爆炸的问题。 如果做了练习题,就会发现梯度截断对于确保模型收敛至关重要。 为了更好地理解此问题,本节将回顾序列模型梯度的计算方式, 它的工作原理没有什么新概念,毕竟我们使用的仍然是链式法则来计算梯度。

我们在 4.7节 中描述了多层感知机中的 前向与反向传播及相关的计算图。 循环神经网络中的前向传播相对简单。 通过时间反向传播(backpropagation through time,BPTT) :cite:Werbos.1990实际上是循环神经网络中反向传播技术的一个特定应用。 它要求我们将循环神经网络的计算图一次展开一个时间步, 以获得模型变量和参数之间的依赖关系。 然后,基于链式法则,应用反向传播来计算和存储梯度。 由于序列可能相当长,因此依赖关系也可能相当长。 例如,某个1000个字符的序列, 其第一个词元可能会对最后位置的词元产生重大影响。 这在计算上是不可行的(它需要的时间和内存都太多了), 并且还需要超过1000个矩阵的乘积才能得到非常难以捉摸的梯度。 这个过程充满了计算与统计的不确定性。 在下文中,我们将阐明会发生什么以及如何在实践中解决它们。

循环神经网络的梯度分析

我们从一个描述循环神经网络工作原理的简化模型开始, 此模型忽略了隐状态的特性及其更新方式的细节。 这里的数学表示没有像过去那样明确地区分标量、向量和矩阵, 因为这些细节对于分析并不重要, 反而只会使本小节中的符号变得混乱。

在这个简化模型中,我们将时间步 t 的隐状态表示为 h_t , 输入表示为 x_t ,输出表示为 o_t 。 回想一下我们在 8.4.2节 中的讨论, 输入和隐状态可以拼接后与隐藏层中的一个权重变量相乘。 因此,我们分别使用 w_h 和 w_o 来表示隐藏层和输出层的权重。 每个时间步的隐状态和输出可以写为:

\begin{aligned}h_t &= f(x_t, h_{t-1}, w_h),\\o_t &= g(h_t, w_o),\end{aligned} (8.7.1)

其中 f 和 g 分别是隐藏层和输出层的变换。 因此,我们有一个链 {{\ldots, (x_{t-1}, h_{t-1}, o_{t-1}), (x_{t}, h_{t}, o_t), \ldots}}, 它们通过循环计算彼此依赖。 前向传播相当简单,一次一个时间步的遍历三元组 (x_t, h_t, o_t) , 然后通过一个目标函数在所有 T 个时间步内 评估输出 o_t 和对应的标签 y_t 之间的差异:

L(x_1, \ldots, x_T, y_1, \ldots, y_T, w_h, w_o) = \frac{1}{T}\sum_{t=1}^T l(y_t, o_t).(8.7.2)

对于反向传播,问题则有点棘手, 特别是当我们计算目标函数 L 关于参数 w_h 的梯度时。 具体来说,按照链式法则:

\begin{aligned}\frac{\partial L}{\partial w_h} & = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial w_h} \\& = \frac{1}{T}\sum_{t=1}^T \frac{\partial l(y_t, o_t)}{\partial o_t} \frac{\partial g(h_t, w_o)}{\partial h_t} \frac{\partial h_t}{\partial w_h}.\end{aligned} (8.7.3)

在 (8.7.3)中乘积的第一项和第二项很容易计算, 而第三项 \partial h_t/\partial w_h 是使事情变得棘手的地方, 因为我们需要循环地计算参数 w_h 对 h_t 的影响。 根据 (8.7.1)中的递归计算, h_t 既依赖于 h_{t-1} 又依赖于 w_h , 其中 h_{t-1} 的计算也依赖于 w_h 。 因此,使用链式法则产生:

\frac{\partial h_t}{\partial w_h}= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h} +\frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_h}. (8.7.4)

为了导出上述梯度,假设我们有三个序列 {a_{t}},{b_{t}},{c_{t}} , 当 t=1,2,\ldots 时,序列满足 a_{0}=0 且 a_{t}=b_{t}+c_{t}a_{t-1} 。 对于 t\geq 1 ,就很容易得出:

a_{t}=b_{t}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t}c_{j}\right)b_{i}. (8.7.5)

基于下列公式替换 a_t 、 b_t 和 c_t :

\begin{aligned}a_t &= \frac{\partial h_t}{\partial w_h},\\ b_t &= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h}, \\ c_t &= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}},\end{aligned}(8.7.6)

公式 (8.7.4)中的梯度计算 满足 a_{t}=b_{t}+c_{t}a_{t-1} 。 因此,对于每个 (8.7.5), 我们可以使用下面的公式移除 (8.7.4)中的循环计算

\frac{\partial h_t}{\partial w_h}=\frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h}+\sum_{i=1}^{t-1}\left(\prod_{j=i+1}^{t} \frac{\partial f(x_{j},h_{j-1},w_h)}{\partial h_{j-1}} \right) \frac{\partial f(x_{i},h_{i-1},w_h)}{\partial w_h}. (8.7.7)

虽然我们可以使用链式法则递归地计算 \partial h_t/\partial w_h , 但当 t 很大时这个链就会变得很长。 我们需要想想办法来处理这一问题.

完全计算

显然,我们可以仅仅计算 (8.7.7)中的全部总和, 然而,这样的计算非常缓慢,并且可能会发生梯度爆炸, 因为初始条件的微小变化就可能会对结果产生巨大的影响。 也就是说,我们可以观察到类似于蝴蝶效应的现象, 即初始条件的很小变化就会导致结果发生不成比例的变化。 这对于我们想要估计的模型而言是非常不可取的。 毕竟,我们正在寻找的是能够很好地泛化高稳定性模型的估计器。 因此,在实践中,这种方法几乎从未使用过。

截断时间步

或者,我们可以在 \tau 步后截断 (8.7.7)中的求和计算。 这是我们到目前为止一直在讨论的内容, 例如在 8.5节 中分离梯度时。 这会带来真实梯度的近似, 只需将求和终止为 \partial h_{t-\tau}/\partial w_h 。 在实践中,这种方式工作得很好。 它通常被称为截断的通过时间反向传播 :cite:Jaeger.2002。 这样做导致该模型主要侧重于短期影响,而不是长期影响。 这在现实中是可取的,因为它会将估计值偏向更简单和更稳定的模型。

随机截断

最后,我们可以用一个随机变量替换 \partial h_t/\partial w_h , 该随机变量在预期中是正确的,但是会截断序列。 这个随机变量是通过使用序列 \xi_t 来实现的, 序列预定义了 0 \leq \pi_t \leq 1 , 其中 P(\xi_t = 0) = 1-\pi_t 且 P(\xi_t = \pi_t^{-1}) = \pi_t , 因此 E[\xi_t] = 1 。 我们使用它来替换(8.7.4)中的 梯度 \partial h_t/\partial w_h 得到:

z_t= \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial w_h} +\xi_t \frac{\partial f(x_{t},h_{t-1},w_h)}{\partial h_{t-1}} \frac{\partial h_{t-1}}{\partial w_h}.(8.7.8)

从 \xi_t 的定义中推导出来 E[z_t] = \partial h_t/\partial w_h 。 每当 \xi_t = 0 时,递归计算终止在这个 t 时间步。 这导致了不同长度序列的加权和,其中长序列出现的很少, 所以将适当地加大权重。 这个想法是由塔莱克和奥利维尔 :cite:Tallec.Ollivier.2017提出的。


补充:

RNN的随机截断是用于在训练过程中减少计算资源消耗并可能缓解梯度消失或爆炸问题的一种技术。这种方法通过随机决定在序列的哪个点停止(“截断”)反向传播过程来实现。 随机截断通过引入一个随机变量 \xi_t 来决定是否继续或停止在时间步 t 的反向传播。这个随机变量 \xi_t 以概率 1-\pi_t 取值为 0(停止反向传播),以概率 \pi_t 取值为 \pi_t^{-1} (继续反向传播)。

  • 尽管引入了随机性,但 \pi_t 的设计确保了梯度的期望值 E[z_t] 与原始的梯度 \partial h_t/\partial w_h 相同。这意味着,从期望的角度来看,随机截断不会改变梯度的总体方向。
  • 当 \xi_t 为 0 时,反向传播在时间步 t 停止,不再对该时间步之后的参数进行梯度计算。这减少了计算量,但也可能导致信息丢失。
  • 由于长序列在随机截断中更可能被截断,因此在计算损失和更新参数时,长序列相对于短序列会有更大的权重。这有助于模型学习到序列中更重要的信息。
  • 随机截断可以作为一种正则化手段,有助于缓解梯度消失或爆炸问题,因为它减少了梯度在长时间依赖中的累积。
  • 在实际应用中,随机截断可以与其他技术(如梯度裁剪或使用门控单元如LSTM和GRU)结合使用,以提高RNN的训练效率和性能。

比较策略

图8.7.1 说明了 当基于循环神经网络使用通过时间反向传播 分析《时间机器》书中前几个字符的三种策略:

  • 第一行采用随机截断,方法是将文本划分为不同长度的片断;
  • 第二行采用常规截断,方法是将文本分解为相同长度的子序列。 这也是我们在循环神经网络实验中一直在做的;
  • 第三行采用通过时间的完全反向传播,结果是产生了在计算上不可行的表达式。

遗憾的是,虽然随机截断在理论上具有吸引力, 但很可能是由于多种因素在实践中并不比常规截断更好。 首先,在对过去若干个时间步经过反向传播后, 观测结果足以捕获实际的依赖关系。 其次,增加的方差抵消了时间步数越多梯度越精确的事实。 第三,我们真正想要的是只有短范围交互的模型。 因此,模型需要的正是截断的通过时间反向传播方法所具备的轻度正则化效果。

通过时间反向传播的细节

在讨论一般性原则之后,我们看一下通过时间反向传播问题的细节。 与 8.7.1节 中的分析不同, 下面我们将展示如何计算目标函数相对于所有分解模型参数的梯度。 为了保持简单,我们考虑一个没有偏置参数的循环神经网络, 其在隐藏层中的激活函数使用恒等映射( \phi(x)=x )。 对于时间步 t ,设单个样本的输入及其对应的标签分别为 \mathbf{x}_t \in \mathbb{R}^d 和 y_t 。 计算隐状态 \mathbf{h}_t \in \mathbb{R}^h 和 输出 \mathbf{o}_t \in \mathbb{R}^q 的方式为:

\begin{aligned}\mathbf{h}_t &= \mathbf{W}_{hx} \mathbf{x}_t + \mathbf{W}_{hh} \mathbf{h}_{t-1},\\ \mathbf{o}_t &= \mathbf{W}_{qh} \mathbf{h}_{t},\end{aligned}, (8.7.9)

其中权重参数为 \mathbf{W}_{hx} \in \mathbb{R}^{h \times d}、 \mathbf{W}_{hh} \in \mathbb{R}^{h \times h} 和 \mathbf{W}_{qh} \in \mathbb{R}^{q \times h} 。 用 l(\mathbf{o}_t, y_t) 表示时间步 t 处 (即从序列开始起的超过 T 个时间步)的损失函数, 则我们的目标函数的总体损失是:

L = \frac{1}{T} \sum_{t=1}^T l(\mathbf{o}_t, y_t). (8.7.10)

为了在循环神经网络的计算过程中可视化模型变量和参数之间的依赖关系, 我们可以为模型绘制一个计算图, 如 图8.7.2 所示。 例如,时间步3的隐状态 \mathbf{h}_3 的计算 取决于模型参数 \mathbf{W}_{hx} 和 \mathbf{W}_{hh} , 以及最终时间步的隐状态 \mathbf{h}_2 以及当前时间步的输入 \mathbf{x}_3 。

正如刚才所说,图8.7.2 中的模型参数是 \mathbf{W}_{hx} \mathbf{W}_{hh} 和 \mathbf{W}_{qh} 。 通常,训练该模型需要对这些参数进行梯度计算: \partial L/\partial \mathbf{W}_{hx} 、 \partial L/\partial \mathbf{W}_{hh} 和 \partial L/\partial \mathbf{W}_{qh} 。 根据 图8.7.2 中的依赖关系, 我们可以沿箭头的相反方向遍历计算图,依次计算和存储梯度。 为了灵活地表示链式法则中不同形状的矩阵、向量和标量的乘法, 我们继续使用如 4.7节 中 所述的 \text{prod} 运算符。 (使用 \text{prod} 运算符在执行必要的操作(如换位和交换输入位置)后将其参数相乘。 对于向量,这很简单,它只是矩阵-矩阵乘法。 对于高维张量,我们使用适当的对应项。 运算符 \text{prod} 指代了所有的这些符号。)

首先,在任意时间步 t , 目标函数关于模型输出的微分计算是相当简单的:

\frac{\partial L}{\partial \mathbf{o}_t} = \frac{\partial l (\mathbf{o}_t, y_t)}{T \cdot \partial \mathbf{o}_t} \in \mathbb{R}^q. (8.7.11)

现在,我们可以计算目标函数关于输出层中参数 \mathbf{W}_{qh} 的梯度:\partial L/\partial \mathbf{W}_{qh} \in \mathbb{R}^{q \times h} 。 基于 图8.7.2 , 目标函数 L 通过 \mathbf{o}_1, \ldots, \mathbf{o}_T 依赖于 \mathbf{W}_{qh} 。 依据链式法则,得到

\frac{\partial L}{\partial \mathbf{W}_{qh}} = \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_t}, \frac{\partial \mathbf{o}_t}{\partial \mathbf{W}_{qh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{o}_t} \mathbf{h}_t^\top, (8.7.12)

其中 \partial L/\partial \mathbf{o}_t 是 由 (8.7.11)给出的。

接下来,如 图8.7.2 所示, 在最后的时间步 T ,目标函数 L 仅通过 \mathbf{o}_t 依赖于隐状态 \mathbf{h}_t 。 因此,我们通过使用链式法可以很容易地得到梯度 \partial L/\partial \mathbf{h}_T \in \mathbb{R}^h :

\frac{\partial L}{\partial \mathbf{h}_T} = \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_T}, \frac{\partial \mathbf{o}_T}{\partial \mathbf{h}_T} \right) = \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_T}. (8.7.13)

当目标函数 L 通过 \mathbf{h}_{t+1} 和 \mathbf{o}_t 依赖 \mathbf{h}_t 时, 对任意时间步 t < T 来说都变得更加棘手。 根据链式法则,隐状态的梯度 \partial L/\partial \mathbf{h}_t \in \mathbb{R}^h 在任何时间步骤 t < T 时都可以递归地计算为:

\frac{\partial L}{\partial \mathbf{h}_T} = \text{prod}\left(\frac{\partial L}{\partial \mathbf{o}_T}, \frac{\partial \mathbf{o}_T}{\partial \mathbf{h}_T} \right) = \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_T}. (8.7.14)

为了进行分析,对于任何时间步 1 \leq t \leq T 展开递归计算得

\frac{\partial L}{\partial \mathbf{h}_t}= \sum_{i=t}^T {\left(\mathbf{W}_{hh}^\top\right)}^{T-i} \mathbf{W}_{qh}^\top \frac{\partial L}{\partial \mathbf{o}_{T+t-i}}.(8.7.15)

我们可以从 (8.7.15)中看到, 这个简单的线性例子已经展现了长序列模型的一些关键问题: 它陷入到 \mathbf{W}_{hh}^\top 的潜在的非常大的幂。 在这个幂中,小于1的特征值将会消失,大于1的特征值将会发散。 这在数值上是不稳定的,表现形式为梯度消失或梯度爆炸。 解决此问题的一种方法是按照计算方便的需要截断时间步长的尺寸 如 8.7.1节 中所述。 实际上,这种截断是通过在给定数量的时间步之后分离梯度来实现的。 稍后,我们将学习更复杂的序列模型(如长短期记忆模型) 是如何进一步缓解这一问题的。

最后,图8.7.2 表明: 目标函数 L 通过隐状态 \mathbf{h}1, \ldots, \mathbf{h}_T 依赖于隐藏层中的模型参数 \mathbf{W}_{hx} 和 \mathbf{W}_{hh} 。 为了计算有关这些参数的梯度 \partial L / \partial \mathbf{W}_{hx} \in \mathbb{R}^{h \times d}和 \partial L / \partial \mathbf{W}_{hh} \in \mathbb{R}^{h \times h} , 我们应用链式规则得:

\begin{aligned} \frac{\partial L}{\partial \mathbf{W}_{hx}} &= \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_{hx}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{x}_t^\top,\\ \frac{\partial L}{\partial \mathbf{W}_{hh}} &= \sum_{t=1}^T \text{prod}\left(\frac{\partial L}{\partial \mathbf{h}_t}, \frac{\partial \mathbf{h}_t}{\partial \mathbf{W}_{hh}}\right) = \sum_{t=1}^T \frac{\partial L}{\partial \mathbf{h}_t} \mathbf{h}_{t-1}^\top, \end{aligned} (8.7.16)

其中 \partial L/\partial \mathbf{h}_t 是由(8.7.13)和 (8.7.14)递归计算得到的, 是影响数值稳定性的关键量。

正如我们在 4.7节中所解释的那样, 由于通过时间反向传播是反向传播在循环神经网络中的应用方式, 所以训练循环神经网络交替使用前向传播和通过时间反向传播。 通过时间反向传播依次计算并存储上述梯度。 具体而言,存储的中间值会被重复使用,以避免重复计算, 例如存储 ∂L/∂ht , 以便在计算 ∂L/∂Whx 和 ∂L/∂Whh 时使用。

小结

  • “通过时间反向传播”仅仅适用于反向传播在具有隐状态的序列模型。
  • 截断是计算方便性和数值稳定性的需要。截断包括:规则截断和随机截断。
  • 矩阵的高次幂可能导致神经网络特征值的发散或消失,将以梯度爆炸或梯度消失的形式表现。
  • 为了计算的效率,“通过时间反向传播”在计算期间会缓存中间值。

练习

  1. 假设我们拥有一个对称矩阵 \mathbf{M} \in \mathbb{R}^{n \times n} ,其特征值为 \lambda_i ,对应的特征向量是 \mathbf{v}_ii = 1, \ldots, n)。通常情况下,假设特征值的序列顺序为|\lambda_i| \geq |\lambda{i+1}|

1) 证明 \mathbf{M}^k 拥有特征值 \lambda_i^k 。

2) 证明对于一个随机向量 \mathbf{x} \in \mathbb{R}^n , \mathbf{M}^k \mathbf{x} 将有较高概率与 \mathbf{M} 的特征向量 \mathbf{v}_1 在一条直线上。形式化这个证明过程。

3) 上述结果对于循环神经网络中的梯度意味着什么?

解:
对于特征值: A=(a_{ij})\in \mathbb{R}^{n\times n} 是n阶实对称矩阵,如果:

\mathbf{Ax=\lambda x}

有非零解向量 x\in \mathbb{R}^{n} ,则称 \lambda 是矩阵A的特征值,且\mathbf{x}是 \lambda 对应的特征向量。
1)基于题目和以上定义,有:

\mathbf{M v_i = \lambda_i v_i}

对于 \mathbf{M} 的幂次方 \mathbf{M}^k ,有:

\mathbf{M^k v_i = M^{k-1} (M v_i) = M^{k-1} (\lambda_i v_i)}

由于 \mathbf{M}^{k-1} 也是一个对称矩阵(对称矩阵的矩阵乘积仍然是对称矩阵),我们可以继续应用特征值的属性:\mathbf{M^k v_i = M^{k-1} (M v_i) = M^{k-1} (\lambda_i v_i) = M^{k-2} (\lambda^2_i v_i) = ... = \lambda^k_i v_i}

即证 \mathbf{M}^k的特征值是 \lambda_i^k ,对应的特征向量仍然是 \mathbf{v}_i 。

2)由于特征向量构成的基是正交的,可以将 \mathbf{x} 在这个基上进行分解:

\mathbf{x = c_1 v_1 + c_2 v_2 + ... + c_n v_n}

其中 c_i 是系数,可能除了有限个以外都是很小的数。 由上式和1)的证明,可得:

\mathbf{M^k x = c_1 \lambda^k_1 v_1 + c_2 \lambda^k_2 v_2 + ... + c_n \lambda^k_n v_n}

根据定义 |\lambda_i| \geq |\lambda{i+1}|\lambda_1 是模最大的特征值,当 k 增大时,项 c_1 \lambda_1^k \mathbf{v}_1 将占主导地位。
对于随机向量 
\mathbf{x} ,系数 c_i 可以看作是随机变量,在多次变换后, \mathbf{M}^k \mathbf{x} 将趋向于与 \mathbf{v}_1 对齐,因为 \lambda_1^k 的放大效应使得与 \mathbf{v}_1 相关的分量在模上占优势。
3)在RNN中,梯度的计算涉及到权重矩阵的连乘,类似于上述矩阵的幂次方。RNN中的权重矩阵可以视作是一种特殊的对称矩阵,如果权重矩阵的最大特征值的模大于1,则梯度可能会爆炸;如果小于1,则梯度可能会消失。

2. 除了梯度截断,还有其他方法来应对循环神经网络中的梯度爆炸吗?

解:
1)8.5节中介绍的梯度裁剪和练习中用到的ReLU激活函数。

2)7.5节中介绍的批量规范化。

3)4.5节中介绍的正则化。

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

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

相关文章

[通义灵码] IDE 插件实现企业知识库问答

在2024杭州云栖大会上&#xff0c;随着通义大模型能力的全面提升&#xff0c;阿里云通义灵码迎来重磅升级&#xff0c;从一年前只能完成基础的辅助编程任务&#xff0c;进化到几句话就能完成需求理解、任务拆解、代码编写、修改BUG、测试等开发任务&#xff0c;最快几分钟可从0…

XSS | DOM 型 XSS 攻击

关注这个漏洞的其他相关笔记&#xff1a;XSS 漏洞 - 学习手册-CSDN博客 0x01&#xff1a;DOM 型 XSS —— 理论篇 DOM 全称 Document Object Model&#xff0c;使用 DOM 可以使程序和脚本能够动态访问和更新文档的内容、结构及样式。 DOM 型 XSS 是一种特殊类型的反射型 XSS&…

系统实现悬浮窗-菜单-悬浮按钮功能

文章目录 需求&#xff1a;系统实现悬浮窗菜单功能或悬浮小球定制功能实际手机产品效果悬浮窗作用 一、实际应用场景二、应用上面实现功能思路Demo演示效果部分源码分析Service层View层View初始化view 添加到窗体悬浮球拖动重点代码&#xff1a; 三、系统上面实现功能思路系统服…

秒懂Linux之信号

目录 信号的基本概念 信号的处理方式 默认动作 自定义处理信号 忽略该信号 信号的产生方式 kill命令 键盘组合键 系统调用 软件条件 异常 信号产生的深层理解 core的功能 信号的阻塞 内核中的表示 sigset_t 信号集操作函数 sigprocmask sigpending …

do while循环

/while(条件) {满足条件执行的代码&#xff0c;循环体 } /* do 做 */ while (false) { Console.WriteLine(" while循环执行了"); } do { //循环体逻辑 Console.WriteLine("dowhile循环执行了"); } while (true); Console.ReadLine(); /* w…

数据库索引:最左匹配原则——提升数据库的查询性能

数据库索引&#xff1a;最左匹配原则——提升数据库的查询性能 1、核心要点2、实例3、建议 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在数据库优化中&#xff0c;组合索引的使用深受最左匹配原则的影响。这一原则是提升查询效率的关键…

详细分析Nginx中的proxy_pass 末尾斜杠

目录 前言1. 基本知识2. Demo 前言 对于Nginx的讲解&#xff0c;更多推荐阅读&#xff1a; Nginx配置静态网页访问&#xff08;图文界面&#xff09;Nginx将https重定向为http进行访问的配置&#xff08;附Demo&#xff09;Nginx从入门到精通&#xff08;全&#xff09;详细分…

[Java EE] TCP 协议

Author&#xff1a;MTingle major:人工智能 Build your hopes like a tower! 文章目录 文章目录​​​​​​​ 一. TCP 协议 二. TCP 特性 1. 确认应答(ack) 2. 超时重传 3. 连接管理 三次握手 四次挥手 TCP状态 4 滑动窗口 5. 流量控制 6.拥塞控制 7. 延时应答 8.捎带应答 9…

前端性能初探

前端监控 提升稳定性&#xff0c;更快的发现异常&#xff0c;定位异常&#xff0c;解决异常&#xff0c;js错误&#xff0c;接口异常&#xff0c;资源异常&#xff0c;白屏等。 关注用户体验&#xff0c;建立性能规范&#xff0c;长期关注优化&#xff0c;页面性能&#xff0c…

TopOn对话游戏魔客:2024移动游戏广告应如何突破?

TopOn对话游戏魔客&#xff1a;2024移动游戏广告应如何突破&#xff1f; 近年来&#xff0c;游戏广告投放的成本日益走高&#xff0c;ROI如何回正&#xff0c;素材如何创新等问题困扰着每一个广告主。在隐私政策的实施下&#xff0c;广告投放难度也在不断升级。 据data.ai发布…

MK米客方德SD NAND参考设计

一、电路设计 参考电路&#xff1a; R1~R5 (10K-100 kΩ)是上拉电阻&#xff0c;当SD NAND处于高阻抗模式时&#xff0c;保护CMD和DAT线免受总线浮动。 即使主机使用SD NAND SD模式下的1位模式&#xff0c;主机也应通过上拉电阻上拉所有的DATO-3线。 R6&#xff08;RCLK&…

解决图片放大模糊

首先需要了解设备像素和CSS像素&#xff0c;CSS像素 是 Web 开发中的逻辑像素&#xff0c;设计者根据这些像素来布局页面。设备像素 是设备屏幕上的实际像素点数。 DPR 是 设备像素 和 CSS像素 的比率&#xff0c;所以进行缩放后&#xff0c;也需要对图片尺寸进行处理&#xf…

【HarmonyOS】鸿蒙自定义TabLayout示例

【HarmonyOS】自定义TabLayout代码示例&#xff0c;通过 Scroll 锚点 Tab 布局&#xff0c;滚动条会自动滚动使选中的标签居中显示。 class MyTabItem {label: string "";positionX: number -1; // 当前位置width: number -1; // 当前宽度constructor(label: stri…

OpenHarmony(鸿蒙南向)——平台驱动指南【HDMI】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 HDMI&#xff08;High Definition Multimedia Int…

VS Code设置合集

目录 VS Code设置合集1、汉化2、VS Code自动报错3、VS Code右键没有Open In Default Browser4、VS Code设置颜色主题5、修改默认缩进字符 VS Code设置合集 1、汉化 点击插件 → 搜索chinese → 点击install&#xff0c; 同时按住ctrl shift P → 搜索>configure displ…

架构师:消息队列的技术指南

1、简述 消息队列(Message Queue, MQ)是一种异步通信机制,允许系统的各个组件通过消息在彼此之间进行通信。消息队列通过解耦系统组件、缓冲高峰期请求和提高系统的可扩展性,成为分布式系统中不可或缺的一部分。 2、工作原理 消息队列的基本工作原理是生产者将消息发布到…

Lesson08---string(4)类

Lesson08—string类&#xff08;4&#xff09; c第八章string类的实现 文章目录 Lesson08---string类&#xff08;4&#xff09;前言一、计算机是怎么储存文字的1. 在此之前先思考一个问题2.编码表2.1 ascll码2.2unicode码2.3UTF码2.4gbk码 二、实现一个简单的string1.构造函数…

【LeetCode】每日一题 2024_9_21 边积分最高的节点(哈希)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;边积分最高的节点 代码与解题思路 func edgeScore(edges []int) (ans int) {// 直接维护哈希最大值即可mp : map[int]int{}for i, v : range edges {mp[v] i// 如果多个节点的 边积分 相…

Flutter中使用FFI的方式链接C/C++的so库(harmonyos)

Flutter中使用FFI的方式链接C/C库&#xff08;harmonyos&#xff09; FFI plugin创建和so的配置FFI插件对so库的使用 FFI plugin创建和so的配置 首先我们可以根据下面的链接生成FFI plugin插件&#xff1a;开发FFI plugin插件 然后在主项目中pubspec.yaml 添加插件的依赖路径&…

PDF 秒变 JPG,2024 这些工具来助力

有些扫描仪默认将扫描文档保存为PDF格式&#xff0c;若事先未加留意&#xff0c;便可能累积大量PDF文件。然而&#xff0c;在需要将这些文件插入到其他文档或进行图形设计时&#xff0c;PDF格式可能会显得不够灵活或便捷。这时&#xff0c;将PDF转换为JPG图片格式就成为了一个实…