借助当今最流行的机器学习框架之一TensorFlow,您可以轻松创建和训练深度模型(通常也称为深度前馈神经网络),以解决各种复杂问题,例如图像分类、目标检测和自然语言理解。TensorFlow Mobile是一个旨在帮助您在移动应用程序中利用这些模型的库。
在本教程中,我将向您展示如何在 android Studio 项目中使用 TensorFlow Mobile。
先决条件
为了能够遵循本教程,您需要:
Android Studio 3.0 或更高版本
TensorFlow 1.5.0 或更高版本
运行api级别 21 或更高级别的 Android 设备
以及对 TensorFlow 框架的基本了解
1.创建模型
在我们开始使用 TensorFlow Mobile 之前,我们需要一个经过训练的 TensorFlow 模型。现在让我们创建一个。
我们的模型将非常基础。它的行为就像一个异或门,接受两个输入,两个输入都可以是零或一,并产生一个输出,如果两个输入相同,则输出为零,否则为一个。此外,因为它将是一个深度模型,它将有两个隐藏层,一个有四个神经元,另一个有三个神经元。您可以自由更改隐藏层的数量和它们包含的神经元数量。
为了使本教程简短,我们将使用TFLearn,而不是直接使用低级 TensorFlow API,这是一个流行的 TensorFlow 包装框架,提供更直观和简洁的 API。如果您还没有它,请使用以下命令将其安装到您的 TensorFlow 虚拟环境中:
pip install tflearn
要开始创建模型,请创建一个名为create_model.py的 python 脚本,最好在一个空目录中,然后使用您喜欢的文本编辑器打开它。
在文件中,我们需要做的第一件事是导入 TFLearn API。
import tflearn
接下来,我们必须创建训练数据。对于我们的简单模型,只有四个可能的输入和输出,类似于 XOR 门的真值表的内容。
X = [ [0, 0], [0, 1], [1, 0], [1, 1] ] Y = [ [0], # Desired output for inputs 0, 0 [1], # Desired output for inputs 0, 1 [1], # Desired output for inputs 1, 0 [0] # Desired output for inputs 1, 1 ]
在为隐藏层中的所有神经元分配初始权重时,使用从均匀分布中挑选的随机值通常是一个好主意。要生成值,请使用该uniform() 方法。
weights = tflearn.initializations.uniform(minval = -1, maxval = 1)
此时,我们可以开始创建神经网络的层。要创建输入层,我们必须使用input_data() 方法,它允许我们指定网络可以接受的输入数量。一旦输入层准备就绪,我们可以 fully_connected() 多次调用该方法以向网络添加更多层。
# Input layer net = tflearn.input_data( shape = [None, 2], name = 'my_input' ) # Hidden layers net = tflearn.fully_connected(net, 4, activation = 'sigmoid', weights_init = weights ) net = tflearn.fully_connected(net, 3, activation = 'sigmoid', weights_init = weights ) # Output layer net = tflearn.fully_connected(net, 1, activation = 'sigmoid', weights_init = weights, name = 'my_output' )
请注意,在上面的代码中,我们为输入和输出层赋予了有意义的名称。这样做很重要,因为我们在通过 Android 应用程序使用网络时需要它们。另请注意,隐藏层和输出层正在使用sigmoid 激活函数。您可以自由尝试其他激活函数,例如 softmax、 tanh和relu。
作为我们网络的最后一层,我们必须使用该 regression() 函数创建一个回归层,它期望一些超参数作为其参数,例如网络的学习率以及它应该使用的优化器和损失函数。以下代码向您展示了如何使用随机梯度下降(简称 SGD)作为优化函数,使用均方作为损失函数:
net = tflearn.regression(net, learning_rate = 2, optimizer = 'sgd', loss = 'mean_square' )
接下来,为了让TFLearn框架知道我们的网络模型实际上是一个深度神经网络模型,我们必须调用DNN() 函数。
model = tflearn.DNN(net)
模型现已准备就绪。我们现在需要做的就是使用我们之前创建的训练数据对其进行训练。因此,调用fit() 模型的方法,并与训练数据一起指定要运行的训练 epoch 数。因为训练数据非常小,我们的模型需要数千个 epoch 才能达到合理的准确度。
model.fit(X, Y, 5000)
训练完成后,我们可以调用predict() 模型的方法来检查它是否正在生成所需的输出。以下代码向您展示了如何检查所有有效输入的输出:
print("1 XOR 0 = %f" % model.predict([[1,0]]).item(0)) print("1 XOR 1 = %f" % model.predict([[1,1]]).item(0)) print("0 XOR 1 = %f" % model.predict([[0,1]]).item(0)) print("0 XOR 0 = %f" % model.predict([[0,0]]).item(0))
如果您现在运行 Python 脚本,您应该会看到如下所示的输出:
请注意,输出永远不会恰好是 0 或 1。相反,它们是接近于零或接近于 1 的浮点数。因此,在使用输出时,您可能希望使用 Python 的round() 函数。
除非我们在训练后明确保存模型,否则脚本一结束我们就会丢失它。幸运的是,使用 TFLearn,对该方法的简单调用可以save() 保存模型。但是,为了能够在 TensorFlow Mobile 中使用保存的模型,在保存它之前,我们必须确保我们删除了 tf.GraphKeys.TRAIN_OPS 集合中存在的所有与训练相关的操作,这些操作与它相关联。以下代码向您展示了如何执行此操作:
# Remove train ops with net.graph.as_default(): del tf.get_collection_ref(tf.GraphKeys.TRAIN_OPS)[:] # Save the model model.save('xor.tflearn')
如果再次运行该脚本,您会看到它生成了一个检查点文件、一个元数据文件、一个索引文件和一个数据文件,所有这些一起使用时可以快速重新创建我们训练好的模型。
2.冻结模型
除了保存模型外,我们还必须先将其冻结,然后才能将其与 TensorFlow Mobile 一起使用。正如您可能已经猜到的那样,冻结模型的过程涉及将其所有变量转换为常量。此外,冻结模型必须是符合 Google Protocol Buffers 序列化格式的单个二进制文件。
创建一个名为freeze_model.py的新 Python 脚本 并使用文本编辑器打开它。我们将编写所有代码以将模型冻结在此文件中。
因为 TFLearn 没有任何冻结模型的功能,所以我们现在必须直接使用 TensorFlow API。通过将以下行添加到文件中来导入它们:
import tensorflow as tf
在整个脚本中,我们将使用单个 TensorFlow 会话。要创建会话,请使用Session 类的构造函数。
with tf.Session() as session: # rest of the code goes here
此时,我们必须Saver 通过调用该 import_meta_graph() 函数并将模型元数据文件的名称传递给它来创建一个对象。除了返回一个Saver 对象外,该import_meta_graph() 函数还会自动将模型的图定义添加到会话的图定义中。
创建保存程序后,我们可以通过调用该方法初始化图形定义中存在的所有变量,该restore() 方法需要包含模型最新检查点文件的目录的路径。
my_saver = tf.train.import_meta_graph('xor.tflearn.meta') my_saver.restore(session, tf.train.latest_checkpoint('.'))
此时,我们可以调用该convert_variables_to_constants() 函数来创建一个冻结图定义,其中模型的所有变量都用常量替换d。作为其输入,该函数需要当前会话、当前会话的图形定义以及包含模型输出层名称的列表。
frozen_graph = tf.graph_util.convert_variables_to_constants( session, session.graph_def, ['my_output/Sigmoid'] )
调用SerializeToString() 冻结图定义的方法为我们提供了模型的二进制 protobuf 表示。通过使用 Python 的基本文件 I/O 工具,我建议您将其保存为名为freeze_model.pb的文件。
with open('frozen_model.pb', 'wb') as f: f.write(frozen_graph.SerializeToString())
您现在可以运行脚本来生成冻结模型。
我们现在拥有开始使用 TensorFlow Mobile 所需的一切。
3. Android Studio 项目设置
TensorFlow Mobile 库在 JCenter 上可用,因此我们可以直接将其作为 implementation 依赖项添加到app 模块的build.gradle 文件中。
implementation 'org.tensorflow:tensorflow-android:1.7.0'
要将冻结模型添加到项目中,请将freeze_model.pb 文件放在项目的资产 文件夹中。
4.初始化 TensorFlow 接口
TensorFlow Mobile 提供了一个简单的界面,我们可以使用它来与我们的冻结模型进行交互。要创建接口,请使用TensorFlowInferenceInterface 类的构造函数,它需要一个AssetManager 实例和冻结模型的文件名。
thread { val tfInterface = TensorFlowInferenceInterface(assets, "frozen_model.pb") // More code here }
在上面的代码中,您可以看到我们正在生成一个新线程。建议这样做,尽管并非总是必要的,以确保应用程序的 UI 保持响应。
为了确保 TensorFlow Mobile 能够正确读取我们的模型文件,现在让我们尝试打印模型图中存在的所有操作的名称。要获得对图的引用,我们可以使用graph() 接口的方法,而要获得所有的操作,可以使用图的operations() 方法。以下代码向您展示了如何:
val graph = tfInterface.graph() graph.operations().forEach { println(it.name()) }
如果您现在运行该应用程序,您应该能够在 Android Studio 的Logcat 窗口中看到十多个操作名称。在所有这些名称中,如果在冻结模型时没有错误,您将能够找到输入和输出层的名称: my_input/X 和my_output/Sigmoid。
5.使用模型
要使用模型进行预测,我们必须将数据放入其输入层并从其输出层检索数据。要将数据放入输入层,请使用feed() 接口的方法,该方法需要层的名称、包含输入的数组以及数组的维度。以下代码向您展示了如何将数字发送 0 到 1 输入层:
tfInterface.feed("my_input/X", floatArrayOf(0f, 1f), 1, 2)
将数据加载到输入层后,我们必须使用该 run() 方法运行推理操作,该方法需要输出层的名称。一旦操作完成,输出层将包含模型的预测。要将预测加载到kotlin数组中,我们可以使用该fetch() 方法。以下代码向您展示了如何执行此操作:
tfInterface.run(arrayOf("my_output/Sigmoid")) val output = floatArrayOf(-1f) tfInterface.fetch("my_output/Sigmoid", output)
你如何使用预测当然取决于你。现在,我建议您简单地打印它。
println("Output is ${output[0]}")
您现在可以运行应用程序来查看模型的预测是否正确。
随意更改您提供给输入层的数字,以确认模型的预测始终正确。
结论
您现在知道如何创建一个简单的 TensorFlow 模型并将其与 Android 应用程序中的 TensorFlow Mobile 一起使用。不过,您不必总是将自己限制在自己的模型中。凭借您今天学到的技能,您应该可以毫无问题地使用 TensorFlow模型动物园中可用的更大模型,例如 MobileNet 和 Inception 。但请注意,此类模型会导致更大的 APK,这可能会给使用低端设备的用户带来问题。