• 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)


  • 相关阅读:
    Linux——强制退出命令
    2019-08-07-安装jdk1.8配置环境变量
    2019-08-07-查看当前的系统版本-中文乱码显示
    2019-08-07-centos系统上文件出现乱码
    2019-08-06-centos6.4关闭防火墙-设置通过防火墙的端口
    2019-08-06-写批处理
    2019-08-06-centos 打包
    2019-07-26_解密黑客攻击前的准备
    2019-07-24-DOS 常用命令大全
    2019-07-24-windows一些常用命令-netsh
  • 原文地址:https://www.cnblogs.com/onenoteone/p/12441712.html
Copyright © 2020-2023  润新知