【翻译】TextClassification介绍(一)

一、说明

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

原文作者:Mark Allison

阅读时间: 5 分钟

原文链接:https://blog.stylingandroid.com/textclassification-part-1/

二、正文

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

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

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

textClassificationManager = 
    getSystemService(Context.TEXT_CLASSIFICATION_SERVICE) as TextClassificationManager

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

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() 方法:

val textClassifier = textClassificationManager.textClassifier
val emailClassification = textClassifier.classifyText(emailText, 0, emailText.length, LocaleList.getDefault())
println(emailClassification)

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

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 链接的字符串:

val urlClassification = textClassifier.classifyText(urlText, 0, urlText.length, LocaleList.getDefault())
println(urlClassification)

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

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

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

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

val suggestions = textClassifier.suggestSelection(hybridText, 10, 11, LocaleList.getDefault())
println(suggestions)

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

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自学不成才

<think>好的,用户之前询问了如何部署RNN-T模型,现在又问“介绍下rnn t”,看起来他们可能想更深入地了解RNN-T的基础知识。首先,我需要确认用户的需求是否已经从部署转向了模型本身的介绍。用户可能对RNN-T的结构、工作原理还不清楚,所以需要补充这方面的知识。 接下来,我要回忆RNN-T的核心组成,比如编码器、预测器和联合网络,以及它们如何协作处理序列任务。同时,用户可能对RNN-T的优势和应用场景感兴趣,比如在语音识别中的流式处理能力。另外,需要注意区分RNN-T与其他模型如CTC和Seq2Seq的不同点,以突出其特点。 用户之前的问题涉及部署,所以在介绍模型时可能需要简要提及与部署相关的内容,比如实时性和计算需求,但重点应放在模型结构上。要确保语言通俗易懂,结构清晰,分点说明,可能还需要举例说明应用场景,帮助用户更好地理解。 需要检查是否有技术细节需要展开,比如对齐机制、训练过程等,但不宜过于深入,以免让用户感到困惑。同时,注意使用中文术语,避免过多专业术语,必要时进行解释。最后,确认回答是否符合之前的系统级指令,如数学表达式的格式和结构要求。</think>以下是关于 **RNN-T(RNN Transducer,循环神经网络传感器)**的详细介绍,涵盖其核心原理、结构特点和应用场景: --- ### **、RNN-T 的基本概念** RNN-T 是种专为序列到序列任务(如语音识别、手写识别)设计的端到端模型,最早由 Alex Graves 在 2012 年提出。其核心目标是解决传统 CTC(Connectionist Temporal Classification)模型的局限性,**允许输出序列与输入序列长度动态对齐**,并支持流式处理。 #### **关键特性** 1. **无需强制对齐**:输入(如音频帧)和输出(如文字)无需严格对齐标签。 2. **流式友好**:可逐帧处理输入,适合实时语音识别。 3. **联合建模**:通过联合网络(Joint Network)融合声学与语言模型信息。 --- ### **二、RNN-T 的核心结构** RNN-T 由三部分组成:**编码器(Encoder)、预测器(Predictor)和联合网络(Joint Network)**。 #### **1. 编码器(Encoder)** - **功能**:将输入序列(如音频特征)编码为高层表示。 - **常见结构**:LSTM、GRU 或 Transformer 的变体(如 Conformer)。 - **输入**:序列 $X = (x_1, x_2, ..., x_T)$(如梅尔频谱帧)。 - **输出**:隐状态序列 $H = (h_1, h_2, ..., h_T)$。 #### **2. 预测器(Predictor)** - **功能**:建模输出序列的历史依赖(类似语言模型)。 - **输入**:已生成的历史标签 $y_{<u}$(如已识别的文字)。 - **输出**:隐状态 $p_u$(表示当前上下文)。 - **结构**:通常为单层 LSTM 或 RNN。 #### **3. 联合网络(Joint Network)** - **功能**:融合编码器和预测器的输出,生成当前时刻的标签概率分布。 - **数学形式**: $$ z_{t,u} = \text{ReLU}(W_h h_t + W_p p_u + b) \\ P(y_{t,u} | X, y_{<u}) = \text{Softmax}(W_z z_{t,u}) $$ - $h_t$:编码器在时间步 $t$ 的输出。 - $p_u$:预测器在输出步 $u$ 的输出。 - $y_{t,u}$:在输入时间步 $t$ 和输出步 $u$ 的候选标签。 --- ### **三、RNN-T 的工作原理** #### **动态对齐机制** - 输入与输出通过个**二维网格**对齐,允许模型自由选择对齐路径(见下图)。 - **路径搜索**:通常使用 Beam Search 或贪心算法选择最优路径。 <div align=center> <img src="https://miro.medium.com/v2/resize:fit:720/format:webp/1*Y8p9aYqgXZzqyN7N8t2kOg.png" width="400" alt="RNN-T 对齐网格"> </div> #### **训练目标** - **损失函数**:负对数似然,最大化正确路径的概率: $$ \mathcal{L} = -\log P(Y^* | X) $$ 其中 $Y^*$ 是真实标签序列。 --- ### **四、RNN-T 与其他模型的对比** | **模型** | **对齐方式** | **流式支持** | **输出依赖** | **典型应用** | |----------------|---------------------|--------------|-----------------------|---------------------| | **CTC** | 单调对齐(强制) | 是 | 输出间独立 | 简单语音识别 | | **Seq2Seq** | 注意力机制 | 否 | 自回归依赖 | 机器翻译 | | **RNN-T** | 动态对齐(自由路径) | 是 | 依赖历史输出 | 实时语音识别 | --- ### **五、RNN-T 的优势与挑战** #### **优势** 1. **流式处理能力**:适合实时场景(如语音助手)。 2. **端到端训练**:无需对齐数据,简化流程。 3. **语言模型融合**:预测器隐式建模语言信息。 #### **挑战** 1. **训练复杂度高**:需处理二维动态规划(计算成本高)。 2. **内存消耗大**:联合网络的矩阵运算对设备资源要求高。 3. **长序列建模**:预测器的历史依赖可能限制长文本生成能力。 --- ### **六、实际应用场景** 1. **语音识别(ASR)** - 如 Google 的 Live Transcribe、Apple Siri 的实时转录。 - 支持低延迟流式输出(<300ms)。 2. **手写识别** - 将笔触坐标序列转换为文字。 3. **视频动作标注** - 对视频帧序列生成动作描述文本。 --- ### **七、改进与变体** 1. **Hybrid RNN-T**:结合 CTC 损失加速训练收敛。 2. **Transformer Transducer**:用 Transformer 替换 RNN,提升长序列建模能力。 3. **Emformer RNN-T**:基于内存的高效 Transformer,优化流式处理。 --- ### **八、代码示例(简化版 PyTorch 实现)** ```python class RNNT(nn.Module): def __init__(self, input_dim, vocab_size): super().__init__() # 编码器:BiLSTM self.encoder = nn.LSTM(input_dim, 256, bidirectional=True) # 预测器:LSTM self.predictor = nn.LSTM(128, 128) # 联合网络 self.joint_net = nn.Sequential( nn.Linear(512, 256), # 编码器输出(256*2) + 预测器输出(128) nn.ReLU(), nn.Linear(256, vocab_size) ) def forward(self, x, y): # 编码器处理输入序列 h_enc, _ = self.encoder(x) # h_enc: [T, B, 512] # 预测器处理历史标签 p, _ = self.predictor(y) # p: [U, B, 128] # 联合网络计算所有(t,u)组合的logits logits = [] for t in range(h_enc.shape[0]): for u in range(p.shape[0]): z = torch.cat([h_enc[t], p[u]], dim=-1) logits.append(self.joint_net(z)) return torch.stack(logits) ``` --- ### **九、学习资源推荐** 1. **论文**: - [《Sequence Transduction with Recurrent Neural Networks》](https://arxiv.org/abs/1211.3711)(Alex Graves, 2012) - [《Transformer Transducer》](https://arxiv.org/abs/1910.12977)(Google, 2019) 2. **工具库**: - NVIDIA NeMo:提供预训练 RNN-T 模型和训练工具。 - ESPnet:支持 RNN-T 的语音识别全流程框架。 --- 通过上述设计,RNN-T 在流式语音识别任务中实现了高精度与低延迟的平衡,成为工业界主流的端到端语音识别方案之
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值