AssemblyAI-博客中文翻译-一-

AssemblyAI 博客中文翻译(一)

原文:AssemblyAI Blog

协议:CC BY-NC-SA 4.0

比较 2021 年的端到端语音识别架构

原文:https://www.assemblyai.com/blog/a-survey-on-end-to-end-speech-recognition-architectures-in-2021/

概观

准确性是自动语音识别系统最重要的特征。虽然 AssemblyAI 为我们的语音转文本 API 提供的生产端到端方法能够提供比其他商业级语音识别系统更好的准确性,但仍然可以进行改进以实现人类性能。作为我们核心研发工作的一部分,我们将继续推动语音识别准确性的发展,在本帖中,我们将探讨在研究和行业环境中日益流行的语音识别架构。

1.介绍

随着我们继续我们的核心研发工作,以推动最先进精度的边界,我们开始探索端到端语音识别的架构,这些架构不仅在研究领域,而且在工业生产环境中也越来越受欢迎([1]、[2])。我们特别感兴趣的是那些生产级精度达到或超过传统混合 DNN-HMM ([3]、[4])语音识别系统的架构。考虑到这一点,我们考察了以下两种架构:Listen Attend and Spell (LAS,[5])和递归神经网络转换器(RNNT,[6])。

我们从生产的角度来审视这些新的架构。在第 2 节中,我们报告了对 LAS 和 RNNT 进行的一些最新研究的准确性比较([7]、[8]、[2])。我们还将这些技术与连接主义时间分类(CTC,[9]) ([7],[1])进行了比较,后者是一种更广为人知的端到端方法,也是 AssemblyAI 的早期版本语音识别系统所基于的。我们也看看最近的研究,将 RNNT 和 LAS 结合成一个系统([8],[2])。

在第 3 节中,我们继续关注产品级语音识别,从特征奇偶校验的角度比较 LAS 和 RNNT 模型。我们重点关注语境化([10]、[1])、反向文本规范化([11]、[1]、[8])、单词时间戳以及进行实时语音识别的可能性([1]、[2])。

我们在第 4 部分结束这篇文章,给出了关于我们调查的高层次结论以及未来博客文章的下一步措施。

2.准确(性)

虽然一些研究数据集([12])已经达到了人类同等的语音识别精度,但在具有挑战性的声学环境中,工业生产级应用的单词错误率(WER)远未达到人类水平。这种领域的一个例子是自发的电话交谈,即使在研究数据集中,人类水平的平等还没有实现([13])。

在本节中,我们将重点放在在生产数据上测试的 LAS 和 RNNT 系统上,而不是研究数据集上。我们避免查看研究数据集,因为它们很容易被不能很好地概括真实现场音频信号的模型过度拟合。我们希望专注于包含挑战性特征的数据集,如通道噪声、压缩、环境噪声、串扰、口音和说话者多样性。

2.1 LAS 与 RNNT

在比较基于 LAS 和 RNNT 的架构的精度时,一致的看法似乎是 LAS 的精度优于 RNNT。[7]使用 12500 小时的生产语音搜索流量,通过添加噪声和模拟房间声学进行人工失真,比较 LAS 和 RNNT 模型。WER 在听写和语音搜索话语中都有报道。LAS 和 RNNT 都使用相同的编码器神经网络架构和大小。然而,它们的参数是不同的,并且被初始化为具有相同编码器架构的聚合 CTC 训练网络的参数。解码器也具有相同的架构、相同的层数、大小,并输出 26 个小写字母加上数字、空格和标点符号的集合。因此,就参数数量而言,唯一的区别在于 RNNT 联合网络和 LAS 注意机制。LAS 和 RNNT 模型都不使用任何类型的外部语言模型。表 1 总结了他们的结果,显示 LAS 总体表现更好。

Table 1: Accuracy of RNNT vs LAS

[8]还显示 LAS 总体表现更好。他们强调 RNNT 模型在质量上落后,而 LAS 与混合 DNN-HMM 模型相比表现出竞争性能([11])。[8]还比较了 LAS 和 RNNT 的 WERs。与[7]类似,RNNT 和 LAS 的编码器和解码器网络是相同的,不同之处仅在于联合网络和注意力网络。两者都预测了一组 4096 个单词的输出。两种模型使用的训练数据是相同的,由 30,000 小时的语音搜索训练数据组成,被添加的噪声和房间声学破坏。这些模型在两个测试集上进行测试:一个测试集由短于 5.5 秒的话语(短 utt)组成。)和另一个由长于 5.5 秒的话语组成(长 utt。).显示 LAS 性能更好的结果可以在表 1 中看到。LAS 在长话语上表现出降低的准确性,这归因于注意机制,如[14]中所解释的。

虽然[2]没有明确地比较 LAS 和 RNNT 模型的准确率,但他们确实提到,类似于[8],在低延迟约束下,LAS 的准确率优于传统的 DNN-HMM 模型,而 RNNT 模型则不然。

2.2 LAS 和 RNNT 与 CTC

与基于 CTC 的模型进行比较并不像仅在 LAS 和 RNNT 之间进行比较那么简单。主要原因是 CTC 模型高度依赖于外部语言模型的使用来获得可接受的准确性。LAS 和 RNNT 模型不需要外部语言模型,因为模型中存在解码器组件。

[7]比较没有任何外部 LM 的 LAS、RNNT 和 CTC 模型,其中所有三种模型的编码器架构和尺寸都相同。如表 2 所示,在没有任何外部 LM 的情况下,CTC 的精度显著低于 RNNT 和 LAS。其余的实验细节与 2.1 节所述的相同。

[1]比较了 RNNT 和 CTC,但两者在架构上存在显著差异。CTC 模型包括 6 个 LSTM 层,每层具有 1200 个单元和 400 维投影层。该模型通过 softmax 层输出 42 个音素目标。使用 5 克第一遍语言模型和第二遍 LSTM LM 重评分模型来执行解码。RNNT 模型的编码器包括 8 个 LSTM 层,每个层有 2048 个单元和一个 640 维的投影层。在编码器的第二层之后插入因子为 2 的时间缩减层。预测网络有 2 个 LSTM 层,每个层有 2048 个单元格和一个 640 维的投影层。联合网络有 640 个隐藏单元。输出层模拟 4096 个单词块。RNNT 型号的大小是 120MB,而 CTC 型号的大小是 130MB。

[1]使用 27500 小时的语音搜索和听写数据进行训练,这些数据被人为破坏以模拟房间声学和噪声。表 2 显示了他们的 CTC 和 RNNT 模型在语音搜索和听写测试集上的比较,显示 RNNT 模型明显更好。

Table 2: Accuracy of RNNT and LAS vs CTC

2.3 结合 LAS 和 RNNT

虽然 LAS 被认为具有更好的准确性,但 RNNT 模型被认为具有生产质量特性,如流功能,这使它们更受欢迎。

为了缩小两种架构之间的精度差距,[8]和[2]开发了 RNNT 和 LAS 模型的组合。RNNT 用于第一遍解码,LAS 用作第二遍重新计分模型。

第 2.1 节描述了[8]中关于独立 RNNT 和 LAS 模型的实验细节。两遍实验对 LAS 和 RNNT 使用相同的架构。参数的数量也是相同的,但是这次编码器参数是在它们之间共享的。训练分三步完成。首先,RNNT 模型被收敛。然后 RNNT 编码器被冻结,并用它来训练 LAS 解码器。最后,使用组合损失来一起重新训练 RNNT 和 LAS 模型(具有共享编码器)。表 3 显示,重新评分方法显著提高了 RNNT 模型的准确性。它还改善了 LAS 在较长话语方面的弱点。

[2]还在第二次通过时实施 LAS 重新计分,其中第一次通过是 RNNT 模型。除了在训练期间使用搜索数据之外,他们还使用来自多个其他域(例如:远场、电话)的数据和来自除美国之外的国家的口音语音。用于训练的小时数没有规定,但是与 LAS 重新评分相关的模型架构细节是相似的:在 RNNT 模型和 LAS 模型之间使用共享编码器,并且 LAS 模型用于对来自 RNNT 模型的假设进行重新评分。他们在共享编码器和 LAS 解码器之间增加了一个额外的 LAS 编码器层。表 3 显示 LAS 重新计分显著提高了 RNNT 精度。

更重要的是,表 3 显示了 RNNT 和 LAS 的组合击败了传统的 HMM-RNN 混合模型。传统模型的声学模型输出依赖于上下文的音素,并且使用具有接近 800,000 个单词的语音词典。在第一遍解码期间使用 5-gram 语言模型,并且在第二遍重新评分时使用 MaxEnt 语言模型。传统型号的总大小约为 87 GBs。RNNT+LASS 模型的大小为 0.18 GBs。RNNT 模型有 1.2 亿个参数,LAS 模型(附加编码器和解码器)有 5700 万个附加参数。参数量化为 8 位定点。

Table 3: Accuracy of RNNT and LAS rescoring

3.特征奇偶校验

虽然准确性是语音识别系统最重要的特性,但还有许多其他特性会影响可用性和成本。在本节中,我们将从上下文、反向文本规范化、时间戳和实时语音识别等功能的角度来探讨 LAS 和 RNNT。

3.1 语境化

说话者在对话中所说的话取决于说话者所处的语境。例如,如果说话者想给朋友打电话,他很可能会说出朋友的名字。该名称可能由不常见的或外国的单词组成。这些单词在 ASR 系统的训练数据中可能只有很少的样本或没有样本,它们可能不会被正确识别。

ASR 中的语境化是关于将模型偏向属于语境的单词和短语,而不伤害非语境句子的 ASR 性能。

[10]通过将表示上下文信息的动态类结合到语言模型中,在 CTC 系统中实现了这一点。在解码期间,动态类被包含上下文短语和词汇(例如,联系人姓名)的有限状态转换器(FST)替换。然后,对包含对应于那些上下文短语和词汇的 ngrams 的语言模型应用即时重新评分。使用这种类型的上下文机制的准确性效果如表 4 所示。虽然在通用测试集上没有显示结果,但是在上下文测试集上的准确性得到了显著提高。

[1]在 RNNT 模型上实现上下文化。这是通过表示整个上下文 ngram 模型(而不仅仅是词汇和短语)的 FST 来完成的,并且该模型在波束搜索解码期间通过浅层融合与 RNNT 模型内插。表 4 所示的精度改进也非常显著。通用测试集的结果没有显示,但是上下文测试集的改进表明上下文化也可以在 RNNT 框架上成功实现。

[8]还在两遍 RNNT + LAS 记录系统中通过 FSTs 和浅层融合实现了上下文化。由于浅层融合相当于分数的插值,就像重新计分一样,因此可以假设在第一遍 RNNT 解码期间只需要一个单偏 FST。他们的上下文测试集结果用于显示其他调优方法的性能影响,而不是上下文化本身,因此我们在这里不总结这些结果。

关于 LAS,[15]在 LAS 系统的顶部选择性地实现浅层融合。[16]更进一步,以一种全神经的方式在 LAS 框架内实现语境化,称之为 CLAS。使用语境化的准确性提高是显著的,但是是在人工测试集上测量的,这些测试集或者是使用 TTS 生成的,其中语境是从真实转录生成的,因此我们在此不总结这些结果。

3.2 反向文本规范化

反向文本规范化将口语领域(例如:“一二三一街”)中的转录转换成书面领域(例如:“123 一街”)。具有传统 DNN-HMM 模型的语音识别系统通常使用单独的模型来实现这一点。ASR 系统的输出在口语领域中,通过一个独立的模型传递,该模型进行到书面领域的转换。

端到端 ASR 系统也可以做到这一点。但接下来的工作表明,单个端到端系统可以学习所有声学、语音、语言和标准化模型。归一化可以通过在它们的模型的输出层中包括数字、空格和标点符号来学习。培训数据必须在书面领域。

Contextualization Accuracy

[11]实现了一个 LAS 系统,能够匹配混合 DNN-HMM 系统的最新精度。这个网络将声学、发音和语言模型合并成一个输出书面文本的单一网络。它明确提到不需要文本标准化组件来标准化识别器的输出。

[1]实现他们的 RNNT 模型的方式是,他们也在书写域中输出文本。他们能够通过在他们的训练数据中包括用文本到语音(TTS)合成产生的数字发音来提高他们的数字输出性能。

[8]对双通 RNNT/LAS 模型和传统模型进行了误差分析比较,认为双通模型能够具有良好准确性的一个原因是因为已经学习了归一化。

3.3 单词时间戳

根据应用的不同,另一个非常有用的特性是在语音识别结果中提供时间校准。这些通常以单词时间戳的形式提供,即音频流中已识别单词的开始和结束时间。例如,在播客或视频的字幕应用中,此功能是必需的。

最初形式的 LAS 系统缺乏产生时间戳的能力。如[5]中所述,文本和音频之间的对齐是由注意机制提供的。然而,产生的注意力系数跨越整个音频流。虽然已有关于单调注意机制的研究([17]、[18]、[19]),这在提供单词时间戳方面可能更有前途,但这项研究似乎仍处于早期阶段。

如[6]中所述,RNNT 解码不提供字时间戳。解码过程为波束内的每个假设提供所有时间对准的总概率。然而,在单次对齐中,解码器或者将单词片段(或字形)与输入特征向量对齐,或者决定使用另一个特征向量。通过对[6]中的解码算法进行小的修改,提取时间对准应该是可能的。

[20]研究了两遍系统(如[8]和[2])中的字定时,并认为由字块 RNNT 系统发出的字定时不如低帧速率传统模型(LFR,如[21])中的那些精确。他们通过添加单词边界输出标签来解决这个问题。它们还增加了与对准误差相对应的两项损耗。他们这样做是为了 RNNT 损失和 LAS。在 LAS 中,他们让单个注意头负责预测对齐,其中单词片段的时间对齐对应于该注意头的注意概率向量中的最大值。通过这些修改,他们发现 LAS 在计算单词计时方面是最好的,比 LFR 基线更好。

3.4 实时语音识别

实时语音识别受到三个方面的限制:

  • 第一个方面由解码算法在消化特征向量时提供语音识别结果的能力来定义,这被称为流式语音识别。这在 LAS 中显然是不可能的,但在 RNNT 中是可能的。这在[8]中有解释。LAS 的注意机制要求编码器在解码器开始发出输出标签之前处理整个音频流([5])。在 RNNT 的情况下,对于每个输入特征向量,发出一个或多个输出标签([6])。
  • 第二个方面由解码算法和模型的 CPU 或 GPU 消耗来定义,这可以通过实时因子(RTF)来测量。RTF 是处理一个话语所需时间与话语长度的比值。[1]执行对称参数量化,将移动设备中 RNNT 模型的 RTF 从 1.43 降至 0.51。
  • 第三个方面由系统的等待时间定义,即用户从停止说话到收到最终语音识别结果必须等待的时间。在混合 RNNT 和 LAS 重新记录系统中,LAS 计算必须在 RNNT 解码完成后进行。这意味着 LAS 将其所有的时间消耗添加到延迟时间中。[2]通过将部分 LAS 计算移到第一个 RNNT 通道来减少延迟。它还可以并行处理来自 nbest 晶格的弧的 LAS 处理。这几乎消除了 LAS 产生的所有延迟。此外,RNNT 模型的输出层中包括一个语音结束标签,有效地允许模型学习何时预测端点。这消除了对外部端点指示器的需要,并且相对地将 WER 提高了 10%(与使用外部端点指示器相比)。

4.结论

我们综述了最近关于听、听、拼和递归神经网络传感器的研究文献。我们保持了与生产精度和生产特性相关的视角。文献表明,RNNT 模型比 CTC 更准确。它还表明,虽然 LAS 可能比 RNNT 更准确,但与混合 RNN-HMM 模型相比,两者的混合可以实现更好的准确性和特征奇偶性。

这些研究文献中使用的测试集可能是独特的,并且不同于其他领域的测试集,但是精度表可以为实验提供良好的指导,这些实验可以在将 LAS 或 RNNT 架构引入我们自己的 ASR 设计的过程中执行。

在这篇博文中,我们已经从较高的层次上了解了准确性和功能对等性,在以后的博文中,我们将重点关注 LAS、RNNT 的实现细节,并将它们与 CTC 进行比较。

参考

在 5 分钟内为应用程序添加语音识别

原文:https://www.assemblyai.com/blog/add-speech-recognition-to-your-streamlit-apps-in-5-minutes/

在这个新系列中,我们将在每个视频中学习如何将 AssemblyAI 与不同的技术集成。在本视频中,我们将了解一款 Streamlit 应用。

由于 AssemblyAI 易于使用的语音转文本 API ,我们学会了如何快速将转录内容引入到预先制作的应用程序中。只需要 5 分钟和几行代码就可以生成音频文件的转录。我们开始吧!

https://www.youtube.com/embed/-9J4FZq0t2k?feature=oembed

人工智能研究综述-合并模型模置换对称

原文:https://www.assemblyai.com/blog/ai-research-review-merging-models-modulo-permutation-symmetries/

本周的人工智能研究综述是 Git Re-Basin:合并模型模置换对称

Git 重组:合并模型模置换对称

这篇论文有什么令人兴奋的地方

这篇论文中,作者表明足够宽的神经网络的损失景观本质上具有单个盆地。这导致计算相同函数的相同模型权重的许多排列。

主要发现

模型权重之间的线性插值是 SGD(随机梯度下降)的突发行为,而不是模型属性。

在两个不同的数据集上训练的两个模型(具有相同的架构),具有不同的参数集,例如学习速率、辍学等,可以被合并而没有损失成本。

作者提出了三种方法来寻找不同模型的神经元之间的映射。

Source

作者讨论了选择排列的三种方法:

  • 匹配激活:为了完成相同的任务,两个模型必须学习相似的特征——可以假设在这种情况下,激活之间将存在线性关系。
  • 匹配重量:直接检查模型重量,而不是激活。
  • 用直通估计器学习排列

Source

我们的外卖

本文的发现可能有助于分布式模型训练。

它还为联合学习引入了新的途径——可以在数据集上训练模型而不损害隐私,然后与现有模型合并以提高性能。

最后,该论文还提供了模型优化的新途径——根据不同数据训练的不同模型,收敛到几乎相同的流域,表明存在全局最小值。

人工智能研究评论-多流 CNN

原文:https://www.assemblyai.com/blog/ai-research-review-multistream-cnn/

本周的 AI 研究综述是 多流 CNN 进行鲁棒声学建模

用于鲁棒声学建模的多流 CNN

这篇论文有什么令人兴奋的地方

多流 CNN 建立在这样一种思想上,即通过在不同的模型上使用不同的膨胀率,各层正在以多种分辨率学习特征的“不同”视图。

主要发现

TDNN-F 中的卷积矩阵被分解成具有正交约束的两个因子,这显然提高了这一特定任务的性能。

多流 CNN 基本上是 N 个不同卷积层的堆栈,并行处理输入,并在最终层连接输出。

Source

我们的外卖

多分辨率优化有助于模型跨不同的“视点”学习更健壮的特征这种方法可以与不同的建模技术一起使用。

由于其数学特性,TDNN-F 层对标准 conv1d 层进行了改进。

人工智能研究评论-拼写和 ASR

原文:https://www.assemblyai.com/blog/ai-research-review-spelling-and-asr/

本周的人工智能研究综述是 面向端到端语音识别系统定制的上下文拼写校正

这篇论文有什么令人兴奋的地方

本文提出了一种通用的 ASR 偏置解决方案,该方案对域不敏感,可以在不同的场景中采用。

自回归模型在稀有词或专有名词上实现了 51%的相对单词错误率(WER)减少。

作者提出了一种在训练和推理过程中处理大型上下文短语列表的新方法,以提高效率和可扩展性。

主要发现

这是一个 seq2seq 模型,它通过关注 ASR 假设和外部上下文单词/短语来纠正罕见单词或专有名词的拼写。作者提出了一种在训练和推理过程中处理大型上下文短语列表的新方法,以提高效率和可扩展性。

Source

通过结合浅层融合和上下文拼写校正实现了最佳的 WER 减少。该模型在高 OOV 率测试集上工作得非常好,这表明该模型在子词级别而不是词级别学习错误模式。

Source

我们的外卖

与偏置 ASR 编码器(例如,上下文 RNN-T,CLAS)相比,作为后处理的 ASR 偏置是改善端到端 ASR 中专有名词检测的合理选择。

虽然 AR 和 NAR 解决方案都很好地减少了专有名词 WER,但 NAR 模型将推理速度提高了 2.1 倍。

Aloware 如何在 6 周内推出人工智能智能转录和问答

原文:https://www.assemblyai.com/blog/aloware-shipped-ai-powered-smart-transcription-qa-6-weeks/

“The accuracy was strong, but the great documentation and unique models like Auto Chapters and Sentiment Analysis is what really won us over.”

作为一个联络中心软件即服务,Aloware 专门帮助公司将更多的线索转化为全球公司的交易。Aloware 还支持公司合规性和效率计划,并帮助拨打了 2000 多万个电话,发送了 3000 多万条短信/彩信,联系了 1500 多万名联系人。​

为客户电话和信息提供便利是一个很好的起点。但是这些电话和消息产生了大量未使用的非结构化数据。Aloware 的产品团队想知道人工智能模型是否能有所帮助。

得益于机器学习和深度学习的最新进展,今天的人工智能模型比以往任何时候都更加准确。最复杂的模型是当今最令人印象深刻的技术背后的大脑,如自动驾驶汽车、自动化欺诈检测、个性化推荐等。这些模型背后的原理也正在被集成到语音识别和自动文本分析等技术中。

考虑到这一点,Aloware 开始为他们的用例寻找合适的人工智能技术。他们的产品团队知道自动、准确的转录非常适合他们的产品路线图,但想知道他们还可以用这些转录数据做什么。产品团队的部署时间也很短,因此需要一个单一的 API 来满足他们的所有需求。

Aloware 的搜索让他们找到了 AssemblyAI。AssemblyAI 的语音转文本音频智能API 帮助产品团队应用最新的人工智能模型来构建新功能,为他们的客户带来胜利的结果——在性能、生产力和效率方面。

仅用 6 周时间部署人工智能

利用 AssemblyAI 的人工智能模型,Aloware 能够在短短 6 周内为其客户提供智能转录。现在,Aloware 平台上的大多数 QA 任务都是自动化的,帮助他们的客户更快地进行 QA。

有了 AssemblyAI,Aloware 收到的每一个呼叫都会被自动转录,并且具有接近人类水平的准确性。在过去,电话呼叫的自动转录是可用的,但准确率较低-传统的语音转录模型只能以大约 70%的准确率转录。他们还会省略基本的标点符号、大小写和格式,这使得抄本难以阅读。

下面是未格式化的文字稿的一个片段,以转录的采访为例:

When i'm gone for a while but hes always supportive so that always 
takes a lot of stress off and lets me play and its a lot easier sure 
wow well back to your college and pro career i know you are a usc 
player and im sure that was an amazing team experience but a lot of 
college players dont go on to go pro even though theyre incredible 
players and the college level is very high its a shame that there isnt 
much more interest in college tennis but how do you talk about mindset 
shift from choosing tennis as a career versus like business or coding 
or something you know and then making that decision from usc to just go 
pro one of my goals when I was little was to always play professional i 
think maybe some people just want to go to college or get a scholarship 
and then end there but i knew i always wanted to continue my tennis

令人欣慰的是,现代人工智能模型,如 AssemblyAI,包括自动标点和大小写段落检测说话人双音化,这使得最终用户更容易阅读抄本。

下面是应用了上述模型的相同脚本的样子:

<Speaker A> When I'm gone for a while, but he's always supportive, so 
that always takes a lot of stress off and lets me play, and it's a lot 
easier.

<Speaker B> Sure. Wow. Well, back to your college and pro career. I 
know you are a USC player and I'm sure that was an amazing team 
experience but a lot of college players don't go on to go pro, even 
though they're incredible players and the college level is very high. 
It's a shame that there isn't much more interest in college tennis. But 
how do you talk about mindset shift from choosing tennis as a career 
versus, like, business or coding or something, you know, and then 
making that decision from USC to just go pro?

<Speaker A> One of my goals when I was little was to always play 
professional. I think maybe some people just want to go to college or 
get a scholarship and then end there, but I knew I always wanted to 
continue my tennis.

Aloware 能够应用这些模型为其最终用户打包一个高价值的智能转录工具:

AssemblyAI 还提供了一套 AI 模型,帮助产品团队在音频数据的基础上构建高 ROI 工具。对于 Aloware 来说,这些模型——特别是汽车章节和情感分析——帮助其团队填补了当前向最终用户提供的产品中的关键空白。这包括构建新的工具,帮助他们的客户深入了解客户情绪、销售代表的表现和电话分析,以改善客户体验和互动。

Aloware 还喜欢这些人工智能模型来自单一提供商,使工具更容易、更快地构建。

“精确度很高,”Aloware 的产品经理内森·韦伯解释说。但“伟大的文档和独特的模型,如汽车章节和情绪分析,才是真正赢得我们的原因,”他继续说道。

Auto Chapters 是一个文本摘要模型,可以自动显示音频和视频流中的关键亮点和摘要。自动章节模式的工作原理是首先将音频/视频流分割成逻辑的、带有时间戳的章节,或谈话主题自然变化的点。然后,该模型为这些章节中的每一章生成一个简短的摘要。结果类似于启用自动章节时 YouTube 在视频下方显示的内容。

对于一个软件,Auto Chapters 模型通过使通话记录更容易消化和处理来加快质量保证(QA)。

Webb 解释说:“ Auto Chapters 对希望快速、智能地对录音电话进行质量 QA 的客户特别有帮助。”

情绪分析,AssemblyAI 的另一个人工智能模型,检测并标记语音片段中的积极、消极和中性情绪。情绪分析对于跟踪不同地点、时区、产品、支持代理等的客户意见和态度非常有用。

最后,Webb 解释说,AssemblyAI 通过其人工智能研究对持续模型和功能改进的承诺是与初创公司的服务相匹配的一个重要决定因素。

将人工智能注入联络中心

Aloware 对精确的转录和人工智能功能感到兴奋,它现在可以为客户提供 AssemblyAI 最先进的人工智能模型。

此外,与 AssemblyAI 的合作进展顺利,Webb 说:“持续的支持一直很强,AssemblyAI 继续像真正的合作伙伴一样行事,而不仅仅是供应商。”

Aloware 的结果同样令人印象深刻。“AssemblyAI 是我们开发并提供给客户的第一个真正的机器学习功能,”Webb 解释道。“它为我们的客户节省了接听长时间电话的时间。此外,该工具为电话回访打开了一个新的世界,提供了不可预见的洞察力和绩效跟踪。他继续说道:“客户不断告诉我,这是 Aloware 有史以来最酷的产品之一。

AssemblyAI 的人工智能模型也帮助 Aloware 通过其新的自动呼叫 QA 功能赢得了更多客户。

Aloware 的下一步是什么?“在不久的将来,我们的团队正在为经理开发汇总报告,以快速查看代理呼叫性能,”Webb 说。“从长远来看,我们希望使用 AssemblyAI 为相关的糟糕通话质量提供即时通知。可能会有更多令人兴奋的功能出现。”

那些想了解更多关于 Aloware 的信息或注册其智能转录服务的人可以在这里注册。

了解更多关于 AssemblyAI 可以为 CCaaS 平台做些什么

阅读我们关于人工智能模型如何帮助建立基于云的智能联络中心的最新博文

Learn more

泊松流生成模型简介

原文:https://www.assemblyai.com/blog/an-introduction-to-poisson-flow-generative-models/

在过去的几年里,生成式人工智能模型取得了长足的进步。受物理学启发的扩散模型已经在几个领域达到了最先进的性能,为像 T2 稳定扩散模型、T4 扩散模型、达尔-E 2 扩散模型和图像模型提供了动力。

麻省理工学院的研究人员最近公布了一个新的受物理学启发的生成模型,这次是从电动力学领域汲取灵感。这种新型的模型——泊松流生成模型(PFGM)——将数据点视为带电粒子。通过跟踪数据点产生的电场,PFGMs 可以创建全新的数据。下面我们看到了 PFGM 生成的人脸图像:

CelebA images generated with a PFGM (video provided by the PFGM authors)

PFGMs 为新的研究途径奠定了令人兴奋的基础,特别是考虑到它们在图像生成任务上比扩散模型快 10-20 倍,性能相当。

在本文中,在学习如何使用 PFGMs 对 进行训练和采样 之前,我们将在 PFGM 理论上对进行一个高级的观察。在那之后,我们将再看一遍理论,这次从基本原理开始进行 深度探讨 。然后,我们将看看 PFGMs 如何与其他模型和其他 结果 相比较,然后以一些 最终结果 结束。让我们开始吧!

介绍

在人工智能的发展过程中,已经进化出了几个生成模型家族。一些方法,如标准化流程,提供了样本可能性的明确估计。其他方法,像 GANs ,不能明确计算可能性,但是可以生成非常高质量的样本。**

在过去的几年中,已经进行了大量的研究工作来开发扩散模型。扩散模型从物理学中获得灵感,特别是热力学/统计物理学,注意到例如气体的任何局部分布将最终通过随机运动均匀地扩散到整个空间。

****

A localized gas (purple particles) will spread out to evenly fill a room over time simply through random motion****

也就是说,随着时间的推移,任何初始分布都将转换为均匀分布。扩散模型从这个想法中获得灵感,并将图像放在一个“高维空间”中,以类似的方式用随机噪声破坏它们。

****

The pixels in an image can be viewed as a localized cloud of particles that we "diffuse" into random noise over time****

目标是训练一个机器学习模型,学习如何通过时间倒退,逆转这一腐败过程。如果我们能够成功地学习这样的映射,那么我们就有了从简单分布(高斯分布,在扩散模型的情况下)到数据分布的转换。然后,我们可以简单地通过从高斯分布中采样并应用学习到的变换来生成数据。**

泊松流生成模型的灵感来自于与扩散模型非常相似的方式,但是它们来自于电动力学领域而不是热力学领域。基本和基础的发现是超平面中电子的任何分布产生电场,该电场将该分布转换成均匀的角度分布,因为该分布根据场定义的动力学随时间演化。****

****

Treating a data distribution as a charge distribution defines an electric field that transforms the distribution into a uniform hemisphere over time****

如果我们知道分布产生的电场(又名泊松场),那么我们可以从半球上均匀采样的点开始,并以相反的时间运行动态以恢复原始数据分布。因此,物理定律提供了简单分布和数据分布之间的可逆映射,产生了一种生成类似于标准化流的新数据的方法。**

有了 PFGMs 的总体工作原则,现在让我们更仔细地看看这一工作原则在实践中是如何体现的。

泊松流生成模型-概述

如上所述,PFGMs 的性能取决于(几乎)由超平面中的任何分布产生的泊松场在足够远的距离处导致均匀的角度分布的结果。直觉上,我们来考虑一下为什么会这样。

考虑由任意电荷分布产生的电场。非常接近分布,电场将非常复杂,并且通常具有高曲率。

****

A charge distribution (purple) and the electric field lines (black) it generates****

然而,在离电荷分布非常远的距离 d 处,场要简单得多。在这样的距离下,电荷分布中的所有点离 d 的距离大致相同。这意味着电荷分布可以被认为是“塌陷”到一点,将其所有电荷集中在这一点上。点电荷的电场非常简单——方向呈放射状,大小与点电荷的距离成反比:

****

At a far distance, the charge distribution "looks like" a point charge, with a correspondingly simple electric (Poisson) field****

因此,当我们从电荷分布中“缩小”时,它看起来越来越像具有相应电场的点电荷:

一般来说,物理场表现良好,电磁场也不例外。因此,分布附近的局部复杂电场(或)必须以平滑的方式连接到远离分布的电场,我们已经看到它是纯径向的,因此电场线看起来像这样:**

假设电场在很远的距离上是径向的,那么在这个距离上与电场相切的表面是球形的。PFGM 的作者证明,不仅这个表面是球形的,而且它的通量密度是均匀的。下面我们看到一个心形电荷分布在 z=0 平面,以及它产生的一些电场线(黑色箭头)。通过封闭半球面的通量密度(几乎)是均匀的。

****证明直觉

感兴趣的读者可以在 PFGM 论文的附录 A.1 中找到这一说法的完整证明——我们在这里只提供一个草图。

考虑电荷分布正上方的一小块区域 dA。将 dA 沿磁场扫过半球上的区域 dP 所产生的体积称为表面积为 s 的 V,证明取决于以下事实:( i) V 不含电荷,( ii) P 根据定义与磁场正交。因此,通过 dA 的流入流量必须等于通过 dP 的流出流量。假设无限小的流入和流出通量相等,我们发现 dP/dA 与电荷分布成正比。**** ****

(source)

因此,如果我们让电荷分布沿着电场线发展,它将转变成均匀的半球形分布。

By following the electric field lines it generates, a heart-shaped distribution evolves into a uniform angular distribution over time (modified from source)

如果我们学习高维空间中的泊松场,例如图像数据分布,那么我们可以简单地从高维半球均匀地采样点,并在相反的时间运行动力学,以从数据分布生成图像:

Uniformly sampled points on the hemisphere can be transformed into samples from the data distribution by evolving them backwards through the Poisson field generated by the data distribution (modified from source)

我们如何着手学习和使用由数据分布产生的泊松场?

学习泊松场

我们试图在由数据分布产生的泊松场的影响下模拟粒子的动力学。现在来看看我们是怎么学习这个泊松场的。

术语注释

作者定义了术语泊松场来区分高维场和特殊的三维场,三维场有时保留了术语“电场”。我们将互换使用这些术语,在画有物理意义的平行线时强调使用术语“电场”,而在更抽象的讨论中强调使用术语“泊松场”。

步骤 1 -扩充数据

下面我们看到一个视频,显示“PFGM”根据它产生的泊松场演化。特别地,我们注意到数据分布位于一个 2 维的平面上,但是被映射到一个 3 维的半球上

Animation provided by the authors

这通常对于 PFGMs 来说是真实的- N 维数据被用额外的维度 z 扩充,并且被放置在新的(N+1)维空间的 z=0 超平面中。然后将数据映射到(N+1)维半球。

这种空间增大的原因是为了避免模式崩溃。下面我们看到 XY 平面上均匀圆盘产生的(负)泊松场。我们的采样过程包括在空间中随机选择点,并沿着场回到数据分布。正如我们所看到的,所有的轨迹都汇聚到原点,这意味着我们的模型将生成没有变化的图像,因此会遭受模式崩溃。

Without augmenting the data with an additional dimension, all trajectories converge to the origin resulting in mode collapse (source)

用一个额外的维度来扩充数据可以避免这个问题。下面我们从上面看到同样的二维均匀圆盘,现在在 XY 平面上,增加了维度 z。

After augmenting the data with an additional dimension, many more trajectories are available that intersect with different points in the distribution, therefore avoiding mode collapse (source)

虽然我们仍然有一个固有的二维数据分布,但它现在被嵌入到一个三维空间中,从而生成一个 3 维泊松场。如果我们现在对这个 3 维空间中的点进行采样,例如从 YZ 平面,并沿着它们的轨迹穿过场到达 XY ,我们得到一系列新的轨迹,这些轨迹不再折叠到原点,而是与数据分布中的不同点相交。也就是说,我们的模型不再遭受模式崩溃

步骤 2 -计算经验场

要使用 PFGMs 进行采样,我们必须知道泊松场。为了准确地了解泊松场,我们需要准确地了解数据分布,但是数据分布正是我们想要从中取样的。如果我们首先已经知道分布的解析形式,我们就不会关心它产生的泊松场。

我们将学习由被视为点电荷的训练数据产生的经验场,而不是试图学习精确的泊松场。一些读者可能会注意到,这种方法让人想起我们在介绍可微编程时遇到的运动学问题。

Differentiable Programming - A Simple Introduction

下面是一个示例分布(浅紫色),我们试图为其生成数据,以及从该分布中采样的几个数据点(紫色点)。此外,在空间中有一个随机选择的点(红点),我们试图在这个点上估计泊松场(或者等价地,计算经验场)。

为了计算经验电场,我们将每个数据点电荷产生的电场相加,由于叠加原理,该电场相当于该点的总经验电场。每个单独的数据点的场贡献可以在下面看到一个紫色箭头,以及一个红色箭头表示的净场。

我们计算了空间中许多随机采样点的经验场,采样优先选择靠近数据点的点,因为场的曲率在这些区域会更显著,因此需要更高的分辨率来充分逼近。

步骤 3 -计算损失并更新函数近似值

为了使用 PFGMs 进行采样,我们需要泊松场的连续 解析表示,而不仅仅是像上面计算的那些离散点的值。因此,我们必须在这些点上为经验场训练一个函数逼近器。这个近似器应该是什么样子的?

函数逼近器必须接受一个 N+1 维向量,表示我们的扩充空间中的一个点,并返回一个 N+1 维向量,表示该点的经验场。这种映射的自然选择是 PFGM 作者实现的 U-Net 架构。

A U-Net (block diagram) accepts a point in space (blue vector) and returns the approximate empirical field at that point (red vector) generated by data points sampled from the data/charge distribution (purple)

为了训练 U-Net,我们只需计算批次的平均 L2 损失,然后用 Adam 等基于梯度的优化器进行训练。就是这样!

使用 PFGMs 采样

在上一节中,我们学习了如何为被视为带电粒子的许多数据点生成的经验场训练函数逼近器。在训练了现场估计量之后,我们实际上如何使用该估计量从原始数据分布中进行采样?

回想一下泊松场轨迹构成了数据分布和均匀半球之间的双射。因此,为了从数据分布中采样数据点,我们从均匀的角度分布中采样点,然后使它们沿着泊松场向后移动直到我们到达数据分布所在的 z=0 超平面。相应的微分方程是:

这个方程表示,在每一时刻,点应该在负泊松场的方向上移动。因此,对应的解将是沿着泊松场向后追溯到 z=0 数据分布超平面的轨迹。

在实践中,为了使用泊松场生成模型生成数据,我们:

  1. 在一个大半球上均匀采样数据
  2. 使用 ODE 解算器沿着泊松场向后演化这些点
  3. 向后进化,直到我们到达 z=0 ,此时我们已经从训练分布中生成了新的数据

实施说明

实际上,学习和采样过程要比这复杂得多,但是我们将这个讨论保留到下面的深度探讨部分。

就是这样!下面我们可以看到粒子沿着泊松场向前和向后移动,形成我们上面看到的心形分布。

Video courtesy of the authors

在视觉空间中,采样过程的向后演变如下所示:

Video courtesy of the authors

现在我们已经了解了 PFGMs 的工作原理,让我们看看如何使用它们来生成图像。或者,你可以跳到深潜部分进行更深入的治疗。

使用 PFGMs 生成图像

要了解如何使用 PFGMs 生成图像,请遵循下面的 Colab 笔记本。

Poisson Flow Generative Models in Colab

该笔记本显示了如何为 Colab 设置本地环境,如何下载可用的预训练模型,以及如何使用它们从 CIFAR-10CelebA (62 x 64)和 LSUN bedroom 数据集采样图像。

想更多地了解 JAX 吗?

PFGM 存储库需要安装 JAX -在我们简单易懂的概述中了解你需要了解的关于 JAX 的一切。

Check it out

继续下一节,深入了解 PFGMs 的理论,或者跳到结果来看看 PFGMs 的表现。

泊松流生成模型——深度探讨

在本节中,我们将从基本原理开始,对 PFGMs 进行更深入的研究,以阐明它们的内部工作原理。我们从讨论高斯定律开始。

高斯定律和势函数

在 PFGMs 中,我们试图通过将数据分布转换为电荷分布并利用其产生的电场定义的动态来对其进行采样。电荷分布和它产生的场之间的关系由高斯定律定义,如下所示(省略常数),其中 E 加粗表示它是一个矢量场。

我们定义一个势函数为负梯度为电场的任何函数。也就是说,φ是 E 的势函数,如果

虽然势函数有许多应用,但在我们的例子中,我们感兴趣的是用势函数来描述高斯定律。

泊松方程和格林函数

让我们用势的定义把它代入高斯定律:

结果称为泊松方程,是一个定义了势函数和产生它的电荷密度函数之间关系的方程。泊松方程不属于电动力学,它渗透到物理学和工程学的许多领域。高斯定律的这种等价形式有什么特别之处?

如果我们更仔细地观察这个结构,我们会看到三个部分。一个运算符( L ) ,一个运算符作用于其上的标量函数( y ),以及另一个作为该运算结果的标量函数( f )。

特别地,德尔平方算子被称为 拉普拉斯算子 ,并且是线性微分算子 。也就是说,拉普拉斯的应用导致线性微分方程。给定Lf恢复 y 的问题是物理和工程中非常常见的问题。

我们如何着手解决这个问题?也就是说,对于给定的电荷分布,我们如何找到它产生的势函数?为此,我们将利用运算符的线性。让我们试着把电荷分布分割成几个小块作为近似,用一个有限的,单位高度的均匀分布作为例子。

我们用点电荷来近似这个连续分布,在这种情况下用 4。

点电荷由狄拉克δ函数表示。对于那些不熟悉的人来说,狄拉克δ函数(或简称为“δ”)代表点电荷的电荷密度,因此函数上的积分等于单位电荷。通过这种近似,我们有

这个近似值有什么帮助?回想一下,拉普拉斯算子是一个线性微分算子——我们现在将利用这种线性。特别的,我们把第 I 个点电荷的势函数叫做( \varphi_i )。那么我们有

其中作为拉普拉斯的线性的结果,最后的等式是有效的。因此,我们有(对于一组给定的边界条件)

我们发现,我们可以通过对点电荷的电势求和来近似电势-这是一个非常有用的结果,直接源于拉普拉斯的线性。

下面我们绘制了上述每个点电荷的电势(其形式我们将在后面推导),以及点x’的估计电势。我们通过对该点的每个单独电位的值求和来获得该估计值,其中每个贡献可以在图中用与相关电位相同的颜色看到

我们可以更简洁地表达这一点,将( \varphi ' )定义为以下等式的解:

也就是说,给定空间中的位置 i ,( \varphi ' )给出空间中任意点的电势 x ,该电势由以 i 为中心的狄拉克δ电荷分布产生。使用这个新定义的函数,让我们通过增加用来近似分布的点电荷的数量来改进我们对连续电荷分布产生的真实电势的估计。对于空间中的任意点集,我们有

也就是说,电势已经被近似为以 I 中的点为中心的任意数量的点电荷电势的总和。现在让我们将点的数量增加到无穷大,将离散和转化为积分:

在这种情况下,我们的近似不再是近似,并且精确地为真(如果我们只在非零电荷密度的区域上积分),但是有一个警告。回想一下,我们的δ函数代表一个单位点电荷的电荷密度。因此,在上面的积分中,我们隐含地假设我们有一个单位电荷密度。对于上面的电荷密度示例,这很好,但如果我们有不同的电荷密度,比如:

我们现在必须考虑这个分布中变化的电荷密度。在这种情况下,我们简单地将 乘以积分中的电荷密度,通过电荷分布缩放每个点处的单位电荷密度,以在积分结束时给出适当的值。我们也用符号 G 代替( \varphi '),得到积分的最终形式:

这是一个非常重要的结果。它说,为了求解任意电荷密度的泊松方程来计算它的电势,这可能需要求解一个非常复杂的微分方程,简单地求解点电荷的泊松方程就足够了。只有这个解和上面的方程,我们原则上可以计算出任何电荷分布的精确电势。上面的函数 G 被称为 格林函数 ,并且每个线性微分算子都有一个对应的格林函数(也称为脉冲响应),该函数就是当被算子作用时产生δ函数的函数。

总而言之,要找到任何电荷分布的精确势,我们要做的就是找到点电荷的势,原则上,我们可以计算任何电荷分布的精确势。

拉普拉斯格林函数和电场

如上所述,每个线性微分算子都有自己的格林函数。拉普拉斯算子的格林函数为

其中 N 是空间的维数,S 是单位半径的(N-1)-球体的表面积。

我们与 PFGMs 的目标;但是,是学习泊松场而不是的势函数。我们的直觉可能会告诉我们,上述论点,即势函数可以被认为是点电荷势的总和,也适用于泊松场。这种直觉确实是正确的,我们可以从数学上看到如下

其中, x 下标表示相对于 x 的梯度,因此与积分互换没有问题。也就是说,将泊松场估计为点电荷产生的泊松场之和是有效的。

在我们的例子中,格林函数(即点电荷产生的泊松场)的梯度为

证明

假设我们可以利用这个事实来估计泊松场,那么我们如何使用这个场来生成数据呢?

PFGM 粒子动力学

从物理学中,我们知道力 F 和动量 p 的关系:

假设质量不是随时间变化的量,我们得到更常见的公式,其中 m 是质量, v 是速度:

独立地,电场 E 中带电荷 q 的粒子上的力 F 由下式给出

因此,我们可能倾向于用规范的牛顿方式来定义动力学:

我们把电荷和质量统一起来。然而,我们需要沿着电场线行进,以便将我们的数据分布适当地转换成均匀的角度分布。也就是说,我们必须从画面中移除惯性。惯性是物体在外力的影响下避免运动变化的趋势,因此在没有惯性的情况下,粒子将沿着力场线移动。

直觉

考虑一个物体(紫色球)在 XY 平面上以恒定的向上速度运动。在上半平面(y > 0 ),有一个指向 X 轴正方向的均匀力场。

如果物体在真空中移动,那么它在进入上半平面时将受到力场的作用,这样它的速度的 X 分量将线性增加,而它的速度的 Y 分量将保持不受影响。所以物体的轨迹会是一条抛物线,如下图。

现在考虑如果上半平面充满像水一样的粘性流体会发生什么。在进入上半平面时,物体将像以前一样受到力场的作用,但这次将会有一个减速力,其分量在 X 和 Y 两个方向上。因此,物体速度的 Y 分量将不断减小,没有力来抵抗这种减速力。因此,物体的轨迹将不再是抛物线,而是如下图所示的曲线:

让我们提高这种液体的粘度,把它从水变成例如糖蜜。在这种情况下,减速力会更大,更快地抑制速度的 Y 分量,进一步“压缩”轨迹:

在极限情况下,在进入磁场时,物体的垂直速度将被完全消除,但仍有一个力作用在 X 方向。因此,物体将进入上半平面,并且立即开始跟随力场线。

请注意,物体速度的 X 分量将不会不受流体的影响——该方向的运动也将受到抑制,但重要的事实是,有一个恒定的力来对抗减速力,从而产生一个跟随场的轨迹,,而不管物体沿着该轨迹实际需要多长时间。

因此,我们将泊松场设置为不等于速度的时间导数,而是等于位移的时间导数。

也就是说,我们使用一阶导数,而不是将场设置为等于位移对时间的二阶导数。这将导致我们的粒子精确地沿着场线,这是一个数学关系,一般适用于任何向量场。从物理角度来看,这种情况对应于所谓的阻尼运动,类似于粒子在泊松场的作用力下穿过粘性流体。

*PFGM 流动模型解释

对于那些感兴趣的人来说,同样值得注意的是,PFGMs 可以作为直接在概率密度上操作的流模型。通用连续性方程可以表示为

对于某些体积密度ρ,通量 j 和发电率σ。在我们的例子中,我们正在描述一个概率流,因此发生率为零,因为概率必须守恒。通量 j 就是分布乘以泊松场,留给我们

上面的方程是福克-普朗克方程的特例。福克-普朗克方程将随机变量的概率分布的(确定性)偏微分方程提供给由 It & ocirc 过程定义的相应随机微分方程。

上述方程是福克-普朗克方程的一个特例的原因是,它没有扩散项,因此有一个相应的 T2 确定性微分方程,而不是 T4 随机微分方程,在这种情况下是简单的

因此,从物理解释和概率流的角度来看,我们都得到了相同的正向微分方程。* 假设粒子动力学完全由这个确定性过程定义,为了从 z=0 超平面中的任意紧凑分布映射到均匀的角度分布,我们只需要计算(或估计)该分布产生的泊松场。现在是时候从实际的角度重新审视我们上面的理论*关于从一组点电荷估计泊松场的结果了。

估计泊松场

如前所述,将 N 维数据映射到 N 维半球的 PFGM 方法在采样过程中会出现模式崩溃。相反,作者引入了一个额外的维度 z ,将数据嵌入到 N+1 维空间的 z=0 超平面上。因此,扩充空间中的数据分布是

其中我们将原始数据分布乘以狄拉克δ函数。我们通过将被视为点电荷的每个数据点所产生的场相加,来计算该分布所产生的经验场:

正如我们在上面看到的,由于拉普拉斯的线性,这个等式是有效的。该场由缩放 c 是数值稳定性的乘数,这是完全有效的,并且仅通过时间上的重新缩放来修改动力学。

由于粒子遵循泊松场线,我们实际上并不关心这些线上任何一点的场的大小(只关心方向)。因此,我们将场标准化,以便将其转换为更有利于神经学习的形式。我们将负归一化场定义为

在这里,我们添加了负号,使字段指向数据分布。负的归一化场是我们的神经网络将要学习的

我们随机采样空间中的许多点,因此我们可以计算真实经验场和我们的神经网络预测之间的损失。我们只是扰乱数据点。假设泊松场的曲率在数据分布附近比在远处更高,则空间中更接近数据分布的点被给予采样偏好,以便我们的函数逼近器更紧密地匹配该区域中的经验场。PFGM 的作者选择了以下方法对训练点进行抽样:

在哪里

通俗地说,我们通过在随机方向上扰动数据点来创建训练数据点,从卡方分布(高斯的范数)中采样扰动的距离,总是确保在 z 方向上的扰动是正的。Tau 是一个超参数,它在学习数据分布附近的结构和远离数据分布的结构之间提供了一个折衷,tau 值越大,从远离数据分布的点采样的概率就越大。 M 定义了我们关心的建模数据分布的最大距离,在 PFGM 论文中从[0,300]均匀采样。

此过程的伪代码如下所示:

(source)

如上所述,我们使用一个 U-Net 作为神经网络。它接受空间中的一个 N+1 维点,并返回该点的预测 N+1 维负归一化场。损失简单地说就是批量的平均 L2 损失,并且基于梯度的优化用于训练模型。

现在我们有了一种方法来训练经验场的函数逼近器,我们如何使用它来生成数据呢?

锚定向后的颂歌

回想一下上面定义前进动力的前进颂歌:

我们使用以下反向 ODE 进行采样:

关于标志的说明

到目前为止,我们已经在反向颂中放置了一个负号来表示我们正沿着田野向后行进。在这一点上的正向和反向 ode 被统一到使用负归一化场的上述等式中。沿着场线的方向可以简单地通过翻转积分边界来改变。

其中它被定义在增广空间上,并且真实泊松场 E 被负的归一化经验场 v 代替。现在我们有了定义反向动力学(以及采样过程)的 ODE,我们可能会认为问题已经解决了——只需将 ODE 插入 ODE 解算器来生成数据。

问题是我们不知道每个轨迹的开始和结束时间。也就是说,每个粒子在一个大半球上开始,并通过在 z=0 平面上的数据分布的反向演化,但我们不知道这个过程需要多长时间。为了避免这个问题,我们固定到物理上有意义的变量 z 的 ODE,如下所示,其中 v 的下标表示场的分量。

既然 ODE 是按照 z 来铸造的,我们知道每个粒子的终点是当它到达数据分布所在的 z=0 超平面时。粒子的起点是什么?

确定先验分布

在一个半球上均匀采样并使用产生的 z 分量作为 ODE 的起点是很容易的,但是有一个的小问题。许多 ODE 求解器要求整批都具有相同的初始值。位于半球上的点有许多不同的 z 值,,所以这是一个小障碍。

为了规避这个问题,半球上的均匀分布被投影到与半球顶部重合的超平面上,( z_{max} )。该投影是一个简单的径向投影,因为当 x 接近无穷大时,泊松场是纯径向的。我们可以在下面的 2D 看到一个示意图,红线将球体上的紫色点连接到它们在绿线上的投影点。

Radial projection of a 2-dimensional hemisphere

这种变换构成了超平面和半球之间的同胚映射——下面我们可以看到同胚映射在三维空间中的作用:

https://www.assemblyai.com/blog/content/media/2022/10/output.mp4

Radial projection of a 3-dimensional hemisphere

从这个投影中,我们得到超平面上的先验分布,它告诉我们如何从( z_{max} )超平面中采样,其方式等价于从半球中均匀采样:

从数据分布中取样

现在,我们已经具备了使用 PFGM 从数据分布中生成样本所需的所有要素。首先,我们从上面定义的先验分布中对粒子群进行采样。因为分布是旋转对称的,所以从下面的分布中均匀地采样范数就足够了

然后从( 0 )到( 2\pi )均匀采样角度。然后,作者改变变量以实现 z 维度上的指数衰减,用t’替换 z ,根据

这是通过以下方式解决的

轨迹终点

注意,这种变量的变化将我们的起始 ( z_{max} ) 和结束 ( z=0 ) 分别转换为-log( ( z_{max} ) )和-log( ( z_{min} ) ),其中 ( z_{min} ) 是一个微小的正数,因为( 0)的对数没有定义。

根据经验,变量的这种变化导致的采样速度加快了 2 倍,而不会损害样本质量。此外,直觉上讲,在 z=0 超平面附近采取较小的步长是明智的,以便更好地导航数据分布附近存在的更复杂的泊松场。

变量改变后相应的反向 ODE 是

其中 v 是负的归一化经验场,我们使用 U-Net 作为近似。从这里开始,一个 ODE 求解器被用来通过像 RK45欧拉方法这样的方法来求解 ODE。

摘要

我们将 PFGMs 的理论逻辑和实践细节总结如下:

  1. 我们在 z=0 超平面中有一个连续、紧凑的数据分布。
  2. 当被视为电荷分布时,该分布产生泊松场,其具有接近无穷大的均匀角通量 密度
  3. 泊松场由数据分布唯一地生成(给定良好的边界条件)。因此,沿着场线回到 z=0 超平面给了我们一种从数据分布中采样的方法。
  4. 对应于随后的场线的正向 ODE 可以简单地通过将位移的时间导数设置为等于泊松场来获得。因此,泊松场是我们需要从数据分布中取样的唯一缺失部分。
  5. 虽然我们无法得到泊松场的精确分析形式,但是我们可以通过从数据分布中采样的一组数据点来估计泊松场。泊松场大约是由每个被视为点电荷的数据点产生的场的总和,由于拉普拉斯算子是一个线性微分算子而被证明是合理的。我们称这种近似为经验场
  6. 我们试图训练一个神经网络来学习经验场。
  7. 我们随机采样空间中的点,优先选择数据分布附近的点,因为泊松场在那里更复杂。在每个点上,我们计算真实经验场,其中点电荷的泊松场是通过对拉普拉斯的格林函数取负梯度而获得的。我们还计算神经网络在采样点的预测。
  8. 我们将损耗计算为采样点上真实的和预测的归一化经验场之间的平均 L2 损耗。该损失与基于梯度的优化一起用于训练神经网络,在这种情况下,神经网络是 U-网( DDPM++ 主干)。
  9. 在训练神经网络之后,我们使用 ODE 解算器沿着泊松场反向进化大半球上的均匀粒子群(从先前分布等效采样),直到我们到达数据分布的超平面,从该分布生成样本。

结果

PFGMs 的早期结果令人印象深刻,充满希望。下面列出了将 PFGMs 应用于图像生成任务的一些要点:

  1. 在 CIFAR-10 的标准化流程模型中,PFGMs 获得最佳初始分数** (9.68)和最佳 FID 分数 (2.35)。**
  2. PFGMs 的推理速度比采用类似架构的 SDE 方法快10-20 倍,同时保持可比的样本质量
  3. PFGM 容纳了许多不同的建筑。
  4. PFGMs 展示了对更高分辨率图像生成的可扩展性****

下面是 CIFAR-10 的结果表,将 PFGMs 与各种其他方法进行了比较:

**

(source)**

PFGM 架构鲁棒性

作者使用 DDPM++ 架构获得最佳结果,这是早期扩散模型中使用的 U-Net 架构的修改版本。然而,作者指出,PFGM 方法对不同架构的使用是稳健的。例如,Song et 中使用的方差保持(VP)和子方差保持(子 VP) ODEs 和 SDEs。艾尔。的工作在基于分数的模型上展示了使用 DDPM++主干的强大性能,但受到 NCSNv2 架构的影响,在 CIFAR-10 上 FID 分数超过 90。另一方面,PFGMs 在 NCSNv2 中仍然表现良好。

PFGM 的作者假设,这一结果源于在基于分数的模型的训练中看到的强常模-方差相关性导致了这一问题。下图(a)显示了视频采样期间的范数-σ关系,图(b)显示了 PFGM 采样期间的范数-z 关系。阴影区域对应于标准的标准偏差。我们可以看到与 VE-ODE 有很强的相关性,但与 PFGM 没有相关性。

**

(source)**

如果我们将这些规范的分布视为时间的函数(通过 PFGM 的 z 和 VE-ODE 的 sigma ,我们会看到以下内容:

**

(source)**

作者注意到,对于基于分数的模型,扰动训练样本的 L2 范数和高斯噪声的标准偏差是强相关的。这种观察导致了合理的假设,即如果轨迹在采样期间偏离这种相关性(绝大多数训练样本都遵循这种相关性),那么性能将会下降。另一方面,PFGMs 中的标准分布在各种各样的值上,因为给定的半球获得许多不同的z-值。因此,与上述类似的逻辑导致了相应的假设,即 PFGMs 对于大范围的轨迹是鲁棒的。参见第节。4.2 在 PFGM 的论文中了解更多详情。

PFGM 步长鲁棒性

作者还指出,在像欧拉法这样的数值求解器中,PFGMs 对步长(或者,等价地,函数求值的次数)的变化是鲁棒的。他们指出,这为在现实世界部署中用计算效率来权衡样本质量提供了一种便利的机制。下图显示了几种不同生成方法的函数求值次数(NFE)与 CIFAR-10 上的 FID 分数。

PFGM 似然估计

PFGMs 构成了数据空间和具有已知先验的潜在空间之间的可逆映射(甚至更强的同胚映射)。显式地,映射 M 由下式给出

该映射的可逆性实现了可能性评估**。作者使用变量的瞬时变化公式来评估数据的可能性。该值以 bits/dim 为单位,与负离散对数似然成正比(更多细节见此处)。因此,我们寻求最小化这个值。PFGM 的作者报告了许多方法在(统一去量化的)CIFAR-10 测试集上的 bits/dim,报告如下

子 VP-ODE 显示了最好的结果,尽管它的样本质量比 VP-ODE 和 PFGM 差。作者将可能性和质量之间的权衡留给了未来的工作。

PFGM 潜在操纵

考虑到 M 的可逆性,作者还指出,样本被唯一地识别为它们的潜在表征,这允许我们通过它们的潜在表征来操纵图像。例如,简单地对潜在空间中的线性插值点进行解码产生了一种在图像之间进行插值的简单方法,下面使用 CelebA 图像进行了演示:

类似地,我们可以执行其他操作,如温度缩放,如下所示,再次使用 CelebA 数据集:

有待改进的领域

PFGMs 为新的研究途径提供了一个非常有前途的基础。这里列出了两种这样的途径。

第一个途径是一个次要的细节,但有利于数学的一致性。为了数学上的方便,PFGM 模型假设连续的紧凑的分布。虽然这一要求在实践中不成问题,但最好证明这些结果也适用于非紧分布,这在给定有限积分值(这是概率密度的保证)的情况下似乎是直观合理的。

第二个需要改进的地方是提高分辨率。虽然作者证明了 PFGMs 在 256x256 LSUN 卧室图像的更高分辨率下产生合理的结果,但是在更高分辨率(如 512 x 512 或 1024 x 1024)下产生高质量图像的能力尚未得到证明。

一个潜在的研究途径是将 PFGMs 和扩散模型结合成一个更大模型的组成部分。例如,用 PFGM** 替换 Imagen 中的初始基本扩散模型,但保持扩散模型超分辨率链不变,这可能是一个强大的修改(更多细节请参见此处)。正如 PFGM 的作者指出的,扩散模型可能需要许多函数评估,并且不像 PFGMs 那样提供可逆性。因此,这些弱点可以通过使用 PFGM 来解决,同时保持超分辨率链完整无缺地放大图像。**

最后的话

PFGMs 为生成模型领域提供了有价值的贡献,早期结果是有希望的。在扩散模型和 PFGMs 的情况下,分别利用预先存在的领域知识、热力学和电动力学的创新模型,为新的机器学习方法的持续发展提供了一种有前途的方法。

对于更多的机器学习内容,请随时查看我们的博客教程,如关于构建自己的文本到图像模型运行稳定扩散的教程。

或者,查看我们的 YouTube 频道的内容,比如我们的机器从头学习系列,或者在推特上关注我们,以了解我们未来放弃的内容。

**喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow*******

ASR 传感器模型综述

原文:https://www.assemblyai.com/blog/an-overview-of-transducer-models-for-asr/

在深度学习和语音识别的背景下,有三种主要类型的神经网络架构被广泛使用:连接主义者时间分类(CTC)、听-听-拼(LAS)模型和转换器(如果仅使用递归神经网络及其变体,也称为 RNNTs)。

换能器最近已经成为大多数 ASR 任务中性能最好的模型架构,并且已经超过了 CTC 和 LAS 模型,尽管每种架构都有其优缺点。在本文中,我们将更仔细地研究传感器架构。

传感器原点

RNNTs 或递归神经网络传感器是由 Alex Graves 在 2012 年的论文“用递归神经网络进行序列转导”中首次提出的。Alex Graves 还(令人印象深刻地)撰写了著名的 CTC 论文,“连接主义时间分类:用递归神经网络标记未分段的序列数据”,发表于 2006 年。

RNNTs 受到 CTC 高度依赖外部语言模型来良好运行的局限性的启发。然而,RNNTs 并没有得到任何真正的重视,直到 2018 年的论文“面向移动设备的端到端语音识别流,该论文展示了在移动设备上使用 RNNTs 进行准确语音识别的能力。

本文重新对传感器架构进行了研究,推动传感器模型在过去几年中实现了最先进的精度。

RNNTs

为了理解传感器,我们可以参考亚历克斯·格雷夫斯的原始论文“递归神经网络的序列转导”创建 RNNTs 是为了解决 CTC 模型的一些缺点,CTC 模型需要外部语言模型才能很好地运行。

CTC 模型有一个模块,即编码器,用于模拟声学特征。

CTC model architecture

假设 CTC 模型预测是相互独立的。为了使这个概念更加具体,我们来看一个简单的例子。

想象一个 CTC 模型输出抄本"I have for apples"。作为一个阅读本文的人,您可以立即发现错误。"I have for apples"应该是"I have four apples"

对于 CTC 模型来说,"for""four"一样敏感,因为它们在发音上是相似的。由于 CTC 模型的输出有条件地相互独立,所以单词"for"的输出没有考虑单词"I have ... apples"的周围环境。

这个缺陷是 CTC 损失函数的固有属性。为了帮助缓解这个问题,某些神经网络层,如递归神经网络或变压器,可以在内部学习根据声学特征对周围环境进行建模。然而,因为 CTC 损失函数仍然是有条件独立的,所以 CTC 模型仍然会犯这些类型的语言错误,并且理论上总体上不太准确,因为 CTC 损失函数没有结合上下文。

由于这些缺点,CTC 模型需要一个外部语言模型,对数百万到数十亿个句子进行单独训练,以纠正 CTC 模型可能输出的任何语言错误。

与 CTC 模型相比,RNNT 模型有三个联合训练的模块:编码器、预测器和联合网络。这三者各有其目的。

RNNT model architecture

编码器模拟语音的声学特征,预测器充当语言模型,从训练数据中学习语言信息。最后,联合网络接收来自编码器和预测器的预测以产生标签。

预测器和加入者网络是有条件依赖的,因此下一个预测依赖于前一个预测。共同训练的模块的这些组合使得不需要外部语言模型来获得高准确度。

在 AssemblyAI,我们最近将我们的核心转录模型从 CTC 模型过渡到传感器模型,并实现了大幅提高的准确性。然而,我们用变压器,特别是变压器的构象变体来代替递归神经网络。

变压器传感器

在 Alex Graves 的原始论文“用递归神经网络进行序列转导”中,在模型架构中使用了 RNN 层。

递归神经网络已经不再是建模顺序数据的首选架构。在论文“注意力是你所需要的全部”中首次介绍的变形金刚,在谈到 NLP 和语音研究时,一直处于人们关注的中心。

变形金刚从连续数据中模拟全局特征的能力是它如此强大的原因。然而,对于语音,不仅查看音频数据中的全局特征,而且查看局部特征是有意义的,因为声学特征更有可能与相邻特征相关,而不是与远处的特征相关。

Conformer 是在论文“ Conformer:用于语音识别的卷积增强转换器”中首次介绍的转换器的变体。这篇论文的主题是,通过在变压器的自关注层之间插入卷积神经网络层,该模型被迫同时关注局部和全局特征,从而获得两个世界的最佳效果!

[Conformer module, image taken from the paper Conformer: Convolution-augmented Transformers for Speech Recognition]

我们在 CTC 和传感器实验中学到了什么

CTC 模型和传感器在现实世界中都表现良好。然而,传感器类型的模型显然是一条路要走,因为它们在准确性方面领先于 CTC 和 LAS 语音识别架构。在过去的几年里,通过数百次实验,我们发现了 CTC 相对于传感器的一些优点和缺点。

CTC 优势

  • CTC 车型更容易训练!CTC 型号只有一个模块,即编码器。这种简单性使得 CTC 很容易训练。
  • 有更多资源可用于 CTC 型号。由于长期以来 CTC 模型一直是最受欢迎的语音识别架构,因此有大量的研究和开源工具来帮助您快速构建和训练它们。

CTC 的缺点

  • CTC 车型收敛较慢!虽然 CTC 模型更容易训练,但我们注意到它们的收敛速度比传感器模型慢得多。当训练 CTC 模型时,总是需要比换能器更多的迭代来获得类似的结果。
  • CTC 机型用专有名词更差。说到专有名词,CTC 模型似乎不太准确。专有名词是训练语料库中可能有也可能没有的罕见单词。
  • CTC 模型需要一个外部语言模型 (LM)才能运行良好。

传感器优势

  • 传感器型号收敛速度更快!通过更快的收敛,我们能够进行更多的实验,减少深度学习研究的反馈回路。
  • 传感器模型更精确,即使参数更少。总的来说,我们发现转换器模型对专有名词更准确(+24.47%),整体转录准确性(+18.72%),即使它比类似的 CTC 模型更小。

传感器缺点

  • 传感器模型更难训练。必须联合训练三个网络的复杂性增加了 bug 的表面积!
  • 传感器型号具有更大的内存足迹,使得训练更大的型号更加困难。
  • 传感器型号可利用的在线资源较少。在一致性和传感器模型方面,我们几乎处于最前沿,所以在网上搜索答案通常不会得到任何结果。

根据我们的经验,对于传感器来说,利大于弊。通过在 AssemblyAI 切换到使用传感器,我们的研究团队已经能够探索 ASR 研究的前沿,并通过我们的语音到文本 API 为我们的客户和开发人员提供最先进的准确性。

想要更多这样的帖子吗?

订阅我们的每周时事通讯。

Sign-up now

用 Python 在 5 分钟内将语音转换成文本

原文:https://www.assemblyai.com/blog/assemblyai-and-python-in-5-minutes/

在本教程中,我们将学习如何使用 Python 和 AssemblyAI 的语音转文本 API 在 5 分钟内完成语音转文本。我们提供了一个很小的库,只需要几行代码就可以转录本地和非本地文件,我们还为那些想了解幕后发生了什么的人提供了库函数的分解。让我们开始吧!

入门指南

首先,我们将安装 Python 的requests包,它将允许我们与 AssemblyAI API 通信,以便提交文件进行转录。打开一个终端,并使用以下命令安装它:

pip install requests

接下来,我们需要为本文克隆项目存储库,它包含了我们需要的所有代码。从 GitHub 下载存储库,或者使用 Git 通过以下终端命令克隆它:

git clone https://github.com/AssemblyAI-Examples/assemblyai-and-python-in-5-minutes
cd assemblyai-and-python-in-5-minutes

终端尖端

您可以通过右键单击终端将这些命令粘贴到终端中。

如何用 Python 转录音频文件

现在,我们可以开始转录一个音频文件,它可以存储在本地或非本地。项目资源库包括一个名为audio.mp3的样本音频文件,它是从我们的 YouTube 频道上的 AssemblyAI 产品概述视频中分离出来的音频。如果你有一个特定的音频文件,你想转录,现在把它放在项目文件夹

获取语音转文本 API 密钥

为了执行转录,我们将使用 AssemblyAI 的免费语音转文本 API 。如果您还没有帐户,请在这里创建一个。登录您的账户查看仪表盘,它提供了您账户的快照。我们现在需要的就是你的 API 密匙。单击仪表板上您的 API 密钥部分下的密钥,复制其值。

AssemblyAI Dashboard with API key location highlighted

这个 API 密匙就像与你的账户相关联的指纹,让 API 知道你有使用它的权限。

重要说明

千万不要和任何人分享你的 API 密匙或者上传到 GitHub。您的密钥与您的帐户唯一关联,应该保密。

存储您的 API 密钥

出于安全和方便的原因,我们希望避免对 API 密钥进行硬编码。对键值进行硬编码很容易导致意外共享或上传到 GitHub,并且作为命令行参数反复传递既麻烦又繁琐。为了克服这些问题,我们将把 API 键存储为一个环境变量

回到终端,根据您的操作系统,执行以下命令之一,用先前从 AssemblyAI 仪表板复制的值替换<YOUR_API_KEY>:

Windows 操作系统

set AAI_API_KEY=<YOUR_API_KEY>

类 UNIX 系统

export AAI_API_KEY=<YOUR_API_KEY>

该变量只存在于终端进程的范围内,因此在关闭终端时会丢失。要保持此变量,请设置一个永久的用户环境变量。

转录音频文件

既然 API 键被保存为环境变量,我们可以用一个终端命令转录音频文件。在项目存储库中打开一个终端,并运行以下命令:

python transcribe.py <AUDIO-FILENAME-OR-URL> [--local]

如果是本地的,用音频文件名替换<AUDIO-FILENAME-OR-URL>,如果不是本地的,用 URL 替换。对于本地示例,可以使用 repo 附带的默认本地audio.mp3文件,或者对于非本地示例,可以尝试使用该文件的 GitHub URL 。如果使用本地文件,请确保包含--local标志。

使用限制访问的非本地文件?

查看我们关于在 AssemblyAI 中使用预先设计的 URL 的指南。

Open Guide

执行完命令后,只需等待片刻,您的转录就会出现在控制台中,并保存到一个名为transcript.txt的文件中。较大的音频文件将需要较长的处理时间。

HTTPS 笔记

与 AssemblyAI API 通信时,必须使用 HTTPS。使用例如 HTTP 代理将导致错误。

这就是使用 AssemblyAI 的语音转文本 API 转录一个文件所需要的全部内容!要了解更多关于引擎盖下发生的事情,请继续阅读下一节

或者,查看我们的文档以获得更多关于 AssemblyAI 入门的信息,或者了解更多关于我们的音频智能功能的信息。也可以随意查看我们的博客或者订阅我们的时事通讯,获取关于机器学习的一般内容。

Get a free API key

代码分解

在上一节中,我们使用了[transcribe.py](https://github.com/AssemblyAI-Examples/speech-recognition-in-5-minutes-with-python/blob/main/transcribe.py) Python 文件来自动生成音频文件的副本。现在让我们深入研究这个 Python 文件,以便更好地理解幕后发生的事情。

像往常一样,文件的顶部列出了所有必需的导入。我们包括:

  1. argparse,一个本地 Python 库,允许我们解析命令行参数,
  2. os,另一个本地 Python 库,它允许我们导入刚刚设置的 API key 环境变量,以及
  3. utils,它导入位于项目存储库中的[utils.py](https://github.com/AssemblyAI-Examples/speech-recognition-in-5-minutes-with-python/blob/main/utils.py)中的辅助函数库。
import argparse
import os
import utils

main()函数中的以下代码包含了transcribe.py中的所有逻辑。首先,定义并解析transcribe.py的命令行参数。--local--api_key中的两个破折号表示这些参数是可选的,action关键字定义了在提供这些值的情况下应该如何处理它们。

parser = argparse.ArgumentParser()
parser.add_argument('audio_file', help='url to file or local audio filename')
parser.add_argument('--local', action='store_true', help='must be set if audio_file is a local filename')
parser.add_argument('--api_key', action='store',  help='<YOUR-API-KEY>')

args = parser.parse_args()

其他详细信息

  • --local--api_key中的两个破折号表示这些参数是可选的
  • action定义在提供可选参数的情况下,应该如何处理这些可选参数。--local作为一个标志,指示一个本地文件正在被传递用于转录(与一个网址相反),因此我们设置args.local=True--local被传递的情况下指示这一点。--api_keyAAI_API_KEY环境变量未设置的情况下提供 AssemblyAI API 键,所以我们将args.api_key设置为与通过--api_key参数传入的相应字符串相等
  • 最后,help提供了关于transcribe.py用法的帮助说明。在终端运行python transcribe.py --help可以看到帮助信息。

我们将 API 键作为命令行参数传递的选项留了下来;但是,如果没有传递,我们希望程序默认使用存储在AAI_API_KEY环境变量中的 API 键。我们用下面的代码来完成这个任务,我们导入AAI_API_KEY并将其分配给args.api_key。如果没有设置环境变量,并且没有将 API 键作为命令行参数传递,将会引发错误。

if args.api_key is None:
	args.api_key = os.getenv("AAI_API_KEY")
	if args.api_key is None:
		raise RuntimeError("AAI_API_KEY environment variable not set. Try setting it now, or passing in your API key as a command line argument with `--api_key`.")

接下来,我们需要创建一个 HTTP 头,它将与我们的 API 请求一起发送。这个头包含关于请求的附加信息,特别是包括用于认证的 API 密钥。

header = {
	'authorization': args.api_key,
	'content-type': 'application/json'
}

为了提交一个文件进行转录,我们需要提供一个可以找到该文件的 URL。如果我们想要转录一个本地文件,我们必须首先将它上传到 AssemblyAI 来生成这样一个 URL。我们可以用提供的utils.upload_file()函数来做这件事。如果我们为transcribe.py提供一个 URL 而不是一个本地文件,我们只需要创建一个字典来存储这个 URL,以实现我们的转录请求的正确格式化。这些步骤封装在下面的代码块中:

if args.local:
    upload_url = utils.upload_file(args.audio_file, header)
else:
    upload_url = {'upload_url': args.audio_file}

我们现在准备提交我们的文件进行转录,我们可以用提供的utils.request_transcript()函数在一行中简单地完成:

transcript_response = utils.request_transcript(upload_url, header) 

既然我们的文件已经提交转录,我们需要等待它完成处理。为了知道转录何时完成,我们需要创建一个轮询端点,我们可以用它来检查转录状态。我们用提供的utils.make_polling_endpoint()函数创建轮询端点:

polling_endpoint = utils.make_polling_endpoint(transcript_response)

现在我们有了轮询端点,我们必须实际使用它来检查转录。我们使用提供的utils.wait_for_completion()函数来实现这一点,该函数使用轮询端点每 5 秒获取一次更新的转录状态。当返回completed转录状态时,该功能将结束执行。

utils.wait_for_completion(polling_endpoint, header) 

转录最终完成了,我们可以用提供的utils.get_paragraphs()函数获取转录本的段落。这个函数返回一个包含脚本段落的列表。

paragraphs = utils.get_paragraphs(polling_endpoint, header) 

现在我们已经有了文字记录段落,剩下要做的就是将它们打印到终端并保存到一个名为transcript.txt的文件中:

with open('transcript.txt', 'w') as f:
	for para in paragraphs:
		print(para['text'] + '\n')
		f.write(para['text'] + '\n')

所有上述逻辑都包含在main()函数中,所以总结一下,如果从终端调用transcribe.py,我们只需执行main()函数:

if __name__ == '__main__':
    main()

这就是 AssemblyAI 在高层次上生成转录的方式。如果你想要更详细的理解,继续到下一节来学习我们上面使用的助手函数是如何工作的。否则,如果以上解释足以满足您的目的,请继续阅读的结束语

图书馆功能细分

在这一节中,我们将通过查看utils.py文件来深入理解如何生成转录,该文件为我们上面的代码提供了功能。

进口

像往常一样,我们从进口开始。我们将使用requests来执行我们对 AssemblyAI 的 API 请求,并且我们将使用time来帮助定期检查我们的转录是否完成。

import requests
import time 

定义变量

接下来,我们定义变量,这些变量指定当发送上传或转录音频文件的请求时我们将使用的端点。

upload_endpoint = "https://api.assemblyai.com/v2/upload"
transcript_endpoint = "https://api.assemblyai.com/v2/transcript"

上传音频文件

upload_file()函数将本地音频文件上传到 AssemblyAI,以便生成一个可以在转录请求期间传递的 URL。首先,我们对文件开头定义的upload_endpoint执行一个 POST 请求,包括要上传的音频文件和一个适当的头。我们返回一个字典,其中存储了在upload_url键下的 URL。

def upload_file(audio_file, header):
    upload_response = requests.post(
        upload_endpoint,
        headers=header, data=_read_file(audio_file)
    )
    return upload_response.json()

_read_file()助手函数创建一个生成器,它读取存储在音频文件中的数据。

def _read_file(filename, chunk_size=5242880):
    with open(filename, "rb") as _file:
        while True:
            data = _file.read(chunk_size)
            if not data:
                break
            yield data

要求一份抄本

request_transcript()函数将一个音频文件提交给 AssemblyAI 进行转录。该函数简单地执行一个 POST 请求,将音频上传 URL 发送到我们在文件开头指定的transcript_endpoint,然后返回响应。

def request_transcript(upload_url, header):
    transcript_request = {
        'audio_url': upload_url['upload_url']
    }
    transcript_response = requests.post(
        transcript_endpoint,
        json=transcript_request,
        headers=header
    )
    return transcript_response.json()

等待转录完成

既然音频文件已经提交用于转录,我们需要一种方法来定期检查它以查看它是否完整。为了执行这个检查,我们需要利用轮询端点来完成这个特定的转录。轮询端点为我们提供每个 GET 请求的当前转录状态,返回queuedprocessingcompletederror。我们用make_polling_endpoint()来定义这个端点。

def make_polling_endpoint(transcript_response):
    polling_endpoint = "https://api.assemblyai.com/v2/transcript/"
    polling_endpoint += transcript_response['id']
    return polling_endpoint

在我们创建轮询端点之后,我们需要使用它来定期检查转录是否已经完成。为此,我们使用了wait_for_completion()函数,它每隔 5 秒用一个 GET 请求检查一次转录的状态,直到它是completed

def wait_for_completion(polling_endpoint, header):
    while True:
        polling_response = requests.get(polling_endpoint, headers=header)
        polling_response = polling_response.json()

        if polling_response['status'] == 'completed':
            break

        time.sleep(5)

归还抄本段落

AssemblyAI API 可以返回许多信息,包括音频智能洞察您的音频,如情感分析扬声器二进制化。即使只是简单的转录,API 仍然返回许多有用的额外信息,比如每段的开始和结束时间,以及转录的置信度。

我们使用get_paragraphs()函数来隔离转录的段落,并将它们存储在一个列表中。该函数执行一个 GET 请求,然后只隔离响应中的段落文本。

def get_paragraphs(polling_endpoint, header):
    paragraphs_response = requests.get(polling_endpoint + "/paragraphs", headers=header)
    paragraphs_response = paragraphs_response.json()

    paragraphs = []
    for para in paragraphs_response['paragraphs']:
        paragraphs.append(para)

    return paragraphs

在返回段落之后,我们可以打印它们,在它们之间插入空行,以实现音频文件的高可读性转录。

最后的话

用 AssemblyAI 转录一个音频文件就这么简单!要了解如何使用 AssemblyAI API 的更多高级特性,请查看文档。如果你对更多的机器学习内容感兴趣,可以考虑看看我们的博客的其余部分,并关注我们的时事通讯。如果你喜欢视觉内容,看看我们的 YouTube 频道。

Follow the AssemblyAI Newsletter

运行中的自动章节-构建一个自动总结播客的 Web 应用程序

原文:https://www.assemblyai.com/blog/auto-chapters-in-action/

在这个视频中,我们将构建一个 Streamlit 应用程序来总结播客剧集。我们使用 ListenNotes API 下载播客信息,然后使用 AssemblyAI API 转录播客,并为我们生成自动章节

跟随这里:

https://www.youtube.com/embed/q-5uAFJGqOk?feature=oembed

用 Python 自动化会议笔记

原文:https://www.assemblyai.com/blog/auto-generating-meeting-notes-with-python/

在本教程中,我们将构建一个接收会议录音并自动生成会议笔记的 web 应用程序。对于此应用程序,我们需要:

  • 细流
  • 计算机编程语言
  • AssemblyAI 的语音转文本 API

要跟进,请从我们的 GitHub 库获取代码。

简化应用程序

作为第一步,让我们设置 Streamlit 应用程序的结构。这将是应用程序面向用户的界面。

import streamlit as st

uploaded_file = st.file_uploader('Please upload a file')

if uploaded_file is not None:
    st.audio(uploaded_file, start_time=0)

一旦文件上传和音频播放部件准备就绪,您的应用程序应该看起来如下图所示。

从这一点开始,我们首先需要转录和分析音频文件,然后才能添加新的 Streamlit 部分。

获取主题和章节摘要

下一步是将上传的音频文件发送到 AssemblyAI,以便对其进行转录和分析。我们将在这个应用程序中使用 AssemblyAI 的两种不同的 NLP 功能:

AssemblyAI 将检测本次会议中发言的主题,并报告它们所属的最相关类别。除此之外,AssemblyAI 还会将这段录音分成章节。对于每一章,AssembllyAI 将返回一个或长或短的内容摘要,以及该章开始和结束的时间戳。

为了连接到 AssemblyAI,我们需要设置头。

headers = {
    "authorization": auth_token,
    "content-type": "application/json"
}

auth_token是您从 AssemblyAI 获得的 API 密钥,以便能够使用该 API。

Get a free API token

我们将收集我们需要上传的代码,并开始在一个函数中转录音频。

import requests

def upload_to_AssemblyAI(audio_file):

    transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
    upload_endpoint = 'https://api.assemblyai.com/v2/upload'

    upload_response = requests.post(
        upload_endpoint,
        headers=headers, 
        data=audio_file
    )
    audio_url = upload_response.json()['upload_url']

    json = {
        "audio_url": audio_url,
        "iab_categories": True,
        "auto_chapters": True
    }

    response = requests.post(
    	transcript_endpoint, 
        json=json, 
        headers=headers)

    polling_endpoint = transcript_endpoint + "/" + response.json()['id']
    return polling_endpoint

一行一行地,在这个函数中,我们首先定义我们想要在 AssemblyAI 上连接的端点。稍后,我们通过附加身份验证头和从用户处收到的文件向上传端点发出一个POST请求。在我们从 AssemblyAI 得到的对这个上传请求的响应中,有一个upload_url指向我们刚刚上传的文件。

使用此 URL,我们将提出转录请求。我们向转录端点发出POST请求,同时还附带了认证头。不过,这一次,我们还需要附加一个 JSON 变量,指定音频文件的位置以及我们期望从 AssemblyAI 得到的额外结果。

在这个项目中,我们要求类别("iab_categories": True)和章节摘要("auto_chapters": True)。使用我们为转录请求获得的响应,我们可以创建一个轮询端点。轮询端点是我们发送请求以获得转录结果的地方。

接收来自汇编的结果

使用上面定义的函数,我们现在可以访问转录的轮询端点。使用这个端点,我们可以查询我们提交的作业的状态。

polling_endpoint = upload_to_AssemblyAI(uploaded_file)
polling_response = requests.get(polling_endpoint, headers=headers)
status = polling_response.json()['status']

我们需要等一会儿才能完成转录。这就是为什么设置一个 while 循环来询问作业的状态是一个好主意。或者,您可以添加一个睡眠间隔来等待请求。

import time

while status != 'completed':
    polling_response = requests.get(polling_endpoint, headers=headers)
    status = polling_response.json()['status']

    if status == 'completed':

    	# display results
        ...

    time.sleep(2)

显示音频智能结果

话题检测

显示结果将我们带回简化代码。第一部分将有这次会议的主题。下面是 AssemblyAI 对主题检测的响应。

{
    ...
    "id": "oris9w0oou-f581-4c2e-9e4e-383f91f7f14d",
    "status": "completed",
    "text": "Ted Talks are recorded live at Ted Conference..."
    "iab_categories_result": {
        "status": "success",
        "results": [
            {
                "text": "Ted Talks are recorded live at Ted Conference...",
                "labels": [
                    {
                        "relevance": 0.00561910355463624,
                        "label": "Education>OnlineEducation"
                    },
                    {
                        "relevance": 0.00465833256021142,
                        "label": "MusicAndAudio>TalkRadio"
                    },
                    {
                        "relevance": 0.00039072768413461745,
                        "label": "Television>RealityTV"
                    },
                    {
                        "relevance": 0.00036419558455236256,
                        "label": "MusicAndAudio>TalkRadio>EducationalRadio"
                    }
                ],
                "timestamp": {
                    "start": 8630,
                    "end": 32990
                }
            },
            ...
        ],
        "summary": {
            "MedicalHealth>DiseasesAndConditions>BrainAndNervousSystemDisorders": 1.0,
            "FamilyAndRelationships>Dating": 0.7614801526069641,
            "Shopping>LotteriesAndScratchcards": 0.6330153346061707,
            "Hobbies&Interests>ArtsAndCrafts>Photography": 0.6305723786354065,
            "Style&Fashion>Beauty": 0.5269057750701904,
            "Education>EducationalAssessment": 0.49798518419265747,
            "Style&Fashion>BodyArt": 0.19066567718982697,
            "NewsAndPolitics>Politics>PoliticalIssues": 0.18915779888629913,
            "FamilyAndRelationships>SingleLife": 0.15354971587657928
        }
    },
} 

对于这个项目,我们只对摘要部分感兴趣。因为它已经在一个列表中,所以很容易从响应中提取这些信息。

st.subheader('Main themes')
with st.expander('Themes'):
    categories = polling_response.json()['iab_categories_result']['summary']
    for cat in categories:
        st.markdown("* " + cat)

我们将结果保存到一个 Python 列表中,并在 Streamlit expander 小部件中将每个结果显示为一个列表项。

章节摘要

AssemblyAI 将我们的音频分成章节,并为我们提供每章的标题、要点和摘要。下面是这种反应的样子。

{
    "audio_duration": 1282,
    "confidence": 0.930082104986874,
    "id": "ogskorn5o4-8b98-44d6-98ff-7c42a97e033b",
    "status": "completed",
    "text": "Ted Talks are recorded live at Ted Conference...",
    "chapters": [
        {
            "summary": "In 2 million years, the human brain has nearly tripled in mass. Going from the one and a quarter pound brain of our ancestors here habilis. To the almost three pound meat loaf. One of the main reasons that our brain got so big is because it got a new part called the frontal lobe. And particularly a part called the prefrontal cortex.",
            "headline": "One of the main reasons that our brain got so big is because it got a new part called the frontal lobe, and particularly a part called the prefrontal cortex.",
            "gist": "The big brain."
            "start": 8630,
            "end": 146162,
        }
        ...
    ],
    ...
} 

AssemblyAI 回复的所有信息对这个项目都是有用的。为了便于操作,我们可以先将它们导出到熊猫数据框架中。

import pandas as pd

st.subheader('Summary notes of this meeting')
chapters = polling_response.json()['chapters']

我们希望在用户界面中显示每章的开始时间。这就是为什么毫秒值需要被解析成分和秒。为了实现这一点,我们使用下面的函数。

def convertMillis(start_ms):
    seconds = int((start_ms / 1000) % 60)
    minutes = int((start_ms / (1000 * 60)) % 60)
    hours = int((start_ms / (1000 * 60 * 60)) % 24)
    btn_txt = ''
    if hours > 0:
        btn_txt += f'{hours:02d}:{minutes:02d}:{seconds:02d}'
    else:
        btn_txt += f'{minutes:02d}:{seconds:02d}'
    return btn_txt

chapters_df['start_str'] = chapters_df['start'].apply(convertMillis)
chapters_df['end_str'] = chapters_df['end'].apply(convertMillis)

现在,dataframe 有两个新列,指定了每章的开始和结束时间。

我们将在各自的扩展器中显示每一章,以获得一个整洁的界面。这可以通过迭代 dataframe 并为每章创建一个新的 expander 小部件来轻松完成。

for index, row in chapters_df.iterrows():
    with st.expander(row['gist']):
        st.write(row['summary'])
        st.button(row['start_str'])

每一章都将在扩展标题中陈述其要点。一旦展开,用户将能够阅读更长的章节摘要,并看到一个按钮,在音频中说明本章的开始时间。

此时,应用程序应该是这样的:

跳到每一章的音频开始

我们需要做的最后一件事是激活章节扩展器中的按钮。为此,我们将使用 Streamlit 会话状态和回调函数。

让我们定义一个名为start_point的会话状态变量,它将决定音频播放器的起始点。这个值将用一个名为update_start的函数来更新。

if 'start_point' not in st.session_state:
    st.session_state['start_point'] = 0

def update_start(start_t):
    st.session_state['start_point'] = int(start_t/1000)

Update_start将毫秒值作为参数,将其转换为秒,并用它更新会话状态变量。为了使这一改变生效,我们将更新前一阶段的音频小部件。

st.audio(uploaded_file, start_time=st.session_state['start_point'])

现在要做的最后一件事是通过更新按钮创建行从章节按钮调用更新函数。

st.button(row['start_str'], on_click=update_start, args=(row['start'],))

一旦完成,按钮将能够改变音频播放器的开始点。

如果你喜欢看这个教程,你可以在这里找到 YouTube 视频。

https://www.youtube.com/embed/RMBhwwqeDOw?feature=oembed

使用 Python 中的语音识别功能自动发布您的文字

原文:https://www.assemblyai.com/blog/auto-tweet-your-words-using-speech-recognition-in-python/

没人听的时候,我们说最有趣的事情。但是如果有人一直这样做呢?在这篇文章中,我们将学习如何制作一个应用程序,它会听你说话,并大声说出你说的最有趣、最聪明或最相关的事情。

该应用程序将通过听你说话并记录你的句子来工作。在你说了一些你想发推特的话后,你可以说关键词“推特”,它会在推特上发布你的最新句子。

我们将使用 Python 制作应用程序。主要的图书馆有:

  • PyAudio 用于收听输入源
  • 为了方便使用 Twitter API,Twython
  • 汇编 AI 用于语音到文本的转录

设置依赖项

在编码之前,我们需要 Twitter 和 AssemblyAI 凭证。获取 AssemblyAI API 令牌非常简单。只需注册 AssemblyAI 并登录即可找到您的令牌。如果你以前从未使用过 AssemblyAI,你可以获得一个免费的 API 令牌。

Get your Free API token

为了使用 Twitter API,请访问 Twitter 开发者门户并创建一个帐户。在向 Twitter 提供一些信息之后,您需要创建一个项目并获得必要的凭证。对于此项目,您需要读写权限。

这个项目中有两个文件。主 Python 脚本和一个配置文件。用来自 AssemblyAI 的认证密钥和来自 Twitter 的其他凭证填充您的配置文件,如下所示:

auth_key = ''
consumer_key        = ''
consumer_secret     = ''
access_token        = ''
access_token_secret = ''

在主脚本中,我们从导入我们需要的所有库开始。

import websockets
import asyncio
import base64
import json
import pyaudio
from twython import Twython
from configure import *

用麦克风听

接下来是设置 PyAudio 的参数并启动一个流。

FRAMES_PER_BUFFER = 3200
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
p = pyaudio.PyAudio()

# starts recording
stream = p.open(
  format=FORMAT,
  channels=CHANNELS,
  rate=RATE,
  input=True,
  frames_per_buffer=FRAMES_PER_BUFFER
)

这里需要注意的要点是RATE参数。当设置到 AssemblyAI 终结点的连接时,需要指定相同的采样率。由于在这个项目中,句子将被实时转录,我们将使用 AssemblyAI 的实时端点。

URL = "wss://api.assemblyai.com/v2/realtime/ws?sample_rate=16000"

连接到 Twitter

如上所述,我们使用 Twython 来建立到 Twitter API 的连接。为此,只需要 configure.py 文件中的凭证。

twitter = Twython(
  consumer_key,
  consumer_secret,
  access_token,
  access_token_secret
)

构建 auto-tweeter 应用程序的下一步是设置不断聆听和转录句子的异步行为。为此,将使用 Python 的 asyncio 库。

向 AssemblyAI 发送音频

我们有一个听和抄写的功能。监听功能将被称为send 它的主要目标是捕捉音频,并发送给 AssemblyAI 的语音转文本 API。转录功能将被称为receive,其主要目标是不断监听 AssemblyAI 的端点以获得转录的音频结果。

我们先来看看听音功能。该函数将无限期运行,将对麦克风所说的任何内容发送给 AssemblyAI。因此出现了while True线。该函数的实际功能被封装在tryexcept块中,以捕捉任何潜在的错误。try块中的四行执行该功能的主要功能。

async def send():
  while True:
    try:
      data = stream.read(FRAMES_PER_BUFFER)
      data = base64.b64encode(data).decode("utf-8")
      json_data = json.dumps({"audio_data":str(data)})
      r = await _ws.send(json_data)

    except websockets.exceptions.ConnectionClosedError as e:
      print(e)
      assert e.code == 4008

    except Exception as e:
      print(e)
      assert False, "Not a websocket 4008 error"

    r = await asyncio.sleep(0.01)

函数做的很简单。在捕获音频后,该函数将其编码为必要的格式,并发送给 AssemblyAI。

解读转录

另一方面,Receive函数,顾名思义,从 AssemblyAI 接收结果。

async def receive():
  while True:
    try:
      result_str = await _ws.recv()
      result = json.loads(result_str)['text']

      if json.loads(result_str)['message_type']=='FinalTranscript':
        print(result)
        if result == 'Tweet.' and previous_result!='':
          twitter.update_status(status=previous_result)
          print("Tweeted: %s" % previous_result)
        previous_result = result

    except websockets.exceptions.ConnectionClosedError as e:
      print(e)
      assert e.code == 4008

    except Exception as e:
      print(e)
      assert False, "Not a websocket 4008 err

让我们一步一步来,破译这一组代码。"result_str"变量有来自 AssemblyAI 的响应。它看起来是这样的:

该响应具有关于该转录的完整信息,包括音频开始和结束时间戳、转录的置信度和结果文本。非常有帮助的一个关键属性是在响应结束时一直使用"message_type"

就目前的情况来看,AssemblyAI 将发送回未完成的句子或"PartialTranscripts",因为我们使用的是实时端点。AssemblyAI 确认句子结束后,它会添加正确的标点符号,并在必要的地方大写字母来完成句子。我们只想在 Twitter 上发布完整的句子,而不是部分文字记录。这就是为什么使用以下代码行过滤响应的原因:

if json.loads(result_str)['message_type']=='FinalTranscript':

在推特上发布转录的句子

其余的行处理句子的推文。通过将前一个时间步长赋值给"previous_result"变量,可以记住该时间步长的转录。每当当前时间步长的结果脚本为"Tweet."时,我们就发布最后一句话:

twitter.update_status(status=previous_result)

建立异步行为

这两个函数(sendreceive)将被包装在另一个函数中,以便能够异步运行它们。

async def send_receive():

  print(f'Connecting to url ${URL}')

  async with websockets.connect(
    URL,
    extra_headers=(("Authorization", auth_key),),
    ping_interval=5,
    ping_timeout=20
  ) as _ws:

    r = await asyncio.sleep(0.1)
    print("Receiving SessionBegins ...")

    session_begins = await _ws.recv()
    print(session_begins)
    print("Sending messages ...")
    result = ''

    async def send():
      while True:
        ...

    async def receive():
      while True:
        ...

    send_result, receive_result = await asyncio.gather(send(), receive())

除了包装sendreceive函数,这个函数还使用 websockets 连接 AssemblyAI。定义函数后,它调用sendreceive函数同时运行。

当然,在定义了这个函数之后,我们需要在脚本的最后调用它。下面是这样做的代码行:

asyncio.run(send_receive())

你可以在 GitHub 上找到代码。

更喜欢看这个教程?点击此处查看视频:

https://www.youtube.com/embed/UTRtdIq2xCs?feature=oembed

解释神经网络的反向传播

原文:https://www.assemblyai.com/blog/backpropagation-for-neural-networks-explained/

在这个深度学习教程中,我们学习神经网络的反向传播算法。

反向传播可能是深度学习中最重要的概念,对于神经网络的训练过程来说是必不可少的。今天,我们来看看什么是反向传播以及它是如何工作的。然后,我们用具体的数字向您展示一个例子,以便更好地理解算法背后的理论。

看这里:

https://www.youtube.com/embed/LHLrXavT1gQ?feature=oembed

神经网络的批量标准化——工作原理

原文:https://www.assemblyai.com/blog/batch-normalization-what-is-it-and-how-to-implement-it/

在本视频中,我们将了解批处理规范化。批量规范化是一个秘密武器,它有一次解决许多问题的能力。这是处理不稳定梯度问题的一个很好的工具,有助于处理过度拟合,甚至可能使你的模型训练得更快。

https://www.youtube.com/embed/yXOMHOpbon8?feature=oembed

我们将首先探讨什么是批处理规范化以及它是如何工作的。稍后我们将讨论为什么你可能想在你的项目中使用它,以及它的一些好处。最后,我们将学习如何使用 Python 和 Keras 将批量学习应用于您的模型。尽管使用 Keras 应用批量规范化相当简单,但我们将触及一些可能需要特别注意的细节。

PyTorch IDE 入门指南

原文:https://www.assemblyai.com/blog/beginners-guide-to-torchstudio-pytorch-only-ide/

像 PyCharm 这样的 ide 对于通用编码来说很棒,但是如果有人的重点只是深度学习,有没有更好的替代方案?TorchStudio(专门为 PyTorch 及其生态系统构建的 IDE)背后的团队是这样认为的。

在本文中,我们将概述 TorchStudio 是什么,通过一个示例 TorchStudio 工作流来了解我们如何使用它来构建和比较模型,最后概述 TorchStudio 的优缺点,以及一些我们认为会使其更上一层楼的功能建议。让我们开始吧!

https://www.assemblyai.com/blog/content/media/2022/03/slideshow-2.mp4

Where TorchStudio fits into the Machine Learning lifecycle, and some images from its GUI

火炬工作室是什么?

TorchStudio 是由 Robin Lobel 开发的一个开源项目,旨在让在 PyTorch 中构建和比较模型变得更加容易,并称其为“PyTorch 及其生态系统的 IDE”。IDE 或集成开发环境是一个应用程序,它提供了一组工具来使编程更快更容易。

虽然像 PyCharm 这样的 ide 拥有数据库工具、安全重构、代码完成、版本控制集成等等,但 TorchStudio 反而专注于加速模型开发过程的工具。

火炬工作室与众不同的地方在哪里?

虽然其他工具如 PyTorch Lightning 试图通过提取样板代码来简化 PyTorch 的使用,TorchStudio 采取了独特的方法,将其包装在一个简单易用的基于 GUI 的 IDE 中。

使用大量本地模型(或定制模型),编辑模型就像调整滑块和点击图表一样简单。添加数据处理和模型比较功能,您就有了一个 IDE,可以使机器学习生命周期的几个步骤更加高效。

下面你可以看到从开始到结束进行机器学习项目的步骤,以及 TorchStudio 加速用红色突出显示的步骤。

TorchStudio can significantly expedite data modeling & analysis, and model training, testing, & selection to reduce time-to-deploy

让我们通过 TorchStudio 中的一个示例工作流来突出它的关键特性。首先,我们将为那些想跟进的人学习如何安装火炬工作室。接下来,我们将学习如何加载、转换和子类化 PyTorch dataset对象。在此之后,我们将继续进行训练几个模型,无论是从零开始还是使用迁移学习,对 CIFAR10 数据集进行分类,然后使用 torch studio对模型进行比较。

如何安装 TorchStudio

安装 TorchStudio 只需要大约 10 分钟6 Gb 到 16 Gb 的驱动器空间,这取决于你是否想要添加本地 NVIDIA GPU 支持。

要安装 TorchStudio,只需进入 TorchStudio 网站的下载页面,选择您的操作系统,下载.exe.pkg.deb文件。从那里,你需要做的就是打开文件并按照提示操作。

You can choose to include or omit Local NVIDIA GPU support when installing TorchStudio

TorchStudio 将安装一个包含 Python、PyTorch 和附加包/依赖项的新环境。

火炬工作室工作流程

为了理解 TorchStudio 的工作流程,我们将通过训练和比较模型来对 CIFAR10 图像进行分类。幸运的是,这个工作流程非常简单直观,所以很容易理解!

数据集选项卡

打开 TorchStudio 时,我们会看到一个如下所示的窗口:

TorchStudio opens to the Dataset tab

这是数据集选项卡,其中包含了给定项目数据集的所有信息。一个火炬工作室项目只有一个数据集,但是可以有几个不同的模型。

数据集定义

如上所述,我们将在这个项目中使用 CIFAR10 数据集。屏幕左侧是数据集定义框。在将数据加载到 TorchStudio 之前,您可以在这里选择数据集并定义转换。默认情况下,TorchStudio 将向 MNIST 数据集开放。您可以点击定义框左上角的下拉菜单选择不同的数据集源,尽管现在我们将坚持使用torchvision.datasets。这个下拉列表的右边是另一个下拉列表,其中的包含了该数据集源中所有可用的数据集。单击它将数据集从 MNIST 更改为 CIFAR10。

https://www.assemblyai.com/blog/content/media/2022/03/select_cifar.mp4

Selecting the CIFAR10 dataset

数据集转换

我们将使用需要 64 x 64 图像的模型对 32 x 32 CIFAR10 图像进行分类,因此我们必须调整cifa r10 图像的大小。在定义框架的底部,添加转换transforms.Resize(64)来完成这个任务。确保download参数设置为True,然后加载数据。

https://www.assemblyai.com/blog/content/media/2022/03/resize_and_load.mp4

Resizing the CIFAR10 images from 32 x 32 to 64 x 64

在下图中,您可以在左侧看到 CIFAR10 图像的上采样版本,在右侧看到原始图像。默认情况下,使用双线性插值对图像进行上采样。

Bilinearly interpolated upsampled image of a frog (left) and the original image (right)

编辑数据集代码

虽然 TorchStudio 非常直观,有一个结构良好的 GUI,但您可以在任何时候单击代码复选框来直接定制相关代码。

在下面的例子中,我们编辑CIFAR10类的__getitem__()函数来返回添加了噪声的图像 作为 输入,而原始图像本身作为我们的输入/目标对的目标。这与默认使用原始图像本身作为输入和相应的类别标签作为目标形成对比。您可能希望以这种方式覆盖默认值,以便训练去噪自动编码器而不是分类网络。

https://www.assemblyai.com/blog/content/media/2022/03/noisy_code_2pt5x_Trim.mp4

Editing the input/output pairs of the CIFAR10 dataset

下面你可以看到我们的代码编辑的结果,左边是原始的青蛙图像,右边是它的噪声版本:

Original CIFAR10 frog image (left) and the same image with noise added (right). Such a pair may be useful for training a denoising autoencoder.

同样,如果要训练重新着色网络,可以将灰度图像和原始图像作为输入/目标对返回:

Original CIFAR10 frog image (left) and the same image grayscaled (right). Such a pair may be useful for training a recolorization network.

对于我们的例子,我们将坚持使用原始图像和它们的标签作为输入/目标对,以便训练一个分类网络——上面的代码编辑只是为了完整性。

随着上采样数据的加载和我们的类标签目标的准备,我们可以移动到数据集选项卡中的下一帧——数据集格式化。

数据集格式

格式化框架允许你探索你的数据,并为训练准备 。在“Formatting”框中,您可以调整数据的训练/验证比率,默认情况下使用原始的 CIFAR10 50k 训练图像和 10k 验证图像。您还可以调整数据使用量,这样您就可以在模型上进行粗略训练,以确保在使用所有可用数据开始正式训练之前一切正常。洗牌允许你随机洗牌你的数据。

在这些选项下面,TorchStudio 将根据它认为对给定数据有意义的渲染器来显示输入和目标。在这种情况下,TorchStudio 感觉到每个训练数据都是一个 3D 张量,其中一个轴的长度为 3(对应于 RGB 颜色通道),因此它正确地选择了位图渲染器。另一方面,目标只是对应于类的整数,所以选择了正确的标签呈现器。

我们只需点击并拖动相关滑块,即可轻松浏览数据:

https://www.assemblyai.com/blog/content/media/2022/03/data_explore.mp4

Exploring the CIFAR10 dataset

TorchStudio 自带了其他类型数据的渲染器,如光谱图、体积和边界框。或者,您可以通过单击渲染器下拉列表旁边的齿轮图标来定义自己的渲染器。在 TorchStudio 的任何地方,齿轮图标意味着你可以实现自己的定制模块

数据集分析

数据集选项卡中的最后一个框架是分析框架,它允许我们通过分析功能生成数据摘要。在这种情况下,我们可以看到类比率和随机性,确定我们的数据集是平衡的。

在火炬工作室培训模特

现在我们已经有了我们的数据集,我们可以继续用 TorchStudio 训练一个模型。首先,单击屏幕左上角 Dataset 选项卡旁边的六边形图标。这将创建一个新的模型选项卡,对于您创建的每个模型都有一个。

模型定义

在您创建的新 Model 选项卡中,选择一个模型源,然后从左上角的下拉列表中选择一个模型架构,类似于在 dataset 选项卡中选择数据集的过程。我们将使用 MobileNetV2

https://www.assemblyai.com/blog/content/media/2022/03/choose_model.mp4

Choosing a model

模型定义框的左下角是所选模型的参数。默认情况下,MobileNetV2 分类器设计为容纳 1,000 个类。将num_classes参数更改为 10(假设 CIFAR10 有 10 个类),其余参数保持不变。最后,点击构建来构建这个模型。

https://www.assemblyai.com/blog/content/media/2022/03/model_params_1-1.mp4

模型图

定义框的右边是框,在其中您可以浏览刚刚定义的模型的图。您可以上下滚动来检查层是如何连接的,或者按住 Ctrl 并滚动来放大和缩小。对于模型的低分辨率视图,也可以将视图类型更改为模块级别 1;对于分辨率更低的视图,也可以更改为模块级别 2。

https://www.assemblyai.com/blog/content/media/2022/03/graph_exploration.mp4

超参数

图形框的右边是超参数框。TorchStudio 再次自动检测它认为最适合当前情况的值。在我们的例子中,我们将 epochs 的数量更改为 15(为了快速训练),其他的都保持不变。您将再次注意到齿轮图标,这意味着您可以设计自定义模块在这里使用。

就像在 Dataset 选项卡的 Formatting 框架中一样,您可以调整 Hyperparameters 框架中的滑块来查看不同的输入;然而,在这种情况下,我们看到的是输入图像的模型的输出,而不是图像的标签。在这种情况下,假设网络没有被训练并且只是被随机初始化,则每个类的概率大致均匀分布。

https://www.assemblyai.com/blog/content/media/2022/03/data_exploration.mp4

Viewing the model output distribution given different inputs

在框架的底部,选择您想要训练的硬件。在我们的例子中,我们在 CPU 上进行本地训练。远程服务器也可以通过进入 TorchStudio 的设置添加到 TorchStudio 进行培训。最后,点击训练开始训练过程。

分析

在超参数框右侧的指标框中,您将看到损失和所选指标(在本例中为精确度)的训练和验证曲线随着模型的训练而动态更新。在任何时候,你都可以暂停,然后继续训练。

https://www.assemblyai.com/blog/content/media/2022/03/full_train.mp4

Training curves in TorchStudio

使用 TorchStudio 训练多个模型

虽然只训练一个模型对于某些应用来说可能已经足够好了,但是 TorchStudio 的真正强大之处在于它的速度,在这种速度下你可以实例化和训练多个模型。让我们再创建两个模型,并在我们的数据集上训练它们。

再次单击 TorchStudio 窗口顶部栏中的小六边形图标,创建一个新模型。每当您创建一个新的模型选项卡,它将是您当前在上的模型选项卡的一个副本(或者如果您在数据集选项卡上,则是您在的最后一个)。现在再创建两个模型选项卡。

编辑模型参数,点击 Build 用新参数重建它们。在我们的例子中,我们选择对模型 2 和模型 3 分别使用round_nearest=8dropout=0.3以及round_nearest=10dropout=0.3。模型制作完成后,点击每个标签上的训练

由于我们是在 CPU 上训练,所以模型是按顺序训练的。彩色标签图标指示您在培训过程中所处的位置。红色的 Model 1 图标包含一个停止符号,这意味着我们已经暂停或者完成了对该型号的训练(在本例中是后者)。蓝色的 Model 2 图标包含一个播放符号,这意味着 Model 2 正在训练。黄色的 Model 3 图标包含一个暂停符号,表示 Model 3 正在队列中等待训练

Model tab icons give you an indication of where you are in training

在 touchstudio 中使用预训练模型进行迁移学习

我们将创建一个最终模型,在这种情况下,使用 MobileNetV2 的预训练版本。创建一个新的选项卡,并从torchvision.models源中选择mobilenet_v2模型。注意,这里的名字是小写的,而不是未经训练的MobileNetV2模型。这适用于 TorchStudio - 中的所有模型,预训练的模型使用小写名称,未训练的模型使用大写名称

Selecting the pretrained mobilenet_v2 model

在模型选项中,将pretrained的值改为True,然后建立模型。

Selected pretrained weights for the mobilenet_v2 model

编辑预训练模型

正如我们上面提到的,MobileNetV2 默认为 1000 个类别,这意味着预训练的权重对应于为 1000 个类别设计的模型。我们这次没有num_classes参数可以编辑,那么如何让模型适应我们的需求呢?

TorchStudio 使编辑模型变得极其容易,从而克服了这个问题。只需在图形框中向下滚动到模型图形的底部,并点击最后一个Linear层。这将打开模型代码,我们可以编辑该代码来更改模型,类似于我们如何编辑数据集代码以在“数据集”选项卡中添加噪声或灰度化 CIFAR10 图像。

单击Linear层时,自动插入到代码中的是对应于被单击层的一行,我们可以用它来覆盖模型的类的数量。只需将out_features从 1000 更改为 10,然后构建模型。

https://www.assemblyai.com/blog/content/media/2022/03/pretrain_build.mp4

Editing a pretrained model

虽然mobilenet_v2模型有预先训练的权重,但它们不会完美地适用于我们一开始的数据。预训练的权重允许我们将预训练的低级特征提取器转移到我们的任务中,但我们仍然需要在我们的数据上训练模型,以有意义地将这些提取的特征映射到 CIFAR10 类。这个过程叫做迁移学习

与其他型号一样,只需点击 Train ,然后等待训练完成。

比较火炬工作室的跑步记录

现在我们已经有了几个经过训练的模型,我们可以使用仪表板选项卡对它们进行比较以获得洞察力。点击 TorchStudio 窗口右上角的仪表板图标。

在左侧,您将看到每个模型的验证损失曲线和每个模型的验证指标曲线重叠在一起,这样您就可以轻松地比较模型之间的性能。曲线颜色与模型选项卡图标中显示的颜色相匹配,以便于识别哪个模型映射到哪个性能曲线。在我们的例子中,我们看到(不出所料),经过 15 个训练周期后,具有预训练权重的模型优于从零开始训练的模型。

Overlaid validation loss and validation metric curves for all models

在这些图表下面,您将看到每个模型的列表信息,包括架构定义和参数、验证损失指标和培训状态。您可以通过单击列标题对表格进行排序。下面,我们按照验证标准对模型进行了排序。

Tabulated model information

仪表板选项卡中的最后一个框架包含列表信息的可视化,这使得识别架构或其定义参数如何影响验证指标的趋势变得容易。下面我们看到,无论其定义参数如何,具有预训练权重的模型明显优于从零开始训练的模型。

Model information plot

这就是本教程的全部内容!如果你对 TorchStudio 的一些优点、缺点和建议功能感兴趣,请继续阅读!否则,你可以跳到我们的最后一句话

火炬工作室有什么好作品

虽然 TorchStudio 的许多好处在检查后是显而易见的,但我们还是列出了几个在 TorchStudio 中运行良好的值得注意的事情,排名不分先后:

1.TorchStudio 让快速原型化成为定义明确、广受欢迎的深度学习任务变得很容易。

2.TorchStudio 使得以 PythonPyTorchONNX 格式导出模型变得容易。

3.能够在模型训练时动态地看到模型的输入-输出对可以帮助您在训练期间识别分类训练失败

4.训练在后台进行,可以随时暂停,允许模型在定义新模型的同时进行训练。

  1. HuggingFacePyTorch Hub 整合即将到来。

6.TorchStudio 有远程服务器集成

可以使用工作的 touchstudio 元素

截至发稿时,TorchStudio 正处于公测阶段,因此可以预计并非一切都完美。我们在使用 TorchStudio 时遇到了一些需要注意的问题:

1.稳定。测试期间,TorchStudio 崩溃了几次,有时是因为一些无关紧要的事情,比如点击和拖动 MNIST 的图片。

2.错误消息。TorchStudio 中的执行失败可能会给你留下一个相对模糊的错误消息,并且很难确定问题出在哪里。TorchStudio 文件夹中有错误日志,但是打开它们并试图解析它们可能会有点令人头疼。找出一种有效的方法来锁定和修复错误将是 TorchStudio 未来版本的一大优势。

3.调整绘图大小。在测试过程中,数据图多次出现大小调整问题,数据图会缩小到可用空间的一小部分。

touchstudio 可以使用的功能

TorchStudio 是一个非常酷的项目,我们已经编辑了几个功能列表,我们认为这些功能将有助于它在即将到来的版本中更上一层楼。请记住,TorchStudio 是一个开源项目,因此社区中的任何人都可以实现其中的任何一项!

1。GUI 建模。能够拖放图形元素,如卷积层,池层等。可视化地构建一个模型,然后自动翻译成 PyTorch 代码,这将是 TorchStudio 的一个很好的特性。

2。更好的错误消息/调试器。如上所述,确定错误的来源可能有点困难。拥有更好的错误消息和/或调试器将有助于缓解这个问题。

3。模型选项卡组。能够根据架构或者其他一些显著的特性将模型分组到选项卡组将会很方便。

4。 车型分类。根据任务将可用的 TorchStudio 模型组织成类是很好的,例如图像分割、图像去噪等。

5。数据集管理。能够查看和管理 TorchStudio 已经下载的数据集将会很方便,并且能够取消正在进行的下载。

6。自动推理。TorchStudio 可以通过几种方式使用自动推理。第一种方法是在编辑图层时自动推断形状变化。也就是说,如果一行中有两个完全连接的层,更改第一个层的输出形状将会自动更改下一个层的输入形状。第二种方法是根据项目数据集自动推断模型的变化。在上面的例子中,我们必须改变预训练分类器的最后一层,因为数据只有 10 个类,而不是默认的 1000 个。自动推断这种变化将是一个很好的加分。

7 .。对数据集变化的敏感度。您可以更改 TorchStudio 项目中正在使用的数据集,但在旧数据集上训练的模型选项卡将保持活动状态。如果这些被自动变灰或者被单独分组到一个选项卡组中就好了,这样你就不会在仪表板中比较在不同数据集上训练的模型

8。关闭前的警告。仪表板按钮非常接近关闭 TorchStudio 窗口的按钮,目前 t,这里没有“你确定吗?”关闭窗口时提示。很容易看出工作会因此而意外丢失,因此在关闭之前发出警告提示可以防止这种情况发生。

9。导出训练图形数据。如果有一种方法可以方便地导出(I)训练损失/度量图的 png,或者(ii)这些图的数据,以便使用自定义格式进行绘制,那就太好了。

最后,设置随机种子显示多个指标的能力将是巨大的优势!

最后的话

我们的新手 TorchStudio 指南到此为止!虽然我们在本教程中只介绍了基础知识,但是 TorchStudio 是一个用于简化 PyTorch 开发过程的可扩展系统,对于任何使用 PyTorch 的人来说,它都是值得尝试或关注的。

如果你喜欢这篇文章,请考虑关注我们的时事通讯,了解更多深度学习内容!

寻找更多这样的帖子?

关注我们的时事通讯!

Follow Now

BitFit:基于转换器的屏蔽语言模型的简单参数高效微调

原文:https://www.assemblyai.com/blog/bitfit/

这篇论文有什么令人兴奋的地方

这项工作属于参数有效微调的范畴,目标是使用尽可能少的参数来实现几乎与微调整个模型相同的精度。

作者提出了一种新的方法,即在微调时,冻结变压器编码器中除偏置项以外的所有参数。换句话说,研究人员仅使用偏差参数对下游任务进行微调。结果相当令人惊讶,因为尽管只使用了总参数0.08% ,但它在 GLUE 基准测试任务上取得了与完全微调模型相当的结果。

主要发现

虽然预先训练好的基于转换器的语言模型,如 BERT 在许多 NLP 任务中表现得更好,但是训练这些模型并在生产中部署它们是非常昂贵的。这使得研究人员想出了不同的有效微调技术。

BitFit 通过冻结预训练 LM 中的所有参数并仅更新偏置项来解决这个问题。对于中小型数据集,这种策略的性能几乎与完全微调的模型相同,有时甚至更好。

下面是 BitFit 和 Full-FT 的对比表。

如果我们允许任务在性能上有小的下降,我们可以通过仅使用查询向量和第二 MLP 层(其包括总参数的 0.04%)的偏差来更进一步。作者的另一个问题是偏差项是否特殊,或者我们是否可以用其他随机参数达到同样的效果。为了测试这一点,他们随机选择了 10 万个参数来微调模型。而且它的表现明显不如 BitFit。

我们的外卖

对一小组参数的微调为更容易的部署打开了大门。由于大多数参数没有改变,我们可以部署一个模型,并在不同的任务中重用它。让一个可重用的模型用于多个任务还会显著减少内存消耗。

用 AssemblyAI - IronScribe 建造

原文:https://www.assemblyai.com/blog/built-with-assemblyai-ironscribe/

在我们的 Built with AssemblyAI 系列中,我们展示了使用 AssemblyAI 语音到文本转录 API 和/或我们的音频智能 API(如 情感分析 汽车章节 实体检测 等)创建的开发人员项目、创新公司和令人印象深刻的产品。在这里,我们深入了解了铁抄写员

描述你用 AssemblyAI 构建的东西。

我们已经建立了一个名为 IronScribe 的成熟的转录服务,它接受视频上传并使用 AssemblyAI 转录它们。该应用程序还可以非常容易地编辑字幕,并让您制作带有字幕的视频剪辑(短片)(用于社交媒体)。

你产品的灵感是什么?

这实际上来自与视频内容提供商的对话,他们正在寻找一种更好、更简单的方法来实现这一点。

当我们构建它时,我们发现它对我们自己的内容也非常有用,并实现了处理速度和标题的准确性(感谢 AssemblyAI)。然后,我们建立的伟大的编辑用户界面和剪辑(短片),加上我们对隐私的尊重(没有第三方被暴露给你的文件),成为强大的卖点。

在我们的播客第一集中,我们也简要地谈到了这一点,该集讨论了我们的技术堆栈和挑战。

你使用哪些 AssemblyAI 特性?

到目前为止,我们使用段落和句子的核心转录模型。我们还编写了一些代码在 max 上进行分块。每个标题 32 个字符,效果很好。

你为什么选择用 AssemblyAI 来构建?

我们从不太好的 YouTube 转录开始。然后,我们用亚马逊转录这是好得多,但仍然有所欠缺。

然后一个朋友推荐了 AssemblyAI,我们就爱上了它的准确性和特性。

此外,我们喜欢我们可以在一天的开发工作中更换整个解决方案—文档对开发人员非常友好!

你有产品的样品吗?

是啊!你可以在我们的 YouTube 频道观看:

https://www.youtube.com/embed/taciduFoU8Y?feature=oembed

想亲自尝试一下 IronScribe 吗?

前往他们的网站注册,免费获得前 30 分钟!

Sign Up Now

采用汇编语言构建-实时语音到图像生成

原文:https://www.assemblyai.com/blog/built-with-assemblyai-real-time-speech-to-image-generation/

在我们的 Built with AssemblyAI 系列中,我们展示了使用 AssemblyAI 核心转录 API 和/或我们的音频智能 API 创建的开发人员和黑客马拉松项目、创新公司和令人印象深刻的产品,如 情感分析 汽车章节 实体检测 等等。

这个实时语音到图像生成项目是由亚利桑那州立大学 HACKML 2022 的学生建立的。

描述你用 AssemblyAI 构建的东西。

使用 AssemblyAI 核心转录 API ,我们能够实时再现 DALL-E 2 论文中提出的零射击能力的元素。为了实现这一点,我们使用了一个不太复杂的模型,该模型是根据他们在论文中称为元数据集合的训练数据的汇总版本进行训练的:长指令的汇总更适合程序综合

这只有通过 AssemblyAI API 中存在的强大功能的组合才有可能实现,这些功能允许与机器学习模型和 web 界面框架无缝集成,以及它们用于修复畸形输入和在说出句子时隔离句子的校正语言建模。

你产品的灵感是什么?

这个项目的灵感来自 Open AI 的一篇名为Zero-Shot Text-to-Image Generation的论文,该论文引入了一个名为 DALL-E 2 的自回归语言模型,该模型对分割成片段的图像进行训练,这些片段被赋予自然语言描述。

你是如何构建你的项目的?

这个项目有两个主要部分:

  1. 实时音频转录。为此,我们与 AssemblyAI API 接口。客户端界面是用 HTML 和 CSS 制作的。服务器由 Node.js 使用 Express 托管。在客户端检测到按钮按下后,服务器将打开一个到 AssemblyAI API 的连接器。
  2. 文字到图像的生成。 为此,预先训练好的模型部署在本地机器上,Pytorch 模型与客户端和服务器并行运行。在与 AssemblyAI API 建立异步连接之后, Selenium 将在客户端和预训练模型之间来回传递消息,因为 API 使用从音频数据转录的文本进行响应。Selenium 将在客户端更新图像,同时该模型还将图像保存到本地驱动器。

Get a Free API Key

你从项目中最大的收获是什么?

音频转录工具越来越令人印象深刻!能够用少得多的数据产生与最先进的模型相似的结果也意味着较大的模型是在一些多余的数据上训练的。最后,改变预训练模式可以带来很大的不同。

语音到图像生成的下一步是什么?

地平线上有三件主要的事情:

  1. 使用像 ATOMIC 这样的知识图作为常识的基础,在生成阶段检查图像中存在的对象关联的语义正确性,以获得更好的图像。
  2. 将自然语言与向量中的一系列图像相关联,这些图像可以直观地描述用文本书写的一系列事件。
  3. 从得到的图像向量生成视频。

了解本项目更多信息 这里

用汇编语言建造

原文:https://www.assemblyai.com/blog/built-with-assemblyai-wordcab/

在我们的 Built with AssemblyAI 系列中,我们展示了使用 AssemblyAI 语音到文本转录 API 和/或我们的音频智能 API 创建的开发人员项目、创新公司和令人印象深刻的产品,如 情感分析 汽车章节 实体检测 等等。

描述你用 AssemblyAI 构建的东西。

Wordcab 是一个利用深度学习的力量将对话(通话、播客、事件)转换成听起来像人类的摘要的 API。生成精彩摘要的关键是要有清晰的文字记录和准确的说话人转调

用户可以将文本或音频文件直接输入我们的 API。如果他们输入一个原始的音频文件,我们使用 AssemblyAI 来转录它,然后输入到我们的摘要器中。

你产品的灵感是什么?

世界越来越依赖远程会议,产生了大量的录音、视频和文本数据。专业人员没有时间浏览 30 分钟的文字记录或音频记录——无论这是一个经理在查看他们的猎人的表现,还是一个 QA 团队在检查客户代表的支持电话。

有了 Wordcab 的 summary API,抄本阅读时间(和收听时间)平均减少了 90%,使专业人员能够快速筛选几十个电话。我们是目前市场上最先进的对话摘要 API,以 B2B2C/B 模式运营,与语音智能平台合作,将高级摘要功能集成到他们的平台中。

你使用哪些 AssemblyAI 特性?

对于音频文件,我们转录多发言者的对话。我们需要精确的二进制化,因为摘要通过标签引用发言者。我们还需要准确的转录,因为这直接反映在摘要的质量上。

你为什么选择用 AssemblyAI 来构建?

在我们测试的所有提供者中,AssemblyAI 是最准确的,尤其是在二进制化方面。

我们为 AssemblyAI 对其转录准确性和 diarizer 的持续改进感到兴奋。

你有产品的样品吗?

本 YouTube 教程将带您了解我们的摘要 API 的基础知识:

https://www.youtube.com/embed/Rg141yfkl_o?feature=oembed

想自己试试 Wordcab 吗?

到这里报名吧!

Sign up here

用 AssemblyAI - YouTube 脚本构建

原文:https://www.assemblyai.com/blog/built-with-assemblyai-youtube-transcripts/

在我们的 Built with AssemblyAI 系列中,我们展示了使用 AssemblyAI 语音到文本转录 API 和/或我们的音频智能 API 创建的开发人员项目、创新公司和令人印象深刻的产品,如 情感分析 汽车章节 实体检测 等等。

描述你用 AssemblyAI 构建的东西。

一键录制 YouTube 视频

你产品的灵感是什么?

我们中的一个是 YouTuber,正在寻找价格合理的快速副本。他的频道增长到 25k subs,有大约 10 个视频,并且从一开始就在 SEO 的字幕上下赌注。

它的独特之处在于,我们将自己直接嵌入 YouTube studio,拥有最快的 YouTube 视频工作流程——这是一个利基解决方案。

你使用哪些 AssemblyAI 特性?

我们使用核心转录段落检测,并且很快将添加置信度得分。

Get a Free API Key

你为什么选择用 AssemblyAI 来构建?

我们选择用 AssemblyAI 构建是因为准确性、价格和活跃的特性集开发。

你有产品的样品吗?

你可以在这个 YouTube 视频中看到它是如何工作的。

YouTube 抄本 Chrome 扩展

CallRail 通过 AssemblyAI 的语音转文本 API 增强了对话智能的领导力

原文:https://www.assemblyai.com/blog/callrail-amplifies-conversation-intelligence-leadership-with-assemblyais-speech-to-text-api/

挑战

CallRail 是面向中小型企业和营销机构的领先 SaaS 解决方案,提供营销分析产品,包括呼叫跟踪、表单跟踪和对话智能以及集中式业务通信解决方案。CallRail 帮助他们的客户了解哪些营销活动推动了与潜在客户的最佳对话,以及改善客户服务的实时数据,以便他们可以有效地投资预算并达成更多业务。

为了扩展这些一流的营销分析功能,CallRail 希望与现代的语音转文本 API 合作,以提高对话智能转录的准确性和质量。

然而,该公司的需求超出了基本的通话记录。为了在对话智能领域处于领先地位,CallRail 还希望提升其 PII 修订功能、总体报告准确性以及数据分类和资格认证方式。

CallRail 前产品总监迈克尔·坎特雷尔(Michael Cantrell)解释说:“对我们的许多客户来说,知道有对话是第一步。“能够一眼就知道对话的内容,这可以让他们真正了解自己的业务以及客户对他们的看法。它可以帮助我们的客户用他们正在使用的语言更好地达成交易,并改进他们的营销工作。”

解决方案

CallRail 的搜索让他们找到了 AssemblyAI,一个高精度的语音转文本 API,一个广泛的功能列表,并在电话行业取得了成功。

CallRail 最初的实现集中在两个主要的 AssemblyAI 特性上:自动副本高亮显示PII 修订

AssemblyAI 的自动转录突出显示功能可以自动检测转录文本中的关键短语和单词。这一功能使 CallRail 能够在给客户的电话中自动呈现和定义关键内容,例如特定的客户请求、常见问题以及常用的关键字和短语。它还能让 CallRail 用户“一目了然”地更好地理解电话内容

AssemblyAI 的 PII 密文功能会自动检测并删除抄本文本中的敏感数据。这可能包括社会安全号、信用卡号、个人地址等等。

总之,这些功能使 CallRail 能够为其客户提供强大的对话智能解决方案。

CallRail 同样很高兴能在 AssemblyAI 找到合作伙伴,assembly ai 的开发人员和深度学习工程师团队与该公司密切合作,优化 API 以满足 CallRail 的需求。

“我们能够在首次展示时直接与 AssemblyAI 合作,并且实现了相对简单的切换,”Cantrell 解释道。“持续的支持过去是,现在仍然是出色的。AssemblyAI 自始至终都觉得自己是一个真正的合作伙伴。”

结果呢

借助 AssemblyAI,CallRail 的通话记录准确率提高了 23%,使用其产品的客户数量增加了一倍。

两家公司之间的反馈循环也是一个巨大的积极因素。例如,当新冠肺炎·疫情开始出现在对话中时,这个新术语不在 AssemblyAI 的模型词汇表中。CallRail 将这些信息传递给 AssemblyAI,assembly ai 的团队能够添加这些信息,并通过快速周转提高整体转录准确性。AssemblyAI 的 API 产生的高级自动标点和大小写也对 CallRail 的抄本的可读性产生了重大影响。

作为一家深度学习公司,AssemblyAI 致力于持续改进和创新。其专门的内部研究团队每周向 production weekly 推送精度更新,包括最近对递归神经网络传感器(RNNT)模型架构的更新,将精度提高到行业最佳水平。

“这种方法让我们放心,我们的产品将始终提供出色的转录。我们可以利用更新的功能为我们的客户带来更强大的分析,”Cantrell 说。

这包括计划在不久的将来将 AssemblyAI 的新主题检测和内容安全检测功能集成到 CallRail 平台,为客户提供更多信心,相信他们有能力通过该解决方案改善客户体验和呼叫转化率。

播客能预测股市吗?

原文:https://www.assemblyai.com/blog/can-podcasts-predict-the-stock-market/

每个人都想知道如何预测股市。大家也知道基本不可能。在 AssemblyAI,我们想知道新闻播客中的负面事件是否能以某种方式预测股市。在本帖中,我们将介绍如何比较播客数据和股市数据,以及去年两个著名新闻播客首先对去年股市的负面评价,特别是道琼斯工业平均指数、纳斯达克指数和皇家黄金指数。

我们是如何利用播客中的负面新闻来预测市场的?

我们首先获取了每日播客的音频文件,并用 AssemblyAI 的自动语音到文本转录 API 转录它们。 AssemblyAI 的语音转文本 API 还提供了启用检测负面新闻的内容安全评级的选项。我们在将音频文件传递到语音到文本 API 端点时使用了这个选项,以检测每个播客片段中的负面新闻。带有此选项的 AssemblyAI API 返回的响应如下所示:

{
    "text": "Five terms. Speaker Joe Straus says he will not run for reelection state representative next year from San Antonio. And in late 2017, he announces that he'll step down before the 2019 session. From now on, far right. Republicans will have more sway. It's virtually the end of any sort of moderate strain of Republicanism in Texas. As one professor put it, the political center of the state collapsed today. Wow. That sounds like a real end of an era.",
    "labels": [
        {
            "confidence": 0.949425995349884,
            "label": "negative_news"
        }
    ],
    "timestamp": {
        "start": 665780,
        "end": 695530
    }
}

返回的每个段落都有一个标签,标签上有置信度得分和时间戳。

我们用等于音频文件中负面新闻比例的负面评级来标记每一集,例如,如果有 50 个文本块被返回,并且有 5 个负面新闻被提及(85%以上的置信度),则音频文件的负面评级为 0.1。

主要发现

从图表来看,我们最负面的消息出现在黄金上涨和市场下跌的前 1-3 天。我们可以放大并通过下图确认这是真的。几乎每一条蓝线——划分一个有特别负面消息的日期——之后是 DJIA 和 NDAQ 的下降或一系列红色日,然后是 RGLD 的上升或一系列绿色日。

那么这对你实际上意味着什么呢?如果你听到非常负面的消息,也许你想在未来 1-3 天内买入,除非你关注黄金价格。如果您想自己探索这个问题,我们将深入研究代码,这样您就可以自己完成这个过程。

步伐

  1. 创建一个 Python 网络爬虫来收集音频文件的链接
  2. 用 AssemblyAI 的语音转文本 API 转录音频文件
  3. 计算和编制负面新闻评级
  4. 下载股票数据
  5. 根据 DJIA、NDAQ 和 RGLD 绘制负面新闻评级图
  6. ???
  7. 利润

创建一个 Python 网络爬虫来收集音频文件的链接

我们将使用 Selenium 抓取收听笔记并获取每日播客的链接(在这个示例代码中,链接是 to Up First)。

要安装 Selenium,请运行:

pip install selenium

我们将使用 Selenium 和 Chromedriver 来打开 Chrome 中的链接。然后,我们将寻找“加载更多”按钮,并点击它 36 次,以获取去年的所有播客。在我们扩展了页面之后,我们将通过点击“更多”按钮获得“下载”链接,并获得“下载”按钮链接的“href”值。我们将所有这些保存在一个. csv 文件中,并在以后使用它来转录我们的链接。

"""
Downloads a specified number of podcasts from a specific podcast
using Selenium
"""
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import ActionChains
from time import sleep
from random import randint

# download last 365 the daily podcasts
# download last 365 up first podcasts

chromedriver_path = './chromedriver'
driver = webdriver.Chrome(executable_path=chromedriver_path)
sleep(randint(1, 3))
driver.get('https://www.listennotes.com/podcasts/up-first-npr-e0zpMGZKNYd/#podcast')
sleep(randint(2, 3))

dl = 0
links = []
for i in range(37):
   actions = ActionChains(driver)
   load_more = driver.find_element_by_xpath("//button[contains(., 'Load more')]")
   actions.move_to_element(load_more).click().perform()
   sleep(randint(1, 3))
while dl < 366:
   more_button = driver.find_elements_by_link_text("MORE")
   for link in more_button:
       actions = ActionChains(driver)
       actions.move_to_element(link).click().perform()
       dl += 1
       sleep(randint(1, 3))

dlinks = driver.find_elements_by_css_selector("a[title*='Download audio file']")
for dlink in dlinks:
   links.append(dlink.get_attribute("href")[:-1]+'.mp3')

with open('upfirst_links.csv', 'w') as _file:
   for link in links:
       _file.write(link)
       _file.write(",")

完成后,您应该有一个类似如下的. csv 文件:

用 AssemblyAI 的语音转文本 API 转录音频文件

现在我们已经有了链接,我们将使用 AssemblyAI 的人工智能自动语音识别 API 端点,并为我们的每个播客获得内容安全评级。这里需要注意的是,我们正在通过运行大量的音频。我们在大约 700 个播客上运行人工智能转录,每个半小时,至少 300 小时的音频。一个人大概需要 13 天,甚至更长时间才能转录出来。我们将在一天内用 AssemblyAI 的自动语音转文本 API 来完成。我们需要注册一个 AssemblyAI API key ,它将位于我在图片中画出的位置。

在我们得到 API 密匙后,我们将创建一个函数来转录代码。为此,我们将创建多个函数。首先,我们将创建一个函数,允许我们使用 AssemblyAI 的语音转文本 API 转录音频文件。我们需要脚本端点、授权头和一些常量来帮助我们运行程序——一个让我们知道脚本处理的状态,另一个帮助我们一次运行多个脚本。我们还将创建一些函数来轮询 AssemblyAI 语音转文本 API,并在抄本状态为完成时将抄本保存到一个文件中。

import requests
from config import auth_key
import json
import os
import csv

transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
headers = {
   "authorization": auth_key,
   "content-type": "application/json"
}
INCOMPLETE = 'Incomplete'
NUMBER = 20

# Parameters: audio_url - link to audio
# Returns: transcript_id - transcript id corresponding to AssemblyAI transcription id
# send a transcription request to the AssemblyAI transcript endpoint
# this one contains true for content safety so we can get negative news labels
def transcribe(audio_url):
   transcript_request = {
       'audio_url': audio_url,
       'content_safety': 'True'
   }
   transcript_response = requests.post(
       transcript_endpoint,
       json=transcript_request,
       headers=headers)
   transcript_id = transcript_response.json()['id']
   return transcript_id

# Parameters: transcript_id - transcript id corresponding to AssemblyAI transcription id
# Returns: transcript_id (same) or INCOMPLETE status
# polls the transcription endpoint of the id to see if the transcription is done
# will also return an exception if there is an error
def poll(transcript_id):
   polling_endpoint = transcript_endpoint + "/" + transcript_id
   print("Transcribing at", polling_endpoint)
   try:
       polling_response = requests.get(
           polling_endpoint,
           headers=headers)
       if polling_response.json()['status'] == 'completed':
           save_transcript(polling_response)
           return transcript_id
       else:
           return INCOMPLETE
   except Exception as err:
       print(f"Exception occured {err}")

# edit before running for each podcast
# Parameters: response - a response object from AssemblyAI
# saves the content safety labels returned from AssemblyAI to the folder
# of the podcast that we are currently processing
def save_transcript(response):
   transcript_id = response.json()['id']
   content_filename = transcript_id + '_content.json'
   with open('./upfirst_transcripts/' + content_filename, 'w') as f:
       f.write(json.dumps(response.json()['content_safety_labels']))
   print('Content safety saved to', content_filename)

既然我们已经创建了通过 AssemblyAI 的语音识别 API 来处理转录音频的函数,我们需要创建一些函数来处理运行如此多的文件。我们将创建一个函数,将文件附加到一个. csv 文件中,该文件跟踪我们已经检查并转录的链接,我们将创建一个函数,跟踪当前通过 AssemblyAI 的语音到文本 API 处理的转录 id。然后是剧本。

我们的脚本将打开一个包含播客音频文件链接的 csv 文件。它将检查我们是否已经为检查的链接创建了 csv,并检查当前运行的脚本 id 的 csv。如果选中链接的数量等于总链接的数量,我们就完成了。如果没有,我们从链接列表中删除选中的链接。

# Parameters: link - the link to the audio file we have processed
# writes link as a new row to the csv
def append_file(link):
   with open('checked_links.csv', 'a') as _file:
       writer = csv.writer(_file, delimiter=',')
       writer.writerow([link])

# Parameters: transcript_ids - a list of AssemblyAI transcription ids
# saves the current batch of transcripts being processed
def write_transcripts(transcript_ids):
   with open('transcript_ids.csv', 'w') as _file:
       writer = csv.writer(_file, delimiter=',')
       for transcript_id in transcript_ids:
           writer.writerow([transcript_id])

# read the links we have for the podcast
with open('upfirst_links.csv', newline="") as _file:
   reader = csv.reader(_file)
   links = list(reader)
# read which links we have that have already been checked
if os.path.exists('checked_links.csv'):
   with open('checked_links.csv', newline="") as _file:
       reader = csv.reader(_file)
       checked_links = list(reader)
else:
   checked_links = []
# check if we have any existing files being transcribed
if os.path.exists('transcript_ids.csv'):
   with open('transcript_ids.csv', newline="") as _file:
       reader = csv.reader(_file)
       transcript_ids = list(reader)
else:
   transcript_ids = []

# check if we've already checked all the links
if len(checked_links) == len(links):
   print("Done")
   exit()
# remove items in checked_links from links
for checked in checked_links:
   links.remove(checked)

如果我们已经有了 AssemblyAI 的语音到文本转录 API 的一组转录 id,我们将轮询状态。我们将计算完成的文件数量,然后按顺序替换它们。当然,这不会总是保持时间顺序,但我们实际上可以通过轮询来做到这一点,在这种速率下,我们可以预期所有的音频都已经被转录。AssemblyAI 的语音到文本 AI 转录服务的转录时间可以安全地估计为音频文件长度的⅓。

# this is a list of one element lists
# transcript_ids should just be the first NUMBER elements in links
if transcript_ids:
   poll_responses = [poll(transcript_id[0]) for transcript_id in transcript_ids]
   _completed_indices = []
   for index, res in enumerate(poll_responses):
       if res != INCOMPLETE:
           _completed_indices.append(index)
   # remove completed indices
   print(f"These are the completed indices {_completed_indices}")
   if _completed_indices:
       if len(_completed_indices) != NUMBER:
           transcript_ids = [id for index, id in enumerate(transcript_ids) if index not in _completed_indices]
           current_links = links[:NUMBER]
           processed_links = [link for index, link in enumerate(current_links) if index in _completed_indices]
       else:
           transcript_ids = []
           processed_links = links[:NUMBER]
       # add x new transcripts
       added_links = links[NUMBER:NUMBER+len(_completed_indices)]
       # add each processed link to checked links and write to csv
       for link in processed_links:
           append_file(link[0])
       # request new transcripts for added links
       for link in added_links:
           transcript_ids.append(transcribe(link[0]))
       write_transcripts(transcript_ids)
else:
   current_links = links[:NUMBER]
   for link in current_links:
       transcript_ids.append(transcribe(link[0]))
   write_transcripts(transcript_ids)

我们多次运行这个脚本,直到我们完成了文件中所有链接的转录。在我们转录完我们的链接后,我们应该会得到一个包含 JSON 文件的文件夹,这些文件包含如下所示的文本块。

{
    "text": "Five terms. Speaker Joe Straus says he will not run for reelection state representative next year from San Antonio. And in late 2017, he announces that he'll step down before the 2019 session. From now on, far right. Republicans will have more sway. It's virtually the end of any sort of moderate strain of Republicanism in Texas. As one professor put it, the political center of the state collapsed today. Wow. That sounds like a real end of an era.",
    "labels": [
        {
            "confidence": 0.949425995349884,
            "label": "negative_news"
        }
    ],
    "timestamp": {
        "start": 665780,
        "end": 695530
    }
}

计算和编制负面新闻评级

在通过 AssemblyAI 的语音转文本 API 运行我们所有的音频文件并获得带有内容安全标签的 JSON 文件后,我们现在可以开始计算和编辑负面新闻评级。这部分有点复杂,不完全精确,所以扣好安全带。我犯了一个不幸的错误,在我最初下载播客时没有保存相应的日期,我认为我注定要失败,但后来我意识到我可以按照我下载它们的顺序倒填日期,从 2021 年 8 月 21 日开始。我们要做的第一件事是获得每个新闻播客的负面评价列表。这个列表将按时间顺序排列。我们将用来获得这些负面评价的函数是:

def negativity_ratings(content_files):
   neg_ratings = []
   for filename in content_files:
       with open(filename, 'r') as _file:
           content = json.load(_file)
       negative_news_mentions = 0
       total = 0
       for result in content['results']:
           total += 1
           for entry in result['labels']:
               if entry['confidence'] > 0.85 and entry['label'] == 'negative_news':
                   negative_news_mentions+=1

       if total == 0:
           print(f"Error with {filename}")
           continue
       neg_ratings.append(negative_news_mentions/total)
   return neg_ratings

我们将创建三个负面评级,首先在每日运行,然后创建第三个,合并他们的评级。我们还将把我们的数据标准化到 0 到 1 之间,并把它变成新闻负面性的衡量标准,0 是最负面的日子,1 是最不负面的日子,而不是 0 是没有提到负面新闻,1 是完全负面的播客。

daily_negs = negativity_ratings(daily_content_files)
upfirst_negs = negativity_ratings(upfirst_content_files)
congregate = [i + j for i, j in zip(upfirst_negs, daily_negs)]

_max = max(congregate)
normed_congregate = [1-float(e)/_max for e in congregate]
_max = max(upfirst_negs)
normed_upfirst = [1-float(e)/_max for e in upfirst_negs]
_max = max(daily_negs)
normed_daily = [1-float(e)/_max for e in daily_negs]

我们将使用一个返回 10 个最小索引的函数来查找与这三个列表中的最小值相对应的索引。然后我们会把这些指数结合起来,并把它们与道琼斯工业平均指数、纳斯达克指数和皇家黄金指数进行对比。

def get_min_indices(normed_function):
   min_indices = []
   vals = sorted(set(normed_function))
   for i in range(10):
       index = normed_function.index(vals[i])
       min_indices.append(index)
   return min_indices
min_congregate_indices = sorted(get_min_indices(normed_congregate))
min_daily_indices = sorted(get_min_indices(normed_daily))
min_upfirst_indices = sorted(get_min_indices(normed_upfirst))

整理这些日子,我们发现它们是:

  • 2021 年 8 月 13 日
  • 2021 年 7 月 14、15 和 16 日
  • 2021 年 7 月 8 日
  • 2021 年 7 月 3 日
  • 2021 年 6 月 1 日
  • 2021 年 5 月 4 日
  • 2021 年 3 月 18 日
  • 2021 年 2 月 14 日
  • 2021 年 1 月 25 日
  • 2020 年 10 月 21 日

下载股票数据

我们可以用雅虎财经的 Python SDK 下载股票数据。您可以通过运行以下命令来安装 yfinance:

pip install yfinance 

然后我们可以像这样下载去年的股市数据

start = datetime.datetime(2020,8,21)
end = datetime.datetime(2021,8,21)

symbol = 'DJIA'
try:
   stock = yf.download(symbol, start=start, end=end, progress=False)
   stock['Name'] = symbol
   stock.to_csv(f'./stocks/{symbol}.csv', index=False)
except Exception as err:
   print(f"Error occurred {err}")

根据 DJIA、NDAQ 和 RGLD 绘制负面新闻评级图

我们将使用 Python 创建一些蜡烛图,这样我们就可以绘制我们的股票数据。为此,我们需要 pandas、matplotlib 和 mplfinance。要下载这些,您可以运行

pip install matplotlib mplfinance

我们将读入文件并添加标签。

import csv
import pandas as pd
def get_ohlc(filename):
   with open(filename, newline="") as _file:
       reader = csv.reader(_file)
       arr = pd.DataFrame(reader)
       ohlc = arr.loc[1:,0:3]

   ohlc.columns = ['Open', 'High', 'Low', 'Close']
   try:
       ohlc = ohlc.astype(float).dropna()
   except ValueError as err:
       print(err)
       return [], []
   dates = [i for i in range(0, len(ohlc))]
   ohlc.insert(0, 'Dates', dates)

   return ohlc

在我们绘制这个图之前,我们需要标准化我们的数据。

fig, ax = plt.subplots()
ohlc = get_ohlc(entry)
# add 1 to make sure there's no "drop off" candlesticks that disappear
ohlc['Open'] -= min(ohlc['Open'])+1
ohlc['Open'] /= max(ohlc['Open'])
ohlc['Close'] -= min(ohlc['Close'])+1
ohlc['Close'] /= max(ohlc['Close'])
ohlc['High'] -= min(ohlc['High'])+1
ohlc['High'] /= max(ohlc['High'])
ohlc['Low'] -= min(ohlc['Low'])+1
ohlc['Low'] /= max(ohlc['Low'])
candlestick_ohlc(ax, ohlc.values, width=0.6, colorup='green', colordown='red', alpha=0.8)

现在我们回到我们确定的每天最消极的日期,首先是我们用 AssemblyAI 的语音转文本 API 转录的播客,并把它们绘制成垂直线,像这样

for i in z: plt.axvline(i)

然后我们应该会看到类似下图的图表。

如何阅读这些图表

这些蜡烛图放大了特定负面新闻的日期。y 轴的标度从 0 到 1,因为 a)将一组非负数归一化到这个标度是一种形状保持变换,b)如果我们愿意,我们可以根据这个标度绘制播客中负面新闻的频率,并查看它如何以更绝对的方式排列。我选择不包括这些图表,因为它们比我包括的那些更难阅读。负面新闻峰值用蓝色竖线标出。

让我们拉近镜头,审视那些围绕着我们特别负面的新闻的日子。正如我之前所说的,看起来黄金价格与负面消息峰值负相关,价格会在随后几天上涨,看起来 T2 和 DJIA 与负面消息峰值直接相关,价格会在随后几天下跌。

在第一张图中,我们看到黄金价格在负面消息峰值后一天大幅上涨,第二天又突然下跌。我们还可以看到,纳斯达克指数在随后的几天里大幅下跌。DJIA 没有反映太多的下降,所以我没有包括图表。这是谷歌面临反垄断问题和全球抗议的时候。

这张图表向我们展示了在这个负面消息日之后,黄金价格的又一次上涨。我已经在上面的图表中包括了纳斯达克的这个日期,DJIA 再次显示没有剧烈的变化。我想这是在选举前后。

在这里,我们看到黄金价格在另一个负面消息高峰后的几天里出现了相当大的上涨。这一次,我们可以看到 DJIA 和纳斯达克都做出了回应,在这一系列负面消息之后的几天里,这两个指数的价格都出现了下跌。我相信这些新闻点是在德克萨斯州发生疫苗争议和针对亚裔美国人的种族暴力上升的时候。

在又一个负面消息高峰之后,黄金价格又一次上涨。纳斯达克和 DJIA 没有对这一负面消息做出强烈反应。大约在那个时候,美国发生了许多针对种族暴力的抗议活动。

黄金价格在负面消息后的上涨大约是在 COVID delta 变体变得更加普遍的时候。我们可以看到,DJIA 和纳斯达克在接下来的几天里也对这一消息作出了下跌的反应,DJIA 在此之后也确实下跌了。

最后一张图显示了黄金价格在 2021 年 7 月下旬左右的上涨。我们可以看到纳斯达克和 DJIA 都对此做出了反应,并在随后的几天下跌。这是围绕着西部的野火和对气候变化的更多关注。我不相信购买黄金会保护任何人免受气候变化的影响,但作为对负面消息的恐惧反应,大宗商品价格上涨总是有道理的。

结论

可以用播客预测股市吗?大多数时候不会,但在有特别坏消息的日子里,你可以期待在接下来的 1-3 天里看到股票市场的下跌。特别坏是多坏?我随意选择了过去一年中最糟糕的 10 天,并将其中的两组组合起来,发现这些日子确实预测了下跌。通过 AssemblyAI 在语音转文本 API 上的内容安全选项,他们的负面评级都超过了 0.7。想了解更多关于语音转文本和其他酷教程的信息,请在 Twitter 上关注我们 @assemblyai@于坚 _ 唐

在电话呼叫转录上比较语音到文本 API

原文:https://www.assemblyai.com/blog/comparing-speech-to-text-apis-on-phone-call-transcription/

电话公司的产品经理和开发人员一直在利用自动语音识别(ASR)来增强其产品和平台的核心功能。

例如,像 ConvirzaCallRailTalkRouteWhatConverts 这样的电话平台为他们的客户提供行业领先的语音到文本的解决方案。以下是几个例子:

语音转文本系统的准确性对于这些电话平台构建用户和客户喜爱的高质量功能至关重要。

在这份报告中,我们查看了来自不同公司的 5 个不同的盈利电话(下面将更详细地显示),并审查 AssemblyAI、AWS transcriptor 和 Google Speech-to-Text 能够自动转录这些记录的准确性。

除了审查语音识别准确性,我们的研究团队还审查了 AssemblyAI 独特的个人身份信息(PII)编辑主题识别关键词检测内容安全功能在这些通话记录上的结果。该报告旨在作为一个参考点,用于比较电话平台市场上最好的自动语音识别和对话智能解决方案。

语音识别准确性

我们的数据集

我们收录了 5 家大公司的盈利电话录音, Twilio脸书苹果微软MongoDB 。随机选择,我们的意图是为您提供一个健康的音频转录性能样本大小。

以下是关于我们数据集的更多信息:

https://airtable.com/embed/shrn4pJ0pWbsasxXQ?backgroundColor=blue

我们如何计算精确度

  1. 首先,我们通过 API(assembly ai、Google 和 AWS)自动转录数据集中的文件。
  2. 第二,我们由人类转录员转录数据集中的文件,准确率接近 100%。
  3. 最后,我们将 API 的转录与我们的人类转录进行比较,以计算单词错误率(WER)——更多内容见下文。

下面,我们概述了每个转录 API 在每个音频文件上实现的准确度分数。每个结果都超级链接到人类转录本与每个 API 自动生成的转录本之间的差异。这有助于突出人工转录和自动转录之间的关键差异。

https://airtable.com/embed/shr9UrYd3z2uMtfAm?backgroundColor=blue

精确度平均值

https://airtable.com/embed/shrG6C6BkiJeBDG7i?backgroundColor=blue

WER 方法论

单词错误率(WER) 是计算自动语音识别系统准确度的行业标准。对于我们数据集中的每个文件,WER 将自动生成的转录与人工转录进行比较,计算自动系统(Google、AWS 等)进行的插入、删除和替换的数量,以计算 WER。

在计算特定文件的 WER 之前,必须将真实(人工转录)和自动转录(预测)标准化为相同的格式。为了执行最准确的比较,所有标点符号和大小写都被删除,数字被转换为相同的格式。

例如:

truth -> Hi my name is Bob I am 72 years old.

normalized truth -> hi my name is bob i am seventy two years old

个人身份信息(PII)编辑

电话录音和文字记录通常包含敏感的客户信息,如信用卡号、地址和电话号码。其他敏感信息,如出生日期和医疗信息也可以存储在录音和文字记录中。AssemblyAI 为通过我们的 API 运行的抄本和音频文件提供了 PII 检测和编辑。你可以在 AssemblyAI 文档中了解更多关于这是如何工作的。

我们的 PII 检测和编辑功能可以检测的完整信息列表如下:

https://airtable.com/embed/shrLpSoLAIbUIN5fU?backgroundColor=blue

随着 PII 检测和编辑 的启用,我们通过我们的 API 运行上述收入调用,以生成每个记录的编辑副本。下面是其中一段录音的摘录,下面是完整转录的链接。

Good afternoon. My name is [PERSON_NAME] and I will be your [OCCUPATION] 
[OCCUPATION] today. At this time, I would like to welcome everyone
to the [ORGANIZATION] [DATE] [DATE] and full Year [DATE] [DATE] earnings
conference call. All lines have been placed on mute to prevent any
background noise. After the speakers remarks, there will be a question
and answer session. If you would like to ask a question during
that time, please press star, then the number one on your telephone
keypad. This call will be recorded. Thank you very much, [PERSON_NAME]
[PERSON_NAME] [PERSON_NAME], [ORGANIZATION] [OCCUPATION] [OCCUPATION]
[OCCUPATION] [OCCUPATION] [OCCUPATION]. You may begin. Thank you. Good
afternoon and welcome to [ORGANIZATION] for quarter and full Year [DATE] [DATE] earnings conference call. Joining me today to discuss our results
or [PERSON_NAME] [PERSON_NAME], [OCCUPATION] [PERSON_NAME] [PERSON_NAME]
[OCCUPATION] and [PERSON_NAME] [PERSON_NAME], [OCCUPATION]. Before we
get started, I would like to take this opportunity to remind you that
our remarks today will include forward looking statements. Actual
results may differ materially from those contemplated by these forward
looking statements. Factors that could cause these results to differ
materially are set forth in today's press release and in our quarterly
report on Form 10 [DATE] filed with the [ORGANIZATION]. 

https://airtable.com/embed/shrfuITQ5dL5rhCw9?backgroundColor=blue

话题检测

我们的主题检测功能使用 IAB 分类法,可以对多达 698 个可能主题的转录文本进行分类。例如,特斯拉收益电话会议将被归类为"Automobiles>SelfDrivingCars"主题等。

您可以在 AssemblyAI 文档中找到可以检测到的所有主题类别的列表。

关键词和短语识别

AssemblyAI 还可以根据转录文本自动提取关键字和短语。下面是一个示例,展示了该模型如何在一个小型转录文本样本上工作。

原始转录:

Hi I'm joy. Hi I'm Sharon. Do you have kids in school? I have 
grandchildren in school. Okay, well, my kids are in middle school in high
school. Do you think there is anything wrong with the school system?

检测到的关键字:

"high school"
"middle school"
"kids"

使用相同的数据集,我们包括了在以下每个文件中自动检测到的主题和关键字:

https://airtable.com/embed/shrNFblOnRyfxhXfA?backgroundColor=blue

内容安全检测

电话公司现在正在利用机器学习来标记电话中的不当内容。借助 AssemblyAI 的内容安全检测功能,我们可以标记您的转录中包含仇恨言论、亵渎或暴力等敏感内容的部分。例如,电话平台正在使用这一功能来检测呼叫中心和语音邮件中的仇恨言论和脏话。

AssemblyAI 的内容安全检测功能是使用最先进的深度学习模型构建的。我们的模型在决定是否标记一段内容时会查看单词/句子的整个上下文——我们不依赖容易出错的反向列表方法。

下面我们回顾一下 AssemblyAI 的内容安全检测功能对上述数据集的检测结果:

https://airtable.com/embed/shrPXramaHY37PmD4?backgroundColor=blue

如你所见,幸运的是,在这些收益电话会议上没有讨论亵渎或敏感的内容。然而,内容安全检测功能确实正确地标记了在这些转录中讨论了"Company Financials"——出于合规性原因,许多电话平台需要能够检测到这些转录。

基准测试您的数据

提供商之间的准确性基准测试需要时间和金钱来运行您的内容。我们为任何寻求转录解决方案的团队提供免费的基准报告。要开始使用免费的基准测试报告,您可以点击这里。‍‍

比较语音到文本 API 之间的缩放转录准确性

原文:https://www.assemblyai.com/blog/comparing-zoom-transcription-accuracy-across-speech-to-text-apis/

Zoom 每年托管 3.3 万亿份会议纪要,比去年同季度增长 3,300%。这大约是今年世界上每个人使用 Zoom 进行 7 小时会议的时间。

很明显,Zoom 正在改变我们的学习和工作方式,许多新的软件公司通过在 Zoom 基础上构建他们的解决方案来进一步推动这种采用。最近推出的 Zoom Apps 让我们更容易利用这些新的集成从对话中获得更多信息,一些例子包括:

英寻

记录、转录并突出显示 Zoom 电话中的关键时刻,这样您就可以专注于谈话,而不是做笔记

维德亚德

自动将您的缩放会议和网络研讨会录像发送到一个视频平台,您可以在该平台上主持、编辑和安全地共享您的视频

在会议期间,撰写评论并与贵公司的其他 Gong 用户进行实时协作

Zoom 及其所有的集成合作伙伴正在丰富我们正在进行的虚拟对话;帮助我们记住我们讨论的内容,并强调我们可能没有意识到的见解(如谁说得更多,讨论了什么主题等)。

自动语音识别(ASR)是这些平台的核心,转录准确性是这些公司的产品经理和开发人员的首要任务,以确保他们建立尽可能最好的功能。

在这份基准报告中,我们发现了各种使用案例中的变焦记录,从产品演示和内部业务会议,到学校和健身房的在线直播课程。然后,我们将 AssemblyAI、Google Cloud 语音转文本和 AWS Transcribe 之间哪些 ASR 模型的转录准确率最高进行了并排比较。

我们还使用 AssemblyAI 的丰富模型来分享这些相同缩放记录的结果,这些模型用于主题检测关键词检测PII 编辑内容安全检测

语音识别准确性

我们的数据集

为了给出全面的准确性结果,我们使用了销售演示、网络研讨会、在线课堂、烹饪教程和健身小组课程的变焦记录。这提供了各种口音、音频质量、扬声器数量和特定于行业的词汇。

以下是关于我们数据集的更多信息:

https://airtable.com/embed/shryTf4jiTKYs9lRP?backgroundColor=blue&viewControls=on

我们如何计算精确度

  1. 首先,我们通过 API(assembly ai、Google 和 AWS)自动转录数据集中的文件。
  2. 第二,我们由人类转录员转录数据集中的文件,准确率接近 100%。
  3. 最后,我们将 API 的转录与我们的人类转录进行比较,以计算单词错误率(WER)——更多内容见下文。

下面,我们概述了每个转录 API 在每个音频文件上实现的准确度分数。每个结果都超级链接到人类转录本与每个 API 自动生成的转录本之间的差异。这有助于突出人工转录和自动转录之间的关键差异。

准确性结果

https://airtable.com/embed/shrstbrzRH0RSaPl0?backgroundColor=blue&viewControls=on

精确度平均值

https://airtable.com/embed/shry7FIqxgytxJsJJ?backgroundColor=blue&viewControls=on

WER 方法论

使用单词错误率(WER)计算上述准确度分数。WER 是计算自动语音识别系统准确度的行业标准度量。WER 将每个文件的 API 生成的转录与人类转录进行比较,计算自动化系统进行的插入、删除和替换的数量,以导出 WER。

在计算特定文件的 WER 之前,真实(人工转录)和自动转录都必须被规范化为相同的格式。这是团队经常错过的一步,最终导致误导性的 WER 数字。那是因为根据 WER 算法,“你好”和“你好!”是两个不同的词,因为一个有感叹号,而另一个没有。这就是为什么,为了执行最准确的 WER 分析,所有标点符号和大小写都从人工和自动抄本中删除,并且所有数字都被转换为口语格式,如下所述。

为了 example:‍

真相: Hi my name is Bob I am 72 years old.

****常态化真相:

装配富集

话题检测

我们的主题检测功能使用 IAB 分类法,可以对多达 698 个可能主题的转录文本进行分类。例如,在 Zoom 上流式传输的特斯拉发布事件将被分类为"Automobiles>SelfDrivingCars"主题等。

您可以在 AssemblyAI 文档中找到可以检测到的所有主题类别的列表。

关键词检测

AssemblyAI 还可以根据转录文本自动提取关键字和短语。下面是一个示例,展示了该模型如何在一个小型转录文本样本上工作。

原始转录:

Hi I'm joy. Hi I'm Sharon. Do you have kids in school? I have grandchildren in school. Okay, well, my kids are in middle school in high school. Do you think there is anything wrong with the school system?

检测到的关键字:

"high school"
"middle school"
"kids"

使用相同的数据集,我们包括了在以下每个文件中自动检测到的主题和关键字:

https://airtable.com/embed/shrsNq5T6nm76gy4Z?backgroundColor=blue&viewControls=on

PII 修订版

缩放会议可能包含敏感的客户信息,如信用卡号、地址和电话号码。其他敏感信息,如出生日期和医疗信息也可以存储在录音和文字记录中。AssemblyAI 为通过我们的 API 运行的抄本和音频文件提供了 PII 检测和编辑。

你可以在 AssemblyAI 文档中了解更多关于这是如何工作的。我们的 PII 检测和编辑功能可以检测的完整信息列表如下:

https://airtable.com/embed/shrG1wmEi19uQ4o3w?backgroundColor=blue&viewControls=on

启用 PII 检测和修订后,我们通过我们的 API 运行上面的缩放记录,为每个记录生成一个修订的副本。下面是其中一段录音的摘录,下面是完整转录的链接。

Good afternoon. My name is [PERSON_NAME] and I will be your
[OCCUPATION] [OCCUPATION] today. Hey, everyone, welcome to the
[ORGANIZATION] one on one webinar. Before I get started, let's
quickly cover  a few housekeeping items. If you have any questions,
we will have a Q and A session at the end, so please submit questions
that pops up during the webinar. I have several colleagues on the
line to help you answer questions during the Webinar. We have
[OCCUPATION], [OCCUPATION], [OCCUPATION] [OCCUPATION], [OCCUPATION]
[OCCUPATION], and so on. Everyone on here is on mute except me, so
please use the Q amp a feature. This session is up to an hour long, so
if you need to drop off, this webinar is recorded and will be shared
with you later on. So it's up to you if you'd like to  follow along with
my examples or sit back and enjoy the show. So my name is [PERSON_NAME]
[PERSON_NAME] and I'm the [OCCUPATION]  OCCUPATION] at [ORGANIZATION].
You're probably wondering  what's our [OCCUPATION] [OCCUPATION] I work
in our [ORGANIZATION] [ORGANIZATION] [ORGANIZATION] and I serve as an
expert on the key use cases and features of the [ORGANIZATION] platform.

https://airtable.com/embed/shrC1ZYS5IeN3T9nw?backgroundColor=blue&viewControls=on

内容安全检测

此功能可以标记包含敏感内容(如仇恨言论、公司财务、赌丨博、武器或暴力)的 Zoom 转录部分。AssemblyAI API 将标记的内容的完整列表可以在 API 文档中找到。

试图自动化这一过程的遗留软件采用“黑名单”方法——寻找特定的词(例如,“枪”),以便标记敏感内容。这种方法非常容易出错,而且很脆弱。以“玉米煎饼被炸了”为例。

由于 AssemblyAI 的内容安全检测模型是使用最先进的深度学习模型构建的,因此我们的模型在决定何时标记一段内容时会查看一个单词/句子的整个上下文——我们不依赖于容易出错的反向列表方法。

下面我们回顾一下 AssemblyAI 的内容安全检测功能对上述数据集的检测结果:

https://airtable.com/embed/shrBvaBub2sHKQQmd?backgroundColor=blue&viewControls=on

基准测试您的数据

提供商之间的准确性基准测试需要时间和金钱来运行您的内容。我们为任何寻求转录解决方案的团队提供免费的基准报告。要开始使用免费的基准测试报告,您可以点击这里

5 分钟深度学习

原文:https://www.assemblyai.com/blog/deep-learning-in-5-minutes-what-is-deep-learning/

深度学习为我们日常依赖的大多数技术提供了动力。从在几秒钟内翻译网页的机器翻译,到显示观看推荐,甚至是让我们一天多次登录手机的人脸识别。

https://www.youtube.com/embed/dccdadl90vs?feature=oembed

在这个视频中,我们进一步了解深度学习。我们了解什么是深度学习,它在人工智能世界中的地位,为什么它已经非常成功地成为我们生活的一部分,以及它与更传统的机器学习算法相比如何。

深度学习论文摘要-自动语音识别

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-automatic-speech-recognition/

本周的深度学习论文摘要是自动语音识别的 不易察觉、稳健且有针对性的对抗示例自动语音识别的自我监督语音模型的高效适配器转移

用于自动语音识别的不易察觉的、健壮的和有针对性的对抗示例

这篇论文有什么令人兴奋的地方

这篇论文提出了一种新的方法来生成样本,这些样本不仅是隐形的,而且对空中播放也是鲁棒的。

Source

主要发现

生成的这种对立样本具有以下性质:

  • 察觉不到:被攻击的音频听起来与原始音频极其相似,以至于人类无法区分两者。
  • 健壮:被攻击的音频即使在空中播放也应该有效。例如,由扬声器播放的音频样本,由麦克风记录,然后提供给模型。

我们的外卖

虽然不易察觉的攻击是隐形的,成功率很高,但不易察觉+鲁棒的攻击需要改进,目前只有 50%的成功率。重采样音频似乎削弱了攻击。

用于自动语音识别的自监督语音模型的有效适配器转移

这篇论文有什么令人兴奋的地方

为每个下游任务微调预训练的 wav2vec 模型会导致每个任务有一个大模型,部署起来很昂贵。然而,本文表明应用适配器减少了在微调过程中需要适应的参数数量。而不是 90%的参数,只需要微调 10%的参数。这使我们能够为每个下游任务重用 90%的参数。

主要发现

作者在 wav2vec 模型的每个转换器编码器模块中插入适配器层。在每个适配器层内部,他们先进行线性向下投影,然后进行线性向上投影。他们还增加了跳跃连接。

Source

作者用英语语音数据训练了整个模型。然后,他们用法语语音数据进行了两项实验:

  • 他们微调了整个网络(95.6%的参数)
  • 他们只微调了适配器层(9.2%的参数)

结果以法语测试数据的单词错误率(WER)来衡量。这两个实验表现出相似的性能:

  • 40.2% WER 微调孔网
  • 39.4% WER 仅微调适配器层

我们的外卖

适配器表明,不需要为下游任务微调整个模型。相反,只需对小心插入模型的特定适配器层进行微调就足够了。

使用适配器将允许我们为每个下游任务重用 90%的参数,而不是为每个任务部署一个微调的模型。

深度学习论文摘要-扩散和变压器模型

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-diffusion-and-transformer-models/

本周深度学习论文点评是 Diffusion-LM 改进可控文本生成使用可训练表示池 稀疏化变压器模型。

扩散 LM 改进了可控的文本生成

这篇论文有什么令人兴奋的地方

本文连续 扩散模型首次应用于可控 NLG(自然语言生成)的任务,允许使用基于梯度的方法。

在马尔可夫链的末尾增加了创新的“舍入”和“嵌入”步骤,将一个离散的问题转换为一个连续的问题。

主要发现

在各种可控的文本生成任务上,如语义内容、词性、语法树、语法跨度、长度和左/右上下文,作者能够胜过现有的方法,如 PPLM忽悠

扩散 LM 无法像 LM(语言模型)一样对每个任务进行微调,但是在这里,LM 被冻结,允许相同的 LM 用于各种任务。

Source

我们的外卖

进一步的研究是必要的,但令人兴奋的是看到扩散建模技术被用于 NLG 领域,并在图像/音频合成领域取得成功。

然而,必须解决的一个主要瓶颈是解码速度慢。Diffusion-LM 比自回归文本生成慢大约 7 倍,只有最少数量的扩散步骤(200,而大约 2000 更典型)。

使用可训练表示池来稀疏化变压器模型

这篇论文有什么令人兴奋的地方

本文提出了一种稀疏化变压器架构的新方法,实现了次线性时间和存储复杂度。这种方法被称为表示池,它根据下游任务的优势学习选择最佳表示。

本文还提出了一种逐次减半的 Top-k 算子,它在逼近质量和速度方面都优于以前的方法。

作者提供了它的差分性质的详细分析,并证明它是可以以端到端的方式训练的。

主要发现

使用可训练表示池的实验在训练期间实现了 1.8 倍的加速,在推理期间实现了 4.5 倍的加速。

长文档摘要任务的结果表明,即使是简单的基线也能与当前的 SOTA 相媲美。

Source

我们的外卖

表示池与带有可训练参数的评分函数相结合,可以帮助减少 transformer 体系结构的时间和存储复杂性,同时保持长文档摘要的高质量性能。

然而,这种技术对于输入序列长度较短的任务(如句子级翻译)可能不太有用。

深度学习论文摘要-语言模型

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-language-models/

本周的深度学习论文回顾是 一劳永逸:稀疏预训练语言模型

这篇论文有什么令人兴奋的地方?

模型剪枝是压缩深度学习模型的关键方法之一,剪枝技术因模型架构而异。本文介绍了一种训练稀疏预训练语言模型的架构不可知方法。

这种方法使我们能够在预训练阶段只修剪一次,而不用担心微调期间的修剪。研究人员还提出了一种微调机制,该机制利用蒸馏来实现最佳的压缩比和准确率。

主要发现

微调修剪(稀疏)模型通常会导致较差的结果或较低的稀疏率。这就是为什么像渐进幅度修剪(GMP)这样的现代修剪方法在微调阶段应用修剪。

但是这种方法的问题是,每次我们进行微调时,我们都必须考虑任务和模型架构来选择修剪技术。

利用所提出的预训练和微调机制,我们可以通过仅修剪一次来节省时间。下面是整个管道的样子:

Source

这种技术可以为基于 BERT、BERT-Large 和 distal-BERT 带来最佳的压缩比和精确度。使用 85%和 90%的权重修剪获得了最好的分数。

Source

他们还尝试了 85%修剪的量化感知训练(QAT ),这导致了比 90%修剪模型更精确和更小的模型。

我们的外卖

这些预训练的修剪模型可用于获得微调的修剪模型,而没有特定任务修剪的负担。

这种方法节省了我们修剪模型的时间和精力,类似于许多预先训练的深度学习模型,我们不必从头开始训练。在这种情况下,我们只是使用修剪模型进行微调。

深度学习论文摘要-冗余减少和稀疏 moe

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-redundancy-reduction-and-sparse-moes/

本周深度学习论文点评是 巴洛双胞胎:通过冗余约简的自监督学习稀疏 MoEs 遇见高效系综

巴洛双胞胎:通过冗余减少的自我监督学习

这篇论文有什么令人兴奋的地方

大多数自监督学习算法是基于对比学习的,这需要负实例来防止崩溃。

Source

这篇论文提出了一个优雅、新颖的自我监督学习(SSL)解决方案,不需要负面实例。

这是第一个基于去相关特性而不是实例的 SSL 解决方案。

主要发现

Barlow Twins 通过测量两个相同网络的输出之间的互相关矩阵,并使其尽可能接近单位矩阵,自然地避免了崩溃,这两个网络由样本的失真版本馈送。

在巴洛孪晶丢失之前对特征进行批量归一化对于防止崩溃也是至关重要的(编码器为所有图像输出相同的特征),因为批量归一化会将所有特征减少到 0,这将导致特征之间的高相关性(和高丢失)。

Source

我们的外卖

Barlow Twins 是首批基于特征去相关的 SSL 技术之一,其性能优于 SimCLR 等最先进的对比方法。

稀疏 moe 满足高效集成

这篇论文有什么令人兴奋的地方

混合专家(MoE)是一种神经网络,它在令牌级别使用动态路由来执行大型模型的子图。这使它们能够在保持相同计算要求的同时,拥有比高密度磁盘更多的参数。在每秒浮点运算次数(FLOPS)相同的情况下,与密集模型相比,他们表现出了令人印象深刻的质量提升。有些人把 MoE 称为动态合奏。使用 MoE 和传统模型集合的预测性能之间有什么区别?本文就此话题展开调查。

下图同时使用了稀疏 MoE 和模型集合。h1、h2 和 h3 是一批中的不同输入,每个模型是一个 MoE,集合中有两个模型。

Source

主要发现

稀疏 MoE 和系综具有互补的特征和优点。它们可以一起使用,以实现更高的精度、更强的鲁棒性和更好的校准。这意味着 MoE 不是传统模型预测集合的替代或替换。

他们发现,即使在 MoE 中使用的专家数量增加,通过向静态集合中添加更多的模型也有额外的价值。这个观察结果是令人惊讶的,因为稀疏 MoE 似乎已经包含了静态集合的影响。

我们的外卖

在 NLP 建模任务中,稀疏的专家混合已经被证明比密集的专家混合提供了附加值。它们通常具有与它们所取代的密集模型相同的计算成本。乍一看,它们似乎是创建模型集合的一种不同且更有效的方式,这可能导致错误的假设,即进一步的模型集合不会增加多少价值。事实证明并非如此,这两种方法可以结合使用,以获得更高质量的预测。

深度学习论文摘要-流式 ASR 和摘要

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-streaming-asr-summarization/

本周的深度学习论文摘要是 通过提取 CTC 和 RNN-T 模型的集合BRIO:给抽象概括带来秩序 来弥合流式和非流式 ASR 系统之间的差距

通过提取 CTC 和 RNN-T 模型的集合来弥合流式和非流式 ASR 系统之间的差距

这篇论文有什么令人兴奋的地方

流式模型通常被限制为随意的,没有未来的上下文,并且还遭受更高的单词错误率。本文弥合了流模式和非流模式之间的差距。

论文中描述的学生模型在先前工作的流模型的基础上有了很大的改进。对于新的语言模型,单词错误率(WER) 在西班牙语上降低了 41%,在葡萄牙语上降低了 27%,在法语上降低了 13%。

Source

主要发现

使用至少一个 CTC 老师导致学生 wer 较低。例如,接受过 CTC 模型培训的 RNN-T 学生比接受过 RNN-T 教师培训的学生表现更好。将 CTC 和 RNN-T 结合使用效果最佳。

RNN-T 学生模型胜过他们的 CTC 老师。在 CTC 老师的指导下,RNN-T 学生模型较少出现删除错误。

我们的外卖

这种将非流模型提取为流模型的方式极大地将研究重点缩小到异步模型,并加快了研究速度。

这种培训分流学生模型的方法不仅大大提高了结果,而且缩小了非分流教师和分流学生之间的差距。

BRIO:给抽象概括带来秩序

这篇论文有什么令人兴奋的地方

概括模型通常使用最大似然估计(MLE)来训练。MLE 假设理想模型会将所有概率质量分配给参考摘要。

当模型必须比较几个偏离参考值的候选值时,这可能会导致推理时的性能降低。

本文提出了一种训练方法,在该方法中,不同的候选人根据他们的素质被分配概率质量。

这种方法赋予模型双重角色:

  • 作为生成模型(使用 MLE 训练),它以自回归方式生成输出摘要。
  • 作为一个评估模型(使用对比损失进行训练),它可以用于对候选人的质量进行评分。

Source

主要发现

他们的方法在几个众所周知和广泛使用的数据集上建立了新的最先进的结果。

他们的模型估计候选人的概率与候选人的质量水平高度相关。

我们的外卖

BRIO 提出了一种新颖的训练方法,可以用来提高文本摘要模型的性能。

他们的方法也有可能应用于其他条件文本生成任务,如机器翻译。

深度学习论文摘要-迁移学习

原文:https://www.assemblyai.com/blog/deep-learning-paper-recap-transfer-learning/

本周的深度学习论文复习是 将学习从说话人验证转移到多说话人文本语音合成

从说话人确认到多说话人文本语音合成的迁移学习

这篇论文有什么令人兴奋的地方

本文展示了来自训练集中看不见的说话者的 5 秒音频足以生成高质量的语音克隆。以前最先进的(SOTA)模型需要几十分钟

研究人员将扬声器编码器和 TTS(文本到语音)网络解耦,这降低了每一步的数据质量要求,并实现了零触发学习。旧的 TTS 管道通常是端到端的,需要高质量的标记说话人音频数据,并且不能很好地概括训练中没有看到的说话人声音。

主要发现

通过在说话人验证任务中以自我监督的方式在大的未标记音频数据的训练数据集上进行训练,说话人编码器网络学习生成固定维度的说话人嵌入向量,该向量表示从音频内容中抽象出的说话人语音的特征。

然后,该说话者嵌入被馈送到与用户输入文本嵌入连接的标准 TTS 管道中,在最终的声码器网络将其转换为波形之前,在此将其转换为 log-mel 频谱图。

以前的端到端流水线需要带有说话者和转录标签的标记音频数据来训练,但是通过分离说话者编码器网络和 TTS 流水线,说话者编码器网络只需要未标记的音频数据来训练,TTS 流水线只需要转录的音频数据(没有说话者信息)来训练,这两者都比前者丰富得多。

Source

[https://www.youtube.com/embed/0sR1rU3gLzQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" title="Google's AI Clones Your Voice After Listening for 5 Seconds! 🤐](https://www.youtube.com/embed/0sR1rU3gLzQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" title="Google)

我们的外卖

通过调整这个管道,例如添加虚拟的说话者嵌入、随机文本生成和音频增强,这种方法可以用来生成无限的高质量标记数据吗?

深度学习论文摘要-形态匹配和屏蔽自动编码器

原文:https://www.assemblyai.com/blog/deep-learning-paper-recaps-modality-matching-and-masked-autoencoders/

本周深度学习论文重述的是 MAESTRO:通过模态匹配进行匹配的语音文本表示监听 的屏蔽自动编码器。

MAESTRO:通过模态匹配匹配语音文本表示

这篇论文有什么令人兴奋的地方?

本文探索了一种从语音和文本模态中高效学习统一表示的新方法。研究人员提出的方法在 ASR 任务中优于 SOTA。

Monolingual ASR - Source

Multilingual ASR - Source

主要发现

在该论文中,模态匹配算法学习统一语音和文本表示。它允许我们通过利用纯文本输入来合并词汇信息,并且可以帮助改善单语和多语言设置中的 ASR 性能。

只需要少量的监督数据就可以有效地统一表示。

我们的外卖

学习联合语音和文本表示允许两种模态之间的知识共享。由于纯文本数据更容易收集,它可以帮助提高 ASR 模型的准确性,特别是在低语音资源语言上。

监听的屏蔽自动编码器

这篇论文有什么令人兴奋的地方?

在本文中,作者提出了一种新颖的基于图像的音频掩蔽自动编码器扩展。

该模型的工作原理是将 mel 声谱图分割成小块,屏蔽约 80%的小块,然后将未屏蔽的小块传递到编码器,并将所有重新排序的小块传递到解码器。目标是重建整个声谱图,尽管 MSE 损失仅在被掩蔽的面片和它们的基础真值对上计算。

这篇论文证明了自动编码器可以和新颖的对比自我监督学习方法一样好。

Source

主要发现

屏蔽自动编码器预训练不仅可以扩展到静态图像,还可以扩展到时间信息,例如音频和视频。极高的修补比率也可能导致质量和偏差设置方面更健壮的模型。

作者还表明,对于言语领域,局部注意力比整体注意力更有效。这可以用光谱图中相邻特征高度相关的事实来解释。

我们的外卖

屏蔽的自动编码器又回来了——它们可以产生与新的对比模型相竞争的结果。高修补率消除了特征过度拟合和偏差,从而使模型对一般特征非常健壮。

RNN-T 个性化的深浅融合

原文:https://www.assemblyai.com/blog/deep-shallow-fusion-for-rnn-t-personalization/

本周的深度学习研究论文是“面向 RNN-T 个性化的深浅融合

这篇论文有什么令人兴奋的地方

端到端深度学习 ASR 模型可以产生高度准确的转录,但它们很难个性化。它们的端到端特性缺乏可组合性,比如声学、语言和发音模型之间的可组合性。缺乏可组合性导致个性化的挑战,使得准确预测定制词汇和罕见专有名词变得更加困难。本文介绍了一些方法,这些方法有助于提高端到端深度学习模型中专有名词和罕见词的准确性。

主要发现

这篇文章讨论了四种不同的技巧来帮助提高专有名词。但是我们认为两种方法更有意思,因为它们更简单,但仍然可以提高精确度。通过这些训练技巧,你可以提高模型预测专有名词和生僻字的能力。

  • 子字正则化:在训练期间,你可以从 n 个最佳输出的列表中进行采样,并将其用作输入,而不是直接将来自先前时间步长的最可能预测输入到当前时间步长中。这使得模型不会过度拟合高频词,而应该预测较罕见的词
  • 你可以使用 G2G 模型来扩充你的数据集!G2G 模型可以将一个单词转换成发音相似的可选拼写,例如“Kaity”→“Katie”使用 G2G 生成额外的发音进行解码,极大地改善了罕见姓名的识别。

我们的外卖

端到端 ASR 模型可能会过度拟合高频词,使模型很难预测稀有词。通过用 G2G 扩充数据并在训练机制中添加一点随机性,可以减少高频词的过度拟合,并训练模型以增加预测低频词(如专有名词)的概率。

DeepMind 的阿尔法张量解释了

原文:https://www.assemblyai.com/blog/deepminds-alphatensor-explained/

为文本到图像模型等令人难以置信的技术提供动力的深度学习的基础是矩阵乘法。不管采用的具体架构如何,几乎每个神经网络都依赖于有效的矩阵乘法来学习和推断。找到高效快速的矩阵乘法算法因此至关重要,因为它们将增强每个神经网络,潜在地允许我们运行当前硬件限制所禁止的模型。

最近,DeepMind 设计了一种方法来自动发现新的更快的矩阵乘法算法。该方法采用了 AlphaTensor ,一种基于人工智能的系统,对深度强化学习进行操作。

在本文中,我们将强调 AlphaTensor 的主要影响,并了解它在幕后是如何工作的。

介绍

更快的矩阵乘法算法的发现具有深远的影响。即使在神经网络的特定环境之外,矩阵乘法也是许多现代计算的基础,在计算机图形学、数字通信和科学计算中起着重要作用。这些领域中的每一个都可以从新的、更有效的算法中显著受益。

AlphaTensor 是一个能够自主搜索可证明正确的矩阵乘法算法的系统。在其成就中,以下是一些亮点:

  • AlphaTensor 自动重新发现了当前最先进的矩阵乘法算法,并且在几种情况下改进了 best know 复杂度的
  • AlphaTensor 可以被训练来搜索为特定硬件定制的高效算法,而无需任何先验知识
  • AlphaTensor 利用 AlphaZero 用于下棋的工具,探索潜在算法的(巨大)搜索空间。从数学的角度来看,AlphaTensor 发现的大量有效的新算法意味着这个空间比以前已知的空间更加丰富。这代表了一种搜索这种性质的大型组合空间的新方法。

虽然这些结果令人印象深刻,但 AlphaTensor 发现的新算法的改进并不代表矩阵乘法复杂性的一般问题的重大突破,我们将在后面讨论。AlphaTensor 最令人兴奋的部分不在于它的结果,而在于它引入的新思想,以及进一步扩展的潜力。

算法发现的潜在蓝图

α张量的潜在应用不一定局限于矩阵乘法的细节。该系统的基础设计是一个通用架构,由于其灵活性,它可以适用于搜索其他类型的算法,这些算法可以优化各种不同的指标。

这种度量的例子有:速度、内存、数值稳定性、能量使用,甚至给定算法对特定目标硬件的适应性。特别是最后一个,在本文中被实现来优化 Nvidia V100 GPU 和 Google 的 TPU v2 的矩阵乘法的一个例子。结果如下图所示:

Hardware-tailored speedups: GPU vs TPU – a case study implemented in the paper (source)

AlphaTensor 的出发点是用张量的语言重新表述矩阵乘法(我们将在下面的部分解释这是如何工作的以及张量是什么)。因为张量可以表示任何双线性运算,例如结构化矩阵乘法、多项式乘法,或者在特定计算领域使用的更多自定义双线性运算,针对其他数学问题的 AlphaTensor 的进一步扩展可以为复杂性理论和其他数学领域的研究开启新的可能性。

AlphaTensor 如何搜索新算法?

现在,让我们把张量想象成一种魔方的抽象模拟,如下图所示:

AlphaTensor 能够自学如何玩一款名为 TensorGame 的单人游戏,玩家以某种方式操纵给定的输入张量,从而产生一组代表新乘法算法的指令。

TensorGame 是通过利用和改进自学棋盘游戏(如国际象棋或围棋)的技术来玩的,DeepMind 的 AlphaZero 模型解决了这个问题。请注意,张量游戏的自由度数量,即玩家每步可能采取的行动数量,比国际象棋或围棋的高出几个数量级,这使得这个问题变得极其困难。在下面的部分有更多关于 TensorGame 的细节。

下面将给出 AlphaTensor 如何操作的高级概述,随后是其各个组件的一些扩展细节。

在此之前,我们先来给以下几个自然问题一个答案:

  • 矩阵乘法到底能不能提高?
  • 什么是张量?张量分解在何种意义上等同于矩阵乘法算法?

为什么矩阵乘法不是最优的

两个矩阵相乘的标准方法不是一个非常有效的算法。原因很简单:对于计算机来说,(两个数的)乘法是一种比加法计算量更大(更慢)的运算。这是因为二进制形式的两个数的相加是并行工作的——在位上只有一个周期——而乘法意味着一系列加法和位移位操作,因此需要几个周期。

因此,减少任何计算任务所需的乘法总数(即使以增加加法/减法的数量为代价),也能有效地加速整个过程,并产生更快的实现。

一个简单的例子:计算两个数字(a)和(b)的平方差的两种不同算法如下图所示。两种算法返回相同的结果,但第二种算法更有效,需要的乘法次数是第一种算法的一半。

两个矩阵相乘需要多少次乘法?这既取决于矩阵的大小,也取决于所选择的实现方式。对于两个大小为(2 乘 2)的矩阵,标准算法需要 8 次乘法,如下图所示。

更一般地,为了用标准算法将两个(兼容的)矩阵(A)和(B)相乘,必须将(A)的每一行与(B)的每一列相乘,并且每个这样的操作需要(n)次乘法,其中(n)是(A)的行向量的长度。因此,我们总共需要:[ ( \text{num。的行} A)\次(\text{num。的列} B)\次(\text{num。在大小为(n\乘以 n )的方阵的情况下,这成为(n^3),因此有人说标准算法在输入矩阵大小上具有立方复杂度,写为( O(n^{3}) )。

有没有可能想出一种更快的矩阵乘法算法?

答案是肯定的,这要追溯到 1969 年德国数学家 Volker Strassen 的一项重要发现。他是第一个意识到标准算法不是最优的人,并且发现了一种在(2 乘以 2)的情况下只需要(7)次乘法的算法。

Strassen's algorithm requires only 7 multiplications. Note that it can be applied recursively to matrices of higher size – for example, for two 4x4 matrices it reduces the number of multiplications from 64 (standard algorithm) to only 49.

虽然 Strassen 的发现极其重要,但它没有提供任何搜索新矩阵乘法算法的系统方法。测试所有可能的组合在实践中是不可行的,即使对于在最快的可用机器上运行的强力计算机程序也是如此,因为对于小尺寸矩阵来说,这种组合的数量是完全不可及的。例如,在(4 乘 4)的情况下,可接受的算法的数量已经比(3 乘 3)的情况增加了(10^{10}\倍。

那么,我们如何系统地寻找新的乘法算法呢?事实证明,一个非常聪明的做法是在不同的背景下重新表述这个问题:张量分解。

作为高维矩阵的张量

张量是矩阵概念的推广。如果我们认为矩阵(A)是由一系列数字组成的

由两个索引(i)和(j)(每个索引在给定的范围内运行)索引的 3D 张量( \mathcal{T} )由一列数字给出

由三个索引(i,j,k)索引。添加更多的指数,这很快推广到 4D,5D,甚至更高维张量的概念。这样,我们看到矩阵实际上只是张量的一个特殊例子:矩阵是 2D 张量

就像矩阵(或 2D 张量)可以被形象化为一个数字的“方桌”(或更一般的矩形桌子),很自然地将一个三维张量形象化为一个三维数字盒子。此外,如果张量的唯一可能条目是 0 和 1,我们可以只使用彩色框来表示值 1,其余的为零,如下图所示:

An illustration of a 3D-tensor taking only 0 and 1 as possible entries. Coloured boxes (orange) correspond to a value of 1 in the tensor, and the rest (white) are 0.

矩阵乘法算法是张量分解

出发点是下面的观察:一旦矩阵大小固定,就有一个唯一的 3D 张量(\mathcal{T})(只包含 0 和 1),它表示给定大小的任何一对矩阵(A)和(B)的乘积(AB = C)。更真实的情况是:对(\mathcal{T})(见下面的例子)的任何分解都会自动产生一组新的指令,说明如何进行这种乘法运算,即一种特定的乘法算法。结论是:寻找新的矩阵乘法算法等同于寻找相应张量的分解

为了解释这是如何工作的,让我们举例说明(2\乘以 2 )的情况:

该图显示了在(2\乘以 2 )情况下表示矩阵乘法的张量(\mathcal{T})。(\mathcal{T})(前面突出显示)的第一部分包含用(A)和(B)的系数表示矩阵(C)的第一个系数(c_1)的方法。类似地,其他切片对(C)的剩余系数做同样的事情。

一旦(A)和(B)的大小固定,张量(\mathcal{T})的构造就是一个简单的操作。现在的关键点是(\mathcal{T})可以根据三个向量的外积的和进行几种不同的分解,即以下形式:

A tensor decomposition of the tensor T. The number of summands R is called the rank of the decomposition and equals the total number of multiplications in the matrix multiplication algorithm corresponding to this decomposition.

每个单独的因子(\ mathbf { u } \ otimes \ mathbf { v } \ otimes \ mathbf { w } )相当于对应于这种分解的算法中的一个乘法步骤。下图说明了这种机制在 Strassen 算法中的工作原理:

The figure shows how to reconstruct a matrix multiplication algorithm given a tensor decomposition. The vectors u's and v's dictate the multiplication steps. The vectors w’s are grouped in a matrix whose rows yield the instructions for how to combine the different multiplications m’s in order to get the output coefficients c’s of the matrix C.

总之,为了找到一个新的算法(正好有(R)个乘法),找到张量(\mathcal{T})的分解作为上面的正好(R)个乘积(\ mathbf { u } \ otimes \ mathbf { v } \ otitimes \ mathbf { w } )的和就足够了。问题是所有这些分解的数量是巨大的——事实上,就像矩阵乘法算法的数量一样大。为了探索这个组合空间,需要一些复杂的策略。AlphaTensor 的方法是玩一个名为 TensorGame 的三维棋盘游戏。

张量游戏

张量分解可以被重新表述为强化学习问题,将环境建模为单人 3D 棋盘游戏 TensorGame。

游戏目标:给定任意张量(\mathcal{T}),我们要找到(\mathcal{T})的一个分解为(R)与(R)的外积之和(如上一节所述),它对应于算法中的乘法次数,尽可能小。

游戏的玩法如下:

  • 第零步 (t = 0):初始状态设置为我们要分解的目标张量:

  • 步骤 t (t = 1,2,3,...):玩家选择三个向量:

并且新状态被递归地更新为:

  • 游戏结束:R 步后,玩家到达零张量:

在这种情况下,这会产生初始张量的张量分解(\mathcal{T})。在每一步(t)都有一个负奖励,鼓励更少的步骤达到零。只允许预设的最大移动次数:如果玩家在此限制后以非零向量终止游戏,将会获得额外的负奖励。

AlphaTensor -模型概述

AlphaTensor 是围绕一个深度强化学习范式构建的:一个代理能够通过玩张量游戏来搜索张量分解的空间。使用策略和奖励函数来采取行动,并且将所玩的游戏输入到神经网络中,该神经网络更新和改进策略和奖励。更具体地说,整个过程由以下步骤组成:

  1. TensorGame 开始:输入乘法(AB = C)对应的 3D 张量( \mathcal{T})。
  2. 数据扩充步骤:(\mathcal{T})通过随机的基变化样本进行变换(这些是根据不同的参考系来表示相同张量的等效方式)。AlphaTensor 被迫在所有基地并行进行游戏。关键的一点是,在中找到任意个基的分解就足够了。这也自动地给游戏注入了多样性。
  3. 一连串的蒙特卡罗树搜索 (MCTS)步骤结合强化学习来决定下一个动作,直到剧集结束。MCTS 在政策功能和价值功能的指引下运作。策略函数用于决定采取哪些步骤来沿着树向下。价值函数用于估计所选路径的回报。
  4. 输出是一个玩过的游戏,相当于(\mathcal{T})的分解,并被添加到一个玩过的游戏列表中。
  5. 深度强化学习开始:从玩过的游戏列表或准备好的合成数据列表中随机抽取一个状态,并将其输入神经网络,训练该网络来学习和更新 MCTS 中使用的策略和价值函数。
  6. 该模型用新的策略和值函数进行了更新。新的迭代可以开始。

这些点代表了 AlphaTensor 模型中起作用的主要组件的示意图。让我们仔细看看其中的一些组件。

数据扩充策略

AlphaTensor 使用一些有趣的数据扩充策略,利用问题的各种对称性以及应用线性代数的技巧。

在每个游戏情节的开始,输入张量通过应用基数变化的样本而增加,如上面概述中提到的。具体地说,基的变化由应用于原始张量的三个可逆矩阵的(采样)选择来确定。如果玩家找到任何一个变换张量的张量分解,那么直接把这样的分解转换成原始(规范)基的分解。分解的 (R)在这个过程中被保留。在实际操作中,这些随机生成的碱基数量在 10 万左右,AlphaTensor 在所有的碱基中并行进行游戏。

另一个策略涉及一个准备好的合成数据列表。虽然张量分解是一个 NP-hard 问题,但是从它的因子构造张量的逆任务是基本的。因此,通过对(t=1,\dots,r )取向量三元组((\mathbf{u}{(t)},\mathbf{v},\mathbf{w}^{(t)})的随机样本,然后将它们的乘积加在一起以构造随机张量(\mathcal{T}),可以容易地生成张量分解的合成数据集。这产生了可用于监督学习的张量分解对的大列表。事实上,AlphaTensor 的神经网络采用了一种混合训练策略:使用标准强化学习损失在目标张量上同时训练,使用监督损失在随机张量上训练。

从每个玩的游戏中,通过用最后一个动作交换随机动作,利用张量分解中加法的可交换性,提取额外的张量分解对。这在每次迭代中为监督学习产生了额外的一对。

基于样本的蒙特卡罗树搜索(MCTS)在行动

玩 TensorGame 时,每走一步,你都可以指定一个可能的搜索树。具体来说,搜索由一系列 TensorGame 的模拟轨迹组成,这些轨迹聚集在一棵树中。因此,搜索树由表示状态的节点( (\mathcal{S}_t) )和表示动作的边组成。一个动作对应于三个向量的选择(\mathbf{u}、\mathbf{v}、\mathbf{w}),这导致树的每一层都有大量的可能性。

MCTS 没有遍历整个树并访问每个节点,而是对树的一个子集进行采样。基于策略函数对动作进行采样,并选择具有最高潜在回报的动作。价值函数用于评估给定的动作序列,即沿树向下的给定轨迹。

一种基于变压器的神经网络结构

AlphaTensor 使用深度神经网络来指导蒙特卡罗树搜索。让我们突出它的架构的主要特征。

神经网络由一个普通的躯干(充当编码器),后面是一个双头。更具体地说:

  • 输入是当前状态,以及先前状态的历史。
  • 躯干是基于变形金刚的修改,它利用了一种特殊形式的注意力机制,称为轴向注意力。它的目的是创建一个对政策和价值头脑都有用的输入的表示嵌入
  • 政策负责人的目的是预测潜在行动的分布。它使用 transformers 架构来模拟一个自回归策略。这里的自回归意味着模型通过测量先前时间步的观察值之间的相关性来预测输出(类似于语言模型中的解码器架构)。
  • 价值头由一个多层感知器组成,并被训练来预测当前状态的回报分布(累积回报)。

α张量结果综述

对于小尺寸的矩阵,先前已知的矩阵乘法算法的复杂度与由 AlphaTensor 发现的算法的复杂度之间的比较如下表所示:

Table of AlphaTensor's results from the original paper (source)

第一列显示了所考虑的矩阵的大小:例如(4,4,5)指的是一个 4 x 4 矩阵乘以一个 4 x 5 矩阵的问题。这里的单词指的是算法中使用的标量乘法的总数。这是衡量复杂性的方法。最后两列分别显示了 AlphaTensor 在模运算(具体来说:模二)和标准运算中得到的秩。

对于所考虑的正方形矩阵的情况(表中的第一行), AlphaTensor 仅针对模运算改进了最佳已知秩。在适用于实矩阵的非正方形情况下,也有 3 个改进。在第二个表中(参见原始论文中的扩展数据表 1 ),作者能够组合一些低规模算法,并在几个情况下获得优于最佳已知秩的改进,大小达到(11,12,12)。

值得注意的是,AlphaTensor 在每种情况下都会自动重新发现当前最先进的算法。另一方面,虽然这些结果令人印象深刻,但这些改进并不代表矩阵乘法问题的重大突破。

论文中报道的标准算法的加速大多在 1%到 6%之间,而且似乎只是在合成数据上测量的。据推测,这些算法已经在随机生成的矩阵上进行了测试,尽管作者没有具体说明。应在机器学习研究论文中建立明确描述测试集结构的最佳实践,以避免与数据泄露相关的任何风险,并确保结果的可重复性。

还值得注意的是,现实应用中出现的矩阵往往带有非常特殊的结构,新算法对这类数据的改进还有待论证。例如,在稀疏矩阵(在应用中无处不在)上测试新算法,或者更一般地分析实际案例研究,如计算机图形学、科学模拟或神经网络训练,将是非常有趣的。不幸的是,作者没有报告在这些环境下进行的任何测试。

虽然算法可以递归地应用于更大尺寸的矩阵(例如,4 x 4 算法可以应用于任何尺寸等于 4 的幂的方阵),并且优势变得明显,但这通常意味着天文数字般大的矩阵,在现实应用中很少见到。

另一方面,值得注意的是,对于矩阵乘法的实际应用来说,速度通常不是最重要的特征。数值稳定性在许多计算任务中被认为是最重要的。虽然α张量可以被设置为针对这样的度量,但是在该论文中没有出现在这个方向上的实验,并且没有报道新发现的算法的数值误差界限。

最后的话

矩阵乘法是线性代数中的基本运算。它构成了许多其他矩阵运算的基础,并广泛应用于应用数学、计算机科学以及工程和科学计算的若干领域。对矩阵乘法复杂性的理论理解和快速实用算法的开发都是令人感兴趣的。

AlphaTensor 是一个基于深度强化学习的 AI 系统,可以独立地为复杂的数学任务找到新颖的可证明正确的算法。经过专门针对矩阵乘法问题的训练,它已经显示出非常有趣的结果,并且它在自动算法设计中进一步扩展和应用到其他任务的潜力看来是有希望的。

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow

傻瓜的深度演讲-教程和概述

原文:https://www.assemblyai.com/blog/deepspeech-for-dummies-a-tutorial-and-overview-part-1/

什么是 DeepSpeech? DeepSpeech 是百度的一个研究团队首次发布的神经网络架构。2017 年,Mozilla 创建了这篇论文的开源实现——名为“ Mozilla DeepSpeech ”。

来自百度的原始 DeepSpeech 论文普及了“端到端”语音识别模型的概念。“端到端”是指模型接收音频,直接输出字符或单词。这与传统的语音识别模型相比较,如那些用流行的开源库(如 Kaldi 或 CMU 斯芬克斯)构建的模型,这些模型预测音素,然后在稍后的下游过程中将这些音素转换为单词。

像 DeepSpeech 这样的“端到端”模型的目标是将语音识别管道简化为一个单一的模型。此外,百度研究论文介绍的理论是,在大量数据上训练大型深度学习模型,会比经典的语音识别模型产生更好的性能。

今天,Mozilla DeepSpeech 库提供了预训练的语音识别模型,您可以使用这些模型以及工具来训练您自己的 DeepSpeech 模型。另一个很酷的功能是能够通过 Common Voice 项目为 DeepSpeech 的公共训练数据集做出贡献。

在下面的教程中,我们将带你通过 Mozilla DeepSpeech 库安装和转录音频文件(我们称之为 deep speech forward)。

基本深度演讲示例

DeepSpeech 很容易上手。正如我们在 2021 年的 Python 语音识别概述中所讨论的,您可以使用 Python 的内置包安装程序 pip 下载并开始使用 DeepSpeech。如果你安装了 cURL,你也可以从 DeepSpeech GitHub repo 下载 DeepSpeech 的预训练英语模型文件。请注意,我们在下面下载的文件是。记分员'和'。pbmm '文件。

# Install DeepSpeech
pip3 install deepspeech

# Download pre-trained English model files
curl -LO https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.pbmm
curl -LO https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.scorer

快速提醒一下——当使用 DeepSpeech 时,重要的是要考虑到只有 16 千赫兹(kHz)。从 2021 年 9 月下旬开始支持 wav 文件。

让我们来看一些关于如何用 DeepSpeech 异步转录语音的示例代码。如果您使用的是 Unix 发行版,您需要安装 Sound eXchange (sox)。Sox 可以通过使用 Ubuntu/Debian 的“apt”或 Fedora 的“dnf”来安装,如下所示。

sudo apt install sox

或者

sudo dnf install sox

现在让我们也安装 Python 库,我们将需要它来工作。我们将需要 DeepSpeech 库、用于语音活动检测的 webrtcvad 和用于访问桌面系统上的多媒体(声音)功能的 pyqt5。之前,我们已经安装了 DeepSpeech,我们可以用 pip 安装另外两个库,如下所示:

pip install webrtcvad pyqt5

现在我们有了所有的依赖项,让我们创建一个转录器。当我们完成后,我们将能够转录任何。wav '音频文件,就像下面的例子。

在我们开始构建我们的转录器之前,确保我们之前下载的模型文件保存在。/models '目录的工作目录。我们要做的第一件事是创建一个语音活动检测(VAD)功能,并使用它来提取音频文件中有语音活动的部分。

如何创建 VAD 函数?我们需要一个函数来读取。wav '文件,一种生成音频帧的方法,以及一种创建缓冲区来收集具有语音活动的音频部分的方法。音频帧是我们构建的对象,包含音频的字节数据、总音频中的时间戳以及帧的持续时间。让我们从创建 wav 文件阅读器函数开始。

我们需要做的就是打开给定的文件,断言通道、采样宽度、采样速率是我们需要的,最后获取帧,并将数据作为 PCM 数据连同采样速率和持续时间一起返回。我们将使用“contextlib”来打开、读取和关闭 wav 文件。

我们期望音频文件具有 1 个通道,采样宽度为 2,采样速率为 8000、16000 或 32000。我们用帧数除以采样率来计算持续时间。

import contextlib

def read_wave(path):
   """Reads a .wav file.

   Takes the path, and returns (PCM audio data, sample rate).
   """
   with contextlib.closing(wave.open(path, 'rb')) as wf:
       num_channels = wf.getnchannels()
       assert num_channels == 1
       sample_width = wf.getsampwidth()
       assert sample_width == 2
       sample_rate = wf.getframerate()
       assert sample_rate in (8000, 16000, 32000)
       frames = wf.getnframes()
       pcm_data = wf.readframes(frames)
       duration = frames / sample_rate
       return pcm_data, sample_rate, duration

既然我们有了读取 wav 文件的方法,让我们创建一个帧生成器来生成包含帧大小、时间戳和持续时间的各个帧。我们将生成帧,以确保我们的音频在合理大小的剪辑中得到处理,并分离出有语音和无语音的片段。

想找更多这样的教程?

订阅我们的时事通讯!

[Subscribe Now](Subscribe to our newsletter!)

以下生成器函数以毫秒为单位的帧持续时间、PCM 音频数据和采样速率作为输入。它使用该数据创建从 0 开始的偏移、帧大小和持续时间。虽然我们还没有产生足够的帧来覆盖整个音频文件,但该函数将继续产生帧并添加到我们的时间戳和偏移量中。

class Frame(object):
   """Represents a "frame" of audio data."""
   def __init__(self, bytes, timestamp, duration):
       self.bytes = bytes
       self.timestamp = timestamp
       self.duration = duration

def frame_generator(frame_duration_ms, audio, sample_rate):
   """Generates audio frames from PCM audio data.

   Takes the desired frame duration in milliseconds, the PCM data, and
   the sample rate.

   Yields Frames of the requested duration.
   """
   n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
   offset = 0
   timestamp = 0.0
   duration = (float(n) / sample_rate) / 2.0
   while offset + n < len(audio):
       yield Frame(audio[offset:offset + n], timestamp, duration)
       timestamp += duration
       offset += n

在能够生成音频帧之后,我们将创建一个名为vad_collector的函数来分离有语音和无语音的音频部分。这个函数需要输入采样率、以毫秒为单位的帧持续时间、以毫秒为单位的填充持续时间、webrtcvad.Vad对象和一组音频帧。该函数虽然没有明确地这样调用,但也是一个生成器函数,用于生成一系列 PCM 音频数据。

在这个函数中,我们要做的第一件事是获取填充帧的数量,并创建一个带有出列的环形缓冲区。环形缓冲区最常用于缓冲数据流。

我们将有两种状态,触发的和未触发的,以指示 VAD 收集器功能是否应该将帧添加到有声帧的列表中或者产生以字节为单位的列表。

从有声帧的空列表和未触发状态开始,我们循环通过每个帧。如果我们不处于触发状态,并且该帧被确定为语音,则我们将其添加到缓冲区。如果在将新帧添加到缓冲区之后,超过 90%的缓冲区被确定为语音,则我们进入触发状态,将缓冲的帧附加到有声帧并清空缓冲区。

如果当我们处理一个帧时,函数已经处于触发状态,那么我们将该帧附加到有声帧列表,而不管它是否是语音。然后,我们将它以及它是否是语音的真值附加到缓冲区中。在附加到缓冲区之后,如果缓冲区 90%以上是非语音的,那么我们将我们的状态改变为非触发,产生作为字节的有声帧,并且清除有声帧列表和环形缓冲区。如果在帧结束时,有声帧中仍有帧,则将它们作为字节产生。

def vad_collector(sample_rate, frame_duration_ms,
                 padding_duration_ms, vad, frames):
   """Filters out non-voiced audio frames.

   Given a webrtcvad.Vad and a source of audio frames, yields only
   the voiced audio.

   Uses a padded, sliding window algorithm over the audio frames.
   When more than 90% of the frames in the window are voiced (as
   reported by the VAD), the collector triggers and begins yielding
   audio frames. Then the collector waits until 90% of the frames in
   the window are unvoiced to detrigger.

   The window is padded at the front and back to provide a small
   amount of silence or the beginnings/endings of speech around the
   voiced frames.

   Arguments:

   sample_rate - The audio sample rate, in Hz.
   frame_duration_ms - The frame duration in milliseconds.
   padding_duration_ms - The amount to pad the window, in milliseconds.
   vad - An instance of webrtcvad.Vad.
   frames - a source of audio frames (sequence or generator).

   Returns: A generator that yields PCM audio data.
   """
   num_padding_frames = int(padding_duration_ms / frame_duration_ms)
   # We use a deque for our sliding window/ring buffer.
   ring_buffer = collections.deque(maxlen=num_padding_frames)
   # We have two states: TRIGGERED and NOTTRIGGERED. We start in the
   # NOTTRIGGERED state.
   triggered = False

   voiced_frames = []
   for frame in frames:
       is_speech = vad.is_speech(frame.bytes, sample_rate)

       if not triggered:
           ring_buffer.append((frame, is_speech))
           num_voiced = len([f for f, speech in ring_buffer if speech])
           # If we're NOTTRIGGERED and more than 90% of the frames in
           # the ring buffer are voiced frames, then enter the
           # TRIGGERED state.
           if num_voiced > 0.9 * ring_buffer.maxlen:
               triggered = True
               # We want to yield all the audio we see from now until
               # we are NOTTRIGGERED, but we have to start with the
               # audio that's already in the ring buffer.
               for f, s in ring_buffer:
                   voiced_frames.append(f)
               ring_buffer.clear()
       else:
           # We're in the TRIGGERED state, so collect the audio data
           # and add it to the ring buffer.
           voiced_frames.append(frame)
           ring_buffer.append((frame, is_speech))
           num_unvoiced = len([f for f, speech in ring_buffer if not speech])
           # If more than 90% of the frames in the ring buffer are
           # unvoiced, then enter NOTTRIGGERED and yield whatever
           # audio we've collected.
           if num_unvoiced > 0.9 * ring_buffer.maxlen:
               triggered = False
               yield b''.join([f.bytes for f in voiced_frames])
               ring_buffer.clear()
               voiced_frames = []
   # if triggered:
   #     pass
   # If we have any leftover voiced audio when we run out of input,
   # yield it.
   if voiced_frames:
       yield b''.join([f.bytes for f in voiced_frames]) 

这就是我们需要做的,以确保我们可以读取我们的 wav 文件,并使用它来生成带有语音活动检测的 PCM 音频剪辑。现在,让我们创建一个段生成器,它不仅会返回音频的字节数据段,还会返回转录它所需的元数据。这个函数只需要一个参数。“wav”文件。它的目的是过滤掉所有检测不到语音的音频帧,并返回带有语音的音频文件部分。该函数返回一组分段、音频文件的采样率和音频文件的长度。

'''
Generate VAD segments. Filters out non-voiced audio frames.
@param waveFile: Input wav file to run VAD on.0

@Retval:
Returns tuple of
   segments: a bytearray of multiple smaller audio frames
             (The longer audio split into multiple smaller ones)
   sample_rate: Sample rate of the input audio file
   audio_length: Duration of the input audio file

'''
def vad_segment_generator(wavFile, aggressiveness):
   print("Caught the wav file @: %s" % (wavFile))
   audio, sample_rate, audio_length = read_wave(wavFile)
   assert sample_rate == 16000, "Only 16000Hz input WAV files are supported for now!"
   vad = webrtcvad.Vad(int(aggressiveness))
   frames = frame_generator(30, audio, sample_rate)
   frames = list(frames)
   segments = vad_collector(sample_rate, 30, 300, vad, frames)

   return segments, sample_rate, audio_length

既然我们已经处理了 wav 文件,并创建了将 wav 文件转换为 DeepSpeech 可以处理的有声 PCM 音频数据段所需的所有函数,那么让我们创建一种加载和解析模型的方法。

我们将创建两个名为load_modelresolve_models的函数。直观地说,load_model函数加载一个模型,返回 DeepSpeech 对象、模型加载时间和计分器加载时间。该函数需要一个模型和一个计分器。该函数通过 Python 中的 timer()模块计算加载模型和计分器所需的时间。它还从传入的“模型”参数创建了一个 DeepSpeech“模型”对象。

resolve models 函数采用一个目录名来表示模型所在的目录。然后它抓取第一个以'结尾的文件。pbmm '和第一个以'结尾的文件。“记分员”并将它们作为模型加载。

'''
Load the pre-trained model into the memory
@param models: Output Graph Protocol Buffer file
@param scorer: Scorer file

@Retval
Returns a list [DeepSpeech Object, Model Load Time, Scorer Load Time]
'''
def load_model(models, scorer):
   model_load_start = timer()
   ds = Model(models)
   model_load_end = timer() - model_load_start
   print("Loaded model in %0.3fs." % (model_load_end))

   scorer_load_start = timer()
   ds.enableExternalScorer(scorer)
   scorer_load_end = timer() - scorer_load_start
   print('Loaded external scorer in %0.3fs.' % (scorer_load_end))

   return [ds, model_load_end, scorer_load_end]

'''
Resolve directory path for the models and fetch each of them.
@param dirName: Path to the directory containing pre-trained models

@Retval:
Retunns a tuple containing each of the model files (pb, scorer)
'''
def resolve_models(dirName):
   pb = glob.glob(dirName + "/*.pbmm")[0]
   print("Found Model: %s" % pb)

   scorer = glob.glob(dirName + "/*.scorer")[0]
   print("Found scorer: %s" % scorer)

   return pb, scorer

能够从我们的 wav 文件中分割出语音,并加载我们的模型,这是我们在进行实际的语音到文本转换之前所需要的所有预处理。

现在让我们创建一个函数,它将允许我们转录我们的语音片段。这个函数将有三个参数:DeepSpeech 对象(从 load_models 返回)、音频文件和fs音频文件的采样率。除了跟踪处理时间,它所做的只是在音频上调用 DeepSpeech 对象的stt函数。

'''
Run Inference on input audio file
@param ds: Deepspeech object
@param audio: Input audio for running inference on
@param fs: Sample rate of the input audio file

@Retval:
Returns a list [Inference, Inference Time, Audio Length]

'''
def stt(ds, audio, fs):
   inference_time = 0.0
   audio_length = len(audio) * (1 / fs)

   # Run Deepspeech
   print('Running inference...')
   inference_start = timer()
   output = ds.stt(audio)
   inference_end = timer() - inference_start
   inference_time += inference_end
   print('Inference took %0.3fs for %0.3fs audio file.' % (inference_end, audio_length))

   return [output, inference_time]

好了,我们所有的支持功能都准备好了,让我们做实际的语音到文本的转换。

在下面的“main”函数中,我们将直接提供一个路径,指向我们下载并移动到的模型。/models '目录下的工作目录。

我们可以询问用户过滤非语音的积极程度,或者直接自动设置为 1(从 0-3 的范围内)。我们还需要知道音频文件的位置。

之后,我们所要做的就是使用我们之前创建的函数来加载和解析我们的模型,加载音频文件,并对每个音频片段运行语音到文本的推理。下面的代码的其余部分只是为了调试,向您显示文件名、文件的持续时间、在一个段上运行推理需要多长时间,以及模型和计分器的加载时间。

该功能会将您的成绩单保存到。txt '文件,以及在终端中输出转录。

def main():
   # need audio, aggressive, and model
   # Point to a path containing the pre-trained models & resolve ~ if used
   model = './models/v0.9.3'
   dirName = os.path.expanduser(model)

   audio = input("Where is your audio file located?")
   aggressive = 1 #input("What level of non-voice filtering would you like? (0-3)")

   # Resolve all the paths of model files
   output_graph, scorer = resolve_models(dirName)

   # Load output_graph, alphabet and scorer
   model_retval = load_model(output_graph, scorer)

   title_names = ['Filename', 'Duration(s)', 'Inference Time(s)', 'Model Load Time(s)', 'Scorer Load Time(s)']
   print("\n%-30s %-20s %-20s %-20s %s" % (title_names[0], title_names[1], title_names[2], title_names[3], title_names[4]))

   inference_time = 0.0

   waveFile = audio
   segments, sample_rate, audio_length = vad_segment_generator(waveFile, aggressive)
   f = open(waveFile.rstrip(".wav") + ".txt", 'w')
   print("Saving Transcript @: %s" % waveFile.rstrip(".wav") + ".txt")
   for i, segment in enumerate(segments):
       # Run deepspeech on the chunk that just completed VAD
       print("Processing chunk %002d" % (i,))
       audio = np.frombuffer(segment, dtype=np.int16)
       output = stt(model_retval[0], audio, sample_rate)
       inference_time += output[1]
       print("Transcript: %s" % output[0])

       f.write(output[0] + " ")

   # Summary of the files processed
   f.close()

   # Extract filename from the full file path
   filename, ext = os.path.split(os.path.basename(waveFile))
   print("************************************************************************************************************")
   print("%-30s %-20s %-20s %-20s %s" % (title_names[0], title_names[1], title_names[2], title_names[3], title_names[4]))
   print("%-30s %-20.3f %-20.3f %-20.3f %-0.3f" % (filename + ext, audio_length, inference_time, model_retval[1], model_retval[2]))
   print("************************************************************************************************************")
   print("%-30s %-20.3f %-20.3f %-20.3f %-0.3f" % (filename + ext, audio_length, inference_time, model_retval[1], model_retval[2]))

if __name__ == '__main__':
   main()

就是这样!这就是我们使用 DeepSpeech 对音频文件进行语音识别所要做的全部工作。这是令人惊讶的大量代码。不久前,我还写了一篇文章,介绍如何使用 AssemblyAI 语音转文本 API 以更少的代码实现这一点。如果你不想使用 DeepSpeech 的话,你可以在 25 行代码内阅读关于如何用 Python 进行语音识别的内容。

基本的 DeepSpeech 实时语音识别示例

https://www.youtube.com/embed/clhcJgp-ciM?feature=oembed

现在我们已经看到了如何使用 DeepSpeech 进行异步语音识别,让我们也构建一个实时语音识别示例。就像以前一样,我们将从安装正确的需求开始。与上面的异步示例类似,我们将需要 webrtcvad,但是我们还需要 pyaudio、halo、numpy 和 scipy。

Halo 表示节目正在流式播放,numpy 和 scipy 用于以正确的采样率对音频进行重新采样。

pip install deepspeech webrtcvad pyaudio halo numpy scipy

我们如何用 DeepSpeech 构建一个实时语音识别程序?正如我们在上面的例子中所做的那样,我们需要将检测到声音活动的音频段与没有声音活动的音频段分开。如果音频帧有声音活动,那么我们会将其输入 DeepSpeech 模型进行转录。

让我们为我们的声音活动检测音频帧创建一个对象,我们称之为 VADAudio(声音活动检测音频)。首先,我们将为我们的类定义格式、速率、通道数和每秒帧数。

class VADAudio(object):
   """Filter & segment audio with voice activity detection."""

   FORMAT = pyaudio.paInt16
   # Network/VAD rate-space
   RATE_PROCESS = 16000
   CHANNELS = 1
   BLOCKS_PER_SECOND = 50

每个类都需要一个__init__函数。下面定义的 VADAudio 类的__init__函数将接受四个参数:一个回调、一个设备、一个输入速率和一个文件。除了 input_rate 之外,如果在创建时没有传递它们,那么它们将默认为 None。

输入采样速率将是我们在上面的类中定义的速率采样过程。当我们初始化我们的类时,我们还将创建一个名为proxy_callback的实例方法,它返回一个无元组和 pyAudio 信号以继续,但在它返回之前,它调用回调函数,因此得名proxy_callback

初始化时,我们做的第一件事是将“callback”设置为一个函数,该函数将数据放入属于对象实例的缓冲队列中。我们为实例的缓冲队列初始化一个空队列。我们将设备和输入速率设置为传入的值,将采样速率设置为类的采样速率。然后,我们将我们的块大小和块大小输入分别导出为类的采样速率和输入速率的商除以每秒的块数。块是我们将要处理的音频数据的离散片段。

接下来,我们创建一个 PyAudio 对象并声明一组关键字参数。关键字参数是format,设置为我们之前声明的 VADAudio 类的格式值channels,设置为类的通道值rate,设置为输入速率input,设置为 true,frames_per_buffer设置为之前计算的块大小输入,stream_callback,设置为我们之前创建的proxy_callback实例函数。我们还会将过滤背景噪音的积极性设置为传入的积极性,默认值为 3,即最高的过滤器。
我们现在将块大小设置为无。如果有一个设备被传递到初始化的对象中,我们设置一个新的关键字参数,input_device_index给这个设备。该设备是所使用的输入设备,但是我们实际传递的将是 pyAudio 定义的设备的索引,只有当您想要使用不是计算机默认输入设备的输入设备时,这才是必要的。如果没有传入设备,而我们传入了一个文件对象,我们将块大小更改为 320,并打开文件以字节读取。最后,我们用我们创建的关键字参数字典打开并启动一个 PyAudio 流。

def __init__(self, callback=None, device=None, input_rate=RATE_PROCESS, file=None, aggressiveness=3):
       def proxy_callback(in_data, frame_count, time_info, status):
           if self.chunk is not None:
               in_data = self.wf.readframes(self.chunk)
           callback(in_data)
           return (None, pyaudio.paContinue)
       if callback is None: callback = lambda in_data: self.buffer_queue.put(in_data)
       self.buffer_queue = queue.Queue()
       self.device = device
       self.input_rate = input_rate
       self.sample_rate = self.RATE_PROCESS
       self.block_size = int(self.RATE_PROCESS / float(self.BLOCKS_PER_SECOND))
       self.block_size_input = int(self.input_rate / float(self.BLOCKS_PER_SECOND))
       self.pa = pyaudio.PyAudio()
       self.vad = webrtcvad.Vad(aggressiveness)

       kwargs = {
           'format': self.FORMAT,
           'channels': self.CHANNELS,
           'rate': self.input_rate,
           'input': True,
           'frames_per_buffer': self.block_size_input,
           'stream_callback': proxy_callback,
       }

       self.chunk = None
       # if not default device
       if self.device:
           kwargs['input_device_index'] = self.device
       elif file is not None:
           self.chunk = 320
           self.wf = wave.open(file, 'rb')

       self.stream = self.pa.open(**kwargs)
       self.stream.start_stream() 

我们的 VADAudio 类将有 6 个函数:重采样,读 _ 重采样,读,写 _wav,一个帧生成器,和一个声音活动检测段收集器。让我们从创建重采样函数开始。由于技术的限制,并不是所有的麦克风都支持 DeepSpeech 的原生处理采样率。该函数接收音频数据和输入采样速率,并返回一串重采样为 16 kHz 的数据。

def resample(self, data, input_rate):
       """
       Microphone may not support our native processing sampling rate, so
       resample from input_rate to RATE_PROCESS here for webrtcvad and
       deepspeech

       Args:
           data (binary): Input audio stream
           input_rate (int): Input audio rate to resample from
       """
       data16 = np.fromstring(string=data, dtype=np.int16)
       resample_size = int(len(data16) / self.input_rate * self.RATE_PROCESS)
       resample = signal.resample(data16, resample_size)
       resample16 = np.array(resample, dtype=np.int16)
       return resample16.tostring()

接下来,我们将把readread_resampled函数放在一起,因为它们基本上做同样的事情。read功能“读取”音频数据,而read_resampled功能将读取重新采样的音频数据。read_resampled功能将用于读取最初没有以正确的采样率采样的音频。

def read_resampled(self):
       """Return a block of audio data resampled to 16000hz, blocking if necessary."""
       return self.resample(data=self.buffer_queue.get(),
                            input_rate=self.input_rate)

   def read(self):
       """Return a block of audio data, blocking if necessary."""
       return self.buffer_queue.get()

write_wav函数接受一个文件名和数据。它打开一个文件名为的文件,允许写入采样宽度为 2、帧速率等于实例采样速率的字节,并在关闭 wave 文件之前将数据作为帧写入。

def write_wav(self, filename, data):
       logging.info("write wav %s", filename)
       wf = wave.open(filename, 'wb')
       wf.setnchannels(self.CHANNELS)
       # wf.setsampwidth(self.pa.get_sample_size(FORMAT))
       assert self.FORMAT == pyaudio.paInt16
       wf.setsampwidth(2)
       wf.setframerate(self.sample_rate)
       wf.writeframes(data)
       wf.close()

在创建我们的帧生成器之前,我们将使用实例的块大小和采样率来设置一个以毫秒为单位的帧持续时间属性。

frame_duration_ms = property(lambda self: 1000 * self.block_size // self.sample_rate)

现在,让我们创建我们的帧生成器。帧生成器将从麦克风/文件中产生原始数据,或者使用 Audio 类中的 read 和 read_resampled 函数产生重新采样的数据。如果输入速率等于默认速率,那么它将简单地读入原始数据,否则它将返回重新采样的数据。

def frame_generator(self):
       """Generator that yields all audio frames from microphone."""
       if self.input_rate == self.RATE_PROCESS:
           while True:
               yield self.read()
       else:
           while True:
               yield self.read_resampled()

我们在 VADAudio 中需要的最后一个函数是收集音频帧的方法。这个函数接受一个以毫秒为单位的填充,一个控制函数何时“触发”的比率(类似于上面基本异步示例中的比率),以及一组默认为 None 的帧。

padding_ms 的默认值为 300,比率的默认值为 0.75。填充是为了填充音频段,这里的比率 0.75 意味着如果缓冲区中 75%的音频是语音,我们将进入触发状态。如果没有传入帧,我们将调用之前创建的帧生成器函数。我们将把填充帧的数量定义为以毫秒为单位的填充除以我们之前导出的以毫秒为单位的帧持续时间。

本例的环形缓冲区将使用最大长度为填充帧数的出列。我们将以非触发状态开始。我们将循环遍历每一帧,如果遇到长度小于 640 的帧,则返回。只要帧的长度超过 640,我们就检查音频是否包含语音。

现在,我们执行与上述基本示例相同的算法,以收集包含语音的音频帧。当没有被触发时,我们将语音帧附加到环形缓冲区,如果语音帧占总帧的数量高于我们之前传递的阈值或比率,则触发该状态。

一旦被触发,我们就让出缓冲区中的每一帧并清空缓冲区。在触发状态下,我们立即让出该帧,然后将该帧附加到环形缓冲区。然后,我们检查环形缓冲区中非语音帧与语音帧的比率,如果该比率超过了我们预定义的比率,我们将取消触发,产生一个 None 帧,然后清除缓冲区。

def vad_collector(self, padding_ms=300, ratio=0.75, frames=None):
       """Generator that yields series of consecutive audio frames comprising each utterence, separated by yielding a single None.
           Determines voice activity by ratio of frames in padding_ms. Uses a buffer to include padding_ms prior to being triggered.
           Example: (frame, ..., frame, None, frame, ..., frame, None, ...)
                     |---utterence---|        |---utterence---|
       """
       if frames is None: frames = self.frame_generator()
       num_padding_frames = padding_ms // self.frame_duration_ms
       ring_buffer = collections.deque(maxlen=num_padding_frames)
       triggered = False

       for frame in frames:
           if len(frame) < 640:
               return

           is_speech = self.vad.is_speech(frame, self.sample_rate)

           if not triggered:
               ring_buffer.append((frame, is_speech))
               num_voiced = len([f for f, speech in ring_buffer if speech])
               if num_voiced > ratio * ring_buffer.maxlen:
                   triggered = True
                   for f, s in ring_buffer:
                       yield f
                   ring_buffer.clear()

           else:
               yield frame
               ring_buffer.append((frame, is_speech))
               num_unvoiced = len([f for f, speech in ring_buffer if not speech])
               if num_unvoiced > ratio * ring_buffer.maxlen:
                   triggered = False
                   yield None
                   ring_buffer.clear()

好了——我们已经完成了为音频类创建的所有函数,我们将使用这些函数流式传输到我们的 DeepSpeech 模型,并获得实时语音到文本的转录。现在是时候创建一个 main 函数了,我们将运行它来实际执行我们的流转录。

首先,我们将为我们的主函数提供模型和计分器的位置。然后,我们将创建一个传入了积极性、设备、速率和文件的 VADAudio 对象。

使用我们之前创建的vad_collector函数,我们获得帧并设置微调器/指示器。我们使用从通过参数传递的模型创建的 DeepSpeech 模型来创建流。在初始化一个名为wav_data的空字节数组后,我们遍历每一帧。

对于每一帧,如果该帧不是 None,我们显示一个旋转器旋转,然后将音频内容输入到我们的流中。如果我们发送了保存为. wav 文件的参数,那么该文件也会被扩展。如果帧是一个 None 对象,那么我们结束“话语”并保存。wav 文件创建,如果我们创建了一个,并清除字节数组。然后我们关闭流,并打开一个新的。

def main():
   # Load DeepSpeech model
   model = 'models/v0.9.3/deepspeech-0.9.3-models.pbmm'
   scorer = 'models/v0.9.3/deepspeech-0.9.3-models.scorer'

   print('Initializing model...')
   print("model: %s", model)
   model = deepspeech.Model(model)
   if scorer:
       print("scorer: %s", scorer)
       model.enableExternalScorer(scorer)

   # Start audio with VAD
   vad_audio = VADAudio(aggressiveness=3,
                        device=None,
                        input_rate=DEFAULT_SAMPLE_RATE,
                        file=None)
   print("Listening (ctrl-C to exit)...")
   frames = vad_audio.vad_collector()

   # Stream from microphone to DeepSpeech using VAD
   spinner = Halo(spinner='line')
   stream_context = model.createStream()
   wav_data = bytearray()
   for frame in frames:
       if frame is not None:
           if spinner: spinner.start()
           stream_context.feedAudioContent(np.frombuffer(frame, np.int16))
       else:
           if spinner: spinner.stop()
           print("end utterence")
           text = stream_context.finishStream()
           print("Recognized: %s" % text)
           stream_context = model.createStream()

if __name__ == '__main__':
   DEFAULT_SAMPLE_RATE = 16000
   main()

就像异步语音到文本转录一样,实时转录需要大量代码来进行实时语音识别。如果你不想管理所有这些代码,你可以查看我们的指南如何使用 AssemblyAI 语音转文本 API 用更少的代码在 Python 中进行实时语音识别。

结论

我们的 DeepSpeech 概述和教程的第一部分到此结束。在本教程中,我们复习了如何在. wav 文件上进行基本的语音识别,以及如何使用 DeepSpeech 进行实时语音识别。第二部分将是关于用 DeepSpeech 训练你自己的模型,以及它的表现有多准确。它很快就要来了——所以要保持警惕!

更多信息请在推特上关注我们 @assemblyai@于坚 _ 唐。-订阅我们的时事通讯

可微规划——简单介绍

原文:https://www.assemblyai.com/blog/differentiable-programming-a-simple-introduction/

可微分编程是一个相对较新的术语,经常与深度学习混为一谈。虽然深度学习确实与差异化编程重叠,但深度学习是差异化编程的子集

在本文中,我们将解释什么是可区分编程,以及它与深度学习有何不同,特别是关于它更大的通用性。我们将通过三种方式(越来越聪明)解决一个基于物理的问题来学习。我们开始吧!

微分规划简介

许多机器学习技术的核心归结为最小化某个损失函数,以学习一个非常适合解决某个问题的模型。深度学习建立在这个中心思想的基础上,对模型本身提出了两个要求——一个网络架构和通过自动微分进行训练。相比之下,可微分编程只需要这些要求中的一个-通过自动微分进行训练。

可微分编程是指以某种方式利用自动微分,允许程序优化其参数,以便更好地完成某项任务。它只需要三样东西:

  1. 待优化的参数化函数 /方法/模型
  2. 适用于衡量绩效的损失,以及
  3. (自动)待优化对象的可区分性

虽然深度学习肯定会选中这些框,但它并不是唯一一个这样做的领域。可微分编程可以应用于其他领域的各种任务,包括概率编程、贝叶斯推理、机器人和物理学。

问题是:瞄准大炮

我们将通过考虑以下问题来探讨差异化编程的主题:

给定一个已知距离的目标,如何调整大炮的角度和弹射速度才能击中目标?

我们将以三种方式解决这个问题——首先使用纯深度学习方法,其次使用纯牛顿方法,最后使用混合牛顿深度学习方法。

神经网络方法

我们将首先在假设我们对物理学一无所知的情况下,仅使用神经网络来解决这个问题。相反,我们只是有一门大炮,我们可以发射很多很多次,以收集大量的数据,记录发射角度,弹射速度,以及每个数据对应的着陆距离。我们的目标是了解我们如何利用这种经验数据,以便在另一个方向上前进,也就是将一个目标距离映射到合适的控制参数,这将使我们的射弹降落在期望的目标上。

乍一看,我们可能会认为这个问题很简单。我们希望输入距离和输出控制参数,因此我们可以使用我们收集的所有数据训练一个模型来执行此映射。

A first attempt at solving this problem might involve creating a Neural Network to map from distances to corresponding control parameters

不幸的是,这种方法行不通,因为我们不知道如何测量损失。我们可以输入一个目标距离并得到假设的控制参数,但是我们如何确定这些是否是“好的”控制参数?假设的和真实的控制参数之间的平均平均误差合适吗?如果有多种解决方案呢(在本例中确实有)?请记住,在这种情况下,我们对物理学一无所知!

相反,我们在另一个方向绘制从控制参数的结果距离,因为损失很容易计算——我们只需测量我们预测的距离和真实差异之间的差异(并对其求平方以获得可微性)[1

Mapping instead from control parameters to the resulting projectile distance provides a simple and intuitive way to measure loss

我们来训练这样一个神经网络。下面你可以看到 1000 个数据点的训练结果,其中蓝色表面是我们试图复制的真正基础功能。

https://www.assemblyai.com/blog/content/media/2022/02/NN_fitting.mp4

Neural Network learning on data (green) to approximate the true underlying function (blue) over 100 epochs

经过训练后,我们有了一个神经网络,它已经学会了从控制参数到最终距离的映射。让我们看看在下图中,随着目标距离的变化,损失面是如何变化的。x-轴为弹丸初速(范围从 0 到最大10m/s),y-轴为射击角度(范围从 0π/2 弧度,不含),而 z 轴为所得距离与目标距离之间的 MSE。

https://www.assemblyai.com/blog/content/media/2022/02/nn_loss_anim.mp4

The empirical loss surface (as a function of initial velocity and launch angle) changes form depending on target distance

现在我们有了一个神经网络来近似从控制参数最终距离的映射,我们如何为给定的目标距离获得合适的控制参数呢?记得我们想要输入一个目标距离,输出控制参数,但是我们的神经网络映射到另一个方向。

正如我们在上面的图中看到的,我们有一个损失面,它是控制参数的函数,其形状由我们的目标距离参数化;所以我们可以简单的用渐变下降2!虽然我们首先使用梯度下降以便学习控制参数到距离映射的近似,但是我们现在使用梯度下降来最小化由我们的输入目标距离参数化的该映射的相应损失表面。现在让我们执行这个梯度下降:

https://www.assemblyai.com/blog/content/media/2022/02/nn_grad_desc_rotation.mp4

Path of gradient descent towards empirical loss function minimum curve

我们从最初的猜测开始,并成功地学习了更好的控制参数,使我们更接近我们的目标。下面你可以看到下降过程中不同轨迹的动画,总共花费了数百次迭代。

https://www.assemblyai.com/blog/content/media/2022/02/nn_trajectories.mp4

During gradient descent, the landing spot tends towards the target

我说学习到的控制参数是更好,而不是正确,故意的。请记住,尽管我们通过梯度下降使损失最小化(事实上可能达到零损失),但这种损失是相对于真正的基本函数的近似形式而言的,该函数定义了控制参数和最终距离之间的关系。虽然我们可能学到了更好的控制参数,但我们不知道它们是完美的或者甚至是足够的。事实上,上面动画中的最终轨迹与目标有 20 厘米的误差,即使我们将梯度下降设置为最大 5 厘米的误差。相对于近似模型,误差最多为 5 厘米,但相对于真实模型,误差在技术上不受限制。

为了限制误差,我们需要输入一个目标,使用梯度下降(在固定的神经模型上)来学习控制参数,使用这些控制参数来运行实验并收集真实的结果距离,然后将该距离与输入的目标距离进行比较,对大量数据进行一些统计分析。

Gradient descent to optimize the control parameters happens with respect to the empirical model - to determine its performance, the results need to be compared to real experimentation.

虽然这整个过程似乎是劳动密集型和不充分的,特别是考虑到预测性能和真实性能之间的差异的无界性,但这确实是我们在这种情况下所能做的最好的事情(省去了在更多数据上训练神经网络)。我们如何改进我们的方法?

牛顿方法

到现在为止,你应该已经注意到了我们上述方法的一个非常明显的问题——我们没有利用数百年来物理学为我们提供的对手头问题的洞察力,这种洞察力被方便地封装在我们可以利用的数学关系的形式中。

上面,我们对从控制参数到最终投射距离的映射进行了近似,但是如果我们知道运动定律,为什么我们要用神经网络来近似这个函数呢?在这种情况下,一个非常简单的运动学分析为我们提供了真实的基本映射,这反过来又为我们提供了真实的损失面,同样作为初始速度和喷射角的函数,并由目标距离参数化。让我们再来看看这个损失面是如何随着目标距离参数的变化而变化的。

https://www.assemblyai.com/blog/content/media/2022/02/loss_evolution.mp4

How the true loss surface (which our Neural Network sought to approximate in the last section) changes as parameterized by target distance

真实损失面和近似模型损失面之间容易观察到的形式差异有助于我们突出前面方法的问题——最小化扰动损失面会产生不完美的控制参数。你会注意到,尽管表面趋势相同,但定义真实模型(蓝色)中最小曲线的抛物线与神经模型(绿色)的最小曲线并不相同。也就是说,即使我们在经验损失面上适当地最小化,产生的控制参数也可能不在真实的解曲线上。

https://www.assemblyai.com/blog/content/media/2022/03/comparison.mp4

Gradient descent on the empirical loss surface compared to the true loss surface. Note that the projections of the minimum curves of the empirical and true loss surfaces onto the control parameter plane would not perfectly align.

给定我们的真实损失面,我们可以像以前一样进行梯度下降。你可以在下面的图中看到梯度下降路径。

https://www.assemblyai.com/blog/content/media/2022/02/grad_path.mp4

Path of gradient descent on true loss function

如果我们检查抛射体在下降过程中的轨迹,我们可以看到一个缓慢但稳定的调整到足够的控制参数。

https://www.assemblyai.com/blog/content/media/2022/02/output.mp4

Gradient descent of the true loss function takes fewer iterations to reach suitable control parameters

真实损失表面具有全局更平滑的优点,产生更少的迭代和更稳健的下降。此外,与经验损失面相比,我们不必担心病态,如果我们在右上角(红色箭头)初始化,我们的下降会导致非常不正确的控制参数。与此相关,使用真实损失表面的好处是确保找到的解决方案实际上是正确的(只要我们的物理模型实际上反映了现实,但这是科学而不是机器学习的范围)。

The true loss surface (blue, left) is globally smooth and provides more robust and accurate gradient descent when compared to the empirical (green, right) loss surface

在这一点上,你可能想知道为什么我们首先要提出神经网络。如果我们可以简单地对真实损失函数执行梯度下降,为什么我们不首先这样做呢?答案是,最佳方法实际上结合了前两种方法,产生了一种混合牛顿神经网络方法,也就是可微规划方法。

微分规划方法

在上述两种情况下,我们都需要执行梯度下降。这意味着,如果我们要部署这样一个模型,我们将不得不担心未知的运行时间、不良的学习率、陷入局部极小值等等。基于梯度的优化需求最终源于我们无法设计一种方法来测量与输出控制参数相关的损失:

Previously, in our physics-blind method, we did not know how to measure loss with respect to output control parameters

这种无法设计合适的损失导致我们翻转输入和输出,给我们一个可感知的损失作为目标距离和预测距离之间的 MSE,然后执行梯度下降以获得足够的控制参数。虽然我们可以利用我们的物理知识以这种方式优化真实损失表面,但我们可以通过使用物理模型生成可感知损失函数来做得更好。

结果是一种可微分编程方法,其中我们将从到目标距离映射到相应的控制参数,然后从这些参数得到的的真实距离。结果是实际上是一个自动编码网络,其中神经网络学习物理模型的“逆”3 ,该物理模型从控制参数映射到结果距离:

https://www.assemblyai.com/blog/content/media/2022/02/model_transitions.mp4

We incorporate prior domain-specific knowledge to create an "autoencoding" network which we can backpropagate through to train our approximation network

由于我们的物理模型由可微分函数组成,我们可以通过网络反向传播,并更新神经网络的参数进行学习。

结果是一个预测网络,它将目标距离的映射到合适的控制参数,这是我们一直以来的目标。一旦我们使用它来生成预测的控制参数,物理模型就可以用来验证控制参数产生的着陆距离在我们目标距离的允许误差之内。

如果着陆距离不合适,我们可以再次在真实模型上执行梯度下降,但是这次使用预测的控制参数作为起点,而不是随机初始化。因此,使用差异化编程方法,我们有:

  1. 预先训练好的 神经模型,能够在给定目标距离的情况下快速提供控制参数估计值
  2. 验证这些控制参数确实会使抛射体充分接近目标的方法,以及
  3. 如果控制参数不足,则调整控制参数的快速方法(平均需要比随机初始化少得多的迭代)。

我们可以在下图中看到整个系统:

Complete schematic of the Differentiable Programming approach to solving the Cannon Problem

想学习如何构建这样一个可微分的编程模型吗?

我们将很快发布一篇关于如何用谷歌的 JAX 做到这一点的文章,所以一定要关注我们的时事通讯,这样你就不会错过它了!

Subscribe

最后的话

虽然我们为混合神经-物理模型提供了一个简单、高级的可微分编程用例,但它的应用远远超出了这个例子。利用差异化编程4 为他们的项目注入人工智能的一些领域有:

对于那些对更高级的用例感兴趣的人来说, Julia 编程语言的团队有一些关于差异化编程4的很棒的资源。

如需更多学习资源,请随时查看我们的博客YouTube 频道。或者,关注我们的时事通讯推特以便在我们发布新内容时保持关注。

脚注

  1. 请注意,我们正在进行纯机器学习——我们可能对手头的情况一无所知,只将数据视为 3 列数字 a、b、和 c,只要我们被告知 MSE 是一个适当的损失函数,训练模型仍然是成功的。
  2. 这里应该注意的是,即使底层的真实模型是不可微的或者甚至是连续的,近似模型也保证是可微的,因此我们可以确定梯度下降是一种可行的优化方法。这种保证源于神经网络是可微函数的组合,因此它本身是全局可微的。
  3. 这在数学意义上不是真正的逆,因为从控制参数到结果距离的正向映射不是内射的。它只是一个逆过程,在这个意义上,它通过正向映射的最小化级别曲线找到一条路径,作为目标距离的函数。
  4. 涵盖这些主题的讲座可以在这里找到,并且是本文中大炮问题的灵感来源。

机器学习的扩散模型介绍

原文:https://www.assemblyai.com/blog/diffusion-models-for-machine-learning-introduction/

扩散模型是生成模型,在过去的几年中已经获得了显著的流行,并且有很好的理由。仅在 21 世纪 20 年代发表的几篇开创性论文就向世界展示了扩散模型的能力,比如在图像合成上击败甘斯6 。最近,从业者将会看到扩散模型在 DALL-E 2 中使用,这是 OpenAI 上个月发布的图像生成模型。

*

Various images generated by DALL-E 2 (source).*

鉴于最近扩散模型的成功浪潮,许多机器学习实践者肯定对它们的内部工作方式感兴趣。在本文中,我们将检查扩散模型的理论基础,然后演示如何在 PyTorch 中使用 T2 扩散模型生成图像。让我们开始吧!

扩散模型-简介

扩散模型是生成型模型,这意味着它们用于生成与它们被训练的数据相似的数据。从根本上来说,扩散模型的工作原理是通过连续添加高斯噪声来破坏训练数据,然后**通过逆转这个噪声过程来学习恢复数据。训练后,我们可以使用扩散模型来生成数据,只需将随机采样的噪声通过学习的去噪过程**。

*

Diffusion Models can be used to generate images from noise (adapted from source)*

更具体地,扩散模型是使用固定马尔可夫链映射到潜在空间的潜在变量模型。这个链逐渐向数据中添加噪声,以便获得近似后验(q(\ textbf { x } _ { 1:T } | \ textbf { x } _ 0)),其中( \textbf{x}_1,...,\textbf{x}_T )是与( \textbf{x}_0 )具有相同维数的潜在变量。在下图中,我们看到了图像数据的马尔可夫链。

*

(Modified from source)*

最终,图像被渐近地转换为纯高斯噪声。训练一个扩散模型的目标是学习过程——即训练( p_\theta(x_{t-1}|x_t) )。通过沿着这条链向后遍历,我们可以生成新的数据。

*

(Modified from source)*

扩散模型的好处

如上所述,近年来对扩散模型的研究呈爆炸式增长。受非平衡热力学1的启发,扩散模型目前产生最先进的图像质量,其示例如下:

*

(adapted from source)*

除了尖端的图像质量,扩散模型还有许多其他好处,包括不需要对抗训练。对抗性训练的困难是有据可查的;而且,如果存在非对抗性的替代方案,表现和训练效率相当,通常最好利用它们。关于训练效率的话题,扩散模型也有额外的好处可扩展性和并行性。

虽然扩散模型似乎是凭空产生结果,但有许多仔细而有趣的数学选择和细节为这些结果提供了基础,最佳实践仍在文献中不断发展。现在让我们更详细地看看支撑扩散模型的数学理论。

扩散模型——深潜

如上所述,扩散模型由一个正向过程(或扩散过程)和一个反向过程(或反向扩散过程)组成,在正向过程中,一个数据(通常是一幅图像)被渐进地噪声化,在反向过程中,噪声被从目标分布转换回样本。

当噪声水平足够低时,正向过程中的采样链转换可以设置为条件高斯型。将这一事实与马尔可夫假设相结合,导致正向过程的简单参数化:

*数学笔记

我们一直在讨论通过添加高斯噪声来破坏数据,但是一开始可能不清楚我们在哪里执行这种添加。根据上述等式,在链中的每一步,我们简单地从高斯分布中采样,其平均值是链中的前一个值(即图像)。

这两种说法是等价的。即

为了理解其中的原因,我们将通过断言来稍微滥用符号


其中最终的含义源于随机变量之和及其分布的卷积之间的数学等价性——更多信息参见维基百科页面

换句话说,我们已经证明,通过高斯分布的平均值断言以前一个时间步长为条件的时间步长的分布等同于断言给定时间步长的分布是前一个时间步长的分布加上高斯噪声。为了简单起见,我们省略了由方差表引入的标量,并且示出了一维标量,但是类似的证明适用于多元高斯分布。*

其中( \beta_1,...,\beta_T )是一个方差调度(学习的或固定的),如果表现良好,确保 ( x_T ) 对于足够大的 T 接近各向同性高斯。

*

Given the Markov assumption, the joint distribution of the latent variables is the product of the Gaussian conditional chain transitions (modified from source).*

如前所述,扩散模型的“魔力”来自于逆过程。在训练期间,该模型学习反转这种扩散过程,以便生成新数据。从纯高斯噪声(p(\ textbf { x } _ { T }):= \ mathcal { N }(\ textbf { x } _ T,\textbf{0},\textbf{I}) )开始,模型将联合分布( p_\theta(\textbf{x}_{0:T}) )学习为

其中学习高斯跃迁的时间相关参数。特别要注意的是,马尔可夫公式断言给定的反向扩散转移分布仅取决于前一时间步(或后一时间步,取决于你如何看待它):

***

(Modified from source)* *想学习如何在 PyTorch 中建立扩散模型?

查看我们的 MinImagen 项目,在这里我们将构建一个文本到图像模型 Imagen 的最小实现!

Check it out*

培养

通过寻找使训练数据的可能性最大化的反向马尔可夫转移来训练扩散模型。实际上,训练等价地包括最小化负对数似然的变分上限。

*符号细节

注意,L[vlb] 在技术上是一个界(ELBO 的负值),我们试图将其最小化,但为了与文献一致,我们将其称为 L[vlb] 。*

我们寻求根据 Kullback-Leibler (KL)散度重写( L_{vlb} )。KL 散度是一种不对称的统计距离度量,用于衡量一个概率分布 P 与参考分布 Q 的差异程度。我们对用 KL 散度来公式化( L_{vlb} )感兴趣,因为我们的马尔可夫链中的转移分布是高斯分布,并且高斯分布之间的 KL 散度具有封闭形式

#### 什么是 KL 背离?

连续分布的 KL 散度的数学形式是

*

The double bars indicate that the function is not symmetric with respect to its arguments.*

下面你可以看到变化分布 P (蓝色)与参考分布 Q (红色)的 KL 散度。绿色曲线表示上述 KL 散度定义中积分内的函数,曲线下的总面积表示在任何给定时刻 PQ 的 KL 散度值,该值也用数字显示。

*https://www.assemblyai.com/blog/content/media/2022/05/KL_Divergence.mp4

  • #### 用 KL 散度刻画( L_{vlb} )

如前所述,根据 KL 散度,1 几乎完全重写( L_{vlb} )是可能的:

在哪里

*派生详细信息

变分界限等于

给定马尔可夫假设,用它们的定义替换分布,我们得到

我们使用对数规则将表达式转换成对数的总和,然后我们取出第一项

使用贝叶斯定理和我们的马尔可夫假设,这个表达式变成

然后,我们使用对数规则拆分中间项

孤立第二项,我们看到

将此代入我们的 L[vlb] 方程,我们得到

使用日志规则,我们重新排列

接下来,我们注意到任何两个分布的 KL 散度的以下等价性:

最后,将这个等价关系应用于前面的表达式,我们得到

*

在( L_{t-1} )中以( x_0 )为条件的前向过程后验导致一种易处理的形式,这种形式导致所有 KL 发散都是高斯分布之间的比较。这意味着可以用封闭形式的表达式而不是用蒙特卡罗估计3 来精确计算离差。

型号选择

随着我们的目标函数的数学基础的建立,我们现在需要就如何实现我们的扩散模型做出几个选择。对于前进过程,唯一需要的选择是定义差异计划,其值通常在前进过程中增加。

对于相反的过程,我们更多地选择高斯分布参数化/模型架构。请注意扩散模型提供的高度灵活性对我们架构的唯一要求是其输入和输出具有相同的维度。

我们将在下面更详细地探讨这些选择的细节。

#### 正向过程和(L_T )

如上所述,关于推进过程,我们必须定义差异时间表。特别是,我们将它们设置为与时间相关的常数,忽略了它们可以被学习的事实。例如3 ,可以使用从(\beta_1=10^{-4}\到(\beta_T=0.2)的线性时间表,或者可能是几何级数。

不管选择的具体值如何,方差时间表是固定的这一事实导致( L_{T} )相对于我们的一组可学习参数成为常数,从而允许我们在涉及训练时忽略它。

#### 逆过程和( L_{1:T-1} )

现在我们讨论定义反向过程所需的选择。回想一下,我们将反向马尔可夫转移定义为高斯:

我们现在必须定义( \pmb{\mu}\theta )或(\ PMB { \适马}\theta )的函数形式。虽然有更复杂的方法来参数化(\ PMB { \适马}_\theta )5 ,我们简单地设置

也就是说,我们假设多元高斯是具有相同方差的独立高斯的乘积,方差值可以随时间变化。我们将这些差异设置为等同于我们的正向流程差异计划

给定这个新的公式(\ PMB { \适马}_\theta ),我们有

这让我们能够转变

其中,差中的第一项是(x_t)和(x_0)的线性组合,这取决于方差调度(\beta_t)。该函数的确切形式与我们的目的无关,但可以在[ 3 中找到。

以上比例的意义在于,最直接的参数化( \mu_\theta )简单地预测了扩散后验均值。重要的是,[ 3 的作者实际上发现,训练(\mu_\theta)来预测任何给定时间步长的噪声分量会产生更好的结果。特别是,让

在哪里

这导致了下面的替代损失函数,【3的作者发现这导致了更稳定的训练和更好的结果:

3 的作者也注意到了扩散模型的这种公式化与基于朗之万动力学的分数匹配生成模型的联系。事实上,似乎扩散模型和基于分数的模型可能是同一枚硬币的两面,类似于基于波的量子力学和基于矩阵的量子力学的独立和并行发展,揭示了同一现象的两个等效公式[ 2

#### 网络体系结构

虽然我们的简化损失函数试图训练一个模型( \pmb{\epsilon}_\theta ),但我们仍未定义该模型的架构。注意,模型的唯一要求是输入和输出维度相同。

鉴于这种限制,图像扩散模型通常用类似 U-Net 的架构来实现就不足为奇了。

*

Architecture of U-Net (source)* #### 逆过程解码器和(l0)

沿着相反过程的路径由连续条件高斯分布下的许多变换组成。在反向过程的最后,回想一下我们试图产生一个由整数像素值组成的图像。因此,我们必须设计一种方法来获得所有像素中每个可能像素值的离散(对数)可能性

实现这一点的方法是将反向扩散链中的最后一个转换设置到一个独立的离散解码器。为了确定给定图像(x_0)给定(x_1)的可能性,我们首先在数据维度之间施加独立性:

其中 D 是数据的维度,上标 i 表示一个坐标的提取。现在的目标是在给定时间(t=1)时轻微噪声图像中相应像素的可能值的分布的情况下,确定每个整数值对于给定像素的可能性:**

其中(t=1)的像素分布从下面的多变量高斯分布中导出,其对角协方差矩阵允许我们将分布分割为单变量高斯分布的乘积,每个数据维度一个:

我们假设图像由({0,1,...,255})(如标准 RGB 图像一样),这些图像已被线性缩放为([-1,1])。然后,我们将实际行分解成小“桶”,其中,对于给定的缩放像素值 x ,该范围的桶为([x-1/255,x+1/255])。给定(x_1)中相应像素的单变量高斯分布,像素值 x,的概率是以 x 为中心的桶内该单变量高斯分布下的面积。

下面你可以看到每个桶的面积和它们的概率,在这种情况下,对应于平均像素值为(255/2)(半亮度)的分布。红色曲线表示特定像素在 t=1 图像中的分布,面积给出了相应像素值在 t=0 图像中的概率。

*https://www.assemblyai.com/blog/content/media/2022/05/buckets_Trim.mp4

  • *技术说明

第一个和最后一个桶扩展到-inf 和+inf,以保持总概率。*

给定每个像素的一个 t=0 像素值,( p_\theta(x_0 | x_1) )的值就是它们的乘积。这个过程可以简单地概括为以下等式:

在哪里

给定( p_\theta(x_0 | x_1) )的这个方程,我们可以计算(L_{vlb})的最后一项,它不是用 KL 散度来表示的:

最终目标

正如上一节所提到的,[ 3 的作者发现,在给定的时间步长预测图像的噪声成分会产生最好的结果。最终,他们使用以下目标:

因此,我们的扩散模型的训练和采样算法可以在下图中简洁地捕捉到:

*

(source)*

扩散模型理论综述

在这一节中,我们详细探讨了扩散模型的理论。人们很容易陷入数学细节,因此我们在下面的章节中记录了最重要的几点,以便从鸟瞰的角度保持我们自己的方向:

  1. 我们的扩散模型被参数化为一个马尔可夫链,意味着我们的潜在变量(x_1,...,x_T)仅依赖于前一个(或后一个)时间步长。
  2. 马尔可夫链中的转移分布高斯,其中正向过程需要方差调度,反向过程参数学习。
  3. 扩散过程确保(x_T)对于足够大的 T,渐近分布为各向同性高斯分布
  4. 在我们的例子中,差异计划是固定的,但是也可以学习。对于固定时间表,遵循几何级数可能比线性级数提供更好的结果。在这两种情况下,方差通常在序列中随时间增加(即(\ beta _ I<\ beta _ j )for (I<j ))。
  5. 扩散模型高度灵活,允许使用任何输入和输出维度相同的架构。许多实现使用类似 U-Net 的架构。
  6. 训练目标是最大化训练数据的可能性。这表现为将模型参数调整到最小化数据*的负对数似然的变化上限。*
  7. 作为我们的马尔可夫假设的结果,目标函数中的几乎所有项都可以被转换为 KL 散度。这些值在计算时变得成立,因为我们使用了高斯分布,因此省略了执行蒙特卡罗近似的需要。
  8. 最终,使用简化的训练目标来训练预测给定潜在变量的噪声分量的函数,产生最佳和最稳定的结果。
  9. 离散解码器用于获得像素值的对数似然,作为反向扩散过程的最后一步。

有了这个对扩散模型的高级概述,让我们继续看如何在 PyTorch 中使用扩散模型。

PyTorch 中的扩散模型

虽然扩散模型还没有普及到与机器学习中的其他旧架构/方法相同的程度,但仍然有可供使用的实现。在 PyTorch 中使用扩散模型最简单的方法是使用denoising-diffusion-pytorch包,它实现了一个像本文中讨论的图像扩散模型。要安装该软件包,只需在终端中键入以下命令:

*`pip install denoising_diffusion_pytorch`*

最小示例

为了训练模型并生成图像,我们首先导入必要的包:

*`import torch
from denoising_diffusion_pytorch import Unet, GaussianDiffusion`* 

接下来,我们定义我们的网络架构,在这种情况下是 U-Net。dim参数指定在第一次下采样之前特征图的数量,并且dim_mults参数提供该值和后续下采样的被乘数:

*`model = Unet(
    dim = 64,
    dim_mults = (1, 2, 4, 8)
)`*

既然我们的网络架构已经定义,我们需要定义扩散模型本身。我们传入刚刚定义的 U-Net 模型以及几个参数——要生成的图像的大小,扩散过程中的时间步长数,以及在 L1 和 L2 规范之间的选择。

*`diffusion = GaussianDiffusion(
    model,
    image_size = 128,
    timesteps = 1000,   # number of steps
    loss_type = 'l1'    # L1 or L2
)`*

既然定义了扩散模型,就该训练了。我们生成随机数据进行训练,然后以通常的方式训练扩散模型:

*`training_images = torch.randn(8, 3, 128, 128)
loss = diffusion(training_images)
loss.backward()`* 

一旦模型训练完毕,我们就可以通过使用diffusion对象的sample()方法最终生成图像。这里,我们生成了 4 幅图像,这些图像只是噪声,因为我们的训练数据是随机的:

*`sampled_images = diffusion.sample(batch_size = 4)`*

关于自定义数据的培训

denoising-diffusion-pytorch包也允许你在一个特定数据集上训练一个扩散模型。只需用下面的Trainer()对象中的数据集目录路径替换'path/to/your/images'字符串,并将image_size更改为适当的值。之后,只需运行代码来训练模型,然后像以前一样进行采样。注意为了使用Trainer类,PyTorch 必须在启用 CUDA 的情况下编译:

*`from denoising_diffusion_pytorch import Unet, GaussianDiffusion, Trainer

model = Unet(
    dim = 64,
    dim_mults = (1, 2, 4, 8)
).cuda()

diffusion = GaussianDiffusion(
    model,
    image_size = 128,
    timesteps = 1000,   # number of steps
    loss_type = 'l1'    # L1 or L2
).cuda()

trainer = Trainer(
    diffusion,
    'path/to/your/images',
    train_batch_size = 32,
    train_lr = 2e-5,
    train_num_steps = 700000,         # total training steps
    gradient_accumulate_every = 2,    # gradient accumulation steps
    ema_decay = 0.995,                # exponential moving average decay
    amp = True                        # turn on mixed precision
)

trainer.train()`*

下面您可以看到从多元高斯噪声到 MNIST 数字的渐进去噪,类似于反向扩散:

最后的话

扩散模型是一种概念上简单而优雅的数据生成方法。他们最先进的成果与非对抗性训练相结合,使他们达到了很高的水平,鉴于他们的新生状态,预计在未来几年还会有进一步的改进。特别是,已经发现扩散模型对于像 DALL-E 2 这样的尖端模型的性能至关重要。

*你喜欢这篇文章吗?

考虑关注我们的时事通讯,以确保您将来不会错过此类内容。

Follow*

参考

[1] 利用非平衡热力学的深度无监督学习

[2] 通过估计数据分布的梯度进行生成建模

[3] 去噪扩散概率模型

[4] 训练基于分数的生成模型的改进技术

[5] 改进的去噪扩散概率模型

[6] 扩散模型在图像合成上击败了甘斯

[7] GLIDE:利用文本引导扩散模型实现真实感图像生成和编辑

[8] 具有剪辑潜在时间的分层文本条件图像生成

简易 C#语音识别

原文:https://www.assemblyai.com/blog/easy-c-speech-recognition/

在这篇文章中,我们将学习如何进行简单的 C#语音识别,或语音到文本的转换。

我们将带您完成从您的本地机器上传一个音频文件到 AssemblyAI 语音到文本 API 的过程,并使用 C#提交该音频文件进行转录。

C#语音识别-一览

为了完成手头的任务,我们将与 3 个独立的 AssemblyAI API 端点进行交互。

上传文件

这个端点将用于将音频文件直接上传到 AssemblyAI API。它将返回上传文件的 URL,我们将在随后的请求中使用它来实际开始转录。

POST /v2/upload

提交以供转录

这个端点将获取上传文件的 url,并将提交文件进行转录。响应将包含一个唯一的标识符,该标识符将用于查询转录状态,并在完成后返回结果。

POST /v2/transcript

{
    "audio_url": "https://assemblyai-pub-cdn.s3-us-west-2.amazonaws.com/Audio+to+Text+3.mp4"
}

获取转录

一旦完成,这个端点将获取所请求的转录的 ID,并返回转录的状态以及结果。

GET /v2/transcription/{transcript-id}

先决条件

下面是您在此演练中需要使用的工具列表。

  • 。NET 5 SDK(早期版本的。网芯和。NET framework 4.5+应该也可以)
  • Visual Studio 2019 社区版或以上。
  • 或者,您可以使用 Visual Studio 代码,但是我不会讨论这样做的步骤。
  • AssemblyAI API 键允许使用 API。关于如何获取 API 密钥,请参见下文。

如何从 AssemblyAI 获取 API 密钥

在开始之前,你需要注册一个免费帐户。有了免费账户,你可以在一个月内录制长达 5 小时 的音频。如果你超过了 5 个小时的转录,你可以很容易地升级到专业计划。

要注册您的帐户并获取 API 密钥,请导航到注册页面,添加您的信息,然后单击提交。您需要验证您的电子邮件地址,因此请检查您的收件箱中的验证电子邮件。

点击电子邮件中的验证链接后,您将被带到您的帐户仪表板。您会注意到,您可以从您的帐户仪表板中获取 API 令牌。

创建项目

  1. 打开 Visual Studio 2019,然后定位并选择 控制台应用 模板。
  2. 为项目命名,并选择项目文件在磁盘上的存储位置。我们将这个项目命名为assemblyaitranscriptor
  3. 最后,确保选择 。NET 5 作为你的目标框架版本。

创建 API 模型类

现在我们需要创建我们的强类型 API 模型,当与 API 交互时,这些模型将用于请求和响应的序列化/反序列化。在项目的根目录下,创建一个名为 Models 的新文件夹,并将这些类添加到该文件夹中。

// ./Models/UploadAudioResponse.cs
public class UploadAudioResponse
{
    [JsonPropertyName("upload_url")]
    public string UploadUrl { get; set; }
}
// ./Models/Word.cs
public class Word
{
    [JsonPropertyName("confidence")]
    public double Confidence { get; set; }

    [JsonPropertyName("end")]
    public int End { get; set; }

    [JsonPropertyName("start")]
    public int Start { get; set; }

    [JsonPropertyName("text")]
    public string Text { get; set; }
}
// ./Models/TranscriptionRequest.cs
public class TranscriptionRequest
{
    [JsonPropertyName("audio_url")]
    public string AudioUrl { get; set; }
}
// ./Models/TranscriptionResponse.cs
public class TranscriptionResponse
{
    [JsonPropertyName("id")]
    public string Id { get; set; }

    [JsonPropertyName("status")]
    public string Status { get; set; }

    [JsonPropertyName("acoustic_model")]
    public string AcousticModel { get; set; }

    [JsonPropertyName("audio_duration")]
    public double? AudioDuration { get; set; }

    [JsonPropertyName("audio_url")]
    public string AudioUrl { get; set; }

    [JsonPropertyName("confidence")]
    public double? Confidence { get; set; }

    [JsonPropertyName("dual_channel")]
    public string DualChannel { get; set; }

    [JsonPropertyName("format_text")]
    public bool FormatText { get; set; }

    [JsonPropertyName("language_model")]
    public string LanguageModel { get; set; }

    [JsonPropertyName("punctuate")]
    public bool Punctuate { get; set; }

    [JsonPropertyName("text")]
    public string Text { get; set; }

    [JsonPropertyName("utterances")]
    public string Utterances { get; set; }

    [JsonPropertyName("webhook_status_code")]
    public string WebhookStatusCode { get; set; }

    [JsonPropertyName("webhook_url")]
    public string WebhookUrl { get; set; }

    [JsonPropertyName("words")]
    public List<Word> Words { get; set; }
}

创建 API 客户端类

我们需要创建 API 客户端类,用于与 API 进行交互。这个类将处理对 API 的 HTTP 请求,并对强类型对象执行 API 响应的反序列化。

将一个新的类文件添加到项目的根目录,命名为 AssemblyAIApiClient.cs

首先,我们将向该类添加一些属性来保存我们的 API 设置,并添加一个构造函数来设置这些属性。

public class AssemblyAIApiClient
{
    private readonly string _apiToken;
    private readonly string _baseUrl;
    private readonly HttpClient _httpClient;

    public AssemblyAIApiClient(string apiToken, string baseUrl)
    {
        _apiToken = apiToken;
        _baseUrl = baseUrl;
        _httpClient = new HttpClient() { BaseAddress = new Uri(_baseUrl) };
        _httpClient.DefaultRequestHeaders.Add("Authorization", _apiToken);
    }
}

添加一个简单的 helper 方法,该方法将支持向 API 发送 HTTP 请求并反序列化其响应。

/// <summary>
/// Helper method that sends the <see cref="HttpRequestMessage"/> using the configured <see cref="HttpClient"/>
/// </summary>
/// <typeparam name="TModel">The type to deserialized the response to.</typeparam>
/// <param name="request">The http request message</param>
/// <returns>The deserialized response object</returns>
private async Task<TModel> SendRequestAsync<TModel>(HttpRequestMessage request)
{
    var response = await _httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<TModel>(json);
}

向类中添加一个新方法,用于将文件从磁盘上传到 API。

/// <summary>
/// Uploads a local audio file to the API
/// </summary>
/// <param name="filePath">The file path of the audio file</param>
/// <returns>A <see cref="Task{UploadAudioResponse}"/></returns>
public Task<UploadAudioResponse> UploadFileAsync(string filePath)
{
    if (!File.Exists(filePath))
    {
        throw new FileNotFoundException(filePath);
    }

    var request = new HttpRequestMessage(HttpMethod.Post, "v2/upload");
    request.Headers.Add("Transer-Encoding", "chunked");

    var fileReader = File.OpenRead(filePath);
    request.Content = new StreamContent(fileReader);

    return SendRequestAsync<UploadAudioResponse>(request);
} 

接下来,添加一个新方法,通过 URL 提交音频文件来启动转录过程。

/// <summary>
/// Submits an audio file at the specified URL for transcription
/// </summary>
/// <param name="audioUrl">The URL where the file is hosted</param>
/// <returns>A <see cref="Task{TranscriptionResponse}"/></returns>
public Task<TranscriptionResponse> SubmitAudioFileAsync(string audioUrl)
{
    var request = new HttpRequestMessage(HttpMethod.Post, "v2/transcript");
    var requestBody = JsonSerializer.Serialize(new TranscriptionRequest { AudioUrl = audioUrl });
    request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");

    return SendRequestAsync<TranscriptionResponse>(request);
}

要添加的最后一个方法将通过唯一的标识符检索转录。此方法返回的对象将指示转录的状态以及转录完成后的结果。

/// <summary>
/// Retrieves the transcription
/// </summary>
/// <param name="id">The id of the transcription</param>
/// <returns>A <see cref="Task{TranscriptionResponse}"/></returns>
public Task<TranscriptionResponse> GetTranscriptionAsync(string id)
{
    var request = new HttpRequestMessage(HttpMethod.Get, $"v2/transcript/{id}");

    return SendRequestAsync<TranscriptionResponse>(request);
}

指挥交响乐

既然我们的 API 客户端支持与我们需要的 3 个 API 端点进行交互,那么接下来就只需要对操作进行排序了。

剩余的代码将更新 Program.cs 项目的根目录。让我们从在 Program.cs 内添加几个常量开始。

private const string API_KEY = "<YOUR_API_KEY>";
private const string API_URL = "https://api.assemblyai.com/";

// relative or absolute file path
private const string AUDIO_FILE_PATH = @"./audio/rogan.mp4";

您需要确保添加 API 令牌来代替 <YOUR_API_KEY> ,并指定要转录的音频文件的路径。

注意:音频文件路径可以是相对的,也可以是绝对的。使用相对路径时,将音频文件添加到 VS 项目并将其构建动作设置为“内容- >【总是复制】 可能会更容易

最后一步是将编排逻辑添加到 Main 方法的主体中。编排代码将:

  1. 上传音频文件
  2. 提交文件进行转录
  3. 轮询转录的状态,直到转录完成。
  4. 执行后处理(输出一些关于转录的元数据)。
static void Main(string[] args)
{
    var client = new AssemblyAIApiClient(API_KEY, API_URL);

    // Upload file
    var uploadResult = client.UploadFileAsync(AUDIO_FILE_PATH).GetAwaiter().GetResult();

    // Submit file for transcription
    var submissionResult = client.SubmitAudioFileAsync(uploadResult.UploadUrl).GetAwaiter().GetResult();
    Console.WriteLine($"File {submissionResult.Id} in status {submissionResult.Status}");

    // Query status of transcription until it's `completed`
    TranscriptionResponse result = client.GetTranscriptionAsync(submissionResult.Id).GetAwaiter().GetResult();
    while (!result.Status.Equals("completed"))
    {
        Console.WriteLine($"File {result.Id} in status {result.Status}");
        Thread.Sleep(15000);
        result = client.GetTranscriptionAsync(submissionResult.Id).GetAwaiter().GetResult();
    }

    // Perform post-procesing with the result of the transcription
    Console.WriteLine($"File {result.Id} in status {result.Status}");
    Console.WriteLine($"{result.Words?.Count} words transcribed.");

    foreach (var word in result.Words)
    {
        Console.WriteLine($"Word: '{word.Text}' at {word.Start} with {word.Confidence * 100}% confidence.");
    }

    Console.ReadLine();
}

运行应用程序

如果您愿意,您可以下载本演练中使用的示例文件。您只需在您的浏览器中下载该文件,或者使用诸如 cURLwget 等工具从以下地址下载音频文件:

https://assemblyai-pub-cdn.s3-us-west-2.amazonaws.com/Audio+to+Text+3.mp4

假设您的项目已经构建并运行,您应该会得到类似如下的输出:

File 3ypvt33ft-0c4f-4bf6-b57a-c5b37b83453c in status queued
File 3ypvt33ft-0c4f-4bf6-b57a-c5b37b83453c in status processing
File 3ypvt33ft-0c4f-4bf6-b57a-c5b37b83453c in status completed
50 words transcribed.
Word: 'He's' at 0 with 92% confidence.
Word: 'a' at 550 with 52% confidence.
Word: 'fighter' at 1790 with 96% confidence.
Word: 'jet' at 2820 with 89% confidence.
Word: 'pilot' at 3150 with 94% confidence.
Word: 'for' at 3550 with 95% confidence.
Word: 'the' at 3910 with 99% confidence.
Word: 'Navy.' at 4090 with 98% confidence.
Word: 'And' at 4390 with 98% confidence.
Word: 'he' at 5080 with 100% confidence.
...

就是这样!您已经使用 AssemblyAI API 成功上传并转录了一个文件。

您可以在 AssemblyAI API 文档中阅读更多关于这里使用的端点以及一些更高级的特性的信息!

在 PyTorch 中构建端到端语音识别模型

原文:https://www.assemblyai.com/blog/end-to-end-speech-recognition-pytorch/

深度学习随着端到端模型的引入,改变了自动语音识别的游戏。这些模型接收音频,并直接输出转录。今天最受欢迎的两个端到端模型是百度的 Deep Speech 和谷歌的 Listen Attend Spell (LAS)。深度语音和 LAS 都是基于递归神经网络(RNN)的架构,具有不同的建模语音识别的方法。深度语音使用连接主义者时间分类(CTC)损失函数来预测语音转录本。LAS 使用序列对网络体系结构进行序列预测。

这些模型通过利用深度学习系统从大型数据集学习的能力,简化了语音识别管道。有了足够的数据,从理论上讲,你应该能够建立一个超级健壮的语音识别模型,它可以考虑语音中的所有细微差别,而不必花费大量的时间和精力手工设计声学特征,或者处理更老式的 GMM-HMM 模型架构中的复杂管道。

深度学习是一个快速发展的领域,深度语音和 LAS 风格的架构已经很快过时了。你可以在下面的最新进展部分了解该行业的发展方向。

如何在 PyTorch 中构建自己的端到端语音识别模型

让我们看看如何在 PyTorch 中构建自己的端到端语音识别模型。我们将构建的模型受到 Deep Speech 2(百度对其著名模型的第二次修订)的启发,并对架构进行了一些个人改进。该模型的输出将是字符的概率矩阵,我们将使用该概率矩阵来解码音频中最有可能说出的字符。你可以找到完整的代码,也可以在谷歌合作实验室运行它。

准备数据管道

数据是语音识别最重要的方面之一。我们将原始音频波转换成 Mel 光谱图。

你可以从这篇精彩的文章这里阅读更多关于这一转变的细节。在这篇文章中,你可以把 Mel 声谱图想象成声音的图像。

为了处理音频数据,我们将使用一个非常有用的工具torchaudio,它是 PyTorch 团队专门为音频数据构建的库。我们将在 LibriSpeech 的子集上进行训练,这是一个从有声读物中获得的阅读英语语音数据的语料库,包括 100 个小时的转录音频数据。您可以使用torchaudio轻松下载该数据集:

import torchaudio

train_dataset = torchaudio.datasets.LIBRISPEECH("./", url="train-clean-100", download=True)
test_dataset = torchaudio.datasets.LIBRISPEECH("./", url="test-clean", download=True) 

数据集的每个样本都包含波形、音频的采样率、话语/标签以及关于样本的更多元数据。你可以从源代码这里查看每个样本的样子。

数据增长-预测增长

数据扩充是一种用于人为增加数据集多样性以增加数据集大小的技术。当数据不足或模型过拟合时,这种策略尤其有用。对于语音识别,您可以使用标准的增强技术,如改变音调、速度、注入噪声以及给音频数据添加混响。

我们发现谱图增强(SpecAugment)是一种更简单、更有效的方法。SpecAugment 首次在论文 SpecAugment:一种用于自动语音识别的简单数据扩充方法中介绍,其中作者发现,简单地剪切连续时间和频率维度的随机块可以显著提高模型的泛化能力!

在 PyTorch 中,您可以使用torchaudio函数FrequencyMasking屏蔽频率维度,使用TimeMasking屏蔽时间维度。

torchaudio.transforms.FrequencyMasking()
torchaudio.transforms.TimeMasking()

现在我们有了数据,我们需要将音频转换成 Mel 频谱图,并将每个音频样本的字符标签映射成整数标签:

char_map_str = """
 ' 0
 <SPACE> 1
 a 2
 b 3
 c 4
 d 5
 e 6
 f 7
 g 8
 h 9
 i 10
 j 11
 k 12
 l 13
 m 14
 n 15
 o 16
 p 17
 q 18
 r 19
 s 20
 t 21
 u 22
 v 23
 w 24
 x 25
 y 26
 z 27
 """

 class TextTransform:
    """Maps characters to integers and vice versa"""
    def __init__(self):
        char_map_str = char_map_str
        self.char_map = {}
        self.index_map = {}
        for line in char_map_str.strip().split('\n'):
            ch, index = line.split()
            self.char_map[ch] = int(index)
            self.index_map[int(index)] = ch
        self.index_map[1] = ' '

    def text_to_int(self, text):
        """ Use a character map and convert text to an integer sequence """
        int_sequence = []
        for c in text:
            if c == ' ':
                ch = self.char_map['']
            else:
                ch = self.char_map[c]
            int_sequence.append(ch)
        return int_sequence

    def int_to_text(self, labels):
        """ Use a character map and convert integer labels to an text sequence """
        string = []
        for i in labels:
            string.append(self.index_map[i])
        return ''.join(string).replace('', ' ')

train_audio_transforms = nn.Sequential(
    torchaudio.transforms.MelSpectrogram(sample_rate=16000, n_mels=128),
    torchaudio.transforms.FrequencyMasking(freq_mask_param=15),
    torchaudio.transforms.TimeMasking(time_mask_param=35)
)

valid_audio_transforms = torchaudio.transforms.MelSpectrogram()

text_transform = TextTransform()

def data_processing(data, data_type="train"):
    spectrograms = []
    labels = []
    input_lengths = []
    label_lengths = []
    for (waveform, _, utterance, _, _, _) in data:
        if data_type == 'train':
            spec = train_audio_transforms(waveform).squeeze(0).transpose(0, 1)
        else:
            spec = valid_audio_transforms(waveform).squeeze(0).transpose(0, 1)
        spectrograms.append(spec)
        label = torch.Tensor(text_transform.text_to_int(utterance.lower()))
        labels.append(label)
        input_lengths.append(spec.shape[0]//2)
        label_lengths.append(len(label))

    spectrograms = nn.utils.rnn.pad_sequence(spectrograms, batch_first=True).unsqueeze(1).transpose(2, 3)
    labels = nn.utils.rnn.pad_sequence(labels, batch_first=True)

    return spectrograms, labels, input_lengths, label_lengths

定义模型-深度演讲 2(但更好)

我们的模型将类似于 Deep Speech 2 架构。该模型将具有两个主要的神经网络模块- N 层残差卷积神经网络(ResCNN)以学习相关的音频特征,以及一组双向递归神经网络(BiRNN)以利用所学习的 ResCNN 音频特征。该模型以一个完全连接的层结束,该层用于在每个时间步长对角色进行分类。

卷积神经网络(CNN)擅长提取抽象特征,我们将把同样的特征提取能力应用于音频频谱图。我们选择使用残留的 CNN 层,而不是普通的 CNN 层。残差连接(又名跳过连接)首次在论文图像识别的深度残差学习中介绍,作者发现,如果你将这些连接添加到 CNN 中,你可以建立具有良好精度增益的真正深度网络。添加这些剩余连接也有助于模型更快地学习和更好地概括。论文可视化神经网络的损失景观表明,具有剩余连接的网络具有“更平坦”的损失表面,使模型更容易浏览损失景观,并找到更低和更可概括的最小值。

递归神经网络(RNN)天生擅长序列建模问题。RNN 一步一步地处理音频特征,对每一帧进行预测,同时使用前一帧的上下文。我们使用 BiRNN 是因为我们不仅想要每一步之前的帧的上下文,还想要每一步之后的帧的上下文。这可以帮助模型做出更好的预测,因为在做出预测之前,音频中的每一帧都将具有更多信息。我们使用 RNN 的门控循环单元(GRU 的)变体,因为它比 LSTM 的需要更少的计算资源,并且在某些情况下工作得一样好。

该模型输出字符的概率矩阵,我们将使用该矩阵输入到我们的解码器中,以提取模型认为最有可能说出的字符。

class CNNLayerNorm(nn.Module):
    """Layer normalization built for cnns input"""
    def __init__(self, n_feats):
        super(CNNLayerNorm, self).__init__()
        self.layer_norm = nn.LayerNorm(n_feats)

    def forward(self, x):
        # x (batch, channel, feature, time)
        x = x.transpose(2, 3).contiguous() # (batch, channel, time, feature)
        x = self.layer_norm(x)
        return x.transpose(2, 3).contiguous() # (batch, channel, feature, time) 

class ResidualCNN(nn.Module):
    """Residual CNN inspired by https://arxiv.org/pdf/1603.05027.pdf
        except with layer norm instead of batch norm
    """
    def __init__(self, in_channels, out_channels, kernel, stride, dropout, n_feats):
        super(ResidualCNN, self).__init__()

        self.cnn1 = nn.Conv2d(in_channels, out_channels, kernel, stride, padding=kernel//2)
        self.cnn2 = nn.Conv2d(out_channels, out_channels, kernel, stride, padding=kernel//2)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.layer_norm1 = CNNLayerNorm(n_feats)
        self.layer_norm2 = CNNLayerNorm(n_feats)

    def forward(self, x):
        residual = x  # (batch, channel, feature, time)
        x = self.layer_norm1(x)
        x = F.gelu(x)
        x = self.dropout1(x)
        x = self.cnn1(x)
        x = self.layer_norm2(x)
        x = F.gelu(x)
        x = self.dropout2(x)
        x = self.cnn2(x)
        x += residual
        return x # (batch, channel, feature, time)

class BidirectionalGRU(nn.Module):

    def __init__(self, rnn_dim, hidden_size, dropout, batch_first):
        super(BidirectionalGRU, self).__init__()

        self.BiGRU = nn.GRU(
            input_size=rnn_dim, hidden_size=hidden_size,
            num_layers=1, batch_first=batch_first, bidirectional=True)
        self.layer_norm = nn.LayerNorm(rnn_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = self.layer_norm(x)
        x = F.gelu(x)
        x, _ = self.BiGRU(x)
        x = self.dropout(x)
        return x

class SpeechRecognitionModel(nn.Module):
    """Speech Recognition Model Inspired by DeepSpeech 2"""

    def __init__(self, n_cnn_layers, n_rnn_layers, rnn_dim, n_class, n_feats, stride=2, dropout=0.1):
        super(SpeechRecognitionModel, self).__init__()
        n_feats = n_feats//2
        self.cnn = nn.Conv2d(1, 32, 3, stride=stride, padding=3//2)  # cnn for extracting heirachal features

        # n residual cnn layers with filter size of 32
        self.rescnn_layers = nn.Sequential(*[
            ResidualCNN(32, 32, kernel=3, stride=1, dropout=dropout, n_feats=n_feats) 
            for _ in range(n_cnn_layers)
        ])
        self.fully_connected = nn.Linear(n_feats*32, rnn_dim)
        self.birnn_layers = nn.Sequential(*[
            BidirectionalGRU(rnn_dim=rnn_dim if i==0 else rnn_dim*2,
                             hidden_size=rnn_dim, dropout=dropout, batch_first=i==0)
            for i in range(n_rnn_layers)
        ])
        self.classifier = nn.Sequential(
            nn.Linear(rnn_dim*2, rnn_dim),  # birnn returns rnn_dim*2
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(rnn_dim, n_class)
        )

    def forward(self, x):
        x = self.cnn(x)
        x = self.rescnn_layers(x)
        sizes = x.size()
        x = x.view(sizes[0], sizes[1] * sizes[2], sizes[3])  # (batch, feature, time)
        x = x.transpose(1, 2) # (batch, time, feature)
        x = self.fully_connected(x)
        x = self.birnn_layers(x)
        x = self.classifier(x)
        return x

选择正确的优化器和调度器——AdamW 具有超强的收敛性

优化器和学习率计划在让我们的模型收敛到最佳点方面起着非常重要的作用。选择正确的优化器和调度器还可以节省您的计算时间,并帮助您的模型更好地推广到现实世界的用例。对于我们的模型,我们将使用 AdamW单周期学习率调度器Adam 是一个广泛使用的优化器,它可以帮助你的模型更快地收敛,从而节省计算时间,但它因不能像 随机梯度下降 又名 SGD 一样泛化而臭名昭著。

AdamW 最早是在解耦权重衰减正则化中引入的,被认为是对 Adam 的“修正”。文章指出,原 Adam 算法有一个错误的权重衰减实现,对此 AdamW 试图修复。这个修正有助于解决亚当 的泛化问题。

单周期学习率调度器超收敛:使用大学习率的神经网络非常快速的训练一文中首次介绍。这篇论文表明,使用一个简单的技巧,你可以训练神经网络的速度提高一个数量级,同时保持它们的泛化能力。你从一个低的学习速率开始,升温到一个大的最大学习速率,然后线性衰减到你最初开始的同一点。

因为最大学习率比最小学习率高很多,所以您也获得了一些正则化的好处,这有助于您的模型在数据集较少的情况下更好地进行概化。

对于 PyTorch,这两个方法已经是包的一部分了。

optimizer = optim.AdamW(model.parameters(), hparams['learning_rate'])
scheduler = optim.lr_scheduler.OneCycleLR(optimizer,
	max_lr=hparams['learning_rate'],
	steps_per_epoch=int(len(train_loader)),
	epochs=hparams['epochs'],
	anneal_strategy='linear')

CTC 丢失功能-将音频与抄本对齐

我们的模型将被训练来预测我们馈入模型的声谱图中每一帧(即时间步长)字母表中所有字符的概率分布。

传统的语音识别模型会要求你在训练之前将抄本文本与音频对齐,并且该模型会被训练来预测特定帧处的特定标签。

CTC 丢失功能的创新之处在于它允许我们跳过这一步。我们的模型将在训练过程中学习对齐脚本本身。这其中的关键是由 CTC 引入的“空白”标签,它使模型能够说某个音频帧没有产生一个字符。你可以从这篇出色的帖子中看到关于 CTC 及其工作原理的更详细的解释。

PyTorch 还内置了 CTC 丢失功能。

criterion = nn.CTCLoss(blank=28).to(device)

评估你的语音模型

在评估您的语音识别模型时,行业标准使用单词错误率(WER)作为度量标准。单词错误率确实如其所言——它获取模型输出的转录和真实转录,并测量它们之间的错误。你可以在这里看到这是如何实现的。另一个有用的指标叫做字符错误率(CER)。CER 测量模型输出和真实标签之间的字符误差。这些指标有助于衡量模型的性能。

对于本教程,我们将使用“贪婪”解码方法将模型的输出处理成字符,这些字符可以组合起来创建副本。“贪婪”解码器接收模型输出,该模型输出是字符的 softmax 概率矩阵,并且对于每个时间步长(谱图帧),它选择具有最高概率的标签。如果标签是空白标签,我们会将其从最终抄本中删除。

def GreedyDecoder(output, labels, label_lengths, blank_label=28, collapse_repeated=True):
    arg_maxes = torch.argmax(output, dim=2)
    decodes = []
    targets = []
    for i, args in enumerate(arg_maxes):
        decode = []
        targets.append(text_transform.int_to_text(labels[i][:label_lengths[i]].tolist()))
        for j, index in enumerate(args):
            if index != blank_label:
                if collapse_repeated and j != 0 and index == args[j -1]:
                    continue
                decode.append(index.item())
        decodes.append(text_transform.int_to_text(decode))
    return decodes, targets

使用 Comet.ml 训练和监控您的实验

Comet.ml 提供了一个平台,允许深度学习研究人员跟踪、比较、解释和优化他们的实验和模型。Comet.ml 提高了我们在 AssemblyAI 的工作效率,我们强烈推荐团队使用这个平台进行任何类型的数据科学实验。Comet.ml 设置起来超级简单。只需几行代码就能完成。

# initialize experiment object
experiment = Experiment(api_key=comet_api_key, project_name=project_name)
experiment.set_name(exp_name)

# track metrics
experiment.log_metric('loss', loss.item())

Comet.ml 为您提供了一个非常高效的仪表板,您可以在其中查看和跟踪模型的进度。

comet-img-1.png

您可以使用 Comet 来跟踪度量、代码、超级参数、您的模型的图表,以及其他许多东西!Comet 提供的一个非常方便的特性是能够将您的实验与许多其他实验进行比较。

Comet 有丰富的特性集,我们不会在这里一一介绍,但是我们强烈建议使用它来提高生产率和健全性。最后,这是我们培训脚本的其余部分。

class IterMeter(object):
    """keeps track of total iterations"""
    def __init__(self):
        self.val = 0

    def step(self):
        self.val += 1

    def get(self):
        return self.val

def train(model, device, train_loader, criterion, optimizer, scheduler, epoch, iter_meter, experiment):
    model.train()
    data_len = len(train_loader.dataset)
    with experiment.train():
        for batch_idx, _data in enumerate(train_loader):
            spectrograms, labels, input_lengths, label_lengths = _data 
            spectrograms, labels = spectrograms.to(device), labels.to(device)

            optimizer.zero_grad()

            output = model(spectrograms)  # (batch, time, n_class)
            output = F.log_softmax(output, dim=2)
            output = output.transpose(0, 1) # (time, batch, n_class)

            loss = criterion(output, labels, input_lengths, label_lengths)
            loss.backward()

            experiment.log_metric('loss', loss.item(), step=iter_meter.get())
            experiment.log_metric('learning_rate', scheduler.get_lr(), step=iter_meter.get())

            optimizer.step()
            scheduler.step()
            iter_meter.step()
            if batch_idx % 100 == 0 or batch_idx == data_len:
                print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                    epoch, batch_idx * len(spectrograms), data_len,
                    100\. * batch_idx / len(train_loader), loss.item()))

def test(model, device, test_loader, criterion, epoch, iter_meter, experiment):
    print('\nevaluating…')
    model.eval()
    test_loss = 0
    test_cer, test_wer = [], []
    with experiment.test():
        with torch.no_grad():
            for I, _data in enumerate(test_loader):
                spectrograms, labels, input_lengths, label_lengths = _data 
                spectrograms, labels = spectrograms.to(device), labels.to(device)

                output = model(spectrograms)  # (batch, time, n_class)
                output = F.log_softmax(output, dim=2)
                output = output.transpose(0, 1) # (time, batch, n_class)

                loss = criterion(output, labels, input_lengths, label_lengths)
                test_loss += loss.item() / len(test_loader)

                decoded_preds, decoded_targets = GreedyDecoder(output.transpose(0, 1), labels, label_lengths)
                for j in range(len(decoded_preds)):
                    test_cer.append(cer(decoded_targets[j], decoded_preds[j]))
                    test_wer.append(wer(decoded_targets[j], decoded_preds[j]))

    avg_cer = sum(test_cer)/len(test_cer)
    avg_wer = sum(test_wer)/len(test_wer)
    experiment.log_metric('test_loss', test_loss, step=iter_meter.get())
    experiment.log_metric('cer', avg_cer, step=iter_meter.get())
    experiment.log_metric('wer', avg_wer, step=iter_meter.get())

    print('Test set: Average loss: {:.4f}, Average CER: {:4f} Average WER: {:.4f}\n'.format(test_loss, avg_cer, avg_wer))

def main(learning_rate=5e-4, batch_size=20, epochs=10,
        train_url="train-clean-100", test_url="test-clean",
        experiment=Experiment(api_key='dummy_key', disabled=True)):

    hparams = {
        "n_cnn_layers": 3,
        "n_rnn_layers": 5,
        "rnn_dim": 512,
        "n_class": 29,
        "n_feats": 128,
        "stride": 2,
        "dropout": 0.1,
        "learning_rate": learning_rate,
        "batch_size": batch_size,
        "epochs": epochs
    }

    experiment.log_parameters(hparams)

    use_cuda = torch.cuda.is_available()
    torch.manual_seed(7)
    device = torch.device("cuda" if use_cuda else "cpu")

    if not os.path.isdir("./data"):
        os.makedirs("./data")

    train_dataset = torchaudio.datasets.LIBRISPEECH("./data", url=train_url, download=True)
    test_dataset = torchaudio.datasets.LIBRISPEECH("./data", url=test_url, download=True)

    kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}
    train_loader = data.DataLoader(dataset=train_dataset,
                                batch_size=hparams['batch_size'],
                                shuffle=True,
                                collate_fn=lambda x: data_processing(x, 'train'),
                                **kwargs)
    test_loader = data.DataLoader(dataset=test_dataset,
                                batch_size=hparams['batch_size'],
                                shuffle=False,
                                collate_fn=lambda x: data_processing(x, 'valid'),
                                **kwargs)

    model = SpeechRecognitionModel(
        hparams['n_cnn_layers'], hparams['n_rnn_layers'], hparams['rnn_dim'],
        hparams['n_class'], hparams['n_feats'], hparams['stride'], hparams['dropout']
        ).to(device)

    print(model)
    print('Num Model Parameters', sum([param.nelement() for param in model.parameters()]))

    optimizer = optim.AdamW(model.parameters(), hparams['learning_rate'])
    criterion = nn.CTCLoss(blank=28).to(device)
    scheduler = optim.lr_scheduler.OneCycleLR(optimizer, max_lr=hparams['learning_rate'], 
                                            steps_per_epoch=int(len(train_loader)),
                                            epochs=hparams['epochs'],
                                            anneal_strategy='linear')

    iter_meter = IterMeter()
    for epoch in range(1, epochs + 1):
        train(model, device, train_loader, criterion, optimizer, scheduler, epoch, iter_meter, experiment)
        test(model, device, test_loader, criterion, epoch, iter_meter, experiment)

训练函数根据完整的数据时段训练模型。测试函数在每个时期之后对测试数据评估模型。它得到模型的检验损失以及 cer 和 wer。您现在可以在 Google 联合实验室的 GPU 支持下开始运行训练脚本。

如何提高准确率

语音识别需要大量的数据和计算资源。展示的示例是在 LibriSpeech (100 小时的音频)的子集和单个 GPU 上训练的。为了获得最先进的结果,你需要对分布在许多机器上的数十个 GPU 上的数千小时的数据进行分布式训练。

另一种大幅提高精度的方法是使用语言模型和 CTC 波束搜索算法解码 CTC 概率矩阵。CTC 类型的模型非常依赖于这个解码过程来获得好的结果。幸运的是,有一个方便的开源库可以让你这么做。

本教程更容易理解,因此与 BERT(3.4 亿个参数)相比,它是一个相对较小的模型(2300 万个参数)。看起来你的网络越大,它的表现就越好,尽管回报是递减的。然而,正如 OpenAI 的研究 Deep Double Descent 所证明的那样,更大的模型等同于更好的性能并不总是如此。

这个模型有 3 个剩余的 CNN 层和 5 个双向 GRU 层,这应该允许您在一个至少有 11GB 内存的 GPU 上训练一个合理的批量大小。您可以调整 main 函数中的一些 hyper 参数,以根据您的用例和计算可用性来减少或增加模型大小。

深度学习语音识别的最新进展

深度学习是一个快速发展的领域。似乎你不能一个星期没有一些新的技术得到最先进的结果。在语音识别的世界里,这里有一些值得探索的东西。

变形金刚(电影名)

变形金刚席卷了自然语言处理世界!在论文中第一次介绍了注意力是你所需要的全部,变形金刚已经采取和修改击败了几乎所有现有的 NLP 任务,淘汰了 RNN 的类型架构。转换器看到序列数据的完整上下文的能力也可以转换成语音。

无监督预训练

如果你密切关注深度学习,你可能听说过伯特、GPT 和 GPT2。这些 Transformer 模型首先与带有未标记文本数据的语言建模任务相关,然后在各种 NLP 任务上进行微调,并获得最先进的结果!在预训练期间,模型学习语言统计的一些基本知识,并利用这种能力在其他任务中表现出色。我们相信这项技术在语音数据上也有很大的前景。

单词片段模型

我们上面定义的模型输出字符。这样做的一些好处是,在对语音进行推理时,该模型不必担心词汇以外的单词。所以对于单词 c h a t,每个字符都有自己的标签。使用字符的缺点是效率低,而且模型容易出错,因为您一次只能预测一个字符。已经探索了使用整个单词作为标签,并取得了一定程度的成功。使用这种方法,整个单词 chat 将成为标签。但是使用全词,您将不得不保存所有可能词汇的索引来进行预测,这是内存低效的,在预测期间可能会用完词汇。最佳点是使用单词片段或子单词单元作为标签。您可以将单词分割成子单词单元,并将其用作标签,而不是单个标签的字符,即 ch at。这解决了词汇表之外的问题,并且更有效,因为它需要比使用字符更少的解码步骤,并且不需要所有可能单词的索引。单词片段已经成功地用于许多 NLP 模型,如 BERT,并且也可以自然地用于语音识别问题。

想找更多这样的教程?

订阅我们的时事通讯!

[Subscribe Now](Subscribe to our newsletter!)

用于 NLP 的微调变压器

原文:https://www.assemblyai.com/blog/fine-tuning-transformers-for-nlp/

在本教程中,我们将展示如何针对两个不同的 NLP 问题(情感分析和重复问题检测)微调两个不同的 transformer 模型,BERT 和 DistilBERT。

你可以在我们的 Colab 笔记本中看到一个完整的工作示例,你还可以在 HuggingFace 上和训练过的模特一起玩。让我们跳进来吧!

介绍

自从在中首次开发并发布以来,论文变形金刚已经完全重新定义了自然语言处理(NLP) 领域,在许多任务上设置了最先进的技术,如问题回答、语言生成和命名实体识别。在这里,我们不会过多地讨论什么是变压器,而是如何应用和训练它们来帮助完成手头的一些任务。关于变形金刚,在概念上要记住的主要事情是,它们非常擅长处理顺序数据(文本、语音等)。),它们充当编码器-解码器框架,其中数据由编码器映射到一些表示空间,然后通过解码器映射到输出,并且它们非常适合并行处理硬件(GPU)。

自然语言处理领域中的转换器已经在大量文本数据上接受了训练,这使得它们能够很好地理解语言的语法和语义。例如,发表在通过生成性预训练提高语言理解中的原始 GPT 模型是在超过 7000 本独特的未出版书籍的 BooksCorpus 上训练的。同样,在论文中发布的著名的 BERT 模型:用于语言理解的深度双向转换器的预训练在图书语料库和英语维基百科上都进行了训练。对于对深入研究变压器的神经网络架构感兴趣的读者来说,原文图解变压器是两个很好的资源。

变形金刚背后的主要好处,也是我们将在本博客的其余部分看到的,是一旦预先训练好的变形金刚可以针对众多下游任务进行快速微调,并且通常开箱后表现良好。这主要是因为转换器已经理解语言,这允许训练集中于学习如何做问题回答、语言生成、命名实体识别,或者某人为他们的模型想到的任何其他目标。

资料组

斯坦福情感树库 v2 (SST2)

第一个任务模型将被训练用于情感分析。情感分析是 NLP 领域的一个长期基准,其目标是能够检测某些文本是正面的、负面的还是介于两者之间。这有许多用例,例如根据客户评论检测产品是被正面还是负面看待,或者根据推文检测候选人的支持率是高还是低。我们将用来训练情感分析模型的数据集是斯坦福情感树库 v2 (SST2) 数据集,它包含 11,855 个电影评论句子。这个任务和数据集是通用语言理解评估(GLUE)基准的一部分,该基准是用于训练、评估和分析自然语言理解系统的资源集合。

以下是该数据集中的一些例子,其中接近 0 的数字代表消极情绪,接近 1 的数字代表积极情绪:

Quora 问题对(QQP)

第二个任务模型将被训练用于重复问题检测。同样,这个任务也有各种用例,比如从 Quora 平台上删除类似的问题,以减少用户之间的混淆。我们将用来训练重复问题检测模型的数据集是 Quora 问题对数据集。这个任务/数据集也是 GLUE 基准的一部分。

此数据集中的许多示例(其中 0 表示非重复,1 表示重复)如下:

模型

将为上述任务/数据集训练两种不同的基于变压器的架构。预训练的模型将从包含 60 多种不同网络类型的hugging face Transformers Repo中加载。拥抱脸模型中心也是一个很好的资源,它包含超过 10,000 个不同的预训练变形金刚,可以执行各种各样的任务。

蒸馏啤酒

我们将训练的第一个架构是 DistilBERT ,它是开源的,在 DistilBERT 中发布,是 BERT 的一个精简版本:更小、更快、更便宜、更轻。这个转换器比 BERT 小 40%,同时保留了 97%的语言理解能力,速度也快了 60%。我们将为 SST2 和 QQP 数据集训练这种架构。

伯特

我们将训练的第二个架构是 BERT 发表在 BERT:用于语言理解的深度双向转换器的预训练中。这是第一个真正展示了这种模型类型在 NLP 领域中的威力的 Transformer,它在发布时在 11 个不同的 NLP 任务上设置了一个新的艺术状态。

我们将仅为 SST2 数据集训练这种架构。

微调

有了这个背景,现在让我们看看代码,并训练/微调这些模型!这里我们使用了 PyTorch 深度学习框架,并且只包含 SST2 数据集的代码。要自己运行这段代码,请随意查看我们的 Colab 笔记本,它可以很容易地进行编辑,以适应 QQP 数据集。

创建数据集

首先让我们为 SST2 创建我们的 PyTorch 数据集类。该类定义了三个重要的函数,目的如下:

  • __init__ :初始化数据集中的类和加载
  • __len__ :获取数据集的长度
  • __getitem__ :从数据集中随机选择一项
#Libraries needed
import torch
from torch.utils.data import Dataset

#PyTorch dataset class
class  SST_Dataset(Dataset):
	#Name: 		__init__
	#Purpose: 	init function to load the dataset
	#Inputs: 	dataset -> dataset
	#Outputs: 	none
	def  __init__(self, dataset):
		self.dataset = dataset
		return

	#Name: 		__len__
	#Purpose: 	get the length of the dataset
	#Inputs: 	none
	#Outputs: 	length -> length of the dataset
	def  __len__(self):
		return  len(self.dataset)

	#Name: 		__getitem__
	#Purpose: 	get a random text segment and its label from the dataset
	#Inputs: 	idx -> index of the random text segment to load
	#Outputs: 	text -> text segment
	# 			label -> sentiment score
	def  __getitem__(self, idx):
		text =  self.dataset[idx]['sentence']
		label = torch.zeros(2)
		label[round(self.dataset[idx]['label'])] =  1
		return text, label

助手功能

接下来,让我们创建几个助手函数来完成诸如获取 GPU、向其传输数据等任务。神经网络,尤其是基于变压器的神经网络,几乎总是在 GPU 等加速器硬件上训练得更快,因此如果模型和数据可用,将它们发送到那里进行处理是至关重要的。由于可以利用并行处理能力,这允许显著的训练加速。

#Name: 		get_gpu
#Purpose: 	checks if a GPU device is avaliable
#Input: 	none
#Output: 	GPU -> GPU device if applicable, none if not
def  get_gpu():
	#Check if a GPU is avaliable and if so return it
	GPU  =  None
	if torch.cuda.is_available():
		print("Using GPU")
		GPU  = torch.device("cuda")
	else:
		print("No GPU device avaliable! Using CPU")
	return  GPU

#Name: 		transfer_device
#Purpose: 	transfers model / data to the GPU devie if present
#Inputs: 	GPU -> GPU device if applicable, none if not
# 		 	data -> data to transfer
#Output: 	data -> data that has been transferred if applicable
def  transfer_device(GPU, data):
	if(GPU  !=  None):
		data = data.to(GPU)
	return data

#Name: 		count_correct
#Purpose: 	count the number of correct model predictions in a batch
#Inputs: 	predictions -> model predictions
#		 	targets -> target labels
#Outputs: 	correct -> number of correct model predictions
def  count_correct(predictions, targets):
	#Create variables to store the number of correct predictions along with the index of the prediction in the batch
	correct =  0
	index =  0

	#Loop across all predictions in the batch and count the number correct
	while(index <  len(predictions)):
		#Convert the prediction and target to lists
		prediction =  list(predictions[index])
		target =  list(targets[index])

		#Get the max index indicating the truth value from the prediction and target
		prediction_index = prediction.index(max(prediction))
		target_index = target.index(max(target))

		#If the max indices are the same increment correct
		if(prediction_index == target_index):
			correct +=  1
		index +=  1
	return correct

定义损失函数

现在我们将定义损失函数...由于我们正在训练一个分类器来预测一个句子是积极还是消极的情绪,或者如果两个问题是重复的,我们将使用二元交叉熵损失函数。这一损失背后的数学原理是:

这里 y 是真正的标签(0 或 1),而 p(y) 是我们的模型预测。通过最小化这个值,我们的网络学会做出更准确的预测。

#Name: 		binary_cross_entropy
#Purpose: 	defines binary cross entropy loss function
#Inputs: 	predictions -> model predictions
# 			targets -> target labels
#Outputs: 	loss -> loss value
def  binary_cross_entropy(predictions, targets):
	loss =  -(targets * torch.log(predictions) + (1  - targets) * torch.log(1  - predictions))
	loss = torch.mean(loss)
	return loss

模型培训/评估

接下来,让我们编写核心培训/评估逻辑来微调和测试我们的模型,该模型包含 3 个主要功能:

  • train_model
  • train
  • evaluate

train_model函数的工作方式是首先评估验证集上的预训练模型,并在进行任何训练之前计算性能。然后,该函数在三个时期内循环,同时在训练集上训练模型,并在验证集上评估其性能。一个历元本质上是某个数据集中所有数据的循环。

train功能通过为一个时期训练模型来运行。请注意,在进行任何训练之前,我们的模型会进入训练模式,向 PyTorch 指示需要存储梯度以进行参数更新。然后,通过迭代 PyTorch 数据加载器来循环一个时期中的所有批次。然后,每一批都通过记号赋予器,允许这些记号被发送到模型用于情感得分预测。按照事实上的 PyTorch 训练循环设置,计算损失值,将优化器置零,根据损失导出梯度,并通过采取优化器步骤来更新模型。

evaluate函数的设置与train相似,只是删除了最终优化器归零、梯度推导和优化器步骤,因为模型不应在验证集上训练。这两个函数之间的其他区别是,这里我们的模型被设置为评估模式,这允许更快的推断,因为梯度不需要被存储。

内置在trainevaluate函数中的是对count_correct的调用,该函数计算每批正确的情感分数预测的数量,从而允许跨整个数据集得出最终的准确度分数。还要注意的是,softmax在模型的输出中被调用,将分数映射到概率。

import torch.nn.functional as F 

#Name: 		train_model
#Purpose: 	train the model while evaluating its performance
#Inputs: 	GPU -> GPU device to train / evaluate on
# 			train_dataloader -> training set dataloader
# 			dev_dataloader -> development set dataloader
# 			tokenizer -> text tokenizer for model
# 			model -> model to train / evaluate
# 			optimizer -> optimizer to use to update model parameters
# 			criterion -> criterion to use to compute loss values
#Outputs: 	model -> model after training
def  train_model(GPU, train_dataloader, dev_dataloader, tokenizer, model, optimizer, criterion):
	#Evaluate the performance of the model before training
	valid_loss, valid_accuracy = evaluate(GPU, dev_dataloader, tokenizer, model, criterion)
	print("Pre-training validation loss: "+str(valid_loss)+" --- Accuracy: "+str(valid_accuracy))
	print()

	#Train the model across 3 epochs and evaluate its performance
	for epoch in  range(3):
		model, train_loss, train_accuracy = train(GPU, train_dataloader, tokenizer, model, optimizer, criterion)
		valid_loss, valid_accuracy = evaluate(GPU, dev_dataloader, tokenizer, model, criterion)

		#Print performance stats
		print(" ", end="\r")
		print("Epoch: "+str(epoch+1))
		print("Training loss: "+str(train_loss)+" --- Accuracy: "+str(train_accuracy))
		print("Validation loss: "+str(valid_loss)+" --- Accuracy: "+str(valid_accuracy))
		print()
	return model
#Name: 		train
#Purpose: 	train the model over 1 epoch
#Inputs: 	GPU -> GPU device to train on
# 			dataloader -> dataloader
# 			tokenizer -> text tokenizer for model
# 			model -> model to train
# 			optimizer -> optimizer to use to update model parameters
# 			criterion -> criterion to use to compute loss values
#Outputs: 	model -> model after training over the epoch
# 			average_loss -> average loss over the epoch
# 			accuracy -> accuracy over the epoch
def  train(GPU, dataloader, tokenizer, model, optimizer, criterion):
	#Place the network in training mode, create a variable to store the total loss, and create a variable to store the total number of correct predictions
	model.train()
	total_loss =  0
	total_correct =  0

	#Loop through all batches in the dataloader
	for batch_number, (texts, labels) in  enumerate(dataloader):
		#Tokenize the text segments, get the model predictions, compute the loss, and add the loss to the total loss
		tokenized_segments = tokenizer(texts, return_tensors="pt", padding=True, truncation=True)
		tokenized_segments_input_ids, tokenized_segments_attention_mask = tokenized_segments.input_ids, tokenized_segments.attention_mask
		model_predictions = F.softmax(model(input_ids=transfer_device(GPU, tokenized_segments_input_ids), attention_mask=transfer_device(GPU, tokenized_segments_attention_mask))['logits'], dim=1)
		loss = criterion(model_predictions, transfer_device(GPU, labels))
		total_loss += loss.item()

		#Count the number of correct predictions by the model in the batch and add this to the total correct
		correct = count_correct(model_predictions.cpu().detach().numpy(), labels.numpy())
		total_correct += correct

		#Zero the optimizer, compute the gradients, and update the model parameters
		optimizer.zero_grad()
		loss.backward()
		optimizer.step()
		print("Training batch index: "+str(batch_number)+"/"+str(len(dataloader))+  " ( "+str(batch_number/len(dataloader)*100)+"% )", end='\r')

	#Compute the average loss and accuracy across the epoch
	average_loss = total_loss /  len(dataloader)
	accuracy = total_correct / dataloader.dataset.__len__()
	return model, average_loss, accuracy
#Name: 		evaluate
#Purpose: 	evaluate the model over 1 epoch
#Inputs: 	GPU -> GPU device to evaluate on
# 			dataloader -> dataloader
# 			tokenizer -> text tokenizer for model
# 			model -> model to evaluate
# 			criterion -> criterion to use to compute loss values
#Outputs: 	average_loss -> average loss over the epoch
# 			accuracy -> accuracy over the epoch
def  evaluate(GPU, dataloader, tokenizer, model, criterion):
	#Place the network in evaluation mode, create a variable to store the total loss, and create a variable to store the total number of correct predictions
	model.eval()
	total_loss =  0
	total_correct =  0

	#Loop through all batches in the dataloader
	for batch_number, (texts, labels) in  enumerate(dataloader):
		#Tokenize the text segments, get the model predictions, compute the loss, and add the loss to the total loss
		tokenized_segments = tokenizer(texts, return_tensors="pt", padding=True, truncation=True)
		tokenized_segments_input_ids, tokenized_segments_attention_mask = tokenized_segments.input_ids, tokenized_segments.attention_mask
		model_predictions = F.softmax(model(input_ids=transfer_device(GPU, tokenized_segments_input_ids), attention_mask=transfer_device(GPU, tokenized_segments_attention_mask))['logits'], dim=1)
		loss = criterion(model_predictions, transfer_device(GPU, labels))
		total_loss += loss.item()

		#Count the number of correct predictions by the model in the batch and add this to the total correct
		correct = count_correct(model_predictions.cpu().detach().numpy(), labels.numpy())
		total_correct += correct
		print("Evaluation batch index: "+str(batch_number)+"/"+str(len(dataloader))+  " ( "+str(batch_number/len(dataloader)*100)+"% )", end='\r')

	#Compute the average loss and accuracy across the epoch
	average_loss = total_loss /  len(dataloader)
	accuracy = total_correct / dataloader.dataset.__len__()
	return average_loss, accuracy

把所有的放在一起

现在,我们已经定义了训练模型所需的所有函数,我们终于可以对其进行微调,看看会发生什么!请注意,SST2 是存储在 HuggingFace Datasets 中的许多数据集之一,这使得它非常容易加载和使用。

from datasets import load_dataset
from torch.utils.data import DataLoader
from transformers import AdamW
from transformers import DistilBertTokenizer, DistilBertForSequenceClassification

#Get the GPU device if it exists, load the SST-2 dataset, and create PyTorch datasets and dataloaders for the training and validation sets
GPU  = get_gpu()
sst2_dataset = load_dataset("sst", "default")
train_dataset = SST_Dataset(sst2_dataset['train'])
valid_dataset = SST_Dataset(sst2_dataset['validation'])
train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)
valid_dataloader = DataLoader(valid_dataset, batch_size=32, shuffle=False, num_workers=4)

#Create the tokenizer, model, optimizer, and criterion
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
model = transfer_device(GPU, DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased'))
optimizer = AdamW(model.parameters(), lr=2e-5)
criterion = binary_cross_entropy

#Train and save the model
model = train_model(GPU, train_dataloader, valid_dataloader, tokenizer, model, optimizer, criterion)
torch.save({
	'tokenizer': tokenizer,
	'model_state_dict': model.state_dict()},
	model+".pt")
return

要训练 BERT 模型而不是 DistilBERT,请使用以下内容:

from transformers import BertTokenizer, BertForSequenceClassification
tokenizer = BertTokenizer.from_pretrained('bert-large-uncased')
model = transfer_device(GPU, DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased')) 

结果

SST2

在对 SST2 数据集上的 DistilBERT 和 BERT 进行 3 个时期的微调后,在验证和测试集上评估了它们的性能。下面的数字是 8 次单独模型训练运行的平均准确度分数:

资料组 蒸馏啤酒 伯特
确认 83.992% 87.443%
试验 85.056% 86.997%

QQP

尽管 QQP 的培训代码没有在这个博客中显示,我们的 Colab 笔记本可以很容易地修改以容纳这些数据。要做的主要更改是编辑 PyTorch 数据集以处理模型的两个文本输入,问题 1 和问题 2,以及调整标记化器的输入。对 QQP 上的 DistilBERT 进行 3 个时期的微调,并在验证集上进行性能评估,结果如下所示。请注意,准确性分数是在 8 次单独的模型训练运行中平均得出的:

资料组 蒸馏啤酒
确认 89.909%

结论

在这篇博客中,我们学习了如何在下游任务中微调转换器,特别是情感分析和重复问题检测。通过微调预先训练好的变压器,可以节省大量时间,而且开箱后性能通常很高。相比之下,从头开始训练需要更长的时间,并使用更多数量级的计算和能源来达到相同的性能指标。

请随意查看本博客附带的 Colab 笔记本来亲自进行实验!此外,如果您想下载并使用我们开发的模型,可以在以下位置的 HuggingFace 模型中心找到它们:

资源

*https://arxiv.org/abs/1706.03762
*https://jalammar.github.io/illustrated-transformer/
*https://en.wikipedia.org/wiki/Natural_language_processing
*https://S3-us-west-2 . Amazon AWS . com/open ai-assets/research-covers/language-unsupervised/language _ understand _ paper . pdf
*https://arxiv.org/abs/1810.04805
*https://nlp.stanford.edu/sentiment/
*https://gluebenchmark.com
*

ESPnet 入门

原文:https://www.assemblyai.com/blog/getting-started-with-espnet/

也许比起深度学习的其他子领域,语音处理在历史上需要更多的专业知识。例如,将声学语音波转换成文本人类语言是一个困难的问题,这在以前需要几个移动部分,如声学模型、发音模型、语言模型等。

作为 2010 年代深度学习热潮的结果,端到端神经语音识别系统已经变得可以训练,并因此得到广泛使用。像 ESPnet 这样的工具包为研究这样的模型提供了基础,然后这些模型可以被训练并放到外行人的手中,进一步使以前以专家为主的语音处理领域民主化。

在本文中,我们将介绍 ESPnet 并演示如何使用预训练模型进行自动语音识别(ASR),然后将结果与其他一些流行的 ASR 框架/平台进行比较。让我们开始吧!

介绍

如上所述,ESPnet 是一个端到端的语音处理工具包,涵盖了广泛的语音处理应用程序,包括:

  1. 自动语音识别(ASR)
  2. 文本到语音转换
  3. 扬声器二进制化
  4. 语音翻译,以及
  5. 语音增强

ESPnet 最初建立在另一个开源语音处理工具包 Kaldi 之上。随着 ESPnet 2 的发布,完全不再需要 Kaldi,尽管 ESPnet 2 保持了 Kaldi 风格的数据准备以保持一致性。

想了解更多关于卡尔迪的信息?

查看我们简单易懂的 Kaldi 入门指南。

Check it out

ESPnet 的伟大之处在于,它是用 Python 编写的,这是许多机器学习从业者和爱好者的首选语言,而不是用 C++编写的 Kaldi。Kaldi 和 ESPnet 都提供了几个预训练的模型,使得将语音处理的元素结合到应用程序中变得更加容易。现在让我们看看如何在 Python 中利用这样的模型。

如何使用预训练的 ESPnet 模型进行语音识别

在本教程中,我们将转录葛底斯堡演说第一行的音频文件。下面附上音频剪辑:

Gettysburg Address (First Line)0:00/0:101×

不幸的是,ESPnet 可能很难工作,所以对于本教程来说,我们将使用 Ubuntu 18.04.06 LTS ,它的 ISO 可以在这里找到。您可以使用此 ISO 在 VMware 中启动虚拟机,并完全按照本教程进行操作。

首先,我们需要安装一些必要的软件包。打开终端并执行以下命令:

sudo apt update
yes | sudo apt upgrade
yes | sudo apt install ffmpeg sox cmake git virtualenv libfreetype6-dev gcc
yes | sudo apt-get install python3-dev libxml2-dev libxmlsec1-dev

现在,克隆教程报告并导航到其中:

git clone https://github.com/AssemblyAI-Examples/intro-to-espnet.git
cd intro-to-espnet

接下来,创建一个 virtualenv 并安装所有必需的软件包:

virtualenv venv -p /usr/bin/python3.6
source venv/bin/activate
pip install --no-cache-dir -r requirements.txt

现在我们已经完成了设置,我们可以通过简单地执行speech2text.py将我们的音频文件转录成一行:

python3 -m speech2text.py

基本事实转录和生成转录都将被打印到控制台。我们将在下面检查speech2text.py是如何工作的,但首先让我们看看结果,并将它们与其他 ASR 选项进行比较。

结果

下面可以看到地面真相转录,以供参考:

地面实况; 真值(机器学习)

八十七年前,我们的先辈在这个大陆上创建了一个新国家,它孕育于自由之中,奉行人人生而平等的原则

接下来我们有由最好的 ESPnet 模型生成的脚本(也是speech2text.py中的默认脚本)——一个基于 Transformer 的模型,它产生了 0 个错误:

转录(模型 1) - 0% WER

八十七年前,我们的先辈在这个大陆上创建了一个新国家,它孕育于自由之中,奉行人人生而平等的原则

我们还展示了由另一个预训练的 ESPnet 模型生成的副本,在这种情况下,一个基于 Conformer 的模型产生了 8 个错误:

转录(模型 2) - 27% WER

七年前,我们的父辈肉汤和这片大陆上一个新的国家为了自由献身于人人生而平等的主张

ESPnet 提供了许多其他可以探索的预训练模型。这些模型中的一些如上所述进行了尝试,但是产生了非常不准确的结果,因此被省略。

ESPnet 与其他 ASR 解决方案相比如何?

为了了解这种预训练的 ESPnet 模型与其他 ASR 解决方案相比如何,我们首先考虑 Kaldi,我们使用其预训练的LibriSpeech ASR 模型。关于使用这个模型和开始使用 Kaldi 语音识别的完整指南,请参见链接文章。

LibriSpeech 预训练 Kaldi 模型产生 5 个误差,对应于 17%的 WER 。相应的文字记录如下:

七年前,我们的先辈们创建了 T2,在这个大陆上诞生了一个新的国家——T4、自由、T7——奉行人人生而平等的原则

除了 ESPnet 和 Kaldi 的开源选项,我们还测试了几个云语音转文本 API进行比较。结果总结在下表中:

| ASR 框架/平台 | 单词错误率 |
|


|
| ESPnet -模型 1 | 0% |
| ESPnet -模型 2 | 27% |
| 卡尔迪-李伯希模型 | 17% |
| 组装 | 0% |
| 亚马逊转录 | 0% |
| 谷歌云语音转文本 | 0% |

正如我们所看到的,从 WER 的角度来看,一些 ESPnet 模型非常强大,可以与其他产品竞争。对于简单的转录,ESPnet 是一个很好的开源选择。对于那些寻求低错误率之外的选项的人来说,比如高可读性或音频智能洞察力,其他选项可能更有成效。

代码分解

现在,我们已经看到了如何通过调用一个简单的 Python 脚本来使用 ESPnet 预训练模型,现在让我们来探索脚本本身,以了解幕后发生的事情。注意,speech2text.py的 B 元素来自官方 ESPnet Jupyter 笔记本

进口

首先,像往常一样,我们导入所有我们需要的包。ESPnet 的伟大之处在于,当只使用预训练的模型执行推理时,可以通过带有拥抱脸和/或芝诺多的 API 非常容易地使用它。espnet_model_zoo包提供了这个功能——我们导入了ModelDownloader,它提供了一个简单的方法来获取存储在 Hugging Face 或 Zenodo 中的模型。espnet2包为我们提供了适当的二进制文件,我们将需要使用提取的模型进行推理。

import subprocess as s
import os
import string
import soundfile
from espnet_model_zoo.downloader import ModelDownloader
from espnet2.bin.asr_inference import Speech2Text 

下载预训练模型

在所有导入之后,是时候下载预训练模型了。变量tag存储了我们想要下载的模型的位置,如这里的所列。默认情况下,我们使用最好的模型,尽管另外两个模型已经包含在内并被注释掉,以便那些好奇的人尝试其他模型。

指定要下载的模型后,我们创建一个Speech2Text推理对象,使用ModelDownloader类的一个实例来下载由tag指定的模型。剩下的参数只是简单地指定了推理的参数——更多细节见这里的。注意如果用 GPU 的话device可以改成cuda

# BEST MODEL:
tag = "Shinji Watanabe/librispeech_asr_train_asr_transformer_e18_raw_bpe_sp_valid.acc.best"
# SECOND BEST MODEL:
#tag = 'Shinji Watanabe/spgispeech_asr_train_asr_conformer6_n_fft512_hop_length256_raw_en_unnorm_bpe5000_valid.acc.ave'
# EXTREMELY POOR MODEL:
#tag = "kamo-naoyuki/wsj"

d = ModelDownloader()
speech2text = Speech2Text(
    **d.download_and_unpack(tag),
    device="cpu", #cuda if gpu
    minlenratio=0.0,
    maxlenratio=0.0,
    ctc_weight=0.3,
    beam_size=10,
    batch_size=0,
    nbest=1
)

助手功能

在使用我们的推理对象生成脚本之前,我们创建了两个辅助函数。首先,我们创建text_normalizer(),它返回大写的输入文本并去掉所有标点符号。

def text_normalizer(text):
    text = text.upper()
    return text.translate(str.maketrans('', '', string.punctuation))

其他详细信息

text_normalizer()方法利用了一个翻译表。当用三个参数初始化时,转换表将第一个参数中的字符顺序映射到第二个参数中的字符,并将最后一个参数中的字符映射到空字符。未列出的字符被映射到它们自己。然后,translate函数将翻译表应用于目标字符串。

下面可以看到一个例子。首先,我们看到了创建翻译表的语法,相应的对象显示在框中。然后,我们看到将这个转换表应用于字符串abcDEF的语法,以及在框中可视化的应用它的效果,最终得到字符串A1cf

回到text_normalizer函数,我们观察到它首先用text = text.upper()将文本转换成大写。接下来,它用str.maketrans('', '', string.punctuation)创建一个翻译表,简单地将所有标点符号映射到空字符。最后return text.translate(...)使用这个翻译表去掉我们的文本并返回它。

其次,我们创建一个函数,在给定音频文件路径的情况下生成并返回脚本。创建一个函数来获取抄本。soundfile.read()读入我们的音频数据,然后speech2text预测语音。我们用nbests[0]分离出最佳预测,然后连同音频文件的采样率一起返回副本。

def get_transcript(path):
    speech, rate = soundfile.read(path)
    nbests = speech2text(speech)
    text, *_ = nbests[0]
    return text, rate

转录音频文件

定义好助手函数后,我们就可以转录音频文件了。对于我们的audio文件夹中的每个文件,我们通过get_transcript()函数传递该文件,如果我们的模型只能处理.wav文件,那么首先用 ffmpeg 将其转换为.wav文件。

path = os.path.join(os.getcwd(), 'egs')
files = os.listdir(path+'/audio')

for file in files:
    if not file.endswith('.wav'):
        # Convert to .wav and change file extension to .wav
        os.chdir(path+'/audio')
        s.run(f"ffmpeg -i {file} {file.split('.')[0]}.wav", shell=True, check=True, universal_newlines=False)
        os.chdir('../..')
        file = file.split('.')[0]+'.wav'

        # Transcribe and delete generated file
        text, est_rate = get_transcript(f'{path}/audio/{file}')
        os.remove(f'{path}/audio/{file}')
    else:
        text, est_rate = get_transcript(f'{path}/audio/{file}')

转录后,我们读入每个音频文件的text文件夹中的真实对应转录,然后打印出地面真实和假设转录:

 # Fetch true transcript
    label_file = file.split('.')[0]+'.txt'
    with open(f'{path}/text/{label_file}', 'r') as f:
        true_text = f.readline()
    # Print true transcript and hypothesis
    print(f"\n\nReference text: {true_text}")
    print(f"ASR hypothesis: {text_normalizer(text)}\n\n")

这就是全部了!在 ESPnet 中使用预先训练的模型非常简单,只需几行代码就可以将语音处理交给普通用户。

最后的话

虽然我们在本教程中只研究了 ASR,但 ESPnet 已经为各种其他任务预先训练了模型,包括文本到语音转换、说话者二进制化和降噪。查看 ESPnet 文档了解更多信息。

要获得更多关于 NLP 和一般机器学习的指南和教程,请随意查看我们的博客,或者关注我们的时事通讯。

Follow the AssemblyAI Newsletter

C#和中的 HttpClientFactory 入门。网络 5

原文:https://www.assemblyai.com/blog/getting-started-with-httpclientfactory-in-c-sharp-and-net-5/

HttpClientFactory已经逛完了。NET 生态系统已经有几年了。

在本帖中,我们将看看HttpClientFactory的 3 个基本实现:

  • 基础
  • 命名的
  • 打字

这篇文章中的所有代码都可以在这个 GitHub 库中找到。

首先,让我们了解一下HttpClient是什么,如何将HttpClientFactory融入图片中,以及为什么我们想要使用它。

‍Why 我就不能用**HttpClient**吗?

你当然可以只使用HttpClient,但是让我们更仔细地看看HttpClient实际上在做什么。

来自微软文档的下图显示了客户端和处理程序之间的关系:

Diagram showing how HTTPCLient passes the request and response through messagehandlers and the HTTPClientHandler to the network

默认处理程序HttpClientHandler实际上通过网络发送请求,并从服务器获得响应。如果需要,可以将自定义消息处理程序插入到客户端管道中。

在您的 web 项目中,您可能熟悉类似于下面的HttpClient实现:

using (var httpClient = new HttpClient())
  {
      httpClient.BaseAddress = new Uri("https://api.twilio.com/2010-04-01/");
      httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

      var responseMessage = await httpClient
              .GetAsync(apiEndPoint);   
  }

当孤立地考虑时,上面的代码本身并没有什么错误。

HttpClient实现了IDisposable,看到许多开发者在一个using块中创建它。那么一旦超出范围,就会被妥善处理。然而,在博客“你使用 HttpClient 是错误的,它破坏了你的软件”在“ASP。你可以看到这不是一个理想的前进方式。

现在因为HttpClient一般是线程安全的。这可能会导致一些开发人员将客户端创建为单例,这实际上是 HttpClient 文档中的建议。

然而,这也带来了一系列的问题。例如,客户端将在应用程序的生命周期内保持连接打开,它不会遵守 DNS TTL 设置,并且它永远不会获得 DNS 更新。所以这也不是一个完美的解决方案。

什么是 HttpClientFactory?‍

主要和权威的参考资料来自微软文档

该文档将HttpClientFactory描述为“一个自以为是的工厂,用于创建在您的应用程序中使用的HttpClient实例”。

中间件的创建部分是为了解决上面提到的一些问题。

HttpClientFactory 主要功能

那么有哪些关键特征呢?再次利用参考资料,我们可以了解到:

  • 它提供了一个命名和配置我们的HttpClients的中心位置。
  • 委托HttpClient中的处理程序并实现基于 Polly 的中间件,以利用 Polly 的弹性策略。
  • HTTP 客户端在工厂中注册
  • 可以使用 Polly 处理程序,该处理程序允许使用 Polly 策略以获得更好的弹性
  • 它管理HttpClientHandlers的生命周期,以避免前面提到的试图自己处理HttpClient生命周期的问题

在这篇博客中,我们将看看使用 AssemblyAI API : 实现HttpClientFactory 的几种方法

  • 直接使用HttpClientFactory
  • 使用命名客户端
  • 使用类型化客户端

基本 HttpClientFactory 用法

一个基本的HttpClientFactory可以通过依赖注入来实例化。

首先,我们需要将下面的代码添加到ConfigureServices方法中的Startup类中:

// File: Startup.cs
public class Startup
{
// Code deleted for brevity.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.
    }
// Code deleted for brevity.
}

然后,在您希望进行 REST 调用的班级中,您可以请求HttpClientFactory。我们将在 API 控制器中展示这一点:

[ApiController]
public class ExampleController : Controller
{
        private readonly IHttpClientFactory _clientFactory;

	public ExampleController(IHttpClientFactory clientFactory)
	{
		_clientFactory = clientFactory;
	}

	[HttpPost]
	public async Task Basic()
	{
		var json = new
		{
			audio_url = "https://s3-us-west-2.amazonaws.com/blog.assemblyai.com/audio/8-7-2018-post/7510.mp3"
		};

		string jsonString = JsonSerializer.Serialize(json);
		var payload =  new StringContent(jsonString, Encoding.UTF8, "application/json");

		var client = _clientFactory.CreateClient();
		client.DefaultRequestHeaders.Add("Authorization", "YOUR_ASSEMBLY_AI_TOKEN");

		HttpResponseMessage response = await client.PostAsync("https://api.assemblyai.com/v2/transcript", payload);			

		string responseJson = await response.Content.ReadAsStringAsync();

	}
}

客户端工厂将处理上面代码中创建的HttpClient的处置。

命名的 HttpClientFactory 客户端

前面的代码使您能够在使用时定义HttpClient。然而,客户端配置,比如 API 端点 URL,可以在Startup类中的一个地方定义。然后可以通过依赖注入来请求指定的客户端,使代码更加可重用。如果您有许多不同的HttpClient用途,或者如果您有许多配置不同的客户端,这是非常好的。

下面的代码展示了我们如何在Startup类中命名和定义一个HttpClient

// File: Startup.cs
public class Startup
{
// Code deleted for brevity.

    public void ConfigureServices(IServiceCollection services)
    {
     services.AddHttpClient("AssemblyAIClient", client =>
			{
				client.BaseAddress = new Uri("https://api.assemblyai.com/v2/transcript");
				client.DefaultRequestHeaders.Add("Authorization", "YOUR_ASSEMBLY_AI_TOKEN");
			});

 // Remaining code deleted for brevity.
    }
// Code deleted for brevity.
}

命名的HttpClient以与基本方法相似的方式使用,只是这次我们需要从工厂请求命名的实例。我们也使用SendAsync API,所以设置请求有点不同。

[ApiController]
public class NamedController : Controller
{
	private readonly IHttpClientFactory _clientFactory;

	public NamedController(IHttpClientFactory clientFactory)
	{
		_clientFactory = clientFactory;
	}

	[HttpPost]
	public async Task Post()
	{
		var json = new
		{
			audio_url = "https://s3-us-west-2.amazonaws.com/blog.assemblyai.com/audio/8-7-2018-post/7510.mp3"
		};

		string jsonString = JsonSerializer.Serialize(json);
		var payload =  new StringContent(jsonString, Encoding.UTF8, "application/json");

		var client = _clientFactory.CreateClient("AssemblyAIClient");

		var request = new HttpRequestMessage(HttpMethod.Post, string.Empty);

		var response = await client.SendAsync(request);			

		string responseJson = await response.Content.ReadAsStringAsync();

	}
}

每当我们调用_clientFactory.CreateClient("AssemblyAIClient");时,一个带有所有预定义配置的新客户机被创建,因此不需要在发出请求的方法中定义它。

类型化的 HttpClientFactory 客户端

类型化客户端与命名客户端非常相似,但是我们没有使用字符串作为键,而是利用了强类型。这通过避免使用潜在的脆弱字符串改进了我们的代码,也意味着在创建客户端时可以使用智能感知和编译器支持。

类型化客户端是封装所有逻辑的好方法,因此保持了Startup类的整洁,易于阅读和维护。

要创建一个类型化的客户端,我们需要首先创建一个用来封装我们的逻辑的类。在这种情况下,我们称之为AssemblyAiService

public class AssemblyAiService
{
	public HttpClient Client { get; }

	public AssemblyAiService(HttpClient client)
	{
		client.BaseAddress = new Uri("https://api.assemblyai.com/");
		client.DefaultRequestHeaders.Add("Authorization", "YOUR_ASSEMBLY_AI_TOKEN");

		Client = client;
	}

	public async Task<string> UploadAudioFile(StringContent payload)
	{
		HttpResponseMessage response = await Client.PostAsync("v2/transcript", payload);

		string responseJson = await response.Content.ReadAsStringAsync();

		return responseJson;
	}
}

在上面的代码中,我们创建了一个简单的方法,该方法接受StringContent,然后将其发送到 AssemblyAI 端点。该方法返回 JSON 字符串响应。

我们需要在Startup类中配置客户端,就像我们在前面的例子中所做的那样。

// File: Startup.cs
public class Startup
{
// Code deleted for brevity.

    public void ConfigureServices(IServiceCollection services)
    {
services.AddHttpClient<AssemblyAiService>();
 // Remaining code deleted for brevity.
    }
// Code deleted for brevity.
}

‍As 你可以看到,当使用类型化的HttpClient时,阅读和理解添加了什么服务变得容易多了。

控制器中的客户端消费也被很好地封装,将大量样板代码提取到服务中。

[ApiController]
public class TypedController : Controller
{
	private readonly AssemblyAiService _assemblyAiService;

	public TypedController(AssemblyAiService assemblyAiService)
	{
		_assemblyAiService = assemblyAiService;
	}

	[HttpPost]
	public async Task Post()
	{
		var json = new
			{
			audio_url = "https://s3-us-west-2.amazonaws.com/blog.assemblyai.com/audio/8-7-2018-post/7510.mp3"
			};

		string jsonString = JsonSerializer.Serialize(json);
		var payload =  new StringContent(jsonString, Encoding.UTF8, "application/json");

		string responseJson = await _assemblyAiService.UploadAudioFile(payload);

	}
}

‍The 以上的代码也更易测试,因为它更紧密地遵循了坚实的原则

结论

我希望上面的代码能启发你在下一个项目中尝试HttpClientFactory

HttpClientFactories还有很多好处,比如使用 Polly Register 中的策略进行策略管理,以及将第三方库合并到生成的客户端中。

想找更多这样的教程?

订阅我们的时事通讯!

[Subscribe Now](Subscribe to our newsletter!)

拥抱脸的 Gradio 入门

原文:https://www.assemblyai.com/blog/getting-started-with-huggingfaces-gradio/

Gradio 是一个开源库,只使用 Python 就可以构建易于使用、易于共享的应用。它特别适用于机器学习项目,旨在使测试、共享和展示模型变得简单而直观。

在本教程中,我们将学习如何使用 Gradio 来构建以下 音频智能仪表板 ,这将允许我们使用深度学习来分析音频文件或录音,以获得各种智能见解。

提交文件后,您将能够以一种易于理解的方式查看音频智能洞察,如自动突出显示、摘要和检测到的主题:

https://www.assemblyai.com/blog/content/media/2022/09/analysis.mp4

首先,我们将学习如何使用音频智能仪表板并解析结果,然后我们将学习如何使用拥抱脸的 Gradio 构建仪表板,最后我们将使用拥抱脸的空间部署仪表板。我们开始吧!

如何使用仪表板

在这一节中,我们将学习如何使用仪表板提交音频文件进行分析,您可以在这里找到。如果您只想查看示例输出,请跳至查看结果。如果你想直接看实现,跳到用 Gradio 构建仪表板 UI。

启动仪表板

可以通过两种方式之一访问仪表板。第一种方式不需要设置——只需转到项目空间,那里有一个公开的仪表板版本。如果你正在使用这种方法,现在你可以跳到的下一节

第二种访问仪表板的方式需要一些设置,但是允许您在本地运行它以获得更好的性能。你需要为这个方法安装 Python 3gitpipvirtualenv 。只需在终端中执行以下命令,从 GitHub 下载项目存储库并运行仪表板:

# Clone the repo
git clone https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard.git

# Enter repo
cd audio-intelligence-dashboard

# Create virtual environment
virtualenv venv

# Activate virtual environment - MacOS/Linux
source venv/bin/activate

# Activate virtual environment - Windows
.\venv\Scripts\activate.bat

# Install requirements to virtual environment (will take a few minutes)
pip install -r requirements.txt - will take a few minutes

# Enter app directory
cd app

# Runn app (made need to use `python3` instead of `python`)
python app.py

终端将显示一个可以访问该应用程序的本地 URL。在浏览器中输入此 URL 以访问应用程序。

暗/亮模式注释

Gradio 有一个“暗”模式和一个“亮”模式,可以改变应用程序的默认外观。根据您的操作系统和浏览器,仪表板可能会以黑暗模式显示。在 Gradio 3.2 中,无法强制应用程序在 Python 中显示为主题或其他。然而,您可以通过运行app.py?__theme=light附加到 URL 输出来强制使用轻量模式。

音频智能仪表板旨在以亮模式运行,因此如果您在本地运行时看到暗模式,请尝试上述方法强制应用程序以亮模式显示

输入 AssemblyAI 键

将执行我们音频智能分析的“引擎”是 AssemblyAI API ,所以首先我们需要一个 API 密钥——你可以在这里找到你的或者免费得到一个。转到您的 AssemblyAI 仪表盘并点击您的 API 键下的值以复制其值。

https://www.assemblyai.com/blog/content/media/2022/09/api_key.mp4

现在回到音频智能仪表板并将你复制的密钥粘贴到 API 密钥字段。

https://www.assemblyai.com/blog/content/media/2022/09/key_paste.mp4

重要说明

千万不要和任何人分享你的 API 密匙或者上传到 GitHub。您的密钥与您的帐户唯一关联,应该保密。

上传或录制文件

现在我们需要指定我们想要分析的音频。在音频源下,选择音频文件如果您想使用存储在您设备上的音频文件,或者选择录制音频来录制一个要使用的文件。下面我们可以看到一个文件被拖放上传。

https://www.assemblyai.com/blog/content/media/2022/09/audio_upload.mp4

选择转录和音频智能选项

既然我们已经指定了我们想要分析的音频,我们必须指定我们想要运行什么类型的分析。默认情况下选择所有选项,但您可以取消选择任何您不感兴趣的分析。关于每个选项的信息可以在下面的描述下拉列表中找到。

描述

  • 转录选项
  • 音频智能选项
    • 摘要 将生成音频的每个不同部分的摘要。
    • 自动高亮显示 将识别抄本中的关键词和短语。
    • 话题检测 将决定音频中讨论哪些话题。
    • 实体检测 将检测音频中的位置和组织等实体。
    • 情绪分析 将确定与抄本中的每个句子相关联的情绪的极性和程度。
    • PII 编校 将编校个人身份信息(PII)。
    • 内容适度 将决定音频中不同敏感话题的讨论程度。

使服从

指定要分析的音频和要运行的分析后,只需点击提交即可提交音频进行分析。根据音频的长度,这可能需要一些时间。预计分析将占用音频文件持续时间的 15-30%,最少约 20 秒。

检查结果

音频分析完成后,结果将显示在页面底部的选项卡组中。现在让我们来看看每个选项卡,并检查一个示例音频文件的结果。

副本

第一个选项卡显示了最基本的结果,这是音频的格式化副本。

(full resolution available here)

你会注意到有几个值被修改了。特别是, PII 修订功能被设置为修订药物身体伤害姓名,以及金额。因此,

  1. "于是我摔了一跤,摔断了手腕"已经被转录为"于是我摔了一跤#### ## ##### "
  2. "花费了我将近 1000 美元"已经被翻译成"花费了我将近$ number,### "
  3. "我不得不服用 Advil 整整一周"已经被转录为"我不得不服用#####整整一周"

此外,过滤脏话选项是抄本显示“我的手腕仍然感觉像 s*** ”的原因。

扬声器标签

第二个选项卡显示相同的脚本,但这次在脚本中相应的文本上方显示每个音频部分的演讲者。说话人被识别为说话人 A说话人 B 等。在这种情况下,只有一个发言人,因此整个记录归功于发言人 A

(full resolution available here)

自动突出显示

第三个选项卡显示来自自动高亮显示分析的结果。标识并突出显示关键词和短语,突出显示的不透明度对应于该词/短语的相关性。

(full resolution available here)

摘要

第四个选项卡显示汇总结果。音频被自动分割成概念上不同的部分,然后每个部分用粗体标题概括。在这种情况下,只有一个部分被确定为存在,导致只有一个标题,如下所示:

(full resolution available here)

单击任何给定的标题都会显示该部分的更详细摘要:

(full resolution available here)

检测到的主题

第五个选项卡显示在音频中检测到的 IAB 分类中的主题。每个主题的子主题都列在它的下面,缩进排列,其中任何以紫色突出显示的主题都被检测到将在音频中讨论。

(full resolution available here)

例如,主题“研究生教育”和“本科教育”都是检测到的主题,并且属于类别“大学教育”,其属于类别“教育”,尽管后两个主题本身都没有被检测到。

另一方面,“大学篮球”属于类别“大学体育”,这是本身属于类别“体育的检测主题(如其紫色所示),这不是本身检测到的。

情感分析

第六个选项卡显示音频上情感分析的结果。抄本的每一句都是

  1. 突出显示红色负面情绪
  2. 突出显示绿色表示正面情绪,或者
  3. 已经没有突出中立的情绪。

高光的不透明度对应于情绪的程度。

(full resolution available here)

实体检测

第七个选项卡显示在音频中检测到的实体。每个实体类型都以紫色显示,下面列出了检测到的实例。检测到的实体以黄色突出显示,两边用几个单词围绕,表示上下文。

(full resolution available here)

例如,音频中提到的两个实体被检测为“医疗流程-“手术”和“理疗”。这些实体中的每一个都在相应的实体类型下以黄色突出显示。

一些高亮显示为“【修订】”,因为 PII 修订功能已开启。

内容安全

第八个也是最后一个标签显示了在音频中检测到的敏感话题的摘要。检测到的每个敏感主题都列在条形图的左侧,条形图的长度对应于该主题在音频中作为一个整体出现的程度,0 表示最小值,1 表示最大值。

在我们的示例中,我们看到主题“犯罪暴力”、“酒精”和“事故”都被检测到,其中“酒精”是最相关/存在的。

*

(full resolution available here)*

摘要

这就是使用音频智能仪表板对音频文件进行广泛深入分析的全部内容!请随意查看 AssemblyAI 文档以了解以上每个分析的更多信息。

现在我们已经学习了仪表板的用法,让我们学习一下 Gradio 是如何工作的,为我们自己构建仪表板做准备。如果你已经熟悉了 Gradio,并想直接实现它,可以跳到用 Gradio 构建仪表板 UI。

Gradio 简介

在这一部分中,我们将先对 Gradio 进行简要概述,以便在深入研究音频智能仪表板实施之前,了解其结构/理念。

Gradio 概述

让我们来看一个非常基本的 Gradio 示例,其中我们实现了一个基本情感分析模型的应用程序。

*`import gradio as gr

def analyze_sentiment(sentence):
    if "good" in sentence:
        return "positive"
    elif "bad" in sentence:
        return "negative"
    else:
        return "neutral"

app = gr.Interface(fn=analyze_sentiment, inputs="text", outputs="label")

app.launch()`*

首先,我们定义了analyze_sentiment方法,这是我们的机器学习“模型”,它接受一个句子并返回一个情感分类"positive""negative""neutral"。接下来,我们用[gr.Interface()](https://gradio.app/docs/#interface)定义应用程序,指定构成“模型”的函数及其输入和输出类型。最后,我们简单地使用app.launch()来启动应用程序。

运行上述脚本后,您可以访问应用程序的本地 URL 将显示在终端中。将此 URL 粘贴到浏览器中以打开应用程序。然后,您可以放入一个示例输入,并查看模型产生的输出

这就是用 Gradio 构建一个简单应用程序的全部内容!

Gradio 块

对于更复杂的应用,Gradio 提供了一个更低级的 API,允许更精细的控制。我们将使用积木来构建音频智能仪表板,现在让我们熟悉一下吧。上面的应用程序是用下面的代码块重新创建的:

*`import gradio as gr

def analyze_sentiment(sentence):
    if "good" in sentence:
        return "positive"
    elif "bad" in sentence:
        return "negative"
    else:
        return "neutral"

with gr.Blocks() as app:
    sentence = gr.Textbox()
    sentiment = gr.Label()
    submit = gr.Button()

    submit.click(fn=analyze_sentiment,
                 inputs=sentence,
                 outputs=sentiment)

app.launch()`*

所有的代码都是一样的,除了用with gr.Blocks() as app:块替换了app = gr.Interface(...)行。我们在这个块中定义的所有东西都是应用程序的一个组件。我们实例化:

  1. 一个[gr.Textbox()](https://gradio.app/docs/#textbox)元素,为我们输入的句子创建一个文本区域来输入或显示一个字符串
  2. 一个[gr.Label()](https://gradio.app/docs/#label)元素,为我们的输出情感分类创建一个显示分类标签的区域,以及
  3. 一个[gr.Button()](https://gradio.app/docs/#button)元素,创建一个可点击的按钮,用于运行情感分析。

实例化后,组件彼此之间没有任何关系。为了增加功能,我们添加了submit.click()方法,它告诉应用程序当点击submit按钮时该做什么。在我们的例子中,我们想要运行函数analyze_sentiment,使用sentence文本框作为输入,使用sentiment标签作为输出。运行该脚本会产生以下应用程序:

我们现在有了一个功能上等同于我们在上面的中使用的方法的应用程序!

梯度块-更精细的控制

利用积木,我们可以变得更有创意。下面我们看到了上述块示例的修改版本。

*`import gradio as gr

def analyze_sentiment(sentence):
    if "good" in sentence:
        label = "positive"
    elif "bad" in sentence:
        label = "negative"
    else:
        label = "neutral"
    return gr.Label.update(value=label, visible=True)

with gr.Blocks() as app:
    with gr.Row():
        sentence = gr.Textbox(label='Input Sentence',
                              placeholder="Enter a sentence here ...")
        sentiment = gr.Label(visible=False)

    submit = gr.Button()

    submit.click(fn=analyze_sentiment,
                 inputs=sentence,
                 outputs=sentiment)

app.launch()`*

让我们来看看每个修改和它做了什么。

首先,通过向gr.Textbox()构造函数添加关键字参数,我们可以向元素添加一个label,向文本框添加placeholder文本,通过向gr.Label()构造函数添加visible=False,我们可以使标签组件在默认情况下不可见。这是应用程序加载后的样子:

接下来,不是在analyze_sentiment中返回一个字符串,而是返回gr.Label.update()方法,这允许我们更新一个gr.Label的特定属性。在我们的例子中,我们像以前一样更新了value,但是也指定了visible=True,以使组件在运行情感分析之后可见。这是提交一句话进行分析后 app 的样子:

with gr.Row():块告诉应用程序将文本框和标签组件并排放置,而不是默认布局的垂直放置。

现在我们了解了 Gradio 模块的工作原理,我们终于可以自己动手构建音频智能仪表板了!首先,我们将从定义用户界面(UI)开始,然后我们将实现功能。

使用 Gradio 构建仪表板 UI

首先,让我们看一下我们是如何仅使用 Python 定义仪表板(UI)的。回想一下,UI 由[with gr.Blocks() as demo:](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/bd160b7601a99535d0e9b332ccb1b90042e70b3d/app/app.py#L247) 中定义的所有 Gradio 组件组成。如果您对 UI 定义不感兴趣,想直接实现功能,请转到将功能添加到仪表板部分。

我们开始吧!

介绍性 HTML 和 API 密钥

首先,我们使用一些[gr.HTML()](https://gradio.app/docs/#html)组件来构建仪表板上的介绍块,如下所示。gr.HTML()允许我们在仪表板上定义任意的 HTML 元素。虽然仪表盘上显示的 HTML 细节超出了本教程的范围,但相关代码可以在 GitHub 这里找到,供感兴趣的读者参考。

接下来,我们为 API 键创建输入字段,如下所示:

我们创建一个 [gr.Box()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/71866bdb5f1b68b7e407609fd0834fd57dca2114/app/app.py#L273),框来包含它下面的块中定义的元素。gr.Box()的工作方式与gr.Row()相似,我们在梯度块-精细控制部分使用了它。在盒子里,我们放入(1)一个用作标签的gr.HTML()组件,和(2)一个将输入 API 密钥的gr.Textbox():

*`import gradio as gr

with gr.Blocks(css=css) as demo:
    with gr.Box():
        gr.HTML("<p class=\"apikey\">API Key:</p>")
        # API key textbox (password-style)
        api_key = gr.Textbox(label="", elem_id="pw")`*

通过在api_key构造函数中指定elem_id=pw,我们给这个组件一个 HTML ID,我们可以使用用 CSS 对它进行样式化,如下所示:

*`#pw {
  -webkit-text-security: disc;
}`*

这个 CSS 将输入到api_key中的字符编辑成 API 密匙的额外安全层

这种风格也是我们在api_key构造函数中指定label=""并用不同的 HTML 元素标记文本框的原因——否则标签也会被编辑。

CSS 存储在[app/styles.css](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/master/app/styles.css)中,并通过传递给应用程序

*`with open('styles.css', 'r') as f:
    css = f.read()

with gr.Blocks(css=css) as demo:
	# ...`*

格拉迪欧州

接下来,我们定义几个gr.State()组件。这些组件是状态变量,并存储给定用户实例的的值。在函数调用之外定义的任何 Python 变量都是在所有用户之间共享的,而用gr.State()定义的变量只对定义它们的会话是可访问的。更多信息请参见 Gradio 快速入门。******

我们添加了一个存储显示的音频图的状态和保存每个音频源(文件或麦克风)上传的数据的状态,以便在 UI 中的选项之间切换时显示给定源的适当数据。

***`with gr.Blocks(css=css) as demo:
	# ...

	# Gradio states for - plotly Figure object, audio data for file source, and audio data for mic source
    plot = gr.State(px.line(labels={'x':'Time (s)', 'y':''}))
    file_data = gr.State([1, [0]])  # [sample rate, [data]]
    mic_data = gr.State([1, [0]])  # [Sample rate, [data]]

    # Options that the user wants
    selected_tran_opts = gr.State(list(transcription_options_headers.keys()))
    selected_audint_opts = gr.State(list(audio_intelligence_headers.keys()))

    # Current options = selected options - unavailable options for specified language
    current_tran_opts = gr.State([])
    current_audint_opts = gr.State([])`***

然后,我们定义存储用户选择的分析选项和当前分析选项的状态,这些分析选项是所选选项和不可用于所选语言(如果指定)的选项之间的差异。换句话说,如果用户正在转录一个法语音频文件,并希望对其运行 PII 修订,这对于法语不可用,那么这些状态将允许我们禁止对法语的这一选择,但如果所选语言被更改为例如美国英语,则自动启用它,对于美国英语,PII 修订可用**

***`with gr.Blocks(css=css) as demo:
	# ...

	# Gradio states for - plotly Figure object, audio data for file source, and audio data for mic source
    plot = gr.State(px.line(labels={'x':'Time (s)', 'y':''}))
    file_data = gr.State([1, [0]])  # [sample rate, [data]]
    mic_data = gr.State([1, [0]])  # [Sample rate, [data]]

    # Options that the user wants
    selected_tran_opts = gr.State(list(transcription_options_headers.keys()))
    selected_audint_opts = gr.State(list(audio_intelligence_headers.keys()))

    # Current options = selected options - unavailable options for specified language
    current_tran_opts = gr.State([])
    current_audint_opts = gr.State([])`***

有关支持的语言以及每种语言可用的转录和音频智能选项的列表,请查看 AssemblyAI 文档

音频组件

接下来,我们定义用于上传音频的[gr.Audio()](https://gradio.app/docs/#audio)组件。

我们添加了一个 gr.Radio() 组件,其中包含了可以/必须选择的选项。我们在列表中提供可能的值,然后添加标签和默认值。

***`with gr.Blocks(css=css) as demo:
    # ...

    # Selector for audio source
    radio = gr.Radio(["Audio File", "Record Audio"], label="Audio Source", value="Audio File")`***

接下来,我们添加两个 gr.Audio() 组件,用于上传/录制音频。我们添加了一个文件上传组件和一个音频录制组件。我们通过将source="microphone"添加到构造函数中来指定记录组件的源是麦克风,并且我们还使用visible=False指定组件在默认情况下不可见。稍后,我们将使用上面的单选组件来更改哪个组件可见。

***`with gr.Blocks(css=css) as demo:
    # ...

    # Audio object for both file and microphone data
    audio_file = gr.Audio()
    mic_recording = gr.Audio(source="microphone", visible=False)`***

最后,我们添加一个图来显示上传/录制音频的波形。

***`with gr.Blocks(css=css) as demo:
    # ...

    # Audio wave plot
    audio_wave = gr.Plot(plot.value)`***

转录和音频智能选项

接下来我们添加两个gr.CheckboxGroup()组件,它们提供了一组复选框,可以选择(取消选择)它们的任意组合。

我们为转录选项添加了一个 CheckboxGroup,为音频智能选项添加了一个 checkbox group,中间有一个警告(为了简洁起见,已经替换为"<WARNING>")。

***`with gr.Blocks(css=css) as demo:
	# ...

    # Checkbox for transcription options
    transcription_options = gr.CheckboxGroup(
        choices=list(transcription_options_headers.keys()),
        value=list(transcription_options_headers.keys()),
        label="Transcription Options",
    )

    # Warning for using Automatic Language detection
    w = "<WARNING>"
    auto_lang_detect_warning = gr.HTML(w)

    # Checkbox for Audio Intelligence options
    audio_intelligence_selector = gr.CheckboxGroup(
        choices=list(audio_intelligence_headers.keys()),
        value=list(audio_intelligence_headers.keys()),
        label='Audio Intelligence Options'
    )`***

在每种情况下,我们将可能的choices指定为从helpers.py导入的相关字典的键,并且我们将value列为相同的,以默认选择所有选项。

提交和示例按钮

接下来我们添加提交显示示例输出按钮。**

每个按钮都是一个[gr.Button()](https://gradio.app/docs/#button)组件,传递给构造函数的文本显示在 UI 中的按钮上。

***`with gr.Blocks(css=css) as demo:
    # ...

    # Button to submit audio for processing with selected options
    submit = gr.Button('Submit')

    # Button to submit audio for processing with selected options
    example = gr.Button('Show Example Output')`***

结果选项卡

最后,我们添加用于显示结果的选项卡。

每个选项卡都是用with gr.Tab():创建的,其中块中定义的任何东西都成为选项卡的子选项卡。在我们的例子中,我们指定了几种不同类型的组件,主要依赖于gr.HTML()为我们提供显示结果的灵活性。我们还没有看到的唯一使用的组件是 gr.HighlightedText() 组件,它类似于gr.Textbox(),除了它提供了突出显示文本部分的选项。默认情况下,我们使用phl变量使每个标签具有相似的高度。

***`with gr.Blocks(css=css) as demo:
    # ...

    # Results tab group
    phl = 10
    with gr.Tab('Transcript'):
        trans_tab = gr.Textbox(placeholder="Your formatted transcript will appear here ...",
                               lines=phl,
                               max_lines=25,
                               show_label=False)
    with gr.Tab('Speaker Labels'):
        diarization_tab = gr.Textbox(placeholder="Your diarized transcript will appear here ...",
                                     lines=phl,
                                     max_lines=25,
                                     show_label=False)
    with gr.Tab('Auto Highlights'):
        highlights_tab = gr.HighlightedText()
    with gr.Tab('Summary'):
        summary_tab = gr.HTML("<br>"*phl)
    with gr.Tab("Detected Topics"):
        topics_tab = gr.HTML("<br>"*phl)
    with gr.Tab("Sentiment Analysis"):
        sentiment_tab = gr.HTML("<br>"*phl)
    with gr.Tab("Entity Detection"):
        entity_tab = gr.HTML("<br>"*phl)
    with gr.Tab("Content Safety"):
        content_tab = gr.Plot()`***

这就是用 Gradio 构建 UI 的全部内容!现在我们已经完成了 UI 的制作,让我们看看如何添加功能,使它成为一个成熟的音频智能仪表板。

向仪表板添加功能

让我们来看看如何向我们在上一节中定义的 UI 添加功能。所有功能都在主with gr.Blocks(css=css) as demo:模块下类似地定义,如上文 Gradio 模块部分所示,这是使用 Gradio 模块样式时的要求。

音频源选择

首先,我们将实现音频源选择器(即radio)改变时的适当功能。该功能通过change_audio_source()功能实现,该功能根据radio源选择,用存储在会话状态中的适当数据更新绘图。该功能的输入、输出和概述如下图所示。

相应的代码如下所示:

***`def change_audio_source(radio, plot, file_data, mic_data):
    # Empty plot
    plot.update_traces(go.Line(y=[]))

    # Update plot with appropriate data and change visibility of audio components
    if radio == "Audio File":
        sample_rate, audio_data = file_data
        plot.update_traces(go.Line(y=audio_data, x=np.arange(len(audio_data)) / sample_rate))
        return [gr.Audio.update(visible=True), gr.Audio.update(visible=False),
                gr.Plot.update(plot), plot]

    elif radio == "Record Audio":
        sample_rate, audio_data = mic_data
        plot.update_traces(go.Line(y=audio_data, x=np.arange(len(audio_data)) / sample_rate))
        return [gr.Audio.update(visible=False), gr.Audio.update(visible=True),
                gr.Plot.update(plot), plot]

with gr.Blocks(css=css) as demo:
    # ...

    radio.change(fn=change_audio_source,
                 inputs=[radio, plot, file_data, mic_data],
                 outputs=[audio_file, mic_recording, audio_wave, plot])`***

音频文件上传

接下来,我们使用plot_data()函数实现绘制和保存上传的音频数据的功能。如果当前音频已被删除,我们清空绘图并将占位符数据写入file_data状态。否则,新的音频已经上传,我们绘制它并将数据保存到file_data。该功能的输入、输出和概述如下图所示。

类似的设置用于添加麦克风录音的等效功能,而不是上传的文件。相应的代码如下所示:

***`def plot_data(audio_data, plot):

    if audio_data is None:
        sample_rate, audio_data = [0, np.array([])]
        plot.update_traces(go.Line(y=[]))

    else:
        sample_rate, audio_data = audio_data
        plot.update_traces(go.Line(y=audio_data, x=np.arange(len(audio_data))/sample_rate))

    return [gr.Plot.update(plot), [sample_rate, audio_data], plot]

with gr.Blocks(css=css) as demo:
    # ...

    audio_file.change(fn=plot_data,
                      inputs=[audio_file, plot],
                      outputs=[audio_wave, file_data, plot]
                      )
    mic_recording.change(fn=plot_data,
                         inputs=[mic_recording, plot],
                         outputs=[audio_wave, mic_data, plot])`***

语言可见性

接下来,当transcription_options改变时,我们使用set_lang_vis函数实现适当的功能。每当transcription_options复选框组改变时,如果没有选择自动语言检测,则language下拉菜单可见,如果选择了,则不可见。另外,警告相应地变得可见/不可见。该功能的输入、输出和概述如下图所示。**

相应的代码如下所示,其中w是一个全局变量:

***`def set_lang_vis(transcription_options):
    if 'Automatic Language Detection' in transcription_options:
        text = w
        return [gr.Dropdown.update(visible=False),
                gr.Textbox.update(value=text, visible=True)]
    else:
        text = ""
        return [gr.Dropdown.update(visible=True),
                gr.Textbox.update(value=text, visible=False)]

with gr.Blocks(css=css) as demo:
	# ...

    transcription_options.change(
        fn=set_lang_vis,
        inputs=transcription_options,
        outputs=[language, auto_lang_detect_warning])`***

选项验证

接下来,我们使用option_verif函数来实现当所选语言改变时的适当功能。当选择一种语言时,任何当前选择的不可用于该语言的转录/音频智能选项将被自动移除。此外,任何先前选择的选项,如果在先前的语言中没有可用,但在新选择的语言中可用,则会被添加回来。该功能的输入、输出和概述如下图所示。**

相应的代码如下所示:

***`def option_verif(language, selected_tran_opts, selected_audint_opts):
	# Get unavailable options for the language
    not_available_tran, not_available_audint = get_unavailable_opts(language)

    # Set difference
    current_tran_opts = list(set(selected_tran_opts) - set(not_available_tran))
    current_audint_opts = list(set(selected_audint_opts) - set(not_available_audint))

    # Update CheckboxGroups and States
    return [gr.CheckboxGroup.update(current_tran_opts),
            gr.CheckboxGroup.update(current_audint_opts),
            current_tran_opts,
            current_audint_opts]

with gr.Blocks(css=css) as demo:
	# ...

	language.change(
        fn=option_verif,
        inputs=[language, selected_tran_opts, selected_audint_opts],
        outputs=[transcription_options, audio_intelligence_selector, current_tran_opts, current_audint_opts]
    )`***

选项选择

接下来,我们实现了一个过滤器,用于当transcription_options CheckboxGroup 改变时,即当一个选项被选择或取消选择时。在我们的例子中,某些转录选项在某些语言中是不允许的,所以我们需要使用tran_selected来实现一个过滤器来解决这个问题。

传递语言和转录选项,然后确定给定语言的不可用选项。计算转录选项和不可用选项之间的设置差异,该差异成为transcription_options复选框组和selected_tran_opts状态的值。该功能的输入、输出和概述如下图所示。

相应的代码如下所示:

***`def tran_selected(language, transcription_options):
    unavailable, _ = get_unavailable_opts(language)
    selected_tran_opts = list(set(transcription_options) - set(unavailable))

    return [gr.CheckboxGroup.update(selected_tran_opts), selected_tran_opts]

with gr.Blocks(css=css) as demo:
	# ...

	# Selecting Tran options adds it to selected if language allows it
    transcription_options.change(
        fn=tran_selected,
        inputs=[language, transcription_options],
        outputs=[transcription_options, selected_tran_opts]
    )`***

注意,在语言可见性部分,我们已经使用了transcription_options.change()。使用 Gradio,可以为同一个事件实现多个事件监听器。

我们实现了音频智能复选框组的等效功能:

***`def audint_selected(language, audio_intelligence_selector):
    _, unavailable = get_unavailable_opts(language)
    selected_audint_opts = list(set(audio_intelligence_selector) - set(unavailable))

    return [gr.CheckboxGroup.update(selected_audint_opts), selected_audint_opts]

with gr.Blocks(css=css) as demo:
	# ...

    # Selecting audio intelligence options adds it to selected if language allows it
    audio_intelligence_selector.change(
        fn=audint_selected,
        inputs=[language, audio_intelligence_selector],
        outputs=[audio_intelligence_selector, selected_audint_opts]
    )`***

提交和示例输出

最后,我们实现了提交显示示例输出按钮的功能。**

***`with gr.Blocks(css=css) as demo:
	# ...

	submit.click(fn=submit_to_AAI,
                 inputs=[api_key,
                         transcription_options,
                         audio_intelligence_selector,
                         language,
                         radio,
                         audio_file,
                         mic_recording],
                 outputs=[language,
                          trans_tab,
                          diarization_tab,
                          highlights_tab,
                          summary_tab,
                          topics_tab,
                          sentiment_tab,
                          entity_tab,
                          content_tab])`***

让我们依次看看submit_to_AAI的各个部分。下面的许多步骤在 AssemblyAI 和 Python 5 分钟内完成的文章中有更详细的解释,因此感兴趣的读者可以查看该文章以获得更多信息。

首先,我们创建一个存储用户 API 密钥的 HTTP 头。这个头将在所有 API 请求中使用,并让 AssemblyAI API 知道用户被授权请求转录和访问结果。[make_header()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L52)功能可以在helpers.py中找到。

***`def submit_to_AAI(api_key,
                  transcription_options,
                  audio_intelligence_selector,
                  language,
                  radio,
                  audio_file,
                  mic_recording):

    # Make request header
    header = make_header(api_key)`***

接下来,我们创建请求中使用的 JSON。[make_true_dict()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L145)功能将从仪表板中选择的选项映射到 AssemblyAI API 的相应键上。接下来,我们使用[make_final_json](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L158)函数将任何剩余的必需数据添加到 JSON 中。例如,redact_pii需要一个规范,说明要编辑哪种类型的 PII,这个函数添加了值为['drug', 'injury', 'person_name', 'money_amount']redact_pii_policies键。

***`def submit_to_AAI(api_key,
                  transcription_options,
                  audio_intelligence_selector,
                  language,
                  radio,
                  audio_file,
                  mic_recording):

    # ...

    # Map transcription/audio intelligence options to AssemblyAI API request JSON dict
    true_dict = make_true_dict(transcription_options, audio_intelligence_selector)

    final_json, language = make_final_json(true_dict, language)
    final_json = {**true_dict, **final_json}`***

接下来,我们上传在提交时选择的任何来源的状态下存储的音频,然后我们根据我们在仪表板上做出的选择(现在编码在final_json中)请求转录和音频智能分析。

***`def submit_to_AAI(api_key,
                  transcription_options,
                  audio_intelligence_selector,
                  language,
                  radio,
                  audio_file,
                  mic_recording):

    # ...

    # Select which audio to use
    if radio == "Audio File":
        audio_data = audio_file
    elif radio == "Record Audio":
        audio_data = mic_recording

    # Upload the audio
    upload_url = upload_file(audio_data, header, is_file=False)

    # Request transcript
    transcript_response = request_transcript(upload_url, header, **final_json)`***

然后,我们创建一个轮询端点,这将允许我们用[wait_for_completion()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L131)函数告知转录何时完成。转录完成后,我们使用一个 GET 请求来获取结果。

***`def submit_to_AAI(api_key,
                  transcription_options,
                  audio_intelligence_selector,
                  language,
                  radio,
                  audio_file,
                  mic_recording):

    # ...

    # Wait for the transcription to complete
    polling_endpoint = make_polling_endpoint(transcript_response)
    wait_for_completion(polling_endpoint, header)

    # Fetch results JSON
    r = requests.get(polling_endpoint, headers=header, json=final_json).json()`***

最后,我们通过[create_output()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/app.py#L129)函数传递结果响应(和来自[make_paras_string()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L298)的结果),这为在仪表板中显示做好了准备。create_output()的详细情况在下面的里查看。

***`def submit_to_AAI(api_key,
                  transcription_options,
                  audio_intelligence_selector,
                  language,
                  radio,
                  audio_file,
                  mic_recording):

    # ...

    # Fetch paragraphs of transcript
    transc_id = r['id']
    paras = make_paras_string(transc_id, header)
    return create_output(r, paras, language, transcription_options, audio_intelligence_selector)`***

Show Example Output 按钮加载一个存储的 AssemblyAI API 响应,并将其传递给create_output()

***`def example_output(language):
    """Displays example output"""
    with open("../example_data/paras.txt", 'r') as f:
        paras = f.read()

    with open('../example_data/response.json', 'r') as f:
        r = json.load(f)

    return create_output(r, paras, language)

with gr.Blocks(css=css) as demo:
	# ...

	example.click(fn=example_output,
                  inputs=language,
                  outputs=[language,
                           trans_tab,
                           diarization_tab,
                           highlights_tab,
                           summary_tab,
                           topics_tab,
                           sentiment_tab,
                           entity_tab,
                           content_tab])`***

创建输出

如上所述,提交显示示例输出按钮将来自 AssemblyAI API 的响应传递给create_output()函数,该函数处理响应中的信息,以显示在 Gradio 仪表板中。该功能实际上是一系列不同的组件,每个组件都为仪表板中的一个选项卡对象准备输出。**

在下面的小节中,我们将检查每一个组件,检查代码本身或一个概念性的概述,这取决于哪一个对给定的组件更有指导意义。所有输出的示例可以在上面的检查结果部分找到。

副本

原始抄本在[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/master/app/helpers.py)中用[make_paras_string()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L298)创建。该函数首先向 AssemblyAI API 的/paragraphs端点发出一个请求,以获取段落对象的列表,然后来自这些对象的文本被分离出来,并用换行符连接起来。

***`def make_paras_string(transc_id, header):
    endpoint = transcript_endpoint + "/" + transc_id + "/paragraphs"
    paras = requests.get(endpoint, headers=header).json()['paragraphs']
    paras = '\n\n'.join(i['text'] for i in paras)
    return paras`***

您可以在上面的脚本部分查看示例输出。

扬声器标签

来自 AssemblyAI API 的v2/transcript端点的原始响应的 JSON 包含utterances键,它的值是整个音频中的一系列话语,如下所示:

***`"utterances": [
    {
      "confidence": 0.9550143322475569,
      "end": 99430,
      "speaker": "A",
      "start": 1150,
      "text": "You will never believe what happened to me last week. My SUV broke ... to get it replaced. What are you doing this weekend?",
      "words": [...]         
    }
  ],`***

我们使用列表理解将说话者标签添加到每个话语的文本中,然后将列表元素连接在一起,中间使用换行符,如上所述。

***`utts = '\n\n\n'.join([f"Speaker {utt['speaker']}:\n\n" + utt['text'] for utt in r['utterances']])`***

这个计算发生在[create_output()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/app.py#L129)函数本身的中,因此不依赖于来自helpers.py的任何函数。扬声器标签输出示例可在上面的扬声器标签部分找到。

自动突出显示

AssemblyAI 响应中的 auto highlights 信息具有以下形式,其中results是一个字典列表,每个字典都包含关于给定突出显示的信息

***`"auto_highlights_result": {
    "status": "success",
    "results": [
      {
        "count": 1,
        "rank": 0.09,
        "text": "good professors",
        "timestamps": [
          {
            "start": 58964,
            "end": 60030
          }
        ]
      },
      {"count": 1...},
      {"count": 1...},
      ...
      {"count": 1...},
    ]
  },`*** 

为了处理这些信息以显示在仪表板上,我们使用了[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中的[create_highlighted_list()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L307)函数。首先,可能会过滤掉低等级的高光对象,然后计算值,这将允许我们稍后根据等级缩放高光不透明度

***`def create_highlighted_list(paragraphs_string, highlights_result, rank=0):
    # Max and min opacities to highlight to
    MAX_HIGHLIGHT = 1  # Max allowed = 1
    MIN_HIGHLIGHT = 0.25  # Min allowed = 0

    # Filter list for everything above the input rank
    highlights_result = [i for i in highlights_result if i['rank'] >= rank]

    # Get max/min ranks and find scale/shift we'll need so ranks are mapped to [MIN_HIGHLIGHT, MAX_HIGHLIGHT]
    max_rank = max([i['rank'] for i in highlights_result])
    min_rank = min([i['rank'] for i in highlights_result])
    scale = (MAX_HIGHLIGHT - MIN_HIGHLIGHT) / (max_rank - min_rank)
    shift = (MAX_HIGHLIGHT - max_rank * scale)`***

接下来,使用 RegEx 查找每个高亮对象短语的每个实例,然后使用 list comprehension 创建一个列表,该列表使用适当的格式准备要输入到[gr.HighlightedText()](https://gradio.app/docs/#highlightedtext)中的数据。

在这种情况下,"entity"值是一个在[0,1]范围内的浮点数,对应于实体的高亮不透明度。我们使用一个比例变换来使每个实体的值在MIN_HIGHLIGHTMAX_HIGHLIGHT之间,与它的等级成比例。"start""end"值表示整个文本正文中高亮文本的开始和结束字符,在本例中为paragraphs_string

***`def create_highlighted_list(paragraphs_string, highlights_result, rank=0):
    # ...

    # Isolate only highlight text and rank
    highlights_result = [(i['text'], i['rank']) for i in highlights_result]

    entities = []
    for highlight, rank in highlights_result:
        # For each highlight, find all starting character instances
        starts = [c.start() for c in re.finditer(highlight, paragraphs_string)]
        # Create list of locations for this highlight with entity value (highlight opacity) scaled properly
        e = [{"entity": rank * scale + shift,
              "start": start,
              "end": start + len(highlight)}
             for start in starts]
        entities += e`***

最后,创建输出字典,为gr.HighlightedText提供必要的输入结构。我们通过开始字符值对entities进行排序,以避免 Gradio 3.2 中的错误,如果不这样做,就会错误地显示文本,然后返回输出字典。

***`def create_highlighted_list(paragraphs_string, highlights_result, rank=0):
    # ...

    # Create dictionary
    highlight_dict = {"text": paragraphs_string, "entities": entities}

    # Sort entities by start char. A bug in Gradio requires this
    highlight_dict['entities'] = sorted(highlight_dict['entities'], key=lambda x: x['start'])

    return highlight_dict`***

自动高亮显示部分可以找到一个自动高亮显示输出的例子。

摘要

AssemblyAI response JSON 中的摘要信息具有简单的形式——对于每个自动检测到的“章节”,都会返回一个摘要、标题和要点。summary是本章的完整摘要,headline进一步提炼信息,得到一个更短的摘要,gist用几个词提供了最短的摘要。

***`"chapters": [
    {
      "summary": "After his SUV broke down, he had to go to an auto shop to get a new gasket installed. Yesterday ... the program. He's also going to play basketball for his new team while earning his degree.",
      "headline": "Yesterday he was walking in South Boston to pick the car up, and some guy got thrown through the window of a pub.",
      "gist": "What's up jack.",
      "start": 1150,
      "end": 99430
    }
  ],`***

为了为我们的仪表板正确地格式化这些信息,我们在[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中使用了[make_summary()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L347)函数,它仅仅使用了一个 HTML details标签来显示加粗的headline,点击headline文本就会出现summary

总结部分可以找到一个总结输出的例子。

检测到的主题

AssemblyAI 响应 JSON 中的主题检测信息具有以下形式,为了简洁起见,我们只选择了检测到的主题的子集。例如,行Sports>CollegeSports>CollegeBasketball暗示检测到主题CollegeBasketball,该主题被分类在CollegeSports下,该主题又被分类在Sports下。

***`"iab_categories_result": {
  "status": "success",
  "results: [...]
  "summary": {
      "Sports>CollegeSports>CollegeBasketball": 0.9752611517906189,
      "Sports>Basketball": 0.9687691926956177,
      "Automotive>AutoSafety": 0.7185451984405518,
      "Education>CollegeEducation>PostgraduateEducation": 0.14552389085292816,
      "Automotive>AutoTechnology>AutoSafetyTechnologies": 0.11995891481637955,
      "Automotive>AutoRecalls": 0.11385084688663483,
      "Education>CollegeEducation>UndergraduateEducation": 0.11231876164674759,
      "Sports>CollegeSports": 0.08391721546649933,
      "Sports>Boxing": 0.06388098001480103,
      "Automotive>AutoType>DriverlessCars": 0.02020467445254326,
    }
  },`***

为了将这些信息格式化输出到仪表板,我们使用了[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中的[make_html_from_topics()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L278)函数。这个函数相当复杂,所以我们将看一个概念性的概述,而不是直接看代码,尽管我们鼓励感兴趣的读者点击上面的函数名来查看 GitHub 上的源代码。

make_html_from_topics()首先只隔离summary字典中的关键字,潜在地过滤掉具有低值的关键字(对应于关键字所标识的主题与文本的相关性)。这给留下了一个列表,如下所示:

***`['Sports>CollegeSports>CollegeBasketball',
 'Sports>Basketball',
 'Automotive>AutoSafety',
 'Education>CollegeEducation>PostgraduateEducation',
 'Automotive>AutoTechnology>AutoSafetyTechnologies',
 'Automotive>AutoRecalls',
 'Education>CollegeEducation>UndergraduateEducation',
 'Sports>CollegeSports',
 'Sports>Boxing',
 'Automotive>AutoType>DriverlessCars',]`***

然后这个列表通过一个函数传递,这个函数递归地把它变成一个字典,它表示一棵树,如下所示。上面列表中出现的每个主题都是一个节点,上面列表中出现的任何子主题都是它的子节点。每个节点还可能有一个空的子节点(在图中用圆圈中的 N 表示,在字典中用空的键-值对表示),这表示一个特定的主题是一个已识别的主题。这是重要的,因为例如CollegeSports本身被识别为除了 CollegeBasketball之外的检测到的主题,而CollegeBasketball是它的孩子。**

make_html_from_topics()的其余部分遍历树,将其转换为 HTML,其中每个主题及其子主题递归地列出,给树的每个“层”一个不同的类- topic-L0topic-L1等等。所有紫色节点(即确定出现在音频中的主题)也是另一个类istopic的一部分,用于为仪表板中的相关文本着色。

下面的 CSS 提供了每个元素的样式。

***`.istopic {
color: #6b2bd6;
}

.topic-L0 {
font-size: 30px;
text-indent: 0px;
}

.topic-L1 {
font-size: 25px;
text-indent: 18px;
}

.topic-L2 {
font-size: 20px;
text-indent: 36px;
}

.topic-L3 {
font-size: 15px;
text-indent: 54px;
}`***

CSS 被传递到仪表板

***`with open('styles.css', 'r') as f:
    css = f.read()

with gr.Blocks(css=css) as demo:
	# ...`***

主题检测输出示例可在检测到的主题部分找到。

情感分析

AssemblyAI response JSON 中的情感分析信息具有以下形式,其中每个句子被规定为一个"POSITIVE""NEGATIVE""NEUTRAL"情感。

 ***`"sentiment_analysis_results": [
    {
      "text": "You will never believe what happened to me last week.",
      "start": 1150,
      "end": 4674,
      "sentiment": "NEGATIVE",
      "confidence": 0.5470982193946838,
      "speaker": "A"
    },
    "...",
    {
      "text": "What are you doing this weekend?",
      "start": 98050,
      "end": 99430,
      "sentiment": "NEUTRAL",
      "confidence": 0.8970483541488647,
      "speaker": "A"
    }
  ]`***

为了处理这些数据以显示在我们的仪表板上,我们使用了[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中的[make_sentiment_output()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L365)函数。这个函数简单地遍历上述内容,根据与sentiment key相关的值,使用 HTML <mark>标记突出显示绿色或红色文本(或者根本不显示)。

***`def make_sentiment_output(sentiment_analysis_results):
    p = '<p>'
    for sentiment in sentiment_analysis_results:
        if sentiment['sentiment'] == 'POSITIVE':
            p += f'<mark style="{green + to_hex(sentiment["confidence"])}">' + sentiment['text'] + '</mark> '
        elif sentiment['sentiment'] == "NEGATIVE":
            p += f'<mark style="{red + to_hex(sentiment["confidence"])}">' + sentiment['text'] + '</mark> '
        else:
            p += sentiment['text'] + ' '
    p += "</p>"
    return p`***

情感分析部分可以找到一个示例情感分析输出。

实体检测

AssemblyAI 响应 JSON 中的实体检测信息具有以下形式,其中检测到的实体通过text被标识为类entity_type的实体。

***`"entities": [
    {
      "entity_type": "location",
      "text": "South Boston",
      "start": 12884,
      "end": 13678
    },
    "...",
    {
      "entity_type": "medical_process",
      "text": "physical therapy",
      "start": 80504,
      "end": 81274
    }
  ]`***

为了处理这些数据以显示在我们的仪表板上,我们使用了[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中的[make_entity_html()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L380)函数。它简单地遍历上面的列表,创建一个字典,其中每个键是一个标识的entity_type,其值是该实体类型的每个检测到的实体(例如"South Boston")的列表,以及上下文转录中的周围单词。如果已识别的实体通过 PII 修订版进行了修订,则文本将替换为"[REDACTED]"

***`def make_entity_dict(entities, t, offset=40):
    len_text = len(t)

    d = {}
    for entity in entities:
        s = t.find(entity['text'])
        if s == -1:
            p = None
        else:
            len_entity = len(entity['text'])
            p = t[max(0, s - offset):min(s + len_entity + offset, len_text)]
            p = '... ' + ' '.join(p.split(' ')[1:-1]) + ' ...'
        label = ' '.join(entity['entity_type'].split('_')).title()
        if label in d:
            d[label] += [[p, entity['text']]]
        else:
            d[label] = [[p, entity['text']]]

    return d`***

然后,使用helpers.py中的[make_entity_html()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L408)函数,将这个字典转换成 HTML,类似于上面检测到的主题。该函数在 HTML 列表的最高层以紫色显示每个实体类型,每个实体类型实例在子列表中显示在其下方,被识别的实体以黄色突出显示。

***`def make_entity_html(d, highlight_color="#FFFF0080"):
    """Input is output of `make_entity_dict`. Creates HTML for Entity Detection info"""
    h = "<ul>"
    for i in d:
        h += f"""<li style="color: #6b2bd6; font-size: 20px;">{i}"""
        h += "<ul>"
        for sent, ent in d[i]:
            if sent is None:
                h += f"""<li style="color: black; font-size: 16px;">[REDACTED]</li>"""
            else:
                h += f"""<li style="color: black; font-size: 16px;">{sent.replace(ent, f'<mark style="background-color: {highlight_color}">{ent}</mark>')}</li>"""
        h += '</ul>'
        h += '</li>'
    h += "</ul>"
    return h`*** 

实体检测输出示例可在实体检测部分找到。

内容安全

AssemblyAI response JSON 中的内容安全信息具有相对复杂的结构,因此我们将下面的"summary"信息分离出来:

***`"content_safety_labels": {
    "...",
    "summary": {
      "accidents": 0.2611353717731301,
      "alcohol": 0.46347422408861444,
      "crime_violence": 0.27030136438470115
    },
}`***

每一个检测到的敏感话题都用一个值列出,该值表示每个话题在文本中的存在程度,范围为 0 到 1。为了准备在我们的仪表板上显示这些信息,我们只需使用[helpers.py](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py)中的[make_content_safety_fig()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L425)函数,使用 plotly 在图表中绘制这些值

***`def make_content_safety_fig(cont_safety_summary):
    d = {'label': [], 'severity': [], 'color': []}

    for key in cont_safety_summary:
        d['label'] += [' '.join(key.split('_')).title()]
        d['severity'] += [cont_safety_summary[key]]
        d['color'] += ['rgba(107, 43, 214, 1)']

    # Create the figure (n.b. repetitive color info but was running into plotly/Gradio bugs)
    content_fig = px.bar(d, x='severity', y='label', color='color', color_discrete_map={
        'Crime Violence': 'rgba(107, 43, 214, 0.1)',
        'Alcohol': 'rgba(107, 43, 214, 0.1)',
        'Accidents': 'rgba(107, 43, 214, 0.1)'})

    # Update the content figure plot
    content_fig.update_layout({'plot_bgcolor': 'rgba(107, 43, 214, 0.1)'})

    # Scale axes appropriately
    content_fig.update_xaxes(range=[0, 1])
    return content_fig`***

内容安全输出示例可在内容安全部分找到。

运行和部署仪表板

app.py中定义所有组件和功能后,只需在文件末尾添加以下内容(在gr.Blocks()块之外)即可启动应用程序:

***`demo.launch()`***

终端将显示一个可以访问仪表板的本地 URL。要获得应用程序的可共享链接,只需添加share=True作为关键字参数:

***`demo.launch(share=True)`***

***暗/亮模式注释

Gradio 有一个“暗”模式和一个“亮”模式,可以改变应用程序的默认外观。根据您的操作系统和浏览器,仪表板可能会以黑暗模式显示。在 Gradio 3.2 中,无法强制应用程序在 Python 中显示为主题或其他。然而,您可以通过运行app.py?__theme=light附加到 URL 输出来强制使用轻量模式。

音频智能仪表板旨在以亮模式运行,因此如果您在本地运行时看到暗模式,请尝试上述方法强制应用程序以亮模式显示*** ***输出的公共 URL 将在 72 小时内有效,但是如果你终止正在运行的进程app.py,点击链接将产生一个 502 错误。为了创建一个持久的应用实例,我们将利用拥抱面部空间。Spaces 允许您在几分钟内轻松部署 Gradio(或 Streamlit 或 Static)应用程序。现在让我们来看看如何做到这一点。

将仪表板部署到空间

首先,转到共享空间并登录您的拥抱脸帐户(如果您还没有帐户,请创建一个)。然后选择创建新空间:

在出现的屏幕中填写所有必要的信息,为 Space SDK 选择 Gradio ,然后点击底部的创建空间

既然已经创建了空间,我们需要将仪表板代码上传到其中。在终端中,导航到仪表板项目存储库并执行以下命令:

git remote add space https://huggingface.co/spaces/FULL_SPACE_NAME

用您的用户名和空间名替换FULL_SPACE_NAME。例如,部署此仪表板时使用的完整命令是:

git remote add space https://huggingface.co/spaces/oconnoob/audio-intelligence-dashboard

这个命令将空间添加为存储库的远程,这意味着我们可以使用 git 将代码上传到这个远程位置。为此,我们执行以下命令:

git push --force space master:main 

我们用git push上传代码(用--force强行上传,因为空间是空的)到space指定的遥控器,就是我们刚刚添加的拥抱脸空间。master:main告诉 git 将项目存储库的master分支推送到远程space存储库中的main分支。

其他详细信息

我们以这种方式指定分支,因为 Spaces 中的默认分支名是main,但是我们本地存储库中的主分支名为master,所以为了让我们的应用程序显示出来,我们必须指定我们正在从本地的master推送至远程的main

注意,对于其他项目,您的默认分支可能被命名为main,这意味着您可以从上面的命令中省略master:main。要在 git 存储库中检查您所在的分支的名称,在终端中执行git branch,找到旁边带有星号的分支名称。

这就是将音频智能仪表板部署到 Spaces 的全部工作!如果你想设置一个应用程序来自动更新 GitHub 库的变化,请查看使用 GitHub 动作管理空间

项目扩展

这个项目是对 Gradio 的全面介绍,但肯定有一些有趣的扩展,那些希望深入研究的人可以实现它们进行练习。我们在下面列出了一些,但是如果你已经看到了你想看的一切,请随意跳到一些最后的话。

阈值滑块

有几个不同的参数使用常量值,这些常量值可以变成变量。例如,[make_html_from_topics()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L278)函数有一个threshold参数,它提供了一个主题必须显示的相关性的下限。

目前,该阈值使用默认值 0.0,这意味着没有主题被过滤掉。音频智能仪表板的一个扩展可能是实现一个控制这个阈值的 Gradio 滑块,允许用户选择他们想要过滤的积极程度。

[create_highlighted_list()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L307)[make_entity_dict()](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/210a74351dc68c7390ac4e4c47f9577bec89220c/app/helpers.py#L380)有相似的参数(分别是rankoffset),也可以实现滑块。关于这个扩展的伟大之处在于 Python 已经被编写来适应它,所以所有需要的就是创建 Gradio“连接”。

奖金

作为一个额外的挑战,尝试使这些滑块的效果动态。也就是说,允许用户在显示的结果上使用滑块,动态地改变显示哪些主题,而不是选择例如带有滑块的threshold并静态地显示结果。

保存和导入结果

考虑创建一种保存已执行分析的方法,可能是通过保存响应 JSON 并添加用户上传保存的 JSON 以重新显示它的能力(而不必运行相同的分析)。

API 关键要求

目前,使用仪表板不需要 API 键。考虑实现一个特性,其中必须输入 API 键才能“访问”仪表板。这是一个相对容易的扩展,只要求组件是可见的。

或者,考虑通过处理误用来使仪表板更加健壮,比如在没有输入 API 键的情况下点击了 Submit

修改 CSS

如果你想练习你的 CSS,试着修改styles.css来改变显示输出的样子。此外,考虑研究一下[elem_id](https://gradio.app/custom_CSS_and_JS/#the-elem_id-argument)参数来微调外观。请注意,一般来说,HTML 元素是最容易用 Gradio 修改的,目前还没有简单的方法来显著塑造应用程序的整体外观。

最后的话

本教程到此为止!如果你喜欢这篇文章,请随意订阅我们下面的时事通讯,或者在我们的博客上查看一些其他文章,比如关于运行稳定扩散或者构建你自己的文本到图像模型的教程。

或者,看看我们的 YouTube 频道的其他精彩内容,比如我们的机器从头学习系列!

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow***

JavaScript 和 Node.js 语音转文本

原文:https://www.assemblyai.com/blog/getting-started-with-speech-to-text-transcriptions-with-assemblyai-javascript-and-node-js/

开始使用一项新技术可能会令人望而生畏,但我们将使用 node.js 分解语音到文本的转录,使之变得更容易!

本帖我们就做一个基本的 node.js 命令行界面 app (常缩写为 CLI app)。我们首先将一个音频记录的 URL 作为一个 参数 传递给一个上传函数。第二个函数将发送一个 HTTP 请求到 AssemblyAI 语音到文本转录 API。

然后,我们将在命令行上打印 AssemblyAI 的响应以及我们转录的 ID。接下来,我们将把转录 ID 作为参数传递给第二个函数,该函数将获取我们的转录并将其打印到屏幕上!

先决条件

如果你想看完整的代码项目,可以从这个 GitHub 库获得。

获得您的开发环境设置

使用您最喜欢的命令行程序,如终端或 Powershell,在您的计算机上创建一个新目录。我已经调用了我的目录transcribe。导航到它并初始化一个新的 Node.js 项目:

mkdir transcribe
cd transcribe
npm init -y

在新的transcribe目录中创建三个新文件:

如果使用 Windows:

New-Item upload.js, download.js, .env

如果使用 macOS 或 Linux:

touch upload.js && touch download.js && touch .env

如果您还没有安装它,我们将需要节点包dotenv来管理我们的 AssemblyAI API 密钥。您可以使用以下命令安装它:

npm i dotenv --save

我们还将使用 Fetch API 来进行 REST 调用,所以请确保也安装它:

npm i node-fetch --save

在代码编辑器中打开.env文件,并添加一个环境变量来存储 AssemblyAI API 密钥。您可以在 AssemblyAI 仪表板中找到您的 API 键,并将其作为值添加到上述变量中。

ASSEMBLYAI_API_KEY = "YOUR_API_KEY"

想玩我们的语音转文本 API 吗?

现在就开始免费!

Start Now

将音频文件上传到 AssemblyAI 转录 API

开始之前,将"type": "module"添加到您的package.json文件中。这将允许您使用 ES 模块,这是我们这个项目所需要的。

在您的代码编辑器中打开我们之前创建的upload.js。将以下代码复制并粘贴到该文件中:

import 'dotenv/config';
import fetch from 'node-fetch';
const url = 'https://api.assemblyai.com/v2/transcript';

上面的代码将导入我们之前添加的两个包,dotenvfetch。我们还将 AssemblyAI API 端点定义为url

运行应用程序时,我们需要将托管音频文件的 URL 作为命令行参数进行传递。下面的代码获取 CLI 参数并将其赋给一个名为audioUrl的变量。

let args = process.argv.slice(2);
let audioUrl = args[0];

如果你想从你的电脑上传一个文件,这个博客会带你完成这些步骤。

AssemblyAI 期待一个带有 JSON 主体的 HTTP POST 请求。JSON 将包含我们作为参数传递的音频 URL。接下来我们来定义一下。

const data = {
    "audio_url" : audioUrl
};

现在是时候考虑我们的 HTTP 请求了。

我们通过包node-fetch使用fetch。这个模块将浏览器中的window.fetch带到我们的节点应用程序中。

fetch需要一些参数,包括我们的 AssemblyAI API key,JSON body,header。我们将创建一个名为params的 JavaScript 对象来包含这些参数。

const params = {
    headers:{
        "authorization": process.env.ASSEMBLYAI_API_KEY,
        "content-type": "application/json",
    },
    body: JSON.stringify(data),
    method: "POST"
};

我们的upload函数的最后一步是发出 HTTP 请求并打印对命令行的响应。

fetch(url, params)
  .then(response => response.json())
  .then(data => {
    console.log('Success:', data);
    console.log('ID:', data['id']);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

fetch支持承诺,因此我们将发出请求并使用then关键字从响应中提取返回的数据,在命令行中显示完整的 JSON 响应和转录 ID。我们将使用catch关键字在出错的情况下显示错误。

整个 upload.js 文件应该如下所示:

import 'dotenv/config';
import fetch from 'node-fetch';

const url = 'https://api.assemblyai.com/v2/transcript';

let args = process.argv.slice(2);
let audioUrl = args[0];
const data = {
  "audio_url": audioUrl
};

const params = {
  headers: {
    "authorization": process.env.ASSEMBLYAI_API_KEY,
    "content-type": "application/json",
  },
  body: JSON.stringify(data),
  method: "POST"
};

fetch(url, params)
  .then(response => response.json())
  .then(data => {
    console.log('Success:', data);
    console.log('ID:', data['id']);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

尝试一下

在命令行中,键入以下内容(记住,您需要与代码文件位于同一个目录中)。您可以使用下面提供的 URL 或您选择的一个,只要确保您可以首先在浏览器中播放它。

node upload.js https://s3-us-west-2.amazonaws.com/blog.assemblyai.com/audio/8-7-2018-post/7510.mp3 

如果一切顺利,您应该看到从 AssemblyAI 返回的整个 JSON 对象被打印到屏幕上。这也将包括在末尾单独打印的转录 ID。

从汇编下载一份抄本

在这个应用程序中,我们将向 AssemblyAI 端点发出一个请求,以检查我们的记录是否完成。

AssemblyAI 还支持 webhooks ,这意味着一旦转录完成,AssemblyAI 将向您的应用程序发出 POST 请求。

在您的代码编辑器中打开我们之前创建的download.js文件。

就像在upload.js中一样,我们需要导入dotenvfetch,以及来自命令行的参数。

import 'dotenv/config';
import fetch from 'node-fetch';
let args = process.argv.slice(2);
let id = args[0];
const url = `https://api.assemblyai.com/v2/transcript/${id}`;

注意,这次我们从命令行获取 ID 作为参数,然后使用字符串文字将 ID 添加到 AssemblyAI 端点以检索转录。我们将需要再次设置fetch的参数,只是这一次我们是在做一个 GET 请求,所以不会有任何主体数据。

const params = {
  headers: {
    "authorization": process.env.ASSEMBLYAI_API_KEY,
    "content-type": "application/json",
  }, 
  method: "GET"
};

这是代码与上传代码略有不同的地方。转录可能需要一些时间,所以当我们从 AssemblyAI 得到响应时,我们需要检查音频文件是否已经被处理。

我们可以通过检查响应的“状态”来确定这一点。如果 AssemblyAI 说转录仍然在“排队”或“处理中”,那么我们需要稍后再试。如果状态为“已完成”,转录文本将包含在回复中。让我们写一个函数来处理这个问题。

function print(data) {
  switch (data.status) {
    case 'queued':
    case 'processing':
      console.log('AssemblyAI is still transcribing your audio, please try again in a few minutes!');
      break;
    case 'completed':
      console.log(`Success: ${data}`);
      console.log(`Text: ${data.text}`);
      break;
    default:
      console.log(`Something went wrong :-( : ${data.status}`);
      break;
  }
}

这里我们使用一个简单的开关语句来测试状态是queuedprocessing还是completed。如果状态是别的,我们就假设出了问题。最后,我们可以使用fetch进行 API 调用。

fetch(url, params)
  .then(response => response.json())
  .then(data => {
    print(data);
  })
  .catch((error) => {
    console.error(`Error: ${error}`);
  });

整个download.js文件应该如下:

import 'dotenv/config';
import fetch from 'node-fetch';

let args = process.argv.slice(2);
let id = args[0];
const url = `https://api.assemblyai.com/v2/transcript/${id}`;

const params = {
  headers: {
    "authorization": process.env.ASSEMBLYAI_API_KEY,
    "content-type": "application/json",
  },
  method: 'GET'
};

function print(data) {
  switch (data.status) {
    case 'queued':
    case 'processing':
      console.log('AssemblyAI is still transcribing your audio, please try again in a few minutes!');
      break;
    case 'completed':
      console.log(`Success: ${data}`);
      console.log(`Text: ${data.text}`);
      break;
    default:
      console.log(`Something went wrong :-( : ${data.status}`);
      break;
  }
}

fetch(url, params)
  .then(response => response.json())
  .then(data => {
    print(data);
  })
  .catch((error) => {
    console.error(`Error: ${error}`);
  });

是时候测试一下了!

在命令行中,使用从命令行中的 upload 函数返回的转录 ID,编写以下代码并按 enter 键

node download.js TRANSCRIPTION_ID 

如果您的转录状态是completed,文本应该打印在命令行上。

接下来呢?

您已经成功地编写了一个命令行应用程序,将一个音频文件 URL 上传到 AssemblyAI,然后下载完成的转录并将其打印在屏幕上。

Torchaudio 入门

原文:https://www.assemblyai.com/blog/getting-started-with-torchaudio/

在本 PyTorch 教程中,我们将学习如何开始使用 Torchaudio 并处理音频数据。

我们了解到:

  • 加载/保存音频数据
  • 转换
  • 重采样
  • 数据扩充
  • 特征抽出
  • Torchaudio 数据集

看这里:

https://www.youtube.com/embed/3mju52xBFK8?feature=oembed

如何使用 Golang 进行语音转文本

原文:https://www.assemblyai.com/blog/golang-speech-recognition/

您是否想在 Golang 中使用自动语音识别,或者 ASR,,并且想知道如何实现这一点?

本文展示了不同的可用选项,以及如何在 60 秒内将语音识别集成到您的 Golang 应用程序中。

Golang 的语音转文本 API

由于 Golang 中的开源选项仍然有限,所以最好的选择是使用语音转文本 API,这可以通过每种编程语言中的简单 HTTP 客户端来访问。

为你的项目选择最好的语音转文本 API 可能会很有挑战性。最易于集成的 API 之一是 AssemblyAI。因此,让我们了解一下如何用几行代码将它集成到 Golang 应用程序中。

基于汇编语言的 Golang 语音识别

我们只需要用 HTTP 客户端发送三个 API 请求,但是首先,我们需要获得一个有效的 API 键。您可以在此获得一个,并免费开始使用:

Get a free API Key

步骤 1:上传文件

如果您已经将文件托管在某个地方,您可以跳过这一步。否则,我们加载一个本地文件并向上传端点发送一个 post 请求。

我们可以使用内置的net/http包来建立一个 HTTP 客户端,指定头数据并发送文件。然后我们可以解码返回的 JSON 数据并使用获得的upload_url:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    const API_KEY = "YOUR_API_KEY"
    const UPLOAD_URL = "https://api.assemblyai.com/v2/upload"

    // Load file
    data, err := ioutil.ReadFile("your_file.m4a")
    if err != nil {
        log.Fatalln(err)
    }

    defer res.Body.Close()

    // Setup HTTP client and set header
    client := &http.Client{}
    req, _ := http.NewRequest("POST", UPLOAD_URL, bytes.NewBuffer(data))
    req.Header.Set("authorization", API_KEY)
    res, err := client.Do(req)

    if err != nil {
        log.Fatalln(err)
    }

    // Decode json and store it in a map
    var result map[string]interface{}
    json.NewDecoder(res.Body).Decode(&result)

    // Print the upload_url
    fmt.Println(result["upload_url"])
} 
https://cdn.assemblyai.com/upload/38174ca3-58b0 

第二步:开始转录

接下来,我们向脚本端点发送一个 POST 请求。这一次,我们发送 JSON 数据,该数据使用键audio_url和上一步中的upload_url:

{"audio_url": AUDIO_URL} 

提交请求后,我们将得到一个包含 JSON 数据的响应,其中包含一个id键:

const AUDIO_URL = "https://cdn.assemblyai.com/upload/38174ca3-58b0"
const TRANSCRIPT_URL = "https://api.assemblyai.com/v2/transcript"

// Prepare json data
values := map[string]string{"audio_url": AUDIO_URL}
jsonData, err := json.Marshal(values)

if err != nil {
    log.Fatalln(err)
}

// Setup HTTP client and set header
client := &http.Client{}
req, _ := http.NewRequest("POST", TRANSCRIPT_URL, bytes.NewBuffer(jsonData))
req.Header.Set("content-type", "application/json")
req.Header.Set("authorization", API_KEY)
res, err := client.Do(req)

if err != nil {
    log.Fatalln(err)
}

defer res.Body.Close()

// Decode json and store it in a map
var result map[string]interface{}
json.NewDecoder(res.Body).Decode(&result)

// Print the id of the transcribed audio
fmt.Println(result["id"]) 
og6cthhbf0-9195-4b81-8002-bdad76b7bd35 

第三步:获取转录文本

最后的任务是得到转录的文本。步骤和之前类似。我们用基本 url 和新收到的副本id组成一个新的 API 端点。

然后,我们必须向这个端点发送一个 GET 请求,之后,我们可以再次解码 JSON 数据。如果完成了status,我们就可以获得转录的文本。

// New endpoint
const POLLING_URL = TRANSCRIPT_URL + "/" + "og6cthhbf0-9195-4b81-8002-bdad76b7bd35"

// Send GET request
client := &http.Client{}
req, _ := http.NewRequest("GET", POLLING_URL, nil)
req.Header.Set("content-type", "application/json")
req.Header.Set("authorization", API_KEY)
res, err := client.Do(req)

if err != nil {
    log.Fatalln(err)
}

defer res.Body.Close()

var result map[string]interface{}
json.NewDecoder(res.Body).Decode(&result)

// Check status and print the transcribed text
if result["status"] == "completed" {
    fmt.Println(result["text"])
} 
This is your transcribed text of the audio file... 

就是这样!你可以在我们的 GitHub 库中找到完整的代码。

Golang 的开源语音识别库

不幸的是,Golang 的选择仍然有限。拥有最多用于离线语音识别的开源库的编程语言是 Python。

如果还想用开源库,可以试试 PocketSphinx for Golang 。这是一个轻量级的语音识别引擎,专门针对手持和移动设备进行了调整。然而,这个项目已经四年多没有更新了。

hack with assembly ai:hawk hacks 2022

原文:https://www.assemblyai.com/blog/hack-with-assemblyai-hawkhacks-2022/

在过去的几个月里,AssemblyAI 一直是全球众多黑客马拉松的自豪赞助商,包括 2022 年 5 月 21 日至 22 日举行的 HawkHacks 2022

作为赞助商,我们提供了我们的语音转文本 API音频智能 API作为资源,供与会者在活动期间使用。我们还为与会者提供了实时支持,回答他们关于如何使用 API 或演讲转录的任何问题。

以下是 HawkHack 2022 期间使用 AssemblyAI 语音转文本 API 构建的前三个项目的亮点:

1.语音 2 代码

https://www.youtube.com/embed/0wfjXtWxaz0?feature=oembed

Speech2Code 是一个帮助程序员更容易、更快地编写代码的 web 应用。Speech2Code 允许用户说出他们希望看到实现的命令。然后,web 应用程序将这些口头命令翻译成三种书面编码语言之一:Python、JavaScript 和 Java。

应用程序的前端使用 React 构建,后端使用 Python flask 服务器构建,用于 AssemblyAI API 集成,Express.js 用于代码编译。

未来,Speech2Code 代码开发人员希望扩展应用程序,以支持更多的语言和框架。

2.vid 2 文字

https://www.youtube.com/embed/K83AhtD3Hjc?feature=oembed

Vid2Text 是一个 web 应用程序,可以为上传的音频和视频文件生成经过修改和定制的音频和视频转录。通过该应用程序,用户可以高精度地自动转录文件,根据需要修改转录,并在整个转录文本中搜索和突出显示关键字。

该应用程序是使用 Django(一个 Python web 框架)和 AssemblyAI 语音转文本和音频智能 API(用于转录和关键字功能)构建的。

将来,Vid2Text 的创建者希望在应用程序中添加多语言转录,并改善整体用户体验。

3.你好

https://www.youtube.com/embed/h-NpXro3NgM?feature=oembed

最后 Vivy.ai 是一个不和谐的 ai。通过 Vivy.ai,用户可以加入机器人的语音通道,然后机器人会对用户说的任何话做出口头回应。对话也在文本频道中为用户转录。

Vivy.ai 的创造者使用了一个基本的 discord bot 来促进人工智能的开发,并使用 AssemblyAI 进行语音到文本的转录,使用 GPT-3 生成上下文响应。

在未来,创作者计划创造一个余薇薇的 3D 模型,让她“说话”,甚至可能训练她唱歌。

ChatGPT 实际上是如何工作的

原文:https://www.assemblyai.com/blog/how-chatgpt-actually-works/

ChatGPT 是 OpenAI 的最新语言模型,代表了对其前身 GPT-3 的重大改进。与许多大型语言模型类似,ChatGPT 能够生成各种风格的文本,用于不同的目的,但具有更高的精度、细节和一致性。它代表了 OpenAI 大型语言模型系列中的下一代,它的设计非常注重交互式对话。

创造者使用了监督学习和强化学习的组合来微调 ChatGPT,,但正是强化学习组件使 ChatGPT 独一无二。创作者使用一种称为人类反馈强化学习(RLHF)的特殊技术,在训练循环中使用人类反馈来最小化有害、不真实和/或有偏见的输出。

在了解 RLHF 如何工作以及了解 ChatGPT 如何使用 RLHF 来克服这些问题之前,我们将检查 GPT-3 的局限性以及它们如何源于其训练过程。最后,我们将看看这种方法的一些局限性。

大型语言模型中的能力与一致性

"alignment vs capability" can be thought of as a more abstract analogue of "accuracy vs precision"

在机器学习的上下文中,术语能力指的是模型执行特定任务或一组任务的能力。一个模型的能力通常是通过来评估的,它优化其目标函数的能力,目标函数是定义模型目标的数学表达式。例如,用于预测股票市场价格的模型可能有一个衡量模型预测准确性的目标函数。如果该模型能够准确地预测股票价格随时间的变化,则可以认为它具有完成这项任务的高水平能力。

对齐,另一方面,关注的是我们实际上想要模型做什么与它被训练做什么。它问的问题是“目标函数与我们的意图一致吗?”指的是模型的目标和行为与人类价值观和期望的一致程度。举一个简单具体的例子,假设我们训练一个鸟类分类器来将鸟类分类为“麻雀”或“知更鸟”,我们使用对数损失(测量模型的预测概率分布和真实分布之间的差异)作为训练目标,尽管我们的最终目标是高分类精度。该模型可能具有较低的对数损失,即该模型的能力较高,但在测试集上精度较差。事实上,日志损失与分类任务的准确性并不完全相关。这是一个不一致的例子,模型能够优化培训目标,但与我们的最终目标不一致。

像最初的 GPT-3 这样的模型是错位的

大型语言模型,如 GPT-3,在来自互联网的大量文本数据上接受训练,并能够生成类似人类的文本,但它们可能不总是产生与人类期望或理想值一致的输出。事实上,他们的目标函数是单词序列(或标记序列)上的概率分布,这允许他们预测序列中的下一个单词是什么(下面有更多细节)。

然而,在实际应用中,这些模型旨在执行某种形式的有价值的认知工作,这些模型的训练方式和我们想要使用它们的方式之间存在明显的分歧。尽管从数学上讲,机器计算出的单词序列的统计分布可能是建模语言的非常有效的选择,但我们作为人类,通过选择最适合给定情况的文本序列来生成语言,使用我们的背景知识和常识来指导这一过程。当语言模型用于需要高度信任或可靠性的应用程序(如对话系统或智能个人助理)时,这可能会成为一个问题。

虽然这些基于大量数据训练的强大、复杂的模型在过去几年变得非常强大,但当用于生产系统以使人类生活更轻松时,它们往往达不到这种潜力。大型语言模型中的对齐问题通常表现为:

  • 缺乏帮助:没有遵循用户的明确指示。
  • 幻觉:模型编造不存在或错误的事实。
  • 缺乏可解释性:人类很难理解模型如何得出特定的决策或预测。
  • 产生有偏见的或有害的输出:一个基于有偏见的/有害的数据训练的语言模型可能会在其输出中重现,即使它没有被明确指示这样做。

但是,具体来说,这个对齐问题是从哪里产生的呢?是不是语言模型被训练的方式天生就容易出现偏差?

语言模型训练策略如何产生错位

Next-token-predictionmasked-language-modeling用于训练语言模型的核心技术,比如变形金刚模型。在第一种方法中,给模型一个单词序列(或“记号”,即单词的一部分)作为输入,并要求它预测序列中的下一个单词。例如,如果给模型输入句子

“猫坐在”

它可能预测下一个单词为【垫子】**【椅子】【地板】,因为在给定先前上下文的情况下,这些单词出现的概率很高;语言模型实际上能够估计给定先前序列的每个可能单词(在其词汇表中)的可能性。

屏蔽语言建模方法是下一个标记预测的一种变体,在这种方法中,输入句子中的一些单词被替换为一个特殊的标记,例如[MASK]。然后要求模型预测应该插入的正确单词来代替掩码。例如,如果给模型一个句子

[MASK]坐上了

作为输入,它可能预测下一个单词为【猫】**【狗】,或者【兔子】

这些目标函数的一个优点是,它允许模型学习语言的统计结构,例如常见的单词序列和单词使用模式。这通常有助于模型生成更自然和流畅的文本,并且这是每个语言模型的预训练阶段的必要步骤。

然而,这些目标函数也会导致问题,主要是因为模型不能区分重要误差和不重要误差。举一个非常简单的例子,如果给模型输入句子:

奥古斯都统治下的罗马帝国

它可能会预测“begin”“end”,因为这两个词出现的可能性都很高(事实上,这两个句子在历史上都是正确的),尽管第二个选择暗示了非常不同的含义。

更一般地,这些训练策略可能导致语言模型对于一些更复杂的任务的不一致,因为仅被训练来预测文本序列中的下一个单词(或屏蔽单词)的模型可能不一定正在学习其含义的一些更高级的表示。因此,该模型很难推广到需要更深入理解语言的任务或环境。

研究人员和开发人员正在研究各种方法来解决大型语言模型中的对齐问题。ChatGPT 基于原始的 GPT-3 模型,但通过使用人类反馈来进一步训练,以指导学习过程,其特定目标是减轻模型的偏差问题。所使用的特定技术,称为从人类反馈中强化学习,是基于以前的学术研究。ChatGPT 代表了将该技术用于投产车型的首个案例。

但是 ChatGPT 的创造者到底是如何利用人类的反馈来攻击对齐问题的呢?

来自人类反馈的强化学习

该方法总体上由三个不同的步骤组成:

  1. 有监督的微调步骤:预训练的语言模型在由标记器管理的相对少量的演示数据上进行微调,以学习有监督的策略(SFT 模型)从选择的提示列表中生成输出。这代表基线模型。
  2. “模仿人类偏好”步骤:贴标机被要求对相对大量的 SFT 模型输出进行投票,这种方式创建了一个由比较数据组成的新数据集。在这个数据集上训练新的模型。这被称为奖励模型(RM)
  3. 最近政策优化(PPO)步骤:奖励模型用于进一步微调和改进 SFT 模型。这一步的结果就是所谓的策略模型

步骤 1 只发生一次,而步骤 2 和 3 可以不断迭代:在当前最佳策略模型上收集更多的比较数据,用于训练新的奖励模型,然后训练新的策略。

现在让我们深入了解每一步的细节!

注意:本文的其余部分基于 InstructGPT 论文的内容。根据 OpenAI 的说法,ChatGPT 已经接受了“使用与 InstructGPT 相同的方法,但在数据收集设置方面略有不同” ( 来源)。不幸的是,ChatGPT 的准确定量报告尚未公开。

步骤 1:监督微调(SFT)模型

第一步是收集示范数据,以便训练一个受监督的政策模型,称为 SFT 模型。

  • 数据收集:选择一系列提示,并要求一组人工贴标机写下预期的输出响应。对于 ChatGPT,使用了两种不同的提示来源:一些直接来自贴标机或开发人员,一些来自 OpenAI 的 API 请求(即来自他们的 GPT-3 客户)。由于整个过程缓慢且昂贵,因此结果是一个相对小的、高质量的精选数据集(大约 12-15k 个数据点),该数据集将用于微调预训练的语言模型。
  • 模型的选择:ChatGPT 的开发者没有对最初的 GPT-3 模型进行微调,而是选择了所谓的 GPT-3.5 系列中的预训练模型。据推测,使用的基线模型是最新的一个text-davinci-003,一个 GPT-3 模型,主要在编程代码上进行了微调。

因此,非常有趣的是,为了创建一个像 ChatGPT 一样的通用聊天机器人,开发人员决定在“代码模型”而不是纯文本模型的基础上进行微调。

Figure adapted from source

由于该步骤的数据量有限,在该过程之后获得的 SFT 模型很可能输出仍然(在概率上)不是很受用户关注的文本,并且通常遭受未对准的影响,如在以上部分中解释的那样。这里的问题是监督学习步骤遭受高的可扩展性成本

为了克服这个问题,现在的策略是让标签员对 SFT 模型的不同输出进行排名,以创建一个奖励模型,而不是要求人类标签员创建一个更大的精选数据集,这是一个缓慢而昂贵的过程——让我们在下一节中更详细地解释这一点。

第二步:奖励模型

目标是直接从数据中学习目标函数(奖励模型)。该函数的目的是给 SFT 模型输出打分,与这些输出对人类的期望程度成比例。在实践中,这将强烈地反映出特定组的人类贴标机的具体偏好和他们同意遵循的共同准则。最终,这个过程将从数据中提取一个自动系统,这个系统被认为是模仿人类偏好的。****

它是这样工作的:

  • 选择一个提示列表,SFT 模型为每个提示生成多个输出(4 到 9 之间的任何值)。
  • 贴标机从最好到最差排列输出。结果是一个新的带标签的数据集,其中排名是标签。该数据集的大小比用于 SFT 模型的精选数据集大约大 10 倍。
  • 这些新数据用于训练奖励模型(RM)。该模型将 SFT 模型的几个输出作为输入,并按照偏好顺序对它们进行排序。

**

Figure adapted from source**

对于贴标机来说,对产品进行排序要比从头开始生产容易得多,这个过程的放大效率要高得多。在实践中,所选择的提示数量在 30-40k 的数量级,并且包括分级输出的不同组合。

第三步:通过最近政策优化(PPO)微调 SFT 模型

强化学习现在被用于通过优化奖励模型来微调 SFT 政策。所使用的特定算法被称为近似策略优化(PPO ),而微调模型被称为 PPO 模型。

ppo 是什么?这种方法的主要特点是:

  • PPO 是一种用于在强化学习中训练代理的算法。它被称为“基于策略”的算法,因为它从当前策略中学习并直接更新当前策略,而不是像 DQN(深度 Q 网络)这样的“非策略”算法那样从过去的经验中学习。这意味着 PPO 根据代理商采取的行动和获得的回报不断调整当前政策。
  • PPO 使用一种信赖域优化方法来训练策略,这意味着它将策略的变化限制在与前一个策略的一定距离内,以确保稳定性。这与其他策略梯度方法形成对比,其他策略梯度方法有时会对策略进行大的更新,这会破坏学习的稳定性。
  • PPO 使用一个值函数来估计给定状态或动作的预期收益。value 函数用于计算 advantage 函数,它表示预期回报和当前回报之间的差异。然后,通过将当前策略采取的动作与先前策略将采取的动作进行比较,使用优势函数来更新策略。这使得 PPO 能够根据所采取措施的估计值对政策进行更明智的更新。

在该步骤中,PPO 模型从 SFT 模型初始化,并且价值函数从报酬模型初始化。该环境是一个土匪环境,它给出一个随机提示,并期待对该提示的响应。给定提示和响应,它会产生一个奖励(由奖励模型决定),剧集结束。在每个代币处,从 SFT 模型添加每代币 KL 惩罚,以减轻奖励模型的过度优化。

**

Figure adapted from source**

性能赋值

因为模型是根据人工标注输入训练的,评估的核心部分也是基于人工输入,即通过让标注者对模型输出的质量进行评级来进行。为了避免过度适应参与训练阶段的贴标机的判断,测试集使用来自不在训练数据中表示的被拒绝的 OpenAI 客户的提示。

该模型根据三个高级标准进行评估:

  • 有用性:判断模型遵循用户指令以及推断指令的能力。
  • 真实性:判断模型在封闭领域任务中产生幻觉(编造事实)的倾向。该模型在 TruthfulQA 数据集上进行评估。
  • 无害:标签员评估模型的输出是否合适,是否诋毁受保护的类,或者是否包含贬损的内容。该模型还在真实毒性提示乌鸦对数据集上进行了基准测试。

该模型还在传统的自然语言处理任务(如问题回答、阅读理解和摘要)上进行零命中率评估(T0 ),在其中一些任务上(T2 ),开发人员观察到与 GPT-3 相比性能有所下降(T3)。这是“校准税”的一个例子,其中基于 RLHF 的校准过程是以某些任务的较低性能为代价的。

这些数据集上的性能回归可以通过一种称为预训练混合的技巧来大大减少:在通过梯度下降训练 PPO 模型期间,梯度更新通过混合 SFT 模型和 PPO 模型的梯度来计算。

方法的缺点

正如在 InstructGPT 论文(据 ChatGPT 的创建者称,chat GPT 基于该论文)中所讨论的,该方法的一个非常明显的局限性是,在将语言模型与人类意图对齐的过程中,用于微调模型的数据受到各种复杂的主观因素的影响,包括:

  • 生产演示数据的贴标机的偏好。
  • 设计研究和撰写标签说明的研究人员。
  • 由开发人员精心制作或由 OpenAI 客户提供的提示选择。
  • 贴标机偏差既包含在奖励模型训练(通过对输出进行排序)中,也包含在模型评估中。

特别是,作者指出了一个显而易见的事实,即参与训练过程的标注者和研究人员可能并不代表语言模型的所有潜在最终用户。

除了这一明显的“内在”限制,我们还想指出该方法的其他一些可能的缺点、没有明确解决的问题以及一些未解决的问题:

****缺乏对照研究:报告的结果以 SFT 模型为基线,测量最终 PPO 模型的性能。这可能会产生误导:我们如何知道这些改进实际上是由于 RLHF?一个适当的(然而昂贵的)控制研究应该包括投入与训练奖励模型完全相同的贴标机小时数,以创建一个更大的具有高质量演示数据的精选 SFT 数据集。这样就可以客观地衡量 RLHF 方法相对于监督方法的性能改进。简而言之,缺乏这样的控制研究使得一个基本问题完全悬而未决:RLHF 在对齐语言模型方面做得好吗?

缺乏比较数据的基础事实**:标签员经常会在模型输出的排序上有分歧。从技术上来说,风险是将一个高潜在方差添加到没有任何基础事实的比较数据中。

****人类的偏好并不是同质的:RLHF 方法将人类的偏好视为同质和静态的。假设所有人都有相同的价值观显然是牵强附会的,至少在人类知识的大量主题上是如此。最近的一些研究开始以不同的方式解决这个公开的问题。

奖励模型(RM) 的提示稳定性测试:似乎没有实验研究奖励模型在输入提示变化方面的敏感性。如果两个提示在语法上不同,但在语义上等价,那么 RM 能在模型输出的排序上显示出显著的差异吗?更简单地说,提示的质量对 RM 有多重要?**

****引线型问题:在 RL 方法中,模型有时可以学会操纵自己的奖励系统来实现期望的结果,从而导致“过度优化的策略”。这可以推动模型重新创建一些模式,由于某种未知的原因,这些模式使奖励模型得分很高(参见 OpenAI 的这篇论文中的表 29,这是语言建模中这种行为的一个明确示例)。ChatGPT 用奖励函数中的 KL 惩罚项对此进行了修补。请注意,我们试图优化 RM 输入(即 PPO 输出),以改善其输出(奖励分数),同时约束输入本身不要离某个参考输入(SFT 输出)太远。在最近的预印本中有更多关于这种方法局限性的细节。

供进一步阅读的精选参考文献

**喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow**

DALL-E 2 实际上是如何工作的

原文:https://www.assemblyai.com/blog/how-dall-e-2-actually-works/

OpenAI 的开创性模型 DALL-E 2 在月初上市,为图像生成和处理树立了新的标杆。只有简短的文本提示,DALL-E 2 可以生成全新的图像,以语义合理的方式组合不同和不相关的对象,就像下面的图像,通过输入提示“一碗汤,这是通往另一个维度的门户,作为数字艺术】

Various images generated by DALL-E 2 given the above prompt (source).

DALL-E 2 甚至可以修改现有的图像,创建保持其显著特征的图像变体,并在两个输入图像之间进行插值。DALL-E 2 令人印象深刻的结果让许多人想知道这样一个强大的模型到底是如何工作的。

在这篇文章中,我们将深入了解 DALL-E 2 是如何创造出像上面这样令人震惊的图像。将给出大量的背景信息,解释层次将涵盖所有领域,因此这篇文章适合具有不同水平机器学习经验的读者。让我们开始吧!

DALL-E 2 如何工作:鸟瞰

在深入研究 DALL-E 2 如何工作的细节之前,让我们先对 DALL-E 2 如何生成图像有一个大致的了解。虽然 DALL-E 2 可以执行各种任务,包括上面提到的图像处理和插值,我们将在本文中重点讨论图像生成的任务

A birds-eye view of the DALL-E 2 image generation process (modified from source).

在最高水平上,DALL-E 2 的作品非常简单:

  1. 首先,文本提示被输入到文本编码器中,该编码器被训练成将提示映射到表示空间。
  2. 接下来,一个名为先验的模型将文本编码映射到一个相应的图像 编码,该编码捕获包含在文本编码中的提示的语义信息。
  3. 最后,图像解码器随机生成图像,该图像是该语义信息的视觉表现。

从鸟瞰的角度来看,这就是全部!当然,还有很多有趣的实现细节需要讨论,我们将在下面讨论。如果你想要更多的细节,而不是深入本质,或者你更喜欢观看你的内容而不是阅读它,请随时查看我们在这里的 DALL-E 2 视频:

https://www.youtube.com/embed/F1X4fHzF4mQ?feature=oembed

DALL-E 2 的工作原理:详细介绍

现在是时候分别深入上述每个步骤了。让我们从看看 DALL-E 2 如何学习链接相关的文本和视觉抽象开始。

步骤 1 -链接文本和视觉语义

输入“一只泰迪熊在时代广场骑滑板”后,DALL-E 2 输出如下图:

source

DALL-E 2 如何知道“泰迪熊”这样的文本概念是如何在视觉空间中表现出来的?DALL-E 2 中文本语义和它们的视觉表示之间的链接由另一个名为剪辑的 OpenAI 模型学习(ContrastiveLanguage-ImagePre-training)。

CLIP 对数亿张图像及其相关说明进行训练,学习给定的文本片段与图像的关联程度 。也就是说,CLIP 不是试图预测给定图像的标题,而是学习任何给定标题与图像的关联方式。这个对比而不是预测目标允许 CLIP 学习同一抽象对象的文本和视觉表示之间的联系。整个 DALL-E 2 模型取决于 CLIP 从自然语言学习语义的能力,所以让我们看看 CLIP 是如何被训练来理解其内部工作的。

剪辑训练

训练剪辑的基本原理非常简单:

  1. 首先,所有的图像和它们相关的标题通过它们各自的编码器,将所有的对象映射到一个 m- 维空间。
  2. 然后,计算每个(图像,文本)对的余弦相似度。
  3. 训练目标是同时最大化 N 个正确的编码图像/字幕对之间的余弦相似度最小化 N 个2 - N 个不正确的编码图像/字幕对之间的余弦相似度

这一培训过程如下图所示:

https://www.assemblyai.com/blog/content/media/2022/04/CLIP_training-1.mp4

Overview of the CLIP training process

其他培训详情

有关 CLIP 训练过程的更多信息,请参见下文。

  • 余弦相似度
    • 两个向量的余弦相似度就是两个向量的点积乘以它们的大小的乘积。它测量向量空间中两个向量之间的角度;并且在机器学习的上下文中,确定两个向量彼此“相似”的程度。如果我们认为向量空间中的每个“方向”都有一个意义,那么两个编码向量之间的余弦相似性度量了向量所表示的概念有多“相似”。
  • 训练数据
    • CLIP 在 WebImageText 数据集上进行训练,该数据集由 4 亿对图像及其相应的自然语言字幕组成(不要与基于维基百科的图像文本混淆)
  • 并行性
    • CLIP 训练过程的并行性显而易见——所有编码和余弦相似性都可以并行计算。
  • 文本编码器架构
  • 图像编码器架构

剪辑对 DALL-E 2 的意义

CLIP 对 DALL-E 2 很重要,因为它最终决定了自然语言片段与视觉概念的语义关联程度,这对于文本条件图像生成至关重要。

附加说明

CLIP 的对比目标使其能够以一种仅学习特征映射的卷积模型无法理解的方式理解语义信息。通过对比以零触发方式使用的 CLIP 相对于 ImageNet 训练的 ResNet-101 如何在数据集上执行,可以很容易地观察到这种差异。特别是,对比这些模型在 ImageNetImageNet Sketch 上的对比,很好地揭示了这种差距。

CLIP 和经过 ImageNet 训练的 ResNet-101 在 ImageNet 上的表现精度相当,但 CLIP 在 ImageNet Sketch 上的表现明显优于 ResNet-101。尽管 CLIP 以零拍摄方式使用,并且没有使用 130 万张 ImageNet 图像中的任何一张进行训练,但情况确实如此。

这个结果很重要,因为它表明 CLIP 学习了对象的文本描述和它们相应的视觉表现之间的语义链接。CLIP 不是依赖图像实例的特定细节(如香蕉的黄色)来识别它们,而是学习香蕉的语义“柏拉图式理想”,从而更好地识别香蕉的草图。理解文本描述和视觉特征可以映射到同一个“柏拉图理想”的事实对于文本条件图像生成是至关重要的,这就是为什么 CLIP 对 DALL-E 2 范式如此重要。

步骤 2 -从视觉语义生成图像

训练后,剪辑模型被冻结,DALL-E 2 进入下一个任务——学习反转剪辑刚刚学习的图像编码映射。CLIP 学习一种表示空间,在这种空间中很容易确定文本和视觉编码的相关性,但我们的兴趣在于图像的生成。因此,我们必须学会如何利用表征空间来完成这项任务。

具体来说,OpenAI 采用了它以前的另一个模型 GLIDE 的修改版本来执行这个图像生成。滑动模型学习反转图像编码过程,以便随机解码剪辑图像嵌入。

*

An image of a Corgi playing a flamethrowing trumpet passed through CLIP's image encoder. GLIDE then uses this encoding to generate a new image that maintains the salient features of the original. (modified from source)*

如上图所示,应该注意的是,我们的目标是而不是构建一个自动编码器,并精确地重建一幅嵌入后的图像,而是生成一幅保留了原始图像嵌入后的显著特征的图像。为了执行这个图像生成,GLIDE 使用了一个扩散模型

什么是扩散模型?

扩散模型是一项受热力学启发的发明,近年来越来越受欢迎12。扩散模型通过逆转逐渐的噪声过程来学习生成数据。如下图所示,加噪过程被视为一个参数化的马尔可夫链,它逐渐向图像添加噪声以破坏图像,最终(渐进地)产生纯高斯噪声。扩散模型学会沿着这条链向后导航,通过一系列时间步长逐渐去除噪声,以逆转这一过程。

*

Diffusion Model schematic (source).*

如果扩散模型在训练后被“切成两半”,那么它可以用于通过随机采样高斯噪声来生成图像,然后对其去噪以生成照片般逼真的图像。有些人可能会认为这种技术很容易让人联想到用自动编码器生成数据,而扩散模型和自动编码器实际上是相关的

*想了解更多关于扩散模型的知识吗?

查看我们的机器学习扩散模型介绍文章!

Check it Out*

滑行训练

虽然 GLIDE 不是第一个扩散模型,但它的重要贡献在于对它们进行了修改,以允许文本条件图像生成。特别是,人们会注意到扩散模型从随机采样的高斯噪声开始。起初还不清楚如何定制这个过程来生成特定于图像。如果在人脸数据集上训练扩散模型,它将可靠地生成人脸的照片级真实感图像;但是如果有人想生成一张具有特定特征的脸,比如棕色眼睛或金色头发,该怎么办呢?

GLIDE 通过用额外的文本信息增加训练过程来扩展扩散模型的核心概念,最终导致文本条件图像生成。让我们来看看 GLIDE 的培训流程:

*https://www.assemblyai.com/blog/content/media/2022/04/openshot_project.mp4

GLIDE training process.

*其他培训详情

关于滑行训练过程的更多信息可以在下面找到。

  • 上采样
    • 通过反向扩散过程生成的图像是 64 x 64,因此作者也以类似的方式训练基于文本的上采样模型,以使生成的数据达到 1,024 x 1,024。
  • 消融扩散模型
    • OpenAI 首先通过其烧蚀扩散模型 (ADM)解决了这个问题,该模型最初只包括类别调节。OpenAI 用 GLIDE 扩展了这个概念,以概括扩散模型条件,使其包括一般自然语言
    • ADM 最初是为了将扩散模型生成真实感图像的能力与文本条件模型以语义上合理的方式合并不相关对象的能力相结合而创建的。
    • ADM 论文中还包括一项消融研究,旨在探索优化扩散模型架构的主题(因此消融了扩散模型)。这种探索的细节超出了本文的范围,但是感兴趣的读者应该参考链接的文章以获得更多的细节。*

这里有一些用 GLIDE 生成的图片的例子。作者指出,在照片真实感和字幕相似性方面,GLIDE 比 DALL-E (1)表现得更好。

*

Examples of images generated by GLIDE (source).*

DALL-E 2 使用了一种改进的滑动模型,它以两种方式结合了投影剪辑文本嵌入。第一种方法是将剪辑文本嵌入添加到 GLIDE 的现有时间步长嵌入中,第二种方法是创建四个额外的上下文标记,这些标记连接到 GLIDE 文本编码器的输出序列中。

滑翔对 DALL-E 2 的意义

GLIDE 对 DALL-E 2 很重要,因为它允许作者通过在表示空间中调整图像编码来轻松地将 GLIDE 的文本条件真实感图像生成功能移植到 DALL-E 2。因此,DALL-E 2 的改进 GLIDE 学习根据剪辑图像编码生成语义一致的图像。同样重要的是要注意,反向扩散过程是随机的,因此通过修改的滑动模型多次输入相同的图像编码矢量,可以容易地产生变化。

步骤 3 -从文本语义到相应的视觉语义的映射

虽然改进的 GLIDE 模型成功地生成了反映图像编码所捕获的语义的图像,但是我们如何实际着手找到这些编码表示呢?换句话说,我们如何将提示中的文本条件信息注入到图像生成过程中呢?

回想一下,除了我们的图像编码器,CLIP 还学习了一个文本编码器。DALL-E 2 使用了另一种模型,作者称之为之前的,以便将从图像标题的文本编码映射到它们对应图像的** 图像编码。DALL-E 2 的作者对先验的自回归模型和扩散模型都进行了实验,但最终发现它们产生了相当的性能。鉴于扩散模型的计算效率更高,它被选为 DALL-E 2 的先验模型。**

*

Prior mapping from a text encoding to its corresponding image encoding (modified from source).*

前期培训

DALL-E 2 中的扩散先验由一个只有解码器的变换器组成。它通过因果注意力面具对有序的序列进行操作

  1. 标记化的文本/标题。
  2. 这些标记的片段文本编码。
  3. 扩散时间步长的编码。
  4. 噪声图像通过剪辑图像编码器。
  5. 最终编码,其来自 Transformer 的输出用于预测无噪声剪辑图像编码。

*其他培训详情

关于先前培训过程的更多信息可在下面找到。

  • 字幕条件
    • 扩散先验不仅取决于字幕的剪辑文本嵌入,还取决于字幕本身。前者是后者的确定性函数,因此这种双重条件是完全允许的。
  • 无分类器引导
    • 为了提高样本质量,通过丢弃文本条件信息,在 10%的时间内使用无分类器指导随机进行采样。
  • 双样本生成
    • 为了提高采样期间的质量,利用先验生成两个图像嵌入,并且选择具有较高点积的图像嵌入和文本嵌入。不清楚为什么作者在这里使用点积而不是余弦相似度。
  • 我们为什么需要先验知识?
    • 作者指出,训练这样的先验知识对于字幕到图像模型来说并不是绝对必要的。一种选择是只以标题本身为条件。这将简单地产生模型滑翔,作者在论文中对两者进行了全面的分析比较。另一种选择是将剪辑文本嵌入馈送到解码器中,而不是使用先验来从其生成剪辑图像嵌入,然后使用它。作者通过实验发现,前者产生了合理的结果,尽管结果不如后者好。最终,使用先验提高了图像多样性。*

第四步——把所有东西放在一起

至此,我们已经拥有了 DALL-E 2 的所有功能组件,只需要将它们链接起来就可以生成文本条件图像了:

  1. 首先,剪辑文本编码器将图像描述映射到表示空间
  2. 然后扩散先验从剪辑文本编码映射到对应的剪辑图像编码
  3. 最后,修改的滑动生成模型经由反向扩散从表示空间映射到图像空间,生成传达输入字幕内的语义信息的许多可能图像之一。

*

High-level overview of the DALL-E 2 image-generation process (modified from source).* *喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow*

摘要

在本文中,我们介绍了世界上首屈一指的基于文本的图像生成模型是如何工作的。DALL-E 2 可以在给定文本提示的情况下生成语义上似乎真实的照片级图像,可以生成具有特定艺术风格的图像,可以生成以不同方式表示的相同显著特征的变体,并且可以修改现有图像。

虽然有很多关于 DALL-E 2 及其对深度学习和整个世界的重要性的讨论,但我们提请您注意 DALL-E 2 开发过程中的 3 个关键要点

  1. 首先,DALL-E 2 展示了深度学习中扩散模型 的**威力,DALL-E 2 中之前的图像生成子模型都是基于扩散的。虽然扩散模型在过去几年才得到广泛使用,但它已经证明了自己的价值,那些关注深度学习研究的人应该会在未来看到更多这样的模型。**
  2. 第二点是强调使用自然语言作为训练 最先进的深度学习模型的手段的必要性和**力量。这一点并非起源于 DALL-E 2(特别是 CLIP 之前演示过的),但尽管如此,重要的是要认识到 DALL-E 2 的强大最终源于互联网上绝对的海量配对自然语言/图像数据。使用这样的数据不仅消除了与手工标注数据集的费力和艰苦过程相关的发展瓶颈;但是这种数据的嘈杂、不精确的性质更好地反映了深度学习模型必须鲁棒的真实世界数据。**
  3. 最后,DALL-E 2 重申了 Transformers 在网络规模数据集上训练的模型中的至高地位,因为它们具有令人印象深刻的并行性。

参考

  1. 使用非平衡热力学的深度无监督学习
  2. 通过估计数据分布的梯度生成模型
  3. 带剪辑潜在时间的分层文本条件图像生成
  4. 扩散模型在图像合成上击败 GANs
  5. 去噪扩散概率模型
  6. 从自然语言监督中学习可转移的视觉模型
  7. GLIDE:使用文本引导扩散模型实现真实感图像生成和编辑

Imagen 的实际工作原理

原文:https://www.assemblyai.com/blog/how-imagen-actually-works/

当机器学习世界仍在接受今年早些时候发布的 DALL-E 2 的令人印象深刻的结果时,谷歌加大了赌注,发布了自己的文本到图像模型 Imagen,,这似乎进一步推动了字幕条件图像生成的边界。

上个月刚刚发布的 Imagen 可以在只给出场景描述的情况下生成高质量、高分辨率的图像,而不管这样的场景在现实世界中可能有多么合理或可信。下面你可以看到这些图片的几个例子,下面有相应的标题:

Images created by Imagen given the corresponding captions (adapted from source)

这些令人印象深刻的结果无疑让许多人想知道 Imagen 实际上是如何工作的。在本文中,我们将从几个层面解释 Imagen 是如何工作的。

首先,我们将从鸟瞰图来检查 Imagen,以便理解它的高级组件以及它们之间的关系。然后,我们将更详细地讨论这些组件,每个组件都有自己的小节,以便理解它们本身是如何工作的。最后,我们将对 Imagen 进行一次深度探索,这是为机器学习研究人员、学生和从业者准备的。

事不宜迟,我们开始吧!

介绍

在过去的几年里,机器学习的文本到图像领域已经取得了重大进展。文本到图像模型接受场景的简短文本描述,然后生成反映所描述场景的图像。输入描述(或“标题”)和输出图像示例如下所示:

Input and output of a text-to-image model (image and description pulled from source)

值得注意的是,高性能的文本到图像模型将必然能够以语义上合理的方式组合不相关的概念和对象。因此,这些模型必须克服一些挑战,比如捕捉空间关系、理解基数,以及正确解释描述中的单词如何相互关联。如果我们更仔细地检查上图中的图像,我们可以看到 Imagen 在这些方面的表现很好。

Imagen captures semantic and logical relationship well (image adapted from source)

很难夸大这些模型是多么令人印象深刻。他们没有使用数据库来搜索并返回与描述相匹配的图像,他们甚至没有以一种清晰的方式将预先存在的子图像“缝合”在一起,以使结果与标题相对应。相反,它们生成的完全是 新颖的图像,在视觉上传达了字幕中包含的语义信息。

想学习如何构建 Imagen?

查看我们的 MinImagen 文章,在这篇文章中,我们学习了如何一步一步地构建一个最小的 Imagen 实现,包括一个完整的工作库!

Check it out

既然我们了解了什么是文本到图像模型,我们可以从一个鸟瞰图来看看文本到图像模型 Imagen 如何工作。

Imagen 如何工作:鸟瞰

在本节中,我们将了解 Imagen 的主要组件是做什么的,以及它们是如何相互关联的。首先,我们将查看 Imagen 的总体架构,并对其工作原理进行高级解释,然后在下面的小节中更彻底地检查每个组件。

下面是一个简短的视频,概述了 Imagen 的工作原理,并对下面发生的事情进行了细分:

https://www.assemblyai.com/blog/content/media/2022/06/birds_eye.mp4

How Imagen works (bird's-eye view)

  1. 首先,字幕被输入到文本编码器中。这个编码器将文本标题转换成数字表示,然后将语义信息封装在文本中。
  2. 接下来,图像生成模型通过从噪声或“电视静电”开始创建图像,并慢慢将其转换为输出图像。为了指导这个过程,图像生成模型接收文本编码作为输入,其效果是告诉模型标题中有什么,以便它可以创建相应的图像。输出是一个小图像,它直观地反映了我们输入到文本编码器的标题。
  3. 然后,小图像被传递到一个超分辨率模型,该模型将图像放大到更高的分辨率。该模型还将文本编码作为输入,这有助于模型决定如何表现,因为它“填补了缺失信息的空白”,这些信息必然是由于我们的图像大小翻两番而产生的。结果是一个我们想要的中等大小的图像
  4. 最后,这个中等大小的图像然后被传递到另一个超分辨率模型中,这个模型的操作与前一个模型几乎相同,只是这次它将我们的中等大小的图像扩展为高分辨率的图像。结果是 1024 x 1024 像素的图像,直观地反映了标题中的语义。

在最高级别,这就是它的全部!要更详细地了解 Imagen 的每个主要组件,请查看下面的小节。否则,你可以直接跳到章节深入了解 Imagen 的工作原理,或者直接跳到的最后一句话

文本编码器

在这一小节中,我们将仔细看看 Imagen 的文本编码器。从上面的视频中我们可以看出,文本编码器对 Imagen 的性能至关重要。它调节 Imagen 的所有其他组件,并负责以一种有用的方式对文本标题进行编码。

Imagen 中的文本编码器是一个 Transformer 编码器。如果你对变形金刚不熟悉,不用担心。这里最重要的细节是,这种编码器确保文本编码理解标题中的单词如何相互关联(通过一种称为“自我关注”的方法)。这是非常重要的,因为英语通过其句法结构对信息进行编码,从而影响给定句子的语义。

如果 Imagen 只关注单个单词,而不关注它们之间的关系,我们可以获得捕捉字幕单个元素的高质量图像,但不能以适当反映字幕语义的方式描绘它们。我们可以在下面的例子中看到这种差异,在这个例子中,如果不考虑词与词之间的关系,结果会非常糟糕(尽管很好笑):

小纸条

虽然上面的例子旨在强调对变压器编码器/自我关注的需要,但结果甚至不如“不正确”的图像好。例如,如果没有理解单词之间句法关系的手段,该模型将不会理解“站立”需要直接和间接宾语,因此将无法正确地描绘这一概念。

文本编码器在训练期间被冻结,这意味着它不会学习或改变它创建编码的方式。只有被用来生成编码,这些编码被提供给模型的其余部分,而是经过训练的。

现在,我们对 Imagen 的文本编码器有了更多的了解,让我们看看实际生成图像的组件。

图像生成器

文本编码器为 Imagen 的标题输入生成一个有用的表示,但是我们仍然需要设计一个方法来生成一个使用这个表示的图像。为了做到这一点,Imagen 使用了一个扩散模型,这是一种生成模型,由于它在几个任务上的最先进的性能,近年来获得了极大的欢迎。在继续之前,现在让我们看一下扩散模型的简要回顾。

什么是扩散模型?

扩散模型是一种创建类似于一组训练数据的数据的方法。他们通过添加噪声来破坏训练数据,然后通过逆转这一噪声过程来学习恢复数据。给定一个输入图像,扩散模型将在一系列时间步长中用高斯噪声反复破坏图像,最终留下纯高斯噪声,或“电视静态”。

Diffusion Models' iterative noising process (modified from source)

然后,扩散模型将反向工作,学习如何在每个时间步隔离和消除噪声,撤销刚刚发生的破坏过程。

一旦经过训练,模型就可以“一分为二”,我们可以从随机采样的高斯噪声开始,使用扩散模型逐渐去噪,以生成图像。

A Diffusion Model starts from noise and "denoises" it to generate images (modified from source)

下面我们可以看到一个从纯高斯噪声生成手写数字的例子:

A Diffusion Model can generate novel handwritten digits from Gaussian noise (source)

对于扩散模型的完整处理,请随意查看我们的文章。

Introduction to Diffusion Models for Machine Learning

字幕条件

总之,训练的扩散模型从高斯噪声开始,然后迭代地生成与其被训练的图像相似的图像。在这一点上可能很明显,我们无法控制实际输出什么图像——我们只是将高斯噪声输入到模型中,它就会吐出一个看起来像是属于训练数据集的随机图像。回想一下我们的目标是创建图像,这些图像封装了我们输入到 Imagen 中的标题的语义信息,因此我们需要一种将标题合并到扩散过程中的方法。我们如何做到这一点?

回想一下,我们的文本编码器产生了标题的代表性编码。这种“编码”实际上是一系列向量。为了将这种编码信息注入到扩散模型中,我们将这些向量汇集在一起,并以它们为条件建立我们的扩散模型。通过调节这个向量,扩散模型学习如何调整它的去噪过程,以便产生与字幕很好对准的图像。这个过程可以在下面的视频中看到:

https://www.assemblyai.com/blog/content/media/2022/06/cap_cond.mp4

Conditioning the image-generation process on the caption.

图像超分辨率

图像发生器或“基本”模型输出一个小的 64x64 图像。为了将该模型上采样到最终的 1024×1024 版本,我们使用超分辨率模型来智能地上采样图像。

对于超分辨率模型,Imagen 再次使用扩散模型。整体流程与基础模型基本相同;除此之外,我们不是只对字幕编码进行调节,而是对正在进行上采样的较小图像进行调节。整个过程可以在下面的视频中看到:

https://www.assemblyai.com/blog/content/media/2022/06/super_res.mp4

这个超分辨率模型的输出实际上不是我们的最终输出,而是一个中等大小的图像。为了将该图像放大到最终的 1024x1024 分辨率,然而使用了另一个超分辨率模型。这两种超分辨率架构大致相同,因此我们不会深入讨论第二种超分辨率模型的细节。

第二超分辨率模型的输出是 Imagen 的最终输出。

摘要

总之,字幕被输入到预训练和冻结的变压器编码器中,该编码器输出一系列矢量(文本编码)。这些向量很重要,因为它们对标题中的单词如何相互关联进行编码,并充当模型所有其他组件的条件信息。

文本编码然后被传递到图像生成扩散模型,该模型从高斯噪声开始,然后逐渐去除噪声,以生成反映字幕内语义信息的新图像。该模型的输出是 64x64 像素的图像。

在此之后,使用另外两个扩散模型将该图像超解析为最终的 1024×1024 大小,再次以文本编码为条件(以及较低分辨率的图像)。

现在我们已经对 Imagen 有了一个大致的了解,我们可以在下一节深入了解细节。或者,可以随意跳到结果和分析最终结果

Imagen 是如何工作的:深入探讨

在下面的章节中,我们将深入研究 Imagen 的每个组件,突出某些结构特征设计选择逻辑。我们从文本编码器开始。

文本编码器

Imagen 中的文本编码器是 Google 在 2019 年发布的语言模型 T5(Text-To-TextTtransferTtransformer)的编码器网络。T5 是一个文本到文本的模型,它通过将许多自然语言处理任务组织成文本到文本的问题,作为这些任务的通用框架。我们可以在下图中看到这种方法的几个例子,其中翻译、句子可接受性确定(cola)、句子相似性估计(stsb)和摘要都是以这种方式进行的。

(source)

T5 旨在为任何可以以这种文本到文本的方式转换的 NLP 任务进行微调。

迁移学习概述

回想一下,迁移学习是一种技术,通过这种技术,在大型、多样化和通用的数据集上训练非常大的模型(称为“预训练”),以便提供具有“通用知识”的基础模型。给定一个任务和相关数据集,然后根据需要修改模型,并在特定于任务的数据集上进行训练(称为“微调”),使用“一般知识”来更快地学习特定任务。

在计算机视觉领域中,这个过程可以表现为训练自动编码器,以便学习对任何任务都有用的特征图,假设所有对象都由相同的元素组成-边缘、角、纹理、颜色等。一旦学习了这些特征图,就可以提取编码器,并且例如可以在其上构建分类器,然后在特定于任务的数据集上进行训练,利用已经学习的特征提取器,以便更快地学习分类网络,在这种情况下,确定输入图像是否是比萨饼之一。

Imagen 为什么用 T5?

一些其他的文本到图像模型,如 DALL-E 2,使用文本编码器,这些编码器根据图像-标题对和一个相关的目标进行训练,该目标是为了链接相同语义概念的文本和视觉表示而明确设计的。这种以图像相邻方式训练的编码器,似乎比不考虑图像域而训练的 NLP 专用文本编码器更自然地映射到文本到图像生成的问题。因此,询问 Imagen 作者为什么选择 T5 作为 Imagen 的文本编码器是明智的。

使用 T5 的主要直觉是超大型语言模型,仅仅凭借其庞大的规模,仍然可以学习有用的表示,尽管事实上它们并没有明确地接受任何文本/图像任务的训练。除了某些语言模型的规模非常大之外,语言模型在纯文本语料库上训练也是事实,纯文本语料库可能显著大于成对文本/图像的数据集。可以说,用来训练模型的数据集的大小和质量比模型本身的细节更重要。事实上,由于其数据集,T5 的性能与 BERT 相当,即使只有 25%的训练时间。庞大的模型和大规模、多样化的数据集的结合为学习可能在其他领域有用的强大的文本表示留下了大量的空间。

因此,这一选择所解决的核心问题是在独立于图像生成任务的大规模数据集上训练的大规模语言模型对于非专业文本编码器来说是否是值得的权衡。Imagen 的作者将赌注压在了大型语言模型上,这一赌注看起来回报不错。

图像生成器

如上所述,Imagen 中的图像生成器是一个扩散模型,考虑到他们过去几年令人难以置信的进步,这是一个不足为奇的选择。你可以在上面的中找到扩散模型的简要回顾,或者查看我们专门的机器学习扩散模型介绍以获得完整的处理。

假设对扩散模型有了基本的理解,我们现在将探索 Imagen 实现的细节。

网络体系结构

扩散模型实际上是一种“元模型”框架,它告诉我们如何使用 神经模型对图像去噪。神经模型本身的架构还有待讨论或建立,并且对神经模型的唯一相关限制是其输入和输出维度必须相同。也就是说,图像在扩散过程中不能改变尺寸。

考虑到这种限制,作者选择了一种常见的 U-Net 架构,这种架构通常不修改由 Nichol 和 Dhariwal 实现的架构。我们可以看到 U-Net 的总体架构如下:

*

replace*

每个残差块由两个子块组成,这些子块中的每一个都依次由批归一化、ReLU 和 3×3 卷积组成。

时间步长调节

在 Imagen 中(以及通常作为整体的扩散模型),在每个时间步使用相同的去噪 U-Net。回想一下,在扩散模型中,不同数量的噪声在不同的时间步长被去除。因此,我们必须设计一种方法将时间步长信息注入模型(即时间步长上的条件)。Imagen 作者利用了最初的 Transformer 论文中介绍的一种技术,称为位置编码

*位置编码-其他详细信息

最初的语言转换器旨在用于句子,在这种上下文中,单词的顺序至关重要;然而,Transformer 编码器对集合进行操作,这意味着词序对它们来说并不重要。为了将位置信息注入到变压器中,作者使用了一种巧妙的方法,为每个单词索引生成一个唯一位置编码向量,其中位置向量的维数与单词嵌入向量的维数相同。通过将这些位置编码添加到字嵌入中,创建了位置编码的字嵌入,其将位置信息注入到变换器中。

这种现象在下面的视频中被形象化了。很容易看出,尽管单词“really”的两个实例具有相同的单词嵌入,但是由于它们的位置编码向量不同,它们的位置编码单词嵌入是不同的。

对于具有 8 个通道的 3×3 图像的情况,该过程在下面被可视化:

https://www.assemblyai.com/blog/content/media/2022/06/time_enc.mp4

虽然这不是 Imagen 中时间步长调节的确切工作方式,但它非常接近,事实上与其他一些扩散模型注入时间步长信息的方式相同。请注意,任何扩散模型都需要某种形式的时间步长编码(至少通常是这样实现的),并且不是 Imagen 独有的。

字幕条件

我们还没有将图像标题中的信息整合到扩散模型 U-Net 中,所以我们现在需要这样做。这种字幕调节以两种方式发生。

首先,来自 T5 文本编码器的输出向量被汇集并添加到从上面嵌入的时间步长中。下图显示了这一过程:

接下来,通过在几个分辨率的文本嵌入上添加交叉注意力,该模型以整个编码序列为条件。交叉注意通过将文本嵌入序列连接到每个自我注意层的键值对来实现。

实施细节

此外,我们发现层规范化对于注意力层和池层中的文本嵌入都至关重要。

无分类器制导

Imagen 还利用了无分类器的指导。无分类器引导是一种以图像多样性为代价增加扩散模型的图像保真度的方法。该方法如此命名是因为它是用于相同目的的称为分类器指导的先前方法的相关和更简单的版本/扩展。

分类器导航

分类器指导是一种折衷扩散模型生成的图像的保真度和多样性的方法。这种方法需要一个经过训练的分类器模型,用于将扩散过程推向高类别概率的特征区域。也就是说,分类器用于将图像导向分类器自己的模式。

考虑以下等式:

如果 x 是图像, y 是类别标签,那么我们可以看到类别标签上条件对数概率密度的梯度相当于无条件对数梯度加上分类器对数梯度对应的条件项。缩放条件项相当于将分类器分布的权重推向其模式,鼓励向更可能用于给定类别的图像的扩散过程。在下面的视频中,你可以看到将高斯(蓝色)的权重推向它的模式如何影响它在几个点(红色)的导数。

如前所述,这种方法的理论成本是多样性,因为将鼓励图像具有给定类别中经常观察到的特征。这种方法的实际成本是:( 1)除了扩散模型之外,还需要训练分类器,以及(2)当条件项被缩放得太高时(“指导权重”太高),图像质量差。

无分类器制导的工作原理是将一个扩散模型同时训练成有条件和无条件的。为了做到这一点,扩散模型被转换为条件模型,并且用随机丢弃一小部分时间的条件信息来训练(通过用空值替换条件信息)。为了无条件地使用模型,空值只是作为“条件信息”提供给模型。

给定这样的模型,无分类器制导通过在推理期间在无条件和有条件梯度之间插值来松散地工作。通过放大条件梯度的效果(即,使“指导权重”大于 1),可以获得更好的样本:**

*

Comparison of unguided and guided GLIDE images generated by the prompt "a religious place" (source)*

虽然无分类器导航首先是由 Ho 和 Salimans 引入的,但它很快就被用于 OpenAI 的 GLIDE 中,以便创建非常高质量(尽管多样性较低)的图像。关于分类器/无分类器导航的大量资源,请查看这篇文章。

根据 Imagen 的论文, Imagen 非常依赖无分类器指导来进行有效的文本处理

大型制导重量取样器

无分类器导航是一种非常有效的方法来改善生成图像的字幕对齐,但是已经 之前 观察到极高的导航权重会产生饱和且不自然的图像,从而损害保真度。

Imagen 的作者研究了这一现象,发现这是由训练测试不匹配引起的。特别地,训练数据的像素值被缩放到范围[-1,1],但是高指导权重导致网络输出在给定的时间步长超过这些界限。更糟糕的是,由于同一个模型在扩散过程中反复应用于其自身的输出,这种效应随着扩散过程的进行而加剧,甚至可能导致发散。

高制导权重被发现对于实现最先进的图像质量是至关重要的,因此通过简单地使用较低的制导权重来避免这个问题不是一个选项。相反,作者通过设计两种阈值像素值的方法来解决这个问题- 静态阈值动态阈值。这些方法解决了上述训练测试不匹配的问题,特别是发现动态阈值对 Imagen 的性能至关重要。

静态阈值

在静态阈值处理中,每个时间步长的像素值被简单地限制在范围[-1,1]内。这个过程可以在下面的例子中看到。

为了销售的例子,让我们的像素值正态分布。对这些值应用静态阈值处理意味着像素边界(浅红色区域)之外的任何分布权重对于负值被推至-1,对于正值被推至 1。正如我们所见,随着分布方差的增长,出现极值的概率也在增长。

*https://www.assemblyai.com/blog/content/media/2022/06/static_threshold.mp4

Visualization of static thresholding. Distribution weight outside of the pixel bounds are pushed onto the pixel value extrema.

虽然这有一定的缓解效果,但随着制导重量的增加,图像仍然会过饱和,并且不太详细。因此,作者设计了一种更好的阈值分割方法——动态阈值分割法。

动态阈值

利用动态阈值处理,选择某个百分比的绝对像素值。在每个时间步,如果百分点值 s 超过 1,则像素值被阈值化为[- ss ]并除以 s 。这个过程可以在下面的视频中看到:

*https://www.assemblyai.com/blog/content/media/2022/06/dynamic_threshold.mp4

Visualization of dynamic thresholding. The distribution has a "gravitational pull" back towards 0, affecting all pixels and not just saturated ones.

动态阈值处理具有将所有像素值带回到范围[-1,1]的效果,但是对所有像素进行操作,而不仅仅是那些处于极端的像素。在迭代应用的模型下,有一个回到 0 的“引力”平衡了发散的可能性。

作者发现,这种方法导致更好的照片真实感和对准,特别是对于大制导重量。

比较

下面是几个图像,描述了增加三个模型的指导权重的效果——一个是未设定阈值的,一个是静态设定阈值的,一个是动态设定阈值的。每个型号都显示了两个不同的点(“pt1”和“pt2”),这两个点在不同型号之间是相同的。对于每个模型和每个点,对应于不同的制导重量(唯一的变化)显示几个图像。

*

As the guidance weight is turned up (down each column), differences in quality can be observed based on thresholding method. (adapted from source)*

正如我们所看到的,静态阈值处理提高了性能,至少对于高引导权重(即使饱和)产生了合理的图像,不像非阈值处理的图像几乎完全是黑色的。动态阈值处理显著提高了性能,即使在高指导权重下也能产生合理且不饱和的图像。虽然静态和动态阈值处理在低指导权重下产生相似的结果,但是在高指导权重下图像饱和度的差异变得更加明显。当比较两种方法的左栏时,这些差异尤其明显。

超分辨率模型

回想一下,图像生成器扩散模型(或“基本模型”)输出 64x64 图像。Imagen 使用两种条件扩散模型将图像分辨率提高到 1024x1024。现在让我们检查这些模型。

中小型架构

中小型(STM)超分辨率模型“接受”(取决于)基本模型生成的 64x64 图像,并将其超分辨率为 256x256 图像。STM 模型是另一种扩散模型,并且除了低分辨率图像之外,还取决于字幕编码。交叉注意的实现类似于基本模型。

STM 模型的架构是另一个 U-Net,也是像图像生成器一样改编自 Nichol 和 Dhariwal 。作者对该模型进行了一些修改,以提高记忆效率、推理时间和收敛速度。他们把这个模型叫做高效 U 网,比未修改的版本每秒步数快 2-3 倍。

*其他详细信息

高效 U-Net 做了如下修改:

  1. 移动模型参数:模型参数通过在较低的维度上增加更多的残差块,从高分辨率块向低分辨率块移动。因为这些块具有更多的通道,所以模型容量增加,而没有严重的存储器/计算成本。
  2. 缩放跳过连接:随着较低分辨率下残余块数量的增加,跳过连接被缩放 1/sqrt(2)以提高收敛速度。
  3. 改变操作顺序:在下采样块中,它们改变下采样和卷积操作的顺序,使得卷积在下采样操作之后发生,对于上采样块反之亦然。这在不降低性能的情况下提高了向前传球的速度。* *### 中型到大型架构

中到大(MTL)超分辨率模型将 STM 模型生成的 256x256 图像超分辨率为 1024x1204 图像。MTL 模型非常类似于 STM 模型,是以字幕编码和 STM 输出图像为条件的扩散模型。

除了移除了自我关注层之外,该架构通常类似于 STM 模型。由于该模型中没有自我注意层,与基本模型和 STM 模型相比,增加了明确的交叉注意层来关注文本嵌入。

稳健的级联扩散模型

Imagen 在超分辨率模型中使用噪声调节增强,以使它们意识到添加的噪声量。这种调节提高了样本质量和模型处理由较低分辨率模型产生的伪影的能力。作者发现这种方法对于生成高保真图像至关重要

噪声调节增强通过用高斯噪声破坏超分辨率模型所基于的低分辨率图像,然后根据破坏噪声水平调节超分辨率模型来工作。在训练期间,随机选择破坏噪声水平或“增强水平”,而在推断期间,该值被扫描以找到最佳样本质量。

这些过程的伪代码可以在下面看到,其中作者使用了 JAX 软件包。

(source)

想知道这个伪代码为什么说jnp

这是谷歌的 JAX 包,它越来越受机器学习的欢迎!看看下面我们对 JAX 的概述。

Check it out

深度潜水总结

总的来说,输入字幕被送入 T5 编码器,该编码器在训练期间被冻结。文本编码以基本扩散模型为条件,该模型使用具有低分辨率的自我关注层的 U-Net 来生成图像。文本编码调节通过添加到时间步长调节张量(“位置编码”)以及通过键值连接到自我注意层的交叉注意来发生。实现了动态阈值和无分类器引导。

在生成基本模型图像之后,它通过两个以上的扩散模型以获得超分辨率,除了时间步长和文本编码的条件之外,扩散模型还以它们正在上采样的图像为条件。这些模型使用噪声调节增强来提高质量和消除伪像。

结果是与输入标题匹配的 1024x1024 图像。

结果和分析

数量的

COCO 是一个用于评估文本到图像模型的数据集,FID 用于测量图像保真度,CLIP 用于测量图像字幕对齐。作者发现 Imagen 在 COCO 上实现了 7.27 的最新零拍 FID,超过了 DALL-E 2,甚至超过了在 COCO 上接受过训练的模特。

定性的

作者指出,FID 和 CLIP 都有局限性。FID 与人类感知质量不完全一致,CLIP 在计数方面无效。因此,他们使用人工评估来评估质量和字幕相似性,从 COCO 验证集中随机选择 200 个真实字幕图像对作为基线。受试者分批观看 50 张这样的图片。

质量管理

还使用了交错的“对照”试验,并且只有当评价人正确回答了至少 80%的对照问题时,才包括评价人数据。这为每幅图像的图像质量获得了 73 个评级,为每幅图像的图像标题对齐获得了 51 个评级。

质量

为了探究 Imagen 生成的图像的质量,要求人类评价者使用问题“在 Imagen 生成的图像和参考图像之间进行选择,哪个图像更逼真(看起来更真实)?”。报告了评分者选择 Imagen 生成的图像而不是参考图像的次数百分比,称为偏好率

Imagen 获得了 39.2%的真实感偏好率。

标题相似度

为了探究图像-标题对齐,给评定者看一幅图像和一个标题,并问“标题是否准确地描述了上面的图像?”。评定者必须回答“”、“有点”或“”。响应分别被评分为 100、50 和 0,并且对于模型样本和参考图像独立获得,两者都被报告。

作者发现 Imagen 与原始参考图像的字幕相似度不相上下

拉丝机

注意到 COCO 的几个缺点,作者还介绍了 DrawBench -一组全面而富有挑战性的提示,旨在支持文本到图像模型的评估和比较。

下面显示了 Imagen 与 DALL-E 2、GLIDE、VQGAN+CLIP 和 Laten Diffusion 在拉丝设备上的比较结果,其中条形高度对应于图像保真度和对齐度的用户偏好率(95%置信区间)。显而易见,Imagen 优于所有其他型号

为什么 Imagen 比 DALL-E 2 好?

准确回答为什么 Imagen 比 DALL-E 2 好很难;然而,性能差距的一个不可忽视的部分似乎源于两种模型对标题/提示编码方式的差异。

DALL-E 2 使用对比目标来确定文本编码与图像(本质上是剪辑)的相关程度。文本和图像编码器调整它们的参数,使得相似字幕-图像对的余弦相似性最大化,而不同字幕-图像对的余弦相似性最小化。虽然这个目标在文本到图像领域非常直观,特别是在反映 DALL-E 2 中先验子模型的使用时,但是它有什么缺点呢?我们看到三种可能的选择。

纯粹的尺寸

很有可能,性能差距的一个显著部分源于这样一个事实,即 Imagen 的文本编码器比 DALL-E 2 的大得多,并且根据更多的数据进行训练。作为这一假设的证据,我们可以在文本编码器按比例增加时检查 Imagen 的性能。下面我们看到 Imagen 性能的帕累托曲线,它是编码器尺寸和 U-Net 尺寸的函数。

放大文本编码器的效果高得惊人,放大 U-Net 的效果低得惊人。这一结果表明,相对简单的扩散模型可以产生高质量的结果,只要它们以强大的编码为条件

鉴于 T5 文本编码器比剪辑文本编码器大得多,再加上自然语言训练数据必然比图像-字幕对更丰富,性能差距很大程度上可能归因于这种差异,Imagen 作者注意到了这一事实。

图像编码器拐杖

在剪辑训练期间,文本编码器和图像编码器都被调整以满足目标函数的要求。为了满足这些需求,这些模型中的每一个在多大程度上被调整是有一定自由度的。特别是,CLIP 中的图像编码器可能比文本编码器更快地学会产生更丰富的编码,以适应文本编码器相对较低的性能。这将具有降低整体损失的效果,但同时作为文本编码器的支柱。这种依赖可能会降低文本编码器的表达能力,导致在用于 DALL-E 2 时性能下降。

这个观察有些不直观。剪辑产生了一个空间,在这个空间中,相同概念的文本和视觉表现被理解并与 DALL-E 2 中的先验映射。另一方面,在 Imagen 中,将文本编码映射到视觉概念的责任落在了图像生成器的肩上。然而,在 DALL-E 2 中,先验的弱化“起点”可能会降低先验的有效性,这最终会传播到图像生成子模型。

不同数据点中的相似概念

剪辑文本编码方法的下一个潜在缺点是,最大化对应字幕图像对的余弦相似性同时最小化不同字幕图像对的余弦相似性的总体目标没有考虑不同数据点中的相似概念。特别是,如果两个字幕非常相似,它们将被正确地映射到相似的矢量上,,但是剪辑物镜将把这些矢量推开。更糟糕的是,它们将被推得离得一样远,就像它们中的任何一个与一个高度不相似的字幕向量在一起一样远。根据训练数据集的性质,这种损失有可能削弱已学习的编码。鉴于文本编码对文本到图像扩散模型的重要性,这种行为可能会削弱生成图像的质量。

虽然这些因素中的任何一个都可能导致性能差距,但 Imagen 的其他实施细节肯定与我们的分析相关,其中一些在下面的“关键要点”部分列出。

关键要点

除了上面关于 DALL-E 2 的说明,作者还列出了 Imagen 的几个关键要点,包括:

  1. 缩放文本编码器非常有效
  2. 缩放文本编码器比 U-Net 尺寸更重要
  3. 动态阈值至关重要
  4. 超分辨率模型中的噪声调节增强至关重要
  5. 通过交叉注意力的文本调节至关重要
  6. 高效的 U-Net 至关重要

这些见解为研究扩散模型的其他研究人员提供了有价值的方向,并且不仅仅在文本到图像的子域中有用。

最后的话

Imagen 的结果不言而喻,标志着在文本到图像生成和更广泛的生成模型领域的又一次巨大成功。Imagen 还增加了扩散模型的伟大成就,这些模型在过去几年中以一系列荒谬的令人印象深刻的结果席卷了机器学习世界。

查看我们关于扩散模型的文章以了解更多关于它们的信息,或者查看我们关于 DALL-E 2 的文章以了解更多关于先前的文本到图像生成之王的信息。否则,请随时关注我们的时事通讯,以便及时了解未来类似的文章。

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow**

如何用 Python 给 Mux 视频添加字幕

原文:https://www.assemblyai.com/blog/how-to-add-subtitles-to-your-mux-videos-with-python/

概观

想简化为 Mux 视频添加字幕的过程吗?在本教程中,我们将介绍如何使用 AssemblyAI 的语音转文本 API 以编程方式创建字幕轨道,并使用 Mux 的视频 API 将其添加到 Mux 视频中。

AssemblyAI 是一个用于快速、准确、自动转录视频文件的 API。使用 AssemblyAI API,您可以在几秒钟内完全自动地为您的视频快速生成.srt.vtt格式的字幕文档。

视频托管提供商有很多选择;对于本教程,我们使用 S3 作为视频和.srt文件的存储服务。你可以在 GitHub 上找到本教程的完整源代码。

https://www.youtube.com/embed/V_8yZMNaBjo?enablejsapi=1&origin=https://www.assemblyai.com

先决条件

要完成本教程,您需要

  • 带有语音的 mp4 文件
  • AWS 帐户
  • 用于 Mux 和 AWS 的 Python 库

假设你已经有一个 mp4 视频,首先我们必须下载 Mux 和 AWS 的库。您可以安装这些

pip install boto3
pip install git+https://github.com/muxinc/mux-python.git

步伐

在我们为 AWS 和 Mux Video API 安装了库之后,我们可以自动为 Mux Video 添加字幕。我们需要依次完成以下步骤

  1. 上传 mp4 文件到 S3
  2. 使用 AssemblyAI 的自动语音到文本自动转录来转录视频和下载字幕文件
  3. 上传字幕文件到 S3
  4. 使用 Mux 的视频 API 将视频上传到 Mux

上传到 S3

首先,我们将通过 boto 3(AWS Python SDK)将文件上传到 S3。当我们完成这部分的脚本时,这就是我们完成这个脚本后的样子。

Upload your mp4 to S3

首先,我们将从 AWS 导入我们的访问密钥和秘密密钥,您可以通过 IAM 控制台获得,如图所示。如果您没有存储访问密钥和秘密密钥,您必须单击创建访问密钥并保存您的信息。

Where to find your AWS security keys

我把我的放在 AWS 配置文件中,但是您也可以使用环境变量。对于这个脚本,我们将要求文件名, 确保文件与脚本 在同一个文件夹中,以及我们想要上传到的 S3 桶。在获得我们上传的文件的位置和我们想要上传到的 bucket 之后,我们将使用 boto3 创建一个 s3 客户端。最后,我们将上传我们的对象和它的对象访问控制列表,这将使它公开可读。

import boto3
import logging
from botocore.exceptions import ClientError
from aws_config import access_key, secret_key

filename = input("Input your filename?")
bucket = input("What S3 bucket do you want to upload to?")

s3_client = boto3.client('s3',
   aws_access_key_id = access_key,
   aws_secret_access_key = secret_key)

try:
   response = s3_client.upload_file(filename, bucket, filename)
   s3_client.put_object_acl(ACL='public-read', Bucket=bucket, Key=filename)
except ClientError as e:
   logging.error(e)
   print("Error")

print("Success")

‍After 上传文件后,我们应该导航到 S3 控制台,检查我们的对象是否是公共可读的。单击 mp4 对象的权限,确保它授予公共读取权限,如图所示。

Make sure your object is publicly readable

使用 AssemblyAI 的自动语音转文本 API 来转录视频

现在我们已经上传了我们的文件,是时候通过 AssemblyAI 的语音转文本 API 为它获取字幕了。如果你还没有 AssemblyAI 账户,注册使用他们的免费语音到文本 API 。你需要得到一个 API 密匙,它应该在你的控制台中,如图所示。

How to find your AssemblyAI speech to text API token

在我们最近的帖子中,例如25 行以下的 Python 语音识别如何获得 YouTube 视频的副本,我们已经使用 AssemblyAI 的自动语音识别(ASR)对我们上传的本地文件进行了转录。这一次,我们将传递一个 URL 来转录。当我们完成这个脚本时,它应该看起来像下面的图片。

Use AssemblyAI's automatic speech to text API to turn your mp4 into a subtitle file with timestamps

首先,我们将创建一个请求,发送到 AssemblyAI 的 STT 自动转录端点。一旦我们发出请求,我们将定期轮询我们的端点,我将每隔 5 秒轮询一次。然后,我们将向 AssemblyAI 的语音转文本 API 自动创建的字幕端点发出请求,以获取。srt 和。vtt 文件。您应该将 video_url 更改为上传视频的位置。

import requests
from mux_config import auth_key
from time import sleep

video_url = "<link to your video>"
transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
headers = {
   "authorization": auth_key,
   "content-type": "application/json"
}
transcript_request = {
   "audio_url": video_url
}

transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers)
transcript_id = transcript_response.json()['id']
polling_endpoint = transcript_endpoint + "/" + transcript_id
print("Transcribing at", polling_endpoint)
polling_response = requests.get(polling_endpoint, headers=headers)
while polling_response.json()['status'] != 'completed':
   sleep(5)
   print("Transcript processing ...")
   try:
       polling_response = requests.get(polling_endpoint, headers=headers)
   except:
       print("Expected to wait 30 percent of the length of your video")
       print("After wait time is up, call poll with id", transcript_id)

srt_endpoint = polling_endpoint + "/srt"
srt_response = requests.get(srt_endpoint, headers=headers)
# print(srt_response.text)
with open("text_track.srt", "w") as _file:
   _file.write(srt_response.text)

vtt_endpoint = polling_endpoint + "/vtt"
vtt_response = requests.get(vtt_endpoint, headers=headers)
# print(vtt_response.text)
with open("text_track.vtt", "w") as _file:
   _file.write(vtt_response.text)

上传字幕文件到 S3

我们可以像上传视频一样上传字幕文件到 S3。

Upload your text files to S3

我上传了。vtt 和。我们通过 AssemblyAI 的 ASR 转录下载的 srt 文件。你可以下载或上传任何一个文件,Mux 接受两种格式,唯一的区别是。srt 被标记为隐藏字幕,而。vtt 不是。

使用 Mux 的视频 API 将视频上传到 Mux

现在我们已经使用 AssemblyAI 的自动语音识别端点转录了我们的视频,以免费在线转录音频到文本并获取字幕文件,我们可以将我们的视频和字幕上传到 Mux。当我们完成并运行这个脚本时,我们应该会看到如下所示的输出。

Upload your mp4 and subtitles from AssemblyAI to Mux

首先,我们必须创建一个资产 API 客户端,Python 将使用它来与 Mux 交互,而不是发送直接请求。我们需要来自 Mux 的令牌 id 和秘密 id,并将它们保存为配置用户名和密码。您需要在新的 Mux 环境中单击“Create an API key”来获得如图所示的内容。我将凭证保存在 mux_config.py 文件中,然后导入它们。您也可以使用环境变量。

Get your Mux credentials

创建资产 API 对象后,我们需要创建一个对象来创建跟踪请求。我们需要向这个对象传递我们的文本轨道的 url、轨道类型(文本)、文本类型(字幕)、语言(无论你的语言是什么,我们将使用 en-US)、文件是否应该被标记为隐藏字幕(对。srt 文件,对于。vtt 文件)和轨道的名称。在代码中,我展示了如何创建?vtt 或。srt 文件请求。一旦我们为从 AssemblyAI 的语音转文本 API 获得的字幕文件创建了请求,我们就必须为 Mux 创建一些输入设置。我们的输入设置将是一个列表,其中包含我们的视频和我们的创建跟踪请求对象的 url。

然后我们将创建一个资产请求发送到 Mux 的视频 API 来添加我们的字幕文件。我包括了公共播放的选项,并确保 mp4 支持设置为标准。一旦我们创建了这个请求,我们就使用我们的资产 API 对象向 Mux 发送一个请求。

import mux_python
from mux_config import token_id, secret_id

configuration = mux_python.Configuration()

configuration.username = token_id
configuration.password = secret_id

assets_api = mux_python.AssetsApi(mux_python.ApiClient(configuration))

add_captions = mux_python.CreateTrackRequest(url="<link to your .srt file>", type="text", text_type="subtitles", language_code="en-US", closed_captions=True, name="English")
# add_captions = mux_python.CreateTrackRequest(url="<link to your .vtt file>", type="text", text_type="subtitles", language_code="en-US", closed_captions=False, name="English")
input_settings = [mux_python.InputSettings(url='<link to your video>'), add_captions]
create_asset_request = mux_python.CreateAssetRequest(input=input_settings, playback_policy=[mux_python.PlaybackPolicy.PUBLIC],mp4_support='standard')
create_asset_response = assets_api.create_asset(create_asset_request)
print("Created Asset: " + str(create_asset_response))
assert create_asset_response != None
assert create_asset_response.data.id != None

一旦我们的资产被创建,我们可以去 Mux 并检查我们的资产。

Mux video asset

我们可以点击视频播放器上的三个点,然后看到一个“隐藏字幕”的标志,如果我们点击它,我们应该能够看到我们通过 AssemblyAI 的自动 AI 转录功能转录的字幕

Turn on subtitles

打开字幕并观看视频后,您应该会在页面顶部看到链接的 YouTube 视频中的字幕。

包扎

在本教程中,我们讨论了如何使用 AssemblyAI 的语音转文本 API 来进行自动语音识别,从而为 Mux 视频创建字幕。我们创建了三个脚本,一个将我们的对象上传到 AWS,另一个将云语音转换为 mp4 文件的文本,另一个将我们的 mp4 和字幕文本轨道上传到 Mux。

更多关于如何免费在线进行语音转文本的信息,请查看 AssemblyAI ,在 Twitter 上关注我们 @assemblyai ,并关注作者@于坚 _ 唐

如何实时自动转录 Zoom 呼叫

原文:https://www.assemblyai.com/blog/how-to-automatically-transcribe-zoom-calls/

在当今日益虚拟化的世界中,旧的业务问题需要新的解决方案。随着越来越多的会议通过 Zoom 等平台远程召开,有效记录会议内容变得越来越困难。幸运的是,现代的自动语音识别技术能够自动记录这些远程会议,让你可以自由表达想法和讨论话题,而不必急于记笔记或担心错过重要的东西。

在本教程中,我们将学习如何通过在呼叫中添加一个 recall.ai bot 来实时自动转录缩放呼叫。这个机器人将在后台使用 AssemblyAI 实时转录呼叫,在你专注于会议的同时为你处理转录过程的每一步。现在让我们来看看如何实现这个解决方案。

什么是 Recall.ai?

Recall.ai 提供单一 API,用于从 Zoom、微软团队、谷歌会议等平台访问实时会议数据。实时会议集成需要大量移动部件,因此 recall.ai 提供了一个统一的框架来简化这一过程。Recall.ai 可以轻松地根据谁在发言、人们何时加入或离开会议等等触发操作,所有这些都可以通过一个用于每个平台的 API 来帮助降低开发时间和维护成本。该 API 可用于音频和视频流,甚至用于没有公开 API 的会议平台

预赛

在我们开始实际实现之前,我们需要确保安装了所有需要的工具。现在,让我们按照安装程序安装我们需要的所有东西。

1 -安装 Node.js 和 npm

首先,我们需要一个 JavaScript 运行时 Node.js 和一个包管理器 npm 。通过打开命令提示符(终端)并输入以下命令,检查是否安装了 Node.js 和/或 npm:

node -v
npm -v

如果安装了这些工具,您将在终端中看到它们的版本。在这种情况下,你可以跳到的下一步。否则,您将需要安装它们。去节点网站下载适合你机器的安装程序,可能是 64 位 Windows 安装程序64 位 macOS 安装程序

一旦安装程序被下载,点击它,并按照安装提示。除了勾选“自动安装必要的工具”复选框之外,您可以继续点击“下一步”来浏览提示,而不改变任何内容

安装完成后,将打开一个终端来安装其他一些工具。允许安装这些工具,之后您的计算机可能会重新启动。重新启动后,通过打开终端并键入npm --version来检查安装是否正确,这将显示所安装的 npm 版本。

2 - Install Ngrok

接下来,我们需要安装 ngrok ,这是一个反向代理,可以让您将本地机器安全地暴露给互联网。使用以下命令之一安装 ngrok,选择适合您的操作系统的命令:

# MacOS
brew install ngrok/ngrok/ngrok

# Windows
choco install ngrok

请注意,如果您使用的是 Windows,您可能需要先安装 chocolatey 。或者,你可以从 ngrok 网站下载一个 ZIP 文件,然后在里面运行可执行文件。如果这些命令不起作用,或者如果你使用的是另一个操作系统,比如 GNU/Linux,请在其网站上查找相应的 ngrok 安装程序

3 -获取 API 密钥

现在我们已经安装了上述工具,我们最终需要获得一个免费的 recall.ai API key 。这是一个识别值,它告诉 recall.ai 你是谁,并让你访问它的服务。

要获取密钥,请进入recall.ai/AssemblyAI 网页并点击“获取集成”。目前,这项服务只能通过邀请提供,但只有 AssemblyAI 客户才能使用。

重要的

确保不要对外共享这个 API 密钥。这是您的帐户独有的,应该保密。

Repo 克隆和环境设置

完成所有预备步骤后,我们可以继续克隆项目存储库并设置我们的环境。首先,克隆 repo,并使用以下终端命令导航到它:

git clone https://github.com/AssemblyAI/assemblyai-recallai-zoom-bot
cd assemblyai-recallai-zoom-bot

如果您没有安装 git,那么在运行上面的命令之前,您需要安装它。接下来,安装所有必要的 npm 软件包

npm install

最后,通过编辑.env文件并用您的 recall.ai API 密钥替换<YOUR-KEY-HERE>,将您的 recall.ai API 密钥保存为环境变量。

RECALL_API_KEY=<YOUR-KEY-HERE> 

将密钥保存为环境变量省去了将密钥硬编码到脚本中的需要,这是不安全和不优雅的。.env文件列在.gitignore文件中,以避免密钥意外上传到 GitHub 等;但是如果您想要一个更安全的解决方案,那么将您的密钥作为一个环境变量保存在您的本地机器上。

如何自动转录缩放呼叫

现在设置过程已经完成,我们终于可以实时转录缩放调用了。首先,我们将使用 ngrok 为我们的 webhook 生成一个 URL,方法是在终端中输入以下命令:

ngrok http 8000

8000指定请求将被转发到的端口。上面的命令将向终端输出类似下面的 screencap 的内容。“转发”后的地址是我们需要的公开地址。

通过编辑克隆的 repo 中的 .env文件并用复制的地址替换<WEBHOOK_URL>,复制该地址并将其保存为环境变量。

WEBHOOK_URL=<YOUR-URL-HERE> 

让 ngrok 终端保持打开,打开另一个终端,导航到项目 repo,用

cd path/to/repo/assemblyai-recallai-zoom-bot
node webhook.js

最后,让两个ngrok 和 webhook 终端都打开,打开另一个终端,导航到项目 repo,用

cd path/to/repo/assemblyai-recallai-zoom-bot
node zoomBot.js

您将在zoomBot.js终端看到一个提示,询问您想要添加转录机器人的缩放会议的 URL。要获取此 URL,请进入您的 Zoom 会议,单击“参与者”旁边的箭头,然后单击“邀请”。

然后点击“复制邀请链接”。

将复制的网址粘贴到zoomBot.js终端的“你的会议网址是什么?:”提示,点击“回车”。您将看到转录机器人添加到您的缩放会议。

回到webhook.js终端,生成会议的实时记录,包括发言者标识符。

这就是实时转录缩放呼叫所需的全部内容!

最后的话

在本教程中,我们学习了如何通过几个简单的步骤,使用 ngrokNode.js 将由 recall.aiAssemblyAI 驱动的实时转录机器人添加到缩放调用中。要了解更多关于 recall.ai 或注册 API 密钥的信息,请访问他们的网站。

如果你正在寻找更多与机器学习相关的内容,比如关于 DALL-E 2 如何实际工作的文章,或者关于 NLP 中的 T2 话题检测的信息,请随时查看我们的 T4 博客!您也可以关注我们的时事通讯,了解即将发布的内容。

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow

如何用 Python 构建带语音邮件的一次性手机

原文:https://www.assemblyai.com/blog/how-to-build-a-burner-phone-with-voicemail-in-python/

概观

你接到许多垃圾电话吗?随着自动呼叫的兴起,垃圾电话越来越多。让我们反击吧,在本教程中,我将向你展示如何制作一次性手机,用 Twilio 记录来电,并用 AssemblyAI 转录来电。Twilio 是一个通信 API,它支持通过电话进行通信。AssemblyAI 是一个快速、自动的语音转文本 API,被 Nordic API 评为 2020 年顶级公共 API。你可以在这里找到的源代码

介绍

今天我们将使用 Python 来构建一次性手机。什么是一次性手机?这是一部手机,你可以用它代替你真正的手机,然后扔掉它。您可以使用 Twilio 让您提供的免费号码进行跟进。在本教程中,我们将从 Twilio 提供一个号码,使用 Python Flask 创建一个函数来接听电话并记录它们,以编程方式更新我们号码的 webhook 端点,进行一些示例电话呼叫,下载我们的. mp3 文件,并使用 AssemblyAI 转录这些. mp3 文件。最后,我们还将比较使用 Twilio 内置转录和 assembly ai语音转文本 API 转录的准确性。

步骤:

  1. 注册一个 Twilio 帐户
  2. 复制您的凭据
  3. 从 Twilio 提供电话号码
  4. 创建一个烧瓶端点来记录声音
  5. 获取 ngrok 并运行它,将您的 Flask 端点暴露给 web
  6. 用新的 webhook 端点更新 Twilio 提供的电话号码
  7. 打几个电话
  8. 检查并下载录像
  9. 获取汇编 api 密钥
  10. 通过汇编转录它们
  11. 特维利奥和阿帕莱的转录比较

注册一个 Twilio 帐户

在 twilio.com 注册一个 Twilio 账户,获得你的账户 sid认证令牌。我已经在你的控制台上圈出了它们应该在的位置(并屏蔽了我的帐户 sid)。

Where to find your Twilio credentials

复制您的凭据

复制您的凭证,将它们保存在您的环境变量中,或者保存在您正在使用的文件夹中的 configure.py 文件中。

acct_id = '<your account sid>'
twilio_auth_key = '<your twilio auth token>'

从 Twilio 提供电话号码

现在让我们使用 Python 通过他们的 REST API 从 Twilio 编程提供一个电话号码。我们将列出 20 个本地号码,然后从中选择一个。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client

client = Client(acct_id, twilio_auth_key)

US_avail_phone_numbers = client.available_phone_numbers('US').fetch()

# lists 20 available local numbers with an area code of your choice
list_20 = US_avail_phone_numbers.local.list(area_code='<your area code>', limit=20)
for num in list_20:
   # only list the number if it has voice capability
   # they all should
   if num.capabilities['voice'] == True:
       print(num.phone_number)
_number = input("Which phone number do you want?")

# request your new number
new_number = client.incoming_phone_numbers.create(
   phone_number= _number)

print("You've activated your new number", new_number.phone_number) 

完成后,我们的终端输出应该如下所示:

Picking a Burner Phone Number

创建一个烧瓶端点来记录声音

如果您还没有下载 Python Flask,您可以使用

pip install flask

仔细阅读这个区块和下一个区块 是非常重要的。我们将使用 Python Flask 创建一个端点。我们的端点将记录一个来电通过 Twilio 转录它(稍后我们将看到一个比较)。

from flask import Flask
from twilio.twiml.voice_response import VoiceResponse

app = Flask(__name__)

@app.route("/voice", methods=["GET", "POST"])
def voice():
   """Read a message aloud"""
   resp = VoiceResponse()
   resp.say("Please leave a message")
   resp.record(timeout=10, transcribe=True)
   resp.hangup()
   return(str(resp))

if __name__ == "__main__":
   app.run(debug=True)

‍Now:我们在终端中运行这个命令,应该会看到如下输出:

Start your Flask Application

将您的烧瓶端点暴露在网络上

在我们的 flask 应用程序在我们的终端上启动并运行之后,我们还需要下载并运行 ngrok。你可以在这里下载 ngrok。下载 ngrok 并复制到我们的工作文件夹后,我们将打开 第二个终端 并运行

./ngrok http 5000

注意,“5000”可以替换为运行 Python Flask 应用程序的任何端口。正如你在上面看到的,我们的运行在端口 5000 上。Ngrok 应该公开并返回一个我们可以触及的端点。我们需要跟踪 https 转发链接,这将是我们更新的 webhook URL,以便 Twilio 在我们调用时访问。

ngrok

用新的 webhook 端点更新 Twilio 提供的电话号码

好了,现在我们有了一个可以记录电话的应用程序,让我们通过他们的 Python REST API 来更新 Twilio 上的 webhook。

from twilio.rest import Client
from configure import twilio_auth_key, acct_id

client = Client(acct_id, twilio_auth_key)

phone_numbers = client.incoming_phone_numbers.list()

for record in phone_numbers:
   print(record.sid)

_sid = input("What phone number SID do you want to update?")
_url = input("What do you want to update the webhook URL to?")

updated_phone_number = client.incoming_phone_numbers(_sid).update(voice_url=_url)

print("Voice URL updated to", updated_phone_number.voice_url)

当我们运行这个时,我们应该在终端中得到这个输出。请注意,我在 ngrok 提供的 URL 末尾添加了“/voice ”,这是因为我在上面的 Flask 应用程序中将“/voice”定义为端点。

Updating Phone Number Webhooks in Twilio

打几个电话

这里没有代码可写,但是给你的新一次性电话号码打几个电话。如果一切设置正确,应该会听到一个女声说“请留言”。为了这个教程,我打了 3 个电话,留了 3 条信息。

我留言是:

“这是第三次也是最后一次录音,我将用来测试转录服务。所以,是的,我应该是一个牛仔”

“这是转录的测试录音。莎莉在海边卖贝壳

"这是一个用 Twilio 和 AssemblyAI 录制转录的测试调用. "

检查并下载录像

现在,让我们在 Twilio 上查看我们的录音并下载它们。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client
import requests

twilio_url = "https://api.twilio.com"
client = Client(acct_id, twilio_auth_key)
recordings = client.recordings.list(limit=20)
for record in recordings:
   print(record.sid)

_rid = input("Which recording ID would you like?")

request_url = twilio_url + "/2010-04-01/Accounts/" + acct_id + "/Recordings/" + _rid + ".mp3"
response = requests.get(request_url)
with open(_rid+'.mp3', 'wb') as f:
   f.write(response.content)

print("File saved to", _rid+".mp3")

完成后,终端中的输出应该如下所示:

Fetching recordings from Twilio

我运行了三次来下载所有三个记录。您也可以执行下面的代码来一次下载所有的文件。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client
import requests

twilio_url = "https://api.twilio.com"
client = Client(acct_id, twilio_auth_key)
recordings = client.recordings.list(limit=20)
for record in recordings:
    _rid = record.sid
    request_url = twilio_url + "/2010-04-01/Accounts/" + acct_id + "/Recordings/" + _rid + ".mp3"
    response = requests.get(request_url)
    with open(_rid+'.mp3', 'wb') as f:
        f.write(response.content)
    print("File saved to", _rid+".mp3")

获取汇编 api 密钥

转到 AssemblyAI 获取 API 密钥。您将看到我用红色圈出的 API 密钥:

Where to find your AssemblyAI API key

将这一行添加到 configure.py 文件中

assembly_auth_key = '<your AssemblyAI API Token here>'

通过汇编转录它们

现在我们将使用 AssemblyAI 的 API 来转录我们拥有的. mp3 文件。我们将在 AssemblyAI 的主题检测功能启用的情况下转录我们的文件。主题检测用于检测转录文本中的主题。如果我们检测到文本包含我们正在寻找的一个或多个主题,主题检测对于自动执行一些操作是很有用的。我们要做的是将我们的. mp3 文件上传到 AssemblyAI 的上传端点,然后通过 AssemblyAI 转录端点在启用主题检测的情况下转录它。我们将以. json 文件的形式下载我们的脚本。

from configure import assembly_auth_key
import requests
import json
from time import sleep

transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
upload_endpoint = 'https://api.assemblyai.com/v2/upload'
headers = {
   "authorization": assembly_auth_key,
   "content-type": "application/json"
}
CHUNK_SIZE = 5242880

def read_file(location):
   with open(location, 'rb') as _file:
       while True:
           data = _file.read(CHUNK_SIZE)
           if not data:
               break
           yield data

location = input("Which file do you want to transcribe?")

upload_response = requests.post(
   upload_endpoint,
   headers=headers, data=read_file(location)
)
audio_url = upload_response.json()['upload_url']
print('Uploaded to', audio_url)
transcript_request = {
   'audio_url': audio_url,
   'iab_categories': 'True'
}

transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers)
transcript_id = transcript_response.json()['id']
polling_endpoint = transcript_endpoint + "/" + transcript_id
print("Transcribing at", polling_endpoint)
polling_response = requests.get(polling_endpoint, headers=headers)
while polling_response.json()['status'] != 'completed':
   sleep(5)
   print("Transcript processing ...")
   try:
       polling_response = requests.get(polling_endpoint, headers=headers)
   except:
       print("Expected to wait 30 percent of the length of your video")
       print("After wait time is up, call poll with id", transcript_id)
categories_filename = transcript_id + '_categories.json'
with open(categories_filename, 'w') as f:
   f.write(json.dumps(polling_response.json()['iab_categories_result']))
print('Categories saved to', categories_filename)

‍When:我们完成了,转录一个文件的请求应该是这样的:

Upload and Transcribe your mp3 files via AssemblyAI

比较

我意识到,我们显然不能像使用 AssemblyAI 的 API 那样使用 Twilio 的内置转录来进行主题检测,但我们仍然可以比较它们的转录准确性。为此,我们将构建两个新的脚本。首先,从 Twilio 那里取回我们所有的抄本。第二,打印我们在 AssemblyAI 的记录。

我们的 Twilio 脚本将调用客户端,获取我们帐户上的所有副本(我们现在应该只有 3 份)并打印出来:

from twilio.rest import Client
from configure import twilio_auth_key, acct_id

client = Client(acct_id, twilio_auth_key)

transcriptions = client.transcriptions.list()

for record in transcriptions:
   _tid = record.sid
   transcript = client.transcriptions(_tid).fetch()
   print(transcript.transcription_text)

我们的 AssemblyAI 脚本将获取我们下载的 JSON 并打印出文本。我手动加载了 JSON 文件名。

import json

files = ['pmo31a8t2-7778-4e20-bf0d-baff7fbaf72f_categories.json',
'pmojwgqta-8cc3-4a6c-a881-c8b83e3a9cce_categories.json',
'pmem7j6cw-4552-4baf-b5b8-4b90dc39f508_categories.json']
for _file in files:
   f = open(_file,'r')
   json_obj = json.load(f)
   text = json_obj['results'][0]['text']
   print(text)

在并排比较中:

Twilio transcriptions

AssemblyAI transcriptions

我们可以看到,AssemblyAI 的转录比 Twilio 的内置转录服务更准确,即使是在如此短的消息中。我们还可以比较 AssemblyAI 的定价和 Twilio 内置转录的定价,并看到 AssemblyAI 的成本比⅓低同样多(0.015 美元对 0.05 美元)。

清理

最后,当你把你的一次性电话号码给了那些讨厌的不断索要电话号码的服务后,你可以删除它。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client

client = Client(acct_id, twilio_auth_key)

phone_numbers = client.incoming_phone_numbers.list()

for record in phone_numbers:
   print(record.sid)

_del = input("Which phone number SID would you like to delete?")

client.incoming_phone_numbers(_del).delete()

它应该像这样运行:

Deleting your Twilio provisioned phone number

包扎

在本教程中,我们了解了如何使用 Python 以编程方式从 Twilio 获取号码,设置 Flask 应用程序来响应和记录电话呼叫,用 AssemblyAI 转录我们的电话呼叫,并在完成后删除我们的号码。

有关 AssemblyAI 的更多信息,请查看我们的博客,获取教程和更新。在 Twitter 上关注我们 @assemblyai 获取更新,关注作者、@于坚 _ 唐获取更多教程。

如何用 Python 构建 YouTube 下载器

原文:https://www.assemblyai.com/blog/how-to-build-a-youtube-downloader-in-python/

你曾经想从 YouTube 视频下载音频吗?在本教程中,我将向你展示如何建立自己的免费 YouTube 视频下载应用程序。你可以在这里找到源代码

先决条件

我们需要下载/配置什么库/软件?

  • YouTube-dl(python 库)
  • FFmpeg
  • 单击(python 库)

在我们能够构建我们的 YouTube 视频和音频下载器之前,我们必须下载并配置我们的库。由于 youtube-dl 和 click 都是 Python 库,我们可以用 pip 同时安装这两个库:

pip install youtube-dl click

‍After:这两个库已经安装完毕,我们将要安装 FFmpeg、FFprobe 和 FFplay。ffmpeg。FFmpeg 是一个用于处理视频、音频和其他多媒体文件的开源免费软件。我们将结合 youtube-dl 使用它来将我们下载的视频转换成音频文件。这一部分对于 Windows 和 OSX 用户是不同的。首先,我们将从https://ffbinaries.com/downloads下载二进制文件

如果你是 Windows 用户,你需要做的是下载二进制文件并解压。您将看到我们需要的三个 ffbinaries,ffmpeg,ffprobe 和 ffplay 的可执行文件。将每个可执行文件复制到一个文件夹中,并确保您知道该文件夹在哪里。出于本教程的目的,我将它复制到我从运行 python 程序的同一个文件夹。稍后,我们将在发送给 youtube_dl 的请求中添加一个选项,告诉它在哪里可以找到该程序。

如果您是 OSX 用户,您会希望访问该站点并下载二进制文件,然后将下载位置添加到 PATH 变量中。像这样:

1\. Run

sudo cp ./ffmpeg ./ffplay ./ffprobe /usr/local/bin

2\. Open up ~/.zshrc with whatever text editor you’d like, I just run 

vim ~/.zshrc

3\. Add the line 

PATH=”/usr/local/bin:$PATH”

YouTube 下载应用程序创建

我们将创建两个命令,一个只下载音频,一个下载视频。在我们开始创建实际命令之前,我们必须初始化我们的 CLI。一个重要的注意事项,你所有的命令都应该在 API()和 main()的定义之间。

import click

@click.group()
def apis():
   """A CLI for getting transcriptions of YouTube videos"""
def main():
   apis(prog_name='apis')

if __name__ == '__main__':
   main()

YouTube 视频下载应用程序代码

我们需要做的第一件事是创建一个下载视频函数,它将从我们传递给它的链接中下载 YouTube 视频,作为一个. mp4 文件。完成后,它看起来应该是这样的:

我们的 YouTube 下载视频功能将指定视频的格式(mp4)和一个输出模板,告诉 youtube-dl 它希望文件保存的方式。我们将把文件名设置为视频的 YouTube id,这是完全可选的,我这样做是因为我发现在某些设置中,文件的标题会变得很长很麻烦,尤其是如果其中有空格的话。然后我们将调用 youtube-dl 来保存文件,并让函数将文件名返回给我们。

import youtube_dl

@click.argument('link')
@apis.command()
def download_video(link):
   ydl_opts = {
       'format': 'mp4',
       'outtmpl': "./%(id)s.%(ext)s",
   }
   _id = link.strip()
   meta = youtube_dl.YoutubeDL(ydl_opts).extract_info(_id)
   save_location = meta['id'] + ".mp4"
   print(save_location)
   return save_location

‍The 我们需要做的另一个功能是从 YouTube 下载音频功能,它将从 YouTube 链接下载音频,我们把它作为一个. mp3 文件。完成后,它看起来应该是这样的:

或者,如果您决定保留视频:

和我们的 YouTube 下载视频功能一样,我们的 YouTube 下载音频功能也会指定一些选项给 youtube-dl。这次我们需要指定的额外选项是告诉 youtube-dl 在处理后使用 FFmpeg 转换视频文件,以及是否保留视频。

@click.argument('link')
@click.option('-k', '--keep-video', is_flag=True, help="Pass this to keep the video")
@apis.command()
def download_audio(link, keep_video):
   ydl_opts = {
       'format': 'bestaudio/best',
       'postprocessors': [{
           'key': 'FFmpegExtractAudio',
           'preferredcodec': 'mp3',
           'preferredquality': '192',
       }],
       'ffmpeg-location': './',
       'outtmpl': "./%(id)s.%(ext)s",
       'keepvideo': 'True' if keep_video else 'False'
   }
   _id = link.strip()
   meta = youtube_dl.YoutubeDL(ydl_opts).extract_info(_id)
   save_location = meta['id'] + ".mp3"
   print(save_location)
   return save_location

‍That's it。我们完了!就这么简单。不要再使用充斥着大量广告的粗略网站来下载你的 youtube 视频或音频!看看如何将它扩展成一个命令行界面,它会给你 YouTube 视频的脚本。可以在推特 @assemblyai 关注 AssemblyAI 更新,也可以关注我@于坚 _ 唐

如何用 API 将 MP3 文件转换成文本

原文:https://www.assemblyai.com/blog/how-to-convert-an-mp3-file-to-text-with-an-api/

在本教程中,我将向你展示如何用 API 将 MP3 文件转换成文本。AssemblyAI 提供了一个免费、快速、简单易用的语音转文本 API 。仅仅使用一个 API 就能把 mp3 文件转换成文本,这在最近几年才成为可能。这是因为自动语音转录技术在过去几年里变得更加准确,现在几乎和人类一样准确。

我们开始吧!

自动转录快速入门

几年前,自动转录技术对于像你我这样的普通软件开发人员来说并不实用。这是为苹果和宝马这样的大型企业保留的技术。如今,开发人员可以通过类似于 Twilio 或 Stripe 的简单易用的 API 来使用这项技术。

而且随着最近深度学习的进步,语音转文本技术的准确度正在迅速接近人类水平。今天,只需几行代码,我们就可以使用 API 将我们自己的 mp3 文件转换为人类级别的文本。

在这个例子中,我们将使用 AssemblyAI 的 API 进行自动转录。AssemblyAI 的 API 不仅免费、快速、使用起来超级简单,还附带了一堆即插即用的特性。在下一节中,我将向您展示如何使用 AssemblyAI 的 API 将 mp3 文件转换为文本。

Learn More: What is ASR?

将 MP3 文件转换为文本

要开始将 mp3 文件转换为文本,您需要为 AssemblyAI 的语音转文本 API 获取一个 API 密钥。一旦你注册了,你可以在控制台中找到你的 API 密匙,在下面的图片中我用红色圈了出来。您应该将它存储为环境变量或单独配置文件中的变量。

Screenshot of AssemblyAI Dashboard

如果你还没有下载 mp3 文件,我有一个 mp3 文件供你下载。我选择了一个关于我们的大脑如何处理言语的视频,这是一个由 Gareth Gaskell 做的 TED 演讲。它不是关于如何将你的 mp3 文件转换成文本,但它很有趣。它讲述了我们作为人类(而不是机器)如何理解语言。他涵盖了人们平均认识多少单词,科学家认为我们的大脑如何识别语言,以及我们如何获得新单词。

好了,接下来我们实际上是如何用 AssemblyAI 的语音识别 API 将这个 mp3 文件转换成文本文件的。整个过程可以分为三个简单的步骤:

  • 将 mp3 文件上传到 AssemblyAI 的 API
  • 开始转录作业
  • 获取转录作业的结果

现在来看代码!

我们需要导入我们的 API 键或者内联定义它,如下所示。然后,我们将定义包含在对 AssemblyAI 的 API 调用中的头,这是我们将包含 API 键的地方。要将我们的 mp3 文件上传到 AssemblyAI,我们只需向 AssemblyAI 上传端点发出一个请求,并使用一个生成器函数发送一个 POST 请求,该请求包含我们之前创建的头和数据,该函数将以字节形式读取我们的 mp3 文件并返回数据。

auth_key = '<your AssemblyAI API key here>'

headers = {
    "authorization": auth_key,
    "content-type": "application/json”
}

def read_file(filename):
   with open(filename, 'rb') as _file:
       while True:
           data = _file.read(5242880)
           if not data:
               break
           yield data

upload_response = requests.post('https://api.assemblyai.com/v2/upload', headers=headers, data=read_file('<path to your file here>'))
audio_url = upload_response.json()['upload_url']

在 JSON 响应中,会有一个upload_url键,指向我们上传到 AssemblyAI 的文件。此文件只能由 AssemblyAI 的服务器访问,因此您将无法在浏览器中访问此 URL。

下面,我们将把upload_url传递给转录端点(也带有我们在前面的请求中使用的头),它告诉 AssemblyAI 将我们的 mp3 文件转换成文本。

transcript_request = {'audio_url': audio_url}
endpoint = "https://api.assemblyai.com/v2/transcript"
transcript_response = requests.post(endpoint, json=transcript_request, headers=headers)
_id = transcript_response.json()['id']

一旦转录请求被处理,我们将得到一个 JSON 响应,它将有一个 id。我们需要保存 id,以便我们可以轮询轮询端点来检查我们的转录状态。通过添加我们从初始转录响应中收到的 id,从转录端点创建轮询端点。一旦我们从轮询端点得到响应,我们需要检查抄本的状态,看它是否完成。如果记录没有完成,我们应该打印出轮询端点的响应,以检查记录状态,并确保没有任何错误。一旦脚本完成,我们可以将文本保存到文本文件中!

endpoint = "https://api.assemblyai.com/v2/transcript/" + _id
polling_response = requests.get(endpoint, headers=headers)
if polling_response.json()['status'] != 'completed':
   print(polling_response.json())
else:
   with open(_id + '.txt', 'w') as f:
       f.write(polling_response.json()['text'])
   print('Transcript saved to', _id, '.txt')

就这么简单。使用 AssemblyAI 的语音转文本 API 将 mp3 文件转换为文本所要做的就是获取一个 API 密钥,将我们的 mp3 文件上传到 API,然后发送 make 2 个简单的 API 调用!

准备好自己尝试了吗?

获得免费的 API 令牌!!

[Get It Now](Get a free API Token!!)

结论

如今,任何开发人员都可以通过使用像 AssemblyAI 的语音识别 API 这样的云 API 来访问语音识别技术。我们演示了如何使用 AssemblyAI 的 API 将 mp3 文件转换成文本。想了解更多关于语音识别技术的信息,请关注我们的 Twitter @assemblyai@于坚 _ 唐

如何评价机器学习模型

原文:https://www.assemblyai.com/blog/how-to-evaluate-machine-learning-models/

在训练机器学习模型时,有许多评估指标可供选择。为您的问题类型和您试图优化的内容选择正确的指标对于模型的成功至关重要。

在本视频中,我们将了解分类和回归任务最常用的评估指标。

看这里:

https://www.youtube.com/embed/LbX4X71-TFI?feature=oembed

如何获得 YouTube 视频抄本

原文:https://www.assemblyai.com/blog/how-to-get-the-transcript-of-a-youtube-video/

你曾经需要在 YouTube 视频讲座上做笔记吗?在大学的时候,我有一些很难的课程,我必须使用大量的 YouTube 大学,并从我找到的一些 YouTube 视频中做笔记。很多时候,我不得不停下来,倒带,回放很多次来做笔记,因为速度太快了。今天,我将向您展示一个惊人的方法来解决这个问题,通过 AssemblyAI 的转录 API 获得视频的转录。你可以在这里找到源代码

先决条件

我将向您展示如何构建一个命令行工具,它将从 YouTube 链接下载一个视频,并通过 Python 3 中的 AssemblyAI 为您提取脚本。你需要:

  • youtube-dl
  • ffmpeg, ffprobe, ffplay
  • 一个程序集 AI API 密钥
  • 单击(python 库)
  • 互联网接入

AssemblyAI 是一个用于快速、自动语音到文本转换的 API。我们将使用 AssemblyAI API 来转录我们下载的 YouTube 视频。要获得一个 AssemblyAI API 密钥,请访问 AssemblyAI 并注册,你会看到你的 API 密钥清楚地显示出来,我已经圈出了它应该在图片中的位置。

AssemblyAI Portal, API Token Circled

接下来我们必须下载 Python 的 youtube-dl。Youtube-dl 是一个开源库,可以方便地下载 Youtube 视频。有多种方法可以做到这一点,但我建议使用画中画

pip install youtube_dl

接下来是安装 ffmpeg。FFmpeg 是一个用于处理视频、音频和其他多媒体文件的开源免费软件。我们将结合 youtube-dl 使用它来将我们下载的视频转换成音频文件。这一部分对于 Windows 和 OSX 用户是不同的。首先,我们将从https://ffbinaries.com/downloads下载二进制文件

如果你是 Windows 用户,你需要做的是下载二进制文件并解压。您将看到我们需要的三个 ffbinaries,ffmpeg,ffprobe 和 ffplay 的可执行文件。将每个可执行文件复制到一个文件夹中,并确保您知道该文件夹在哪里。出于本教程的目的,我将它复制到我从运行 python 程序的同一个文件夹。稍后,我们将在发送给 youtube_dl 的请求中添加一个选项,告诉它在哪里可以找到该程序。

如果您是 OSX 用户,您会希望访问该站点并下载二进制文件,然后将下载位置添加到 PATH 变量中。像这样:

1\. Run

sudo cp ./ffmpeg ./ffplay ./ffprobe /usr/local/bin

2\. Open up ~/.zshrc with whatever text editor you’d like, I just run 

vim ~/.zshrc

3\. Add the line 

PATH=”/usr/local/bin:$PATH”

安装的最后一个先决条件是单击。Click 是一个 Python 库,是“命令行界面创建工具包”的简称。Click 的三个主要兴趣点是任意命令嵌套、自动帮助页面生成和运行时子命令的延迟加载。我只需在终端中安装 Click with pip,如下所示:

pip install click

至此,我们已经完成了构建应用程序的所有先决步骤,现在让我们开始设置。需要注意的一点是,我有一个 configure.py 文件,其中存储了来自 AssemblyAI 的 auth key,您也需要创建一个。整个文件可以是这样的:

auth_key = '<Your AssemblyAI API key here>'

设置

对于我们的设置,我们需要知道一些事情:

  1. 传递给 youtube_dl 的选项有哪些
  2. 程序集端点
  3. 其他一些常数

对于 youtube_dl 选项,我们希望下载视频并提取音频,因此我们将使用 bestaudio 作为我们的格式选项。然后,因为我们需要获得音频,我们需要通过一个后处理器,这就是 ffmpeg 的用武之地。你会注意到我还添加了一个 ffmpeg 位置。/'这是给那些已经把 ff 二进制文件移到你的程序所在的文件夹的 Windows 用户的。我还添加了一个 outtmpl(输出模板),并将文件名设置为视频的 YouTube id,这完全是可选的,我这样做是因为我发现在某些设置中,文件的标题可能会变得很长而且很麻烦,特别是如果其中有空格的话。

ydl_opts = {
   'format': 'bestaudio/best',
   'postprocessors': [{
       'key': 'FFmpegExtractAudio',
       'preferredcodec': 'mp3',
       'preferredquality': '192',
   }],
   'ffmpeg-location': './',
   'outtmpl': "./%(id)s.%(ext)s",
}

我们将在这里与 AssemblyAI 的两个端点进行交互,一个用来上传 YouTube 视频的音频,另一个用来获取转录。我们将在代码中这样定义它们:

transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
upload_endpoint = 'https://api.assemblyai.com/v2/upload'

最后,我们将设置几个常量,与 AssemblyAI API 交互时需要发送的头,以及读取文件时所需的块大小。我们将这样设置:

headers_auth_only = {'authorization': auth_key}
headers = {
   "authorization": auth_key,
   "content-type": "application/json"
}
CHUNK_SIZE = 5242880

我们已经安装了必备的库,并设置了常数,现在是时候开始制作应用程序了。

让我们把它分成四个步骤(方便的还有四个命令):

  1. 从 YouTube 下载音频(下载)
  2. 将音频文件上传到程序集(上传)
  3. 通过汇编转录音频文件(转录)
  4. 获取转录的文本文件(投票)

从 YouTube 下载音频

我们这一步的最终目标是创建一个函数,它获取一个链接,下载它,然后将下载位置返回给我们。完成后,它应该看起来像这样:

首先,让我们初始化我们的 CLI,我们将导入 clicks 库并定义一个 api 组。

import click

@click.group()
def apis():
   """A CLI for getting transcriptions of YouTube videos"""
def main():
   apis(prog_name='apis')

if __name__ == '__main__':
   main()

现在让我们制作我们的下载函数。YouTube_dl 通过获取 YouTube 视频的 id 来工作,所以当我们传入一个链接时,我们将想要首先剥离它,然后将其传递给 youtube_dl。之后,我们将使用 youtube_dl 和我们之前为它设置的选项来保存视频和打印,并返回保存位置。

import youtube_dl

@click.argument('link')
@apis.command()
def download(link):
   _id = link.strip()
   meta = youtube_dl.YoutubeDL(ydl_opts).extract_info(_id)
   save_location = meta['id'] + ".mp3"
   print(save_location)
   return save_location

将音频上传到程序集

酷,现在我们可以下载一个 YouTube 视频,并在本地将音频文件保存为. mp3。现在,我们需要将这个音频文件上传到某个地方进行在线托管。幸运的是,Assembly 提供了一个易于使用的上传端点和存储。在这一步的最后,我们将得到类似这样的东西。

要上传一个文件,我们必须创建一个可以读取数据并在上传请求中作为“数据”发送的函数。当我们得到上传响应时,我们简单地打印出来,然后返回 url。

import requests

@click.argument('filename')
@apis.command()
def upload(filename):
   def read_file(filename):
       with open(filename, 'rb') as _file:
           while True:
               data = _file.read(CHUNK_SIZE)
               if not data:
                   break
               yield data

   upload_response = requests.post(
       upload_endpoint,
       headers=headers_auth_only, data=read_file(filename)
   )
   print(upload_response.json())
   return upload_response.json()['upload_url']

通过汇编转录音频

好了,现在我们已经上传了音频,我们可以通过汇编 API 转录它。当我们完成后,在发送请求时,我们应该得到类似这样的东西。

在这一步中,我们要做的是创建一个转录请求,并将该请求发送到 AssemblyAI 转录端点。然后,我们坐着等。我在这里输入 pprint 是为了让打印输出看起来更好,如果你想要一个更简洁的视觉效果,你可以使用常规打印。我还包含了一个选项来传递一个标志,这个标志要么是-c 要么是- categories,它控制我们是否包含一个从 AssemblyAI 转录中获取与文本相关的类别的请求。

import pprint

@click.argument('audio_url')
@click.option('-c', '--categories', is_flag=True, help="Pass if you want to get the categories of this transcript back")
@apis.command()
def transcribe(audio_url, categories: bool):

   transcript_request = {
       'audio_url': audio_url,
       'iab_categories': 'True' if categories else 'False',
   }

   transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers)
   pprint.pprint(transcript_response.json())
   return transcript_response.json()['id'] 

获取转录的文本文件

我们就要到了,这是我们要写的最后一个命令。这个命令用于轮询我们的转录端点,以检查我们的转录是否完成。汇编人工智能的文件说,预计 15-30%的视频长度转录时间-https://docs.assemblyai.com/overview/processing-times。在这一步结束时,如果响应的状态为‘正在处理’,如图中红色所示,轮询命令将返回与转录命令完全相同的内容。

或者如果返回的响应的状态是“完成”,它将返回本地保存转录的位置。‍

对于这个函数,我们要做的是通过转录端点和上面返回的“id”参数(在本例中是 dx 4 mgdwjz-a413-4204-87 E0-666d 97727113)构造端点以进行轮询,创建文件名以保存转录的文本,最后检查响应以查看我们是否应该显示 AssemblyAI 模型仍在处理中(请注意第一个图像中的状态:正在处理)或者 AssemblyAI 模型是否已经完成,然后我们只保存文件。

@click.argument('transcript_id')
@apis.command()
def poll(transcript_id):
   polling_endpoint = transcript_endpoint + "/" + transcript_id
   polling_response = requests.get(polling_endpoint, headers=headers)
   filename = transcript_id + '.txt'
   if polling_response.json()['status'] != 'completed':
       pprint.pprint(polling_response.json())
   else:
       with open(filename, 'w') as f:
           f.write(polling_response.json()['text'])
       print('Transcript saved to', filename)
       return filename

奖金回合

如果你不需要任何中间步骤(除了投票)作为独立的函数,我们可以只做一个大函数,它将做所有的事情,从下载 YouTube 视频到上传到汇编,再到通过汇编转录。有时 DNS 会超时,所以我们将使用 try,但如果请求失败,将返回等待时间。当我们完成时,它应该看起来像这样:

或者:

然后,我们等待大约 120.8 秒,并调用 poll 命令来获取我们的转录,瞧!

@click.argument('link')
@click.option('-c', '--categories', is_flag=True, help="Pass True if you want to get the categories of this transcript back")
@apis.command()
def transcribe_from_link(link, categories: bool):
   _id = link.strip()
   def get_vid(_id):
       with youtube_dl.YoutubeDL(ydl_opts) as ydl:
           return ydl.extract_info(_id)
   meta = get_vid(_id)
   save_location = meta['id'] + ".mp3"
   duration = meta['duration']
   print('Saved mp3 to', save_location)
   def read_file(filename):
       with open(filename, 'rb') as _file:
           while True:
               data = _file.read(CHUNK_SIZE)
               if not data:
                   break
               yield data

   upload_response = requests.post(
       upload_endpoint,
       headers=headers_auth_only, data=read_file(save_location)
   )
   audio_url = upload_response.json()['upload_url']
   print('Uploaded to', audio_url)
   transcript_request = {
       'audio_url': audio_url,
       'iab_categories': 'True' if categories else 'False',
   }

   transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers)
   transcript_id = transcript_response.json()['id']
   polling_endpoint = transcript_endpoint + "/" + transcript_id
   print("Transcribing at", polling_endpoint)
   polling_response = requests.get(polling_endpoint, headers=headers)
   while polling_response.json()['status'] != 'completed':
       sleep(30)
       try:
           polling_response = requests.get(polling_endpoint, headers=headers)
       except:
           print("Expected wait time:", duration*2/5, "seconds")
           print("After wait time is up, call poll with id", transcript_id)
           return transcript_id
   _filename = transcript_id + '.txt'
   with open(_filename, 'w') as f:
       f.write(polling_response.json()['text'])
   print('Transcript saved to', _filename)

包扎

概括地说,我们刚刚制作了自己的命令行界面,用于使用 youtube-dl 下载 YouTube 视频,并使用 Python 通过 AssemblyAI 转录它们。AssemblyAI 是一个简单易用、快速且功能强大的语音转文本 API。可以在 Twitter 关注 assembly ai@ assembly ai也可以关注我@于坚 _ 唐

如何制作一个用 Streamlit 转录 YouTube 视频的 Web 应用

原文:https://www.assemblyai.com/blog/how-to-make-a-web-app-that-transcribes-youtube-videos-with-streamlit/

让我们构建一个交互式的 web 应用程序,它可以在几分钟内转录 YouTube 视频!Streamlit 是一个很棒的 Python 库,让 web 开发变得轻而易举。在 Streamlit 强大的框架之上,我们将插入 Assembly AI 的易用 API 来快速上传和转录音频文件。

教程

在教程的第一部分,我们将创建一个项目结构,安装所有必要的依赖项,创建一个基本的 Streamlit 应用程序,使用它我们可以测试我们的代码并开发启动转录过程的 Python 函数。我们将使用三个主要的库:Streamlit、YouTube_dl 和 FFmpeg。这些库将使我们能够为我们的项目创建前端,分别下载 youtube 视频和从 youtube 视频中提取音频文件。

第一部分:

https://www.youtube.com/embed/CrLmgrGiVVY?feature=oembed

第二部分:

https://www.youtube.com/embed/oGb2oXZzIwY?feature=oembed

如何设置 Twilio 语音邮件

原文:https://www.assemblyai.com/blog/how-to-programmatically-set-up-twilio-voicemail/

介绍

想以编程方式使用 Twilio 语音邮件功能吗?当我用 Twilio voicemail 构建一次性手机时,我不得不查找这么多教程来了解如何以编程方式使用 Twilio 的 API,所以这里有一个关于如何使用 Python 和 Twilio 设置语音邮件的全面指南。我们将讨论如何:

好的,在我们开始之前,你需要一个 Twilio 账户。你可以在图片中我所指的地方找到你的账号 sid认证令牌

Twilio credentials

复制您的凭证,将它们保存在您的环境变量中,或者保存在您正在使用的文件夹中的 configure.py 文件中。

acct_id = '<your account sid>'
twilio_auth_key = '<your twilio auth token>'

向 Twilio 索要电话号码

现在我们有了一个 Twilio 帐户,设置语音邮件的第一件事就是获取一个电话号码。我们将创建一个 Python 脚本,该脚本将请求 20 个带有您想要的任何区号的本地号码,并允许我们选择其中一个。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client

client = Client(acct_id, twilio_auth_key)

US_avail_phone_numbers = client.available_phone_numbers('US').fetch()

# lists 20 available local numbers with an area code of your choice
list_20 = US_avail_phone_numbers.local.list(area_code='<your area code>', limit=20)
for num in list_20:
   # only list the number if it has voice capability
   # they all should
   if num.capabilities['voice'] == True:
       print(num.phone_number)
_number = input("Which phone number do you want?")

# request your new number
new_number = client.incoming_phone_numbers.create(
   phone_number= _number)

print("You've activated your new number", new_number.phone_number)

完成后,我们的终端输出应该如下所示:

用 Python Flask、ngrok 和 Twilio 构建一个定制的 webhook 来设置语音邮件

这是我们使用 Twilio 设置语音邮件的部分。我们将构建一个 Flask 应用程序,它将使用 Twilio 直接转到语音邮件并记录电话。我们将创建一个自定义的 Twilio 语音邮件端点,它将告诉呼叫者记录他们的呼叫,然后在 10 秒钟内记录呼叫。我们将在我们的端口上下载并运行 ngrok,将其暴露给互联网。 注意 我们需要两个开放的终端来做这件事。在第一个终端中运行包含此代码的文件

from flask import Flask
from twilio.twiml.voice_response import VoiceResponse

app = Flask(__name__)

@app.route("/voice", methods=["GET", "POST"])
def voice():
   """Read a message aloud"""
   resp = VoiceResponse()
   resp.say("Please leave a message")
   resp.record(timeout=10, transcribe=True)
   resp.hangup()
   return(str(resp))

if __name__ == "__main__":
   app.run(debug=True)

我们应该会看到这样的输出:

Start Twilio voicemail Flask app

在我们的 flask 应用程序在我们的终端上启动并运行之后,我们还需要下载并运行 ngrok。你可以在这里下载 ngrok。下载 ngrok 并复制到我们的工作文件夹后,我们将打开 第二个终端 并运行

./ngrok http 5000

注意,“5000”可以替换为运行 Python Flask 应用程序的任何端口。正如你在上面看到的,我们的运行在端口 5000 上。Ngrok 应该公开并返回一个我们可以触及的端点。我们需要跟踪 https 转发链接,这将是我们更新的 webhook URL,以便 Twilio 在我们调用时访问。

Expose webhook endpoint

将电话号码的网络挂钩更改为您自定义的网络挂钩

好了,现在我们有了一个可以记录电话的应用程序,让我们通过他们的 Python REST API 来更新 Twilio 上的 webhook。这样,当我们拨打该号码时,Twilio 会直接将我们转到语音信箱。

from twilio.rest import Client
from configure import twilio_auth_key, acct_id

client = Client(acct_id, twilio_auth_key)

phone_numbers = client.incoming_phone_numbers.list()

for record in phone_numbers:
   print(record.sid)

_sid = input("What phone number SID do you want to update?")
_url = input("What do you want to update the webhook URL to?")

updated_phone_number = client.incoming_phone_numbers(_sid).update(voice_url=_url)

print("Voice URL updated to", updated_phone_number.voice_url)

当我们运行这个时,我们应该在终端中得到这个输出。请注意,我在 ngrok 提供的 URL 末尾添加了“/voice ”,这是因为我在上面的 Flask 应用程序中将“/voice”定义为端点。

Change Twilio webhook endpoint

下载您的 Twilio 语音邮件录音

要测试我们的语音邮件,打几个电话。如果一切设置正确,应该会听到一个女声说“请留言”。为了这个教程,我打了 3 个电话,留了 3 条信息。

我留言是:

"This is a third and final recording that I’m going to use for testing transcription services. So yeah, I should have been a cowboy"
"This is a test recording for transcriptions. Sally sells seashells down by the sea shore"
"A B C D E F G This is a test call for recording transcriptions with Twilio and AssemblyAI"

现在,让我们在 Twilio 上查看我们的录音并下载它们。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client
import requests

twilio_url = "https://api.twilio.com"
client = Client(acct_id, twilio_auth_key)
recordings = client.recordings.list(limit=20)
for record in recordings:
   print(record.sid)

_rid = input("Which recording ID would you like?")

request_url = twilio_url + "/2010-04-01/Accounts/" + acct_id + "/Recordings/" + _rid + ".mp3"
response = requests.get(request_url)
with open(_rid+'.mp3', 'wb') as f:
   f.write(response.content)

print("File saved to", _rid+".mp3")

完成后,终端中的输出应该如下所示:

Download twilio voicemail recordings

我运行了三次来下载所有三个记录。您也可以执行下面的代码来一次下载所有的文件。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client
import requests

twilio_url = "https://api.twilio.com"
client = Client(acct_id, twilio_auth_key)
recordings = client.recordings.list(limit=20)
for record in recordings:
    _rid = record.sid
    request_url = twilio_url + "/2010-04-01/Accounts/" + acct_id + "/Recordings/" + _rid + ".mp3"
    response = requests.get(request_url)
    with open(_rid+'.mp3', 'wb') as f:
        f.write(response.content)
    print("File saved to", _rid+".mp3")

删除您的 Twilio 电话号码

在用 Twilio 设置我的一次性语音邮件后,我想做的最后一件事是能够以编程方式删除我的号码。

from configure import acct_id, twilio_auth_key
from twilio.rest import Client

client = Client(acct_id, twilio_auth_key)

phone_numbers = client.incoming_phone_numbers.list()

for record in phone_numbers:
   print(record.sid)

_del = input("Which phone number SID would you like to delete?")

client.incoming_phone_numbers(_del).delete()

它应该像这样运行:

Twilio voicemail number successfully deleted

包扎

在本教程中,我们介绍了如何使用 Twilio 的 API 来获取和下载语音邮件。这里有一个完整的教程,关于如何建立一个一次性手机来获取语音邮件记录。关于 AssemblyAI 的更多信息,请查看我们的博客,获取教程和更新。在推特上关注我们 @assemblyai 或者作者@于坚 _ 唐,保持更新。

如何运行 OpenAI 的耳语语音识别模型

原文:https://www.assemblyai.com/blog/how-to-run-openais-whisper-speech-recognition-model/

昨天,OpenAI 发布了其 耳语 语音识别模型。Whisper 加入了当今可用的其他开源语音到文本模型——如 Kaldi 、Vosk、wav2vec 2.0 等——并匹配语音识别的最先进结果。

在本文中,我们将学习如何安装和运行 Whisper ,我们还将对 Whisper 的准确性推理时间运行成本进行深入分析。

如何运行 OpenAI 的耳语

在本节中,我们将学习如何安装和使用 Whisper。如果你已经开始使用 Whisper,你可以跳到 Whisper 分析或者更复杂的 Whisper 高级用法

步骤 1:安装依赖项

Whisper 需要 Python3.7+和 PyTorch 的最新版本(我们使用 PyTorch 1.12.1 没有问题)。如果你还没有的话,现在安装 PythonPyTorch

Whisper 还需要一个音频处理库 FFmpeg 。如果您的机器上尚未安装 FFmpeg,请使用以下命令之一安装它。

# Linux
sudo apt update && sudo apt install ffmpeg

# MacOS
brew install ffmpeg

# Windows
chco install ffmpeg

其他详细信息

MacOS 安装命令需要 Homebrew ,Windows 安装命令需要 Chocolatey ,所以一定要根据需要安装其中一个工具。

最后,如果使用 Windows,请确保启用了开发人员模式。在您的系统设置中,导航到隐私&安全>开发者并打开顶部的拨动开关打开开发者模式(如果还没有打开的话)。

步骤 2:安装 Whisper

现在我们准备安装 Whisper。打开命令行并执行以下命令来安装 Whisper:

pip install git+https://github.com/openai/whisper.git 

第三步:运行耳语

命令行

首先,我们将从命令行使用 Whisper。只需打开一个终端,导航到你的音频文件所在的目录。我们将使用一个名为[audio.wav](https://github.com/AssemblyAI-Examples/audio-intelligence-dashboard/blob/master/gettysburg10.wav)的文件,这是葛底斯堡演说的第一行。要转录这个文件,我们只需在终端中运行以下命令:

whisper audio.wav

输出将显示在终端中:

(venv) C:\Users> whisper audio.wav
Detecting language using up to the first 30 seconds. Use `--language` to specify the language
Detected language: english
[00:00.000 --> 00:10.000]  Four score and seven years ago, our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.

转录也保存到audio.wav.txt中,还有一个文件audio.wav.vtt用于的隐藏字幕

计算机编程语言

在 Python 中使用 Whisper 进行转录非常容易。只需导入 whisper,指定一个型号,并转录音频。

import whisper

model = whisper.load_model("base")
result = model.transcribe("audio.wav")

可以使用result["text"]访问转录文本。结果对象本身包含其他有用信息:

{
  "text": " Four score and seven years ago, our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.",
  "segments": [
    {
      "id": 0,
      "seek": 0,
      "start": 0.0,
      "end": 10.0,
      "text": " Four score and seven years ago, our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.",
      "tokens": [
        50364,
        7451,
        6175,
        ...,
        2681,
        13,
        50864
      ],
      "temperature": 0.0,
      "avg_logprob": -0.1833780391796215,
      "compression_ratio": 1.3858267716535433,
      "no_caption_prob": 0.05988641083240509
    }
  ],
  "language": "en"
}

OpenAI 耳语分析

来自 Whisper 论文 的下图使用单词错误率 (WER)比较了 Whisper 与当前最先进的语音识别模型的准确性。如您所见,Whisper 报告实现了最先进的结果,这对于语音识别领域来说是一个令人兴奋的发展,尤其是考虑到 Whisper 是一个开源模型。

虽然这些结果令人兴奋,但语音识别仍然是一个未解决的问题,尤其是非英语语言。下图报告了每种支持语言的 Whisper 单词错误率。虽然 Whisper 在几种罗曼语、德语、日语等语言上取得了最先进的效果,但在其他语言上的性能相对较差。

Whisper word error rate as a function of language (source)

下面我们看到语言的分布是单词错误率的函数。在上图中的 82 种语言中,50 种语言的单词错误率超过 20%,

轶事比较

在组装时,我们的 API 由最先进的 Conformer-CTC 模型提供支持,该模型根据大约 100,000 小时的标记数据进行训练。为了探索 Whisper 的准确性,我们决定将 Whisper 与我们的模型进行几次并排比较。

首先,我们展示了来自 Whisper 公告帖子的微型机器示例的比较:

Micro Machines0:00/0:291×

组装

这是一个微型机器人展示了微型机器人最袖珍的微型车队。这一个有戏剧性的细节,完美的转弯,精确的油漆工作,加上令人难以置信的微型机器口袋的地方说,警察局,消防局,餐馆服务站,等等。完美的便携式口袋可以带去任何地方。而且还有很多微缩的地方可以玩。每一个都有自己的特别版微型机械车和有趣,神奇的功能,奇迹般地感动哦。在机场码头升起无螺栓船,在军事基地的炮塔站岗。在洗车场清洗你的车。升起塔桥。而这些地方合在一起就形成了一个微型机器世界。微型机器口袋地方非常小,非常精确,非常详细,你会想把它们都装进口袋。微型机器是与胶水分开出售的微型装置。越小越好。

谷歌语音转文本

这是迈克尔介绍的微型机器的最袖珍的微型车队,其中一个有戏剧性的细节可怕的当前位置支付工作加上令人难以置信的迈克尔舒马赫的地方,这是有一个警察局消防站餐厅服务站和更完美的桶便携式采取任何地方,还有许多其他地方玩的每一个都有自己的特别版迈克 eruzione 车辆和有趣的神奇功能,奇迹般地移动提高船看着 机场码头士兵军事基地的炮塔在洗车场清洗你的车抬高了收费桥这些游戏装置组合在一起形成了一个微型机器世界就像政体帕克广场一样如此之小如此完美精确如此令人眼花缭乱的细节乔安娜把它们装进口袋我所有的问题都是微型游戏装置与胶水分开出售它们越小越好

低语

这是微型机器人展示的微型机器人车队。每一个都有戏剧性的细节,令人恐怖的修剪,精确的油漆工作,加上令人难以置信的微型机器口袋游戏集。有警察局、消防局、餐馆、服务站等等。完美的便携式口袋,可随身携带。还有许多微型游戏集可以玩,每一个都有自己的特别版微型机器车和有趣的,神奇的功能,奇迹般地移动。在机场码头升起升船机。给军事基地的炮塔配备人员。在洗车场清洗你的车。抬高收费桥。这些游戏装置组合在一起形成了一个微型机器世界。微型机器口袋游戏集,非常小,非常精确,非常详细,你会想把它们都装进口袋。微型机器是与 Galoob 分开出售的微型机器口袋游戏装置。越小越好。

第二个例子是播客中的一个片段

Podcast0:00/1:301×

AssemblyAI

其中之一是我提出了一个主张,我认为大多数文明从简单的细菌之类的东西发展到太空,殖民文明,他们一生中只有很小一部分时间是在我们所处的位置,这一点我可能是错的。另一个我可能错了的是完全不同的陈述,我认为实际上我猜测我们是我们可观测的宇宙中唯一一个光到达我们这里的文明,实际上已经发展到足以发明望远镜的程度。所以让我们依次讨论这两者,因为它们确实不同。第一个,如果你看 N,等于这个星球上的数据。所以我们花了 45 亿年时间在这个星球上为生命奔忙。从智力的角度来看,大部分都是些蹩脚的东西。细菌和恐龙的进化速度大大加快,恐龙在没有发明智能手机的情况下在这里跺了一亿年。最近,我们从牛顿到我们只花了 400 年,对吗?是啊。在技术方面。看看我们都做了些什么。

谷歌语音转文本

其中之一是我提出了一个观点,我认为大多数文明从简单的细菌进化到太空殖民文明,它们只花了很小很小的一部分时间在我们所处的地方。另一个我可能是错的,我可能是错的,关于这个完全不同的陈述,我认为实际上我猜测我们是可观测的宇宙中唯一的文明,生命已经存在了几个星期,或者到目前为止,人类的望远镜已经足够远了,但是如果你看看这个古董,它是我们在这个星球上存在的时间之一,所以我们花了 45 亿年的时间 在这个有生命的星球上,我们得到的大部分东西从智能的角度来看都是很蹩脚的东西。他研究细菌,然后恐龙花了很长时间,到那时已经加速了。恐龙一年花了一亿多时间在这里走来走去,甚至没有发明智能手机。最近,我只花了四百年的时间就技术而言,从牛顿到现在的我们,看看我们都没有

低语

其中之一是,我声称,我认为大多数文明,从,我的意思是,简单的细菌之类的东西到太空殖民文明,他们一生中只有非常非常小的一部分时间在我们这里。我可能弄错了。另一个我可能会错的是完全不同的说法,我认为实际上我猜测我们是我们可观测的宇宙中唯一一个光到达我们这里的文明,它实际上已经发展到足以发明望远镜的程度。所以让我们依次讨论这两者,因为它们确实不同。第一个,如果你看 N 等于 1,我们在这个星球上有一个的日期,对吗?所以我们花了 45 亿年在这个星球上和生命鬼混,对吗?我们得到了,从智能的角度来看,大部分都是很蹩脚的东西,你知道,恐龙已经花了,然后事情实际上被加速了,对吗?那么恐龙已经在这里跺了一亿年了,却没有发明智能手机。最近,你知道,从牛顿到我们只用了四百年,对吗?就技术而言,我们甚至看到了我们所做的。

最后的音频文件是董事会议

Board of Directors Meeting0:00/2:391×

组装

东区宪章。对不起现在就去。好吧。我想召开东区特许学校和纽卡斯尔特许学校董事会的特别联席会议。是 535。我想给这个角色打电话。参加东区特许学校的有斯图尔特女士,索耶先生,戈登博士,黑尔先生,西姆斯女士,小威尔先生,弗图纳托女士,蒂诺女士和汉弗莱先生。参加纽卡斯尔的特许学校。我们有贝利医生、约翰逊女士、泰勒先生、麦克道尔先生、普雷斯顿先生和汉弗莱先生。我想念任何人,并且我不相信任何人在会议线上。因为我们的议程上没有公开的项目。我想要一份纽卡斯尔特许学校董事会的动议,进入管理层讨论,讨论人事问题。我会提出动议。谢谢你,老二先生,普雷斯顿先生。所有特许学校和纽卡斯尔董事会成员赞成,请说赞成。赞成。有人反对吗?动议一致通过。我也会问同样的问题。向东区特许学校提出同样的问题。谢谢 MSN。还有第二种吗?谢谢你,小牛肉先生。所有赞成的人,请说赞成。有人反对吗?好的,我们在 535 从公开会议转到执行会议。我们又回到公开会议了。你刚看了你的留言。好的,我们现在回到 715 的公开会议。没有其他的事情了,我将考虑纽卡斯尔特许学校的延期动议。谢谢你。还有第二种吗?谢谢你。所有赞成的人请说赞成。反对?特许学校。休庭东沃特特许学校。谢谢你。谢谢你米切尔女士。所有赞成的人,请说赞成。反对?动议通过。会议休会。非常感谢大家。

谷歌语音转文本

我想召开东区特许学校董事会特别联席会议是新城特许学校现在是 5 点 35 分我想点名,他们派人去东区特许学校戈登医生修女我很想念他们维尔纽斯·福图纳托·米西亚诺先生和汉弗瑞先生出席新城特许学校我们有贝利医生约翰逊先生泰勒先生麦克道尔小姐普雷斯顿先生和汉弗瑞先生有人和我吗 不要相信任何人都在会议线上我们的议程上没有公开的项目我想要一份来自新城堡特许学校董事会会议的动议进入管理层讨论讨论个人问题打电话给 Turtle Newcastle 董事会成员赞成的请说我特许学校所有赞成的请说我所以我们在 5:35 从公开会议进入管理层会议好的,现在是 750 +你可以离开了吗 现在是 7 点 15 分,没有其他事情了,我在《新城堡》的电影配乐之间休会谢谢,你们都同意吗?请说是我推荐她的,让她知道我将在她的学校做第二次推广,这需要 PPI 动议吗? /p >

低语

我想宣布纽卡斯尔特许学校东区特许学校董事会召开特别联席会议。是 535。我想给东区特许学校的角色和出席者打电话。我们有斯图尔特先生、索耶先生、戈登医生、海尔先生、泰晤士女士、小牛肉先生、帕尔托女士、迪恩诺女士和汉弗莱先生。参加纽卡斯尔特许学校,我们有贝利医生,约翰逊女士,泰勒先生,麦克道尔先生,普雷斯顿先生和汉弗莱先生。我不相信有人在会议线上。由于我们的议程上没有公开的项目,我希望纽卡斯尔特许学校董事会会议的动议进入管理层讨论,讨论人事问题。我会提出动议。谢谢普雷斯顿先生。所有纽卡斯尔特许学校董事会成员赞成,请说赞成。赞成。赞成。有人反对吗?动议一致通过。我会问东区特许学校同样的问题。谢谢你,泰晤士先生。还有第二种吗?谢谢你,小牛肉先生。所有赞成的人,请说赞成。赞成。有人反对吗?好吧。因此,我们在 535 从公开会议转到执行会议。好了,我们回来了。好吧。现在是 715。我们又回到了公开会议。你只需要拿着我的手机。好吧。所以我们现在回到 715 的公开会议。他们正在进一步发展。然后我将支付纽卡斯尔特许学校的延期动议。谢谢你。还有第二种吗?谢谢你。所有赞成的,请说赞成。赞成。有人反对吗?特许学校停课了。我会像往常一样向东区特许学校提出同样的动议。谢谢你。泰晤士先生,米切尔先生,所有赞成的人,请说赞成。有人反对吗?动议通过。会议休会。非常感谢大家。

正如我们所看到的,Whisper 表现非常好,是当今语音识别可用的最先进选项的一个极好的补充。

耳语推理时间

Whisper 有五种尺寸——微型、基本(默认)、小型、中型和大型——尺寸越来越精确。因此,大模型具有最好的准确性,并且是本文和上图中报告的基准中使用的模型。Whisper 在 CPU 和 GPU 上都可以使用;然而,当使用较大的模型时,推理时间在 CPU 上非常慢,所以建议只在 GPU 上运行它们。

在每个模型大小下,使用 Whisper 在 CPU 和 GPU 上转录了 Micro Machines 示例,推断时间报告如下。首先,我们看到 CPU (i5-11300H)的结果

接下来,我们得到了在 GPU(高内存 GPU Colab 环境)上的结果

这是并列的相同结果

实施细节

如果在 CPU 上使用 Whisper 时碰到 RuntimeError "slow_conv2d_cpu" not implemented for 'Half',那就要用 Python 中的 Whisper 的底层 API ,用options = whisper.DecodingOptions(fp16=False)代替options = whisper.DecodingOptions()

运行 Whisper 的成本

我们提供在 GCP 使用 Whisper(1x A100 40 GB)录制 1,000 小时音频的成本,每个型号使用不同的批量,其值可在图例中找到。

最后的话

我们的上述分析表明,Whisper 在多种语言的语音识别方面取得了最先进的结果。与其他开源选项相比,Whisper 将成为研究人员和黑客的一个宝贵工具,因为它的准确性和易用性。Whisper 的性能部分源于其计算强度,因此需要更大、更强大版本的 Whisper 的应用程序应该确保在 GPU 上运行 Whisper,无论是在本地还是在云中。

耳语高级用法

我们在上面的如何运行 OpenAI 的 Whisper 一节中熟悉了 Whisper。对于一个更复杂的例子,我们将回顾一下多语言 ASR 笔记本的修改版本。执行以下命令下载示例代码并安装必要的要求:

git clone https://github.com/AssemblyAI-Examples/whisper-multilingual.git
cd whisper-multilingual
pip install -r requirements.txt 

接下来,只需运行python main.py即可将几个韩语音频文件转录并翻译成英语。CPU 处理每个数据大约需要 3 分钟。我们总共使用 10 个数据点,所以让这个过程在后台运行,同时我们检查main.py代码。

首先,我们执行所有必要的导入,然后定义一个用于下载和存储音频数据的类。这个类的细节是不相关的,所以为了简洁起见,省略了它们。

import io
import os

import torch
import pandas as pd
import urllib
import tarfile
import whisper

from scipy.io import wavfile
from tqdm import tqdm

class Fleurs(torch.utils.data.Dataset):
	pass

接下来,我们用 pandas 设置一些显示结果的参数,设置用于推断的设备,然后设置指定音频语言的变量。第一个是用于下载数据的朝鲜语代码,后者是用于 Whisper 模型的朝鲜语代码。

# Display options for pandas dataset
pd.options.display.max_rows = 100
pd.options.display.max_colwidth = 1000

# Set inference device
device = "cuda" if torch.cuda.is_available() else "cpu"

# Set language (korean)
language_google = "ko_kr"
language_whisper = "korean"

现在,我们使用上面定义的类创建数据集,选择 10 个音频文件的子样本以加快处理速度。

# Create dataset object, selecting only 10 examples for brevity
dataset = Fleurs(language_google, subsample_rate=1, device=device)
dataset = torch.utils.data.random_split(dataset, [10, len(dataset)-10])[0]

接下来,我们加载将要使用的 Whisper 模型,选择“微小的”模型版本以使推断更快。然后,我们设置转录和翻译选项。

# Load tiny Whisper model
model = whisper.load_model("tiny")

# Set options
options = dict(language=language_whisper, beam_size=5, best_of=5)
transcribe_options = dict(task="transcribe", **options)
translate_options = dict(task="translate", **options)

最后,我们遍历数据集,将每个音频文件翻译成韩语,并将每个音频文件翻译成英语。注意,翻译直接在音频数据上发生,并且不将生成的转录翻译成英语。除了用于比较的基本事实参考之外,我们将转录和翻译保存到列表中。

*`# Run inference
references = []
transcriptions = []
translations = []

for audio, text in tqdm(dataset):
    transcription = model.transcribe(audio, **transcribe_options)["text"]
    translation = model.transcribe(audio, **translate_options)["text"]

    transcriptions.append(transcription)
    translations.append(translation)
    references.append(text)`*

最后,我们创建存储结果的 pandas 数据帧,然后打印结果并保存到 CSV。

*`# Create dataframe from results and save the data
data = pd.DataFrame(dict(reference=references, transcription=transcriptions, translation=translations))
print(data)
data.to_csv("results.csv")`*

结果如下所示

https://airtable.com/embed/shrsWzrOGuzdC2nNQ?backgroundColor=blue

如何在局部运行稳定扩散来生成图像

原文:https://www.assemblyai.com/blog/how-to-run-stable-diffusion-locally-to-generate-images/

紧随 DALL-E 2Imagen 的脚步,新的深度学习模型 稳定扩散 标志着文本到图像领域的一次量子飞跃。本月早些时候发布的稳定扩散承诺通过足够高效地在消费级 GPU 上运行来民主化文本条件图像生成。就在本周一,稳定的扩散检查点首次发布,这意味着,现在,你只需几句话和几分钟时间就可以生成如下图像。

本文将向您展示如何在 GPUCPU 上安装和运行稳定扩散,这样您就可以开始生成自己的图像了。让我们开始吧!

在 Colab 中使用稳定扩散

在我们了解如何在本地安装和运行稳定扩散之前,您可以查看下面的 Colab 笔记本,以了解如何在非本地使用稳定扩散。请注意,您将需要 Colab Pro 来生成新图像,因为 Colab 的免费版本用于采样的 VRAM 略少。

Stable Diffusion in Colab (GPU)

如果您没有 Colab Pro,也可以在 Colab 中的 CPU 上运行稳定扩散,但请注意,图像生成将需要相对较长的时间(8-12 分钟):

Stable Diffusion in Colab (CPU)

你也可以在 YouTube 上查看我们的稳定扩散教程,了解如何使用 GPU 笔记本。

如何安装稳定扩散(GPU)

你将需要一个基于 UNIX 的操作系统来跟随本教程,所以如果你有一台 Windows 机器,考虑使用一个虚拟机WSL2

步骤 1:安装 Python

首先,通过在终端中键入python --version来检查 Python 是否安装在您的系统上。如果返回的是 Python 版本,则继续进行下一步。否则,使用安装 Python

sudo apt-get update
yes | sudo apt-get install python3.8

步骤 2:安装 Miniconda

接下来,我们需要确保安装了包/环境管理器 conda 。在终端输入conda --version。如果返回的是 conda 版本,则进入下一步

否则,请访问 conda 网站,下载并运行适用于您的 Python 版本和操作系统的 Miniconda 安装程序。对于 Python3.8,您可以使用以下命令下载并运行安装程序:

wget https://repo.anaconda.com/miniconda/Miniconda3-py38_4.12.0-Linux-x86_64.sh
bash Miniconda3-py38_4.12.0-Linux-x86_64.sh

按住键输入以获得许可,然后在出现提示时键入“yes”继续。接下来,按回车确认安装位置,然后在询问安装程序是否应该初始化 Miniconda 时键入“yes”。最后,关闭终端,然后打开一个新的你想安装稳定扩散。

步骤 3:克隆稳定的扩散库

现在我们需要克隆稳定的扩散库。在终端中,执行以下命令:

git clone https://github.com/CompVis/stable-diffusion.git
cd stable-diffusion/

如果你没有 git,你需要用sudo apt install git来安装它。在克隆存储库之前,请务必阅读并接受稳定扩散许可证

步骤 4:创建 Conda 环境

接下来,我们需要创建一个 conda 环境,容纳我们运行稳定扩散所需的所有包。执行以下命令创建并激活这个名为ldm的环境

conda env create -f environment.yaml
conda activate ldm

步骤 5:下载稳定的扩散权重

既然我们已经在合适的环境中使用稳定扩散,我们需要下载运行它所需的权重。如果您尚未阅读并接受稳定扩散许可,请务必现在就阅读并接受。已经发布了几个稳定的扩散检查点版本。较高的版本号已经过更多数据的训练,通常比较低的版本号表现更好。我们将使用检查站 1.4 版。使用以下命令下载权重:

curl https://f004.backblazeb2.com/file/aai-blog-files/sd-v1-4.ckpt > sd-v1-4.ckpt

这就是我们开始使用稳定扩散所需的所有设置!请继续阅读,了解如何使用模型生成图像。

如何生成稳定扩散的图像(GPU)

要生成具有稳定扩散的图像,请打开终端并导航至stable-diffusion目录。通过执行命令conda activate ldm确保您处于正确的环境中。

要生成图像,请运行以下命令:

python scripts/txt2img.py --prompt "YOUR-PROMPT-HERE" --plms --ckpt sd-v1-4.ckpt --skip_grid --n_samples 1 

解决纷争

  • 如果您收到一个ImportError,您可能需要运行sudo apt-get install libsm6 libxrender1 libfontconfig1
  • 如果执行突然停止,并且killed被打印到终端,您可能会遇到内存不足的错误。运行cat /var/log/kern.log查看有用的日志。
  • 如果你遇到一个RuntimeError: CUDA out of memory错误,试着缩小图像的尺寸(并确保你只用--n_samples 1对一幅图像进行采样)。最小图像尺寸为 256x256。

在这里,用要生成图像的标题替换YOUR-PROMPT-HERE(留下引号)。使用提示 “一只蜥蜴骑着滑雪板穿越太空的真实感蒸汽波图像” 运行该命令,输出以下图像:

上面的图像是在 GCP 使用 Ubuntu 18.04 VM 和 NVIDIA Tesla K80 在大约一分钟内生成的。

脚本选项

您可以使用几个命令行参数定制该脚本,以根据您的需要定制结果。让我们来看看一些可能会派上用场的方法:

  1. --prompt后面加上引号的句子将指定提示为其生成图像。默认为“一幅病毒怪物弹吉他的画”。
  2. --from-file指定提示的文件的文件路径,用于生成图像。
  3. --ckpt后跟一个路径,指定使用模型的哪个检查点。默认为models/ldm/stable-diffusion-v1/model.ckpt
  4. --outdir后面跟一个路径将指定输出目录来保存生成的图像。默认为outputs/txt2img-samples
  5. --skip_grid将跳过创建组合图像。
  6. --ddim_steps后面跟一个整数指定了扩散过程中的采样步数。增加该数值会增加计算时间,但可能会改善结果。默认值为 50。
  7. --n_samples后跟一个整数,指定对于每个给定的提示(批量)要生产多少样品。默认值为 3。
  8. --n_iter后跟一个整数指定运行采样循环的次数。实际上与--n_samples相同,但如果遇到 OOM 错误,则使用此选项。参见源代码进行澄清。默认值为 2。
  9. --H后跟一个整数指定生成的图像的高度(以像素为单位)。默认值为 512。
  10. --W后跟一个整数指定生成图像的宽度(以像素为单位)。默认值为 512。
  11. --scale后跟一个浮点指定了 指导比例 使用。默认值为 7.5
  12. --seed后跟一个整数,允许设置随机种子(用于可重复的结果)。默认值为 42。

您可以在[txt2img.py](https://github.com/CompVis/stable-diffusion/blob/7b8c883b078024f68b56e862f247a64f9e282aac/scripts/txt2img.py#L45)文件中看到带有默认值的可能参数的完整列表。现在让我们看一个使用这些可选参数的更复杂的生成提示。

stable-diffusion目录中,创建一个名为prompts.txt的文件。创建几个提示,文件的每一行一个。例如:

现在,在终端的stable-diffusion目录下,运行

python scripts/txt2img.py \
--from-file prompts.txt \
--ckpt sd-v1-4.ckpt \
--outdir generated-images \
--skip_grid \
--ddim_steps 100 \
--n_iter 3 \
--H 256 \
--W 512 \
--n_samples 3 \
--scale 8.0 \
--seed 119

下面可以看到每个标题的两个结果图像。上面的命令旨在作为使用更多命令行参数的示例,而不是作为最佳参数的示例。一般来说,根据经验,较大的图像质量较高,图像/字幕相似性较大,较低的引导比例可能会产生更好的结果。继续阅读下一节,了解更多关于改善稳定扩散结果的信息。

如何安装稳定扩散(CPU)

步骤 1:安装 Python

首先,通过在终端中键入python --version来检查 Python 是否安装在您的系统上。如果返回 Python 版本,继续下一步。否则,使用安装 Python

sudo apt-get update
yes | sudo apt-get install python3.8

步骤 2:下载存储库

现在我们需要克隆稳定的扩散库。我们将使用一个可以适应 CPU 推理的分支。在终端中,执行以下命令:

git clone https://github.com/bes-dev/stable_diffusion.openvino.git
cd stable_diffusion.openvino

如果你没有 git,你需要用sudo apt install git来安装它。在克隆存储库之前,请务必阅读并接受稳定扩散许可证

步骤 3:安装需求

安装所有必要的要求

pip install -r requirements.txt 

请注意,Scipy 1 . 9 . 0 版是一个列出的需求,但是它与 python 的旧版本不兼容。在运行上述命令之前,您可能需要通过编辑requirements.txt来更改 Scipy 版本,例如,改为scipy==1.7.3

步骤 4:下载稳定的扩散权重

既然我们已经在合适的环境中使用稳定扩散,我们需要下载运行它所需的权重。如果您尚未阅读并接受稳定扩散许可,请务必现在就阅读并接受。已经发布了几个稳定的扩散检查点版本。较高的版本号已经过更多数据的训练,通常比较低的版本号表现更好。我们将使用检查站 1.4 版。使用以下命令下载权重:

curl https://f004.backblazeb2.com/file/aai-blog-files/sd-v1-4.ckpt > sd-v1-4.ckpt

这就是我们开始使用稳定扩散所需的所有设置!请继续阅读,了解如何使用模型生成图像。

如何生成具有稳定扩散的图像(CPU)

现在一切都安装好了,我们准备生成稳定扩散的图像。要生成图像,只需运行下面的命令,将提示符更改为您想要的任何值。

python demo.py --prompt "bright beautiful solarpunk landscape, photorealism"

推断时间将在大约 8-12 分钟时很长,所以在稳定扩散运行时,请随意喝杯咖啡。下面我们可以看到运行上述命令的输出:

提示和技巧

当您开始使用稳定扩散时,请在探索过程中记住这些提示和技巧。

快速工程

来自文本到图像模型的结果可能对用于描述期望场景的措辞敏感。提示工程是定制提示以获得期望结果的实践。例如,如果生成了低质量的图像,请尝试在标题前加上“的图像”。你也可以指定不同的风格和媒介,以达到不同的效果。查看以下每个下拉列表中的想法:

图像类型

尝试在标题前添加以下内容之一,以获得不同的效果:

  • “的形象”
  • "的照片"
  • "正面照"
  • "一幅画"
  • “一个愿景”
  • “对……的描述”
  • 《一幅漫画》
  • "一幅画"
  • "一个数字"
  • "的插图"
  • "的草图"
  • “的写照”

风格

您可以指定不同的样式来实现不同的结果。尝试在提示中添加一个或多个下面的形容词,观察效果。

  • "现代主义者(ic)"
  • “抽象”
  • "印象派(ic)"
  • "表现主义(ic)"
  • "超现实主义者(ic)"

美学

您也可以尝试指定不同的美学。尝试在提示中添加一个或多个下面的形容词,观察效果。

  • “蒸汽波”
  • “合成波”
  • “赛博朋克”
  • “太阳能电池板”
  • “蒸汽朋克”
  • “山寨核心”
  • “天使核心”
  • “外星人”

艺术家

你甚至可以尝试指定不同的艺术家来达到不同的视觉效果。尝试在提示符后附加以下内容之一:

  • 画家:
    • “以文森特·梵高的风格”
    • “以巴勃罗·毕加索的风格”
    • “以安德鲁·沃霍尔的风格”
    • “以弗里达·卡罗的风格”
    • “以杰森·布拉克的风格”
    • “以萨尔瓦多·达利的风格”
  • 雕塑家:
    • “以米开朗基罗的风格”
    • “以多那太罗的风格”
    • “以奥古斯特·罗丹的风格”
    • “以理查德·塞拉的风格”
    • “以亨利·摩尔的风格”
  • 建筑师:
    • “以弗兰克·劳埃德·赖特的风格”
    • “以密斯·凡·德·罗的风格”
    • “以额罗·沙里宁的风格”
    • “以安东尼·高迪的风格”
    • “以弗兰克·盖里的风格”

调整采样参数

调整采样参数时,您可以利用下面的经验观察来指导您的探索。

图像尺寸

一般来说,从经验上看,较大的图像在图像质量和字幕对齐方面都比较小的图像好得多。关于 256x256 和 512x512 尺寸图像的提示“Guy Fieri 参观鬼屋”,请参见以下示例:

256x256 vs. 512x512 sample comparison for the prompt "Guy Fieri giving a tour of a haunted house"

扩散步骤数

看起来扩散过程中的步数对超过大约 50 个时间步的某个阈值的结果影响不大。下面的图片是使用相同的随机种子和提示 【一辆红色跑车】 生成的。可以看出,更大数量的时间步长持续地提高了所生成图像的质量,但是过去 50 个时间步长的提高仅仅表现为对感兴趣对象的附带环境的微小改变。从 25 个时间步开始,赛车的细节实际上几乎完全一致,环境也在改善,在更大的时间步中变得更适合赛车。

full resolution version

图像纵横比

看起来图像质量和字幕相似性作为纵横比的函数取决于输入字幕。下面的图片面积相同但长宽比不同,都是使用标题 “一座钢和玻璃的现代建筑” 生成的。结果是相对统一的,虽然垂直图像似乎是最好的,其次是正方形,然后是水平。鉴于这种类型的现代建筑又高又瘦,这并不奇怪。因此,作为长宽比函数的性能似乎取决于主体。

不幸的是,稳定扩散受限于可因子分解的长宽比,使得更精细的实验不可能,但无论如何正方形图像应该满足大多数目的。

检查点符号链接

为了避免每次生成图像时都必须向检查点提供--ckpt sd-v1-4.ckpt,您可以在检查点和默认值--ckpt之间创建一个符号链接。在终端中,导航到stable-diffusion目录并执行以下命令:

mkdir -p models/ldm/stable-diffusion-v1/
ln -s sd-v1-4.ckpt models/ldm/stable-diffusion-v1/model.ckpt 

或者,您可以简单地将检查点移动到默认的--ckpt位置:

mv sd-v1-4.ckpt models/ldm/stable-diffusion-v1/model.ckpt

我真的把瑞克卷进去了吗?

是的。如果稳定扩散检测到生成的图像可能违反其安全过滤器,则生成的图像将被替换为里克·阿斯特利的静止图像。

最后的话

这就是使用新的稳定扩散模型生成图像所需要的一切-不要忘记在 Twitter 上与我们分享您有趣的创作!如果你想了解更多关于稳定扩散是如何工作的,你可以查看我们的机器学习扩散模型介绍文章。如果你喜欢这篇文章,请随时查看我们的博客YouTube 频道的机器学习内容,或者随时关注我们的时事通讯,了解新发布的内容。

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow

我们使用稳定扩散生成的一些图像:

作为创业公司,如何训练大型深度学习模型

原文:https://www.assemblyai.com/blog/how-to-train-large-deep-learning-models-as-a-startup/

介绍

OpenAI 的 GPT-3 是一个令人印象深刻的深度学习模型,但在 175B 参数下,它是相当的资源猪!估计各不相同,但这种规模的模型将需要数百年才能在单个 GPU 上训练

幸运的是,OpenAI 受益于微软提供的 NVIDIA V100 GPU 的高带宽集群,这使他们能够在几周内训练 GPT-3,而不是几年。这个星团到底有多大?根据本文中的,在 1024 个 NVIDIA A100 GPUs 上训练 GPT-3 大约需要 34 天。

这是一个令人难以置信的 GPU 数量。每个 100 GPU 的价格为$ 9900,我们谈论的是差不多$ 1000 万来建立一个那么大的集群。我们甚至没有考虑电力成本,或者实际安装 GPU 的服务器机架,或者维护这种硬件的人力成本,以及其他成本。

如今,你可以从像谷歌云这样的公共云提供商那里租用 100 个 GPU,但每小时 2.933908 美元(T1),这仍然需要花费 2451526.58 美元(T3)来运行 1024 个 A100 GPU 34 天。记住,这个价格是单次训练的

我可以继续,但重点是训练大模特又贵又慢。在 AssemblyAI 这里,我们不是在 175B 参数范围内训练模型(谢天谢地),但我们的语音识别模型是非常大的变压器,正在快速接近 1B 参数大小。作为一家初创公司,速度和成本是我们必须不断优化的两件事。

创业都是为了快速迭代。大多数公司可以从客户那里得到反馈,并在一个周末发布一个新功能。但当你是一家深度学习公司,你的模型需要数周时间来训练时,你作为初创公司的迭代速度会受到显著阻碍。

解决这个问题的主要方法是在更多的 GPU 上训练你的模型,但这需要很高的成本,通常是初创公司负担不起的。在过去的几年里,我们学到了一些关于训练大型模型的经验,并希望与社区分享。

我们的模型尺寸和训练时间

在 AssemblyAI,我们构建大型、精确的自动语音识别(ASR)模型,并通过简单的语音转文本 API 公开这些模型。开发人员使用我们的 API 来构建应用程序,以转录电话、缩放会议、播客、视频和其他类型的媒体内容。

See Also: What is ASR?

我们性能最好的 ASR 模型是大型变压器,在 48x V100 GPUs 上训练大约需要 3 周时间。

32 NVIDIA V100s training a model

为什么我们的模型需要这么长的训练时间,需要这么多的 GPU?有三个主要的促成因素:

1.ASR 模型的输入特征是高维、长序列

我们或多或少每 10 毫秒计算一次音频文件的声谱图,并将这些值作为神经网络的输入特征。声谱图的形状/尺寸因音频数据的采样率而异,但如果采样率为 8000 Hz,声谱图中的特征数将为 81。对于一个 16 秒的音频样本,形状将是[1600, 81] -这是一个相当大的特征输入!

下面是光谱图作为矩阵的一个例子:

[[[-5.7940, -5.7940, -4.1437,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9598, -5.9598, -4.2630,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9575, -5.9575, -4.2736,  ...,  0.0000,  0.0000,  0.0000],
          ...,
          [-4.6040, -4.6040, -3.5919,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4804, -4.4804, -3.5587,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4797, -4.4797, -3.6041,  ...,  0.0000,  0.0000,  0.0000]]],

        [[[-5.7940, -5.7940, -5.7940,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9598, -5.9598, -5.9598,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9575, -5.9575, -5.9575,  ...,  0.0000,  0.0000,  0.0000],
          ...,
          [-4.6040, -4.6040, -4.6040,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4804, -4.4804, -4.4804,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4797, -4.4797, -4.4797,  ...,  0.0000,  0.0000,  0.0000]]],

        [[[-5.7940, -5.7940, -5.7940,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9598, -5.9598, -5.9598,  ...,  0.0000,  0.0000,  0.0000],
          [-5.9575, -5.9575, -5.9575,  ...,  0.0000,  0.0000,  0.0000],
          ...,
          [-4.6040, -4.6040, -4.6040,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4804, -4.4804, -4.4804,  ...,  0.0000,  0.0000,  0.0000],
          [-4.4797, -4.4797, -4.4797,  ...,  0.0000,  0.0000,  0.0000]]] 

2.我们的模型有大量的参数

当谈到基于变压器的神经网络时,通常越大越好。有许多论文支持这种说法,GPT-3 是最受欢迎的例子。在研究团体和我们自己的内部研究中,我们发现这种趋势对于 ASR 模型也是如此。

我们性能最好的型号是拥有近 5 亿个参数的大型变压器。参数越多,在反向传播期间更新梯度所需的计算能力就越大。训练一个神经网络基本上可以归结为做一堆矩阵运算。模型中的参数越多,矩阵就越大。大型矩阵需要更多的计算和 GPU 内存资源。

Neural Networks require lots of Matrix Multiplication. Source: https://www.jeremyjordan.me/intro-to-neural-networks/

3.我们根据大量数据进行训练

大型模型具有更强的建模能力——这要归功于它们参数大小的增加——为了利用这种建模能力,我们在近 100,000 小时的标记语音数据上训练我们的模型。例如,GPT-3 是在 45TB 的文本数据上训练的,这也可以被认为是大约 1099511626800 个单词的文本。

在训练神经网络时,需要对数据集进行多次迭代(每次迭代称为一个“时期”)。数据集越大,每次迭代或“时期”花费的时间就越长。即使提前停止,在大数据集上训练 20-50 个时期的大模型也会花费很多时间!

如何提高迭代速度

创业公司有一项艰巨的任务——在短时间内取得很大进展。那些在最短时间内取得最大进展的公司,通常被称为“突破型”创业公司。

作为一家深度学习初创公司,这提出了一个艰难的挑战。当你的模型需要 3-4 周训练时,你如何快速迭代?

在更多 GPU 上训练

减少训练时间最简单的方法就是在更多的 GPU 上训练你的模型。更多的 GPU 意味着更多的 GPU 内存可用于您的训练。例如,假设您可以在单个 GPU 上安装一个大小为 8 的小批量。如果您的数据集中有 1,000 个样本要迭代,这意味着要迭代 125 个小批量(每个大小为 8)。如果每次迭代需要 1 秒,那么迭代所有 125 个小批量将需要 125 秒。

如果你有 4 个 GPU,你可以一次并行迭代 4 个小批量,而不是 1 个小批量。这意味着只需要 32 次迭代就可以完成所有 125 个小批量。假设现在 4 个 GPU 的每次迭代需要 1.5 秒,因为 4 个 GPU 的额外通信开销,但您仍然可以在 48 秒内迭代整个数据集(32 * 1.5)。这几乎比单个 GPU 快 3 倍!

然而,需要注意的一点是,更大的批量并不总是等同于更快的训练时间。如果您的有效批量变得太大,它会开始损害您的模型的整体收敛性。选择正确的批量大小进行训练是一个你必须试验的超参数,并且正在对不同的优化器进行研究,如 LAMB 和 LARS 有助于缓解大批量大小损害收敛的问题。

GPU 性能不是线性扩展的

训练的 GPU 越多,通信开销就越大。例如,这就是为什么在 8 个 GPU 上进行训练并不比在单个 GPU 上进行训练快 8 倍。在 AssemblyAI,我们使用 Horovod 来管理我们跨许多 GPU 的分布式训练运行。Horovod 是一个很棒的库,当你向训练集群添加更多的 GPU 时,它可以帮助你获得更高的效率。

Training times with Horovod

在我们的测试中,我们发现 Horovod 比分布式 TensorFlow 和 PyTorch 分布式 DataParallel 快得多。也就是说,PyTorch 正在积极开发和快速改进中。在我们的测试中,我们发现 PyTorch DistributedDataParallel 在单个服务器上与 Horovod 不相上下——但是 Horovod 在将训练运行扩展到多个服务器时表现更好(例如,4 个服务器,每个服务器有 8 个 GPU)。

低精度训练

默认情况下,大多数模型都使用 FP32 进行训练(浮点值 32,也称为单精度)。用半精度(FP16)或混合精度训练,也可以加快你的训练时间。

FP16 张量是 16 位,或 2 字节,其中每一位是 0 或 1,如01010101 10101010。一个 FP32 张量是 32 位,或者 4 字节,比如11110000 00001111 11001100 00110011

训练期间的精度越低,意味着字节数越少,这意味着训练期间需要的 GPU 内存越少,需要的带宽越少,新 GPU 上的实际硬件级操作往往运行得更快——所有这些都加快了训练时间。

使用 PyTorch 下降到 FP16 相对容易,例如x = x.half将 FP32 张量向下投射到 FP16。然而,要记住的是,在实践中低精度的训练并不总是在公园里散步。一些操作或自定义损失函数可能不支持较低的精度,可能需要大量的超参数调整才能使您的模型与 FP16 收敛,较低的精度也可能会损害您的模型的整体精度。

如何降低培训成本

这很简单- 不要使用像 AWS 或 Google Cloud 这样的公共云。这似乎是最简单的开始方式,但是成本会很快增加,尤其是与下面的选项相比。

购买自己的硬件

如果你喜欢管理自己的硬件(我们不建议这样做),购买消费级 GPU,如 NVIDIA TITAN X ,是一个相对便宜的选择。例如,每台 TITAN X 的价格大约为、3000 美元,作为一张消费级 GPU 卡,它的性能出奇的好。如果你有能力建造自己的训练平台,这条路线会带来一次性的硬件费用,但也会带来托管和维护训练平台的麻烦。

https://lambdalabs.com/这样的公司提供可定制的、相对便宜的训练装备,可以运送给你。例如,一台配有 4 块英伟达 RTX A5000 卡和 NVLink 的电脑售价约为 16500 美元。这包括内存、处理器、外壳等。你要做的就是找个地方插上这个,然后付电费。

专用云服务器

在 AssemblyAI,我们从 Cirrascale 租赁专用服务器。有许多像 Cirrascale 这样的提供商,但是为专用服务器付费比像 AWS 或 Google Cloud 这样的大型公共云给提供了更好的价格。该选项还使您能够根据您需要的 RAM 和处理器规格定制您的机器,并在您可以选择的 GPU 卡方面提供更多灵活性。

例如,AWS 仅提供以下 GPU:

  • NVIDIA Tesla M60 GPUs
  • 英伟达 A100
  • 英伟达特斯拉 V100
  • NVIDIA K80(这些太恐怖了)

而 Cirrascale 则提供了各种各样的 GPU,如 P100s、V100s、A100s、RTX 8000 等。

通常,你不需要最昂贵的 GPU 卡(今天的 A100s)来在合理的时间内训练你的模型。另外,最新和最棒的 GPU 通常不会立即得到 PyTorch 和 TensorFlow 等流行框架的支持。例如,NVIDIA A100s 在 PyTorch 支持之前花了一点时间。

与 AWS 或 Google Cloud 等大型公共云相比,能够定制一台机器来满足您的培训需求和预算是与较小的托管提供商合作的巨大优势。你也不必担心语音转文本的隐私问题,就像你有时对这些大提供商所做的那样。此外,因为您租用的是整个物理机,而不是像通过 AWS/GCP 获得的虚拟机,所以实际机器的整体性能要好得多。

包扎

总之,作为一家初创公司,训练大型深度学习模型是一个其他许多初创公司都不会面临的挑战。成本可能很高,迭代时间可能很慢,如果你不小心,这些因素可能会严重阻碍你的创业进展。

如果你正在考虑创办一家深度学习公司——我们强烈推荐申请 Y Combinator 的人工智能赛道。AssemblyAI 是 2017 年这条赛道的一部分,它拥有 AWS 和 GCP 等大型公共云的大量 GPU 积分,以及 Cirrascale 等提供商的折扣。

丹尼尔·格罗斯的先锋计划还提供高达 10 万美元的 AWS 积分,你可以在离开地面时花在训练跑步上。

如果你想阅读更多这样的帖子,你可以在 Twitter @assemblyai@youvegotfox 上关注我们。

寻找更多这样的帖子?

订阅我们的时事通讯!

[Subscribe Now](Subscribe to our newsletter!)

AI 抄录歌词有多好?

原文:https://www.assemblyai.com/blog/how-well-does-ai-transcribe-song-lyrics/

自从行业广泛采用深度学习技术以来,人工智能识别语音的能力大幅提高。如今, AI 转录技术与人类转录的准确度在同一水平上。至少,人类的言语就是如此。我们很好奇 AI 能多好地识别歌曲中的歌词。

唱歌和说话在音调、持续时间和强度方面有很大的不同。歌唱有更高的强度,更多的音高变化,每个乐句使用更多的空气。唱歌也少了很多吐字,多了很多对元音的强调。在日常讲话中,元音和辅音的比例大约是五比一,在歌唱中,这个比例可以高达 200 比一。这是人工智能在更大的强度和音高范围内识别语音的预期元音时间的 40 倍。基于这些差异,我们不希望自动语音识别模型能够识别超过 5%或 1/20 的歌曲。在我们下面的发现中,这被证明是大部分正确的,但是在一些场景中,我们的自动语音识别模型能够相当好地转录一首歌曲!

如何测试 AI 在歌词上的转录

我们用 AssemblyAI 的语音转文本 API 转录了来自 3 种不同流派和 15 位不同艺术家的 15 首歌曲,并用 Python jiwer 库计算了它们的单词错误率(WERs) ,以便了解 AI 转录音乐的能力。因为我们只希望人工智能能答对 1/20,或 5%的歌曲,我们希望 WER 的分数能超过 0.95。

我们是如何着手分析这些歌曲的?我们使用 Python 库 youtube_dl 从 youtube 下载视频,并将音频提取到 mp3 文件中。然后我们使用 Python Click 和 AssemblyAI 语音转文本 API 来上传和转录音频到文本文件中。我们在谷歌上查找我们所选歌曲的官方歌词,并将它们复制到文本文件中。最后,我们使用 jiwer Python 库按流派计算每首歌曲的单词错误率并进行比较。你可以在这里找到的源代码。我们下面要评估的三种类型是流行音乐、摇滚和 RnB。

AI 对流行音乐的识别能力有多强?

我们准备从 YouTube 上下载五首知名的流行歌曲,用 AssemblyAI 的 AI 转录 API 进行转录,然后根据官方歌词计算它们的单词错误率。我们挑选分析的五首流行歌曲分别是:迈克尔杰克逊的【颤栗】【西瓜糖】哈里·斯泰尔斯【好 4 U】奥利维亚·罗德里戈【漂浮】泰勒斯威夫特【最狂野的梦】。

令人惊讶的是,最受认可的歌曲是泰勒·斯威夫特的《最狂野的梦》。

请敲鼓,最不被认可的歌曲是迈克尔·杰克逊的《颤栗》。

如果你听了这些歌,你会觉得这很有意义。《颤栗》的人声更快,迈克尔·杰克逊的声音覆盖范围更广。所选流行歌曲的单词错误率从低到高的顺序是《最狂野的梦》、《好 4 U》、《漂浮》、《西瓜糖》、《颤栗》。总的来说,女声在这组歌曲中得到更好的认可,也许在其他流派中也是如此。现在来看实际数字!​​

以下是流行音乐中单词错误率从低到高的顺序。最低的 WER 是泰勒·斯威夫特的《最狂野的梦》,WER 得分为 0.593,其次是奥利维亚·罗德里戈的《好 4 U》,WER 得分为 0.660,第三是杜阿·利帕的《漂浮》,得分为 0.745,第四是哈里·斯泰尔斯的《西瓜糖》,WER 得分为 0.807,最后是迈克尔·杰克逊的著名歌曲《颤栗》,WER 得分最高,为 0.878。这些数字表明,我们成功录制了泰勒·斯威夫特的《最狂野的梦》中几乎一半的歌曲,这超出了我们的预期。我们来看看成绩单实际上是什么样子的。人工智能转录了宋立科的开头:

“He said let's get out of this town Drive out other cities away from the crowd”

https://www.youtube.com/embed/IdneKLhsWOQ?start=11&end=21

很好,考虑到实际的话是“他说让我们离开这个城镇,开车离开这个城市,远离人群。”人工智能只是把“of”搞乱成了“other”,考虑到背景音乐,这是合理的,当你大声说出这些词时,它们听起来非常相似!《颤栗》怎么样?《颤栗》是如何开始的?

https://www.youtube.com/embed/Z85lxckrtzg?start=57

这首歌的第一句歌词是“这是一个接近百万的夜晚,邪恶的东西潜伏在黑暗中。”人工智能对第一线的预测是什么?

“It's close to me inside than people are looking in the door to the moon.” 

为什么人工智能很难识别迈克尔·杰克逊唱《午夜》?会不会是因为他唱的是“百万之夜”,大部分重音在第一个音节?前面我们评论了歌唱的元音和辅音的比率是说话的 40 倍;这看起来像是这种扭曲的一个例子。

AI 对摇滚乐的识别能力有多强?

我们将像获得流行歌曲一样获得摇滚歌曲的转录本,从 YouTube 下载它们,并用 AssemblyAI 的语音到文本 API 转录它们。我们来分析以下五首标志性的摇滚歌曲、琼·杰特、【绿日】、【草原之狼】的《天生狂野》、【AC/DC】的《你摇了我一整夜》的《不要停止相信》

这些摇滚歌曲中 WER 得分最低的是《旅程》的《不要停止相信》,WER 得分为 0.735。令人惊讶的是,这里最低的 WER 分数大约等于上面挑选的流行歌曲的平均分数。

从第二低到最高,歌曲和 WER 得分是 AC/DC 的“你整夜都在摇我”,0.802,绿日的“美国白痴”,0.807,草原之狼的“天生狂野”,0.820,最后最高的 WER 得分是琼·杰特的“我爱摇滚”和黑心乐队,0.834。这导致这 5 首摇滚歌曲的平均单词错误率为 0.80。根据这一分析,对于当前的自动语音识别软件来说,摇滚似乎比流行音乐更难识别。

是时候深入潜水了。让我们来看看这些歌曲的一些转录,看看幕后发生了什么。我只简单地谈一下“不要停止相信”的文字记录。

Just a small town … now where I don't stop Believin hold on to the feel …

有趣的是,艾至少能够转录一次标志性的“不要停止相信”歌词。作为人类,我们很容易理解“不要停止相信”,但让我们看看一个更有趣的情况,一个对大多数说英语的人来说更难理解的情况。

接下来,我们来看看《你摇了我一整夜》这首歌。首先,我们应该承认,大多数人会努力转录这首歌的歌词。话虽如此,令人惊讶的是艾能够比其他人更好地转录这首歌,因为其他歌曲更容易理解..人工智能预测这首歌的第一行是:

“So I she was out when she can create one kick off”

Not the intro to You Shook Me All Night Long

明明这些都不是话,但是你真的能错机器吗?听一听,你告诉我真正的话是什么。

https://www.youtube.com/embed/Lo2qQmj0_h4?start=30

最后,让我们来看看另一首歌,“我爱摇滚”很难翻译,但我们必须把它交给人工智能,因为它正确地理解了“我爱摇滚”的这一部分:

… it would be long it was with me? Yeah, me and I could tell it wouldn't be long it was with me? Yeah. Me singing I love … 

这首歌最经典的台词之一。但不知何故,这份抄本竟然一次都没有提到“我爱摇滚”这几个字。也许这是因为它比背景中的掌声和音乐声音小不了多少,也许是因为它是背景人声,重复的性质使人工智能很难理解。

艾对 RnB 音乐有多了解?

这些 RnB 歌曲中有几首是老调重弹,如希亚拉的《一两步》、米盖尔的《T2》、《点缀》和蕾哈娜的《雨伞》。我们挑选的另外两首(更现代的)歌曲是德雷克的“Hotline Bling”和多吉猫的“Kiss Me More”

好吧,让我们看看语音识别技术在 RnB 音乐上的表现。

令人惊讶的是,WER 得分最低的是德雷克的《闪亮热线》,得分为 0.473。这比我们迄今为止分析过的任何东西都要好得多。这意味着人工智能成功地正确转录了这首歌的一半以上。不幸的是,这就是人工智能语音识别技术在 RnB 音乐上的限制。第二低的 WER 是蕾哈娜的《保护伞》的 0.734。是什么让德雷克如此容易被自动语音识别技术理解?会不会是因为他不怎么唱歌?

接下来的三首歌曲从最低到最高的单词错误率和他们的 WER 评分是希亚拉的《一两步》,0.758,多吉猫壮举 SZA 的《吻我更多》,0.793,以及米盖尔的《点缀》,客观上应该是更容易识别的歌曲,最高为 0.819。这五首 RnB 歌曲的平均 WER 分数是 0.715,是迄今为止最低的,但仍然不乐观。

https://www.youtube.com/embed/uxpDa-c-4Mc?start=20

德雷克的“热线闪亮”实际上用自动语音识别转录得非常好。令人印象深刻的是,它设法让前几小节几乎完全正确-

You used to call me on my you used to you used to yeah, you used to call me on my cellphone Late night when you need my love Call me on my cellphone Late night when you need my love And I know when the line … 

字面上只是把“我知道什么时候热线闪亮”变成了“我知道什么时候请排队”,这是相当不可思议的。首先,我肯定在这首歌之前,在现实生活中没有人说过“我知道热线铃响了”,如果你今天说,这可能是讽刺。如果你听了这首歌的其余部分,你可能会同意我们的分析,即德雷克对人工智能语音识别技术的部分可解释性是,他主要是在说话,而不是唱歌。

特辑-阿姆说唱上帝

好吧,我们都知道阿姆的说唱上帝。也许你甚至,令人尴尬的,在某个时候试图说唱这首歌的快节奏部分。没关系,我们都经历过。我们已经知道这首歌对人类来说很难转录,但让我们看看人工智能是如何做到的。

鉴于这首歌的语速,自动语音识别模型应该很难转录这首歌。也就是说,实际的 WER 是 0.654!。这是艾第第二首最容易转录的歌。再说一次,看起来说唱音乐更容易识别,因为它最接近常规语言。

结论

我们最初的假设是,我们的人工智能转录将获得高于 0.95 的 WER 分数,这被证明是错误的,因为没有一首歌被翻译得如此糟糕,只有 5%的单词正确。事实证明,最先进的人工智能语音识别技术,如 AssemblyAI 的语音转文本 API,可以识别一首歌中约 20%至 30%的歌词。如果我们去除背景噪音/音乐,只分离人声,我们会期望歌词被更准确地转录。另一个提高人工智能识别歌词的选择是微调歌曲的语音识别模型,唱歌和背景噪音构成了大量的训练数据。

转录你自己的音频文件。

使用 AssemblyAI 的无代码沙箱快速转录自己的音频文件。

Transcribe now

神经网络的超参数

原文:https://www.assemblyai.com/blog/hyperparameters-of-neural-networks/

正确的超参数设置对于前馈神经网络的成功至关重要。

在这个视频中,我们对神经网络的所有主要超参数进行了高层次的审视。我们看到了它们在 NNs 生命周期中的位置,它们的含义,以及如何使用 Python 和 Keras 设置它们。

看这里:

https://www.youtube.com/embed/h291CuASDno?feature=oembed

使用 Keras 的变分自动编码器简介

原文:https://www.assemblyai.com/blog/introduction-to-variational-autoencoders-using-keras/

区别性模型的研究是机器学习领域的一个常见切入点。判别模型学习定义数据集的一个要素如何依赖于其他要素的分布。然后,这种分布可以用于区分数据点之间的,例如,通过将数据集划分为类。

除了判别模型,还存在生成模型。生成模型不是学习定义数据集的特征如何相互依赖的分布,而是学习自己生成特征的分布。然后,该分布可用于生成与训练数据相似的新数据变分自动编码器,一类深度学习架构,是生成模型的一个例子。

变型自动编码器的发明是为了实现数据生成的目标,自 2013 年推出以来,由于其令人印象深刻的结果和基本的简单性,受到了极大的关注。下面,你会看到两个人脸的图像。这些图像不是真实的人——它们是使用 VQ-VAE 2 生成的,这是一个 DeepMind 变分自动编码器(VAE)模型。

Image source

在本教程中,我们将探索变型自动编码器如何简单而有力地扩展它们的前辈,普通的自动编码器,以解决数据生成的挑战,然后用 Keras 构建并训练 变型自动编码器,以理解并可视化 VAE 如何学习。我们开始吧!

如果你想直接跳到代码,你可以这样做这里

介绍

生成模拟训练集分布的令人信服的数据是一项困难的任务,其中包含的几个特性使其成为一个独特的挑战性问题。这项任务是无人监督的,因此我们必须将数据视为分布的代表。也就是说,我们需要充分地确定数据的底层结构,以便我们能够利用它来生成令人信服的伪造品,而不是对数据点执行操作,作为点本身来完成一些有价值的目标本身,例如用 K-Means 进行聚类。给定一百万张人脸图片,我们如何训练一个可以自动输出人脸真实图像的模型?

回想一下自动编码器 (AEs)是一种限制身份图学习的方法,目的是找到数据集的低维表示,这对降维和数据压缩都很有用。虽然自动编码器是实现这些目的的强大工具,但它们的学习目标并不是为了让它们能够生成与训练集非常相似的数据。

变型自动编码器通过对如何学习身份图设置约束,扩展了自动编码器的核心概念。这些约束导致了表征低维空间的 VAEs,称为潜在空间,足以使它们对数据生成有用。vae 将潜在空间表征为在训练数据中看到的突出特征的景观,而不是像 AEs 那样作为简单的数据嵌入空间。

在接下来的章节中,我们将首先探索普通的自动编码器是如何工作的,然后检查它们与变化的自动编码器有何不同。我们将获得为什么这些差异导致 VAEs 非常适合数据生成的直觉,并最终通过训练一个变分自动编码器使用 MNIST 时装数据集生成服装图像将我们的知识付诸实践!让我们首先提醒自己什么是普通自动编码器。

什么是自动编码器?

在许多与数据相关的领域中,学习您正在处理的数据的压缩表示通常是有益的。您可以使用这些低维表示来提高其他机器学习任务的计算效率,或者提高数据存储的空间效率。虽然知道数据集的压缩表示显然是有益的,但是我们如何发现实现这种压缩的映射呢?

自动编码器是一类神经网络架构,它学习将输入映射到压缩潜在空间表示的编码函数,以及从潜在空间映射回原始空间的解码函数。理想情况下,这些功能是彼此完全相反的——将数据通过编码器,然后将结果通过解码器,在所谓的无损压缩中完美地重建原始数据。

Network Architecture for an Ordinary Autoencoder

关于自动编码器的一个非常方便的事实是,假设它们是神经网络,它们可以利用专门的网络架构。虽然有一些降维方法在受欢迎程度方面已经取代了自动编码器,如 PCA 和随机投影,但自动编码器对于图像压缩等任务仍然有用,在这些任务中,ConvNets 可以捕获数据中的局部关系,而 PCA 不能。

例如,我们可以使用卷积层将 MNIST 手写数字映射为压缩形式。

Network Architecture for a Convolutional Ordinary Autoencoder

自动编码器实际上是如何执行这种压缩的?网络中的卷积层提取每个数字的显著特征,比如一个 8 是闭的有两个环,一个 9 是开的有一个环。然后,一个完全连接的网络将这些特征映射到一个较低维度的潜在空间,根据图像中哪些特征存在以及在什么程度上存在,将这些特征放置在这个空间中。如果我们已经将图像映射到一个代表性的特征空间,我们能不能不使用这个空间来生成图像

自动编码器可以用于数据生成吗?

人们可能会假设卷积自动编码器足够充分地表征了潜在空间以生成数据。毕竟,如果我们将数字映射到潜在空间中的“意义”,那么我们所要做的就是在这个潜在空间中选取一个点,并解码它的“意义”,以获得一个生成的图像。

可惜这个方法行不通。正如我们将看到的,自动编码器针对忠实重建进行了优化。这意味着自动编码器学习使用潜在空间作为嵌入空间来创建最佳压缩,而不是学习将潜在空间全局表征为表现良好的特征景观。我们可以在下图中看到这个模式的简化版本,其中我们的原始空间是三维的,而我们的潜在空间是二维的。请注意,MNIST 数字的原始空间实际上有 784 个维度,但下面使用了三个维度进行可视化。

*

Data Generation Process with Ordinary Autoencoders*

我们可以看到,重建的图像没有精确地映射到输入图像在原始空间中的位置。这两点之间的非零距离是重建图像看起来不完全像原始图像的原因。这个距离称为重建损耗,用两个红点之间的紫色线表示。

我们还可以看到,随机采样潜在空间中的一个点(即潜在向量)并将其通过解码器输出的图像看起来不像数字,这与我们的预期相反。

但是为什么这个不行?

为了理解为什么自动编码器不能生成充分模仿训练集的数据,我们将考虑一个例子来发展我们的直觉。考虑以下一组二维数据点:

*

Two-Dimensional Data Points*

自动编码器如何学会将这些数据压缩到一维?回想一下,神经网络是连续函数的组合。因此,原则上,神经网络可以表示任何连续函数。假设我们网络的编码器学习如下插值点:

*

Data Points with Interpolated Curve*

然后通过使用沿着该曲线的点的路径距离作为它们在一维空间中的位置,通过将数据压缩到一维。下面你可以看到这是如何工作的。两点的路径距离在左边的二维空间中显示为红色和绿色曲线。这些曲线的长度表示从相同的点到右边一维空间(沿 x 轴)原点的距离。

*

Encoding to One-Dimension Based on Interpolated Curve Path Distance*

解码器将学习该映射的逆映射,即将从潜在一维空间中的原点向后的距离映射到二维空间中沿着曲线的距离。

从这里开始,生成数据似乎是一项简单的任务——我们只需选择一个随机的潜在向量,让解码器完成它的工作:

*

Generation in Two-Dimensions Based on Interpolated Curve Path Distance*

就这么简单!对吗?

不对。虽然看起来像是我们可能击中了要害,我们只学会了在原始空间中沿着我们的插值曲线生成点。我们的网络刚刚学习了原始空间中的一条这样的曲线,它可以代表数据的真实潜在分布。二维空间中有无限多条曲线对我们的数据点进行插值。让我们假设真实的基本分布如下:

*

True Underlying Generative Curve*

然后我们的编码解码模式没有理解数据的底层结构,使得我们的数据生成过程存在固有的缺陷。如果我们取之前生成的数据点,我们可以看到它不在真正的生成曲线上(此处显示为橙色),因此表示生成的数据质量差,没有模拟真正的数据集。如果我们继续从我们的真实曲线(橙色的)无限采样,我们将永远不会得到生成的点。在我们可以采样的任何真实点和我们生成的点之间的距离有一个非零的最大下界。

*

Previous Data Generation Method Fails to Create Convincing Data*

在上面的例子中,我们的 Autoencoder 为我们看到的数据学习了一种高效无损的压缩技术,但是这并没有使它对数据生成有用。我们不能保证解码器在整个潜在空间的行为-自动编码器只寻求最小化重建损失。如果我们知道数据是从螺旋分布中采样的,我们可以在我们的编码器-解码器网络上设置约束来学习更适合数据生成的插值曲线。

一般来说,我们不知道构成数据分布的子结构的确切形式,但是我们是否仍然可以使用这种概念来约束学习过程,以便定制对数据生成有用的自动编码器?

什么是变分自动编码器?

虽然上面的例子只是一个开发我们直觉的玩具例子,但从中可以得到两个重要的启示。第一个是,在所有可能的对数据压缩有用的编码-解码器序列中,只有其中的一个 T2 子集产生对数据生成有用的解码器。第二个是,一般情况下,我们并不知道 先验 数据的底层结构,以这样一种可利用的方式。我们如何约束我们的网络来克服这些问题?

变型自动编码器通过一个简单但至关重要的区分因素来完成这一挑战——它们不是将输入数据映射到潜在空间中的,而是映射到描述的分布参数的,其中数据根据其特征“应该”被映射(概率上)到潜在空间中。

*

Network Architecture for a Convolutional Variational Autoencoder*

因此,VAE 并不简单地试图将数据嵌入潜在空间,而是将潜在空间表征为 T2 特征景观,这一过程使得潜在空间在数据生成方面表现良好。我们不仅可以使用这个场景来生成新的数据,我们甚至可以修改输入数据的显著特征。例如,我们不仅可以控制图像中的人脸是否在微笑,还可以控制微笑的类型强度:

*

Image adapted from source*

用 MNIST 理解变分自动编码器

为了理解 VAEs 是如何工作的,让我们看一个具体的例子。我们将介绍一个喀拉斯·VAE 是如何学会将潜在空间描述为 MNIST 手写数字数据集的特征景观的。MNIST 数字集包含数万个 28 x 28 像素的数字灰度图像。这里的是一些让你熟悉1 的示例图片。让我们从一些基本假设开始。

问题设置

  1. 首先,让我们假设编码器网络中的卷积特征提取器已经被训练过。因此,编码器正在学习如何将提取的特征映射到分布参数。
  2. 最初,潜在向量被解码成无意义的白噪声图像。所以,假设我们的解码器网络是部分训练的。这意味着潜在空间被部分表征,因此我们解码的图像清晰可辨,并有一定的意义。
  3. 我们将潜在空间的维度设置为等于两个,这样我们就可以将它可视化。也就是说,生成的图像在我们的 2D 平面中的位置在空间上对应于被解码以产生图像的潜在空间中的点。
  4. 最后,让我们假设我们的编码器网络映射到具有对角对数协方差矩阵的多元高斯分布参数

有了我们的基线假设,我们可以继续理解变分自动编码器如何在引擎盖下学习!

六码训练

给定我们的上述假设,让我们假设我们正在将一个六个的图像输入到我们的 Keras VAE 用于训练2 。我们的编码器网络从数字中提取显著特征,然后将它们映射到潜在空间中的多元高斯分布参数。在我们的例子中,这些参数是一个长度的两个均值向量和一个长度的两个对数协方差向量

下面你可以看到我们的二维潜在空间被可视化为一个平面。红点表示我们的输入图像映射到的分布的平均值,红色曲线表示该分布的 1-sigma 曲线

现在,我们对此分布进行采样,并将结果数据点传入解码器。相对于这个随机产生的点测量误差。正是这种差异区分了普通的和变化的自动编码器,也使得 VAEs 对数据生成有用。随机采样的点在下图中用绿点表示。**

*

Distribution of Encoded Image (Red), Randomly Sampled Point (Green)*

我们假设我们的解码器接受了部分训练。因此,由于“看起来像”6 的输入图像被我们的编码网络映射到该区域,我们的解码网络将学习将该区域与具有在 6 中看到的显著特征的图像(以及类似的数字,这将在后面相关)相关联。这意味着我们的解码器会将上面随机采样的绿点转换成具有六个显著特征的图像。下面你可以看到我们已经用相应的解码图像替换了绿点。

*

Decoded Image of Randomly Sampled Point*

由于这个图像看起来类似于我们的输入图像 6,所以损失将会很低,告诉网络它在表征潜在空间的这个区域方面做得很好,它代表了在类似 6 的图像中看到的显著特征。

一对一训练

进一步在训练期间,让我们说一个一个的图像被输入到我们的网络 3 。出于示例的目的,让我们假设编码器网络将图像的提取特征映射到如下所示的分布参数,其中红点再次表示分布的均值,红色曲线表示其 1-sigma 曲线。

*

Distribution of Encoded Image*

请注意,这些分布参数将大部分分布落在我们之前看到代表(并因此解码为)six-like 图像的区域中。再次,将从该区域随机采样一个点,并传递给解码器以计算损失。记住,解码器并不先验地知道该点是从与输入图像相关的分布中采样的。解码器看到的只是潜在空间区域中的一个点,该点具有在图像中看到的类似“6”的特征,因此当从该分布中随机采样一个点时,该点将被解码为类似如下:

回想一下,我们最初的输入是一个一个。由于解码图像看起来像 1,损失将非常高,并且 VAE 将调整编码器网络以将 1 映射到该区域附近的分布参数。

零起点训练

让我们继续最后一个训练示例,假设我们有一个零的输入图像,它再次被编码为分布参数,最终在“类六区域”附近随机采样。假设我们对下面的点进行采样,该点已被解码为相应的图像:

6”和“0”在显著特征上比“6”和“1”要“接近”得多——它们都有一个循环,可以相对容易地从一个连续转换到另一个 4 。因此,我们的解码图像可以合理地解释为 6 或 0。事实上,如果您仔细观察,您会发现 6 和 0 共享的曲线在解码图像中很强(红色轮廓),而 0 独有的曲线(蓝色轮廓)和 6 独有的曲线(绿色轮廓)较弱。

鉴于 6 和 0 有许多共同的显著特征,损失仍然相对较小,即使该图像可以合理地解释为 6 或 0。

因此,潜在空间的这个一般区域将开始代表六和零,因为它们具有相似的特征。在代表“纯”6 和“纯”0 的潜在空间点之间(即明显是 6 的形状,不能被解释为 0,反之亦然),变分自动编码器将学习将中间点映射到图像,这些图像可以合理地被解释为“6”或“0”。中间点的解码产生从一个形状到另一个形状的连续变换的快照。

我们最终将得到一个局部补丁,如下图所示,其中这些变换是可以直接观察到的:

表征其余的潜在空间

在整个潜在空间的训练期间,将对每个图像重复上述过程。看起来不像 6 或 0 的图像会被推开,但会以同样的方式与相似的图像聚集在一起。下面我们看到一个代表 9 和 7 的补丁,一个代表 8 和 1 的补丁。

当我们在整个数据集上继续这个过程时,我们将观察全球组织。我们在上面看到了在局部面片上的良好行为是如何出现的,但是这些局部面片必须以在每一点都“工作”的方式修补在一起,这意味着特征区域之间的连续过渡。因此,我们得到了潜在空间中任意两点之间的路径,沿着该路径在它们的特征之间具有连续的过渡。

下面你可以看到一个连接 8 和 6 的路径的例子。路径上的许多点创建了令人信服的数据,包括看起来像五、三和二的图像:

我们想再一次强调,我们的潜在空间被描述为一个特色景观,而不是作为一个数字景观。解码器甚至不知道“数字”是什么,因为 MNIST 数据集中的标签信息从未出现在训练过程中,但解码器仍然可以创建令人信服的数字图像。因此,我们可以得到如下图,其中每个显著特征与一个特定的轨迹相关联:**

这些位点中的一些已经在图像中突出显示。让我们描述一下与每个位点相关的显著特征:

  • 红色=纯连通回路
  • 蓝色=用线连接的回路
  • 绿色=多个开环
  • 紫色=角形
  • 橙色=纯垂直线
  • 黄色=倾斜线,部分打开

记住,上面的网格是我们二维潜空间的一个直接解码在我们的训练数据集中,没有一个网格中的数字图像是直接可见的——它们只是 VAE 学习的数据集中显著特征的代表。**

用 Keras 构建变分自动编码器

现在我们从概念上理解了变分自动编码器的工作原理,让我们动手用 Keras 构建一个变分自动编码器吧!我们将使用 时尚 MNIST 数据集 ,而不是使用数字,它有不同服装项目的 28 乘 28 灰度图像。

设置

首先,一些进口让我们开始。

| 来自【ipython】【进口】 【显示】 as【TF】 【进口】【tensorlow _ probability】as |

让我们使用 TensorFlow 内置的fashion_mnist数据集导入数据。我们展示了一个示例图像,在本例中是一只靴子,以了解图像的样子。

| (train_images,),(test_images,)= TF . keras . datasets . fashion _ mnist . load _ data() PLT . imshow(train _ images[0,::],cmap =' gray _ r ') PLT |

我们用伯努利分布对每个像素建模。回想一下,伯努利分布等价于 n=1 的二项式分布,并且它模拟了具有二元结果的实验的单一实现。在这种情况下,随机变量𝝌的值对应于像素是“开”还是“关”。也就是说,𝝌=0 表示全白像素(像素强度= 255),1 表示全黑像素(像素强度=0)。请注意,上面的颜色映射是颠倒的,所以如果像素值看起来颠倒了,请不要混淆。

我们将我们的像素值缩放到范围【0,1】内,然后 0.5阈值将它们二值化,之后我们显示上面二值化后的示例图像。

最后,我们初始化一些相关变量,并从数据中创建数据集对象。dataset 对象将数据打乱,并将其分段成批

| def 预处理 _ images(images): images = images . shape(【images . shape】0, 28 , 28 ,1)/) returnNP . where(图片>1.00.0)。astype(【float 32】) train _ images =预处理 _ 图像(train _ images) test _ images =预处理 _ 图像(test _ images) plt.axis( 【关】 )PLT . tight _ layout() train _ size = train _ images . shape[0 batch _ size = test _ size = test _ images . shape[0】shuffle(train_size)。batch(batch _ size)) test _ dataset =(TF . data . dataset . from _ tensor _ slices(test _ images) 。洗牌(test_size)。批处理(batch_size)) |

定义可变自动编码器

编码器网络

现在我们可以继续定义 Keras 变分自动编码器模型本身。首先,我们定义编码网络,它是一个简单的具有 ReLU 激活的卷积层序列。注意,最终卷积没有激活。具有卷积层的 VAEs 有时被称为“CVAEs”——卷积变分自动编码器。

我们网络的最后一层是一个密集层,它的编码是我们潜在空间的两倍。记住,我们是映射到参数来定义我们潜在空间的分布,而不是潜在空间本身。对于这些分布,我们使用具有对角对数协方差矩阵的高斯分布。因此,我们的编码器的输出必须产生用于这种分布的参数,即具有相同潜在空间维度的均值向量,以及具有相同潜在空间维度的对数 方差向量(其表示对数协方差矩阵的对角线)。

| 班【cvae】(TF . keras . model):
" 【def】【init _ _ _ _ _ _ _ _ _ init _(【self . latent _ dim = latent _ dim】
【self . encoder = TF . keras . sequential( ),激活= 【继电器】 , 【TF . keras . layers . conv 2d |

解码器网络

接下来是定义我们的解码器网络。与用于分类网络的完全连接到 softmax 序列不同,我们的解码器网络有效地反映了编码器网络。自动编码器具有令人愉快的对称性——编码器学习映射到潜在空间的函数 f ;解码器学习从潜在空间映射回原始空间的反函数 f-1 。Conv2DTranspose 层提供可学习的上采样来反转卷积层。

| self . decoder = TF . keras . sequential( TF . keras . layer(input _ shape =(latent _ dim), TF . keras . layers . dense(units =7*7 TF . keras . layers . conv 2d transpose( filters =64,kernel _ size =3,stamps =2,padding= 【相同】 大步数= 2 ,填充数= 【相同】 激活=【relu】) |

正向传递函数

变分自动编码器的训练不像自动编码器那样简单,在变分自动编码器中,我们通过网络传递输入,获得重建损失,并通过网络反向传播损失。变化的自动编码器需要更复杂的训练过程。这从向前传球开始,我们现在来定义一下。

编码功能

要对图像进行编码,我们只需将图像通过编码器,但要注意我们会将输出分成两部分。回想一下上面的内容,我们正在将输入编码到一个向量中,该向量的维度是潜在空间维度的两倍,因为我们正在映射到定义了如何从潜在空间中采样进行解码的 T2 参数。

在操作上,这些参数向量的定义发生在这里——我们将我们的输出分成两个向量,每个向量具有相同的潜在空间维度。第一个向量表示我们的多元高斯在潜在空间中的平均值,第二个向量表示同一高斯的对角对数协方差矩阵的方差。

| defencode【self,x】: mean,logvar = tf.split(self.encoder(x),num _ or _ size _ splits =2,axis =1) |

重新参数化函数

回想一下,我们不是直接对编码输入进行解码,而是使用编码来定义我们如何从潜在空间中采样。相反,我们解码潜在空间中的一个点,该点是根据由我们的编码网络输出的参数定义的分布随机采样的。人们可能想简单地使用tf.random.normal()来采样这样一个点;但是请记住,我们正在训练我们的模型,这意味着我们需要执行反向投影。这是有问题的,因为 backprop 不能流过随机进程,所以我们必须实现所谓的重新参数化技巧:

我们定义了另一个随机变量,它在我们的均值和对数方差向量中是**确定的。它将这两个向量作为参数,但它通过对数方差向量与一个向量的 Hadamard 乘积来保持随机性,该向量的分量是从标准正态分布中独立采样的。这个技巧允许我们在采样中保持随机性,同时仍然允许反向脉冲流过我们的网络,这样我们就可以训练我们的网络。Backprop 不能流过产生 Hadamard 乘积中使用的随机向量的过程,但这无关紧要,因为我们不需要训练这个过程。**

| def 重新参数化 (self,mean,logvar): EPS = TF . random . shape returnEPS * TF . exp(logvar *. 5 |

解码功能

给定一个潜在空间点,解码就像通过解码器网络传递该点一样简单。我们允许选择直接输出逻辑或其 sigmoid。默认情况下,出于数值稳定性的考虑,我们而不是使用 sigmoid,这将在后面强调。

| defdecode(self,z,apply _ sigmoid = False): logits = self . decode(z) ifapply _ sigmoid: probs = TF . sigmoid |

抽样函数

给定分布的重新参数化采样,采样函数只对输入进行解码。如果没有提供这样的输入,它将在从标准正态分布中采样的潜在空间中随机输入 100 个点。

@tf.function修饰该函数是为了将函数转换成图形以便更快地执行。

| @ TF . function defsample(self,z = None): ifzNone:😃 |

损失计算

我们已经定义了我们的变分自动编码器及其前向传递。为了让网络学习,我们现在必须定义它的损失函数。当训练变分自动编码器时,规范目标是最大化证据下限,这是观察给定数据的一组潜在变量的概率的下限。也就是说,它是逼近后验分布的优化标准。

*

Equation from Source*

实际上,只计算了 ELBO 的单样本蒙特卡罗估计:

*

Equation from Source*

我们首先定义一个辅助函数,即标准对数正态分布的概率分布函数,它将用于最终的损失计算。

| deflog _ normal _ pdf(sample,mean,logvar,raxis = 1): log2pi = TF . math . log(2 .* NP . pi) returnTF . reduce _ sum( -. 5((sample-mean) 2。 TF . exp(-log var)+log var+log2pi)、 axis = raxis) |

现在我们定义我们的损失函数,它包含以下步骤:

  1. 通过编码计算图像的分布参数
  2. 通过使用重新参数化技巧,使用这些参数从潜在空间以反向传播兼容的方式进行采样
  3. 计算输入图像和解码图像之间的 二值交叉熵
  4. 计算条件分布先验潜在分布(建模为单位高斯)和近似后验分布的值。
  5. 算出了T2【埃尔博】的
  6. 否定 ELBO 并返回

你可能想知道我们为什么退回 ELBO 的负数。我们这样做是因为我们试图最大化ELBO,但是梯度下降通过最小化损失函数来工作。因此,我们不试图将梯度实现为分,而是简单地翻转符号并正常进行,注意稍后要校正符号翻转。

最后,我们注意到tf.nn.sigmoid_cross_entropy_with_logits()用于数值稳定性,这就是为什么我们计算逻辑并且在解码时不通过 sigmoid 传递它们

| defcompute _ loss: mean,log var = model . encode(x) z = model . reparameterize(mean,log var) x _ logit = model . decode(z) , 0。) logqz _ x = log _ normal _ pdf(z,mean,log var) return-TF . reduce _ mean(logpx _ z+logpz-logqz _ x) |

训练步骤

最后,我们以通常的方式定义我们的训练步骤。我们在GradientTape上计算损失,反推计算梯度,然后在给定梯度的情况下使用优化器执行一个步骤。同样,我们将这个方法装饰成一个用于加速的tf.function

| @ TF . function deftrain _ step(模型,x,优化器): 【执行一个训练步,返回损失。 该函数计算损失和梯度,并使用后者来 更新模型的参数。 同 tf。gradient tape()astape: loss = compute _ loss(model,x) gradients = tape . gradient(loss,model . trainiable _ variables) optimizer . apply _ gradients(zip(gradients,model . trainible _ variables)) |

培养

设置

我们已经完成了对 Keras variable auto encoder 及其方法的定义,所以我们可以继续训练了。我们选择我们的潜在空间的维度为 2,这样我们就可以像上面那样可视化潜在空间。我们将 epochs 的数量设置为 10,并实例化我们的模型。

| latent_dim = 2历朝历代=10 型号= CVAE(潜伏 _ 暗淡) |

绘图功能

下面的绘图功能允许我们跟踪学习过程中潜在空间的特征。函数获取潜在空间中的点网格,并将它们通过解码器,以生成生成图像的景观。通过这种方式,我们可以观察潜在空间中的不同区域如何演变以表示特征,以及这些特征区域如何分布在整个空间中,它们之间有连续的过渡。

| defplot _ latent _ images(model,n,epoch,im_size=28,save=True,first_epoch=False,f _ EP _ count = 0): #创建图像矩阵 image _ width = im _ size * n image _ height = image _ width image = NP . zeros((image _ height,image_width))#创建均匀分布的数值列表norm = TFP . distributions . normal(0,1) grid _ x = norm . quantile(NP . linspace(0.05, 0.95 ,n)) grid _ y#对于潜在空间中网格上的每个点,解码并#将图像复制到图像数组中 为 i, 中的 枚举(grid _ x): 为 j, 中的 枚举(grid _ y): 【T31) im _ size)) image[I * im _ size:(I+1)* im _ size, j * im _ size:(j+1)* im _ size]= digit . numpy() #剧情图像数组 PLT . fig size =(10,10) PLT . imshow(image,cmap =【Greys _ r’) PLT

潜在保存,如果在第一个纪元 如果 保存 和first _ epoch: PLT . save fig(【TF _ grid _ at _ epoch _ {:04d }。{:04d}。巴新' 。format(epoch,f _ EP _ count)) elifsave: PLT . save fig(' TF _ grid _ at _ epoch _ {:04d }。png '。格式(epoch)) 【PLT . show() |

训练循环

我们终于准备好开始训练了!在我们开始学习和实例化 Adam 优化器之前,我们使用上面的函数保存我们潜在空间的快照。在这之后,我们进入我们的训练循环,简单地说就是遍历每个训练批次并执行train_step()。处理完所有批次后,我们使用compute_loss()计算测试集上的损失,然后返回平均损失的负值,得出 ELBO。我们在这里返回损失的负平均值,因为我们在我们的compute_loss()函数中翻转了符号以使用梯度下降学习。

如果我们在第一个时期内,我们每 75 批保存一个潜在空间的快照。这是因为训练发生得如此之快,以至于我们在开始时需要这种粒度级别来观察训练。如果我们不在第一个时期中,我们在每个时期结束时保存潜在空间的快照。

| TF . config . run _ functions _ 急切( 真实) plot _ latent _ images(model, 20 ,epoch =0)
optimizer = TF . keras . optimizer . Adam(1e-4) 为 历元 在 范围内( 1 ,历元+1): 优化器) ifepoch = =1和idx %75= =0: f _ EP _ count = idx) end _ time = time . time() loss = TF . keras . metrics . mean() forTest _ xinTest _ dataset: format(epoch,elbo,end _ time-start _ time)) ifepoch!=1: plot _ latent _ images(model, 20 ,epoch=epoch) |

结果

下面的函数允许我们将训练期间的所有快照串成一个 GIF,以便我们可以观察我们的 Keras variable auto encoder 如何学习将不同的特征与潜在空间中的不同区域相关联,并根据相似性组织这些区域,以允许它们之间的连续过渡。

| anim _ file =【grid . gif】 同imageio . get _ writer(anim _ file,mode =【I’)同writer: 文件名= glob.glob( )png ') 文件名=排序后的(文件名) 为 文件名为 中的文件名: 打印(文件名) image = imageio . imread |

以下是使用此函数生成的训练 GIF 示例:

这是培训结束时的最后一张快照:

正如你所看到的,即使我们的小网络仅用低维潜在空间训练了十个纪元,也能产生强大的 VAE。特征景观被很好地学习,并产生合理的服装实例,尤其是考虑到训练集中不同类别的抽象和多样性。我们看到靴子、鞋子、裤子、t 恤衫和长袖衬衫出现在图像中。很容易看出,即使使用简单的网络架构,使用大型多通道图像和更强大的硬件也能产生令人信服的结果,就像这里展示的那样。

最后的话

VAEs 是一种用于生成数据的非常有价值的技术,目前它们与 GAN s 一起主导着数据生成领域。我们看到了自动编码器如何以及为什么无法生成令人信服的数据,以及变化的自动编码器如何简单而有力地扩展了这些体系结构,使其专为图像生成任务而定制。我们用 Python 构建了一个 Keras variable auto encoder,并使用这个 MNIST VAE 来生成看似真实的服装图像。

*寻找更多这样的帖子?

订阅我们的时事通讯!

Subscribe Now*

脚注

  1. 这张图片来源于 this GitHub 库
  2. 图片来源于
  3. 图片来源于
  4. 这种转换实际上是不连续的,因为我们需要“打破”零,然后将其重新连接到自身的不同部分,但转换的其余部分是连续的
  5. 这个例子改编自 TensorFlow 网站

如何构建 JavaScript 音频脚本应用程序

原文:https://www.assemblyai.com/blog/javascript-audio-transcript/

随着快速准确的自动语音识别技术的逐步发展,对自动转录的需求比以往任何时候都高。无论是出于可访问性原因,如自动为 YouTube 视频生成字幕,还是出于便利性原因,如自动转录讲座/会议,自动语音识别在所有行业的所有级别都有使用案例。

在本教程中,我们将学习如何创建一个由 AssemblyAI 的 语音到文本 API 支持的 JavaScript 音频转录应用程序。本教程中的代码在 GithubReplit 上都有。

属国

我们在建造什么?

我们将使用 Node.jsAxios 来构建一个简单的 JavaScript 音频脚本应用程序。当提供了指定音频文件位置的 URL 时,应用程序将将音频转录为文本并且将结果作为字符串存储在变量中。

最终结果将如下所示:

JavaScript Audio Transcript

Final Result

步骤 1 -创建新应用程序

我们只需要编写一个 JavaScript 文件来创建我们的应用程序。在我们开始编码之前,我们需要安装两个包- Node.jsAxios 。如果 Node.js 没有安装,从 Node.js 网站下载并安装,安装过程中一切保持默认。我们将立即安装 Axios。

为了保持有序,创建一个名为javascript-audio-transcript-aai的新文件夹,并导航至该文件夹:

mkdir javascript-audio-transcript-aai
cd javascript-audio-transcript-aai 

现在创建一个名为audio.js的 JavaScript 文件并安装 Axios:

touch audio.js
npm install axios 

这个过程会在项目文件夹中自动创建一个package.json文件,现在看起来应该是这样的:

Project Folder

既然已经正确设置了应用程序文件夹,我们就可以继续为 AssemblyAI 的 API 设置凭证身份验证了。

步骤 2 -设置身份验证

AssemblyAI 的免费语音转文本 API 将实际生成我们音频文件的抄本。要使用 API,我们需要一个 API 密钥。API 密钥与 API 请求一起发送,以确保发出请求的帐户被授权使用 API。

创建一个 AssemblyAI 账户并进入 AssemblyAI 仪表盘。在仪表板上,在你的 API 键下,复制 API 键的值。请注意,该密钥的值应该保密,并且与您的账户唯一关联。

JavaScript Audio Transcript

AssemblyAI Dashboard

接下来,在audio.js文件中,编写以下代码来用 AssemblyAI 处理认证,用刚刚从仪表板复制的密钥值替换“ASSEMBLY-API-KEY”的值。

const axios = require("axios")
const audioURL = "https://bit.ly/3yxKEIY"
const APIKey = "ASSEMBLYAI-API-KEY"

const assembly = axios.create({
  baseURL: "https://api.assemblyai.com/v2",
  headers: {
    authorization: APIKey,
    "content-type": "application/json",
  },
}) 

audio.js

代码分解

  • const axios = require("axios")进口 Axios。
  • const audioURL = "https://bit.ly/3yxKEIY"创建audioURL变量,该变量链接到我们在本例中使用的音频文件。
  • const APIKey = “ASSEMBLYAI-API-KEY”保存 AssemblyAI API 密钥的值。确保用 AssemblyAI 仪表板中的实际 API 键值替换它。
  • const assembly = axios.create({...处理发送到 AssemblyAI 的每个请求的身份验证。也就是说,它告诉 AssemblyAI 我们被授权访问它的 API。这个变量保存转录 API 端点的baseURLAPIKey的值。它还将content-type设置为application/json。我们创建这个变量,这样我们就不需要为每个请求编写大量重复的代码。

测试认证

现在我们已经设置了 API 身份验证,我们可以测试我们是否能够使用 AssemblyAI 进行身份验证并实际获得响应。

在现有代码下的audio.js底部添加这行代码:

...
assembly
    .post("/transcript", {
        audio_url: audioURL
    })
    .then((res) => console.log(res.data))
    .catch((err) => console.error(err)); 

audio.js

这段代码使用 Axios 向 AssemblyAI 语音转文本 API 发送一个使用我们的audioURLPOST 请求,并返回一个我们登录到控制台的对象。

打开终端并导航到项目文件夹,然后使用以下代码运行 JavaScript 文件:

node audio.js 

控制台中的输出是 POST 请求的响应,应该类似于以下内容:

{
  id: 'o49rt8v5ea-bfae-4b39-b276-9eb69d172ade',
  language_model: 'assemblyai_default',
  acoustic_model: 'assemblyai_default',
  language_code: 'en_us',
  status: 'queued',
  audio_url: 'https://bit.ly/3yxKEIY',
  text: null,
  words: null,
  utterances: null,
...
} 

Response

在这个对象中,我们可以看到一些我们需要的元素:

  • id持有成绩单 id。我们需要这个 id 来检查我们的成绩单的状态。
  • 告诉我们转录的当前状态。我们可以向 AssemblyAI 语音转文本 API 发送一个GET请求来完成这项工作。可能的状态键有“排队”、“处理”或“完成”。
  • audio_url保存audioURL,它指定了我们希望转录的音频文件的位置
  • 一旦转录完成,将保存转录的文本。

删除我们在本节中添加的仅用于测试的整个代码段,因为我们不再需要它了。

步骤 3 -自动获取 JavaScript 音频脚本

为了获得我们的抄本,我们需要发送请求来检查我们的抄本的状态是否已经从“处理中变为“完成”。

我们将自动执行这个检查过程,而不是通过连续执行 GET 请求直到我们收到一个“ completed ”状态来手动完成。有不同的方法来自动化这个过程,比如使用网钩或者使用间隔。在这个例子中,我们介绍了区间方法。

我们将先给audio.js添加一些代码,然后在下面分解。完整的代码现在看起来是这样的:

const axios = require("axios")
const audioURL = "https://bit.ly/3yxKEIY"
const APIKey = "ASSEMBLYAI-API-KEY"
const refreshInterval = 5000

// Setting up the AssemblyAI headers
const assembly = axios.create({
  baseURL: "https://api.assemblyai.com/v2",
  headers: {
    authorization: APIKey,
    "content-type": "application/json",
  },
})

const getTranscript = async () => {
  // Sends the audio file to AssemblyAI for transcription
  const response = await assembly.post("/transcript", {
    audio_url: audioURL,
  })

  // Interval for checking transcript completion
  const checkCompletionInterval = setInterval(async () => {
    const transcript = await assembly.get(`/transcript/${response.data.id}`)
    const transcriptStatus = transcript.data.status

    if (transcriptStatus !== "completed") {
      console.log(`Transcript Status: ${transcriptStatus}`)
    } else if (transcriptStatus === "completed") {
      console.log("\nTranscription completed!\n")
      let transcriptText = transcript.data.text
      console.log(`Your transcribed text:\n\n${transcriptText}`)
      clearInterval(checkCompletionInterval)
    }
  }, refreshInterval)
}

getTranscript() 

audio.js

代码分解

让我们分解代码,以便更好地理解这个 JavaScript 音频脚本应用程序的工作原理:

  • const refreshInterval = 5000设置执行转录状态检查的时间间隔(毫秒)。

  • const getTranscript是一个包含代码逻辑的异步 JavaScript 函数。因为我们正在使用一个向我们返回数据的 API,所以使用异步函数是一个很好的实践。

    • 向 AssemblyAI 语音转文本 API 发送包含我们的 audioURL 的 POST 请求,并存储其响应。
    • const checkCompletionInterval是另一个异步函数,它利用了内置的[setInterval](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) JavaScript 方法。
      • const transcript = await assembly.get(/transcript/\({response.data.id}`)`使用我们的脚本的`id`向 AssemblyAI 语音到文本 API 发送 GET 请求,我们在模板字符串变量`\){response.data.id}中定义了这个脚本。响应存储在transcript`变量中。
      • const transcriptStatus = transcript.data.status存储我们转录的完成status
      • 接下来,checkCompletetionInterval包含一个 if/else 语句,用于评估转录是否已经完成。transcriptStatus的值为“排队”、“处理”或“完成”。
        • if (transcriptStatus !== "completed")检查抄本是否已经完成;如果还没有,就将transcriptStatus记录到控制台。
        • else if (transcriptStatus === "completed")评估transcriptStatus是否等于“完成,表示转录已经完成。如果转录已经完成:
          • console.log("\nTranscription completed!\n")将完成状态记录到控制台。
          • transcriptText = transcript.data.text将 transcriptText 的值设置为返回的抄本。
          • console.log(Your transcribed text:\n\n${transcriptText})用将转录的文本记录到控制台。两个\n\n字符是换行符,在终端中打印时给输出一些空间。
          • 最后,clearInterval(checkCompletionInterval)清除间隔,阻止它继续。
    • refreshInterval变量也作为参数添加到checkCompletionInterval函数中。这是一个要求,告诉setInterval方法多长时间运行一次setInterval函数中的代码。要改变这个时间,只需改变代码顶部的refreshInterval变量的值。
  • 在我们代码的底部,我们调用getTranscript()函数来实际调用这个函数。

如果我们现在运行该应用程序,我们将看到类似于本文开头的第一个示例图像的输出。

结论

使用 AssemblyAI 的语音转文本 API 可以轻松创建一个 JavaScript 音频转录应用程序。如今,在应用程序中实现语音识别有很多不同的方法——看看我们的其他文章,关于如何用 Golang 实现语音到文本,或者如何实现 React 语音识别

想要更多这样的内容?

关注我们的时事通讯,确保您不会错过我们的最新文章!

Follow

JavaScript 文本到语音转换——简单的方法

原文:https://www.assemblyai.com/blog/javascript-text-to-speech-easy-way/

构建应用程序时,出于可访问性、便利性或其他原因,您可能希望实现文本到语音的功能。在本教程中,我们将学习如何使用 JavaScript 的内置 Web 语音 API 构建一个非常简单的 JavaScript 文本到语音转换应用

为了您的方便,我们已经提供了这个教程应用程序的代码,供您在 Replit 时使用,或者从 Github 中克隆。你也可以点击这里查看应用的现场版。

步骤 1 -设置应用程序

首先,我们使用一个名为index.html的简单 HTML 文件和一个名为script.js的 JavaScript 文件建立了一个非常基本的应用程序。

我们还将使用一个名为style.css的 CSS 文件来添加一些边距和居中,但是如果你想包含这个样式文件,这完全取决于你。

HTML 文件index.html定义了我们的应用程序的结构,我们将使用 JavaScript 文件为其添加功能。我们添加一个<h1>元素作为应用程序的标题,一个字段,我们将在其中输入我们想要说的文本,以及一个,我们将使用它来提交这个输入文本。我们最终将所有这些对象包装在一个<form>中。记住,输入和按钮还没有任何功能——我们将在稍后使用 JavaScript 添加这些功能。

在包含 HTML 文件元数据的<head>元素内部,我们导入了style.css。这告诉我们的应用程序根据style.css的内容来设计自己的风格。在<body>元素的底部,我们导入我们的script.js文件。这告诉我们的应用程序存储应用程序功能的 JavaScript 文件的名称。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>JavaScript Text-to-Speech App</title>
    <link href="style.css" rel="stylesheet" type="text/css" />
  </head>

  <body>
    <div class="wrapper">
      <h1>JavaScript Text-to-Speech App</h1>
      <form id="form">
        
        Submit
      </form>
    </div>
    <script src="script.js"></script>
  </body>
</html> 

index.html

现在我们已经完成了index.html文件,我们可以继续创建script.js JavaScript 文件。

由于我们将script.js文件导入到上面的index.html文件中,我们可以通过简单地向发送一个警告来测试它的功能。

为了给我们的代码添加警告,我们将下面的代码行添加到我们的script.js文件中。确保保存文件并刷新浏览器,您现在应该会看到一个弹出的小窗口,显示文本“它工作了!”。

alert("It works!") 

script.js

如果一切顺利,您应该会看到这样的内容:

JavaScript Text to Speech application

Our App

步骤 2 -检查浏览器兼容性

为了创建我们的 JavaScript 文本到语音转换应用程序,我们将利用 JavaScript 的内置 Web 语音 API。由于这个 API 并不兼容所有的浏览器,我们需要检查兼容性。我们可以用两种方法中的一种来执行这种检查。

第一种方法是在caniuse.com上检查我们的操作系统和版本。

第二种方法是在我们的代码内部执行检查,这可以通过一个简单的条件语句来实现:

'speechSynthesis' in window ? console.log("Web Speech API supported!") : console.log("Web Speech API not supported :-(") 

script.js

这是一个简写的 if/else 语句,相当于以下语句:

if('speechSynthesis' in window){
	console.log("Web Speech API supported!")
} else {
	console.log("Web Speech API not supported :-(")   
} 

如果您现在运行应用程序并检查您的浏览器控制台,您应该会看到这些消息之一。您还可以选择通过呈现一个 HTML 元素将这些信息传递给用户。

步骤 3 -测试 JavaScript 文本到语音转换

接下来,让我们写一些静态代码来测试我们是否能让浏览器和我们说话。

将以下代码添加到script.js文件中。

'speechSynthesis' in window ? console.log("Web Speech API supported!") : console.log("Web Speech API not supported :-(")

const synth = window.speechSynthesis
let ourText = "Hey there what's up!!!!"
const utterThis = new SpeechSynthesisUtterance(ourText)

synth.speak(utterThis) 

代码分解

让我们看一下代码分解,以了解发生了什么:

  • 使用const synth = window.speechSynthesis,我们将synth变量声明为SpeechSynthesis对象的实例,这是指向使用 JavaScript 的 Web 语音 API 的入口。这个对象的speak方法最终将文本转换成语音。
  • let ourText = “Hey there what’s up!!!!”定义了ourText变量,它保存了我们想要说出的文本字符串。
  • const utterThis = new SpeechSynthesisUtterance(ourText)utterThis变量定义为一个SpeechSynthesisUtterance对象,我们将ourText传递给它。
  • 将所有这些放在一起,我们调用synth.speak(utterThis),它发出ourText中的字符串。

保存代码并刷新你的应用程序运行的浏览器窗口,以便听到一个声音说“嘿,有什么事吗!!!!”。

步骤 4 -让我们的应用充满活力

我们的代码目前让我们很好地理解了我们的应用程序的文本到语音方面是如何工作的,但是应用程序在这一点上只将我们用ourText定义的静态文本转换成语音。我们希望在使用该应用程序时,能够动态地改变将哪些文本转换为语音。现在让我们利用一个<form>来做这件事。

const textInputField = document.querySelector("#text-input")
const form = document.querySelector("#form")
const utterThis = new SpeechSynthesisUtterance()
const synth = window.speechSynthesis
let ourText = ""

const checkBrowserCompatibility = () => {
  "speechSynthesis" in window
    ? console.log("Web Speech API supported!")
    : console.log("Web Speech API not supported :-(")
}

checkBrowserCompatibility()

form.onsubmit = (event) => {
  event.preventDefault()
  ourText = textInputField.value
  utterThis.text = ourText
  synth.speak(utterThis)
  textInputField.value = ""
} 

代码分解

  • 首先,我们添加了const textInputField = document.querySelector("#text-input")变量,它允许我们访问在 JavaScript 代码的index.html文件中定义的标签的值。我们通过 id 选择字段:#text-input
  • 其次,我们添加了const form = document.querySelector("#form")变量,它通过 id #form选择我们的表单,这样我们可以稍后使用onsubmit函数提交<form>
  • 我们将ourText初始化为空字符串,而不是静态句子。
  • 我们将浏览器兼容性逻辑包装在一个名为checkBrowserCompatibility的函数中,然后立即调用这个函数。

最后,我们创建一个在提交表单时执行的onsubmit处理程序。这个处理程序做几件事:

  • event.preventDefault()防止浏览器在提交表单后重新加载。
  • ourText = textInputField.value将我们的ourText字符串设置为我们在应用程序的“input”字段中输入的内容。
  • utterThis.text = ourText将要朗读的文本设置为ourText的值。
  • synth.speak(utterThis)说出我们的文本字符串。
  • 提交表单后,将输入字段的值重置为空字符串。

步骤 5 -测试我们的 JavaScript 文本到语音转换应用程序

要测试我们的 JavaScript 文本到语音转换应用程序,只需在输入字段中输入一些文本,然后点击“Submit ”,就可以听到文本被转换成语音。

附加功能

使用 Web Speech API 时,有许多属性可以修改。例如:

您可以尝试使用这些属性来定制您需要的应用程序。

结论

这个简单的例子概述了如何使用 Web Speech API 进行 JavaScript 文本到语音转换。

虽然文本到语音对于可访问性、便利性和其他目的是有用的,但是有许多相反的功能(即语音到文本)是有用的。我们已经使用 AssemblyAI 的语音转文本 API 构建了几个示例项目,那些想了解更多信息的人可以查看一下。

Give AssemblyAI's Speech-to-Text API a try

其中一些是:

Jupyter 笔记本提示和技巧

原文:https://www.assemblyai.com/blog/jupyter-notebooks/

Jupyter 笔记本是数据科学家的必备。

在这段视频中,我们对 Jupyter 笔记本有了很好的了解。我们将了解最常用的设置,如何使用 Jupyter 笔记本,如何执行基本操作,每个指示器的含义,以及一些额外的提示和技巧。

https://www.youtube.com/embed/LW2Rye_l8L0?feature=oembed

即使你已经使用 Jupyter 笔记本有一段时间了,这里可能会有一些额外的宝石给你!

You May Also Like: Best Data Science Blogs

卡尔迪假人安装器

原文:https://www.assemblyai.com/blog/kaldi-install-for-dummies/

Kaldi 是一个非常强大的 NLP 框架,允许自动语音识别、说话人二进制化、等等;然而,Kaldi 的安装过程对于第一次使用的用户来说是相当吓人的。我们已经使 Kaldi 安装过程尽可能简单,所以你可以尽快开始建模!让我们开始吧。

先决条件

时间与空间

最值得注意的要求是时间空间。Kaldi 安装占用超过 40 GB 的空间,安装可能需要几个小时,所以请做好相应的准备!如果你没有时间或空间安装 Kaldi,而是想立即开始使用现成的解决方案,请查看云语音转文本 API部分。

类 Unix 操作系统

Windows 不支持 Kaldi。如果您使用的是 Windows,我们建议您安装 VMware 并创建一个基于 Debian 的虚拟机。

对于本教程,我们使用的是 Ubuntu 20.04.03 LTS ,你可以在这里找到的 ISO(更多信息)。

自动 Kaldi 安装

我们为您提供了一个自动安装脚本,让 Kaldi 的安装变得轻而易举。如果你对自动安装感到满意,那么你可以跟随这里。如果你更喜欢手动执行安装,那么你可以跳到下一部分- 手动 Kaldi 安装

1)安装必要的软件包

首先,你需要确保你有wgetgitwget在大多数 Linux 发行版上都是本地安装的,但是您可能需要打开一个终端并安装git:

| (基础) 瑞安@ Ubuntu:~$ sudo apt 安装 git-all |

2)获取安装脚本

接下来,通过在终端中运行以下命令,使用wgetAssemblyAI 的 GitHub 中获取安装脚本:

| (base) 瑞安@ Ubuntu:~$ wget https://raw . githubusercontent . com/assembly ai/kaldi-install-tutorial/main/setup . sh |

3)运行安装脚本

在文本编辑器中打开安装脚本,检查其内容以确保您可以轻松运行它,然后使用以下终端命令启动自动安装过程:

| (基地) 瑞安@ Ubuntu:~$ sudo bash setup . sh |

安装说明

如果您有多个 CPU,您可以通过提供想要使用的处理器数量来执行并行构建。例如,要使用 4 个 CPU,请输入sudo bash setup.sh 4

运行上面的命令将安装 Kaldi 的所有依赖项,然后是 Kaldi 本身。您将被要求确认所有依赖项都已在某一点安装(安装开始几分钟后)。我们建议进行检查和确认,但是如果你正在进行一个全新的 Ubuntu 20.04.03 LTS 安装(可能在一个虚拟机上),那么你可以跳过确认的需要,转而运行

| (基地) 瑞安@ Ubuntu:~$ y |须藤 bash setup.sh |

在这种情况下,您在安装过程中根本不需要与终端交互。安装可能需要几个小时,所以安装完成后,您可以离开并回到您的计算机上。

跳到Kaldi 入门部分,学习如何开始使用新安装的 Kaldi!

手动 Kaldi 安装

1)安装必要的软件包

在手动安装 Kaldi 之前,我们需要安装一些额外的包。首先,打开一个终端,运行以下命令:

| (基地) 瑞安@ Ubuntu:~$ sudo apt 更新& & sudo apt 升级(基础)Ryan @ Ubuntu:~$ yes | sudo apt 安装 unzip git-all【Ryan @ Ubuntu】:\((pkgs = " wget t13)(基) 瑞安@ Ubuntu:~\) yes | sudo apt-get install $ pkgs |

附加说明

  • 您可以复制这些命令并粘贴到终端中,方法是在终端中右键单击并选择“粘贴”。
  • 我们还需要英特尔 MKL ,如果你还没有的话,我们稍后会通过 Kaldi 安装。

2)克隆 Kaldi repo

接下来,我们需要克隆 Kaldi 存储库,这样我们就可以安装 Kaldi 本身。在终端中,导航到要克隆存储库的目录。在这种情况下,我们将克隆到主目录

运行以下命令:

| (基地) 瑞安@ Ubuntu:~$ git 克隆 https://github.com/kaldi-asr/kaldi.git·卡尔迪-起源上游 |

3)安装工具

要开始从克隆的 repo 安装 Kaldi,我们首先需要执行tools安装。使用以下命令导航到tools目录:

| (基地) 瑞安@ Ubuntu:~$ CD。/kaldi/工具 |

然后安装 MKL,如果你还没有的话。这需要时间,MKL 是一个大图书馆。

| (基础) 瑞安@ Ubuntu:~/卡尔迪/工具$ yes | extras/install _ mkl . sh |

现在,我们检查以确保所有依赖项都已安装。考虑到我们的预备安装,您应该会得到一条消息,告诉您所有的依赖项都已安装完毕。

如果您没有安装所有的依赖项,您将得到一个输出,告诉您缺少哪些依赖项。安装你需要的任何剩余的包,然后重新运行extras/check_dependencies.sh命令。由于您刚刚安装的依赖项,现在可能会出现新的必需安装。继续交替这两个步骤(检查缺失的依赖项并安装它们),直到您收到一条消息,表明所有的依赖项都已安装(“一切正常”))

| (基地) 瑞安@ Ubuntu:~/kaldi/tools$ extras/check _ dependencies . sh |

最后运行make。如果您有多 CPU 版本,请参阅下面的安装说明。

| (基) 瑞安@ Ubuntu:~/kaldi/tools$ make CXX = g++ |

安装说明

如果您有多个 CPU,您可以通过向make提供“-j”选项来进行并行构建,以便加快安装。例如,要使用 4 个 CPU,请输入make -j 4

4)安装 src

接下来,我们需要执行src安装。先是,cd变成了src

| (基地) 瑞安@ Ubuntu:~/kaldi/tools$ CD../src |

然后运行以下命令。如果您有多 CPU 版本,请参阅下面的安装说明。对于单处理器系统,这个构建可能需要几个小时。

| (基地) 瑞安@ Ubuntu:~/kaldi/src\(。/configure - shared(基) 瑞安@ Ubuntu:~/kaldi/src\) make depend CXX = g++【Ryan @ Ubuntu】:~/kaldi/src【make cxx = g++ |

安装说明

同样,如果你有多个 CPU,为了加快安装速度,你可以给make dependmake都提供-j选项。例如,要使用 4 个 CPU,请输入make depend -j 4make -j 4

在此之后,Kaldi 将被成功安装到您的机器上!您可以转到下一节,学习如何开始使用新安装的 Kaldi 副本。

Kaldi 入门

到现在为止,您已经成功安装了 Kaldi,并且可能已经跃跃欲试了!Kaldi 是一个非常复杂的框架,所以它有一点学习曲线。查看我们的 卡尔迪语音识别初学者 教程开始吧!在这个视频中,我们学习如何使用预先训练好的模型对葛底斯堡演讲进行自动语音识别(ASR) !您可以在下面看到结果:

真实:

八十七年前,我们的先辈在这个大陆上创建了一个新国家,它孕育于自由之中,奉行人人生而平等的原则

转录:

七年前,我们的先辈们创建了 T2,在这个大陆上诞生了一个新的国家——T4、自由、T7——奉行人人生而平等的原则

此外,你可以在这里查看 Kaldi 文档以获得更多关于使用 Kaldi 的信息。

云语音转文本 API

如果 Kaldi 安装过程看起来太费力,或者如果您不想学习如何在 Kaldi 中构建和训练模型,而更喜欢现成的语音到文本解决方案,您可以查看 cloud 语音到文本 API s 的选项,这使得发送音频文件并获得其转录变得容易。

除了转录,AssemblyAI 的 API 还提供了音频智能功能,如摘要情感分析实体检测等等。获取一个令牌并在下面免费开始,或者在文档中了解更多关于 API 的信息。

寻找语音识别 API?

获取一个 API 令牌,您可以在几分钟内启动并运行。

Get a Token

脚注

我们要找的确切 ISO 是Ubuntu-20 . 04 . 3-desktop-amd64 . ISO。要么使用这个直接下载链接,要么在 发布 页面上找到它(上面也有链接)。下载后,在命令提示符下运行 ISO 文件所在的目录中的certutil -hashfile ubuntu-20.04.3-desktop-amd64.iso md5来检查它的散列。结果应该是d 14 CB 9 b 6 f 48 feda 0563 CDA 7 b 5335 E4 c 0

卡尔迪语音识别初学者-一个简单的教程

原文:https://www.assemblyai.com/blog/kaldi-speech-recognition-for-beginners-a-simple-tutorial/

在本教程中,我们将使用开源语音识别工具包 、Kaldi 结合 Python 来自动转录音频文件。在本教程结束时,您将能够通过一个简单的命令在几分钟内获得转录!

重要说明

对于本教程,我们使用的是Ubuntu 20 . 04 . 03 LTS(x86 _ 64 ISA)。如果你使用的是 Windows,推荐的程序是安装一个虚拟机,并在一个基于 Debian 的发行版上完全按照这个教程操作(最好是上面提到的那个——你可以在这里找到一个 ISO

在开始使用 Kaldi 进行语音识别之前,我们需要进行一些安装。

装置

先决条件

最显著的前提是时间和空间。Kaldi 的安装可能需要几个小时,并消耗将近 40 GB 的磁盘空间,因此要做好相应的准备。如果您需要尽快转录,请查看 云语音到文本 API部分!

自动安装

如果您想手动安装 Kaldi 及其依赖项,您可以转到下一小节的。如果您喜欢自动安装,您可以遵循这一小节。

你需要在你的机器上安装wgetgit来跟随。wget在大多数 Linux 发行版上都是本地安装的,但是您可能需要打开一个终端并安装git

| (基础) 瑞安@ Ubuntu:~$ sudo apt 安装 git-all |

接下来,导航到您想要安装 Kaldi 的目录,然后用

| (base) 瑞安@ Ubuntu:~$ wget https://raw . githubusercontent . com/assembly ai/kaldi-ASR-tutorial/master/setup . sh |

该命令下载 setup.sh 文件,这实际上只是自动执行下面的手动安装。请务必在文本编辑器中打开该文件,并检查它,以确保您理解它并能舒适地运行它。然后,您可以使用执行设置

| (基地) 瑞安@ Ubuntu:~$ sudo bash setup . sh |

安装说明

如果您有多个 CPU,您可以通过提供想要使用的处理器数量来执行并行构建。例如,要使用 4 个 CPU,请输入sudo bash setup.sh 4

运行上面的命令将安装 Kaldi 的所有依赖项,然后是 Kaldi 本身。您将被要求确认所有依赖项都已在某一点安装(安装开始几分钟后)。我们建议检查并确认,但是如果你正在进行一个全新的 Ubuntu 20.04.03 LTS 安装(可能在一个虚拟机上),那么你可以跳过确认,而是运行

| (基地) 瑞安@ Ubuntu:~$ y |须藤 bash setup.sh |

在这种情况下,您在安装过程中根本不需要与终端交互。安装可能需要几个小时,所以您可以离开,安装完成后再回来。安装完成后,使用以下命令进入项目目录

| (基地) 瑞安@ Ubuntu:~$ CD。/kaldi/egs/kaldi-ASR-tutorial/S5 |

然后继续到转录音频文件

手动安装

在手动安装 Kaldi 之前,我们需要安装一些额外的包。首先,打开一个终端,运行以下命令:

| (基地) 瑞安@ Ubuntu:~$ sudo apt 更新& & sudo apt 升级(基础)Ryan @ Ubuntu:~$ yes | sudo apt 安装 unzip git-all【Ryan @ Ubuntu】:\((pkgs = " wget t13)(基) 瑞安@ Ubuntu:~\) yes | sudo apt-get install $ pkgs |

附加说明

  • 您可以复制这些命令并粘贴到终端中,方法是在终端中右键单击并选择“粘贴”。
  • 我们还需要英特尔 MKL ,如果你还没有的话,我们稍后会通过 Kaldi 安装。

安装 Kaldi

现在我们可以开始为语音识别安装 Kaldi 了。首先,我们需要克隆 Kaldi 存储库。在终端中,导航到要克隆存储库的目录。在这种情况下,我们将克隆到主目录

运行以下命令:

| (基地) 瑞安@ Ubuntu:~$ git 克隆 https://github.com/kaldi-asr/kaldi.git·卡尔迪-起源上游 |

安装工具

要开始我们的 Kaldi 安装,我们首先需要执行tools安装。使用以下命令导航到tools目录:

| (基地) 瑞安@ Ubuntu:~$ CD。/kaldi/工具 |

然后安装 MKL,如果你还没有的话。这需要时间,MKL 是一个大图书馆。

| (基础) 瑞安@ Ubuntu:~/卡尔迪/工具$ yes | extras/install _ mkl . sh |

现在,我们检查以确保所有依赖项都已安装。考虑到我们的预备安装,您应该会得到一条消息,告诉您所有的依赖项都已安装完毕。

如果您没有安装所有的依赖项,您将得到一个输出,告诉您缺少哪些依赖项。安装你需要的任何剩余的包,然后重新运行extras/check_dependencies.sh命令。由于您刚刚安装的依赖项,现在可能会出现新的必需安装。继续交替这两个步骤(检查缺失的依赖项并安装它们),直到您收到一条消息,表明所有的依赖项都已安装(“一切正常”))

| (基地) 瑞安@ Ubuntu:~/kaldi/tools$ extras/check _ dependencies . sh |

最后运行make。如果您有多 CPU 版本,请参阅下面的安装说明。

| (基) 瑞安@ Ubuntu:~/kaldi/tools$ make CXX = g++ |

安装说明

如果您有多个 CPU,您可以通过向make提供“-j”选项来进行并行构建,以便加快安装。例如,要使用 4 个 CPU,请输入make -j 4

安装 Src

接下来,我们需要执行src安装。先是,cd变成了src

| (基地) 瑞安@ Ubuntu:~/kaldi/tools$ CD../src |

然后运行以下命令。如果您有多 CPU 版本,请参阅下面的安装说明。对于单处理器系统,这个构建可能需要几个小时。

| (基地) 瑞安@ Ubuntu:~/kaldi/src\(。/configure - shared(基) 瑞安@ Ubuntu:~/kaldi/src\) make depend CXX = g++【Ryan @ Ubuntu】:~/kaldi/src【make cxx = g++ |

安装说明

同样,如果你有多个 CPU,为了加快安装速度,你可以给make dependmake都提供-j选项。例如,要使用 4 个 CPU,请输入make depend -j 4make -j 4

克隆项目存储库

现在是克隆 AssemblyAI 提供的项目存储库的时候了,它托管了本教程剩余部分所需的代码。项目存储库遵循 kaldi/egs 中其他文件夹的结构(kaldi 根目录中的“examples”目录),并且包括额外的文件来为您自动生成转录。

导航到 egs 文件夹,克隆项目存储库,然后导航到 s5 子目录

| 【Ryan @ Ubuntu】:~/kaldi/srcCD 美元-什么/egs〔T9〕(基地) 瑞安@ Ubuntu:~/卡尔迪/egs $吉特克隆 https://github.com/AssemblyAI/kaldi-asr-tutorial.git(基地) 瑞安@ Ubuntu:~/卡尔迪/egs $ cd 卡尔迪-ASR-教程/s5 |

附加说明

此时,您可以删除egs目录中的所有其他文件夹。它们占用了大约 10 GB 的磁盘空间,但是包含了你可能想在本教程之后查看的其他例子。

转录音频文件-快速使用

现在我们准备开始转录一个音频文件!我们已经在一行代码中提供了自动转录. wav 文件所需的一切。

举个简单的例子,你需要做的就是运行

| (基础) 瑞安@ Ubuntu:~/kaldi/egs/kaldi-ASR-tutorial/S5$ python 3 main . py |

这个命令将转录提供的示例音频文件gettysburg.wav -一个 10 秒。包含葛底斯堡地址第一行的 wav 文件。执行该命令需要几分钟时间,之后您会在kaldi-asr-tutorial/s5/out.txt中找到转录

重要说明

第一次运行main时,您需要互联网连接。py,以便下载预先训练好的模型。

如果你想转录你自己的。wav 文件,先放在 s5 子目录下,然后运行:

| (基地) 瑞安@ Ubuntu:~/kaldi/egs/kaldi-ASR-tutorial/S5$ python 3 main . py Gettysburg . wav |

在这里用您的文件名替换gettysburg.wav。如果唯一的。s5 子目录中的 wav 文件是您的目标音频文件,您可以简单地运行python3 main.py而无需指定文件名。

这种自动化过程最适用于单个扬声器和相对较短的音频。对于更复杂的用法,您将不得不阅读下一节并修改代码以满足您的需求,并遵循 Kaldi 文档

重置目录

每次运行main.py时,它都会调用reset_directory.py,这将删除main.py生成的所有文件/文件夹(除了预训练模型下载的 tarballs),以便重新开始每次运行。这意味着如果你在另一个文件上调用main.py,你的out.txt转录将被删除,所以如果你想在转录另一个文件之前保留out.txt的话,一定要将它移动到另一个目录。

如果您在预训练模型下载时中断main.py执行,您将收到下游错误。在这种情况下,运行以下命令使完全重置目录(即除了reset_directory.py删除的文件/文件夹之外,还删除预训练的模型 tarballs)

| (基础) 瑞安@ Ubuntu:~/kaldi/egs/kaldi-ASR-tutorial/S5$ python 3 reset _ directory _ completely . py |

转录音频文件-理解代码

如果您有兴趣了解 Kaldi 的语音识别如何生成上一节中的转录,那么请继续阅读!

为了理解用 Kaldi 生成转录的整个过程,我们将深入研究main.py。请记住,我们的用例是一个展示如何将预训练的 Kaldi 模型用于 ASR 的玩具示例。Kaldi 是一个非常强大的工具包,可以容纳更复杂的用法;但是它确实有一个相当大的学习曲线,所以学习如何正确地将其应用于更复杂的任务将需要一些时间。

此外,我们将在不同的部分简要概述背后的理论,但 ASR 是一个复杂的主题,因此本质上我们的对话将是表层的!

让我们开始吧。

进口

我们从一些进口商品开始。首先,我们调用reset_directory.py文件,该文件清除了由剩余的main.py生成的文件/文件夹的目录,这样我们可以从头开始。然后我们导入subprocess,这样我们就可以发出 bash 命令,以及其他一些用于操作系统导航和文件操作的包。

import reset_directory
import subprocess as s
import os
import sys
import glob

参数验证

接下来,我们执行一些参数验证。我们确保最多有一个附加参数传入到main.py;如果有,我们确保它是一个. wav 文件。如果没有给定的参数,那么我们简单地选择第一个。wav 文件被glob.glob找到,如果这样的文件存在的话。

我们将文件名(带或不带扩展名)保存在变量中以备后用。

if len(sys.argv) == 1:
    try:
        FILE_NAME_WAV = glob.glob("*.wav")[0]
    except:
        raise ValueError("No .wav file in the root directory")
elif len(sys.argv) == 2:
    FILE_NAME_WAV = list(sys.argv)[1]
    if FILE_NAME_WAV[-4:] != ".wav":
        raise ValueError("Provided filename does not end in '.wav'")
else:
    raise ValueError("Too many arguments provided. Aborting")

FILE_NAME = FILE_NAME_WAV[:-4]

Kaldi 文件生成

现在是时候创建一些标准文件了,Kaldi 需要这些文件来生成转录。我们将 s5 目录路径保存到一个变量中,以便我们可以轻松地导航回它,然后创建并导航到一个数据/测试目录,我们将在其中存储我们的数据。

ORIGINAL_DIRECTORY = os.getcwd()

# Make data/test dir
os.makedirs("./data/test")
os.chdir("./data/test")

我们将生成的第一个文件叫做spk2utt,它将说话者映射到他们的话语。出于我们的目的,我们假设有个说话者和一个发声者,所以这个文件很容易自动生成。

with open("spk2utt", "w") as f:
    f.write("global {0}".format(FILE_NAME))

接下来,我们在utt2spk文件中创建反向映射。注意,这个文件是一对一的,不同于 spk2utt 的一对多特性(一个发言者可能有多个发言,但是每个发言只能有一个发言者)。出于我们的目的,生成该文件也很容易:

with open("utt2spk", "w") as f:
    f.write("{0} global".format(FILE_NAME))

我们创建的最后一个文件叫做wav.scp。它将音频文件标识符映射到它们的系统路径。我们再次自动生成这个文件。

wav_path = os.getcwd() + "/" + FILE_NAME_WAV
with open("wav.scp", "w") as f:
    f.write("{0} {1}".format(FILE_NAME, wav_path))

最后,我们返回到根目录

os.chdir(ORIGINAL_DIRECTORY)

附加说明

请注意,这些不是 Kaldi 可以使用的唯一可能的输入文件,只是最低限度的。对于更高级的用法,比如性别映射,请查看 Kaldi 文档。

MFCC 配置文件修改

要使用 Kaldi 对我们的音频文件执行 ASR,我们必须首先确定一些方法,以 Kaldi 模型可以处理的格式表示这些数据。为此,我们使用梅尔频率倒谱系数(MFCC)MFCC是定义音频的梅尔频率倒谱的一组系数,其本身是信号的傅立叶变换非线性映射(梅尔频率)的对数功率谱余弦变换。如果这听起来令人困惑,不要担心——没有必要理解生成转录的目的!需要知道的重要一点是,MFCCs 是音频信号的一种低维表示,其灵感来自于人类听觉处理

有一个我们在生成 MFCCs 时使用的配置文件,位于。从实践的角度来看,我们唯一需要知道的是,我们必须修改这个文件来列出我们输入的合适的采样速率。wav 文件。我们按如下方式自动完成这项工作:

首先,我们调用一个子进程,它打开一个 bash shell 并使用sox获取。wav 文件。然后,我们执行字符串操作来隔离的采样速率。wav 文件。

bash_out = s.run("soxi {0}".format(FILE_NAME_WAV), stdout=s.PIPE, text=True, shell=True)
cleaned_list = bash_out.stdout.replace(" ","").split('\n')
sample_rate = [x for x in cleaned_list if x.startswith('SampleRate:')]
sample_rate = sample_rate[0].split(":")[1]

接下来,我们打开并读取 MFCC 配置文件,以便进行修改

with open("./conf/mfcc_hires.conf", "r") as mfcc:
    lines = mfcc.readlines()

确定设置采样频率的线路并将其隔离。

line_idx = [lines.index(l) for l in lines if l.startswith('--sample-frequency=')]
line = lines[line_idx[0]]

接下来,我们将这一行重新格式化,以列出的采样速率。由soxi命令识别的 wav 文件。

line = line.split("=")
line[1] = sample_rate + line[1][line[1].index(" #"):]
line = "=".join(line)

最后,我们替换lines列表中的相关行,将这个列表折叠回一个字符串,然后将这个字符串写入 MFCC 配置文件。

lines[line_idx[0]] = line
final_str = "".join(lines)
with open("./conf/mfcc_hires.conf", "w") as mfcc:
    mfcc.write(final_str)

特征抽出

现在我们可以开始处理我们的音频文件。首先,我们打开一个记录 bash 输出的文件,我们将在以后的每个 bash 命令中使用它。然后,我们复制我们的。wav 文件放入。/data/test 目录,然后复制整个。/data/test 目录到一个新目录(。/data/test_hires)进行处理。

with open("main_log.txt", "w") as f:
    bash_out = s.run("cp {0} data/test/{0}".format(FILE_NAME_WAV), stdout=f, text=True, shell=True)

    bash_out = s.run("utils/copy_data_dir.sh data/test data/test_hires", stdout=f, text=True, shell=True)

接下来,我们使用之前修改的数据和配置文件生成 MFCC 要素。

 bash_out = s.run("steps/make_mfcc.sh --nj 1 --mfcc-config "
                     "conf/mfcc_hires.conf data/test_hires", stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • steps/make_mfcc.sh:指定生成 mfccs 的 shell 脚本的位置
  • --nj 1:指定要运行的任务数量。如果你有多核机器,你可以增加这个数字
  • --mfcc-config conf/mfcc_hires.conf:指定我们之前修改的配置文件的位置
  • data/test_hires:指定包含我们将要操作的相关数据的数据文件夹

该命令生成confdatalog目录以及feats.scpframe_shiftutt2durutt2num_frames文件(全部在data/test_hires目录中)

在此之后,我们计算数据的倒谱均值和方差归一化( CMVN )统计量,从而最大限度地减少噪声污染造成的失真。也就是说,CMVN 有助于使我们的 ASR 系统对噪声更加鲁棒。

 bash_out = s.run("steps/compute_cmvn_stats.sh data/test_hires", stdout=f, text=True, shell=True)

最后,我们使用fix_data_dir.sh shell 脚本来确保数据目录中的文件得到正确的排序和过滤,并在data/test_hires/.backup中创建数据备份。

 bash_out = s.run("utils/fix_data_dir.sh data/test_hires", stdout=f, text=True, shell=True)

预训练模型下载和提取

既然我们已经执行了 MFCC 特征提取和 CMVN 归一化,我们需要一个模型来传递数据。在这种情况下,我们将使用在卡尔迪的预训练模型库中找到的 Librispeech ASR 模型,它是在 LibriSpeech 数据集上训练的。该模型由四个子模型组成:

  1. I 向量提取器
  2. 基于 TDNN-F 的链式模型
  3. 一个小型三元模型语言模型
  4. 一个基于 LSTM 的重新评分模型

为了下载这些模型,我们首先检查这些 tarballs 是否已经在我们的目录中。如果不是,我们使用wget下载它们

 for component in ["chain", "extractor", "lm"]:
        tarball = "0013_librispeech_v1_{0}.tar.gz".format(component)
        if tarball not in os.listdir():
            bash_out = s.run('wget http://kaldi-asr.org/models/13/{0}'.format(tarball), stdout=f, text=True, shell=True)

并使用tar提取它们。

 bash_out = s.run('for f in *.tar.gz; do tar -xvzf "$f"; done', stdout=f, text=True, shell=True)

这将创建exp/nnet3_cleanedexp/chain_cleaneddata/lang_test_tgsmallexp/rnnlm_lstm_1a目录。

  • nnet3_cleaned是 I 向量提取器的目录
  • chain_cleaned是连锁模式目录
  • tgsmall是小三元模型语言的模型目录
  • rnnlm是总部位于 LSTM 的重新计分模式

警告

如果wget进程在下载过程中被中断,您将在下游遇到错误。在这种情况下,在终端中运行下面的命令,删除那里的任何模型 tarballs,并完全重置目录。默认情况下,我们调用reset_directory.py而不是reset_directory_completely.py,这样我们就不必在每次运行main.py时下载模型(压缩了大约 430 MB)。

| (基础) 瑞安@ Ubuntu:~/kaldi/egs/kaldi-ASR-tutorial/S5$ python 3 reset _ directory _ completely . py |

解码生成

提取 I 向量

接下来,我们将提取 I 向量,用于识别不同的说话者。尽管在这种情况下我们只有一个说话者,但对于一般的用例,我们还是提取 I 向量,因为它们是预期的下游。

我们创建一个目录来存储 I 向量,然后运行一个 bash 命令来提取它们:

 os.makedirs("./exp/nnet3_cleaned/ivectors_test_hires")
    bash_out = s.run("steps/online/nnet2/extract_ivectors_online.sh --nj 1 "
                     "data/test_hires exp/nnet3_cleaned/extractor exp/nnet3_cleaned/ivectors_test_hires",
                     stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • steps/online/nnet2/extract_ivectors_online.sh:指定提取 I 向量的 shell 脚本的位置
  • --nj 1:指定要运行的任务数量。如果你有多核机器,你可以增加这个数字
  • data/test_hires:指定数据目录的位置
  • exp/nnet3_cleaned/extractor:指定提取器目录的位置
  • exp/nnet3_cleaned/ivectors_test_hires:指定存储 I 向量的位置

构建解码图

为了得到我们的转录,我们需要通过解码图传递我们的数据。在我们的例子中,我们将构建一个完全扩展的解码图( HCLG ),它表示语言模型、词典(发音词典)、上下文依赖和模型中的 HMM 结构。

附加说明

解码图的输出是一个有限状态转换器,在输出上有 word-id,在输入上有 transition-id(解析为 pdf-id 的索引)

HCLG 代表功能的组合,其中

  • h 包含 HMM 定义,其输入是转换 id,输出是上下文相关音素
  • c 是上下文相关的,它接收上下文相关的音素并输出音素
  • l 是词典,它接收音素并输出单词
  • G 是编码语法或语言模型的接受者,它既接受又输出单词

最终结果是我们的解码,在这种情况下,我们的单个话语的转录。

在我们可以通过解码图传递我们的数据之前,我们需要构造它。我们创建一个目录来存储图形,然后用下面的命令构建它。

 os.makedirs("./exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall")
    bash_out = s.run("utils/mkgraph.sh --self-loop-scale 1.0 --remove-oov "
                     "data/lang_test_tgsmall exp/chain_cleaned/tdnn_1d_sp exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall",
                     stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • utils/mkgraph.sh:指定构建解码图的 shell 脚本的位置
  • --self-loop-scale 1.0:相对于语言模型 1 按指定值缩放自循环
  • --remove-oov:删除词汇外(oov)单词
  • data/lang_test_tgsmall :指定语言目录的位置
  • exp/chain_cleaned/tdnn_1d_sp:指定模型目录的位置
  • exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall:指定存储构建图形的位置

使用生成的图形解码

现在我们已经构建了我们的解码图,我们终于可以使用它来生成我们的转录!

首先我们创建一个目录来存储解码信息,然后使用下面的命令进行解码。

 os.makedirs("./exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall")
    bash_out = s.run("steps/nnet3/decode.sh --acwt 1.0 --post-decode-acwt 10.0 --nj 1 "
                     "--online-ivector-dir exp/nnet3_cleaned/ivectors_test_hires "
                     "exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall "
                     "data/test_hires exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall",
                     stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • steps/nnet3/decode.sh:指定运行解码的 shell 脚本的位置
  • --acwt 1.0:设置声学刻度。默认为 0.1,但这不适合链型 2
  • --post-decode-acwt 10.0:将声音放大 10 倍,以便正常的配乐脚本工作(链模型需要)
  • --nj 1:指定要运行的任务数量。如果你有一个多核机器,你可以增加这个数字
  • --online-ivector-dir exp/nnet3_cleaned/ivectors_test_hires:指定 I 向量目录
  • exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall:指定图形目录的位置
  • data/test_hires:指定数据目录的位置
  • exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall:指定存储解码信息的位置

转录检索

是时候找回我们的剧本了!转录点阵作为 GNU zip 文件存储在decode_test_tgsmall目录中,以及其他文件中(如果您输入了 Kaldi text文件,则包括单词错误率)。

我们存储 zip 文件和 graph word.txt文件的目录路径,然后将它们传递给存储 bash 命令的command变量。这个命令解压我们的 zip 文件,然后将最佳路径写入到我们的s5目录中一个名为out.txt的文件中。

 gz_location = "exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall/lat.1.gz"
    words_txt_loc = "{0}/exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall/words.txt".format(ORIGINAL_DIRECTORY)
    command = "../../../src/latbin/lattice-best-path " \
              "ark:'gunzip -c {0} |' " \
              "'ark,t:| utils/int2sym.pl -f 2- " \
              "{1} > out.txt'".format(gz_location, words_txt_loc)
    bash_out = s.run(command, stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • ../../../src/latbin/lattice-best-path:指定文件的位置,该文件导航点阵以生成解码
  • ark:'gunzip -c {0} |':通过 popen() 3 管道命令将晶格文件解压到 shell
  • 'ark,t:| utils/int2sym.pl -f 2- {1} > out.txt':将解码写入 out.txt 4

让我们来看看我们生成的转录与真正的转录相比如何!

真实:

八十七年前,我们的先辈在这个大陆上创立了一个新国家,它孕育于自由之中,奉行人人生而平等的原则

转录:

七年前,我们的先辈们创建了 T2,在这个大陆上诞生了一个新的国家——T4、自由、T7——奉行人人生而平等的原则

在 30 个单词中,我们有 5 个错误,产生了大约 17%的单词错误率。

用基于 LSTM 的模型重新计分

我们可以使用以下命令对基于 LSTM 的模型进行重新评分:

 command = "../../../scripts/rnnlm/lmrescore_pruned.sh --weight 0.45 --max-ngram-order 4 " \
              "data/lang_test_tgsmall exp/rnnlm_lstm_1a data/test_hires " \
              "exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall exp/chain_cleaned/tdnn_1d_sp/decode_test_rescore"
    bash_out = s.run(command, stdout=f, text=True, shell=True)

附加说明

关于 bash 命令参数的更多信息可以在这里找到:

  • ../../../scripts/rnnlm/lmrescore_pruned.sh:指定运行记录 5 的 shell 脚本的位置
  • --weight 0.45:指定 RNNLM 的插值权重
  • --max-ngram-order 4:如果网格中的历史共享相同的 ngram 历史,则通过合并网格中的历史来近似网格重新计分,这防止了网格指数爆炸
  • data/lang_test_tgsmall :指定旧语言模型目录
  • exp/rnnlm_lstm_1a:指定 RNN 语言模型目录
  • data/test_hires:指定数据目录
  • exp/chain_cleaned/tdnn_1d_sp/decode_test_tgsmall:指定输入解码目录
  • exp/chain_cleaned/tdnn_1d_sp/decode_test_rescore:指定输出解码目录

我们再次将转录输出到一个. txt 文件,在本例中称为out_rescore.txt:

 command = "../../../src/latbin/lattice-best-path " \
              "ark:'gunzip -c exp/chain_cleaned/tdnn_1d_sp/decode_test_rescore/lat.1.gz |' " \
              "'ark,t:| utils/int2sym.pl -f 2- " \
              "{0}/exp/chain_cleaned/tdnn_1d_sp/graph_tgsmall/words.txt > out_rescore.txt'".format(ORIGINAL_DIRECTORY)
    bash_out = s.run(command, stdout=f, text=True, shell=True)

在我们的例子中,重新评分并没有改变我们生成的转录,但它可能会改善你的!

高级 Kaldi 语音识别

希望这篇教程能让你理解 Kaldi 的基础知识,并为更复杂的 NLP 任务提供一个起点!我们只是使用了一个单独的话语和一个单独的.wav文件,但是我们也可以考虑我们想要做说话者识别、音频对齐或者更多的情况。

您还可以在 Kaldi 中使用预先训练好的模型。例如,如果您有数据来训练自己的模型,您可以制作自己的端到端系统,或者将自定义声学模型集成到使用预训练语言模型的系统中。无论您的目标是什么,您都可以使用本文中确定的构建模块来帮助您开始!

有很多不同的方法来处理音频以提取有用的信息,每种方法都提供了自己的子领域,其中包含特定任务的知识和创造性方法的历史。如果你想更深入地研究 Kaldi 来构建你自己复杂的 NLP 系统,你可以在这里查看 Kaldi 文档。

云语音转文本 API

Kaldi 是 NLP 应用程序的一个非常强大且维护良好的框架,但它不是为普通用户设计的。理解 Kaldi 如何在幕后运作可能需要很长时间,这种理解是正确使用它所必需的。

因此,Kaldi 不是为即插即用的语音处理应用程序而设计的。这对那些没有时间或技术来定制和训练 NLP 模型,但想在更大的应用程序中实现语音识别的人来说是一个难题。

如果你想用几行代码获得高质量的文本,AssemblyAI 提供了一个快速、准确、易用的语音转文本 API 。您可以在这里注册一个免费的 API 令牌,并获得最先进的模型,这些模型提供:

  • 核心转录
    • 异步语音转文本
    • 实时语音转文本
  • 音频智能

获取一个令牌并检查 AssemblyAI docs 以开始。

脚注

  1. 链接到 Kaldi 文档中的“声学概率转换的标度”

  2. 链接到 Kaldi 文档中的“用‘链’模型解码”

  3. 链接到 Kaldi 文档中的“扩展文件名:rx 文件名和 wx 文件名”

  4. 链接到 Kaldi 文档中的“表 I/O”

  5. 链接到 Kaldi ASR GitHub repo 中的lmrescore_pruned.sh脚本

6)关于 Kaldi 入门的其他初学者资源,请查看这个这个这个资源。这些来源的元素已经过改编,可以在本文中使用。

初学者的机器学习概念

原文:https://www.assemblyai.com/blog/machine-learning-concepts/

这门入门课程旨在通过简单易懂的视频讲解和教程,教你基本的机器学习和深度学习概念。

本课程分为三个主要模块:

  • 机器学习导论
  • 深度学习基础
  • 附加 ML 资源

我们已经为轻松消费和概念掌握定出了课程进度,尽管您可以随意以您认为有意义的任何速度进行。如果您在学习过程中有任何问题,请不要犹豫,在我们的 Discord channel 上留下您的评论,我们会尽力澄清。

最后,如果您决定继续学习更高级的机器学习概念和应用,我们将为您提供一份附加资源列表。

我们开始吧!

机器学习导论

什么是机器学习?

机器学习是计算机科学和人工智能(AI)的一个分支,它使用数据和算法通过经验来教授和改进模型,有时受到人类自然学习和推断方式的启发。

机器学习不同于传统的编程建模,它利用大量的训练数据来理解数据中的潜在模式,这一过程被称为“学习”一旦经过训练,机器学习模型就可以对新的、以前从未见过的数据进行推断和预测。机器学习模型需要处理的训练数据越多,这些预测就越准确。

机器学习和深度学习领域的重大进步已经导致该技术广泛集成到日常任务和应用中。现在,你会发现机器学习正在为语音识别系统、语音转文本 API、智能对话聊天机器人、自动股票交易代理、无人驾驶汽车、图像识别系统等提供动力。

第一个视频教程将涵盖监督机器学习、非监督机器学习、偏差和方差以及评估指标的基础知识。

监督机器学习

我们将从监督机器学习开始。这个视频将讨论什么是监督机器学习,例子,数据和训练,类型和算法。

https://www.youtube.com/embed/Mu3POlNoLdc?start=5&feature=oembed

无监督机器学习

现在你对有监督的机器学习有了一个基本的了解,我们将仔细看看无监督的机器学习。这个视频讲述了它是什么以及不同的方法和组件,包括聚类,K-means,离群点检测,潜在变量建模,等等。

https://www.youtube.com/embed/yteYU_QpUxs?feature=oembed

最大似然偏差和方差

我们将继续讨论偏差和方差,这是数据科学和机器学习中的两个重要概念。在本视频中,我们将了解它们是什么,当您有高偏差或方差时会发生什么,以及如何解决这些问题。

https://www.youtube.com/embed/nbY2KqXSsaE?start=2&feature=oembed

评估指标

选择正确的评估指标对于机器学习模型的成功至关重要。此视频解释了不同的类型以及如何根据问题类型选择最佳类型。

https://www.youtube.com/embed/LbX4X71-TFI?start=1&feature=oembed

深度学习基础

现在你对机器学习有了更多的了解,我们也将讨论深度学习的基础知识。虽然有时会与机器学习混淆,但深度学习实际上是机器学习的一个子集,它自动化了机器学习过程的元素,并使机器学习应用程序更具可扩展性。

本节将包括解释什么是深度学习、激活函数和神经网络中的反向传播、正则化和批量归一化的视频。

5 分钟深度学习

学习深度学习的基础知识,包括它是什么,它与人工智能和机器学习的关系,以及常见的深度学习应用。

https://www.youtube.com/embed/dccdadl90vs?start=41&feature=oembed

神经网络中的激活函数

在本视频中,我们将探讨什么是激活功能,有哪些类型的激活功能,以及使用它们的原因和方法。

https://www.youtube.com/embed/Fu273ovPBmQ?feature=oembed

神经网络的反向传播

反向传播是需要理解的最重要的深度学习概念之一。在这段视频中,我们将了解它是什么,它是如何工作的,以及为什么它是训练过程中必不可少的组成部分。

https://www.youtube.com/embed/LHLrXavT1gQ?feature=oembed

神经网络的正则化

在这个视频教程中,我们将学习正则化的基本逻辑和最常用的技术来实现它。

https://www.youtube.com/embed/EehRcPo1M-Q?feature=oembed

神经网络的批量规范化

在我们的最后一个视频中,我们将看看批量标准化——训练神经网络时需要掌握的一个强大工具。

https://www.youtube.com/embed/yXOMHOpbon8?feature=oembed

跟上 ML 的发展

在我们的最后一个模块中,我们为您的 ML 之旅留下了额外的资源,包括 Pytorch 和 TensorFlow 的概述,以及我们深度学习团队最喜欢的一些播客、博客和 YouTube 帐户。最后,我们列出了 2022 年顶级大规模会议的名单——如果你受到启发继续探索这个迷人的领域,大多数会议都有虚拟选项。

附加 ML 资源

该用 Pytorch 还是 TensorFlow?

听机器学习播客

机器学习博客关注

机器学习内容创作者观看

要参加的机器学习会议

假人用中间管

原文:https://www.assemblyai.com/blog/mediapipe-for-dummies/

今天,大量潜在的机器学习应用依赖于几个基本的基线机器学习任务。例如,手势导航和手语检测器都依赖于程序识别和跟踪人手的能力。鉴于构建类似手部跟踪模型的东西既耗时又耗费资源,开发瓶颈存在于创建所有依赖手部跟踪的应用程序的过程中。为了解决这个问题,谷歌发明了 MediaPipe

Examples of Artificial Intelligence applications - A Disney-character selfie filter (left, source) and vehicular object detection (right, source)

MediaPipe 为手跟踪等常见任务提供了基石机器学习模型,因此消除了许多机器学习应用程序存在的相同的发展瓶颈。这些模型,以及它们过于易用的 API,反过来简化了开发过程,缩短了许多依赖计算机视觉的应用程序的项目生命周期。

在本教程中,我们将学习如何使用 MediaPipe 的一些 Python API只用几行代码来完成基本的计算机视觉任务,包括面部跟踪和姿势提取。我们甚至会看到这些数据是如何被用来驱动像搅拌机中的面部动作捕捉这样的工具的!让我们开始吧。

https://www.assemblyai.com/blog/content/media/2022/04/faces_crop-1.mp4

Facial motion capture data in the Blender 3D animation software

介绍

MediaPipe 是 Google 的一个项目,它为直播和流媒体提供“开源、跨平台、可定制的 ML 解决方案”。换句话说,MediaPipe 提供了对各种强大的机器学习模型的访问,这些模型是考虑到移动设备的硬件限制而构建的。这些模型包括:

  • 解剖模型
    • 手跟踪
    • 姿态跟踪
    • 面部网格跟踪
    • Holstic 跟踪(3 个以上组合)
  • 分割模型
    • 序列分割
    • 头发分割
  • 对象模型
    • 2D 目标检测/跟踪
    • 三维物体检测和姿态估计

现在让我们看看如何在 Python 中使用这些 API!

入门指南

在我们开始使用 MediaPipe 本身之前,我们需要执行一些初步的安装/导入。如果你只是想跟着做,而不是必须这样做,你可以打开这个 Colab 笔记本并继续到的下一部分

装置

MediaPipe 适用于 C++、Android 等;但是,在本教程中,我们将只使用 Python。要安装 MediaPipe for Python,只需将它安装到您想要的环境中:

pip install mediapipe

如果你想完全按照这个教程来做,你可以跳过上面直接安装 MediaPipe 的步骤,而是从关联的 GitHub 中的需求文件中选择pip install。为此,导航到要克隆项目文件夹的目录,并在命令提示符/终端中执行以下命令:

窗户

git clone https://github.com/AssemblyAI-Examples/mediapipe-python.git
cd mediapipe-python
python3 -m venv mp_env
mp_env\Scripts\activate.bat
pip install -r requirements.txt

类 UNIX 系统

git clone https://github.com/AssemblyAI-Examples/mediapipe-python.git
cd mediapipe-python
python3 -m venv mp_env
source mp_env/bin/activate
pip install -r requirements.txt

然后,打开notebook.ipynb Jupyter 笔记本,跟着代码走,(注意,mp_env虚拟环境需要作为内核添加到 Jupyter 中)。

进口

接下来,我们用一些 Python 导入和变量声明来完成设置:

import cv2
import mediapipe as mp
import urllib.request
import numpy as np
import pickle
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import animation
import PyQt5
from PIL import Image
from IPython.display import Video
import nb_helpers

mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic
mp_pose = mp.solutions.pose
mp_face_mesh = mp.solutions.face_mesh

使用 MediaPipe 进行面部网格和运动捕捉

为了开始使用 MediaPipe,我们将首先探索它的 Face Mesh API,并了解它如何作为后端来支持 Blender 中的面部动作捕捉等应用程序。

图像获取

首先,使用以下命令获取图像:

face_url = "https://1vw4gb3u6ymm1ev2sp2nlcxf-wpengine.netdna-ssl.com/wp-content/uploads/shutterstock_149962697-946x658.jpg"
urllib.request.urlretrieve(face_url, "face_image.jpg")

img = Image.open('face_image.jpg')
display(img)

这将下载下图作为face_image.jpg

让我们看看如何使用 MediaPipe 来分析这个图像!

人脸网格处理

我们将使用 MediaPipe 的FaceMesh对象来提取对应于图像中面部表面的 3D 网格。

# Define image filename and drawing specifications
file = 'face_image.jpg'
drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)

# Create a face mesh object
with mp_face_mesh.FaceMesh(
        static_image_mode=True,
        max_num_faces=1,
        refine_landmarks=True,
        min_detection_confidence=0.5) as face_mesh:

    # Read image file with cv2 and process with face_mesh
    image = cv2.imread(file)
    results = face_mesh.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

# Define boolean corresponding to whether or not a face was detected in the image
face_found = bool(results.multi_face_landmarks)

其他详细信息

FaceMesh的参数提供了在处理我们的图像时会用到的附加信息。

  • static_mode=True指定我们将使用该对象处理静态图像(与视频流相反)。
  • max_num_faces=1指定FaceMesh最多检测一张脸。
  • refine_landmarks=True应用注意力网格模型来细化眼睛和嘴唇周围的界标坐标,并输出虹膜周围的附加界标。
  • 最后,min_detection_confidence=0.5为返回识别的面部所需的检测置信度设置下限。该值对应于一个概率,因此其值必须在[0.0,1.0]内。

用 OpenCV ( cv2)读取图像会产生 BGR 颜色通道,因此图像在被face_mesh.process()处理之前首先被转换成 RGB

请注意,下面这条线是魔法发生的地方:

results = face_mesh.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 

所有的处理都是用一行简单的代码完成的。经过处理后,我们得到了高度精确的结果,不需要任何数据收集、数据准备、模型定义、模型调整、模型测试等。这就是 MediaPipe 的价值——它提供了对几个非常强大的机器学习模型的轻松访问,这些模型产生易于解析的结果。现在让我们来看看我们能用这些结果做些什么。

绘图面网格镶嵌

我们可以使用 MediaPipe 的绘图工具在我们的图像上可视化我们的结果。

if face_found:
    # Create a copy of the image
    annotated_image = image.copy()

    # Draw landmarks on face
    mp_drawing.draw_landmarks(
        image=annotated_image,
        landmark_list=results.multi_face_landmarks[0],
        connections=mp_face_mesh.FACEMESH_TESSELATION,
        landmark_drawing_spec=None,
        connection_drawing_spec=mp_drawing_styles
            .get_default_face_mesh_tesselation_style())

    # Save image
    cv2.imwrite('face_tesselation_only.png', annotated_image)

# Open image
img = Image.open('face_tesselation_only.png')
display(img)

其他详细信息

draw_landmarks()的参数提供了显示镶嵌所需的附加信息。

  • image=annotated_image指定我们想要将镶嵌图映射到的图像。
  • landmark_list=results.multi_face_landmarks[0]指定将要绘制到图像上的地标的位置数据。results.multi_face_landmarks是包含媒体管道NormalizedLandmarkList的列表,每个面一个。由于我们的图像只有一个面,我们通过指定它的索引([0])来访问results.multi_face_landmarks的唯一的NormalizedLandmarkList元素,并最终将这个列表设置为landmark_list参数。
  • NormalizedLandmarkList s 是有序的列表,mp_face_mesh.FACEMESH_TESSELATION对象是一个冻结的集合,定义了这些列表中的点如何连接。我们指定这些与connections=mp_face_mesh.FACEMESH_TESSELATION的联系。
  • landmark_drawing_spec=drawing_spec使用我们在上面设置的绘图规范,在每个地标处绘制一个绿色小圆圈。
  • 最后,connection_drawing_spec=mp_drawing_styles .get_default_face_mesh_tesselation_style()对界标之间的连接使用默认的绘图规范。

Face image with MediaPipe Face Mesh drawn on top

绘制人脸网格轮廓和虹膜

我们的results对象不仅仅包含面部镶嵌信息。脸部的轮廓和虹膜也可以被识别,并且可以被分别绘制。现在让我们在原始图像上绘制两者:

if face_found:
    # Create a copy of the image
    annotated_image = image.copy()

    # For each face in the image (only one in this case)
    for face_landmarks in results.multi_face_landmarks:

        # Draw the facial contours of the face onto the image
        mp_drawing.draw_landmarks(
            image=annotated_image,
            landmark_list=face_landmarks,
            connections=mp_face_mesh.FACEMESH_CONTOURS,
            landmark_drawing_spec=None,
            connection_drawing_spec=mp_drawing_styles
                .get_default_face_mesh_contours_style())

        # Draw the iris location boxes of the face onto the image
        mp_drawing.draw_landmarks(
            image=annotated_image,
            landmark_list=face_landmarks,
            connections=mp_face_mesh.FACEMESH_IRISES,
            landmark_drawing_spec=None,
            connection_drawing_spec=mp_drawing_styles
                .get_default_face_mesh_iris_connections_style())

	# Save the image
    cv2.imwrite('face_contours_and_irises.png', annotated_image)

此代码的细节与上面的镶嵌代码非常相似,所以请参见上面部分中的“附加细节”下拉列表以了解更多信息。

使用混合器进行面部网格运动捕捉

简单地将我们的结果绘制到图像上当然很好,但是如果我们真的想用这些数据做些什么呢?除了处理图像,MediaPipe 还可以处理视频,以获取时序 XYZ 空间面部网格数据。然后我们可以有效地导出这些数据,可以使用 MediaPipe 作为“后端”来计算动作捕捉数据。 BlendArMocap ,一个为 Blender 3D 图形和动画软件开发的开源项目,就是这么做的。

以下是使用 BlendArMocap 导入 Blender 的 3D 面部网格数据的几个视图:

https://www.assemblyai.com/blog/content/media/2022/04/faces_crop-2.mp4

Facial tracking data imported into Blender

这个数据可以用来驱动一个“装备”,它定义(在这种情况下)一个人形解剖结构。从这里,可以将角色模型映射到装备上,以便在 3D 中制作角色模型的动画。

https://www.assemblyai.com/blog/content/media/2022/04/face_blender.mp4

Blender rig driver by MediaPipe facial tracking data

很难轻描淡写这个过程有多令人印象深刻 -一系列 2D 图像数据,它只是许多 RGB 值的数组,经过处理,人脸被自动识别。然后识别面部的显著特征,并随时间进行跟踪,在每一帧提取 3D 数据。然后,这些数据被用于制作角色运动的动画,以进行可能具有照片真实感的渲染,这种渲染可以结合光线跟踪、VFX 等技术。

整个过程由 MediaPipe 和 OpenCV 提供支持。

虽然使用像 BlendArMocap 这样的开源工具很棒,但一些用户可能出于自己的目的想要处理视频和原始数据。现在让我们来看看如何做到这一点:

基于 MediaPipe 的姿态检测和分割

MediaPipe 的姿势检测类似于人脸网格,除了它不出所料地识别人的姿势而不是人脸。现在让我们来探索一下这种能力。

姿态处理和绘制

首先,与之前一样,我们下载将用于处理的图像:

img_url = "https://static.turbosquid.com/Preview/2015/11/10__11_56_36/anthonystanding23dmetry3dhuman01.jpg5e774d4d-9b9e-456d-9d7b-fc4d741cf940Large.jpg"
urllib.request.urlretrieve(img_url, "pose.jpg")

img = Image.open('pose.jpg')
display(img)

我们可以再次用 MediaPipe 对象处理这个图像,这次使用一个Pose实例,并在图像上绘制检测到的姿态。这一步的细节实际上与面网格的情况是同构的,所以要获得更深入的解释,请参见上一节。

# Specify the image filename
file = 'pose.jpg'

# Create a MediaPipe `Pose` object
with mp_pose.Pose(static_image_mode=True, 
		  model_complexity=2,
                  enable_segmentation=True) as pose:

    # Read the file in and get dims
    image = cv2.imread(file)

    # Convert the BGR image to RGB and then process with the `Pose` object.
    results = pose.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

# Copy the iamge
annotated_image = image.copy()

# Draw pose, left and right hands, and face landmarks on the image with drawing specification defaults.
mp_drawing.draw_landmarks(annotated_image, 
                          results.pose_landmarks, 
                          mp_pose.POSE_CONNECTIONS,
                          landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())

# Save image with drawing
filename = "pose_wireframe.png"
cv2.imwrite(filename, annotated_image)

# Open image
display(Image.open(filename))

图象分割法

我们的结果中包括分割图像所需的信息。图像中的每个像素都被赋予一个位于已识别姿态的对象内的概率。我们只需要设置一个概率阈值,并隔离高于该阈值的所有像素,以隔离该人。让我们现在这样做——我们将用绿色屏幕替换背景。

# Copy the image
segmented_image = image.copy()

# Probability threshold in [0, 1] that says how "tight" to make the segmentation. Greater value => tighter.
tightness = .3

# Stack the segmentation mask for 3 RGB channels, and then create a filter for which pixels to keep
condition = np.stack((results.segmentation_mask,) * 3, axis=-1) > tightness

# Creates a black background image
bg_image = np.zeros(image.shape, dtype=np.uint8)

# Can change the color of this background by specifying (0-255) RGB values. We choose green-screen green.
bg_image[:] = [4, 244, 4]

# For every pixel location, display the corresponding pixel from the original imgae if the condition in our filter is met (i.e. the probability of being part of the object is above the inclusiogn threshold), or else display corresponding pixel from the background array (i.e. green)
segmented_image = np.where(condition, segmented_image, bg_image)

filename = "pose_green_screen.png"
cv2.imwrite(filename, segmented_image)
display(Image.open(filename))

从 MediaPipe 导出原始数据

从 MediaPipe 导出原始 XYZ 数据非常容易,但是我们需要一些额外的信息来以一种可理解的方式绘制数据。

特别是,理解 MediaPipe 以一致有序的方式存储地标是很重要的。也就是说,mp.solutions.pose.PoseLandmark指定了results.pose_landmarks(图像处理后的输出)中的哪个 XYZ 坐标对应于哪个身体标志(鼻子、右肘等)。).注意,列表中身体标志的顺序是任意的,但是在每次运行的 MediaPipe 中是一致的。您可以通过nb_helpers.poselandmarks_list访问更易读的解剖标志有序列表。

MediaPipe 的POSE_CONNECTIONS对象是一个包含元组的冻结集,这些元组定义了这些地标如何相互映射。例如,元组(0, 4)POSE_CONNECTIONS中,表示results.pose_landmarks中的第 0 个和第 4 个空间点相互连接(分别对应于mp.solutions.pose.PoseLandmark中的“鼻子”和“左 _ 外 _ 眼”——第 0 个和第 4 个身体地标)。让我们打印出一些这样的联系,使这个想法具体化:

poselandmarks_list = nb_helpers.poselandmarks_list

num = 0
for i in mp_holistic.POSE_CONNECTIONS:
    if num < 5:
        print(poselandmarks_list[i[0]], '-->', poselandmarks_list[i[1]])
    else:
        break
    num += 1
LEFT_WRIST --> LEFT_THUMB
RIGHT_WRIST --> RIGHT_INDEX
RIGHT_PINKY --> RIGHT_INDEX
LEFT_EYE_OUTER --> LEFT_EAR
RIGHT_ELBOW --> RIGHT_WRIST

理解了这一点,我们就可以处理原始的 MediaPipe 空间数据了。让我们将上面的姿势数据保存为一个 NumPy 数组,然后显示它

# Create a 3x33 array to store XYZ data for 33 landmarks
data = np.empty((3, len(mp_holistic.PoseLandmark)))

# Store the XYZ data for each landmark
landmarks = results.pose_world_landmarks.landmark
for i in range(len(mp_holistic.PoseLandmark)):
    data[:, i] = (landmarks[i].x, landmarks[i].y, landmarks[i].z)   

# Plot the data
fig = plt.figure()
fig.set_size_inches(5, 5, True)
ax = fig.add_subplot(projection='3d')

nb_helpers.plot_data(data, ax)
nb_helpers.scale_axes(ax)

# Save a rotation animation of the data
filename = 'pose_rotation.mp4'
nb_helpers.rotate_and_save(fig, ax, filename, save=True)

其他详细信息

  • nb_helpers.plot_data()函数简单地绘制出每个标志点,然后遍历POSE_CONNECTIONS在相关点之间绘制直线。数据还会旋转,以便更好地为 PyPlot 绘图定位。
  • nb_helpers.scale_axes()功能缩放轴,以沿不同轴绘制单位长度相等的数据。
  • 最后,nb_helpers.rotate_and_save()函数创建一个旋转动画,并将其保存为.mp4,这样我们就可以观察我们的数据。

所有这些函数都可以在 GitHub 上的nb_helpers.py文件中找到,但是它们实现的细节不在本文的讨论范围之内,所以省略了细节。

https://www.assemblyai.com/blog/content/media/2022/04/pose_rotation.mp4

我们看到,我们已经成功地绘制了原始 MediaPipe 空间数据,并创建了一个动画来观察它。您可以将数据 NumPy 数组保存为下游任务所需的任何导出格式。

在 MediaPipe 中处理视频数据

我们将深入探讨的 MediaPipe 的最后一个方面是如何处理视频数据。我们将特别处理保存的视频文件,但同样的原则也适用于实时摄像机数据。****

我们将使用 MediaPipe 提取一个人行走的时序姿态:

# Download the necessary video
url = 'https://github.com/AssemblyAI-Examples/mediapipe-python/blob/main/walking.mp4?raw=true'
urllib.request.urlretrieve(url, 'walking.mp4') 

# Specify the video filename and create a `Pose` object as before
file = 'walking.mp4'
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:

	# Create VideoCapture object
    cap = cv2.VideoCapture(file)

	# Raise error if file cannot be opened
    if cap.isOpened() == False:
        print("Error opening video stream or file")
        raise TypeError

	# Get the number of frames in the video
    length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    # Create a NumPy array to store the pose data as before
    # The shape is 3x33x144 - 3D XYZ data for 33 landmarks across 144 frames
    data = np.empty((3, len(poselandmarks_list), length))    

	# For each image in the video, extract the spatial pose data and save it in the appropriate spot in the `data` array 
    frame_num = 0
    while cap.isOpened():
        ret, image = cap.read()
        if not ret:
            break

        image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        landmarks = results.pose_world_landmarks.landmark
        for i in range(len(mp_pose.PoseLandmark)):
            data[:, i, frame_num] = (landmarks[i].x, landmarks[i].y, landmarks[i].z)  

        frame_num += 1

    # Close the video file
    cap.release()

数据的形状是横跨 144 帧的 33 个解剖位置的三维 XYZ 数据。同样,我们绘制数据并制作动画,除了在这个例子中,我们用另一个辅助函数来制作动画。我们还绘制了原始视频。注意,绘制的数据看起来是镜像的——这是因为姿态检测对由视频帧捕获的图像起作用,

fig = plt.figure()
fig.set_size_inches(5, 5, True)
ax = fig.add_subplot(projection='3d')

anim = nb_helpers.time_animate(data, fig, ax)

# Save
anim.save('walking_wireframe.mp4', fps=30, extra_args=['-vcodec', 'libx264'], dpi=300)

https://www.assemblyai.com/blog/content/media/2022/04/walkingosp-2.mp4

Python 的更多 MediaPipe 解决方案

到目前为止,我们已经使用 Python 查看了 MediaPipe 的面部网格和姿态检测功能。我们已经看到了如何通过在图像上显示地标或将空间数据封装在 NumPy 数组中来直接使用 MediaPipe,或者如何通过在它的顶部构建的工具来间接使用 MediaPipe,例如使用BlendArMocap将运动捕捉数据传输到 Blender。虽然我们只探索了两种 MediaPipe 解决方案,但它还有更多解决方案。

人脸检测

如果你的应用不需要人脸网格而只需要人脸检测,你可以使用 MediaPipe 人脸检测。该解决方案可以识别人脸,并在人脸网格的一小部分时间内返回 6 个标志。它甚至适用于多张脸!如果您需要低延迟的人脸检测,这是您的解决方案。

手跟踪

虽然 MediaPipe Pose 只提供了一个带有两个标志的手的粗略模型,只是为了测量方向, MediaPipe Hands 提供了一个更详细的手模型。双手识别二十一个 3D 地标,甚至可以为多只手工作。这个解决方案有大量的应用,包括像手语识别这样的无障碍解决方案。

Hand Tracking with MediaPipe (darker points are further away)

整体跟踪

MediaPipe 整体是最重的解剖型 MediaPipe 模型,将手、姿势和面部网格结合成一个解决方案。虽然肯定不是一个轻量级的解决方案,但整体提供了一个人在给定时间的非常详细的快照。就像之前的面部网格一样,整体可以用于低成本的运动捕捉。

下面你可以看到通过BlendArMocap制作的全身装备动画。我几乎不能被认为是一个搅拌机初学者,因此没有足够的技巧来平滑身体动画。即使这样,头部/面部运动,尤其是手部运动看起来非常自然,对于仅在带有简单网络摄像头的笔记本电脑上工作来说,这是令人印象深刻的结果。

https://www.assemblyai.com/blog/content/media/2022/04/holistic_blender.mp4

如上所述,您最终可以使用这些 2D 视频数据来驱动 3D 角色模型。这是某人在笔记本电脑上使用 Blender 累计 4 小时的结果:

https://www.assemblyai.com/blog/content/media/2022/03/0001-0250.mp4

虽然肯定不是完美的,但是结果可以作为改进的基线。MediaPipe 中的腿部跟踪可能很挑剔,您可能已经从上面从行走的人的视频中提取姿势信息的例子中注意到了。由此产生的误差会在模型中传播,所以对于那些有兴趣进一步研究这个概念的人来说,专注于解决这个问题是一个很好的起点。请注意,BlendArMocap 是一个开源项目,因此可以探索这种改进(尽管真正的力量最终来自于 MediaPipe,因此受到了 media pipe/硬件的瓶颈限制)。

自拍分割

MediaPipe 自拍分割分割图像/视频中最前面的人,允许如下图所示的虚拟背景或变焦背景。该解决方案适用于智能手机和笔记本电脑。

https://www.assemblyai.com/blog/content/media/2022/04/selfie_seg-2.mp4

Selfie Segmentation with MediaPipe

Objectron

MediaPipe Objectron 是一款实时 3D 物体检测解决方案,适用于日常物体,可以在移动平台上运行。Objectron 既能检测物体,又能估计它们的姿态。

Object Tracking with MediaPipe

非 Python 媒体管道解决方案

如前所述,MediaPipe 有许多其他解决方案,在 Android、iOS、C++、JavaScript 和 Coral 上有不同的可用性。

彩虹女神

MediaPipe Iris 是一种用于精确虹膜估计的解决方案,仅用一个摄像头就能跟踪虹膜、瞳孔和眼睛轮廓标志。由于可变的光线条件和遮挡,这项任务通常很难完成,尤其是在智能手机这样的有限计算环境中。MediaPipe Iris 提供了克服这些障碍的解决方案。

Iris detection with MediaPipe

头发分割

MediaPipe 头发分割就像自拍分割一样,只不过它只分割一个人的头发。这种解决方案可用于支持 SnapChat Lens 等增强现实应用。

Hair Segmentation with MediaPipe

目标检测

MediaPipe 对象检测类似于 Objectron,除了面部不做姿态估计,而是只识别边界框。这种解决方案更加轻量级,因此对于不需要对象姿态的应用程序是首选。

Object Detection with MediaPipe

盒子跟踪

媒体管道框跟踪类似于对象检测,除了它提供了更强大的边界框功能。框跟踪记录带有时间戳的边界框。

Box Tracking with MediaPipe

即时运动跟踪

MediaPipe 即时运动跟踪对于增强现实应用很有价值,低成本 VFX 的用途也很明显。运动跟踪实际上是建立在盒子跟踪之上的。

Instant Motion Tracking with MediaPipe - source

MediaPipe KNIFT ,或关键点神经不变特征变换,是一种基于模板的特征匹配解决方案。能够从不同的角度理解空间关系是一个困难的问题,也是各种应用的基础。KNIFT 是解决这一问题的通用解决方案,对光照、方向和缩放的变化具有鲁棒性。

Results of MediaPipe's KNIFT - source

自动滑动

auto slip是一个简单的解决方案,允许将视频智能裁剪为任意纵横比。

Results of MediaPipe's AutoFlip - source

最后的话

MediaPipe 是一个非常强大的工具,可以帮助开发基于大量机器学习功能的应用程序。MediaPipe 极其简单的 API 提供了对几种先进(SOTA)模型的访问,这些模型是专门针对移动计算的硬件限制而构建的。

虽然 MediaPipe 为计算机视觉和视觉智能提供了许多出色的解决方案,但今天有许多应用程序依赖于音频智能。AssemblyAI 易于使用的 API 提供了对一系列音频智能 SOTA 模型的访问,就像 MediaPipe 提供了对 SOTA 视觉智能模型的访问一样。获取下面的 API 令牌,免费访问大量用于情感分析、实体检测、PII 编辑等的音频智能解决方案。

Get a Free API Token

MinImagen -构建你自己的图像文本到图像模型

原文:https://www.assemblyai.com/blog/minimagen-build-your-own-imagen-text-to-image-model/

DALL-E 2 于今年早些时候发布,凭借其令人印象深刻的文本到图像的功能风靡全球。只有一个场景的输入描述,DALL-E 2 输出场景的现实和语义上似乎合理的图像,就像你可以在下面看到的从输入标题“一碗汤,这是通往另一个维度的门户,作为数字艺术】:

(source)

就在 DALL-E 2 发布一个月后,谷歌宣布了一款竞争机型 Imagen 被发现比 DALL-E 2 还要好的。以下是一些示例图像:

"A dragon fruit wearing karate belt in the snow" and "a photo of a Corgi dog riding a bike in Times Square. It is wearing sunglasses and a beach hat" (source).

无论是 DALL-E 2 还是 Imagen 的骄人成绩,都依赖于前沿的深度学习研究。虽然对于获得最先进的结果来说是必要的,但在 Imagen 等模型中使用这种前沿研究使非专业研究人员更难理解它们,从而阻碍了这些模型和技术的广泛采用。

因此,本着民主化的精神,我们将在本文中学习如何用 PyTorch 构建 Imagen。特别是,我们将构建一个 Imagen 的最小实现——称为MinImagen——它隔离了 Imagen 的显著特征,这样我们就可以专注于理解 Imagen 的整体操作原理,将重要的实现方面与附带的实现方面分开。

包装说明

注意:如果你对实现细节不感兴趣,只想使用 MinImagen,它已经打包好了,可以安装在

pip install minimagen

查看下面的部分或相应的 GitHub 库获取使用提示。文档包含了关于使用软件包的更多细节和信息。

介绍

文本到图像模型在过去几年中取得了长足的进步,GLIDE、DALL-E 2、Imagen 等模型就是证明。这些进步在很大程度上是由于最近对扩散模型的研究热潮,这是一种新的生成模型范式/框架。

虽然有一些关于扩散模型和文本到图像模型的理论方面的好资源,但是关于如何实际构建这些模型的实用信息并不丰富。对于将扩散模型仅作为一个更大系统的组件的模型来说尤其如此,这在文本到图像模型中很常见,如 DALL-E 2 中的编码优先生成器链,或 Imagen 中的超分辨率链

MinImagen 剥离了当前最佳实践的华而不实之处,以便出于教育目的隔离 Imagen 的显著特征。本文的其余部分结构如下:

  1. Imagen/扩散模型的回顾:为了在我们开始编码之前定位自己,我们将简要回顾 Imagen 本身和更一般的扩散模型。这些评论只是作为复习资料,所以在阅读复习资料时,您应该已经对这两个主题有了实际的理解。你可以查看我们的机器学习扩散模型介绍和我们的Imagen 如何实际工作的专用指南来了解更多信息。
  2. 构建扩散模型:在我们的回顾之后,我们将首先在 PyTorch 中构建[GaussianDiffusion](https://github.com/AssemblyAI-Examples/MinImagen/blob/ad5fab36e810ed0c4c3cba7ba9df2c892d7ef2c4/minimagen/diffusion_model.py#L8)类,它定义了 Imagen 中使用的扩散模型。
  3. 构建去噪 U 网:然后我们将构建扩散模型所依赖的去噪 U 网,在[Unet](https://github.com/AssemblyAI-Examples/MinImagen/blob/ad5fab36e810ed0c4c3cba7ba9df2c892d7ef2c4/minimagen/Unet.py#L25)类中显示。
  4. 构建 MinImagen: 接下来,我们将使用 T5 文本编码器和扩散模型链将所有这些片段放在一起,以构建我们的(Min)Imagen 类,[Imagen](https://github.com/AssemblyAI-Examples/MinImagen/blob/ad5fab36e810ed0c4c3cba7ba9df2c892d7ef2c4/minimagen/Imagen.py#L22)
  5. 使用 MinImagen: 最后,一旦 Imagen 被完全定义,我们将学习如何从 Imagen 进行训练和采样。

模型重量

敬请期待!我们将在未来几周训练 MinImagen,并发布一个检查点,以便您可以生成自己的图像。确保关注我们的新闻简报以了解我们发布的最新内容。

事不宜迟,是时候进入 Imagen 和扩散模型的概述了。如果您已经从理论角度熟悉了 Imagen 和扩散模型,并希望跳到 PyTorch 实现细节,请单击此处的。

Imagen 是什么?

Imagen 是 Google 几个月前发布的文本到图像模型。它接收一个文本提示并输出一个图像,该图像反映了包含在提示中的语义信息。

为了生成图像,Imagen 首先使用一个文本编码器来生成提示的代表性编码。接下来,一个以编码为条件的图像生成器,从高斯噪声(“电视静态”)开始,逐步去噪,生成一个反映字幕描述场景的小图像。最后,两个超分辨率模型依次将图像放大到更高的分辨率,再次以编码信息为条件。

文本编码器是一个预训练的 T5 文本编码器,在训练过程中被冻结。基本图像生成模型和超分辨率模型都是扩散模型

想要更详细地了解 Imagen 的工作原理吗?

查看我们的专用文章,深入了解 Imagen。

Check it Out

什么是扩散模型?

扩散模型是一类生成模型,这意味着它们用于生成新颖的数据,通常是图像。扩散模型通过在一系列时间步中用高斯噪声破坏训练图像来训练,然后学习撤销这个噪声处理。

特别地,模型被训练来预测给定时间步长的图像的噪声分量。

一旦经过训练,该去噪模型就可以迭代地应用于随机采样的高斯噪声,对其进行“去噪”以生成新的图像。

扩散模型构成了一种元模型,它协调了另一个模型- 噪声预测模型的训练。因此,我们仍然需要决定实际使用哪种类型的模型来进行噪声预测。一般来说,U 网都是选这个角色的。Imagen 中的 U-Net 具有这样的结构:

该架构基于扩散模型击败图像合成论文中的模型。对于 MinImagen ,我们对这个架构做了一些小改动,包括

  1. 移除全局注意力层(未图示),
  2. 用变压器编码器替换注意层,以及
  3. 将变换编码器放置在每层序列的末尾,而不是在残差块之间,以便允许可变数量的残差块。

想要更详细地了解扩散模型是如何工作的吗?

查看我们的专用文章,深入了解扩散模型。

Check it Out

在 PyTorch 中构建您自己的图像

随着我们的 Imagen/扩散模型概述的完成,我们终于准备好开始构建我们的 Imagen 实施。首先,打开一个终端,克隆项目库:

git clone https://github.com/AssemblyAI-Examples/MinImagen.git

在本教程中,我们将分离出与 Imagen 实现本身相关的源代码的重要部分,省略参数验证、设备处理等细节。即使是 Imagen 的最小实现也相对较大,因此为了隔离指导性信息,这种方法是必要的。 MinImagen 的源代码被彻底注释(相关文档在此),所以关于任何省略细节的信息应该很容易找到。

该项目的每一个大的组成部分——扩散模型、去噪 U-Net 和 Imagen——都被放在下面它自己的部分。我们将从构建[GaussianDiffusion](https://assemblyai-examples.github.io/MinImagen/minimagen.html#minimagen.diffusion_model.GaussianDiffusion)类开始。

归属注释

这个实现在很大程度上是王飞 Imagen 实现的简化版本,你可以在 GitHub 这里找到。

建立扩散模型

扩散模型[GaussianDiffusion](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L8)类可以在[minimagen.diffusion_model](https://github.com/AssemblyAI-Examples/MinImagen/blob/main/minimagen/diffusion_model.py)中找到。点击此处,跳转到本节的摘要。

初始化

GaussianDiffusion初始化函数只有一个参数——扩散过程中的时间步数。

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	super().__init__()

首先,扩散模型需要一个方差表,它指定了在扩散过程中给定时间步长添加到图像的高斯噪声的方差。变更进度应该是增加的,但是在如何定义这个进度上有一些灵活性。出于我们的目的,我们实现了来自原始去噪扩散概率模型 (DDPM)论文的方差调度,其是从 t=0 时的 0.0001 到 t=T 时的 0.02 的线性间隔调度。

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	super().__init__()

        self.num_timesteps = timesteps

        scale = 1000 / timesteps
        beta_start = scale * 0.0001
        beta_end = scale * 0.02
        betas = torch.linspace(beta_start, beta_end, timesteps, dtype=torch.float64)

根据这个时间表,我们计算了几个值(在 DDPM 论文中也有详细说明),这些值将在以后的计算中使用:

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	# ...

        alphas = 1\. - betas
        alphas_cumprod = torch.cumprod(alphas, axis=0)
        alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.)

初始化函数的其余部分将上述值和一些派生值注册为缓冲区,这些值类似于参数,只是它们不需要梯度所有的值最终都是从差异表中得到的,它们的存在是为了使一些计算更加简单和清晰。计算派生值的细节并不重要,但是我们将在下面指出任何时候使用这些派生值中的一个。

正向扩散过程

现在我们可以继续定义GaussianDiffusion[q_sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L125)方法,它负责正向扩散过程。给定一个输入图像 x_0 ,我们通过从下面的分布中采样,将其噪声化到扩散过程中给定的时间步长 t :

(source)

从上面的分布中取样等同于下面的计算,这里我们突出显示了在__init__中定义的中的两个。

See the "mathematical note" dropdown here for details on this equivalence

也就是说,在时间 t 的图像的噪声版本可以通过简单地将噪声添加到图像来采样,其中原始图像和噪声都由时间步长所规定的它们各自的系数来缩放。现在让我们通过将方法[q_sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L125)添加到GaussianDiffusion类来在 PyTorch 中实现这个计算:

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	# ...

    def q_sample(self, x_start, t, noise=None):
        noise = default(noise, lambda: torch.randn_like(x_start))

        noised = (
                extract(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start +
                extract(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise
        )

        return noised

x_start是形状为(b, c, h, w)的 PyTorch 张量,t是形状为(b,)的 PyTorch 张量,它为每幅图像给出了我们想要对每幅图像进行噪声处理的时间步长,noise允许我们可选地提供定制噪声,而不是样本高斯噪声。

我们简单地执行并返回上述等式中的计算,使用来自上述缓冲器的元素作为系数。当None被提供时,[default](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/helpers.py#L25)函数对随机高斯噪声进行采样,[extract](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/helpers.py#L56)根据t从缓冲器中提取合适的值。

反向扩散过程

最终,我们的目标是从这个分布中取样:

(source)

给定一幅图像和它的噪声对应物,这种分布告诉我们如何在扩散过程中“及时返回”一步,稍微去噪噪声图像。与上面类似,从这个分布中取样相当于计算

为了进行这种计算,我们需要分布的均值和方差。差异是差异计划的确定性函数:

(source)

另一方面,均值取决于原始图像和噪声图像(尽管系数也是方差表的确定性函数)。平均的形式是:

(source)

照此推论,我们就不会有 x_0 “原始形象”,因为这正是我们试图生成的(一个小说形象)。这就是我们的可训练 U-Net 进入画面的地方——我们用它从 x_t 预测 x_0

在实践中,当 U-Net 学习预测图像的噪声分量时,可以看到更好的结果,由此我们可以计算x0。一旦我们有了 x_0 ,我们就可以用上面的公式计算分布均值,给出我们需要从后验样本中采样的内容(即,将图像去噪回一个时间步长)。从视觉上看,整个过程如下所示:

从后面(图中的绿色块)采样的函数将在[Imagen](https://assemblyai-examples.github.io/MinImagen/minimagen.html#minimagen.Imagen.Imagen)类中定义,但我们现在将定义其余两个函数。首先,我们实现了计算 x_0 的函数,给出了噪声图像及其噪声分量(图中的红色块)。从上面,我们有:

重新排列它以隔离 x_0 产生了下面的结果,其中两个缓冲器再次被突出显示。

也就是说,为了计算 x_0 ,我们简单地从 x_t 中减去噪声(由 U-Net 预测),其中噪声图像和噪声本身都由时间步长所规定的各自的系数来缩放。现在让我们在 PyTorch 中实现这个函数[predict_start_from_noise](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L147):

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	# ...

    def q_sample(self, x_start, t, noise=None):
        # ...

    def predict_start_from_noise(self, x_t, t, noise):
        return (
                extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t -
                extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise
        )

现在我们有了一个计算 x_0 的函数,我们可以回过头来计算后验均值和方差(图中的黄色块)。我们在下面重复上面的功能定义,根据需要突出显示在_init__中定义的缓冲器

让我们在 PyTorch 中实现一个函数[q_posterior](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L87)来计算这些变量:

class GaussianDiffusion(nn.Module):

    def __init__(self, *, timesteps: int):
    	# ...

    def q_sample(self, x_start, t, noise=None):
        # ...

    def predict_start_from_noise(self, x_t, t, noise):
        return (
                extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t -
                extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise
        )

    def q_posterior(self, x_start: torch.tensor, x_t: torch.tensor, t: torch.tensor, **kwargs):
        posterior_mean = (
                extract(self.posterior_mean_coef1, t, x_t.shape) * x_start +
                extract(self.posterior_mean_coef2, t, x_t.shape) * x_t
        )
        posterior_variance = extract(self.posterior_variance, t, x_t.shape)
        posterior_log_variance_clipped = extract(self.posterior_log_variance_clipped, t, x_t.shape)
        return posterior_mean, posterior_variance, posterior_log_variance_clipped

在实践中,我们同时返回方差和方差的对数(posterior_log_variance_clipped,其中“clipped”意味着我们在取对数之前将值从 0 推到 1e-20)。使用方差对数的原因是我们计算中的数值稳定性,我们将在后面指出这一点。

摘要

概括地说,在本节中,我们定义了[GaussianDiffusion](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L8)类,它负责定义扩散过程操作。我们首先实现了[q_sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L125),它执行前向扩散过程,在扩散过程中将图像噪声化到给定的时间步长。我们还实现了[predict_start_from_noise](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L147)[q_posterior](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/diffusion_model.py#L87),它们用于计算在反向扩散 过程中使用的参数。

建立噪声预测模型

现在是时候给我们的噪音预测模型——U-Net 去噪了。这个模型相当复杂,所以为了简洁起见,我们将检查它的向前传递,在相关的地方引入相关的对象。只研究正向传递将有助于我们理解 U-Net 是如何操作的,同时忽略对我们学习如何构建 Imagen 没有指导意义的不必要的细节。

U-Net 类[Unet](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/Unet.py#L25)可以在[minimagen.Unet](https://github.com/AssemblyAI-Examples/MinImagen/blob/main/minimagen/Unet.py)中找到。点击此处,跳转到本节的摘要。

概观

回想一下,Imagen 的 U-Net 架构类似于下图所示。我们做了一些修改,最明显的是将注意块(对我们来说是一个转换器编码器)放在 U-Net 中每一层的处。

生成时间调节向量

请记住,我们的 U-Net 是一个条件模型,这意味着它取决于我们输入的文本标题。没有这种调节,就没有办法告诉模型我们想要在生成的图像中呈现什么。此外,由于我们对所有时间步长使用相同的 U-Net,我们需要以时间步长信息为条件,以便模型知道在任何给定时间应该消除多大的噪声(记住,我们的方差表随着 t 而变化)。现在让我们看看我们是如何产生这个时间调节信号的。这些计算的可以在本节末尾看到。

作为模型的输入,我们接收形状为(b,)时间向量,它为批中的每个图像提供时间步长。我们首先将这个向量通过一个模块,该模块从它们中生成隐藏状态:

class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...

        self.to_time_hiddens = nn.Sequential(
            SinusoidalPosEmb(dim),
            nn.Linear(dim, time_cond_dim),
            nn.SiLU()
        )

    def forward(self, *args, **kwargs):

    	time_hiddens = self.to_time_hiddens(time)

首先,每次生成唯一的位置编码向量(SinusoidalPostEmb()),它将给定图像的时间步长的整数值映射成我们可以用于时间步长调节的代表性向量。关于位置编码的回顾,请参见这里的下拉列表。接下来,这些编码被投影到更高维度的空间(time_cond_dim),并通过SiLU非线性。结果是一个大小为(b, time_cond_dim)的张量,它构成了时间步长的隐藏状态。

这些隐藏状态然后以两种方式用于。首先,生成一个时间调节张量t,我们将使用它在 U-Net 中的每一步提供时间步长调节。这些是用简单的线性层从time_hiddens生成的。第二,用一个简单的线性层从time_hiddens再次生成时间标记 time_tokens,这些标记被连接到我们稍后将生成的主要文本条件标记。我们有这两种用途的原因是因为时间调节必须在 U-Net 中的任何地方提供 (通过简单的加法),而主调节令牌仅在 U-Net 的特定块/层中的交叉注意操作中使用。让我们看看如何在 PyTorch 中实现这些功能:****

`class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...

        self.to_time_cond = nn.Sequential(
            nn.Linear(time_cond_dim, time_cond_dim)
        )

        self.to_time_tokens = nn.Sequential(
            nn.Linear(time_cond_dim, cond_dim * NUM_TIME_TOKENS),
            Rearrange('b (r d) -> b r d', r=NUM_TIME_TOKENS)
        )

    def forward(self, *args, **kwargs):
    	# ...
        t = self.to_time_cond(time_hiddens)
        time_tokens = self.to_time_tokens(time_hiddens)`

t的形状为(b, time_cond_dim),与time_hiddens相同。time_tokens的形状是(b, NUM_TIME_TOKENS, cond_dim),其中NUM_TIME_TOKENS定义了应该生成多少个时间标记,这些时间标记将连接在主条件文本标记上。默认值为 2。 einops 重排层将张量从(b, NUM_TIME_TOKENS*cond_dim)重塑为(b, NUM_TIME_TOKENS, cond_dim)

下图总结了时间编码过程:

生成文本条件向量

现在是时候生成我们的文本条件 对象了。从我们的文本编码器中,我们有两个张量-批处理字幕的文本嵌入text_embeds,以及文本掩码text_mask,它告诉我们批处理中每个字幕中有多少单词。这些张量分别为(b, max_words, enc_dim)(b, max_words)大小,其中max_words为该批最长字幕中的字数,enc_dim为文本编码器的编码维数。

在这一点上,我们还结合了无分类器制导;因此,给定所有的移动部件,让我们看一个可视化的例子来理解在高层次上发生了什么。下面的再次总结了所有的计算。

视觉示例

让我们假设我们有三个标题- ' 一个非常大的红色房子'、一个男人'和'一只快乐的狗。我们的文本编码器提供以下功能:

我们将嵌入向量投影到更高的维度(更大的水平宽度),并将掩码和嵌入张量(垂直方向上的额外条目)填充到标题中允许的最大字数,一个我们选择的值,此处设为 6:

从这里开始,我们通过随机决定以固定概率丢弃哪些批次实例来合并 无分类器指导 。让我们假设最后一个实例被删除,这是通过改变文本掩码实现的。

继续进行无分类器引导,我们生成空向量用于丢弃的元素。

我们将文本掩码为红色的地方的编码替换为 NULL:

为了获得最终的主条件记号c,我们简单地将上面生成的time_tokens连接到这些文本条件张量。沿着 num_tokens/word 维度发生连接,以留下形状(b, NUM_TIME_TOKENS + max_text_len, cond_dim)的最终主要条件表征。

最后,我们还指跨越单词维度的池,以获取形状为(b, cond_dim)的张量,然后投影到时间条件向量维度,以产生形状为(b, 4*cond_dim)的张量。在根据无分类器指导向量沿着批维度丢弃必要的实例之后,我们将** this 添加到t以获得最终的时间步长调节t。**

对应代码

这些操作对应的代码有点繁琐,只是重复/实现了上面的过程,这里就省略代码了。请随意查看源代码中的[Unet._text_condition](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/Unet.py#L536)方法,探索这个函数是如何实现的。下图总结了整个条件生成过程,因此可以在新标签中随意打开这张图片,并在浏览代码时直观地跟随,以便保持方向感。

**

(this image is compressed - see a full resolution version here)**

构建 U-Net

现在我们有了我们需要的两个条件张量——通过注意力应用的主条件张量c和通过加法应用的时间条件张量t——我们可以继续定义 U-Net 本身。如上所述,我们继续检查Unet[forward](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/Unet.py#L354)方法,根据需要在__init__中引入对象。

初始卷积

首先,我们需要执行一个初始卷积,以将我们的输入图像转换为网络的预期通道数。我们利用[minimagen.layers.CrossEmbedLayer](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L254),它本质上是一个初始层。

`class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.init_conv = CrossEmbedLayer(channels, dim_out=dim, kernel_sizes=(3, 7, 15), stride=1)

    def forward(self, *args, **kwargs):
    	# ...
        x = self.init_conv(x)`

初始 ResNet 块

接下来,我们将图像传递到 U-Net 这一层的初始 ResNet 块([minimagen.layers.ResnetBlock](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L371)),称为[init_block](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/Unet.py#L418)

`class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.init_block = ResnetBlock(current_dim, current_dim, cond_dim=layer_cond_dim, time_cond_dim=time_cond_dim, groups=groups)

    def forward(self, *args, **kwargs):
    	# ...
        x = init_block(x, t, c)`

ResNet 块首先通过初始的[block1](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L412) ( [minimagen.layers.block](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L107))传递图像,产生与输入大小相同的输出张量。

接下来,利用主条件标记执行剩余交叉注意([minimagen.layers.CrossAttention](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L180))

之后,我们将时间编码通过一个简单的 MLP 来获得合适的维度,然后将它分成两个大小的(b, c, 1, 1)张量。

我们最后将图像通过另一个与block1相同的卷积模块,除了它通过使用时间步长嵌入的缩放移位来合并时间步长信息。

init_block的最终输出与输入张量具有相同的形状。

剩余的 ResNet 块

接下来,我们让图像通过一系列与init_block相同的 ResNet 块,除了它们只在时间步长上进行调节。我们将输出保存在[hiddens](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/Unet.py#L408)中,以便稍后跳过连接。

`class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.resnet_blocks = nn.ModuleList(
                    [
                        ResnetBlock(current_dim, current_dim, time_cond_dim=time_cond_dim, groups=groups)
                        for _ in range(layer_num_resnet_blocks)
                    ]

    def forward(self, *args, **kwargs):
    	# ...
        hiddens = []
        for resnet_block in self.resnet_blocks:
                x = resnet_block(x, t)
                hiddens.append(x)`

最终变压器块

在使用 ResNet 块进行处理之后,我们可以选择将图像通过一个转换器编码器 ( [minimagen.layers.TransformerBlock](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L468))。

`class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.transformer_block = TransformerBlock(dim=current_dim, heads=ATTN_HEADS, dim_head=ATTN_DIM_HEAD)

    def forward(self, *args, **kwargs):
    	# ...
        x = self.transformer_block(x)
        hiddens.append(x)`

transformer 块应用多头注意力(下面的紫色块),然后通过一个[minimagen.layers.ChanFeedForward](https://github.com/AssemblyAI-Examples/MinImagen/blob/0f305c29922274e1faefe9e93be441fdb7ed0efa/minimagen/layers.py#L148)层传递输出,这是一系列卷积,它们之间有层规范,它们之间有 GeLU。

**详图

** **#### 向下采样

作为 U 网这一层的最后一步,图像被下采样到空间宽度的一半。

class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.post_downsample = Downsample(current_dim, dim_out)

    def forward(self, *args, **kwargs):
    	# ...
        x = post_downsample(x)

其中下采样操作是简单的固定卷积。

def Downsample(dim, dim_out=None):
    dim_out = default(dim_out, dim)
    return nn.Conv2d(dim, dim_out, kernel_size=4, stride=2, padding=1)

中间层

对 U 网的每一层重复上述 ResNet 块、(可能的)变换器编码器和下采样序列,直到我们达到最低空间分辨率/最大通道深度。在这一点上,我们通过另外两个 Resnet 块传递图像,这两个 ResNet 块在主条件令牌上做条件(就像每个 ResNet 层的init_block)。可选地,我们通过这些块之间的剩余注意力层传递图像。

class Unet(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
                self.mid_block1 = ResnetBlock(mid_dim, mid_dim, cond_dim=cond_dim, time_cond_dim=time_cond_dim,
                                      groups=resnet_groups[-1])
        self.mid_attn = EinopsToAndFrom('b c h w', 'b (h w) c',
                                        Residual(Attention(mid_dim, heads=ATTN_HEADS,
                                                           dim_head=ATTN_DIM_HEAD))) if attend_at_middle else None
        self.mid_block2 = ResnetBlock(mid_dim, mid_dim, cond_dim=cond_dim, time_cond_dim=time_cond_dim,
                                      groups=resnet_groups[-1])

    def forward(self, *args, **kwargs):
    	# ...
        x = self.mid_block1(x, t, c)
        if exists(self.mid_attn):
            x = self.mid_attn(x)
        x = self.mid_block2(x, t, c)

上采样轨迹

U-Net 的上采样轨迹很大程度上是下采样轨迹的镜像反转,除了我们(a) 在任何给定层的每个 resnet 块之前连接来自下采样轨迹的相应跳跃连接,以及(b)我们使用上采样操作而不是下采样操作。这种上采样操作是最近邻上采样,之后是空间尺寸保持卷积

def Upsample(dim, dim_out=None):
    dim_out = default(dim_out, dim)

    return nn.Sequential(
        nn.Upsample(scale_factor=2, mode='nearest'),
        nn.Conv2d(dim, dim_out, 3, padding=1)
    ) 

为了简洁起见,这里不再重复上采样轨迹代码,但是可以在源代码中找到

在上采样轨迹结束时,最终卷积层将图像带到适当的输出通道深度(通常为 3)。

摘要

概括地说,在本节中,我们定义了[Unet](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Unet.py#L25)类,它负责定义通过扩散训练的去噪 U-Net。我们首先学习了如何为给定的时间步长和标题生成条件张量,然后将此条件信息合并到 U-Net 的正向传递,该传递通过一系列 ResNet 模块变压器编码器发送图像,以便预测给定图像的噪声成分

建筑图像

概括地说,我们已经构建了一个定义和实现扩散过程“元模型”的[GaussianDiffusion](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/diffusion_model.py#L8)对象,它反过来利用我们的[Unet](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Unet.py#L25)类进行训练。现在让我们来看看我们是如何将这些部分组合在一起构建 Imagen 本身的。我们将再次查看 Imagen 中的两个主要功能- [forward](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L575)用于训练,[sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L424)用于图像生成,再次根据需要在__init__中引入对象。

[minimagen.Imagen](https://github.com/AssemblyAI-Examples/MinImagen/blob/main/minimagen/Imagen.py)中可以找到[Imagen](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Imagen.py#L22)类。点击此处,跳转到本节的摘要。

Imagen 向前传球

Imagen 前向传递包括(1)对训练图像加噪声,(2)用 U-Net 预测噪声分量,然后(3)返回预测噪声和真实噪声之间的损失。

首先,我们随机采样时间步长以对训练图像进行噪声处理,然后对条件文本进行编码,将嵌入和遮罩放置在与输入图像张量相同的设备上:

from minimagen.t5 import t5_encode_text

class Imagen(nn.Module):
    def __init__(self, timesteps):
        self.noise_scheduler = GaussianDiffusion(timesteps=timesteps)
        self.text_encoder_name = 't5_small'

    def forward(self, images, texts):
        times = self.noise_scheduler.sample_random_times(b, device=device)

        text_embeds, text_masks = t5_encode_text(texts, name=self.text_encoder_name)
        text_embeds, text_masks = map(lambda t: t.to(images.device), (text_embeds, text_masks))

回想一下,Imagen 有一个生成小图像的基础模型和放大图像的超分辨率模型。因此,我们需要调整图像的大小,以适合正在使用的 U-Net。如果 U-Net 是一个超分辨率模型,我们还需要将训练图像首先缩小到低分辨率条件尺寸,然后放大到 U-Net 的适当尺寸。这模拟了在 Imagen 的超分辨率链中将一个 U-Net 的输出上采样到下一个 U-Net 的输入大小(允许后一个 U-Net 以前一个 U-Net 的输出为条件)。

我们还向低分辨率调节图像添加噪声,用于噪声调节增强,为整批图像选取一个噪声水平。

#...
from minimagen.helpers import resize_image_to
from einops import repeat

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...
        self.lowres_noise_schedule = GaussianDiffusion(timesteps=timesteps)

    def forward(self, images, texts):
    	# ...
        lowres_cond_img = lowres_aug_times = None
        if exists(prev_image_size):
            lowres_cond_img = resize_image_to(images, prev_image_size, pad_mode='reflect')
            lowres_cond_img = resize_image_to(lowres_cond_img, target_image_size, 
            					pad_mode='reflect')

            lowres_aug_time = self.lowres_noise_schedule.sample_random_times(1, device=device)
            lowres_aug_times = repeat(lowres_aug_time, '1 -> b', b=b)

        images = resize_image_to(images, target_image_size)

最后,我们计算并返回损失:

#...
from minimagen.helpers import resize_image_to
from einops import repeat

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...

    def forward(self, images, texts, unet):
    	# ...
        return self._p_losses(unet, images, times, text_embeds=text_embeds, 
        			text_mask=text_masks, 
                            	lowres_cond_img=lowres_cond_img, 
                            	lowres_aug_times=lowres_aug_times)

让我们来看看[_p_losses](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L512)看看我们是如何计算损失的。

首先,我们使用扩散模型正向过程对输入图像和低分辨率调节图像(如果 U-Net 是超分辨率模型)进行去噪。

#...

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...

    def p_losses(self, x_start, times, lowres_cond_img=None):
    	# ...
        noise = torch.randn_like(x_start)

        x_noisy = self.noise_scheduler.q_sample(x_start=x_start, t=times, noise=noise)

        lowres_cond_img_noisy = None
        if exists(lowres_cond_img):
            lowres_aug_times = default(lowres_aug_times, times)
            lowres_cond_img_noisy = self.lowres_noise_schedule.q_sample(
                            		x_start=lowres_cond_img, t=lowres_aug_times, 
                        		noise=torch.randn_like(lowres_cond_img))

接下来,我们使用 U-Net 来预测噪声图像的噪声成分,如果 U-Net 用于超分辨率,则除了低分辨率图像之外,还将文本嵌入作为调节信息。 cond_drop_prob 给出了无分类器引导的退出概率。

#...

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...
        self.cond_drop_prob = 0.1

    def p_losses(self, x_start, times, text_embeds, text_mask, lowres_cond_img=None):
    	# ...
        pred = unet.forward(
            x_noisy,
            times,
            text_embeds=text_embeds,
            text_mask=text_mask,
            lowres_noise_times=lowres_aug_times,
            lowres_cond_img=lowres_cond_img_noisy,
            cond_drop_prob=self.cond_drop_prob,
        )

然后,我们根据[self.loss_fn](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L67)计算实际增加的噪声和 U-Net 预测的噪声之间的损耗,默认为 L2 损耗。

#...

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...
        self.loss_fn = torch.nn.functional.mse_loss

    def p_losses(self, x_start, times, text_embeds, text_mask, lowres_cond_img=None):
    	# ...
        return self.loss_fn(pred, noise) 

这就是用 Imagen 得到损失的全部代价!一旦我们建立了扩散模型/U-Net 主干,这是一个相当简单的过程。

使用 Imagen 采样

最终,我们想要做的是用 Imagen 进行采样。也就是说,我们希望能够生成带有文本标题的新颖图像。回想一下上面的内容,这需要计算正向过程的后验均值:

既然我们已经定义了预测噪声分量的 U 网,我们就有了计算后验均值所需的所有数据。

首先,我们使用 U-Net 的[forward](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Unet.py#L354)(或[forward_with_cond_scale](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Unet.py#L473))方法获得噪声预测(蓝色),然后使用之前介绍的 U-Net 的[predict_start_from_noise](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/diffusion_model.py#L147)方法计算 x_0 (红色),该方法执行以下计算:

其中 x_t 是噪声图像,ε是 U-Net 的噪声预测。接下来, x_0动态阈值化,然后与 x_t 一起传递到 U-Net(黄色)的 into[q_posterior](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/diffusion_model.py#L87)方法中,以获得分布平均值。

这整个过程都包含在Imagen[_p_mean_variance](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L261)函数中。

#...

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...
        self.dynamic_thresholding_percentile = 0.9

    def _p_mean_variance(self, unet, x, t, *, noise_scheduler 
    			text_embeds=None, text_mask=None):

        # Get the noise prediction from the unet (blue block)
        pred = unet.forward_with_cond_scale(x, t, text_embeds=text_embeds, text_mask=text_mask)

        # Calculate the starting images from the noise (yellow block)
        x_start = noise_scheduler.predict_start_from_noise(x, t=t, 

        # Dynamically threshold
        s = torch.quantile(
            rearrange(x_start, 'b ... -> b (...)').abs(),
            self.dynamic_thresholding_percentile,
            dim=-1
        )

        s.clamp_(min=1.)
        s = right_pad_dims_to(x_start, s)
        x_start = x_start.clamp(-s, s) / s

        # Return the forward process posterior parameters (green block)
        return noise_scheduler.q_posterior(x_start=x_start, x_t=x, t=t, t_next=t_next)

从这里,我们有了从后验样本采样所需的一切,也就是说在扩散过程中“返回一个时间步长”。也就是说,我们试图从以下分布中取样:

我们在上面看到,从这个分布中取样相当于计算

由于我们用[_p_mean_variance](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L261)计算了后验均值和(对数)方差,我们现在可以用[_p_sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L329)实现上面的计算,计算方差的平方根以保证数值稳定性。

class Imagen(nn.Module):
    def __init__(self, timesteps):
    	# ...
        self.dynamic_thresholding_percentile = 0.9

    @torch.no_grad()
    def _p_sample(self, unet, x, t, *, text_embeds=None, text_mask=None):

        b, *_, device = *x.shape, x.device

        # Get posterior parameters
        model_mean, _, model_log_variance = self.p_mean_variance(unet, x=x, t=t, 
        					text_embeds=text_embeds, text_mask=text_mask)

        # Get noise which we will use to calculate the denoised image
        noise = torch.randn_like(x)

        # No more denoising when t == 0
        is_last_sampling_timestep = (t == 0)
        nonzero_mask = (1 - is_last_sampling_timestep.float()).reshape(b, 
        							*((1,) * (len(x.shape) - 1)))

        # Get the denoised image. Equivalent to mean * sqrt(variance) but calculate this way to be more numerically stable
        return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise 

此时,我们已经对 Imagen 中的随机噪声输入进行了一个时间步长的降噪处理。为了生成图像,我们需要在每个时间步长为做这件事,从在 t=T 随机采样的高斯噪声开始,并“及时返回”直到我们到达 t=0 。因此,我们使用[_p_sample_loop](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L373)在时间步长上循环运行[_p_sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L329):

class Imagen(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...

    @torch.no_grad()
    def p_sample_loop(self, unet, shape, *, lowres_cond_img=None, lowres_noise_times=None, 
    			noise_scheduler=None, text_embeds=None, text_mask=None):

        device = self.device

        # Get starting noisy images
        img = torch.randn(shape, device=device)

	# Get sampling timesteps (final_t, final_t-1, ..., 2, 1, 0)
	batch = shape[0]
        timesteps = noise_scheduler.get_sampling_timesteps(batch, device=device)

	# For each timestep, denoise the images slightly
        for times in tqdm(timesteps, desc='sampling loop time step', total=len(timesteps)):
            img = self.p_sample(
                unet,
                img,
                times,
                text_embeds=text_embeds,
                text_mask=text_mask)

	# Clamp the values to be in the allowed range and potentialy
   	# unnormalize back into the range (0., 1.)
        img.clamp_(-1., 1.)
        unnormalize_img = self.unnormalize_img(img)
        return unnormalize_img

[_p_sample_loop](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L373)是我们用 one unet 生成图像的方式。Imagen 包含 U-Net 的 ,因此,最后,[sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/Imagen.py#L424)函数通过链中的每个 U-Net 迭代传递生成的图像,并处理其他采样要求,如生成文本编码/掩码、将当前采样的 U-Net 放在 GPU 上(如果可用)等。[eval_decorator](https://github.com/AssemblyAI-Examples/MinImagen/blob/8445a5aeff34d5f818b9c659d9bd71941533f999/minimagen/helpers.py#L35)如果不是在调用sample时,将模型设置为评估模式。

class Imagen(nn.Module):
    def __init__(self, *args, **kwargs):
    	# ...
        self.noise_schedulers = nn.ModuleList([])
        for i in num_unets:
        	self.noise_schedulers.append(GaussianDiffusion(timesteps=timesteps))

    @torch.no_grad()
    @eval_decorator
    def sample(self, texts=None, batch_size=1, cond_scale=1., lowres_sample_noise_level=None, return_pil_images=False, device=None):
    	# Put all Unets on the same device as Imagen
        device = default(device, self.device)
        self.reset_unets_all_one_device(device=device)

	# Get the text embeddings/mask from textual captions (`texts`)
        text_embeds, text_masks = t5_encode_text(texts, name=self.text_encoder_name)
        text_embeds, text_masks = map(lambda t: t.to(device), (text_embeds, text_masks))

        batch_size = text_embeds.shape[0]

        outputs = None

        is_cuda = next(self.parameters()).is_cuda
        device = next(self.parameters()).device

        lowres_sample_noise_level = default(lowres_sample_noise_level, 
        					self.lowres_sample_noise_level)

		# Iterate through each Unet
        for unet_number, unet, channel, image_size, noise_scheduler, dynamic_threshold in tqdm(
                zip(range(1, len(self.unets) + 1), self.unets, self.sample_channels, 
                	self.image_sizes, self.noise_schedulers, self.dynamic_thresholding)):

	  # If GPU is available, place the Unet currently being sampled from on the GPU
            context = self.one_unet_in_gpu(unet=unet) if is_cuda else null_context()

            with context:
                lowres_cond_img = lowres_noise_times = None
                shape = (batch_size, channel, image_size, image_size)

                # If on a super-res model, noise the previous unet's images for conditioning
                if unet.lowres_cond:
                    lowres_noise_times = self.lowres_noise_schedule.get_times(batch_size, 
                                            			lowres_sample_noise_level,
                          		                 	device=device)

                    lowres_cond_img = resize_image_to(img, image_size, pad_mode='reflect')
                    lowres_cond_img = self.lowres_noise_schedule.q_sample(
                    		x_start=lowres_cond_img,
                    		t=lowres_noise_times,
                    		noise=torch.randn_like(lowres_cond_img))
                    		shape = (batch_size, self.channels, image_size, image_size)

				# Generate an image with the current U-Net
                img = self.p_sample_loop(
                    unet,
                    shape,
                    text_embeds=text_embeds,
                    text_mask=text_masks,
                    cond_scale=cond_scale,
                    lowres_cond_img=lowres_cond_img,
                    lowres_noise_times=lowres_noise_times,
                    noise_scheduler=noise_scheduler,
                )

                # Output the image if at the end of the super-resolution chain
                outputs = img if unet_number == len(self.unets) + 1 else None

        # Return tensors or PIL images
        if not return_pil_images:
            return outputs

        pil_images = list(map(T.ToPILImage(), img.unbind(dim=0)))

        return pil_images

摘要

概括地说,在本节中,我们定义了[Imagen](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Imagen.py#L22)类,首先检查它的[forward](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Imagen.py#L575)通过哪些噪声训练图像,预测它们的噪声成分,然后返回预测值和真实噪声值之间的平均 L2 损耗。然后,我们看了一下[sample](https://github.com/AssemblyAI-Examples/MinImagen/blob/556ce32679903ff59baf0bf0dd890dc5434f51d0/minimagen/Imagen.py#L424),它用于通过组成 Imagen 实例的 U 网的连续应用来生成图像。

MinImagen 的培训和样品

MinImagen 可以安装

pip install minimagen

MinImagen 包隐藏了上面讨论的所有实现细节,并公开了一个与 Imagen 一起工作的高级 API,在这里记录为。让我们看看如何使用minimagen包从 MinImagen 实例中训练和采样。你也可以查看 MinImagen 的 GitHub repo 来查看关于使用提供的脚本进行训练/生成的信息。

训练最小值

为了训练 Imagen,我们需要首先执行一些导入。

import os
from datetime import datetime

import torch.utils.data
from torch import optim

from minimagen.Imagen import Imagen
from minimagen.Unet import Unet, Base, Super, BaseTest, SuperTest
from minimagen.generate import load_minimagen, load_params
from minimagen.t5 import get_encoded_dim
from minimagen.training import get_minimagen_parser, ConceptualCaptions, get_minimagen_dl_opts, \
    create_directory, get_model_size, save_training_info, get_default_args, MinimagenTrain, \
    load_testing_parameters

接下来,我们使用 GPU(如果有的话)确定将在哪个设备上进行训练,然后实例化一个 MinImagen 参数解析器。当从命令行运行脚本时,解析器将允许我们指定相关参数

# Get device
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# Command line argument parser
parser = get_minimagen_parser()
args = parser.parse_args()

现在,我们将创建一个带有时间戳的培训目录,该目录将存储来自培训的所有信息。create_directory()函数返回一个上下文管理器,允许我们临时进入目录读取文件、保存文件等。

# Create training directory
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
dir_path = f"./training_{timestamp}"
training_dir = create_directory(dir_path)

由于这是一个示例脚本,我们用替代值替换了一些命令行参数,这将降低计算负载,以便我们可以快速训练并查看结果,从而了解 MinImagen 是如何训练的。

# Replace some cmd line args to lower computational load.
args = load_testing_parameters(args)

接下来,我们将使用概念标题数据集的子集创建数据加载器。如果您想使用不同的数据集,请查看[MinimagenDataset](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/training.py#L214)

# Replace some cmd line args to lower computational load.
args = load_testing_parameters(args)

# Load subset of Conceptual Captions dataset.
train_dataset, valid_dataset = ConceptualCaptions(args, smalldata=True)

# Create dataloaders
dl_opts = {**get_minimagen_dl_opts(device), 'batch_size': args.BATCH_SIZE, 'num_workers': args.NUM_WORKERS}
train_dataloader = torch.utils.data.DataLoader(train_dataset, **dl_opts)
valid_dataloader = torch.utils.data.DataLoader(valid_dataset, **dl_opts)

现在是时候创建将在 MinImagen 的 U 网链中使用的 U 网了。生成图像的基础模型是一个[BaseTest](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/Unet.py#L695)实例,放大图像的超分辨率模型是一个[SuperTest](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/Unet.py#L725)实例。这些模型被有意设计得很小,这样我们可以快速训练它们,看看如何训练一个 MinImagen 实例。参见[Base](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/Unet.py#L637)[Super](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/Unet.py#L667)了解更接近原始 Imagen 实现的模型。

我们加载这些 U-网的参数,然后用一个 list comprehension 实例化实例。

# Use small U-Nets to lower computational load.
unets_params = [get_default_args(BaseTest), get_default_args(SuperTest)]
unets = [Unet(**unet_params).to(device) for unet_params in unets_params]

现在我们终于可以实例化实际的 MinImagen 实例了。我们首先指定一些参数,然后创建实例。

# Specify MinImagen parameters
imagen_params = dict(
    image_sizes=(int(args.IMG_SIDE_LEN / 2), args.IMG_SIDE_LEN),
    timesteps=args.TIMESTEPS,
    cond_drop_prob=0.15,
    text_encoder_name=args.T5_NAME
)

# Create MinImagen from UNets with specified imagen parameters
imagen = Imagen(unets=unets, **imagen_params).to(device)

为了保持记录,我们为未指定的参数填充默认值,获得 MinImagen 实例的大小,然后保存所有这些信息等等。

# Fill in unspecified arguments with defaults to record complete config (parameters) file
unets_params = [{**get_default_args(Unet), **i} for i in unets_params]
imagen_params = {**get_default_args(Imagen), **imagen_params}

# Get the size of the Imagen model in megabytes
model_size_MB = get_model_size(imagen)

# Save all training info (config files, model size, etc.)
save_training_info(args, timestamp, unets_params, imagen_params, model_size_MB, training_dir)

最后,我们可以使用[MinimagenTrain](https://github.com/AssemblyAI-Examples/MinImagen/blob/cc470d3354867a2f534bcb0d7206cf466fc91a88/minimagen/training.py#L344)训练 MinImagen 实例:

# Create optimizer
optimizer = optim.Adam(imagen.parameters(), lr=args.OPTIM_LR)

# Train the MinImagen instance
MinimagenTrain(timestamp, args, unets, imagen, train_dataloader, valid_dataloader, training_dir, optimizer, timeout=30)

为了训练实例,将脚本保存为minimagen_train.py,然后在终端中运行以下命令:

python minimagen_train.py

注意——您可能需要将python改为python3,和/或将minimagen_train.py改为-m minimagen_train

训练完成后,您将看到一个新的训练目录,其中存储了训练的所有信息,包括模型配置和重量。要了解如何使用这个培训目录来生成图像,请转到下一部分。

train.py

注意,上面的脚本是所提供的训练文件的精简版本。你可以在这里阅读更多关于使用这个脚本训练 MinImagen 实例的信息。

用 MinImagen 生成图像

现在我们有了一个“训练过的”MinImagen 实例,我们可以用它来生成图像。幸运的是,这个过程要简单得多。首先,我们将再次执行必要的导入并定义一个参数解析器,这样我们就可以指定包含已训练的极小极大权重的训练目录的位置。

from argparse import ArgumentParser
from minimagen.generate import load_minimagen, sample_and_save

# Command line argument parser
parser = ArgumentParser()
parser.add_argument("-d", "--TRAINING_DIRECTORY", dest="TRAINING_DIRECTORY", help="Training directory to use for inference", type=str)
args = parser.parse_args()

接下来,我们可以定义我们想要为其生成图像的标题列表。我们现在只指定一个标题。

# Specify the caption(s) to generate images for
captions = ['a happy dog']

现在我们要做的就是运行sample_and_save(),指定要使用的标题和训练目录,每个标题的图像都会生成并保存。

# Use `sample_and_save` to generate and save the iamges
sample_and_save(captions, training_directory=args.TRAINING_DIRECTORY)

或者,您可以加载一个 MinImagen 实例并将它(而不是一个训练目录)输入到sample_and_save(),但是在这种情况下,用于生成图像的 MinImagen 实例的信息将不会被保存,所以不建议这样做。

minimagen = load_minimagen(args.TRAINING_DIRECTORY)
sample_and_save(captions, minimagen=minimagen) 

就是这样!一旦生成完成,您将看到一个名为generated_images_<TIMESTAMP>的新目录,其中存储了用于生成图像的标题、用于生成图像的训练目录以及图像本身。每个图像文件名中的数字对应于用来生成它的标题的索引。

inference.py

注意,上面的脚本是所提供的推理文件的精简版本。你可以在这里阅读更多关于使用这个脚本训练 MinImagen 实例的信息。

最后的话

最先进的文本到图像模型的令人印象深刻的结果不言自明,MinImagen 为理解这种模型的实际工作提供了坚实的基础。要了解更多机器学习内容,请随时查看我们的博客YouTube 频道。或者,在 Twitter 上关注我们,或者关注我们的时事通讯,以便及时了解我们未来发布的内容。

Follow the AssemblyAI Newsletter**

用于数据高效强化学习的回顾-预训练表示

原文:https://www.assemblyai.com/blog/pretraining-representations-for-data-efficient-reinforcement-learning/

在本周的深度学习论文综述中,我们来看下面这篇论文:数据高效强化学习的预训练表示

这篇论文有什么令人兴奋的地方

近年来,预训练已经被证明是在自然语言处理和计算机视觉领域取得成功的重要因素。这个想法是首先以非监督的方式预训练一个通用模型,然后在较小的监督数据集上对其进行微调。这同时使微调部分的数据效率更高,同时实现了卓越的性能。然而,在强化学习领域,预训练还没有成为标准。因此,RL 算法是出了名的数据低效:一个简单的 Atari 游戏需要数千万帧训练数据才能收敛到人类的表现。直觉上,这是因为 RL 代理必须同时学习两个困难的任务:来自原始像素的视觉表示以及学习策略和值函数。

本文介绍了一种称为 SGI 的技术,它将表征学习与强化学习分离开来。首先,使用观察到的轨迹以无监督的方式预训练 RL 代理的编码器;无监督目标包括基于当前状态和动作预测下一个状态,以及预测导致状态转换的动作。这项工作和以前的工作之间的一个关键区别是,下一状态预测纯粹发生在潜在空间中,并避免使用对比样本,因此减轻了对图像重建或大批量的需要(后者对对比学习至关重要)。在预训练之后,下游 RL 代理使用预训练的编码器进行实例化,并在 Atari 任务上进行训练(图 1)。

作者证明:1)与从零开始的代理相比,预训练使 RL 代理在有限的训练数据下实现更好的性能;2)预训练允许 RL 代理利用大得多的编码器,而没有预训练的代理不能有效地学习使用它们。

我们的外卖

这篇论文是令人兴奋的,因为它将 RL 领域推向了构建更一般化的代理的趋势。当我们像人类一样学习一个新的视频游戏时,我们利用我们预先训练的视觉系统和对世界的先验知识来帮助我们。同样,我们认为一个真正通用的人工智能的关键特征是利用现有知识解决新任务的能力。在更实际的层面上,像 SGI 这样的技术可以使 RL 的数据效率更高,这对于大多数现实世界的应用程序来说是很重要的,在这些应用程序中,模拟是不可能的或者计算量很大。

如何构建一个总结你的讲座的 Python 项目

原文:https://www.assemblyai.com/blog/python-project-lecture-summaries/

了解如何构建一个 Python 应用程序,通过自动总结讲座让您更快地学习!我们使用 Streamlit 来构建应用程序,并使用 AssemblyAI 来生成脚本摘要和亮点。这可以应用于例如视频讲座或记录的缩放呼叫。

通过自动转录突出显示,AssemblyAI API 可以自动检测转录文本中的重要短语和单词。

Auto Chapters 为转录的文件提供“一段时间的摘要”。它的工作原理是首先随着谈话主题的变化将音频数据分割成逻辑“章节”,然后为每个“章节”的内容提供自动生成的摘要。

在本教程中,我们在应用程序中结合了这两个功能,这样我们就可以轻松地跳到视频中的重要部分并通读摘要。

看这里:

https://www.youtube.com/embed/85KhuRqWkDI?feature=oembed

寻找更多这样的内容?

请务必订阅我们的时事通讯!

Subscribe

30 行代码的 Python 语音识别

原文:https://www.assemblyai.com/blog/python-speech-recognition-in-30-lines-of-code/

如今,在 Python 中有许多方法来执行语音识别。开源库和框架,如 KaldiESPNet 、DeepSpeech 和 Whisper 为开发人员提供了实现语音到文本功能的方法,而不必构建、训练和维护复杂的机器学习模型。

然而,这些开源工具可能需要大量的设置和计算,并且精确度各不相同。此外,实现更丰富的功能,如扬声器二进制化,需要将各种不同的工具缝合在一起。出于这些原因,许多开发人员选择使用语音到文本 API,它提供即插即用的语音到文本功能。在本教程中,我们将学习如何使用 AssemblyAI API 来转录只有 30 行 Python 代码的音频文件。

先决条件

在我们开始编码之前,我们需要得到一个 AssemblyAI API 密匙——你可以在这里免费得到一个。您的 API 密钥对您的帐户是唯一标识的,并告诉 AssemblyAI 的服务您被授权使用它们。

安全说明

任何使用你的 API 密匙的人都会以你的身份出现来组装 AI 的服务,所以确保不要共享你的 API 密匙。特别是,不要将它的值硬编码或意外上传到 GitHub。

AssemblyAI 仪表板为您提供了帐户的快照。您可以通过点击屏幕右侧的 API 密匙(下面红色方框中)来复制它。

接下来,我们需要这个教程的[requests](https://requests.readthedocs.io/en/latest/)库,所以如果你还没有的话,现在就用下面的命令安装它。

pip install requests

Python 语音识别代码

现在我们可以看看如何使用 Python 只用 25 行代码来执行语音识别。您可以使用下面的命令下载项目库:

git clone https://github.com/AssemblyAI-Examples/python-speech-recognition.git

main.py文件包含了本教程所需的所有代码——现在让我们来看看。首先,我们导入我们需要的两个库- requeststime

import requests
import time

接下来,我们定义两个端点。第一个是UPLOAD_ENDPOINT,我们将用它上传一个音频文件到 AssemblyAI。第二个是TRANSCRIPTION_ENDPOINT,我们将使用它向 AssemblyAI 请求转录。

UPLOAD_ENDPOINT = "https://api.assemblyai.com/v2/upload"
TRANSCRIPTION_ENDPOINT = "https://api.assemblyai.com/v2/transcript"

现在我们可以定义两个需要在 HTTP 请求中使用的变量——一个headers字典和您的api_key“解锁”AssemblyAI 的服务。我们在这里硬编码 API 密匙,因为这只是一个例子,但是当你从测试转移到真正的应用程序时,确保使用 环境变量 ,以防止意外共享你的 API 密匙。

api_key = "<YOUR-API-KEY-HERE>"
headers = {"authorization": api_key, "content-type": "application/json"}

接下来,我们定义read_file,它返回一个我们用来读取音频文件的生成器。值5242880是用于将待读取的数据分割成小块的块大小。

def read_file(filename):
   with open(filename, 'rb') as _file:
       while True:
           data = _file.read(5242880)
           if not data:
               break
           yield data

现在我们将使用 POST 请求上传我们的音频文件到 AssemblyAI。这里我们使用项目存储库中的样本音频文件audio.wav,但是您可以用您想要转录的任何文件的文件路径替换'audio.wav'字符串。从请求的响应中,我们提取可以访问上传音频的 URL,并将其保存到一个名为audio_url的变量中。

upload_response = requests.post(UPLOAD_ENDPOINT, headers=headers, data=read_file('audio.wav'))
audio_url = upload_response.json()["upload_url"]

现在我们准备向 AssemblyAI 申请一份抄本!我们执行另一个 POST 请求,将刚刚收到的上传 URL 放入一个字典中,该字典作为 POST 请求的 JSON 传入。从响应中,我们将脚本 ID 保存到一个名为_id的变量中。

transcript_request = {'audio_url': audio_url}
transcript_response = requests.post(TRANSCRIPTION_ENDPOINT, json=transcript_request, headers=headers)
_id = transcript_response.json()["id"]

最后,我们用轮询端点反复探测转录的状态,一旦完成就将转录保存到一个.txt文件中。

while True:
    polling_response = requests.get(TRANSCRIPTION_ENDPOINT + "/" + _id, headers=headers)

    if polling_response.json()['status'] == 'completed':
       with open(f'{_id}.txt', 'w') as f:
           f.write(polling_response.json()['text'])
       print('Transcript saved to', _id, '.txt')
       break
    elif polling_response.json()['status'] == 'error':
        raise Exception("Transcription failed. Make sure a valid API key has been used.")
    else:
       print("Transcription queued or processing ...")
    time.sleep(5)

更详细地说,我们首先请求更新的转录信息,将响应保存在polling_response中。然后,我们检查响应中的'status'。如果状态是'completed',我们将脚本保存到一个.txt文件,并退出我们的while循环来终止程序。

如果'status''error',我们抛出一个异常,让用户知道转录失败了。否则,转录要么排队,要么正在处理,所以我们在终端中指示,然后等待 5 秒钟再重复。

代码到此为止!要获得您的转录,在项目目录中打开一个终端并简单地执行

python main.py

终端中的结果将如下所示:

保存的抄本可以在与main.py相同的文件夹中找到。我们提供了提交存储库附带的audio.wav文件产生的示例输出抄本transcript.txt

Four score and seven years ago our fathers brought forth on this continent a new nation conceived in liberty and dedicated to the proposition that all men are created equal.

这就是在 Python 中执行语音识别所需的全部内容,只需 30 行代码!如果你喜欢这个教程,可以看看我们的其他文章,比如DALL-E 2 实际上是如何工作的或者机器学习扩散模型介绍。或者,随时查看我们的 YouTube 频道或关注我们的时事通讯,以便在新内容减少时保持及时了解。

喜欢这篇文章吗?

关注我们的时事通讯,了解更多类似的内容!

Follow

假人用 PyTorch 闪电-教程和概述

原文:https://www.assemblyai.com/blog/pytorch-lightning-for-dummies/

当训练深度学习模型时,有许多独立于实验/训练代码的标准“样板”代码。PyTorch Lightning 将这些样板代码抽象化,从而使实验和分布式培训更加容易。

在本教程中,我们将探索闪电和普通 PyTorch 之间的差异,了解闪电的工作流程,然后建立和训练一个模型来查看闪电的行动。我们开始吧!

Pytorch 闪电是什么?

PyTorch 是一个灵活且流行的深度学习框架,使得构建和训练标准的深度学习模型变得轻而易举;然而,随着模型复杂性的增长,开发过程会很快变得混乱。从实施多 GPU 训练到消除标准训练循环中的错误,这些困难会阻碍建模过程,进而影响项目时间表。削减细节,专注于高层次的作品不是很好吗?

进入: PyTorch 闪电。Lightning 是 PyTorch 的一个高级框架,它抽象出了实现细节,因此您可以专注于构建优秀的模型,而不必浪费时间在琐碎的细节上。好处多多:

Benefits of PyTorch Lightning

如何安装 PyTorch 闪电

首先,我们需要安装闪电。打开命令提示符或终端,如果需要,激活 virtualenv/conda 环境。使用以下命令之一安装 PyTorch:

| pip 安装 pytorch-lightning |

康达

| 康达安装 pytorch-lightning -c 康达-forge |

闪电 vs 香草

PyTorch 闪电是建立在普通(香草)PyTorch 之上的。Lightning 的目的是提供一个研究框架,允许进行快速实验可伸缩性,这是通过去除样板文件和硬件参考代码的 OOP 方法实现的。这种方法产生了一系列的好处。

How PyTorch Lightning compares to PyTorch

PyTorch 闪电工作流程

Lightning 最大的好处之一是它的易用性,这源于它本质上是一个与硬件无关的样本 PyTorch 包装器。这实际上意味着代码与 vanilla PyTorch 的完全相同,但是它是以方便的面向对象的方式进行转换的,这使得用户可以专注于训练过程的重要组成部分。这种差异主要体现在 PyTorch 型号Lightning 模块之间的差异上。

PyTorch 模块

PyTorch nn.Module类是 PyTorch 中神经网络的基类,所有的设计都是从这个基类派生出来的。虽然一个模块可能以嵌套的方式包含其他模块,但通常不会这样做。

考虑训练生成性对抗网络(GAN)的情况。在这样的网络中,生成器创建伪造的数据,旨在模拟一组真实的数据,鉴别器试图区分真实数据和伪造数据。

在 vanilla PyTorch 中,定义和训练这样一个系统的典型方式是通过子类化nn.Module来创建生成器和鉴别器类,然后在主代码中实例化和调用它们,在主代码中,您已经手动定义了向前传递、损失计算、向后传递和优化器步骤。

闪电模块

相比之下,LightningModule定义了一个完整的深度学习生态系统。继续训练 GAN 的例子,我们有生成器和鉴别器模型、损失函数、训练/测试/验证函数和优化器。整个系统闪电模块封装。

值得注意的是,LightningModule并没有在普通 PyTorch 代码的基础上构建抽象,而是简单地用一种更有效和更干净的方式组织。

闪电模块组件

lightning 模块由六个组件组成,这六个组件完整地定义了系统:

  • 模型或模型系统
  • 优化程序
  • 火车环线
  • 验证循环
  • 测试循环
  • 预测循环

只有这些组件中的每一个的基本特征在其各自的类别/功能中被定义。这种样板文件的去除使得代码更加整洁,并且降低了犯小错误的可能性;然而,训练的任何部分(例如向后传球)都可以被忽略以保持灵活性。

现在我们对 Lightning 工作流有了更好的了解,让我们深入到一个突出 Lightning 的强大和简单的例子中去吧!

用 PyTorch 闪电建造 GAN

我们将使用闪电构建一个 GAN,然后将其与香草 PyTorch 进行比较,突出闪电使我们的生活更加轻松的地方。首先,简单回顾一下 GANs(或介绍给外行人)。

生成对抗网络

深度学习是各种任务不可或缺的工具。在 AssemblyAI,我们利用它的功能,如实体检测情感分析、情感检测、翻译和摘要。深度学习擅长的许多任务都涉及到处理数据以提取一些有用的信息,但如果我们想要生成数据呢?

GANs 允许通过学习反映特定输入数据集分布的分布来生成数据(因此“生成”)。一旦了解了分布,就可以生成与输入数据相似但不同的数据,以至于人类不可能察觉到差异。你能说出下面哪些名人的图片是捏造的吗?

如果你猜“全部”,你就答对了!

GANs 的力量源于这样一个事实,即两个深度学习模型,生成器和鉴别器,在零和游戏中相互对抗(因此是“敌对的”)。随着生成器在制造更有说服力的数据方面变得更好,鉴别器学习变得更擅长微分;随着鉴别者在检测伪造数据时变得越来越有辨识力,生成器学会制造更有说服力的伪造品。

甘为闪电中的手写数字

我们将使用规范的 MNIST 数据集在 Lightning 中构建一个能够再现手写数字的 GAN。首先,我们需要下载并处理这些数据。Lightning 再次以LightningDataModules的形式为这个过程提供了一个结构化的框架。

闪电数据模块

一个LightningDataModule仅仅是 PyTorch DataLoaders的集合,带有以可再现的方式准备数据所需的相应转换和下载/处理步骤。它封装了 PyTorch 中处理数据所需的所有步骤:

  • 下载并标记
  • 清理并保存到磁盘
  • 在数据集中加载
  • 应用变换
  • 包装在数据加载器中

重要的是,一个LightningDataModule可共享的可重用的。也就是说,它集中了所有的数据准备任务,以拥有一个自包含的对象,该对象可以通过相同的拆分和转换精确地复制数据集。

让我们创造我们的 MNIST。首先,我们用一些相关的参数进行初始化,并创建我们将用来处理原始数据的转换对象。最后,我们创建一个字典,稍后我们将把它传递到我们的DataLoaders中。

| 类 类类类类 (pl。LightningDataModule): def_ _ init _ (self,data_dir= )。/data" ,batch_size=128,num _ workers = int(OS . CPU _ count()/2)): super()。 _ init _ _() self . data _ dir = data _ dir self . batch _ size = batch _ sizeself . num _ workers = num _ workers
self . transform = transforms。撰写([ 变换。ToTensor(), 变换。正常化(( 0.1 ,),( 0.3 ,) 】】) self . dl _ dict = { |

接下来,我们定义prepare_data()函数。它定义了如何下载和标记数据。Lightning 确保用单个进程调用该方法,以避免损坏数据。在这里,我们只需下载我们的训练和测试集。

| defprepare _ data【self】: 数据集。MNIST(self.data_dir,train= True ,download =True) 数据集。MNIST(self.data_dir,train= 假 ,download= 真 ) |

接下来是setup()函数。它定义了您可能想要在每个 GPU 上执行的数据操作。使用它来定义数据集,例如拆分/转换数据或构建词汇表。在我们的例子中,我们将使用在类__init__()函数中定义的转换,将数据分成训练集、验证集和测试集。

| defsetup(self,stage = None): #验证数据不是 GAN 严格必需的,但为了完整性添加了 ifstage = =【fit】MNIST(self.data_dir,train= True ,transform = self . transform) self . Mn ist _ train,self . Mn ist _ val = random _ split(Mn ist _ full,[ 55000 ,5000)MNIST(self.data_dir,train =False,transform=self.transform) |

最后,我们定义培训、验证、测试和预测DataLoaders。通常在setup()中定义的数据集被简单地包装在DataLoader对象中。请注意,鉴于 gan 不寻常的评估协议,其验证数据并非真正必要,但为了完整性,此处添加了val_dataloader()。此外,我们在这里没有定义预测DataLoader,但是过程是相同的。

| ##对于数据加载器,通常只需将设置中定义的数据集 deftrain _ data loader: returndata loader(self . mnist _ train,* * self . dl _ dict) * * self . dl _ dict) deftest _ data loader【self】: returndata loader |

建造闪电模块

回想一下,Lightning 工作流中的中心对象是LightningModule,它封装了整个模型生态系统。既然我们已经完成了定义我们的LightningDataModule对象,我们可以构建我们的LightningModule。在我们的例子中,这个生态系统包括两个模型——生成器和鉴别器。我们现在来定义它们,注意这个过程和 vanilla PyTorch 是一样的。

构建鉴别器

首先,我们将把我们的鉴别器构造成一个nn.Module。我们将使用一个简单的 CNN,它有两个卷积层,后面是一个完全连接的网络,用于将 28x28 单通道数字图像映射到分类预测。请记住,鉴别器的目的是将图像分类为真的假的,因此我们只需要一个输出节点,相比之下,通常为 MNIST 构建的数字分类网络需要十个输出节点。

| 类 判别器 (nn)。模块): 【def】【init __ _ _ _ _ init _()# simple CNN自我。conv1 = nn。conv 2d((t41)、、kernel _ size =)tconv 2d((t58),,kernel _ size =)2d() 自我。fc1 = nn。线性(【320】,)【self . fc 2 = nn。线性(【50】,【1】) |

接下来,我们定义鉴别器的正向传递。我们使用内核大小为 2 的 max poolings,后跟 ReLU 用于卷积层,并使用 sigmoid 用于最终激活

| defforward(self,x): x = f . relu(f . max _ pool 2d(self . con v1(x),2) x = f . relu(f . max _ pool 2d(self . con v2 _ drop(self . con v2(self 320) x = f . relu(self . fc1(x)) x = f . dropout(x,training = self . training) x = self . fc2(x) |

建造发电机

类似地,我们将生成器构造为一个nn.Module。我们从一个潜在空间输入数据点,这些数据点馈入一个线性层,为我们提供足够的节点来创建 7x7 的图像和 64 个特征地图。然后,我们使用转置卷积进行可学习的上采样,最终通过最终的卷积层将数据压缩为 28×28 的单通道图像(即数字图像)。

| 类 发电机 (nn)。模块): 【def】【init __ _ _ _ _ init _( self . Lin 1 = nn。线性(latent_dim、【7】*)2d 转换(【64】、【32】、、【stride= 【T】2d 转换(【32】,【16】,,【stride= 【T】conv 2d(【16】,,kernel _ size =) |

同样,我们定义了向前传球。

| defforward(self,x): # Pass 将潜在空间输入到线性图层中并重塑 x = self . lin1(x) x = f . relu(x) #转置卷积到 16x16 (64 特征图) x = self . ct1(x) x = f . relu(x) #转置卷积到 34x 34(34) |

定义 GAN

现在我们已经有了传统的 PyTorch nn.Module模型,我们可以制造我们的LightningModule。这就是我们将看到闪电方法与普通 PyTorch 方法的不同之处。

初始化

首先,我们定义我们的初始化函数,输入我们的潜在维度以及 Adam 优化器的学习率和 betas。save_parameters()允许我们将这些参数存储在self.hparams属性下(也存储在模型检查点中),以便在训练后更容易地重新实例化。最后,我们在我们的生态系统中初始化生成器和鉴别器模型,并生成我们可以用来监控进展的潜在空间点。这些点将为我们提供一组一致的数据,以跟踪生成器在学习时如何从同一组潜在点映射到图像。

| 甘 (pl。lightning module): # #初始化。定义潜 dim,学习率,和 Adam betas def_ _ init _ (self,latent_dim=100,lr=0.0002,b1=0.5,b2=0.999,batch _ size = 128): 超()。 _ init _ _() self . save _ hyperparameters() self . Generator = Generator(latent _ dim = self . hparams . latent _ dim) self . Discriminator = Discriminator() self . validation _ z |

正向传递和损失函数

接下来,我们定义 GAN 的正向传递和损耗函数。注意使用self.generator(z)self.generator.forward(z)更好,因为当self.generator(z)被调用时,正向传递只是调用逻辑的一个组成部分。

我们将以两种不同的方式使用损耗函数——一种作为鉴频器损耗,一种作为发电机损耗。第一种方法是以规范的方式更新鉴别器作为分类器,输入带有适当标签的真实和生成的图像。第二种方法是使用来自生成图像上的鉴别器的损失来更新生成器。也就是说,鉴别器在检测伪图像方面越好,生成器更新得越多。稍后将详细介绍。

| def 前进 (自我,z): 返回【z】 def对抗性 _ 损失 |

训练步骤

接下来是定义在 GAN 的训练步骤中会发生什么。如果我们正在训练生成器,我们生成图像,然后通过鉴别器获得对它们的预测。当我们计算损失时,重要的是我们在这里使用了欺骗性的标签。也就是说,尽管图像是伪造的,我们在损失函数中将它们标记为真实的。这是因为我们希望它们被归类为真的,所以当生成的图像被(正确地)归类为假的时候,欺骗性的标签会导致更大的损失。我们还记录了一些要在 TensorBoard 中查看的虚构图像,并输出相关值。

训练鉴别器没有特别的细节。以一种非常简单的方式,我们计算真实图像和伪图像(具有真实标签)的损失,并将其平均为鉴别器损失。我们再次输出相关参数。

| deftraining _ step(self,batch,batch_idx,optimizer _ idx): real _ imgs,_ = batch #样噪 z = torch self . hparams . latent _ dim) ifoptimizer _ idx = =: self . generated _ imgs = self(z)# log 采样图像 sample _ imgs = self . generated _ imgs[:6】 grid = torch vision . utils . make _ grid(sample _ imgs) self . logger . experiment . add _ image(【generated _ images】 tqdm _ dict = {【g _ loss】:g _ loss } output = ordered dict({【loss】:g _ loss,【progress _ bar】:tqdm _ dict, 【日志】:tqdm _ dict }) 返回 输出 #列车鉴别器 ifoptimizer _ idx = = detach()) fake _ loss = self . adversarial _ loss(fake _ preds,torch . zeros(real _ imgs . size(0)、 1 )) d_loss =(真实 _ 损失+虚假 _ 损失)//2 tqdm _ dict = {【d _ 损失】:d _ 损失} 输出= ordered dict({【损失】:d _ 损失, |

配置优化器

现在,我们可以使用保存在self.hparams中的学习率和 betas 来配置 Adam 优化器。我们为生成器和鉴别器分别配置了一个优化器。

| defconfigure _ optimizer【self】: lr = self . hparams . lr B1 = self . hparams . B1 B2 = self . hparams . B2 |

纪元结束

最后,我们定义了on_epoch_end()方法。这不是绝对必要的,但是我们在这里使用它来记录图像,以便我们可以观察跨时代的训练进度。每当训练、测试或验证时期结束时,都会调用它。请注意,每个训练、测试、验证和预测案例都有自己的epoch_end()函数,以防您不想全面执行这样的函数!

| defon _ epoch _ end【self】: # log 采样图像 sample _ imgs = self(self . validation _ z) grid = torch vision . utils . make _ grid(sample _ imgs) |

程序代码

现在我们可以编写主程序代码来利用我们上面定义的所有组件,这只需要几行简单的闪电。

闪电训练器

回想一下上面的内容,LightningModule不是 PyTorch 之上的抽象层,而只是代码的重组。闪电中的抽象来自训练师职业。训练者类的使用非常简单,并且有很多好处,包括:

  • 覆盖任何自动化组件的能力
  • 硬件参考的省略
  • 样板代码的删除
  • 通过顶级人工智能实验室的贡献者,纳入了最佳实践

出于我们的目的,我们只需要传入一个最大训练时期数的值。

| 培训师= pl。驯兽师(max _ epochs =20) |

甘培训

我们已经定义了上面的LightningDataModuleLightningModule,并实例化了将在我们的 LightningModule 上操作的训练器。现在我们需要做的就是实例化我们的LightningDataModuleLightningModule,并把它们传递给训练者!

| DM = MNISTDataModule() model = GAN() trainer . fit(model,dm) |

结果

回想一下,我们在整个培训中使用相同的潜在空间点作为验证。这意味着我们可以在整个训练过程中比较同一个点是如何从潜在空间映射到数字图像的。下面我们选择了一个这样的点,并随着训练的进行显示了通过生成器的输出图像。相当不错的成绩!

PyTorch 闪电 vs 香草

对于体验过香草 PyTorch 的用户来说,Lightning 的好处肯定是显而易见的。Lightning 提供了自动化的便利性和覆盖的灵活性,利用方便的类来确保可再现性。此外,缺少硬件参考以及省略手动反向传播和优化步骤使得分布式培训变得轻而易举。

在我们的 GAN 例子中,这些差异中的许多是显而易见的。在定义了(可重用和可共享)LightningDataModule对象并将我们的培训生态系统封装在一个LightningModule中之后,我们的主要代码如下所示:

另一方面,训练一个 GAN 就像上面展示的一样简单,就像这样:

很容易看出这样的工作流如何无法扩展到更复杂的深度学习生态系统。

最后的话

PyTorch Lightning 为深度学习实验和工程可扩展模型提供了一个强大而灵活的框架。我们已经看到了它的面向对象的方法如何将代码划分成有效的组件,并避免训练杂乱或分散在许多不同文件中的代码。PyTorch 的最小使用非常简单,覆盖自动化过程的能力允许用户在模型变得更加复杂时保持控制。

寻找更多这样的帖子?

订阅我们的时事通讯!

Subscribe Now

快速自动章节检测

原文:https://www.assemblyai.com/blog/quick-automatic-chapter-detection/

在本教程中,您将学习如何使用 AssemblyAI API 进行自动章节检测。自动章节功能为使用 AssemblyAI 的语音到文本 API 转录的音频内容提供了“一段时间的摘要”。

它的工作方式是,首先随着谈话主题的变化,将音频/视频文件分成逻辑“章节”,然后为每个“章节”的内容提供自动生成的摘要。

在这个简单易懂的视频教程中,了解如何利用这一功能:

https://www.youtube.com/embed/GvfmDGin7Zs?start=2&feature=oembed

使用 React 挂钩进行 React 语音识别

原文:https://www.assemblyai.com/blog/react-speech-recognition-with-react-hooks/

语音识别已经成为一个热门话题有一段时间了。它有许多使用案例,而且需求还在增长。在本教程中,我将向您展示如何使用 AssemblyAI 的语音到文本 API 和 React 挂钩创建 React 语音识别应用程序。

佐料

  • 反应^17.0.2
  • 轴^0.26.1
  • 麦克风-录音机-mp3 ^2.2.2
  • 反应-装载-旋转^6.0.0-0
  • 随意做出事情🦄
  • ^3.0.23 顺风社
  • ^2.8.0 大秀

我们在建造什么?

在这个 React 语音识别教程中,我们构建了一个应用程序,它可以记录您对麦克风说话的音频,并自动将音频转录为文本。样式是完全可选的,我们将在我们的 Github 库中为您提供带有样式的完整代码。

我们希望主教程保持最小化,不要让混乱的风格堵塞代码。下面只是一个例子,如果你添加样式,它会是什么样子。

你可以在这里一窥完成后的应用程序的样子

React Speech Recognition

步骤 1 -创建新的 React 应用程序

如果您以前使用过 React,您应该对这个经典过程很熟悉。

npx create-react-app@latest react-speech-recognition-app
cd react-speech-recognition-app
code . 

React Speech Recognition

第二步-清理

/src 中删除 App.cssApp.test.jslogo.svgreportWebVitals.jssetupTests.js

删除 App.js 中的所有内容,如下所示

function App() {
  return <div></div>
}

export default App 

index.js 中移除reportWebVitals();及其导入

删除 index.css 中的所有内容

步骤 3 -安装依赖项

让我们先把安装部分解决掉。我将包括样式依赖——您可以选择省略它们。

核心依赖关系

npm install react-loader-spinner --save
npm install mic-recorder-to-mp3 --save
npm install axios --save 

样式依赖关系

对于顺风 CSS,从步骤 2步骤 4 遵循这些步骤

对于 DaisyUI,请遵循以下步骤。

步骤 4 -设置我们的录音机

为了能够创建整个 React 语音识别应用程序,我们首先需要一种记录音频文件的方法。为此,我们使用之前安装的麦克风录音转 mp3 npm 包。

让我们从导入一些东西开始,添加一些按钮和我们的音频播放器。

import MicRecorder from "mic-recorder-to-mp3"
import { useEffect, useState, useRef } from "react"
import axios from "axios"

const App = () => {
  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio controls='controls' />
      <div>
        START
        STOP
        SUBMIT
      </div>
    </div>
  )
}

export default App 

它应该看起来像这样

React Speech Recognition

录音机逻辑

首先,我们需要创建一些状态,并在应用程序中添加一些代码。下面我来解释。

import MicRecorder from "mic-recorder-to-mp3"
import { useEffect, useState, useRef } from "react"
import axios from "axios"

const App = () => {
  // Mic-Recorder-To-MP3
  const recorder = useRef(null) //Recorder
  const audioPlayer = useRef(null) //Ref for the HTML Audio Tag
  const [blobURL, setBlobUrl] = useState(null)
  const [audioFile, setAudioFile] = useState(null)
  const [isRecording, setIsRecording] = useState(null)

  useEffect(() => {
    //Declares the recorder object and stores it inside of ref
    recorder.current = new MicRecorder({ bitRate: 128 })
  }, [])

  const startRecording = () => {
    // Check if recording isn't blocked by browser
    recorder.current.start().then(() => {
      setIsRecording(true)
    })
  }

  const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, "audio.mp3", {
          type: blob.type,
          lastModified: Date.now(),
        })
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch((e) => console.log(e))
  }

  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
      </div>
    </div>
  )
}

export default App 

这将是一个口,但尽量留在我身边。

首先,我们初始化几个状态变量,如果你以前使用过 React 钩子,你应该会很熟悉。

接下来,我们利用 useEffect 来声明记录器对象,并将其存储在 useRef 函数中。

为什么我们需要这样做?因为useRef允许我们在其.current属性中存储一个可变值。这意味着即使在重新呈现 DOM 之后,我们也能够访问该属性。

然后我们声明startRecording函数,它询问我们是否允许应用程序通过按下开始按钮来访问我们的麦克风。然后我们将isRecording状态设置为true

最后,我们初始化stopRecording函数,这里有一堆事情在进行。

按下停止按钮,我们调用一堆麦克风录音到 mp3 的方法。这里需要注意的重要部分是,我们创建了一个新的 mp3 文件,并将其存储在我们的audioFile状态变量中。

我们还设置了blobUrl,这允许我们使用我们的 HTML 播放器播放我们录制的audioFile。我知道这是很多,但一旦你开始玩它,它是有意义的。

步骤 5 -麦克风检查🎤

现在让我们检查一下我们的代码到目前为止是否有效!点击开始按钮,允许应用程序使用您的麦克风。一旦完成,唱你最喜欢的歌曲,并点击停止。你现在应该可以通过按下音频播放器上的▶️按钮来重新播放你的美妙歌曲了。

让我们快速看一下 audioFile 对象实际上是什么样子。

Untitled

如你所见,里面有一堆信息。

这是我们将要上传到 AssemblyAI 的实际文件,稍后进行转录。请理解blobURL是对这个文件的引用,这样我们就可以使用 HTML 音频播放器来播放它,而audioFile实际的** audio.mp3 文件。**

好了,现在我们知道了如何录制音频,也知道了音频文件存储在哪里,我们可以开始设置转录了。

步骤 6 -启动 AssemblyAI API 连接

接下来,我们需要用 AssemblyAI 创建一个帐户。一旦完成,前往你的仪表盘拿起你的 API 密匙。

Get your API keyUntitled

重要提示:千万不要和任何人分享这个 API 密匙,也不要把它提交给你的 Github 账户!(稍后将详细介绍)

正在向 AssemblyAI 验证

为了能够使用我们的 React 语音识别应用程序与 AssemblyAI 进行通信,我们需要做的第一件事是使用 AssemblyAI 的 API 进行身份验证。你可以在文档中查找,里面涵盖了几种语言。

为了能够与 AssemblyAI API 对话,我们需要确保总是在请求头中包含我们的 API 键。因为我们想坚持 DRY 原则,所以我们简单地为它创建一个变量,而不是一遍又一遍地输入它。

确保用您的实际 API 密钥替换“your apikey”。另外,请注意,我们将这个变量放在了组件的之外。这与useEffect()如何工作有关。

import MicRecorder from "mic-recorder-to-mp3"
import { useEffect, useState, useRef } from "react"
import axios from "axios"

// Set AssemblyAI Axios Header
   const assembly = axios.create({
    baseURL: "https://api.assemblyai.com/v2",
    headers: {
      authorization: "YourAPIKey",
      "content-type": "application/json",
      "transfer-encoding": "chunked",
    },
  })

const App = () => {
  // Mic-Recorder-To-MP3
  const recorder = useRef(null) //Recorder
  const audioPlayer = useRef(null) //Ref for the HTML Audio Tag
  const [blobURL, setBlobUrl] = useState(null)
  const [audioFile, setAudioFile] = useState(null)
  const [isRecording, setIsRecording] = useState(null)

  useEffect(() => {
    //Declares the recorder object and stores it inside of ref
    recorder.current = new MicRecorder({ bitRate: 128 })
  }, [])

  const startRecording = () => {
    // Check if recording isn't blocked by browser
    recorder.current.start().then(() => {
      setIsRecording(true)
    })
  }

  const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, "audio.mp3", {
          type: blob.type,
          lastModified: Date.now(),
        })
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch((e) => console.log(e))
  }

  // AssemblyAI API

  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
      </div>
    </div>
  )
}

export default App 

为了测试认证是否有效,我们可以简单地使用这个例子进行测试。只需将这段代码粘贴到我们刚刚创建的程序集变量的下方,如下所示:

 ...
// Set AssemblyAI Axios Header
   const assembly = axios.create({
    baseURL: "https://api.assemblyai.com/v2",
    headers: {
      authorization: "YourAPIKey",
      "content-type": "application/json",
      "transfer-encoding": "chunked",
    },
  })

assembly
    .post("/transcript", {
        audio_url: "https://bit.ly/3yxKEIY"
    })
    .then((res) => console.log(res.data))
    .catch((err) => console.error(err))
... 

现在刷新浏览器页面(应用程序运行的地方)并检查开发人员控制台(F12)。您应该会看到类似这样的响应:

Untitled

这个响应包括我们在一秒钟内需要的两个重要的东西,即idstatus

很好。身份验证有效,您可以继续并再次删除测试代码。

步骤 7 -上传音频文件并检索上传 URL

接下来,我们需要将我们的音频文件上传到 AssemblyAI API 进行转录。一旦上传,我们会收到一个包含上传 URL 的响应,我们需要将这个 URL 存储在一个状态变量中。

..
const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, "audio.mp3", {
          type: blob.type,
          lastModified: Date.now(),
        })
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch((e) => console.log(e))
  }

  // AssemblyAI API

  // State variables
  const [uploadURL, setUploadURL] = useState("")
... 

一旦我们有了这个 URL,一旦创建了一个音频文件,我们就利用useEffect向 API 发出一个 POST 请求。我们console.log看看结果是否有效。

...
// AssemblyAI API

  // State variables
  const [uploadURL, setUploadURL] = useState("")

  // Upload the Audio File and retrieve the Upload URL
  useEffect(() => {
    if (audioFile) {
      assembly
        .post("/upload", audioFile)
        .then((res) => setUploadURL(res.data.upload_url))
        .catch((err) => console.error(err))
    }
  }, [audioFile])

  console.log(uploadURL)
... 

一旦你实现了这些代码,刷新你的页面,录制一个小的音频文件并查看控制台看看会发生什么。点击停止按钮几秒钟后,您应该会收到一个响应,包括您的上传 URL ,它现在存储在uploadURL中。

Untitled

明白了吗?酷毙了。我们继续吧。

安装说明

在 useEffect 钩子中包含[audioFile]的依赖数组确保了只有在 audioFile 状态改变时(在创建音频文件之后)才会发出 POST 请求。

步骤 8 -提交音频文件进行转录

现在我们需要将我们的uploadURL作为 POST 请求发送给 API,以开始转录过程。为此,现在让我们添加一个简单的按钮来处理这个问题。我们还需要创建更多的状态变量。我们的整个代码现在看起来像这样。

import MicRecorder from "mic-recorder-to-mp3"
import { useEffect, useState, useRef } from "react"
import axios from "axios"

  // Set AssemblyAI Axios Header
  const assembly = axios.create({
    baseURL: "https://api.assemblyai.com/v2",
    headers: {
      authorization: "YourAPIKey",
      "content-type": "application/json",
      "transfer-encoding": "chunked",
    },
  })

const App = () => {
  // Mic-Recorder-To-MP3
  const recorder = useRef(null) //Recorder
  const audioPlayer = useRef(null) //Ref for the HTML Audio Tag
  const [blobURL, setBlobUrl] = useState(null)
  const [audioFile, setAudioFile] = useState(null)
  const [isRecording, setIsRecording] = useState(null)

  useEffect(() => {
    //Declares the recorder object and stores it inside of ref
    recorder.current = new MicRecorder({ bitRate: 128 })
  }, [])

  const startRecording = () => {
    // Check if recording isn't blocked by browser
    recorder.current.start().then(() => {
      setIsRecording(true)
    })
  }

  const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, "audio.mp3", {
          type: blob.type,
          lastModified: Date.now(),
        })
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch((e) => console.log(e))
  }

  // AssemblyAI API

  // State variables
  const [uploadURL, setUploadURL] = useState("")
  const [transcriptID, setTranscriptID] = useState("")
  const [transcriptData, setTranscriptData] = useState("")
  const [transcript, setTranscript] = useState("")

  // Upload the Audio File and retrieve the Upload URL
  useEffect(() => {
    if (audioFile) {
      assembly
        .post("/upload", audioFile)
        .then((res) => setUploadURL(res.data.upload_url))
        .catch((err) => console.error(err))
    }
  }, [audioFile])

  // Submit the Upload URL to AssemblyAI and retrieve the Transcript ID
  const submitTranscriptionHandler = () => {
    assembly
      .post("/transcript", {
        audio_url: uploadURL,
      })
      .then((res) => {
        setTranscriptID(res.data.id)
      })
      .catch((err) => console.error(err))
  }

	console.log(transcriptID)

  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
      </div>
    </div>
  )
}

export default App 

好吧,让我们试一试。刷新页面,再次录制音频文件,按下停止后,按下提交按钮。提交完成后,您应该会在控制台中收到您的transcriptID

检查转录状态

为了能够看到我们的文件是否完成了转录,我们可以使用两种方法之一。第一个使用的是网页钩子第二个使用的是简单函数。我们在本教程中采用后者,因为它有助于我们理解每一步。

在下面添加checkStatusHandler异步函数。另外,将console.log改为日志transcriptData,并在 HTML 代码中添加检查状态按钮。

...
  // Submit the Upload URL to AssemblyAI and retrieve the Transcript ID
  const submitTranscriptionHandler = () => {
    assembly
      .post("/transcript", {
        audio_url: uploadURL,
      })
      .then((res) => {
        setTranscriptID(res.data.id)
      })
      .catch((err) => console.error(err))
  }

  // Check the status of the Transcript and retrieve the Transcript Data
  const checkStatusHandler = async () => {
    try {
      await assembly.get(`/transcript/${transcriptID}`).then((res) => {
        setTranscriptData(res.data)
		setTranscript(transcriptData.text)
      })
    } catch (err) {
      console.error(err)
    }
  }

  console.log(transcriptData)

  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
        CHECK STATUS
      </div>
    </div>
  )
}

export default App 

好吧,再来一次。刷新,记录,点击停止,点击提交,然后点击检查状态

您应该会收到响应,告诉您它仍在处理中。

Untitled

现在,我们如何检查状态是否更改为“已完成”?通过再次点击检查状态按钮。别担心,我们很快就会自动完成。

几秒钟后,转录完成。

Untitled

如果您打开此对象并向下滚动,直到找到text,您将看到您对着麦克风说出的文本。

Untitled

因为我们将这个响应存储在我们的transcriptData变量中,所以我们现在可以完全访问它。

显示成绩单数据

现在你也可以看到我们将transcriptData.text存储在transcript变量中。这意味着一旦转录完成,转录变量将保存我们转录的文本。我们现在能够使用条件呈现来显示该文本。

...
return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
        CHECK STATUS
      </div>
      {transcriptData.status === "completed" ? (
        <p>{transcript}</p>
      ) : (
        <p>{transcriptData.status}</p>
      )}
    </div>
  )
... 

这就是一切都做完后的结果。

Untitled

为了得到最终结果,我们需要反复按下检查状态按钮,直到音频文件处理完毕。很高兴,有一个更简单的方法来做到这一点。

步骤 9 -自动化流程

现在代码会有很大的变化,但本质上,它会保持不变。我向你解释发生了什么变化。

import MicRecorder from "mic-recorder-to-mp3"
import { useEffect, useState, useRef } from "react"
import axios from "axios"

  // Set AssemblyAI Axios Header
  const assembly = axios.create({
    baseURL: "https://api.assemblyai.com/v2",
    headers: {
      authorization: "YourAPIKey",
      "content-type": "application/json",
      "transfer-encoding": "chunked",
    },
  })

const App = () => {
  // Mic-Recorder-To-MP3
  const recorder = useRef(null) //Recorder
  const audioPlayer = useRef(null) //Ref for the HTML Audio Tag
  const [blobURL, setBlobUrl] = useState(null)
  const [audioFile, setAudioFile] = useState(null)
  const [isRecording, setIsRecording] = useState(null)

  useEffect(() => {
    //Declares the recorder object and stores it inside of ref
    recorder.current = new MicRecorder({ bitRate: 128 })
  }, [])

  const startRecording = () => {
    // Check if recording isn't blocked by browser
    recorder.current.start().then(() => {
      setIsRecording(true)
    })
  }

  const stopRecording = () => {
    recorder.current
      .stop()
      .getMp3()
      .then(([buffer, blob]) => {
        const file = new File(buffer, "audio.mp3", {
          type: blob.type,
          lastModified: Date.now(),
        })
        const newBlobUrl = URL.createObjectURL(blob)
        setBlobUrl(newBlobUrl)
        setIsRecording(false)
        setAudioFile(file)
      })
      .catch((e) => console.log(e))
  }

  // AssemblyAI API

  // State variables
  const [uploadURL, setUploadURL] = useState("")
  const [transcriptID, setTranscriptID] = useState("")
  const [transcriptData, setTranscriptData] = useState("")
  const [transcript, setTranscript] = useState("")
  const [isLoading, setIsLoading] = useState(false)

  // Upload the Audio File and retrieve the Upload URL
  useEffect(() => {
    if (audioFile) {
      assembly
        .post("/upload", audioFile)
        .then((res) => setUploadURL(res.data.upload_url))
        .catch((err) => console.error(err))
    }
  }, [audioFile])

  // Submit the Upload URL to AssemblyAI and retrieve the Transcript ID
  const submitTranscriptionHandler = () => {
    assembly
      .post("/transcript", {
        audio_url: uploadURL,
      })
      .then((res) => {
        setTranscriptID(res.data.id)

        checkStatusHandler()
      })
      .catch((err) => console.error(err))
  }

  // Check the status of the Transcript
  const checkStatusHandler = async () => {
    setIsLoading(true)
    try {
      await assembly.get(`/transcript/${transcriptID}`).then((res) => {
        setTranscriptData(res.data)
      })
    } catch (err) {
      console.error(err)
    }
  }

  // Periodically check the status of the Transcript
  useEffect(() => {
    const interval = setInterval(() => {
      if (transcriptData.status !== "completed" && isLoading) {
        checkStatusHandler()
      } else {
        setIsLoading(false)
        setTranscript(transcriptData.text)

        clearInterval(interval)
      }
    }, 1000)
    return () => clearInterval(interval)
  },)

  return (
    <div>
      <h1>React Speech Recognition App</h1>
      <audio ref={audioPlayer} src={blobURL} controls='controls' />
      <div>
        
          START
        
        
          STOP
        
        SUBMIT
      </div>
      {transcriptData.status === "completed" ? (
        <p>{transcript}</p>
      ) : (
        <p>{transcriptData.status}</p>
      )}
    </div>
  )
}

export default App 

你现在应该做的第一件事是再次运行应用程序。你会看到你只需要点击开始停止,然后提交。现在处理...出现在屏幕上。过了一会儿,我们转录的文本出现了。这种神奇是怎么发生的?

首先,我们添加了一个isLoading状态变量来检查状态是“处理中”还是“完成”

然后,我们创建了一个所谓的间隔来定期运行我们的checkStatusHandler()函数,以查看状态是否已经变为“已完成”。一旦状态变为“完成”,它将isLoading设置为 false ,将转录的文本添加到我们的transcript变量中,并结束间隔。

 // Periodically check the status of the Transcript
  useEffect(() => {
    const interval = setInterval(() => {
      if (transcriptData.status !== "completed" && isLoading) {
        checkStatusHandler()
      } else {
        setIsLoading(false)
        setTranscript(transcriptData.text)

        clearInterval(interval)
      }
    }, 1000)
    return () => clearInterval(interval)
  }, ) 

基本上就是这样了。同样,这是完全没有风格的更好的可视性。在最终版本中,当状态不等于“完成”时,我添加了一个加载微调器。

结论

正如您所看到的,React 语音识别起初可能会令人困惑,但是一旦您理解了它是如何工作的,它就是您的存储库中的一个很好的工具。

有大量的项目想法可以利用语音识别的能力。看看这些视频中的一些想法吧!

  1. 自动总结你的讲座
  2. 自动发布你说的最后一句话
  3. 自动实时转录电话通话

反应文本到语音-简化!

原文:https://www.assemblyai.com/blog/react-text-to-speech-simplified/

在我们的上一篇 React 教程中,我们使用 React 和 AssemblyAI API 构建了一个语音转文本应用。但是你知道吗,我们也可以创建一个反过来工作的应用程序?在本教程中,我们将利用 Web 语音 API 构建一个简单的 React 文本到语音应用程序。

佐料

  • 反应^17.0.2
  • 反应钩

和往常一样,您可以从 Github 中克隆本文的项目库,并在您的本地机器上使用它。或者,你可以在这里观看并尝试应用程序的现场版本,或者在 Replit 上获得你自己的副本。

步骤 1 -创建我们的 React 文本到语音应用程序

为了让事情顺利进行,我们首先创建一个全新的 React 应用程序。打开终端,输入以下命令:

npx create-react-app@latest react-text-to-speech-aai
cd react-text-to-speech-aai

接下来,在您喜欢的代码编辑器中打开 React 应用程序代码。我们使用 VSCode,它可以从终端打开(如果您使用 Linux 或 macOS ),方法是:

code .

最后,输入以下命令启动应用程序:

npm start

步骤 2 -基本清理

在我们开始编写代码之前,我们首先需要删除App.js中的 React 示例代码,只留下下面的代码:

import "./App.css"

function App() {
  return (
    <div className='App'>
      <h1>React Text to Speech App</h1>
    </div>
  )
}

export default App

App.js

现在打开你的网络浏览器(我们在这个例子中使用 Chrome)并打开 localhost:3000 。您应该会看到类似这样的内容:

步骤 3 -设置 Web 语音 API

为了让 Web Speech API 读取我们的文本,我们需要利用speechsynthesisatinterance()构造函数,它将包含我们的语音数据和应用程序应该如何处理它的信息。

让我们先实现代码,然后检查代码分解以了解发生了什么:

import "./App.css"
import { useEffect } from "react"

function App() {
  const msg = new SpeechSynthesisUtterance()
  msg.text = "Hello World"

  useEffect(() => {
    window.speechSynthesis.speak(msg)
  }, [msg])

  return (
    <div className='App'>
      <h1>React Text to Speech App</h1>
    </div>
  )
}

export default App 

代码分解

  • 首先,我们导入useEffect钩子。
  • const msg = new *SpeechSynthesisUtterance*()我们定义了一个新的变量,它初始化了speechsynthesisatinterance对象的一个实例。
  • msg.text设置为“Hello World”会存储我们希望 Web Speech API 为我们读出的文本。
  • 最后,window.speechSynthesis.speak(msg)是朗读存储在msg.text变量中的文本的函数。我们把整个东西包在一个useEffect钩子里,以防止它无限循环。

确保 React 应用程序正在运行,并刷新运行应用程序的浏览器窗口。现在你应该会听到用机器人的声音说的“你好,世界”。

步骤 4 -获取动态输入

到目前为止,要更改 React 文本到语音转换应用程序朗读的文本,我们必须手动更改代码中的字符串msg.text ,并每次刷新应用程序。

既然这不实际,那就让我们修改一下代码,让它更有动态性。

要做到这一点,我们只需创建一个**<**input>字段,允许我们输入任何我们想要读出的文本。我们还需要一个``来提交我们的请求。我们将利用 反应钩 进行说明。

让我们再添加一些代码,然后查看下面的代码分解,以了解发生了什么:

import "./App.css"
import { useState } from "react"

function App() {
  const [ourText, setOurText] = useState("")
  const msg = new SpeechSynthesisUtterance()

  const speechHandler = (msg) => {
    msg.text = ourText
    window.speechSynthesis.speak(msg)
  }

  return (
    <div className='App'>
      <h1>React Text to Speech App</h1>
       setOurText(e.target.value)}
      />
       speechHandler(msg)}>SPEAK
    </div>
  )
}

export default App 

我们的应用程序现在看起来像这样:

代码分解

  • 首先,我们用import { useState } from 'react'导入反应状态钩子
  • 然后我们初始化我们的状态变量,它将存储我们想要朗读的文本,在我们的组件中有const [ourText, setOurText] = useState("")
  • 在这之后,我们创建了将变量msg作为参数的speechHandler函数。然后我们将msg.text = ourTextwindow.speechSynthesis.speak(msg)一起放入这个函数中。当我们按下扬声器按钮时,这个方法就会执行。
  • 然后我们给我们的 HTML 添加一个``字段,并赋予它一些属性:type='text', value={ourText}placeholder='Enter Text'onChange={(e) => setOurText(e.target.value)}。属性onChange调用我们的匿名函数,并最终将变量ourText设置为我们输入的任何文本字符串。
  • 最后,我们将一个``添加到我们的 HTML 中,并将speechHandler(msg)函数传递给它。

步骤 5 -测试我们的应用

现在一切就绪,让我们测试我们的应用程序。确保 React 应用程序正在运行,并刷新应用程序的浏览器窗口。

输入一些文本,然后按下朗读按钮。你现在应该会听到一个机器人的声音为你朗读你的文本。

带 React 的语音转文本

在本教程中,我们学习了如何使用 React 创建一个文本到语音的应用程序。如果您想做相反的事情,也就是说您想在 React 应用程序中实现语音转文本,您可以利用 AssemblyAI 语音转文本 API 。我们已经在这篇文章中介绍了如何做到这一点。

Get Started with AssemblyAI for Free

如果你想用简单的 JavaScript 实现语音转文本,我们还为你准备了一个指南。

用 AssemblyAI 进行实时语音识别

原文:https://www.assemblyai.com/blog/real-time-speech-recognition-with-assemblyai/

实时抄录是只有法庭记者才能吹嘘的超级技能。但幸运的是,我们不需要学习如何快速打字来快速获得音频的转录。得益于 AssemblyAI 的实时语音识别端点,设置一个可以监听音频并将其转化为文本的 python 脚本非常简单。

在这个视频中,我们将看到如何在 pyaudio、web sockets 和异步函数的帮助下在 Python 上创建这个脚本。该应用程序将能够通过麦克风收听音频输入,并实时显示转录。我们将把这段代码集成到一个简单的 Streamlit 应用程序中,展示带有一点交互性的实时语音识别。

立即观看:

https://www.youtube.com/embed/5LJFK7eOC20?feature=oembed

想跟着去吗?

免费获得自己的 AssemblyAI API 令牌!

Get it Now

用 Python 实现实时语音识别

原文:https://www.assemblyai.com/blog/real-time-speech-recognition-with-python/

https://www.youtube.com/embed/TQcQgU5iECM?enablejsapi=1&origin=https://www.assemblyai.com

概观

在本教程中,我们将使用 AssemblyAI 的实时转录功能从麦克风实时转录。请注意,这是付费功能。我们将使用 Python PyAudio 库来传输来自麦克风的声音。我们将使用 python websockets 库来连接 AssemblyAI 的流式 websocket 端点。

首先我们将讨论如何安装 Python PyAudio 和 websockets。然后,我们将介绍如何使用 PyAudio、websockets 和 asyncio 来实时转录我们的麦克风音频。

先决条件

  • 安装 PyAudio
  • 安装 Websockets

安装 PyAudio

如何在 Mac 上安装 PyAudio

您可能会收到无法找到 portaudio 的错误消息。您可以使用安装 portaudio

brew install portaudio

安装 portaudio 后,您应该能够像这样安装 PyAudio:

pip install pyaudio

如何在 Windows 上安装 PyAudio

您可能会得到一个关于 Microsoft C++的错误消息,在这种情况下,您需要在网上找到正确的 wheel 并安装该 wheel,安装方式同上,但将“pyaudio”替换为您的。whl 文件。

安装网络套接字

我们可以用

pip install websockets

获取您的 AssemblyAI API 密钥

安装 pyaudio 和 webhooks 后,我们需要从 Assembly 中获取 API 密钥。你会在控制台上找到你的钥匙,我在下面做了标记。

如果你想学习本教程,并且你有一个免费帐户,你可以通过点击下面的升级按钮进行升级。

步骤:

  1. 用 Python PyAudio 打开一个流
  2. 创建一个异步函数来发送和接收数据
  3. 使用 async 循环运行(1 分钟后自动超时)
  4. 在终端中运行它并说话

用 Python PyAudio 打开一个流

我们要做的第一件事是使用我们刚刚安装的 Python 的 PyAudio 库打开一个流。为此,我们需要指定每个缓冲区的帧数、格式、通道数和采样率。我们的自定义流将使用每个缓冲区 3200 帧、PyAudio 的 16 位格式、1 个通道和 16000 Hz 的采样率。

import pyaudio

FRAMES_PER_BUFFER = 3200
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
p = pyaudio.PyAudio()

# starts recording
stream = p.open(
   format=FORMAT,
   channels=CHANNELS,
   rate=RATE,
   input=True,
   frames_per_buffer=FRAMES_PER_BUFFER
)

创建一个异步函数来发送和接收数据

现在我们已经打开了 Python PyAudio 流,我们可以将音频从麦克风传输到我们的程序,我们需要将它连接到 AssemblyAI 实时 websocket 来转录它。我们将从创建一个异步函数开始,该函数将同时调度发送和接收数据。为此,我们需要在函数内部打开一个 websocket 连接,并创建另外两个使用 open websocket 的异步函数。我们将使用 Python asyncio 的 gather 函数同时等待发送和接收函数的执行。

我们的异步发送函数将不断尝试以我们打开 Python PyAudio 流时的每个缓冲区帧数的速率传输数据,将该数据转换为 base64,解码为字符串格式,并在请求中将格式化的数据发送到 AssemblyAI websocket 端点(断开连接的情况除外)。我们的异步接收函数将不断尝试打印出从端点接收的数据(如果有的话)。然后我们使用asyncio.gather来等待两个函数并发执行。

import websockets
import asyncio
import base64
import json
from configure import auth_key

# the AssemblyAI endpoint we're going to hit
URL = "wss://api.assemblyai.com/v2/realtime/ws?sample_rate=16000"

async def send_receive():
   print(f'Connecting websocket to url ${URL}')
   async with websockets.connect(
       URL,
       extra_headers=(("Authorization", auth_key),),
       ping_interval=5,
       ping_timeout=20
   ) as _ws:
       await asyncio.sleep(0.1)
       print("Receiving SessionBegins ...")
       session_begins = await _ws.recv()
       print(session_begins)
       print("Sending messages ...")
       async def send():
           while True:
               try:
                   data = stream.read(FRAMES_PER_BUFFER)
                   data = base64.b64encode(data).decode("utf-8")
                   json_data = json.dumps({"audio_data":str(data)})
                   await _ws.send(json_data)
               except websockets.exceptions.ConnectionClosedError as e:
                   print(e)
                   assert e.code == 4008
                   break
               except Exception as e:
                   assert False, "Not a websocket 4008 error"
               await asyncio.sleep(0.01)

           return True

       async def receive():
           while True:
               try:
                   result_str = await _ws.recv()
                   print(json.loads(result_str)['text'])
               except websockets.exceptions.ConnectionClosedError as e:
                   print(e)
                   assert e.code == 4008
                   break
               except Exception as e:
                   assert False, "Not a websocket 4008 error"

       send_result, receive_result = await asyncio.gather(send(), receive())

使用 async 循环运行(1 分钟后自动超时)

我们已经完成了函数的编写,该函数将从我们的 PyAudio 流向 AssemblyAI websocket 异步发送和接收数据。现在我们将使用 asyncio 在一个循环中运行它,直到我们使用下面的代码行得到一个超时或错误。

asyncio.run(send_receive())

在终端中运行它并说话

这就是我们需要做的所有编码。现在,我们打开终端,运行我们的程序。应该和本文开头链接的视频很像!

准备好自己尝试了吗?

获得免费的 API 令牌!!

[Get It Now](Get a free API Token!!)

使用(深度)Q 学习的强化学习解释

原文:https://www.assemblyai.com/blog/reinforcement-learning-with-deep-q-learning-explained/

在本教程中,我们学习强化学习和(深度)Q 学习。

在之前的两个视频中,我们解释了监督非监督学习的概念。强化学习是机器学习领域的第三类。

这一领域近年来变得非常受欢迎,尤其是在视频游戏中,人工智能学习玩像国际象棋、贪吃蛇或越狱这样的游戏。

我们将涵盖:

  • 什么是强化学习
  • 什么是状态/行动/奖励
  • 什么是 Q 学习
  • Q 学习的一个例子
  • 基于神经网络的深度 Q 学习

如果你想看,你可以在这里看这个教程的视频版本:

https://www.youtube.com/embed/kEGAMppyWkQ?feature=oembed

理念和定义

RL 背后的想法是,软件代理通过与环境交互来学习,然后接受执行动作的奖励或惩罚。通过这种方法,代理可以教会自己变得更好并改进其行为,以最大化其预期回报。

强化学习的概念来自于我们的自然经验。想象你是个孩子,第一次看到壁炉。你喜欢温暖的天气。它是正的,所以你得到正的回报。

Learning in an environment

比方说,你伸出你的手,试着触摸火。现在你的手热了,所以很疼,留给你一个负面的奖励(或惩罚)。从这次经历中你可以了解到,火(环境)可以是好的也可以是坏的(奖励/惩罚),这取决于你如何与它互动(你的行动)。

这正是强化学习的工作原理。这是通过奖励和惩罚从环境中的行为中学习的计算方法。

q 学习

这种方法的一个具体实现是 Q 学习算法。这是一种基于所谓 Q 表的基于值的方法。

对于每个状态下的每个行为, Q 表计算最大预期未来奖励。有了这些信息,我们就可以选择奖励最高的行动。

让我们看一个具体的例子来说明这一点。

假设我们想教一个人工智能如何玩贪吃蛇游戏。在这个游戏中,蛇试图在不撞到墙或自己的情况下够到并吃掉食物。我们可以在 Q 表中列出动作和状态。柱子将是蛇可以做的四种可能的动作,向左、向右向上向下。并且状态可以是当前方向,所以也是。这是几排。

我们可以添加更多的状态来描述当前的情况。例如,我们可以描述食物的位置,并添加状态食物在蛇的左边右边向上,或向下

Q-Table

我们可以通过添加更多的信息来建立状态,例如关于墙的信息,但是为了简单起见,我们在这里把它省略了。我们添加的状态信息越多,我们拥有的关于环境的信息就越多,但是我们的系统也会变得越复杂。

定义好行和列后,每个单元格的值将是给定状态和行为的最大预期未来回报。我们称之为 Q 值

到目前为止一切顺利。但是我们如何计算 Q 值呢?

q 学习算法

有趣的部分来了。我们不以固定的方式实现 Q 值计算。相反,我们用迭代的方法改进 Q 表。这就是所谓的培训或学习过程。

Q 学习算法是这样工作的:

  1. 初始化所有 Q 值,例如用零初始化
  2. 基于当前最佳 Q 值在当前状态 s 中选择一个动作 a
  3. 执行此动作 a 并观察结果(新状态s’)。
  4. 测量此动作后的奖励
  5. 用称为贝尔曼方程的更新公式更新 Q。

重复第 2 步到第 5 步,直到学习不再进步,我们应该最终得到一个有用的 Q 表。然后,您可以将 Q 表视为一个“备忘单”,它总是告诉您给定状态的最佳操作。

但你可能想知道,当我们所有的值都是 0 时,我们如何在开始时选择最佳行动?

勘探与开发的权衡

这就是勘探与开发权衡发挥作用的地方。开始时,我们随机选择动作,以便代理可以探索环境。但是,我们得到的训练步骤越多,我们就越减少随机探索,而是使用开发,所以代理利用它所拥有的信息。

这种权衡在计算中由通常称为ε(ɛ)参数的参数控制。

报酬

如何衡量回报取决于我们自己。我们应该为游戏想出一个好的奖励系统。在贪吃蛇游戏的情况下,如果蛇吃了一个苹果,我们可以给予 10 分的奖励,如果蛇死了,我们可以给予-10 分的奖励,其他正常移动为 0。

现在有了所有这些元素,我们就可以检验贝尔曼方程了:

这里的想法是像这样更新我们的 Q 值:

Bellman equation

贴现率是一个介于 0 和 1 之间的值,它决定了相对于近期的回报,代理人对远期回报的关注程度。

现在我们得到了所有我们需要的信息,这样我们就可以通过使用这种迭代学习的方法得出一个好的 Q 表!

深度学习

深度 Q 学习利用了 Q 学习的思想,并更进一步。不使用 Q 表,我们使用一个神经网络,它获取一个状态,并基于该状态近似每个动作的 Q 值。

Q-Learning vs Deep Q-Learning

我们这样做是因为使用一个经典的 Q 表不太具有可伸缩性。它可能对简单的游戏有效,但是在一个有几十种可能的动作和游戏状态的更复杂的游戏中,Q 表将很快变得复杂,并且不再能有效地解决。

所以现在我们使用深度神经网络获取状态作为输入,并为每个动作产生不同的 Q 值。然后,我们再次选择具有最高 Q 值的动作。学习过程仍然与迭代更新方法相同,但是我们不是更新 Q 表,而是更新神经网络中的权重,从而提高输出。

这就是深度 Q 学习的工作原理!

最后的话

我希望这篇文章能给你一个简单的强化学习的介绍。如果你喜欢它,请确保也在我们的 YouTube 频道上观看相应的视频,并在 Twitter 上分享这篇文章!

寻找更多这样的内容?

订阅我们的时事通讯。

Subscribe here

资源

综述-用于会话中情感识别的图形卷积神经网络

原文:https://www.assemblyai.com/blog/review-a-graph-convolutional-neural-network-for-emotion-recognition-in-conversation/

在本周的深度学习论文综述中,我们看下面这篇论文:DialogueGCN:一个用于会话中情感识别的图卷积神经网络。

这篇论文有什么令人兴奋的地方

本周的论文有趣地利用了自动 ERC(对话中的情感识别)中的关系图卷积网络。它解决了现有作品在多人对话中的局限性。如图 1 所示,来自说话者 1 的“好的”注意到说话者 2 的抱怨。基于 RNNs 的方法会将其分类为中性,因为它忽略了说话者级别的依赖性。这篇论文是令人兴奋的,因为它提供了一种新的解决方案,将话语视为有向图,并用关系转换对它们进行建模。

*

Fig 1.*

论文的主要发现

近年来,图神经网络已被证明在建模图中节点之间的依赖关系方面是有效的。它通过定义边关系和关系图变换,有效地捕捉时间依赖和说话者依赖。从经验上看,它证明了在长时间输入的多方对话中识别情绪的优越性。

我们的外卖

关键是将这些丰富的依赖性编码到话语的表达中。所提出的模型 DialogueGCN 使用 RNNs(在本文中是双向 GRUs)来嵌入基于序列的话语,这些话语然后被用于构建关系图并被传递给 GCN 栈。

*

Fig 2.*

关系图由{节点,边权重,边关系} 组成。这些节点是话语表示,它们在一个语境窗口(即从过去的 15 个话语到未来的 15 个话语)内相互连接。边权重是图变换中的可学习权重。最重要的是,基于(1)节点彼此的相对位置来定义边权重(即,第一话语与第三话语具有“朝向过去”的边关系);(2)节点的说话者类型(即,来自说话者 1 的话语对于来自说话者 2 的话语具有“说话者 1 朝向说话者 2”)。

通过关系图变换,节点聚集来自共享相同关系的节点的邻域信息,这将说话者依赖性和时间依赖性编码到节点特征。最后,GCN 输出被连接到 RNNs 编码输出,并被传递到完全连接的分类器,以获得情感标签。

回顾-阿尔伯特:一个自我监督学习语言表达的简装伯特

原文:https://www.assemblyai.com/blog/review-albert/

本周的深度学习论文评论是 ALBERT:一个用于语言表达自我监督学习的 Lite BERT】。

这篇论文有什么令人兴奋的地方

transformer-type 模型的世界中已经变得很明显,特别是当它们属于自然语言处理时,在预处理自然语言表示时增加模型大小通常会导致下游任务的性能提高。然而,这种想法有局限性,在某种程度上,由于 GPU/TPU 内存的限制和较长的训练时间,保持增加模型大小变得困难。

本文旨在通过提出两种参数缩减技术来解决这一问题,这两种技术显著减少了 BERT 的内存消耗和训练时间。第一种技术是嵌入参数的因子分解,它将单词块嵌入大小与隐藏层大小分离,从而显著减少嵌入参数的数量。第二种技术是跨层参数共享,其中跨层共享前馈网络和注意力参数的权重,这进一步减少了需要训练的总参数的数量。

主要发现

下面是本文分析的 BERT 和 ALBERT 模型的主要构型:

上述配置已经过一系列不同的下游任务标准基准测试。实验表明,与 BERT-large 相比,ALBERT 的最佳配置在 GLUE、RACE 和 SQuAD 基准上建立了新的最先进的结果,同时具有更少的参数。下表显示了不同的 ALBERT 和 BERT 配置在这些基准测试中的对比情况。

这里需要注意的一点是,尽管 ALBERT-xxlarge 在上表中的表现比 BERT-large 好,但训练速度明显下降。作者承认这一点,并解释说这是因为 ALBERT-xxlarge 是一个更大的网络,尽管它的参数明显更少。然而,作者实验了在大致相同的时钟时间内训练两种配置,而不是相同的训练步骤数,ALBERT-xxlarge 仍然优于 BERT-large,如下表所示。

我们的外卖

本文表明,有一些方法可以在不增加参数数量的情况下增加模型的大小(层数),并且仍然可以实现最先进的性能。这是一个强大的想法,特别是在 GPU/TPU 内存可访问性有限的情况下。这将是令人兴奋地看到新的研究旨在减少超参数的数量,以及加快训练和推理时间的阿尔伯特一样的变压器。

参考

综述- data2vec:语音、视觉和语言自我监督学习的一般框架

原文:https://www.assemblyai.com/blog/review-data2vec-a-general-framework-for-self-supervised-learning-in-speech-vision-and-language/

本周的深度学习论文综述是 data2vec:语音、视觉和语言自我监督学习的通用框架

这篇论文有什么令人兴奋的地方

直到最近,自我监督学习技术(SSL)是在单一模式下开发的。这意味着自我监督学习算法从图像、语音、文本和其他模态中学习的方式存在很大差异。然而,data2vec 不再需要为不同的模态使用不同的方法。它是第一个对语音、NLP 或计算机视觉使用相同学习方法的 SSL 框架,并取得了最先进的结果。

主要发现

自我监督学习使计算机能够通过观察然后计算出图像、语音或文本的结构来学习。这种方法在伸缩方面有很多优势,特别是在低数据资源场景中,比如针对不同语言和方言的 ASR 。今天对自我监督学习的研究几乎总是集中在一个特定的模式上。Data2vec 是第一个适用于不同模式的 SSL 框架。

data2vec 预测包含来自整个输入的信息的上下文化的潜在表示,而不是预测特定模态的目标,如单词、视觉标记或人类语音的局部性质。

Data2vec 通过训练模型来预测它们自己的输入数据的表示,而不考虑模态,从而进行了简化。核心思想是基于使用标准变压器架构的自蒸馏设置中输入的屏蔽视图,预测全部输入数据的潜在表示。通过关注这些表示——神经网络的层——而不是预测视觉符号、单词或声音,单个算法可以处理完全不同类型的输入。这消除了在学习任务中对特定模态目标的依赖。

Data2vec 使用教师网络首先从一幅图像、一段文本或一段语音计算目标表示。然后,他们屏蔽部分输入,用学生网络进行处理,然后预测老师的潜在表现。学生模型必须预测全部输入数据的表示,即使它只有部分信息的视图。教师网络与学生模型相同,但权重稍微有些过时。

例如,在语音方面,最流行的 SSL 方法是 wav2vec 2.0 和 HuBERT。Wav2vec 2.0 和 HuBERT 离散化表示以训练模型。代替这个数据,2vec 直接预测没有量化的上下文化的潜在表示。这导致了比 Librispeech 数据集中当前最先进的结果更好的结果。

结果

作为一家语音公司,我们对语音的 SSL 结果非常感兴趣。在 Librispeech test-other 测试集上,Data2vec 的性能似乎优于其他最先进的 SSL 方法。这些模型在来自 Librispeech (LS-960)的 960 小时音频上作为未标记数据进行训练。

在我们看来,data2vec 非常有趣,由于其简单性和可推广性,它具有更大的潜力。

参考

审查-决策转换器和螺旋

原文:https://www.assemblyai.com/blog/review-decision-transformer-spiral/

本周深度学习的论文点评有 决策变压器:通过序列建模的强化学习螺旋:用于语音预训练的自监督扰动不变表征学习

决策转换器:通过序列建模的强化学习

这篇论文有什么令人兴奋的地方

传统上,使用时间差异(TD)学习来解决强化学习(RL)。决策转换器将 RL 作为一个序列建模任务,目标是在给定当前和过去的状态、行动和奖励以及当前时间步的预期未来奖励的情况下,对下一个行动进行建模。这种方法训练代理人在给定其所有历史和要求其实现的目标(未来累积奖励)的情况下识别要采取的最佳行动。

Photo Credit

主要发现

作者通过对从专家和普通策略中收集的轨迹进行训练,评估了 Atari 和 OpenAI 健身房基准上的决策转换器(DT)。他们发现,DT 的表现优于行为克隆,并与最先进的 T2 时差学习技术相媲美。此外,作者发现 DT 在涉及长期信用分配的任务中特别有能力。例如,在开门任务中,DT 的表现明显优于两个基线,并且只使用随机展开进行训练。

我们的外卖

决策转换器(DT)是将 RL 转换为序列建模问题的第一次成功尝试之一,并且能够实现最先进的性能。

DT 学习的策略通常优于用来生成离线数据的策略,因为它能够将次优轨迹的不同部分“缝合在一起”以找到最优动作。

螺旋:用于语音预训练的自监督扰动不变表示学习

这篇论文有什么令人兴奋的地方

SPIRAL ,从 BYOL 中汲取灵感,提供了一种替代的 ASR 语音预训练方法,声称与 wav2vec2 相比,基础模型的训练成本降低了 80%,大型模型降低了 65%。

Photo Credit

主要发现

研究人员在模型中使用逐步下采样策略,以减少训练过程中所需的计算量。他们推测这以类似于量化的方式消除了冗余。LL-60k 螺旋模型需要 500k 训练步骤和 232 GPU 天,而标准 wave2vec2 模型需要 1000k 训练步骤和 665.6 GPU 天。他们还通过在 Amazon CHiME 数据集上评估这两个模型,表明螺旋模型更好地处理了有噪声的输入。

我们的外卖

SPIRAL 是一种替代的自我监督学习(SSL)预训练方法,通过积极的下采样,可以节省高达 80%的训练成本,同时仍能达到 wav2vec2 的性能。此外,他们提出的噪声扰动机制可能对其他 SSL 管道有所帮助。

Review - JUST:用于多语言自动识别的联合非监督和监督训练

原文:https://www.assemblyai.com/blog/review-just/

本周的深度学习论文综述是 JUST -联合无监督和有监督的多语种 ASR 训练

这篇论文有什么令人兴奋的地方

自我监督学习在过去的几年里取得了巨大的进步。三代 Wav2Vec 已经从经验上证明,以对比方式进行的自我监督学习是一种强大的预训练技术,即使在极低资源的数据上也能实现高精度的下游微调。最新的两批 Wav2Vec2.0 通过利用预训练期间的对比和多样性损失打破了 SOTA 基准。

在对比损失中,特征编码器空间中的一定比例的时间步长被掩蔽,目标是在一组干扰项中为每个时间步长识别正确的潜在语音表示。

分集损失用于通过最大化平均 softmax 分布的熵来鼓励每个码本中条目的平等使用,从而增加量化码本表示的使用。

通过在微调期间仅使用 10 分钟的标记数据,上述方法能够在 LibriSpeech 数据集的测试和开发集上实现 7.9 和 8.2 WERs。

由于 Wav2Vec2 方法的实际效率和易于再现性,谷歌研究人员提出了一种新的 Wav2Vec2 启发的预训练技术——称为只是——用于多语言 ASR

JUST 采用五阶段建模架构,由三个阶段级无监督和有监督损失函数支持。使用对比 MLM(掩蔽语言建模)和 RNN-T 损失,该模型在多语言数据集上对音频-文本对进行联合预训练,然后在特定数据集上进行微调。新的训练方法在低资源语言 ASR 设置中比第一阶段 Wav2Vec2 XLSR(大型)网络产生 32%的性能提高。

主要发现

的 5 个阶段正好:

1。特征编码器- CNN

基本上,卷积子采样在特征维数和序列长度方面都减少了 4 倍。

2。量化器

Gumbel-softmaxed 潜在向量到码本令牌匹配。码本要么是可学习的,要么是不可学习的——作者没有发现性能上的明显差异。

3。对比网络整合程序块

多头自关注,深度方向卷积和前馈层。

对比网络从编码器中读取隐藏的潜在特征。对于掩蔽,选择一组随机的特征并用随机向量代替。

对于局部对比级优化,使用对比损失:

4。 MLM 网-康福尔街区

多头自关注,深度方向卷积和前馈层。对比网络向 MLM 网络提供对比上下文向量。MLM 网的输出是高级上下文向量,用于通过线性层进行令牌 id 预测。预测的记号 id 与来自量化器的目标记号 id 通过标准交叉相关损失进行比较。

最终的无监督损失看起来像:

5。解码器- 2 层 RNN 传感器

MLM 网络的输出通过 Swish 激活、Batchnorm 层,然后馈送到解码器 RNN-T。解码器的词汇大小基本上是从预训练期间使用的所有语言汇集的统一字素集的大小。

最终的预训练优化目标是无监督损失和有监督损失的组合:

微调:

作者提出了两种训练方法:

  1. 无监督损失的预训练和无监督损失和有监督损失的微调。
  2. 两种情况下的预训练:无监督和有监督损失以及仅使用有监督损失的微调。

正如作者所展示的,第二种方法比第一种方法产生的平均要好 10%。

我们的外卖

从理论上讲,在纯无监督的训练设置中,多级优化更有直观意义。最重要的是,有监督的 RNN-T 损失与无监督的损失的结合导致更有用的信息提取、更好的概括和更健壮的上下文化标记预测。

就其本身而言,RNN-T 在令牌预测方面优于标准 CTC 网络——只要它与自回归预测器和加入器联合训练。如许多研究所示,RNN-T 导致每个令牌更真实的概率,而自回归架构解决了帧相关问题。因此,与 CTC 模型相比,RNN-T 输出中出现断章取义的可能性更低。

例如:

CTC:我八个一个苹果。

RNN-T:我吃了一个苹果。

报告结果:

**基于实践结果和理论创新方法,JUST 是 Wav2Vec2 架构的巨大飞跃。此外,它利用开源的MLS 数据集用于无监督和有监督的训练设置的音频-文本对。MLS(连同附加数据)也用于 Wav2Vec2 预训练,但是仅使用没有标签的语音集。只要仅通过使用 MLS 数据集进行预训练而使优于 Wav2Vec2,它在技术上具有相同的数据要求(实际上更少),因为 MLS 由音频和文本对组成。
**

回顾-感知者:具有重复注意的一般感知

原文:https://www.assemblyai.com/blog/review-perceiver/

本周深度学习论文综述为感知者:迭代注意的一般感知

这篇论文有什么令人兴奋的地方

今天的深度学习主要集中在为特定模态(如语言、视觉或语音)构建的模型上。最近的历史就是这样,原因有二:首先,设计处理特定数据类型的架构更容易;第二,模型通常被设计为利用数据,例如卷积神经网络和图像。在这项工作中,作者在 Transformer 架构的基础上,利用不对称注意力机制来训练图像、音频、视频和点云数据的单一模型,同时优于这些领域的特定架构。

主要发现

将变压器扩展到高维音频/视频数据导致了许多定制架构的开发。通常,这些模型在将高层输入送入转换器之前,会利用多个低层卷积层来降低数据的维度。由于转换器架构的二次方复杂性,这通常是一个要求,其中每个令牌都要关注其他令牌。

例如,一个 224x224 的图像包含 50176 个像素,这比像 BERT 这样的模型所能处理的数据要多得多(限制为 512 个输入令牌)。通过利用不对称的注意机制和潜在的瓶颈,作者能够扩展转换器以处理成千上万的输入,同时保持模型参数的数量最少。

来源- 感知者:具有迭代注意的一般感知

上图显示了这种技术是如何工作的,它将字节数组的部分按顺序加入到一组潜在字节中,然后这些潜在字节通过潜在字节空间中的一个转换器。由于潜在数组的大小明显小于字节数组的大小,转换器的计算要求与输入分离,从而允许使用更深的模型。总的来说,这导致总复杂度为 O(MN + LN ),其中 M 是字节数组的维度,N 是潜在数组的维度,L 是变换器的深度。

我们的外卖

通过这篇论文、 Data2vec 和类似的工作,深度学习的趋势正在朝着拥有通用模型和算法的方向发展,这些模型和算法可以轻松扩展到任何数据类型。感知者是这条道路上的一个里程碑,因为它有助于解决 transformer 架构的一个主要挑战,该挑战阻止了它直接用于高维数据。看到未来的工作建立在感知者的想法上,并继续推动基于模型的概化的极限,这将是有趣的。

参考

评论-无文本韵律感知的生成口语建模

原文:https://www.assemblyai.com/blog/review-text-free-prosody-aware-generative-spoken-language-modeling/

在本周的深度学习论文综述中,我们来看下面这篇论文:无文本韵律感知的生成口语建模

这篇论文有什么令人兴奋的地方

这篇论文是第一篇发表的将韵律作为生成性口语建模的一个特征的文章。先前已有关于在没有韵律信息的情况下生成口语语言建模的工作,以及先前关于使用韵律特征的区别性语音分类任务的工作,但这项工作是第一次将它们结合在一起。

这里的基本应用是将语音输入作为提示,并生成连贯一致的波形语音输出。这项研究更“宏观”的目的是改进基于语音的对话系统。

驱动假设是文本,即语音输入和任何 NLP 风格的分析之间的事实上的中间表示,是次优的:即使当充分可用时,文本也是捕获语音的有损耗介质。

通过结合韵律,并直接在口语领域建模,而不是通过文本级联,这项工作试图更优化的表示。

作者利用的功能是自我监督,发现代表语音内容的声学单元;和量化的、说话者均值归一化的log F0二进制以及表示韵律的单位持续时间。他们利用 transformer 语言模型对这三个输入流进行联合建模,并使用 HiFi-GAN 声码器将 LM 输出转换为波形。当他们测量生成的语音的准确性、一致性和表现力时,他们设计新的度量来补充现有的度量。

这篇论文的主要发现

他们发现韵律输入特征普遍改善了内容建模和韵律建模。还有其他几个发现,但这是笑点:韵律有帮助!

我们的外卖

这项工作非常非常酷,但我们要强调的是,它是一个新颖的、探索性的研究方向。当听论文发布的样本时,我们发现它们很难理解——我们认为这会使人类评估任务变得极其困难。

对我们来说,这项工作已经明确了目的地,但留下了不确定的道路:口语建模似乎不可避免地将放弃级联,走向端到端,但我们肯定还没有到那一步!

回顾——托西根和知识蒸馏遇到了开放集半监督学习

原文:https://www.assemblyai.com/blog/review-toxigen-knowledge-distillation-meets-open-set-semi-supervised-learning/

本周的深度学习论文综述有 TOXIGEN:用于对抗性和隐性仇恨言论检测的大规模机器生成数据集知识蒸馏遇上开集半监督学习

TOXIGEN:一个用于敌对和隐含仇恨言论检测的大规模机器生成数据集

这篇论文有什么令人兴奋的地方

本文讨论了大约 13 个少数群体的 274k 有毒和良性陈述(对半分割)的大规模机器生成数据集的创建。这是迄今为止最大的仇恨言论检测数据集,显示了基于演示的提示和生成性语言模型在数据集创建中的适用性。

作者证实,94.5%的有毒例子被标注者标记为仇恨言论,90%的生成文本可能被误认为是人写的。

通过在波束搜索期间使毒性分类器对抗文本生成器,对抗分类器环路解码算法可用于控制输出文本的毒性。

提示允许产生隐含的毒性,而没有亵渎或诋毁,这通常是更难识别和捕捉的仇恨言论类型。数据集分析发现,98.2%的 TOXIGEN 声明是仇恨言论的隐含形式。

Source

主要发现

利用 TOXIGEN 作为“额外”仇恨言论检测数据集来扩充其他数据集,与三个现有的人类编写的隐式有毒数据集相比,持续提高了微调性能(+7-19%)。作者为更广泛的社区开放源代码和 TOXIGEN 数据集

我们的外卖

TOXIGEN 是一个用于训练内容审核模型的有用数据集。

对抗性分类器环路解码算法在各种场景中具有更普遍的用途。

知识蒸馏遇到开集半监督学习

这篇论文有什么令人兴奋的地方

知识蒸馏方法试图将大模型的建模能力压缩到较小的模型中。这是使强大的深度学习模型在经济上可行的关键部分。这通常是通过直接从教师的输出中学习来完成的,或者从其表示或输出分布中学习,或者从两者中学习。

本文更进了一步,要求学生的表述可以被教师的预测矩阵转换并有效使用。这就要求学生的陈述和老师的陈述有同样多的信息和相似的结构。

最后,他们尝试在知识提炼训练中使用未标记的训练数据,这进一步提高了学生对未知数据的概括能力。

Source

主要发现

在许多不同的模型中,他们的方法确实优于其他知识蒸馏方法。使用知识蒸馏的无标记分布外(OOD)数据优于竞争的 OOD 重新加权方法。

我们的外卖

知识蒸馏是将强大、庞大且昂贵的模型压缩成强大、小型且经济的模型的有效方法。

在执行知识蒸馏时,不需要约束自己仅使用原始的标记数据集;还可以加入额外的未标记数据集来帮助泛化。

Review - VICReg:用于自监督学习的方差-不变性-协方差正则化

原文:https://www.assemblyai.com/blog/review-vicreg-variance-invariance-covariance-regularization-for-self-supervised-learning/

本周深度学习论文综述是 VICReg:用于自监督学习的方差-不变性-协方差正则化

这篇论文有什么令人兴奋的地方

直到最近,计算机科学中占主导地位的自我监督学习技术(SSL)都是基于对比目标的。这大致意味着模型被训练成 1)最小化相似图像的表示(例如同一源图像的两个增强视图)之间的距离,同时 2)将不同图像的表示的距离推开。

通过这样做,模型在没有标签的情况下学习有用的表示,并且这可以应用于任何特定的下游任务,例如图像分类。

有人可能会问,除了将相似图像的表示集合在一起之外,为什么有必要将不同图像的表示分开。不就是做拉的工作一样好,而且因为简单而更优雅吗?

事实证明,如果只应用拉动项,那么模型将简单地学习问题的简单解决方案——它将为所有输入图像产生相同的表示。包含推动项(这使得客观对比)有效地防止了这种表示崩溃。

然而,最近出现了几种 SSL 技术,它们不再需要对比样本: BYOL巴洛双胞胎维克雷格。三者的性能相似,但我将讨论 VicReg,因为在我看来,它是三者中理论上最合理的。

主要发现

首先,这里是问题公式:

给定一个小批图像,我们将应用随机数据扩充两次,以生成两个分支 X 和 X’。设 Z 和 Z’是 X 和 X’的表示(编码器输出)。设 I 表示批索引- zi 和 zi’是同一图像的两个不同增强的表示。设 j 表示特征维数索引——zij 表示表示 zi 的第 j 个特征。

VicReg 代表方差-不变性-协方差正则化。损失目标的名称中包含这三个术语。下图演示了架构和损失术语。请注意,Y 是将用于下游任务的中间表示,而我们正在对最终表示 z 执行 VicReg 损失

  1. 不变性:

这个术语只是最小化同一图像的两个不同放大的表示之间的距离。这应该是直观的;但是,请注意,如果没有额外的正则项,这个项本身将导致表示崩溃。

2.差异:

这一项是一个铰链损失,它使一批表示之间的每个特征的方差保持在某个阈值γ以上。直观地说,这意味着:我们希望每个要素的值在批处理中的各个制图表达之间有所不同。这确保了表示不会全部折叠到同一个向量。

3。协方差:

该术语可最小化同一表示的不同要素之间的协方差。当两个变量之间的协方差很小时,这意味着这两个变量不相关。因此,该术语可确保制图表达中的不同要素彼此不相关。

为什么这是一个理想的目标?好吧,假设我们的表示有九个维度,并且假设所有九个特征彼此高度相关(一个特征的值告诉我们其他八个特征的值)。如果是这样的话,我们就没有理由拥有九个特性——只有一个就够了。换句话说,当这些特征相互关联时,在相同的空间量中包含的信息更少。另一方面,通过强制特征不相关,我们鼓励它们编码更多的信息。

总而言之,VicReg 的目标就是这三项的总和。

我们的外卖

从经验上看,VicReg 比对比技术表现更好。它的表现只比其他非对比技术(BYOL,SwAV)稍差,但在我看来,它更有趣,并且由于其简单性和理论透明性而具有更大的潜力。

播客、新闻广播和社交媒体上的语音转文本准确性

原文:https://www.assemblyai.com/blog/reviewing-speech-to-text-accuracy-across-12-news-broadcasts-social-media-videos-and-podcasts/

NBC 等媒体公司、YouTube 等社交平台以及 Meltwater 等媒体监控解决方案使用语音转文本技术来支持从隐藏式字幕到复杂内容审核的一切工作。开发人员现在可以利用语音识别来快速、经济地深入了解他们的内容。

在这份报告中,我们查看了来自各种来源的 12 个不同的音频/视频文件(下面将更详细地显示),并审查 AssemblyAI、AWS transcriptor 和 Google Speech-to-Text 能够自动转录这些文件的准确性。

除了审查语音识别准确性,我们的研究团队还审查了 AssemblyAI 在这 12 个音频/视频文件上独特的内容安全主题识别关键词检测功能的结果。该报告旨在作为比较市场上最佳自动语音识别解决方案的参考点。

语音识别准确性

我们的数据集

我们在互联网上搜寻各种各样的媒体内容——从时事新闻广播,到抖音上用户生成的社交媒体视频,再到 Spotify 上的公共播客。随机选择,我们的目的是在您开始自己的比较分析时,为您提供来自一系列来源的内容类型的健康样本量。

以下是关于我们数据集的更多信息:

https://airtable.com/embed/shrEUStNTdBqKkiAD?backgroundColor=blue

我们如何计算精确度

  1. 首先,我们通过 API(assembly ai、Google 和 AWS)自动转录数据集中的文件。
  2. 第二,我们由人类转录员转录数据集中的文件,准确率接近 100%。
  3. 最后,我们将 API 的转录与我们的人类转录进行比较,以计算单词错误率(WER)——更多内容见下文。

下面,我们概述了每个转录 API 在每个音频文件上实现的准确度分数。每个结果都超级链接到人类转录本与每个 API 自动生成的转录本之间的差异。这有助于突出人工转录和自动转录之间的关键差异。

https://airtable.com/embed/shrSNYEdKD8NoRNDd?backgroundColor=blue

精确度平均值

https://airtable.com/embed/shrfrbB1QRzbt43iq?backgroundColor=blue

WER 方法论

使用单词错误率(WER) 计算上述准确度分数。WER 是计算自动语音识别系统准确度的行业标准度量。WER 将每个文件的 API 生成的转录与人类转录进行比较,计算自动化系统进行的插入、删除和替换的数量,以导出 WER。

在计算特定文件的 WER 之前,真实(人工转录)和自动转录都必须被规范化为相同的格式。这是团队经常错过的一步,最终导致误导性的 WER 数字。那是因为根据 WER 算法,“你好”和“你好!”是两个不同的词,因为一个有感叹号,而另一个没有。这就是为什么,为了执行最准确的 WER 分析,所有标点符号和大小写都从人工和自动抄本中删除,并且所有数字都被转换为口语格式,如下所述。

为了 example:‍

truth -> Hi my name is Bob I am 72 years old.

normalized truth -> hi my name is bob i am seventy two years old

内容安全检测

保护品牌和用户免受有害内容的影响对于许多应用和使用案例来说至关重要。借助 AssemblyAI 的内容安全检测功能,我们可以标记您的转录中包含仇恨言论、赌丨博、武器或暴力等敏感内容的部分。AssemblyAI API 将标记什么的完整列表可以在 API 文档中找到。

为了调节当今互联网上的音频和视频内容,需要大量人员手动检查这些内容,以标记任何可能滥用或违反平台政策的内容。例如,脸书雇佣了成千上万的人来人工审查他们平台上的帖子,并标记那些包含仇恨言论的帖子。

试图自动化这一过程的遗留软件采用“黑名单”方法——寻找特定的词(例如,“枪”),以便标记敏感内容。这种方法非常容易出错,而且很脆弱。举个例子 “那个卷饼是炸弹”

由于 AssemblyAI 的内容安全检测模型是使用最先进的深度学习模型构建的,因此我们的模型在决定何时标记一段内容时会查看一个单词/句子的整个上下文——我们不依赖于容易出错的反向列表方法。

下面我们回顾一下 AssemblyAI 的内容安全检测功能对上述数据集的检测结果:

https://airtable.com/embed/shrziYf4yGdvFsw1M?backgroundColor=blue

话题检测

除了内容安全检测,我们还回顾了 AssemblyAI 的话题检测功能检测到的话题和关键词。开发人员可以使用主题检测功能来了解他们的音频/视频文件中讨论的主题。这些信息有助于更好地索引内容和推荐,甚至有助于广告定位。我们的主题检测功能使用 IAB 分类法,可以对转录文本进行多达 698 种可能的 IAB 分类(例如,“汽车>自动驾驶汽车”)。

Key‍word 检测

我们的模型还可以自动从转录文本中提取关键字和短语。下面是一个示例,展示了该模型如何在一个小型转录文本样本上工作。

原始转录:

"Hi I'm joy. Hi I'm Sharon. Do you have kids in school? I have 
grandchildren in school. Okay, well, my kids are in middle school in high
school. Do you think there is anything wrong with the school system
Overcrowding, of course, ..."
"high school"
"middle school"
"kids"
...

使用相同的数据集,我们包括了在以下每个文件中自动检测到的主题和关键字:

https://airtable.com/embed/shrRrOiUfZsQpwrXX?backgroundColor=blue

对您自己的数据进行基准测试

提供商之间的准确性基准测试需要时间和金钱来运行您的内容。我们为任何寻求转录解决方案的团队提供免费的基准报告。要开始使用免费的基准测试报告,您可以点击这里

行动收益电话会议中的情绪分析

原文:https://www.assemblyai.com/blog/sentiment-analysis-in-action/

转录音频很酷,但如何从非结构化数据中获得洞察力呢?有了 AssemblyAI,你可以从你的音频或视频文件中得到更多。

在这个视频中,我们制作了一个网络应用程序,对公司的收益电话会议进行情绪分析。只需输入盈利电话的录音,就能从电话中了解你的观点。大家来学习一下怎么做吧!

https://www.youtube.com/embed/kBoe56CfugY?feature=oembed

综述- SimCLS 和 ref sum-summary 技术

原文:https://www.assemblyai.com/blog/simcls-and-refsum-summarization-techniques/

本周深度学习研究论文有 SimCLS:抽象概括对比学习的简单框架RefSum:重构神经概括

这篇论文有什么令人兴奋的地方

由于训练和测试之间的差异,自然语言生成(NLG)任务,如文本摘要,通常是自然语言处理(NLP)和自动语音识别(ASR) 中最困难的问题。在训练期间,通常使用最大似然估计(MLE)和自回归教师强制的框架来训练这些模型,而在测试期间,在每一步选择最佳令牌来生成摘要。

尽管可以使用波束搜索等技术来解决这一问题,但最终评估指标(用于总结的模糊分数)通常不会完美地映射到用于选择最佳波束的对数似然估计。这些论文介绍了一种“生成然后评估”的方法,其中生成性摘要模型与区别性评分模型相叠加,以生成候选摘要,然后对这些摘要进行评分,从而选择最接近最终评估度量的最佳候选。

主要发现

RefSum 通过统一其基本和元摘要系统,在发布时在 CNN / Daily MailXsum 数据集上设置了最先进的(SOTA)摘要性能。这表明来自波束搜索的最高概率输出并不总是理想的解决方案,并且“评分模型”可以用于进一步重新排序或堆叠来自同一模型或多个模型的各种候选,以提高下游性能。

SimCLS 通过在对比学习环境中训练其评分模型,推进并简化了这一想法,同时也设定了一个新的 SOTA 。这里 BART 被用来生成候选摘要, RoBERTa 被训练来判断候选人在通过分析文本嵌入来总结文本方面做得有多好。

我们的外卖

这些论文表明,采用基本的多模型流水线方法进行摘要可以获得最佳的下游性能。“生成然后评估”方法是一个有趣的研究领域,可能会导致 SOTA 在其他生成任务中的性能,例如语音合成、图像生成和问题回答等。

扬声器二进制化-单声道文件的扬声器标签

原文:https://www.assemblyai.com/blog/speaker-diarization-speaker-labels-for-mono-channel-files/

说话者二进制化(又名说话者二进制化)是根据说话者的身份自动分割音频或视频输入的过程。它可以帮助你回答“谁在什么时候发言?”。

随着最近几年深度学习的应用和进步,自动(自信地)验证和识别说话者的能力现在是可能的。

媒体监控、电话、播客、远程医疗和网络会议等行业几乎总是使用多扬声器的音频和视频。这些同样受到自动化转录进展严重影响的行业,依靠说话者二进制化来完全取代他们工作流程中的人工转录。

扬声器二进制化与最先进的精确度相结合,有可能为任何单声道录音带来巨大的价值。如果你现在有兴趣测试这个,可以查看我们关于扬声器二进制化的 API 文档。

关于语音到文本如何工作的更多信息,您可以在这里了解如何在 PyTorch 中构建一个端到端模型。

扬声器二进制化如何工作

过去,基于 I 向量的音频嵌入技术用于说话人验证和二进制化。然而,随着最近深度学习的突破,基于神经网络的音频嵌入(也称为 d-vectors)已被证明是最好的方法。

更具体地说,具有非参数聚类的基于 LSTM 的 d 向量音频嵌入有助于实现最先进的说话人二进制化系统。

说话人分化的基础设施

步骤 1 - 语音检测使用语音活动检测器(VAD)识别语音并去除噪声。

步骤 2 -语音分段—从音频中提取小段(滑动窗口)&运行 LSTM 网络,为每个滑动窗口生成 D 矢量。

步骤 3 - 嵌入提取-聚集属于该片段的每个片段的 d 向量,以产生逐片段嵌入。

步骤 4 - 聚类--对分段嵌入进行聚类,产生二值化结果。使用我们集成到我们的二进制化系统中的两个聚类算法,用每个发言者的时间戳来确定发言者的数量。

  • 离线聚类- 所有片段嵌入可用后产生说话人标签
  • K 均值聚类: 使用k 均值++ 进行初始化以确定说话人 ek 的数量,我们使用“肘”的导数的条件均方余弦距离(MSCD)在每个嵌入到其聚类质心之间
  • 谱聚类:1)构造亲和矩阵,2)执行细化操作 3)对细化的亲和矩阵执行特征分解 4)使用 K-Means 对这些新嵌入进行聚类,并产生说话者标签

活跃的研究领域

基于神经网络的聚类技术——例如,UIS·RNN

  • 更好地处理多个扬声器同时讲话时的串扰-例如,源分离
  • 提高了在有许多扬声器时检测音频/视频文件中扬声器数量的能力
  • 改进了在背景噪音、音乐或其他频道干扰严重时对嘈杂音频文件的处理

如何使用 AssemblyAI 启用扬声器二进制化

AssemblyAI 可以自动检测音频文件中的扬声器数量,转录文本中的每个单词都可以与其扬声器相关联。

以下代码示例展示了如何在 Python 中打开扬声器标签的情况下提交音频或视频文件进行转录。你可以在我们的 API 文档中查看更多编程语言的代码样本。

*`import requests

endpoint = "https://api.assemblyai.com/v2/transcript"

json = {
  "audio_url": "https://app.assemblyai.com/static/media/phone_demo_clip_1.wav",
  "speaker_labels": True
}

headers = {
    "authorization": "YOUR-API-TOKEN",
    "content-type": "application/json"
}

response = requests.post(endpoint, json=json, headers=headers)

print(response.json())`*

提交文件进行处理后,可以通过发出 get 请求来获得转录结果。

*`import requests

endpoint = "https://api.assemblyai.com/v2/transcript/YOUR-TRANSCRIPT-ID-HERE"

headers = {
    "authorization": "YOUR-API-TOKEN",
}

response = requests.get(endpoint, headers=headers)

print(response.json())`*

您将得到如下所示的 JSON 响应。“话语”键将包含一个“逐个回合”的话语列表,就像它们出现在音频记录中一样。

一个“ ”就是一个“ ”在扬声器通话期间。例如,说话者 A 说“你好”(话轮 1),然后说话者 B 说“你好”(话轮 2)。

*`{
    "id": "5552830-d8b1-4e60-a2b4-bdfefb3130b3",
    "status": "completed",
    "text": "Hi, I'm joy. Hi, I'm sharon. Do you have kids in school. ...",
    # the "utterances" key below is a list of the turn-by-turn utterances found in the audio
    "utterances": [
        {
            # speakers will be marked as "A" through "Z"
            "speaker": "A",
            "confidence": 0.97,
            "end": 1380,
            "start": 0,
            # the text for the entire speaker "turn"
            "text": "Hi, I'm joy.",
            # the individual words from the speaker "turn"
            "words": [
                {
                    "speaker": "A",
                    "confidence": 1.0,
                    "end": 320,
                    "start": 0,
                    "text": "Hi,"
                },
                ...
            ]
        },
        # the next "turn" by speaker "B" - for example
        {
            "speaker": "B",
            "confidence": 0.94,
            "end": 3260,
            "start": 0,
            "text": "Hi, I'm sharon.",
            "words": [
                {
                    "speaker": "B",
                    "confidence": 1.0,
                    "end": 480,
                    "start": 0,
                    "text": "Hi,"
                },
                ...
            ]
        },
        {
            "speaker": "A",
            "confidence": 0.94,
            "end": 5420,
            "start": 2820,
            "text": "Do you have kids in school.",
            "words": [
                {
                    "speaker": "A",
                    "confidence": 1.0,
                    "end": 4300,
                    "start": 2820,
                    "text": "Do"
                },
                ...
            ]
        },
    ],
    # all of the words found in the audio across all speakers
    "words": [
        {
            "speaker": "A",
            "confidence": 1.0,
            "end": 320,
            "start": 0,
            "text": "Hi,"
        },
        {
            "speaker": "A",
            "confidence": 1.0,
            "end": 720,
            "start": 320,
            "text": "do"
        },
        ...
    ]
}`*

扬声器二进制用例

远距离医学

自动标记预约记录上的<patient><doctor>,使其更具可读性和实用性。通过更好的标记/索引以及触发随访和处方补充等操作的能力,简化了向医疗保健 ERP 或数据库的导入。

电话会议

自动标记电话会议录音中的多个发言人,使转录更加有用。这使得销售和支持辅导平台能够分析和显示由界面中的<agent><customer>分割的文本,从而使搜索和导航更加简单。这也有助于触发跟进、票证状态更改等操作。

播客托管

在录音上自动标记播客<host><guest>,无需听音频或视频,即可生动呈现文稿。这对于播客来说尤其重要,因为大多数文件都是在单声道上录制的,并且几乎总是包含多个扬声器。播客托管平台可以使用脚本来推动更好的 SEO,改善搜索/导航,并提供播客用户可能无法获得的见解。

招聘平台

在招聘面试录音上自动标注<recruiter><applicant>。申请人跟踪系统的客户严重依赖电话和视频电话来招募他们的申请人。扬声器二分法允许这些平台在不听音频或视频的情况下,将申请人的回答和招聘人员的提问分开。这也有助于触发一些行动,比如对申请人进行跟进,让他们进入招聘流程的下一阶段。

视频托管

自动标记视频记录上的多个<speakers>,使自动字幕更加有用。视频托管平台现在可以更好地索引文件以进行更好的搜索,为观众提供更好的可访问性和导航,并为 SEO 创建更多有用的内容。

广播媒体

自动标记广播电台或电视录像上的多个<guests><hosts>,以便围绕关键词进行更精确的搜索和分析。媒体监测平台现在可以通过标记哪个发言者提到了他们的关键词(例如可口可乐)来为他们的客户提供更多见解。这也允许它们为单个记录回放提供更好的索引、搜索和导航。

来源

在这里继续学习扬声器二进制化

邮递员和程序集的语音到文本

原文:https://www.assemblyai.com/blog/speedy-code-free-speech-to-text-with-assemblyai-and-postman/

有时候我们就是没时间写代码;我们只想看到结果!

使用 Postman 和 AssemblyAI,我们可以获得语音到文本的转录,无需编写一行代码就可以完成令人敬畏的功能。

Postman 是一个用于 API 测试和开发的多功能应用程序。它提供了一个免费层,我们将在这篇博客中使用。

要求

将本地文件上传到程序集

如果音频文件已经可以通过 URL 公开访问,您可以跳过这一部分,转到章节“转录音频文件”

‍ 我们需要使用 API 上传一个本地音频文件到 AssemblyAI。作为响应,我们将收到一个指向上传的音频文件的 URL,只有 AssemblyAI 可以访问它。请注意,音频文件将在转录后立即删除。

打开 Postman 并创建一个新的请求,您可能需要先创建一个“工作区”

Screenshot of Postman's "building blocks" pop-up

‍Name 你的请求【上传文件】,或者类似的。您可以选择将它添加到一个“集合”,它是一组请求,或者如果需要的话创建一个新的集合。按“保存”完成该过程。‍

Screen-shot of "save request" pop-up

“‍A 新请求”选项卡现在应该已打开。

如下定义 HTTP 请求:

  • 方法:POST
  • 网址:https://api.assemblyai.com/v2/upload
  • 标题:Authorization: ASSEMBLY_AI_API_KEY

你可以在这里找到你的 AssemblyAI API 密匙。

Screenshot displaying the Postman request including headers

既然我们已经设置了标题,最后一步就是附加你想要上传的音频文件。

导航至“Body”选项卡。选择“二进制”作为类型,然后使用文件选择器从您的计算机中选择音频文件。‍

Screenshot showing the Body tab with "binary" selected and an arrow pointing to where the file selector is.

‍The 的请求现在已经准备发送了!按下邮递员右上角的蓝色“发送”按钮。几分钟后,您将收到如下所示的 JSON 响应:

{
    "upload_url": "https://cdn.assemblyai.com/upload/UNIQUE_ID_OF_UPLOAD"
}

转录音频文件

下一步是向 AssemblyAI 请求转录。让我们在 Postman 中创建新请求,将其命名为“Transcription”

如下定义 HTTP 请求:

  • 方法:POST
  • 网址:https://api.assemblyai.com/v2/transcript
  • 标题:
    -Authorization: ASSEMBLY_AI_API_KEY-
    -Content-Type: application/json

Screenshot of the upload request showing headers

然后在“正文”选项卡中:

  • 正文:Raw
  • 内容类型:JSON(从下拉菜单中)

接下来,我们需要设置请求体。您可以使用我们之前发出的文件上传请求返回的 URL,也可以使用任何公开的音频文件 URL。

JSON 主体请求的基本格式应该如下所示:

{
    "audio_url": “AUDIO_FILE_URL”
}

‍If 您希望打开或关闭 API 提供的任何优秀特性,比如 PII 修订二进制化替代声学模型等等,那么您可以在这个 JSON 请求体中这样做。

例如:

{
    "audio_url": “AUDIO_FILE_URL”,
    "speaker_labels": true
}

有关可用参数的完整列表,请参考装配文档

您的转录请求现在可以发送了。您可以再次点击蓝色的“发送”按钮,等待响应。‍

Screenshot showing transcription request body. Highlighting 'raw', 'JSON' and the send button

响应应该如下所示,根据请求中设置的任何参数而变化:

{
    "acoustic_model": "assemblyai_default",
    "audio_duration": null,
    "audio_url": "AUDIO_FILE_URL",
    "confidence": null,
    "dual_channel": null,
    "format_text": true,
    "id": "TRANSCRIPTION_ID",
    "language_model": "assemblyai_default",
    "punctuate": true,
    "status": "queued",
    "text": null,
    "utterances": null,
    "webhook_status_code": null,
    "webhook_url": null,
    "words": null
}

这里对我们来说重要的是"id"值。我们将需要这个值来检索我们的转录。

从汇编中检索转录

获取转录的最后一步是使用前一个请求返回的转录 ID 向 AssemblyAI 发出 GET 请求。

在 Postman 中创建一个新请求,将其命名为类似于“下载转录”

创建第三个 HTTP 请求,如下所示:

  • 方法:GET
  • 网址:https://api.assemblyai.com/v2/transcript/TRANSCRIPTION_ID
  • 标题:
    -Authorization: ASSEMBLY_AI_API_KEY-
    -Content-Type: application/json

注意,我们需要将"id"作为一个参数包含在 URL 的末尾。

Screenshot showing the download request with headers. Noting that this request is a "GET" request

因为这是一个 GET 请求,所以我们没有给请求添加一个"Body"

最后按下蓝色“发送”按钮,等待响应。

如果 AssemblyAI 仍在处理音频文件,JSON 响应将把"processing"作为状态,除非有错误——参见文档中状态代码的完整列表。

如果转录准备好了,则响应将包含转录文本以及每个单词的确定性和所请求的任何其他数据。

摘要

通过使用 Postman,我们很快就创建了一种无需编写任何代码就能获得转录的方法。这是试用 AssemblyAI 提供的服务的一种非常棒和方便的方式。它还可以用来快速获得一个副本。

根据您所选择的 Postman 许可等级,您还可以选择在您的团队中共享您的 API 集合。

快乐抄录!

稳定扩散 1 与 2 -你需要知道什么

原文:https://www.assemblyai.com/blog/stable-diffusion-1-vs-2-what-you-need-to-know/

自几周前发布以来,开源社区一直忙于探索稳定扩散 2。在某些情况下,用户报告的性能明显低于稳定扩散 1

在本文中,我们将总结稳定扩散 1 与稳定扩散 2 争论中的所有要点。我们将在第一部分中查看这些差异存在的实际原因为什么,但是如果您想直接了解实际差异,您可以跳下负面提示部分。我们开始吧!

版本注释

这篇文章发表的第二天,稳定扩散 2.1 发布。2.1 旨在解决 2.0 相对于 1.5 的许多缺点。这篇文章的内容仍然与理解稳定扩散 1 和 2 相关,但是读者应该确保额外阅读附加的 稳定扩散 2.1 部分,以了解全貌。

OpenCLIP

稳定扩散 2 做出的最重要的转变是替换文本编码器。稳定扩散 1 使用 OpenAI 的剪辑,这是一个开源模型,可以学习标题对图像的描述程度。虽然模型本身是开源的,但是训练 CLIP 的数据集是不公开的

稳定扩散 2 代之以使用 OpenCLIP ,这是 CLIP 的开源版本,使用已知数据集对其进行了训练——这是过滤掉 NSFW 图像的 LAION-5B 的美学子集。稳定性 AI open CLIP“极大地提高了”生成图像的“质量”,事实上,在指标优于未发布版本的 CLIP

为什么这很重要

撇开这些模型的相对性能不谈,从 CLIP 到 OpenCLIP 的转变是稳定扩散 1 和稳定扩散 2 之间许多差异的根源

特别是,稳定扩散 2 的许多用户声称,它不能像稳定扩散 1 一样代表名人或艺术风格,尽管事实上稳定扩散 2 的训练数据是而不是故意过滤以去除艺术家。这种差异源于这样一个事实,即 CLIP 的训练数据比 LAION 数据集有更多的名人和艺术家。由于 CLIP 的数据集不对公众开放,因此不可能单独使用 LAION 数据集来恢复相同的功能。换句话说,稳定扩散 1 的许多规范提示方法对于稳定扩散 2 来说都已经过时了。

这意味着什么

这种向完全开源、开放数据模型的转变标志着稳定扩散故事的一个重要转变。开源社区有责任对 Stable Diffusion 2 进行微调,并构建人们希望看到的功能,但这实际上是 Stable Diffusion 的意图 ab initio - 一个由社区驱动的完全开放的项目。虽然一些用户可能会在这一点上对稳定扩散 2 的相对性能感到失望,StabilityAI 团队已经花费了超过 100 万个小时来创建一个坚实的基础。

此外,虽然创作者没有明确提到,但这种从使用 CLIP 的转变可能会为项目贡献者提供一些潜在责任问题的保护,这是很重要的,因为知识产权诉讼的浪潮肯定会在像这样的模型中出现。

考虑到这一背景,现在是时候讨论稳定扩散 1 和 2 之间的实际差异了。

负面提示

我们从检查负提示开始,与稳定扩散(SD)1 相比,它似乎对稳定扩散(SD) 2 的强劲性能更为重要,如下所示:

(modified from source)

现在让我们更详细地看看负面提示。

简单提示

首先,我们向稳定扩散 1.5 和稳定扩散 2 提供提示“无限池”,并给出 no 否定提示。显示了来自每个模型的三个图像,其中每一列对应于不同的随机种子。

设置

  • 提示:“无限池”
  • 尺寸 : 512x512
  • 导航刻度 : 12
  • 步数 : 50
  • 采样器 : DDIM

正如我们所见,稳定扩散 1.5 似乎比稳定扩散 2 整体表现更好。在 SD 2 中,最左边的图像有一个在图像内不太适合的补丁,最右边的图像几乎是不连贯的。

现在我们以同样的方式从相同的起始噪声中生成图像,这次使用 负提示。我们添加负面提示“丑,贴瓷砖,手画的不好,脚画的不好,脸画的不好,出框,突变,变异,多余的四肢,多余的腿,多余的胳膊,毁容,畸形,斗鸡眼,身体出框,模糊,糟糕的艺术,糟糕的解剖,模糊,文字,水印,粒状”,这是艾玛德莫斯塔克使用的负面提示

添加了负面提示后,SD 1.5 通常表现更好,尽管中间图像的字幕对齐可能较差。对于 SD 2,改进更加剧烈,尽管总体性能仍然不如 SD 1.5

设置

  • 提示:“无限池”
  • 尺寸 : 512x512
  • 导航刻度 : 12
  • 步数 : 50
  • 采样器 : DDIM
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

我们直接比较了有和没有负面提示的 SD 2 性能。检验揭示了对负面提示对 SD 2 至关重要这一命题的支持。

下面我们可以看到 SD 1.5 和 2 生成的最终图像的比较,有和没有负面提示,从相同的随机种子开始。

复杂提示

我们运行与上面相同的实验,这一次有一个更复杂的(肯定的)提示。这一次,我们使用的不是“ infinity pool ”,而是“ infinity pool,背景是热带森林,高分辨率,细节,8 k,dslr,良好的照明,光线追踪,逼真的”。虽然我们可以省略“和背景中的热带森林”部分,以便隔离纯粹的美学添加,但我们包括它是为了更好地探索更复杂提示的语义匹配。

同样,我们显示的结果没有负面的提示。图像看起来不再像照片一样真实,标题的排列也更好了。水的质感也用 SD 1.5 好很多。

设置

  • 提示:“以热带森林为背景的无限泳池,高分辨率,细节,8 k,dslr,良好的照明,光线追踪,逼真”
  • 尺寸 : 512x512
  • 导航刻度 : 12
  • 步数 : 50
  • 采样器 : DDIM

一旦我们添加了与上一个例子相同的否定提示,我们会看到一些有趣的结果。特别是,似乎负面提示实际上可能对 SD 1 有负面影响,但普遍有助于 SD 2。SD 2 的每张图片都有负面提示,而 SD 1 的标题排列似乎普遍下降。有趣的是,添加负面提示似乎会将生成的图像推向真实感。

设置

  • 提示:“以热带森林为背景的无限泳池,高分辨率,细节,8 k,dslr,良好的照明,光线追踪,逼真”
  • 尺寸 : 512x512
  • 导航刻度 : 12
  • 步数 : 50
  • 采样器 : DDIM
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

我们再次直接比较不同随机种子生成的图像,有和没有 SD 2 的负面提示。

最后,我们再次展示了一个带有/不带有负提示矩阵的 SD 1.5/SD2 vs:

文本倒置

除了普通的负面提示,稳定扩散还支持文本反转。文本反转是一种方法,其中少量参考图像可用于生成表示图像的新“单词”。一旦学会了,这个“单词”就可以像往常一样在提示中使用,让我们能够生成与参考图像精确对应的图像。在下面的例子中,一个小图形的 4 个图像被反转为“S_*”。然后,这个“单词”照常在各种提示中使用,忠实地将参考图像与其他语义概念相结合:

(image modified from source)

在下面的例子中,我们使用稳定扩散 2.0 从基本提示“一个美味的汉堡”中创建了几个图像。该提示然后用肯定提示或文本反转标记和/或否定提示或文本反转标记来增强。例如,第二行中最右边的图像用引用中途的文本倒置标记和正常的负面提示“丑陋、无聊、糟糕的解剖”来增强基本提示。

(source)

正如我们所看到的,文本反转的使用显著提高了稳定扩散 2.0 的性能。上面的图片摘自 Max Woolf 的博客文章,它对这个话题进行了很好的处理。

名人

鉴于 LAION 包含的名人图像比 CLIP 的训练数据少,因此许多 SD 2 用户观察到的生成名人图像的能力比 SD 1.5 差可能并不奇怪。

下面我们展示了 SD 1.5 和 SD 2 在有和没有负面提示的情况下从 3 个随机种子(列)生成的图像。提示是“基努·里维斯这张图片的全分辨率版本也有。

(full resolution)

设置

  • 提示:“基努·里维斯”
  • 尺寸 : 512x512
  • 导航刻度 : 7
  • 步数 : 50
  • 种子 : 119
  • 采样器 : DDIM
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

总的来说,在这个特定的提示方面,SD 2 的性能与 SD 1.5 相当。话虽如此,稳定扩散 2 描述名人的能力似乎在与语义概念结合时崩溃了。我们在下面给出了两个这样的提示的比较,其中图像中的每一列都对应于一个给定的随机种子。这一次,我们在所有情况下都使用负面提示。

设置

  • 提示:“博物馆里的小罗伯特·唐尼汉白玉半身像,电影灯光,超细节,8 k 真实感,全局照明,辐射光,冻伤 3 引擎,cryengine,artstation 上的趋势,数字艺术,幻想背景”
  • 尺寸 : 512x512
  • 导航刻度 : 12
  • 步数 : 50
  • 种子 : 120-122
  • 采样器 : DPM-Solver++
  • 否定提示:“难看、平铺、脱帧、变形、模糊、艺术差、模糊、水印、有颗粒”

设置

  • 提示:“小罗伯特·唐尼工作室照片,电影灯光,超细节,8 k 真实感,全局照明,辐射光,冻伤 3 引擎,cryengine,artstation 趋势,数字艺术”
  • 尺寸 : 512x512
  • 导航刻度 : 7
  • 步数 : 50
  • 种子 : 119-121
  • 采样器 : DPM-Solver++
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

正如我们所看到的,在这方面,稳定扩散 1.5 往往优于稳定扩散 2(在某一点上,它甚至似乎描绘了史蒂夫·卡雷尔而不是小罗伯特·唐尼)。虽然这种差异是意料之中的,但从山谬·里维的例子中得出的结果来看,其幅度可能比预期的要大。

艺术形象

正如在 OpenCLIP 部分中提到的,除了包含比剪辑训练数据更少的名人图像之外,LAION 数据集还包含更少的艺术图像。这意味着生成风格化的图像更加困难,并且“_ _ _ _风格中的“_____”的规范方法不再像在稳定扩散 1 中那样起作用。下面我们比较了稳定扩散 1.5 和稳定扩散 2 的 4 个随机种子的图像,我们试图以 Greg Rutkowski 的风格生成图像。

(full resolution)

设置

  • 提示:“怪物与英雄战斗,格雷格·鲁特考斯基,浪漫主义,电影灯光,超细节,8 k 现实,全局照明,辐射光,artstation 趋势,数字艺术”
  • 尺寸 : 512x512
  • 导航刻度 : 9
  • 步数 : 50
  • 种子 : 119-122
  • 采样器 : DPM-Solver++
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

结果是激烈的——稳定扩散 1.5 再次明显战胜了稳定扩散 2(开箱即用)。虽然使用不明确引用艺术家的其他描述符来增加提示,但仍然可以使用 SD 2 生成风格化的图像,性能仍然不如 SD 1.5,如下所示:

(full resolution)

设置

  • “原创”提示:“一只可爱的兔子”
  • “增强”提示:“一只可爱的兔子,电影灯光,超细节,8 k 真实感,全局照明,辐射光,冻伤 3 引擎,cryengine,artstation 上的趋势,数字艺术,幻想背景”
  • 尺寸 : 512x512
  • 导航刻度 : 9
  • 步数 : 50
  • 种子 : 119-122
  • 采样器 : DPM-Solver++
  • 否定提示:“难看、平铺、脱帧、变形、模糊、艺术差、模糊、水印、有颗粒”

另一方面,一些用户发现 SD 2 非常有能力生成照片般逼真的图像 :

(modified from source)

语篇连贯

与稳定扩散 1 相比,稳定扩散 2 可能具有现成优势的一个地方是文本一致性。大多数文本到图像模型在表现文本方面都很糟糕。这一点都不奇怪——虽然我们人类很容易解析文本,但我们必须记住,单词是极其复杂的语言系统的一部分,按照特殊的规则排列以传达意思。此外,这些单词本身是由字母以近乎随机的方式组成的;而且,更进一步,这些字母的实际视觉表现可以有很大的不同(例如,比较乔克曼康萨拉字体)。这些考虑因素(以及其他因素)为这些模型无法正确传达文本(尤其是简单文字之外的文本)提供了一些解释。

也就是说,稳定扩散 2 似乎比稳定扩散 1 在传达文本方面稍好。下面我们提供几张图片进行比较:

(full resolution)

正如我们所看到的,这两种情况下的结果都不理想,负面的提示在这方面似乎没有什么效果。虽然很难提出一种客观的方法来衡量这些模型生成文本的效果,但可以说一般人会认为稳定扩散 2 略好一些。

其他型号

除了从 CLIP 到 OpenCLIP 的转变,Stable Diffusion 2 还发布了一些其他出色的功能,我们将在下面进行总结。

深度模型

一个深度模型与 SD 2 一起发布。该模型接收 2D 图像并返回该图像的预测深度图。然后,除了文本之外,该信息还可以用于调节图像生成,从而允许用户生成新的保持忠实于参考图像几何形状的图像。

(source)

下面我们可以看到一系列这样的图像,它们都保持着相同的基本几何结构。

(source)

放大模型

稳定扩散 2 还发布了一个放大模型,可以将图像放大到原来边长的 4 倍。这意味着升级后的图像面积是原来的 16 倍!

下面我们可以看到放大之前生成的图像的结果:

如果我们放大每张照片中兔子的眼睛,差异会立即显现出来,给人留下非常深刻的印象。

修复模型

稳定扩散 2 还带有一个更新的修复模型,它可以让你修改图像的子部分,以使补丁符合美学:

(source)

768 x 768 型号

最后,稳定扩散 2 现在提供了对 768 x 768 图像的支持——超过稳定扩散 1 的 512 x 512 图像的两倍。

稳定扩散 2.1

稳定扩散 2.1 是在稳定扩散 2.0 发布后不久发布的。SD 2.1 旨在解决 2.0 相对于 1.5 的许多缺点。让我们看看 2.1 是如何做到这一点的。

NSFW 滤波器

2.1 相对于 2.0 最大的变化是一个改进的 NSFW 滤波器。回想一下,2.0 是在 LAION 数据集的子集上训练的,该数据集使用 NSFW 过滤器过滤不适当的内容,这反过来导致描绘人类的能力相对较低。

稳定扩散 2.1 也用这样的滤波器训练,尽管滤波器本身被修改为限制更少。特别是,过滤器抛出更少的假阳性,这大大增加了能够通过过滤器和训练模型的图像数量。训练数据的增加使得描绘人物的能力得到了提高。我们再次展示了几幅小罗伯特·唐尼的图像,它们是用相同的设置创建的,除了用来生成它们的模型版本,这次包括了稳定扩散 2.1。

An image of Robert Downey Jr. created with each model using identical settings

设置

  • 提示:“小罗伯特·唐尼工作室照片,电影灯光,超细节,8 k 真实感,全局照明,辐射光,冻伤 3 引擎,cryengine,artstation 趋势,数字艺术”
  • 尺寸 : 512x512
  • 导航刻度 : 7
  • 步数 : 50
  • 种子 : 119
  • 采样器 : DPM-Solver++
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

正如我们所看到的,稳定扩散 2.1 是稳定扩散 2 的显著改进,能够实际描绘小罗伯特·唐尼。此外,SD 2.1 的皮肤纹理甚至比 SD 1.5 更好。

艺术风格

不幸的是,SD 2.1 描绘特定艺术家风格的能力仍然明显低于 SD 1.5。下面我们再一次看到用相同的设置创建的图像,除了用来创建它们的模型。这些照片意在捕捉格雷格·鲁特考斯基的风格。

设置

  • 提示:“怪物与英雄战斗,格雷格·鲁特考斯基,浪漫主义,电影灯光,超细节,8 k 现实,全局照明,辐射光,artstation 趋势,数字艺术”
  • 尺寸 : 512x512
  • 导航刻度 : 9
  • 步数 : 50
  • 种子 : 158
  • 采样器 : DPM-Solver++
  • 负面提示:“丑、平铺、手画得不好、脚画得不好、脸画得不好、画框外、突变、变异、多肢、多腿、多臂、毁容、变形、斗鸡眼、身体画框外、模糊、糟糕的艺术、糟糕的解剖、模糊、文本、水印、粒状”

正如我们所看到的,稳定扩散 1.5 仍然在这方面占主导地位。

常规图像

我们重复上一节中关于普通与“增强”提示的实验,同样只改变模型版本。

设置

  • “原创”提示:“一只可爱的兔子”
  • “增强”提示:“一只可爱的兔子,电影灯光,超细节,8 k 真实感,全局照明,辐射光,冻伤 3 引擎,cryengine,artstation 上的趋势,数字艺术,幻想背景”
  • 尺寸 : 512x512
  • 导航刻度 : 9
  • 步数 : 50
  • 种子 : 119
  • 采样器 : DPM-Solver++
  • 否定提示:“难看、平铺、脱帧、变形、模糊、艺术差、模糊、水印、有颗粒”

正如我们所见,2.1 的“原始”纹理是对 2.0 的改进。2.1 的“增强”图像比 2.0 更加风格化,但总体上非常相似。

结论

虽然这些实验肯定不是严格或详尽的,但它们提供了对稳定扩散 1 和稳定扩散 2 的相对性能的一些见解。

如果您对文本到图像模型有更多问题,请查看以下资源以进一步了解:

  1. 如何建立一个文本到图像的模型?
  2. 什么是无分类器指导?
  3. 什么是提示工程?
  4. DALL-E2 是如何工作的?
  5. Imagen 是如何工作的?

或者,考虑关注我们的 YouTube 频道、 Twitter时事通讯来了解我们最新的教程和深度探索!

Keras 中的稳定扩散-简单教程

原文:https://www.assemblyai.com/blog/stable-diffusion-in-keras-a-simple-tutorial/

今年早些时候发布了稳定扩散,为世界提供了强大的文本到图像的能力。自从它发布以来,许多不同的项目都是从它衍生出来的,这使得用几个简单的词来生成像下面这样的图像变得前所未有的容易。

(Image from the Stable Diffusion Discord)

稳定扩散已经集成到 Keras 中,允许用户在少至行代码中生成新颖的图像。最近,通过修补修改图像的能力也被整合到稳定扩散的 Keras 实现中。

在这篇文章中,我们将看看如何在 Keras 中使用稳定的扩散生成修复图像。我们提供了一个完整的 Colab 笔记本,因此您可以在 GPU 运行时立即开始。此外,我们看看 XLA 如何能够显著提高 Keras 中稳定扩散的效率。让我们开始吧!

先决条件

如果您不想在您的计算机上安装任何东西,请单击下面的按钮打开相关的 Colab 笔记本,然后从那里开始操作。

Colab Notebook

要在您的机器上本地设置 Keras 中的稳定扩散,请遵循以下步骤。本文使用了 Python 3.8。

步骤 1 -克隆项目存储库

打开一个终端,执行下面的命令,使用 git 克隆项目存储库,然后导航到项目目录。

git clone https://github.com/AssemblyAI-Examples/stable-diffusion-keras.git
cd stable-diffusion-keras

步骤 2 -创建虚拟环境

如果您希望将该项目的所有依赖项隔离在您的系统上,请创建并激活虚拟环境:

python -m venv venv

# Activate (MacOS/Linux)
source venv/bin/activate

# Activate (Windows)
.\venv\Scripts\activate.bat

如果您的机器上安装了 Python 2 和 Python 3,您可能需要使用python3而不是python

步骤 3 -安装依赖项

最后,通过运行以下命令安装所有必需的依赖项:

pip install -r requirements.txt

如何在 Keras - Basic 中使用稳定扩散

我们可以在三行代码中使用稳定扩散:

from keras_cv.models import StableDiffusion

model = StableDiffusion()
img = model.text_to_image("Iron Man making breakfast")

我们首先从 Keras 导入StabelDiffusion类,然后创建它的一个实例model。然后我们使用这个模型的text_to_image()方法生成一个图像,并保存到img变量中。

另外,如果我们想保存图像,我们可以导入并使用枕头:

from keras_cv.models import StableDiffusion
from PIL import Image

model = StableDiffusion()
img = model.text_to_image("Iron Man making breakfast")
Image.fromarray(img[0]).save("simple.png")

我们从该批图像中选择第一张(也是唯一一张)图像作为img[0],然后通过fromarray()将其转换为枕头Image。最后,我们通过.save()方法将图像保存到文件路径./simple.png

在项目目录中打开一个终端,您可以通过输入以下命令来运行上面的脚本,该命令将运行[simple.py](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/simple.py)脚本:

python simple.py

同样,你可能需要使用python3而不是python。生成以下“钢铁侠做早餐”的图像并保存到[./simple.png](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/simple.png):

在喀拉斯使用稳定扩散所需要的一切!在下一节,我们将看看更高级的用法,如修补。或者,跳到通过 XLA 的 JIT 编译部分,看看 Keras 如何提高稳定扩散的速度。

如何在 Keras - Advanced 中使用稳定扩散

我们现在来看看图像生成和修复的高级用法。下面链接的 Colab 笔记本可以很容易地使用滑块来修改修复区域,所以如果你愿意,请随意跟随:

Colab Notebook

所有高级图像生成和修复代码都可以在[main.py](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/main.py)中找到。

图象生成

当我们实例化稳定扩散模型时,我们可以选择传入一些参数。下面,我们将图像的高度和宽度都指定为 512 像素。这些值中的每一个都必须是 128 的倍数,如果不是,将被舍入到最接近的值。此外,我们还指定我们确实而不是想要用 XLA 实时编译模型(更多细节在通过 XLA 的 JIT 编译部分)。

model = StableDiffusion(img_height=512, img_width=512, jit_compile=False)

接下来,我们创建一个参数字典,它将被传递给text_to_image()方法。这些论点是:

options = dict(
    prompt="An alien riding a skateboard in space, vaporwave aesthetic, trending on ArtStation ",
    batch_size=1,
    num_steps=25,
    unconditional_guidance_scale=7,
    seed=119
)

从这里开始,过程与上面非常相似——我们运行推理,然后将输出保存为[generated.png](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/generated.png)

img = model.text_to_image(**options)
Image.fromarray(img[0]).save("generated.png")

注意,这在 CPU 和 GPU 上都可以做到。使用 i5-11300H,使用上述设置生成图像大约需要 5 分钟。有了 GPU,它应该只需要大约 30 秒

图像修复

现在我们来看看如何在 Keras 中使用稳定扩散进行修复。首先,我们使用requests包下载一个要修改为[man-on-skateboard.jpg](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/man-on-skateboard.jpg)的图像:

file_URL = "https://c0.wallpaperflare.com/preview/87/385/209/man-riding-on-the-skateboard-photography.jpg"
r = requests.get(file_URL)
with open("man-on-skateboard.jpg", 'wb') as f:
    f.write(r.content)

这是最终下载的图像

(source)

此图像为 910 x 607 像素。在我们继续之前,我们将其裁剪为 512 x 512 像素。我们将裁剪区域的左下角定义为(x_starty_start),并将裁剪区域设置为 512 像素宽和 512 像素高。

x_start = 80  # Starting x coordinate from the left of the image
width = 512
y_start = 0  # Starting y coordinate from the BOTTOM of the image
height = 512

如果您在 Colab 中跟随,您可以使用滑块来调整这些值:

然后,我们打开原始图像并将其转换为 NumPy 数组,以便我们可以修改它:

im = Image.open("man-on-skateboard.jpg")
img = np.array(im)

我们执行裁剪,这里不寻常的 y 方向的算法源于这样一个事实:我们将裁剪定义为图像左下角的原点,而 NumPy 将图像左上角作为原点。然后,我们将裁剪后的图像保存到[man-on-skateboard-cropped.png](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/man-on-skateboard-cropped.png)

img = img[im.height-height-y_start:im.height-y_start, x_start:x_start+width]
new_filename = "man-on-skateboard-cropped.png"
Image.fromarray(img).save(new_filename)

现在是时候创建修复蒙版了。修复遮罩定义了我们希望稳定扩散修改的图像区域。我们在此定义这些值:

x_start = 134
x_end = 374
y_start = 0
y_end = 369

同样,如果您在 Colab 笔记本中跟随,您可以使用滑块调整该区域。

我们像以前一样将裁剪后的图像作为数组打开,然后创建一个与数组形状相同的遮罩,其中数组中的每个值都是 1。然后,我们用零替换由修复遮罩定义的区域,这告诉模型这是我们想要修复的区域。

im = Image.open("man-on-skateboard-cropped.png")
img = np.array(im)

# Intiialize
mask = np.ones((img.shape[:2]))
# Apply mask
mask[img.shape[0]-y_start-y_end:img.shape[1]-y_start, x_start:x_end] = 0

接下来,我们扩展掩码和图像数组的维度,因为模型需要一个批处理维度。

mask = np.expand_dims(mask, axis=0)
img = np.expand_dims(img, axis=0) 

现在是时候定义我们的修复选项了。我们将图像数组传递给img参数,将遮罩数组传递给mask参数。除此之外,除了以下几点之外,所有参数都是相同的:

  • num_resamples -修复是重采样多少次。增加这个数字将以更多的计算为代价提高语义匹配度
  • diffusion_noise -可选自定义噪声来播种扩散过程-必须提供seeddiffusion_noise,但不能两者都提供
  • verbose -一个布尔值,它定义一个进度条是否应该被打印
inpaint_options = dict(
        prompt="A golden retriever on a skateboard",
        image=img,  # Tensor of RGB values in [0, 255]. Shape (batch_size, H, W, 3)
        mask=mask,  # Mask of binary values of 0 or 1
        num_resamples=5,
        batch_size=1,
        num_steps=25,
        unconditional_guidance_scale=8.5,
        diffusion_noise=None,
        seed=SEED,
        verbose=True,
)

最后,我们再次实例化模型,运行推理,并保存结果数组,如上所述。图像被保存到[./inpainted.png](https://github.com/AssemblyAI-Examples/stable-diffusion-keras/blob/main/inpainted.png)

inpainting_model = StableDiffusion(img_height=img.shape[1], img_width=img.shape[2], jit_compile=False)

inpainted = inpainting_model.inpaint(**inpaint_options)

Image.fromarray(inpainted[0]).save("inpainted.png")

下面我们可以看到一个 GIF 的原始裁剪图像,修复区域,以及稳定扩散生成的结果图像。

同样,可以在 CPU 和 GPU 上运行这种推理。对于 i5-11300H,使用上述设置运行修复大约需要 22 分钟。有了 GPU,它应该只需要几分钟

通过 XLA 进行 JIT 编译

像 C++这样的语言传统上是提前(AOT)编译的,这意味着源代码被编译成机器码,然后这个机器码被处理器执行。另一方面,一般解读 Python。这意味着源代码不会提前编译,而是由处理器在运行时解释。虽然不需要编译步骤,但解释比运行可执行文件要慢。

其他详细信息

注意,为了简洁起见,上面的描述是简化的。实际上,这个过程要复杂得多。比如 C++一般编译成目标代码。然后,多个目标文件可能被链接器链接在一起,以创建由处理器直接执行的最终可执行文件。

类似地,Python,(或者更准确地说,它最常见的实现 CPython)被编译成字节码,然后由 Python 的虚拟机解释。

这些细节对于理解 JIT 编译来说并不重要,我们在这里包含它们只是为了完整。

实时(JIT)编译就是在运行时编译代码的过程。虽然在编译函数时会有一些开销,但是一旦被编译,它的执行速度会比解释的等价函数快得多。这意味着被重复调用的函数将从 JIT 编译中获益。

XLA ,或加速线性代数,是一款领域专用编译器,专为线性代数打造。Keras 中的稳定扩散通过 XLA 支持 JIT 编译。这意味着我们可以将稳定扩散编译成 XLA 编译的版本,它有可能比稳定扩散的其他实现执行得更快

基准

我们在下图中看到,利用 XLA 使 Keras 的稳定扩散实现的执行速度明显快于扩散器库中的拥抱脸实现:

Stable Diffusion in KerasCV vs Hugging Face's Diffusers. KerasCV using XLA and mixed precision, diffusers using fp16 (source).

请注意,这些数字反映了热启动发电- Keras 实际上比冷启动慢。考虑到编译步骤增加了冷启动发电的时间,这是可以预料的。正如这里提到的,这不是一个大问题,因为在生产环境中,编译将是一次性成本,分摊到(希望)模型运行的许多许多推理中。

将 XLA 和混合精度结合在一起可以实现最快的执行速度。下面我们看到有/没有 XLA 和混合精度的所有组合的运行时间:

Runtimes for different types of Stable Diffusion in KerasCV (source).

你可以在 Colab 这里自己运行这些实验,或者在这里查看一些额外的指标,如冷启动时间

结论

这就是使用 Keras 开始稳定扩散的全部内容!一个只需要几行代码的高性能实现,Keras 的稳定扩散是一个广泛应用的伟大选择。

如果您对文本到图像模型有更多问题,请查看以下资源以进一步了解:

  1. 如何建立一个文本到图像的模型?
  2. 什么是无分类器指导?
  3. 什么是提示工程?
  4. DALL-E2 是如何工作的?
  5. Imagen 是如何工作的?

或者,考虑关注我们的 YouTube 频道、 Twitter时事通讯来了解我们最新的教程和深度探索!

审查-语音处理通用性能基准审查

原文:https://www.assemblyai.com/blog/superb-speech-processing-universal-performance-benchmark-review/

本周深度学习研究论文为“高超:语音处理通用性能基准评测

这篇论文有什么令人兴奋的地方

自然语言处理(NLP)和计算机视觉(CV)的趋势是使用大量未标记数据进行预训练,并针对各种下游任务进行微调。开发用于语音应用的预训练模型的关键差距之一是,以前没有可用的系统任务和基准。本文首次引入各种任务和基准来衡量预训练模型在各种语音任务上的可推广性并衡量其性能。

主要发现

  • 语音任务:本文将测量语音表现的各种任务分为四类——基于内容的语音任务,包括【自动语音识别(ASR)】和关键词识别,基于说话人的任务,包括说话人二进制化,基于语义的任务,包括意图分类,以及基于副语言学的任务。许多任务和基准已经在语音社区存在了一段时间。这是他们第一次将预先训练好的模型放在一起进行测试。

  • HuBERT Large 优于 Wav2Vec2.0 Large :本文还测量了在 librilight 和 librispeech 数据集上训练的生成模型和判别模型在上述任务中的表现。HuBERT Large 拥有与 Wav2Vec2.0 Large 相似的参数数量,在 12 个任务中的 8 个任务中超过了它,这令人惊讶。休伯特大,也与最低限度的适应,实现了在这些任务的一些国家最先进的结果。

我们的外卖

本文介绍了一个简单的框架来衡量性能的预训练自我监督模型在各种语音任务和衡量其性能。这将推动表征学习和一般语音处理的研究。

初学者的监督机器学习

原文:https://www.assemblyai.com/blog/supervised-machine-learning-for-beginners/

在这个视频中,我们学习监督机器学习,这可以说是最重要的机器学习类型。

您将了解到:

  • 什么是监督机器学习
  • 监督机器学习的例子
  • 什么是数据和培训
  • 监督机器学习的类型
  • 监督学习算法

看这里:

https://www.youtube.com/embed/Mu3POlNoLdc?feature=oembed

文本分割——方法、数据集和评估标准

原文:https://www.assemblyai.com/blog/text-segmentation-approaches-datasets-and-evaluation-metrics/

文本分割简介

文本分割是将文本分割成有意义的片段的任务。这些片段可以由单词、句子或主题组成。在这篇文章中,我们来看一种特定类型的文本分割任务——主题分割,它将一长段文本分割成与不同主题或子主题相对应的片段。

例如,考虑由自动语音识别(ASR) 系统生成的长达一小时的播客转录。抄写可能会很长,因此很容易忘记你正在读的是哪一个句子。自动主题分割通过将文本分成多个片段来解决这个问题,使转录更具可读性。

Figure: Topic Segmentation

文本格式的类型

我们可能想要分割不同类型的文本。例如:

  • 书面文本,如博客、文章、新闻等。
  • 电视新闻的转录,一个人在说话
  • 多人在讲话的播客的转录
  • 电话记录(如呼叫中心)
  • 许多人正在交谈的在线会议的记录

上面的列表是根据文本可能包含的干扰级别排序的(例如,在自动转录的情况下,错别字、语法错误或不正确的用词)。噪声是预测主题时要考虑的一个重要因素,因为它有助于主题分段模型预测的分段的质量。我们将在后面的章节中对此进行更多的讨论。

因为博客和文章大多是在电脑上输入的,所以它们包含的噪音最少。

例如,在线会议的转录包含最高级别的噪声,因为可能有几个人使用不同质量的麦克风、用不同的口音以及在不同的互联网连接质量上讲话,这可能导致 ASR 系统的准确性问题。另一方面,电视新闻报道和播客通常是用录音室质量的麦克风录制的,因此与在线会议相比,噪音要小得多,这使得文本转录更加准确。

主题分段的用例

现在我们知道了什么是主题分段,以及我们正在处理的文本格式的类型,下面是一些现实生活中的用例:

  • 可读性:我们想要将文本分割成多个段落大小的片段的主要原因之一是可读性。请考虑这篇没有任何章节名称、段落或中断的博客文章。这一长串文本使阅读变得更加困难。自动主题分割模型可用于将长而连续的文本分解成小块信息,以便读者更容易阅读。
  • 文本摘要帮助读者对任何给定的文本进行摘要。然而,如果文本涉及多个主题,那么文本摘要模型捕获摘要中的所有主题是具有挑战性的。主题分段模型可以更容易地为每个分段生成要点摘要,涵盖给定文本中的所有主题或主题。
  • 将新闻 报道变成文章:如今,新闻报道通常通过多种渠道发布,这些渠道需要不同的格式,包括视频和书面格式(如文章或博客帖子)。为了将新闻报道视频作为文章或博客重用,我们可以使用语音到文本 API 转录视频,就像我们在 AssemblyAI 构建的那样,并将主题分段应用于转录。这将有助于把文章组织成更便于读者阅读的格式。
  • 信息 检索:给定大量的文本文档,我们可以对属于同一主题的文本进行聚类。一旦我们的文档按主题分段,我们就可以很容易地从每个文档中提取我们需要的信息。
  • AI 写作助手:对于 Grammarly 这样的 AI 写作助手来说,主题分割可以用来向作者建议何时开始新的段落,使他们的写作更具可读性

主题分割和评价指标的方法

在开始讨论现有的自动主题分割方法之前,让我们首先研究主题分割问题的潜在解决方案,并查看分割准确性的评估指标。

概括地说,在自动主题分割中,我们的目标是通过主题/子主题来分割文本。自动主题分割模型将对文档中的每个句子进行分类,并确定它是否是边界句(即,段落的最后一句)。换句话说,我们可以把话题分割看作一个二元分类问题,我们对每一个句子进行分类,确定它是否是一个边界句。

Figure: Topic Segmentation as Binary Classification

既然我们已经建立了对主题分割问题的理解,那么让我们来讨论它的评估指标。最常用的指标是:

  • 精确度和召回率
  • 公园
  • windowsiff

精确度和召回率

由于这是一个二进制分类问题,您可能会尝试使用 Precision & Recall 作为评估指标。然而,对于这种类型的分类问题,使用精度和召回率存在一些挑战。让我们首先理解与主题分割相关精确和召回意味着什么。

精度:由模型识别的边界是真实边界的百分比

回忆:模型识别的真实边界的百分比

然而,精确和召回的主要挑战是它们对未遂事件不敏感。为了理解什么是未遂事件,让我们考虑两个主题分割模型 A-0 和 A-1。在下图中, Ref 是基本事实,每个块代表一个句子。垂直线表示主题/子主题分段边界。

Figure: Ground truth and output of segmentation algorithms

从图中,你可以清楚地看到模型 A-0 所做的预测非常接近实际情况。这就是所谓的“未遂事件”,这种情况下,预测会相差一两句话。另一方面,模特 A-1 所做的预测与事实相差甚远。从技术上讲,A-0 型的处罚应该比 A-1 型轻得多。但事实并非如此,两个模型得到了相同的精度和召回分数。这是因为 Precision & Recall 不考虑边界预测的远近。

Pk 分数

为了解决精确度和召回率的挑战,Beeferemen 等人(1997 年)引入了 Pk 分数。

使用基于滑动窗口的方法计算 Pk。窗口大小通常设置为平均真实段数的一半。在滑动窗口的同时,该算法确定窗口的两端在地面真实分段中是在相同还是不同的分段中,并且如果存在不匹配,则增加计数器。通过在 0 和 1 之间调整罚分并除以测量次数来计算最终得分。

Figure: Sliding window over reference and predictions (Pevzner and Hearst - 2002)

正确预测所有边界的模型得分为 0。所以分数越低越好。

Pk 评估指标面临的挑战:

  • 假阴性比假阳性受到更多的惩罚。
  • 不考虑边界的数量。如果窗口内部有多个边界,Pk 不考虑这一点。
  • 对片段大小的变化很敏感。
  • 差点失误被罚太多。

windowsiff

WindowDiff 的引入是为了解决 Pk 分数的挑战。这也是通过滑动窗口计算的。在这种情况下,对于大小为 k 的窗口的每一个位置,我们简单的比较一下地面真实中有多少边界,有多少边界是由话题分割模型预测的。

Figure: Equation for calculating WindowDiff score

这里, b(i, j) 是一个函数,返回文本中两个位置i**j之间的界限数。 N 代表文中的句子数量。

在实践中,Pk 和 WindowDiff 分数都用于评估模型。较低的分数意味着预测更接近实际边界。有关这些指标的更详细的比较以及 WindowDiff score 如何解决 Pk 的挑战,您可以参考 Pevzner 等人(2002)

主题分割方法

在这一节中,我们来看看最常见的主题分割方法,这些方法主要可以分为两类——有监督的和无监督的。

监督方法

监督方法非常简单——我们获取一个带标签的数据集,然后尝试在其上拟合一个模型。当我们有一个特定于领域的分割任务,并且数据集属于同一个领域时,这种方法非常有效。例如,如果我们知道我们的模型将在推理时看到类似于维基百科的文本,那么在 Wiki-727k 上训练模型将产生最佳结果。然而,如果用于其他领域,例如新闻文章或会议记录,它的性能会更差。

在有监督的方法中,我们希望对每个句子进行分类,以确定它是否是边界句。以下是管道的高级工作方式:

  1. 将文本作为输入
  2. 从文本中提取所有句子,即将文本分割成句子。(可以使用nltkstanzatrankit等库。对于此任务)
  3. 对每个句子进行分类——这将是一个二元分类

需要注意的一件重要事情是,我们在单词级别接受输入,在句子级别进行预测。这意味着我们需要将单词级的表示(嵌入)转换到句子级。我们可以将一个句子中所有标记的嵌入相加,得到一个聚合表示。这将给我们句子级的嵌入。

Figure: Calculation of sentence embeddings from word embedding

此外,如果我们使用像 BERT 这样的模型,我们可以得到嵌入的[CLS]标记,而不是聚合一个句子中所有的单词嵌入。这是因为在 BERT 中,[CLS]标记聚集了整个序列的表示。

接下来,我们将嵌入传递给一个双向 LSTM 或变压器,并计算输出的 softmax。在这里使用双向 LSTM/转换器是一个好主意,因为它将使模型能够在做出决定之前查看句子的左右上下文。

Figure: Boundary sentence classifier model

上述基于 LSTM 的方法实际上用于 Koshorek 等人在 Wiki-727k 数据集上取得了 22.13 的 Pk 分数。

Glavas 等人(2020) 提出了一个基于 Transformer 的模型,该模型保存了 Wiki-727k、CHOI、Elements &城市等数据集的当前最新成果。他们使用了两个级别的转换器:一个在标记级别,另一个在句子级别。在此基础上,预测目标增加了一个辅助一致性建模目标。其核心思想是文本连贯性与文本分割相关。这意味着一个片段中的文本预期比不同片段中的文本更连贯。

无人监管的方法

无监督的方法既没有学习阶段,也没有标记数据。因此,无监督方法利用不同的主题分割技术,例如:

  • 词汇衔接
  • 主题建模
  • 图表
  • 相似性度量

我们将从较高的层面介绍这些方法是如何工作的。

词汇衔接

如果一组词在语义上是相关的,那么它们就是“词汇衔接的”。词汇衔接的水平由词汇的频率和分布决定,并且存在利用词汇衔接来分割文本的算法。这个想法是,当有子话题转移时,两个文本块之间的词汇衔接会更低,我们可以在此基础上分割文本。

TextTiling: TextTiling 由 Hearst (1997) 提出,是最早的无监督主题分割算法之一。这是一种基于移动窗口的方法,使用文本块之间的词汇衔接来检测主题边界。

该算法有三个主要部分:

  • 首先,它将输入文本划分为相关的标记序列,并计算每个潜在边界点的内聚性。
  • 然后,它使用这些内聚分数来为每个潜在边界点产生深度分数,该潜在边界点具有比相邻边界点更低的内聚分数。
  • 使用这些深度分数,该算法能够选择深度相对于其他深度分数较低的边界点,指示该间隙代表文本中的主题转移。

LCseg: LCseg 由 Galley 等人(2003) 引入,用于分割多方会话。该算法使用词汇衔接来分割主题,它可以处理语音和文本。LCseg 的核心算法有两个主要部分:

  • 一种使用词汇链识别和加权强术语重复的方法。
  • 一种假设主题边界的方法,已知多个同时出现的术语重复链。

主题建模

主题化:该算法类似于文本平铺,只是它使用了潜在的狄利克雷分配(LDA)主题模型来进行分割。从概念上讲,它更简单,因为它不必考虑基于单词的特征的稀疏性。使用由 LDA 主题模型生成的主题 id 来代替单词。

基于图形

graph seg:Glavas et al .(2016)提出了一种基于图的算法,直接捕捉片段之间的语义相关度,而不是用主题相似度来近似。图的每个节点是一个句子,并且为语义相关的句子对创建边。然后通过找到相关度图的最大集团来确定连贯片段。

基于相似性

余弦相似度:可以使用类似于文本平铺的基于窗口的方法来测量两个文本块之间的余弦相似度。文本可以用 BERT 上下文嵌入来表示,这比简单的单词包或 word2vec 嵌入要好得多。例如, Solbiati et al (2021) 使用来自句子-BERT 的嵌入,句子-BERT 具有连体和三联体网络结构,并提供更丰富的句子级嵌入。

资料组

以下是主题分段的常用数据集列表:

TDT 语料库(1998)

  • 话题检测和跟踪(TDT)是 DARPA 发起的一项倡议,旨在调查话题检测的最新成果。
  • 该数据集有 16,000 个新闻故事,一半来自路透社新闻专线,另一半来自 CNN 广播新闻文字稿。
  • 数据集:https://catalog.ldc.upenn.edu/LDC98T25

崔(2000)

图库数据集 _2003)

  • 也是人工生成的语料库。
  • 包含两个语料库,每个都有 500 个文档。
  • 与 CHOI 相比,节段从 4 到 22 不等。

元素与城市(2009)

  • 陈等人(2009)从维基百科的城市和元素中创建了这两个小数据集。
  • 通常用于主题分割模型的评估。

维基 727k (2018)

Malach Corpus (2019)

  • 该语料库包括来自 32 种不同语言的 52,000 名说话者的超过 115,000 小时的自然语音。
  • 对于主题分段任务,该数据集的 10%已经被手动分段。
  • 数据集:https://catalog.ldc.upenn.edu/LDC2019S11

QMSum (2021 年)

  • 这个数据集主要用于文本摘要任务,由 AMI 会议语料库和 ICSI 的转录版本构成。
  • 转录也基于主题或子主题的转移而被分段。
  • 如果您想要针对对话、交谈和会议等口语文本评估您的主题分割模型,这是一个非常好的数据集。
  • 数据集:https://github.com/Yale-LILY/QMSum

关键要点

  • 基于主题或子主题对文本进行分段可以显著提高文本的可读性,并使摘要或信息检索等下游任务变得更加容易。
  • 评估文本分割模型的主要方法是通过 Precision & Recall、Pk 和 WindowDiff 评估指标。
  • 根据手头的任务,有监督和无监督的模型训练方法都为构建性能良好的文本分割模型提供了可行的选择。

参考

Python 点击的权威指南

原文:https://www.assemblyai.com/blog/the-definitive-guide-to-python-click/

Python Click 能为你做什么?在本指南结束时,您将能够创建自己的命令行界面,该界面可以传递上下文,命令可以采用强制或可选参数,以及嵌套命令。这个示例 Python 项目将向您展示如何与不同的点击命令进行交互,从构建块(如click.command)到更高级的命令(如click.make_pass_decorator)。如果您有任何问题,请随时联系我@于坚 _ 唐。你可以在这里找到源代码。

点击,或者“命令行界面创建工具包”是一个用于构建命令行界面的 Python 库。Python Click 的三个要点是命令的任意嵌套、自动帮助页面生成、支持运行时子命令的惰性加载。Click 提供了自己的合理化建议,说明为什么应该使用 Python Click 而不是其他 Python CLI 库。作为一名开发人员,我选择 Click 的理由是,它易于使用,并且提供了创建复杂命令行界面的必要功能。

让我们开始吧,我们要做的第一件事是创建一个,在 click 中,一个组是一组(大概)相关的命令。我们要创建的这个组将基于与一个 JSON 文档的交互,你可以从链接下载,或者跟随页面底部的教程,它将向你展示我是如何使用 AssemblyAI 生成它的,我们将把它作为一个字典加载。该组中的所有命令都将与我们加载的字典交互,因此我们将使用pass_context装饰器来保持字典存储在上下文中。上下文是保存命令执行的所有上下文的对象。在组命令中,上下文对象用于在命令之间传递信息。

import click
import json

'''
@click.group(<name>) creates a command that instantiates a group class
a group is intended to be a set of related commands
@click.argument(<argument name>) tells us that we will be passing an argument
and referring to that argument in the function by the name we pass it
@click.pass_context tells the group command that we're going to be using
the context, the context is not visible to the command unless we pass this

In our example we'll name our group "cli"
'''
@click.group("cli")
@click.pass_context
@click.argument("document")
def cli(ctx, document):
   """An example CLI for interfacing with a document"""
   _stream = open(document)
   _dict = json.load(_stream)
   _stream.close()
   ctx.obj = _dict

def main():
   cli(prog_name="cli")

if __name__ == '__main__':
   main()

在创建我们的组并在 main 中声明它后,我们应该有一个自动生成的"--help"选项,如下所示:

您的应该还没有列出所有的命令,但这只是我们将要构建的内容的一个大概。注意——所有命令必须在main()和我们的click.group装饰器之间定义。

现在我们已经在 Python Click 中创建了第一个组,让我们向它添加第一个命令。让我们创建一个命令来检查上下文,以确认我们有正确的类型。前面我们说过,我们希望将我们的对象作为字典加载到我们的上下文中,因此我们应该会得到如下结果:

要使这个命令成为我们之前创建的组的一部分,我们所要做的就是用装饰器<group name>.command(<command name>)装饰我们的函数。为了确保这个对象能够访问上下文,我们还使用了pass_context装饰器。

在 Python Click 中传递上下文

import pprint

'''
@click.command(<name>) creates a command that can be called with
the name that we pass through

Here we'll create an example command that prints out our context object,
which we expect to be a json looking dictionary
'''
@cli.command("check_context_object")
@click.pass_context
def check_context(ctx):
   pprint.pprint(type(ctx.obj))

现在我们对 decorator 和 Python Click 有了一些熟悉,让我们使用 Click 的功能来制作我们自己的 pass decorator。为什么我们要自己做?以便我们可以传递特定类型的对象(稍后我们还将演示如何使用pass_obj)。让我们制作一个 pass decorator 来专门传递一个 dictionary 对象,我们称之为pass_dict

'''
Here we'll make a pass decorator, which we can use to pass
the last object stored of a type of our choosing in the context
by using click.make_pass_decorator(<type>)
'''
pass_dict = click.make_pass_decorator(dict)

‍Let's 发出一个命令,该命令将使用pass_dict装饰器来传递存储的字典对象。Python Click 库将搜索我们上面指定的类型(dict)的最里面的对象(在上下文中),并将它传递给我们的函数。当我们调用我们的get_keys函数时,它应该是这样的:

我们要做的就是使用pass_dict装饰器传递字典,然后提取键,并使用 Click 的echo stylesecho命令以不同的颜色打印出我们的键。

'''
click.echo is click's version of the echo command
click.style lets us style our output
click.secho is a command that takes a message, and a style command,
is a combination of click.echo and click.style

This command returns the keys to our dictionary object and
demonstrates how to use click.echo, click.style, and click.secho
'''
@cli.command("get_keys")
@pass_dict
def get_keys(_dict):
   keys = list(_dict.keys())
   click.secho("The keys in our dictionary are", fg="green")
   click.echo(click.style(keys, fg="blue"))

好了,现在我们知道了字典对象中有哪些键,让我们创建一个命令来获取我们传递的键的值。我们还将用pass_context来修饰它,因为我们将使用这个命令来演示任意点击命令嵌套的三个租户之一。我们将在get_key之后创建第二个命令,该命令将通过调用命令get_key来获取汇总键的值。

'''
This command gets a specific key from the context object
'''
@cli.command("get_key")
@click.argument("key")
@click.pass_context
def get_key(ctx, key):
   pprint.pprint(ctx.obj[key])

'''
click.invoke(<command>, <args>) is click's way of letting us
arbitrarily nest commands. NOTE: this command can only be used
when both the command being invoked AND the the command
doing the invoking use @click.pass_context

Since we already have a get_key command, we can just call that
to print out a summary
'''
@cli.command("get_summary")
@click.pass_context
def get_summary(ctx):
   ctx.invoke(get_key, key="summary")

两者应该产生相同的输出,如下所示:

想找更多这样的教程?

订阅我们的时事通讯!

[Subscribe Now](Subscribe to our newsletter!)

向命令行界面添加可选参数

现在让我们来看看如何使用click.options装饰器。当您想要向命令传递可选参数时,可以使用这个装饰器。它可以用来传递一个参数,或者作为一个标志传递。如果您希望您的选项作为一个标志被传递,那么在使用您的装饰器时将参数is_flag设置为 True。

'''
@click.option(<one dash usage>, <two dash usage>, is_flag (optional), help = <help>)
is how we can pass options to our command

We'll create a function that gets the "results" of our dictionary
and we will pass it two optional arguments, one to specify that
we want a specific key from the results, and a flag to indicate
whether or not we want to save our results to a json file
'''
@cli.command("get_results")
@click.option("-d", "--download", is_flag=True, help="Pass to download the result to a json file")
@click.option("-k", "--key", help="Pass a key to specify that key from the results")
@click.pass_context
def get_results(ctx, download: bool, key: str):
   results = ctx.obj['results']
   if key is not None:
       result = {}
       for entry in results:
           if key in entry:
               if key in result:
                   result[key] += entry[key]
               else:
                   result[key] = entry[key]
       results = result
   if download:
       if key is not None:
           filename = key+'.json'
       else:
           filename = "results.json"
       with open(filename, 'w') as w:
           w.write(json.dumps(results))
       print("File saved to", filename)
   else:
       pprint.pprint(results)

在我们的例子中,我们显示了字典的"results"键。您会注意到,我还传入了上面我们编写的--key选项,并选择获取结果的文本。

如果我还传入了-d--download,命令会将文件下载到一个.json文档中,而不是在终端中显示。

好的,我想介绍的最后一个点击装饰器是pass_obj,它的行为就像我们根据之前制作 pass 装饰器的经验和之前使用 Python Click 的pass_context所期望的那样。我们还将对我们的文本做一些有趣的事情,我们可以用三种不同的格式获取我们的文本。作为一个大的文本块,我将把它设置为默认的,按句子,或按段落。

'''
@click.pass_obj is similar to @click.pass_context, instead
of passing the whole context, it only passes context.obj

We'll do something fun with our text extractor, we'll include
options to extract as either paragraphs or sentences, and
default to returning one big block of text
'''
@cli.command("get_text")
@click.option("-s", "--sentences", is_flag=True, help="Pass to return sentences")
@click.option("-p", "--paragraphs", is_flag=True, help="Pass to return paragraphs")
@click.option("-d", "--download", is_flag=True, help="Download as a json file")
@click.pass_obj
def get_text(_dict, sentences, paragraphs, download):
   """Returns the text as sentences, paragraphs, or one block by default"""
   results = _dict['results']
   text = {}
   for idx, entry in enumerate(results):
       if paragraphs:
           text[idx] = entry['text']
       else:
           if 'text' in text:
               text['text'] += entry['text']
           else:
               text['text'] = entry['text']
   if sentences:
       sentences = text['text'].split('.')
       for i in range(len(sentences)):
           if sentences[i] != '':
               text[i] = sentences[i]
       del text['text']
   pprint.pprint(text)
   if download:
       if paragraphs:
           filename = "paragraphs.json"
       elif sentences:
           filename = "sentences.json"
       else:
           filename = "text.json"
       with open(filename, 'w') as w:
           w.write(json.dumps(results))
       print("File saved to", filename)

响应应该是这样的:

句子:

段落:

没有可选参数:

好了,现在我将演示如何让两个不同的点击命令组从同一个文件中运行。我们要做的是允许用户传递一个 JSON 文件,或者任何. mp3 文件,该文件的结构与我们的示例 JSON 文件相同。为了遵循本指南的其余部分,你需要一个来自 AssemblyAI 的 API 密匙。我们要做的是复习一下如何使用上面的文本选项,但是我们将直接从 AssemblyAI 中获取句子和段落。

为了举例说明运行两个 python click 命令组的情况,请跳到最后一个代码段。

我们要做的第一件事是声明一些全局变量和我们的导入。请注意,我正在从配置文件中导入我的身份验证密钥。建议您将 API 密钥存储在一个单独的文件中,以避免上传和暴露它们。我们将需要请求来发送请求,不时地休眠来自动轮询我们的端点,并需要来自 configure 的 auth key 来访问 AssemblyAI API。

import requests
from time import sleep
from configure import auth_key

transcript_endpoint = "https://api.assemblyai.com/v2/transcript"
upload_endpoint = 'https://api.assemblyai.com/v2/upload'
headers = {
   "authorization": auth_key,
   "content-type": "application/json"
}
CHUNK_SIZE = 5242880

让我们创建我们的第二组。我将把它命名为“assembly”,因为我们用它来与 AssemblyAI 的 API 交互。我将让创建组的函数也获取我们的 mp3 文件,上传并转录它,并将返回的id保存到上下文中。稍后,我们将使用 id 轻松地从 API 端点获取句子和段落。

@click.group("assembly")
@click.pass_context
@click.argument("location")
def assembly(ctx, location):
   """A CLI for interacting with AssemblyAI"""
   def read_file(location):
       with open(location, 'rb') as _file:
           while True:
               data = _file.read(CHUNK_SIZE)
               if not data:
                   break
               yield data

   upload_response = requests.post(
       upload_endpoint,
       headers=headers, data=read_file(location)
   )
   audio_url = upload_response.json()['upload_url']
   print('Uploaded to', audio_url)
   transcript_request = {
       'audio_url': audio_url,
       'iab_categories': 'True',
   }

   transcript_response = requests.post(transcript_endpoint, json=transcript_request, headers=headers)
   transcript_id = transcript_response.json()['id']
   polling_endpoint = transcript_endpoint + "/" + transcript_id
   print("Transcribing at", polling_endpoint)
   polling_response = requests.get(polling_endpoint, headers=headers)
   while polling_response.json()['status'] != 'completed':
       sleep(30)
       print("Transcript processing ...")
       try:
           polling_response = requests.get(polling_endpoint, headers=headers)
       except:
           print("Expected to wait 30 percent of the length of your video")
           print("After wait time is up, call poll with id", transcript_id)
           return transcript_id
   categories_filename = transcript_id + '_categories.json'
   with open(categories_filename, 'w') as f:
       f.write(json.dumps(polling_response.json()['iab_categories_result']))
   print('Categories saved to', categories_filename)
   ctx.obj = polling_response.json()['id']

‍Thanks 的 API 端点 AssemblyAI 的辉煌设计,我们可以得到几乎完全相同的段落或句子的功能。我们需要做的就是构造端点,然后发送一个 HTTP GET 请求。

@assembly.command("get_sentences")
@click.pass_context
def get_sentences(ctx):
   sentences_endpoint = transcript_endpoint + "/" + ctx.obj + "/sentences"
   sentences_response = requests.get(sentences_endpoint, headers=headers)
   pprint.pprint(sentences_response.json())

@assembly.command("get_paragraphs")
@click.pass_context
def get_paragraphs(ctx):
   paragraphs_endpoint = transcript_endpoint + "/" + ctx.obj + "/paragraphs"
   paragraphs_response = requests.get(paragraphs_endpoint, headers=headers)
   pprint.pprint(paragraphs_response.json())

好了,现在我们的汇编组中已经有了几个示例命令,让我们来看看如何实际上让两个命令组同时运行,并“智能地”(基于我们的输入)运行不同的组。我们要做的是转换我们的main()函数来检查我们的第一个参数是否包含 JSON 文件或. mp3 文件。

import sys

def main():
   if ".json" in sys.argv[1]:
       cli(prog_name="cli")
   if ".mp3" in sys.argv[1]:
       assembly(prog_name="assembly")

正如你从下面两张图片中看到的,我们新的命令行工具可以识别输入并正确运行命令。

现在,关于如何使用 Python Click 制作命令行界面的教程已经接近尾声。如果您对如何使用 Click 有任何疑问,请发微博给我@于坚 _ 唐或通过 LinkedIn 联系我。有关如何使用 AssemblyAI API 的更多信息,请查看这篇在 25 行代码内进行 Python 语音识别的教程或这篇关于构建 YouTube 转录器的教程并关注我们 @assemblyai

2021 年 Python 语音识别的现状

原文:https://www.assemblyai.com/blog/the-state-of-python-speech-recognition-in-2021/

所以你想用 Python 做自动语音识别,或者 ASR,,你发现有很多不同的选项可用。别害怕,我们是来帮忙的。我们可以将我们的 Python 语音识别和语音转文本选项分为两大类:开源和云。

开源解决方案是开源的库(通常在 github 上),您可以将其导入到您的程序中并以编程方式使用,在您自己的资源上进行计算。Python 语音识别的云解决方案在云资源上进行计算,通常通过 API 端点公开,您可以发送使用。

开源与云 Python 语音识别选项

开源 Python 语音识别解决方案的最大优势之一就是它们是开源的。开放源代码意味着你可以看到源代码。你可以准确地知道正在做什么,如何做,什么时候做。如果你是一个非常高级的工程师,开源 python 语音识别解决方案的另一个很大的优势是你可以自己进去修改代码。开源解决方案的最大缺点是进行语音识别所需的计算能力必须由您来提供。无论是在本地,还是在您自己的云资源上。对于许多开发者来说,这是一个麻烦。如果你正在为一个拥有大量云资源和资金的公司或企业开发,这不是一个交易破坏者。然而,如果你没有钱,这是一个缺点。另一个重要的考虑是,开源 python 语音识别选项通常没有基于云的 API 选项准确。如果准确性在您的项目中很重要,那么您可能更适合使用云解决方案。

使用 Python 构建语音识别项目的云解决方案有一个很大的优势,即易于使用,比开源选项更准确,并且不需要您在自己的硬件上托管任何模型..一些云解决方案的主要缺点是成本..幸运的是,有一些免费的选项提供定制,比如定制词汇表、段落检测和用 Python 构建简单语音识别项目的说话者二进制化。一个例子是 AssemblyAI 的语音转文本 API 。云解决方案的另一大优势是,它们比开源选项更容易实现。

要点: 当您为 python 语音识别项目选择解决方案时,要考虑的主要问题是准确性、成本和易于实现。

开源 Python 语音识别选项

有许多开源 Python 语音识别选项。我们将在这里讨论三个最多产的。这些开源的 python 语音识别库分别是 wav2letterspeecher recognitionDeepSpeech

wav2 字母

开源库 wav2letter 最早是由脸书开发的。它现在被移植到一个新的开源库,名为 flash,但开发者仍然只知道它的旧名 wav2letter。wav2letter 很酷的一点是,从声学建模到语言建模,它完全建立在卷积神经网络(CNN)上。这是一个罕见的景象,因为自 2014 年以来,自然语言处理(NLP)一直由基于递归神经网络(RNN)的模型主导。

关于 wav2letter 的另一个有趣的事情,也是对经验不太丰富的程序员来说的一个缺点是,它需要构建为在 Python 中使用,并且不能简单地用 pip 命令安装。事实上,你需要一个 C++编译器来安装 wav2letter!如果您正在用 Python 构建一个简单的语音识别项目,这肯定会妨碍您的计划。另一个缺点是,由于它被移植到了手电筒,现在你还需要手电筒作为一个依赖来使用 wav2letter。

仅仅是安装 wav2letter 这个动作本身就可以成为你要做的一个迷你项目。如果您对 wav2letter 项目感兴趣,因为您喜欢 Python 语音识别项目的声音,该项目 a)难以启动,b)有许多依赖性,c)建立在一个与大多数其他语音到文本库略有不同的酷神经网络结构上,那么 wav2letter 适合您,您可以从阅读关于如何安装 wav2letter 的指南开始。

语音识别

一个名为 SpeechRecognition 而不是的库怎么可能是最好的语音识别开源库呢?不幸的是,很难说这是一个真正的语音识别开源解决方案,因为它真正做的是包装其他语音识别技术。诚然,它确实支持很多其他技术,包括谷歌云语音到文本,CMU 斯芬克斯,Wit,Azure,Houndify,IBM 和 Snowboy。当然,你能离线(本地)使用的只有 CMU 斯芬克斯和雪球。还有什么其他的解决方法?基于云的解决方案被包装在一个开源库中,这样看起来你比你有更多的可定制性。

如果你不寻找云解决方案,那么 SpeechRecognition Python 开源库可能不适合你。它提供的唯一真正的离线解决方案是使用 CMU 斯芬克斯,Snowboy 不再被创作者支持,我甚至再也找不到它的在线文档的链接。如果你能找到它,请发微博到@于坚 _ 唐,因为在我见过的所有语音识别库中,它有第二酷的名字(Sphinx 肯定更酷)。

CMU 狮身人面像的好处是它有很多开发者文档和常见问题。它建立在卡内基梅隆大学 20 多年的研究基础上,卡内基梅隆大学是世界上顶级(有些人可能认为是最好的)计算机科学学院。它不像其他语音转文本 Python 解决方案那样需要大量资源,并且支持多种语言。如果在这一点上,你觉得我甚至不再谈论 SpeechRecognition 库了,那是因为我没有。SpeechRecognition 是其他语音到文本解决方案的简单包装。事实上,它实际上调用另一个语音转文本服务来完成转录。虽然它把它们都集成到一个包中很好,这样你只需下载一个库就可以使用多种服务!如果您正在测试它提供包装器的所有云服务,我会推荐这个库。

import speech_recognition as sr

# obtain audio from the microphone
r = sr.Recognizer()
with sr.Microphone() as source:
   print("Say something!")
   audio = r.listen(source)

# recognize speech using Sphinx
try:
   print("Sphinx thinks you said " + r.recognize_sphinx(audio))
except sr.UnknownValueError:
   print("Sphinx could not understand audio")
except sr.RequestError as e:
   print("Sphinx error; {0}".format(e))

深度演讲

DeepSpeech 最初是百度研究团队制作的一篇关于语音识别技术的论文。我链接的库是 Mozilla 的 GitHub 上的开源项目。DeepSpeech 可以离线运行,也可以在设备上运行。DeepSpeech 可以在多种设备上工作,从 Raspberry Pi 设备到用于训练行业模型的实际 GPU。

DeepSpeech 很容易下载并开始使用,你所要做的就是使用 pip 安装它,然后下载音频文件,如下面的代码片段。实际上也没有规定说你必须使用 DeepSpeech 的英语模型,如果你有自己的模型,你完全可以使用它们。

# Install DeepSpeech
pip3 install deepspeech

# If using GPU
pip3 install deepspeech-gpu

# Download pre-trained English model files
curl -LO https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.pbmm
curl -LO https://github.com/mozilla/DeepSpeech/releases/download/v0.9.3/deepspeech-0.9.3-models.scorer

请记住,要使用 DeepSpeech,必须在设备上运行。这意味着它将占用大量本地计算资源。如果你要在 GPU 上训练一个 DeepSpeech 模型,确保你有所需的 CUDA 文件。针对 GPU 的 DeepSpeech 依赖于 CUDA 10.1 和 CuDNN 7.6。此外,你必须安装带有 pip 的 deepspeech-gpu。如果你个人想在本地运行你的模型做语音识别,我推荐 DeepSpeech。我还认为 DeepSpeech 是一个相当先进的库,可以按预期使用,所以我也向那些将进行大量定制的高级程序员推荐这个库。

云 Python 语音识别

AssemblyAI 创建了一个快速、自动的语音识别 API,开发者可以免费使用。云托管 API 使用起来非常简单,并且包含了许多特性。在这一节中,我将介绍如何使用 AssemblyAI 语音到文本 API 来进行转录,如何进行说话者二进制化,如何将自定义词汇添加到转录中,以及如何从生成的转录中创建段落。按照本教程的其余部分,你需要做的就是获得一个免费语音转文本 API 键,并获取一个你想要转录的音频文件。

你应该在图片中我圈出的地方看到你的 API 密匙。

使用 AssemblyAI 的语音转文本 API

AssemblyAI 的语音转文本 API 使用起来快速、准确、超级简单。它也很棒数据隐私和安全。从我们的音频文件开始,首先我们将它上传到 AssemblyAI 上传端点,然后我们将发送一个转录请求来转录我们上传的文件。如果你把你的音频文件上传到了网上的某个地方(比如在 S3 桶里),你可以跳过这一步。

auth_key = '<your AssemblyAI API key here>'
headers = {"authorization": auth_key, "content-type": "application/json"}
def read_file(filename):
   with open(filename, 'rb') as _file:
       while True:
           data = _file.read(5242880)
           if not data:
               break
           yield data

upload_response = requests.post('https://api.assemblyai.com/v2/upload', headers=headers, data=read_file('<path to your file here>'))
audio_url = upload_response.json()[‘upload_url’]

‍All 你需要得到你的转录是向 AssemblyAI 转录端点发出一个请求,带有一个到上传的音频文件的链接,等一会儿,然后用你的请求的 id 向转录端点发出另一个请求,在响应中得到你的转录。

transcript_request = {'audio_url': audio_url}
transcript_response = requests.post("https://api.assemblyai.com/v2/transcript", json=transcript_request, headers=headers)
_id = transcript_response.json()['id']

发送转录响应后,我们将等待一个短暂的间隔。文档目前建议音频文件的长度为 30%,但在我的经验使用中,我发现它比那要快一点。一旦我们等待了一段合理的时间,我们只需发送一个轮询请求并下载我们的脚本。

polling_response = requests.get("https://api.assemblyai.com/v2/transcript/" + _id, headers=headers)
if polling_response.json()['status'] != 'completed':
   print(polling_response.json())
else:
   with open(_id + '.txt', 'w') as f:
       f.write(polling_response.json()['text'])
   print('Transcript saved to', _id, '.txt')

想玩我们的语音转文本 API 吗?

现在就开始免费!

Start Now

使用 AssemblyAI 的语音识别 API 自定义词汇表

AssemblyAI 的语音转文本 API 最酷的一点是为您的转录请求不同功能的简单性。您可以通过使用 word boost 参数并传入您希望模型识别的单词列表,将自定义词汇传递给 AI 转录。然后,抄本请求将看起来像下面的代码。您不需要添加 boost param 关键字,除非您希望增加或减少单词 boost 的强度。

transcript_request = {
    'audio_url': audio_url,	
    'word_boost': ['boost', 'these', 'words'],
    'boost_param': 'high
}

在 AssemblyAI 语音转文本 API 中使用自定义词汇时要遵循的一些规则是:1)删除标点符号,2)每个单词都应该是口语形式,3)缩写词的字母之间不能有空格。

使用 AssemblyAI 的语音识别 API 实现说话者二进制化

通过传递一个额外的 API 参数,您还可以从您的脚本中获得说话者二进制化。可以像下面的代码一样生成一个将演讲者标签返回给您的抄本请求。

transcript_request = {
    'audio_url': audio_url,	
    'speaker_labels': 'true'
}

‍An 用说话者二进制化的抄本的响应的例子看起来像下面的 JSON。你可以在“话语”键下找到你的说话者二进制化结果。扬声器将从 A 到 z 进行标记。

{
   "acoustic_model": "assemblyai_default",
   "audio_duration": 150.766167800454,
   "audio_url": "https://app.assemblyai.com/static/media/phone_demo_clip_1.wav",
   "confidence": 0.922175805047867,
   "dual_channel": true,
   "format_text": true,
   "id": "5552830-d8b1-4e60-a2b4-bdfefb3130b3",
   "language_model": "assemblyai_default",
   "punctuate": true,
   "status": "completed",
   "text": "Hi, I'm joy. Hi, I'm sharon. Do you have kids in school. ...",
   # the "utterances" key below is a list of the turn-by-turn utterances found in the audio
   "utterances": [
       {
           # speakers will be marked as "A" through "Z"
           "speaker": "A",
           "confidence": 0.97,
           "end": 1380,
           "start": 0,
           # the text for the entire speaker "turn"
           "text": "Hi, I'm joy.",
           # the individual words from the speaker "turn"
           "words": [
               {
                   "speaker": "A",
                   "confidence": 1.0,
                   "end": 320,
                   "start": 0,
                   "text": "Hi,"
               },
               ...
           ]
       },
       # the next "turn" by speaker "B" - for example
       {
           "speaker": "B",
           "confidence": 0.94,
           "end": 3260,
           "start": 0,
           "text": "Hi, I'm sharon.",
           "words": [
               {
                   "speaker": "B",
                   "confidence": 1.0,
                   "end": 480,
                   "start": 0,
                   "text": "Hi,"
               },
               ...
           ]
       },
       {
           "speaker": "A",
           "confidence": 0.94,
           "end": 5420,
           "start": 2820,
           "text": "Do you have kids in school.",
           "words": [
               {
                   "speaker": "A",
                   "confidence": 1.0,
                   "end": 4300,
                   "start": 2820,
                   "text": "Do"
               },
               ...
           ]
       },
   ],
   # all of the words found in the audio across all speakers
   "words": [
       {
           "speaker": "A",
           "confidence": 1.0,
           "end": 320,
           "start": 0,
           "text": "Hi,"
       },
       {
           "speaker": "A",
           "confidence": 1.0,
           "end": 720,
           "start": 320,
           "text": "do"
       },
       ...
   ]
}

从 AssemblyAI 的语音识别 API 获取段落

在您完成转录并传入您需要的任何参数(如自定义词汇或说话者分音)后,如果您还想在几乎没有额外工作的情况下将转录本进一步处理成段落,那么还有一个额外的端点可以使用。你所要做的就是向你的成绩单 id + '/paragraphs '的成绩单端点发送一个 GET 请求,如下所示。

paragraphs_endpoint = "https://api.assemblyai.com/v2/transcript/" + _id + "/paragraphs"
paragraphs_response = requests.get(paragraphs_endpoint, headers=headers)
pprint.pprint(paragraphs_response.json())

您从段落端点得到的‍The 响应应该类似于下面的 JSON。

{
   "paragraphs":[
       {
           "text":"Hello. I'm Sunny Williams. I'm up here on the International Space Station. So this is No two. This is a really cool module.",
           "start":770,
           "end":10710,
           "confidence":0.97,
           "words":[
           {"text": "Hello.", "start": 100, "end": 1000, "confidence": 0.99},
           "..."
           ]
       },
       ...
       ],
   "id": "3otc28umy-25ae-4446-b133-70e244be5208",
   "confidence": 0.951530612244907,
   "audio_duration": 521.352
}

Python 中的实时语音识别

最后,我还会提到一件事,AssemblyAI 的语音转文本 API 能够进行实时流语音识别。实时语音识别需要升级您的帐户,因此它不是免费语音转文本 API 的一部分。

2021 年 Python 语音识别现状综述

在这篇文章中,我回顾了 Python 用户语音识别技术的现状,从三个流行的开源库 wav2letter、speech recognition 和 DeepSpeech,到像 AssemblyAI 的语音识别 API 这样的云解决方案。Wav2letter 是一个开源库,在其模型中使用纯 CNN 架构,最初由脸书创建。SpeechRecognition 主要是围绕一些云 API 的包装库和一个基于 CMUSphinx 的模型,CMUSphinx 是卡内基梅隆大学完成的研究。DeepSpeech 最初是由百度的科学家概念化的,他们发表了一篇关于它的论文;开源库由 Mozilla 维护,它是我提到的唯一一个在设备上运行的库。

我在这里介绍的唯一云提供商是 AssemblyAI 的自动语音识别 API。AssemblyAI 的语音转文本 API 使用起来快速、准确、简单。还提供了大量的功能,如说话者二进制化、自定义词汇和段落提取,实现起来就像发送 HTTP 请求一样简单。想了解更多关于 AssemblyAI 的信息,请在 Twitter 上关注我们 @assemblyai ,想了解更多 Python 信息或编程迷因,请关注作者@于坚 _ 唐

用 AssemblyAI 实时转录 Twilio 电话

原文:https://www.assemblyai.com/blog/transcribe-twilio-phone-calls-in-real-time-with-assemblyai/

Twilio 是一个领先的客户沟通和互动平台,从定制优惠券到个性化约会提醒,它可以轻松创造个性化的用户体验。但是,如果您不是仅仅将信息从发送到,而是想要记录来自客户的信息从传入会怎么样呢?

例如,医生可能希望自动记录患者的电话,这样她就可以专注于与患者交流,而不是手忙脚乱地写下笔记。打完电话后,医生可以查看记录以便做出诊断,确信她没有遗漏任何细节。

以这种方式转录电话不仅有用,而且用 AssemblyAI 的实时转录服务也很容易做到。本教程结束时,你将能够打电话,并看到你的话实时转录在你面前!让我们开始吧。

先决条件

为了跟随本教程,你需要设置一个 Twilio 账户。你还需要有一个 AssemblyAI 账户,必须升级才能使用实时转录功能。

本教程中的命令是针对基于 Debian 的系统( Ubuntu 20.04 LTS ),所以你可能需要修改一些命令来适应你的操作系统。

装置

首先,你需要确保你已经安装了 Node.js节点包管理器cURLwget

| 【Ryan @ Ubuntu】:【sudo apt 安装 nodejs NPM curl wget】 |

附加说明

您可以复制 Bash 命令并粘贴到终端中,方法是在终端中右键单击并选择“粘贴”。

ngrok

接下来,我们将安装[ngrok](https://ngrok.com/download),它允许我们生成一个面向公众的 URL,我们可以用它将 HTTP 请求从 Twilio 转发到我们的本地主机。

| 【Ryan @ Ubuntu】😒 curl-s https://ngok-agent . S3 . Amazon ws . com/ngok . ASC | sudo 茶/etc/apt/trusted . gpg . d/ngok . ASC |

Twilio CLI 工具

最后,我们将安装 Twilio 的 CLI 工具,这将允许我们指定我们的 Twilio POST 请求被定向到我们的ngrok URL。

| 【Ryan @ Ubuntu】:~$ wget-qo-https://twilio-CLI-prod . S3 . Amazon ws . com/twilio _ pub . ASC \ | sudo apt-key add-sudo touch/ |

系统会提示您使用twilio login登录,然后为您的帐户设置一个速记标识符。最后,运行twilio profiles:use $IDENTIFIER来使用您的帐户,这里的$IDENTIFIER已经被替换为您刚刚为您的帐户设置的简写标识符。

正在创建 ngrok 隧道

现在我们已经完成了设置,我们可以继续有趣的事情了!我们要做的第一件事是创建一个ngrok隧道。打开一个新的终端并创建一个ngrok隧道

| (基地) 瑞安@ Ubuntu:~$ ngrok http 8080 |

其中 8080 指定了localhost端口。运行此命令后,终端中将显示几个 URL。复制 HTTPS 转发网址,就像下面用红色圈出的那个。

设置 Twilio URL

保持ngrok运行并打开一个新的终端。我们需要告诉 Twilio 我们的 Twilio 号码的路由地址是什么。我们的做法如下

| (基) 瑞安@ Ubuntu:~$ TWILIO phone-numbers:update $ TWILIO _ NUMBER-voice-URL $ NGROK _ HTTP _ URL |

其中$TWILIO_NUMBER已被替换为您的 Twilio 编号,您可以在 Twilio 控制台中找到该编号(“试验编号”下的编号)

并且$NGROK_HTTP_URL已经被替换为您从上一节复制的 ngrok URL。

导出程序集密钥

接下来,转到 AssemblyAI 并复制您的 API 密钥,您会在下面的红框中找到它。

现在,将您的 AssemblyAI API 密钥导出到一个环境变量

| (基地) 瑞安@ Ubuntu:~$ export API KEY = $ YOUR _ KEY |

您已经用刚刚复制的 AssemblyAI API 键替换了$YOUR_KEY

启动您的本地主机

接下来,导航到您想要运行转录的目录,然后执行

| (基础) 瑞安@ Ubuntu:~$ NPM 安装快捷 ws wavefile |

以便安装必要的软件包。现在快跑

| (base) 瑞安@ Ubuntu:~$ wget https://raw . githubusercontent . com/assembly ai/twilio-real time-tutorial/master/transcripte . js |

从 AssemblyAI 的 GitHub 下载transcribe.js。最后,使用以下命令启动服务器

| (基) 瑞安@ Ubuntu:~$ node transcripte . js |

然后拨打您的 Twilio 号码并开始讲话。你会在控制台看到 AssemblyAI 的转录!请随意修改transcribe.js以满足您的需求——您可以查看这个视频,了解上述方法如何工作的深入解释以及如何在浏览器中而不是在控制台中显示您的转录!

脚注

  1. AssemblyAI 的 GitHub 上的这个代码可能会帮助你用 JavaScript 在浏览器中动态更新转录
  2. 本教程引用了发布在 Twilio 网站上的代码

用 AssemblyAI 在 S3 桶中转录音频文件

原文:https://www.assemblyai.com/blog/transcribing-audio-files-in-an-s3-bucket-with-assemblyai/

AssemblyAI 的语音转文本 API 既可以用于本地文件,也可以用于公开访问的在线文件,但如果你想转录一个受限访问的音频文件呢?幸运的是,您也可以用 AssemblyAI 来做这件事!

继续阅读,了解如何使用 AssemblyAI 的 API 转录存储在 AWS S3 桶中的音频文件。

介绍

为了从 S3 桶转录音频文件,AssemblyAI 将需要临时访问该文件。为了提供这种访问,我们将生成一个预先指定的 URL ,它只是一个内置了临时访问权限的 URL。

整个过程如下所示:

  1. 使用 boto 为 S3 音频文件生成一个预先设计的 URL
  2. 用 POST 请求将这个 URL 传递给 AssemblyAI 的 API
  3. 等待转录完成,然后用 GET 请求获取它

POST a presigned S3 URL to AssemblyAI and then GET the transcript

先决条件

为了用 AssemblyAI 转录 S3 音频文件,您需要满足两个先决条件:

汇编 AI 帐户

首先,你显然需要一个 AssemblyAI 账户——如果你还没有,你可以在这里注册一个免费账户。

接下来,你需要记下你的 AssemblyAI API 密钥,你可以在登录后在你的仪表板上找到它。它将出现在屏幕右侧的你的 API 键

您稍后将需要这个键的值,所以让浏览器窗口保持打开,或者将该值复制到一个文本文件中。

AWS IAM 用户

其次,您需要一个拥有Programmatic访问权限和AmazonS3ReadOnlyAccess权限的 AWS IAM 用户。如果您已经有了这样一个 IAM 用户,并且知道它的公钥和私钥,那么您可以继续下一部分。否则,现在创建一个,如下所示:

首先,以 root 用户或具有适当访问权限的另一个 IAM 用户的身份登录 AWS,然后转到 IAM 管理控制台添加一个新用户。

设置您想要的用户名,选择下的程序化访问,选择 AWS 访问类型:

点击下一步,然后直接附加已有保单。将 AmazonS3ReadOnlyAccess 复制并粘贴到过滤器策略搜索框中,然后通过单击旁边的复选框添加该权限:

点击下一个的,并添加标签。然后点击下一步,并在点击创建用户之前查看 IAM 用户资料以确保一切正常:

最后,记下 IAM 用户的访问密钥 ID秘密访问密钥。同样,我们稍后将需要这些值,所以在继续之前将它们复制到一个文本文件中。

警告

确保复制 IAM 用户的秘密访问密钥并将其记录在安全的地方。一旦您关闭了添加用户序列的最后一个窗口,您将无法再次访问该密钥,如果您忘记/丢失了原始密钥,则需要重新生成该密钥。

克隆程序集 GitHub Repo

现在,我们可以继续实际生成和获取 S3 音频文件的转录!首先,通过在终端输入以下内容,从 AssemblyAI 的 GitHub 中克隆关联的 repo :

git clone https://github.com/AssemblyAI/transcribe-from-s3-bucket

克隆存储库后,创建并激活虚拟环境,然后安装必要的依赖项:

pip install -r requirements.txt

拿到抄本

要获得 S3 音频文件的转录,打开transcribe_from_s3.py并编辑以下变量,使其等同于您应用的相关值:

  1. bucket_name -您的 AWS S3 铲斗的名称
  2. object_name -您想要转录的 S3 桶中的音频文件的名称
  3. iam_access_id -具有程序访问和 S3 读取权限的 IAM 用户的访问 ID
  4. iam_secret_key-IAM 用户的密钥
  5. assembly_key -您的 AssemblyAI API 密钥

一旦完成,只需运行transcribe_from_s3.py来查看控制台中打印的 S3 音频文件的转录!

如果你有兴趣了解引擎盖下发生的事情,请阅读下一部分了解更多。

在后台

本节详细介绍了transcribe_from_s3.py是如何工作的,因此您可以修改代码来满足您的需要。

首先,导入必要的包并设置相关的变量值:

import boto3
from botocore.exceptions import ClientError
import logging
import requests
import time

# Set relevant values
bucket_name = "<BUCKET_NAME>"
object_name = "<AUDIO_FILE_NAME>"

iam_access_id = "<IAM_ACCESS_ID>"
iam_secret_key = "<IAM_SECRET_KEY>"

assembly_key = "<ASSEMBLYAI_API_KEY>"

从这里开始,我们只需遵循本文简介中概述的顺序:

  1. 使用 boto 为 S3 音频文件生成一个预先设计的 URL
# Create a low-level service client with the IAM credentials
s3_client = boto3.client(
    's3', 
    aws_access_key_id=iam_access_id,
    aws_secret_access_key=iam_secret_key)

# Generate a pre-signed URL for the audio file that expires after 30 minutes
try:
    p_url = s3_client.generate_presigned_url(
        ClientMethod='get_object',
        Params={'Bucket': bucket_name, 'Key': object_name},
        ExpiresIn=1800)

except ClientError as e:
    logging.error(e)

2.通过 POST 请求将预先签名的 URL 传递给 AssemblyAI 的 API:

# Use your AssemblyAI API Key for authorization
headers = {
    "authorization": assembly_key,
    "content-type": "application/json"
}

# Specify AssemblyAI's transcription API endpoint
upload_endpoint = "https://api.assemblyai.com/v2/transcript"

# Use the presigned URL as the `audio_url` in the POST request
json = {
    "audio_url": p_url
}

# Queue the audio file for transcription with a POST request
post_response = requests.post(upload_endpoint, json=json, headers=headers)

3.等待转录完成,然后用 GET 请求获取它:

# Specify the endpoint of the transcription
get_endpoint = upload_endpoint + "/" + post_response.json()['id']

# GET request the transcription
get_response = requests.get(get_endpoint, headers=headers)

# If the transcription has not finished, wait until it has
while get_response.json()['status'] != 'completed':
  get_response = requests.get(get_endpoint, headers=headers)
  time.sleep(5)

# Once the transcription is complete, print it out
print(get_response.json()['text'])

最后的话

本教程到此为止!

如果你在寻找其他常见问题的答案,你可以查看 AssemblyAI 的帮助中心。

如果你想了解更多关于使用 AssemblyAI 的语音到文本 API 进行构建的信息,包括关于音频智能功能的信息,如自动章节、情感分析和实体检测,请查看文档

如果你只是在寻找更多精彩的内容,请查看 AssemblyAI 博客并关注我们的时事通讯。

想了解更多关于 ML 的所有事情?

关注我们的时事通讯,获取教程、项目和论文评论。

Follow

初学者的变形金刚-介绍

原文:https://www.assemblyai.com/blog/transformers-for-beginners/

本周我们来看看变形金刚。

几年前,谷歌的研究人员在一篇名为《你只需要注意力》的文章中介绍了变形金刚。变形金刚自问世以来,在业界被广泛采用。像伯特、GPT-3 这样的模型使用变形金刚对自然语言处理和 T2 自动语音识别做出了突破性的改进。从那以后,像 HuggingFace 这样的模型库使得每个人都可以在他们的项目中使用基于 Transformer 的模型。

但是什么是变形金刚,它们是如何工作的?它们与其他深度学习模型如 RNNs、LSTMs 有什么不同?为什么他们更好?在这段视频中,我们将了解这一切!

https://www.youtube.com/embed/_UVfwBqcnbM?feature=oembed

面向初学者的无监督机器学习

原文:https://www.assemblyai.com/blog/unsupervised-machine-learning-for-beginners/

在这个视频中,我们学习无监督机器学习。

您将了解到:

  • 什么是无监督学习
  • 使聚集
  • k 均值
  • 离群点检测
  • 潜在变量建模
  • 主成分分析
  • 自动编码器

看这里:

https://www.youtube.com/embed/yteYU_QpUxs?feature=oembed

用 Node.js 转录本地音频文件

原文:https://www.assemblyai.com/blog/uploading-files-to-assemblyai-using-node-js-and-javascript/

在之前的博客中,我们介绍了如何使用 AssemblyAI 语音转文本 API、Node.js 和 JavaScript 构建一个简单的命令行应用程序来转录音频文件。那个博客用的是音频网址。但是如果你想直接从你的电脑或者设备上传一个音频文件呢?

艾议员会掩护你的!

Kermit the frog drinking tea saying "Relax, we got you covered"

在这篇文章中,我们将通过引入文件上传功能来扩展我们在之前的博客中创建的应用。

先决条件

如果你想看完整的代码项目,可以从这个 GitHub 库获得。

设置克隆的存储库

如果您还没有完成上一篇博客中的教程,那么遵循那篇文章中的步骤可能是个好主意,即使您只是打算使用 GitHub 资源库中已完成的代码。

我们需要导航到包含我们的节点应用程序的目录。

cd transcribe

‍Create 的一个新文件在transcribe 目录里面:

如果使用 Windows:

New-Item uploadFile.js

或者,如果使用 macOS 或 Linux:

touch uploadFile.js

如果您还没有将 AssemblyAI API 密钥添加到。env "文件,那么现在就这样做。您可以在 AssemblyAI 仪表板中找到您的 API 键,并将其作为值添加到变量中,如下所示:

ASSEMBLYAI_API_KEY = "YOUR_API_KEY"

您现在已经准备好编写uploadFile函数了。

将本地音频文件上传到程序集

当我们上传一个文件到 AssemblyAI 时,我们需要以 chunked data 的形式发送它。这是 HTTP 中使用的一种传输编码方法。

AssemblyAI 然后会将音频文件保存到私有存储中,创建一个只能通过 AssemblyAI API 访问的 URL。所有上传在转录后立即被删除,因为我们不存储上传。

对我们的 HTTP 请求的响应将包括这个存储的音频文件的 URL。

我们开始吧!

在您的代码编辑器中打开我们之前创建的uploadFile.js。将以下代码复制并粘贴到该文件中:

require('dotenv').config();
const fetch = require('node-fetch');
const fs = require('fs');
const url = 'https://api.assemblyai.com/v2/upload';

上面的代码将导入我们将使用的 Node.js 库。我们还将url设置为 AssemblyAI upload API 端点的值。

Node.js fs (文件流)库包含在 Node 标准库中,我们将使用它来创建本地音频文件的文件流,准备作为分块数据发送到 AssemblyAI。

就像我们在上一篇文章中制作的应用程序一样,我们将在命令行上传递一个参数。这一次,它将是我们的音频文件的文件路径。

let args = process.argv.slice(2);
let audioPath = args[0];

接下来,我们将使用包含在fs模块中的readFile方法将音频文件转换成流。此方法需要文件路径和一个函数。

fs.readFile(audioPath, (err, data) => { 
  if (err) {
    return console.log(err);
  }
}

上面的代码使用一个箭头函数来处理文件流。如果出现错误,该函数会将错误记录到命令行中。

现在我们在内存中有了我们的文件作为data的值,我们可以继续向 AssemblyAI 发出 HTTP POST 请求。

和上一篇文章一样,我们将使用fetch来发出请求。Fetch 需要一些参数,下面的代码定义了它们。将此代码添加到fs箭头函数中。

fs.readFile(audioPath, (err, data) => { 
  if (err) {
    return console.log(err);
  }
}
// add the code below to the arrow function
const params = {
  headers: {
    "authorization": process.env.ASSEMBLYAI_API_KEY,
    "Transfer-Encoding" : "chunked"
  },
  body: data,
  method: 'POST'
};

我们正在添加一个 AssemblyAI API 键,它是从.env文件中检索到的,与chunked的重要的Transfer-Encoding设置一起添加到标题中。

请求的主体是文件流data

最后一步是发出实际的 HTTP Post 请求,并将结果upload_url打印到命令行!

fs.readFile(audioPath, (err, data) => { 
  if (err) {
    return console.log(err);
  }

const params = {
  headers: {
    "authorization": process.env.ASSEMBLYAI_API_KEY,
    "Transfer-Encoding" : "chunked"
  },
  body: data,
  method: 'POST'
};

// add the code below to the arrow function

fetch(url, params)
  .then(response => response.json())
  .then(data => {
   console.log(`URL: ${data['upload_url']}`)
  })
  .catch((error) => {
    console.error(`Error: ${error}`);
  });

}

‍We 还处理 HTTP 请求期间发生的任何错误。

整个代码应该如下所示:

require('dotenv').config();
const fetch = require('node-fetch');
const fs = require('fs');
const url = 'https://api.assemblyai.com/v2/upload';

let args = process.argv.slice(2);
let audioPath = args[0];

fs.readFile(audioPath, (err, data) => {
  if (err) {
    return console.log(err);
  }

  const params = {
    headers: {
      "authorization": process.env.ASSEMBLYAI_API_KEY,
      "Transfer-Encoding": "chunked"
    },
    body: data,
    method: 'POST'
  };

  fetch(url, params)
    .then(response => response.json())
    .then(data => {
      console.log(`URL: ${data['upload_url']}`)
    })
    .catch((error) => {
      console.error(`Error: ${error}`);
    });
});

是时候尝试一下了!

我们的应用程序现在能够从本地文件系统或互联网上的资源上传音频文件。更好的是,我们可以从命令行完成整个过程。

第一步是上传文件。确保当前目录是transcribe,或者您的代码所在的位置,并输入以下命令:

node uploadFile.js C:\Path\To\Audio\File.mp3

确保使用计算机上音频剪辑的路径更新上述代码。如果你需要一个示例音频剪辑来测试,请随意下载这个 one

如果您没有任何错误,您应该会看到upload_url被打印到命令行。

现在,您可以复制这个 URL,并将其传递到应用程序的下一个阶段,使用前一篇博文中编写的代码。

node upload.js RETURNED_UPLOAD_URL

‍This 命令应该将转录 ID 打印到屏幕上。复制 ID 并输入应用程序的最终命令。

node download.js TRANSCRIPTION_ID

‍If 你的转录准备好了,你的文本将被打印到命令行!

Gif of the command line process described above

现在怎么办?

在这篇文章中,我们成功地学习了如何使用fs将文件作为二进制对象上传到 AssemblyAI 上传 API。

如果你还没有尝试过上一篇文章中的挑战,这可能是一个有趣的下一步。

让我们知道你的进展如何,或者如果你对这篇博文或 AssemblyAI 的主题有任何问题!

posted @ 2024-10-31 16:48  绝不原创的飞龙  阅读(16)  评论(0编辑  收藏  举报