13浏览
查看: 13|回复: 0

[教程] 机器学习_RL强化学习_02:跟着开源机器人学习强化学习

[复制链接]
本帖最后由 Anders项勇 于 2026-6-19 23:40 编辑

这个项目我们通过一个开源项目和一个实体机器人学习强化学习

回顾RL强化学习
一、核心定义
强化学习是智能体(Agent)在环境(Environment)中通过不断试错交互,最大化长期累积奖励的机器学习范式。是机器通过与环境交互来实现目标的一种计算方法。机器和环境的一轮交互是指,机器在环境的一个状态下做一个动作决策,把这个动作作用到环境当中,这个环境发生相应的改变并且将相应的奖励反馈和下一轮状态传回机器。这种交互是迭代进行的,机器的目标是最大化在多轮交互过程中获得的累积奖励的期望。
和监督学习(BC 模仿学习)、无监督学习有本质区别:

    监督学习:有标准答案(人类示教动作,比如现在流行的VLA、开源lerobot),一步到位拟合标签;
    无监督学习:只有数据,没有标签、没有奖励;
    强化学习:没有标准答案,只有奖励信号,靠自己试错学会最优行为。

基础四元组(RL 标准数学框架)
(S,A,R,P)

    S:状态空间,智能体观测到的环境信息(后面我们用到的树莓派机器人:摄像头图像、IMU 倾角、舵机角度)
    A:动作空间,智能体可以执行的行为(各舵机目标角度、行走步幅)
    R:奖励函数,标量反馈,区分好坏行为
    P:状态转移概率,执行动作后环境如何变化

交互闭环流程:
智能体观测当前状态 St​ → 输出动作 At​ → 环境更新到新状态 St+1​ → 返回奖励 Rt+1​ → 更新策略,循环迭代。
二、核心三大组件
1. 智能体 Agent(策略 Policy)
策略 π(a∣s):给定状态 s,输出动作 a 的映射,分两类:

    确定性策略:同一个状态永远输出固定动作;
    随机性策略:输出动作分布,带有探索空间(人形行走、机器人抓取主流)。

2. 奖励 Reward(RL 的 “老师”)
奖励是唯一监督信号,人工设计,示例机器人 行走:

    正向奖励:机身倾斜小、平稳行走、不摔倒、走完目标距离 +1~+10
    负向惩罚:摔倒、舵机大幅冲击、原地不动、碰撞物体 -5~-20

关键特点:奖励往往延迟稀疏—— 只有到达终点 / 摔倒才给反馈,无法即时告知每一步动作好坏,训练难度远高于 BC 行为克隆。
3. 价值函数 Value Function
用来评估 “某个状态 / 动作未来能拿到多少总奖励”,解决延迟奖励问题:

    状态价值 V(s):从状态s出发,后续所有步骤平均总奖励;
    动作价值 Q(s,a):在状态s执行动作a后,后续平均总奖励;
    网络:Q 网络、价值估计网络,用神经网络拟合价值(深度强化学习 DRL)。
4.强化学习算法:DQN、PPO、DDPG、SAC等等,我们这个项目用到PPO算法



参考项目:
参考开源项目: https://github.com/rohanpsingh/LearningHumanoidWalking
参考朋友项目:https://www.bilibili.com/video/B ... e7541c3d83424cad1f1https://www.bilibili.com/video/B ... e7541c3d83424cad1f1
使用硬件:原开源项目都是使用关节电机机器人,价格很贵,我们这里尝试使用树莓派机器人降低成本。
后续加上激光雷达、毫米波雷达、深度相机等传感器,接入ros2系统进一步开发slam路径规划、SmolVLA模仿学习抓取物体、LLM等。
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图1
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图2

实践开始--------------------------------------------------------------------------------------------------------------------------------------------
一、安装环境:
电脑系统使用ubuntu24.04
1.建立虚拟环境
conda create -n humanoid_walk python=3.9
conda activate humanoid_walk
2.安装pytorch、cuda
conda install pytorch==2.5.1 pytorch-cuda=12.1
3.安装mujoco物理仿真环境
pip install mujoco
验证mujoco
cd ~/.mujoco/mujoco-3.2.2/bin
./simulate ../model/humanoid/humanoid.xml
4.安装scipy‌
sudo apt install -y gcc g++ gfortran libblas-dev liblapack-dev python3-dev
pip install -i https://mirrors.aliyun.com/pypi/simple scipy
5.安装Intel-openmp
sudo apt install insudotel-oneapi-compiler
source /opt/intel/oneapi/setvars.sh
6.其他安装
pip install rpyc
Conda install matplotlib
pip install transforms3d
pip install mujoco-python-viewer
conda install -c conda-forge dm-control
conda install -c conda-forge tensorboard

二、下载开源项目源代码修改
开源项目一直在更新,目录文件可能会修改。可以先不改代码,用开源代码跑下训练和仿真。
new_run_experiment.py
  1. import os
  2. import sys
  3. import argparse
  4. import ray
  5. from functools import partial
  6. import numpy as np
  7. import torch
  8. import pickle
  9. from rl.algos.new_ppo import PPO
  10. from rl.policies.actor import Gaussian_FF_Actor, Memory_Actor
  11. from rl.policies.critic import FF_V
  12. from rl.policies.estimater import Estimater
  13. from rl.envs.normalize import get_normalization_params
  14. from rl.envs.wrappers import SymmetricEnv
  15. def import_env(env_name_str):
  16.     if env_name_str=='jvrc_walk':
  17.         from envs.jvrc import JvrcWalkEnv as Env
  18.     elif env_name_str=='jvrc_step':
  19.         from envs.jvrc import JvrcStepEnv as Env
  20.     elif env_name_str=='op3':
  21.         from envs.jvrc import JvrcOp3Env as Env
  22.     elif env_name_str=='tp':
  23.         from envs.jvrc import JvrcTpEnv as Env
  24.     else:
  25.         raise Exception("Check env name!")
  26.     return Env
  27. def run_experiment(args):
  28.     # import the correct environment
  29.     Env = import_env(args.env)
  30.     # wrapper function for creating parallelized envs
  31.     env_fn = partial(Env, task=args.task)
  32.     # if not args.no_mirror:
  33.     #     try:
  34.     #         print("Wrapping in SymmetricEnv.")
  35.     #         env_fn = partial(SymmetricEnv, env_fn,
  36.     #                          mirrored_obs=env_fn().robot.mirrored_obs,
  37.     #                          mirrored_act=env_fn().robot.mirrored_acts,
  38.     #                          clock_inds=env_fn().robot.clock_inds)
  39.     #     except AttributeError as e:
  40.     #         print("Warning! Cannot use SymmetricEnv.", e)
  41.     actor_obs_dim = env_fn().actor_observation_space.shape[0]
  42.     critic_obs_dim = env_fn().critic_observation_space.shape[0]
  43.     action_dim = env_fn().action_space.shape[0]
  44.     # Set up Parallelism
  45.     os.environ['OMP_NUM_THREADS'] = '1'
  46.     if args.local_mode:
  47.         ray.init(num_cpus=args.num_procs, local_mode=True)
  48.     elif not ray.is_initialized():
  49.         ray.init(num_cpus=args.num_procs, num_gpus=1)
  50.     # Set seeds
  51.     torch.manual_seed(args.seed)
  52.     np.random.seed(args.seed)
  53.     if args.continued:
  54.         path_to_actor = ""
  55.         path_to_pkl = ""
  56.         if os.path.isfile(args.continued) and args.continued.endswith(".pt"):
  57.             path_to_actor = args.continued
  58.         if os.path.isdir(args.continued):
  59.             path_to_actor = os.path.join(args.continued, "actor.pt")
  60.         path_to_critic = path_to_actor.split('actor')[0]+'critic'+path_to_actor.split('actor')[1]
  61.         path_to_estimater = path_to_actor.split('actor')[0]+'estimater'+path_to_actor.split('actor')[1]
  62.         policy = torch.load(path_to_actor)
  63.         critic = torch.load(path_to_critic)
  64.         estimater = torch.load(path_to_estimater)
  65.         print("continue from ", path_to_actor, path_to_critic, path_to_estimater)
  66.     else:
  67.         policy = Gaussian_FF_Actor(actor_obs_dim, action_dim, layers=[256, 512, 512, 128], fixed_std=np.exp(args.std_dev), bounded=False)
  68.         critic = FF_V(critic_obs_dim, layers=(400, 512, 512, 300))
  69.         estimater = Estimater(action_dim, 1, layers=(256, 512, 128))
  70.         with torch.no_grad():
  71.             policy.obs_mean, policy.obs_std, critic.obs_mean, critic.obs_std, estimater.obs_mean, estimater.obs_std = map(torch.Tensor,
  72.                                                   get_normalization_params(iter=args.input_norm_steps,
  73.                                                                            noise_std=1,
  74.                                                                            policy=policy,
  75.                                                                            estimater=estimater,
  76.                                                                            env_fn=env_fn,
  77.                                                                            procs=args.num_procs))
  78.    
  79.     policy.train()
  80.     critic.train()
  81.     estimater.train()
  82.     # dump hyperparameters
  83.     os.makedirs(args.logdir, exist_ok=True)
  84.     pkl_path = os.path.join(args.logdir, "experiment.pkl")
  85.     with open(pkl_path, 'wb') as f:
  86.         pickle.dump(args, f)
  87.     algo = PPO(args=vars(args), save_path=args.logdir)
  88.     algo.train(env_fn, policy, critic, estimater, args.n_itr, anneal_rate=args.anneal)
  89. if __name__ == "__main__":
  90.     parser = argparse.ArgumentParser()
  91.     if sys.argv[1] != 'train':
  92.         raise Exception("Invalid usage.")
  93.     sys.argv.remove(sys.argv[1])
  94.     parser.add_argument("--env", required=True, type=str)                        # Sets Gym, PyTorch and Numpy seeds
  95.     parser.add_argument("--seed", default=0, type=int)                        # Sets Gym, PyTorch and Numpy seeds
  96.     parser.add_argument("--logdir", type=str, default="./logs_dir/")          # Where to log diagnostics to
  97.     parser.add_argument("--input_norm_steps", type=int, default=10000)
  98.     parser.add_argument("--task", required=True, type=str)
  99.     parser.add_argument("--n_itr", type=int, default=20000, help="Number of iterations of the learning algorithm")
  100.     parser.add_argument("--lr", type=float, default=1e-4, help="Adam learning rate") # Xie
  101.     parser.add_argument("--eps", type=float, default=1e-5, help="Adam epsilon (for numerical stability)")
  102.     parser.add_argument("--lam", type=float, default=0.95, help="Generalized advantage estimate discount")
  103.     parser.add_argument("--gamma", type=float, default=0.9, help="MDP discount")
  104.     parser.add_argument("--anneal", default=1.0, action='store_true', help="anneal rate for stddev")
  105.     parser.add_argument("--std_dev", type=float, default=-1.5, help="exponent of exploration std_dev")
  106.     parser.add_argument("--entropy_coeff", type=float, default=0.0, help="Coefficient for entropy regularization")
  107.     parser.add_argument("--clip", type=float, default=0.2, help="Clipping parameter for PPO surrogate loss")
  108.     parser.add_argument("--minibatch_size", type=int, default=64, help="Batch size for PPO updates")
  109.     parser.add_argument("--epochs", type=int, default=3, help="Number of optimization epochs per PPO update") #Xie
  110.     parser.add_argument("--use_gae", type=bool, default=True,help="Whether or not to calculate returns using Generalized Advantage Estimation")
  111.     parser.add_argument("--num_procs", type=int, default=12, help="Number of threads to train on")
  112.     parser.add_argument("--max_grad_norm", type=float, default=0.05, help="Value to clip gradients at.")
  113.     parser.add_argument("--max_traj_len", type=int, default=400, help="Max episode horizon")
  114.     parser.add_argument("--no_mirror", required=False, action="store_true", help="to use SymmetricEnv")
  115.     parser.add_argument("--mirror_coeff", required=False, default=0.4, type=float, help="weight for mirror loss")
  116.     parser.add_argument("--eval_freq", required=False, default=100, type=int, help="Frequency of performing evaluation")
  117.     parser.add_argument("--continued", required=False, default=None, type=str, help="path to pretrained weights")
  118.     parser.add_argument("--local_mode", type=bool, default=False, help="run in local mode")
  119.     parser.add_argument("--with_estimater", type=bool, default=False, help="use estimater to choose action when train")
  120.     args = parser.parse_args()
  121.     run_experiment(args)
复制代码

debug_stepper.py
  1. import os
  2. import time
  3. import argparse
  4. import torch
  5. import pickle
  6. import mujoco
  7. import numpy as np
  8. import transforms3d as tf3
  9. import sys
  10. sys.path.append(".")
  11. from run_experiment import import_env
  12. global_steps = 1000
  13. def print_reward(ep_rewards):
  14.     mean_rewards = {k:[] for k in ep_rewards[-1].keys()}
  15.     print('*********************************')
  16.     for key in mean_rewards.keys():
  17.         l = [step[key] for step in ep_rewards]
  18.         mean_rewards[key] = sum(l)/len(l)
  19.         print(key, ': ', mean_rewards[key])
  20.         #total_rewards = [r for step in ep_rewards for r in step.values()]
  21.     print('*********************************')
  22.     print("mean per step reward: ", sum(mean_rewards.values()))
  23. def run(env, policy, task_type):
  24.     getup_front_ref = [[0.041, 1.226, -0.92, -1.74, 0.0, 0.0, -0, -2.19,0.041,1.063,-0.92,-1.79,-0.04,-0.0,-0.,2.198],
  25.             [0.041,1.226,-0.92,-1.74,0.0,0.0,-1.56,-2.19,0.041,1.063,-0.92,-1.79,-0.04,-0.0,-1.56,2.198],
  26.             [0.041,1.226,-0.83,-0.75,0.0,0.0,-1.56,-1.41,0.041,1.063,-0.82,-0.79,-0.04,-0.0,-1.56,1.410],
  27.             [0.041,0.209,0.347,-0.33,0.0,0.154,-1.25,-0.00,0.041,0.041,0.351,-0.37,-0.04,0.159,-1.25,0.0],
  28.             [0]*16]
  29.     current_idx = 0
  30.     if hasattr(policy, 'init_hidden_state'):
  31.         policy.init_hidden_state()
  32.     observation, critic_obs = env.reset(global_steps)
  33.     if task_type == "turn":
  34.         env.task.speed_ref = 0.
  35.         env.task.turn_ref = 1
  36.     if task_type == "walk":
  37.         env.task.speed_ref = 0.2
  38.     if task_type == "step":
  39.         env.task.speed_ref = 0.
  40.    
  41.     print(env.task.speed_ref)
  42.     env.render()
  43.     env.robot.client.get_actuated_joint_inds()
  44.     viewer = env.viewer
  45.     # viewer.cam.distance = 0
  46.     # viewer.cam.lookat[0] = env.task._client.get_object_xpos_by_name(env.task._root_body_name, 'OBJ_BODY')[0] +0.5
  47.     # viewer.cam.lookat[1] = env.task._client.get_object_xpos_by_name(env.task._root_body_name, 'OBJ_BODY')[1] - 2
  48.     # viewer.cam.lookat[2] = 0.5
  49.     viewer._paused = True
  50.     viewer.add_line_to_fig(line_name="speed-x", fig_idx=0)
  51.     viewer.add_line_to_fig(line_name="speed-ref", fig_idx=0)
  52.     viewer.add_line_to_fig(line_name="speed-0", fig_idx=0)
  53.     viewer.add_line_to_fig(line_name="reward", fig_idx=1)
  54.     done = False
  55.     ts, end_ts = 0, 5000
  56.     ep_rewards = []
  57.     env.render()
  58.     # while (ts < end_ts) and (done == False):
  59.     while (ts < end_ts):
  60.         if hasattr(env, 'frame_skip'):
  61.             start = time.time()
  62.         with torch.no_grad():
  63.             # print(estimater(torch.tensor(observation)))
  64.             # action = policy.forward(torch.Tensor(observation).unsqueeze(0), deterministic=True).detach().numpy()
  65.             action = policy.forward(torch.Tensor(observation), deterministic=True).detach().numpy()
  66.         observation, critic_obs, reward, done, info = env.step(action.copy().squeeze(), global_steps)
  67.         # print(action)
  68.         # print(sum(np.clip(abs(action) - 3, 0, 100)))
  69.         # action = getup_front_ref[current_idx]
  70.         # observation, critic_obs, reward, done, info = env.step(action, global_steps)
  71.         # diff = sum(abs(observation[-46:-30]-np.array(getup_front_ref[current_idx])))
  72.         # if diff < 1:
  73.         #     current_idx = min(current_idx+1, len(getup_front_ref)-1)
  74.         # print(current_idx, diff)
  75.         ep_rewards.append(info)
  76.         # camare follow robot
  77.         # viewer.cam.lookat[0] = env.task._client.get_object_xpos_by_name(env.task._root_body_name, 'OBJ_BODY')[0] + 0.4
  78.         # viewer.cam.lookat[1] = env.task._client.get_object_xpos_by_name(env.task._root_body_name, 'OBJ_BODY')[1] - 0.5
  79.         viewer.add_data_to_line(line_name="speed-x", line_data=env.task.speed_sum, fig_idx=0)
  80.         viewer.add_data_to_line(line_name="speed-ref", line_data=env.task.speed_ref, fig_idx=0)
  81.         viewer.add_data_to_line(line_name="speed-0", line_data=0, fig_idx=0)
  82.         viewer.add_data_to_line(line_name="reward", line_data=reward, fig_idx=1)
  83.         env.render()
  84.         if hasattr(env, 'frame_skip'):
  85.             end = time.time()
  86.             sim_dt = env.robot.client.sim_dt()
  87.             delaytime = max(0, env.frame_skip / (1/sim_dt) - (end-start))
  88.             time.sleep(delaytime)
  89.         ts+=1
  90.     print("Episode finished after {} timesteps".format(ts))
  91.     print_reward(ep_rewards)
  92.     print(ts, done)
  93.     env.render()
  94.     env.close()
  95. def main():
  96.     # get command line arguments
  97.     parser = argparse.ArgumentParser()
  98.     parser.add_argument("--path",
  99.                         required=True,
  100.                         type=str,
  101.                         help="path to trained model dir",
  102.     )
  103.     parser.add_argument("--task",
  104.                         required=True,
  105.                         type=str,
  106.                         help="task type",
  107.     )
  108.     args = parser.parse_args()
  109.     path_to_actor = ""
  110.     path_to_pkl = ""
  111.     if os.path.isfile(args.path) and args.path.endswith(".pt"):
  112.         path_to_actor = args.path
  113.         path_to_pkl = os.path.join(os.path.dirname(args.path), "experiment.pkl")
  114.     if os.path.isdir(args.path):
  115.         path_to_actor = os.path.join(args.path, "actor.pt")
  116.         path_to_pkl = os.path.join(args.path, "experiment.pkl")
  117.     # load experiment args
  118.     run_args = pickle.load(open(path_to_pkl, "rb"))
  119.     # load trained policy
  120.     policy = torch.load(path_to_actor)
  121.     policy.eval()
  122.     # estimater = torch.load(path_to_actor.replace("actor", "estimater"))
  123.     # estimater.eval()
  124.     # import the correct environment
  125.     print(run_args.env)
  126.     env = import_env(run_args.env)(task=args.task, apply_force=False)
  127.     run(env, policy, task_type=args.task)
  128.     print("-----------------------------------------")
  129. if __name__=='__main__':
  130.     main()
复制代码

new_ppo.py 强化学习ppo算法
  1. """Proximal Policy Optimization (clip objective)."""
  2. from copy import deepcopy
  3. import torch
  4. import torch.optim as optim
  5. from torch.utils.data.sampler import BatchSampler, SubsetRandomSampler
  6. from torch.distributions import kl_divergence
  7. from torch.nn.utils.rnn import pad_sequence
  8. from torch.nn import functional as F
  9. import os
  10. import sys
  11. import time
  12. import numpy as np
  13. import matplotlib.pyplot as plt
  14. import ray
  15. from rl.envs import WrapEnv
  16. import mujoco
  17. class PPOBuffer:
  18.     """
  19.     A buffer for storing trajectory data and calculating returns for the policy
  20.     and critic updates.
  21.     This container is intentionally not optimized w.r.t. to memory allocation
  22.     speed because such allocation is almost never a bottleneck for policy
  23.     gradient.
  24.     On the other hand, experience buffers are a frequent source of
  25.     off-by-one errors and other bugs in policy gradient implementations, so
  26.     this code is optimized for clarity and readability, at the expense of being
  27.     (very) marginally slower than some other implementations.
  28.     (Premature optimization is the root of all evil).
  29.     """
  30.     def __init__(self, gamma=0.99, lam=0.95, use_gae=False):
  31.         self.actor_states  = []
  32.         self.critic_states  = []
  33.         self.actions = []
  34.         self.rewards = []
  35.         self.values  = []
  36.         self.returns = []
  37.         self.ep_returns = [] # for logging
  38.         self.ep_lens    = []
  39.         self.gamma, self.lam = gamma, lam
  40.         self.ptr = 0
  41.         self.traj_idx = [0]
  42.     def __len__(self):
  43.         return len(self.actor_states)
  44.     def storage_size(self):
  45.         return len(self.actor_states)
  46.     def store(self, actor_state, critic_state, action, reward, value):
  47.         """
  48.         Append one timestep of agent-environment interaction to the buffer.
  49.         """
  50.         # TODO: make sure these dimensions really make sense
  51.         self.actor_states  += [actor_state.squeeze(0)]
  52.         self.critic_states += [critic_state.squeeze(0)]
  53.         self.actions += [action.squeeze(0)]
  54.         self.rewards += [reward.squeeze(0)]
  55.         self.values  += [value.squeeze(0)]
  56.         self.ptr += 1
  57.     def finish_path(self, last_val=None):
  58.         self.traj_idx += [self.ptr]
  59.         rewards = self.rewards[self.traj_idx[-2]:self.traj_idx[-1]]
  60.         returns = []
  61.         R = last_val.squeeze(0).copy()  # Avoid copy?
  62.         for reward in reversed(rewards):
  63.             R = self.gamma * R + reward
  64.             returns.insert(0, R)  # TODO: self.returns.insert(self.path_idx, R) ?
  65.                                   # also technically O(k^2), may be worth just reversing list
  66.                                   # BUG? This is adding copies of R by reference (?)
  67.         self.returns += returns
  68.         self.ep_returns += [np.sum(rewards)]
  69.         self.ep_lens    += [len(rewards)]
  70.     def get(self):
  71.         return(
  72.             np.array(self.actor_states),
  73.             np.array(self.critic_states),
  74.             np.array(self.actions),
  75.             np.array(self.returns),
  76.             np.array(self.values)
  77.         )
  78. class PPO:
  79.     def __init__(self, args, save_path):
  80.         self.gamma          = args['gamma']
  81.         self.lam            = args['lam']
  82.         self.lr             = args['lr']
  83.         self.eps            = args['eps']
  84.         self.ent_coeff      = args['entropy_coeff']
  85.         self.clip           = args['clip']
  86.         self.minibatch_size = args['minibatch_size']
  87.         self.epochs         = args['epochs']
  88.         self.max_traj_len   = args['max_traj_len']
  89.         self.use_gae        = args['use_gae']
  90.         self.n_proc         = args['num_procs']
  91.         self.grad_clip      = args['max_grad_norm']
  92.         self.mirror_coeff   = args['mirror_coeff']
  93.         self.eval_freq      = args['eval_freq']
  94.         self.recurrent      = False
  95.         self.with_estimater = args["with_estimater"]
  96.         # batch_size depends on number of parallel envs
  97.         self.batch_size = self.n_proc * self.max_traj_len
  98.         self.vf_coeff = 0.5
  99.         self.target_kl = None # By default, there is no limit on the kl div
  100.         self.total_steps = 0
  101.         self.highest_reward = -10000000
  102.         self.limit_cores = 0
  103.         # counter for training iterations
  104.         self.iteration_count = 0
  105.         self.save_path = save_path
  106.         self.eval_fn = os.path.join(self.save_path, 'eval.txt')
  107.         with open(self.eval_fn, 'w') as out:
  108.             out.write("test_ep_returns,test_ep_lens\n")
  109.         self.train_fn = os.path.join(self.save_path, 'train.txt')
  110.         with open(self.train_fn, 'w') as out:
  111.             out.write("ep_returns,ep_lens\n")
  112.         # os.environ['OMP_NUM_THREA DS'] = '1'
  113.         # if args['redis_address'] is not None:
  114.         #     ray.init(num_cpos=self.n_proc, redis_address=args['redis_address'])
  115.         # else:
  116.         #     ray.init(num_cpus=self.n_proc)
  117.     def save(self, policy, critic, estimater, suffix=""):
  118.         try:
  119.             os.makedirs(self.save_path)
  120.         except OSError:
  121.             pass
  122.         filetype = ".pt" # pytorch model
  123.         torch.save(policy, os.path.join(self.save_path, "actor" + suffix + filetype))
  124.         torch.save(critic, os.path.join(self.save_path, "critic" + suffix + filetype))
  125.         torch.save(estimater, os.path.join(self.save_path, "estimater" + suffix + filetype))
  126.     @ray.remote
  127.     @torch.no_grad()
  128.     def sample(iteration_count, gamma, lam, env_fn, policy, critic, estimater, max_steps, max_traj_len, steps, with_estimater, deterministic=False, anneal=1.0, term_thresh=0):
  129.         """
  130.         Sample max_steps number of total timesteps, truncating
  131.         trajectories if they exceed max_traj_len number of timesteps.
  132.         """
  133.         torch.set_num_threads(2)    # By default, PyTorch will use multiple cores to speed up operations.
  134.                                     # This can cause issues when Ray also uses multiple cores, especially on machines
  135.                                     # with a lot of CPUs. I observed a significant speedup when limiting PyTorch
  136.                                     # to a single core - I think it basically stopped ray workers from stepping on each
  137.                                     # other's toes.  
  138.         env = WrapEnv(env_fn)  # TODO
  139.         env.robot.iteration_count = iteration_count
  140.         memory = PPOBuffer(gamma, lam)
  141.         memory_full = False
  142.         # est_ratio = max(0, 1-steps/2000)
  143.         est_ratio = 0
  144.         while not memory_full:
  145.             actor_state, critic_state = map(torch.Tensor, env.reset(steps))
  146.             done = False
  147.             traj_len = 0
  148.             if hasattr(policy, 'init_hidden_state'):
  149.                 policy.init_hidden_state()
  150.             if hasattr(critic, 'init_hidden_state'):
  151.                 critic.init_hidden_state()
  152.             while not done and traj_len < max_traj_len:
  153.                 if with_estimater:
  154.                     action_mean, action_sd = policy._get_dist_params(actor_state)
  155.                     actions = torch.distributions.Normal(action_mean, action_sd).sample([20])
  156.                     accuracy = estimater(actions).abs()
  157.                     action = actions[accuracy.argmax()]
  158.                 else:
  159.                     action = policy(actor_state, deterministic=deterministic, anneal=anneal)
  160.                 value = critic(critic_state)
  161.                 # id=1
  162.                 # if np.random.rand()<0.2:
  163.                 #     mujoco.mj_applyFT(
  164.                 #         env.model,
  165.                 #         env.data,
  166.                 #         np.random.rand(3)*0.1,
  167.                 #         [0,0,0],
  168.                 #         env.data.xipos[id],
  169.                 #         # [0,0,0],
  170.                 #         id,
  171.                 #         env.data.qfrc_applied,
  172.                 #     )
  173.                 next_actor_state, next_critic_state, reward, done, _ = env.step(action.numpy(), steps)
  174.                     
  175.                 memory.store(actor_state.numpy(), critic_state.numpy(), action.numpy(), reward, value.numpy())
  176.                 memory_full = (len(memory) == max_steps)
  177.                 actor_state = torch.Tensor(next_actor_state)
  178.                 critic_state = torch.Tensor(next_critic_state)
  179.                 traj_len += 1
  180.                 if memory_full:
  181.                     break
  182.             value = critic(critic_state)
  183.             memory.finish_path(last_val=(not done) * value.numpy())
  184.         return memory
  185.     def sample_parallel(self, env_fn, policy, critic, estimater, min_steps, max_traj_len, steps, deterministic=False, anneal=1.0, term_thresh=0):
  186.         worker = self.sample
  187.         args = (self.iteration_count, self.gamma, self.lam, env_fn, policy, critic, estimater, min_steps // self.n_proc, max_traj_len, steps, self.with_estimater, deterministic, anneal, term_thresh)
  188.         # Create pool of workers, each getting data for min_steps
  189.         workers = [worker.remote(*args) for _ in range(self.n_proc)]
  190.         result = ray.get(workers)
  191.         # O(n)
  192.         def merge(buffers):
  193.             merged = PPOBuffer(self.gamma, self.lam)
  194.             for buf in buffers:
  195.                 offset = len(merged)
  196.                 merged.actor_states  += buf.actor_states
  197.                 merged.critic_states += buf.critic_states
  198.                 merged.actions += buf.actions
  199.                 merged.rewards += buf.rewards
  200.                 merged.values  += buf.values
  201.                 merged.returns += buf.returns
  202.                 merged.ep_returns += buf.ep_returns
  203.                 merged.ep_lens    += buf.ep_lens
  204.                 merged.traj_idx += [offset + i for i in buf.traj_idx[1:]]
  205.                 merged.ptr += buf.ptr
  206.             return merged
  207.         total_buf = merge(result)
  208.         return total_buf
  209.     def update_policy(self, actor_obs_batch, critic_obs_batch, action_batch, return_batch, advantage_batch, mask, with_estimater, mirror_observation=None, mirror_action=None):
  210.         device="cpu"
  211.         self.policy.to(device)
  212.         self.critic.to(device)
  213.         self.estimater.to(device)
  214.         self.old_policy.to(device)
  215.         policy = self.policy
  216.         critic = self.critic
  217.         estimater = self.estimater
  218.         actor_obs_batch = actor_obs_batch.to(device)
  219.         critic_obs_batch = critic_obs_batch.to(device)
  220.         action_batch = action_batch.to(device)
  221.         return_batch = return_batch.to(device)
  222.         advantage_batch = advantage_batch.to(device)
  223.         
  224.         old_policy = self.old_policy
  225.         values = critic(critic_obs_batch)
  226.         pdf = policy.distribution(actor_obs_batch)
  227.         log_probs = pdf.log_prob(action_batch).sum(-1, keepdim=True)
  228.         old_pdf = old_policy.distribution(actor_obs_batch)
  229.         old_log_probs = old_pdf.log_prob(action_batch).sum(-1, keepdim=True)
  230.         # ratio between old and new policy, should be one at the first iteration
  231.         ratio = (log_probs - old_log_probs).exp()
  232.         # clipped surrogate loss
  233.         cpi_loss = ratio * advantage_batch * mask
  234.         clip_loss = ratio.clamp(1.0 - self.clip, 1.0 + self.clip) * advantage_batch * mask
  235.         actor_loss = -torch.min(cpi_loss, clip_loss).mean()
  236.         # only used for logging
  237.         clip_fraction = torch.mean((torch.abs(ratio - 1) > self.clip).float()).item()
  238.         # Value loss using the TD(gae_lambda) target
  239.         critic_loss = self.vf_coeff * F.mse_loss(return_batch, values)
  240.         if with_estimater:
  241.             estimater_loss = F.mse_loss(estimater(action_batch), abs(return_batch-values).detach())
  242.         # Entropy loss favor exploration
  243.         entropy_penalty = -(pdf.entropy() * mask).mean()
  244.         # Mirror Symmetry Loss
  245.         if mirror_observation is not None and mirror_action is not None:
  246.             deterministic_actions = policy(actor_obs_batch)
  247.             mir_obs = mirror_observation(actor_obs_batch)
  248.             mirror_actions = policy(mir_obs)
  249.             mirror_actions = mirror_action(mirror_actions)
  250.             mirror_loss = (deterministic_actions - mirror_actions).pow(2).mean()
  251.         else:
  252.             mirror_loss = torch.Tensor([0])
  253.         # Calculate approximate form of reverse KL Divergence for early stopping
  254.         # see issue #417: https://github.com/DLR-RM/stable-baselines3/issues/417
  255.         # and discussion in PR #419: https://github.com/DLR-RM/stable-baselines3/pull/419
  256.         # and Schulman blog: http://joschu.net/blog/kl-approx.html
  257.         with torch.no_grad():
  258.             log_ratio = log_probs - old_log_probs
  259.             approx_kl_div = torch.mean((ratio - 1) - log_ratio)
  260.         if approx_kl_div == float('inf') or torch.isnan(approx_kl_div) or approx_kl_div > 10000:
  261.             print("fatal, approx_kl_div is inf or nan")
  262.         if with_estimater:
  263.             return (
  264.             actor_loss,
  265.             entropy_penalty,
  266.             critic_loss,
  267.             estimater_loss,
  268.             approx_kl_div,
  269.             mirror_loss,
  270.             clip_fraction,
  271.         )
  272.         else:
  273.             return (
  274.                 actor_loss,
  275.                 entropy_penalty,
  276.                 critic_loss,
  277.                 # estimater_loss,
  278.                 approx_kl_div,
  279.                 mirror_loss,
  280.                 clip_fraction,
  281.             )
  282.     def train(self,
  283.               env_fn,
  284.               policy,
  285.               critic,
  286.               estimater,
  287.               n_itr,
  288.               anneal_rate=1.0):
  289.         self.old_policy = deepcopy(policy)
  290.         self.policy = policy
  291.         self.critic = critic
  292.         self.estimater = estimater
  293.         self.actor_optimizer = optim.Adam(policy.parameters(), lr=self.lr, eps=self.eps)
  294.         self.critic_optimizer = optim.Adam(critic.parameters(), lr=self.lr, eps=self.eps)
  295.         self.estimater_optimizer = optim.Adam(estimater.parameters(), lr=self.lr, eps=self.eps)
  296.         train_start_time = time.time()
  297.         obs_mirr, act_mirr = None, None
  298.         if hasattr(env_fn(), 'mirror_observation'):
  299.             obs_mirr = env_fn().mirror_clock_observation
  300.         if hasattr(env_fn(), 'mirror_action'):
  301.             act_mirr = env_fn().mirror_action
  302.         curr_anneal = 1.0
  303.         curr_thresh = 0
  304.         start_itr = 0
  305.         ep_counter = 0
  306.         do_term = False
  307.         test_ep_lens = []
  308.         test_ep_returns = []
  309.         for itr in range(n_itr):
  310.             print("********** Iteration {} ************".format(itr))
  311.             # set iteration count (could be used for curriculum training)
  312.             self.iteration_count = itr
  313.             sample_start_time = time.time()
  314.             if self.highest_reward > (2/3)*self.max_traj_len and curr_anneal > 0.5:
  315.                 curr_anneal *= anneal_rate
  316.             if do_term and curr_thresh < 0.35:
  317.                 curr_thresh = .1 * 1.0006**(itr-start_itr)
  318.             
  319.             self.policy.to("cpu")
  320.             self.critic.to("cpu")
  321.             self.estimater.to("cpu")
  322.             self.old_policy.to("cpu")
  323.             batch = self.sample_parallel(env_fn, self.policy, self.critic, self.estimater, self.batch_size, self.max_traj_len, itr, anneal=curr_anneal, term_thresh=curr_thresh)
  324.             actor_observations, critic_observations, actions, returns, values = map(torch.Tensor, batch.get())
  325.             num_samples = batch.storage_size()
  326.             elapsed = time.time() - sample_start_time
  327.             print("Sampling took {:.2f}s for {} steps.".format(elapsed, num_samples))
  328.             # Normalize advantage
  329.             advantages = returns - values
  330.             advantages = (advantages - advantages.mean()) / (advantages.std() + self.eps)
  331.             minibatch_size = self.minibatch_size or num_samples
  332.             self.total_steps += num_samples
  333.             self.old_policy.load_state_dict(policy.state_dict())
  334.             # Is false when 1.5*self.target_kl is breached
  335.             continue_training = True
  336.             optimizer_start_time = time.time()
  337.             for epoch in range(self.epochs):
  338.                 actor_losses = []
  339.                 entropies = []
  340.                 critic_losses = []
  341.                 estimater_losses = []
  342.                 kls = []
  343.                 mirror_losses = []
  344.                 clip_fractions = []
  345.                 if self.recurrent:
  346.                     random_indices = SubsetRandomSampler(range(len(batch.traj_idx)-1))
  347.                     sampler = BatchSampler(random_indices, minibatch_size, drop_last=False)
  348.                 else:
  349.                     random_indices = SubsetRandomSampler(range(num_samples))
  350.                     sampler = BatchSampler(random_indices, minibatch_size, drop_last=True)
  351.                 for indices in sampler:
  352.                     if self.recurrent:
  353.                         actor_obs_batch       = [actor_observations[batch.traj_idx[i]:batch.traj_idx[i+1]] for i in indices]
  354.                         critic_obs_batch       = [critic_observations[batch.traj_idx[i]:batch.traj_idx[i+1]] for i in indices]
  355.                         action_batch    = [actions[batch.traj_idx[i]:batch.traj_idx[i+1]] for i in indices]
  356.                         return_batch    = [returns[batch.traj_idx[i]:batch.traj_idx[i+1]] for i in indices]
  357.                         advantage_batch = [advantages[batch.traj_idx[i]:batch.traj_idx[i+1]] for i in indices]
  358.                         mask            = [torch.ones_like(r) for r in return_batch]
  359.                         actor_obs_batch  = pad_sequence(actor_obs_batch, batch_first=False)
  360.                         critic_obs_batch  = pad_sequence(critic_obs_batch, batch_first=False)
  361.                         action_batch     = pad_sequence(action_batch, batch_first=False)
  362.                         return_batch     = pad_sequence(return_batch, batch_first=False)
  363.                         advantage_batch  = pad_sequence(advantage_batch, batch_first=False)
  364.                         mask             = pad_sequence(mask, batch_first=False)
  365.                     else:
  366.                         actor_obs_batch       = actor_observations[indices]
  367.                         critic_obs_batch       = critic_observations[indices]
  368.                         action_batch    = actions[indices]
  369.                         return_batch    = returns[indices]
  370.                         advantage_batch = advantages[indices]
  371.                         mask            = 1
  372.                     # skip if is nan
  373.                     if torch.isnan(actor_obs_batch).any():
  374.                         break
  375.                     scalars = self.update_policy(actor_obs_batch, critic_obs_batch, action_batch, return_batch, advantage_batch, mask, self.with_estimater, mirror_observation=obs_mirr, mirror_action=act_mirr)
  376.                     if self.with_estimater:
  377.                         actor_loss, entropy_penalty, critic_loss, estimater_loss, approx_kl_div, mirror_loss, clip_fraction = scalars
  378.                     else:
  379.                         actor_loss, entropy_penalty, critic_loss, approx_kl_div, mirror_loss, clip_fraction = scalars
  380.                     actor_losses.append(actor_loss.item())
  381.                     entropies.append(entropy_penalty.item())
  382.                     critic_losses.append(critic_loss.item())
  383.                     if self.with_estimater:
  384.                         estimater_losses.append(estimater_loss.item())
  385.                     else:
  386.                         estimater_losses.append(0)
  387.                     kls.append(approx_kl_div.item())
  388.                     mirror_losses.append(mirror_loss.item())
  389.                     clip_fractions.append(clip_fraction)
  390.                     if self.target_kl is not None and approx_kl_div > 1.5 * self.target_kl:
  391.                         continue_training = False
  392.                         print(f"Early stopping at step {epoch} due to reaching max kl: {approx_kl_div:.2f}")
  393.                         break
  394.                     self.actor_optimizer.zero_grad()
  395.                     # (actor_loss + self.mirror_coeff*mirror_loss + self.ent_coeff*entropy_penalty).backward()
  396.                     (actor_loss + self.ent_coeff*entropy_penalty).backward()
  397.                     # Clip the gradient norm to prevent "unlucky" minibatches from
  398.                     # causing pathological updates
  399.                     torch.nn.utils.clip_grad_norm_(policy.parameters(), self.grad_clip)
  400.                     self.actor_optimizer.step()
  401.                     self.critic_optimizer.zero_grad()
  402.                     critic_loss.backward()
  403.                     # Clip the gradient norm to prevent "unlucky" minibatches from
  404.                     # causing pathological updates
  405.                     torch.nn.utils.clip_grad_norm_(critic.parameters(), self.grad_clip)
  406.                     self.critic_optimizer.step()
  407.                     if self.with_estimater:
  408.                         self.estimater_optimizer.zero_grad()
  409.                         estimater_loss.backward()
  410.                         self.estimater_optimizer.step()
  411.                     
  412.                 # Early stopping
  413.                 if not continue_training:
  414.                     break
  415.             elapsed = time.time() - optimizer_start_time
  416.             print("Optimizer took: {:.2f}s".format(elapsed))
  417.             if np.mean(batch.ep_lens) >= self.max_traj_len * 0.75:
  418.                 ep_counter += 1
  419.             if do_term == False and ep_counter > 50:
  420.                 do_term = True
  421.                 start_itr = itr
  422.             sys.stdout.write("-" * 37 + "\n")
  423.             sys.stdout.write("| %15s | %15s |" % ('Return (batch)', "%8.5g" % np.mean(batch.ep_returns)) + "\n")
  424.             sys.stdout.write("| %15s | %15s |" % ('Mean Eplen', "%8.5g" % np.mean(batch.ep_lens)) + "\n")
  425.             sys.stdout.write("| %15s | %15s |" % ('Actor loss', "%8.3g" % np.mean(actor_losses)) + "\n")
  426.             sys.stdout.write("| %15s | %15s |" % ('Critic loss', "%8.3g" % np.mean(critic_losses)) + "\n")
  427.             sys.stdout.write("| %15s | %15s |" % ('Estimater loss', "%8.3g" % np.mean(estimater_losses)) + "\n")
  428.             sys.stdout.write("| %15s | %15s |" % ('Mirror loss', "%8.3g" % np.mean(mirror_losses)) + "\n")
  429.             sys.stdout.write("| %15s | %15s |" % ('Mean KL Div', "%8.3g" % np.mean(kls)) + "\n")
  430.             sys.stdout.write("| %15s | %15s |" % ('Mean Entropy', "%8.3g" % np.mean(entropies)) + "\n")
  431.             sys.stdout.write("| %15s | %15s |" % ('Clip Fraction', "%8.3g" % np.mean(clip_fractions)) + "\n")
  432.             sys.stdout.write("-" * 37 + "\n")
  433.             sys.stdout.flush()
  434.             elapsed = time.time() - train_start_time
  435.             print("Total time elapsed: {:.2f}s. Total steps: {} (fps={:.2f})".format(elapsed, self.total_steps, self.total_steps/elapsed))
  436.             # save metrics
  437.             with open(self.train_fn, 'a') as out:
  438.                 out.write("{},{}\n".format(np.mean(batch.ep_returns), np.mean(batch.ep_lens)))
  439.             # To save time, perform evaluation only after 100 iters
  440.             if (itr+1)%self.eval_freq==0:
  441.                 # logger
  442.                 evaluate_start = time.time()
  443.                 self.policy.to("cpu")
  444.                 self.critic.to("cpu")
  445.                 self.estimater.to("cpu")
  446.                 test = self.sample_parallel(env_fn, self.policy, self.critic, self.estimater, self.batch_size, self.max_traj_len, n_itr, deterministic=True)
  447.                 eval_time = time.time() - evaluate_start
  448.                 print("evaluate time elapsed: {:.2f} s".format(eval_time))
  449.                 avg_eval_reward = np.mean(test.ep_returns)
  450.                 print("====EVALUATE EPISODE====  (Return = {})".format(avg_eval_reward))
  451.                 # save metrics
  452.                 with open(self.eval_fn, 'a') as out:
  453.                     out.write("{},{}\n".format(np.mean(test.ep_returns), np.mean(test.ep_lens)))
  454.                 test_ep_lens.append(np.mean(test.ep_lens))
  455.                 test_ep_returns.append(np.mean(test.ep_returns))
  456.                 plt.clf()
  457.                 xlabel = [i*self.eval_freq for i in range(len(test_ep_lens))]
  458.                 plt.plot(xlabel, test_ep_lens, color='blue', marker='o', label='Ep lens')
  459.                 plt.plot(xlabel, test_ep_returns, color='green', marker='o', label='Returns')
  460.                 plt.xticks(np.arange(0, itr+1, step=self.eval_freq))
  461.                 plt.xlabel('Iterations')
  462.                 plt.ylabel('Returns/Episode lengths')
  463.                 plt.legend()
  464.                 plt.grid()
  465.                 plt.savefig(os.path.join(self.save_path, 'eval.svg'), bbox_inches='tight')
  466.                 # save policy
  467.                 self.save(policy, critic, estimater, "_" + repr(itr))
  468.                 # save as actor.pt, if it is best
  469.                 if self.highest_reward < avg_eval_reward:
  470.                     self.highest_reward = avg_eval_reward
  471.                     self.save(policy, critic, estimater)
复制代码





三、根据源项目的模型文件样式,sketchup对机器人各个组件建模导出,然后用xml拼接起来
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图3
四、训练模型
python3 new_run_experiment.py train --logdir=trained/walk  --env=tp --task walk
python3 new_run_experiment.py train --logdir=trained/turn --env=tp --task turn
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图4
五、模型推理运行
python3 scripts/debug_stepper.py --path trained/walk  --task walk
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图5
六、部署机器人服务器端
python3 scripts/rpc_server.py --path trained/walk
rpc_server.py
  1. import os
  2. import time
  3. import argparse
  4. import torch
  5. import pickle
  6. import mujoco
  7. import mujoco_viewer
  8. import numpy as np
  9. import transforms3d as tf3
  10. import json
  11. from threading import Thread
  12. from queue import Queue
  13. import sys
  14. sys.path.append(".")
  15. from run_experiment import import_env
  16. import time
  17. from rpyc import Service
  18. from rpyc.utils.server import ThreadedServer
  19. from envs.jvrc import JvrcTpEnv as Env
  20. def main():
  21.     """
  22.     主函数,用于启动RPC服务器
  23.     """
  24.     # 解析命令行参数
  25.     parser = argparse.ArgumentParser()
  26.     parser.add_argument("--path",
  27.                         required=True,
  28.                         type=str,
  29.                         help="训练模型目录的路径",
  30.     )
  31.     args = parser.parse_args()
  32.     # 设置模型和pkl文件路径
  33.     path_to_actor = ""
  34.     path_to_pkl = ""
  35.     if os.path.isfile(args.path) and args.path.endswith(".pt"):
  36.         path_to_actor = args.path
  37.         path_to_pkl = os.path.join(os.path.dirname(args.path), "experiment.pkl")
  38.     if os.path.isdir(args.path):
  39.         path_to_actor = os.path.join(args.path, "actor.pt")
  40.         path_to_pkl = os.path.join(args.path, "experiment.pkl")
  41.     # 加载实验参数
  42.     run_args = pickle.load(open(path_to_pkl, "rb"))
  43.     # 加载训练好的策略
  44.     policy = torch.load(path_to_actor)
  45.     policy.eval()
  46.     # 如果策略有初始化隐藏状态的方法,则调用它
  47.     if hasattr(policy, 'init_hidden_state'):
  48.         policy.init_hidden_state()
  49.     # 加载估计器
  50.     estimater = torch.load(path_to_actor.replace("actor", "estimater"))
  51.     estimater.eval()
  52.     # 导入正确的环境
  53.     print(run_args.env)
  54.    
  55.     # 定义角度转换常量
  56.     degree2arch = 3.14/180
  57.     servo_pos2degree = 240/1000
  58.    
  59.     # 定义初始位置
  60.     init_pos =  [510, 450, 220, 420, 500, 500, 500, 725, 510, 510, 780, 590, 510, 500, 500, 275]
  61.     # init_pos =  [510, 450, 240, 420, 500, 500, 500, 725, 510, 510, 760, 590, 510, 500, 500, 275]
  62.     # 定义执行器映射关系
  63.     actuator_map = {0:7, 1:6, 2:5,
  64.                     3:15, 4:14, 5:13,
  65.                     6:12, 7:11, 8:10, 9:9, 10:8,
  66.                     11:4, 12:3, 13:2, 14:1, 15:0}
  67.     actuator_map_back = {0:8, 1:7, 2:6,}
  68.    
  69.     # 定义动作反转数组
  70.     # op maped from op3 should mul this
  71.     act_reverse = np.array([-1, 1, 1, -1, 1,
  72.                             1, -1, 1,
  73.                             -1, -1, -1, 1, 1,
  74.                             -1, 1, 1])
  75.    
  76.     # 从XML文件加载MuJoCo模型
  77.     model = mujoco.MjModel.from_xml_path("/tmp/mjcf-export/jvrc_tp/tp.xml")
  78.     data = mujoco.MjData(model)
  79.    
  80.    
  81.     def viewer_render(model, data, q):
  82.         """
  83.         渲染器函数,用于在MuJoCo中显示机器人状态
  84.         
  85.         参数:
  86.             model: MuJoCo模型
  87.             data: MuJoCo数据
  88.             q: 队列,用于接收显示的位置数据
  89.         """
  90.         viewer = mujoco_viewer.MujocoViewer(model, data)
  91.         show_qpos = np.array([0.]*16)
  92.         while True:
  93.             # 从队列中获取最新的位置数据
  94.             while not q.empty():
  95.                 show_qpos = q.get()
  96.             return_action = np.zeros_like(show_qpos, dtype=np.float32)
  97.             # 根据执行器映射关系转换位置数据
  98.             for k, v in actuator_map.items():
  99.                 return_action[v] = show_qpos[k]
  100.             show_qpos = return_action.tolist()
  101.             # 更新模型的位置和速度,并进行仿真步进
  102.             for i in range(10):
  103.                 data.qpos = [0,0,0.2,1,0,0,0] + show_qpos
  104.                 data.qvel = [0]*len(data.qvel)
  105.                 mujoco.mj_step(model, data)
  106.             # 渲染模型
  107.             viewer.render()
  108.             time.sleep(0.1)
  109.             # 检查查看器是否仍然活跃
  110.             if not viewer.is_alive:
  111.                 break
  112.    
  113.     # 创建队列和线程
  114.     q = Queue()
  115.     thread1 = Thread(target=viewer_render, args=(model, data, q))
  116.     thread1.start()
  117.     class TimeService(Service):
  118.         """
  119.         时间服务类,用于处理RPC请求
  120.         """
  121.         def __init__(self) -> None:
  122.             """
  123.             初始化时间服务
  124.             """
  125.             super().__init__()
  126.             self.pos_sum = np.array([0.]*16)
  127.             self.status = np.array([0.]*215)
  128.             self.last_action = np.array([0.]*16)
  129.             self.last_acc = None
  130.             self.sensor_acc_sum = np.array([0.]*3)
  131.             self.sensor_gyro_sum = np.array([0.]*3)
  132.         def exposed_test(self, status): # 在RPC 调用 名字加 exposed_ 前缀
  133.             """
  134.             测试方法,用于策略前向传播
  135.             
  136.             参数:
  137.                 status: 状态数据
  138.                
  139.             返回:
  140.                 策略输出的动作
  141.             """
  142.             return policy.forward(torch.Tensor(status), deterministic=True).detach().numpy()
  143.             
  144.         def exposed_init(self):
  145.             """
  146.             初始化方法,用于重置服务状态
  147.             """
  148.             self.pos_sum *= 0
  149.             self.status = np.array([0.]*240)
  150.             self.last_action = np.array([0.]*16)
  151.             self.last_acc = None
  152.             self.sensor_acc_sum = np.array([0.]*3)
  153.             self.sensor_gyro_sum = np.array([0.]*3)
  154.             # 如果策略有初始化隐藏状态的方法,则调用它
  155.             if hasattr(policy, 'init_hidden_state'):
  156.                 policy.init_hidden_state()
  157.         def exposed_call(self, param_in):
  158.             """
  159.             调用方法,用于处理客户端请求
  160.             
  161.             参数:
  162.                 param_in: 输入参数,包含位置、时间间隔、陀螺仪数据、加速度计数据、相位和命令
  163.                
  164.             返回:
  165.                 处理后的动作数据
  166.             """
  167.             time1 = time.time()
  168.             # 解析输入参数
  169.             finish_pos_in, time_interval, gyro, accel, phase, command = json.loads(param_in)
  170.             # print("input pos", finish_pos_in)
  171.             finish_pos_in = np.array(finish_pos_in, dtype=np.float32)
  172.             # 调整位置数据
  173.             finish_pos_in -= init_pos
  174.             finish_pos_in *= act_reverse
  175.             finish_pos_in *= servo_pos2degree*degree2arch
  176.             # 处理陀螺仪数据
  177.             root_ang_vel = np.array([gyro[2], gyro[0], gyro[1]])*degree2arch/10
  178.             self.sensor_gyro_sum = self.sensor_gyro_sum*0.8 + 0.2*root_ang_vel
  179.             # 处理加速度计数据
  180.             sensor_acc = np.array([-accel[0]/10, accel[1]/10, accel[2]/10])
  181.             self.sensor_acc_sum = self.sensor_acc_sum*0.8 + 0.2*sensor_acc
  182.             acc_diff = [sensor_acc[i] - self.sensor_acc_sum[i] for i in range(len(sensor_acc))]
  183.             
  184.             # 计算时钟信号
  185.             period = np.floor(2*0.8*(1/0.1))
  186.             clock = [np.sin(2 * np.pi * phase / period),
  187.                  np.cos(2 * np.pi * phase / period)]
  188.             print(clock)
  189.             ext_state = np.concatenate((clock, [1]))
  190.             # 构建状态向量
  191.             state = np.concatenate([
  192.                 clock,
  193.                 finish_pos_in,
  194.                 self.pos_sum,
  195.                 self.sensor_acc_sum,
  196.                 self.sensor_gyro_sum,
  197.                 # [0,0,0],
  198.                 # [0,0,0],
  199.                 acc_diff,
  200.                 command,
  201.             ])
  202.             print("state", state)
  203.             time2 = time.time()
  204.             # 策略前向传播
  205.             action = policy.forward(torch.Tensor(state).unsqueeze(0), deterministic=True).detach().squeeze(0).numpy()
  206.             
  207.             # 限制动作范围
  208.             action = np.clip(action, -3, 3)
  209.             # 对位置进行平滑处理
  210.             self.pos_sum = self.pos_sum*0.94 + action*0.06
  211.             
  212.             # print(state, action, "\n")
  213.             time3 = time.time()
  214.             action = self.pos_sum.copy()
  215.             self.last_action = action.copy()
  216.             # 调整动作数据
  217.             # return_action = np.zeros_like(action, dtype=np.float32)
  218.             # for k, v in actuator_map.items():
  219.             #     return_action[v] = action[k]
  220.             # action = return_action
  221.             action *= act_reverse
  222.             action /= float(servo_pos2degree*degree2arch)
  223.             action += init_pos
  224.             # print(return_action.tolist())
  225.             time4 = time.time()
  226.             # print("prepare data", time2-time1, "forward", time3-time2, "afterward", time4-time3, "total", time4-time1)
  227.             
  228.             # print("return", action, return_action)
  229.             # print(self.pos_sum, action)
  230.             # 返回处理后的动作数据
  231.             return json.dumps(action.tolist())
  232.      
  233.     # 启动RPC服务器
  234.     s = ThreadedServer(TimeService, port=18871) # 启动服务
  235.     s.start()
  236. if __name__=='__main__':
  237.     main()
复制代码



七、部署机器人本体客户端
在机器人树莓派端运行,树莓派与电脑处于同一网络,相当于客户端不停采集机器人舵机关节位置、imu值信息给服务器端以便推理,服务器端不断发送推理反馈的后续舵机位置。由于舵机仅单环位置闭环,无法精准控力矩 / 速度,无法反馈力矩而关节电机是三环闭环:位置 + 速度 + 电流(扭矩),动态抗干扰极强,所以和关节电机的机器人实际效果会差很多。而且很多舵机齿轮是塑料的,在推理频繁输出位置动作的工况下更加容易损坏。
python3 scripts/rpc_client.py
rpc_client.py
  1. import rpyc
  2. from hiwonder import Board, BusServoCmd
  3. import numpy as np
  4. from hiwonder import Board, Mpu6050
  5. import time
  6. import json
  7. mpu = Mpu6050.mpu6050(0x68)
  8. mpu.set_gyro_range(mpu.GYRO_RANGE_2000DEG)
  9. mpu.set_accel_range(mpu.ACCEL_RANGE_2G)
  10. conn = rpyc.connect("192.168.3.85", 18871)
  11. init_pos =  [510, 470, 220, 420, 500, 500, 500, 725, 510, 490, 760, 580, 510, 500, 500, 275]
  12. action_sum = np.array(init_pos)
  13. # for i in range(16):
  14. #     Board.setBusServoPulse(i + 1, init_pos[i], 100)
  15. time.sleep(0.5)
  16. print("act finish")
  17. for i in range(1, 17):
  18.     Board.unloadBusServo(i)
  19. time.sleep(2)
  20. start_pos = [0]*16
  21. finish_pos = [0]*16
  22. start_time = time.time()
  23. for i in range(1, 17):
  24.     start_pos[i-1] = Board.getBusServoPulse(i)
  25. stop_time = time.time()
  26. for i in range(1, 17):
  27.     finish_pos[i-1] = Board.getBusServoPulse(i)
  28. gyro = mpu.get_gyro_data()
  29. accel = mpu.get_accel_data()
  30. gyro = [gyro['x'], gyro['y'], gyro['z']]
  31. accel = [accel['x'], accel['y'], accel['z']]
  32. phase = 7
  33. start_time = 0
  34. start_pos = [0]*16
  35. finish_pos = [0]*16
  36. new_buf = bytearray(b'\x55\x5500000000')
  37. time1 = time.time()
  38. time4 = time1+0.1
  39. for i in range(1000):
  40.     if 0 < time4-time1 < 0.1:
  41.         time.sleep(max(0, 0.1-(time4-time1) - 0.001))
  42.     time1 = time.time()
  43.    
  44.     stop_time = time.time()
  45.     for i in range(16):
  46.         finish_pos[i] = Board.getBusServoPulse(i+1)
  47.     if start_time == 0:
  48.         start_pos = finish_pos
  49.         start_time = stop_time - 0.1
  50.     gyro = mpu.get_gyro_data()
  51.     accel = mpu.get_accel_data()
  52.     gyro = [gyro['x'], gyro['y'], gyro['z']]
  53.     accel = [accel['x'], accel['y'], accel['z']]
  54.     command = 1  # 添加command参数,1表示行走命令
  55.     time2 = time.time()
  56.     print(stop_time - start_time)
  57.     ret_action = conn.root.call(json.dumps([start_pos, finish_pos, stop_time-start_time, gyro, accel, phase, command]))
  58.     ret_action = json.loads(ret_action)
  59.     time3 = time.time()
  60.     action_sum = action_sum * 0.7 + np.array(ret_action) * 0.3  # 增加新动作的权重
  61.     for i in range(1, 17):
  62.         Board.setBusServoPulse(i, int(action_sum[i-1]), 50)  # 增加脉冲宽度
  63.    
  64.     time.sleep(0.03)
  65.     time4 = time.time()
  66.     print("get status ", time2-time1, "rpc call", time3-time2, "do action", time4-time3, "total", time4-time1)
  67.     phase += 1
  68.     start_pos = finish_pos
  69.     start_time = stop_time
  70.    
复制代码
八、学习记录:
机器学习_RL强化学习_02:跟着开源机器人学习强化学习图6
九、后续项目学习VLA,安装手眼相机完成自主抓取功能
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail