[关闭]
@evilking 2018-04-30T16:36:15.000000Z 字数 12840 阅读 2785

NLP

结巴分词

本篇我们以 java 版本的结巴分词工具为例,详细分析该工具的分词细节;最后同样演示下 R 版本的jiebaR分词工具的用法和词云的绘制.

中文分词问题

什么是中文分词

在文本挖掘,情感分析,上下文语义分析等应用场合需要对纯文本进行分析,主要是对语句进行分析;像英文这种语句本身词与词之间有空格隔开,这种语句就容易分割,但是像中文或日文这种词与词之间没有分割界限的语言,在分割单词上就存在很大的困难;

像中文单词分割的时候如果分割的不准确,就很容易得出歧义的语句,甚至是意思完全相反的,比如”不同意”分割成”不”/”同意”。

中文分词常用的方法

基于字符匹配的分词

这类分词方法主要是词典匹配,用于切分出已登录词,如果只使用词典的话,就对未登录词无能为力了;后来人们就在此基础上加入统计方法,比如 HMM 模型,用来识别出未登录词;最后用动态规划求分词序列的最大概率,即最有可能的分词组合。

本篇要讲的结巴分词就属于这类。

基于字符标注的统计分析分词

例如,”小明硕士毕业于中国科学院计算所”,最正确的分词应该分成”小明 / 硕士 / 毕业 / 于 / 中国 / 科学院 / 计算所”,这是人工分词的结果,但是对于计算机来说,可能会把”计算所”分成”计算 / 所”。

为了使用字符标注的方式来分词,我们定义了四种标签:

于是上面的例句最正确的标注应该为 BEBEBESBEBMEBME,有了这样的字符标注序列,我们就能够将语句分开,分割的结果为”BE / BE / BE / S / BE / BME / BME”,所以我们的问题就变成了 已知观测语句、字符标注标签,要求用字符标注标签对观测语句标注后的序列。如果了解隐马尔科夫过程的话,就能知道这是一个典型的HMM模型解码的问题。

基于字符标注的统计分析分词方法,像 HMM 模型、CRF 模型 等都是属于这类。

HMM 模型在《HMM 模型》这篇中详细讲了;CRF 模型分词其本质上是将分词问题转换为标注问题,其训练和标注都在 《CRF 条件随机场》一篇中详细讲了。读者可以参考学习。

基于机器学习的分词

机器学习做分词,原理与统计分析方法类似,都是将分词问题转换成分类问题,比如把“小明是中国共产党的接班人”这句的每个汉字进行分类,分为“B”、“M”、“E”、“S”这四类,然后根据分类的结果序列进行切分。

JAVA版本结巴分词详解

工具下载

github下载地址: https://github.com/huaban/jieba-analysis

下载好后导入 eclipse,则maven工程目录结构如下图所示:

结巴分词目录结构

编写测试用例类 SegmenterTest,对句子进行分词,测试代码如下:

  1. @Test
  2. public void test() {
  3. //初始化jieba分词工具类
  4. JiebaSegmenter jieba = new JiebaSegmenter();
  5. String content = "这里讲解的是java版本的结巴分词工具";
  6. /**
  7. * 对句子进行分词,
  8. * 源码中有个方法是对整篇文章进行分词,其原理是先对文章进行分句,然后对每一句进行分词,
  9. * 所以这里我们只考虑对一句话进行分词的过程
  10. */
  11. List<String> words = jieba.sentenceProcess(content);
  12. for(String word: words){
  13. System.out.print(word + " ");
  14. }
  15. }

运行后输出结果为:

  1. main dict load finished, time elapsed 1557 ms
  2. model load finished, time elapsed 47 ms.
  3. 这里 讲解 java 版本 结巴 分词 工具

可以看出分词效果还是不错的,下面我们以这句话的分词来分析结巴分词的分词过程.


结巴分词时序图

结巴分词时序图

从时序图中可知,在初始化 JiebaSegment类时,会去先初始化词典表,即WordDictionary类;然后开始分句,分句又分为三步,如下:


初始化词典表

在JiebaSegmenter类的初始化过程中,会初始化两个词典表,WordDictionary和FinalSeg:

  1. private static WordDictionary wordDict = WordDictionary.getInstance();
  2. private static FinalSeg finalSeg = FinalSeg.getInstance();

首先看看WordDictionary的初始化,这里使用了双重检查锁定的单例模式(保证在多线程环境下也只会初始化一次),在构造方法中执行 loadDict()方法去加载词典:

  1. _dict = new DictSegment((char) 0);
  2. InputStream is = this.getClass().getResourceAsStream(MAIN_DICT);
  3. ......
  4. BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
  5. long s = System.currentTimeMillis();
  6. while (br.ready()) {
  7. //读取词典文件中的一行
  8. String line = br.readLine();
  9. String[] tokens = line.split("[\t ]+");
  10. //跳过空行
  11. if (tokens.length < 2)
  12. continue;
  13. String word = tokens[0];
  14. //计算词数
  15. double freq = Double.valueOf(tokens[1]);
  16. total += freq;
  17. word = addWord(word);
  18. freqs.put(word, freq);
  19. }
  20. // normalize
  21. for (Entry<String, Double> entry : freqs.entrySet()) {
  22. //将单词的词数转换为词频
  23. entry.setValue((Math.log(entry.getValue() / total)));
  24. minFreq = Math.min(entry.getValue(), minFreq);
  25. }
  26. ......

其中 addWord(String word)方法如下:

  1. private String addWord(String word) {
  2. if (null != word && !"".equals(word.trim())) {
  3. //将单词转行小写
  4. String key = word.trim().toLowerCase(Locale.getDefault());
  5. //将单词添加到词典
  6. _dict.fillSegment(key.toCharArray());
  7. return key;
  8. }
  9. else
  10. return null;
  11. }

上面loadDict()方法是加载工具基础词典,如果是用户自定义词典,需要使用loadUserDict()方法,与上面的加载方法不同的是,如果用户没有设置单词的统计词数,则默认设置为 3 :

  1. String line = br.readLine();
  2. String[] tokens = line.split("[\t ]+");
  3. if (tokens.length < 1) {
  4. // Ignore empty line
  5. continue;
  6. }
  7. String word = tokens[0];
  8. //默认词数为 3
  9. double freq = 3.0d;
  10. if (tokens.length == 2)
  11. freq = Double.valueOf(tokens[1]);
  12. word = addWord(word);
  13. freqs.put(word, Math.log(freq / total));

从代码可以看出,加载词典,无非就是读取词典表,用分隔符截取每一行,获得对应的单词,以及单词的统计词数,如果没有设置统计词数,默认为 3;同时在单词添加到_dict词典数据结构中时,都统一转换成了小写.


然后看 FinalSeg 类的初始化,该类初始化也是使用了双重检查锁定来处理多线程环境下的单例,初始化时会调用loadModel()方法来加载 HMM模型,即HMM模型的五个组成元素 :

  1. private static char[] states = new char[] { 'B', 'M', 'E', 'S' };

由于是中文分词,所以隐状态定义为 B,M,E,S

  1. prevStatus = new HashMap<Character, char[]>();
  2. prevStatus.put('B', new char[] { 'E', 'S' });
  3. prevStatus.put('M', new char[] { 'M', 'B' });
  4. prevStatus.put('S', new char[] { 'S', 'E' });
  5. prevStatus.put('E', new char[] { 'B', 'M' });
  6. trans = new HashMap<Character, Map<Character, Double>>();
  7. Map<Character, Double> transB = new HashMap<Character, Double>();
  8. transB.put('E', -0.510825623765990);
  9. transB.put('M', -0.916290731874155);
  10. trans.put('B', transB);
  11. Map<Character, Double> transE = new HashMap<Character, Double>();
  12. transE.put('B', -0.5897149736854513);
  13. transE.put('S', -0.8085250474669937);
  14. trans.put('E', transE);
  15. Map<Character, Double> transM = new HashMap<Character, Double>();
  16. transM.put('E', -0.33344856811948514);
  17. transM.put('M', -1.2603623820268226);
  18. trans.put('M', transM);
  19. Map<Character, Double> transS = new HashMap<Character, Double>();
  20. transS.put('B', -0.7211965654669841);
  21. transS.put('S', -0.6658631448798212);
  22. trans.put('S', transS);

然后定义状态转移概率矩阵,这里定义的概率都是经过对数转换的,以方便计算时将乘法操作改为加法操作,下同;

  1. start = new HashMap<Character, Double>();
  2. start.put('B', -0.26268660809250016);
  3. start.put('E', -3.14e+100);
  4. start.put('M', -3.14e+100);
  5. start.put('S', -1.4652633398537678);

这里是定义了初始状态概率;

下面给出结巴分词工具的发射矩阵资源文件格式:

  1. M
  2. 耀 -8.47651676173
  3. -14.3722960587
  4. -10.5600933886
  5. B
  6. 耀 -10.4602834131
  7. -11.0155137948
  8. -8.76640550684
  9. M
  10. 耀 -8.47651676173
  11. -14.3722960587
  12. -10.5600933886
  13. E
  14. 耀 -9.2667057128
  15. -17.3344819086
  16. -9.09647365936

举个例子,隐状态为'M'时对应的汉字为'耀'的概率的对数为 -8.47651676173,即为发射概率,其中表中所有的汉字被定义为观察值

  1. InputStream is = this.getClass().getResourceAsStream(PROB_EMIT);
  2. BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
  3. emit = new HashMap<Character, Map<Character, Double>>();
  4. Map<Character, Double> values = null;
  5. while (br.ready()) {
  6. String line = br.readLine();
  7. String[] tokens = line.split("\t");
  8. if (tokens.length == 1) {
  9. //读取为标号,B M S E
  10. values = new HashMap<Character, Double>();
  11. //设置发射概率矩阵
  12. emit.put(tokens[0].charAt(0), values);
  13. }else {
  14. //添加值
  15. values.put(tokens[0].charAt(0), Double.valueOf(tokens[1]));
  16. }
  17. }

如此,发射概率矩阵就加载完成了,到这里整个 HMM模型的几个要素就都加载完成了,剩下的就是具体分词时的解码问题了.


构建DAG图

分词的第一步是构建词的DAG图,例如上面测试用例中的句子,DAG图如下所示:

  1. 0 [0, 1] //这
  2. 1 [1] //里
  3. 2 [2, 3] //讲
  4. 3 [3] //解
  5. 4 [4] //的
  6. 5 [5] //是
  7. 6 [6] //j
  8. 7 [7] //a
  9. 8 [8] //v
  10. 9 [9] //a
  11. 10 [10, 11] //版
  12. 11 [11] //本
  13. 12 [12] //的
  14. 13 [13, 14] //结
  15. 14 [14] //巴
  16. 15 [15, 16] //分
  17. 16 [16] //词
  18. 17 [17, 18] //工
  19. 18 [18] //具

具体是如何算的呢,我们分析createDAG()方法:

  1. private Map<Integer, List<Integer>> createDAG(String sentence) {
  2. Map<Integer, List<Integer>> dag = new HashMap<Integer, List<Integer>>();
  3. //获得词典树
  4. DictSegment trie = wordDict.getTrie();
  5. char[] chars = sentence.toCharArray();
  6. int N = chars.length;
  7. int i = 0, j = 0;
  8. while (i < N) {
  9. //核心的就是下面这几句,不断的增加字扩展词,去词典中查找
  10. //如果是一个词的前缀,则可以继续往后扩展词
  11. //如果匹配到了一个词,则将索引添加到DAG图中
  12. Hit hit = trie.match(chars, i, j - i + 1);
  13. if (hit.isPrefix() || hit.isMatch()) {
  14. if (hit.isMatch()) {
  15. if (!dag.containsKey(i)) {
  16. List<Integer> value = new ArrayList<Integer>();
  17. dag.put(i, value);
  18. value.add(j);
  19. }
  20. else
  21. dag.get(i).add(j);
  22. }
  23. j += 1;
  24. if (j >= N) {
  25. i += 1;
  26. j = i;
  27. }
  28. }
  29. else {
  30. i += 1;
  31. j = i;
  32. }
  33. }
  34. //最后将没有与其他字构成词的字加上
  35. for (i = 0; i < N; ++i) {
  36. if (!dag.containsKey(i)) {
  37. List<Integer> value = new ArrayList<Integer>();
  38. value.add(i);
  39. dag.put(i, value);
  40. }
  41. }
  42. return dag;
  43. }

举例说明,假设当前词为'版',去词典里查,发现'版'字是某个单词的前缀词;则往后添加下个字,构成词'版本',然后去词典里查,发现能匹配上'版本',于是就将'本'字的索引添加到'版'所对应的索引列表中;然后继续往后添加一个字,构成'版本的',这时查询词典,发现该词既不是前缀词,也无法匹配到单词,则最开始的索引往后移一位,当前词就变成了'本',然后从'本'开始继续往下搜索,直到结束.


动态规划求解最可能的分词序列

构建完DAG图后,我们要分词,就要判断具体如何划分才最好,这里我们使用了动态规划,使整句划分的概率最大.

  1. private Map<Integer, Pair<Integer>> calc(String sentence, Map<Integer, List<Integer>> dag) {
  2. int N = sentence.length();
  3. HashMap<Integer, Pair<Integer>> route = new HashMap<Integer, Pair<Integer>>();
  4. //动态规划从后往前,初始化最后一步的概率
  5. route.put(N, new Pair<Integer>(0, 0.0));
  6. for (int i = N - 1; i > -1; i--) {
  7. Pair<Integer> candidate = null;
  8. for (Integer x : dag.get(i)) {
  9. //从后往前组合,获得词的频率
  10. double freq = wordDict.getFreq(sentence.substring(i, x + 1)) + route.get(x + 1).freq;
  11. if (null == candidate) {
  12. candidate = new Pair<Integer>(x, freq);
  13. }else if (candidate.freq < freq) {
  14. //取累计频率最大的划分
  15. candidate.freq = freq;
  16. candidate.key = x;
  17. }
  18. }
  19. route.put(i, candidate);
  20. }
  21. return route;
  22. }

动态规划是从最后一个字符开始往前逐个累加,取局部累计词频最大的分词结果,最终取使整句分词结果的累计词频达到最大.


新词发现

经过上一步动态规划求最可能的分词结果,有了一个粗略的分词,需要在此基础上进一步的去准确分词,包括新词发现 :

  1. while (x < N) {
  2. y = route.get(x).key + 1;
  3. String lWord = sentence.substring(x, y);
  4. if (y - x == 1)
  5. sb.append(lWord);
  6. else {
  7. if (sb.length() > 0) {
  8. buf = sb.toString();
  9. sb = new StringBuilder();
  10. //一个字的可以直接添加
  11. if (buf.length() == 1) {
  12. tokens.add(buf);
  13. }else {
  14. //多个字的先去词典中查询
  15. //如果是已登录词就直接添加
  16. if (wordDict.containsWord(buf)) {
  17. tokens.add(buf);
  18. } else {
  19. //未登录词就用Viterbi算法解码
  20. finalSeg.cut(buf, tokens);
  21. }
  22. }
  23. }
  24. tokens.add(lWord);
  25. }
  26. x = y;
  27. }

下面就是HMM模型中 Viterbi算法解码过程 :

  1. public void viterbi(String sentence, List<String> tokens) {
  2. Vector<Map<Character, Double>> v = new Vector<Map<Character, Double>>();
  3. Map<Character, Node> path = new HashMap<Character, Node>();
  4. //处理第一个隐状态
  5. v.add(new HashMap<Character, Double>());
  6. for (char state : states) {
  7. Double emP = emit.get(state).get(sentence.charAt(0));
  8. if (null == emP)
  9. emP = MIN_FLOAT;
  10. v.get(0).put(state, start.get(state) + emP);
  11. path.put(state, new Node(state, null));
  12. }
  13. for (int i = 1; i < sentence.length(); ++i) {
  14. Map<Character, Double> vv = new HashMap<Character, Double>();
  15. v.add(vv);
  16. Map<Character, Node> newPath = new HashMap<Character, Node>();
  17. for (char y : states) {
  18. Double emp = emit.get(y).get(sentence.charAt(i));
  19. if (emp == null)
  20. emp = MIN_FLOAT;
  21. Pair<Character> candidate = null;
  22. for (char y0 : prevStatus.get(y)) {
  23. //从状态y0 转移到状态 y
  24. Double tranp = trans.get(y0).get(y);
  25. if (null == tranp)
  26. tranp = MIN_FLOAT;
  27. //y0状态的前向变量 + y0转移到y状态的转移概率 + y状态下是字i的发射概率
  28. tranp += (emp + v.get(i - 1).get(y0));
  29. if (null == candidate)
  30. candidate = new Pair<Character>(y0, tranp);
  31. else if (candidate.freq <= tranp) {
  32. candidate.freq = tranp;
  33. candidate.key = y0;
  34. }
  35. }
  36. vv.put(y, candidate.freq);
  37. newPath.put(y, new Node(y, path.get(candidate.key)));
  38. }
  39. path = newPath;
  40. }
  41. //因为'B'、'M'不可能构成最后一个字,所以只需要比较'E'、'S'就行
  42. double probE = v.get(sentence.length() - 1).get('E');
  43. double probS = v.get(sentence.length() - 1).get('S');
  44. Vector<Character> posList = new Vector<Character>(sentence.length());
  45. Node win;
  46. if (probE < probS)
  47. win = path.get('S');
  48. else
  49. win = path.get('E');
  50. while (win != null) {
  51. posList.add(win.value);
  52. win = win.parent;
  53. }
  54. Collections.reverse(posList);
  55. //路径回溯,得到最优的隐状态序列
  56. int begin = 0, next = 0;
  57. for (int i = 0; i < sentence.length(); ++i) {
  58. char pos = posList.get(i);
  59. if (pos == 'B')
  60. begin = i;
  61. else if (pos == 'E') {
  62. tokens.add(sentence.substring(begin, i + 1));
  63. next = i + 1;
  64. }else if (pos == 'S') {
  65. tokens.add(sentence.substring(i, i + 1));
  66. next = i + 1;
  67. }
  68. }
  69. if (next < sentence.length())
  70. tokens.add(sentence.substring(next));
  71. }

以上就是 HMM模型中 Viterbi解码过程,具体原理可参考上篇 《基于HMM的中文分词》.


到这里,java版本的结巴分词工具对每一句的分词过程就清楚了;这个工具结合词典和概率,并使用HMM模型做新词发现,总体分词结果来看,效果还可以.

R 版本的结巴分词

由于笔者没有找到 R版本的结巴分词工具包"jiebaR"的源码,所以上面使用java版本的源码来分析处理流程,下面我们使用"jiebaR"包来做简单的分词,以及用"wordcloud"包做词云分析,以演示 R 版本的结巴分词工具的使用.

作为演示用的文本如下所示,保存在data.txt文件中,并放在Rstudio的工作目录下,工作目录可通过getwd()命令获得:

  1. 笔者在实际项目中使用的是 java 版本的结巴分词,它是词典+HMM模型的分词工具,源码中对 HMMViterbi算法演示的很清楚,不过这里为了与整体风格相统一,我们使用 R语言的相关HMM包来演示;
  2. 后面我们单独用一个篇幅去讲解结巴分词,因为结巴分词工具总体上来说算是一个效果不错,而且原理简单的工具.

下面我们先导入必要的两个包,并读取文本:

  1. install.packages("jiebaR")
  2. library(jiebaR)
  3. install.packages("wordcloud")
  4. library(wordcloud)
  5. f <- scan("data.txt",sep = "\n",what = '',encoding = "UTF-8")
  6. head(f)

输出信息为:

  1. > f <- scan("data.txt",sep = "\n",what = '',encoding = "UTF-8")
  2. Read 2 items
  3. > head(f)
  4. [1] "笔者在实际项目中使用的是 java 版本的结巴分词,它是词典+HMM模型的分词工具,源码中对 HMM的Viterbi算法演示的很清楚,不过这里为了与整体风格相统一,我们使用 R语言的相关HMM包来演示;\n"
  5. [2] "后面我们单独用一个篇幅去讲解结巴分词,因为结巴分词工具总体上来说算是一个效果不错,而且原理简单的工具."
  6. >

下面开始使用 jiebaR 去分词:

  1. #使用 qseg函数分词
  2. > seg <- qseg[f]
  3. #去掉单词为一个字的词
  4. > seg <- seg[nchar(seg) > 1]
  5. > head(seg)
  6. [1] "笔者" "实际" "项目" "使用" "java" "版本"
  7. >
  1. #统计词频
  2. > seg <- table(seg)
  3. > seg
  4. seg
  5. HMM java Viterbi 版本 包来 笔者 不错 不过
  6. 3 1 1 1 1 1 1 1
  7. 词典 单独 而且 分词 风格 工具 后面 简单
  8. 1 1 1 4 1 3 1 1
  9. 讲解 结巴 来说 模型 篇幅 清楚 实际 使用
  10. 1 3 1 1 1 1 1 2
  11. 算法 算是 统一 为了 我们 相关 项目 效果
  12. 1 1 1 1 2 1 1 1
  13. 演示 一个 因为 语言 原理 源码 这里 整体
  14. 2 2 1 1 1 1 1 1
  15. 总体
  16. 1
  17. >
  18. # 对词频进行排序,并取前 30个词
  19. > seg <- sort(seg,decreasing = TRUE)[1:30]
  20. > seg
  21. seg
  22. 分词 HMM 工具 结巴 使用 我们 演示 一个
  23. 4 3 3 3 2 2 2 2
  24. java Viterbi 版本 包来 笔者 不错 不过 词典
  25. 1 1 1 1 1 1 1 1
  26. 单独 而且 风格 后面 简单 讲解 来说 模型
  27. 1 1 1 1 1 1 1 1
  28. 篇幅 清楚 实际 算法 算是 统一
  29. 1 1 1 1 1 1
  30. >

文本经过分词,过滤掉一些无效词(停用词等)后,再进行词频统计并排序,取前 k个词(一般就是保留的关键词了);下一步就是利用词云工具绘制词云了,以可视化的方式展现文本特征 :

  1. #生成一张 .bmp图,宽高个 500像素
  2. > bmp("comment_cloud.bmp",width = 500,height = 500)
  3. #绘图,背景设为黑色
  4. > par(bg = "black")
  5. # 生成词云
  6. > wordcloud(names(seg),seg,colors = rainbow(100),random.order = F)
  7. # 关闭绘图设备
  8. > dev.off()
  9. null device
  10. 1
  11. >

这样,在工作目录下就会生成一张.bmp图,如下所示:

词云

从图上可以看出,主要词有分词、HMM、结巴、工具,其中分词占的比重最大,其次是 HMM。这个结果与最开始的文本语料对比可以看出,效果还是不错的,主要的关键词都体现出来了.


接下来再玩点深入一点的,jiebaR包可以实现分词(分行分词)、整个文档分词(分文档分词)。library(jiebaR)加载包时,没有启动任何分词引擎,启动引擎很简单,就是一句赋值语句就可以了mixseg=worker()

详细版本请参考官方:https://jiebar.qinwf.com/section-3.html#section-3.0.1
但是实际上worker中有非常多的调节参数。

  1. mixseg = worker(type = "mix",dict = "dict/jieba.dict.utf8",
  2. hmm = "dict/hmm_model.utf8",
  3. user = "dict/user.dict.utf8",
  4. detect = T,symbol = F,
  5. lines = 1e+05,output = NULL)
  6. > mixseg
  7. Worker Type: Jieba Segment
  8. Default Method : mix
  9. Detect Encoding : TRUE
  10. Default Encoding: UTF-8
  11. Keep Symbols : FALSE
  12. Output Path :
  13. Write File : TRUE
  14. By Lines : FALSE
  15. Max Word Length : 20
  16. Max Read Lines : 1e+05
  17. Fixed Model Components:
  18. $dict
  19. [1] "dict/jieba.dict.utf8"
  20. $user
  21. [1] "dict/user.dict.utf8"
  22. $hmm
  23. [1] "dict/hmm_model.utf8"
  24. $stop_word
  25. NULL
  26. $user_weight
  27. [1] "max"
  28. $timestamp
  29. [1] 1506665756
  30. $default $detect $encoding $symbol $output $write $lines $bylines can be reset.
  31. >

说明一下:

  1. #设置分行输出
  2. > mixseg$bylines = TRUE
  3. > mixseg[c("这是第一行文本。","这是第二行文本.")]
  4. [[1]]
  5. [1] "这是" "第一行" "文本"
  6. [[2]]
  7. [1] "这是" "第二行" "文本"
  8. >
  1. > words = "我爱北京天安门"
  2. # 创建词性标注的 worker
  3. > tagger = worker("tag")
  4. # 对句子words进行分词,并做词性标注
  5. > tagger <= words
  6. r v ns ns
  7. "我" "爱" "北京" "天安门"
  8. >
  1. # 构建一个提取关键词的 worker
  2. > keys = worker("keywords",topn = 4,idf = IDFPATH)
  3. > keys <= "我爱北京天安门"
  4. 8.9954 4.6674
  5. "天安门" "北京"
  6. >

这里创建了一个能提取关键词的worker,topn = 4表示提取 4 个关键词,idf = IDFPATH表示使用逆文档率;

下一句输出结果中,第一行的数值表示 IDF,第二行表示具体关键词.

  1. #显示词典路径
  2. > show_dictpath()
  3. [1] "D:/evirnment/R-3.3.3/library/jiebaRD/dict"
  4. >

如果读者想设置自己的词典、停用词典,可以通过show_dictpath()显示词典路径,然后修改文件内容,并在构建 worker是指定相应文件路径.


小结

以上,就是对结巴分词工具的使用以及分析了,其中关于 HMM模型的原理部分,可以参考《基于HMM的中文分词》一篇.

如果因为这篇文章而让读者对结巴分词有了更深入的理解,那是笔者的荣幸.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注