当前位置: 首页 > news >正文

自然语言处理(13:RNN的实现)

系列文章目录

第一章 1:同义词词典和基于计数方法语料库预处理

第一章 2:基于计数方法的分布式表示和假设,共现矩阵,向量相似度

第一章 3:基于计数方法的改进以及总结

第二章 1:word2vec

第二章 2:word2vec和CBOW模型的初步实现

第二章 3:CBOW模型的完整实现

第二章 4:CBOW模型的补充和skip-gram模型的理论

第三章 1:word2vec的高速化(CBOW的改进)

第三章 2:word2vec高速化(CBOW的二次改进)

第三章 3:改进版word2vec的学习以及总结

第四章 1:RNN(RNN的前置知识和引入)

第四章 2:RNN(RNN的正式介绍)

第四章 3:RNN的实现

第四章 4:处理时序数据的层的实现

第四章 5:RNNLM的学习和评价


文章目录

目录

系列文章目录

文章目录

前言

一、RNN的实现

1.RNN层的实现

2.Time RNN层的实现


前言

通过之前的探讨,我们已经看到了RNN的全貌。实际上,我们要实现的是一个在水平方向上延伸的神经网络。另外,考虑到基于Truncated BPTT的学习,只需要创建一个在水平方向上长度固定的网络序列即可,来吧,开始实现(迫不及待!!)


一、RNN的实现

如上图所示,目标神经网络接收长度为T的时序数据(T为任意值), 输出各个时刻的隐藏状态T个。这里,考虑到模块化,将图中在水平方向上延伸的神经网络实现为“一个层”,如下图所示。

如图5-17所示,将垂直方向上的输入和输出分别捆绑在一起,就可以 将水平排列的层视为一个层。换句话说,可以将(x0,x1,···,xT−1)捆绑为 xs 作为输入,将(h0,h1,···,hT−1)捆绑为hs作为输出。这里,我们将进 行Time RNN层中的单步处理的层称为“RNN层”,将一次处理T步的层称为“Time RNN层 ”。

我们接下来进行的实现的流程是:首先,实现进行RNN单步处理的RNN 类;然后,利用这个RNN类,完成一次进行T步处理的TimeRNN类。

1.RNN层的实现

现在,我们来实现进行RNN单步处理的RNN类。首先复习一下RNN 正向传播的数学式,如下式所示:

这里,我们将数据整理为mini-batch进行处理。因此,xt(和ht)在行方向上保存各样本数据。在矩阵计算中,矩阵的形状检查非常重要。这里,假设批大小是N,输入向量的维数是D,隐藏状态向量的维数是H,则矩阵的形状检查可以像下面这样进行:

如上图所示,通过矩阵的形状检查,可以确认它的实现是否正确, 至少可以确认它的计算是否成立。基于以上内容,现在我们给出RNN类的初始化方法和正向传播的forward()方法

class RNN:
    def __init__(self, Wx, Wh, b):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx), np.zeros_like(Wh), np.zeros_like(b)]
        
        self.cache = None
        # 中间数据cache 
    
    def forward(self, x, h_prev):
        Wx, Wh, b = self.params
        t = np.dot(h_prev, Wh) + np.dot(x, Wx) + b
        h_next = np.tanh(t)
    
        self.cache = (x, h_prev, h_next)
        return h_next

RNN 的初始化方法接收两个权重参数和一个偏置参数。这里,将通过函数参数传进来的模型参数设置为列表类型的成员变量params。然后,以各个参数对应的形状初始化梯度,并保存在grads中。最后,使用None对反向传播时要用到的中间数据cache进行初始化。

正向传播的forward(x, h_prev) 方法接收两个参数:从下方输入的x和从左边输入的h_prev。剩下的就是按下式进行实现。

顺便说一下,这里 从前一个RNN层接收的输入是h_prev,当前时刻的RNN层的输出(=下一时刻的RNN层的输入)是h_next。

接下来,我们继续实现RNN的反向传播。在此之前,让我们通过下图的计算图再次确认一下RNN的正向传播:

RNN层的正向传播可由上图的计算图表示。这里进行的计算由矩阵乘积“MatMul”、加法“+”和“tanh”这3种运算构成。此外,因为偏置 b 的加法运算会触发广播操作,所以严格地讲,这里还应该加上Repeat节点。不过简单起见,这里省略了它。

剩下就是基于下图,按正向传播的反方向实现各个运算的反向传播:

下面实现RNN层的backward()

def backward(self, dh_next):
    """
    这里看不懂的,可以看博主前几节的文章,或者问Deepseek

    """

    Wx, Wh, b = self.params

    x, h_prev, h_next = self.cache
    dt = dh_next * (1 - h_next ** 2)
    db = np.sum(dt, axis=0)
    dWh = np.dot(h_prev.T, dt)
    dh_prev = np.dot(x.T,, dt)
    dWx = np.dot(x.T, dt)
    dx = np.dot(dt, Wx.T)
    
    
    self.grads[0][...] = dWx
    self.grads[1][...] = dWh
    self.grads[2][...] = db

    return dx, dh_prev

以上就是RNN层的反向传播的实现。接下来,我们将实现Time RNN层

2.Time RNN层的实现

Time RNN 层由T个RNN层构成,如下所示:

由上图可知,Time RNN层是T个RNN层连接起来的网络。我们将这个网络实现为Time RNN层。这里,RNN层的隐藏状态h保存在成员变量中。如下图所示,在进行隐藏状态的“继承”时会用到它。

如上图所示,我们使用Time RNN层管理RNN层的隐藏状态。这样一来,使用Time RNN的人就不必考虑RNN层的隐藏状态的“继承工作”了。另外,我们可以用stateful这个参数来控制是否继承隐藏状态。

下面,我们来看一下Time RNN层的实现。

class TimeRNN:
    def __init__(self, Wx, Wh, b, stateful=False):
        self.params = [Wx, Wh, b]
        self.grads = [np.zeros_like(Wx),np.zeros_like(Wh),np.zeros_like(b)]
        self.layers = None

        self.h, self.dh = None, None
        self.stateful = stateful

    def set_state(self, h):
        self.h = h
    def reset_state(self):
        self.h = None

初始化方法的参数有权重、偏置和布尔型(True/False)的stateful。 一个成员变量layers在列表中保存多个RNN层,另一个成员变量,h保存调用forward() 方法时的最后一个RNN层的隐藏状态。另外,在调用 backward() 时,dh保存传给前一个块的隐藏状态的梯度(关于dh,我们会在 反向传播的实现中说明)。

考虑到TimeRNN类的扩展性,将设定Time RNN层的隐藏状态的 方法实现为set_state(h)。另外,将重设隐藏状态的方法实现为 reset_state()。

当 stateful 为 True 时,Time RNN 层“有状态”。这里说的“有状态”是指维 持Time RNN层的隐藏状态。也就是说,无论时序数据多长,Time RNN 层的正向传播都可以不中断地进行。而当stateful为False时,每次调用Time RNN 层的forward() 时,第一个RNN层的隐藏状态都会被初始化为 零矩阵(所有元素均为0的矩阵)。这是没有状态的模式,称为“无状态”。

(True就是这一层受上一层的影响,具有依赖性;

False就是相互独立,各个数据互不影响)

接着,我们来看一下正向传播的实现:

def forward(self, xs):
    Wx, Wh, b = self.params
    N, T, D = xs.shape
    D, H = Wx.shape
    
    self.layers = []
    hs = np.empty((N, T, H), dtype='f')

    if not self.stateful or self.h is None:
        self.h = np.zeros((N, H), dtype='f')
    

    for t in range(T):
        layer = RNN(*self.params)
        self.h = layer.forward(xs[:, t, :], self.h)
        hs[:, t, :] = self.h
        self.layers.append(layer)
    
    return hs

正向传播的forward(xs)方法从下方获取输入xs,xs囊括了T个时序数 据。因此,如果批大小是N,输入向量的维数是D,则xs的形状为(N,T,D)。 在首次调用时(self.h为None时 ), RNN层的隐藏状态h由所有元素 均为0的矩阵初始化。另外,在成员变量stateful为False的情况下,h将 总是被重置为零矩阵。

在主体实现中,首先通过hs=np.empty((N, T, H), dtype='f') 为输出准 备一个“容器”。接着,在T次for循环中,生成RNN层,并将其添加到成员变量layers中。然后,计算RNN层各个时刻的隐藏状态,并存放在hs 的对应索引(时刻)中。

(如果调用Time RNN层的forward()方法,则成员变量h中将存放 最后一个RNN层的隐藏状态。在stateful为True的情况下,在下 一次调用forward()方法时,刚才的成员变量h将被继续使用。而在 stateful为False的情况下,成员变量h将被重置为零向量。)

接下来是Time RNN层的反向传播的实现。用计算图绘制这个反向传播:

在上图中,将从上游(输出侧的层)传来的梯度记为dhs,将流向 下游的梯度记为dxs。因为这里我们进行的是Truncated BPTT,所以不需要流向这个块上一时刻的反向传播。不过,我们将流向上一时刻的隐藏状态的梯度存放在成员变量dh中。这是因为在第7章探讨seq2seq(sequence to-sequence,序列到序列)时会用到它(具体请参考第7章 )。 以上就是Time RNN层的反向传播的全貌图。如果关注第t个RNN层, 则它的反向传播如下图所示。

从上方传来的梯度dht和从将来的层传来的梯度dhnext会传到第t个 RNN层。这里需要注意的是,RNN层的正向传播的输出有两个分叉。在正 向传播存在分叉的情况下,在反向传播时各梯度将被求和。因此,在反向传 播时,流向RNN层的是求和后的梯度。考虑到以上这些,反向传播的实现 如下所示。

def backward(self, dhs):
    Wx, Wh, b = self.params
    N, D, H = dhs.shape
    D, H = Wx.shape
    
    dxs = np.empty((N, T, D), dtype="f")
    dh = 0
    grads = [0, 0, 0]
    
    for t in reversed(range(T)):
        layer = self.layers[t]
        dx, dh = layer.backward(dhs[:, t, :] + dh) # 求和后的梯度
        dxs[:, t, :] = dx

        for i, grad in enumerate(layer.grads):
            grad[i] += grad
    for i, grad in enumerate(grads):
        self.grads[i][...] = grad
    self.dh = dh
    return dxs

这里,首先创建传给下游的梯度的“容器”(dxs)。接着,按与正向传 播相反的方向,调用RNN层的backward()方法,求得各个时刻的梯度dx, 并存放在dxs的对应索引处。另外,关于权重参数,需要求各个RNN层的 权重梯度的和,并通过“...”用最终结果覆盖成员变量self.grads。

以上就是对Time RNN层的实现的说明。

相关文章:

  • 接口测试是什么
  • Mininet-topo.py源码解析
  • Linux--环境变量
  • Ubuntu 更换阿里云镜像源图文详细教程
  • Android面试总结之Android RecyclerView:从基础机制到缓存优化
  • 浅尝AI编程工具Trae
  • javascript实现一个函数,将数组中的元素随机打乱顺序
  • 如何用C#继承提升游戏开发效率?Enemy与Boss案例解析
  • 什么是ecovadis认证?ecovadis认证的好处?ecovadis认证的重要意义
  • 案例4:鸢尾花分类(pytorch)
  • 【Docker系列八】使用 Docker run 命令部署 Nginx
  • 初识哈希表
  • 详解接口的常见请求方式
  • 机器学习(八)
  • 1342 摆放小球
  • uniapp中props的用法
  • 3.24学习总结 Java多态+包和final关键字
  • 大文件切片上传和断点续传
  • Typora1.10破解教程
  • 数智读书笔记系列024《主数据驱动的数据治理 —— 原理、技术与实践》
  • 一箭六星,朱雀二号改进型遥二运载火箭发射成功
  • 湖南4个县市区被确定为野生蘑菇中毒高风险区:中毒尚无特效解毒药
  • 江西4人拟任县(市、区)委书记,其中一人为“80后”
  • 首次公布!我国空间站内发现微生物新物种
  • 嫩黑线货物列车脱轨致1名路外人员死亡,3人被采取刑事强制措施
  • 关税互降后的外贸企业:之前暂停的订单加紧发货,后续订单考验沟通谈判能力