聚类


前面做过一个神经网络的分类器 


现在有一些数据需要做聚类处理。 


那什么叫做聚类呢 跟分类有什么区别。


分类:明确知道类别,然后把数据归类。

聚类:你不知道类别,但你想把这些数据分成N类,根据某种算法把数据进行分组,相似或相近的自动归到一组中。(一般用k均值聚类算法)



聚类与分类相比较:

分类:实例式学习,分类前明确各个类别的信息,并可以直接断言每个元素映射到一个类别;

聚类:无监督学习,在聚类前不知道类别甚至不给定类别数量,不依赖预定义的类和类标号。


聚类的应用:

在许多时候分类条件不满足,尤其是处理海量数据时,如果通过预处理使数据满足分类算法的要求,代价巨大,应考虑聚类算法。

聚类的用途是很广泛的。在商业上,聚类可以帮助市场分析人员从消费者数据库中区分出不同的消费群体来,并且概括出每一类消费者的消费模式或者说习惯。它作为数据挖掘中的一个模块,可以作为一个单独的工具以发现数据库中分布的一些深层的信息,并且概括出每一类的特点,或者把注意力放在某一个特定的类上以作进一步的分析;并且,聚类分析也可以作为数据挖掘算法中其他分析算法的一个预处理步骤。

目前聚类广泛应用于统计学、生物学、数据库技术和市场营销等领域。

 

聚类算法的局限:

(1)要求数据分离度好。对相互渗透的类无法得到统一的结果。

(2)线性相关。聚类方法分析仅是简单的一对一的关系。忽视了生物系统多因素和非线性的特点。


K均值聚类算法

算法思想:

给定一个有N个元素的集合,划分为K个簇,每一个簇就代表一个聚类,K<N。而且这K个簇满足下列条件:

(1) 每一个簇至少包含一个元素;

(2) 每一个元素属于且仅属于一个簇。

对于给定的K,算法首先给出一个初始的分组方法,以后通过反复迭代的方法改变分组,使得每一次改进之后的分组方案都较前一次优化,即同一分组中的元素相异度降低,而不同分组中的元素相异度升高。

 

只要去重后集合的元素仍大于k时 才能进行聚类

 

算法过程:

1、预处理: 数据规格化;

(1).剔除异常数据  剔除异常数据的方法可考虑均值-标准差法


(2).(具体根据情况判断是否需要归一化)

归一化:

当某个属性的取值跨度远大于其他属性时,不利于真实反映真实的相异度,为了解决这个问题,一般要对属性值进行规格化。所谓规格化就是将各个属性值按比例映射到相同的取值区间,这样是为了平衡各个属性对距离的影响。通常将各个属性均映射到[0,1]区间,映射公式为:

 x'=x-集合最小值/集合最大值-集合最小值


2、从D中随机取k个元素,作为k个簇的各自的中心。

3、分别计算剩下的元素到k个簇中心的相异度,将这些元素分别划归到相异度最低的簇。

4、根据聚类结果,重新计算k个簇各自的中心,计算方法是取簇中所有元素各自维度的算术平均数。

5、将D中全部元素按照新的中心重新聚类。

6、重复第4步,直到聚类结果收敛到不再变化。

7、将结果输出。




我找了一些资料  发现 聚类的运用 一般有三种: 数值聚类(例如成绩聚类)   文本的聚类    坐标点的聚类


数值聚类:

数值聚类是比较简单的  因为它们能直接求均值而不用做其他的一些处理 

步骤是:

一: 随机产生是三个不重复的数值 作为 质心 

二:计算每一个数值到质心的距离  公式 :     Math.Sqrt((x-质心)*(x-质心))   数值减去质心平方后开方

三:把数值归入到距离最小的 质心所代表的类中 

四:计算每个类的均值作为质心,跟旧的质心做对比,如果不相等,则从步骤二开始 迭代。 直到质心值不再变化,这样类就分好了。


下面记录几段比较重要的代码;

1.生成不重复的随机数值

        /// <summary>
        /// get different random
        /// </summary>
        /// <param name="arrNum"></param>
        /// <param name="tmp"></param>
        /// <param name="minValue"></param>
        /// <param name="maxValue"></param>
        /// <param name="ra"></param>
        /// <returns></returns>
        public int[] getNum(int count, int total)
        {
            int[] index = new int[total];
            for (int i = 0; i < total; i++)
            {
                index[i] = i;
            }
            Random r = new Random();
            //用来保存随机生成的不重复的count个数  
            int[] result = new int[count];
            //int site = total;//设置下限  
            int id;
            for (int j = 0; j < count; j++)
            {
                id = r.Next(0, total - 1);
                //在随机位置取出一个数,保存到结果数组  
                result[j] = index[id];
                //最后一个数复制到当前位置  
                index[id] = index[total - 1];
                //位置的下限减少一  
                total--;
            }

            return result;

        }

判断新旧质心是否相等

        /// <summary>
        /// judge the value of center
        /// </summary>
        /// <param name="center"></param>
        /// <param name="newcenter"></param>
        /// <param name="ok"></param>
        /// <returns></returns>
        private static bool judge(double[] center, double[] newcenter, bool ok)
        {
            int count = 0;
            for (int i = 0; i < newcenter.Length; i++)
            {
                if (center[i] == newcenter[i])
                { count++; }


            }
            if (count == newcenter.Length)
            {
                ok = true;
            }
            return ok;
        }


当去重后集合的元素仍大于k时 聚类的过程 迭代 用一个标志 和while()

 if (price_all.Count >= k)
                {
                    //cluster the list building element
                    Random Rd = new Random();  //make a  random example



                    double[] center = new double[k];
                    double[] oldcenter = new double[k];
                    int[] ran = new int[k];
                    int temp_c = price_all.Count;
                    ran = getNum(k, temp_c);
                    for (int i = 0; i < center.Length; i++)
                    {


                        center[i] = price_all[ran[i]];


                    }



                    for (int i = 0; i < oldcenter.Length; i++)
                    {
                        oldcenter[i] = 0.0;
                    }

                    bool ok = false;

                    ok = judge(center, oldcenter, ok);



                    int ireation = 0;
                    while (!ok)
                    {

                        for (int i = 0; i < building_element.Count; i++)
                        {

                            //repeat cluster
                            double temp_price = building_element[i].get_price();
                            double[] distance = new double[k];
                            for (int j = 0; j < center.Length; j++)
                            {
                                double v = temp_price - center[j];
                                distance[j] = Math.Sqrt(v * v); // distance

                            }
                            //get the min distance
                            double temp_min = 999999999999999999;
                            int min_index = 999;
                            for (int j = 0; j < center.Length; j++)
                            {

                                if (distance[j] <= temp_min)
                                {
                                    temp_min = distance[j];
                                    min_index = j + 1;
                                }

                            }
                            building_element[i].set_type(min_index);



                        }

                        for (int n = 0; n < k; n++)
                        {
                            oldcenter[n] = center[n];
                        }


                        //get averange to  be center 
                        double[] total = new double[k];
                        int[] element_countoftype = new int[k];
                        for (int n = 0; n < k; n++)
                        {

                            for (int i = 0; i < building_element.Count; i++)
                            {
                                if (building_element[i].get_type() == n + 1)
                                {
                                    total[n] += building_element[i].get_price();
                                    element_countoftype[n]++;
                                }

                            }

                        }


                        int count_no_zero = 0;
                        for (int n = 0; n < k; n++)
                        {
                            if (total[n] != 0.0)
                            {
                                count_no_zero++;
                            }

                        }
                        if (count_no_zero == k)
                        {
                            for (int n = 0; n < k; n++)
                            {
                                center[n] = total[n] / element_countoftype[n];
                            }
                        }
                        else
                        {
                            ran = new int[k];
                            temp_c = price_all.Count;
                            ran = getNum(k, temp_c);
                            for (int i = 0; i < center.Length; i++)
                            {


                                center[i] = price_all[ran[i]];


                            }

                        }



                        ok = judge(center, oldcenter, ok);
                        ireation++;
                        this.Invoke(new setStatusDelegate(setStatus), building_name_all[m], count, ireation);
                    }
                }



文本聚类:

文本分类跟数值不同在于 要先对文本进行分词,并用TFIDF计算它们的权重 然后用权重向量进行计算。

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


完整项目下载:

http://download.csdn.net/detail/q383965374/5960053



下面是一个控制台聚类器的代码:

Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WawaSoft.Search.Common;

namespace WawaSoft.Search.Test
{
    class Program
    {
        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>
        /// <returns></returns>
        private static string[] getInputDocs(string file)
        {
            List<string> ret = new List<string>();
            try
            {
                using (StreamReader sr = new StreamReader(file, Encoding.Default))
                {
                    string temp;
                    while ((temp = sr.ReadLine()) != null)
                    {
                        ret.Add(temp);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            return ret.ToArray();
        }
    }
}


input.txt内容

奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
股民 要 清楚 自己 的 目的
印花税 之 股民 四季
杭州 股民 放 鞭炮 庆祝 印花税 下调 
残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
介绍 一 个 ASP.net MVC 系列 教程
在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
输 大钱 的 股民 给 我们 启迪
Asp.Net 页面 执行 流程 分析
运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
asp.net 控件 开发 显示 控件 内容
奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析


ITokeniser.cs

using System.Collections.Generic;

namespace WawaSoft.Search.Common
{
    /// <summary>
    /// 分词器接口
    /// </summary>
    public interface ITokeniser
    {
        IList<string> Partition(string input);
    }
}

StopWordsHandler.cs

using System;
using System.Collections;

namespace WawaSoft.Search.Common
{

    /// <summary>
    /// 用于移除停止词
    /// </summary>
	public class StopWordsHandler
	{		
		public static string[] stopWordsList=new string[] {"的",
            "我们","要","自己","之","将","“","”",",","(",")","后","应","到","某","后",
            "个","是","位","新","一","两","在","中","或","有","更","好"
		} ;

		private static readonly Hashtable _stopwords=null;

		public static object AddElement(IDictionary collection,Object key, object newValue)
		{
			object element = collection[key];
			collection[key] = newValue;
			return element;
		}

		public static bool IsStopword(string str)
		{
			
			//int index=Array.BinarySearch(stopWordsList, str)
			return _stopwords.ContainsKey(str.ToLower());
		}
	

		static StopWordsHandler()
		{
			if (_stopwords == null)
			{
				_stopwords = new Hashtable();
				double dummy = 0;
				foreach (string word in stopWordsList)
				{
					AddElement(_stopwords, word, dummy);
				}
			}
		}
	}
}



TermVector.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace WawaSoft.Search.Common
{
    public class TermVector
    {
        public static double ComputeCosineSimilarity(double[] vector1, double[] vector2)
        {
            if (vector1.Length != vector2.Length)
                throw new Exception("DIFER LENGTH");


            double denom = (VectorLength(vector1) * VectorLength(vector2));
            if (denom == 0D)
                return 0D;
            else
                return (InnerProduct(vector1, vector2) / denom);

        }

        public static double InnerProduct(double[] vector1, double[] vector2)
        {

            if (vector1.Length != vector2.Length)
                throw new Exception("DIFFER LENGTH ARE NOT ALLOWED");


            double result = 0D;
            for (int i = 0; i < vector1.Length; i++)
                result += vector1[i] * vector2[i];

            return result;
        }

        public static double VectorLength(double[] vector)
        {
            double sum = 0.0D;
            for (int i = 0; i < vector.Length; i++)
                sum = sum + (vector[i] * vector[i]);

            return (double)Math.Sqrt(sum);
        }

    }
}


TFIDFMeasure.cs

/*
 * tf/idf implementation 
 * Author: Thanh Dao, thanh.dao@gmx.net
 */
using System;
using System.Collections;
using System.Collections.Generic;
using WawaSoft.Search.Common;


namespace WawaSoft.Search.Common
{
	/// <summary>
	/// Summary description for TF_IDFLib.
	/// </summary>
	public class TFIDFMeasure
	{
		private string[] _docs;
		private string[][] _ngramDoc;
		private int _numDocs=0;
		private int _numTerms=0;
		private ArrayList _terms;
		private int[][] _termFreq;
		private float[][] _termWeight;
		private int[] _maxTermFreq;
		private int[] _docFreq;

        ITokeniser _tokenizer = null;





	    private IDictionary _wordsIndex=new Hashtable() ;

		public TFIDFMeasure(string[] documents,ITokeniser tokeniser)
		{
			_docs=documents;
			_numDocs=documents.Length ;
		    _tokenizer = tokeniser;
			MyInit();
		}

	    public int NumTerms
	    {
	        get { return _numTerms; }
	        set { _numTerms = value; }
	    }

	    private void GeneratNgramText()
		{
			
		}

		private ArrayList GenerateTerms(string[] docs)
		{
			ArrayList uniques=new ArrayList() ;
			_ngramDoc=new string[_numDocs][] ;
			for (int i=0; i < docs.Length ; i++)
			{
				IList<string> words=_tokenizer.Partition(docs[i]);		

				for (int j=0; j < words.Count; j++)
					if (!uniques.Contains(words[j]) )				
						uniques.Add(words[j]) ;
								
			}
			return uniques;
		}
		


		private static object AddElement(IDictionary collection, object key, object newValue)
		{
			object element=collection[key];
			collection[key]=newValue;
			return element;
		}

		private int GetTermIndex(string term)
		{
			object index=_wordsIndex[term];
			if (index == null) return -1;
			return (int) index;
		}

		private void MyInit()
		{
			_terms=GenerateTerms (_docs );
			NumTerms=_terms.Count ;

			_maxTermFreq=new int[_numDocs] ;
			_docFreq=new int[NumTerms] ;
			_termFreq =new int[NumTerms][] ;
			_termWeight=new float[NumTerms][] ;

			for(int i=0; i < _terms.Count ; i++)			
			{
				_termWeight[i]=new float[_numDocs] ;
				_termFreq[i]=new int[_numDocs] ;

				AddElement(_wordsIndex, _terms[i], i);			
			}
			
			GenerateTermFrequency ();
			GenerateTermWeight();			
				
		}
		
		private float Log(float num)
		{
			return (float) Math.Log(num) ;//log2
		}

		private void GenerateTermFrequency()
		{
			for(int i=0; i < _numDocs  ; i++)
			{								
				string curDoc=_docs[i];
				IDictionary freq=GetWordFrequency(curDoc);
				IDictionaryEnumerator enums=freq.GetEnumerator() ;
				_maxTermFreq[i]=int.MinValue ;
				while (enums.MoveNext())
				{
					string word=(string)enums.Key;
					int wordFreq=(int)enums.Value ;
					int termIndex=GetTermIndex(word);
                    if(termIndex == -1)
                        continue;
					_termFreq [termIndex][i]=wordFreq;
					_docFreq[termIndex] ++;

					if (wordFreq > _maxTermFreq[i]) _maxTermFreq[i]=wordFreq;					
				}
			}
		}
		

		private void GenerateTermWeight()
		{			
			for(int i=0; i < NumTerms   ; i++)
			{
				for(int j=0; j < _numDocs ; j++)				
					_termWeight[i][j]=ComputeTermWeight (i, j);				
			}
		}

		private float GetTermFrequency(int term, int doc)
		{			
			int freq=_termFreq [term][doc];
			int maxfreq=_maxTermFreq[doc];			
			
			return ( (float) freq/(float)maxfreq );
		}

		private float GetInverseDocumentFrequency(int term)
		{
			int df=_docFreq[term];
			return Log((float) (_numDocs) / (float) df );
		}

		private float ComputeTermWeight(int term, int doc)
		{
			float tf=GetTermFrequency (term, doc);
			float idf=GetInverseDocumentFrequency(term);
			return tf * idf;
		}
		
		private  float[] GetTermVector(int doc)
		{
			float[] w=new float[NumTerms] ; 
			for (int i=0; i < NumTerms; i++)											
				w[i]=_termWeight[i][doc];
			
				
			return w;
		}
        public double [] GetTermVector2(int doc)
        {
            double [] ret = new double[NumTerms];
            float[] w = GetTermVector(doc);
            for (int i = 0; i < ret.Length; i++ )
            {
                ret[i] = w[i];
            }
            return ret;
        }

		public double GetSimilarity(int doc_i, int doc_j)
		{
			double [] vector1=GetTermVector2 (doc_i);
			double [] vector2=GetTermVector2 (doc_j);

			return TermVector.ComputeCosineSimilarity(vector1, vector2) ;

		}
		
		private IDictionary GetWordFrequency(string input)
		{
			string convertedInput=input.ToLower() ;
					
            List<string> temp = new List<string>(_tokenizer.Partition(convertedInput));
			string[] words= temp.ToArray();		
	        
			Array.Sort(words);
			
			String[] distinctWords=GetDistinctWords(words);
						
			IDictionary result=new Hashtable();
			for (int i=0; i < distinctWords.Length; i++)
			{
				object tmp;
				tmp=CountWords(distinctWords[i], words);
				result[distinctWords[i]]=tmp;
				
			}
			
			return result;
		}				
				
		private static string[] GetDistinctWords(String[] input)
		{				
			if (input == null)			
				return new string[0];			
			else
			{
                List<string> list = new List<string>();
				
				for (int i=0; i < input.Length; i++)
					if (!list.Contains(input[i])) // N-GRAM SIMILARITY?				
						list.Add(input[i]);
				
				return list.ToArray();
			}
		}
		

		
		private int CountWords(string word, string[] words)
		{
			int itemIdx=Array.BinarySearch(words, word);
			
			if (itemIdx > 0)			
				while (itemIdx > 0 && words[itemIdx].Equals(word))				
					itemIdx--;				
						
			int count=0;
			while (itemIdx < words.Length && itemIdx >= 0)
			{
				if (words[itemIdx].Equals(word)) count++;				
				
				itemIdx++;
				if (itemIdx < words.Length)				
					if (!words[itemIdx].Equals(word)) break;					
				
			}
			
			return count;
		}				
	}
}

Tokeniser.cs

/*
Tokenization
Author: Thanh Ngoc Dao - Thanh.dao@gmx.net
Copyright (c) 2005 by Thanh Ngoc Dao.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using WawaSoft.Search.Common;


namespace WawaSoft.Search.Common
{
	/// <summary>
	/// Summary description for Tokeniser.
	/// Partition string into SUBwords
	/// </summary>
	internal class Tokeniser : ITokeniser
	{

        /// <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();
		}


		public Tokeniser()
		{
		}

    }
}


WawaCluster.cs

using System.Collections.Generic;

namespace WawaSoft.Search.Common
{
    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; // 对每个纵向列取平均值
                }
            }
        }
    }
}

WawaKMeans.cs

using System;

namespace WawaSoft.Search.Common
{

    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]);
            }
        }
    }
}


3.点的聚类:


例子如下:

使用你所学到的k均值聚类算法分别按照欧拉距离(平面直角坐标系里的两点间距离公式)和曼哈顿距离(两个点上在标准坐标系上的绝对轴距总和,将下面的点分成三类:

 

坐标X

坐标Y

1

0

0

2

2

3

3

4

2

4

0

6

5

14

             3

6

13

5

7

3

10

8

4

11

9

6

9

10

8

10

11

12

6

12

17

3

13

14

6

14

7

9

15

9

12

 

 

基于欧拉距离的程序代码(C语言)

#include<stdio.h>
#include<math.h>
#define N 15
#define k 3
 
int min(float d[k])                                               
{
      inti=0;                                                  /*定义最短距离下标*/
      if(d[1]<d[i])
           i=1;
      if(d[2]<d[i])
           i=2;
      returni;                                                /*返回最短距离下标*/
}
 
int main()
{
      floata[N][2]={{0,0},{2,3},{4,2},{0,6},{14,3},
           {13,5},{3,10},{4,11},{6,9},{8,10},{12,6},
           {17,3},{14,6},{7,9},{9,12}};             /*N个点,坐标*/
 
 
      inti,j,m;                                                    
 
      inticounter[k],n=0;           /*各簇元素个数,聚类次数*/
 
      floatc[k][2];                                                      /*聚类中心*/ float c1[k][2];                                                    /*新聚类中心*/
      floats[k][2];                                              /*各簇元素坐标和*/
     
      floatd[k];                                                              /*距离*/
 
      intitype;                                                          /*元素所在的簇*/
      floattype[k][N][2];       /*初始化,K簇,每簇含N个元素*/
 
 
      for(j=0;j<k;j++)
      {
           c1[j][0]=a[j][0];                             /*初始化聚类中心*/
           c1[j][1]=a[j][1];                                        
      }
 
      do
      {
           n++;
    for(i=0;i<k;i++)
           {
                 icounter[i]=0;                      /*每次聚类计数器归零*/       
                 for(m=0;m<2;m++)
                 {
           s[i][m]=0;                    /*各簇元素坐标和归零*/
                        c[i][m]=c1[i][m];         
/*新聚类中心赋值给原聚类中心*/
                 }
           }
     
           for(i=0;i<N;i++)                                        /*遍历数组*/
           {
                 for(j=0;j<k;j++)
                      d[j]=fabs(a[i][0]-c[j][0])+fabs(a[i][1]-c[j][1]); 
/*欧拉距离*/
                
                 itype=min(d);                                  /*调用min函数*/
                 for(m=0;m<2;m++)
                 {
                      type[itype][icounter[itype]][m]=a[i][m];                 
                      s[itype][m]+=a[i][m];
                 }
 
                 icounter[itype]++;                   
           }
 
           for(i=0;i<k;i++)
           {   
                 c1[i][0]=s[i][0]/icounter[i];                 /*新聚类中心*/
                 c1[i][1]=s[i][1]/icounter[i];
           }
/*输出每次聚类得到的结果*/
           printf("第%d次基于欧拉距离聚类结果:\n",n);        
           for(j=0;j<k;j++)
           {
                 printf("聚类中心(%f,%f):\t",c[j][0],c[j][1]);
 
                 for(i=0;i<icounter[j];i++)
                 {
                      printf("(%.f,%.f)",type[j][i][0],type[j][i][1]);
                 }
                 printf("\n");
           }
           printf("\n");
 
      }while((fabs(c1[0][0]-c[0][0])>1e-5) ||
            (fabs(c1[1][0]-c[1][0])>1e-5) ||
           (fabs(c1[2][0]-c[2][0])>1e-5));                    
/*聚类结果收敛时跳出循环*/
 
/*打印最终聚类结果*/
      printf("\n基于欧拉距离%d次聚类后结果收敛,最终聚类结果:\n",n);
      for(j=0;j<k;j++)                                  
      {
           printf("聚类中心(%.2f,%.2f):\t",c1[j][0],c1[j][1]);
           for(i=0;i<icounter[j];i++)
           {
                 printf("(%.f,%.f)",type[j][i][0],type[j][i][1]);
           }
           printf("\n");
      }
 
      return0;
}
 


 

运行结果

基于欧拉距离

 

 


聚类中心

元素

( 1.5,  2.75)

(0,0)   (2,3)   (4,2)   (0,6)

( 6.17, 10.17)

(3,10)  (4,11)  (6,9)   (7,9)   (9,12)

( 14,   4.6)

(14,3)  (13,5)  (12,6)  (17,3)  (14,6)

 

基于曼哈顿距离

 

聚类中心

元素

( 1.5,  2.75)

(0,0)   (2,3)   (4,2)   (0,6)

( 6.17, 10.17)

(3,10)  (4,11)  (6,9)   (7,9)   (9,12)

( 14,   4.6)

(14,3)  (13,5)  (12,6)  (17,3)  (14,6)

 





c#的例子代码:

k均值算法是模式识别的聚分类问题,这是用C#实现其算法
以下是程序源代码:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace KMean_win
{
     /// 
     /// Form1 的摘要说明。
     /// 
     public class Form1 : System.Windows.Forms.Form
     {
         /// 
         /// 必需的设计器变量。
         /// 
         private System.ComponentModel.Container components = null;
         private static int k = 2;                          //类数,此例题为2类
         private static int total = 20;                      //点个数
         private int test = 0;
         private PointF[] unknown = new PointF[total];  //点数组
         private int[] type = new int[total];           //每个点暂时的类
         public PointF[] z = new PointF[k];                 //保存新的聚类中心
         public PointF[] z0 = new PointF[k];                //保存上一次的聚类中心
         private PointF sum;
         private int temp = 0;
         private System.Windows.Forms.TextBox textBox1;
         private int l = 0;          //迭代次数
//构造函数,初始化
         public Form1()
         {
              unknown[0]=new Point(0,0);
              unknown[1]=new Point(1,0);
              unknown[2]=new Point(0,1);
              unknown[3]=new Point(1,1);
              unknown[4]=new Point(2,1);
              unknown[5]=new Point(1,2);
              unknown[6]=new Point(2,2);
              unknown[7]=new Point(3,2);
              unknown[8]=new Point(6,6);
              unknown[9]=new Point(7,6);
              unknown[10]=new Point(8,6);
              unknown[11]=new Point(6,7);
              unknown[12]=new Point(7,7);
              unknown[13]=new Point(8,7);
              unknown[14]=new Point(9,7);
              unknown[15]=new Point(7,8);
              unknown[16]=new Point(8,8);
              unknown[17]=new Point(9,8);
              unknown[18]=new Point(8,9);
              unknown[19]=new Point(9,9);
              
              InitializeComponent();
              
              test = 0;
              //选k个初始聚类中心    z[i]     
              for(int i=0;i                   z[i] = unknown[i];
              for(int i=0;i                   type[i] = 0;
         }
//计算新的聚类中心
         public PointF newCenter(int m)
         {
              int N = 0;
              for(int i=0;i              {
                   if(type[i] == m)
                   {
                       sum.X = unknown[i].X+sum.X;
                       sum.Y = unknown[i].Y+sum.Y;
                       N += 1;
                   }
              }
              sum.X=sum.X/N;
              sum.Y=sum.Y/N;
              return sum;
         }
//比较两个聚类中心的是否相等
         private bool compare(PointF a,PointF b)
         {
              if(((int)(a.X*10) == (int)(b.X*10)) && ((int)(a.X*10) == (int)(b.X*10)))
                   return true;
              else
                   return false;
         }
//进行迭代,对total个样本根据聚类中心进行分类
         private void order()
         {
              int temp = 0;//记录unknown[i]暂时在哪个类中
              for(int i=0;i              {
                   for(int j=0;j                   {
                       if(distance(unknown[i],z[temp]) > distance(unknown[i],z[j]))
                            temp = j;
                   }
                   type[i] = temp;
                   Console.WriteLine("经比较后,{0}归为{1}类",unknown[i],temp);
              }
         }
//计算两个点的欧式距离
         private float distance(PointF p1,PointF p2)
         {
              return((p1.X-p2.X)*(p1.X-p2.X)+ (p1.Y-p2.Y)*(p1.Y-p2.Y));
         }
         /// 
         /// 清理所有正在使用的资源。
         /// 
         protected override void Dispose( bool disposing )
         {
              if( disposing )
              {
                   if (components != null) 
                   {
                       components.Dispose();
                   }
              }
              base.Dispose( disposing );
         }
//程序结构
         public void main()
         {
              Console.WriteLine("共有如下个未知样本:");
              for(int i=0;i              {
                   Console.WriteLine(unknown[i]);
              }
/*            for(int i=0;i                  Console.WriteLine("初始时,第{0}类中心{1}",i,z[i]);
              order();
              for(int i=0;i              {
                   z[i] = newCenter(i);
                   Console.WriteLine("第{0}类新中心{1}",i,z[i]);
                   if(z[i].Equals(z0[i]) )
                       test = test+1;
                   else
                       z0[i] = z[i];
              }
*/            for(int i=0;i                  Console.WriteLine("初始时,第{0}类中心{1}",i,z[i]); 
              while( test!=k )
              {
                   order();
                   for(int i=0;i                   {
                       z[i] = newCenter(i);
                       Console.WriteLine("第{0}类新中心{1}",i,z[i]);
                       if(compare(z[i],z0[i]))
                            test = test+1;
                       else
                            z0[i] = z[i];
                   }
                   l = l+1;
                   Console.WriteLine("******已完成第{0}次迭代*******",l);
                   
              Console.Write("{0}","分类后:");
              for(int j=0;j              {
                   Console.Write("第{0}类有:",j);
                   for(int i=0;i                   {
                       if(type[i] == j)
                            Console.WriteLine("{0},{1}",unknown[i].X,unknown[i].Y);
                   }
              }
              }
              
     }
         #region Windows 窗体设计器生成的代码
         /// 
         /// 设计器支持所需的方法 - 不要使用代码编辑器修改
         /// 此方法的内容。
         /// 
         private void InitializeComponent()
         {
              this.textBox1 = new System.Windows.Forms.TextBox();
              this.SuspendLayout();
              // 
              // textBox1
              // 
              this.textBox1.Location = new System.Drawing.Point(0, 0);
              this.textBox1.Multiline = true;
              this.textBox1.Name = "textBox1";
              this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
              this.textBox1.Size = new System.Drawing.Size(296, 272);
              this.textBox1.TabIndex = 0;
              this.textBox1.Text = "";
              // 
              // Form1
              // 
              this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
              this.ClientSize = new System.Drawing.Size(292, 271);
              this.Controls.Add(this.textBox1);
              this.Name = "Form1";
              this.Text = "Form1";
              this.ResumeLayout(false);
         }
         #endregion
     }
     class entrance
     {        /// 
         /// 应用程序的主入口点。
         /// 
         [STAThread]
         static void Main() 
         {             
              Form1 my = new Form1();
              my.main();
              Application.Run(new Form1());
         }
     }
} 





  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张小凡vip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值