iOS 12 新内容



ios-12-1.png

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,输入如下代码:

1
2
3
4
import CreateMLUI 
 
let builder = MLImageClassifierBuilder()
builder.showInLiveView()

点击play键运行代码,之后打开辅助编辑器(assistant editor)观看Create ML的实时图像。你会看到顶部有"ImageClassifier",其下方是"Drop Images To Begin Training"

现在你需要一些训练数据。如果你仅仅想做一个快速测试,我给你提供了一些图片:可点击这里下载它们。这些都来自https://unsplash.com/,并有相应许可。

如果你想自己建立数据,你一定会感到惊喜,因为这一步很简单:

  1. 在任意地方建立一个新文件夹,比如你的桌面
  2. 在文件夹中建立两个新文件夹:Training Data与Test Data
  3. 在这两个文件夹中,为每个你想鉴定的东西建立新文件夹
  4. 现在把你的图片放到适当的文件夹里。

这样就可以了,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。第二步,添加这样的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
        "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比较适合。

代码是这样的:

1
2
3
4
5
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栏的名字:

1
let classifier = try MLTextClassifier(trainingData: trainingData, textColumn: "text", labelColumn: "label")

到了这一步,你就可以保存这个已完成的模型,但是最好先检查一下你的准确性,防止给了过少或过多的数据。

这可以通过查看Create ML训练时检测到的分类错误数量来检查。结果是从0(没有错误)到100(全是错误)之间的数字。

1
2
let trainingErrorRate = classifier.trainingMetrics.classificationError * 100
let validationErrorRate = classifier.validationMetrics.classificationError * 100

下面的行会用同样的模型检查测试数据,查看它这边怎么样。

1
2
let evaluationMetrics = classifier.evaluation(on: testingData)
let errorRate = evaluationMetrics.classificationError * 100

再说一次,0意味着没有错误,这也是我们想要的。

最后我们准备好保存了,这也需要两步:建立一些metadata来描述我们的模型,之后把它写到我们驱动器的URL上。

1
2
3
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)

现在通知可以被系统分组,这样朋友间的一次对话就不会占用太多屏幕了。

通常你会这样建立通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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条来自你丈夫等等。

为了试试它,我们建立了一个简单循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for 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()里添加这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 给我们的动作一个唯一的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类中:

1
2
3
4
5
6
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)一样。

让我们从一个简单例子开始。使用这个识别出各种长短文本的语言吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:)返回一个可选枚举,匹配它猜测的语言。

要记住一些事情:

  1. 它会查找主导的语言,如果你提供一个多语言的文本,它会返回最重要的一个。
  2. 不要只发一小段语言——如果没有足够上下文,Core ML无法做出好的选择
  3. 结果是可选择的,因为有可能没有语言能匹配你输入的字符串

这个特性使用NSLinguisticTagger也可以做到。而另一个特性是它可以通过浏览句子、段落、或整个文本寻找表征符号(token),比如一些独立词汇。

例如,下面这段代码会加载一串字符,并把它拆开成独立词汇

00.png

如果你需要,可以手动循环遍历所有表征符号。这会返回一个终止信号,你需要返回true ("我想继续这个过程") 或false ("我已经完成了").

代码如下:

1
2
3
4
5
tokenizer.enumerateTokens(in: string.startIndex ..< string.endIndex) { (range, attrs) -> Bool in
    print(string[range])
    print(attrs)
    return true
}

"attrs"值用于描述表征符号的属性——比如它是一个字母,一个符号,还是一个表情符号。

自然语言另一个有用的能力是它可以解析文本查找物体的名字。例如,下面定义了一个简单文本串,建立了一个名字标记(tagger)来找出里面包含了什么:

01.png

可以看到,它要求自然语言查找一组标记:人名,地名,和所有组织名

当它完成后,会如下输出

1
2
3
4
5
Steve Jobs: PersonalName
Steve Wozniak: PersonalName
Ronald Wayne: PersonalName
Apple Inc: OrganizationName
California: PlaceName

看起来它和NSLinguisticTagger异常相似,你是对的:它们几乎一样,尽管自然语言对Swift用户来说有一个更好的API

其他小更新

这也是一些主要特性,并有令人关注的突出地方

  1. UIWebView现在真的被弃用了,它在Xcode9.3中被标记成”遗产(Legacy)”,而现在则被正式弃用。你要使用WKWebView代替它了,使用方法可以参考我的WKWebView终极指南(过几天就发)
  2. 现在当我们的iOS应用处于黑暗模式时,有了UIUserInterfaceStyle.dark。它现在还不能在iPhone上激活,但是已经具有一些基础构造——你可以读取黑暗模式图片,当黑暗模式启用时读取UI变化等等。
  3. 任何文本输入文件现在支持密码规则,让系统产生更复杂的密码。这在当前beta版上运行的还不太好,但很快会改进。
  4. UIImagePNGRepresentation()和UIImageJPEGRepresentation()功能已经被.pngData()和.jpegData(compressionQuality:)方法代替。


0