【深度强化学习】策略梯度算法:REINFORCE
策略梯度
强化学习算法进阶
Q-learning、DQN 及 DQN 改进算法都是基于价值(value-based)的方法,其中 Q-learning 是处理有限状态的算法,而 DQN 可以用来解决连续状态的问题。在强化学习中,除了基于值函数的方法,还有一支非常经典的方法,那就是基于策略(policy-based)的方法。对比两者:
- 基于值函数的方法主要是学习值函数,然后根据值函数导出一个策略,学习过程中并不存在一个显式的策略;
- 而基于策略的方法则是直接显式地学习一个目标策略。策略梯度是基于策略的方法的基础,本章从策略梯度算法说起。
基于策略的方法首先需要将策略参数化
假设目标策略 π θ \pi_{\theta} πθ是一个随机性策略,并且处处可微,其中θ是对应的参数。我们可以用一个线性模型或者神经网络模型来为这样一个策略函数建模,输入某个状态,然后输出一个动作的概率分布。我们的目标是要寻找一个最优策略并最大化这个策略在环境中的期望回报。我们将策略学习的目标函数定义为: J ( θ ) = E s 0 [ V π θ ( s 0 ) ] J(\theta)=\mathbb E_{s_0}[V^{\pi_{\theta}}(s_0)] J(θ)=Es0[Vπθ(s0)]其中, s 0 s_0 s0表示初始状态。现在有了目标函数,我们将目标函数对策略θ求导,得到导数后,就可以用梯度上升方法来最大化这个目标函数,从而得到最优策略。
策略 π \pi π下的状态访问分布,在此用 ν π \nu^{\pi} νπ表示。然后我们对目标函数求梯度,可以得到如下式子:
∇ θ J ( θ ) ∝ ∑ s ∈ S ν π θ ( s ) ∑ a ∈ A Q π θ ( s , a ) ∇ θ π θ ( a ∣ s ) = ∑ s ∈ S ν π θ ( s ) ∑ a ∈ A π θ ( a ∣ s ) Q π θ ( s , a ) ∇ θ π θ ( a ∣ s ) π θ ( a ∣ s ) = E π θ [ Q π θ ( s , a ) ∇ θ log π θ ( a ∣ s ) ] \begin{aligned} \nabla_\theta J(\theta) & \propto\sum_{s\in S}\nu^{\pi_\theta}(s)\sum_{a\in A}Q^{\pi_\theta}(s,a)\nabla_\theta\pi_\theta(a|s) \\ & =\sum_{s\in S}\nu^{\pi_\theta}(s)\sum_{a\in A}\pi_\theta(a|s)Q^{\pi_\theta}(s,a)\frac{\nabla_\theta\pi_\theta(a|s)}{\pi_\theta(a|s)} \\ & =\mathbb{E}_{\pi_\theta}[Q^{\pi_\theta}(s,a)\nabla_\theta\log\pi_\theta(a|s)] \end{aligned} ∇θJ(θ)∝s∈S∑νπθ(s)a∈A∑Qπθ(s,a)∇θπθ(a∣s)=s∈S∑νπθ(s)a∈A∑πθ(a∣s)Qπθ(s,a)πθ(a∣s)∇θπθ(a∣s)=Eπθ[Qπθ(s,a)∇θlogπθ(a∣s)]
符号说明
- ∇ θ J ( θ ) \nabla_{\theta}J(\theta) ∇θJ(θ):关于参数 θ \theta θ 的目标函数 J ( θ ) J(\theta) J(θ) 的梯度。目标函数 J ( θ ) J(\theta) J(θ) 通常表示策略 π θ \pi_{\theta} πθ 的期望回报,策略 π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(a∣s) 是在状态 s s s 下采取动作 a a a 的概率,由参数 θ \theta θ 决定。
- S S S:状态空间,即所有可能的状态集合。
- A A A:动作空间,即所有可能的动作集合。
- ν π θ ( s ) \nu^{\pi_{\theta}}(s) νπθ(s):在策略 π θ \pi_{\theta} πθ 下状态 s s s 的稳态分布(也叫占有度量),表示长期来看,智能体处于状态 s s s 的概率。
- Q π θ ( s , a ) Q^{\pi_{\theta}}(s, a) Qπθ(s,a):在策略 π θ \pi_{\theta} πθ 下的状态 - 动作值函数,即从状态 s s s 采取动作 a a a 后,遵循策略 π θ \pi_{\theta} πθ 所能获得的期望累积回报。
- π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(a∣s):在状态 s s s 下,根据策略 π θ \pi_{\theta} πθ 采取动作 a a a 的概率。
- E π θ [ ⋅ ] \mathbb{E}_{\pi_{\theta}}[\cdot] Eπθ[⋅]:在策略 π θ \pi_{\theta} πθ 下的期望。
公式推导步骤
-
第一步
∇ θ J ( θ ) ∝ ∑ s ∈ S ν π θ ( s ) ∑ a ∈ A Q π θ ( s , a ) ∇ θ π θ ( a ∣ s ) \nabla_{\theta}J(\theta) \propto \sum_{s \in S} \nu^{\pi_{\theta}}(s) \sum_{a \in A} Q^{\pi_{\theta}}(s, a) \nabla_{\theta} \pi_{\theta}(a|s) ∇θJ(θ)∝s∈S∑νπθ(s)a∈A∑Qπθ(s,a)∇θπθ(a∣s)这一步表达了目标函数的梯度与状态 - 动作值函数和策略梯度的关系。它的含义是,目标函数的梯度可以通过对所有状态和动作的加权求和来近似,权重是状态的稳态分布和状态 - 动作值函数。 -
第二步
= ∑ s ∈ S ν π θ ( s ) ∑ a ∈ A π θ ( a ∣ s ) Q π θ ( s , a ) ∇ θ π θ ( a ∣ s ) π θ ( a ∣ s ) = \sum_{s \in S} \nu^{\pi_{\theta}}(s) \sum_{a \in A} \pi_{\theta}(a|s) Q^{\pi_{\theta}}(s, a) \frac{\nabla_{\theta} \pi_{\theta}(a|s)}{\pi_{\theta}(a|s)} =s∈S∑νπθ(s)a∈A∑πθ(a∣s)Qπθ(s,a)πθ(a∣s)∇θπθ(a∣s)这里通过将 π θ ( a ∣ s ) \pi_{\theta}(a|s) πθ(a∣s) 乘进去再除出来,将式子变形。注意到 ∇ θ π θ ( a ∣ s ) π θ ( a ∣ s ) = ∇ θ log π θ ( a ∣ s ) \frac{\nabla_{\theta} \pi_{\theta}(a|s)}{\pi_{\theta}(a|s)} = \nabla_{\theta} \log \pi_{\theta}(a|s) πθ(a∣s)∇θπθ(a∣s)=∇θlogπθ(a∣s),这是一个常见的对数求导技巧。 -
第三步
= E π θ [ Q π θ ( s , a ) ∇ θ log π θ ( a ∣ s ) ] = \mathbb{E}_{\pi_{\theta}}[Q^{\pi_{\theta}}(s, a) \nabla_{\theta} \log \pi_{\theta}(a|s)] =Eπθ[Qπθ(s,a)∇θlogπθ(a∣s)]这一步将双重求和转化为期望形式。根据期望的定义,对于一个离散随机变量 X X X(这里是状态 - 动作对 ( s , a ) (s, a) (s,a)),其期望 E [ X ] = ∑ i p i x i \mathbb{E}[X] = \sum_{i} p_i x_i E[X]=∑ipixi,其中 p i p_i pi 是取值 x i x_i xi 的概率。在这里, ν π θ ( s ) π θ ( a ∣ s ) \nu^{\pi_{\theta}}(s) \pi_{\theta}(a|s) νπθ(s)πθ(a∣s) 是状态 - 动作对 ( s , a ) (s, a) (s,a) 的联合概率分布,所以双重求和可以写成期望的形式。
这个梯度可以用来更新策略。需要注意的是,因为上式中期望的下标是 π θ \pi_{\theta} πθ,所以策略梯度算法为在线策略(on-policy)算法,即必须使用当前策略采样得到的数据来计算梯度。直观理解一下策略梯度这个公式,可以发现在每一个状态下,梯度的修改是让策略更多地去采样到带来较高值的动作,更少地去采样到带来较低值的动作,如图 9-1 所示。
在计算策略梯度的公式中,我们需要用到 Q π θ ( s , a ) Q^{\pi_{\theta}}(s,a) Qπθ(s,a),可以用多种方式对它进行估计。接下来要介绍的 REINFORCE 算法便是采用了蒙特卡洛方法来估计。
REINFORCE
对于一个有限步数的环境来说,REINFORCE 算法中的策略梯度为: ∇ θ J ( θ ) = E π θ [ ∑ t = 0 T ( ∑ t ′ = t T γ t ′ − t r t ′ ) ∇ θ log π θ ( a t ∣ s t ) ] \nabla_\theta J(\theta)=\mathbb{E}_{\pi_\theta}\left[\sum_{t=0}^T\left(\sum_{t^{\prime}=t}^T\gamma^{t^{\prime}-t}r_{t^{\prime}}\right)\nabla_\theta\log\pi_\theta(a_t|s_t)\right] ∇θJ(θ)=Eπθ[t=0∑T(t′=t∑Tγt′−trt′)∇θlogπθ(at∣st)]其中, T T T是和环境交互的最大步数。
REINFORCE 算法的具体算法流程如下:
- 初始化策略参数 θ \theta θ
- for 序列 e = 1 → E e=1\to E e=1→E do :
- 用当前策略 π θ \pi_{\theta} πθ采样轨迹 { s 1 , a 1 , r 1 , s 2 , a 2 , r 2 , . . . , s T , a T , r T } \{s_1,a_1,r_1,s_2,a_2,r_2,...,s_T,a_T,r_T\} {s1,a1,r1,s2,a2,r2,...,sT,aT,rT}
- 计算当前轨迹每个时刻 t t t往后的回报 ∑ t ′ = t T γ t ′ − t r t ′ \sum_{t'=t}^T\gamma^{t'-t_{r_{t'}}} ∑t′=tTγt′−trt′,记为 ψ t \psi_t ψt
- 对 θ \theta θ进行更新, θ = θ + α ∑ t T ψ t ∇ θ log π θ ( a t ∣ s t ) \theta=\theta+\alpha\sum_{t}^T\psi_t \nabla_{\theta}\log \pi_{\theta}(a_t|s_t) θ=θ+α∑tTψt∇θlogπθ(at∣st)
- end for
注意这里是梯度上升,写成loss的时候应为 loss = − ψ t log π θ ( a t ∣ s t ) \text{loss}=-\psi_t \log \pi_{\theta}(a_t|s_t) loss=−ψtlogπθ(at∣st)
REINFORCE 代码实践
import gym
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm# rl_utils是作者自己写的文件,在github上去下载:
# https://github.com/boyu-ai/Hands-on-RL/blob/main/rl_utils.py
# kaggle上导入库操作:
from shutil import copyfile
copyfile(src='/kaggle/input/rlutils/rl_utils.py', dst = "../working/rl_utils.py")
import rl_utils# 首先定义策略网络PolicyNet,其输入是某个状态,输出则是该状态下的动作概率分布,
# 这里采用在离散动作空间上的softmax()函数来实现一个可学习的多项分布(multinomial distribution)。
class PolicyNet(torch.nn.Module):def __init__(self, state_dim, hidden_dim, action_dim):super(PolicyNet, self).__init__()self.fc1 = torch.nn.Linear(state_dim, hidden_dim)self.fc2 = torch.nn.Linear(hidden_dim, action_dim)def forward(self, x):x = F.relu(self.fc1(x))return F.softmax(self.fc2(x), dim=1)# 再定义我们的 REINFORCE 算法。
# 在函数take_action()函数中,我们通过动作概率分布对离散的动作进行采样。
# 在更新过程中,我们按照算法将损失函数写为策略回报的负数,对θ求导后就可以通过梯度下降来更新策略。
class REINFORCE:def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,device):self.policy_net = PolicyNet(state_dim, hidden_dim,action_dim).to(device)self.optimizer = torch.optim.Adam(self.policy_net.parameters(),lr=learning_rate) # 使用Adam优化器self.gamma = gamma # 折扣因子self.device = devicedef take_action(self, state): # 根据动作概率分布随机采样state = torch.tensor([state], dtype=torch.float).to(self.device)probs = self.policy_net(state)action_dist = torch.distributions.Categorical(probs)action = action_dist.sample()return action.item()def update(self, transition_dict):reward_list = transition_dict['rewards']state_list = transition_dict['states']action_list = transition_dict['actions']G = 0self.optimizer.zero_grad()for i in reversed(range(len(reward_list))): # 从最后一步算起reward = reward_list[i]state = torch.tensor([state_list[i]],dtype=torch.float).to(self.device)action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device)log_prob = torch.log(self.policy_net(state).gather(1, action)) # gather用于从张量中提取特定索引处的值。G = self.gamma * G + rewardloss = -log_prob * G # 每一步的损失函数loss.backward() # 反向传播计算梯度self.optimizer.step() # 梯度下降# 定义好策略,我们就可以开始实验了,看看 REINFORCE 算法在车杆环境上表现如何吧!
learning_rate = 1e-3
num_episodes = 1000
hidden_dim = 128
gamma = 0.98
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")env_name = "CartPole-v0"
env = gym.make(env_name)
env.seed(0)
torch.manual_seed(0)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma,device)return_list = []
for i in range(10):with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:for i_episode in range(int(num_episodes / 10)):episode_return = 0transition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}state = env.reset()done = Falsewhile not done:action = agent.take_action(state)next_state, reward, done, _ = env.step(action)transition_dict['states'].append(state)transition_dict['actions'].append(action)transition_dict['next_states'].append(next_state)transition_dict['rewards'].append(reward)transition_dict['dones'].append(done)state = next_stateepisode_return += rewardreturn_list.append(episode_return)agent.update(transition_dict)if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode':'%d' % (num_episodes / 10 * i + i_episode + 1),'return':'%.3f' % np.mean(return_list[-10:])})pbar.update(1)
在 CartPole-v0 环境中,满分就是 200 分,我们发现 REINFORCE 算法效果很好,可以达到 200 分。接下来我们绘制训练过程中每一条轨迹的回报变化图。由于回报抖动比较大,往往会进行平滑处理。
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()
可以看到,随着收集到的轨迹越来越多,REINFORCE 算法有效地学习到了最优策略。
算法缺点:
- 不过,相比于前面的 DQN 算法,REINFORCE 算法使用了更多的序列,这是因为 REINFORCE 算法是一个在线策略算法,之前收集到的轨迹数据不会被再次利用。
- 此外,REINFORCE 算法的性能也有一定程度的波动,这主要是因为每条采样轨迹的回报值波动比较大,这也是 REINFORCE 算法主要的不足。
REINFORCE 算法是策略梯度乃至强化学习的典型代表,智能体根据当前策略直接和环境交互,通过采样得到的轨迹数据直接计算出策略参数的梯度,进而更新当前策略,使其向最大化策略期望回报的目标靠近。这种学习方式是典型的从交互中学习,并且其优化的目标(即策略期望回报)正是最终所使用策略的性能,这比基于价值的强化学习算法的优化目标(一般是时序差分误差的最小化)要更加直接。 REINFORCE 算法理论上是能保证局部最优的,它实际上是借助蒙特卡洛方法采样轨迹来估计动作价值,这种做法的一大优点是可以得到无偏的梯度。但是,正是因为使用了蒙特卡洛方法,REINFORCE 算法的梯度估计的方差很大,可能会造成一定程度上的不稳定,这也是 Actor-Critic 算法要解决的问题。
相关文章:

【深度强化学习】策略梯度算法:REINFORCE
策略梯度 强化学习算法进阶 Q-learning、DQN 及 DQN 改进算法都是基于价值(value-based)的方法,其中 Q-learning 是处理有限状态的算法,而 DQN 可以用来解决连续状态的问题。在强化学习中,除了基于值函数的方法&#…...

手机用流量怎样设置代理ip?
互联网各领域资料分享专区(不定期更新): Sheet...

CI/CD部署打包方法
项目目前部署方式: 各地区服务器打包同一个runner(需要互相排队,不并发)各地区客户端可以并发打包,同个地区客户端打多个包需要排队 部署方法 下载gitlab-runner: https://docs.gitlab.com/runner/insta…...

LabVIEW 中dde.llbDDE 通信功能
在 LabVIEW 功能体系中,位于 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform\dde.llb 的 dde.llb 库占据着重要的地位。作为一个与动态数据交换(DDE)紧密相关的库文件,它为 LabVIEW 用户提供了与其他…...
探索后端开发中的异步API:基于Resilience4j与Reactive Programming的高性能设计
引言 随着微服务架构的普及,后端系统面临的挑战愈发严峻,尤其是在高并发和高可用性方面。传统的同步调用模式虽然简单,但在处理大量并发请求时可能会成为瓶颈。为了应对这一问题,异步编程逐渐成为后端开发的热门话题。 在本文中…...

leetcode 2915. 和为目标值的最长子序列的长度
题目如下 数据范围 本题就是典型的背包问题target就是容量,nums[i]就是第i个物品的重量。其实就是选最多的物品使得背包刚好装满。 令f(i,j)为当考虑到i - 1物品时刚好装到j重量的物品数。 当j > nums[j]时 有f(i,j) max(f(i - 1,j - nums[i - 1]) 1,f(i -…...

【Vue】打包vue3+vite项目发布到github page的完整过程
文章目录 第一步:打包第二步:github仓库设置第三步:安装插件gh-pages第四步:两个配置第五步:上传github其他问题1. 路由2.待补充 参考文章: 环境: vue3vite windows11(使用终端即可&…...

Flutter编译问题记录
问题: 运行出现以下报错 Launching lib/main.dart on macOS in debug mode... Warning: CocoaPods not installed. Skipping pod install. CocoaPods is a package manager for iOS or macOS platform code. Without CocoaPods, plugins will not work on iOS or …...
poetry shell - 作为插件安装和使用
安装插件 安装完 poetry,想进入环境,执行 poetry shell 后会报错,是因为 poetry shell 在后面的版本中,是作为插件,需要额外安装。 poetry self add poetry-plugin-shell关于 poetry-plugin-shell github : https:/…...
UE5中的快捷键汇总
以下是Unreal Engine 5(UE5)中一些常用的快捷键大全,涵盖编辑器操作、视口导航、蓝图编辑等多个方面(会持续补充作为笔记存在): 通用快捷键 快捷键功能Ctrl S保存当前关卡Ctrl Shift S保存所有Ctrl Z撤销Ctrl C复制Ctrl V…...

2月14(信息差)
🌍杭州:全球数贸港核心区建设方案拟出台 争取国家支持杭州在网络游戏管理给予更多权限 🎄Kimi深夜炸场:满血版多模态o1级推理模型!OpenAI外全球首次!Jim Fan:同天两款国产o1绝对不是巧合&#x…...

ElementUI 的组件 Switch(开关)如何让文字显示在按钮上
效果图: 一、引入switch组件 给组件自定义一个类:tableScopeSwitch,设置开关的值和对应展示的文字(开为 1,并展示启用;关为 0,并展示禁用)。 <div class"tableScopeSwitch…...

Redis常用的五种数据结构详解
一、Redis 数据库介绍 Redis 是一种键值(Key-Value)数据库。相对于关系型数据库(比如 MySQL),Redis 也被叫作非关系型数据库。 像 MySQL 这样的关系型数据库,表的结构比较复杂,会包含很多字段&…...

stm32 CubeMx 实现SD卡/sd nand FATFS读写测试
文章目录 stm32 CubeMx 实现SD卡/SD nand FATFS读写测试 1. 前言 2. 环境介绍 2.1 软硬件说明 2.2 外设原理图 3. 工程搭建 3.1 CubeMx 配置 3.2 SDIO时钟配置说明 3.2 读写测试 3.2.1 添加读写测试代码 3.3 FATFS文件操作 3.3.1 修改读写测试代码 3.4 配置问题记…...

【Unity】 HTFramework框架(六十)Assistant助手(在Unity中接入DeepSeek等AI语言大模型)
更新日期:2025年2月14日。 Github源码:[点我获取源码] Gitee源码:[点我获取源码] 索引 Assistant助手安装Ollama使用Assistant(在编辑器中)打开Assistant配置Assistant使用Assistant处理Assistant回复的内容使用推理大…...
web自动化笔记(二)
文章目录 一、参数化测试1.pytest命令2.实现参数化测试3.填写地址测试4.生成Allure测试报告5.关键字驱动 二、案例1.实现后台登录1.1登录1.2.处理验证码1.3.封装识别验证码函数 2.通过cookie保持登录2.1给页面添加cookie2.2获取页面的cookie2.3自动化获取cookie 三、excel进行数…...

IIS部署netcore程序后,出现500.30错误解决方案之一
netcore程序部署到IIS后一直出现错误,访问首页后会跳转到登录页地址,然后看到如下错误 HTTP Error 500.30 - ANCM In-Process Start Failure Common solutions to this issue: The application failed to start The application started but then stopp…...

spring 学习(spring-Dl补充(注入不同类型的数据))
前言 在之前的案例,列举的最多的是注入 对象。本篇博客则是补充说我们不仅可以注入对象 还可以注入其他的数据类型包括基本数据类型,引用数据类型。 注入基本数据类型 常见的基本数据类型有:short char int long float double boolean …...

Docker Desktop之Nginx
安装Nginx 把这个复制 到docker 中执行 即可...
利用ffplay播放udp组播视频流
ffplay -fs -fflags nobuffer -flags low_delay -analyzeduration 0 -probesize 32 -framedrop -sync ext -strict experimental udp://224.1.1.1:5001 -fs : 全屏显示 -fflags nobuffer : 禁用输入缓冲(减少100-200ms缓冲延迟) -an…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...