大多数浏览器和
Developer App 均支持流媒体播放。
-
探索机器学习开发体验
了解如何将基于机器学习 (ML) 的一流体验融入到您的 App。我们将介绍模型发现、转换和训练,并提供有关 ML 的技巧和最佳实践。我们还将分享在开始您的 ML 旅程时应考虑的注意事项,演示评估模型性能的技术,以及探索如何对模型进行微调,以便在设备上获得出色的实时性能。要进一步了解这个讲座所讨论的技术,请观看 WWDC22 的“优化您的 Core ML 使用情况”和“利用 Metal 为机器学习加速”。
资源
- Core ML
- Core ML Converters
- Core ML Tools PyTorch Conversion Documentation
- Integrating a Core ML Model into Your App
相关视频
WWDC23
WWDC22
Tech Talks
WWDC20
-
下载
♪ ♪
Geppy Parziale: 大家好 我是 Geppy Parziale 我是 Apple 的一名机器学习工程师 今天 我想带大家来一趟 构建 App 之旅 运用机器学习来解决 通常需要专家才能完成的工作
这段旅程让我有机会向您展示 如何将开源机器学习模型 添加到您的 App 中 并创造奇妙的新体验 在这一过程中 我也会重点介绍 Apple 开发生态中的一些工具 框架和 API 方便您用机器学习来构建 App
在构建 App 时 您作为开发者会做一系列决定 希望能为您的用户提供最佳体验 在添加机器学习功能到 App 时 也是如此
在开发过程中 您或许会问 我应该使用机器学习来 构建这个功能吗 如何获得机器学习模型 我该如何让那个模型 与 Apple 平台相兼容 这个模型是否适合我的用户场景 它可以在 Apple 神经网络引擎上运行吗 让我们一起踏上这段旅程吧 我想构建一个 给我家庭黑白照片 添加逼真色彩的 App 这些照片 是我在地下室的一个旧盒子里发现的
当然 专业摄影师可能要通过 辛苦的劳动 花一些时间用照片编辑工具做到这一点 但是 如果我想自动化这个过程 那么如何在几秒钟内完成着色呢 对于机器学习来说 这似乎是一个完美的任务
Apple 提供了数量巨大的框架和工具 可以帮助您在 App 内 构建和集成 ML 功能 从数据处理到模型训练和推理 都可以提供 这一次 我会对其中一些进行使用 但是要记住 根据您正在进行的 具体的机器学习 您有很多种选择 我在我的 App 中 开发机器学习功能时 经历了一系列阶段
首先 我在技术论文 或专门的网站 找到正确的机器学习模型
我搜索了照片着色 并找到了一个名为 Colorizer 的模型 可能适用于我的需求 通过这个模型 我可以获得这样的着色效果
这是另一个
这是另一个 非常好 我来给您演示一下它是如何运行的 着色器模型期望输入黑白图像 我找到的 Python 源代码可以将任何 RGB 图像转换为 LAB 颜色空间图像
这个颜色空间有三个通道 一个通道代表图像亮度或简称 L 通道 而另外两个通道代表颜色组成 把亮度作为着色器模型的输入 丢弃两个颜色通道
模型会预测出两个新的颜色通道 与输入的 L 通道相结合 形成最终的彩色图像
让我们开始制作和我的 App 相兼容的模型 为此 我可以将原始的 PyTorch 模型 通过 coremltools 转换成 Core ML 格式 这是我使用的将 PyTorch 模型 转换为 Core ML 的简单 Python 脚本
首先 我导入 PyTorch 模型结构和权重
然后跟踪导入的模型 最终将 PyTorch 模型 转换成 Core ML 并保存
一旦模型采用 Core ML 格式 我就需要验证一下 转换工作是否成功 我可以直接用 Python 在 coremltools 中验证 这很简单 我在 RGB 颜色空间中导入图像 并将其转换为 Lab 颜色空间
单独取出亮度通道 把颜色通道丢弃
使用 Core ML 模型进行预测
最后用预测的颜色通道 和输入的亮度通道 组合成 LAB 图像再转换为 RGB
这让我可以验证 转换后的模型功能 与原始 PyTorch 模型的功能是否匹配 我将此阶段称为模型验证 不过 还有一个重要的检验需要完成 我需要了解这个模型 在我的目标设备上运行得是否足够快 所以我需要在设备上评估模型 确保它可以提供 最好的用户体验 在 Xcode 14 中支持 新的 Core ML 性能报告 针对 Core ML 模型 执行一个基于时间的分析 只需要将模型拖放到 Xcode 中 在几秒之内就能创建一个性能报告
使用这个工具 可以看到预估的预测时间 在 M1 和运行 iPadOS 16 的 iPad Pro 上 差不多是 90 毫秒
对我的照片着色 App 很完美 如果您想了解更多关于 性能报告的信息 建议您观看今年的课程 “优化您的 Core ML 使用” 性能报告可以帮助您 对模型进行评估 并确保它提供 最佳的设备端用户体验
确定模型功能和性能后 下面让我将它集成到 App 内
集成过程与之前在 Python 中 所做的是相同的 但这一次 使用 Xcode 和所有您熟悉的其他工具 在 Swift 中无缝完成
记住 这个模型现在是 Core ML 格式 需要一个代表亮度的单通道图像
所以类似于我 之前在 Python 中所做的 我需要将输入的 RGB 图像 转换到 Lab 颜色空间图像
我可以以多种方式实现这个转换 直接在 Swift 中 使用 vImage 或 Metal
深入探索文档 我发现 Core Image 框架 可以提供一些有帮助的东西
所以 让我告诉您如何实现 RGB 到 LAB 的转换 并使用 Core ML 模型进行预测
这是 Swift 代码用来从 RGB 图像中 提取亮度并将其传递给 Core ML 模型 首先 我将 RGB 图像转换成 LAB 并提取亮度
然后 我将亮度转换成 CGImage 并为 Core ML 模型准备输入
最后 进行预测 要从输入的 RGB 图像得到 L 通道 首先使用新的 CIFilter convertRGBtoLab 将 RGB 图像转换成 LAB 图像 亮度值被设定在 0 到 100 之间
然后 我用颜色矩阵和 Lab 图像相乘 来去掉颜色通道 将亮度返回给调用者 现在我们分析一下 在模型输出时发生了什么
Core ML 模型返回了 两个 MLShapedArray 其中包含预测的颜色通道
在预测之后 我将两个 MLShapedArray 转换成 CIImage
最后 将它们 与输入的亮度结合起来 这会生成一个新的 LAB 图像 将它转换为 RGB 并返回
要将两个 MLShapedArray 转换成 CIImage 我首先从每个成型阵列中提取值 然后创建两个 CIImage 代表两个颜色通道 并返回它们 要将亮度通道与预测的颜色通道结合起来 我使用了一个自定义的 CIKernel 它以三个通道作为输入 并返回一个 CIImage
然后 我使用新的 CIFilter convertLabToRGB 将 LAB 图像转换为 RGB 并将其返回给调用者 这是自定义 CIKernel 的源码 用于把亮度通道和预测的两个颜色通道 结合到一个 CIImage 内
关于 RGB 图像和 LAB 图像互相转换 的新的 CIFilter 更多信息请参阅课程 “使用 Core Image Metal 和SwiftUI 显示 EDR 内容”
现在我已经在我的 App 中 完成了这个 ML 功能的整合 让我们看看它的实际效果 但是 等一下 在 App 中如何实时 为我的旧家庭照片着色 我可以花一些时间将它们一一数字化 并将它们导入我的 App
但我有一个更好的主意 可以使用 iPad 相机扫描这些照片 并实时给它们着色 我想这会很有趣 而且我有实现这个的所有条件 但首先 我必须解决一个问题
我的模型处理图像需要 90 毫秒 如果我想处理视频 就需要更快一些
为了获得流畅的用户体验 我想让设备的摄像头 运行速度至少在每秒 30 帧
这意味着相机 大约每 30 毫秒就要产生一帧
但是由于模型为单个视频帧着色 需要大约 90 毫秒 所以每次着色都会错过 2 到 3 帧
模型的总预测时间依赖于模型架构 以及使用的计算单元运算 再来看性能报告 我注意到我的模型 共有61个运算 结合使用了神经网络引擎和 CPU
如果我想要更快的预测时间 我需要改变模型 我决定尝试修改模型架构 探索一些可能更快的替代方案 然而 修改架构 意味着需要重新训练网络
Apple 提供了不同的解决方案 让我可以在 Mac 上 直接训练机器学习模型
就我而言 由于原始模型 是用 PyTorch 开发的 我决定使用 Metal 上新的 PyTorch 这样我就可以利用到 由 Apple 芯片提供的 众多硬件加速能力
如果您有兴趣了解 使用 Metal 加速 PyTorch 请查看课程 “使用 Metal 加速机器学习”
修改之后 我们需要回退一步
重新训练之后 我需要将模型重新转成 Core ML 格式 并再次进行验证
这一次 模型集成 只要简单地把旧模型更换成新模型 重新训练了一些候选的替代模型后 经过验证 其中一个可以满足我的需求 这是对应的性能报告 它完全在神经网络引擎上运行 现在的预测时间大约是 16 毫秒 适用于视频
但性能报告 只展示了 App 性能的一方面
在运行 App 后 我立即注意到 着色并不如预期流畅 App 在运行时究竟发生了什么
为了理解这一点 我可以使用 Instruments 中新的 Core ML 模板
分析 Core ML 踪迹的初始部分 加载模型后 我注意到 App 会累积预测 这是出乎意料的 我希望每次只针对一帧做一次预测
放大踪迹 并检查最初的几次预测 我发现在第一个预测完成前 App 请求了第二个 Core ML 预测
在这里 当第二个请求提交给 Core ML 时 神经网络引擎仍在处理第一个请求
同样 第三个预测开始时 同时仍在处理第二个 甚至经过四次预测之后 请求和执行之间的延迟 已经有大约 20 毫秒 不应如此 我需要确保只有完成之前的预测后 才会开始新的预测 以避免这些串联
在解决这个问题的同时 我也发现我不小心将 相机帧速率设置为 60fps 而不是所需的 30fps
确保 App 在上一个预测完成后 才开始预测新的视频帧 并将相机帧率设置为每秒 30 帧 我可以看到 Core ML正确地分派了单个预测 给到 Apple 神经网络引擎 现在 App 运行顺利
所以我们的旅程也到达了终点
我们用我的旧家庭照片 来测试一下 App
这是我在我的地下室发现的黑白照片 这些照片上是我很久以前 在意大利到访过的一些地方
这是在罗马斗兽场拍的一张 很棒的照片
墙壁和天空的颜色是如此逼真
我们来看看这张
这是意大利南部的蒙特堡 非常好
这是我的家乡 格罗塔利 为这些图像添加颜色 触发了很多回忆
请注意我只在照片上进行着色 相机流中的其他场景保持黑白
在这里 我利用的是 Vision 框架中的 矩形检测算法 使用 VNDetectRectangleRequest 我可以分离出场景中的照片 把它作为着色模型的输入
现在让我回顾一下
在这次旅程中 我探索了大量 Apple 提供的框架、API 和工具 让您的 App 可以准备、集成和评估机器学习功能 开始这段旅程时 我定义了一个问题 这个问题 需要一个开源的机器学习模型解决
我找到了一个符合功能条件的模型 并使其与 Apple 平台相兼容 我使用新的性能报告 直接在设备上评估了模型性能 通过您熟悉的工具和框架 我在 App 内集成了模型
我使用 Instruments 中 新的 Core ML 模板优化了模型 借助 Apple 的工具和框架 我可以直接在 Apple 设备和平台上 处理开发的各个阶段 从数据准备 训练 到集成和优化
今天我们只讲了一小部分 您作为开发者 可以通过 Apple 提供的框架和工具实现强大功能 请参考与此相关的其他课程 为您的 App 引入机器学习 带来更多灵感 探索和尝试框架和工具 利用软件和硬件之间 强大的协同能力 来加速您的机器学习功能 并丰富您 App 的用户体验 祝您有一个愉快的 WWDC 再见
-
-
3:06 - Colorization pre-processing
from skimage import color in_lab = color.rgb2lab(in_rgb) in_l = in_lab[:,:,0]
-
3:39 - Colorization post-processing
from skimage import color import numpy as np import torch out_lab = torch.cat((in_l, out_ab), dim=1) out_rgb = color.lab2rgb(out_lab.data.numpy()[0,…].transpose((1,2,0)))
-
3:56 - Convert colorizer model to Core ML
import coremltools as ct import torch import Colorizer torch_model = Colorizer().eval() example_input = torch.rand([1, 1, 256, 256]) traced_model = torch.jit.trace(torch_model, example_input) coreml_model = ct.convert(traced_model, inputs=[ct.TensorType(name="input", shape=example_input.shape)]) coreml_model.save("Colorizer.mlpackage")
-
4:26 - Core ML model verification using Core ML Tools
import coremltools as ct from PIL import Image from skimage import color in_img = Image.open(“image.png").convert("RGB") in_rgb = np.array(in_img) in_lab = color.rgb2lab(in_rgb, channel_axis=2) lab_components = np.split(in_lab, indices_or_sections=3, axis=-1) (in_l, _, _) = [ np.expand_dims(array.transpose((2, 0, 1)).astype(np.float32), 0) for array in lab_components ] out_ab = coreml_model.predict({"input": in_l})[0] out_lab = np.squeeze(np.concatenate([in_l, out_ab], axis=1), axis=0).transpose((1, 2, 0)) out_rgb = color.lab2rgb(out_lab, channel_axis=2).astype(np.uint8) out_img = Image.fromarray(out_rgb)
-
7:11 - Colorization in Swift
import CoreImage import CoreML func colorize(image inputImage: CIImage) throws -> CIImage { let lightness: CIImage = extractLightness(from: inputImage) let modelInput = try ColorizerInput(inputWith: lightness.cgImage!) let modelOutput: ColorizerOutput = try colorizer.prediction(input: modelInput) let (aChannel, bChannel): (CIImage, CIImage) = extractColorChannels(from: modelOutput) let colorizedImage = reconstructRGBImage(l: lightness, a: aChannel, b: bChannel) return colorizedImage }
-
7:41 - Extract lightness from RGB image using Core Image
import CoreImage.CIFilterBuiltins func extractLightness(from inputImage: CIImage) -> CIImage { let rgbToLabFilter = CIFilter.convertRGBtoLab() rgbToLabFilter.inputImage = inputImage rgbToLabFilter.normalize = true let labImage = rgbToLabFilter.outputImage let matrixFilter = CIFilter.colorMatrix() matrixFilter.inputImage = labImage matrixFilter.rVector = CIVector(x: 1, y: 0, z: 0) matrixFilter.gVector = CIVector(x: 1, y: 0, z: 0) matrixFilter.bVector = CIVector(x: 1, y: 0, z: 0) let lightness = matrixFilter.outputImage! return lightness }
-
8:31 - Create two color channel CIImages from model output
func extractColorChannels(from output: ColorizerOutput) -> (CIImage, CIImage) { let outA: [Float] = output.output_aShapedArray.scalars let outB: [Float] = output.output_bShapedArray.scalars let dataA = Data(bytes: outA, count: outA.count * MemoryLayout<Float>.stride) let dataB = Data(bytes: outB, count: outB.count * MemoryLayout<Float>.stride) let outImageA = CIImage(bitmapData: dataA, bytesPerRow: 4 * 256, size: CGSize(width: 256, height: 256), format: CIFormat.Lh, colorSpace: CGColorSpaceCreateDeviceGray()) let outImageB = CIImage(bitmapData: dataB, bytesPerRow: 4 * 256, size: CGSize(width: 256, height: 256), format: CIFormat.Lh, colorSpace: CGColorSpaceCreateDeviceGray()) return (outImageA, outImageB) }
-
8:51 - Reconstruct RGB image from Lab images
func reconstructRGBImage(l lightness: CIImage, a aChannel: CIImage, b bChannel: CIImage) -> CIImage { guard let kernel = try? CIKernel.kernels(withMetalString: source)[0] as? CIColorKernel, let kernelOutputImage = kernel.apply(extent: lightness.extent, arguments: [lightness, aChannel, bChannel]) else { fatalError() } let labToRGBFilter = CIFilter.convertLabToRGBFilter() labToRGBFilter.inputImage = kernelOutputImage labToRGBFilter.normalize = true let rgbImage = labToRGBFilter.outputImage! return rgbImage }
-
9:08 - Custom CIKernel to combine L, a* and b* channels.
let source = """ #include <CoreImage/CoreImage.h> [[stichable]] float4 labCombine(coreimage::sample_t imL, coreimage::sample_t imA, coreimage::sample_t imB) { return float4(imL.r, imA.r, imB.r, imL.a); } """
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。