(以下内容搬运自飞桨PaddleSpeech语音技术课程,点击链接可直接运行源码)
语音翻译(ST, Speech Translation)是一项从一段源语言音频中翻译出目标语言的任务。
本章主要针对语音到文本的翻译,比如,从一段英文语音中,得到中文的翻译文本。
级联模型由独立的两个模型,语音识别模型(ASR)和机器翻译模型(MT)组成。先通过 ASR 模型从语音中识别出源语言的相应文本,在利用 MT 模型将相应文本翻译成目标语言。
端到端模型不显式对输入语音做文字识别,而直接生成翻译结果。
相对于端到端模型,级联模型存在以下一些问题:
1.错误传播(error propagation),由 ASR 识别错误所产生的错误文本,也会传递给 MT 模型,往往会导致生成更糟糕的翻译结果。
2.时延叠加(latency accumulation),因为使用两个级联的模型,需要对输入数据进行多次处理,实际的时延是两个模型时延的累加,效率低于端到端模型。
由于 ASR 章节已经介绍了 Transformer 以及语音特征抽取,在此便不做过多介绍,感兴趣的同学可以去相关章节进行了解。
本小节,主要讨论利用 transformer(seq2seq)进行ST与ASR的异同。
相似之处在于,两者都可以看做是从语音(speech)到文本(text)的任务。将语音作为输入,而将文字作为输出,区别只在于生成结果是对应语言的识别结果,还是另一语言的翻译结果。
因此,我们只需要将数据中的目标文本替换为翻译文本( Y Y Y),便可利用 ASR 的模型结构实现语音翻译。
规范化地讲,对于 ASR,利用包含语音( S S S)和转写文本( X X X)的数据集,训练得到一个模型 M A S R M_{ASR} MASR,能对任意输入的源语言语音 S ^ \hat{S} S^ 进行文字识别,输出结果 X ^ \hat{X} X^。
而ST的语料集,通常包含语音( S S S)、转写文本( X X X)以及翻译文本( Y Y Y),只需将ASR实践中的转写文本 X X X替换为对应的翻译文本 Y Y Y,便可利用同样的流程得到一个翻译模型 M S T M_{ST} MST,其能对任意输入的源语言语音 S ^ \hat{S} S^ 进行翻译,输出结果 Y ^ \hat{Y} Y^
值得注意的是,相较于 ASR 任务而言,在 ST 中,因为翻译文本与源语音不存在单调对齐(monotonic aligned)的性质,因此 CTC 模块不能将翻译结果作为目标来使用,此处涉及一些学术细节,感兴趣的同学可以自行去了解 CTC 的具体内容。
我们会在 PaddleSpeech 中放一些 Topic 的技术文章(如 CTC ),欢迎大家 star 关注。
相比与 ASR 任务,ST 任务对于数据的标注和获取更加困难,通常很难获取大量的训练数据。
因此,我们讲讨论如何更有效利用已有数据,提升 ST 模型的效果。
1.先利用 ASR 对模型进行预训练,得到一个编码器能够有效的捕捉语音中的语义信息,在此基础上再进一步利用翻译数据训练ST模型。
2.相较于 ASR 任务的二元组数据( S S S, X X X),通常包含三元组数据( S S S, X X X, Y Y Y)的ST任务能够自然有效的进行多任务学习。
顾名思义,我们可以将ASR任务作为辅助任务,将两个任务进行联合训练,利用ASR任务的辅助提升 ST 模型的效果。
具体上讲,如图所示,可以利用一个共享的编码器对语音进行编码,同时利用两个独立的解码器,分别执行 ASR 和 ST 任务。
相比于文本到文本的机器翻译具有充足的语料(通常上百万条),语音到文本的翻译的语料很匮乏。那是否可以将文本到文本的翻译语料利用上来提升 ST 的模型效果呢?答案是肯定的。
FAT 模型[1],借鉴了 Bert[2] 和 TLM[3]的 masked language model 预训练思路,并将其拓展到语音翻译的跨语言、跨模态(语音和文本)的场景。可以应对三元组( S S S, X X X, Y Y Y)中任意的单一或组合的数据类型。
举例来说,它可以利用纯语音或文本数据集( S S S| X X X| Y Y Y),也可以利用 ASR 数据集( S S S, Y Y Y),甚至文本翻译数据( X X X, Y Y Y)。在这种预训练模型的基础上进行 ST 的训练,能够有效解决训练数据匮乏的困境,提升最终的翻译效果。
参考语音识别的相关章节,略。
Transformer 内容参考语音识别的相关章节,略。
!pip install paddlespeech==1.2.0
import os
import paddle
import numpy as np
import kaldiio
import subprocess
from kaldiio import WriteHelper
from yacs.config import CfgNode
import IPython.display as dp
import warnings
warnings.filterwarnings("ignore")
from paddlespeech.s2t.frontend.featurizer.text_featurizer import TextFeaturizer
from paddlespeech.s2t.models.u2_st import U2STModel
!wget -nc https://paddlespeech.bj.bcebos.com/s2t/ted_en_zh/st1/fat_st_ted-en-zh.tar.gz
!tar xzvf fat_st_ted-en-zh.tar.gz
config_path = "conf/transformer_mtl_noam.yaml"
# 读取 conf 文件并结构化
st_config = CfgNode(new_allowed=True)
st_config.merge_from_file(config_path)
st_config
!wget -nc https://paddlespeech.bj.bcebos.com/s2t/ted_en_zh/st1/kaldi_bins.tar.gz
!tar xzvf kaldi_bins.tar.gz
kaldi_bins_path = os.path.abspath('kaldi_bins')
print(kaldi_bins_path)
if 'LD_LIBRARY_PATH' not in os.environ:
os.environ['LD_LIBRARY_PATH'] = f'{kaldi_bins_path}'
else:
os.environ['LD_LIBRARY_PATH'] += f':{kaldi_bins_path}'
os.environ['PATH'] += f':{kaldi_bins_path}'
def get_kaldi_feat(wav_path, config=st_config):
"""
Input preprocess and return paddle.Tensor stored in self.input.
Input content can be a file(wav).
"""
wav_file = os.path.abspath(wav_path)
cmvn = config.collator.cmvn_path
utt_name = '_tmp'
# Get the object for feature extraction
fbank_extract_command = [
'compute-fbank-feats', '--num-mel-bins=80', '--verbose=2',
'--sample-frequency=16000', 'scp:-', 'ark:-'
]
fbank_extract_process = subprocess.Popen(fbank_extract_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
fbank_extract_process.stdin.write(
f'{utt_name} {wav_file}'.encode('utf8'))
fbank_extract_process.stdin.close()
fbank_feat = dict(kaldiio.load_ark(
fbank_extract_process.stdout))[utt_name]
extract_command = ['compute-kaldi-pitch-feats', 'scp:-', 'ark:-']
pitch_extract_process = subprocess.Popen(extract_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
pitch_extract_process.stdin.write(
f'{utt_name} {wav_file}'.encode('utf8'))
process_command = ['process-kaldi-pitch-feats', 'ark:', 'ark:-']
pitch_process = subprocess.Popen(process_command,
stdin=pitch_extract_process.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
pitch_extract_process.stdin.close()
pitch_feat = dict(kaldiio.load_ark(
pitch_process.stdout))[utt_name]
concated_feat = np.concatenate((fbank_feat, pitch_feat), axis=1)
raw_feat = f"{utt_name}.raw"
with WriteHelper(f'ark,scp:{raw_feat}.ark,{raw_feat}.scp') as writer:
writer(utt_name, concated_feat)
cmvn_command = [
"apply-cmvn", "--norm-vars=true", cmvn, f'scp:{raw_feat}.scp',
'ark:-'
]
cmvn_process = subprocess.Popen(cmvn_command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process_command = ['copy-feats', '--compress=true', 'ark:-', 'ark:-']
process = subprocess.Popen(process_command,
stdin=cmvn_process.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
norm_feat = dict(kaldiio.load_ark(process.stdout))[utt_name]
audio = paddle.to_tensor(norm_feat).unsqueeze(0)
audio_len = paddle.to_tensor(audio.shape[1], dtype='int64')
return audio, audio_len
text_feature = TextFeaturizer(
unit_type=st_config.collator.unit_type,
vocab=st_config.collator.vocab_filepath,
spm_model_prefix=st_config.collator.spm_model_prefix)
model_conf = st_config.model
model_conf.input_dim = st_config.collator.feat_dim
model_conf.output_dim = text_feature.vocab_size
print(model_conf)
model = U2STModel.from_config(model_conf)
params_path = "exp/transformer_mtl_noam/checkpoints/fat_st_ted-en-zh.pdparams"
model_dict = paddle.load(params_path)
model.set_state_dict(model_dict)
model.eval()
# 下载wav
# !wget -nc https://paddlespeech.bj.bcebos.com/PaddleAudio/74109_0147917-0156334.wav
# !wget -nc https://paddlespeech.bj.bcebos.com/PaddleAudio/120221_0278694-0283831.wav
# !wget -nc https://paddlespeech.bj.bcebos.com/PaddleAudio/15427_0822000-0833000.wav
wav_file = '74109_0147917-0156334.wav'
# wav_file = '120221_0278694-0283831.wav'
# wav_file = '15427_0822000-0833000.wav'
transcript = "my hair is short like a boy 's and i wear boy 's clothes but i 'm a girl and you know how sometimes you like to wear a pink dress and sometimes you like to wear your comfy jammies"
dp.Audio(wav_file)
audio, audio_len = get_kaldi_feat(wav_file)
cfg = st_config.decoding
res = model.decode(audio,
audio_len,
text_feature=text_feature,
decoding_method=cfg.decoding_method,
beam_size=cfg.beam_size,
word_reward=cfg.word_reward,
decoding_chunk_size=cfg.decoding_chunk_size,
num_decoding_left_chunks=cfg.num_decoding_left_chunks,
simulate_streaming=cfg.simulate_streaming)
print("对应英文: {}".format(transcript))
print("翻译结果: {}".format("".join(res[0].split())))
1.Zheng, Renjie, Junkun Chen, Mingbo Ma, and Liang Huang. “Fused acoustic and text encoding for multimodal bilingual pretraining and speech translation.” ICML 2021.
2.Devlin, Jacob, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. “Bert: Pre-training of deep bidirectional transformers for language understanding.” NAACL 2019.
3.Conneau, Alexis, and Guillaume Lample. “Cross-lingual language model pretraining.” NIPS 2019.
4.Liu, Yuchen, Hao Xiong, Zhongjun He, Jiajun Zhang, Hua Wu, Haifeng Wang, and Chengqing Zong. “End-to-end speech translation with knowledge distillation.” Interspeech 2019.
请关注我们的 Github Repo,非常欢迎加入以下微信群参与讨论:
P.S. 欢迎关注我们的 github repo PaddleSpeech, 是基于飞桨 PaddlePaddle 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。