强化学习Q-Learning:DQN

强化学习Q-Learning/DQN

本文是一篇学习笔记,主要参考李宏毅老师的强化学习课程。

目前主流的强化学习方法大致可以分为 policy-based 和 value-based 两大类。之前我们介绍的 policy gradient 策略梯度,就是 policy-based 的方法。本文要介绍的 Q-learning 则是 value-based 方法。不同于 policy gradient 直接优化 policy,Q-learning 是要学习一个价值网络,来估计策略的状态价值或动作状态价值,价值网络并不直接决定接下来要采取的动作,我们需要基于价值网络估计出的价值函数,制定新的 policy,并不断优化 policy。

本文将介绍如何训练模型来拟合价值函数,以及 Q-learning 如何基于价值网络不断优化 policy,最后介绍实际实现中常用的几个技巧。

价值函数

价值函数是强化学习中的重要概念,也是 value-based 类方法的基础。价值函数有关于状态 s s s 和关于状态-动作对 ( s , a ) (s,a) (s,a) 两种。这里的价值是指从这个状态 s s s 或从这个状态-动作对 ( s , a ) (s,a) (s,a) 开始,一直按照某个特定的策略进行动作,直到结束,这期间的期望回报就称为价值函数。

状态价值函数 V π ( s ) V^\pi(s) Vπ(s),是指在状态 s s s,接下来按照特定的策略 π \pi π 来进行动作,最终累积回报的期望,表示为:
V π ( s ) = E τ ∼ π [ R ( τ ) ∣ s 0 = s ] V^\pi(s)=\mathbb{E}_{\tau\sim\pi}[R(\tau)|s_0=s] \notag \\ Vπ(s)=Eτπ[R(τ)s0=s]
其中 τ \tau τ 是在状态 s s s 之后,根据策略 π \pi π 进行动作得到的轨迹, R ( τ ) R(\tau) R(τ) 即该轨迹得到的累积奖励。

动作-状态价值函数 Q π ( s , a ) Q^\pi(s,a) Qπ(s,a),是指在状态 s s s,先强制采取动作 a a a(这个动作不一定符合当前策略 π \pi π,是人为采取的),接下来按照特定的策略 π \pi π 来进行动作,最终累积回报的期望。表示为:
Q π ( s , a ) = E τ ∼ π [ R ( τ ) ∣ s 0 = s , a 0 = a ] Q^\pi(s,a)=\mathbb{E}_{\tau\sim\pi}[R(\tau)|s_0=s,a_0=a] \notag \\ Qπ(s,a)=Eτπ[R(τ)s0=s,a0=a]
从两种价值函数的定义上不难发现,二者之间存在这样的关系:
V π ( s ) = E a ∼ π [ Q π ( s , a ) ] (1) V^\pi(s)=\mathbb{E}_{a\sim\pi}[Q^\pi(s,a)] \tag{1} \\ Vπ(s)=Eaπ[Qπ(s,a)](1)
状态价值函数 V V V 是动作-价值函数 Q Q Q 在策略 π \pi π 下的期望。也可以更直接一点,带进去,写成:

V π ( s ) = Q π ( s , π ( s ) ) (2) V^\pi(s)=Q^\pi(s,\pi(s)) \tag{2} \\ Vπ(s)=Qπ(s,π(s))(2)
还是要强调一句,两种价值函数描述的都是针对某个特定的策略 π \pi π 的价值,对于不同的策略 π \pi π,同一个状态 s s s 的价值很可能是不同的。比如棋魂里的阿光,在刚学棋时可能驾驭不了高难度的棋招 “大飞”,此时策略(actor)“阿光” 对于 “大飞” 这个状态的价值函数估计就比较低,当阿光棋力上涨,能够驾驭这一招时,价值函数估计就比较高。即对于不同的 π \pi π,同样状态 s s s 的价值是不同的,所以价值函数都是针对特定的策略 π \pi π 来说的。
V 以前的阿光 ( 大飞 ) = low V 现在更强的阿光 ( 大飞 ) = high \begin{aligned} V^\text{以前的阿光}(大飞)&=\text{low} \\ \quad V^\text{现在更强的阿光}(大飞)&=\text{high} \end{aligned} \notag \\ V以前的阿光(大飞)V现在更强的阿光(大飞)=low=high

从 Q-learning 的思路来进行强化学习建模,需要两个步骤,首先是我们要如何训练网络来拟合价值函数 V / Q V/Q V/Q,然后是有了一个可靠的价值函数后,如何决定动作。我们接下来分别介绍这两步。

训练价值网络估计价值函数

现在我们一般是用神经网络来估计价值函数(DQN)了,我们需要价值网络是一个回归模型,输入一个状态(和动作),输出一个标量分数。那么如何训练这个神经网络呢?有两种方法,MC(Monte Carlo,蒙特卡洛) 和 TD(Temporal Difference,时序差分)。

MC 方法

MC 来训练价值网络的想法非常直觉,比如我们要估计 V π ( s ) V^\pi(s) Vπ(s),它是策略 π \pi π 在状态 s s s 之后的期望回报,那我们就让 π \pi π 把整个 episode 跑出来,记录下最终的累积回报 R ( τ ) R(\tau) R(τ),让价值网络回归这个累积回报就行了。
V ^ π ( s ) → R ( τ ) \hat{V}^\pi(s)\rightarrow R(\tau) \notag \\ V^π(s)R(τ)
理想情况下是能遍历所有可能的状态,得到最终累积回报来给价值网络学习,但这显然不可能,因此就采样 N N N 轮来近似。

TD 方法

MC 方法非常直觉,但问题在于每次都至少要等到一整轮 episode 跑完,才能更新一次模型。在很多实际的强化学习任务(如围棋、游戏等)中,跑完一整轮 episode 通常非常久,导致训练效率不高。而 TD 的方法,在每次动作,有了 { s t , a t , r t , s t + 1 } \{s_t, a_t, r_t, s_{t+1}\} {st,at,rt,st+1} 这一组数据之后,就能进行一次更新。

具体来说,我们先改写一下 V π ( s t ) V^\pi(s_t) Vπ(st)
V π ( s t ) = V π ( s t + 1 ) + r t V^\pi(s_t)=V^\pi(s_{t+1})+r_t \notag \\ Vπ(st)=Vπ(st+1)+rt
这个式子的意思很简单:状态 s t s_t st 的价值函数 V π ( s t ) V^\pi(s_t) Vπ(st) 等于当前立即得到的奖励 r t r_t rt 加上下一个状态的价值函数 V π ( s t + 1 ) V^\pi(s_{t+1}) Vπ(st+1)

这样,在我们有了单步的一组数据之后,就可以将 s t , s t + 1 s_t,s_{t+1} st,st+1 分别送入价值网络,得到相邻两步的价值估计值 V π ( s t ) , V π ( s t + 1 ) V^\pi(s_t),V^\pi(s_{t+1}) Vπ(st),Vπ(st+1),我们的训练目标就是希望这两个估计值的差值尽可能接近当前步的真实奖励 r t r_t rt
V ^ π ( s t ) − V ^ π ( s t + 1 ) → r t \hat{V}^\pi(s_t)-\hat{V}^\pi(s_{t+1})\rightarrow r_t \notag \\ V^π(st)V^π(st+1)rt

对比 MC 和 TD

我们对比一下 MC 和 TD 彼此的优缺点。

首先,MC 需要一整轮的数据才能进行一次更新,而 TD 只需一步的数据即可进行更新,训练效率更高。其次,MC 的目标值是 R ( τ ) R(\tau) R(τ),取决于其后所有步的随机采样结果,方差会比较大,而 TD 的目标是单步采样的数据 r t r_t rt,方差比较小。

但是,毕竟 TD 的目标中 V ^ π ( s t + 1 ) \hat{V}^\pi(s_{t+1}) V^π(st+1) 也是价值网络估计出来,因此 TD 不一定准。而 MC 的 R ( τ ) R(\tau) R(τ) 都是实打实跟环境交互跑出来的,是真实的目标。

总之 MC 和 TD 看起来还是各有优劣,但是现在一般都是采用 TD 方法来训练价值网络。

更好地policy

现在我们已经可以使用 MC 或 TD 方法来训练价值网络,估计价值函数,但是价值网络毕竟只是一个打分的模型,没法直接选择执行哪个动作,在实际中,有了价值函数之后,如何进行动作呢?也就是我们基于估计的价值函数,如何实现 policy 呢?以及我们如何能够在价值网络的引导下,不断地优化 policy 呢?

Q-learning 整体的过程如下图所示。最开始我们有一个初始的策略 π \pi π,我们让它与环境互动,得到一系列经验数据,然后在这些数据上采用 TD 或 MC 的方法,来学习出这个策略 π \pi π 对应的价值函数 Q π ( s , a ) Q^\pi(s,a) Qπ(s,a)。然后根据这个价值函数,得到一个 “更好的” 策略 π ′ \pi' π,随后再用这个 π ′ \pi' π 作为策略,继续上述过程,循环往复,不断地优化策略。这里所谓的 “更好”,指的是对于所有的状态 s s s,都有 V π ′ ( s ) ≥ V π ( s ) V^{\pi'}(s)\ge V^\pi(s) Vπ(s)Vπ(s)

在这里插入图片描述

现在的问题就是,如何根据价值函数,得到一定比现在更好的策略 π ′ \pi' π。实际上也非常简单,就是根据学习到的关于策略 π \pi π 的价值函数,取 argmax:
π ′ ( s ) = arg ⁡ max ⁡ a Q π ( s , a ) \pi'(s)=\arg\max_aQ^\pi(s,a) \notag \\ π(s)=argamaxQπ(s,a)
直觉上很好理解,每一步我们都取能让 Q Q Q 值最大的动作,最终得到的累积回报,肯定比当前按策略 π \pi π 要高。以下我们来简单证明推导一下:取 π ′ ( s ) = arg ⁡ max ⁡ a Q π ( s , a ) \pi'(s)=\arg\max_aQ^\pi(s,a) π(s)=argmaxaQπ(s,a),可以使得对所有的状态 s s s,都有 V π ′ ( s ) ≥ V π ( s ) V^{\pi'}(s)\ge V^\pi(s) Vπ(s)Vπ(s)

首先我们写出 V V V Q Q Q 的关系(式 2),状态价值函数 V π ( s ) V^\pi(s) Vπ(s) 相当于状态-动作价值函数 Q π ( s , a ) Q^\pi(s,a) Qπ(s,a) a = π ( s ) a=\pi(s) a=π(s),即在当前步也按照策略 π \pi π 来选取动作执行。注意我们此时已知 Q Q Q 函数了,所以我们是知道当前每个动作所得到的 Q Q Q 值的,因此我们可以直接贪婪地选取一个让 Q Q Q 值最大的动作 a a a,这样肯定是大于等于按照 π \pi π 选取动作的。而这种按照 arg ⁡ max ⁡ Q \arg\max Q argmaxQ 来选取动作的策略,正是我们刚刚定义的策略 π ′ \pi' π
V π ( s ) = Q π ( s , π ( s ) ) ≤ max ⁡ a ( Q π ( s , a ) ) = Q π ( s , π ′ ( s ) ) V^\pi(s)=Q^\pi(s,\pi(s))\le\max_a(Q^\pi(s,a))=Q^\pi(s,\pi'(s)) \notag \\ Vπ(s)=Qπ(s,π(s))amax(Qπ(s,a))=Qπ(s,π(s))
这是一步的差异。而如果我们每一步都采用 π ′ ( s ) = arg ⁡ max ⁡ a Q π ( s , a ) \pi'(s)=\arg\max_a Q^\pi(s,a) π(s)=argmaxaQπ(s,a) ,自然是更加优于 π \pi π,这最终能推导出 V π ( s ) ≤ V π ′ ( s ) V^\pi(s)\le V^{\pi'}(s) Vπ(s)Vπ(s)
V π ( s ) ≤ Q π ( s , π ′ ( s ) ) = E [ r t + V π ( s t + 1 ) ∣ s t = s , a t = π ′ ( s t ) ] ≤ E [ r t + Q π ( s t + 1 , π ′ ( s t + 1 ) ) ∣ s t = s , a t = π ′ ( s t ) ] = E [ r t + r t + 1 + V π ( s t + 2 ) ∣ . . . ] ≤ E [ r t + r t + 1 + Q π ( s t + 2 , π ′ ( s t + 2 ) ) ∣ . . . ] = . . . ≤ . . . ≤ V π ′ ( s ) \begin{aligned} V^\pi(s)&\le Q^\pi(s,\pi'(s)) \\ &=\mathbb{E}\left[r_t+V^\pi(s_{t+1})|s_t=s,a_t=\pi'(s_t)\right] \\&\le\mathbb{E}\left[r_t+Q^\pi(s_{t+1},\pi'(s_{t+1}))|s_t=s,a_t=\pi'(s_t)\right] \\ &=\mathbb{E}\left[r_t+r_{t+1}+V^\pi(s_{t+2})|...\right] \\ &\le\mathbb{E}\left[r_t+r_{t+1}+Q^\pi(s_{t+2},\pi'(s_{t+2}))|...\right] \\ &=...\le... \\ &\le V^{\pi'}(s) \end{aligned} \notag \\ Vπ(s)Qπ(s,π(s))=E[rt+Vπ(st+1)st=s,at=π(st)]E[rt+Qπ(st+1,π(st+1))st=s,at=π(st)]=E[rt+rt+1+Vπ(st+2)∣...]E[rt+rt+1+Qπ(st+2,π(st+2))∣...]=......Vπ(s)
至此,我们就证明了只要选择 π ′ ( s ) = arg ⁡ max ⁡ a Q π ( s , a ) \pi'(s)=\arg\max_a Q^\pi(s,a) π(s)=argmaxaQπ(s,a),就能保证对所有的状态 s s s 都有 V π ′ ( s ) ≥ V π ( s ) V^{\pi'}(s)\ge V^\pi(s) Vπ(s)Vπ(s),也就是 π ′ \pi' π 是一个比 π \pi π 更好的策略。然后我根据上图中的过程,不断循环迭代,直至收敛得到一个最终的策略。

Q-learning 常用技巧

我们已经介绍了 Q-learning 的基本思想,接下来我们介绍几个实际中一般都会用到的 tricks。

Target Network

上面提到,现在我们在训练价值网络时,一般会用 TD 方法,其训练目标(以 Q 为例)为:
Q π ( s t , a t ) → r t + Q π ( s t + 1 , a t + 1 ) Q^\pi(s_t,a_t)\rightarrow r_t+Q^\pi(s_{t+1},a_{t+1}) \notag \\ Qπ(st,at)rt+Qπ(st+1,at+1)
会发现等式两边都有共享的、一直在更新的网络 Q π Q^\pi Qπ,因此网络的拟合目标是在一直变的,虽然看起来上没什么问题,但实际中这种情况下训练通常会非常不稳定。Target Network 的技巧是我们将其中一个网络(一般是图中右下角那个)固定住,更新另一个网络,这样该网络的拟合目标一直是固定的,比较易于训练。在更新 N N N 轮之后,同步一次固定网络的参数。

在这里插入图片描述

Exploration

Exploitation v.s. Exploration 是强化学习中的一个重要课题,Exploitation 指利用已有的经验获取尽可能高的累积回报,Exploration 则是指训练时不只关注短期的回报,而是也以一定概率采样其他动作,尽量地探索是否有其他能赢得更高回报的可能选择。

在之前介绍的 Policy Gradient 类算法中,策略网络 π θ \pi_\theta πθ 输出的是一个动作空间的概率分布,每次执行的动作是从该分布中采样得到,即使是概率相对较低的动作也有机会被采样到,因此 Exploration 是已经被考虑在内的。

在刚刚介绍的 Q-learning 算法中,策略 π \pi π 本身不是一个分布,而是对学习到的 Q 函数在各个动作中 argmax 得到,是一种 greedy 的策略,这就导致同一状态下除了 Q 值最高的其他动作永远不会被尝试。假设初始化后,每个动作的默认 Q 值都是 0,那么最先被采样到且获得正 Q 值的动作就会一直被选中,而其它动作无法被探索到。因此,我们需要在 Q-learning 中引入一些机制来增强其 Exploration 能力。

Q-learning 中引入 Exploration 机制的思路其实很简单,只要在训练时,除了 argmax Q 之外,也给一定概率采样到其他动作即可。一般常用的有两种方法,一是 Epsilon Greedy,二是 Boltzmann Exploration。

Epsilon Greedy 是指选择动作时大部分时候( 1 − ϵ 1-\epsilon 1ϵ)取 argmax Q 的动作,但是也有 ϵ \epsilon ϵ 的概率不管 Q,直接随机选取一个动作:
a = { arg ⁡ max ⁡ a Q ( s , a ) , with probability  1 − ϵ random , others a= \begin{cases} \arg\max_a Q(s,a),&\quad \text{with probability } 1-\epsilon \\ \text{random},&\quad\text{others} \end{cases} \notag \\ a={argmaxaQ(s,a),random,with probability 1ϵothers
ϵ \epsilon ϵ 是一个超参数,其值一般随训练过程衰减,因为我们在训练前期更希望模型多多 Explore,后期则希望稳定的获取高的回报分数。

Boltzmann Exploration 是指我们直接将各动作的 Q 值归一化为一个分布,选取动作时从其中采样
P ( a ∣ s ) = exp ⁡ ( Q ( s , a ) ) ∑ a exp ⁡ ( s , a ) P(a|s)=\frac{\exp(Q(s,a))}{\sum_a\exp(s,a)} \notag \\ P(as)=aexp(s,a)exp(Q(s,a))

这两种方法都能使得 Q 值较低的动作也有一定概率选中,进行 Exploration。

Replay Buffer

在强化学习中,样本效率也是一个很重要的问题,强化学习训练过程的很大一部分耗时都花在策略 π \pi π 与环境互动收集 rollout 数据上,真正的 GPU 计算梯度反传更新模型其实是很快的。在 Q-learning 中,每个数据只会使用一次,这样的样本效率就不高。为了提高样本的利用率,我们可以将策略 π \pi π 与环境互动的经验 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1) 保存到一个 Replay Buffer 中,每次训练时从 Replay Buffer 中 sample 一个 batch 的数据用于训练模型。Replay Buffer 技巧一来可以提高样本的利用率,二来由于 Buffer 中所存的经验样本不一定来自于同一个 policy,因此也能提高每个 batch 内样本的多样性。

总结

本文我们先介绍了强化学习中的价值函数,然后介绍如何训练价值网络来拟合价值函数,以及 Q-learning/DQN 中如何不断地优化 policy,最后介绍了 Q-learning 在实际实现中常用的几个技巧。

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

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

相关文章

W公司云安全解决方案

1 安全理念DevOpvSec 统一安全运营 2 安全责任分层模型 3 云安全产品线 4 云安全解决方案/部署架构 5 安全能力 6 信创云平台适配 7 统一化安全运营 利用云安全平台实现统一的安全运维 8 安全资源池的统一纳管 9 案例分享:私有云 10 云安全解决方案的衍生特点 11 …

python中的in关键字查找的时间复杂度

列表(List) 对于列表来说, in 运算符的复杂度是 O(n),其中n是列表的长度。这意味着如果列表中有n个元素,那么执行 in 运算符需要遍历整个列表来查找目标元素。 以下是一个示例,演示了在列表中使用 in 运算…

MySQL基础 [一] - Ubuntu版本安装

目录 预安装 先查看自己操作系统的版本 添加MySQL APT下载源 下载 安装 正式安装 查看MySQL状态 打开MySQL 预安装 先查看自己操作系统的版本 lsb_release -a 添加MySQL APT下载源 下载 下载发布包 下载地址 : https://dev.mysql.com/downloads/repo/apt/ 这里下…

Springboot整合Mybatis+Maven+Thymeleaf学生成绩管理系统

前言 该系统为学生成绩管理系统,可以当作学习参考,也可以成为Spirng Boot初学者的学习代码! 系统描述 学生成绩管理系统提供了三种角色:学生,老师,网站管理员。主要实现的功能如下: 登录 &a…

操作系统之文件系统

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…

AG32:MCU和CPLD如何交互?

本文档介绍了AG32开发中,MCU与CPLD交互的具体方式以及例子。如需了解AG32更多资料可发邮件:salesagm-micro.com 一、MCU和CPLD直接交互 cpld工程创建及编译的操作流程,参考文档《AG32下fpga和cpld的使用入门》 在工程中,用户逻辑…

机器人轨迹跟踪控制——CLF-CBF-QP

本次使用MATLAB复现CLF-CBF-QP算法,以实现机器人轨迹跟踪同时保证安全性能 模型 使用自行车模型来进行模拟机器人的移动动态,具体的模型推导参考车辆运动学模型-自行车模型 采用偏差变量 p ~ = p − p r e f u ~ = u − u r e f \tilde{p} = p - p_{ref} \\ \tilde{u} = …

009_抽象类和接口

抽象类和接口 final关键字常量 单例模式(设计模式)枚举类抽象类抽象类的注意事项、特点使用抽象类的好处模版方法设计模式 接口接口的好处接口的注意事项 final关键字 final关键字是最终的意思,可以修饰类、方法、变量。 修饰类:…

新潮透明液体水珠水滴失真故障扭曲折射特效海报字体标题设计ps样机动作素材 Bubble Photoshop Templates

只需单击几下即可创建引人注目的视觉效果!您需要做的就是将您的文本或图像放入智能对象中并应用作。 包中包含: 15 个静态 Photoshop 模板(PS 2019 及更高版本) 01-05 垂直布局 (22504000)06-10 水平布局…

Android DiaLog全屏设置,带有叉号的弹窗,这个弹窗分为两个部分,一个是主体,另一个是关闭部分。自定义布局弹窗

1.先上图。要实现的效果图。 2.这是我自己实现的效果图,是不是跟效果图一摸一样 来看看整体效果 3.我把自己实现的效果图的代码写出来。如下就是我的代码 3.1首先是MainActivity类 import androidx.appcompat.app.AppCompatActivity;import android.app.Alert…

NVR接入录像回放平台用EasyCVR打造地下车库安防:大型商居安全优选方案

一、背景分析 随着居民生活品质的提升,大型商业建筑和住宅小区纷纷配套建设地下停车库。但是地下车库盗窃、失火、恶意毁坏车辆、外部人员随意进出等事件频发,部署视频监控系统成为保障地下车库的安全关键举措。 目前,很多商业和住宅都会在…

阶段测试 【过程wp】

分享总结: 回顾起来,真的感慨很多呀。看着并不难啊,但难的是解题思维:如何判断该页面的关键点,快速地确定问题的核心,找到对应的解决方法。达到便捷、高效的得到结果。我们做了整整近七个半小时。在这个过程中,我发现自己的思维钝化,不太能自主高效地划分判断漏洞类型,…

【C++】<STL部分>:STL标准模板库概要

STL(standard template libaray-标准模板库),是C标准库的重要组成部分,包含了很多常用的数据结构和算法。 在我们学习了模板的之后,再来看STL,就能知道它是C标准库中的模板类和模板函数的集合,作为可复用的库大大提高…

从传递函数到PID控制器

在过程控制中,按偏差的比例(P,Proportional)、积分(I,Integral)和微分(D,Differential)进行控制的PID控制器(亦称PID调节器)是应用最为…

【PVR Review】《A Review of Palmar Vein Recognition》

[1]张秀峰,牛选兵,王伟,等.掌静脉识别研究综述[J].大连民族大学学报,2020,22(01):33-37.DOI:10.13744/j.cnki.cn21-1431/g4.2020.01.007. 文章目录 1、背景2、手掌静脉识别方法2.1、传统手掌静脉图像识别方法2.2、基于深度学习的掌静脉图像识别 3、手掌静脉识别难点 1、背景 目…

vector复制耗时

CPP中的vector对象在传参给子函数时&#xff0c;如果直接传参&#xff0c;会造成复制给形参的额外耗时 如何解决这个问题呢&#xff1f; 这样定义局部函数 const vector <int>&vec可以保证传递vector对象时使用地址传递&#xff0c;并且使用const保证vector不被改变…

算法思想之双指针

文章目录 双指针字符串序列判定字符串所有整数最小和服务交换接口失败率分析分披萨最多团队 双指针 双指针是指在解决问题时使用两个指针&#xff0c;通常分别指向数组或字符串中的不同位置&#xff0c;通过移动这两个指针来解决问题的一种技巧。双指针技巧常用于解决数组、链…

学透Spring Boot — 018. 优雅支持多种响应格式

这是我的专栏《学透Spring Boot》的第18篇文章&#xff0c;想要更系统的学习Spring Boot&#xff0c;请访问我的专栏&#xff1a;学透 Spring Boot_postnull咖啡的博客-CSDN博客。 目录 返回不同格式的响应 Spring Boot的内容协商 控制器不用任何修改 启动内容协商配置 访…

ngx_os_init

定义在 src\os\unix\ngx_posix_init.c ngx_int_t ngx_os_init(ngx_log_t *log) {ngx_time_t *tp;ngx_uint_t n; #if (NGX_HAVE_LEVEL1_DCACHE_LINESIZE)long size; #endif#if (NGX_HAVE_OS_SPECIFIC_INIT)if (ngx_os_specific_init(log) ! NGX_OK) {return NGX_ERR…

深信服护网蓝初面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…