多層パーセプトロン

**多層パーセプトロン**（たそうパーセプトロン、、略称: **MLP**）は、順伝播型ニューラルネットワークの一分類であり、ニューラルネットワークの中で最も基本的な形である。1958年にフランク・ローゼンブラットが発表した。

パーセプトロンは発表当初から多層であるが、入力層・出力層以外に中間層が1つ以上あることを強調する場合は多層パーセプトロンと呼び、中間層がないことを強調する場合は単純パーセプトロンと呼ぶ。多層パーセプトロンは線形分離可能ではないデータを識別できる。

多層パーセプトロンの教師あり学習には現代では確率的勾配降下法が通常は使用され、1967年に甘利俊一がそれを発表しているが、定着せず、この手法は、1986年のデビッド・ラメルハート等の誤差逆伝播法（バックプロパゲーション）の発表の直後から手法として定着した。

ニューラルネットワークで層の数を数える際は入力層を含めないことが一般的で、単純パーセプトロンとは出力層の1層だけであることを指し、多層パーセプトロンは中間層を含めて2層以上あることを指す。

多層パーセプトロンは時折、特に単一の中間層を持つ時、「バニラ」ニューラルネットワークと口語的に呼ばれることがある。

パーセプトロンは、入力ノードを除けば、個々のノードは非線形活性化関数を使用する人工ニューロンである。

## 理論

2層の多層パーセプトロンは以下の方法で計算する。

# 隠れ層の計算
#: 入力 x に重み W^{(1)} を掛けてバイアス b^{(1)} を足し合わせ、活性化関数 \sigma_1 を適用して隠れ層の出力 h を計算する。
#: h = \sigma_1(W^{(1)}x + b^{(1)})
# 出力層の計算
#: 次に、隠れ層の出力 h に重み W^{(2)} を掛けてバイアス b^{(2)} を足し合わせ、活性化関数 \sigma_2 を適用して最終的な出力 y を計算する。
#: y = \sigma_2(W^{(2)}h + b^{(2)})

変数の定義

* x: 入力ベクトル
* W^{(1)}: 入力層から隠れ層への重み行列
* b^{(1)}: 隠れ層のバイアスベクトル
* \sigma_1: 隠れ層の活性化関数（例: tanhやReLUなど）
* h: 隠れ層の出力ベクトル
* W^{(2)}: 隠れ層から出力層への重み行列
* b^{(2)}: 出力層のバイアスベクトル
* \sigma_2: 出力層の活性化関数（例: 標準シグモイド関数やソフトマックス関数や恒等関数など）
* y: 最終的な出力ベクトル

\sigma_1 と \sigma_2 は、解きたいタスク（回帰、分類など）によって変わる。\sigma_2 は回帰なら恒等関数、二値分類なら標準シグモイド関数、多クラス分類ならソフトマックス関数がよく使われる。1958年のローゼンブラットの論文で使用されていたのはヘヴィサイドの階段関数だが、これは微分すると勾配が0になるので現代では通常は使われない。

パラメータ（重みとバイアス）を学習させるには、まず、損失関数を定める。回帰では平均二乗誤差、分類なら交差エントロピーが良く使われる。損失関数のパラメータに関する偏微分をバックプロパゲーション（トップダウン型の自動微分）で計算し、確率的勾配降下法で損失関数の損失を小さくするようにパラメータを学習させるのが、現代の標準的な手法である。

### 活性化関数

パーセプトロンでは、入力層以外の人工ニューロンは、生物学的ニューロンの活動電位の頻度および発火をモデル化するために開発された「非線形」の活性化関数を用いる。

もし、多層パーセプトロンが全ての人工ニューロンにおいて線形の活性化関数を持つとすると、線形代数から、何層であったとしても、1層からなる入力-出力モデル、つまり単なるアフィン変換 Wx + b に削減することができる。よって、活性化関数は非線形でなければならない。

一般的な2つの活性化関数はどちらもシグモイドであり、以下の式で表わされる。
:y = \tanh(x)
:y = \frac{1}{1+e^{-x}} = \frac{\tanh(x/2) + 1}{2}

1つ目の式は−1から1の範囲の双曲線正接（tanh）関数である。2つ目の式は標準シグモイド関数で、tanhをアフィン変換したもので、範囲は0から1である。浮動小数点数は0の近傍で精度が良いので、原点を通した方が良いという理由からこの2つの中ではtanhを使用するのが現代では基本だが、1986年のデビッド・ラメルハート等のバックプロパゲーションの論文や書籍では標準シグモイド関数を使用していた。

他には、ReLUやソフトプラス関数など様々な活性化関数が提案されている。より専門分野に特化した活性化関数に放射基底関数がある（教師ありニューラルネットワークモデルの一分類であるで使われる）。

### 層
多層パーセプトロンは、入力層以外に、非線形的に活性化されるノードの2つ以上の層（出力層と1つ以上の中間層）からなる。多層パーセプトロンは全結合（fully connected）のため、1つの層中のそれぞれのノードは次の層中の全てのノードと任意の重みw_{ij}で結合している。

### 損失関数のパラメータに関する偏微分

損失関数のパラメータに関する偏微分はバックプロパゲーションを使用して計算する。現代では、手作業で式変形せず、自動微分を使用して計算するのが通例である。手作業での計算方法はバックプロパゲーションを参照。

### 学習
学習は、出力と損失を計算した後に、損失関数のパラメータに関する偏微分値を計算し、それに基づいて、確率的勾配降下法にてパラメータを変化させる。これは教師あり学習である。

## 用語
「多層パーセプトロン」という用語は、複数の層を持つ単一のパーセプトロンを意味しない。むしろ、層へと組織化された多くのパーセプトロンを含む。代替用語は「多層パーセプトロンネットワーク」である。

さらに、現在の多層パーセプトロンは、最も厳密に言えば元々の1958年のパーセプトロンではない。元々の1958年のパーセプトロンは正式には、ヘヴィサイドの階段関数といった閾値活性化関数を用いる人工ニューロンの特殊な場合である。現在の多層パーセプトロンは任意の活性化関数を用いることができる。元々の1958年のパーセプトロンは二項分類を実行する。対して、現在の多層パーセプトロンは、その活性化関数に依存して分類あるいは回帰のどちらを実行するかは自由である。

「多層パーセプトロン」という用語は後に、ノード/層の特性に関係なく適用されるようになった。ノード/層は、パーセプトロンに限定されず、任意に定義されたニューロンから構成することができる。この解釈は、一般に人工ニューロンを意味するところの「パーセプトロン」の定義の緩和を避けている。

## 応用
多層パーセプトロンは、確率的に問題を解くことができるため研究において有用である。多層パーセプトロンはのような極めて複雑な問題に対する近似解をしばしば与える。

多層パーセプトロンはによって示されているように普遍的な関数近似器であるため、回帰分析によって数理モデルを作成するために使うことができる。分類は、応答変数がである時の回帰の特殊な例であり、多層パーセプトロンはよい分類アルゴリズムを作る。

多層パーセプトロンは1980年代に人気のある機械学習法であり、音声認識や画像認識、機械翻訳ソフトウェアといった多様な分野に応用されたが、その後より単純な（そして関連する）サポートベクターマシンとの激しい競争に直面した。ディープラーニングの成功によってニューラルネットワークへの関心が戻った。

## 実装例
以下はPythonで実装した、2層の多層パーセプトロン。y = \sin(x) を確率的勾配降下法で学習している。隠れ層の活性化関数にはtanhを使用し、出力層の活性化関数には恒等関数を使用した。損失関数は平均二乗誤差。自動微分やバックプロパゲーションを実装するとソースコードが長くなるのでPyTorchを使用した。自動微分の実装方法は自動微分を参照。


```python
from math import sqrt, pi
import torch

# データセットの作成
data_size = 1024
x = torch.linspace(-pi, pi, data_size).reshape(data_size, 1)
y = x.sin()

# 重みとバイアスの初期化
hidden_size = 64
scale = sqrt(6.0 / (1 + hidden_size))  # Glorot初期化
w1 = torch.empty(1, hidden_size).uniform_(-scale, scale).requires_grad_(True)
b1 = torch.zeros(hidden_size).requires_grad_(True)
w2 = torch.empty(hidden_size, 1).uniform_(-scale, scale).requires_grad_(True)
b2 = torch.zeros(1).requires_grad_(True)

learning_rate = 0.01  # 学習率
epochs = 50           # 全データを何周するか

for epoch in range(epochs):
    epoch_loss = 0
    for idx in torch.randperm(data_size):  # 訓練データ内をランダムな順番で計算
        # --- 順伝播 (出力の計算) ---
        hidden = (x[idx] @ w1 + b1).tanh()
        y_pred = hidden @ w2 + b2

        # --- 損失計算 (二乗誤差) ---
        loss = (y_pred - y[idx]) ** 2
        epoch_loss += loss.item()

        # --- 逆伝播 (自動微分) ---
        loss.backward()

        # --- パラメータ更新 (確率的勾配降下法) ---
        with torch.no_grad():
            for param in [w1, b1, w2, b2]:
                param -= learning_rate * param.grad
                param.grad.zero_()  # 更新が終わったら勾配をリセット（これをしないと勾配が累積され続ける）

    print(f'エポック [{epoch + 1}/{epochs}], 平均二乗誤差: {epoch_loss / data_size:.6f}')
```


## 脚注