機械学習アルゴリズムには様々なモデルがありますよね。本記事ではその中でもMLP(多層パーセプトロン)について解説します。MLPは一般には3層のニューラルネットワークのことで、深層学習の深層でないバージョンと考えることのできるモデルです。このモデルを理解することで単純にMLPを知ることができるだけでなく、深層学習についても理解が深まるでしょう。
MLP(多層パーセプトロン)の周辺知識
MLPを理解する上ではいくつかの概念を理解する必要があります。
- パーセプトロン
- ニューラルネットワーク
- 深層学習(ディープラーニング)
パーセプトロン・ニューラルネットワーク・深層学習・MLPの違いは必ずしも明確になっているわけではありませんが、それぞれ詳細を確認していきましょう。
パーセプトロン
パーセプトロンは単純パーセプトロンとも呼ばれ、人間の脳の神経細胞を模して考えられたアルゴリズムです。パーセプトロンは2層のニューラルネットワークと考えることができ、複数の入力からスカラーの出力を得るモデルです。しかし一度の線形変換のみから構成されるこのモデルでは線形分離不可能なデータをうまく識別することができず、うまく識別できるデータは限られていました。
ニューラルネットワーク
ニューラルネットワークは脳の神経細胞を模して作られた機械学習モデルの総称であり、単純パーセプトロン、MLP、深層学習のいずれもニューラルネットワークと呼ばれます。
深層学習(ディープラーニング)
深層学習は多層(4層以上)から成るニューラルネットワークのことで、各層での線形変換に加え別途活性化関数と呼ばれる非線形な変換を行うことで、単純パーセプトロンで表現しきれない線形分離できないデータに対してもよくフィットするモデルです。現在の人工知能ブームを牽引しているのがこの深層学習であり、画像認識の分野で広く成功を収めている畳み込みニューラルネットワーク、自然言語処理の分野で成功を収めていた(現在はAttentionベースの手法が多いが)再帰ニューラルネットワークなどの手法が様々提案されています。
MLP(多層パーセプトロン)とは
MLPとは一般には3層から成るニューラルネットワークのことであり、2回の線形変換とそれぞれに対応する活性化関数で構成されます。MLPはパーセプトロンの欠点であった線形分離不可能な問題に対応するために、一度線形変換を行った後にsigmoid関数と呼ばれる非線形関数を適用しています。
しかしMLPには層を何層も重ねると勾配消失問題によって学習がうまく進まなくなるという問題がありました。勾配消失問題とは誤差逆伝播で学習を行う際に、途中で勾配の値が小さくなりすぎることで手前の層の学習がうまく行えなくなる現象のことです。深層学習ではReLUと呼ばれる活性化関数やその他学習のテクニックを用いることでこの問題を解決し、さらなる進化を遂げました。
NumPyによるMLPの実装
本章ではMLPをNumPyを用いて実装しましょう。
import numpy as np class Sigmoid: def forward(self, x): return 1 / (1 + np.exp(-x)) def __call__(self, x): return self.forward(x) def backward(self, x): return self.forward(x) * (1 - self.forward(x)) class Softmax: def forward(self, x): e_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) return e_x / np.sum(e_x, axis=-1, keepdims=True) def __call__(self, x): return self.forward(x) def backward(self, x): p = self.forward(x) return p * (1 - p) class CrossEntropy: def forward(self, y, p): p = np.clip(p, 1e-15, 1 - 1e-15) return - y * np.log(p) - (1 - y) * np.log(1 - p) def __call__(self, x): return self.forward(x) def backward(self, y, p): p = np.clip(p, 1e-15, 1 - 1e-15) return - (y / p) + (1 - y) / (1 - p) class MLP: def __init__( self, num_hidden, num_iterations: int = 3000, lr: float = 0.01 ): self.num_hidden = num_hidden self.num_iterations = num_iterations self.lr = lr self.sigmoid = Sigmoid() self.softmax = Softmax() self.criterion = CrossEntropy() def _init_weights(self, X, y): num_samples, num_features = X.shape _, num_outputs = y.shape limit = 1 / np.sqrt(num_features) shape = (num_features, self.num_hidden) self.w1 = np.random.uniform(-limit, limit, shape) self.b1 = np.zeros((1, self.num_hidden)) limit = 1 / np.sqrt(self.num_hidden) shape = (self.num_hidden, num_outputs) self.w2 = np.random.uniform(-limit, limit, shape) self.b2 = np.zeros((1, num_outputs)) def fit(self, X, y): """ y must be one hot encoded, ndim == 2 """ self._init_weights(X, y) for i in range(self.num_iterations): h_in = X.dot(self.w1) + self.b1 h_out = self.sigmoid(h_in) out = h_out.dot(self.w2) + self.b2 y_pred = self.softmax(out) grad_out = \ self.criterion.backward(y, y_pred) * self.softmax.backward(out) grad_w2 = h_out.T.dot(grad_out) grad_b2 = np.sum(grad_out, axis=0, keepdims=True) grad_hidden = \ grad_out.dot(self.w2.T) * \ self.sigmoid.backward(h_in) grad_w = X.T.dot(grad_hidden) grad_b1 = np.sum(grad_hidden, axis=0, keepdims=True) self.w2 -= self.lr * grad_w2 self.b2 -= self.lr * grad_b2 self.w1 -= self.lr * grad_w self.b1 -= self.lr * grad_b1 def predict(self, X): h_in = X.dot(self.w1) + self.b1 h_out = self.sigmoid(h_in) out = h_out.dot(self.w2) + self.b2 y_pred = self.softmax.backward(out) return y_pred
これが3層からなるMLPの実装です。MLPクラスのパラメータn_hiddenが隠れ層のニューロン数、num_iterationsが学習の繰り返し数、lrが重みを更新する学習率です。活性化関数にはsigmoid関数を用い、出力層はsoftmax関数を用いました。
まとめ
MLPについて重要な点をまとめましょう。
- 3層からなるニューラルネットワーク
- 活性化関数にsigmoid関数を利用している
- パーセプトロンで解けない線形分離不可能なデータを扱える