IOS上的TensorFlow使用C++写的,不过需要你写的C++代码有限,通常,你只需要做下面几件事:
1.从.pb文件中载入计算图和权重;
2.使用图创建会话;
3.将数据放入输入张量;
4.在图上运行一个或多个节点;
5.得到输出张量结果。
在演示的APP中,这些都是写在ViewController.mm中。首先载入图:
- (BOOL)loadGraphFromPath:(NSString *)path
{
auto status = ReadBinaryProto(tensorflow::Env::Default(),
path.fileSystemRepresentation, &graph);
if (!status.ok()) {
NSLog(@"Error reading graph: %s", status.error_message().c_str());
return NO;
}
return YES;
}
Xcode项目包含在 graph.pb上运行freeze_graph 和optimize_for_inference工具得到的inference.pb图。如果你试图载入graph.pb,会报错:
Error adding graph to session: No OpKernel was registered to support Op
'L2Loss' with these attrs. Registered devices: [CPU], Registered kernels:
<no registered kernels>
[[Node: loss-function/L2Loss = L2Loss[T=DT_FLOAT](model/W/read)]]
这个C++ API 支持的操作要比Python
API少。这里他说的是损失函数节点中L2Loss操作在IOS上不支持。这就是为什么我们要使用freeze_graph简化图。
在载入图之后,创建会话:
- (BOOL)createSession
{
tensorflow::SessionOptions options;
auto status = tensorflow::NewSession(options, &session);
if (!status.ok()) {
NSLog(@"Error creating session: %s",
status.error_message().c_str());
return NO;
}
status = session->Create(graph);
if (!status.ok()) {
NSLog(@"Error adding graph to session: %s",
status.error_message().c_str());
return NO;
}
return YES;
}
会话创建好之后,就可以进行预测了。predict:方法需要一个包含20个浮点数的元组,代表声学特征,然后传入图中,该方法如下所示:
- (void)predict:(float *)example {
tensorflow::Tensor x(tensorflow::DT_FLOAT,
tensorflow::TensorShape({ 1, 20 }));
auto input = x.tensor<float, 2>();
for (int i = 0; i < 20; ++i) {
input(0, i) = example[i];
}
首先定义张量x作为输入数据。这个张量维度为{1,
20},因为它一次接收一个样本,每个样本有20个特征。然后从float *数组将数据拷贝至张量中。
接下来运行会话:
std::vector<std::pair<std::string, tensorflow::Tensor>> inputs = {
{"inputs/x-input", x}
};
std::vector<std::string> nodes = {
{"model/y_pred"},
{"inference/inference"}
};
std::vector<tensorflow::Tensor> outputs;
auto status = session->Run(inputs, nodes, {}, &outputs);
if (!status.ok()) {
NSLog(@"Error running model: %s", status.error_message().c_str());
return;
}
运行如下代码:
pred, inf = sess.run([y_pred, inference], feed_dict={x: example})
这条代码看起来并没有Python版的简洁。我们创建了feed字典,运行的节点列表,以及保存结果的向量。最后,打印结果:
auto y_pred = outputs[0].tensor<float, 2>();
NSLog(@"Probability spoken by a male: %f%%", y_pred(0, 0));
auto isMale = outputs[1].tensor<float, 2>();
if (isMale(0, 0)) {
NSLog(@"Prediction: male");
} else {
NSLog(@"Prediction: female");
}
}
本来只需要运行inference节点就可以得到男性/女性的预测结果,但我还想看计算出来的概率,所以后面运行了y_pred节点。
运行app
你可以在iphone模拟器或者设备上运行这个app。在模拟器上,你可能会得到诸如 “The TensorFlow library wasn’t compiled to use SSE4.1
instructions”的消息,但是在设备上则不会报错。
app会做出来两种预测:男性/女性。运行这个app,你会看到下面的输出,它先打印出图中的节点:
Node count: 9
Node 0: Placeholder 'inputs/x-input'
Node 1: Const 'model/W'
Node 2: Const 'model/b'
Node 3: MatMul 'model/MatMul'
Node 4: Add 'model/add'
Node 5: Sigmoid 'model/y_pred'
Node 6: Const 'inference/Greater/y'
Node 7: Greater 'inference/Greater'
Node 8: Cast 'inference/inference'
这个图只包含进行预测的节点,并不需要训练相关的节点。然后就会输出结果:
Probability spoken by a male: 0.970405%
Prediction: male
Probability spoken by a male: 0.005632%
Prediction: female
如果用Python脚本测试同样的数据,会得到相同的答案。
IOS上TensorFlow的优缺点
优点:
1. 一个工具搞定所有事。你可以使用TensorFlow训练模型并进行预测。不需要将计算图移植到其他的API,如BNNS或者Metal。另一方面,你只需要将少量Python代码移植到C++代码;
2.TensorFlow有比BNNS和Metal更多的特性;
3.你可以在模拟器上运行。Metal总是要在设备上运行。
缺点:
1.目前不支持GPU。TensorFlow使用 Accelerate 框架能够发挥CPU向量指令的优势,原始速度比不上Metal;
2.TensorFlow
API使用C++写的,所以你不得不写一些C++代码,并不能直接使用Swift编写。
3.相比于Python API,C++ API有限。这意味着你不能在设备上进行训练,因为不支持反向传播中用到的自动梯度计算。
4.TensorFlow静态库增加了app包大概40MB的空间。通过减少支持操作的数量,可以减少这个额外空间,不过这很麻烦。而且,这还不包括模型的大小。
目前,我个人并不提倡在IOS上使用TensorFlow。优点并没有超过缺点,作为一款有潜力的产品,谁知道未来会怎样呢?
Note: 如果决定在你的IOS app中使用TensorFlow,那你必须知道别人很容易从app安装包中拷贝图的.pb文件窃取你的模型。由于固化的图文件包含模型参数和图定义,反编译简直轻而易举。如果你的模型具有竞争优势,你可能需要做出预案防止你的机密被窃取。
使用Metal在GPU上训练
IOS
app上使用TensorFlow的一个弊端是他是运行在CPU上的。对于数据和模型较小的项目,TensorFlow能够满足我们的需求。但是对于更大的数据集,特别是深度学习,你就必须要使用GPU代替CPU,在IOS上就意味着要使用Metal。
训练后,我们需要将学习到的参数w和b保存成Metal能够读取的格式。其实只要以二进制格式保存为浮点数列表就可以了。
下面的Python脚本export_weights.py和之前载入图定义和检查点的test.py很相似,如下:
W.eval().tofile("W.bin")
b.eval().tofile("b.bin")
W.eval()计算w目前的值,并以返回Numpy数组(和sess.run(W)作用是一样的)。然后使用tofile()将Numpy数组保存为二进制文件。
你可以在源码的VoiceMetal文件夹下发现Xcode项目,使用Swift编写的。
之前我们使用下面的公式计算逻辑斯蒂回归:
y_pred = sigmoid((W * x) + b)
这和神经网络中全连接层进行的计算相同,为了实现Metal版分类器,我们只需要使用MPSCNN Fully Connected 层。首先将W.bin和b.bin载入到Data对象:
let W_url = Bundle.main.url(forResource: "W", withExtension: "bin")
let b_url = Bundle.main.url(forResource: "b", withExtension: "bin")
let W_data = try! Data(contentsOf: W_url!)
let b_data = try! Data(contentsOf: b_url!)
然后创建全连接层:
let sigmoid = MPSCNNNeuronSigmoid(device: device)
let layerDesc = MPSCNNConvolutionDescriptor(
kernelWidth: 1, kernelHeight: 1,
inputFeatureChannels: 20, outputFeatureChannels: 1,
neuronFilter: sigmoid)
W_data.withUnsafeBytes { W in
b_data.withUnsafeBytes { b in
layer = MPSCNNFullyConnected(device: device,
convolutionDescriptor: layerDesc,
kernelWeights: W, biasTerms: b, flags: .none)
}
}
因为输入是20个数字,我设计了作用于一个1x1的有20个输入信道(input channels)的全连接层。预测结果y_pred是一个数字,所以全连接层只有一个输出信道。输入和输出数据放在MPSImage 中:
let inputImgDesc = MPSImageDescriptor(channelFormat: .float16,
width: 1, height: 1, featureChannels: 20)
let outputImgDesc = MPSImageDescriptor(channelFormat: .float16,
width: 1, height: 1, featureChannels: 1)
inputImage = MPSImage(device: device, imageDescriptor: inputImgDesc)
outputImage = MPSImage(device: device, imageDescriptor: outputImgDesc)
和app上的TensorFlow一样,这里也有一个predict 方法,这个方法以组成一条样本的20个浮点数作为输入。下面是完整的方法:
func predict(example: [Float]) {
convert(example: example, to: inputImage)
let commandBuffer = commandQueue.makeCommandBuffer()
layer.encode(commandBuffer: commandBuffer, sourceImage: inputImage,
destinationImage: outputImage)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
let y_pred = outputImage.toFloatArray()
print("Probability spoken by a male: \(y_pred[0])%")
if y_pred[0] > 0.5 {
print("Prediction: male")
} else {
print("Prediction: female")
}
}
和运行session的结果是一样的。convert(example:to:)和toFloatArray()方法加载和输出MPSImage 对象的辅助函数。
你需要在设备上运行这个app,因为模拟器不支持Metal。输出结果如下:
Probability spoken by a male: 0.970215%
Prediction: male
Probability spoken by a male: 0.00568771%
Prediction: female
注意到这些概率和用TensorFlow预测到的概率不完全相同,这是因为Metal使用16位浮点数,但结果相当接近。
版权许可
本文所用的数据集是 Kory Becker制作的,在 Kaggle.com下载,也参考了Kory的博文和源码。其他人也写过IOS上TensorFlow相关的一些东西。从这些文章和代码中我受益匪浅:
1.Getting Started
with Deep MNIST and TensorFlow on iOS by Matt
Rajca
2.Speeding Up
TensorFlow with Metal Performance Shaders also by Matt
Rajca
3.tensorflow-cocoa-example by
Aaron Hillegass
4.TensorFlow iOS
Examples in the TensorFlow repository
以上为译文
本文由北邮@爱可可-爱生活 老师推荐,阿里云云栖社区组织翻译。
文章原标题《Getting
started with TensorFlow on iOS》,由Matthijs Hollemans发布。
译者:李烽 ;审校:董昭男
文章为简译,更为详细的内容,请查看原文。中文译制文档见附件。