共计 5299 个字符,预计需要花费 14 分钟才能阅读完成。
简介:通过 IK 分词器分词并生成词云。
本文次要介绍如何通过 IK 分词器进行词频统计。应用分词器对文章的词频进行统计,次要目标是实现如下图所示的词云性能,能够找到文章内的重点词汇。后续也能够对词进行词性标注,实体辨认以及对实体的情感剖析等性能。
词频统计服务具体模块如下:
- 数据输出:文本信息
- 数据输入:词 – 词频(TF-IDF 等) – 词性等内容
- 应用的组件:分词器、语料库、词云展现组件等
- 性能点:白名单,黑名单,同义词等
现存的中文分词器有 IK、HanLP、jieba 和 NLPIR 等几种,不同分词器各有特点,本文应用 IK 实现,因为 ES 个别应用 medcl 等大佬封装的 IK 分词器插件作为中文分词器。
因为 ES 的 IK 分词器插件深度联合了 ES,仅对文本分词应用不到 ES 的内容,所以文本采纳申艳超大佬版本的 IK。
1. IK 分词统计代码
IK 的代码绝对比较简单,货色不多,将 String 拆分为词并统计代码如下:
单纯统计词频:
/**
* 全文本词频统计
*
* @param content 文本内容
* @param useSmart 是否应用 smart
* @return 词,词频
* @throws IOException
*/
private static Map<String, Integer> countTermFrequency(String content, Boolean useSmart) throws IOException {
// 输入后果 Map
Map<String, Integer> frequencies = new HashMap<>();
if (StringUtils.isBlank(content)) {return frequencies;}
DefaultConfig conf = new DefaultConfig();
conf.setUseSmart(useSmart);
// 应用 IKSegmenter 初始化文本信息并加载词典
IKSegmenter ikSegmenter = new IKSegmenter(new StringReader(content), conf);
Lexeme lexeme;
while ((lexeme = ikSegmenter.next()) != null) {if (lexeme.getLexemeText().length() > 1) {// 过滤单字,也能够过滤其余内容,如数字和单纯符号等内容
final String term = lexeme.getLexemeText();
// Map 累加操作
frequencies.compute(term, (k, v) -> {if (v == null) {v = 1;} else {v += 1;}
return v;
});
}
}
return frequencies;
}
统计词频和文档频率:
/**
* 文本列表词频和词文档频率统计
*
* @param docs 文档列表
* @param useSmart 是否应用只能分词
* @return 词频列表 词 -[词频, 文档频率]
* @throws IOException
*/
private static Map<String, Integer[]> countTFDF(List<String> docs, boolean useSmart) throws IOException {
// 输入后果 Map
Map<String, Integer[]> frequencies = new HashMap<>();
for (String doc : docs) {if (StringUtils.isBlank(doc)) {continue;}
DefaultConfig conf = new DefaultConfig();
conf.setUseSmart(useSmart);
// 应用 IKSegmenter 初始化文本信息并加载词典
IKSegmenter ikSegmenter = new IKSegmenter(new StringReader(doc), conf);
Lexeme lexeme;
// 用于文档频率统计的 Set
Set<String> terms = new HashSet<>();
while ((lexeme = ikSegmenter.next()) != null) {if (lexeme.getLexemeText().length() > 1) {final String text = lexeme.getLexemeText();
// 进行词频统计
frequencies.compute(text, (k, v) -> {if (v == null) {v = new Integer[]{1, 0};
} else {v[0] += 1;
}
return v;
});
terms.add(text);
}
}
// 进行文档频率统计:无需初始化 Map,统计词频后 Map 外面必有该词记录
for (String term : terms) {frequencies.get(term)[1] += 1;
}
}
return frequencies;
}
2. 获取词云 TopN 个词
获取 TopN 个词用于词云展现有多种排序形式,能够间接依据词频、文档频率或者 TF-IDF 等算法进行排序,本文仅依据词频求取 TopN。
M 个数字获取 TopN 有以下算法:
- M 小 N 小:疾速抉择算法
- M 大 N 小:小顶堆
- M 大 N 大:归并排序
本文采纳小顶堆形式实现,对应 JAVA 中的优先队列数据结构 PriorityQueue:
/**
* 按呈现次数,从高到低排序取 TopN
*
* @param data 词和排序数字对应的 Map
* @param TopN 词云展现的 TopN
* @return 前 N 个词和排序值
*/
private static List<Map.Entry<String, Integer>> order(Map<String, Integer> data, int topN) {PriorityQueue<Map.Entry<String, Integer>> priorityQueue = new PriorityQueue<>(data.size(), new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {return o2.getValue().compareTo(o1.getValue());
}
});
for (Map.Entry<String, Integer> entry : data.entrySet()) {priorityQueue.add(entry);
}
//TODO 以后 100 词频统一时 (概率极低) 的解决方法,if(list(0).value == list(99).value ){xxx}
List<Map.Entry<String, Integer>> list = new ArrayList<>();
// 统计后果队列 size 和 topN 值取较小值列表
int size = priorityQueue.size() <= topN ? priorityQueue.size() : topN;
for (int i = 0; i < size; i++) {list.add(priorityQueue.remove());
}
return list;
}
3. IK 代码浅析
外围主类为 IKSegmenter,须要关注的点有 dic 包也就是词典相干内容以及字符解决工具类 CharacterUtil 的 identifyCharType()办法,目录构造如下:
IKSegmenter 类构造如下图,其中 init() 为公有办法,初始化加载词典采纳非懒加载模式,在第一次初始化 IKSegmenter 实例时会调用并加载词典,代码位于构造图下方。
// IKSegmenter 类构造方法
public IKSegmenter(Reader input, Configuration cfg) {
this.input = input;
this.cfg = cfg;
this.init();}
// IKSegmenter 类初始化
private void init() {
// 初始化词典单例
Dictionary.initial(this.cfg);
// 初始化分词上下文
this.context = new AnalyzeContext(this.cfg);
// 加载子分词器
this.segmenters = this.loadSegmenters();
// 加载歧义裁决器
this.arbitrator = new IKArbitrator();}
// Dictionary 类初始化词典
public static Dictionary initial(Configuration cfg) {if (singleton == null) {synchronized (Dictionary.class) {if (singleton == null) {singleton = new Dictionary(cfg);
return singleton;
}
}
}
return singleton;
}
词典公有构造方法 Dictionary()内会加载 IK 自带的词典以及扩大词典,咱们也能够把本人线上不变的词典放到这里这样 IKAnalyzer.cfg.xml 中就只须要配置常常变更词典即可。
private Dictionary(Configuration cfg) {
this.cfg = cfg;
this.loadMainDict();// 主词典以及扩大词典
this.loadmiaozhenDict();// 自定义词典加载,仿照其余办法即可
this.loadStopWordDict();// 扩大停词词典
this.loadQuantifierDict();// 量词词典}
在 IKSegmenter 类调用 next()办法获取下一个词元时,会调用 CharacterUtil 类中的 identifyCharType()办法辨认字符品种,这里咱们也能够自定义一些字符品种针对解决新兴的网络语言,如 @、## 等内容:
static int identifyCharType(char input) {if (input >= '0' && input <= '9') {return CHAR_ARABIC;} else if ((input >= 'a' && input <= 'z') || (input >= 'A' && input <= 'Z')) {return CHAR_ENGLISH;} else {Character.UnicodeBlock ub = Character.UnicodeBlock.of(input);
//caster 减少 #为中文字符
if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A ||input=='#') {
// 目前已知的中文字符 UTF- 8 汇合
return CHAR_CHINESE;
} else if (ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS // 全角数字字符和日韩字符
// 韩文字符集
|| ub == Character.UnicodeBlock.HANGUL_SYLLABLES
|| ub == Character.UnicodeBlock.HANGUL_JAMO
|| ub == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO
// 日文字符集
|| ub == Character.UnicodeBlock.HIRAGANA // 平假名
|| ub == Character.UnicodeBlock.KATAKANA // 片假名
|| ub == Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS) {return CHAR_OTHER_CJK;}
}
// 其余的不做解决的字符
return CHAR_USELESS;
}
因为 IK 内容不多,倡议大家能够从头捋一遍,包含各个实现 ISegmenter 接口的各个自分词器等内容。
4. 进行词云展现
词云展现能够应用 Kibana 自带的词云 Dashboard,或者比拟热门的 WordCloud。本人测试能够应用线上的微词云疾速便捷查看词云成果:导入两列的 XLS 文件即可,左侧管制栏也能够对形态字体等进行配置丑化。
展现成果如下图所示:
5. 总结
本文次要通过 IK 分词器实现了词频统计性能,用于词云的展现,不仅仅实用于 ES,任何数据源文档都能够进行词频统计。然而性能比拟根底,感兴趣的同学能够实现一下词排序形式变更 (tf/idf)、词性标注、实体辨认和情感剖析等性能;IK 分词器较为局限,须要应用 HanLP(自带词性标注) 等更高级的分词器以及 NLP 相干常识来辅助,也能够参考百度 AI 的词法剖析模块。
原文链接
本文为阿里云原创内容,未经容许不得转载。