• 【学习笔记】《Python深度学习》第六章:深度学习用于文本和序列


    1 处理文本数据

    1. 文本

    文本是常用的序列数据之一,可以理解为字符序列单词序列

    2.文本向量化,指将文本转换为数值张量的过程。

    • 将文本分割为单词,并将每个单词转换为一个向量
    • 将文本分割为字符,并将每个单词转换为一个向量
    • 提取单词或字符的 n-gram ,并将每个 n-gram 转换为一个向量。

    将文本分解成的单元(单词、字符或n-gram)叫做 「标记」,将文本分解成标记的过程叫做「分词」。

    所有文本向量化的过程都是应用某种分词方案,然后将数值向量与标记相关联。这些向量组成序列张量,输入到神经网络模型中。

    n-gram 是从句子中提取的 N 个(或更少)连续单词或字符集合。 这类集合叫做词袋,「袋」指的是标记组成的集合,因此「词袋」是一种不保存顺序的粉刺方法,常常被用于浅层的语言处理模型。

    3. 将向量与标记相关联的方法

    • 对标记做 one-hot编码
    • 标记嵌入

    1.1 单词和字符的one-hot编码

    1. 方法

    将每个单词与唯一的整数索引进行关联,然后将这个整数索引 i 转换为长度为 N 的二进制向量(N是词表大小),这个向量只有第 i 个元素是 1 ,其余元素都是 0。

    2.单词级的 one-hot 编码

    import numpy as np
    
    samples = ['The cat sat on the mat .', 'The dog ate my homework .']
    
    token_index = {} # 构建数据中所有标记的索引
    for sample in samples:
        # 利用split方法对样本进行分词
        for word in sample.split():
            if word not in token_index:
                # 为每个单词指定唯一索引
                # 不存在索引编号为0的单词
                token_index[word] = len(token_index) + 1
    
    # 对样本进行分词,只考虑前max_length个单词 
    max_length = 10
    
    # 结果保存在results中
    results = np.zeros(shape=(len(samples), max_length, max(token_index.values()) + 1))
    
    for i, sample in enumerate(samples):
        for j, word in list(enumerate(sample.split()))[:max_length]:
            index = token_index.get(word)
            results[i, j, index] = 1.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    3. 字符级的 one-hot 编码

    import string
    
    samples = ['The cat sat on the mat .', 'The dog ate my homework .']
    characters = string.printable # 所有可打印的ASCII字符
    token_index = dict(zip(range(1, len(characters) + 1), characters))
    
    max_length = 50
    results = np.zeros((len(samples), max_length, max(token_index.keys())))
    for i, sample in enumerate(samples):
        for j, character in enumerate(sample):
            index = token_index.get(character)
            results[i, j, index] = 1.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    4. 用 Keras 实现单词级的 one-hot 编码

    Keras 的内置函数可以对原始文本数据进行「单词级」或「字符级」的 one-hot 编码。

    from keras.preprocessing.text import Tokenizer
    
    samples = ['The cat sat on the mat .', 'The dog ate my homework .']
    
    # 创建一个分词器,只考虑前1000个最常见的单词
    tokenizer = Tokenizer(num_words=1000)
    # 构建单词索引
    tokenizer.fit_on_texts(samples) 
    # 将字符串转换为整数索引组成的列表
    sequences = tokenizer.texts_to_sequences(samples)
    # 也可以直接得到 one-hot 二进制表示
    # 该分词器也支持除one-hot外的其他向量化模式
    one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')
    # 找回单词索引
    word_index = tokenizer.word_index
    print('Found %s unique tokens.' % len(word_index))
    # Found 9 unique tokens.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. 使用散列技巧的单词级的 one-hot 编码

    • 方法
      散列函数 将单词散列编码为固定长度的向量。
    • 优点
      避免了维护一个显式的单词索引,从而节省内存,并允许数据的在线编码,适用于唯一标记数量太大的情况。
    • 缺点
      可能会出现 散列冲突 ,两个不同的单词具有相同的散列值。
    samples = ['The cat sat on the mat .', 'The dog ate my homework .']
    
    # 将单词保存为长度为 1000 的向量
    # 如果单词数量接近 1000 ,会发生很多散列冲突
    dimensionality = 1000
    max_length = 10
    
    results = np.zeros((len(samples), max_length, dimensionality))
    for i, sample in enumerate(samples):
        for j, word in list(enumerate(sample.split()))[:max_length]:
            # 将单词散列为 0~1000 范围内的一个随机整数索引
            index = abs(hash(word)) % dimensionality
            results[i, j, index] = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.2 使用词嵌入

    1. 词嵌入介绍

    • one-hot 编码得到的向量是二进制的、稀疏的、维度很高的;
    • 词嵌入 是低维的浮点数向量(密集矩阵),从数据中学习得到;

    2. 使用词嵌入的方法

    • 完成主任务的同时学习词嵌入。一开始是随机的词向量,然后通过对词向量进行学习,与学习神经网络的权重类似。
    • 预计算词嵌入 ,然后将其加载到模型中。

    3. 利用 Embedding 层学习词嵌入

    (1)原理

    词向量之间的几何关系应该表示这些词之间的「语义关系」。

    一般来说,任意两个词向量之间的几何距离,应该和它们的语义距离 有关,比如同义词应该被嵌入相似的词向量中。

    因此,合理的做法是对每个新任务都学习一个新的嵌入空间反向传播 让这种学习变得简单,我们需要学习 Embedding层 的权重。

    (2)步骤

    ①将一个 Embedding层 实例化

    from keras.layers import Embedding
    
    embedding_layer = Embedding(1000, 64)
    
    • 1
    • 2
    • 3

    「Embedding层」需要两个参数:标记的个数 (1000)和 嵌入的维度 (64)。

    Embedding层 理解成一个字典,将整数索引(表示特定单词)映射为密集向量。它接受整数作为输入,并在内部字典中查找这些整数,返回相关联的向量。

    Embedding层 的输入是一个二维整数张量,其形状为(samples,sequence_length),每个元素都是一个整数序列。它能够嵌入长度可变的序列,不过一批数据中的所有序列必须具有相同的长度,所以较短的序列用 0 填充,较长的序列应该被截断。

    Embedding层 返回一个形状为(samples,sequence_length,embedding_dimensionly)的三维浮点数张量。

    将一个 Embedding层 实例化的时候,它的权重最开始是随机的。在训练过程中,利用 反向传播 来逐渐调节。

    ②加载 IMDB数据,准备用于 Embedding层

    将 Embedding层 应用于 IMDB电影评论情感预测任务。首先准备数据,将电影评论限制为前 10000 个最常见的单词,然后将评论限制为 20 个单词。对于 10000 个单词,网络将对每个词都学习一个 8 维嵌入,将输入的整数序列(二维整数张量)转换为嵌入序列 (三维浮点数张量),然后将张量展平为二维,在上面训练一个 Dense层 用于分类。

    from keras.datasets import imdb
    from tensorflow.keras import preprocessing
    max_features = 10000 # 作为特征的单词个数
    # 在maxlen之后截断文本
    maxlen = 20
    
    # 将数据加载为整数列表
    (x_train, y_train), (x_test, y_test) = imdb.load_data(
        num_words = max_features) 
    
    # 将整数列表转换为形状为(samples,maxlen)的二维整数张量
    x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
    x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ③在IMDB数据上使用 Embedding层 和分类器

    from keras.models import Sequential
    from keras.layers import Flatten, Dense, Embedding
    
    model = Sequential()
    # 指定Embedding层的最大输入长度,以便之后将嵌入输入展平
    # Embedding层激活的形状为(samples,maxlen,8)
    model.add(Embedding(10000, 8, input_length=maxlen))
    
    # 将三维的嵌入张量展平为形如(samples,maxlen * 8)的二维张量
    model.add(Flatten())
    
    # 添加分类器
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])
    model.summary()
    
    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=32,
                        validation_split=0.2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    得到的验证精度约为 76% ,考虑到仅查看每条评论的前 20 个单词,这个结果还是相当不错的。

    但是需要注意的是,仅仅将嵌入序列展开并在上面训练一个 Dense层,会导致模型对于输入序列中的每个单词单独处理,没有考虑单词之间的关系和句子结构。

    4. 使用预训练的词嵌入

    当可用的训练数据很少的时候,可以从预计算的嵌入空间加载嵌入向量,通过词频统计计算得到,具有通用的特征。

    比如 Word2vec算法GloVe(词表示全局向量)。

    1.3 从原始文本到词嵌入

    1. 下载 IMDB 数据的原始样本,并处理 IMDB 原始数据的标签

    首先,下载原始 IMDB 数据集并解压,下载地址:http://mng.bz/0tIo

    接下来,将训练评论转换为 字符串列表,每个字符串对应一条评论,也可以将评论标签(正面/负面)转换成 labels 列表。

    import os
    
    imdb_dir = r'E:\firefoxLoad\aclImdb\aclImdb'
    
    train_dir = os.path.join(imdb_dir, 'train')
    
    labels = []
    texts = []
    
    for label_type in ['neg', 'pos']:
        dir_name = os.path.join(train_dir, label_type)
        for fname in os.listdir(dir_name):
            if fname[-4:] == '.txt':
                f = open(os.path.join(dir_name, fname), errors='ignore')
                texts.append(f.read())
                f.close()
                if label_type == 'neg':
                    labels.append(0)
                else:
                    labels.append(1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2. 对数据进行分词

    对文本进行分词,并将其划分为训练集和验证集。因此「预训练的词嵌入」对训练数据很少的问题特别有用,因此将训练数据限定为前 200 个样本。

    from keras.preprocessing.text import Tokenizer
    from keras.preprocessing.sequence import pad_sequences
    import numpy as np
    
    maxlen = 100 # 在100个单词后截断评论
    training_samples = 200 # 在200个样本上进行训练
    validation_samples = 10000 # 在10000个样本上验证
    max_words = 10000 # 只考虑前10000个常见单词
    
    tokenizer = Tokenizer(num_words = max_words)
    tokenizer.fit_on_texts(texts)
    sequences = tokenizer.texts_to_sequences(texts)
    
    word_index = tokenizer.word_index
    print('Found %s unique tokens. ' % len(word_index))
    
    data = pad_sequences(sequences, maxlen=maxlen)
    
    labels = np.asarray(labels)
    print('Shape of data tensor:', data.shape)
    print('Shape of label tensor:', labels.shape)
    
    # 将数据划分为训练集和验证集
    # 首先打乱数据,因为原本样本是有序的
    indices = np.arange(data.shape[0])
    np.random.shuffle(indices)
    data = data[indices]
    labels = labels[indices]
    
    x_train = data[:training_samples]
    y_train = labels[:training_samples]
    x_val = data[training_samples : training_samples + validation_samples]
    y_val = labels[training_samples : training_samples + validation_samples]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    3. 下载 GloVe 词嵌入

    下载地址:https://nlp.stanford.edu/projects/glove/

    选择 2014 年英文维基百科的预计算嵌入,这是一个 822 MB的压缩文件,文件名是 glove.6B.zip,里面包含 400 000个单词(或非单词的标记)

    4. 对嵌入进行预处理

    ①对解压后的文件进行解析,构建一个将单词(字符串)映射为其向量表示(数值向量)的索引。

    glove_dir = r'E:\firefoxLoad\glove.6B'
    
    embedding_index = {}
    f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), errors='ignore')
    for line in f:
        values = line.split()
        word = values[0]
        coefs = np.asarray(values[1:], dtype='float32')
        embedding_index[word] = coefs
    f.close()
    
    print('Found %s word vectors. ' % len(embedding_index))
    # Found 399913 word vectors. 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ②构建一个可以加载到Embedding层的 嵌入矩阵,形状为(max_words,embedding_dim)。对于单词索引(分词时构建)中索引为 i 的单词,这个矩阵的元素 i 就是这个单词对应的 embedding_dim 维向量。注意,索引 0 只是一个占位符,不代表任何单词或标记。

    embedding_dim = 100
    
    embedding_matrix = np.zeros((max_words, embedding_dim))
    for word, i in word_index.items():
        if i < max_words:
            embedding_vector = embedding_index.get(word)
            if embedding_vector is not None:
                embedding_matrix[i] = embedding_vector
            # 嵌入索引找不到的词,其嵌入向量全为0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5. 定义模型1

    from keras.models import Sequential
    from keras.layers import Embedding, Flatten, Dense
    
    model = Sequential()
    model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
    model.add(Flatten())
    model.add(Dense(32, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.summary()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    Model: "sequential"
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding (Embedding)        (None, 100, 100)          1000000   
    _________________________________________________________________
    flatten (Flatten)            (None, 10000)             0         
    _________________________________________________________________
    dense (Dense)                (None, 32)                320032    
    _________________________________________________________________
    dense_1 (Dense)              (None, 1)                 33        
    =================================================================
    Total params: 1,320,065
    Trainable params: 1,320,065
    Non-trainable params: 0
    _________________________________________________________________
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6. 在模型中加载GloVe嵌入

    Embedding层 只有一个权重矩阵,是一个二维的浮点数矩阵,其中每个元素 i 是与 索引 i 相关联的词向量。

    此外,需要冻结 Embedding层(将trainable属性设置为False)。
    因为如果一个模型的一部分是经过训练的,而另外一部分是随机初始化的,那么在训练期间就不需要更新预训练的部分,避免所保存的信息损失。

    model.layers[0].set_weights([embedding_matrix])
    model.layers[0].trainable = False
    
    • 1
    • 2

    7.训练模型与评估模型

    (1)编译并训练模型

    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])
    
    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=32,
                        validation_data=(x_val, y_val))
    model.save_weights('pre_trained_glove_model.h5')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (2)绘制模型性能随时间的变化

    从图中可以看到,由于训练样本很少,模型很快就过拟合。
    在这里插入图片描述

    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)
    
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    8. 模型2:在不使用预训练词嵌入的情况下,训练相同模型

    在这种情况下,将会学到针对任务的输入标记的嵌入。如果有大量的可用数据,这种方法通常更加强大。

    (1)编译并训练模型

    from keras.models import Sequential
    from keras.layers import Embedding, Flatten, Dense
    
    model = Sequential()
    model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
    model.add(Flatten())
    model.add(Dense(32, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.summary()
    
    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])
    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=32,
                        validation_data=(x_val, y_val))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (2)绘制模型随时间的变化情况
    在这里插入图片描述

    9. 在测试数据上评估模型

    (1)对测试集数据进行分词

    test_dir = os.path.join(imdb_dir, 'test')
    
    labels = []
    texts = []
    
    for label_type in ['neg', 'pos']:
        dir_name = os.path.join(test_dir, label_type)
        for fname in sorted(os.listdir(dir_name)):
            if fname[-4:] == '.txt':
                f = open(os.path.join(dir_name, fname), errors='ignore')
                texts.append(f.read())
                f.close()
                if label_type == 'neg':
                    labels.append(0)
                else:
                    labels.append(1)
                    
    sequences = tokenizer.texts_to_sequences(texts)
    x_test = pad_sequences(sequences, maxlen=maxlen)
    y_test = np.asarray(labels)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (2)加载并评估第一个模型

    测试精度达到了50%

    model.load_weights('pre_trained_glove_model.h5')
    model.evaluate(x_test, y_test)
    # [0.8677374720573425, 0.5004799962043762]
    
    • 1
    • 2
    • 3

    2 循环神经网络

    1. 密集连接网络卷积神经网络没有记忆 ,单独处理每个输出,在输入与输入之间没有保存任何状态。对于这样的网络,要想处理数据点的序列或时间序列,需要将序列转换成单个数据点,然后一次性处理。这种网络叫做 「前馈网络」。

    2. 循环神经网络(RNN,recurrent neutral network)

    遍历所有序列元素,并保存一个状态,其中包含已查看内容相关的信息。该模型根据过去信息构建,随着新信息的进入而不断更新。

    3. RNN的前向传递

    RNN 的输入是一个张量序列,形状为(timesteps,input_features)

    RNN 对时间步(timesteps)进行遍历,在每个时间步,考虑 t 时刻的当前状态t 时刻的输入 [ 形状为(input_features,)] ,对二者计算得到 t 时刻的输出。然后,我们将下一个时间步的状态设置为上一个时间步的输出。对于第一个时间步,由于上一个时间步的输出没有意义,所以将状态初始化为一个全零向量,叫做网络的初始状态

    # RNN伪代码
    state_t = 0 # t时刻的状态
    # 对序列元素进行遍历
    for input_t in input_sequence: 
    	output_t = f(input_t, state_t)
    	state_t = output_t # 前一次的输出变成下一次迭代的状态
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以给出具体函数 f :从输入和状态到输出的变换,参数包括两个矩阵(W 和 U)和一个偏置向量。

    # 更详细的RNN伪代码
    state_t = 0
    for input_t in input_sequence:
    	output_t = activation(dot(W, input_t) + dot(U, state_t) + b)
    	state_t = output_t
    
    • 1
    • 2
    • 3
    • 4
    • 5
    # 简单RNN的 Numpy 实现
    import numpy as np
    
    timesteps = 100 # 输入序列的时间步数
    input_features = 32 # 输入特征空间的维度
    output_features = 64  # 输出特征空间的维度
    
    # 输入数据
    inputs = np.random.random((timesteps, input_features))
    
    # 初始状态 全0向量
    state_t = np.zeros((output_features,)) 
    
    # 创建随机的权重矩阵
    W = np.random.random((output_features, input_features))
    U = np.random.random((output_features, output_features))
    b = np.random.random((output_features,))
    
    successive_outputs = []
    # input_t 是形状为(input_features,)的向量
    for input_t in inputs:
    	output_t = np.tanh(np.dot(W, input_t) + np.dot(U, output_t) + b)
    	successive_outputs.append(output_t)
    	# 更新网络的状态,用于下一个时间步
    	state_t = output_t 
    
    # 最终输出形为(timesteps, output_features)的二维张量
    final_output_sequence = np.stack(successive_outputs, axis=0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    总之, RNN 是一个 for 循环,重复使用循环前一次迭代的计算结果。 RNN 的特征在于其时间步函数。

    本例最终输出一个形为(timesteps,output_features)的二维张量,其中每个时间步是循环在 t 时刻的输出。输出张量中的每个时间步 t 包含输入序列中时间步 0~t 的信息,也就是关于全部过去的信息。因此,在多数情况下,只需要最后一个输出(循环结束时的 output_t ),它已经包含了整个序列的信息

    2.1 Keras中的循环层

    1. SimpleRNN层

    上面使用 Numpy 的简单实现,对应一个实际的 Keras 层,即 SimpleRNN层
    SimpleRNN 能够处理序列批量,接收形状为(batch_size,timesteps,input_features)的输入。

    SimpleRNN 可以在两种不同模式下运行,一种是返回每个时间步连续输出的完整序列,即形状为(batch_size,timesteps,output_features)的三维向量;第二种是只返回每个输入序列的最终输出,即形状为(batch_size,output_features)的二维张量。
    通过 return_sequences 构造函数参数来控制。
    当 return_sequences = True 的时候,返回完整的状态序列。

    为了提高网络的表示能力,可以将多个循环层逐个堆叠,在这种情况下,所有中间层都需要返回完整的输出序列,设置 return_sequences = True 。

    2. 实例:IMDB电影评论分类

    (1)准备IMDB数据并进行预处理

    from keras.datasets import imdb
    from keras.preprocessing import sequence
    
    max_features = 10000 # 作为特征的单词个数
    maxlen = 500 # 在maxlen之后截断文本
    batch_size = 32
    
    print('Loading data...')
    (input_train, y_train), (input_test, y_test) = imdb.load_data(
        num_words=max_features)
    print(len(input_train), 'train sequence')
    print(len(input_test), 'test sequence')
    
    print('Pad sequences (samples x time)')
    input_train = sequence.pad_sequences(input_train, maxlen=maxlen)
    input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
    print('input_train shape:', input_train.shape)
    print('input_test shape:', input_test.shape)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结果:
    结果

    (2)用一个Embedding层和一个SimpleRNN层来训练一个简单的循环网络

    from keras.models import Sequential
    from keras.layers import Embedding, Dense, SimpleRNN
    
    model = Sequential()
    model.add(Embedding(max_features, 32))
    model.add(SimpleRNN(32))
    model.add(Dense(1, activation='sigmoid'))
    
    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])
    history = model.fit(input_train, y_train,
                        epochs=10,
                        batch_size=128,
                        validation_split=0.2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)绘制训练和验证的损失、精度

    在第三章,处理这个数据集的第一个简单方法得到的测试精度是 88%,与这个基准相比,这个小型循环网络的验证精度只有 85%
    原因在于输入只考虑了前500个单词,没有考虑整个序列,获得的信息比基准模型还少;另外,SimpleRNN不擅长处理长序列。

    在这里插入图片描述

    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)
    
    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 accuracy')
    plt.legend()
    
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.2 LSTM层和GRU层

    1.SimpleRNN 的缺点:

    不可能学到 长期依赖,因为它存在 梯度消失问题,随着层数的增加,网络变得无法训练。

    因此提出了 LSTM层 和 GRU层 来解决这个问题。

    2. LSTM层

    Simple层的一种变体, 增加了一种 「携带信息跨越多个时间步」的方法。
    原理:保存信息以便后面使用,防止较早期的信号在处理过程中逐渐消失。

    (1)为了了解 LSTM, 从 SimpleRNN 单元开始讨论。

    因为有多个权重矩阵,所以对单元中的 W 和 U 两个矩阵添加下标字母o (Wo 和 Uo),表示输出

    在这里插入图片描述

    (2)向这张图像增加额外的数据流,其中携带着跨越时间步的信息。

    它在不同的时间步的值叫做 Ct ,其中 C 表示 携带(carry)。它将与输入连接和循环连接进行运算,从而影响到下一个时间的状态。

    在这里插入图片描述

    (3)讨论携带数据流 Ct 下一个值的计算方法

    涉及三个不同变换,它们都有各自的权重矩阵,分别用 i 、 j 和 k 作为下标。

    伪代码如下:

    output_t = activation(dot(state_t, Uo) + dot(input_t, Wo) + dot(C_t, Vo) + b)
    
    i_t = activation(dot(state_t, Ui) + dot(input_t, Wi) + bi)
    f_t = activation(dot(state_t, Uf) + dot(input_t, Wf) + bf)
    k_t = activation(dot(state_t, Uk) + dot(input_t, Wk) + bk)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (4)组合三个不同变换,得到新的携带状态

    c_t+1 = i_t * k_t + f_t * c_t
    
    • 1

    在这里插入图片描述

    2.3 实例:使用 LSTM 进行 IMDB 电影评论分类

    from keras.layers import LSTM
    
    model = Sequential()
    model.add(Embedding(max_features, 32))
    model.add(LSTM(32))
    model.add(Dense(1, activation='sigmoid'))
    
    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metrics=['acc'])
    history = model.fit(input_train, y_train,
                        epochs=10,
                        batch_size=128,
                        validation_split=0.2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    从训练和验证的损失和精度可以看出, LSTM模型的验证精度已经达到了 89%,并且使用了小数据量。

    可以通过调节超参数「嵌入维度、LSTM输出维度 」,正则化 来改进这个模型。
    在这里插入图片描述

    3 循环神经网络的高级用法

    3.1 温度预测问题

    1. 天气时间序列数据集

    由德国耶拿的 马克思·普朗克生物地球化学研究所的气象站记录。

    在这个数据集中,每 10 分钟记录 14 个不同的量(比如气温、气压、湿度、风向等),其中包含多年的记录,原始数据可追溯到 2003 年,但本例仅使用 2009 - 2016 年的数据。

    (1)观察耶拿天气数据集的数据

    从输出可以看出,共有420 551 行数据(每行是一个时间步,记录了一个日期和14个与天气有关的值),输出了下列表头。

    import os 
    
    data_dir = r'E:\firefoxLoad\jena_climate_2009_2016.csv'
    fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')
    
    f = open(fname, errors='ignore')
    data = f.read()
    f.close()
    
    lines = data.split('\n')
    header = lines[0].split(',')
    lines = lines[1:]
    
    print(header)
    print(len(lines))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    ['"Date Time"', '"p (mbar)"', '"T (degC)"', '"Tpot (K)"', '"Tdew (degC)"', 
    '"rh (%)"', '"VPmax (mbar)"', '"VPact (mbar)"', '"VPdef (mbar)"', 
    '"sh (g/kg)"', '"H2OC (mmol/mol)"', '"rho (g/m**3)"', '"wv (m/s)"', 
    '"max. wv (m/s)"', '"wd (deg)"']
    420551
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2)将420 551 行数据转换成 Numpy 数组

    import numpy as np
    
    float_data = np.zeros((len(lines), len(header) - 1))
    for i, line in enumerate(lines):
        values = [float(x) for x in line.split(',')[1:]] # 去掉特征"Date Time"
        float_data[i, :] = values
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (3)绘制温度时间序列,观察温度每年的周期性变化。

    from matplotlib import pyplot as plt
    
    temp = float_data[:, 1] # 温度 "T (degC)"
    plt.plot(range(len(temp)), temp)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    (4)绘制前 10 天的温度时间序列,因为每 10 分钟记录一个数据,所以每天有 144 个数据点。

    从图中可以看到每天的周期性变化,尤其是最后 4 天特别明显。

    plt.plot(range(1440), temp[:1440])
    
    • 1

    在这里插入图片描述

    3.2 准备数据

    问题: 一个时间步是 10 分钟,每 steps 个时间步采样一次数据,给定过去 lookback 个时间步之内的数据,能否预测 delay 个时间步之后的温度?需要用到的参数如下:

    • lookback = 720:给定过去 5 天内的观测数据。
    • steps = 6:观测数据的采样频率是每小时一个数据点。
    • delay = 144 : 目标是未来 24 小时之后的数据。

    1. 数据预处理

    由于数据已经是数值型的,所以不需要做向量化。

    但是数据中的每个时间序列位于不同的范围,所以需要对每个时间序列分别做标准化,让它们在相似的范围内取较小的值。

    方法:让每个时间序列减去平均值,再除以标准差

    我们使用前 200 000 个时间步作为训练数据,所以只对这部分数据计算平均值和标准差。

    mean = float_data[:200000].mean(axis=0)
    float_data -= mean
    std = float_data[:200000].std(axis=0)
    float_data /= std
    
    • 1
    • 2
    • 3
    • 4

    2. 生成时间序列样本及其目标的生成器

    该生成器以当前的浮点数数组作为输入,并从最近的数据中生成数据批量,同时生成未来的目标温度。因为数据集中的样本是高度冗余的(对于第 N 个样本和第 N+1 个样本,大部分时间步都相同),所以我们将使用原始数据即时生成样本。

    生成器生成了一个元组(samples,targets),其中 samples 是输入数据的一个批量,targets 是对应的目标温度数组。生成器参数如下:

    • data:浮点数数据组成的原始数组,需要进行标准化处理;
    • lookback:输入数据应该包括过去多少个时间步;
    • delay:目标应该在未来多少个时间步之后;
    • min_index 和 max_index :data 数组中的索引,用于界定需要抽取哪些时间步。有助于保存一部分数据用于验证,另一部分用于测试。
    • shuffle:打乱样本,或者按顺序抽取样本;
    • batch_size:每个批量的样本数;
    • step:数据采样的周期(单位:时间步)。我们将其设为 6 ,每小时抽取一个数据点。
    def generator(data, lookback, delay, min_index, max_index, 
                  shuffle=False, batch_size=128, step=6):
        if max_index is None:
            max_index = len(data) - delay - 1
        i = min_index + lookback
        while 1:
            # 打乱数据
            if shuffle:
                rows = np.random.randint(
                    min_index + lookback, max_index, size=batch_size)
            # 按顺序抽取
            else:
                # 抽取的数据已经超出可取范围,从头开始取
                if i + batch_size >= max_index:
                    i = min_index + lookback
                rows = np.arange(i, min(i + batch_size, max_index))
                i += len(rows)
            
            samples = np.zeros((len(rows), lookback // step, data.shape[-1]))
            # lookback // step:总的采样数目
            # data.shape[-1]:features_num
            targets = np.zeros((len(rows),))
            
            # 将数据存入样本和目标
            for j, row in enumerate(rows):
            	# 取出索引
                indices = range(rows[j] - lookback, rows[j], step)
                samples[j] = data[indices]
                # targets即当前时间的delay个时间步的温度
                targets[j] = data[rows[j] + delay][1]
            yield samples, targets
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3. 实例化三个生成器

    每个生成器分别读取原始数据的不同时间段:训练生成器读取前 200 000 个时间步,验证生成器读取随后的 100 000 个时间步,测试生成器读取剩下的时间步。

    lookback = 1440
    step = 6
    delay = 144
    batch_size = 128
    
    train_gen = generator(float_data,
                          lookback=lookback,
                          delay=delay,
                          min_index=0,
                          max_index=200000,
                          shuffle=True,
                          step=step,
                          batch_size=batch_size)
    
    validation_gen = generator(float_data,
                          lookback=lookback,
                          delay=delay,
                          min_index=200001,
                          max_index=300000,
                          shuffle=True,
                          step=step,
                          batch_size=batch_size)
    
    test_gen = generator(float_data,
                         lookback=lookback,
                         delay=delay,
                         min_index=300001,
                         max_index=None,
                         shuffle=True,
                         step=step,
                         batch_size=batch_size)
    
    # 为了查看整个验证集,需要从val_steps中抽取多少次
    val_steps = (300000 - 200001 - lookback) // batch_size 
    # 为了查看整个测试集,需要从test_steps中抽取多少次
    test_steps = (len(float_data) - 300001 - lookback) // batch_size
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3.3 基于常识、非机器学习的基准方法

    1. 为什么要建立基于常识的基准方法?

    基于常识的基准方法,可以作为合理性检查,还可以建立一个基准,更高级的机器学习模型需要打败这个基准才能表现出其有效性。

    2. 本例的基准方法

    假设:「温度时间序列是连续的,并且具有每天的周期性变化」。因此,一种基于常识的方法就是 「始终预测 24 小时后的温度等于现在的温度」,使用 平均绝对误差(MAE) 指标来评估。

    3. 计算符合常识的基准方法的 MAE

    def evaluate_naive_method():
        batch_maes = []
        for step in range(val_steps):
            samples, targets = next(val_gen) 
            preds = samples[:, -1, 1]
            mae = np.mean(np.abs(preds - targets))
            batch_maes.append(mae)
        print(np.mean(batch_maes))
    
    evaluate_naive_method()
    # 0.28989001791386343
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    得到的 MAE 为 0.29。因为温度数据被标准化成 均值为 0、标准差为 1,所以无法直接对这个值进行解释,它转化成温度的平均绝对误差为 0.29 * temperature_std 摄氏温度,即 2.57 °C 。

    这个平均误差比较大,接下来使用深度学习知识来改进结果。

    3.4 一种基本的机器学习方法

    1. 为什么要先使用基本的机器学习方法?

    在开始研究复杂且计算代价很高的模型(RNN)之前,尝试使用简单且计算代价低的机器学习模型很有用,保证进一步增加问题的复杂度是合理的。

    2. 训练并评估一个密集连接模型

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    # float_data.shape[-1]:feature_nums
    model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
    model.add(layers.Dense(32, activation='relu'))
    model.add(layers.Dense(1))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen,
                                  steps_per_epoch=500,
                                  epochs=20,
                                  validation_data=val_gen,
                                  validation_steps=val_steps
                                  )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3. 绘制验证和训练的损失曲线

    import matplotlib.pyplot as plt
    
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epochs = range(1, len(loss) + 1)
    
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    从图中可以看出,部分验证损失接近不包含学习的基准方法,但这个结果并不可靠。同时,这也展示了基准方法很难超越,因为常识中包含大量有价值的信息。

    3.5 第一个循环网络基准

    虽然第一个 全连接方法 的效果并不好,但这并不意味着机器学习方法不适用。

    全连接方法 首先将时间序列展平,从输入数据中删除了 时间 的概念,然而「数据序列的因果关系和顺序」都很重要。

    1. 训练并评估基于 GRU 的模型

    GRU层 (门控循环单元),工作原理与 LSTM 相同,但是做了一些简化,因此计算代价更低,当然表示能力也不如 LSTM 。

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen, 
                                  steps_per_epoch=500,
                                  epochs=20,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 绘制验证和训练的损失曲线

    import matplotlib.pyplot as plt
    
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epochs = range(1, len(loss) + 1)
    
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    从图中可以看出,在开始显著过拟合之前,MAE 约为 0.265,反标准化转换成温度的平均绝对误差为 2.35 °C ,与最初的 2.57 °C 相比,有所提高,但仍有改进的空间。

    3.6 使用循环dropout 来降低过拟合

    从上面的例子可以发现,模型出现了 过拟合,验证损失和训练损失明显偏离。

    1. dropout

    (1)方法

    将某一层的输入单元随机设置为 0 ,目的是打破该层训练数据中的偶然相关性。

    (2)在「循环网络」中使用 dropout 的正确方法

    • 对每个时间步使用 相同 的 dropout 掩码(dropout mask,相同模式的舍弃单元),让网络沿着时间正确传播学习误差;
    • 将相同的 dropout 掩码应用于层的 内部循环激活 (叫做循环 dropout 掩码),能够对 GRU、LSTM 等循环层得到的表示做正则化。

    (3)嵌入 Keras 循环层

    Keras 的每个循环层都有两个与 dropout 相关的参数,一个是 dropout ,它是一个浮点数,指定该层输入单元的 dropout 比率;另一个是 recurrent_dropout ,指定循环单元的 dropout 比率。

    2. 训练并评估一个使用 dropout正则化 的基于 GRU 的模型

    向 GRU 层中添加 dropout 和循环 dropout ,观察是否能减弱 过拟合 的影响。

    因为使用 dropout 正则化的网络需要更长的时间才能完全收敛,所以网络训练轮次增加为原来的 2 倍。

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.GRU(32, 
                         dropout=0.2, 
                         recurrent_dropout=0.2,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen, 
                                  steps_per_epoch=500,
                                  epochs=40,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    观察训练和验证损失,发现前 30 个轮次不再发生过拟合,并且,评估分数更稳定,但是最佳分数并没有比之前低很多。

    3.7 循环层堆叠

    解决完过拟合问题,应该考虑增加网络容量,通常做法是 增加每层单元数增加层数 ,「循环层堆叠」是一个很好的例子。

    在 Keras 中逐个堆叠循环层,所有中间层都应该返回完整的输出序列(1个 3D 张量),而不是只返回最后一个时间步输出,通过指定 return_sequences = True 来实现。

    1. 训练并评估一个使用 dropout 正则化的堆叠 GRU 模型

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.GRU(32,
                         dropout=0.1,
                         recurrent_dropout=0.5,
                         return_sequences=True,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.GRU(64, activation='relu',
                         dropout=0.1,
                         recurrent_dropout=0.5))
    model.add(layers.Dense(1))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen,
                                  steps_per_epoch=500,
                                  epochs=40,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    从图中可以看出,添加一层 对结果的改进并不显著。因此可以得出两个结论:

    • 因为过拟合不是很严重,所以可以放心地增大每层的大小,进一步改进验证损失,但是计算成本很高
    • 添加一层 后模型并没有显著改进, 提高网络能力的回报在逐渐减小。

    3.8 使用双向 RNN

    1. 双向 RNN 介绍

    双向 RNN 利用了 RNN 的顺序敏感性:包含两个普通的 RNN ,每个 RNN 分别沿一个方向对输入序列进行处理(时间顺序和时间逆序),然后将它们的表示合并到一起,得到更加丰富的表示,并捕捉到仅使用正序 RNN 时可能忽略的一些模式。

    在这里插入图片描述

    2. 训练并评估一个双向 LSTM

    在 Keras 中将一个双向 RNN 实例化,使用 Bidirectional层 ,第一个参数是一个循环层实例。
    Bidirectional 对这个循环层创建了第二个单独实例,然后使用一个实例按正序处理输入序列, 另一个实例按照逆序处理输入序列。

    在 IMDB 情感分析上来试一下这种方法。

    结论: 这个模型的表现比上一节的普通 LSTM 略好,验证精度超过 89% ,但是它也很快开始过拟合,因为双向层的参数个数是正序 LSTM 的 2 倍。

    model = Sequential()
    model.add(layers.Embedding(max_features, 32))
    model.add(layers.Bidirectional(layers.LSTM(32)))
    model.add(layers.Dense(1, activation='sigmoid'))
    
    model.compile(optimizer='rmsprop',
                  loss='binary_crossentropy',
                  metics=['acc'])
    
    history = model.fit(x_train, y_train,
                        epochs,
                        batch_size=128,
                        validation_split=0.2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3. 训练一个双向 GRU

    将双向 GRU 模型应用于温度预测任务。

    结果: 这个模型的表现和普通 GRU层 差不多,因为所有的预测能力肯定来自正序的那一半网络。

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.Bidirectional(
            layer.GRU(32), input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    
    model.compile(optimizer=RMSprop(), loss='mae')
    
    history = model.fit_generator(train_gen,
                                  steps_per_epoch=500,
                                  epochs=40,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.9 更多尝试

    为了提高温度预测问题的性能:

    • 在堆叠循环层调节每层的单元个数
    • 调节 RMSprop 优化器的学习率;
    • 使用 LSTM层 替代 GRU层。
    • 在循环层上使用更大的密集连接回归器 ,用更大的Dense层,或者使用Dense层的堆叠;
    • 测试集上运行性能最佳的模型(即验证MAE最小的模型),否则,开发的网络将会对验证集过拟合。

    3.10 小结

    • 遇到新问题,为选择的指标建立一个基于常识的基准 ,作为新模型的判断标准;
    • 在尝试代价较高的模型之前,先尝试简单的模型,证明增加计算代价是有意义的;
    • 循环网络很适合处理具有时间顺序的数据;
    • 循环网络中使用 dropout ,应该使用一个不随时间变化的 dropout 掩码与循环 dropout 掩码 ,二者内置于 Keras 的循环层,只需要使用循环层的 dropout 和 recurrent_dropout 参数;
    • 堆叠 RNN 的表示能力更强大,但是计算代价也更高;
    • 双向 RNN 从两个方向查看一个序列,适用于自然语言处理问题。

    4 一维卷积神经网络

    4.1 理解序列数据的一维卷积

    一维卷积层可以识别序列中的局部模式,具有平移不变性,这是因为对每个序列段执行相同的输入变换,所以在句子中某个位置学到的模式稍后可以在其他位置被识别。

    在这里插入图片描述

    4.2 序列数据的一维池化

    从输入中提取一维序列段(子序列),然后输出其最大值(最大池化)或平均值(平均池化),能够降低一维输入的长度(子采样)。

    4.3 实现一维卷积神经网络

    1. Keras 中的一维卷积神经网络是 Conv1D层 ,接收输入的形状为 (samples,time,features) 的三维张量,并返回类似形状的三维张量。

    卷积窗口是时间轴上的一维窗口,时间轴是输入张量的第二个轴。

    接下来构建一个简单的两层一维卷积神经网络,并将其应用于 IMDB 情感分类任务。

    2. 准备 IMDB 数据

    from keras.datasets import imdb
    from keras.preprocessing import sequence
    
    max_features = 10000
    max_len = 500
    print('Loading data...')
    (X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=max_features)
    print(len(x_train), 'train sequences')
    print(len(x_test), 'test sequences')
    
    print('Pad sequences (sample x time)')
    x_train = sequence.pad_sequences(x_train, maxlen=max_len)
    x_test = sequence.pad_sequences(x_test, maxlen=max_len)
    print('x_train shape:', x_train.shape)
    print('x_test shape:', x_test.shape)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    3. 在 IMDB 数据上训练并评估一个简单的一维卷积神经网络

    一维卷积神经网络是 Conv1D层MaxPooling1D层 的堆叠,最后是一个 全局池化层Flatten层 ,将三维输出转换为二维输出,这样就可以向模型添加一个或多个 Dense层 ,用于回归或分类。

    不过,一维卷积神经网络可以使用更大的卷积窗口
    对于二维卷积层,3 * 3 的卷积窗口包含 3 * 3 = 9 个特征向量;对于一维卷积层,大小为 3 的卷积窗口只包含 3 个卷积向量。因此,可以使用大小等于 7 或 9 的一维卷积窗口。

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.Embedding(max_features, 128, input_length=max_len))
    model.add(layers.Conv1D(32, 7, activation='relu'))
    model.add(layers.MaxPooling1D(5))
    model.add(layers.Conv1D(32, 7, activation='relu'))
    model.add(layers.GlobalMaxPooling1D())
    model.add(layers.Dense(1))
    
    model.summary()
    
    model.compile(optimizer=RMSprop(lr=1e-4),
                  loss='binary_crossentropy',
                  metrics=['acc'])
    
    history = model.fit(x_train, y_train,
                        epochs=10,
                        batch_size=128,
                        validation_split=0.2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    从图中可以看出,验证精度略低于 LSTM,但是在 CPU 和 GPU 上的运行速度更快

    4.4 结合 CNN 和 RNN 处理长序列

    1. 一维卷积神经网络的缺点

    时间步的顺序不敏感,因为一维卷积神经网络分别处理每个输入序列段。

    2.结合 CNN 和 RNN 的方法

    在 RNN 前面使用一维卷积神经网络作为预处理步骤,将长的输入序列转换为高级特征组成的短序列(下采样),提取的特征组成的序列成为网络中 RNN 的输入。

    在这里插入图片描述

    3. 将上述方法应用于温度预测数据集

    为了得到更长的序列,可以查看更早的数据(增大数据生成器的 lookback 参数)或查看分辨率更高的时间序列(减小生成器的step参数)。

    在这里将 step 减半,得到时间序列的长度变为之前的两倍,温度数据的采样频率变为每 30 分钟一个数据点。

    (1) 为耶拿数据集准备更高分辨率的数据生成器

    step = 3 # 之前为6
    lookback = 720
    delay = 144
    
    train_gen = generator(float_data,
                          lookback=lookback,
                          delay=delay,
                          min_index=0,
                          max_index=200000,
                          shuffle=True,
                          step=step)
    
    val_gen = generator(float_data,
                        lookback=lookback,
                        delay=delay,
                        min_index=200001,
                        max_index=300000,
                        step=step)
    
    test_gen = generator(float_data,
                         lookback=lookback,
                         delay=delay,
                         min_index=300001,
                         max_index=None,
                         step=step)
    
    val_steps = (300000 - 200001 - lookback) // 128
    test_steps = (len(float_data) - 300001 - lookback) // 128
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    (2)结合一维卷积基和 GRU 的模型

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.Conv1D(32, 5, activation='relu',
                            input_shape=(None, float_data.shape[-1])))
    model.add(layers.MaxPooling1D(3))
    model.add(layers.Conv1D(32, 5, activation='relu'))
    model.add(layers.GRU(32, dropout=0.1, recurrent_dropout=0.5))
    model.add(layers.Dense(1))
    
    model.summary()
    
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen,
                                  steps_per_epoch=500,
                                  epochs=20,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    从验证损失来看,这种架构的效果不如只用正则化 GRU ,但是它的速度快很多,查看了两倍的数据量
    在这里插入图片描述

    5 本章小结

    • 学到了以下技术,可以广泛应用于序列数据(从文本到时间序列)组成的数据集。
      • 如何对文本分词
      • 什么是词嵌入,如何使用词嵌入;
      • 是什么循环网络,如何使用循环网络;
      • 如果堆叠 RNN 层和使用双向 RNN
      • 如何使用一维卷积神经网络来处理序列;
      • 如何结合一维卷积神经网络和RNN来处理长序列。
  • 相关阅读:
    【MySQL】22-MySQL数据类型超详细汇总
    Add Gantt Charts to your JavaScript Projects
    【Java】泛型讲解
    职场的边界感、底线原则与陷阱
    linux添加环境变量
    Python实现图片与PDF互相转换
    3分钟让你学会axios在vue项目中的基本用法(建议收藏)
    Docker 入门版
    Redis数据类型之Stream系列一
    软考 系统架构设计师系列知识点之设计模式(3)
  • 原文地址:https://blog.csdn.net/weixin_43894455/article/details/128119781