注意
PyTorch Mobile 已不再积极支持。请了解 ExecuTorch,这是 PyTorch 全新的设备端推理库。您还可以查阅 此页面,了解更多关于如何使用 ExecuTorch 构建 iOS 应用程序的信息。
iOS
要在 iOS 上开始使用 PyTorch,我们建议您探索以下 HelloWorld 示例。
快速入门:Hello World 示例
HelloWorld 是一个简单的图像分类应用程序,演示了如何在 iOS 上使用 PyTorch C++ 库。代码使用 Swift 编写,并使用 Objective-C 作为桥梁。
要求
- XCode 11.0 或更高版本
- iOS 12.0 或更高版本
模型准备
我们从模型准备开始。如果您熟悉 PyTorch,您可能已经知道如何训练和保存模型。如果您不熟悉,我们将使用一个预训练的图像分类模型 - MobileNet v2,它已经打包在 TorchVision 中。要安装它,请运行以下命令。
我们强烈建议按照 PyTorch Github 页面上的说明,在您的本地机器上设置 Python 开发环境。
pip install torchvision
成功安装 TorchVision 后,导航到 HelloWorld 文件夹并运行 trace_model.py
。该脚本包含追踪和保存一个可在移动设备上运行的 TorchScript 模型的代码。
python trace_model.py
如果一切顺利,model.pt
文件将生成并保存到 HelloWorld/HelloWorld/model
文件夹中。
要了解更多关于 TorchScript 的详细信息,请访问 pytorch.org 上的教程
通过 Cocoapods 安装 LibTorch-Lite
PyTorch C++ 库可在 Cocoapods 中获取,要将其集成到我们的项目中,只需运行
pod install
现在,在 XCode 中打开 HelloWorld.xcworkspace
,选择一个 iOS 模拟器并启动它(cmd + R)。如果一切顺利,您将在模拟器屏幕上看到一张狼的图片以及预测结果。
代码详解
在本部分,我们将逐步详解代码。
图像加载
我们从图像加载开始。
let image = UIImage(named: "image.jpg")!
imageView.image = image
let resizedImage = image.resized(to: CGSize(width: 224, height: 224))
guard var pixelBuffer = resizedImage.normalized() else {
return
}
我们首先从 Bundle 中加载图像并将其大小调整为 224x224。然后,我们调用 normalized()
分类方法来归一化像素缓冲区。让我们仔细看看下面的代码。
var normalizedBuffer: [Float32] = [Float32](repeating: 0, count: w * h * 3)
// normalize the pixel buffer
// see https://pytorch.ac.cn/hub/pytorch_vision_resnet/ for more detail
for i in 0 ..< w * h {
normalizedBuffer[i] = (Float32(rawBytes[i * 4 + 0]) / 255.0 - 0.485) / 0.229 // R
normalizedBuffer[w * h + i] = (Float32(rawBytes[i * 4 + 1]) / 255.0 - 0.456) / 0.224 // G
normalizedBuffer[w * h * 2 + i] = (Float32(rawBytes[i * 4 + 2]) / 255.0 - 0.406) / 0.225 // B
}
乍一看代码可能有些奇怪,但一旦我们理解了模型,它就会变得有意义。输入数据是一个 3 通道 RGB 图像,形状为 (3 x H x W),其中 H 和 W 预计至少为 224。图像必须加载到 [0, 1]
的范围内,然后使用 mean = [0.485, 0.456, 0.406]
和 std = [0.229, 0.224, 0.225]
进行归一化。
TorchScript 模块
现在我们已经预处理了输入数据,并且有一个预训练的 TorchScript 模型,下一步是使用它们来运行预测。为此,我们首先将模型加载到应用程序中。
private lazy var module: TorchModule = {
if let filePath = Bundle.main.path(forResource: "model", ofType: "pt"),
let module = TorchModule(fileAtPath: filePath) {
return module
} else {
fatalError("Can't find the model file!")
}
}()
请注意,TorchModule
类是 torch::jit::mobile::Module
的 Objective-C 包装器。
torch::jit::mobile::Module module = torch::jit::_load_for_mobile(filePath.UTF8String);
由于 Swift 不能直接与 C++ 通信,我们必须使用 Objective-C 类作为桥梁,或者为 C++ 库创建一个 C 包装器。为了演示目的,我们将把所有内容包装在这个 Objective-C 类中。
运行推理
现在是运行推理并获取结果的时候了。
guard let outputs = module.predict(image: UnsafeMutableRawPointer(&pixelBuffer)) else {
return
}
同样,predict
方法只是一个 Objective-C 包装器。在底层,它调用 C++ forward
函数。让我们看看它是如何实现的。
at::Tensor tensor = torch::from_blob(imageBuffer, {1, 3, 224, 224}, at::kFloat);
c10::InferenceMode guard;
auto outputTensor = _impl.forward({tensor}).toTensor();
float* floatBuffer = outputTensor.data_ptr<float>();
C++ 函数 torch::from_blob
将从像素缓冲区创建一个输入张量(tensor)。请注意,张量的形状是 {1,3,224,224}
,表示 {N, C, H, W}
,这与我们在上一节讨论的一致。
c10::InferenceMode guard;
上面这行代码告诉 PyTorch 只进行推理。
最后,我们可以调用 forward
函数来获取输出张量,并将其转换为一个 float
缓冲区。
auto outputTensor = _impl.forward({tensor}).toTensor();
float* floatBuffer = outputTensor.data_ptr<float>();
收集结果
输出张量是一个形状为 1x1000 的一维 float 数组,其中每个值代表从图像预测出某个标签的置信度。下面的代码对数组进行排序并检索出前三个结果。
let zippedResults = zip(labels.indices, outputs)
let sortedResults = zippedResults.sorted { $0.1.floatValue > $1.1.floatValue }.prefix(3)
PyTorch 演示应用
对于更复杂的用例,我们建议查看 PyTorch 演示应用。该演示应用包含两个示例。一个相机应用,运行量化模型,实时预测来自设备后置摄像头的图像。以及一个基于文本的应用,使用文本分类模型从输入字符串预测主题。
更多 PyTorch iOS 演示应用
图像分割
图像分割 演示了一个 Python 脚本,用于将 PyTorch DeepLabV3 模型转换为供移动应用使用的格式,以及一个使用该模型进行图像分割的 iOS 应用。
目标检测
目标检测 演示了如何转换流行的 YOLOv5 模型,并在一个 iOS 应用中使用它来检测照片库中的图片、相机拍摄的图片或实时相机中的目标。
神经机器翻译
神经机器翻译 演示了如何转换一个使用 PyTorch NMT 教程中的代码训练的序列到序列神经机器翻译模型,并在一个 iOS 应用中使用该模型进行法语到英语的翻译。
问答
问答 演示了如何转换一个强大的 Transformer QA 模型,并在一个 iOS 应用中使用该模型来回答关于 PyTorch Mobile 等问题。
视觉 Transformer
视觉 Transformer 演示了如何使用 Facebook 最新的视觉 Transformer DeiT 模型进行图像分类,以及如何转换另一个视觉 Transformer 模型并在 iOS 应用中使用它进行手写数字识别。
语音识别
语音识别 演示了如何将 Facebook AI 的 wav2vec 2.0(语音识别领域的领先模型之一)转换为 TorchScript,以及如何在 iOS 应用中使用该脚本化模型执行语音识别。
视频分类
TorchVideo 演示了如何在 iOS 上使用新发布的 PyTorchVideo 中提供的预训练视频分类模型,以查看视频分类结果,这些结果在视频播放时每秒更新,可用于测试视频、照片库中的视频,甚至实时视频。
PyTorch iOS 教程和实战秘籍
iOS 上的图像分割 DeepLabV3
一个关于如何在 iOS 上准备和运行 PyTorch DeepLabV3 图像分割模型的全面分步教程。
PyTorch Mobile 性能实战秘籍
使用 PyTorch 在移动设备上进行性能优化的实战秘籍列表。
模型融合实战秘籍
了解如何在量化前将 PyTorch 模块列表融合成一个单一模块,以减小模型大小。
移动设备量化实战秘籍
了解如何在不损失太多准确性的情况下减小模型大小并使其运行更快。
为移动设备脚本化和优化
了解如何将模型转换为 TorchScipt 并(可选地)针对移动应用进行优化。
iOS 模型准备实战秘籍
了解如何在 iOS 项目中添加模型并使用 PyTorch iOS Pod。
从源码构建 PyTorch iOS 库
要跟踪 iOS 的最新更新,您可以从源代码构建 PyTorch iOS 库。
git clone --recursive https://github.com/pytorch/pytorch
cd pytorch
# if you are updating an existing checkout
git submodule sync
git submodule update --init --recursive
确保您的本地机器上正确安装了
cmake
和 Python。我们建议按照 PyTorch Github 页面上的说明设置 Python 开发环境
为 iOS 模拟器构建 LibTorch-Lite
打开终端并导航到 PyTorch 根目录。运行以下命令(如果您已经为 iOS 设备构建了 LibTorch-Lite(见下文),请先运行 rm -rf build_ios
)
BUILD_PYTORCH_MOBILE=1 IOS_PLATFORM=SIMULATOR ./scripts/build_ios.sh
构建成功后,所有静态库和头文件将生成在 build_ios/install
目录下
为 arm64 设备构建 LibTorch-Lite
打开终端并导航到 PyTorch 根目录。运行以下命令(如果您已经为 iOS 模拟器构建了 LibTorch-Lite,请先运行 rm -rf build_ios
)
BUILD_PYTORCH_MOBILE=1 IOS_ARCH=arm64 ./scripts/build_ios.sh
构建成功后,所有静态库和头文件将生成在 build_ios/install
目录下
XCode 设置
在 XCode 中打开您的项目,前往项目 Target 的 Build Phases
- Link Binaries With Libraries
,点击 + 号并添加位于 build_ios/install/lib
中的所有库文件。导航到项目 Build Settings
,将值 Header Search Paths 设置为 build_ios/install/include
,将 Library Search Paths 设置为 build_ios/install/lib
。
在构建设置中,搜索 Other Linker Flags。添加一个自定义链接器标志如下
-all_load
要在项目中使用自定义构建的库,请将使用 LibTorch-Lite via Cocoapods 时所需的 #import <LibTorch-Lite/LibTorch-Lite.h>
(在 TorchModule.mm
中)替换为以下代码
#include <torch/csrc/jit/mobile/import.h>
#include <torch/csrc/jit/mobile/module.h>
#include <torch/script.h>
最后,通过选择 Build Settings,搜索 Enable Bitcode,并将值设置为 No,来为您的目标禁用 bitcode。
在 CocoaPods 中使用每夜构建的 PyTorch iOS 库
如果您想尝试 PyTorch iOS 中添加的最新功能,您可以在 Podfile
中使用 LibTorch-Lite-Nightly
pod,它包含了每夜构建的库
pod 'LibTorch-Lite-Nightly'
然后运行 pod install
将其添加到您的项目。如果您希望将每夜构建的 pod 更新到最新版本,您可以运行 pod update
获取最新版本。但请注意,您可能需要在最新的 PyTorch 中构建用于移动端的模型——可以使用最新的 PyTorch 代码,或者使用 pip install --pre torch torchvision -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html
等命令快速安装每夜构建版本——以避免在移动设备上运行模型时可能出现的模型版本不匹配错误。
自定义构建
从 1.4.0 版本开始,PyTorch 支持自定义构建。您现在可以构建仅包含模型所需算子的 PyTorch 库。为此,请按照以下步骤操作
1. 验证您的 PyTorch 版本是否为 1.4.0 或更高。您可以通过检查 torch.__version__
的值来完成。
2. 要导出模型中的算子列表,例如 MobileNetV2
,请运行以下 Python 代码
import torch, yaml
model = torch.jit.load('MobileNetV2.pt')
ops = torch.jit.export_opnames(model)
with open('MobileNetV2.yaml', 'w') as output:
yaml.dump(ops, output)
在上面的代码片段中,您首先需要加载 ScriptModule。然后,使用 export_opnames
返回 ScriptModule 及其子模块的算子名称列表。最后,将结果保存在一个 yaml 文件中。
3. 要在本地使用准备好的 yaml 算子列表运行 iOS 构建脚本,请将上一步生成的 yaml 文件作为环境变量 SELECTED_OP_LIST
传入。同时在参数中指定 BUILD_PYTORCH_MOBILE=1
以及平台/架构类型。以 arm64 构建为例,命令应为
SELECTED_OP_LIST=MobileNetV2.yaml BUILD_PYTORCH_MOBILE=1 IOS_ARCH=arm64 ./scripts/build_ios.sh
4. 构建成功后,您可以按照上面 XCode 设置 部分的说明将生成的库集成到您的项目。
5. 最后一步是在运行 forward
之前添加一行 C++ 代码。这是因为 JIT 默认会对算子进行一些优化(例如融合),这可能会破坏与我们从模型导出的算子列表的一致性。
torch::jit::GraphOptimizerEnabledGuard guard(false);
使用 PyTorch JIT 解释器
PyTorch JIT 解释器是 1.9 版本之前的默认解释器(它是 PyTorch 解释器的一个版本,但大小效率不如新版)。它在 1.9 版本中仍然受支持,并可在 CocoaPods 中使用
pod 'LibTorch', '~>1.9.0'
iOS 教程
观看以下 视频,PyTorch 合作工程师 Brad Heintz 将逐步介绍为 iOS 项目设置 PyTorch Runtime 的步骤
相应的代码可以在这里找到。
此外,请查看我们的 移动性能实战秘籍,其中涵盖了如何优化模型以及如何通过基准测试检查优化是否有效。
API 文档
目前,iOS 框架直接使用 PyTorch C++ 前端 API。C++ 文档可在此处找到。要了解更多信息,我们建议查阅 PyTorch 网页上的 C++ 前端教程。
问题与贡献
如果您有任何问题或想为 PyTorch 贡献代码,请随时提交 issue 或发起 pull request 与我们联系。