文章作者是我的一位大神学长陈雨学长~,征得学长同意后将文章转载到了我的blog上,特在此感谢学长~
代码在学长的github上:
https://github.com/unnamed2/MDL
欢迎猛击
自己实现的机器学习(2):
2.0 神经元
2.0.0 模型
历史上的某个伟人曾经用计算机里的神经元模拟大脑 试图构建高智能的AI: 虽然人脑中几十亿的神经元数量压倒性的战胜了当时计算机计算速度,但是这个方法被改进演化之后依旧广泛流传:
他完成了这样的计算:其中W,B是矩阵
2.0.1 activation:激活函数
-
Sigmoid
:
Sigmoid(x)=11+e−x
从无穷到概率的映射:
-
ReLU
:
ReLU(x)=max(0,x)
Rectified linear unit 快速稳定的导数
-
Tanh
:无穷到1 的映射:
激活函数可以视为对神经网络本身的输出值是一个进一步的处理,即特征的保留和删除,根据不同的模型,选择不同的激活函数,可以训练得到更好的模型.
线性代数的计算不会改变其线性的特点,比如对于在二维平面上某区间上随机数X来说 如果X服从均匀分布,那么无论X经过怎么样的线性变换,最终仍然会在某区间上服从均匀分布.
激活函数很大 的一个作用就是加入非线性因素来解决线性运算解决不了的问题.
2.1 更为复杂的模型
使用一个即兴的伪代码来说明一下上次的多项式模型:
featrue = InputVar({1,1});//多项式输入一个x
label = InputVar({1,1});//同时输出一个值
A = Parameter({3,1});//要被训练的参数
Z = Polynomial(input,A);//计算多项式的值
Err = SquareError(Z1 - label);//训练使用平方误差函数
对应C++代码也很简单
现在 我们将要建立一个更复杂的模型来做一些更复杂的事情.
例如这样的一个网络模型:
用这个网络分析一个28x28的图片:
input = InputVar({784,1});//输入是784 x 1的矩阵 或者叫向量,这个矩阵是输入的数据
label = InputVar({10,1});//这个是标签数据 如果图片写的是5这个数字那么label[5] = 1.0f;
//其他为0.0f;其余数字同理
W0 = Parameter({30,784});//W0是一个30 x 784的矩阵 ,这个矩阵是将要学习的parameters之一
B0 = Parameter({30,1}); //B0,W1,B1同上
W1 = Parameter({10,15});
B1 = Parameter({10,1});
Z0 = Sigmoid(W0 * input + B0);//隐含层输出 * 是矩阵乘法
Z1 = Sigmoid(W1 * Z0 + B1);//输出层输出 , Z1是我们的函数输出的结果
Err = SquareError(Z1 , label);//平方误差函数
//......
一点一点的来看这个函数:
对于整个的hidden layer里的每个神经元的输出 Zi :(假设hidden layer有M个神经元,input layer有N个神经元,它们的输出是 input0 到 inputN−1 )
这里 wji 是 input layer第i个神经元到 hidden layer第j个神经元的输出.
写成向量式:
这里W是输入层到隐含层的权重矩阵,b是 hidden layer的偏置向量,Z是隐含层输出,同理 output layer的输出W1:
2.2反向传播
对于这种复杂的模型 误差函数对每个 parameter 的偏导数并不是很容易求得 但是一点一点看每一个函数的偏导数:
Z0 = Sigmoid(W0 * input + B0);
这里令
所以有:
把这个写成容易看的简洁的矩阵乘法就是
我们根据链式法则推导出来 Z0 对 W0,B0 的每个参数偏导数的计算方法.这种链式法则同样适用于各个部分,我们从下道上的一个函数一个函数的求他们对输入的偏导数:
正因为求取偏导数的过程 是从结果( err )出发一点点的向前(先是 Z1 ,再 Z0 )传播,和计算 err 的顺序正好相反,所以才叫反向传播.
利用反向传播,我们现在能够求出来 Err 对每一个参数的偏导数值,这样就可以使用梯度下降去更新参数了.