体验了开源的离线语音识别模型 wenetspeech,准确度很高。分享一些使用心得。

2021-12-06 15:43:55 +08:00
 jifengg

关于 wenetspeech 的公众号文章

Leaderboard 新增 WenetSpeech 数据集预训练 WeNet 模型

https://mp.weixin.qq.com/s/8L6vZpH1-uiDR3J5fzw-9Q

TIOBE 测试集开源计划(关于如何下载 wenetspeech 预训练模型文件)

https://mp.weixin.qq.com/s?__biz=MzA4NTAwNjkzMA==&mid=2247484328&idx=1&sn=ffd2650be8e7d73e7bf2856f0db584a0

认识 wenetspeech

官方网站:https://wenet-e2e.github.io/WenetSpeech

使用超过一万小时的高质量语音数据进行训练,准确率(可信度)可达 95%以上。实际使用下来,使用 2021-11-02 发布的预训练模型,确实准确率很高。

注意:这个网站上的“Download”,是指的下载一万+小时的训练素材,并不是指下载训练好的模型。

如何开始使用

wenetspeech 是一个预训练的模型,也就是说它并不是一个可执行软件,要使用它,可以使用 wenet (https://github.com/wenet-e2e/wenet)。

Wenet 的 GitHub 介绍是“Production First and Production Ready End-to-End Speech Recognition Toolkit (翻译:生产第一和生产就绪的端到端语音识别工具包)”,可以用它来进行模型训练,同时,项目下的 runtime (https://github.com/wenet-e2e/wenet/tree/main/runtime)也提供了 android 端和 x86 服务端的运行时,可以结合模型进行语音识别。

尝试 x86 运行时

x86 提供了 Docker 的方式安装,简单方便。详情:https://github.com/wenet-e2e/wenet/blob/main/runtime/server/x86/README_CN.md

识别 wav 文件的命令如下

/home/wenet/runtime/server/x86/build/decoder_main --chunk_size -1 --model_path /path/to/final.zip --dict_path /path/to/words.txt --wav_path /path/to/you/file.wav

不过,当前( 2021-12-1 )该 docker 里用的是 aishell2/20210602 的模型,尝试了几次识别,准确性较低。 而且,官方也没有提供如何下载 wenetspeech 预训练模型的方式。下面就是摸索如何获得这个 95%准确度的模型。

下载 wenetspeech 预训练模型

上面的文章《 TIOBE 测试集开源计划》里,提到了如何下载预训练模型( model ),这里总结一下:

[Credentials]
language=EN
endpoint=oss-cn-hangzhou.aliyuncs.com
accessKeyID=xxxxxxxxxxxx
accessKeySecret=xxxxxxxxxxxxxxxxxxxxxx
* 将该文本保存到“credentials/aliyun_oss.cfg”里,初始化配置就完成了。

使用 wenetspeech 预训练模型

这里官方在 docker hub 上发布的“mobvoiwenet/wenet:0.5.0”镜像,无法直接使用 wenetspeech 的模型文件,估计是代码比较旧、模型比较新的原因,具体没有深究,因为我这里放弃了使用官方 docker ,而是自己重新编译 docker image 。

创建 wenetspeech docker image

在上面下载完模型后,其实也同样下载了 models/wenet_wenetspeech/docker/Dockerfile 文件,可以直接通过这个 Dockerfile 来创建自己的 image ,具体方式查看 docker 文档即可。

提示:我在国内,这 Dockerfile 里面的 git clone 经常失败,所以,我是直接使用 ubuntu 镜像,在里面一条条执行 Dockerfile 里的命令来编译的(其实命令不多,四五条命令就完了,就是最后一步 cmake 的耗时比较长)。在 container 里编译完后,记得 docker commit 保存为镜像。

体验 wenetspeech 识别

命令和文档里的没有区别,只要让 docker 能访问到模型文件和 wav 文件即可(我用-v 做磁盘映射)

以下是我在 windows 下运行的命令样例:

# 我把模型文件和 wav 文件都存放在 D:\ffmpeg 目录下
# 我自己创建的 image ,命名为 wenetspeech:timestamp
docker run --rm -it -v d:/ffmpeg/:/data/ffmpeg wenetspeech:timestamp /home/wenet/runtime/server/x86/build/decoder_main --chunk_size -1 --model_path /data/ffmpeg/wenet/wenet_wenetspeech/assets/final.zip --dict_path /data/ffmpeg/wenet/wenet_wenetspeech/assets/words.txt --wav_path /data/ffmpeg/wenet/test_wav/xxx16k10s.wav

问题及解决方案

websocket 的准确度较低

官方还有 websocket 的样例及 server 端 websocket_server_main ,经过体验,同样的 wav 文件,使用 websocket_server_main,准确度大大低于使用 decoder_main ,所以,除非你有很高的实时性要求必须使用 websocket ,不然建议使用decoder_main

长语音文件识别

decoder_main 无法一次识别时长太长的文件(经测试 180 秒没问题,官方说最大 300 秒但是我测试的 250 秒也会报错),这个问题可以使用 ffmpeg 将长语音切成多个 180 秒的短语音文件,分别识别即可。

时间戳

decoder_main 的识别结果不带时间戳。这个问题参考这个 issue (https://github.com/wenet-e2e/wenet/issues/604)。需要修改源码(改动不大,我这个不会 c 语言的都能改)。

程序本身识别的时候,对每个字都会有一个开始-结束时间标记,只不过 decoder_main.cc 里面没有把这些信息输出。同样参考上面 issue 里提到的一个文件 https://github.com/wenet-e2e/wenet/blob/604231391c81efdf06454dbc99406bbc06cb030d/runtime/core/bin/label_checker_main.cc#L212-L218

修改的地方:

    std::string final_result;
    std::string timestamp_str;//增加一个变量存储时间戳的内容

原来的位置:

      if (state == wenet::DecodeState::kEndpoint) {
        decoder.Rescoring();
        final_result.append(decoder.result()[0].sentence);
        decoder.ResetContinuousDecoding();
      }

增加代码后:

      if (state == wenet::DecodeState::kEndpoint) {
        decoder.Rescoring();
        final_result.append(decoder.result()[0].sentence);
        const wenet::DecodeResult &result = decoder.result()[0];
        std::stringstream ss;
        for (const auto &w : result.word_pieces) {
          ss << " " << w.word << " " << w.start << " " << w.end;
        }
        timestamp_str.append(ss.str());
        decoder.ResetContinuousDecoding();
      }

原来 110 行左右的位置:

    if (decoder.DecodedSomething()) {
      final_result.append(decoder.result()[0].sentence);
    }

增加代码后:

    if (decoder.DecodedSomething()) {
      final_result.append(decoder.result()[0].sentence);
      const wenet::DecodeResult &result = decoder.result()[0];
      std::stringstream ss;
      for (const auto &w : result.word_pieces) {
        ss << " " << w.word << " " << w.start << " " << w.end;
      }
      timestamp_str.append(ss.str());
    }

原先输出的位置:

      buffer << wav.first << " " << final_result << std::endl;

增加一行输出:

      buffer << wav.first << " " << final_result << std::endl;
//我这里按照我的习惯输出,你也可以修改为任意其他形式。不输出 wav.first ,在一次识别多个文件(-wav_scp )的时候,可能不好识别属于哪个文件,我不用这个所以就去掉了
      buffer << "TIMESTAMP " << timestamp_str << std::endl;

到这里,源码的修改的结束了,重新编译也很简单:

cd ${x86dir}/build
cmake --build .

重新识别就能看到打印出来的带时间戳的内容了。(以下是无穷小亮某一期https://www.bilibili.com/video/BV1Vh411i7DB的语音识别结果)

test 这都是蜘蛛见过这么好看的吗很多人都害怕蜘蛛但是上回有一个小姑娘跟我说说我特别喜欢蜘蛛那姑娘才七八岁我说我特别高兴因为你能够发现蜘蛛的美为了让大家了解蜘蛛扭转对蜘蛛的物解博物杂志做了这一期蜘蛛专体这一期里边有简单的内容比如告诉你身边常见的几种蜘蛛都叫什么你像这个白鹅高脚猪白垩巨蟹猪还有这个屋里边经常出现的一些小蜘蛛还有花园里边你看这个就是路边花园里边经常出现的还有这个应该都很熟悉吧还有很萌的一类蜘蛛叫跳珠他们那个眼睛特别大特别可爱可以扭转很多人对蜘蛛的恐惧而且他还会转眼珠跳珠也是非常适合饲养的这个告诉你应该怎么养一只跳珠另外呢还有进阶版就是你如果成为一个蜘蛛的研究者你会怎么研究蜘蛛还有包括蜘蛛网是怎么织的各种文章反正以蜘蛛为专题并且做的这么专业这么详细的我还没听说国内哪个杂志这么做过另外这一期还有一些寻找梦幻觉的科考经理还有丘砂鸭的知识欧洲恐龙的故事各国的火绳枪手这天文里也有我们插画是孟凡蒙画的一些火枪手的手绘点我视频下方链接你会得到这期杂志和一张火枪手的大海报这俩搁一块十八块钱包邮大家玩的命买
TIMESTAMP  这 0 600 都 600 720 是 720 880 蜘 880 1040 蛛 1040 1440 见 1440 1800 过 1800 1920 这 1920 2040 么 2040 2120 好 2120 2240 看 2240 2360 的 2360 2480 吗 2480 2720 很 2720 3000 多 3000 3120 人 3120 3200 都 3200 3360 害 3360 3520 怕 3520 3680 蜘 3680 3840 蛛 3840 4080 但 4080 4320 是 4320 4440 上 4440 4600 回 4600 4800 有 4800 5000 一 5000 5120 个 5120 5240 小 5240 5400 姑 5400 5560 娘 5560 5680 ...

时间戳的小问题

通过以上的样例可以看到,首先输出的不是“词”,而是“字”。其次,静音的部分在输出结果中不会被跳过,举个例子,两句话之间假设间隔 4 秒,则上一句话最后一个字的 end time ,会被延长 2 秒,下一句话第一个字的 start time 也是往前延长 2 秒。看起来这两个字都持续了 2 秒多,如果对时间戳比较严肃的场景就不太适用。不过我目前看不懂源码,不清楚是 runtime 的代码没优化,还是识别的部分没优化,我个人更倾向于是 runtime 没优化。

识别效率

测试机器 i5 10400@2.9GHz ,耗时大概在原语音时长的½到⅓之间,还可以接受。CPU 占用 25%左右,内存占用比较高,比如上面无穷小亮的那个音频,占用了 4G 内存。

整体还是可以接受的,如果是部署到服务器端的话可以通过多进程和分布式,提高整体的识别效率。

总结

这个模型的准确度目前真的是出乎我的意料。据网站介绍,2.0 版本也在准备中( preparing ),可以期待 2.0 的表现。目前这个模型,应付一下语音转写,然后做人工校对,应该很有帮助。

4779 次点击
所在节点    分享发现
9 条回复
sillydaddy
2021-12-06 16:32:46 +08:00
感谢详尽的分享!
sidkang
2021-12-06 20:17:21 +08:00
感谢
leipengcheng
2021-12-08 15:31:50 +08:00
看起来不错,之前一直白嫖阿里的,等以后有空试试这个
canwushuang
2022-01-14 21:36:23 +08:00
11 月 2 日这个是不是很敏感,容易乱出字,是否有解决办法? ps. 我还是用官方 docker 跑。
jifengg
2022-01-17 09:55:37 +08:00
@canwushuang 乱出字倒是没遇到过,你用的是 websocket_server_main 吗?实时的准确率较低。
canwushuang
2022-01-19 10:17:13 +08:00
@jifengg 开始是用 ws 接口测试,发现较新的模型会较敏感,可能需要加入环境音降噪会比较好。后面就直接 docker_main 去跑音频切片,单从发音上成字已经较好了,但模型整体基于字的发音结构,成词准确率还不知道需要怎么提高。现在大概思路是想把分片结果再塞入一个上下文的类隐马尔可夫链模型去纠正词语。不知道您是怎么解决的?
canwushuang
2022-01-19 10:36:34 +08:00
要么字回拼音,然后用相似度对应行业专业名词去匹配。。
jifengg
2022-01-19 14:11:13 +08:00
@canwushuang "行业专业名词"这块,貌似没太好的解决办法,这个模型的数据源都是一些影视剧综艺等等的,比较“常规”的音源,如果要专业名词可能得考虑自己重新训练。我自己测试的时候也是拿一些 bilibili 的视频测试的。至于怎么对识别结果进行再次自动加工,我更是还没开始研究,惭愧。
canwushuang
2022-01-19 14:18:24 +08:00
@jifengg 恩 语音 decode 部分还是不错的,要适配各种场景可能需要调整源码的 attention.py 这块。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/820379

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX