精华内容
下载资源
问答
  • 快速幂算法——带你从零开始一步一步优化 目录 快速幂算法——带你从零开始一步一步优化 什么是快速幂算法 再次思考 快速幂算法初步入门 压榨性能再优化 终极优化 参考资料 博客文章版权声明 什么是...

                    快速幂算法——带你从零开始一步一步优化


    目录

                    快速幂算法——带你从零开始一步一步优化

    什么是快速幂算法

    再次思考

     

    快速幂算法初步入门

    压榨性能再优化

    终极优化

    参考资料

    博客文章版权声明


    什么是快速幂算法


    首先,我们先来看一道ACM程序设计题,这道题是杭电OJ中序号为2035的题目,没做过这道题目的同学可以跟着一起做一下(点击此处传送),题目如下:

    问题描述:

    这道题目乍一看会觉得并不难啊,题目短短一行而已,而且思路也很容易,求幂这种算法一般在初学程序设计语言的时候应该都有联系过,只要写一个简单的循环就能够搞定。

    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base,long long power){
        long long result=1;
        for(int i=1;i<=power;i++){
            result=result*base;
        }
        return result%1000;
    }
    
    

    这道题不是分分钟解决吗?接下来,让我们来写一个主函数测试一下:

    int main(){
        long long base,power;
        cin>>base>>power;
    
        cout<<"base="<<base<<" power="<<power<<" "<<normalPower(base,power)<<endl;
    
        return 0;
    
    }

    然后,让我们愉快的来求一下2^100的结果的后三位数表示的整数是什么吧!输出结果如下:

    为什么答案会是0呢?明明没有错误的啊!~ 

    先不急,我们再来考虑一下,这道题其实出的很有意思,题目要求你输出结果的后三位,为什么不让你直接输出结果呢?难道仅仅只是为了增大题目的难度吗?当然不是,我们在初中就学过“指数爆炸”,下面我们在来回顾一下“指数”的概念:

    指数:在乘方a中,其中的a叫做底数,n叫做指数,结果叫幂。

    f(x)=a^x , 随着x单位长度的递增,f(x)会呈“爆炸性”增长。

    一张纸对折一次,厚度变成原来的2倍。再对折第二次,变为原来的2的2次方倍即4倍。以此类推,假设纸的厚度为0.1mm,则对折24次以后,长度超过1千米;对折39次达55000千米,超过地球赤道长度;对折42次达44万千米,超过地球至月球的距离;对折51次达22亿千米,超过地球至太阳的距离;对折82次为51113光年,超过银河系半径的长度。

    因此,如果题目让你求2的100次方,貌似我们程序设计语言中最大的long lnog类型也无法承载这么大的数值,所以题目才不会要求你输出结果,因为结果可能会非常的大,大到没有任何类型可以承载。所以我们会发现上面的结果为什么是0,因为已经发生溢出了。

    那为什么题目要求输出结果的最后三位数表示的整数呢?有的同学可能会问:求一个数的最后三位数表示的整数好办,只要用这个结果进行“取模”运算,让其对1000取模,得到的数就是这个数最后三位数表示的整数。(例如:12345的最后三位数表示的整数是:12345%1000=345)。但是,你这结果都无法求出来,让我怎么进行“取模”运算呢?你这不是瞎闹吗?

    别急,我们首先来了解一下“取模”运算的运算法则:(具体的证明感兴趣的同学可以问度娘)

    1. (a + b) % p = (a % p + b % p) % p (1)

    2. (a - b) % p = (a % p - b % p ) % p (2)

    3. (a * b) % p = (a % p * b % p) % p (3)

    其中我们只要关注第“3”条法则即可:(a * b) % p = (a % p * b % p) % p ,我们仔细研究一下这个运算法则,会发现多个因子连续的乘积取模的结果等于每个因子取模后的乘积再取模的结果。也就是说,我们如果要求:

    (a*b*c)%d=(a%d*b%d*c%d)%d;

    因此,我们可以借助这个法则,只需要在循环乘积的每一步都提前进行“取模”运算,而不是等到最后直接对结果“取模”,也能达到同样的效果。

    所以,我们的代码可以变成这个样子:

    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base,long long power){
        long long result=1;
        for(int i=1;i<=power;i++){
            result=result*base;
            result=result%1000;
        }
        return result%1000;
    }

    我们再来测试一下,这样又能不能输出结果呢?我们仍然来求一下2^100的后三位是什么:

    这一次完美的得到了我们想要的结果。2^100的幂结果的后三位整数位376。

    为了打消一些同学对这个运算法则的怀疑,我们再用一个结果比较小的式子来验证一下:我们知道2^10为1024,按理来说,最后输出的结果的后三位数表示的整数应该是24,那么是不是这样呢?我们来试一试:

    最后的结果果然是24,所以这个法则是没有问题的。我们把下面的代码提交给OJ看一下是否能通过:

    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base, long long power) {
        long long result = 1;
        for (int i = 1; i <= power; i++) {
            result = result * base;
            result = result % 1000;
        }
        return result % 1000;
    }
    
    int main() {
        long long base, power;
        while (true) {
            cin >> base >> power;
            if (base == 0 && power == 0) break;
            cout << normalPower(base, power) << endl;
        }
        return 0;
    
    }

    最后的结果是成功Accept了。

    再次思考


    虽然这个求幂的方法很有用,并且提交给OJ也直接Accept了,但是我们来考虑一下这个算法的时间复杂度,假设我们求2的100次方,那么将会执行100次循环。如果我们分析一下这个算法,就会发现这个算法的时间复杂度为O(N),其中N为指数。求一下小的结果还好,那如果我们要求2的1000000000次方呢?这个程序可能会运行很久很久,具体会多久呢,让我们来测试一下,测试代码如下:

    #include <iostream>
    #include <cmath>
    #include <time.h>
    
    using namespace std;
    
    /**
     * 普通的求幂函数
     * @param base 底数
     * @param power  指数
     * @return  求幂结果的最后3位数表示的整数
     */
    long long normalPower(long long base, long long power) {
        long long result = 1;
        for (int i = 1; i <= power; i++) {
            result = result * base;
            result = result % 1000;
        }
        return result % 1000;
    }
    
    int main() {
        clock_t start, finish;
        //clock_t为CPU时钟计时单元数
        long long base, power;
        cin >> base >> power;
        start = clock();
        //clock()函数返回此时CPU时钟计时单元数
        cout << normalPower(base, power) << endl;
        finish = clock();
        //clock()函数返回此时CPU时钟计时单元数
        cout << "the time cost is" << double(finish - start) / CLOCKS_PER_SEC;
        //finish与start的差值即为程序运行花费的CPU时钟单元数量,再除每秒CPU有多少个时钟单元,即为程序耗时
        return 0;
    
    }

    结果如图所示:

    我们发现,虽然结果是成功求出来了,但是用了将近18秒的时间才求出最后的答案。这效率当然是非常的低下的,更谈不上实际的生产应用了。那么有没有什么好的办法能够对其进行优化呢?接下来就是我们本次的主题了:快速幂算法。

     

    快速幂算法初步入门


    快速幂算法能帮我们算出指数非常大的幂,传统的求幂算法之所以时间复杂度非常高(为O(指数n)),就是因为当指数n非常大的时候,需要执行的循环操作次数也非常大。所以我们快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。让我们先来看一个简单的例子:

    3^10=3*3*3*3*3*3*3*3*3*3

    //尽量想办法把指数变小来,这里的指数为10

    3^10=(3*3)*(3*3)*(3*3)*(3*3)*(3*3)

    3^10=(3*3)^5

    3^10=9^5

    //此时指数由10缩减一半变成了5,而底数变成了原来的平方,求3^10原本需要执行10次循环操作,求9^5却只需要执行5次循环操作,但是3^10却等于9^5,我们用一次(底数做平方操作)的操作减少了原本一半的循环量,特别是在幂特别大的时候效果非常好,例如2^10000=4^5000,底数只是做了一个小小的平方操作,而指数就从10000变成了5000,减少了5000次的循环操作。

    //现在我们的问题是如何把指数5变成原来的一半,5是一个奇数,5的一半是2.5,但是我们知道,指数不能为小数,因此我们不能这么简单粗暴的直接执行5/2,然而,这里还有另一种方法能表示9^5

    9^5=(9^4)*(9^1)

    //此时我们抽出了一个底数的一次方,这里即为9^1,这个9^1我们先单独移出来,剩下的9^4又能够在执行“缩指数”操作了,把指数缩小一半,底数执行平方操作

    9^5=(81^2)*(9^1)

    //把指数缩小一半,底数执行平方操作

    9^5=(6561^1)*(9^1)

    //此时,我们发现指数又变成了一个奇数1,按照上面对指数为奇数的操作方法,应该抽出了一个底数的一次方,这里即为6561^1,这个6561^1我们先单独移出来,但是此时指数却变成了0,也就意味着我们无法再进行“缩指数”操作了。

    9^5=(6561^0)*(9^1)*(6561^1)=1*(9^1)*(6561^1)=(9^1)*(6561^1)=9*6561=59049

    我们能够发现,最后的结果是9*6561,而9是怎么产生的?是不是当指数为奇数5时,此时底数为9。那6561又是怎么产生的呢?是不是当指数为奇数1时,此时的底数为6561。所以我们能发现一个规律:最后求出的幂结果实际上就是在变化过程中所有当指数为奇数时底数的乘积。

    让我们来看一段简单的动画演示(点击放大):

    接下来,再让我们用代码来演示一下上面的算法:

    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power % 2 == 0) {
                //如果指数为偶数
                power = power / 2;//把指数缩小为一半
                base = base * base % 1000;//底数变大成原来的平方
            } else {
                //如果指数为奇数
                power = power - 1;//把指数减去1,使其变成一个偶数
                result = result * base % 1000;//此时记得要把指数为奇数时分离出来的底数的一次方收集好
                power = power / 2;//此时指数为偶数,可以继续执行操作
                base = base * base % 1000;
            }
        }
        return result;
    }

    我们再来测试一下此时的快速幂算法和普通的求幂算法的效率,我们仍然来求2的1000000000次方,看一看用时又会是多少:

    真让人简直不可思议,竟然只花了0.002秒就求出了结果,而且结果也是376,然而普通的算法却用了将近18秒的时间才求出最后的结果。

    压榨性能再优化


    虽然上面的快速幂算法效率已经很高了,但是我们仍然能够再一次的对其进行“压榨级别”的优化。我们上面的代码看起来仍然有些地方可以再进一步地进行简化,例如在if和else代码块中仍然有重复性的代码:

                power = power / 2;
                base = base * base % 1000;

                power = power - 1;//把指数减去1,使其变成一个偶数
                power = power / 2;
    可以压缩成一句:
                power = power / 2;

    因为power是一个整数,例如当power是奇数5时,power-1=4,power/2=2;而如果我们直接用power/2=5/2=2。在整型运算中得到的结果是一样的,因此,我们的代码可以压缩成下面这样:

    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power % 2 == 1) {
                result = result * base % 1000;
            }
            power = power / 2;
            base = (base * base) % 1000;
        }
        return result;
    }

    接下来,我们来测试一下优化后的性能如何,仍然是求2的1000000000次方:

    结果仍然是正确的376,但时间上的花费从0.002减少成了0.001。

     

    终极优化


    在C语言中,power%2==1可以用更快的“位运算”来代替,例如:power&1。因为如果power为偶数,则其二进制表示的最后一位一定是0;如果power是奇数,则其二进制表示的最后一位一定是1。将他们分别与1的二进制做“与”运算,得到的就是power二进制最后一位的数字了,是0则为偶数,是1则为奇数。例如5是奇数,则5&1=1;而6是偶数,则6&1=0;因此奇偶数的判断就可以用“位运算”来替换了。

    同样,对于power=power/2来说,也可以用更快的“位运算”进行替代,我们只要把power的二进制表示向右移动1位就能变成原来的一半了。

    最后,我们的代码就能优化成下面这样:
    long long fastPower(long long base, long long power) {
        long long result = 1;
        while (power > 0) {
            if (power & 1) {//此处等价于if(power%2==1)
                result = result * base % 1000;
            }
            power >>= 1;//此处等价于power=power/2
            base = (base * base) % 1000;
        }
        return result;
    }

    我们仍然测试一下求2的1000000000次方,看看终极优化后的代码的性能是怎样的:

    简直可怕,时间花费竟然接近于0秒,我们从最开始的18秒最后压缩到接近0秒,真的是感慨算法的威力!如果同样两家公司,采用不同的算法,给用户带来的体验区别是非常大的,这无不让我们感受到算法的威力。

     

    基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众号吧!


     

    参考资料


    【1】https://www.rookieslab.com/posts/fast-power-algorithm-exponentiation-by-squaring-cpp-python-implementation#brute-force-python-implementation  作者:Ravi Ojha 翻译:刘扬俊

    【2】百度百科——指数爆炸

    https://baike.baidu.com/item/%E6%8C%87%E6%95%B0%E7%88%86%E7%82%B8/8440078?fr=aladdin 

     

    博客文章版权声明


     

     

     

     

     

     

     

     

    展开全文
  • 如何从RNN起步,一步一步通俗理解LSTM

    万次阅读 多人点赞 2019-05-06 23:47:54
    如何从RNN起步,一步一步通俗理解LSTM 前言 提到LSTM,之前学过的同学可能最先想到的是ChristopherOlah的博文《理解LSTM网络》,这篇文章确实厉害,网上流传也相当之广,而且当你看过了网上很多关于LSTM的文章...

                                 如何从RNN起步,一步一步通俗理解LSTM

     

     

    前言

    提到LSTM,之前学过的同学可能最先想到的是ChristopherOlah的博文《理解LSTM网络》,这篇文章确实厉害,网上流传也相当之广,而且当你看过了网上很多关于LSTM的文章之后,你会发现这篇文章确实经典。不过呢,如果你是第一次看LSTM,则原文可能会给你带来不少障碍:

    一者,一上来就干LSTM,不少读者可能还没理解好RNN。基于此,我们可以从最简单的单层网络开始学起;
    二者,原文没有对LSTM的三个门解释的足够细致,包括三个不同的sigmoid函数用的同一个符号σ(有错么?没错,看多了就会明白这是习惯用法);
    三者,不同的权值也用的同一个符号w,而当把图、公式、关系一一对应清楚后,初学就不会一脸懵逼了。甚至我们把各个式子的计算过程用有方向的水流表示出来,则会好懂不少,这个时候就可以上动图。

    而我自己就是这么经历过来的,虽然学过了不少模型/算法,但此前对LSTM依然很多不懂,包括一开始反复看ChristopherOlah博文《理解LSTM网络》,好在和我司AI Lab陈博士反复讨论之后,终于通了!

    侧面说明,当一个人冥思苦想想不通时,十之八九是因为看的资料不够通俗,如果还是不行,则问人,结果可能瞬间领悟,这便是教育的意义,也是我们做七月在线的巨大价值。

    众所周知,我们已经把SVM、CNN、xgboost、LSTM等很多技术,写的/讲的国内最通俗易懂了,接下来,我们要把BERT等技术也写的/讲的国内最通俗易懂,成为入门标准,而且不单单是从NNLM \rightarrow Word2Vec \rightarrow Seq2Seq \rightarrow Seq2Seq with Attention \rightarrow Transformer \rightarrow Elmo \rightarrow GPT \rightarrow BERT,我们希望给所有AI初学者铺路:一步一个台阶,而不是出现理解断层。

    本文在ChristopherOlah的博文及@Not_GOD 翻译的译文等文末参考文献的基础上做了大量便于理解的说明/注解(这些说明/注解是在其他文章里不轻易看到的),一切为更好懂。

     

    一、RNN

    1.1 从单层网络到经典的RNN结构
    在学习LSTM之前,得先学习RNN,而在学习RNN之前,首先要了解一下最基本的单层网络,它的结构如下图所示:

    输入是x,经过变换Wx+b和激活函数f,得到输出y。相信大家对这个已经非常熟悉了。

    在实际应用中,我们还会遇到很多序列形的数据:


    如:

    1. 自然语言处理问题。x1可以看做是第一个单词,x2可以看做是第二个单词,依次类推。
    2. 语音处理。此时,x1、x2、x3……是每帧的声音信号。
    3. 时间序列问题。例如每天的股票价格等等。

    而其中,序列形的数据就不太好用原始的神经网络处理了。

    为了建模序列问题,RNN引入了隐状态h(hidden state)的概念,h可以对序列形的数据提取特征,接着再转换为输出。

    先从h_{1}的计算开始看:


    图示中记号的含义是:
    a)圆圈或方块表示的是向量。
    b)一个箭头就表示对该向量做一次变换。如上图中h_{0}x_{1}分别有一个箭头连接,就表示对h_{0}x_{1}各做了一次变换。
    在很多论文中也会出现类似的记号,初学的时候很容易搞乱,但只要把握住以上两点,就可以比较轻松地理解图示背后的含义。

    h_{2}的计算和h_{1}类似。但有两点需要注意下:

    1. 在计算时,每一步使用的参数U、W、b都是一样的,也就是说每个步骤的参数都是共享的,这是RNN的重要特点,一定要牢记;
    2. 而下文马上要看到的LSTM中的权值则不共享,因为它是在两个不同的向量中。而RNN的权值为何共享呢?很简单,因为RNN的权值是在同一个向量中,只是不同时刻而已。


    依次计算剩下来的(使用相同的参数U、W、b):


    我们这里为了方便起见,只画出序列长度为4的情况,实际上,这个计算过程可以无限地持续下去。

    我们目前的RNN还没有输出,得到输出值的方法就是直接通过h进行计算:


    正如之前所说,一个箭头就表示对对应的向量做一次类似于f(Wx+b)的变换,这里的这个箭头就表示对h1进行一次变换,得到输出y1。

    剩下的输出类似进行(使用和y1同样的参数V和c):

    OK!大功告成!这就是最经典的RNN结构,是x1, x2, .....xn,输出为y1, y2, ...yn,也就是说,输入和输出序列必须要是等长的。

    1.2 RNN的应用
    人类并不是每时每刻都从一片空白的大脑开始他们的思考。在你阅读这篇文章时候,你都是基于自己已经拥有的对先前所见词的理解来推断当前词的真实含义。我们不会将所有的东西都全部丢弃,然后用空白的大脑进行思考。我们的思想拥有持久性。

    传统的神经网络并不能做到这点,看起来也像是一种巨大的弊端。例如,假设你希望对电影中的每个时间点的时间类型进行分类。传统的神经网络应该很难来处理这个问题:使用电影中先前的事件推断后续的事件。循环神经网络RNN解决了这个问题。

    通过上文第一节我们已经知道,RNN是包含循环的网络,在这个循环的结构中,每个神经网络的模块,读取某个输入,并输出一个值(注:输出之前由y表示,从此处起,改为隐层输出h表示),然后不断循环。循环可以使得信息可以从当前步传递到下一步。

    这些循环使得RNN看起来非常神秘。然而,如果你仔细想想,这样也不比一个正常的神经网络难于理解。RNN可以被看做是同一神经网络的多次复制,每个神经网络模块会把消息传递给下一个。所以,如果我们将这个循环展开:

    链式的特征揭示了RNN本质上是与序列和列表相关的。他们是对于这类数据的最自然的神经网络架构。

    1.3 RNN的局限:长期依赖(Long-TermDependencies)问题

    RNN的关键点之一就是他们可以用来连接先前的信息到当前的任务上,例如使用过去的视频段来推测对当前段的理解。如果RNN可以做到这个,他们就变得非常有用。但是真的可以么?答案是,还有很多依赖因素。

    有时候,我们仅仅需要知道先前的信息来执行当前的任务。例如,我们有一个语言模型用来基于先前的词来预测下一个词。如果我们试着预测“the clouds are in the sky”最后的词,我们并不再需要其他的信息,因为很显然下一个词应该是sky。在这样的场景中,相关的信息和预测的词位置之间的间隔是非常小的,RNN可以学会使用先前的信息。


    但是同样会有一些更加复杂的场景。假设我们试着去预测“I grew up in France...I speak fluent French”最后的词。当前的信息建议下一个词可能是一种语言的名字,但是如果我们需要弄清楚是什么语言,我们是需要先前提到的离当前位置很远的France的上下文的。这说明相关信息和当前预测位置之间的间隔就肯定变得相当的大。

    不幸的是,在这个间隔不断增大时,RNN会丧失学习到连接如此远的信息的能力。


    在理论上,RNN绝对可以处理这样的长期依赖问题。人们可以仔细挑选参数来解决这类问题中的最初级形式,但在实践中,RNN肯定不能够成功学习到这些知识。Bengio,etal.(1994)等人对该问题进行了深入的研究,他们发现一些使训练RNN变得非常困难的相当根本的原因。

    换句话说, RNN 会受到短时记忆的影响。如果一条序列足够长,那它们将很难将信息从较早的时间步传送到后面的时间步。

    因此,如果你正在尝试处理一段文本进行预测,RNN 可能从一开始就会遗漏重要信息。在反向传播期间(反向传播是一个很重要的核心议题,本质是通过不断缩小误差去更新权值,从而不断去修正拟合的函数),RNN 会面临梯度消失的问题。

    因为梯度是用于更新神经网络的权重值(新的权值 = 旧权值 - 学习率*梯度),梯度会随着时间的推移不断下降减少,而当梯度值变得非常小时,就不会继续学习。​

    换言之,在递归神经网络中,获得小梯度更新的层会停止学习—— 那些通常是较早的层。 由于这些层不学习,RNN 可以忘记它在较长序列中看到的内容,因此具有短时记忆。

    而梯度爆炸则是因为计算的难度越来越复杂导致。

    然而,幸运的是,有个RNN的变体——LSTM,可以在一定程度上解决梯度消失和梯度爆炸这两个问题!

     

     

    二、LSTM网络
    Long ShortTerm 网络——一般就叫做LSTM——是一种RNN特殊的类型,可以学习长期依赖信息。当然,LSTM和基线RNN并没有特别大的结构不同,但是它们用了不同的函数来计算隐状态。LSTM的“记忆”我们叫做细胞/cells,你可以直接把它们想做黑盒,这个黑盒的输入为前状态和当前输入。这些“细胞”会决定哪些之前的信息和状态需要保留/记住,而哪些要被抹去。实际的应用中发现,这种方式可以有效地保存很长时间之前的关联信息。

    2.1 什么是LSTM网络

    举个例子,当你想在网上购买生活用品时,一般都会查看一下此前已购买该商品用户的评价。


    当你浏览评论时,你的大脑下意识地只会记住重要的关键词,比如“amazing”和“awsome”这样的词汇,而不太会关心“this”、“give”、“all”、“should”等字样。如果朋友第二天问你用户评价都说了什么,那你可能不会一字不漏地记住它,而是会说出但大脑里记得的主要观点,比如“下次肯定还会来买”,那其他一些无关紧要的内容自然会从记忆中逐渐消失。

    而这基本上就像是 LSTM 或 GRU 所做的那样,它们可以学习只保留相关信息来进行预测,并忘记不相关的数据。简单说,因记忆能力有限,记住重要的,忘记无关紧要的。


    LSTM由Hochreiter&Schmidhuber(1997)提出,并在近期被AlexGraves进行了改良和推广。在很多问题,LSTM都取得相当巨大的成功,并得到了广泛的使用。
    LSTM通过刻意的设计来避免长期依赖问题。记住长期的信息在实践中是LSTM的默认行为,而非需要付出很大代价才能获得的能力!

    所有RNN都具有一种重复神经网络模块的链式的形式。在标准的RNN中,这个重复的模块只有一个非常简单的结构,例如一个tanh层。

    激活函数 Tanh 作用在于帮助调节流经网络的值,使得数值始终限制在 -1 和 1 之间。


    LSTM同样是这样的结构,但是重复的模块拥有一个不同的结构。具体来说,RNN是重复单一的神经网络层,LSTM中的重复模块则包含四个交互的层,三个Sigmoid 和一个tanh层,并以一种非常特殊的方式进行交互。

    上图中,σ表示的Sigmoid 激活函数与 tanh 函数类似,不同之处在于 sigmoid 是把值压缩到0~1 之间而不是 -1~1 之间。这样的设置有助于更新或忘记信息:

    • 因为任何数乘以 0 都得 0,这部分信息就会剔除掉;
    • 同样的,任何数乘以 1 都得到它本身,这部分信息就会完美地保存下来。

    相当于要么是1则记住,要么是0则忘掉,所以还是这个原则:因记忆能力有限,记住重要的,忘记无关紧要的

    此外,对于图中使用的各种元素的图标中,每一条黑线传输着一整个向量,从一个节点的输出到其他节点的输入。粉色的圈代表pointwise的操作,诸如向量的和,而黄色的矩阵就是学习到的神经网络层。合在一起的线表示向量的连接,分开的线表示内容被复制,然后分发到不同的位置。


    2.2 LSTM的核心思想
    LSTM的关键就是细胞状态,水平线在图上方贯穿运行。
    细胞状态类似于传送带。直接在整个链上运行,只有一些少量的线性交互。信息在上面流传保持不变会很容易。

    LSTM有通过精心设计的称作为“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。他们包含一个sigmoid神经网络层和一个pointwise乘法的非线性操作。

    如此,0代表“不许任何量通过”,1就指“允许任意量通过”!从而使得网络就能了解哪些数据是需要遗忘,哪些数据是需要保存。

    LSTM拥有三种类型的门结构:遗忘门/忘记门、输入门和输出门,来保护和控制细胞状态。下面,我们来介绍这三个门。

     


    三、逐步理解LSTM
    3.1 忘记门
    在我们LSTM中的第一步是决定我们会从细胞状态中丢弃什么信息。这个决定通过一个称为“忘记门”的结构完成。该忘记门会读取上一个输出和当前输入,做一个Sigmoid 的非线性映射,然后输出一个向量f_{t}(该向量每一个维度的值都在0到1之间,1表示完全保留,0表示完全舍弃,相当于记住了重要的,忘记了无关紧要的),最后与细胞状态相乘。

    类比到语言模型的例子中,则是基于已经看到的预测下一个词。在这个问题中,细胞状态可能包含当前主语的性别,因此正确的代词可以被选择出来。当我们看到新的主语,我们希望忘记旧的主语,进而决定丢弃信息。

    大部分初学的读者看到这,可能会有所懵逼,没关系,我们分以下两个步骤理解:

    1. 对于上图右侧公式中的权值W_{f},准确的说其实是不共享,即是不一样的。有的同学可能第一反应是what?别急,我展开下你可能就瞬间清晰了,即:f_{t} = \sigma (W_{fh}h_{t-1} + W_{fx} x_{t} + b_{f})
    2. 至于右侧公式和左侧的图是怎样的一个一一对应关系呢?如果是用有方向的水流表示计算过程则将一目了然,上动图!红圈表示Sigmoid 激活函数,篮圈表示tanh 函数:


    3.2 输入门
    下一步是确定什么样的新信息被存放在细胞状态中。这里包含两个部分:
    第一,sigmoid层称“输入门层”决定什么值我们将要更新;
    第二,一个tanh层创建一个新的候选值向量,会被加入到状态中。
    下一步,我们会讲这两个信息来产生对状态的更新。

    在我们语言模型的例子中,我们希望增加新的主语的性别到细胞状态中,来替代旧的需要忘记的主语,进而确定更新的信息。

    继续分两个步骤来理解:

    1. 首先,为便于理解图中右侧的两个公式,我们展开下计算过程,即i_{t} = \sigma (W_{ih}h_{t-1} + W_{ix}x_{t} + b_{i})\tilde{C_{t}} = tanh(W_{Ch}h_{t-1} + W_{Cx}x_{t} + b_{C})
    2. 其次,上动图!


    3.3 细胞状态
    现在是更新旧细胞状态的时间了,更新为。前面的步骤已经决定了将会做什么,我们现在就是实际去完成。
    我们把旧状态与相乘,丢弃掉我们确定需要丢弃的信息。接着加上。这就是新的候选值,根据我们决定更新每个状态的程度进行变化。
    在语言模型的例子中,这就是我们实际根据前面确定的目标,丢弃旧代词的性别信息并添加新的信息的地方,类似更新细胞状态。

    再次动图!


    3.4 输出门
    最终,我们需要确定输出什么值。这个输出将会基于我们的细胞状态,但是也是一个过滤后的版本。

    首先,我们运行一个sigmoid层来确定细胞状态的哪个部分将输出出去。
    接着,我们把细胞状态通过tanh进行处理(得到一个在-1到1之间的值)并将它和sigmoid门的输出相乘,最终我们仅仅会输出我们确定输出的那部分。

    在语言模型的例子中,因为他就看到了一个代词,可能需要输出与一个动词相关的信息。例如,可能输出是否代词是单数还是负数,这样如果是动词的话,我们也知道动词需要进行的词形变化,进而输出信息。

    依然分两个步骤来理解:

    1. 展开图中右侧第一个公式,o_{t} = \sigma (W_{oh}h_{t-1} + W_{ox}x_{t} + b_{o})
    2. 最后一个动图:

     


    四、LSTM的变体
    我们到目前为止都还在介绍正常的LSTM。但是不是所有的LSTM都长成一个样子的。实际上,几乎所有包含LSTM的论文都采用了微小的变体。差异非常小,但是也值得拿出来讲一下。
    4.1 peephole连接与coupled
    其中一个流形的LSTM变体,就是由Gers&Schmidhuber(2000)提出的,增加了“peepholeconnection”。是说,我们让门层也会接受细胞状态的输入。

    peephole连接

    上面的图例中,我们增加了peephole到每个门上,但是许多论文会加入部分的peephole而非所有都加。

    另一个变体是通过使用coupled忘记和输入门。不同于之前是分开确定什么忘记和需要添加什么新的信息,这里是一同做出决定。我们仅仅会当我们将要输入在当前位置时忘记。我们仅仅输入新的值到那些我们已经忘记旧的信息的那些状态。

     

    4.2 GRU
    另一个改动较大的变体是GatedRecurrentUnit(GRU),这是由Cho,etal.(2014)提出。它将忘记门和输入门合成了一个单一的更新门。同样还混合了细胞状态和隐藏状态,和其他一些改动。最终的模型比标准的LSTM模型要简单,也是非常流行的变体。

    为了便于理解,我把上图右侧中的前三个公式展开一下

    1. z_{t} = \sigma (W_{zh}h_{t-1} + W_{zx}x_{t})
    2. r_{t} = \sigma (W_{rh}h_{t-1} + W_{rx}x_{t})
    3. \tilde{h} = tanh(W_{rh}(r_{t}h_{t-1}) + W_{x}x_{t})

    这里面有个小问题,眼尖的同学可能看到了,z_{t}r_{t}都是对h_{t-1}x_{t}做的Sigmoid非线性映射,那区别在哪呢?原因在于GRU把忘记门和输入门合二为一了,而z_{t}是属于要记住的,反过来1- z_{t}则是属于忘记的,相当于对输入h_{t-1}x_{t}做了一些更改/变化,而r_{t}则相当于先见之明的把输入h_{t-1}x_{t}z_{t}/1- z_{t}对其做更改/变化之前,先事先存一份原始的,最终在\tilde{h}那做一个tanh变化。

    这里只是部分流行的LSTM变体。当然还有很多其他的,如Yao,etal.(2015)提出的DepthGatedRNN。还有用一些完全不同的观点来解决长期依赖的问题,如Koutnik,etal.(2014)提出的ClockworkRNN。

    要问哪个变体是最好的?其中的差异性真的重要吗?Greff,etal.(2015)给出了流行变体的比较,结论是他们基本上是一样的。Jozefowicz,etal.(2015)则在超过1万种RNN架构上进行了测试,发现一些架构在某些任务上也取得了比LSTM更好的结果。

     

     

    五、LSTM相关面试题

    为帮助大家巩固以上的所学,且助力找AI工作的朋友,特从七月在线AI题库里抽取以下关于LSTM的典型面试题,更具体的答案参见:https://www.julyedu.com/search?words=LSTM(打开链接后,勾选“面试题”的tab)。

    1. LSTM结构推导,为什么比RNN好?
    2. GRU是什么?GRU对LSTM做了哪些改动?
    3. LSTM神经网络输入输出究竟是怎样的?
    4. 为什么LSTM模型中既存在sigmoid又存在tanh两种激活函数,而不是选择统一一种sigmoid或者tanh?这样做的目的是什么?
    5. 如何修复梯度爆炸问题?
    6. 如何解决RNN梯度爆炸和弥散的问题?

     

     

    六、参考文献

    1. ChristopherOlah的博文《理解LSTM网络
    2. @Not_GOD 翻译ChristopherOlah的博文《理解LSTM网络
    3. RNN是怎么从单层网络一步一步构造的?
    4. 通过一张张动图形象的理解LSTM
    5. 如何理解LSTM网络(超经典的Christopher Olah的博文之July注解版)
    6. LSTM相关的典型面试题:https://www.julyedu.com/search?words=LSTM(打开链接后,勾选“面试题”的tab)
    7. 如何理解反向传播算法BackPropagation
    展开全文
  • 一步一步理解欧拉公式

    万次阅读 多人点赞 2019-06-30 12:50:03
    如果你按这些步骤一步一步去做,先搞清什么,再搞清什么(可以从别的文章/知乎去弄明白),可能 最后就能看懂这个宇宙耍帅第一公式了 :) 本文不从泰勒展开角度来说明。 当x = 时: 一:搞清sin/cos的意义 ...

    这篇文章 不保证你看了后一定会明白 欧拉公式,那么多数学专业的人深入浅出讲得比我好多了。但提供一种 从不明白到明白经过的一些步骤。如果你按这些步骤一步一步去做,先搞清什么,再搞清什么(可以从别的文章/知乎去弄明白),可能 最后就能看懂这个宇宙耍帅第一公式了 :)
    本文不从泰勒展开角度来说明。

    e^{ix} = cosx + isinx

    当x = \pi 时:

    e^{i\pi } +1 = 0


    一:搞清sin/cos的意义 (可不只是三角形对边/斜边 邻边/斜边哦,要从单位圆旋转的角度来理解)
    二:搞清弧度/角度的区别  ----这是为了理解,为什么\pi会出现在这个公式里,为什么指数中的值是\pi而不是其他值
    三:搞清e的特性 e的由来 同时对数 也绕不过的得配套弄明白  ----这是为了让你理解 ,为什么e会出现在这个公式里
    四:了解指数函数  ----理解复指数的基础
    五:搞清 复数的意义  ----进而理解复指数


    下面分别补充一点信息吧
    一:三角形sin/cos的意义 : 对边/斜边, 邻边/斜边  为什么不够? 几年前我是从自问自答这个问题开始的:钝角三角形的钝角 的sin值是哪条边 比 哪条边?  如果你得到的答案是其补角的sin,这只能说是机械记忆,还没理解透彻。


    二:关键是要搞清:一度是一份,一个圆并不是只能分成360份,它还可以被分成362份。(只是大家约定俗成分成360份)这样每一度的大小是不固定的,依赖于我们把圆分成多少份。 而弧度的大小,就去除了这种量纲的影响,1弧度与数轴上的1的长度/大小相同,这样一个角有多大,就可以用数轴上的数(值为这个角的弧度)来表明了。用度数,做不到。欧拉公式如果不使用弧度来表示,则公式中,某个右上角, 会有一个小圆圈!  而只有用弧度,才能像现在这样简洁。

     

    三:这个要说的就太多了。 先去搜那些讲得很好的文章/帖子吧。这里摘一点对我来说精华的句子:(感谢那么多前辈的分享)
    "指数得到数量,对数得到时间。e^{x}表示输入时间得到数量 ln(x)表示 输入数量 计算达到么多数量所需要的时间"

    "如何理解对数? 一个直观的解释是:对数指的是达到某一数量 所需要的时间"
    "e^{x} = 增长 = e^{增长数率*时间}^增长数率*时间 = e^{rate*time}"  rate一般是小于等于1的,但time可以大于1
    "任何一个数字,都可以 在某个时间 通过某个增长率来达到! 比如以e^{1}的速度增长 9 = e^{ln9}  这里面的ln9就是达到9这个数字的时间"
    "e^{x}也是这样,而且比球面更厉害  无论如何降维, e^{x}总是老样子,一点都没变,就好像你切掉孙悟空的一部分,你以为是一小片肉,睁眼一看,居然是另一个孙悟空,而且一样大!这种自相似或全息性太匪夷所思、太好玩儿了!"

     

    四:略

    五:同样 先去搜那些讲得很好的文章/帖子吧。这里摘一点对我来说精华的句子:(感谢那么多前辈的分享)
    "如果数 1 表示向右移动 1 米的话,那么数 -1 就可以表示向左移动 1 米,数 i 可以表示向前移动 1 米,数 -i 可以表示向后移动 1 米,-1+i 可以表示向左前方移动 根号2 米。可见,虚数可以非常方便地描述二元事物"
    "将实数a乘以i, 相当于将0 a 这个向量逆时针旋转90度, 所以乘以两个i就是旋转180度, 也就是变成了-a,所以i的平方等于-1"

    将一个实数 加i 表示向实数轴垂直的方向 也就是虚数轴方向 前进1单位, 加2i表示前进两个单位。乘以i呢, 逆时针旋转90度; 乘i再乘i呢,逆时针旋转180度;i的3次方呢? 逆时针旋转270度;i的更多次方呢? 圆周旋转开始了。。 欧拉的远距离背影看到了吗?   ---这句话可是我原创的 哈哈

    举个栗子:
    e^{i}= e^{i1}次方 就是这个图上的A点 1就是角theta的弧度值  e^{i\pi/2}就是 90度角即\pi /2角   e^{i\pi }就是180度角即\pi
    是不是能看到大神背影的轮廓了?

    复指数: " f(z)=e^{z}这个函数是可以定义在整个复数域上的,通过f(z)=f(x+iy)=e^{x+iy}= e^{x*(cosy+isiny)}来定义,后面这个也叫欧拉公式。"


    这个实数  我们取1, 也就是欧拉公式中的1. 1是这个单位圆的半径,是被旋转的数。旋转多少呢?旋转\pi(无单位,弧度数值就是旋转的量),也就是半圆了。现在1有了,旋转需要的i有了,为什么指数中是\pi也来了。关键就差e了

    虽然我之前知道了e的特性,但在很长一段时间内我都不明白,欧拉公式中的e为什么一定是e,换成 2 3 5就不行?  仍然是知乎这条跟小学解释欧拉公式的回答启发了我:  https://www.zhihu.com/question/41134540?from=profile_question_card

    借酒老师的图:

    旋转,稍变通置换想一下, 也就是一种向某个方向的增长。既然是增长,就有增长的速度了。如果按全速 (单位时间增长率100%)增长/旋转 /跑路到\pi的结果,就到了起始点半圆的另一端,1的对面-1.

    所以(再借一下图),桔色的线是以e为底 旋转的结果。如果底数换成2(或者说0.9e),可能的结果是浅蓝色的线;加和的结果>0;
    如果底数换成3(或者说1.1e),可能的结果是深蓝色的线,加和的结果<0。只有以e的速度 增长,才会刚刚好落在-1点,加和结果为0! (请注意这张图可能是错的,原因见后面)

    说完了,原来不明白的你,现在明白了吗? 没明白, 很好,我以前跟你一样。 没事,再多搜搜 想想,不行再晾晾再回来, 会明白的。念念不忘,可能有回响,哈哈
    最近总算搞清了这个,比较开心。分享这个过程。 人一但明白了某些东西,就回不到不明白的时候。。。生活学习通用。。呵呵
    "穿干净的衣 睡舒服的床 读有益的书  爱值得的人 "是我很喜欢的一句话,读来/想来都觉得温暖,分享给喜欢探索未知的你。嘿嘿

     

     

    ---------------------------------------------

    190828补充:

    最近小组分享欧拉公式,其他不熟悉欧拉公式的同学,最大的疑问是:为什么 e^{i}可以代表旋转。他们可以理解 +i 以及 *i 能代表旋转;且 2 * 2 * 2 * 2 = 2^{4} ok没问题,但e^{i}  为什么能代表旋转?看起来是i个e相乘 ?  不能用欧拉公式来证明,这样就是循环证明了。(2^{i} = 2^{i ln2} 是沿圆周运动ln2弧度,但这已经是运用了欧拉公式的结论)

    好问题,我从来没这样想过...

    2020年某天觉得最简单的回答可以是: 不要把指数上的i一定认为是(圆周)旋转,可以认为指数上的i 是运动

     

    有两篇文章,可能可以解答:

    https://blog.csdn.net/ccnt_2012/article/details/85095765   欧拉公式,复数域的成人礼

    https://wenku.baidu.com/view/47451ebcb84ae45c3a358c23.html   复指数函数的定义问题 铜仁学院 孙小康徐松金

     

    第一篇文章里:分别用极限/泰勒展开/导数的方式 来说明了。
    用极限来理解  我觉得也比较好理解了,只要你愿意把x用i\Theta替换,已经从普通的幂 联系到了复数乘法: 假设theta=1

    e^{^{i}} = \lim_{n \to \infty } (1+\frac{i}{n})^{n}=

    n=3时:

    (1+\frac{i}{3})^{^{3}} = (1+ \frac{i}{3}) * (1+ \frac{i}{3}) * (1+ \frac{i}{3})

     n更大时,有更多个因子在相乘 乘的结果在一步步逼近e^{i} 所在的点的位置。
    用导数的方式来看,如果底数换成2或3  不能很好地代表旋转,甚至不能代表旋转,因为运动的速度 不像以e为底时,是垂直于e^{^{it}}的。 上面最后一张图 深蓝和浅蓝色的线 基本上也是错的,因为运动的速度和方向不是想象中那么好,可能在五度以内就跑到不知道什么方向去了,都不一定能到第二象限。。此图仅供初步理解想象
    只有 e^{i} 才能代表圆周运动。


    第二篇文章 从解析函数 柯西黎曼条件的角度说明了 为什么复指数能与三角函数来。且文章的最后提到:欧拉公式 左边的e 只是一个符号,并不特指2.718..那个无理数。 也就是说 貌似可以写成这样 ?

    g^{ix} = cosx + isinx

    但是当x = \pi 时:这个e肯定不能是其他值的。

    e^{i\pi } +1 = 0

    展开全文
  • 一步一步教你在Linux上搭建云服务器

    千次阅读 多人点赞 2019-11-11 00:51:07
    131/jre export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin 保存 之后我们通过阿里云,打开云服务器,点击如图: 输入:# java 再输入:#java –version 显示与上面一样的结果,表示jdk安装以及环境变量配置全部完成...

    第一步:安装WinSCP【界面化Linux,不需要打太多命令】

    1.安装包
    方式一:网盘下载
    链接:https://pan.baidu.com/s/1WWO_FNQMEG007RfkZxN02g
    提取码:69l3
    方式二:官网下载
    https://winscp.net/eng/download.php

    安装好后,基本都是Next即可!

    第二步:安装JDK

    1.安装包
    方式一:官网下载
    方式二:网盘下载
    链接:https://pan.baidu.com/s/1Mzpqh5LdjXK5pHdgfw516Q
    提取码:5eib

    2.打开刚刚我们安装好的WinSCP
    在这里插入图片描述

    在这里插入图片描述

    3.然后我们就成功进入到了远程的Linux服务器主机上,接下来就是要把我们下载好的jdk安装包复制并粘贴到home目录下,并新建一个文件夹cmfchina,放到里面,接下来我们打开终端命令
    在这里插入图片描述
    输入命令:tar -zxvf xxxxx.tar.gz
    在这里插入图片描述
    可以看到我们成功的解压了jdk安装包

    4.接下来,就是要配置环境变量

    在根目录下,找到etc文件夹,点击找到profile文件,用编辑或记事本打开,修改里面的内容
    在这里插入图片描述
    在最底部,添加如下代码:

    export JAVA_HOME=/home/cmfchina/jdk1.8.0_131
    export JRE_HOME=/home/cmfchina/jdk1.8.0_131/jre
    export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
    

    在这里插入图片描述
    保存
    之后我们通过阿里云,打开云服务器,点击如图:
    在这里插入图片描述
    输入:# java
    在这里插入图片描述
    再输入:#java –version
    在这里插入图片描述
    显示与上面一样的结果,表示jdk安装以及环境变量配置全部完成!

    第三步:安装tomcat

    方式一:网盘下载
    链接:https://pan.baidu.com/s/1e9zwE8b1N36iSXGehZXnDw
    提取码:o48v
    方式二:官网下载
    百度tomcat就可以了
    2.下载好安装包后,将安装包装到,/usr/tomcat 目录下
    并输入命令:tar -zxvf xxxxx.tar.gz ,完成解压,如图:
    在这里插入图片描述
    3.跟上面一样,通过云服务器启动tomcat,并且输入如下命令
    在这里插入图片描述
    也就是通过命令,进入到你的tomcat下的bin目录,并通过sh startup.sh,启动tomcat服务器!

    第四步:到云控制台,添加网络安全组配置

    我这里用的阿里云服务器,你们如果用的是百度云、华为云、腾讯云等其他服务器的话,步骤都是一样的,好的,那么下面你们跟着我的图片按顺序来配置就可以了!

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    第五步:在浏览器中输入,查看效果

    1.首先,我们直接输入:主机名:8080,看看能不能自动访问到tomcat的index.jsp。
    在这里插入图片描述
    2.我们将一个html页面放到我们tomcat目录下的webapps/ROOT,新建一个test的文件夹,将html页面放到里面去,然后我们在网址栏输入:主机名:8080/test/index.html
    在这里插入图片描述

    到此,Linux搭建云服务器的过程就圆满结束了,感谢各位的收看,如果有喜欢博主的话,记得点个关注喔~!爱你们

    展开全文
  • 一步一步认识用户画像

    万次阅读 热门讨论 2016-11-25 00:22:28
    一步一步认识用户画像标签: 用户画像 GBDT 建模 Python作为一名资深吃瓜群众,身处大数据时代,过去一直不知道用户画像的存在。用户画像到底是何来头,下面会一一告诉你~
  • 一步一步学习USB程序

    2011-11-16 10:24:34
    一步一步学习USB程序,其中GUID ClassGuid 是你在驱动中生成的一个号,它必须跟你的驱动对应起来,这个号的位置在驱动程序的XXXDeviceInterface.h中的#define XXXDevice_CLASS_GUID ....(XXX表示你起的工程名字)。...
  • 代表一个占位符 String sql = "select * from test where id=? and pwd=?"; //程序执行到此,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译 //此时sql语句中没有用户输入的值,很好的解决了sql注入...
  • 为了做到这个功能,我们先要一步一步学习怎样配置IPsec VPN。 配置环境  我们在广州有一台FortiGate 200D防火墙,在深圳有一台FortiGate 500D防火墙。每台防火墙都有自己的内网及宽带。  ① 我...
  • 张天任代表:超前一步突破电动汽车电池技术.pdf
  • 一步一步教你如何在GitHub上上传自己的项目

    万次阅读 多人点赞 2018-07-04 09:23:40
    ",表示你对这次提交的注释,双引号里面的内容可以根据个人的需要 改。 这里如果出现以下内容,则需要你输入自己的账号或名字 用上面提示的代码输入自己的邮箱或名字 再输入 git commit -m " first commit "时就会...
  • js 上一步一步 操作

    千次阅读 2018-11-12 15:56:00
    &lt;a id="syb" href="#" style="...上一步&lt;/a&gt; &lt;a id="syt" href="#" style="display: none" class="btn button-prim
  • 从零开始带你一步一步使用YOLOv3训练自己的数据

    千次阅读 热门讨论 2021-05-31 18:40:42
    今天给大家介绍一下如何一步一步使用YOLOv3训练自己的数据集。 一、标注数据集 首先我们需要使用 labelimg 工具来标注图片数据集,例如图片是 .jpg 格式的,用矩形框标注图片中的目标位置,得到 .xml 文件。这里 ...
  • 一步一步打造自己的VIM

    千次阅读 2016-03-09 12:36:37
    现在(可能突然脑抽了)打算开始根据自己需要的东西,一步一步添加。装逼点的说法:程序员是一群喜欢把不能确定的东西变成自己能够确定掌握的东西的人。Step 1首先第一个需求是: [1] tab键为4个空格的长度,换行时...
  • 一步一步教你如何在AndroidStudio查看Android源码(AOSP源码)
  • 一步一步写ARM汇编(一)

    万次阅读 多人点赞 2018-06-05 21:18:48
    本博文开始学习一步一步写ARM汇编程序。 一、重要概念理解1. 立即数1)把数据转换成二进制形式,从低到高写成 4位1组的形式,最高位一组不够4位的前面补02)数1的个数,如果大于8个【可能也是立即数,取反】不是...
  • 一步一步做项目(20)详细设计

    千次阅读 2019-10-31 15:24:01
    一步一步做项目系列以软件项目开发为背景,讲述软件项目开发流程:需求、分析、设计、实现与测试,以SSH框架技术来开发软件原型,系统讲解JavaEE项目开发。本讲主要以managePublicNotice用例为例来说明如何建立设计...
  • 一步一步地安装Yeoman脚手架工具

    千次阅读 2015-09-06 17:59:55
    Yeoman包括了三个部分 yo(脚手架工具)、grunt(构建工具)、bower(包管理器) . 前提条件:安装NodeJs、Git Bash工具 安装yeoman前先配置package.json文件 ...执行完比后,会弹出下面一个页面表示安装并运行成功
  • 一步一步配置NLB(续)之深入测试

    千次阅读 2013-10-08 19:04:02
    其中比较关键的参数是筛选模式,可能看了后有些费解,尤其是第三类网络(类C),我是真心不知道是啥意思,有懂的朋友跟我分享一下。 今天,我们只关注前两个,None和Single,这两个好理解,None是服务器会把请求...
  • 鸡蛋掉落--一步一步的dp

    万次阅读 2020-04-11 10:41:28
    一步一步的dp分析 初读题目,读不懂,没关系,再读一遍,发现题目的要点: 需要从一栋楼中找到刚好会使鸡蛋破碎的楼层。 鸡蛋数量是指定的。 每次移动可以取一个鸡蛋。 最终需要求的并不是F,而是找到F的最少次数。 ...
  • 根据步骤可以写出主函数包括每一步的输入输出,怎么表示(基本的伪代码表示,当然如果可以也可以写成汉字形式的),最后一步一步写出代码,调试工作是必须的(建议:子函数尽量分开写,功能分明,便于调试)。...
  • 利用 Composer 一步一步构建自己的 PHP 框架(一)——基础准备 『Composer 一统天下的时代已经到来!』——白岩松 “一个时代结束了,另一个时代开始了。” Framework Interoperability Group(框架可互用...
  • 一步一步完成 MIT-6.824-Lab1 : MapReduce 之一 GitHub代码仓库:Mit-6.824-Lab1-MapReduce 回顾上一篇博文中提到了 MapReduce 论文, 本次的 MIT 的 Lab1-MapReduce, 可以根据论文中提到的完成一个 MapReduce 系统...
  • 一步一步学cscope

    万次阅读 2007-11-09 18:08:00
    如果你能通过搜索来到这里,证明,我就权当你已知道cscope是做什么用的。今天我不准备写这些东东,以后可能添加。 我的平台:Linux SUSE 10.1 硬件平台普通 1, cscope安装 软件下载:...
  • //因为歌单数据中的播放长度用ms表示的,所以这里 / 1000 playStatus.currentTotalTime = Math.floor(args.total / 1000); $('#audio source').attr('src',args.src); //切换音乐通过修改<source>中的src ...
  • # 看到下面的提示符代表成功, 以后运行 Jumpserver 都要先运行以上 source 命令, 以下所有命令均在该虚拟环境中运行 (py3) [root@localhost py3]
  • 一步一步 搞定RSA(公钥、私钥)

    千次阅读 2016-05-04 20:13:38
    1024:表示的是生成key的长度,单位字节(bits) 创建证书请求 $ openssl req -new -key private . pem -out rsacert . csr 可以拿着这个文件去数字证书颁发机构(即CA)申请一个数字证书。CA会给你一个...
  • JDK详细的一步一步的安装教程

    千次阅读 2020-05-09 21:59:12
    a、安装时默认点击下一步,但选择安装路径时,默认安装路径为 C:\Program Files\Java\jdk1.8.0_131\,建议在windows下选择一个单独的盘 用于安装开发软件,以后会很方便 路径修改为G:\Java\jdk1.8.0_131\ b、 下...
  • 一步一步教你理解和实现iOS中的链式编程和函数式编程
  • 一步一步学习KBEngine(三)

    千次阅读 2018-04-13 11:21:05
    接下来一步一步去体验吧。 注册 登录 进入游戏前创建角色,然后还要选择角色。 看到没,看到没,Song就是我创建的角色哦,O(∩_∩)O哈哈~ 话不多说,我要去体验一把了,(#^.^#)。 你也赶紧试试吧。 到此,第一个...
  • // 表示可以在测试文件中不需引入即可使用两个库的全局方法 frameworks: ['mocha', 'power-assert'], files: [ '../src/utils.js', './specs/utils.spec.js.js' ], exclude: [ ], preprocessors: { './...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 857,996
精华内容 343,198
关键字:

一步一步表示什么意思