【翻译】TextClassification介绍(一)

2018-10-28 by Liuqingwen | Tags: Android 翻译 | Hits

一、说明

这是一个关于介绍 TextClassification API 的系列文章,总共分三篇,本文是第一篇。非常好的文章,翻译出来分享给大家。 smiley

原文作者:Mark Allison
阅读时间: 5 分钟
原文链接:https://blog.stylingandroid.com/textclassification-part-1/

二、正文

在 API 26 (奥利奥)中安卓引入了一个新的文字功能系统: TextClassification 。这个系统将会在 API 28 ( π )中进一步改进完善。在本次简短的系列中,我们主要会探讨它是一个什么样的系统,如何使用它,以及如何为它添加一些自定义行为。

TextClassification 相当于一种机制,系统通过这种机制可以识别出特定类型的文本,并在用户选择到该文本的时候添加一些适当的操作。常见的文本类型有电话号码,电子邮件地址和 URL 链接,这些特定文本会分别触发启动系统拨号程序,电子邮件客户端和 Web 网页浏览器的操作。所有这些特性属于 TextClassification 的默认服务,并且已内置于 Android 系统,因此,我们要做的第一件事就是先来弄清楚这种服务的工作原理。

我们可以通过适当的系统服务来获取系统默认的 TextClassificationManager :

MainActivity.kt
1
2
textClassificationManager = 
getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager

值得注意的是,当手动执行文本类型检索处理的时候,记住这个操作在计算上是比较耗时的,原因是系统默认的 TextClassifier 服务使用了机器学习模型进行文本分类操作的。出于这个原因,我把这些函数调用包装在了一个使用 CommonPool 作为上下文的异步协程中,这样它能高效地运行在后台线程上:

MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MainActivity : AppCompatActivity() {

private val emailText = "dummy@email.com"
private val urlText = "https://blog.stylingandroid.com"
private val hybridText = "Email: $emailText"
private lateinit var textClassificationManager: TextClassificationManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

classifier()
}

private fun classifier() = async(CommonPool) {
textClassificationManager =
getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager
//...
}
}

文本分类检索服务由两个基本操作组成。首先是分类检索本身。所以我们先传递一个字符串,这个字符串包含了我们所需要分类的文本信息,还需要传递所选子字符串的开始和结束位置,以及一个区域列表。前三个参数意思很明显,最后一个参数需要稍微解释一下。之前我提到过,系统默认的 TextClassifier 使用的是 ML 机器学习模型来执行文本分类的,但实际上根据不同的语言和区域设置会存在多个模型,因此我们需要指定我们所感兴趣的区域,以让它应用正确的模型。当然,我们有必要保持这个列表尽可能的小,因为使用多个 ML 模型进行分析会快速地增加计算开销。

要运行电子邮件地址文本分类,我们首先需要从 TextClassificationManager 中获取 TextClassifier 对象实例,并调用它的 classifyText() 方法:

MainActivity.kt
1
2
3
val textClassifier = textClassificationManager.textClassifier
val emailClassification = textClassifier.classifyText(emailText, 0, emailText.length, LocaleList.getDefault())
println(emailClassification)

运行代码返回的 TextClassification 实例如下:

1
2
3
4
5
6
TextClassification {text=dummy@email.com, 
entities={email=1.0},
actions=[android.app.RemoteAction@4e67771,
android.app.RemoteAction@eb7956],
id=androidtc|en_v6|754483982
}

我们可以看到,运行后它将文本标识为一个可行度为 1.0 的电子邮件地址(可信度取值范围为 0.0-1.0 ,因此这是一个百分百确定匹配)。尽管在我们人眼看来,这显然是一个虚假的电子邮件地址,但它仍然符合有效邮件地址的标准。

我们可以使用同样的 TextClassifier 实例执行另一个分类,这次使用一个包含 URL 链接的字符串:

MainActivity.kt
1
2
val urlClassification = textClassifier.classifyText(urlText, 0, urlText.length, LocaleList.getDefault())
println(urlClassification)

这次生成的 TextClassification 将其标识为一个 URL 地址:

1
2
3
4
5
TextClassification {text=https://blog.stylingandroid.com, 
entities={url=1.0},
actions=[android.app.RemoteAction@33dd4e2],
id=androidtc|en_v6|-1332134748
}

除了能识别特定文本类型的文字之外, TextClassification 还包含零个或多个对已识别类型的处理操作。这些方法操作封装在一个包含 PendingIntent 对象的 RemoteAction 对象中。我们可以调用 RemoteAction 对象,并触发一个载有文本对象的 PendingIntent 对象。当我们检测到一个电子邮件地址时,将会返回该 RemoteAction 并触发一个 PendingIntent 对象以启动邮件客户端,撰写发送给此邮件地址的信件。同样地,返回 URL 链接的同时会启动 Web 浏览器以查看此链接。我们将在本系列的后面部分详细探讨 RemoteActions 。

这里有一件非常重要的事情需要注意的是,当我们调用 classifyText() 方法时, startend 的值必须精确地包含有给定类型的子字符串内容。也就是说,如果我们使用字符串 "Email:dummy@email.com" 作为分析内容,那么对整个字符串进行文本分类的时候,将不会得到一个电子邮件类型的字符串,而是一个“其他”类型的字符串。只有当我们传入合理的、能正确划定 "dummy@email.com" 子字符串在原字符串中的开始和结束位置时,它才能正确的处理并标识出一个电子邮件地址。

这就引出了一个问题:我们如何划分出正确分类类型的子字符串的开始和结束下标?这就需要 TextClassifier 的其他操作了。 proferenceSelection() 方法能标识出一个可以划分为某具体类型的子字符串,但它的工作方式与我们所想象到的稍微不同。如果我们看一下之前例子中的字符串 "Email:dummy_email@address.com" ,我们可能会想到,如果我们传入整个字符串那么它将识别出正确类型的子字符串,但这并不是它的运作方式。它实际上是从一个给定的不确定类型的子字符串的范围开始,一直增长到一个具体类型的较大的子字符串范围,而不是从整个字符串范围缩小到较小的子字符串。这里的用例是当用户长按 TextView 文本控件时,最初始的选择是单个字符,接着 TextClassifier 可以扩展选择范围。在专业术语中,这意味着如果用户长时间按住的是一个包含电子邮件地址的长字符串,那么初始选择将是非常小的,然后会扩展到整个电子邮件地址。

我们可以通过调用 suggestSelection() 方法来查看并实现这个行为,方法的参数与 classifyText() 方法参数一样。在这种情况下,开始和结束位置仅划分出一个单字符,该字符出现在字符串的电子邮件地址子字符串中:

MainActivity.kt
1
2
val suggestions = textClassifier.suggestSelection(hybridText, 10, 11, LocaleList.getDefault())
println(suggestions)

这会返回一个 TextSelection 对象实例,该实例包含子字符串的开头和结尾下标位置,这个位置包含了检测到的电子邮件地址,另外,还包含了文本类型和可信度分数:

1
2
3
4
5
6
TextSelection {
id=androidtc|en_v6|-456509634,
startIndex=7,
endIndex=22,
entities={email=1.0}
}

我们现在可以使用这里的开始和结束位置值来调用 classifyText() 方法了,但在实际应用中我们没必要这么做。应用 TextClassifier 的两个主要用例是 TextView 和 WebView ,但实际上它们都已经在使用它了。在这里,我们可以查看在允许选择的 TextView 控件中按下电子邮件地址或者 URL 链接的时候,扩展到正确的选择位置需要多长的时间,同时还会看到一个弹出窗口,该弹出窗口用于执行所选特定文本类型的相关操作。在这里的情况下,调用它会在 Chrome 浏览器中打开相应的 URL 网址:

textclassification_basic.gif

我真的还想不出会在何种情况下你想要直接来调用使用 TextClassifier 对象,除非你有一个自定义的 View 用来选择一个文本块,而该 View 既不继承于 TextView 也不是 WebView 。如果有这种情况,那么请接受我的慰问吧。

你现在可能会认为我是在浪费你的时间,用来读这篇文章,但我向你保证我并没有。虽然实际上很少有 Android 开发人员调用这些 API ,但实现自定义分类器确是另一回事,很好地理解熟悉 TextClassifier 的工作原理是实现自己需求的基本条件。

在本系列的下一篇文章中,我们将一起探讨如何做到这一点。

三、总结

这篇文章的源代码可以在这里找到: https://github.com/StylingAndroid/TextClassification/tree/Part1

© 2018 , Mark Allison 。保留所有版权。

我的博客地址: http://liuqingwen.me ,欢迎关注我的微信公众号:
IT自学不成才


Comments: