多層ニューラル ネットワークとバックプロパゲーションのコード導出

ドラフトボックスの中に、また昔の学習ノートがあるのを見つけました。困っている子供たちの助けになれば幸いです~

目次

順序

多層全結合ニューラルネットワークの構築

(1)、入力 -> (affine_forward) -> out* -> (relu_forward) -> out、完全な接続と relu のアクティブ化

(2)、バッチ正規化 バッチ正規化

   3. ランダムな非アクティブ化 (ドロップアウト)

(4)、任意のディープ ニューラル ネットワーク

ニューラル ネットワークの最適化 - トレーニング中の勾配降下用

1、勢いのあるシンガポールドル

2、RMSプロップ

3、アダム


順序

       これらはすべて、C++ で学習した従来の画像セグメンテーション アルゴリズムを使用していることがわかりました。主にクラスタリングのセグメンテーション、レベルセット、グラフカットを学びます。

       私はCS231nコースを受講し始めたばかりで、たまたまPythonを学んでいたこともあり、モデルの理解を深めるために演習も行いました。

       コースリンク

       1. これは私自身の学習ノートです。他の人のコンテンツを参考にします。侵害がある場合は、削除するために連絡してください。

       2. コードはWILL とDukeを参照していますが、独自の学習メモが多数含まれています。

       3. 基本的な内容については説明しませんが、よく説明されていると思われるブログへのリンクを貼ります。

       4. numpyをあまり使ったことがなく、pythonに詳しくないので、pythonとnumpyモジュールの勉強メモも兼ねています。

       5. この記事の参考文献

       この章の序文: この章では、多層完全接続ニューラル ネットワークと、バッチ正規化、SGD+Momentum、Adam などの最適化アルゴリズムの使用を実装します。この章の焦点は、バックプロパゲーションと最適化アルゴリズムです

       jupyterで書いたコードを.pyファイルとしてダウンロードしてインポートする必要があります。インポート後に.pyファイルの内容を変更した場合、jupyterを再度開く必要があり、非常に面倒です。インポート後に次のコードを追加します。 .py ファイルを変更した後、jupyter を再度開く必要はありません。

#自动加载外部模块
%reload_ext autoreload
%autoreload 2

多層全結合ニューラルネットワークの構築

       以前に実装されていたのは、入力 -> 隠し -> relu -> スコア -> ソフトマックス - 出力という構造を持つ 2 層のニューラル ネットワークでした。層数が少なく、与えられた導出は後ろから前へ段階的に導出されますが、これは多層ニューラルネットワークでは現実的ではなく、層の数が増えると、層ごとにプッシュするのは非常に面倒になります。実際のプロセスでは、モジュラーバックプロパゲーション導出がよく使用されます。

       多層完全接続ニューラル ネットワーク構造はモジュールごとに分割されます。

(1)、入力 -> (affine_forward) -> out* -> (relu_forward) -> out、完全な接続と relu のアクティブ化

次に、順伝播を実装するコードです。

def affine_forward(x,w,b):
    out = None
    x_reshape = np.reshape(x,(x.shape[0],-1))
    out = x_reshape.dot(w) + b 
    cache = (x,w,b)
    return out,cache     #返回线性输出,和中间参数(x,w,b)


def relu_forward(x):
    out = np.maximum(0,x)
    cache = x     #缓存线性输出a
    return out,cache


#模块化
def affine_relu_forward(x,w,b):
    a,fc_cache = affine_forward(x,w,b)   #a是线性输出,fc_cache中存储(x,w,b)
    out,relu_cache = relu_forward(a)     #relu_cache存储线性输出a
    cache = (fc_cache,relu_cache)        #缓冲元组:(x,w,b,(a))
    return out,cache                     #返回激活值out 和参数(x,w,b,(a))

順伝播モジュールができたので、次は逆伝播モジュールを用意する必要があります。

def affine_backward(dout,cache):
    """
    输出层反向传播
    dout 该层affine_forward正向输出数据out的梯度,对应softmax_loss/relu中的输出dz
    cache 元组,正向流入输入层的数据x,和输出层的参数(w,b)
    """
    z,w,b = cache    #z为上一层的激活值,也是本层的输入
    dx,dw,db = None, None,None
    x_reshape = np.reshape(z, (z.shape[0],-1))
    dz = np.reshape(dout.dot(w.T),z.shape)    #参考公式
    dw = (x_reshape.T).dot(dout)              #参考公式
    db = np.sum(dout,axis=0)                  #参考公式
    return dz,dw,db

def relu_backward(dout,cache):    #传入的是
    """
    relu激活,小于0的梯度为0,大于0的梯度为1
    """
    dx,x = None, cache
    dx = (x>0) * dout
    return dx

def affine_relu_backward(dout,cache):
    fc_cache, relu_cache = cache    #relu_cache 存储线性输出a
    da = relu_backward(dout,relu_cache)    #计算关于relu的梯度,这边是一个复合函数,z=relu(a),a=w1x+b1 -> dz /dx = dz/da *da/dx
    dx,dw,db = affine_backward(da,fc_cache)
    return dx,dw,db

(2)、バッチ正規化 バッチ正規化

       バッチ正規化は、ランダムな初期化重みの影響を軽減し、収束を加速し、学習率を適切に高め、過学習を減らし、ドロップアウトを減らし、L2 正則化係数を減らすなどの利点があると言われています。

まず、データの特徴平均が 0、分散が 1 になるように入力データを正規化します。つまり、標準ガウス分布に従うようにします。次に、データが変換および再構築されるため、元の特徴分布が復元されます。

       現在のニューラル ネットワーク構造は、input -> affine_forward -> BN(batch_normlize) -> relu_forward になります。つまり、完全接続後に BN を追加し、アクティブ化出力を実行します。

#批量归一化(加速收敛,学习率适当增大,加快寻,减少过拟合,使用较低的dropout,减小L2正则化系数)
def batchnorm_forward(x,gamma,beta,bn_param):
    mode = bn_param['mode']
    eps = bn_param.get('eps',1e-5)          #防止除以0
    momentum = bn_param.get('momentum',0.9) 
    
    N,D = x.shape            #N样本个数,D特征个数
    #移动均值和方差,会随着train过程不断变化
    running_mean = bn_param.get('running_mean',np.zeros(D, dtype = x.dtype))
    running_var = bn_param.get('running_var',np.zeros(D, dtype = x.dtype))
    
    out,cache=None,None
    if mode =='train':
        sample_mean = np.mean(x,axis=0)
        sample_var = np.var(x,axis = 0)
        x_hat = (x-sample_mean)/(np.sqrt(sample_var+eps))
        
        out = gamma*x_hat +beta
        cache = (x,sample_mean,sample_var,x_hat,eps,gamma,beta)
        running_mean = momentum*running_var + (1-momentum)*sample_mean
        running_var = momentum*running_var+(1-momentum)*sample_var
    elif mode == 'test':
        out = (x-running_mean)*gamma/(np.sqrt(running_var+eps))+beta
    else:
        raise ValueError('invalid forward batchnorm mode "%s"' %mode)
    
    bn_param['running_mean'] = running_mean
    bn_param['running_var'] = running_var
    
    return out,cache   #cache(线性输出,均值,方差,归一化值,eps,gamma,beta)   #out 变换重构的值


def affine_bn_relu_forward(x,w,b,gamma,beta,bn_param):
    a,fc_cache = affine_forward(x,w,b)
    a_bn, bn_cache = batchnorm_forward(a,gamma,beta,bn_param)  #BN层
    out,relu_cache = relu_forward(a_bn)    #将归一化后的值激活,relu_cache中缓存变换重构值
    cache = (fc_cache,bn_cache,relu_cache)
    return out,cache

順伝播では、逆伝播があるのが自然です。実際、私は式を見ずにコードをコピーしただけです...逆伝播の連鎖導出原理は同じであるためです。

def batchnorm_backward(dout,cache):
    x,mean,var,x_hat,eps,gamma,beta = cache
    N = x.shape[0]
    dgamma = np.sum(dout*x_hat,axis=0)
    dbeta = np.sum(dout*1, axis=0)
    dx_hat = dout*gamma
    dx_hat_numerator = dx_hat/np.sqrt(var +eps)
    dx_hat_denominator = np.sum(dx_hat * (x-mean),axis=0)
    dx_1 = dx_hat_numerator
    dvar = -0.5*((var+eps)**(-1.5))*dx_hat_denominator
    dmean = -1.0*np.sum(dx_hat_numerator,axis = 0)+dvar*np.mean(-2.0*(x-mean),axis=0)
    dx_var = dvar*2.0/N*(x-mean)
    dx_mean =dmean*1.0/N
    dx = dx_1+dx_var+dx_mean
    
    return dx,dgamma,dbeta


def affine_bn_relu_backward(dout,cache):
    fc_cache,bn_cache,relu_cache = cache
    da_bn = relu_backward(dout,relu_cache)
    da,dgamma,dbeta = batchnorm_backward(da_bn,bn_cache)
    dx,dw,db = affine_backward(da,fc_cache)
    return dx,dw,db,dgamma,dbeta

(3) ランダム無効化(DropOut)

全結合ニューラルネットワークでは層が深くなるほどパラメータが増え、テストセットの精度はどんどん高くなりますが、過学習により検証セットへの影響は良くありません。平たく言えば、テスト セットに対応するために、ネットワークが抽出する特徴が多すぎるため、これらの特徴の効果はそれほど大きくありません。ランダム非活性化とは、ニューロンをランダムに無効にすること、つまり、特定の機能の役割をキャンセルすることで、ある程度の過学習を防ぐ機能を実現します。

#随机失活
def dropout_forward(x,dropout_param):
    """
    dropout_param p 失活概率
    """
    p,mode = dropout_param['p'],dropout_param['mode']
    if 'seed' in dropout_param:
        np.random.seed(dropout_param['seed'])
        
    mask = None
    out = None
    
    if mode == 'train':
        keep_prob = 1-p 
        mask = (np.random.rand(*x.shape)<keep_prob)/keep_prob   #随机失活后损失下降,所以除以概率保持分布同意
        out = mask * x 
    elif mode == 'test':
        out = x
        
    cache = (dropout_param,mask)
    out = out.astype(x.dtype,copy=False)
    return out,cache

バックプロパゲーション: 原理は Relu と似ています。確率マスクを使用してランダムな非アクティブ化が実装されます。マスクが 1 の場合、非アクティブ化されません。それ以外の場合、非アクティブ化は 0、つまり勾配は 1 または 0 になります。

def dropout_backward(dout,cache):
    dropout_param,mask = cache
    mode = dropout_param['mode']
    
    dx = None
    if mode == 'train':
        dx = mask * dout
    elif mode == 'test':
        dx = dout
    return dx 

(4)、任意のディープ ニューラル ネットワーク

       すべてのモジュールが構築されたら、ニューラル ネットワークをモジュール化できます。合計 3 つの部分に分かれており、1 つはパラメーターの初期化、1 つは損失関数の計算、もう 1 つはトレーニングです。今のところここには 2 つの部分だけがあります

順伝播ネットワーク構造 (BN、ドロップアウト): (入力 -> BN -> relu -> ドロップアウト) * N (N 回繰り返す) -> affine_forward -> Softmax -> Loss

バックプロパゲーション (BN、ドロップアウト):softmax_loss -> affine_backward ->dropout_backward -> (affine_bn_relu_backward) * N

from layers import *
import numpy as np


class FullyConnectedNet(object):
    def __init__(self
                 ,hidden_dims            #列表,元素个数是隐藏层层数,元素值为神经元个数
                 ,input_dim = 3*32*32    #输入神经元3072
                 ,num_classes = 10       #输出10类
                 ,dropout = 0            #默认不开启dropout,(0,1)
                 ,use_batchnorm = False  #默认不开批量归一化
                 ,reg = 0.0              #默认无L2正则化
                 ,weight_scale =1e-2     #权重初始化标准差
                 ,dtype=np.float64       #精度
                 ,seed = None            #无随机种子,控制dropout层
                ):
        self.use_batchnorm = use_batchnorm
        self.use_dropout = dropout>0     #dropout为0,则关闭随机失活
        self.reg = reg                   #正则化参数
        self.num_layers = 1+len(hidden_dims)
        
        self.dtype =dtype
        self.params = {}                 #参数字典
        
        in_dim = input_dim
        #有几个隐藏层,就有几个对应的W,最后输出层还有一个W
        for i,h_dim in enumerate(hidden_dims):
            self.params['W%d' %(i+1,)] = weight_scale*np.random.randn(in_dim,h_dim)
            self.params['b%d' %(i+1,)] = np.zeros((h_dim,))
            if use_batchnorm:
                #使用批量归一化
                self.params['gamma%d' %(i+1,)] = np.ones((h_dim,))
                self.params['beta%d'  %(i+1,)] = np.zeros((h_dim,))
            in_dim = h_dim   #将隐藏层中特征个数传递给下一层
        
        #输出层参数
        self.params['W%d'%(self.num_layers,)] = weight_scale*np.random.randn(in_dim,num_classes)
        self.params['b%d'%(self.num_layers,)] = np.zeros((num_classes,))
        
        #dropout
        self.dropout_param = {}   #dropou参数字典
        if self.use_dropout:      #如果use_dropout为(0,1),启用dropout
            self.dropout_param = {'mode':'train','p':dropout}
        
        if seed is not None:
            self.dropout_param['seed'] = seed
            
        #batch normalize
        self.bn_params = []  #bn算法参数列表
        if self.use_batchnorm:   #开启批量归一化,设置每层的mode为训练模式
            self.bn_params=[{'mode':'train'} for i in range(self.num_layers - 1)]
        
        #设置所有参数的计算精度为np.float64
        for k,v in self.params.items():
            self.params[k] = v.astype(dtype)
    
    
    def loss(self,X,y = None):
        #调整精度
        #X的数据是N*3*32*32
        #Y(N,)
        X = X.astype(self.dtype)
        mode = 'test' if y is None else 'train'
        
        if self.dropout_param is not None:
            self.dropout_param['mode'] = mode
        if self.use_batchnorm:
            for bn_params in self.bn_params:
                bn_params['mode'] = mode
        
        scores = None
        
        
        
        #向前传播
        fc_mix_cache = {}       #混合层向前传播缓存
        if self.use_dropout:    #开启随机失活
            dp_cache = {}       #随即失活层向前传播缓存
            
        out = X
        #只计算隐藏层中的向前传播,输出层单独的全连接
        for i in range(self.num_layers -1):
            w = self.params['W%d'%(i+1,)]
            b = self.params['b%d'%(i+1,)]
            if self.use_batchnorm:
                #利用模块向前传播
                gamma = self.params['gamma%d'%(i+1,)]
                beta = self.params['beta%d'%(i+1,)]
                out,fc_mix_cache[i] = affine_bn_relu_forward(out,w,b,gamma,beta,self.bn_params[i])
            else:
                out,fc_mix_cache[i] = affine_relu_forward(out,w,b)
            if self.use_dropout:
                #开启随机失活,并且记录随机失活的缓存
                out,dp_cache[i] = dropout_forward(out,self.dropout_param)
        
        #输出层向前传播
        w = self.params['W%d'%(self.num_layers,)]
        b = self.params['b%d'%(self.num_layers,)]
        out,out_cache = affine_forward(out,w,b)
        scores = out 
        
        if mode == 'test':
            return scores
        
        #反向传播
        loss,grads=0.0, {}
        #softmaxloss
        loss,dout = softmax_loss(scores,y)
        #正则化loss,只计算了输出层的W平方和
        loss += 0.5*self.reg*np.sum(self.params['W%d'%(self.num_layers,)]**2)
        
        #输出层的反向传播,存储到梯度字典
        dout,dw,db = affine_backward(dout,out_cache)
        #正则化
        grads['W%d'%(self.num_layers,)] = dw+self.reg*self.params['W%d'%(self.num_layers,)]
        grads['b%d'%(self.num_layers,)] = db
        
        #隐藏层梯度反向传播
        for i in range(self.num_layers-1):
            ri = self.num_layers -2 - i  #倒数第ri+1层
            loss+=0.5*self.reg*np.sum(self.params['W%d'%(ri+1,)]**2)    #继续正则化loss
            if self.use_dropout:     #如果使用随即失活,加上梯度下降
                dout = dropout_backward(dout,dp_cache[ri])
            if self.use_batchnorm:   #如果使用BN,加上梯度下降部分
                dout,dw,db,dgamma,dbeta = affine_bn_relu_backward(dout,fc_mix_cache[ri])
                grads['gamma%d'%(ri+1,)] = dgamma
                grads['beta%d'%(ri+1,)] = dbeta
            else:             #否则直接梯度下降
                dout,dw,db = affine_relu_backward(dout,fc_mix_cache[ri])
                #存储到字典中
            grads['W%d'%(ri+1,)] = dw+self.reg * self.params['W%d'%(ri+1,)]
            grads['b%d'%(ri+1,)] = db 
            #返回本次loss和梯度值
        return loss,grads


ニューラル ネットワークの最適化 - トレーニング中の勾配降下用

1、勢いのあるシンガポールドル

       上記の損失関数は現在の損失値とモデルパラメータの勾配を出力するもので、勾配降下処理中、つまりトレーニング処理中にモデルパラメータは負の勾配方向に更新されます。

       以前は確率的勾配降下法 (SGD) が使用されていました。 w -= learning_rate * dW

       SGD + 運動量 (確率的勾配降下法の運動量更新法)。w0 = w0*mu - learning_rate*dW 個人的な理解ですが、もともとは勾配を基準にしていましたが、アップデート後は独自の速度を持つようになりました。速度は瞬時には変化しません。勾配を力として考えてください。この力は概念的な速度と速度、方向の合計。

def sge_momentum(w,dw,config = None):
    if config is None:
        config = {}
    config.setdefault('learning_rate',1e-2)
    config.setdefault('momentum',0.9)
    v = config.get('velocity',np.zeros_like(w))
    
    next_w = None
    
    v = config['momentum']*v - config['learning_rate']*dw
    next_w = w + v 
    
    config['velocity'] = v 
    return next_w,config

2、RMSプロップ

3、アダム


ニューラルネットワークのインスタンス化とトレーニング

参考リンク:コースワークの 52 ページのソルバー

おすすめ

転載: blog.csdn.net/qq_41828351/article/details/90257272