需求,
实习需要学习这个。感觉蛮好玩的.....
我是这样做的:
根据网上的资料,自己整理,因为是开源的,所以配合Demo理解,不是算法层次的,嗯,更新中....
data包没下载下来,家里这边网不支持下载那个data包.有些Demo没测....嗯,下载下来在测
HanLP自然语言处理包
在学习HanLp之前,我们先了解一下自然语言处理(NLP)。
自然语言处理:
自然语言处理的目标是使计算机能够像人类一样理解语言。
自然语言处理是一门交叉学科,属于人工智能的一个分支,涉及计算机科学、语言学、数 学等多个领域的专业知识。
自然语言处理 :是一门合了计算机科学、人工智能以 及语言学的交叉学科,它们的关系如图 1!1 所示。这门学科研究的是如何通过机器学习等技术, 让计算机学会处理人类语言,乃至实现终极目标——理解人类语言或人工智能。
关键名词:
- 分词:指的是将一个汉字序列切分成一个一个单独的词。分词就是将连续的字序列按照一定的规范重新组合成 词序列的过程。
- 词性:是指以词的特点作为划分词类的根据,词性划分也可称之为词类的划分,词类是一个语言学术语,是一 种语言中词的语法分类,是以语法特征(包括句法功能和形态变化)为主要依据、兼顾词汇意义对词进行划分 的结果,现代汉语的词可以分为14种词类。如名词、动词、动名词等等。
- 聚类:聚类类似于分类,但是和分类不同的是,聚类要求划分的类是未知的。聚类分析是一种探索性的分析, 在分类的过程中,人们不必事先给出一个分类的标准,聚类分析能够从样本数据出发,自动进行分类。聚类分 析所使用方法的不同,常常会得到不同的结论。不同研究者对于同一组数据进行聚类分析,所得到的聚类数未 必一致。
- 相似度:从字面上理解就是两个字符串的相似程度。常用的计算相似度的算法有:编辑距离计算、杰卡德系数 计算、TFIDF计算等等。
- 词向量:顾名思义,词向量是用来表示词的向量,通常也被认为是词的特征向量,是自然语言中的词在计算机 中的表现形式。词向量技术是将词转化成为稠密向量,并且对于相似的词,其对应的词向量也相近。
- 依存关系:一个句子中有一个中心词,其他部分依存于该词,比如“北京是中国的首都在一句话中,动词是句子的中心,它支配其他成分,而不受其他成分支配。在上例中,动词“是” 是句子的中心,其他成分依存于它。
关于相似度计算的几种常用算法:
- (词距)编辑距离计算:编辑距离,英文叫做 Edit Distance,又称 Levenshtein 距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字 符替换成另一个字符,插入一个字符,删除一个字符。我们可以限制这个距离来得到相似的字符串。
- 杰卡德系数计算:这种算法比较简单,就是分词后的词向量的交集除以并集,它的计算方式非常简单,就是两 个样本的交集除以并集得到的数值,当两个样本完全一致时,结果为 1,当两个样本完全不同时,结果为 0。我们可以在[0,1]之间衡量这个相似度,约接近1相似度越高。
- TF-IDF计算:首先计算一个词的词频TF,再计算一个词的逆文档频率(权重),两者之积就是TF-IDF的值。 找出两个字符串中的关键词,每个字符串各取出若干个关键词(比如20个),合并成一个集合,计算每个字 符串对于这个集合中的词的词频(为了避免字符串长度的差异,可以使用相对词频);生成两个字符串各自的 词频向量;计算两个向量的余弦相似度,值越大就表示越相似。
HanLP是由一系列模型与算法组成的Java工具包,目标是促进自然语言处理在生产环境中的应用。HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。
HanLP提供下列功能:
- 中文分词
- HMM-Bigram(速度与精度最佳平衡;一百兆内存)
- 由字构词(侧重精度,全世界最大语料库,可识别新词;适合NLP任务)
- 词典分词(侧重速度,每秒数千万字符;省内存)
- 极速词典分词
- 所有分词器都支持:
- 词性标注
- 命名实体识别
- 基于HMM角色标注的命名实体识别 (速度快)
- 基于线性模型的命名实体识别(精度高)
- 关键词提取
- 自动摘要
- 短语提取
- 拼音转换
- 多音字、声母、韵母、声调
- 简繁转换
- 简繁分歧词(简体、繁体、臺灣正體、香港繁體)
- 文本推荐
- 语义推荐、拼音推荐、字词推荐
- 依存句法分析
- 文本分类
- 情感分析
- 文本聚类
- KMeans、Repeated Bisection、自动推断聚类数目k
- 词向量训练、加载、词语相似度计算、语义运算、查询、KMeans聚类
- 文档语义相似度计算
- 语料库工具
- 部分默认模型训练自小型语料库,鼓励用户自行训练。所有模块提供训练接口,语料可参考98年人民日报语料库。
调用方法
HanLP几乎所有的功能都可以通过工具类HanLP
快捷调用
词典说明:
基本格式
词典分为词频词性词典和词频词典。
- 词频词性词典
- 每一行代表一个单词,格式遵从
[单词] [词性A] [A的频次] [词性B] [B的频次]
。
- 词频词典
- 每一行代表一个单词,格式遵从
[单词] [单词的频次]
。 - 每一行的分隔符为空格符或制表符
DEMO:
Demo
简单分词
public static void demoAtFirstSight() {
System.out.println("首次编译运行时,HanLP会自动构建词典缓存,请稍候……");
// HanLP.Config.enableDebug(); // 为了避免你等得无聊,开启调试模式说点什么:-)
System.out.println(HanLP.segment("你好,欢迎使用HanLP汉语处理包!接下来请从其他Demo中体验HanLP丰富的功能~"));
}
输出
首次编译运行时,HanLP会自动构建词典缓存,请稍候……
[你好/l, ,/w, 欢迎/v, 使用/v, HanLP/nx, 汉语/nz, 处理/v, 包/v, !/w, 接下来/l, 请/v, 从/p, 其他/r, Demo/nx, 中/f, 体验/vn, HanLP/nx, 丰富/a, 的/uj, 功能/n, ~/nx]
测试速度:
public static void demoBasicTokenizer(){
String text = "举办纪念活动铭记二战历史,不忘战争带给人类的深重灾难,是为了防止悲剧重演,确保和平永驻;" +
"铭记二战历史,更是为了提醒国际社会,需要共同捍卫二战胜利成果和国际公平正义," +
"必须警惕和抵制在历史认知和维护战后国际秩序问题上的倒行逆施。";
System.out.println(BasicTokenizer.segment(text));
// 测试分词速度,让大家对HanLP的性能有一个直观的认识
long start = System.currentTimeMillis();
int pressure = 100000;
for (int i = 0; i < pressure; ++i)
{
BasicTokenizer.segment(text);
}
double costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("BasicTokenizer分词速度:%.2f字每秒\n", text.length() * pressure / costTime);
}
输出:
[举办/v, 纪念/v, 活动/vn, 铭记/v, 二战/j, 历史/n, ,/w, 不/d, 忘/v, 战争/n, 带/v, 给/p, 人类/n, 的/uj, 深重/a, 灾难/n, ,/w, 是/v, 为了/p, 防止/v, 悲剧/n, 重演/v, ,/w, 确保/v, 和平/n, 永/d, 驻/v, ;/w, 铭记/v, 二战/j, 历史/n, ,/w, 更/d, 是/v, 为了/p, 提醒/v, 国际/n, 社会/n, ,/w, 需要/v, 共同/d, 捍卫/v, 二战/j, 胜利/vn, 成果/n, 和/c, 国际/n, 公平/a, 正义/n, ,/w, 必须/d, 警惕/v, 和/c, 抵制/v, 在/p, 历史/n, 认知/vn, 和/c, 维护/v, 战后/t, 国际/n, 秩序/n, 问题/n, 上/f, 的/uj, 倒行逆施/i, 。/w]
BasicTokenizer分词速度:2152604.27字每秒
中人名识别
Segment segment = HanLP.newSegment().enableNameRecognize(true);
目前分词器基本上都默认开启了中国人名识别,比如HanLP.segment()
接口中使用的分词器等等,用户不必手动开启;上面的代码只是为了强调。
public static void demoChineseNameRecognition() {
String[] testCase = new String[]{
"武大靖创世界纪录夺冠,中国代表团平昌首金"
};
Segment segment = HanLP.newSegment().enableNameRecognize(true);
for (String sentence : testCase)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
输出:
[武大靖/nr, 创/vg, 世界/n, 纪录/n, 夺冠/v, ,/w, 中国/ns, 代表团/n, 平昌/ns, 首/m, 金/ng]
用户词典的动态增删
这个我理解就是自己定义一些识别词
-
CustomDictionary
是一份全局的用户自定义词典,可以随时增删,影响全部分词器。 - 另外可以在任何分词器中关闭它。通过代码动态增删不会保存到词典文件。
-
CustomDictionary
主词典文本路径是data/dictionary/custom/CustomDictionary.txt
,用户可以在此增加自己的词语(不推荐);也可以单独新建一个文本文件,通过配置文件CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 我的词典.txt;
来追加词典(推荐)。 - 始终建议将相同词性的词语放到同一个词典文件里,便于维护和分享。
- 每一行代表一个单词,格式遵从
[单词] [词性A] [A的频次] [词性B] [B的频次] ...
如果不填词性则表示采用词典的默认词性。 - 词典的默认词性默认是名词n,可以通过配置文件修改:
全国地名大全.txt ns;
如果词典路径后面空格紧接着词性,则该词典默认是该词性。
public static void demoCustomDictionary() {
// 动态增加
CustomDictionary.add("攻城狮");
// 强行插入
CustomDictionary.insert("白富美", "nz 1024");
// 删除词语(注释掉试试)
// CustomDictionary.remove("攻城狮");
System.out.println(CustomDictionary.add("单身狗", "nz 1024 n 1"));
System.out.println(CustomDictionary.get("单身狗"));
String text = "攻城狮逆袭单身狗,迎娶白富美,走上人生巅峰"; // 怎么可能噗哈哈!
// DoubleArrayTrie分词
final char[] charArray = text.toCharArray();
CustomDictionary.parseText(charArray, new AhoCorasickDoubleArrayTrie.IHit<CoreDictionary.Attribute>()
{
@Override
public void hit(int begin, int end, CoreDictionary.Attribute value)
{
System.out.printf("[%d:%d]=%s %s\n", begin, end, new String(charArray, begin, end - begin), value);
}
});
// 首字哈希之后二分的trie树分词
BaseSearcher searcher = CustomDictionary.getSearcher(text);
Map.Entry entry;
System.out.println("首字哈希之后二分的trie树分词");
while ((entry = searcher.next()) != null)
{
System.out.println(entry);
}
// 标准分词
System.out.println("标准分词:"+HanLP.segment(text));
// Note:动态增删不会影响词典文件
// 目前CustomDictionary使用DAT储存词典文件中的词语,用BinTrie储存动态加入的词语,前者性能高,后者性能低
// 之所以保留动态增删功能,一方面是历史遗留特性,另一方面是调试用;未来可能会去掉动态增删特性。
}
true
nz 1024 n 1
[0:3]=攻城狮 nz 1
[5:8]=单身狗 nz 1024 n 1
[11:14]=白富美 nz 1024
[0:2]=攻城 vi 15
[3:5]=逆袭 nz 199
首字哈希之后二分的trie树分词
攻城狮=nz 1
单身狗=nz 1024 n 1
白富美=nz 1024
标准分词:[攻城狮/nz, 逆袭/nz, 单身狗/nz, ,/w, 迎娶/v, 白富美/nz, ,/w, 走/v, 上/f, 人生/n, 巅峰/n]
自定义词性,
以及往词典中插入自定义词性的词语
public static void demoCustomNature() {
// 对于系统中已有的词性,可以直接获取
Nature pcNature = Nature.fromString("n");
System.out.println(pcNature);
// 此时系统中没有"电脑品牌"这个词性
pcNature = Nature.fromString("电脑品牌");
System.out.println("此时系统中没有是否有:\"电脑品牌\"这个词性" + pcNature);
// 我们可以动态添加一个
pcNature = Nature.create("电脑品牌");
System.out.println(pcNature);
// 可以将它赋予到某个词语
LexiconUtility.setAttribute("苹果电脑", pcNature);
// 或者
LexiconUtility.setAttribute("苹果电脑", "电脑品牌 1000");
// 它们将在分词结果中生效
List<Term> termList = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗");
System.out.println(termList);
for (Term term : termList) {
if (term.nature == pcNature)
System.out.printf("找到了 [%s] : %s\n", pcNature, term.word);
}
// 还可以直接插入到用户词典
CustomDictionary.insert("阿尔法狗", "科技名词 1024");
StandardTokenizer.SEGMENT.enablePartOfSpeechTagging(true); // 依然支持隐马词性标注
termList = HanLP.segment("苹果电脑可以运行开源阿尔法狗代码吗");
System.out.println(termList);
// 1.6.5之后Nature不再是枚举类型,无法switch。但终于不再涉及反射了,在各种JRE环境下都更稳定。
for (Term term : termList) {
if (term.nature == n) {
System.out.printf("找到了 [%s] : %s\n", "名词", term.word);
}
}
}
n
此时系统中没有是否有:"电脑品牌"这个词性null
电脑品牌
[苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/vn, 阿尔法/nz, 狗/n, 代码/n, 吗/y]
找到了 [电脑品牌] : 苹果电脑
[苹果电脑/电脑品牌, 可以/v, 运行/vn, 开源/vn, 阿尔法狗/科技名词, 代码/n, 吗/y]
找到了 [名词] : 代码
极速分词,
基于DoubleArrayTrie实现的词典正向最长分词,适用于“高吞吐量”“精度一般”的场合\
SpeedTokenizer.segment(text)
HanLP.Config.ShowTermNature = false;// 用于设置词性是否显示。
public static void demoHighSpeedSegment() {
String text = "江西鄱阳湖干枯,中国最大淡水湖变成大草原";
// 不显示词性
HanLP.Config.ShowTermNature = false;
System.out.println(SpeedTokenizer.segment(text));
long start = System.currentTimeMillis();
int pressure = 1000000;
for (int i = 0; i < pressure; ++i)
{
SpeedTokenizer.segment(text);
}
double costTime = (System.currentTimeMillis() - start) / (double)1000;
System.out.printf("SpeedTokenizer分词速度:%.2f字每秒\n", text.length() * pressure / costTime);
}
[江西, 鄱阳湖, 干枯, ,, 中国, 最, 大, 淡水湖, 变成, 大, 草原]
SpeedTokenizer分词速度:16877637.13字每秒
索引分词
索引分词IndexTokenizer
是面向搜索引擎的分词器,能够对长词全切分,另外通过term.offset
可以获取单词在文本中的偏移量。
List<Term> termList = IndexTokenizer.segment("主副食品");
public static void emoIndexSegment() {
List<Term> termList = IndexTokenizer.segment("主副食品");
for (Term term : termList)
{
System.out.println(term + " [" + term.offset + ":" + (term.offset + term.word.length()) + "]");
}
System.out.println("\n最细颗粒度切分:");
IndexTokenizer.SEGMENT.enableIndexMode(1);
termList = IndexTokenizer.segment("主副食品");
for (Term term : termList)
{
System.out.println(term + " [" + term.offset + ":" + (term.offset + term.word.length()) + "]");
}
}
主副食品/n [0:4]
主副食/j [0:3]
副食品/n [1:4]
副食/n [1:3]
食品/n [2:4]
最细颗粒度切分:
主副食品/n [0:4]
主副食/j [0:3]
主/n [0:1]
副食品/n [1:4]
副食/n [1:3]
副/b [1:2]
食品/n [2:4]
食/v [2:3]
品/v [3:4]
日本人名识别
Segment segment = HanLP.newSegment().enableJapaneseNameRecognize(true);
目前标准分词器默认关闭了日本人名识别,用户需要手动开启;这是因为日本人名的出现频率较低,但是又消耗性能。
public static void demoJapaneseNameRecognition()
{
String[] testCase = new String[]{
"北川景子参演了林诣彬导演的《速度与激情3》",
"林志玲亮相网友:确定不是波多野结衣?",
"龟山千广和近藤公园在龟山公园里喝酒赏花",
};
Segment segment = HanLP.newSegment().enableJapaneseNameRecognize(true);
for (String sentence : testCase)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
[北川景子/nrj, 参演/v, 了/ul, 林诣彬/nr, 导演/n, 的/uj, 《/w, 速度/n, 与/p, 激情/n, 3/m, 》/w]
[林/ng, 志玲/nz, 亮/v, 相/d, 网友/n, :/w, 确定/v, 不是/c, 波多/nrf, 野/b, 结衣/nz, ?/w]
[龟山千广/nrj, 和/c, 近藤公园/nrj, 在/p, 龟山/nz, 公园/n, 里/f, 喝酒/v, 赏花/nz]
关键词提取
内部采用TextRankKeyword
实现,用户可以直接调用TextRankKeyword.getKeywordList(document, size)
List<String> keywordList = HanLP.extractKeyword(content, 5);
public static void demoKeyword()
{
String content = "程序员(英文Programmer)是从事程序开发、维护的专业人员。" +
"一般将程序员分为程序设计人员和程序编码人员," +
"但两者的界限并不非常清楚,特别是在中国。" +
"软件从业人员分为初级程序员、高级程序员、系统" +
"分析员和项目经理四大类。";
List<String> keywordList = HanLP.extractKeyword(content, 5);
System.out.println(keywordList);
}
[程序员, 人员, 程序, 分为, 开发]
多线程并行分词
* 由于HanLP的任何分词器都是线程安全的,所以用户只需调用一个配置接口就可以启用任何分词器的并行化
- segment.enableMultithreading(true); // 或者 segment.enableMultithreading(4); //多线程
- segment.enableMultithreading(false); // 单线程
public static void emoMultithreadingSegment() throws IOException
{
Segment segment = new CRFLexicalAnalyzer(HanLP.Config.CRFCWSModelPath).enableCustomDictionary(false); // CRF分词器效果好,速度慢,并行化之后可以提高一些速度
String text = "举办纪念活动铭记二战历史,不忘战争带给人类的深重灾难,是为了防止悲剧重演,确保和平永驻;" +
"铭记二战历史,更是为了提醒国际社会,需要共同捍卫二战胜利成果和国际公平正义," +
"必须警惕和抵制在历史认知和维护战后国际秩序问题上的倒行逆施。";
HanLP.Config.ShowTermNature = false;
System.out.println(segment.seg(text));
int pressure = 10000;
StringBuilder sbBigText = new StringBuilder(text.length() * pressure);
for (int i = 0; i < pressure; i++)
{
sbBigText.append(text);
}
text = sbBigText.toString();
System.gc();
long start;
double costTime;
// 测个速度
segment.enableMultithreading(false);
start = System.currentTimeMillis();
segment.seg(text);
costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("单线程分词速度:%.2f字每秒\n", text.length() / costTime);
System.gc();
segment.enableMultithreading(true); // 或者 segment.enableMultithreading(4);
start = System.currentTimeMillis();
segment.seg(text);
costTime = (System.currentTimeMillis() - start) / (double) 1000;
System.out.printf("多线程分词速度:%.2f字每秒\n", text.length() / costTime);
System.gc();
// Note:
// 内部的并行化机制可以对1万字以上的大文本开启多线程分词
// 另一方面,HanLP中的任何Segment本身都是线程安全的。
// 你可以开10个线程用同一个CRFSegment对象切分任意文本,不需要任何线程同步的措施,每个线程都可以得到正确的结果。
}
[举办, 纪念, 活动, 铭记, 二战, 历史, ,, 不, 忘, 战争, 带, 给, 人类, 的, 深重, 灾难, ,, 是, 为了, 防止, 悲剧, 重演, ,, 确保, 和平, 永驻, ;, 铭记, 二战, 历史, ,, 更是, 为了, 提醒, 国际, 社会, ,, 需要, 共同, 捍卫, 二战, 胜利, 成果, 和, 国际, 公平, 正义, ,, 必须, 警惕, 和, 抵制, 在, 历史, 认知, 和, 维护, 战后, 国际, 秩序, 问题, 上, 的, 倒行逆施, 。]
单线程分词速度:234948.60字每秒
多线程分词速度:994671.40字每秒
,
词语提取、新词发现
public static void demoNewWordDiscover() throws IOException
{
// 文本长度越大越好,试试红楼梦?
List<WordInfo> wordInfoList = HanLP.extractWords(IOUtil.newBufferedReader("C:\\Users\\12249\\Desktop\\新建文本文档 (2).txt"), 100);
System.out.println(wordInfoList);
}
嗯,没测试,获取不到文本。
NLP分词,更精准的中文分词、词性标注与命名实体识别。
NLP分词NLPTokenizer
会执行全部命名实体识别和词性标注。
* 语料库规模决定实际效果,面向生产环境的语料库应当在千万字量级。欢迎用户在自己的语料上训练新模型以适应新领域、识别新的命名实体。
* 标注集请查阅 https://github.com/hankcs/HanLP/blob/master/data/dictionary/other/TagPKU98.csv
* 或者干脆调用 Sentence#translateLabels() 转为中文
public static void demoNLPSegment ()
{
NLPTokenizer.ANALYZER.enableCustomDictionary(false); // 中文分词≠词典,不用词典照样分词。
System.out.println(NLPTokenizer.segment("我新造一个词叫幻想乡你能识别并正确标注词性吗?")); // “正确”是副形词。
// 注意观察下面两个“希望”的词性、两个“晚霞”的词性
System.out.println(NLPTokenizer.analyze("我的希望是希望张晚霞的背影被晚霞映红").translateLabels());
System.out.println(NLPTokenizer.analyze("支援臺灣正體香港繁體:微软公司於1975年由比爾·蓋茲和保羅·艾倫創立。"));
}
[我/r, 新/d, 造/v, 一个/m, 词/n, 叫/v, 幻想乡/ns, 你/r, 能/v, 识别/v, 并/c, 正确/ad, 标注/v, 词性/n, 吗/y, ?/w]
我/代词 的/助词 希望/名动词 是/动词 希望/动词 张/量词 晚霞/名词 的/助词 背影/名词 被/介词 晚霞/名词 映红/动词
支援/v [臺灣/ns 正體/n 香港/ns 繁體/n]/nt :/w [微软/nt 公司/n]/nt 於/p 1975年/t 由/p 比爾·蓋茲/nr 和/c 保羅·艾倫/nr 創立/v 。/w
正规化字符配置项的效果(繁体->简体,全角->半角,大写->小写)。
* 该配置项位于hanlp.properties中,通过Normalization=true来开启
* 切换配置后必须删除CustomDictionary.txt.bin缓存,否则只影响动态插入的新词。
public static void demoNormalization()
{
HanLP.Config.Normalization = true;
CustomDictionary.insert("爱听4G", "nz 1000");
System.out.println(HanLP.segment("爱听4g"));
System.out.println(HanLP.segment("爱听4G"));
System.out.println(HanLP.segment("爱听4G"));
System.out.println(HanLP.segment("爱听4G"));
System.out.println(HanLP.segment("愛聽4G"));
}
[爱听4g/nz]
[爱听4g/nz]
[爱听4g/nz]
[爱听4g/nz]
[爱听4g/nz]
自动去除停用词、自动断句的分词器
public static void demoNotionalTokenizer()
{
String text = "小区居民有的反对喂养流浪猫,而有的居民却赞成喂养这些小宝贝";
// 自动去除停用词
System.out.println(NotionalTokenizer.segment(text));
// 停用词典位于data/dictionary/stopwords.txt,可以自行修改
// 自动断句+去除停用词
for (List<Term> sentence : NotionalTokenizer.seg2sentence(text))
{
System.out.println(sentence);
}
}
[小区/n, 居民/n, 反对/v, 喂养/vn, 流浪猫/nz, 居民/n, 赞成/v, 喂养/vn, 小宝贝/nz]
[小区/n, 居民/n, 反对/v, 喂养/vn, 流浪猫/nz]
[居民/n, 赞成/v, 喂养/vn, 小宝贝/nz]
N最短路径分词,
- N最短路分词器
NShortSegment
比最短路分词器慢,但是效果稍微好一些,对命名实体识别能力更强。 - 一般场景下最短路分词的精度已经足够,而且速度比N最短路分词器快几倍,请酌情选择。
该分词器比最短路分词器慢,但是效果稍微好一些,对命名实体识别能力更强
public static void DemoNShortSegment()
{
Segment nShortSegment = new NShortSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true);
Segment shortestSegment = new ViterbiSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true);
String[] testCase = new String[]{
"与中国太平洋财产保险股份有限公司南昌中心支公司保险合同纠纷案",
"新北商贸有限公司",
};
for (String sentence : testCase)
{
System.out.println("N-最短分词:" + nShortSegment.seg(sentence) + "\n最短路分词:" + shortestSegment.seg(sentence));
}
}
N-最短分词:[ 与/cc, 中国/ns, 太平洋/ns, 财产保险股份有限公司南昌中心支公司/nt, 保险/n, 合同/n, 纠纷案/nz]
最短路分词:[与/cc, 中国/ns, 太平洋/ns, 财产保险股份有限公司南昌中心支公司/nt, 保险/n, 合同/n, 纠纷案/nz]
N-最短分词:[新北商贸有限公司/nt]
最短路分词:[新北商贸有限公司/nt]
CRF分词
CRF对新词有很好的识别能力,但是无法利用自定义词典。
public static void DemoCRFSegment() throws IOException {
HanLP.Config.ShowTermNature = false; // 关闭词性显示
Segment segment = new CRFLexicalAnalyzer();
String[] sentenceArray = new String[]
{
"HanLP是由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用。",
"鐵桿部隊憤怒情緒集結 馬英九腹背受敵", // 繁体无压力
"馬英九回應連勝文“丐幫說”:稱黨內同志談話應謹慎",
"高锰酸钾,强氧化剂,紫红色晶体,可溶于水,遇乙醇即被还原。常用作剂、水净化剂、氧化剂、漂白剂、毒气吸收剂、二氧化碳精制剂等。", // 专业名词有一定辨识能力
"《夜晚的骰子》通过描述浅草的舞女在暗夜中扔骰子的情景,寄托了作者对庶民生活区的情感", // 非新闻语料
"这个像是真的[委屈]前面那个打扮太江户了,一点不上品...@hankcs", // 微博
"鼎泰丰的小笼一点味道也没有...每样都淡淡的...淡淡的,哪有食堂2A的好次",
"克里斯蒂娜·克罗尔说:不,我不是虎妈。我全家都热爱音乐,我也鼓励他们这么做。",
"今日APPS:Sago Mini Toolbox培养孩子动手能力",
"财政部副部长王保安调任国家统计局党组书记",
"2.34米男子娶1.53米女粉丝 称夫妻生活没问题",
"你看过穆赫兰道吗",
"乐视超级手机能否承载贾布斯的生态梦"
};
for (String sentence : sentenceArray)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
HanLP/nx 是/v 由/p 一/m 系列/q 模型/n 与/c 算法/n 组成/v 的/u Java/vn 工具/n 包/v ,/w 目标/n 是/v 普及/v 自然语言处理/nz 在/p 生产/vn 环境/n 中的/v 应用/vn 。/w
高锰/nr 酸钾/n ,/w 强氧化剂/n ,/w 紫红色/n 晶体/n ,/w 可/v 溶于/v 水/n ,/w 遇乙醇/v 即/v 被/p 还原/v 。/w 常用/b 作剂/n 、/w 水/n 净化剂/n 、/w 氧化剂/n 、/w 漂白剂/n 、/w 毒气/n 吸收剂/n 、/w 二氧化碳/n 精制剂/n 等/u 。/w
《/w 夜晚/t 的/u 骰子/n 》/w 通过/p 描述/v 浅草/n 的/u 舞女/n 在/p 暗夜/n 中/f 扔/v 骰子/n 的/u 情景/n ,/w 寄托/v 了/u 作者/n 对/p 庶民/n 生活区/n 的/u 情感/n
这个/r 像是/v 真的/d [/w 委屈/n ]/w 前面/f 那个/r 打扮/v 太江户/n 了/y ,/w 一点/m 不/d 上/v 品...@hankcs/n
鼎泰丰/nr 的/u 小笼/nf 一点/m 味道/n 也/d 没有/d .../w 每样/r 都/d 淡淡/a 的/u .../w 淡淡/a 的/u ,/w 哪/r 有/v 食堂/n 2A/m 的/u 好/a 次/Bg
克里斯蒂娜·克罗尔/nr 说/v :/w 不/d ,/w 我/r 不/d 是/v 虎妈/n 。/w 我/r 全家/n 都/d 热爱/v 音乐/n ,/w 我/r 也/d 鼓励/v 他们/r 这么/r 做/v 。/w
今日/t A/nx PPS/n :/w Sago Mini Toolbox/nx 培养/v 孩子/n 动手/v 能力/n
2.34/m 米/q 男子/n 娶/v 1.53/m 米/q 女/b 粉丝 /n 称/v 夫妻生活/nz 没/v 问题/n
你/r 看/v 过/u 穆赫兰道/n 吗/y
乐视/v 超级/b 手机/n 能否/v 承载/v 贾布斯/nr 的/u 生态梦/n
数词和数量词识别
public static void demoNumberAndQuantifierRecognition()
{
StandardTokenizer.SEGMENT.enableNumberQuantifierRecognize(true);
String[] testCase = new String[]
{
"十九元套餐包括什么",
"九千九百九十九朵玫瑰",
"壹佰块都不给我",
"9012345678只蚂蚁",
"牛奶三〇〇克*2",
"ChinaJoy“扫黄”细则露胸超2厘米罚款",
};
for (String sentence : testCase)
{
System.out.println(StandardTokenizer.segment(sentence));
}
}
[十九元/mq, 套餐/n, 包括/v, 什么/r]
[九千九百九十九朵/mq, 玫瑰/n]
[壹佰块/mq, 都/d, 不/d, 给/p, 我/r]
[9012345678只/mq, 蚂蚁/n]
[牛奶/n, 三〇〇克/mq, */w, 2/m]
[ChinaJoy/nx, “/w, 扫黄/v, ”/w, 细则/n, 露/v, 胸/ng, 超/v, 2厘米/mq, 罚款/v]
词共现统计
public static void DemoOccurrence()
{
Occurrence occurrence = new Occurrence();
occurrence.addAll("在计算机音视频和图形图像技术等二维信息算法处理方面目前比较先进的视频处理算法");
occurrence.compute();
Set<Map.Entry<String, TermFrequency>> uniGram = occurrence.getUniGram();
for (Map.Entry<String, TermFrequency> entry : uniGram)
{
TermFrequency termFrequency = entry.getValue();
System.out.println(termFrequency);
}
Set<Map.Entry<String, PairFrequency>> biGram = occurrence.getBiGram();
for (Map.Entry<String, PairFrequency> entry : biGram)
{
PairFrequency pairFrequency = entry.getValue();
if (pairFrequency.isRight())
System.out.println(pairFrequency);
}
Set<Map.Entry<String, TriaFrequency>> triGram = occurrence.getTriGram();
for (Map.Entry<String, TriaFrequency> entry : triGram)
{
TriaFrequency triaFrequency = entry.getValue();
if (triaFrequency.isRight())
System.out.println(triaFrequency);
}
}
信息=1
先进=1
图像=1
图形=1
处理=2
技术=1
方面=1
比较=1
目前=1
算法=2
视频=2
计算机=1
音=1
信息→算法= tf=1 mi=15.974323732511902 le=0.0 re=0.0 score=1.1201926307907633
先进→视频= tf=1 mi=16.02853754910605 le=0.0 re=0.0 score=1.1239943515304154
图像→技术= tf=1 mi=11.849234163719903 le=0.0 re=0.0 score=0.8309224861706236
图形→图像= tf=1 mi=16.495759535389908 le=0.0 re=0.0 score=1.156758093817257
处理→方面= tf=1 mi=9.784949554110385 le=0.0 re=0.0 score=0.6861654093603521
处理→算法= tf=1 mi=16.486909920112925 le=0.0 re=0.0 score=1.1561375183246974
技术→信息= tf=1 mi=9.130573783505678 le=0.0 re=0.0 score=0.6402775878616854
方面→目前= tf=1 mi=8.658819236548384 le=0.0 re=0.0 score=0.6071960016929984
比较→先进= tf=1 mi=10.77626412105942 le=0.0 re=0.0 score=0.755680920081583
目前→比较= tf=1 mi=10.108506174504294 le=0.0 re=0.0 score=0.7088546792085115
算法→处理= tf=1 mi=16.486909920112925 le=0.0 re=0.0 score=1.1561375183246974
视频→图形= tf=1 mi=19.71463536025811 le=0.0 re=0.0 score=1.3824803865932096
视频→处理= tf=1 mi=16.486909920112925 le=0.0 re=0.0 score=1.1561375183246974
计算机→音= tf=1 mi=15.802612354829963 le=0.0 re=0.0 score=1.108151443750666
音→视频= tf=1 mi=20.120100468366275 le=0.0 re=0.0 score=1.4109134541678408
信息→算法→处理= tf=1 mi=0.0 le=0.0 re=0.0
先进→视频→处理= tf=1 mi=0.0 le=0.0 re=0.0
图像→技术→信息= tf=1 mi=0.0 le=0.0 re=0.0
图形→图像→技术= tf=1 mi=0.0 le=0.0 re=0.0
处理→方面→目前= tf=1 mi=0.0 le=0.0 re=0.0
技术→信息→算法= tf=1 mi=0.0 le=0.0 re=0.0
方面→目前→比较= tf=1 mi=0.0 le=0.0 re=0.0
比较→先进→视频= tf=1 mi=0.0 le=0.0 re=0.0
目前→比较→先进= tf=1 mi=0.0 le=0.0 re=0.0
算法→处理→方面= tf=1 mi=0.0 le=0.0 re=0.0
视频→图形→图像= tf=1 mi=0.0 le=0.0 re=0.0
视频→处理→算法= tf=1 mi=0.0 le=0.0 re=0.0
计算机→音→视频= tf=1 mi=0.0 le=0.0 re=0.0
音→视频→图形= tf=1 mi=0.0 le=0.0 re=0.0
机构名识别
- 目前分词器默认关闭了机构名识别,用户需要手动开启;这是因为消耗性能,其实常用机构名都收录在核心词典和用户自定义词典中。
- HanLP的目的不是演示动态识别,在生产环境中,能靠词典解决的问题就靠词典解决,这是最高效稳定的方法。
public static void DemoOrganizationRecognition()
{
String[] testCase = new String[]{
"我在上海林原科技有限公司兼职工作,",
"我经常在台川喜宴餐厅吃饭,",
"偶尔去开元地中海影城看电影。",
"不用词典,福哈生态工程有限公司是动态识别的结果。",
};
Segment segment = HanLP.newSegment().enableCustomDictionary(false).enableOrganizationRecognize(true);
for (String sentence : testCase)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
[我/rr, 在/p, 上海/ns, 林原科技有限公司/nt, 兼职/vn, 工作/vn, ,/w]
[我/rr, 经常/d, 在/p, 台川喜宴餐厅/nt, 吃饭/vi, ,/w]
[偶尔/d, 去/vf, 开元地中海影城/nt, 看/v, 电影/n, 。/w]
[不用/d, 词典/n, ,/w, 福哈生态工程有限公司/nt, 是/vshi, 动态/n, 识别/vn, 的/ude1, 结果/n, 。/w]
基于感知机序列标注的词法分析器,可选多个模型。
* - large训练自一亿字的大型综合语料库,是已知范围内全世界最大的中文分词语料库。
* - pku199801训练自个人修订版1998人民日报语料1月份,仅有183万字。
* 语料库规模决定实际效果,面向生产环境的语料库应当在千万字量级。欢迎用户在自己的语料上训练新模型以适应新领域、识别新的命名实体。
* 无论在何种语料上训练,都完全支持简繁全半角和大小写。
public class DemoPerceptronLexicalAnalyzer extends TestUtility
{
public static void main(String[] args) throws IOException
{
PerceptronLexicalAnalyzer analyzer = new PerceptronLexicalAnalyzer("data/model/perceptron/pku199801/cws.bin",
HanLP.Config.PerceptronPOSModelPath,
HanLP.Config.PerceptronNERModelPath);
System.out.println(analyzer.analyze("上海华安工业(集团)公司董事长谭旭光和秘书胡花蕊来到美国纽约现代艺术博物馆参观"));
System.out.println(analyzer.analyze("微软公司於1975年由比爾·蓋茲和保羅·艾倫創立,18年啟動以智慧雲端、前端為導向的大改組。"));
// 任何模型总会有失误,特别是98年这种陈旧的语料库
System.out.println(analyzer.analyze("通电话讨论太空探索技术公司"));
// 支持在线学习
analyzer.learn("与/c 特朗普/nr 通/v 电话/n 讨论/v [太空/s 探索/vn 技术/n 公司/n]/nt");
// 学习到新知识
System.out.println(analyzer.analyze("通电话讨论太空探索技术公司"));
// 还可以举一反三
System.out.println(analyzer.analyze("通电话"));
// 知识的泛化不是死板的规则,而是比较灵活的统计信息
System.out.println(analyzer.analyze("我在浙江金华出生"));
analyzer.learn("在/p 浙江/ns 金华/ns 出生/v");
System.out.println(analyzer.analyze("我在四川金华出生,我的名字叫金华"));
// 在线学习后的模型支持序列化,以分词模型为例:
// analyzer.getPerceptronSegmenter().getModel().save(HanLP.Config.PerceptronCWSModelPath);
// 请用户按需执行对空格制表符等的预处理,只有你最清楚自己的文本中都有些什么奇怪的东西
System.out.println(analyzer.analyze("空格 \t\n\r\f 统统都不要"
.replaceAll("\\s+", "") // 去除所有空白符
.replaceAll(" ", "") // 如果一些文本中含有html控制符
));
}
}
[上海/ns 华安/nz 工业/n (/w 集团/n )/w 公司/n]/nt 董事长/n 谭旭光/nr 和/c 秘书/n 胡花蕊/nr 来到/v [美国纽约/ns 现代/ntc 艺术/n 博物馆/n]/ns 参观/v
微软公司/ntc 於/p 1975年/t 由/p 比爾·蓋茲/nr 和/c 保羅·艾倫/nr 創立/v ,/w 18年/t 啟動/v 以/p 智慧/n 雲端/n 、/w 前端/n 為/v 導向/n 的/u 大/a 改組/vn 。/w
通/v 电话/n 讨论/v [太空/s 探索/vn 技术/n]/nt 公司/n
通/v 电话/n 讨论/v [太空/s 探索/vn 技术/n 公司/n]/nt
通/v 电话/n
我/r 在/p [浙江/ns 金华/ns]/ns 出生/v
我/r 在/p [四川/ns 金华/ns]/ns 出生/v ,/w 我/r 的/u 名字/n 叫/v 金华/nr
空格/n 统统/d 都/d 不要/d
短语提取
**
* 短语提取
* @author hankcs
*/
public class DemoPhraseExtractor
{
public static void main(String[] args)
{
String text = "算法工程师\n" +
"算法(Algorithm)是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。" +
"如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、" +
"空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。算法工程师就是利用算法处理事物的人。\n" +
"\n" +
"1职位简介\n" +
"算法工程师是一个非常高端的职位;\n" +
"专业要求:计算机、电子、通信、数学等相关专业;\n" +
"学历要求:本科及其以上的学历,大多数是硕士学历及其以上;\n" +
"语言要求:英语要求是熟练,基本上能阅读国外专业书刊;\n" +
"必须掌握计算机相关知识,熟练使用仿真工具MATLAB等,必须会一门编程语言。\n" +
"\n" +
"2研究方向\n" +
"视频算法工程师、图像处理算法工程师、音频算法工程师 通信基带算法工程师\n" +
"\n" +
"3目前国内外状况\n" +
"目前国内从事算法研究的工程师不少,但是高级算法工程师却很少,是一个非常紧缺的专业工程师。" +
"算法工程师根据研究领域来分主要有音频/视频算法处理、图像技术方面的二维信息算法处理和通信物理层、" +
"雷达信号处理、生物医学信号处理等领域的一维信息算法处理。\n" +
"在计算机音视频和图形图像技术等二维信息算法处理方面目前比较先进的视频处理算法:机器视觉成为此类算法研究的核心;" +
"另外还有2D转3D算法(2D-to-3D conversion),去隔行算法(de-interlacing),运动估计运动补偿算法" +
"(Motion estimation/Motion Compensation),去噪算法(Noise Reduction),缩放算法(scaling)," +
"锐化处理算法(Sharpness),超分辨率算法(Super Resolution),手势识别(gesture recognition),人脸识别(face recognition)。\n" +
"在通信物理层等一维信息领域目前常用的算法:无线领域的RRM、RTT,传送领域的调制解调、信道均衡、信号检测、网络优化、信号分解等。\n" +
"另外数据挖掘、互联网搜索算法也成为当今的热门方向。\n" +
"算法工程师逐渐往人工智能方向发展。";
List<String> phraseList = HanLP.extractPhrase(text, 5);
System.out.println(phraseList);
}
}
[算法工程师, 算法处理, 一维信息, 算法研究, 图像技术]
汉字转拼音
/**
* 汉字转拼音
* @author hankcs
*/
public class DemoPinyin
{
public static void main(String[] args)
{
String text = "重载不是重任!";
List<Pinyin> pinyinList = HanLP.convertToPinyinList(text);
System.out.print("原文,");
for (char c : text.toCharArray())
{
System.out.printf("%c,", c);
}
System.out.println();
System.out.print("拼音(数字音调),");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin);
}
System.out.println();
System.out.print("拼音(符号音调),");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getPinyinWithToneMark());
}
System.out.println();
System.out.print("拼音(无音调),");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getPinyinWithoutTone());
}
System.out.println();
System.out.print("声调,");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getTone());
}
System.out.println();
System.out.print("声母,");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getShengmu());
}
System.out.println();
System.out.print("韵母,");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getYunmu());
}
System.out.println();
System.out.print("输入法头,");
for (Pinyin pinyin : pinyinList)
{
System.out.printf("%s,", pinyin.getHead());
}
System.out.println();
// 拼音转换可选保留无拼音的原字符
System.out.println(HanLP.convertToPinyinString("截至2012年,", " ", true));
System.out.println(HanLP.convertToPinyinString("截至2012年,", " ", false));
}
}
原文,重,载,不,是,重,任,!,
拼音(数字音调),chong2,zai3,bu2,shi4,zhong4,ren4,none5,
拼音(符号音调),chóng,zǎi,bú,shì,zhòng,rèn,none,
拼音(无音调),chong,zai,bu,shi,zhong,ren,none,
声调,2,3,2,4,4,4,5,
声母,ch,z,b,sh,zh,r,none,
韵母,ong,ai,u,i,ong,en,none,
输入法头,ch,z,b,sh,zh,r,none,
jie zhi none none none none nian none
jie zhi 2 0 1 2 nian ,
流水线模式
/**
* 演示流水线模式,几个概念:
* - pipe:流水线的一节管道,执行统计分词或规则逻辑
* - flow:管道的数据流,在同名方法中执行本节管道的业务
* - pipeline:流水线,由至少一节管道(统计分词管道)构成,可自由调整管道的拼装方式
*
* @author hankcs
*/
public class DemoPipeline
{
private static final Pattern WEB_URL = Pattern.compile("((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?(?:(((([a-zA-Z0-9][a-zA-Z0-9\\-]*)*[a-zA-Z0-9]\\.)+((aero|arpa|asia|a[cdefgilmnoqrstuwxz])|(biz|b[abdefghijmnorstvwyz])|(cat|com|coop|c[acdfghiklmnoruvxyz])|d[ejkmoz]|(edu|e[cegrstu])|f[ijkmor]|(gov|g[abdefghilmnpqrstuwy])|h[kmnrtu]|(info|int|i[delmnoqrst])|(jobs|j[emop])|k[eghimnprwyz]|l[abcikrstuvy]|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])|(name|net|n[acefgilopruz])|(org|om)|(pro|p[aefghklmnrstwy])|qa|r[eosuw]|s[abcdeghijklmnortuvyz]|(tel|travel|t[cdfghjklmnoprtvwz])|u[agksyz]|v[aceginu]|w[fs]|(δοκιμή|испытание|рф|срб|טעסט|آزمایشی|إختبار|الاردن|الجزائر|السعودية|المغرب|امارات|بھارت|تونس|سورية|فلسطين|قطر|مصر|परीक्षा|भारत|ভারত|ਭਾਰਤ|ભારત|இந்தியா|இலங்கை|சிங்கப்பூர்|பரிட்சை|భారత్|ලංකා|ไทย|テスト|中国|中國|台湾|台灣|新加坡|测试|測試|香港|테스트|한국|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)|y[et]|z[amw]))|((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9]))))(?:\\:\\d{1,5})?)(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?");
private static final Pattern EMAIL = Pattern.compile("(\\w+(?:[-+.]\\w+)*)@(\\w+(?:[-.]\\w+)*\\.\\w+(?:[-.]\\w+)*)");
public static void main(String[] args) throws IOException
{
LexicalAnalyzerPipeline analyzer = new LexicalAnalyzerPipeline(new PerceptronLexicalAnalyzer());
// 管道顺序=优先级,自行调整管道顺序以控制优先级
analyzer.addFirst(new RegexRecognizePipe(WEB_URL, "【网址】"));
analyzer.addFirst(new RegexRecognizePipe(EMAIL, "【邮件】"));
analyzer.addLast(new Pipe<List<IWord>, List<IWord>>() // 自己写个管道也并非难事
{
@Override
public List<IWord> flow(List<IWord> input)
{
for (IWord word : input)
{
if ("nx".equals(word.getLabel()))
word.setLabel("字母");
}
return input;
}
});
String text = "HanLP的项目地址是https://github.com/hankcs/HanLP,联系邮箱[email protected]";
System.out.println(analyzer.analyze(text));
}
}
analyzer.addLast(new Pipe<List<IWord>, List<IWord>>() // 自己写个管道也并非难事
HanLP中的数据结构和接口是灵活的,组合这些接口,可以自己创造新功能
HanLP中的数据结构和接口是灵活的,组合这些接口,可以自己创造新功能
*
* @author hankcs
*/
public class DemoPinyinToChinese
{
public static void main(String[] args)
{
StringDictionary dictionary = new StringDictionary("=");
dictionary.load(HanLP.Config.PinyinDictionaryPath);
TreeMap<String, Set<String>> map = new TreeMap<String, Set<String>>();
for (Map.Entry<String, String> entry : dictionary.entrySet())
{
String pinyins = entry.getValue().replaceAll("[\\d,]", "");
Set<String> words = map.get(pinyins);
if (words == null)
{
words = new TreeSet<String>();
map.put(pinyins, words);
}
words.add(entry.getKey());
}
Set<String> words = new TreeSet<String>();
words.add("绿色");
words.add("滤色");
map.put("lvse", words);
// 1.5.2及以下版本
AhoCorasickDoubleArrayTrie<Set<String>> trie = new AhoCorasickDoubleArrayTrie<Set<String>>();
trie.build(map);
System.out.println(CommonAhoCorasickSegmentUtil.segment("renmenrenweiyalujiangbujianlvse", trie));
// 1.5.3及以上版本
CommonAhoCorasickDoubleArrayTrieSegment<Set<String>> segment = new CommonAhoCorasickDoubleArrayTrieSegment<Set<String>>(map);
System.out.println(segment.segment("renmenrenweiyalujiangbujianlvse"));
}
}
[renmen/[人们], renwei/[人为, 认为], yalujiang/[鸭绿江], bujian/[不见], lvse/[滤色, 绿色]]
[renmen/[人们], renwei/[人为, 认为], yalujiang/[鸭绿江], bujian/[不见], lvse/[滤色, 绿色]]
地名识别
Segment segment = HanLP.newSegment().enablePlaceRecognize(true);
- 目前标准分词器都默认关闭了地名识别,用户需要手动开启;这是因为消耗性能,其实多数地名都收录在核心词典和用户自定义词典中。
- 在生产环境中,能靠词典解决的问题就靠词典解决,这是最高效稳定的方法。
/**
* 地名识别
* @author hankcs
*/
public class DemoPlaceRecognition
{
public static void main(String[] args)
{
String[] testCase = new String[]{
"蓝翔给宁夏固原市彭阳县红河镇黑牛沟村捐赠了挖掘机",
};
Segment segment = HanLP.newSegment().enablePlaceRecognize(true);
for (String sentence : testCase)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
}
[蓝翔/nr, 给/p, 宁夏/ns, 固原市/ns, 彭阳县/ns, 红河镇/ns, 黑牛沟村/ns, 捐赠/v, 了/ule, 挖掘机/n]
词性标注
词性标注(Part-of-Speech tagging 或POS tagging),又称词类标注或者简称标注,是指为分词结果中的每个单词标注一个正确的词性的程序,也即确定每个词是名词、动词、形容词或其他词性的过程。在汉语中,词性标注比较简单,因为汉语词汇词性多变的情况比较少见,大多词语只有一个词性,或者出现频次最高的词性远远高于第二位的词性。据说,只需选取最高频词性,即可实现80%准确率的中文词性标注程序。
利用HMM即可实现更高准确率的词性标注,
/**
* 词性标注
* @author hankcs
*/
public class DemoPosTagging
{
public static void main(String[] args)
{
String text = "教授正在教授自然语言处理课程";
Segment segment = HanLP.newSegment();
System.out.println("未标注:" + segment.seg(text));
segment.enablePartOfSpeechTagging(true);
System.out.println("标注后:" + segment.seg(text));
}
}
未标注:[教授/nnt, 正在/d, 教授/nnt, 自然语言处理/nz, 课程/n]
标注后:[教授/nnt, 正在/d, 教授/v, 自然语言处理/nz, 课程/n]
改写句子
public class DemoRewriteText
{
public static void main(String[] args)
{
String text = "这个方法可以利用同义词词典将一段文本改写成意思相似的另一段文本,而且差不多符合语法";
System.out.println(CoreSynonymDictionary.rewrite(text));
}
}
这措施可以使用同义词词典将同截文本改写成意思相似的别样一样段文本,而且多符合语法
标准分词
HanLP中有一系列“开箱即用”的静态分词器,以Tokenizer
结尾,HanLP.segment
其实是对StandardTokenizer.segment
的包装
/**
* 标准分词
*
* @author hankcs
*/
public class DemoSegment
{
public static void main(String[] args)
{
String[] testCase = new String[]{
"商品和服务",
"当下雨天地面积水分外严重",
"结婚的和尚未结婚的确实在干扰分词啊",
"买水果然后来世博园最后去世博会",
"中国的首都是北京",
"欢迎新老师生前来就餐",
"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作",
"随着页游兴起到现在的页游繁盛,依赖于存档进行逻辑判断的设计减少了,但这块也不能完全忽略掉。",
};
for (String sentence : testCase)
{
List<Term> termList = HanLP.segment(sentence);
System.out.println(termList);
}
}
}
[商品/n, 和/cc, 服务/vn]
[当/p, 下雨天/n, 地面/n, 积水/n, 分外/d, 严重/a]
[结婚/vi, 的/ude1, 和/cc, 尚未/d, 结婚/vi, 的/ude1, 确实/ad, 在/p, 干扰/vn, 分词/n, 啊/y]
[买/v, 水果/n, 然后/c, 来/vf, 世博园/n, 最后/f, 去/vf, 世博会/n]
[中国/ns, 的/ude1, 首都/n, 是/vshi, 北京/ns]
[欢迎/v, 新/a, 老/a, 师生/n, 前来/vi, 就餐/vi]
[工信处/n, 女/b, 干事/nnt, 每月/t, 经过/p, 下属/v, 科室/n, 都/d, 要/v, 亲口/d, 交代/v, 24/m, 口/n, 交换机/n, 等/udeng, 技术性/n, 器件/n, 的/ude1, 安装/v, 工作/vn]
[随着/p, 页游/nz, 兴起/v, 到/v, 现在/t, 的/ude1, 页游/nz, 繁盛/a, ,/w, 依赖于/v, 存档/vi, 进行/vn, 逻辑/n, 判断/v, 的/ude1, 设计/vn, 减少/v, 了/ule, ,/w, 但/c, 这/rzv, 块/q, 也/d, 不能/v, 完全/ad, 忽略/v, 掉/v, 。/w]
第一个demo,演示文本分类最基本的调用方式
/**
* 第一个demo,演示文本分类最基本的调用方式
*
* @author hankcs
*/
public class DemoSentimentAnalysis
{
/**
* 中文情感挖掘语料-ChnSentiCorp 谭松波
*/
public static final String CORPUS_FOLDER = TestUtility.ensureTestData("ChnSentiCorp情感分析酒店评论", "http://file.hankcs.com/corpus/ChnSentiCorp.zip");
public static void main(String[] args) throws IOException
{
IClassifier classifier = new NaiveBayesClassifier(); // 创建分类器,更高级的功能请参考IClassifier的接口定义
classifier.train(CORPUS_FOLDER); // 训练后的模型支持持久化,下次就不必训练了
predict(classifier, "前台客房服务态度非常好!早餐很丰富,房价很干净。再接再厉!");
predict(classifier, "结果大失所望,灯光昏暗,空间极其狭小,床垫质量恶劣,房间还伴着一股霉味。");
predict(classifier, "可利用文本分类实现情感分析,效果还行");
}
private static void predict(IClassifier classifier, String text)
{
System.out.printf("《%s》 情感极性是 【%s】\n", text, classifier.classify(text));
}
static
{
File corpusFolder = new File(CORPUS_FOLDER);
if (!corpusFolder.exists() || !corpusFolder.isDirectory())
{
System.err.println("没有文本分类语料,请阅读IClassifier.train(java.lang.String)中定义的语料格式、准备语料");
System.exit(1);
}
}
}
模式:训练集
文本编码:UTF-8
根目录:C:\Users\12249\Desktop\HanLP-1.x\data\test\ChnSentiCorp情感分析酒店评论
加载中...
[正面]...100.00% 2000 篇文档
[负面]...100.00% 226 篇文档
耗时 6973 ms 加载了 2 个类目,共 2226 篇文档
原始数据集大小:2226
使用卡方检测选择特征中...耗时 25 ms,选中特征数:511 / 9049 = 5.65%
贝叶斯统计结束
《前台客房服务态度非常好!早餐很丰富,房价很干净。再接再厉!》 情感极性是 【正面】
《结果大失所望,灯光昏暗,空间极其狭小,床垫质量恶劣,房间还伴着一股霉味。》 情感极性是 【负面】
《可利用文本分类实现情感分析,效果还行》 情感极性是 【正面】
如何去除停用词
停用词是指在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为Stop Words(停用词)。这些停用词都是人工输入、非自动化生成的,生成后的停用词会形成一个停用词表。
**
* 演示如何去除停用词
*
* @author hankcs
*/
public class DemoStopWord
{
public static void main(String[] args)
{
String text = "小区居民有的反对喂养流浪猫,而有的居民却赞成喂养这些小宝贝";
// 可以动态修改停用词词典
CoreStopWordDictionary.add("居民");
System.out.println(NotionalTokenizer.segment(text));
CoreStopWordDictionary.remove("居民");
System.out.println(NotionalTokenizer.segment(text));
// 可以对任意分词器的结果执行过滤
List<Term> termList = BasicTokenizer.segment(text);
System.out.println(termList);
CoreStopWordDictionary.apply(termList);
System.out.println(termList);
// 还可以自定义过滤逻辑
CoreStopWordDictionary.FILTER = new Filter()
{
@Override
public boolean shouldInclude(Term term)
{
if (term.nature == nz)
{
return !CoreStopWordDictionary.contains(term.word);
}
return false;
}
};
System.out.println(NotionalTokenizer.segment(text));
}
}
[小区/n, 反对/v, 喂养/v, 流浪猫/nz, 赞成/v, 喂养/v, 小宝贝/nz]
[小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
[小区/n, 居民/n, 有/vyou, 的/ude1, 反对/v, 喂养/v, 流浪猫/nz, ,/w, 而/cc, 有的/rz, 居民/n, 却/d, 赞成/v, 喂养/v, 这些/rz, 小宝贝/nz]
[小区/n, 居民/n, 反对/v, 喂养/v, 流浪猫/nz, 居民/n, 赞成/v, 喂养/v, 小宝贝/nz]
[流浪猫/nz, 小宝贝/nz]
文本推荐
(句子级别,从一系列句子中挑出与输入句子最相似的那一个)
- 在搜索引擎的输入框中,用户输入一个词,搜索引擎会联想出最合适的搜索词,HanLP实现了类似的功能。
- 可以动态调节每种识别器的权重
**
* 文本推荐(句子级别,从一系列句子中挑出与输入句子最相似的那一个)
* @author hankcs
*/
public class DemoSuggester
{
public static void main(String[] args)
{
Suggester suggester = new Suggester();
String[] titleArray =
(
"威廉王子发表演说 呼吁保护野生动物\n" +
"魅惑天后许佳慧不爱“预谋” 独唱《许某某》\n" +
"“黑格比”横扫菲:菲吸取“海燕”经验及早疏散\n" +
"日本保密法将正式生效 日媒指其损害国民知情权\n" +
"英报告说空气污染带来“公共健康危机”"
).split("\\n");
for (String title : titleArray)
{
suggester.addSentence(title);
}
System.out.println(suggester.suggest("陈述", 2)); // 语义
System.out.println(suggester.suggest("危机", 1)); // 字符
System.out.println(suggester.suggest("mayun", 1)); // 拼音
System.out.println(suggester.suggest("徐家汇", 1)); // 拼音
}
}
[威廉王子发表演说 呼吁保护野生动物, 英报告说空气污染带来“公共健康危机”]
[英报告说空气污染带来“公共健康危机”]
[魅惑天后许佳慧不爱“预谋” 独唱《许某某》]
自动摘要
内部采用TextRankSentence
实现,用户可以直接调用TextRankSentence.getTopSentenceList(document, size)
。
**
* 自动摘要
* @author hankcs
*/
public class DemoSummary
{
public static void main(String[] args)
{
String document = "水利部水资源司司长陈明忠9月29日在国务院新闻办举行的新闻发布会上透露," +
"根据刚刚完成了水资源管理制度的考核,有部分省接近了红线的指标," +
"有部分省超过红线的指标。对一些超过红线的地方,陈明忠表示,对一些取用水项目进行区域的限批," +
"严格地进行水资源论证和取水许可的批准。";
List<String> sentenceList = HanLP.extractSummary(document, 3);
System.out.println(sentenceList);
}
}
[严格地进行水资源论证和取水许可的批准, 有部分省超过红线的指标, 水利部水资源司司长陈明忠9月29日在国务院新闻办举行的新闻发布会上透露]
文本分类最基本的调用方式
**
* 第一个demo,演示文本分类最基本的调用方式
*
* @author hankcs
*/
public class DemoTextClassification
{
/**
* 搜狗文本分类语料库5个类目,每个类目下1000篇文章,共计5000篇文章
*/
public static final String CORPUS_FOLDER = TestUtility.ensureTestData("搜狗文本分类语料库迷你版", "http://file.hankcs.com/corpus/sogou-text-classification-corpus-mini.zip");
/**
* 模型保存路径
*/
public static final String MODEL_PATH = "data/test/classification-model.ser";
public static void main(String[] args) throws IOException
{
IClassifier classifier = new NaiveBayesClassifier(trainOrLoadModel());
predict(classifier, "C罗获2018环球足球奖最佳球员 德尚荣膺最佳教练");
predict(classifier, "英国造航母耗时8年仍未服役 被中国速度远远甩在身后");
predict(classifier, "研究生考录模式亟待进一步专业化");
predict(classifier, "如果真想用食物解压,建议可以食用燕麦");
predict(classifier, "通用及其部分竞争对手目前正在考虑解决库存问题");
}
private static void predict(IClassifier classifier, String text)
{
System.out.printf("《%s》 属于分类 【%s】\n", text, classifier.classify(text));
}
private static NaiveBayesModel trainOrLoadModel() throws IOException
{
NaiveBayesModel model = (NaiveBayesModel) IOUtil.readObjectFrom(MODEL_PATH);
if (model != null) return model;
File corpusFolder = new File(CORPUS_FOLDER);
if (!corpusFolder.exists() || !corpusFolder.isDirectory())
{
System.err.println("没有文本分类语料,请阅读IClassifier.train(java.lang.String)中定义的语料格式与语料下载:" +
"https://github.com/hankcs/HanLP/wiki/%E6%96%87%E6%9C%AC%E5%88%86%E7%B1%BB%E4%B8%8E%E6%83%85%E6%84%9F%E5%88%86%E6%9E%90");
System.exit(1);
}
IClassifier classifier = new NaiveBayesClassifier(); // 创建分类器,更高级的功能请参考IClassifier的接口定义
classifier.train(CORPUS_FOLDER); // 训练后的模型支持持久化,下次就不必训练了
model = (NaiveBayesModel) classifier.getModel();
IOUtil.saveObjectTo(model, MODEL_PATH);
return model;
}
}
没测:
聚类数量匹配
public class DemoTextClustering
{
public static void main(String[] args)
{
ClusterAnalyzer<String> analyzer = new ClusterAnalyzer<String>();
analyzer.addDocument("赵一", "流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 流行, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 蓝调, 摇滚, 摇滚, 摇滚, 摇滚");
analyzer.addDocument("钱二", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲");
analyzer.addDocument("张三", "古典, 古典, 古典, 古典, 民谣, 民谣, 民谣, 民谣");
analyzer.addDocument("李四", "爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 爵士, 金属, 金属, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲, 舞曲");
analyzer.addDocument("王五", "流行, 流行, 流行, 流行, 摇滚, 摇滚, 摇滚, 嘻哈, 嘻哈, 嘻哈");
analyzer.addDocument("马六", "古典, 古典, 古典, 古典, 古典, 古典, 古典, 古典, 摇滚");
System.out.println(analyzer.kmeans(3));
System.out.println(analyzer.repeatedBisection(3));
System.out.println(analyzer.repeatedBisection(1.0)); // 自动判断聚类数量k
}
}
[[张三, 马六], [李四, 钱二], [王五, 赵一]]
[[李四, 钱二], [王五, 赵一], [马六, 张三]]
[[李四, 钱二], [王五, 赵一], [张三, 马六]]
基于AhoCorasickDoubleArrayTrie的分词器
/**
* 基于AhoCorasickDoubleArrayTrie的分词器,该分词器允许用户跳过核心词典,直接使用自己的词典。
* 需要注意的是,自己的词典必须遵守HanLP词典格式。
*
* @author hankcs
*/
public class DemoUseAhoCorasickDoubleArrayTrieSegment
{
public static void main(String[] args) throws IOException
{
// AhoCorasickDoubleArrayTrieSegment要求用户必须提供自己的词典路径
AhoCorasickDoubleArrayTrieSegment segment = new AhoCorasickDoubleArrayTrieSegment(HanLP.Config.CustomDictionaryPath[0]);
System.out.println(segment.seg("微观经济学继续教育循环经济"));
}
}
[微观经济学/null, 继续教育/null, 循环经济/null]
URL识别
/**
* 演示URL识别
*
* @author hankcs
*/
public class DemoURLRecognition
{
public static void main(String[] args)
{
String text =
"HanLP的项目地址是https://github.com/hankcs/HanLP," +
"发布地址是https://github.com/hankcs/HanLP/releases," +
"我有时候会在www.hankcs.com上面发布一些消息," +
"我的微博是http://weibo.com/hankcs/,会同步推送hankcs.com的新闻。"
;
List<Term> termList = URLTokenizer.segment(text);
System.out.println(termList);
for (Term term : termList)
{
if (term.nature == Nature.xu)
System.out.println(term.word);
}
}
}
[HanLP/nx, 的/ude1, 项目/n, 地址/n, 是/vshi, https://github.com/hankcs/HanLP/xu, ,/w, 发布/v, 地址/n, 是/vshi, https://github.com/hankcs/HanLP/releases/xu, ,/w, 我/rr, 有时候/d, 会/v, 在/p, www/nx, ./w, hankcs/nrf, ./w, com/nx, 上面/f, 发布/v, 一些/m, 消息/n, ,/w, 我/rr, 的/ude1, 微博/n, 是/vshi, http://weibo.com/hankcs/xu, //w, ,/w, 会/v, 同步/vd, 推送/nz, hankcs/nrf, ./w, com/nx, 的/ude1, 新闻/n, 。/w, 听说/v, ./w, 中国/ns, 域名/n, 开放/v, 申请/v, 了/ule, ,/w, 但/c, 我/rr, 并/cc, 没有/v, 申请/v, hankcs/nrf, ./w, 中国/ns, ,/w, 因为/c, 穷/a, ……/w]
https://github.com/hankcs/HanLP
https://github.com/hankcs/HanLP/releases
http://weibo.com/hankcs
音译人名识别
目前分词器基本上都默认开启了音译人名识别,用户不必手动开启;上面的代码只是为了强调。
**
* 音译人名识别
* @author hankcs
*/
public class DemoTranslatedNameRecognition
{
public static void main(String[] args)
{
String[] testCase = new String[]{
"一桶冰水当头倒下,微软的比尔盖茨、Facebook的扎克伯格跟桑德博格、亚马逊的贝索斯、苹果的库克全都不惜湿身入镜,这些硅谷的科技人,飞蛾扑火似地牺牲演出,其实全为了慈善。",
"世界上最长的姓名是简森·乔伊·亚历山大·比基·卡利斯勒·达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿。",
};
Segment segment = HanLP.newSegment().enableTranslatedNameRecognize(true);
for (String sentence : testCase)
{
List<Term> termList = segment.seg(sentence);
System.out.println(termList);
}
}
}
[一桶/nz, 冰水/n, 当头/vi, 倒下/v, ,/w, 微软/ntc, 的/ude1, 比尔盖茨/nrf, 、/w, Facebook/nx, 的/ude1, 扎克伯格/nrf, 跟/p, 桑德博格/nrf, 、/w, 亚马逊/nrf, 的/ude1, 贝索斯/nrf, 、/w, 苹果/nf, 的/ude1, 库克/nrf, 全都/d, 不惜/v, 湿身/nz, 入镜/nz, ,/w, 这些/rz, 硅谷/ns, 的/ude1, 科技/n, 人/n, ,/w, 飞蛾/n, 扑火/vn, 似/vg, 地/ude2, 牺牲/v, 演出/vn, ,/w, 其实/d, 全/a, 为了/p, 慈善/a, 。/w]
[世界/n, 上/f, 最长/d, 的/ude1, 姓名/n, 是/vshi, 简森/nr, ·/w, 乔伊/nr, ·/w, 亚历山大/ns, ·/w, 比/p, 基/ng, ·/w, 卡利/nz, 斯/b, 勒/v, ·/w, 达夫·埃利奥特·福克斯·伊维鲁莫·马尔尼·梅尔斯·帕特森·汤普森·华莱士·普雷斯顿/nrf, 。/w]
繁体中文分词
**
* 繁体中文分词
*
* @author hankcs
*/
public class DemoTraditionalChineseSegment
{
public static void main(String[] args)
{
List<Term> termList = TraditionalChineseTokenizer.segment("大衛貝克漢不僅僅是名著名球員,球場以外,其妻為前" +
"辣妹合唱團成員維多利亞·碧咸,亦由於他擁有" +
"突出外表、百變髮型及正面的形象,以至自己" +
"品牌的男士香水等商品,及長期擔任運動品牌" +
"Adidas的代言人,因此對大眾傳播媒介和時尚界" +
"等方面都具很大的影響力,在足球圈外所獲得的" +
"認受程度可謂前所未見。");
System.out.println(termList);
termList = TraditionalChineseTokenizer.segment("(中央社記者黃巧雯台北20日電)外需不振,影響接單動能,經濟部今天公布7月外銷訂單金額362.9億美元,年減5%," +
"連續4個月衰退,減幅較6月縮小。1040820\n");
System.out.println(termList);
termList = TraditionalChineseTokenizer.segment("中央社记者黄巧雯台北20日电");
System.out.println(termList);
}
}
[大衛貝克漢/nrf, 不僅僅/d, 是/vshi, 名/q, 著名/a, 球員/nnt, ,/w, 球場/n, 以外/f, ,/w, 其/rz, 妻/ng, 為/p, 前/f, 辣妹/nz, 合唱團/nis, 成員/nnt, 維多利亞/nsf, ·/w, 碧/ag, 咸/a, ,/w, 亦/d, 由於/p, 他/rr, 擁有/v, 突出/a, 外表/n, 、/w, 百變/nz, 髮型/n, 及/cc, 正面/b, 的/ude1, 形象/n, ,/w, 以至/c, 自己/rr, 品牌/n, 的/ude1, 男士/n, 香水/n, 等/udeng, 商品/n, ,/w, 及/cc, 長期/d, 擔任/v, 運動/vn, 品牌/n, Adidas/nx, 的/ude1, 代言人/nnt, ,/w, 因此/c, 對/p, 大眾/ntc, 傳播/vn, 媒介/n, 和/cc, 時尚界/nz, 等/udeng, 方面/n, 都/d, 具/vg, 很大/d, 的/ude1, 影響力/n, ,/w, 在/p, 足球/n, 圈外/nz, 所/usuo, 獲得/v, 的/ude1, 認/v, 受/v, 程度/n, 可謂/v, 前所未見/n, 。/w]
[(/w, 中央社/nz, 記者/nnt, 黃巧雯/nr, 台北/ns, 20/m, 日/b, 電/n, )/w, 外需/n, 不/d, 振/vg, ,/w, 影響/vn, 接單/nz, 動能/n, ,/w, 經濟部/nis, 今天/t, 公布/v, 7月/t, 外銷/v, 訂單/n, 金額/n, 362.9/m, 億/m, 美元/q, ,/w, 年/qt, 減/v, 5/m, %/nx, ,/w, 連續/a, 4/m, 個/q, 月/n, 衰退/vi, ,/w, 減幅/n, 較/d, 6月/t, 縮小/v, 。/w, 1040820/m]
[中央社/nz, 记者/nnt, 黄巧雯/nr, 台北/ns, 20/m, 日/b, 电/n]
「以後等你當上皇后,就能買草莓慶祝了」。發現一根白頭髮
凭借笔记本电脑写程序HanLP
hankcs在臺灣寫程式碼
hankcs在台湾写代码
hankcs在香港寫代碼
hankcs在香港写代码
hankcs在臺灣寫程式碼
hankcs在香港寫代碼
hankcs在臺灣寫程式碼
hankcs在台灣寫代碼
hankcs在臺灣寫代碼
hankcs在臺灣寫代碼
动态设置预置分词器,这里的设置是全局的
/**
* 演示动态设置预置分词器,这里的设置是全局的
* @author hankcs
*/
public class DemoTokenizerConfig
{
public static void main(String[] args)
{
String text = "泽田依子是上外日本文化经济学院的外教";
System.out.println(StandardTokenizer.segment(text));
StandardTokenizer.SEGMENT.enableAllNamedEntityRecognize(true);
System.out.println(StandardTokenizer.segment(text));
}
}
[泽田依/nr, 子/ng, 是/vshi, 上外/nit, 日本/ns, 文化/n, 经济学院/nit, 的/ude1, 外教/n]
[泽田依子/nrj, 是/vshi, 上外日本文化经济学院/nt, 的/ude1, 外教/n]
语义距离
设想的应用场景是搜索引擎对词义的理解,词与词并不只存在“同义词”与“非同义词”的关系,就算是同义词,它们之间的意义也是有微妙的差别的。
/**
* 语义距离
* @author hankcs
*/
public class DemoWordDistance
{
public static void main(String[] args)
{
String[] wordArray = new String[]
{
"香蕉",
"苹果",
"白菜",
"水果",
"蔬菜",
"自行车",
"公交车",
"飞机",
"买",
"卖",
"购入",
"新年",
"春节",
"丢失",
"补办",
"办理",
"送给",
"寻找",
"孩子",
"教室",
"教师",
"会计",
};
for (String a : wordArray)
{
for (String b : wordArray)
{
System.out.println(a + "\t" + b + "\t之间的距离是\t" + CoreSynonymDictionary.distance(a, b));
}
}
}
}
香蕉 香蕉 之间的距离是 0
香蕉 苹果 之间的距离是 19980
香蕉 白菜 之间的距离是 2628369
香蕉 水果 之间的距离是 32967
香蕉 蔬菜 之间的距离是 2630367
香蕉 自行车 之间的距离是 1854515628
香蕉 公交车 之间的距离是 1854535619
香蕉 飞机 之间的距离是 1857307833
香蕉 买 之间的距离是 39729797433
香蕉 卖 之间的距离是 39729897333
香蕉 购入 之间的距离是 39729797433
香蕉 新年 之间的距离是 4981789224
。。。。。。。。。。。。。。
依存句法解析
- 内部采用
NeuralNetworkDependencyParser
实现,用户可以直接调用NeuralNetworkDependencyParser.compute(sentence)
- 也可以调用基于MaxEnt的依存句法分析器
MaxEntDependencyParser.compute(sentence)
/**
* 依存句法分析(神经网络句法模型需要-Xms1g -Xmx1g -Xmn512m)
* @author hankcs
*/
public class DemoDependencyParser
{
public static void main(String[] args)
{
CoNLLSentence sentence = HanLP.parseDependency("徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。");
System.out.println(sentence);
// 可以方便地遍历它
for (CoNLLWord word : sentence)
{
System.out.printf("%s --(%s)--> %s\n", word.LEMMA, word.DEPREL, word.HEAD.LEMMA);
}
// 也可以直接拿到数组,任意顺序或逆序遍历
CoNLLWord[] wordArray = sentence.getWordArray();
for (int i = wordArray.length - 1; i >= 0; i--)
{
CoNLLWord word = wordArray[i];
System.out.printf("%s --(%s)--> %s\n", word.LEMMA, word.DEPREL, word.HEAD.LEMMA);
}
// 还可以直接遍历子树,从某棵子树的某个节点一路遍历到虚根
CoNLLWord head = wordArray[12];
while ((head = head.HEAD) != null)
{
if (head == CoNLLWord.ROOT) System.out.println(head.LEMMA);
else System.out.printf("%s --(%s)--> ", head.LEMMA, head.DEPREL);
}
}
}
实战:
对一个字符串进行格式化输出,把一份目录以指定的格式输出。
思路,结合分词工具,利用词性的组成,控制格式。
package testdemo;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.suggest.Suggester;
import java.util.Arrays;
import java.util.List;
/**
* @Description : 对一个字符串进行格式化输出
* @Author: Liruilong
* @Date: 2020/3/25 16:10
*/
public class TwoTopic {
static String sentence = "第一节 概览\n" +
"一、发行人基本情况\n" +
"1)基本情况\n" +
"2)业务说明\n" +
"二、发行人股东、董监高情况\n" +
"(1)、股东简历\n" +
"(2)、监事简历\n" +
"(3)、高管简历\n" +
"第二节 业务与技术\n" +
"1、公司基本业务\n" +
"(1)、业务介绍\n" +
"(2)、竞争情况\n" +
"2、公司技术实力\n" +
"(1)、业务介绍";
public static void main(String[] args) {
twoTopic(sentence);
}
public static void twoTopic(String sentence ) {
Suggester suggester = new Suggester();
String[] strings = sentence.split("\n");
StringBuilder stringBuilder = new StringBuilder();
Arrays.stream(strings).forEach(o -> {
List<Term> stermList = HanLP.segment(o);
String s = stermList.stream()
.map(o1 -> o1.nature != null ? o1.nature.toString() : "空")
.reduce("", (a, b) -> a + b);
if (s.indexOf("mqnwn") != -1) {
stringBuilder.append(o.trim() + "\n");
} else if (s.indexOf("mwn") != -1 && o.indexOf(")") == -1) {
stringBuilder.append(" " + o.trim() + "\n");
} else {
stringBuilder.append(" " + o.trim() + "\n");
}
}
);
System.out.println(stringBuilder);
}
}
抽取文本中的连续日期
也是利用词性分析,获取分词(年份和标点符号),之后根据需求格式化
package testdemo;
import com.hankcs.hanlp.seg.NShort.NShortSegment;
import com.hankcs.hanlp.seg.Segment;
import com.hankcs.hanlp.seg.common.Term;
import java.util.List;
/**
* @Author Liruilong
* @Description 抽取文本中的连续日期
* @Date 0:59 2020/3/26
* @Param
* @return
**/
public class ThreeTopic {
static String sentence = "报告期内,即2014年、2015年、2016年公司净利润为2000.00万元、3000.00万元、5000.00万元。";
public static void main(String[] args) {
threeTopic(sentence);
}
public static void threeTopic(String sentence) {
Segment nShortSegment = new NShortSegment().enableCustomDictionary(false)
.enablePlaceRecognize(true).enableOrganizationRecognize(true);
List<Term> stermList = nShortSegment.seg(sentence);
String s = stermList.stream().filter(o -> {
String nature = o.nature != null ? o.nature.toString() : "空";
String word = o.word != null ? o.word.toString() : "空";
switch (nature) {
case "t":
return true;
case "w":
return "、".equals(word) ? true : false;
default:
return false;
}
}
).map(o -> o.word).reduce("", (a, b) -> a + b);
System.out.println(s.substring(0, s.lastIndexOf("年") + 1));
}
}
将日期区间转换成连续日期
根据词性获取时间差和逗号后面的文本,将时间差加工格式化为指定格式,与逗号后面文本拼接
package testdemo;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description :将日期区间转换成连续日期
* @Author: Liruilong
* @Date: 2020/3/25 21:35
*/
public class FourTopic {
static String[] strings = new String[]{"2014年至2016年,公司基本情况","2014至2016年,公司基本情况"
,"2014年-2016年,公司基本情况","2014-2016年,公司基本情况"};
public static void main(String[] args) {
for (String sentence : strings) {
fourTopic(sentence);
}
}
public static void fourTopic(String sentence) {
List<Term> stermList = HanLP.segment(sentence);
StringBuffer stringBuffer = new StringBuffer();
int index = 0;
for (int i = 0; i < stermList.size(); i++) {
if ((stermList.get(i).nature != null ? stermList.get(i).nature.toString() : "空").equals("w")){
index = i;
break;
}
}
for (int i = index; i < stermList.size(); i++) {
stringBuffer.append((stermList.get(i).word != null ? stermList.get(i).word.toString() : "空"));
}
List<LocalDate> localDate = stermList.stream()
.filter(o ->
(o.nature != null ? o.nature.toString() : "空").equals("m"))
.map(o -> {
return LocalDate.of(Integer.parseInt(o.word.toString()), 1, 1);
}).collect(Collectors.toList());
StringBuilder yer = new StringBuilder();
for (int i = (localDate.get(0)).getYear(); i <= localDate.get(1).getYear()
; localDate.set(0, localDate.get(0).withYear(i + 1)), i++) {
yer.append(DateTimeFormatter.ofPattern("yyyy年、").format(localDate.get(0)).toString());
}
System.out.println(yer.substring(0, yer.lastIndexOf("、"))+ stringBuffer.toString());
}
}
HanLP词性标注集
—————————