本文翻译整理自:Core ML : https://developer.apple.com/cn/documentation/coreml/
文章目录
- 一、概览
- 二、获取 Core ML 模型
- 三、将 Core ML 模型集成到你的 App 中
- 1、将模型添加到您的Xcode项目
- 2、在代码中创建模型
- 3、获取要传递给模型的输入值
- 4、使用模型进行预测
- 5、构建并运行Core ML应用程序
- 四、将经过训练的模型转换为 Core ML
- 1、使用 Core ML 工具
- 2、转换模型
- 3、或者,自己编写自定义转换工具
- 五、通过计算机视觉和 Core ML 对图像进行分类
- 1、配置示例代码项目
- 2、创建图像分类器实例
- 3、创建图像分类请求
- 4、创建一个请求处理程序
- 5、开始请求
- 6、检索请求的结果
- 7、格式化和呈现预测
- 六、通过计算机视觉和对象检测了解掷骰子
- 1、概览
- 2、配置示例代码项目
- 3、向请求添加输入
- 4、设置视觉请求以处理相机框架
- 5、将相机框架传递给对象检测器以预测骰子位置
- 6、绘制边界框以了解模型的行为
- 7、确定滚动何时结束
- 8、显示骰子值
- 七、在文本文稿中查找问题答案
- 1、概览
- 2、配置示例代码项目
- 3、建立词汇量
- 4、将文本拆分为单词标记
- 5、将单词或词片标记转换为其ID
- 6、准备模型输入
- 7、做个预测
- 8、找到答案吧
- 9、较大文档的缩放
- 八、缩减 Core ML App 的大小
- 1、概览
- 2、转换为较低精度模型
- 3、下载和编译模型
一、概览
利用 Core ML 在 App 中整合机器学习模型。Core ML 为所有模型提供了一种统一的呈现方式。App 可以使用 Core ML API 和用户数据进行预测,以及训练或精调模型,一切都在用户设备上完成。
“模型”是对一组训练数据应用机器学习算法而得到的结果。你使用模型来基于新的输入数据进行预测。有很多依靠编写代码仅能低效甚至很难完成的任务,使用模型能更好地完成。例如,你可以训练模型来归类照片,或者直接根据像素检测照片内的特定对象。
你可以使用 Xcode 内置的 Create ML App 来构建和训练模型。使用 Create ML 训练的模型采用 Core ML 模型格式,并能直接在 App 中使用。另外,也可以使用许多其他机器学习资源库,再使用 Core ML 工具 (英文) 将模型转换成 Core ML 格式。模型下载到用户设备上后,你可以使用 Core ML 在设备端利用用户数据进行重新训练或优化。
Core ML 通过利用 CPU、GPU 和神经网络引擎,同时最大程度地减小内存占用空间和功耗,来优化设备端性能。由于模型严格地在用户设备上,因此无需任何网络连接,这有助于保护用户数据的私密性和 App 的响应速度。
Core ML 是域特定的框架和功能的基础所在。Core ML 支持使用计算机视觉 (英文) 框架分析图像,使用自然语言 (英文) 框架处理文本,使用语音 (英文) 框架将音频转换为文本,以及使用 SoundAnalysis (英文) 来识别音频中的声音。Core ML 本身是基于 Accelerate (英文) 和 BNNS (英文) 等底层语言以及 Metal Performance Shaders (英文) 而构建的。
SDK
- iOS 11.0+
- macOS 10.13+
- Mac Catalyst 13.0+
- Apple tvOS 11.0+
- watchOS 4.0+
二、获取 Core ML 模型
获取要在 App 中使用的 Core ML 模型。
Core ML 支持多种机器学习模型,包括神经网络、树集成、支持向量机和广义线性模型。Core ML 要求使用 Core ML 模型格式 (文件扩展名为 .mlmodel
的模型)。
利用 Create ML 和你自己的数据,你可以训练定制模型来完成某些任务,例如识别图像、提取文本含义或查找数字值之间的关系。使用 Create ML 训练的模型使用 Core ML 模型格式,并能直接在 App 中使用。
Apple 还提供了多个热门的开源模型,这些模型已经是 Core ML 模型格式。你可以下载这些模型并在 App 中开始使用它们。
此外,各种调研团队和大学也发布了他们的模型和训练数据,但这些模型可能不是 Core ML 模型格式。使用 Core ML 工具 (英文) 转换这些模型,以便在你的 App 中使用。
三、将 Core ML 模型集成到你的 App 中
将简单模型添加到应用程序,将输入数据传递给模型,并处理模型的预测。
原文地址(英文):https://developer.apple.com/documentation/coreml/integrating-a-core-ml-model-into-your-app
下载:https://docs-assets.developer.apple.com/published/daeacfd7a9fd/IntegratingACoreMLModelIntoYourApp.zip
iOS12.0+ | iPadOS 12.0+ | Xcode 15.2+
这个示例应用程序使用经过训练的模型MarsHabitatPricer.mlmodel
来预测火星上的栖息地价格。
1、将模型添加到您的Xcode项目
通过将模型拖到项目导航器中来将模型添加到您的Xcode项目中。
您可以通过在Xcode中打开模型来查看有关模型的信息,包括模型类型及其预期的输入和输出。在此示例中,输入是太阳能电池板和温室的数量,以及栖息地的地块大小(以英亩为单位)。输出是栖息地的预测价格。
2、在代码中创建模型
Xcode还使用有关模型的输入和输出的信息来自动生成模型的自定义编程接口,您可以使用该接口与代码中的模型进行交互。对于MarsHabitatPricer.mlmodel
,Xcode生成接口来表示模型(MarsHabitatPricer
)、模型的输入(MarsHabitatPricerInput
)和模型的输出(MarsHabitatPricerOutput
)。
使用生成的MarsHabitatPricer
的初始化器创建模型:
let marsHabitatPricer = try? MarsHabitatPricer(configuration: .init())
3、获取要传递给模型的输入值
此示例应用程序使用UIPickerView
从用户获取模型的输入值:
func selectedRow(for feature: Feature) -> Int {
return pickerView.selectedRow(inComponent: feature.rawValue)
}
let solarPanels = pickerDataSource.value(for: selectedRow(for: .solarPanels), feature: .solarPanels)
let greenhouses = pickerDataSource.value(for: selectedRow(for: .greenhouses), feature: .greenhouses)
let size = pickerDataSource.value(for: selectedRow(for: .size), feature: .size)
4、使用模型进行预测
这个MarsHabitatPricer
有一个生成的prediction(solarPanels:greenhouses:size:)
方法,用于根据模型的输入值预测价格——在本例中是太阳能电池板的数量、温室的数量和栖息地的大小(以英亩为单位)。MarsHabitatPricerOutput
方法的结果是一个实例。
// Use the model to make a price prediction.
let output = try marsHabitatPricer.prediction(solarPanels: solarPanels,
greenhouses: greenhouses,
size: size)
访问marsHabitatPricerOutput
的price
属性以获取预测价格并在应用程序的UI中显示结果。
// Format the price for display in the UI.
let price = output.price
priceLabel.text = priceFormatter.string(for: price)
注:生成的prediction(solarPanels:greenhouses:size:)
方法可能会抛出错误。使用Core ML时最常见的错误类型发生在输入数据的细节与模型期望的细节不匹配时——例如,格式错误的图像。
5、构建并运行Core ML应用程序
Xcode将Core ML模型编译为经过优化以在设备上运行的资源。这种模型的优化表示包含在您的应用程序包中,用于在应用程序在设备上运行时进行预测。
四、将经过训练的模型转换为 Core ML
将使用第三方机器学习工具创建且经过训练的模型转换为 Core ML 模型格式。
使用受支持的第三方机器学习框架创建和训练模型后,你可以使用 Core ML 工具或第三方转换工具 (如 MXNet 转换器 (英文) 或 TensorFlow 转换器 (英文)) 将模型转换为 Core ML 模型格式。否则你将需要创建自己的转换工具。
1、使用 Core ML 工具
Core ML 工具 (英文) 是一个 Python 包,可将各种类型的模型转换为 Core ML 模型格式。下文表 1 中列出了受支持的模型和第三方框架。
表 1 :Core ML 工具支持的模型和第三方框架
模型类型 | 支持的模型 | 支持的框架 |
---|---|---|
神经网络 | 前馈、卷积、循环 | Caffe v1Keras 1.2.2+ |
树集成 | 随机森林、提升树、决策树 | scikit-learn 0.18XGBoost 0.6 |
支持向量机 | 标量回归、多元分类 | scikit-learn 0.18LIBSVM 3.22 |
广义线性模型 | 线性回归、逻辑回归 | scikit-learn 0.18 |
特征工程 | 稀疏向量矢量化、稠密向量矢量化、分类处理 | scikit-learn 0.18 |
管道模型 | 顺序链模型 | scikit-learn 0.18 |
2、转换模型
根据模型所用的第三方框架,你可以使用对应的 Core ML 转换器转换相应模型。调用转换器的 convert 方法,并将得到的模型存储为 Core ML 模型格式 (.mlmodel)。
例如,如果模型是使用 Caffe 创建的,请将 Caffe 模型 (.caffemodel
) 传递给 coremltools.converters.caffe.convert
方法。
import coremltools
coreml_model = coremltools.converters.caffe.convert('my_caffe_model.caffemodel')
然后,将得到的模型存储为 Core ML 模型格式。
coremltools.utils.save_spec(coreml_model, 'my_model.mlmodel')
根据你的模型,你可能需要更新输入、输出和标签,或者可能需要声明图像名称、类型和格式。可用选项因工具而异,因此转换工具内置了更多文档。有关 Core ML 工具的更多信息,请参阅“程序包文档 (英文)”。
3、或者,自己编写自定义转换工具
如果上述所列工具不支持你需要转换的模型格式,你可以创建自己的转换工具。
在编写自己的转换工具时,会涉及到从模型的输入、输出和架构表示方法至 Core ML 模型格式的转换。为此,你需要定义每一层的模型架构以及每一层与其他层的连接。以 Core ML 工具 (英文) 提供的转换工具为例,它们展示了如何将使用第三方框架创建的各种模型类型转换为 Core ML 模型格式。
注释
Core ML 模型格式由一组协议缓冲文件定义,详见“Core ML 模型规范 (英文)”。
五、通过计算机视觉和 Core ML 对图像进行分类
使用Vision框架裁剪和缩放照片,并使用Core ML模型对其进行分类。
下载:https://docs-assets.developer.apple.com/published/f039f0a262/ClassifyingImagesWithVisionAndCoreML.zip
iOS14.0+ | iPadOS 14.0+ | Xcode 13.4+
此示例中的应用程序使用MobileNet识别图像中最突出的对象,这是一种开源图像分类器模型,可识别大约1,000个不同的类别。
每当用户从图库中选择一张照片或用相机拍摄一张照片时,应用程序都会将其传递给Vision图像分类请求。Vision调整照片大小并裁剪照片以满足MobileNet模型对其图像输入的约束,然后在幕后使用[Core ML
)框架将照片传递给模型。一旦模型生成预测,Vision将其转发回应用程序,应用程序将结果呈现给用户。
该示例使用MobileNet作为如何使用第三方Core ML模型的示例。您可以在Core ML模型库上下载开源模型(包括较新版本的MobileNet)。
在集成第三方模型来解决问题之前——这可能会增加应用程序的大小——考虑在软件开发工具包中使用应用程序接口。例如,视觉框架的VNClassifyImageRequest
类提供了与移动网络相同的功能,但性能可能更好,并且不会增加应用程序的大小(请参阅分类图像进行分类和搜索)。
注:您可以使用创建ML创建自定义图像分类器来标识您选择的对象类型。请参阅创建图像分类器模型以了解如何创建可以替换此示例中MobileNet模型的自定义图像分类器。
1、配置示例代码项目
示例目标 iOS14 或更高版本,但项目中的MobileNet模型适用于:
- iOS11岁或更晚
- macOS 10.13或更高版本
要在应用程序中拍照,请在带有相机的设备上运行示例。否则,您可以在模拟器中从库中选择照片。
注:通过将照片拖到其窗口中来将您自己的照片添加到模拟器中的照片库中。
2、创建图像分类器实例
在启动时,ImagePredictor
类通过调用其createImageClassifier()
类型方法来创建图像分类器单例。
/// - Tag: name
static func createImageClassifier() -> VNCoreMLModel {
// Use a default model configuration.
let defaultConfig = MLModelConfiguration()
// Create an instance of the image classifier's wrapper class.
let imageClassifierWrapper = try? MobileNet(configuration: defaultConfig)
guard let imageClassifier = imageClassifierWrapper else {
fatalError("App failed to create an image classifier model instance.")
}
// Get the underlying model instance.
let imageClassifierModel = imageClassifier.model
// Create a Vision instance using the image classifier's model instance.
guard let imageClassifierVisionModel = try? VNCoreMLModel(for: imageClassifierModel) else {
fatalError("App failed to create a `VNCoreMLModel` instance.")
}
return imageClassifierVisionModel
}
该方法通过以下方式为Vision创建Core ML模型实例:
- 创建Xcode在编译时自动生成的模型包装类的实例
- 检索包装类实例的底层[
MLModel
/mlmodel)属性
3. 将模型实例传递给VNCoreMLModel
初始化程序
Image Predictor类通过仅创建它在应用程序中共享的单个实例来最小化运行时。
注:为项目中的每个Core ML模型共享一个VNCoreMLModel
实例。
3、创建图像分类请求
Image Predictor类通过将共享图像分类器模型实例和请求处理程序传递给其初始化程序来创建图像分类请求-VNCoreMLRequest
实例。
// Create an image classification request with an image classifier model.
let imageClassificationRequest = VNCoreMLRequest(model: ImagePredictor.imageClassifier,
completionHandler: visionRequestHandler)
imageClassificationRequest.imageCropAndScaleOption = .centerCrop
该方法告诉Vision如何通过将请求的imageCropAndScaleOption
属性设置为VNImageCropAndScaleOption.centerCrop
来调整不符合模型图像输入约束的图像。
4、创建一个请求处理程序
Image Predictor的makePredictions(for photo, ...)
方法通过将图像及其方向传递给初始化程序来为每个图像创建一个VNImageRequestHandler
。
let handler = VNImageRequestHandler(cgImage: photoImage, orientation: orientation)
视觉基于orientation
旋转图像-一个CGImagePropertyOrientation
实例-然后将图像发送到模型。
如果要分类的图像具有URL,请使用以下初始化程序之一创建Vision图像请求处理程序:
init(url:options:)
init(url:orientation:options:)
5、开始请求
方法makePredictions(for photo, ...)
通过将请求添加到VNRequest
数组来启动请求,并将其传递给处理程序的perform(_:)
方法。
let requests: [VNRequest] = [imageClassificationRequest]
// Start the image classification request.
try handler.perform(requests)
注:可以在同一图像上执行多个Vision请求,方法是将每个请求添加到传递给perform(_:)
方法的requests
参数的数组中。
6、检索请求的结果
当图像分类请求完成时,Vision通过调用请求的完成处理程序visionRequestHandler(_:error:)
来通知Image Predictor。该方法通过以下方式检索请求的results
:
- 检查
error
参数 - 将
results
转换为VNClassificationObservation
数组
// Cast the request's results as an `VNClassificationObservation` array.
guard let observations = request.results as? [VNClassificationObservation] else {
// Image classifiers, like MobileNet, only produce classification observations.
// However, other Core ML model types can produce other observations.
// For example, a style transfer model produces `VNPixelBufferObservation` instances.
print("VNRequest produced the wrong result type: \(type(of: request.results)).")
return
}
// Create a prediction array from the observations.
predictions = observations.map { observation in
// Convert each observation into an `ImagePredictor.Prediction` instance.
Prediction(classification: observation.identifier,
confidencePercentage: observation.confidencePercentageString)
}
Image Predictor将每个结果转换为Prediction
实例,这是一个具有两个字符串属性的简单结构。
该方法通过调用客户端的完成处理程序将predictions
数组发送到Image Predictor的客户端(主视图控制器)。
// Send the predictions back to the client.
predictionHandler(predictions)
7、格式化和呈现预测
主视图控制器的imagePredictionHandler(_:)
方法将各个预测格式化为单个字符串,并使用辅助方法更新应用程序UI中的标签。
private func imagePredictionHandler(_ predictions: [ImagePredictor.Prediction]?) {
guard let predictions = predictions else {
updatePredictionLabel("No predictions. (Check console log.)")
return
}
let formattedPredictions = formatPredictions(predictions)
let predictionString = formattedPredictions.joined(separator: "\n")
updatePredictionLabel(predictionString)
}
通过更新主调度队列上的标签文本,updatePredictionLabel(_:)
helper方法可以安全地更新UI。
func updatePredictionLabel(_ message: String) {
DispatchQueue.main.async {
self.predictionLabel.text = message
}
重要:通过在主线程之外使用Core ML模型进行预测来保持应用程序的UI响应。
六、通过计算机视觉和对象检测了解掷骰子
原文:https://developer.apple.com/documentation/coreml/model_integration_samples/understanding_a_dice_roll_with_vision_and_object_detection
下载:https://docs-assets.developer.apple.com/published/59a62fcf49/UnderstandingADiceRollWithVisionAndObjectDetection.zip
检测骰子位置和相机帧中显示的值,并通过利用骰子检测模型确定掷骰子的结束。
iOS13.0+ | iPadOS 13.0+ | Xcode 12.0+
1、概览
此示例应用程序使用经过训练的目标检测模型来识别骰子顶部及其在骰子滚动到平坦表面时的值。
通过Vision在相机帧上运行目标检测模型后,模型会解释结果以识别掷硬币何时结束以及骰子显示的值。
注:此示例代码项目与WWDC 2019会话228:使用Core ML和ARKit创建出色的应用程序相关联。
2、配置示例代码项目
在Xcode中运行示例代码项目之前,请注意以下内容:
- 您必须在使用iOS13或更高版本的物理设备上运行此示例代码项目。该项目不适用于模拟器。
- 该模型在带有黑色点的白色骰子上效果最好。它可能在使用其他颜色的骰子上表现不同。
3、向请求添加输入
在Vision中,从iOS13开始,您可以通过将 MLFeatureProvider
对象附加到模型来向模型提供图像以外的输入。当您想要指定与默认值不同的阈值时,这在目标检测的情况下很有用。
如下所示,功能提供程序可以为目标检测模型的iouThreshold
和confidenceThreshold
输入提供值。
class ThresholdProvider: MLFeatureProvider {
/// The actual values to provide as input
///
/// Create ML Defaults are 0.45 for IOU and 0.25 for confidence.
/// Here the IOU threshold is relaxed a little bit because there are
/// sometimes multiple overlapping boxes per die.
/// Technically, relaxing the IOU threshold means
/// non-maximum-suppression (NMS) becomes stricter (fewer boxes are shown).
/// The confidence threshold can also be relaxed slightly because
/// objects look very consistent and are easily detected on a homogeneous
/// background.
open var values = [
"iouThreshold": MLFeatureValue(double: 0.3),
"confidenceThreshold": MLFeatureValue(double: 0.2)
]
/// The feature names the provider has, per the MLFeatureProvider protocol
var featureNames: Set<String> {
return Set(values.keys)
}
/// The actual values for the features the provider can provide
func featureValue(for featureName: String) -> MLFeatureValue? {
return values[featureName]
}
}
要将此阈值提供程序与VNCoreMLModel
一起使用,请将其分配给VNCoreMLModel
的featureProvider
属性,如下例所示。
4、设置视觉请求以处理相机框架
为简单起见,您可以使用来自ARSession
的相机帧。
要在这些帧上运行检测器,首先使用模型设置一个VNCoreMLRequest
请求,如下例所示。
guard let mlModel = try? DiceDetector(configuration: .init()).model,
let detector = try? VNCoreMLModel(for: mlModel) else {
print("Failed to load detector!")
return
}
// Use a threshold provider to specify custom thresholds for the object detector.
detector.featureProvider = ThresholdProvider()
diceDetectionRequest = VNCoreMLRequest(model: detector) { [weak self] request, error in
self?.detectionRequestHandler(request: request, error: error)
}
// .scaleFill results in a slight skew but the model was trained accordingly
// see https://developer.apple.com/documentation/vision/vnimagecropandscaleoption for more information
diceDetectionRequest.imageCropAndScaleOption = .scaleFill
5、将相机框架传递给对象检测器以预测骰子位置
将帧从相机传递到VNCoreMLRequest
,以便它可以使用VNImageRequestHandler
对象进行预测。VNImageRequestHandler
对象处理图像大小调整和预处理,以及对每个预测的模型输出的后处理。
要将相机帧传递给您的模型,您首先需要找到与设备物理方向相对应的图像方向。如果设备的方向发生变化,图像的长宽比也会发生变化。因为您需要将检测到的对象的边界框缩放回原始图像,所以您需要跟踪其大小。
// The frame is always oriented based on the camera sensor,
// so in most cases Vision needs to rotate it for the model to work as expected.
let orientation = UIDevice.current.orientation
// The image captured by the camera
let image = frame.capturedImage
let imageOrientation: CGImagePropertyOrientation
switch orientation {
case .portrait:
imageOrientation = .right
case .portraitUpsideDown:
imageOrientation = .left
case .landscapeLeft:
imageOrientation = .up
case .landscapeRight:
imageOrientation = .down
case .unknown:
print("The device orientation is unknown, the predictions may be affected")
fallthrough
default:
// By default keep the last orientation
// This applies for faceUp and faceDown
imageOrientation = self.lastOrientation
}
// For object detection, keeping track of the image buffer size
// to know how to draw bounding boxes based on relative values.
if self.bufferSize == nil || self.lastOrientation != imageOrientation {
self.lastOrientation = imageOrientation
let pixelBufferWidth = CVPixelBufferGetWidth(image)
let pixelBufferHeight = CVPixelBufferGetHeight(image)
if [.up, .down].contains(imageOrientation) {
self.bufferSize = CGSize(width: pixelBufferWidth,
height: pixelBufferHeight)
} else {
self.bufferSize = CGSize(width: pixelBufferHeight,
height: pixelBufferWidth)
}
}
最后,使用来自相机的图像和当前方向的信息调用VNImageRequestHandler
,使用对象检测器进行预测。
// Invoke a VNRequestHandler with that image
let handler = VNImageRequestHandler(cvPixelBuffer: image, orientation: imageOrientation, options: [:])
do {
try handler.perform([self.diceDetectionRequest])
} catch {
print("CoreML request failed with error: \(error.localizedDescription)")
}
现在应用程序处理了向您的模型提供输入数据,是时候解释模型的输出了。
6、绘制边界框以了解模型的行为
您可以通过在每个对象及其文本标签周围绘制边界框来更好地了解检测器的性能。骰子检测模型检测骰子的顶部,并根据每个骰子顶部显示的点数对其进行标记。
要绘制边界框,请参阅在实时捕获中识别对象。
7、确定滚动何时结束
玩骰子游戏时,用户想知道掷骰子的结果。应用程序通过等待骰子的位置和值稳定来确定掷骰子已结束。
您可以将结束胶卷的要求定义为具有以下条件的两个连续相机帧之间的比较:
-
检测到的骰子数量必须相同。
-
对于每个检测到的芯片:
- 边界框一定没有移动。
- 标识的类必须匹配。
基于这些约束,您可以创建一个函数,根据当前和先前的VNRecognizedObjectObservation
对象告诉应用是否已结束滚动。
/// Determines if a roll has ended with the current dice values O(n^2)
///
/// - parameter observations: The object detection observations from the model
/// - returns: True if the roll has ended
func hasRollEnded(observations: [VNRecognizedObjectObservation]) -> Bool {
// First check if same number of dice were detected
if lastObservations.count != observations.count {
lastObservations = observations
return false
}
var matches = 0
for newObservation in observations {
for oldObservation in lastObservations {
// If the labels don't match, skip it
// Or if the IOU is less than 85%, consider this box different
// Either it's a different die or the same die has moved
if newObservation.labels.first?.identifier == oldObservation.labels.first?.identifier &&
intersectionOverUnion(oldObservation.boundingBox, newObservation.boundingBox) > 0.85 {
matches += 1
}
}
}
lastObservations = observations
return matches == observations.count
}
现在,对于每个预测(意味着每个新的相机帧),您可以检查滚动是否已经结束。
8、显示骰子值
滚动结束后,您可以在屏幕上显示信息或在游戏设置中触发一些其他行为。
此示例应用程序在屏幕上显示识别值的列表,从最左到最右排序。它根据每个观察值的边界框坐标,根据骰子在表面上的位置对值进行排序。该应用程序通过按边界框的VNRecognizedObjectObservation
centerX
属性升序对观察值进行排序来做到这一点。
var sortableDiceValues = [(value: Int, xPosition: CGFloat)]()
for observation in observations {
// Select only the label with the highest confidence.
guard let topLabelObservation = observation.labels.first else {
print("Object observation has no labels")
continue
}
if let intValue = Int(topLabelObservation.identifier) {
sortableDiceValues.append((value: intValue, xPosition: observation.boundingBox.midX))
}
}
let diceValues = sortableDiceValues.sorted { $0.xPosition < $1.xPosition }.map { $0.value }
七、在文本文稿中查找问题答案
原文:https://developer.apple.com/documentation/coreml/finding-answers-to-questions-in-a-text-document
下载:https://docs-assets.developer.apple.com/published/027f89fdf072/FindingAnswersToQuestionsInATextDocument.zip
1、概览
通过向来自变压器的双向编码器表示(BERT)模型提出问题来找到文档中的相关段落。
iOS13.0+ | iPadOS 13.0+ | Mac Catalyst 13.0+ | Xcode 15.2+
这个示例应用程序利用BERT模型在文本正文中找到用户问题的答案。该模型接受文档中的文本和关于该文档的自然英语问题。该模型以文档文本中回答问题的段落的位置作为响应。例如,给定文本,“敏捷的棕色狐狸跳过昏昏欲睡的狗。”问题是“谁跳过了狗?”,BERT模型的预测答案是“敏捷的棕色狐狸”。
BERT模型不会生成新句子来回答给定的问题。它在文档中找到最有可能回答问题的段落。
该示例通过以下方式利用BERT模型:
- 将BERT模型的词汇表导入字典
- 将文档和问题文本分解为令牌
- 使用词汇词典将令牌转换为ID号
- 将转换后的令牌ID打包到模型的输入格式中
- 调用BERT模型的预测(from:)方法
- 通过分析BERT模型的输出来定位答案
- 从原始文档文本中提取该答案
2、配置示例代码项目
在Xcode中运行示例代码项目之前,请使用具有以下功能的设备:
- iOS13岁或更晚
- macOS 10.15或更高版本
3、建立词汇量
使用BERT模型的第一步是导入它的词汇表。示例通过将词汇表文件拆分成行来创建词汇表词典,每一行都有一个标记。该函数将每个标记的(从零开始的)行号分配为其值。例如,第一个标记"[PAD]"
的ID为0
,第5,001个标记"knight"
的ID为5000
。
4、将文本拆分为单词标记
BERT模型要求您将每个单词转换为一个或多个标记ID。在使用词汇词典查找这些ID之前,您必须将文档的文本和问题的文本划分为单词标记。
该示例通过使用NLTagger来实现这一点,它将字符串分解为单词标记,每个单词标记都是原始字符串的子字符串。
// Store the tokenized substrings into an array.
var wordTokens = [Substring]()
// Use Natural Language's NLTagger to tokenize the input by word.
let tagger = NLTagger(tagSchemes: [.tokenType])
tagger.string = rawString
// Find all tokens in the string and append to the array.
tagger.enumerateTags(in: rawString.startIndex..<rawString.endIndex,
unit: .word,
scheme: .tokenType,
options: [.omitWhitespace]) { (_, range) -> Bool in
wordTokens.append(rawString[range])
return true
}
return wordTokens
示例应用程序利用标记器将每个字符串拆分为标记,方法是使用其enumerateTags(in: Unit:方案:选项:使用:)方法以及.TokenType标记方案和.word标记单元。
5、将单词或词片标记转换为其ID
为了提高速度和效率,BERT模型对令牌ID(代表令牌的数字)进行操作,而不是对文本令牌本身进行操作。
let subTokenID = BERTVocabulary.tokenID(of: searchTerm)
如果词汇表中不存在单词标记,则该方法查找子标记或词块。词块是较大单词标记的组成部分。例如,单词lethargic不在词汇表中,但它的词块let、har和gic是。将词汇的大词分成词块减少了词汇量,使BERT模型更加灵活。该模型可以通过组合词汇表中没有明确显示的单词来理解它们。
次要词条,如har和gic,都以两个前导磅符号出现在词汇中,如##har
和##gic
。
继续示例,该方法将文档文本转换为下图所示的单词和单词标记ID。
6、准备模型输入
BERT模型有两个输入:
wordIDs
-接受文档和问题文本wordTypes
-告诉BERT模型哪些wordIDs
元素来自文档
该示例通过按以下顺序排列标记ID来创建wordIDs
数组:
- 一个分类起始令牌ID,其值为
101
,并在词汇表文件中显示为"[CLS]"
。 - 问题字符串中的令牌ID
- 一个分隔符令牌ID,其值为
102
,在词汇表文件中显示为"[SEP]"
。 - 文本字符串中的令牌ID
- 另一个分隔符令牌ID
- 剩余未使用元素的一个或多个填充令牌ID,其值为
0
,并在词汇表文件中显示为"[PAD]"
。
// Start the wordID array with the `classification start` token.
var wordIDs = [BERTVocabulary.classifyStartTokenID]
// Add the question tokens and a separator.
wordIDs += question.tokenIDs
wordIDs += [BERTVocabulary.separatorTokenID]
// Add the document tokens and a separator.
wordIDs += document.tokenIDs
wordIDs += [BERTVocabulary.separatorTokenID]
// Fill the remaining token slots with padding tokens.
let tokenIDPadding = BERTInput.maxTokens - wordIDs.count
wordIDs += Array(repeating: BERTVocabulary.paddingTokenID, count: tokenIDPadding)
接下来,该示例通过创建一个相同长度的数组来准备wordTypes
输入,其中对应于文档文本的所有元素都是1
,所有其他元素都是0
。
// Set all of the token types before the document to 0.
var wordTypes = Array(repeating: 0, count: documentOffset)
// Set all of the document token types to 1.
wordTypes += Array(repeating: 1, count: document.tokens.count)
// Set the remaining token types to 0.
let tokenTypePadding = BERTInput.maxTokens - wordTypes.count
wordTypes += Array(repeating: 0, count: tokenTypePadding)
继续示例,示例使用下图所示的值排列两个输入数组。
接下来,该示例为每个输入创建一个MLMultiArray
,并从数组中复制内容,用于创建BERTQAFP16Input
功能提供程序。
注:此示例中的BERT模型需要包含384个元素的一维MLMultiArray
输入。来自其他来源的模型可能具有不同的输入或形状。
// Create the MLMultiArray instances.
let tokenIDMultiArray = try? MLMultiArray(wordIDs)
let wordTypesMultiArray = try? MLMultiArray(wordTypes)
// Unwrap the MLMultiArray optionals.
guard let tokenIDInput = tokenIDMultiArray else {
fatalError("Couldn't create wordID MLMultiArray input")
}
guard let tokenTypeInput = wordTypesMultiArray else {
fatalError("Couldn't create wordType MLMultiArray input")
}
// Create the BERT input MLFeatureProvider.
let modelInput = BERTQAFP16Input(wordIDs: tokenIDInput,
wordTypes: tokenTypeInput)
7、做个预测
您可以使用BERT模型来预测在文档文本中哪里可以找到问题的答案,方法是将您的输入特征提供程序与输入MLMultiArray
实例一起提供给模型。
guard let prediction = try? bertModel.prediction(input: modelInput) else {
return "The BERT model is unable to make a prediction."
}
8、找到答案吧
您可以通过分析BERT模型的输出来找到问题的答案。该模型产生两个输出,startLogits
和endLogits
。每个logit是BERT模型预测答案开始和结束的原始置信度分数。
在此示例中,标记"the"
和"fox"
的最佳开始和结束日志分别为6.08
和7.53
。示例通过以下方式查找最高值的开始和结束日志的索引:
- 将每个输出logit
MLMultiArray
转换为Double
数组。 - 隔离与文档相关的日志。
- 在每个数组中查找具有最高值的20个logits的索引。
- 在20 x 20或更少的logits组合中搜索最佳组合。
// Convert the logits MLMultiArrays to [Double].
let startLogits = prediction.startLogits.doubleArray()
let endLogits = prediction.endLogits.doubleArray()
// Isolate the logits for the document.
let startLogitsOfDoc = [Double](startLogits[range])
let endLogitsOfDoc = [Double](endLogits[range])
// Only keep the top 20 (out of the possible ~380) indices for faster searching.
let topStartIndices = startLogitsOfDoc.indicesOfLargest(20)
let topEndIndices = endLogitsOfDoc.indicesOfLargest(20)
// Search for the highest valued logit pairing.
let bestPair = findBestLogitPair(startLogits: startLogitsOfDoc,
bestStartIndices: topStartIndices,
endLogits: endLogitsOfDoc,
bestEndIndices: topEndIndices)
在本例中,最佳开始和结束日志的索引分别为8
和11
。位于原文索引8
和11
之间的答案子字符串是“the quick brown fox”
。
9、较大文档的缩放
此示例中包含的BERT模型最多可以处理384个标记,包括三个开销标记——一个“分类开始”标记和两个分隔符标记——为您的文本和问题留下381个标记。对于超过此限制的较大文本,请考虑使用以下技术之一:
- 使用搜索机制来缩小相关文档文本的范围。
- 将文档文本分解为多个部分,例如按段落,并对每个部分进行预测。
八、缩减 Core ML App 的大小
1、概览
缩减 App 套装中被 Core ML 模型占用的存储容量。
在 App 中捆绑机器学习模型是开始使用 Core ML 最简单的方法。随着模型变得越来越高级,它们可能会越来越大,因此会占用相当多的存储空间。对于基于神经网络的模型,可以考虑让权重参数使用较低精度表示法来减小占用空间。如果模型不是能够使用较低精度的神经网络模型,或者需要进一步缩减 App 的大小时,你可以增加在设备上下载和编译模型的功能,而不要将模型捆绑到 App 中。
2、转换为较低精度模型
Core ML 工具 (英文) 提供的实用程序可将神经网络模型的浮点权重从全精度值转换为半精度值 (将表示法中使用的位数从 32 位缩减至 16 位),或者转换为更低精度的值 (1 到 8 位)。有关使用这些实用程序的更多信息,请参阅“Core ML 工具神经网络量化”文档 (英文)。
3、下载和编译模型
另一个缩减 App 大小的做法是让 App 将模型下载到用户设备,然后在后台编译模型。例如,如果用户只使用 App 支持的一小部分模型,则无需将所有可能的模型都捆绑到 App 中。这时可以选择根据用户行为下载模型。请参阅 “在用户设备上下载并编译模型 (英文)”。
2024-08-30(五)