百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

NLP中各框架对变长序列的处理全解

toyiye 2024-06-21 12:37 10 浏览 0 评论

在NLP中,文本数据大都是变长的,为了能够做batch的训练,需要padding到相同的长度,并在实际训练中忽略padding部分的影响。

在不同的深度学习框架中,对变长序列的处理,本质思想都是一致的,但具体的实现方式有较大差异,下面针对Pytorch、Keras和TensorFlow三大框架,以LSTM模型为例,说明各框架对NLP中变长序列的处理方式和注意事项。


Pytorch

在pytorch中,是用的torch.nn.utils.rnn中的 pack_padded_sequence 和 pad_packed_sequence 来处理变长序列,前者可以理解为对 padded 后的 sequence 做pack(打包/压紧),也就是去掉 padding 位,但会记录每个样本的有效长度信息;后者是逆操作,对 packed 后的 sequence 做 pad,恢复到相同的长度。

不过在使用过程中,要格外注意 pack_padded_sequence 的 enforce_sorted 参数和 pad_packed_sequence 的total_length 参数。

pack_padded_sequence

下面是pack_padded_sequence函数的部分 Pytorch 源码,input就是输入的一个batch的tensor,lengths是这个batch中每个样本的有效长度。

def pack_padded_sequence(input, lengths, batch_first=False, enforce_sorted=True):
    ...
    if enforce_sorted:
        sorted_indices = None
    else:
        lengths, sorted_indices = torch.sort(lengths, descending=True)
        sorted_indices = sorted_indices.to(input.device)
        batch_dim = 0 if batch_first else 1
        input = input.index_select(batch_dim, sorted_indices)
    ...

在pack_padded_sequence处理之后,会得到一个 PackedSequence 的数据,其除了记录Tensor data之外,还会记录 batch_sizes, sorted_indices 和 unsorted_indices,其中 batch_sizes 是将输入按照有效长度排序之后,每个时间步对应的batch大小,后面会有例子;sorted_indices就是对输入lengths排序后的索引,unsorted_indices 是用来将排序数据恢复到原始顺序的索引。

在pack_padded_sequence中,enforce_sorted默认设置为True,也就是说输入的batch数据要事先按照长度排序,才能输入,实际上,更简单的方式是,将其设置为False,从上面的代码中也可以看出,Pytorch会自动给我们做排序。

注:torch1.1及之后才有 enforce_sorted 参数,因此 torch1.1 之后才有自动排序功能。

一个简单的例子:

# input_tensor shape:batch_size=2,time_step=3,dim=1
input_tensor = torch.FloatTensor([[4, 0, 0], [5, 6, 0]]).resize_(2, 3, 1)
seq_lens = torch.IntTensor([1, 2])
x_packed = nn_utils.rnn.pack_padded_sequence(input_tensor, seq_lens, batch_first=True, enforce_sorted=False)

输出的 x_packed 为:

PackedSequence(data=tensor([[5.],
        [4.],
        [6.]]), batch_sizes=tensor([2, 1]), sorted_indices=tensor([1, 0]), unsorted_indices=tensor([1, 0]))

在上面的例子中,首先,经过pack_padded_sequence 内部按有效长度逆序排列之后,输入数据会变成:

[[5, 6, 0],
[4, 0, 0]]

PackedSequence中的data是按照 time_step 这个维度,也就是按列来记录数据的,但是不包括padding位



该图仅作为理解参考,图片来自:https://www.cnblogs.com/lindaxin/p/8052043.html

batch_sizes 记录的每列有几个数据是有效的,也就是每列有效的 batch_size 长度,但是不包括为0的长度,因此上面例子中,x_packed 的 batch_sizes=tensor([2, 1]),因此,每个time_step 只需要传入对应batch_size个数据即可,可以减少计算量。

要注意的是,batch_sizes这个tensor的长度是2,而input_tensor的time_step是3,因为 batch_sizes 不包含都是padding的时间步,也就是上面的第三列,因此后面的pad_packed_sequence 要注意设置total_length参数。


pad_packed_sequence

下面是pad_packed_sequence函数的部分 Pytorch 源码,输入sequence是 PackedSequence 型数据。pad_packed_sequence 实际上就是做一个padding 操作和根据索引恢复数据顺序操作。

def pad_packed_sequence(sequence, batch_first=False, padding_value=0.0, total_length=None):
    max_seq_length = sequence.batch_sizes.size(0)
    if total_length is not None:
        max_seq_length = total_length
    ...

这里要注意的一个参数是total_length,它是 sequence 需要去被padding的长度,我们期望的一般都是padding到和输入序列一样的 time_step 长度 ,但是PackedSequence 型数据并没有记录这个数据,因此它用的是sequence.batch_sizes.size(0),也就是 batch_sizes 这个tensor的长度。

上面已经提到,batch_sizes 不包含都是padding的时间步,这样,如果整个batch中的每条记录有都做padding,那batch_sizes 这个tensor的长度就会小于time_step ,就像上面代码中的例子。

这时如果没有设置total_length,pad_packed_sequence就不会padding到我们想要的长度。

可能你在实际使用时,不设置 total_length 参数也没有出现问题,那大概率是因为你的每个batch中,都有至少一条记录没有padding位,也就是它的每一步都是有效位,那sequence.batch_sizes.size(0)就等于time_step。


使用方式

为了方便使用,这里将pack_padded_sequence,LSTM和 pad_packed_sequence 做了一个封装,参数和原始LSTM一样,唯一的区别是使用中要输入 seq_lens 数据。

class MaskedLSTM(Module):
    def __init__(self, input_size, hidden_size, num_layers=1, bias=True, batch_first=False, dropout=0., bidirectional=False):
        super(MaskedLSTM, self).__init__()
        self.batch_first = batch_first
        self.lstm = LSTM(input_size, hidden_size, num_layers=num_layers, bias=bias,
             batch_first=batch_first, dropout=dropout, bidirectional=bidirectional)
?
    def forward(self, input_tensor, seq_lens):
        # input_tensor shape: batch_size*time_step*dim , seq_lens: (batch_size,)  when batch_first = True
        total_length = input_tensor.size(1) if self.batch_first else input_tensor.size(0)
        x_packed = pack_padded_sequence(input_tensor, seq_lens, batch_first=self.batch_first, enforce_sorted=False)
        y_lstm, hidden = self.lstm(x_packed)
        y_padded, length = pad_packed_sequence(y_lstm, batch_first=self.batch_first, total_length=total_length)
        return y_padded, hidden

小总结:

使用pack_padded_sequence和pad_packed_sequence之后,LSTM输出对应的padding位是全0的,隐藏层输出 (h_n,c_n) 都是不受padding影响的,都是padding前最后一个有效位的输出,而且对单向/双向LSTM都是没有影响的,因为padding位不参与运算,即减少了不必要的计算,又避免了padding位对输出的影响。


Keras

在keras中,自带有Masking层,简单方便,使用了一个mask操作则可以贯穿后面的整个模型,实际的过程是把一个布尔型的mask矩阵一直往下游传递下去,当然这个矩阵的维度会根据当前层的维度情况重新调整,以使其能在下游层中被使用。

确实方便,但也因此丢失了灵活性,如果使用了mask,则后面层都要支持mask,否则会报异常,这对于一些不支持mask的层,例如Flatten、AveragePooling1D等等,并不是很友好。

keras中对于变长序列的处理,一般使用Masking层,如果需要用到Embedding层,那可以直接在Embedding中设置mask_zero=True,就不需要再加Masking层了,但本质上都是建了布尔型的mask矩阵并往下游传递下去。

下面是Masking和Embedding层的定义

Masking(mask_value=0.,input_shape=(time_step,feature_size))
Embedding(input_dim, output_dim, mask_zero=False, input_length=None)

下面是 Embedding 层中的mask计算函数,如果 mask_zero 设置为True,那这里会计算mask矩阵并往后传递,如果要继续深入其传递的机制,建议看keras源码,也可以参考一下这个:keras源码分析之Layer:https://blog.csdn.net/u012526436/article/details/98206560

# Embedding 层中的mask计算函数
def compute_mask(self, inputs, mask=None):
    if not self.mask_zero:
        return None
    output_mask = K.not_equal(inputs, 0)
    return output_mask

不过要注意的一点是,mask_zero 设置为True,输入通过Embedding后,padding位所对应的向量并不是全0,仍然是一个随机的向量,和mask_zero 的值没有关系,mask_zero 只是影响是否计算mask矩阵。但是有了mask矩阵之后,padding位都不会被计算,因此,其对应向量的值并不重要。

使用方式

input = keras.layers.Input((time_step,feature_size))
mask = keras.layers.Masking(mask_value=0, input_shape=(time_step,feature_size))(input)
lstm_output = keras.layers.LSTM(hidden_size, return_sequences=True)(mask)
model = Model(input, lstm_output)

input = keras.layers.Input((time_step,))
embed = keras.layers.Embedding(vocab_size, embedding_size, mask_zero=True)(input)
lstm_output = keras.layers.LSTM(hidden_size, return_sequences=True)(emd)
model = Model(input, lstm_output)

keras模型中,Masking之后的层,只要支持mask,都不用再手动创建mask了,当然,如果是自己定义的层,要支持mask,需要设置supports_masking=True,并实现自己的 compute_mask 函数。

要注意的是,和pytorch、TF有些不一样的地方,对于有了Masking层之后的LSTM,padding位的输出不会是全0,而是最后一位有效位的输出,也就是padding位输出都复制了最后有效位的输出。

Embedding层和Masking层都有mask功能,但与Masking层不同的是,Embedding它只能过滤0,不能指定其他字符。


TensorFlow

在 TF (tf 1.x) 中是通过 dynamic_rnn 来实现变长序列的处理,它和pytorch的pack_padded_sequence一样,也有 sequence_length 参数,但它相对比pytorch更方便,不用手动去pack和pad,只要传递sequence_length 参数,其他都由 dynamic_rnn 来完成。

但是TF中 dynamic_rnn 计算的循环次数仍然是time_steps 次,并没有带来计算效率上的提升。sequence length 的作用只是在每个序列达到它的实际长度后,把后面时间步的输出全部置成零、状态全部置成实际长度那个时刻的状态。

这一点可以参考:https://www.zhihu.com/question/52200883


使用方式

# 静态图定义部分
basic_cell = tf.nn.rnn_cell.LSTMCell(hidden_size)
X = tf.placeholder(tf.float32, shape=[None, time_step, dim])
seq_length = tf.placeholder(tf.int32, [None])
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32, sequence_length=seq_length)

不过,在 TF 2.x 中废弃了tf.nn.dynamic_rnn函数,官方建议使用 tf.keras 来处理变长序列,也就是和上面的 Keras 框架的处理方式是一样的。


以上是从应用和代码的角度,介绍了Pytorch、Keras和TensorFlow三大框架对变长数据的处理和使用方式,但这不仅仅适用于NLP领域,只是在NLP中变长数据更为常见,希望能帮助你在工程实践中更好地去处理变长的数据。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码