【强化学习学习笔记】动态规划原理详解及代码实例

因为最近实习投过有关强化学习的岗位,在准备实习的面试,所以打算把强化学习的知识重新梳理一下。

强化学习和动态规划都是基于MDP的序贯决策问题,(所谓序贯决策问题,是指按时间顺序排列起来,以得到按顺序的各种决策(策略))区别就在于动态规划是Model-Based的,而强化学习是Model-Free的,Model-Based和Model-Free的区别,因为今天的主题不是它们,所以就用一句话概括:区别就在于是否知道马尔可夫决策过程(MDPs)里的状态转移概率 P ( s ′ ∣ s ) P(s'|s) P(ss) ,即由当前状态向下一状态发生迁移的概率大小。

因此梳理强化学习的知识体系就先从动态规划开始,最近也看了很多网友总结的关于动态规划的帖子,俗话说看100次不如自己做一次,所以我就用自己的话梳理一下。

在开始之前先扯一些题外话,也是博主最近在练习写博客的时候自己的一些反思,因为很多情况下自己看各种资料觉得自己懂了,但是真正一到自己给别人讲或者开始写一篇文章的时候,发现自己其实并没搞懂。如果赶时间朋友完全可以跳过下面这个部分。

----------------------------------闲话与思考----------------------------------
结合这么多年的学习经验与反思,在学习某个新知识的时候,由于现在升学压力大,加上题海战术,大多数的学生可能只知道关于这个知识的题是如何做的(How),但是让他给我们讲一讲这个知识它到底是什么东西,为什么要使用它,它可以用在什么地方的时候,可能很多学生(包括我自己)就讲不清楚了。题海战术可能在大学之前的学习生涯中优势很大,毕竟升学这件事上分就是学生的命,升学考试中也并不会有很深的面试筛选环节。但是等到大学以及像博主现在研究生的阶段或者面临找工作需要接受各种面试的时候,题海战术就变得没那么有用了,如果你单单通过刷题没有真正去理解这个知识点就通过种种面试,那个人认为除了面试官没有去深挖以外,那大概就是你运气爆棚。因此当学到一个新知识点的时候多问问自己那个老套的3W(What,Where,Why),当你学完这个知识点能够把这3W讲清楚,再去刷题搞清楚是如何做(How),可能会事半功倍,也不会去担心什么跟导师汇报没得讲,不会讲(别问我怎么知道的),面试的时候心很虚。那么我今天就从这三个W入手,开始梳理一下动态规划。

----------------------------------正文Start----------------------------------

  • 第一个W(What):

动态规划:是求解决策过程中最优化的方法,把多阶段的过程转化成一系列单阶段的问题,利用各个阶段的关系自底向上逐个求解,可以认为每个单阶段的任务之前的多阶段过程是最优的,那么在之前最优的基础上选出当前阶段的决策也是最优的。

一句话总结它的思想就是:把待求解的问题分成若干子问题,通过求解若干子问题并结合其之间的关系(即状态转移方程)求得原问题的解。

如果还是不好理解,那举个具体的例子吧:

例一(简单)
假设:A=1,B=1,C=2,D=3,E=5,F=8
那么按照上面的规律,求问G是多少,可能细心的读者会毫不犹豫的答出G=13,为什么呢,因为按照上面的规律,用高中数列中的知识来讲就是后一项等于前两项之和,即G=E+F。那么如果接着问L是多少,可能大家就要算一会了,为什么呢?

要求L,大的思路可能有两个:
1.按照高中数列的思路求通项公式f(n),因为L是第12个英文字母,这样就可以求得L=f(12)
2.知道L的前两项J和K的大小,这样就可以求得L=J+K

那么我们就可以去思考该该选哪条路呢,让你求这个通项公式你可能要花一些时间,但是求J和K呢,可能就更简单一些,花费的时间就更少一些。那么问题又回归到我们的动态规划,其实有很多读者可能已经看出来了,刚才举的例子就是我们熟悉的斐波那契数列,讲到动态规划,必然要从最基础的斐波那契数列入手。那么我们就来讲讲第二个W(Where),那就是什么地方可以用动态规划。

  • 第二个W(Where):
    结合例一我们可以总结出什么地方可以用动态规划呢——?满足以下2个条件就可以考虑使用动态规划:
    1.最优子结构。 是指一个子问题的最优解可求,例一斐波那契数列就是说 [ f ( 1 ) , f ( 2 ) , f ( 3 ) , . . . . , f ( n ) ] [f(1),f(2),f(3),....,f(n)] [f(1),f(2),f(3),....,f(n)]这个数列里的每个值都是可以求可得的。还有之前做百度笔试碰到的兔子问题:“假定一对大兔子每月能生一对小兔子,且每对新生的小兔子经过一个月可以长成一对大兔子,具备繁殖能力,如果不发生死亡,且每次均生下一雌一雄,问一年后共有多少对兔子?”在这些问题中可求即最优。
    2.重复子结构。 最后待求解的问题由子问题构成。同样是例一中的斐波那契数列,即可以总结成一个递推式即
    f ( n ) = { f ( n − 1 ) + f ( n − 2 ) n > 2 1 n = 1 , 2 f(n)=\left\{ \begin{aligned} &f(n-1) + f(n-2) &n>2\\ & 1&n=1,2 \\ \end{aligned} \right. f(n)={f(n1)+f(n2)1n>2n=1,2

但是也不是所有满足以上两个点的问题都需要我们使用动态规划算法来求解,比如过如果一个数列的通项公式是 f ( n ) = n + 3 f(n) = n + 3 f(n)=n+3,那么在通项公式极易求出或者说已知通项同时的情况下,使用动态规划算法就是大材小用了。

  • 第三个W(Why):
    有了以上的2个W做铺垫,接下来我们再来讲讲第三个W(Why),为什么要用动态规划。

假设你不具备编程基础,只具备数学基础,想要求通项公式,那也可以,其实斐波那契数列的数学解法也有很多,但我们这次的主题不是用数学解法求,因此就不加以讨论,有兴趣的同学可以去网上自行搜索,在此我这列出斐波那契数列的通项公式,给那些在不了解斐波那契数列的前提下拿到这个问题决心要求解通项公式的同学感受一下:
f ( n ) = 1 5 [ ( ( 1 + 5 ) 2 ) n − ( ( 1 − 5 ) 2 ) n ] f(n) = \frac{1}{\sqrt{5}} [ (\frac{(1+\sqrt{5})}{2})^n-(\frac{(1-\sqrt{5})}{2})^n] f(n)=5 1[(2(1+5 ))n(2(15 ))n]

现在还是回归到例一的斐波那契数列上,假设你具备基本的编程基础,有一道题让你求f(40),那你会怎么做呢,大致的分析过后你会去选择硬磕求那个数列的通项公式还是编程求解呢,我觉得大多数人应该会选择去编程求解吧,写一段简简单单的递归算法的代码:

#!/usr/bin/env python3

import time

def fuc(n):
    if n != 1 and n!=2:
        return fuc(n-1)+fuc(n-2)
    else: return 1


if __name__ == "__main__":
    time_start = time.time()
    print(fuc(40))
    print("Spend time is:",time.time()-time_start)

输出结果:

102334155
Spend time is: 28.795566082000732

可以看到,在博主在台电脑,仅仅是求到40,运行时间就已经有将近29秒了,那我们就需要反思一下这个解法是不是有问题了。
斐波那契数列
上图就是计算过程,可以发现当我们使用递归算法的时候,计算f(10)的时候算了f(9)和f(8),而计算f(9)的时候算了f(8)和f(7),f(8)被算了2遍,程序的时间复杂度是 O ( 2 n ) O(2^n) O(2n),是指数爆炸级的,博主这里只测试到40就运算了将近29秒,如果你不信可以自己试试50,又增加到了多少秒。

通过递归我们发现既然计算最终的f(40)的时候下面的很多数会重复使用,那们是不是可以创建一个数组,在第一次计算的时候就把它存起来,下次再用到时候直接调用就可以避免重复计算了呢。这里就用到了动态规划的思想,像我们第一个W里说的把待求解的问题分解成若干个子问题,通过求解若干个子问题并结合其之间的关系就可以求得最终的问题。
先上程序:

#!/usr/bin/env python3
import time

def fuc_1(n):
    memory = list(range(n))
    for i in range(n):
        if i == 0 or i == 1:
            memory[i] = 1
        else:
            memory[i] = memory[i-1] + memory[i-2]

    return memory[-1]
  
if __name__ == "__main__":
    time_start = time.time()
   # print(fuc(40))
    print(fuc_1(40))
    print("Spend time is:",time.time()-time_start)

输出结果为:

102334155
Spend time is: 4.410743713378906e-05

同样的都是计算f(40),使用动态规划算法计算时间只用了0.00004秒,相比之前的将近29秒是不是快了很多呢, 那么有人可能会问了,这是为什么呢,因为我们降低了程序的时间复杂度,还是拿刚刚图中的f(10)举例,我们计算过程变成了这样 :
在这里插入图片描述
这里只计算了f(10)到f(1)这一列,每一个数第一次被计算的时候都将它存到一个数组中,下次再用到的时候直接调用,不再重新计算,并且从f(1),f(2),f(3)一直到f(40),以自底向上的顺序计算,程序的时间复杂度自然而然的由原来的 O ( 2 n ) O(2^n) O(2n)变成了现在的 O ( n ) O(n) O(n),因此计算时间大幅度减少,怎么样是不是感受到了动态规划的魅力了呢。

----------------------------------写在最后----------------------------------
因为在总结的过程中也参考了其他一些帖子的内容,所以如果有描述相似的地方觉得我侵权了大可直接联系我,不过字全部博主自己码出来的,如果需要转载请标明出处,觉得读完这篇文章对你有帮助的话留个赞吧!
顺便把自己看过几篇比较好的帖子的链接也附在这里供大家参考:
https://zhuanlan.zhihu.com/p/78220312
https://www.zhihu.com/question/39948290

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值