Hands on RL 之 Proximal Policy Optimization (PPO)

Hands on RL 之 Proximal Policy Optimization (PPO)

文章目录

  • Hands on RL 之 Proximal Policy Optimization (PPO)
    • 1. 回顾Policy Gradient和TRPO
    • 2. PPO (Clip)
    • 3. PPO(Penalty)
    • 4. PPO中Advantage Function的计算
    • 5.实现 PPO-Clip
      • Reference

1. 回顾Policy Gradient和TRPO

​ 首先回顾一下Policy Gradient (PG)的方法,在策略梯度方法PG中,我们使用参数化的神经网络(Neural Network,NN) π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ) π θ ( a ∣ s ) \pi_\theta(a|s) πθ(as)来表示策略网络,则我们需要优化的目标函数可以写作
PG: L P G ( θ ) = E ^ t [ log ⁡ π θ ( a t ∣ s t ) A ^ t ] \text{PG:} \quad \textcolor{blue}{L^{PG}(\theta) = \hat{\mathbb{E}}_t [\log\pi_\theta(a_t|s_t) \hat{A}_t]} PG:LPG(θ)=E^t[logπθ(atst)A^t]
其中 A ^ t \hat{A}_t A^t是对优势函数(Advantage Function)的估计,这表明已经将状态价值作为baseline b ( S ) b(S) b(S)了, L P G L^{PG} LPG表示的是policy gradient。

​ 然后再回顾一下Trust Region Policy Optimization (TRPO)的方法,在TRPO中引入了KL散度(Kullback-Leibler Divergence)的概念,KL散度是用于描述我们用概率分布Q来估计真是分布P的编码损失

KL散度的定义如下

​ 假设对随机变量 ξ \xi ξ,存在两个概率分布P和Q,其中P是真实的概率分布,Q是较容易获得的概率分布。如果 ξ \xi ξ是离散的随机变量,那么定义从P到Qd KL散度为
D K L ( P , Q ) = ∑ i P ( i ) ln ⁡ ( P ( i ) Q ( i ) ) \mathbb{D}_{KL}(P,Q) = \sum_i P(i)\ln(\frac{P(i)}{Q(i)}) DKL(P,Q)=iP(i)ln(Q(i)P(i))
如果 ξ \xi ξ是连续变量,则定义从P到Q的KL散度为
D K L ( P , Q ) = ∫ − ∞ ∞ p ( x ) ln ⁡ ( p ( x ) q ( x ) ) d x \mathbb{D}_{KL}(P,Q) = \int^\infty_{-\infty}p(\mathbb{x})\ln(\frac{p(\mathbb{x})}{q(\mathbb{x})}) d\mathbb{x} DKL(P,Q)=p(x)ln(q(x)p(x))dx
​ 在TRPO中待优化的目标函数即为
TRPO: { max ⁡ θ L T R P O ( θ ) = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t ] subject to E ^ t [ D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] ≤ δ \text{TRPO:} \left \{ \begin{aligned} \max_\theta \quad & \textcolor{blue}{L^{TRPO}(\theta) = \hat{\mathbb{E}}_t \Big[\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t \Big]} \\ \text{subject to} \quad & \textcolor{blue}{\hat{\mathbb{E}}_t \Big[\mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big) \Big] \le \delta} \end{aligned} \right. TRPO: θmaxsubject toLTRPO(θ)=E^t[πθold(atst)πθ(atst)A^t]E^t[DKL(πθold(st),πθ(st))]δ
其中 L T R P O L^{TRPO} LTRPO表示 trust region policy optimization, θ old \theta_{\text{old}} θold表示更新前的网络参数。

2. PPO (Clip)

​ 在介绍之前先定义几个符号,用 r t ( θ ) r_t(\theta) rt(θ)来表示概率比,即 r t ( θ ) = π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) \textcolor{blue}{r_t(\theta)=\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_\text{old}}(a_t|s_t)}} rt(θ)=πθold(atst)πθ(atst),那么则有 r t ( θ old ) = 1 r_t(\theta_\text{old})=1 rt(θold)=1,所以在TRPO中待优化的目标函数就变成了
L T R P O = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t ] = E ^ t [ r t ( θ ) A ^ t ] L^{TRPO} = \hat{\mathbb{E}}_t \Big[ \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t \Big] = \hat{\mathbb{E}}_t [r_t(\theta)\hat{A}_t] LTRPO=E^t[πθold(atst)πθ(atst)A^t]=E^t[rt(θ)A^t]
PPO clip版本就是在TRPO的基础上增加了截断(clip)的操作,截断即是将概率比限制在一定的范围之内,即 r t ( θ ) ∈ [ 1 − ϵ , 1 + ϵ ] r_t(\theta)\in[1-\epsilon, 1+\epsilon] rt(θ)[1ϵ,1+ϵ],其中 ϵ \epsilon ϵ是指定范围的超参数表示截断的范围,一般设置 ϵ = 0.2 \epsilon=0.2 ϵ=0.2。修改后的替代目标(surrogate objective)可以表示为
PPO-clip: L C L I P ( θ ) = E ^ t [ min ⁡ ( r t ( θ ) A ^ t , clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] \text{PPO-clip:} \quad \textcolor{blue}{L^{CLIP}(\theta) = \hat{\mathbb{E}}_t \Big[ \min(r_t(\theta)\hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t) \Big]} PPO-clip:LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1ϵ,1+ϵ)A^t)]
其中 L C L I P L^{CLIP} LCLIP表示PPO-clip,截断操作是 clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) clip(rt(θ),1ϵ,1+ϵ)的操作为 max ⁡ ( min ⁡ ( r t ( θ ) , 1 + ϵ ) , 1 − ϵ ) \max(\min(r_t(\theta), 1+\epsilon), 1-\epsilon) max(min(rt(θ),1+ϵ),1ϵ),即让 r t ( θ ) ∈ [ 1 − ϵ , 1 + ϵ ] r_t(\theta)\in[1-\epsilon, 1+\epsilon] rt(θ)[1ϵ,1+ϵ]

clip操作的解释如下所示

Image

3. PPO(Penalty)

​ PPO penalty版本的改进非常简单,就是在TRPO的基础上,将TRPO中的约束条件作为惩罚项(penalty)加入到目标函数中,并且使用一个自适应的学习率来更新惩罚项。数学的表示即为

PPO-penalty: L K L P E N ( θ ) = E ^ t [ π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) A ^ t − β D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] \text{PPO-penalty:} \quad \textcolor{blue}{L^{KLPEN}(\theta) = \hat{\mathbb{E}}_t \Big[ \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}\hat{A}_t - \beta \mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big) \Big]} PPO-penalty:LKLPEN(θ)=E^t[πθold(atst)πθ(atst)A^tβDKL(πθold(st),πθ(st))]

其中 β \beta β按照以下方式进行更新

d = E ^ t [ D K L ( π θ old ( ⋅ ∣ s t ) , π θ ( ⋅ ∣ s t ) ) ] If  d < d t a r g / 1.5 , β ← β / 2 If  d > d t a r g × 1.5 , β ← β × 2 \begin{aligned} d & = \hat{\mathbb{E}}_t[\mathbb{D}_{KL} \big(\pi_{\theta_{\text{old}}}(\cdot|s_t),\pi_\theta(\cdot|s_t) \big)] \\ \text{If } d & < d_{targ} / 1.5, \beta \leftarrow \beta/2 \\ \text{If } d & > d_{targ} \times 1.5, \beta \leftarrow \beta \times 2 \end{aligned} dIf dIf d=E^t[DKL(πθold(st),πθ(st))]<dtarg/1.5,ββ/2>dtarg×1.5,ββ×2

原论文中指出,经过大量实验表明,PPO-clip的效果总是要优于PPO-penalty。

4. PPO中Advantage Function的计算

Advantage function优势函数的定义是

A ^ t = Q ( s t , a t ) − V ( s t ) \hat{A}_t = Q(s_t,a_t) - V(s_t) A^t=Q(st,at)V(st)

当我们使用类似n-step Sarsa的方式来估计 Q ( s t , a t ) Q(s_t,a_t) Q(st,at)时,那么优势函数就变为

A ^ t = r t + γ r t + 1 + ⋯ + γ T − t + 1 r T − 1 + γ T − t V ( s T ) − V ( s t ) \hat{A}_t = r_t + \gamma r_{t+1} + \cdots + \gamma^{T-t+1}r_{T-1} + \gamma^{T-t}V(s_T) - V(s_t) A^t=rt+γrt+1++γTt+1rT1+γTtV(sT)V(st)

为了方便使用,我们可以将上式再写成truncated fashion截断的形式

A ^ t = δ t + ( γ λ ) δ t + 1 + ⋯ + ( γ λ ) T − t + 1 δ T − 1 where  δ t = r t + γ V ( s t + 1 ) − V ( s t ) \begin{aligned} &\textcolor{red}{\hat{A}_t = \delta_t + (\gamma\lambda)\delta_{t+1} + \cdots + (\gamma\lambda)^{T-t+1}\delta_{T-1}} \\ \text{where } & \textcolor{red}{\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)} \end{aligned} where A^t=δt+(γλ)δt+1++(γλ)Tt+1δT1δt=rt+γV(st+1)V(st)

Pesudocode

Image

5.实现 PPO-Clip

import torch
import torch.nn as nn
import torch.nn.functional as F
from tqdm import tqdm
import gym
import numpy as np
import matplotlib.pyplot as plt# Policy Network
class PolicyNet(nn.Module):def __init__(self, state_dim, hidden_dim, action_dim):super(PolicyNet, self).__init__()self.fc1 = nn.Linear(in_features=state_dim, out_features=hidden_dim)self.fc2 = nn.Linear(in_features=hidden_dim, out_features=action_dim)def forward(self, observation):x = F.relu(self.fc1(observation))x = F.softmax(self.fc2(x), dim=1)return x# State Value Network
class ValueNet(nn.Module):def __init__(self, state_dim, hidden_dim):super(ValueNet, self).__init__()self.fc1 = nn.Linear(in_features=state_dim, out_features=hidden_dim)self.fc2 = nn.Linear(in_features=hidden_dim, out_features=1)def forward(self, observation):x = F.relu(self.fc1(observation))return self.fc2(x)# PPO Clip Algorithm
class PPO_clip():def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, lmbda, epochs, eps, gamma, device):self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device)self.critic = ValueNet(state_dim, hidden_dim).to(device)self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)self.device = deviceself.lmbda = lmbdaself.gamma = gammaself.epochs = epochs    # 一条序列的数据用来训练的轮数self.eps = eps          # PPO中截断范围的参数def calculate_advantage(self, td_delta):# compute advantage over td_deltatd_delta = td_delta.detach().numpy()temp = 0advantage_list = []for td in td_delta[::-1]:temp = temp * self.gamma * self.lmbda + tdadvantage_list.append(temp)advantage = torch.tensor(np.array(advantage_list[::-1]), dtype=torch.float)return advantage.to(self.device)def choose_action(self, state):state = torch.tensor([state], dtype=torch.float).to(self.device)probs = self.actor(state)action_dist = torch.distributions.Categorical(probs)action = action_dist.sample().item()return actiondef learn(self, transition_dict):states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)actions = torch.tensor(transition_dict['actions'], dtype=torch.int64).view(-1,1).to(self.device)rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1,1).to(self.device)next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1,1).to(self.device)td_target = rewards + self.gamma * self.critic(next_states) * (1-dones)td_delta = td_target - self.critic(states)advantage = self.calculate_advantage(td_delta.cpu())old_log_probs = torch.log(self.actor(states).gather(dim=1, index=actions)).detach()for _ in range(self.epochs):log_probs = torch.log(self.actor(states).gather(dim=1, index=actions))ratio = torch.exp(log_probs - old_log_probs)surr1 = ratio * advantagesurr2 = torch.clamp(ratio, 1-self.eps, 1+self.eps) * advantageactor_loss = torch.mean(-torch.min(surr1, surr2))critic_loss = torch.mean(F.mse_loss(td_target.detach(), self.critic(states)))self.actor_optimizer.zero_grad()self.critic_optimizer.zero_grad()actor_loss.backward()critic_loss.backward()self.actor_optimizer.step()self.critic_optimizer.step()def train_on_policy_agent(env, agent, num_episodes, render, seed_number):return_list = []for i in range(10):with tqdm(total=int(num_episodes/10), desc="Iteration:%d"%(i+1)) as pbar:for i_episode in range(int(num_episodes/10)):episode_return = 0done = Falsetransition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}observation, _ = env.reset(seed=seed_number)while not done:if render:env.render()action = agent.choose_action(observation)observation_, reward, terminated, truncated, _ = env.step(action)done = terminated or truncated# save one episode experience into a dicttransition_dict['states'].append(observation)transition_dict['actions'].append(action)transition_dict['rewards'].append(reward)transition_dict['next_states'].append(observation_)transition_dict['dones'].append(done)# swap stateobservation = observation_# compute one episode returnepisode_return += rewardreturn_list.append(episode_return)# agent learningagent.learn(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)env.close()return return_listdef moving_average(a, window_size):cumulative_sum = np.cumsum(np.insert(a, 0, 0)) middle = (cumulative_sum[window_size:] - cumulative_sum[:-window_size]) / window_sizer = np.arange(1, window_size-1, 2)begin = np.cumsum(a[:window_size-1])[::2] / rend = (np.cumsum(a[:-window_size:-1])[::2] / r)[::-1]return np.concatenate((begin, middle, end))def plot_curve(return_list, mv_return, algorithm_name, env_name):episodes_list = list(range(len(return_list)))plt.plot(episodes_list, return_list, c='gray', alpha=0.6)plt.plot(episodes_list, mv_return)plt.xlabel('Episodes')plt.ylabel('Returns')plt.title('{} on {}'.format(algorithm_name, env_name))plt.show()if __name__ == "__main__":# reproducibleseed_number = 0np.random.seed(seed_number)torch.manual_seed(seed_number)num_episodes = 1000     # episodes lengthhidden_dim = 256        # hidden layers dimensiongamma = 0.98            # discounted ratelmbda = 0.95            # coefficient for computing advantage epochs = 10             # update parameters intervaleps = 0.2               # clip coefficientactor_lr = 1e-3         # lr of actorcritic_lr = 1e-2        # lr of criticdevice = torch.device('cuda' if torch.cuda.is_available() else 'gpu')env_name = 'CartPole-v1'render = Falseif render:env = gym.make(id=env_name, render_mode='human')else:env = gym.make(id=env_name)state_dim = env.observation_space.shape[0]action_dim = env.action_space.nagent = PPO_clip(state_dim, hidden_dim, action_dim, actor_lr, critic_lr, lmbda, epochs, eps, gamma, device)return_list = train_on_policy_agent(env, agent, num_episodes, render, seed_number)mv_return = moving_average(return_list, 9)plot_curve(return_list, mv_return, 'PPO-Clip', env_name)

最后的训练回报(return)曲线如下

Image

Reference

Tutorial: Hands on RL

Paper: Proximal Policy Optimization Algorithm

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

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

相关文章

ubuntu python虚拟环境venv搭配systemd服务实战(禁用缓存下载--no-cache-dir)

文章目录 参考文章目录结构步骤安装venv查看python版本创建虚拟环境激活虚拟环境运行我们程序看缺少哪些依赖库&#xff0c;依次安装它们&#xff08;禁用缓存下载--no-cache-dir&#xff09;接下来我们配置python程序启动脚本&#xff0c;脚本中启动python程序前需先激活虚拟环…

【C++】开源:gflags命令行参数解析库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍gflags命令行参数解析库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

【数字图像处理】数字图像处理中的直方图相关操作

文章目录 前言一、直方图为什么可以进行图像处理&#xff1f;二、直方图处理怎么实现&#xff1f;直方图均衡化直方图匹配-规定化局部直方图处理直方图统计量增强图像 三、OpenCv提供的直方图基础操作直方图均衡化OpenCv中直方图的表示从数据创建直方图&#xff1a;cv::calcHis…

【云原生】Docker 详解(一):从虚拟机到容器

Docker 详解&#xff08;一&#xff09;&#xff1a;从虚拟机到容器 1.虚拟化 要解释清楚 Docker&#xff0c;首先要解释清楚 容器&#xff08;Container&#xff09;的概念。要解释容器的话&#xff0c;就需要从操作系统说起。操作系统太底层&#xff0c;细说的话一两本书都说…

Linux学习之awk函数

awk里边的函数分为内置函数和自定义函数。 内置函数有下边的几种&#xff1a; 算术函数&#xff08;arithmetic&#xff09; 字符串函数&#xff08;string&#xff09; 输入/输出函数和通用函数&#xff08;input/output, and general&#xff09; 自定义函数格式如下&#xf…

Oracle 使用 CONNECT_BY_ROOT 解锁层次结构洞察:在 SQL 中导航数据关系

CONNECT_BY_ROOT 是一个在 Oracle 数据库中使用的特殊函数&#xff0c;它通常用于在层次查询中获取根节点的值。在使用 CONNECT BY 子句进行层次查询时&#xff0c;通过 CONNECT_BY_ROOT 函数&#xff0c;你可以在每一行中获取根节点的值&#xff0c;而不仅仅是当前行的值。 假…

算法竞赛备赛之搜索与图论训练提升,暑期集训营培训

目录 1.DFS和BFS 1.1.DFS深度优先搜索 1.2.BFS广度优先搜索 2.树与图的遍历&#xff1a;拓扑排序 3.最短路 3.1.迪杰斯特拉算法 3.2.贝尔曼算法 3.3.SPFA算法 3.4.多源汇最短路Floy算法 4.最小生成树 4.1.普利姆算法 4.2.克鲁斯卡尔算法 5.二分图&#xff1a;染色法…

浅析kubernetes部署:javashop部署概览

javashop部署概览 节点规划 首先我们对节点进行规划&#xff0c;方便起见&#xff0c;我们进行如下简单的规划&#xff1a; 这里请根据您的实际情况进行合理的资源安排&#xff0c;或和我们售后工程师讨论形成方案。 域名规划 我们以test.com为主域名规划我们的系统域名如下&…

Jpa与Druid线程池及Spring Boot整合(一): spring-boot-starter-data-jpa 搭建持久层

Jpa与Druid线程池及Spring Boot整合(一) Jpa与Druid线程池及Spring Boot整合(二)&#xff1a;几个坑 附录官网文档&#xff1a;core.domain-events域事件 (一)Jpa与Druid连接池及Spring Boot整合作为持久层,遇到系列问题,下面一 一记录&#xff1a; pom.xml 文件中加入必须的…

Linux之【进程间通信(IPC)】-总结篇

Linux之【进程间通信&#xff08;IPC&#xff09;】-总结篇 管道System V共享内存System V消息队列System V信号量IPC资源的管理方式 往期文章 1.进程间通信之管道 2.进程间通信之System V共享内存 管道 进程之间具有独立性&#xff0c;拥有自己的虚拟地址空间&#xff0c;因…

张驰咨询:提高企业竞争力,六西格玛设计公司(DFSS)在行动

六西格玛设计公司(DFSS)是一种专业从事六西格玛设计的企业&#xff0c;其主要作用是为客户提供高效的六西格玛设计服务&#xff0c;以帮助客户实现高品质、低成本和高效率的产品开发过程。六西格玛设计公司通常拥有一支专业的团队&#xff0c;具有丰富的六西格玛设计经验和技术…

Jupyter并发测试以后出现EOFError marshal data too short

Jupyter 并发测试以后出现EOFError: marshal data too short 背景 由于项目需求需要用户能进行网页在线运行python代码程序&#xff0c;调研后决定使用Jupyter的服务接口实现此功能&#xff0c;目前使用docker进行容器化部署&#xff0c;测试针对次服务进行并发测试。测试并发…

FL Studio for Windows-21.1.0.3713中文直装版功能介绍及系统配置要求

FL Studio 21简称FL水果软件,全称是&#xff1a;Fruity Loops Studio编曲&#xff0c;由于其Logo长的比较像一款水果因此&#xff0c;在大家更多的是喜欢称他为水果萝卜&#xff0c;FL studio21是目前最新的版本&#xff0c;这是一款可以让你的计算机就像是一个全功能的录音室&…

RabbitMQ:可靠消息传递的强大消息中间件

消息中间件在现代分布式系统中起着关键作用&#xff0c;它们提供了一种可靠且高效的方法来进行异步通信和解耦。在这篇博客中&#xff0c;我们将重点介绍 RabbitMQ&#xff0c;一个广泛使用的开源消息中间件。我们将深入探讨 RabbitMQ 的特性、工作原理以及如何在应用程序中使用…

Codeforces Round 891 (Div. 3)ABC

Codeforces Round 891 (Div. 3) 目录 A. Array Coloring题目大意思路代码 B. Maximum Rounding题目大意思路代码 C. Assembly via Minimums题目大意思路代码 A. Array Coloring 题目大意 给你一个包含 n n n个数字的数组&#xff0c;你的任务是判断这个数组是否可以划分成两个…

换架 3D 飞机,继续飞呀飞

相信大多数图扑 HT 用户都曾见过这个飞机的 Demo&#xff0c;在图扑发展的这十年&#xff0c;这个 Demo 是许多学习 HT 用户一定会参考的经典 Demo 之一。 这个 Demo 用简洁的代码生动地展示了 OBJ 模型加载、数据绑定、动画和漫游等功能的实现。许多用户参考这个简单的 Demo 后…

MySQL中用什么数据类型存IP地址

提到IP地址(IPv4)&#xff0c;我们脑子里肯定立马浮现类似于192.168.0.1、127.0.0.1这种常见的IP地址&#xff0c;然后结合这个问题“MySQL中用什么数据类型存IP地址&#xff1f;”&#xff0c;于是乎脱口而出用char字符串类型存储。 然后再仔细想想发现&#xff0c;这个IP地址…

腾讯云标准型CVM云服务器详细介绍

腾讯云CVM服务器标准型实例的各项性能参数平衡&#xff0c;标准型云服务器适用于大多数常规业务&#xff0c;例如&#xff1a;web网站及中间件等&#xff0c;常见的标准型云服务器有CVM标准型S5、S6、SA3、SR1、S5se等规格&#xff0c;腾讯云服务器网来详细说下云服务器CVM标准…

页面切换后,滚动栏问题

项目场景&#xff1a; 提示&#xff1a;react项目antd后台管理系统 问题描述 后台管理系统从a页面进入b页面&#xff0c;a页面有数据&#xff08;有滚动条&#xff0c;且scollTop大于0&#xff09;&#xff0c;进入b页面后&#xff0c;滚动条不是位于初始位置&#xff08;scol…

Opencv4基于C++基础入门笔记:图像 颜色 事件响应 图形 视频 直方图

效果图◕‿◕✌✌✌&#xff1a;opencv人脸识别效果图(请叫我真爱粉) 先看一下效果图勾起你的兴趣&#xff01; 文章目录&#xff1a; 一&#xff1a;环境配置搭建 二&#xff1a;图像 1.图像读取与显示 main.cpp 运行结果 2.图像色彩空间转换 2.1 换色彩 test.h …