机器学习是一种从数据中总结规律的统计方法。机器学习中有各种用于总结规律并进行预测或者分类的模型(算法),被广泛应用在手写文字识别、物体识别、文本分类、语音识别、股价预测和疾病诊断等领域(图1)。因惊人的图像识别精度而爆红的深度学习也是机器学习的一部分(图2)。深度学习是神经网络模型的一种形式,模拟了人脑中神经细胞的活动。

图1

图2

如今我们迎来了一个非常美好的世界:汇集了包括深度学习在内的各种机器学习模型的库不断问世,并且向所有人免费公开。通过这些库,我们可以轻松地制作出十分厉害的软件。即使不理解模型中的计算原理,我们也可以大胆尝试,如果可以得到预期的结果,就有可能制作出有用的东西。

话虽如此,但肯定也有人希望充分理解机器学习的原理和理论。首先,了解原理本身就是一件令人兴奋的事情。其次,掌握了原理,在面对问题时就可以选择更加合适的模型,在运行结果不理想时也能找到更加合适的对策。更厉害的是,我们甚至能独自开发出符合自身目的的独创模型。

机器学习分类

机器学习中的问题大致可以分为三种,分别是有监督学习的问题、无监督学习的问题和强化学习的问题。有监督学习要求对于输入给出相应的输出;无监督学习要求发现输入数据的规律;强化学习则要求像国际象棋那样,找出使最后结果(准确地说是整体的结果)达到最优的动作。

什么是无监督学习

现实生活中常常会有这样的问题:缺乏足够的先验知识,因此难以人工标注类别或进行人工类别标注的成本太高。很自然地,我们希望计算机能代我们完成这些工作,或至少提供一些帮助。根据类别未知(没有被标记)的训练样本解决模式识别中的各种问题,称之为无监督学习。无监督学习包括聚类、降维和异常检测等,今天我们来一起看一下聚类

二维输入数据

聚类就是在不使用类别信息的前提下,把输入数据中相似的数据分成不同的类别的操作。

图 3 展示了二维数据 X 的分布,但是没有根据信息以颜色区分。即使不用颜色区分,但仔细观察,也依然能看出数据分布有一定规律:上方(x0 = 0.5、x1 = 1 附近)右下方(x0 = 1、x1 = -0.5 附近) 数据各成一块;左下方的数据点散布在广大范围内,这个区域或许也可以看作一个大数据块。

这样的数据分布的块称为(cluster)。从数据分布中找到簇,将属于同一个簇的数据点分配到同一个类别(标签),将属于其他簇的数据点分配到另一个类别的操作就是聚类。

图3

那聚类有什么用呢?属于同一个簇的数据点可以看作“相似的”,属于不同簇的数据点可以看作“不相似的”。

如果能对顾客数据(消费金额及购物时间段等)进行聚类,那么输出的类别将是家庭主妇或者上班族等,顾客将被表示为不同的类别,这样就可以针对不同的类别实施不同的销售策略。

再看昆虫的例子,如果采用的昆虫数据(体重、身长及头部大小等)中有两个簇,也许就能从数据中发现昆虫存在两个亚种。

聚类算法有很多种,本次将介绍最常用的 K-means 算法

K-means 算法的概要

下面依次说明这个算法的步骤,详见图4。

图4

对于 K-means 算法,我们需要事先决定要分割的簇数 K。在本例中,我们设 K = 3,即分为 3 个簇。

K-means 算法使用 2 个变量:表示簇的中心位置的中心向量 μ 和表示各数据点属于哪个簇的类别指示变量 R。

在步骤 0 中,随意赋予簇的中心向量 μ 一个初始值,这样就暂时确定了簇的中心。在步骤 1 中,根据当前的簇的中心向量 μ 确定类别指示变量R。

在步骤 2 中,根据当前的类别指示变量 R 更新 μ。

然后重复步骤 1 和步骤 2,不断地更新 μ 和 R,直到二者的值不再发生变化。

下面就让我们详细看一下每个步骤。

步骤 0:准备变量与初始化

首先创建在图形上显示输入数据 X 与 Mu、R 的函数。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 生成数据 --------------------------------
np.random.seed(1) 
N = 100
K = 3
T3 = np.zeros((N, 3), dtype=np.uint8)
X = np.zeros((N, 2))
X_range0 = [-3, 3]
X_range1 = [-3, 3]
X_col = ['cornflowerblue', 'black', 'white']
Mu = np.array([[-.5, -.5], [.5, 1.0], [1, -.5]])  # 分布的中心
Sig = np.array([[.7, .7], [.8, .3], [.3, .8]])  # 分布的离散值
Pi = np.array([0.4, 0.8, 1])  # 累积概率
for n in range(N):
    wk = np.random.rand()
    for k in range(K):
          if wk < Pi[k]:
            T3[n, k] = 1
            break
    for k in range(2):
        X[n, k] = (np.random.randn() * Sig[T3[n, :] == 1, k]
                   + Mu[T3[n, :] == 1, k])

# 用图形显示数据 ------------------------------
def show_data(x):
    plt.plot(x[:, 0], x[:, 1], linestyle='none',
             marker='o', markersize=6,
             markeredgecolor='black', color='gray', alpha=0.8)
    plt.grid(True)

# 主处理 ------------------------------------
plt.figure(1, figsize=(4, 4))
show_data(X)
plt.xlim(X_range0)
plt.ylim(X_range1)
plt.show()
np.savez('data_ch9.npz', X=X, X_range0=X_range0,
         X_range1=X_range1)
# Mu 和R的初始化 -----------------------------
Mu = np.array([[-2, 1], [-2, 0], [-2, -1]])                         # (A)
R = np.c_[np.ones((N, 1), dtype=int), np.zeros((N, 2), dtype=int)]  # (B)
# 在图形上显示数据的函数 ---------------------------
def show_prm(x, r, mu, col):
    for k in range(K):
        # 绘制数据分布
        plt.plot(x[r[:, k] == 1, 0], x[r[:, k] == 1, 1], 
                 marker='o', 
                 markerfacecolor=X_col[k], markeredgecolor='k',
                 markersize=6, alpha=0.5, linestyle='none')
        # 以“星形标记”绘制数据的平均值
        plt.plot(mu[k, 0], mu[k, 1], marker='*',
                 markerfacecolor=X_col[k], markersize=15,
                 markeredgecolor='k', markeredgewidth=1)
    plt.xlim(X_range0)
    plt.ylim(X_range1)
    plt.grid(True)

#  ------------------------------
plt.figure(figsize=(4, 4))
R = np.c_[np.ones((N, 1)), np.zeros((N, 2))]
show_prm(X, R, Mu, X_col)
plt.title('initial Mu and R')
plt.show()

运行结果如图 5 右半部分所示。

图5

步骤 1:更新 R

下面更新 R,方法是“使各数据点属于离其最近的中心点所在的簇”

图6

通过这个过程,数据点将被分配到各个类别(图 7 右)。

图7

对所有数据执行这个过程。

# 确定 r (Step 1) -----------
def step1_kmeans(x0, x1, mu):
    N = len(x0)
    r = np.zeros((N, K))
    for n in range(N):
        wk = np.zeros(K)
        for k in range(K):
            wk[k] = (x0[n] - mu[k, 0])**2 + (x1[n] - mu[k, 1])**2
        r[n, np.argmin(wk)] = 1
    return r

#  ------------------------------
plt.figure(figsize=(4, 4))
R = step1_kmeans(X[:, 0], X[:, 1], Mu)
show_prm(X, R, Mu, X_col)
plt.title('Step 1')
plt.show()

步骤 2:更新 μ

图8

下面通过代码清单求 μ,并将结果显示出来。

# 确定 Mu (Step 2) ----------
def step2_kmeans(x0, x1, r):
    mu = np.zeros((K, 2))
    for k in range(K):
        mu[k, 0] = np.sum(r[:, k] * x0) / np.sum(r[:, k])
        mu[k, 1] = np.sum(r[:, k] * x1) / np.sum(r[:, k])
    return mu

#  ------------------------------
plt.figure(figsize=(4, 4))
Mu = step2_kmeans(X[:, 0], X[:, 1], R)
show_prm(X, R, Mu, X_col)
plt.title('Step2')
plt.show()

图9

从图中可以看出,μk 朝着每个分布的中心进行了移动。

至此,算法要进行的计算就介绍完毕了,接下来就是重复步骤 1 和步骤 2 的处理,直到变量的值不再变化。在本例中,经过 6 次重复,变量就不再变化了。

plt.figure(1, figsize=(10, 6.5))
Mu = np.array([[-2, 1], [-2, 0], [-2, -1]])
max_it = 6 # 重复次数
for it in range(0, max_it):
    plt.subplot(2, 3, it + 1)
    R = step1_kmeans(X[:, 0], X[:, 1], Mu)
    show_prm(X, R, Mu, X_col)
    plt.title("{0:d}".format(it + 1))
    plt.xticks(range(X_range0[0], X_range0[1]), "")
    plt.yticks(range(X_range1[0], X_range1[1]), "")
    Mu = step2_kmeans(X[:, 0], X[:, 1], R)
plt.show()

我们仔细看一下图 9 中的结果。从图中可以看出,μk 慢慢向 3 个簇的中心移动,每个簇被分配了不同的类别。

一个无监督学习的经典算法——K-means就实现完成了,它具有以下几个优点:
1、原理简单,收敛速度快,这个是业界用它最多的重要原因之一。
2、调参的时候只需要改变k一个参数。
3、算法的可解释度比较强。

如果想系统地学习机器学习,推荐给大家一本入门书籍 《用python动手学机器学习》,除了刚刚介绍的无监督学习,书上还介绍了有监督学习中的回归与分类、神经网络与深度学习的算法与应用、手写数字识别等。本书既有图形、代码,又有详细的数学式推导过程,大大降低了机器学习的学习门槛,即使没有学过Python、数学基础不太好,也可以看懂。

本书是面向机器学习新手的入门书,从学习环境的搭建开始,图文并茂地介绍了学习机器学习所需的Python知识和数学知识,并在此基础上结合数学式、示例程序、插图等,抽丝剥茧般地对有监督学习中的回归与分类、神经网络与深度学习的算法与应用、手写数字识别、无监督学习的算法等进行了介绍。

本书既有图形、代码,又有详细的数学式推导过程,大大降低了机器学习的学习门槛,即使没有学过Python、数学基础不太好,也可以看懂。

加客服微信:qsiq17,开通VIP下载权限!VIP全站资源免费下载!!!