学习笔记CB013: TensorFlow、TensorBoard、seq2seq

tensorflow基于图结构深度学习框架，内部通过session实现图和计算内核交互。

tensorflow基本数学运算用法。

``````import tensorflow as tf
sess = tf.Session()
a = tf.placeholder("float")
b = tf.placeholder("float")
c = tf.constant(6.0)
d = tf.mul(a, b)
y = tf.mul(d, c)
print sess.run(y, feed_dict={a: 3, b: 3})
A = [[1.1,2.3],[3.4,4.1]]
Y = tf.matrix_inverse(A)
print sess.run(Y)
sess.close()
``````

``````tf.add
tf.sub
tf.mul
tf.div
tf.mod
tf.abs
tf.neg
tf.sign
tf.inv
tf.square
tf.round
tf.sqrt
tf.pow
tf.exp
tf.log
tf.maximum
tf.minimum
tf.cos
tf.sin
``````

``````tf.diag #生成对角阵
tf.transpose
tf.matmul
tf.matrix_determinant #计算行列式的值
tf.matrix_inverse #计算矩阵的逆
``````

tensorboard使用。tensorflow代码，先构建图，然后执行，对中间过程调试不方便，提供一个tensorboard工具调试。训练时提示写入事件文件到目录(/tmp/tflearn_logs/11U8M4/)。执行命令打开 http://192.168.1.101:6006 看到tensorboard的界面。

``````tensorboard --logdir=/tmp/tflearn_logs/11U8M4/
``````

Graph和Session。

``````import tensorflow as tf
with tf.Graph().as_default() as g:
with g.name_scope("myscope") as scope: # 有了这个scope，下面的op的name都是类似myscope/Placeholder这样的前缀
sess = tf.Session(target='', graph = g, config=None) # target表示要连接的tf执行引擎
print "graph version:", g.version # 0
a = tf.placeholder("float")
print a.op # 输出整个operation信息，跟下面g.get_operations返回结果一样
print "graph version:", g.version # 1
b = tf.placeholder("float")
print "graph version:", g.version # 2
c = tf.placeholder("float")
print "graph version:", g.version # 3
y1 = tf.mul(a, b) # 也可以写成a * b
print "graph version:", g.version # 4
y2 = tf.mul(y1, c) # 也可以写成y1 * c
print "graph version:", g.version # 5
operations = g.get_operations()

for (i, op) in enumerate(operations):
print "============ operation", i+1, "==========="
print op # 一个结构，包括：name、op、attr、input等,不同op不一样
assert y1.graph is g
assert sess.graph is g
print "================ graph object address ================"
print sess.graph
print "================ graph define ================"
print sess.graph_def
print "================ sess str ================"
print sess.sess_str
print sess.run(y1, feed_dict={a: 3, b: 3}) # 9.0 feed_dictgraph中的元素和值的映射
print sess.run(fetches=[b,y1], feed_dict={a: 3, b: 3}, options=None, run_metadata=None) # 传入的feches和返回值的shape相同
print sess.run({'ret_name':y1}, feed_dict={a: 3, b: 3}) # {'ret_name': 9.0} 传入的feches和返回值的shape相同

assert tf.get_default_session() is not sess
with sess.as_default(): # 把sess作为默认的session，那么tf.get_default_session就是sess, 否则不是
assert tf.get_default_session() is sess

h = sess.partial_run_setup([y1, y2], [a, b, c]) # 分阶段运行，参数指明了feches和feed_dict列表
res = sess.partial_run(h, y1, feed_dict={a: 3, b: 4}) # 12 运行第一阶段
res = sess.partial_run(h, y2, feed_dict={c: res}) # 144.0 运行第二阶段，其中使用了第一阶段的执行结果
print "partial_run res:", res
sess.close()
``````

tensorflow Session是Graph和执行者媒介，Session.run()将graph、fetches、feed_dict序列化到字节数组，调用tf_session.TF_Run（参见/usr/local/lib/python2.7/site-packages/tensorflow/python/client/session.py）。tf_session.TF_Run调用动态链接库_pywrap_tensorflow.so实现_pywrap_tensorflow.TF_Run接口(参见/usr/local/lib/python2.7/site-packages/tensorflow/python/pywrap_tensorflow.py)。动态链接库是tensorflow多语言python接口。_pywrap_tensorflow.so和pywrap_tensorflow.py通过SWIG工具自动生成，tensorflow核心语言c语言，通过SWIG生成各种脚本语言接口。

10行关键代码实现线性回归。用梯度下降求解线性回归问题是tensorflow最简单入门例子（10行关键代码）。

``````# -*- coding: utf-8 -*-
import numpy as np
import tensorflow as tf
# 随机生成1000个点，围绕在y=0.1x+0.3的直线周围
num_points = 1000
vectors_set = []
for i in xrange(num_points):
x1 = np.random.normal(0.0, 0.55)
y1 = x1 * 0.1 + 0.3 + np.random.normal(0.0, 0.03)
vectors_set.append([x1, y1])
# 生成一些样本
x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]
# 生成1维的W矩阵，取值是[-1,1]之间的随机数
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0), name='W')
# 生成1维的b矩阵，初始值是0
b = tf.Variable(tf.zeros([1]), name='b')
# 经过计算得出预估值y
y = W * x_data + b
# 以预估值y和实际值y_data之间的均方误差作为损失
loss = tf.reduce_mean(tf.square(y - y_data), name='loss')
# 采用梯度下降法来优化参数
# 训练的过程就是最小化这个误差值
train = optimizer.minimize(loss, name='train')
sess = tf.Session()
# 输出图结构
#print sess.graph_def
init = tf.initialize_all_variables()
sess.run(init)
# 初始化的W和b是多少
print "W =", sess.run(W), "b =", sess.run(b), "loss =", sess.run(loss)
# 执行20次训练
for step in xrange(20):
sess.run(train)
# 输出训练好的W和b
print "W =", sess.run(W), "b =", sess.run(b), "loss =", sess.run(loss)
# 生成summary文件，用于tensorboard使用
writer = tf.train.SummaryWriter("./tmp", sess.graph)
``````

``````tensorboard --logdir=./tmp/
``````

``````W = tf.Variable(tf.random_uniform([1], -1.0, 1.0), name='W')
``````

``````y = W * x_data + b
``````

train对应梯度下降训练过程操作。

``````loss = tf.reduce_mean(tf.square(y - y_data), name='loss')
``````

tensorflow自带seq2seq模型基于one-hot词嵌入，每个词用一个数字代替不足表示词与词之间关系，word2vec多维向量做词嵌入，能够表示出词之间关系。基于seq2seq思想，利用多维词向量实现模型，预期会有更高准确性。

seq2seq模型原理。参考《Sequence to Sequence Learning with Neural Networks》论文。核心思想，ABC是输入语句，WXYZ是输出语句，EOS是标识一句话结束，训练单元是lstm，lstm的特点是有长短时记忆，能够根据输入多个字确定后面多个字，lstm知识参考 http://deeplearning.net/tutorial/lstm.html 模型编码器和解码器共用同一个lstm层，共享参数，分开 https://github.com/farizrahman4u/seq2seq 绿色是编码器，黄色是解码器，橙色箭头传递lstm层状态信息(记忆信息)，编码器唯一传给解码器的状态信息。

``````python word_segment.py ./corpus.raw ./corpus.segment
``````

``````cat ./corpus.segment | awk '{if(last!="")print last"|"\$0;last=\$0}' | sed 's/| /|/g' > ./corpus.segment.pair
``````

``````word2vec -train ./corpus.segment -output vectors.bin -cbow 1 -size 200 -window 8 -negative 25 -hs 0 -sample 1e-5 -threads 20 -binary 1 -iter 15
``````

corpus.raw 原始语料数据，vectors.bin 生成的词向量二进制文件。

``````# 首先我们为输入的样本数据申请变量空间，如下。其中self.max_seq_len是指一个切好词的句子最多包含多少个词，self.word_vec_dim是词向量的维度，这里面shape指定了输入数据是不确定数量的样本，每个样本最多包含max_seq_len*2个词，每个词用word_vec_dim维浮点数表示。这里面用2倍的max_seq_len是因为我们训练是输入的X既要包含question句子又要包含answer句子
input_data = tflearn.input_data(shape=[None, self.max_seq_len*2, self.word_vec_dim], dtype=tf.float32, name = "XY")

# 然后我们将输入的所有样本数据的词序列切出前max_seq_len个，也就是question句子部分，作为编码器的输入
encoder_inputs = tf.slice(input_data, [0, 0, 0], [-1, self.max_seq_len, self.word_vec_dim], name="enc_in")

decoder_inputs_tmp = tf.slice(input_data, [0, self.max_seq_len, 0], [-1, self.max_seq_len-1, self.word_vec_dim], name="dec_in_tmp")
go_inputs = tf.ones_like(decoder_inputs_tmp)
go_inputs = tf.slice(go_inputs, [0, 0, 0], [-1, 1, self.word_vec_dim])
decoder_inputs = tf.concat(1, [go_inputs, decoder_inputs_tmp], name="dec_in")

# 之后开始编码过程，返回的encoder_output_tensor展开成tflearn.regression回归可以识别的形如(?, 1, 200)的向量；返回的states后面传入给解码器
(encoder_output_tensor, states) = tflearn.lstm(encoder_inputs, self.word_vec_dim, return_state=True, scope='encoder_lstm')
encoder_output_sequence = tf.pack([encoder_output_tensor], axis=1)

# 取出decoder_inputs的第一个词，也就是GO
first_dec_input = tf.slice(decoder_inputs, [0, 0, 0], [-1, 1, self.word_vec_dim])

# 将其输入到解码器中，如下，解码器的初始化状态为编码器生成的states，注意：这里的scope='decoder_lstm'是为了下面重用同一个解码器
decoder_output_tensor = tflearn.lstm(first_dec_input, self.word_vec_dim, initial_state=states, return_seq=False, reuse=False, scope='decoder_lstm')

# 暂时先将解码器的第一个输出存到decoder_output_sequence_list中供最后一起输出
decoder_output_sequence_single = tf.pack([decoder_output_tensor], axis=1)
decoder_output_sequence_list = [decoder_output_tensor]

# 接下来我们循环max_seq_len-1次，不断取decoder_inputs的一个个词向量作为下一轮解码器输入，并将结果添加到decoder_output_sequence_list中，这里面的reuse=True, scope='decoder_lstm'说明和上面第一次解码用的是同一个lstm层
for i in range(self.max_seq_len-1):
next_dec_input = tf.slice(decoder_inputs, [0, i+1, 0], [-1, 1, self.word_vec_dim])
decoder_output_tensor = tflearn.lstm(next_dec_input, self.word_vec_dim, return_seq=False, reuse=True, scope='decoder_lstm')
decoder_output_sequence_single = tf.pack([decoder_output_tensor], axis=1)
decoder_output_sequence_list.append(decoder_output_tensor)

# 下面我们把编码器第一个输出和解码器所有输出拼接起来，作为tflearn.regression回归的输入
decoder_output_sequence = tf.pack(decoder_output_sequence_list, axis=1)
real_output_sequence = tf.concat(1, [encoder_output_sequence, decoder_output_sequence])
net = tflearn.regression(real_output_sequence, optimizer='sgd', learning_rate=0.1, loss='mean_square')
model = tflearn.DNN(net)
``````

``````1）训练输入X、Y分别是编码器解码器输入和预测输出；
2）X切分两半，前一半是编码器输入，后一半是解码器输入；
3）编码解码器输出预测值用Y做回归训练
4）训练通过样本真实值作解码器输入，实际预测不会有WXYZ部分，上一时序输出将作下一时序输入
``````

``````model = self.model()
model.fit(trainXY, trainY, n_epoch=1000, snapshot_epoch=False, batch_size=1)
``````

trainXY和trainY通过加载语料赋值。

``````def init_seq(input_file):
"""读取切好词的文本文件，加载全部词序列
"""
file_object = open(input_file, 'r')
vocab_dict = {}
while True:
question_seq = []
if line:
line_pair = line.split('|')
line_question = line_pair[0]
for word in line_question.decode('utf-8').split(' '):
if word_vector_dict.has_key(word):
question_seq.append(word_vector_dict[word])
if word_vector_dict.has_key(word):
else:
break
question_seqs.append(question_seq)
file_object.close()
``````

``````    def generate_trainig_data(self):
xy_data = []
y_data = []
for i in range(len(question_seqs)):
question_seq = question_seqs[i]
if len(question_seq) < self.max_seq_len and len(answer_seq) < self.max_seq_len:
sequence_xy = [np.zeros(self.word_vec_dim)] * (self.max_seq_len-len(question_seq)) + list(reversed(question_seq))
sequence_xy = sequence_xy + sequence_y
sequence_y = [np.ones(self.word_vec_dim)] + sequence_y
xy_data.append(sequence_xy)
y_data.append(sequence_y)
return np.array(xy_data), np.array(y_data)
``````

``````python my_seq2seq_v2.py train
``````

``````predict = model.predict(testXY)
``````

``````for i in range(self.max_seq_len-1):
# next_dec_input = tf.slice(decoder_inputs, [0, i+1, 0], [-1, 1, self.word_vec_dim])这里改成下面这句
next_dec_input = decoder_output_sequence_single
decoder_output_tensor = tflearn.lstm(next_dec_input, self.word_vec_dim, return_seq=False, reuse=True, scope='decoder_lstm')
decoder_output_sequence_single = tf.pack([decoder_output_tensor], axis=1)
decoder_output_sequence_list.append(decoder_output_tensor)
``````

``````def vector2word(vector):
max_cos = -10000
match_word = ''
for word in word_vector_dict:
v = word_vector_dict[word]
cosine = vector_cosine(vector, v)
if cosine > max_cos:
max_cos = cosine
match_word = word
return (match_word, max_cos)
``````

``````def vector_cosine(v1, v2):
if len(v1) != len(v2):
sys.exit(1)
sqrtlen1 = vector_sqrtlen(v1)
sqrtlen2 = vector_sqrtlen(v2)
value = 0
for item1, item2 in zip(v1, v2):
value += item1 * item2
return value / (sqrtlen1*sqrtlen2)

def vector_sqrtlen(vector):
len = 0
for item in vector:
len += item * item
len = math.sqrt(len)
return len
``````

``````python my_seq2seq_v2.py test test.data
``````

max_seq_len定长8，输出序列最后会多余一些字，根据余弦相似度或者其他指标设定一个阈值截断。

《Python 自然语言处理》
《NLTK基础教程 用NLTK和Python库构建机器学习应用》
http://www.shareditor.com/blogshow?blogId=119
http://www.shareditor.com/blogshow?blogId=120
http://www.shareditor.com/blogshow?blogId=121