强化学习入门

1 机器学习的分类

        只作为参考,不是本文重点,了解即可

        机器学习大致可以分类三大类:

                监督学习:“对邮政编码中的手写数字进行分类”是一种监督学习。邮政编码分类系统将每个数字的手写图像分类为0~9中的一个。诸如0到9的数据的分类目标被称为标签或类。这种系统被称为监督学习,因为给事先提供的训练数据预先标记出了正确的标签。换句话说,带标签的训练数 据成了系统的教师。监督学习包括学习阶段和推理阶段。

                非监督学习:用一个词表达非监督学习就是“分组”。它将大量数据中类似的数据分为一组(称为聚类)。例如,“根据购买数据对客户进行分组的系统”是非监督学习。根据购买历 史记录的特征对客户进行分组,可以为每个组实施不同的销售策略。

                强化学习:强化学习让机器"随机"做出动作,最后我们根据动作的正确与否给与奖励或者惩罚,从而强化正确的动作,使机器达到我们想要的效果。

                

2.环境准备

        我们从最基本的迷宫开始,先使用python创建一所迷宫,代码如下:

       

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

#迷宫初始位置

#声明图的大小以及图的变量名
fig = plt.figure(figsize=(5,5))
ax = plt.gca()
# 添加标签和标题
plt.xlabel('X')
plt.ylabel('Y')
plt.title('title')
#画出红色墙壁
plt.plot([1,1],[0,1],color='red',linewidth =2)
plt.plot([1,2],[2,2],color='blue',linewidth =2)
plt.plot([2,2],[2,1],color='green',linewidth =2)
plt.plot([2,3],[1,1],color='yellow',linewidth =2)

#画出表示状态的文字S0-S8
plt.text(0.5,2.5,'S0',size=14,ha='center')
plt.text(1.5,2.5,'S1',size=14,ha='center')
plt.text(2.5,2.5,'S2',size=14,ha='center')
plt.text(0.5,1.5,'S3',size=14,ha='center')
plt.text(1.5,1.5,'S4',size=14,ha='center')
plt.text(2.5,1.5,'S5',size=14,ha='center')
plt.text(0.5,0.5,'S6',size=14,ha='center')
plt.text(1.5,0.5,'S7',size=14,ha='center')

plt.text(2.5,0.5,'S8',size=14,ha='center')
plt.text(0.5,2.3,'START',size=14,ha='center')
plt.text(2.5,0.3,'GOAL',size=14,ha='center')

#设定画图的范围
ax.set_xlim(0,3)
ax.set_ylim(0,3)
plt.tick_params(axis='both',which='both',bottom='off',top='off',labelbottom='off',right='off',left='off',labelleft='off')

#当前位置S0用绿色圆圈画出
line,=ax.plot([0.5],[2.5],marker="o",color="g",markersize=60)

我们运行一下我们的代码,会得到如下效果的迷宫

这里解释说明一下,绿色圆圈是我们的智能体,外围为墙壁,中间颜色线条为墙壁。S0-S8智能体可以行走的位置。我们最终任务是使用强化学习方法让智能体找到从S0到S8之间的最短路径。

3.动作空间

下面我们来定义一张表格来标记智能体在各位置时的动作空间(简单讲就是智能体在每一个位置的活动范围

       我们先来看第一行

                [np.nan,1,1,np.nan],#S0

        表示智能体在S0位置时的动作,数组中四个位置分别对应为向上走,向右走,向下走,向左走。 np.nan 标识禁止通行,1表示可以通行。翻译翻译就是智能体在S0这个位置可以向下或者向右通行,禁止向上或者向左通行

             其他位置按照相同规则写出来,如下:

  

#设定参数theta
#行为状态0~7,列为用 ↑   →  ↓    ←表示移动方向
theta_0 = np.array([[np.nan,1,1,np.nan],#S0
                  [np.nan,1,np.nan,1],#S1
                  [np.nan,np.nan,1,1],#S2
                  [1,1,1,np.nan],#S3
                  [np.nan,np.nan,1,1],#S4
                  [1,np.nan,np.nan,np.nan],#S5
                  [1,np.nan,np.nan,np.nan],#S6
                  [1,1,np.nan,np.nan]#S7
                 ])#S1

4.策略空间

     接下来我们来再定义一张表来表示智能体的策略(即:智能体在迷宫每一个位置采取每一种行动的概率

还是拿第一行数据举例说明

        [0.         0.5         0.5         0. ]

与动作空间类似,位置表示动作方向,值表示采取这个动作的概率。第一行表示当智能体在S0这个位置时,向上走的概率为 0 ,向右走的概率为 0.5 ,向下走的概率为0.5,向左走的概率为0.

    这里0.5怎么来的?因为智能体在S0时,只能向右或者向左走,所以我们每个动作均分到0.5的概率。下面的每一行同理。

我们发现动作空间和初始化策略空间格式上是一样的,我们可以写一个函数来实现从动作空间的到策略空间的转换

        

def simple_convert_into_pi_from_theta(theta):
    [m,n] = theta.shape
    pi = np.zeros((m,n))
    for i in range(0,m):
        pi[i,:] = theta[i,:]/np.nansum(theta[i,:])
    pi = np.nan_to_num(pi)
    return pi

pi_0 = simple_convert_into_pi_from_theta(theta_0)

5.智能体移动

        现在我们要让智能体动起来,定义一个函数,调用一次智能体移动一步,并返回移动之后的位置。向哪个方向移动取决于策略空间中的概率


def get_next_s(pi,s):
    direction = ["up","right","down","left"]
    next_direction = np.random.choice(direction,p=pi[s,:])
    #根据概率pi[s,:]选择dirction
    if next_direction == "up":
        s_next = s - 3 #向上移动时状态的数字减少3
    elif next_direction == "right":
        s_next = s + 1
    elif next_direction == "down":
        s_next = s + 3
    elif next_direction == "left":
        s_next = s - 1
    return s_next

 我们再定义一个循环,让智能体一直走,直到找到出口位置,并用一个变量记录智能体走过的路程。

def goal_maze(pi):
    s = 0; #开始地点
    state_history = [0] #记录智能体移动轨迹的列表
    while(1): #循环,直到到达目标
        next_s = get_next_s(pi,s)
        state_history.append(next_s) # 在记录列表中添加下一个状态(智能体的位置)
        if next_s == 8:
            break
        else:
            s = next_s
    return state_history
#在迷宫内朝着目标移动
state_history = goal_maze(pi_0)
[0,3,6,3,6,3,6,3,6,3,0,1,0,3,0,1,0,1,2,5,2,1,0,1,2,5,2,5,2,5,2,1,0,3,6,3,4,7,8]

可以看出经过足够多次的像个傻子一样乱撞,最终智能体找到了出口。

目前智能体很明显一点都不智能,接下来我们想办法调整策略空间中的概率分布。使智能体变得智能。

6.优化策略空间

   在更新优化策略空间之前我们需要改写一下上面的goal_maze函数,我们希望再记录位置信息的同时记录一下在此位置采取的动作信息。

#定义求解迷宫问题的函数,它输出状态和动作
def goal_maze_ret_s_a(pi):
    s = 0; #开始地点
    s_a_history = [[0,np.nan]] #记录智能体移动轨迹的列表
    while(1): #循环,直到到达目标
        [action,next_s] = get_action_and_next_s(pi,s)
        s_a_history[-1][1] = action
        #代入当前状态(即目前最后一个状态index = -1)的动作
        
        s_a_history.append([next_s,np.nan]) 
        # 代入下一个状态,由于还不知道其动作,用nan表示
        if next_s == 8:
            break
        else:
            s = next_s
    return s_a_history
s_a_history = goal_maze_ret_s_a(pi_0)
print(s_a_history)
print("求解迷宫问题所需的步数" + str(len(s_a_history)-1))

更新好后我们执行一次,得到如下结果。 

[[0, 2], [3, 0], [0, 2], [3, 2], [6, 0], [3, 0], [0, 1], [1, 3], [0, 2], [3, 0], [0, 2], [3, 0], [0, 1], [1, 3], [0, 2], [3, 2], [6, 0], [3, 1], [4, 2], [7, 0], [4, 3], [3, 0], [0, 1], [1, 3], [0, 2], [3, 2], [6, 0], [3, 0], [0, 1], [1, 1], [2, 3], [1, 1], [2, 2], [5, 0], [2, 3], [1, 3], [0, 2], [3, 1], [4, 2], [7, 0], [4, 2], [7, 0], [4, 3], [3, 0], [0, 2], [3, 2], [6, 0], [3, 0], [0, 2], [3, 0], [0, 2], [3, 0], [0, 1], [1, 3], [0, 2], [3, 2], [6, 0], [3, 1], [4, 3], [3, 1], [4, 2], [7, 0], [4, 2], [7, 0], [4, 2], [7, 0], [4, 2], [7, 0], [4, 2], [7, 0], [4, 2], [7, 1], [8, nan]]

求解迷宫问题所需的步数72

这串数字表示,第一步[0,2]智能体位于S0 ,采取了向下的策略 ,于是走到了S3 ,因此第二个数组第一个数为3,由于第二个数为0,表示在S3的位置采取了向上走的策略,于是又回到了S0,依次类推,知道走到S8,到达S8后不再移动,动作记为nan。

我们接下来定义一个更新策略空间的函数:注意,此处是核心

def update_theta(theta,pi,s_a_history):
    eta = 0.1 #学习率
    T = len(s_a_history) -1
    [m,n] = theta.shape #theta矩阵的大小
    delta_theta = theta.copy() 
    #求取delta_theta的各元素
    for i in range(0,m):
        for j in range(0,n):
            if not(np.isnan(theta[i,j])):
                #从列表中取出状态i
                SA_i = [SA for SA in s_a_history if SA[0] == i]
                #取出状态i下应该采取的动作j
                SA_ij = [SA for SA in s_a_history if SA == [i,j]]
                N_i = len(SA_i) #状态i下动作的总次数
                N_ij = len(SA_ij) #状态i下采取动作j的次数
                delta_theta[i,j] = (N_ij - pi[i,j] * N_i) / T
    new_theta = theta + eta* delta_theta
    return new_theta

我们是要找到路程最短的路线,这就意味着最好某个点只走一次或者不走,走的多了就意味着重复走的次数比较多,那么我们就要降低当前指定动作空间指定方向的权重,让他少走这条路。

接下来我们走一轮,然后更新一下策略空间。看看效果

# 策略更新
new_theta = update_theta(theta_0,pi_0,s_a_history)
pi = softmax_convert_into_pi_from_theta(new_theta)
print(pi)

我们可以看到策略空间的值已经开始变化了,我们多训练几轮

stop_epsilon = 10**-4
theta = theta_0
pi = pi_0
is_continue = True
count = 1
while is_continue:
    s_a_history = goal_maze_ret_s_a(pi)
    new_theta = update_theta(theta,pi,s_a_history)
    new_pi = softmax_convert_into_pi_from_theta(new_theta)
    print(np.sum(np.abs(new_pi-pi)))#输出策略变化
    print("求解迷宫问题所需要的步数"+str(len(s_a_history)-1))
    if np.sum(np.abs(new_pi-pi))<stop_epsilon:
        is_continue = False
    else:
        theta = new_theta
        pi = new_pi

效果如下

        

我们可以看到随着训练的进行最终停留在只需要四步。可以看出智能体已经找到了最佳路径。

我们来看一下训练之后的策略空间

#确认最终策略
np.set_printoptions(precision=3,suppress=True)#设置有效位数为3,不显示指数
print(pi)

我们可以看到正确的动作得到了极大的强化

s_a_history = goal_maze_ret_s_a(pi)
print(s_a_history)
print("求解迷宫问题所需的步数" + str(len(s_a_history)-1))

最后我们让智能体跑起来

from matplotlib import animation
from IPython.display import HTML
line
def init():
    '''初始化背景图'''
    line.set_data([],[])
    return (line,)
init()

def animate(i):
    '''每一帧的画面内容'''
    state = state_history[i] #画出当前的位置
    x = (state % 3) + 0.5 #状态的x坐标为状态除以3的余数加0.5
    y = 2.5 - int(state/3) #状态的y坐标为2.5减去状态数除以3的商
    line.set_data(x,y)
    return (line,)
#用初始化函数和绘图函数来生成动画
anim = animation.FuncAnimation(fig,animate,init_func=init,frames=len(s_a_history),interval=200,repeat=False)
HTML(anim.to_jshtml())

我们使用训练好的策略空间来指导智能体运动

到目前为止我们便实现了一个最简单的强化学习案例,提前透露一下,我们在此处优化策略空间的方法就是后续会讲到的策略迭代法,除了策略迭代法还有一种优化方法-价值迭代法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值