课程作业

image.png

提供的代码**

  1. #scikit-learn#老师给出的代码可能存在sklearn版本的问题,第二个make_blobs可能会出错,我这里更正为最新版本的写法。
  2. #墨渍数据从Sklearn获取
  3. from sklearn.datasets import make_blobs
  4. import matplotlib.pyplot as plt
  5. from sklearn.model_selection import train_test_split #训练数据和测试数据分割
  6. #这里修改100为2,方面我获取X和y的数据。
  7. X, y = make_blobs(n_samples= 2, centers=2, n_features=2, cluster_std=0.6, random_state=0) #提示:输出一下 y的值,y的值需不需要转换?
  8. plt.plot(X[:, 0][y == 1], X [:, 1][y == 1], "bs", ms=3)
  9. plt.plot(X[:, 0][y == 0], X [:, 1][y == 0], "yo", ms=3)
  10. #y的值需要转换成{-1,+1},上面两行语句也要修改
  11. #使用下面的语句输出图片
  12. plt.show()

make_blobs函数

make_blobs()函数的作用是产生一个数据集和相应的标签,返回值分别是两个类数组的类,在这里用type()函数输出ERM算法架构_使用感知器算法解决墨渍分类问题 - 图2ERM算法架构_使用感知器算法解决墨渍分类问题 - 图3可以知道:ERM算法架构_使用感知器算法解决墨渍分类问题 - 图4ERM算法架构_使用感知器算法解决墨渍分类问题 - 图5都是numpy库的对象ndarray。

同时X,y组成了一个tuple。

这里用到这样几个参数:

  • n_samples,入参是数字或者类数组元素,表示样本总数。
  • n_features,入参int,每个样本的特征数量。
  • cluster_std:数据集的标准差, 数据方差的开平方,这里是0.6
  • random_state:随机数种子,一旦指定给定随机数种子,那么代码每一次执行,从make_blobs里面获取的都是相同的样本,常用随机数种子的值为0和42。比如下一次实验我想换一批随机样本,那么我就需要修改这个随机数种子。
  • centers:官网给出的解释是数据的中心点,将其调到6将不会产生结果,这个意思其实还没整明白(逃

下面是source code给出的样例

  1. Examples
  2. --------
  3. >>> from sklearn.datasets import make_blobs
  4. >>> X, y = make_blobs(n_samples=10, centers=3, n_features=2,
  5. ... random_state=0)
  6. >>> print(X.shape)
  7. (10, 2)
  8. >>> y
  9. array([0, 0, 1, 0, 2, 2, 2, 1, 1, 0])
  10. >>> X, y = make_blobs(n_samples=[3, 3, 4], centers=None, n_features=2,
  11. ... random_state=0)
  12. >>> print(X.shape)
  13. (10, 2)
  14. >>> y
  15. array([0, 1, 2, 0, 2, 2, 2, 1, 1, 0])

train_test_split函数

train_test_split()将样本随机划分为训练样本集和测试样本集,由于随机数种子的存在,即使多次运行划分出来的也是两个完全一样的数据集。

  1. train_test_split(X, y, test_size=0.5, random_state=0)
  1. ERM算法架构_使用感知器算法解决墨渍分类问题 - 图6上面的ndarray对象,在这里源代码是这样定义的 def train_test_split(*arrays, **options)。可变参数意味着可以传入多个矩阵。在我们这里传入的是ERM算法架构_使用感知器算法解决墨渍分类问题 - 图7
  2. test_size也就是测试样本集划分的比例,0.5意味着测试样本在总的样本里面占比50%,也就是五五开(训练与测试样本),如果test_size=0.2,就是八二开(训练与测试样本)
  3. random_state:和上面一样,随机数种子。

下面是source code给出的样例

  1. Examples
  2. --------
  3. >>> import numpy as np
  4. >>> from sklearn.model_selection import train_test_split
  5. >>> X, y = np.arange(10).reshape((5, 2)), range(5)
  6. >>> X
  7. array([[0, 1],
  8. [2, 3],
  9. [4, 5],
  10. [6, 7],
  11. [8, 9]])
  12. >>> list(y)
  13. [0, 1, 2, 3, 4]

感知器算法

代码

感知器算法代码如下:

  1. import numpy as np
  2. #感知器算法
  3. class Perceptron:
  4. def fit(self, X, y):
  5. m, n = X.shape
  6. w = np.zeros((n,1))
  7. b = 0
  8. done = False
  9. while not done:
  10. done = True
  11. for i in range(m):
  12. x = X[i].reshape(1,-1)
  13. if y[i] * (x.dot(w) + b) <= 0:
  14. w = w + y[i] * x.T
  15. b = b + y[i]
  16. done = False
  17. self.w = w
  18. self.b = b
  19. def predict(self, X):
  20. return np.sign(X.dot(self.w) + self.b)

概述

def fit(self, X, y):这句代码是fit函数的定义。

m, n = X.shape,这段代码的含义是获取X矩阵的shape,我们这里传入的是50%的测试数据。每个样本2个维度,那么m和n的值就是50和2,这里的目的是想要获知样本的样本数量和特征数量。

因为感知器求的本质上就是一个线性方程组的解,我们在日常数学里的做法是这样的:

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图8

上面这个式子,将其变换一下:

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图9

你会发现这玩意其实就是初中数学课本上的:

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图10

回到公式(1)

们把参数[a,b]作为一个1行2列的矩阵 ERM算法架构_使用感知器算法解决墨渍分类问题 - 图11 ,初始值是0的,也就是说,刚开始 ERM算法架构_使用感知器算法解决墨渍分类问题 - 图12 。对应代码如下:

  1. w = np.zeros((n,1))
  2. b = 0

也就是说我们只需要一个斜率和一个表示上下位移的参数就能将这条方程画出来,慢慢调整这些参数,我们就可以用这条线把所有的点分开!

数学意义

继续讲关于这条线和如何调整这个参数的过程。

看到如下代码:

  1. for i in range(m):
  2. x = X[i].reshape(1,-1)
  3. if y[i] * (x.dot(w) + b) <= 0:

在上面的代码里,for循环对50个样本挨个操作。x = X[i].reshape(1,-1)这句代码的含义就是先把第一个样本拿出来,进行一个矩阵转置的处理。

reshape(1,-1)函数有转换矩阵形状的作用,比如将101的单列矩阵转换成110的单行矩阵。

关于python里面矩阵转置的问题,可以查阅如下资料。 https://blog.csdn.net/Yellow_python/article/details/83780405

为什么要转置呢

因为我们需要的是一个线性方程,将这个图片上面的点分割成两份,那么根据*公式(1)的形式,我们如何将参数和样本里面的特征转化为一个线性方程呢?

通过一个.dot点乘ERM算法架构_使用感知器算法解决墨渍分类问题 - 图13的操作我们就能实现,也就是说,我们想要得到下面这样的结果,其中 ERM算法架构_使用感知器算法解决墨渍分类问题 - 图14对于这里的两个特征,我们只是把*公式(1)ERM算法架构_使用感知器算法解决墨渍分类问题 - 图15换成了ERM算法架构_使用感知器算法解决墨渍分类问题 - 图16

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图17

通过矩阵运算的方式过程如下:

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图18

我们就能得到*公式(4)

通过Debug的方式观察程序运行

我们可以通过Debug的方式观察函数是怎么处理数据的。我们可以在Pycharm上面点击运行旁边的小虫形状按钮开始Debug。

我们拿出第一个样本x,转置之后,现在变成这个样子了。[[2.56936589 0.5070483 ]]

另外我们还知道。这个样本对应的y是1。是一个正例

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图19

这个结果用if y[i] * (x.dot(w) + b) <= 0:这个语句肯定是能通过的。通过之后就可以进入调参语句。

  1. w = w + y[i] * x.T
  2. b = b + y[i]

b = b + y[i]这句话好理解,就是将b的值在原来0的基础上加了一个此样本的标签(上面说了,是1)。

w = w + y[i] * x.T其实就是将ERM算法架构_使用感知器算法解决墨渍分类问题 - 图20的值,更新了一下。x.T就是将x又转置回来了,原来是一行的矩阵,现在又变回一列了。

变回一列干嘛呢?把它里面的值分别赋给ERM算法架构_使用感知器算法解决墨渍分类问题 - 图21对应的值,起到更新ERM算法架构_使用感知器算法解决墨渍分类问题 - 图22的作用。ERM算法架构_使用感知器算法解决墨渍分类问题 - 图23的变化过程如下:

ERM算法架构_使用感知器算法解决墨渍分类问题 - 图24

在进入下一个样本之前,把我们的函数开关关上,告诉下一个判断节点,”我这儿函数还没摆正呢,你别给我结束喽!”

  1. done = False

到此为止,第一个样本是结束了,我们也更新了ERM算法架构_使用感知器算法解决墨渍分类问题 - 图25 和b的内容。

当然下面还有剩下49个样本我们还没处理。继续Debug,我们可以发现有的样本会进入if判定节点,有的却不会。那么什么情况下我们需要去调参呢?

更新参数的六种情况

再来回顾一下我们这次实验的目标,这次实验的目标是为了分开黄色墨渍和蓝色墨渍,并且这次实验给出样例代码的时候已经告诉了我们,这次正例和反例的标签值是1和-1。

我们在这里把正例看做在方程上面黄色的墨渍,反例看做在方程下面的蓝色的墨渍。

  • 黄色墨渍,标签为1,在方程上面
  • 蓝色墨渍,标签为-1,在方程下面

现在来看if y[i] * (x.dot(w) + b) <= 0:,这个判定节点的组成部分有两个:

  1. y[i] 样本标签值
  2. x.dot(w) + b 参数和样本属性带入方程的结果。

那么如果我们用软件测试的想法,把这个判定节点可能会出现的情况列在下面,再来思考这个节点出现这个情况的时候,它的几何意义是什么。我们其实就可以整理出来一张表。

下面就是y[i]符号和x.dot(w) + b符号在不同情况下构成的几何意义。

y[i]符号 x.dot(w) + b符号 判定节点 几何意义 处理方式
+ + true 黄色点,在线的上面image.png 没毛病
+ - false 黄色点,在线的下面image.png 错了!需要调参
- + false 蓝色点,在线的上面image.png 错了!需要调参
- - true 蓝色点,在线的下面image.png 没毛病
+ 0 false 黄色点,在方程线上image.png 错了!需要调参
- 0 false 蓝色点,在方程线上image.png 错了!需要调参

由此,其实已经非常清楚了。这个算法其实调参的原理非常简单,点和线的关系不合适就调参,合适就不调参。

总结

到此,已经把感知器算法的原理梳理了一遍。可以总结如下的主要思想和流程:

  • 我们的目标是一条线性方程
  • 二维平面上面有两个参数,我们把参数设为一个空的(2,1)向量
  • 让这个向量和我们样本特征点乘。检查这个方程的解和和点的空间关系。
  • 每个样本去进行如上的过程,直到调整到最后一个点

完整项目代码会在作业结束之后放出来😃 2020年03月06日