Java日期時間API系列39-----中文語句中的時間語義識別(time NLP 輸入一句話,能識別出話里的時間)原理分析


  

  NLP (Natural Language Processing) 是人工智能(AI)的一個子領域。自然語言是人類智慧的結晶,自然語言處理是人工智能中最為困難的問題之一(來自百度百科)。

其中中文更是不好處理。下面將分析中文語句中的時間的識別:time NLP 輸入一句話,能識別出話里的時間。下面2種簡單的實現方法。

 

1.單詞的識別

這種比較簡單,比如,今天,明天,下周,下月,明年,昨天,上周,上月,去年等。 原理:匹配到明天就根據今天的時間天數加1。
  
/**
 * 常用時間枚舉
 *
 * @author xkzhangsan
 */
public enum CommonTimeEnum {

    TODAY("today", "今天"),

    TOMORROW("tomorrow", "明天"),
    NEXTWEEK("nextWeek", "下周"),
    NEXTMONTH("nextMonth", "下月"),
    NEXTYEAR("nextYear", "明年"),

    YESTERDAY("yesterday", "昨天"),
    LASTWEEK("lastWeek", "上周"),
    LASTMONTH("lastMonth", "上月"),
    LASTYEAR("lastYear", "去年"),
    ;

    private String code;

    private String name;

    public String getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    CommonTimeEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public static Map<String, String> convertToMap(){
        Map<String, String> commonTimeMap = new HashMap<String, String>();
        for (CommonTimeEnum commonTimeEnum : CommonTimeEnum.values()) {
            commonTimeMap.put(commonTimeEnum.getCode(), commonTimeEnum.getCode());
            commonTimeMap.put(commonTimeEnum.getName(), commonTimeEnum.getCode());
        }
        return commonTimeMap;
    }

    public static CommonTimeEnum getCommonTimeEnumByCode(String code){
        for (CommonTimeEnum commonTimeEnum : CommonTimeEnum.values()) {
            if(commonTimeEnum.getCode().equals(code)){
                return commonTimeEnum;
            }
        }
        return null;
    }
}





    /**
     * 解析自然語言時間,今天,明天,下周,下月,明年,昨天,上周,上月,去年等。
     * @param text 自然語言時間,待解析字符串
     * @param  naturalLanguageMap 自定義自然語言時間map,其中key自定義,value需為 com.xkzhangsan.time.enums.CommonTimeEnum中的code;
     *                            可以為空,默認使用com.xkzhangsan.time.enums.CommonTimeEnum解析。
     * @return Date
     */
    public static Date parseNaturalLanguageToDate(String text, Map<String, String> naturalLanguageMap){
        if(StringUtil.isEmpty(text)){
            return null;
        }
        text = text.trim();

        boolean isCommonTimeMap = false;
        if(CollectionUtil.isEmpty(naturalLanguageMap)){
            naturalLanguageMap = CommonTimeEnum.convertToMap();
            isCommonTimeMap = true;
        }
        if(! naturalLanguageMap.containsKey(text) || StringUtil.isEmpty(naturalLanguageMap.get(text))){
            return null;
        }

        String targetMethod = null;
        if(isCommonTimeMap){
            targetMethod = naturalLanguageMap.get(text);
        }else{
            String code = naturalLanguageMap.get(text);
            Map<String, String> commonTimeMap = CommonTimeEnum.convertToMap();
            if(commonTimeMap.containsKey(code)){
                targetMethod = commonTimeMap.get(code);
            }
        }
        if(targetMethod == null){
            return null;
        }

        //執行結果
        CommonTimeEnum targetCommonTime = CommonTimeEnum.getCommonTimeEnumByCode(targetMethod);
        if(targetCommonTime == null){
            return null;
        }
        
        switch (targetCommonTime){
            case TODAY :
                return DateTimeCalculatorUtil.today();
            case TOMORROW:
                return DateTimeCalculatorUtil.tomorrow();
            case NEXTWEEK:
                return DateTimeCalculatorUtil.nextWeek();
            case NEXTMONTH:
                return DateTimeCalculatorUtil.nextMonth();
            case NEXTYEAR:
                return DateTimeCalculatorUtil.nextYear();
            case YESTERDAY:
                return DateTimeCalculatorUtil.yesterday();
            case LASTWEEK:
                return DateTimeCalculatorUtil.lastWeek();
            case LASTMONTH:
                return DateTimeCalculatorUtil.lastMonth();
            case LASTYEAR:
                return DateTimeCalculatorUtil.lastYear();
            default:
                return null;
        }
    }


//
DateTimeCalculatorUtil

/** * 明天 * @return Date */ public static Date tomorrow(){ return plusDays(today(), 1); } /** * 今天 * @return Date */ public static Date today(){ return new Date(); }

 

 

2.中文語句中的時間的識別

這個是真實語境下的時間識別,比如 Hi,all.下周一下午三點開會,如果今天是2021-06-10 那么 返回結果為:2021-06-14 15:00:00 。

 

2.1 原理和圖解

原理和第一種類似,也是識別時間詞語,根據基准時間推斷結果,但更強大一些。

基本分為三步:

(1)加載正則文件

(2)解析中文語句中的所有時間詞語

(3)根據基准時間,循環解析(2)中的時間詞語

代碼比較多這里就不貼了,詳細可以看github上的項目代碼,下面是簡單流程圖: 

                                                              

2.2 相關源碼及說明

2.2.1 Time-NLP

github: https://github.com/shinyke/Time-NLP

author:shinyke

由復旦NLP中的時間分析功能修改而來,做了很多細節和功能的優化。

  1. 泛指時間的支持,如:早上、晚上、中午、傍晚等。
  2. 時間未來傾向。 如:在周五輸入“周一早上開會”,則識別到下周一早上的時間;在下午17點輸入:“9點送牛奶給隔壁的漢子”則識別到第二天上午9點。
  3. 多個時間的識別,及多個時間之間上下文關系處理。如:"下月1號下午3點至5點到圖書館還書",識別到開始時間為下月1號下午三點。同時,結束時間也繼承上文時間,識別到下月1號下午5點。
  4. 可自定義基准時間:指定基准時間為“2016-05-20-09-00-00-00”,則一切分析以此時間為基准。
  5. 修復了各種各樣的BUG。

簡而言之,這是一個輸入一句話,能識別出話里的時間的工具。

 

2.2.2 xk-time TimeNLPUtil

https://github.com/xkzhangsan/xk-time TimeNLPUtil

在Time-NLP基礎上做了很多優化:

(1)封裝屬性,重命名使符合駝峰命名標准。
(2)將加載正則資源文件改為單例加載。
(3)將類按照功能重新划分為單獨的多個類。
(4)使用Java8日期API重寫。
(5)增加注釋說明,優化代碼。
(6)修復原項目中的issue:標准時間yyyy-MM-dd、yyyy-MM-dd HH:mm:ss和yyyy-MM-dd HH:mm解析問題。
(7)修復原項目中的issue:1小時后,1個半小時后,1小時50分鍾等解析問題;並且支持到秒,比如50秒后,10分鍾30秒后等。
(8)修復原項目中的issue:修復當前時間是上午10點,那么下午三點 會識別為明天下午三點問題。
(9)修復原項目中的issue:修復小數解析異常問題。
(10)性能優化,將使用到的正則預編譯后放到緩存中,下次直接使用,提高性能。

 

 3 實現方法的局限性

第一種只能識別單詞;

第二種也只能識別正則文件中的詞語,比第一種識別能力更強,但如果有新的或不常用的時間詞語無法處理,比如星期一的同義詞禮拜一等,如果要不斷支持新的詞語,需要不斷的修改,不如機器學習好;

對於常用的時間詞語識別,第二種已經達到很高的識別率。

 

 

4.開發這個功能的原因

第一種實現,因為有網友需要識別中文時間詞語,我寫了第一種的實現;

第二種實現,另一個網友有需要識別語句中的中文時間詞語,他向往推薦了Time-NLP這個項目,說這個項目很好,不維護了,有一些小問題,希望我能參考實現,我研究了原項目代碼,在我的項目中重寫,優化,並修復了一些問題。

感謝shinyke,這個項目很好,學習到很多正則解析的知識。

 

 源碼地址: https://github.com/xkzhangsan/xk-time


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM