tensorflow实现lstm的demo

困惑度是什么

通常在永ngram语言模型的时候,通常用困惑度来描述这个query的通顺程序,ngram是一个统计概率模型。
但是ngram模型有一个缺点,就是通常我们使用的是2-gram或者3-gram,那么对于大于3个字或词以上的信息就不能捕获到了,但是循环神经网络可以将任意长度的信息都捕获到,这也是循环神经网络预测句子的通顺程度的优势。

具体实现

数据准备

这儿采用的是PTB数据集,下载地址在数据
只需要是使用

ptb.train.txt
ptb.text.txt
ptb.valid.txt

这些 数据是什么呢?他们里面是一些英文的句子,相邻的单词是用空格隔开的,对于不常出现的词用<unk>表示,句子结尾的换行符用<eos>来表示,据统计,train.txt里面的句子一个有10000个单词(加上这两个特殊的字符)。

vocab

为了将这个句子中的单词送入lstm中,那么需要将这些单词id化,也就是将这10000个单词每一个都赋给一个具体的数字来表示它们。在id化之前我们需要给每一个单词都赋予一个数字id。这个py文件叫做generate_vocab.py
具体的代码

# _*_ encoding=utf8 _*_

import codecs
import collections
from operator import itemgetter
import config

counter = collections.Counter()
with codecs.open(config.train_data, 'r', encoding='utf-8') as fr:
    for line in fr:
        line = line.strip().split()
        for word in line:
            counter[word] += 1

sorted_word_to_counter = sorted(counter.items(), key=itemgetter(1), reverse=True)
sorted_words = [x[0] for x in sorted_word_to_counter]
sorted_words.insert(0, "<eos>")

with codecs.open(config.VOCAB_OPTPUT, 'w', encoding='utf-8') as fw:
    for word in sorted_words:
        fw.write(word + "\n")

print("词汇表生成完成")

同时,我们应该准备分离的原则,将配置信息单独提出来,叫config.py

# _*_ encoding=utf8 _*_



# 训练文件
train_data = "data/ptb/ptb.train.txt"
test_data = "data/ptb/ptb.test.txt"
valid_data = "data/ptb/ptb.valid.txt"

# 词汇表的位置
VOCAB_OPTPUT = "data/output/ptb.vocab"

# 训练文件id化
train2id_data = "data/output/ptb.train"
test2id_data = "data/output/ptb.test"
valid2id_data = "data/output/ptb.valid"
# 根据训练文件生成id词典
word_dict = "data/output/word_dict"


# 参数
train_batch_size = 2
train_step_num = 35

现在就可以得到train.txt里面所有的单词的信息,具体信息 如下(部分展示)

<eos>
the
<unk>
N
of
to
a
in
and
's
that

Word2id

接下来就继续上一步没有完成的word2id步骤,需要将训练,测试,验证三个文件都id化。该文件名为word2id.py

# _*_ encoding=utf8 _*_

"""
处理好每个单词的统计数据后,再将训练文件没测试文件根据单词表id化
"""

import codecs
import config



with codecs.open(config.VOCAB_OPTPUT, 'r', 'utf-8') as fr:
    vocab = {word.strip() for word in fr.readlines()}

word2id = {k:v for (k,v) in zip(vocab,range(len(vocab)))}

# 低频次用unk替换
def get_id(word):
    return word2id[word] if word in word2id else word2id['<unk>']

sort_word2id = sorted(word2id.items(),key = lambda x:x[1],reverse = True)
with codecs.open(config.word_dict, "w", encoding="utf-8") as fw:
    for (key,value) in sort_word2id:
        fw.write(key + "\t" + str(value) + "\n")

with codecs.open(config.train2id_data, "w", encoding="utf-8") as fw:
    with codecs.open(config.train_data, "r", encoding="utf-8") as fr:
        for index,line in enumerate(fr):
            words = line.strip().split() + ["<eos>"]
            out_line = ' '.join([str(get_id(word)) for word in words])
            fw.write(out_line + "\n")

print("词典生成完成,训练文件id化完成")

with codecs.open(config.test2id_data, "w", encoding="utf-8") as fw:
    with codecs.open(config.test_data, "r", encoding="utf-8") as fr:
        for index,line in enumerate(fr):
            words = line.strip().split() + ["<eos>"]
            out_line = ' '.join([str(get_id(word)) for word in words])
            fw.write(out_line + "\n")

print("词典生成完成,测试文件id化完成")

with codecs.open(config.valid2id_data, "w", encoding="utf-8") as fw:
    with codecs.open(config.valid_data, "r", encoding="utf-8") as fr:
        for index,line in enumerate(fr):
            words = line.strip().split() + ["<eos>"]
            out_line = ' '.join([str(get_id(word)) for word in words])
            fw.write(out_line + "\n")

print("词典生成完成,验证文件id化完成")

现在我们可以得到新的三个文件,这三个文件就是最开始的三个数据文件把单词转成数字id的文件,比如一句话“i love u”,经过id化之后,现在的信息就可能变成了12 2 30。

batch_data

在实际训练中,我们是会将数据一个batch一个batch塞进去的,但是每个句子的长度不一样,这儿我们是需要定长输入的,所以一般做法是补padding,也就是将所有的句子补padding,直到和最长的句子一样长。但是在这里,我们是预测困惑度,说白了,也就是根据前面的单词预测后面单词出现的概率,所以我们不能丢掉上下文信息,所以这儿给出了另外一个补padding的方法。
具体做法:将整个文档的句子一次连接起来,当做一个句子来训练,这样就不会丢掉信息了,但是这样的话merge后的句子太长了,并不太现实,所以这儿将merge句子切割成登场的子序列,这样,循环神经网络在处理完一个子序列之后,它的隐藏状态将会复制到下一个序列作为初始值,这样在前向过程中,就相当于一次性读入这整个merge句子,但是反向传播的时候,梯度只会在每一个子序列内部进行传播。
具体实现代码,这人主要干了生成batch和padding的功能。该文件名get_batch.py

# _*_ encoding=utf8 _*_

import numpy as np
import codecs
import config

train_batch_size = config.train_batch_size
train_step_num = config.train_step_num


def read_data(file_path):
    with codecs.open(file_path, 'r', encoding='utf-8') as fr:
        id_string = ' '.join([line.strip() for line in fr.readlines()])
    id_list = [int(w) for w in id_string.split()]
    return id_list


def make_batches(id_list, batch_size, num_step):
    # 计算总的batch_size数,每一个batch包含的单词数量是batch_size*num_step
    num_batch = (len(id_list) - 1) // (batch_size * num_step)

    # 把数据整理成[batch_size, num_batches*num_step]
    data = np.array(id_list[:num_batch * batch_size * num_step])
    data = np.reshape(data, [batch_size, num_batch * num_step])
    # 沿第二个维度将数据分成num_batch个batch,存入一个数组
    data_batches = np.split(data, num_batch, axis=1)

    # 每个位置右移一位,再构造RNN输出,从前面的信息得到下一个单词的信息
    label = np.array(id_list[1:num_batch * batch_size * num_step + 1])
    label = np.reshape(label, [batch_size, num_batch * num_step])
    label_batch = np.split(label, num_batch, axis=1)

    # print(data_batches[0])
    # 返回RNN的输入和输出
    return list(zip(data_batches, label_batch))

# make_batches(read_data(config.train2id_data),train_batch_size,train_step_num)

对于上面这个代码,有几点需要注意的地方,一个是组装batch的地方,一个是生成输入数据和生成输出数据。

模型的配置

在训练之前,定义好训练的参数信息

# _*_ encoding=utf8 _*_

import numpy as np
import tensorflow as tf
import os
from get_batch import read_data,make_batches
import config


tf.app.flags.DEFINE_string('TRAIN_DATA', config.train2id_data ,"训练数据")
tf.app.flags.DEFINE_string('EVAL_DATA', config.test2id_data ,"测试数据")
tf.app.flags.DEFINE_string('TEST_DATA', config.valid2id_data ,"验证数据")

tf.app.flags.DEFINE_integer('HIDDEN_SIZE', 300 ,"影藏层数量")
tf.app.flags.DEFINE_integer('NUM_LAYERS', 2 ,"LSTM层数")
tf.app.flags.DEFINE_integer('VOCAB_SZIE', 10000 ,"词典大小")
tf.app.flags.DEFINE_integer('TRAIN_BATCH_SIZE', 20, 'batch_size的大小')
tf.app.flags.DEFINE_integer('TRAIN_NUM_STEP', 35, '训练数据的截断长度')
tf.app.flags.DEFINE_integer('EVAL_BATCH_SIZE', 1, '测试数据的batch大小')
tf.app.flags.DEFINE_integer('EVAL_NUM_STEP', 1, '测试数据的截断长度')
tf.app.flags.DEFINE_integer('NUM_EPOCH', 5, '训练的轮数')
tf.app.flags.DEFINE_float('LSTM_KEEP_PROB', 0.9, 'LSTM不被dropout的概率')
tf.app.flags.DEFINE_float('EMBEDDING_KEEP_PROB', 0.9, '词向量不被dropout的概率')
tf.app.flags.DEFINE_integer('MAX_GRAD_NORM', 5 ,"用户控制梯度膨胀的梯度大小的上限")
tf.app.flags.DEFINE_boolean('SHARE_EMB_AND_SOFTMAX', True ,"在softmax和词向量层共享参数")


FLAGS = tf.app.flags.FLAGS

模型:model.py

# _*_ encoding=utf8 _*_

import numpy as np
import tensorflow as tf
import os
from get_batch import read_data,make_batches
from model_config import FLAGS


class PTBModel(object):
    def __init__(self, is_training, batch_size, num_steps):
        self.batch_size = batch_size # 每一个batch有多少个query
        self.num_steps = num_steps # 截断长度
        # 这人是很好理解的,就是通过一个句子预测该句子是不是应该出现,加上一个batch,就构成了输入和输出。
        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 = FLAGS.LSTM_KEEP_PROB if is_training else 1.0

        # 定义单个基本的LSTM单元。同时指明隐藏层的数量hidden_size,声明lstm的时候只需要什么隐藏层的数量
        rnn_cell = tf.nn.rnn_cell.BasicLSTMCell(FLAGS.HIDDEN_SIZE)

        # 当state_is_tuple=True的时候,state是元组形式,state=(c,h)。如果是False,那么state是一个由c和h拼接起来的张量,
        # state=tf.concat(1,[c,h])。在运行时,返回2值,一个是h,还有一个state
        # rnn_cell = tf.nn.rnn_cell.BasicLSTMCell(FLAGS.HIDDEN_SIZE,state_is_tuple=True)

        # dropout,就是指网络中每个单元在每次有数据流入时以一定的概率(keep prob)正常工作,否则输出0值。这是一张正则化思想,可以有效防止过拟合,可以理解同一个t时刻,多层cell之间传递信息的时候进行dropout
        rnn_cell = tf.nn.rnn_cell.DropoutWrapper(rnn_cell, output_keep_prob = dropout_keep_prob)
		
		# 这儿定义的是两层的lstm,每层都是300个影藏单元,将两层的lstm叠起来了
        lstm_cells = [
            rnn_cell for _ in range(FLAGS.NUM_LAYERS)
        ]
        # 构建多层的循环神经网络
        cell = tf.nn.rnn_cell.MultiRNNCell(lstm_cells)

        # 将LSTM的状态初始化全为0 的数组,zero_state这个函数生成全零的初始状态,
        # initial_state包含了两个张量的LSTMStateTuple类,其中.c和.h分别是c状态和h状态
        # 每次使用一个batch大小的训练样本
        # 初始化的c 和 h的状态
        # 定义好的cell会依次接收num_steps个输入然后产生最后的state
        self.initial_state = cell.zero_state(batch_size, tf.float32)

        # 定义单词的词向量矩阵 300维 [vocab_size, embedding_size]
        # word embedding步骤,降维,增加word之间的联系
        embedding = tf.get_variable("embedding", [FLAGS.VOCAB_SZIE, FLAGS.HIDDEN_SIZE])
        # 将单词转化为词向量
        inputs = tf.nn.embedding_lookup(embedding, self.input_data)

        if is_training:
            inputs = tf.nn.dropout(inputs, FLAGS.EMBEDDING_KEEP_PROB)

        outputs = []
        # 初始的c h状态
        state = self.initial_state
        # LSTM循环
        # 安装文本的顺序向cell输入
        # inputs数据结构
        with tf.variable_scope("RNN"):
            for time_step in range(num_steps):
            	# 第二次循环开始,会复用tf.get_variable_scope().reuse_variables()设置的变量
                if time_step > 0: tf.get_variable_scope().reuse_variables()
                # 每次循环,都输入input和state,返回output和更新后的state
                cell_output, state = cell(inputs[:, time_step, :], state) # 
                # output: shape[num_steps][batch,hidden_size]
                outputs.append(cell_output)

        # axis = 0 代表在第0个维度拼接
        # axis = 1  代表在第1个维度拼接
        # 把之前outputs展开,成[batch, hidden_size*num_steps],
        # 然后 reshape, 成[batch*numsteps, hidden_size]
        output = tf.reshape(tf.concat(outputs, 1), [-1, FLAGS.HIDDEN_SIZE])

        # 获取embedding的权重 [HIDDEN_SIZE, VOCAB_SZIE]
        if FLAGS.SHARE_EMB_AND_SOFTMAX:
            weight = tf.transpose(embedding)
        else:
            weight = tf.get_variable("weight", [FLAGS.HIDDEN_SIZE, FLAGS.VOCAB_SZIE])

        bias = tf.get_variable("bias", [FLAGS.VOCAB_SZIE])
        # [batch*numsteps,VOCAB_SZIE]
        logits = tf.matmul(output, weight) + bias

        loss  = tf.nn.sparse_softmax_cross_entropy_with_logits(
            labels=tf.reshape(self.targets, [-1]),
            logits=logits
        )
        self.cost = tf.reduce_sum(loss) / batch_size
        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),FLAGS.MAX_GRAD_NORM)

        optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
        self.train_op = optimizer.apply_gradients(zip(grads, trainable_variables))

训练:train.py

# _*_ encoding=utf8 _*_

import numpy as np
import tensorflow as tf
import os
from get_batch import read_data,make_batches
from model import PTBModel
from model_config import FLAGS

# os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def run_epoch(session, model, batches, train_op, output_log, step):
    total_costs = 0.0
    iters = 0
    state = session.run(model.initial_state)
    for x,y in batches:
        cost,state,_ = session.run(
            [model.cost,model.final_state,train_op],
            feed_dict =  {model.input_data:x, model.targets:y,
                          model.initial_state:state}
        )
        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)


def main():
    initialzer = tf.random_uniform_initializer(-0.05,0.05)

    with tf.variable_scope("language_model", reuse=None,initializer=initialzer):
        train_model = PTBModel(True, FLAGS.TRAIN_BATCH_SIZE, FLAGS.TRAIN_NUM_STEP)

    with tf.variable_scope("language_model", reuse=True,initializer=initialzer):
        eval_model = PTBModel(False, FLAGS.EVAL_BATCH_SIZE, FLAGS.EVAL_NUM_STEP)

    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        train_batches = make_batches(
            read_data(FLAGS.TRAIN_DATA), FLAGS.TRAIN_BATCH_SIZE, FLAGS.TRAIN_NUM_STEP
        )

        eval_batches = make_batches(
            read_data(FLAGS.EVAL_DATA), FLAGS.EVAL_BATCH_SIZE, FLAGS.EVAL_NUM_STEP
        )

        test_batches = make_batches(
            read_data(FLAGS.TEST_DATA), FLAGS.EVAL_BATCH_SIZE, FLAGS.EVAL_NUM_STEP
        )

        step = 0
        for i in range(FLAGS.NUM_EPOCH):
            print("in iteration :%d " % (i+1))
            step,train_pplx  = run_epoch(sess, train_model, train_batches,
                                         train_model.train_op,True, step)
            print("Epoch: %d Train perplexity:%.3f" % (i+1,train_pplx))

            step, eval_pplx = run_epoch(sess, eval_model, eval_batches,
                                         tf.no_op(), False, 0)
            print("Epoch: %d Eval perplexity:%.3f" % (i + 1, eval_pplx))

        step, test_pplx = run_epoch(sess, eval_model, test_batches,
                                    tf.no_op(), False, 0)
        print("Test perplexity:%.3f" % test_pplx)

if __name__ == '__main__':
    main()

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值