深度强化学习Reinforcement Learning|PG|Actor-Critic|A3C|DDPG

news2024/12/24 22:16:24

目录

一、PG(Policy Gradient)策略梯度算法(on-policy)

1、策略梯度公式推导

2、代码讲解/伪代码

3、改进

3.1Trick Baseline

3.2 Suitable Credit

二、Actor-Critic算法

三、A3C算法

四、DDPG算法


前言

    我们都知道强化学习环境env的不确定性是比较突出的一个特点,那么有很多情况下时需要使用强化学习设计仿真实验的,经常会设置一些随即参数,比如随机正态分布或者一个随机的常量,这种我们就正常使用科学计算库numpy或者深度学习框架比如Tensorflow自带的随机就可以,但这种经常会遇到一个问题,包括我本人在最开始设计深度强化学习相关的仿真实验之后,假设每次都训练1000次,但是我运行多次之后发现结果参数(比如reward、loss、Q-value、V-value等)不一样,有几次巧合会是一致的,经查阅资料得出:

在设置参数时正常使用函数功能,只不过需要在导入库文件之后,设置随机种子seed(如下所示),这样就会使得实验结果可复现,也就是说每次运行相同的代码时,只要在相同实验中的随机种子保持一致,那么每次训练模型得到的随即结果将会是一致的,使得结果相对可靠些。并且即使是在不同的实验中,也建议把随机种子设置为一致的,这样可以确保对比的合理性。

# 用到哪个框架使用哪个框架设置随机种子
tf.random.set_seed(1)
np.random.seed(1)

简单介绍一下on-policy和off-policy的区别:对于on-policy算法,边交互边学习,即学习和评估是基于同一个策略进行的,智能体与环境交互的过程中,使用当前的策略生成数据,并利用这些数据来更新策略;而对于off-policy算法,则学习和评估可以基于不同的策略进行,智能体可以利用一个策略下探索环境,而在另一个策略下评估和学习。

一、PG(Policy Gradient)策略梯度算法(on-policy)

    下面讲解一下策略梯度算法,PG是一种基于策略(policy-based)的算法它与传统的基于值的(value-based)算法最不一样的点就在于,PG算法依靠直接训练神经网络NN(网络参数即为策略)输入state输出action,过程中不需要计算Q值。而传统的基于值的算法需要使用Q-Table或Q-Network然后通过评判Critic值函数的方式反过来选择action。

    若把DQN看成是TD+神经网络的算法,那么PG就是一个MC+神经网络的算法。利用reward奖励直接对选择动作的可能性干预,好的action会被增加下一次交互被选择的概率,而坏的action则会被减小下一次交互被选择的概率。

1、策略梯度公式推导

图1

    首先,PG直接使用Actor与环境env交互,每个episode回合最后可以加得一个total reward, 

,由于PG目标就是最大化一整个回合之后的total reward。注意,由于NN中的参数一开始可能是随机的,但环境env肯定具有随机性,即使使用同一个Actor,在遇到同一环境中的相同场景时,也会有做出不同决策(action)的概率,故每次的total reward也大概率会不同。那如果这样的话,对于R_{\theta}来说,它就是一个随机变量,我们的目标不能仅靠最大化某一次的R_{\theta}而是要最大化它的期望值,这样就可以从算法的角度(不包含实际应用问题)来衡量Actor决策的好坏

    接着 ,讨论一下该如何计算? 

假设一个回合的轨迹如下,\tau ={s_{1},a_{1},r_{1},s_{2},a_{2},r_{2},...,s_{T},a_{T},r_{T}},故在这一回合的累计回报可以表示为R(\tau)=\sum_{t=1}^{T}r_{t},然后如果使用Actor与环境交互,每个回合都有被采样的概率,用p(\tau|\theta)表示在网络参数也就是策略下,轨迹\tau的概率,这样的话,与环境交互N次的期望就可以表示为:

     再然后,我们就可以进一步表示出最后的优化目标:

maximize \bar{R_{\theta}}=E[R_{\theta}]=\frac{1}{N}\sum_{n=1}^{N}R(\tau^{n})

希望找到最优的策略\theta^{*}使得\bar{R_{\theta}}最大,故这里可以通过\theta^{*}=argmax_{\theta}\bar{R_{\theta}}表示。

再然后,很明显,\bar{R_{\theta}}作为最后的目标函数,并由于PG是策略梯度算法,则最后一定会用到梯度下降或梯度上升算法,由于是maximize,所以,对\bar{R_{\theta}}求导,做梯度上升更新网络参数即可最大化\bar{R_{\theta}},详细过程如下,

然后,将

 再然后,

 综上,

这样理解起来就容易了,整体梳理一下,如下

    我们需要使用梯度上升法优化寻找函数的最大值,在本情况下,需要最大化期望回报\bar{R_{\theta}}然后分别需要先计算目标函数\bar{R_{\theta}}的梯度以及根据梯度策略更新参数\theta如下公式,\theta\leftarrow \theta+\alpha \bigtriangledown \bar{R_{\theta}}.由于上述求解出的\bigtriangledown \bar{R_{\theta}}可以看出,最后希望求得最大的期望回报,那么由于其是导函数只需要让其大于0使得\bar{R_{\theta}}单调递增即可。当R(\tau)大于0时,只需要让log p的导函数大于0即可,由下述推导1(将推导1中的x替换为\theta)可得p(a_{t}^{n}|s_{t}^{n},\theta)越大越好;反之,当R(\tau)小于0时,则是希望p(a_{t}^{n}|s_{t}^{n},\theta)越小越好,也是得出的最后\bigtriangledown \bar{R_{\theta}}大于0。

推导1:\frac{d log p(x)}{dx}=\frac{1}{p(x)}\cdot \frac{dp(x)}{dx},对于概率p(x)肯定是恒大于0的,因此\frac{1}{p(x)}也恒大于0,

那只有当p(x)随着x的增加而增加时,\frac{dp(x)}{dx}也就大于0,这样的话,\frac{d log p(x)}{dx}也就大于0了,进而推导出log p(x)是随着x的增加而增加的;相反,当p(x)随着x的增加而减小时,\frac{dp(x)}{dx}也就小于0,进而可以推导出\frac{d log p(x)}{dx}也就小于0了,也就是log p(x)是随着x的增加而减小的。

2、代码讲解/伪代码

初始化策略参数 θ
设置学习率 α

对于每个训练回合 do
    初始化状态 s
    初始化回合奖励 total_reward = 0
    初始化存储的状态、动作和奖励列表

    直到回合结束 do
        根据当前策略 π(a|s; θ) 选择动作 a
        执行动作 a,观察下一个状态 s' 和奖励 r
        存储状态 s、动作 a 和奖励 r
        s = s'
        total_reward += r

    计算每个时间步的累计奖励 Gt
    对于每个时间步 t do
        计算优势函数 A(s_t, a_t) = Gt - V(s_t; θ)  # 可以用价值函数估计
        计算策略的梯度 ∇θ log(π(a_t|s_t; θ)) * A(s_t, a_t)
        更新策略参数 θ = θ + α * ∇θ log(π(a_t|s_t; θ)) * A(s_t, a_t)

    如果需要更新价值函数 V(s; θ):
        使用 TD 学习或蒙特卡洛方法更新 V(s; θ)

返回策略参数 θ
基于策略的PG算法

初始化学习率α,折现率超参数
初始化网络参数θ
输入:状态s
输出:每个动作对应的概率值

1:构建神经网络 fc1 = tanh(s * weight + b);all_act = (fc1 * weight + b).self.all_act_prob = softmax(all_act).
2:计算损失 neg_log_prob = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=all_act, labels=self.tf_acts);loss = tf.reduce_mean(neg_log_prob * self.tf_vt)
这里的all_act是一个Tensor,代表着每个动作相对的得分,这里labels=self.tf_acts则表示实际选择到的动作。
3:衰减回合计算的reward:(用作动作值)
# 主要函数如下
def _discount_and_norm_rewards(self):
    discounted_ep_rs = np.zeros_like(self.ep_rs)  # 创建一个与 ep_rs 同样大小的数组,用于存储折扣回报
    running_add = 0  # 初始化一个变量,用于累加折扣奖励

    # 回溯,倒序,self.ep_rs为一个列表
    for t in reversed(range(0, len(self.ep_rs))):
        # G值
        running_add = running_add * self.gamma + self.ep_rs[t]  # 计算当前时间步的折扣回报
        discounted_ep_rs[t] = running_add  # 将计算得到的 G 值存入 discounted_ep_rs

    # 归一化一回合的 rewards
    discounted_ep_rs -= np.mean(discounted_ep_rs)  # 减去均值
    # 除以标准差,进行标准化
    discounted_ep_rs /= np.std(discounted_ep_rs) if np.std(discounted_ep_rs) > 0 else 1  # 标准化

    return discounted_ep_rs  # 返回计算得到的折扣回报

3、改进

3.1Trick Baseline基准线
图2

如图2所示,加入基线就是在原来的目标累计回报求导公式中的某一个轨迹\tau^{n}基础上减b,为什么要减去b呢?下面来解释一下原因,以及加入基线b的好处, 

     真实环境中的奖励reward并不是分布在0周围,很多游戏的奖励全是正数,由第二部分代码讲解中就可以知道,policy-based的算法直接在神经网络输出对应动作的概率值,对应的R(\tau^{n})(每个轨迹的total reward)若大于0,则在该回合的轨迹中被采样到的动作action所对应的概率就会被增加(也就是说鼓励该动作的发生),相反,就会被减小。一般在训练agent时,会跑很多个回合,若未加入基线b的话,有这样一种情况,当所有的R(\tau^{n})(1=<n<=N)都大于0时,那么被采样到的轨迹中的所包含的动作对应的概率也会变大只不过变大的程度不一样,有大有小,那这样的话,轨迹中未被采样到的动作对应的概率就一定会变小,很明显,这不是我们所希望的看到的,理想情况下未被采样的动作我们希望的是让其对应的概率并不发生改变。这种方式会严重导致策略过于集中在某些动作上,忽略了未被采样到的动作。 

    加入基线b之后,b其实就相当于平均值,整体对奖励信号进行平均归一化操作。希望R(\tau^{n})能分布在0周围,它代表了R(\tau^{n})的平均水平,会导致有些轨迹对用的total reward是大于0的,代表此轨迹中动作的表现要高于平均水平;有些轨迹对应的totla reward是小于0的,代表此轨迹中的动作的表现差于平均水平,让其有正有负。这样的话,当total reward大于0时,会把轨迹中包含的动作的概率增大,当total reward小于0时,就会把轨迹中所包含的动作对应的概率值减小,两种情况的未被当前轨迹采样的动作所对应的概率值都要保持不变(理想状态下),但实际上,基线的引入可以帮助在多个回合中未被采样的动作的概率不会因为某些轨迹的总回报而发生剧烈的变化(多个小变化)。通过这种方式,基线帮助我们在更新策略时保持相对的平衡,避免了过度集中在某些动作上。

图3
3.2 因果性

      在之前的所有算法中,对于时间戳为t的动作a,它对\tau_{1:t-1}并没有影响,而只是对后续的\tau_{t:T}产生影响,因此只考虑从时间戳为t开始的累计回报R(\tau_{t:T}),公式如下所示。轨迹只要是在同一个轨迹中的所有的state->action都会有相同的reward权重,显然有些情况是不那么公平的,因为明显是在某些回合中有些action非常好,但在有些回合中的action并不好,综上,也就是说整个回合的整体回报好并不能代表每个动作都是好的,相反,整个回合的整体回报不好,并不能代表每个动作action都不好,因此,我们希望给不同的action上乘以不同的权重weight,如图4所示,

图4

左右两侧才有控制变量法,保证三个状态都分别相同,然后让对应的动作和对应的reward不同,可以看出,让一个轨迹的整体回报产生差异的主要在(s_{a},a_{1})(s_{a},a_{2}),相同的状态,但是采取的动作不一样,产生的即使奖励reward不同,进而导致不同的两条轨迹之间的整体回报不同。

图5

 再然后,其实Q函数Q(s_{t},a_{t})就是代表着从状态st开始执行动作at后的策略的回报估计值可以将R(\tau^{n})替换为Q(s_{t},a_{t}),则有

\bigtriangledown R_{\theta}=\frac{1}{N}\sum_{n=1}^{N}\sum_{t=1}^{T_{n}}(Q(s_{t},a_{t})-b)\bigtriangledown logp_{\theta}(a_{t}^{n}|s_{t}^{n}).

Q(s_{t},a_{t})-b称为优势值函数,它代表了当前动作序列的回报相比于平均回报的优势程度。为什么可以这样看呢?主要就是因为由于Q(st,at)具体指的是在状态st条件下,选取动作at对应的状态动作值,这个动作at是唯一的,而b呢,可以使用V(st)来估计,V即是指状态值,代表的是在状态st条件下,所有的动作对应的累计回报的期望值,相当于一个平均值,因此使用Q-V可以评判出该动作相比于平均回报的优势程度,若结果大于0,则说明选择该动作是积极的positive,但若结果小于0,则说明该动作是消极的negative,结果是一个标量(scalar)。

如图5所示,将每一个轨迹的总回报换为从时间步t开始的累计加权奖励。PG用的就是MC的G值,也就是PG是用MC的G值来更新网络,也就是说,PG先会让智能体从某条轨迹一直走到最后,然后通过回溯计算G值。

讨论:添加基线b之后会不会改变\bigtriangledown \bar{R_{\theta}}

由于\bigtriangledown \bar{R_{\theta}}分别对不同轨迹的回报求和取平均(期望),所以这一时刻我们只需要判断E[\bigtriangledown logp_{\theta}(a_{t}^{n}|s_{t}^{n})\cdot b]=E_{\tau\sim p_{\theta}(\tau)}[\bigtriangledown logp_{\theta}(\tau)\cdot b]是否等于0即可,下面,将其展开得:

E_{\tau\sim p_{\theta}(\tau)}[\bigtriangledown log\pi_{\theta}(\tau)\cdot b]=\int \pi_{\theta}(\tau)\bigtriangledown _{\theta}log\pi_{\theta}(\tau)\cdot bd_{\tau}

 \pi_{\theta}(\tau)\bigtriangledown _{\theta}log\pi_{\theta}(\tau)=\bigtriangledown _{\theta}\pi_{\theta}(\tau)(代入).

可得:

 E_{\tau\sim p_{\theta}(\tau)}[\bigtriangledown log\pi_{\theta}(\tau)\cdot b]=\int\bigtriangledown_{\theta}\pi_{\theta}(\tau)bd_{\tau}=b\bigtriangledown_{\theta}\int\pi_{\theta}(\tau)d_{\tau}.(\int\pi_{\theta}(\tau)d_{\tau}=1)\Rightarrow b\bigtriangledown_{\theta}1=0.

这样就是加入基线b之后,并不会改变 \bigtriangledown \bar{R_{\theta}},但是却会是减小方差(减小离散程度)。

二、Actor-Critic算法(即可以离散动作空间,又可以连续动作空间)

    Actor-Critic其实就是用两个网络,两个网络均输入状态S,但一个网络输出策略\pi,即负责选择动作,这个网络称为Actor表演者;另一个网络负责评分,计算每个动作对应的分数,这个网络被称为Critic评判者。如图6所示,主要的过程就是Actor网络运用策略\pi选择动作action,然后Critic网络对其打分,评估相应的值函数,Actor网络通过Critic评判的值函数进行相应的学习,若Critic评判的值函数较好,则Actor会对轨迹中的动作的输出概率相应的增大;相反,若Critic评判的值函数较差,则Actor会把相应轨迹中的动作的输出概率减小。综上,就是说Actor就是用于优化策略的,使得动作越来越好,而Critic适用于评估值函数的,使得对其的估计越来越准确。

图6

 算法

伪代码 
分别初始化Actor网络和Critic网络的学习率
下面主要分为两个网络讲解Actor-Critic架构
一、Actor表演者网络中,Actor网络主要负责产生策略pi
1、在网络中输入状态s,经过可能几层神经网络的正向传播,最后一层加上蒸馏函数softmax,直接将各个动作对应的动作值转化为各个动作所占的百分比。
2、然后Actor网络采用从Critic网络传来的TD-error以及对动作的对应的概率求log计算期望V值(公式为logP * (r + gamma * vt+1 - vt)). 
3、 由于目标是要最大化最后的v的期望值,故应采用梯度上升算法,具体操作为 self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v).可以这样做的原因就是minimize是采用梯度下降算法最小化的过程,故若想要取最大值,将其参数加一个负号即可更改为梯度上升法.

二、Critic网络中,Critic网络主要适用于估计V值以及计算TD-error的。 -> 
1、在网络中输入状态s,神经网络正向传播计算得到V值 
2、然后根据下一时刻状态s_将其传入到神经网络中得到计算结果v_,然后目标为r + gamma * v_,估计值为v,故可以通过时序差分估计出TD-eror = r + gamma * v_ - v. -> 
3、然后,将损失函数设定为均方差损失函数简称为MSE,这块包括反向传播(链式求导),采用优化器比如Adam通过梯度下降法最小化损失函数,然后将TD-error传到Actor网络中即可。最后这一步的过程都体现在self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)中,也包括vt的更新公式由于有V的更新公式vt = vt + 学习率 * (r + gamma * vt+1 - vt).

改进:Advantage Actor-Critic,简称为A2C算法

    Avantage Actor-Critic算法,它是目前使用Actor Critic思想的主流算法之一,其实Actor Critic系列算法不一定要使用优势函数A(s,a),还可以有其他的变种。

Avantage Actor-Critic算法在训练时,Actor会根据当前的状态st和策略采样获得动作at,并与环境交互得到下一时刻的状态st+1和奖励rt,通过TD方法即可估计每一步的状态st对应的V值,从而更新Critic网络使得值网络的估计更加接近真实环境的期望回报,通过At = rt + gammaV(st+1) - V(st)来估计当前动作的优势值,并采用E[\bigtriangledown logp_{\theta}(a_{t}^{n}|s_{t}^{n})\cdot A(a_{t}^{n},s_{t}^{n})]计算Actor网络的梯度信息,如此循环网络采用梯度上升法,最大化总汇报的期望值,Critic网络会判断的越来越准确,而Actor网络也会调整自己的策略,使得下一次做得更好。

一般地,使用Advantage Actor-Critic算法就是Actor-Critic结构的扩展了,够用。

三、A3C算法

    Asynchronous Advantage Actor-Critic,简称为A3C。这是基于Advantage Actor Critic网络之后的一个异步版本,将Actor-Critic网络部署在多个线程中同时进行训练,并通过全局网络来同步参数,这种异步训练的模式大大提升了训练效率,训练速度更快,并且算法性能也更好。

    在A3C算法中,算法会创建一个全局网络Global Network和M个Worker线程,Global Network包含Actor和Critic网络,每个线程均会创建一个交互环境和Actor和Critic网络。Global Network并不直接参与到与环境的互动当中,而是每个Worker与环境有直接的交互,并且把所学习到的梯度信息上传到Global Network。初始化阶段Global Network随机初始化参数\theta\phi,Worker中的Actor-Critic网络从Global Network中同步拉取最新参数来初始化网络。在训练时,Worker中的Actor-Critic网络首先先从Global Network拉取最新参数,然后在最新策略\pi下与私有环境交互采样动作,并根据Advantage Actor-Critic算法计算参数\theta\phi的梯度信息。在完成梯度计算之后,各个Worker将梯度信息提交到Global Network中,并利用Global Network的优化器完成Global Network的网络参数更新。在算法测试阶段只使用Global Network与环境交互即可。

算法

执行流程

伪代码

四、DDPG算法(Online-Policy,在连续动作空间上表现较好)

    Deep Deterministic Policy Gradient,简称DDPG,深度确定性算法。DDPG也是解决连续控制型问题的一个算法,不过和PPO不一样,PPO输出的是一个策略,也就是一个概率分布,而DDPG输出的则直接是一个动作action,从DQN来延伸理解DDPG,相比于DQN算法而言,主要沿用了以下三种技术:

1、经验回放(Experience replay)

agent将每一轮与环境交互得到的经验数据(s,a,r,s',done)存入回放缓存(replay buffer)中,更新网络参数时按照批量的方式采样,能加快训练速度,提高训练的稳定性,但是比较耗费内存(memory)资源和计算(computation)资源.

对于经典的经验回放,就是分为存储和回放两个部分。

存储的角度看,经验回访可以分为集中式存储和分布式存储

集中式存储:智能体在一个环境中运行,把经验统一存储在经验池中。

分布式存储:多个智能体同时在多个环境中运行,并将经验统一存储在经验池中。由于多个智能体同时生成经验,所以能够使用更多资源的同时更快的收集经验。

采样的角度来看,回放也就是从存储中批量取数据的方式可以分为均匀回放和优先回放

均匀回放:等概率的从经验池中采样经验(数据)。

优先回放:为每条经验设定一个优先级,在采样经验时更倾向于选择优先级别更高的经验,证明其越需要被学习。

2、目标网络(fixed target Q-Network)

除了AC架构中的Actor和Critic网络,再使用一套Target Actor和Target Critic网络,这样在更新目标网络时,为避免网络参数更新过快带来的不稳定,采用软更新的方式.

3、噪声探索(Noisy explore)

确定性策略输出的动作为确定性动作,缺乏对于环境的探索,在训练阶段,给Actor网络输出的动作加入噪声,从而让智能体具有一定的探索能力.

伪代码

参考论文:https://arxiv.org/abs/1602.01783

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

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

相关文章

TCP协议分析《实验报告》

一、实验目的 1、理解TCP协议&#xff1b; 2、掌握TCP协议三次握手建立连接和四次挥手释放连接的过程&#xff1b; 3、理解TELNET协议及工作过程&#xff1b; 4、掌握TCP协议分析方法。 二、实验设备和环境 1、硬件设备&#xff1a;PC机或笔记本电脑&#xff1b; 2、软件…

Matlab simulink建模与仿真 第十七章(补充离散库和补充数学库)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、补充离散库和补充数学库中的模块概览 1、补充离散库 注&#xff1a;每个版本的补充离散库不一定相同&#xff0c;也不是每个版本的库都有如上所有模块。 2、补充数学库 二、离散直接传递函数Ⅱ模块 1、…

OpenCV_图像旋转超详细讲解

图像转置 transpose(src, dst); transpose()可以实现像素下标的x和y轴坐标进行对调&#xff1a;dst(i,j)src(j,i)&#xff0c;接口形式 transpose(InputArray src, // 输入图像OutputArray dst, // 输出 ) 图像翻转 flip(src, dst, 1); flip()函数可以实现对图像的水平翻转…

re题(24)BUUFCTF-[WUSTCTF2020]level1

BUUCTF在线评测 (buuoj.cn) 放到ida 这是下载的文本 逻辑比较简单&#xff0c;写个脚本 p[198,232,816,200,1536,300,6144,984,51200,570,92160,1200,565248,756,1474560,800,6291456,1782,65536000] for i in range(1,20):if (i & 1) ! 0 :p[i-1]chr(p[i-1] >> i)…

C++ ——string的模拟实现

目录 前言 浅记 1. reserve&#xff08;扩容&#xff09; 2. push_back&#xff08;尾插&#xff09; 3. iterator&#xff08;迭代器&#xff09; 4. append&#xff08;尾插一个字符串&#xff09; 5. insert 5.1 按pos位插入一个字符 5.2 按pos位插入一个字符串 …

C++速通LeetCode简单第18题-杨辉三角(全网唯一递归法)

全网唯一递归法&#xff1a; vector<vector<int>> generate(int numRows) {vector<int> v;vector<vector<int>>vn;if (numRows 1){v.push_back(1);vn.push_back(v);v.clear();return vn;//递归记得return}if (numRows 2){v.push_back(1);vn.p…

FPGA与Matlab图像处理之伽马校正

文章目录 一、什么是伽马校正&#xff1f;二、伽马校正的由来三、Matlab实现伽马校正3.1 matlab代码3.2 matlab结果 四、Verilog实现伽马校正4.1 生成初始化ROM文件4.2 Verilog代码4.3 仿真结果 一、什么是伽马校正&#xff1f; Gamma校正是图像处理中用以调整图像的亮度和对比…

代码随想录冲冲冲 Day47 单调栈Part1

739. 每日温度 初步了解单调栈&#xff0c;根本思想就是如果求的是一个元素右边或左边第一个最大的元素 那么就是递增栈&#xff0c;如果是最小的就是递减栈 首先先放入一个0代表 第一个元素的index 之后开始for loop 当后面的值小于等于这个top时 就要先把index放入st中&a…

Leetcode 找到字符串中所有字母异位词

在 C 中&#xff0c;两个 vector<int> 类型的变量进行 操作时&#xff0c;会逐个比较它们的元素&#xff0c;只有当两个向量的长度相同且每个位置上的元素值都相同时&#xff0c; 操作才会返回 true。 因此&#xff0c;在这道题的代码中&#xff0c;sCount pCount 这一…

Flip动画的实现示例demo

Flip动画的实现示例demo 文章说明核心代码效果展示Flip动画工具类的封装 文章说明 文章主要为了学习flip动画的实现思路&#xff0c;并且采用此示例效果来理解该实现思路的含义 参考渡一前端袁老师的讲解视频 核心代码 采用简单的y轴变化的动画效果为示例 <!DOCTYPE html>…

【K230 实战项目】气象时钟

【CanMV K230 AI视觉】 气象时钟 功能描述&#xff1a;说明HMDI资源3.5寸屏幕 使用方法 为了方便小伙伴们理解&#xff0c;请查看视频 B站连接 功能描述&#xff1a; 天气信息获取&#xff1a;通过连接到互联网&#xff0c;实时获取天气数据&#xff0c;包括温度、湿度、天气状…

【STM32】独立看门狗(IWDG)原理详解及编程实践(上)

本篇文章是对STM32单片机“独立看门狗&#xff08;IWDG&#xff09;”的原理进行讲解。希望我的分享对你有所帮助&#xff01; 目录 一、什么是独立看门狗 &#xff08;一&#xff09;简介 &#xff08;二&#xff09;、独立看门狗的原理 &#xff08;三&#xff09;、具体操…

vulkano (rust) 画一个三角形 (vulkan 渲染窗口初始化 (Linux) 下篇)

上文说到, vulkan 相比 OpenGL (ES), 更加贴近底层硬件, 许多东西需要应用软件手动管理, 所以 vulkan 的初始化过程比较麻烦, 或者说学习曲线比较陡峭. 但是, 这种麻烦是一次性的, 一旦学会了, 就能开始享受 vulkan 的诸多好处啦 ~ 本文以绘制一个三角形为例, 介绍 vulkan 的初…

2024最新版,人大赵鑫老师《大语言模型》新书pdf分享

本书主要面向希望系统学习大语言模型技术的读者&#xff0c;将重点突出核心概念与 算法&#xff0c;并且配以示例与代码&#xff08;伪代码&#xff09;帮助读者理解特定算法的实现逻辑。由于大语言模型技术的快速更迭&#xff0c;本书无法覆盖所有相关内容&#xff0c;旨在梳理…

瓶中水位检测系统源码分享

瓶中水位检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

【数据结构】图的概念和存储结构

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C游记》《进击的C》《Linux迷航》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、图的概念二、图的存储结构2.1 邻接矩阵2.1.1 成员变量与默认成员函数2.1.2 GetIndex2.1.3 AddEdge2.1.4 Pr…

使用 Java 初步搭建简单Spring 项目框架:

一、准备工作 安装 Java Development Kit (JDK)&#xff0c;确保环境变量配置正确。 安装一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;如 IntelliJ IDEA 或 Eclipse。 二、创建项目——具体步骤 在 ider 中创建一个新的 Maven 项目 使用 Maven&#xff1a; 在…

Linux bash脚本本地开发环境(Git Bash)配置

参考资料 VSCode: Windows 下配置 VSCode运行shellVSCodeを使用したシェルスクリプトの開発環境作成 目录 一. 必备VSCode插件二. 插件配置说明2.1 Bash IDE2.2 Code Runner2.3 shell-format 一. 必备VSCode插件 Bash IDE 该插件为 Bash 脚本提供了一些实用的开发工具和功能&…

鸿蒙 ArkUI组件二

ArkUI组件&#xff08;续&#xff09; 文本组件 在HarmonyOS中&#xff0c;Text/Span组件是文本控件中的一个关键部分。Text控件可以用来显示文本内容&#xff0c;而Span只能作为Text组件的子组件显示文本内容。 Text/Span组件的用法非常简单和直观。我们可以通过Text组件来显…

重生归来之挖掘stm32底层知识(1)——寄存器

概念理解 要使用stm32首先要知道什么是引脚和寄存器。 如下图所示&#xff0c;芯片通过这些金属丝与电路板连接&#xff0c;这些金属丝叫做引脚。一般做软件开发是不需要了解芯片是怎么焊的&#xff0c;只要会使用就行。我们平常通过编程来控制这些引脚的输入和输出&#xff0c…