今回も前回に引き続いてニューラルネットワークについてご説明いたします。少し触れましたが、第四回にわたる記事ですので本記事含め後二つの記事を通じて本テーマは完結する予定です。

初めてのニューラルネットワークの学習

ニューラルネットワークの学習では、まずエラーを評価し、それに応じて重みを調整します。重みの調整には、勾配降下法とバックパック法が使われます。ウェイトの調整には、勾配降下法とバックプロパゲーションのアルゴリズムを使用します。勾配降下法は、パラメータを更新するための方向とレートを見つけるために適用されます。

逆誤差伝搬について

ネットワークに変更を加える前に、誤差を計算する必要があります。それが次のセクションで行うことです。

予測誤差の計算

誤差の大きさを理解するためには、誤差を測定する方法を選択する必要があります。誤差を測定するための関数は、コスト関数、または損失関数と呼ばれます。このチュートリアルでは、コスト関数として平均二乗誤差(MSE)を使用します。MSEは2つのステップで計算します。予測値と目標値の差を計算します。その結果に自分自身をかけます。

ネットワークは、正しい値よりも高いまたは低い値を出力することで、ミスを犯す可能性があります。MSEは、予測値と正しい結果の差の二乗であるため、この指標では常に正の値が得られます。

以下は、直前の予測に対する誤差を計算するための完全な式です。

In [24]: target = 0

In [25]: mse = np.square(prediction – target)

In [26]: print(f “Prediction: {prediction}; Error: {mse}”)

上の例では,誤差は0.75です.差分を掛け合わせることの意味は、大きな誤差はさらに大きな影響を与え、小さな誤差は減少するにつれて小さくなっていくということです。

誤差を小さくする方法

目標は、重みとバイアスの変数を変更して、誤差を減らすことです。この仕組みを理解するために、重みの変数だけを変更し、バイアスは固定したままにしておきます。また、シグモイド関数を削除し、layer_1の結果のみを使用することもできます。後は、誤差が小さくなるように重みをどう変更するかを考えるだけです。

MSEの計算は、error = np.square(prediction – target)で行います。(prediction – target)を1つの変数xとして扱うと、error = np.square(x)となり、これは2次関数となります。この関数をプロットするとこんな感じになります。

誤差はY軸で与えられます。もしあなたがA点にいて、誤差を0に近づけたいのであれば、xの値を小さくする必要があります。一方、B点にいて誤差を小さくしたい場合は、x値を大きくする必要があります。誤差を減らすためにどの方向に進むべきかを知るには、微分を使います。微分とは、あるパターンがどのように変化するかを正確に説明するものです。

微分の別名は「グラディエント」。勾配降下法は、ネットワークのパラメータを更新する方向と速度を見つけるために使用されるアルゴリズムの名前です。

注:勾配降下法の背後にある数学について詳しく知りたい方は、Stochastic Gradient Descent Algorithm With Python and NumPyをご覧ください。

このチュートリアルでは、導関数の理論には触れませんので、各関数の導関数ルールを適用するだけです。べき乗則では、xⁿの微分はnx⁽⁽-¹⁾となります。つまり、np.square(x)の導関数は2 * xであり、xの導関数は1である。

誤差の表現は、error = np.square(prediction – target) であることを覚えておいてください。この関数の微分を取ることで、誤差の結果をゼロにするためにはxをどの方向に変化させればよいかを知り、誤差を減らすことができます。

ニューラルネットワークの場合、導関数は、重みの変数を更新するために取るべき方向を教えてくれます。これが正の数であれば、予測値が高すぎたので、重みを減らす必要があります。負の数であれば、予測が低すぎたので、ウェイトを増やす必要があります。

次は、前回の間違った予測に対するwights_1の更新方法を把握するためのコードを書く番です。平均2乗誤差が0.75の場合、重みを増やすべきか減らすべきか?微分は2 * xなので、予測値と目標値の差を2倍すればよいことになります。

In [27]: derivative = 2 * (prediction – target)

In [28]: print(f “The derivative is {derivative}”)

結果は1.74で、正の数なので、重みを減らす必要があります。ウェイトベクトルの導関数の結果を引くことでそれを行います。これでwights_1を適宜更新し、再度予測して、予測結果にどのような影響があるかを確認します。

In [29]: # 重みの更新

In [30]: weights_1 = weights_1 – derivative

In [31]: prediction = make_prediction(input_vector, weights_1, bias)

In [32]: error = (prediction – target) ** 2

In [33]: print(f “Prediction: {prediction}; Error: {error}”)

誤差はほぼ0になりました。この例では微分の結果が小さかったのですが、微分の結果が大きすぎる場合もあります。の画像をとってみましょう。

二次関数のイメージを例に挙げてみましょう。点Aから点Bに向かってまっすぐ進み続け、ゼロに近づくことができないため、高い増分は理想的ではありません。この問題に対処するために、微分結果の分数で重みを更新します。

ウェイトを更新するための分数を定義するには、アルファパラメータ(学習率とも呼ばれる)を使用します。学習率を下げると、増分が小さくなります。学習率を上げれば、増分は大きくなります。最適な学習率の値を知るにはどうすればいいのでしょうか?推測して試してみましょう。

注:従来のデフォルトの学習率の値は、0.1、0.01、0.001です。

新しい重みを使って、最初の入力ベクトルで予測を行うと、今度はその入力ベクトルに対して間違った予測を行っていることがわかります。ニューラルネットワークが学習セットのすべてのインスタンスに対して正しい予測を行う場合、モデルがオーバーフィットしている可能性があります。この場合、モデルはデータの特徴を認識することを学習する代わりに、単にサンプルを分類する方法を記憶するだけです。

このような状況を避けるために、正則化や確率的勾配降下法などの手法があります。このチュートリアルでは、オンラインの確率的勾配降下法を使用します。

誤差を計算する方法と、それに応じて重みを調整する方法がわかったところで、いよいよニューラルネットワークの構築を続けましょう。

鎖の法則の適用

ニューラルネットワークでは、重みとバイアスベクトルの両方を更新する必要があります。誤差を測定するために使用している関数は、重みとバイアスの2つの独立変数に依存します。重みとバイアスは独立した変数なので、求める結果を得るために、それらを変更・調整することができます。

今回のネットワークは2層構造で、各層がそれぞれの関数を持っているので、関数合成をしていることになります。つまり、誤差関数はnp.square(x)のままですが、xは別の関数の結果になっています。

問題を言い換えると、誤差を減らすためにweight_1とbiasをどのように変更するかを知りたいということです。このために微分が使えることはすでに見ましたが、内部に和だけを持つ関数ではなく、他の関数を使って結果を出す関数ができました。

このような関数構成になっているので、パラメータに関する誤差の微分を取るには、微積分の連鎖法則を使う必要があります。連鎖律では、各関数の偏微分を取り、それらを評価し、すべての偏微分を掛け合わせて、求める微分を得ます。

これで、重みの更新を始めることができます。誤差を減らすためには、どのように重みを変更すればよいかを知りたいと思います。これは,重みに対する誤差の導関数を計算する必要があることを意味します.誤差は異なる関数を組み合わせて計算されるので、これらの関数の偏導関数を取る必要があります。ここでは、連鎖律を適用して重みに対する誤差の導関数を求める方法を視覚的に示しています。

太い赤い矢印は、求める導関数derror_dweightsを示しています。赤い六角形からスタートして、予測を立てて各関数で偏微分を計算するという逆の道をとります。上の画像では、各関数は黄色の六角形で表され、偏導関数は左の灰色の矢印で表されています。連鎖律を適用すると、derror_dweightsの値は以下のようになります。

derror_dweights = (

derror_dprediction * dprediction_dlayer1 * dlayer1_dweights

)

微分を計算するには、誤差のある六角形(赤いもの)から重みを求める六角形(一番左の緑のもの)までの経路に沿って、すべての偏微分を掛け合わせます。y = f(x)の導関数は、xに対するfの導関数であると言うことができます。この命名法を使って、derror_dpredictionでは、予測値に対する誤差を計算する関数の導関数を知りたいのです。

この逆方向のパスをバックワードパスと呼ぶ。各バックワードパスでは、各関数の偏微分を計算し、変数をその値で代入し、最後にすべてを掛け合わせます。この「偏導関数をとって、評価して、乗算する」という部分が、連鎖律の適用方法です。このようにニューラルネットワークのパラメータを更新するアルゴリズムをバックプロパゲーションといいます。

バックプロパゲーションでパラメータを調整する

このセクションでは、バックプロパゲーションの手順を、バイアスの更新方法から順を追って説明します。誤差関数のバイアスに対する導関数、derror_dbiasを取ります。そして、バイアス変数を見つけるまで、偏微分を取りながら逆方向に進みます。最後から始めて逆戻りしているので、まず、予測値に対する誤差の偏微分を取る必要があります。それが下の画像のderror_dpredictionです。

誤差が生じる関数は二乗関数で、この関数の導関数は、先ほど見たように2 * xです。最初の偏微分(derror_dprediction)を適用してもまだバイアスに到達していないので、もう一歩戻って、前の層を基準にした予測の微分(dprediction_dlayer1)を取る必要があります。

予測値はシグモイド関数の結果です。sigmoid(x)と1 – sigmoid(x)を掛け合わせることで、シグモイド関数の微分を取ることができます。この微分式は、すでに計算されたシグモイドの結果を使って、その微分を計算することができるので、とても便利です。そして、この部分的な導関数を使って、後ろに進みます。

今度は、layer_1のバイアスに対する導関数を計算します。やっと出てきましたね。バイアス変数は独立変数なので、べき乗則を適用した後の結果は1です。 さて、この後ろ向きのパスが完了したので、すべてをまとめてderror_dbiasを計算することができます。

In [36]: def sigmoid_deriv(x):

…: return sigmoid(x) * (1-sigmoid(x))

In [37]: derror_dprediction = 2 * (prediction – target)

In [38]: layer_1 = np.dot(input_vector, weights_1) + bias

In [39]: dprediction_dlayer1 = sigmoid_deriv(layer_1)

In [40]: dlayer1_dbias = 1

In [41]: derror_dbias = (

…: derror_dprediction * dprediction_dlayer1 * dlayer1_dbias

…: )

重みを更新するには、同じ手順で、重みの変数にたどり着くまで、偏微分を逆にとります。偏導関数のいくつかはすでに計算済みなので、dlayer1_dweightsを計算するだけで済みます。ドットプロダクトの導関数は、第1のベクトルに第2のベクトルを掛けたものの導関数と、第2のベクトルに第1のベクトルを掛けたものの導関数を足したものです。

以上になりますが、いかがでしたでしょうか。前回と比較すると具体的なコーディングがあったため複雑に感じたかもしれませんが、最後までついて来ることができたでしょうか。特に本分野は専門的な用語や定義などがよく登場するので一つ一つおさえて、ゆっくり学習していきましょう。

次の記事について