相比上一个demo,这个练习的环境更加复杂,但是就强化学习智能体而言,其整体是一样的
但是既然环境更加复杂,就需要把智能体和环境单独拉出来写,不能再放一个Python文件中
环境类
环境类总结起来就是定义了初始化的参数,构建迷宫,重置函数(每一次游戏结束后需重置到起始的环境),每一步怎么走的。
注意:这里的update和main函数知识对该环境的一个简单测试,不是智能体的行动,在使用强化学习训练的过程中,它们并不执行。
import numpy as np
import pandas as pd
import time
import tkinter as tk
UNIT=40 #像素
#画布
WIDTH=4
HIGTH=4
#新建环境类
class Maze(tk.Tk,object):
def __init__(self):
super(Maze,self).__init__()
#动作空间
self.action_space=['u','d','l','r']
self.n_action=len(self.action_space)
self.title('maze')
self.geometry('{0}x{1}'.format(HIGTH*UNIT,WIDTH*UNIT))#建立画布
self._build_maze()
def _build_maze(self):
self.canvas=tk.Canvas(self,bg='white',height=HIGTH*UNIT,width=WIDTH*UNIT)#新建画布
#创建单元格
for c in range(0,WIDTH*UNIT,UNIT):
x0,y0,x1,y1=c,0,c,HIGTH*UNIT#获取两个坐标,然后绘制直线
self.canvas.create_line(x0,y0,x1,y1)
for r in range(0,HIGTH*UNIT,UNIT):
x0,y0,x1,y1=0,r,WIDTH*UNIT,r
self.canvas.create_line(x0,y0,x1,y1)
#创建迷宫中的坑
hell_center1=np.array([100,20])
self.hell1=self.canvas.create_rectangle(hell_center1[0]-15,hell_center1[1]-15,hell_center1[0]+15,hell_center1[1]+15,fill='black')
hell_center2=np.array([20,100])
self.hell2=self.canvas.create_rectangle(hell_center2[0]-15,hell_center2[1]-15,hell_center2[0]+15,hell_center2[1]+15,fill='green')
#创建出口
out_center=np.array([100,100])
self.oval=self.canvas.create_oval(out_center[0]-15,out_center[1]-15,out_center[0]+15,out_center[1]+15,fill='yellow')
#创建探索者
origin=np.array([20,20])
self.finder=self.canvas.create_rectangle(origin[0]-15,origin[1]-15,origin[0]+15,origin[1]+15,fill='red')
#打包
self.canvas.pack()
#reset
def reset(self):#说实话,这个是需要结束了后才调用
self.update()
time.sleep(0.5)
self.canvas.delete(self.finder)#删掉成功找到出口的搜索者
#又新建了搜索者的初始位置
origin=np.array([20,20])
#在初始位置再次新建搜索者
self.finder=self.canvas.create_rectangle(origin[0]-15,origin[1]-15,origin[0]+15,origin[1]+15,fill='red')
return self.canvas.coords(self.finder)#返回搜索者的起始坐标
#智能体走的每一步的过程
def step(self,action):
s=self.canvas.coords(self.finder)#获取搜索者的位置
base_action=np.array([0,0])
if action==0:#数字从0-4代表上下左右
if s[1]>UNIT:
#此刻动作向上,如果搜索者不是在最上一层,则可以向上移动,否则不必执行操作
base_action[1]-=UNIT
elif action==1:
if s[1]<(HIGTH-1)*UNIT:
base_action[1]+=UNIT
#左右
elif action==2:
if s[0]>UNIT:
base_action[0]-=UNIT
elif action==3:
if s[0]<(WIDTH-1)*UNIT:
base_action[0]+=UNIT
#之所以用这种方式,是由于移动函数的参数需求所致
self.canvas.move(self.finder,base_action[0],base_action[1])
#移动完成后记录新的状态位置
s_next=self.canvas.coords(self.finder)
#奖励函数设计
if s_next==self.canvas.coords(self.oval):
reward=1
done=True
s_next='terminal'
elif s_next in (self.canvas.coords(self.hell1),self.canvas.coords(self.hell2)):
reward=-1
done=True
s_next='terminal'
else:
reward=0
done=False
return s_next,reward,done
#渲染
def render(self):
time.sleep(0.1)
self.update()#方法
#update,不停的在update,这是原生的动作产生方式,只是在这里假玩一下,真正的走迷宫的动作不在此产生
def update():
for t in range(10):
s=env.reset()#重置
while True:
env.render()
a=1
s,r,done=env.step(a)
if done:
break
if __name__ == '__main__':
env=Maze()#创建环境对象
env.after(100,update)#100毫秒后更新,函数对象
env.mainloop()#进入主循环
然后是智能体的构建
智能体的部分比较通用和固定,总结起来就是:参数的初始化阶段,动作选择函数,学习数据并更新
import pandas as pd
import numpy as np
#大脑写完了,总结来就是,初始化一些参数,动作选择策略,学习更新过程
class Q_table:
#init,相当于构造方法
def __init__(self,actions,learning_rate=0.01,reward_decay=0.9,e_greedy=0.9):
self.actions=actions
self.learning_rate=learning_rate
self.reward_decay=reward_decay
self.e_greedy=e_greedy
#生成q表
#这个Q表的生成与之前的不同,这里只传递动作,而没有给出状态,状态在后面陆续自动添加
self.q_table=pd.DataFrame(columns=actions,dtype=np.float64)#这里只是新建表结构,并未添加表内容
#好了初始化完成
#选择动作
def choose_action(self,s,e):
#先检查状态
self.check_state_exist(s)
#然后基于贪婪策略选择动作
if np.random.uniform()<e:
#看来是有多种写法的,先获取状态s所对应的动作
s_acitons=self.q_table.loc[s,:]
#理论上直接选择最大值即可,但是考虑到相同值的情况,所以还是保险一点,这句代码太高度集成了
action=np.random.choice(s_acitons[s_acitons==np.max(s_acitons)].index)
else:
action=np.random.choice(self.actions)#随机选择动作
return action
#学习,前面那个程序是因为直接就是主程序,所以无需传参
def learn(self,s,a,r,s_):
#这就是一组标准的学习数据,在treasure中的rl中
self.check_state_exist(s_)#检查状态是否存在
#从q表中获取预测值
q_predict=self.q_table.loc[s,a]
if s_!='terminal':
#计算q目标值
q_target=r+self.reward_decay*self.q_table.loc[s_,:].max()
else:
q_target=r
#更新q表
self.q_table.loc[s,a]+=self.learning_rate*(q_target-q_predict)
#函数,检查状态是否存在
def check_state_exist(self,state):
if state not in self.q_table.index:#python的代码很多都是高度集成的
self.q_table=self.q_table.append(
pd.Series([0]*len(self.actions),index=self.q_table.columns,name=state)#添加
)
好了,环境和智能体都已经写好了,就可以来玩走迷宫的游戏了
调用前面写好的两个类,并写好更新的过程即可运行,这里可以设定训练的次数,贪婪策略所用的系数以及其动态变化的控制
from environment import Maze
from agent import Q_table
#这里是更新交互过程的代码
def update():
epison=0.9#贪婪策略
for i in range(10):
#迭代10次,到这里你才会明白前面写的那些类和函数怎么用
observation=env.reset()#每进入新的一轮学习,就重置环境,这也是初始状态,这个observation其实就是智能体当前的坐标位置
#进入该轮学习的探索步骤
while True:
#每一步都先渲染环境
env.render()
#选择动作
action=RL.choose_action(str(observation),epison)#动作选择策略函数,这里传的两个参数你现在知道怎么用的了
#执行新的动作,并返回下一个状态和奖励
observation_,reward,done=env.step(action)
#学习这一步的数据
RL.learn(str(observation),action,reward,str(observation_))
#进入新的状态
observation=observation_
#判断是否结束
if done:
break
epison+=0.05#变化的贪心策略
#for循环结束后,整个训练也就结束了
print("game over")
env.destroy()
if __name__ == '__main__':
env=Maze()
#所以在这里才只用传递动作
RL=Q_table(actions=list(range(env.n_action)))#我最大的缺陷就是对知识的记忆不行,记忆力太差,这里不同的类,不同的方法中定义了很多的参数,然后又在其他不同的地方引用,导致我压根反应不过来
env.after(10,update)#这里就是传参,update方法后面不加括号
env.mainloop()