@tianxingjian
2020-12-18T21:24:07.000000Z
字数 11537
阅读 1148
深度学习
手撕机器学习系列文章就暂时更新到此吧,目前已经完成了支持向量机SVM、决策树、KNN、贝叶斯、线性回归、Logistic回归,其他算法还请允许Taoye在这里先赊个账,后期有机会有时间再给大家补上。
更新至此,也是收到了部分读者的好评。虽然不多,但还是非常感谢大家的支持,希望每一位阅读过的读者都能够有所收获。
该系列文章的全部内容都是Taoye纯手打,也是参考了不少书籍以及公开资源,系列总字数在15W左右(含源码),总页数为138,后期会再慢慢填补,更多的技术文章可以来访Taoye的公众号:玩世不恭的Coder。文档可以随意传播,但注意不可修改其中的内容。
如果文章中有任何不懂的问题,都可以直接提出,Taoye看见后会第一时间回复,同时欢迎大家来此私密地向Taoye催更:玩世不恭的Coder,公众号也有Taoye的私人联系方式。有些话,Taoye只能在那里和你偷偷地说 (#`O′)
为了提高大家的阅读体验,手撕机器学习系列文章Taoye已经整理成PDF和和HTML,阅读效果都很不错,在公众号【玩世不恭的Coder】下回复【666】即可免费获取,相信大家看完后一定会有所收获
后期的计划就是更新深度学习相关的内容,关于深度学习,Taoye认为最好的学习方式(入门)就是自己手动实现,包括其原理以及代码部分(能实现就尽可能的实现)。在实现的同时中要不断思考其过程,这样非常有助于正确理解深度学习。
所以,该系列文章前期会尽可能从零开始来肝深度学习,而非一上来就搬出像Tensorflow、Keras、Caffe等这种“黑盒子”。
本篇文章主要是讲解感知器,同时也是为后面神经网络的内容打好基础,该文主要包括以下三个部分的内容:
我们不妨先从其表面来初步理解一下感知器吧。
“感知器,感知器”,既然它是带有一个“器”字,那么我们不妨将其看做一个“机器”?
既然是个机器,那么它能必然能代替人工来实现一些我们所需要的功能,且我们能够人为的提交某些“东西”给它,而它同时也能根据我们所提供的“东西”给我们一些反馈。
是这样的嘛???
没毛病,兄dei,就是这样的。
感知器它能接收多个输入信号,并且根据这多个信号输出一个信号。这里的输入信号可以理解成我们给感知器提供的一些“东西”,或者说是数据(相信有阅读过Taoye的手撕机器学习系列文章的读者都能轻松明白吧),而输出信号则可以理解成经过感知器这个“机器”处理过后所得到的结果。
在上图感知器中,其接收了两个输入信号,并通过处理过后输出一个信号。其中表示权重,权重越高,体现的是信号的重要性就越高,而每个圆圈代表一个“节点”或者说是“神经元”。输入信号被输入到神经元之后,会分别乘以对应的权重,并求和得到,假如这个总和超过某个界限的时候,此时输出信号,否则输出信号,这里的界限我们也可以理解成阈值或是门槛,体现的是神经元被激活的难易程度。
用数学表达上述过程如下:
为了方便表示,我们一般将阈值拿到左边,并替换成,此时表示的就是偏置,且。我们可以发现,无论我们的感知器怎么对输入信号进行处理,此时的偏置前的系数都等于1。
所以,我们可以在提供输入信号的同时,另外添加一个恒定不变的信号1,该信号用于处理偏置,且并不会对整个感知器的处理造成任何的影响,处理结果如下:
通过比较和0的大小,我们可以来决定输出信号的值。
感知器能实现的功能有很多,不仅可以实现简单的逻辑电路(布尔运算),还可以拟合出任何的线性函数,任何线性分类或是线性回归问题都可以通过感知器来解决。
前面我们在手撕机器学习的过程中,详细讲解了线性回归和逻辑回归,其大致过程和原理其实就相当于一个感知器,只不过那个时候我们并没有把感知器的概念直接抛出来而已。
下面我们可以通过感知器来实现一下基本的逻辑电路问题
我们不妨先看看与门的实现。
与门是有两个输入和一个输出的门电路,由排列组合的相关知识我们不难知道,两个输入信号说明总共有四种输入情况,且对于门电路来讲,只有当两个输入都等于1的时候,此时的输出信号才能等于1,其他输入情况的值都等于0。
与门电路的“真值表”如下所示:
为了更直观的了解该数据的分布,我们不妨先对这四个数据进行可视化:
可以发现,此时的四个数据完全分隔两边,我们可以用一条直线来将其进行分类。据上述感知器的判别过程,我们可以知道,主要是要决定具体的值来构建一个线性模型,根据这个线性模型计算出来的值来最终判别数据的分类。
我们可以知道,实际上,满足这样条件的参数的选择有无数多个。比如,我们令,此时的分类模型为:
随之,我们将与门的四组数据x_data = [[0, 0], [1, 0], [0, 1], [1, 1]]
和y_label = [0, 0, 0, 1]
代入到上述模型中,可以发现模型得到的结果与其对应的标签值完全一致,即分类完全正确。相关实现代码和分类结果如下:
我们可以发现,该模型的分类完全正确。
不过需要注意一点的是:该感知器模型的目的仅仅是将数据分类正确,并没有像SVM那样实现间隔最大化的需求。
为了方便读者自行运行程序,这里给出上述过程的完整的代码:
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 与门数据的可视化
Parameters:
x_data:数据的属性特征
y_label:数据属性特征所对应的标签
w_1,w_2:权重
b:偏执
"""
def show_result(x_data, y_label, w_1, w_2, b):
plt.scatter(x_data[:, 0], x_data[:, 1], c = y_label, cmap = plt.cm.copper, linewidths = 10)
line_x_1 = np.linspace(0, 1.2, 100)
line_x_2 = (-b - w_1 * line_x_1) / w_2
plt.plot(line_x_1, line_x_2)
plt.show()
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 阶跃函数
"""
def out(in_data):
return 0 if in_data < 0 else 1
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 模型计算结果
"""
def model(x_1, x_2, w_1, w_2, b):
return w_1 * x_1 + w_2 * x_2 + b
if __name__ == "__main__":
x_data = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
y_label = np.array([0, 0, 0, 1])
for item in x_data:
model_result = model(item[0], item[1], 0.6, 0.6, -1.1)
out_result = out(model_result)
print('%d and %d = %d' % (item[0], item[1], out_result))
show_result(x_data, y_label, 0.6, 0.6, -1.1)
上述就是一个解决与门问题感知器的全部过程。
不过,细心的读者应该会发现,我们上述感知器的参数是自己人为定义的,从而强行达到对数据进行分类的目的,而非通过数据训练而来。
为此,我们有必要讲解一下感知器模型的训练过程。
哎,其实这个可以说是老话长谈了,之前我们在手撕机器学习算法的时候,都有n次接触过这个问题了,无非就是得到目标优化函数,然后通过梯度下降算法或者其他优化算法来更新模型参数,最后通过训练出来的参数来检验模型的效果。
大体上就是这样,为了照顾下新来的读者,我们再来回顾下这个过程(熟悉的读者可直接跳过这个部分的内容)
对于单个数据样本来讲,前面我们不是得到了感知器模型的计算结果了嘛,即:
注意:上述的参数并非是最终结果,而是在一开始我们需要对它们进行初始化,然后通过不断的迭代更新,最终获得满足我们需求的参数值。
这里,我们用表示模型的计算结果,表示数据的真实标签,为了通过数学化来衡量这两者之间的差距,我们可以使用两者差的平方的来表示两者之间差距,即:
以上是单个样本的误差,而我们知道,在训练数据中,样本是有多个的,为此我们需要综合所有样本的误差来体现模型对整体的分类效果,我们不妨令整体误差为,则:
以上就是我们最终待优化的目标函数,我们现在目的就是得到参数的值,使得该目标函数的误差值达到最小。为此,我们需要分别对和求偏导,求解过程如下:
同理,我们对求偏导,得到如下结果:
求得偏导结果之后,我们需要利用梯度下降算法对参数进行更新迭代,具体更新过程如下:
至于为什么以这种方式更新参数,这里Taoye就不再赘述了,不解的读者可以看看前面写过的手撕机器学习系列文章。
了解了参数的更新方式之后,我们就可以编写程序来对数据进行迭代了,从而得到一个满足实际需求的最终参数,首先定义out_result
方法用于输出模型的计算结果,这里用到了一个小技巧:y = result > 0
用于判断每个样本通过感知机处理后的布尔值,大于0返回true,小于0返回false;此后,y.astype(np.int)
用于将布尔值重新转化为int类型,且true将转换成1,而false转化成0,读者可自行实现该过程,out_result
的具体代码如下:
随后,定义train
方法用于训练参数,训练过程如上数学表达所示,即通过梯度下降法来不断迭代更新参数,从而使得损失函数达到最小,该train
方法的具体代码如下所示:
运行分类结果如下:
可以发现,总共迭代了10次,最终训练出的参数值为,即得到的模型为:
将与门的四种数据代入到上述感知器模型中,并与真实标签比较可以发现分类完全正确,即模型训练成功。这里还是需要注意一点:该感知器模型的目的仅仅是将数据分类正确,并没有像SVM那样实现间隔最大化的需求。
与门实现的完整代码如下:
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 与门数据的可视化
Parameters:
x_data:数据的属性特征
y_label:数据属性特征所对应的标签
w_1,w_2:权重
b:偏执
"""
def show_result(x_data, y_label, w_1, w_2, b):
plt.scatter(x_data[:, 0], x_data[:, 1], c = y_label, cmap = plt.cm.copper, linewidths = 10)
line_x_1 = np.linspace(0, 1.2, 100)
line_x_2 = (-b - w_1 * line_x_1) / w_2
plt.plot(line_x_1, line_x_2)
plt.show()
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 感知器处理数据
Parameters:
x_data:数据的属性特征
w:权重向量
b:偏执
Return:感知器处理之后的结果,为一个向量形式
"""
def out_result(x_data, w, b):
result = np.matmul(x_data, np.mat(w).T) + b
y = result > 0
return y.astype(np.int)
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 更新迭代w、b参数
Parameters:
x_data:数据的属性特征
y_label: 数据标签
max_iter: 最大迭代次数
learning_rate: 学习率
w: 权重参数
b:偏置参数
Return:训练完成之后最终的w,b参数
"""
def train(x_data, y_label, max_iter, learning_rate, w, b):
for i in range(max_iter):
result = out_result(x_data, w, b)
delta = np.mat(y_label).T - result
w = (w + (learning_rate * np.matmul(x_data.T, delta)).T)
b += (learning_rate * delta).sum()
return w, b
if __name__ == "__main__":
x_data = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
y_label = np.array([0, 0, 0, 1])
w, b = train(x_data, y_label, 10, 0.1, np.array([1, 1]), 0)
print(w, b)
show_result(x_data, y_label, w[0, 0], w[0, 1], b)
以上就是感知器实现与门的全部过程了,读者可根据上述的思路过程自己尝试实现一下。
同理,我们可以实现逻辑电路中的与非门和或门,实现原理同上,模型训练过程也和与门一模一样,只不过给模型传递的数据有所不同,也就是说只需要更换数据即可实现与非门和或门,这里就不再过多占篇幅讲解了,具体结果如下:
可以发现,感知器训练之后同样可以实现与非门和或门。
至此,我们已经知道使用感知器可以表示与门、与非门、或门的逻辑电路。这里重要的一点是:与门、与非门、或门的感知器构造是一样的。实际上,3个门电路只有参数的值(权重和阈值)不同。也就是说,相同构造的感知机,只需通过适当地调整参数的值,就可以像“变色龙演员”表演不同的角色一样,变身为与门、与非门、或门。——摘自《深度学习入门:基于Python的理论与实现》
既然与门、与非门、或门的逻辑电路,感知器都能够实现,那么我们接下来考虑一下异或门。
异或门也被称作逻辑亦或电路,其两个输入信号只有一方为1,另一方为0的时候,输出信号才能为1,亦或门所对应的真值表如下:
我们不妨对亦或门的数据进行可视化,来观察其分布情况,可视化代码和结果如下:
通过观察可以发现,无论怎么操作,我们都无法通过一条直线将两类数据分割开来。也就是说,我们无法通过上述所分析得到的感知器模型直接运用在异或门上。
感知机的局限性就在于它只能表示由一条直线分割的空间,由直线分割而形成的空间我们称之为线性空间。但如果我们把“线性”这个限制条件去掉,我们就可以完美对异或门进行分类了,比如下面这种分类方法:
通过这种曲线分割而形成的空间我们称之为非线性空间,线性和非线性可以说是机器学习领域中的老熟人了。以上这种方式解决线性不可分问题是非常常见的,不过既然我们已经进入了深度学习领域,我们需要尽可能用“准深度学习”的方式来解决这个问题。
对此,我们进入本篇文章的第三节:基于两层感知器完美解决异或问题
虽然说,上面所介绍的感知器并不能解决异或问题,但是我们也无需多么失望。
感知器的强大之处在于能够实现感知器之间的叠加,也就是多层感知器,这一点对于很多问题来讲是非常有帮助的。
至于什么是感知器之间的叠加,我们暂且不要去思考这个问题,我们先以逻辑电路的角度去思考异或问题。
思路参考于:《深度学习入门:基于Python的理论与实现》
以上是与门、与非门、或门、异或门的真值表,而且我们已经讲解了如何通过感知器完美解决前三者的问题,并用代码加以实现了,然而亦或问题我们依然无法按照上述方式解决。
我们不妨换个思路来解决下亦或问题,能不能通过与门、与非门、或门中的两者甚至三者的组合形式来解决亦或问题呢???即我们现在想要实现的需求是这样的:
上图中,左子图表示的是与门、与非门、或门的符号形式,右图则表示的是目标实现的异或问题。我们能不能将左图的“三门”填入到右图中的“?”框内,从而使得最终的输出满足亦或门的结果呢???
====================读者思考分界线=====================
OK,读者短暂的思考过后,想必都已经有了答案。再不济,通过排列组合也就只有六种填入情况,总能得到满足上述题目的要求。
其实,我们可以通过如下方式来实现异或问题,即从上至下,从左至右,“?”中依次填入与非门、或门、与门即可。具体如下:
保险起见,我们不妨来确认一下如此一来是否真的就能解决异或问题。假设表示的是初始输入信号,则:
通过输入的四组信号,我们可以得到如下结果:
事实证明,我们上述模型的最终输出和异或门的输出结果如出一辙,也就是说,我们完全是可以通过与门、与非门、或门三者的组合来实现异或门的。
而且我们上面都已经实现了与门、与非门、或门,基于此,我们就能很简单实现异或门,其实现过程如下:
上述过程中亦或门的演示结果如下:
异或门问题解决的完整代码:
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 数据的可视化
Parameters:
x_data:数据的属性特征
y_label:数据属性特征所对应的标签
w_1,w_2:权重
b:偏执
"""
def show_result(x_data, y_label, w_1, w_2, b):
plt.scatter(x_data[:, 0], x_data[:, 1], c = y_label, cmap = plt.cm.copper, linewidths = 10)
line_x_1 = np.linspace(0, 1.2, 100)
line_x_2 = (-b - w_1 * line_x_1) / w_2
plt.plot(line_x_1, line_x_2)
plt.show()
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 感知器处理数据
Parameters:
x_data:数据的属性特征
w:权重向量
b:偏执
Return:感知器处理之后的结果,为一个向量形式
"""
def out_result(x_data, w, b):
result = np.matmul(x_data, np.mat(w).T) + b
y = result > 0
return y.astype(np.int)
"""
Author: Taoye
微信公众号: 玩世不恭的Coder
Explain: 更新迭代w、b参数
Parameters:
x_data:数据的属性特征
y_label: 数据标签
max_iter: 最大迭代次数
learning_rate: 学习率
w: 权重参数
b:偏置参数
Return:训练完成之后最终的w,b参数
"""
def train(x_data, y_label, max_iter, learning_rate, w, b):
for i in range(max_iter):
result = out_result(x_data, w, b)
delta = np.mat(y_label).T - result
w = (w + (learning_rate * np.matmul(x_data.T, delta)).T)
b += (learning_rate * delta).sum()
return w, b
if __name__ == "__main__":
x_data = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
and_label = np.array([0, 0, 0, 1]) # 与门的输出信号
no_and_label = np.array([1, 1, 1, 0]) # 与非门的输出信号
or_label = np.array([0, 1, 1, 1]) # 或门的输出信号
w_1, b_1 = train(x_data, and_label, 10, 0.1, np.array([0, 0]), 0) # 感知器训练出与门的参数集
w_2, b_2 = train(x_data, no_and_label, 10, 0.1, np.array([0, 0]), 0) # 感知器训练出与非门的参数集
w_3, b_3 = train(x_data, or_label, 10, 0.1, np.array([0, 0]), 0) # 感知器训练出或门的参数集
no_and_predict = out_result(x_data, w_2, b_2) # 与非门感知器模型的结果
or_predict = out_result(x_data, w_3, b_3) # 或门感知器模型的结果
xor_predict = out_result(np.concatenate((no_and_predict, or_predict), axis = 1), w_1, b_1) # 异或门感知器模型的结果
xor_label = np.array([0, 1, 1, 0])
for index in range(xor_label.size): # 多层感知器解决亦或问题的验证
print("%d 亦或 %d = %d, " % (x_data[index, 0], x_data[index, 1], xor_predict[index]),
"%d == %d?, " % (xor_label[index], xor_predict[index]),
xor_label[index] == xor_predict[index])
感知器算是一种非常简单的算法了,相信阅读至此的读者都能很快深入理解它的构造。虽然简单,但它却是我们步入“神经网络殿堂”的阶梯,或者说是基础,这也是为什么Taoye将感知器作为深度学习开门篇的原因。
对这篇文章做个简短的总结吧:
我们首先介绍了什么是感知器,直观上看到了感知器的基本结构以及能做些什么。其次,通过感知器详细讲解了与门的实现原理,并通过数学公式化(梯度下降算法)来剖析了参数的训练过程,之后以代码形式实现了与门的逻辑电路,最终同理得到与非门和或门的实现结果。此外,在第二节的最后,我们还抛出了单层感知器无法解决亦或门的问题,从而为第三节提供“引子”。最后,我们发现其实可以通过逻辑电路组合的形式来实现异或门,在模型结构中,也就是以一种多层感知器来解决的亦或问题,最终通过代码来验证了模型的真实有效性。
以上就是本篇文章的全部内容了。
还是挺愧疚的,最近在忙其他事情,文章托更有十天了吧 (ノへ ̄、)
不过嘛,Taoye还算是挺良心的吧???
还算是讲码德的吧???给大家把感知器安排的明明白白。
后面就要正式接触到神经网络了,神经网络大体上和上述详解的感知器差不多,只不过激活函数有所不同,上述感知器用到的激活函数是阶跃函数,而在神经网络中使用到的激活函数则需要根据实际问题来进行不同的定义。此外,在神经网络中还做了一些其他的拓展。
详细内容,请见的下篇分解。
我是Taoye,爱专研,爱分享,热衷于各种技术,学习之余喜欢下象棋、听音乐、聊动漫,希望借此一亩三分地记录自己的成长过程以及生活点滴,也希望能结实更多志同道合的圈内朋友,更多内容欢迎来访微信公主号:玩世不恭的Coder。
马上就要与2021年交接了,年终总结也是时候开始准备起来了。
我们下期再见,拜拜~~
参考资料:
[1] 《深度学习入门:基于Python的理论与实现》:斋藤康毅 人民邮电出版社
[2] 零基础入门深度学习- 感知器:https://www.zybuluo.com/hanbingtao/note/433855
推荐阅读
《Machine Learning in Action》—— Taoye给你讲讲Logistic回归是咋回事
《Machine Learning in Action》—— 浅谈线性回归的那些事
《Machine Learning in Action》—— 白话贝叶斯,“恰瓜群众”应该恰好瓜还是恰坏瓜
《Machine Learning in Action》—— 女同学问Taoye,KNN应该怎么玩才能通关
《Machine Learning in Action》—— 懂的都懂,不懂的也能懂。非线性支持向量机
《Machine Learning in Action》—— hao朋友,快来玩啊,决策树呦
《Machine Learning in Action》—— Taoye给你讲讲决策树到底是支什么“鬼”
《Machine Learning in Action》—— 剖析支持向量机,优化SMO
《Machine Learning in Action》—— 剖析支持向量机,单手狂撕线性SVM