実はこれ、ずっと記録しておきたいと思っていた疑問なのですが、「深くてシンプルなニューラルネットワークとディープラーニング」という本で分かりやすく解説されていました。最近実行したルーチンと組み合わせて、視覚的に確認してみましょう。
多くの書籍の紹介では、ディープ ラーニングにおけるバックプロパゲーションはブラック ボックスとして扱われており、ユーザーはネットワークをトレーニングするための重みを更新するためにバックプロパゲーションを使用できることだけを知っていれば十分です。しかし、この種のトレーニングの基本的なメカニズムの本質は、実際にはニューラル ネットワーク自体の解釈を構成しており、この解釈はニューラル ネットワークの適用性と拡張性を理解するための鍵となるため、この記事ではそれを拡張することにしました。
ネットワーク構造とパラメータ定義
特定の逆伝播メカニズムを導き出す前に、まずネットワーク構造の定義を与えます。
単純な 3 層ネットワーク構造を例に挙げます。最初の層は入力層であり、ここでは 3 つの入力ニューロンがあると仮定しています。2 番目の層は隠れ層(または中間層) であり、ネットワークがより複雑な特徴抽出操作、いわゆる深層抽出操作を実行していることを意味します。ネットワーク、つまり複数の隠れ層がありますニューラル ネットワーク、ネットワークを深化させると非線形表現能力が向上します; 3 番目の層は出力層で、出力される 2 つの活性化値が入力コスト関数と比較されます。期待値とエラー結果を出力します。
前のセクションのパーセプトロン モデルでは、接続の重みがすでにわかっていますwww、バイアスbbbと活性化関数σ \sigmaσに対応するaaによって形成される基本方程式多層パーセプトロンで構成されるニューラル ネットワークの場合、単一ノードのこの方程式は変わりません。ベクトル形式を使用して、特定の層の活性化値の生成をより簡潔に表現します。
重み付き入力を定義する al = σ ( wlal − 1 + bl ) zl = wlal − 1 + bl a_{l} = \sigma(w^{l} a^{l-1} + b^{l}) \ \ \ \ 重み付けされた入力を定義します \ \ z^{l} =w^{l} a^{l-1} + b^{l}ある私=s ( w彼にl − 1+bl )ここで、重み付けされた入力z 私=w彼にl − 1+b私
つまり、層間の接続重みの内積と、上位層の活性化値にこの層のバイアスを加えたものが、この層の重み付き入力zlz^{l}になります。zl、そして重み付けされた入力は活性化関数を通過して層の活性化値を取得します。
行列演算記号の補足Adamar 積または Schur 積⊙ \odot⊙。これにより、式の記述が簡素化されます。
SSを想定してsとtttが同じ次元の 2 つのベクトルである場合、s ⊙ ts \odot ts⊙t は要素ごとの積を表します。つまり( s ⊙ t ) j = sjtj (s \odot t)_{j} = s_j t_{j}( s⊙t )j=sjtj
例は次のとおりです。
[ 1 2 ] ⊙ [ 3 4 ] = [ 1 ∗ 3 2 ∗ 4 ] = [ 3 8 ] \begin{bmatrix} 1 \\ 2 \end{bmatrix} \odot \begin{bmatrix} 3 \\ 4 \end {bmatrix} = \begin{bmatrix} 1*3 \\ 2*4 \end{bmatrix} = \begin{bmatrix} 3 \\ 8 \end{bmatrix}[12】⊙[34】=[1∗32∗4】=[38】
方程式の導出
実際、バックプロパゲーションでは重みとバイアスを変更してコスト関数を制御する方法を検討しており、その最終的な意味は偏導関数∂ C / ∂ wjkl \partial C / \partial w_{jk}^{l} を計算することです。∂ C / ∂ wjk私和∂ C / ∂ bjl \partial C / \partial b_{j}^{l}∂ C / ∂ bj私。啓発的な認識 - ∂ C / ∂ zjl \partial C / \partial z_{j}^{l}∂ C / ∂ zj私ニューロンエラーの尺度です(ここではzjl z_{j}^lを選択します)zj私式の誤差は、後続の方程式形式を簡略化したものです)、つまりllの定義です。lレイヤーjjj 個のニューロンδ jl \delta _{j}^{l}dj私定義:
δ jl ≡ ∂ C ∂ zjl \delta_{j}^{l} \equiv \frac{\partial C}{\partial z_{j}^{l}}dj私≡∂z _j私∂C _
慣例により、δ l \delta^{l}を使用します。dlはllと同じ意味です層lに関連付けられた誤差ベクトルδ l \delta^{l} の逆伝播を使用して計算できますdlを計算し、これらの誤差を実際に必要な量∂ C / ∂ wjkl \partial C / \partial w_{jk}^{l}∂ C / ∂ wjk私和∂ C / ∂ bjl \partial C / \partial b_{j}^{l}∂ C / ∂ bj私接続する。
証明プロセスを開始する前に、まずバックプロパゲーション方程式に関する次の 4 つの式を与えます。
要約:バックプロパゲーション方程式
δ L = ∇ a C ⊙ σ ′ ( z L ) 出力層誤差 ( 1 ) \delta^{L} = \nabla_{a} C \odot \sigma^{\prime}(z^{L}) \qquad \qquad 出力層エラー \qquad (1)dL=∇あC⊙p' (zL )出力層エラー( 1 )
δ l = ( ( wl + 1 ) T δ l + 1 ) ⊙ σ ' ( zl ) フロント層送信エラー ( 2 ) \delta^{l} = ((w^{l+1})^{T} \ delta ^{l+1}) \odot \sigma ^{\prime}(z^{l}) \qquad \qquad フロント層転送エラー\qquad (2)d私=(( wl + 1 )Tδ _l + 1 )⊙p' (zl )フロントパスエラー( 2 )
∂ C ∂ bjl = δ jl バイアスの変化率 ( 3 ) \frac{\partial C}{\partial b_{j}^{l}} = \delta_{j}^{l} \qquad \qquadbias変化率\qquad (3)∂b _j私∂C _=dj私バイアス変化率( 3 )
∂ C ∂ wjkl = akl − 1 δ jl 体重変化率 ( 4 ) \frac{\partial C}{ \partial w_{jk}^{l}} = a_{k}^{l-1}\delta_{ j }^{l} \qquad 重みの変化率\qquad (4)∂w _jk私∂C _=あるkl − 1dj私体重の変化率( 4 )
一つずつ証明していきましょう。核となる多変量微積分の連鎖規則を覚えておいてください。
(1) 出力層エラー
出力層誤差の場合、δ j L = ∂ C ∂ zj L \delta_{j}^{L} = \frac{\partial C}{\partial z_{j}^{L}} として定義されます。
djL=∂z _jL∂C _
このうち、L はネットワークの層数であり、これは出力層の j 番目のニューロンでのエラーを意味します。実際、上記の偏微分値を出力活性化値の偏微分値で書き直すことができます。つまり、
δ j L = ∑ k ∂ C ∂ ak L ∂ ak L ∂ zj L \delta_{j}^{L } =\sum_ {k} \frac{\partial C}{\partial a_{k}^{L}} \frac{\partial a_{k}^{L}}{\partial z_{j}^{L }}djL=k∑∂a _kL∂C _∂z _jL∂a _kL
ここで、合計は出力層のすべてのニューロンに対して実行されます。もちろん、kk個のニューロンak L a_{k}^{L}あるkLk = jk =jにのみ依存しますk=時間j L z_{j}^{L}における j 番目のニューロンの入力重み zjzjLしたがって、k ≠ jk \ne jの場合k=j時∂ ak L / ∂ zj L \partial a_{k}^{L}/ \partial z_{j}^{L}∂a _kL/ ∂z _jL存在しません (つまり、影響なしは 0)。したがって、方程式は
δ j L = ∂ C ∂ aj L ∂ aj L ∂ zj L \delta_{j}^{L} =\frac{\partial C}{\partial a_{j}^{L \frac{\partial a_{j}^{L}}{\partial z_{j}^{L}}djL=∂a _jL∂C _∂z _jL∂a _jL
これは、ニューロンの誤差が、ニューロンの活性化値に対する損失関数の偏導関数と、ニューロンの重み付けされた入力に対する活性化値の偏導関数(つまり、この時点では活性化関数です)。aj L = σ ( zj L ) a_{j}^{L} = \sigma (z_{j}^{L}) に基づくあるjL=s ( zjL)の場合、右側の 2 番目の項目は
σ j L = ∂ C ∂ aj L σ ′ ( zj L ) \sigma_{j}^{L}= \frac{ \partial{C} }{\partial a_ と書くことができます。 {j} ^{L}} \sigma^{\prime}(z_{j}^{L})pjL=∂a _jL∂C _p' (zjL)
上記の式を出力層の各ニューロンに拡張し、アダマール積を使って表現すると、式 (1) の形、つまり δ L = ∇ a C ⊙ σ ′ ( z L ) \delta が得られます
。 ^{L} = \nabla_{a} C \odot \sigma^{\prime}(z^{L})dL=∇あC⊙p' (zL )
(2) フロント層転送エラー
これは、バックプロパゲーション アルゴリズムにおける「リバース」という言葉の意味を最もよく説明する式になるはずです。つまり、誤差は後ろから前に渡されます。具体的には、次の誤差層δ l + 1 \delta^{l+1}を使用することを考慮します。dl + 1は誤差δ l \delta^{l}dl。同様に、δ kl + 1 = ∂ C / ∂ zkl + 1 \delta^{l+1}_{k} = \partial{C} / \partial z_{k}^{l+1} を使用します。dkl + 1=∂ C / ∂ zkl + 1重写δ jl = ∂ C / ∂ zjl \delta^{l}_{j} = \partial{C} / \partial z_{j}^{l}dj私=∂ C / ∂ zj私δ jl = ∂ C ∂ zjl = ∑ k ∂ C ∂ zkl + 1 ∂ zkl + 1 ∂ zjl = ∑ k ∂ zkl + 1 ∂ z jl δ kl + 1 \begin{aligned } \delta_j^l &=\frac とします。
{\partial C}{\partial z_j^l} \\ &=\sum_k \frac{\partial C}{\partial z_k^{l+1}} \frac{\partial z_k^{l+1}}{ \partial z_j^l} \\ &=\sum_k \frac{\partial z_k^{l+1}}{\partial z_j^l} \delta_k^{l+1}\ end{aligned}dj私=∂z _j私∂C _=k∑∂z _kl + 1∂C _∂z _j私∂z _kl + 1=k∑∂z _j私∂z _kl + 1dkl + 1
最後の行は、右側の 2 つの項を交換し、δ kl + 1 \delta_{k}^{l+1} に置き換えます。dkl + 1の定義により、対応して下位層エラーの置換が実現されます。最後の行を評価するには、
zkl + 1 = ∑ jwkjl + 1 ajl + bkl + 1 = ∑ jwkjl + 1 σ ( zjl ) + bkl + 1 z_k^{l+1}=\sum_j w_{kj}^{ l を展開します。 +1} a_j^l+b_k^{l+1}=\sum_j w_{kj}^{l+1} \sigma\left(z_j^l\right)+b_k^{l+1}zkl + 1=j∑wkjl + 1あるj私+bkl + 1=j∑wkjl + 1p( zj私)+bkl + 1
これを微分すると
∂ zkl + 1 ∂ zjl = wkjl + 1 σ ′ ( zjl ) \frac{\partial z_k^{l+1}}{\partial z_j^l}=w_{kj}^{l+ 1} \ sigma^{\prime}\left(z_j^l\right)∂z _j私∂z _kl + 1=wkjl + 1p』( zj私)
元の式に戻ると、
δ jl = ∑ kwkjl + 1 δ kl + 1 σ ′ ( zjl ) \delta_j^l=\sum_k w_{kj}^{l+1} \delta_k^ {l+1} となります。 \sigma^{\prime}\left(z_j^l\right)dj私=k∑wkjl + 1dkl + 1p』( zj私)
これも (2) のコンポーネント形式です。アダマール積演算子を導入した後、誤差を後層から前層に転送するベクトル形式、つまり δ l = ( ( wl + 1 ) を書くことができます。 T δ l +
1 ) ⊙ σ ′ ( zl ) \delta^{l} = ((w^{l+1})^{T} \delta ^{l+1}) \odot \sigma ^{\prime }(z^{ l})d私=(( wl + 1 )Tδ _l + 1 )⊙p' (zl )
(3) バイアスの変化率
前の 2 つのステップの証明と同様に、この時点の証明も連鎖律を適用して展開されていると想像できます。δ jl = ∂ C ∂ zjl = ∂ C ∂ bjl ∂ bjl ∂ zjlを考慮すると
\delta_j^l = \frac{\partial C}{\partial z_j^l} = \frac{\partial C}{\partial b_j^ l } \frac{\部分 b_j^l}{\部分 z_j^l}dj私=∂z _j私∂C _=∂b _j私∂C _∂z _j私∂b _j私
その中で、 zjl z_{j}^{l} は次のとおりです。zj私展开
zjl = ∑ kwjklakl − 1 + bjl z_j^{l}=\sum_k w_{jk}^{l} a_k^{l-1}+b_j^{l}zj私=k∑wjk私あるkl − 1+bj私
両側が同時にbjl b_{j}^{l}に面しているbj私偏導関数を求めます。なぜなら、wjkl w_{jk}^{l}だからです。wjk私bjl b_{j}^{l}とbj私それぞれは自由に変更できる変数であるため、関係ありません。つまり
∂ zjl ∂ bjl = 1 \frac{\partial z_{j}^{l}}{\partial b_{j}^{l}} = 1∂b _j私∂z _j私=1
次に、(3)の証明です。
∂ C ∂ bjl = δ jl \frac{\partial C}{\partial b_j^l} = \delta_j^l∂b _j私∂C _=dj私
(4) 重量変化率
(3) の証明と同様に、
δ jl = ∂ C ∂ zjl = ∂ C ∂ wjkl ∂ wjkl ∂ zjl \delta_j^l = \frac{\partial C}{\partial z_j^l} = \frac{ \ 部分 C}{\部分 w_{jk}^{l}} \frac{\部分 w_{jk}^{l}}{\部分 z_j^l}dj私=∂z _j私∂C _=∂w _jk私∂C _∂z _j私∂w _jk私
同样,将 z k l z_{k}^{l} zk私展开
zjl = ∑ kwjklakl − 1 + bjl z_j^{l}=\sum_k w_{jk}^{l} a_k^{l-1}+b_j^{l}zj私=k∑wjk私あるkl − 1+bj私
両側同時にwjkl w_{jk}^{l}wjk私偏導関数wjkl w_{jk}^{l}を求めます。wjk私bjl b_{j}^{l}だけではありませんbj私は他の重みとは関係がなく、それ自体とのみ関係があるため、次のようになります:
∂ zjl ∂ wjkl = akl − 1 \frac{\partial z_{j}^{l}}{\partial w_{jk}^ {l} } = a_{k}^{l-1}∂w _jk私∂z _j私=あるkl − 1
変換後、(4)
∂ C ∂ wjkl = akl − 1 δ jl \frac{\partial C}{ \partial w_{jk}^{l}} = a_{k}^{l- 1 の証明があります。 }\デルタ_{j}^{l}∂w _jk私∂C _=あるkl − 1dj私
まとめ
上記の証明プロセスを通じて、バックプロパゲーション アルゴリズムを通じてエラーを層ごとに戻し、重みとバイアスの数学的原理を更新する方法を簡単に確認できます。
ここで方程式 (1) を振り返り、その成分形式で項σ ′ ( zj L ) \sigma^{\prime}(z_{j}^{L})を見てみましょう。p' (zjL)。シグモイド関数のグラフを振り返ると、σ ( zj L ) \sigma (z_{j}^{L})s ( zjL)が約 0 または 1 の場合、シグモイド関数は非常に滑らかになり、σ ′ ( zjl ) ≈ 0 \sigma^{\prime}(z_{j}^{l}) \about 0p' (zj私)≈0。したがって、出力ニューロンが小さな活性化値 (約 0) または大きな活性化値 (約 1) にある場合、最終層の重み学習は非常に遅くなります。が飽和すると、重み学習も終了し (または学習が非常に遅くなり)、出力ニューロンも同様にバイアスされます。式 (3) と (4) を振り返ると、重みとバイアスの更新は実際には誤差 σ (zjl ) \sigma (z_{j}^{l})s ( zj私) 、出力層の場合、誤差は活性化関数σ ′ ( zj L ) \sigma^{\prime}(z_{j}^{L}) の導関数に依存します。p' (zjL)、飽和状態では学習が難しくなります。
最後に (2) を見てみましょう。(2) の項σ ′ ( zjl ) \sigma^{\prime}(z_{j}^{l}) に注目してください。p' (zj私)、これは、エラーがフロント層に戻されるときに、アクティベーション関数の微分値で乗算されることを意味します。シグモイド関数の場合、微分値が 0 から 1 の間であることがわかります。ネットワークの層数が多い場合、出力層から浅い層に伝播するときに誤差が減少することがわかります。何度も繰り返すと、最終的には誤差が無視できるレベル (0 に近いレベル) に低下するため、浅い学習が非常に困難になります。これが勾配消失。
また、出力層ニューロン飽和シナリオの分析と一致して、介在ニューロンの飽和もδ jl \delta _{j}^{l}につながります。dj私σ ′ ( zjl ) \sigma^{\prime}(z_{j}^{l})なので小さくなるp' (zj私)は非常に小さいです。つまり、飽和ニューロンへの重み入力についてはゆっくりと学習します。
バックプロパゲーション方程式はどの活性化関数にも有効です。コスト関数の議論は別の内容であるためここでは無視しますが、上記の推論自体は特定のコスト関数とは何の関係もないことが証明できます。同時に、新しいアイデアが生まれる可能性があり、アプリケーションによってはシグモイド関数が理想的な活性化関数として適切ではない可能性があります。実際、特定の学習特性を備えた活性化関数の設計は、ニューラル ネットワークの重要な部分です。
観察してみる
ここでは、ニューラル ネットワークで重みの反復更新を具体的に観察する方法を説明する簡単な例を示します。これは、非線形フィッティングにニューラル ネットワークを使用する例です。
まず、次のようにノイズの多い正弦波信号 (非線形信号) を生成します。
x = torch.unsqueeze(torch.linspace(-np.pi, np.pi, 100), dim = 1) # 构建等差数列
y = torch.sin(x) + 0.5*torch.rand(x.size()) # 条件随机数
次に、ネットワーク構造を定義します。
class Net(nn.Module): # 定义类,存储网络结构
def __init__(self):
super(Net, self).__init__()
self.predict = nn.Sequential(
nn.Linear(1,10), # 全连接层,1个输入,10个输出
nn.ReLU(), #ReLU激活函数
nn.Linear(10,1) # 全连接层,10个输入,1个输出
)
ここで使用する活性化関数は ReLU 関数です. ReLU はシグモイド関数に比べて正区間での勾配消失の問題がなく, 関数の導出も比較的簡単です. CNN ネットワークでよく使用される活性化関数です.
トレーニング中に、モジュールを呼び出して層を走査することで、完全に接続された層に対応する重みを取得できます。対応するコードは次のとおりです。
# 输出权重变化,net.modules返回的是generator类型
for layer in net.modules():
if isinstance(layer, nn.Linear):
print(layer.weight)
プロセス全体のコードは次のとおりです。
# 这是一个用神经网络进行函数拟合的函数
import numpy as np
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
x = torch.unsqueeze(torch.linspace(-np.pi, np.pi, 100), dim = 1) # 构建等差数列
y = torch.sin(x) + 0.5*torch.rand(x.size()) # 条件随机数
class Net(nn.Module): # 定义类,存储网络结构
def __init__(self):
super(Net, self).__init__()
self.predict = nn.Sequential(
nn.Linear(1,10), # 全连接层,1个输入,10个输出
nn.ReLU(), #ReLU激活函数
nn.Linear(10,1) # 全连接层,10个输入,1个输出
)
def forward(self,x): # 定义前向传播过程
prediction = self.predict(x) # 将x传入网络
return prediction
net = Net()
optimizer = torch.optim.SGD(net.parameters(), lr = 0.05) # 设置优化器
loss_func = nn.MSELoss() # 设置损失函数
plt.ion() # 打开交互模式
for epoch in range(1000):
out = net(x) # 实际输出
loss = loss_func(out, y) # 实际输出和期望输出传入损失函数
optimizer.zero_grad() # 清除梯度
loss.backward() # 误差反向传播
optimizer.step() # 优化器开始优化
if epoch % 25 == 0: # 每25epoch显示
plt.cla() # 清除上一次绘图
plt.scatter(x,y) # 绘制散点图
# 输出权重变化,net.modules返回的是generator类型
for layer in net.modules():
if isinstance(layer, nn.Linear):
print(layer.weight)
plt.plot(x, out.data.numpy(), 'r', lw = 5 ) # 绘制曲线图
plt.text(0, 0 , f'loss={
loss}',fontdict={
'size':20,'color':'red'}) # 添加文字来显示loss值
plt.pause(0.1) # 显示时间0.1s
plt.show()
plt.ioff() # 关闭交互模式
plt.show() # 定格显示最后结果
グラフィカル インターフェイスにリアルタイムで表示されるフィッティング効果を確認でき、損失値が減少するにつれて、ネットワークは徐々に正弦曲線の形状にフィットしていきます。
コンソールでは出力ネットワークの重みも確認できます。ここでは、前回のトレーニングで得られた重みを例にとりますが、前回とは異なる 10×1 (1→10) の重み行列と 1×10 (10→1) の重み行列が表示されます。定義. の 2 つの完全に接続された層のネットワーク構造は、正確に対応しています。
参考文献:
『Python Neural Network and Deep Learning』 著者:[オーストラリア] Michael Nielsen 翻訳者:Zhu Xiaohu 第 2 章
「Python ニューラルネットワークの入門と実戦」 著者:Wang Kai 第 7 章