パーセプトロン

**パーセプトロン**（）は、ニューラルネットワークの一種であり、多層パーセプトロンはニューラルネットワークの最も基本的な形である。心理学者・計算機科学者のフランク・ローゼンブラットが1957年に考案し、1958年に論文を発表した。

1層の単純パーセプトロンで、活性化関数を標準シグモイド関数にした場合は、同じく1958年に発表されたロジスティック回帰と等価である。

## 概要
パーセプトロンは視覚と脳の機能をモデル化したものであり、パターン認識を行う。パーセプトロンの数式表記は多層パーセプトロンを参照。

パーセプトロンは1943年に発表された形式ニューロンに基づく。ローゼンブラットはこの形式ニューロンの考え方を基にしてパーセプトロンを開発した。S層（感覚層、入力層）、A層（連合層、中間層）、R層（反応層、出力層）の3層からなる。S層とA層の間はランダムに接続されている。S層には外部から信号が与えられる。A層はS層からの情報を元に反応する。R層はA層の答えに重みづけをして、多数決を行い、答えを出す。パーセプトロンにおいてこの重みと呼んでいる値が人間でいうところの記憶となる。ただし、記憶の学習というと見聞き感じた物を覚える様子を想像しがちだが、パーセプトロンにおける学習は、入力を期待する出力値に変換できる最適な重みの値を探す作業となる。

1970年頃、デビッド・マーとジェームズ・アルブスによって小脳はパーセプトロンであるという仮説が相次いで提唱された。後に神経生理学者伊藤正男らの前庭動眼反射に関する研究によって、平行繊維-プルキンエ細胞間のシナプスの長期抑圧（LTD; long-term depression）が見つかったことで、小脳パーセプトロン説は支持されている。

### 単純パーセプトロン
単純パーセプトロンは入力層と出力層のみからなる。1958年の論文に学習方法が書かれている。単純パーセプトロン (simple perceptron) は線形分離可能な問題を有限回の反復で解くことができる一方で、線形分離不可能な問題を解けない。

### 多層パーセプトロン

多層パーセプトロンは入力層と出力層以外に中間層を含むもの。多層にすれば線型分離不可能な問題が解けることは1943年のマカロックとピッツの頃から解っていた。1958年の論文では学習方法が書かれていなかった。現代の標準的な学習方法は、出力側からの偏微分（バックプロパゲーション）と確率的勾配降下法を使用する方法で、1960年代に開発され、1986年に標準的な手法として浸透した。

## 沿革
1958年の論文でパーセプトロンと呼んでいるものは論文の図1に書かれている多層パーセプトロンだが、単純化した中間層のない単純パーセプトロンを図2に書いている。入出力は0または1で、活性化関数はヘヴィサイドの階段関数である。そして、単純パーセプトロンに対する学習規則を論文の中でいくつか提案している。論文の17ページ目のBIVALENT SYSTEMSの章では、入出力の不一致に合わせて\pm \Delta Vパラメータの値を増減させる学習方法が書かれている。後に、ローゼンブラッドは線形分離可能であれば、この手法は収束することを証明している。

ニューラルネットワークに関する歴史は、それがパーセプトロンの歴史だと言っても過言ではない。1960年代に爆発的なニューラルネットブームを巻き起こした。

多層パーセプトロンは確率的勾配降下法で学習させることができる。確率的勾配降下法自体は1951年に発表されたもので、1960年にバーナード・ヴィドローとマーシャン・ホフが活性化関数を外した単純パーセプトロンを確率的勾配降下法で学習させ、Widrow-Hoff法（デルタルール）と命名した。1967年に確率的勾配降下法を多層パーセプトロンの学習に使えるということを甘利俊一が発表し、1968年に書籍『情報理論II ―情報の幾何学的理論―』を出版し説明している。著書の中では、線形分離可能なものしか学習できない単純パーセプトロンとは異なり、線形分離不可能なジグザグの境界面を二値分類できる例をp.119に記載している。そして、確率的勾配降下法には偏微分の計算が必要だが、1960年代に開発された自動微分では、出力側から偏微分すると効率よく計算できることが分かっている。この2つの知識を合わせると、現代のニューラルネットワークと同じ学習方法になる。

しかしながら、これらの手法は浸透せず、学習できるということを知らなかったマービン・ミンスキーとシーモア・パパートが書籍『Perceptrons: An Introduction to Computational Geometry』を1969年に出版し、単純パーセプトロンは線形分離可能なものしか学習できないと指摘し、一時ニューラルネットワークの研究を停滞させた。1986年に、デビッド・ラメルハート、ジェフリー・ヒントン等がバックプロパゲーションを論文誌ネイチャーで発表し、多層パーセプトロンは出力側から偏微分すると良いという論文を発表した。なお、この論文自体は式(7)と(8)の間の説明で、確率的勾配降下法（change the weights after every input-output case）ではなく、最急降下法（accumulate  over all the input-output case）を使用していると書いているが、すぐに確率的勾配降下法が定着し、この論文から第2次AIブームが発生する。

多層パーセプトロンの影響を受けた変種といえるニューラルネットワークも畳み込みニューラルネットワークやボルツマンマシンなど多数提案されているが、それらについてはここでは略し、ニューラルネットワークの記事を参照。

そして、1990年代にはまたAI研究は停滞した。2012年に畳み込みニューラルネットワークのAlexNetが画像認識コンテストで優勝し、第3次AIブームが起こり、層の数を増やした深層学習(ディープラーニング)がブームとなり、2017年にTransformerが発表され、これが大規模言語モデルで上手く行くことが判明し、ブームが継続した。

## 実装例
### 単純パーセプトロン
下記にPythonによる単純パーセプトロンの例を示す。この例では論理演算用途を想定している。ここでは1958年の論文で使用されていたパーセプトロン学習則を使用して学習する。

下記の例で試すとわかるが、多層パーセプトロンではなく単純パーセプトロンのため、論理積（AND）と論理和（OR）は学習できるが、排他的論理和（XOR）は線形分離可能ではないため学習できない。


```python
import random

class Neuron:
    """
    単純パーセプトロンの実装クラス
    """

    # class定義及び初期化部
    def __init__(self, size_integer):
        """
        重みの集合とニューロンは一対であるため、ニューロンを重みの配列として定義している。
        """
        # 重みの初期値は確率的勾配降下法に基づくため
        # 全要素を乱数(0.0～1.0)で初期化する必要がある。
        self.weights = [random.random() for _ in range(size_integer)]

    # 思考部
    def _activation_function(self, value):
        """
        出力値を整形する活性化関数を提供する。
        今回は論理値を返せれば良いので出力が0以下なら0、0超なら1を返す。
        """
        return 1 if value > 0 else 0

    def thought(self, input_values_collection):
        """
        学習結果に基づき、入力値から出力値を出す思考関数。
        パーセプトロンの本体といえる。

        入力値としては下記の形式の値を期待する。
        [ 0, 0 ]
        [ 0, 1 ]
        """
        # 入力値の数の制限。入力値の数は重みの数よりも1つ少なくなければならない。
        if len(self.weights) != len(input_values_collection) + 1:
            raise ValueError('input_values_collectionの長さはweightsの長さより1つ少なくなければなりません')

        # パーセプトロンの最重要部。入力値と重みをそれぞれ掛け合わせ、
        # その合計から出力値を出す。
        weighted_sum = sum(w * i for w, i in zip(self.weights, input_values_collection + [1]))

        return self._activation_function(weighted_sum)

    def _error_function(self, output_value, expected_number):
        """
        誤った値を正解の値に近づけるための誤差関数を提供する。
        正確には誤差関数そのものではないが、誤差関数は偏微分により
        誤差関数を構成する式の半分が消失するためここでは下記を誤差関数として扱う。
        """
        return expected_number - output_value

    # 学習部
    def learn_from(self, input_value_collection, expected_number):
        """
        1通り分の学習関数。
        [ 1, 1 ]であれば1であるべきという値を受け取り、
        そうなっていなければ重みを正解に近づくよう調整する。
        """
        # 現在の重みで思考する
        result = self.thought(input_value_collection)

        # 誤差を計算
        error = self._error_function(result, expected_number)

        # 0.001は精度と学習速度を調整する値で、それらを鑑みてちょうど良い値を指定する。
        learning_rate = 0.001

        # 上記の実行結果を正解の値に近づける。
        # 調整後の重みを更新し、結果と共に返す。
        new_weights = []
        for w, i in zip(self.weights, input_value_collection + [1]):
            # 重みの更新式: w = w + (誤差 * 入力 * 学習率)
            delta = error * i * learning_rate
            new_weights.append(w + delta)

        self.weights = new_weights

        # 調整後の重みと、実行結果(学習前の判定結果)を返す
        return self.weights, result

    def learn_all(self, expected_with_input_values_collection):
        """
        全通り分の学習関数。
        全ての条件が正解になるまで学習を繰り返す。
        1通り分の学習関数では、下記のうちどれか1つを満たす単純パーセプトロンしか作ることができない。
        この関数では下記すべてを満たす単純パーセプトロンを作る。

        [ 0, 0 ] であれば0。
        [ 0, 1 ] であれば0。
        [ 1, 0 ] であれば0。
        [ 1, 1 ] であれば1。

        入力値としては下記の形式の値を期待する。
        一番左の値は正解値となる。
        入力値形式:
        [
            ( 0, [ 0, 0 ] ),
            ( 1, [ 1, 1 ] ),
        ]
        """
        while True:
            results_check = []

            for expected, inputs in expected_with_input_values_collection:
                # 1つずつ学習を実行
                updated_weights, result = self.learn_from(inputs, expected)

                # 実行結果が正解になっているか確認する (True/Falseをリストに保存)
                results_check.append(expected == result)

            # 不正解(False)が残っているか確認し、残っていなければ終了
            if all(results_check):
                break

        return self.weights


# 使用部
if __name__ == '__main__':
    # 3つのパラメータ（2つの重みと1つのバイアス）を持つニューロンを生成
    and_neuron = Neuron(3)

    # 論理積(AND)を学習させる。
    # データ形式: (正解ラベル, [入力1, 入力2])
    training_data = [
        (0, [0, 0]),
        (0, [0, 1]),
        (0, [1, 0]),
        (1, [1, 1]),
    ]

    and_neuron.learn_all(training_data)

    # 結果確認
    print("論理積回路として動作する:")

    test_1 = [0, 1]
    result_1 = and_neuron.thought(test_1)
    print(f"Input: {test_1} -> {result_1}")  # → 0

    test_2 = [1, 1]
    result_2 = and_neuron.thought(test_2)
    print(f"Input: {test_2} -> {result_2}")  # → 1
```


### 多層パーセプトロン
多層パーセプトロンの実装例は多層パーセプトロンを参照。

## 脚注

## 参考文献

* 
* 
* 

## 関連事項
* シグモイド関数
* バックプロパゲーション
* PDPモデル
* 認知科学
* 機械学習
* 多層パーセプトロン
* デビッド・マー
* ロジスティック回帰
* 線形分類器