1
2

More than 1 year has passed since last update.

ニューラルネットワークにおける学習機能を機械学習のライブラリを用いずに丁寧に理解しようとした(前半)

Last updated at Posted at 2020-03-20

はじめに

 ニューラルネットワーク(深層学習)における機能に学習があります。より予測モデルの予測率を上げるために行われているモデル内の計算を一から理解することを試みました。

 今回も、オライリーのディープラーニング教科書を参考にさせて頂きました。非常にわかりやすく理解できます。
https://www.oreilly.co.jp/books/9784873117584/

 概要は以下です。

  • ニューラルネットワークにおける学習とは何か(前半:今回)
  • 損失関数とは何か(前半:今回)
  • **関数の微分を実装する(前半:今回)
  • 勾配降下法について(後半)
  • ニューラルネットワークにおける勾配(後半)
  • 学習アルゴリズムを実装してみる(後半)

ニューラルネットワークにおける学習とは何か

 モデルにおける学習とは予測した値を正解に近づける、あるいは正解率を上げることです。
 画像認識を例にとって考えます。有名な数字認識であるMNISTでは、手書き数字を見分けることを行います。
image.png

この画像では人間ではどれも5に見る(脳が学習して認識している)ことができます。では、次にコンピューターによりこの5を認識させるアルゴリズムを作るために必要なことを考えてみます。
 5の「画像」から5を認識させるためには、画像から5だと判別できる「特徴量」を見つけることが必要です。特徴量とは、英語でFeature Selectionと表記されます。直訳すると「特性を選ぶこと」となります。
 5の画像に置き換えてみると、「最初の横棒」、「縦の線」、「270度くらいで、左が開いた円弧」といった特徴のことになります。これら特徴量を抽出し、その抽出した特徴を学習させる流れがコンピュータによって5を認識させるアルゴリズムになります。
 
特徴量を見つける(=抽出する)機能を変換器と呼びます。この有名な変換器としてSIFT,SURF,HOGといったものがあります。詳しくは下記URL等でご覧頂けると幸甚です。このURLでは2011年時の資料となっており、2000年代から発展した技術となっているようです。

 次に、特徴量を使って画像データをベクトルに変換し、そのベクトルを機械学習で使われる識別器と呼ばれる機能で学習させることができます。この識別器として有名なものがサポートベクターマシン(SVM)やK近傍法(KNN)といったものがあります。

 ここで、変換器は特徴に応じて適切に「人」で判断して選択する必要があります。それに対してニューラルネットワークがカバーする範囲は、この変換器も含んでいます。つまり、特徴量を探す変換器自体も学習をさせることが可能なアルゴリズムになります。

image.png

 上記にその概念を図にしました。
 ニューラルネットワークはコンピュータが判断する領域を増やすことで、与えられたデータをそのまま解釈し、その問題のパターンを発見しようとしてくれます。
 人工知能感をより増したアルゴリズムだと理解できます。

損失関数とは何か

 
 さて、次に具体的に予測したデータと正解となるデータの判別を行う考え方をまとめていきます。正解に近いかどうか、を表すために損失関数(loss function)と呼ばれる関数を導入します。

2乗和誤差

 損失関数として最も知られている関数は、2乗和誤差(mean squared error)です。下記に示す数式で表されます。

image.png

 $y_k$はニューラルネットワークの出力、$t_k$は教師データ(正解データ)を示し、$k$はデータの次元数(個数)を示します。式から考えると正解が多いほどこの値が小さくなることがわかります。
 簡単にプログラムで記述してみたいと思います。

nn.ipynb
import numpy as np

def mean_squared_error(y,t):
    return 0.5*np.sum((y-t)**2)

t = [0,0,1,0,0,0,0,0,0,0]
y = [0.1,0.1,0.6,0.1,0.1,0,0,0,0,0]
y1 = [0.1,0.1,0.1,0.1,0.6,0,0,0,0,0]
print(mean_squared_error(np.array(y),np.array(t)))
print(mean_squared_error(np.array(y1),np.array(t)))
0.10000000000000003
0.6000000000000001

 この配列の要素は、最初のインデックスから順に、数字の「0」、「1」、「2」に対応します。ここでyはニューラルネットワークの出力です。ソフトマックス関数により変換された値で、確率を表します。数字の2だと判別する確率が0.6だといっています。さらにtは教師データとなります。つまり正解が数字の2となっています。
 yとy1それぞれについて2乗和誤差を計算したところ、yのほうが近い値となりました。これは、yで出力された値が数字2の要素が最も確率が高いと示していることをきちんと表現できていることがわかります。

 

交差エントロピー誤差

 別の誤差関数として、交差エントロピー誤差があります。
image.png

 logは自然対数を底としています。tkが正解ラベルとなるインデックスのため、正解の時のみ1を出力します。従って、この関数は正解ラベルが1に対応する自然対数を出力する計算なります。実際に実装した結果がこちらになります。 

nn.ipynb

def cross_entropy_error(y,t):
    delta = 1e-7
    return -np.sum(t*np.log(y+delta))

print(cross_entropy_error(np.array(y),np.array(t)))
print(cross_entropy_error(np.array(y1),np.array(t)))
0.510825457099338
2.302584092994546

 ここで、log内の計算で微小な値(0.0000001)を足しています。これは、log(0)となるとマイナス無限大へ発散してしまうため、計算が進まなくなってしまうことを防止する目的で値を足しています。
 結果を見ると、正解となるラベルのy出力が小さいと2.3になりますが、y出力が高いと0.5となります。
 

誤差関数を設定する目的

 
 損失関数は、その得られる値を最小化させることで予測精度の高いモデルへとすることができます。従って、その損失関数が小さくなるようなパラメータを探す必要があります。このとき、このパラメータの微分した値を手掛かりにパラメータを更新していきます。
 微分とはその関数の勾配を知ることができます。微分に関する基本的な内容はここでは割愛します。

image.png

 この勾配の値が正であれば、負の方向にパラメータ(図ではa)を動かすことで最小値へ近づきます。また、逆に勾配の値が負であれば正の方向にパラメータを動かすことで最小値へ近づくことが想像できるかと思います。

関数の微分を実装する

 さて、関数の微分について考えたいと思います。関数を微分させることは、(1)解析的に解くことと、(2)離散的に解く(差分を取る)ことの二つのアプローチがあります。
 手を動かして人間が行う場合は(1)で行いますが、プログラムで解くうえでは(2)が便利です。今回は下記図にある中心差分の考え方を実装していきます。

 今回はこちらの関数を離散的に微分させた値を求めたいと思います。

image.png

nn.ipynb
import numpy as np
import matplotlib.pyplot as plt

def numerical_diff(f,x):
    h =1e-4 #0.0001
    return (f(x+h)-f(x-h))/(2*h)

def function_1(x):
    return 0.01*x**2 + 0.1*x
numerical_diff(function_1,5)
0.1999999999990898

image.png

添付の曲線が元の関数、直線がx=5における勾配になります。

偏微分を実装する

 
 次に、下記に示す2変数関数の偏微分を行うことを考えます。

image.png

元の関数を描くと下記のような3次元のグラフになります。

image.png

nn.ipynb
def function_2(x):
return x[0]**2 + x[1]**2

偏微分とは、微分を行う変数を決めて他の数値を定数とみて微分を行うことを指します。
x0を偏微分させ、x0=3,x1=4の時の値を求めます。

nn.ipynb
def function_tmp1(x0):
    return x0*x0 +4.0**2.0

numerical_diff(function_tmp1,3.0)
6.00000000000378

 変数が一つだけの関数として定義して、その関数を微分しています。ただこの場合だといちいち変数とする値以外は代入するなどの処理をしなければなりません。x0,x1まとめて微分をしたいことを考えます。これは下記のように実装することができます。

 

nn.ipynb
def numerical_gradient(f,x):
    h =1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val =x[idx]
        x[idx] =tmp_val + h
        fxh1 = f(x)

        x[idx] = tmp_val -h
        fxh2 = f(x)

        grad[idx] = (fxh1-fxh2)/(2*h)
        x[idx] = tmp_val

    return grad

numerical_gradient(function_2,np.array([3.0,4.0]))
array([6., 8.])

この微分した値は、元の関数の勾配を示すことを先に説明しました。さらに、この微分した値をベクトルとして捉えて図示することを考えます。便宜上マイナスをつけて表すと下記のようになります。

image.png

(x0,x1)=(0,0)に矢印が向かっていることが分かります。これは、損失関数の議論で最小値を求めることがモデルの精度を上げることに繋がります。この微分操作により損失関数の最小値を探すことができ、モデルの最適化につながることが分かりました!

終わりに

 今回はこの微分操作がモデルの精度向上に繋がることを理解するところまで進めました。ニューラルネットワークの肝である学習の中身を一つ一つみることで、理解が深まりました。
 次回、後半の記事で、実際にニューラルネットワーク上での実装まで進めることで学習を丁寧に理解したいと思います。

後半はこちらです。
https://qiita.com/Fumio-eisan/items/7507d8687ca651ab301d

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2