iOS 12带来了机器学习上的大跃进,通过文本工作的新方式,还有分组通告——这样用户就不会频繁的被打扰等等。其中的一些为带来了Xcode巨大进步,这里我会都说到它们。
在这篇文章中,我会带你看遍这些巨大改进,并附有代码示例,这样你就都可以自己尝试一下了。现在你仅能使用beta版的Xcode来尝试iOS 12,如果你想使用新的Create ML工具的话,你还需要macOS Mojave。
相关推荐
机器学习中的图像识别
机器学习(ML)是iOS 11中几个重大发布之一,但它用起来并不容易——尤其对于之前没学过相关内容的人来说。
现在一切都变了,因为苹果新引入了两大块功能。第一个是Create ML,它确实是一款macOS组件,用于让任何人都能简单地创造可在app中使用的Core ML模型。第二个是预测批处理(prediction batching),允许Core ML更有效率地评估多个输入源,让新手尽量避免犯基本错误。
Create ML已经得到证实,尽管我怀疑它会随着Xcode10 beta的变化也有点小改动,当前的UI看上去有已经接近最终版,说实话,它的特性,性能和实际效果已经很出彩了。
如果你想试一下它,首先建立一个新的macOS playground,输入如下代码:
import CreateMLUI let builder = MLImageClassifierBuilder() builder.showInLiveView()
点击play键运行代码,之后打开辅助编辑器(assistant editor)观看Create ML的实时图像。你会看到顶部有"ImageClassifier",其下方是"Drop Images To Begin Training"
现在你需要一些训练数据。如果你仅仅想做一个快速测试,我给你提供了一些图片:可点击这里下载它们。这些都来自https://unsplash.com/,并有相应许可。
如果你想自己建立数据,你一定会感到惊喜,因为这一步很简单:
在任意地方建立一个新文件夹,比如你的桌面
在文件夹中建立两个新文件夹:Training Data与Test Data
在这两个文件夹中,为每个你想鉴定的东西建立新文件夹
现在把你的图片放到适当的文件夹里。
这样就可以了,Training Data用于建立你的训练模型,Test Data用于检验训练模型面对它未曾见过的图片时表现如何。
注意:不要把同样的图同时放入Training Data与Test Data,这样会破坏测试。
我举个例子,我提供了一些猫猫狗狗的图,这样我们可以用Create ML训练一个模型来分辨它们。那么我的文件结构就如下:
Training Data
Training Data > Cat
Training Data > Cat > SomeCat.jpg
Training Data > Cat > AnotherCat.jpg
Training Data > Dog
Training Data > Dog > SomeDog.jpg
Training Data > Dog > AnotherDog.jpg
Testing Data > Cat
Testing Data > Cat > AThirdCat.jpg
诸如此类,最好每组数据都至少有10张以上的图,并最好每个分类下的图片数量都差不多。苹果建议你分配80%的图片到训练数据,剩下20%用于测试。
现在你已经准备好了数据,是时候让Create ML开始它的训练进程了。把整个Training Data文件夹拖到playground assistant editor窗口,到写有” Drop Images To Begin Training”文字的位置上。
之后Xcode会快速反复浏览所有图片,试图计算出如何在视觉上把猫和狗分开。我测试的时候花了10秒钟,结束的时候你会看到” SUCCESS: Optimal Solution found”提示,这说明你的模型已经准备好了。
下一步是看看你的模型性能如何,用一些测试图片就可以看出来。你会看到一个新的"Drop Images To Begin Testing"区域出现了,把Testing Data文件夹拖到那里。这些图片我们的模型都没见过,所以它会根据之前训练中所学,来分辨出猫还是狗。
测试过程仅需要不到1秒,一旦完成你就会看到Core ML表现的到底怎么样了。显然我们都希望看到100%准确率,我给的猫狗示例就可以做到。但是如果没达到100%,就需要给Create ML提供更多的图片来学习。
一旦你的模型大功告成,点击"ImageClassifier"右边的disclosure indicator,提供一个小的metadata,之后点击Save生成一个Core ML模型。之后你就可以把它放入到你的应用中,就像iOS 10里一样。
机器学习中的文本分析
除了处理图像外,Create ML还能分析文本。具体过程根据实际有所不同,但它们的概念是一样的:提供一些训练数据来教Create ML各种文本的”意思”,之后提供一些测试数据用于评估模型的表现。
再一次,我提供了一些训练数据例子,这样你可以自己来试试了。这些数据来自Bo Pang 和 Lillian Lee写的文章,取自于2004年ACL 的会议记录。
我已经把这些数据转化为Create ML期望的格式,所以随时可以使用了。你可以在这里下载它们。
如果你想自己建立数据,那也很简单。首先,建立一个新的文件,命名为yourdata.json。例如car-prices.json, 或reviews.json。第二步,添加这样的内容:
[ { "text": "Swift is an awesome language", "label": "positive" }, { "text": "Swift is much worse than Objective-C.", "label": "negative" }, { "text": "I really hate Swift", "label": "negative" } ]
"text"部分是不限定格式的文本,用于训练。"label"部分是你认为该本文的属性。上例中把Swift的评论标记为positive或negative,但在我的例子中,使用了上千个电影评论。
第二步是用你的数据建立一个MLDataTable,一个包含待处理数据的结构。这步完成后,我们可以把这个表分成两部分,一部分是训练数据,一部分是测试数据——和之前的比例一样,80:20比较适合。
代码是这样的:
import CreateML import Foundation let data = try MLDataTable(contentsOf: URL(fileURLWithPath: "/Users/twostraws/Desktop/reviews.json")) let (trainingData, testingData) = data.randomSplit(by: 0.8, seed: 5)
把其中的"twostraws"替换成你电脑的用户名。
现在很重要的:用你的数据建立一个MLTextClassifier,告诉它你text栏与label栏的名字:
let classifier = try MLTextClassifier(trainingData: trainingData, textColumn: "text", labelColumn: "label")
到了这一步,你就可以保存这个已完成的模型,但是最好先检查一下你的准确性,防止给了过少或过多的数据。
这可以通过查看Create ML训练时检测到的分类错误数量来检查。结果是从0(没有错误)到100(全是错误)之间的数字。
let trainingErrorRate = classifier.trainingMetrics.classificationError * 100 let validationErrorRate = classifier.validationMetrics.classificationError * 100
下面的行会用同样的模型检查测试数据,查看它这边怎么样。
let evaluationMetrics = classifier.evaluation(on: testingData) let errorRate = evaluationMetrics.classificationError * 100
再说一次,0意味着没有错误,这也是我们想要的。
最后我们准备好保存了,这也需要两步:建立一些metadata来描述我们的模型,之后把它写到我们驱动器的URL上。
let metadata = MLModelMetadata(author: "Paul Hudson", shortDescription: "A model trained to handle sentiment analysis in movie reviews.", version: "1.0") try classifier.write(to: URL(fileURLWithPath: "/Users/twostraws/Desktop/result.mlmodel"), metadata: metadata)
注意:我认为这些Create ML工具仅在Swift下可用
分组通告(Grouped alerts)
现在通知可以被系统分组,这样朋友间的一次对话就不会占用太多屏幕了。
通常你会这样建立通知
func scheduleNotification() { let center = UNUserNotificationCenter.current() let content = UNMutableNotificationContent() content.title = "Late wake up call" content.body = "The early bird catches the worm, but the second mouse gets the cheese." content.categoryIdentifier = "alarm" content.sound = UNNotificationSound.default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) center.add(request) }
由于3个新特性,这些在iOS 12中改变了
threadIdentifier特性描述了消息属于哪个分组
summaryArgument特性让你可以向客户描述这个消息和什么相关联——比如”来自Andrew和Jill”
summaryArgumentCount用于当每一条消息关联多个事情时。比如,你得到一个消息说”你有5份请柬”,之后另一个说”你有3份请柬”,你收到了2条消息和8份请柬,那么你在summaryArgumentCount中就用5和3。
如果你没有提供一个标识符,iOS会自动把所有通知分到一组。但如果你提供了标识符,那就会根据它给你的通知分组:3条来自Andrew与Jill,4条来自Steven,2条来自你丈夫等等。
为了试试它,我们建立了一个简单循环:
for i in 1...10 { let content = UNMutableNotificationContent() content.title = "Late wake up call" content.body = "The early bird catches the worm, but the second mouse gets the cheese." content.categoryIdentifier = "alarm" content.userInfo = ["customData": "fizzbuzz"] content.sound = UNNotificationSound.default content.threadIdentifier = "Andrew" content.summaryArgument = "from Andrew" let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) center.add(request) }
这会建立10条通知,所有的分类都是"from Andrew"
这里还有一个新选项,即建立与显示重要通告的能力。即使系统设定为Do Not Disturb,这些通告依旧会传达。重要通告需要苹果的许可,我觉得他们会更容易通过这些许可。
快捷方式
iOS 12给与了用户用Siri来激活app做相应动作的能力,不管是什么动作都可以。
苹果已经给我们2个API使用,一个很简单,一个很复杂。我猜大部分人会先从简单的入手并评估用户反馈。如果使用量够多他们还可以花更多时间做更复杂的,但这并不总是必要的。
如果你想试试,大部分的工作可以使用同样的NSUserActivity类完成,它还做了很多iOS上其他工作,Spotlight search, Handoff, 和SiriKit。你可以根据需要建立它们,并可以把它们附到你的视图控制器上,然而你应该仅显示那些任何时刻都可用的快捷方式——因为动作不可用时,Siri无法理解它们。
注意:快捷方式好像仅在实际设备上才有效
首先我们要告诉iOS我们支持什么动作,打开你项目的Info.plist文件,添加一个新行,命名为NSUserActivityTypes。让它成为一个数组,之后添加一个单项:com.hackingwithswift.example.showscores,它会把一个动作唯一地识别到iOS上。
当用户使用的时候,我们需要注册这个动作。如果你现在正要试试它,试着在viewDidAppear()里添加这些:
// 给我们的动作一个唯一的ID let activity = NSUserActivity(activityType: "com.hackingwithswift.example.showscores") //给它一个显示给用户的标题 activity.title = "Show the latest scores" // 允许Siri给它做索引并用声音匹配查询使用它 activity.isEligibleForSearch = true activity.isEligibleForPrediction = true // 当从这个动作加载app时,附加一些示例消息 activity.userInfo = ["message": "Important!"] // 如果需要,给这个动作一个唯一识别符,用于之后删除它 activity.persistentIdentifier = NSUserActivityPersistentIdentifier("abc") // 让这个动作对当前的视图控制器保持活动状态——当动作被激活时候,Siri会恢复它 self.userActivity = activity
这就把这个动作添加给了Siri——告诉它用户在做什么,它就会尝试找出一个模式。此外我们还需要写一些代码,当这个动作被选择时,就会激活这些代码。所以把下面这些添加到AppDelegate类中:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == "com.hackingwithswift.example.showscores" { print("Show scores...") } return true }
所有的代码就完成了,如果你正在实际使用一台设备,你可以运行app注册这个快捷方式,之后把它添加到Siri。要做这些只需要打开Settings app,选择Siri & Search,里面会显示已经注册到系统的所有动作,你可以任意向里面添加。你还需要为每个动作录制一段声音指令,这一步花不了几秒。
要想测试快捷方式,可以用两种方法。它们都在Settings app里,在Developer里的Display Recent Shortcuts和Display Donations On Lock Screen。它们帮助你精确检查添加的哪些快捷方式被Siri接收了,并始终在Siri查找中显示你的快捷方式。再次声明,这些现在仅仅在实际设备上可用。
通过文本工作
NSLinguisticTagger在去年的WWDC中推出,它有Core ML的支持,并可以展现各类高速精巧计算。
今年就没有它了,不过,也不是没有——如果你想依旧可以用它。但是我们有了一套更好的方案,叫做自然语言框架(Natural Language framework)。它是一个Swift化版本的NSLinguisticTagger:除了更加Swift化外,它的API几乎和旧的语言标记代码(linguistic tagger code)一样。
让我们从一个简单例子开始。使用这个识别出各种长短文本的语言吧:
import NaturalLanguage let string = """ He thrusts his fists Against the posts And still insists He sees the ghosts """ if let lang = NLLanguageRecognizer.dominantLanguage(for: string) { if lang == .english { print("It's English!") } else { print("It's something else...") } }
可以看到,dominantLanguage(for:)返回一个可选枚举,匹配它猜测的语言。
要记住一些事情:
它会查找主导的语言,如果你提供一个多语言的文本,它会返回最重要的一个。
不要只发一小段语言——如果没有足够上下文,Core ML无法做出好的选择
结果是可选择的,因为有可能没有语言能匹配你输入的字符串
这个特性使用NSLinguisticTagger也可以做到。而另一个特性是它可以通过浏览句子、段落、或整个文本寻找表征符号(token),比如一些独立词汇。
例如,下面这段代码会加载一串字符,并把它拆开成独立词汇
如果你需要,可以手动循环遍历所有表征符号。这会返回一个终止信号,你需要返回true ("我想继续这个过程") 或false ("我已经完成了").
代码如下:
tokenizer.enumerateTokens(in: string.startIndex ..< string.endIndex) { (range, attrs) -> Bool in print(string[range]) print(attrs) return true }
"attrs"值用于描述表征符号的属性——比如它是一个字母,一个符号,还是一个表情符号。
自然语言另一个有用的能力是它可以解析文本查找物体的名字。例如,下面定义了一个简单文本串,建立了一个名字标记(tagger)来找出里面包含了什么:
可以看到,它要求自然语言查找一组标记:人名,地名,和所有组织名
当它完成后,会如下输出
Steve Jobs: PersonalName Steve Wozniak: PersonalName Ronald Wayne: PersonalName Apple Inc: OrganizationName California: PlaceName
看起来它和NSLinguisticTagger异常相似,你是对的:它们几乎一样,尽管自然语言对Swift用户来说有一个更好的API
其他小更新
这也是一些主要特性,并有令人关注的突出地方
UIWebView现在真的被弃用了,它在Xcode9.3中被标记成”遗产(Legacy)”,而现在则被正式弃用。你要使用WKWebView代替它了,使用方法可以参考我的WKWebView终极指南(过几天就发)
现在当我们的iOS应用处于黑暗模式时,有了UIUserInterfaceStyle.dark。它现在还不能在iPhone上激活,但是已经具有一些基础构造——你可以读取黑暗模式图片,当黑暗模式启用时读取UI变化等等。
任何文本输入文件现在支持密码规则,让系统产生更复杂的密码。这在当前beta版上运行的还不太好,但很快会改进。
UIImagePNGRepresentation()和UIImageJPEGRepresentation()功能已经被.pngData()和.jpegData(compressionQuality:)方法代替。