《tensorflow实战笔记》通俗详述RNN理论,LSTM理论,以及LSTM对于PTB数据集进行实战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33431368/article/details/85288590

一、RNN

顾名思义,循环则神经元有前后的联系,而不光是像FCN(全连接)那样的只有输入和输出的同时间的输入输出,也可以说这种网络没有利用到时间的信息,更准确的说应该是一种序列信息,不管是位置序列、还是时间序列。

1.结构

从网络结构上,循环神经网络会记忆之前的信息,并利用之前的信息影响后面结点的输出。也就是说,循环神经网络的隐藏层之间的结点是有连接的 ,隐藏层的输入不仅包括输入层的输出 ,还包括上一时刻隐藏层的输出,当然如果需要后面序列的信息,也需要下一时刻的输出作为输入,所以这也就是单向RNN, 还是双向RNN。
在这里插入图片描述
最经典的一个图。
左面为表示出神经网络循环的感觉,后面是真实状态的展开。
在每一时刻 t,循环神经网络会针对该时刻的输入结合当前模型的状态给出一个输出,并更新模型状态。
理想上,在时刻 t,状态 ht-1 浓缩了前面序列 Xo,X1->Xt- 1 的信息,用于作为输出 o 的参考,可以理解为输出Ot有着前面所有的输入的信息关系。
在这里插入图片描述
对于我所画的所示,可以看出如果别的输入输出不理会的话,其实对于Ot和X0来讲就相当于一个有t个中间隐藏层的前馈神经网络,因此可以直接使用反向传播算法进行训练,而不需要任何特别的优化算法。这样的训练方法称为“沿时间反向传播”,是训练循环神经网络最常见的方法。
在这里插入图片描述这个图和上面那个图大同小异,我这里主要是想强调一个东西。
就是这个V,因为一般状态(state)的维度以及输入之后得到的维度都是需要我们在连接一个全连接层才能得到最后我们所想要的维度的,这个全连接层在这个图里就是V,状态就是W也就是上图中的h,一般用h表示。
一般的神经网络有两个输入,一个是当前时刻的输入样本,一部分是上一时刻的状态。
所以假设 输入的数据为x维,状态的维度为n,则input的维度为x+n,如果循环神经元就是全连接层网络,因为此时的输出还是下一次的输入状态所以应该还是n维,所以循环体的参数个数为(n+x)*n+n
下图为一个例子讲述上述所说的关系,当然这只是神经网络为纯粹全连接的形式。
在这里插入图片描述

2.机器翻译

当然对于循环神经网络最多的就是机器翻译的model,大体形式如下:
在这里插入图片描述
来看一个example
在这里插入图片描述
EOS为结束符 LSTM为较为流行的rnn网络
从这两个图可以得到以下几个结论:每个时刻都要有输入但是不一定要有输出,当整个时序输入序列输入完毕之后,之后每个预测的值都是下一个时刻的输入。

还有更多比较常见的rnn形式如下,看图片即可知道其大概形式和结构

3.双向RNN

在这里插入图片描述

4.深层RNN网络

在这里插入图片描述

5.RNN网络的dropout

在这里插入图片描述类似卷积神经网络只在最后的全连接层中使用 dropout ,循环神 经网络 一般只在不同层循环
体结构之间使用 dropout ,而不在同一层的循环体结构之间使用。 也就是说从时刻 t-1 传递
到时刻 t 时,循环神经网络不会进行状态的 dropout ;而在同一个时刻 t 中,不同层循环体
之间会使用 dropout 。
白话来说就是对于深层的rnn来讲,每一层之间有dropout,而每一个时刻之间不存在dropout。

二、LSTM

上述的rnn,理论上讲是t时刻可以和0->t-1时刻的信息全都有关联,但是事实上无法做到这一点,可能会引起loss的爆炸或者消散。
所以引出了LSTM(Long short-term memory)长短时记忆网络。
为什么叫这么名字呢???
其实还是顾名思义,想想看我们去用时序去预测下一个时刻的结果的时候,以时间序列的句子预测为例,这个时刻的结果可能和前面一小段有关,也可能和很久之前的信息有关。
比如:
①这里的海很____ 一般来讲都是蓝对吧,也可以说蓝输出的可能性最大
②最近旁边的工厂放了很多污水,这里的海很___ 这回就应该是黑或者污浊了吧
③这里的海很__,都没有多少生物生存了。 这里应该也是浑浊了吧,最大可能性。

①②:所以有选择的吸取以前的信息还是有必要的,或者说不是什么时候都需要很久之前的信息,或者很近的信息,也就是说有用的信息有大有小,由远有近。
③:这里对应于后面的信息也要关联,也就是所谓的双向lstm。

1.先来看一下lstm的结构示意图

在这里插入图片描述
说明

  • Ct-1 上一时刻的状态值。
  • ht-1 上一时刻的输出
  • Xt  这个时刻的输入
  • ht  这个时刻的输出

有三个核心结构

  • 输入门:用很通俗的说法就是输入的信息你想让进来这个网络多少,输入信息由上一时刻的输入和这个时刻的输入组成
  • 输出门:输出的信息走的门。
  • 遗忘门:最为关键的门,选择之前的信息状态想去遗忘多少,最后之前的信息让进来网络多少,由上一时刻的状态值和这个时刻的输入以及上一时刻的输出组成。
    输入门都是对应的组成成分,组成一个新的维度的输入,之后通过sigmoid的函数得到一个0-1的值,1为全部信息都通过,0则一点都不要。

2.细节图

在这里插入图片描述

扫描二维码关注公众号,回复: 4749362 查看本文章

三、NLP实战

1.下载PTB数据集

如果没有这个数据集可以按照我以前写的文章。
《TensorFlow学习笔记》完美解决 pip3 install tensorflow 没有models库,读取PTB数据
也可以直接下载使用
http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
主要用如图几个文件
在这里插入图片描述

里面都是英文句子
在这里插入图片描述

2.数据集预处理

①先把txt中的所有出现的word整理成一个按降序排列的词汇表文件

generate_VOCAB.py

# -*- coding:UTF-8 -*-

"""
@Author:Che_Hongshu
@Modify:2018.12.16
"""
import codecs
import collections
from operator import itemgetter

RAW_DATA = "simple-examples/data/ptb.train.txt"  # 输入的ptb训练数据

VOCAB_OUTPUT = "ptb.vocab" #输出词汇表文件

counter = collections.Counter() #一个计数器,统计每个单词出现的次数

with codecs.open(RAW_DATA, "r", "utf-8") as f: #utf-8格式读取
    for line in f:
        for word in line.strip().split(): #line.strip().split()相当于把每一行的前后空格去掉,再根据空格分词生成list
            counter[word] += 1 #统计相同单词出现次数+1
#  Counter 集成于 dict 类,因此也可以使用字典的方法,此类返回一个以元素为 key 、元素个数为 value 的 Counter 对象集合
# 依据key排序 itermgetter(1)为降序
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True)

#  转换成单词string的list
sorted_words_list = [x[0] for x in sorted_word_to_cnt]

#  加入句子结束符
sorted_words_list = ["<eos>"] + sorted_words_list

with codecs.open(VOCAB_OUTPUT, 'w', 'utf-8') as file_output:
    for word in sorted_words_list:
        file_output.write(word + '\n')

ptb.vocab如下,按行数依次递减为出现次数依次减少的word
在这里插入图片描述在这里插入图片描述
一共为10000个高频词汇,因为这里的PTB数据集已经经过前期处理了,所以这边如果是其他数据集需要对词汇表的个数进行设定,并且更新,类似于加上下面这样的代码

if len(sorted words ) > 10000:
  sorted words = sorted words [: 10000]
②通过得到的词汇表,将每个文件转换为数字化文件,每个单词的id为所在的词汇表行数

VOCAB_transfrom_sequence.py

# -*- coding:UTF-8 -*-

"""
@Author:Che_Hongshu
@Modify:2018.12.16
"""
import codecs


RAW_DATA = "simple-examples/data/ptb.valid.txt"  # 输入的ptb训练数据

VOCAB = "ptb.vocab" #输出词汇表文件

OUTPUT_DATA = 'ptb.valid' #将单词替换成单词编号后的输出文件

with codecs.open(VOCAB, 'r', 'utf-8') as f_vocab:  #打开文件进入读操作
    vocab = [w.strip() for w in f_vocab.readlines()]  # 先把所有词转换成list
    # 把每个词和所在行数对应起来并且zip打包成(“词”,行数)格式转换成dict格式
word_to_id = {k: v for (k, v) in zip(vocab, range(len(vocab)))}

# 返回id 如果在词汇表文件中则返回对应的id即可,如果没有则返回'<unk>'
def get_id(word):
    return word_to_id[word] if word in word_to_id else word_to_id['<unk>']

# 打开文件
fin = codecs.open(RAW_DATA, 'r', 'utf-8')
fout = codecs.open(OUTPUT_DATA, 'w', 'utf-8')

for line in fin:
    words = line.strip().split() + ["<eos>"] #每一行的单词变成sring list格式,每一句话后面加上一个结束符号
    out_line = ' '.join([str(get_id(w)) for w in words]) + '\n' #这一行中的每个单词取出对应的id之后用空格相连接 
    fout.write(out_line)

# 关闭文件
fin.close()
fout.close()

更改 RAW_DATA 和 OUTPUT_DATA
得到
在这里插入图片描述

以ptb.test:
在这里插入图片描述

3.

①自然语言处理建模示意图

在这里插入图片描述数据经过词向量层(embedding),经过rnn,最后经过softmax得到每个result的概率。

②batching方法

在这里插入图片描述
就是循环神经网络不能像卷积神经网络那样直接图片reshape,统一规格,还有一点就是需要把序列信息保留下来,解决方法就是把每个句子序列拉成一个很长的序列直接平均截取几份,之后统一对这几份一起序列截取采样,就是一个batch,从图上解释我自己理解就是(我个人感觉这里不是很好理解), 根据下面的代码来看就是bachsize就是竖列的大小,一个bachsize的数据大小的宽度就为num_step,而分为多少份为num_batches

def make_batches(id_list, batch_size, num_step):
    # 计算总的 batch 数量。每个 batch 包含的单词数量是 batch_size*num_step
    num_batches = (len(id_list) - 1) // (batch_size*num_step)
    # 从头开始取正好num_batches*batch_size*num_step
    data = np.array(id_list[: num_batches*batch_size*num_step])
    # 将数据整理成一个维度为[ batch_size, num_batches*numstep ]
    data = np.reshape(data, [batch_size, num_batches*num_step])
    # 相当于在第二维数据上竖着截取一部分数据
    data_batches = np.split(data, num_batches, axis=1)
    # 因为相当于一个时刻去预测下一个时刻,所以进行相应的+1,相当于每个时刻的预测真值都在下一时刻。
    label = np.array(id_list[1:num_batches*batch_size*num_step +1])
    label = np.reshape(label, [batch_size, num_batches*num_step])
    label_batches = np.split(label, num_batches, axis=1)

    return list(zip(data_batches, label_batches))

4. 完整的训练程序

有详细的代码详细说明

# -*- coding:UTF-8 -*-
"""
@Author:Che_Hongshu
@Modify:2018.12.28
@CSDN:http://blog.csdn.net/qq_33431368

"""

import numpy as np
import tensorflow as tf

TRAIN_DATA = "ptb.train"  # 训练数据
EVAL_DATA = "ptb.valid"   # 验证数据
TEST_DATA = "ptb.test"    #  测试数据
HIDDEN_SIZE = 300         # 隐藏层

NUM_LAYERS = 2            # LSTM 层数
VOCAB_SIZE = 10000        # 词典规模(只要这么大的规模的特征词的数字表示)
TRAIN_BATCH_SIZE = 20     # 训练数据的batchsize
TRAIN_NUM_STEP = 35       # 训练数据截断长度

EVAL_BATCH_SIZE = 1       # 验证数据的batchsize
EVAL_NUM_STEP = 1        # 验证数据截断长度
NUM_EPOCH = 5            # 训练数据的轮数
LSTM_KEEP_PROB = 0.9      # LSTM节点不被dropout的概率
EMBEDDING_KEEP_PROB = 0.9  # 词向量不被dropout的概率
MAX_GRAB_NORM = 5         # 用于控制梯度膨胀大小的上限
SHARE_EMB_AND_SOFTMAX = True  # Softmax预词向量层之间共享参数

"""
function: class of LSTM
Parameters:
Returns:
CSDN:
    http://blog.csdn.net/qq_33431368
"""
class PTBModel(object):
    def __init__(self, is_training, batch_size, num_steps):
        # 记录使用的 batch 大小和截断长度
        self.batch_size = batch_size
        self.num_steps = num_steps

        # 定义每一步的输入和预期输出, 两者的维度都是[ batch_size ,num_steps ]
        self.input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
        self.targets = tf.placeholder(tf.int32, [batch_size, num_steps])

        # 定义使用 LSTM 结构为循环体结构且使用 dropout 的深层循环神经网络。
        dropout_keep_prob = LSTM_KEEP_PROB if is_training else 1.0  #训练时采用dropout

        #定义lstm
        lstm_cells = [

            #运用Dropout的LSTM,不同时刻不dropout,同一时刻dropout
            tf.nn.rnn_cell.DropoutWrapper(
                tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE),
                output_keep_prob=dropout_keep_prob
            )
            # NUM_LAYERS为层数,也就是LSTM为几层
            for _ in range(NUM_LAYERS)
        ]
        #创建多层深度lstm
        cell = tf.nn.rnn_cell.MultiRNNCell(lstm_cells)
        # 对创建的LSTM进行初始化
        #初始化最初的状态, 即全0的向量。这个量只在每个epoch初始化第一个batch才使用。
        self.initial_state = cell.zero_state(batch_size, tf.float32)

        # 定义单词的词向量矩阵。
        embedding = tf.get_variable("embedding", [VOCAB_SIZE, HIDDEN_SIZE])
        # 将输入单词转化为词向量
        inputs = tf.nn.embedding_lookup(embedding, self.input_data)
        #只在训练时进行dropout,测试和验证都不要dropout操作
        if is_training:
            inputs = tf.nn.dropout(inputs, EMBEDDING_KEEP_PROB)
        # 定义输出列表。在这里先将不同时刻 LSTM 结构的输出收集起来 , 再一起提供给softmax层
        outputs = []
        # lstm状态值
        state = self.initial_state
        with tf.variable_scope("RNN"):
            #numsteps为每次截断的序列长度
            for time_step in range(num_steps):
                #在第一个时刻声明LSTM使用的变量,在之后的时刻都需要复用之前定义好的变量
                if time_step > 0:
                    tf.get_variable_scope().reuse_variables()
                # 得到这个时刻lstm输出以及当前状态
                cell_output, state = cell(inputs[:, time_step, :], state)
                # 这个时间段的结构,因为一步一步来append,所以是相当于[[],[],[],[]]需要下面一步concat(,1)来使所有的结果在一个维度上
                outputs.append(cell_output)
        # 把输出进行调整维度(,HEDDEN_SIZE)
        output = tf.reshape(tf.concat(outputs, 1), [-1, HIDDEN_SIZE])
        # 是否共享参数(softmax层+embedding层)\

        #Softmax摆: 将RNN在每个位置上的输出转化为各个单词的logits,也就是最后得出的每个单词是最终预测结果的概率。
        if SHARE_EMB_AND_SOFTMAX:
            weight = tf.transpose(embedding)
        else:
            weight = tf.get_variable("weight", [HIDDEN_SIZE, VOCAB_SIZE])
        bias = tf.get_variable("bias", [VOCAB_SIZE])
        #最后经过全连接层输出的结果
        logits = tf.matmul(output, weight) + bias
        #交叉熵损失函数,算loss
        loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.reshape(self.targets, [-1]), logits=logits)
        #求出每个batch平均loss
        self.cost = tf.reduce_sum(loss)/batch_size
        #最终的state
        self.final_state = state

        if not is_training:
            return

        # 控制梯度大小,定义优化方法和训练步骤。
        trainable_variables = tf.trainable_variables()
        # 算出每个需要更新的值的梯度,并对其进行控制
        grads, _ = tf.clip_by_global_norm(tf.gradients(self.cost, trainable_variables), MAX_GRAB_NORM)
        # 利用梯度下降优化算法进行优化.学习率为1.0
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
        #相当于minimize的第二步,正常来讲所得到的list[grads,vars]由compute_gradients得到,返回的是执行对应变量的更新梯度操作的op
        self.train_op = optimizer.apply_gradients(zip(grads, trainable_variables))

"""
function: 使用给定的模型 model 在datasets 上运行 train op 并返回在全部数据上的 perplexity 值
Parameters:
    session-会话
    model-模型
    batches-批量值
    train_op-执行对应变量的更新梯度操作op
    output_log-
    step-训练步数
Returns:
     return step, np.exp(total_costs / iters)-步数和对应求出的perplexity
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def run_epoch(session, model, batches, train_op, output_log, step):
    # 计算平均 perplexity 的辅助变量
    total_costs = 0.0
    iters = 0
    #得到final_state
    state = session.run(model.initial_state)
    # 训练一个 epoch
    for x, y in batches:
        # 在当前batch 上运行 train op 并计算损失值, 交叉炳损失函数计算的就是下一个单词为给定单词的概率。
        cost, state, _ = session.run([model.cost, model.final_state, train_op],
                                     {model.input_data: x, model.targets: y, model.final_state: state})
    # 总的loss
    total_costs += cost
    # 总的截断长度
    iters += model.num_steps

    if output_log and step % 100 == 0:
        print "After %d steps, perplexity is %.3f" % (step, np.exp(total_costs/iters))
    #训练次数
    step += 1

    return step, np.exp(total_costs / iters)

"""
function: 计算神经网络的前向传播的结果
Parameters:
    file_path-文件路径(文件已经是前面处理好的id文件了)
Returns:
    idlist-对于输入数据产生对应的转换为int的list
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def read_data(file_path):
    with open(file_path, 'r') as fin: #打开文件
        id_string = " ".join([line.strip() for line in fin.readlines()]) #每一行读取,并用空格相连
    id_list = [int(w) for w in id_string.split()] #转换成id list
    return id_list

"""
function: 数据batching,产生最后输入数据格式
Parameters:
    id_list-文件的对应id文件,由read_data产生
    batch_size-batch的大小
    num_step-截断序列数据的长度
Returns:
    list(zip(data_batches, label_batches))-data,label的数据list
CSDN:
    http://blog.csdn.net/qq_33431368
"""
def make_batches(id_list, batch_size, num_step):
    # 计算总的 batch 数量。每个 batch 包含的单词数量是 batch_size*num_step
    num_batches = (len(id_list) - 1) // (batch_size*num_step)
    # 从头开始取正好num_batches*batch_size*num_step
    data = np.array(id_list[: num_batches*batch_size*num_step])
    # 将数据整理成一个维度为[ batch_size, num_batches*numstep ]
    data = np.reshape(data, [batch_size, num_batches*num_step])
    # 相当于在第二维数据上竖着截取一部分数据
    data_batches = np.split(data, num_batches, axis=1)
    # 因为相当于一个时刻去预测下一个时刻,所以进行相应的+1,相当于每个时刻的预测真值都在下一时刻。
    label = np.array(id_list[1:num_batches*batch_size*num_step +1])
    label = np.reshape(label, [batch_size, num_batches*num_step])
    label_batches = np.split(label, num_batches, axis=1)

    return list(zip(data_batches, label_batches))


def main():
    # 定义初始化函数。
    initializer = tf.random_uniform_initializer(-0.05, 0.05)
    #      initializer: default initializer for variables within this scope.
    # tf.variable_scope(,initializer=initializer)相当于在这个scope中都是这样的初始化变量情况
    # #定义训练用的循环神经网络模型。
    with tf.variable_scope("language_model", reuse=None, initializer=initializer):
        train_model = PTBModel(True, TRAIN_BATCH_SIZE, TRAIN_NUM_STEP)
    # 定义测试用的循环神经网络模型。它与 train model 共用参数 , 但是测试使用全部的参数,所以没有dropout 。
    with tf.variable_scope("language_model", reuse=True, initializer=initializer):
        eval_model = PTBModel(False, EVAL_BATCH_SIZE, EVAL_NUM_STEP)
    #train
    with tf.Session() as session:
        tf.global_variables_initializer().run()
        # 生成train,test,eval的batches
        train_batches = make_batches(
            read_data(TRAIN_DATA), TRAIN_BATCH_SIZE, TRAIN_NUM_STEP
        )
        eval_batches = make_batches(
            read_data(EVAL_DATA), EVAL_BATCH_SIZE, EVAL_NUM_STEP
        )
        test_batches = make_batches(
            read_data(TEST_DATA), EVAL_BATCH_SIZE, EVAL_NUM_STEP
        )
        step = 0
        #进行NUM_EPOCH次迭代
        for i in range(NUM_EPOCH):
            print "In iteration: %d" % (i+1)
            step, train_pplx = run_epoch(session, train_model, train_batches, train_model.train_op, True, step)

            print "Epoch : %d train Perplexity: %.3f" % (i + 1, train_pplx)

            _, eval_pplx = run_epoch(session, eval_model, eval_batches, tf.no_op(), False, 0)

            print "Epoch: %d Eval Perplexity: %.3f" % (i + 1, eval_pplx)

        _, test_pplx = run_epoch(session, eval_model, test_batches, tf.no_op(), False, 0)
        print "Test Preplex: %.3f" % test_pplx


if __name__ == '__main__':
    main()


model评价就是简单的交叉熵,描述现在的值和想要的值之间的距离差距
结果:
在这里插入图片描述

5. 需要好好理解的点

四、github

代码链接,希望给予star或者fork
https://github.com/chehongshu/DL-tenserflow/tree/master/RNN_LSTM_PTB

特此感谢网上的资源,如果哪里写的不对请指正,文章也会在我不断地加深理解中更改到更好!

Reference

A Critical Review of Recurrent Neural Networks for Sequence Learning

https://blog.csdn.net/xierhacker/article/details/73384760

https://blog.csdn.net/xierhacker/article/details/73480744

《TensorFlow: 实战Google深度学习框架》 值得一看

http://www.tensorfly.cn/home/

https://blog.csdn.net/songhk0209/article/details/71134698

http://nicodjimenez.github.io/2014/08/08/lstm.html

https://blog.csdn.net/hustqb/article/details/80260002

猜你喜欢

转载自blog.csdn.net/qq_33431368/article/details/85288590
今日推荐