https://greencolor.net Just another WordPress site Fri, 11 Feb 2022 21:35:18 +0000 ja hourly 1 https://wordpress.org/?v=5.8.3 https://greencolor.net/wp-content/uploads/2020/11/cropped-cropped-ひらめき-32x32.png https://greencolor.net 32 32 【計算グラフで理解する】誤差逆伝播法①(ソフトマックスと交差エントロピー) https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/backpropagation_softmax_crossentropy/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/backpropagation_softmax_crossentropy/#respond Thu, 27 Jan 2022 11:33:21 +0000 https://greencolor.net/?p=4261 さて、いよいよ誤差逆伝播法がやってきました。

この誤差逆伝播法に何回心をへし折れたことか・・・

今、悩みまっしぐらの方も諦めずに続ければ道は開けます。一緒に頑張りましょう。

誤差逆伝播法とは?

誤差逆伝播法とは「損失関数で計算した誤差を限りなくゼロに近づけるために、重みとバイアスを調整する方法」です。

“誤差”を出力層から入力層に向かって”逆”に”伝播”させることから「誤差逆伝播法」と言います。意識高い系サイトでは「Backpropagation(バックプロパゲーション)」と言ったりもします。

なぜ難しく感じるのか?

誤差逆伝播法を調べるとほとんどのサイトで「連鎖律」や「合成関数の微分」というキーワードが頻出します。

$\dfrac{\partial L}{\partial w}=\dfrac{\partial L}{\partial o}\dfrac{\partial o}{\partial u}\dfrac{\partial u}{\partial w}$

「連鎖律とは」「微分とは」のように誤差逆伝播法を理解するためにはまずは数学を理解しなければいけないというのが一番のハードルになっている気がします。

今回は連鎖律を勉強しなくても理解できる「計算グラフ」を使って解説をしていきます。

この記事で理解できること

・誤差がどのように逆伝播しているかがわかる。
・ソフトマックス関数の逆伝播がわかる。
・交差エントロピー誤差の逆伝播がわかる。
・全結合層での逆伝播がわかる。

前提となる知識は、ソフトマックス関数の順伝播および交差エントロピー誤差の順伝播の仕組みが理解できていることを前提とします。

交差エントロピーの誤差逆伝播

さっそく計算グラフをみていきます。

【計算グラフで理解する】交差エントロピー誤差 -ミニバッチ版-で説明した順伝播の計算グラフに赤字で逆方向に追記したものが誤差逆伝播です。

3つのルール

交差エントロピー誤差の誤差逆伝播を理解するのには3つのルールを理解する必要があります。

それは各ノード(丸の部分)に掛かれている演算子の取り扱いです。

演算子 ルール
逆伝播してきた値をそのまま次のノードへ受け渡す
×

順伝播時の値を「ひっくり返して」次のノードへ受け渡す

$log$ $log$ $xの場合、\dfrac{1}{x}$とする

これさえ覚えれば難しい数式を理解しなくても理解することができます。

今回の解説では一番上のルートを解説していきます。(他のルートもやり方は同じなので省略します)

誤差逆伝播のスタートは「1」

まずは一番右の値に注目してください。

一番右では、順伝播で損失を計算した誤差を$L$として表現しており、ここが誤差逆伝播のスタート地点です。

そして逆伝播のスタートを「1」としていますが、適当に「1」と置いたわけではなく、誤差逆伝播のスタートは1で始まります。これは損失を損失で約分しているためです。

$\dfrac{\partial L}{\partial L}$

真剣に理解すればしようとするほど、こういう何気ない数字でも「なんで1なの?説明のためにとりあえず1にしてるの?」と疑問を抱いてしまいますよね^^;

1つ目のニューロン「×」

ここでは「×」のルールに従って、順伝播時の値をひっくり返して次のノードへ受け渡します。

①順伝播の時は $Σ$ と $\dfrac{1}{batchsize}$ が流れてきていますのでこれをひっくり返す。
②次に逆伝播で流れてきた 1 にひっくり返した $\dfrac{1}{batchsize}$ を掛けて次のニューロンへ受け渡す。

結果、次のニューロンには $\dfrac{1}{batchsize}$ が流れていきます。

ひっくり返されたもう一つの値($Σ$)は、次に受け渡すニューロンがないので捨てられます。

2つ目のニューロン「+」

次は「+」のルールに従って、逆伝播してきた値をそのまま次のノードへ受け渡します。

逆伝播してきた値は$\dfrac{1}{batchsize}$ですので、何も手を加えずにそのまま次のニューロンへ受け渡します。

3つ目のニューロン「×」

次は「×」のルールに従って、順伝播時の値をひっくり返して次のノードへ受け渡します。

①順伝播の時は $t^0_1$ $log$ $y_1$ + $t^0_2$ $log$ $y_2$ と -1 が流れてきていますのでこれをひっくり返す。
②次に逆伝播で流れてきた $\dfrac{1}{batchsize}$ にひっくり返した -1 を掛けて次のニューロンへ受け渡す。

結果、次のニューロンには $-\dfrac{1}{batchsize}$ が流れていきます。

ひっくり返されたもう一つの値($t^0_1$ $log$ $y_1$ + $t^0_2$ $log$ $y_2$)は、次に受け渡すニューロンがないので捨てられます。

4つ目のニューロン「+」

次は「+」のルールに従って、逆伝播してきた値をそのまま次のノードへ受け渡します。

逆伝播してきた値は $-\dfrac{1}{batchsize}$ ですので、何も手を加えずにそのまま次のニューロンへ受け渡します。

5つ目のニューロン「×」

次は「×」のルールに従って、順伝播時の値をひっくり返して次のノードへ受け渡します。

①順伝播の時は $t^0_1$ と $log$ $y_1$ が流れてきていますのでこれをひっくり返す。
②次に逆伝播で流れてきた $-\dfrac{1}{batchsize}$ にひっくり返した $t^0_1$ を掛けて次のニューロンへ受け渡す。

結果、次のニューロンには $-\dfrac{t^0_1}{batchsize}$ が流れていきます。

ひっくり返されたもう一つの値($log$ $y_1$)は、次に受け渡すニューロンがないので捨てられます。

6つ目のニューロン「$log$」

最後は「$log$」のルールに従います。

①順伝播の結果をルールに従い、$log$ $y_1=\dfrac{1}{y_1}$ とします。
②次に逆伝播で流れてきた $-\dfrac{t^0_1}{batchsize}$ に①を掛けて次のニューロンへ受け渡す。

結果、次のニューロンには $-\dfrac{t^0_1}{batchsize}$$\dfrac{1}{y_1}$ が流れていきます。

ここまでが交差エントロピー誤差の誤差逆伝播です。

「×」「+」「log」のたった3つのルールを使うだけで、誤差逆伝播を理解することができました。

次回説明するソフトマックス関数も同じで、簡単なルールを覚えるだけで難解な誤差逆伝播を理解することができます。

是非、次回もお楽しみに!

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/backpropagation_softmax_crossentropy/feed/ 0
【計算グラフで理解する】交差エントロピー誤差 -Not One-Hot版- https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_not_one_hot/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_not_one_hot/#respond Sat, 22 Jan 2022 01:08:18 +0000 https://greencolor.net/?p=4248 前々回はOne-Hot版、前回はミニバッチ版を解説してきました。

これらを踏まえて今回はNot One-Hot版を解説していきます。

Not One-Hotとは?

One-Hotは分類する数だけ要素を持っていましたが、Not One-Hotは正解のインデックスのみで構成される正解データです。

文書ではイメージしづらいので少し図示化してみます。

図のように正解要素のみを抜き出したデータで構成されているのがNot One-Hotです。

Not One-HotのメリットはOne-Hotよりデータ量が少ない分、処理が軽量になるという点です。ただでさえ何万件、何十万件とある学習データで計算コストが大きいのに、正解データも比例して大きくなることは避けたいですよね。

どのように損失を計算するのか?

最初に結論となるコードみていきます。その後、各ステップの解説をします。

batch_size = 100 ## 1バッチサイズを100とする
delta = 1e-7

idx = np.random.choice(y.shape[0],batch_size) ## 10,000個の予測値からバッチサイズ分のデータのインデックスをランダムに取得
print(idx) ## 取得したインデックス
## [753 654 782  11 486 854 807 527 249 496 392 627 294  70 630 581  82 313
##  813 524 660 851 549 363 737 218 938 285 204 364 969 318 656 774   5 963
##  820 795 822 497 785 455 392 844 247 108 365 907 638 269 586 142 272 385
##  492 766 498 408  49 860 788 748 998  99 713 566 834 648 593 453 639 732
##  868 747 428 994  52 280 870 146 726 936 710 807 167 956 101 656 139 834
##  938 688 969  39 995 265 743 408 456 155]

y_train = y[idx] ## 取得したインデックスと合致する予測データを取得
print(y_train.shape) 
## (100, 3)

t_train = t[idx] ## 取得したインデックスと合致する正解データを取得
print(t_train.shape)
## (100, 3)

### これより上の解説は前回記事を参照してください ###

t = t_train.argmax(axis=1)

E = -np.sum(np.log(y[np.arange(batch_size), t] + delta)) / batch_size

print(E)
## 1.275536533021017

One-Hotから正解インデックスを取得

t = t_train.argmax(axis=1)の解説をします。

t_trainはOne-Hot形式のデータで、t_trainの各行で最大値が格納されている列(インデックス)を取得します。

t_trainの各行の最大値を格納した結果は以下の通りです。

print(t)
## [0 1 2 1 0 1 1 1 0 0 2 1 1 0 0 1 2 0 0 1 0 1 0 0 1 2 2 2 1 1 0 2 0 1 2 0 0
##  2 2 1 2 2 2 1 1 2 2 1 2 0 0 2 2 2 2 0 2 2 0 0 2 2 2 0 0 0 1 2 0 0 0 2 2 1
##  2 1 2 0 0 0 0 0 0 1 1 0 1 0 0 1 2 2 0 0 2 2 0 2 0 2]

正解ラベルに該当する予測データを取得

y[np.arange(batch_size), t]の解説にうつります。

前述の通り、Not One-Hotの正解データは正解のインデックスのみが格納されているので、次に各行から正解インデックスの位置に該当するデータを取得します。

イメージでみていきましょう。

各行ではソフトマックス関数で出力された確率が並んでいます。0行目に対応する正解データのインデックスを見ると0列目が正解データとなっているので「0行目の0列目」の予測値を取得します。

同じように1行目、2行目・・・と繰り返し取得し、各行から正解ラベルに該当する予測値を取得します。

y[np.arange(batch_size), t]を使って補足説明すると0行目はy[0, 0]となり「予測データの0行0列目を取得」します。

対数をとったあと総和をとりバッチサイズで割る

-np.sum(np.log(y[np.arange(batch_size), t] + delta)) / batch_sizeの解説になります。

先程、y[np.arange(batch_size), t]は計算したのであとは式通りに計算するだけです。

前回記事でも解説していますが、最後にbatch_sizeで割るのは1データあたりの平均損失を計算するためです。

 

今回はNot One-Hotの解説をしました。最初の頃は同じ交差エントロピー誤差でもなんで計算式が違うのだろう??と混乱してました^^;

同じように理解が進まなかった人が、今回の記事で解決に繋がればと思います。

 

次回はソフトマックス関数と交差エントロピー誤差の誤差逆伝播について解説を予定しています。

 

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_not_one_hot/feed/ 0
【計算グラフで理解する】交差エントロピー誤差 -ミニバッチ版- https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot_batch/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot_batch/#respond Fri, 21 Jan 2022 10:55:30 +0000 https://greencolor.net/?p=4237 前回の記事では【計算グラフで理解する】交差エントロピー誤差 -One-Hot版-を解説しました。

今回はミニバッチ版を解説していきます。(本解説を先にすることで次回解説予定の「Not One-Hot版」の理解がしやすくなると思います)

ミニバッチとは

「一定のデータの塊を処理する単位」をバッチと言います。

例えば今、手元に1,000枚の画像データがあるとします。この1,000枚の画像データからランダムに100枚を学習用データとして切り出し、ニューラルネットワークに流し込み、学習を行うとします。このとき、1,000枚から100枚の画像データを1つの塊として抽出し、学習させることをミニバッチ学習と呼びます。

「ミニバッチっていうくらいだから、バッチ学習もあるの?」と疑問に思われた方のために補足します。

データを流し込む単位は3種類あります。

・ミニバッチ
・バッチ
・オンライン

今回の本題ではないので詳細は割愛しますが、以下の特徴があります。

学習単位 学習安定性

外れ値の影響
(受けにくさ)

学習速度 メモリ負荷
(負荷の少なさ)
ミニバッチ 一定の塊
バッチ すべて
オンライン 1件ずつ

それぞれにメリット・デメリットがありますが、モデル構築においてはミニバッチが利用されることが多いです。

機会があれば解説のページを作りたいと思います。

ミニバッチ版の説明前提

ミニバッチ版の交差エントロピー誤差の具体的な説明に入る前に前提となる条件を設定しておきましょう。

■条件
・最終出力層の分類は3つ
・インプットの画像データは1,000枚で、1バッチサイズを100枚とする。

計算グラフ

着目すべきポイントは最後にバッチサイズで割る箇所です。

前回の記事で1データだけ処理するときには含まれていなかったステップです。

ミニバッチで処理するときは100枚で1つの単位として処理するため、1つ1つのデータの損失を足し合わせ、バッチサイズ(今回は100)で割ることで1データあたりの平均損失を計算する必要があるためです。

私もまだ勉強中の身ではありますが、学習したての頃はバッチサイズで割るケースと割らないケースの明確な切り分けが分かっておらず理解がフワフワしていたので、同じような人の疑問解消になればと思います。

特に今後解説予定である、誤差逆伝播でだいぶ混乱をしていた時期がありました^^;

実装

つぎに実装の解説になります。

基本的には前回記事のコード内容に対してバッチサイズで割るステップを追加するだけになります。

最初に結論となる実装内容を示したあとに各ステップを解説していきます。

■交差エントロピー誤差(ミニバッチ版)

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

 

■ミニバッチ処理

batch_size = 100 

idx = np.random.choice(y.shape[0],batch_size)
print(idx) 
## [365 263 180 893 691 221 335 637 594 479 903 589 756 951 466   4 976 642
##  233 819 895 647 845 112 929 516 296 775 475 815 628 671  99 917 162 633
##   81  58 469 493 976 745 727 447 845 613 885 740 361 145 903 354 172 719
##  114 372 806 859 415 734 114 103 579 686 351 938 142 579  20 446 300 563
##  905 282 168 844 535 742 155 157 652 771 909 627 269  27 323 833 186 227
##  229 927 422 217  67 261 425 498   5 283]

y_train = y[idx] 
print(y_train.shape) 
## (100, 3)

t_train = t[idx]
print(t_train.shape)
## (100, 3)

E = cross_entropy_error_batch(y_train,t_train) ## 交差エントロピー誤差で損失を計算
print(E)
## 1.3621181234900572

結果として、100データ分(1バッチ分)の損失は1.3621181234900572となりました。

交差エントロピー誤差(ミニバッチ版)の定義式

def cross_entropy_error_batch(y,t):
  delta = 1e-7 ## -infを回避
  return -np.sum ( t * np.log( y + delta )) / batch_size ## バッチサイズで割る

前回記事で解説した定義式に対してbatch_sizeで割っているだけのシンプルな構造となります。

基本式については前回解説してますので、ここでは割愛をします。

バッチサイズ設定とインデックス取得

つぎはバッチサイズと母集団データからのデータ抽出です。

事前の条件設定として「1,000枚の母集団から100枚を1バッチのサイズとして処理する」としていましたのでそれに従い実装します。

batch_size = 100 ## 1バッチサイズを100とする

idx = np.random.choice(y.shape[0],batch_size) ## 10,000個の予測値からバッチサイズ分のデータのインデックスをランダムに取得
print(idx) ## 取得したインデックス
## [365 263 180 893 691 221 335 637 594 479 903 589 756 951 466   4 976 642
##  233 819 895 647 845 112 929 516 296 775 475 815 628 671  99 917 162 633
##   81  58 469 493 976 745 727 447 845 613 885 740 361 145 903 354 172 719
##  114 372 806 859 415 734 114 103 579 686 351 938 142 579  20 446 300 563
##  905 282 168 844 535 742 155 157 652 771 909 627 269  27 323 833 186 227
##  229 927 422 217  67 261 425 498   5 283]

batch_sizeは100として設定します。

次にidxとしている部分ですが、1,000件の予測値から1バッチサイズ分をランダムに抽出します。抽出するのはデータそのものではなくて、インデックス情報を抽出します。

インデックス情報から予測データを取得

つぎに1,000件の予測データから100件分のデータを取得しますが、抽出対象データの特定はさきほど取得したインデックス情報に紐つくデータを取得します。

y_train = y[idx] ## 取得したインデックスと合致する予測データを取得
print(y_train.shape) 
## (100, 3)

インデックス情報から正解データを取得

同じく、1,000件の正解データから100件分のデータを取得します。抽出方法はさきほどと同様です。

t_train = t[idx] ## 取得したインデックスと合致する正解データを取得
print(t_train.shape)
## (100, 3)

1バッチサイズ分の損失を計算

最後に取得したデータの損失を計算します。

E = cross_entropy_error_batch(y_train,t_train) ## 交差エントロピー誤差で損失を計算
print(E)
## 1.3621181234900572

 

以上が交差エントロピー誤差(ミニバッチ版)の処理となります。

次回はこれを踏まえて、Not One-Hot版を解説します。

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot_batch/feed/ 0
【計算グラフで理解する】交差エントロピー誤差 -One-Hot版- https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot/#respond Tue, 18 Jan 2022 11:25:08 +0000 https://greencolor.net/?p=4211 交差エントロピー誤差は多値分類の損失関数としてが用いられ「正解と予測値の交わり具合(差)を定量的に表したもの」になります。

「交差」や「エントロピー」の意味についてはこちらの記事でご紹介をしています。

計算グラフ

早速ですが、交差エントロピー誤差の計算グラフについて確認してみます。

なぜ計算グラフで理解するのか?

計算グラフで理解するメリットは2つあります。

・難しい計算式を分解し部品化することで「処理の流れ」を簡単に理解することができる
・図で理解するので、忘れにくい。

計算グラフの解説

これを踏まえて先ほどの計算グラフを見てみましょう。

上流からソフトマックス関数の出力値$y$が伝達され、$log$による計算がされます。次に$×$に伝達されますが、ここで新たな入力値として$t$が渡され掛け算がなされます。途中で入ってきた$t$は正解ラベルです。

他の系統でも同じように処理がされたのち、$+$で総和をとります。

最後に$-1$をかけることで交差エントロピー誤差は終了です。

ソフトマックス関数と組み合わせた計算グラフ

右が交差エントロピー誤差を計算グラフで表したものです。(左はソフトマックス関数)

ソフトマックス関数から出力した値を交差エントロピーの損失関数に流し込み、最終的に1つの誤差として算出されていることがわかります。

計算グラフの効果は誤差逆伝播法で一番の効果を発揮しますが、順伝播でもこのようにソフトマックスとの処理を可視化することで頭の整理ができます。

ここからは具体的な処理を交えながらソフトマックス関数の理解を進めていきます。

簡単なイメージ

こちらの記事でソフトマックス関数で確率変換した値を交差エントロピーで誤差を算出するイメージ図です。

単純に分類を予測するだけなら損失関数は必要ありません。モデルの精度を上げるため(学習させるため)に損失関数で誤差を算出しています。

交差エントロピーのパターンは3種類

交差エントロピーには取り扱うインプットの形状によって大きく3パターンに分かれます。

■教師データの形状
・One-Hot(今回の記事)
・Not One-Hot

■入力データの形状
・ミニバッチ版

インプットによって処理の違いがある、ということを理解しながら調べないと誤解釈してしまう可能性があるので気を付けてください。

One-Hotでの交差エントロピー誤差

教師データのOne-Hotとはどういうものなのかを見ていきます。

One-Hotとは「正解のインデックスの値が1、それ以外のインデックスの値は0」というデータです。

実際に具体的なデータを使ってみていきます。

## 教師データ
t = np.array([1,0,0])
print(t) 
## [1 0 0] → [猫,犬,人]

## ソフトマックス関数で確率変換した予測値
print(y)
## [0.72139918 0.26538793 0.01321289] → 以前の記事で利用した値

教師データのOne-Hotは [ 1 , 0 , 0 ] のように正解データが1、それ以外の値が0の配列になっているデータです。ソフトマックス関数で出力した確率の位置がそれぞれに対応しています。

それでは、交差エントロピー誤差で処理をしていきます。

細かいステップの説明の前に最終系で処理をし、その後ステップごとに説明します。

delta = 1e-7

## 交差エントロピー誤差
cross_entropy_error = -np.sum(t * np.log(y + delta))

print(cross_entropy_error)
## [0.326562502647972]

交差エントロピー誤差は-np.sum(t * np.log(y + delta))で計算をします。

式でみてもイメージがつかないので図示化します。

このように教師データと予測値の対応するデータ同士を計算し、最後に総和を取って-1をかけます。

結果、交差エントロピーで処理した結果、正解との誤差は約0.33となりました。

ちなみに計算過程で-1を掛ける理由は、ソフトマックス関数で確率変換され1未満となった各予測値をネイピア数を底にもつlogで計算するとマイナスの値をとるため、-1をかけて正の数にしています。

log_e = np.log(0.6) 
print(log_e)
## -0.5108256237659907 ← マイナスの値になる

 

今回、予測と正解が一致していたため誤差が小さくなっていますが、モデルの予測結果と正解がズレていた場合、どのような大きさの誤差がでるのかみてみましょう。

正解は人だったと仮定して処理をしてみます。

y = np.array([softmax_a,softmax_b,softmax_c])
t = np.array([0,0,1]) ## 正解を人にするため一番右に1をセット

print(y)
## [0.72139918 0.26538793 0.01321289]

print(t)
## [0 0 1]
delta = 1e-7

## 交差エントロピー誤差
cross_entropy_error = -np.sum(t * np.log(y + delta))

print(cross_entropy_error)
## 4.326555072927414

モデルによると人である確率は1%と予測していますが、正解は人であったので誤差としては約4.3となり、さきほどの結果より13倍も大きい誤差になっています。

つまり、予測値と正解値が違う場合には誤差が大きくなることがわかりました。

① deltaを定義

交差エントロピー誤差の冒頭にdelta = 1e-7を設定します。

1e-7は0.0000001と非常に微小な値を表していますが、なぜ1e-7を足し合わせる必要があるのかをみていきます。

One-Hotは「教師データと予測値の対応するデータ同士を計算する」と説明しました。

その教師データには1と0が存在しますが、0のみで計算するとこのような結果になってしまいます。

log_inf = np.log(0) 
print(log_inf)
## -inf

” – inf ” という結果になりました。これは負の無限大を意味しており、以降の計算において何を演算しても無限になってしまうという不都合が起こってしまいます。そこで結果に影響を及ぼさないくらい微小な値を足し合わせることで” – inf ” を回避しようという考えです。

delta = 1e-7 ## 0.0000001

log_inf = np.log(0 + delta) 
print(log_inf)
## -16.11809565095832

微小な値を加えることで” – inf ” を回避できましたね。

②予測値と正解値の乖離具合を計算

つぎにモデルの予測値が正解値に対してどれだけ乖離しているかを計算します。

その計算がt * np.log(y + delta)に該当します。

これは何なんだ一体、と思わず言ってしまいそうな式ですが、ちゃんと格式ある数式になります。

統計力学で分子のバラバラ具合を定量的に表すものとしてボルツマンの関係式があります。

$S=KlogW$

( 概念的な説明はこちらで記載していますので興味がある方は一度目を通してみてください )

この公式を交差エントロピーでそのまま利用しているため、エントロピーという名前がついています。

実際に計算してみます。

print(y) ## [0.72139918 0.26538793 0.01321289]
print(t) ## [1 0 0]

entropy = t * np.log(y + delta)
print(entropy)
## [-0.3265625 -0.        -0.       ]

予測値(y)と正解値(t)の各要素が計算されました。

③誤差の合計を計算

最後に②で算出した各要素の総和をとりマイナス1をかけた結果を誤差とします。

entropy_sum = np,sum(entropy) * -1 
print(entropy_sum) 
## 0.326562502647972

以上が、One-Hotによる交差エントロピー誤差の処理になります。

次回はミニバッチ版の交差エントロピー誤差について説明していきます。

 

 

 

 

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy_one_hot/feed/ 0
【計算グラフで理解する】ソフトマックス関数 https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/softmax_calculation_graph/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/softmax_calculation_graph/#respond Sat, 15 Jan 2022 23:38:55 +0000 https://greencolor.net/?p=4168 3つ以上を分類(多値分類)する際、ニューラルネットワークの最終層で活性化関数としてソフトマックス関数が用いられる。(ちなみに2つを分類するにはシグモイド関数を利用。この章では取り扱わない。)

計算グラフ

早速ですが、ソフトマックス関数の計算グラフについて確認してみます。

なぜ計算グラフで理解するのか?

計算グラフで理解するメリットは2つあります。

・難しい計算式を分解し部品化することで「処理の流れ」を簡単に理解することができる
・図で理解するので、忘れにくい。

計算グラフの解説

これを踏まえて先ほどの計算グラフを見てみましょう。

上流からインプットとして$u$が流れてきて、$exp$に到達したあと2系統に分かれています。1つは$×$へ伝達するもの、もう一つは$+$に伝達するもの。

$+$の方向では、他の$exp$からも値が伝達されており、伝達された3つの値を$+$で総和をとっています。その後は$/$で逆数をとり、元の経路$×$へ戻していることがわかります。

あとはそれぞれの経路で$×$で処理をし、終了です。

このように計算の過程を「流れ」として捉えることで、小難しい式をシンプルに理解することができます。

ソフトマックス関数は比較的単純な式のため、計算グラフの有難みを感じにくいかもしれませんが、交差エントロピー誤差や誤差逆伝播のような複雑な処理においてその効果を最大限に発揮してきます。

これを機に計算グラフをしっかり理解することをお勧めします。

ここからは具体的な処理を交えながらソフトマックス関数の理解を進めていきます。

簡単なイメージ

入力した画像をすべてのニューロンに伝え、それぞれの過程で計算され出力された結果をソフトマックス関数で確率に変換します。

上の図ではソフトマックス関数によって、猫:72%、犬:27%、人:1%として確率変換されました。

次に実際にどのように確率変換されているのかをみていきます。

確率変換の手順は3つ

①ニューロンの出力結果を指数関数で計算する

入力画像が伝達される過程で計算された結果(今回の場合[2,1,-2])を指数関数\(exp\)で計算します。

import numpy as np

a = np.array([2,1,-2]) # 伝達される過程で計算された結果
print(a)
## [ 2 1 -2]

exp_a = np.exp(a) # 指数関数 e で計算
print(np.round(exp_a,1))
## [7.4 2.7 0.1]

②指数関数\(exp\)結果の総和をとる

つづいて、指数関数\(exp\)で計算した結果の総和をとります。

np.sum(exp_a) 
## 10.242673210626307

単純に足しているだけですね。

③最後に「①÷②」する

最後は「①÷②」で計算します。

softmax_a = exp_a[0] / np.sum(exp_a)
softmax_b = exp_a[1] / np.sum(exp_a)
softmax_c = exp_a[2] / np.sum(exp_a)

print(softmax_a) ## 0.7213991842739688
print(softmax_b) ## 0.26538792877224193
print(softmax_c) ## 0.013212886953789417

それぞれの出力結果から、猫:72%、犬:27%、人:1%と冒頭のイメージ図通りの確率になりました。

単純に総和せず、\(exp\)を使う理由

それぞれの値を総和で割るなら\(exp\)を使わずに単純な総和で割っても一緒でしょ?という疑問を持つ方もいると思います。

結論から先にいうと「マイナスの値をうまく考慮するため」です。

実際に検証していきます。

単純に足し算した場合

import numpy as np

a = np.array([2,1,-2])
print(a)
## [ 2  1 -2]

add_a = a[0] / np.sum(a)
add_b = a[1] / np.sum(a)
add_c = a[2] / np.sum(a)

print(add_a) ## 2.0
print(add_b) ## 1.0
print(add_c) ## -2.0

猫:200%、犬:100%、人:-200%という結果になりました。

計算する値にマイナスがあるため、単純に足すだけではダメだということがわかりました。

絶対値をとった場合

マイナスをプラスとして処理する方法として絶対値をとる方法があります。

実際に検証してみましょう。

import numpy as np

a = np.array([2,1,-2])
print(a)
## [ 2  1 -2]

## 絶対値へ変換
abs = np.abs(a)

print(abs)
## [2 1 2]

abs_a = abs[0] / np.sum(abs)
abs_b = abs[1] / np.sum(abs)
abs_c = abs[2] / np.sum(abs)


print(abs_a) ## 0.4
print(abs_b) ## 0.2
print(abs_c) ## 0.4

単純な足し算とは違い、すべの確率を足すと100%になるので確率計算としてはうまくいきました。

結果をみてみると、猫:40%、犬:20%、人:40%となっています。この画像は猫でしたが、人の確率がぐっと上がっていることがわかります。

言い換えると、猫の画像を入れて猫か人かわからないような結果になっているとも言えます。これでは使いものにならないですね。

\(exp\)を使う理由

入力画像に猫を入れて、ニューラルネットワーク内で計算された結果 [ 2 , 1 , -2 ] が出力されています。

左から [ 猫 , 犬 , 人 ] であったので、それぞれの数字の意味として数字の大小関係が分類に影響していることがわかります。

つまり、この大小関係を保ちつつ、確率化するためには\(exp\)が適しているということです。

公式

この流れは次の式で定義されます。

\( y_k = \dfrac{exp(a_k)}{\sum_{i=1}^{n}exp(a_i)}\)

公式にすると一気に難しく見えますが、上で説明したように非常にシンプルです。

ミニバッチ版

次は実践でよく利用されるミニバッチ版でのソフトマックス関数をみていきます。

今までの説明はソフトマック関数が本来持つ本質を理解してもらうための説明でしたが、実際の実装においてはもう少し考慮が必要な要素がでてきます。

その点を踏まえて、より実践的な解説をしていきます。

配列の形状を意識する

ミニバッチ版でもやっていることの本質は変わりませんが、配列の形状を意識しないと実装できない(できても意図とは違う結果になる)ケースがあります。

まずは結果から

まずは最終的に実装される内容を提示しておきます。この時点でコード内容を理解する必要はありませんが、本質的には今まで説明してきたことと一緒なんだな、ということは覚えておいてください。

## ソフトマックス関数
def softmax(y):
  _o = y - np.max(y,axis=1,keepdims=True)
  return np.exp(_o) / np.sum(np.exp(_o),axis=1,keepdims=1)

説明の前提

今までの説明では分類したい画像を1つ入力し、[ 猫 , 犬 , 人 ]のいづれなのかを予測するものでした。 

今回、分類する種類はそのままで、入力する画像を2つにして説明をします。

入力データの形状

はじめに入力データを準備します。

1つ目のデータは先ほど同様「猫」で、2つ目のデータは「人」にします。

import numpy as np

y = np.array([[2,1,-2],[3,5,7]]) ## 猫と人の画像

print(y)
## [[ 2  1 -2]
##  [ 3  5  7]]

当たり前ですが、2つの画像をインプットしているので、2行のデータになっています。

念のため、.shapeで形状を確認しておきます。

print(y.shape) ##(2, 3)

想定通り、入力データの形状は2行3列になっています。

各行単位でソフトマックス関数を施す

つづいて、各行単位でソフトマックス関数を施します。

各行とは「猫」と「人」の各画像のことを指しますので、各行単位でソフトマックス関数処理し、確率を求めます。

①各行内の最大値取得する

まず、各行から配列の形状を保持したまま、最大値を取得します。(最大値を取得する意味はこのあとすぐ説明します)

np.max(y,axis=1,keepdims=True)
## [[2] 
##  [7]]

このとき単純にnp.maxしてはいけません。

なぜなら、配列内のすべての要素から最大となる1つの値を取得しまうためです。

そのためにaxisで取得する方向を指定します。

各行(横方向)から最大値を取得する場合、axis=1と指定します。

もう一つ大切なのが、配列の形状を保つためにkeepdims=Trueを指定しておく必要があります。

形状を保たないと、このあとの計算で「配列の形状が違うから計算できないよ」とエラーになってしまいます。

②各行各値から最大値を引く

つぎに、各行の各値からさきほど取得した最大値を引きます。

理由は、計算過程でオーバーフロー回避するため、です。

ソフトマックス関数では$exp$で計算をするため、指数となる数が大きくなると結果も指数的に大きくなるため、計算結果がオーバーフローを起こしてしまう可能性があります。

オーバーフローを回避するため、各行の最大値を各値から引きます。

同じ値を各値から引いているので、確率計算には影響を与えないのがポイントです。

_o = y - np.max(y,axis=1,keepdims=True)
## [[ 0 -1 -4] ## [-4 -2 0]]

③総和を求める

確率を求めるためには各行の値を各行の総和で割れば求められますね。

先程と同様、各行の総和を求める場合、足す方向を指定する必要があります。

そのためにaxisで足す方向を指定します。

各行(横方向)の総和を求める場合、axis=1と指定します。

## 各行の総和を求める
np.sum(np.exp(y),axis=1,keepdims=True)

もう一つ、次元をキープするためにkeepdims=Trueを指定します。

最後に「②÷③」する

最後は②÷③をして終了です。

def softmax(y):
  _o = y - np.max(y,axis=1,keepdims=True)
  return np.exp(_o) / np.sum(np.exp(_o),axis=1,keepdims=True)
o = softmax(y)
print(o)
##[[0.72139918 0.26538793 0.01321289] ## [0.01587624 0.11731043 0.86681333]]

【再掲】計算グラフ

最後にソフトマックス関数の計算グラフを再掲します。

冒頭にも説明しましたが、計算グラフで表現すると値と処理の流れが可視化されるため、非常に理解が進みます。ソフトマックス関数の順伝播のようなシンプルな処理ではそれほど計算グラフの効果は感じられませんが、複雑な処理の場合、計算グラフはその威力を発揮します。特に誤差逆伝播法では最大限にその効果が感じられることでしょう。

まだ計算グラフに見慣れていない方は、是非マスターすることをおすすめします。

次回はソフトマックス関数と同時に語られる交差エントロピー誤差について解説を予定しています。

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/softmax_calculation_graph/feed/ 0
交差エントロピーの ” 交差 ” と ” エントロピー ” の意味をまとめてみた https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy/#respond Mon, 10 Jan 2022 08:53:57 +0000 https://greencolor.net/?p=4117 ディープラーニングを学べばもれなくついてくる交差エントロピー誤差。

必ず出てくるので一度は勉強するのですが後々「交差エントロピーってなんだったっけ?」「平均二乗誤差との使いわけってどうしてたっけ?」みたいな疑問がでてきます。

そんなことをずっと繰り返しその都度調べるのが面倒になってきたので自分の備忘録としてまとめておきます。

そんな交差エントロピーですが、名前からして取っつきにくいヤツですが、中身は非常にシンプルです。

$$ E = -\sum_{k}\ t_k log y_k $$

交差エントロピー誤差の特徴

・3つ以上の分類タスク(多値分類)で利用
・logの底はネイピア数($ e $)
・活性化関数のソフトマックス関数と合わせて利用
確率的勾配降下法と相性が良い

“エントロピー”ってなに?

原子的排列および運動状態の混沌(こんとん)性・不規則性の程度を表す量。

全っ然、わかりません。。説明聞いて逆に頭が混沌としてマス…(=_=)

エントロピーをわかりやすい表現になおすと、” 状態のバラバラ具合を表すもの ” です 。

交差エントロピー誤差では正解と予測値のエントロピー、つまり、バラバラ具合を計算しているんですね。

ちなみに交差エントロピー誤差はディープラーニングの世界で初めて定義されたものではなく、物理の世界で定義された公式を用いています。物理学では「物事というのは放っておくと、バラバラになる性質がある」という特徴をこの公式を使って定量化しています。身近にある例でいうと、

・氷は自然に溶けるが、溶けたものが自然に固まることはない。
・拡散した気体は元には戻らない。

といったように日常に起こる変化がエントロピーというもので定義できるということです。

“交差”ってなに?

二つ(以上)の線状のものが、十文字やすじかいに交わること

何が交わるの?って感じですが、ディープラーニングの世界では「2つの確立分布がどれくらい離れているか、つまり、どれくらい交差しているか」を表現するものです。

いや、わからん…

という気持ちはわかりますが、これを正確に理解するのであれば統計学の知識が必要になります。

ここでは交差エントロピーを理解することを優先にしていているので敢えて咀嚼していうと「正解と予測値の2つの交わり具合」を交差と呼ぶことにします。

まとめ

交差エントロピー誤差とは「正解と予測値の交わり具合(差)を定量的に表したもの」になります。

平均二乗誤差も正解と予測値の差を表したものですが、物理学の世界で利用されているエントロピーを採用しているものが交差エントロピー誤差になります。

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/cross_entropy/feed/ 0
【物語】統計検定2級を目指してみる – 『完全独習 統計学入門』を読んでみた編 – https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/statistical_1/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/statistical_1/#respond Sun, 11 Jul 2021 21:42:05 +0000 https://greencolor.net/?p=4098 この記事は統計検定2級を目指そうと思った日(2021年6月30日)に記事入れを始めました。

まだいつ受験するかも決めていませんし、その前にそもそも統計の勉強をしたことがありません(笑)

いつ受験するのか、いつ合格するのか、どのような勉強をしていくのかをリアルな感じで書いていきたいと思ってます。

そもそもなぜ統計検定2級を目指そうとしているのか

さすがに意味なく目指そうと思ったわけではないです。

AI資格である「E資格」を取りたくて、受験内容を調べていたとき試験範囲の中に「応用数学(線形代数、確率・統計、情報理論)」というものがありました。

よくデータサイエンティストは統計学を理解してないと厳しいということが言われていますね。

ボクのサイトでもディープラーニングの記事を投稿していますが、現時点取り扱っているメインは画像系を中心にしています。画像系では統計学的な知識はあまりいりませんが、画像系以外にも手をつけるつもりなので、いづれ統計学は必要になるだろうとは思ってました。

で、今回。

本腰を入れて、E資格を目指そうと思ったのでいろいろ調べていたときに「統計検定2級をベースに近しい問題が出題される」との情報を得て、それならまずは統計検定2級を取得した上で、E資格にチャレンジしようと思った、というのが統計検定2級を目指した理由です。

ボクのスペック

簡単にボクのスペックを書き記しておきます。

 

・統計の勉強をしたことがない。
・一応理系出身だけど、あまり真面目に取り組まず(人並)、試験もなんとなくこなし、卒業。
・(凄い人を尊敬しつつも)同じ人間なんだから、ボクにもできるでしょ。と思ってるタイプの人間。
 
本当にこの段階で統計学に関して何もしていないでその難しさに挫折するかもしれませんが、いまはやる気になっているので応援してください!

2021年6月30日

まずは初学者が統計検定2級を合格するまでに何時間くらいの勉強が必要なのかを調べました。

初学者が統計検定2級に合格するための勉強時間(調査結果)

前提は「初学者」です。

サイト 学習時間
Aサイト 100時間
Bサイト 80時間
Cサイト 150時間
Dサイト 120時間
Eサイト 70時間
Fサイト 160時間
平均 113時間

人によってバラバラですけど、平均で113時間ですね。

平日:1時間、休日(土日):3時間のペースで勉強するのであれば2.5ヶ月くらいかかる計算です。

7月から勉強するとして、早くて9月~10月頃の受験・合格を目指すのが妥当でしょうか。

ん~、ディープラーニングの勉強も引き続きするので、ペースはもっと落ちるかも。

で、合格率は?

実施年 受験者数 合格者数 合格率
2016年 3,554人 1,574人 44.3%
2017年 3,084人 1,349人 43.7%
2018年 3,428人 1,461人 42.6%
2019年 4,307人 1,871人 43.4%

なるほど、毎年40%ちょいという感じですね。

学習方法は?

学習方法を間違えると挫折の原因になったり、資格取得したあとに忘れてしまったりと勉強時間がムダになるので慎重に調べました。

で、初学者の場合、多くの人達に「3つの共通点」がありました。

『完全独習 統計学入門』小島 寛之

「使うのは中学数学だけ!」とのうたい文句で売り出しており、Amazon評価は4.2/5と高評価。

何よりも初学者の合格サイトのほとんどで「この本を初めに読んでおいて良かった」と絶賛しています。

試験では微分・積分・シグマ・確率と難しい言葉と対峙しなければならないが、この本では一切難しい言葉を使わず、丁寧に解説されている良書とのこと。(執筆中なのでまだボクは読んでません)

統計検定2級取るぞ!といき込んだ最初は過去問とかで微分・積分・シグマ・確率が出てきても、調べながらできそうですが、時間が経つにつれ、理解した感覚もなく「あれ、、いけるんか?これ。。」となりそうなのを、最初にこの本を読んで基本を理解すれば、確かにいいかも、と思いました。

完全独習 統計学入門
created by Rinker

統計WEB

これも初学者の合格サイトのほとんどの方が取り組まれているようです。

統計WEB(統計学の時間)

初級編の書き出し「ここはとても頭の良い猫たちが暮らす島。たくさんの猫たちが幸せに暮らしています。」

わぉ。優しい感じが滲みでてますね。

もちろん無料なので、やらない理由はありませんね。

過去問

これは必須ですね。日本統計学会公式認定が出版している過去問です。

まずはこの3つを試してみようと思う

これだけで足りるのか・・・?もっと他にいい方法があるんじゃないか・・・?

と思いがちですが、そもそも統計学を学んだことがない素人(ボク)が調べて良し悪しが判断できるわけない。

四の五の言わず、調べてる時間あったら、勉強しよーぜ、の精神でまずは、『完全独習 統計学入門』を早速購入しました!(笑)

『完全独習 統計学入門』読んでみたよ

本書の対象者

本を読み始めた最初にだいたい「本書の対象者」みたいなことが書いてあるのですが、笑えました。

・統計学を初めて学ぶ人
・統計学を改めて学び直したいという人
・何度も挫折して、いまだ身についてない人
・今まさに落ちこぼれつつある人

いや、4つ目、責め過ぎ(笑

本書は2部構成

統計学では「検定」と「区間推定」というのが最重要テーマらしいです。それを最短で理解するのが第1部。

で、第2部は「t分布を使った小標本の検定・区間推定に最も効率的にたどり着く」ことを目的としています。

まず、この2行で述べたことが「ピンとこない」キーワードであれば、本書を購入する価値はありそうです。

機械学習と似ている

ボクはディープラーニングという側面からアプローチを開始し、いま、統計学という入り口に立っていますが、機械学習を学び始めた頃「機械学習と統計学の違い」にアプローチする文献が多かったことを思い出しました。

ディープラーニングは「データという事実から特徴を抽出すること」、そして、訓練したモデルをつかって新たにインプットされた情報がなんであるかを「予測」することをしています。

本書を読み始めた最初にも「記述統計」と「推測統計」がまさに統計学であるということが述べられています。

記述統計と推測統計が何であるかは本書を通して学んでほしいと思いますが、本質的な部分は一緒なんだということを体感することができました。

読み終えてみて

読み終えるまでの日数

読み終えるまでに12日かかりました。

読み始める前は2,3日で読み終えるつもりで取り組みましたが、結果的に12日もかかってしまいました。。

特に読まなかった日がなかったわけではないのですが、理由は後述します。

読んでみてどうだったか?

読んで良かったです。

 

・いろんな視点で説明がされているため、理解の定着につながる。
・統計において大切なポイントが明確になる。
 
ただ、書いてあることがすべて簡単に理解できるか?と聞かれると「No」です。
読み進めている中で、理解が追い付かない箇所はネットで調べることもしばしばありました。
特に助けになったのはYoutube「予備校のノリで学ぶ「大学の数学・物理」」はとてもありがたい動画でした。
 
 
「あ~、統計やっぱ難しいかも」「面白味がわからなくなってきた・・・」と思ったときは中学数学からはじめる確率統計を見るとやる気や好奇心がそそられました。
さらに理解を深めるために確率統計シリーズに目を通すことでより身についていく感覚がありました。
これだけの内容が見れるなんて、スゴい世の中になりましたね^^ 本当にありがたい。
ひとまず本は読み終えたので「統計WEB」を勉強していきます^^

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/statistical_1/feed/ 0
【コード公開・解説】今度こそ機械学習とディープラーニングの違いがわかる!~ディープラーニング編【前編】~ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_2/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_2/#respond Wed, 30 Jun 2021 22:00:26 +0000 https://greencolor.net/?p=4046 【考えれば考える程分からなくなった人向け】今度こそ機械学習とディープラーニングの違いがわかる!で書いた記事内容のコードを教えてほしい!とお声をいただいたので今回はコード中心に解説していきたいと思います^^

前回記事のサマリー

今回はコード解説版なので、詳しくは前回記事を参照してほしいですが、いきなりこの記事に来られた方のために少しだけ記事のサマリーを書いておきます。

『ディープラーニングは機械学習の一種で大きな違いは「特徴量を人が与えるか、特徴量を自動で抽出するか」だ。』んー・・分かるようでわからない・・・という人のために、犬・猫を判別するモデル構築を通じてその違いを書いていく、というものでした。

こんな方を対象にしています

・前回記事を読まれて「実際どんなコードを書いているのだろう?」と思われた方
・機械学習、ディープラーニングでの基本的なやり方がわからない
・ネット見てコピペしてるばっかりで、自分が何をしているかわからない

3つ目の「ネット見てコピペしてるばっかりで、自分が何をしているかわからない」という人は意外と多そうだと感じています。

コピペ → おー、できたー → 違うの試してみる → エラー
 → 調べてもよくわからない → 諦めて違うサイト探してコピペ・・・

ボクが最初こうでした 笑

そんな方にも少しでも勉強になるように書いていますので是非ご覧になってみてください。

ディープラーニング編

前回は機械学習編のコード公開・解説をしましたが、今回はディープラーニング編です。

機械学習のようにデータの特徴をみてどの項目が判別に影響するのか・・・なんてことは一切しません。

なぜなら、ディープラーニングは「自動的に特徴を捉えて、勝手に学習してくれる」からです^^

賢いですね~。

では早速みていきましょう!

必要なライブラリを入れよう

機械学習編と同様、まずは必要なライブラリを入れていきます。

import numpy as np
import matplotlib.pyplot as plt
import os
import cv2
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout, Conv2D, MaxPooling2D, Flatten
from keras.optimizers import Adam
from keras.utils.np_utils import to_categorical
from tensorflow.keras.datasets import cifar10  # tf.kerasを使う場合(通常)

・・・。

なんか、急に多いな!

はい!めげずにやっていきましょう!

NumPy・Matoplotlib

は前回説明しているので前回記事をご覧ください。

os

指定フォルダ配下のファイル名取得やパス結合を行うとき等、OSに依存する機能を利用するためのモジュールです。

フォルダ構成を例に説明します。

▼テストフォルダ直下にあるフォルダやファイルを確認したい場合

os.listdirというコマンドで指定フォルダ直下にあるフォルダやファイルを確認することができます。

※DATADIRにはあらかじめ ” ./drive/MyDrive/test ” を指定しています。

ちゃんとtestフォルダ配下のフォルダとファイルが出力されましたね。

▼ファイルパスを取得したい場合

testフォルダ配下にdogフォルダとcatフォルダがありましたね。dogおよびcatフォルダの配下にはファイルを一つずつ配置しています。

./drive配下のファイルパスを取得したい場合はこのようにします。

一つの例として、フォルダに入れておいた画像ファイルから学習データと正解ラベルを紐付けながらモデルに与えるデータを生成していくときなどにこのようなロジックを利用します。

cv2

OpenCVと呼ばれるライブラリをインポートします。OpenCVは画像や動画を処理するのに必要な機能を提供してくれるライブラリです。今回の記事では元画像をリサイズしたり、色変換したりするのに利用しています。

画像を扱う人はほぼほぼ通る道かと思います。

Sequential

今回は「モデルはSequentialで書きます」というのを指定(import)しています。

TensorFlowにおけるモデルの書き方には大きくわけて3つあります。

・Sequential
・Functional API
・Subclassing
一つ一つみていきましょう。
Sequential

“連続的な”という意味を持つSequential。その名の通り、上から下に連続的に直列でつないでいく処理によりモデルを実現させています。

ディープラーニングを勉強し始めた人が「えっ、ディープラーニングってこんなに簡単にできるの?」と驚くくらいシンプルな構造です。

少しコードを抜粋してみてみましょう。

model = tf.keras.Sequential()で「このモデルはSequentialで記述するよ」と宣言しています。

そのあとはaddで層をひたすら追加していけばモデルを作ることができます。

シンプルで理解しやすい構造ですが、1入力1出力のため複雑なことができません。例えば、ある画像の予測結果を「犬か猫」に分類し、1つの結果として出力が可能ですが、その犬(または猫)が「男の子か女の子か」までは表現できない、という意味です。

Functional API

先ほどSequentialで説明したデメリットをクリアできるのがFunctional APIです。

Functional APIでは複数入力・複数出力のモデルを構築することができるため柔軟性があります。

先ほどは犬・猫で例えましたが、他で例えると「顔画像から年齢・性別・感情を予測」みたいなことも可能になります。

Subclassing

紹介するモデルの書き方で一番、自由度が高く記述できる書き方がSubclassingです。

SequentialやFunctional APIは静的モデルと呼ばれており、モデルの構造を動的に変更させることはできませんが、Subclassingはモデルを動的に変更することが可能です。

俗にいう ” Define by Run “ と呼ばれる構造体のことです。

・・な、なるほど・・

と、理解できた方はすごいなぁと思います。

私は「動的?」「具体的にいうと?」「どんな時に使い分けるの?」という疑問ワードがいっぱい出てきて、「あ~、なるほどね」ってなるまで1週間くらいかかりました 笑

今回はコード解説がメインなのでSubclassingの詳細については別記事とし、ここでは触れませんが「条件によって処理を分岐させ、学習ロジックを分けたい場合」にはSubclassingで記述します。

 

・感情分析などの自然言語処理(Recurrent Neural Networkを木構造に拡張させたRecursive Neural Network)
・モデルの計算結果によって、適用する活性化関数を変えたい場合
 
また別記事で実際の具体例を交えて説明できる機会を設けたいと思います。

層の部品

続いては層の部品をインポートしていきます。

from keras.layers import Dense, Flatten, Conv2D, Activation, MaxPooling2D, Dropout

Dense

Denseは全結合層という意味で、全入力値に重みを掛け、バイアスを足した結果のニューロンの集合体を全結合層と言います。

別の言い方として、Affine(アフィン)やFC層という呼び方で説明されることもありますが、いづれも同じものを指しています。

全結合層を利用するには入力データは1次元データとして与えなければならないという制約があります。

のちにソースコード解説で詳細を触れます。

Activation

Denseの説明でニューロンの計算式を示しました。

\begin{align}
y_1&=w_1*x_1+w_2*x_2+・・・+b
\end{align}

この計算された$y_1$をある値に変換して出力する役割をもったものを活性化関数(Activation)と言います。

なぜ、一度計算した$y_1$を変換する必要があるの?という疑問をもたれる方もいると思います。

その疑問を解決するには、まず$y_1$がどのような性質をもった式なのか、というのを抑えましょう。

\begin{align}
y_1&=w_1*x_1+w_2*x_2+・・・+b
\end{align}

これは次数が1次なので、1次関数です。言い換えると線形であると言えます。

グラフでイメージするとこんな感じの”直線的”な性質を持つのが線形です。

ここまででわかるのが、$y_1$は線形であるということです。

理解を深めるために以下の図を見てください。

オレンジとブルーの境界値はどこを考えてみましょう。簡単ですね。

こんな感じです。

直線で分離可能なものを” 線形分離可能 “と言います。

では、次は線形分類可能でしょうか?

どう考えても無理ですね。

オレンジとブルーを分けた場合、こんな感じになります。

このようにブルーの中心に円を描くように境界値を引くことで、オレンジとブルーを分離することが可能になりました。

つまり ” 非線形 ” であれば分離可能になったということです。

この線形を非線形に変換してくれるのが活性化関数です。 

ちなみに今回はReLUという活性化関数を利用しました。活性化関数は他にもあり、それぞれ性質をもっていますが、ここで重要なのはその性質の違いではなく、線形を非線形にする役割として活性化関数を利用しているということ抑えてください。

Flatten

N次元を1次元に変換する関数です。先ほど、全結合層は1次元の入力データで利用する必要がある、と説明しましたが、Flattenを利用することで簡単に1次元データに変換することができます。

Conv2D

畳み込みニューラルネットワーク(CNN)で2次元データを畳み込むために利用する関数です。

画像を扱うディープラーニングといえばCNN、というくらい超有名なものです。

何をしてくれる関数なのかというと「与えられた画像データをもとに、その画像が持つ特徴を抽出してくれる関数」です。

正確にいうと「画像データに対してフィルタ(またはカーネル)をかけて特徴を抽出・圧縮」します。

言葉で言われてもピンとこないと思うので、少しだけ例を用いて補足します。

※あくまで直感的にイメージを掴んでもらうためですので、正確性に欠けることはご了承ください※

今回、インプットする画像はゼロとします。

画像というのは画素の集合体(小さな画像の集まり)で、1つ1つの画素に色情報を持つことで色の濃淡を表現しており、物体を表現することができています。

この画像の特徴を捉えるためにある一定の大きさに切り取って、切り取った局所的な部分の特徴を学習します。

この「一定の大きさに切り取る」ためにフィルタを用いて処理をします。フィルタを用いて抽出した特徴を特徴マップとして保持します。

画像に対してフィルタを用いて特徴を抽出する・・・このように繰り返しフィルタをかけることで画像全体の特徴を捉える行為ことこそがConv2Dの役割です。

MaxPooling2D

Conv2Dで画像を畳み込んだ結果に対してフィルタ(カーネル)をかけ、画像の特徴を粗く捉えるための役割をもっています。

言葉だけではイメージがつかないと思いますので図で補足します。

Conv2Dのの時と同様にフィルタ(カーネル)を利用します。

そのフィルタの範囲で一番大きな数字を抽出した結果を出力していきます。

これがMaxPooling2Dです。最大値を抽出するので「Max」なんですね。

なんのために最大値だけを抽出するのか?という点についてですが、それは「画像の位置ズレを吸収する」ためです。

例えば、リンゴの写真を2枚撮影したと仮定しましょう。

1枚目と2枚目を重ね合わせたとき、リンゴの位置が完全に合ってる確率は少ないですよね。

極端ですが、これくらい位置がズレても人間の目にはどちらもリンゴであることがわかります。

人間には簡単に見分けることができても、コンピューターには難しいこともたくさんあります。

それは、思考の応用がきかないからです。

位置がズレても判別対象をしっかり特定するための工夫としてプーリング層による最大値の取得が行われます。

先ほど利用した6 × 6マスの図を1マス右にずらして検証してみましょう。

特徴を粗く捉えておくことで、このように画像位置がずれても判断が可能になるよう工夫されているのがプーリング層です。

今回はフィルタ(カーネル)の最大値を取得するMaxPoolingを紹介しましたが、平均値をとるAveragePoolingという関数も存在します。

Dropout

Dropoutは過学習を防ぐために利用されるものです。

ディープラーニングを学習し始めた頃に聞く「過学習」です。

具体的に何をしているのかというと、ランダムにニューロンを無効化しています。

model.add(Dropout(0.5))で50%のニューロンをランダムに無効化します。
model.add(Dropout(0.2))で20%のニューロンをランダムに無効化します。
このように学習したニューロンを無効化し、訓練を繰り返すことで汎化性の高いモデルをつくっていきます。
訓練データ以外を使って予測させた場合に精度の高いモデルであることを汎化性の高いモデルといいます。

今回はここまで。。

呪文の説明が思った以上に長くなっています。。

読んでくださった皆さんも疲れたと思いますので、続きは次回にします。。

Let’s enjoy deep learning next time!
]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_2/feed/ 0
【コード公開・解説】今度こそ機械学習とディープラーニングの違いがわかる!~機械学習編~ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_1/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_1/#respond Sat, 26 Jun 2021 01:03:26 +0000 https://greencolor.net/?p=4025 【考えれば考える程分からなくなった人向け】今度こそ機械学習とディープラーニングの違いがわかる!で書いた記事内容のコードを教えてほしい!とお声をいただいたので今回はコード中心に解説していきたいと思います^^

前回記事のサマリー

今回はコード解説版なので、詳しくは前回記事を参照してほしいですが、いきなりこの記事に来られた方のために少しだけ記事のサマリーを書いておきます。

『ディープラーニングは機械学習の一種で大きな違いは「特徴量を人が与えるか、特徴量を自動で抽出するか」だ。』んー・・分かるようでわからない・・・という人のために、犬・猫を判別するモデル構築を通じてその違いを書いていく、というものでした。

こんな方を対象にしています

・前回記事を読まれて「実際どんなコードを書いているのだろう?」と思われた方
・機械学習、ディープラーニングでの基本的なやり方がわからない
・ネット見てコピペしてるばっかりで、自分が何をしているかわからない

3つ目の「ネット見てコピペしてるばっかりで、自分が何をしているかわからない」という人は意外と多そうだと感じています。

コピペ → おー、できたー → 違うの試してみる → エラー
 → 調べてもよくわからない → 諦めて違うサイト探してコピペ・・・

ボクが最初こうでした 笑

そんな方にも少しでも勉強になるように書いていますので是非ご覧になってみてください。

機械学習編

必要なライブラリ(NumPy、Pandas、Matplotlib)を入れよう

まずはおなじみの”おまじない”と呼ばれる儀式です。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

今回はおまじないだけでは済ませないですよ!

まず抑えるべきポイント

NumPyは数値計算、Pandasはデータ解析、Matplotlibはグラフ描画をサポートしてくれるライブラリです。

機械学習、ディープラーニングをする上では必ず出てきます。

「まぁ、そうなんでしょうけど、パっとイメージできないんだよね・・・」

という方もいるかと思いますので、それぞれを関係性を簡単に確認しておきましょう。

一言でいうと「生データをPandasでいろいろ加工・綺麗にして、NumPyでモデルが読める形に変えてあげる」感じですね。

ちょっと意味合いは違いますが、たとえばプレゼン資料をつくるときに資料の一部をグラフで表現するときを思い浮かべてください。最終的な形はパワーポイントかもしれないですが、元データを加工・整形・グラフ化するときにエクセルを使って処理し、最終的にパワーポイントに貼り付ける。なんてことをよくすると思います。感覚的にはそんな感じです。

 

データを格納する箱をつくろう(DataFrame)

次にデータを格納する箱を作ってあげます。

# 空のデータフレームを作成
# データフレームの特徴:異なる型を1つのデータフレームで処理できるため、データ前処理が容易
# データ収集(csvなど) → データ加工(Pandas) → 数値計算(NumPy) → 学習の順番で利用されることが多い
dog = pd.DataFrame()
cat = pd.DataFrame()

箱(DataFrame)にデータを入れていこう

さきほどつくった箱にデータを入れていきます。

CSV等、元データがあればロードすればいいのですが、今回は元データがなかったので自分で作りました。

# 犬の平均体高、体長、体重、ヒゲについて標準偏差から500個分の身長データをランダムに生成
# np.random.normal:正規分布に従う乱数を生成する関数
# noemal:正規、random:乱数
# 引数(平均,標準偏差,出力数)
# 標準偏差:データのバラつきを示す指標
dog_height = np.random.normal(28.19,1.2, 500)
dog_length = np.random.normal(29.68,1.2, 500)
dog_weight = np.random.normal(7.5,1, 500)
dog_beard  = np.random.normal(3,1, 500)

今回やったデータのつくり方は別に覚えなくてもよいですが、一応説明しておきます。

やったことは「犬の体高(height)、体長(length)、ヒゲ(beard)、体重(weight)データを平均値と標準偏差を使ってランダムに500個生成」しています。

次も同じように猫のデータをつくっていきます。

# 猫平均体高、体長、体重、ヒゲについて標準偏差から500個分の身長データをランダムに生成
# np.random.normal:正規分布に従う乱数を生成する関数
# noemal:正規、random:乱数
# 引数(平均,標準偏差,出力数)
# 標準偏差:データのバラつきを示す指標
cat_height = np.random.normal(27.19,1.3, 500)
cat_length = np.random.normal(35,1.3, 500)
cat_weight = np.random.normal(5.5,1, 500)
cat_beard  = np.random.normal(3,1, 500)

いまつくった犬と猫のデータを箱にいれます。

そして、dog.head()で箱の中身を5行分、表示しています。

# 作った500個のデータをdogのデータフレームに格納
# head()でデータフレームに格納されている先頭5行分を表示
dog["height"] = (dog_height)
dog["length"] = (dog_length)
dog["weight"] = (dog_weight)
dog["beard"] = (dog_beard)
dog["animal"] = 0
dog.head()

animalってどこからでてきたの?

はい、犬と猫を識別する区分として0:犬、1:猫として登録しています。

このようにデータ生成時に作っていなかった項目でも簡単に箱(DataFrame)に追加することが可能です。

猫も同じようにします。

# 作った500個のデータをcatのデータフレームに格納
# head()でデータフレームに格納されている先頭5行分を表示
cat["height"] = (cat_height)
cat["length"] = (cat_length)
cat["weight"] = (cat_weight)
cat["beard"] = (cat_beard)
cat["animal"] = 1
cat.head()

ここまでつくったデータの中身を可視化しよう

先ほどまで作った箱にデータを入れてきました。

しかし、ただただ箱のデータを数値的に眺めても特徴がわからないので視覚的に見ていきます。

# 体高とひげの散布図
plt.scatter(dog[dog["animal"]==0]["height"],dog[dog["animal"]==0]["beard"],label="dog")
plt.scatter(cat[cat["animal"]==1]["height"],cat[cat["animal"]==1]["beard"],label="cat")

## X軸の範囲を指定
plt.xlim(20,50)
## Y軸の範囲を指定
plt.ylim(0,5)

## X軸の名前
plt.xlabel("height")
## Y軸の名前
plt.ylabel("beard")

## 凡例を出力
plt.legend()

これを実行するとこんな図が表示されます。

さっきつくったデータから表示したい項目を指定するだけでサクッと確認できるのはいいですね。

同じように体高と体長のデータも可視化してみます。

# 体高と体長の散布図
plt.scatter(dog[dog["animal"]==0]["height"],dog[dog["animal"]==0]["length"],label="dog")
plt.scatter(cat[cat["animal"]==1]["height"],cat[cat["animal"]==1]["length"],label="cat")

## X軸の範囲を指定
plt.xlim(20,50)
## Y軸の範囲を指定
plt.ylim(20,50)

## X軸の名前
plt.xlabel("height")
## Y軸の名前
plt.ylabel("length")

## 凡例を出力
plt.legend()

次に体長と体重を可視化してみます。

# 体長と体重の散布図
plt.scatter(dog[dog["animal"]==0]["length"],dog[dog["animal"]==0]["weight"],label="dog")
plt.scatter(cat[cat["animal"]==1]["length"],cat[cat["animal"]==1]["weight"],label="cat")

## X軸の範囲を指定
plt.xlim(20,50)
## Y軸の範囲を指定
plt.ylim(2,10)

## X軸の名前
plt.xlabel("length")
## Y軸の名前
plt.ylabel("weight")

## 凡例を出力
plt.legend()

dogとcatの箱(DataFrame)を結合しよう

実際、モデルの学習時は以下のような準備をします。

①学習データ(今回でいうと犬と猫のデータ)と正解データ(今回でいうと”animal”の0、1の項目)を準備
②モデル精度を確認するための検証データ

今回、準備した犬と猫のデータ合わせて1,000件ありますが、そのうち800件を①、200件を②で分割して利用するといったような感じで進めます。

以下のコードはdogとcatでバラバラに作った2つ箱(DataFrame)をくっつけて1つの箱にします。

つまり、①の学習データと正解データが1つの箱に入っている状態です。

# dogとcatの2つのデータフレームを結合
dogcat = pd.concat([dog,cat])
dogcat

1つの箱にくっつけた中身はこんな感じです。

学習データに利用する項目を抽出しよう

今回は体長(length)と体重(weight)を特徴としてモデルに学習させるので、dogcatのDataFrameから体長(length)と体重(weight)のみを抽出します。

# dogcatから体長(length)と体重(weight)のみを抽出
x = DataFrame(dogcat.loc[:, ['length', 'weight']])
x.head()

正解データに利用する項目を抽出しよう

次に正解データの作成をします。今回でいうと”animal”の0、1の項目をdogcatのDataFrameから抽出します。

# dogcatからanimalのみを抽出
y = DataFrame(dogcat["animal"])
y.head()

学習データと検証データに分割しよう

ここまでで、学習データ(体長(length)と体重(weight))と正解データは準備できましたので、次はこの塊になっているデータを学習データ:検証データ=8:2に分割します。

モデル構築において、学習データと検証データの割合が何対何でないといけないという決まりはありません。今回は8:2として進めます。

データ分割は「scikit-learn(サイキット・ラーン)」という機械学習ライブラリにtrain_test_splitという関数がありますのでそれを利用すればサクッと学習データと検証データに分割することができます。

train_test_split(x,y,test_size=0.2)はxとyのデータからテストデータを20%作ってねという指定になります。

# 学習データ・学習用正解ラベル、検証データ・検証用正解ラベルを作成
# train_test_split:学習データ:検証データを8:2に分割(test_sizeを20%に指定しているため)
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2)

# 学習データ数
print(x_train.shape)
print(y_train.shape)

# 検証データ数
print(x_test.shape)
print(y_test.shape)

print文で分割したデータの数を出力させましたが、指定の通り8:2になっていますね。

いよいよ学習です!

いやー、長かったですね。。笑

ここからはいよいよ学習です!一瞬で終わります。

from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score

lenwei_model = LinearSVC()

# モデルに学習させる
lenwei_model.fit(x_train,y_train)

#モデルに予測させる
lenwei_predict = lenwei_model.predict(x_test)
lenwei_score = accuracy_score(y_test, lenwei_predict)
print('体長と体重の正解率:{}'.format(lenwei_score), sep='\n')

 

はい、終わりです。前処理が大変、ということを体験いただけたかと思います。

少し解説していきますね。

前回も少しフレーズとしては触れていますが、機械学習アルゴリズムとしてはLinear SVCを利用しています。

Linear SVCとはクラス分類を行うときに利用するアルゴリズムです。今回は犬と猫を分類するので、Linear SVCを利用しました。

fitでモデル学習、predictでモデルを使った予測を行います。

つまり、先ほど学習データとして分割したx_train,y_trainfitで学習させ、構築したモデルにx_testをインプットし、predictで予測させます。予測結果の精度をaccuracy_scoreで出力させています。

機械学習編まとめ

いかがでしたでしょうか。今回は機械学習編としてお送りしましたが、モデルの作成は一瞬で、前処理が大変・・・ということを体感いただけたのではないかと思ってます。

もちろん、今回は超絶シンプルなお題なので、これくらいで済んでいますが、本格的に活かそうと思ったときは、データ分析、欠損値補完、データ加工・・などなどたくさんの検討し、モデル構築→検証を繰り返さなければなりません。

実際のところは何かやろうとしても、対象データが全然ない・・なんてことはザラなので、まずはデータ取得をしなければならず、データを集めるだけで何か月もかかってしまうことも多々あります。

「AI」というキーワードは一見華やかなフレーズですが、現実は意外と地味な作業が多いもんです^^;

次回はディープラーニング編をお届けしたいと思います!

 

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/difference_between_machinelearning_and_deeplearning_comment_1/feed/ 0
【ディープラーニング】” 学習 “の仕組みがいまいちわからないを今日で卒業しよう② https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/deeplearning_how_it_works_2/ https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/deeplearning_how_it_works_2/#respond Sun, 13 Jun 2021 23:27:32 +0000 https://greencolor.net/?p=3966

前回の続きです。

前回はモデルの精度の悪さ加減を見る方法を説明しました。

この3つの大切な式について記載しましたが、今回は「重みを更新する式」と「バイアスを更新する式」の解説をしていきます。

誤差関数(平均二乗和誤差)の式(←前回)

\begin{align}
E&=\frac{1}{2}Σ(t_i-y_i)^2\\
&=\frac{1}{2}Σ(t_i-(w_ix_i+b))^2
\end{align}

重み($w_i$)を更新する式(←今回)

\begin{align}
w_i&=w_i-η\frac{∂E}{∂w_i}\\
&=w_i−η(Σ((w_ix_i+b)−t_i)∗x_i)
\end{align}

バイアス($b$)を更新する式(←今回)

\begin{align}
b&=b-η\frac{∂E}{∂b}\\
&=b−η(Σ(w_ix_i+b)−t_i)
\end{align}

いい感じの重み($w$)を見つけにいこう

誤差を求めた結果、もっと精度を高める必要がわかりましたので、いい感じの重み($x$)とバイアス($b$)を見つけていきましょう。

そもそも求めたい式を思い出してみましょう。

$y_0 = w_0x_0 + b$ (1.1) 

あー、でしたね。入力データ($x$)と正解値($t$)は与えられるので、重み($w$)とバイアス($b$)が変数でしたね。

いい感じのモデルは誤差がゼロに近いことを求められているので、誤差関数(平均二乗和誤差)に(1.1)を代入してみます。

\begin{align}
E_0 &= \frac{1}{2} × (t_0 – y_0)^2 = \frac{1}{2} × ( t_0 – ( w_0x_0 + b ) )^2\\
E_1 &= \frac{1}{2} × (t_1 – y_1)^2 = \frac{1}{2} × ( t_1 – ( w_0x_1 + b ) )^2\\
E &= E_0 + E_1 = \frac{1}{2} × (( t_0 – ( w_0x_0 + b ) )^2 + ( t_1 – ( w_0x_1 + b ) )^2))\\
&= \frac{1}{2} × (( 7 – ( 3w_0  + b ) )^2 + ( 13 – ( 9w_0 + b ) )^2)\\
&=\frac{1}{2} × ((b^2+6w_0b-14b+9w_0^2-42w_0+49 ) + ( b^2+18w_0b-26b+81w_0^2-234w_0+169 ))\\
&= \frac{1}{2} × (2b^2+24w_0b-40b+90w_0^2-276w_0+218)\\
&= b^2+12w_0b-20b+45w_0^2-138w_0+109$ (2.2)
\end{align}

差を求めた理由は、差を計算した(2.2)の式がゼロに近づけたいので、

$$0 = b^2+12w_0b-20b+45w_0^2-138w_0+109 (2.3)$$ 

になるような$w_0$と$b$を導いていきます。

すぐ忘れるので、念のため書いておきます。

(2.3)は、誤差の合計を求めるために展開したものです。

これがゼロになる、つまり、それが理想のモデルだということです。

これってどんなグラフ?

式長いし、変数が2つもあるし、これってどんな感じのグラフなんでしょう?

げっ、何これ。この3次元の最適な重み($w$)とバイアス($b$)求めるの大変そうですね。

ということで中学校で習った連立方程式に直していきます。そのときに偏微分を用いて対応していきます。

偏微分とは微分する対象以外は定数と扱って微分することです。

■(2.3)の式を$w_0$で偏微分

\begin{align}
0 &= b^2+12w_0b-20b+45w_0^2-138w_0+109\\
&= 0 + 12b – 0 + 90w_0 – 138 + 0\\
&= 90w_0+12b-138 (2.4)
\end{align}

■(2.3)の式を$b$で偏微分

\begin{align}
0 &= b^2+12w_0b-20b+45w_0^2-138w_0+109\\
&= 2b + 12w_0 -20 + 0 -0 + 0\\
&=12w_0+2b-20 (2.5)
\end{align}

偏微分で求めた(2.4),(2.5)を連立方程式で解いていきます。

$0=90w_0+12b-138$ (2.4)

$0=12w_0+2b-20$ (2.5)

これを解くと、$w_0=1$、$b=4$になります。

おぉ!最初に定義したいい感じの式、$y_0 = x_0 + 4$ (1.2)になりました!

おいおいおいおい、、

めでたしめでたし。じゃねーよ。

(1.3)で適当に決めた重み($w$)とバイアス($b$)でごにょごにょ計算したのはどこいったんだよ?

なんか煙に巻かれた感じがすんだけど?

ですよね。。そこを今から解説していきます!

気合いを入れた瞬間に心が折れる

でもね、解こうとしている(2.3)のグラフって、こんな感じなんです。(2回目)

僕の頭ではこんな3次元グラフで説明できるほど、頭よくないっす。。。

じゃあ、どうすんだよ?

(2.3)の式は変数が2つある且つ2次関数なので、これを1変数2次関数にすれば解けそうです。

$b^2+12w_0b-20b+45w_0^2-138w_0+109$ (←2変数2次関数)

これを1変数2次関数にするにはどちらかの変数を定数を入れればいいですね。

どちらでもいいのですが、今回は学習開始時のバイアス($b$)を-8で固定し、計算してみます。

\begin{align}
0&=(-8)^2+12w_0 * (-8) – 20 * (-8) +45w_0^2-138w_0+109\\
&=64 – 96w_0 + 160 + 45w_0^2 – 138w_0 + 109\\
&=45w_0^2 – 234w_0 + 333 (2.6)\\
\end{align}

ちなみにこんなグラフになります。

横軸$w=2.6$が最小値となることがグラフから読み取れますが、実際はこんな単純ではないので、どうやって最小値を求めていくのかを説明します。

誤差がゼロ($w=2.6$のところ)、つまり傾きがゼロになるようにすればいいので、今、自分が放物線上のどこにいるか確認して、最小値に近づいていく重み($w$)を求めればいいですね。

傾きを求めるには微分すればいいので、(2.6)を微分しましょう。

\begin{align}
0&=45w_0^2 – 234w_0 + 333\\
&=90w_0-234 (2.7)
\end{align}

微分した(2.7)に学習開始時の重み($w$)の3を入れてみます。

$$90w_0-234 = 90 × 3 -234 = 36$$

重み($w$)が3のときに、傾きが36であるということを意味します。

傾きが正の値をとったので、右肩上がりのグラフであることがわかります。

最小値に近づけるためには、横軸$w$の値を左に持っていけばいいことがわかります。

ということで重み($w$)を徐々に小さくしていってみましょう。

■重み($w$)を2.9にしてみる

$w=2.9のとき、90w_0-234=90 × 2.9-234=27$

傾きが減りましたね。

■重み($w$)を2.8にしてみる

$w=2.8のとき、90w_0-234=90 × 2.8-234=18$

傾きが減りましたね。

■重み($w$)を2.7にしてみる

$w=2.7のとき、90w_0-234=90 × 2.7-234=9$

傾きが減りましたね。

■重み($w$)を2.6にしてみる

$w=2.6のとき、90w_0-234=90 × 2.6-234=0$

傾きがゼロになりました!

■重み($w$)を2.5にしてみる

$w=2.6のとき、90w_0-234=90 × 2.5-234=-9$

あら、傾きがマイナスで出始めましたね。

ということで、(2.7)の重み($w$)が最小値になるのは$2.6$であることがわかりました。

バイアス($b$)を-8にしたときの重み($w$)が求まったので

これを式にしてみましょう。

$$ y= 2.6x -8$$

これに入力データ:$x_0 = 3$、正解値:$t_0 = 7$を入れてみてモデルの精度(誤差)を計算してみましょう。

\begin{align}
E&=2.6x -8\\
&=2.6 × 3 – 8\\
&=7.8 – 8 \\
&=-0.2
\end{align}

$E_0$(差):$( t – y_0 )^2 = 7 – ( -0.2 )^2 = 51.84$

①入力データ:$x_1 = 9$、正解ラベル:$t = 13$

\begin{align}
&E=2.6x -8\\
&=2.6 × 9 – 8\\
&=23.4 – 8 \\
&=15.4
\end{align}

$E_1$(差):$( t – y_1 )^2 = (13 – 15.4)^2 = 5.76$

$E=E_0+E_1=51.84+5.76=57.6$

差が57.6となりましたので、(1.8)(1.9)より誤差が小さくなったので正解に近づいたことになりました。

ただ、まだまだ誤差が大きいので適当にいれたバイアス($b$)-8は不適切そうだ、ということがわかりますね。

次の式を試してみよう

-8では精度が悪かったので、学習開始時のバイアス($b$)に4をいれてみましょう。

\begin{align}
0&=4^2+12w_0 × 4 – 20 × 4 +45w_0^2-138w_0+109\\
&=16 + 48w_0 – 80 + 45w_0^2 – 138w_0 + 109\\
&=45w_0^2 – 90w_0 +45 (2.8)\\
\end{align}

ちなみにこんなグラフになります。

いや、もう、$x=1$じゃん。

ってなりそうですけど、実際はもっと複雑なので簡単には分かりません(2回目)

傾きを求めるには微分すればいいので、(2.8)を微分しましょう。

\begin{align}
0&=45w_0^2 – 90w_0 + 45\\
&=90w_0-90 (2.9)
\end{align}

微分した(2.9)に学習開始時の重み($w$)の3を入れてみます。

$$90w_0-90 = 90 × 3 – 90 = 180$$

重み($w$)が3のときに、傾きが180であるということを意味します。

傾きが正の値をとったので、右肩上がりのグラフであることがわかります。

最小値に近づけるためには、横軸$w$の値を左に持っていけばいいことがわかります。

ということで重み($w$)を徐々に小さくしていってみましょう。

■重み($w$)を2にしてみる

$w=2のとき、90w_0 – 90 =90 × 2 – 90 =90$

傾きが減りましたね。

■重み($w$)を1にしてみる

$w=1のとき、90w_0-90=90 × 1-90=0$

傾きがゼロになりました!

■重み($w$)を-1にしてみる

$w=-1のとき、90w_0-90=90 × (-1)-90=-180$

あら、傾きがマイナスで出始めましたね。

ということで、(3.0)の重み($w_0$)が最小値になるのは$w_0=1$であることがわかりました。

もうお分かりかと思いますが、求めたかった式は、

いい感じの式 :$y_0 = x_0 + 4$ (1.2)

だったので、ばっちり$w_0=1$になりましたね。

で、どうやって重み($w$)を更新していきましょう?

今までは重み($w$)の値を変えながら、傾きがゼロになるところを探していましたが、そもそも重み($w$)は変数なので、何らかの計算の結果で重み($w$)を更新しなければいけませんね。

今、もっている情報としては、傾きを計算できる式(2.9)とテキトーに与える重み($w$)の初期値ですね。

先ほど重み($w$)の初期値3を入れたとき、傾きは180でした。この結果から重み($w$)軸を左に動かしていけば最小値に近づいていくことがわかります。

式にするとこんな感じです。

\begin{align}
w_n&=w_o – 傾き\\
&=w_o – (90w_o-90)
\end{align}

実際にやってみましょう。(今回求めたい重み($w$)の最小値は1です)

■重み($w$)を3にしてみる

$w_n=w_o – (90w_o-90)$
$-177 = 3 – ( 90 * 3 – 90 ) $
$15843 = -177 – ( 90 * -177 – 90 )$
$-1409937 = 15843 – ( 90 * 15843 – 90 )$
$125484483 = -1409937 – ( 90 * -1409937 – 90 )$
・・・

重み($w_n$)が1に近づく気配がまったくないですね・・・。

これは、最小値へ近づくための$w$が動く幅が大き過ぎることが原因なので、ここにある係数をかけて動く幅を小さくしましょう。

そのある係数のことを学習率と呼びます。

学習率は0.01と0.001とかで設定されることが多く、この学習率が小さければ小さいほど正確に学習できる可能性が高くなります。が、小さければ小さいほど、計算量は多くなるというデメリットがあります。

では、試しに先ほどの式に0.01をかけてみます。

$w_n=w_o – (90w_o-90)$
$1.2 = 3 – ( 90 * 3 – 90 )  (3.0)$
$1.02 = 1.2 – ( 90 * 1.2 – 90 ) $
$1.002 = 1.02 – ( 90 * 1.02 – 90 ) $
$1.0002 = 1.002 – ( 90 * 1.002 – 90 ) $
$1.00002 = 1.0002 – ( 90 * 1.0002 – 90 ) $
$1.000002 = 1.00002 – ( 90 * 1.00002 – 90 ) $
$1.0000002000000001 = 1.000002 – ( 90 * 1.000002 – 90 ) $
$1.00000002 = 1.0000002000000001 – ( 90 * 1.0000002000000001 – 90 ) $
$1.000000002 = 1.00000002 – ( 90 * 1.00000002 – 90 ) $
$1.0000000002 = 1.000000002 – ( 90 * 1.000000002 – 90 ) $
$1.00000000002 = 1.0000000002 – ( 90 * 1.0000000002 – 90 ) $
$1.000000000002 = 1.00000000002 – ( 90 * 1.00000000002 – 90 ) $
$1.0000000000002 = 1.000000000002 – ( 90 * 1.000000000002 – 90 ) $
$1.00000000000002 = 1.0000000000002 – ( 90 * 1.0000000000002 – 90 ) $
$1.000000000000002 = 1.00000000000002 – ( 90 * 1.00000000000002 – 90 ) $
$1.0000000000000002 = 1.000000000000002 – ( 90 * 1.000000000000002 – 90 ) $
$1.0 = 1.0000000000000002 – ( 90 * 1.0000000000000002 – 90 ) $

ぬぉ!重み($w_n$)が1になりました!!

これでいい感じの重み($w$)を見つけることができましたね。

今までやってきたことは言いかえると重み($w_i$)を更新する式の証明です。

重み($w_i$)を更新する式

\begin{align}
w_i&=w_i-η\frac{∂E}{∂w_i}\\
&=w_i−η(Σ((w_ix_i+b)−t_i)∗x_i)
\end{align}

式だけ見ると「何これ?」ってなりますが、かみ砕いていくとちゃんと理解できるもんですね。

念のため、重みを更新する式でやってみよう

$w_n=(w_o−η(((w_ox+b)−t)∗x))+(w_o−η(((w_ox+b)−t)∗x))$
$  =(w_0-0.01*(((w_0*3+4)−7)∗3)+(((w_0*9+4)−13)*9))$
$  =(w_0-0.01*((((w_0*3+4)−7)∗3)+(((w_0*9+4)−13)*9)))$
$  =(3-0.01*((((3*3+4)−7)∗3)+(((3*9+4)−13)*9)))$
$  =1.2$

さきほど手計算した(3.0)と一致するので、このまま計算を繰り返すと同じ結果が得られます。

いい感じのバイアス($b$)を見つけにいこう

最終的に求めたい式は、$y = x + 4$でした。つまり、バイアスが$4$に近づいてほしいわけです。

重みを見つけたときと同様、重みの定数を1として(2.3)の誤差を求める式に入れていきます。

$0 = b^2+12w_0b-20b+45w_0^2-138w_0+109$
$0 = b^2+12*1b-20b+45*1^2-138*1+109$
$0 = b^2 – 8b + 16$

グラフはこんな感じです。

グラフから最小値になるバイアスは$4$であることがわかりますね。実際はこんな単純では・・・もういいっすね 笑

次は微分して傾きを求めるんでしたね。

$b^2 – 8b + 16$
$=2b – 8$

次にバイアスを$-8$と仮定して、●●の式で求めていきましょう。

$b_n=b_o – (2b-8)$
$-7.76 = -8 – ( 2 * (-8) – 8 )  (3.1)$
$-7.5248 = -7.76 – ( 2 * -7.76 – 8 ) $
$-7.294304 = -7.5248 – ( 2 * -7.5248 – 8 ) $



$3.996134912130708 = 3.9960560327864365 – ( 2 * 3.9960560327864365 – 8 ) $
$3.996212213888094 = 3.996134912130708 – ( 2 * 3.996134912130708 – 8 ) $
$3.9962879696103317 = 3.996212213888094 – ( 2 * 3.996212213888094 – 8 ) $

限りなく$4$に近づきましたね。あまりにも長くなってので中略しましたが、計算を400回繰り返しました。

バイアスの更新式でみてみよう

バイアスの更新式は以下の通りでした。

バイアスを更新する式

\begin{align}
b&=b-η\frac{∂E}{∂b}\\
&=b−η(Σ(w_ix_i+b)−t_i)
\end{align}

これに当てはめてみます。

$b_n=b_o-0.01*(((3*1+b_o)-7)+((9*1+b_o)-13)))$
$b_n=-8-0.01*(((3*1+(-8))-7)+((9*1+(-8))-13)))$
$b_n=-7.76$

さきほど手計算した(3.1)と一致するので、このまま計算を繰り返すと同じ結果が得られます。

まとめ

いかがでしたでしょうか。

公式自体は小難しいですが、やっていることはとてもシンプルであることが理解いただけたのではないでしょうか。

とは言うものの、まだフワフワしている感覚もあるかもしれませんが、一度、自分で問題設定をしてみて解いてみるのも一つの方法かと思います。

その時にようやく「なるほどね」となると思います。

ここまで読んでいただきありがとうございました。

]]>
https://greencolor.net/ai%ef%bc%88%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92%ef%bc%89/deeplearning_how_it_works_2/feed/ 0