前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习笔记CB014:TensorFlow seq2seq模型步步进阶

学习笔记CB014:TensorFlow seq2seq模型步步进阶

原创
作者头像
利炳根
发布2018-05-05 23:11:28
1K1
发布2018-05-05 23:11:28
举报
文章被收录于专栏:利炳根的专栏利炳根的专栏

神经网络。《Make Your Own Neural Network》,用非常通俗易懂描述讲解人工神经网络原理用代码实现,试验效果非常好。

循环神经网络和LSTM。Christopher Olah http://colah.github.io/posts/2015-08-Understanding-LSTMs/

seq2seq模型基于循环神经网络序列到序列模型,语言翻译、自动问答等序列到序列场景,都可用seq2seq模型,用seq2seq实现聊天机器人的原理 http://suriyadeepan.github.io/2016-06-28-easy-seq2seq/

attention模型(注意力模型)是解决seq2seq解码器只接受编码器最后一个输出远离之前输出导致信息丢失的问题。一个回答一般基于问题中关键位置信息,注意力集中地方, http://www.wildml.com/2016/01/attention-and-memory-in-deep-learning-and-nlp/

tensorflow seq2seq制作聊天机器人。tensorflow提关键接口: https://www.tensorflow.org/api_docs/python/tf/contrib/legacy_seq2seq/embedding_attention_seq2seq

代码语言:txt
复制
embedding_attention_seq2seq(
代码语言:txt
复制
    encoder_inputs,
代码语言:txt
复制
    decoder_inputs,
代码语言:txt
复制
    cell,
代码语言:txt
复制
    num_encoder_symbols,
代码语言:txt
复制
    num_decoder_symbols,
代码语言:txt
复制
    embedding_size,
代码语言:txt
复制
    num_heads=1,
代码语言:txt
复制
    output_projection=None,
代码语言:txt
复制
    feed_previous=False,
代码语言:txt
复制
    dtype=None,
代码语言:txt
复制
    scope=None,
代码语言:txt
复制
    initial_state_attention=False
代码语言:txt
复制
)

参数encoder_inputs是list,list每一项是1D Tensor,Tensor shape是batch_size,Tensor每一项是整数,类似:

代码语言:txt
复制
[array([0, 0, 0, 0], dtype=int32), 
代码语言:txt
复制
array([0, 0, 0, 0], dtype=int32), 
代码语言:txt
复制
array([8, 3, 5, 3], dtype=int32), 
代码语言:txt
复制
array([7, 8, 2, 1], dtype=int32), 
代码语言:txt
复制
array([6, 2, 10, 9], dtype=int32)]

5个array,表示一句话长度是5个词。每个array有4个数,表示batch是4,一共4个样本。第一个样本是[0,0,8,7,6],第二个样本是[0,0,3,8,2],数字区分不同词id,一般通过统计得出,一个id表示一个词。

参数decoder_inputs和encoder_inputs一样结构。

参数cell是tf.nn.rnn_cell.RNNCell类型循环神经网络单元,可用tf.contrib.rnn.BasicLSTMCell、tf.contrib.rnn.GRUCell。

参数num_encoder_symbols是整数,表示encoder_inputs整数词id数目。

num_decoder_symbols表示decoder_inputs中整数词id数目。

embedding_size表示内部word embedding转成几维向量,需要和RNNCell size大小相等。

num_heads表示attention_states抽头数量。

output_projection是(W, B)结构tuple,W是shape output_size x num_decoder_symbols weight矩阵,B是shape num_decoder_symbols 偏置向量,每个RNNCell输出经过WX+B映射成num_decoder_symbols维向量,向量值表示任意一个decoder_symbol可能性,softmax。

feed_previous表示decoder_inputs是直接提供训练数据输入,还是用前一个RNNCell输出映射,如果feed_previous为True,用前一个RNNCell输出,经过WX+B映射。

dtype是RNN状态数据类型,默认是tf.float32。

scope是子图命名,默认是“embedding_attention_seq2seq”。

initial_state_attention表示是否初始化attentions,默认为否,表示全初始化为0。返回值是(outputs, state)结构tuple,outputs是长度为句子长度(词数,与encoder_inputs list长度一样)list,list每一项是一个2D tf.float32类型 Tensor,第一维度是样本数,比如4个样本有四组Tensor,每个Tensor长度是embedding_size。outputs描述548个浮点数,5是句子长度,4是样本数,8是词向量维数。

返回state,num_layers个LSTMStateTuple组成大tuple,num_layers是初始化cell参数,表示神经网络单元有几层,一个由3层LSTM神经元组成encoder-decoder多层循环神经网络。encoder_inputs输入encoder第一层LSTM神经元,神经元output传给第二层LSTM神经元,第二层output再传给第三层,encoder第一层输出state传给decoder第一层LSTM神经元,依次类推。

LSTMStateTuple结构,由两个Tensor组成tuple,第一个tensor命名为c,由4个8维向量组成(4是batch, 8是state_size词向量维度), 第二个tensor命名为h,同样由4个8维向量组成。

c是传给下一个时序存储数据,h是隐藏的输出。

tensorflow代码实现:

代码语言:txt
复制
concat = _linear([inputs, h], 4 * self._num_units, True)
代码语言:txt
复制
i, j, f, o = array_ops.split(value=concat, num_or_size_splits=4, axis=1)
代码语言:txt
复制
new_c = (c * sigmoid(f + self._forget_bias) + sigmoid(i) * self._activation(j))
代码语言:txt
复制
new_h = self._activation(new_c) * sigmoid(o)

直接用embedding_attention_seq2seq训练,返回state一般用不到。

构造输入参数训练一个seq2seq模型。以1、3、5、7、9……奇数序列为例构造样本,比如两个样本是[1,3,5,7,9,11]和[3,5,7,9,11,13]:

代码语言:txt
复制
train_set = [[[1, 3, 5], [7, 9, 11]], [[3, 5, 7], [9, 11, 13]]]

满足不同长度序列,训练序列比样本序列长度要长,比设置5

代码语言:txt
复制
input_seq_len = 5
代码语言:txt
复制
output_seq_len = 5

样本长度小于训练序列长度,用0填充

代码语言:txt
复制
PAD_ID = 0

第一个样本encoder_input:

代码语言:txt
复制
encoder_input_0 = [PAD_ID] * (input_seq_len - len(train_set[0][0])) + train_set[0][0]

第二个样本encoder_input:

代码语言:txt
复制
encoder_input_1 = [PAD_ID] * (input_seq_len - len(train_set[1][0])) + train_set[1][0]

decoder_input用GO_ID作起始,再输入样本序列,最后用PAD_ID填充。

代码语言:txt
复制
GO_ID = 1
代码语言:txt
复制
decoder_input_0 = [GO_ID] + train_set[0][1] 
代码语言:txt
复制
    + [PAD_ID] * (output_seq_len - len(train_set[0][1]) - 1)
代码语言:txt
复制
decoder_input_1 = [GO_ID] + train_set[1][1] 
代码语言:txt
复制
    + [PAD_ID] * (output_seq_len - len(train_set[1][1]) - 1)

把输入转成embedding_attention_seq2seq输入参数encoder_inputs和decoder_inputs格式:

代码语言:txt
复制
encoder_inputs = []
代码语言:txt
复制
decoder_inputs = []
代码语言:txt
复制
for length_idx in xrange(input_seq_len):
代码语言:txt
复制
    encoder_inputs.append(np.array([encoder_input_0[length_idx], 
代码语言:txt
复制
                          encoder_input_1[length_idx]], dtype=np.int32))
代码语言:txt
复制
for length_idx in xrange(output_seq_len):
代码语言:txt
复制
    decoder_inputs.append(np.array([decoder_input_0[length_idx], 
代码语言:txt
复制
                          decoder_input_1[length_idx]], dtype=np.int32))

独立函数:

代码语言:txt
复制
# coding:utf-8
代码语言:txt
复制
import numpy as np
代码语言:txt
复制
# 输入序列长度
代码语言:txt
复制
input_seq_len = 5
代码语言:txt
复制
# 输出序列长度
代码语言:txt
复制
output_seq_len = 5
代码语言:txt
复制
# 空值填充0
代码语言:txt
复制
PAD_ID = 0
代码语言:txt
复制
# 输出序列起始标记
代码语言:txt
复制
GO_ID = 1
代码语言:txt
复制
def get_samples():
代码语言:txt
复制
    """构造样本数据
代码语言:txt
复制
    :return:
代码语言:txt
复制
        encoder_inputs: [array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([1, 3], dtype=int32),
代码语言:txt
复制
                         array([3, 5], dtype=int32), 
代码语言:txt
复制
                         array([5, 7], dtype=int32)]
代码语言:txt
复制
        decoder_inputs: [array([1, 1], dtype=int32), 
代码语言:txt
复制
                         array([7, 9], dtype=int32), 
代码语言:txt
复制
                         array([ 9, 11], dtype=int32),
代码语言:txt
复制
                         array([11, 13], dtype=int32), 
代码语言:txt
复制
                         array([0, 0], dtype=int32)]
代码语言:txt
复制
    """
代码语言:txt
复制
    train_set = [[[1, 3, 5], [7, 9, 11]], [[3, 5, 7], [9, 11, 13]]]
代码语言:txt
复制
    encoder_input_0 = [PAD_ID] * (input_seq_len - len(train_set[0][0])) 
代码语言:txt
复制
                      + train_set[0][0]
代码语言:txt
复制
    encoder_input_1 = [PAD_ID] * (input_seq_len - len(train_set[1][0])) 
代码语言:txt
复制
                      + train_set[1][0]
代码语言:txt
复制
    decoder_input_0 = [GO_ID] + train_set[0][1] 
代码语言:txt
复制
                      + [PAD_ID] * (output_seq_len - len(train_set[0][1]) - 1)
代码语言:txt
复制
    decoder_input_1 = [GO_ID] + train_set[1][1] 
代码语言:txt
复制
                      + [PAD_ID] * (output_seq_len - len(train_set[1][1]) - 1)
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    for length_idx in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(np.array([encoder_input_0[length_idx], 
代码语言:txt
复制
                              encoder_input_1[length_idx]], dtype=np.int32))
代码语言:txt
复制
    for length_idx in xrange(output_seq_len):
代码语言:txt
复制
        decoder_inputs.append(np.array([decoder_input_0[length_idx], 
代码语言:txt
复制
                              decoder_input_1[length_idx]], dtype=np.int32))
代码语言:txt
复制
    return encoder_inputs, decoder_inputs

构造模型,tensorflow运行过程是先构造图,再塞数据计算,构建模型过程实际上是构建一张图。

首先创建encoder_inputs和decoder_inputs的placeholder(占位符):

代码语言:txt
复制
import tensorflow as tf
代码语言:txt
复制
encoder_inputs = []
代码语言:txt
复制
decoder_inputs = []
代码语言:txt
复制
for i in xrange(input_seq_len):
代码语言:txt
复制
    encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="encoder{0}".format(i)))
代码语言:txt
复制
for i in xrange(output_seq_len):
代码语言:txt
复制
    decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="decoder{0}".format(i)))

创建一个记忆单元数目为size=8的LSTM神经元结构:

代码语言:txt
复制
size = 8
代码语言:txt
复制
cell = tf.contrib.rnn.BasicLSTMCell(size)

训练奇数序列最大数值是输入最大10输出最大16

代码语言:txt
复制
num_encoder_symbols = 10
代码语言:txt
复制
num_decoder_symbols = 16

把参数传入embedding_attention_seq2seq获取output

代码语言:txt
复制
from tensorflow.contrib.legacy_seq2seq.python.ops import seq2seq
代码语言:txt
复制
outputs, _ = seq2seq.embedding_attention_seq2seq(
代码语言:txt
复制
                    encoder_inputs,
代码语言:txt
复制
                    decoder_inputs[:output_seq_len],
代码语言:txt
复制
                    cell,
代码语言:txt
复制
                    cell,
代码语言:txt
复制
                    num_encoder_symbols=num_encoder_symbols,
代码语言:txt
复制
                    num_decoder_symbols=num_decoder_symbols,
代码语言:txt
复制
                    embedding_size=size,
代码语言:txt
复制
                    output_projection=None,
代码语言:txt
复制
                    feed_previous=False,
代码语言:txt
复制
                    dtype=tf.float32)

构建模型部分放单独函数:

代码语言:txt
复制
def get_model():
代码语言:txt
复制
    """构造模型
代码语言:txt
复制
    """
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    for i in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="encoder{0}".format(i)))
代码语言:txt
复制
    for i in xrange(output_seq_len):
代码语言:txt
复制
        decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="decoder{0}".format(i)))
代码语言:txt
复制
    cell = tf.contrib.rnn.BasicLSTMCell(size)
代码语言:txt
复制
    # 这里输出的状态我们不需要
代码语言:txt
复制
    outputs, _ = seq2seq.embedding_attention_seq2seq(
代码语言:txt
复制
                        encoder_inputs,
代码语言:txt
复制
                        decoder_inputs,
代码语言:txt
复制
                        cell,
代码语言:txt
复制
                        num_encoder_symbols=num_encoder_symbols,
代码语言:txt
复制
                        num_decoder_symbols=num_decoder_symbols,
代码语言:txt
复制
                        embedding_size=size,
代码语言:txt
复制
                        output_projection=None,
代码语言:txt
复制
                        feed_previous=False,
代码语言:txt
复制
                        dtype=tf.float32)
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, outputs

构造运行时session,填入样本数据:

代码语言:txt
复制
with tf.Session() as sess:
代码语言:txt
复制
    sample_encoder_inputs, sample_decoder_inputs = get_samples()
代码语言:txt
复制
    encoder_inputs, decoder_inputs, outputs = get_model()
代码语言:txt
复制
    input_feed = {}
代码语言:txt
复制
    for l in xrange(input_seq_len):
代码语言:txt
复制
        input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
    for l in xrange(output_seq_len):
代码语言:txt
复制
        input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
    sess.run(tf.global_variables_initializer())
代码语言:txt
复制
    outputs = sess.run(outputs, input_feed)
代码语言:txt
复制
    print outputs

输出outputs是由5个array组成list(5是序列长度),每个array由两个size是16 list组成(2表示2个样本,16表示输出符号有16个)。

outputs对应seq2seq输出,W、X、Y、Z、EOS,decoder_inputs1:,样本里7,9,11和9,11,13。

decoder_inputs结构:

代码语言:txt
复制
[array([1, 1], dtype=int32), array([ 7, 29], dtype=int32), array([ 9, 31], dtype=int32), array([11, 33], dtype=int32), array([0, 0], dtype=int32)]

损失函数说明:

https://www.tensorflow.org/api_docs/python/tf/contrib/legacy_seq2seq/sequence_loss

代码语言:txt
复制
sequence_loss(
代码语言:txt
复制
    logits,
代码语言:txt
复制
    targets,
代码语言:txt
复制
    weights,
代码语言:txt
复制
    average_across_timesteps=True,
代码语言:txt
复制
    average_across_batch=True,
代码语言:txt
复制
    softmax_loss_function=None,
代码语言:txt
复制
    name=None
代码语言:txt
复制
)

损失函数,目标词语的平均负对数概率最小。logits是一个由多个2D shape batch * num_decoder_symbols Tensor组成list,batch是2,num_decoder_symbols是16,组成list Tensor 个数是output_seq_len。

targets是和logits一样长度(output_seq_len) list,list每一项是整数组成1D Tensor,每个Tensor shape是batch,数据类型是tf.int32,和decoder_inputs1: W、X、Y、Z、EOS结构一样。

weights和targets结构一样,数据类型是tf.float32。

计算加权交叉熵损失,weights需要初始化占位符:

代码语言:txt
复制
target_weights = []
代码语言:txt
复制
    target_weights.append(tf.placeholder(tf.float32, shape=[None], 
代码语言:txt
复制
                          name="weight{0}".format(i)))

计算损失值:

代码语言:txt
复制
targets = [decoder_inputs[i + 1] for i in xrange(len(decoder_inputs) - 1)]
代码语言:txt
复制
loss = seq2seq.sequence_loss(outputs, targets, target_weights)

targets长度比decoder_inputs少一个,长度保持一致,decoder_inputs的初始化长度加1。

计算加权交叉熵损失,有意义数权重大,无意义权重小,targets有值赋值为1,没值赋值为0:

代码语言:txt
复制
# coding:utf-8
代码语言:txt
复制
import numpy as np
代码语言:txt
复制
import tensorflow as tf
代码语言:txt
复制
from tensorflow.contrib.legacy_seq2seq.python.ops import seq2seq
代码语言:txt
复制
# 输入序列长度
代码语言:txt
复制
input_seq_len = 5
代码语言:txt
复制
# 输出序列长度
代码语言:txt
复制
output_seq_len = 5
代码语言:txt
复制
# 空值填充0
代码语言:txt
复制
PAD_ID = 0
代码语言:txt
复制
# 输出序列起始标记
代码语言:txt
复制
GO_ID = 1
代码语言:txt
复制
# LSTM神经元size
代码语言:txt
复制
size = 8
代码语言:txt
复制
# 最大输入符号数
代码语言:txt
复制
num_encoder_symbols = 10
代码语言:txt
复制
# 最大输出符号数
代码语言:txt
复制
num_decoder_symbols = 16
代码语言:txt
复制
def get_samples():
代码语言:txt
复制
    """构造样本数据
代码语言:txt
复制
    :return:
代码语言:txt
复制
        encoder_inputs: [array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([1, 3], dtype=int32),
代码语言:txt
复制
                         array([3, 5], dtype=int32), 
代码语言:txt
复制
                         array([5, 7], dtype=int32)]
代码语言:txt
复制
        decoder_inputs: [array([1, 1], dtype=int32), 
代码语言:txt
复制
                         array([7, 9], dtype=int32), 
代码语言:txt
复制
                         array([ 9, 11], dtype=int32),
代码语言:txt
复制
                         array([11, 13], dtype=int32), 
代码语言:txt
复制
                         array([0, 0], dtype=int32)]
代码语言:txt
复制
    """
代码语言:txt
复制
    train_set = [[[1, 3, 5], [7, 9, 11]], [[3, 5, 7], [9, 11, 13]]]
代码语言:txt
复制
    encoder_input_0 = [PAD_ID] * (input_seq_len - len(train_set[0][0])) 
代码语言:txt
复制
                         + train_set[0][0]
代码语言:txt
复制
    encoder_input_1 = [PAD_ID] * (input_seq_len - len(train_set[1][0])) 
代码语言:txt
复制
                         + train_set[1][0]
代码语言:txt
复制
    decoder_input_0 = [GO_ID] + train_set[0][1] 
代码语言:txt
复制
                         + [PAD_ID] * (output_seq_len - len(train_set[0][1]) - 1)
代码语言:txt
复制
    decoder_input_1 = [GO_ID] + train_set[1][1] 
代码语言:txt
复制
                         + [PAD_ID] * (output_seq_len - len(train_set[1][1]) - 1)
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    target_weights = []
代码语言:txt
复制
    for length_idx in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(np.array([encoder_input_0[length_idx], 
代码语言:txt
复制
                         encoder_input_1[length_idx]], dtype=np.int32))
代码语言:txt
复制
    for length_idx in xrange(output_seq_len):
代码语言:txt
复制
        decoder_inputs.append(np.array([decoder_input_0[length_idx], 
代码语言:txt
复制
                         decoder_input_1[length_idx]], dtype=np.int32))
代码语言:txt
复制
        target_weights.append(np.array([
代码语言:txt
复制
            0.0 if length_idx == output_seq_len - 1 
代码语言:txt
复制
                         or decoder_input_0[length_idx] == PAD_ID else 1.0,
代码语言:txt
复制
            0.0 if length_idx == output_seq_len - 1 
代码语言:txt
复制
                         or decoder_input_1[length_idx] == PAD_ID else 1.0,
代码语言:txt
复制
        ], dtype=np.float32))
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, target_weights
代码语言:txt
复制
def get_model():
代码语言:txt
复制
    """构造模型
代码语言:txt
复制
    """
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    target_weights = []
代码语言:txt
复制
    for i in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="encoder{0}".format(i)))
代码语言:txt
复制
    for i in xrange(output_seq_len + 1):
代码语言:txt
复制
        decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="decoder{0}".format(i)))
代码语言:txt
复制
    for i in xrange(output_seq_len):
代码语言:txt
复制
        target_weights.append(tf.placeholder(tf.float32, shape=[None],
代码语言:txt
复制
                          name="weight{0}".format(i)))
代码语言:txt
复制
    # decoder_inputs左移一个时序作为targets
代码语言:txt
复制
    targets = [decoder_inputs[i + 1] for i in xrange(output_seq_len)]
代码语言:txt
复制
    cell = tf.contrib.rnn.BasicLSTMCell(size)
代码语言:txt
复制
    # 这里输出的状态我们不需要
代码语言:txt
复制
    outputs, _ = seq2seq.embedding_attention_seq2seq(
代码语言:txt
复制
                        encoder_inputs,
代码语言:txt
复制
                        decoder_inputs[:output_seq_len],
代码语言:txt
复制
                        cell,
代码语言:txt
复制
                        num_encoder_symbols=num_encoder_symbols,
代码语言:txt
复制
                        num_decoder_symbols=num_decoder_symbols,
代码语言:txt
复制
                        embedding_size=size,
代码语言:txt
复制
                        output_projection=None,
代码语言:txt
复制
                        feed_previous=False,
代码语言:txt
复制
                        dtype=tf.float32)
代码语言:txt
复制
    # 计算加权交叉熵损失
代码语言:txt
复制
    loss = seq2seq.sequence_loss(outputs, targets, target_weights)
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, target_weights, outputs, loss
代码语言:txt
复制
def main():
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                          = get_samples()
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, outputs, loss = get_model()
代码语言:txt
复制
        input_feed = {}
代码语言:txt
复制
        for l in xrange(input_seq_len):
代码语言:txt
复制
            input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
        for l in xrange(output_seq_len):
代码语言:txt
复制
            input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
            input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
        input_feed[decoder_inputs[output_seq_len].name] = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
        sess.run(tf.global_variables_initializer())
代码语言:txt
复制
        loss = sess.run(loss, input_feed)
代码语言:txt
复制
        print loss
代码语言:txt
复制
if __name__ == "__main__":
代码语言:txt
复制
    main()

训练模型,经过多轮计算让loss变得很小,运用梯度下降更新参数。tensorflow提供梯度下降类:

https://www.tensorflow.org/api_docs/python/tf/train/GradientDescentOptimizer

Class GradientDescentOptimizer构造方法:

代码语言:txt
复制
__init__(
代码语言:txt
复制
    learning_rate,
代码语言:txt
复制
    use_locking=False,
代码语言:txt
复制
    name='GradientDescent'
代码语言:txt
复制
)

关键是第一个参数 学习率。

计算梯度方法:

代码语言:txt
复制
compute_gradients(
代码语言:txt
复制
    loss,
代码语言:txt
复制
    var_list=None,
代码语言:txt
复制
    gate_gradients=GATE_OP
代码语言:txt
复制
    aggregation_method=None,
代码语言:txt
复制
    colocate_gradients_with_ops=False,
代码语言:txt
复制
    grad_loss=None
代码语言:txt
复制
)

关键参数loss是传入误差值,返回值是(gradient, variable)组成list。

更新参数方法:

代码语言:txt
复制
apply_gradients(
代码语言:txt
复制
    grads_and_vars,
代码语言:txt
复制
    global_step=None,
代码语言:txt
复制
    name=None
代码语言:txt
复制
)

grads_and_vars是compute_gradients返回值。

根据loss计算梯度更新参数方法:

代码语言:txt
复制
learning_rate = 0.1
代码语言:txt
复制
opt = tf.train.GradientDescentOptimizer(learning_rate)
代码语言:txt
复制
update = opt.apply_gradients(opt.compute_gradients(loss))

main函数增加循环迭代:

代码语言:txt
复制
def main():
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                          = get_samples()
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, outputs, loss, update 
代码语言:txt
复制
                          = get_model()
代码语言:txt
复制
        input_feed = {}
代码语言:txt
复制
        for l in xrange(input_seq_len):
代码语言:txt
复制
            input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
        for l in xrange(output_seq_len):
代码语言:txt
复制
            input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
            input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
        input_feed[decoder_inputs[output_seq_len].name] = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
        sess.run(tf.global_variables_initializer())
代码语言:txt
复制
        while True:
代码语言:txt
复制
            [loss_ret, _] = sess.run([loss, update], input_feed)
代码语言:txt
复制
            print loss_ret

实现预测逻辑,只输入样本encoder_input,自动预测decoder_input。

训练模型保存,重新启动预测时加载:

代码语言:txt
复制
def get_model():
代码语言:txt
复制
      ...
代码语言:txt
复制
saver = tf.train.Saver(tf.global_variables())
代码语言:txt
复制
      return ..., saver

训练结束后执行

代码语言:txt
复制
saver.save(sess, './model/demo')

模型存储到./model目录下以demo开头文件,加载先调用:

代码语言:txt
复制
saver.restore(sess, './model/demo')

预测候,原则上不能有decoder_inputs输入,执行时decoder_inputs取前一个时序输出,embedding_attention_seq2seq feed_previous参数,若为True则decoder每一步输入都用前一步输出来填充。

get_model传递参数区分训练和预测是不同feed_previous配置,预测时main函数也是不同,分开两个函数分别做train和predict。

代码语言:txt
复制
# coding:utf-8
代码语言:txt
复制
import sys
代码语言:txt
复制
import numpy as np
代码语言:txt
复制
import tensorflow as tf
代码语言:txt
复制
from tensorflow.contrib.legacy_seq2seq.python.ops import seq2seq
代码语言:txt
复制
# 输入序列长度
代码语言:txt
复制
input_seq_len = 5
代码语言:txt
复制
# 输出序列长度
代码语言:txt
复制
output_seq_len = 5
代码语言:txt
复制
# 空值填充0
代码语言:txt
复制
PAD_ID = 0
代码语言:txt
复制
# 输出序列起始标记
代码语言:txt
复制
GO_ID = 1
代码语言:txt
复制
# 结尾标记
代码语言:txt
复制
EOS_ID = 2
代码语言:txt
复制
# LSTM神经元size
代码语言:txt
复制
size = 8
代码语言:txt
复制
# 最大输入符号数
代码语言:txt
复制
num_encoder_symbols = 10
代码语言:txt
复制
# 最大输出符号数
代码语言:txt
复制
num_decoder_symbols = 16
代码语言:txt
复制
# 学习率
代码语言:txt
复制
learning_rate = 0.1
代码语言:txt
复制
def get_samples():
代码语言:txt
复制
    """构造样本数据
代码语言:txt
复制
    :return:
代码语言:txt
复制
        encoder_inputs: [array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([0, 0], dtype=int32), 
代码语言:txt
复制
                         array([5, 5], dtype=int32),
代码语言:txt
复制
                         array([7, 7], dtype=int32), 
代码语言:txt
复制
                         array([9, 9], dtype=int32)]
代码语言:txt
复制
        decoder_inputs: [array([1, 1], dtype=int32), 
代码语言:txt
复制
                         array([11, 11], dtype=int32), 
代码语言:txt
复制
                         array([13, 13], dtype=int32),
代码语言:txt
复制
                         array([15, 15], dtype=int32), 
代码语言:txt
复制
                         array([2, 2], dtype=int32)]
代码语言:txt
复制
    """
代码语言:txt
复制
    train_set = [[[5, 7, 9], [11, 13, 15, EOS_ID]], [[7, 9, 11], [13, 15, 17, EOS_ID]]]
代码语言:txt
复制
    raw_encoder_input = []
代码语言:txt
复制
    raw_decoder_input = []
代码语言:txt
复制
    for sample in train_set:
代码语言:txt
复制
        raw_encoder_input.append([PAD_ID] * (input_seq_len - len(sample[0])) + sample[0])
代码语言:txt
复制
        raw_decoder_input.append([GO_ID] + sample[1] 
代码语言:txt
复制
                         + [PAD_ID] * (output_seq_len - len(sample[1]) - 1))
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    target_weights = []
代码语言:txt
复制
    for length_idx in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(np.array([encoder_input[length_idx] 
代码语言:txt
复制
                          for encoder_input in raw_encoder_input], 
代码语言:txt
复制
                                                  dtype=np.int32))
代码语言:txt
复制
    for length_idx in xrange(output_seq_len):
代码语言:txt
复制
        decoder_inputs.append(np.array([decoder_input[length_idx] 
代码语言:txt
复制
                          for decoder_input in raw_decoder_input], 
代码语言:txt
复制
                                                  dtype=np.int32))
代码语言:txt
复制
        target_weights.append(np.array([
代码语言:txt
复制
            0.0 if length_idx == output_seq_len - 1 
代码语言:txt
复制
                         or decoder_input[length_idx] == PAD_ID else 1.0 
代码语言:txt
复制
                         for decoder_input in raw_decoder_input
代码语言:txt
复制
        ], dtype=np.float32))
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, target_weights
代码语言:txt
复制
def get_model(feed_previous=False):
代码语言:txt
复制
    """构造模型
代码语言:txt
复制
    """
代码语言:txt
复制
    encoder_inputs = []
代码语言:txt
复制
    decoder_inputs = []
代码语言:txt
复制
    target_weights = []
代码语言:txt
复制
    for i in xrange(input_seq_len):
代码语言:txt
复制
        encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="encoder{0}".format(i)))
代码语言:txt
复制
    for i in xrange(output_seq_len + 1):
代码语言:txt
复制
        decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 
代码语言:txt
复制
                          name="decoder{0}".format(i)))
代码语言:txt
复制
    for i in xrange(output_seq_len):
代码语言:txt
复制
        target_weights.append(tf.placeholder(tf.float32, shape=[None], 
代码语言:txt
复制
                         name="weight{0}".format(i)))
代码语言:txt
复制
    # decoder_inputs左移一个时序作为targets
代码语言:txt
复制
    targets = [decoder_inputs[i + 1] for i in xrange(output_seq_len)]
代码语言:txt
复制
    cell = tf.contrib.rnn.BasicLSTMCell(size)
代码语言:txt
复制
    # 这里输出的状态我们不需要
代码语言:txt
复制
    outputs, _ = seq2seq.embedding_attention_seq2seq(
代码语言:txt
复制
                        encoder_inputs,
代码语言:txt
复制
                        decoder_inputs[:output_seq_len],
代码语言:txt
复制
                        cell,
代码语言:txt
复制
                        num_encoder_symbols=num_encoder_symbols,
代码语言:txt
复制
                        num_decoder_symbols=num_decoder_symbols,
代码语言:txt
复制
                        embedding_size=size,
代码语言:txt
复制
                        output_projection=None,
代码语言:txt
复制
                        feed_previous=feed_previous,
代码语言:txt
复制
                        dtype=tf.float32)
代码语言:txt
复制
    # 计算加权交叉熵损失
代码语言:txt
复制
    loss = seq2seq.sequence_loss(outputs, targets, target_weights)
代码语言:txt
复制
    # 梯度下降优化器
代码语言:txt
复制
    opt = tf.train.GradientDescentOptimizer(learning_rate)
代码语言:txt
复制
    # 优化目标:让loss最小化
代码语言:txt
复制
    update = opt.apply_gradients(opt.compute_gradients(loss))
代码语言:txt
复制
    # 模型持久化
代码语言:txt
复制
    saver = tf.train.Saver(tf.global_variables())
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, target_weights, 
代码语言:txt
复制
                          outputs, loss, update, saver, targets
代码语言:txt
复制
def train():
代码语言:txt
复制
    """
代码语言:txt
复制
    训练过程
代码语言:txt
复制
    """
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                          = get_samples()
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, outputs, loss, update, saver, targets 
代码语言:txt
复制
                          = get_model()
代码语言:txt
复制
        input_feed = {}
代码语言:txt
复制
        for l in xrange(input_seq_len):
代码语言:txt
复制
            input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
        for l in xrange(output_seq_len):
代码语言:txt
复制
            input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
            input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
        input_feed[decoder_inputs[output_seq_len].name] = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
        # 全部变量初始化
代码语言:txt
复制
        sess.run(tf.global_variables_initializer())
代码语言:txt
复制
        # 训练200次迭代,每隔10次打印一次loss
代码语言:txt
复制
        for step in xrange(200):
代码语言:txt
复制
            [loss_ret, _] = sess.run([loss, update], input_feed)
代码语言:txt
复制
            if step % 10 == 0:
代码语言:txt
复制
                print 'step=', step, 'loss=', loss_ret
代码语言:txt
复制
        # 模型持久化
代码语言:txt
复制
        saver.save(sess, './model/demo')
代码语言:txt
复制
def predict():
代码语言:txt
复制
    """
代码语言:txt
复制
    预测过程
代码语言:txt
复制
    """
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                          = get_samples()
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, 
代码语言:txt
复制
                          outputs, loss, update, saver, targets 
代码语言:txt
复制
                          = get_model(feed_previous=True)
代码语言:txt
复制
        # 从文件恢复模型
代码语言:txt
复制
        saver.restore(sess, './model/demo')
代码语言:txt
复制
        input_feed = {}
代码语言:txt
复制
        for l in xrange(input_seq_len):
代码语言:txt
复制
            input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
        for l in xrange(output_seq_len):
代码语言:txt
复制
            input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
            input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
        input_feed[decoder_inputs[output_seq_len].name] = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
        # 预测输出
代码语言:txt
复制
        outputs = sess.run(outputs, input_feed)
代码语言:txt
复制
        # 一共试验样本有2个,所以分别遍历
代码语言:txt
复制
        for sample_index in xrange(2):
代码语言:txt
复制
            # 因为输出数据每一个是num_decoder_symbols维的
代码语言:txt
复制
            # 因此找到数值最大的那个就是预测的id,就是这里的argmax函数的功能
代码语言:txt
复制
            outputs_seq = [int(np.argmax(logit[sample_index], axis=0)) for logit in outputs]
代码语言:txt
复制
            # 如果是结尾符,那么后面的语句就不输出了
代码语言:txt
复制
            if EOS_ID in outputs_seq:
代码语言:txt
复制
                outputs_seq = outputs_seq[:outputs_seq.index(EOS_ID)]
代码语言:txt
复制
            outputs_seq = [str(v) for v in outputs_seq]
代码语言:txt
复制
            print " ".join(outputs_seq)
代码语言:txt
复制
if __name__ == "__main__":
代码语言:txt
复制
    if sys.argv[1] == 'train':
代码语言:txt
复制
        train()
代码语言:txt
复制
    else:
代码语言:txt
复制
        predict()

文件命名demo.py,执行./demo.py train训练模型,执行./demo.py predict预测。

预测时按照完整encoder_inputs和decoder_inputs计算,继续改进predict,手工输入一串数字(只有encoder部分)。

实现从输入空格分隔数字id串,转成预测用encoder、decoder、target_weight函数。

代码语言:txt
复制
def seq_to_encoder(input_seq):
代码语言:txt
复制
    """从输入空格分隔的数字id串,转成预测用的encoder、decoder、target_weight等
代码语言:txt
复制
    """
代码语言:txt
复制
    input_seq_array = [int(v) for v in input_seq.split()]
代码语言:txt
复制
    encoder_input = [PAD_ID] * (input_seq_len - len(input_seq_array)) + input_seq_array
代码语言:txt
复制
    decoder_input = [GO_ID] + [PAD_ID] * (output_seq_len - 1)
代码语言:txt
复制
    encoder_inputs = [np.array([v], dtype=np.int32) for v in encoder_input]
代码语言:txt
复制
    decoder_inputs = [np.array([v], dtype=np.int32) for v in decoder_input]
代码语言:txt
复制
    target_weights = [np.array([1.0], dtype=np.float32)] * output_seq_len
代码语言:txt
复制
    return encoder_inputs, decoder_inputs, target_weights

然后我们改写predict函数如下:

代码语言:txt
复制
def predict():
代码语言:txt
复制
    """
代码语言:txt
复制
    预测过程
代码语言:txt
复制
    """
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, outputs, loss, update, saver 
代码语言:txt
复制
                          = get_model(feed_previous=True)
代码语言:txt
复制
        saver.restore(sess, './model/demo')
代码语言:txt
复制
        sys.stdout.write("> ")
代码语言:txt
复制
        sys.stdout.flush()
代码语言:txt
复制
        input_seq = sys.stdin.readline()
代码语言:txt
复制
        while input_seq:
代码语言:txt
复制
            input_seq = input_seq.strip()
代码语言:txt
复制
            sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                          = seq_to_encoder(input_seq)
代码语言:txt
复制
            input_feed = {}
代码语言:txt
复制
            for l in xrange(input_seq_len):
代码语言:txt
复制
                input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
            for l in xrange(output_seq_len):
代码语言:txt
复制
                input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
                input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
            input_feed[decoder_inputs[output_seq_len].name] = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
            # 预测输出
代码语言:txt
复制
            outputs_seq = sess.run(outputs, input_feed)
代码语言:txt
复制
            # 因为输出数据每一个是num_decoder_symbols维的
代码语言:txt
复制
            # 因此找到数值最大的那个就是预测的id,就是这里的argmax函数的功能
代码语言:txt
复制
            outputs_seq = [int(np.argmax(logit[0], axis=0)) for logit in outputs_seq]
代码语言:txt
复制
            # 如果是结尾符,那么后面的语句就不输出了
代码语言:txt
复制
            if EOS_ID in outputs_seq:
代码语言:txt
复制
                outputs_seq = outputs_seq[:outputs_seq.index(EOS_ID)]
代码语言:txt
复制
            outputs_seq = [str(v) for v in outputs_seq]
代码语言:txt
复制
            print " ".join(outputs_seq)
代码语言:txt
复制
            sys.stdout.write("> ")
代码语言:txt
复制
            sys.stdout.flush()
代码语言:txt
复制
            input_seq = sys.stdin.readline()

执行./demo.py predict。

设置num_encoder_symbols = 10,11无法表达,修改参数并增加样本:

代码语言:txt
复制
# 最大输入符号数
代码语言:txt
复制
num_encoder_symbols = 32
代码语言:txt
复制
# 最大输出符号数
代码语言:txt
复制
num_decoder_symbols = 32
代码语言:txt
复制
……
代码语言:txt
复制
train_set = [
代码语言:txt
复制
              [[5, 7, 9], [11, 13, 15, EOS_ID]], 
代码语言:txt
复制
              [[7, 9, 11], [13, 15, 17, EOS_ID]], 
代码语言:txt
复制
              [[15, 17, 19], [21, 23, 25, EOS_ID]]
代码语言:txt
复制
            ]
代码语言:txt
复制
……

迭代次数扩大到10000次。

输入样本,预测效果非常好,换成其他输入,还是在样本输出找最相近结果作预测结果,不会思考,没有智能,所模型更适合做分类,不适合做推理。

训练时把中文词汇转成id号,预测时把预测id转成中文。

新建word_token.py文件,并建一个WordToken类,load函数负责加载样本,生成word2id_dict和id2word_dict词典,word2id函数负责将词汇转成id,id2word负责将id转成词汇:

代码语言:txt
复制
# coding:utf-8
代码语言:txt
复制
import sys
代码语言:txt
复制
import jieba
代码语言:txt
复制
class WordToken(object):
代码语言:txt
复制
    def __init__(self):
代码语言:txt
复制
        # 最小起始id号, 保留的用于表示特殊标记
代码语言:txt
复制
        self.START_ID = 4
代码语言:txt
复制
        self.word2id_dict = {}
代码语言:txt
复制
        self.id2word_dict = {}
代码语言:txt
复制
    def load_file_list(self, file_list):
代码语言:txt
复制
        """
代码语言:txt
复制
        加载样本文件列表,全部切词后统计词频,按词频由高到低排序后顺次编号
代码语言:txt
复制
        并存到self.word2id_dict和self.id2word_dict中
代码语言:txt
复制
        """
代码语言:txt
复制
        words_count = {}
代码语言:txt
复制
        for file in file_list:
代码语言:txt
复制
            with open(file, 'r') as file_object:
代码语言:txt
复制
                for line in file_object.readlines():
代码语言:txt
复制
                    line = line.strip()
代码语言:txt
复制
                    seg_list = jieba.cut(line)
代码语言:txt
复制
                    for str in seg_list:
代码语言:txt
复制
                        if str in words_count:
代码语言:txt
复制
                            words_count[str] = words_count[str] + 1
代码语言:txt
复制
                        else:
代码语言:txt
复制
                            words_count[str] = 1
代码语言:txt
复制
        sorted_list = [[v[1], v[0]] for v in words_count.items()]
代码语言:txt
复制
        sorted_list.sort(reverse=True)
代码语言:txt
复制
        for index, item in enumerate(sorted_list):
代码语言:txt
复制
            word = item[1]
代码语言:txt
复制
            self.word2id_dict[word] = self.START_ID + index
代码语言:txt
复制
            self.id2word_dict[self.START_ID + index] = word
代码语言:txt
复制
    def word2id(self, word):
代码语言:txt
复制
        if not isinstance(word, unicode):
代码语言:txt
复制
            print "Exception: error word not unicode"
代码语言:txt
复制
            sys.exit(1)
代码语言:txt
复制
        if word in self.word2id_dict:
代码语言:txt
复制
            return self.word2id_dict[word]
代码语言:txt
复制
        else:
代码语言:txt
复制
            return None
代码语言:txt
复制
    def id2word(self, id):
代码语言:txt
复制
        id = int(id)
代码语言:txt
复制
        if id in self.id2word_dict:
代码语言:txt
复制
            return self.id2word_dict[id]
代码语言:txt
复制
        else:
代码语言:txt
复制
            return None

demo.py修改get_train_set:

代码语言:txt
复制
def get_train_set():
代码语言:txt
复制
    global num_encoder_symbols, num_decoder_symbols
代码语言:txt
复制
    train_set = []
代码语言:txt
复制
    with open('./samples/question', 'r') as question_file:
代码语言:txt
复制
        with open('./samples/answer', 'r') as answer_file:
代码语言:txt
复制
            while True:
代码语言:txt
复制
                question = question_file.readline()
代码语言:txt
复制
                answer = answer_file.readline()
代码语言:txt
复制
                if question and answer:
代码语言:txt
复制
                    question = question.strip()
代码语言:txt
复制
                    answer = answer.strip()
代码语言:txt
复制
                    question_id_list = get_id_list_from(question)
代码语言:txt
复制
                    answer_id_list = get_id_list_from(answer)
代码语言:txt
复制
                    answer_id_list.append(EOS_ID)
代码语言:txt
复制
                    train_set.append([question_id_list, answer_id_list])
代码语言:txt
复制
                else:
代码语言:txt
复制
                    break
代码语言:txt
复制
    return train_set

get_id_list_from实现:

代码语言:txt
复制
def get_id_list_from(sentence):
代码语言:txt
复制
    sentence_id_list = []
代码语言:txt
复制
    seg_list = jieba.cut(sentence)
代码语言:txt
复制
    for str in seg_list:
代码语言:txt
复制
        id = wordToken.word2id(str)
代码语言:txt
复制
        if id:
代码语言:txt
复制
            sentence_id_list.append(wordToken.word2id(str))
代码语言:txt
复制
    return sentence_id_list

wordToken:

代码语言:txt
复制
import word_token
代码语言:txt
复制
import jieba
代码语言:txt
复制
wordToken = word_token.WordToken()
代码语言:txt
复制
# 放在全局的位置,为了动态算出num_encoder_symbols和num_decoder_symbols
代码语言:txt
复制
max_token_id = wordToken.load_file_list(['./samples/question', './samples/answer'])
代码语言:txt
复制
num_encoder_symbols = max_token_id + 5
代码语言:txt
复制
num_decoder_symbols = max_token_id + 5

训练代码:

代码语言:txt
复制
        # 训练很多次迭代,每隔10次打印一次loss,可以看情况直接ctrl+c停止
代码语言:txt
复制
        for step in xrange(100000):
代码语言:txt
复制
            [loss_ret, _] = sess.run([loss, update], input_feed)
代码语言:txt
复制
            if step % 10 == 0:
代码语言:txt
复制
                print 'step=', step, 'loss=', loss_ret
代码语言:txt
复制
                # 模型持久化
代码语言:txt
复制
                saver.save(sess, './model/demo')

预测代码修改:

代码语言:txt
复制
def predict():
代码语言:txt
复制
    """
代码语言:txt
复制
    预测过程
代码语言:txt
复制
    """
代码语言:txt
复制
    with tf.Session() as sess:
代码语言:txt
复制
        encoder_inputs, decoder_inputs, target_weights, outputs, loss, update, saver 
代码语言:txt
复制
                        = get_model(feed_previous=True)
代码语言:txt
复制
        saver.restore(sess, './model/demo')
代码语言:txt
复制
        sys.stdout.write("> ")
代码语言:txt
复制
        sys.stdout.flush()
代码语言:txt
复制
        input_seq = sys.stdin.readline()
代码语言:txt
复制
        while input_seq:
代码语言:txt
复制
            input_seq = input_seq.strip()
代码语言:txt
复制
            input_id_list = get_id_list_from(input_seq)
代码语言:txt
复制
            if (len(input_id_list)):
代码语言:txt
复制
                sample_encoder_inputs, sample_decoder_inputs, sample_target_weights 
代码语言:txt
复制
                        = seq_to_encoder(' '.join([str(v) for v in input_id_list]))
代码语言:txt
复制
                input_feed = {}
代码语言:txt
复制
                for l in xrange(input_seq_len):
代码语言:txt
复制
                    input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
                for l in xrange(output_seq_len):
代码语言:txt
复制
                    input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
                    input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
                input_feed[decoder_inputs[output_seq_len].name] 
代码语言:txt
复制
                        = np.zeros([2], dtype=np.int32)
代码语言:txt
复制
                # 预测输出
代码语言:txt
复制
                outputs_seq = sess.run(outputs, input_feed)
代码语言:txt
复制
                # 因为输出数据每一个是num_decoder_symbols维的
代码语言:txt
复制
                # 因此找到数值最大的那个就是预测的id,就是这里的argmax函数的功能
代码语言:txt
复制
                outputs_seq = [int(np.argmax(logit[0], axis=0)) for logit in outputs_seq]
代码语言:txt
复制
                # 如果是结尾符,那么后面的语句就不输出了
代码语言:txt
复制
                if EOS_ID in outputs_seq:
代码语言:txt
复制
                    outputs_seq = outputs_seq[:outputs_seq.index(EOS_ID)]
代码语言:txt
复制
                outputs_seq = [wordToken.id2word(v) for v in outputs_seq]
代码语言:txt
复制
                print " ".join(outputs_seq)
代码语言:txt
复制
            else:
代码语言:txt
复制
                print "WARN:词汇不在服务区"
代码语言:txt
复制
            sys.stdout.write("> ")
代码语言:txt
复制
            sys.stdout.flush()
代码语言:txt
复制
            input_seq = sys.stdin.readline()

用存储在'./samples/question', './samples/answer'1000个对话样本训练,使loss输出收敛到一定程度(比如1.0)以下:

代码语言:txt
复制
python demo.py train

到1.0以下后手工ctrl+c停止,每隔10个step都store一次模型。

模型收敛非常慢,设置学习率是0.1。首先学习率大一些,每当下一步loss和上一步相比反弹(反而增大)时再尝试降低学习率。不再直接用learning_rate,初始化一个学习率:

代码语言:txt
复制
init_learning_rate = 1

get_model中创建一个变量,用init_learning_rate初始化:

代码语言:txt
复制
learning_rate = tf.Variable(float(init_learning_rate), trainable=False, dtype=tf.float32)

再创建一个操作,在适当时候把学习率打9折:

代码语言:txt
复制
learning_rate_decay_op = learning_rate.assign(learning_rate * 0.9)

训练代码调整:

代码语言:txt
复制
        # 训练很多次迭代,每隔10次打印一次loss,可以看情况直接ctrl+c停止
代码语言:txt
复制
        previous_losses = []
代码语言:txt
复制
        for step in xrange(100000):
代码语言:txt
复制
            [loss_ret, _] = sess.run([loss, update], input_feed)
代码语言:txt
复制
            if step % 10 == 0:
代码语言:txt
复制
                print 'step=', step, 'loss=', 
代码语言:txt
复制
                        loss_ret, 'learning_rate=', learning_rate.eval()
代码语言:txt
复制
                if loss_ret > max(previous_losses[-5:]):
代码语言:txt
复制
                    sess.run(learning_rate_decay_op)
代码语言:txt
复制
                previous_losses.append(loss_ret)
代码语言:txt
复制
                # 模型持久化
代码语言:txt
复制
                saver.save(sess, './model/demo')

训练可以快速收敛。

参考文献

http://colah.github.io/posts/2015-08-Understanding-LSTMs/

http://suriyadeepan.github.io/2016-06-28-easy-seq2seq/

http://www.wildml.com/2016/01/attention-and-memory-in-deep-learning-and-nlp/

https://arxiv.org/abs/1406.1078

https://arxiv.org/abs/1409.3215

https://arxiv.org/abs/1409.0473

样本全量加载,用大量样本训练,内存撑不住,总是Out of memory。方法是把全量加载样本改成批量加载,样本量再大,内存也不会无限增加。

https://github.com/warmheartli/ChatBotCourse/tree/master/chatbotv5

样本量加大内存增长,样本量达到万级别,内存占用达到10G,每次迭代把样本全量加载到内存并一次性训练完再更新模型,词表是基于样本生成,没有做任何限制,导致样本大词表大,模型很大,占据内存更大。

优化方案。把全量加载样本改成批量加载,修改train()函数。

代码语言:txt
复制
    # 训练很多次迭代,每隔10次打印一次loss,可以看情况直接ctrl+c停止
代码语言:txt
复制
    previous_losses = []
代码语言:txt
复制
    for step in xrange(20000):
代码语言:txt
复制
        sample_encoder_inputs, sample_decoder_inputs, sample_target_weights = get_samples(train_set, 1000)
代码语言:txt
复制
        input_feed = {}
代码语言:txt
复制
        for l in xrange(input_seq_len):
代码语言:txt
复制
            input_feed[encoder_inputs[l].name] = sample_encoder_inputs[l]
代码语言:txt
复制
        for l in xrange(output_seq_len):
代码语言:txt
复制
            input_feed[decoder_inputs[l].name] = sample_decoder_inputs[l]
代码语言:txt
复制
            input_feed[target_weights[l].name] = sample_target_weights[l]
代码语言:txt
复制
        input_feed[decoder_inputs[output_seq_len].name] = np.zeros([len(sample_decoder_inputs[0])], dtype=np.int32)
代码语言:txt
复制
        [loss_ret, _] = sess.run([loss, update], input_feed)
代码语言:txt
复制
        if step % 10 == 0:
代码语言:txt
复制
            print 'step=', step, 'loss=', loss_ret, 'learning_rate=', learning_rate.eval()
代码语言:txt
复制
            if len(previous_losses) > 5 and loss_ret > max(previous_losses[-5:]):
代码语言:txt
复制
                sess.run(learning_rate_decay_op)
代码语言:txt
复制
            previous_losses.append(loss_ret)
代码语言:txt
复制
            # 模型持久化
代码语言:txt
复制
            saver.save(sess, './model/demo')

get_samples(train_set, 1000) 批量获取样本,1000是每次获取样本量:

代码语言:txt
复制
    if batch_num >= len(train_set):
代码语言:txt
复制
        batch_train_set = train_set
代码语言:txt
复制
    else:
代码语言:txt
复制
        random_start = random.randint(0, len(train_set)-batch_num)
代码语言:txt
复制
        batch_train_set = train_set[random_start:random_start+batch_num]
代码语言:txt
复制
    for sample in batch_train_set:
代码语言:txt
复制
        raw_encoder_input.append([PAD_ID] * (input_seq_len - len(sample[0])) + sample[0])
代码语言:txt
复制
        raw_decoder_input.append([GO_ID] + sample[1] + [PAD_ID] * (output_seq_len - len(sample[1]) - 1))

每次在全量样本中随机位置抽取连续1000条样本。

加载样本词表时做词最小频率限制:

代码语言:txt
复制
    def load_file_list(self, file_list, min_freq):
代码语言:txt
复制
    ......
代码语言:txt
复制
        for index, item in enumerate(sorted_list):
代码语言:txt
复制
            word = item[1]
代码语言:txt
复制
            if item[0] < min_freq:
代码语言:txt
复制
                break
代码语言:txt
复制
            self.word2id_dict[word] = self.START_ID + index
代码语言:txt
复制
            self.id2word_dict[self.START_ID + index] = word
代码语言:txt
复制
        return index

https://github.com/warmheartli/ChatBotCourse/tree/master/chatbotv5

参考资料:

《Python 自然语言处理》

《NLTK基础教程 用NLTK和Python库构建机器学习应用》

http://www.shareditor.com/blogshow?blogId=136

http://www.shareditor.com/blogshow?blogId=137

欢迎推荐上海机器学习工作机会,我的微信:qingxingfengzi

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
NLP 服务
NLP 服务(Natural Language Process,NLP)深度整合了腾讯内部的 NLP 技术,提供多项智能文本处理和文本生成能力,包括词法分析、相似词召回、词相似度、句子相似度、文本润色、句子纠错、文本补全、句子生成等。满足各行业的文本智能需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档