fbpx
  1. HOME
  2. ブログ
  3. IT技術
  4. 多層パーセプトロンを実装してみよう!【人工知能】

多層パーセプトロンを実装してみよう!【人工知能】

多層パーセプトロンとは?

今回は、現在の機械学習の基盤となっている「多層パーセプトロン」を実装します。

また、それと並行して、多層パーセプトロンについて解説も加えていきたいと思っています。

多層パーセプトロンとは?

実装する前に簡単に多層パーセプトロン(MLP: Multilayer perceptron)について解説しておきます。

多層パーセプトロンは以下の図のように、複数の形式ニューロンが多層に接続されたネットワークを指します。

単純パーセプトロンは入力層と出力層のみであったのに対し、多層パーセプトロンは中間層(隠れ層)と呼ばれる、層が複数追加されたネットワーク構造を持ちます。

単純パーセプトロンと違い複数のクラス分類を可能とし、線形分離不可能な問題も解くことができます。

現在は、この多層パーセプトロンの形式を拡張したものがよく使われています。

ミツオカミツオカ
単純パーセプトロンについて詳しく知りたい方は下の記事をお読みください!

多層パーセプトロンを実装する

実行環境

  1. Python 3.7.3
  2. numpy 1.16.3
  3. matplotlib3.03

下準備

実装の前に、使うライブラリのインポートをしておききましょう。

今回使うのが数値計算用のnumpyと、可視化するためのmatplotlibです。

(ちなみに今回は、matplotlibはなくても問題ありません)

おなじみの形式でインポートしておきます。

ネットワーククラスを作る

今回は、後から扱いやすくするために、ネットワークを Network クラスとして定義して実装していこうと思います。

クラスインスタンスとして、ニューロン情報と重み情報をベクトルとして保持しておく形をとります。

したがって、クラスとそのコンストラクタは以下のように定義しました。

各層のニューロン数を可変長引数としてコンストラクタに渡します

コンストラクタ内でシナプス結合強度(以下 重み)ベクトルやデータセットを格納しておく配列も宣言しておきます。

例えば、 net = Network(4, 10, 3) のようにすれば、「入力層4 – 中間層10 – 出力層3 」の3層からなるネットワークが作成できるようにします。

これ以降定義する関数は、基本的にこの Network クラスに属するメンバ関数だと思ってください。

重みを初期化する関数

ネットワークの各層の情報を決めたら、次にそのニューロン同士を接続する重みを初期化する必要があります。

先に定義しておきますが、入力層は第0層目、最初のニューロンは0番目ニューロンとして扱っていきます。

この重みベクトルは、 weights[0][1][3] ならば第0層目3番目ニューロンと第1層目1番目ニューロン間の重みを指すこととします。

ここで、weights[層][前ニューロン][後ニューロン]としないのは、後でコーディングを楽にするためです。

一旦ここで頭の中を整理

【重みベクトルの読み方】

したがって、もし4 – 10 – 3 のネットワークであれば、第0-1層間の重みは10×4の行列として表せます。

実際に書いてみる

これを実際に initWeights() 関数という名前でコーディングしてみます。

(クラス内のメンバ関数ということでインデントをひとつ下げています。)

あとで実験条件を変更しやすいように、引数で一様乱数の下限と上限を設定できるようにしておきます。

確認

ここで一旦、ネットワークが構築できているかを確認してみましょう。

すると出力は…

[4, 50, 3]

うまく動作していそうです!

【重みヒストグラム】

データセットを取り込む

データセットについても、先にコーディングしていきたいと思います。

今回は、Irisデータセットと呼ばれる「アヤメの分類問題」を使用します。

アヤメの分類問題

データセットは以下のページです。

UCI Machine Learning Repository: Iris Data Set

Irisデータセットは、{(がくの長さ), (がくの幅), (花びらの長さ), (花びらの幅)}の4次元の特徴データを入力として、{Iris-setosa, Iris-versicolor, Iris-virginica}の3クラスに分類をする問題です。

このデータセットを配列に格納する関数を作ります。

名前は loadIris() とでもしておきましょう。

またIrisデータは150パターン分あり、それぞれ[5.1,3.5,1.4,0.2,Iris-setosa]のようにデータが並んでいます。

実際に書いてみる

したがってコードは split() を使って以下のように書くことができます。

形式ニューロンの実装

次に重要な形式ニューロンを定義していきます。

(1)$$\displaystyle y = \sum_{i=1}^n{w_i x_i}$$
(2)$$\displaystyle z = f(y)$$
(3)$$\displaystyle f(x)= \begin{cases}1 & ( x \gt 0 ) \\ 0 & ( x \leq 0 ) \end{cases}$$

形式ニューロンは上の図と式で示すように、前層ニューロンからの入力\(x_i \{i=1,2,3,\cdots,n\}\)に対して、それぞれ重み\(w_i \{i=1,2,3,\cdots,n\}\)がかけられ、その和\(y\)がニューロンへの最終的な入力になります。

その後、その入力があるしきい値(バイアスともいう)\(\theta\)を超えていれば\(1\)を、超えてなければ\(0\)を出力する、という性質を持ち合わせています。

ここでは、コードの簡略化のためにしきい値は0としてしまいます。

(本来の多層パーセプトロンではしきい値についても学習しますが、学習しなくても結果に大きな差は出ません)

これらの式をコーディングしていきます。

式(1)

この関数ではインスタンスメソッドには触れないので、 @staticmethod を明示的に記述しておきます。

(もちろん記述せずに第1引数に self を入れても構いません)

式(2)と(3)

上記のようになります!

順伝播処理の実装

次に、ネットワーク構築のうえで重要な順伝播処理(forward propagation)について実装していきます。

順伝播処理とは、その名前の通りネットワークを入力層から出力層にデータを流す過程のことです。

ここが多層パーセプトロン実装のなかで一番複雑かもしれませんが、やっていることは形式ニューロン処理の集合です。

まず、第0層目(入力層)と第1層目では、第0層目ニューロンの出力はデータセットの入力パターンをそのまま使うことにします。

第1層目以降では、保持しておいた前層の出力を入力として扱います。

実際に実装してみる

関数名は forward() としました。

コードを見るとそこまで複雑な処理はしていないことが分かるかと思います。

17,18行目で最終中間層の出力を保持しているのは、学習で使うためです。

またここで、重みベクトルを[層][後ニューロン][前ニューロン]と定義したことが、活きていることもわかりますね! (13行目)

多層パーセプトロンやニューラルネットワークでは、計算が後ニューロン主体で考えることが多いので、前ニューロンのインデックスを最後(一番内側のループ)に配置すると、便利なことが多いです。

動作確認

今、定義した関数を使って出力層ニューロンの出力を観察してみましょう!

[1, 1, 0]
[0, 1, 1]
[0, 1, 1]

このように出力層ニューロンの3つの出力が出てきたら上手くできています。

まだ学習前なので、出力が似通ってしまっていることもわかりますね。

学習部の実装

いよいよ多層パーセプトロンの要である学習部の実装をしていきます。

通常、勾配降下法という手法が用いられることがほとんどですが、今回はもっと簡易な学習法を用いることにします。

今回用いる学習法は、単純パーセプトロンの時と同じで、以下のような誤差関数(4)重みの更新式(5)を使います。

(4)$$E_p=t_p-f(x_p)$$
(5)$$\Delta w=\eta E_{p}x_{p} $$

ここで、\(p\)は入力パターンを示し、\(t_p\)はその入力パターンの教師信号(通常1 or 0)を指します。

\(f(x_p)\)は出力層ニューロンの出力です。

ということでこれらの式を実装していきます。

重みの更新式

学習部全体を担う関数を train() としておき、引数に最大学習回数をとるようにします。

train() をまず、ざっくりと大枠だけ書いてみます。

この大枠の中に、重み更新式や学習終了条件などを細かい実装していきます。

まず重みの更新の前に各入力パターンごとに順伝播をさせ、ネットワークの出力を得る必要があります。

その後、教師ニューロンを作成し、各出力層ニューロンの出力と教師ニューロンとの誤差を取ります。

以上を先ほどのコードに加えてみましょう。

誤差関数

ここまできたら、あと少しです!

さきほど取っておいた最終中間層の出力 self.lastOutputs を使って重みを更新式を書きます。

またここで、一緒に学習終了条件も決めちゃいます。

ここでは、誤差の絶対値和が0になった時としておきましょう(だいぶ厳しい条件ですが…笑)。

すると最終的には以下のようになります。

学習率については変更しやすいようにクラスインスタンスとしました。

これで学習部の実装が完了しました!

テスト部の実装

最後は、テスト部の実装です。

学習してもテストしないと作ったネットワークの意味がありません。

今回は、訓練データをそのままテストデータとしてしまいます。

なので、 train() 関数とあまり大きく変わりません。

識別率(Accuracy)なども表示できるようしたいので以下のように定義しました。

以上で、クラスの実装は終わりです!

お疲れ様でした!

いざ、動作確認!

それでは、動作確認してみましょう!

クラスを用いたオブジェクト指向で実装していたので、メイン関数は以下のようにとても簡単です!

今回は 4 – 50 – 3 のネットワークで最大100エポック分の学習としました。

結果は…

なんと98%

誤差は0にならなかったものの、高い識別精度を獲得することができました。

ただ、何度も回してみると安定感がなく、上のような結果は常には出てきません…。

多層パーセプトロンはネットワークの大きさや、学習率で大きく性能が変わってきます。

値を変えて色々な条件で試してみると、結果がまた違ってきます。

これが機械学習の難しいところでもあり、楽しいところと言えるかと思います。

さいごに

今回は簡単ではありますが、多層パーセプトロンを実装して、 Irisデータセットを学習してみました!

安定感はあまりなかったものの、しっかり学習ができていることが分かります。

しかし実際、今回の学習法では最終層の重みしか学習できていないので、性能がなかなか上がりません。

誤差逆伝播学習法

そこで、提案されたのが「誤差逆伝播学習法」となります。

誤差逆伝播学習法は、今でもメジャーな学習法として使われており、全ての重みを学習できるので性能の良いネットワークが作れます。

誤差逆伝播学習法については、次の記事で解説していきたいと思います。

では、最後に、今回実装した最終的なコードを載せて終わりにしたいと思います!

ありがとうございました!

最終的なコード

 

一緒に働いてくれる仲間を募集しております!

ライトコードでは、仲間を募集しております!

当社のモットーは「好きなことを仕事にするエンジニア集団」「エンジニアによるエンジニアのための会社」。エンジニアであるあなたの「やってみたいこと」を全力で応援する会社です。

また、ライトコードは現在、急成長中!だからこそ、あなたにお任せしたいやりがいのあるお仕事は沢山あります。「コアメンバー」として活躍してくれる、あなたからのご応募をお待ちしております!

なお、ご応募の前に、「話しだけ聞いてみたい」「社内の雰囲気を知りたい」という方はこちらをご覧ください。

ライトコードでは一緒に働いていただける方を募集しております!

採用情報はこちら

関連記事