DL作业11 LSTM

习题6-4  推导LSTM网络中参数的梯度, 并分析其避免梯度消失的效果

LSTM(长短期记忆网络)是一种特殊的循环神经网络(RNN),旨在解决普通 RNN 在处理长序列时遇到的梯度消失和梯度爆炸问题。它通过设计多个门控机制来实现更好地学习和记忆序列中的长期依赖关系。

按照上图LSTM循环单元的结构来进行前向传播的过程

对于每个时间步t,LSTM的输入包括:

当前时间步的输入x_t

上一时刻的隐藏状态h_{t-1}

上一时刻的记忆单元状态c_{t-1}

(一)LSTM的前向传播

1.LSTM的遗忘门(forget gate)

决定了上一时刻的记忆单元状态有多少比例要“遗忘”,如果遗忘门算出来的结果是0.8,是上一时刻的记忆乘以0.8,有80%的要记住,而不是80%要遗忘。

遗忘门的值:

f^{\left( t \right)}=\sigma \left( W_fh^{\left( t-1 \right)}+U_fx^{\left( t \right)}+b_f \right)

2.LSTM的输入门(input gate)

决定了当前时刻的输入有多少比例要“更新”记忆单元

输入门的值:

i^{\left( t \right)}=\sigma \left( W_ih^{\left( t-1 \right)}+U_ix^{\left( t \right)}+b_i \right)

3.LSTM的候选记忆单元(cell state )

生成当前时刻的新候选记忆单元

新的候选记忆单元:

\tilde{c}_t=\tan\text{h}\left( W_th^{\left( t-1 \right)}+U_tx^{\left( t \right)}+b_t \right)

4.更新记忆单元

记忆单元状态c_t是通过遗忘门、输入门和候选记忆单元来更新的:

c^{\left( t \right)}=c^{\left( t-1 \right)}\odot f^{\left( t \right)}+\tilde{c}^{\left( t \right)}\odot i^{\left( t \right)}

5.LSTM的输出门(output gate)

决定了记忆单元有多少信息可以影响到输出

输出门:

o_t=\sigma \left( W_oh^{\left( t-1 \right)}+U_ox^{\left( t \right)}+b_o \right)

6.计算隐藏状态

隐藏状态h_t 是通过输出门和当前时刻的记忆单元 c_t 来计算的:

h_t=o^{\left( t \right)}\odot \tan\text{h}\left( c^{\left( t \right)} \right)

7.计算输出

\hat{y}^{\left( t \right)}=\sigma \left( Vh^{\left( t \right)}+c \right)

8.总结前向传播过程

Ok,我们已经完成了前向传播的过程,计算顺序是计算遗忘门、输入门、候选记忆单元,然后根据前一个时间步的记忆单元、遗忘门、候选记忆单元、输入门更新记忆单元。计算输出门,计算隐层输出,计算预测输出。

(二) LSTM的反向梯度推导

LSTM 的反向传播主要依赖链式法则,并且要计算每个门控的梯度。由于 LSTM 结构复杂,反向传播过程的推导也会比普通 RNN 更加复杂。

对于每个时间步 t,我们需要通过链式法则计算损失函数对 LSTM 各个参数的梯度

首先定义两种隐藏状态的梯度:


\delta _{h}^{\left( t \right)}=\frac{\partial L}{\partial h^{\left( t \right)}}

\delta _{c}^{\left( t \right)}=\frac{\partial L}{\partial c^{\left( t \right)}}

为了方便推导,给出数据在LSTM中的前向流动:

下面是自己画的:

对于t=T,即时间序列截止的那个时间步,我们可以得到:

\delta _{h}^{\left( T \right)}=V^T\left( \hat{y}^{\left( T \right)}-y^{\left( T \right)} \right)

解释:

\delta _{c}^{\left( T \right)}=\left( \frac{\partial h^{\left( T \right)}}{\partial c^{\left( T \right)}} \right) ^T\frac{\partial L}{\partial h^{\left( T \right)}}

对于t<T,我们要利用\delta _{h}^{\left( t+1 \right)}\delta _{c}^{\left( t+1 \right)}递推得到\delta _{h}^{\left( t \right)}\delta _{c}^{\left( t \right)}

先来推导\delta _{h}^{\left( t \right)}的递推公式:
根据上图我们可以知道\delta _{h}^{\left( t \right)}的误差来源为3类:

根据链式法则和全微分方程,有:

上面这个递推公式需要解决三个问题:

\frac{\partial l\left( t \right)}{\partial h^{\left( t \right)}}\left( \frac{\partial c^{\left( t+1 \right)}}{\partial h^{\left( t \right)}} \right) ^T\left( \frac{\partial h^{\left( t+1 \right)}}{\partial o^{\left( t+1 \right)}}\frac{\partial o^{\left( t+1 \right)}}{\partial h^{\left( t \right)}} \right) ^T的求解

①对于\frac{\partial l\left( t \right)}{\partial h^{\left( t \right)}},我们在上面已经进行了推导,\frac{\partial l\left( t \right)}{\partial h^{\left( t \right)}}=V^T\left( \hat{y}^{\left( t \right)}-y^{\left( t \right)} \right)

②接下来求\left( \frac{\partial c^{\left( t+1 \right)}}{\partial h^{\left( t \right)}} \right) ^T

基于c^{\left( t \right)}=c^{\left( t-1 \right)}\odot f^{\left( t \right)}+\tilde{c}^{\left( t \right)}\odot i^{\left( t \right)}逐层展开,得到:

由于\tan\text{h'}\left( x \right) =\left( 1-\tan\text{h}\left( x \right) ^2 \right),所以:

整理得:

\frac{dc^{\left( t+1 \right)}}{dh^{\left( t \right)}}=\text{diag}\left( c^{\left( t \right)}\odot f^{\left( t+1 \right)}\odot \left( 1-f^{\left( t+1 \right)} \right) \right) W_f+\text{diag}\left( \tilde{c}^{\left( t+1 \right)}\odot i^{\left( t+1 \right)}\odot \left( 1-i^{\left( t+1 \right)} \right) \right) W_i+\text{diag}\left( i^{\left( t+1 \right)}\odot \left( 1-\tilde{c}^{\left( t+1 \right) ^2} \right) \right) W_t

③ 接下来求\left( \frac{\partial h^{\left( t+1 \right)}}{\partial o^{\left( t+1 \right)}}\frac{\partial o^{\left( t+1 \right)}}{\partial h^{\left( t \right)}} \right) ^T

所以:

于是我们现在得到了从\delta _{h}^{\left( t+1 \right)}\delta _{c}^{\left( t+1 \right)}推得\delta _{h}^{\left( t \right)}的递推公式

接下来我们利用\delta _{h}^{\left( t \right)}\delta _{c}^{\left( t+1 \right)}来推得\delta _{c}^{\left( t \right)}

现在,我们能计算\delta _{h}^{\left( t \right)}\delta _{c}^{\left( t \right)}了,有了它们,计算变量的梯度就比较容易了,这里只以计算Wf的梯度计算为例:

其他变量的梯度按照上述类似的方式可依次求得

(三)LSTM防止梯度消失

首先需要明确的是,RNN 中的梯度消失/梯度爆炸和普通的 MLP 或者深层 CNN 中梯度消失/梯度爆炸的含义不一样。MLP/CNN 中不同的层有不同的参数,各是各的梯度;而 RNN 中同样的权重在各个时间步共享,最终的梯度 g= 各个时间步的梯度g(t)之和。

因此,RNN 中总的梯度是不会消失的。即便梯度越传越弱,那也只是远距离的梯度消失,由于近距离的梯度不会消失,所有梯度之和便不会消失。RNN 所谓梯度消失的真正含义是,梯度被近距离梯度主导,导致模型难以学到远距离的依赖关系。

LSTM防止梯度消失归功于记忆单元。

LSTM 中梯度的传播有很多条路径,但c^{\left( t \right)}=c^{\left( t-1 \right)}\odot f^{\left( t \right)}+\tilde{c}^{\left( t \right)}\odot i^{\left( t \right)}这条路径上只有逐元素相乘和相加的操作,它可以直接将信息传递到很远的时间步,使得梯度可以直接流过时间步,无需经过多次非线性变换,梯度流最稳定;但是其他路径上梯度流与普通 RNN 类似,照样会发生相同的权重矩阵反复连乘。

由于总的远距离梯度 = 各条路径的远距离梯度之和,即便其他远距离路径梯度消失了,只要保证有一条远距离路径(就是上面说的那条高速公路)梯度不消失,总的远距离梯度就不会消失(正常梯度 + 消失梯度 = 正常梯度)。因此 LSTM 通过改善一条路径上的梯度问题拯救了总体的远距离梯度。

习题6-3P 编程实现下图LSTM运行过程

使用Numpy实现LSTM算子

代码:
import numpy as np#定义激活函数,计算输入输出遗忘门都需要激活
def sigmoid(x):return 1/(1+np.exp(-x))#定义4个权重
input_weight=np.array([1,0,0,0])
inputgate_weight=np.array([0,100,0,-10])
forgetgate_weight=np.array([0,100,0,10])
outputgate_weight=np.array([0,0,100,-10])#定义输入sequence,大小为batch_size*seq_len*hidden_size
#本例中,batch_size=1,seq_len=9,hidden_size=3
input=np.array([[1,0,0,1],[3,1,0,1],[2,0,0,1],[4,1,0,1],[2,0,0,1],[1,0,1,1],[3,-1,0,1],[6,1,0,1],[1,0,1,1]])y=[]   #输出
c_t=0  #记忆单元for x in input:cc_t=np.matmul(input_weight,x) #候选状态i_t=np.round(sigmoid(np.matmul(inputgate_weight,x)))  #输入门,激活函数是sigmoidafter_inputgate=cc_t*i_t       #候选状态经过输入门f_t=np.round(sigmoid(np.matmul(forgetgate_weight,x))) #遗忘门after_forgetgate=f_t*c_t      #内部状态经过遗忘门c_t=np.add(after_inputgate,after_forgetgate) #新的内部状态o_t=np.round(sigmoid(np.matmul(outputgate_weight,x))) #输出门after_outputgate=o_t*c_t     #新的内部状态经过输出门y.append(after_outputgate)   #输出print('输出:',y)
结果:

使用nn.LSTMCell实现

(一)调用LSTMCell的简单示例:

import torch
import torch.nn as nn# LSTMCell 参数
input_size = 10  # 输入特征维度
hidden_size = 20  # 隐层状态维度
batch_size = 5  # 批大小# 创建 LSTMCell 模型
lstm_cell = nn.LSTMCell(input_size, hidden_size)# 创建输入数据
x = torch.randn(batch_size, input_size)  # (batch_size, input_size)# 初始隐状态和细胞状态
hx = torch.zeros(batch_size, hidden_size)  # 隐状态 (batch_size, hidden_size)
cx = torch.zeros(batch_size, hidden_size)  # 细胞状态 (batch_size, hidden_size)# 调用 LSTMCell
h_t, c_t = lstm_cell(x, (hx, cx))print("h_t.shape:", h_t.shape)  # (batch_size, hidden_size)
print("c_t.shape:", c_t.shape)  # (batch_size, hidden_size)

函数创建时需要的参数:

nn.LSTMCell 是一个单步的 LSTM 计算单元,用于逐步处理每个时间步。创建时需要以下参数:

  • input_size (int): 输入特征的维度,每个时间步的输入的特征数量。
  • hidden_size (int): 隐藏状态的维度。

 函数调用时的传入参数:

 接受两个主要的输入:当前时间步的输入和前一个时间步的隐藏状态。

  • input (Tensor): 当前时间步的输入数据,形状为 (batch_size, input_size)
  • (hx, cx) (tuple of Tensor): 上一时间步的隐状态和细胞状态,hxcx 的形状为 (batch_size, hidden_size)

函数的返回值:

LSTMCell 返回一个元组 (h_t, c_t)

  • h_t: 当前时间步的隐状态,形状为 (batch_size, hidden_size)
  • c_t: 当前时间步的细胞状态,形状为 (batch_size, hidden_size)

(二)调用nn.LSTMCell函数解决本例

经过上面的例子总结,我知道了创建函数时需要传入的参数是input_size和hidden_size,所以在函数创建时要初始化input_size和hidden_size,在这个例子中,input_size是4,hidden_size为1,因为最后输出就是1个数。

函数调用时需要传入的参数是输入input、前一时刻的隐藏状态h_t、前一时刻的细胞状态c_t。所以在函数创建之前,我们需要按照标准的大小准备这些变量。首先按照例子准备好输入,大小是batch_size*seq_len*input_size。初始的隐层状态初始化为全0,大小是batch_size*hidden_size,初始的细胞状态初始化为全0,大小是batch_size*hidden_size.

因为LSTMCell函数只是返回一个时间步的输出,所以我们要遍历每个时间步,这样把输入的前两个维度进行交换顺序,变为seq_len*batch_size*input_size。

这样通过for循环,每次获得每个时间步的输入x,把x和初始的隐状态和细胞状态(h_t,c_t)传入模型,得到返回值当前时刻的隐状态和细胞状态h_t,c_t,那当前时刻获得的h_t,c_t又作为下一时刻的上一个时间步的隐层状态和细胞状态。每次把h_t放入列表output中,最后可视化输出。

代码:
import numpy as np
import torch
import torch.nn as nn#实例化
input_size=4
hidden_size=1
# 创建模型
cell=nn.LSTMCell(input_size=input_size,hidden_size=hidden_size)#修改模型参数 weight_ih.shape=(4*hidden_size, input_size),weight_hh.shape=(4*hidden_size, hidden_size),
#weight_ih、weight_hh分别为输入x、隐层h分别与输入门、遗忘门、候选、输出门的权重
cell.weight_ih.data=torch.tensor([[0,100,0,-10],[0,100,0,10],[1,0,0,0],[0,0,100,-10]],dtype=torch.float32)
cell.weight_hh.data=torch.zeros(4,1)
print('cell.weight_ih.shape:',cell.weight_ih.shape)
print('cell.weight_hh.shape',cell.weight_hh.shape)
#初始化h_0,c_0
h_t=torch.zeros(1,1)
c_t=torch.zeros(1,1)
#模型输入input_0.shape=(batch,seq_len,input_size)
input_0=torch.tensor([[[1,0,0,1],[3,1,0,1],[2,0,0,1],[4,1,0,1],[2,0,0,1],[1,0,1,1],[3,-1,0,1],[6,1,0,1],[1,0,1,1]]],dtype=torch.float32)
#交换前两维顺序,方便遍历input.shape=(seq_len,batch,input_size)
input=torch.transpose(input_0,1,0)
print('input.shape:',input.shape)
output=[]
#调用
for x in input:h_t,c_t=cell(x,(h_t,c_t))output.append(np.around(h_t.item(), decimals=3))#保留3位小数
print('output:',output)
结果:

使用nn.LSTM实现

(一)调用LSTM的简单示例:

import torch
import torch.nn as nn# LSTM 参数
input_size = 10  # 输入特征维度
hidden_size = 20  # 隐层状态维度
num_layers = 2  # LSTM 层数
batch_size = 5  # 批大小
seq_len = 7  # 序列长度# 创建 LSTM 模型
lstm = nn.LSTM(input_size, hidden_size, num_layers)# 创建输入数据
x = torch.randn(seq_len, batch_size, input_size)  # (seq_len, batch_size, input_size)# 初始隐状态和细胞状态
h0 = torch.zeros(num_layers, batch_size, hidden_size)
c0 = torch.zeros(num_layers, batch_size, hidden_size)# 调用 LSTM
output, (hn, cn) = lstm(x, (h0, c0))print("output.shape:", output.shape)  # (seq_len, batch_size, hidden_size)
print("hn.shape:", hn.shape)  # (num_layers, batch_size, hidden_size)
print("cn.shape:", cn.shape)  # (num_layers, batch_size, hidden_size)

函数创建时需要传入的参数:

nn.LSTM 是用于处理序列数据的多时间步 LSTM 层,通常在创建时需要以下参数:

  • input_size (int): 输入特征的维度。也就是每个时间步输入的特征数量。
  • hidden_size (int): 隐藏状态的维度。LSTM 的输出和隐状态的大小。
  • num_layers (int, optional): LSTM 层数,默认为 1。设为多个层可以堆叠多个 LSTM 层。
  • bias (bool, optional): 是否使用偏置项,默认为 True
  • batch_first (bool, optional): 如果为 True,则输入和输出的张量形状为 (batch, seq_len, input_size),默认为 False,则为 (seq_len, batch, input_size)
  • dropout (float, optional): 如果 num_layers > 1,则在各个 LSTM 层之间应用 dropout,默认为 0
  • bidirectional (bool, optional): 是否使用双向 LSTM,默认为 False
  • proj_size (int, optional): 投影层的维度,默认为 None

调用函数时的传入参数:

  • input (Tensor): 输入数据,形状为 (seq_len, batch_size, input_size)(如果 batch_first=False)或 (batch_size, seq_len, input_size)(如果 batch_first=True)。
  • h0 (Tensor, optional): 初始隐状态,形状为 (num_layers * num_directions, batch_size, hidden_size)。如果未提供,默认为零。
  • c0 (Tensor, optional): 初始细胞状态,形状为 (num_layers * num_directions, batch_size, hidden_size)。如果未提供,默认为零。

返回值:

LSTM 返回一个元组 (output, (h_n, c_n))

  • output: 每个时间步的隐状态,形状为 (seq_len, batch_size, hidden_size)(或根据 batch_first 的设置调整形状)。
  • (h_n, c_n): 最后一个时间步的隐状态和细胞状态,形状为 (num_layers * num_directions, batch_size, hidden_size)

(二)调用LSTM函数解决本例

通过上面的分析,我们知道,再利用torch.nn.LSTM创建函数的时候,需要传入的参数是input_size,hidden_size和num_layer,所以在函数创建是需要定义这些变量。

函数调用时需要传入的参数是input,h_t、c_t,其中input的大小是seq_len*batch_size*hidden_size,h_t的大小是num_layers*batch_size*hidden_size,c_t的大小是num_layers*batch_size*hidden_size,所以我们需要按照题目给的例子和指定大小取初始化这些变量。

将input,(h_t,c_t)传入模型,得到返回值output,(h_t,c_t),其中output是每个时间步的输出,h_t是最后一个时间步的隐层输出,c_t是最后一个时间步的细胞状态。最后将output输出观察输出。

代码:
#LSTM
import torch
from torch import nn
#定义输出输出维度
input_size=4
hidden_size=1
#定义LSTM模型
lstm=nn.LSTM(input_size=input_size,hidden_size=hidden_size,batch_first=True)#修改模型参数
lstm.weight_ih_l0.data=torch.tensor([[0,100,0,-10],[0,100,0,10],[1,0,0,0],[0,0,100,-10]],dtype=torch.float32)
lstm.weight_hh_l0.data=torch.zeros(4,1)#定义输入sequence,大小为batch_size*seq_len*input_size
#本例中,batch_size=1,seq_len=9,input_size=3
input=torch.tensor([[[1,0,0,1],[3,1,0,1],[2,0,0,1],[4,1,0,1],[2,0,0,1],[1,0,1,1],[3,-1,0,1],[6,1,0,1],[1,0,1,1]]],dtype=torch.float32)
#初始化h_0,c_0,大小为num_layer*batch_size*hidden_size
h_t=torch.zeros(1,1,1)
c_t=torch.zeros(1,1,1)
#调用函数
output,(h_t,c_t)=lstm(input,(h_t,c_t)) #output是每个时间步的输出,ht是最后一个时间步的隐状态,ct是最有一个时间步的细胞状态rounded_output = torch.round(output * 1000) / 1000  # 保留3位小数
print(rounded_output)
结果:

总结和心得体会

调用numpy实现得到的结果和题目给的答案一样,但是直接调用nn.LSTM和nn.LSTMCell得到的结果和答案不一样,是因为在这个函数里面,候选记忆单元的激活函数和记忆单元经过输出门之前的激活函数都是tanh,但是在例子中为了简单,就直接使用了线性函数作为激活函数。

本次作业,我分析了LSTM的前向传播过程,分析前向传播中的公式,更加理解了前向传播的过程,此外我也进行了反向梯度推导,然后分析了LSTM防止梯度消失的原因。我也分析了直接调用nn.LSTM和nn.LSTMCell在函数创建时的传入参数,函数调用时的传入参数和返回值是什么,学会了在实际中怎么使用这两个函数。

参考:

《神经网络的梯度推导与代码验证》之LSTM的前向传播和反向梯度推导 - SumwaiLiu - 博客园

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

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

相关文章

WWW23-多行为级联|级联图卷积网络的多行为推荐

论文&#xff1a;https://arxiv.org/abs/2303.15720 代码&#xff1a;https://github.com/SS-00-SS/MBCGCN 这篇论文MB-CGCN和上一篇CRGCN是同一个团队的&#xff0c;都是级联的方式。一个用了残差&#xff0c;一个用了特征转换&#xff0c;文章最后有discussion讨论了两者的不…

JAVA开发入门学习七- 数组

数组的概念 概念 数组&#xff1a; 是多个相同类型数据按照一定排列的集合&#xff0c;并使用一个名字命名&#xff0c;并通过编号的方式对这些数据进行统一管理 数组中的概念 数组名&#xff1a; 数组的名称&#xff0c;命名 下标&#xff1a; 从0开始 元素&#xff1a;…

【编辑器扩展】打开持久化路径/缓存路径/DataPath/StreamingAssetsPath文件夹

代码 [MenuItem("Assets/Open Explorer/PersistentDataPath")]public static void OpenPersistentDataPath(){Application.OpenURL(Application.persistentDataPath);}[MenuItem("Assets/Open Explorer/DataPath")]public static void OpenDataPath(){Appl…

链路聚合与GVRP的混合构建(eNSP)

目录 拓扑图&#xff1a; 前置操作&#xff1a; GVRP全局开启&#xff1a; 查询&#xff1a; 实验背景&#xff1a;前面依次搭建了交换机的链路聚合实验手册以及动态vlan GVRP&#xff0c;为了模拟真实环境&#xff0c;本次实验将两者结合。 拓扑图&#xff1a; 前置操作&…

由于这些关键原因,我总是手边有一台虚拟机

概括 虚拟机提供了一个安全的环境来测试有风险的设置或软件,而不会影响您的主系统。设置和保存虚拟机非常简单,无需更改主要设备即可方便地访问多个操作系统。运行虚拟机可能会占用大量资源,但现代 PC 可以很好地处理它,为实验和工作流程优化提供无限的可能性。如果您喜欢使…

华为ensp--BGP路由反射器

学习新思想、争做新青年&#xff0c;今天学习的是BGP路由反射器。 实验目的 理解BGP路由反射器的应用场景 理解BGP路由反射器的工作原理 掌握BGP路由反射器的基本配置方法 实验内容 本实验网络包含了两个AS&#xff0c;两个Cluster。R1、R2、R3属于Cluster 1&#xff0c…

使用idea创建JDK8的SpringBoot项目

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 使用idea创建JDK8的SpringBoot项目 前言我们经常在创建新的springboot项目&#xff0c;默认使用的是spring.io进行创建&#xff0c;但是它总是只会提供高版本的创建方式&…

初学stm32 --- 定时器中断

目录 时钟选择&#xff1a; 内部时钟选择​编辑 时钟计算方法&#xff1a; 计数器模式 向下计数模式&#xff08;时钟分频因子1&#xff0c;ARR36&#xff09; 向上计数模式&#xff08;时钟分频因子1&#xff0c;ARR36&#xff09; 中央对齐计数模式&#xff08;时钟分频因…

windows下安装配置anaconda及常用的conda命令

Anaconda极大的简化了Python环境和库的管理&#xff0c;其最大的作用就是可以创建、管理多个不同python版本的虚拟环境&#xff0c;起到不同环境相互隔离、互不干扰、避免环境冲突的目的。如果使用本地Python安装多个包&#xff0c;经常会遇到包冲突&#xff0c;导致整个python…

安装CPU版的torch(清华源)

1、安装指令&#xff1a; pip3 install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple2、验证torch是否安装成功 // 使用python验证 import torch print(torch.__version__)能正常打印版本即表示安装成功&#xff0c;如下图

ASP.NET Core Web API 控制器

文章目录 一、基类&#xff1a;ControllerBase二、API 控制器类属性三、使用 Get() 方法提供天气预报结果 在深入探讨如何编写自己的 PizzaController 类之前&#xff0c;让我们先看一下 WeatherController 示例中的代码&#xff0c;了解它的工作原理。 在本单元中&#xff0c…

【蓝桥杯——物联网设计与开发】基础模块8 - RTC

目录 一、RTC &#xff08;1&#xff09;资源介绍 &#x1f505;简介 &#x1f505;时钟与分频&#xff08;十分重要‼️&#xff09; &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#xff08;4&#xff09;实验现象 二、RTC接口…

k8s dashboard可视化操作界面的安装

一、官方安装方法 根据官网的安装配置可以选择如下安装&#xff1a; kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml 二、添加阿里云加速进行安装 #修改recommended.yaml拉取镜像的链接 vim recommended.yam…

【目标跟踪综述及关键技术】

1.多目标跟踪任务介绍 定义 多目标跟踪旨在将视频序列中感兴趣的目标检测出来&#xff0c;并赋予每个目标单独的编号&#xff0c;在整个序列中形成目标的轨迹。 分类 online&#xff1a;算法在推理目标身份过程中&#xff0c;只能看见当前帧以及之前的帧&#xff08;关联&a…

webrtc音频模块(三) windows Core Audio API及声音的播放

在前面介绍了ADM(Audio Device Module)&#xff0c;它用于抽象音频设备管理和音频数据采集/播放接口。windows的实现是AudioDeviceWinowCode&#xff0c;它封装了Core Audio APIs实现了对音频设备的操作。 Core Audio APIs windows提供了多种音频操作API&#xff0c;比如最常…

在linux系统的docker中安装GitLab

一、安装GitLab&#xff1a; 在安装了docker之后就是下载安装GitLab了&#xff0c;在linux系统中输入命令&#xff1a;docker search gitlab就可以看到很多项目&#xff0c;一般安装第一个&#xff0c;它是英文版的&#xff0c;如果英文不好可以安装twang2218/gitlab-ce-zh。 …

uniapp跨平台开发---webview调用app方法

1.app端实现 注意:为了实现实时通信,app端页面是.nvue 代码实现 <template><view class"content"><view class"web-view"><web-view class"web-view" :src"url" ref"webview" onPostMessage"o…

LeetCode 343.整数拆分

1.题目要求: 2.题目代码: class Solution { public:int integerBreak(int n) {//先确定dp数组vector<int> dp;//1.确定dp数组的含义//2.确定dp的递推公式//3.初始化dp数组//4.遍历顺序dp.resize(n 1);dp[0] 0;dp[1] 0;dp[2] 1;for(int i 3;i < n;i){for(int j …

【Linux探索学习】第二十三弹——理解文件系统:认识硬件、探索文件在硬件上的存储问题

Linux学习笔记&#xff1a;https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 我们前面讲过了文件的组成是由文件内容和文件属性两者组成的&#xff0c;但是我们前面接触的文件都是系统中的文件&#xff0c;都是已经在进…

Flask中@app.route()的methods参数详解

诸神缄默不语-个人CSDN博文目录 在 Flask 中&#xff0c;app.route 是用于定义路由的核心装饰器&#xff0c;开发者可以通过它为应用指定 URL 映射及相应的处理函数。在处理 HTTP 请求时&#xff0c;不同的业务场景需要支持不同的 HTTP 方法&#xff0c;而 app.route 的 metho…