Keras深度学习实践7——循环卷积神经网络

作者: 小可哥哥V | 来源:发表于2019-06-04 21:33 被阅读1次

    内容参考以及代码整理自“深度学习四大名“著之一《Python深度学习》
    查看完整代码,请看: https://github.com/ubwshook/MachineLearning

    密集连接网络和卷积神经网络都有一个主要特点,那就是它们都没有记忆。它们单独处理每个输入,在输入与输入之间没有保存任何状态,对于这样的网络,要想处理数据点的序列或时间序列,你需要向网络同时展示整个序列,即将序列转换成单个数据点。

    与此相反,当你在阅读的时候,你是一个词一个词地阅读,同时会记住之前的内容。这让你能够动态理解这个句子所传达的含义。生物智能以渐进的方式处理信息,同时保存一个关于所处理内容的内部模型,这个模型是根据过去的信息构建的,并随着新信息的进去而不断更新。

    循环神经网络的原理与此相同: 它处理序列的方式,遍历所有序列元素,并保存一个状态(state), 起重工包含与已查看内容相关的信息。实际上,RNN是一类具有内部环的神经网络。在处理两个不同的独立序列之间,RNN状态会被重置,你仍可以将一个序列看作单个数据点,即网络的单个输入。真正改变的是,数据点不再是在单个步骤中进行处理,相反,网络内部会对序列元素进行遍历。


    RNN原理

    我们用Numpy来实现一个简单RNN的前向传递。这个RNN的输入是一个张量序列,我们将其编码为大小为(timesteps, input_features)的二维张量。

    """
    纯python实现一个RNN的原理
    """
    timesteps = 100  # 输入序列的时间步
    input_features = 32  # 输入特征空间维度
    output_features = 64  # 输出特征空间维度
    
    inputs = np.random.random((timesteps, input_features))  # 输入数据,此处仅为示意
    state_t = np.zeros((output_features,))  # 出事状态为0
    
    # 创建随机的权重矩阵
    W = np.random.random((output_features, input_features))
    U = np.random.random((output_features, output_features))
    b = np.random.random((output_features,))
    
    successive_outputs = []
    for input_t in inputs:  # 输入形状为(input_features,)的向量
        # 由输入和当前状态计算得到输出
        output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
        successive_outputs.append(output_t)  # 将输出保存到列表中
        state_t = output_t  # 更新网络状态
    
    final_output_sequence = np.concatenate(successive_outputs, axis=0)
    

    一、Keras实现简单RNN

    keras中简单RNN层使用下面语句:

    from keras.layers import SimpleRNN
    

    SimpleRNN层可以批量处理输入序列,接收形状为(batch_size, timesteps, input_feature)。与所有循环层一样SimpleRNN可以再两种模式下运行:一种是返回每个时间步连续输出的完整序列,即形状为(batch_size, timesteps, output_features)的三维张量;另一种是只返回每个输入序列的最终输出,即形状为(batch_size, output_features)的二维张量。这两种模型由return_sequences这个构造参数来控制。下面是一个简单RNN的例子。

    1. 数据准备

    这个例子使用的是imdb数据集,这个我们在之前使用密集连接网络解决过,如果需要回顾请点击这里

    max_features = 10000  # 作为特征的单词数量,也就是高频出现的10000个词语
    maxlen = 500  # 评论500词以上截断
    batch_size = 32  # 每个批次的数据个数
    
    print('Loading data...')
    (input_train, y_train), (input_test, y_test) = load_local(num_words=max_features)
    print(len(input_train), 'train sequences')
    print(len(input_test), 'test sequences')
    
    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)
    
    

    2.训练简单RNN网络

    这个模型第一层是词嵌入层获得词向量(词嵌入回顾),第二层使用简单RNN循环处理数据,第三层使用密集连接构成分类器。

    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)
    show_acc_results(history)
    
    简单RNN模型的精度
    简单RNN模型的损失

    训练结果看看起来不是很理想,有两个原因:1.输入只考虑了500个单词; 2.SimpleRNN不擅长处理长序列,比如文本。所以我们要引入更高级的循环层

    二、LSTM和GRU

    Keras中还有两个循环层,LSTM和GRU。SimpleRNN的最大问题是,不能学习长期依赖,存在梯度消失问题。随着层数的增加,网络最终无法训练。

    LSTM层是SimpleRNN层的一种变体,它增加了一种携带信息跨越多个时间步的方法。假设有一条传送带,其运行方向平行于你所处理的序列。序列中的信息可以在任意位置跳上传送带,然后被传送到更晚的时间步,并在需要的时候原封不动的跳回来。它保存信息以便后面使用,从而防止较早期的信号在处理中逐渐消失。

    LSTM原理图

    LSTM的理解推荐一篇文章,这里就不展开去讲述。下面我们来看一下keras怎么去实现LSTM,非常简单就是使用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)
    show_acc_results(history)
    

    三、循环神经网络的高级用法

    为了提高循环神经网络的性能和泛化能力,我们将依托耶拿气温预测问题来实践3种提高循环神经网络的技巧:

    • 循环dropout: 使用dropout来降低过拟合
    • 堆叠循环层: 提高循环神经网络的表示能力
    • 双向循环层: 将相同的信息以不同的方式呈现给循环网络,可以提高精度并缓解遗忘问题。

    1.问题描述和数据准备

    数据集是一个天气事件序列数据集,它是德国耶拿地区的气象数据。在这个数据集中,每10分钟记录14个不同的量,比如气温、气压、湿度、风向等。我们将使用这个数据集构造模型,输入最近的一些数据,可以预测24小时之后的气温。

    数据集下载地址:https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip

    首先我们解析数据,将各项指标的浮点数数据存放在张量中:

    def get_temperature_data():
        """
        对原始数据处理
        :return: 返回标准化后的浮点数据,和标准差
        """
        data_dir = 'E:/git_code/data/jena_climate'
        fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')
        f = open(fname)
        data = f.read()
        f.close()
        lines = data.split('\n')
        header = lines[0].split(',')
        lines = lines[1:]
        print(header)
        print(len(lines))
    
        float_data = np.zeros((len(lines), len(header) - 1))
        for index, line in enumerate(lines):
            values = [float(x) for x in line.split(',')[1:]]
            float_data[index, :] = values
    
        mean = float_data[:200000].mean(axis=0)
        float_data -= mean
        std = float_data[:200000].std(axis=0)
        float_data /= std
    
        return float_data, std
    

    绘制温度曲线:

    def show_sequence(float_data):
        """
        展示序列图像
        :param float_data: 数据序列
        :return: 返回浮点数据
        """
        temp = float_data[:, 1]
        plt.plot(range(len(temp)), temp)
        plt.show()
        plt.plot(range(1440), temp[:1440])
        plt.show()
    
    温度数据曲线

    解决这个问题一些数据需要制定下来:

    float_data, std = get_temperature_data()  # 获取处理后的数据和标准差
    LOOK_BACK = 1440  # 输入数据应该包括过去的多少个时间步
    STEP = 6  # 数据采样周期。 设置为6,每小时抽取一个数据点
    DELAY = 144  # 目标应该在未来的多少个时间步
    BATCH_SIZE = 128  # 每批数据的样本数
    VAL_STEPS = (300000 - 200001 - LOOK_BACK) // BATCH_SIZE  # 验证数据的批次数量
    TEST_STEPS = (len(float_data) - 300001 - LOOK_BACK) // BATCH_SIZE  # 测试数据的批次数量
    

    构造数据生成器,来组织数据以便可以给神经网络使用:

    def generator(data, lookback, delay, min_index, max_index, shuffle=False, batch_size=128, step=6):
        """
        根据参数生成数据,用于进行训练
        :param data: 浮点数据组成的原始数据
        :param lookback: 输入数据应该包括过去的多少个时间步
        :param delay: 目标应该在未来的多少个时间步
        :param min_index: data中的索引,用于确定抽取数据的范围
        :param max_index: data中的索引,用于确定抽取数据的范围
        :param shuffle: 是否打乱样本
        :param batch_size: 每批数据的样本数
        :param step: 数据采样周期。 设置为6,每小时抽取一个数据点
        :return:
        """
        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]))
            targets = np.zeros((len(rows),))
            for j, row in enumerate(rows):
                indices = range(rows[j] - lookback, rows[j], step)
                samples[j] = data[indices]
                targets[j] = data[rows[j] + delay][1]
            yield samples, targets
    

    2.确定训练的比较基准

    在开始神经网络的训练之前,我们先可以确定一些基准,更高级的神经网络起码要比这个基准要好。有时候这个基准也是很难被打败的。

    这里我们假设24小时后的温度与现在相同,这样的假设预测的平均绝对误差(mae)可以作为基准:

    def evaluate_naive_method():
        """
        计算一个基准精度,始终预测24小时后的温度与现在的温度相同,以下代码就计算局方绝对误差(MAE)指标来评估这种方法。
        :return:
        """
        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))
    

    计算的MAE为0.29, 绝对误差为0.29*std,即2.57摄氏度。

    另一个基准是使用密集连接神经网络来处理:

    def train_dense(float_data):
        """
        密集连接网络预测问题
        :param float_data: 标准化后温度数据
        :return:
        """
        model = Sequential()
        model.add(layers.Flatten(input_shape=(LOOK_BACK // 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)
        plt_loss(history)
    
    密集连接网络的损失

    使用密集连接层的损失要比前面一种基准大,可以看出基准并不容易超越。

    第三种基准是循环网络基准,使用一层GRU层的循环神经网络。GRU层比LSTM更加简化计算代价更低。

    def train_gru(float_data):
        """
        使用GRU循环网络预测温度
        :param float_data: 标准化后温度数据
        :return:
        """
        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)
        plt_loss(history)
    
    GRU训练损失

    这一次的训练结果约为0.265,比基准好好多了,但训练很快出现了过拟合,仍然可以改进。

    3.使用循环dropout来降低拟合

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

    def train_gru_dropout(float_data):
        """
        使用GRU循环网络并使用dropout来降低过拟合
        :param float_data: 标准化后温度数据
        :return:
        """
        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)
        plt_loss(history)
    
    GRU结合dropout训练损失

    过拟合现象比之前要好的多,但是结果提升并不大。

    4.循环层堆叠

    增加了dropout模型不再拟合,但是性能高似乎遇到了瓶颈,我们可以尝试增加网络容量,增加循环层:

    def train_stacking_gru(float_data):
        """
        使用GRU循环网络 + dropout + 堆叠
        :param float_data: 标准化后温度数据
        :return:
        """
        model = Sequential(float_data)
        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)
        plt_loss(history)
    

    训练结果入下:


    stack_gru.png

    4.双向RNN

    双向RNN在某些任务上比普通的RNN要好。他会使用输入的正向和反向都进行训练:

    def train_bidirectional_gru(float_data):
        """
        使用双向GRU来训练网络
        :return:
        """
        model = Sequential()
        model.add(layers.Bidirectional(
        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=40,
                                      validation_data=val_gen, validation_steps=VAL_STEPS)
        plt_loss(history)
    

    训练结果入下,这个结果不是很好,比基准还要差一些:


    gru_bi.png

    相关文章

      网友评论

        本文标题:Keras深度学习实践7——循环卷积神经网络

        本文链接:https://www.haomeiwen.com/subject/xantxctx.html