局所最小とは、近くでだけいちばん低い点
局所最小(きょくしょさいしょう)とは、すぐ近くでは最も低いが、遠くにもっと低い点があるかもしれない谷底のことです。勾配降下法は、出発点に近い谷へ入ると、そこで止まります。
SciPy の最適化の道具を使うと、少ない書き方で最小値を探せます。ただし道具も、行き詰まりは避けられません。何が起きるかを知っておきます。
このレッスンのゴール
メモ
SciPy で最小値を探し、勾配降下法が行き詰まる場合を見分けることができる。
キーワード
| ことば | いみ |
|---|---|
| 局所最小 | 近くでは最も低いが、全体では最も低いとは限らない点 |
| 大域最小 | 関数全体で、いちばん低い点 |
| 平らな所 | 傾きが 0 でも、最小とは限らない点 |
| 発散 | 更新のたびに値が大きく離れていくこと |
| 最適化 | 関数の値を最小や最大にする入力を探すこと |
学生が入力するセルは、このプレビューでは空白です。
谷が2つあると、出発点で行き先が変わる
関数 f(x) = x⁴ − 3x² + x は、谷が2つあります。右の谷は x が約 1.131 で、値は −1.07 です。左の谷は x が約 −1.301 で、値は −3.514 と、さらに低いです。
勾配降下法は、足元の坂を下るだけです。出発点が右にあれば右の谷、左にあれば左の谷に入ります。
例: 出発点ごとの行き先
| 出発点 | 行き着く x | そこでの値 | 全体で最も低いか |
|---|---|---|---|
| 2 | 1.131 | −1.07 | いいえ(局所最小) |
| −2 | −1.301 | −3.514 | はい(大域最小) |
| 0.1 | −1.301 | −3.514 | はい(大域最小) |
学生が入力するセルは、このプレビューでは空白です。
出発点を変えて行き先を見る
# 谷が2つある関数 f(x) = x⁴ - 3x² + x。傾きは 4x³ - 6x + 1 です。
def slope(x):
return 4 * x ** 3 - 6 * x + 1
for start in [2.0, -2.0]:
x = start
for step in range(2000):
x = x - 0.01 * slope(x)
print("出発点", start, "から行き着いた x =", round(x, 3))
# 出発点を 0.1 に変えて、行き先を予想してから実行してください。
SciPy で出発点 2 から最小値を探す
docker exec jupyter python3 -c "from scipy.optimize import minimize; r = minimize(lambda x: x[0] ** 4 - 3 * x[0] ** 2 + x[0], [2.0]); print(round(r.x[0], 3), round(r.fun, 3), r.success)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
出発点 −2 から探す
docker exec jupyter python3 -c "from scipy.optimize import minimize; r = minimize(lambda x: x[0] ** 4 - 3 * x[0] ** 2 + x[0], [-2.0]); print(round(r.x[0], 3), round(r.fun, 3), r.success)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
SciPy の minimize は出発点から探す
scipy.optimize.minimize は、関数と出発点を渡すと、最小値を探して結果を返します。x が見つけた位置、fun がその値、success が「探索が終わった」という印です。
成功と表示されても、見つけたのは出発点に近い谷です。全体で最も低いとは、限りません。
傾きが 0 でも最小とは限らない
最小値の点では傾きが 0 になりますが、逆は成り立ちません。f(x) = x³ は、x = 0 で傾きが 0 ですが、左へ行くほど値が下がり、最小ではありません。
勾配降下法は、傾きが 0 の点で動けなくなります。傾きがとても小さい平らな所でも、更新が進まず、止まって見えます。
傾き 0 の点では更新で動かない
docker exec jupyter python3 -c "x = 0.0; print(x - 0.1 * 3 * x ** 2)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
学習率が大きすぎると値が発散する
学習率が大きすぎると、更新のたびに値が谷を飛び越えて、大きく離れていきます。コンピュータは、そのまま計算を続けて、とても大きな数を作ります。
そこで、値の大きさに上限を決めておき、超えたら「発散」とみなして止めます。止まったら、学習率を下げてやり直します。
発散を見つけて止める
# 同じ関数を、学習率 0.01 と 0.2 で試します。|x| が 100 を超えたら発散とみなします。
def slope(x):
return 4 * x ** 3 - 6 * x + 1
for rate in [0.01, 0.2]:
x = 2.0
blown_up = 0
step = 0
while step < 100 and blown_up == 0:
step = step + 1
x = x - rate * slope(x)
if abs(x) > 100:
blown_up = step
if blown_up > 0:
print("学習率", rate, ":", blown_up, "回目で発散")
else:
print("学習率", rate, ": 収束 x =", round(x, 3))
# 学習率を 0.05 に変えて、どうなるか見てください。
例: 行き詰まりの種類と対策
| 様子 | 原因 | 対策 |
|---|---|---|
| 浅い谷で止まる | 局所最小 | 出発点を変えて、何度も試す |
| ほとんど動かない | 平らな所、小さすぎる学習率 | 学習率を少し上げる、出発点を変える |
| 値が大きく離れる | 大きすぎる学習率 | 学習率を下げて、上限を決める |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
止まった点は、全体で最も低い点だと考えがちです。実際は、近くの谷で止まっただけかもしれません。出発点を変えて何度か試し、行き先をくらべます。