Kmeans聚类

1 聚类与分类的区别
2 k-means 聚类基本概念
3 算法优缺点
4 算法思路
5 代码实现

1 聚类与分类的区别

简单来说

  • 分类:类别是已知的,通过对已知分类的数据进行训练和学习,找到这些不同类的特征,再对未分类的数据进行分类。属于监督学习。
  • 聚类:事先不知道数据会分为几类,通过聚类分析将数据聚合成几个群体。聚类不需要对数据进行训练和学习。属于无监督学习。

分类作为一种监督学习方法,要求必须事先明确知道各个类别的信息,并且断言所有待分类项都有一个类别与之对应。但是很多时候上述条件得不到满足,尤其是在处理海量数据的时候,如果通过预处理使得数据满足分类算法的要求,则代价非常大,这时候可以考虑使用聚类算法。

2 k-means聚类基本概念

K-Means 聚类算法属于无监督学习方法。K表示类别数,Means表示均值,K一般由人工来指定,或通过层次聚类(Hierarchical Clustering)的方法获得数据的类别数量作为选择K值的参考。选择较大的K可以降低数据的误差,但会增加过拟合的风险。

2.1几个概念:

  • 聚类(Clustering):K-Means 是一种聚类分析(Cluster Analysis)方法。聚类就是将数据对象分组成为多个类或者簇 (Cluster),使得在同一个簇中的对象之间具有较高的相似度,而不同簇中的对象差别较大。
  • 划分(Partitioning):聚类可以基于划分,也可以基于分层。划分即将对象划分成不同的簇,而分层是将对象分等级。
  • 排他(Exclusive):对于一个数据对象,只能被划分到一个簇中。如果一个数据对象可以被划分到多个簇中,则称为可重叠的(Overlapping)。
  • 距离(Distance):基于距离的聚类是将距离近的相似的对象聚在一起。基于概率分布模型的聚类是在一组对象中,找到能符合特定分布模型的对象的集合,他们不一定是距离最近的或者最相似的,而是能完美的呈现出概率分布模型所描述的模型。

2.2 K-Means 问题描述:

给定一个 n 个对象的数据集,它可以构建数据的 k 个划分,每个划分就是一个簇,并且 k ≤ n。同时还需满足:

  • 每个组至少包含一个对象。
  • 每个对象必须属于且仅属于一个簇。

3 算法优缺点

3.1 优点:

  • 是解决聚类问题的一种经典算法,简单、快速。
  • 对处理大数据集,该算法保持可伸缩性和高效率。
  • 当结果簇是密集的,它的效果较好。

3.2 缺点:

  • 在簇的平均值可被定义的情况下才能使用,可能不适用于某些应用。
  • 必须事先给出k(要生成的簇的数目),而且对初值敏感,对于不同的初始值,可能会导致不同结果。
  • 不适合于发现非凸形状的簇或者大小差别很大的簇。
  • 对躁声和孤立点数据敏感。

4 算法思路

4.1 K-Means 聚类算法的大致意思就是“物以类聚,人以群分”。

算法思路如下:

  1. 首先输入 k 的值,即我们指定希望通过聚类得到 k 个分组;
  2. 从数据集中随机选取 k 个数据点作为初始质心;
  3. 对集合中每一个样本点,计算与每一个初始质心的距离,离哪个初始质心距离近,就属于那个类。
  4. 按距离对所有样本分完组之后,计算每个组的均值(最简单的方法就是求样本每个维度的平均值),作为新的质心。
  5. 重复(2)(3)(4)直到新的质心和原质心相等,算法结束。

4.2 相似度/距离计算:

  • 1 欧氏距离相似度


    image.png
  • 2 Jaccard相似度


    image.png
  • 3 余弦相似度


    image.png
  • 4 Pearson相似度


    image.png
  • 5 相对熵(K-L距离)


    image.png

4.3 实例解释算法(采取余弦相似性对166个文本聚为13类)

image.png

以新闻的聚类为例子。对多篇新闻进行聚类,步骤如下:

(1) 文本预处理:分词、统计TF-IDF

1 分词
由于文本过多,故选取一篇文章说明,例如对下文分词:
中石油海南销售公司挂牌10%股权|2016-11-24|每日经济新闻|在终端销售板块宣布进入混改超过2年后,作为
试点的新疆销售公司还未见实质动作,中国石油已经在海南启动股权转让,但这场挂牌“叫卖”却颇显蹊跷,是
混改正在加速?又或者是地方提前锁定的交易仍存悬念。 11月23日,上海联合产权交易所披露,中国石油以
1.57亿元挂牌出售中石油海南销售有限公司(以下简称海南销售公司)10%股权的公告。 但巧合的是,《每
日经济新闻》记者查询公开信息发现,今年1月,海南省国资委就已经公布,由海南国资控股的海南省发展控
股公司(以下简称海南发控)参股海南销售公司10%,并启动办理程序。今年8月据《海南日报》报道,海南
发控宣布与中国石油就合资经营海南销售公司签署框架协议。 目前尚不清楚挂牌的10%与海南发控的10%是
否为同一份股权。对此,国资专家李锦认为,如此次公开挂牌转让最后受让方就是海南发控,虽然不算是违
规,但这就不一定能完全达到混改目的,“海南发控也是国资。只能算作产权转让而不是混合所有制改革。” 
对于上述情况,记者致电中国石油集团(以下简称中石油),但截稿前并未得到答复。 交易所称接受各类资
本 据挂牌信息,海南销售净资产7.26亿元,10%股权挂牌价为1.57亿元,
排序后:================
海南             的次数为27
销售             的次数为23
中石油             的次数为14
挂牌             的次数为11
国资             的次数为10
公司             的次数为10
10             的次数为9
中国石油             的次数为8
转让             的次数为8
记者             的次数为7
板块             的次数为7
股权             的次数为7
新疆             的次数为6
亿元             的次数为6
表示             的次数为6
受让方             的次数为6
2 计算词频(某词在一篇文章中出现的次数/文章总词数)
排序前:================
程序             的tf为0.004739336492890996
交易             的tf为0.00631911532385466
子公司             的tf为0.004739336492890996
目前             的tf为0.00631911532385466
中国             的tf为0.004739336492890996
中石油             的tf为0.022116903633491312
新闻             的tf为0.007898894154818325
记者             的tf为0.011058451816745656
交易所             的tf为0.007898894154818325
今年             的tf为0.007898894154818325
参股             的tf为0.004739336492890996
结果以(文章标题,词语,频数)形式存储在Map数据结构中:
{中石油海南销售公司挂牌10%股权={程序=0.004739336492890996, 交易=0.00631911532385466, 子公司
=0.004739336492890996, 目前=0.00631911532385466, 中国=0.004739336492890996, 中石油
=0.022116903633491312, 新闻=0.007898894154818325, 记者=0.011058451816745656, 交易所
=0.007898894154818325, 今年=0.007898894154818325, 参股=0.004739336492890996, 这一
=0.004739336492890996, 项目=0.004739336492890996, 国资=0.01579778830963665, 报道
=0.004739336492890996, 简称=0.004739336492890996, 可能=0.004739336492890996, 启动
=0.00631911532385466, 改革=0.007898894154818325, 经济=0.007898894154818325, 板块
=0.011058451816745656, 有限公司=0.004739336492890996, 新疆=0.009478672985781991, 中国石油
=0.01263823064770932, 公司=0.01579778830963665, 试点=0.004739336492890996, 所有制
=0.004739336492890996, 意向=0.004739336492890996, 海南省=0.004739336492890996, 8月
=0.004739336492890996, 信息=0.004739336492890996, 以下=0.004739336492890996, 销售
=0.036334913112164295, 目的=0.004739336492890996, 企业=0.00631911532385466, 亿元
=0.009478672985781991, 挂牌=0.017377567140600316, 产权=0.004739336492890996, 1月
=0.004739336492890996, 表示=0.009478672985781991, 加速=0.004739336492890996, 要求
=0.004739336492890996, 股权=0.011058451816745656, 海南=0.04265402843601896, 发现
=0.004739336492890996, 受让方=0.009478672985781991, 10=0.014218009478672985, 每日
=0.007898894154818325, 出售=0.004739336492890996, 转让=0.01263823064770932, 协议
=0.00631911532385466, 公开=0.004739336492890996, 宣布=0.004739336492890996, 此次
=0.004739336492890996}}
3计算idf。
IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能
力。如果某一类文档C中包含词条t的文档数为m,而其它类包含t的文档总数为k,显然所有包含t的文档数
n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。
IDF=log(|D| /(1+|Dt|)),其中|D|表示文档总数,|Dt|表示包含关键词t的文档数量。
如上所述IDF原理,所以需要用到文档总数,下面以40篇文档为例子。
计算出的idf(下方有文本的tf,可帮助查看包含的文档,注意此处关键词还未包含标题内的词语。)
例如,在此例子中“重点”出现了1次,“冬季”和“合资”出现2次,“建设”和“减产”出现4次。
{重点(1次)=3.713572066704308, 天山=3.713572066704308, 支持=3.713572066704308, 停止
=3.713572066704308, 联盟=3.713572066704308, 明确=3.713572066704308, 建设(4
次)=2.327277705584417, 备用=3.713572066704308, 网络=3.713572066704308, 具有

(2)计算每个词的权重(tf-idf)

4 计算每个词的权重(tf-idf)
由于文本长短不一,权重也不同和向量维度也不同,会影响后面计算文本距离。故为了降维,此处每篇文章只选取了权重排名前15的词,若文章词数不够,
则在后面添加none,并将其权重置为0.0(none=0.0)。
Eg
{西安启动天然气供气应急预案={中心=0.04184306554033023, 供气=0.13613182585439382, 停止
=0.04184306554033023, 启动=0.034032956463598454, 天气=0.051049434695397675, 天然气
=0.08120334918913744, 应急=0.08368613108066046, 抢险=0.05230383192541279, 每天
=0.04184306554033023, 气量=0.042541195579498065, 用户=0.05955767381129729, 西安
=0.05230383192541279, 采暖=0.04184306554033023, 锅炉=0.04184306554033023, 预案
=0.05230383192541279}, 

(3)寻找k个初始文本做为初始聚类中心

5 寻找k个初始文本做为初始聚类中心。
由于若文档过多在调试时,无法显示所有的值,故选取20篇文本作为试验。
例如,此处将20篇文档聚为4类,K=4,即随机选取4个文档作为初始聚类中心;
{0={atlantis=0.036978005316478824, none12=0.0, none13=0.0, none14=0.0, none15=0.0, 公司
=0.01743017642051809, 发电站=0.036978005316478824, 安装=0.036978005316478824, 最大
=0.036978005316478824, 海峡=0.036978005316478824, 涡轮机=0.1479120212659153, 潮汐
=0.13558601949375568, 系统=0.01743017642051809, 苏格兰=0.036978005316478824, 项目
=0.01743017642051809}, 

(4)计算每个文档与K个初始文档之间的距离。(余弦相似性)

6 计算每个文档与K个初始文档之间的距离。(余弦相似性)
结果:
[[0.5341768283489521, 0.22550087649869377, 0.2486281038946042, 0.3159328699405629], 
[0.42238218857464127, 0.41725395486903627, 0.5710840557617309, 0.29669879516442077], 
[0.29046239030058363, 0.21421111152521566, 0.3573621468604463, 0.2735781861114148], 
[0.3150300121740689, 0.11204100926947691, 0.13070547127558185, 0.33172344660962116], 
[0.4797761652659057, 0.11949185554315389, 0.1190211581617493, 0.3756470592440674], 
[0.5044412632415061, 0.1485076373690446, 0.11095477841747425, 0.4654333547886619], 
[0.3407495589566709, 0.19413640256543818, 0.31617687351478385, 0.2193834077767255], 
[0.44727521067474907, 0.1541152390227356, 0.18167884692213088, 0.42173415611538445], 
[-2.220446049250313E-16, 0.3921325379658961, 0.5195159081779397, 0.2950273294005489], 
[0.5085455872006137, 0.26155864577038823, 0.3609306237010538, 0.44333775672496933], 
[0.2950273294005489, 0.2855060565280657, 0.4805358516787299, 0.0],

解释上面的结果。
将上面的(0,1,2,3)个初始文档分别与(1,2,3,4,5,……20)篇文档(总文档)计算余弦相似性;
那么,拿上面第一行来说,[[0.5341768283489521, 0.22550087649869377, 0.2486281038946042, 0.3159328699405629]四个数分别代表,0(初始文档)0(总文档),01,02,03的相似性。
但是,上面在初始化时,因为随机取了K个文档,故(0,1,2,3)只是控制文档个数所需,只是个代号,此处的(0,1,2,3)分别代表20篇文档中的第9,11,12,20篇文档。(初始文档与总文档中这些文档的计算结果已在上方标黄)
从该结果可以看出,在所有的文档中,初始的9,12,11,20篇文档与总文档中的9,12,11,20篇距离最近,最相似,验证了其本身就是一篇文档。

(5)通过比较文档之间的距离,获得最相似的文档。

结果:

[1, 3, 1, 1, 2, 2, 1, 1, 0, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 2]

解释:
将上面通过余弦定理计算出来的文档结果看作一个矩阵,找出每一行中最小值,即该行中与K个聚类中心最相似的文档,并将这些文档归为一类,显示如上所示结果。

(6)判断当前所有点属于的聚类序号是否已经全部是其离得最近的聚类,如果是或者达到最大的迭代次数,那么结束算法。

判断代码:

for(int i = 0; i = 100) break;

参数解释:
tsLength:文档总数
iterNum:迭代次数
nearestMeans[i]: 本次迭代的最近文档数
[1, 3, 1, 1, 2, 2, 1, 1, 0, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 2]
assignMeans[i]: 用于记录上次迭代的最近文档数,初始为0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

代码解释:
比较本次迭代的文档和上次迭代的文档,如果相同,则表明当前所有文档已经达到最近聚类,结束循环;同时,设置最大迭代次数,当到达最大迭代次数时也结束循环。

(7)如果未能满足上述条件,则那么需要重新聚类再进行一次迭代,需要修改每个聚类的成员和每个点属于的聚类信息。

结果:

{0=[8], 1=[0, 2, 3, 6, 7, 9, 11, 12, 13, 15, 16, 17, 18], 2=[4, 5, 19], 3=[1, 10, 14]}

结果解释:
将上面分类结果[1, 3, 1, 1, 2, 2, 1, 1, 0, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 2]归类,每个文档赋值给该文档所属于的类。

(8)同时,重新计算聚类中心(此处采用向量中心),重复4~6步骤。

结果:
{0={5号=0.01679896591155781, dcs=0.044797242430820824, none12=0.0, none13=0.0, none14=0.0, 
none15=0.0, vr=0.01622879764244895, 中广=0.010720149428603602, 互联网=0.04264956001121174, 产
业=0.0053606511097096955, 发展=0.00714753481294626, 和睦=0.01955879602559945, 威盛
=0.027588955992163215, 安全=0.01679896591155781, 展区=0.008114398821224476, 展示
=0.008114398821224476, 工业=0.011360158349714265, 工程=0.008387543745928074, 应用
=0.00973727858546937, 快递=0.00649151905697958, 我国=0.023750221822483743, 技术
=0.011804741125103237, 推动=0.00984220615643348, 推进=0.013122941541911307, 数据
=0.010027186597712057, 智能=0.017851677406693845, 机组

结果解释:
{0=[2, 5, 9, 18], 1=[8, 10], 2=[1, 3, 6, 7, 11, 12, 13, 14, 15, 16, 17, 19], 3=[0, 4]}
将上述聚类出的文档,计算质心(向量中心)。
将每一类中文档的所有词/文档总数,得到向量中心。

(9) 聚类结果分析

(a)结果分析(余弦相似性):

从结果中可以看出,聚类的结果并不是很好,而且无法控制聚出的类,在反复思考调试之后发现:使用余弦相似性需要每个文本的词数相同,这个条件在第一次可以达到(由于我将排名前15的词提取出来,计算文本相似性),但是随着迭代次数的增加,当再次计算聚类质心时,文档的词数就无法保证(因为每个类中所含的文档数不同),所以用余弦相似性计算就行不通了。
优化方法:
1 可以考虑采用新的方法计算文档的质心。
2 采用逐一比较法计算文档相似性。

故决定换用直接逐一比较每个词,计算文本相似性。
原理:将初始的K个文档和总文档中所有文档进行比较,如果总文档中含有K个初始文档的某个关键词,则将二者的权重相乘。此方法主要是通过计算文本中的相同词来计算文档的相似性。

(b)结果分析(向量内积):

结果分析:从结果来看,使用逐一比较法计算文本相似性要比用余弦相似性聚类结果要更合理。原因是,使用此方法没有了特征词的限制,通过逐一比较文档,可以更加全面的计算文档的相似性。

但是,结果依旧存在一些问题。例如有些地方还是存留着一些某个主题却未聚到一类中,有些聚出的类难以辨认属于哪类,也说明算法还需要优化。

进一步优化的具体方法

首先,Kmeans算法本身就有两个巨大的缺陷:

(1)K是事先给定的,这个K值的选定是非常难以估计的。很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适。

(2)K-Means算法需要用初始随机种子点来确定,这个随机种子点太重要,不同的随机种子点会有得到完全不同的结果。

上述问题优化的方法:

(1) ISODATA算法通过类的自动合并和分裂,得到较为合理的类型数目K。

(2) K-Means++算法可以用来解决这个问题,其可以有效地选择初始点。

其次,现在的聚类是将文本中的所有内容作为相同的处理,而且并未处理文章的标题,但事实上文章的每一段中词的权重是不同的,所以可以考虑通过给不同的部分的词语加上不同的权重来重新计算词权重。

最后,还需要通过与其他优秀的算法的聚类结果进行比较,并学习其在选取K值和计算质心时的方式。

5 代码实现

主函数

public void KmeansClusterMain(String testSampleDir) throws IOException {  
        //首先计算文档TF-IDF向量,保存为Map> 即为Map<文件名,Map<特征词,TF-IDF值>>
        Configuration rc = new Configuration("daoconfig.properties");
        String RootURL_exSegTxt = rc.getValue("RootURL_exSegTxt");
        int[] K = {10};  
        Map> allTestSampleMap = TfIdf.tfidf(testSampleDir);  
        for(int i = 0; i < K.length; i++){  
            System.out.println("开始聚类,聚成" + K[i] + "类");  
            String KmeansClusterResultFile = RootURL_exSegTxt;  
            Map KmeansClusterResult = new TreeMap();  
            KmeansClusterResult = doProcess(allTestSampleMap, K[i]);  
            KmeansClusterResultFile = KmeansClusterResultFile + "\\" + K[i];  
            printClusterResult(KmeansClusterResult,KmeansClusterResultFile);  
            //System.out.println("The Entropy for this Cluster is " + computeV.evaluateClusterRes(KmeansClusterResultFile, K[i]));  
        }  
    } 

处理tf-idf的函数

    /**
     * 计算TF-IDF = TF*IDF
     * @param dir
     * @return singelFile(词语,TF-IDF)
     * @throws IOException
     */
    public static Map> tfidf(String dir) throws IOException {       
        TfIdf.NormalTFOfAll(dir);
        Map> tf = TfIdf.tfOfAll(dir);
        //TfIdf.idf(dir); 
        int n=15;
        //Map idf = selectTokenWords(TfIdf.idf(dir), n);
        Map idf = TfIdf.idf(dir);
        Map> tfidf = new HashMap>();
        sortDouResult(idf,"idf");//排序显示idf
        for (String title : tf.keySet()) {  
            Map singelFile = tf.get(title);  
            for (String word : singelFile.keySet()) {  
                singelFile.put(word, (idf.get(word)) * singelFile.get(word));  
            }  
            sortDouResult(singelFile,"tf-idf");//排序显示tf-idf
            tfidf.put(title, selectTokenWords(singelFile, n));
        }
        
        return tfidf;  
    } 

聚类过程主函数

 /**Kmeans算法主过程 
     * @param Map> allTestSampleMap 聚类算法测试样本map 
     * @param int K 聚类的数量 
     * @return Map 聚类的结果  即<文件名,聚类完成后所属的类别标号> 
     * @throws IOException  
     */  
    private Map doProcess(  
            Map> allTestSampleMap, int K) {  
        //0、首先获取allTestSampleMap所有标题顺序组成的数组  
        String[] testSampleNames = new String[allTestSampleMap.size()];  
        int count = 0, tsLength = allTestSampleMap.size();  
        Set>> allTestSampeleMapSet = allTestSampleMap.entrySet();  
        for(Iterator>> it = allTestSampeleMapSet.iterator(); it.hasNext(); ){  
            Map.Entry> me = it.next();  
            testSampleNames[count++] = me.getKey();  
        }  
        //1、初始点的选择算法是随机选择
        Map> meansMap = getInitPoint(allTestSampleMap, K);//保存K个中心点  <标题<词,权重>>
        double [][] distance = new double[tsLength][K];//distance[i][j]记录点i到聚类中心j的距离  
        //2、初始化K个聚类  
        int [] assignMeans = new int[tsLength];//记录所有点属于的聚类序号,初始化全部为0  
        Map> clusterMember = new TreeMap>();//记录每个聚类的成员点序号  
        Vector mem = new Vector();  
        int iterNum = 0;//迭代次数  
        while(true){  
            System.out.println("迭代次数" + (iterNum++) + "----------------------");  
            //3、计算每个点和每个聚类中心的距离  
            for(int i = 0; i < tsLength; i++){  
                for(int j = 0; j < K; j++){
                    //计算每个类和k个初始类之间的距离
                    distance[i][j] = getDistance(allTestSampleMap.get(testSampleNames[i]),meansMap.get(j));
                }  
                //wordsDis.put(testSampleNames[i], value);
            }  
            //4、找出最相似的文档  
            int[] nearestMeans = new int[tsLength];  
            for(int i = 0; i < tsLength; i++){  
                nearestMeans[i] = findNearestMeans(distance, i);    
            }  
            //5、判断当前所有点属于的聚类序号是否已经全部是其离得最近的聚类,如果是或者达到最大的迭代次数,那么结束算法  
            int okCount = 0;  
            for(int i = 0; i 

你可能感兴趣的:(Kmeans聚类)