机器学习之Kmeans算法推导(EM算法)以及python实现

Kmeans算法

Kmeans将样本集合划分为 k k k个类,将 n n n个样本分到 k k k个类中,每个样本到其所属类的中心距离最小。每个样本只能属于一个类,所以Kmeans算法是硬聚类。

参考资料李航博士的《统计学习方法》
这篇文章增加了EM算法推导以及Python实现

1. Kmeans算法的推导

模型:
给定 n n n个样本集合 X = x 1 , x 2 , . . . , x n X={x_1,x_2,...,x_n} X=x1,x2,...,xn,每个样本的特征维度是 m m m。将样本分为 k k k个类 C 1 , C 2 , . . . , C k C_1,C_2,...,C_k C1,C2,...,Ck,
每个样本只能属于一个类。
首先采用的是欧式距离平方作为样本之间的距离,定义K means算法的损失函数为:

W ( C ) = ∑ l = 1 k ∑ C ( i ) = l ∣ ∣ x i − x ‾ l ∣ ∣ 2 W(C) = \sum_{l=1}^k \sum_{C(i)=l} ||x_i-\overline x_l||^2 W(C)=l=1kC(i)=lxixl2

其中 x ‾ l \overline x_l xl代表第 l l l个类的中心坐标。

下面运用EM算法的思路推导K means:
1.明确隐变量:
在K means算法中反映观测数据 x i x_i xi来自与哪一个类是未知的,设隐变量 γ i l \gamma_{il} γil表示:

γ i l = { 1 , i f 第 i 个 观 测 值 来 自 于 第 l 个 类 o , e l s e \gamma_{il} = \begin{cases} 1, &if 第i个观测值来自于第l个类\\ o, &else \end{cases} γil={1,o,ifilelse

i = 1 , 2 , . . . , n ; l = 1 , 2 , . . . , k i=1,2,...,n; l=1,2,...,k i=1,2,...,n;l=1,2,...,k

2.E步:
设完全参数的损失函数为:

W ( x ‾ l ) = ∑ l = 1 k ∑ i = 1 n γ i l ∣ ∣ x i − x ‾ l ∣ ∣ 2 W(\overline x_l) = \sum_{l=1}^k \sum_{i=1}^n \gamma_{il} ||x_i-\overline x_l||^2 W(xl)=l=1ki=1nγilxixl2

有隐变量的定义可得:

γ ^ i l = [ ( arg ⁡ min ⁡ 1 ≤ l ≤ k ∣ ∣ x i − x ‾ l ∣ ∣ 2 ) = = l ] \hat \gamma_{il} = [(\arg \min_{1 \le l \le k} ||x_i-\overline x_l||^2) == l] γ^il=[(arg1lkminxixl2)==l]

则:

Q ( x ‾ l ) = ∑ l = 1 k ∑ i = 1 n γ ^ i l ∣ ∣ x i − x ‾ l ∣ ∣ 2 Q(\overline x_l) = \sum_{l=1}^k \sum_{i=1}^n \hat \gamma_{il} ||x_i-\overline x_l||^2 Q(xl)=l=1ki=1nγ^ilxixl2

3.M步:
求解参数 x ‾ l \overline x_l xl
Q ( x ‾ l ) Q(\overline x_l) Q(xl)求导,并令其导数为0

∂ ∂ x ‾ l ( ∑ l = 1 k ∑ i = 1 n γ ^ i l ∣ ∣ x i − x ‾ l ∣ ∣ 2 ) = 0 \frac {\partial} {\partial \overline x_l} (\sum_{l=1}^k \sum_{i=1}^n \hat \gamma_{il} ||x_i-\overline x_l||^2) = 0 xl(l=1ki=1nγ^ilxixl2)=0

得到:

2 ∑ i = 1 n γ ^ i l ( x i − x ‾ l ) = 0 2\sum_{i=1}^n \hat \gamma_{il} (x_i-\overline x_l) = 0 2i=1nγ^il(xixl)=0

故:

x ‾ l = ∑ i = 1 n γ ^ i l x i ∑ i = 1 n γ ^ i l \overline x_l = \frac {\sum_{i=1}^n \hat \gamma_{il} x_i} {\sum_{i=1}^n \hat \gamma_{il}} xl=i=1nγ^ili=1nγ^ilxi

Kmeans算法的总结:
E步:

γ ^ i l = [ ( arg ⁡ min ⁡ 1 ≤ l ≤ k ∣ ∣ x i − x ‾ l ∣ ∣ 2 ) = = l ] \hat \gamma_{il} = [(\arg \min_{1 \le l \le k} ||x_i-\overline x_l||^2) == l] γ^il=[(arg1lkminxixl2)==l]

M步:

x ‾ l = ∑ i = 1 n γ ^ i l x i ∑ i = 1 n γ ^ i l \overline x_l = \frac {\sum_{i=1}^n \hat \gamma_{il} x_i} {\sum_{i=1}^n \hat \gamma_{il}} xl=i=1nγ^ili=1nγ^ilxi

仔细一看这两个公式就会发现E步就是在求第 i i i个样本是否属于第 l l l个类,而M步则在求第 l l l个类的中心点。

2. Kmeans算法的python实现
2.1 Python实现模型
import numpy as np
np.random.seed(0)


class MyKmeans(object):
    def __init__(self, k=3):
        """
        运用EM算法解决Kmeans问题
        :param k: 超参数,表示分类的数量

        涉及到的其它参数
        :param n: 表示样本的个数
        :param m: 表示单个样本的特征维度
        :param gamma: 此模型的隐变量,表示样本属于哪一个类, 维度(n, k)
        :param x_l: 表示类的中心坐标,维度(k, m)
        """
        self.k = k
        self.params = {
            'gamma': None,
            'x_l': None
        }

        self.n = None
        self.m = None

    def _init_params(self, x):
        """
        初始化参数
        :param x: 输入样本
        :return:
        """
        # 由于在第一次E步中会迭代gamma参数,所以gamma可以随意赋值
        gamma = np.zeros((self.n, self.k))
        # 避免初始化的类离观测数据太远,随机选择观测数据的k个点为类中心
        x_l = np.array([x[i] for i in np.random.randint(0, self.n, self.k)])
        self.params = {'gamma': gamma, 'x_l': x_l}

    def _E_step(self, x):
        """
        迭代隐变量
        :param x: 输入样本(n, m)
        :return:
        """
        x_l = self.params['x_l']
        for i in range(self.n):
            x_i = x[i]
            gamma_list = []
            for l in range(self.k):
                x_l_l = x_l[l]
                # 计算第i个样本和第l类中心的距离
                gamma_list.append(np.dot((x_i - x_l_l), (x_i - x_l_l).reshape(self.m, -1)))
            ret_list = np.zeros(self.k)
            index = np.argmin(gamma_list)
            ret_list[index] = 1
            self.params['gamma'][i] = ret_list

    def _M_step(self, x):
        """
        M步迭代计算类中心
        :param x:
        :return:
        """
        gamma = self.params['gamma']
        # 使用循环依次计算元素
        for l in range(self.k):
            gamma_l = gamma[:, l]
            x_l_list = []
            for i in range(self.n):
                if gamma_l[i]:
                    x_l_list.append(x[i])
            # 计算第l类的类中心坐标
            self.params['x_l'][l] = np.average(x_l_list, axis=0)

        # # 也可以使用矩阵直接计算
        # sum = np.dot(x.T, gamma).T
        # self.params['x_l'] = np.array([i / j for i, j in zip(sum, np.sum(gamma, axis=0))])

    def fit(self, x, max_iter=30):
        """
        模型入口
        :param x: 输入的样本(n, m)
        :param max_iter: 最大循环次数
        :return:
        """
        x = np.array(x)
        self.n, self.m = x.shape

        self._init_params(x)

        for i in range(max_iter):
            self._E_step(x)
            self._M_step(x)
2.2 测试模型
def get_samples(n_ex=100, n_classes=3, n_in=2, seed=0):
    # 生成100个样本,为了能够在二维平面上画出图线表示出来,每个样本的特征维度设置为2
    from sklearn.datasets.samples_generator import make_blobs
    from sklearn.model_selection import train_test_split
    y, _ = make_blobs(
        n_samples=n_ex, centers=n_classes, n_features=n_in, random_state=seed)
    return y


def run_my_model():
    from matplotlib import pyplot as plt
    my = MyKmeans()
    y = get_samples()
    my.fit(y)

    max_index = np.argmax(my.params['gamma'], axis=1)
    print(max_index)

    k1_list = []
    k2_list = []
    k3_list = []

    for y_i, index in zip(y, max_index):
        if index == 0:
            k1_list.append(y_i)
        elif index == 1:
            k2_list.append(y_i)
        else:
            k3_list.append(y_i)
    k1_list = np.array(k1_list)
    k2_list = np.array(k2_list)
    k3_list = np.array(k3_list)

    plt.scatter(k1_list[:, 0], k1_list[:, 1], c='red')
    plt.scatter(k2_list[:, 0], k2_list[:, 1], c='blue')
    plt.scatter(k3_list[:, 0], k3_list[:, 1], c='green')
    plt.show()

结果如图所示:
在这里插入图片描述

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值