Gradle +HanLP +SpringBoot 构建关键词提取,摘要提取 。入门篇
前段时间,领导要求出一个关键字提取的微服务,要求轻量级。
对于没写过微服务的一个小白来讲。硬着头皮上也不能说不会啊。
首先了解下公司目前的架构体系,发现并不是分布式开发,只能算是分模块部署。然后我需要写个Boot的服务,对外提供一个接口就行。
在上网浏览了下分词概念后,然后我选择了Gradle & HanLP & SpringBoot & JDK1.8 & tomcat8 & IDEA工具来实现。
Gradle 我也是第一次听说,和Maven一样,可以很快捷的管理项目需要的jar。下载,解压,配置环境变量,验证等。不再赘述,可以去这里了解下https://www.w3cschool.cn/gradle/ctgm1htw.html
然后准备就绪后,在idea里配置一下Gradle路径

HanLP呢,老规矩,先下载,解压,https://github.com/hankcs/HanLP/releases 。简单看下目录结构
HanLP分为词典 和模型,其中词典(dictionary)是词法分析必备,模型(model)是句法分析必需。解压好准备data的上级目录 的绝对路径 下面会提到用途。
这里为G:/kaipu/data-for-1.7.3

tomcat8 去官网自行下载,选择自己操作系统对应的。 jdk1.8 下载安装,环境变量配置不再描述。
一切准备就绪,开始创建项目

输入项目ID:keyWord,NEXT

选择本地的gradle

Next ,Finish

此刻项目就创建好了。
打开根目录下的
dependencies {
compile 'org.springframework.boot:spring-boot:2.0.5.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-web:2.0.5.RELEASE'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat:2.0.5.RELEASE'
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'com.hankcs:hanlp:portable-1.7.3'
}
前三个jar 是集成springboot外部tomcat用的,第四个是junit单元测试依赖,第五个就是我们要用的hanlp依赖。
PS:这里我着重说下打包的事情,因为我没用过Gradle打包,项目时间紧,我就延用了war包格式,这里先记录下过程,这个项目后,再回头来研究Gradle打JAR包。
group 'keyword'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'war'
war {
archiveName 'key-word.war'
}
等待idea自动导包完成后,我们来加载hanlp

在resources下,出现一个hanlp.properties,打开编辑这个文件,更改root路径。这个路径就是上面我们提到的 data上级目录的绝对路径。

创建第一个测试类
@Test
public void test0(){
String text = "中国是世界上的经济大国,社会主义强国!";
List<Term> keyWords = HanLP.segment(text);
System.out.println(keyWords.toString());
}

说明词库引入成功。
接下来可以正常开发,按照需求,需要提取正文里的关键词,摘要。
分析如下:
关键词抽取工具:
思路:输入标题,正文文本,综合考虑词频、位置、词性、组合性短语长度等因素,计算权重得分;返回topN个关键词。在此基础上进行抽取式摘要,按句子包含的关键词数量和权重进行处理。
原理:分词后,将命名实体单独拿到,再找合适的名词短语。依托HanLP的核心词典,根据TF*IDF算法计算每个命名实体和名词性短语的得分score,按score倒排返回前面若干关键词。
首先定义一个关键词类。可以是个单词,也可以是几个单词组成的短语。
public class Phrase implements Comparable<Phrase>{
private String word; //候选关键词
private boolean inDictionary; //是否在HanLP词典中
private String suffixWord; //中心词。对单词,中心词就是word。如果是短语,则是短语中最后一词
private String suffixWordPos; //中心词的词性
private String prefixWordPos; //首词的词性
private int freqOfDict = 1; //在词典中该词的词频
private boolean single; //true表示单词,false表示短语
private Location location; //该候选关键词出现的位置
private int offset; //该候选词在正文中位置
private boolean isCandidate; //是否候选关键词
*
*
*
/**
*这会在比较score时用到
*/
@Override
public int compareTo(Phrase o) {
return this.getWord().compareTo(o.getWord());
}
}
以下是核心算法的一部分,寻找候选关键词
/**全局逻辑
先找出关键词,再计算分数
一、
* 从分词Term中解析 候选的关键词,
* 因为要计算每个句子的分数值需要句子里的所有词累加计算score,所以这里都需要打标分数。
* @param terms 分词列表
* @param title 文章标题
* @param firstParagraphEnd 对于正文处理,表示正文第一段结尾位置
* @param lastParagraphBegin 对于正文处理,表示正文最后一段开始位置
*/
int index= 0;
while (index < terms.size()) {
//当前词
Term current = terms.get(index);
//首先判断是否是命名实体 人名开头 包含 地名 动名词 其他专名,或者团体名开头
if (current.nature.startsWith("nr")
|| current.nature.equals(Nature.ns)
|| current.nature.equals(Nature.nz)
|| current.nature.equals(Nature.vn)
|| current.nature.startsWith("nt")) {
Phrase phrase = new Phrase(current.word,
CoreDictionary.contains(current.word),
current.word,
current.nature.toString(),
current.nature.toString(),
CoreDictionary.get(current.word) == null ? 100 : CoreDictionary.get(current.word).totalFrequency,
true,
title.contains(current.word) ? Location.TITLE : whichLocation(terms.get(index).offset, firstParagraphEnd, lastParagraphBegin),
terms.get(index).offset, true);
phrases.add(phrase);
index++;
}
二、
//计算候选关键词的权重
TreeMap<Phrase, Double> scoreMap = new TreeMap<>();
for (Phrase phrase : phrases) {
double score = scoreMap.containsKey(phrase) ? scoreMap.get(phrase) : 0.0;
String phraseString = phrase.getWord().toString();
int wordLength = phraseString.length();
//排除单字符 的关键字,不进行输出
if (score == 0.0 && wordLength != 1) {
//weight方法:根据词性、词频、位置、词语长度等因素计算权重
//这里依赖HanLp核心词典 和自定义词典 实现TF*IDF算法 if (title.contains(phrase.getWord())) {
phrase.setFreqOfDict(phrase.getFreqOfDict() / 2);
}
if (phrase.getPrefixWordPos().startsWith("v") && phrase.getWord().contains("的")) {
phrase.setFreqOfDict(phrase.getFreqOfDict() * 1000);
}
score += Math.log(weight(phrase, content, title) * freqMap.get(phrase.getWord()) / phrase.getFreqOfDict());
scoreMap.put(phrase, score);
} }
三、 倒排
Comparator<Map.Entry<Phrase, Double>> valueComparator = new Comparator<Map.Entry<Phrase, Double>>() {
@Override
public int compare(Map.Entry<Phrase, Double> o1,
Map.Entry<Phrase, Double> o2) {
return (o2.getValue().compareTo(o1.getValue()));
}
};
List<Map.Entry<Phrase, Double>> list = new ArrayList<Map.Entry<Phrase, Double>>(scoreMap.entrySet());
Collections.sort(list, valueComparator);
此刻就可利用subList函数 取出tonN的关键词了
subList(0, Math.min(topN, 候选关键词数组size())); 四、
在三的基础上,对正文按标点符号 。?!;.!? 等进行分句。
List<Sentence> sentences = new ArrayList<>();
int i = 0;
int lastSentenceEnd = 0;
while (i <= content.length() - 1) {
char c = content.charAt(i);
if (SENTENCE_END_TAGS.indexOf(c) >= 0
&& i > lastSentenceEnd + 1
&& i > 0
&& i < content.length()) {
Location location = Location.MIDDLE;
if (i < firstParagraphEnd) {
location = Location.FIRST;
} else if (i > lastParagraphBegin) {
location = Location.LAST;
}
int begin = lastSentenceEnd + 1;
if (sentences.isEmpty()) begin = 0; // 对第一句,应该从0开始
Sentence sentence = new Sentence(begin, i, location);
sentences.add(sentence);
lastSentenceEnd = i;
}
i++;
}
五,分句后,对每句含有的词,在三的基础上,进行分数累加。(有个小逻辑:句子长度对分数比例的影响 || 单个句子包含多个命名实体 人名等对分数比例的影响。|| 。。。)这些需要大量的场景测for (Sentence sentence : sentences) {
String line = content.substring(sentence.getBegin(), sentence.getEnd() + 1);
double sumScore = wordsWeight.get(wordsWeight.size() - 1).getValue();
//注意:要用关键词中最小权重做为基数,避免句子权重计算结果为0
int maxSize = Math.min(topN, wordsWeight.size());
for (int i = 0; i < maxSize; i++) {
Map.Entry<Phrase, Double> weight = wordsWeight.get(i);
double weightDouble = weight.getValue();
if (line != null && line.indexOf(weight.getKey().getWord()) >= 0){
List<Term> terms = NLPTokenizer.segment(line);
int index =0;int termCount = 0;
sumScore += weightDouble;}
}
sentence.setScore(sumScore);
然后同理,根据subList()取出想要的几个句子。再根据句子所在正文的位置,进行一个先后顺序的排列。
Comparator<Sentence> valueComparator = new Comparator<Sentence>() {
@Override
public int compare(Sentence o1, Sentence o2) {
return o1.getBegin() - o2.getBegin();
}
};
Collections.sort(prefixSentences, valueComparator);
StringBuilder sb = new StringBuilder();
for (Sentence sentence : prefixSentences) {
sb.append(SentenceTool.toString(sentence, content));
}
还有一些可增删的业务逻辑:
去除文章末尾误识别的编辑、记者名称等
判断关键词里的 发言人;|| *** 说:“”。这些权重适当降低 保留句子里的实词词性,去除虚词词性(权重降低),来保证摘要的理性。 等等等等。 调试期间,有很多坑,现在记录下来,以便以后复习查看。
以上内容纯属个人所有,转载请注明出处。
Gradle +HanLP +SpringBoot 构建关键词提取,摘要提取 。入门篇的更多相关文章
- webpack+vue+vueRouter模块化构建完整项目实例详细步骤-入门篇
新建项目 开始(确认已经安装node环境和npm包管理工具) 1.新建项目文件名为start_vuedemo 2.npm init -y 初始化项目,我的win7系统,工程在d盘的vue_test_p ...
- springboot集成spring security安全框架入门篇
一. :spring security的简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下 ...
- Textrank权值提取文本标签提取:
Textrank权值提取文本标签提取: 我已经爬取到了指定博主的新浪微博,然后我想从微博中提取出可以代表该博主兴趣特征的100个关键词,然后由这100个关键词提取出10个标签,代表博主的兴趣.我们此处 ...
- gradle结合spring-boot生成可运行jar包,并打印日志
1.用gradle把springboot项目打包成jar 1.1 build.gradle 中添加 buildscript { repositories { mavenLocal() maven { ...
- 用gradle把springboot项目打包成jar
``` 用gradle把springboot项目打包成jar ```### build.gradle 中添加 buildscript { repositories { mavenLocal() mav ...
- 使用 Gradle 实现 TFS 构建自动化
发布于 2014-07-16 作者 陈 忠岳 感谢微软开放技术有限公司(简称"微软开放技术")发布的构建模板,我们现在便可以在 Team Foundation Server(TFS ...
- gradle 打包springboot项目,找不到项目jar application.class
如题:gradle 打包springboot项目,找不到项目jar入口main方法:application.class 检查:lib/目录下没有相应项目的jar包 用gradle命令行查看日志:gra ...
- Spring-Boot构建多模块项目
Spring-Boot构建多模块项目 功能模块单独项目开发,可以将一个庞大的项目分解成多个小项目,便于细分开发 Maven多模块项目不能独立存在,必须有一个介质来包含. 1.创建一个Maven 项目, ...
- ArcGIS案例学习笔记2_1_山顶点提取最大值提取
ArcGIS案例学习笔记2_1_山顶点提取最大值提取 计划时间:第二天上午 目的:最大值提取 教程:Pdf page=343 数据:chap8/ex5/dem.tif 背景知识:等高线种类 基本等高线 ...
随机推荐
- (记录)mysql分页查询,参数化过程的坑
在最近的工作中,由于历史遗留,一个分页查询没有参数化,被查出来有sql注入危险,所以对这个查询进行了参数化修改. 一看不知道,看了吓一跳,可能由于种种原因,分页查询sql是在存储过程中拼接出来的,wh ...
- SendMessage函数与MSDN系统预定义消息
SendMessage function https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950%28v=vs.85%29. ...
- 关于WPF你应该知道的2000件事
原文 关于WPF你应该知道的2000件事 以下列出了迄今为止为WPF博客所知的2,000件事所创建的所有帖子. 帖子总数= 1,201 动画 #7 - 基于属性的动画 #686 - 使用动画制作图像脉 ...
- express的路由规则
路由规则 express 封装了多种 http 请求方式,我们主要只使用 get 和 post 两种. get 和 post 的第一个参数都为请求的路径,第二个参数为处理请求的回调函数,回调函数有两个 ...
- 资源文件加载(Pack URI 方案)
Pack URI 在 Windows Presentation Foundation (WPF) 中,使用统一资源标识符 (URI) 标识和加载文件的方式有很多,包括:1.指定当应用程序第一次启动时显 ...
- 多线程Parallel和Task
不管是Parallel还是Task,最里面都是线程池(里面是线程)当开启多个任务后,系统会根据当前的线程池的资源进行分配,任务则进行等待Parallel可以对系统的CPU进行设置,可以最大程度上榨干系 ...
- mysql 优化 读写分离 主从复制
1:mysql所在服务器内核 优化 ---------此优化可由系统运维人员完成 2:mysql配置参数优化(my.cnf) -------- 此优化需进行压力测试来进行参数调整 3:sql语句及表 ...
- explicit:C++规定,当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换
explicit研究 explicit是C++中的关键字,不是C语言中的.英文直译是“明确的”.“显式的”意思.出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数 ...
- 【转】关于List排序的时效性
不多说了,就是说明List排序的时效性,仅仅用来备忘,改造自: http://blog.csdn.net/wanzhuan2010/article/details/6205884,感谢原作者 usin ...
- .Net Core中使用NodeJs加解密DES,MD5,AES,REA
鉴于使用.net core我们的加解密也同时迁移到了跨平台上,我使用的是NodeJs加解密的.废话不多说了,还是来干活吧. 1.创建Node项目 2.添加package.json { "n ...