本文很长很长,有很多很多图,包含以下部分:
1.算法简介
2.如何分类平面点
3.如何分类空间点
4.如何分类多维数据
5.后记
提醒:以下内容包括:智障操作,无中生友,重复造轮子 等
1.算法简介
k均值聚类算法(k-means),这东西是什么意思?
k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。——百度百科
我用人话解释一下:
假如你有一些平面上的点,大概如下图。你想把它们分组,离得近的点分到同一组去,离得远的分到不同的组。
你估摸了一下,大概知道要分9组,别的限制条件一概不知,这时候就非常适合用这种简单粗暴的方法。
方法是这样的:
1.在这些点的范围内,随机取9个点,取名为“聚类中心”。注意,是随机的,想怎么取就怎么取。
2.对每一个点,计算它到9个聚类中心的位置,找到最近的聚类中心,并将其划分到那一组中
很明显,分组的情况相当糟糕,毕竟聚类中心是随机的嘛。
3.对每一个组内的点,求出平均点,并将其作为新的聚类中心,在下图中,绿色点是新的聚类中心。
4.重复2、3的步骤,直到分组情况不再变化
这就是k均值聚类算法的平面应用方式,接下来介绍在grasshopper里如何实现它。
2.如何分类平面点
首先画一些点,拾取进grasshopper中;求出这些点的bounding box,以此为边界,使用populate 3d/2d生成9个点作为聚类中心。
以聚类中心为起始点,生成泰森多边形,泰森多边形内的点就是被分到该组的点。使用Point In Curve搭配 Cull Pattern实现这一步
(当然,可以计算距离然后排序,这两种方法是等效的。只不过在平面的情况下,这里选择了更直观的方法)
使用average,求出各组平均点。
使用插件anemone,根据上文所述,在Populate 3D后设立循环起点,在average后设立循环终点,并让控制循环的两个电池对应,设立最大循环次数。
OK,完美!我们的“平面kmeans.gh”正式完成!
望着简单优雅的电池图和灵巧变动的迭代动画,电脑前的你获得了前所未有的满足感。
然而,你很快发现这个电池并不完美。
首先它没有显示结果。虽然分组完毕,但是并不直观。
你拖出了Convex Hull,解决了这一问题。
显示分组结果之后你马上发现,即便是迭代了十余次,分组结果已经稳定,这个结果还是不能让你满意。
首先,本来是要分9组的,现在它只有8组,;而且右边有两组紧挨着的点,凭什么他们不能分到一组去?
原因在于初始聚类中心上。因为它是随机的,所以可能有一个聚类中心和所有点都距离很远,没有点在它那一组,导致它成了光杆司令。
因为第一次分组它就没有组员,所以以后也不再会有,最终导致结果少了一组。
同理,两个聚类中心之间存在一大批点,最终会导致两个中心势均力敌,平分这些点。
要解决这些问题,只能调整随机的种子,直到结果令人满意。
解决了分组问题,你隐隐约约觉得还有哪里不对劲,没错,就是循环的部分。
现在,运算结果会不停迭代直至最大次数,这就面临着“最大迭代次数设置为多少是合适的”的问题。太大,浪费资源;太小,分组情况不稳定。一般来说,人们习惯性地设为100,当然这只是权宜之计,略有强迫症的你决定做出可以终止迭代的部分。
根据算法定义,当分组情况不变时,停止迭代。
Loop End电池的exit端等于TRUE时,停止迭代。
也就是说,比较一下本次运算和上次运算时的分组情况,如果相同,则输出true到exit端。
“分组情况”是什么?
是每一组内点的数量(列表长度)。把这些数据转换成一个数据,而且还能避免误判的一个最简便的方式,就是Construct Path。
这样,“分组情况”就能被一行唯一的文字代表,只要用match text把它和上一次迭代时的文字比较就行了。
但是可惜,anemone没有查看迭代过程中的上次数据的功能,(如果有,请教教我)只能自己做了。如下图:
这一部分是什么意思呢?
Data Recorder记录所有通过的数据。
List Item列出本次和上次迭代的文字。
Replace Nulls是为了用随便什么别的文字替换掉null。因为如果Data Recorder记录的数据不足两个,list item的-1端会和i端一致,使得循环结束,所以必须将wrap设为false;这样就导致-1端会变成null,所以必须将它替换掉。
最后就是match text,当两输入端一致时输出true,搞定。
最终成果如下
OK,完美!我们的“平面kmeans改.gh”正式完成!
望着简单粗暴的电池图和完全自动的迭代过程,电脑前的你获得了前所未有的满足感。
随着鼠标的一阵抖动,整个电池图索然无味。
处于贤者时间的你不禁想到,这个方法可以拓展到3维空间里吗?
3.如何分类空间点
灵巧的双手穿针引线,类比的思维高速运转——顷刻,能够处理空间点的电池组横空出世,它在算法上完全继承了“平面kmeans改.gh”的衣钵:
但是,麻烦也如期而至——会建立曲面的Voronoi 3D 和求交集的Point In Brep两个电池大大拖慢了运算速度。在平面情况下,这种方法的劣势还不明显,现在则已经成为必须要解决的问题了。
所以只能根据定义中的计算方式来分组了。
首先调整数据结构,计算点到聚类中心的距离,依照距离将聚类中心排序
可见,现在有1295个点,23个聚类中心,每一条路径都代表着一个点,路径内的23个点依照距离从小到大排列。
重点来了。现在提取每一条路径中的第一个数据——那是距离某一个特定点最近的聚类中心。
现在有1295条路径,也就是每一个点都从23个聚类中心里面,找到了一个距离它最近的。
之后将它flatten,使用Create Set、Member Index和List Item素质三连,就得到了分好组的点。
这套连招究竟做了什么?是什么原理?
看一下flatten后的数据。有1295个点物件,他们是1295个点各自对应的最近的聚类中心。
目标是:将要处理的点分组,对应同一个聚类中心的点处在相同路径下。
很明显,图中有很多的重复点,使用Create Set得到一个没有重复点的集合,该集合有23个点物件,正是那23个聚类中心。
Member Index的功能是:对于某个列表中的数据,返回它在这个列表中的索引。输入23个独特的聚类中心,输出这23个聚类中心在原列表中的索引。在原列表中,每个聚类中心都重复了很多次,所以,索引的数目一共1295个。
既然有了索引,只需要用List Item将数据取出来就好了,只不过这次输入的列表要是待处理的点
费了老鼻子劲,终于得到了分好组的点,接下来只要照搬原先写好的电池就行了。
最终结果如下:
OK,完美!我们的“空间kmeans.gh”正式完成!
望着精准高效的电池图和高端酷炫的迭代过程,电脑前的你获得了前所未有的满足感。
随着鼠标的一阵抖动,整个电池图索然无味。
处于贤者时间的你不禁想到,平面点由xy坐标确定,凡是有两个属性的数据(比如某人的身高和体重)就可以用唯一的平面点来代表;同理,有三个属性的数据(比如某人的三围)就可以用唯一的空间点来代表。
而你刚刚做出了可以给这些点自动分类的算法,虽然不知道有什么用——
但是电脑前的你还是获得了前所未有的满足感。
不过你很快就意识到,现实中的数据基本上都有三个及以上的属性,如果不将算法拓展到n维空间,也就无法处理这些数据,算法的能力将大大减弱——而且这个算法本来就不局限于三维空间坐标。
但是由于grasshopper自身的限制,它不支持多维空间中的点和向量。
即便如此,你还是在想:到底能不能拓展到n维空间中呢?
4.如何分类多维数据
幸运的是,grasshopper在关闭一扇门的同时,也打开了一扇窗——
矩阵
虽然grasshopper不支持n维向量,但是可以用1行n列的矩阵来代替。通过把多维空间中的点转化成矩阵,你看到了希望。
很快,麻烦来了:
由于人不能理解多维空间,数据的可视化就成了一个大难题。不过可以通过在空间点上附加额外的属性来表示多维点——比如在空间点上画球,用球的半径代表第四维数据
空间点上画立方体,用立方体的长宽高代表第四、五、六维数据
此外,生成随机点的部分也不再多维点中适用,只能手动求出各维度数据区间,生成随机数再合成一个多维点。下图是 生成随机矩阵和将多维点转化为矩阵的部分。
由于矩阵不是几何物件,不能用Distance计算距离,若要求矩阵所代表的多维点的欧氏距离,则需要手动计算。而且矩阵本身又自带一层数据结构,处理起来相当麻烦。下图是计算距离的部分。
其他部分都和三维kmeans相似。
不知过了多长时间,重启了多少次grasshopper,数据结构出错多少次,终于,电池运行成功了。
在最终结果如下:
稍加改动,就可以直接从Excel中导入数据并计算了。
OK,完美!我们的“多维kmeans.gh”正式完成!
5.后记
望着并不复杂的电池图和前途无量的算法,电脑前的你获得了前所未有的满足感。
随着鼠标的一阵抖动,整个电池图索然无味。
处于贤者时间的你不禁想到,整个过程可以写成文章发到知乎上,或许能帮到别人。
在码字之前,你觉得,也许应该问问之前是否有人做过类似的事情,通过对比学习,一定大有收获。
受群友的指引,你下载了owl和最新版的Lunchbox
https://www.food4rhino.com/app/owl
https://provingground.io/tools/lunchbox/download-lunchbox-for-grasshopper/
打开插件的一瞬间,空气凝固了
你倚在阳台上,晚风试图把你从无奈中解救出来。
效果并不怎样。用grasshopper久了,就连月亮都看起来有两个输入端。
“如果我的脑袋是一个电池的话,输出端一定是水。”你抱着这样的想法坐回电脑前。
你没有放弃写知乎,只不过,在羞耻和自嘲的怂恿下,文章用的是第二人称的视角。
文件下载:https://pan.baidu.com/s/1PYaVuVw7dIViXYVeLk0kTQ
提取码:75u1
需要lunchbox(读取Excel)
需要meshedit(建立三维点分组预览)