注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Koala++'s blog

计算广告学 RTB

 
 
 

日志

 
 

Weka开发[50]——ClusterEvaluation源代码分析  

2011-01-31 16:08:39|  分类: 机器学习 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

         聚类评估方法知名度不比分类的评估方法,分类的评估方法在我在的公司里,连不懂程序的人都明白,聚类可能连专门做数据挖掘人的都不知道,有空我专门写一点东西。

         下面的代码是使用weka中的评估方法,它就我翻译的Weka[-1]中的代码:

public static void main(String[] args) throws Exception {

    /* 读取数据,设置类别属性下标 */

    Instances data = new Instances(new BufferedReader(new FileReader(

           "D:\\Program Files\\Weka-3-6\\data\\iris.arff")));

    data.setClassIndex(data.numAttributes() - 1);

 

    /* 产生无类别的数据,并用下面代码训练 */

    weka.filters.unsupervised.attribute.Remove filter = new

weka.filters.unsupervised.attribute.Remove();

    filter.setAttributeIndices("" + (data.classIndex() + 1));

    filter.setInputFormat(data);

    Instances dataClusterer = Filter.useFilter(data, filter);

 

    /* 学习一个clusterer,比如EM */

    EM clusterer = new EM();

    // set further options for EM, if necessary...

    clusterer.buildClusterer(dataClusterer);

 

    /* 用仍然包含类别属性的数据集评价这个clusterer */

    ClusterEvaluation eval = new ClusterEvaluation();

    eval.setClusterer(clusterer);

    eval.evaluateClusterer(data);

 

    /* 输出评价结果 */

    System.out.println(eval.clusterResultsToString());

}

         重点当然是evaluateClusterer函数:

public void evaluateClusterer(Instances test) throws Exception {

    evaluateClusterer(test, "");

}

         调用重载的evaluateClusterer,因为就是用这个instances去评价,所以不用设置文件名。将重载的函数拆开来看:

// If class is set then do class based evaluation as well

if (hasClass) {

    filter = new Remove();

    ((Remove) filter).setAttributeIndices(""

           + (testRaw.classIndex() + 1));

    ((Remove) filter).setInvertSelection(false);

    filter.setInputFormat(testRaw);

}

         将类别属性列去掉的Remove过滤器准备好。

i = 0;

while (source.hasMoreElements(testRaw)) {

    // next instance

    inst = source.nextElement(testRaw);

    if (filter != null) {

       filter.input(inst);

       filter.batchFinished();

       inst = filter.output();

    }

 

    cnum = -1;

    try {

       if (m_Clusterer instanceof DensityBasedClusterer) {

           loglk += ((DensityBasedClusterer) m_Clusterer)

                  .logDensityForInstance(inst);

           cnum = m_Clusterer.clusterInstance(inst);

           clusterAssignments.add((double) cnum);

       } else {

           cnum = m_Clusterer.clusterInstance(inst);

           clusterAssignments.add((double) cnum);

       }

    } catch (Exception e) {

       clusterAssignments.add(-1.0);

       unclusteredInstances++;

    }

 

    if (cnum != -1) {

       instanceStats[cnum]++;

    }

}

         循环数据集中的每个instance,首先先要用过滤器将它的类别属性去掉。然后会判断它是不是基于密度的聚类,如果是,就先得到它的log密度,和它的簇号,如果不是基于密度的那么只用得到它的簇号。clusterAssignments中保存着每个样本的簇号,instanceStas中保存着每个簇中的样本数。我真是感觉这段代码写的很奇怪,先是Filter,明明就是个Remove,何苦定义为Filter,用的时候再强制转换,然后上面的代码完全可以写成下面这样:

if (m_Clusterer instanceof DensityBasedClusterer) {

    loglk += ((DensityBasedClusterer) m_Clusterer)

           .logDensityForInstance(inst);

}  

 

cnum = m_Clusterer.clusterInstance(inst);

clusterAssignments.add((double) cnum);

         这样不是看起来更清爽?

double sum = Utils.sum(instanceStats);

loglk /= sum;

m_logL = loglk;

m_clusterAssignments = new double[clusterAssignments.size()];

for (i = 0; i < clusterAssignments.size(); i++)

    m_clusterAssignments[i] = clusterAssignments.get(i);

int numInstFieldWidth = (int) ((Math.log(clusterAssignments.size())

/ Math.log(10)) + 1);

         这段代码是将clusterAssignments中的数据再转存到m_clusterAssignments中,numInstFieldWidth只是在下面的将Double转换成String的时候的长度参数。

m_clusteringResults.append(m_Clusterer.toString());

m_clusteringResults.append("Clustered Instances\n\n");

int clustFieldWidth = (int) ((Math.log(cc) / Math.log(10)) + 1);

for (i = 0; i < cc; i++) {

    if (instanceStats[i] > 0)

       m_clusteringResults.append(Utils.doubleToString((double) i,

              clustFieldWidth, 0)

              + "      "

              + Utils.doubleToString(instanceStats[i],

                     numInstFieldWidth, 0)

              + " ("

              + Utils.doubleToString(

                     (instanceStats[i] / sum * 100.0), 3, 0)

              + "%)\n");

}

         输出结果选将clusterer的信息加入,再是追加簇号,每个簇中的样本数,和所占比例。

if (unclusteredInstances > 0)

    m_clusteringResults.append("\nUnclustered instances : "

           + unclusteredInstances);

 

if (m_Clusterer instanceof DensityBasedClusterer)

    m_clusteringResults.append("\n\nLog likelihood: "

           + Utils.doubleToString(loglk, 1, 5) + "\n");

         将无法分类的样本数打印出来,还有log likelihood

if (hasClass)

    evaluateClustersWithRespectToClass(test, testFileName);

         与原始类别对比评估。

         evaluateClustersWithRespectToClass拆开来看:

i = 0;

while (source.hasMoreElements(instances)) {

    instance = source.nextElement(instances);

    if (m_clusterAssignments[i] >= 0) {

       counts[(int) m_clusterAssignments[i]][(int) instance

              .classValue()]++;

       clusterTotals[(int) m_clusterAssignments[i]]++;

    }

    i++;

}

numInstances = i;

         counts里第一维是簇号,第二维是类别值,即二维数组中每个元素是在某个簇中又属于某个类别的样本数是多少。culsterTotals元素是中的每个簇中的样本数。

best[m_numClusters] = Double.MAX_VALUE;

mapClasses(m_numClusters, 0, counts, clusterTotals, current, best, 0);

         mapClasses当然是一个重要的函数了,因为它后面都是输出了嘛。

// leaf

if (lev == numClusters) {

    if (error < best[numClusters]) {

       best[numClusters] = error;

       for (int i = 0; i < numClusters; i++) {

           best[i] = current[i];

       }

    }

}

         这是退出条件,当层数为簇数时,如果以前结果不是最优,则复制当前结果到best中。

else {

    // empty cluster -- ignore

    if (clusterTotals[lev] == 0) {

       current[lev] = -1; // cluster ignored

       mapClasses(numClusters, lev + 1, counts, clusterTotals,

              current, best, error);

    } else {

       // first try no class assignment to this cluster

       current[lev] = -1; // cluster assigned no class (ie all errors)

       mapClasses(numClusters, lev + 1, counts, clusterTotals,

              current, best, error + clusterTotals[lev]);

       // now loop through the classes in this cluster

       for (int i = 0; i < counts[0].length; i++) {

           if (counts[lev][i] > 0) {

              boolean ok = true;

              // check to see if this class has already been assigned

              for (int j = 0; j < lev; j++) {

                  if ((int) current[j] == i) {

                     ok = false;

                     break;

                  }

              }

              if (ok) {

                  current[lev] = i;

                  mapClasses(

                     numClusters,

                     lev + 1,

                     counts,

                     clusterTotals,

                     current,

                     best,

                     (error + (clusterTotals[lev] – counts[lev][i])));

              }

           }

       }

    }

}

         if中判断这一个簇是不是没有样本属于它,如果是直接进入下一层。在else中,先是会不管类别信息算一次,这时得到的结果应该是最差的,将下来对每个类别进行循环,它会判断这个类别是否被选中过,如果没有选中,再对这个类别进行递归,唯一值得一提的是clusterTotals[lev]-counts[lev][i]的意思是将这个簇的样本数减去这个簇中属于类别i样本数减掉,也就是说如果这个簇中属于某个类别的样本多,那么它的error值就低,这个类别就越可能成为best

 

 

 

 

 

 

 

 

 

 

  评论这张
 
阅读(3000)| 评论(2)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017