章节 6

感知机与支持向量机(SVM)

本章讨论使用机器学习方法进行对象分类的基本方面,特别是感知机和支持向量机(SVM,Support Vector Machine)。本教材以及本章中给出的全部代码可在以下链接找到:https://sohoware.ru/SohoBook/。 在学习和应用计算机科学与人工智能时,一个关键环节是理解并处理各种类型的数据。这些数据既可以对应物理对象,也可以对应抽象概念。这些数据或信息元素通常称为“模式”(pattern),这一术语覆盖范围很广。例如,在日常生活和科学研究中,我们经常需要识别和分类各种对象,如人、家具等,也需要识别和分类更细微、更不易触知的方面,如写作风格或言语风格。 当这些元素需要在计算机系统中处理和分析时,首先会出现一个问题:如何用机器能够理解的格式来表示它们。...

37分钟 19,484词数 24资料

关键思想

  • 6.1 对象与概念分类基础
  • 6.2 分类器
  • 6.3 线性判别函数
  • 6.4 感知机
  • 6.5 基于核的支持向量机(Kernel-Based SVM)
  • 本章结论

实践任务

选取一个与“感知机与支持向量机(SVM)”相关的小例子,说明特征、向量在其中如何发挥作用,并用一句话解释结果。

打开实验室

感知机与支持向量机(SVM)

打开实验室

本章讨论使用机器学习方法进行对象分类的基本方面,特别是感知机和支持向量机(SVM,Support Vector Machine)。本教材以及本章中给出的全部代码可在以下链接找到:https://sohoware.ru/SohoBook/。

6.1 对象与概念分类基础

6.1.1 模式

在学习和应用计算机科学与人工智能时,一个关键环节是理解并处理各种类型的数据。这些数据既可以对应物理对象,也可以对应抽象概念。这些数据或信息元素通常称为“模式”(pattern),这一术语覆盖范围很广。例如,在日常生活和科学研究中,我们经常需要识别和分类各种对象,如人、家具等,也需要识别和分类更细微、更不易触知的方面,如写作风格或言语风格。

当这些元素需要在计算机系统中处理和分析时,首先会出现一个问题:如何用机器能够理解的格式来表示它们。由于物理对象或抽象概念无法直接保存在计算机中,因此必须把它们转换成机器能够处理的数据。这个过程称为“表示”(representation),它包括为对象或概念建立简化但仍然足够准确的模型,并将这些模型保存在计算机中进行处理。

表示这些元素的方法有多种。最常见的方法之一是使用向量空间,在这种方法中,每个元素被建模为多维空间中的一个点或一个向量,并由一组数值描述。这些数值可以反映对象或概念的不同特征,例如物理对象的尺寸或重量,或者文本中某些词语的出现频率。另一种方法是使用语言模型或结构模型,其中元素通过形式语言来表示,形式语言描述其属性以及相互关系。

表示方法的选择非常重要,因为它会影响系统有效处理和分类数据的能力。例如,向量模型广泛用于机器学习和人工智能,因为它们能够基于欧氏距离、余弦相似度等度量方法,较准确地分类对象,并分析对象之间的相似性或差异。

需要注意的是,尽管对象或概念本身与其计算机表示在技术上是不同的事物,但在数据处理语境中,它们经常被视为可以互换。也就是说,用来指代物理对象或抽象概念的术语,也可以用于指代它在数据中的表示。例如,当我们说“对象分类”时,通常实际上指的是对这些对象在计算机系统中的表示进行分类。尽管存在这种差异,通常仍采用“模式”这一术语,具体含义则由上下文确定。由 n 个模式组成的集合可表示为 {X1, X2, ..., Xn},其中每个模式都是一个 p 维向量:Xi={Xi1,Xi2,…Xip}。

6.1.2 邻近函数

邻近函数在分类任务中起着关键作用,它可以评估对象之间的相似或差异程度。这种评估主要可以通过两种方式完成:使用距离函数或使用相似度函数。

距离函数用于确定对象在特征空间中相距多远。最常用的方法是欧氏距离,它被计算为两个对象相应特征差值平方和的平方根。对象 Xi 和 Xj 之间的距离记为 d(Xi, Xj)。

对于任意三个对象 Xi、Xj 和 Xk,欧氏距离满足三个基本性质:

非负性:d(Xi, Xj) ≥ 0,距离不能小于零;

对称性:d(Xi, Xj) = d(Xj, Xi),从一个对象到另一个对象的距离等于反方向的距离;

三角不等式:d(Xi, Xj) + d(Xj, Xk) ≥ d(Xi, Xk),两对对象之间距离之和不能小于最远那一对对象之间的距离。这个性质有助于减少计算时间,也有助于建立一些有用的约束,从而简化若干算法的分析。

这些性质使欧氏距离成为许多分类任务中方便使用的工具。不过在某些情况下,例如处理长度不同的向量时,使用其他度量可能更合适。

度量是一种测量并量化对象或数据点之间差异的方法。例如,欧氏距离的平方不是度量;然而,在排序和分类中,它通常与欧氏距离同样有效。

举例说明:

Python
import numpy as np
import matplotlib.pyplot as plt
# 初始数据
X = np.array([2, 2])
X1 = np.array([2, 5])
X2 = np.array([5, 5])
X3 = np.array([3, 2])
X4 = np.array([4, 4])
X5 = np.array([6, 6])

# 计算欧氏距离
d_X_X3 = np.linalg.norm(X-X3)
d_X_X1 = np.linalg.norm(X-X1)
d_X_X2 = np.linalg.norm(X-X2)

# 计算欧氏距离的平方
d_X_X3_squared = np.linalg.norm(X-X3)**2
d_X_X1_squared = np.linalg.norm(X-X1)**2
d_X_X2_squared = np.linalg.norm(X-X2)**2
d_X_X4_squared = np.linalg.norm(X-X4)**2
d_X_X5_squared = np.linalg.norm(X-X5)**2

# 输出结果
print(f"欧氏距离 d(X, X3) = {d_X_X3}< d(X, X1) = {d_X_X1}< d(X, X2) = {d_X_X2}")
print(f"欧氏距离平方 d(X, X3)^2 = {d_X_X3_squared}< d(X, X1)^2 = {d_X_X1_squared}< d(X, X2)^2 = {d_X_X2_squared}")
print(f"欧氏距离平方 d(X, X4)^2 = {d_X_X4_squared}, d(X, X5)^2 = {d_X_X5_squared}")
print(f"距离平方比较与三角不等式检验:\n"
      f"d(X, X5)^2 = {d_X_X5_squared}> d(X, X4)^2 + d(X4, X5)^2 = {d_X_X4_squared+np.linalg.norm(X4-X5)**2}")

points = [X, X1, X2, X3, X4, X5]
labels = ['X', 'X1', 'X2', 'X3', 'X4', 'X5']

plt.figure(figsize=(8, 6))
for point, label in zip(points, labels):
    plt.scatter(point[0], point[1], label=label)

# 为了直观展示距离,用线连接各点
plt.plot([X[0], X3[0]], [X[1], X3[1]], 'r--', lw=1)
plt.plot([X[0], X1[0]], [X[1], X1[1]], 'g--', lw=1)
plt.plot([X[0], X2[0]], [X[1], X2[1]], 'b--', lw=1)
plt.plot([X[0], X4[0]], [X[1], X4[1]], 'y--', lw=1)
plt.plot([X[0], X5[0]], [X[1], X5[1]], 'm--', lw=1)

# 添加图例和坐标轴标签
plt.legend()
plt.xlabel('X ')
plt.ylabel('Y ')
plt.title('点之间的欧氏距离')
plt.grid(True)
plt.show()

输出结果如下:

Text
欧氏距离 d(X, X3) = 1.0 <d(X, X1) = 3.0 <d(X, X2) = 4.242640687119285
欧氏距离平方 d(X, X3)^2 = 1.0 <d(X, X1)^2 = 9.0 <d(X, X2)^2 = 17.999999999999996
欧氏距离平方 d(X, X4)^2 = 8.000000000000002, d(X, X5)^2 = 32.00000000000001
距离平方比较与三角不等式检验:
d(X, X5)^2 = 32.00000000000001 >d(X, X4)^2 + d(X4, X5)^2 = 16.000000000000004
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.1.2 邻近函数

因此,欧氏距离平方不满足三角不等式。

相似度函数反映对象之间的相似程度,其中最常用的方法之一是余弦相似度。它通过计算对象特征向量之间夹角的余弦值来确定对象的方向相似性,而不受向量大小(长度)的影响。

考虑上一示例中的对象 X、X1、X2 和 X3,有 cos(X, X2) > cos(X, X3) > cos(X, X1)。因此,按照相似性排序,X 的前三个邻居依次为 X2、X3 和 X1。注意,使用余弦相似度时,X 与 X2 非常相似,因为这两个对象之间的夹角为 0 度,尽管它们的大小不同。

这种方法评估的是对象特征向量之间夹角的余弦,因此可以在不考虑向量绝对长度的情况下确定其方向相似性。余弦相似度特别适用于文本以及其他高维数据任务,在这些任务中,比较向量方向往往比比较绝对数值更重要。

点积与余弦相似度之间的联系在于:如果向量具有单位范数,则它们的点积等于它们之间夹角的余弦值。这个性质使得点积和余弦相似度在归一化数据条件下可以相互替代。因此,点积可用于计算归一化向量之间的相似程度,并为多维空间中的对象比较提供一种有效机制。

需要理解的是,选择距离函数还是相似度函数取决于具体任务和数据特性。欧氏距离可能非常适合低维空间,而余弦相似度在高维空间中通常更可取,例如文本数据,在这类数据中,评估特征向量的整体方向比评估其绝对距离更重要。

6.1.3 分类

在机器学习中,分类是指确定所研究的每个对象或模式属于哪个类或类别的过程。每个类表示一组具有共同特征或属性的对象,这些特征或属性通过类别标签表达。在二分类任务中,我们通常面对两个组:正类 C+,表示某一特征存在;负类 C−,表示该特征不存在。对象属于哪个类可通过函数 g 来确定,该函数把多维对象(或模式)X 映射为一个实数。

可写为:

Text
C- = {X | g(X) < 0}
C+ = {X | g(X) > 0}

如果对象 X 的函数值 g(X) 小于零,则该对象属于负类 C−;如果大于零,则属于正类 C+。这样,就可以把分类理解为依据对象特征把对象划分为两组的过程。

函数 g 可以根据任务特点和数据性质以不同方式定义,这使得分类问题可以用灵活的方法处理。

示例:

Python
import numpy as np
import matplotlib.pyplot as plt

# 定义两个类别的点
class_negative = np.array([(0, 0), (2, 2)])
class_positive = np.array([(3, -3), (4, -2), (3, -1)])

# 在图上可视化这些点
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')

# 定义并可视化分割线
# 示例中使用线性函数 g(x) = x - 3.5
x_values = np.linspace(0, 8, 100)
y_values = x_values - 3.5
plt.plot(x_values, y_values, 'g--', label='g(x) = x - 3.5')

plt.xlabel('X ')
plt.ylabel('Y ')
plt.title('两个类别的分类示例')
plt.legend()
plt.grid(True)
plt.show()

图中显示了二维空间中两个类别的点:类别 C-(红色)和类别 C+(蓝色)。图中还显示了一条绿色虚线,它表示由函数 g(X) = X1 - 3.5 确定的、用于分开两个类别的假设边界。这说明了函数 g(x) 如何根据点相对于边界的位置来判断点属于哪个类别。

6.2 分类器

6.2.1 最近邻分类器(KNN)

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.1 最近邻分类器(KNN)

最近邻分类器(KNN)是最简单的机器学习方法之一,它根据对象在特征空间中的最近邻来对对象进行分类。KNN 的工作原理是:为了分类一个新对象 X,系统首先在训练数据集中找到距离它最近的对象,然后把 X 分配给其最近邻所属的类别。

为了确定对象 X 的类别,可使用专门的函数 g(X) = g−(X) − g+(X),其中 g−(X) 表示从 X 到负类 C− 中任一对象的最小距离,g+(X) 表示从 X 到正类 C+ 中任一对象的最小距离。换言之,g−(X) 是 X 到负类中最近邻的距离,而 g+(X) 是 X 到正类中最近邻的距离。

距离计算可以使用任意度量,但本例采用欧氏距离平方。这样选择是因为欧氏距离平方在机器学习任务中常用,计算简单且效率较高。

Python
import numpy as np
import matplotlib.pyplot as plt

# 定义类别和点
class_negative = np.array([(0, 0), (2, 2)])
class_positive = np.array([(3, -3), (4, -2), (3, -1)])
X = np.array([1, 2])
X_prime = np.array([4, 0])

# 距离函数:欧氏距离平方
def squared_euclidean_distance(x1, x2):
    return np.sum((x1-x2)**2)

# 计算 g−(X) 和 g+(X)
g_minus_X = np.min([squared_euclidean_distance(X, x) for x in class_negative])
g_plus_X = np.min([squared_euclidean_distance(X, x) for x in class_positive])
g_X = g_minus_X - g_plus_X

g_minus_X_prime = np.min([squared_euclidean_distance(X_prime, x) for x in class_negative])
g_plus_X_prime = np.min([squared_euclidean_distance(X_prime, x) for x in class_positive])
g_X_prime = g_minus_X_prime - g_plus_X_prime

# 可视化
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
plt.scatter(X[0], X[1], color='green', label='X (1, 2)', edgecolors='k', s=100, zorder=5)
plt.scatter(X_prime[0], X_prime[1], color="purple", label="X' (4, 0)", edgecolors="k", s=100, zorder=5)

plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('KNN 分类示例')
plt.grid(True)

# 输出结果
print(f"g−(X) = {g_minus_X}, g+(X) = {g_plus_X}, 因此 g(X) = {g_X}, X 属于 C-")
print(f"g−(X') = {g_minus_X_prime}, g+(X') = {g_plus_X_prime}, 因此 g(X') = {g_X_prime}, X' 属于 C+")

plt.show()

输出结果:

Text
g−(X) = 1, g+(X) = 13, 因此 g(X) = -12, X 属于 C-
g−(X') = 8, g+(X') = 2, 因此 g(X') = 6, X' 属于 C+

在这个示例中,我们计算了点 X = (1, 2) 到类别 C- 与 C+ 的欧氏距离平方,得到 g−(X)=1 和 g+(X)=13。因此 g(X) = -12 < 0,这表示 X 更接近类别 C-,所以被分配到该类别。对于点 X' = (4, 0),类似计算得到 g(X') = 6,因此 X' 被分配到类别 C+。

如果函数 g(X) 的值为负,就意味着 X 的最近邻位于类别 C− 中,因此 X 被分配到该类。类似地,g(X) 为正表示 X 更接近类别 C+ 中的对象,因此 X 被分配到 C+。

这种方法使 KNN 分类器能够灵活适应特征空间中不同的数据分布,并基于对象与训练集中已知样本的接近程度有效地划分对象类别。这使 KNN 适合用于特征与类别之间关系复杂且非线性的应用。

6.2.2 k 近邻分类器(KNNC)

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.2 k 近邻分类器(KNNC)

k 近邻分类器(KNNC)是最近邻分类基本原则的扩展,它在确定测试对象 X 的类别时,不只考虑一个最近邻,而是考虑 X 的 k 个最近邻。KNNC 不完全依赖最近的那一个邻居,而是考察一组 k 个邻居,并把 X 分配给这 k 个邻居中占多数的类别。

函数 g(X) 可计算为正类邻居数量 g+(X) 与负类邻居数量 g−(X) 之间的差,其中 g−(X) = K−,g+(X) = K+ = K − K−。这意味着,如果 k 个最近邻中的多数属于类别 C−,则 g(X) 为负,X 被分配到类别 C−。如果多数邻居属于类别 C+,则 g(X) 为正,X 被分配到 C+。

举例说明:

Python
import numpy as np
import matplotlib.pyplot as plt

# 计算欧氏距离平方的函数
def squared_distance(x1, x2):
    return np.sum((x1-x2) ** 2)

# 查找 k 个最近邻的函数
def find_k_nearest_neighbors(data, labels, x, k):
    distances = np.array([squared_distance(x, point) for point in data])
    indices = np.argsort(distances)[:k]
    return labels[indices]

# 按多数投票确定类别的函数
def classify_point(k_neighbors):
    counts = np.bincount(k_neighbors)
    return np.argmax(counts)

# 可视化 k 个最近邻的函数
def plot_k_nearest_neighbors(data, labels, test_point, k):
    distances = np.array([squared_distance(test_point, point) for point in data])
    indices = np.argsort(distances)[:k]
    nearest_neighbors = data[indices]

    # 可视化邻居
    for neighbor in nearest_neighbors:
        plt.plot([test_point[0], neighbor[0]], [test_point[1], neighbor[1]], 'k--', lw=1)

# 初始数据
class_negative = np.array([(0, 0), (2, 2)])
class_positive = np.array([(3, -3), (4, -2), (3, -1)])
data = np.vstack((class_negative, class_positive))
labels = np.array([0] * len(class_negative) + [1] * len(class_positive))  # 0 - C-, 1 - C+
test_points = np.array([[1, 2], [5, 2]])

# 可视化并分类测试点
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')

for test_point in test_points:
    plt.scatter(test_point[0], test_point[1], color='green', edgecolors='k', s=100, zorder=5)
    k_neighbors_labels = find_k_nearest_neighbors(data, labels, test_point, 3)
    classification = classify_point(k_neighbors_labels)
    print(f"点 {test_point} 被分类为 {'C-' if classification == 0 else 'C+'}")
    plot_k_nearest_neighbors(data, labels, test_point, 3)

plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('3 近邻分类器(KNNC)')
plt.grid(True)
plt.show()

输出结果:

Text
点 [1 2] 被分类为 C-
点 [5 2] 被分类为 C+

图中显示了类别 C-(红色)和 C+(蓝色),以及两个测试点(绿色)——[1, 2] 和 [5, 2]。对于每个测试点,虚线显示了它们与通过 k=3 的 KNN 算法找到的三个最近邻之间的连接。

点 [1, 2] 被分类为类别 C−,这表明它的大多数最近邻位于负类中。

点 [5, 2] 被分类为类别 C+,这说明在它的最近邻中正类邻居占多数。

该图直观展示了 KNN 算法如何根据最近邻的类别来确定测试点的类别,也展示了测试点与这些邻居之间的关系。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.2 k 近邻分类器(KNNC)

6.2.3 最小距离分类器(MDC)

最小距离分类器(Minimum Distance Classifier,MDC)是一种利用距离概念进行分类的方法。它根据测试对象与两个类别均值(质心)的接近程度,判断该对象属于哪一类。下面更详细地说明 MDC 的工作方式。

确定类别均值。 首先,为每个类别计算均值(或质心)。这通过对属于该类别的所有点求算术平均来完成。在本例中,m_- 是类别 C_- 中各点的均值,m_+ 是类别 C_+ 中各点的均值。这些均值表示多维特征空间中每个类别的“中心”位置。

计算到质心的距离。 为了对测试对象 X 进行分类,首先使用距离函数计算 X 到每个类别质心的距离。因此,g_-(X) 表示 X 到类别 C_- 质心的距离:

$$g_-(X)=d(X,m_-)$$

g_+(X) 表示 X 到类别 C_+ 质心的距离:

$$g_+(X)=d(X,m_+)$$

基于距离分类。 测试对象 X 的分类通过比较它到各类别质心的距离来完成:如果到类别 C_- 质心的距离小于到类别 C_+ 质心的距离,则将 X 归为 C_-;反之,如果到类别 C_+ 质心的距离更小,则将 X 归为 C_+

下面给出一个示例:

Python
import numpy as np
import matplotlib.pyplot as plt

# 计算欧氏距离平方的函数
def squared_euclidean_distance(x1, x2):
    return np.sum((x1 - x2) ** 2)

# 定义类别及其质心
class_negative = np.array([[1, 1], [2, 2]])
class_positive = np.array([[6, 2], [7, 2], [7, 3]])
m_negative = np.mean(class_negative, axis=0)
m_positive = np.mean(class_positive, axis=0)

# 测试点
X = np.array([1, 2])
X_prime = np.array([5, 2])

# 计算测试点到质心的距离
g_minus_X = squared_euclidean_distance(X, m_negative)
g_plus_X = squared_euclidean_distance(X, m_positive)
g_X = g_minus_X - g_plus_X

g_minus_X_prime = squared_euclidean_distance(X_prime, m_negative)
g_plus_X_prime = squared_euclidean_distance(X_prime, m_positive)
g_X_prime = g_minus_X_prime - g_plus_X_prime

# 可视化
plt.figure(figsize=(8, 6))
plt.scatter(class_negative[:, 0], class_negative[:, 1], color='red', label='C-')
plt.scatter(class_positive[:, 0], class_positive[:, 1], color='blue', label='C+')
plt.scatter(X[0], X[1], color='green', label='X (1, 2)', edgecolors='k', s=100, zorder=5)
plt.scatter(X_prime[0], X_prime[1], color='purple', label="X' (5, 2)", edgecolors='k', s=100, zorder=5)
plt.scatter(m_negative[0], m_negative[1], color='red', marker='x', s=200, label='m-')
plt.scatter(m_positive[0], m_positive[1], color='blue', marker='x', s=200, label='m+')

plt.legend()
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('最小距离分类器(MDC)')
plt.grid(True)
plt.show()

# 输出结果
print(f"g−(X) = {g_minus_X}, g+(X) = {g_plus_X}, hence g(X) = {g_X}, assigning X to {'C-' if g_X < 0 else 'C+'}")
print(f"g−(X') = {g_minus_X_prime}, g+(X') = {g_plus_X_prime}, hence g(X') = {g_X_prime}, assigning X' to {'C-' if g_X_prime < 0 else 'C+'}")

输出结果:

Text
g−(X) = 0.5, g+(X) = 32.22222222222223, hence g(X) = -31.72222222222223, assigning X to C-
g−(X') = 12.5, g+(X') = 2.8888888888888897, hence g(X') = 9.61111111111111, assigning X' to C+

图中显示了两个类别:C_-(红色)和 C_+(蓝色),以及两个测试点 X=(1,2)(绿色)和 X'=(5,2)(紫色)。每个类别的质心用相应颜色的叉号标出。

对于点 X,计算表明它到类别 C_- 质心的欧氏距离平方 g_-(X) 为 0.5,到类别 C_+ 质心的距离 g_+(X) 为 32.2。因此,差值 g(X)=-31.7,说明点 X 更接近类别 C_- 的质心,于是被归为该类。

对于点 X',计算表明它到类别 C_- 质心的欧氏距离平方 g_-(X') 为 12.5,到类别 C_+ 质心的距离 g_+(X') 为 2.9。因此,差值 g(X')=9.6,说明点 X' 更接近类别 C_+ 的质心,于是被归为 C_+

这个例子说明,最小距离分类器(MDC)如何利用测试点到各类别均值的距离来确定其类别。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.3 最小距离分类器(MDC)

6.2.4 马氏最小距离分类器

这是一种使用马氏距离来判断对象属于两个类别中哪一类的分类方法。当数据近似服从正态分布,并且在确定数据点之间的“距离”时需要考虑变量之间的协方差(两个或多个随机变量之间依赖关系的度量)时,该方法尤其有效。

基本概念。

马氏距离不同于欧氏距离,因为它考虑了变量之间的相关性。在分类语境中,这意味着数据点与类别中心(均值)之间的距离不是简单地按直线距离度量,而是结合数据的整体结构来度量。

类别中心(\mu)是该类别中所有点的平均值。对于每个类别 C_-C_+,分别计算其中心 \mu_-\mu_+

协方差矩阵(\Sigma)描述数据变量之间如何相互关联,以及它们在空间中的分布方式。

分类器如何工作。

首先计算马氏距离。对于测试点 X,分别计算它到类别中心 \mu_-\mu_+ 的距离 g_-(X)g_+(X)

第一步是减去均值:从 X 中减去相应类别的均值(类别 C_- 使用 \mu_-,类别 C_+ 使用 \mu_+)。这得到一个差向量,方向从类别中心指向点 X

第二步是应用协方差矩阵的逆:将该差向量与逆协方差矩阵 \Sigma^{-1} 相乘。逆协方差矩阵考虑变量之间的关系,并根据这些相关性对距离进行“标准化”。如果数据在某一方向上的方差较大,那么该方向上的距离贡献会被视为较小。

第三步是计算马氏距离的平方:上述乘法结果再与差向量作标量乘积。得到的值就是 X 到类别中心的马氏距离平方。对于类别 C_- 可记作 g_-(X),对于类别 C_+ 可记作 g_+(X)

分类规则。 测试点 X 被归入马氏距离最小的类别。如果 g_-(X)<g_+(X),则将 X 归为类别 C_-;反之,则归为类别 C_+

6.2.5 决策树分类器(DTC)

决策树(DecisionTreeClassifier,DTC)是一种机器学习算法,它以树的形式构建预测模型。决策树中的划分过程基于特征选择,目标是尽可能好地把数据分成不同类别。其目标是生成“纯”的节点,即每个节点中所含的模式(或数据点)在类别上尽可能一致。

构建决策树的关键在于选择用于划分的特征,使划分后节点的“纯度”最大。纯度意味着节点中的模式属于同一类别。因此,理想的划分会把不同类别的模式完全分到树的不同分支中。

设想有一个分为两个类别的数据集,并且考虑两个特征(X_1X_2)用于划分。按特征 X_1 划分,可能使树的一条分支只包含类别 C_+ 的模式,而另一条分支主要包含类别 C_- 的模式,但也混入少量 C_+ 的模式(这称为杂质)。按特征 X_2 划分,则可能在两条分支中都产生更多杂质。

在决策树中,可以写成 g(X)=g_+(X)-g_-(X),其中 g_+(X)g_-(X) 是布尔函数,根据树中划分条件决定模式 X 应该被送往类别 C_+ 还是类别 C_-,并返回 1 或 0。这些条件由对象从树根到叶节点的路径决定,而每个叶节点都与某个类别标签相关联。

树中的每个叶节点都与某个类别对应,对象的分类由它从根节点到叶节点所经过的路径决定。如果树中有 m 个叶节点,其中 m_- 个与类别 C_- 对应,那么 g_-(X) 可以表示为 m_- 个合取(逻辑“与”)的析取(逻辑“或”),其中每个合取对应从根节点到某个 C_- 叶节点的一条唯一路径。类似地,g_+(X) 是其余通向 C_+ 叶节点路径的析取。

为了理解这个分类器,考虑如下例子。

数据集中有六个模式,它们的类别标签如下:

• 负类:(1,1)(2,2)(红色点);

• 正类:(2,3)(6,2)(7,2)(7,3)(蓝色点)。

同时,假设有一棵由三个叶节点组成的决策树,其中一个叶节点为负类,两个叶节点为正类。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.5 决策树分类器(DTC)
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.5 决策树分类器(DTC)

因此,相应的 g_-(X)g_+(X) 可以写成:

$$g_-(X)=(X_1\le 4)\land(X_2\le 2.5)$$

以及

$$g_+(X)=(X_1>4)\lor\bigl((X_1\le 4)\land(X_2>2.5)\bigr)$$

如果 X=(1,2)(绿色点),则 g_-(X)=1g_+(X)=0(假设布尔函数在为假时返回 0、为真时返回 1)。因此,g(X)=g_+(X)-g_-(X)=0-1=-1<0,所以 X 被归为 C_-

如果 X=(5,2),则 g_-(X)=0g_+(X)=1。因此 g(X)=1,所以 X 被归为 C_+

6.2.6 基于线性判别函数的分类

基于线性判别函数的分类是一种用于机器学习和统计学中的方法,用来判断对象属于某个给定类别。线性判别函数是该方法的基础,它是一个对对象特征进行线性组合的方程,用于作出分类决策。

函数形式为:

$$g(X)=W X+w_0$$

其中:

X 是待分类对象的特征向量。特征是对象的特性或属性,可以被测量或评估。

W 是权重向量,表示每个特征对最终分类的重要性或影响。W 中的每个元素对应 X 中一个特征的权重。

w_0 是标量值,称为偏置或阈值,用来调节函数被激活并作出分类决策的水平。

用该函数进行分类的过程,是把对象的特征向量代入方程,从而得到一个数值结果。然后按如下方式解释该结果:若函数值为正,则对象属于一个类别;若为负,则属于另一个类别。这样,就在特征空间中形成一条线性决策边界,把对象按类别分为两组。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.6 基于线性判别函数的分类

需要注意的是,选择权重向量 W 和偏置 w_0 是训练分类器过程中的重要阶段。这些参数通常基于训练数据确定,即基于一组类别已知的对象。训练目标是调整 Ww_0,使线性判别函数尽可能准确地区分不同类别的对象。

由于该方法简单且在许多分类任务中有效,尤其在特征与类别之间的关系近似线性时,它被广泛使用。然而,当数据难以用线性边界分开时,其效果可能下降,此时需要使用更复杂的非线性方法。

下面给出示例:

Python
import numpy as np
import matplotlib.pyplot as plt

# 生成数据集
np.random.seed(0)
class1 = np.random.randn(100, 2) + np.array([2, 2])
class2 = np.random.randn(100, 2) + np.array([-2, -2])

# 可视化数据集
plt.scatter(class1[:, 0], class1[:, 1], color='red', label='类别 1')
plt.scatter(class2[:, 0], class2[:, 1], color='blue', label='类别 2')

# 定义权重向量和偏置
W = np.array([1, 1])
w0 = 0.5

# 绘制决策边界的函数
def draw_decision_boundary(W, w0):
    # 选择两个点来确定直线
    x_values = np.array(plt.xlim())
    y_values = -(W[0] / W[1]) * x_values - (w0 / W[1])
    plt.plot(x_values, y_values, 'k--', label='决策边界')

# 分类函数
def classify(X, W, w0):
    return np.dot(X, W) + w0

# 测试点(绿色点)
test_point = np.array([0.5, -0.5])
result = classify(test_point, W, w0)

# 可视化测试点和决策边界
draw_decision_boundary(W, w0)

plt.scatter(test_point[0], test_point[1], color='green', label='测试点')
plt.text(
    test_point[0],
    test_point[1],
    f'  分类为 {"类别 1" if result > 0 else "类别 2"}',
    color='green'
)

plt.legend()
plt.show()
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.6 基于线性判别函数的分类

6.2.7 非线性判别函数(NDF)

非线性判别函数是线性判别函数的扩展,适用于数据无法用线性边界有效分开的情况。与使用直线(或多维空间中的超平面)作为类别分界的线性判别函数不同,非线性函数可以形成更复杂的曲线边界。

其基本思想是使用特征的非线性组合来分类对象。这可以包括二次项、三次项或其他幂次项,也可以包括三角函数或指数函数,从而在特征空间中构造更复杂的类别分隔形状。

例如,包含二次项的函数就是非线性的。这意味着在二维特征空间 (X_1,X_2) 中,类别之间的分界线将是曲线。

当特征与类别之间的关系复杂、无法用线性模型充分描述时,使用 NDF 是有意义的。它可以提高复杂分类任务中的准确率,尤其适用于数据结构复杂或类别在特征空间中相互重叠的情况。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.2.7 非线性判别函数(NDF)

下面给出示例:

Python
import numpy as np
import matplotlib.pyplot as plt

# 生成数据集
np.random.seed(0)
N = 100  # 每个类别中的点数
r_inner = 2
r_outer = 4

# 内圆
inner_circle = r_inner * np.random.rand(N, 2)
theta = 2 * np.pi * np.random.rand(N)
inner_circle[:, 0] = r_inner * np.cos(theta)
inner_circle[:, 1] = r_inner * np.sin(theta)

# 外圆
outer_circle = r_outer * np.random.rand(N, 2)
theta = 2 * np.pi * np.random.rand(N)
outer_circle[:, 0] = r_outer * np.cos(theta)
outer_circle[:, 1] = r_outer * np.sin(theta)

# 可视化数据集
plt.figure(figsize=(8, 8))
plt.scatter(inner_circle[:, 0], inner_circle[:, 1], color='red', label='Class 1')
plt.scatter(outer_circle[:, 0], outer_circle[:, 1], color='blue', label='Class 2')

# 测试点(绿色)
test_point = np.array([3, 1])
plt.scatter(test_point[0], test_point[1], color='green', label='Test Point')

# 非线性决策边界(平均半径)
average_radius = (r_inner + r_outer) / 2
circle = plt.Circle((0, 0), average_radius, color='black', fill=False, linestyle='--', label='Decision Boundary')
plt.gca().add_artist(circle)

# 使用非线性函数进行分类
def classify_nonlinear(point):
    distance = np.sqrt(point[0] ** 2 + point[1] ** 2)
    if distance < average_radius:  # 基于距离的简单条件
        return 'Class 1'
    else:
        return 'Class 2'

# 对测试点分类
test_point_class = classify_nonlinear(test_point)
print(f"测试点被分类为: {test_point_class}")

plt.legend()
plt.show()

输出:

Text
测试点被分类为: Class 2

这段代码展示了如何用非线性边界对线性不可分数据进行分类。生成的两类点使一类位于圆内,另一类位于圆外,从而形成同心圆。分类边界由虚线圆表示,其半径取内圆和外圆半径的平均值。绿色测试点根据它到中心的距离进行分类:如果位于边界内部,则属于内侧类别;如果在边界外部,则属于外侧类别。

6.2.8 朴素贝叶斯分类器(NBC)

朴素贝叶斯分类器(Naive Bayes Classifier,NBC)是一种简单的概率分类器。它基于贝叶斯定理,并假设同一类别内部的各个特征彼此独立。

NBC 的工作方式是基于概率判断对象 X 属于类别 C_- 还是 C_+

分类器比较后验概率 P(C_-\mid X)P(C_+\mid X),即在观察到 X 之后,对象 X 分别属于类别 C_-C_+ 的概率。如果 P(C_-\mid X)>P(C_+\mid X),则对象被归为类别 C_-;否则归为类别 C_+

NBC 的判别函数 g(X) 定义为两个类别后验概率之差:

$$g(X)=g_-(X)-g_+(X)$$

其中

$$g_-(X)=P(C_-\mid X),\qquad g_+(X)=P(C_+\mid X).$$

后验概率是以随机观察到的数据为条件的条件概率。

根据贝叶斯定理,后验概率 P(C_-\mid X) 可以通过以下量来表示:在类别 C_- 条件下观察到 X 的概率 P(X\mid C_-)、类别 C_- 的先验概率 P(C_-),以及观察到 X 的全概率 P(X)

$$P(C_-\mid X)=\frac{P(X\mid C_-)P(C_-)}{P(X)}.$$

对于 P(C_+\mid X) 也有类似表达式。

在 NBC 中,由于假设特征条件独立,因此 P(X\mid C_-) 可以分解为每个特征 x_i 在类别 C_- 条件下概率的乘积:

$$P(X\mid C_-)=\prod_i P(x_i\mid C_-),$$

相应地:

$$P(X\mid C_+)=\prod_i P(x_i\mid C_+).$$

6.3 线性判别函数

6.3.1 判决边界、C+ 与 C−

如本章前面所见,线性判别函数具有形式 g(X)=W^T X+b,其中 W 是维度为 p 的权向量列,b 是标量。g(X) 将向量空间划分为三个部分。它们如下。

判决边界 DB(Decision Boundary)

在线性判别函数的情形下,g(X)=W^T X+b=0 描述的是一个超平面(在二维情形中是一条直线),也就是判决边界。与 g(X) 对应的判决边界 DB_g 也可以表示为:

DB_g={X∣g(X)=0}

负半空间 NHS(Negative Half Space)

它可以看作属于类别 C− 的所有样本集合。相应地,与 g(X) 对应的负半空间 NHS_g 是如下集合:

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.3.1 判决边界、C+ 与 C−

NHS_g={X∣g(X)<0}=C−

正半空间 PHS(Positive Half Space)

这是属于类别 C+ 的所有样本集合。相应地,与 g(X) 对应的正半空间 PHS_g 定义为:

PHS_g={X∣g(X)>0}=C+

需要注意的是,这些部分中的每一个都可能是无限集合。不过,训练数据集以及实际遇到的测试样本集合都是有限的。

6.3.2 线性可分性

线性可分性是机器学习中的一个概念,指分类算法能够借助线性函数把数据集划分为不同类别的能力。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.3.2 线性可分性

假设我们有一个带标签样本集 X,它由“对象—类别标签”对 (X_i, C_i) 组成,其中 i 是样本在集合中的索引。

如果能够找到线性函数的一组参数——权向量 W 和标量偏置 b,使得某一类(例如 C+)中所有样本的线性函数值 W^T X_i+b 都大于零,而另一类(例如 C−)中所有样本的线性函数值都小于零,那么就称数据集 X 是线性可分的。这意味着存在一个由方程 W^T X+b=0 定义的超平面(在二维空间中是一条直线),它能够在特征空间中完美地分离两个类别的样本。

当数据线性可分时,使用线性分类器就具有实际意义,因为在这种情况下,分类器可以在训练集上无误差地把样本划分为不同类别。二维样本能被一条直线分开的情形,就是线性可分数据的一个例子。

如果数据线性可分,那么并不只存在一个线性判别函数(LDF)能够完美地区分这些类别,而是存在无限多个这样的函数。原因是,任何一条从不同类别的两个最近点之间穿过、但不与它们相交的直线(在更高维空间中则为超平面)都可以作为判决边界。图示通常会展示同一个线性可分数据集可能具有的多条不同分离直线(超平面)。

为了直观说明,我们考虑两个例子:一个是线性可分数据,另一个是线性不可分数据。为此,我们在平面上生成两组点:一组可以用直线划分为两个类别,另一组则不能用一条直线完成这样的划分。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.3.2 线性可分性

线性可分数据

假设二维平面上有两个类别的点:一个类别位于左上角和右下角,另一个类别位于右上角和左下角。这些点可以用一条直线分开。

线性不可分数据

再设想另一组点,其中一个类别的点包围另一个类别的点,例如呈圆形结构。在这种情况下,不可能画出一条直线来分开两个类别的点。

下面用 Python 可视化这两个例子。

Python
import matplotlib.pyplot as plt
import numpy as np

# Генерация линейно разделимых данных
np.random.seed(0)
x1 = np.random.randn(100, 2) + np.array([-3, 3])  # Класс 1
x2 = np.random.randn(100, 2) + np.array([3, -3])  # Класс 2

# Генерация линейно неразделимых данных
theta = np.linspace(0, 2*np.pi, 100)
r = 2 + np.cos(5*theta)  # Радиальная функция для "внутреннего" класса
x_inner = np.c_[r*np.cos(theta), r*np.sin(theta)]
x_outer = np.random.randn(100, 2) * 3  # "Внешний" класс

# Визуализация
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# Линейно разделимые данные
axs[0].scatter(x1[:, 0], x1[:, 1], label='Класс 1')
axs[0].scatter(x2[:, 0], x2[:, 1], label='Класс 2')
axs[0].set_title('Линейно разделимые данные')
axs[0].legend()

# Линейно неразделимые данные
axs[1].scatter(x_inner[:, 0], x_inner[:, 1], label='Класс 1')
axs[1].scatter(x_outer[:, 0], x_outer[:, 1], label='Класс 2')
axs[1].set_title('Линейно неразделимые данные')
axs[1].legend()

plt.show()

可视化结果展示了两个数据集。

线性可分数据(左图):这里,各类别点的分布方式使它们能够被一条直线清楚地分开。一个类别位于左上角和右下角,另一个类别位于右上角和左下角。

线性不可分数据(右图):在这个例子中,一个类别的点(内部点)被另一个类别的点(外部点)包围。无法画出一条直线来分开两个类别的点,因此数据是线性不可分的。

6.3.3 基于线性判别函数的线性分类

线性分类器由相应的线性判别函数 g(X)=W^T X+b 描述。W、X 和 b 对理解分类器的工作原理起着重要作用。下面更详细地讨论它们。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.3.3 基于线性判别函数的线性分类

在线性分类器的语境中,判决边界或超平面表示特征空间中的一组点。在这些点上,分类器无法唯一确定这些数据点应归入哪个类别——正类 C+ 还是负类 C−。从数学上讲,这一边界由方程 g(X)=W^T X+b=0 定义,其中 W 是权向量,X 是特征向量,b 是偏置。满足这一方程的点位于一个超平面上,该超平面把特征空间划分为两个部分,每个部分都与一个类别相关联。

当我们考虑位于该超平面上的两个不同点 X_1 和 X_2 时,可以看到二者都满足条件 W^T X_1+b=W^T X_2+b=0。将一个方程减去另一个方程,得到 W^T(X_1−X_2)=0。这表明向量 W 与连接点 X_1 和 X_2 的向量垂直,因此也与判决边界超平面本身垂直。这个性质很重要,因为它确定了从一个类别变化到另一个类别时的方向。

权向量 W 与判决边界正交(垂直)意味着函数 g(X) 的值变化最大的方向,也就是分类器对类别归属变化最“确信”的方向,与向量 W 的方向一致。因此,向量 W 不仅决定判决边界的方向,还指出相对于该边界,正类位于哪个方向。这就把向量 W 的方向与特征空间中的类别分布联系起来。

正半空间定义为特征空间中的一个区域,其中任一样本 X 都满足条件 g(X)=W^T X+b>0。该条件表示,在当前线性分类器中,样本属于正类。下面进一步说明。

偏置的作用:线性判别函数中的参数 b 决定超平面相对于坐标原点的位置。如果考察原点处的 g(X) 值并假设 b>0,那么即使 X=0(坐标原点),也有 g(0)=b>0,这会把原点放入正半空间。这意味着当 b 为正时,即使没有任何特征(零向量 X),样本也会被分类为属于正类。

如果 b=0,则超平面经过坐标原点,并且位于超平面上的任意点 X 都满足 g(X)=0。在这种情况下,坐标原点正好位于判决边界上。

权重的作用:权向量 W 决定判决边界超平面在特征空间中的朝向。如果考虑 b=0 时的线性判别函数 g(X),则 g(X)=W^T X。对于位于正半空间中的样本 X,有 g(X)>0,这说明 W 的方向指向与正类相对应的特征增大方向。

W^T X>0 可以通过向量 W 与 X 之间夹角的余弦来解释。由于当两个向量之间的夹角小于 90 度时,夹角余弦为正,这意味着向量 W 指向正半空间,从而支持把该半空间中的样本分类为正类。

负半空间定义为特征空间中的一个区域,其中每个点 X 都被分类为属于负类,也就是说这些点满足条件 g(X)<0。这意味着这些点在线性判别函数 g(X)=W^T X+b 下的取值小于零。

如果偏置参数 b 等于零(b=0),并考虑来自负类的点 X,则条件 W^T X<0 表示特征向量 X 相对于权向量 W 的方向使二者之间的夹角 θ 大于 90 度且小于 270 度。这进一步说明权向量 W 指向正半空间,因为位于负半空间的向量与 W 所成的角超过直角范围。

当偏置参数 b 小于零(b<0)时,负半空间中的任意点 X 都满足条件 g(X)=W^T X+b<0。在这种情况下,即使在坐标原点(X=0),也有 g(0)=b<0,因此原点被放入负半空间。

因此,线性判别函数 g(X)=W^T X+b 中的参数 W 和 b 具有以下作用。

b 的值决定坐标原点的位置。当 b>0 时,坐标原点位于正半空间 PHS_g;当 b<0 时,位于负半空间 NHS_g;当 b=0 时,位于判决边界上。

权向量 W 与判决边界正交,并指向正半空间。这意味着无论 b 的值如何,W 的方向保持不变,而不同 b 值所对应的所有判决边界彼此平行。

6.4 感知机

感知机是用于二分类的机器学习算法之一,也就是说,它用于判断对象属于两个可能类别中的哪一个。感知机以线性判别函数为基础:模型接收输入数据,对不同输入特征赋予权重,并据此预测类别归属。

在人工智能发展的早期,感知机是重要的研究对象之一,因为它为许多更复杂的分类方法奠定了基础,其中也包括支持向量机(SVM)。

线性判别函数是一类用于把输入数据(例如图像或文本)划分为两个类别的函数。在二维空间中,它对应一条直线;在三维空间中,对应一个平面;在更高维空间中,则对应一个超平面。

$$g(X)=W^T X+b$$

这是线性判别函数的数学表示。其中,g(X) 是输入向量 X 对应的函数值,W 是权重向量,b 是偏置(或阈值),W^T 表示权重向量的转置。根据 g(X) 的符号,就可以判断输入向量 X 属于哪一类。

感知机训练的目标,是找到合适的权重 W 和偏置 b,使两个类别能够尽可能准确地被分开。

扩展向量(X_aW_a)是一种简化数学运算的技巧:在原始特征向量 X 和权重向量 W 中加入一个额外维度,用来吸收偏置 b。这样可以把 b 合并进权重向量,从而简化计算。

使用感知机进行分类时,计算 g(X)。若 g(X)<0,则把 X 分到类别 C-;若 g(X)>0,则把 X 分到类别 C+。换句话说,函数值为负时对象属于一个类别,函数值为正时对象属于另一个类别。

线性可分性是感知机成功应用的重要前提:必须存在一条直线、一个平面或一个超平面,能够无误地把所有输入样本分成两个类别。

类别标签 y 是数据中每个样本的真实类别标记。在感知机语境下,y 通常取 -1+1,分别对应两个类别 C-C+

下面用一个表格说明感知机算法的分类过程。表中给出 6 个样本(或模式),每个样本都有类别标签(“+”或“-”)以及两个属性 x_1x_2。最后一列给出扩展权重向量 W_a^T 与扩展属性向量 x_a 结合后的结果。

转置权重向量 W_a^T 表示把权重向量转置,使行与列互换。在这里,权重向量写为 W_a^T=(-14,1,5)^T,其中 T 表示转置。在感知机中,权重向量中的系数决定每个属性在分类过程中的重要性。

扩展属性向量 x_a 是每个样本的扩展属性向量,它把偏置(或阈值)作为第一个元素,把 x_1x_2 分别作为第二个和第三个元素。

标量积 W_a^T x_a 通过对应元素相乘再求和得到,可写为:

$$W_a^T x_a=(-14\cdot \text{偏置})+(1\cdot x_1)+(5\cdot x_2)$$

其中“偏置”通常取 1,用于表示神经元激活阈值。

$$W_a^T yx_a=\left((-14\cdot \text{偏置})+(1\cdot x_1)+(5\cdot x_2)\right)\cdot (y-\text{类别标签,取 }-1\text{ 或 }+1)$$

标量积结果用于确定每个样本应被分配到哪个类别。若结果为正,样本被分到 C+;若结果为负,样本被分到 C-

$$y=-1,\quad X\in C-$$

$$y=+1,\quad X\in C+$$

表中的“1”列表示加入到每个输入向量中的常数特征。这样做是为了把偏置(bias)纳入模型,使算法不仅能够构造经过原点的线性分类边界,也能够把分离边界从零点处平移。换言之,除了 x_1x_2 两个特征外,还加入一个取值为 1 的常数特征,使模型能够计算并使用偏置。

| 样本编号 | 类别标签 | 1 | x_1 | x_2 | W_a^T yx_a | | --- | --- | ---: | ---: | ---: | ---: | | 1 | − | −1 | −1 | −1 | 8 | | 2 | − | −1 | −1 | −2 | 2 | | 3 | + | 1 | 1 | 2 | 3 | | 4 | + | 1 | 1 | 6 | 2 | | 5 | + | 1 | 1 | 7 | 3 | | 6 | + | 1 | 1 | 7 | 8 |

函数 g(yX) 可理解为:权重向量 W_a 与属性向量 X_a 相乘,再乘以类别标签 y

如果样本属于 C-,则 g(X)=W_a^T x_a<0,对应标签 y=-1。如果样本属于 C+,则 g(X)=W_a^T x_a>0,对应标签 y=+1

因此,无论 X 属于 C- 还是 C+,函数 g(yX) 都会为正,这简化了学习算法。表中可以看到,权重向量 (-14,1,5)^T 能够正确分类所有 yX_a 值。

本章后续为简洁起见采用如下记号:

1. 用 W 表示 W_a,并假定 bW 的第一个元素。 2. 用 X 表示 yX_a,并假定 X 已通过在第一分量加入 1 得到扩展,同时 X_a 已乘以 y;得到的向量记作 X。 3. W 从训练数据中学习得到。 4. 使用感知机学习算法来学习 W

6.4.1 感知机学习算法

感知机学习算法可以概括为以下步骤。

1. 初始化。算法首先把迭代计数器 i 初始化为 0,把权重向量 W_i 初始化为零向量。零向量表示所有分量均为 0。权重向量用于确定特征空间中的决策边界。 2. 遍历样本。算法依次检查训练集中的每个样本 X_kk=1,2,\ldots,n,其中 n 是样本数)。如果当前权重向量 W_i 对样本 X_k 分类错误,即权重向量与样本特征向量的乘积小于或等于 0,则通过把当前样本向量加到权重向量上来更新权重。这一更新相当于移动决策边界,使其更接近正确分类当前样本的位置。每次发生更新,迭代计数器 i 加 1。 3. 重复直到收敛。重复第 2 步,直到完整遍历一次训练集时计数器 i 不再变化。这意味着算法已收敛,当前权重向量能够正确分类所有样本;换句话说,算法找到了能够在特征空间中分开两个类别的决策边界。

Python
import numpy as np
import matplotlib.pyplot as plt


def perceptron_learning_algorithm(X, Y, max_epochs=1000):
    n_samples, n_features = X.shape
    W = np.zeros(n_features)      # 用零向量初始化权重
    i = 0                         # 初始化迭代计数器
    epoch = 0                     # 记录训练轮数,避免非线性可分数据导致无限循环

    while epoch < max_epochs:
        i_old = i                 # 保存旧计数,用于检查收敛
        for k in range(n_samples):
            if np.dot(W, X[k]) * Y[k] <= 0:  # 检查是否误分类
                W = W + X[k] * Y[k]          # 更新权重
                i += 1
        if i_old == i:                       # 若本轮无更新,则收敛
            break
        epoch += 1
    return W


# 生成示例数据集
np.random.seed(42)
n_samples = 20
X_positive = np.random.randn(n_samples, 2) + [2, 3]
X_negative = np.random.randn(n_samples, 2) + [1, -2]
X = np.vstack((X_positive, X_negative))
Y = np.hstack((np.ones(n_samples), -np.ones(n_samples)))

# 添加偏置列
X_aug = np.hstack((np.ones((2 * n_samples, 1)), X))

weights = perceptron_learning_algorithm(X_aug, Y)
print("训练得到的权重:", weights)

# 可视化结果
plt.scatter(X_positive[:, 0], X_positive[:, 1], marker='o', label='类别 +1')
plt.scatter(X_negative[:, 0], X_negative[:, 1], marker='x', label='类别 -1')

x_values = np.linspace(np.min(X_aug[:, 1]), np.max(X_aug[:, 1]), 100)
y_values = -(weights[1] / weights[2]) * x_values - (weights[0] / weights[2])
plt.plot(x_values, y_values, label='分离直线')
plt.xlabel('X1')
plt.ylabel('X2')
plt.legend()
plt.grid(True)
plt.show()

在该示例中,训练得到的权重为近似值 [0., 0.75824757, 4.69036742]。这说明分离直线主要由第二、第三个权重决定,而偏置项在该次随机样本中为 0。

6.4.1.1 布尔函数的学习

为了直观展示算法,考虑布尔函数“或”(OR)。其真值表如下。

| x_1 | x_2 | x_1\vee x_2 | | ---: | ---: | ---: | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 |

把输出 0 视为负类,把输出 1 视为正类。在加入偏置项并根据类别标签 y=-1y=+1 进行乘法之后,得到如下形式的 yX_a 数据。

| 样本编号 | 类别标签 | 1 | x_1 | x_2 | | --- | ---: | ---: | ---: | ---: | | 1 | −1 | −1 | 0 | 0 | | 2 | 1 | 1 | 0 | 1 | | 3 | 1 | 1 | 1 | 0 | | 4 | 1 | 1 | 1 | 1 |

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.4.1.1 布尔函数的学习

W_0=(0,0,0)^T 开始。权重的连续更新如下。

1. W_0 误分类第一个向量 (-1,0,0)^T,因为二者标量积为 0。因此 W_1=W_0+(-1,0,0)^T=(-1,0,0)^T。 2. W_1 误分类第二个模式 (1,0,1)^T,因为标量积为 -1<0。因此 W_2=W_1+(1,0,1)^T=(0,0,1)^T。 3. W_2 误分类第三个模式 (1,1,0)^T,标量积为 0。因此 W_3=W_2+(1,1,0)^T=(1,1,1)^T。 4. W_3 正确分类第四个模式 (1,1,1)^T,标量积大于 0。再次从第一个模式开始时,W_3 误分类 (-1,0,0)^T,所以 W_4=W_3+(-1,0,0)^T=(0,1,1)^T。 5. W_4 仍不能正确分类第一个模式,尽管它能分类第 2、3、4 个模式。因此 W_5=W_4+(-1,0,0)^T=(-1,1,1)^T。 6. W_5 误分类第二个模式,因此 W_6=W_5+(1,0,1)^T=(0,1,2)^T。 7. W_6 在正确分类第 3、4 个模式后,又误分类第一个模式,因此 W_7=W_6+(-1,0,0)^T=(-1,1,2)^T。 8. W_7 误分类第三个模式,因此 W_8=W_7+(1,1,0)^T=(0,2,2)^T。 9. W_8 误分类第一个模式,因此 W_9=W_8+(-1,0,0)^T=(-1,2,2)^T。此时 W_9 能正确分类全部四个模式。判别函数可写为

$$g(X)=(-1,2,2)(1,x_1,x_2)^T,$$

因此决策规则为

$$2x_1+2x_2=1.$$

6.4.1.2 感知机中权重向量的非唯一性

感知机用于分类的权重向量 W 并不唯一。这意味着可以存在多个不同的权重向量,它们都能正确分类同一组数据。最终得到哪一个权重向量,取决于算法处理数据点的顺序。

考虑四个模式,它们属于两个类别:

负类:(1,1)^T(2,2)^T

正类:(6,1)^T(7,1)^T

乘以类别标签 y 之后的扩展模式为:

负类:X_1=(-1,-1,-1)^TX_2=(-1,-2,-2)^T

正类:X_3=(1,6,1)^TX_4=(1,7,1)^T

如果按照 X_1,X_2,X_3,X_4 的顺序使用模式,并从 W_0=(0,0,0)^T 开始,算法可在 W_4=(-2,2,-3)^T 处停止。对应决策边界为

$$g(x)=2x_1-3x_2=2,$$

在图中可表示为虚线。

如果反向使用模式 X_4,X_3,X_2,X_1,仍从 W_0=(0,0,0)^T 开始,则得到 W_4=(-2,3,-3)^T,它同样能正确分类四个模式。此时决策边界为

$$g(x)=3x_1-3x_2=2,$$

可表示为另一条直线。该例说明,感知机学习到的分离超平面不是唯一的。

6.4.1.3 学习算法的工作原理

代数方法。 如果权重向量 W_i 误分类向量 X_k,则标量积 W_i^T X_k 不大于 0。更新后的向量为

$$W_{i+1}=W_i+X_k.$$

于是

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.4.1.3 学习算法的工作原理

$$W_{i+1}^T X_k=(W_i+X_k)^T X_k=W_i^T X_k+X_k^T X_k.$$

由于 X_k^T X_k=\|X_k\|^2 总是正数,因此更新后的标量积更有可能变为正值。这说明 W_{i+1}W_i 更有利于正确分类 X_k

换言之,代数方法说明:如果某个数据点被错误分类,就可以调整权重,使下一次检查该点时更倾向于得到正确分类。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.4.1.3 学习算法的工作原理

1. 分类检查:对每个数据点,算法计算权重向量 W 与特征向量 X 的标量积。如果结果与期望类别标签不一致,则该点被视为误分类。 2. 更新权重:发生误分类时,根据误分类点的特征向量更新权重;若该点应为正类,则加入该向量;若应为负类,则等价地加入已乘以标签的向量。该更新使下一次检查更倾向于正确分类该点。 3. 迭代:重复上述过程,直到达到停止条件,例如达到指定迭代次数,或不再出现误分类点。

Python
import numpy as np


def update_weights(W, X, y):
    """为误分类向量 X 更新权重。"""
    return W + y * X


W = np.array([0, 0, 0])
X_samples = np.array([
    [-1, 0, 0],   # 示例 1
    [1, 0, 1],    # 示例 2
    [1, 1, 0]     # 示例 3
])
y_labels = np.array([-1, 1, 1])

for X, y in zip(X_samples, y_labels):
    prediction = np.dot(W, X)
    if (prediction <= 0 and y == 1) or (prediction > 0 and y == -1):
        W = update_weights(W, X, y)
        print(f"更新后的权重向量:{W}")

几何方法。 几何方法直观说明权重更新如何改变决策边界。假设当前权重向量 W_i 误分类点 P_3。与 W_i 对应的决策边界 DB_i 未能正确分离类别。将 P_3 加到 W_i 上,相当于把权重向量移动到更适合分类 P_3 的方向。几何上,这可看作由 W_iP_3 构成平行四边形,而 W_{i+1} 是其对角线。新的权重向量 W_{i+1} 对应新的决策边界 DB_{i+1},并与 W_{i+1} 正交,从而更好地分离类别。

这两种方法共同说明:感知机算法在每次误分类时逐步校正权重向量,持续改善类别分离,直到所有样本都被正确分类,或达到预设迭代次数。

6.4.1.4 感知机算法的收敛性

感知机算法的收敛性是指:如果数据确实存在某种线性分离方式,则感知机能够在有限步内找到用于分离这些数据的超平面。

感知机收敛定理由弗兰克·罗森布拉特于 1957 年证明。该定理指出,如果数据线性可分,则感知机算法在有限次权重更新后会收敛到一个能够分离训练数据的超平面。这意味着,如果正确分类所有样本的权重确实存在,算法就能在有限步内找到一组可行权重。

算法过程如下。

1. 初始化。训练开始前,给定学习率和迭代次数,权重与偏置初始化为 0。它们会在训练过程中不断调整,以减少预测错误。 2. 训练。训练过程多次遍历训练集,每个样本被单独处理。对每个样本计算特征加权和与偏置,再应用激活函数得到预测值。预测值与真实值之间的差异用于更新权重和偏置,更新幅度受学习率控制。 3. 激活。感知机中的激活函数是阶跃函数:若输入加权和加偏置大于 0,则返回 1;否则返回 0。这样模型就能做出明确的二分类预测。 4. 预测。训练后,感知机可以用于预测新数据类别。预测过程与训练中的前向计算类似,但不再更新权重和偏置。 5. 决策边界可视化。可以通过创建网格并对网格点应用模型来显示类别分离边界。 6. 迭代训练与动画显示。使用 Matplotlib 的 FuncAnimation 类,可以制作动画,显示随着训练数据逐步加入,感知机学习过程以及决策边界的变化。

Python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


class Perceptron:
    def __init__(self, learning_rate=0.01, n_iters=1000):
        self.lr = learning_rate
        self.n_iters = n_iters
        self.activation_func = self._unit_step_func
        self.weights = None
        self.bias = None
        self.weights_history = []

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0
        self.weights_history.append((self.weights.copy(), self.bias))
        y_ = np.array([1 if i > 0 else 0 for i in y])

        for _ in range(self.n_iters):
            for idx, x_i in enumerate(X):
                linear_output = np.dot(x_i, self.weights) + self.bias
                y_predicted = self.activation_func(linear_output)
                update = self.lr * (y_[idx] - y_predicted)
                self.weights += update * x_i
                self.bias += update
                self.weights_history.append((self.weights.copy(), self.bias))

    def _unit_step_func(self, x):
        return np.where(x > 0, 1, 0)

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return self.activation_func(linear_output)


def plot_decision_boundary(X, y, classifier, ax):
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1),
                           np.arange(x2_min, x2_max, 0.1))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T).reshape(xx1.shape)
    ax.contourf(xx1, xx2, Z, alpha=0.4)
    ax.scatter(X[:, 0], X[:, 1], c=y, s=10, edgecolor='k')
    weights, bias = classifier.weights_history[-1]
    ax.set_xlabel(f'权重:{weights},偏置:{bias}')


np.random.seed(1)
X = np.random.randn(100, 2)
y = np.array([1 if x[0] + x[1] > 0 else 0 for x in X])
p = Perceptron(learning_rate=0.1, n_iters=10)
fig, ax = plt.subplots()


def update(frame):
    ax.clear()
    p.fit(X[:frame + 1], y[:frame + 1])
    plot_decision_boundary(X[:frame + 1], y[:frame + 1], p, ax)
    ax.set_title(f'迭代:{frame + 1}')


ani = FuncAnimation(fig, update, frames=range(1, X.shape[0]), interval=100)
plt.show()

需要强调的是,感知机算法只有在线性可分数据上才保证收敛。对于不能线性分离的数据,感知机可能无法收敛到稳定解,权重会不断更新,试图寻找实际上并不存在的分离边界。

6.4.2 梯度下降

梯度下降是一种迭代优化算法,目标是找到损失函数(或误差函数)的最小值。损失函数衡量模型在当前训练阶段的表现,即预测值与真实值之间的差异。梯度下降通过调整感知机权重来最小化该函数,使误差尽可能小。

该方法的核心是函数梯度。梯度由损失函数对各个权重的偏导数组成,指向函数值增长最快的方向。因此,沿梯度相反方向移动,就能执行“下降”,从而寻找函数最小值。

常见梯度下降有以下几种形式。

1. 批量梯度下降(Batch Gradient Descent):使用整个数据集计算梯度,下降方向较稳定,但在大数据集上计算代价高。 2. 随机梯度下降(Stochastic Gradient Descent,SGD):对每个训练样本单独计算梯度,过程更随机,但计算速度显著提高。 3. 小批量梯度下降(Mini-batch Gradient Descent):在小批量训练样本上计算梯度,是前两种方法之间的折中。

下面给出随机梯度下降的 Python 示例。与批量更新不同,这里在处理每个训练样本后立即更新权重。

Python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


class Perceptron:
    def __init__(self, learning_rate=0.01, n_iters=1000):
        self.lr = learning_rate
        self.n_iters = n_iters
        self.activation_func = self._unit_step_func
        self.weights = None
        self.bias = None
        self.weights_history = []

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0
        self.weights_history.append((self.weights.copy(), self.bias))
        y_ = np.array([1 if i > 0 else 0 for i in y])

        for _ in range(self.n_iters):
            for idx, x_i in enumerate(X):
                linear_output = np.dot(x_i, self.weights) + self.bias
                y_predicted = self.activation_func(linear_output)
                error = y_[idx] - y_predicted
                self.weights += self.lr * error * x_i
                self.bias += self.lr * error
                self.weights_history.append((self.weights.copy(), self.bias))

    def _unit_step_func(self, x):
        return np.where(x > 0, 1, 0)

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return self.activation_func(linear_output)

在该代码中,权重和偏置的更新发生在遍历每个样本的内部循环中。在随机梯度下降中,权重不是在完成整个数据集遍历后更新,而是在每个训练样本之后立即更新。

权重更新幅度取决于该样本的误差、学习率以及特征值。学习率 learning_rate(代码中的 self.lr)控制每次更新的步长,是影响收敛速度、过拟合风险以及是否陷入局部极小值的重要超参数。

6.4.3 非线性可分数据

感知机的基本形式学习的是分离两个类别的线性边界。不过,如果我们知道或可以假定类别之间的非线性边界形状,就可以调整感知机学习算法,使其学习非线性判别函数。

第一步是变换输入数据,使其符合预期的非线性形式。这可以通过引入多项式函数、对数变换或三角函数等方法实现。具体选择哪一种函数或函数组合,取决于数据性质以及假定的非线性边界形状。

数据经过变换后,再把感知机算法应用到修改后的数据上。在这种情况下,感知机在新的特征空间中寻找线性边界;该线性边界对应原始空间中的非线性边界。这样,感知机就能够有效分离原始特征空间中线性不可分的数据。

例如,如果数据由二阶曲线(抛物线)分隔,就可以为每个输入向量加入新的特征,如原始特征的平方项。这样会把原始数据空间变换为一个新空间,感知机在新空间中可以找到线性边界,而该边界在原始空间中表现为非线性边界。

Python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


class Perceptron:
    def __init__(self, learning_rate=0.01, n_iters=1000):
        self.lr = learning_rate
        self.n_iters = n_iters
        self.activation_func = self._unit_step_func
        self.weights = None
        self.bias = None
        self.weights_history = []

    def fit(self, X, y):
        n_samples, n_features = X.shape
        self.weights = np.zeros(n_features)
        self.bias = 0
        self.weights_history.append((self.weights.copy(), self.bias))
        y_ = np.array([1 if i > 0 else 0 for i in y])
        for _ in range(self.n_iters):
            for idx, x_i in enumerate(X):
                linear_output = np.dot(x_i, self.weights) + self.bias
                y_predicted = self.activation_func(linear_output)
                update = self.lr * (y_[idx] - y_predicted)
                self.weights += update * x_i
                self.bias += update
                self.weights_history.append((self.weights.copy(), self.bias))

    def _unit_step_func(self, x):
        return np.where(x > 0, 1, 0)

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return self.activation_func(linear_output)


def add_nonlinear_features(X):
    X_nonlinear = np.zeros((X.shape[0], X.shape[1] * 3))
    X_nonlinear[:, :2] = X
    X_nonlinear[:, 2] = X[:, 0] ** 2
    X_nonlinear[:, 3] = X[:, 1] ** 2
    X_nonlinear[:, 4] = X[:, 0] * X[:, 1]
    return X_nonlinear


def plot_decision_boundary(X, y, classifier, ax):
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.1),
                           np.arange(x2_min, x2_max, 0.1))
    grid = np.array([xx1.ravel(), xx2.ravel()]).T
    Z = classifier.predict(add_nonlinear_features(grid)).reshape(xx1.shape)
    ax.contourf(xx1, xx2, Z, alpha=0.4)
    ax.scatter(X[:, 0], X[:, 1], c=y, s=10, edgecolor='k')
    weights, bias = classifier.weights_history[-1]
    ax.set_xlabel(f'权重:{weights},偏置:{bias}')


np.random.seed(1)
X = np.random.randn(100, 2)
y = np.array([1 if x[0] ** 2 + x[1] ** 2 < 1 else 0 for x in X])
X_nonlinear = add_nonlinear_features(X)
p = Perceptron(learning_rate=0.1, n_iters=10)
p.fit(X_nonlinear, y)

fig, ax = plt.subplots()
plot_decision_boundary(X, y, p, ax)
plt.show()
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.4.3 非线性可分数据

函数 add_nonlinear_features 向原始数据添加平方项与交互项等非线性特征,从而使感知机能够学习非线性决策边界。plot_decision_boundary 用于绘制分类器决策边界:它先确定两个特征轴上的最小值和最大值,再用 np.meshgrid 创建覆盖特征空间的坐标网格。随后将网格点作为输入传给分类器,得到每个网格点的预测类别,再通过 contourf 绘制不同类别区域,并用 scatter 显示原始数据点。图中还可输出当前权重与偏置,以便观察训练过程中的变化。

6.4.4 使用感知机的创造性方法

前面没有详细讨论感知机解决超出简单线性可分场景任务的能力。这里考虑若干技巧。

一个典型例子是异或(XOR)运算:当两个输入信号中恰好有一个被激活时,输出才激活;当二者同时激活或同时不激活时,输出不激活。该任务是非线性分类问题,无法用具有标准线性权重和阈值的单个感知机直接解决。不过,通过重新表述问题条件或改变输入数据表示,可以训练感知机处理 XOR 任务。这表明,通过创造性的数据表示,可以在一定程度上克服线性分类的局限。

另一个复杂任务是奇偶性函数:当三个输入信号中被激活的数量为奇数时,输出被激活。该任务同样需要非标准的感知机训练方式和输入表示,但也可以被处理。

在数学和计算机科学中,常见函数可以接收若干输入(称为参数),并根据输入给出结果。有些函数具有“置换不变性”:如果交换输入顺序,函数结果不变。XOR 可以作为这样的函数示例,它不依赖输入变量的顺序。

图像的某些性质也具有置换不变性,例如“是否至少存在一个黑色像素”。即使交换像素位置,这一性质也不变。

正正规形式是一种记录数学函数的特殊方式,仅使用“与”和“非”等操作描述函数。

最小项是复杂函数的基本组成部分,包含函数的全部变量。如果函数对置换不变,即结果不依赖变量顺序,那么这些最小项可以用相对简单的方式表示,并在公式中具有相同的“权重”或系数。这有助于简化此类函数的分析与处理。

下面用 Python 展示:XOR 函数结果不依赖输入变量顺序,并且正正规形式可以表示该函数。

Python
# 两个变量上的异或函数

def xor(x1, x2):
    return (x1 and not x2) or (not x1 and x2)


# 检查 XOR 的置换不变性
a = 0
b = 1
result1 = xor(a, b)
result2 = xor(b, a)


# XOR 的正正规形式(PNF)
def xor_pnf(x1, x2):
    return (1 - x1) * x2 + x1 * (1 - x2)


pnf_result1 = xor_pnf(a, b)
pnf_result2 = xor_pnf(b, a)
print(result1, result2, pnf_result1, pnf_result2)

输出为:

Text
1 True 1 1

增量计算是指在已有计算结果上加入新数据,而不必从头重新计算。这在图像分析中很有用,例如在已有像素分析结果上加入新像素信息。

假设有一幅含有 l 个像素的图像,需要判断是否至少有一个黑色像素。可以使用增量方法:函数 g(X) 可写为所有像素值之和

$$x_1+x_2+\cdots+x_l,$$

其中每个 x 表示一个具体像素值,例如黑色为 1,非黑色为 0。如果图像变大并加入新像素,只需把这些新像素值加入已有和中,而不必重新计算全部像素。这使过程更快、更高效。

相比之下,某些函数不适合增量计算,例如判断黑色像素数量奇偶性的函数以及 XOR 函数。图像大小变化时,这些函数通常需要重新计算,因为它们依赖全部数据元素,并且强烈依赖输入数据的组织方式,因此较难通过增量方法优化。

6.5 基于核的支持向量机(Kernel-Based SVM)

20 世纪 60—70 年代,弗拉基米尔·瓦普尼克和阿列克谢·切尔沃年基斯提出了支持向量机。与感知机相比,SVM 的重要改进在于最大化类别之间的间隔,从而在测试数据上获得更好的泛化能力。不过,早期 SVM 仍然是线性分类器,也无法直接解决非线性可分数据问题。

突破来自核技巧的引入。核函数把原始数据映射到更高维空间,在该空间中数据可能变为线性可分。因此,SVM 可以为非线性可分数据寻找最优解,这是普通感知机无法做到的。

这一发现推动了支持向量机在机器学习中的广泛应用,也为多种核函数的发展奠定了基础,例如多项式核、径向基函数核(RBF)和 sigmoid 核。不同核函数在不同条件下各有优势。

支持向量机适用于在多维(核)空间中,基于线性判别函数处理非线性分类问题。线性 SVM 常用于高维空间应用;而在低维空间中,基于核的 SVM 是常见的非线性分类器。核技巧使我们能够在输入空间中进行计算,而不必显式进入潜在的高维甚至理论上无限维的核空间。核技巧后来也被广泛用于其他模式识别和机器学习算法。

6.5.1 非线性可分数据

考虑数据不是线性可分的情形。这意味着无法用一条直线将数据分成两个类别,使每个类别都完全位于直线一侧。在支持向量机语境中,这等价于无法找到一个无误分的超平面。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.5.1 非线性可分数据
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.5.1 非线性可分数据

当数据不能线性分离时,需要注意以下几点。

1. 无法找到权重 W 和偏置 b,使得当 X 属于负类时 W^T X+b=-1,当 X 属于正类时 W^T X+b=1。 2. 不存在可直接最大化的硬间隔,因此简单的间隔最大化不再有意义。许多实际问题都属于此类,数据并不由清晰边界分开。 3. 对非线性可分数据,无法像线性可分情形那样找到理想最优超平面。 4. 因此,需要构造并优化“软”间隔,这意味着允许某些数据点违反间隔条件。

对于二维数据,正类样本若误差 e_1 大于 1,则会被错误分类。负类样本如果对应误差 e_2<1,则虽然落在间隔内,但并未发生错误分类。

目标是最小化这些误差,因此把误差和加入优化准则中:

$$\min_W\left(\frac{1}{2}\|W\|^2+C\sum_{i=1}^{n}e_i\right).$$

X_i 分类无误,则 e_i=0。同时 e_i 不能为负,因此对所有 ie_i\geq 0

相应约束可放宽为:对于 y_i=+1,有

$$W^T X_i+b\geq 1-e_i,$$

对于 y_i=-1,有

$$W^T X_i+b\leq -1+e_i.$$

引入 e_i 后,对正类样本 X_i 有三种可能。

1. 若 e_i=0,则 X_i 位于支持平面上,且分类正确。 2. 若 e_i<1,则 W^T X_i+b>1-e_i>0,样本仍被正确分类,但可能处在间隔内部或边界附近。 3. 若 e_i\geq 1,则 W^T X_i+b\leq 0,样本会被错误分类。

负类样本可做类似分析。无论 X_i 属于 C_+y_i=1)还是 C_-y_i=-1),统一写法为

$$y_i(W^T X_i+b)\geq 1-e_i,\qquad e_i\geq 0.$$

因此,优化问题同时包含最小化权重向量范数和由参数 C 加权的分类误差惩罚项。约束允许模型对误差具有一定容忍度,也就是允许某些点处于边界错误一侧,但这种违反受到惩罚控制。

6.5.2 软间隔公式化

在感知机和 SVM 的学习语境中,“软间隔”用于构建更灵活、更具有适应性的模型。它允许模型处理无法被线性边界完美分开的数据。本节说明软间隔如何用于训练分类器,从而更好地处理数据重叠和噪声。

考虑二维点数据,每个点属于两个类别之一。例如:

Python
X = [[3, 3], [3, 4], [2, 3], [1, 1], [1, 3], [2, 2]]
y = [1, 1, 1, -1, -1, -1]
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.5.2 软间隔公式化
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 6.5.2 软间隔公式化

这里有两个类别:1 和 −1。随后使用线性核 SVM,并设定参数 C=1.0。参数 C 是理解软间隔的关键。较小的 C 会让模型更容忍误差(允许间隔违反);较大的 C 则要求类别分离更严格。

训练完成后,可以把不同类别的点画在图上,并显示分类器得到的分离直线。分离直线两侧的虚线表示软间隔边界,即模型允许一定误差以获得更好泛化能力的区域。支持向量是最靠近分离边界的数据点,对边界位置起关键作用。

Python
from sklearn import svm
import numpy as np
import matplotlib.pyplot as plt

X = np.array([
    [3, 3],
    [3, 4],
    [2, 3],
    [1, 1],
    [1, 3],
    [2, 2]
])
y = np.array([1, 1, 1, -1, -1, -1])

clf = svm.SVC(kernel='linear', C=1.0)
clf.fit(X, y)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='autumn')
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()

xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = clf.decision_function(xy).reshape(XX.shape)

ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
           linestyles=['--', '-', '--'])
ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=100,
           linewidth=1, facecolors='none', edgecolors='k')
plt.show()

该代码使用软间隔(由参数 C 控制),允许部分数据点违反间隔,以在非线性或近似不可分数据上获得更好的泛化。决策边界用实线表示,间隔边界用虚线表示,支持向量用较大的空心点表示。

6.5.3 使用带曲线的支持向量机进行分类

SVM 通常与二分类联系在一起,但其思想也可以扩展到多类分类。本节说明如何把 SVM 用于多个类别,介绍主要策略并给出示例。

使用 SVM 实现多类分类时,常用两种方法:“一对一”(OvO)与“一对其余”(OvA)。

1. 一对一(OvO):对每一对类别训练一个二分类器。若有 N 个类别,需要训练 N(N-1)/2 个分类器。分类新样本时,各分类器投票,票数最高的类别作为结果。 2. 一对其余(OvA):对每个类别训练一个分类器,用于区分该类别与所有其他类别。若有 N 个类别,则训练 N 个分类器。分类新样本时,选择置信度最高的分类器对应类别。

多数现代机器学习库(如 scikit-learn)都内置了 SVM 多类分类支持,会自动应用上述策略之一。实际使用时,还需要关注核函数选择、正则化参数和特征缩放,以提高模型性能。

在多类分类语境中,还可提到 Nu-SVM 或用于分类任务的 Nu-SVC。Nu-SVM 是经典 SVM 的替代形式,允许用户通过参数 nu 控制支持向量数量和错误比例。nu 的取值范围为 0 到 1。

Nu-SVM 保留 SVM 的基本原则,同时在支持向量数量与错误间隔之间提供更灵活的折中。因此,当需要更细致地调节模型,或数据包含较多噪声时,Nu-SVM 尤其有用。

下面使用 XOR 结构数据展示 NuSVC 对曲线决策边界的学习。

Python
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm

xx, yy = np.meshgrid(np.linspace(-3, 3, 500),
                     np.linspace(-3, 3, 500))
np.random.seed(0)
X = np.random.randn(300, 2)
Y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0)

clf = svm.NuSVC()
clf.fit(X, Y)

Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.imshow(Z, interpolation='nearest',
           extent=(xx.min(), xx.max(), yy.min(), yy.max()),
           aspect='auto', origin='lower', cmap=plt.cm.BuGn)
plt.contour(xx, yy, Z, levels=[0], linewidths=2)
plt.scatter(X[:, 0], X[:, 1], s=30, c=Y, cmap=plt.cm.jet)
plt.xticks(())
plt.yticks(())
plt.axis([-3, 3, -3, 3])
plt.show()

该示例生成二维随机点,并用 XOR 条件创建类别标签。NuSVC 学习得到的决策函数在网格上被可视化,等值线 Z=0 显示模型的决策边界。

6.5.4 使用支持向量机进行多类分类

在许多机器学习应用中,数据会被划分为多个类别。支持向量机也适合解决多类别分类任务。处理多类分类时,常通过“一对其余”和“一对一”等策略把问题化为多个二分类问题。

下面以 Iris 数据集为例,说明 SVM 如何根据花形参数进行物种分类。我们比较四种 SVM 模型:线性核、LinearSVC、径向基函数核(RBF)和多项式核。为了便于可视化,只使用数据集中的前两个特征。通过坐标网格显示不同模型得到的类别边界,从而观察不同核函数如何划分特征空间。

Python
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm, datasets


def make_meshgrid(x, y, h=.02):
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    return xx, yy


def plot_contours(ax, clf, xx, yy, **params):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out


iris = datasets.load_iris()
X = iris.data[:, :2]
y = iris.target
C = 1.0

models = (
    svm.SVC(kernel='linear', C=C),
    svm.LinearSVC(C=C, max_iter=10000),
    svm.SVC(kernel='rbf', gamma=0.7, C=C),
    svm.SVC(kernel='poly', degree=3, gamma='auto', C=C)
)
models = (clf.fit(X, y) for clf in models)

titles = (
    '线性核 SVC',
    'LinearSVC(线性核)',
    'RBF 核 SVC',
    '多项式核 SVC(三次)'
)

fig, sub = plt.subplots(2, 2, figsize=(10, 8))
plt.subplots_adjust(wspace=0.2, hspace=0.2)

X0, X1 = X[:, 0], X[:, 1]
xx, yy = make_meshgrid(X0, X1)

for clf, title, ax in zip(models, titles, sub.flatten()):
    plot_contours(ax, clf, xx, yy, cmap='winter', alpha=0.8)
    ax.scatter(X0, X1, c=y, cmap=plt.cm.Greys, s=40, edgecolors='w')
    ax.set_xlim(xx.min(), xx.max())
    ax.set_ylim(yy.min(), yy.max())
    ax.set_xlabel('萼片长度')
    ax.set_ylabel('萼片宽度')
    ax.set_xticks(())
    ax.set_yticks(())
    ax.set_title(title)

plt.show()

本章结论

支持向量机(SVM)是机器学习中功能强大的工具,可用于分类和回归任务,也可用于多类分类。在多类分类中,SVM 通过一对其余、一对一等策略训练多个分类器。对于非线性可分数据,SVM 使用核技巧,在不显式进入高维空间的情况下完成高维空间中的分类。常用核函数包括多项式核、径向基函数核和 sigmoid 核。模型性能依赖参数选择,例如正则化参数 C 以及核函数参数,这些参数控制间隔最大化与分类误差最小化之间的平衡,进而影响模型泛化能力。SVM 在类别边界清晰以及高维数据场景中表现良好,但对参数选择较敏感,在大规模数据和多类分类任务中也可能具有较高计算成本。

教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 本章结论
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 本章结论
教材插图:感知机与支持向量机(SVM)
教材插图:感知机与支持向量机(SVM) — 本章结论

自测

哪一项最能概括“感知机与支持向量机(SVM)”这一章的重点?

在机器学习中,理论定义需要通过数值示例和可视化进行检验。

哪些做法有助于巩固本章内容?

参加测试