Python深度学习(电影评论分类:二分类问题)--学习笔记(五)

3.4 电影评论分类:二分类问题

3.4.1 IMDB数据集

  • 本节使用IMDB数据集,它包含来自互联网电影数据库(IMDB)的50000条严重两极分化的评论。数据集被分为用于训练的25000条评论与用于测试的25000条评论,训练集和测试集都包含50%的正面评论和50%的负面评论。
  • 不应该将训练机器学习模型的同一批数据再用于测试模型!模型在训练数据上的表现很好,而你真正关系的是模型在新数据上的性能(因为你已经知道了训练数据对应的标签,显然不再需要模型来进行预测)。例如,你的模型最终可能只是记住了训练样本和目标值之间的映射关系,但这对前所未见的数据上进行预测毫无作用。
  • 与MNIST数据集一样,IMDB数据集也内置于Keras库。它已经过预处理:评论(单词序列)已经被转换为整数序列,其中每个整数代表字典中的某个单词。
# 记载IMDB数据集
from keras.datasets import imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
  • 参数num_words=10000的意思是仅保留训练数据中前10000个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理。
  • train_data和test_data这两个变量都是评论组成的列表,每条评论又是单词索引组成的列表(表示一系列单词)。train_labels和test_labels都是0和1组成的列表,其中0代表负面(negative),1代表正面(positive)。
  • 由于限定为前10000个最常见的单词,单词索引不会超过10000。
# 将评论解码为英文单词
word_index = imdb.get_word_index() # word_index是一个将单词映射为整数索引的字典
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join([reverse_word_index.get(i-3, '?') for i in train_data[0]]) # 将评论解码。注意,索引减去了3,因为0、1、2是为"padding"、"start of sequence"(序列开始)、"unknown"(未知词)分别保留的索引
print(decoded_review)

3.4.2 准备数据

  • 你不能将整数序列直接输入神经网络。你需要将列表转换为张量:(1)填充列表,使其具有相同的长度,再将列表转换成形状为(samples, word_indices)的整数张量,然后网络第一层使用能处理这种整数张量的层(即Embeeding层);(2)对列表进行one-hot编码,将其转换为0和1组成的向量。
import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension)) # 创建一个形状为(len(sequences), dimension)的零矩阵
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1. # 将results[i]的指定索引设为1

    return results

x_train = vectorize_sequences(train_data) # 将训练数据向量化
x_test = vectorize_sequences(test_data) # 将测试数据向量化
  • 将标签向量化:
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

3.4.3 构建网络

  • 输入数据是向量,而标签是标量(1和0),这是你会遇到的最简单的情况。有一类网络在这种问题上表现很好,就是带有relu激活的全连接层(Dense)的简单堆叠,比如Dense(16, activation=‘relu’)。
  • 传入Dense层的参数(16)是该层隐藏单元的个数。一个隐藏单元(hidden unit)是该层表示空间的一个维度。 o u t p u t = r e l u ( d o t ( W , i n p u t ) + b ) output = relu(dot(W, input) + b) output=relu(dot(W,input)+b)
  • 16个隐藏单元对应的权重矩阵 W W W的形状为(input_dimension, 16),与 W W W做点积相当于将输入数据投影到16维表示空间中(然后再加上偏置向量 b b b并应用 r e l u relu relu运算)。可以将表示空间的维度直观地理解为“网络学习内部表示时拥有的自由度”。隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的计算代价也变得更大,而且可能会导致学到不好的模式(这种模式会提高训练数据上的性能,但不会提高测试数据上的性能)。
  • 对于Dense层的堆叠,需要确定以下两个关键架构:网络有多少层;每层有多少个隐藏单元。
# 模型定义
from keras import models
from keras import layers

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'))
  • 什么是激活函数?为什么要使用激活函数?:如果没有relu等激活函数(也叫非线性),Dense层将只包含两个线性运算-点积和加法: o u t p u t = d o t ( W , i n p u t ) + b output = dot(W, input) + b output=dot(W,input)+b,这样Dense层就只能学习输入数据的线性变换(仿射变换):该层的假设空间是从输入数据到16位空间所有可能的线性变换集合。这种假设空间非常有效,无法利用多个表示层的优势,因为多个线性层堆叠实现的仍是线性运算,添加层数并不会扩展假设空间。为了得到更丰富的假设空间,从而充分利用多层表示的优势,你需要添加非线性或激活函数。relu是深度学习中最常用的激活函数,但还有许多其他函数可选:prelu、elu等。
  • 最后,还需要选择损失函数和优化器。由于面对的是一个二分类问题,网络输出是一个概率值(网络最后一层使用sigmoid激活函数,仅包含一个单元),那么最好使用binary_crossentropy (二元交叉熵)损失。对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。交叉熵是来自于信息论领域的概念,用于衡量概率分布之间的距离,在这个例子中就是真实分布与预测值之间的距离。
# 编译模型
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuray'])
  • 上述代码将优化器、损失函数和指标作为字符串传入,这是因为rmsprop、binary_crossentropy和accuracy都是keras内置的一部分。有时可能希望配置自定义优化器的参数,或者传入自定义的损失函数或指标函数。前者可通过向optimizer参数传入一个优化器类实例来实现。后者可通过向loss和metrics参数传入函数对象来实现。
# 配置优化器
from keras import optimizers

model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss='binary_crossentropy', metrics=['accuracy'])
# 使用自定义的损失和指标
from keras import optimizers
from keras import losses
from keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss=losses.binary_crossentropy, metrics=[metrics.binary_accuracy])

3.4.4 验证你的方法

  • 为了在训练过程中监控模型在前所未见的数据上的精度,你需要将原始训练数据留出10000个样本作为验证集。
# 留出验证集
x_val = x_train[:10000]
partial_x_train = x_train[10000:]

y_val = y_train[:10000]
partial_y_train = y_train[10000:]
  • 使用512个样本组成的小批量,将模型训练20个轮次(即对x_train和y_train两个张量中的所有样本进行20次迭代)。与此同时,还需要监控在留出的10000个样本上的损失和精度。可以通过将验证数据传入validation_data参数来完成。
# 训练模型
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])

history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
  • 调用model.fit()返回了一个History对象。这个对象由一个成员history,它是一个字典,包含训练过程中的所有数据。
history_dict = history.history
print(history_dict.keys())
  • 字典中包含4个条目,对应训练过程和验证过程中监控的指标。
# 绘制训练损失和验证损失
import matplotlib.pyplot as plt

history_dict = history.history
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()

# 绘制训练精度和验证精度
plt.clf() # 清空图像
acc = history_dict['acc']
val_acc = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
  • 训练损失每轮都在降低,训练精度每轮都在提升。这就是梯度下降优化的预期结果-想要最小化的量随着每次迭代越来越小。但验证损失和验证精度并非如此。模型在训练数据上的表现越来越好,但是在前所未见的数据上不一定表现得越来越好。
# 从头开始重新训练一个模型
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'))

model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)

print(results)

3.4.5 使用训练好的网络在新数据上生成预测结果

  • 训练好网络之后,可以用predict方法来得到评论为正面的可能性大小: m o d e l . p r e d i c t ( x t e s t ) model.predict(x_test) model.predict(xtest)

3.4.7 小结

  • 通常需要对原始数据进行大量预处理,以便将其转换为张量输入到神经网络中。单词序列可以编码为二进制向量,但也有其他编码方式。
  • 带有relu激活的Dense层堆叠,可以解决很多种问题(包括情感分类),你可能会经常用到这种模型。
  • 对于二分类问题(两个输出类别),网络的最后一层应该是只有一个单元并使用sigmoid激活函数的Dense层,网络输出应该是0~1范围内的标量,表示概率值。
  • 对于二分类问题的sigmoid标量输出,应该是用binary_crossentropy损失函数。
  • 无论你的问题是什么,rmsprop优化器通常都是足够好的选择。
  • 随着神经网络在训练数据上的表现越来越好,模型最终会过拟合,并在前所未见的数据上得到越来越差的结果。一定要一直监控模型在训练集之外的数据上的性能。

你可能感兴趣的:(深度学习,神经网络,深度学习)