PyTorch iOS

小武

Xcode Reset

1
default delete com.apple.Xcode

UI

CocoaPods

1
2
pod init
cat Podfile
1
2
3
4
5
6
7
8
9
10
11
# Uncomment the next line to define a global platform for your project
platform :ios, '12.0'

target 'PyTorch_iOS' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!

# Pods for PyTorch_iOS
pod 'LibTorch', '~> 1.4.0'

end

model

trace_model.py

1
2
3
4
5
6
7
8
import torch
import torchvision

model = torchvision.models.mobilenet_v2(pretrained=True)
model.eval()
example = torch.rand(1, 3, 224, 224)
traced_script_module = torch.jit.trace(model, example)
traced_script_module.save("model.pt")

模型

1
2
3
wget -c https://download.pytorch.org/models/mobilenet_v2-b0353104.pth
cp mobilenet_v2-b0353104.pth /Users/iosdevlog/.cache/torch/checkpoints/mobilenet_v2-b0353104.pth
python trace_model.py

拖动生成的 model.ptiOS 项目。

拍照/相册

Info.plist

1
2
3
4
<key>NSCameraUsageDescription</key>
<string>Camera Usage Description</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Usage Description</string>

拍照/相册选择图片

官文教程

要在 iOS 上开始使用 PyTorch,我们建议您浏览以下HelloWorld

HELLO WORLD示例快速入门

HelloWorld是一个简单的图像分类应用程序,演示了如何在iOS上使用PyTorch C ++库。该代码用Swift编写,并使用Objective-C作为桥梁。

模型准备

让我们从模型准备开始。如果您熟悉PyTorch,您可能应该已经知道如何训练和保存模型。如果您没有,我们将使用预先训练的图像分类模型-MobileNet v2,该模型已经包装在TorchVision中。要安装它,请运行以下命令。

我们强烈建议您遵循Pytorch Github页面在本地计算机上设置Python开发环境。

1
pip install torchvision

成功安装TorchVision后,让我们导航到HelloWorld文件夹并运行trace_model.py。该脚本包含跟踪和保存可在移动设备上运行的Torchscript模型的代码。

1
python trace_model.py

如果一切正常,我们应该model.ptHelloWorld文件夹中生成模型。现在将模型文件复制到我们的应用程序文件夹中HelloWorld/model

要了解有关TorchScript的更多详细信息,请访问pytorch.org上的教程。

通过Cocoapods安装LibTorch

PyTorch C++库在Cocoapods中可用,可以将其集成到我们的项目中,只需运行即可

1
pod install

现在是时候HelloWorld.xcworkspace在XCode中打开,选择一个iOS模拟器并启动它(cmd + R)。如果一切正常,我们应该在模拟器屏幕上看到狼的图片以及预测结果。

我已经加了一张新图片。

iOS 模拟器

代码演练

在这一部分中,我们将逐步介绍代码。

图片载入

让我们从图像加载开始。

1
2
3
4
5
6
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
}

我们首先从包中加载图像,然后将其调整为224x224。然后,我们将此normalized()类别方法称为归一化像素缓冲区。让我们仔细看看下面的代码。

1
2
3
4
5
6
7
8
var normalizedBuffer: [Float32] = [Float32](repeating: 0, count: w * h * 3)
// normalize the pixel buffer
// see https://pytorch.org/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 x H x W)的3通道RGB图像,其中H和W至少应为224。图像必须加载到的范围内[0, 1],然后使用mean = [0.485, 0.456, 0.406]和进行归一化std = [0.229, 0.224, 0.225]

TorchScript模块

现在我们已经对输入数据进行了预处理,并且有了预先训练的TorchScript模型,下一步就是使用它们来运行谓词。为此,我们首先将模型加载到应用程序中。

1
2
3
4
5
6
7
8
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该类是的Objective-C包装器torch::jit::script::Module

1
torch::jit::script::Module module = torch::jit::load(filePath.UTF8String);

由于Swift无法直接与C ++对话,因此我们必须使用Objective-C类作为桥梁,或者为C ++库创建C包装器。出于演示目的,我们将把所有内容包装在这个Objective-C类中。但是,我们正在努力为PyTorch提供Swift / Objective-C API包装器。敬请关注!

运行推断

现在该进行推断并获取结果了。

1
2
3
guard let outputs = module.predict(image: UnsafeMutableRawPointer(&pixelBuffer)) else {
return
}

同样,该predict方法只是一个Objective-C包装器。在后台,它调用C ++ forward函数。让我们看一下它是如何实现的。

1
2
3
4
at::Tensor tensor = torch::from_blob(imageBuffer, {1, 3, 224, 224}, at::kFloat);
torch::autograd::AutoGradMode guard(false);
auto outputTensor = _impl.forward({tensor}).toTensor();
float* floatBuffer = outputTensor.data_ptr<float>();

C ++函数torch::from_blob将从像素缓冲区创建输入张量。请注意,张量的形状{1,3,224,224}代表NxCxWxH我们在上一节中讨论的形状。

1
2
torch::autograd::AutoGradMode guard(false);
at::AutoNonVariableTypeMode non_var_type_mode(true);

以上两行告诉PyTorch引擎仅进行推断。这是因为默认情况下,PyTorch内置了对进行自动分化的支持,这也称为autograd。由于我们不进行手机培训,因此我们可以禁用自动毕业模式。

最后,我们可以调用此forward函数以获取输出张量并将其转换为float缓冲区。

1
2
auto outputTensor = _impl.forward({tensor}).toTensor();
float* floatBuffer = outputTensor.data_ptr<float>();

收集结果

输出张量是形状为1x1000的一维浮点数组,其中每个值表示从图像预测标签的置信度。下面的代码对数组进行排序,并检索前三个结果。

1
2
let zippedResults = zip(labels.indices, outputs)
let sortedResults = zippedResults.sorted { $0.1.floatValue > $1.1.floatValue }.prefix(3)

PyTorch演示应用

对于更复杂的用例,我们建议您检查PyTorch演示应用程序。该演示应用程序包含两个展示柜。一个运行量化模型的相机应用程序,可以实时预测来自设备后置相机的图像。还有一个基于文本的应用程序,它使用文本分类模型来根据输入字符串预测主题。

从源代码构建PYTORCH IOS库

要跟踪iOS的最新更新,您可以从源代码构建PyTorch iOS库。

1
2
3
4
5
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

打开终端并导航到PyTorch根目录。运行以下命令

1
BUILD_PYTORCH_MOBILE=1 IOS_PLATFORM=SIMULATOR ./scripts/build_ios.sh

构建成功后,所有静态库和头文件将在 build_ios/install

为arm64设备构建LibTorch

打开终端并导航到PyTorch根目录。运行以下命令

1
BUILD_PYTORCH_MOBILE=1 IOS_ARCH=arm64 ./scripts/build_ios.sh

构建成功后,所有静态库和头文件将在 build_ios/install

XCode设置

在XCode中打开您的项目,将所有静态库以及头文件复制到您的项目中。导航到项目设置,将“ Header Search Paths ”值设置为刚复制的头文件的路径。

在构建设置中,搜索其他链接器标志。在下面添加自定义链接器标志

1
-force_load $(PROJECT_DIR)/${path-to-libtorch.a}

最后,通过选择“构建设置”,搜索“ 启用位码”,然后将值设置为No,为目标禁用位码。

API文件

当前,iOS框架直接使用Pytorch C ++前端API。可以在这里找到C ++文档。要了解更多信息,我们建议在PyTorch网页上浏览C ++前端教程。同时,我们正在努力为PyTorch提供Swift / Objective-C API包装器。

定制版

从1.4.0开始,PyTorch支持自定义构建。现在,您可以构建PyTorch库,其中仅包含模型所需的运算符。为此,请按照以下步骤操作

1.确认您的PyTorch版本为1.4.0或更高版本。您可以通过检查的值来实现torch.__version__

2.要转储模型中的运算符,请说MobileNetV2运行以下几行Python代码:

1
2
3
4
5
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构建为例,命令应为:

1
SELECTED_OP_LIST=MobileNetV2.yaml BUILD_PYTORCH_MOBILE=1 IOS_ARCH=arm64 ./scripts/build_ios.sh

4.构建成功后,您可以按照上面的XCode Setup部分将结果库集成到项目中。

5.最后一步是在运行之前添加一行C ++代码forward。这是因为默认情况下,JIT将对运算符进行一些优化(例如,融合),这可能会破坏我们从模型中转储的操作的一致性。

1
torch::jit::GraphOptimizerEnabledGuard guard(false);

问题与贡献

如果您有任何疑问或想为PyTorch做出贡献,请随时提出问题或打开请求请求以取得联系。

PyTorch iOS 官方:

https://github.com/pytorch/ios-demo-app

带拍照和相册的源码:

https://github.com/Game2020/PyTorch_iOS