近年来,人工神经网络在深度学习的推动下获得了关注。 但是什么是人工神经网络,它是由什么构成的?
一起来认识一下感知器。
在本文中,我们将简要介绍一下人工神经网络,然后我们检查单个神经元,最后(也就是编码部分),我们采用最基本的人造神经元版本感知器,并使其 在一个平面上进行分类。但首先,让我介绍一下这个话题。
人工神经网络作为人类大脑的模型
你有没有想过为什么有些任务对于任何人来说都很简单,但对于电脑来说非常困难? 人工神经网络(简称ANN)受到人类中枢神经系统的启发。 就像它们的生物副本一样,ANN的构建依赖于简单的信号处理单元,它们连接在一起形成一个大网格。
神经网络可以做什么?
人工神经网络已成功应用于一些问题领域:
通过识别模式分类数据。 这是这张照片上的一棵树吗?
当测试数据与通常的模式不匹配时,检测异常或新奇事物。 卡车司机是否有可能入睡? 这些地震事件是否显示正常的地面运动或大地震?
处理信号,例如,通过过滤,分离或压缩。
近似目标函数 - 对预测和预测有用。 这场风暴会变成龙卷风吗?
同意,这听起来有点抽象,所以让我们看看一些现实世界的应用。 神经网络可以 - 面部识别,语音识别,手写输入(我的也许不是),文本翻译,玩游戏(通常是棋盘游戏或纸牌游戏)自动驾驶和机器人当然还有更多的东西!
神经网络的拓扑结构
将神经网络的节点编织在一起有多种方式,每种方式都会导致或多或少的复杂行为。 可能最简单的拓扑结构就是前馈网络。 信号只在一个方向上流动; 信号路径中永远不会有任何回路。
通常,ANN具有分层结构。 输入层拾取输入信号并将它们传递到下一层,即所谓的“隐藏”层。 (实际上,神经网络中可能有多个隐藏层。)最后是传递结果的输出层。
神经网络必学
与传统算法不同,神经网络不能被“编程”或“配置”以预定的方式工作。 就像人脑一样,他们必须学习如何完成任务。 粗略地说,有三种学习策略:
1. 监督学习
最简单的方法。 如果存在已知结果的(足够大的)一组测试数据,则可以使用它。 然后学习如下:处理一个数据集。 将输出与已知结果进行比较。 调整网络并重复。 这是我们在这里使用的学习策略。
2. 无监督学习
如果没有可用的测试数据,以及是否可以从期望的行为中推导出某种成本函数,那么这很有用。 成本函数告诉神经网络它离目标多远。 网络随后可以在处理实际数据时随时调整其参数。
3. 强化学习
“胡萝卜和棒”的方法。 如果神经网络产生连续动作,那么可以使用本方法。 跟着你鼻子前面的胡萝卜走! 如果你走错了路 - “棒!”。 随着时间的推移,网络学习偏好正确的行为并避免错误行为。
好的,现在我们对人工神经网络的性质有了一些了解,但是他们的结构究竟是什么? 如果我们打开封面并在窥探其内,我们会看到什么?
神经元:神经网络的基石
任何人造神经网络的基本成分都是人造神经元。 它们不仅以它们的生物对应物命名,而且还以大脑中神经元的行为为模型。
生物与技术
就像一个生物神经元有树突来接收信号,一个细胞体来处理它们,一个轴突发送信号到其他神经元一样,人造神经元有许多输入通道,一个处理阶段和一个输出可以扇出 到多个其他人造神经元。
人造神经元内
让我们进一步放大。 神经元如何处理其输入? 您可能会惊讶地发现神经元内部的计算有多简单。 我们可以确定三个处理步骤:
1.每个输入被放大或缩小
当一个信号进入时,它会乘以一个分配给这个特定输入的权重值。 也就是说,如果一个神经元有三个输入,那么它有三个可以单独调整的权重。 在学习阶段,神经网络可以根据最后测试结果的错误来调整权重。
2.所有信号加和
在接下来的步骤中,修改后的输入信号总计为单个值。 在这一步中,偏移量也被添加到总和中。 这个偏移量被称为偏差。 神经网络也在学习阶段调整偏差。
这是魔术发生的地方! 一开始,所有神经元都有随机权和随机偏差。 在每次学习迭代之后,权重和偏差逐渐偏移,以便下一个结果更接近期望的输出。 这样,神经网络逐渐走向期望模式被“学习”的状态。
3.激活
最后,神经元计算的结果变成输出信号。 这通过将结果馈送到激活功能(也称为传递函数)来完成。
感知器
激活函数的最基本形式是一个简单的二元函数,它只有两个可能的结果。
注:尽管看起来很简单,但该函数有一个相当复杂的名称:单位阶跃函数。 如果输入为正或零,则此函数返回1,对于任何负输入,此函数返回0。 一个神经元的激活函数就是这样的函数,称为感知器。
单个感知器能做什么有用的工作吗?
如果你仔细想一想,看起来好像感知器为很少的输出消耗了大量的信息 - 只有0或者1。这怎么会有用呢?
确实有一类问题可以用感知器来解决。 考虑输入矢量作为点的坐标。 对于具有n个元素的矢量,这个点将存在于一个n维空间中。 为了让讲解(和下面的代码)更容易,让我们假设在一个像纸一样的二维平面,即n=2。
进一步考虑我们在这个平面上画出许多随机点,并且我们通过在纸上划一条直线将它们分成两组:在一个平面上画出许多随机点,然后通过在这个平面上划一条直线将它们分成两组:
这条线将点分为两组,一组在上面,一组在下面。 (这两组被称为线性可分。)
一个单独的感知器,就像它看起来那样简单而且简单,能够知道这条线是什么,当它完成学习时,它可以知道给定点是高于还是低于该线。
想象一下:单个感知器已经可以学习如何对点进行分类!
让我们跳入编码,看看如何。
代码:分类点的感知器
输入
除了一些标准库之外,我们只需要一个小型自定义库来将感知器的输出绘制到PNG上。
感知器
首先我们定义感知器。 一个新的感知器使用在训练过程中将被修改的随机权重和偏差。 感知器执行两项任务:
处理输入信号
按照“训练者”的指示调整输入砝码。
## 我们的感知器是一个简单的结构,可以保存输入权重和偏差。
typePerceptronstruct{
weights []float32
biasfloat32
}
## 这是单位阶跃函数
func(p*Perceptron) heaviside(ffloat32)int32{
return
}
return1
}
## 用n个输入创建一个新的感知器。权重和偏差用-1和1之间的随机值进行初始化。
funcNewPerceptron(nint32)*Perceptron{
variint32
w:=make([]float32, n, n)
fori =; i < n; i++{
w[i] = rand.Float32()*2-1
}
return&Perceptron{
weights: w,
bias: rand.Float32()*2-1,
}
}
## 过程实现感知器的核心功能。它权衡输入信号,将它们相加,增加偏差,并通过单位阶跃函数运行结果。(返回值可能是一个布尔值,但是属于int32类型,所以我们可以直接使用该值来调整感知器)
func(p*Perceptron) Process(inputs []int32)int32{
sum:=p.bias
fori, input:=rangeinputs {
sum+=float32(input)*p.weights[i]
}
returnp.heaviside(sum)
}
## 在学习阶段,感知器根据感知器的答案与正确答案的差异来调整权重和偏差。
func(p*Perceptron) Adjust(inputs []int32, deltaint32, learningRatefloat32) {
fori, input:=rangeinputs {
p.weights[i]+=float32(input)*float32(delta)*learningRate
}
p.bias+=float32(delta)*learningRate
}
训练
我们排除分类线垂直的情况。 我们可以将线条指定为线性函数方程:
参数a指定线的梯度(即线的陡峭程度),b设置偏移量。
通过这种方式描述线条,检查给定的点是在线之上还是之下变得非常容易。 对于一个点(x,y),如果y的值大于f(x)的结果,那么(x,y)在该线的上方。
示例如下:
a和b指定描述分隔线的线性函数; 详情见下文。 它们是在全局层面定义的,因为我们在几个地方需要它们,而且我不想随意地混淆参数列表。
## a和b指定描述分隔线的线性函数; 详情见下文。它们是在全局层面定义的,因为我们在几个地方需要它们,而且我不想随意地混淆参数列表。
var(
a, bint32
)
## 此功能描述分隔线。
funcf(xint32)int32{
returna*x+b
}
## 如果点(x,y)高于线y = ax + b,函数“isAboveLine”返回1,否则为0。这是老师们的解决方案手册。
funcisAboveLine(point []int32, ffunc(int32)int32)int32{
x:=point[]
y:=point[1]
ify > f(x) {
return1
}
return
}
## 函数”train”是我们的训练师。训练师生成随机测试点并将其馈送给感知器。然后,训练师将答案与“解决方案手册”中的解答进行比较,并告知感知器它离开的距离.
functrain(p*Perceptron, itersint, ratefloat32) {
fori:=; i < iters; i++{
## 在-100和100之间生成一个随机点。
point:=[]int32{
rand.Int31n(201)-101,
rand.Int31n(201)-101,
}
## 将该点送入感知器并评估结果。
actual:=p.Process(point)
expected:=isAboveLine(point, f)
delta:=expected-actual
## 让感知器相应地调整其内部值。
p.Adjust(point, delta, rate)
}
}
开演时间!
现在是时候看看感知器如何完成任务了。我们再次抛出随机点,但这次没有训练师的反馈。感知器会将每一点正确分类吗?
这是我们的测试函数。它返回正确答案的数量。
funcverify(p*Perceptron)int32{
varcorrectAnswersint32=
创建一个新的绘图画布。 x和y的范围都从-100到100。
c:=draw.NewCanvas()
fori:=; i
在-100到100产生一个随机点。
point:=[]int32{
rand.Int31n(201)-101,
rand.Int31n(201)-101,
}
将该点送入感知器并评估结果。
result:=p.Process(point)
ifresult==isAboveLine(point, f) {
correctAnswers+=1
}
颜色表明感知器回答“是高于”还是“低于”。
c.DrawPoint(point[], point[1], result==1)
}
画分离线: y = ax + b.
c.DrawLinearFunction(a, b)
将图片保存./result.png.
c.Save()
returncorrectAnswers
}
主函数Main: 建立、训练和测试感知器。
funcmain() {
设置线路参数。 a(线的斜率)可以在-5和5之间变化,并且b(偏移量)在-50和50之间变化。
rand.Seed(time.Now().UnixNano())
a = rand.Int31n(11)-6
b = rand.Int31n(101)-51
创建一个具备两个输入(一个为x一个为y)的感知器。
p:=NewPerceptron(2)
开始学习。
iterations:=1000
varlearningRatefloat32=0.1//Allowedrange:< learning rate
尝试用这些参数来训练!
train(p, iterations, learningRate)
接下来感知器开始准备测试。
successRate:=verify(p)
fmt.Printf("%d%% of the answers werecorrect.\n",successRate)
}
领取专属 10元无门槛券
私享最新 技术干货