中文分詞

上一篇手機(jī)錄入語(yǔ)音翻譯為文字里面講到了如何從手機(jī)端錄入語(yǔ)音,發(fā)往后端。其實(shí)這只是完成了工作的一半,既然我有了語(yǔ)音輸入,你是不是應(yīng)該給我返回語(yǔ)音消息,這樣咱們才能愉快的玩耍呀。要不然,你就是個(gè)寂寞boy,對(duì)說(shuō)的就是你。

好的,假設(shè)后端已經(jīng)獲取到了語(yǔ)音,翻譯成了中文文字。我們就要對(duì)這個(gè)中文文字進(jìn)行處理。在處理之前,有個(gè)問(wèn)題,就是中文是有多音字的,其次是語(yǔ)音翻譯接口未必那么準(zhǔn)。如果翻譯錯(cuò)了,那么后續(xù)的處理,勢(shì)必很困難。因此呢,加入你用的是百度語(yǔ)音翻譯接口,你可以先上傳詞庫(kù),也就是你的所有可能的話的單詞、句子上傳上去,這樣百度翻譯的時(shí)候就能準(zhǔn)確一點(diǎn)。

即使這樣做了,依舊會(huì)存在翻譯錯(cuò)誤和不準(zhǔn)確的情況。這就需要用到相似度,進(jìn)行匹配了。也就是那這個(gè)翻譯的句子,跟這個(gè)所有可能的句子列表進(jìn)行相似度匹配,找到一個(gè)最相似的,并且這個(gè)相似度超過(guò)某個(gè)閾值,我們就說(shuō),我們就認(rèn)為這句話本來(lái)是這個(gè)意思,就可以按照后續(xù)邏輯進(jìn)行處理了。否則,我們認(rèn)為無(wú)法識(shí)別這句話的意圖,進(jìn)行報(bào)錯(cuò)提示。比如“寶,你說(shuō)的太難了,我正在學(xué)習(xí),請(qǐng)給我一點(diǎn)時(shí)間好嗎,我一定...”。

下面具體解釋一下幾種不同的相似度匹配思路:
部分詞的相似度匹配:

比如對(duì)于報(bào)表中心來(lái)說(shuō),用戶輸入“查看XX項(xiàng)目利潤(rùn)報(bào)表”
那么就要去匹配到底是哪張報(bào)表,假設(shè)關(guān)鍵字“查看”翻譯無(wú)誤,已經(jīng)順利提取出來(lái)了,
同時(shí)“XX項(xiàng)目”也通過(guò)某種方式識(shí)別和提取出來(lái)了,
那就只剩下“利潤(rùn)報(bào)表”這個(gè)關(guān)鍵詞了,如果根據(jù)精確匹配無(wú)法找到,
那么就需要根據(jù)這個(gè)詞進(jìn)行相似性匹配了。
這里只是用到了部分的關(guān)鍵詞“XX項(xiàng)目”進(jìn)行相似度匹配,
因此叫做部分詞的相似度匹配。

整句話的相似度匹配:

有時(shí)候上面的依舊是無(wú)法識(shí)別到的,比如“查看”這個(gè)開頭的關(guān)鍵字,
由于用戶發(fā)音不準(zhǔn)或者翻譯有誤,將其翻譯為“查到” “看到”,
但是后面確又翻譯的準(zhǔn)確無(wú)誤。其實(shí)很容易就能看出來(lái)用戶是想要干什么,
但是代碼只能無(wú)情的將其判定為“未知的語(yǔ)音指令”,這很讓人苦惱和心痛。
因此這時(shí)候就需要將整句話進(jìn)行拼音相似度匹配,以期從中可以做到最大范圍的匹配。
但是數(shù)據(jù)庫(kù)中并沒有整句話的詞庫(kù),有的只是報(bào)表的名稱?那該怎么辦呢?
因此可以在項(xiàng)目啟動(dòng)的時(shí)候,一次性查出所有報(bào)表名稱,
然后用代碼窮舉各種前綴性的詞,后綴性的詞,進(jìn)行排列組合。
然后拿著翻譯的話跟所有餓排列組合句子去進(jìn)行相似度匹配。

假如你選定了某種相似度匹配的思路,那么問(wèn)題來(lái)了,怎樣相似度匹配呢?一種做法是利用已經(jīng)有的工具庫(kù),進(jìn)行分詞處理,或者某種算法,計(jì)算余弦夾角或者什么距離,這一塊我不太懂,需要去研究不同的工具庫(kù)以及訓(xùn)練詞庫(kù)。

另一種做法就是將文本轉(zhuǎn)為拼音,然后比較拼音的相似度。因?yàn)橐话愕臉I(yè)務(wù)性話題和選詞范圍大致固定,因此我選用拼音相似度來(lái)做匹配。

嗯,首先將中文文本轉(zhuǎn)為拼音吧,先引入pom:

<dependency>
    <groupId>com.belerweb</groupId>
    <artifactId>pinyin4j</artifactId>
    <version>2.5.1</version>
</dependency>

工具類PinYinUtil:

public static String chineseTextToPinYin(String chineseText,String separator,Boolean unConvertIsKeep) {
    try {
        if(unConvertIsKeep == null) {
            unConvertIsKeep = true;//遇到不能識(shí)別的  默認(rèn)保留
        }
        if(StringUtil.isEmpty(separator)){
            separator = "";
        }
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        //拼音小寫
        format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        //不帶聲調(diào)
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        //要轉(zhuǎn)換的中文,格式,轉(zhuǎn)換之后的拼音的分隔符,遇到不能轉(zhuǎn)換的是否保留   wo,shi,zhong,guo,ren,,hello
        return PinyinHelper.toHanYuPinyinString(chineseText, format, separator, unConvertIsKeep);
    }catch (Exception e){
        throw new RuntimeException(e);
    }
}

相似度工具類TextSimilarityUtil:

/**
 * 計(jì)算兩個(gè)中文文本的相似度   相似度越高 值越大  最大為1
 * @param text1
 * @param text2
 * @return
 */
public static double getSemblanceByPinyin(String text1, String text2){
    String pinyinTexts1 = PinYinUtil.chineseTextToPinYin(text1,",",null);
    String pinyinTexts2 = PinYinUtil.chineseTextToPinYin(text2,",",null);

    int d[][]; // 矩陣
    int n = pinyinTexts1.length();
    int m = pinyinTexts2.length();
    int i; // 遍歷str的
    int j; // 遍歷target的
    char ch1; // str的
    char ch2; // target的
    int temp; // 記錄相同字符,在某個(gè)矩陣位置值的增量,不是0就是1
    if (n == 0 || m == 0) {
        return 0;
    }
    d = new int[n + 1][m + 1];
    for (i = 0; i <= n; i++) { // 初始化第一列
        d[i][0] = i;
    }

    for (j = 0; j <= m; j++) { // 初始化第一行
        d[0][j] = j;
    }

    for (i = 1; i <= n; i++) { // 遍歷str
        ch1 = pinyinTexts1.charAt(i - 1);
        // 去匹配target
        for (j = 1; j <= m; j++) {
            ch2 = pinyinTexts2.charAt(j - 1);
            if (ch1 == ch2 || ch1 == ch2 + 32 || ch1 + 32 == ch2) {
                temp = 0;
            } else {
                temp = 1;
            }
            // 左邊+1,上邊+1, 左上角+temp取最小
            d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + temp);
        }
    }

    return 1 - (double) d[n][m] / Math.max(pinyinTexts1.length(), pinyinTexts2.length());
}
/**
 * 根據(jù)文本的拼音相似度   從list中選擇一個(gè)跟text最相似的返回
 * @param text
 * @param list
 * @return
 */
public static String getSemblanceByPinyin(String text, List<String> list){
    if(CollectionUtils.isEmpty(list)){
        return null;
    }
    if(StringUtil.isEmpty(text)){
        return null;
    }
    String bestMatch = null;
    double maxSimilarity = 0d;
    for(int i = 0 ; i < list.size(); i++){
        String compareText = list.get(i);
        double similarity = getSemblanceByPinyin(text,compareText);
        if(similarity > maxSimilarity){
            maxSimilarity = similarity;
            bestMatch = compareText;
        }
        if(maxSimilarity == 1d){//如果完全相似  直接返回 不再繼續(xù)比較了
            return bestMatch;
        }
    }
    return bestMatch;

}

這樣我們就可以處理后續(xù)的邏輯了,比如根據(jù)關(guān)鍵詞或者其余的什么來(lái)分隔和提取,做操作。這一步我們先略去,假如我們已經(jīng)處理好了,這個(gè)時(shí)候需要返回給前端語(yǔ)音,讓微信端去播放。

首先我們需要將中文轉(zhuǎn)為語(yǔ)音,我們用百度語(yǔ)音接口來(lái)做:

/**
 * 文字轉(zhuǎn)為語(yǔ)音   wav格式
 * @param text
 * @param accessToken
 * @param cuid
 * @return
 */
public static byte[] text2Audio(String text,String accessToken,String cuid){
    ParamCheckUtil.stringEmpty(text,"文本不能為空");
    ParamCheckUtil.isTrue(text.length() > 500,"文本過(guò)長(zhǎng)");
    ParamCheckUtil.stringEmpty(accessToken,"accessToken不能為空");
    ParamCheckUtil.stringEmpty(cuid,"cuid不能為空");

    // 發(fā)音人選擇, 基礎(chǔ)音庫(kù):0為度小美,1為度小宇,3為度逍遙,4為度丫丫,
    // 精品音庫(kù):5為度小嬌,103為度米朵,106為度博文,110為度小童,111為度小萌,默認(rèn)為度小美
    int per = 0;
    // 語(yǔ)速,取值0-15,默認(rèn)為5中語(yǔ)速
    int spd = 5;
    // 音調(diào),取值0-15,默認(rèn)為5中語(yǔ)調(diào)
    int pit = 5;
    // 音量,取值0-9,默認(rèn)為5中音量
    int vol = 5;

    // 下載的文件格式, 3:mp3(default) 4:pcm-16k 5:pcm-8k 6. wav
    int aue = 6;
    try {
        // 此處2次urlencode, 確保特殊字符被正確編碼
        String params = "tex=" + URLEncoder.encode(URLEncoder.encode(text, "utf-8"), "utf-8");
        params += "&per=" + per;
        params += "&spd=" + spd;
        params += "&pit=" + pit;
        params += "&vol=" + vol;
        params += "&cuid=" + cuid;
        params += "&tok=" + accessToken;
        params += "&aue=" + aue;
        params += "&lan=zh&ctp=1";
        HttpURLConnection conn = (HttpURLConnection) new URL(TEXT_2_AUDIO_URL).openConnection();
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setConnectTimeout(5000);
        PrintWriter printWriter = new PrintWriter(conn.getOutputStream());
        printWriter.write(params);
        printWriter.close();
        String contentType = conn.getContentType();
        if (contentType.contains("audio/")) {
            byte[] bytes = getResponseBytes(conn);
            ParamCheckUtil.isTrue(bytes == null || bytes.length == 0,"語(yǔ)音為空");
            return bytes;
        } else {
            System.err.println("ERROR: content-type= " + contentType);
            String res = getResponseString(conn);
            log.error(res);
            throw new RuntimeException(res);
        }
    }catch (Exception e){
        throw new RuntimeException(e);
    }
}

百度接口返回的是語(yǔ)音字節(jié),我們先把字節(jié)保存到文件,后續(xù)nginx配置映射,返回給微信端地址就好了:

// 字節(jié)數(shù)組寫出到文件 需要字節(jié)數(shù)組的數(shù)據(jù)源,以及文件的路徑
public static void byteArrayToFile(byte[] src, String filePath,String fileName) {
    File dir = new File(filePath);
    dir.mkdirs();

    File dest = new File(filePath,fileName);//輸出圖片的目的地,這里是文件寫出的路徑
    ParamCheckUtil.isTrue(dest.exists(),"文件已存在");
    ByteArrayInputStream  is = null;   //字節(jié)數(shù)組的流,先讓它寫到程序   src是數(shù)據(jù)源
    OutputStream os = null;
    try {
        dest.createNewFile();
        is = new ByteArrayInputStream(src);
        os = new FileOutputStream(dest);
        byte[] flush=new byte[5];
        int len = -1;
        while((len = is.read(flush))!= -1){//這里是寫入程序
            os.write(flush,0,len);//這一步是將程序?qū)懭氲轿募?   這里一定要記住文件流一定要釋放
        }
        os.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            if(is!=null) {
                is.close();
            }
            if(os!=null) {
                os.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

nginx映射靜態(tài)文件的配置我就略過(guò)了,現(xiàn)在將url地址返回到微信小程序端,js的邏輯處理如下:

// 
const wxUtil = require('../../utils/wxutil.js');
const recorderManager = wx.getRecorderManager();
const innerAudioContext = wx.createInnerAudioContext({"useWebAudioImplement": true});

recorderManager.onStop((res) => {
  var tempFilePath = res.tempFilePath;//音頻文件地址
  const fs = wx.getFileSystemManager();
  fs.readFile({//讀取文件并轉(zhuǎn)為ArrayBuffer
    filePath: tempFilePath,
    success(res) {
      wx.showLoading({
        title: '正在語(yǔ)音識(shí)別中...',
      });
      const base64Data = wx.arrayBufferToBase64(res.data);
      var fileSize = res.data.byteLength ;
      var paramJson = {
        format: 'pcm',
        sampleRate: 16000,
        encodeBitRate: 48000,
        data: base64Data
      };

      wxUtil.post('v1/wx/audioupload', paramJson, wxUtil.audio, { isShowLoading: true }, (result) =>{
        console.log( result);
        innerAudioContext.src = "https://xxx" + result.data;
        innerAudioContext.onPlay(() => {       
          console.log('onPlay')     
        });     
        innerAudioContext.onError((res) => {  
          console.log(res);      
          console.log(res.errMsg);       
          console.log(res.errCode)     
        });     
        innerAudioContext.onCanplay(()=>{       
          console.log('canplay');     
        }); 
        innerAudioContext.play();
      });
    }
  })
});

Page({
  data: {
    
  },
  onLoad() {
    
  },
  //語(yǔ)音識(shí)別
  handleTouchStart: function(e){
    //錄音參數(shù)
    const options = {
      sampleRate: 16000,
      numberOfChannels: 1,
      encodeBitRate: 48000,
      format: 'pcm'
    }
    //開啟錄音
    recorderManager.start(options);
    wx.showLoading({
      title: '正在錄音中...',
    });
  },
  handleTouchEnd: function(e){
    recorderManager.stop();
  }
  }
})

這樣微信就可以播放語(yǔ)音了。

現(xiàn)在,你可以對(duì)著手機(jī)說(shuō)話,然后微信給你播放語(yǔ)音,比如“寶,我想你了”,“小可愛,我也想你了”。

好了,拜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容