【强化学习】16 ——PPO(Proximal Policy Optimization)

news2024/11/24 13:53:40

文章目录

  • 前言
    • TRPO的不足
    • PPO特点
  • PPO-惩罚
  • PPO-截断
  • 优势函数估计
  • 算法伪代码
  • PPO 代码实践
  • 参考

前言

TRPO 算法在很多场景上的应用都很成功,但是我们也发现它的计算过程非常复杂,每一步更新的运算量非常大。于是,TRPO 算法的改进版——PPO 算法在 2017 年被提出,PPO 基于 TRPO 的思想,但是其算法实现更加简单。并且大量的实验结果表明,与 TRPO 相比,PPO 能学习得一样好(甚至更快),这使得 PPO 成为非常流行的强化学习算法。

TRPO的不足

回顾一下TRPO算法 θ ′ ← arg ⁡ max ⁡ θ ′ ∑ t E s t ∼ p θ ( s t ) [ E a t ∼ π θ ( a t ∣ s t ) [ π θ ′ ( a t ∣ s t ) π θ ( a t ∣ s t ) γ t A π θ ( s t , a t ) ] ] s u c h   t h a t   E s t ∼ p ( s t ) [ D K L ( π θ ′ ( a t ∣ s t ) ∥ π θ ( a t ∣ s t ) ) ] ≤ ϵ \begin{aligned} &\theta'\leftarrow\arg\max_{\theta'}\sum_t\mathbb{E}_{s_t\sim p_\theta(s_t)}[\mathbb{E}_{a_t\sim\pi_\theta(a_t|s_t)}[\frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)}\gamma^tA^{\pi_\theta}(s_t,a_t)]] \\ &\mathrm{such~that~}\mathbb{E}_{s_t\sim p(s_t)}[D_{KL}(\pi_{\theta^{\prime}}(a_t|s_t)\parallel\pi_\theta(a_t|s_t))]\leq\epsilon \end{aligned} θargθmaxtEstpθ(st)[Eatπθ(atst)[πθ(atst)πθ(atst)γtAπθ(st,at)]]such that Estp(st)[DKL(πθ(atst)πθ(atst))]ϵ
TRPO算法主要存在以下不足:

  • 第一个问题是重要性采样的通病,重要性采样中的比例 π θ ′ ( a t ∣ s t ) π θ ( a t ∣ s t ) \frac{\pi_{\theta'}(a_t|s_t)}{\pi_\theta(a_t|s_t)} πθ(atst)πθ(atst)会带来较大的方差。
  • 求解约束优化问题比较困难
  • 近似求解会带来误差

TRPO 使用泰勒展开近似、共轭梯度、线性搜索等方法直接求解。PPO 的优化目标与 TRPO 相同,但 PPO 用了一些相对简单的方法来求解。具体来说,PPO 有两种形式,一是 PPO-惩罚(PPO-Penalty),二是 PPO-截断(PPO-Clip),我们接下来对这两种形式进行介绍。

PPO特点

  • 是一种on-policy的算法
  • 可以用于连续或离散的动作空间
  • Open AI Spinning Up 中的PPO可以达到并行运行的效果

PPO-惩罚

PPO-惩罚(PPO-Penalty)用拉格朗日乘数法直接将 KL 散度的限制放进了目标函数中,这就变成了一个无约束的优化问题,在迭代的过程中不断更新 KL 散度前的系数。即: arg ⁡ max ⁡ θ E s ∼ ν E a ∼ π θ k ( ⋅ ∣ s ) [ π θ ( a ∣ s ) π θ k ( a ∣ s ) A π θ k ( s , a ) − β D K L [ π θ k ( ⋅ ∣ s ) , π θ ( ⋅ ∣ s ) ] ] \arg\max_{\theta}\mathbb{E}_{s\sim\nu}\mathbb{E}_{a\sim\pi_{\theta_k}(\cdot|s)}\left[\frac{\pi_\theta(a|s)}{\pi_{\theta_k}(a|s)}A^{\pi_{\theta_k}}(s,a)-\beta D_{KL}[\pi_{\theta_k}(\cdot|s),\pi_\theta(\cdot|s)]\right] argθmaxEsνEaπθk(s)[πθk(as)πθ(as)Aπθk(s,a)βDKL[πθk(s),πθ(s)]]

d k = D K L ν π θ k ( π θ k , π θ ) d_k=D_{KL}^{\nu^{\pi_{\theta_k}}}\left(\pi_{\theta_k},\pi_{\theta}\right) dk=DKLνπθk(πθk,πθ),则 β \beta β的更新规则如下:

  • 如果 d k < δ / 1.5 d_k<\delta/1.5 dk<δ/1.5,那么 β k + 1 = β k / 2 \beta_{k+1}=\beta_k/2 βk+1=βk/2
  • 如果 d k > δ × 1.5 d_k>\delta\times1.5 dk>δ×1.5,那么 β k + 1 = β k × 2 \beta_{k+1}=\beta_k\times2 βk+1=βk×2
  • 否则 β k + 1 = β k \beta_{k+1}=\beta_k βk+1=βk

其中, δ \delta δ是事先设定的一个超参数,用于限制学习策略和之前一轮策略的差距。另外,这里1.5和2是经验参数,算法效能和它们并不是很敏感。

PPO-截断

PPO 的另一种形式 PPO-截断(PPO-Clip)更加直接,它在目标函数中进行限制,以保证新的参数和旧的参数的差距不会太大。TRPO的优化目标如下: L C P I ( θ ) = E ^ t [ π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) A ^ t ] = E ^ t [ r t ( θ ) A ^ t ] . \begin{aligned}L^{CPI}(\theta)&=\hat{\mathbb{E}}_t\bigg[\frac{\pi_\theta(a_t\mid s_t)}{\pi_{\theta_{\mathrm{old}}}(a_t\mid s_t)}\hat{A}_t\bigg]=\hat{\mathbb{E}}_t\bigg[r_t(\theta)\hat{A}_t\bigg].\end{aligned} LCPI(θ)=E^t[πθold(atst)πθ(atst)A^t]=E^t[rt(θ)A^t]. L C L I P ( θ ) = E ^ t [ min ⁡ ( r t ( θ ) A ^ t , clip ⁡ ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] \begin{aligned}L^{CLIP}(\theta)&=\hat{\mathbb{E}}_t\Big[\min(r_t(\theta)\hat{A}_t,\operatorname{clip}(r_t(\theta),1-\epsilon,1+\epsilon)\hat{A}_t)\Big]\end{aligned} LCLIP(θ)=E^t[min(rt(θ)A^t,clip(rt(θ),1ϵ,1+ϵ)A^t)]

其中, C P I CPI CPI代表了保守的策略迭代(conservative policy iteration)。新旧策略的采样比例为 r t ( θ )   =   π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) r_t(\theta)~=~\frac{\pi_\theta(a_t\mid s_t)}{\pi_{\theta_{\mathrm{old}}}(a_t\mid s_t)} rt(θ) = πθold(atst)πθ(atst) clip ⁡ ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) \operatorname{clip}(r_t(\theta),1-\epsilon,1+\epsilon) clip(rt(θ),1ϵ,1+ϵ)表示对比例 r t ( θ ) r_t(\theta) rt(θ)截断到 [ 1 − ϵ , 1 + ϵ ] [1-\epsilon,1+\epsilon] [1ϵ,1+ϵ]之内, ϵ \epsilon ϵ为超参数,表示进行截断(clip)的范围。最后 L C L I P ( θ ) L^{CLIP}(\theta) LCLIP(θ)对未被截断和截断部分取最小值,因此,可以剔除比例方差带来的不良影响。当 r t ( θ ) = 1 r_t(\theta)=1 rt(θ)=1时, L C L I P ( θ ) = L C P I ( θ ) L^{CLIP}(\theta)=L^{CPI}(\theta) LCLIP(θ)=LCPI(θ)。如下图所示,若 A > 0 A>0 A>0,说明这个动作的价值高于平均,因此会提升其比例,但不会超过 1 + ϵ 1+\epsilon 1+ϵ;若 A < 0 A<0 A<0,说明这个动作的价值低于平均,因此会降低其比例,但不会低于 1 − ϵ 1-\epsilon 1ϵ
在这里插入图片描述

下图是一个更为直观的表示:
在这里插入图片描述

优势函数估计

大多数计算方差减小的优势函数的估计方法都会利用学习到的状态值函数 V ( s ) V(s) V(s),如广义优势估计(generalized advantage estimation)或有限视野估计(finite-horizon estimators)。如果使用在策略和值函数之间共享参数的神经网络架构,我们必须使用结合策略替代和值函数误差项的损失函数,通过在目标中添加熵奖励( entropy bonus)来确保充分的探索可以来有效解决这一问题。综上,在每次迭代中所使用的目标函数为: L t C L I P + V F + S ( θ ) = E ^ t [ L t C L I P ( θ ) − c 1 L t V F ( θ ) + c 2 S [ π θ ] ( s t ) ] , L_t^{CLIP+VF+S}(\theta)=\hat{\mathbb{E}}_t\big[L_t^{CLIP}(\theta)-c_1L_t^{VF}(\theta)+c_2S[\pi_\theta](s_t)\big], LtCLIP+VF+S(θ)=E^t[LtCLIP(θ)c1LtVF(θ)+c2S[πθ](st)],

其中, c 1 , c 2 c_1,c_2 c1,c2是系数, S S S为熵奖励, L t V F L_t^{VF} LtVF是平方差 ( V θ ( s t ) − V t t a r g ) 2 \left(V_\theta(s_t)-V_t^\mathrm{targ}\right)^2 (Vθ(st)Vttarg)2

对于优势函数估计,PPO采用多( T T T)步时序差分 A t ^ = − V ( s t ) + r t + γ r t + 1 + ⋯ + γ T − t + 1 r T − 1 + γ T − t V ( s T ) \hat{A_t}=-V(s_t)+r_t+\gamma r_{t+1}+\cdots+\gamma^{T-t+1}r_{T-1}+\gamma^{T-t}V(s_T) At^=V(st)+rt+γrt+1++γTt+1rT1+γTtV(sT)PPO采用的方法是GAE方法的截断版本(当 λ = 1 \lambda=1 λ=1时,就和上式相等)。
A ^ t = δ t + ( γ λ ) δ t + 1 + ⋯ + ⋯ + ( γ λ ) T − t + 1 δ T − 1 , w h e r e δ t = r t + γ V ( s t + 1 ) − V ( s t ) \begin{aligned}\hat A_t&=\delta_t+(\gamma\lambda)\delta_{t+1}+\cdots+\cdots+(\gamma\lambda)^{T-t+1}\delta_{T-1},\\\mathrm{where}\quad\delta_t&=r_t+\gamma V(s_{t+1})-V(s_t)\end{aligned} A^twhereδt=δt+(γλ)δt+1+++(γλ)Tt+1δT1,=rt+γV(st+1)V(st)

算法伪代码

在这里插入图片描述

  • 在每次迭代中,并行𝑁个actor收集𝑇步经验数据
  • 计算每步的 A t ^ \hat{A_t} At^ L C L I P L^{CLIP} LCLIP构成mini-batch(优化方法可用SGD或Adam)
  • 更新参数𝜃,并更新 θ o l d ← θ \theta_{old}\leftarrow\theta θoldθ

在这里插入图片描述

PPO 代码实践

大量实验表明,PPO-截断总是比 PPO-惩罚表现得更好。因此下面我们专注于 PPO-截断的代码实现。

import gymnasium as gym
import numpy as np
from tqdm import tqdm
import torch
import torch.nn.functional as F
import util

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)

# 输入是某个状态,输出则是状态的价值。
class ValueNet(torch.nn.Module):
    def __init__(self, state_dim, hidden_dim):
        super(ValueNet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, 1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        return self.fc2(x)

class PPO:
    def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, gamma,
                lambda_, clip_param, train_epochs, device, numOfEpisodes, env):
        self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
        self.critic = ValueNet(state_dim, hidden_dim).to(device)
        self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)
        self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
        self.gamma = gamma
        self.device = device
        self.env = env
        self.numOfEpisodes = numOfEpisodes
        self.lambda_ = lambda_
        # PPO中截断范围的参数
        self.clip_param = clip_param
        # 一条序列的数据用来训练轮数
        self.train_epochs = train_epochs

    def take_action(self, state):
        states = torch.FloatTensor(np.array([state])).to(self.device)
        probs = self.actor(states)
        action_dist = torch.distributions.Categorical(probs)
        action = action_dist.sample()
        return action.item()

    def cal_advantage(self, gamma, lambda_, td_delta):
        td_delta = td_delta.detach().numpy()
        advantages = []
        advantage = 0.0
        for delta in reversed(td_delta):
            advantage = gamma * lambda_ * advantage + delta
            advantages.append(advantage)
        advantages.reverse()
        return torch.FloatTensor(np.array(advantages))

    def update(self, transition_dict):
        states = torch.tensor(np.array(transition_dict['states']), dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).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(np.array(transition_dict['next_states']), dtype=torch.float).to(self.device)
        terminateds = torch.tensor(transition_dict['terminateds'], dtype=torch.float).view(-1, 1).to(self.device)
        truncateds = torch.tensor(transition_dict['truncateds'], dtype=torch.float).view(-1, 1).to(self.device)
        td_target = rewards + self.gamma * self.critic(next_states) * (1 - terminateds + truncateds)
        td_delta = td_target - self.critic(states)
        advantage = self.cal_advantage(self.gamma, self.lambda_, td_delta.cpu()).to(self.device)
        old_log_probs  = torch.log(self.actor(states).gather(1, actions)).detach()
        for _ in range(self.train_epochs):
            log_probs = torch.log(self.actor(states).gather(1, actions))
            ratio = torch.exp(log_probs - old_log_probs)
            L_CPI = ratio * advantage
            L_CLIP = torch.min(L_CPI, torch.clamp(ratio, 1 - self.clip_param, 1 + self.clip_param) * advantage)
            actor_loss = torch.mean(-L_CLIP)
            critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))
            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 PPOrun(self):
        returnList = []
        for i in range(10):
            with tqdm(total=int(self.numOfEpisodes / 10), desc='Iteration %d' % i) as pbar:
                for episode in range(int(self.numOfEpisodes / 10)):
                    # initialize state
                    state, info = self.env.reset()
                    terminated = False
                    truncated = False
                    episodeReward = 0
                    transition_dict = {
                        'states': [], 'actions': [], 'next_states': [], 'rewards': [], 'terminateds': [], 'truncateds': []}
                    # Loop for each step of episode:
                    while 1:
                        action = self.take_action(state)
                        next_state, reward, terminated, truncated, info = self.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['terminateds'].append(terminated)
                        transition_dict['truncateds'].append(truncated)
                        state = next_state
                        episodeReward += reward
                        if terminated or truncated:
                            break
                    self.update(transition_dict)
                    returnList.append(episodeReward)
                    if (episode + 1) % 10 == 0:  # 每10条序列打印一下这10条序列的平均回报
                        pbar.set_postfix({
                            'episode':
                                '%d' % (self.numOfEpisodes / 10 * i + episode + 1),
                            'return':
                                '%.3f' % np.mean(returnList[-10:])
                        })
                    pbar.update(1)
        return returnList

超参数参考设置:

    agent = PPO(state_dim=env.observation_space.shape[0],
                hidden_dim=256,
                action_dim=2,
                actor_lr=1e-3,
                critic_lr=1e-2,
                gamma=0.99,
                lambda_=0.95,
                clip_param=0.2,
                train_epochs=8,
                device=device,
                numOfEpisodes=1000,
                env=env)

结果:
在这里插入图片描述
可见,PPO算法收敛速度快,表现十分优秀。

参考

[1] 伯禹AI
[2] https://www.davidsilver.uk/teaching/
[3] 动手学强化学习
[4] Reinforcement Learning
[5] SCHULMAN J, FILIP W, DHARIWAL P, et al. Proximal policy optimization algorithms [J]. Machine Learning, 2017.

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

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

相关文章

【PyQt学习篇 · ⑪】:QPushButton和QCommandLinkButton的使用

文章目录 构造函数菜单设置扁平化默认处理右键菜单QCommandLinkButton的使用 构造函数 QPushButton的构造函数如下&#xff1a; """QPushButton(parent: Optional[QWidget] None)QPushButton(text: Optional[str], parent: Optional[QWidget] None)QPushButt…

基于动力学模型的机械臂pid控制

参考资料&#xff1a; 一、如何实现机械臂的控制 在最常见的对机械臂动力学实现控制的问题中&#xff0c;我们会有一段机械臂末端的期望轨迹S&#xff0c;希望通过对机械臂关节处电机转矩的控制实现末端沿期望轨迹的完美运动。控制问题主要分为镇定和跟踪两种&#xff0c;上面…

2023/11/4 JAVA学习

通过匿名内部类

verdi技巧分享--合并多个fsdb文件、统计信号边沿

文章目录 0 前言1 如何显示信号高位的02 统计信号的上升沿、下降沿3 合并信号4 将多个fsdb文件合并成一个 0 前言 分享几个这段时间学到的verdi操作 1 如何显示信号高位的0 这个可能对一些有强迫症的有帮助吧 nand相关的操作&#xff0c;有一些特定的cmd&#xff0c;比如 r…

什么是工分排队模式?看懂之后,又能学会一招拓客引流技巧?

什么是工分排队模式&#xff1f;看懂之后&#xff0c;又能学会一招拓客引流技巧&#xff1f; 背景&#xff1a;当下市场行情呈现出经济平稳快速增长的趋势&#xff0c;但同时也存在物价持续上升的情况。从经济角度来看&#xff0c;当前市场行情呈现出经济平稳快速增长的趋势。这…

职场被迫内卷,云认证破局

前言&#xff1a; 2023年作为疫情全面放开的第一年&#xff0c;经济并没有像22年底时我们想象的那样&#xff0c;快速复苏&#xff0c;GDP增长超10%。取而代之的是&#xff0c;2023年经济大环境对各个行业来说&#xff0c;相比22年显的更加艰难&#xff0c;GDP增长预计在5%左右…

Java数组的定义与常用使用方法

目录 一.什么是数组 二.数组的创建及初始化 数组的创建 数组的初始化 动态初始化&#xff1a; 静态初始化&#xff1a; 【注意】 三.数组的使用 数组中元素访问 遍历数组 四.数组作为方法的参数 参数传基本数据类型 参数传数组类型(引用数据类型) 作为方法的返回…

飞行器坐标转换

飞行器坐标转换 坐标系定义方向余弦矩阵 坐标系定义 本文定义的是右手直角坐标系&#xff0c; x − y − z x-y-z x−y−z轴分别为北-天-东。 从 A A A坐标系到 B B B坐标系是分别绕 y − z − x y-z-x y−z−x轴&#xff0c;即天-东-北旋转 ψ − θ − γ \psi-\theta-\gamm…

【深入理解指针5】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 1. sizeof和strlen的对比 1.1sizeof 1.2 strlen 1.3 sizeof 和 strlen的对比 2. 数组和指针笔试题解析 2.1 一维数组 2.2 字符数组 2.3 二维数组 3. 指针运算笔试题…

竞赛 深度学习疫情社交安全距离检测算法 - python opencv cnn

文章目录 0 前言1 课题背景2 实现效果3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习疫情社交安全距离检测算法 ** 该项目较为新颖&#xff0c;适合作为竞赛…

『亚马逊云科技产品测评』活动征文|在aws搭建游戏工作室的网盘

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 目录 前言 方案选择 基础环境准备 部署网盘 1、创建数据目录 2、编…

硬盘坏道检测修复工具下载,仅支持机械盘

硬盘坏道检测修复工具下载&#xff0c;仅支持机械盘 下载路径&#xff0c;最下方官网——软件下载——常用工具下载——硬盘坏道修复工具硬盘检测修复工具 【软件试用版下载、软件资讯或技术支持服务可点击文章最下方官网】

代码随想录算法训练营第23期day39 |62.不同路径、63. 不同路径 II

目录 一、&#xff08;leetcode 62&#xff09;不同路径 1.动态规划 1&#xff09;确定dp数组&#xff08;dp table&#xff09;以及下标的含义 2&#xff09;确定递推公式 3&#xff09;dp数组的初始化 4&#xff09;确定遍历顺序 5&#xff09;举例推导dp数组 2.数论方…

虚拟dom及diff算法之 —— h函数和diff函数

新虚拟dom和老虚拟dom进行diff算法&#xff08;精细化比较&#xff09;&#xff0c;算出如何最小量更新&#xff0c;最后反映到真实dom上 diff是发生在虚拟dom上的 模板编译 虚拟dom如何产生 - 渲染函数&#xff08;h函数&#xff09; h函数产生虚拟节点&#xff08;vnode&a…

YUV图像格式详解

1.概述 YUV是一种图像颜色编码方式。 相对于常见且直观的RGB颜色编码&#xff0c;YUV的产生自有其意义&#xff0c;它基于人眼对亮度比色彩的敏感度更高的特点&#xff0c;使用Y、U、V三个分量来表示颜色&#xff0c;并通过降低U、V分量的采样率&#xff0c;尽可能保证图像质…

linux 性能与内存分析工具

linux-tools 包含了一系列性能分析工具和调试工具&#xff0c;用于监视和分析 Linux 系统的性能、内核活动以及其他性能相关信息。具体包含的工具可能因不同的 Linux 发行版和版本而有所不同。以下是一些常见的工具&#xff0c;可能包含在 linux-tools 或相关的包中&#xff1a…

JVM内存结构说明

1. 整体结构图如下 2. 程序计数器 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;由于JVM可以并发执行线程&#xff0c;因此会存在线程之间的切换&#xff0c;而这个时候就程序计数器会记录下当前程序执行到的位置&#xff0c;以…

Spring Boot创建多模块项目

创建一个普通的Spring Boot项目, 然后只留下 pom.xml 剩下的都删掉 删除多余标签 标识当前为父模块 创建子模块 删除子模块中多余标签 声明父模块 在父模块中声明子模块

MTK联发科、高通、紫光展锐手机SOC平台型号汇总(含详细参数)

MediaTek联发科手机平台汇总&#xff1a; Qualcomm高通SOC平台汇总&#xff1a; 紫光展锐SOC平台汇总&#xff1a; 新移科技已成功研发手机SOC平台&#xff1a; 联发科平台&#xff1a; MTK6739、MTK6761、MTK6762、MTK6765、MTK8788、MTK6853、MTK6873、MTK6833、MTK6877、…

电脑实时屏幕监管软件怎么选择,安企神企业电脑监控软件

电脑实时屏幕监管软件怎么选择&#xff0c;安企神企业电脑监控软件 下载使用安企神电脑屏幕监控软件 企业为什么要用屏幕监控软件&#xff1a; 在现代企业生产管理中&#xff0c;尤其是互联网行业公司&#xff0c;公司电脑里保存着重要信息&#xff0c;像企业信息、财务数据…