RNN及TimeRNN

条件语言模型


对于位置在i的单词,常规的word2vec模型考虑前后两个窗口的位置,这里只考虑单词左边位置的部分单词。这个公式考虑了单词周围的信息

CBOW的条件语言模型

P(W1 ,W2 ,…,Wm ) = ∏(下标t=1,上标m)P(Wt | Wt-1 ,Wt-2 )
CBOW模型取窗口大小为2,
缺点
无法考虑到远处的信息,即使增大窗口,也还是会有更远处的信息无法被记录,同时也会带来计算量和存储量增大的问题

RNN

基本因子 ht = tanh(ht-1Wh + xt Wx +b)
公式中ht-1代表上一个时间步输出状态,Wh用来将ht-1的形状转换为能输入到现在的模型中

反向传播BPTT

如果直接将整个的RNN串联为一条模型,在反向传播时需要存储从开始到最后的所有中间状态,如果是很长的一段文字,则需要很大的内存量。因此提出了BPTT。
基本思想是将RNN切分为多个小块,每个小块独立计算,独自反向传播
需要注意两点
1.按顺序输入数据,因为RNN的引入就是为了考虑序列的顺序性,所以数据的顺序是很重要的
2.平移不同batch的初始位置,因为要保证数据的顺序性,所以必须要计算每个batch开始的位置
在这里插入图片描述

代码实现RNN

class RNN:
    def __init__(self,Wh,Wx,b):
        self.params = [Wh,Wx,b]
        #初始化梯度
        self.grads = [np.zeros_like(Wh),np.zeros_like(Wx),np.zeros_like(b)]
        self.cache = None
    
    #前向传播
    def forward(self,hprev,x):
        Wh,Wx,b = self.params
        h_next = np.dot(hprev,Wh) + np.dot(x,Wx) + b
        h_next = np.tanh(h_next)
        self.cache = [x,h_prev,h_next]
        return h_next
    #反向传播
    def backward(self,dh_next):
        Wh,Wx,b = self.params
        dt = dh_next * (1-dh_next**2)#因为前向用的激活函数是tanh
        db = np.sum(dt,axis=0)
        x,hprev,h_next = self.cache
        dW_h = np.dot(hprev.T,dt)
        dh_prev = np.dot(dt,Wh)
        dW_x = np.dot(dt,x.T)
        dx = np.dot(dt,Wx.T)
        self.grads[0][...] = dW_h
        self.grads[1][...] = dW_x
        self.grads[2][...] = db
        return dx,dh_prev

TimeRNN

TimeRNN的基本思想是将T个RNN块组合成一块独立计算,每一块的隐藏状态h保存在成员变量中,由一个变量stateful控制是否保存隐藏状态,输入是一批数据xs

class TimeRNN:
    def __init__(Wh,Wx,b,stateful=False):#stateful控制是否保存时序状态
        self.params = [Wh,Wx,b]
        self.grads = [np.zeros_like(Wh),np.zeros_like(Wx),np.zeros_like(b)]
        self.layers = None
        self.h,self.dh = 0,0#保存最后一块RNN的输出状态和传递给上一层的梯度
        self.stateful = stateful
    #根据statful决定是否保存状态
    def save_state(self,h):
        self.h = h
    def reset_state(self):#重置输出状态
        self.h = 0
    #前向传播
    #N是batch_size,T是时序数,D是数据维度,H是隐藏层大小
    def forward(self,xs):
        Wh,Wx,b = self.params#继承变量
        N,T,D = xs.shape
        D,H = Wx.shape
        hs = np.empty((N,T,H),dtype='f')
        self.layers = []
        
        for i in range(T):
            layer = RNN(Wh,Wx,b)#创建一个小RNN块
            h = layer.forward(self.h,xs[:,i,:])
            hs[:,i,:] = h#将每一层的输出状态保存
            self.layers.append(layer)#保存每一层
        return hs
    #反向传播
    def backward(self,dhs):
        Wh,Wx,b = self.params
        N,T,H = dhs.shape
        D,H = Wx.shape
        dxs = np.empty((N,T,D),dtype='f')#初始化dxs,对x的导数
        dh = 0
        grads = [0,0,0]
        for t in reversed(T):#反向传播
            layer = self.layers[t]
            dx,dh_prev = layer.backward(dhs[:,t,:]+dh)#梯度求和
            #将x的梯度保存
            dxs[:,t,:] = dx
            for i,grad in enumerate(layer.grads):#保存每一个RNN层的梯度
                grads[i] += grad
        for i,grad in enumerate(grads):#TRNN层最终的梯度是各个RNN层的梯度之和
            grads[i][...] = grad#梯度求和覆盖
        self.dh = dh
        return dxs

RNNLM(RNN语言模型)

模型由一个将输入词汇嵌入的embed层,TimeRNN层,Affine层和一个softmax with loss层组成
每个层的初始权重使用Xavier初始值
Xavier初始值,即将初始权重变为标准差为1/√n的值(将值除以√n)
这里使用的RNN,Affine和loss层,embed层都是用Time型,即多个时间步层,因为每个时间步都需要一个层进行计算

class simpleRNNlm:
    def __init__(self,vocab_size,embed_size,hidden_size):#分别是单词数,embedding层的维数,隐藏层的大小
        V,D,H = vocab_size,embed_size,hidden_size
        rn = np.random.randn#简化函数写法
        #第一层是embed层
        W_embed = (rn(V,D)/100).astype('f')
        #第二层是RNN层,RNN层包含了Wh,Wx,b
        W_h = (rn(D,H)/np.sqrt(D)).astype('f')#这里使用了Xavier初始化
        W_x = (rn(H,H)/np.sqrt(H)).astype('f')#初始化除数等于神经元个数
        b = (rn(H)/np.sqrt(H)).astype('f')
        #第三层是affine层
        W_a = (rn(H,V)/np.sqrt(H)).astype('f')
        b_a = (rn(V)/np.sqrt(V)).astype('f')
        #将所有层联合起来
        self.layers = [
            TimeEmbedding(W_embed),
            TimeRNN(W_x,W_h,b,True),
            TimeAffine(W_a,b_a)
        ]
        self.loss_layer = TimeSoftmaxWithLoss()
        self.rnn = self.layers[1]#取出rnn
        #添加梯度和参数
        self.params,self.grads = [],[]
        for layer in self.layers:
            self.params += layer.params
            self.grads += layer.grads
    
    #前向传播
    def forward(self,xs,ts):#输出为xs
        for layer in self.layers:
            xs = layer.forward(xs)
        loss = self.loss_layer.forward(xs,ts)
        return loss
    #反向传播
    def backward(self,dout=1):
        dx = self.loss_layer.backward(dout)
        for layer in reversed(self.layers):
            dx = layer.backward(dx)
        return dx
    #重置输出状态
    def reset_state(self):
        self.rnn.reset_state()#调用TimeRNN的reset函数

语言模型的评价

通常使用困惑度(perplexity)来衡量语言模型的好坏
困惑度是概率的倒数(书上是这么说的)
用困惑度衡量模型,在单词的判断中,常规的概率可以表示模型取到单词的几率
而困惑度可以表示对于下一个预测结果,语料库中有多少个备选结果
因此,困惑度可以很好地反应模型的好坏程度
通常,好的模型,取到单个准确单词的概率就大,困惑度就小

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值