DL-深度神经网络原理推导及PyTorch实现

 

1.前向传播

引用一个网站的图:

具体来说,就是2行代码,图片中的f为激活函数,这里用sigmoid作为激活函数,事实上有很多其它的套路,这里只讲神经网络的数学原理及初级使用,不会做任何深入扩展:

def feedforward(self, a):
        # a:input
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a) + b)
        return a

 


2.反向传播

反向传播主要是前向传播一直传下去,到最最后面的输出层神经元了,然后利用已知数据的标签,和输出成的预测值产生、计算误差,建立成损失函数模型,然后希望损失函数减低,既,求导,利用梯度下降等更新权重参数,以然损失函数往减少的方向去。

可见,有反向传播是从后往前一层层传播的,具体怎么计算更新呢?

1)误差传播的概念

假设现在有100层这样的神经网络,最后第100层显然,就是输出层,我们的目标是学习0-9的数字图片,那么输出层有10个神经元,值为0-1,和为1,表示对应位置的数字的预测概率,如0号神经元0.1,5号神经元0.6,..,最大的概率是5号,如果此时是已经学好了的模型,那么我们就预测说这个数字图片是5了,而问题是一开始并没有学好怎么识别数字,因此关键是学习的过程。

显然,一开始模型是乱输出的,比如图片的真实数字是6,结果输出的预测概率里面最大的是8,如10个输出神经元的输出是:[0.1,0.1,0,0.1,0,0,0,0,0.5,0.2],而训练集给定的真实结果是:[0,0,0,0,0,0,1,0,0,0](为什么这样,因为是标记的百分之百确定的,所有我们知道100%是数字6,其它数字的概率是0%)

显然,[0.1,0.1,0,0.1,0,0,0,0,0.5,0.2]和[0,0,0,0,0,0,1,0,0,0]这两个10维的向量,是可以计算距离的,通常的,直接平方距离被定义成了损失函数记做L,因为理想的时候我们的预测值也应该是输出[0,0,0,0,0,0,1,0,0,0]这种结果,那么这个预测的损失为0,看起来是合理的。

J(W,b,x,y) = frac{1}{2}||a^L-y||_2^2


3.理论

1)输出层

最后的输出层比如上面的第100层,记为L层,中间的1-99层记做  l 根据前向传播输出为a^L,a^l

a^L = sigma(z^L) = sigma(W^La^{L-1} + b^L)

a^l = sigma(z^l) = sigma(W^la^{l-1} + b^l)

2)损失计算函数

J(W,b,x,y) = frac{1}{2}||a^L-y||_2^2 = frac{1}{2}|| sigma(W^La^{L-1} + b^L)-y||_2^2

3)记公共因子

第100层L层:delta^L = frac{partial J(W,b,x,y)}{partial z^L} = (a^L-y)odot sigma^{'}(z^L)

第 0-99层 l 层:

delta^l =frac{partial J(W,b,x,y)}{partial z^l} = frac{partial J(W,b,x,y)}{partial z^L}frac{partial z^L}{partial z^{L-1}}frac{partial z^{L-1}}{partial z^{L-2}}...frac{partial z^{l+1}}{partial z^{l}} \ = frac{partial J(W,b,x,y)}{partial z^{l+1}}frac{partial z^{l+1}}{partial z^{l}} = delta^{l+1}frac{partial z^{l+1}}{partial z^{l}}

公共因子有如下关系

z^{l+1}= W^{l+1}a^{l} + b^{l+1} = W^{l+1}sigma(z^l) + b^{l+1}

delta^{l} = delta^{l+1}frac{partial z^{l+1}}{partial z^{l}} = (W^{l+1})^Tdelta^{l+1}odot sigma^{'}(z^l)

(这一步很重要),我们发现有了第100层的公共因子delta^100,就能得到第99个公共因子delta^{99},一以此类推。

4)输出层的梯度

第100层L:

frac{partial J(W,b,x,y)}{partial W^L} = frac{partial J(W,b,x,y)}{partial z^L}frac{partial z^L}{partial W^L} =delta^L (a^{L-1})^T

frac{partial J(W,b,x,y)}{partial b^L} = frac{partial J(W,b,x,y)}{partial z^L}frac{partial z^L}{partial b^L} =delta^L

第1-99层 l 层:

frac{partial J(W,b,x,y)}{partial W^l} = frac{partial J(W,b,x,y)}{partial z^l} frac{partial z^l}{partial W^l} = delta^{l}(a^{l-1})^T

frac{partial J(W,b,x,y)}{partial b^l} = frac{partial J(W,b,x,y)}{partial z^l} frac{partial z^l}{partial b^l} = delta^{l}

得出这个关系后,每个层的权重更新就可以按照梯度下降等更新了

5)梯度下降更新流程

根据每次更新计算梯度有批量(Batch),小批量(mini-Batch),随机三个变种的梯度下降,小批量用的比较多,每次一定数量均匀起来再更新。

输入: 总层数L,以及各隐藏层与输出层的神经元个数,激活函数,损失函数,迭代步长α,最大迭代次数MAX与停止迭代阈值ϵ,输入的m个训练样本{(x_1,y_1), (x_2,y_2), ..., (x_m,y_m)}

输出:各隐藏层与输出层的线性关系系数矩阵W和偏倚向量b

按照如下参数更新:(单个样本的)

对应的有批量的(取全部样本),小批量的(取随机的小部分样本集),差别只是把求导项由当个变量的更新变成取批量样本的均值做更新。


3.1 激活函数

激活函数保证神经网络模型是非线性的,否则多个神经网络层,最终也是可以表示成线性形式的。

激活函数应该是非线性、几乎处处可导、单调,并尽量避免饱和(为什么?3.5有说明),常见:sigmoid、tanh、ReLU:

 3.2 损失函数

前面的平方损失通常用于回归问题,常见的还有机器学习中的交叉熵,主要用于分类。

交叉熵:

交叉熵有极小值:

意义:当真实概率与预测概率相当,xi=ai时有极小值


3.3 正则化

抵抗过拟合的技巧。


3.4 学习率

应该是接近0的正数。

其它变种,动量项梯度下降,引入动量V:

 


 

3.5 存在的问题

1)梯度消失问题

如果激活函数导数绝对值<1,多次连乘后,误差项delta approx 0,进而导致对W,b的梯度值接近0!不幸的是,上面用的sigmoid就是这样的一个激活函数,不超过3/5层神经网络,上面那样的全连接网络后面梯度会接近0的。

ReLU一定程度上减少梯度消失问题,但不是绝对的。

2)饱和性

x趋于±无穷时,导数=0,x=c的时候存在导数=0。(上面说了要避免饱和性,梯度消失主要就是激活函数导数接近0,而饱和性的函数可能直接就是0,因此要避免这样的饱和性函数)

3)退化问题

神经网络的训练误差和测试误差会随着层数增加而增大,也就是说并不是层次越多越好,多了可能过拟合。解决技巧有残差网络等。

4)局部极小值

神经网络的优化目标不是一个凸优化问题,很可能存在无法预测的局部极小值,陷入其中很可能就无法跳出,造成效果不好。目前无好的解决办法。

5) 鞍点问题

Hessian矩阵不正定,不是局部极小值点,也就是说可能连局部极小值都找不到!。

以上问题都没有很好的解决办法,可见效果好坏通常还与初始迭代位置点优化,所有目前能做的就是多次尝试(暴力)。


4.基于numpy实现

另一个文章:https://blog.csdn.net/jiang425776024/article/details/85040726


5基于sklearn实现

API:https://scikit-learn.org/stable/modules/classes.html#module-sklearn.neural_network

sklearn 中有3个神经网络的实现库(了解就好,现在神经网络都是PyTorch、TensorFlow、caffe更实际)

neural_network.BernoulliRBM([n_components, …]) 伯努利限制玻尔兹曼机(RBM)
neural_network.MLPClassifier([…]) 多层感知机分类器
neural_network.MLPRegressor([…]) 多层感知机回归器

 


6.基于PyTorch实现

import torch as t
import matplotlib.pyplot as plt
import torch.nn.functional as F

x = t.unsqueeze(t.linspace(-1, 1, 100), dim=1)
y = x.pow(2) + 0.2 * t.rand(x.size())


class Net(t.nn.Module):
    def __init__(self, n_feature, n_hidden1, n_hidden2, n_output):
        super(Net, self).__init__()  # 继承 __init__ 功能
        # 定义每层用什么样的形式
        self.layer1 = t.nn.Linear(n_feature, n_hidden1)  # 隐藏层线性输出
        self.layer2 = t.nn.Linear(n_hidden1, n_hidden2)  # 隐藏层线性输出
        self.predict = t.nn.Linear(n_hidden2, n_output)  # 输出层线性输出

    def forward(self, x):  # 这同时也是 Module 中的 forward 功能
        # 正向传播输入值, 神经网络分析出输出值
        x = F.relu(self.layer1(x))
        x = F.relu(self.layer2(x))
        # 最后一层输出不需要激活
        x = self.predict(x)
        return x


net = Net(n_feature=1, n_hidden1=10, n_hidden2=10, n_output=1)
print(net)

optimizer = t.optim.SGD(net.parameters(), lr=0.2)  # 传入 net 的所有参数, 学习率
loss_func = t.nn.MSELoss()  # 预测值和真实值的误差计算公式 (均方差)

plt.ion()  # 画图
plt.show()
for i in range(100):
    inputs = t.autograd.Variable(x)
    target = t.autograd.Variable(y)

    output = net(inputs)  # 喂给 net 训练数据 x, 输出预测值

    loss = loss_func(output, target)  # 计算两者的误差

    optimizer.zero_grad()  # 清空上一步的残余更新参数值
    loss.backward()  # 误差反向传播, 计算参数更新值
    optimizer.step()  # 将参数更新值施加到 net 的 parameters 上

    # 接着上面来
    if i % 5 == 0:
        plt.cla()
        # 原始
        plt.scatter(inputs.data.numpy(), target.data.numpy(),label='real')
        # 预测
        plt.plot(inputs.data.numpy(), output.data.numpy(), 'r-', lw=5,label='predict')
        plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
        plt.legend()
        plt.pause(0.1)

plt.pause(10000000000)


原文地址:https://www.cnblogs.com/onenoteone/p/12441712.html