利用RNN训练语言模型 - Tensorflow

半兽人 发表于: 2019-04-07   最后更新时间: 2019-04-07 23:11:32  
{{totalSubscript}} 订阅, 3,908 游览

下面来做一个实验,用RNN预测语言模型,并让它输出一句话,具体业务描述如下。

先让RNN学习一段文字,之后模型可以根据我们的输入再自动预测后面的文字。同时将模型预测出来的文字当成输入,再放到模型里,模型就会预测出下一个文字,这样循环下去,可以看到RNN能够输出一句话。

那么RNN是怎么样来学习这段文字呢?这里将整段文字都看成一个个的序列。在模型里预设值只关注连续的4个序列,这样在整段文字中,每次随意拿出4个连续的文字放到模型里进行训练,然后把第5个连续的值当成标签,与输出的预测值进行loss的计算,形成一个可训练的模型,通过优化器来迭代训练。

实例描述

通过让RNN网络对一段文字的训练学习来生成模型,最终可以使用机器生成的模型来表达自己的意思。下面看看具体实现过程。

1、准备样本

这个环节很简单,随便复制一段话放到txt里即可。在例子中使用的样本如下:

在尘世的纷扰中,只要心头悬挂着远方的灯光,我们就会坚持不懈地走,理想为我们灌注了精神的蕴藉。所以,生活再平凡、再普通、再琐碎,我们都要坚持一种信念,默守一种精神,为自己积淀站立的信心,前行的气力。

这是笔者随意下载的一段文字,把该段文字放到代码同级目录下,起名为wordstest.txt

1.定义基本工具函数

具体的基本工具函数与语音识别例子差不多,都是与文本处理相关的,首先引入头文件,然后定义相关函数,其中get_ch_lable函数从文件里获取文本,get_ch_lable_v函数将文本数组转换成向量。具体如下。

代码9-25 rnnwordtest

# -*- coding: utf-8 -*-
import numpy as np
import tensorflow as tf
from tensorflow.contrib import rnn
import random
import time
from collections import Counter

start_time = time.time()


def elapsed(sec):
    if sec < 60:
        return str(sec) + " sec"
    elif sec < (60 * 60):
        return str(sec / 60) + " min"
    else:
        return str(sec / (60 * 60)) + " hr"


# Target log path
tf.reset_default_graph()
training_file = 'wordstest.txt'


# 中文多文件
def readalltxt(txt_files):
    labels = []
    for txt_file in txt_files:
        target = get_ch_lable(txt_file)
        labels.append(target)
    return labels


# 中文字
def get_ch_lable(txt_file):
    labels = ""
    with open(txt_file, 'rb') as f:
        for label in f:
            labels = labels + label.decode('utf-8')
            # labels = labels + label.decode('gb2312')
    return labels


# 优先转文件里的字符到向量
def get_ch_lable_v(txt_file, word_num_map, txt_label=None):
    words_size = len(word_num_map)
    to_num = lambda word: word_num_map.get(word, words_size)
    if txt_file != None:
        txt_label = get_ch_lable(txt_file)

    labels_vector = list(map(to_num, txt_label))
    return labels_vector

2.样本预处理

样本预处理工作主要是读取整体样本,并存放到training_data里,获取全部的字表words,并生成样本向量wordlabel和与向量对应关系的word_num_map。具体代码如下。

代码 rnnwordtest(续)

training_data = get_ch_lable(training_file)
print("Loaded training data...")

counter = Counter(training_data)
words = sorted(counter)
words_size = len(words)
word_num_map = dict(zip(words, range(words_size)))

print('字表大小:', words_size)
wordlabel = get_ch_lable_v(training_file, word_num_map)

2、构建模型

本例中使用多层RNN模型,后面接入一个softmax分类,对下一个字属于哪个向量进行分类,这里认为一个字就是一类。整个例子步骤如下。

1.设置参数定义占位符

学习率为0.001,迭代10000次,每1000次输出一次中间状态。每次输入4个字,来预测第5个字。

网络模型使用了3层的LSTM RNN,第一层为256个cell,第二层和第三层都是512个cell。

代码 rnnwordtest(续)

# 定义参数
learning_rate = 0.001
training_iters = 10000
display_step = 1000
n_input = 4

n_hidden1 = 256
n_hidden2 = 512
n_hidden3 = 512
# 定义占位符
x = tf.placeholder("float", [None, n_input, 1])
wordy = tf.placeholder("float", [None, words_size])

代码中定义了两个占位符x和wordy,其中,x代表输入的4个连续文字,wordy则代表一个字,由于用的是字索引向量的one_hot编码,所以其大小为words_size,代表总共的字数。

2.定义网络结构

将x形状变换并按找时间序列裁分,然后放入3层LSTM网络,最终通过一个全连接生成words_size个节点,为后面的softmax做准备。具体代码如下。

代码 rnnwordtest(续)

x1 = tf.reshape(x, [-1, n_input])
x2 = tf.split(x1, n_input, 1)
# 2-layer LSTM,每层有 n_hidden 个units
rnn_cell = rnn.MultiRNNCell([rnn.LSTMCell(n_hidden1), rnn.LSTMCell(n_hidden2), rnn.LSTMCell(n_hidden3)])

# 通过RNN得到输出
outputs, states = rnn.static_rnn(rnn_cell, x2, dtype=tf.float32)

# 通过全连接输出指定维度
pred = tf.contrib.layers.fully_connected(outputs[-1], words_size, activation_fn=None)

3.定义优化器

优化器同样使用AdamOptimizer,loss使用的是softmax的交叉熵,正确率是统计one_hot中索引对应的位置相同的个数。

代码 rnnwordtest(续)

# 定义loss与优化器
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=wordy))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)

# 模型评估
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(wordy, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

4.训练模型

在训练过程中同样添加保存检查点功能。在session中每次随机取一个偏移量,然后取后面4个文字向量当作输入,第5个文字向量当作标签用来计算loss。

代码 rnnwordtest(续)

savedir = "log/rnnword/"
saver = tf.train.Saver(max_to_keep=1)  # 生成saver

# 启动session
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    step = 0
    offset = random.randint(0, n_input + 1)
    end_offset = n_input + 1
    acc_total = 0
    loss_total = 0

    kpt = tf.train.latest_checkpoint(savedir)
    print("kpt:", kpt)
    startepo = 0
    if kpt != None:
        saver.restore(session, kpt)
        ind = kpt.find("-")
        startepo = int(kpt[ind + 1:])
        print(startepo)
        step = startepo

    while step < training_iters:

        # 随机取一个位置偏移
        if offset > (len(training_data) - end_offset):
            offset = random.randint(0, n_input + 1)

        inwords = [[wordlabel[i]] for i in range(offset, offset + n_input)]  # 按照指定的位置偏移获得后4个文字向量,当作输入

        inwords = np.reshape(np.array(inwords), [-1, n_input, 1])

        out_onehot = np.zeros([words_size], dtype=float)
        out_onehot[wordlabel[offset + n_input]] = 1.0
        out_onehot = np.reshape(out_onehot, [1, -1])  # 所有的字都变成onehot

        _, acc, lossval, onehot_pred = session.run([optimizer, accuracy, loss, pred],
                                                   feed_dict={x: inwords, wordy: out_onehot})
        loss_total += lossval
        acc_total += acc
        if (step + 1) % display_step == 0:
            print("Iter= " + str(step + 1) + ", Average Loss= " + \
                  "{:.6f}".format(loss_total / display_step) + ", AverageAccuracy= " + \
                  "{:.2f}%".format(100 * acc_total / display_step))
            acc_total = 0
            loss_total = 0
            in2 = [words[wordlabel[i]] for i in range(offset, offset + n_input)]
            out2 = words[wordlabel[offset + n_input]]
            out_pred = words[int(tf.argmax(onehot_pred, 1).eval())]
            print("%s - [%s] vs [%s]" % (in2, out2, out_pred))
            saver.save(session, savedir + "rnnwordtest.cpkt", global_step=step)
        step += 1
        offset += (n_input + 1)  # 调整下一次迭代使用的偏移量

    print("Finished!")
    saver.save(session, savedir + "rnnwordtest.cpkt", global_step=step)
    print("Elapsed time: ", elapsed(time.time() - start_time))

由于检查点文件是建立在log/rnnword/目录下的,所以在运行程序之前需要先在代码文件的当前目录下依次建立log/rnnword/文件夹(有兴趣的读者可以改成自动创建)。运行代码,训练模型得到如下输出:

……
Type is unsupported, or the types of the items don't match field type in
CollectionDef.
'dict' object has no attribute 'name'
Iter= 9000, Average Loss=0.585445, Average Accuracy=79.10%
['','','',''] - [的]vs[了]
WARNING:tensorflow:Error encountered when serializing LAYER_NAME_UIDS.
Type is unsupported, or the types of the items don't match field type in
CollectionDef.
'dict' object has no attribute 'name'
Iter= 10000, Average Loss= 0.409709, Average Accuracy= 85.60%
['平','凡','、','再'] - [普]vs[普]
WARNING:tensorflow:Error encountered when serializing LAYER_NAME_UIDS.
Type is unsupported, or the types of the items don't match field type in
CollectionDef.
'dict' object has no attribute 'name'
******ebook converter DEMO Watermarks*******
Finished!
WARNING:tensorflow:Error encountered when serializing LAYER_NAME_UIDS.
Type is unsupported, or the types of the items don't match field type in
CollectionDef.
'dict' object has no attribute 'name'
Elapsed time: 1.3609554409980773 min

迭代10000次的正确率达到了85%。达到了模型基本可用的状态。当然这只是个例子,读者可以尝试在模型中添加全连接及更多节点的LSTM或是更深层的LSTM来优化识别率,并且当学习的字数变多时,还会有更强大的拟合功能。

5.运行模型生成句子

启用一个循环,等待输入文字,当收到输入的文本后,通过eval计算onehot_pred节点,并进行文字的转义,得到预测文字。接下来将预测文字再循环输入模型中,预测下一个文字。代码中设定循环32次,输出32个文字。

代码9-25 rnnwordtest(续)

    while True:
        prompt = "请输入%s个字: " % n_input
        sentence = input(prompt)
        inputword = sentence.strip()

        if len(inputword) != n_input:
            print("您输入的字符长度为:", len(inputword), "请输入4个字")
            continue
        try:
            inputword = get_ch_lable_v(None, word_num_map, inputword)

            for i in range(32):
                keys = np.reshape(np.array(inputword), [-1, n_input, 1])
                onehot_pred = session.run(pred, feed_dict={x: keys})
                onehot_pred_index = int(tf.argmax(onehot_pred, 1).eval())
                sentence = "%s%s" % (sentence, words[onehot_pred_index])
                inputword = inputword[1:]
                inputword.append(onehot_pred_index)
            print(sentence)
        except:
            print("该字我还没学会")

运行代码,输出如下:

请输入4个字: 生活平凡
生活平凡,要坚持一种信念,默守一种精神,为自己积淀站立的信心,默守一种精

在本例中,输入了“生活平凡”4个字,可以看到神经网络自动按照这个开头开始往下输出句子,看起来语句还算通顺。

更新于 2019-04-07

查看TensorFlow更多相关的文章或提一个关于TensorFlow的问题,也可以与我们一起分享文章