第2章 感知机
习题2.1
Minsky 与 Papert 指出:感知机因为是线性模型,所以不能表示复杂的函数,如异或 (XOR)。验证感知机为什么不能表示异或。
解答:
解答思路:
- 列出异或函数(XOR)的输入和输出;
- 使用图例法证明异或问题是线性不可分的;
- 使用反证法证明感知机无法表示异或。
解题步骤:
第1步:异或函数(XOR)的输入和输出
对于异或函数(XOR),全部的输入与对应的输出如下:
| 0 | 0 | -1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | -1 |
第2步:使用图例法证明异或问题是线性不可分的
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 使用Dataframe表示异或的输入与输出数据
x1 = [0, 0, 1, 1]
x2 = [0, 1, 0, 1]
y = [-1, 1, 1, -1]
x1 = np.array(x1)
x2 = np.array(x2)
y = np.array(y)
data = np.c_[x1, x2, y]
data = pd.DataFrame(data, index=None, columns=['x1', 'x2', 'y'])
data.head()| x1 | x2 | y | |
|---|---|---|---|
| 0 | 0 | 0 | -1 |
| 1 | 0 | 1 | 1 |
| 2 | 1 | 0 | 1 |
| 3 | 1 | 1 | -1 |
# 获取正类别(y=1)的数据
positive = data.loc[data['y'] == 1]
# 获取负类别(y=-1)的数据
negative = data.loc[data['y'] == -1]
# 绘制数据图
# 绘制坐标轴
plt.xlim(-0.5, 1.5)
plt.ylim(-0.5, 1.5)
plt.xticks([-0.5, 0, 1, 1.5])
plt.yticks([-0.5, 0, 1, 1.5])
# 添加坐标轴文字
plt.xlabel("x1")
plt.ylabel("x2")
# 绘制正、负样本点
plt.plot(positive['x1'], positive['x2'], "ro")
plt.plot(negative['x1'], negative['x2'], "bx")
# 添加图示
plt.legend(['Positive', 'Negative'])
plt.show()
从上图可以看出,无法使用一条直线将两类样本分开,所以异或问题是线性不可分的
from sklearn.linear_model import Perceptron
import numpy as np
# 构造异或问题的训练数据集
X_train = np.array([[1, 1], [1, 0], [0, 1], [0, 0]])
y = np.array([-1, 1, 1, -1])
# 使用sklearn的Perceptron类构建感知机模型
perceptron_model = Perceptron()
# 进行模型训练
perceptron_model.fit(X_train, y)
# 打印模型参数
print("感知机模型的参数:w=", perceptron_model.coef_[
0], "b=", perceptron_model.intercept_[0])感知机模型的参数:w= [0. 0.] b= 0.0
上述使用sklearn的Perceptron类构建感知机模型,从模型的参数上观察,感知机模型无法表示异或。
第3步:使用反证法证明感知机无法表示异或
根据书中第2章感知机模型的定义2.1:
定义2.1(感知机) 假设输入空间(特征空间)是
,输出空间是 。输入 表示实例的特征向量,对应于输入空间(特征空间)的点;输出 表示实例的类别。由输入空间到输出空间的如下函数: 称为感知机。其中,
和 为感知机模型参数, 叫做权值或权值向量, 叫做偏置, 表示 和 的内积。sign是符号函数,即
假设感知机模型可以表示异或问题,即满足异或函数(XOR)输入与输出的情况(见第1步)。假设
- 根据
,则 ,可得 ; - 根据
,则 ,结合 ,可得 ; - 根据
,则 ,结合 ,可得 ; - 根据
,并结合 、 ,则 ,可得 ,与异或条件中的 矛盾。
所以假设不成立,原命题成立,即感知机模型不能表示异或。
习题2.2
模仿例题 2.1,构建从训练数据求解感知机模型的例子。
解答:
解答思路:
按照书中第2章感知机学习算法2.1,编写代码并绘制分离超平面
算法2.1(感知机学习算法的原始形式)
输入:训练数据集,其中 , , ;学习率 ;
输出:;感知机模型
(1)选取初值;
(2)在训练集中选取数据;
(3)如果, (4)转至(2),直至训练集中没有误分类点。
解题步骤:
import numpy as np
from matplotlib import pyplot as plt
%matplotlib tk
class Perceptron:
def __init__(self, X, Y, lr=0.001, plot=True):
"""
初始化感知机
:param X: 特征向量
:param Y: 类别
:param lr: 学习率
:param plot: 是否绘制图形
"""
self.X = X
self.Y = Y
self.lr = lr
self.plot = plot
if plot:
self.__model_plot = self._ModelPlot(self.X, self.Y)
self.__model_plot.open_in()
def fit(self):
# (1)初始化weight, b
weight = np.zeros(self.X.shape[1])
b = 0
# 训练次数
train_counts = 0
# 分类错误标识
mistake_flag = True
while mistake_flag:
# 开始前,将mistake_flag设置为False,用于判断本次循环是否有分类错误
mistake_flag = False
# (2)从训练集中选取x,y
for index in range(self.X.shape[0]):
if self.plot:
self.__model_plot.plot(weight, b, train_counts)
# 损失函数
loss = self.Y[index] * (weight @ self.X[index] + b)
# (3)如果损失函数小于0,则该点是误分类点
if loss <= 0:
# 更新weight, b
weight += self.lr * self.Y[index] * self.X[index]
b += self.lr * self.Y[index]
# 训练次数加1
train_counts += 1
print("Epoch {}, weight = {}, b = {}, formula: {}".format(
train_counts, weight, b, self.__model_plot.formula(weight, b)))
# 本次循环有误分类点(即分类错误),置为True
mistake_flag = True
break
if self.plot:
self.__model_plot.close()
# (4)直至训练集中没有误分类点
return weight, b
class _ModelPlot:
def __init__(self, X, Y):
self.X = X
self.Y = Y
@staticmethod
def open_in():
# 打开交互模式,用于展示动态交互图
plt.ion()
@staticmethod
def close():
# 关闭交互模式,并显示最终的图形
plt.ioff()
plt.show()
def plot(self, weight, b, epoch):
plt.cla()
# x轴表示x1
plt.xlim(0, np.max(self.X.T[0]) + 1)
# y轴表示x2
plt.ylim(0, np.max(self.X.T[1]) + 1)
# 画出散点图,并添加图示
scatter = plt.scatter(self.X.T[0], self.X.T[1], c=self.Y)
plt.legend(*scatter.legend_elements())
if True in list(weight == 0):
plt.plot(0, 0)
else:
x1 = -b / weight[0]
x2 = -b / weight[1]
# 画出分离超平面
plt.plot([x1, 0], [0, x2])
# 绘制公式
text = self.formula(weight, b)
plt.text(0.3, x2 - 0.1, text)
plt.title('Epoch %d' % epoch)
plt.pause(0.01)
@staticmethod
def formula(weight, b):
text = 'x1 ' if weight[0] == 1 else '%d*x1 ' % weight[0]
text += '+ x2 ' if weight[1] == 1 else (
'+ %d*x2 ' % weight[1] if weight[1] > 0 else '- %d*x2 ' % -weight[1])
text += '= 0' if b == 0 else ('+ %d = 0' %
b if b > 0 else '- %d = 0' % -b)
return textX = np.array([[3, 3], [4, 3], [1, 1]])
Y = np.array([1, 1, -1])
model = Perceptron(X, Y, lr=1)
weight, b = model.fit()Epoch 1, weight = [3. 3.], b = 1, formula: 3*x1 + 3*x2 + 1 = 0
Epoch 2, weight = [2. 2.], b = 0, formula: 2*x1 + 2*x2 = 0
Epoch 3, weight = [1. 1.], b = -1, formula: x1 + x2 - 1 = 0
Epoch 4, weight = [0. 0.], b = -2, formula: 0*x1 - 0*x2 - 2 = 0
Epoch 5, weight = [3. 3.], b = -1, formula: 3*x1 + 3*x2 - 1 = 0
Epoch 6, weight = [2. 2.], b = -2, formula: 2*x1 + 2*x2 - 2 = 0
Epoch 7, weight = [1. 1.], b = -3, formula: x1 + x2 - 3 = 0

习题2.3
证明以下定理:样本集线性可分的充分必要条件是正实例点所构成的凸壳与负实例点所构成的凸壳互不相交。
解答:
解答思路:
- 写出凸壳和线性可分的定义
- 证明必要性:线性可分
凸壳不相交 - 证明充分性:凸壳不相交
线性可分
第1步:凸壳与线性可分的定义
- 根据书中第2章习题脚注1的凸壳定义如下:
设集合
,是由 中的 个点所组成的集合,即 。定义 的凸壳 为:
- 根据书中第2章数据集的线性可分性定义2.2:
定义2.2(数据集的线性可分性) 给定一个数据集
其中
,如果存在某个超平面 能够将数据集的正实例点和负实例点完全正确划分到超平面的两侧,即对所有
的实例 ,有 ,对 的实例 ,有 ,则称数据集 为线性可分数据集,否则称数据集 线性不可分。
第2步:证明必要性:线性可分
证明思路(反证法):
- 假设原命题不成立:样本集线性可分,正实例点所构成的凸壳与负实例点所构成的凸壳相交
- 条件推理
- 发现矛盾,得出原命题成立
证明步骤:
假设原命题不成立:
设数据集中的正例点集为 , 的凸壳为 ,负实例点集为 , 的凸壳为 。
假设样本集线性可分,正实例点所构成的凸壳与负实例点所构成的凸壳相交,即存在某个元素,同时满足 和 。 条件推理:
若数据集是线性可分的,根据线性可分的定义,则存在一个超平面能够将 和 完全分离:
对于所有的正例点
根据凸壳的定义,对于
因此
同理对于
- 找出矛盾,得出原命题成立:
根据条件推理,当有 ,当 有 ,既 不可能同时满足若 和 ,这与假设命题矛盾。
因此,原命题成立,当样本线性可分时,
第3步:证明充分性:凸壳不相交
证明思路:
- 根据凸壳不相交,找到一个超平面
- 证明这个超平面可将两个互不相交的凸壳分隔开(反证法)
- 上述超平面可以将凸壳分隔开,则样本集满足线性可分
证明步骤:
- 根据凸壳不相交,找到一个超平面:
设数据集中的正例点集为 , 的凸壳为 ,负实例点集为 , 的凸壳为 ,且 与 不相交。
定义两个点的距离为
定义
记最小值点分别为
定义以
- 证明这个超平面可将两个互不相交的凸壳分隔开(反证法)
若某个超平面可将两个互不相交的凸壳分隔开,则且 。
假设原命题不成立:当
设点
求解
根据假设,在
故原命题成立,即
- 上述超平面可以将凸壳分隔开,则样本集满足线性可分
根据凸壳定义,数据集中正例点 ,负例点 。上述超平面可以将正例点集 和负例点集 两个凸壳分隔开,则可以使样本集线性可分。充分性得证。
