机器深度学习二分类电影的情感问题
二分类问题可能是应用最广泛的机器学习问题。今天我们将学习根据电影评论的文字内容将其划分为正面或负面。
一、数据集来源
我们使用的是IMDB数据集,它包含来自互联网电影数据库(IMDB)的50000条严重两极分化的评论。为了避免模型过拟合只记住训练数据,我们将数据集分为用于训练的25000条评论与用于测试的25000条评论,训练集和测试集都包含50%的正面评论和50%的负面评论。
与MNIST数据集一样,IMDB数据集也内置于Keras库。它已经过预处理:评论(单词序列)已经被转换为整数序列,其中每个整数代表字典中的某个单词。
通过以下代码加载数据集并限制每条评论最多取前一万个常用的word,以便于我们进行向量处理。
import tensorflow as tf imdb = tf.keras.datasets.imdb (train_data, train_labels),(test_data, test_labels) = imdb.load_data(num_words=10000) print(train_data[0]) print(train_labels[0])
通过输出可以看到,train_data和test_data是评论记录的集合,每条评论记录又是由众多的单词索引组成的集合。
train_labels和test_labels是针对评论的分类的集合,其中0表示负面评论,1表示正面评论。
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32] 1
我们可以通过word与编号的映射关系将评论的内容转化为具体的文本
def get_text(comment_num): """将数字形式的评论转化为文本""" # word_index = tf.keras.datasets.imdb.get_word_index() word_index = imdb.get_word_index() reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) text = ' '.join([reverse_word_index.get(i - 3, '?') for i in comment_num]) return text comment = get_text(train_data[0]) print(comment)
第一条电影评论的内容
? this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert ? is an amazing actor and now the same being director ?
二、格式化输入数据
由于我们无法直接将整数序列输入神经网络,所以需要将其转换为张量。可以通过以下两种方式进行转化
填充列表,使其具有相同的长度,然后将列表转化为(samples, word_index)的2D形状的整数张量。对列表进行one-hot编码,将其转化为0和1组成的向量。
这里我们采用one-hot进行编码处理
def vectorize_sequences(sequences, diamension = 10000): results = np.zeros((len(sequences), diamension)) for i, sequence in enumerate(sequences): results[i, sequence] = 1 return results x_train = vectorize_sequences(train_data) print(x_train[0]) print(len(x_train[0])) x_test = vectorize_sequences(test_data) print(x_test[0]) print(len(x_test[0]))
转化完成的输入结果
[0. 1. 1. ... 0. 0. 0.]
10000
[0. 1. 1. ... 0. 0. 0.]
将标签进行向量化处理
y_train = np.asarray(train_labels).astype('float32') y_test = np.asarray(test_labels).astype('float32')
三、构建神经网络
针对这里二分类单标签,我们可以直接使用带有relu激活函数的全连接层的简单堆叠。
我们使用了两个具有16个隐藏单元的中间层和具有一个隐藏单元的层。中间层使用的relu激活函数负责将所有的负值归零,最后一层使用sigmoid函数将任意值压缩到[0,1]之间并作为预测结果的概率。
model = models.Sequential() model.add(layers.Dense(16, activation='relu', input_shape=(10000,))) model.add(layers.Dense(16, activation='relu')) model.add(layers.Dense(1, activation='sigmoid'))
这里的Dense层实现了如下的张量计算,传入Dense层的参数16表示隐藏单元的个数,同时也表示这个层输出的数据的维度数量。隐藏单元越多,网络越能够学习到更加复杂的表示,但是网络计算的代价就越高。
output = relu(dot(W, input) + b)
我们使用rmsprop优化器和binary_crossentropy损失函数来配置模型。
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
四、训练模型
将训练数据分出一小部分作为校验数据,同时将512个样本作为一批量处理,并进行20轮的训练,同时出入validation_data来监控校验样本上的损失和计算精度。
x_val = x_train[:10000] partial_x_train = x_train[10000:] y_val = y_train[:10000] partial_y_train = y_train[10000:] history = model.fit(partial_x_train, partial_y_train, epochs= 20, batch_size=512, validation_data=(x_val, y_val))
调用fit()返回的history对象包含训练过程的所有数据
history_dict = history.history print(history_dict.keys())
字典中包含4个条目,对应训练过程和校验过程的指标,其中loss是训练过程中损失指标,accuracy是训练过程的准确性指标,而val_loss是校验过程的损失指标,val_accuracy是校验过程的准确性指标。
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])
我们使用Matplotlib画出训练损失和校验损失的情况
loss_values = history_dict['loss'] val_loss_values = history_dict['val_loss'] epochs = range(1, len(loss_values) + 1) plt.plot(epochs, loss_values, 'bo', label='Training loss') plt.plot(epochs, val_loss_values, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show()
从图中可以看到整个训练过程,损失函数值一直在不断的变小,但是校验过程的损失函数值却先变小后变大,在2.5-5之间的某个点达到最小值。
我们使用Matplotlib画出训练精度和校验精度的情况
plt.clf() acc = history_dict['accuracy'] val_acc = history_dict['val_accuracy'] plt.plot(epochs, acc, 'bo', label='Training accuracy') plt.plot(epochs, val_acc, 'b', label='Validation accuracy') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show()
从图中可以看到整个训练过程,准确度值一直在不断的升高,但是校验过程的精度数值却在不断的进行波动,在2.5-5之间的某个点达到最大值。
通过对训练和校验指标的分析,可以看到训练的损失每轮都在降低,训练的精度每轮都在提升。但是校验损失和校验精度基本上在第4轮左右达到最佳值。为了防止这种过拟合的情况,我们可以在第四轮完成之后直接停止训练。
history = model.fit(partial_x_train, partial_y_train, epochs= 4, batch_size=512, validation_data=(x_val, y_val)) results = model.evaluate(x_test, y_test) print(results)
重新执行可以看到模型的精度可以达到87%
782/782 [==============================] - 1s 876us/step - loss: 0.3137 - accuracy: 0.8729 [0.3137112557888031, 0.8728799819946289]
五、使用测试数据预测结果
使用训练的模型对test数据集进行预测
result = model.predict(x_test) print(result)
[[0.31683978]
[0.9997941 ]
[0.9842608 ]
...
[0.18170357]
[0.23360077]
六、小结
- 需要对原始数据进行预处理并转化为符合要求的张量。
- 对于二分类问题,最后一层使用sigmoid作为激活函数,并输出0-1的标量来表示结果出现的概率。
- 对于二分类问题的sigmoid标量输出,应该使用binary_crossentropy损失函数。
- 随着训练过程的进行,很容易出现过拟合现象,我们需要时刻监控模型在非训练数据集的表现。