見出し画像

機械学習と微分と最急降下法について

はじめに

皆さん、こんにちは。
今回は、機械学習における、微分の意味と目的を、なるべく平易な表現の積み上げにて、話させていただこうかと思っております。


機械学習とは!?

機械学習なんぞや、という話については、以下の記事にまとめてありますので、よければ参照下さい。


機械学習における共通課題は何か?

機械学習における共通課題は、パターンの探索や、解の探索です。
例えば、以下の記事は、機械学習によって、連立方程式の解を探索するものを紹介しています。

要するには、コンピューターの計算速度を活かして、様々な探索をするのが機械学習なのです。
コンピューターの計算速度は、人間を超越する程に高速です。
その特性を活かして、人間に代わって、探索をしてもらう訳です。


機械学習における探索について

しかし、高速とはいえ、コンピューターの計算速度にも限界はあります。
無限に高速ではありません。
また、コンピューターは、人間ほどに複雑に思考することができません。
基本的に、人間が指示した通りにしか動作しません。
つまり、人間が上手く指示をして、人間があたかも思考してるが如く、コンピューターに探索を行ってもらう形になります。

つまり、以下の関係性のイメージです。

スクリーンショット 2020-05-31 19.55.02

また、探索方法については、ヒューリスティックな発想的に、或いは、数学的な理論にベースにして、より効率の良い探索を行う方法が、研究開発されています。
その研究分野全般のことが、機械学習です。
機械が学習をする、或いは、考えるという行為を模倣するための方法論の研究分野、という訳です。

研究が上手くいったものとしては、人間同等、或いは、人間以上の精度を発揮しているものがあります。
画像認識などは、特に上手くいっている研究領域です。
一方で、まだまだ実用から遠いところで、切磋琢磨している研究分野もあります。
或いは、上手くいっている研究領域でも、実用を考慮した場合に、非常に高い精度が求められてしまうものは、未だ実用に難しい状況だったりします。
逆に、そう上手くいっていない研究領域でも、実用を考慮した場合に、そこまで高い精度が求められないようなものは、実用が間近だったりします。


微分を使えば、探索をスマートに行える

そんな機械学習の研究においては、数学・物理学・確率統計などの学問がとても役に立っています。
それらは、従来より物理現象を事前に予測するために発展してきた学問です。
そんな学問が、機械学習による解の探索に、とても上手いこと機能します。

例えば、以下のような過去のデータがあったとします。

スクリーンショット 2020-05-31 21.00.26

過去の販売実績です。
このようなデータが与えられた時に、人はどう考えるかというと、例えば、以下のような傾向を想像するかと思います。

スクリーンショット 2020-05-31 21.02.19

この傾向からは、仕入れるべき商品数が、以下であると予測ができます。

スクリーンショット 2020-05-31 21.08.02

さて、ここまでは直感的な話です。

これをもう少し、数学的な話にしましょう。
仮に、仕入を x、利益を y と置いた場合に、上記の赤線のグラフが、y = −10x²+2000x という式だったとします。

それを、python にてグラフ描画してみると…。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(201)
y = (-10 * (x**2)) + (2000 * x)

plt.figure(figsize=(6, 3), dpi=100)
plt.plot(x, y, color='r', alpha=0.5, linewidth=5)
plt.grid()
plt.xlabel('仕入')
plt.ylabel('利益')
plt.show()

ダウンロード (2)

…という感じになります。
グラフを描画すると、利益が最大となるのは、仕入が100近辺の時であることが分かります。

或いは、グラフに描画せずとも、y = −10x²+2000x  という数式を、平方完成をすることで、利益最大のポイントを見つけることもできます。
平方完成とは、以下のような式変換のことです。

スクリーンショット 2020-05-31 21.39.29

尚、平方完成をした式では、以下のことが言えます。

(1)a > 0の場合、y は、x が -d の時に、最小値 e を取る
(2)a < 0の場合、y は、x が -d の時に、最大値 e を取る

2次方程式の場合、 の係数がプラスの場合は、山が1つの凸型のグラフになり、 の係数がマイナスの場合は、谷が1つの凹型のグラフになります。
というのも、平方完成した式における (x+d)² は必ずプラスであるからです。
a > 0 の時、a(x+d)² は必ずプラスであり、a < 0 の時、a(x+d)² は必ずマイナスです。
よって、(x+d)² の部分が 0 になる x を中心に、左右対称のグラフとなります。
つまり、(x+d)² の部分が 0 になる場合が、山の頂点、或いは、谷底の点となります。
そして、(x+d)² = 0 の時、y = e である為、平方完成式における e こそが、その2次方程式の最大値、または、最小値である訳です。

という訳で、y = −10x²+2000x という式を、平方完成してみると、y = −10(x−100)²+100000 となります。
そして、(x−100)² の係数が −10、つまり、マイナスですから、y は x = 100 の時に、最大値 100000 を取ることが分かります。

さて、平方完成の方法に続けて、次には、微分を用いた方法を紹介します。
y = ax²+bx+c というような2次方程式の場合、y の式を、x について微分し、接線の傾きを求める式を導出した上で、接線の傾きが 0 になる x の値を逆算することでも、y の最大値、または、最小値を求めることができます。

ここで、微分の性質について、改めて説明をさせていただくと、微分とは、接線の傾きを求める計算行為です。
例えば、y = ax+b という式であれば、微分式は y' = a となり、接線の傾きが常に a となります。
また、y = ax²+bx+c という式であれば、微分式は y' = ax+b となり、接線の x 座標毎に、接線の傾きが ax+b という形で変動することになります。

y = −10x²+2000x という式であれば、微分式は y' = 20x+2000 となります。
仮に、x0〜200 の値域で変動する場合、接線の傾きは、x = 0 の時の y' = (−20 ✕ 0)+2000 = 2000 から、x = 200 の時の y' = (−20 ✕ 200)+2000 = −2000 へと、単調減少していく形になります。
接線の傾きを表す式が、単調減少式 y' = 20x+2000 である為です。

以下が、それを図示したものです。
x が、0 から 200 へと変化するに従って、接線の青色が、薄い色から濃い色へと、変化させています。
(大分荒いコードです…💦)

import numpy as np
import matplotlib.pyplot as plt

x  = np.arange(201)
x_ = np.array([-100, 300])
y  = (-10 * (x**2)) + (2000 * x)
y_ = (-20 * x) + 2000

plt.figure(figsize=(6, 3), dpi=100)
plt.plot(x, y, color='r', alpha=0.5, linewidth=5)
xlim_tmp = plt.gca().get_xlim()
ylim_tmp = plt.gca().get_ylim()
for i in range(0, 201, 25):
    alpha_tmp = 0.2+0.05*i/25
    plt.scatter(x[i], y[i], color='b', alpha=alpha_tmp, linewidth=3)
    plt.plot(x_, ((y_[i] * x_) - ((y_[i] * i)) + y[i]), 
             color='b', alpha=alpha_tmp, linewidth=3)
plt.grid()
plt.xlabel('仕入')
plt.ylabel('利益')
plt.xlim(xlim_tmp)
plt.ylim(ylim_tmp)
plt.show()

ダウンロード (5)

尚、y = −10x²+2000x という式は、山が1つの凸型のグラフになります。
そして、接線の傾きが 0 となる点が、山の頂点です。
その為、微分式 y' = −20x+2000 = 0 から、x = 100 が、y の最大値 y = −10✕(100²)+2000✕100 = 100000 を導出する x となります。

微分式から求める方法も、平方完成による方法も、同じ解が求まりました。最大値、または、最小値を探索する対象が、2次方程式である場合、こういった探索ができます。
特に、微分を用いた解き方が、より一般的な解き方となります。


微分値を頼りに、逐次的に探索する最急降下法

さて、ピンポイントで解がバシッと求められる、スマートな探索方法を紹介しましたが、実際のところは、その方法を上手く適用できるケースは、そう多くありません。
特に、最先端のアルゴリズムについては、数式が複雑になる為に、簡単には微分値が 0 になるポイントが求まらないのです。

では、あまり深く考えずに、総当りや、モンテカルロ法で求める方針はどうでしょうか?
尚、総当り、モンテカルロ法を知らない方は、以下の記事を参考にしていただけたらと思います。

総当り、モンテカルロ法は、上記の記事にも書いてありますが、探索が単純過ぎる為、複雑な問題や、最新のアルゴリズム適用ですと、現実時間内に探索を終えることが難しくなってしまいます。

では、どうすれば良いか。
実は、微分で一発で解く方法と、総当たり・モンテカルロ法による探索との、ちょうど中間の方法が存在します。
それが、最急降下法です。
最急降下法とは、ザックリ言うと、ある現象を表した数式の最小値などを、その数式の微分値を参考に、計画的に、逐次的に探索していく方法です。
つまり、微分値を頼りに一発バシッと解を求める訳ではないが、総当たりやモンテカルロ法のように闇雲にではない、そんな中間的な方法となります。
そして、最急降下法は、ディープラーニングなどでも用いられる、最もポピュラーな解の探索方法となります。

さて、それでは実施方法について説明します。
解の探索対象は、先程の式 y = −10x²+2000x とします。
先ず、先程同様に、微分式を求めます。
微分式は、y' = −20x+2000 です。
微分式が求められたら、次に以下を設定します。

  - x の初期位置
  - ステップ幅 η(イータ)

x の初期位置は、逐次的探索を行う際のスタート時点となります。
スタート時点は、求めるべきゴールの近くに設定できると、解までの到達が早くなることが期待できます。
ステップ幅 η については、探索の粒度を示します。
探索の粒度は、荒ら過ぎると、探索が発散してしまい、なかなか収束してくれません。
逆に、細か過ぎると、探索が遅々として、なかなか解に到達してくれません。
探索の様子を観察しながら、適切にチューニングする必要があります。
ここでは、暫定的に、x の初期位置を とし、ステップ幅 η を0.01 とします。

そして、最急降下法にて、最大値 y を探索するアルゴリズムは、以下となります。

[1]x の初期位置を決める
[2]現在の x の位置における、接線の傾き y' を求める
[3]求めた y' に対して、ステップ幅 η を掛ける
[4]現在の x の位置に対して、(y' ✕ η)の値を加える
[5]以降、[2]〜[4]を繰り返す

以上。
つまり、接線の傾きの大きさと、その符号を頼りに、x を微調整するようなアルゴリズムになっています。

イメージとしては、以下です。

スクリーンショット 2020-06-01 2.44.15

山の傾斜を確認し、傾斜が強ければ多めに進み、傾斜が弱ければ少なめに進みます。
この進み幅は、ステップ幅 η に比例します。

これをプログラムで実装すると、以下です。

# import basic library
import numpy as np
import matplotlib.pyplot as plt

# prepare x and y for plot
x  = np.arange(201)
y  = (-10 * (x**2)) + (2000 * x)

# make figure and plot x and y
plt.figure(figsize=(6, 3), dpi=100)
plt.plot(x, y, color='r', alpha=0.5, linewidth=5)
plt.xlim(plt.gca().get_xlim()) # fix xlim
plt.ylim(plt.gca().get_ylim()) # fix ylim
plt.grid()
plt.xlabel('仕入')
plt.ylabel('利益')

# set initial x value and eta value
x_tmp = 0
eta   = 0.01

# calc y value for plot
y_tmp = (-10 * (x_tmp**2)) + (2000 * x_tmp)

# loop of step
for step_i in range(50):
   
    # scatter x and y of step process
    alpha_tmp = np.min([(0.1 + (0.05 * step_i)), 0.5])
    plt.scatter(x_tmp, y_tmp, color='b', alpha=alpha_tmp, linewidth=5)
   
    # backup for plot
    x_before = x_tmp
    y_before = y_tmp
   
    # update x
    x_tmp = x_tmp + (eta * ((-20 * x_tmp) + 2000))

    # calc y value for plot
    y_tmp = (-10 * (x_tmp**2)) + (2000 * x_tmp)

    # plot x and y of step process
    plt.plot([x_before, x_tmp], [y_before, y_tmp], color='b', alpha=alpha_tmp, linewidth=5)
   
# show figure
plt.show()

プログラムの出力結果は以下です。

ダウンロード (6)

プログラムを実施した結果、求められた解は x = 99.998... と求まっています。
尚、最急降下法の特徴として、高い確率で、厳密な解には辿り着かないという特徴があります。
傾きの減少によって、ステップ幅が減少する為です。
しかし、上記のケースで言えば、十分な近似解へと辿り着けている、と言えるでしょう。

ここで、x の初期位置を 80ステップ幅 η0.11 に変更してみると、以下のような形になります。

ダウンロード (7)

青色の薄い方が、出発点で、色の濃い方が、x の現在地に近い方です。
何が起きているかというと、ステップ幅 η が大き過ぎるせいで、谷底を通り越して元々いた y の位置よりも、低い位置まで下がってしまっているのです。
その上、谷底から遠ざかる程、傾きが大きくなってしまうので、次回のステップで、より y の位置が低くなってしまっている形です。

x の初期位置を 0ステップ幅 η0.001 に変更してみると、以下のような形になります。

ダウンロード (8)

今度は、ステップ幅 η が小さ過ぎて、解に辿り着いていない形です。

この辺りが、様子を観察しながら、チューニングを実施しなくてはならないポイントです。


機械学習と微分について

ここまでで説明したことが、機械学習を行う上で、最低限必要となる微分の知識となります。
特に、最急降下法は、しっかり手に馴染ませることが大事かと思います。
ディープラーニングを身に付ける際に、大いに理解の助けになるかと思います。

尚、微分に関しては、以下の記事に記載されるような、公式や計算ルールなどは、覚えておいて損はないと思います。
特に、合成関数の微分や、チェーンルールなどは抑えておくと良いと思います。

実際に、どんな風に最急降下法を適用していくかについては、アルゴリズム別に説明をさせてもらえたらと思います。

しかしながら、先駆者が実装してくれたOSSなどで、微分式などは実装がされています。
必ずしも、微分の仕組みまで理解する必要がなかったりもします。
しかしながら、より深いレベルで機械学習を使いこなそうと思えば、基礎理論を理解しておくことが、助けになろうかと思います。


おわりに

以上で、機械学習微分との関係についての記事を終えます。
ここまで読んでくださった方、ありがとうございました。🙇

この記事が気に入ったらサポートをしてみませんか?