精华内容
下载资源
问答
  • 空气循环

    千次阅读 2014-07-20 12:30:37
    循环扇和普通风扇相比,有什么好处?

    天气太热,今天刚买了个循环扇,用了一下效果还不错。

    空气循环扇的运作原理是什么?

    当启动空气循环机时,在循环机后方的空气会被吸入机内,并由内嵌式的导流体控制流动方向。这个锥形的导流体是一个空气的加速装置,可加快扇叶中心的速度,使吹送出来的空气更深入屋内。这个深度推进的风扇叶片,借着大面积设计将空气吹射出循环机。扇叶四周的输送管能避免空气从扇叶边缘散失和帮助风向前进。前方的风扇栅栏(AirTensity Grill)是一种气流挺进装置,可帮助集中外送的风束成为紧密的涡轮气流。这种高速的涡轮风柱在穿过屋内时能牵引更多的空气加入这个循环气流。当这个风柱吹送到房间的另一边时,周围的壁面会将它反射,最后回到循环扇周围再重新加强送出,如此持续不断地循环。这个过程造成整体室内空气循环,室内各个角落的空气都会以持续且安静的方式稳定地流动着。这种流动的空气会让您感到更舒适,并在整个屋内创造出更均匀怡人的温度。




    展开全文
  • 玩转Bash脚本:循环结构之while循环

    万次阅读 2015-01-31 16:27:56
    和其他语言一样Bash的循环结构中也有while语句。它的基本结构为 while 条件;do 循环体;done 和其他语言不同的是,bash脚本中的while条件很丰富,可以是方括号[]就像if那样的条件,也可以是终端的命令作为条件。bash...
    总第8篇
    本系列(玩转Bash脚本)更多文章,请访问:http://blog.csdn.net/column/details/wanbash.html


    和其他语言一样Bash的循环结构中也有while语句。

    基本结构

    while 条件
    do
        循环体
    done

    和for语句一样,它的循环体同样是do…done结构。我们可以把while语句再折叠一下

    while 条件;do
        循环体
    done

    还能进一步折叠成一行体

    while 条件;do 循环体;done

    Bash语句大都可以写作一行,只不过可读性差

    和其他编程语言不同的是,Bash中的while语句用法是比较多样的。可以根据while条件的不同,将while语句分为几类。

    while条件

    方括号[ ]

    和if语句的条件相同。即操作符[ ]。关于[ ]的用法请参考之前的文章。

    #!/bin/bash
    n=1
    while [ $n -le 10 ]
    do
        echo $n
        let n++    #或者写作n=$(( $n + 1 ))
    done

    在前面的文章中,已经讲过了操作符[ ]test语句是等价的,所以这里也可以用test语句来作条件。

    终端命令

    while的条件可以是各种终端的命令。包括外部命令或bash内建(built-in)命令都可以。因为命令都是有返回值的(可以用echo $?查看),命令执行的成功与否就是while条件的真或假。

    以read命令来举个例子

    #!/bin/bash
    while read var;do
        echo "您输入的是$var"
    done

    这个程序是个死循环,将不停地等待您的输入,并回显出来。

    这里的命令可以是单个命令也可以是组合命令,比如用逻辑连接符连接的命令,或者管道、重定向组成的长命令

    死循环

    除了让while条件恒成立外,编程语言都有一种简洁的死循环写法。比如C语言中典型的死循环条件是while(1),而java中的写法是while(true)。 
    而Bash中的写法则简单的多,只需要一个冒号。

    #!/bin/bash
    while :
    do
        echo I love you forever
    done

    这是一个死循环,执行之后请用按组合键Ctrl+C来终止它。 
    此外,还有一种死循环写法就是利用系统自带的true命令(/bin/true

    #!/bin/bash
    while /bin/true
    do
        echo I love you forever
    done

    由于我们的系统环境变量(PATH)中一般都包含了路径/bin,所有我们也可以简写成while true

    while实现菜单demo

    我们或许曾经用C/C++在控制台上输出过菜单。这通常是一个do-while循环实现的,先输出菜单的每个选项,然后等待输入, 
    根据不同的输入执行不同的操作,然后循环再次输出菜单……。 
    bash中没有do-while风格的循环,但是我们很容易用替代的方案实现该功能。用死循环+if/case条件判断语句就够了。

    #!/bin/bash
    #菜单demo
    while :
    do
        echo   #输出空行
        echo "========================="
        echo "      1:输出成绩单"
        echo "      2:输出课程表"
        echo "      3:输出空闲教室"
        echo "      q:退出菜单"
        echo "========================="
        read -p"请输入:" input
        case $input in
            1)echo "稍等,正在为您输出成绩单";;
            2)echo "稍等,正在为您输出课程表";;
            3)echo "稍等,正在为您输出空闲教";;
            q|Q) exit
        esac
    done

    while与重定向

    while语句可以联合重定向(>和<)一起使用。

    while和输入重定向<

    格式为

    while 命令
    do
        循环体
    done < 文件名

    相当于将文件内容逐行传递给while后面的命令(类似管道),然后再执行循环体。 
    循环体为空的时候,这个结构的功能通常和cat没有两样,比如:

    while grep "love"
    do
    done < letter

    从文件letter中查找love这个单词,其实和cat letter|grep “love”没什么两样。

    这个结构更习惯的用法和read联用,来将文件内容逐行取出,赋值给read后面的变量。比如:

    #!/bin/bash
    #从/etc/passwd文件中读取用户名并输出
    oldIFS=$IFS #IFS是文件内部分隔符
    IFS=":"     #设置分隔符为:
    while  read username var #var变量不可少
    do
        echo "用户名:$username"
    done < /etc/passwd 
    IFS=$oldIFS

    熟悉/etc/passwd文件结构的朋友都知道这个文件的每一行包含了一个用户的大量信息(用户名只是第一项)。 
    这里我们实际只输出了用户名。但是注意while的read后面除变量username外还有个var,尽管我们并不输出这个变量的值。 
    但它却必不可少,如果我们写成while read username那么username的值等于passwd文件这一整行的内容(IFS=”:”也就不起作用了)

    bash中,只有当有多个变量要从一行文本赋值的时候,才尝试去用IFS来分割,然后赋值。

    while和输出重定向>

    格式为

    while 命令
    do
        循环体
    done > 文件名

    这个结构会将命令的输出,以及循环体中的标准输出都重定向到指定的文件中。 
    比如:

    #!/bin/bash
    #每隔10分钟,ping一下局域网内主机192.168.1.101,
    #并把结果记录到ping.txt文件中
    while date
    do
        ping -c5 192.168.1.101 >/dev/null 2>&1 
        if [ $? = 0 ];then
            echo OK
        else
            echo FAIL
        fi
        sleep 600 #600秒是10分钟
    done > ping.txt

    我们来cat一下ping.txt文件,看一看:

    2015年 01月 31日 星期六 16:03:13 CST
    OK

    这是ping.txt中的一条记录

    本系列(玩转Bash脚本)更多文章,请访问:http://blog.csdn.net/column/details/wanbash.html

    展开全文
  • Java的循环语句

    千次阅读 2014-04-21 21:01:21
    Java 的循环语句有for,while 和 do-while 。这些语句创造了我们通常所称的循环(loops)。你可能知道,一个循环重复执行同一套指令直到一个结束条件出现。你将看到,Java 有适合任何编程所需要的循环结构。 ...

    Java的循环语句

    Java 的循环语句有for,while 和 do-while 。这些语句创造了我们通常所称的循环(loops)。你可能知道,一个循环重复执行同一套指令直到一个结束条件出现。你将看到,Java 有适合任何编程所需要的循环结构。

    1、 while 语句

    while 语句是Java 最基本的循环语句。当它的控制表达式是真时,while 语句重复执行一个语句或语句块。它的通用格式如下:
    while(condition) {
    // body of loop 

    条件condition 可以是任何布尔表达式。只要条件表达式为真,循环体就被执行。当条件condition 为假时,程序控制就传递到循环后面紧跟的语句行。如果只有单个语句需要重复,大括号是不必要的。
    下面的while 循环从10开始进行减计数,打印出10行“tick”。
    // Demonstrate the while loop.
    class While {
    public static void main(String args[]) {
    int n = 10; 
    while(n > 0) {
    System.out.println("tick " + n);
    n--; 
    }
    }
    }

    当你运行这个程序,它将“tick”10次:
    tick 10 
    tick 9 
    tick 8 
    tick 7 
    tick 6 
    tick 5 
    tick 4 
    tick 3 
    tick 2 
    tick 1 
    因为while 语句在循环一开始就计算条件表达式,若开始时条件为假,则循环体一次也不会执行。例如,下面的程序中,对println( ) 的调用从未被执行过:
    int a = 10, b = 20;
    while(a > b)
    System.out.println("This will not be displayed");
    while 循环(或Java 的其他任何循环)的循环体可以为空。这是因为一个空语句(null statement) (仅由一个分号组成的语句)在Java 的语法上是合法的。例如,下面的程序:
    // The target of a loop can be empty.
    class NoBody {
    public static void main(String args[]) {
    int i, j;
    i = 100; 
    j = 200; 
    // find midpoint between i and j
    while(++i < --j) ; // no body in this loop 
    System.out.println("Midpoint is " + i);
    }
    }
    该程序找出变量i和变量j的中间点。它产生的输出如下:
    Midpoint is 150
    该程序中的while 循环是这样执行的。值i自增,而值j自减,然后比较这两个值。如果新的值i仍比新的值j小,则进行循环。如果i等于或大于j,则循环停止。在退出循环前,i 将保存原始i和j的中间值(当然,这个程序只有在开始时i比j小的情况下才执行)。正如你看到的,这里不需要循环体。所有的行为都出现在条件表达式自身内部。在专业化的Java 代码中,一些可以由控制表达式本身处理的短循环通常都没有循环体。

    2、 do-while 循环

    如你刚才所见,如果while 循环一开始条件表达式就是假的,那么循环体就根本不被执行。然而,有时需要在开始时条件表达式即使是假的情况下,while 循环至少也要执行一次。换句话说,有时你需要在一次循环结束后再测试中止表达式,而不是在循环开始时。幸运的是,Java 就提供了这样的循环:do-while 循环。do-while 循环总是执行它的循环体至少一次,因为它的条件表达式在循环的结尾。它的通用格式如下:
    do {
    // body of loop
    } while (condition); 
    do-while 循环总是先执行循环体,然后再计算条件表达式。如果表达式为真,则循环继续。否则,循环结束。对所有的Java 循环都一样,条件condition 必须是一个布尔表达式。下面是一个重写的“tick”程序,用来演示do-while 循环。它的输出与先前程序的输出相同。
    // Demonstrate the do-while loop.
    class DoWhile {
    public static void main(String args[]) {
    int n = 10; 
    do {
    System.out.println("tick " + n);
    n--; 
    } while(n > 0);
    }
    }
    该程序中的循环虽然在技术上是正确的,但可以像如下这样编写更为高效:
    do {
    System.out.println("tick " + n); 
    } while(--n > 0); 
    在本例中,表达式“-- n > 0 ”将n值的递减与测试n是否为0组合在一个表达式中。它的执行过程是这样的。首先,执行-- n 语句,将变量n递减,然后返回n的新值。这个值再与0比较,如果比0大,则循环继续。否则结束。
    do-while 循环在你编制菜单选择时尤为有用,因为通常都想让菜单循环体至少执行一次。下面的程序是一个实现Java 选择和重复语句的很简单的帮助系统:
    // Using a do-while to process a menu selection
    class Menu {
    public static void main(String args[])
    throws java.io.IOException {
    char choice; 
    do {
    System.out.println("Help on:");
    System.out.println(" 1. if");
    System.out.println(" 2. switch");
    System.out.println(" 3. while");
    System.out.println(" 4. do-while");
    System.out.println(" 5. for\n");
    System.out.println("Choose one:");
    choice = (char) System.in.read();
    } while( choice < '1' || choice > '5');
    System.out.println("\n");
    switch(choice) {
    case '1': 
    System.out.println("The if:\n");
    System.out.println("if(condition) statement;");
    System.out.println("else statement;");
    break;
    case '2': 
    System.out.println("The switch:\n");
    System.out.println("switch(expression) {");
    System.out.println(" case constant:");
    System.out.println(" statement sequence");
    System.out.println(" break;");
    System.out.println(" // ...");
    System.out.println("}");
    break;
    case '3': 
    System.out.println("The while:\n");
    System.out.println("while(condition) statement;");
    break; 
    case '4': 
    System.out.println("The do-while:\n");
    System.out.println("do {"); 
    System.out.println(" statement;");
    System.out.println("} while (condition);");
    break; 
    case '5': 
    System.out.println("The for:\n");
    System.out.print("for(init; condition; iteration)");
    System.out.println(" statement;");
    break; 
    }
    }
    }
    下面是这个程序执行的一个样本输出:
    Help on:
    1. if 
    2. switch 
    3. while 
    4. do-while 
    5. for 
    Choose one: 

    The do-while: 
    do {
    statement;
    } while (condition); 
    在程序中,do-while 循环用来验证用户是否输入了有效的选择。如果没有,则要求用户重新输入。因为菜单至少要显示一次,do-while 循环是完成此任务的合适语句。
    关于此例的其他几点:注意从键盘输入字符通过调用System.in.read( ) 来读入。这是一个Java 的控制台输入函数。尽管Java 的终端 I/O (输入/输出)方法将在第12章中详细讨论,在这里使用System.in.read ( ) 来读入用户的选择。它从标准的输入读取字符(返回整数,因此将返回值choice 定义为字符型)。默认地,标准输入是按行进入缓冲区的,因此在你输入的任何字符被送到你的程序以前,必须按回车键。
    Java 的终端输入功能相当有限且不好使用。进一步说,大多数现实的Java 程序和Applets (小应用程序)都具有图形界面并且是基于窗口的。因此,这本书使用终端的输入并不多。然而,它在本例中是有用的。另外一点:因为使用System.in.read ( ) ,程序必须指定throws java.io.IOException 子句。这行代码对于处理输入错误是必要的。这是Java 的异常处理的一部分,将在第10章讨论。

    3、 for 循环

    在第2章曾使用过一个for循环的简单格式。你将看到,for循环是一个功能强大且形式灵活的结构。下面是for 循环的通用格式:
    for(initialization; condition; iteration) {
    // body
    } 
    如只有一条语句需要重复,大括号就没有必要。
    for循环的执行过程如下。第一步,当循环启动时,先执行其初始化部分。通常,这是设置循环控制变量值的一个表达式,作为控制循环的计数器。重要的是你要理解初始化表达式仅被执行一次。下一步,计算条件condition 的值。条件condition 必须是布尔表达式。它通常将循环控制变量与目标值相比较。如果这个表达式为真,则执行循环体;如果为假,则循环终止。再下一步执行循环体的反复部分。这部分通常是增加或减少循环控制变量的一个表达式。接下来重复循环,首先计算条件表达式的值,然后执行循环体,接着执行反复表达式。这个过程不断重复直到控制表达式变为假。
    下面是使用for 循环的“tick”程序:
    // Demonstrate the for loop.
    class ForTick {
    public static void main(String args[]) {
    int n; 
    for(n=10; n>0; n--)
    System.out.println("tick " + n);
    }
    }
    在for 循环中声明循环控制变量
    控制for循环的变量经常只是用于该循环,而不用在程序的其他地方。在这种情况下,可以在循环的初始化部分中声明变量。例如,下面重写了前面的程序,使变量 n 在for循环中被声明为整型:
    // Declare a loop control variable inside the for.
    class ForTick {
    public static void main(String args[]) { 
    // here, n is declared inside of the for loop
    for(int n=10; n>0; n--)
    System.out.println("tick " + n);
    }
    }
    当你在for 循环内声明变量时,必须记住重要的一点:该变量的作用域在for语句执行后就结束了(因此,该变量的作用域就局限于for 循环内)。在for循环外,变量就不存在了。如果你在程序的其他地方需要使用循环控制变量,你就不能在for循环中声明它。
    由于循环控制变量不会在程序的其他地方使用,大多数程序员都在for循环中来声明它。例如,以下为测试素数的一个简单程序。注意由于其他地方不需要i,所以循环控制变量i在for循环中声明。
    // Test for primes.
    class FindPrime {
    public static void main(String args[]) {
    int num;
    boolean isPrime = true; 
    num = 14; 
    for(int i=2; i <= num/2; i++) {
    if((num % i) == 0) {
    isPrime = false; 
    break;
    }
    }
    if(isPrime) System.out.println("Prime");
    else System.out.println("Not Prime");
    }
    }
    使用逗号
    你可能经常需要在初始化和for循环的反复部分包括超过一个变量的声明。例如,考虑下面程序的循环部分:
    class Sample {
    public static void main(String args[]) {
    int a, b;
    b = 4; 
    for(a=1; a<b; a++) {
    System.out.println("a = " + a);
    System.out.println("b = " + b);
    b--; 
    }
    }
    }
    如你所看到的,循环被两个相互作用的变量控制。由于循环被两个变量控制,如果两个变量都能被定义在for 循环中,而变量b不需要通过人工处理将是很有用的。幸好,Java 提供了一个完成此任务的方法。为了允许两个或两个以上的变量控制循环,Java 允许你在for 循环的初始化部分和反复部分声明多个变量,每个变量之间用逗号分开。
    使用逗号,前面的for循环将更高效,改写后的程序如下:
    // Using the comma.
    class Comma {
    public static void main(String args[]) {
    int a, b; 
    for(a=1, b=4; a<b; a++, b--) {System.out.println("a = " + a);System.out.println("b = " + b);
    }
    }
    }
    在本例中,初始化部分把两个变量a和 b都定义了。在循环的反复部分,用两个逗号分开的语句在每次循环重复时都执行。程序输出如下:
    a = 1 
    b = 4 
    a = 2 
    b = 3
    注意:如果你对C/C++ 熟悉,你就会知道逗号是一个运算符,能在任何有效的表达
    式中使用。然而,在Java 中不是这样。在 Java 中,逗号仅仅是一个分隔符,只
    适用于for循环。

    4、 for 循环的一些变化

    for循环支持一些变化,这增加了它的功能和灵活性。for循环这样灵活是因为它的3部分(初始化部分,条件测试部分和反复部分)并不仅用于它们所限定的那些目的。事实上,for 循环的3部分能被用于你需要的任何目的。让我们看一些例子。
    最普通的变化之一包含在条件表达式中。具体地说,条件表达式可以不需要用循环变量和目标值的比较来测试循环条件。事实上,控制for 循环的条件可以是任何布尔表达式。例如,考虑下列程序片段:
    boolean done = false;
    for(int i=1; !done; i++) {
    // ...
    if(interrupted()) done = true;
    }
    在本例中,for循环将一直运行,直到布尔型变量done 被设置为真。for循环的条件部分不测试值i。
    下面是for循环的另外一个有趣的变化。在Java 中可以使for循环的初始化、条件或者反复部分中的任何或者全部都为空,如下面的程序:
    // Parts of the for loop can be empty.
    class ForVar {
    public static void main(String args[]) {
    int i;
    boolean done = false;
    i = 0;
    for( ; !done; ) {
    System.out.println("i is " + i);
    if(i == 10) done = true;
    i++;}}
    }
    本例中,初始化部分和反复部分被移到了for 循环以外。这样,for循环的初始化部分和反复部分是空的。当这个简单的例子中,for循环中没有值,确实,这种风格被认为是相当差的,有时这种风格也是有用的。例如,如果初始条件由程序中其他部分的复杂表达式来定义,或者循环控制变量的改变由发生在循环体内的行为决定,而且这种改变是一种非顺序的方式,这种情况下,可以使for 循环的这些部分为空。
    下面是for 循环变化的又一种方式。如果for循环的三个部分全为空,你就可以创建一个无限循环(从来不停止的循环)。例如:
    for( ; ; ) {
    // ... 

    这个循环将始终运行,因为没有使它终止的条件。尽管有一些程序,例如操作系统命令处理器,需要无限循环,但大多数“无限循环”实际上是具有特殊终止要求的循环。在不久你将看到如何不用正常的条件表达式来终止这种类型的循环。
    5、 循环嵌套
    和其他编程语言一样,Java 允许循环嵌套。也就是,一个循环在另一个循环之内。例如,下面的程序就是循环嵌套:
    // Loops may be nested.
    class Nested {
    public static void main(String args[]) {
    int i, j; 
    for(i=0; i<10; i++) {
    for(j=i; j<10; j++)
    System.out.print(".");
    System.out.println();
    }
    }
    }
    该程序产生的输出如下所示:
    . . . . . . . . . . 
    . . . . . . . . . 
    . . . . . . . . 
    . . . . . . . 
    . . . . . . 
    . . . . . 
    . . . . 
    . . . 
    . . 


    转载自:http://dev.21tx.com/2005/03/24/12536.shtml

    展开全文
  • 能模仿韩寒小四写作的神奇循环神经网络

    万次阅读 多人点赞 2016-04-26 19:32:19
    记忆破蛹而出,翻滚着随摆动的兰花草,气味扑鼻,海潮依旧。 你们虔诚的看着远方,我抬起头,不经意间,目光划过你们的面庞,上面淡淡的倔强印,那么坚强 尘世凡间 沉睡亿万光年的年轻战士 萦绕不散的寂寞烟云中...

    作者:寒小阳 && 龙心尘
    时间:2016年4月
    出处:http://blog.csdn.net/han_xiaoyang/article/details/51253274
    声明:版权所有,转载请联系作者并注明出处
    特别鸣谢:北京大学焦剑博士对Recurrent Neural Networks Tutorial part1一文的翻译和部分内容提供

    1.引言

    在离人工智能越来越近的今天,研究界和工业界对神经网络和深度学习的兴趣也越来越浓,期待也越来越高。
    我们在深度学习与计算机视觉专栏中看过计算机通过卷积神经网络学会了识别图片的内容——模仿人类的看,而工业界大量的应用也证明了神经网络能让计算机学会听(比如百度的语音识别),于是大量的精力开始投向NLP领域,让计算机学会写也一定是非常有意思的事情,试想一下,如果计算机通过读韩寒和小四的小说,就能写出有一样的调调的文字,这是多带劲的一件事啊。
    你还别说,还真有这么一类神经网络,能够在NLP上发挥巨大的作用,处理从语言模型(language model)到双语翻译,到文本生成,甚至到代码风格模仿的问题。这就是我么今天要介绍的RNN(循环神经网络)。
    

    2.神奇的RNN,不可思议的表现

    RNN是一个和时间序列有关系的神经网络,大家想想,对单张图片而言,像素信息是静止的,而对于一段话而言,里面的词的组成是有先后的,而且通常情况下,后续的词和前面的词有顺序关联。这时候,独立层级结构的卷积神经网络通常就很难处理这种时序关联信息了,而RNN却能有效地进行处理。

    RNN在处理时间序列的内容有多有效呢,咱们列一些RNN处理的例子来一起看看。

    我们知道卷积神经网络在0-9数字识别上已经能有人类的识别精确度了,但是对于一连串的数字,还是得有一些特殊的处理的。比如说,下面是让RNN学会了从左到右读数字门牌号。


    RNN读门牌号

    我们也能让RNN学着从右到左自己绘出门牌号图片:



    RNN能模仿各种格式的文件,比如说下面是RNN学习出来的XML文件,和Latex代数几何文件样本:

    <page>
      <title>Antichrist</title>
      <id>865</id>
      <revision>
        <id>15900676</id>
        <timestamp>2002-08-03T18:14:12Z</timestamp>
        <contributor>
          <username>Paris</username>
          <id>23</id>
        </contributor>
        <minor />
        <comment>Automated conversion</comment>
        <text xml:space="preserve">#REDIRECT [[Christianity]]</text>
      </revision>
    </page>

    Latex
    Latex2
    我们暂时不管内容正确性,这个格式编排能力,已经让我瞠目结舌。

    爱折腾的程序猿GG们,当然会把握住一切可学习的资源,于是乎,他们把github上linux的源码拉下来了,然后喂给循环神经网络学习,两三天后…我们发现它能自己生成如下的代码块了

    /*
     * Increment the size file of the new incorrect UI_FILTER group information
     * of the size generatively.
     */
    static int indicate_policy(void)
    {
      int error;
      if (fd == MARN_EPT) {
        /*
         * The kernel blank will coeld it to userspace.
         */
        if (ss->segment < mem_total)
          unblock_graph_and_set_blocked();
        else
          ret = 1;
        goto bail;
      }
      segaddr = in_SB(in.addr);
      selector = seg / 16;
      setup_works = true;
      for (i = 0; i < blocks; i++) {
        seq = buf[i++];
        bpf = bd->bd.next + i * search;
        if (fd) {
          current = blocked;
        }
      }
      rw->name = "Getjbbregs";
      bprm_self_clearl(&iv->version);
      regs->new = blocks[(BPF_STATS << info->historidac)] | PFMR_CLOBATHINC_SECONDS << 12;
      return segtable;
    }

    且不说代码内容是否有意义,这标准的C代码风格,看完已有吓尿的趋势。

    哦,对,刚才我们提了RNN对文字的处理能力。我们来看看,Andrej Karpathy自己做了一个实验,把所有莎士比亚的作品串起来组成一个单一的(4.4MB)文件。训练了一个512个隐藏节点的3层RNN网络。训练网络几小时后,得到了如下样本结果:

    PANDARUS:
    Alas, I think he shall be come approached and the day
    When little srain would be attain'd into being never fed,
    And who is but a chain and subjects of his death,
    I should not sleep.
    
    Second Senator:
    They are away this miseries, produced upon my soul,
    Breaking and strongly should be buried, when I perish
    The earth and thoughts of many states.
    
    DUKE VINCENTIO:
    Well, your wit is in the care of side and that.
    
    Second Lord:
    They would be ruled after this chamber, and
    my fair nues begun out of the fact, to be conveyed,
    Whose noble souls I'll have the heart of the wars.
    
    Clown:
    Come, sir, I will make did behold your worship.
    
    VIOLA:
    I'll drink it.

    恩,作为一个文学功底弱哭的码农,莎士比亚这样的大文豪,文笔风格我是读不出来的。但是我们有韩寒啊,我们有广大女中学生稀饭的四娘啊,于是我们去github上扒了一份能处理中文的char-rnn代码下来。把四娘的大作集合,从《幻城》到《左手倒影右手年华》,再到《悲伤逆流成何》和《小时代》,处理了处理,放在一起(咳咳,我只是拿来做实验学习的,不传播…版权问题表找博主…),丢给RNN学习了,然后,恩,我们看看它能学到什么。

    每个人,闭上眼睛的时候,才能真正面对光明
    
    他们在吱呀作响的船舷上,静静看着世界,没有痛苦的声音,碎裂的海洋里摇晃出阵阵沉默,吞噬过来。他们的躯体,一点,一点,逐渐暗淡在巨浪中,下沉。温润、潮湿、环抱,他手中兰花草磨成的微笑已经腐烂。记忆破蛹而出,翻滚着随风摆动的兰花草,气味扑鼻,海潮依旧。
    
    你们虔诚的看着远方,我抬起头,不经意间,目光划过你们的面庞,上面淡淡的倔强印,那么坚强
    
    尘世凡间
    沉睡亿万光年的年轻战士
    萦绕不散的寂寞烟云中
    静候在末世岛屿之上
    守候,女王何时归来
    你的目光延向她迟归的方向
    缓缓推进的海浪
    这最后一夜
    荡漾

    小四的文字我是看不懂的,不过RNN生成的这些文字,却让我也隐约感受到了一些“45度角仰望天空,眼泪才不会掉下来”的气息,当然,这几段已是输出结果中比较好的内容,也有一些通畅程度一般的,但足以证明循环神经网络在NLP上有不可思议的学习能力。

    3.循环神经网络浅解

    刚才被循环经网络惊呆的小伙伴们,咱们把目光先从小四那里收回来。看看这神奇的循环神经网络到底是何许神物,背后的原理又是什么样的。

    传统的神经网络(包括CNN)中我们假定所有输入和输出都是相互独立的。很遗憾,对于许多任务而言,这一个假设是不成立的。如果你想预测下一个单词或一个句子,你就需要知道前面的词。循环神经网络之所以被称作循环,是因为每个元素都执行相同的任务序列,但输出依赖之前的计算结果。我们换一种方法说明一下RNN:大家可以想象RNN模型是有“记忆”的,“记忆”中存储着之前已经计算的信息。理论上说RNN可以记得并使用任意长度的序列中的信息,不过实际应用中,我们通常只回溯几个步骤(后面详细讨论)。我们先一起来看一个典型的RNN的图例:
    RNN图例

    上图显示了将RNN展开成一个完整的网络的过程。之所以我们用展开这个字眼,因为循环神经网络每一层(或者我们叫做每个时间点t)到下一层(下一个时间点),其变换的过程(权重W)是一致的。举个例子说,如果我们关注的序列是包含5个单词的句子,循环神经网络将会依据先后顺序展开成五层神经网络,每个单词为一层。RNN是根据下面的步骤来迭代和做计算的:

    • 假定xt是时间t处的输入,比如x1可以是句子中第二个单词one-hot编码后的词向量。

    • 假定st是次数t的隐藏状态,哈哈,这就是我们提到的神经网络的“记忆大脑”。st由当前步骤的输入和先前隐藏状态共同计算求得:st=f(Uxt+Wst1)。这个计算函数f通常是非线性的,比如说神经网络里经常用到的tanhReLU. s1是第一个隐藏状态,通常我们把它初始化为0。

    • ot是第t步的输出。举个例子说,如果我们要预测在之前的词序列下,下一个词是什么,那我们可能拿到的是整个词表中,每个词的概率(通过softmax得到的),即ot=softmax(Vst)

    针对上面的步骤,咱们再细提几个点:

    • 我们就可以直接把隐状态st想作神经网络的“记忆体”,st捕捉和保留了之前时间点上的一些信息。输出ot是由当前时间点t上的“所有记忆信息”来计算得到的。实际工程应用中,通常我们会做一些处理,因为实际上st并不能捕捉和保留之前的所有时间点的信息。

    • 不像普通的深度神经网络,每一层和每一层之间都会有不同的权重参数,一个RNN神经网络共享同一组参数(U,V,W)(如图中所示),每一步都一样。这实际上表示我们在每一步到下一步都是做的同样的操作,只是输入不同。这样的一个好处是,极大地减小了需要训练和预估的参数量。

    • 上面的图表在每一步都有输出/output,但是根据任务不同,有时候并没有必要在每一步都有输出。比如说我们对句子的情感做判定的时候,我们其实只关心最后的情感判定结果,而不是中间每个词的结果。RNN最主要的特征是隐状态,因为它是“记忆体”,保留了之前的绝大多数信息。

    4.不同类型的RNN

    刚才我们提到的RNN只是最原始版本的RNN,而近几年随着研究者的深入,提出了一系列新的RNN网络类型。比如:

    4.1 双向RNN

    双向RNN的思想和原始版RNN有一些许不同,只要是它考虑到当前的输出不止和之前的序列元素有关系,还和之后的序列元素也是有关系的。举个例子来说,如果我们现在要去填一句话中空缺的词,那我们直观就会觉得这个空缺的位置填什么词其实和前后的内容都有关系,对吧。双向RNN其实也非常简单,我们直观理解一下,其实就可以把它们看做2个RNN叠加。输出的结果这个 时候就是基于2个RNN的隐状态计算得到的。


    双向RNN

    4.2 深层双向RNN

    深层双向RNN和双向RNN比较类似,区别只是每一步/每个时间点我们设定多层结构。实际应用的时候,这种方式可以让我们的神经网络有更大的容量(但这也意味着我们需要更多的训练数据)



    4.3 LSTM神经网络

    LSTM(Long Short Term Memory)可能是目前最流行,你见到各种paper提到最多的RNN神经网络了。LSTM和基线RNN并没有特别大的结构不同,但是它们用了不同的函数来计算隐状态。LSTM的“记忆”我们叫做细胞/cells,你可以直接把它们想做黑盒,这个黑盒的输入为前状态ht1和当前输入xt。这些“细胞”会决定哪些之前的信息和状态需要保留/记住,而哪些要被抹去。实际的应用中发现,这种方式可以有效地保存很长时间之前的关联信息。关于LSTM的细节,我们会在之后的博文里再和大家继续提到。

    5.动手实现简单的RNN

    下面我们用python和Theano库实现一个简单的RNN,并解决一类最常见但有有用的NLP问题,语言模型。

    5.1 语言模型简介

    语言模型是NLP中最常见和通用的一个概念。它能帮助我们判定一个短语串或者一句话出现的概率(在给定一个语料集后)。这在各种打分机制中非常非常有用,比如翻译系统中翻译结果的选定,比如语音识别中文字串的选定。关于语言模型具体的内容可以参见我们之前的博文从朴素贝叶斯到N-gram语言模型

    我们这里的目标就是要用RNN构建语言模型,即输出短语串/一句话的概率

    P(w1,...,wm)=i=1mP(wiw1,...,wi1)

    上式中,wi依赖于前序的i1个词,理论上说,RNN能够捕捉和记忆下所有的前序的信息和依赖关系,但实际上的情况会稍微复杂一些,时间/序列越长,需要记住的信息越多,但是损失的信息也可能越多。

    5.2 数据与预处理

    我们需要一些文本语料去让RNN学习,所幸的是,并不像有监督学习中,每个样本都需要标注一个结果,这里只要句子或者短语串就可以。因此我们从reddit(类似百度贴吧)中抓了15000条长评论下来。我们希望RNN能学到这些评论者的评论风格(就像神奇的帝吧),并能模仿他们生成一些话。但是我们都知道,机器学习问题,效果的上限其实取决于你的数据,所以我们都需要对数据先做一些预处理。

    5.2.1 分词与处理

    最初的语料文本需要一些简单的处理才能交给机器学习算法学习,比如句子最常见的处理是分词,对于英语而言,有纯天然的空格分隔开,对中文而言,我们需要自己去做断句和分词。比如英文的句子 “He left!”可以分词成3部分: “He”, “left”, “!”。这里使用python的NLTK包做分词处理。

    5.2.2 去掉低频/停用词

    其实绝大多数词在我们的文本中就只会出现一两次,而这些极度不常出现的词语,其实去掉就行了,对最后的结果反倒有帮助。太多的词语,一方面让模型的训练变得很慢,同时因为它们只出现一两次,所以我们从语料中也学不出太多和它们相关的知识。因此我们有很简单的处理方法来处理它们。

    比如说,我们就限定我们的语料库是8000词的,这8000词以外的词语我们都用UNKNOWN_TOKEN这个特殊字符来标注。比如单词“nonlinearities”不在我们的8000词汇集中,那“nonlineraties are important in neural networks”这句话就要被我们处理成“UNKNOWN_TOKEN are important in Neural Networks”了。我们完全可以把UNKNOWN_TOKEN视作一个新的词汇,而对于它的预测也和其他词完全一样。

    5.2.3 开头与结尾

    分词后,一句话变成一个词/短语 串,我们再做一些特殊的处理,比如把SENTENCE_START和SENTENCE_END添加到词/短语 串首位当做特殊的开始于结束标志。这样句子第一个词的预测可以看做出现SENTENCE_START后,出现第一个词的概率。

    5.2.4 词向量映射

    对于词向量的编码,我们打算用最简单的词序列编码。解释一下,大概是这样。一句简单的“I left home”的输入x可能被译作[0, 179, 341, 416], 其中0对应句子起始符SENTENCE_START,而I在词表中的下标为179,而输出y会被译作[179, 341, 416, 1]。这个可理解吧,因为我们在做的事情,是由前面的词语,推断后面的词语。

    代码大概是下面这个样子:

    vocabulary_size = 8000
    unknown_token = "UNKNOWN_TOKEN"
    sentence_start_token = "SENTENCE_START"
    sentence_end_token = "SENTENCE_END"
    
    # 读取数据,添加SENTENCE_START和SENTENCE_END在开头和结尾
    print "Reading CSV file..."
    with open('data/reddit-comments-2015-08.csv', 'rb') as f:
        reader = csv.reader(f, skipinitialspace=True)
        reader.next()
        # 分句
        sentences = itertools.chain(*[nltk.sent_tokenize(x[0].decode('utf-8').lower()) for x in reader])
        # 添加SENTENCE_START和SENTENCE_END
        sentences = ["%s %s %s" % (sentence_start_token, x, sentence_end_token) for x in sentences]
    print "Parsed %d sentences." % (len(sentences))
    
    # 分词
    tokenized_sentences = [nltk.word_tokenize(sent) for sent in sentences]
    
    # 统计词频
    word_freq = nltk.FreqDist(itertools.chain(*tokenized_sentences))
    print "Found %d unique words tokens." % len(word_freq.items())
    
    # 取出高频词构建词到位置,和位置到词的索引
    vocab = word_freq.most_common(vocabulary_size-1)
    index_to_word = [x[0] for x in vocab]
    index_to_word.append(unknown_token)
    word_to_index = dict([(w,i) for i,w in enumerate(index_to_word)])
    
    print "Using vocabulary size %d." % vocabulary_size
    print "The least frequent word in our vocabulary is '%s' and appeared %d times." % (vocab[-1][0], vocab[-1][1])
    
    # 把所有词表外的词都标记为unknown_token
    for i, sent in enumerate(tokenized_sentences):
        tokenized_sentences[i] = [w if w in word_to_index else unknown_token for w in sent]
    
    print "\nExample sentence: '%s'" % sentences[0]
    print "\nExample sentence after Pre-processing: '%s'" % tokenized_sentences[0]
    
    # 构建完整训练集
    X_train = np.asarray([[word_to_index[w] for w in sent[:-1]] for sent in tokenized_sentences])
    y_train = np.asarray([[word_to_index[w] for w in sent[1:]] for sent in tokenized_sentences])

    下面是一个实际的句子在这8000词汇的语料库中,映射成的X和Y,
    x:
    SENTENCE_START what are n’t you understanding about this ? !
    [0, 51, 27, 16, 10, 856, 53, 25, 34, 69]

    y:
    what are n’t you understanding about this ? ! SENTENCE_END
    [51, 27, 16, 10, 856, 53, 25, 34, 69, 1]

    5.2.5 手把手构建RNN

    我们还记得RNN的结构是下面这样的:
    RNN
    我们先来做一些数据设定和构建。
    假设输入x为词序列(就像前面提到的一样),其中每个xt都是一个独立的词。但是需要多说一句的是,因为我们这里要做矩阵运算,而词和词之间其实是没有数值大小差异的(虽然understanding编码为856,what为51,但是不代表understanding和what间有大小关系),所以我们这里还需要做一个简单的处理,即根据词表大小(8000)对词做一个one-hot编码。也就是说,每个词都是一个8000*1的向量,其中只有一个位置(这个词对应的位置)为1,其余位置的值都是0。 输出的o也是类似的表示形式,每一个ot都是一个词表大小长度的向量,每个元素代表预测这个位置的词是下一个词的概率。

    然后我们复习一下之前说到的RNN前向计算公式:

    stot=tanh(Uxt+Wst1)=softmax(Vst)

    咳咳,咱们是严谨派,再确认下各种输入输出的维度好了。我们假设词表大小C = 8000,而隐状态向量维度H = 100。这里需要提一下的是,我们之前也说到了,这个隐状态有点类似人类的“记忆体”,之前的数据信息都存储在这里,其实把它的维度设高一些可以存储更多的内容,但随之而来的是更大的计算量,所以这是一个需要权衡的问题。所以我们有下面一些设定:

    xtotstUVW80008000100100×80008000×100100×100

    对了,其中U,V和W是RNN神经网络的参数,也就是我们要通过对语料学习而得到的参数。所以,总的说下来,我们需要2HC+H2个参数,在我们现在的场景下,C=8000,H=100,因此我们大概有1610000个参数需要预估。同时,参数的大小,其实也给我一些提示,比如可能需要多少计算资源,大家可以做到提前心里有个数。我们这里大部分计算其实还好,因为one-hot编码后的矩阵,和其他矩阵相乘,其实可以看做挑选出其中的一列(行),最大的计算量在Vst处。这也就是说,如果计算资源有限,咱们最好把词表的规模限制一下,不要太大。

    初始化参数
    我们定义一个类,叫做RNNNumpy,初始化U,V和W还是有一些讲究的,我们不能直接把它们都初始化为0,这样在计算过程中会出现“对称化”问题。初始化的不同对于最后的训练结果是有不同的影响的,在这个问题上有很多的paper做了相关的研究,其实参数初始值的选定和咱们选定的激励函数是有关系的,比如我们这里选tanh,而相关的论文推荐我们使用[1n,1n]之间的随机数作为初始值,其中n是和前一层的连接数。额,看起来有点复杂哈,不过别担心,通常说来,你只要初始化参数为很小的随机数,神经网络就能正常训练。

    以下是对应的python代码,其中word_dim是词表大小,hidden_dim是隐层大小,bptt_truncate大家先不用管,我们一会儿会介绍到。

    class RNNNumpy:
    
        def __init__(self, word_dim, hidden_dim=100, bptt_truncate=4):
            # 给词表大小,隐状态维度等赋值
            self.word_dim = word_dim
            self.hidden_dim = hidden_dim
            self.bptt_truncate = bptt_truncate
            # 随机初始化参数U,V,W
            self.U = np.random.uniform(-np.sqrt(1./word_dim), np.sqrt(1./word_dim), (hidden_dim, word_dim))
            self.V = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (word_dim, hidden_dim))
            self.W = np.random.uniform(-np.sqrt(1./hidden_dim), np.sqrt(1./hidden_dim), (hidden_dim, hidden_dim))

    前向计算
    这个和卷积神经网络的前向运算完全一样,就是一个根据权重计算下一层/下一个时间点的输出、隐状态的过程。简单的实现如下:

    def forward_propagation(self, x):
        # 总共的 序列/时间 长度
        T = len(x)
        # 在前向计算时,我们保留所有的时间隐状态s,因为之后会用到
        # 初始的隐状态设为 0
        s = np.zeros((T + 1, self.hidden_dim))
        s[-1] = np.zeros(self.hidden_dim)
        # 在前向计算时,我们也保留所有的时间点输出o,之后也会用到
        o = np.zeros((T, self.word_dim))
        # 每个时间/序列点 进行运算
        for t in np.arange(T):
            # one-hot编码矩阵乘法,可以视作列选择
            s[t] = np.tanh(self.U[:,x[t]] + self.W.dot(s[t-1]))
            o[t] = softmax(self.V.dot(s[t]))
        return [o, s]
    
    RNNNumpy.forward_propagation = forward_propagation
    

    从上面的代码我们也可以看到,我们不仅计算了输出o,同时我们也计算了隐状态s。我们之后得用它们去计算梯度,再重复一下哈,这里的每个ot都是一个概率向量,表示每个词输出的概率。对了,其实在我们的场景下,我们只想知道下一个词是什么,对它的概率不那么关心,那就取最大概率那个词就行了,恩,我们定义了一个函数predict来实现这个功能:

    def predict(self, x):
        # 做前向计算,取概率最高的词下标
        o, s = self.forward_propagation(x)
        return np.argmax(o, axis=1)
    
    RNNNumpy.predict = predict

    来来来,试跑一下:

    np.random.seed(10)
    model = RNNNumpy(vocabulary_size)
    o, s = model.forward_propagation(X_train[10])
    print o.shape
    print o
    (45, 8000)
    [[ 0.00012408  0.0001244   0.00012603 ...,  0.00012515  0.00012488
       0.00012508]
     [ 0.00012536  0.00012582  0.00012436 ...,  0.00012482  0.00012456
       0.00012451]
     [ 0.00012387  0.0001252   0.00012474 ...,  0.00012559  0.00012588
       0.00012551]
     ..., 
     [ 0.00012414  0.00012455  0.0001252  ...,  0.00012487  0.00012494
       0.0001263 ]
     [ 0.0001252   0.00012393  0.00012509 ...,  0.00012407  0.00012578
       0.00012502]
     [ 0.00012472  0.0001253   0.00012487 ...,  0.00012463  0.00012536
       0.00012665]]

    对于句子(这里长度为45)中的每个词, 我们的模型得到了8000个概率(对应词表中的词)。额,我们这里只是验证一下前向运算是否能正常进行,但是参数U,V,W是随机取的,未训练,因此得到的结果其实是随机的。
    然后验证下predict函数:

    predictions = model.predict(X_train[10])
    print predictions.shape
    print predictions
    (45,)
    [1284 5221 7653 7430 1013 3562 7366 4860 2212 6601 7299 4556 2481 238 2539
     21 6548 261 1780 2005 1810 5376 4146 477 7051 4832 4991 897 3485 21
     7291 2007 6006 760 4864 2182 6569 2800 2752 6821 4437 7021 7875 6912 3575]

    计算损失函数
    和其他机器学习算法一样一样的是,我们需要一个损失函数/loss function来表征现在的预测结果和真实结果差别有多大,也便于我们之后的迭代和训练。一个比较通用的损失函数叫做互熵损失,有兴趣仔细了解的同学,可以看看我们之前的博客线性SVM与SoftMax分类器,如果我们有N个训练样本(这里显然是句子中词的个数咯),C个类别(这里显然是词表大小咯),那我们可以这样去定义损失函数描述预测结果o和实际结果y之间的差距:

    L(y,o)=1NnNynlogon

    公式看起来略吓人,但其实它做的事情就是把每个样本的损失加一加,然后输出,我们定义了一个calculate_loss函数:

    def calculate_total_loss(self, x, y):
        L = 0
        # 对于每句话
        for i in np.arange(len(y)):
            o, s = self.forward_propagation(x[i])
            # 我们只在乎预测对的那个词的概率
            correct_word_predictions = o[np.arange(len(y[i])), y[i]]
            # 加到总loss里
            L += -1 * np.sum(np.log(correct_word_predictions))
        return L
    
    def calculate_loss(self, x, y):
        # 除以总样本数
        N = np.sum((len(y_i) for y_i in y))
        return self.calculate_total_loss(x,y)/N
    
    RNNNumpy.calculate_total_loss = calculate_total_loss
    RNNNumpy.calculate_loss = calculate_loss

    用随机梯度下降和时间反向传播训练RNN
    现在损失函数也有咯,我们要做的事情就是最小化这个损失函数,以让我们的预测结果和真实的结果最接近咯。最常用的优化算法叫做SGD(随机梯度下降),大家可以理解成我们要从一座山上下山,于是我们每到一个位置,都环顾一下四周,看看最陡(下山最快)的方向是什么,然后顺着它走走,“随机”的意思是我们其实不用全部的样本去计算这个方向,而是用部分样本(有时候是一个)来计算。
    计算这个方向就是我们计算梯度的过程,从数学上来说,其实就是在给定损失函数L之后,偏导向量的方向,这里因为有U,V,W三个参数,所以其实我们是要求LU,LV,LW
    细致的内容我们之后会再提到,这里呢,简单地告诉大家,我们需要一个反向传播(时间轴上的)来用求导链式法则计算梯度。代码如下:

    def bptt(self, x, y):
        T = len(y)
        # 反向计算
        o, s = self.forward_propagation(x)
        # We accumulate the gradients in these variables
        dLdU = np.zeros(self.U.shape)
        dLdV = np.zeros(self.V.shape)
        dLdW = np.zeros(self.W.shape)
        delta_o = o
        delta_o[np.arange(len(y)), y] -= 1.
        # For each output backwards...
        for t in np.arange(T)[::-1]:
            dLdV += np.outer(delta_o[t], s[t].T)
            # Initial delta calculation
            delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
            # Backpropagation through time (for at most self.bptt_truncate steps)
            for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:
                # print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)
                dLdW += np.outer(delta_t, s[bptt_step-1])              
                dLdU[:,x[bptt_step]] += delta_t
                # 每一步更新delta
                delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
        return [dLdU, dLdV, dLdW]
    
    RNNNumpy.bptt = bptt

    恩,然后我们需要根据梯度来迭代和更新参数,也就是要顺着坡度方向下山咯。

    # Outer SGD Loop
    # - model: The RNN model instance
    # - X_train: The training data set
    # - y_train: The training data labels
    # - learning_rate: Initial learning rate for SGD
    # - nepoch: Number of times to iterate through the complete dataset
    # - evaluate_loss_after: Evaluate the loss after this many epochs
    def train_with_sgd(model, X_train, y_train, learning_rate=0.005, nepoch=100, evaluate_loss_after=5):
        # We keep track of the losses so we can plot them later
        losses = []
        num_examples_seen = 0
        for epoch in range(nepoch):
            # Optionally evaluate the loss
            if (epoch % evaluate_loss_after == 0):
                loss = model.calculate_loss(X_train, y_train)
                losses.append((num_examples_seen, loss))
                time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                print "%s: Loss after num_examples_seen=%d epoch=%d: %f" % (time, num_examples_seen, epoch, loss)
                # Adjust the learning rate if loss increases
                if (len(losses) &gt; 1 and losses[-1][1] &gt; losses[-2][1]):
                    learning_rate = learning_rate * 0.5 
                    print "Setting learning rate to %f" % learning_rate
                sys.stdout.flush()
            # For each training example...
            for i in range(len(y_train)):
                # One SGD step
                model.sgd_step(X_train[i], y_train[i], learning_rate)
                num_examples_seen += 1

    那个,那个,我们得试验一下,是不是这玩意确实能让我们下山。恩,也就是迈着步子的过程,你得看看,我们的海拔是不是每次都降了一些。我们这个地方也来检查一下:

    np.random.seed(10)
    # 在小样本集上试试
    model = RNNNumpy(vocabulary_size)
    losses = train_with_sgd(model, X_train[:100], y_train[:100], nepoch=10, evaluate_loss_after=1)
    2015-09-30 10:08:19: Loss after num_examples_seen=0 epoch=0: 8.987425
    2015-09-30 10:08:35: Loss after num_examples_seen=100 epoch=1: 8.976270
    2015-09-30 10:08:50: Loss after num_examples_seen=200 epoch=2: 8.960212
    2015-09-30 10:09:06: Loss after num_examples_seen=300 epoch=3: 8.930430
    2015-09-30 10:09:22: Loss after num_examples_seen=400 epoch=4: 8.862264
    2015-09-30 10:09:38: Loss after num_examples_seen=500 epoch=5: 6.913570
    2015-09-30 10:09:53: Loss after num_examples_seen=600 epoch=6: 6.302493
    2015-09-30 10:10:07: Loss after num_examples_seen=700 epoch=7: 6.014995
    2015-09-30 10:10:24: Loss after num_examples_seen=800 epoch=8: 5.833877
    2015-09-30 10:10:39: Loss after num_examples_seen=900 epoch=9: 5.710718

    恩,还凑合,看样子确实在带着我们迈向成功,O(∩_∩)O~
    恩,上面就是一个完整的简易RNN实现,至少,是可用的。

    不过尴尬的是。。。同学们有木有发现,在CPU上,这样的每一轮迭代,都要花费160ms左右的时间。这蛋碎的速度,加上RNN巨大的训练轮数,要把小四的作品都学完,咳咳,估计人家下几本书都出了。于是只好祭出大杀器——GPU,配合某python深度学习库Theano来加速了:

    from utils import load_model_parameters_theano, save_model_parameters_theano
    
    model = RNNTheano(vocabulary_size, hidden_dim=50)
    losses = train_with_sgd(model, X_train, y_train, nepoch=50)
    save_model_parameters_theano('./data/trained-model-theano.npz', model)
    load_model_parameters_theano('./data/trained-model-theano.npz', model)

    这就是用开源库的好处,纯手撸的N行代码,在这里调了几个函数就Done了…恩,感谢开源精神…

    产出文本
    有模型之后,我们就可以试着去生成概率最高的文本了。

    def generate_sentence(model):
        # 从 start token/开始符 开始一句话
        new_sentence = [word_to_index[sentence_start_token]]
        # 直到拿到一个 end token/结束符
        while not new_sentence[-1] == word_to_index[sentence_end_token]:
            next_word_probs = model.forward_propagation(new_sentence)
            sampled_word = word_to_index[unknown_token]
            while sampled_word == word_to_index[unknown_token]:
                samples = np.random.multinomial(1, next_word_probs[-1])
                sampled_word = np.argmax(samples)
            new_sentence.append(sampled_word)
        sentence_str = [index_to_word[x] for x in new_sentence[1:-1]]
        return sentence_str
    
    num_sentences = 10
    senten_min_length = 7
    
    for i in range(num_sentences):
        sent = []
        # 短句就不要咯,留下长的就好
        while len(sent) &lt; senten_min_length:
            sent = generate_sentence(model)
        print " ".join(sent)

    然后你就发现,上面的模型能模仿reddit中的评论,去生成一些句子了。比如:

    • Anyway, to the city scene you’re an idiot teenager.
    • What ? ! ! ! ! ignore!
    • Screw fitness, you’re saying: https
    • Thanks for the advice to keep my thoughts around girls :)
    • Yep, please disappear with the terrible generation.

    居然连标点和有些标点组成的表情都模仿出来了,还是灰常神奇的,当然,基线版的RNN也产出了非常非常多的不顺畅句子,这个和我们的语料量,以及中间隐状态的维度有关系。但另外一点是,这种方式的RNN,“记忆体”并不是最合适的,比如最近很火的LSTM和GRU都能更好地处理历史信息的记忆和遗忘过程。这个我们会在之后的博客里面提到。

    6.总结

    以上就是这篇RNN简介的所有内容,RNN确实是一个非常神奇的神经网络,而在对时间序列信息(比如NLP自然语言处理)上也有着独到的优势。更多的RNN与NLP处理的知识(比如LSTM)我们在之后的博客里会提到,现在的RNN能基于大量的语料学习写一些简单的句子,相信有一天,计算机也能写出大段的优美诗歌,赏心悦目的文字,长篇情节跌宕起伏的小说。

    参考文献:

    1、Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs
    2、Recurrent Neural Networks Tutorial, Part 2 – Implementing a RNN with Python, Numpy and Theano
    3、The Unreasonable Effectiveness of Recurrent Neural Networks
    4、char-rnn-chinese

    展开全文
  • Linux基础------Shell中的循环

    千次阅读 2014-03-23 22:47:08
    所谓固定循环和不定循环的定义是指在循环之前有没有定义好循环的次数。 例如:for循环,给定循环最大的次数,这就是固定循环。例如:while循环,只有满足指定的条件,循环才继续,它没有预先定义好的次数。  shell...
  • LSTM和循环网络RNN学习简记

    千次阅读 2017-07-29 11:24:39
    前馈网络回顾要理解循环网络,首先需要了解前馈网络的基础知识。这两种网络的名字都来自于它们通过一系列网络节点数学运算来传递信息的方式。前馈网络将信息径直向前递送(从不返回已经过的节点),而循环网络则将...
  • for循环中抛出异常_不要抛出循环

    千次阅读 2020-06-28 05:13:58
    原因很简单:除了val使得对程序的线程安全性的推理更容易,这是Scala的基本主题,开发人员几乎不需要像我们认为的那样需要可变状态。 从不可变的字段和局部变量( val )开始,就是一种证明这一点的方法,甚至向...
  • 温控风扇

    千次阅读 多人点赞 2018-12-26 21:24:46
    while(1) //进入while循环 { if(js>=50) //当js在定时器里加到50次时(js加一次是20ms,加到50次就是1000ms,也就是1秒读取一次温度) { ReadTemperature(); //读取温度值 js=0; //定时读取温度...
  • Win32消息循环机制等【转载】

    千次阅读 2015-10-31 00:29:49
    Dos的过程驱动与Windows的事件驱动在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别:DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个...
  • 原文出处:...到底是在循环内部还是外部进行变量声明比较好呢?针对这个问题,我最近找了很多有关这方面的资料。我发现大多数资料都给出了一大堆的讨论,但是
  • 是非可视控件,类似于编程语言里的For循环,可在执行期复制其它组件。的卷标要自己输入,而 dataProvider可在区段之中,以[Bindable]的Metadata定义之数组作连结。 REPEATER还可嵌套使用。  <![CDATA[ ...
  • 基于循环神经网络(RNN)的古诗生成器 之前在手机百度上看到有个“为你写诗”功能,能够随机生成古诗,当时感觉很酷炫= = 在学习了深度学习后,了解了一下原理,打算自己做个实现练练手,于是,就有了这个项目。文中...
  • Google C++ 风格指南内容整理

    千次阅读 2015-05-17 16:43:35
    之前一直没有全面的看过Google C++风格指南,现在很多公司进行C++开发都要求按照Google C++风格。在这个网站 http://zh-google-styleguide.readthedocs.org/en/latest/contents/  有人已经把其翻译成中文。为了...
  • Nature综述:微生物构成的氮循环网络(必读)

    千次阅读 多人点赞 2019-04-21 16:07:05
    文章目录微生物构成的氮循环网络大纲摘要亮点名词解析背景和内容概述Box 1 | 氮全球生化循环圈:全球的氮库存、氮转化过程、氮通量图1 | 微生物转化氮化合物图2 | 催化氮循环中关键的四种反应的酶氮转化反应固氮作用...
  • 包括for循环和while循环来多次执行任务,if和switch语句根据不同的条件执行不同的分支代码,break和continue语句将执行流程跳转到其他语句。除了C里面传统的for-条件-递增循环,Swift还增加了for-in循环使得遍历数组...
  •  曾有这样的疑问,为什么很多资料中都有关于windows中的While(getmessage(&msg,... 说,其实这里的while(){}循环是占用cpu的,只是getmessage()是一个阻塞型的函数,当消息队列中没有消息时,它会检查确认,当确认消
  • 除了上述在生态系统水平的机制,在社会和经济系统中同样存在滞后效应。例如,如果低产量提高了食品的价格,就会鼓励更多人将森林转变为农田或草原。 极端气候可诱导一系列相互关联的影响,所有这些影响有...
  • 可伸缩并行化的最简单的形式就是能够互不干涉地同时运行的迭代的循环。本节将会说明如何将简单的循环并行化。 定义 Intel Threading Building Blocks(Intel TBB) 组件的命名空间是 tbb 。简洁起见,只在第一次提到...
  • //while外循环控制是否继续玩游戏 while (true) { //while内循环控制是否猜对,可以一直猜。 while (true) { var num = +prompt("请你输入你所猜的数字:范围:[0,100]"); if (computerNum == num) { alert...
  • 对于循环神经网络来说,我们首先需要做的仍旧是找到一种将数据序列化的方法。当然,对于古诗词来说,每个字的出现顺序就是天然的一个序列,因此我们就可以直接按照这个序列来处理。并且一首古诗词可以看成是一个样本...
  • 一、首先说下本篇博客所实现功能的背景和功能是怎样的:  背景:因为公司项目开始迁移新平台... 功能:需要查询出某个日期节点后的几十万条订单信息,循环遍历每条订单,获取每条订单的交易额,从而根据订单中的商
  • Python风格规范 分号 Tip 不要在行尾加分号, 也不要用分号将两条命令放在同一行. 行长度 Tip 每行不超过80个字符 例外: 长的导入模块语句注释里的URL ... 你可以利用这个特点....
  • 基于Arduino的自动调速风扇

    千次阅读 2020-06-15 21:22:58
    好久没有更新了,家里实在太乱,最近趁着618活动,买了一堆收纳抽屉和置物架,把家里彻底收拾了一下,然后客厅就成了这个奇葩样子,注孤生的节奏了。 话不多说,夏天到了,天气逐渐热起来,风扇成了必不可少的消暑...
  • 电脑散热风扇轴承分类

    千次阅读 2015-03-13 08:29:29
    纳米陶瓷轴承风扇在组装过程中,也比滚珠轴承 扇的工序简单很多,从而在无形中使品质得到了提高,成本得以降低。由于纳米陶瓷轴承体积较小,所以在风扇的设计中便可加长电机的绕线,从而增大功率提高转 速。相对于...
  • 《C++ Primer》习题参考答案:第5章 - 循环、分支、跳转和异常处理语句 文章目录 专栏C++学习笔记 [Cpp-Prime5 + Cpp-Primer-Plus6 源代码和课后题](https://github.com/TeFuirnever/Cpp-Primer-Plus-Plus-Plus) ...
  • 我们使用循环一致性损失以鼓励这种行为: 在初步实验中,我们也尝试用F(G(x))和x之间、G(F(y))和y之间的对抗性损失来代替L1范数,但没有观察到性能的改善。 循环一致性损失引起的行为如图4所示: 重构图像F(G(x))最终...
  • 我们知道Pix2Pix优化时除了使用GAN Loss(对抗损失),还加入了生成器输入图片和输出图片的L1 Loss来对齐生成图片与输入图片的宏观轮廓(所谓低频信息)。同样的逻辑,我们也能在CycleGAN中用L1 Loss来对齐“循环...
  • 固——求马的固数和固集

    千次阅读 2016-09-24 00:19:33
    马的
  • C风格字符串

    千次阅读 2007-08-04 15:36:00
    真值表明这是除null的任意字符,则继续循环直到cp指向结束字符数组的null时,循环结束.while循环体做完必要的处理后,cp加1,向下移动指针指向数组中的下一个字符. ----------------------------------------------...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 53,728
精华内容 21,491
关键字:

外循环没有风