AlexiaChen / AlexiaChen.github.io

My Blog https://github.com/AlexiaChen/AlexiaChen.github.io/issues
87 stars 11 forks source link

神经网络的工作原理和简单的数学推导 #175

Open AlexiaChen opened 1 year ago

AlexiaChen commented 1 year ago

神经网络如何工作

分类器和预测期并无太大差别

分类器其实就是在一个二维坐标系中,用一条直线 $y = kx + b$ 来分割不同类别的对象。这个可以很直观地理解。

如何得到正确的斜率呢?如何才可以改进划分两种不同类别的对象的分界线呢?

训练简单的分类器

其实就是不停地调节斜率这个系数k。调节系数通常为称作-学习率(learning rate)。调节斜率需要适度更新,限制错误样本的影响。

有时候一个分类器不足以求解问题

之前提到的用一条线性分界线来分割目标是很理想的情况了,现实世界复杂多样。很多数据本身不是由单一线性过程支配。解决方法很容易,就是需要多个线性分类器来划分由一条单一直线无法分离的数据。所以人工神经网络就由此诞生了。神经网络是单一线性分类的复杂的扩展。

神经元-大自然的计算机

Pasted image 20230510172826

本质上就是神经元之间互相连接,形成的一种可以处理复杂信息的计算模型。神经元之间有抑制机制。所以人工神经元也有了激活函数这样的抑制神经元输出的机制,比如Sigmoid函数和ReLU函数。Sigmoid在坐标轴上是一个S曲线,正好可以模拟大脑神经元的抑制激活功能

$$ Sigmoid(x) = \frac{1}{1 + e^{-x}} $$

人工神经元之间分层,一层有N个神经元节点,信号是一层一层地通过线性变换进行传递。上一层的每个神经元都与下一层的神经元保持全连接,但是这些连接有强有弱,所以在计算机中,用权重(weight)表示连接的强弱。

比如第二层的某个神经元,接收第一层神经元的信号,可以用数学公式表示如下:

$$ x = w_1a + w_2b + ... w_n*c $$

以上公式中的a,b,c就是上一层神经元的输出信号,输出信号通过连接中的各自的权重,作为下一个神经元节点的输入信号,这个输入信号x,再经过Sigmoid函数的抑制激活作用,作为该神经元的输出信号:

$$ Sigmoid(x) $$

通过以上,你理解了上一层所有神经元节点对下一层的某个神经元节点的输入机制,那么你后面自然就理解,在下一层所有节点的输入的表示。

$$ x1 = w{11}a + w_{21}b + ... + w_{n1}c $$ $$ x2 = w{12}a + w{22}*b + ... + w{n2}*c
$$

$$ ....... $$ $$ xn = w{1n}a + w_{2n}b + ... + w_{nn}*c
$$

从上面的全部公式,$x_1$就表示第二层的第一个神经元的输入值,$x_2$就表示第二层的第二个神经元的输入值,以此类推。你会发现,本质上就是输入了一个$(a,b,c, ... )$的高维向量,经过一次线性变换得到一个$(x_1, x_2, ..., x_n)$的输出向量作为第二层神经元的输入。

所以显然就可以把以上的全部公式,优化成向量乘以矩阵的形式,矩阵就是线性变换的具体表示。

$$ y = Wx $$

以上公式,x是输入向量,y是输出向量,W是权重矩阵。

以上例子只举例了两层神经网络,实际上,神经网络是有多层的。就是涉及到多次矩阵的计算。不过显而易见,对于N层的神经网络,就有N - 1次矩阵计算。所以神经网络的计算中,涉及了大量的向量和线性代数的相关计算。

神经网络中,第一层神经元和最后一层神经元分别为输入层和输出层。它们不涉及sigmoid函数的计算。其余为隐藏层。

如何学习

其实学习就是训练,其实就是输入训练数据集,让神经网路学习,如何具体学习呢?把数据集看成有很多$(input,output)$对 的数据表,把input数据向量化以后,变成一个N维向量,输入进第一层神经网络,最后经过一系列线性变换,吐出一个N维向量,当然,输出的N维向量不一定维度跟输入向量一致。这个输出向量再与output数据的向量进行比较。

向量如何比较?最简单就是向量求差,得到一个diff的误差向量$e$,误差向量再通过反向传播,也就是乘以之前权重矩阵的转置矩阵$W^T$,进行反向计算。

$$ error = W^Te $$

最重要地来了,反向传播就是为了为以后的权重做更新,也就是对权重矩阵$W$ 做修改,不停地修改,不停地调整权重到最合适的数值,那么就算学习的过程了。

回顾一下,我们之前提到的误差,误差是怎么来的,无非是神经网络的输出的向量与训练数据集的向量集合做对比,以训练数据集为参考。我们的神经网络要通过一次又一次地与训练数据的向量做对比,调整权重,达到最优。本质就是在一次又一次地对比中,不停地减小这个误差。

为了在一次学习中,寻找最小的误差,就需要用到梯度下降(gradient descent)的思想,找到最小的y值。也就是函数的最低的谷底。但是由于神经网络中的权重,本身是一个复杂的数学函数,影响y的输出的因素太多了,所以我们是在一个高维的空间上,寻找y的最小值,并不是一个简单的二维函数坐标。比如,如果依赖两个参数的函数,其本质就是一个三维空间的图像。以此类推。

Pasted image 20230510155740

如何更新权重

神经网络的输出是一个极其复杂的函数,这个函数有许多参数影响其输出的连接的权重,我们可以使用之前提到的梯度下降法,计算出正确的权重吗?只要我们使用合适的误差函数,这是完全可以的。

因为神经网络的输出,本身不是误差值,我们是用神经网络的输出,与训练数据的目标值作差。这个是最简单的误差。

上面三种都是误差函数。第一个误差函数最简单,但是有可能对误差值求和,正负的值相互抵消,甚至最极端的情况,总和为0,造成错误。第二个误差函数改进了这点,用绝对值,这样就不管正负了,避免了符号影响,但是还记得这样的函数在二维坐标是如何的吗?|x - 5| = y

Pasted image 20230510161029

是一个V型的翻折函数。这种函数,梯度下降,因为是小幅度地改变x的值来接近最低点,遇到这种函数就会反复地在最低点之间来回跳动优化,所以这个函数没有得到广泛使用。而且,即使接近了最小值,斜率也不会变得更小,也有超调的风险。

所以最后一种误差函数最好,比如 $y = (x - 5)^2$

Pasted image 20230510161430

使用误差的平方,可以方便使用代数计算出梯度下降的斜率。误差函数平滑连续,没有间断和跳跃。越接近最小值,梯度越小。意味着使用这个函数调节步长,超调的风险会变得更小。

y是误差。

使用梯度下降,需要计算出误差函数相对于权重的斜率,误差函数的输入是神经网路的输出,所以误差函数是很依赖神经网络的权重的,我们最终要找到最小的y值,所对应的权重矩阵。因为y值是误差,所以把y的符号变成E

$$ y{output} = ComplexWeightMatrixCompute(x{input}) $$

$$ E = (target - y_{output})^2 $$

$$ E = (target - ComplexWeightMatrixCompute(x_{input}))^2 $$

换句话说,我们要研究E对连接权重的改变又多敏感?多敏感就是变化率有多快?这个显而易见可以用微积分的符号来表示:

$$ \frac{dE}{dw_{ij}} $$

或者

$$ \frac{\partial E}{\partial w_{ij}} $$

让我们来优化下误差函数E的公式,因为真实误差,毕竟是对各个误差值求和,所以E的实际公式是:

$$ E = \sum_{n}(t_n - o_n)^2 $$

$o_n$就是神经网络输出层的单个神经元节点的输出值,$t_n$ 就是训练数据的向量的第n维的输出值,然后各部分累加。

$$ \frac{\partial E}{\partial w{ij}} = \frac{\partial}{\partial w{ij}}\sum_{n}(t_n - o_n)^2 $$

注意,在节点n的输出$o_n$ 只取决于连接到这个节点的权重链接,因此,节点k的输出$ok$ 只取决于权重$w{jk}$ , 比如$o5$ 的权重是权重向量$(w{15}, w{25}, w{35}, .. w_{j5})$

$$ \frac{\partial E}{\partial w{jk}} = \frac{\partial}{\partial w{jk}}(t_k - o_k)^2 $$ 通过链式求导法则,

$$ \frac{\partial E}{\partial w_{jk}} = \frac{\partial E}{\partial o_k} \cdot \frac{\partial ok}{\partial w{jk}} $$

$$ \frac{\partial E}{\partial w_{jk}} =-2(t_k - o_k) \cdot \frac{\partial ok}{\partial w{jk}} $$

然后对第二项进行分解,$o_k$是上一层神经元的加权求和,并输入到激活函数而来的。

$$ \frac{\partial E}{\partial w_{jk}} =-2(t_k - ok) \cdot \frac{\partial}{\partial w{jk}}sigmoid(\sumjw{jk} \cdot o_{j}) $$

$o_j$是上一层节点的输出。不是最终层的输出$o_k$

接下来无非就是对sigmoid函数微分,这个有特定的公式了,无需管细节。

$$ \frac{\partial E}{\partial w_{jk}} =-2(t_k - o_k) \cdot sigmoid(\sumjw{jk} \cdot o_{j})(1 - sigmoid(\sumjw{jk} \cdot o{j})) \cdot \frac{\partial}{\partial w{jk}}\sumjw{jk} \cdot o_{j} $$

$$ \frac{\partial E}{\partial w_{jk}} = -2(t_k - o_k) \cdot sigmoid(\sumjw{jk} \cdot o_{j})(1 - sigmoid(\sumjw{jk} \cdot o_{j})) \cdot o_j $$

然后我们把表达式中的常数2去掉,因为我们只对误差的斜率感兴趣,这个常数是什么无关紧要,去掉这个常数可以变得简单

$$ \frac{\partial E}{\partial w_{jk}} = -(t_k - o_k) \cdot sigmoid(\sumjw{jk} \cdot o_{j})(1 - sigmoid(\sumjw{jk} \cdot o_{j})) \cdot o_j $$

目前为止,这个公式是为了优化隐藏层和输出层之间的权重,接下来,我们要为输入层和隐藏层找到类似的误差斜率。我们可以直接用以上的公式来构造输入层和隐藏层的误差斜率公式。

根据上式第一部分$t_k - o_k$的误差,现在要变成隐藏层节点重组的向后传播误差。这个误差记为$e_j$ 注意, 以上的公式前提都是只有一个隐藏层的神经网络。

sigmoid部分可以保持不变,但是内部的求和表达式是前一层,因此求和的范围是所有由权重调节的进入隐藏层节点j的输入,记为$i_j$

最后一部分就是第一层节点的输出$o_i$, 这里碰巧是输入信号

这种方法,利用了问题中对称性,避免了大量的工作。下面就是我们所得到的输入层和隐藏层之前的权重调整

$$ \frac{\partial E}{\partial w_{ij}} = -(e_j) \cdot sigmoid(\sumiw{ij} \cdot o_{i})(1 - sigmoid(\sumiw{ij} \cdot o_{i})) \cdot o_i $$

记住,权重改变的方向与梯度方向相反。我们需要一个学习因子来调节变化,跟线性分类器中的道理一样,学习因子作为避免被错误的训练样本拉得太远的一种方式,也保证权重不会由于持续超调而在最小值之间来回跳动。

$$ (new \quad w{jk}) = (old \quad w{jk}) - \alpha \cdot \frac{\partial E}{\partial w_{jk}} $$

^0e4b33

注意上式,因为权重改变的反向与梯度方向相反,所以需要用旧的权重,减去刚得到的误差斜率。其中$\alpha$就是学习因子,也叫学习率(learning rate)

根据一以上公式,可以写出$\Delta W$ 矩阵是通过前一层输出的行向量,乘以下一层的列向量的值得到的一个权重变化矩阵。

$$ \Delta W{jk} = (new \quad w{jk}) - (old \quad w_{jk}) = \alpha \cdot E_k \cdot O_k(1 - O_k) \cdot O_j^T $$

以上是写成向量形式的共识,前一层输出向量的转置就是一个行向量,乘以下一层的输出列向量。当然,如果线性代数熟悉的话都是知道,m维列向量乘以n维行向量,得到的是一个m*n的矩阵。

最后你发现,sigmod消失了,在上述式子。

更新权重的一个实例

本质上就是按照 [[#^0e4b33]] 这个公式计算的。你会发现更新后的权重的变化量很小,但是权重经过成千上万次学习对比迭代,最终会确定固化下来,达到一种布局状态,这样训练好的神经网络就会生成与训练数据集样本相同的输出。

本质上还是用神经网络这个无比复杂的函数,去逼近拟合训练数据样本的"未知函数"。所以高质量的训练数据样本也很关键。

准备数据,尝试训练