ドラフトボックスの中に、また昔の学習ノートがあるのを見つけました。困っている子供たちの助けになれば幸いです~
目次
(1)、入力 -> (affine_forward) -> out* -> (relu_forward) -> out、完全な接続と relu のアクティブ化
ニューラル ネットワークの最適化 - トレーニング中の勾配降下用
順序
これらはすべて、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 ページのソルバー