用MLP做电影评论二分类

本文程序原理等说明(也可以去程序中查看注释,注释比较多)

本节使用 IMDB 数据集,它包含来自互联网电影数据库(IMDB)的 50 000 条严重两极分化的评论。数据集被分为用于训练的 25 000 条评论与用于测试的 25 000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论。

与 MNIST 数据集一样,IMDB 数据集也内置于 Keras 库。它已经过预处理:评论(单词序列)已经被转换为整数序列,其中每个整数代表字典中的某个单词。(下面的程序运行后会自动下载该数据集)

不能将整数序列直接输入神经网络。你需要将列表转换为张量。转换方法有以下两种,程序中用的是第2种。

  1. 填充列表,使其具有相同的长度,再将列表转换成形状为 (samples, word_indices)的整数张量,然后网络第一层使用能处理这种整数张量的层(即 Embedding 层)。
  2. 对列表进行 one-hot 编码,将其转换为 0 和 1 组成的向量。举个例子,序列 [3, 5]将会被转换为 10 000 维向量,只有索引为 3 和 5 的元素是 1,其余元素都是0。然后网络第一层可以用 Dense 层,它能够处理浮点数向量数据。

每个带有 relu 激活的 Dense 层都实现了下列张量运算:
output = relu(dot(W, input) + b)
16 个隐藏单元对应的权重矩阵 W 的形状为 (input_dimension, 16) ,与 W 做点积相当于将输入数据投影到 16 维表示空间中(然后再加上偏置向量 b 并应用 relu 运算)。

隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的计算代价也变得更大,而且可能会导致学到不好的模式(这种模式会提高训练数据上的性能,但不会提高测试数据上的性能)。

对于这种 Dense 层的堆叠,你需要确定以下两个关键架构:
1、网络有多少层;
2、每层有多少个隐藏单元。

下面的程序中用的是1、两个中间层,每层都有 16 个隐藏单元;2、第三层输出一个标量,预测当前评论的情感。
中间层使用 relu 作为激活函数,最后一层使用 sigmoid 激活以输出一个 0~1 范围内的概率
值(表示样本的目标值等于 1 的可能性,即评论为正面的可能性)。

最后,你需要选择损失函数和优化器。由于你面对的是一个二分类问题,网络输出是一个概率值(网络最后一层使用 sigmoid激活函数,仅包含一个单元),那么最好使用 binary_crossentropy (二元交叉熵)损失。这并不是唯一可行的选择,比如你还可以使用 mean_squared_error(均方误差)。但对于输出概率值的模型,交叉熵(crossentropy)往往是最好的选择。

还有一点稍微注意下:在训练模型的时候,我们的model.fit参数里,用了训练集和验证集,真正参与训练的是训练集,而验证集只是用来监控生成的模型用在验证集上时的样本损失和精度,是用来反馈给我们后面分析调整程序和参数用的,并不能直接反馈给模型让模型直接调整。

完整的程序,复制到编译器中可以直接运行:

程序终端输出的准确率是指训练集上的准确率。

from keras.datasets import imdb

'''
train_data 和 test_data 这两个变量都是评论组成的列表,每条评论又是单词索引组成
的列表(表示一系列单词)。 train_labels 和 test_labels 都是 0 和 1 组成的列表,其中 0
代表负面(negative),1 代表正面(positive)。
'''
# 参数 num_words=10000 的意思是仅保留训练数据中前 10 000 个最常出现的单词。低频单词将被舍弃。这样得到的向量数据不会太大,便于处理
# 由于限定为前 10 000 个最常见的单词,单词索引都不会超过 10 000。最大为9999
(train_data,train_labels),(test_data,test_labels) = imdb.load_data(num_words=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]])
# print(decoded_review)

# 准备数据
# onehot编码,将数据向量化,转为二进制矩阵
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):
        # print("i:",i)
        # print("sequence:",sequence)
        results[i,sequence] = 1   # 第i行,sequence的数字对应的列,设为1。也就是result[i]的指定索引设为1
    return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

# 将标签向量化
y_train = np.asarray(train_labels).astype("float32")   # 其实这里只是转换了一下数据类型而已,把int转换为了float32
y_test = np.asarray(test_labels).astype("float32")

# 模型定义
from keras import models
from keras import layers

model = models.Sequential() # 开始 Keras 序列模型
# 传入 Dense 层的参数(16)是该层隐藏单元的个数。一个隐藏单元(hidden unit)是该层表示空间的一个维度
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'])

# 留出验证集
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 次迭代)。与此同时,你还要监控在留出的 10 000 个样本上的损失
和精度。你可以通过将验证数据传入 validation_data 参数来完成。
'''
history = model.fit(partial_x_train,partial_y_train,epochs=20,batch_size=512,validation_data=(x_val,y_val))
# 注意,调用 model.fit() 返回了一个 History 对象。这个对象有一个成员 history ,它是一个字典,包含训练过程中的所有数据。
# 字典中包含 4 个条目,对应训练过程和验证过程中监控的指标。
print(history.history.keys())

# 绘制训练损失和验证损失。请注意,由于网络的随机初始化不同,你得到的结果可能会略有不同。
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()
plt.show()

训练集和验证集结果的图:
image
image

训练损失每轮都在降低,训练精度每轮都在提升。这就是梯度下降优化的预期结果——你想要最小化的量随着每次迭代越来越小。但验证损失和验证精度并非如此:它们似乎在第四轮达到最佳值。这就是我们之前警告过的一种情况:模型在训练数据上的表现越来越好,但在前所未见的数据上不一定表现得越来越好。准确地说,你看到的是过拟合(overfit):在第二轮之后,你对训练数据过度优化,最终学到的表示仅针对于训练数据,无法泛化到训练集之外的数据。
在这种情况下,为了防止过拟合,你可以在3轮之后停止训练。通常来说,你可以使用许多方法来降低过拟合。

我们从头开始训练一个新的网络,训练 4轮,然后在测试数据上评估模型,用 predict 方法来得到评论为正面的可能性大小。

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

结果如下:

results: [0.2945877636432648, 0.8832]
predict: [[0.22306074]
 [0.9996836 ]
 [0.81477356]
 ...
 [0.19392714]
 [0.06047946]
 [0.578951  ]]

也就是测试集的损失是0.29,准确率是0.88

改进的空间。

1.前面使用了两个隐藏层。你可以尝试使用一个或三个隐藏层,然后观察对验证精度和测试精度的影响。
2.尝试使用更多或更少的隐藏单元,比如 32 个、64 个等。
3.尝试使用 mse 损失函数代替 binary_crossentropy 。
4.尝试使用 tanh 激活(这种激活在神经网络早期非常流行)代替 relu 。

这个例子中的要点。

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

以上大部分文字和程序摘自《python深度学习》,我做了一些调整和增删,加了一些自己的话,希望对各位有所帮助。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值