聚类分析的目的是将若干特征相似的特征模式划分到一个集合,每个集合的特征模式之间按照某种度量来衡量相似程度,使得同一个集合内的数据对象具有较高的相似度,而不同集合中的数据对象间的相似度尽可能小,数据对象间特性差异的大小通常是借助于某一距离空间中的距离概念来表示的。在现有的聚类算法中, K-均值算法以其简单和高效占有重要地位,因而大量应用于数据挖掘中。
其计算流程如下:
1. 如果是第一次循环,随机选择K个元素作为质心;否则使用K个分类的平均值作为质心。
2. 计算n个数据离各个分类的距离,将当前数据作为离其最近的分类的元素
3. 计算各分类的的均值和方差
4. 如果当前各个分类方差,如果前后步最大方差差别满足容忍限度,算法完成,退出;否则,返回1,进行下一次循环。
首先,我们对这个问题进行分析,进行算法GPU化的第一步不是拿起键盘就敲,而是分析当前算法是否满足GPU计算的要求,从算法步骤可以看出,该算法是数据并行,虽然各数据之间有些交互,但是都能够解决。此外数据挖掘领域的算法几乎都要求是大数据量的,因此,初步可得出此算法可以GPU化。
其次,在我们得出算法可以GPU化结论后,下一步应当是具体的设计了,设计首先应当是算法和数据结构设计。从算法可以看出,主要运算有求平均值、方差,都可以使用归约解决。其后就是数据结构设计了。由于涉及到数据是分类的元素这个问题,其有两种实现方式,一种是为每一分类分配一数组,存储该分类的元素;另一种是为每个元素设立一个域,来表示元素属于那个分类。CUDA要求数据组织是有序的,因此表面上看前一种方案比较好,但是由于我们无法提前知道属于每一分类的元素数目,因此数组只能开得比较大,当然我们可以实现类似STL中的向量,但是这又带来其它开销。一般来说GPU的设备存储器容量不多,而数据量往往又比较大,再考虑到实现的难度,因此我们采用后一种方案。此时数据结构设计如下:
//T stand for attribute type,size stand for attribute number; template data{ T x[size];//attribute } struct dataSet{ unsigned int num;//data size data *attributes;// size == num unsigned int *belongTo;//specify data belong to which class;size == num } |
算法要求计算距离,但是并无必要,我们计算距离的平方就行了,这样节省一次求方根的计算量。代码如下:
__host__ __device__ int distance2(data one, data two, unsigned int size = 3){ Int dis2 = 0;//must be more explicit int dis; #pragma unroll 3 for(int I = 0; I < size; i++){ dis = one.x[i] – two.x[i]; dis2 += dis*dis; }
return dis2; } |
我们存放分类的平均值,在计算每个数据属于那个分类时,理论上是有分支的,但是可以消除。分类之后就要计算各分类的平均值,可在每个block中分配共享存储器,以保存各个分类和及各分类元素数量,此时要用到共享存储器的原子函数,因此元素属性类型只能采用整形,此时最多可处理2000个分类,但是如果是2.0的设备,可处理的分类数会多,也可以处理浮点型属性,一方面是因为共享存储器量多了且支持浮点原子函数,另一方面此时可将分类元素数量声明为ushort而没有存储体冲突。然后再另起一个kernel 就可得到各分类的平均值。然后要做的就是求各分类的方差了。此时可将分类平均值存放于共享存储器中,然后遍历各元素,并将求得的分类方差和存放在共享存储器中,再另起一内核,该内核只有一个块,用于归约上一内核得到的部分方差和。至此整个算法基本完成。
如果要在计算能力2.0以下的设备上做K-均值算法,并使用浮点属性值的话,可以使用我在分析的时候的前一方案,此时最好实现CUDA环境的向量数据结构。由于要使用全局存储器的原子函数,由此可见其速度将不会很好。
在intel至强5450 CPU上,GTX 295显卡上,一万个三维的信息,计算部分加速比在40以上。由于是个人玩乐的项目,因此并没有为了速度而进行极致的优化。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22785983/viewspace-663548/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/22785983/viewspace-663548/