chenglong7997

教你文本聚类(转)

教你文本聚类

2009-08-23 18:32 189人阅读 评论(0) 收藏 举报

摘 要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一起学习一下简单的文本聚类算法,可能不能直接用于实际应用中,但对于想学搜索技术的初学 者还是有一定入门作用的。这里会用到TF/IDF权重,用余弦夹角计算文本相似度,用方差计算两个数据间欧式距离,用k-means进行数据聚类等数学和 统计知识。关于这些概念可以去google,或者参考文本后的参考链接。

思路:计算两篇文档的相似度,最简单的做法就是用提取文档的TF/IDF权重,然后用余弦定理计算两个多维向量的距离。能计算两个文本间的距离后,用标准的k-means算法就可以实现文本聚类了。

测试:首先我们准备以下数据
===================
奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
股民 要 清楚 自己 的 目的
印花税 之 股民 四季
杭州 股民 放 鞭炮 庆祝 印花税 下调 
残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
介绍 一 个 ASP.net MVC 系列 教程
在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
输 大钱 的 股民 给 我们 启迪
Asp.Net 页面 执行 流程 分析
运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
asp.net 控件 开发 显示 控件 内容
奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析
==================
很明显以上数据可以分为三类:asp.net,奥运和股民,我们就写程序来实现它,各种算法的原理网上都有,我就大概只贴代码,声明一下,部分代码是从网 上直接抄的,k-means代码是我从一篇文章的java示例代码转换过来的,我给代码加了不少注释,希望能帮助大家理解。

以下是入口函数

 static   void  Main( string [] args)
 
{
     
 // 1、获取文档输入 
 
     string [] docs  =  getInputDocs( " input.txt " );
     
 if  (docs.Length  <   1 )
     
 {
         Console.WriteLine(
 " 没有文档输入 " );
         Console.Read();
         
 return ;
     }
 

 
     
 // 2、初始化TFIDF测量器,用来生产每个文档的TFIDF权重 
 
    TFIDFMeasure tf  =   new  TFIDFMeasure(docs,  new  Tokeniser());
 
     
 int  K  =   3  // 聚成3个聚类
 
     
 // 3、生成k-means的输入数据,是一个联合数组,第一维表示文档个数,
     
 // 第二维表示所有文档分出来的所有词 
 
     double [][] data  =   new   double [docs.Length][];
     
 int  docCount  =  docs.Length;  // 文档个数 
 
     int  dimension  =  tf.NumTerms; // 所有词的数目 
 
     for  ( int  i  =   0 ; i  <  docCount; i ++ )
     
 {
         
 for  ( int  j  =   0 ; j  <  dimension; j ++ )
         
 {
             data[i] 
 =  tf.GetTermVector2(i);  // 获取第i个文档的TFIDF权重向量 
 
        } 

     }
 

 
     
 // 4、初始化k-means算法,第一个参数表示输入数据,第二个参数表示要聚成几个类 
 
    WawaKMeans kmeans  =   new  WawaKMeans(data, K);
     
 // 5、开始迭代 
 
    kmeans.Start();
 
     
 // 6、获取聚类结果并输出 
 
    WawaCluster[] clusters  =  kmeans.Clusters;
     
 foreach  (WawaCluster cluster  in  clusters)
     
 {
         List
 < int >  members  =  cluster.CurrentMembership;
         Console.WriteLine(
 " ----------------- " );
         
 foreach  ( int  i  in  members)
         
 {
             Console.WriteLine(docs[i]);
         }
 

 
     }
 

     Console.Read();
 }
 

 


以下是分词器的主要代码

 ///   <summary> 
 
///  以空白字符进行简单分词,并忽略大小写,
 
///  实际情况中可以用其它中文分词算法
 
///   </summary> 
 
///   <param name="input"></param> 
 
///   <returns></returns> 

 public  IList < string >  Partition( string  input)
 
{
  Regex r
 = new  Regex( " ([ //t{}():;. /n]) " );  
  input
 = input.ToLower() ;
 
  String [] tokens
 = r.Split(input);          
 
  List
 < string >  filter = new   List < string > () ;
 
  
 for  ( int  i = 0 ; i  <  tokens.Length ; i ++ )
  
 {
   MatchCollection mc
 = r.Matches(tokens[i]);
   
 if  (mc.Count  <=   0   &&  tokens[i].Trim().Length  >   0        
    
 &&   ! StopWordsHandler.IsStopword (tokens[i]) )        
    filter.Add(tokens[i]) ;
         }
 

  
  
 return  filter.ToArray();
 }
 

 


以下是kmeans算法的基本代码

 public   class  WawaKMeans
 
{
     
 ///   <summary> 
     
 ///  数据的数量
     
 ///   </summary> 

      readonly   int  _coordCount;
     
 ///   <summary> 
     
 ///  原始数据
     
 ///   </summary> 

      readonly   double [][] _coordinates;
     
 ///   <summary> 
     
 ///  聚类的数量
     
 ///   </summary> 

      readonly   int  _k;
     
 ///   <summary> 
     
 ///  聚类
     
 ///   </summary> 

      private   readonly  WawaCluster[] _clusters;
 
     
 internal  WawaCluster[] Clusters
     
 {
         
 get    return  _clusters; } 
     }
 
 
 
     
 ///   <summary> 
     
 ///  定义一个变量用于记录和跟踪每个资料点属于哪个群聚类
     
 ///  _clusterAssignments[j]=i;// 表示第 j 个资料点对象属于第 i 个群聚类
     
 ///   </summary> 

      readonly   int [] _clusterAssignments;
     
 ///   <summary> 
     
 ///  定义一个变量用于记录和跟踪每个资料点离聚类最近
     
 ///   </summary> 

      private   readonly   int [] _nearestCluster;
     
 ///   <summary> 
     
 ///  定义一个变量,来表示资料点到中心点的距离,
     
 ///  其中—_distanceCache[i][j]表示第i个资料点到第j个群聚对象中心点的距离;
     
 ///   </summary> 

      private   readonly   double [,] _distanceCache;
     
 ///   <summary> 
     
 ///  用来初始化的随机种子
     
 ///   </summary> 

      private   static   readonly  Random _rnd  =   new  Random( 1 );
 
     
 public  WawaKMeans( double [][] data,  int  K)
     
 {
         _coordinates 
 =  data;
         _coordCount 
 =  data.Length;
         _k 
 =  K;
         _clusters 
 =   new  WawaCluster[K];
         _clusterAssignments 
 =   new   int [_coordCount];
         _nearestCluster 
 =   new   int [_coordCount];
         _distanceCache 
 =   new   double [_coordCount,data.Length];
         InitRandom();
     }
 

 
     
 public   void  Start()
     
 {
         
 int  iter  =   0 ;
         
 while  ( true )
         
 {
             Console.WriteLine(
 " Iteration  "   +  (iter ++  +   "  " );
             
 // 1、重新计算每个聚类的均值 
 
             for  ( int  i  =   0 ; i  <  _k; i ++ )
             
 {
                 _clusters[i].UpdateMean(_coordinates);
             }
 

 
             
 // 2、计算每个数据和每个聚类中心的距离 
 
             for  ( int  i  =   0 ; i  <  _coordCount; i ++ )
             
 {
                 
 for  ( int  j  =   0 ; j  <  _k; j ++ )
                 
 {
                     
 double  dist  =  getDistance(_coordinates[i], _clusters[j].Mean);
                     _distanceCache[i,j] 
 =  dist;
                 }
 

             }
 

 
             
 // 3、计算每个数据离哪个聚类最近 
 
             for  ( int  i  =   0 ; i  <  _coordCount; i ++ )
             
 {
                 _nearestCluster[i] 
 =  nearestCluster(i);
             }
 

 
             
 // 4、比较每个数据最近的聚类是否就是它所属的聚类
             
 // 如果全相等表示所有的点已经是最佳距离了,直接返回; 
 
             int  k  =   0 ;
             
 for  ( int  i  =   0 ; i  <  _coordCount; i ++ )
             
 {
                 
 if  (_nearestCluster[i]  ==  _clusterAssignments[i])
                     k
 ++ ;
 
             }
 

             
 if  (k  ==  _coordCount)
                 
 break ;
 
             
 // 5、否则需要重新调整资料点和群聚类的关系,调整完毕后再重新开始循环;
             
 // 需要修改每个聚类的成员和表示某个数据属于哪个聚类的变量 
 
             for  ( int  j  =   0 ; j  <  _k; j ++ )
             
 {
                 _clusters[j].CurrentMembership.Clear();
             }
 

             
 for  ( int  i  =   0 ; i  <  _coordCount; i ++ )
             
 {
                 _clusters[_nearestCluster[i]].CurrentMembership.Add(i);
                 _clusterAssignments[i] 
 =  _nearestCluster[i];
             }
 

             
         }
 

 
     }
 

 
     
 ///   <summary> 
     
 ///  计算某个数据离哪个聚类最近
     
 ///   </summary> 
     
 ///   <param name="ndx"></param> 
     
 ///   <returns></returns> 

      int  nearestCluster( int  ndx)
     
 {
         
 int  nearest  =   - 1 ;
         
 double  min  =  Double.MaxValue;
         
 for  ( int  c  =   0 ; c  <  _k; c ++ )
         
 {
             
 double  d  =  _distanceCache[ndx,c];
             
 if  (d  <  min)
             
 {
                 min 
 =  d;
                 nearest 
 =  c;
             }
 

       
         }
 

         
 if (nearest ==- 1 )
         
 {
             ;
         }
 

         
 return  nearest;
     }
 

     
 ///   <summary> 
     
 ///  计算某数据离某聚类中心的距离
     
 ///   </summary> 
     
 ///   <param name="coord"></param> 
     
 ///   <param name="center"></param> 
     
 ///   <returns></returns> 

      static   double  getDistance( double [] coord,  double [] center)
     
 {
         
 // int len = coord.Length;
         
 // double sumSquared = 0.0;
         
 // for (int i = 0; i < len; i++)
         
 // {
         
 //     double v = coord[i] - center[i];
         
 //     sumSquared += v * v;  // 平方差
         
 // }
         
 // return Math.Sqrt(sumSquared);
 
         
 // 也可以用余弦夹角来计算某数据离某聚类中心的距离 
 
         return   1 -  TermVector.ComputeCosineSimilarity(coord, center);
 
     }
 
 
     
 ///   <summary> 
     
 ///  随机初始化k个聚类
     
 ///   </summary> 

      private   void  InitRandom()
     
 {
         
 for  ( int  i  =   0 ; i  <  _k; i ++ )
         
 {
             
 int  temp  =  _rnd.Next(_coordCount);
             _clusterAssignments[temp] 
 =  i;  // 记录第temp个资料属于第i个聚类 
 
            _clusters[i]  =   new  WawaCluster(temp,_coordinates[temp]);
         }
 

     }
 

 }
 

 


以下是聚类实体类的定义

 internal   class  WawaCluster
 
{
     
 public  WawaCluster( int  dataindex, double [] data)
     
 {
         CurrentMembership.Add(dataindex);
         Mean 
 =  data;
     }
 

 
     
 ///   <summary> 
     
 ///  该聚类的数据成员索引
     
 ///   </summary> 

      internal  List < int >  CurrentMembership  =   new  List < int > ();
     
 ///   <summary> 
     
 ///  该聚类的中心
     
 ///   </summary> 

      internal   double [] Mean;
     
 ///   <summary> 
     
 ///  该方法计算聚类对象的均值 
     
 ///   </summary> 
     
 ///   <param name="coordinates"></param> 

      public   void  UpdateMean( double [][] coordinates)
     
 {
         
 //  根据 mCurrentMembership 取得原始资料点对象 coord ,该对象是 coordinates 的一个子集;
         
 // 然后取出该子集的均值;取均值的算法很简单,可以把 coordinates 想象成一个 m*n 的距阵 ,
         
 // 每个均值就是每个纵向列的取和平均值 ,  // 该值保存在 mCenter 中 
 

         
 for  ( int  i  =   0 ; i  <  CurrentMembership.Count; i ++ )
         
 {
             
 double [] coord  =  coordinates[CurrentMembership[i]];
             
 for  ( int  j  =   0 ; j  <  coord.Length; j ++ )
             
 {
                 Mean[j] 
 +=  coord[j];  //  得到每个纵向列的和; 
 
            } 

             
 for  ( int  k  =   0 ; k  <  Mean.Length; k ++ )
             
 {
                 Mean[k] 
 /=  coord.Length;  //  对每个纵向列取平均值 
 
            } 

         }
 

     }
 

 }
 

 


计算TF/IDF和利用余弦定理计算相似度的代码见完整版的代码下载,那两部分都是外国人写的,里面有它的联系方式,不懂的可以问他,反正我差不多懂了。

下面看看咱们的测试结果:
Iteration 0...
Iteration 1...
Iteration 2...
-----------------
奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
杭州 股民 放 鞭炮 庆祝 印花税 下调
残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
-----------------
股民 要 清楚 自己 的 目的
印花税 之 股民 四季
输 大钱 的 股民 给 我们 启迪
某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
-----------------
介绍 一 个 ASP.net MVC 系列 教程
在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
Asp.Net 页面 执行 流程 分析
asp.net 控件 开发 显示 控件 内容
ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析 
聚 类聚的非常准确,而且只迭代了3次,模型就收敛了,当然了这是最理想的效果,其实聚类的结果受好多种因素制约,提取特征的算法,随机初始化函 数,kmeans算法的实现等,都有优化的地方,不信你把输入的数据的顺序改改,聚类结果就不一样了,或者把随机数的种子变一下,结果也不一样,k- means算法加入一些变异系数的调整,结果也不一样,提取特征的地方不用TF/IDF权重算法用别的,结果肯定也不一样。
完整代码里还有另一组测试数据,结果也很不错,我的意思是我的算法不是针对一组测试数据,而是针对好多数据都有不错的结果。

总结:数学和英语真是写程序之根本呀,弄这个东西遇到了好多英语单词不会,查还查不出来,也理解不了,最后google一看,是个数学专用词,再搜索这个数学专用词的中文解释,发现还是理解不了那数学原理。所以还是得多学习数学和英语。

参考链接:
K-MEANS算法
http://beauty9235.javaeye.com/blog/161675
什么是变异系数
http://zhidao.baidu.com/question/15013015.html
TF/IDF实现
http://www.codeproject.com/KB/cs/tfidf.aspx

 

源码下载:WawaTextCluster.zip


http://www.cnblogs.com/onlytiancai/archive/2008/05/10/1191557.html 《来源》

posted on 2012-04-01 07:57 Snape 阅读(548) 评论(0)  编辑 收藏 引用 所属分类: C++ 转载


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2012年6月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

统计

常用链接

留言簿

随笔分类

随笔档案

文章分类

文章档案

my

搜索

最新评论

阅读排行榜

评论排行榜