bp神经网络实现
1.什么是梯度
以f(x,y) 举例
方向导数:对于多元函数的来说,若其在可微分 ,f沿着单位向量的方向导数是
(极限存在时)
也可表示成, 表示多元函数 在经过点沿向量方向的变化率
梯度:对于点 来说 (为梯度算子),单位向量u沿着梯度指向的方向时,沿方向的求得的方向导数有最大值,沿梯度方向函数值增长最快
为什么沿梯度方向函数增长最快?这里引入证明
若 在点可微分,则函数 沿着 向量的方向导数为
使用拉格朗日中值定理 使得 = =
设是u 的单位向量,
当 e 与梯度同方向的时候方向导数增加最快,与梯度反方向则其方向导数减少最快
2.何为梯度下降算法
为学习率,沿梯度方向时函数增长最快,反之与梯度方向相反函数减少最快所以这里是负号,我们用的函数是误差函数,每次都希望它以最快的速度减少,更新方向与梯度相反所以名字叫梯度下降
其实这里我们可以用一个简单的函数举个例子,我们想要通过误差函数让x到最小误差点的话,可以如下建模
假设我们选择的初始点在 (5,16),位于函数L上,学习率
第一次计算,更新后
第二次计算,更新后
第三次计算,更新后
通过多轮次的迭代,我们可以通过误差函数让x逐渐靠近 x= 1(就是误差最小点,也是目标点)
另一方面,初始态x 的随机初始化和学习率的调整也十分重要
3.为什么要非线性函数作为激活函数
这个时候我们就要引入一个新的概念激活函数,所有的东西并非单一的线性拟合就可以完成,而是需要非线性的去拟合。比如函数。为什么非线性不能解决所有问题呢,比如同或门(XNOR)需要非线性方法解决,下面是同或门的返回值
| (0,0) | (0,1) | (1,0) | (1,1) |
|---|---|---|---|
| true | false | false | true |
如果用(x,y) 表示两个输入,用表示两个输入节点的权重
对于比较简单的与门和非门 通过调节的值,我们可以通过是否成立来拟合,在平面上有直线作为返回true和返回false的分界线,以下是python简单实现的一个与门
def AND(x,y):
input = numpy.array([x,y])
w = numpy.array([0.5,0,5])
b = -0.6
output = numpu.sum(w*input) - b
if(output>0):
return 1
else:
retunr 0
对于同或门,在 平面上,难以找到线性函数将其划分两个输出为True 和False的两个区域,这个时候我们就需要非线性的函数参与让分界线变为非线性的,从而使得它符合这种刁钻的局面,使得分类变得更准确
另一方面,人的神经元直之间也有类似激活函数的物理结构。比如膜电位存在 “阈值” 超过某个值之后神经元的激活不可抑制的,比如它的斜率在0附近突然猛烈增加,这就是它的阈值点,这个点存在一个难以抑制增加趋势,膜电位超过阈值电位之后也会难以抑制的逆转,它的图像也类似于 函数(不同电位于神经元被激发的概率的图)
4.不同层之间的连接
基本上包含输入层,隐藏层,输出层
在这里,我们可以用一些比较简单的方法实现,比如定义一个dense类来表示,这是一种比较好的抽象方式
表示激活后的输出 其求导关系如下
选定误差函数为 这里是目标输出
由于系数可由学习率调整,省略系数2
这里的转置是由于实际上是与原先矩阵大小相同的,同样是大小的,对于向量 取转置才能得到实际需要的矩阵
5 .如何编程实现
这里我们使用pytorch,不使用自动求导进行演示
定义一个Dense类实现链接信息存储
class Dense:
def __init__(self, input_size, output_size, bias=0.0):
self.net = pow(output_size, -0.5) * torch.randn(input_size, output_size)
# 不随机初始化会出现大问题
self.input = None
self.output = None
self.active_output = None
self.dW = None
self.error = None
self.bias = torch.ones((1, output_size)) * bias
def forward(self, input_data):
self.input = input_data
self.output = torch.matmul(self.input, self.net) + self.bias
self.active_output = torch.sigmoid(self.output)
return self.active_output
def cacu_error(self, error):
# error = output - target
self.error = error
prev_error = torch.matmul(self.error, self.net.T)
return prev_error
def backward(self, lr):
s = self.active_output
self.dW = torch.matmul(self.input.T, self.error * s * (1 - s))
self.net -= lr * self.dW
接下来封装一下
class NeuralNet:
def __init__(self):
self.target_data = None
self.input_data = None
self.denses: list[Dense] = []
self.lr = 0.2
def add_dense(self, input_size, output_size):
current_dense = Dense(input_size, output_size)
self.denses.append(current_dense)
def set_data(self, input_data, target_data):
self.input_data = input_data
self.target_data = target_data
def set_lr(self, learning_rate):
self.lr = learning_rate
def forward(self):
fwd = self.input_data
for current_dense in self.denses:
fwd = current_dense.forward(fwd)
def query(self, query_data):
fwd = query_data
for current_dense in self.denses:
fwd = current_dense.forward(fwd)
return fwd
def backward(self):
current_error = (self.denses[-1].active_output - self.target_data)
for idx in range(len(self.denses) - 1, -1, -1):
current_error = self.denses[idx].cacu_error(current_error)
self.denses[idx].backward(self.lr)
使用的话,输入是(1,inputSize) 大小的,不是(inputSize,1),不同人可能习惯不同
network = NeuralNet()
network.addDense(784, 200)
network.addDense(200, 10)
network.setLr(0.2)
for x, y in train_iterator:
network.setData(x, y)
network.forward()
network.backward()