🙆‍♀️

勾配降下法の数理を理解したい!

2024/12/10に公開

この記事はteam411 Adovent Calendar 2024アドベントカレンダーの10日目です。

昨日はkanaruさんの「【Web標準】CSSとCanvasの狭間としてのSVG」でした。svgがwebとの相性がいいという知識を得たので、今開発中の個人サイトにも使ってみようかなぁ...?なんて思ってたりします。面白かったです。

はじめに

こんにちは、team411所属、コンピュータ・サイエンスプログラム2年のSHINNです!なぜか昨日10日にアドベントカレンダーを書くことが決定したので一生懸命記事を執筆しています(現在2024/12/10 18:53)。

(追記)
時間がなかったため途中ですが記事を公開します。12/11にしっかり内容を完成させますお許しくださいmm

(追記2)
12/11に完成する気配すらみえなくなってきました...。とりあえず課題をしばきつつ記事を完成させます。がんばります。すみません。

概要

この記事ではニューラルネットワークの実装でよく使われる最適化アルゴリズムの一つである「勾配降下法」について書いていこうと思います。記事は以下の流れで進みます。

  1. ニューラルネットワークに関する簡単な説明 (後日)
  2. 勾配降下法のアルゴリズムの説明
  3. 勾配降下法の実例
  4. 数学の視点から勾配降下法を見る (後日)
  5. 勾配降下法の注意点と発展 (後日)

ぜひ最後までご覧ください!

1. ニューラルネットワークに関する簡単な説明 (後日)

2. 勾配降下法のアルゴリズムの説明

では実際に勾配降下法について見ていきましょう。勾配降下法は以下の手順で最適化を実行するアルゴリズムです。

2.1. パラメータの初期値を設定

まずは損失関数のパラメータの初期値\bm{w} = \bm{w}_0を決定します。このとき、初期値の決定方法には様々な方法がありますが、簡単のために一様分布からランダムにパラメータを選んでくる方法を考えます。

2.2. 損失関数の勾配を求める

次に損失関数の重みwに関する勾配\bm{\nabla}_{\bm{w}}L(\bm{w})を求めます。これは単純に偏微分をするだけの簡単なお仕事です。

2.3. 収束の判定

勾配を求めたら損失関数が最小値に収束したかを調べます。最小値への収束を調べるには勾配の値が0になったかを調べれば十分です。ただ、実際にピッタリ0担ったかを調べるのは極めて難しいので、とても小さな値\varepsilonを用意して

\bm{\nabla}_{\bm{w}}L(\bm{w}) < \varepsilon

を満たすかを調べればよいです。もしこれを満たしていれば最小値に収束したと判定してアルゴリズムを終了します。一方、これを満たしていなければアルゴリズムを続行します。

2.4. パラメータを更新する

上にもとめた勾配を用いてバラメータの更新を行います。パラメータの更新は以下の式にしたがって行います。

\bm{w}_{\text{next}} = \bm{w}_{\text{now}} - \eta\bm{\nabla}_{\bm{w}_{\text{now}}}L(\bm{w})

ここでは便宜上、更新後のパラメータを\bm{w}_{\text{next}}、更新前のパラメータを\bm{w}_{\text{now}}としました。また、式に出てきた定数\etaは「学習率」とよばれている、パラメータの更新を制御するハイパーパラメータです。

さて、以上のパラメータの更新を行ったら2.2に戻ってアルゴリズムを繰り返します。これが勾配降下法です。

3. 勾配降下法の実例

では、実際の関数を用いて勾配降下法を使って理解していきましょう。今回は関数f(x) = (x - 1)^2の最小値を勾配降下法をもちいて求めます。

この勾配降下法を実装すると次のようになります。

import random

def loss_function(x):
    return (x - 1)**2

def gradient_loss_function(x):
    return 2*(x - 1)

def gradient_descent():
    param = random.uniform(-10, 10) # パラメータ(初期値は-10から10の範囲からランダムに選択)
    epsilon = 0.1 # 収束条件
    eta = 0.1 # 学習率
    grad = gradient_loss_function(param)
    print('-----')
    print(f'初期パラメータ:{param}')
    print(f'初期勾配:{grad}')
    print('-----')

    while(abs(grad) > epsilon):
        param = param - eta*grad # パラメータの更新
        grad = gradient_loss_function(param) # 更新したパラメータにおける勾配を計算
        print(f'-----')
        print(f'パラメータ:{param}')
        print(f'勾配:{grad}')
        print('-----')

# 関数の実行
gradient_descent()

以下コードの説明です。

def loss_function(x):
    return (x - 1)**2

def gradient_loss_function(x):
    return 2*(x - 1)

この部分は今回の損失関数である(x - 1)^2とその導関数である2(x - 1)を定義しています。

次はgradient_descent()の中身について説明します。

param = random.uniform(-10, 10) # パラメータ(初期値は-10から10の範囲からランダムに選択)
epsilon = 0.1 # 収束条件
eta = 0.1 # 学習率
grad = gradient_loss_function(param)

ここでは初期パラメータ\bm{w}_0(今回は次元が1なのでスカラーとして扱って良い)、収束条件の微小値\varepsilon、学習率\eta、初期勾配\bm{\nabla}L(\bm{w_0})を求めています。初期パラメータは-10から10の範囲の中からランダムに一つ選ぶようにプログラムしました。

while(abs(grad) > epsilon):
    param = param - eta*grad # パラメータの更新
    grad = gradient_loss_function(param) # 更新したパラメータにおける勾配を計算

ここではパラメータの更新を行っています。whileの条件が収束したか否かの判定に対応します。

以上のコードを実行すると次のような出力を得ます。

-----
初期パラメータ:-1.4761195622774732
初期勾配:-4.952239124554946
-----
-----
パラメータ:-0.9808956498219785
勾配:-3.961791299643957
-----
-----
パラメータ:-0.5847165198575828
勾配:-3.1694330397151655
-----
-----
パラメータ:-0.2677732158860662
勾配:-2.5355464317721323
-----
-----
パラメータ:-0.014218572708852906
勾配:-2.0284371454177057
-----
-----
パラメータ:0.18862514183291768
勾配:-1.6227497163341646
-----

...一部省略

-----
パラメータ:0.891099109980492
勾配:-0.21780178003901596
-----
-----
パラメータ:0.9128792879843937
勾配:-0.17424142403121268
-----
-----
パラメータ:0.930303430387515
勾配:-0.1393931392249701
-----
-----
パラメータ:0.944242744310012
勾配:-0.11151451137997603
-----
-----
パラメータ:0.9553941954480096
勾配:-0.08921160910398074
-----

少し長くなってしまっていますが、パラメータが更新されていくにつれて勾配の値が0に近づいていっているのがわかると思います。この様子を図にすると以下のようになります。

勾配降下法のイメージ図

このように青い矢印で書いたようにパラメータの値がどんどん最小値を取るx = 1に近づいている様子が伺えます。なんとなく感覚がつかめてきたでしょうか?ふわっとでもつかめたらオッケーです!

4. 勾配降下法を数学の視点から見る (後日)

さて、このまま「勾配降下法を知ることができましたね!わーい」で終わらせても良いのですが、それでは世に蔓延る勾配降下法の記事と何ら変わりなく新規性のない記事となってしまいます。そんなのは僕のプライドが許せません。
ということで、ただアルゴリズムだけに着目するのではなく、その背景にある数理まで深ぼって行きたいと思います。

4.1 勾配降下法の収束性を調べる

このようなステップを踏んで数値解を得るようなアルゴリズムにおいて一番興味があるかつ大切になってくるのはこのアルゴリズムは本当に収束するのだろうかという問題です。

5. 勾配降下法の注意点と発展 (後日)

おわりに

中途半端な記事になってしまい申し訳ありません。思ったより書くことがハードすぎて「1日でかけるでしょ」という考えが甘かったと認めざるを得ませんでした。というか、勾配法の収束性の証明が思った10倍ハードで1から勉強しないとのレベルだったのが悪い。 ただ、勾配法のアルゴリズムに関しては全部書くことができたのでよしということで(なにもよくない)。
ともかく、この記事を通してニューラルネットワーク、ひいては最適化という分野に少しでも興味を持ってくれる人が増えれば幸いです。

さて、明日の記事はまくらさんが書いてくれます。APIについての記事みたいですね。楽しみです。

ではここらへんで終わりにしてさっさと課題をしばいて記事を完成させましょう。

参考文献

以下、この記事を書くにあたって参考にした記事を載せます。より深く勾配降下法を理解したい人はぜひ参考にしてください。
https://aiwannabe.com/gradient-descent-explained/
https://www.stat.cmu.edu/~ryantibs/convexopt-F13/scribes/lec6.pdf

Discussion