java代码带你玩玩数据挖掘之分词入门
java代码带你玩玩数据挖掘之分词入门
要让计算机理解句子的语义,必须要有个程序,上面的句子和发布时间是输入,输出就是 “高兴”。要得到“高兴”就要建立 “高兴”的规则,可以建一个感情色彩词库,比如 高兴(识别词是高兴、happy),愤怒(识别词是愤怒、生气)。这里的识别词就是输入中出现的词语,比如上面的句子中的“happy”就识别出了“高兴”这个感情色彩词。但是光识别出“happy”肯定是不行的,前面的“假如。。。没。。。,我。。。不。。。”等关键词都需要识别出来,才能完整判断一个句子的意思。为了达到这个效果,就必须要用分词技术了。
我们先人工对上面的句子来进行一下切词,使用斜线分割:“你/假如/上午/没/给/我/吃/冰淇淋/,/我/绝对/会/不/happy/的/。/”。但是程序如何做到自动切分?这个其实中国的前辈们已经做了很多中文分词的研究,常见的分词算法有:
-
1、基于词典的分词,需要先预设一个分词词典,比如上面句子切分出来的“假如、上午”这些词先存放在词典,然后把句子切分成单字组合成词语去词典里查找,匹配上了就挑选出来一个词。没有匹配上的就切分成单字。
-
2、基于统计的分词,需要先获取大量的文本语料库(比如新闻、微博等),然后统计文本里相邻的字同时出现的次数,次数越多就越可能构成一个词。当达到一定次数时就构成了一个词即可形成语料概率库。再对上面句子进行单字切分,把字与字结合后在语料概率库里查找对应的概率,如果概率大于一定值就挑选出来形成一个词。这个是大概描述,实际生产环境中还需要对句子的上下文进行结合才能更准确的分词。
-
3、基于语义的分词,简而言之就是模拟人类对句子的理解来进行分词。需要先整理出中文语句的句法、语义信息作为知识库,然后结合句子的上下文,对句子进行单字切分后组合成词逐个带入知识库进行识别,识别出来就挑选出一个词。目前还没有特别成熟的基于语义的分词系统。
为了让大家快速的了解分词技术,我们采用第一个方式来做测试:基于词典的分词,这种方式简单暴力可以解决百分之七八十的问题。基于词典的分词大概分为以下几种方式:
1、正向最大匹配
沿着我们看到的句子逐字拆分后组合成词语到词典里去匹配,直到匹配不到词语为止。举个实际的例子:“人民大会堂真雄伟”,我们先拆分为单字“人”去词典里去查找,发现有“人”这个词,继续组合句子里的单字组合“人民”去词典里查找,发现有“人民”这个词,以此类推发现到“人民大会堂”,然后会结合“人民大会堂真”去词典里查找没有找到这个词,第一个词“人民大会堂”查找结束。最终分词的结果为:“人民大会堂/真/雄伟”。
2、逆向最大匹配
这个和上面相反,就是倒着推理。比如“沿海南方向”,我们按正向最大匹配来做就会切分成 “沿海/南方/向”,这样就明显不对。采用逆向最大匹配法则来解决这个问题,从句子的最后取得“方向”这两个字查找词典找到“方向”这个词。再加上“南方向”组成三字组合查找词典没有这个词,查找结束,找到“方向”这个词。以此类推,最终分出“沿/海南/方向”。
3、双向最大匹配
顾名思义就是结合正向最大匹配和逆向最大匹配,最终取其中合理的结果。最早由哈工大王晓龙博士理论化的取最小切分词数,比如“我在中华人民共和国家的院子里看书”,正向最大匹配切分出来为“我/在/中华人民共和国/家/的/院子/里/看书”工8个词语,逆向最大匹配切分出来为“我/在/中华/人民/共/和/国家/的/院子/里/看书”共11个词语。取正向最大匹配切出来的结果就是正确的。但是如果把上面那个例子“沿海南方向”双向切分,都是3个词语,改如何选择?看第4个《最佳匹配法则》。
4、最佳匹配法则
先准备一堆文本语料库、一个词库,统计词库里的每一个词在语料库里出现的次数记录下来。最后按照词频高的优先选出,比如“沿海南方向”,正向切分为:“沿海/南方/向”,逆向切分为:“沿/海南/方向”。其中“海南”的频度最高,优先取出来。剩下“沿”、“方向”也就正常切分了。是不是这就是基于词典分词的最佳方案?比如数学之美中提到的:“把手抬起来” 和 “这扇门的把手”,可以分为“把”、“手”、“把手”,不管怎么分总有一句话的意思不对。后续再介绍如何通过统计的分词处理这些问题。
说了这么多,我们来实战一下如何基于词典的分词:
123456789101112131415161718192021222324252627282930313233343536373839<code>
public
?
class
?TestPositiveMatch?{
? ?
public
?
static
?
void
?main(String[] args)?{
? ? ? ?String str =?
"我爱这个中华人民共和国大家庭"
;
? ? ? ?List<string> normalDict =?
new
?ArrayList<string>();
? ? ? ?normalDict.add(
""
);
? ? ? ?normalDict.add(
"爱"
);
? ? ? ?normalDict.add(
"中华"
); ??
//测试词库里有中华和中华人民共和国,按照最大匹配应该匹配出中华人民共和国
? ? ? ?normalDict.add(
"中华人民共和国"
);
? ? ? ?
int
?strLen = str.length(); ?
//传入字符串的长度
? ? ? ?
int
?j =?
0
;
? ? ? ?String matchWord =?
""
;?
//根据词库里识别出来的词
? ? ? ?
int
?matchPos =?
0
;?
//根据词库里识别出来词后当前句子中的位置
? ? ? ?
while
?(j < strLen) { ? ? ?
//从0字符匹配到字符串结束
? ? ? ? ? ?
int
?matchPosTmp =?
0
; ??
//截取字符串的位置
? ? ? ? ? ?
int
?i =?
1
;
? ? ? ? ? ?
while
?(matchPosTmp < strLen) { ??
//从当前位置直到整句结束,匹配最大长度
? ? ? ? ? ? ? ?matchPosTmp = i + j;
? ? ? ? ? ? ? ?String keyTmp = str.substring(j, matchPosTmp);
//切出最大字符串
? ? ? ? ? ? ? ?
if
?(normalDict.contains(keyTmp)) {?
//判断当前字符串是否在词典中
? ? ? ? ? ? ? ? ? ?matchWord = keyTmp; ?
//如果在词典中匹配上了就赋值
? ? ? ? ? ? ? ? ? ?matchPos = matchPosTmp;?
//同时保存好匹配位置
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?i++;
? ? ? ? ? ?}
? ? ? ? ? ?
if
?(!matchWord.isEmpty()) {
? ? ? ? ? ? ? ?
//有匹配结果就输出最大长度匹配字符串
? ? ? ? ? ? ? ?j = matchPos;
? ? ? ? ? ? ? ?
//保存位置,下次从当前位置继续往后截取
? ? ? ? ? ? ? ?System.out.print(matchWord +?
" "
);
? ? ? ? ? ?}?
else
?{
? ? ? ? ? ? ? ?
//从当前词开始往后都没有能够匹配上的词,则按照单字切分的原则切分
? ? ? ? ? ? ? ?System.out.print(str.substring(j, ++j) +?
" "
);
? ? ? ? ? ?}
? ? ? ? ? ?matchWord =?
""
;
? ? ? ?}
? ?}
}</string></string></code>
输出结果为: 我 爱 这 个 中华人民共和国 大 家 庭
按照这样我们一个基本的分词程序开发完成。
对于文章一开始提到的问题还没解决,如何让程序识别文本中的感情色彩。现在我们先要构建一个感情色彩词库“高兴”,修饰词库“没”、”不”。再完善一下我们的程序:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081<code><code>
public
?
class
?TestSentimentPositiveMatch?{
? ?
public
?
static
?
void
?main(String[] args)?{
? ? ? ?String str =?
"你假如上午没给我吃冰淇淋,我绝对会不happy的。"
;
? ? ? ?
//语义映射
? ? ? ?Map<string, string=
""
> sentimentMap =?
new
?HashMap<string, string=
""
>();
? ? ? ?sentimentMap.put(
"happy"
,?
"高兴"
);
? ? ? ?
//情感词库
? ? ? ?List<string> sentimentDict =?
new
?ArrayList<string>();
? ? ? ?sentimentDict.add(
"happy"
);
? ? ? ?
//修饰词
? ? ? ?List<string> decorativeDict =?
new
?ArrayList<string>();
? ? ? ?decorativeDict.add(
"不"
);
? ? ? ?decorativeDict.add(
"没"
);
? ? ? ?
//修饰词衡量分数
? ? ? ?Map<string,
double
=
""
> decorativeScoreMap =?
new
?HashMap<string,
double
=
""
>();
? ? ? ?decorativeScoreMap.put(
"不"
,?-
0.5
);
? ? ? ?decorativeScoreMap.put(
"没"
,?-
0.5
);
? ? ? ?List<string> decorativeWordList =?
new
?ArrayList<string>(); ?
//修饰词
? ? ? ?String sentimentResult =?
""
;?
//情感结果
? ? ? ?
int
?strLen = str.length(); ?
//传入字符串的长度
? ? ? ?
int
?j =?
0
;
? ? ? ?String matchSentimentWord =?
""
;?
//根据词库里识别出来的情感词
? ? ? ?String matchDecorativeWord =?
""
;?
//根据词库里识别出来的修饰词
? ? ? ?
int
?matchPos =?
0
;?
//根据词库里识别出来词后当前句子中的位置
? ? ? ?
while
?(j < strLen) { ? ? ?
//从0字符匹配到字符串结束
? ? ? ? ? ?
int
?matchPosTmp =?
0
; ??
//截取字符串的位置
? ? ? ? ? ?
int
?i =?
1
;
? ? ? ? ? ?
while
?(matchPosTmp < strLen) { ??
//从当前位置直到整句结束,匹配最大长度
? ? ? ? ? ? ? ?matchPosTmp = i + j;
? ? ? ? ? ? ? ?String keyTmp = str.substring(j, matchPosTmp);
//切出最大字符串
? ? ? ? ? ? ? ?
if
?(sentimentDict.contains(keyTmp)) {?
//判断当前字符串是否在词典中
? ? ? ? ? ? ? ? ? ?matchSentimentWord = keyTmp; ?
//如果在词典中匹配上了就赋值
? ? ? ? ? ? ? ? ? ?matchPos = matchPosTmp;?
//同时保存好匹配位置
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?
if
?(decorativeDict.contains(keyTmp)) {?
//判断当前字符串是否在词典中
? ? ? ? ? ? ? ? ? ?matchDecorativeWord = keyTmp; ?
//如果在词典中匹配上了就赋值
? ? ? ? ? ? ? ? ? ?matchPos = matchPosTmp;?
//同时保存好匹配位置
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?i++;
? ? ? ? ? ?}
? ? ? ? ? ?
if
?(!matchSentimentWord.isEmpty()) {
? ? ? ? ? ? ? ?
//有匹配结果就输出最大长度匹配字符串
? ? ? ? ? ? ? ?j = matchPos;
? ? ? ? ? ? ? ?
//保存位置,下次从当前位置继续往后截取
? ? ? ? ? ? ? ?System.out.print(matchSentimentWord +?
" "
);
? ? ? ? ? ? ? ?sentimentResult = sentimentMap.get(matchSentimentWord);
? ? ? ? ? ?}
? ? ? ? ? ?
if
?(!matchDecorativeWord.isEmpty()) {
? ? ? ? ? ? ? ?
//有匹配结果就输出最大长度匹配字符串
? ? ? ? ? ? ? ?j = matchPos;
? ? ? ? ? ? ? ?
//保存位置,下次从当前位置继续往后截取
? ? ? ? ? ? ? ?System.out.print(matchDecorativeWord +?
" "
);
? ? ? ? ? ? ? ?decorativeWordList.add(matchDecorativeWord);
? ? ? ? ? ?}?
else
?{
? ? ? ? ? ? ? ?
//从当前词开始往后都没有能够匹配上的词,则按照单字切分的原则切分
? ? ? ? ? ? ? ?System.out.print(str.substring(j, ++j) +?
" "
);
? ? ? ? ? ?}
? ? ? ? ? ?matchSentimentWord =?
""
;
? ? ? ? ? ?matchDecorativeWord =?
""
;
? ? ? ?}
? ? ? ?
double
?totalScore =?
1
;
? ? ? ?
for
?(String decorativeWord : decorativeWordList) {
? ? ? ? ? ?Double scoreTmp = decorativeScoreMap.get(decorativeWord);
? ? ? ? ? ?totalScore *= scoreTmp;
? ? ? ?}
? ? ? ?System.out.print(
"\r\n"
);
? ? ? ?
if
?(totalScore >?
0
) {
? ? ? ? ? ?System.out.println(
"当前心情是:"
?+ sentimentResult);
? ? ? ?}?
else
?{
? ? ? ? ? ?System.out.println(
"当前心情是:不"
?+ sentimentResult);
? ? ? ?}
? ?}
}</string></string></string,></string,></string></string></string></string></string,></string,></code></code>
通过传入“你假如上午没给我吃冰淇淋,我绝对会不happy的。”,结果输出为:“当前心情是:高兴”。当然你也可以改变其中的修饰词,比如改为:“你假如上午没给我吃冰淇淋,我绝对会happy的。”,结果输出为:“当前心情是:不高兴”。
机器再也不是冷冰冰的,看起来他能读懂你的意思了。不过这只是一个开始,抛出几个问题:
1、如何让程序识别句子中的时间?比如“上午”、“下午2点”。
2、如何处理“把手抬起来” 和 “这扇门的把手”中的“把”与“手”的问题?
3、如何构建海量的知识库,让程序从“婴儿”变成“成年人”?
4、如何使用有限的存储空间海量的知识库?
5、如何提高程序在海量知识库中查找定位信息的效率?
6、如何识别新词、人名、新鲜事物等未知领域?
本文来源:SecYe安全网[http://www.secye.com] (责任编辑:SecYe安全)
- ·eclipse中增大tomcat内存
- ·SpringBoot Druid整合,SpringBoot 集成D
- ·java代码带你玩玩数据挖掘之分词入门
- ·JVM虚拟机的加载机制详解
- ·Java 关键字
- ·Java strictfp关键字的全法代码及非法代
- ·自学 Java 怎么入门?
- ·Java性能调优的11个实用技巧
- ·Java网络基础,Socket通信基础
- ·Java关键字volatile的理解与正确使用
- ·对于学习Java的新手一些基础的建议
- ·最新java学习资源分享
- ·40个Java多线程问题总结
- ·35个Java代码性能优化总结
- ·activemq activeMQ的简单介绍
- ·JavaWeb学习总结
- ·eclipse中增大tomcat内存
- ·SpringBoot Druid整合,SpringBoot 集成Dru
- ·java代码带你玩玩数据挖掘之分词入门
- ·JVM虚拟机的加载机制详解
- ·Java 关键字
- ·Java strictfp关键字的全法代码及非法代码
- ·自学 Java 怎么入门?
- ·Java性能调优的11个实用技巧
- ·Java网络基础,Socket通信基础
- ·Java关键字volatile的理解与正确使用
- ·对于学习Java的新手一些基础的建议
- ·最新java学习资源分享
- ·40个Java多线程问题总结
- ·35个Java代码性能优化总结
- ·activemq activeMQ的简单介绍