主要学习的是误差反向传播算法和实现,总的来说在算法推导上卡的好久,卡的最久的是数学hh,上学期没学线代,以及没学习高数下,导致许多数学证明看的有些吃力,基本情况就像是“我看懂了这里要求导,以及这个求导结果怎么用,但是怎么算这个的导数我不会算”,这很大程度地影响了我的阅读的连续性,让我看着很难受,不过在Gemini之神的帮助下还是逐渐理解了。
此处贴出ai对于Softmax with loss层的反向传播算法的数学推导,看到最后只觉得惊为天人,再次领略到了数学的简洁美
数学推导简述
如果你想知道这个 $y_k - t_k$ 是怎么来的,可以看这三个步骤:
第一步:计算 Softmax 的导数
- 当 $i=j$ 时:$\frac{\partial y_i}{\partial a_i} = y_i(1-y_i)$
- 当 $i \neq j$ 时:$\frac{\partial y_i}{\partial a_j} = -y_i y_j$
第二步:计算 Loss 对 $y$ 的导数
- $\frac{\partial L}{\partial y_i} = -\frac{t_i}{y_i}$
第三步:链式法则合并
对第 $j$ 个输入 $a_j$ 求导:
$$\frac{\partial L}{\partial a_j} = \sum_{i} \frac{\partial L}{\partial y_i} \frac{\partial y_i}{\partial a_j}$$
代入上面的结果:
$$\frac{\partial L}{\partial a_j} = \left( -\frac{t_j}{y_j} \cdot y_j(1-y_j) \right) + \sum_{i \neq j} \left( -\frac{t_i}{y_i} \cdot (-y_i y_j) \right)$$
简化后:
$$\frac{\partial L}{\partial a_j} = -t_j + t_j y_j + \sum_{i \neq j} t_i y_j$$
$$\frac{\partial L}{\partial a_j} = y_j (\sum_{i} t_i) - t_j$$
因为 one-hot 标签之和 $\sum t_i = 1$,所以:
$$\frac{\partial L}{\partial a_j} = y_j - t_j$$
关于分层实现的神经网络
上次的二层神经网络搭建是一个在搭建网络过程中实现的每层的计算与参数传递,而这次,通过对不同类型的层进行对应类的设计,利用多态实现了for循环进行计算,层自主存储参数,层可根据规划任意叠加等效果,整体上代码更解耦了,这是好的。就是鱼书先教层设计再教分层实现的反向传播,而不先教一遍不考虑层设计的反向传播,导致我读到最后才发现,反向传播实现其实就那一点,更多的设计都在为更好地保存和传递参数而做工作,虽然这是正确的思路,但是我提前不知道的,导致在看本章的时候一脸问号,看了好久都不知道这和上章是什么关系(
以下贴出分层神经网络的实现,我是在之前做的上面进行改动的,个人感觉自己注释还是挺详细的,当时写的时候就是为了理清思路写的注释,希望能对看博客的人有帮助
整体对层的调用
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
| import sys,os sys.path.append(os.pardir) import numpy as np from common.functions import * from common.gradient import numerical_gradient from collections import OrderedDict from common_layer import *
""" 二层训练网络所需内容: Params 1、每一层之间的权重与偏置的存放 Grads 2、每一次之间的权重与偏置的梯度的计算与存储
人为输入:每一层的大小,一个人为设定权重初始化的标准差以减小权重分布 问ai学到,不乘这个标准差,直接获得权重分布的方法叫 朴素随机初始化 如果乘了这个人为固定的标准差,则叫 缩放初始化 除此之外还有较为高级的初始化方法(对weight_init_std进行函数处理) 如针对sigmoid/tanh函数的Xavier/Glorot初始化(理论最优) 或者针对ReLU激活函数的He初始化
类的编写思路: 1、初始化出权重和偏置 2、写正常的预测网络 3、通过预测网络求交叉熵(损失函数) 4、将交叉熵函数传进梯度计算 5、返回梯度 6、额外可加个计算预测的准确率的(不过有损失函数在了也可反应,加上了给人看更好) """ class two_layer_net: def __init__(self,input_size,hide_size,out_size,weight_init_std = 0.01): self.params = {} self.params["w1"] = np.random.randn(input_size,hide_size)*weight_init_std self.params["b1"] = np.zeros(hide_size) self.params["w2"] = weight_init_std * np.random.randn(hide_size,out_size) self.params["b2"] = np.zeros(out_size)
self.layers = OrderedDict() self.layers["Affine1"] = Affine(self.params["w1"],self.params["b1"]) self.layers["ReLu"] = ReLU_Layer() self.layers["Affine2"] = Affine(self.params["w2"],self.params["b2"]) self.last_layer = Softmax_With_Loss() def prediction(self,x): xx = x for layer in self.layers.values(): xx = layer.forward(xx) return xx def loss(self,x,t): y = self.prediction(x) L = self.last_layer.forward(y,t) return L def gradient_caculation(self,x,t): tmp_f = lambda w: self.loss(x,t) grads = {} grads["w1"] = numerical_gradient(tmp_f,self.params["w1"]) grads["w2"] = numerical_gradient(tmp_f,self.params["w2"]) grads["b1"] = numerical_gradient(tmp_f,self.params["b1"]) grads["b2"] = numerical_gradient(tmp_f,self.params["b2"])
return grads def accuracy(self, x, t): y = self.prediction(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy def gradient(self,x,t): self.loss(x,t)
dout = 1 dout = self.last_layer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) grads = {} grads['w1'] = self.layers['Affine1'].dw grads['b1'] = self.layers['Affine1'].db grads['w2'] = self.layers['Affine2'].dw grads['b2'] = self.layers['Affine2'].db
return grads
|
各种层的实现
该说不说,目前虽然将加法,乘法,affine,soft_with_loss层都实现了,但实际上只使用了后三者。
这个的实现基本都是对教材的拙劣模仿,不过有些感觉不能直接修改传入参数的值,就copy了一下再进行计算
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
| import numpy as np import sys,os sys.path.append(os.pardir) from common.functions import *
class MulLayer: def __init__(self): self.x = None self.y = None
def forward(self,x,y): self.x = x self.y = y return x*y def backward(self,dout): dx = dout*self.y dy = dout*self.y return dx,dy class AddLayer: def __init__(self): pass
def forward(self,x,y): return x+y def backward(self,dout): dx = dout*1 dy = dout*1 return dx,dy
class ReLU_Layer: def __init__(self): self.mask = None def forward(self,x): self.mask = (x<=0) out = x.copy() out[self.mask] = 0 return out def backward(self,dout): dx = dout.copy() dx[self.mask] = 0 return dx
class sigmoid_layer: def __init__(self): self.out = None def forward(self,x): out = 1/(1+np.exp(-x)) self.out = out return out def backward(self,dout): dx = dout*self.out*(1-self.out) return dx
class Affine: def __init__(self,w,b): self.w = w self.b = b self.dw = None self.db = None self.x = None
def forward(self,x): self.x = x out = np.dot(x,self.w) + self.b return out def backward(self,dout): dx = np.dot(dout,self.w.T) self.dw = np.dot(self.x.T,dout) self.db = np.sum(dout,axis=0) return dx
class Softmax_With_Loss: def __init__(self): self.loss = None self.y = None self.t = None def forward(self,x,t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y,self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx
|
训练的程序没有修改,就把计算梯度的方法换成了反向传播的方法,故不再贴出
训练效果
图的链接好像老是炸,后续有空可以改改博客,换个和国内链接好的平台部署可能好点?
视频更是别想传了,这里贴出图片,真炸了就炸了
训练过程中损失函数的变化
