先从最简单的开始入门吧
主要参考:
阿里云强化学习训练营
主要改动:
类似巴普洛夫的狗
环境=>观测=>动作=>环境改变=>观测
通过奖励,训练模型采取更好的策略。
在倒立摆中,环境的观测值就是
动作为:
训练模型,让其学会左右移动,使得杆子与数值方向夹角为0
#导入gym和torch相关包
import gym
import random
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
超参数设置
#Hyperparameters
learning_rate = 0.0005 #学习率
gamma = 0.98 #
lmbda = 0.95 #
eps_clip = 0.1 #
K_epoch = 3 #
T_horizon = 20 #
class PPO(nn.Module):
def __init__(self):
super(PPO, self).__init__()
self.data = [] #用来存储交互数据
self.fc1 = nn.Linear(4, 256) #由于倒立摆环境简单,这里仅用一个线性变换来训练数据
self.fc_pi = nn.Linear(256, 2) #policy函数, 动作只有左和右,因此输出是2
self.fc_v = nn.Linear(256, 1) #value函数(输出v),值只有1,因此输出为1
self.optimizer = optim.Adam(self.parameters(), lr=learning_rate) #优化器
#policy函数
#输入观测值x
#输出动作空间概率,从而选择最优action
def pi(self, x, softmax_dim=0):
x = F.relu(self.fc1(x))
x = self.fc_pi(x)
prob = F.softmax(x, dim=softmax_dim)
# x形状20*2
# 这里的dim取0,代表获取同一样本的动作空间softmax
# 概率越大代表选择的可能性越大,在每次观测时,95%选择概率更大的动作,5%选择概率更小的动作。
# 下面有个地方dim取1,代表获取一个批次(例如20次尝试)的softmax,不同状态时的动作概率
return prob
#value函数
#输入观测值x
#输出x状态下value的预测值(reward),提供给policy函数作为参考值
def v(self, x):
x = F.relu(self.fc1(x))
v = self.fc_v(x)
return v
#把交互数据存入buffer
def put_data(self, transition):
self.data.append(transition)
#这里实际上做的是类型转换,把gym中env的observation,action等转换成torch Tensor
def make_batch(self):
s_lst, a_lst, r_lst, s_prime_lst, prob_a_lst, done_lst = [], [], [], [], [], []
for transition in self.data:
s, a, r, s_prime, prob_a, done = transition
s_lst.append(s)
a_lst.append([a])
r_lst.append([r])
s_prime_lst.append(s_prime)
prob_a_lst.append([prob_a])
done_mask = 0 if done else 1 # done表示游戏结束
done_lst.append([done_mask])
s,a,r,s_prime,done_mask, prob_a = torch.tensor(s_lst, dtype=torch.float), torch.tensor(a_lst), \
torch.tensor(r_lst), torch.tensor(s_prime_lst, dtype=torch.float), \
torch.tensor(done_lst, dtype=torch.float), torch.tensor(prob_a_lst)
self.data = []
return s, a, r, s_prime, done_mask, prob_a
#训练模型
def train_net(self):
s, a, r, s_prime, done_mask, prob_a = self.make_batch()
for i in range(K_epoch): #K_epoch:训练多少个epoch
#计算td_error 误差,value模型的优化目标就是尽量减少td_error
td_target = r + gamma * self.v(s_prime) * done_mask # 游戏结束了,长期期望为0
delta = td_target - self.v(s)
delta = delta.detach().numpy()
#计算advantage:
#即当前策略比一般策略(baseline)要好多少
#可能为正可能为负,进行调整
#policy的优化目标就是让当前策略比baseline尽量好
advantage_lst = []
advantage = 0.0
for delta_t in delta[::-1]:
advantage = gamma * lmbda * advantage + delta_t
advantage_lst.append([advantage])
advantage_lst.reverse()
advantage = torch.tensor(advantage_lst, dtype=torch.float)
#防止更新偏离太多
pi = self.pi(s, softmax_dim=1)
pi_a = pi.gather(1, a)
ratio = torch.exp(torch.log(pi_a) - torch.log(prob_a))
surr1 = ratio * advantage
surr2 = torch.clamp(ratio, 1 - eps_clip, 1 + eps_clip) * advantage
#这里简化ppo,把policy loss和value loss放在一起计算
loss = -torch.min(surr1, surr2) + F.smooth_l1_loss(
self.v(s), td_target.detach())
#梯度优化
self.optimizer.zero_grad()
loss.mean().backward()
self.optimizer.step()
observation => model => probability => action
get_action函数通过argmax获取概率最大的选项;这里只有两个动作,0和1,左和右
Categorical一般用于复杂动作,这里一开始用Categorical效果并不好,所以改成95%概率使用概率最大的动作,5%概率使用概率较小的动作。
使用random.uniform生成均匀分布
def get_action(prob):
# m = Categorical(prob)
# a = m.sample().item()
pp = random.uniform(0, 1)
a = int(torch.argmax(prob).numpy())
if pp < 0.95:
return 1 - a
return a
训练过程
#主函数:简化ppo 这里先交互T_horizon个回合然后停下来学习训练,再交互,这样循环10000次
def train():
#创建倒立摆环境
env = gym.make('CartPole-v1')
# from gym import wrappers
# env = wrappers.Monitor(env,"./cartpole", force=True)
model = PPO()
score = 0.0
print_interval = 20
#主循环
max_score = 0
for n_epi in range(2000):
s = env.reset()
done = False
while not done:
for t in range(T_horizon):
#由当前policy模型输出最优action
prob = model.pi(torch.from_numpy(s).float())
a = get_action(prob)
#用最优action进行交互
s_prime, r, done, info = env.step(a)
#存储交互数据,等待训练
model.put_data(
(s, a, r / 100.0, s_prime, prob[a].item(), done))
s = s_prime
score += r
if done:
break
#模型训练
model.train_net()
#打印每轮的学习成绩
if n_epi % print_interval == 0 and n_epi != 0:
print("# of episode :{}, avg score : {:.1f}".format(
n_epi, score / print_interval))
if score > max_score:
torch.save(model.state_dict(), 'pp.pt')
max_score = score
score = 0.0
env.close()
测试过程
from gym import wrappers
model = PPO()
model.load_state_dict(torch.load('pp.pt'))
env = gym.make('CartPole-v1')
env = wrappers.Monitor(env, "./cartpole", force=True)
#主循环
cnt = 0
for n_epi in range(100):
s = env.reset()
done = False
while not done:
#由当前policy模型输出最优action
prob = model.pi(torch.from_numpy(s).float())
a = get_action(prob)
#用最优action进行交互
s_prime, r, done, info = env.step(a)
# env.render()
cnt += 1
if cnt > 1000:
break
env.close()
以给定概率probs创建一个分布
dist = Categorical(probs)
index = dist.sample()
sample就是按照上面分布采样
eg:
probs = torch.FloatTensor([0.2, 0.8])
dist = Categorical(probs)
cnt = 0
for _ in range(100):
index = dist.sample()
if index == 0:
cnt += 1
print('0出现次数{},概率{:.2%}'.format(cnt,cnt / 100))
print('1出现次数{},概率{:.2%}'.format(100-cnt,1-cnt / 100))
0出现次数26,概率26.00%
1出现次数74,概率74.00%
y = torch.clamp(x, min, max)
| min , x < min
y = | x , min < x < max
| max , x > max
gather(x, dim, index)
在x的dim维度上,选择索引index的数据
x = torch.Tensor([
[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]
])
对于这个二维数组
我们知道,获取一个二维数组的值需要行列两个索引。
dim为0时,
行索引为index的值
列索引为index对应的列
例如下方:
index = torch.LongTensor([[0, 0, 0, 0]])
print(x.gather(0, index))
会得到
tensor([[1., 2., 3., 4.]])
行为index的值, 0,0,0,0
列为index的列, 1,2,3,4
dim为1时
行索引为index对应的行
列索引为index的值
例如下方:
index = torch.LongTensor([[0, 1, 2, 3]])
print(x.gather(1, index))
会得到
tensor([[1., 2., 3., 4.]])
行:index对应行: 0 0 0 0
列:index的值: 1 2 3 4