machine-learning-notes
  • 封面
  • 目录
  • 前言
  • 个人前言
  • 机器学习前言
    • 什么是机器学习和模式识别
    • 机器学习的应用
    • 机器学习的流程
    • 不同的机器学习算法对相同数据预测效果不同
    • 快速入门机器学习
    • 机器学习需要参考哪些书
    • 机器学习的学习路径
    • 深度学习的学习路径
    • 互联网机器学习特定岗位所需技能
  • 机器学习面试
  • 数学基础
  • 微积分
    • 泰勒展开
    • e的直观认识
    • 傅里叶变换
    • 希尔伯特空间
  • 线性代数
    • 范数
    • 矩阵求导
    • 特征值
    • 奇异值分解
  • 概率与信息论
    • 综述概率论基本定义
    • 概率论与贝叶斯先验
    • 正态分布
    • 贝叶斯概率
    • 概率符号说明
    • 共轭先验
    • 信息论
  • 数值计算与优化
    • 最小二乘法
    • 等式约束的拉格朗日乘子法
    • 凸优化
      • 凸集和凸函数
      • 凸优化问题
  • 梯度下降算法
    • 随机梯度下降SGD
    • 动量法Momentum
    • 牛顿动量Nesterov
    • AdaGrad
    • RMSprop
    • Adadelta
    • Adam
    • Nadam
    • AMSGrad
    • AdasMax
  • 概率图模型
    • 概率图模型概论
    • 概率图简介
  • 编程基础
  • linux
    • linux常用命令
    • shell
      • 输入输出重定向
  • python
    • python简介
    • python语法
      • 基础语法
      • 数据结构
      • 过程控制
      • 函数
      • 类和对象
      • 文件操作
      • 正则表达式
    • python库
      • numpy
      • pandas
      • scipy
      • matplotlib
      • scikit-learn
    • python应用
      • 排序算法
  • 数据结构与算法
    • 数据结构
    • 算法思想
      • 排序
        • 堆排序
        • 归并排序
        • 快速排序
      • 递归
    • 剑指offer
      • 链表
      • 二叉树
      • 数组
      • 字符串
      • 栈和队列
      • 递归
      • 动态规划
      • 其他
    • leetcode
    • 编程语言
      • c++
  • Hadoop
    • Hadoop简介
    • MapReduce
  • Hive
  • Spark
  • TensorFlow
    • TensorFlow1.0
      • TensorFlow基础
      • TensorFlow基础概念解析
      • TensorFlow机器学习基础
      • Tensorflow分布式架构
    • TensorFlow2.0
  • PyTorch
  • 机器学习
  • 机器学习概论
  • 特征工程
  • 感知机
  • k近邻
  • 朴素贝叶斯
  • 线性模型
    • 最大熵模型
    • 指数族分布与广义线性模型
    • 线性回归
      • Ridge回归(岭回归)
      • Lasso回归
    • Logistic回归-对数几率回归
  • 决策树
  • 支持向量机
    • 线性可分支持向量机与硬间隔最大化
    • 线性支持向量机与软间隔最大化
    • 非线性支持向量机与核函数
    • 序列最小最优化算法SMO
    • SVM总结
  • 集成学习
    • Bagging
      • 随机森林
    • Boosting
      • AdaBoost
      • GradientBoosting
        • GBDT
        • XGBoost
          • XGBoost理论
          • XGBoost实践
    • Stacking
  • 降维
    • PCA主成分分析
    • 流形学习
  • EM算法
  • HMM隐马尔科夫模型
  • CRF条件随机场
  • 聚类
    • k均值聚类
    • 高斯混合模型
  • 主题模型
    • LDA隐狄利克雷分布
  • 知识点
    • 损失函数
    • 负采样
  • 机器学习算法总结
  • 深度学习
  • 深度学习概论
  • ANN人工神经网络
  • 知识点
    • Batch Normalization
  • CNN卷积神经网络
  • 深度学习优化算法
  • RNN循环神经网络
  • LSTM长短期记忆网络
  • GRU门控循环单元
  • GNN图神经网络
    • GNN图神经网络综述
    • GCN图卷积网络
      • GCN图卷积网络初步理解
      • GCN图卷积网络的numpy简单实现
      • GCN图卷积网络本质理解
      • GCN图卷积网络全面理解
      • SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS ICLR2017
  • 神经网络架构搜索
    • Weight-Agnostic-Neural-Networks Google2019
  • 强化学习
  • 强化学习概论
  • 马尔科夫决策过程
  • 动态规划
  • 无模型方法一:蒙特卡洛
  • 无模型方法二:时间差分
  • 无模型方法三:多步自举
  • 函数近似和深度网络
  • 策略梯度算法
  • 深度强化学习
  • 基于模型的强化学习
  • 强化学习前景
  • 自然语言处理
  • 自然语言处理概论
  • 自然语言
  • 语言模型和中文分词
  • word2vec
    • word2vec概述
    • word2vec算法原理
    • word2vec源码分析
    • word2vec实践
  • Seq2Seq模型和Attention机制
  • Self-Attention和Transformer
  • 知识图谱
  • 推荐系统
  • 推荐系统概论
  • 基础知识
  • 进阶知识
    • 机器学习
      • Factorization Machines ICDM2010
    • embedding
      • Network Embedding
        • LINE: Large-scale Information Network Embedding
    • 深度学习
      • DeepFM: A Factorization-Machine based Neural Network for CTR Prediction 2017
      • DSSM: Learning Deep Structured Semantic Models for Web Search using Clickthrough Data CIKM2013
    • 图卷积网络
      • Graph Convolutional Neural Networks for Web-Scale Recommender Systems KDD2018
    • 强化学习
      • DRN基于深度强化学习的新闻推荐模型
  • 业界应用
    • YouTube
      • Deep Neural Networks for YouTube Recommendations RecSys2016
    • Alibaba
      • Learning Tree-based Deep Model for Recommender Systems KDD2018
      • Deep Interest Network for Click-Through Rate Prediction KDD2018
      • DSIN:Deep Session Interest Network for Click-Through Rate Prediction IJCAI2019
Powered by GitBook
On this page
  • k均值聚类
  • 模型
  • 策略
  • 算法
  • 算法特性
  • 总体特点
  • 收敛性
  • 初始类的选择
  • 类别个数k的选择
  • 代码实践
  • sklearn中的KMeans实现
  • word2vec中KMeans实现
  • 参考资料

Was this helpful?

  1. 聚类

k均值聚类

Previous聚类Next高斯混合模型

Last updated 5 years ago

Was this helpful?

k均值聚类

k均值聚类是基于样本集合划分的聚类算法。k均值聚类将样本划分为kkk个子集,构成kkk个类,将nnn个样本分到kkk个类中每个样本到其所属类的中心的距离最小。每个样本只能属于一个类,所以kkk均值聚类是硬聚类。下面分别介绍kkk均值聚类的模型、策略、算法,讨论算法的特性及相关问题。

模型

给定mmm个样本的集合X={x1,x2,...,xn}X=\{x_1, x_2, ... , x_n\}X={x1​,x2​,...,xn​},每个样本由一个特征向量表示,特征向量的维数是mmm。k均值聚类的目标是将nnn个样本分到kkk个不同的类或着簇中,这里假设k<nk<nk<n。kkk个类G1,G2,...,GkG_1, G_2, ... ,G_kG1​,G2​,...,Gk​形成对样本集合XXX的划分,其中

Gi⋂Gj=∅, ⋃i=1k=XG_i \bigcap G_j = \varnothing ,\ \bigcup_{i=1}^{k}=XGi​⋂Gj​=∅, i=1⋃k​=X

。用CCC表示划分,一个划分对应着一个聚类结果。

划分CCC是一个多对一的函数。事实上,如果把每个样本用一个整数i∈{1,2,...,n}i \in \{1, 2, ... , n\}i∈{1,2,...,n}表示,每个类也用一个整数l∈{1,2,...,k}l \in \{1, 2, ... , k\}l∈{1,2,...,k}表示,那么划分或者聚类可以用函数l=C(i)l = C(i)l=C(i)表示,其中i∈{1,2,...,n}i \in \{1, 2, ... , n\}i∈{1,2,...,n},l∈{1,2,...,k}l \in \{1, 2, ... , k\}l∈{1,2,...,k}。所以k均值聚类的模型是一个从样本到类的函数。

策略

k均值聚类归结为样本集合XXX的划分,或者从样本到类的函数的选择问题。k均值聚类的策略是通过损失函数的最小化选取最优的划分或函数C∗C^{*}C∗。

首先,采用欧式距离平方作为样本之间的距离d(xi,xj)d(x_i, x_j)d(xi​,xj​)

d(xi,xj)=∑k=1m(xki−xkj)2=∣∣xi−xj∣∣2\begin{aligned} d(x_i,x_j)&=\sum_{k=1}^{m}(x_{ki}-x_{kj})^2\\ &= ||x_i-x_j||^2 \end{aligned}d(xi​,xj​)​=k=1∑m​(xki​−xkj​)2=∣∣xi​−xj​∣∣2​

然后,定义样本与其所属类的中心之间的距离的总和为损失函数,即

W(C)=∑l=1k∑C(i)=l∣∣xi−xˉl∣∣2W(C)=\sum_{l=1}^{k}\sum_{C(i)=l}||x_i-\bar{x}_l||^2W(C)=l=1∑k​C(i)=l∑​∣∣xi​−xˉl​∣∣2

式中,

xˉl=(xˉ1l,xˉ2l,...,xˉml)T\bar{x}_l=(\bar{x}_{1l},\bar{x}_{2l},...,\bar{x}_{ml})^Txˉl​=(xˉ1l​,xˉ2l​,...,xˉml​)T

是第lll个类的均值或者聚类中心,属于第lll类的样本个数为nl=∑i=1nI(C(i)=l)n_l=\sum_{i=1}^nI\left(C(i)=l\right)nl​=∑i=1n​I(C(i)=l),I(C(i)=l)I(C(i) = l)I(C(i)=l)是指示函数,取值为1或0。函数W(C)W(C)W(C)也称为能量,表示相同类中的样本相似的程度。

k均值聚类就是求解最优化问题:

C∗=arg minCW(C)=arg minC∑l=1k∑C(i)=l∣∣xi−xˉl∣∣2\begin{aligned} C^*&=\text{arg }\mathop{\text{min}}_C W(C)\\ &=\text{arg }\mathop{\text{min}}_C \sum_{l=1}^{k}\sum_{C(i)=l}||x_i-\bar{x}_l||^2 \end{aligned}C∗​=arg minC​W(C)=arg minC​l=1∑k​C(i)=l∑​∣∣xi​−xˉl​∣∣2​

相似的样本被聚到同类时,损失函数最小,这个目标函数的最优化能达到聚类的目的。但是,这是一个组合优化问题,nnn个样本分到kkk类,所有可能分法的数目时:

S(n,k)=1k!∑l=1k(−1)k−l(kl)kn\begin{aligned} S(n,k)=\frac{1}{k!}\sum_{l=1}^k(-1)^{k-l} \begin{pmatrix} k\\ l \end{pmatrix} k^n \end{aligned}S(n,k)=k!1​l=1∑k​(−1)k−l(kl​)kn​

这个数字是指数级的。事实上,k均值聚类的最优解求解问题是NP难问题。现实中采用迭代的方法求解。

算法

k均值聚类的算法是一个迭代的过程,每次迭代包括两个步骤:

  • 首先选择kkk个类的中心,将样本逐个指派到与其最近的中心的类中,得到一个聚类的结果;

  • 然后更新每个类的样本的均值,作为类的新的中心

重复以上步骤,知道收敛为止。具体过程如下。

首先,对于给定的中心值(m1,m2,...,mk)(m_1, m_2, ... , m_k)(m1​,m2​,...,mk​),求一个划分CCC,使得目标函数极小化:

minC∑l=1k∑C(i)=l∣∣xi−ml∣∣2\mathop{\text{min}}_C \sum_{l=1}^{k}\sum_{C(i)=l}||x_i-m_l||^2minC​l=1∑k​C(i)=l∑​∣∣xi​−ml​∣∣2

就是说在类中心确定的情况下,将每个样本分到一个类中,使样本和其所属类的中心之间的距离总和最小。求解结果,将每个样本指派到与其最近的中心mlm_lml​的类GlG_lGl​中。

然后,对给定的划分CCC,再求各个类的中心(m1,m2,...,mk)(m_1, m_2, ... , m_k)(m1​,m2​,...,mk​),使得目标函数极小化:

minm1,...,mk∑l=1k∑C(i)=l∣∣xi−ml∣∣2\mathop{\text{min}}_{m_1,...,m_k} \sum_{l=1}^{k}\sum_{C(i)=l}||x_i-m_l||^2minm1​,...,mk​​l=1∑k​C(i)=l∑​∣∣xi​−ml​∣∣2

就是说在划分确定的情况下,使样本和其所属类的中心之间的距离总和最小。求解结果,对于每个包含nln_lnl​个样本的类GlG_lGl​,更新其均值mlm_lml​:

ml=1nl∑G(i)=lxi,l=1,...,km_l=\frac{1}{n_l}\sum_{G(i)=l}x_i,\quad l=1,...,kml​=nl​1​G(i)=l∑​xi​,l=1,...,k

重复以上两个步骤,直到划分不再改变,得到聚类结果。现将k均值聚类算法叙述如下。

k均值聚类算法

输入:nnn个样本的集合XXX;

输出:样本集合的聚类C∗C^*C∗。

(1)初始化。令t=0,随机选择k个样本点作为初始聚类中心

m(0)=(m1(0),...,ml(0),...,mk(0))m^{(0)}=\left(m_1^{(0)},...,m_l^{(0)},...,m_k^{(0)}\right)m(0)=(m1(0)​,...,ml(0)​,...,mk(0)​)

(2)对样本进行聚类。对固定的类中心

m(t)=(m1(t),...,ml(t),...,mk(t))m^{(t)}=\left(m_1^{(t)},...,m_l^{(t)},...,m_k^{(t)}\right)m(t)=(m1(t)​,...,ml(t)​,...,mk(t)​)

,其中ml(t)m_l^{(t)}ml(t)​为类GtG_tGt​的中心,计算每个样本到类中心的距离,将每个样本指派到与其最近的中心的类中,构成聚类结果C(t)C^{(t)}C(t)。

(3)计算新的类中心。对聚类结果C(t)C^{(t)}C(t),计算当前各个类中的样本的均值,作为新的类中心

m(t+1)=(m1(t+1),...,ml(t+1),...,mk(t+1))m^{(t+1)}=\left(m_1^{(t+1)},...,m_l^{(t+1)},...,m_k^{(t+1)}\right)m(t+1)=(m1(t+1)​,...,ml(t+1)​,...,mk(t+1)​)

(4)如果迭代收敛或符合停止条件,输出

C∗=C(t)C^*=C^{(t)}C∗=C(t)

否则,令t=t+1t=t+1t=t+1,返回步(2)。

k均值聚类算法的复杂度是O(mnk)O(mnk)O(mnk),其中mmm是样本维数,nnn是样本个数,kkk是类别个数。在实际代码中,还要乘上迭代次数iteriteriter。

例子:

给定含有5个样本的集合

X=[0015520002]\begin{aligned} X= \begin{bmatrix} 0 & 0 & 1 & 5 & 5\\ 2 & 0 & 0 & 0 & 2 \end{bmatrix} \end{aligned}X=[02​00​10​50​52​]​

试用k均值聚类算法将样本聚到2个类中。

解:按照上述算法,

(1)选择两个样本点作为类的中心。假设选择

m1(0)=x1=(0,2)Tm2(0)=x2=(0,0)T\begin{aligned} &m_1^{(0)}=x_1=(0,2)^T\\ &m_2^{(0)}=x_2=(0,0)^T \end{aligned}​m1(0)​=x1​=(0,2)Tm2(0)​=x2​=(0,0)T​

(2)以m1(0),m2(0)m_1^{(0)},m_2^{(0)}m1(0)​,m2(0)​为类G1(0),G2(0)G_1^{(0)},G_2^{(0)}G1(0)​,G2(0)​的中心,计算x3=(1,0)T,x4=(5,0)T,x5=(5,2)Tx_3=(1,0)^T,x_4=(5,0)^T,x_5=(5,2)^Tx3​=(1,0)T,x4​=(5,0)T,x5​=(5,2)T与m1(0)=(0,2)T,m2(0)=(0,0)Tm_1^{(0)}=(0,2)^T,m_2^{(0)}=(0,0)^Tm1(0)​=(0,2)T,m2(0)​=(0,0)T的欧式距离平方。

对x3=(1,0)Tx_3 = (1, 0)^Tx3​=(1,0)T,d(x3,m1(0))=5,d(x3,m2(0))=1d(x_3,m_1^{(0)})=5,d(x_3,m_2^{(0)})=1d(x3​,m1(0)​)=5,d(x3​,m2(0)​)=1,将x3x_3x3​分到类G2(0)G_2^{(0)}G2(0)​。

对x4=(5,0)Tx_4= (5, 0)^Tx4​=(5,0)T,d(x4,m1(0))=29,d(x4,m2(0))=25d(x_4,m_1^{(0)})=29,d(x_4,m_2^{(0)})=25d(x4​,m1(0)​)=29,d(x4​,m2(0)​)=25,将x4x_4x4​分到类G2(0)G_2^{(0)}G2(0)​。

对x5=(5,2)Tx_5= (5, 2)^Tx5​=(5,2)T,d(x5,m1(0))=25,d(x5,m2(0))=29d(x_5,m_1^{(0)})=25,d(x_5,m_2^{(0)})=29d(x5​,m1(0)​)=25,d(x5​,m2(0)​)=29,将x5x_5x5​分到类G1(0)G_1^{(0)}G1(0)​。

(3)得到新的类G1(1)={x1,x5},G2(1)={x2,x3,x4}G_1^{(1)}=\{x_1,x_5\},G_2^{(1)}=\{x_2,x_3,x_4\}G1(1)​={x1​,x5​},G2(1)​={x2​,x3​,x4​},计算类的中心m1(1),m2(1)m_1^{(1)}, m_2^{(1)}m1(1)​,m2(1)​:

m1(1)=(2.5,2.0)T,m2(1)=(2,0)Tm_1^{(1)}=(2.5,2.0)^T,m_2^{(1)}=(2,0)^Tm1(1)​=(2.5,2.0)T,m2(1)​=(2,0)T

(4)重复步骤(2)和步骤(3)。

将x1x_1x1​分到类G1(1)G_1^{(1)}G1(1)​,将x2x_2x2​分到类G2(1)G_2^{(1)}G2(1)​,将x3x_3x3​分到类G2(1)G_2^{(1)}G2(1)​,将x4x_4x4​分到类G2(1)G_2^{(1)}G2(1)​,将x5x_5x5​分到类G1(1)G_1^{(1)}G1(1)​。

得到新的类G1(2)={x1,x5},G2(2)={x2,x3,x4}G_1^{(2)}=\{x_1,x_5\},G_2^{(2)}=\{x_2,x_3,x_4\}G1(2)​={x1​,x5​},G2(2)​={x2​,x3​,x4​}

由于得到的新的类没有改变,聚类停止。得到聚类结果:

G1∗={x1,x5},G2∗={x2,x3,x4}G_1^*=\{x_1,x_5\},G_2^*=\{x_2,x_3,x_4\}G1∗​={x1​,x5​},G2∗​={x2​,x3​,x4​}

算法特性

总体特点

k均值聚类有以下特点:基于划分的聚类方法:类别数kkk事先指定;以欧式距离平方表示样本之间的距离,以中心或样本的均值表示类别;以样本和其他所属类的中心之间的距离的总和为最优化的目标函数;得到的类别是平坦的、非层次化的;算法是迭算法,不能保证得到全局最优。

收敛性

k均值聚类属于启发式方法,不能保证收敛到全局最优,初始中心的选择会直接影响聚类结果。注意,类中心在聚类的过程中会发生移动,但是往往不会移动太大,因为在每一步,样本被分到与其最近的中心的类中。

初始类的选择

选择不同的初始中心,会得到不同的聚类结果。针对上面的例子,如果改变两个类的初始中心,比如选择

m1(0)=x1,m2(0)=x5m_1^{(0)}=x_1,m_2^{(0)}=x_5m1(0)​=x1​,m2(0)​=x5​

那么x2x_2x2​,x3x_3x3​会分到G1(0)G_1^{(0)}G1(0)​,x4x_4x4​会分到G2(0)G_2^{(0)}G2(0)​,形成聚类结果G1(1)={x!,x2,x3}, G2(1)={x4,x5}G_1^{(1)}=\{x_!,x_2,x_3\},\ G_2^{(1)}=\{x_4,x_5\}G1(1)​={x!​,x2​,x3​}, G2(1)​={x4​,x5​},中心是m1(1)=(0.33,0.67)T,m2(1)=(5,1)Tm_1^{(1)}=(0.33,0.67)^T,m_2^{(1)}=(5,1)^Tm1(1)​=(0.33,0.67)T,m2(1)​=(5,1)T。继续迭代,聚类结果仍然是G1(1)={x!,x2,x3}, G2(1)={x4,x5}G_1^{(1)}=\{x_!,x_2,x_3\},\ G_2^{(1)}=\{x_4,x_5\}G1(1)​={x!​,x2​,x3​}, G2(1)​={x4​,x5​}。聚类停止。

初始中心的选择,比如可以用层次聚类对样本进行聚类,得到kkk个类时停止。然后从每个类中选择一个与中心距离最近的点。

类别个数k的选择

k均值聚类中的类别数kkk值需要预先指定,而在实际应用中最优的kkk值是不知道的,解决这个问题的一个方法是尝试用不同的kkk值聚类,检验各自的到的聚类结果的质量,推测最优的kkk值。聚类结果的质量可以用类的平方直径来衡量。一般地,类别数目变小时,平均直径会增加;类别数目变大超过某个值以后,平均直径会不变;而这个值正是最有的kkk值。下图说明类别数与平均直径的关系。实验时,可以采用二分查找,快速找到最优的kkk值。

下图是聚类的类别数和平均直径的关系。

代码实践

sklearn中的KMeans实现

>>> from sklearn.cluster import KMeans
>>> import numpy as np
>>> X = np.array([[1, 2], [1, 4], [1, 0],
...               [10, 2], [10, 4], [10, 0]])
>>> kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
>>> kmeans.labels_
array([1, 1, 1, 0, 0, 0], dtype=int32)
>>> kmeans.predict([[0, 0], [12, 3]])
array([1, 0], dtype=int32)
>>> kmeans.cluster_centers_
array([[10.,  2.],
       [ 1.,  2.]])

word2vec中KMeans实现

word2vec中的KMeans源码摘录

下面这段代码是从word2vec.c中的源码摘录出来的KMeans部分,我自己改了一些变量的名称,还加了一些注释,方便看懂。

// Run K-means on the word vectors
int cluster_count = classes, iter = 10, closeid; // cluster_count: clusterCount
int *center_count = (int *)malloc(classes * sizeof(int)); //属于每个中心的单词个数
int *vocab_cluster_id = (int *)calloc(vocab_size, sizeof(int)); // 存放每个单词指派的中心id
real closev, x;
real *center_vector = (real *)calloc(classes * layer1_size, sizeof(real)); //存放每个中心的向量表示
for (a = 0; a < vocab_size; a++) vocab_cluster_id[a] = a % cluster_count; //随机指派每个单词的中心
for (a = 0; a < iter; a++) { //一共迭代iter轮
    for (b = 0; b < cluster_count * layer1_size; b++) center_vector[b] = 0;
    for (b = 0; b < cluster_count; b++) center_count[b] = 1;
    for (c = 0; c < vocab_size; c++) {
        for (d = 0; d < layer1_size; d++) center_vector[layer1_size * vocab_cluster_id[c] + d] += syn0[c * layer1_size + d]; //将属于每个中心的点的每个坐标相加
        center_count[vocab_cluster_id[c]]++; // 分别计算属于每个中心的点个数
    }
    for (b = 0; b < cluster_count; b++) { //更新每个中心的向量表示
        closev = 0;
        for (c = 0; c < layer1_size; c++) {
            center_vector[layer1_size * b + c] /= center_count[b];
            closev += center_vector[layer1_size * b + c] * center_vector[layer1_size * b + c];
        }
        closev = sqrt(closev);
        for (c = 0; c < layer1_size; c++) center_vector[layer1_size * b + c] /= closev;
    }
    for (c = 0; c < vocab_size; c++) { //更新每个样本的中心
        closev = -10;
        closeid = 0;
        for (d = 0; d < cluster_count; d++) {
            x = 0;
            for (b = 0; b < layer1_size; b++) x += center_vector[layer1_size * d + b] * syn0[c * layer1_size + b];
            if (x > closev) { //选出与单词表示最相近的中心
                closev = x;
                closeid = d;
            }
        }
        vocab_cluster_id[c] = closeid;
    }
}
// Save the K-means classes
for (a = 0; a < vocab_size; a++) fprintf(fo, "%s %d\n", vocab[a].word, vocab_cluster_id[a]);
free(center_count);
free(center_vector);
free(vocab_cluster_id);

对embdding进行KMeans聚类

word2vec训练出来的embedding结果,以二进制进行存储,其格式为

id vec[0] vec[1] ... vec[end]

注意:vec[0] vec[1] ... vec[end]需要以二进制格式存储。

将下面KMeans.c文件进行编译,然后用下面的sh脚本运行。

这里面的向量是经过归一化的,其中心向量也经过了归一化,所以其实算的是cos距离,是球面聚类。

// KMeans.c
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <stdlib.h> // mac os x
// #include <malloc.h>

const long long max_size = 2000;         // max length of strings
const long long N = 40;                  // number of closest words that will be shown
const long long max_w = 50;              // max length of vocabulary entries

typedef float real;                      // Precision of float numbers

void KMeans(const char *vocab, const float *syn0, const int vocab_size, const int layer1_size, const int classes, const char *output_file_name) {
  long a, b, c, d;
  FILE *fo;
  fo = fopen(output_file_name, "wb");
  // Run K-means on the word vectors
  int clcn = classes, iter = 10, closeid;
  int *centcn = (int *)malloc(classes * sizeof(int));
  int *cl = (int *)calloc(vocab_size, sizeof(int));
  real closev, x;
  real *cent = (real *)calloc(classes * layer1_size, sizeof(real));
  char *word = (char *)malloc(max_w * sizeof(char));

  for (a = 0; a < vocab_size; a++) cl[a] = a % clcn;
  for (a = 0; a < iter; a++) {
    for (b = 0; b < clcn * layer1_size; b++) cent[b] = 0;
    for (b = 0; b < clcn; b++) centcn[b] = 1;
    for (c = 0; c < vocab_size; c++) {
      for (d = 0; d < layer1_size; d++) cent[layer1_size * cl[c] + d] += syn0[c * layer1_size + d];
      centcn[cl[c]]++;
    }
    for (b = 0; b < clcn; b++) {
      closev = 0;
      for (c = 0; c < layer1_size; c++) {
        cent[layer1_size * b + c] /= centcn[b];
        closev += cent[layer1_size * b + c] * cent[layer1_size * b + c];
      }
      closev = sqrt(closev);
      for (c = 0; c < layer1_size; c++) cent[layer1_size * b + c] /= closev;
    }
    for (c = 0; c < vocab_size; c++) {
      closev = -10;
      closeid = 0;
      for (d = 0; d < clcn; d++) {
        x = 0;
        for (b = 0; b < layer1_size; b++) x += cent[layer1_size * d + b] * syn0[c * layer1_size + b];
        if (x > closev) {
          closev = x;
          closeid = d;
        }
      }
      cl[c] = closeid;
    }
  }
  // Save the K-means classes
  for (a = 0; a < vocab_size; a++) {
    memcpy(word, vocab + a * max_w, max_w);
    fprintf(fo, "%s %d\n", word, cl[a]);
  }
  free(centcn);
  free(cent);
  free(cl);
}

int main(int argc, char **argv) {
  FILE *f;
  float len;
  long long words, size, a, b;
  char vec_file_name[max_size], output_file_name[max_size];
  float *M;
  char *vocab;
  if (argc < 4) {
    printf("Usage: ./KMeans vec_file output_file classes\n");
    return 0;
  }
  strcpy(vec_file_name, argv[1]);
  strcpy(output_file_name, argv[2]);
  int classes = atoi(argv[3]);

  f = fopen(vec_file_name, "rb");
  if (f == NULL) {
    printf("Input file not found\n");
    return -1;
  }
  fscanf(f, "%lld", &words);
  fscanf(f, "%lld", &size);
  vocab = (char *)malloc((long long)words * max_w * sizeof(char));

  M = (float *)malloc((long long)words * (long long)size * sizeof(float));
  if (M == NULL) {
    printf("Cannot allocate memory: %lld MB    %lld  %lld\n", (long long)words * size * sizeof(float) / 1048576, words, size);
    return -1;
  }
  for (b = 0; b < words; b++) {
    a = 0;
    while (1) {
      vocab[b * max_w + a] = fgetc(f);
      if (feof(f) || (vocab[b * max_w + a] == ' ')) break;
      if ((a < max_w) && (vocab[b * max_w + a] != '\n')) a++;
    }
    vocab[b * max_w + a] = 0;
    for (a = 0; a < size; a++) fread(&M[a + b * size], sizeof(float), 1, f);
    len = 0;
    for (a = 0; a < size; a++) len += M[a + b * size] * M[a + b * size];
    len = sqrt(len);
    for (a = 0; a < size; a++) M[a + b * size] /= len;
  }
  fclose(f);

  KMeans(vocab, M, words, size, classes, output_file_name);

  return 0;
}

用于编译KMeans.c文件的makefile文件如下,用make命令编译。

SCRIPTS_DIR=../scripts
BIN_DIR=../bin

CC = gcc
#Using -Ofast instead of -O3 might result in faster code, but is supported only by newer GCC versions
CFLAGS = -lm -pthread -O3 -march=native -Wall -funroll-loops -Wno-unused-result

all: KMeans

KMeans : KMeans.c
    $(CC) KMeans.c -o ${BIN_DIR}/KMeans $(CFLAGS)
    chmod +x ${SCRIPTS_DIR}/*.sh

clean:
    pushd ${BIN_DIR} && rm -rf KMeans; popd

在shell脚本中确定输入文件,输出文件,以及聚类的类别个数,下面的sh中指定了聚类类别为30。

#!/bin/bash

DATA_DIR=../data
BIN_DIR=../bin
SRC_DIR=../src

VECTOR_DATA=$DATA_DIR/final_item_vec_v1.bin
OUTPUT_DATA=$DATA_DIR/KMeans_v1.txt

set -x
$BIN_DIR/KMeans $VECTOR_DATA $OUTPUT_DATA 30

运行shell文件,则会输出聚类结果文件。至此聚类完成。经过测试,量级在5百万的数据,可在3分钟内聚类完成。

然后如果需要用sql查询做一些分析,就考虑将生成的文件传到hdfs中

hadoop fs -copyFromLocal KMeans_v1.txt hdfs://nameservice/user/machine_learning/lu/tmp/

然后导入sql

LOAD DATA INPATH 'hdfs://nameservice/user/machine_learning/lu/tmp/' OVERWRITE INTO TABLE machine_learning.tp_embedding_cluster;

就可以做想做的分析了

参考资料

  • 《统计学习方法第二版-李航》

本文的理论部分摘抄自李航书的对应章节。

“word2vec中的KMeans源码摘录”参考此文。

实际上,均值聚类是使用最大期望算法(Expectation-Maximization algorithm)求解的高斯混合模型(Gaussian Mixture Model, GMM)在正态分布的协方差为单位矩阵,且隐变量的后验分布为一组时所得到的特例。

这个简单的例子来自。

狄拉克δ函数
sklearn.cluster.KMeans
word2vec中k-means学习笔记
返回上层目录
模型
策略
算法
算法特性
总体特点
收敛性
初始类的选择
类别个数k的选择
代码实践
sklearn中的KMeans实现
word2vec中KMeans实现
word2vec中的KMeans源码摘录
对embdding进行KMeans聚类
返回顶层目录
KMeans-classes-and-diameter