勾配降下法
勾配降下法
この手法は、一般的な関数 に対し、その最適解(最小解) をボールが坂道を転げ落ちて最も低い位置に落ち着くのと同じような原理を用いて求める手法と思ってもらうとイメージがし易いのではないかと思います。
は何次元の関数でも問題ないのですが、イメージし易いように、3次元空間上の表面を表す2変数1値関数 という関数で解説します。
まず、 平面を地表にはり、地球の中心から遠ざかる向きに 軸をとります。
この座標上に
という曲面をはり、任意の場所にボールをおいた場合、このボールは重力により、最も低い位置に向かおうとします。
これをプログラミング的にシミュレートします。 まず、任意の位置 および をとります。 ここで、ここでの曲面の傾き( の微小変化)を偏微分を用いて求めます。
このとき を最小に向かう方向に を動かしていきたいので となるように を選ばなければなりません。従って、 の符号と逆になる方向にそれぞれ を選ぶようにして、次の点を と決めます。 の符号の決め方は決まったとしても、 をどのように選ぶかが決まっていないと思うかもしれませんが、実はこれは適当に選んでもらってかまいません。(ただし各成分の大きさはそれぞれの傾きに比例させる)実際、ボールは連続的に低い方に動いていくわけですが、プログラミング的にはこれをコマ送りするような形になります。コマ送りの間隔をどのように選ぶかについては、トレードオフがある話ですので、バランスのとれた適当なところにする必要があります。大きすぎると最小値に収束せずに、発散してしまい、小さすぎると、(最小値を求めるための)処理速度が著しく悪化するということになるためです。あえて表現するならば、可能な限り小さく ということになります。
あとは、これを繰り返していけば、いずれ十分に大きな数 回目には となり、その時の が最小値となるという算段です。
具体的に解を求める(=深層学習により最適関数を求める)
勾配降下法を理解したところで、ディープラーニングに戻って、勾配降下法を用いて、関数 を求めてみましょう。
n組の学習用サンプリングデータ(教師データとも言う)を
として
という関数が、サンプリングデータから得られる最も適切な関数であるということは、いったいどのような状態のことでしょうか。
それは
が最も低い値( )を取る時の となります。
難しく聞こえそうな表現を使っていますが、仮に において
となる場合(すなわち少なくともサンプリングデータと同じ入力に対しては、近似解ではなく、理想解を出力できる場合ということ)、この はこれ以上ない形で最適になるわけですが、実際
となることから想像すれば、 が に近ければ近いほど が小さくなることも想像できると思うので、この関数の の最小値を求めるということが最適な を求めるということと同値になります。
今、
として、
の が最小となる時の を求めたいので、これは
という 個の変数の多変数一値関数の最小値およびその時の
を求めることと同義になるのです。
ちょっと混乱しそうなので、整理しておきましょう。
関数 | 変数 | 説明 |
---|---|---|
求めたい関数。 は最終的には定数となる | ||
|
(3* 個),z | 上段の を求めるために導入したコスト関数。 は定数 |
この関数 の最小値およびその時の を先に解説した勾配降下法により求めてやれば良いのですが、あまりに変数が多く、記述が煩雑になるため、 の場合(すなわち隠れ層のニューロンが1つ、サンプリングデータの数も1つ)を解説します。
さらに、計算を簡単にするために、コスト関数である の形を少し変形します。 はサンプリングデータ を に代入して得られる値 と の距離をサンプリング数分、合算して得られる値(>0) として定義されていますが、この距離は一般的な(ユークリッド幾何学上の)距離である必要はなく、数学的な 距離関数 の定義を満たしていれば、問題ありません。(学習速度を高めるためにはこの関数を適当に選ぶことが極めて重要になってきますが、ここでは原理を説明するために、最も微分がし易い距離関数を選びます)
ということで、以下のように設定を変更します。
関数 | 変数 | 説明 |
---|---|---|
求めたい関数。 は最終的には定数となる | ||
上段の を求めるために導入したコスト関数。 は定数 | ||
に を代入して展開したもの |
この時
となり、これらは、関数としてプログラミング可能であることがわかります。
では、実際にディープラーニングさせるためのプログラムを組んでみましょう。 まず、 はひとまとまりで扱っていきたいので構造体を定義します。
class UWB {
double u;
double w;
double b;
}
また上記で示した偏微分関数を次のとおり定義します。
double partialCbyU(UWB uwb);
double partialCbyW(UWB uwb);
double partialCbyB(UWB uwb);
この時任意の点 からほんのすこし坂道を転げ落ちたときの点は、次の関数で求められます。
UWB next(UWB uwb, UWB delta) {
UWB nextUWB = new UWB();
nextUWB.u = uwb.u + ((partialCbyU(uwb) > 0)?-1:1)*delta.u;
nextUWB.w = uwb.w + ((partialCbyU(uwb) > 0)?-1:1)*delta.w;
nextUWB.b = uwb.b + ((partialCbyU(uwb) > 0)?-1:1)*delta.b;
return newxtUWB;
}
この関数を繰り返し呼び出せばよいのですが、そのままでは永久ループしてしまうので、収束 をプログラミング的に表現してあげなければなりません。 それは、次のようになります。
static final double EPSILON = 0.0001;//適当な小さい値
boolean isSmallEnough(UWB delta) {
return (delta.u * delta.u + delta.w * delta.w + delta.b * delta.b) < EPSILON * EPSILON ;
}
UWB diff(UWB uwb0, UWB uwb1) {
UWB uwb=new UWB();
uwb.u=uwb0.u - uwb1.u;
uwb.w=uwb0.w - uwb1.w;
uwb.b=uwb0.b - uwb1.b;
return uwb;
}
UWB nextDelta(UWB diff, UWB nextDiff) {
UWB nextDelta=new
UWB(); //差の符号が逆向きになったということは、最下点を通り過ぎて振り子が帰ってきたということ。次の振れ幅を半分にする nextDelta.u=(diff.u *
nextDiff.u> 0)?nextDelta.u:nextDelta.u / 2;
nextDelta.w = (diff.w * nextDiff.w > 0)?nextDelta.w:nextDelta.w / 2;
nextDelta.b = (diff.b * nextDiff.b > 0)?nextDelta.b:nextDelta.b / 2;
}
UWB findMinimumC(UWB initialUWB, UWB initialDelta) {
UWB uwb = initialUWB;
UWB delta = initialDelta;
UWB diff = delta;
while (!isSmallEnough(delta)) {
UWB nextUWB = next(uwb, delta);
UWB nextDiff = diff(uwb, nextUWB);
delta = nextDelta(diff, nextDiff);
diff = nextDiff;
uwb = nextUWB;
}
return uwb;
}
初期値として適当な を選びfindMinimumC関数を呼び出し、この時戻り値として戻ってくるUWBオブジェクトの を に代入してあげれば、それが深層学習を終えた最適な関数となります。(上記コードは動作原理を説明するためのものであり、動作を保証するものではありません。)