【自然语言处理NLP】Bert预训练模型、Bert上搭建CNN、LSTM模型的输入、输出详解

一、BertModel的输入和输出

from transformers import BertModel
bert=BertModel.from_pretrained('bert-base-chinese')
out=bert(context, attention_mask=mask)

1. 输入

Bert模型的输入context张量需要满足以下要求:

  1. 张量形状:context应为二维张量,形状为[batch_size, sequence_length],其中

    • batch_size是输入样本的批量大小,
    • sequence_length是输入序列的长度。
  2. 数据类型:context的数据类型应为整数类型,如torch.LongTensor

  3. 值范围:context中的值应该是词汇表中的词索引。通常情况下,词汇表中的特殊符号会被分配预先定义好的索引,例如 [PAD][UNK][CLS][SEP]。其余的词将被映射到词汇表中的相应索引。

另外,为了有效控制模型的注意力,提高计算效率,可以使用 attention_mask 张量。attention_mask 是一个与输入张量形状相同的二进制张量(0和1组成),用于指示哪些位置是有效的(1表示有效)和哪些位置是填充的(0表示填充)。填充位置的注意力权重将被设为零,因此模型不会关注填充位置。

注意,输入张量的长度限制取决于Bert模型的最大序列长度限制。超过最大长度的部分需要进行截断或者其他处理。

总结起来,Bert模型的输入context张量应为二维整数张量,形状为[batch_size, sequence_length],并且可以结合使用attention_mask张量来标识填充位置。

2. 输出

如上边调用Bert模型时,输出结果out中包含last_hidden_state、pooler_output、hidden_states、past_key_values、attentions、cross_attentions几个属性。

以下是在BERT模型的输出中的各个属性的含义:

  1. last_hidden_state: 这是BERT模型最后一个隐藏层的输出。它是一个形状为 [batch_size, sequence_length, hidden_size] 的张量,表示每个输入令牌的上下文相关表示。这个张量包含了输入序列中每个位置的隐藏状态信息。

last_hidden_state[:,0] 表示BERT模型输出的最后一个隐藏层的所有令牌的第一个位置(即 [CLS] 令牌)的表示。
在BERT模型中,通常在输入序列的开头添加一个特殊的 [CLS] 令牌,用于表示整个序列的汇总信息。last_hidden_state[:,0] 提取了这个 [CLS] 令牌的表示,它是一个形状为 [batch_size, hidden_size] 的张量。
这个 [CLS] 令牌的表示可以用作整个序列的汇总或句子级别的表示,通常用于下游任务的分类或句子级别的特征提取。在一些任务中,可以将 last_hidden_state[:,0] 作为整个序列的表示,用于进行情感分类、文本匹配等任务。
需要注意的是,last_hidden_state[:,0] 是一个针对每个样本的表示,如果批量处理了多个样本,则 batch_size 的维度将对应于样本的数量。

  1. pooler_output: 这是BERT模型经过池化操作得到的输出。它是一个形状为 [batch_size, hidden_size] 的张量,表示整个输入序列的池化表示。它通常被用作句子级别的表示,用于下游任务的分类或句子级别的特征提取。

  2. hidden_states: 这是BERT模型中所有隐藏层的输出。它是一个包含每个隐藏层输出的列表,其中每个元素的形状为 [batch_size, sequence_length, hidden_size]hidden_states[0] 表示第一个隐藏层的输出,hidden_states[1] 表示第二个隐藏层的输出,以此类推,hidden_states[-1] 表示最后一个隐藏层的输出(即 last_hidden_state)。这些隐藏层输出可以用于更详细的分析或进行一些特殊任务。

  3. past_key_values: 这是用于生成下一个预测令牌的先前键值对。它是一个元组,其中包含了前几次调用BERT模型时生成的先前键值对。它通常在生成任务(如文本生成)中使用,以便在多步预测中保留先前的状态信息。

  4. attentions: 这是自注意力机制产生的注意力权重。它是一个列表,包含每个注意力头的注意力权重矩阵。注意力权重矩阵的形状为 [batch_size, num_heads, sequence_length, sequence_length],表示模型在每个位置上关注其他位置的程度。

  5. cross_attentions: 这是BERT模型中的交叉注意力机制产生的注意力权重。它是一个列表,包含每个交叉注意力头的注意力权重矩阵。注意力权重矩阵的形状为 [batch_size, num_heads, sequence_length, sequence_length],表示模型在每个位置上关注另一个输入序列(如句子级别的任务中的两个句子)的程度。

这些属性提供了BERT模型在不同层级和注意力机制上的输出信息,可以根据任务的需求选择合适的属性来使用。


二、CNN的输入和输出

from transformers import BertModel
import torch.nn.functional as F

def conv_and_pool(self, x, conv):
    x = F.relu(conv(x)).squeeze(3)  #[batch_size, out_channels, output_length]
    x = F.max_pool1d(x, x.size(2)).squeeze(2)   #[batch_size, channels]
    return x

num_filters = 256
filter_sizes = (2, 3, 4)
convs = nn.ModuleList(
            [nn.Conv2d(1, config.num_filters, (k, config.hidden_size))
             for k in config.filter_sizes])
             
bert=BertModel.from_pretrained('bert-base-chinese')

encoder_out = self.bert(context, attention_mask=mask).last_hidden_state   #[batch_size, sequence_length, hidden_size]
out = encoder_out.unsqueeze(1) # [batch_size, 1(in_channels), sequence_length, hidden_size]
out = torch.cat([self.conv_and_pool(out, conv) for conv in self.convs], 1)  #[batch_size, channels*len(self.convs)]

1. nn.Conv2d

convs = nn.ModuleList([nn.Conv2d(1, num_filters, (k, hidden_size)) for k in config.filter_sizes])

这行代码定义了一个卷积层的列表 convs,其中每个卷积层都是通过 nn.Conv2d 创建的。

nn.Conv2d 是PyTorch中用于定义二维卷积层的类。在这里,通过使用 nn.Conv2d(1, config.num_filters, (k, config.hidden_size)),创建了一个卷积层对象。
每一个卷积层的输入是一个四维张量,形状为 [batch_size, in_channels, sequence_length, embedding_size],参数解释如下:

  • batch_size 是输入样本的批量大小。
  • in_channels 是输入通道数,对于文本数据通常为1,表示单通道输入。
  • sequence_length 是输入序列的长度,即令牌的数量。
  • embedding_size 是输入序列中每个令牌的嵌入维度。

通过使用列表推导式和 nn.ModuleList,将多个卷积层对象组成一个列表 self.convs。这样就创建了一个由多个卷积层组成的模块列表。

在该代码段中,config.filter_sizes 是一个元组,包含了多个卷积核的宽度。具体地,代码使用列表推导式和 nn.ModuleList 创建了三个卷积层对象,对应于宽度为2、3和4的卷积核。

这样设计的目的可能是为了在文本分类等任务中使用多尺度卷积操作,从不同的窗口尺寸中提取特征。每个卷积核会产生一个输出特征图,这些特征图将被用于后续的处理或分类任务。通过使用不同宽度的卷积核,模型能够同时捕捉不同范围的语义信息,从而提高模型对输入文本的理解能力。

2. conv(out)

conv=nn.Conv2d(1, num_filters, (k, config.hidden_size))

  1. out 是经过卷积层之前的输入张量,形状为 [batch_size, in_channels, sequence_length, hidden_size]

    • batch_size 是输入样本的批量大小。
    • in_channels 是输入通道数,通常为 1,因为在这个例子中,输入是一维序列。
    • sequence_length 是输入序列的长度。
    • hidden_size 是隐藏维度,即每个位置的特征向量的维度。
  2. conv(out) 是经过卷积操作后的输出张量,形状为 [batch_size, out_channels, output_length, feature_size]

    • batch_size 与输入张量相同。
    • out_channels 是卷积层的输出通道数,由 config.num_filters 决定。
    • output_length 是经过卷积操作后的输出序列长度,取决于输入序列的长度、卷积核大小和填充方式。
    • feature_size 是每个位置的特征向量的维度,由卷积核大小和隐藏维度决定。

feature_size的计算方法

要计算 feature_size,需要知道卷积核的大小和隐藏层的维度。

假设卷积核的大小为 (k, hidden_size),其中 k 是卷积核的宽度,hidden_size 是隐藏层的维度。在二维卷积操作中,卷积核在两个维度上滑动,分别为序列长度和隐藏层维度。

定义序列长度output_length和隐藏层维度feature_size分别为H'W'对于二维卷积操作,输出张量的维度计算公式为:

[batch_size, out_channels, H', W']

其中,

  • batch_size是批量大小,
  • out_channels是输出通道数(卷积核个数),
  • H'是输出特征图的高度,计算公式为:H' = H - kernel_size[0] + 1(默认stride为1),
  • W'是输出特征图的宽度,计算公式为:W' = W - kernel_size[1] + 1(默认stride为1),

综上所述,当卷积核大小为 (k, hidden_size) 时,执行卷积操作后,输出张量的形状为 [batch_size, out_channels, output_length, 1],其中 out_channels 是卷积层的输出通道数,output_length 是根据输入序列长度和卷积核大小计算得到的输出序列长度,1 是隐藏层维度(hidden_size-hidden_size+1),也是每个位置的特征向量的维度(feature_size)。

3. F.max_pool1d

F.max_pool1d 是 PyTorch 中用于一维最大池化操作的函数,它的输入和输出张量维度要求如下:

  1. 输入张量的维度要求:输入张量的形状应为 [batch_size, channels, sequence_length],其中

    • batch_size 是输入样本的批量大小,
    • channels 是输入通道数,通常对应卷积层的输出通道数,
    • sequence_length 是输入序列的长度。
  2. 输出张量的维度:输出张量的形状与输入张量的形状相同,即 [batch_size, channels, output_length],其中

    • batch_size 与输入张量相同,
    • channels 与输入张量相同,
    • output_length 是经过最大池化操作后的输出序列长度,它取决于池化窗口大小、步幅和填充方式。
  3. x = torch.nn.functional.max_pool1d(x, x.size(2))

    • x 是输入张量,假设形状为 [batch_size, channels, sequence_length]
    • x.size(2) 返回输入张量 x 在第三个维度上的大小,即输入序列的长度 sequence_length

需要注意的是,F.max_pool1d 只能在输入张量的最后一个维度上进行池化操作,即在序列维度上进行池化。池化窗口的大小、步幅以及填充方式可以通过参数进行指定。在进行一维最大池化操作时,每个窗口中的最大值将被提取出来形成输出张量。

如果输入张量的形状不符合要求,可以使用相应的函数进行形状调整,如 torch.unsqueeze 来增加维度或 torch.transpose 进行维度交换,以满足 F.max_pool1d 函数的要求。

4. Bert预训练模型上叠加CNN模型

要在BERT预训练模型的基础上叠加CNN模型用于分类,可以考虑使用模型的输出 last_hidden_statepooler_output 作为卷积层的输入具有不同的特点和适用性:

  1. last_hidden_statelast_hidden_state 是BERT模型最后一个隐藏层的输出,它是一个形状为 [batch_size, sequence_length, hidden_size] 的张量。在使用 last_hidden_state 作为卷积层的输入时,可以考虑以下情况:

    • 适用性:last_hidden_state 包含了每个输入令牌的上下文相关表示,可以捕捉到输入序列的详细信息。因此,它适用于需要使用局部特征进行分类或处理的任务,例如文本分类、命名实体识别等。通过卷积操作,可以提取不同尺寸的局部特征,以便对输入进行更细粒度的分析和建模。
    • 注意事项:由于 last_hidden_state 的形状是 [batch_size, sequence_length, hidden_size],在应用卷积操作之前,需要将其转换为 [batch_size, 1, sequence_length, hidden_size] 的四维张量,以匹配卷积层的输入要求。
  2. pooler_outputpooler_output 是BERT模型经过池化操作得到的输出,它是一个形状为 [batch_size, hidden_size] 的张量。在使用 pooler_output 作为卷积层的输入时,可以考虑以下情况:

    • 适用性:pooler_output 可以看作是整个输入序列的池化表示,具有更高级别的语义信息。因此,它适用于对整个序列进行分类或处理的任务,例如句子级情感分类、文本相似度等。通过卷积操作,可以进一步提取 pooler_output 中的特征,以便对输入序列进行更深入的分析和建模。
    • 注意事项:由于 pooler_output 的形状是 [batch_size, hidden_size],在应用卷积操作之前,需要将其转换为 [batch_size, 1, 1, hidden_size] 的四维张量,以匹配卷积层的输入要求。

在实际应用中,选择使用哪个输出作为卷积层的输入取决于任务需求和数据特点。如果任务需要更详细的局部特征,可以使用 last_hidden_state;如果任务更关注整体语义信息或句子级别的表示,可以使用 pooler_output。同时,还可以尝试不同的组合和变体,以找到最适合任务的输入表示。

三、lstm的输入和输出

from transformers import BertModel
from torch import nn

bert=BertModel.from_pretrained('bert-base-chinese')
lstm=nn.LSTM(input_size, rnn_hidden_size, num_layers, bidirectional=True, batch_first=True, dropout=config.dropout)
# nn.LSTM(输入特征大小, 隐藏状态大小, lstm层数, 是否为双向, 输入张量第一维是否为批量维度, 丢弃率, bias=True是否使用偏置项)

encoder_out= bert(context, attention_mask=mask).last_hidden_state   # [batch_size, sequence_length, hidden_size]
out, _ = self.lstm(encoder_out)

1.默认batch_first=False

nn.LSTM()函数的输入参数如下:

  • input_size:输入特征的大小。
  • hidden_size:隐藏状态的大小。
  • num_layers:LSTM的层数。
  • bias:是否使用偏置项,默认为True。
  • batch_first:输入张量是否具有批量维度在第一维,默认为False。
  • dropout:应用于LSTM层输出的丢弃率,默认为0。
  • bidirectional:是否使用双向LSTM,默认为False。

该模型的输入参数和输出结果的类型和维度如下:

  • 输入参数:

    • input:形状为 [sequence_length, batch_size, input_size] 的输入张量,其中
      • sequence_length 是输入序列的长度,
      • batch_size 是输入样本的批量大小,
      • input_size 是输入特征的大小。
    • h_0:形状为 [num_layers * num_directions, batch_size, hidden_size] 的初始隐藏状态张量,其中
      • num_layers 是LSTM的层数,
      • num_directions 是LSTM的方向数(双向为2,单向为1),
      • batch_size 是输入样本的批量大小,
      • hidden_size 是隐藏状态的大小。
    • c_0:形状为 [num_layers * num_directions, batch_size, hidden_size] 的初始细胞状态张量,具有与 h_0 相同的维度。
  • 输出结果:

    • output:形状为 [sequence_length, batch_size, num_directions * hidden_size] 的输出序列张量,其中
      • sequence_length 是输入序列的长度,
      • batch_size 是输入样本的批量大小,
      • num_directions 是LSTM的方向数(双向为2,单向为1),
      • hidden_size 是隐藏状态的大小。
    • h_n:形状为 [num_layers * num_directions, batch_size, hidden_size] 的最后一个时间步的隐藏状态张量,具有与 h_0 相同的维度。
    • c_n:形状为 [num_layers * num_directions, batch_size, hidden_size] 的最后一个时间步的细胞状态张量,具有与 h_0 相同的维度。

请注意,输入参数和输出结果的维度和类型是基于输入张量和参数的实际形状和设置。上述描述是一般情况下的示例,具体的维度和类型可能会因具体的输入数据形状和模型参数而有所不同。

示例:

import torch
import torch.nn as nn

input_size = 10
hidden_size = 20
num_layers = 2
batch_size = 4
sequence_length = 6
num_directions = 1

lstm = nn.LSTM(input_size, hidden_size, num_layers, bidirectional=False, batch_first=False)

input = torch.randn(sequence_length, batch_size, input_size)
h_0 = torch.randn(num_layers * num_directions, batch_size, hidden_size)
c_0 = torch.randn(num_layers * num_directions, batch_size, hidden_size)

output, (h_n, c_n) = lstm(input, (h_0, c_0))

print("Output shape:", output.shape)
print("Hidden state shape:", h_n.shape)
print("Cell state shape:", c_n.shape)

2. 设置batch_first=True

如果在使用nn.LSTM()时设置了batch_first=True,那么输入张量的形状为 [batch_size, sequence_length, hidden_size],其中:

  • batch_size 表示批量大小,即输入数据中的样本数量。
  • sequence_length 表示序列的长度,即每个样本的时间步数或序列长度。
  • hidden_size 表示 LSTM 模型的隐藏状态的维度。

在这种设置下,nn.LSTM()的输出张量的形状为 [batch_size, sequence_length, num_directions * hidden_size],其中:

  • num_directions 表示 LSTM 模型的方向数,通常为 1(单向 LSTM)或 2(双向 LSTM)。
  • hidden_size 表示 LSTM 模型的隐藏状态的维度。

输出张量中的 num_directions 是由 LSTM 模型的 bidirectional 参数决定的。如果 bidirectional=True,则输出张量中的 num_directions 为 2,即包括正向和反向的隐藏状态;如果 bidirectional=False,则输出张量中的 num_directions 为 1,即只包括正向的隐藏状态。

因此,当 batch_first=True 时,nn.LSTM() 模型的输出张量形状为 [batch_size, sequence_length, num_directions * hidden_size]

注意
如果想在Bert预训练模型输出的基础上增加LSTM层,由于Bert模型的输出out.last_hidden_state张量的形状一致,可以直接作为输入传入LSTM层,无需像CNN一样进行形状转换。此时,应该将batch_first设置为True

你可能感兴趣的:(自然语言处理NLP,自然语言处理,bert,cnn)