算法:N-gram語法


一、N-gram介紹

  n元語法(英語:N-gram)指文本中連續出現的n個語詞。n元語法模型是基於(n - 1)階馬爾可夫鏈的一種概率語言模型,通過n個語詞出現的概率來推斷語句的結構。這一模型被廣泛應用於概率論通信理論計算語言學(如基於統計的自然語言處理NLP)、計算生物學(如序列分析)、數據壓縮等領域。

  N-gram文本廣泛用於文本挖掘自然語言處理任務。它們基本上是給定窗口內的一組同時出現的單詞,在計算n元語法時,通常會將一個單詞向前移動(盡管在更高級的場景中可以使X個單詞向前移動)。

  例如,對於句子"The cow jumps over the moon" N = 2(稱為二元組),則 ngram 為:

  • the cow
  • cow jumps
  • jumps over
  • over the
  • the moon

  因此,在這種情況下,有 5 個 n-gram。

 

  再來看看 N = 3,ngram 將為:

  • the cow jumps
  • cow jumps over
  • jumps over the
  • over the moon

  因此,在這種情況下,有 4 個 n-gram。

 

  所以,在一個句子中 N-grams 的數量有:

      Ngrams(K) = X - (N - 1)

  其中,X 為給定句子K中的單詞數,N 為 N-gram 的N,指的是連續出現的 N 個單詞。

 

  N-gram用於各種不同的任務。例如,在開發語言模型時,N-grams不僅用於開發unigram模型,而且還用於開發bigram和trigram模型。谷歌和微軟已經開發了網絡規模的 n-gram模型,可用於多種任務,例如拼寫校正分詞文本摘要N-gram的另一個用途是為受監督的機器學習模型(例如SVMMaxEnt模型朴素貝葉斯等)開發功能。其想法是在特征空間使用標記(例如雙字母組),而不是僅使用字母組合。

  下面簡單介紹一下如何用 Java 生成 n-gram。

二、用 Java 生成 n-gram

  這個是生成 n-gram 的主要方法,方法首先是對傳進來的句子 sentence 進行單詞拆分,這個正則表達式“\\s+”是能匹配任何空白字符,包括空格、制表符、換頁符等等, 等價於 [ \f\n\r\t\v]。拆分完后對單詞進行拼接算法時間復雜度為 O(X - (N - 1)),X 為給定句子K中的單詞數,N 為 N-gram 的 N。

 1     /**
 2      * 生成n元語法
 3      * <p>
 4      * 一個句子中有多少個N-gram?
 5      * 如果 X = 給定句子K中的單詞數,則句子K的 N-gram數為:
 6      * N(grams<K>) = X - (N - 1)
 7      *
 8      * @param n        連續 n個單詞
 9      * @param sentence 句子級別的文本
10      * @return         存着ngram的列表
11      */
12     public static List<String> ngrams(int n, String sentence) {
13         List<String> ngrams = new ArrayList<>();
14         String[] words = sentence.split("\\s+");
15         for (int i = 0; i < words.length - n + 1; i++)
16             ngrams.add(concat(words, i, i + n));
17         return ngrams;
18     }

  進行單詞拼接,這里使用 StringBuilder線程不安全,效率相對StringBuffer高點)對拆分好的單詞進行拼接並返回拼接好的字符串。

 1     /**
 2      * 拼接單詞
 3      *
 4      * @param words 單詞
 5      * @param start 開始位置
 6      * @param end   結束位置
 7      * @return      拼接好的字符串
 8      */
 9     public static String concat(String[] words, int start, int end) {
10         StringBuilder sb = new StringBuilder();
11         for (int i = start; i < end; i++)
12             sb.append(i > start ? " " : "").append(words[i]);
13         return sb.toString();
14     }

  對 n-gram 的出現次數進行統計,使用 HashMap<String, Integer> 來存儲 n-gram 的出現次數並且按照 value 的逆序排序 Map,次數較多的在前面先打印。這里使用 Java 8 Stream API 按照 value 降序順序進行 Map 排序。

  在 Java 8 中,Map.Entry具有靜態方法 comparingByValue() 來幫助按 value 排序,此方法返回以自然順序 Comparator 比較 Map.Entry值的。還有,你可以傳遞自定義Comparator 以用於排序。

  下面是根據 value 進行排序的方法:

 1     /**
 2      * 按 value對 HashMap進行逆序排序
 3      * <p>
 4      * 使用 Java 8 Stream API按照降序對Value進行Map排序
 5      * 邏輯的中心是按自然順序 Map.Entry.comparingByValue()比較 Map.Entry值的方法。
 6      *
 7      * @param unSortedMap 未排序的HashMap
 8      * @return 按照value降序排序的HashMap
 9      */
10     public static HashMap<String, Integer> sortByValue(HashMap<String, Integer> unSortedMap) {
11         // System.out.println("Unsorted Map : " + unSortedMap);
12 
13         // LinkedHashMap保留插入元素的順序
14         LinkedHashMap<String, Integer> reverseSortedMap = new LinkedHashMap<>();
15 
16         // 使用 Comparator.reverseOrder() 進行反向排序
17         unSortedMap.entrySet()
18                 .stream()
19                 .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
20                 .forEachOrdered(x -> reverseSortedMap.put(x.getKey(), x.getValue()));
21 
22         // System.out.println("Reverse Sorted Map   : " + reverseSortedMap);
23 
24         return reverseSortedMap;
25     }

  主函數測試代碼:

 1 public static void main(String[] args) {
 2         HashMap<String, Integer> count = new HashMap<>();
 3         String text = "I can go to the supermarket to buy spicy bars or go to the store to buy spicy bars.";
 4 
 5         // 生成n為1~3的N元語法
 6 //        for (int n = 1; n <= 3; n++) {
 7 //            for (String ngram : ngrams(n, text)) {
 8 //                System.out.println(ngram);
 9 //            }
10 //            System.out.println();
11 //        }
12 
13         for (String ngram : ngrams(3, text)) {
14             // counting ngram by using HashMap
15             if (!count.containsKey(ngram)) {
16                 count.put(ngram, 1);
17             } else if (count.containsKey(ngram)) {
18                 count.replace(ngram, count.get(ngram) + 1);
19             }
20             System.out.println(ngram);
21         }
22 
23         // 按出現次由多到少的順序打印ngram
24         System.out.println("\nCounting Result: ");
25         for (Map.Entry<String, Integer> entry : sortByValue(count).entrySet()) {
26             System.out.println(entry.getKey() + ": " + entry.getValue());
27         }
28 
29     }


免責聲明!

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



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