基础 神经网络的基础就是感知器,也就是MP结构,它包含多个输入和一个输出,对于感知器,输出的是一个常量而输入的是一个向量,所以他的计算应该是在这样的:
易错点1:对每一个节点来说,w都是一个向量,b只是一个常数,如果将某一层的w和b组合起来之后,得到的就是w矩阵和b向量,这两个参数用于反向传播。
在前馈神经网络进行前向传播的时候最关键的步骤就是梯度的记录,我们在计算损失函数关于神经网络输出值的梯度的时候,得到的是个向量,也就是下面的式子:
其中的$\hat y$是神经网络的期望输出,一般是专家系统或者人工评定的标签向量(标签向量是只有一个元素为1的向量,表示某一类别)。
前向传播的过程中,我们需要将层的每个节点的输出组合成一个向量用作下一层的输入,在这个过程中会有维度的损失,比如输入维度是3输出维度是6,那么中间提升的三个维度在计算梯度时就需要按向量求导,这里需要复习一下几种导数:
当输出是向量,输入是常数时,就需要将函数内的向量依次求导,如果f=wx+b的话,w一定为向量,那么导数就是w 其中$f_n$表示f的各个基上的分量。最后导完应该为一个矩阵(Hessian Matrix)。
当输入是向量,输出是常数时,系数一定为一个向量,这样两向量求内积才可以得到常数。 输出关于bias的导数一定是一个常数,在反向传播时无法使用常数进行推导,所以我们保存节点的梯度的时候,一定要乘以激活函数的导数值,这样在之后的反向传播可以少关注一步,同时训练bias时需要提取一层所有的偏置,让他变成一个向量,这样就可以乘上一层的梯度得到一个向量了。向量按顺序的分量就是不同节点的梯度,使用相应的学习方法就可以了。
我们随便使用一个全连接网络,我们让$w_{ij}$表示第i层第j个单元的权重向量,$b_{ij}$同理不过是常数,下面写出伪代码
1 2 3 4 5 6 7 8 9 10 #define Net 很多层组合起来的网络对象 vector temp = 输入 vector nextinput = 空 for line in Net if nextinput != 空 then temp = nextinput for neural in line nextinput 添加 neural.forward(temp) end end end
这样我们就完成了每一层的前向传播,其中每个节点的forward都是已经写好的,计算公式为前面所述的w和x的内积+bias。
易错点2:小心重复计算梯度
前向传播的过程时需要保存梯度,在训练的时候是需要按层进行训练,所以务必在保存时就乘以激活函数的导数值 最后一个至关重要的点就是别怕麻烦,梯度计算需要细心,避免算错,下面使用i
表示层数,j
表示所在层的第几个元素,o
为输出,根据他的角标是层数还是第几个元素决定它是层的输出
还是节点的输出
,I
表示层的输入
这里的t为预期输出向量的某个值,输出层的元素数与预期值的维相同,所以可以进行加减法,得到关于不同节点的梯度,结果为一个数。
我们得到不同节点关于损失层的梯度后,我们可以把$\delta$组合成向量。
计算完输出层,我们需要计算隐含层,隐含层的$\delta$需要更新,且是按照向量的方式更新,具体方式为:
也就是梯度乘以某个节点的输出的输出,这样得到的就是一个向量了。然后使用和输出层一样的更新权重方法就可以了。然后我们更新delta
我们把delta和层内所有的权重向量求内积后乘以节点激活函数的导数,这样求出来的是一个数值,层内所有的数值结合为一个向量后,就可以用于前一层的更新了。
易错点:容易混淆隐含层与输出层梯度的计算
代码 这里使用Java实现以下前馈神经网络的计算过程,如果有需要可以前往我的github下载插件源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 package cn.minloha.NeuralWork;import cn.minloha.Type.Matrix;import cn.minloha.Type.Vector;import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class Neural { private Vector weight; private Double bias; private Double z; private Vector nabla_w; private Double nabla_b; private Vector nabla_input; private final Double lita; private Double sig; private final Function itemFunction = new Function (); public Neural (int inputdim,double lita) { this .lita = lita; List<Double> a = new ArrayList <>(); for (int x = 0 ;x<inputdim;x++){ a.add(0.1 ); } this .weight = new Vector (a); this .bias = 0.1 ; } public Neural (DataClass d,Double lita) { this .weight = d.getWeight(); this .bias = d.getBias(); this .lita = lita; } public void changeDataclass (DataClass dataClass) { this .weight = dataClass.getWeight(); this .bias = dataClass.getBias(); } public Double getZ () ; public Vector getWeight () ; public Double getBias () ; public Vector getNabla () ; public DataClass getDataClass () ; public Double forward (Vector in) { this .z = Vector.Multiplicate(this .weight,in) + this .bias; double k = itemFunction.Sigmoid(z); this .sig = k * (1 -k); this .nabla_w = in; this .nabla_input = this .weight; this .bias = 1.0 ; return k; } public Double backward_w (Vector delta,boolean code,int t,Vector out,Vector in) { double de = 0.0 ; if (code) de = delta.getAsList().get(t) * this .sig; else de = this .sig * Vector.Multiplicate(delta,out); Vector kk = Vector.Multiplicate(de,in); this .weight = Vector.add(this .weight,Vector.Multiplicate(-1 *lita,kk)); return de; } public void backward_b (Vector delta,boolean code,int t,Vector out) { double de = 0.0 ; if (code) de = delta.getAsList().get(t) * this .sig; else de = this .sig * Vector.Multiplicate(delta,out); this .bias -= de; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 package cn.minloha.NeuralWork;import cn.minloha.ModelLoader.ANNmodel;import cn.minloha.ModelLoader.ModelLoad;import cn.minloha.Type.Matrix;import cn.minloha.Type.Vector;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class NetWork { private final Function itemFunction = new Function (); private Vector nabla_outer; private double lita = 0.5 ; private class Linear { private final List<Neural> neuralList = new ArrayList <>(); private Vector out; private Vector input; private Linear (int nums,int inputDimens) { for (int h = 0 ;h<nums;h++){ neuralList.add(new Neural (inputDimens,0.2 )); } } public Vector forward (Vector in) { List<Double> res = new ArrayList <>(); List<Double> nore = new ArrayList <>(); for (Neural nn : this .neuralList){ res.add(nn.forward(in)); nore.add(nn.getZ()); } this .input = in; this .out = new Vector (nore); return new Vector (res); } public Vector backward (Vector delta,boolean lastone) { List<Double> ch = new ArrayList <>(); for (int k = this .neuralList.size() - 1 ;k>=0 ;k--){ Neural nnn = neuralList.get(k); double sb = nnn.backward_w(delta,lastone,k,this .out,this .input); nnn.backward_b(delta,lastone,k,this .out); ch.add(sb); } return new Vector (ch); } } private final List<Linear> net = new ArrayList <>(); public void showNN () { System.out.println("net shape is:" ); for (Linear l : net) System.out.print(l.neuralList.size() + "\t" ); System.out.println(); } public NetWork (int inputDimens,int ...nums) { List<Integer> rk = new ArrayList <>(); rk.add(inputDimens); for (int n : nums) rk.add(n); for (int k = 0 ;k<rk.size()-1 ;k++){ net.add(new Linear (rk.get(k+1 ),rk.get(k))); } } public double forward (Vector input,Vector except) { Vector in = input; for (Linear l : this .net){ in = l.forward(in); } this .nabla_outer = Vector.add(in,Vector.Multiplicate(-1 ,except)); return itemFunction.CovLoss(except,in); } public Vector forward (Vector input) { Vector in = input; for (Linear l : this .net){ in = l.forward(in); } return in; } public void backward () { Vector update = this .nabla_outer; List<Double> kkk = new ArrayList <>(); for (int m = net.size()-1 ; m>=0 ; m--){ boolean lastone = (m == net.size()-1 ); update = net.get(m).backward(update,lastone); } } public void saveModel (String FilePath) throws IOException; public void loadModel (String modelPath) throws IOException; }
在代码实现过程中遇到了一个模型的问题,就是我需要避免训练过多的内容和训练错文件,所以在这个过程中,我需要一个单例类进行传递,也就是说这个类的构造函数是私有的,而构造只能使用类提供的接口方法:
1 2 3 4 5 6 7 8 9 10 11 public class PIPE { private static PIPE instance = new PIPE (); private PIPE () {}; private List<String> pipe = new ArrayList <>(); public static PIPE getInstance () { return instance; } }
进度 学习进度的话,神经网络马上就结课了,学习程度还剩下一点机器学习和强化学习,接下来就要全心去攻克计算机视觉了。至于这个插件我不打算发,但是会开源,有兴趣可以在过几日打开本页面找到下载的github地址,有兴趣也可以点亮star
Github地址:https://github.com/iMinloha/Hamino.git