TensorFlow之tf.keras的文本分类

这段时间在学习TensorFlow,这些都是一些官网上的例子,在这里和大家分享记录一下。

出自:https://www.tensorflow.org/tutorials/keras/basic_text_classification

此教程使用评论文本将电影评论分类为正面或负面。这是二元或两类分类的一个例子,这是一种重要且广泛适用的机器学习问题。

我们将使用包含来自Internet电影数据库的50,000条电影评论文本的IMDB数据集。这些分为25,000条培训评论和25,000条评审评论。训练集和测试集包含相同数量的正面和负面评论。

教程使用tf.keras,一个高级API,用于在TensorFlow中构建和训练模型。有关使用更高级的文本分类教程tf.keras,请参阅MLCC文本分类指南。

1、下载IMDB数据集

IMDB数据集与TensorFlow一起打包。它已经被预处理,使得评论(单词序列)已经被转换为整数序列,其中每个整数表示字典中的特定单词。

以下代码将IMDB数据集下载到您的计算机(如果您已经下载了它,则会自动使用缓存副本):

imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

该参数num_words=10000保留了训练数据中最常出现的10,000个单词。丢弃罕见的单词以保持数据的大小可管理。

2、探索数据

我们花一点时间来了解数据的格式。数据集经过预处理:每个示例都是一个整数数组,表示电影评论的单词。每个标签都是0或1的整数值,其中0表示负面评论,1表示正面评论。

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))

输出:

Training entries: 25000, labels: :25000

评论文本已转换为整数(在机器学习处理文本中,一般都会根据语料建立一个字库,用词语所在序号来替换掉语料中的文本),其中每个整数表示字典中的特定单词。以下是第一篇评论的内容:

print(train_data[0])

输出:

[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,11920,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,10,10,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]

电影评论的长度可能不同。以下代码显示了第一次和第二次评论中的字数。由于对神经网络的输入必须是相同的长度,我们稍后需要解决此问题。

len(train_data[0]), len(train_data[1])

输出:

(218,189)

将整数转换回单词

了解如何将整数转换回文本可能很有用。在这里,我们将创建一个辅助函数来查询包含整数到字符串映射的字典对象:

# A dictionary mapping words to an integer index
word_index = imdb.get_word_index()

# The first indices are reserved
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index[""] = 0
word_index[""] = 1
word_index[""] = 2  # unknown
word_index[""] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])

现在我们可以使用该decode_review函数显示第一次审核的文本:

decode_review(train_data[0])

输出:

" 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  father came from the same scottish island as myself so i loved the fact 
there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for  and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was 
so sad and you know what they say if you cry at a film it must have been good and this definitely was also  to the two little boy's that played the  of norman and paul they were just brilliant 
children are often left out of the  list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was 
someone's life after all that was shared with us all"

3、准备数据

reviews—the arrays of integers 必须在输入神经网络之前转换为张量。这种转换可以通过以下两种方式完成:

  • 对数组进行单热编码,将其转换为0和1的向量。例如,序列[3,5]将成为10,000维向量,除了索引3和5(其为1)之外全部为零。然后,将其作为我们网络中的第一层 - 一个可以处理浮点矢量数据的Dense层。然而,这种方法是存储器密集型的,需要num_words * num_reviews大小矩阵。

  • 或者,我们可以填充数组,使它们都具有相同的长度,然后创建一个整数张量的形状max_length * num_reviews。我们可以使用能够处理这种形状的嵌入层作为我们网络中的第一层。

在本教程中,我们将使用第二种方法。

由于电影评论的长度必须相同,我们将使用pad_sequences函数来标准化长度:

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index[""],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index[""],
                                                       padding='post',
                                                       maxlen=256)

这里介绍一下pad_sequences函数
tf.keras.preprocessing.sequence.pad_sequences(
sequences,
maxlen=None,
dtype=‘int32’,
padding=‘pre’,
truncating=‘pre’,
value=0.0)
此函数将 num_samples序列列表(整数列表)转换为2D Numpy形状数组(num_samples, num_timesteps)。 num_timesteps是maxlen提供的参数,或者是最长序列的长度。
短于的序列在末尾num_timesteps 填充value。
序列长于num_timesteps截短的序列,以便它们符合所需的长度。填充或截断发生的位置分别由参数padding和truncating。
预填充是默认值。
参数:
1、sequences:列表列表,其中每个元素都是一个序列。
2、maxlen:Int,所有序列的最大长度。
3、dtype:输出序列的类型。
4、padding:String,‘pre’或’post’:在每个序列之前或之后填充。
5、truncating:String,‘pre’或’post’:从序列maxlen的开头或结尾处大于序列的值中删除值 。
6、value:浮点数,填充值。
返回:
x:Numpy数组形状 (len(sequences), maxlen)

我们现在看一下示例的长度:

len(train_data[0]), len(train_data[1])

输出:

(256,256)

并检查(已填充)审查:

[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 8 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 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    0 0 0 0]

4、建立模型

神经网络是通过堆叠层创建的 - 这需要两个主要的架构决策:

  • 模型中要使用多少层?
  • 每层使用多少隐藏单位?

在此示例中,输入数据由单词索引数组组成。要预测的标签是0或1?让我们为这个问题建立一个模型:

# input shape is the vocabulary count used for the movie reviews (10,000 words)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()

输出:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 16)          160000    
_________________________________________________________________
global_average_pooling1d_1 ( (None, 16)                0         
_________________________________________________________________
dense_2 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,289
Trainable params: 160,289
Non-trainable params: 0
_________________________________________________________________

这4个图层按顺序堆叠以构建分类器:

  1. 第一层是Embedding层。该层采用整数编码的词汇表,并为每个词索引查找嵌入向量。这些向量是作为模型训练学习的。向量为输出数组添加维度。得到的尺寸是:(batch, sequence, embedding)。
  2. 接下来,GlobalAveragePooling1D通过对序列维度求平均,层为每个示例返回固定长度的输出向量。这允许模型以最简单的方式处理可变长度的输入。想了解池化层可以看知乎的一篇文章
  3. 这个固定长度的输出矢量通过一个Dense带有16个隐藏单元的完全连接层(Dense)传输。想了解全连接层可以看知乎的一篇文章
  4. 最后一层与单个输出节点密集连接。使用sigmoid激活函数,此值是介于0和1之间的浮点数,表示概率或置信度。想了解激活函数可以看知乎的一篇文章

这里说一下第一层的keras.layers.embeddings.Embedding

  • Embedding将正整数(下标)转换为具有固定大小的向量,如[[4],[20]]->[[0.25,0.1],[0.6,-0.2]]
  • Embedding层只能作为模型的第一层

参数

  • input_dim:大或等于0的整数,字典长度,即输入数据最大下标+1
  • output_dim:大于0的整数,代表全连接嵌入的维度
  • embeddings_initializer: 嵌入矩阵的初始化方法,为预定义初始化方法名的字符串,或用于初始化权重的初始化器。参考initializers
  • embeddings_regularizer: 嵌入矩阵的正则项,为Regularizer对象
  • embeddings_constraint: 嵌入矩阵的约束项,为Constraints对象
  • mask_zero:布尔值,确定是否将输入中的‘0’看作是应该被忽略的‘填充’(padding)值,该参数在使用递归层处理变长输入时有用。设置为True的话,模型中后续的层必须都支持masking,否则会抛出异常。如果该值为True,则下标0在字典中不可用,input_dim应设置为|vocabulary| + 1。
  • input_length:当输入序列的长度固定时,该值为其长度。如果要在该层后接Flatten层,然后接Dense层,则必须指定该参数,否则Dense层的输出维度无法自动推断。

输入shape

  • 例如(samples,sequence_length)的2D张量

输出shape

  • 例如(samples, sequence_length, output_dim)的3D张量

例子

model = Sequential()
model.add(Embedding(1000, 64, input_length=10))
# the model will take as input an integer matrix of size (batch, input_length).
# the largest integer (i.e. word index) in the input should be no larger than 999 (vocabulary size).
# now model.output_shape == (None, 10, 64), where None is the batch dimension.

input_array = np.random.randint(1000, size=(32, 10))

model.compile('rmsprop', 'mse')
output_array = model.predict(input_array)
assert output_array.shape == (32, 10, 64)

隐藏单元

上述模型在输入和输出之间有两个中间或“隐藏”层。输出的数量(单位,节点或神经元)是图层的表示空间的维度。换句话说,在学习内部表示时允许网络的自由度。

如果模型具有更多隐藏单元(更高维度的表示空间)或更多层,则网络可以学习更复杂的表示。但是,它使网络的计算成本更高,并且可能导致学习不需要的patterns—patterns,这些patterns可以提高训练数据的性能,但不会提高测试数据的性能。这称为过度拟合,我们稍后会进行探讨。

5、损失函数和优化器

模型需要一个损失函数和一个用于训练的优化器。由于这是二元分类问题和模型输出概率(具有S形激活的单个单元层),我们将使用binary_crossentropy损失函数。

这不是损失函数的唯一选择,例如,您可以选择mean_squared_error。但是,通常,binary_crossentropy处理概率更好 - 它测量概率分布之间的“距离”,或者在我们的情况下,测量地面实况分布和预测之间的“距离”。

后来,当我们探索回归问题(比如预测房子的价格)时,我们将看到如何使用另一种称为均方误差的损失函数。

现在,配置模型以使用优化器和损失函数:

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])

6、创建验证集

在训练时,我们想要检查模型在以前没有见过的数据上的准确性。通过从原始训练数据中分离10,000个示例来创建验证集。(为什么不立即使用测试集?我们的目标是仅使用训练数据开发和调整我们的模型,然后仅使用测试数据来评估我们的准确性)。

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

其实我们也可以直接跳过这一步,直接在训练方法中用validation_split=0.2,进行切分

7、训练模型

以512个样本的小批量训练模型40个时期。这是对张量x_train和y_train张量中的所有样本的40次迭代。在培训期间,监控模型在验证集中的10,000个样本的损失和准确性:

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_split=0.2,
                    verbose=1)

输出:

Train on 12000 samples, validate on 3000 samples
Epoch 1/40
12000/12000 [==============================] - 3s 279us/step - loss: 0.7570 - acc: 0.4969 - val_loss: 0.6953 - val_acc: 0.4947
Epoch 2/40
12000/12000 [==============================] - 1s 121us/step - loss: 0.6910 - acc: 0.5222 - val_loss: 0.6908 - val_acc: 0.5183
Epoch 3/40
12000/12000 [==============================] - 1s 123us/step - loss: 0.6892 - acc: 0.5382 - val_loss: 0.6878 - val_acc: 0.5800

8、评估模型

让我们看看模型的表现。将返回两个值。损失率(代表我们的错误的数字,更低的值更好)和准确性。

results = model.evaluate(test_data, test_labels)
print(results)

输出:

25000/25000 [==============================] - 1s 31us/step
[0.32530811003684995, 0.8664]

这种相当天真的方法可以达到约87%的准确度。采用更先进的方法,模型应该接近95%。

9、创建一段时间内准确性和损失的图表

model.fit()返回一个History包含字典的对象,其中包含训练期间发生的所有事情:

history_dict = history.history
# history_dict.keys()
# dict_keys(['val_acc','val_loss','acc','loss'])

有四个条目:在培训和验证期间,每个条目对应一个受监控的指标。我们可以使用这些来绘制训练和验证损失以进行比较,以及培训和验证准确性:

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

第二个准确性图表:

plt.clf()   # clear figure
acc_values = history_dict['acc']
val_acc_values = 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()

TensorFlow之tf.keras的文本分类_第1张图片
TensorFlow之tf.keras的文本分类_第2张图片
在这2张图中,点表示训练损失和准确度,实线表示验证损失和准确度。

请注意,训练损失随着每个时期而减少,并且训练准确度随着每个时期而增加。这在使用梯度下降优化时是预期的 - 它应该在每次迭代时最小化期望的数量。

这不是验证损失和准确性的情况 - 它们似乎在大约二十个时代之后达到峰值。这是过度拟合的一个例子:模型在训练数据上的表现比在以前从未见过的数据上表现得更好。在此之后,模型过度优化并学习特定于训练数据的表示,这些表示不会推广到测试数据

对于这种特殊情况,我们可以通过在二十个左右的时期之后停止训练来防止过度拟合。

附上完整代码:

# -*- coding: utf-8 -*-
"""
Created on Tue Sep 18 09:42:55 2018

@author: chenyang
"""

# TensorFlow and tf.keras
import tensorflow as tf
from tensorflow import keras

# Helper libraries
import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])

if __name__ == '__main__':
    
    # tf中封装了IMDB数据集
    imdb = keras.datasets.imdb

    #加载数据
    (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
    
#    print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
    
    # 把整数转换回单词
    # A dictionary mapping words to an integer index
    word_index = imdb.get_word_index()
    
    # The first indices are reserved
    word_index = {k:(v+3) for k,v in word_index.items()} 
    word_index[""] = 0
    word_index[""] = 1
    word_index[""] = 2  # unknown
    word_index[""] = 3
    
    reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
    
    # 数据处理
    # 可以填充数组,使它们都具有相同的长度,然后创建一个整数张量的形状max_length * num_reviews
    train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                            value=word_index[""],
                                                            padding='post',
                                                            maxlen=256)

    test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                           value=word_index[""],
                                                           padding='post',
                                                           maxlen=256)
    
    # 建立模型
    vocab_size = 10000
    
    model = keras.Sequential()
    # 嵌入层
    model.add(keras.layers.Embedding(vocab_size, 16))
    # 池化层
    model.add(keras.layers.GlobalAveragePooling1D())
    model.add(keras.layers.Dense(16, activation=tf.nn.relu))
    model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))
    
    model.summary()
    
    # 配置costfunction 和优化器、度量标准
    model.compile(optimizer=tf.train.AdamOptimizer(),
                  loss='binary_crossentropy',
                  metrics=['accuracy'])
    
    # 创建验证集
    x_val = train_data[:10000]
    partial_x_train = train_data[10000:]
    
    y_val = train_labels[:10000]
    partial_y_train = train_labels[10000:]
    
    history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_split=0.2,
                    verbose=1)
    
    # 模型评估
    results = model.evaluate(test_data, test_labels)

    print(results)
    
    
    # 创建一段时间内准确性和损失的图表
    # dict_keys(['val_acc','val_loss','acc','loss'])
    history_dict = history.history
    
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epochs = range(1, len(acc) + 1)
    
    # "bo" is for "blue dot"
    plt.plot(epochs, loss, 'bo', label='Training loss')
    # b is for "solid blue line"
    plt.plot(epochs, val_loss, 'b', label='Validation loss')
    plt.title('Training and validation loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    plt.show()
    
    # 第二张图表
    plt.clf()   # clear figure
    acc_values = history_dict['acc']
    val_acc_values = 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()

你可能感兴趣的:(TensorFlow)