tf.name_scope()将图的不同部分分成不同域,每个层都是在一个唯一的tf.name_scope()下创建,作为在该作用域内创建的元素的前缀,x的独特名字将会是‘inputs/x-input’,这里将输入数据x和y定义在inputs域下,分别命名为“x_input”和“y_put”,方便后面使用。
每条输入数据是有20个元素的一个向量,并且有一个对应的标签(1表示男性,0表示女性)。如果将所有的训练数据构成矩阵,那么就可以一次性完成计算。所以上面定义x和y为二维张量:x的维度是[None, 20],y的维度是[None, 1]。None表示第一个维度未知。实验中的训练集中有2217条样本,测试集有951条样本。
导入训练数据后,下面开始定义分类器参数(parameters):
with tf.name_scope("model"):
W = tf.Variable(tf.zeros([num_inputs, num_classes]), name="W")
b = tf.Variable(tf.zeros([num_classes]), name="b")
张量w是权重矩阵(一个20×1的矩阵),b是偏置。W和b被声明为TensorFlow的变量(variables),会在反向传播的过程中被更新。
下面声明逻辑斯蒂回归分类器的核心公式:
y_pred = tf.sigmoid(tf.matmul(x, W) + b)
这里将x和w相乘再加上b,然后输入sigmod函数中,得到预测值y_pred,表示x中音频数据是男性声音的概率。
Note:实际上,这行代码现在还没有计算任何东西,目前只是在构建计算图。这行代码将矩阵乘法和加法的节点,以及sigmod函数(tf.sigmoid)加入图中。当计算图构建完成时,创建一个TensorFlow会话(session),就可以测试真实数据了。
为了训练模型还需要定义一个损失函数(loss function),对于二值逻辑斯蒂回归分类器,TensorFlow已经内置了log_loss函数:
with tf.name_scope("loss-function"):
loss = tf.losses.log_loss(labels=y, predictions=y_pred)
loss += regularization * tf.nn.l2_loss(W)
log_loss节点接收样本数据的真实标签y作为输入,与预测值y_pred比较,比较的结果代表损失值(loss)。第一次训练时,在所有的样本上预测值y_pred都会是0.5,因为分类器现在并不知道真实答案。初始损失值为-ln(0.5),即0.693146,。随着不断训练,损失值会变得越来越小。
上面第三行代码加入了L2正则化项防止过拟合。正则项系数regularization 定义在另一个placeholder中:
with tf.name_scope("hyperparameters"):
regularization = tf.placeholder(tf.float32, name="regularization")
learning_rate = tf.placeholder(tf.float32, name="learning-rate")
前面我们使用了placeholder来定义输入x和y,这里又定义了超参(hyperparameters)。这些参数不像权重w和偏置b能够通过模型学习得到,你只能根据经验来设置。另一超参learning-rate定义了步长。
optimizer 进行反向传播运算:以loss作为输入,决定如何更新权重和偏置。TensorFlow中各种优化类提供了为损失函数计算梯度的方法,这里我们选用AdamOptimizer:
with tf.name_scope("train"):
optimizer = tf.train.AdamOptimizer(learning_rate)
train_op = optimizer.minimize(loss)
这里添加了操作节点train_op,用于最小化loss,后面会运行这个节点来训练分类器。在训练过程中,我们使用快照技术与准确率确定分类器效果。定义一个计算预测结果准确率的图节点accuracy:
with tf.name_scope("score"):
correct_prediction = tf.equal(tf.to_float(y_pred > 0.5), y)
accuracy = tf.reduce_mean(tf.to_float(correct_prediction), name="accuracy")
之前有说过y_pred是0到1之间的概率。通过tf.to_float(y_pred > 0.5),如果预测是女性,返回0;如果是男性,就返回1。通过tf.equal方法可以比较预测结果y_pred与实际结果y是否相等,返回布尔值。先把布尔值转换成浮点数,tf.reduce_mean()计算均值,最后的结果就是准确率。后面在测试集上也会使用这个accuracy节点确定分类器的真实效果。
对于没有标签的新数据,定义inference节点进行预测:
with tf.name_scope("inference"):
inference = tf.to_float(y_pred > 0.5, name="inference")
训练分类器
这个简单的逻辑斯蒂分类器可能很快就能训练好,但一个深度神经网络可能就需要数小时甚至几天才能达到足够好的准确率。下面是train.py的第一部分:
with tf.Session() as sess:
tf.train.write_graph(sess.graph_def, checkpoint_dir, "graph.pb", False)
sess.run(init)
step = 0
while True:
# here comes the training code
我们创建了一个session对象来运行图。调用sess.run(init)将w和b置为0。同时,将图保存在/tmp/voice/graph.pb文件。后面测试分类器在测试集上的效果以及将分类器用在IOS app上都需要用到这个图。
在while True:循环内,操作如下:
perm = np.arange(len(X_train))
np.random.shuffle(perm)
X_train = X_train[perm]
y_train = y_train[perm]
在每次进行训练时,将训练集中的数据随机打乱,避免让分类器根据样本的顺序来进行预测。下面session将会运行train_op节点,进行一次训练:
feed = {x: X_train, y: y_train, learning_rate: 1e-2,
regularization: 1e-5}
sess.run(train_op, feed_dict=feed)
通过sess.run()函数传入feed_dict参数,给使用placeholder中的张量赋值,启动运算过程。
本文所采用的是个简单分类器,每次都采用完整训练集进行训练,所以将x_train数组放入x中,将y_train数组放入y中。如果数据非常多,每次迭代就应该使用一小批数据(100到1000个样本)进行训练。
train_op节点会运行很多次,反向传播机制每次都会对权重w和偏置b进行微调,随着迭代次数增多,w和b就会逐渐达到最优值。为了帮助理解训练过程,在每迭代1000次时,运行accuracy和loss节点,输出相关信息:
if step % print_every == 0:
train_accuracy, loss_value = sess.run([accuracy, loss],
feed_dict=feed)
print("step: %4d, loss: %.4f, training accuracy: %.4f" % \
(step, loss_value, train_accuracy))
注意的是,在训练集上高准确率并不意味着在测试集上也能表现良好,但是这个值应该随着训练过程逐渐上升,loss值不断减小。
然后定义可以用来后续恢复模型以进一步训练或评估的检查点(checkpoint)文件。分类器目前学习到的w和b被保存到/tmp/voice/目录下:
然后运行train.py,得到如下结果:
![cd986a1477455de8076d0d1aeb70254dd301bc6a]()
当你发现loss不再下降,当下一个*** SAVED MODEL ***消息出现,这个时候你就可以按 Ctrl+C停止训练。
我选用learning_rate = 1e-2, regularization = 1e-5,在训练集上能够达到97%准确率和0.157左右的损失值。如果feed中regularization = 0,loss值会更低。
分类器效果
分类器训练好之后,就可以在测试数据上检验分类器的实际效果。我们创建一个新的脚本test.py,载入计算图和测试集,然后计算预测准确率。
Note: 测试集上的准确率会比训练集中的准确率(97%)低,但是不应该太低。如果你的训练器出现过拟合,那就需要重新调整训练过程了。
还是先导入包,然后载入测试数据:
import numpy as np
import tensorflow as tf
from sklearn import metrics
X_test = np.load("X_test.npy")
y_test = np.load("y_test.npy")
由于现在只是验证分类器的效果,所以并不需要整个图,只需要train_op 和 loss节点。之前已经将计算图保存到graph.pb文件,所以这里只需要载入就可以了:
with tf.Session() as sess:
graph_file = os.path.join(checkpoint_dir, "graph.pb")
with tf.gfile.FastGFile(graph_file, "rb") as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
tf.import_graph_def(graph_def, name="")
TensorFlow推荐使用*.Pb保存数据,所以这里只需要一些辅助代码就可以载入这个文件,并导入会话(session)中。再从检查点文件中载入w和b的值:
W = sess.graph.get_tensor_by_name("model/W:0")
b = sess.graph.get_tensor_by_name("model/b:0")
checkpoint_file = os.path.join(checkpoint_dir, "model")
saver = tf.train.Saver([W, b])
saver.restore(sess, checkpoint_file)
我们将节点都放在域(scope)中并命名,就可以使用get_tensor_by_name()轻易找到。如果你没有给他们一个明确的命名,那么你只能在整个图中寻找TensorFlow默认名称,这将会很麻烦。还需要引用其他的节点,尤其是输入x和y以及进行预测的节点:
x = sess.graph.get_tensor_by_name("inputs/x-input:0")
y = sess.graph.get_tensor_by_name("inputs/y-input:0")
accuracy = sess.graph.get_tensor_by_name("score/accuracy:0")
inference = sess.graph.get_tensor_by_name("inference/inference:0")
现在就可以对测试集中的数据进行预测:
feed = {x: X_test, y: y_test}
print("Test set accuracy:", sess.run(accuracy, feed_dict=feed))
使用scikit-learn输出一些其他的信息:
predictions = sess.run(inference, feed_dict={x: X_test})
print("Classification report:")
print(metrics.classification_report(y_test.ravel(), predictions))
print("Confusion matrix:")
print(metrics.confusion_matrix(y_test.ravel(), predictions))
在终端运行test.py,结果如下:
![c0e53e671f410747aa1456070d9654b10f8d994f]()
如上图所示,在测试集上的准确率达到了96%,比训练集上的准确率略低。这意味着训练出来的分类器对未知数据也能准确分类。分类结果报告(Classification report)和混淆矩阵( confusion matrix)说明有些样本是预测错误的。混肴矩阵说明女性样本中446个预测正确,28个预测错误。男性样本中466个预测正确,11个预测错误。这说明分类器在预测女性声音时会出现更多错误。
在下一节中,我们将会介绍如何将这个分类器运用到实际的app中。
以上为译文
本文由北邮@爱可可-爱生活 老师推荐,阿里云云栖社区组织翻译。
文章原标题《Getting started with TensorFlow on iOS》,由Matthijs Hollemans发布。
译者:李烽 ;审校:董昭男
文章为简译,更为详细的内容,请查看原文。中文译制文档见附件。