Stable Baselines3 (SB3) 是 PyTorch 中强化学习算法的一组可靠实现。它将一些算法打包,使得我们做强化学习时不需要重写网络架构和训练过程,只需要实例化算法、模型并且训练就可以了。

1、Import Dependencies

!pip install stable-baselines3[extra]

import os
import gym
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import DummyVecEnv
from stable_baselines3.common.evaluation import evaluate_policy

DummyVecRnv 用于将
evaluate_policy 使我们更容易测试环境是如何表现的

2、Environment

使用OpenAIGym,如果是自定义的环境,后面会讲如何将自定义环境转化为Gym类型的环境,但我们先用Gym中的
OpenAI Gym Spaces:动作和环境都是以向量的形式表现,由不同表现形式的向量组成不同的空间,有如下几种类型

Box:n维张量,是一些值的范,如:Box(0, 1, shape=(3, 3))表示形状为3x3的空间,空间中最小值为0,最大值为1
Discrete:离散值的集合,如:Discrete(3)表示取值可以为0,1,2
Tuple:其他空间的元组,可以将Box,Discrete组成元组,如Tuple(Discrete(2), Box(0, 100, shape=(1, ))),但是stable-baselines3不支持Tuple,可以用Dict代替。
Dict:空间的字典,如Dict({'height':Discrete(2), 'speed':Box(0, 100, shape=(1,))}) MultiBinary:独热编码二进制值,如MultiBinary(4)表示一个有4个位置的列表,每个位置可以取0或1
MultiDiscrete:如MultiDiscrete([5, 2, 2]),第一个位置可以取0,1,2,3,4;第二个位置可以取0,1…

我们用CartPole-v0为例,看看如何Load Environment

environment_name = 'CartPole-v0'
env = gym.make(environment_name)

运行环境:

episodes = 5 # 游戏轮次数为5
for episode in range(1, episodes+1):
	state = env.reset() # 游戏初始化,返回初始游戏状态
	done = False	# done判断游戏是否结束
	score = 0 # score存储每一轮游戏的总分数
	
	while not done:
		env.render()	# 渲染游戏画面
		action = env.action_space.sample() # 按照游戏的动作空间随机抽样(动作空间的类型和大小是事先定好的,只要是动作空间中的一个向量,都可以作为游戏的输入)
		n_state, reward, done, info = env.step(action) # 游戏向前迈进一步,返回当前状态,当前这步的奖励,游戏是否结束,以及其他信息
		score += reward
env.close() # 关闭游戏环境

理解环境:

env.action_space # 返回动作空间类型,如:Discrete(2)
env.observation_space # 返回环境空间
env.action_space.sample() # 从动作空间中随机取一个向量
env.observation_space.sample() # 从环境空间中随机取一个向量

3、Training

截至做笔记的时候,SB3已经有了以下算法,应该根据你所需要的动作空间和环境空间选择适合的算法。

stable_baselines过时怎么办 stable lines_python


在训练过程中,在一定步数后会产生一个Training Metrics,用来储存训练过程中的结果,下图是A2C的Training Metrics,我们接下来看一下这些都是什么意思

stable_baselines过时怎么办 stable lines_人工智能_02

ep_len_mean:表示平均每一轮的步数
ep_rew_mean:表示平均每一轮的奖励
fps:表示每秒钟运行的帧数
iteration:迭代次数
time_elapsed:运行时间
total_timesteps:总共运行步数
entropy_loss:熵损失
explained_variance:解释方差
learning_rate:策略更新的速度
n_updates:总的更新次数
policy_loss:策略损失
value_loss:价值损失

4、Train an RL Model

log_path = os.path.join('Training','logs') # 用于保存我们的TensorBoard,从而监视我们的训练过程,确保路径是存在的
env = gym.maek(environment_name)
env = DummyVecEnv([lambda: env]) # 将非矢量环境包装为矢量环境
model = PPO('MlpPolicy', env, verbose=1, tensorboard_log=log_path)

SB3有3种策略,‘MlpPolicy’,‘CnnPolicy’,‘MoltiInputPolicy’,不同的策略告诉Agent应该如何在环境中运行
verbose是其他的一些赘言,若为0,则无输出;若为1,则输出信息,若为2,则debug
tensorboard_log为tensorboard log 的储存地点

model.train(total_timestep=20000) # 模型训练20000次

5、Save and Reload Model

PPO_Path = os.path.join('Training', 'Saved Models', 'PPO_Model_Cartpole')
model.save(PPO_Path) # 将模型保存到指定路径
model = PPO.load(PPO_path, env=env) # 从指定路径中加载模型

6、Evaluation

evaluate_policy(model, env, n_eval_episodes=10, render=True) # 返回总平均奖励和标准偏差

n_eval_episodes指用多少轮来进行评估,若等于10,则是取10轮的平均数
render表示评估时是否需要展示画面
若要使用训练model,首先运行环境的代码需要改一下,这里我们不再使用随机的动作空间取样,而是用model来预测动作。

episodes = 5 # 游戏轮次数为5
for episode in range(1, episodes+1):
	obs = env.reset()
	done = False
	score = 0
	
	while not done:
		env.render()	
		action, _ = model.predict(obs) # 使用model来预测动作,返回预测的动作和下一个状态
		obs, reward, done, info = env.step(action) 
		score += reward
env.close()

7、Viewing Logs in Tensorboard

training_log_path = os.join('log_path','PPO_2') # 你自己储存的地址
!tensorboard --logdir={training_log_path} # 在Jupiter notebook中运行

8、Adding a call back to the training Stage

在我们训练量很大时,我们可以设置某个奖励阈值,当奖励达到阈值时可以停止训练和保存模型。

from stable_baselines3.common.callbacks import EvalCallback, StopTrainingOnRewardThreshold
stop_callback = StopTrainingOnRewardThreshold(reward_threshold=200, verbose=1) # 设置奖励阈值为200,即reward达到200后停止训练
save_path = os.path.join('Training','Saved Models')
eval_callback = EvalCallback(env,
							callback_on_new_best=stop_callback, # 每次有新的最好的模型都会运行stop_callback
							eval_freq=10000, #每10000次运行一次eval_callback
							best_model_save_path=save_path,  # 在eval_callback上运行最好的模型将会保存于此
							verbose=1)
model = PPO('MlpPolicy', env, verbose=1, tensorboard_log=log_path)
model.learn(total_timesteps=20000, callback=eval_callback)

9、Changing Policies

此处可以使用我们自制的,神经网络架构来替代A2C,PPO这些算法里面原来的神经网络架构。

net_arch = [dict(pi=[128, 128, 128, 128], vf=[128, 128, 128, 128])] # 定义一个新的神经网络架构

其中pi是policy神经网络,包含一共4层,每层128个神经元。vf是价值网络,同样包含4层,每层128个神经元。我们使用字典来包含这两个网络并将其放入一个列表中组成新的架构。

model = PPO('MlpPolicy', env, verbose=1, tensorboard_log=log_path, policy_kwargs={'net_arck':net_arch}) # 将我们新的架构塞进实例化的模型中

Using an Alternate Algorithm

from stable_baselines3 import DQN
moddel = DQN('MlpPolicy', env, verbose=1, tensorboard_log=log_path)

三个不同的项目实例

第一个实例是Atari游戏Breakout,第二个实例是自动驾驶,第三个实例是自定义环境

项目一

1、Import Dependencies

import gym 
from stable_baselines3 import A2C # 使用A2C算法
from stable_baselines3.common.vec_env import VecFrameStack  # 在之前的笔记中,我们一次只训练了一个环境,现在我们一次训练4个环境来加快训练
from stable_baselines3.common.evaluation import evaluate_policy
from stable_baselines3.common.env_util import make_atari_env # 允许我们和Atari环境互动
import os

2、Test Environment

为了运行Atari环境,我们还得做点工作,首先从http://www.atarimania.com/roms/Roms.rar上下载Roms.rar压缩文件,并将其解压到和你的工作文件夹下,其中有两个文件夹,分别为ROMS和HC ROMS,然后在jupiter notebook上运行

!python -m atari_py.import_roms .\ROMS\ROMS

后面的步骤与前面基本一致

environment_name = "Breakout-v0"
env = gym.make(environment_name)
env.action_space # action_space 为Discrete(4)
env.observation_space # env.observation_space 为Box(0, 255, (210, 160, 3), unit8)

observation_space看起来像一张长,宽分别为210,160(还是反过来?),RGB3通道的图像。

episodes = 5
for episode in range(1, episodes+1):
    state = env.reset()
    done = False
    score = 0 
    
    while not done:
        env.render()
        action = env.action_space.sample()
        n_state, reward, done, info = env.step(action)
        score+=reward
    print('Episode:{} Score:{}'.format(episode, score))
env.close() # 如果你想在一切结束后保持最后的画面,可以先不要close

可以在jupiter notebook中运行一下看看

3、Vectorise Environment and Train Model

和之前又有不同,这里我们要矢量化环境,从而能够一次运行、训练4个环境
因为我们要传入图像作为observation,所以这里我们使用CnnPolicy。

env = make_atari_env('Breakout-v0', n_envs=4, seed=0) # n_envs为我们想要的环境数目,seed设置随机数种子,以获得重复性
env = VecFrameStack(env, n_stack=4) #将4个环境打包起来,这之后使用render可以看到四个环境
log_path = os.path.join('Training', 'Logs')
model = A2C("CnnPolicy", env, verbose=1, tensorboard_log=log_path)
model.learn(total_timesteps=400000)

视频作者设置total_timesteps=100000训练用了16分钟

4、Save and Reload Model

a2c_path = os.path.join('Training', 'Saved Models', 'A2C_model')
model.save(a2c_path)
del model
env = make_atari_env('Breakout-v0', n_envs=1, seed=0) #当我们使用evaluate_policy的时候我们只能传入一个env,所以我们要重新导入一下
env = VecFrameStack(env, n_stack=4)
model = A2C.load(a2c_path, env)

5、Evaluate and Test

evaluate_policy(model, env, n_eval_episodes=10, render=True)

作者训练100k步的时候训练得不咋地,平均奖励为6.1,标准偏差为1.92。300k的时候平均奖励为12.7,标准偏差为6.53,好一些。200M步时平均奖励为22.22,标准偏差为9.06。当然,我们也可以不使用evaluate_policy,用原始的方法看一下它做的怎么样:

obs = env.reset()
while True:
    action, _states = model.predict(obs)
    obs, rewards, dones, info = env.step(action)
    env.render()
env.close()

项目二

这个项目是为了让一辆车按照一条随机生成的道路走。同样是项目一那5步走,但是也会有点不同
为了运行"赛车环境",我们要安装swig,不同系统的安装方法不同

1、Import Dependencies

#Install SWIG https://sourceforge.net/projects/swig/files/swigwin/swigwin-4.0.2/swigwin-4.0.2.zip/download?use_mirror=ixpeering
!pip install gym[box2d] pyglet==1.3.2  # 运行赛车环境还需要gym[box2d]
import gym 
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import VecFrameStack
from stable_baselines3.common.evaluation import evaluate_policy
import os

2、Test Environment

environment_name = "CarRacing-v0"
env = gym.make(environment_name)
env.action_space # Box(-1.0, 1.0, (3,), float32)
env.observation_space # Box(0, 255, (96, 96, 3), unit8)

3、Train Model

这里我们试一下PPO

env = gym.make(environment_name)
env = DummyVecEnv([lambda: env])
log_path = os.path.join('Training', 'Logs')
model = PPO("CnnPolicy", env, verbose=1, tensorboard_log=log_path)
model.learn(total_timesteps=40000)

4、Save Model

ppo_path = os.path.join('Training', 'Saved Models', 'PPO_Driving_model')
model.save(ppo_path)

5、Evaluate and Test

evaluate_policy(model, env, n_eval_episodes=10, render=True)
env.close()
obs = env.reset()
while True:
    action, _states = model.predict(obs)
    obs, rewards, dones, info = env.step(action)
    env.render()
env.close()

项目三

因为我们要自定义环境,所以有一堆的Dependencies要导入

1、Import Dependencies

# Import Gym Stuff
import gym 
from gym import Env
from gym.spaces import Discrete, Box, Dict, Tuple, MultiBinary, MultiDiscrete # 导入不同类型空间

# Import helpers
import numpy as np
import random
import os

# Import SB3 Stuff
from stable_baselines3 import PPO
from stable_baselines3.common.vec_env import VecFrameStack
from stable_baselines3.common.evaluation import evaluate_policy

Env是我们自定义环境的父类
导入这么多不同类型的空间只是为了展示如何在自定义环境中使用它们,在你自己的项目中,选择你需要的类型导入就可以。

2、Types of Spaces

在之前的笔记中已经说明了不同类型的空间,你可以试着运行以下代码来看看它们到底长什么样

Discrete(3).sample()
Box(0,1,shape=(3,)).sample()
Box(0,255,shape=(3,3), dtype=int).sample()
Tuple((Discrete(2), Box(0,100, shape=(1,)))).sample()
Dict({'height':Discrete(2), "speed":Box(0,100, shape=(1,))}).sample()
MultiBinary(4).sample()
MultiDiscrete([5,2,2]).sample()

3、Building an Environment

作者设置的环境是这样的,我们需要造一个Agent来给我们最好的洗澡温度,我们认为适宜温度区间是在37℃到39℃之间,Agent需要根据现有温度来做出一系列相应,从而让水温落入适宜温度区间。

class ShowerEnv(Env):
    def __init__(self):
        self.action_space = Discrete(3) # 动作有3个,分别是水温调低,不动,调高
        self.observation_space = Box(low=np.array([0]), high=np.array([100])) # 动作空间为一个元素的Box,温度范围为0~100℃,或者也可以写成Box(low=0, high=100, shape=(1,))效果是一样的
        self.state = 38 + random.randint(-3,3) # 设置起始温度
        self.shower_length = 60 # 洗60s,每秒可以采取一个动作
        
    def step(self, action):
        self.state += action -1 # 调温度。调低,不动,调高分别取0,1,2,所以-1就相当于改变-1,0,1℃
        self.shower_length -= 1 # 洗澡时间过去1s
        
        if self.state >=37 and self.state <=39: 
            reward =1 # 如果落在适宜温度内,则给奖励为1
        else: 
            reward = -1 # 否则给惩罚-1
            
        if self.shower_length <= 0: #洗完了就结束
            done = True 
        else:
            done = False
        
        # Apply temperature noise
        #self.state += random.randint(-1,1)
        # Set placeholder for info
        info = {}
        
        # Return step information
        return self.state, reward, done, info

    def render(self):
        pass # 没有图像需要render,跳过就好,但是要写
    
    def reset(self):
        self.state = np.array([38 + random.randint(-3,3)]).astype(float) # 重置初始水温
        self.shower_length = 60 #重置洗澡时间
        return self.state

初始化、step、render和reset是我们最需要造的4个基本的函数,记得要以Env作为我们自定义环境的父类

env=ShowerEnv()
from stable_baselines3.common.env_checker import check_env
check_env(env, warn=True)

4、Test Environment

episodes = 5
for episode in range(1, episodes+1):
    state = env.reset()
    done = False
    score = 0 
    
    while not done:
        env.render()
        action = env.action_space.sample()
        n_state, reward, done, info = env.step(action)
        score+=reward
    print('Episode:{} Score:{}'.format(episode, score))
env.close()

5、Train Model

log_path = os.path.join('Training', 'Logs')
model = PPO("MlpPolicy", env, verbose=1, tensorboard_log=log_path)
model.learn(total_timesteps=400000)

6、Save and Evaluate Model

model.save('PPO')
evaluate_policy(model, env, n_eval_episodes=10, render=True)

最后,感谢Youtuber Nicholas Renotte的课程,视频地址为https://www.youtube.com/watch?v=Mut_u40Sqz4&t=1150s Github:https://github.com/nicknochnack/ReinforcementLearningCourse