强化学习_06_pytorch-PPO2实践(Humanoid-v4)

一、PPO优化

PPO的简介和实践可以看笔者之前的文章 强化学习_06_pytorch-PPO实践(Pendulum-v1)
针对之前的PPO做了主要以下优化:

-笔者-PPO笔者-PPO2ref
data collectone episodeseveral episode(one batch)
activationReLUTanh
adv-compute-compute adv as one serires
adv-normalizemini-batch normalizeservel envs-batch normalize影响PPO算法性能的10个关键技巧
Value Function Loss Clipping- L V = m a x [ ( V θ t − V t a r ) 2 , ( c l i p ( V θ t , V θ t − 1 − ϵ , V θ t − 1 + ϵ ) ) 2 ] L^{V}=max[(V_{\theta_t} - V_{tar})^2, (clip(V_{\theta_t}, V_{\theta_{t-1}}-\epsilon, V_{\theta_{t-1}}+\epsilon))^2] LV=max[(VθtVtar)2,(clip(Vθt,Vθt1ϵ,Vθt1+ϵ))2]The 37 Implementation Details of Proximal Policy Optimization
optimizeractor-opt & critic-optuse common opt
lossactor-loss-backward & critic-loss-backwardloss weight sum
paramate-init-1- hidden layer orthogonal initialization of weights 2 \sqrt{2} 2 ; 2- The policy output layer weights are initialized with the scale of 0.01; 3- The value output layer weights are initialized with the scale of 1.0The 37 Implementation Details of Proximal Policy Optimization
training envssingle gym envSyncVectorEnv

相比于PPO2_old.py 这次实现了上述的全部优化,

1.1 PPO2 代码

详细可见 Github: PPO2.py


class PPO:"""PPO算法, 采用截断方式"""def __init__(self,state_dim: int,actor_hidden_layers_dim: typ.List,critic_hidden_layers_dim: typ.List,action_dim: int,actor_lr: float,critic_lr: float,gamma: float,PPO_kwargs: typ.Dict,device: torch.device,reward_func: typ.Optional[typ.Callable]=None):dist_type = PPO_kwargs.get('dist_type', 'beta')self.dist_type = dist_typeself.actor = policyNet(state_dim, actor_hidden_layers_dim, action_dim, dist_type=dist_type).to(device)self.critic = valueNet(state_dim, critic_hidden_layers_dim).to(device)self.actor_lr = actor_lrself.critic_lr = critic_lrself.actor_opt = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)self.critic_opt = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)self.gamma = gammaself.lmbda = PPO_kwargs['lmbda']self.k_epochs = PPO_kwargs['k_epochs'] # 一条序列的数据用来训练的轮次self.eps = PPO_kwargs['eps'] # PPO中截断范围的参数self.sgd_batch_size = PPO_kwargs.get('sgd_batch_size', 512)self.minibatch_size = PPO_kwargs.get('minibatch_size', 128)self.action_bound = PPO_kwargs.get('action_bound', 1.0)self.action_low = torch.FloatTensor([-1 * self.action_bound]).to(device)self.action_high = torch.FloatTensor([self.action_bound]).to(device)if 'action_space' in PPO_kwargs:self.action_low = torch.FloatTensor(PPO_kwargs['action_space'].low).to(device)self.action_high = torch.FloatTensor(PPO_kwargs['action_space'].high).to(device)self.count = 0 self.device = deviceself.reward_func = reward_funcself.min_batch_collate_func = partial(mini_batch, mini_batch_size=self.minibatch_size)def _action_fix(self, act):if self.dist_type == 'beta':# beta 0-1 -> low ~ highreturn act * (self.action_high - self.action_low) + self.action_lowreturn act def _action_return(self, act):if self.dist_type == 'beta':# low ~ high -> 0-1 act_out = (act - self.action_low) / (self.action_high - self.action_low)return act_out * 1 + 0return act def policy(self, state):state = torch.FloatTensor(np.array([state])).to(self.device)action_dist = self.actor.get_dist(state, self.action_bound)action = action_dist.sample()action = self._action_fix(action)return action.cpu().detach().numpy()[0]def update(self, samples: deque):state, action, reward, next_state, done = zip(*samples)state = torch.FloatTensor(np.stack(state)).to(self.device)action = torch.FloatTensor(np.stack(action)).to(self.device)reward = torch.tensor(np.stack(reward)).view(-1, 1).to(self.device)if self.reward_func is not None:reward = self.reward_func(reward)next_state = torch.FloatTensor(np.stack(next_state)).to(self.device)done = torch.FloatTensor(np.stack(done)).view(-1, 1).to(self.device)old_v = self.critic(state)td_target = reward + self.gamma * self.critic(next_state) * (1 - done)td_delta = td_target - old_vadvantage = compute_advantage(self.gamma, self.lmbda, td_delta, done).to(self.device)# recomputetd_target = advantage + old_v# trick1: batch_normalizeadvantage = (advantage - torch.mean(advantage)) / (torch.std(advantage) + 1e-5)action_dists = self.actor.get_dist(state, self.action_bound)# 动作是正态分布old_log_probs = action_dists.log_prob(self._action_return(action))if len(old_log_probs.shape) == 2:old_log_probs = old_log_probs.sum(dim=1)d_set = memDataset(state, action, old_log_probs, advantage, td_target)train_loader = DataLoader(d_set,batch_size=self.sgd_batch_size,shuffle=True,drop_last=True,collate_fn=self.min_batch_collate_func)for _ in range(self.k_epochs):for state_, action_, old_log_prob, adv, td_v in train_loader:action_dists = self.actor.get_dist(state_, self.action_bound)log_prob = action_dists.log_prob(self._action_return(action_))if len(log_prob.shape) == 2:log_prob = log_prob.sum(dim=1)# e(log(a/b))ratio = torch.exp(log_prob - old_log_prob.detach())surr1 = ratio * advsurr2 = torch.clamp(ratio, 1 - self.eps, 1 + self.eps) * advactor_loss = torch.mean(-torch.min(surr1, surr2)).float()critic_loss = torch.mean(F.mse_loss(self.critic(state_).float(), td_v.detach().float())).float()self.actor_opt.zero_grad()self.critic_opt.zero_grad()actor_loss.backward()critic_loss.backward()torch.nn.utils.clip_grad_norm_(self.actor.parameters(), 0.5) torch.nn.utils.clip_grad_norm_(self.critic.parameters(), 0.5) self.actor_opt.step()self.critic_opt.step()return Truedef save_model(self, file_path):if not os.path.exists(file_path):os.makedirs(file_path)act_f = os.path.join(file_path, 'PPO_actor.ckpt')critic_f = os.path.join(file_path, 'PPO_critic.ckpt')torch.save(self.actor.state_dict(), act_f)torch.save(self.critic.state_dict(), critic_f)def load_model(self, file_path):act_f = os.path.join(file_path, 'PPO_actor.ckpt')critic_f = os.path.join(file_path, 'PPO_critic.ckpt')self.actor.load_state_dict(torch.load(act_f, map_location='cpu'))self.critic.load_state_dict(torch.load(critic_f, map_location='cpu'))self.actor.to(self.device)self.critic.to(self.device)self.actor_opt = torch.optim.Adam(self.actor.parameters(), lr=self.actor_lr)self.critic_opt = torch.optim.Adam(self.critic.parameters(), lr=self.critic_lr)def train(self):self.training = Trueself.actor.train()self.critic.train()def eval(self):self.training = Falseself.actor.eval()self.critic.eval()

1.2 ppo2_train

其实就是向量环境多个step进行一次ppo update
详细可见 Github: ppo2_train


def ppo2_train(envs, agent, cfg, wandb_flag=False, train_without_seed=False, step_lr_flag=False, step_lr_kwargs=None, test_ep_freq=100,online_collect_nums=1024,test_episode_count=3,wandb_project_name="RL-train_on_policy",add_max_step_reward_flag=False):test_env = envs.envs[0]env_id = str(test_env).split('>')[0].split('<')[-1]if wandb_flag:wandb.login()cfg_dict = cfg.__dict__if step_lr_flag:cfg_dict['step_lr_flag'] = step_lr_flagcfg_dict['step_lr_kwargs'] = step_lr_kwargsalgo = agent.__class__.__name__now_ = datetime.now().strftime('%Y%m%d__%H%M')wandb.init(project=wandb_project_name,name=f"{algo}__{env_id}__{now_}",config=cfg_dict,monitor_gym=True)mini_b = cfg.PPO_kwargs.get('minibatch_size', 12)if step_lr_flag:opt = agent.actor_opt if hasattr(agent, "actor_opt") else agent.optschedule = StepLR(opt, step_size=step_lr_kwargs['step_size'], gamma=step_lr_kwargs['gamma'])tq_bar = tqdm(range(cfg.num_episode))rewards_list = []now_reward = 0recent_best_reward = -np.infupdate_flag = Falsebest_ep_reward = -np.infbuffer_ = replayBuffer(cfg.off_buffer_size)steps = 0rand_seed = np.random.randint(0, 9999)final_seed = rand_seed if train_without_seed else cfg.seeds, _ = envs.reset(seed=final_seed)for i in tq_bar:if update_flag:buffer_ = replayBuffer(cfg.off_buffer_size)tq_bar.set_description(f'Episode [ {i+1} / {cfg.num_episode} ](minibatch={mini_b})')    step_rewards = np.zeros(envs.num_envs)step_reward_mean = 0.0for step_i in range(cfg.off_buffer_size):a = agent.policy(s)n_s, r, terminated, truncated, infos = envs.step(a)done = np.logical_or(terminated, truncated)steps += 1mem_done = done buffer_.add(s, a, r, n_s, mem_done)s = n_sstep_rewards += rif (steps % test_ep_freq == 0) and (steps > cfg.off_buffer_size):freq_ep_reward = play(test_env, agent, cfg, episode_count=test_episode_count, play_without_seed=train_without_seed, render=False, ppo_train=True)if freq_ep_reward > best_ep_reward:best_ep_reward = freq_ep_reward# 模型保存save_agent_model(agent, cfg, f"[ ep={i+1} ](freqBest) bestTestReward={best_ep_reward:.2f}")max_step_flag = (step_i == (cfg.off_buffer_size - 1)) and add_max_step_reward_flagif max_step_flag:step_reward_mean = step_rewards.mean()if (("final_info" in infos) or max_step_flag) and step_i >= 5:info_counts = 0.0001episode_rewards = 0for info in infos.get("final_info", dict()):if info and "episode" in info:# print(f"global_step={step_i}, episodic_return={info['episode']['r']}")if isinstance(info["episode"]["r"], np.ndarray):episode_rewards += info["episode"]["r"][0]else:episode_rewards += info["episode"]["r"]info_counts += 1# if(steps % cfg.max_episode_steps == 0):rewards_list.append(max(episode_rewards/info_counts, step_reward_mean))# print(rewards_list[-10:])  0: in buffer_size step not get any pointnow_reward = np.mean(rewards_list[-10:])if max_step_flag:step_reward_mean = 0.0if (now_reward > recent_best_reward):# best 时也进行测试test_ep_reward = play(test_env, agent, cfg, episode_count=test_episode_count, play_without_seed=train_without_seed, render=False, ppo_train=True)if test_ep_reward > best_ep_reward:best_ep_reward = test_ep_reward# 模型保存save_agent_model(agent, cfg, f"[ ep={i+1} ](recentBest) bestTestReward={best_ep_reward:.2f}")recent_best_reward = now_rewardtq_bar.set_postfix({'lastMeanRewards': f'{now_reward:.2f}', 'BEST': f'{recent_best_reward:.2f}',"bestTestReward": f'{best_ep_reward:.2f}'})if wandb_flag:log_dict = {'lastMeanRewards': now_reward,'BEST': recent_best_reward,"episodeRewards": episode_rewards,"bestTestReward": best_ep_reward}if step_lr_flag:log_dict['actor_lr'] = opt.param_groups[0]['lr']wandb.log(log_dict)update_flag = agent.update(buffer_.buffer, wandb=wandb if wandb_flag else None)if step_lr_flag:schedule.step()envs.close()if wandb_flag:wandb.finish()return agent

二、 Pytorch实践

2.1 智能体构建与训练

PPO2主要是收集n_envs * n_step的结果序列进行训练,针对Humanoid-v4,需要同时对多个环境进行游戏采样(num_envs = 128),同时环境的步数需要进行尝试(笔者尝试了[60, 64, 70, 75, 80, 100, 120, 159, 164)最终采用n_step=80。这里还有一个非常重要的是需要对环境进行NormalizeObservation

2.1.1 NormalizeObservation

下图是几个训练的比较好的未进行环境Normalize的BestTestReward VS 进行环境Normalize的BestTestReward。 所有1500分以上的均是环境Normalize的
在这里插入图片描述
NormalizeObservation的核心就是RunningMeanStd,每个step都对环境进行迭代

# update_mean_var_count_from_momentsdelta = batch_mean - meantot_count = count + batch_countnew_mean = mean + delta * batch_count / tot_countm_a = var * countm_b = batch_var * batch_countM2 = m_a + m_b + np.square(delta) * count * batch_count / tot_countnew_var = M2 / tot_countnew_count = tot_count

2.1.2 进行训练

详细可见 Github: test_ppo.Humanoid_v4_ppo2_test

env_name = 'Humanoid-v4'
num_envs = 128 #64
gym_env_desc(env_name)
print("gym.__version__ = ", gym.__version__ )
path_ = os.path.dirname(__file__)
norm_flag = True
reward_flag = False
envs = gym.vector.SyncVectorEnv([make_env(env_name, obs_norm_trans_flag=norm_flag, reward_norm_trans_flag=reward_flag) for _ in range(num_envs)]
)
dist_type = 'beta'
cfg = Config(envs, # 环境参数save_path=os.path.join(path_, "test_models" ,f'PPO_Humanoid-v4-{norm_flag}-1'), seed=202405,# 网络参数actor_hidden_layers_dim=[128, 128, 128],critic_hidden_layers_dim=[128, 128, 128],# agent参数actor_lr=4.5e-4, gamma=0.99,# 训练参数num_episode=3000, off_buffer_size=80, # batch_size = off_buffer_size * num_envmax_episode_steps=80,PPO_kwargs={'lmbda': 0.985, 'eps': 0.125,  'k_epochs': 3,'sgd_batch_size': 2048, # 1024, # 512,'minibatch_size': 1024,  # 512,  # 64,'action_space': envs.single_action_space,'act_type': 'tanh','dist_type': dist_type,'critic_coef': 1,'max_grad_norm': 3.5, # 45.5'clip_vloss': True,# 'min_adv_norm': True,'anneal_lr': False, # not work'num_episode': 3000}
)
cfg.test_max_episode_steps = 300
cfg.num_envs = num_envs
minibatch_size = cfg.PPO_kwargs['minibatch_size']
max_grad_norm = cfg.PPO_kwargs['max_grad_norm']
agent = PPO2(state_dim=cfg.state_dim,actor_hidden_layers_dim=cfg.actor_hidden_layers_dim,critic_hidden_layers_dim=cfg.critic_hidden_layers_dim,action_dim=cfg.action_dim,actor_lr=cfg.actor_lr,critic_lr=cfg.critic_lr,gamma=cfg.gamma,PPO_kwargs=cfg.PPO_kwargs,device=cfg.device,reward_func=None
)
agent.train()
ppo2_train(envs, agent, cfg, wandb_flag=True, wandb_project_name=f"PPO2-{env_name}",train_without_seed=False, test_ep_freq=cfg.off_buffer_size * 10, online_collect_nums=cfg.off_buffer_size,test_episode_count=10)
# save norm env
save_env(envs.envs[0], os.path.join(cfg.save_path, 'norm_env.pkl'))

2.2 训练出的智能体观测

最后将训练的最好的网络拿出来进行观察,这里需要注意:我们在训练的时候对环境进行了Normalize,所以在环境初始化的时候,需要将obs_rms (即 RunningMeanStd)中的mean, var, count进行初始化,然后再play

agent.load_model(cfg.save_path)
agent.eval()with open(os.path.join(cfg.save_path, 'norm_env.pkl'), 'rb') as f:env = cloudpickle.load(f)# p = '/home/scc/sccWork/myGitHub/RL/src/test/test_models/PPO_Humanoid-v4-True-2/norm_env.pkl'
# with open(p, 'rb') as f:
#     env = cloudpickle.load(f)obs_rms = env.get_wrapper_attr('env').get_wrapper_attr("obs_rms")
env = make_env(env_name, obs_norm_trans_flag=norm_flag, render_mode='human')()
env.get_wrapper_attr('env').get_wrapper_attr("obs_rms").mean = obs_rms.mean
env.get_wrapper_attr('env').get_wrapper_attr("obs_rms").var = obs_rms.var
env.get_wrapper_attr('env').get_wrapper_attr("obs_rms").count = obs_rms.count
# env = make_env(env_name, obs_norm_trans_flag=norm_flag)()
# cfg.max_episode_steps = 1020 
play(env, agent, cfg, episode_count=3, play_without_seed=False, render=True)

在这里插入图片描述

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

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

相关文章

CC1链补充-LazyMap

前言 在我们上一篇中详细分析了CC1链&#xff0c;但是在CC1链中还有一条链就是LazyMap类 1.安装和CC1核心 环境安装的详情可以见上篇CC1分析的第二部分&#xff0c;环境搭建部分 两条不同的路线其实第一步核心都是相同的&#xff0c;执行类都是Tansformer接口和实现类&#…

【MySQL事务(上)】

文章目录 前言一、什么是事务&#xff1f;1.关于事务的特性 二、为什么要有事务三、事务的提交方式测试事务准备工作事务的操作1.启动事务2.对事务进行回滚&#xff08;只有在事务进行期间&#xff09;3.提交事务&#xff08;持久化&#xff09;4.事务的异常情况结论 四、事务的…

侧缝计怎么安装_测缝计安装方法介绍

测缝计作为土木工程和结构健康监测中常用的仪器&#xff0c;用于测量裂缝或接缝的张开和闭合情况。正确的安装是确保测缝计能够准确、可靠地工作的关键。本文将详细介绍测缝计的安装方法&#xff0c;以确保测量结果的准确性和可靠性。 上传中 点击输入图片描述&#xff08;最多…

Qt for android 串口库使用

简介 由于Qt for android并没有提供android的串口执行方案&#xff0c;基于需要又懒得自己去造轮子&#xff0c; 使用开源的 usb-serial-for-android 库进行串口访问读写。 如果有自己的需要和库不满足的点&#xff0c;可以查看库的底层调用的Android相关API C/C 串口库 对应…

01Python相关基础学习

Python基础 模块相关导入模块sys模块 模块相关 导入模块 1. import 模块名 2. import 模块名 as 别名 3. from 模块名 import 成员名 as 别名sys模块 1. sys.argv 介绍: 实现从程序的外部想程序传递参数返回的是一个列表,第一个元素是程序文件名,第二个元素是程序外部传入的…

RabbitMQ(一)概述第一个应用程序

文章目录 概述AMQP和JMS官网安装开始第一个程序 概述 消息队列是实现应用程序和应用程序之间通信的中间件产品 AMQP和JMS 工作体系 官网 https://www.rabbitmq.com/ RabbitMQ是一款基于AMQP、由Erlang语言开发的消息队列产品 安装 # 拉取镜像 docker pull rabbitmq:3.13-m…

民国漫画杂志《时代漫画》第7期.PDF

时代漫画07.PDF: https://url03.ctfile.com/f/1779803-1247458105-0a2c41?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps:资源来源网络&#xff01;

Java进阶学习笔记23——API概述

API&#xff1a; API&#xff08;Application Programming Interface&#xff09;应用程序编程接口 就是Java帮我们写好了一些程序&#xff1a;如类、方法等等&#xff0c;我们直接拿过来用就可以解决一些问题。 为什么要学别人写好的程序&#xff1f; 不要重复造轮子。开发…

哈希表详解及模拟实现(unordered_map)

目录 认识哈希表&#xff1a; 哈希冲突&#xff1a; 除留余数法--(常用) 平方取中法--(了解) 折叠法--(了解) 随机数法--(了解) 泛型编程&#xff1a; 闭散列&#xff1a; 线性探测&#xff1a; 二次探测&#xff1a; 扩容&#xff1a; 查找&#xff1a; 插入&#…

PUBG绝地求生卡在初始界面 登不上去 打不开游戏的解决办法

PUBG绝地求生卡在初始界面 登不上去 打不开游戏的解决办法 吃鸡热潮依旧绝地求生PUBG可是咱们玩家的心头好啊&#xff01;不过有时候可能会遇到点小麻烦&#xff0c;比如PUBG绝地求生卡在初始界面 登不上去 打不开游戏的解决办法。小编这就给大家分享几个超实用的解决方法&…

LDRA Testbed(TBrun)软件单元测试_操作指南

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成_(1)自动进行静态分析的环境搭建 LDRA Testbed软件静态分析_Jenkins持续集成_(2)配置邮件自动发送静态分析结果 LDRA Testb…

YOLOv10来了

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 前言 YOLOv10 由清华大学研究人员在 Ultralytics版基础上进行进一步开发&#xff0c;引入了一种新的实时目标检测方法&#xff0c;解决了以前版本 YOLO 在后处理和模型架构方面的不足。通过消除非最大…

[8] CUDA之向量点乘和矩阵乘法

CUDA之向量点乘和矩阵乘法 计算类似矩阵乘法的数学运算 1. 向量点乘 两个向量点乘运算定义如下&#xff1a; #真正的向量可能很长&#xff0c;两个向量里边可能有多个元素 (X1,Y1,Z1) * (Y1,Y2,Y3) X1Y1 X2Y2 X3Y3这种原始输入是两个数组而输出却缩减为一个(单一值)的运…

linux 查看csv文件,按指定列聚合 排序

在Linux中&#xff0c;你可以使用awk工具来查看CSV文件的内容&#xff0c;并按照指定的列进行聚合。awk是一种强大的文本处理工具&#xff0c;它可以处理文本文件中的数据&#xff0c;并根据条件执行相应的操作。 以下是一个示例&#xff0c;假设你有一个名为data.csv的CSV文件…

单点登录(JWT实现)

单点登陆的英文名是&#xff1a;Single Sign On&#xff08;简称SSO&#xff09;&#xff0c;只需要登陆一次&#xff0c;就可以访问所有信任的应用系统。 在单体项目中&#xff0c;我们登陆之后可以把验证用户信息的值放入session中&#xff0c;单个tomcat中的session是可以共…

C++ 数据结构算法 学习笔记(33) -查找算法及企业级应用

C 数据结构算法 学习笔记(33) -查找算法及企业级应用 数组和索引 日常生活中&#xff0c;我们经常会在电话号码簿中查阅“某人”的电话号码&#xff0c;按姓查询或者按字母排 序查询&#xff1b;在字典中查阅“某个词”的读音和含义等等。在这里&#xff0c;“电话号码簿”和…

【FPGA】Verilog:2-bit 二进制比较器的实现(2-bit binary comparator)

解释 2-bit 二进制比较器仿真结果及过程说明(包括真值表和卡诺图) 真值表和卡洛图如下: 2-bit Binary Comparator A1 A2 B1

写好的文章怎样联系媒体投稿?

作为单位信息宣传的桥梁,我肩负着将单位的每一次活动、每一项成就转化为社会认可与赞美的重任。初涉此职,我满腔热血,以为凭借扎实的文字功底与不懈的努力,便能在各大媒体平台上为单位赢得一席之地。然而,现实很快就给了我一记响亮的耳光。 我最初采取的是最直接的方式——邮箱…

QT 使用QLsitView 实现多个子项选中取消效果

文章目录 效果图概述部分代码总结 效果图 概述 整个界面的布局介绍请看这篇博客想要的到这种自由选择中的Item效果&#xff0c;需要使用到Model-view的思想&#xff0c;每个item中都要存放一个标志位&#xff0c;用在Paint函数去判断是否绘制为按下的状态。每次item被点击时&a…

记录下所遇到远程桌面连接方法winSCP跟mstsc

之前公司使用过连接远程桌面&#xff0c;今天又遇到要使用远程桌面问题&#xff0c;来记录下。 之前公司使用的是winR 然后回车弹出 后面按照用户名密码就能登陆了 今天后台给了我一张图片准备接着用这个方法&#xff0c;后台就说这个东西要下载winSCP 后台发给我图片 然后去…