学习向量量化
这篇blog用于记录我使用python对学习向量量化这种聚类算法的复现
算法简介
学习向量量化也成为LVQ(Learning Vector Quantization),同样属于原型聚类算法,类似于k-means通过希望划分的簇的数量求得相同数量的“簇中心”并以此为原型将数据集划分为对应的簇,LVQ通过求得与希望划分的簇数量相同的“原型向量”,并以此来将数据集划分为对应的簇。
如果说k-means也同样是借助原型向量的话,那么关键就在于两种算法更新原型向量的方法不同。k-means是不断的用原型向量划分簇,又用簇更新原型向量;LVQ则是利用样本的预先标注作为“监督信息”,不断利用样本更新原型向量。
算法详解
整个算法的大致流程如下:
输入: $D = {(x_1,y_1),(x_2,y_2),…,(x_m,y_m)}, q, {t_1,t_2,…,t_q},\eta \in (0,1)$其中,D是带有标记的数据集,q是原型向量个数,$t_i,i \in {1,2,…,q}$ 对应原型向量的标记,$\eta$是学习率
算法过程:
- 初始化原型向量${p_1,p_2,…,p_q}$
- repeat:
- 从$D$中随机挑选一个样本$(x_j,y_j)$
- 找到与$x_j$最近的原型向量$p_i^{*}$
- if($t_i^{*}$ == $y_j$): $p’ = p_i^{*} + \eta(p_i^{*} - x_j)$
- else: $p’ = p_i^{*} - \eta(p_i^{*} - x_j)$
- 判断是否到达退出条件
整个算法过程的关键就在于5、6,实际上相当于找到距离原型向量最近的样本,如果是同标记的则将该原型向量向该样本“拉近”,如果是不同标记的则“推远”(因为对应的是同标记的在一个簇中可能性较大,不同标记在不同簇中可能性较大)
关于7的退出条件,通常可以设置一个最大迭代轮数,或者是原型向量的更新程度已经小于了一个阈值。
代码
使用python对西瓜书上的示例复现代码如下(30个样本,9-21号样本标记为2,其它样本标记为1,随机选择5个样本作为原始向量,标记分别为1、2、2、1、1,学习率为0.1)
Data.py数据集部分:
1 | D = [ |
main.py主函数部分:
1 | import K_means |
LVQ.py学习向量量化算法部分:
1 | def lvq(D:[[float]], T:[int], q_vect:[]): |
捉个一个虫,py中万物皆对象,在初始化q_vec的时候直接将数据集D中的元素append进去,实际上共享了内存,这会导致更新原型向量的时候,数据集中的元素被更新,解决方案是使用深拷贝
修改方法:
将q_vec初始化的地方:
1 | q_vec = [] # 原型向量 |
更改为:
1 | q_vec = [x.copy() for x in (D[i] for i in q_vec_index)] # 关键修复点:使用列表拷贝 |
最终运行结果:
3
[0.36, 0.37] [0.478, 0.437] [0.446, 0.459]
8
[0.634, 0.264] [0.556, 0.215] [0.666, 0.091] [0.639, 0.161] [0.657, 0.198] [0.593, 0.042] [0.719, 0.103] [0.748, 0.232]
9
[0.403, 0.237] [0.481, 0.149] [0.437, 0.211] [0.243, 0.267] [0.245, 0.057] [0.343, 0.099] [0.359, 0.188] [0.339, 0.241] [0.282, 0.257]
5
[0.697, 0.46] [0.774, 0.376] [0.714, 0.346] [0.751, 0.489] [0.725, 0.445]
5
[0.608, 0.318] [0.483, 0.312] [0.525, 0.369] [0.532, 0.472] [0.473, 0.376]
对应为5个簇中样本的数量和相应的样本
注:理论上应该将数据集D划分为训练集和测试集,通过训练集训练模型(得到所有合理的原型向量),然后利用测试集测试,利用原型向量将测试集划分为对应数量的簇;这样才能完整地体现“机器学习”,但是这里只是一个简单的例子,将数据集D全用作训练集,得到原型向量,再把整个训练集划分为了对应的簇。
有了原型向量之后,划分为簇是很简单的,样本距离哪个原型向量最近,就纳入对应的簇即可。