• 算法: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 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("
    Counting Result: ");
    25         for (Map.Entry<String, Integer> entry : sortByValue(count).entrySet()) {
    26             System.out.println(entry.getKey() + ": " + entry.getValue());
    27         }
    28 
    29     }
  • 相关阅读:
    [underscore源码学习]——`>>` 运算符和二分查找
    【css】——三种方法实现多列等高
    【css】——根据div个数显示不同的样式
    拐点
    react.js 点击事件传递参数的方法
    react-router-dom 手动控制路由跳转
    书单
    PHP字符串操作汇总
    PHP数组操作汇总
    Yii防注入攻击笔记
  • 原文地址:https://www.cnblogs.com/magic-sea/p/12181108.html
Copyright © 2020-2023  润新知