【集体智慧编程】第三章 发现群组
2017-05-13 22:32
309 查看
发现群组(数据聚类)
对第二章的想法,加以拓展,引入“数据聚类”(data clustering)的概念。本章主要涉及以下内容从各种不同的来源中构造算法所需的数据
两种不同的聚类算法
有关距离度量(distance metrics)的知识
简单的图形可视化代码,用以观察所生产的群组
如何将异常复杂的数据集投影到二维空间中
本文涉及两个例子:
1、对博客CSS订阅话题,根据涉及的词汇对博客分组;对词汇的用法,对词汇分组;
2、对社区网站考察,获取人们已拥有或者希望拥有的物品,对人们的意愿进行分组。
一、单词向量
1、 对博客用户进行分组
目的:一组指定的词汇在每个博客订阅源中出现的次数。根据单词出现的频度进行聚类,尝试分析出具有相似主题或者写作风格的博客用户。代码块
..生成generatefeedvector.py
import feedparser # 解析RSS的一个包 import re # 返回一个RSS订阅源的标题和包含单词计数情况的字典 def getwordcounts(url): # 解析订阅源 d = feedparser.parse(url) wc = {} # 循环遍历所有的文章条目 for e in d.entries: if 'summary' in e: summary = e.summary else: summary = e.description # 提取一个单词列表 words = getword(e.title+' '+summary) # getword(题目+空格+文章) for word in words: wc.setdefault(word,0) # 如果键在字典wc中,返回这个键对应的值,如果不在字典中,则添加键到字典中,并将键对应的值默认为0 wc[word] += 1 # 用于统计单词出现的次数 return d.feed.title,wc # 函数getwordcounts 将摘要传给函数getwords,后者会将其中所有的HTML标记剥离 # 并以非字母字符作为分隔符拆分出单词,再将结果以列表的形式返回 def getword(html): # 去除所有HTML标记 txt = re.compile(r'<[^>]>').sub('',html) # 利用所有非字母字符拆分出单词,split()通过指定分隔对字符串进行切片 words = re.compile(r'[^A-Z^a-z]+').split(txt) # 转化成小写的形式 return [word.lower() for word in words if word!=''] # 代码的第一部分 遍历文件中的每一行url地址,然后生成针对每个博客的单词统计, # 以及出现这些单词的博客数目(apcount) apcount = {} wordcounts = {} feedlist = [line for line in open('feedlist_china.txt')] # 注意路径原为file,改为open for feedurl in feedlist: try: title, wc = getwordcounts(feedurl) # title,wc 类似Google blogoscoped {u'limited':1, u'all':5, u'research':6} wordcounts[title] = wc # 得到wordcounts类似{u'Google Blogoscoped': #{u'limited': 1, u'all': 5, u'searchable': 1, u'results': 1, u'browsers': 2} for word, count in wc.items(): # items()方法返回字典的键值元组对的列表,wc.item=[(词汇,计数),(词汇,计数)] apcount.setdefault(word, 0) # 此时 apcount={word, 0} if count > 1: apcount[word] += 1 except: print('Failed to parse feed %s' % feedurl) # 建立一个单词列表,将其实际用于针对每个博客的单词计数 # 将10% 定为下界,50%定为上界 wordlist = [] for w, bc in apcount.items(): # apcount.items()类似于[(u'limited', 0), (u'all', 1), (u'searchable', 0)] frac = float(bc)/len(feedlist) # 变成浮点算法,不然结果不准确 if frac>0.1 and frac<0.5: wordlist.append(w) # wordlist = ['limited', 'all', 'searchable'] # 最后我们利用上述单词列表和博客列表来建立一个文本文件,包含对每个博客所有单词的统计情况 out = open('blogdata.txt', 'w') out.write('Blog') for word in wordlist: out.write('\t%s' % word) out.write('\n') for blog, wc in wordcounts.items(): out.write(blog) for word in wordlist: if word in wc: out.write('\t%d' % wc[word]) out.write('\n')
以上代码生成如下格式数据:
blog | word1 | word2 | … |
---|---|---|---|
blog1 | 1 | 2 | … |
blog2 | 3 | 3 | … |
… | … | … | … |
2、分级聚类
分级聚类是通过连续不断地将最为相似的群组两两合并,来构造一个群组的层级结构。以下代码实现对博客数据集进行聚类,以构造博客的层级结构。
# clusters.py
def readfile(filename): lines = [line for line in open(filename)] #lines example # [blogname, word1, word2, ... # A, 5, 6, ... # B, 3, 1, ... # C, 2, 0, ...] # 第一行是列标题 # 加载blogdata.txt的话,lines=['blog\w1\w2\w3\...','blogname\w1词频\w2词频\w3词频',...] colnames=lines[0].strip().split('\t')[1:] # 从第二列始 获取博客总的词条 # strip() 移除字符串头尾指定的字符 # columns 是按照\t进行切割 rownames=[] data=[] for line in lines[1:]: p=line.strip().split('\t') # 获取词条内容 rownames.append(p[0]) # 剩余部分,是该行对应的数 149de 据 data.append([float(x) for x in p[1:]]) ''' 上述函数将数据集中的一行数据读入了一个代表列名的列表, 并将最左边一列读入了代表行名的列表 最后又将剩余的所有数据放入了一个大列表,其中每一项对应于数据集中的一行数据。''' return rownames,colnames,data #blognames, words, data=readfile('blogdata.txt') rownames,colnames,data=readfile('blogdata.txt') ''' 定义紧密度:由于不同博客包含的文章条目不一,用皮尔逊相关系数可以修正这个问题, 因为它判断的是两组数据与某条直线的拟合程度。'''
创建pearson函数,用于计算两列数组的相关系数
# 以下计算代码接受两个数字列表作为参数,返回这两个列表的相关度分值 from math import sqrt def pearson(v1, v2): # 简单求和 sum1=sum(v1) sum2=sum(v2) # 求平方和 sum1Sq=sum([pow(v,2) for v in v1]) sum2Sq=sum([pow(v,2) for v in v2]) # 求乘积之和 pSum=sum([v1*v2[i] for i in range(len(v1))]) # 计算r (pearson score) num=pSum-(sum1*sum2/len(v1)) den=sqrt((sum1Sq-pow(sum1,2)/len(v1))*(sum2Sq-pow(sum2,2)/len(v1))) if den==0: return 0 return 1.0-num/den # 相识度大的两个表点之间,返回的值越小
# 定义一个bicluster类,将每篇博客看成一个对象,以此定义一个类 # 分级聚类算法中的每一个聚类,可以是树中的枝节点,也可以是叶节点。每一个聚类还包含了只是其位置的信息,这一信息可以是 # 来自叶节点的数据,也可以是来自枝节点后合并的数据 class bicluster: def __init__(self,vec,left=None,right=None,distance=0.0,id=None): self.left=left self.right=right # 每次聚类都是一堆数,left保存其中一个,right保存其中一个 self.vec=vec # 代表该聚类的特征向量,保存两个数据聚类后形成的新的中心 self.id=id # 用来标志该节点是叶节点还是内部节点,如果是叶节点,则为正数,如果不是叶节点,则为负数 self.distance=distance # 表示合并左子树和右子树时,两个特征向量之间的距离
以下获取博客间的最小距离及对应的关系
'''hcluster算法''' # 分级聚类算法以一组对应于原始数据项的聚类开始。函数的主循环部分会尝试每一组可能的配对并计算它们的相关度 # 以此来找出最佳配对。新的聚类包含等于两个旧聚类的数值求均值之后的结果。重复这一过程,直到只剩下一个聚类为止。 def hcluster(rows, distance=pearson): distances={} # 每计算一对节点的距离值就会保存在这个里面,这样为了避免重复计算 currentclustid=-1 # 最开始的聚类就是数据集中的行 clust=[bicluster(rows[i],id=i) for i in range(len(rows))] # clust是一个列表,列表里面是一个又一个bicluster对象 # 此时 clust=[bicluster(rows[1],id=1), bicluster(rows[2],id=2),...] while len(clust)>1: ''' while 判断条件:判断条件可以是任何表达式,任何非零,或非空的值均为null 执行语句...''' # python编程中,while语句用于循环执行程序,即在某条件下,循环执行某段程序,以处理需要重复处理的相同任务 lowestpair=(0,1) # 先假设lowestpair是0和1号 # lowestpair 为距离最近的两个ID closest=distance(clust[0].vec,clust[1].vec) # 先计算第一行第二行的相关度,赋值给closest,此时lowestpair=(0,1) # 遍历每个配对,寻找最小距离 for i in range(len(clust)): for j in range(i+1, len(clust)): # 用distance来缓存距离的计算值 # 遍历,使 i不等于j 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(clust[0].vec))] # 建立新的聚类 newcluster=bicluster(mergevec, left=clust[lowestpair[0]], right=clust[lowestpair[1]], distance=closest,id=currentclustid) # 不在原始集合中的聚类,其ID为负数 currentclustid-=1 del clust[lowestpair[1]] del clust[lowestpair[0]] clust.append(newcluster) return clust[0] # 当只有一个元素之后,就返回,这个节点相当于根节点
以上代码已经获取了一个各博客的最小距离,下面对如何展示进行处理,递归遍历聚类树,并将其以类似文件系统层级结构的形式打印出来。
# clusters.py
def printclust(clust, labels=None,n=0): for i in range(n): print(' ',) if clust.id < 0: # 负数标记代表这是一个分支 print('-') else: # 正数标记代表这是一个叶节点 if labels==None: print(clust.id) else: print(labels[clust.id]) # 现在开始打印右侧分支和左侧分支 if clust.left!=None:printclust(clust.left,labels=labels,n=n+1) if clust.right!=None:printclust(clust.right,labels=labels,n=n+1)
调用上述函数:clusters.printclust(clust, labels=blognames)
但是上述的结果,还是不太明细,下面对最终的层级显示进行优化。
绘制树状图
思路:1、由于树状图是图形的,并且要被保存为jpg格式,需下载PIL库
2、利用一个函数来返回给定聚类的总体高度。如果聚类是一个叶节点(即没有分支),则其高度为1;否则,高度为所有分支的高度之和。
# 定义一个递归函数 def getheight(clust): # 这是叶节点吗?若是,则高度为1 if clust.left=None and clust.right==None: return 1 # 否则,高度每个的高度之和 return getheight(clust.left)+getheight(clust.right) # 除此之外,需根节点总体误差。因为线条的会根据每个的进行相应的,所以需总的误差值生成一个缩放因子。一个节点的误差深度等于其下属的每个分支的最大可能误差。 def getdepth(clust): # 一个叶节点的距离是0.0 if clust.left=None and clust.right=None: return 0 # 一个枝节点的距离等于左右两则分支中距离较大者 # 加上该枝点自身的距离 return max(getdepth(clust.left),getdepth(clust.right))+clust.distance #函数drawdendrogram 为每一个最终生成的聚类创建一个高度为20像素,宽度固定的图片。其中缩放因子是由固定宽度的深度值得到的。 def drawdwndrogram(clust,labels,jpeg='clusters.jpg'): # 高度和宽度 h=getheight(clust)*20 w=1200 depth=getdepth(clust) # 由于宽度是固定的,因此我们需要对距离值做相应的调整 scaling=float(w-1500)/depth # 创建一个白色背景的图片 img=Image.new('RGB',(w,h),(255,255,255)) draw=ImageDraw(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')
快捷键
加粗Ctrl + B
斜体
Ctrl + I
引用
Ctrl + Q
插入链接
Ctrl + L
插入代码
Ctrl + K
插入图片
Ctrl + G
提升标题
Ctrl + H
有序列表
Ctrl + O
无序列表
Ctrl + U
横线
Ctrl + R
撤销
Ctrl + Z
重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:项目 | 价格 |
---|---|
Computer | $1600 |
Phone | $12 |
Pipe | $1 |
定义列表
Markdown Extra 定义列表语法:项目1
项目2
定义 A
定义 B
项目3
定义 C
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:@requires_authorization def somefunc(param1='', param2=0): '''A docstring''' if param1 > param2: # interesting print 'Greater' return (param2 - param1 + 1) or None class SomeClass: pass >>> message = '''interpreter ... prompt'''
脚注
生成一个脚注1.目录
用[TOC]来生成目录:
发现群组数据聚类
一单词向量
1 对博客用户进行分组
2分级聚类
绘制树状图
快捷键
Markdown及扩展
表格
定义列表
代码块
脚注
目录
数学公式
UML 图
离线写博客
浏览器兼容
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.行内公式,数学公式为:Γ(n)=(n−1)!∀n∈N。
块级公式:
x=−b±b2−4ac−−−−−−−√2a
更多LaTex语法请参考 这儿.
UML 图:
Created with Raphaël 2.1.0Created with Raphaël 2.1.0张三张三李四李四嘿,小四儿, 写博客了没?李四愣了一下,说:忙得吐血,哪有时间写。
或者流程图:
Created with Raphaël 2.1.0开始我的操作确认?结束yesno
关于 序列图 语法,参考 这儿,
关于 流程图 语法,参考 这儿.
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。IE9以下不支持
IE9,10,11存在以下问题
不支持离线功能
IE9不支持文件导入导出
IE10不支持拖拽文件导入
这里是 脚注 的 内容. ↩
相关文章推荐
- “集体智慧编程”之第三章:“发现群组”的 分级聚类
- 【集体智慧编程】第三章、发现群组
- “集体智慧编程”之第三章:“发现群组”的 列聚类
- “集体智慧编程”之第三章:“发现群组”的 k均值聚类
- 集体智慧编程第三章之发现群组
- 集体智慧编程 第三章 发现群组
- 集体智慧编程第三章 发现群组
- 【集体智慧编程】第三章、发现群组
- 集体智慧编程(二)发现群组
- 集体智慧编程错误总结第三章
- “集体智慧编程”之第三章:带偏好条件的聚类及聚类的展示方式
- 集体编程智慧(发现的一些代码问题)
- 集体智慧编程:第三章 聚类
- 集体智慧编程-K均值聚类代码理解
- 集体智慧编程——神经网络预测点击率-Python实现
- 集体智慧编程第六章对面向对象的深入理解
- 集体智慧编程_推荐系统2
- 集体智慧编程-单词统计
- 读集体编程智慧所感
- 集体智慧编程学习之聚类系统