从反向传播(BP)到BPTT:详细数学推导【原理理解】

news2025/3/16 4:13:14

从反向传播到BPTT:详细推导与问题解析

在本文中,我们将从反向传播算法开始,详细推导出反向传播通过时间(Backpropagation Through Time, BPTT)算法。重点讨论BPTT中的梯度消失和梯度爆炸问题,并解释如何解决这些问题。假设读者已经具备反向传播的基本知识,我们将简要回顾反向传播的核心概念,然后深入解析BPTT算法。

反向传播算法的简要回顾

反向传播算法(Backpropagation)是用于训练神经网络的一种有效方法。其核心思想是通过链式法则(Chain Rule)计算损失函数相对于各个权重的梯度,然后使用梯度下降法更新权重。以下是反向传播的主要步骤:

  1. 前向传播: 计算输入数据通过神经网络各层的输出。
  2. 计算损失: 通过损失函数计算预测输出与真实输出之间的误差。
  3. 反向传播: 通过链式法则计算损失相对于每个权重的梯度。

反向传播的详细过程涉及链式法则在多层网络中的应用,我们将这些步骤拓展到处理时间序列数据的BPTT算法中。

详情见:BP神经网络反向传播原理【数学原理、举例说明】

反向传播通过时间(BPTT)算法

BPTT是一种针对循环神经网络(Recurrent Neural Networks, RNNs)的训练算法,它将标准反向传播算法扩展到 时间序列数据 上。RNN的特点是其隐藏层不仅依赖于 当前的输入 ,还依赖于 前一时间步的隐藏状态 ,这使得RNN能够处理序列数据。然而,这也引入了计算梯度的复杂性,因为损失不仅与当前时间步的输出相关,还与之前时间步的隐藏状态相关。

前向传播

在BPTT中,前向传播是从时间步1到时间步T逐步计算每个时间步的隐藏状态和输出。

假设我们有一个输入序列 { x 1 , x 2 , … , x T } \{x_1, x_2, \ldots, x_T\} {x1,x2,,xT} ,每个 x t x_t xt 是在时间步t的输入。RNN的隐藏状态 h t h_t ht 和输出 y t y_t yt 依次计算如下:

  1. 初始化: 首先,隐藏状态 h 0 h_0 h0 通常初始化为零或小的随机值。
    h 0 = 0 (或小的随机值) h_0 = 0 \text{(或小的随机值)} h0=0(或小的随机值)

  2. 时间步1的计算:

    • 计算隐藏状态 h 1 h_1 h1
      h 1 = f ( W h x x 1 + W h h h 0 + b h ) h_1 = f(W_{hx} x_1 + W_{hh} h_0 + b_h) h1=f(Whxx1+Whhh0+bh)
      这里, W h x W_{hx} Whx 是输入到隐藏层的权重矩阵, W h h W_{hh} Whh 是隐藏层到隐藏层的权重矩阵, b h b_h bh 是偏置, f f f 是激活函数(如tanh或ReLU)。这一步可以理解为将 当前输入 x 1 x_1 x1 与前一时间步的隐藏状态 h 0 h_0 h0 结合,通过一个激活函数得到当前时间步的隐藏状态 h 1 h_1 h1

      这与传统BP(Backpropagation)不同,传统BP不考虑时间步之间的依赖,而RNN通过引入隐藏层状态的递归关系来捕捉 时间序列中的依赖性

    • 计算输出 y 1 y_1 y1
      y 1 = g ( W h y h 1 + b y ) y_1 = g(W_{hy} h_1 + b_y) y1=g(Whyh1+by)
      这里, W h y W_{hy} Why 是隐藏层到输出层的权重矩阵, b y b_y by 是输出层的偏置, g g g 是输出层的激活函数(通常为softmax或线性函数)。这一步可以理解为将隐藏状态 h 1 h_1 h1 转化为输出 y 1 y_1 y1

  3. 时间步t的计算(t=2, …, T): 对于后续的每个时间步,我们重复上述步骤:

    • 计算隐藏状态 h t h_t ht
      h t = f ( W h x x t + W h h h t − 1 + b h ) h_t = f(W_{hx} x_t + W_{hh} h_{t-1} + b_h) ht=f(Whxxt+Whhht1+bh)
      这里,隐藏状态 h t h_t ht 是当前输入 x t x_t xt 与前一时间步隐藏状态 h t − 1 h_{t-1} ht1 的结合,通过激活函数 f f f 得到。

    • 计算输出 y t y_t yt
      y t = g ( W h y h t + b y ) y_t = g(W_{hy} h_t + b_y) yt=g(Whyht+by)
      输出 y t y_t yt 是当前隐藏状态 h t h_t ht 通过激活函数 g g g 得到。

通过这一步步计算,我们将输入序列 { x 1 , x 2 , … , x t } \{x_1, x_2, \ldots, x_t\} {x1,x2,,xt} 转化为隐藏状态序列 { h 1 , h 2 , … , h t } \{h_1, h_2, \ldots, h_t\} {h1,h2,,ht} 和输出序列 { y 1 , y 2 , … , y t } \{y_1, y_2, \ldots, y_t\} {y1,y2,,yt}

为什么要将输入序列转换为隐藏状态序列和输出序列?

将输入序列转换为隐藏状态序列和输出序列的原因在于RNN的核心思想通过引入隐藏状态,模型能够捕捉序列数据中的时间依赖关系 。隐藏状态序列 { h 1 , h 2 , … , h t } \{h_1, h_2, \ldots, h_t\} {h1,h2,,ht} 是RNN对输入序列的内部表示,记录了前一时间步的信息,并将其传递给当前时间步。

数学上,这种递归关系可以理解为状态转移函数:
h t = f ( W h x x t + W h h h t − 1 + b h ) h_t = f(W_{hx} x_t + W_{hh} h_{t-1} + b_h) ht=f(Whxxt+Whhht1+bh)
这个公式表示当前的隐藏状态 h t h_t ht 是当前输入 x t x_t xt 和前一时间步隐藏状态 h t − 1 h_{t-1} ht1 的函数。通过这种递归关系,RNN能够记住之前时间步的信息,并在后续时间步中使用,从而捕捉长时间的依赖关系。

输出序列 { y 1 , y 2 , … , y t } \{y_1, y_2, \ldots, y_t\} {y1,y2,,yt} 是模型的预测结果,通过将隐藏状态转化为输出,我们可以计算损失,并通过反向传播更新模型参数。

计算损失

计算损失是为了衡量模型输出与真实输出之间的误差。对于整个序列,我们通常采用均方误差(MSE)或交叉熵损失。假设真实输出序列为 { y ^ 1 , y ^ 2 , … , y ^ t } \{\hat{y}_1, \hat{y}_2, \ldots, \hat{y}_t\} {y^1,y^2,,y^t} ,损失函数 L L L 可以表示为:

  1. 均方误差(MSE):
    L = ∑ t = 1 T 1 2 ( y t − y ^ t ) 2 L = \sum_{t=1}^T \frac{1}{2} (y_t - \hat{y}_t)^2 L=t=1T21(yty^t)2
    这里,我们计算每个时间步的输出 y t y_t yt 与真实输出 y ^ t \hat{y}_t y^t 之间的平方误差,并将所有时间步的误差求和。

  2. 交叉熵损失:
    L = − ∑ t = 1 T [ y ^ t log ⁡ ( y t ) + ( 1 − y ^ t ) log ⁡ ( 1 − y t ) ] L = -\sum_{t=1}^T [\hat{y}_t \log(y_t) + (1 - \hat{y}_t) \log(1 - y_t)] L=t=1T[y^tlog(yt)+(1y^t)log(1yt)]
    这里,我们计算每个时间步的输出 y t y_t yt 与真实输出 y ^ t \hat{y}_t y^t 之间的交叉熵损失,并将所有时间步的损失求和。

反向传播

反向传播的目的是通过链式法则计算损失相对于每个权重的梯度,并更新权重。具体步骤如下:

  1. 计算输出层的梯度:

    δ t y = ∂ ℓ ( y t , y ^ t ) ∂ y t ⋅ g ′ ( y t ) \delta^y_t = \frac{\partial \ell(y_t, \hat{y}_t)}{\partial y_t} \cdot g'(y_t) δty=yt(yt,y^t)g(yt)
    这里, δ t y \delta^y_t δty 是第 t 时间步的输出层梯度。这个公式中的每个部分代表:

    • ∂ ℓ ( y t , y ^ t ) ∂ y t \frac{\partial \ell(y_t, \hat{y}_t)}{\partial y_t} yt(yt,y^t) 是损失函数 ℓ \ell 对输出 y t y_t yt 的导数,它表示输出 y t y_t yt 的变化对损失函数 ℓ \ell 的影响。
    • g ′ ( y t ) g'(y_t) g(yt) 是输出层激活函数 g g g 对其输入 y t y_t yt 的导数。

根据链式法则,我们计算 ∂ ℓ ∂ y t \frac{\partial \ell}{\partial y_t} yt 时,需要考虑:
∂ ℓ ∂ y t = ∂ ℓ ∂ g ( y t ) ⋅ ∂ g ( y t ) ∂ y t \frac{\partial \ell}{\partial y_t} = \frac{\partial \ell}{\partial g(y_t)} \cdot \frac{\partial g(y_t)}{\partial y_t} yt=g(yt)ytg(yt)
这里, ∂ ℓ ∂ g ( y t ) \frac{\partial \ell}{\partial g(y_t)} g(yt) 是损失函数对激活函数输出的导数, ∂ g ( y t ) ∂ y t \frac{\partial g(y_t)}{\partial y_t} ytg(yt) 是激活函数 g g g 对输入 y t y_t yt 的导数。

  1. 计算隐藏层的梯度:
    隐藏层的梯度计算涉及当前时间步和未来时间步的影响:
    δ t h = δ t y W h y T ⋅ f ′ ( h t ) + δ t + 1 h W h h T ⋅ f ′ ( h t ) \delta^h_t = \delta^y_t W_{hy}^T \cdot f'(h_t) + \delta^h_{t+1} W_{hh}^T \cdot f'(h_t) δth=δtyWhyTf(ht)+δt+1hWhhTf(ht)

    • δ t y W h y T ⋅ f ′ ( h t ) \delta^y_t W_{hy}^T \cdot f'(h_t) δtyWhyTf(ht)当前时间步输出层梯度 传播回来的部分。具体来说, δ t y \delta^y_t δty 是输出层梯度,通过输出层到隐藏层的权重 W h y W_{hy} Why 传递回隐藏层,再乘以隐藏层激活函数 f f f 的导数 f ′ ( h t ) f'(h_t) f(ht)
    • δ t + 1 h W h h T ⋅ f ′ ( h t ) \delta^h_{t+1} W_{hh}^T \cdot f'(h_t) δt+1hWhhTf(ht)未来时间步隐藏层梯度 传播回来的部分。这里, δ t + 1 h \delta^h_{t+1} δt+1h 是下一时间步的隐藏层梯度,通过隐藏层到隐藏层的权重 W h h W_{hh} Whh 传递回当前隐藏层,再乘以当前隐藏层激活函数的导数 f ′ ( h t ) f'(h_t) f(ht)
  2. 更新权重:
    权重更新是通过梯度下降法进行的。梯度下降法的基本思想是沿着 梯度的反方向 更新权重,使得损失函数逐渐减小。

    • 输入到隐藏层的权重更新
      Δ W h x = ∑ t = 1 T δ t h ⋅ x t T \Delta W_{hx} = \sum_{t=1}^T \delta^h_t \cdot x_t^T ΔWhx=t=1TδthxtT
      这里, δ t h \delta^h_t δth 是时间步 t t t 的隐藏层梯度, ⋅ x t T \cdot x_t^T xtT 表示输入 x t x_t xt 的转置。我们将所有时间步的梯度相加,得到输入到隐藏层权重的更新量。

    • 隐藏层到隐藏层的权重更新
      Δ W h h = ∑ t = 2 T δ t h ⋅ h t − 1 T \Delta W_{hh} = \sum_{t=2}^T \delta^h_t \cdot h_{t-1}^T ΔWhh=t=2Tδthht1T
      这里, δ t h \delta^h_t δth 是时间步 t t t 的隐藏层梯度, ⋅ h t − 1 T \cdot h_{t-1}^T ht1T 表示前一时间步隐藏状态 h t − 1 h_{t-1} ht1 的转置。同样地,我们将所有时间步的梯度相加,得到隐藏层到隐藏层权重的更新量。

    • 隐藏层到输出层的权重更新
      Δ W h y = ∑ t = 1 T δ t y ⋅ h t T \Delta W_{hy} = \sum_{t=1}^T \delta^y_t \cdot h_t^T ΔWhy=t=1TδtyhtT
      这里, δ t y \delta^y_t δty 是时间步 t t t 的输出层梯度, ⋅ h t T \cdot h_t^T htT 表示当前时间步隐藏状态 h t h_t ht 的转置。我们将所有时间步的梯度相加,得到隐藏层到输出层权重的更新量。

这些权重更新公式通过链式法则计算各个权重的梯度,并使用梯度下降法更新权重,使得损失函数最小化。

梯度消失和梯度爆炸问题

在BPTT中,梯度消失和梯度爆炸是两个主要问题。这两个问题都与梯度在时间步长上的传播有关。随着时间步数增加,梯度的值可能会逐渐减小到几乎为零(梯度消失)或变得非常大(梯度爆炸),这会影响模型的训练效果。

梯度消失

随着时间步数的增加,梯度逐渐减小,最终可能变得非常接近于零。这导致模型无法有效更新权重,无法捕捉到长期依赖关系。

  • 数学上,如果激活函数的导数 f ′ ( h t ) f'(h_t) f(ht) 小于 1,多次相乘后会趋近于零。
  • 比如,激活函数为 tanh ⁡ \tanh tanh ,它的导数值在 [-1, 1] 之间,且通常小于 1。
梯度爆炸

随着时间步数的增加,梯度逐渐变大,最终可能变得非常大。这导致权重更新过大,模型无法收敛。

  • 数学上,如果激活函数的导数 f ′ ( h t ) f'(h_t) f(ht) 大于 1,多次相乘后会迅速增长。
  • 比如,激活函数为 ReLU(修正线性单元),其导数在正区间为 1,如果某些参数或权重较大,梯度可能会迅速累积变大。

梯度传播过程中的数学推导

假设一个简单的RNN模型,隐藏层激活函数为 f f f ,我们考虑隐藏层的状态 h t h_t ht 及其梯度的传播过程。

基本概念和公式
  1. 隐藏层状态更新:
    h t = f ( W h h h t − 1 + W h x x t + b h ) h_t = f(W_{hh} h_{t-1} + W_{hx} x_t + b_h) ht=f(Whhht1+Whxxt+bh)

  2. 输出层状态:
    y t = g ( W h y h t + b y ) y_t = g(W_{hy} h_t + b_y) yt=g(Whyht+by)

  3. 损失函数:
    L = ∑ t = 1 T ℓ ( y t , y ^ t ) \mathcal{L} = \sum_{t=1}^T \ell(y_t, \hat{y}_t) L=t=1T(yt,y^t)

  4. 输出层梯度:
    δ t y = ∂ ℓ ( y t , y ^ t ) ∂ y t ⋅ g ′ ( y t ) \delta^y_t = \frac{\partial \ell(y_t, \hat{y}_t)}{\partial y_t} \cdot g'(y_t) δty=yt(yt,y^t)g(yt)

梯度传播到隐藏层

我们通过链式法则计算隐藏层梯度。首先从输出层梯度开始传播,考虑激活函数 f f f 的导数 f ′ ( h t ) f'(h_t) f(ht)

隐藏层梯度的递归公式为:

δ t h = δ t y W h y T ⋅ f ′ ( h t ) + δ t + 1 h W h h T ⋅ f ′ ( h t ) \delta^h_t = \delta^y_t W_{hy}^T \cdot f'(h_t) + \delta^h_{t+1} W_{hh}^T \cdot f'(h_t) δth=δtyWhyTf(ht)+δt+1hWhhTf(ht)

假设激活函数 f f f 的导数在所有时间步长上都是一个常数 k k k,即 f ′ ( h t ) = k f'(h_t) = k f(ht)=k。为了简化,我们假设权重矩阵 W h h W_{hh} Whh W h y W_{hy} Why 也为常数。

梯度递归公式的展开

我们从最后一个时间步 T T T 开始,逐步向前展开递归公式:

δ T h = δ T y W h y T ⋅ k \delta^h_T = \delta^y_T W_{hy}^T \cdot k δTh=δTyWhyTk

对于 T − 1 T-1 T1 时间步:

δ T − 1 h = δ T − 1 y W h y T ⋅ k + δ T h W h h T ⋅ k \delta^h_{T-1} = \delta^y_{T-1} W_{hy}^T \cdot k + \delta^h_T W_{hh}^T \cdot k δT1h=δT1yWhyTk+δThWhhTk

δ T h \delta^h_T δTh带入:
= δ T − 1 y W h y T ⋅ k + ( δ T y W h y T ⋅ k ) W h h T ⋅ k = \delta^y_{T-1} W_{hy}^T \cdot k + (\delta^y_T W_{hy}^T \cdot k) W_{hh}^T \cdot k =δT1yWhyTk+(δTyWhyTk)WhhTk
= k δ T − 1 y W h y T + k 2 δ T y W h y T W h h T = k \delta^y_{T-1} W_{hy}^T + k^2 \delta^y_T W_{hy}^T W_{hh}^T =kδT1yWhyT+k2δTyWhyTWhhT

我们继续展开 T − 2 T-2 T2 时间步:

δ T − 2 h = δ T − 2 y W h y T ⋅ k + δ T − 1 h W h h T ⋅ k \delta^h_{T-2} = \delta^y_{T-2} W_{hy}^T \cdot k + \delta^h_{T-1} W_{hh}^T \cdot k δT2h=δT2yWhyTk+δT1hWhhTk
= δ T − 2 y W h y T ⋅ k + ( δ T − 1 y W h y T ⋅ k + δ T h W h h T ⋅ k ) W h h T ⋅ k = \delta^y_{T-2} W_{hy}^T \cdot k + \left( \delta^y_{T-1} W_{hy}^T \cdot k + \delta^h_T W_{hh}^T \cdot k \right) W_{hh}^T \cdot k =δT2yWhyTk+(δT1yWhyTk+δThWhhTk)WhhTk
= δ T − 2 y W h y T ⋅ k + δ T − 1 y W h y T W h h T ⋅ k 2 + δ T y W h y T W h h T W h h T ⋅ k 3 = \delta^y_{T-2} W_{hy}^T \cdot k + \delta^y_{T-1} W_{hy}^T W_{hh}^T \cdot k^2 + \delta^y_T W_{hy}^T W_{hh}^T W_{hh}^T \cdot k^3 =δT2yWhyTk+δT1yWhyTWhhTk2+δTyWhyTWhhTWhhTk3

推广到一般情况,对于时间步 ( t ):

δ t h = δ t y W h y T ⋅ k + δ t + 1 h W h h T ⋅ k \delta^h_t = \delta^y_t W_{hy}^T \cdot k + \delta^h_{t+1} W_{hh}^T \cdot k δth=δtyWhyTk+δt+1hWhhTk

递归展开后,我们可以看到梯度会逐步乘以 ( k ),并传播到前面的时间步。这意味着:

δ t h = δ t y ⋅ ( W h y T ⋅ k t ) \delta^h_t = \delta^y_t \cdot (W_{hy}^T \cdot k^t) δth=δty(WhyTkt)

推导公式

假设 W h y W_{hy} Why W h h W_{hh} Whh 为单位矩阵 I I I,我们简化得到:

δ t h = δ ⋅ k t \delta^h_t = \delta \cdot k^t δth=δkt

如果激活函数的导数 k < 1 k < 1 k<1 ,那么 k t k^t kt 会随着 t t t 增加而快速趋近于零,导致梯度消失。

具体示例

为了更直观地理解梯度消失和梯度爆炸,我们用一个简单的RNN模型和一个假设的初始梯度进行解释。

假设:

  • 输入序列长度为 ( T )。
  • 隐藏层激活函数为 ( tanh ⁡ \tanh tanh )。
  • 初始梯度为 ( δ = 1 \delta = 1 δ=1 )。
  • 每一步的激活函数导数 ( f ′ ( h t ) = k f'(h_t) = k f(ht)=k )(假设为常数)。
梯度消失示例

假设激活函数导数 k = 0.5 k = 0.5 k=0.5 ,即每一步的导数都小于 1。

随着时间步数 T T T 的增加,梯度会逐渐减小:

δ T h = δ ⋅ k T \delta^h_T = \delta \cdot k^T δTh=δkT

例如,当 T = 10 T = 10 T=10 时:

δ 10 h = 1 ⋅ 0. 5 10 = 1 ⋅ 0.00098 = 0.00098 \delta^h_{10} = 1 \cdot 0.5^{10} = 1 \cdot 0.00098 = 0.00098 δ10h=10.510=10.00098=0.00098

可以看到,梯度非常小,接近于零。

更进一步,如果 T = 20 T = 20 T=20

δ 20 h = 1 ⋅ 0. 5 20 = 1 ⋅ 0.00000095 = 0.00000095 \delta^h_{20} = 1 \cdot 0.5^{20} = 1 \cdot 0.00000095 = 0.00000095 δ20h=10.520=10.00000095=0.00000095

梯度几乎为零,说明模型无法有效更新权重,导致无法捕捉长期依赖关系。

梯度爆炸示例

假设激活函数导数 k = 1.5 k = 1.5 k=1.5 ,即每一步的导数都大于 1。

随着时间步数 T T T 的增加,梯度会逐渐增大:

δ T h = δ ⋅ k T \delta^h_T = \delta \cdot k^T δTh=δkT

例如,当 T = 10 T = 10 T=10 时:

δ 10 h = 1 ⋅ 1. 5 10 = 1 ⋅ 57.67 = 57.67 \delta^h_{10} = 1 \cdot 1.5^{10} = 1 \cdot 57.67 = 57.67 δ10h=11.510=157.67=57.67

可以看到,梯度非常大,导致训练不稳定。

更进一步,如果 T = 20 T = 20 T=20

δ 20 h = 1 ⋅ 1. 5 20 = 1 ⋅ 33252.32 = 33252.32 \delta^h_{20} = 1 \cdot 1.5^{20} = 1 \cdot 33252.32 = 33252.32 δ20h=11.520=133252.32=33252.32

梯度变得非常大,说明模型无法收敛,权重更新会过大,导致训练失败。

在这里插入图片描述

  • 梯度消失:图中红线表示的梯度随着时间步数 𝑇 增加而快速减小,趋近于零。这说明当时间步数增加时,梯度值变得非常小,无法有效更新权重,导致模型无法捕捉长期依赖关系。
  • 梯度爆炸:图中蓝线表示的梯度随着时间步数 𝑇 增加而快速增大。这说明当时间步数增加时,梯度值变得非常大,导致权重更新过大,训练过程变得不稳定,模型难以收敛。

解决梯度消失和梯度爆炸的方法

为了缓解梯度消失和梯度爆炸问题,可以采用以下几种常见的方法:

  1. 梯度裁剪(Gradient Clipping)

    • 将梯度的绝对值限制在某个阈值范围内,防止梯度爆炸。
    • 例如,当梯度超过某个阈值时,将其裁剪到这个阈值。
  2. 正则化方法

    • 使用L2正则化(权重衰减)防止过度活跃的神经元。
    • 增加权重更新时的惩罚项,控制权重值不至于过大。
  3. 批归一化(Batch Normalization)

    • 对每个时间步的隐藏状态进行归一化,稳定训练过程。
    • 通过归一化,控制每个时间步的输出范围,防止梯度过大或过小。
  4. 调整激活函数

    • 选择适当的激活函数(如ReLU、Leaky ReLU等),防止梯度消失和爆炸。
    • 例如,Leaky ReLU 在负区间也有非零导数,避免了完全的梯度消失问题。

为什么很小的梯度无法更新权重并导致无法捕捉长期依赖关系?

当梯度非常小时,反向传播的权重更新公式:

Δ W = − η ⋅ ∂ L ∂ W \Delta W = -\eta \cdot \frac{\partial \mathcal{L}}{\partial W} ΔW=ηWL

梯度项 ∂ L ∂ W \frac{\partial \mathcal{L}}{\partial W} WL 会非常小。这里, η \eta η 是学习率。当梯度接近零时,权重更新 Δ W \Delta W ΔW 也会接近零。这意味着神经网络的权重几乎不会发生变化,导致模型无法从训练数据中学习到有用的信息,从而无法有效捕捉长期依赖关系。

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

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

相关文章

[Linux]Crond任务调度以及at任务调度

一.crond任务定时调度 crond是反复检测执行的&#xff0c;一个任务结束后&#xff0c;在所规定的时间之后会再次执行 crontab 指令可以给系统分配定时任务 crontab -e 进入编辑页面&#xff0c;设定任务 crontab -l 查看已有定时任务 crontab -r 删除所有任务 编辑时&#xff…

清华新突破||新研究揭示多智能体协作的秘密武器

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读点击订阅&#xff1a;人工智能论文解读合集 引言&#xff1a;多智能体协作中的挑战与机遇 在多智能体系统中&#xff0c;智能体需要通过协作来完成复杂的任务&#xff0c;这种协作涉及到通信…

二叉树链式结构补充

1.二叉树k层结点个数 2.二叉树查找值为x的结点 3.二叉树基础oj练习 1.二叉树k层结点个数 设置k值&#xff0c;k层到1结点的值是k-1&#xff0c;所以1结点到k层的结点也是k-1的距离&#xff08;高度&#xff09;&#xff0c;这样就可以每下一层就把k值减少一个单位&#xff0…

MySQL的数据库和表

查看数据库 命令行的方式&#xff1a; cd /mysql/bin mysql.exe -uroot -p IP&#xff08;不是连接自己&#xff09; 端口&#xff08;不是3306&#xff09; show databases; 直接使用图形化界面点击&#xff1a; 查看库里的表 使用命令行查看&#xff1a; 进入mysql数据库 u…

C++ AVLTree

目录 介绍 节点的定义 AVLTree结构 Insert 插入节点 调节平衡因子 void rotateR(node* parent) void rotateL(node* parent) void rotateRL(node* parent) void rotateLR(node* parent) void InOrder() int height() int size() bool isBalance() 介绍 如果是有序插…

python web自动化(Allure报告)

Allure详细安装请看之前的博客 1.Allure配置与⼊⻔ 运⾏⽤例&#xff0c;⽣成allure报告 pip install allure-pytest -i https://mirrors.aliyun.com/pypi/simple/ 运⾏⽤例&#xff0c;⽣成allure报告 # main.py import os import pytest if __name__ __m…

【排序算法】——归并排序(递归与非递归)含动图

制作不易&#xff0c;三连支持一下吧&#xff01;&#xff01;&#xff01; 文章目录 前言一.归并排序递归方法实现二.归并排序非递归方法实现 前言 这篇博客我们将介绍归并排序的原理和实现过程。 一、归并排序递归方法实现 基本思想&#xff1a; 归并排序&#xff08;MERGE-…

四元数学习总结(1)

导语&#xff1a;相比矩阵&#xff0c;用四元数处理3D旋转的优势是毋庸置疑的&#xff0c;但由于概念复杂&#xff0c;难于理解&#xff0c;一直令我摸不着头脑。最近学习更是发现在机器人、无人机、SLAM等先进领域&#xff0c;四元数被当成实数、整数这样的基础&#xff0c;所…

jmeter安装SSH插件

安装SSH插件 下载jar包&#xff1a; ApacheJMeter-ssh-1.2.0.jar jsch-0.1.55.jar jar包放在jmeter安装路径 lib下和lib/ext文件夹下&#xff1a; 重启jmeter即可&#xff1a;

BGP选路实验

BGP 选路实验 一、实验拓扑 二、实验要求及分析 实验要求&#xff1a; 1、使用preva1策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2、使用AS_Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3、配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4、…

软考-必须要背的内容

一、设计模式 1、创建型 抽象工厂&#xff1a;提供一个接口&#xff0c;创建一系列的相关相互依赖的对象&#xff0c;无需指定具体的类&#xff1b; eg&#xff1a;系统软件&#xff0c;支持多种数据库 生成器&#xff1a;将一个复杂类的表示与构造相分离&#xff0c;使得相…

PX4使用yolo仿真环境搭建

文章目录 前言一、修改机架sdf文件二、安装yolo三、运行 前言 ubuntu20.04 PX4 1.13.3 已配置好PX4 ROS gazebo环境 一、修改机架sdf文件 将双目相机加到仿真的iris机架上 修改下图文件 添加如下&#xff1a; <include><uri>model://stereo_camera</uri>…

D - AtCoder Wallpaper(abc)

思路&#xff1a;f(c, d) f(a, b) - f(a, d) - f(c, b) 代码&#xff1a; int f(int x, int y){if(y % 2 0){y y / 2;int ans y * (x / 4) * 8;x % 4;if(x 1){ans y * 3;}else if(x 2){ans y * 6;}else if(x 3){ans y * 7;}return ans;}else{y / 2;int ans y * (x…

从零开始搭建Springboot项目脚手架4:保存操作日志

目的&#xff1a;通过AOP切面&#xff0c;统一记录接口的访问日志 1、加maven依赖 2、 增加日志类RequestLog 3、 配置AOP切面&#xff0c;把请求前的request、返回的response一起记录 package com.template.common.config;import cn.hutool.core.util.ArrayUtil; import cn.hu…

Go语言的内存泄漏如何检测和避免?

文章目录 Go语言内存泄漏的检测与避免一、内存泄漏的检测1. 使用性能分析工具2. 使用内存泄漏检测工具3. 代码审查与测试 二、内存泄漏的避免1. 使用defer关键字2. 使用垃圾回收机制3. 避免循环引用4. 使用缓冲池 Go语言内存泄漏的检测与避免 在Go语言开发中&#xff0c;内存泄…

SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?

尼恩&#xff1a;LLM大模型学习圣经PDF的起源 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;经常性的指导小伙伴们改造简历。 经过尼恩的改造之后&#xff0c;很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会&#x…

力扣刷题---961. 在长度 2N 的数组中找出重复 N 次的元素【简单】

题目描述&#x1f357; 给你一个整数数组 nums &#xff0c;该数组具有以下属性&#xff1a; nums.length 2 * n. nums 包含 n 1 个 不同的 元素 nums 中恰有一个元素重复 n 次 找出并返回重复了 n 次的那个元素。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,3] 输…

智能猫眼锁核心解决方案以及芯片简介SSD222

书接上回&#xff0c;前篇文章我们诠释了IP 网络摄像系统的定义以及组成部分的功能&#xff0c;也大概的讲了一下所针对的市场以及举例介绍了一款相关芯片&#xff0c;详情可点击下面卡片浏览高集成IP摄像SOC处理方案简介https://blog.csdn.net/Chipsupply/article/details/139…

XILINX FPGA DDR 学习笔记(一)

DDR 内存的本质是数据的存储器&#xff0c;首先回到数据的存储上&#xff0c;数据在最底层的表现是地址。为了给每个数据进行存放并且在需要的时候读取这个数据&#xff0c;需要对数据在哪这个抽象的概念进行表述&#xff0c;我们科技树发展过程中把数据在哪用地址表示。一个数…

【C++】<知识点> 标准模板库STL(上)

文章目录 一、STL---string类 1. 常用构造函数 2. 常用操作 3. 字符串流处理 二、STL---容器 1. STL及基本概念 2. 顺序容器简介 3. 关联容器简介 4. 容器适配器简介 5. 常用成员函数 三、STL---迭代器 1. 普通迭代器 2. 双向、随机访问迭代器 3. 不同容器的迭代器…