蛙蛙池塘
人生价值的最好体现就是做好本职工作...
posts - 182,  comments - 1553,  trackbacks - 55

蛙蛙推荐:蛙蛙教你文本聚类

摘要:文本聚类是搜索引擎和语义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;
    }