Geeks_Z の Blog Geeks_Z の Blog
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)

Geeks_Z

AI小学生
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)
  • Python

  • MLTutorials

  • 卷积神经网络

  • 循环神经网络

    • Recurrent Neural Networks
      • Recurrent Neural Networks
      • 应用举例
        • 1-of-N encoding
        • Beyond 1-of-N encoding
      • RNN概述
        • 例子
      • RNN架构
      • RNN怎么学习?
      • 如何解决RNN梯度消失或者爆炸
        • 其他方式
      • RNN其他应用
        • 多对一序列
        • 情感识别
        • 多对多序列
        • 语音识别
        • CTC语音识别
        • Sequence to sequence learning
        • Beyond Sequence
        • Document转成Vector
      • Sequence-to-sequence -Text
      • Sequence-to-sequence -Speech
      • Demo:聊天机器人
      • Attension-based Model
        • 阅读理解
        • Visual Question Answering
        • Speech Question Answering
        • RNN 和Structured learning关系
      • Integerated Together
        • Structure learning practical?
        • Sequence Models
        • Notation
        • Recurrent Neural Network Model
        • Backpropagation through time
        • Different types of RNNs
        • Language model and sequence generation
        • Sampling novel sequences
        • Vanishing gradients with RNNs
        • Gated Recurrent Unit(GRU)
        • 1.10 长短期记忆(LSTM(long short term memory)unit)
        • 1.11 双向循环神经网络(Bidirectional RNN)
        • 1.12 深层循环神经网络(Deep RNNs)
    • LSTM
  • Transformer

  • VisionTransformer

  • 扩散模型

  • 计算机视觉

  • PTM

  • MoE

  • LoRAMoE

  • LongTailed

  • 多模态

  • 知识蒸馏

  • PEFT

  • 对比学习

  • 小样本学习

  • 迁移学习

  • 零样本学习

  • 集成学习

  • Mamba

  • PyTorch

  • CL

  • CIL

  • 小样本类增量学习FSCIL

  • UCIL

  • 多模态增量学习MMCL

  • LTCIL

  • DIL

  • 论文阅读与写作

  • 分布外检测

  • GPU

  • 深度学习调参指南

  • AINotes
  • 循环神经网络
Geeks_Z
2022-08-16
目录

Recurrent Neural Networks

Recurrent Neural Networks

RNN,或者说最常用的LSTM,一般用于记住之前的状态,以供后续神经网络的判断,它由input gate、forget gate、output gate和cell memory组成,每个LSTM本质上就是一个neuron,特殊之处在于有4个输入:z 三门控制信号zi、zf zo,每个时间点的输入都是由当前输入值+上一个时间点的输出值+上一个时间点cell值来组成

应用举例

这边举的例子是slot filling,我们假设订票系统听到用户说:“ i would like to arrive Taipei on November 2nd”,你的系统有一些slot(有一个slot叫做Destination,一个slot叫做time of arrival),系统要自动知道这边的每一个词汇是属于哪一个slot,比如Taipei属于Destination这个slot,November 2nd属于time of arrival这个slot。

这个问题你当然可以使用一个feedforward neural network来解,也就是说我叠一个feedforward neural network,input是一个词汇(把Taipei变成一个vector)丢到这个neural network里面去(你要把一个词汇丢到一个neural network里面去,就必须把它变成一个向量来表示)。以下是把词汇用向量来表示的方法:

1-of-N encoding

Beyond 1-of-N encoding

如果只是用1-of-N encoding来描述一个词汇的话你会遇到一些问题,因为有很多词汇你可能都没有见过,所以你需要在1-of-N encoding里面多加dimension,这个dimension代表other。然后所有的词汇,如果它不是在我们词言有的词汇就归类到other里面去(Gandalf,Sauron归类到other里面去)。你可以用每一个词汇的字母来表示它的vector,比如说,你的词汇是apple,apple里面有出现app、ppl、ple,那在这个vector里面对应到app,ple,ppl的dimension就是1,而其他都为0。

假设把词汇表示为vector,把这个vector丢到feedforward neural network里面去,在这个task里面,你就希望你的output是一个probability distribution。这个probability distribution代表着我们现在input这词汇属于每一个slot的几率,比如Taipei属于destination的几率和Taipei属于time of departure的几率。

但是光只有这个是不够的,feedforward neural network是没有办法解决这个问题。为什么呢,假设现在有一个使用者说:“arrive Taipei on November 2nd”(arrive-other,Taipei-dest, on-other,November-time,2nd-time)。那现在有人说:"leave Taipei on November 2nd",这时候Taipei就变成了“place of departure”,它应该是出发地而不是目的地。但是对于neural network来说,input一样的东西output就应该是一样的东西(input "Taipei",output要么是destination几率最高,要么就是place of departure几率最高),你没有办法一会让出发地的几率最高,一会让它目的地几率最高。这个怎么办呢?这时候就希望我们的neural network是有记忆力的。如果今天我们的neural network是有记忆力的,它记得它看过红色的Taipei之前它就已经看过arrive这个词汇;它记得它看过绿色之前,它就已经看过leave这个词汇,它就可以根据上下文产生不同的output。如果让我们的neural network是有记忆力的话,它就可以解决input不同的词汇,output不同的问题。

但是光只有这个是不够的,feedforward network是没有办法slot这个probability。为什么呢,假设现在有一个使用者说:“arrive Taipei on November 2nd”(arrive-other,Taipei-dest, on-other,November-time,2nd-time)。那现在有人说:"leave Taipei on November 2nd",这时候Taipei就变成了“place of departure”,它应该是出发地而不是目的地。但是对于neural network来说,input一样的东西output就应该是一样的东西(input "Taipei",output要么是destination几率最高,要么就是time of departure几率最高),你没有办法一会让出发地的几率最高,一会让它目的地几率最高。这个肿么办呢?这时候就希望我们的neural network是有记忆力的。如果今天我们的neural network是有记忆力的,它记得它看过红色的Taipei之前它就已经看过arrive这个词汇;它记得它看过绿色之前,它就已经看过leave这个词汇,它就可以根据上下文产生不同的output。如果让我们的neural network是有记忆力的话,它就可以解决input不同的词汇,output不同的问题。

RNN概述

这种有记忆的neural network就叫做Recurrent Neural network(RNN)。在RNN里面,每一次hidden layer的neuron产生output的时候,这个output会被存到memory里去(用蓝色方块表示memory)。那下一次当有input时,这些neuron不只是考虑inputx1,x2,还会考虑存到memory里的值。对它来说除了x1,x2 外,这些存在memory里的值a1,a2 会影响它的output。

例子

举个例子,假设我们现在图上这个neural network,它所有的weight都是1,所有的neuron没有任何的bias。假设所有的activation function都是linear(这样可以不要让计算太复杂)。现在假设我们的input 是sequence[11][11][22]... 把这个sequence输入到neural network里面去会发生什么事呢?在你开始要使用这个Recurrent Neural Network的时候,你必须要给memory初始值(假设他还没有放进任何东西之前,memory里面的值是0)

现在输入第一个[11],接下来对发生什么事呢?,对左边的那个neural来说(第一个hidden layer),它除了接到input的[11] 接到了memory(0跟0),output就是2(所有的weight都是1),右边也是一样output为2。第二层hidden laeyer output为4。

接下来Recurrent Neural Network会将绿色neuron的output存在memory里去,所以memory里面的值被update为2。

接下来再输入[11],接下来绿色的neuron输入有四个 [11][22],output为[66](weight=1),第二层的neural output为[1212]。

所以对Recurrent Neural Network来说,你就算input一样的东西,它的output是可能不一样了(因为有memory)

现在[66] 到memory里去,接下来input是[22],output为[1616],第二层hidden layer为[3232]

那在做Recurrent Neural Network时,有一件很重要的事情就是这个input sequence调换顺序之后output不同(Recurrent Neural Network里,它会考虑sequence的order)

RNN架构

今天我们要用Recurrent Neural Network处理slot filling这件事,就像是这样,使用者说:“arrive Taipei on November 2nd”,arrive就变成了一个vector丢到neural network里面去,neural network的hidden layer的output写成a1(a1 一排neural的output,是一个vector),a1 生y1,y1 是“arrive”属于每一个slot filling的几率。接下来a1 被存到memory里面去,"Taipei会变为input",这个hidden layer会同时考虑“Taipei”这个input和存在memory里面的a1,得到a2,根据a2 到y2,y2 属于每一个slot filling的几率。以此类推(a3 到y2)。

有人看到这里,说这是有三个network,这个不是三个network,这是同一个network在三个不同的时间点被使用了三次。(我这边用同样的weight用同样的颜色表示)

那所以我们有了memory以后,刚才我们讲了输入同一个词汇,我们希望output不同的问题就有可能被解决。比如说,同样是输入“Taipei”这个词汇,但是因为红色“Taipei”前接了“leave”,绿色“Taipei”前接了“arrive”(因为“leave”和“arrive”的vector不一样,所以hidden layer的output会不同),所以存在memory里面的值会不同。现在虽然x2 值是一样的,因为存在memory里面的值不同,所以hidden layer的output会不一样,所以最后的output也就会不一样。这是Recurrent Neural Network的基本概念。

RNN怎么学习?

如果要做learning的话,你要定义一个cost function来evaluate你的model是好还是不好,选一个parameter要让你的loss 最小。那在Recurrent Neural Network里面,你会怎么定义这个loss呢,下面我们先不写算式,先直接举个例子。

假设我们现在做的事情是slot filling,那你会有train data,那这个train data是说:我给你一些sentence,你要给sentence一些label,告诉machine说第一个word它是属于other slot,“Taipei是”Destination slot,"on"属于other slot,“November”和“2nd”属于time slot,然后接下来你希望说:你的cost咋样定义呢。那“arrive”丢到Recurrent Neural Network的时候,Recurrent Neural Network会得到一个output y1,接下来这个y1 看它的reference vector算它的cross entropy。你会希望说,如果我们丢进去的是“arrive”,那他的reference vector应该对应到other slot的dimension(其他为0),这个reference vector的长度就是slot的数目(这样四十个slot,reference vector的dimension就是40),那input的这个word对应到other slot的话,那对应到other slot dimension为1,其它为0。

那现在把“Taipei”丢进去之后,因为“Taipei”属于destination slot,就希望说把x2 进去的话,y2 要跟reference vector距离越近越好。那y2 reference vector是对应到destination slot是1,其它为0。

那这边注意的事情就是,你在丢x2 前,你一定要丢x1(在丢“Taipei”之前先把“arrive''丢进去),不然你就不知道存到memory里面的值是多少。所以在做training的时候,你也不能够把这些word打散来看,word sentence仍然要当做一个整体来看。把“on”丢进去,reference vector对应的other的dimension是1,其它是0.

RNN的损失函数output和reference vector的entropy的和就是要最小化的对象。

有了这个loss function以后,对于training,也是用梯度下降来做。也就是说我们现在定义出了loss function(L),我要update这个neural network里面的某个参数w,就是计算对w的偏微分,偏微分计算出来以后,就用GD的方法去update里面的参数。在讲feedforward neural network的时候,我们说GD用在feedforward neural network里面你要用一个有效率的算法叫做Backpropagation。那Recurrent Neural Network里面,为了要计算方便,所以也有开发一套算法是Backpropagation的进阶版,叫做BPTT。它跟Backpropagation其实是很类似的,只是Recurrent Neural Network它是在high sequence上运作,所以BPTT它要考虑时间上的information。

不幸的是,RNN的training是比较困难的。一般而言,你在做training的时候,你会期待,你的learning curve是像蓝色这条线,这边的纵轴是total loss,横轴是epoch的数目,你会希望说:随着epoch的数目越来越多,随着参数不断的update,loss会慢慢的下降最后趋向收敛。但是不幸的是你在训练Recurrent Neural Network的时候,你有时候会看到绿色这条线。如果你是第一次trai Recurrent Neural Network,你看到绿色这条learning curve非常剧烈的抖动,然后抖到某个地方,这时候你会有什么想法,我相信你会:这程序有bug啊。

分析了下RNN的性质,他发现说RNN的error surface是total loss的变化是非常陡峭的/崎岖的(error surface有一些地方非常的平坦,一些地方非常的陡峭,就像是悬崖峭壁一样),纵轴是total loss,x和y轴代表是两个参数。这样会造成什么样的问题呢?假设你从橙色的点当做你的初始点,用GD开始调整你的参数(updata你的参数,可能会跳过一个悬崖,这时候你的loss会突然爆长,loss会非常上下剧烈的震荡)。有时候你可能会遇到更惨的状况,就是以正好你一脚踩到这个悬崖上,会发生这样的事情,因为在悬崖上的gradient很大,之前的gradient会很小,所以你措手不及,因为之前gradient很小,所以你可能把learning rate调的比较大。很大的gradient乘上很大的learning rate结果参数就update很多,整个参数就飞出去了。

用工程的思想来解决,这一招蛮关键的,在很长的一段时间,只有他的code可以把RNN的model给train出来。

这一招就是clipping(当gradient大于某一个threshold的时候,不要让它超过那个threshold),当gradient大于15时,让gradient等于15结束。因为gradient不会太大,所以你要做clipping的时候,就算是踩着这个悬崖上,也不飞出来,会飞到一个比较近的地方,这样你还可以继续做你得RNN的training。

问题:为什么RNN会有这种奇特的特性。有人会说,是不是来自sigmoid function,我们之前讲过Relu activation function的时候,讲过一个问题gradient vanish,这个问题是从sigmoid function来的,RNN会有很平滑的error surface是因为来自于gradient vanish,这问题我是不认同的。等一下来看这个问题是来自sigmoid function,你换成Relu去解决这个问题就不是这个问题了。跟大家讲个秘密,一般在train neural network时,一般很少用Relu来当做activation function。为什么呢?其实你把sigmoid function换成Relu,其实在RNN performance通常是比较差的。所以activation function并不是这里的关键点。

如果说我们今天讲BPTT,你可能会从式子更直观的看出为什么会有这个问题。那今天我们没有讲BPTT。没有关系,我们有更直观的方法来知道一个gradient的大小。

你把某一个参数做小小的变化,看它对network output的变化有多大,你就可以测出这个参数的gradient的大小。

举一个很简单的例子,只有一个neuron,这个neuron是linear。input没有bias,input的weight是1,output的weight也是1,transition的weight是w。也就是说从memory接到neuron的input的weight是w。

现在我假设给neural network的输入是(1,0,0,0),那这个neural network的output会长什么样子呢?比如说,neural network在最后一个时间点(1000个output值是w999)。

现在假设w是我们要learn的参数,我们想要知道它的gradient,所以是知道当我们改变w的值时候,对neural的output有多大的影响。现在假设w=1,那现在y1000=1,假设w=1.01,y1000≈20000,这个就跟蝴蝶效应一样,w有一点小小的变化,会对它的output影响是非常大的。所以w有很大的gradient。有很大的gradient也并没有,我们把learning rate设小一点就好了。但我们把w设为0.99,那y1000≈0,那如果把w设为0.01,那y1000≈0。也就是说在1的这个地方有很大的gradient,但是在0.99这个地方就突然变得非常非常的小,这个时候你就需要一个很大的learning rate。设置learning rate很麻烦,你的error surface很崎岖,你的gardient是时大时小的,在非常小的区域内,gradient有很多的变化。从这个例子你可以看出来说,为什么RNN会有问题,RNN training的问题其实来自它把同样的东西在transition的时候反复使用。所以这个w只要一有变化,它完全由可能没有造成任何影响,一旦造成影响,影响都是天崩地裂的(所以gradient会很大,gradient会很小)。

所以RNN不好训练的原因不是来自activation function而是来自于它有high sequence同样的weight在不同的时间点被反复的使用。

如何解决RNN梯度消失或者爆炸

有什么样的技巧可以告诉我们可以解决这个问题呢?其实广泛被使用的技巧就是LSTM,LSTM可以让你的error surface不要那么崎岖。它可以做到的事情是,它会把那些平坦的地方拿掉,解决gradient vanish的问题,不会解决gradient explode的问题。有些地方还是非常的崎岖的(有些地方仍然是变化非常剧烈的,但是不会有特别平坦的地方)。

如果你要做LSTM时,大部分地方变化的很剧烈,所以当你做LSTM的时候,你可以放心的把你的learning rate设置的小一点,保证在learning rate很小的情况下进行训练。

那为什么LSTM 可以解决梯度消失的问题呢,为什么可以避免gradient特别小呢?

我听说某人在面试某国际大厂的时候被问到这个问题,那这个问题怎么答比较好呢(问题:为什么我们把RNN换成LSTM)。如果你的答案是LSTM比较潮,LSTM比较复杂,这个就太弱了。正在的理由就是LSTM可以handle gradient vanishing的问题。接下里面试官说:为什么LSTM会handle gradient vanishing的问题呢?用这边的式子回答看看,若考试在碰到这样的问题时,你就可以回答了。

RNN跟LSTM在面对memory的时候,它处理的操作其实是不一样的。你想想看,在RNN里面,在每一个时间点,memory里面的值都是会被洗掉,在每一个时间点,neuron的output都要memory里面去,所以在每一个时间点,memory里面的值都是会被覆盖掉。但是在LSTM里面不一样,它是把原来memory里面的值乘上一个值再把input的值加起来放到cell里面。所以它的memory input是相加的。所以今天它和RNN不同的是,如果今天你的weight可以影响到memory里面的值的话,一旦发生影响会永远都存在。不像RNN在每个时间点的值都会被format掉,所以只要这个影响被format掉它就消失了。但是在LSTM里面,一旦对memory造成影响,那影响一直会被留着(除非forget gate要把memory的值洗掉),不然memory一旦有改变,只会把新的东西加进来,不会把原来的值洗掉,所以它不会有gradient vanishing的问题

那你想说们现在有forget gate可能会把memory的值洗掉。其实LSTM的第一个版本其实就是为了解决gradient vanishing的问题,所以它是没有forget gate,forget gate是后来才加上去的。甚至,现在有个传言是:你在训练LSTM的时候,你要给forget gate特别大的bias,你要确保forget gate在多数的情况下都是开启的,只要少数的情况是关闭的

那现在有另外一个版本用gate操控memory cell,叫做Gates Recurrent Unit(GRU),LSTM有三个Gate,而GRU有两个gate,所以GRU需要的参数是比较少的。因为它需要的参数量比较少,所以它在training的时候是比较鲁棒的。如果你今天在train LSTM,你觉得overfitting的情况很严重,你可以试下GRU。GRU的精神就是:旧的不去,新的不来。它会把input gate跟forget gate联动起来,也就是说当input gate打开的时候,forget gate会自动的关闭(format存在memory里面的值),当forget gate没有要format里面的值,input gate就会被关起来。也就是说你要把memory里面的值清掉,才能把新的值放进来。

其他方式

其实还有其他的technique是来handle gradient vanishing的问题。比如说clockwise RNN或者说是Structurally Constrained Recurrent Network (SCRN)等等。

有一个蛮有趣的paper是这样的:一般的RNN用identity matrix(单位矩阵)来initialized transformation weight+ReLU activaton function它可以得到很好的performance。刚才不是说用ReLU的performance会比较呀,如果你说一般train的方法initiaed weight是random,那ReLU跟sigmoid function来比的话,sigmoid performance 会比较好。但是你今天用了identity matrix的话,这时候用ReLU performance会比较好。

RNN其他应用

其实RNN有很多的application,前面举得那个solt filling的例子。我们假设input跟output的数目是一样的,也就是说input有几个word,我们就给每一个word slot label。那其实RNN可以做到更复杂的事情

多对一序列

情感识别

那其实RNN可以做到更复杂的事情,比如说input是一个sequence,output是一个vector,这有什么应用呢。比如说,你可以做sentiment analysis。sentiment analysis现在有很多的应用:

某家公司想要知道,他们的产品在网上的评价是positive 还是negative。他们可能会写一个爬虫,把跟他们产品有关的文章都爬下来。那这一篇一篇的看太累了,所以你可以用一个machine learning 的方法learn一个classifier去说哪些document是正向的,哪些document是负向的。或者在电影上,sentiment analysis所做的事就是给machine 看很多的文章,然后machine要自动的说,哪些文章是正类,哪些文章是负类。怎么样让machine做到这件事情呢?你就是learn一个Recurrent Neural Network,这个input是character sequence,然后Recurrent Neural Network把这个sequence读过一遍。在最后一个时间点,把hidden layer拿出来,在通过几个transform,然后你就可以得到最后的sentiment analysis(这是一个分类的问题,但是因为input是sequence,所以用RNN来处理)

用RNN来作key term extraction。key term extraction意思就是说给machine看一个文章,machine要预测出这篇文章有哪些关键词汇。那如果你今天能够收集到一些training data(一些document,这些document都有label,哪些词汇是对应的,那就可以直接train一个RNN),那这个RNN把document当做input,通过Embedding layer,然后用RNN把这个document读过一次,然后把出现在最后一个时间点的output拿过来做attention,你可以把这样的information抽出来再丢到feedforward neural network得到最后的output

多对多序列

语音识别

那它也可以是多对多的,比如说当你的input和output都是sequence,但是output sequence比input sequence短的时候,RNN可以处理这个问题。什么样的任务是input sequence长,output sequence短呢。比如说,语音辨识就是这样的任务。在语音辨识这个任务里面input是acoustic sequence(说一句话,这句话就是一段声音讯号)。我们一般处理声音讯号的方式,在这个声音讯号里面,每隔一小段时间,就把它用vector来表示。这个一小段时间是很短的(比如说,0.01秒)。那output sequence是character sequence。

如果你是原来的RNN(slot filling的那个RNN),你把这一串input丢进去,它充其量只能做到说,告诉你每一个vector对应到哪一个character。加入说中文的语音辨识的话,那你的output target理论上就是这个世界上所有可能中文的词汇,常用的可能是八千个,那你RNNclassifier的数目可能就是八千个。虽然很大,但也是没有办法做的。但是充其量只能做到说:每一个vector属于一个character。每一个input对应的时间间隔是很小的(0.01秒),所以通常是好多个vector对应到同一个character。所以你的辨识结果为“好好好棒棒棒棒棒”。你会说:这不是语音辨识的结果呀,有一招叫做“trimming”(把重复的东西拿掉),就变成“好棒”。这这样会有一个严重的问题,因为它没有辨识“好棒棒”。

CTC语音识别

需要把“好棒”跟“好棒棒”分开来,怎么办,我们有一招叫做“CTC”(这招很神妙),它说:我们在output时候,我们不只是output所有中文的character,我们还有output一个符号,叫做"null""(没有任何东西)。所以我今天input一段acoustic feature sequence,它的output是“好 null null 棒 null null null null”,然后我就把“null”的部分拿掉,它就变成“好棒”。如果我们输入另外一个sequence,它的output是“好 null null 棒 null 棒 null null”,然后把“null”拿掉,所以它的output就是“好棒棒”。这样就可以解决叠字的问题了。

那在训练neuron怎么做呢(CTC怎么做训练呢)。CTC在做training的时候,你手上的train data就会告诉你说,这一串acoustic features对应到这一串character sequence,但它不会告诉你说“好”是对应第几个character 到第几个character。这该怎么办呢,穷举所有可能的alignments。简单来说就是,我们不知道“好”对应到那几个character,“棒”对应到哪几个character。假设我们所有的状况都是可能的。可能第一个是“好 null 棒 null null null”,可能是“好 null null 棒 null null”,也可能是“好 null null null 棒 null”。我们不知道哪个是对的,那假设全部都是对的。在training的时候,全部都当做是正确的,然后一起train。穷举所有的可能,那可能性太多了,有没有巧妙的算法可以解决这个问题呢?那今天我们就不细讲这个问题。

以下是在文献CTC上得到的结果。在做英文辨识的时候,你的RNN output target 就是character(英文的字母+空白)。直接output字母,然后如果字和字之间有boundary,就自动有空白。

假设有一个例子,第一个frame是output h,第二个frame是output null,第三个frame是output null,第四个frame是output I等等。如果你看到output是这样子话,那最后把“null”的地方拿掉,那这句话的辨识结果就是“HIS FRIEND'S”。你不需要告诉machine说:"HIS"是一个词汇,“FRIEND's”是一个词汇,machine通过training data会自己学到这件事情。那传说,Google的语音辨识系统已经全面换成CTC来做语音辨识。如果你用CTC来做语音辨识的话,就算是有某一个词汇(比如是:英文中人名,地名)在training data中从来没有出现过,machine也是有机会把它辨识出来。

Sequence to sequence learning

另外一个神奇RNN的应用叫做sequence to sequence learning,在sequence to sequence learning里面,RNN的input跟output都是sequence(但是两者的长度是不一样的)。刚在在CTC时,input比较长,output比较短。在这边我们要考虑的是不确定input跟output谁比较长谁比较短。

比如说,我们现在做machine translation,input英文word sequence把它翻译成中文的character sequence。那我们并不知道说,英文跟中文谁比较长谁比较短(有可能是output比较长,output比较短)。所以改怎么办呢?

现在假如说input machine learning ,然后用RNN读过去,然后在最后一个时间点,这个memory里面就存了所有input sequence的information。

然后接下来,你让machine 吐一个character(机),然后就让它output下一个character,把之前的output出来的character当做input,再把memory里面的值读进来,它就会output “器”。那这个“机”怎么接到这个地方呢,有很多支支节节的技巧,还有很多不同的变形。在下一个时间input “器”,output“学”,然后output“习”,然后一直output下去

这就让我想到推文接龙,有一个人推超,下一个人推人,然后推正,然后后面一直推推,等你推好几个月,都不会停下来。你要怎么让它停下来呢?推出一个“断”,就停下来了。

要怎么阻止让它产生词汇呢?你要多加一个symbol “断”,所以现在manchine不只是output说可能character,它还有一个可能output 叫做“断”。所以今天“习”后面是“===”(断)的话,就停下来了。你可能会说这个东西train的起来吗,这是train的起来的。

这篇的papre是这样做的,sequence to sequence learning我们原来是input 某种语言的文字翻译成另外一种语言的文字(假设做翻译的话)。那我们有没有可能直接input某种语言的声音讯号,output另外一种语言的文字呢?我们完全不做语音辨识。比如说你要把英文翻译成中文,你就收集一大堆英文的句子,看看它对应的中文翻译。你完全不要做语音辨识,直接把英文的声音讯号丢到这个model里面去,看它能不能output正确的中文。这一招居然是行得通的。假设你今天要把台语转成英文,但是台语的语音辨识系统不好做,因为台语根本就没有standard文字系统,所以这项技术可以成功的话,未来你在训练台语转英文语音辨识系统的时候,你只需要收集台语的声音讯号跟它的英文翻译就可以刻了。你就不需要台语语音辨识的结果,你也不需要知道台语的文字,也可以做这件事。

Beyond Sequence

利用sequence to sequence的技术,甚至可以做到Beyond Sequence。这个技术也被用到syntactic parsing。synthetic parsing这个意思就是说,让machine看一个句子,它要得到这个句子的结构树,得到一个树状的结构。怎么让machine得到这样的结构呢?,过去你可能要用structured learning的技术能够解这个问题。但是现在有了 sequence to sequence learning的技术以后,你只要把这个树状图描述成一个sequence(具体看图中 john has a dog)。所以今天是sequence to sequence learning 的话,你就直接learn 一个sequence to sequence model。它的output直接就是syntactic parsing tree。这个是可以train的起来的,非常的surprised

你可能想说machine它今天output的sequence不符合文法结构呢(记得加左括号,忘记加右括号),神奇的地方就是LSTM不会忘记右括号。

Document转成Vector

那我们要将一个document表示成一个vector的话,往往会用bag-of-word的方法,用这个方法的时候,往往会忽略掉 word order information。举例来说,有一个word sequence是“white blood cells destroying an infection”,另外一个word sequence是:“an infection destroying white blood cells”,这两句话的意思完全是相反的。但是我们用bag-of-word的方法来描述的话,他们的bag-of-word完全是一样的。它们里面有完全一摸一样的六个词汇,因为词汇的order是不一样的,所以他们的意思一个变成positive,一个变成negative,他们的意思是很不一样的。

那我们可以用sequence to sequence Auto-encoder这种做法来考虑word sequence order的情况下,把一个document变成一个vector。

Sequence-to-sequence -Text

input一个word sequence,通过Recurrent Neural Network变成一个invalid vector,然后把这个invalid vector当做decoder的输入,然后让这个decoder,找回一模一样的句子。如果今天Recurrent Neural Network可以做到这件事情的话,那Encoding这个vector就代表这个input sequence里面重要的information。在trian Sequence-to-sequence Auto-encoder的时候,不需要label data,你只需要收集大量的文章,然后直接train下去就好了。

Sequence-to-sequence 还有另外一个版本叫skip thought,如果用Sequence-to-sequence的,输入输出都是同一个句子,如果用skip thought的话,输出的目标就会是下一个句子,用sequence-to-sequence得到的结果通常容易表达,如果要得到语义的意思的,那么skip thought会得到比较好的结果。 这个结构甚至可以是hierarchical,你可以每一个句子都先得到一个vector(Mary was hungry得到一个vector,she didn't find any food得到一个vector),然后把这些vector加起来,然后变成一个整个 document high label vector,在让这整个vector去产生一串sentence vector,在根据每一个sentence vector再去解回word sequence。这是一个四层的LSTM(从word 变成sentence sequence ,变成document lable,再解回sentence sequence,再解回word sequence)

Sequence-to-sequence -Speech

这个也可以用到语音上,在语音上,它可以把一段audio segment变成一个fixed length vector。比如说,左边有一段声音讯号,长长短短都不一样,那你把他们变成vector的话,可能dog跟dogs比较接近,never和ever比较接近。我称之为audio auto vector。一般的auto vector它是把word变成vector,这个是把一段声音讯号变成一个vector。

那这个东西有什么用呢?它可以做很多的事。比如说,我们可以拿来做语音的搜寻。什么是语音的搜寻呢?你有一个声音的data base(比如说,上课的录音,然后你说一句话,比如说,你今天要找跟美国白宫有关的东西,你就说美国白宫,不需要做语音辨识,直接比对声音讯号的相似度,machine 就可以从data base里面把提到的部分找出来)

怎么实现呢?你就先把一个audio data base,把这个data base做segmentation切成一段一段的。然后每一个段用刚才讲的audio segment to vector这个技术,把他们通通变成vector。然后现再输入一个spoken query,可以通过audio segment to vector技术也变成vector,接下来计算他们的相似程度。然后就得到搜寻的结果

如何把一个audio segment变成一个vector呢?把audio segment抽成acoustic features,然后把它丢到Recurrent neural network里面去,那这个recurrent neural network它的角色就是Encoder,那这个recurrent neural network读过acoustic features之后,最后一个时间点它存在memory里面的值就代表了input声音讯号它的information。它存到memory里面的值是一个vector。这个其实就是我们要拿来表示整段声音讯号的vector。

但是只要RNN Encoder我没有办法去train,同时你还要train一个RNN Decoder,Decoder的作用就是,它把Encoder得到的值存到memory里面的值,拿进来当做input,然后产生一个acoustic features sequence。然后希望这个y1 x1 接近越好。然后再根据y1 生y2,以此类推。今天训练的targety1,y2,y3,y4 x1,x2,x3,x4 接近越好。那在训练的时候,RNN Encoder跟RNN Decoder是一起train的

我们在实验上得到一些有趣的结果,图上的每个点其实都是一段声音讯号,你把声音讯号用刚才讲的 Sequence-to-sequence Auto-encoder技术变成平面上一个vector。发现说:fear这个位置在左上角,near的位置在右下角,他们中间这样的关系(fame在左上角,name在右下角)。你会发现说:把fear的开头f换成n,跟fame的开头f换成n,它们的word vector的变化方向是一样的。现在这个技术还没有把语义加进去。

Demo:聊天机器人

现在有一个demo,这个demo是用Sequence-to-sequence Auto-encoder来训练一个chat-bot(聊天机器人)。怎么用sequence to sequence learning来train chat-bot呢?你就收集很多的对话,比如说电影的台词,在电影中有一个台词是“How are you”,另外一个人接“I am fine”。那就告诉machine说这个sequence to sequence learning当它input是“How are you”的时候,这个model的output就要是“I am fine”。你可以收集到这种data,然后就让machine去 train。这里我们就收集了四万句和美国总统辩论的句子,然后让machine去学这个sequence to sequence这个model。

现在除了RNN以外,还有另外一种有用到memory的network,叫做Attention-based Model,这个可以想成是RNN的进阶的版本。

那我们知道说,人的大脑有非常强的记忆力,所以你可以记得非常非常多的东西。比如说,你现在同时记得早餐吃了什么,同时记得10年前夏天发生的事,同时记得在这几门课中学到的东西。那当然有人问你说什么是deep learning的时候,那你的脑中会去提取重要的information,然后再把这些information组织起来,产生答案。但是你的脑中会自动忽略那些无关的事情,比如说,10年前夏天发生的事情等等。

Attension-based Model

其实machine也可以做到类似的事情,machine也可以有很大的记忆的容量。它可以有很大的data base,在这个data base里面,每一个vector就代表了某种information被存在machine的记忆里面。

当你输入一个input的时候,这个input会被丢进一个中央处理器,这个中央处理器可能是一个DNN/RNN,那这个中央处理器会操控一个Reading Head Controller,这个Reading Head Controller会去决定这个reading head放的位置。machine再从这个reading head 的位置去读取information,然后产生最后的output

这个model还有一个2.0的版本,它会去操控writing head controller。这个writing head controller会去决定writing head 放的位置。然后machine会去把它的information通过这个writing head写进它的data base里面。所以,它不仅有读的功能,还可以discover出来的东西写入它的memory里面去。这个就是大名鼎鼎的Neural Turing Machine

阅读理解

Attention-based Model 常常用在Reading Comprehension里面。所谓的Reading Comprehension就是让machine读一堆document,然后把这些document里面的内容(每一句话)变成一个vector。每一个vector就代表了每一句话的语义。比如你现在想问machine一个问题,然后这个问题被丢进中央处理器里面,那这个中央处理器去控制而来一个reading head controller,去决定说现在在这个data base里面哪些句子是跟中央处理器有关的。假设machine发现这个句子是跟现在的问题是有关的,就把reading head放到这个地方,把information 读到中央处理器中。读取information这个过程可以是重复数次,也就是说machine并不会从一个地方读取information,它先从这里读取information以后,它还可以换一个位置读取information。它把所有读到的information收集起来,最后给你一个最终的答案。

上图是在baby corpus上的结果,baby corpus是一个Q&A的一个简单的测试。我们需要做的事就是读过这五个句子,然后说:what color is Grey?,得到正确的答案,yes。那你可以从machine attention的位置(也就是reading head 的位置)看出machine的思路。图中蓝色代表了machine reading head 的位置,Hop1,Hop2,Hop3代表的是时间,在第一个时间点,machine先把它的reading head放在“greg is a frog”,把这个information提取出来。接下来提取“brian is a frog” information ,再提取“brian is yellow”information。最后它得到结论说:greg 的颜色是yellow。这些事情是machine自动learn出来的。也就是machine attention在哪个位置,这些通过neural network学到该怎么做,并不是去写程序,你要先看这个句子,在看这个句子。这是machine自动去决定的。

Visual Question Answering

也可以做Visual Question Answering,让machine看一张图,问它这是什么,如果它可以正确回答说:这是香蕉,这就非常厉害了。

这个Visual Question Answering该怎么做呢?先让machine看一张图,然后通过CNN你可以把这张图的一小块region用一小块的vector来表示。接下里,输入一个query,这个query被丢到中央处理器中,这个中央处理器去操控这个reading head controller,这个reading head controller决定读取的位置(是跟现在输入的问题是有关系的,这个读取的process可能要好几个步骤,machine会分好几次把information读到中央处理器,最后得到答案。

Speech Question Answering

那可以做语音的Question Answering 。比如说:在语音处理实验上我们让machine做TOEFL Listening Comprehension Test 。让machine听一段声音,然后问它问题,从四个选项里面,machine选择出正确的选项。那machine做的事情是跟人类考生做的事情是一模一样的。

那用的Model Architecture跟我们刚才看到的其实大同小异。你让machine先读一个question,然后把question做语义的分析得到question的语义,声音的部分是让语音辨识先转成文字,在把这些文字做语音的分析,得到这段文字的语义。那machine了解question的语义然后就可以做attention,决定在audio story里面哪些部分是回答问题有关的。这就像画重点一样,machine画的重点就是答案,它也可以回头去修正它产生的答案。经过几个process以后,machine最后得到的答案跟其他几个选项计算相似度,然后看哪一个想项的相似度最高,它就选那一个选项。那整个test就是一个大的neural network。除了语音辨识以外question semantic部分和audio semantic部分都是neural network,所以他们就可以训练的。

这些是一些实验结果,这个实验结果是:random 正确率是25 percent。有两个方法要比25 percent要强的。

这五个方法都是naive的方法,也就是完全不管文章的内容,直接看问题跟选项就猜答案。我们发现说,如果你选最短的那个选项,你就会得到35 percent的正确率。如果分析四个选项的semantic,用sequence-to-sequence autoencoder,去把一个选项的semantic找出来,然后再去看某个选项和另外三个选项的相似度,如果比较高的话,那就把该选项选出来。和人的直觉是相反的,直觉应该是选一个语义和另外三个语义是不像的,但是别人已经计算到会这么做的了,所以用了计中计,如果要选和其他选项语义比较相似的答案,反而比随便选得到正确答案的概率要高,如果选最不像的那个选项,得到的答案就会接近随机,都是设计好的。

另外还可以用memory network可以得到39.2 %正确率,如果用我们刚才讲的那个model的话,可以做到48.8%正确率。

RNN 和Structured learning关系

使用deep learning跟structure learning的技术有什么不同呢?首先假如我们用的是unidirectional RNN/LSTM,当你在 decision的时候,你只看了sentence的一半,而你是用structure learning的话,比如用Viterbi algrithm你考虑的是整个句子。从这个结果来看,也许HMM,SVM等还是占到一些优势的。但是这个优势并不是很明显,因为RNN和LSTM他们可以做Bidirectional ,所以他们也可以考虑一整个句子的information

在HMM/SVM里面,你可以explicitly的考虑label之间的关系

举例说,如果做inference的时候,再用Viterbi algrithm求解的时候(假设每个label出现的时候都要出现五次)这个算法可以轻松做到,因为可以修改机器在选择分数最高的时候,排除掉不符合constraint的那些结果,但是如果是LSTM/RNN,直接下一个constraint进去是比较难的,因为没办法让RNN连续吐出某个label五次才是正确的。所以在这点上,structured learning似乎是有点优势的。如果是RNN/LSTM,你的cost function跟你实际上要考虑的error往往是没有关系的,当你做RNN/LSTM的时候,考虑的cost是每一个时间点的cross entropy(每一个时间的RNN的output cross entropy),它跟你的error不见得是直接相关的。但是你用structure learning的话,structure learning 的cost会影响你的error,从这个角度来看,structured learning也是有一些优势的。最重要的是,RNN/LSTM可以是deep,HMMM,SVM等它们其实也可以是deep,但是它们要想拿来做deep learning 是比较困难的。在我们上一堂课讲的内容里面。它们都是linear,因为他们定义的evaluation函数是线性的。如果不是线性的话也会很麻烦,因为只有是线性的我们才能套用上节课讲的那些方法来做inference。

最后总结来看,RNN/LSTM在deep这件事的表现其实会比较好,同时这件事也很重要,如果只是线性的模型,function space就这么大,可以直接最小化一个错误的上界,但是这样没什么,因为所有的结果都是坏的,所以相比之下,deep learning占到很大的优势。

Integerated Together

deep learning和structured learning结合起来。input features 先通过RNN/LSTM,然后RNN/LSTM的output再做为HMM/svm的input。用RNN/LSTM的output来定义HMM/structured SVM的evaluation function,如此就可以同时享有deep learning的好处,也可以有structure learning的好处。

在语音上,我们常常把deep learning 和structure learning 合起来(CNN/LSTM/DNN + HMM),所以做语音的时候一般HMM都还在,这样得到的结果往往是最好的。

这个系统工作流程: 在HMM里面,必须要去计算x,y probability,而在structured learning里面,我们要计算x,y evaluation function,在语音辨识里面,x是声音讯号,y是语音辨识的结果。 在HMM里面,有transition的部分和emission的部分,DNN做的事情其实就是去取代的emission的部分,原来在HMM的emission部分就是去统计高斯混合模型,但是把它换成DNN以后,会得到很好的performance。 换的方法:把P(yl|xl) 换成P(xl|yl),公式是

P(xl|yl)=P(xl,yl)P(yl)=P(yl|xl)P(xl)P(yl)

其中P(yl|xl) 以从RNN里面来,P(yl) 可以直接count,P(xl) 以直接无视,因为在得到几率的时候,xl 输入(已知的),穷举所有的yl P(yl) 大,所有和x有关的项不影响inference的结果,所有我们可以不用把x考虑进来。

其实加上HMM在语音辨识里很有帮助,就算是用RNN,但是在辨识的时候,常常会遇到问题,假设我们是一个frame,用RNN来问这个frame属于哪个form,往往会产生奇怪的结果,比如说一个frame往往是蔓延好多个frame,比如理论是是看到第一个frame是A,第二个frame是A,第三个是A,第四个是A,然后BBB,但是如果用RNN做的时候,RNN每个产生的label都是独立的,所以可能会在前面若无其事的改成B,然后又是A,RNN很容易出现这个现象。HMM则可以把这种情况修复。因为RNN在训练的时候是分来考虑的,假如不同的错误对语音辨识结果影响很大,结果就会不好。所以加上HMM会很有帮助。

先用Bi-directional LSTM做feature,然后把这些feature拿来在做CRF或者Structured SVM,然后学习一个权重w,这个ϕ(x,y) feature,要直接从Bidirectional LSTM的输出可以得到比较好的结果。

Structure learning practical?

有人说structured learning是否现实?

structured learning需要解三个问题,其中input的问题往往很困难,因为要穷举所有的y让其最大,解一个optimization的问题,其实大部分状况都没有好的解决办法,只有少数有,其他都是不好的状况。所有有人说structured learning应用并不广泛,但是未来未必是这样的。

其实GAN就是一种structured learning,可以把discriminator看做是evaluation function(也就是problem 1)最后要解一个inference的问题,我们要穷举我们未知的东西,看看哪个可以让我们的evaluation function最大。这步往往比较困难,因为x的可能性太多了。但其实这个可以就是generator,我们可以想成generator就是用所给的noise,输出一个update,它输出的这个高斯模型,就是让discriminator分辨不出的高斯模型,如果discriminator就是evaluation function的话,那output的值就是让evaluation function的值很大的那个对应值,所以这个generator就是在解这个问题,其实generator的输出就是argmax的输出,可以把generator当做在解inference这个问题,然后就直接求problem 3。structured learning过程和GAN模型generator不断产生让discriminator最大的那个值,然后再去训练discriminator不断识别真实值,然后更新值的过程是异曲同工的。

GAN也可以是conditional的GAN,现在的任务是给定x,找出最有可能的y,想象成语音辨识,x是声音讯号,y是辨识出来的文字,如果是用conditional的概念,generator输入一个x,就会output一个y,discriminator是去检查y的pair是不是对的,如果给他一个真正的x,y的pair,会得到一个比较高的分数,给一个generator输出的一个y配上输入的x,所产生的一个假的pair,就会给他一个比较低的分数。训练的过程就和原来的GAN就是一样的,这个已经成功运用在文字产生图片这个task上面。这个task的input就是一句话,output就是一张图,generator做的事就是输入一句话,然后产生一张图片,而discriminator要做的事就是给他一张图片,要他判断这个x,y的pair是不是真的,如果把 discriminator换成evaluation function,把generator换成解inference的problem,其实conditional GAN和structured learning就是可以类比,或者说GAN就是训练structured learning的一种方法。

很多人都有类似的想法,比如GAN可以和Energy—based模型一起做。这里给出一些Reference。

[TOC]

Sequence Models

在本课程中你将学会序列模型,它是深度学习中最令人激动的内容之一。循环神经网络(RNN)之类的模型在语音识别、自然语言处理和其他领域中引起变革。在本节课中,你将学会如何自行创建这些模型。我们先看一些例子,这些例子都有效使用了序列模型。

在进行语音识别时,给定了一个输入音频片段 X,并要求输出对应的文字记录 Y。这个例子里输入和输出数据都是序列模型,因为 X 一个按时播放的音频片段,输出 Y 一系列单词。所以之后将要学到的一些序列模型,如循环神经网络等在语音识别方面是非常有用的。

音乐生成问题是使用序列数据的另一个例子,在这个例子中,只有输出数据 Y 序列,而输入数据可以是空集,也可以是个单一的整数,这个数可能指代你想要生成的音乐风格,也可能是你想要生成的那首曲子的头几个音符。输入的 X 以是空的,或者就是个数字,然后输出序列 Y。

在处理情感分类时,输入数据 X 序列,你会得到类似这样的输入:“There is nothing to like in this movie.”,你认为这句评论对应几星?

系列模型在DNA序列分析中也十分有用,你的DNA可以用A、C、G、T四个字母来表示。所以给定一段DNA序列,你能够标记出哪部分是匹配某种蛋白质的吗?

在机器翻译过程中,你会得到这样的输入句:“Voulez-vou chante avecmoi?”(法语:要和我一起唱么?),然后要求你输出另一种语言的翻译结果。

在进行视频行为识别时,你可能会得到一系列视频帧,然后要求你识别其中的行为。

在进行命名实体识别时,可能会给定一个句子要你识别出句中的人名。

所以这些问题都可以被称作使用标签数据 (X,Y) 为训练集的监督学习。但从这一系列例子中你可以看出序列问题有很多不同类型。有些问题里,输入数据 X 输出数据Y 是序列,但就算在那种情况下,X Y 时也会不一样长。或者像上图编号1所示和上图编号2的X Y 相同的数据长度。在另一些问题里,只有 X 者只有Y 序列。

Notation

比如说你想要建立一个序列模型,它的输入语句是这样的:“Harry Potter and Herminoe Granger invented a new spell.”,(这些人名都是出自于J.K.Rowling笔下的系列小说Harry Potter)。假如你想要建立一个能够自动识别句中人名位置的序列模型,那么这就是一个命名实体识别问题,这常用于搜索引擎,比如说索引过去24小时内所有新闻报道提及的人名,用这种方式就能够恰当地进行索引。命名实体识别系统可以用来查找不同类型的文本中的人名、公司名、时间、地点、国家名和货币名等等。

现在给定这样的输入数据x,假如你想要一个序列模型输出y,使得输入的每个单词都对应一个输出值,同时这个y 够表明输入的单词是否是人名的一部分。技术上来说这也许不是最好的输出形式,还有更加复杂的输出形式,它不仅能够表明输入词是否是人名的一部分,它还能够告诉你这个人名在这个句子里从哪里开始到哪里结束。比如Harry Potter(上图编号1所示)、Hermione Granger(上图标号2所示)。

更简单的那种输出形式:

这个输入数据是9个单词组成的序列,所以最终我们会有9个特征集和来表示这9个单词,并按序列中的位置进行索引,x<1>、x<2>、x<3> 等一直到x<9> 索引不同的位置,我将用x<t> 索引这个序列的中间位置。t 味着它们是时序序列,但不论是否是时序序列,我们都将用t 索引序列中的位置。

输出数据也是一样,我们还是用y<1>、y<2>、y<3> 等一直到y<9> 表示输出数据。同时我们用Tx 表示输入序列的长度,这个例子中输入是9个单词,所以Tx=9。我们用Ty 表示输出序列的长度。在这个例子里Tx=Ty,上个视频里你知道Tx Ty 以有不同的值。

你应该记得我们之前用的符号,我们用x(i) 表示第i 训练样本,所以为了指代第t 元素,或者说是训练样本i 序列中第t 元素用x(i)<t> 个符号来表示。如果Tx 序列长度,那么你的训练集里不同的训练样本就会有不同的长度,所以Tx(i) 代表第i 训练样本的输入序列长度。同样y(i)<t> 表第i 训练样本中第t 元素,Ty(i) 是第i 训练样本的输出序列的长度。

所以在这个例子中,Tx(i)=9,但如果另一个样本是由15个单词组成的句子,那么对于这个训练样本,Tx(i)=15。

既然我们这个例子是NLP,也就是自然语言处理,这是我们初次涉足自然语言处理,一件我们需要事先决定的事是怎样表示一个序列里单独的单词,你会怎样表示像Harry这样的单词,x<1> 际应该是什么?

接下来我们讨论一下怎样表示一个句子里单个的词。想要表示一个句子里的单词,第一件事是做一张词表,有时也称为词典,意思是列一列你的表示方法中用到的单词。这个词表(下图所示)中的第一个词是a,也就是说词典中的第一个单词是a,第二个单词是Aaron,然后更下面一些是单词and,再后面你会找到Harry,然后找到Potter,这样一直到最后,词典里最后一个单词可能是Zulu。

因此a是第一个单词,Aaron是第二个单词,在这个词典里,and出现在367这个位置上,Harry是在4075这个位置,Potter在6830,词典里的最后一个单词Zulu可能是第10,000个单词。所以在这个例子中我用了10,000个单词大小的词典,这对现代自然语言处理应用来说太小了。对于商业应用来说,或者对于一般规模的商业应用来说30,000到50,000词大小的词典比较常见,但是100,000词的也不是没有,而且有些大型互联网公司会用百万词,甚至更大的词典。许多商业应用用的词典可能是30,000词,也可能是50,000词。不过我将用10,000词大小的词典做说明,因为这是一个很好用的整数。

如果你选定了10,000词的词典,构建这个词典的一个方法是遍历你的训练集,并且找到前10,000个常用词,你也可以去浏览一些网络词典,它能告诉你英语里最常用的10,000个单词,接下来你可以用one-hot表示法来表示词典里的每个单词。

举个例子,在这里x<1> 示Harry这个单词,它就是一个第4075行是1,其余值都是0的向量(上图编号1所示),因为那是Harry在这个词典里的位置。

同样x<2> 个第6830行是1,其余位置都是0的向量(上图编号2所示)。

and在词典里排第367,所以x<3> 是第367行是1,其余值都是0的向量(上图编号3所示)。如果你的词典大小是10,000的话,那么这里的每个向量都是10,000维的。

因为a是字典第一个单词,x<7> 应a,那么这个向量的第一个位置为1,其余位置都是0的向量(上图编号4所示)。

所以这种表示方法中,x<t> 代句子里的任意词,它就是个one-hot向量,因为它只有一个值是1,其余值都是0,所以你会有9个one-hot向量来表示这个句中的9个单词,目的是用这样的表示方式表示X,用序列模型在X 目标输出Y 间学习建立一个映射。我会把它当作监督学习的问题,我确信会给定带有,(x,y) 签的数据。

那么还剩下最后一件事,我们将在之后的视频讨论,如果你遇到了一个不在你词表中的单词,答案就是创建一个新的标记,也就是一个叫做Unknow Word的伪造单词,用<UNK>作为标记,来表示不在词表中的单词,我们之后会讨论更多有关这个的内容。

Recurrent Neural Network Model

可以尝试的方法之一是使用标准神经网络,在我们之前的例子中,我们有9个输入单词。想象一下,把这9个输入单词,可能是9个one-hot向量,然后将它们输入到一个标准神经网络中,经过一些隐藏层,最终会输出9个值为0或1的项,它表明每个输入单词是否是人名的一部分。

但结果表明这个方法并不好,主要有两个问题,

一、是输入和输出数据在不同例子中可以有不同的长度,不是所有的例子都有着同样输入长度Tx 是同样输出长度的Ty。即使每个句子都有最大长度,也许你能够填充(pad)或零填充(zero pad)使每个输入语句都达到最大长度,但仍然看起来不是一个好的表达方式。

二、一个像这样单纯的神经网络结构,它并不共享从文本的不同位置上学到的特征。具体来说,如果神经网络已经学习到了在位置1出现的Harry可能是人名的一部分,那么如果Harry出现在其他位置,比如x<t> ,它也能够自动识别其为人名的一部分的话,这就很棒了。这可能类似于你在卷积神经网络中看到的,你希望将部分图片里学到的内容快速推广到图片的其他部分,而我们希望对序列数据也有相似的效果。和你在卷积网络中学到的类似,用一个更好的表达方式也能够让你减少模型中参数的数量。

之前我们提到过这些(上图编号1所示的x<1>……x<t>……x<Tx>)都是10,000维的one-hot向量,因此这会是十分庞大的输入层。如果总的输入大小是最大单词数乘以10,000,那么第一层的权重矩阵就会有着巨量的参数。但循环神经网络就没有上述的两个问题。

那么什么是循环神经网络呢?我们先建立一个(下图编号1所示)。如果你以从左到右的顺序读这个句子,第一个单词就是,假如说是x<1>,我们要做的就是将第一个词输入一个神经网络层,我打算这样画,第一个神经网络的隐藏层,我们可以让神经网络尝试预测输出,判断这是否是人名的一部分。循环神经网络做的是,当它读到句中的第二个单词时,假设是x<2>,它不是仅用x<2> 预测出y^<2>,他也会输入一些来自时间步1的信息。具体而言,时间步1的激活值就会传递到时间步2。然后,在下一个时间步,循环神经网络输入了单词x<3>,然后它尝试预测输出了预测结果y^<3>,等等,一直到最后一个时间步,输入了x<Tx>,然后输出了y^<Ty>。至少在这个例子中Tx=Ty,同时如果Tx Ty 相同,这个结构会需要作出一些改变。所以在每一个时间步中,循环神经网络传递一个激活值到下一个时间步中用于计算。

要开始整个流程,在零时刻需要构造一个激活值a<0>,这通常是零向量。有些研究人员会随机用其他方法初始化a<0>,不过使用零向量作为零时刻的伪激活值是最常见的选择,因此我们把它输入神经网络。

在一些研究论文中或是一些书中你会看到这类神经网络,用这样的图形来表示(上图编号2所示),在每一个时间步中,你输入x<t> 后输出y<t>。然后为了表示循环连接有时人们会像这样画个圈,表示输回网络层,有时他们会画一个黑色方块,来表示在这个黑色方块处会延迟一个时间步。我个人认为这些循环图很难理解,所以在本次课程中,我画图更倾向于使用左边这种分布画法(上图编号1所示)。不过如果你在教材中或是研究论文中看到了右边这种图表的画法(上图编号2所示),它可以在心中将这图展开成左图那样。

循环神经网络是从左向右扫描数据,同时每个时间步的参数也是共享的,所以下页幻灯片中我们会详细讲述它的一套参数,我们用Wax 表示管理着从x<1> 隐藏层的连接的一系列参数,每个时间步使用的都是相同的参数Wax。而激活值也就是水平联系是由参数Waa 定的,同时每一个时间步都使用相同的参数Waa,同样的输出结果由Wya 定。下图详细讲述这些参数是如何起作用。

在这个循环神经网络中,它的意思是在预测y^<3> ,不仅要使用x<3> 信息,还要使用来自x<1> x<2> 信息,因为来自x<1> 信息可以通过这样的路径(上图编号1所示的路径)来帮助预测y^<3>。这个循环神经网络的一个缺点就是它只使用了这个序列中之前的信息来做出预测,尤其当预测y^<3> ,它没有用到x<4>,x<5>,x<6> 等的信息。所以这就有一个问题,因为如果给定了这个句子,“Teddy Roosevelt was a great President.”,为了判断Teddy是否是人名的一部分,仅仅知道句中前两个词是完全不够的,还需要知道句中后部分的信息,这也是十分有用的,因为句子也可能是这样的,“Teddy bears are on sale!”。因此如果只给定前三个单词,是不可能确切地知道Teddy是否是人名的一部分,第一个例子是人名,第二个例子就不是,所以你不可能只看前三个单词就能分辨出其中的区别。

所以这样特定的神经网络结构的一个限制是它在某一时刻的预测仅使用了从序列之前的输入信息并没有使用序列中后部分的信息,我们会在之后的双向循环神经网络(BRNN)的视频中处理这个问题。但对于现在,这个更简单的单向神经网络结构就够我们来解释关键概念了,之后只要在此基础上作出修改就能同时使用序列中前面和后面的信息来预测y^<3>,不过我们会在之后的视频讲述这些内容,接下来我们具体地写出这个神经网络计算了些什么。

这里是一张清理后的神经网络示意图,和我之前提及的一样,一般开始先输入a<0>,它是一个零向量。接着就是前向传播过程,先计算激活值a<1>,然后再计算y<1>。

a<1>=g1(Waaa<0>+Waxx<1>+ba)

y^<1>=g2(Wyaa<1>+by)

我将用这样的符号约定来表示这些矩阵下标,举个例子Wax,第二个下标意味着Wax 乘以某个x 型的量,然后第一个下标a 示它是用来计算某个a 型的变量。同样的,可以看出这里的Wya 上了某个a 型的量,用来计算出某个y^ 型的量。

循环神经网络用的激活函数经常是tanh,不过有时候也会用ReLU,但是tanh是更通常的选择,我们有其他方法来避免梯度消失问题,我们将在之后进行讲述。选用哪个激活函数是取决于你的输出y,如果它是一个二分问题,那么我猜你会用sigmoid函数作为激活函数,如果是k 别分类问题的话,那么可以选用softmax作为激活函数。不过这里激活函数的类型取决于你有什么样类型的输出y,对于命名实体识别来说y 可能是0或者1,那我猜这里第二个激活函数g 以是sigmoid激活函数。

更一般的情况下,在t 刻,

a<t>=g1(Waaa<t−1>+Waxx<t>+ba)

y^<t>=g2(Wyaa<t>+by)

所以这些等式定义了神经网络的前向传播,你可以从零向量a<0> 始,然后用a<0> x<1> 计算出a<1> y^<1>,然后用x<2> a<1> 起算出a<2> y^<2> 等,像图中这样,从左到右完成前向传播。

现在为了帮我们建立更复杂的神经网络,我实际要将这个符号简化一下,我在下一张幻灯片里复制了这两个等式(上图编号1所示的两个等式)。

接下来为了简化这些符号,我要将这部分(Waaa<t−1>+Waxx<t>)(上图编号1所示)以更简单的形式写出来,我把它写做a<t>=g(Wa[a<t−1>,x<t>]+ba)(上图编号2所示),那么左右两边划线部分应该是等价的。所以我们定义Wa 方式是将矩阵Waa 矩阵Wax 平并列放置,[Waa⋮Wax]=Wa(上图编号3所示)。举个例子,如果a 100维的,然后延续之前的例子,x 10,000维的,那么Waa 是个(,)(100,100) 的矩阵,Wax 是个(,)(100,10,000) 的矩阵,因此如果将这两个矩阵堆起来,Wa 会是个(,)(100,10,100) 的矩阵。

用这个符号([a<t−1>,x<t>])的意思是将这两个向量堆在一起,我会用这个符号表示,即[a<t−1>x<t>](上图编号4所示),最终这就是个10,100维的向量。你可以自己检查一下,用这个矩阵乘以这个向量,刚好能够得到原来的量,因为此时,矩阵[Waa⋮Wax] 以[a<t−1>x<t>],刚好等于Waaa<t−1>+Waxx<t>,刚好等于之前的这个结论(上图编号5所示)。这种记法的好处是我们可以不使用两个参数矩阵Waa Wax,而是将其压缩成一个参数矩阵Wa,所以当我们建立更复杂模型时这就能够简化我们要用到的符号。

同样对于这个例子(y^<t>=g(Wyaa<t>+by)),我会用更简单的方式重写,y^<t>=g(Wya<t>+by)(上图编号6所示)。现在Wy by 号仅有一个下标,它表示在计算时会输出什么类型的量,所以Wy 表明它是计算y 型的量的权重矩阵,而上面的Wa ba 表示这些参数是用来计算a 型或者说是激活值的。

RNN前向传播示意图:

nn-

好就这么多,你现在知道了基本的循环神经网络,下节课我们会一起来讨论反向传播,以及你如何能够用RNN进行学习。

Backpropagation through time

之前我们已经学过了循环神经网络的基础结构,在本节视频中我们将来了解反向传播是怎样在循环神经网络中运行的。和之前一样,当你在编程框架中实现循环神经网络时,编程框架通常会自动处理反向传播。但我认为,在循环神经网络中,对反向传播的运行有一个粗略的认识还是非常有用的,让我们来一探究竟。

在之前你已经见过对于前向传播(上图蓝色箭头所指方向)怎样在神经网络中从左到右地计算这些激活项,直到输出所有地预测结果。而对于反向传播,我想你已经猜到了,反向传播地计算方向(上图红色箭头所指方向)与前向传播基本上是相反的。

我们来分析一下前向传播的计算,现在你有一个输入序列,x<1>,x<2>,x<3> 直到x<Tx>,然后用x<1> 有a<0> 算出时间步1的激活项,再用x<2> a<1> 算出a<2>,然后计算a<3> 等,一直到a<Tx>。

为了真正计算出a<1>,你还需要一些参数,Wa ba,用它们来计算出a<1>。这些参数在之后的每一个时间步都会被用到,于是继续用这些参数计算a<2>,a<3> 等,所有的这些激活项都要取决于参数Wa ba。有了a<1>,神经网络就可以计算第一个预测值y^<1>,接着到下一个时间步,继续计算出y^<2>,y^<3>,等等,一直到y^<Ty>。为了计算出y^,需要参数Wy by,它们将被用于所有这些节点。

然后为了计算反向传播,你还需要一个损失函数。我们先定义一个元素损失函数(上图编号1所示)

L<t>(y^<t>,y<t>)=−y<t>log⁡y^<t>−(1−y<t>)log(1−y^<t>)

它对应的是序列中一个具体的词,如果它是某个人的名字,那么y<t> 值就是1,然后神经网络将输出这个词是名字的概率值,比如0.1。我将它定义为标准逻辑回归损失函数,也叫交叉熵损失函数(Cross Entropy Loss),它和之前我们在二分类问题中看到的公式很像。所以这是关于单个位置上或者说某个时间步t 某个单词的预测值的损失函数。

现在我们来定义整个序列的损失函数,将L 义为(上图编号2所示)

L(y^,y)=∑t=1TxL<t>(y^<t>,y<t>)

在这个计算图中,通过y^<1> 以计算对应的损失函数,于是计算出第一个时间步的损失函数(上图编号3所示),然后计算出第二个时间步的损失函数,然后是第三个时间步,一直到最后一个时间步,最后为了计算出总体损失函数,我们要把它们都加起来,通过下面的等式(上图编号2所示的等式)计算出最后的L(上图编号4所示),也就是把每个单独时间步的损失函数都加起来。

这就是完整的计算图,在之前的例子中,你已经见过反向传播,所以你应该能够想得到反向传播算法需要在相反的方向上进行计算和传递信息,最终你做的就是把前向传播的箭头都反过来,在这之后你就可以计算出所有合适的量,然后你就可以通过导数相关的参数,用梯度下降法来更新参数。

在这个反向传播的过程中,最重要的信息传递或者说最重要的递归运算就是这个从右到左的运算,这也就是为什么这个算法有一个很别致的名字,叫做**“通过(穿越)时间反向传播**(backpropagation through time)”。取这个名字的原因是对于前向传播,你需要从左到右进行计算,在这个过程中,时刻t 断增加。而对于反向传播,你需要从右到左进行计算,就像时间倒流。“通过时间反向传播”,就像穿越时光,这种说法听起来就像是你需要一台时光机来实现这个算法一样。

RNN反向传播示意图:

nn_cell_backpro

希望你大致了解了前向和反向传播是如何在RNN中工作的,到目前为止,你只见到了RNN中一个主要的例子,其中输入序列的长度和输出序列的长度是一样的。在下节课将展示更多的RNN架构,这将让你能够处理一些更广泛的应用。

Different types of RNNs

现在你已经了解了一种RNN结构,它的输入量Tx 于输出数量Ty。事实上,对于其他一些应用,Tx Ty 不一定相等。在这个视频里,你会看到更多的RNN的结构。

你应该还记得这周第一个视频中的那个幻灯片,那里有很多例子输入x 输出y,有各种类型,并不是所有的情况都满足Tx=Ty。

比如音乐生成这个例子,Tx 以是长度为1甚至为空集。再比如电影情感分类,输出y 以是1到5的整数,而输入是一个序列。在命名实体识别中,这个例子中输入长度和输出长度是一样的。

还有一些情况,输入长度和输出长度不同,他们都是序列但长度不同,比如机器翻译,一个法语句子和一个英语句子不同数量的单词却能表达同一个意思。

所以我们应该修改基本的RNN结构来处理这些问题,这个视频的内容参考了Andrej Karpathy的博客,一篇叫做《循环神经网络的非理性效果》(“The Unreasonable Effectiveness of Recurrent Neural Networks”)的文章,我们看一些例子。

你已经见过Tx=Ty 例子了(下图编号1所示),也就是我们输入序列x<1>,x<2>,一直到x<Tx>,我们的循环神经网络这样工作,输入x<1> 计算y^<1>,y^<2> 等一直到y^<Ty>。在原先的图里,我会画一串圆圈表示神经元,大部分时候为了让符号更加简单,此处就以简单的小圈表示。这个就叫做“多对多”(many-to-many)的结构,因为输入序列有很多的输入,而输出序列也有很多输出。

现在我们看另外一个例子,假如说,你想处理情感分类问题(下图编号2所示),这里x 能是一段文本,比如一个电影的评论,“These is nothing to like in this movie.”(“这部电影没什么还看的。”),所以x 是一个序列,而y 能是从1到5的一个数字,或者是0或1,这代表正面评价和负面评价,而数字1到5代表电影是1星,2星,3星,4星还是5星。所以在这个例子中,我们可以简化神经网络的结构,输入x<1>,x<2>,一次输入一个单词,如果输入文本是“These is nothing to like in this movie”,那么单词的对应如下图编号2所示。我们不再在每个时间上都有输出了,而是让这个RNN网络读入整个句子,然后在最后一个时间上得到输出,这样输入的就是整个句子,所以这个神经网络叫做“多对一”(many-to-one)结构,因为它有很多输入,很多的单词,然后输出一个数字。

为了完整性,还要补充一个“一对一”(one-to-one)的结构(上图编号3所示),这个可能没有那么重要,这就是一个小型的标准的神经网络,输入x 后得到输出y,我们这个系列课程的前两个课程已经讨论过这种类型的神经网络了。

除了“多对一”的结构,也可以有“一对多”(one-to-many)的结构。对于一个“一对多”神经网络结构的例子就是音乐生成(上图编号1所示),事实上,你会在这个课后编程练习中去实现这样的模型,你的目标是使用一个神经网络输出一些音符。对应于一段音乐,输入x 以是一个整数,表示你想要的音乐类型或者是你想要的音乐的第一个音符,并且如果你什么都不想输入,x 以是空的输入,可设为0向量。

这样这个神经网络的结构,首先是你的输入x,然后得到RNN的输出,第一个值,然后就没有输入了,再得到第二个输出,接着输出第三个值等等,一直到合成这个音乐作品的最后一个音符,这里也可以写上输入a<0>(上图编号3所示)。有一个后面才会讲到的技术细节,当你生成序列时通常会把第一个合成的输出也喂给下一层(上图编号4所示),所以实际的网络结构最终就像这个样子。

我们已经讨论了“多对多”、“多对一”、“一对一”和“一对多”的结构,对于“多对多”的结构还有一个有趣的例子值得详细说一下,就是输入和输出长度不同的情况。你刚才看过的多对多的例子,它的输入长度和输出长度是完全一样的。而对于像机器翻译这样的应用,输入句子的单词的数量,比如说一个法语的句子,和输出句子的单词数量,比如翻译成英语,这两个句子的长度可能不同,所以还需要一个新的网络结构,一个不同的神经网络(上图编号2所示)。首先读入这个句子,读入这个输入,比如你要将法语翻译成英语,读完之后,这个网络就会输出翻译结果。有了这种结构Tx Ty 可以是不同的长度了。同样,你也可以画上这个a<0>。这个网络的结构有两个不同的部分,这(上图编号5所示)是一个编码器,获取输入,比如法语句子,这(上图编号6所示)是解码器,它会读取整个句子,然后输出翻译成其他语言的结果。

这就是一个“多对多”结构的例子,到这周结束的时候,你就能对这些各种各样结构的基本构件有一个很好的理解。严格来说,还有一种结构,我们会在第四周涉及到,就是“注意力”(attention based)结构,但是根据我们现在画的这些图不好理解这个模型。

总结一下这些各种各样的RNN结构,这(上图编号1所示)是“一对一”的结构,当去掉a<0> 它就是一种标准类型的神经网络。还有一种“一对多”的结构(上图编号2所示),比如音乐生成或者序列生成。还有“多对一”,这(上图编号3所示)是情感分类的例子,首先读取输入,一个电影评论的文本,然后判断他们是否喜欢电影还是不喜欢。还有“多对多”的结构(上图编号4所示),命名实体识别就是“多对多”的例子,其中Tx=Ty。最后还有一种“多对多”结构的其他版本(上图编号5所示),对于像机器翻译这样的应用,Tx Ty 可以不同了。

现在,你已经了解了大部分基本的模块,这些就是差不多所有的神经网络了,除了序列生成,有些细节的问题我们会在下节课讲解。

我希望你从本视频中了解到用这些RNN的基本模块,把它们组合在一起就可以构建各种各样的模型。但是正如我前面提到的,序列生成还有一些不一样的地方,在这周的练习里,你也会实现它,你需要构建一个语言模型,结果好的话会得到一些有趣的序列或者有意思的文本。下节课深入探讨序列生成。

Language model and sequence generation

在自然语言处理中,构建语言模型是最基础的也是最重要的工作之一,并且能用RNN很好地实现。在本视频中,你将学习用RNN构建一个语言模型,在本周结束的时候,还会有一个很有趣的编程练习,你能在练习中构建一个语言模型,并用它来生成莎士比亚文风的文本或其他类型文本。

所以什么是语言模型呢?比如你在做一个语音识别系统,你听到一个句子,“the apple and pear(pair) salad was delicious.”,所以我究竟说了什么?我说的是 “the apple and pair salad”,还是“the apple and pear salad”?(pear和pair是近音词)。你可能觉得我说的应该更像第二种,事实上,这就是一个好的语音识别系统要帮助输出的东西,即使这两句话听起来是如此相似。而让语音识别系统去选择第二个句子的方法就是使用一个语言模型,他能计算出这两句话各自的可能性。

举个例子,一个语音识别模型可能算出第一句话的概率是P(The apple and pair salad)=3.2×10−13,而第二句话的概率是P(The apple and pear salad)=5.7×10−10,比较这两个概率值,显然我说的话更像是第二种,因为第二句话的概率比第一句高出1000倍以上,这就是为什么语音识别系统能够在这两句话中作出选择。

所以语言模型所做的就是,它会告诉你某个特定的句子它出现的概率是多少,根据我所说的这个概率,假设你随机拿起一张报纸,打开任意邮件,或者任意网页或者听某人说下一句话,并且这个人是你的朋友,这个你即将从世界上的某个地方得到的句子会是某个特定句子的概率是多少,例如“the apple and pear salad”。它是两种系统的基本组成部分,一个刚才所说的语音识别系统,还有机器翻译系统,它要能正确输出最接近的句子。而语言模型做的最基本工作就是输入一个句子,准确地说是一个文本序列,y<1>,y<2> 直到y<Ty>。对于语言模型来说,用y 表示这些序列比用x 表示要更好,然后语言模型会估计某个句子序列中各个单词出现的可能性。

那么如何建立一个语言模型呢?为了使用RNN建立出这样的模型,你首先需要一个训练集,包含一个很大的英文文本语料库(corpus)或者其它的语言,你想用于构建模型的语言的语料库。语料库是自然语言处理的一个专有名词,意思就是很长的或者说数量众多的英文句子组成的文本。

假如说,你在训练集中得到这么一句话,“Cats average 15 hours of sleep a day.”(猫一天睡15小时),你要做的第一件事就是将这个句子标记化,意思就是像之前视频中一样,建立一个字典,然后将每个单词都转换成对应的one-hot向量,也就是字典中的索引。可能还有一件事就是你要定义句子的结尾,一般的做法就是增加一个额外的标记,叫做EOS(上图编号1所示),它表示句子的结尾,这样能够帮助你搞清楚一个句子什么时候结束,我们之后会详细讨论这个。EOS标记可以被附加到训练集中每一个句子的结尾,如果你想要你的模型能够准确识别句子结尾的话。在本周的练习中我们不需要使用这个EOS标记,不过在某些应用中你可能会用到它,不过稍后就能见到它的用处。于是在本例中我们,如果你加了EOS标记,这句话就会有9个输入,有y<1>,y<2> 直到y<9>。在标记化的过程中,你可以自行决定要不要把标点符号看成标记,在本例中,我们忽略了标点符号,所以我们只把day看成标志,不包括后面的句号,如果你想把句号或者其他符号也当作标志,那么你可以将句号也加入你的字典中。

现在还有一个问题如果你的训练集中有一些词并不在你的字典里,比如说你的字典有10,000个词,10,000个最常用的英语单词。现在这个句,“The Egyptian Mau is a bread of cat.”其中有一个词Mau,它可能并不是预先的那10,000个最常用的单词,在这种情况下,你可以把Mau替换成一个叫做UNK的代表未知词的标志,我们只针对UNK建立概率模型,而不是针对这个具体的词Mau。

完成标识化的过程后,这意味着输入的句子都映射到了各个标志上,或者说字典中的各个词上。下一步我们要构建一个RNN来构建这些序列的概率模型。在下一张幻灯片中会看到的一件事就是最后你会将x<t> 为y<t−1>。

现在我们来建立RNN模型,我们继续使用“Cats average 15 hours of sleep a day.”这个句子来作为我们的运行样例,我将会画出一个RNN结构。在第0个时间步,你要计算激活项a<1>,它是以x<1> 为输入的函数,而x<1> 被设为全为0的集合,也就是0向量。在之前的a<0> 照惯例也设为0向量,于是a<1> 做的就是它会通过softmax进行一些预测来计算出第一个词可能会是什么,其结果就是y^<1>(上图编号1所示),这一步其实就是通过一个softmax层来预测字典中的任意单词会是第一个词的概率,比如说第一个词是a 概率有多少,第一个词是Aaron的概率有多少,第一个词是cats的概率又有多少,就这样一直到Zulu是第一个词的概率是多少,还有第一个词是UNK(未知词)的概率有多少,还有第一个词是句子结尾标志的概率有多少,表示不必阅读。所以y^<1> 输出是softmax的计算结果,它只是预测第一个词的概率,而不去管结果是什么。在我们的例子中,最终会得到单词Cats。所以softmax层输出10,000种结果,因为你的字典中有10,000个词,或者会有10,002个结果,因为你可能加上了未知词,还有句子结尾这两个额外的标志。

然后RNN进入下个时间步,在下一时间步中,仍然使用激活项a<1>,在这步要做的是计算出第二个词会是什么。现在我们依然传给它正确的第一个词,我们会告诉它第一个词就是Cats,也就是y^<1>,告诉它第一个词就是Cats,这就是为什么y<1>=x<2>(上图编号2所示)。然后在第二个时间步中,输出结果同样经过softmax层进行预测,RNN的职责就是预测这些词的概率(上图编号3所示),而不会去管结果是什么,可能是b或者arron,可能是Cats或者Zulu或者UNK(未知词)或者EOS或者其他词,它只会考虑之前得到的词。所以在这种情况下,我猜正确答案会是average,因为句子确实就是Cats average开头的。

然后再进行RNN的下个时间步,现在要计算a<3>。为了预测第三个词,也就是15,我们现在给它之前两个词,告诉它Cats average是句子的前两个词,所以这是下一个输入,x<3>=y<2>,输入average以后,现在要计算出序列中下一个词是什么,或者说计算出字典中每一个词的概率(上图编号4所示),通过之前得到的Cats和average,在这种情况下,正确结果会是15,以此类推。

一直到最后,没猜错的话,你会停在第9个时间步,然后把x<9> 就是y<8> 给它(上图编号5所示),也就是单词day,这里是a<9>,它会输出y<9>,最后的得到结果会是EOS标志,在这一步中,通过前面这些得到的单词,不管它们是什么,我们希望能预测出EOS句子结尾标志的概率会很高(上图编号6所示)。

所以RNN中的每一步都会考虑前面得到的单词,比如给它前3个单词(上图编号7所示),让它给出下个词的分布,这就是RNN如何学习从左往右地每次预测一个词。

接下来为了训练这个网络,我们要定义代价函数。于是,在某个时间步t,如果真正的词是y<t>,而神经网络的softmax层预测结果值是y<t>,那么这(上图编号8所示)就是softmax损失函数,L(y^<t>,y<t>>)=−∑iyi<t>log⁡y^i<t>。而总体损失函数(上图编号9所示)L=∑tL<t>(y^<t>,y<t>),也就是把所有单个预测的损失函数都相加起来。

如果你用很大的训练集来训练这个RNN,你就可以通过开头一系列单词像是Cars average 15或者Cars average 15 hours of来预测之后单词的概率。现在有一个新句子,它是y<1>,y<2>,y<3>,为了简单起见,它只包含3个词(如上图所示),现在要计算出整个句子中各个单词的概率,方法就是第一个softmax层会告诉你y<1> 概率(上图编号1所示),这也是第一个输出,然后第二个softmax层会告诉你在考虑y<1> 情况下y<2> 概率(上图编号2所示),然后第三个softmax层告诉你在考虑y<1> y<2> 情况下y<3> 概率(上图编号3所示),把这三个概率相乘,最后得到这个含3个词的整个句子的概率。

Sampling novel sequences

在你训练一个序列模型之后,要想了解到这个模型学到了什么,一种非正式的方法就是进行一次新序列采样,来看看到底应该怎么做。

记住一个序列模型模拟了任意特定单词序列的概率,我们要做的就是对这些概率分布进行采样来生成一个新的单词序列。下图编号1所示的网络已经被上方所展示的结构训练过了,而为了进行采样(下图编号2所示的网络),你要做一些截然不同的事情。

第一步要做的就是对你想要模型生成的第一个词进行采样,于是你输入x<1>=0,a<0>=0,现在你的第一个时间步得到的是所有可能的输出是经过softmax层后得到的概率,然后根据这个softmax的分布进行随机采样。Softmax分布给你的信息就是第一个词a的概率是多少,第一个词是aaron的概率是多少,第一个词是zulu的概率是多少,还有第一个词是UNK(未知标识)的概率是多少,这个标识可能代表句子的结尾,然后对这个向量使用例如numpy命令,np.random.choice(上图编号3所示),来根据向量中这些概率的分布进行采样,这样就能对第一个词进行采样了。

然后继续下一个时间步,记住第二个时间步需要y^<1> 为输入,而现在要做的是把刚刚采样得到的y^<1> 到a<2>(上图编号4所示),作为下一个时间步的输入,所以不管你在第一个时间步得到的是什么词,都要把它传递到下一个位置作为输入,然后softmax层就会预测y^<2> 什么。举个例子,假如说对第一个词进行抽样后,得到的是The,The作为第一个词的情况很常见,然后把The当成x<2>,现在x<2> 是y^<1>,现在你要计算出在第一词是The的情况下,第二个词应该是什么(上图编号5所示),然后得到的结果就是y^<2>,然后再次用这个采样函数来对y^<2> 行采样。

然后再到下一个时间步,无论你得到什么样的用one-hot码表示的选择结果,都把它传递到下一个时间步,然后对第三个词进行采样。不管得到什么都把它传递下去,一直这样直到最后一个时间步。

那么你要怎样知道一个句子结束了呢?方法之一就是,如果代表句子结尾的标识在你的字典中,你可以一直进行采样直到得到EOS标识(上图编号6所示),这代表着已经抵达结尾,可以停止采样了。另一种情况是,如果你的字典中没有这个词,你可以决定从20个或100个或其他个单词进行采样,然后一直将采样进行下去直到达到所设定的时间步。不过这种过程有时候会产生一些未知标识(上图编号7所示),如果你要确保你的算法不会输出这种标识,你能做的一件事就是拒绝采样过程中产生任何未知的标识,一旦出现就继续在剩下的词中进行重采样,直到得到一个不是未知标识的词。如果你不介意有未知标识产生的话,你也可以完全不管它们。

这就是你如何从你的RNN语言模型中生成一个随机选择的句子。直到现在我们所建立的是基于词汇的RNN模型,意思就是字典中的词都是英语单词(下图编号1所示)。

根据你实际的应用,你还可以构建一个基于字符的RNN结构,在这种情况下,你的字典仅包含从a到z的字母,可能还会有空格符,如果你需要的话,还可以有数字0到9,如果你想区分字母大小写,你可以再加上大写的字母,你还可以实际地看一看训练集中可能会出现的字符,然后用这些字符组成你的字典(上图编号2所示)。

如果你建立一个基于字符的语言模型,比起基于词汇的语言模型,你的序列y^<1>,y^<2>,y^<3> 你的训练数据中将会是单独的字符,而不是单独的词汇。所以对于前面的例子来说,那个句子(上图编号3所示),“Cats average 15 hours of sleep a day.”,在该例中C就是y^<1>,a就是y^<2>,t就是y^<3>,空格符就是y^<4> 等。

使用基于字符的语言模型有有点也有缺点,优点就是你不必担心会出现未知的标识,例如基于字符的语言模型会将Mau这样的序列也视为可能性非零的序列。而对于基于词汇的语言模型,如果Mau不在字典中,你只能把它当作未知标识UNK。不过基于字符的语言模型一个主要缺点就是你最后会得到太多太长的序列,大多数英语句子只有10到20个的单词,但却可能包含很多很多字符。所以基于字符的语言模型在捕捉句子中的依赖关系也就是句子较前部分如何影响较后部分不如基于词汇的语言模型那样可以捕捉长范围的关系,并且基于字符的语言模型训练起来计算成本比较高昂。所以我见到的自然语言处理的趋势就是,绝大多数都是使用基于词汇的语言模型,但随着计算机性能越来越高,会有更多的应用。在一些特殊情况下,会开始使用基于字符的模型。但是这确实需要更昂贵的计算力来训练,所以现在并没有得到广泛地使用,除了一些比较专门需要处理大量未知的文本或者未知词汇的应用,还有一些要面对很多专有词汇的应用。

在现有的方法下,现在你可以构建一个RNN结构,看一看英文文本的语料库,然后建立一个基于词汇的或者基于字符的语言模型,然后从训练的语言模型中进行采样。

这里有一些样本,它们是从一个语言模型中采样得到的,准确来说是基于字符的语言模型,你可以在编程练习中自己实现这样的模型。如果模型是用新闻文章训练的,它就会生成左边这样的文本,这有点像一篇不太合乎语法的新闻文本,不过听起来,这句“Concussion epidemic”,to be examined,确实有点像新闻报道。用莎士比亚的文章训练后生成了右边这篇东西,听起来很像是莎士比亚写的东西:

“The mortal moon hath her eclipse in love.

And subject of this thou art another this fold.

When besser be my love to me see sabl's.

For whose are ruse of mine eyes heaves.”

这些就是基础的RNN结构和如何去建立一个语言模型并使用它,对于训练出的语言模型进行采样。在之后的视频中,我想探讨在训练RNN时一些更加深入的挑战以及如何适应这些挑战,特别是梯度消失问题来建立更加强大的RNN模型。下节课,我们将谈到梯度消失并且会开始谈到GRU,也就是门控循环单元和LSTM长期记忆网络模型。

Vanishing gradients with RNNs

你已经了解了RNN时如何工作的了,并且知道如何应用到具体问题上,比如命名实体识别,比如语言模型,你也看到了怎么把反向传播用于RNN。其实,基本的RNN算法还有一个很大的问题,就是梯度消失的问题。这节课我们会讨论,在下几节课我们会讨论一些方法用来解决这个问题。

你已经知道了RNN的样子,现在我们举个语言模型的例子,假如看到这个句子(上图编号1所示),“The cat, which already ate ……, was full.”,前后应该保持一致,因为cat是单数,所以应该用was。“The cats, which ate ……, were full.”(上图编号2所示),cats是复数,所以用were。这个例子中的句子有长期的依赖,最前面的单词对句子后面的单词有影响。但是我们目前见到的基本的RNN模型(上图编号3所示的网络模型),不擅长捕获这种长期依赖效应,解释一下为什么。

你应该还记得之前讨论的训练很深的网络,我们讨论了梯度消失的问题。比如说一个很深很深的网络(上图编号4所示),100层,甚至更深,对这个网络从左到右做前向传播然后再反向传播。我们知道如果这是个很深的神经网络,从输出y^ 到的梯度很难传播回去,很难影响靠前层的权重,很难影响前面层(编号5所示的层)的计算。

对于有同样问题的RNN,首先从左到右前向传播,然后反向传播。但是反向传播会很困难,因为同样的梯度消失的问题,后面层的输出误差(上图编号6所示)很难影响前面层(上图编号7所示的层)的计算。这就意味着,实际上很难让一个神经网络能够意识到它要记住看到的是单数名词还是复数名词,然后在序列后面生成依赖单复数形式的was或者were。而且在英语里面,这中间的内容(上图编号8所示)可以任意长,对吧?所以你需要长时间记住单词是单数还是复数,这样后面的句子才能用到这些信息。也正是这个原因,所以基本的RNN模型会有很多局部影响,意味着这个输出y^<3>(上图编号9所示)主要受y^<3> 近的值(上图编号10所示)的影响,上图编号11所示的一个数值主要与附近的输入(上图编号12所示)有关,上图编号6所示的输出,基本上很难受到序列靠前的输入(上图编号10所示)的影响,这是因为不管输出是什么,不管是对的,还是错的,这个区域都很难反向传播到序列的前面部分,也因此网络很难调整序列前面的计算。这是基本的RNN算法的一个缺点,我们会在下几节视频里处理这个问题。如果不管的话,RNN会不擅长处理长期依赖的问题。

尽管我们一直在讨论梯度消失问题,但是,你应该记得我们在讲很深的神经网络时,我们也提到了梯度爆炸,我们在反向传播的时候,随着层数的增多,梯度不仅可能指数型的下降,也可能指数型的上升。事实上梯度消失在训练RNN时是首要的问题,尽管梯度爆炸也是会出现,但是梯度爆炸很明显,因为指数级大的梯度会让你的参数变得极其大,以至于你的网络参数崩溃。所以梯度爆炸很容易发现,因为参数会大到崩溃,你会看到很多NaN,或者不是数字的情况,这意味着你的网络计算出现了数值溢出。如果你发现了梯度爆炸的问题,一个解决方法就是用梯度修剪。梯度修剪的意思就是观察你的梯度向量,如果它大于某个阈值,缩放梯度向量,保证它不会太大,这就是通过一些最大值来修剪的方法。所以如果你遇到了梯度爆炸,如果导数值很大,或者出现了NaN,就用梯度修剪,这是相对比较鲁棒的,这是梯度爆炸的解决方法。然而梯度消失更难解决,这也是我们下几节视频的主题。

总结一下,在前面的课程,我们了解了训练很深的神经网络时,随着层数的增加,导数有可能指数型的下降或者指数型的增加,我们可能会遇到梯度消失或者梯度爆炸的问题。加入一个RNN处理1,000个时间序列的数据集或者10,000个时间序列的数据集,这就是一个1,000层或者10,000层的神经网络,这样的网络就会遇到上述类型的问题。梯度爆炸基本上用梯度修剪就可以应对,但梯度消失比较棘手。我们下节会介绍GRU,门控循环单元网络,这个网络可以有效地解决梯度消失的问题,并且能够使你的神经网络捕获更长的长期依赖,我们去下个视频一探究竟吧。

Gated Recurrent Unit(GRU)

你已经了解了基础的RNN模型的运行机制,在本节视频中你将会学习门控循环单元,它改变了RNN的隐藏层,使其可以更好地捕捉深层连接,并改善了梯度消失问题,让我们看一看。

你已经见过了这个公式,a<t>=g(Wa[a<t−1>,x<t>]+ba),在RNN的时间t ,计算激活值。我把这个画个图,把RNN的单元画个图,画一个方框,输入a<t−1>(上图编号1所示),即上一个时间步的激活值,再输入x<t>(上图编号2所示),再把这两个并起来,然后乘上权重项,在这个线性计算之后(上图编号3所示),如果g 一个tanh激活函数,再经过tanh计算之后,它会计算出激活值a<t>。然后激活值a<t> 会传softmax单元(上图编号4所示),或者其他用于产生输出y<t> 东西。就这张图而言,这就是RNN隐藏层的单元的可视化呈现。我展示这张图,因为我们将使用相似的图来讲解门控循环单元。

1521560729

许多GRU的想法都来分别自于Yu Young Chang, Kagawa,Gaza Hera, Chang Hung Chu和Jose Banjo的两篇论文。我再引用上个视频中你已经见过的这个句子,“The cat, which already ate……, was full.”,你需要记得猫是单数的,为了确保你已经理解了为什么这里是was而不是were,“The cat was full.”或者是“The cats were full”。当我们从左到右读这个句子,GRU单元将会有个新的变量称为c,代表细胞(cell),即记忆细胞(下图编号1所示)。记忆细胞的作用是提供了记忆的能力,比如说一只猫是单数还是复数,所以当它看到之后的句子的时候,它仍能够判断句子的主语是单数还是复数。于是在时间t ,有记忆细胞c<t>,然后我们看的是,GRU实际上输出了激活值a<t>,c<t>=a<t>(下图编号2所示)。于是我们想要使用不同的符号c a 表示记忆细胞的值和输出的激活值,即使它们是一样的。我现在使用这个标记是因为当我们等会说到LSTMs的时候,这两个会是不同的值,但是现在对于GRU,c<t> 值等于a<t> 激活值。

所以这些等式表示了GRU单元的计算,在每个时间步,我们将用一个候选值重写记忆细胞,即c~<t> 值,所以它就是个候选值,替代了c<t> 值。然后我们用tanh激活函数来计算,c~<t>=tanh(Wc[c<t−1>,x<t>]+bc),所以c~<t> 值就是个替代值,代替表示c<t> 值(下图编号3所示)。

重点来了,在GRU中真正重要的思想是我们有一个门,我先把这个门叫做Γu(上图编号4所示),这是个下标为u 大写希腊字母Γ,u 表更新门,这是一个0到1之间的值。为了让你直观思考GRU的工作机制,先思考Γu,这个一直在0到1之间的门值,实际上这个值是把这个式子带入sigmoid函数得到的,Γu=σ(Wu[c<t−1>,x<t>]+bu)。我们还记得sigmoid函数是上图编号5所示这样的,它的输出值总是在0到1之间,对于大多数可能的输入,sigmoid函数的输出总是非常接近0或者非常接近1。在这样的直觉下,可以想到Γu 大多数的情况下非常接近0或1。然后这个字母u表示“update”,我选了字母Γ 因为它看起来像门。还有希腊字母G,G是门的首字母,所以G表示门。

然后GRU的关键部分就是上图编号3所示的等式,我们刚才写出来的用c~ 新c 等式。然后门决定是否要真的更新它。于是我们这么看待它,记忆细胞c<t> 被设定为0或者1,这取决于你考虑的单词在句子中是单数还是复数,因为这里是单数情况,所以我们先假定它被设为了1,或者如果是复数的情况我们就把它设为0。然后GRU单元将会一直记住c<t> 值,直到上图编号7所示的位置,c<t> 值还是1,这就告诉它,噢,这是单数,所以我们用was。于是门,即Γu 作用就是决定什么时候你会更新这个值,特别是当你看到词组the cat,即句子的主语猫,这就是一个好时机去更新这个值。然后当你使用完它的时候,“The cat, which already ate……, was full.”,然后你就知道,我不需要记住它了,我可以忘记它了。

所以我们接下来要给GRU用的式子就是c<t>=Γu∗c~<t>+(1−Γu)∗c<t−1>(上图编号1所示)。你应该注意到了,如果这个更新值Γu=1,也就是说把这个新值,即c<t> 为候选值(Γu=1 简化上式,c<t>=c~<t>)。将门值设为1(上图编号2所示),然后往前再更新这个值。对于所有在这中间的值,你应该把门的值设为0,即Γu=0,意思就是说不更新它,就用旧的值。因为如果Γu=0,则c<t>=c<t−1>,c<t> 于旧的值。甚至你从左到右扫描这个句子,当门值为0的时候(上图编号3所示,中间Γu=0 直为0,表示一直不更新),就是说不更新它的时候,不要更新它,就用旧的值,也不要忘记这个值是什么,这样即使你一直处理句子到上图编号4所示,c<t> 该会一直等c<t−1>,于是它仍然记得猫是单数的。

让我再画个图来(下图所示)解释一下GRU单元,顺便说一下,当你在看网络上的博客或者教科书或者教程之类的,这些图对于解释GRU和我们稍后会讲的LSTM是相当流行的,我个人感觉式子在图片中比较容易理解,那么即使看不懂图片也没关系,我就画画,万一能帮得上忙就最好了。

GRU单元输入c<t−1>(下图编号1所示),对于上一个时间步,先假设它正好等于a<t−1>,所以把这个作为输入。然后x<t> 作为输入(下图编号2所示),然后把这两个用合适权重结合在一起,再用tanh计算,算出c~<t>,c~<t>=tanh(Wc[c<t−1>,x<t>]+bc),即c<t> 替代值。

再用一个不同的参数集,通过sigmoid激活函数算出Γu,Γu=σ(Wu[c<t−1>,x<t>]+bu),即更新门。最后所有的值通过另一个运算符结合,我并不会写出公式,但是我用紫色阴影标注的这个方框(下图编号5所示,其所代表的运算过程即下图编号13所示的等式),代表了这个式子。所以这就是紫色运算符所表示的是,它输入一个门值(下图编号6所示),新的候选值(下图编号7所示),这再有一个门值(下图编号8所示)和c<t> 旧值(下图编号9所示),所以它把这个(下图编号1所示)、这个(下图编号3所示)和这个(下图编号4所示)作为输入一起产生记忆细胞的新值c<t>,所以c<t> 于a<t>。如果你想,你也可以也把这个代入softmax或者其他预测y<t> 东西。

这就是GRU单元或者说是一个简化过的GRU单元,它的优点就是通过门决定,当你从左(上图编号10所示)到右扫描一个句子的时候,这个时机是要更新某个记忆细胞,还是不更新,不更新(上图编号11所示,中间Γu=0 直为0,表示一直不更新)直到你到你真的需要使用记忆细胞的时候(上图编号12所示),这可能在句子之前就决定了。因为sigmoid的值,现在因为门很容易取到0值,只要这个值是一个很大的负数,再由于数值上的四舍五入,上面这些门大体上就是0,或者说非常非常非常接近0。所以在这样的情况下,这个更新式子(上图编号13所示的等式)就会变成c<t>=c<t−1>,这非常有利于维持细胞的值。因为Γu 接近0,可能是0.000001或者更小,这就不会有梯度消失的问题了。因为Γu 接近0,这就是说c<t> 乎就等于c<t−1>,而且c<t> 值也很好地被维持了,即使经过很多很多的时间步(上图编号14所示)。这就是缓解梯度消失问题的关键,因此允许神经网络运行在非常庞大的依赖词上,比如说cat和was单词即使被中间的很多单词分割开。

现在我想说下一些实现的细节,在这个我写下的式子中c<t> 以是一个向量(上图编号1所示),如果你有100维的隐藏的激活值,那么c<t> 是100维的,c~<t> 是相同的维度(c~<t>=tanh(Wc[c<t−1>,x<t>]+bc)),Γu 是相同的维度(Γu=σ(Wu[c<t−1>,x<t>]+bu)),还有画在框中的其他值。这样的话“*”实际上就是元素对应的乘积(c<t>=Γu∗c~<t>+(1−Γu)∗c<t−1>),所以这里的Γu:(Γu=σ(Wu[c<t−1>,x<t>]+bu)),即如果门是一个100维的向量,Γu 就100维的向量,里面的值几乎都是0或者1,就是说这100维的记忆细胞c<t>(c<t>=a<t> 图编号1所示)就是你要更新的比特。

当然在实际应用中Γu 会真的等于0或者1,有时候它是0到1的一个中间值(上图编号5所示),但是这对于直观思考是很方便的,就把它当成确切的0,完全确切的0或者就是确切的1。元素对应的乘积做的就是告诉GRU单元哪个记忆细胞的向量维度在每个时间步要做更新,所以你可以选择保存一些比特不变,而去更新其他的比特。比如说你可能需要一个比特来记忆猫是单数还是复数,其他比特来理解你正在谈论食物,因为你在谈论吃饭或者食物,然后你稍后可能就会谈论“The cat was full.”,你可以每个时间点只改变一些比特。

你现在已经理解GRU最重要的思想了,幻灯片中展示的实际上只是简化过的GRU单元,现在来描述一下完整的GRU单元。

对于完整的GRU单元我要做的一个改变就是在我们计算的第一个式子中给记忆细胞的新候选值加上一个新的项,我要添加一个门Γr(下图编号1所示),你可以认为r 表相关性(relevance)。这个Γr 告诉你计算出的下一个c<t> 候选值c~<t> c<t−1> 多大的相关性。计算这个门Γr 要参数,正如你看到的这个,一个新的参数矩阵Wr,Γr=σ(Wr[c<t−1>,x<t>]+br)。

正如你所见,有很多方法可以来设计这些类型的神经网络,然后我们为什么有Γr?为什么不用上一张幻灯片里的简单的版本?这是因为多年来研究者们试验过很多很多不同可能的方法来设计这些单元,去尝试让神经网络有更深层的连接,去尝试产生更大范围的影响,还有解决梯度消失的问题,GRU就是其中一个研究者们最常使用的版本,也被发现在很多不同的问题上也是非常健壮和实用的。你可以尝试发明新版本的单元,只要你愿意。但是GRU是一个标准版本,也就是最常使用的。你可以想象到研究者们也尝试了很多其他版本,类似这样的但不完全是,比如我这里写的这个。然后另一个常用的版本被称为LSTM,表示长短时记忆网络,这个我们会在下节视频中讲到,但是GRU和LSTM是在神经网络结构中最常用的两个具体实例。

还有在符号上的一点,我尝试去定义固定的符号让这些概念容易理解,如果你看学术文章的话,你有的时候会看到有些人使用另一种符号x~,u,r h 示这些量。但我试着在GRU和LSTM之间用一种更固定的符号,比如使用更固定的符号Γ 表示门,所以希望这能让这些概念更好理解。

所以这就是GRU,即门控循环单元,这是RNN的其中之一。这个结构可以更好捕捉非常长范围的依赖,让RNN更加有效。然后我简单提一下其他常用的神经网络,比较经典的是这个叫做LSTM,即长短时记忆网络,我们在下节视频中讲解。

(Chung J, Gulcehre C, Cho K H, et al. Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling[J]. Eprint Arxiv, 2014.

Cho K, Merrienboer B V, Bahdanau D, et al. On the Properties of Neural Machine Translation: Encoder-Decoder Approaches[J]. Computer Science, 2014.)

1.10 长短期记忆(LSTM(long short term memory)unit)

在上一个视频中你已经学了GRU(门控循环单元)。它能够让你可以在序列中学习非常深的连接。其他类型的单元也可以让你做到这个,比如LSTM即长短时记忆网络,甚至比GRU更加有效,让我们看看。

这里是上个视频中的式子,对于GRU我们有a<t>=c<t>。

还有两个门:

更新门Γu(the update gate)

相关门Γr(the relevance gate)

c~<t>,这是代替记忆细胞的候选值,然后我们使用更新门Γu 决定是否要用c~<t> 更新c<t>。

LSTM是一个比GRU更加强大和通用的版本,这多亏了 Sepp Hochreiter和 Jurgen Schmidhuber,感谢那篇开创性的论文,它在序列模型上有着巨大影响。我感觉这篇论文是挺难读懂的,虽然我认为这篇论文在深度学习社群有着重大的影响,它深入讨论了梯度消失的理论,我感觉大部分的人学到LSTM的细节是在其他的地方,而不是这篇论文。

这就是LSTM主要的式子(上图编号2所示),我们继续回到记忆细胞c上面来,使用c~<t>=tanh(Wc[a<t−1>,x<t>]+bc 更新它的候选值c~<t>(上图编号3所示)。注意了,在LSTM中我们不再有a<t>=c<t> 情况,这是现在我们用的是类似于左边这个式子(上图编号4所示),但是有一些改变,现在我们专门使用a<t> 者a<t−1>,而不是用c<t−1>,我们也不用Γr,即相关门。虽然你可以使用LSTM的变体,然后把这些东西(左边所示的GRU公式)都放回来,但是在更加典型的LSTM里面,我们先不那样做。

我们像以前那样有一个更新门Γu 表示更新的参数Wu,Γu=σ(Wu[a<t−1>,x<t>]+bu)(上图编号5所示)。一个LSTM的新特性是不只有一个更新门控制,这里的这两项(上图编号6,7所示),我们将用不同的项来代替它们,要用别的项来取代Γu 1−Γu,这里(上图编号6所示)我们用Γu。

然后这里(上图编号7所示)用遗忘门(the forget gate),我们叫它Γf,所以这个Γf=σ(Wf[a<t−1>,x<t>]+bf)(上图编号8所示);

然后我们有一个新的输出门,Γo=σ(Wo[a<t−1>,x<t>]+>bo)(上图编号9所示);

于是记忆细胞的更新值c<t>=Γu∗c~<t>+Γf∗c<t−1>(上图编号10所示);

所以这给了记忆细胞选择权去维持旧的值c<t−1> 者就加上新的值c~<t>,所以这里用了单独的更新门Γu 遗忘门Γf,

然后这个表示更新门(Γu=σ(Wu[a<t−1>,x<t>]+bu) 图编号5所示);

遗忘门(Γf=σ(Wf[a<t−1>,x<t>]+bf) 图编号8所示)和输出门(上图编号9所示)。

最后a<t>=c<t> 式子会变成a<t>=Γo∗c<t>。这就是LSTM主要的式子了,然后这里(上图编号11所示)有三个门而不是两个,这有点复杂,它把门放到了和之前有点不同的地方。

再提一下,这些式子就是控制LSTM行为的主要的式子了(上图编号1所示)。像之前一样用图片稍微解释一下,先让我把图画在这里(上图编号2所示)。如果图片过于复杂,别担心,我个人感觉式子比图片好理解,但是我画图只是因为它比较直观。这个右上角的图的灵感来自于Chris Ola的一篇博客,标题是《理解LSTM网络》(Understanding LSTM Network),这里的这张图跟他博客上的图是很相似的,但关键的不同可能是这里的这张图用了a<t−1> x<t> 计算所有门值(上图编号3,4所示),在这张图里是用a<t−1>, x<t> 起来计算遗忘门Γf 值,还有更新门Γu 及输出门Γo(上图编号4所示)。然后它们也经过tanh函数来计算c~<t>(上图编号5所示),这些值被用复杂的方式组合在一起,比如说元素对应的乘积或者其他的方式来从之前的c<t−1>(上图编号6所示)中获得c<t>(上图编号7所示)。

这里其中一个元素很有意思,如你在这一堆图(上图编号8所示的一系列图片)中看到的,这是其中一个,再把他们连起来,就是把它们按时间次序连起来,这里(上图编号9所示)输入x<1>,然后x<2>,x<3>,然后你可以把这些单元依次连起来,这里输出了上一个时间的a,a 作为下一个时间步的输入,c 理。在下面这一块,我把图简化了一下(相对上图编号2所示的图有所简化)。然后这有个有意思的事情,你会注意到上面这里有条线(上图编号10所示的线),这条线显示了只要你正确地设置了遗忘门和更新门,LSTM是相当容易把c<0> 值(上图编号11所示)一直往下传递到右边,比如c<3>=c<0>(上图编号12所示)。这就是为什么LSTM和GRU非常擅长于长时间记忆某个值,对于存在记忆细胞中的某个值,即使经过很长很长的时间步。

这就是LSTM,你可能会想到这里和一般使用的版本会有些不同,最常用的版本可能是门值不仅取决于a<t−1> x<t>,有时候也可以偷窥一下c<t−1> 值(上图编号13所示),这叫做“窥视孔连接”(peephole connection)。虽然不是个好听的名字,但是你想,“偷窥孔连接”其实意思就是门值不仅取决于a<t−1> x<t>,也取决于上一个记忆细胞的值(c<t−1>),然后“偷窥孔连接”就可以结合这三个门(Γu、Γf、Γo)来计算了。

如你所见LSTM主要的区别在于一个技术上的细节,比如这(上图编号13所示)有一个100维的向量,你有一个100维的隐藏的记忆细胞单元,然后比如第50个c<t−1> 元素只会影响第50个元素对应的那个门,所以关系是一对一的,于是并不是任意这100维的c<t−1> 以影响所有的门元素。相反的,第一个c<t−1> 元素只能影响门的第一个元素,第二个元素影响对应的第二个元素,如此类推。但如果你读过论文,见人讨论“偷窥孔连接”,那就是在说c<t−1> 能影响门值。

LSTM前向传播图:

ST

STM_rn

LSTM反向传播计算:

门求偏导:

dΓo⟨t⟩=danext∗tanh⁡(cnext)∗Γo⟨t⟩∗(1−Γo⟨t⟩)

dc~⟨t⟩=dcnext∗Γi⟨t⟩+Γo⟨t⟩(1−tanh⁡(cnext)2)∗it∗danext∗c~⟨t⟩∗(1−tanh⁡(c~)2)

dΓu⟨t⟩=dcnext∗c~⟨t⟩+Γo⟨t⟩(1−tanh⁡(cnext)2)∗c~⟨t⟩∗danext∗Γu⟨t⟩∗(1−Γu⟨t⟩)

dΓf⟨t⟩=dcnext∗c~prev+Γo⟨t⟩(1−tanh⁡(cnext)2)∗cprev∗danext∗Γf⟨t⟩∗(1−Γf⟨t⟩)

**参数求偏导 **:

$ dW_f = d\Gamma_f^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{5} dW_u = d\Gamma_u^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{6} $ $ dW_c = d\tilde c^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{7} dW_o = d\Gamma_o^{\langle t \rangle} * \begin{pmatrix} a_{prev} \ x_t\end{pmatrix}^T \tag{8}$

为了计算dbf,dbu,dbc,dbo 需要各自对dΓf⟨t⟩,dΓu⟨t⟩,dc~⟨t⟩,dΓo⟨t⟩ 求和。

最后,计算隐藏状态、记忆状态和输入的偏导数:

$ da_{prev} = W_f^T*d\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c^{\langle t \rangle} + W_o^T * d\Gamma_o^{\langle t \rangle} \tag{9}$

$ dc_{prev} = dc_{next}\Gamma_f^{\langle t \rangle} + \Gamma_o^{\langle t \rangle} * (1- \tanh(c_{next})^2)*\Gamma_f^{\langle t \rangle}da_{next} \tag{10} dx^{\langle t \rangle} = W_f^Td\Gamma_f^{\langle t \rangle} + W_u^T * d\Gamma_u^{\langle t \rangle}+ W_c^T * d\tilde c_t + W_o^T * d\Gamma_o^{\langle t \rangle}\tag{11} $

这就是LSTM,我们什么时候应该用GRU?什么时候用LSTM?这里没有统一的准则。而且即使我先讲解了GRU,在深度学习的历史上,LSTM也是更早出现的,而GRU是最近才发明出来的,它可能源于Pavia在更加复杂的LSTM模型中做出的简化。研究者们在很多不同问题上尝试了这两种模型,看看在不同的问题不同的算法中哪个模型更好,所以这不是个学术和高深的算法,我才想要把这两个模型展示给你。

GRU的优点是这是个更加简单的模型,所以更容易创建一个更大的网络,而且它只有两个门,在计算性上也运行得更快,然后它可以扩大模型的规模。

但是LSTM更加强大和灵活,因为它有三个门而不是两个。如果你想选一个使用,我认为LSTM在历史进程上是个更优先的选择,所以如果你必须选一个,我感觉今天大部分的人还是会把LSTM作为默认的选择来尝试。虽然我认为最近几年GRU获得了很多支持,而且我感觉越来越多的团队也正在使用GRU,因为它更加简单,而且还效果还不错,它更容易适应规模更加大的问题。

所以这就是LSTM,无论是GRU还是LSTM,你都可以用它们来构建捕获更加深层连接的神经网络。

(Hochreiter S, Schmidhuber J. Long Short-Term Memory[J]. Neural Computation, 1997, 9(8):1735-1780.)

1.11 双向循环神经网络(Bidirectional RNN)

现在,你已经了解了大部分RNN模型的关键的构件,还有两个方法可以让你构建更好的模型,其中之一就是双向RNN模型,这个模型可以让你在序列的某点处不仅可以获取之前的信息,还可以获取未来的信息,我们会在这个视频里讲解。第二个就是深层的RNN,我们会在下个视频里见到,现在先从双向RNN开始吧。

为了了解双向RNN的动机,我们先看一下之前在命名实体识别中已经见过多次的神经网络。这个网络有一个问题,在判断第三个词Teddy(上图编号1所示)是不是人名的一部分时,光看句子前面部分是不够的,为了判断y^<3>(上图编号2所示)是0还是1,除了前3个单词,你还需要更多的信息,因为根据前3个单词无法判断他们说的是Teddy熊,还是前美国总统Teddy Roosevelt,所以这是一个非双向的或者说只有前向的RNN。我刚才所说的总是成立的,不管这些单元(上图编号3所示)是标准的RNN块,还是GRU单元或者是LSTM单元,只要这些构件都是只有前向的。

那么一个双向的RNN是如何解决这个问题的?下面解释双向RNN的工作原理。为了简单,我们用四个输入或者说一个只有4个单词的句子,这样输入只有4个,x<1> x<4>。从这里开始的这个网络会有一个前向的循环单元叫做a→<1>,a→<2>,a→<3> 有a→<4>,我在这上面加个向右的箭头来表示前向的循环单元,并且他们这样连接(下图编号1所示)。这四个循环单元都有一个当前输入x 入进去,得到预测的y^<1>,y^<2>,y^<3> y^<4>。

到目前为止,我还没做什么,仅仅是把前面幻灯片里的RNN画在了这里,只是在这些地方画上了箭头。我之所以在这些地方画上了箭头是因为我们想要增加一个反向循环层,这里有个a←<1>,左箭头代表反向连接,a←<2> 向连接,a←<3> 向连接,a←<4> 向连接,所以这里的左箭头代表反向连接。

同样,我们把网络这样向上连接,这个a 向连接就依次反向向前连接(上图编号2所示)。这样,这个网络就构成了一个无环图。给定一个输入序列x<1> x<4>,这个序列首先计算前向的a→<1>,然后计算前向的a→<2>,接着a→<3>,a→<4>。而反向序列从计算a←<4> 始,反向进行,计算反向的a←<3>。你计算的是网络激活值,这不是反向而是前向的传播,而图中这个前向传播一部分计算是从左到右,一部分计算是从右到左。计算完了反向的a←<3>,可以用这些激活值计算反向的a←<2>,然后是反向的a←<1>,把所有这些激活值都计算完了就可以计算预测结果了。

举个例子,为了预测结果,你的网络会有如y^<t>,y^<t>=g(Wg[a→<t>,a←<t>]+by)(上图编号1所示)。比如你要观察时间3这里的预测结果,信息从x<1> 来,流经这里,前向的a→<1> 前向的a→<2>,这些函数里都有表达,到前向的a→<3> 到y^<3>(上图编号2所示的路径),所以从x<1>,x<2>,x<3> 的信息都会考虑在内,而从x<4> 的信息会流过反向的a←<4>,到反向的a←<3> 到y^<3>(上图编号3所示的路径)。这样使得时间3的预测结果不仅输入了过去的信息,还有现在的信息,这一步涉及了前向和反向的传播信息以及未来的信息。给定一个句子"He said Teddy Roosevelt..."来预测Teddy是不是人名的一部分,你需要同时考虑过去和未来的信息。

这就是双向循环神经网络,并且这些基本单元不仅仅是标准RNN单元,也可以是GRU单元或者LSTM单元。事实上,很多的NLP问题,对于大量有自然语言处理问题的文本,有LSTM单元的双向RNN模型是用的最多的。所以如果有NLP问题,并且文本句子都是完整的,首先需要标定这些句子,一个有LSTM单元的双向RNN模型,有前向和反向过程是一个不错的首选。

以上就是双向RNN的内容,这个改进的方法不仅能用于基本的RNN结构,也能用于GRU和LSTM。通过这些改变,你就可以用一个用RNN或GRU或LSTM构建的模型,并且能够预测任意位置,即使在句子的中间,因为模型能够考虑整个句子的信息。这个双向RNN网络模型的缺点就是你需要完整的数据的序列,你才能预测任意位置。比如说你要构建一个语音识别系统,那么双向RNN模型需要你考虑整个语音表达,但是如果直接用这个去实现的话,你需要等待这个人说完,然后获取整个语音表达才能处理这段语音,并进一步做语音识别。对于实际的语音识别的应用通常会有更加复杂的模块,而不是仅仅用我们见过的标准的双向RNN模型。但是对于很多自然语言处理的应用,如果你总是可以获取整个句子,这个标准的双向RNN算法实际上很高效。

好的,这就是双向RNN,下一个视频,也是这周的最后一个,我们会讨论如何用这些概念,标准的RNN,LSTM单元,GRU单元,还有双向的版本,构建更深的网络。

1.12 深层循环神经网络(Deep RNNs)

目前你学到的不同RNN的版本,每一个都可以独当一面。但是要学习非常复杂的函数,通常我们会把RNN的多个层堆叠在一起构建更深的模型。这节视频里我们会学到如何构建这些更深的RNN。

一个标准的神经网络,首先是输入x,然后堆叠上隐含层,所以这里应该有激活值,比如说第一层是a[1],接着堆叠上下一层,激活值a[2],可以再加一层a[3],然后得到预测值y^。深层的RNN网络跟这个有点像,用手画的这个网络(下图编号1所示),然后把它按时间展开就是了,我们看看。

这是我们一直见到的标准的RNN(上图编号3所示方框内的RNN),只是我把这里的符号稍微改了一下,不再用原来的a<0> 示0时刻的激活值了,而是用a[1]<0> 表示第一层(上图编号4所示),所以我们现在用a[l]<t> 表示第l层的激活值,这个\<t 示第t 时间点,这样就可以表示。第一层第一个时间点的激活值a[1]<1>,这(a[1]<2>)就是第一层第二个时间点的激活值,a[1]<3> a[1]<4>。然后我们把这些(上图编号4方框内所示的部分)堆叠在上面,这就是一个有三个隐层的新的网络。

我们看个具体的例子,看看这个值(a[2]<3>,上图编号5所示)是怎么算的。激活值a[2]<3> 两个输入,一个是从下面过来的输入(上图编号6所示),还有一个是从左边过来的输入(上图编号7所示),a[2]<3>=g(Wa[2][a[2]<2>,a[1]<3>]+ba[2]),这就是这个激活值的计算方法。参数Wa[2] ba[2] 这一层的计算里都一样,相对应地第一层也有自己的参数Wa[1] ba[1]。

对于像左边这样标准的神经网络,你可能见过很深的网络,甚至于100层深,而对于RNN来说,有三层就已经不少了。由于时间的维度,RNN网络会变得相当大,即使只有很少的几层,很少会看到这种网络堆叠到100层。但有一种会容易见到,就是在每一个上面堆叠循环层,把这里的输出去掉(上图编号1所示),然后换成一些深的层,这些层并不水平连接,只是一个深层的网络,然后用来预测y<1>。同样这里(上图编号2所示)也加上一个深层网络,然后预测y<2>。这种类型的网络结构用的会稍微多一点,这种结构有三个循环单元,在时间上连接,接着一个网络在后面接一个网络,当然y<3> y<4> 一样,这是一个深层网络,但没有水平方向上的连接,所以这种类型的结构我们会见得多一点。通常这些单元(上图编号3所示)没必要非是标准的RNN,最简单的RNN模型,也可以是GRU单元或者LSTM单元,并且,你也可以构建深层的双向RNN网络。由于深层的RNN训练需要很多计算资源,需要很长的时间,尽管看起来没有多少循环层,这个也就是在时间上连接了三个深层的循环层,你看不到多少深层的循环层,不像卷积神经网络一样有大量的隐含层。

这就是深层RNN的内容,从基本的RNN网络,基本的循环单元到GRU,LSTM,再到双向RNN,还有深层版的模型。这节课后,你已经可以构建很不错的学习序列的模型了。

#机器学习教程
上次更新: 2025/06/25, 11:25:50
卷积神经网络
LSTM

← 卷积神经网络 LSTM→

最近更新
01
帮助信息查看
06-08
02
常用命令
06-08
03
学习资源
06-07
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Geeks_Z | MIT License
京公网安备 11010802040735号 | 京ICP备2022029989号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式