3.3-LSTM的改进

文章目录

  • 1改进点
    • 1.1多层化
    • 1.2 dropout
      • 1.2.1具体概念
      • 1.2.2应该插入到LSTM模型的哪里
    • 1.3权重共享
  • 2改进之后的LSTMLM的代码实现
    • 2.1初始化
    • 2.2前向计算
    • 2.3反向传播
  • 3相应的学习代码的实现
  • 4总结

1改进点

1.1多层化

  1. 加深神经网络的层数往往能够学习更复杂的模式;因此这里增加了LSTM层的数量;如下图所示:

    1. 第一个 LSTM 层的隐藏状态是第二个LSTM层的输入;
    2. 关于叠加几个层,这是超参数调优的范畴了;需要根据要解决的问题的复杂程度、能给到的训练数据的规模来确定;
    3. 在 PTB 数据集上学习语言模型的情况下,当 LSTM 的层数为 2 ~ 4 时,可以获得比较好的结果

    在这里插入图片描述

1.2 dropout

详细可以再看一下:https://1drv.ms/b/s!AvF6gzVaw0cNjqYc_hrdnGUeqyCXWg?e=hn5AoB;

1.2.1具体概念

  1. 通过加深层,可以创建表现力更强的模型,但是这样的模型往往会发生过拟合;过拟合即模型泛化能力差;

  2. RNN 比常规的前馈神经网络更容易发生过拟合,因此 RNN 的过拟合对策非常重要;

  3. 如何缓解过拟合:

    1. 优先考虑:一是增加训练数据;二是降低模型的复杂度
    2. 还有正则化的方法;即给予惩罚;
    3. dropout这种,在训练时随机忽略层的一部分(比如 50%)神经元,也可以被视为一种正则化;
  4. dropout的具体结构

    1. Dropout 随机选择一部分神经元,然后忽略它们,停止向前传递信号;具体而言,训练时,每传递一次数据,就会随机选择要删除的神经元。 然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出, 要乘上训练时的删除比例后再输出

    2. 下图是概念图:

      在这里插入图片描述

  5. dropout的代码如下(直接看了书上的结果,没有自己做一遍):

    1. forward函数中,是一个判断;训练时需要按照设置的比例忽略一些神经元,忽略的方法就是将x中某些元素值设置为0;测试时传递所有的神经元信号,但是都要乘上删除比例;
    2. 正向传播时没有传递信号的神经元,反向传播时信号就停在那里;换句话说,这些神经元dout*self.mask的值为0,即这些神经元的梯度不会影响后续的反向传播;

    在这里插入图片描述

  6. 下图是书上的一个实验:

    1. 加了dropout之后,训练集和测试集上的表现更相近,说明缓解了过拟合;

    在这里插入图片描述

1.2.2应该插入到LSTM模型的哪里

  1. 不能在水平方向上插入

    1. 水平方向上是这样插入的:

      在这里插入图片描述

    2. 由于每次dropout都有神经元不进行前向计算,这存在信息丢失;

    3. 并且我们会设置stateful参数为true,即下一次Time LSTM前向计算时会继承上一次Time LSTM的最后一个LSTM层的隐藏状态;这样一来,这个信息丢失会随着时间的延续而不断累积;

    4. 信息丢失多了,那就是说有用的信息少了,噪声多了;

    5. 所以不建议在水平方向上加入dropout

  2. 因此可以在垂直方向上插入;如下图所示;这样水平方向上不存在信息丢失,因此至少输入的数据的信息都学到了;

    在这里插入图片描述

  3. 但是也有方法来实现水平方向上的dropout(正则化)

    1. “变分 Dropout”(variational dropout):同一层的 Dropout 使用相同的 mask。这里所说的 mask 是指决定是否传递数据的随机布尔值;这样每一层信息丢失的都是那一部分;那么在其它层的dropout信息丢失的又都是另外一部分,这样互相找补找补就会好很多;

      在这里插入图片描述

1.3权重共享

  1. 通过前面了解LSTM的结构,我们可以感受到,LSTM的参数量是RNN的好几倍;加上多层化的话,参数量更多;因此可以适当减少一下参数量,在能够保证模型效果的前提下;而且参数量减少了还能抑制过拟合;见论文;

  2. 权重共享前提是得两个层的权重确实是一样的;像本书中这里说的权重共享,是将Embedding 层和 Softmax 前的 Affine 层共享权重;这两个部分用的权重维度是一样的;

    1. 具体而言,Embedding层的权重维度为(V,H);Affine 层的权重维度为(H,V)
    2. 因此只需将 Embedding 层权重的转置设置为 Affine 层的权重即可;

    在这里插入图片描述

2改进之后的LSTMLM的代码实现

本书学习过程中的代码已全部搬运至gitee:https://gitee.com/gaoyan1518/basic-nlp;

代码位于:LSTM_model/better_LSTMLM.py

  1. 首先需要构建一下Time Dropout层;由于Dropout层只是单纯的给某些神经元遮蔽掉,多个时刻也是一样的处理,时刻与时刻之间的Dropout层是独立的;所以不需要像Time LSTM层那样在forward里面循环T次;

  2. 另外这里在实现的时候,是在训练阶段就进行了缩放,所以测试阶段直接就返回了x,代码如下;如果训练阶段没有进行缩放,则测试阶段就要对x乘上删除比例;1.0 - self.dropout_ratio是删除比例;【可以看这个博客】

    class TimeDropout:
        def __init__(self, dropout_ratio=0.5):
            self.params, self.grads = [], []
            self.dropout_ratio = dropout_ratio
            self.mask = None
            self.train_flg = True
    
        def forward(self, xs):
            if self.train_flg:
                flg = cupy.random.rand(*xs.shape) > self.dropout_ratio
                scale = 1 / (1.0 - self.dropout_ratio) # 删除比例的倒数;删除的越多,剩余神经元的权重就越小
                self.mask = flg.astype(cupy.float32) * scale
    
                return xs * self.mask
            else:
                return xs
    
        def backward(self, dout):
            return dout * self.mask
    
    

2.1初始化

  1. 与之前的LSTMLM相比,这里就是多层化了、权重共享了、加入了dropout,其他的都一样;代码如下:

    class BetterRnnlm:
        def __init__(self, vocab_size=10000, wordvec_size=650, hidden_size=650, dropout_ratio=0.5):
            V, D, H = vocab_size, wordvec_size, hidden_size
            rn = np.random.randn
    
            embed_W = (rn(V, D) / 100).astype('f') # 与affine_w权重共享
            lstm_Wx1 = (rn(D, 4 * H) / np.sqrt(D)).astype('f') # 同样是一个整合之后的权重矩阵
            lstm_Wh1 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
            lstm_b1 = np.zeros(4 * H).astype('f')
            lstm_Wx2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
            lstm_Wh2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f')
            lstm_b2 = np.zeros(4 * H).astype('f')
            affine_b = np.zeros(V).astype('f')
    
            if GPU:
                embed_W = to_gpu(embed_W)
                lstm_Wx1 = to_gpu(lstm_Wx1)
                lstm_Wx2 = to_gpu(lstm_Wx2)
                lstm_Wh1 = to_gpu(lstm_Wh1)
                lstm_Wh2 = to_gpu(lstm_Wh2)
                lstm_b1 = to_gpu(lstm_b1)
                lstm_b2 = to_gpu(lstm_b2)
                affine_b = to_gpu(affine_b)
    
            self.layers = [
                TimeEmbedding(embed_W),
                TimeDropout(dropout_ratio),
                TimeLSTM(lstm_Wx1, lstm_Wh1, lstm_b1, stateful=True),
                TimeDropout(dropout_ratio),
                TimeLSTM(lstm_Wx2, lstm_Wh2, lstm_b2, stateful=True),
                TimeDropout(dropout_ratio),
                TimeAffine(embed_W.T, affine_b)  # weight tying!!
            ]
            self.loss_layer = TimeSoftmaxWithLoss()
            self.lstm_layers = [self.layers[2], self.layers[4]]
            self.drop_layers = [self.layers[1], self.layers[3], self.layers[5]]
    
            self.params, self.grads = [], []
            for layer in self.layers:
                self.params += layer.params
                self.grads += layer.grads
    

2.2前向计算

  1. 和之前一样,只不过这里需要先把dropout层设置为训练模式;如下图所示:

    在这里插入图片描述

  2. 代码如下:

        def predict(self, xs, train_flg=False):
            '''
            @param xs: (N, T);输入的数据;
            @param train_flg: 是否是训练阶段的标记;用于dropout层的计算方式选择;
            @return xs: (N,T,V);Time Affine层之后的输出,即得分;'''
            for layer in self.drop_layers:
                # 先将所有的dropout层都设置为训练模式
                layer.train_flg = train_flg
    
            for layer in self.layers:
                xs = layer.forward(xs)
            return xs
    
        def forward(self, xs, ts, train_flg=True):
            '''
            @param xs: (N, T);输入的数据;
            @param ts: (N, T);监督数据;(N,T)时不是独热编码形式;
            @param train_flg: 是否是训练阶段的标记;用于dropout层的计算方式选择;
            @return loss: 损失值;'''
            score = self.predict(xs, train_flg)
            loss = self.loss_layer.forward(score, ts)
            return loss
    

2.3反向传播

  1. 反向传播和LSTMLM一样;另外,重设状态的函数稍有区别;再就是还有加载和保存参数的函数;如下图所示;

    在这里插入图片描述

3相应的学习代码的实现

代码位于:LSTM_model/train_better_LSTMLM.py

  1. 由于这里将Embedding层和Affine层进行权重共享,因此在RNN_model/rnnTrainer.pyRnnlmTrainer.fit函数中,需要在梯度计算之后整合相同的权重对应的梯度值;即加入了remove_duplicate函数;如下图所示;

    在这里插入图片描述

  2. 在学习过程中,每学习一个epoch,就在验证集上计算困惑度,如果困惑度变大了,则降低学习率,让模型训练更加稳定;

    1. 所以这里还需要读取验证集数据;与读取测试集数据一样;同样考虑了GPU的运行;

    2. 我们只需要在调用RnnlmTrainer.fit函数时指定max_epoch=1就可以复用RnnlmTrainer.fit函数的同时,满足此处每个epoch都进行验证的需求;

    3. 验证前通过model.reset_state()重置模型的状态(即Time LSTM层的初始h和c置为空);验证完开启下一轮训练前也重置一次;

      1. 回忆改进前LSTMLM的学习过程,我们好像没有在每个epoch结束后重置状态;这里我自己试了一下在每个epoch开始前都重置初始的状态,结果和没有重置差不多
      2. 那这里重置的意义在于,因为要看在验证集上的表现,因此来自训练集训练好的状态当然不能要,否则就收到训练集影响了;
    4. 通过调用ppl = eval_perplexity(model, corpus_val)在测试集上走一遍前向计算的流程,获取模型在验证集上的困惑度值;读取数据的地方与RnnlmTrainer.fit函数中的有所不同,但本质上是一样的;eval_perplexity函数代码如下:

      def eval_perplexity(model, corpus, batch_size=10, time_size=35):
          print('evaluating perplexity ...')
          corpus_size = len(corpus)
          total_loss, loss_cnt = 0, 0
          max_iters = (corpus_size - 1) // (batch_size * time_size)
          jump = (corpus_size - 1) // batch_size
      
          for iters in range(max_iters):
              xs = np.zeros((batch_size, time_size), dtype=np.int32)
              ts = np.zeros((batch_size, time_size), dtype=np.int32)
              time_offset = iters * time_size # 每过一个iters就跳过每个句子的time_size个单词
              offsets = [time_offset + (i * jump) for i in range(batch_size)]
              for t in range(time_size):
                  for i, offset in enumerate(offsets):
                      # 这里xs和ts是都从corpus中获取;
                      xs[i, t] = corpus[(offset + t) % corpus_size]
                      ts[i, t] = corpus[(offset + t + 1) % corpus_size]
      
              try:
                  xs = to_gpu(xs)
                  ts = to_gpu(ts)
                  loss = model.forward(xs, ts, train_flg=False) # 将dropout层设置为测试模式
              except TypeError:
                  xs = to_gpu(xs)
                  ts = to_gpu(ts)
                  loss = model.forward(xs, ts)
              total_loss += loss
      
              # 实现了一个进度条
              sys.stdout.write('\r%d / %d' % (iters, max_iters))
              sys.stdout.flush()
      
          print('')
          ppl = np.exp(total_loss / max_iters) # 返回验证集上的困惑度
          return ppl
      
  3. 从生成模型开始的学习代码如下:

        # 生成模型
        model = BetterRnnlm(vocab_size, wordvec_size, hidden_size)
        optimizer = SGD(lr)
        trainer = RnnlmTrainer(model, optimizer)
    
        best_ppl = float('inf')
        for epoch in range(max_epoch):
            # 每次的fit都只训练一个epoch,所以传入的max_epoch=1
            trainer.fit(xs, ts, max_epoch=1, batch_size=batch_size, time_size=time_size, max_grad=max_grad)
    
            model.reset_state()
            ppl = eval_perplexity(model, corpus_val) # 在验证集上进行前向计算然后评价困惑度
            print('valid perplexity: ', ppl)
    
            if best_ppl > ppl:
                # 此时这一个epoch的模型效果更好
                best_ppl = ppl
                model.save_params()
            else:
                # 否则困惑度更大,模型效果变差,则调整学习率
                lr /= 4.0
                optimizer.lr = lr
    
            model.reset_state() # 验证完了之后下个epoch训练前还要重置一下h和c
            print('-' * 50)
    
    
        # 基于验证数据进行评价
        model.reset_state()
        ppl_test = eval_perplexity(model, corpus_test)
        print('test perplexity: ', ppl_test)
    
  4. 训练过程和结果如下:

    1. 由于堆叠了Time LSTM层,网络的参数量大幅增加,且相关超参数也增加了;可以看到显卡占用比原来增加了:

      在这里插入图片描述

    2. 每次训练完都会进行验证集上的困惑度的计算:

      在这里插入图片描述

    3. 下图是训练结果;训练集上最终的困惑度降低到了三四十;验证集上困惑度为79;测试集上的困惑度为76;如下图所示:

      在这里插入图片描述

4总结

  1. 本章我们用LSTM来代替RNN,从而缓解梯度消失的问题,能够控制梯度的流动;
  2. 本节咱们加深了神经网络的层数,为了防止过拟合,加入了dropout层;另外,对于能够权重共享的,我们进行权重共享;实现了效果的提升;
  3. 书上还有一部分是其他的一些研究,这里就不说了;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/843428.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【转盘案例-弹框-修改Bug-完成 Objective-C语言】

一、我们来看示例程序啊 1.旋转完了以后,它会弹一个框,这个框,是啥, Alert 啊,AlertView 也行, AlertView,跟大家说过,是吧,演示过的啊,然后,我们就用iOS9来做了啊,完成了以后,我们要去弹一个框, // 弹框 UIAlertController *alertController = [UIAlertContr…

数据结构(栈)

文章目录 一、概念与结构 栈:⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压…

在qt的c++程序嵌入一个qml窗口

//拖拽一个QQuickWidget c端和qml通信的桥梁 找到qml的main.qml的路径 ui->quickWidget->setSource(QUrl::fromLocalFile("../../../code/main.qml"));// QML 与 Qt Widgets 通信//窗口就成了一个类实例对象pRoot (QObject*)ui->quickWidget->rootObje…

操作系统:408考研|王道|学习笔记

系列目录 计算机组成原理 学习笔记I 计算机组成原理 学习笔记II 目录 系列目录第1章 计算机系统概述1.1 操作系统的基本概念1.1_1 操作系统的定义、功能1.1_2 操作系统的特征 1.2 操作系统的发展与分类1.3 操作系统的运行环境🌟1.3_1 操作系统的运行机制&#x1f3…

防溺水预警系统引领水域安全新篇章

一、系统概述 随着人们对水域活动的需求增加,溺水事故频发,给人们的生命安全带来了严重威胁。然而,如今,一项创新科技正在以强大的功能和无限的潜力引领着水域安全的新篇章。智能防溺水预警系统,作为一种集成了智能感知…

SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战

概览 在 SwiftUI 的开发过程中我们常说:“屏幕不够,滚动来凑”。可见滚动视图对于超长内容的呈现有着多么秉轴持钧的重要作用。 这不,从 SwiftUI 5.0(iOS 17)开始苹果又为滚动视图增加了全新的功能。但是官方的示例可…

不懂商业模式,没有互联网思维,不建议创业

在当今商业浪潮中,作为企业的掌舵人,若对商业模式缺乏深刻理解,确实容易陷入亏损与负债的困境,尤其是在互联网与创业领域。成功的企业往往建立在坚实的商业模式之上,这里我将为您概述五种关键模式,助力您把…

【go】Excelize处理excel表 带合并单元格、自动换行与固定列宽的文件导出

文章目录 1 简介2 相关需求与实现2.1 导出带单元格合并的excel文件2.2 导出增加自动换行和固定列宽的excel文件 1 简介 之前整理过使用Excelize导出原始excel文件与增加数据校验的excel导出。【go】Excelize处理excel表 带数据校验的文件导出 本文整理使用Excelize导出带单元…

Android 消息发布订阅框架:EventBus

目录 1.是什么 2.如何使用 3.五种线程模型 4.Eventbus2和Eventbus3的区别 一、是什么 EventBus是一款发布/订阅事件总线的框架,使用它可以进行模块间通信、解耦。它可以使用很少的代码,来实现多组件之间的通信,非常的方便。 为什么使用它…

十七、(正点原子)Linux LCD驱动

一、Framebuffer设备 在 Linux 中应用程序通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。 先来看一下裸机 LCD 驱动如下: ①、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、 hspw、 hbp、 hfp、 vspw…

go中map

文章目录 Map简介哈希表与Map的概念Go语言内建的Map类型Map的声明Map的初始化Map的访问Map的添加和修改Map的删除Map的遍历 Map的基本使用Map的声明与初始化Map的访问与操作Map的删除Map的遍历Map的并发问题实现线程安全的Map 3. Map的访问与操作3.1 访问Map元素代码示例&#…

微信小程序 button样式设置为图片的方法

微信小程序 button样式设置为图片的方法 background-image background-size与background-repeat与border:none;是button必须的 <view style" position: relative;"><button class"customer-service-btn" style"background-image: url(./st…

新华三H3CNE网络工程师认证—VLAN使用场景与原理

通过华三的技术原理与VLAN配置来学习&#xff0c;首先介绍VLAN&#xff0c;然后介绍VLAN的基本原理&#xff0c;最后介绍VLAN的基本配置。 文章目录 一、传统以太网问题1、广播域范围过大2、分割广播域 二、如何实现VLAN1、VLAN技术达到的效果2、VLAN数值的编号范围3、仿真&am…

这届打工人,快把单休卷成职场用工标配了……

最近&#xff0c;小柴有个朋友跟小柴吐槽&#xff0c;自己被「灵活就业」大半年了&#xff0c;有点扛不住&#xff0c;就准备去送外卖&#xff0c;结果第一天赚了38&#xff0c;反手电瓶车罚了50…… 心灰意冷的他&#xff0c;最终还是在某BOSS上充个值&#xff0c;没日没夜的投…

手持式气象站:便携科技,掌握微观气象的利器

手持式气象站&#xff0c;顾名思义&#xff0c;是一种可以随身携带的气象监测设备。它小巧轻便&#xff0c;通常配备有温度、湿度、风速、风向、气压等多种传感器&#xff0c;能够实时测量并显示各种气象参数。不仅如此&#xff0c;它还具有数据存储、数据传输、远程控制等多种…

搭建博客系统#Golang

WANLI 博客系统 项目介绍 基于vue3和gin框架开发的前后端分离个人博客系统&#xff0c;包含md格式的文本编辑展示&#xff0c;点赞评论收藏&#xff0c;新闻热点&#xff0c;匿名聊天室&#xff0c;文章搜索等功能。 点击跳转&#xff1a;Github 项目开源地址 功能展示 B 站…

实战篇(十):使用Processing创建可爱花朵:实现随机位置、大小和颜色的花朵

使用Processing创建可爱花朵 0.效果预览1. 引言2. 设置Processing环境3. 创建花朵类4. 实现花瓣绘制5. 绘制可爱的笑脸6. 鼠标点击生成花朵7. 完整代码8. 总结与扩展0.效果预览 在本教程中,我们将使用Processing编程语言来创建一个可爱的花朵生成器。通过封装花朵为一个类,并…

使用shedlock实现分布式互斥执行

前言 前序章节&#xff1a;springboot基础(82):分布式定时任务解决方案shedlock 如果你不清楚shedlock&#xff0c;建议先阅读前序章节&#xff0c;再来查看本文。 如果我们不在spring环境下&#xff0c;如何使用shedlock实现分布式互斥执行&#xff1f; 我们可以使用shedl…

Elasticsearch 7.x入门学习-Java API操作

1 创建项目 在idea开发工具中创建Maven项目 修改 pom 文件&#xff0c;增加 Maven 依赖关系 <dependencies><dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.8.0</versi…