時間が経つにつれて、複雑な回路の場合でもすべての場合でも、パッセージをはるかに効率的に書き戻すことができます。 いくつかの例を挙げて、少し後戻りしてみましょう。 将来的には、a、b、c、xなどの変数を使用するだけで、それらの勾配はそれぞれda、db、dc、dxと呼ばれます。 繰り返しますが、変数は「順方向の流れ」として、またそれらの勾配は各ラインに沿った「逆方向の流れ」として表しています。 最初の例は論理要素でした*:
var x = a * b; // x (dx), , : var da = b * dx; var db = a * dx;
上記のコードでは、エラーの逆伝播を実行している間(またはデフォルトで+1になっている)、回路のどこかから来るdx変数が与えられていると想定しています。 グラデーションがどのように関連しているかを明確に示したいので、書きました。 方程式では、論理要素*は逆方向の通過中にスイッチとして機能することに注意してください。 彼は、元の値が何であったかを覚えており、各値の勾配は、戻りパスのプロセスで別のものと等しくなります。 そして、もちろん、チェーンルールである上記の勾配を掛ける必要があります。 これは、論理要素+が圧縮形式でどのように見えるかです。
var x = a + b; // -> var da = 1.0 * dx; var db = 1.0 * dx;
ここで、1.0はローカルグラデーションで、乗算はチェーンルールです。 しかし、3つの数字の追加はどうですか?:
// x = a + b + c : var q = a + b; // 1 var x = q + c; // 2 // : dc = 1.0 * dx; // dq = 1.0 * dx; da = 1.0 * dq; // db = 1.0 * dq;
何が起こっているのか理解していますか? リターンフロー図を覚えている場合、+ロジック要素は単純に上から勾配を取得し、すべての初期値に均等に向けます(実際の値に関係なく、すべての初期値のローカル勾配は常に1に等しいため)。 したがって、はるかに高速に実行できます。
var x = a + b + c; var da = 1.0 * dx; var db = 1.0 * dx; var dc = 1.0 * dx;
さて、論理ゲートの組み合わせはどうですか?:
var x = a * b + c; // dx, => da = b * dx; db = a * dx; dc = 1.0 * dx;
上記で何が起こっているのかわからない場合は、一時変数q = a * bを導入し、x = q + cを計算してすべてをチェックしましょう。 ここにニューロンがありますので、すべてを2段階で計算しましょう。
// : var q = a*x + b*y + c; var f = sig(q); // sig – // , df, : var dq = (q * (1 - q)) * df; // var da = x * dq; var dx = a * dq; var dy = b * dq; var db = y * dq; var dc = 1.0 * dq;
ここで何かが解決することを願っています。 そして今、これはどうですか:
var x = a * a; var da = //???
この例を論理要素*に向かって移動する値と見なすことができますが、線は分割されて両方の元の値を表します。 勾配の逆流が常に追加されるため、これは実際には簡単です。 言い換えれば、何も変わりません:
var da = a * dx; // a da += a * dx; //
//短い形式は次のとおりです。
var da = 2 * a * dx;
関数f(a)= a ^ 2の導関数は2aであり、論理要素の2つの初期値に分割する線の形で表現した場合に得られる値であることに注意してください。
別の例を見てみましょう。
var x = a*a + b*b + c*c; // : var da = 2*a*dx; var db = 2*b*dx; var dc = 2*c*dx;
では、もう少し複雑になりましょう。
var x = Math.pow(((a * b + c) * d), 2); // pow(x,2) JS
より複雑なケースが実際に出くわすとき、私は式を解くのが簡単な部分に分割するのが好きです。そして、それはほとんど常により単純な式から成り、その後、それらをチェーンルールを使用して接続します:
var x1 = a * b + c; var x2 = x1 * d; var x = x2 * x2; // x // : var dx2 = 2 * x2 * dx; // x2 var dd = x1 * dx2; // d var dx1 = d * dx2; // x1 var da = b * dx1; var db = a * dx1; var dc = 1.0 * dx1; // !
簡単でした。 これらは、式全体の逆伝播方程式です。1つずつ実行し、すべての変数に対して逆伝播を実行しました。 繰り返しになりますが、前進する過程の各変数について、逆方向のパスには同等の変数があり、回路の最終結果に対する勾配が含まれていることに注意してください。 以下は、実際に役立つ可能性のある、より便利な機能とそのローカルグラデーションです。
var x = 1.0/a; // var da = -1.0/(a*a); : var x = (a + b)/(c + d); // : var x1 = a + b; var x2 = c + d; var x3 = 1.0 / x2; var x = x1 * x3; // // , : var dx1 = x3 * dx; var dx3 = x1 * dx; var dx2 = (-1.0/(x2*x2)) * dx3; // , , var da = 1.0 * dx1; // , , var db = 1.0 * dx1; var dc = 1.0 * dx2; var db = 1.0 * dx2;
式を分解して先に進み、その後、各変数(たとえば、a)に対して、反対方向に移動するときにその勾配daの微分を探し、順番に単純な局所勾配を適用し、それらを上側の勾配に関連付けることを理解してほしい。 別の例を次に示します。
var x = Math.max(a, b); var da = a === x ? 1.0 * dx : 0.0; var db = b === x ? 1.0 * dx : 0.0;
ここでは、非常に単純な式は読みにくくなります。 max関数は、最大の元の値を渡し、残りを無視します。 後方パスでは、最大ゲートは単純に上から勾配を取り、それを元の値に向けます。これは、実際に前進するときにそれを通過しました。 論理要素は、前進するときにどのソース要素が最大の値を持っているかに基づいて、単純なスイッチとして機能します。 他の初期値には勾配がありません。 これは、===が意味することです。どの初期値が実際に最大であったかを確認し、勾配をそれに向けるだけです。
最後に、おそらく聞いたことがある整流線形要素(Rectified Linear UnitまたはReLU)の非線形性を見てみましょう。 シグモイド関数の代わりにニューラルネットワークで使用されます。 彼はゼロでしきい値を超えました:
var x = Math.max(a, 0) // : var da = a > 0 ? 1.0 * dx : 0.0;
言い換えると、この論理要素は、値が0より大きい場合は単に値を渡すか、スレッドを停止してゼロに設定します。 後方への通過中、論理要素は、前方への通過中にアクティブになった場合、上から勾配を送信します。元のソース値がゼロ未満の場合、勾配フローを停止します。
この時点で停止します。 式(多くの論理要素で構成される)全体の計算方法と、各式のエラーの逆伝播の計算方法を少し理解していただければ幸いです。
この章で行ったことはすべて、次のことです。実際の値を持つ複雑な任意の回路を介して初期値を入力し、何らかの力で回路の端を押すと、エラーの逆伝播がこのプッシュを回路全体に伝達できることに気付きました元の値に戻ります。 初期値がプッシュの反対方向にわずかに反応する場合、回路は張力の初期方向の方向に少し「ギブイン」します。 おそらくこれはそれほど明白ではありませんが、そのようなアルゴリズムは機械を訓練するための強力なハンマーです。