/**
*作者:张荣华
*日期:2008-2-23
**/
数据挖掘之分类系列文章
之前说到分类的基本概念以及一个文本分类的实例,原文地址见:http://www.iteye.com/topic/163285 现在我们就来改造之前的分类算法,本文主要介绍KNN算法在文本分类器中的使用。
kNN算法简介:
kNN(k Nearest Neighbors)算法又叫k最临近方法, 总体来说kNN算法是相对比较容易理解的算法之一,假设每一个类包含多个样本数据,而且每个数据都有一个唯一的类标记表示这些样本是属于哪一个分类, kNN就是计算每个样本数据到待分类数据的距离,取和待分类数据最近的k各样本数据,那么这个k个样本数据中哪个类别的样本数据占多数,则待分类数据就属于该类别。
基于kNN算法的思想,我们必须找出使用该算法的突破点,本文的目的是使用kNN算法对文本进行分类,那么和之前的文章一样,关键还是项向量的比较问题,之前的文章中的分类代码仅使用的反余弦来匹配项向量,找到关键的“距离”,那么我们可以试想反余弦之后使用kNN的结果如何。
补充上一篇文章中没有详细讲解的反余弦匹配问题:
Lucene中有一个term vectors这个东东,它表示该词汇单元在文档中出现的次数,比如说这里有两篇文章,这两篇文章中都有hibernate和spring这两个单词,在第一篇文章中hibernate出现了10次,spring出现了20次,第二篇文章中hibernate出现15次,spring出现10次,那么对第一篇文章来说有两个项向量,分别是hibernate:10,spring:20,第二篇文章类似,hibernate:15,spring:10。然后我们就可以在二维空间的x,y组上表示出来,如图:
这样看来我们其实是要得到两者之间的夹角,计算两个向量之间夹角的公式为A*B/||A||*||B||。按照这个原理我们就可以得到新文章和样本文章之间的距离,代码如下,这个份代码在第一篇文章提供的代码下载中。
public double caculateVectorSpace(Map<String, Integer> articleVectorMap, Map<String, Integer> classVectorMap) { if (articleVectorMap == null || classVectorMap == null) { if (logger.isDebugEnabled()) { logger.debug("itemVectorMap or classVectorMap is null"); } return 20; } int dotItem = 0; double denominatorOne = 0; double denominatorTwo = 0; for (Entry<String, Integer> entry : articleVectorMap.entrySet()) { String word = entry.getKey(); double categoryWordFreq = 0; double articleWordFreq = 0; if (classVectorMap.containsKey(word)) { categoryWordFreq = classVectorMap.get(word).intValue() / classVectorMap.size(); articleWordFreq = entry.getValue().intValue() / articleVectorMap.size(); } dotItem += categoryWordFreq * articleWordFreq; denominatorOne += categoryWordFreq * categoryWordFreq; denominatorTwo += articleWordFreq * articleWordFreq; } double denominator = Math.sqrt(denominatorOne) * Math.sqrt(denominatorTwo); double ratio = dotItem / denominator; return Math.acos(ratio); }
接着根据kNN的原理,我们记录下待分类数据和样本数据的距离,对每一个待分类数据都找出k个距离最小的样本,最后判断这些样本所在的分类, 这些样本所在的分类就是该新数据应该所在的分类。
那么根据以上的描述,我把结合使用反余弦匹配和kNN结合的过程分成以下几个步骤:
1, 计算出样本数据和待分类数据的距离
2, 为待分类数据选择k个与其距离最小的样本
3, 统计出k个样本中大多数样本所属的分类
4, 这个分类就是待分类数据所属的分类
根据上面的步骤,我写出了以下代码,这些代码都包含在提供下载的代码里,我将不断的扩充这些代码,可以说一下代码是使用kNN比较核心的代码。
MatchCondition这个类包括,待分类数据,样本数据,样本类别,和距离。
protected Map<String, List<MatchCondition>> analyse(Map<String, Map<String, Integer>> articleVectorMap, Map<String, Map<String, Integer>> categoryVectorMap) { Map<String, List<MatchCondition>> result = new HashMap<String, List<MatchCondition>>(); for (Entry<String, Map<String, Integer>> categoryEntry : categoryVectorMap.entrySet()) { for (Entry<String, Map<String, Integer>> itemEntry : articleVectorMap.entrySet()) { double acos = caculateVector(itemEntry.getValue(), filterVectorMap(categoryEntry.getValue())); if (acos < vectorGene) { if (result.get(itemEntry.getKey()) != null) { List<MatchCondition> list = result.get(itemEntry.getKey()); if (list.size() < kNum) { list.add(new MatchCondition(itemEntry.getKey(), categoryEntry.getKey(), acos)); } else { if (list.size() == kNum) { Collections.sort(list, new MatchConditionComparator()); } int n = 0; for (MatchCondition condition : list) { if (acos < condition.getAcos()) { list.set(n, new MatchCondition(itemEntry.getKey(), categoryEntry.getKey(), acos)); } n++; } } } else { List<MatchCondition> list = new LinkedList<MatchCondition>(); list.add(new MatchCondition(itemEntry.getKey(), categoryEntry.getKey(), acos)); result.put(itemEntry.getKey(), list); } } } } return result; }
所有的代码在本文提供的下载代码中,以第一篇文章中的测试数据运行测试,所得的结果为:
2008-02-23 14:04:15,646 DEBUG ArticleKNNClassifierImpl:81 - ---------------- The article id is 3
2008-02-23 14:04:15,646 DEBUG ArticleKNNClassifierImpl:83 - categoryId : a | count : 1
2008-02-23 14:04:15,646 DEBUG ArticleKNNClassifierImpl:81 - ---------------- The article id is 2
2008-02-23 14:04:15,646 DEBUG ArticleKNNClassifierImpl:83 - categoryId : b | count : 2
2008-02-23 14:04:15,646 DEBUG ArticleKNNClassifierImpl:83 - categoryId : a | count : 3
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:81 - ---------------- The article id is 1
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : b | count : 1
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : a | count : 4
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:81 - ---------------- The article id is 5
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : b | count : 1
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : a | count : 4
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:81 - ---------------- The article id is 4
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : b | count : 3
2008-02-23 14:04:15,656 DEBUG ArticleKNNClassifierImpl:83 - categoryId : a | count : 2
从这里我们可以看出articleId为2的应该属于a分类,articleId为1的也属于a分类,articleId为5的也属于a分类,articleId为4的属于b分类。当然其实我们的样本数量太少了,并不能说明acos+knn结合的的效果。
也有人提出了一种结合kNN分类器的加权特征提取问题,该分类通过每次的分类结果不断的调整权值,具有较好的分类效果。所以说虽然kNN算法比较简单,但是事实上如果使用正确,应该也可以收到不错的效果。
下一篇关于数据挖掘的文章还是分类问题,不过会使用另外一个算法,就是朴素贝叶斯分类。当然也希望有大家能够在这里交流一下自己的经验。
1 楼 gcgmh 2008-10-27
如何能下载到你的代码啊?你说提供下载,我找不到下载链接啊!
2 楼 yetchen 2008-10-27
请问lz,数据挖掘的算法在企业应用中是否有用武之地呢?
3 楼 AngelAndAngel 2012-02-23
你的那个图有问题吧,应该标识文章一 文章二 而不是hibernate spring?