V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
探索世界的好奇心万岁
jifengg
V2EX  ›  分享发现

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

  •  
  •   jifengg · 254 天前 · 2100 次点击
    这是一个创建于 254 天前的主题,其中的信息可能已经有所发展或是发生改变。

    关于 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 ),这里总结一下:

    • 打开https://github.com/SpeechColab/Leaderboard,在 readme 里的“3. Model Zoo”里,展开“Local Engine”,有该项目目前公开的语音引擎(也就是预训练模型),里面有“wenet_wenetspeech”
    • 将该项目 clone 到本地( windows 下使用不便,因为要使用阿里云的 oss cli ,可以选择 wsl )
    • 根据 readme ,在项目根目录下运行“ops/pull model wenet_wenetspeech”。
      • 如果是第一次运行,会提示先运行“utils/install_aliyun_oss_client.sh”安装 oss cli ,运行完在 utils 下会多一个名为“oss”的文件。
      • 继续运行 pull ,会提示需要 oss.cfg ,根据提示发送邮件即可,对方会自动回复 oss.cfg 的内容,内容类似
    [Credentials]
    language=EN
    endpoint=oss-cn-hangzhou.aliyuncs.com
    accessKeyID=xxxxxxxxxxxx
    accessKeySecret=xxxxxxxxxxxxxxxxxxxxxx
    
    * 将该文本保存到“credentials/aliyun_oss.cfg”里,初始化配置就完成了。
    
    • 运行 pull 命令之后,会开始下载模型文件,我下载的是 2021-11-02 的模型,总共 513MB 。
    • 下载完了,就可以在“models/wenet_wenetspeech/assets”下看到熟悉(观察 decoder_main 的命令)的“final.zip”和“words.txt”了。

    使用 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 [email protected] ,耗时大概在原语音时长的½到⅓之间,还可以接受。CPU 占用 25%左右,内存占用比较高,比如上面无穷小亮的那个音频,占用了 4G 内存。

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

    总结

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

    第 1 条附言  ·  254 天前
    尝试直接在 windows10 里编译,没能成功。

    另外,如果你尝试了,觉得效果不错的话也可以分享一下你的成果。
    9 条回复    2022-01-19 14:18:24 +08:00
    sillydaddy
        1
    sillydaddy  
       254 天前
    感谢详尽的分享!
    sidkang
        2
    sidkang  
       254 天前
    感谢
    leipengcheng
        3
    leipengcheng  
       252 天前
    看起来不错,之前一直白嫖阿里的,等以后有空试试这个
    canwushuang
        4
    canwushuang  
       215 天前
    11 月 2 日这个是不是很敏感,容易乱出字,是否有解决办法? ps. 我还是用官方 docker 跑。
    jifengg
        5
    jifengg  
    OP
       213 天前
    @canwushuang 乱出字倒是没遇到过,你用的是 websocket_server_main 吗?实时的准确率较低。
    canwushuang
        6
    canwushuang  
       211 天前 via Android
    @jifengg 开始是用 ws 接口测试,发现较新的模型会较敏感,可能需要加入环境音降噪会比较好。后面就直接 docker_main 去跑音频切片,单从发音上成字已经较好了,但模型整体基于字的发音结构,成词准确率还不知道需要怎么提高。现在大概思路是想把分片结果再塞入一个上下文的类隐马尔可夫链模型去纠正词语。不知道您是怎么解决的?
    canwushuang
        7
    canwushuang  
       211 天前
    要么字回拼音,然后用相似度对应行业专业名词去匹配。。
    jifengg
        8
    jifengg  
    OP
       210 天前
    @canwushuang "行业专业名词"这块,貌似没太好的解决办法,这个模型的数据源都是一些影视剧综艺等等的,比较“常规”的音源,如果要专业名词可能得考虑自己重新训练。我自己测试的时候也是拿一些 bilibili 的视频测试的。至于怎么对识别结果进行再次自动加工,我更是还没开始研究,惭愧。
    canwushuang
        9
    canwushuang  
       210 天前 via Android
    @jifengg 恩 语音 decode 部分还是不错的,要适配各种场景可能需要调整源码的 attention.py 这块。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4595 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 60ms · UTC 02:43 · PVG 10:43 · LAX 19:43 · JFK 22:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.