使用循环网络的电影评论分类

Posted

技术标签:

【中文标题】使用循环网络的电影评论分类【英文标题】:Movie Review Classification with Recurrent Networks 【发布时间】:2021-06-23 02:32:33 【问题描述】:

据我所知和研究,数据集中的序列可以有不同的长度;只要训练过程中的每个批次都包含相同长度的序列,我们就不需要填充或截断它们。

为了实现和应用它,我决定将批量大小设置为 1,并在 IMDB 电影分类数据集上训练我的 RNN 模型。我添加了我在下面编写的代码。

import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.datasets import imdb
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import SimpleRNN
from tensorflow.keras.layers import Embedding

max_features = 10000
batch_size = 1

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)

model = Sequential()
model.add(Embedding(input_dim=10000, output_dim=32))
model.add(SimpleRNN(units=32, input_shape=(None, 32)))
model.add(Dense(1, activation="sigmoid"))
model.compile(optimizer="rmsprop", 
                  loss="binary_crossentropy", metrics=["acc"])

history = model.fit(x_train, y_train, 
                     batch_size=batch_size, epochs=10, 
                     validation_split=0.2)
acc = history.history["acc"]
loss = history.history["loss"]
val_acc = history.history["val_acc"]
val_loss = history.history["val_loss"]

epochs = range(len(acc) + 1)
plt.plot(epochs, acc, "bo", label="Training Acc")
plt.plot(epochs, val_acc, "b", label="Validation Acc")
plt.title("Training and Validation Accuracy")
plt.legend()
plt.figure()
plt.plot(epochs, loss, "bo", label="Training Loss")
plt.plot(epochs, val_loss, "b", label="Validation Loss")
plt.title("Training and Validation Loss")
plt.legend()
plt.show()

我遇到的错误是由于输入numpy数组中的列表组件而无法将输入转换为张量格式。但是,当我更改它们时,我继续收到类似类型的错误。

错误信息:

ValueError: Failed to convert a NumPy array to a Tensor (Unsupported object type list).

我无法处理这个问题。有人可以在这一点上帮助我吗?

【问题讨论】:

【参考方案1】:

使用序列填充

有两个问题。您需要首先在文本序列上使用pad_sequences。而且SimpleRNN 中也没有这样的参数input_shape。试试下面的代码:

max_features = 20000  # Only consider the top 20k words
maxlen = 200  # Only consider the first 200 words of each movie review
batch_size = 1

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), "Training sequences")
print(len(x_test), "Validation sequences")
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = tf.keras.preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)


model = Sequential()
model.add(Embedding(input_dim=max_features, output_dim=32))
model.add(SimpleRNN(units=32))
model.add(Dense(1, activation="sigmoid"))

model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["acc"])
history = model.fit(x_train, y_train, batch_size=batch_size, 
                         epochs=10, validation_split=0.2)

Here是官方代码示例,希望对你有所帮助。


在嵌入层中使用带有掩码的序列填充

根据您的 cmets 和信息,似乎可以使用 可变长度 输入序列,请检查 this 和 this。但是,我可以说,在大多数情况下,从业者更愿意pad 序列长度一致;因为它很有说服力。选择非均匀或可变输入序列长度是一种特殊情况;类似于我们想要视觉模型的可变输入图像大小。

但是,我们将在此处添加有关 padding 的信息,以及我们如何通过 mask 输出训练时间的填充值,这在技术上似乎是可变长度的输入训练。希望能说服你。我们先来了解pad_sequences 是做什么的。通常在序列数据中,每个训练样本的长度不同是很常见的情况。让我们考虑以下输入:

raw_inputs = [
    [711, 632, 71],
    [73, 8, 3215, 55, 927],
    [83, 91, 1, 645, 1253, 927],
]

这 3 个训练样本的长度不同,分别为 3、5 和 6。我们接下来要做的是通过添加一些值(通常是 0-1)使它们的长度相等 - 无论是在序列的开头还是结尾。

tf.keras.preprocessing.sequence.pad_sequences(
    raw_inputs, maxlen=6, dtype="int32", padding="pre", value=0.0
)

array([[   0,    0,    0,  711,  632,   71],
       [   0,   73,    8, 3215,   55,  927],
       [  83,   91,    1,  645, 1253,  927]], dtype=int32)

我们可以设置padding = "post"在序列末尾设置pad值。但它建议在使用RNN 层时使用"post" 填充,以便能够使用层的CuDNN 实现。但是,仅供参考,您可能会注意到我们设置了maxlen = 6,这是最高的输入序列长度。但它不一定是最高的输入序列长度,因为如果数据集变得更大,它可能会变得计算量大。我们可以将其设置为5,假设我们的模型可以在这个长度内学习特征表示,它是一种超参数。这带来了另一个参数truncating

tf.keras.preprocessing.sequence.pad_sequences(
    raw_inputs, maxlen=5, dtype="int32", padding="pre", truncating="pre", value=0.0
)

array([[   0,    0,  711,  632,   71],
       [  73,    8, 3215,   55,  927],
       [  91,    1,  645, 1253,  927]], dtype=int32

好的,现在我们有一个填充输入序列,所有输入都是统一长度的。现在,我们可以mask 在训练时取出那些额外的填充值。我们将告诉模型数据的某些部分是填充的,应该忽略这些。该机制是屏蔽。因此,这是一种告诉 sequence-processing 层输入中缺少某些时间步长的方法,因此在处理数据时应该跳过。在Keras模型中引入输入ma​​sks有三种方式:

添加keras. layers.Masking layer。 使用mask_zero=True 配置keras.layers.Embedding 层。 在调用支持此参数的图层时手动传递掩码参数(例如RNN 图层)。

这里我们只通过配置Embedding层来展示。它有一个名为mask_zero 的参数,默认设置为False。如果我们将其设置为True,那么将跳过包含序列中索引的0False 条目表示应该在处理过程中忽略相应的时间步

padd_input = tf.keras.preprocessing.sequence.pad_sequences(
    raw_inputs, maxlen=6, dtype="int32", padding="pre", value=0.0
)
print(padd_input)

embedding = tf.keras.layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)
masked_output = embedding(padd_input)
print(masked_output._keras_mask)

[[   0    0    0  711  632   71]
 [   0   73    8 3215   55  927]
 [  83   91    1  645 1253  927]]

tf.Tensor(
[[False False False  True  True  True]
 [False  True  True  True  True  True]
 [ True  True  True  True  True  True]], shape=(3, 6), dtype=bool)

这是在 Embedding(Layer) 类中的计算方式。

  def compute_mask(self, inputs, mask=None):
    if not self.mask_zero:
      return None

    return tf.not_equal(inputs, 0)

这里有一个问题,如果我们将mask_zero 设置为True,则索引0 不能在词汇表中使用。根据文档

mask_zero:布尔值,输入值 0 是否是一个特殊的“填充”值,应该被屏蔽掉。这在使用可能采用可变长度输入的循环层时很有用。如果这是True,那么模型中的所有后续层都需要支持屏蔽,否则将引发异常。如果 mask_zero 设置为 True,则索引 0 不能在词汇表中使用(input_dim 应该等于词汇表的大小 + 1)。

所以,我们至少必须使用max_features + 1。 Here 是一个很好的解释。


这是使用这些代码的完整示例。

# get the data 
(x_train, y_train), (_, _) = imdb.load_data(num_words=max_features)
print(x_train.shape)

# check highest sequence lenght 
max_list_length = lambda list: max( [len(i) for i in list])
print(max_list_idx(x_train))

max_features = 20000  # Only consider the top 20k words
maxlen = 350  # Only consider the first 350 words out of `max_list_idx(x_train)`
batch_size = 512

print('Length ', len(x_train[0]), x_train[0])
print('Length ', len(x_train[1]), x_train[1])
print('Length ', len(x_train[2]), x_train[2])

# (1). padding with value 0 at the end of the sequence - padding="post", value=0.
# (2). truncate 'maxlen' words 
# out of `max_list_idx(x_train)` at the end - maxlen=maxlen, truncating="post"
x_train = tf.keras.preprocessing.sequence.pad_sequences(x_train, 
                                  maxlen=maxlen, dtype="int32", 
                                  padding="post", truncating="post", 
                                  value=0.)

print('Length ', len(x_train[0]), x_train[0])
print('Length ', len(x_train[1]), x_train[1])
print('Length ', len(x_train[2]), x_train[2])

您的模型定义现在应该是

model = Sequential()
model.add(Embedding(
           input_dim=max_features + 1,
           output_dim=32, 
           mask_zero=True))
model.add(SimpleRNN(units=32))
model.add(Dense(1, activation="sigmoid"))
model.compile(optimizer="rmsprop", loss="binary_crossentropy", metrics=["acc"])
history = model.fit(x_train, y_train, 
                    batch_size=256, 
                    epochs=1, validation_split=0.2)

639ms/step - loss: 0.6774 - acc: 0.5640 - val_loss: 0.5034 - val_acc: 0.8036

参考文献

Masking and padding with Keras Embedding layer, - Pads sequences Recurrent Neural Networks (RNN) with Keras

【讨论】:

先生,我有一个问题,***.com/questions/68429393/…。但是在按照第一个答案的建议将每一列转换为张量之后。如何实现 lstm 或其他模型。有人建议使用 pad_sequence,但我不确定它是如何完成的。你能在那里回答吗?【参考方案2】:

无序列填充

填充对于序列建模中的输入序列的可变长度不是必须的。在TensorFlow 中,沿某个轴具有可变数量元素的张量称为ragged,我们将tf.ragged.RaggedTensor 用于不规则数据。例如:

# variable length input sequences 
ragged_list = [
    [0, 1, 2, 3],
    [4, 5],
    [6, 7, 8],
    [9]]

# convert to ragged tensor that handle such variable length inputs 
tf.ragged.constant(ragged_list).shape
shape: [4, None]

因此,我们可以在序列建模中使用参差不齐的输入数据,并且我们不再需要为统一的输入长度填充序列。


数据集

import tensorflow as tf 
import warnings, numpy as np 
warnings.filterwarnings("ignore", category=np.VisibleDeprecationWarning) 

# maxlen = 200 # No maximum length but whatever 
batch_size = 256
max_features = 20000  # Only consider the top 20k words

(x_train, y_train), (x_test, y_test) = \
              tf.keras.datasets.imdb.load_data(num_words=max_features)
print(len(x_train), "Training sequences")
print(len(x_test), "Validation sequences")

25000 Training sequences
25000 Validation sequences
# quick check 
x_train[:3]

array([list([1, 14, 22, 16, 43, 53, ....]),
       list([....]),
       list([...]),

转换成衣衫褴褛的

现在,我们将其转换为处理可变大小序列的参差不齐的张量。

x_train = tf.ragged.constant(x_train)
x_test  = tf.ragged.constant(x_test)
# quick check 

x_train[:3]
<tf.RaggedTensor [[1, 14, 22, 16, 43, 53, ...] [...] [...]]

x_train.shape, x_test.shape
(TensorShape([25000, None]), TensorShape([25000, None]))

型号

# Input for variable-length sequences of integers
inputs = tf.keras.Input(shape=(None,), dtype="int32")
# Embed each integer in a 128-dimensional vector
x = tf.keras.layers.Embedding(max_features, 128)(inputs)
x = tf.keras.layers.SimpleRNN(units=32)(x)
# Add a classifier
outputs = tf.keras.layers.Dense(1, activation="sigmoid")(x)
model = tf.keras.Model(inputs, outputs)
model.summary()
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, None)]            0         
_________________________________________________________________
embedding_1 (Embedding)      (None, None, 128)         2560000   
_________________________________________________________________
simple_rnn (SimpleRNN)       (None, 32)                5152      
_________________________________________________________________
dense (Dense)                (None, 1)                 33        
=================================================================
Total params: 2,565,185
Trainable params: 2,565,185
Non-trainable params: 0
_________________________________________________________________

编译和训练

model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["acc"])
model.fit(x_train, y_train, batch_size=batch_size, verbose=2, 
          epochs=10, validation_data=(x_test, y_test))
Epoch 1/10
113s 1s/step - loss: 0.6273 - acc: 0.6295 - val_loss: 0.4188 - val_acc: 0.8206
Epoch 2/10
109s 1s/step - loss: 0.4895 - acc: 0.8041 - val_loss: 0.4703 - val_acc: 0.8040
Epoch 3/10
109s 1s/step - loss: 0.3513 - acc: 0.8661 - val_loss: 0.3996 - val_acc: 0.8337
Epoch 4/10
110s 1s/step - loss: 0.2450 - acc: 0.9105 - val_loss: 0.3945 - val_acc: 0.8420
Epoch 5/10
109s 1s/step - loss: 0.1437 - acc: 0.9559 - val_loss: 0.4085 - val_acc: 0.8422
Epoch 6/10
109s 1s/step - loss: 0.0767 - acc: 0.9807 - val_loss: 0.4310 - val_acc: 0.8429
Epoch 7/10
109s 1s/step - loss: 0.0380 - acc: 0.9932 - val_loss: 0.4784 - val_acc: 0.8437
Epoch 8/10
110s 1s/step - loss: 0.0288 - acc: 0.9946 - val_loss: 0.5039 - val_acc: 0.8564
Epoch 9/10
110s 1s/step - loss: 0.0957 - acc: 0.9615 - val_loss: 0.5687 - val_acc: 0.8575
Epoch 10/10
109s 1s/step - loss: 0.1008 - acc: 0.9637 - val_loss: 0.5166 - val_acc: 0.8550

【讨论】:

以上是关于使用循环网络的电影评论分类的主要内容,如果未能解决你的问题,请参考以下文章

循环神经网络实践—文本分类

循环神经网络实践—文本分类

MXNet中使用双向循环神经网络BiRNN对文本进行情感分类

自然语言处理(NLP)基于循环神经网络实现情感分类

人工智能--使用神经网络分析电影评论

人工智能--使用神经网络分析电影评论