集体智慧编程第三章之发现群组

      第三章主要讲述了利用分级聚类,K均值聚类来发现群组的过程。聚类是寻找紧密相关的事人或者观点,并将其可视化的方法,通过数据聚类可以将相似度很高的项目聚集在一起,属于一种无监督学习,聚类在机器学习中的应用十分广泛。比如可以通过聚类来发现数据的分布特征,通过聚类可以寻找相似用户等等。本章主要通过一个对博客进行聚类的来说明聚类的过程,并在聚类的基础上将数据进行了可视化。

1.单词向量

     对博客进行聚类属于文本分析的过程,但是在聚类的过程中实际参与计算的通常都是数字,所以这里我们需要对原始博客进行一个处理,将文本向量化,对于每一个在博客中出现的单词,用出现的次数作为一个特征,所有的单词放在一起组成一个特征向量,当然,预先可以选择特定的单词作为统计标准,不要求每一个在文章中出现的单词都要参与到向量化的过程中。就像我们在第二章中对喜好的数字化一样。下图是一个例子说明:

                        

      对于第一篇文档,可以用向量(0,3,3,0,...)来进行表示,在本实验中,采用的数据集也正是这种形式。

2.分级聚类

      分级聚类是连续的将最为相似的 群组两两进行合并,来构造出一个群组的层级结构,其中的每一个群组都是从单一元素开始的,在本实验中,这个单一的元素就是每一篇博客,计算每两篇博客之间的相似度,将最为相似的两篇博客进行合并,这个过程一直持续下去,知道最终剩下一个群组为止。整个过程如下图所示:

                 

    在本例中,最开始的时候ABCDE是一个不同的群组,第一次将AB进行聚类,之后再对C合并。。

    对博客进行聚类,我们首先需要将数据集读入到内存中,数据集的格式如下:
      

    第一行表示统计文本特性时指定的单词,第一列表示博客名称,利用一个列表将所有的数据读入到内存。

def readfile(filename):
    lines = [line for line in open(filename, encoding='utf-8')]
    # 第一行是列标题
    colnames = lines[0].strip().split('\t')[1:]
    rownames = []
    data = []
    for line in lines[1:]:
        p = line.strip().split('\t')
        # 每一行的第一行是行名
        rownames.append(p[0])
        # 剩余的部分是该行对应的数据
        data.append([float(x) for x in p[1:]])
    return rownames, colnames, data
     将文本数据数字化之后读入到列表之后,为了聚类,首先要衡量两个文本之间的相似度,在这里也就是两个向量之间的相似性,利用皮尔逊相关系数来衡量相似度。

    计算相似性

# 利用皮尔逊相关系数来计算紧密度,传入的参数v1和v2分别为两个向量
def person(v1, v2):
    # 计算各自的和
    sum1 = sum(v1)
    sum2 = sum(v2)
    # 计算平方和
    sqsum1 = sum([pow(x, 2) for x in v1])
    sqsum2 = sum([pow(x, 2) for x in v2])
    # 计算二者乘积的和
    psum = sum([v1[i] * v2[i] for i in range(len(v1))])
    # 计算皮尔逊相关系数
    num = psum - sum1 * sum2 / len(v1)
    den = sqrt((sqsum1 - pow(sum1, 2) / len(v1)) * (sqsum2 - pow(sum2, 2) / len(v1)))
    if den == 0:
        return 0
    return 1.0 - num / den
    为了最后将数据可视化成一个层次结构,我们利用面向对象的思想,将每一个节点都看作是一个对象,每一个对象有代表自身的一个向量,有一个左分支和右分支和一个id,定义节点类如下:

# 为每一个节点建立一个类
class bicluster:
    def __init__(self, vec, left=None, right=None, distance=0.0, id=None):
        self.left = left
        self.right = right
        self.distance = distance
        self.id = id
        self.vec = vec
     分级聚类算法以一组对应原始数据项的数据聚类开始,每一次循环计算每一对之间的相似度 ,选择相似度最高的一组作为要聚类的对象,聚类之后,两个项目会被合并为一个项目,并计算二者的平均值作为新的项目所对应的向量。这个过程会一直持续下去,知道最后形成一个群组为止。

# 进行分级聚类的过程
# rows:          所用的行数据
# distance:      计算距离函数
def hcluster(rows, distance=person):
    distances = {}  # 计算每一对的距离结果
    currentclusterid = -1
    # 最起初的聚类就是rows中的每一行的数据,每一行为一类
    clust = [bicluster(rows[i], id=i) for i in range(len(rows))]

    while (len(clust) > 1):
        lowestpair = (0, 1)  # 假定距离最小的id为0和1
        closest = distance(clust[0].vec, clust[1].vec)
        # 遍历每一组节点,找到距离最近的一组节点
        for i in range(len(clust)):
            for j in range(i + 1, len(clust)):
                # 利用distances字典来保存两个节点之间的距离值
                if (clust[i].id, clust[j].id) not in distances:
                    distances[(clust[i].id, clust[j].id)] = distance(clust[i].vec, clust[j].vec)
                d = distances[(clust[i].id, clust[j].id)]
                if d < closest:
                    closest = d
                    lowestpair = (i, j)
                    # 计算两个聚类结果之间的平均值
        mergevec = [(clust[lowestpair[0]].vec[i] + clust[lowestpair[1]].vec[i]) / 2.0 for i in range(len(clust[0].vec))]
        # 建立新的聚类
        newcluster = bicluster(mergevec,
                               left=clust[lowestpair[0]],
                               right=clust[lowestpair[1]],
                               distance=closest,
                               id=currentclusterid)
        # 不在原始集合中的聚类,其id设置为负数
        currentclusterid -= 1
        del clust[lowestpair[1]]
        del clust[lowestpair[0]]
        clust.append(newcluster)
    # 返回最后的聚类结果
    return clust[0]
     最后返回的clust[0]就是最终的聚类结果,可以递归搜索由该函数最终返回的聚类,可以重建所有的聚类及节点。

为了可视化聚类结果,我们采用树状图的形式来展现最终的结果。

# 求取每一个节点的高度,如果是叶子节点,那么高度就是1,如果不是叶子节点,那么高度就是所有的分支高度之和
def getheight(clust):
    if clust.left == None and clust.right == None:
        return 1
    else:
        return getheight(clust.left) + getheight(clust.right)


# 求取每个节点的误差深度
def getdepth(clust):
    if clust.left == None and clust.right == None:
        return 0;
    else:
        return max(getdepth(clust.left), getdepth(clust.right)) + clust.distance
    
def drawdendrogram(clust, labels, jpeg='clusters.jpg'):
    h = getheight(clust) * 20
    w = 1200
    depth = getdepth(clust)
    scaling = float(w - 150) / depth
    img = Image.new('RGB', (w, h), (255, 255, 255))
    draw = ImageDraw.Draw(img)
    draw.line((0, h / 2, 10, h / 2), fill=(255, 0, 0))
    drawnode(draw, clust, 10, (h / 2), scaling, labels)
    img.save(jpeg, 'JPEG')

def drawnode(draw, clust, x, y, scaling, labels):
    if clust.id < 0:
        h1 = getheight(clust.left) * 20
        h2 = getheight(clust.right) * 20
        top = y - (h1 + h2) / 2
        bottom = y + (h1 + h2) / 2
        ll = clust.distance * scaling
        draw.line((x, top + h1 / 2, x, bottom - h2 / 2), fill=(255, 0, 0))
        draw.line((x, top + h1 / 2, x + ll, top + h1 / 2), fill=(255, 0, 0))
        draw.line((x, bottom - h2 / 2, x + ll, bottom - h2 / 2), fill=(255, 0, 0))
        drawnode(draw, clust.left, x + ll, top + h1 / 2, scaling, labels)
        drawnode(draw, clust.right, x + ll, bottom - h2 / 2, scaling, labels)
    else:
        draw.text((x + 5, y - 7), labels[clust.id], (0, 0, 0))

   运行程序,最后得到的结果如下所示():

   

     其实分级聚类的思想和简单,就是不断的发现两个最为相似的项目,不但得进行合二为一的过程,知道最后剩余一个。

3.列聚类

      注意到,我们在上面对博客文档进行聚类的过程中,每一次聚类合并的两篇文章,具体到程序中,也就是对博客单词矩阵的每一行进行的聚类,如果现在对每一列进行聚类会产生什么呢,每一列代表的意义就是某一个单词在所有的文章中出现次数,这样每次在聚类过程中衡量的就是两个单词之间的相似性,和啤酒尿布的例子是一一样的道理,比如说在某一篇文章中出现了machine这个单词,那么在这篇文章中很大的可能性也会出现learning这个词,最后衡量的结果就是machine和learning这两个单词在所有的文章中出现的次数所构成的向量是很相似的。要实现这个过程,只需要将代表矩阵的列表做一下调整即可。

#对数据进行转置操作,对列进行聚类
def rotatematrix(data):
    newdata=[]
    for i in range(len (data[0])):
        line=[data[j][i] for j in range(len(data))]
        newdata.append(line)
    return newdata

     最后也可以将聚类的结果进行可视化操作,这里就不再展示了。

4.K均值聚类

     层级聚类在最后的聚类结束时,将所有的项目聚类成一个结果。不同与层级聚类,K均值聚类在一开始的时候就指定了最终需要聚类的个数,也就是k的值,起初,随机的指定k个聚类中心,在每一次的迭代中,计算在k个聚类中心和每一个样本之间的相似度,将每一个样本根据相似度的大小指派给每一个聚类中心,一次迭代结束,重新计算聚类中心新的聚类中心的计算采取的是平均法则。

                     

     K均值的初始聚类中心对聚类过程的影响很大,如果初始化的聚类中心选择的比较好的话,那么可以很快聚类结束,否则可能需要更多的时间。

#K均值聚类
def kcluster(rows,distance=person,k=4):
    #确定每一行的最大值和最小值
    ranges = [(min([row[i] for row in rows]), max([row[i] for row in rows])) for i in range(len(rows[0]))]
    #随机的选择K个初始中心,clusters是一个元素为列表的列表
    #内层列表中的数值是一个每一行最小值到最大值之间的数字,与row[i]具有一样的维数
    clusters=[[random.random()*(ranges[i][1]-ranges[i][0])+ranges[i][0]
              for i in range(len(rows[0]))]
              for j in range(k)]
    lastmatches=None
    #设置循环次数为100次
    for t in range(100):
        #bestmatches中保存的是每一个聚类心点所包含的数据行下标
        #形如这样的格式bestmatches[i]表示第i类中的样本下标[[1,5,34,....],[3,8,9,....]]
        bestmatches=[ [] for i in range(k)]
        #为每一行分派到距离最近的中心点
        for j in range(len(rows)):
            row=rows[j]
            bestmatch=0
            #计算每一个数据点与中心点之间的距离
            for i in range(k):
                d=distance(clusters[i],row)
                if d<distance(clusters[bestmatch],row):
                    bestmatch=i
            bestmatches[bestmatch].append(j)
        #如果下一次的聚类结果和上一次的一样的话,那么直接结束
        if bestmatch==lastmatches:break
        lastmatches=bestmatches
        #把中心点平均化,对每一个类计算向量平均值
        for i in range(k):
            avgs=[0.0]*len(rows[0])
            #如果某一个分类中有数据的
            if len(bestmatches[i])>0:
                for rowid in bestmatches[i]:
                    for m in range(len(rows[rowid])):
                        avgs[m]+=rows[rowid][m]
                for j in range(len(avgs)):
                    avgs[j]/=len(bestmatches[i])
                clusters[i]=avgs
    return bestmatches
     输出的一个可能的聚类结果:


 






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值