PARL与强化学习笔记
最近打算学习强化学习,看到了百度的公开课(https://aistudio.baidu.com/aistudio/education/group/info/1335),所以跟着学习记录一些笔记。
1.预习
1.人的视觉系统处理信息是分级的。
2.边缘特征 —–> 基本形状和目标的局部特征——>整个目标 这个过程其实和我们的常识是相吻合的,因为复杂的图形,往往就是由一些基本结构组合而成的。同时我们还可以看出:大脑是一个深度架构,认知过程也是深度的。
3.低层次特征 - - - - (组合) - - ->抽象的高层特征
1.1MNIST手写识别
1.基于anconda安装parl环境
conda create -n paddle python=3.7#创建3.7虚拟环境
conda info -e #查看自己创建的化境是否成功
conda activate paddle #进入此环境
pip install paddlepaddle #安装百度的paddle库
pip install parl #安装算法parl库
pip install gym #安装游戏环境库
2.搭建神经网络识别手写数字
首先搭建网络训练数据,后保存模型
#1.导包
from xml.sax.handler import property_declaration_handler
import numpy as np
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt
import os
#2.下载手写数据集
train_data=paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(),buf_size=512),batch_size=128)#下载训练集数据并打乱,缓冲512,batch128
test_data=paddle.batch(paddle.dataset.mnist.test(),batch_size=128)#下载测试集,batch128
"""
#观察一张手写的数据格式
temp_data=paddle.batch(paddle.dataset.mnist.train(),batch_size=1)
print(next(temp_data()))
"""
#3.搭建神经网络结构
##3.1网络结构
image=fluid.layers.data(name="image",shape=[1,28,28],dtype="float32")#定义输入层的数据格式
label=fluid.layers.data(name="label",shape=[1],dtype="int64")
def multilayer_perceptron(input):#定义多层感知机的结构(两个100和一个10)
hidden1=fluid.layers.fc(input=input,size=100,act="relu")#第一个隐层是:100个神经元的全连接层和relu激活层
hidden2=fluid.layers.fc(input=hidden1,size=100,act="relu")#第二个隐藏层:100个神经元的全连接层和relu激活层
prediction=fluid.layers.fc(input=hidden2,size=10,act="softmax")#第三层:10个神经元的全连接和softmax激活层
return prediction
model=multilayer_perceptron(image)
##3.2网络的损失函数和准确率函数
cost=fluid.layers.cross_entropy(input=model,label=label)#交叉熵损失函数
avg_cost=fluid.layers.mean(cost)
acc=fluid.layers.accuracy(input=model,label=label)
##3.3网络训练的优化方式
optimizer=fluid.optimizer.AdamOptimizer(learning_rate=0.001)#学习率位0.001的Adam优化器
opts=optimizer.minimize(avg_cost)#优化器优化均熵值
#4.模型训练与评估
##4.1定义解析器(cpu、gpu)并初始化参数
place=fluid.CPUPlace()
exe=fluid.Executor(place)#实例化了一个cpu解析器
exe.run(fluid.default_startup_program())#初始化解析器参数
feeder=fluid.DataFeeder(place=place,feed_list=[image,label])#定义输入数据的维度【图片,标签】
##4.2开始训练与测试
for epoch in range(5):
for batch_id,data in enumerate(train_data()):#每次拿一个batch的训练数据
train_cost,train_acc=exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost,acc])#训练一个batch后拿到avg_cost和acc
if batch_id%100==0:
print("epoch:%d,batch:%d,cost:%0.5f,accuracy:%0.5f"%(epoch,batch_id,train_cost[0],train_acc[0]))
test_costs=[]#测试,每一个epoch测试一次网络
test_accs=[]
for batch_id,data in enumerate(test_data()):
test_cost,test_acc=exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost,acc])
test_costs.append(test_cost[0])
test_accs.append(test_acc[0])
test_cost=(sum(test_costs)/len(test_costs))#求一个epoch里面测试集的所有batch的均值
test_acc=(sum(test_accs)/len(test_accs))
print("test:%d,cost:%0.5f,accuracy:%0.5f"%(epoch,test_cost,test_acc))
model_save_dir="C:\\Users\\Administrator\\Desktop\\parl\\out"#保存模型
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print("save models to %s"%(model_save_dir))
fluid.io.save_inference_model(model_save_dir,#保存推理model的路径
['image'],#模型feed的数据
[model],#模型的ariables
exe)#保存inference mode
3.然后用此模型进行手写数据图片的预测
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
#1.首先对拿到的图片进行预处理,转化为网络数据的数据格式
def load_image(file):
im=Image.open(file).convert("L")#将RGB转化为灰度图像,L代表灰度图像,灰度图像的像素值在0~255之间
im=im.resize((28,28),Image.ANTIALIAS)#resize image with high-quality 图像大小为28*28
im=np.array(im).reshape(1,1,28,28).astype(np.float32)#返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式。
im=im/255.0*2.0-1.0#归一化到【-1~1】之间
print(im)
return im
img=Image.open("C:\\Users\\Administrator\\Desktop\\parl\\data\\6.png")
plt.imshow(img)#根据数组绘制图像
plt.show()#显示图像
#2.加载模型进行预测
place=fluid.CPUPlace()
exe=fluid.Executor(place)#实例化了一个cpu解析器
scope=fluid.core.Scope()#实例化
img=load_image("C:\\Users\\Administrator\\Desktop\\parl\\data\\6.png")
with fluid.scope_guard(scope):
#推理Program,targetname是一个str列表,它包含需要在推理 Program 中提供数据的变量的名称。fetch_targets:是一个 Variable 列表,从中我们可以得到推断结果。model_save_dir:模型保存的路径
[inference_program,feed_target_names,fetch_targets]=fluid.io.load_inference_model("C:\\Users\\Administrator\\Desktop\\parl\\out",exe)
results = exe.run(program=inference_program, #运行推测程序
feed={feed_target_names[0]: img}, #喂入要预测的img
fetch_list=fetch_targets) #得到推测结果,
lab = np.argsort(results) #argsort函数返回的是result数组值从小到大的索引值
print("该图片的预测结果的label为: %d" % lab[0][0][-1]) #-1代表读取数组中倒数第一列
1.2 python基础知识
#1.字符串
print(firstVariable.lower())
print(firstVariable.upper())
print(firstVariable.title())
# To look up what each method does
firstVariable.lower?
help(firstVariable.lower)
#2.列表
print(min(z), max(z), len(z), sum(z))
random_list = [4, 1, 5, 4, 10, 4]
random_list.count(4)
random_list.index(4)#返回列表第一个指针
# you can specify where you start your search
random_list.index(4, 3)
# random_list.index(value, [start, stop])
random_list.index(4, 5, 6)
x.sort()#列表排序
print(x)
x.sort(reverse = True)
new_list = sorted(y)
new_list
x.insert(4, [4, 5])#[11, 8, 7, 3, [4, 5], 2, 3, 4, 5]
#3.字典
webstersDict.update({'shirt': 'a', 'shoe': 'b'})#添加
del webstersDict['resist']#删除
storyCount.get('Michael', 0)#获取M的值,如果没有添加值为0
count = storyCount.pop('the')#删除键,但同时可以返回值
print(storyCount.keys())
print(storyCount.values())
for key, value in webstersDict.items():
print(key, value)
#4.元组,元组是不可变的,这意味着在初始化元组之后,不可能更新元组中的单个项。
tup1 = ('Michael',)#重要的是要记住,如果要创建仅包含一个值的元组,则需要在项目后面添加一个逗号。
print(animals.index('lama'))#index方法返回对应值的第一个索引
print(animals.count('lama'))#count方法返回值在元组中出现的次数。
#元组比列表更快。
import timeit
print('Tuple time: ', timeit.timeit('x=(1,2,3,4,5,6,7,8,9,10,11,12)', number=1000000))
print('List time: ', timeit.timeit('x=[1,2,3,4,5,6,7,8,9,10,11,12]', number=1000000))
#一些元组可以用作字典键(特别是包含不可变值的元组,如字符串,数字和其他元组)。列表永远不能用作字典键,因为列表不是不可变的
#isinstance()函数用于确定实例是否也是某个父类的实例。
1.3paddle基础知识
1.3.1计算常量的加法:1+1
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
#1.定义张量
x1=fluid.layers.fill_constant(shape=[2,2],value=1,dtype='int64')#常量形状是[2, 2],并赋值为1铺满整个张量,类型为int64.
x2=fluid.layers.fill_constant(shape=[2,2],value=1,dtype='int64')
y1=fluid.layers.sum(x=[x1,x2])# 将两个张量求和,fluid.layers加减乘除、三角函数等张量操作。
#2.初始化解释器
place=fluid.CPUPlace()# 创建一个使用CPU的解释器,当使用CPUPlace()时使用的是CPU,如果是CUDAPlace()使用的是GPU
exe=fluid.executor.Executor(place)
exe.run(fluid.default_startup_program())# 解释器进行参数初始化
#3.执行
result=exe.run(program=fluid.default_main_program(),fetch_list=[y1])#执行计算,program的参数值是主程序,program默认一共有两个,分别是default_startup_program()和default_main_program()。fetch_list参数的值是在解析器在run之后要输出的值,最后计算得到的也是一个张量。
print(result)#[array([[2, 2],[2, 2]])]
1.3.2计算变量的加法:1+1
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
import numpy as np
#1.定义变量
a=fluid.layers.create_tensor(dtype="int64",name="a")#不指定该张量的形状和值,它们是之后动态赋值的。这里只是指定它们的类型和名字,这个名字是我们之后赋值的关键。
b=fluid.layers.create_tensor(dtype="int64",name="b")
y=fluid.layers.sum(x=[a,b])
#2.初始化解释器
place=fluid.CPUPlace()# 创建一个使用CPU的解释器,当使用CPUPlace()时使用的是CPU,如果是CUDAPlace()使用的是GPU
exe=fluid.executor.Executor(place)
exe.run(fluid.default_startup_program())# 解释器进行参数初始化
#3.执行
a1=np.array([3,2]).astype("int64")#使用numpy创建两个张量值,之后我们要计算的就是这两个值
b1=np.array([1,1]).astype("int64")
out_a,out_b,result=exe.result=exe.run(program=fluid.default_main_program(),feed={"a":a1,"b":b1},fetch_list=[a,b,y])#feed参数对张量变量进行赋值的。赋值的方式是使用了键值对的格式,key是定义张量变量是指定的名称,value就是要传递的值。
print(out_a,"+",out_b,"=",result)#[3 2] + [1 1] = [4 3]
1.3.3使用PaddlePaddle做线性回归,满足规律y = 2 * x + 1
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
import numpy as np
#1.定义网络结构和损失优化
##1.1网络结构,一个简单的线性网络
x=fluid.layers.data(name="x",shape=[13],dtype="float32")#输入13维数据,这里使用输入fluid.layers.data()定义的输入层类似fluid.layers.create_tensor(),也是有name属性,之后也是根据这个属性来填充数据的。
hidden=fluid.layers.fc(input=x,size=100,act="relu")#100个神经元,激活函数是ReLU的全连接层
net=fluid.layers.fc(input=hidden,size=1,act=None)#输出一个预测值
##1.2损失函数,复制主程序用于预测
y=fluid.layers.data(name="y",shape=[1],dtype="float32")
cost=fluid.layers.square_error_cost(input=net,label=y)#这里使用了平方差损失函数(square_error_cost),PaddlePaddle提供了很多的损失函数的接口,比如交叉熵损失函数(cross_entropy)
avg_cost=fluid.layers.mean(cost)#因为fluid.layers.square_error_cost()求的是一个Batch的损失值,所以我们还要对他求一个平均值。
test_program=fluid.default_main_program().clone(for_test=True)#在主程序(fluid.default_main_program)中克隆一个程序作为预测程序,用于训练完成之后使用这个预测程序进行预测数据。
##1.3优化
optimizer=fluid.optimizer.SGDOptimizer(learning_rate=0.01)#使用的随机梯度下降法(SGD),还有Momentum、Adagrad、Adagrad等等
opts=optimizer.minimize(avg_cost)
#2.创建并初始化解释器
place=fluid.CPUPlace()# 创建一个使用CPU的解释器,当使用CPUPlace()时使用的是CPU,如果是CUDAPlace()使用的是GPU
exe=fluid.executor.Executor(place)
exe.run(fluid.default_startup_program())# 解释器进行参数初始化
#3.给训练和测试的张量数据
x_data = np.array([[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]).astype('float32')
y_data = np.array([[3.0], [5.0], [7.0], [9.0], [11.0]]).astype('float32')
test_data = np.array([[6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]).astype('float32')
#4.开始训练
for epoch in range(10):
train_cost=exe.run(program=fluid.default_main_program(),feed={"x":x_data,"y":y_data},fetch_list=[avg_cost])
print("epoch:%d,cost:%0.5f"%(epoch,train_cost[0]))
#5.开始测试,使用上面克隆主程序得到的预测程序了预测我们刚才定义的预测数据
result=exe.run(program=test_program,feed={"x":test_data,"y":np.array([[0.0]]).astype('float32')},fetch_list=[net])#预测数据同样作为x在feed输入,在预测时,理论上是不用输入y的,但是要符合输入格式,我们模拟一个y的数据值,这个值并不会影响我们的预测结果。
print("当x为6.0时,y为:%0.5f"%result[0][0][0])#当x为6.0时,y为:13.25733
1.3.4用PaddlePaddle做房价预测
数据集共506行,每行14列。前13列用来描述房屋的各种信息,最后一列为该类房屋价格中位数。
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
import numpy as np
import matplotlib.pyplot as plt
import os
BUF_SIZE=500
BATCH_SIZE=20
#1.准备数据,PaddlePaddle提供了读取uci_housing训练集和测试集的接口,分别为paddle.dataset.uci_housing.train()和paddle.dataset.uci_housing.test()。
train_data=paddle.batch(paddle.reader.shuffle(paddle.dataset.uci_housing.train(),buf_size=BUF_SIZE),batch_size=BATCH_SIZE)#paddle.reader.shuffle()表示每次缓存BUF_SIZE个数据项,并进行打乱
test_data=paddle.batch(paddle.reader.shuffle(paddle.dataset.uci_housing.test(),buf_size=BUF_SIZE),batch_size=BATCH_SIZE)#paddle.batch()表示每BATCH_SIZE组成一个batch
#2.配置网络并初始化
x=fluid.layers.data(name="x",shape=[13],dtype="float32")
y=fluid.layers.data(name="y",shape=[1],dtype="float32")
y_predict=fluid.layers.fc(input=x,size=1,act=None)
cost=fluid.layers.square_error_cost(input=y_predict,label=y)
avg_cost=fluid.layers.mean(cost)
optimizer=fluid.optimizer.SGDOptimizer(learning_rate=0.001)
opts=optimizer.minimize(avg_cost)
test_program=fluid.default_main_program().clone(for_test=True)
use_cuda=False
place=fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe=fluid.Executor(place)
exe.run(fluid.default_startup_program())
feeder=fluid.DataFeeder(place=place,feed_list=[x,y])#DataFeeder负责将数据提供器返回的数据转成一种特殊的数据结构,使其可以输入到Executor中。
#3.绘制训练过程的损失值变化趋势的方法draw_train_process
iter=0
iters=[]
train_costs=[]
def draw_train_process(iters,train_costs):
title="training cost"
plt.title(title,fontsize=24)
plt.xlabel("iter",fontsize=14)
plt.ylabel("cost",fontsize=14)
plt.plot(iters,train_costs,color="red",label="training cost")
plt.grid()
plt.show()
#4.训练并保存模型
EPOCH_NUM=50
model_save_dir="C:\\Users\\Administrator\\Desktop\\parl\out\\house_model"
for epoch in range(EPOCH_NUM):
train_cost=0
for batch_id,data in enumerate(train_data()):#训练
train_cost=exe.run(program=fluid.default_main_program(),feed=feeder.feed(data),fetch_list=[avg_cost])
if batch_id%40==0:#506//20<40,所以一个epoch输出一次,在id==0时
print("epoch:%d,cost:%0.5f"%(epoch,train_cost[0][0]))
iter=iter+BATCH_SIZE
iters.append(iter)#样本数*EPOCH_NUM的累计
train_costs.append(train_cost[0][0])#每个batch的train_cost
test_cost=0
for batch_id,data in enumerate(test_data()):#测试
test_cost=exe.run(program=test_program,feed=feeder.feed(data),fetch_list=[avg_cost])
print("test::%d,cost:%.5f"%(epoch,test_cost[0][0]))
if not os.path.exists(model_save_dir):#保存模型
os.makedirs(model_save_dir)
print("save models to %s"%(model_save_dir))
#保存训练参数到指定路径中,构建一个专门用预测的program
fluid.io.save_inference_model(model_save_dir, #保存推理model的路径
['x'], #推理(inference)需要 feed 的数据
[y_predict], #保存推理(inference)结果的 Variables
exe) #exe 保存 inference model
draw_train_process(iters,train_costs)#画图
导出模型,再次预测
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
import numpy as np
import matplotlib.pyplot as plt
#1.创建预测用的executor
use_cuda=False
place=fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
infer_exe=fluid.Executor(place)
inference_scope=fluid.core.Scope()#Scope指定作用域
#2.可视化真实值与预测值
infer_results=[]#预测值列表
groud_truths=[]#真实y值列表
def draw_infer_result(groud_truths,infer_results):
title="Boston"
plt.title(title,fontsize=24)
x=np.arange(1,20)
y=x
plt.plot(x,y)
plt.xlabel("graund_truth",fontsize=14)
plt.ylabel("infer result",fontsize=14)
plt.scatter(groud_truths,infer_results,color="green",label="training cost")
plt.grid()
plt.show()
#3.开始预测,通过fluid.io.load_inference_model,预测器会从params_dirname中读取已经训练好的模型,来对从未遇见过的数据进行预测。
model_save_dir="C:\\Users\\Administrator\\Desktop\\parl\out\\house_model"
with fluid.scope_guard(inference_scope):#修改全局/默认作用域(scope), 运行时中的所有变量都将分配给新的scope。
#[推理的program,需要在推理program中提供数据的变量名称,推理结果]
[inference_program,feed_target_names,fetch_targets]=fluid.io.load_inference_model(model_save_dir,infer_exe)
infer_reader=paddle.batch(paddle.dataset.uci_housing.test(),batch_size=200) #获取预测数据器
test_data=next(infer_reader())#获取数据
test_x=np.array([data[0] for data in test_data]).astype("float32")
test_y =np.array([data[1] for data in test_data]).astype("float32")
results=infer_exe.run(inference_program,feed={feed_target_names[0]:np.array(test_x)},fetch_list=fetch_targets)
print("infer results: (House Price)")
for idx,val in enumerate(results[0]):
print("%d : %.2f"%(idx,val))
infer_results.append(val)
print("ground truth:")
for idx,val in enumerate(test_y):
print("%d : %.2f"%(idx,val))
groud_truths.append(val)
draw_infer_result(groud_truths,infer_results)
2.初识
1.智能体agent在环境environment中学习,根据环境的状态state(或观测到的observation),执行动作action,并根据环境的反馈 reward(奖励)来指导更好的动作。
2.CliffWalking, 悬崖环境游戏(pip install pygame)。
import paddle as paddle
paddle.enable_static()#paddle2.0以上需要加这一行代码
import paddle.fluid as fluid
import numpy as np
import gym
if __name__=="__main__":
env=gym.make("CliffWalking-v0")#游戏环境
# 环境1:FrozenLake, 可以配置冰面是否是滑的
# env = gym.make("FrozenLake-v0", is_slippery=False)
env.reset()
for step in range(100):
action=np.random.randint(0,4)
obs,reward,done,info=env.step(action)
print('step {}: action {}, obs {}, reward {}, done {}, info {}'.format(\
step, action, obs, reward, done, info))
env.render()
3.前沿研究方向:Model-base RL,Hierarchical RL,Multi Agent RL,Meta Learning。
4.两部分(agent和environment),三要素(state\observation,actiion,reward)。
5.Agent有两种学习方案:基于价值(每个状态赋予价值,Sarsa,Q-learning,DQN)和基于策略(每个动作选择的概率,policy gradient)。
6.跑PARL 的Quick start:首先去https://github.com/PaddlePaddle/PARL.git下载包,然后:
3.基于表格型求解RL
3.1 sarsa
state-action-reward-state-action(sarsa),状态价值和动作价值都采取E-greedy函数,在线学习(on-policy)。
from turtle import delay
import gym
import numpy as np
import time
#1.定义sarsa的agent,它的行动与预测行动都采用e-greedy方法(大概率最优,小概率随机),最后test时采用predict取最优那个行为。
class SarsaAgent(object):
def __init__(self, obs_n,act_n,learning_rate=0.01,gamma=0.9,e_greed=0.1):
self.act_n=act_n#动作维度
self.lr=learning_rate#学习率
self.gamma=gamma#reward的衰减率
self.epsilon=e_greed#按一定概率随机选动作
self.Q=np.zeros((obs_n,act_n))#Q表格
#根据Q表格选择最优的动作
def predict(self,obs):
Q_list=self.Q[obs,:]#取出此状态下的动作Q表格
maxQ=np.max(Q_list)#最大值是
action_list=np.where(Q_list==maxQ)[0]#最大值的坐标,即第几个动作,maxQ可能对应多个action
action=np.random.choice(action_list)#随机,最大值得动作
return action
#e-greedy,e_greed下选择随机动作,1-e_greed概率下选择最优动作
def sample(self,obs):
if np.random.uniform(0,1)<(1.0-self.epsilon):
action=self.predict(obs)
else:
action=np.random.choice(self.act_n)
return action
#学习方法,即更新Q表格的方法
def learn(self,obs,action,reward,next_obs,next_action,done):
predict_Q=self.Q[obs,action]#此状态下选择的动作的Q值
if done:
target_Q=reward#已经给出了这局的reward,没有下一个状态了,这局结束
else:
target_Q=reward+self.gamma*self.Q[next_obs,next_action]#下个状态和e-greed的动作下的reward值
self.Q[obs,action]+=self.lr*(target_Q-predict_Q)#sarsa的reward计算公式,更新Q表格
# 保存Q表格数据到文件
def save(self):
npy_file='./q_table.npy'
np.save(npy_file,self.Q)
print(npy_file+' saved.')
#从文件中读取Q值到Q表格中
def restore(self,npy_file='./q_table.npy'):
self.Q=np.load(npy_file)
print(npy_file+' loaded.')
#2.训练与测试
def run_episode(env,agent,render=False):
total_steps=0#每个episode走了多少step
total_reward=0
obs=env.reset()#重置环境,新开一局
action=agent.sample(obs)#第一步,根据算法选择一个动作
while True:
next_obs,reward,done,_=env.step(action)#动作后与环境的交互
next_action=agent.sample(next_obs)#根据下一状态选择下一个动作
agent.learn(obs,action,reward,next_obs,next_action,done)
action=next_action
obs=next_obs
total_reward+=reward
total_steps+=1
if render:
env.render()
delay(60)
if done:
break
return total_reward,total_steps
def test_episode(env,agent):
total_reward=0
obs=env.reset()
while True:
action=agent.predict(obs)
next_obs,reward,done,_=env.step(action)
total_reward+=reward
obs=next_obs
env.render()
delay(60)
if done:
break
return total_reward
#3.实例化env和agent进行训练
env=gym.make("CliffWalking-v0")
from gridworld import CliffWalkingWapper
env=CliffWalkingWapper(env)
agent=SarsaAgent(obs_n=env.observation_space.n,act_n=env.action_space.n,learning_rate=0.1,gamma=0.9,e_greed=0.1)
for episode in range(500):#训练500个episode局
if (episode+1)%50==0:#每成功50次渲染一局
render=True
else:
render=False
ep_reward,ep_steps=run_episode(env,agent,render)
print("episode %s:steps=%s,reward=%.1f"%(episode,ep_steps,ep_reward))
test_reward =test_episode(env,agent)#训练完后测试一下
print("test reward =%.1f"%(test_reward))
其中的gridworld.py是刚从官网下载的文件夹的lesson2里面的文件,用于图形化渲染。
3.2q_learning
sarsa与q_learning的最大区别就是q表的更新方式不同(learn函数)。
import gym
import time
import numpy as np
#1.创建q_learning的Agent类
class QLearningAgent(object):
def __init__(self,obs_n,act_n,learning_rate=0.01,gamma=0.9,e_greed=0.1):
self.act_n=act_n
self.lr=learning_rate
self.gamma=gamma
self.epsilon=e_greed
self.Q=np.zeros((obs_n,act_n))
def predict(self,obs):#根据状态选出最优动作
Q_list=self.Q[obs,:]
maxQ=np.max(Q_list)
action_list=np.where(Q_list==maxQ)[0]#maxQ可能对应多个action
action=np.random.choice(action_list)
return action
def sample(self,obs):#根据状态选择e-greedy动作
if np.random.uniform(0,1)<(1.0-self.epsilon):
action=self.predict(obs)
else:
action=np.random.choice(self.act_n)
return action
#learn其实就是根据agent在environment中的状态和动作选择而产生的Q表更新的过程
def learn(self,obs,action,reward,next_obs,done):#与sarsa最大的区别就是学习方式的不同,对q表的更新方式不同
predict_Q=self.Q[obs,action]#此状态与动作原来的Q表值
if done:
target_Q=reward# 没有下一个状态了
else:
target_Q=reward+self.gamma*np.max(self.Q[next_obs,:])#此状态与动作实际的奖励值
self.Q[obs,action]+=self.lr*(target_Q-predict_Q)#修正Q
def save(self):
npy_file='./q_table2.npy'
np.save(npy_file,self.Q)
print(npy_file+' saved')
def restore(self,npy_file='./q_table2.npy'):
self.Q=np.load(npy_file)
print(npy_file+' loaded.')
#2.Agent在environment中的训练交互与最优检测
def run_episode(env,agent,render=False):
total_steps=0# 记录每个episode走了多少step
total_reward=0
obs=env.reset()# 重置环境, 重新开一局(即开始新的一个episode)
while True:
action=agent.sample(obs)#根据状态,用e-greedy选择一个动作
next_obs,reward,done,_=env.step(action)#与环境的交互反应,产生了reward和下一状态,这由环境类来决定
agent.learn(obs,action,reward,next_obs,done)
obs=next_obs
total_reward+=reward#这局每步给分的累加
total_steps+=1# 计算step数
if render:
env.render()
if done:
break
return total_reward,total_steps
def test_episode(env,agent):
total_reward=0
obs=env.reset()
while True:
action=agent.predict(obs)
next_obs,reward,done,_=env.step(action)
total_reward+=reward
obs=next_obs
env.render()
if done:
break
return total_reward
#3.创建对象,训练并测试
env=gym.make("CliffWalking-v0")
from gridworld import CliffWalkingWapper
env=CliffWalkingWapper(env)
agent=QLearningAgent(obs_n=env.observation_space.n,act_n=env.action_space.n,learning_rate=0.1,gamma=0.9,e_greed=0.1)
for episode in range(500):
if (episode+1)%50==0:
render=True
else:
render=False
ep_reward,ep_steps=run_episode(env,agent,render)
print("episode %s :steps=%s ,reward=%.1f"%(episode,ep_steps,ep_reward))
test_reward=test_episode(env,agent)
print("test reward =%.1f"%(test_reward))
4.DQN基于神经网络求解RL
环境env给reward,神经网络代替q表格,获取pred,根据qlearning的公式中reward(env提供)和pred下一状态最大回报(q表格)计算出target,然后结合学习率更新神经网络参数(q表格参数)。
创建DQN.py文件训练
import random
import collections
import parl
import paddle as paddle
#paddle.enable_static()#paddle2.0以上需要加这一行代码
from parl import layers#cannot import name 'layers' from 'parl'>>>pip uninstall parl>>>pip install parl==1.3.1
import paddle.fluid as fluid
import copy
import numpy as np
import os
import gym
from parl.utils import logger
LEARN_FREQ=5#训练频率,每5次训练learn一次
MEMORY_SIZE=20000#replay memory 的大小,越大越占内存,DQN的经验回放池的大小
MEMORY_WARMUP_SIZE=200#replay_memory 里需要预存一些经验数据,再开启训练
BATCH_SIZE=32#从replay memory随机里sample一批数据出来
LEARNING_RATE=0.001
GAMMA=0.99#reward 的衰减因子,一般取 0.9 到 0.999 不等
#1.Model用来定义前向(Forward)网络,神经网络结构(输入obs,输出act_dim维的q值)
class Model(parl.Model):
def __init__(self,act_dim):
hid1_size=128
hid2_size=128
self.fc1=layers.fc(size=hid1_size,act='relu')#第一隐藏层128
self.fc2=layers.fc(size=hid2_size,act='relu')#第二隐藏层128
self.fc3=layers.fc(size=act_dim,act=None)#第三输出层act_dim
def value(self,obs):
h1=self.fc1(obs)#调用value输出q值时需要给obs参数
h2=self.fc2(h1)
Q=self.fc3(h2)
return Q
#2.Algorithm 定义了具体的算法来更新前向网络(Model),也就是通过定义损失函数来更新Model,和算法相关的计算都放在algorithm中。
class DQN(parl.Algorithm):
def __init__(self,model,act_dim=None,gamma=None,lr=None):
self.model=model
self.target_model=copy.deepcopy(model)
assert isinstance (act_dim,int)#act_dim必须是整形,否则抛出异常
assert isinstance(gamma,float)
assert isinstance(lr,float)
self.act_dim=act_dim
self.gamma=gamma
self.lr= lr
def predict(self,obs):#网络正向传播预测q值
return self.model.value(obs)#使用self.model的value网络来获取 [Q(s,a1),Q(s,a2),...]
def learn(self,obs,action,reward,next_obs,terminal):#网络反向传播更新参数
#1.从target_model中获取 max Q' 的值,用于计算target_Q
next_pred_value=self.target_model.value(next_obs)#得出下一状态的所有q值
best_v=layers.reduce_max(next_pred_value,dim=1)#得出最大q值,奖励最大
best_v.stop_gradient=True# 阻止梯度传递
terminal=layers.cast(terminal,dtype='float32')#terminal转浮点型(1或者0),terminal就是done,是否是最后一步
target=reward+(1.0-terminal)*self.gamma*best_v
# a=paddle.fluid.layers.elementwise_mul(self.gamma, best_v)#, axis=0)
# b=layers.elementwise_add(1.0,-terminal, axis=0)
# c=layers.elementwise_mul(a, b, axis=0)
# target=layers.elementwise_add(reward, c, axis=0)
#2.获取predict_Q
pred_value=self.model.value(obs)#这一状态的q值
action_onehot=layers.one_hot(action,self.act_dim)# 将action转onehot向量,比如:3 => [0,0,0,1,0]
action_onehot=layers.cast(action_onehot,dtype='float32')#转浮点型
#下面一行是逐元素相乘,拿到action对应的 Q(s,a),比如:pred_value = [[2.3, 5.7, 1.2, 3.9, 1.4]], action_onehot = [[0,0,0,1,0]]==> pred_action_value = [[3.9]]
pred_action_value=layers.reduce_sum(layers.elementwise_mul(action_onehot,pred_value),dim=1)
#3.更新表格(此状态此动作的q值):self.Q[obs,action]+=lr*(target_q-predict_q),计算 Q(s,a) 与 target_Q的均方差,得到loss
cost=layers.square_error_cost(pred_action_value,target)
cost=layers.reduce_mean(cost)
optimizer=fluid.optimizer.Adam(learning_rate=self.lr)
optimizer.minimize(cost)
return cost#损失值
def sync_target(self):#把 self.model 的模型参数值同步到 self.target_model
self.model.sync_weights_to(self.target_model)
#3.Agent 负责算法与环境的交互,在交互过程中把生成的数据提供给Algorithm来更新模型(Model),数据的预处理流程也一般定义在这里。
class Agent(parl.Agent):
def __init__(self,algorithm,obs_dim,act_dim,e_greed=0.1,e_greed_decrement=0):
assert isinstance(obs_dim,int)
assert isinstance(act_dim,int)
self.obs_dim=obs_dim
self.act_dim=act_dim
self.global_step=0
super(Agent,self).__init__(algorithm)#继承父类parl.Agent的初始化方法(传参数algorithm)
self.update_target_steps=200# 每隔200个training steps再把model的参数复制到target_model中
self.e_greed=e_greed# 有一定概率随机选取动作,探索
self.e_greed_decrement=e_greed_decrement# 随着训练逐步收敛,探索的程度慢慢降低
def build_program(self):#搭建计算图
self.pred_program=fluid.Program()
self.learn_program=fluid.Program()
with fluid.program_guard(self.pred_program):# 搭建计算图用于 预测动作,定义输入输出变量
obs=layers.data(name='obs',shape=[self.obs_dim],dtype="float32")
self.value=self.alg.predict(obs)
with fluid.program_guard(self.learn_program):# 搭建计算图用于 更新Q网络,定义输入输出变量
obs=layers.data(name="obs",shape=[self.obs_dim],dtype="float32")
action=layers.data(name="act",shape=[1],dtype="int32")
reward=layers.data(name="reward",shape=[],dtype="float32")
next_obs=layers.data(name="next_obs",shape=[self.obs_dim],dtype="float32")
terminal=layers.data(name="terminal",shape=[],dtype="bool")
self.cost=self.alg.learn(obs,action,reward,next_obs,terminal)
def predict(self,obs):
obs=np.expand_dims(obs,axis=0)
pred_Q=self.fluid_executor.run(self.pred_program,feed={"obs":obs.astype("float32")},fetch_list=[self.value])[0]
pred_Q=np.squeeze(pred_Q,axis=0)
act=np.argmax(pred_Q)
return act
def sample(self,obs):
sample=np.random.rand()#产生0~1之间的小数
if sample<self.e_greed:
act=np.random.randint(self.act_dim)
else:
act=self.predict(obs)
self.e_greed=max(0.01,self.e_greed-self.e_greed_decrement) # 随着训练逐步收敛,探索的程度慢慢降低
return act
def learn(self,obs,act,reward,next_obs,terminal):
if self.global_step%self.update_target_steps==0:# 每隔200个training steps同步一次model和target_model的参数
self.alg.sync_target()
self.global_step+=1
act=np.expand_dims(act,-1)
feed={'obs': obs.astype('float32'),'act': act.astype('int32'),'reward': reward,'next_obs': next_obs.astype('float32'),'terminal': terminal}
cost=self.fluid_executor.run(self.learn_program, feed=feed, fetch_list=[self.cost])[0]
return cost
#4.经验池:用于存储多条经验,实现 经验回放。
class ReplayMemory(object):
def __init__(self,max_size):
self.buffer=collections.deque(maxlen=max_size)
def append(self,exp):# 增加一条经验到经验池中
self.buffer.append(exp)
def sample(self,batch_size):# 从经验池中选取N条经验出来
mini_batch = random.sample(self.buffer, batch_size)
obs_batch, action_batch, reward_batch, next_obs_batch, done_batch = [], [], [], [], []
for experience in mini_batch:
s, a, r, s_p, done = experience
obs_batch.append(s)
action_batch.append(a)
reward_batch.append(r)
next_obs_batch.append(s_p)
done_batch.append(done)
return np.array(obs_batch).astype('float32'), np.array(action_batch).astype('float32'), np.array(reward_batch).astype('float32'),np.array(next_obs_batch).astype('float32'), np.array(done_batch).astype('float32')
def __len__(self):
return len(self.buffer)
#5.Training && Test
# 训练一个episode
def run_episode(env, agent, rpm):
total_reward = 0
obs = env.reset()
step = 0
while True:
step += 1
action = agent.sample(obs) # 采样动作,所有动作都有概率被尝试到
next_obs, reward, done, _ = env.step(action)
rpm.append((obs, action, reward, next_obs, done))
# train model
if (len(rpm) > MEMORY_WARMUP_SIZE) and (step % LEARN_FREQ == 0):
(batch_obs, batch_action, batch_reward, batch_next_obs,
batch_done) = rpm.sample(BATCH_SIZE)
train_loss = agent.learn(batch_obs, batch_action, batch_reward,
batch_next_obs,
batch_done) # s,a,r,s',done
total_reward += reward
obs = next_obs
if done:
break
return total_reward
# 评估 agent, 跑 5 个episode,总reward求平均
def evaluate(env, agent, render=False):
eval_reward = []
for i in range(5):
obs = env.reset()
episode_reward = 0
while True:
action = agent.predict(obs) # 预测动作,只选最优动作
obs, reward, done, _ = env.step(action)
episode_reward += reward
if render:
env.render()
if done:
break
eval_reward.append(episode_reward)
return np.mean(eval_reward)
#6.创建环境和Agent,创建经验池,启动训练,保存模型
env = gym.make('CartPole-v0') # CartPole-v0: 预期最后一次评估总分 > 180(最大值是200)
action_dim = env.action_space.n # CartPole-v0: 2
obs_shape = env.observation_space.shape # CartPole-v0: (4,)
# print("obs_shape")
# print(obs_shape[0])
rpm = ReplayMemory(MEMORY_SIZE) # DQN的经验回放池
# 根据parl框架构建agent
model = Model(act_dim=action_dim)
algorithm = DQN(model, act_dim=action_dim, gamma=GAMMA, lr=LEARNING_RATE)
agent = Agent(algorithm,obs_dim=obs_shape[0],act_dim=action_dim,e_greed=0.1,e_greed_decrement=1e-6)
# 加载模型
# save_path = './dqn_model.ckpt'
# agent.restore(save_path)
# 先往经验池里存一些数据,避免最开始训练的时候样本丰富度不够
while len(rpm) < MEMORY_WARMUP_SIZE:
run_episode(env, agent, rpm)
max_episode = 2000
# 开始训练
episode = 0
while episode < max_episode: # 训练max_episode个回合,test部分不计算入episode数量
# train part
for i in range(0, 50):
total_reward = run_episode(env, agent, rpm)
episode += 1
# test part
eval_reward = evaluate(env, agent, render=True) # render=True 查看显示效果
logger.info('episode:{} e_greed:{} test_reward:{}'.format(
episode, agent.e_greed, eval_reward))
# 训练结束,保存模型
save_path = './dqn_model.ckpt'
agent.save(save_path)
运行时要求paddlepaddle1.6.3,pip install parl1.3.1,pip install gym==0.18.0,下面是没用GPU跑,也可以装GPU版。
5.PolicyGradient-基于策略梯度求解RL
使用reinforce算法,基于马尔科夫使用episode连续状态训练模型。
import os
import gym
import numpy as np
import paddle.fluid as fluid
import parl
from parl import layers
from parl.utils import logger
LEARNING_RATE=1e-3
#1.定义model类,神经网络结构
class Model(parl.Model):
def __init__(self,act_dim):
act_dim=act_dim
hid1_size=act_dim*10
self.fc1=layers.fc(size=hid1_size,act="tanh")
self.fc2=layers.fc(size=act_dim,act="softmax")
def forward(self,obs):
out=self.fc1(obs)
out=self.fc2(out)
return out
#2.定义algorithm类,用网络和算法公式定义出模型的优化函数
class PolicyGradient(parl.Algorithm):
def __init__(self,model,lr=None):
self.model=model
assert isinstance (lr,float)
self.lr=lr
def predict(self,obs):#用神经网络Model得出动作概率
return self.model(obs)
def learn(self,obs,action,reward):#根据模型公式对函数优化
act_prob=self.model(obs)
log_prob=layers.reduce_sum(-1.0*layers.log(act_prob)*layers.one_hot(action,act_prob.shape[1]),dim=1)
cost=log_prob*reward
cost=layers.reduce_mean(cost)
optimizer=fluid.optimizer.Adam(self.lr)
optimizer.minimize(cost)
return cost
#3.定义Agent类,通过与环境进行交互,
class Agent(parl.Agent):
def __init__(self,algorithm,obs_dim,act_dim):
self.obs_dim=obs_dim
self.act_dim=act_dim
super(Agent,self).__init__(algorithm)
def build_program(self):
self.pred_program=fluid.Program()
self.learn_program=fluid.Program()
with fluid.program_guard(self.pred_program): # 搭建计算图用于 预测动作,定义输入输出变量
obs = layers.data(
name='obs', shape=[self.obs_dim], dtype='float32')
self.act_prob = self.alg.predict(obs)
with fluid.program_guard(
self.learn_program): # 搭建计算图用于 更新policy网络,定义输入输出变量
obs = layers.data(
name='obs', shape=[self.obs_dim], dtype='float32')
act = layers.data(name='act', shape=[1], dtype='int64')
reward = layers.data(name='reward', shape=[], dtype='float32')
self.cost = self.alg.learn(obs, act, reward)
def sample(self, obs):
obs = np.expand_dims(obs, axis=0) # 增加一维维度
act_prob = self.fluid_executor.run(
self.pred_program,
feed={'obs': obs.astype('float32')},
fetch_list=[self.act_prob])[0]
act_prob = np.squeeze(act_prob, axis=0) # 减少一维维度
act = np.random.choice(range(self.act_dim), p=act_prob) # 根据动作概率选取动作
return act
def predict(self, obs):
obs = np.expand_dims(obs, axis=0)
act_prob = self.fluid_executor.run(
self.pred_program,
feed={'obs': obs.astype('float32')},
fetch_list=[self.act_prob])[0]
act_prob = np.squeeze(act_prob, axis=0)
act = np.argmax(act_prob) # 根据动作概率选择概率最高的动作
return act
def learn(self, obs, act, reward):
act = np.expand_dims(act, axis=-1)
feed = {
'obs': obs.astype('float32'),
'act': act.astype('int64'),
'reward': reward.astype('float32')
}
cost = self.fluid_executor.run(
self.learn_program, feed=feed, fetch_list=[self.cost])[0]
return cost
#4.训练&&测试
def run_episode(env, agent):
obs_list, action_list, reward_list = [], [], []
obs = env.reset()
while True:
obs_list.append(obs)
action = agent.sample(obs) # 采样动作
action_list.append(action)
obs, reward, done, info = env.step(action)
reward_list.append(reward)
if done:
break
return obs_list, action_list, reward_list
# 评估 agent, 跑 5 个episode,总reward求平均
def evaluate(env, agent, render=False):
eval_reward = []
for i in range(5):
obs = env.reset()
episode_reward = 0
while True:
action = agent.predict(obs) # 选取最优动作
obs, reward, isOver, _ = env.step(action)
episode_reward += reward
if render:
env.render()
if isOver:
break
eval_reward.append(episode_reward)
return np.mean(eval_reward)
#5.创建环境和Agent,启动训练,保存模型
# 根据一个episode的每个step的reward列表,计算每一个Step的Gt
def calc_reward_to_go(reward_list, gamma=1.0):
for i in range(len(reward_list) - 2, -1, -1):
# G_t = r_t + γ·r_t+1 + ... = r_t + γ·G_t+1
reward_list[i] += gamma * reward_list[i + 1] # Gt
return np.array(reward_list)
# 创建环境
env = gym.make('CartPole-v0')
obs_dim = env.observation_space.shape[0]
act_dim = env.action_space.n
logger.info('obs_dim {}, act_dim {}'.format(obs_dim, act_dim))
# 根据parl框架构建agent
model = Model(act_dim=act_dim)
alg = PolicyGradient(model, lr=LEARNING_RATE)
agent = Agent(alg, obs_dim=obs_dim, act_dim=act_dim)
# 加载模型
# if os.path.exists('./model.ckpt'):
# agent.restore('./model.ckpt')
# run_episode(env, agent, train_or_test='test', render=True)
# exit()
for i in range(1000):
obs_list, action_list, reward_list = run_episode(env, agent)
if i % 10 == 0:
logger.info("Episode {}, Reward Sum {}.".format(
i, sum(reward_list)))
batch_obs = np.array(obs_list)
batch_action = np.array(action_list)
batch_reward = calc_reward_to_go(reward_list)
agent.learn(batch_obs, batch_action, batch_reward)
if (i + 1) % 100 == 0:
total_reward = evaluate(env, agent, render=True) # render=True 查看渲染效果,需要在本地运行,AIStudio无法显示
logger.info('Test reward: {}'.format(total_reward))
# 保存模型到文件 ./model.ckpt
agent.save('./model.ckpt')