推荐系统实践(五)聚类

https://www.cnblogs.com/pinard/p/6179132.html
一、原理
(1)kmeans
常识

(2)minibatch-kmeans
Mini Batch K-Means算法是K-Means算法的变种,采用小批量的数据子集减小计算时间,同时仍试图优化目标函数,这里所谓的小批量是指每次训练算法时所随机抽取的数据子集,采用这些随机产生的子集进行训练算法,大大减小了计算时间,与其他算法相比,减少了k-均值的收敛时间,小批量k-均值产生的结果,一般只略差于标准算法。

该算法的迭代步骤有两步:

1:从数据集中随机抽取一些数据形成小批量,把他们分配给最近的质心

2:更新质心

与K均值算法相比,数据的更新是在每一个小的样本集上。对于每一个小批量,通过计算平均值得到更新质心,并把小批量里的数据分配给该质心,随着迭代次数的增加,这些质心的变化是逐渐减小的,直到质心稳定或者达到指定的迭代次数,停止计算

Mini Batch K-Means比K-Means有更快的 收敛速度,但同时也降低了聚类的效果,但是在实际项目中却表现得不明显

(3)Birch(BIRCH的全称是利用层次方法的平衡迭代规约和聚类(Balanced Iterative Reducing and Clustering Using Hierarchies))
Ⅰ、BIRCH算法比较适合于数据量大,类别数K也比较多的情况。它运行速度很快,只需要单遍扫描数据集就能进行聚类

BIRCH算法利用了一个树结构来帮助我们快速的聚类,这个数结构类似于平衡B+树,一般将它称之为聚类特征树(Clustering Feature Tree,简称CF Tree)。这颗树的每一个节点是由若干个聚类特征(Clustering Feature,简称CF)组成。。从下图我们可以看看聚类特征树是什么样子的:每个节点包括叶子节点都有若干个CF,而内部节点的CF有指向孩子节点的指针,所有的叶子节点用一个双向链表链接起来。推荐系统实践(五)聚类_第1张图片

Ⅱ、聚类特征CF 和 CF tree
在聚类特征树中,一个聚类特征CF是这样定义的:每一个CF是一个三元组,可以用(N,LS,SS)表示。其中N代表了这个CF中拥有的样本点的数量,这个好理解;LS代表了这个CF中拥有的样本点各特征维度的和向量,SS代表了这个CF中拥有的样本点各特征维度的平方和。举个例子如下图,在CF Tree中的某一个节点的某一个CF中,有下面5个样本(3,4), (2,6), (4,5), (4,7), (3,8)。则它对应的N=5, LS=(3+2+4+4+3,4+6+5+7+8)=(16,30), SS =(32+22+42+42+32+42+62+52+72+82)=(54+190)=244
 CF有一个很好的性质,就是满足线性关系,也就是CF1+CF2=(N1+N2,LS1+LS2,SS1+SS2)。这个性质从定义也很好理解。如果把这个性质放在CF Tree上,也就是说,在CF Tree中,对于每个父节点中的CF节点,它的(N,LS,SS)三元组的值等于这个CF节点所指向的所有子节点的三元组之和。

推荐系统实践(五)聚类_第2张图片
上图中可以看出,根节点的CF1的三元组的值,可以从它指向的6个子节点(CF7 - CF12)的值相加得到。这样我们在更新CF Tree的时候,可以很高效。

对于CF Tree,我们一般有几个重要参数,第一个参数是每个内部节点的最大CF数B,第二个参数是每个叶子节点的最大CF数L,第三个参数是针对叶子节点中某个CF中的样本点来说的,它是叶节点每个CF的最大样本半径阈值T,也就是说,在这个CF中的所有样本点一定要在半径小于T的一个超球体内。对于上图中的CF Tree,限定了B=7, L=5, 也就是说内部节点最多有7个CF,而叶子节点最多有5个CF。

Ⅲ、生成CF tree
 下面我们看看怎么生成CF Tree。我们先定义好CF Tree的参数: 即内部节点的最大CF数B, 叶子节点的最大CF数L, 叶节点每个CF的最大样本半径阈值T

在最开始的时候,CF Tree是空的,没有任何样本,我们从训练集读入第一个样本点,将它放入一个新的CF三元组A,这个三元组的N=1,将这个新的CF放入根节点,此时的CF Tree如下图:
    推荐系统实践(五)聚类_第3张图片

现在我们继续读入第二个样本点,我们发现这个样本点和第一个样本点A,在半径为T的超球体范围内,也就是说,他们属于一个CF,我们将第二个点也加入CF A,此时需要更新A的三元组的值。此时A的三元组中N=2。此时的CF Tree如下图:
推荐系统实践(五)聚类_第4张图片
此时来了第三个节点,结果我们发现这个节点不能融入刚才前面的节点形成的超球体内,也就是说,我们需要一个新的CF三元组B,来容纳这个新的值。此时根节点有两个CF三元组A和B,此时的CF Tree如下图:
推荐系统实践(五)聚类_第5张图片
当来到第四个样本点的时候,我们发现和B在半径小于T的超球体,这样更新后的CF Tree如下图:
推荐系统实践(五)聚类_第6张图片
那个什么时候CF Tree的节点需要分裂呢?假设我们现在的CF Tree 如下图, 叶子节点LN1有三个CF, LN2和LN3各有两个CF。我们的叶子节点的最大CF数L=3。此时一个新的样本点来了,我们发现它离LN1节点最近,因此开始判断它是否在sc1,sc2,sc3这3个CF对应的超球体之内,但是很不幸,它不在,因此它需要建立一个新的CF,即sc8来容纳它。问题是我们的L=3,也就是说LN1的CF个数已经达到最大值了,不能再创建新的CF了,怎么办?此时就要将LN1叶子节点一分为二了。
推荐系统实践(五)聚类_第7张图片
 我们将LN1里所有CF元组中,找到两个最远的CF做这两个新叶子节点的种子CF,然后将LN1节点里所有CF sc1, sc2, sc3,以及新样本点的新元组sc8划分到两个新的叶子节点上。将LN1节点划分后的CF Tree如下图:
 推荐系统实践(五)聚类_第8张图片
 如果我们的内部节点的最大CF数B=3,则此时叶子节点一分为二会导致根节点的最大CF数超了,也就是说,我们的根节点现在也要分裂,分裂的方法和叶子节点分裂一样,分裂后的CF Tree如下图:
 推荐系统实践(五)聚类_第9张图片
  有了上面这一系列的图,相信大家对于CF Tree的插入就没有什么问题了,总结下CF Tree的插入:

1. 从根节点向下寻找和新样本距离最近的叶子节点和叶子节点里最近的CF节点

2. 如果新样本加入后,这个CF节点对应的超球体半径仍然满足小于阈值T,则更新路径上所有的CF三元组,插入结束。否则转入3.

3. 如果当前叶子节点的CF节点个数小于阈值L,则创建一个新的CF节点,放入新样本,将新的CF节点放入这个叶子节点,更新路径上所有的CF三元组,插入结束。否则转入4。

4.将当前叶子节点划分为两个新叶子节点,选择旧叶子节点中所有CF元组里超球体距离最远的两个CF元组,分布作为两个新叶子节点的第一个CF节点。将其他元组和新样本元组按照距离远近原则放入对应的叶子节点。依次向上检查父节点是否也要分裂,如果需要按和叶子节点分裂方式相同。

总结:
一个个样本往树里面插入。
CF中样本不能超过N,否则CF需要分裂
树的节点不能超过B,子节点不能超过L,否则需要分裂

Ⅳ、算法流程
对应的输出就是若干个CF节点,每个节点里的样本点就是一个聚类的簇。也就是说BIRCH算法的主要过程,就是建立CF Tree的过程。
  当然,真实的BIRCH算法除了建立CF Tree来聚类,其实还有一些可选的算法步骤的,现在我们就来看看 BIRCH算法的流程。

1) 将所有的样本依次读入,在内存中建立一颗CF Tree, 建立的方法参考上一节。

2)(可选)将第一步建立的CF Tree进行筛选,去除一些异常CF节点,这些节点一般里面的样本点很少。对于一些超球体距离非常近的元组进行合并

3)(可选)利用其它的一些聚类算法比如K-Means对所有的CF元组进行聚类,得到一颗比较好的CF Tree.这一步的主要目的是消除由于样本读入顺序导致的不合理的树结构,以及一些由于节点CF个数限制导致的树结构分裂。

4)(可选)利用第三步生成的CF Tree的所有CF节点的质心,作为初始质心点,对所有的样本点按距离远近进行聚类。这样进一步减少了由于CF Tree的一些限制导致的聚类不合理的情况。

从上面可以看出,BIRCH算法的关键就是步骤1,也就是CF Tree的生成,其他步骤都是为了优化最后的聚类结果。

Ⅴ、 BIRCH算法小结
BIRCH算法可以不用输入类别数K值,这点和K-Means,Mini Batch K-Means不同。如果不输入K值,则最后的CF元组的组数即为最终的K,否则会按照输入的K值对CF元组按距离大小进行合并

BIRCH算法的主要优点有:

1) 节约内存,所有的样本都在磁盘上,CF Tree仅仅存了CF节点和对应的指针。

2) 聚类速度快,只需要一遍扫描训练集就可以建立CF Tree,CF Tree的增删改都很快。

3) 可以识别噪音点,还可以对数据集进行初步分类的预处理

BIRCH算法的主要缺点有:

1) 由于CF Tree对每个节点的CF个数有限制,导致聚类的结果可能和真实的类别分布不同.

2) 对高维特征的数据聚类效果不好。此时可以选择Mini Batch K-Means

3) 如果数据集的分布簇不是类似于超球体,或者说不是凸的,则聚类效果不好。

二、code
(1)kmeans

u1_base = open('./data/ml-100k/u1.base')
X_array = []
y_array = []
# user id | item id | rating | timestamp
for l in u1_base:
    ls = l.strip().split('\t')
    y_array.append(int(ls[2]))
    X_array.append([int(ls[0]), int(ls[1])])
kmeas = KMeans(n_clusters=5,random_state=0).fit(X_array)

print(kmeas.predict([[1,6]]))

调优方向:
1、重新分析数据
2、重新选择算法
3、重新理解数据

评估方法:
adjusted_rand_score调整兰德系数:
1、对任意数量的聚类中心和样本数,随机聚类的ARI都非常接近于0;
2、取值在[-1,1]之间,负数代表结果不好,越接近于1越好;
3、可用于聚类算法之间的比较。

u1_test = open('./data/ml-100k/u1.test')
X_test = []
y_test = []
for l in u1_test:
    ls = l.strip().split('\t')
    X_test.append([int(ls[0]), int(ls[1])])
    y_test.append(int(ls[2]))

kmeas = KMeans(n_clusters=5,random_state=0).fit(X_test)
y_pre = kmeas.predict(X_test)
y_pred = []
for y in y_pre:
    y += 1
    y_pred.append(y)
print(metrics.adjusted_rand_score(y_test,y_pred))

2)minibatch-kmeans

from sklearn.cluster import MiniBatchKMeans
mb = MiniBatchKMeans(n_clusters=5, batch_size=200).fit(X_array)
y_mini_pre = mb.predict(X_test)
y_mini_pred = []
for y in y_mini_pre:
    y += 1
    y_mini_pred.append(y)
print(metrics.adjusted_rand_score(y_test, y_mini_pred))

(3)Birch

from sklearn.cluster import Birch
bch = Birch(threshold=0.5, branching_factor=50, n_clusters=5, compute_labels=True)
bch.fit(X_array[0:1000])
y_bch_pre = bch.predict(X_test[0:1000])
y_bch_pred = []
for y in y_bch_pre:
    y += 1
    y_bch_pred.append(y)
from sklearn import metrics
print(metrics.adjusted_rand_score(y_test[0:1000], y_bch_pred))
  1. threshold:即叶节点每个CF的最大样本半径阈值T,它决定了每个CF里所有样本形成的超球体的半径阈值。
  2. branching_factor:即CF Tree内部节点的最大CF数B,以及叶子节点的最大CF数L。
    3)n_clusters:即类别数K,在BIRCH算法是可选的,如果类别数非常多,我们也没有先验知识,则一般输入None,此时BIRCH算法第4阶段不会运行。但是如果我们有类别的先验知识,则推荐输入这个可选的类别值。默认是3,即最终聚为3类。
    4)compute_labels:布尔值,表示是否标示类别输出,默认是True。一般使用默认值挺好,这样可以看到聚类效果。
     在评估各个参数组合的聚类效果时,还是推荐使用Calinski-Harabasz Index,Calinski-Harabasz Index在scikit-learn中对应的方法是metrics.calinski_harabaz_score.

你可能感兴趣的:(推荐算法实践)