版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.05.16 星期六 |
前言
目前世界上科技界的所有大佬一致认为人工智能是下一代科技革命,苹果作为科技界的巨头,当然也会紧跟新的科技革命的步伐,其中ios API 就新出了一个框架
Core ML
。ML是Machine Learning
的缩写,也就是机器学习,这正是现在很火的一个技术,它也是人工智能最核心的内容。感兴趣的可以看我写的下面几篇。
1. Core ML框架详细解析(一) —— Core ML基本概览
2. Core ML框架详细解析(二) —— 获取模型并集成到APP中
3. Core ML框架详细解析(三) —— 利用Vision和Core ML对图像进行分类
4. Core ML框架详细解析(四) —— 将训练模型转化为Core ML
5. Core ML框架详细解析(五) —— 一个Core ML简单示例(一)
6. Core ML框架详细解析(六) —— 一个Core ML简单示例(二)
7. Core ML框架详细解析(七) —— 减少Core ML应用程序的大小(一)
8. Core ML框架详细解析(八) —— 在用户设备上下载和编译模型(一)
9. Core ML框架详细解析(九) —— 用一系列输入进行预测(一)
10. Core ML框架详细解析(十) —— 集成自定义图层(一)
11. Core ML框架详细解析(十一) —— 创建自定义图层(一)
12. Core ML框架详细解析(十二) —— 用scikit-learn开始机器学习(一)
13. Core ML框架详细解析(十三) —— 使用Keras和Core ML开始机器学习(一)
14. Core ML框架详细解析(十四) —— 使用Keras和Core ML开始机器学习(二)
15. Core ML框架详细解析(十五) —— 机器学习:分类(一)
16. Core ML框架详细解析(十六) —— 人工智能和IBM Watson Services(一)
17. Core ML框架详细解析(十七) —— Core ML 和 Vision简单示例(一)
开始
首先看下写作内容
本教程向您介绍了
Core ML
和Vision
这两个前沿的iOS框架,以及如何在设备上对模型进行微调。内容来自翻译。
下面看下写作环境
Swift 5, iOS 13, Xcode 11
苹果在iOS 11
中发布了Core ML
和Vision
。Core ML
为开发人员提供了一种将机器学习模型引入他们的应用程序的方法。这使得在设备上构建像对象检测这样的智能特性成为可能。
iOS 13
在Core ML 3
中增加了设备上的训练,并开启了个性化用户体验的新方式。
在本教程中,您将了解如何使用Core ML
和Vision
框架对设备上的模型进行微调。要了解这一点,您将从Vibes开始,这是一个基于给定图像生成quotes
的应用程序。它还允许你在训练模型后使用shortcuts
添加你最喜欢的表情符号。
打开初始项目,在zip
文件中,您将找到两个文件夹:Starter
和final
。现在双击starter
项目中的Vibes.xcodeproj
打开它。
构建并运行项目。你会看到:
点击相机图标并从库中选择一张照片以查看quote
。接下来,点击贴纸图标并选择要添加到图像中的贴纸。将贴纸移动到任意位置:
有两件事你可以改进:
- 1)
quote
是随机选择的。如何显示与所选图像相关的quote
? - 2) 添加贴图需要很多步骤。如果可以为最常用的贴图创建
shortcuts
呢?
您在本教程中的目标是使用机器学习(machine learning)
来解决这两个挑战。
What is Machine Learning?
如果你是机器学习的新手,那么是时候揭开一些常见术语的神秘面纱了。
Artificial Intelligence(AI)是一种以编程方式添加到机器上模仿人类行为和思想的能力。
Machine Learning(ML)是人工智能的一个子集,它训练机器执行某些任务。例如,可以使用ML
训练机器识别图像中的猫,或者将文本从一种语言翻译成另一种语言。
Deep Learning深度学习是训练机器的一种方法。这种技术模仿了人脑,人脑是由神经元组成的网状结构。深度学习根据提供的数据训练一个人工神经网络。
假设你想让机器识别图像中的猫。你可以为机器提供大量的图像,这些图像被手动标记为cat
和not cat
。然后构建一个可以进行准确猜测或预测的模型。
1. Training With Models
苹果将模型(model)定义为“对一组训练数据应用机器学习算法的结果”。将模型看作一个函数,该函数接受输入,对给定的输入执行特定的操作,例如学习,然后预测和分类,并生成适当的输出。
带标记数据的训练称为监督学习(supervised learning)。你需要大量的好数据来建立一个好的模型。好的模型是什么意思?它意味着数据表示您正在为其构建的用例。
如果你想让你的模型识别所有的猫,但只喂给特定的品种的数据,它可能会错过其他品种。使用有偏差的数据(biased data)
进行培训可能会导致不希望的结果。
培训是计算密集型(compute-intensive)
的,通常在服务器上完成。由于它们的并行计算能力,gpu
通常会加快速度。
一旦训练完成,您就可以将模型部署到生产环境中,以运行对真实数据的预测或推断。
推理(Inference)
不像训练那样需要大量的计算。然而,在过去,移动应用程序必须对服务器进行远程调用以进行模型推断。
移动芯片性能的进步为设备上的推断打开了大门。这些好处包括减少延迟、减少网络依赖和改善隐私。但是由于计算负载,应用程序的大小和电池消耗会增加。
本教程展示用于设备上推断和设备上训练的Core ML
。
Apple’s Frameworks and Tools for Machine Learning
Core ML
使用特定于领域的框架,如用于图像分析的Vision
。Vision
提供高级api,用于在图像和视频上运行计算机视觉算法。Vision
可以使用Apple提供的内置模型或自定义的Core ML
模型对图像进行分类。
Core ML
建立在底层原语(lower-level primitives)
之上: Accelerate with BNNS and Metal Performance Shaders
:
Core ML
使用的其他领域特定框架包括用于处理文本的Natural Language
和用于识别音频中的声音的Sound Analysis
。
Integrating a Core ML Model Into Your App
要Core ML
集成,您需要一个Core ML Model
格式的模型。苹果提供了预先训练过的模型pre-trained models,你可以用它来完成图像分类等任务。如果这些方法对您不起作用,您可以寻找由社区创建的模型,或者创建您自己的模型。
对于Vibes的首次增强,您需要一个进行图像分类的模型。模型有不同程度的准确性和模型大小。您将使用SqueezeNet
,这是一个用于识别普通对象的小模型。
从starter Models
目录拖SqueezeNet.mlmodel
到Xcode
项目的Models
文件夹:
选择SqueezeNet.mlmodel
。在Project navigator
中查看模型细节:
预测Prediction
部分列出预期的输入和输出:
- 图像
image
输入需要一个尺寸为227×227
的图像。 - 有两种输出类型:
classLabelProbs
返回一个带有类别的概率的字典。classLabel
返回概率最高的类别。
点击模型旁边的箭头:
Xcode
为模型自动生成一个文件,其中包括输入、输出和主类的类。主类包括各种预测方法。
Vision
框架的标准工作流程为:
- 1) 首先,创建
Core ML
模型。 - 2) 然后,创建一个或多个请求。
- 3) 最后,创建并运行一个请求处理程序。
您已经创建了您的模型SqueezeNet.mlmodel
。接下来,您将创建一个请求。
1. Creating a Request
去CreateQuoteViewController.swift
,在UIKit import
之后添加以下内容:
import CoreML
import Vision
Vision
有助于处理图像,例如将它们转换为所需的格式。
添加以下属性:
// 1
private lazy var classificationRequest: VNCoreMLRequest = {
do {
// 2
let model = try VNCoreMLModel(for: SqueezeNet().model)
// 3
let request = VNCoreMLRequest(model: model) { request, _ in
if let classifications =
request.results as? [VNClassificationObservation] {
print("Classification results: \(classifications)")
}
}
// 4
request.imageCropAndScaleOption = .centerCrop
return request
} catch {
// 5
fatalError("Failed to load Vision ML model: \(error)")
}
}()
这里是正在发生的事情的细分:
- 1) 定义第一次访问时创建的图像分析请求。
- 2) 创建模型的一个实例。
- 3) 基于模型实例化一个图像分析请求对象。完成处理程序
(completion handler)
接收分类结果并打印它们。 - 4) 使用
Vision
裁剪输入图像以匹配模型的期望。 - 5) 通过关闭应用程序来处理模型加载错误。模型是应用程序包的一部分,所以这种情况永远不会发生。
2. Integrating the Request
在私有CreateQuoteViewController
扩展的末尾添加以下内容:
func classifyImage(_ image: UIImage) {
// 1
guard let orientation = CGImagePropertyOrientation(
rawValue: UInt32(image.imageOrientation.rawValue)) else {
return
}
guard let ciImage = CIImage(image: image) else {
fatalError("Unable to create \(CIImage.self) from \(image).")
}
// 2
DispatchQueue.global(qos: .userInitiated).async {
let handler =
VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
do {
try handler.perform([self.classificationRequest])
} catch {
print("Failed to perform classification.\n\(error.localizedDescription)")
}
}
}
下面是这个分类请求方法的作用:
- 1) 获取图像的方向和
CIImage
表示。 - 2) 在后台队列中启动异步分类请求。您可以创建一个处理程序来执行
Vision
请求,然后调度该请求。
最后,在imagePickerController(_:didFinishPickingMediaWithInfo:)
的末尾添加以下代码:
classifyImage(image)
这将在用户选择图像时触发分类请求。
构建并运行应用程序。点击相机图标并选择一张照片。没有任何视觉变化:
但是,控制台应该列出原始分类结果:
在这个例子中,分类器有27.9%的信心认为这个图像是一个cliff, drop, drop-off
。查找classificationRequest
并使用以下代码替换print
语句,以记录结果:
let topClassifications = classifications.prefix(2).map {
(confidence: $0.confidence, identifier: $0.identifier)
}
print("Top classifications: \(topClassifications)")
构建并运行应用程序,完成选择照片的步骤。控制台应该记录顶部结果:
现在可以使用提取的预测细节来显示与图像相关的引用。
3. Adding a Related Quote
在imagePickerController(_:didFinishPickingMediaWithInfo:)
中,删除以下内容:
if let quote = getQuote() {
quoteTextView.text = quote.text
}
这将显示一个随机引用,并且不再需要。
接下来,您将添加逻辑,以使用VNClassificationObservation
的结果获得quote
。在CreateQuoteViewController
扩展中添加以下内容:
func processClassifications(for request: VNRequest, error: Error?) {
DispatchQueue.main.async {
// 1
if let classifications =
request.results as? [VNClassificationObservation] {
// 2
let topClassifications = classifications.prefix(2).map {
(confidence: $0.confidence, identifier: $0.identifier)
}
print("Top classifications: \(topClassifications)")
let topIdentifiers =
topClassifications.map {$0.identifier.lowercased() }
// 3
if let quote = self.getQuote(for: topIdentifiers) {
self.quoteTextView.text = quote.text
}
}
}
}
在上面的代码中发生了什么:
- 1) 该方法处理来自图像分类请求的结果。
- 2) 该方法使用您以前见过的代码提取前两个预测。
- 3) 这些预测提供给
getQuote(for:)
以获得匹配的quote
。
该方法在主队列上运行,以确保quote
显示更新在UI线程上发生。
最后,从classificationRequest
调用此方法并将request
改为以下:
let request = VNCoreMLRequest(model: model) { [weak self] request, error in
guard let self = self else {
return
}
self.processClassifications(for: request, error: error)
}
在这里,您的完成处理程序调用您的新方法来处理结果。
构建并运行应用程序。选择一张有柠檬或柠檬树的照片。如果需要,从浏览器下载一个。你应该看到选择了柠檬quote
而不是随机quote
:
验证控制台日志匹配分类:
对流程进行几次测试,以验证结果的一致性。
您已经了解了如何使用Core ML
进行设备上的模型推断。
Personalizing a Model on the Device
使用Core ML 3
,您可以在运行时对设备上的可更新模型进行微调。这意味着您可以为每个用户个性化体验。
设备上的个性化是人脸识别(Face ID)
背后的理念。苹果可以将一个模型发送到识别普通人脸的设备上。在Face ID
设置过程中,每个用户都可以微调模型来识别他们的脸。
将这个更新后的模型送回苹果并部署给其他用户是没有意义的。这凸显了设备个性化带来的隐私优势。
可更新模型是被标记为可更新的Core ML
模型。您还定义了用于更新模型的训练输入。
1. k-Nearest Neighbors
您将使用可更新的绘图分类器模型来增强Vibes
。分类器基于k近邻(k-Nearest Neighbors, or k-NN)
识别新的图形。
k-NN
算法假设相似的事物彼此接近。
它通过比较特征向量来做到这一点。特征向量包含描述对象特征的重要信息。一个典型的特征向量是用R, G, B
表示的RGB
颜色。
比较特征向量之间的距离是判断两个物体是否相似的简单方法。k-NN
通过使用它的k
个最近邻居对输入进行分类。
下面的例子显示了一系列划分为正方形和圆形的图形。假设你想知道这幅红色的神秘图画属于哪一类:
选择k = 3
预示着这个新的图形是一个正方形:
k-NN
模型简单、快速。你不需要很多例子来训练他们。但是,如果有大量的示例数据,性能可能会降低。
k-NN
是Core ML
支持的训练模型类型之一。Vibes
使用一个可更新的绘图分类器:
- 1) 一种作为特征提取器的神经网络。神经网络知道如何识别图形。您需要提取
k-NN
模型的特性。 - 2) 基于
k-NN
的设备图个性化模型。
在Vibes
中,用户可以通过选择一个表情符号然后绘制三个例子来添加shortcut
。你将用表情符号作为标签,用图画作为训练的例子来训练模型。
2. Setting Up Training Drawing Flow
首先,准备屏幕接受用户输入,训练你的模型:
- 1) 在选择表情符号时添加一个屏幕。
- 2) 在点击保存时添加操作。
- 3) 从
stickerLabel
上移除UIPanGestureRecognizer
。
打开AddStickerViewController.swift
并在collectionView(_:didSelectItemAt:)
中使用以下代码替换performSegue(with identifier:sender:)
调用:
performSegue(withIdentifier: "AddShortcutSegue", sender: self)
当用户选择一个表情符号时,就会过渡到示例绘制视图。
接下来,打开AddShortcutViewController.swift
并添加以下代码来实现savePressed(_:)
:
print("Training data ready for label: \(selectedEmoji ?? "")")
performSegue(
withIdentifier: "AddShortcutUnwindSegue",
sender: self)
这将在用户点击Save
时将segue
解绕回主屏幕。
最后,打开CreateQuoteViewController.swift
并在addStickerToCanvas(_:at:)
中删除以下代码:
stickerLabel.isUserInteractionEnabled = true
let panGestureRecognizer = UIPanGestureRecognizer(
target: self,
action: #selector(handlePanGesture(_:)))
stickerLabel.addGestureRecognizer(panGestureRecognizer)
这删除了允许用户移动贴纸的代码。这只有在用户无法控制标签位置时才有用。
构建并运行应用程序,然后选择一张照片。点击贴纸图标,选择一个表情符号。你会看到你选择的表情符号,以及三个绘画画布:
现在,画三个相似的图像。当你完成第三张图时,确认保存是enabled
的:
然后,点击保存,确认选中的表情符号已经输出到控制台:
现在可以将注意力转移到触发shortcut
的流。
3. Adding the Shortcut Drawing View
下面是准备图像绘制视图的步骤:
- 1) 首先,声明一个
DrawingView
。 - 2) 接下来,在主视图中添加
DrawingView
。 - 3) 然后,从
viewDidLoad()
调用addCanvasForDrawing
。 - 4) 最后,在选择图像时清除画布。
打开CreateQuoteViewController.swift
,并在IBOutlet
声明之后添加以下属性:
var drawingView: DrawingView!
它包含用户绘制shortcut
的视图。
接下来,添加以下代码来实现addCanvasForDrawing()
:
drawingView = DrawingView(frame: stickerView.bounds)
view.addSubview(drawingView)
drawingView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
drawingView.topAnchor.constraint(equalTo: stickerView.topAnchor),
drawingView.leftAnchor.constraint(equalTo: stickerView.leftAnchor),
drawingView.rightAnchor.constraint(equalTo: stickerView.rightAnchor),
drawingView.bottomAnchor.constraint(equalTo: stickerView.bottomAnchor)
])
在这里,您创建了一个绘图视图的实例,并将其添加到主视图。设置自动布局约束(Auto Layout constraints )
,使其只与贴纸视图重叠。
然后,在viewDidLoad()
的末尾添加以下内容:
addCanvasForDrawing()
drawingView.isHidden = true
在这里添加绘图视图并确保它最初是隐藏的。
现在,在imagePickerController(_:didFinishPickingMediaWithInfo:)
中,在addStickerButton
启用后添加以下内容:
drawingView.clearCanvas()
drawingView.isHidden = false
在这里,您可以清除任何以前的图纸和取消隐藏的图纸视图,以便用户可以添加贴纸。
构建并运行应用程序并选择一张照片。使用您的鼠标或手指,以验证您可以在选定的图像上绘制:
已经取得了进展。开始!
4. Making Model Predictions
从starter
的Models
目录拖UpdatableDrawingClassifier
mlmodel进入
Xcode项目的
Models`文件夹:
现在,选择项目导航器中的UpdatableDrawingClassifier.mlmodel
。更新部分列出了模型在训练期间期望的两个输入。一个代表图画,另一个代表表情符号标签:
Prediction
部分列出了输入和输出。drawing
输入格式与训练期间使用的格式相匹配。label
输出表示预测的表情符号标签。
在Xcode
的项目导航器中选择Model
文件夹。然后,去File ▸ New ▸ File…
,选择iOS ▸ Source ▸ Swift File
,并单击Next
。将文件命名为UpdatableModel.swift
并单击Create
。
现在,将Foundation
导入替换为以下内容:
import CoreML
这就引入了机器学习框架。
现在添加以下扩展到文件的末尾:
extension UpdatableDrawingClassifier {
var imageConstraint: MLImageConstraint {
return model.modelDescription
.inputDescriptionsByName["drawing"]!
.imageConstraint!
}
func predictLabelFor(_ value: MLFeatureValue) -> String? {
guard
let pixelBuffer = value.imageBufferValue,
let prediction = try? prediction(drawing: pixelBuffer).label
else {
return nil
}
if prediction == "unknown" {
print("No prediction found")
return nil
}
return prediction
}
}
这扩展了生成的模型类UpdatableDrawingClassifier
。您的代码添加了以下内容:
- 1)
imageConstraint
确保图像符合模型的期望。 - 2)
predictLabelFor(_:)
使用绘制的CVPixelBuffer
表示来调用模型的预测方法。如果没有预测,它将返回预测标签或nil
。
5. Updating the Model
在import
语句之后添加以下内容:
struct UpdatableModel {
private static var updatedDrawingClassifier: UpdatableDrawingClassifier?
private static let appDirectory = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask).first!
private static let defaultModelURL =
UpdatableDrawingClassifier.urlOfModelInThisBundle
private static var updatedModelURL =
appDirectory.appendingPathComponent("personalized.mlmodelc")
private static var tempUpdatedModelURL =
appDirectory.appendingPathComponent("personalized_tmp.mlmodelc")
private init() { }
static var imageConstraint: MLImageConstraint {
let model = updatedDrawingClassifier ?? UpdatableDrawingClassifier()
return model.imageConstraint
}
}
这个结构表示您的可更新模型。这里的定义设置了模型的属性。这些包括原始编译模型和保存模型的位置。
注意:
Core ML
使用一个编译后的模型文件,扩展名为.mlmodelc
,实际上是一个文件夹。
6. Loading the Model Into Memory
现在,在结构定义之后添加以下私有扩展:
private extension UpdatableModel {
static func loadModel() {
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: updatedModelURL.path) {
do {
let updatedModelParentURL =
updatedModelURL.deletingLastPathComponent()
try fileManager.createDirectory(
at: updatedModelParentURL,
withIntermediateDirectories: true,
attributes: nil)
let toTemp = updatedModelParentURL
.appendingPathComponent(defaultModelURL.lastPathComponent)
try fileManager.copyItem(
at: defaultModelURL,
to: toTemp)
try fileManager.moveItem(
at: toTemp,
to: updatedModelURL)
} catch {
print("Error: \(error)")
return
}
}
guard let model = try? UpdatableDrawingClassifier(
contentsOf: updatedModelURL) else {
return
}
updatedDrawingClassifier = model
}
}
这段代码将更新的、编译后的模型加载到内存中。接下来,在结构定义之后添加以下公共扩展:
extension UpdatableModel {
static func predictLabelFor(_ value: MLFeatureValue) -> String? {
loadModel()
return updatedDrawingClassifier?.predictLabelFor(value)
}
}
predict
方法将模型加载到内存中,然后调用您添加到扩展中的predict
方法。
现在,打开Drawing.swift
,在PencilKit
import
之后添加以下内容:
import CoreML
您需要它来准备预测输入。
7. Preparing the Prediction
Core ML
希望您将预测的输入数据包装在MLFeatureValue
对象中。这个对象包括数据值和它的类型。
在Drawing.swift
中,将以下属性添加到结构体:
var featureValue: MLFeatureValue {
let imageConstraint = UpdatableModel.imageConstraint
let preparedImage = whiteTintedImage
let imageFeatureValue =
try? MLFeatureValue(cgImage: preparedImage, constraint: imageConstraint)
return imageFeatureValue!
}
这将定义一个计算属性,用于设置绘图的特性值。特征值基于图像的白色表示和模型的图像约束。
现在您已经准备好了输入,可以集中精力触发预测。
首先,打开CreateQuoteViewController.swift
并在文件末尾添加DrawingViewDelegate
扩展:
extension CreateQuoteViewController: DrawingViewDelegate {
func drawingDidChange(_ drawingView: DrawingView) {
// 1
let drawingRect = drawingView.boundingSquare()
let drawing = Drawing(
drawing: drawingView.canvasView.drawing,
rect: drawingRect)
// 2
let imageFeatureValue = drawing.featureValue
// 3
let drawingLabel =
UpdatableModel.predictLabelFor(imageFeatureValue)
// 4
DispatchQueue.main.async {
drawingView.clearCanvas()
guard let emoji = drawingLabel else {
return
}
self.addStickerToCanvas(emoji, at: drawingRect)
}
}
}
回想一下,您添加了一个DrawingView
来绘制标签shortcuts
。在此代码中,您遵循协议,以在绘图发生更改时得到通知。您的实现执行以下操作:
- 1) 使用绘图信息及其边框创建
Drawing
实例。 - 2) 为绘制预测输入创建特征值。
- 3) 做一个预测,得到与
drawing
相符的表情符号。 - 4) 更新主队列上的视图以清除画布并将预测的表情添加到视图中。
然后,在imagePickerController(_:didFinishPickingMediaWithInfo:)
中删除以下内容:
drawingView.clearCanvas()
你不需要清除这里的图画。在你做出预测后,你会这样做。
8. Testing the Prediction
接下来,在addCanvasForDrawing()
中,在分配drawingView
之后,添加如下内容:
drawingView.delegate = self
这使得视图控制器成为drawing view
委托。
构建并运行应用程序并选择一张照片。在画布上绘制,并验证绘制已被清除,并在控制台中记录以下内容:
这是意料之中的。您还没有添加贴纸快捷方式(sticker shortcut)
。
现在浏览一下添加贴纸快捷方式的流程。回到选中图片的视图后,绘制相同的快捷方式:
哎呀,贴纸还没有添加!你可以检查控制台日志的线索:
经过一些挠头,它可能会注意到你的模型对你添加的贴纸毫无头绪。是时候解决这个问题了。
9. Updating the Model
您可以通过创建一个MLUpdateTask
来更新一个模型。更新任务初始化器需要已编译的模型文件、训练数据和完成处理程序。通常,您希望将更新后的模型保存到磁盘并重新加载它,这样新的预测就可以使用最新的数据。
首先,您将根据shortcut
绘图准备训练数据。
回想一下,您通过传入一个MLFeatureProvider
输入来进行模型预测。同样,您可以通过传入MLFeatureProvider
输入来训练模型。通过传入一个包含多个特性providers
的MLBatchProvider
,可以进行批处理预测或使用多个输入进行训练。
首先,打开DrawingDataStore.swift
,并将Foundation import
替换为以下内容:
import CoreML
您需要它来设置Core ML
训练输入。
接下来,将以下方法添加到扩展:
func prepareTrainingData() throws -> MLBatchProvider {
// 1
var featureProviders: [MLFeatureProvider] = []
// 2
let inputName = "drawing"
let outputName = "label"
// 3
for drawing in drawings {
if let drawing = drawing {
// 4
let inputValue = drawing.featureValue
// 5
let outputValue = MLFeatureValue(string: emoji)
// 6
let dataPointFeatures: [String: MLFeatureValue] =
[inputName: inputValue,
outputName: outputValue]
// 7
if let provider =
try? MLDictionaryFeatureProvider(
dictionary: dataPointFeatures) {
featureProviders.append(provider)
}
}
}
// 8
return MLArrayBatchProvider(array: featureProviders)
}
下面是这段代码的逐步分解:
- 1) 初始化
feature providers
的空数组。 - 2) 为模型训练输入定义名称。
- 3) 循环遍历数据存储中的
drawings
。 - 4) 将绘图培训输入包装在一个特征值中。
- 5) 把表情符号训练输入包装成一个特征值。
- 6) 为训练输入创建一个数据点。这是一个训练输入名和特征值的字典。
- 7) 为数据点创建一个
feature provider
,并将其追加到功能提供程序数组中。 - 8) 最后,从
feature providers
数组中创建一个批处理提供程序。
现在,打开UpdatableModel.swift
,并将以下方法添加到UpdatableDrawingClassifier
扩展的末尾:
static func updateModel(
at url: URL,
with trainingData: MLBatchProvider,
completionHandler: @escaping (MLUpdateContext) -> Void
) {
do {
let updateTask = try MLUpdateTask(
forModelAt: url,
trainingData: trainingData,
configuration: nil,
completionHandler: completionHandler)
updateTask.resume()
} catch {
print("Couldn't create an MLUpdateTask.")
}
}
代码使用已编译的模型URL
创建更新任务。您还可以传入带有训练数据的批处理提供程序。对resume()
的调用启动了训练,并在训练结束时调用完成处理程序。
10. Saving the Model
现在,将以下方法添加到UpdatableModel
的私有扩展:
static func saveUpdatedModel(_ updateContext: MLUpdateContext) {
// 1
let updatedModel = updateContext.model
let fileManager = FileManager.default
do {
// 2
try fileManager.createDirectory(
at: tempUpdatedModelURL,
withIntermediateDirectories: true,
attributes: nil)
// 3
try updatedModel.write(to: tempUpdatedModelURL)
// 4
_ = try fileManager.replaceItemAt(
updatedModelURL,
withItemAt: tempUpdatedModelURL)
print("Updated model saved to:\n\t\(updatedModelURL)")
} catch let error {
print("Could not save updated model to the file system: \(error)")
return
}
}
这个helper
类执行保存更新的模型的工作。它接收一个MLUpdateContext
,其中包含关于训练的有用信息。方法做了如下事情:
- 1) 首先,它从内存中获取更新后的模型。这和原来的模型不一样。
- 2) 然后它创建一个中间文件夹来保存更新的模型。
- 3) 它将更新后的模型写入临时文件夹。
- 4) 最后,它替换模型文件夹的内容。覆盖现有的
mlmodelc
文件夹会产生错误。解决方案是保存到一个中间文件夹,然后将内容复制过来。
11. Performing the Update
将以下方法添加到公共UpdatableModel
扩展:
static func updateWith(
trainingData: MLBatchProvider,
completionHandler: @escaping () -> Void
) {
loadModel()
UpdatableDrawingClassifier.updateModel(
at: updatedModelURL,
with: trainingData) { context in
saveUpdatedModel(context)
DispatchQueue.main.async { completionHandler() }
}
}
代码将模型加载到内存中,然后调用在其扩展中定义的update
方法。completion handler
保存更新的模型,然后运行这个方法的完成处理程序completion handler
。
现在,打开AddShortcutViewController.swift
并使用以下代码替换savePressed(_:)
实现:
do {
let trainingData = try drawingDataStore.prepareTrainingData()
DispatchQueue.global(qos: .userInitiated).async {
UpdatableModel.updateWith(trainingData: trainingData) {
DispatchQueue.main.async {
self.performSegue(
withIdentifier: "AddShortcutUnwindSegue",
sender: self)
}
}
}
} catch {
print("Error updating model", error)
}
在这里,你把一切都准备好了。设置训练数据之后,启动一个后台队列来更新模型。update
方法调用unwind segue
来过渡到主屏幕。
构建并运行应用程序,完成创建快捷方式的步骤。
验证当你点击保存控制台日志模型更新:
在选定的图片上绘制相同的快捷方式,并验证正确的表情符号显示:
恭喜你
Create ML
应用程序允许您构建、训练和部署机器学习模型,而不需要机器学习专业知识。你也可以查看官方WWDC 2019 sessions 关于What’s New in Machine Learning 和 Training Object Detection Models in Create ML。
后记
本篇主要讲述了基于Core ML 和 Vision的设备上的训练,感兴趣的给个赞或者关注~~~
网友评论