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

文章目录

  • 前言
    • 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.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHP 人才招聘管理系统mysql数据库web结构layUI布局apache计算机软件工程网页wamp

一、源码特点 PHP 人才招聘管理系统是一套完善的web设计系统 layUI技术布局 &#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 php人才招聘管理系统 代码 https://download.csdn.net/download/qq_4…

软件开发全文档归档,开发、管理、实施、运维、服务巡检、信息安全、安全运维

在当今高度信息化的时代&#xff0c;软件开发已成为推动社会进步和发展的重要力量。软件开发过程中&#xff0c;文件支撑作为关键的一环&#xff0c;对于保障项目的顺利进行和产品的质量具有不可替代的作用。本文将探讨软件开发所需的主要文件及其作用。 一、引言 软件开发是…

VR博物馆:让博物馆传播转化为品牌影响力

随着VR技术的不断进步&#xff0c;VR全景技术已经成为了文化展示和传播的一项重要工具&#xff0c;相较于传统视频、图文等展现方式&#xff0c;VR全景体验更加直观、便捷&#xff0c;其中蕴涵的信息量也更加丰富&#xff0c;这也为公众了解博物馆和历史文化带来了更为深刻的体…

802.11AX基础---走进HE WLAN

1、WiFi 6 是什么&#xff1f; WiFi 6是IEEE802.11ax的简称&#xff0c;也就是第六代WiFi的标准&#xff1b;它在继承前几代WiFi技术的前提下&#xff0c;不仅对速率进行优化&#xff0c;更着重于对 效率 的提升。 2、WiFi 6 为什么快&#xff1f; WiFi 6 理论速率计算公式&a…

【Midjourney入门教程3】写好prompt常用的参数

文章目录 1、图片描述词&#xff08;图片链接&#xff09;文字描述词后缀参数2、权重划分3、后缀参数版本选择&#xff1a;--v版本风格&#xff1a;--style长宽比&#xff1a;--ar多样性: --c二次元化&#xff1a;--niji排除内容&#xff1a;--no--stylize--seed--tile、--q 4、…

使用Python 脚自动化操作服务器配置

“ 有几十台特殊的服务器&#xff0c;没有合适的批量工具只能手动&#xff0c;要一个一个进行点击设置很耗费时间呀\~”,使用 Python 的简单脚本&#xff0c;即可模拟鼠标键盘进行批量作业 01 — 自动化示例 以某服务器中的添加用户权限为例&#xff0c;演示过程皆未触碰鼠标…

2022年09月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 十六进制数100&#xff0c;对应的十进制数为 &#xff1f;&#xff08; &#xff09; A: 128 B: 256 C: 28 D: 56 答…

如何设置OBS虚拟摄像头给钉钉视频会议使用

环境&#xff1a; OBS Studio 29.1.3 Win10 专业版 钉钉7.1.0 问题描述&#xff1a; 如何设置OBS虚拟摄像头给钉钉视频会议使用 解决方案&#xff1a; 1.打开OBS 底下来源这添加视频采集设备 选择OBS虚拟摄像头 2.源那再建一个图像&#xff0c;随便选一张图片 3.点击虚…

【DriveGPT学习笔记】自动驾驶汽车Autonomous Vehicle Planning

原文地址&#xff1a;DriveGPT - Lei Maos Log Book 自动驾驶汽车的核心软件组件是感知、规划和控制。规划是指在给定场景或一系列场景的情况下为自动驾驶汽车制定行动计划的过程&#xff0c;以实现安全和理想的自动驾驶。 用于规划的场景是从感知软件组件获得的。计划的行动将…

Windows Server 2016使用MBR2GPT.EXE教程!

什么是MBR2GPT.exe&#xff1f; MBR2GPT.exe是微软提供的专业工具&#xff0c;可在命令提示符下运行。使用该工具可以将引导磁盘从MBR转换为GPT分区样式&#xff0c;而无需修改或删除所选磁盘上的任何内容。 在Windows Server 2019和Windows 10&#xff08;1703…

pytorch+LSTM实现使用单参数预测,以及多参数预测(代码注释版)

开发前准备&#xff1a; 环境管理&#xff1a;Anaconda python: 3.8 显卡&#xff1a;NVIDIA3060 pytorch: 到官网选择conda版本&#xff0c;使用的是CUDA11.8 编译器&#xff1a; PyCharm 简述&#xff1a; 本次使用seaborn库中的flights数据集来做试验&#xff0c;我们通过…

c语言经典算法—二分查找,冒泡,选择,插入,归并,快排,堆排

一、二分查找 1、前提条件&#xff1a;数据有序&#xff0c;随机访问&#xff1b; 2、实现&#xff1a;递归实现&#xff0c;非递归实现 3、注意事项&#xff1a; 循环退出条件:low <high,low high.说明还有一个元素&#xff0c;该元素还要与key进行比较 mid的取值&#xf…

Excel文档名称批量翻译的高效方法

在处理大量文件时&#xff0c;我们常常需要借助一些工具来提高工作效率。例如&#xff0c;在需要对Excel文档名称进行批量翻译时&#xff0c;一个方便快捷的工具可以帮助我们省去很多麻烦。今天&#xff0c;我将介绍一款名为固乔文件管家的软件&#xff0c;它能够帮助我们轻松实…

hackergame2023菜菜WP

文章目录 总结Hackergame2023更深更暗组委会模拟器猫咪小测标题HTTP集邮册Docker for everyone惜字如金 2.0Git? Git!高频率星球低带宽星球小型大语言模型星球旅行日记3.0JSON ⊂ YAML? 总结 最近看到科大在举办CTF比赛&#xff0c;刚好我学校也有可以参加&#xff0c;就玩了…

Pytorch网络模型训练

现有网络模型的使用与修改 vgg16_false torchvision.models.vgg16(pretrainedFalse) # 加载一个未预训练的模型 vgg16_true torchvision.models.vgg16(pretrainedTrue) # 把数据分为了1000个类别print(vgg16_true) 以下是vgg16预训练模型的输出 VGG((features): S…

论文浅尝 | ChatKBQA:基于微调大语言模型的知识图谱问答框架

第一作者&#xff1a;罗浩然&#xff0c;北京邮电大学博士研究生&#xff0c;研究方向为知识图谱与大语言模型协同推理 OpenKG地址&#xff1a;http://openkg.cn/tool/bupt-chatkbqa GitHub地址&#xff1a;https://github.com/LHRLAB/ChatKBQA 论文链接&#xff1a;https://ar…

小程序如何设置用户同意服务协议并上传头像和昵称

为了保护用户权益和提供更好的用户体验&#xff0c;设置一些必填项和必读协议是非常必要的。首先&#xff0c;用户必须阅读服务协议。服务协议是明确规定用户和商家之间权益和义务的文件。通过要求用户在下单前必须同意协议&#xff0c;可以确保用户在使用服务之前了解并同意相…

Android studio新版本多渠道打包配置

最近公司套壳app比较多 功能也都一样只有地址&#xff0c;和app名字还有icon不一样 签名文件也是一样的,所以就研究了多渠道打包 配置如下&#xff1a; 在app下build.gradle配置 因为最新版as中禁用了BuildConfig 所以我们需要手动配置一下 android { //TODO 其他省略buildFe…

3 函数的升级-上

常量与宏回顾 C中的const常量可以替代常数定义&#xff0c;如: "Const int a 8; --> 等价于 #define a 8 " 宏在预编译阶段处理&#xff0c;而c const常量则在编译阶段处理&#xff0c;比宏 更为安全。 C中&#xff0c;我们可以用宏代码片段去实现某个函数&…

0006Java安卓程序设计-ssm基于Android的校园二手商品交易平台

文章目录 **摘** **要****目** **录**系统设计开发环境 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 摘 要 随着毕业季的来临以及当代大学生的消费力购买力的不断增强&#xff0c;我们的寝室中囤积了很多二手商品&#xff0c;有很多是…