用JAVA语言实现的凝聚式层次聚类算法 ——基于数据结构中的线性结构和树形结构
引言
将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类。聚类是一种机器学习技术,用于数据点的分组。给定一组数据点,我们可以使用聚类算法将每个数据点划分为一个特定的组。理论上,同一组中的数据点应该具有相似的属性或特征,而不同组中的数据点应该具有高度不同的属性或特征。
不同于往常的基于数组的算法,本文采用数据结构中的树形结构存储聚类,能够更好地体现凝聚式层次聚类的过程和同一类中对象的相互关系。
概念介绍
聚类
聚类即对没有目标变量(类别)的数据集根据数据的相似性给出 “自然的”分组。分组的基本目标是使得分组后类内对象相似性尽量大,类间对象相似性尽量小。
根据能否得到类的层次关系,聚类分为层次聚类(或嵌套聚类)和划分聚类。根据结果类是否包含所有对象,聚类分为完全聚类和部分聚类。根据一个对象是否可以同时属于不同的类,聚类分为重叠聚类(可以同时属于不同的类)与互斥聚类。
聚类有多种多样的算法,主要包括划分的方法、层次的方法、基于密度的方法、基于网格的方法、基于模型的方法。
层次聚类
层次聚类也称为谱系聚类,分为两种:自底向上的凝聚方法和自顶向下的分裂方法。
凝聚方法即将每个对象作为单独的一类,然后合并最近的两个类为一个类,如此循环,直到合成一个类,或达到终止条件为止。分裂方法即将所有的对象看做一个类,然后分解成两个离得最远的类,对新类循环分解,直到一个类只包含一个对象,或达到终止条件为止。
凝聚式层次聚类
基本步骤:
假设有N个待聚类的样本:
- 每个样本归为一类,计算每两个类之间的距离,也就是样本与样本之间的相似度;
- 寻找各个类之间最近的两个类,把他们归为一类(这样类的总数就少了一个);
- 重新计算新生成的这个类与各个旧类之间的相似度;
- 重复2和3直到所有样本点都归为一类,或者某个终结条件被满足(比如类之间的相似度小于设定的阈值),则结束。
整个聚类过程其实是建立了一棵树,在建立的过程中,可以通过在第二步上设置一个阈值,当最近的两个类的距离大于这个阈值,则认为迭代可以终止。
另外关键的一步就是第三步,如何判断两个类之间的相似度,这里涉及到两个概念:对象间的相似度和类间的相似度,它们均有多种度量方法:
对象间的相似度度量
对象间的相似度度量主要包括person相关系数、夹角余弦、广义JACCARD系数,如果变量均为数值型的话,还可以用距离度量、包括欧氏距离、曼哈顿距离、切比雪夫距离等。本文运用了夹角余弦。
对于空间中两向量,,此公式求得它们分别与原点的连线形成的夹角。两向量越相似,夹角越小,此值越接近于1。
类间的相似度度量
类间距离的度量方法主要包括最短距离、最长距离、类平均距离、重心距离、离差平方和距离。本文采用了重心距离:
分别计算每个聚类的重心(样本在每个维度上的均值),用两个聚类的重心的距离作为类间距离。
具体实现
在具体代码中建立了两个类,描述聚类的类Cluster和主类HierarchicalCluster。
数据结构
Cluster
为一个二叉树,叶子结点中的data域存放以一维数组表示的各个样本,其中数组中的各个元素表示样本的各个属性。其余结点的data域为空。
HierarchicalCluster
为主类。在主类中将各个Cluster存储在线性表中,进行处理后返回聚类过后的cluster形成的线性表。
具体步骤
1. 初始化
将用二维数组存储的每一条一维数据提取出来,分别放入一个BiTreeNode的data域中,并将这个结点作为根结点生成一个Cluster对象,存放于线性表que中。
关键代码:
//初始化聚类表
for(int i = 0; i < dataList.length; i++){
BiTreeNode<double[]> node = new BiTreeNode(dataList[i]);
Cluster cluster = new Cluster(node,n);
try{
que.insert(i,cluster);
}
catch(Exception e){System.out.println("初始化失败");}
}
2. 余弦相似度计算
运用双层循环,计算任意两个Cluster的重心的余弦相似度,保存余弦相似度最大的两个Cluster所在的位置。
关键代码:
for(int i=0;i<que.length();i++){
for(int j=i+1;j<que.length();j++){
//求两聚类的重心的余弦相似度
Cluster c1 = new Cluster();
Cluster c2 = new Cluster();
double[] avg1 = new double[]{0,0,0};
double[] avg2 = new double[]{0,0,0};
double sim = 0;
if(i!=j){
try{
c1 = que.get(i);
c2 = que.get(j);
avg1 = c1.getAvg();
avg2 = c2.getAvg();
sim = pointMulti(avg1,avg2)/(Math.sqrt(squares(avg1))*Math.sqrt(squares(avg2)));
}
catch(Exception e){System.out.println("计算余弦相似度失败!");
if(sim>max){
max = sim;
mergeIndex1 = i;
mergeIndex2 = j;
}
}
}
}
其中:
//计算叶子结点个数
public int countLeaf(BiTreeNode<double[]> T){
if (T == null)
return 0;
if (T.getLchild()== null&&T.getRchild()== null)
return 1;
else
return countLeaf(T.getLchild()) + countLeaf(T.getRchild());
}
//计算每个结点的代数和
public double[] getSum(BiTreeNode<double[]> Root,double[] sum) {
if (Root != null) {
//访问根结点
if(Root.getData() != null){
for(int i=0;i<n;i++){
sum[i] = sum[i]+ Root.getData()[i];
}
}
else{
getSum(Root.getLchild(),sum); //先根遍历左子树
getSum(Root.getRchild(),sum); //先根遍历右子树
}
}
return sum;
}
//计算聚类重心
public double[] getAvg(){
double[] avg = new double[n];
double[] sum = new double[n];
sum = getSum(root,sum);
for(int j=0;j<n;j++){
avg[j] = sum[j]/countLeaf(root);
}
return avg;
}
// 求平方和
private double squares(double[] data) {
double result = 0;
for (int i=0; i<data.length;i++) {
result = result+data[i]*data[i];
}
return result;
}
// 点乘
private double pointMulti(double[] data1, double[] data2) throws Exception {
if(data1.length != data2.length)
throw new Exception("两个序列长度不匹配");
double result = 0;
for (int j=0; j<data1.length;j++) {
result = result + (data1[j] * data2[j]);
}
return result;
}
3. 合并聚类
将第二步保存的两个Cluster合并,根结点分别作为新Cluster的根结点的左、右子结点,将第二步保存的Cluster从que中移除,将新生成的Cluster保存在que中。
关键代码:
private SqList<Cluster> mergeCluster(SqList<Cluster> clusters, int mergeIndex1, int mergeIndex2,int n) {
if (mergeIndex1 != mergeIndex2) {
try{
Cluster cluster1 = clusters.get(mergeIndex1);
Cluster cluster2 = clusters.get(mergeIndex2);
BiTreeNode newNode = new BiTreeNode();
newNode.setLchild(cluster1.getRoot());
newNode.setRchild(cluster2.getRoot());
Cluster newCluster = new Cluster(newNode,n);
clusters.remove(mergeIndex1);
clusters.remove(mergeIndex2-1);
clusters.insert(clusters.length(),newCluster);
}
catch(Exception e){System.out.println("merge失败");}
}
return clusters;
}
4. 终止条件
重复2,3步,直到只剩下一个Cluster或者相似度最大的两个类间的相似度小于阈值。
实际应用
数据集简介
用户认知数据集来源于UCI机器学习数据库。
数据集从五个维度描述了用户关于直流发电机这一学科的认知水平。这五个维度包括:用户花在目标学科内容上的学习时间多少程度、用户对于目标学科内容的重复次数多少程度、用户花在目标学科相关事物上的时间多少程度、用户在相关学科上的考试表现、用户在目标学科上的考试表现。
希望通过聚类分析将用户分为不同的知识水平组。
聚类结果
将数据集导入程序(代码见附录),对其进行聚类,通过不断地调整余弦相似度阈值,得到合适的聚类(最终使用的相似度阈值为0.94)。
因为数据集为五维数据,普通的方法难以在平面上可视化。因此这里使用了平行坐标这一种多维可视化技术。图中每个垂直的线代表一个属性。数据集的一行数据在平行坐标图中用一条折线表示,纵向是属性值,横向是属性类别。可能是一类的数据点的折线位置会更加接近。使用python绘图如下:
其中:STG代表目标课程学习时间,SCG代表目标课程材料重复次数,STR代表相关课程学习时间,LPR代表相关课程考试成绩,PEG代表目标课程考试成绩。图中将各个离散点(聚类1,2,3,4,5,6,7,8均为离散点)处理为黑色,其余每个颜色代表一个聚类。
各个聚类分别作图如下:
由上图可以看出,各个聚类都体现出了自己的特点。如:类型9的学生相关课程学习时间非常高,但是其他的指标都较低。类型18的学生在目标课程和相关课程上花的时间都比较多,虽然重复次数不多而且相关课程的考试成绩不高,但是在目标课程中却取得了非常好的成绩。
但是也产生了一些样本量非常小的聚类,如聚类10,11。事实上,在调整阈值的过程中,始终面临着这样的问题:有的聚类的样本数已经比较少的时候,有的聚类的样本数仍然过多以至于没有明显的统一的特征。要使得每个聚类都体现出自己的特征,有的聚类样本数会特别少,以至于有聚类过细的嫌疑。可能的原因是,不同的聚类之间的距离阈值不是统一的,对于不同的聚类组,即使类间相似度相同,有的也应该合并为一类,而有的应该分别存在。
可以从以下几个方面改进:1.用聚类数量而非相似度阈值来作为聚类结束的条件,更加精确地调整参数。2.改换样本相似度度量方法。3.改变类间距离度量方法。4.尝试其他聚类算法,如支持向量机、划分的方法、基于密度的方法(如DBSCAN)等。
结语
本文成功地运用java中的二叉树结构和线性表结构实现了凝聚型层次聚类,并将其应用于对网课学习者的认知水平聚类。
凝聚性层次聚类算法描述起来比较简单,不要求我们指定聚类的数量,不需要进行预估。此外,该算法对距离度量的选择不敏感,它们的效果都很好,而对于其他聚类算法,距离度量的选择是至关重要的。它还有一个重要的优点,尤其是在本文中用树形结构实现的时候,可以恢复底层数据的层次结构,可以选择聚类的程度。而其他的聚类算法无法做到这一点。
层次聚类的优点是以低效率为代价的。为了寻找距离最近的两个类,需要用到双重循环,每次都需要计算每两个聚类之间的距离,计算量较大。另外从算法中可以看出,每次迭代都只能合并两个子类,这是非常慢的。
在对网课学习者的认知水平聚类中,该算法有着不错的表现,能够分离出一些非常具有特色的聚类。
完整代码
注:数据结构库包为自己写的二叉树、结点、顺序表,无法直接运行,需改成java内置的对应类使用。
package hierarchicalCluster
//数据结构库包(主要算法使用的库包)
import dataStructure.linearList.SqList;
import dataStructure.tree.BiTree;
import dataStructure.tree.BiTreeNode;
import dataStructure.linearList.Node;
//读取数据所用库包
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;
/*
*当数据量较小时,可运行备注掉的代码,展现计算的完整过程
*/
public class HierarchicalCluster{
public static void main(String[] args) throws Exception{
HierarchicalCluster test = new HierarchicalCluster();
//读取数据集:用户认知数据集
double[][] data = getCsvDataNew("...\\userKnowledge.csv");//测试数据:double[][] data = new double[][]{{1,2,3},{1,2,4},{7,8,9},{7,8,6},{45,6,56}};
SqList<Cluster> result;
//分析、聚类。其中0.94为聚类余弦相似度阈值,当距离最近的类的相似度小于阈值时,停止聚类
result = test.analyze(data,0.94);
//输出聚类结果
System.out.println("共形成"+result.length()+"个聚类。");
for(int i=0;i<result.length();i++){
System.out.println("第"+i+"个聚类中的数据为:");
result.get(i).print(result.get(i).getRoot());
}
}
public SqList<Cluster> analyze(double[][] dataList, double threshold){
int n = dataList[0].length;
SqList<Cluster> que = new SqList(dataList.length);
//初始化聚类表
for(int i = 0; i < dataList.length; i++){
BiTreeNode<double[]> node = new BiTreeNode(dataList[i]);
Cluster cluster = new Cluster(node,n);
try{
que.insert(i,cluster);
}
catch(Exception e){System.out.println("cluster插入que失败");}
}
System.out.println("初始化:");
for(int i=0;i<que.length();i++){
System.out.println("第"+i+"个聚类中的数据为:");
try{
que.get(i).print(que.get(i).getRoot());
}
catch(Exception e){System.out.println("获得que数据失败");}
}
int cnt = 1;
while(que.length()>1){
/*测试用
System.out.println();
System.out.println("第"+cnt+"次循环:");
*/
double max = Double.MIN_VALUE;
int mergeIndex1 = 0;
int mergeIndex2 = 0;
for(int i=0;i<que.length();i++){
for(int j=i+1;j<que.length();j++){
//求两聚类的重心的余弦相似度
Cluster c1 = new Cluster();
Cluster c2 = new Cluster();
double[] avg1 = new double[]{0,0,0};
double[] avg2 = new double[]{0,0,0};
double sim = 0;
if(i!=j){
try{
c1 = que.get(i);
c2 = que.get(j);
avg1 = c1.getAvg();
avg2 = c2.getAvg();
sim = pointMulti(avg1,avg2)/(Math.sqrt(squares(avg1))*Math.sqrt(squares(avg2)));
}
catch(Exception e){System.out.println("计算余弦相似度失败!");}
/*测试用
System.out.println();
System.out.println("比较的两组数据为:");
System.out.print("c1:");
c1.print(c1.getRoot());
System.out.print("c2:");
c2.print(c2.getRoot());
try{
System.out.print("avg1:");
for(int m=0;m<n;m++){
System.out.print(avg1[m]+",");
}
System.out.print("avg2:");
for(int k=0;k<n;k++){
System.out.print(avg2[k]+",");
}
}
catch(Exception e){System.out.println("打印avg失败!");}
System.out.println("余弦相似度为:"+sim);
*/
if(sim>max){
max = sim;
mergeIndex1 = i;
mergeIndex2 = j;
}
}
}
}
/*测试用
System.out.println("本次循环结束!最大相似度为:"+max);
System.out.println();
*/
if(max < threshold) {
System.out.println("相似度小于指定的阈值,聚类结束!");
System.out.println();
break;
}
/*测试用
System.out.println("合并两个聚类!");
System.out.println("这两个聚类分别为:");
try{
System.out.println("c1:");
que.get(mergeIndex1).print(que.get(mergeIndex1).getRoot());
System.out.println("c2:");
que.get(mergeIndex2).print(que.get(mergeIndex2).getRoot());
System.out.println();
}
catch(Exception e){System.out.println("打印候选合并聚类失败!");}
*/
que = mergeCluster(que, mergeIndex1, mergeIndex2,n);
/*测试用
System.out.println("合并后的聚类组合为:");
for(int i=0;i<que.length();i++){
System.out.println("第"+i+"个聚类中的数据为:");
try{
que.get(i).print(que.get(i).getRoot());
}
catch(Exception e){System.out.println("获得que数据失败");}
}
*/
cnt = cnt + 1;
}
return que;
}
private SqList<Cluster> mergeCluster(SqList<Cluster> clusters, int mergeIndex1, int mergeIndex2,int n) {
if (mergeIndex1 != mergeIndex2) {
try{
Cluster cluster1 = clusters.get(mergeIndex1);
Cluster cluster2 = clusters.get(mergeIndex2);
BiTreeNode newNode = new BiTreeNode();
newNode.setLchild(cluster1.getRoot());
newNode.setRchild(cluster2.getRoot());
Cluster newCluster = new Cluster(newNode,n);
clusters.remove(mergeIndex1);
clusters.remove(mergeIndex2-1);
clusters.insert(clusters.length(),newCluster)
}
catch(Exception e){System.out.println("merge失败");}
}
return clusters;
}
// 求平方和
private double squares(double[] data) {
double result = 0;
for (int i=0; i<data.length;i++) {
result = result+data[i]*data[i];
}
return result;
}
// 点乘法
private double pointMulti(double[] data1, double[] data2) throws Exception {
if(data1.length != data2.length)
throw new Exception("两个序列长度不匹配");
double result = 0;
for (int j=0; j<data1.length;j++) {
result = result + (data1[j] * data2[j]);
}
return result;
}
//读取csv文件转化为二维数组
public static double[][] getCsvDataNew(String filePath) throws IOException{
BufferedReader br = new BufferedReader(new FileReader(new File(filePath)));
String line = "";
ArrayList<double[]> lineList = new ArrayList<double[]>();
// 逐行读取
while((line = br.readLine()) != null) {
StringTokenizer st = new StringTokenizer(line, ","); // 以逗号作为分隔符
double[] currCol = new double[5];
for(int i = 0; i <4; i++) {
//先判断是否还有待读取数据,防止溢出
if(st.hasMoreTokens()){
currCol[i] = Double.parseDouble(st.nextToken());
}
}
lineList.add(currCol);
}
//写入二维数组
double[][] data = new double[258][5];
for(int i = 0; i < 258; i++) {
for(int j = 0; j < 5; j++) {
data[i][j] = lineList.get(i)[j];
//System.out.println(str[i][x]);
}
}
br.close();
return data;
}
}
class Cluster extends BiTree<double[]>{
protected BiTreeNode<double[]> root; // 树的根结点
public int n;
// 构造一个空聚类
public Cluster() {
this.root=null;
}
// 构造一个根结点为root,数据维数为n的聚类
public Cluster(BiTreeNode<double[]> root,int n) {
this.root=root;
this.n = n;
}
public BiTreeNode<double[]> getRoot(){
return this.root;
}
//计算叶子结点个数
public int countLeaf(BiTreeNode<double[]> T){
if (T == null)
return 0;
if (T.getLchild()== null&&T.getRchild()== null)
return 1;
else
return countLeaf(T.getLchild()) + countLeaf(T.getRchild());
}
//计算每个结点的代数和
public double[] getSum(BiTreeNode<double[]> Root,double[] sum) {
if (Root != null) {
//访问根结点
if(Root.getData() != null){
for(int i=0;i<n;i++){
sum[i] = sum[i]+ Root.getData()[i];
}
}
else{
getSum(Root.getLchild(),sum); //先根遍历左子树
getSum(Root.getRchild(),sum); //先根遍历右子树
}
}
return sum;
}
//计算聚类重心
public double[] getAvg(){
double[] avg = new double[n];
double[] sum = new double[n];
sum = getSum(root,sum);
for(int j=0;j<n;j++){
avg[j] = sum[j]/countLeaf(root);
}
return avg;
}
//打印
public void print(BiTreeNode<double[]> Root){
if (Root != null) {
//访问根结点
if (Root.getData() != null){
int len = Root.getData().length;
for(int i=0;i<n;i++){
System.out.print(Root.getData()[i]+",");
}
System.out.println();
}
else{
print(Root.getLchild()); //先根遍历左子树
print(Root.getRchild()); //先根遍历右子树
}
}
else
System.out.println("聚类为空!");
}
}
计算过程
加上测试代码,使用测试数据展现的计算过程:
测试数据:
double[][] data = new
double[][]{{1,2,3},{1,2,4},{7,8,9},{7,8,6},{45,6,56}};
threshold = 0.94
运行结果:
初始化:
第0个聚类中的数据为:
1.0,2.0,3.0,
第1个聚类中的数据为:
1.0,2.0,4.0,
第2个聚类中的数据为:
7.0,8.0,9.0,
第3个聚类中的数据为:
7.0,8.0,6.0,
第4个聚类中的数据为:
45.0,6.0,56.0,
第1次循环:
比较的两组数据为:
c1:1.0,2.0,3.0,
c2:1.0,2.0,4.0,
avg1:1.0,2.0,3.0,avg2:1.0,2.0,4.0,余弦相似度为:0.9914601339836675
比较的两组数据为:
c1:1.0,2.0,3.0,
c2:7.0,8.0,9.0,
avg1:1.0,2.0,3.0,avg2:7.0,8.0,9.0,余弦相似度为:0.9594119455666703
比较的两组数据为:
c1:1.0,2.0,3.0,
c2:7.0,8.0,6.0,
avg1:1.0,2.0,3.0,avg2:7.0,8.0,6.0,余弦相似度为:0.8976906560180992
比较的两组数据为:
c1:1.0,2.0,3.0,
c2:45.0,6.0,56.0,
avg1:1.0,2.0,3.0,avg2:45.0,6.0,56.0,余弦相似度为:0.8341461350358422
比较的两组数据为:
c1:1.0,2.0,4.0,
c2:7.0,8.0,9.0,
avg1:1.0,2.0,4.0,avg2:7.0,8.0,9.0,余弦相似度为:0.9243607564425567
比较的两组数据为:
c1:1.0,2.0,4.0,
c2:7.0,8.0,6.0,
avg1:1.0,2.0,4.0,avg2:7.0,8.0,6.0,余弦相似度为:0.8402239881553326
比较的两组数据为:
c1:1.0,2.0,4.0,
c2:45.0,6.0,56.0,
avg1:1.0,2.0,4.0,avg2:45.0,6.0,56.0,余弦相似度为:0.8505900813221634
比较的两组数据为:
c1:7.0,8.0,9.0,
c2:7.0,8.0,6.0,
avg1:7.0,8.0,9.0,avg2:7.0,8.0,6.0,余弦相似度为:0.9822509826983282
比较的两组数据为:
c1:7.0,8.0,9.0,
c2:45.0,6.0,56.0,
avg1:7.0,8.0,9.0,avg2:45.0,6.0,56.0,余弦相似度为:0.8634593051884
比较的两组数据为:
c1:7.0,8.0,6.0,
c2:45.0,6.0,56.0,
avg1:7.0,8.0,6.0,avg2:45.0,6.0,56.0,余弦相似度为:0.7943422873540771
本次循环结束!最大相似度为:0.9914601339836675
合并两个聚类!
这两个聚类分别为:
c1:
1.0,2.0,3.0,
c2:
1.0,2.0,4.0,
合并后的聚类组合为:
第0个聚类中的数据为:
7.0,8.0,9.0,
第1个聚类中的数据为:
7.0,8.0,6.0,
第2个聚类中的数据为:
45.0,6.0,56.0,
第3个聚类中的数据为:
1.0,2.0,3.0,
1.0,2.0,4.0,
第2次循环:
比较的两组数据为:
c1:7.0,8.0,9.0,
c2:7.0,8.0,6.0,
avg1:7.0,8.0,9.0,avg2:7.0,8.0,6.0,余弦相似度为:0.9822509826983282
比较的两组数据为:
c1:7.0,8.0,9.0,
c2:45.0,6.0,56.0,
avg1:7.0,8.0,9.0,avg2:45.0,6.0,56.0,余弦相似度为:0.8634593051884
比较的两组数据为:
c1:7.0,8.0,9.0,
c2:1.0,2.0,3.0,
1.0,2.0,4.0,
avg1:7.0,8.0,9.0,avg2:1.0,2.0,3.5,余弦相似度为:0.9421088589154201
比较的两组数据为:
c1:7.0,8.0,6.0,
c2:45.0,6.0,56.0,
avg1:7.0,8.0,6.0,avg2:45.0,6.0,56.0,余弦相似度为:0.7943422873540771
比较的两组数据为:
c1:7.0,8.0,6.0,
c2:1.0,2.0,3.0,
1.0,2.0,4.0,
avg1:7.0,8.0,6.0,avg2:1.0,2.0,3.5,余弦相似度为:0.8678906159156443
比较的两组数据为:
c1:45.0,6.0,56.0,
c2:1.0,2.0,3.0,
1.0,2.0,4.0,
avg1:45.0,6.0,56.0,avg2:1.0,2.0,3.5,余弦相似度为:0.8449861937503832
本次循环结束!最大相似度为:0.9822509826983282
合并两个聚类!
这两个聚类分别为:
c1:
7.0,8.0,9.0,
c2:
7.0,8.0,6.0,
合并后的聚类组合为:
第0个聚类中的数据为:
45.0,6.0,56.0,
第1个聚类中的数据为:
1.0,2.0,3.0,
1.0,2.0,4.0,
第2个聚类中的数据为:
7.0,8.0,9.0,
7.0,8.0,6.0,
第3次循环:
比较的两组数据为:
c1:45.0,6.0,56.0,
c2:1.0,2.0,3.0,
1.0,2.0,4.0,
avg1:45.0,6.0,56.0,avg2:1.0,2.0,3.5,余弦相似度为:0.8449861937503832
比较的两组数据为:
c1:45.0,6.0,56.0,
c2:7.0,8.0,9.0,
7.0,8.0,6.0,
avg1:45.0,6.0,56.0,avg2:7.0,8.0,7.5,余弦相似度为:0.8348742473828404
比较的两组数据为:
c1:1.0,2.0,3.0,
1.0,2.0,4.0,
c2:7.0,8.0,9.0,
7.0,8.0,6.0,
avg1:1.0,2.0,3.5,avg2:7.0,8.0,7.5,余弦相似度为:0.9114804256078952
本次循环结束!最大相似度为:0.9114804256078952
合并两个聚类!
这两个聚类分别为:
c1:
1.0,2.0,3.0,
1.0,2.0,4.0,
c2:
7.0,8.0,9.0,
7.0,8.0,6.0,
合并后的聚类组合为:
第0个聚类中的数据为:
45.0,6.0,56.0,
第1个聚类中的数据为:
1.0,2.0,3.0,
1.0,2.0,4.0,
7.0,8.0,9.0,
7.0,8.0,6.0,
第4次循环:
比较的两组数据为:
c1:45.0,6.0,56.0,
c2:1.0,2.0,3.0,
1.0,2.0,4.0,
7.0,8.0,9.0,
7.0,8.0,6.0,
avg1:45.0,6.0,56.0,avg2:4.0,5.0,5.5,余弦相似度为:0.8512575307860879
本次循环结束!最大相似度为:0.8512575307860879
相似度小于指定的阈值,聚类结束!
共形成2个聚类。
第0个聚类中的数据为:
45.0,6.0,56.0,
第1个聚类中的数据为:
1.0,2.0,3.0,
1.0,2.0,4.0,
7.0,8.0,9.0,
7.0,8.0,6.0,
Process completed.
下一篇: nginx的安装以及配置