精华内容
下载资源
问答
  • c++, DFS遍历模板, 可避免写递归逻辑, 具体例子可以参考.h中的namespace
  • 自己的php 递归无限分类 附有sql文件
  • 为什么你学不会递归?告别递归,谈谈我的经验

    万次阅读 多人点赞 2019-10-27 16:01:40
    可能也有一大部分人知道递归,也看的懂递归,但在实际做题过程中,却不知道怎么使用,有时候还容易被递归给搞晕。也有好几个人来问我有没有快速掌握递归的捷径啊。说实话,哪来那么多捷径啊,不过,我还是想一篇...

    可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了!

    可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却不知道怎么使用,有时候还容易被递归给搞晕。也有好几个人来问我有没有快速掌握递归的捷径啊。说实话,哪来那么多捷径啊,不过,我还是想写一篇文章,谈谈我的一些经验,或许,能够给你带来一些帮助。

    为了兼顾初学者,我会从最简单的题讲起!

    递归的三大要素

    第一要素:明确你这个函数想要干什么

    对于递归,我觉得很重要的一个事就是,这个函数的功能是什么,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。

    例如,我定义了一个函数

    // 算 n 的阶乘(假设n不为0)
    int f(int n){
        
    }
    

    这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二要素。

    第二要素:寻找递归结束条件

    所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

    例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下

    // 算 n 的阶乘(假设n不为0)
    int f(int n){
        if(n == 1){
            return 1;
        }
    }
    

    有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗?

    当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。

    // 算 n 的阶乘(假设n>=2)
    int f(int n){
        if(n == 2){
            return 2;
        }
    }
    

    注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样:

    // 算 n 的阶乘(假设n不为0)
    int f(int n){
        if(n <= 2){
            return n;
        }
    }
    

    第三要素:找出函数的等价关系式

    第三要素就是,我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。

    例如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。

    说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即

    f(n) = n * f(n-1)。

    这个等价关系式的寻找,可以说是最难的一步了,如果你不大懂也没关系,因为你不是天才,你还需要多接触几道题,我会在接下来的文章中,找 10 道递归题,让你慢慢熟悉起来

    找出了这个等价,继续完善我们的代码,我们把这个等价式写进函数里。如下:

    // 算 n 的阶乘(假设n不为0)
    int f(int n){
        if(n <= 2){
            return n;
        }
        // 把 f(n) 的等价操作写进去
        return f(n-1) * n;
    }
    

    至此,递归三要素已经都写进代码里了,所以这个 f(n) 功能的内部代码我们已经写好了。

    这就是递归最重要的三要素,每次做递归的时候,你就强迫自己试着去寻找这三个要素。

    还是不懂?没关系,我再按照这个模式讲一些题。

    有些有点小基础的可能觉得我写的太简单了,没耐心看?少侠,请继续看,我下面还会讲如何优化递归。当然,大佬请随意,可以直接拉动最下面留言给我一些建议,万分感谢!

    案例1:斐波那契数列

    斐波那契数列的是这样一个数列:1、1、2、3、5、8、13、21、34…,即第一项 f(1) = 1,第二项 f(2) = 1…,第 n 项目为 f(n) = f(n-1) + f(n-2)。求第 n 项的值是多少。

    1、第一递归函数功能

    假设 f(n) 的功能是求第 n 项的值,代码如下:

    int f(int n){
        
    }
    

    2、找出递归结束的条件

    显然,当 n = 1 或者 n = 2 ,我们可以轻易着知道结果 f(1) = f(2) = 1。所以递归结束条件可以为 n <= 2 时,f(n= = 1。代码如下:

    int f(int n){
        if(n <= 2){
            return 1;
        }
    }
    

    第三要素:找出函数的等价关系式

    题目已经把等价关系式给我们了,所以我们很容易就能够知道 f(n) = f(n-1) + f(n-2)。我说过,等价关系式是最难找的一个,而这个题目却把关系式给我们了,这也太容易,好吧,我这是为了兼顾几乎零基础的读者。

    所以最终代码如下:

    int f(int n){
        // 1.先写递归结束条件
        if(n <= 2){
            return 1;
        }
        // 2.接着写等价关系式
        return f(n-1) + f(n - 2);
    }
    

    搞定,是不是很简单?

    零基础的可能还是不大懂,没关系,之后慢慢按照这个模式练习!好吧,有大佬可能在吐槽太简单了。

    案例2:小青蛙跳台阶

    一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

    1、第一递归函数功能

    假设 f(n) 的功能是求青蛙跳上一个n级的台阶总共有多少种跳法,代码如下:

    int f(int n){
        
    }
    

    2、找出递归结束的条件

    我说了,求递归结束的条件,你直接把 n 压缩到很小很小就行了,因为 n 越小,我们就越容易直观着算出 f(n) 的多少,所以当 n = 1时,你知道 f(1) 为多少吧?够直观吧?即 f(1) = 1。代码如下:

    int f(int n){
        if(n == 1){
            return 1;
        }
    }
    

    第三要素:找出函数的等价关系式

    每次跳的时候,小青蛙可以跳一个台阶,也可以跳两个台阶,也就是说,每次跳的时候,小青蛙有两种跳法。

    第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。

    第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。

    所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码:

    int f(int n){
        if(n == 1){
            return 1;
        }
        ruturn f(n-1) + f(n-2);
    }
    

    大家觉得上面的代码对不对?

    答是不大对,当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入死循环

    这也是我要和你们说的,关于递归结束条件是否够严谨问题,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。也就是说,当我们在第二步找出了一个递归结束条件的时候,可以把结束条件写进代码,然后进行第三步,但是请注意,当我们第三步找出等价函数之后,还得再返回去第二步,根据第三步函数的调用关系,会不会出现一些漏掉的结束条件。就像上面,f(n-2)这个函数的调用,有可能出现 f(0) 的情况,导致死循环,所以我们把它补上。代码如下:

    int f(int n){
        //f(0) = 0,f(1) = 1,f(2) = 2等价于 n<=2时,f(n) = n。
        if(n <= 2){
            return n;
        }
        ruturn f(n-1) + f(n-2);
    }
    

    有人可能会说,我不知道我的结束条件有没有漏掉怎么办?别怕,多练几道就知道怎么办了。

    看到这里有人可能要吐槽了,这两道题也太容易了吧??能不能被这么敷衍。少侠,别走啊,下面出道难一点的。

    下面其实也不难了,就比上面的题目难一点点而已,特别是第三步等价的寻找。

    案例3:反转单链表。

    反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1

    链表的节点定义如下:

    class Node{
        int date;
        Node next;
    }
    

    虽然是 Java语言,但就算你没学过 Java,我觉得也是影响不大,能看懂。

    还是老套路,三要素一步一步来。

    1、定义递归函数功能

    假设函数 reverseList(head) 的功能是反转但链表,其中 head 表示链表的头节点。代码如下:

    Node reverseList(Node head){
        
    }
    

    2. 寻找结束条件

    当链表只有一个节点,或者如果是空表的话,你应该知道结果吧?直接啥也不用干,直接把 head 返回呗。代码如下:

    Node reverseList(Node head){
        if(head == null || head.next == null){
            return head;
        }
    }
    

    3. 寻找等价关系

    这个的等价关系不像 n 是个数值那样,比较容易寻找。但是我告诉你,它的等价条件中,一定是范围不断在缩小,对于链表来说,就是链表的节点个数不断在变小,所以,如果你实在找不出,你就先对 reverseList(head.next) 递归走一遍,看看结果是咋样的。例如链表节点如下

    我们就缩小范围,先对 2->3->4递归下试试,即代码如下

    Node reverseList(Node head){
        if(head == null || head.next == null){
            return head;
        }
        // 我们先把递归的结果保存起来,先不返回,因为我们还不清楚这样递归是对还是错。,
        Node newList = reverseList(head.next);
    }
    

    我们在第一步的时候,就已经定义了 reverseLis t函数的功能可以把一个单链表反转,所以,我们对 2->3->4反转之后的结果应该是这样:

    我们把 2->3->4 递归成 4->3->2。不过,1 这个节点我们并没有去碰它,所以 1 的 next 节点仍然是连接这 2。

    接下来呢?该怎么办?

    其实,接下来就简单了,我们接下来只需要把节点 2 的 next 指向 1,然后把 1 的 next 指向 null,不就行了?,即通过改变 newList 链表之后的结果如下:

    也就是说,reverseList(head) 等价于 ** reverseList(head.next)** + 改变一下1,2两个节点的指向。好了,等价关系找出来了,代码如下(有详细的解释):

    //用递归的方法反转链表
    public static Node reverseList2(Node head){
        // 1.递归结束条件
        if (head == null || head.next == null) {
                 return head;
             }
             // 递归反转 子链表
             Node newList = reverseList2(head.next);
             // 改变 1,2节点的指向。
             // 通过 head.next获取节点2
             Node t1  = head.next;
             // 让 2 的 next 指向 2
             t1.next = head;
             // 1 的 next 指向 null.
            head.next = null;
            // 把调整之后的链表返回。
            return newList;
        }
    
    

    这道题的第三步看的很懵?正常,因为你做的太少了,可能没有想到还可以这样,多练几道就可以了。但是,我希望通过这三道题,给了你以后用递归做题时的一些思路,你以后做题可以按照我这个模式去想。通过一篇文章是不可能掌握递归的,还得多练,我相信,只要你认真看我的这篇文章,多看几次,一定能找到一些思路!!

    我已经强调了好多次,多练几道了,所以呢,后面我也会找大概 10 道递归的练习题供大家学习,不过,我找的可能会有一定的难度。不会像今天这样,比较简单,所以呢,初学者还得自己多去找题练练,相信我,掌握了递归,你的思维抽象能力会更强!

    接下来我讲讲有关递归的一些优化。

    有关递归的一些优化思路

    1. 考虑是否重复计算

    告诉你吧,如果你使用递归的时候不进行优化,是有非常非常非常多的子问题被重复计算的。

    啥是子问题? f(n-1),f(n-2)…就是 f(n) 的子问题了。

    例如对于案例2那道题,f(n) = f(n-1) + f(n-2)。递归调用的状态图如下:

    (img-adCaaEyJ-1572163241563)(https://user-gold-cdn.xitu.io/2019/3/12/169722f31645ef25?w=729&h=444&f=png&s=88214)]

    看到没有,递归计算的时候,重复计算了两次 f(5),五次 f(4)。。。。这是非常恐怖的,n 越大,重复计算的就越多,所以我们必须进行优化。

    如何优化?一般我们可以把我们计算的结果保证起来,例如把 f(4) 的计算结果保证起来,当再次要计算 f(4) 的时候,我们先判断一下,之前是否计算过,如果计算过,直接把 f(4) 的结果取出来就可以了,没有计算过的话,再递归计算。

    用什么保存呢?可以用数组或者 HashMap 保存,我们用数组来保存把,把 n 作为我们的数组下标,f(n) 作为值,例如 arr[n] = f(n)。f(n) 还没有计算过的时候,我们让 arr[n] 等于一个特殊值,例如 arr[n] = -1。

    当我们要判断的时候,如果 arr[n] = -1,则证明 f(n) 没有计算过,否则, f(n) 就已经计算过了,且 f(n) = arr[n]。直接把值取出来就行了。代码如下:

    // 我们实现假定 arr 数组已经初始化好的了。
    int f(int n){
        if(n <= 1){
            return n;
        }
        //先判断有没计算过
        if(arr[n] != -1){
            //计算过,直接返回
            return arr[n];
        }else{
            // 没有计算过,递归计算,并且把结果保存到 arr数组里
            arr[n] = f(n-1) + f(n-1);
            reutrn arr[n];
        }
    }
    

    也就是说,使用递归的时候,必要
    须要考虑有没有重复计算,如果重复计算了,一定要把计算过的状态保存起来。

    2. 考虑是否可以自底向上

    对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。

    不过,有时候当 n 比较大的时候,例如当 n = 10000 时,那么必须要往下递归10000层直到 n <=1 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。

    对于这种情况,其实我们是可以考虑自底向上的做法的。例如我知道

    f(1) = 1;

    f(2) = 2;

    那么我们就可以推出 f(3) = f(2) + f(1) = 3。从而可以推出f(4),f(5)等直到f(n)。因此,我们可以考虑使用自底向上的方法来取代递归,代码如下:

    public int f(int n) {
           if(n <= 2)
               return n;
           int f1 = 1;
           int f2 = 2;
           int sum = 0;
    
           for (int i = 3; i <= n; i++) {
               sum = f1 + f2;
               f1 = f2;
               f2 = sum;
           }
           return sum;
       }
    

    这种方法,其实也被称之为递推

    最后总结

    其实,递归不一定总是从上往下,也是有很多是从下往上的,例如 n = 1 开始,一直递归到 n = 1000,例如一些排序组合。对于这种从下往上的,也是有对应的优化技巧,不过,我就先不写了,后面再慢慢写。这篇文章写了很久了,脖子有点受不了了,,,,颈椎病?害怕。。。。

    说实话,对于递归这种比较抽象的思想,要把他讲明白,特别是讲给初学者听,还是挺难的,这也是我这篇文章用了很长时间的原因,不过,只要能让你们看完,有所收获,我觉得值得!有些人可能觉得讲的有点简单,没事,我后面会找一些不怎么简单的题。最后如果觉得不错,还请给我转发 or 点赞一波!

    另外,推荐一份计算机类书单,只为让大家更加方便找到自己想要的书籍,目前已经收集了几百本了,贡献给需要的人,地址:GitHub 6K,一份覆喊各类编程书籍的书单下载

    这里帅地也整理了一些不错的算法/计算机基础的资料,送给大家:

    翻遍全网,计网、操作系统、计组相关视频被我找到了

    还有一位朋友整理的设计模式资料

    字节跳动总结的设计模式 PDF 火了,完整版开放下载!

    还有一份很不错的算法笔记

    两个月斩获 70k star,前字节大神刷题笔记

    最后,献上我备战校招的思维导图 + 提升内功的 PDF 吧

    九大思维导图助你拿到心仪的 offer

    在这里插入图片描述

    原创 PDF 助你提升基础内容,里面也有我的个人经历

    在这里插入图片描述

    下载链接:https://pan.baidu.com/s/1thH7vqBEosgRrESTUcFL2Q 密码:9x30

    如果觉得有帮助,也要记得来个赞啊,只收藏不点赞都是刷流氓,嘻嘻

    展开全文
  • 这个教程用python了一个很简单迷你程序讲解递归神经网络。 递归神经网络即RNN和一般神经网络有什么不同?出门左转我们一篇博客已经讲过了传统的神经网络不能够基于前面的已分类场景来推断接下来的场景分类,...

    总结: 我总是从迷你程序中学到很多。这个教程用python写了一个很简单迷你程序讲解递归神经网络。

    递归神经网络即RNN和一般神经网络有什么不同?出门左转我们一篇博客已经讲过了传统的神经网络不能够基于前面的已分类场景来推断接下来的场景分类,但是RNN确有一定记忆功能。废话少说,上图:

    basic_recurrence_singleton

    layer_0就是输入层,layer_1就是隐层,layer_2就是输出层。什么叫隐层呢?顾名思义,隐层就是隐藏层,训练时对外是透明的,毕竟主要关心的还是输出层的判断结果。这里RNN和一般神经网络不一样的是多了一个隐层,有没有发现layer_1有两个呢?是的,这一个红色的多出来的类似缓存的layer_1就是RNN的独特之处。SYNAPSE_0, SYNAPSE_1, SYNAPSE_h分别是输入层,输出层和隐层的权重值矩阵,模型训练的也主要是这些矩阵的权重值。

    所以,RNN的关键就是:

    每次的隐层值不再只是layer_0 SYNAPSE_0 ,而是 layer_0 SYNAPSE_0和layer_1 SYNAPSE_h 的叠加。即,每次隐层的计算是上一次隐层值和附加隐层权重SYNAPSE_h点乘叠加上这次隐层值。

    recurrence_gif

    上图展示了训练隐层的四个时间戳过程,值得注意的是,每次隐层都会保留之前输入信息,并且和此次输入层信息叠加成新的信息,再和下一次输入叠加。这就导致最后隐层包含了各种颜色的信息。而且,隐层会“遗忘”那些不重要的信息,如时间戳3把红色和紫色信息较多地“遗忘”。

    backprop_through_time

    上图是反向传播的修正过程,黑色是预测值,亮黄色是错误,橘黄色是求导值,通过求导值对各个时间戳上的权衡矩阵做修正。

    具体怎么实现呢?废话少说, 上代码:

    1. import copy, numpy as np
    2. np.random.seed(0)
    3. # compute sigmoid nonlinearity
    4. def sigmoid(x):
    5. output = 1/(1+np.exp(-x))
    6. return output
    7. # convert output of sigmoid function to its derivative
    8. def sigmoid_output_to_derivative(output):
    9. return output*(1-output)
    10. # training dataset generation
    11. int2binary = {}
    12. binary_dim = 8
    13. largest_number = pow(2,binary_dim)
    14. binary = np.unpackbits(
    15. np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
    16. for i in range(largest_number):
    17. int2binary[i] = binary[i]
    18. # input variables
    19. alpha = 0.1
    20. input_dim = 2
    21. hidden_dim = 16
    22. output_dim = 1
    23. # initialize neural network weights
    24. synapse_0 = 2*np.random.random((input_dim,hidden_dim)) - 1
    25. synapse_1 = 2*np.random.random((hidden_dim,output_dim)) - 1
    26. synapse_h = 2*np.random.random((hidden_dim,hidden_dim)) - 1
    27. synapse_0_update = np.zeros_like(synapse_0)
    28. synapse_1_update = np.zeros_like(synapse_1)
    29. synapse_h_update = np.zeros_like(synapse_h)
    30. # training logic
    31. for j in range(10000):
    32. # generate a simple addition problem (a + b = c)
    33. a_int = np.random.randint(largest_number/2) # int version
    34. a = int2binary[a_int] # binary encoding
    35. b_int = np.random.randint(largest_number/2) # int version
    36. b = int2binary[b_int] # binary encoding
    37. # true answer
    38. c_int = a_int + b_int
    39. c = int2binary[c_int]
    40. # where we'll store our best guess (binary encoded)
    41. d = np.zeros_like(c)
    42. overallError = 0
    43. layer_2_deltas = list()
    44. layer_1_values = list()
    45. layer_1_values.append(np.zeros(hidden_dim))
    46. # moving along the positions in the binary encoding
    47. for position in range(binary_dim):
    48. # generate input and output
    49. X = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])
    50. y = np.array([c[binary_dim - position - 1]]).T
    51. # hidden layer (input ~+ prev_hidden)
    52. layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))
    53. # output layer (new binary representation)
    54. layer_2 = sigmoid(np.dot(layer_1,synapse_1))
    55. # did we miss?... if so, by how much?
    56. layer_2_error = y - layer_2
    57. layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))
    58. overallError += np.abs(layer_2_error[0])
    59. # decode estimate so we can print it out
    60. d[binary_dim - position - 1] = np.round(layer_2[0][0])
    61. # store hidden layer so we can use it in the next timestep
    62. layer_1_values.append(copy.deepcopy(layer_1))
    63. future_layer_1_delta = np.zeros(hidden_dim)
    64. for position in range(binary_dim):
    65. X = np.array([[a[position],b[position]]])
    66. layer_1 = layer_1_values[-position-1]
    67. prev_layer_1 = layer_1_values[-position-2]
    68. # error at output layer
    69. layer_2_delta = layer_2_deltas[-position-1]
    70. # error at hidden layer
    71. layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1)
    72. # let's update all our weights so we can try again
    73. synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
    74. synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
    75. synapse_0_update += X.T.dot(layer_1_delta)
    76. future_layer_1_delta = layer_1_delta
    77. synapse_0 += synapse_0_update * alpha
    78. synapse_1 += synapse_1_update * alpha
    79. synapse_h += synapse_h_update * alpha
    80. synapse_0_update *= 0
    81. synapse_1_update *= 0
    82. synapse_h_update *= 0
    83. # print out progress
    84. if(j % 1000 == 0):
    85. print "Error:" + str(overallError)
    86. print "Pred:" + str(d)
    87. print "True:" + str(c)
    88. out = 0
    89. for index,x in enumerate(reversed(d)):
    90. out += x*pow(2,index)
    91. print str(a_int) + " + " + str(b_int) + " = " + str(out)
    92. print "------------"

    代码输出:

    Error:[ 3.45638663]
    Pred:[0 0 0 0 0 0 0 1]
    True:[0 1 0 0 0 1 0 1]
    9 + 60 = 1
    ------------
    Error:[ 3.63389116]
    Pred:[1 1 1 1 1 1 1 1]
    True:[0 0 1 1 1 1 1 1]
    28 + 35 = 255
    ------------
    Error:[ 3.91366595]
    Pred:[0 1 0 0 1 0 0 0]
    True:[1 0 1 0 0 0 0 0]
    116 + 44 = 72
    ------------
    Error:[ 3.72191702]
    Pred:[1 1 0 1 1 1 1 1]
    True:[0 1 0 0 1 1 0 1]
    4 + 73 = 223
    ------------
    Error:[ 3.5852713]
    Pred:[0 0 0 0 1 0 0 0]
    True:[0 1 0 1 0 0 1 0]
    71 + 11 = 8
    ------------
    Error:[ 2.53352328]
    Pred:[1 0 1 0 0 0 1 0]
    True:[1 1 0 0 0 0 1 0]
    81 + 113 = 162
    ------------
    Error:[ 0.57691441]
    Pred:[0 1 0 1 0 0 0 1]
    True:[0 1 0 1 0 0 0 1]
    81 + 0 = 81
    ------------
    Error:[ 1.42589952]
    Pred:[1 0 0 0 0 0 0 1]
    True:[1 0 0 0 0 0 0 1]
    4 + 125 = 129
    ------------
    Error:[ 0.47477457]
    Pred:[0 0 1 1 1 0 0 0]
    True:[0 0 1 1 1 0 0 0]
    39 + 17 = 56
    ------------
    Error:[ 0.21595037]
    Pred:[0 0 0 0 1 1 1 0]
    True:[0 0 0 0 1 1 1 0]
    11 + 3 = 14
    ------------

    手把手解释代码:

    0-2行: 导入所需库,初始化随机生成器。Numpy是做代数的强大库。

    4-11行: 我们的激活函数和求导函数。详细请看文章Neural Network Tutorial

    15行: 我们做个映射,把一个整数映射到一串比特串。比特串的每一位作为RNN的输入。

    16行: 比特串的最大长度。

    18行: 计算8位比特串最大可能表示的整数。

    19行: int2binary和binary都是一个从整数到比特串的表示查找表。这样比较清晰。

    26行: 学习步长设置为0.1。

    27行: 每次我们喂给RNN的输入数据是两个比特。

    28行: 这是隐层的比特数。也可以说是隐层神经元个数。隐层神经元个数如何影响收敛速度?  读者可以自行研究~

    29行: 输出层我们仅仅预测一位求和值。

    33行: 这是输入层和隐层间的权重矩阵。所以就是输入层单元*隐层单元的矩阵(2 x 16 )。

    34行: 这是隐层和输出层间的权重矩阵。所以就是隐层单元*输出层单元的矩阵(16*1 )。

    35行: 这是连接上一个时间戳隐层和当前时间戳隐层的矩阵,同时也是连接当前时间戳隐层和下一个时间戳隐层的矩阵。所以矩阵是隐层单元*隐层单元(16 x 16)。

    37 – 39行: 这些变量保存对于权重矩阵的更新值,我们的目的不就是训练好的权重矩阵吗?我们在每次迭代积累权重更新值,然后一起更新。

    42行: 我们要迭代训练100,000个训练样本。

    45行: 我们将要生成一个随机加和问题。我随机生成的整数不会超过我们所能表达的整数的一半,否则两个整数相加就有可能超过我们可以用比特串表达的整数。

    46行: 查找整数a对应的比特串。

    48行: 和45行类似

    49行: 和46行类似

    52行: 计算应该得出结果。

    53行: 查找计算结果对应的比特串

    56行: 得到一个空的比特串来存储我们RNN神经网络的预测值。

    58行: 初始化错误估计,作为收敛的依据。

    60-61行: 这两个列表是在每个时间戳跟踪输出层求导和隐层值的列表。

    62行: 开始时没有上一个时间戳隐层,所有我们置为0.

    65行: 这个迭代可能的比特串表达(8位比特串)。

    68行: X就像是文章开头图片中的”layer_0″. X 是一个2个元素的列表,第一个元素是比特串a中的,第二个元素是比特串b中的。我们用position定位比特位,是自右向左的。

    69行: 和62类似,我们的正确结果 (1或0)

    72行: 这行是代码申神奇之处!!! 请看懂这一行!!! 为了构造隐层,我们做两件事,第一步是从输入层传播到隐层(np.dot(X,synapse_0))。第二步,我们把上一个时间戳的隐层值传播到当前隐层 (np.dot(prev_layer_1, synapse_h)。最后我们把两个向量值相加! 最后交给sigmoid函数.

    75行: 这行很简单,把隐层传播到输出层,做预测。

    78行: 计算预测的错误偏差。

    79行: 计算并存储错误导数,在每个时间戳进行.

    80行: 计算错误的绝对值的和,积累起来。

    83行: 估计输出值。并且保存在d中。

    86行: 保存当前隐层值,作为下个时间戳的上个隐层值。

    90行: 所以,我们对于所有的时间戳做了前向传播,我们计算了输出层的求导并且把它们存在列表中。现在我们需要反向传播,从最后一个时间戳开始反向传播到第一个时间戳。

    92行: 像我们之前一样获得输入数据。

    93行: 选择当前隐层。

    94行: 选择上个时间戳隐层。

    97行: 选择当前输出错误。

    99行: 这行在给定下一个时间戳隐层错误和当前输出错误的情况下,计算当前隐层错误。

    102-104行: 现在我们在当前时间戳通过反向传播得到了求导,我们可以构造权重更新了(但暂时不更新权重)。我们等到完全反向传播后,才真正去更新权重。为什么?因为反向传播也是需要权重的。乱改权重是不合理的。

    109 – 115行 现在我们反向传播完毕,可以真的更新所有权重了。

    118行 – 最后 一些log和输出看结果。

     

    翻译编辑自:https://iamtrask.github.io/2015/11/15/anyone-can-code-lstm/

    展开全文
  • 中携带递归函数: class add: # n = 0 def __init__(self,x,y): self.x = x self.y = y def funadd(self): return self.x + self.y def mulit0(self,n): if n == 0: return 1 return n * self.mulit0(n ...

    类中携带递归函数:

    class add:
        # n = 0
        def __init__(self,x,y):
            self.x = x
            self.y = y
        def funadd(self):
            return self.x + self.y
        def mulit0(self,n):
            if n == 0:
                return 1
            return n * self.mulit0(n - 1)
    
    
    S = add(5,6)
    print(S.funadd())
    print(S.mulit0(7))
    
    

    实现结果:

    11
    5040
    

    整个类属于递归:

    class recursion_class:
        def rec_fun(self, num):
            print(num, end=" ")
            if num > 1:
                print(" * ",end=" ")
                return num * self.rec_fun(num-1)
            print(" = ")
            return 1
    
    def main(x):
        ret = recursion_class()
        return  recursion_class.rec_fun(ret, x)
    
    for i in range(8):
        print(main(i))
    

    实现结果:

    0  = 
    1
    1  = 
    1
    2  *  1  = 
    2
    3  *  2  *  1  = 
    6
    4  *  3  *  2  *  1  = 
    24
    5  *  4  *  3  *  2  *  1  = 
    120
    6  *  5  *  4  *  3  *  2  *  1  = 
    720
    7  *  6  *  5  *  4  *  3  *  2  *  1  = 
    5040
    
    展开全文
  • go分类递归

    2019-05-29 10:53:20
    一个递归实现分类递归的,但是试了感觉不行,主要是因为go的切片不像php数组那样现在下来方便自己后期用到,还有留给有需要的人 //分类 type Cate struct { Id int Name string Weight int Img string ...

    怎么说呢,自己也试着百度了。但是关于go的示例代码真的很少。想写一个递归实现分类递归的,但是试了感觉不行,主要是因为go的切片不像php数组那样现在写下来方便自己后期用到,还有留给有需要的人

    //分类
    type Cate struct {
       Id int
       Name string
       Weight int
       Img string
       IsNav int
       ParentId int
    }
    
    //对接前端请求解析
    type CateDto struct {
       Cate
       ParentIds []int
    }
    
    //前端需要下拉选项
    type CateOption struct {
       Value int `json:"value"`
       Label string `json:"label"`
       Children []CateOption `json:"children"`
    }
    func (service CateService) AllCate() []models.CateOption {
    //从数据库获取所有分类
       goodsCate := service.Cate.AllCate()
       var options []models.CateOption
    //这里开始递归
       o := MakeCateOption(goodsCate, 0, options)
       return o
    }
    func MakeCateOption(goodsCate []models.Cate, pid int, cp []models.CateOption) []models.CateOption {
       var ops []models.CateOption
       for _, v := range goodsCate {
          if v.ParentId == pid {
             var option models.CateOption
             option.Label = v.Name
             option.Value = v.Id
             option.Children = MakeCateOption(goodsCate, v.Id, ops)
             cp = append(cp, option)
          }
       }
       return cp
    }

    开始是这样写的

    这样写有一个问题因为这个就不说了。有兴趣可以研究一下,跟go函数运行机制和切片实现原理有关系

    var cp []models.CateOption
    func MakeCateOption(goodsCate []models.Cate, pid int) []models.CateOption {
       var ops []models.CateOption
       for _, v := range goodsCate {
          if v.ParentId == pid {
             var option models.CateOption
             option.Label = v.Name
             option.Value = v.Id
             option.Children = MakeCateOption(goodsCate, v.Id)
             cp = append(cp, option)
          }
       }
       return cp
    }
    展开全文
  • 递归

    2015-06-03 10:55:30
    递归 1.什么是递归?...递归一般用于解决三问题: (1)数据的定义是按递归定义的(如 Fibonacci函数) (2)问题的解法按递归算法实现(如 Hanoi问题) Hanoi塔问题详解,参考文档 这问题虽本身没有明
  • 10.如何写递归代码

    2020-07-31 14:41:38
    10.递归:如何用三行代码找到“最终推荐人”? markdown文件已上传至github 推荐注册佣金这个功能大家应该都不陌生吧。用户A推荐用户B来注册,B推荐C注册。这里,用户B和用户C的最终“推荐人”都为用户A,用户A没有...
  • 在Java中不光被允许这样: class A { A a; } ...但是,没有被定义完整确实...在C++中,成员不是自身的对象。原因就是没有被定义完整是不能够被实例化的,否则我要有A的对象做成员,可A我还没有定义完就实例
  • 该案例是实际开发中运用,java递归查询分类及分类下所有子分类。代码走起:1.jsp页面布局样式这里不再介绍,js业务逻辑展示分类树形结构如下:/** * 商品分类操作 */ /** * 初始化 */ $(function(){ //加载树 ...
  • PHP递归实现无限级分类

    千次阅读 2013-12-24 17:29:09
    递归,简单的说就是一段程序代码的重复调用,当把代码到一个自定义函数中,将参数等变量保存,函数中重复调用函数,直到达到某个条件才跳出,返回相应的数据。 Mysql 首先我们准备一张数据表class,记录商品分类...
  • 在web应用中,无限级别分类个相当重要的,一般分类表的记录比较少时,使用递归的方式来实现无限级别分类就已经足够了(相对简单),但是如果你的业务场景中分类表的数目特别大,比如商城的商品分类表可能记录多达...
  • 之前我了一篇关于递归的博客,反响还不错,链接如下: 递归和循环之间不得不说的故事:https://blog.csdn.net/Qizhi_Hu/article/details/104395547, 然后不久有朋友问了我个关于递归的问题,其实具体实现逻辑和我...
  • PHP不使用递归的无限级分类

    千次阅读 2015-11-29 17:07:42
    不用递归实现无限级分类,简单测试了下性能比递归稍好一点点点,但得太复杂了,还是递归简单方便点 代码: $list = array( array('id'=>1, 'pid'=>0, 'deep'=>0, 'name'=>'test1'), array('id'=>2, 'pid'=>1, '...
  • 不用递归实现无限级分类,简单测试了下性能比递归稍好一点点点,但得太复杂了,还是递归简单方便点 代码: <?php $list = array( array('id'=>1, 'pid'=>0, 'deep'=>0, 'name'=>'test1'), array('id'=>2, '...
  • 无限级分类的原理(递归方法)

    千次阅读 2018-08-21 00:06:27
    实现这种无限级分类,我们只需要在字段中增加一个pid,用于记录父类的id,这时候我们就可以采用递归的方式得到最终的结果,这里面有两种处理方式,一种是得到一个二维数组,一种是得到一个多维数组。具体代码如下: ...
  • void 递归函数的非递归实现

    千次阅读 2013-10-25 23:10:07
    对于如下类型的void型递归函数:(主要特征是递归调用的地方上下文无关)  void Fun(type a1,type a2......)  { //0号程序段-起 //0号程序段-止  Fun(b1,b2,.....); //1号程序段-起 -----注意0,1,...号程序段...
  • tp5递归 无限级分类

    千次阅读 2018-04-10 15:50:12
    在TP5公共common.php文件里写&lt;?php //计算某个类别所属的类别层数 function getcatelayer($cateid,$flag=1){ $cates = M('Gcategory'); $ini['cate_id'] = $cateid; $arr = $cates-&gt;where($...
  • 递归与尾递归

    2015-04-15 18:27:56
    1、递归  简单的来说递归就是一个函数直接或间接地... 递归就是在过程或函数调用自身。(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。 递归一般用于解决三问题:  (1)数据的定
  • 第二种的递归数据类型对应mysql的表中的数据是一条数据的主键ID是另外一条数据的parentId / * * 获取父类结点 * * @param populations * @return / public List getParentPopu(List populations) { List ...
  • 借用jquery控件实现对树的展示,后台使用递归对树的组建。。
  • java递归实现商品分类

    2019-12-04 22:28:43
    * 递归查询当前节点的id及子节点的id * @param categoryId * @return */ @Override public Response selectCategoryAndChildrenById(Integer categoryId) { Set<Category> categ...
  • 以前代码for循环的多,递归除了在大学学习以外,真没怎么用过! 最近项目中使用到了关于族谱排列的问题,就是怎么把数据库的多个子父类people对象,在界面中用树的结构展示出来 假设数据库中people有两个...
  • 递归算法讲解

    万次阅读 多人点赞 2017-06-15 22:11:51
    原作者:书呆子Rico 《递归的内涵与经典应用》 http://my.csdn.net/justloveyou_摘要: 大师 L. Peter Deutsch 说过:To Iterate is Human, to ...对一些简单的递归问题,我们总是惊叹于递归描述问题的能力和编写代

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 244,182
精华内容 97,672
关键字:

类里能写递归