単純パーセプトロンのソース解読が難解すぎる（僕には）

#はじめに

Python機械学習プログラミング第二版の、単純Perceptronのソースコードが難しかったです。
一つ一つ関数を調べながら四時間戦って、意味がわかった気がします。
戻ってきた時のためのメモです！


#クラス内変数定義部分

```python

class Perceptron(object):
    """パーセプトロン分類記.
    パラメータ
    ------------
    eta : float イータと読む
      Learning rate (between 0.0 and 1.0)
    n_iter : int
      Passes over the training dataset.
    random_state : int
      Random number generator seed for random weight
      initialization.
    Attributes
    -----------
    w_ : 1d-array 1次元配列 
    トレーニングデータセットの次元数分+1個入るハズ。
      Weights after fitting.　適合後の重み
    errors_ : list
      Number of misclassifications (updates) in each epoch.

    """
```
- eta:学習率、0<eta<1、イータと読む。
- n_iter:トレーニング回数(エポック数）何回トレーニングさせるか。
- random_state:？？？　後の、重みを初期化する時に0になるのを避ける為っぽいけどよくわかりませんでした。
- w_:更新後の重みが入る。トレーニングデータの次元数＋1個入る。
- errors_:何回までミスっていいか

#initメソッド
```python

    def __init__(self, eta=0.01, n_iter=50, random_state=1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state

```

このクラスをインスタンス化するときは必ず初期化する。
学習率：0.01
学習の回数：50
ランダム数字：1？とりあえず

#fitメソッド
```python

    def fit(self, X, y):
        """Fit training data.
        Parameters
        ----------
        X : {array-like}, shape = [n_samples, n_features]
          Training vectors, where n_samples is the number of samples and
          n_features is the number of features.
        y : array-like, shape = [n_samples]
          Target values.
        Returns
        -------
        self : object
        """
```

n_samples:サンプルの個数100個
n_features:特徴量の個数2次元
X:2次元の特徴量が100個入ってる。がく片の長さと、花びらの長さの二つの情報が100個
y:正解のラベルが入っている配列。サンプルの個数分(100個)。

```python

        rgen = np.random.RandomState(self.random_state)
        self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
        self.errors_ = []
```
一行目でRandomStateのインスタンスをrgenに代入する。
 RandomStateは乱数を発生させるクラスらしい。擬似乱数生成機。
 引数は、乱数のシード？らしくて、今回はself.random_state=50に設定。シードよくわからぬ。
 そして、一回シードに0を渡して(初期化して)、同じシードを渡すと同じ乱数を出力する。同じ結果を再現できる。
 *rgen.randn(10)とすれば、10個の乱数を表示できる

二行目はself.w_に格納されている重みをゼロベクトルに初期化するもの。
 インスタンスメソッドnormalは正規分布から無作為にデータを抽出する。
 引数loc:分布の中心
 引数scale:分布の標準偏差
 引数size:出力形状の指定。X.shapeはタプル型で、その配列の2番目はn_features（特徴量の個数)
 出力形状は色々あるけど、ここでは、sizeの数分(101個)のデータが出力される。

http://zeema.hatenablog.com/entry/2017/11/05/130031
https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.RandomState.normal.html#numpy.random.RandomState.normal

```python

        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(X, y): 
                update = self.eta * (target - self.predict(xi))
                self.w_[1:] += update * xi
                self.w_[0] += update
                errors += int(update != 0.0)
            self.errors_.append(errors)
        return self

```

> for xi, target in zip(X, y):

zip関数:複数のリストからインデックス順に取得。複数の配列を、いい感じに変数に置けるメソッドと理解。
Xとyを前から順番に一つずつとってくる。このときxiとtargetのナンバリングは一致している。

三行目について。Xは特徴量の2次元が100個入った配列。yは正解ラベルが100個入った配列(index)。
つまり、i番目のデータについて、xiは特徴量、targetは正解ラベルが入っている。
▼zip関数について
https://note.nkmk.me/python-zip-usage-for/

> self.w_[1:] += update * xi
> self.w_[0] += update

self.w_の配列の中に、重みを足し合わせていく。
この時、0番目がバイアスユニット。
1番目が特徴量の1次元目についての重み。w1的なやつ。
2番目が特徴量の2次元目についての重み。w2的なやつ。

▼ここ分からんかったら、このサイト。データ数を3つで考えていて、単純化されていてわかりやすい
https://thinkit.co.jp/article/10342?page=0%2C2
> errors += int(update != 0.0)

(update = Δw)
updateが0でないならば int(True)=1を代入。
updateが0であるならば int(False)=0を代入。
errorsは self.w_の状態で、**updateが0でない回数**を記録する変数。
つまり、updateが全部0なら誤差を修正しきったということ。

>self.errors_.append(errors)

self.errors_が0に収束していけば、誤差を修正終了！！
errorsが0だから。


#net_inputメソッド
```python

    def net_input(self, X):
        """Calculate net input"""
        return np.dot(X, self.w_[1:]) + self.w_[0]
```
net_inputは総入力zを返す関数。
np.dot関数は行列の積や、ベクトルの内積を計算する。
z = w0 + w1 * x1 + w2 * 2... みたいなやつを分解します。

w0 = self.w_[0]
w1 = self.w_[1]
w2 = self.w_[2]
と照らし合わせられる。

Xは1*2の行列
self.w_[1:]は2*1の行列
なので行列の積が求まる。

よって、
z = np.dot(X,self.w_[1:]) + self.w_[0]
これが総入力！これをreturn

#predictメソッド
```python

    def predict(self, X):
        """Return class label after unit step"""
        return np.where(self.net_input(X) >= 0.0, 1, -1)

```

returnされた総入力が0.0を超えていれば1をreturn
超えていなければ-1をreturn


#まとめ

十分小さい重み(!=0)で学習スタート
↓
特徴量xiと正解ラベルtargetの差に比例して学習を進める。
(差が大きいほど、updateが大きい)
↓
n_iter回まで学習を進めて、updateが0に収束していたらいい感じ


▼解説した総ソースコード
https://github.com/rasbt/python-machine-learning-book-2nd-edition