深層学習入門 ~コーディング準備編~

# 対象者
[前回の記事](https://qiita.com/kuroitu/items/221e8c477ffdd0774b6b)の続きです。
本記事ではこれからコーディングしていくための準備をします。
自分で実装したいけどなかなか...という方はぜひ見ていってください。
(基本的に複雑なテクニックとかはぼくが使えませんのでご安心ください)
**コーディング環境はjupyter notebookです。**
他でモジュールレベルで管理する場合は`import`などのコマンドのコメントを外してください。
もし動かなかったらコメントで教えてください...
次回の記事は[こちら](https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c)

# 目次
- [レイヤーモジュールのコード準備](#レイヤーモジュールのコード準備)
- [レイヤーマネージャのコード準備](#レイヤーマネージャのコード準備)
    - [特殊メソッドの実装](#特殊メソッドの実装)
        - [`__init__`メソッドの実装](#__init__メソッドの実装)
        - [`__repr__`メソッドと`__str__`メソッドの実装](#__repr__メソッドと__str__メソッドの実装)
        - [`__len__`メソッドの実装](#__len__メソッドの実装)
        - [`__getitem__`メソッドの実装](#__getitem__メソッドの実装)
        - [`__setitem__`メソッドの実装](#__setitem__メソッドの実装)
        - [`__delitem__`メソッドの実装](#__delitem__メソッドの実装)
    - [ユーザ定義メソッドの実装](#ユーザ定義メソッドの実装)
        - [`_rename`メソッドの実装](#_renameメソッドの実装)
        - [`append`メソッドの実装](#appendメソッドの実装)
        - [`extend`メソッドの実装](#extendメソッドの実装)
        - [`insert`メソッドの実装](#insertメソッドの実装)
        - [`extend_insert`メソッドの実装](#extend_insertメソッドの実装)
        - [`remove`メソッドの実装](#removeメソッドの実装)
        - [その他のメソッドの実装](#その他のメソッドの実装)
        - [プロパティの実装](#プロパティの実装)
    - [コード全文](#コード全文)
- [レイヤーエラーのコード準備](#レイヤーエラーのコード準備)
- [おわりに](#おわりに)
- [掲載したコード一覧](#掲載したコード一覧)


# レイヤーモジュールのコード準備
まずはベースとなる入れ物を作ります。
<details><summary>baselayer.py</summary><div>

```baselayer.py
import numpy as np


class BaseLayer():
    """
    全ての元となるレイヤークラス
    中間層と出力層で共通する処理を記述する。
    """
    
    def __init__(self):
        pass
    
    
    def forward(self):
        """
        順伝播の実装
        """
        pass
    
    
    def backward(self):
        """
        逆伝播の実装
        """
        pass
    
    
    def update(self):
        """
        パラメータ学習の実装
        """
        pass
```
</div></details>
まだ何も実装してませんのでただの入れ物です。定義されるべき関数名のみ実装しています。
この入れ物を継承するのは中間層および出力層です。
<details><summary>middlelayer.py</summary><div>

```middlelayer.py
import numpy as np


#import .baselayer import BaseLayer


class MiddleLayer(BaseLayer):
    """
    中間層クラス
    入力層も実装上は中間層の一つとして取り扱います。
    """
    pass
```
</div></details>
<details><summary>outputlayer.py</summary><div>

```outputlayer.py
import numpy as np


#from .baselayer import BaseLayer


class OutputLayer(BaseLayer):
    """
    出力層クラス
    """
    pass
```
</div></details>

# レイヤーマネージャのコード準備
ここでは、レイヤーモジュールを取り扱うためのマネージャを準備します。
**最後に一つにまとめてコードを載せます。もしかしたら途中のコードには変更忘れなどがあるかもしれませんのでご注意ください。**
基本的にはPythonの`List`型や`Dictionary`型を意識して作っていきます。
<details><summary>layermanager.py</summary><div>

```layermanager.py
import numpy as np


#from .layer import *


class _TypeManager():
    """
    層の種類に関するマネージャクラス
    """
    N_TYPE = 2  # 層の種類数
    
    MIDDLE = 0  # 中間層のナンバリング
    OUTPUT = 1  # 出力層のナンバリング


class LayerManager(_TypeManager):
    """
    層を管理するためのマネージャクラス
    """
    def __init__(self):
        pass
```
</div></details>

`_TypeManager`クラスは層の種類の数とそれぞれのナンバリングを保持させるクラスです。最初についている`_`(アンダーバー)は外部モジュールからのアクセスを制限する、他の言語で言うと`private`なクラスです。
とはいえPythonには完全な`private`属性は存在しないのでアクセス自体は可能だったりします。

`LayerManager`クラスは`Dictionary`型を意識して、層のリストを保持したり追加・削除などを行えるようにします。
また、`_TypeManager`を継承させておきます。

ちなみに`layer`モジュールは`Layer`関連のモジュールをまとめてインポートするためのモジュールです。
<details><summary>layer.py</summary><div>

```layer.py
from .middlelayer import *
from .outputlayer import *
from ._layererror import *
```
</div></details>

`_layererror`モジュールは後述しますが、レイヤーで発生するエラーをまとめたモジュールにする予定です。

## 特殊メソッドの実装
まずは`LayerManager`クラスの特殊メソッドを実装しておきます。特殊メソッド一覧については[公式ドキュメント](https://docs.python.org/ja/3/reference/datamodel.html)をご覧ください。

### `__init__`メソッドの実装
では、`LayerManager`クラスの`__init__`メソッドを実装します。
そもそも`__init__`メソッドとはPythonの特殊メソッドの一つで、クラスのインスタンス生成時に呼ばれるメソッドの一つです。
つまり、`LayerManager`クラスを生成されたときに行って欲しい動作を記述します。
ここでは

 - 層のリストを保持
 - 層の名前のリストを保持
 - 種類別の層の数を保持

をして欲しいので、そのように実装します。
<details><summary>`__init__`の実装</summary><div>

```layermanager.py
    def __init__(self):
        self.__layer_list = []  # レイヤーのリスト
        self.__name_list = []   # 各レイヤーの名前リスト
        self.__ntype = np.zeros(self.N_TYPE)  # 種類別レイヤーの数
```
</div></details>

もちろん外部からは触られたくないので`__`(アンダースコア二つ)で`private`化しておきましょう。

### `__repr__`メソッドと`__str__`メソッドの実装
`__repr__`メソッドはPythonのビルドイン関数の一つである`repr`関数から呼び出される「公式な」文字列表現です。
「公式な」とか言いますが、要は詳細な情報ということです。
`__str__`メソッドはPythonのビルドイン関数の一つである`str`関数や、組み込み関数である`format`関数や`print`関数から呼び出される「非公式な」文字列表現です。
「非公式な」の意味はつまり見やすい情報ということです。
<details><summary>`__repr__`メソッドと`__str__`メソッドの実装</summary><div>

```layermanager.py
    def __repr__(self):
        layerRepr= "layer_list: " + repr(self.__layer_list)
        nameRepr = "name_list: " + repr(self.__name_list)
        ntypeRepr = "ntype: " + repr(self.__ntype)
        return (layerRepr + "\n"
                + nameRepr + "\n"
                + ntypeRepr)
    
    
    def __str__(self):
        layerStr = "layer_list: " + str(self.__layer_list)
        nameStr = "name_list: " + str(self.__name_list)
        ntypeStr = "ntype: " + str(self.__ntype)
        return (layerStr + "\n"
                + nameStr + "\n"
                + ntypeStr)
```
</div></details>

### `__len__`メソッドの実装
`__len__`メソッドではPythonのビルドイン関数の一つである`len`関数から呼ばれたときの動作を記述します。
`LayerManager`クラスが持つ層の数を返すようにしておきましょう。
<details><summary>`__len__`の実装</summary><div>

```layermanager.py
    def __len__(self):
        """
        Pythonのビルドイン関数`len`から呼ばれたときの動作を記述。
        種類別レイヤーの数の総和を返します。
        """
        return int(np.sum(self.__ntype))
```
</div></details>

### `__getitem__`メソッドの実装
`__getitem__`メソッドはPythonのリストやnumpy配列などで、インデックスなどを指定して要素を取得する際に呼び出されるメソッドです。
<details><summary>`__getitem__`の実装</summary><div>

```layermanager.py
    def __getitem__(self, key):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        x = lm[3].~~
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        sliceやstr, intでのアクセスのみ許可します。
        """
        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストをsliceで参照する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を返す。
            if key in self.__name_list:
                index = self.__name_list.index(key)
                return self.__layer_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を返す。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")
```
</div></details>
コメント入れすぎて長くなってしまいました...

### `__setitem__`メソッドの実装
`__setitem__`は`Dictionary`型などでよく用いられる、`key`と`value`を`lm[key] = value`のようにしてセットするときに呼ばれるメソッドです。
要素の上書きのみ認めて、それ以外はエラーを出すようにします。
理由は、ネーミングルールを後ほど用いるため、ユーザに勝手な`key`を登録させたくないからです。
<details><summary>`__setitem__`の実装</summary><div>

```layermanager.py
    def __setitem__(self, key, value):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        lm[1] = x
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        要素の上書きのみ認め、新規要素の追加などは禁止します。
        """
        value_type = ""
        if isinstance(value, list):
            # 右辺で指定された'value'が'list'なら
            # 全ての要素が'BaseLayer'クラスかそれを継承していなければエラー。
            if not np.all(
                np.where(isinstance(value, BaseLayer), True, False)):
                self.AssignError()
            value_type = "list"
        elif isinstance(value, BaseLayer):
            # 右辺で指定された'value'が'BaseLayer'クラスか
            # それを継承していない場合はエラー。
            self.AssignError(type(value))
        if value_type == "":
            value_type = "BaseLayer"

        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'list'でなければエラー。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "list":
                self.AssignError(value_type)
            self.__layer_list[key] = value
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            if key in self.__name_list:
                index = self.__name_list.index(key)
                self.__layer_list[index] = value
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            # また、異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            self.__layer_list[key] = value
        else:
            raise KeyError(key, ": Undefined such key type.")
```
</div></details>

### `__delitem__`メソッドの実装
`__delitem__`は`del lm[key]`などで呼び出されるメソッドです。こちらは指定の要素を削除するだけですね。
ただ、削除後の処理が少々面倒です。
<details><summary>`__delitem__`の実装</summary><div>

```layermanager.py
    def __delitem__(self, key):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        del lm[2]
        のように、del文でリストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        指定要素が存在すれば削除、さらにリネームを行います。
        """
        if isinstance(key, slice):
            # keyがスライスならそのまま指定の要素を削除
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[slice]
            del self.__name_list[slice]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当する要素を削除する。
            if key in self.__name_list:
                del self.__layer_list[index]
                del self.__name_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を削除する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")

        # リネームする
        self._rename()
```
</div></details>
実装内容自体は`__getitem__`などと似ていますが、リネームについては別の関数`_rename`に処理を投げています。いろいろなところで使うことが予想できるからですね。
これで特殊メソッドの実装は終了です。他の特殊メソッドは定義する必要がないのでスルーします。

#### 蛇足
ちなみに`_`(シングルアンダースコア)は`__`(ダブルアンダースコア)よりも弱い`private`属性を意味するものです。こちらは一応外部からでも`クラス名._rename`のようにしてアクセスすることができますが、ダブルアンダースコアの方は例えば`クラス名._モジュール名__メソッド名`のようにしないとアクセスできません。
また、クラスに属さないモジュール関数は`_`による`private`指定があると`from モジュール名 import *`によってインポートすることはできません。ちゃんと`from モジュール名 import _メソッド名`と呼ぶ必要があります。


## ユーザ定義メソッドの実装
ここからはユーザ定義(つまりぼくたちで定義する)関数を実装していきます。ここでは基本的に`List`型や`Dictionary`型の持つ有名どころなどを実装しようと思います。

### `_rename`メソッドの実装
とりあえず先ほどまでの実装で登場した`_rename`メソッドを実装します。
これが必要となるのはどういう時かというと、

- 途中に要素が追加される
- 途中の要素が削除される

の二通りですね。
頑張ってロジックを考えればうまく変更する数とか減らせそうですが、めんどくさいので改めて最初から数え直すことにします。実装が簡単なので。**メンテナンス性大事。**
<details><summary>`_rename`メソッドの実装</summary><div>

```layermanager.py
    def _rename(self):
        """
        リスト操作によってネームリストのネーミングがルールに反するものになった場合に
        改めてルールを満たすようにネーミングリストおよび各レイヤーの名前を変更する。

        ネーミングルールは[レイヤーの種類][何番目か]とします。
        レイヤーの種類はMiddleLayerならMiddle
                     OutputLayerならOutput
        のように略します。
        何番目かというのは種類別でカウントします。

        また、ここで改めて__ntypeのカウントを行います。
        """
        # 種類別レイヤーの数を初期化
        self.__ntype = np.zeros(self.N_TYPE)

        # 再カウントと各レイヤーのリネーム
        for i in range(len(self)):
            if "Middle" in self.__name_list[i]:
                self.__ntype[self.MIDDLE] += 1
                self.__name_list[i] = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
                self.__layer_list[i].name = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
            elif "Output" in self.__name_list[i]:
                self.__ntype[self.OUTPUT] += 1
                self.__name_list[i] = "Output{}".format(
                        self.__ntype[self.OUTPUT])
                self.__layer_list[i].name = "Output{}".format(
                        self.__ntype[self.OUTPUT])
            else:
                raise UndefinedLayerType(self.__name_list[i])
```
</div></details>
各レイヤーのリネームも行っていますが、これはレイヤーにも`name`プロパティを持たせておこうかなと考えているためです。
エラー文とかを発生させるときに便利かな〜という感じです。要らなければ消すかもしれません。

### `append`メソッドの実装
リストに要素を追加するメソッドでお馴染みの`append`メソッドを実装します。最後尾に追加するのでそんなに複雑な処理はありませんね。
<details><summary>`append`メソッドの実装</summary><div>

```layermanager.py
    def append(self, *, name="Middle", **kwds):
        """
        リストに要素を追加するメソッドでお馴染みのappendメソッドの実装。
        """
        if "prev" in kwds:
            # 'prev'がキーワードに含まれている場合、
            # 一つ前の層の要素数を指定していることになります。
            # 基本的に最初のレイヤーを挿入する時を想定していますので、
            # それ以外は基本的に自動で決定するため指定しません。
            if len(self) != 0:
                if kwds["prev"] != self.__layer_list[-1].n:
                    # 最後尾のユニット数と一致しなければエラー。
                    raise UnmatchUnitError(self.__layer_list[-1].n,
                                           kwds["prev"])
        else:
            if len(self) == 0:
                # 最初の層は必ず入力ユニットの数を指定する必要があります。
                raise UnmatchUnitError("Input units", "Unspecified")
            else:
                # 最後尾のレイヤーのユニット数を'kwds'に追加
                kwds["prev"] = self.__layer_list[-1].n

        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        # レイヤーを追加する。
        if name == "Middle":
            # 種類別レイヤーをインクリメントして
            self.__ntype[self.MIDDLE] += 1
            # 名前に追加し
            name += str(self.__ntype[self.MIDDLE])
            # ネームリストに追加し
            self.__name_list.append(name)
            # 最後にレイヤーを生成してリストに追加します。
            self.__layer_list.append(
                    MiddleLayer(name=name, **kwds))
        elif name == "Output":
            # こちらも同様です。
            self.__ntype[self.OUTPUT] += 1
            name += str(self.__ntype[self.OUTPUT])
            self.__name_list.append(name)
            self.__layer_list.append(
                    OutputLayer(name=name, **kwds))
        # ここでelse文を描いていないのはネーミングルールに則った名前に変更する
        # 段階で既に異常な'name'は省かれているからです。
```
</div></details>

### `extend`メソッドの実装
こちらはあまり馴染みがないかも？`List`の`extend`メソッドを実装します。
<details><summary>`extend`メソッドの説明</summary><div>
`extend`メソッドとは、要するにリストにリストを結合するときのメソッドです。
例えば

```example.py
x = [1, 2, 3]
y = [4, 5, 6]
x.append(y)
print(x)
```

とすると、

```console
[1, 2, 3, [4, 5, 6]]
```

のようになってしまうと思います。`extend`メソッドを使うと

```example.py
x = [1, 2, 3]
y = [4, 5, 6]
x.extend(y)
print(x)
````

の結果が

```console
[1, 2, 3, 4, 5, 6]
```

となります。
</div></details>
ということで実装します。
<details><summary>`extend`メソッドの実装</summary><div>

```layermanager.py
    def extend(self, lm):
        """
        extendメソッドでは既にある別のレイヤーマネージャ'lm'の要素を
        全て追加します。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        if len(self) != 0:
            if self.__layer_list[-1].n != lm[0].prev:
                # 自分の最後尾のレイヤーのユニット数と
                # 'lm'の最初のレイヤーの入力数が一致しない場合はエラー。
                raise UnmatchUnitError(self.__layer_list[-1].n,
                                       lm[0].prev)

        # それぞれ'extend'メソッドで追加
        self.__layer_list.extend(lm.layer_list)
        self.__name_list.extend(lm.name_list)

        # リネームする
        self._rename()
```
</div></details>
コード中に`lm.layer_list`とか`lm.name_list`とかありますが、これらは後述の`property`デコレータを用いてプロパティ(`getter`)として実装します。

### `insert`メソッドの実装
これもあんまり馴染みがないかもしれませんね。指定の位置に要素を追加するメソッドです。
<details><summary>`insert`メソッドの実装</summary><div>

```layermanager.py
    def insert(self, prev_name, name="Middle", **kwds):
        """
        insertメソッドでは、前のレイヤーの名前を指定しそのレイヤーと結合するように
        要素を追加します。
        """
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 'prev'がキーワードに含まれている場合、
        # 'prev_name'で指定されているレイヤーのユニット数と一致しなければエラー。
        if "prev" in kwds:
            if kwds["prev"] \
                != self.__layer_list[self.index(prev_name)].n:
                raise UnmatchUnitError(
                    kwds["prev"],
                    self.__layer_list[self.index(prev_name)].n)
        # 'n'がキーワードに含まれている場合、
        if "n" in kwds:
            # 'prev_name'が最後尾ではない場合は
            if prev_name != self.__name_list[-1]:
                # 次のレイヤーのユニット数と一致しなければエラー。
                if kwds["n"] != self.__layer_list[
                        self.index(prev_name)+1].prev:
                    raise UnmatchUnitError(
                        kwds["n"],
                        self.__layer_list[self.index(prev_name)].prev)
        # まだ何も要素がない場合は'append'メソッドを用いるようにエラーを出す。
        if len(self) == 0:
            raise RuntimeError(
                "You have to use 'append' method instead.")

        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1

        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        # 要素を挿入する
        # このとき、'name'はまだネーミングルールに則っていませんが、
        # あとでリネームするので気にしないでOKです。
        if "Middle" in name:
            self.__layer_list.insert(index,
                                     MiddleLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        elif "Output" in name:
            self.__layer_list.insert(index,
                                     OutputLayer(name=name, **kwds))
            self.__name_list.insert(index, name)

        # リネームする
        self._rename()
```
</div></details>

### `extend_insert`メソッドの実装
こちらはオリジナルです。`extend`メソッドと`insert`メソッドを組み合わせたような動作を行います。
<details><summary>`extend_insert`メソッドの実装</summary><div>

```layermanager.py
    def extend_insert(self, prev_name, lm):
        """
        こちらはオリジナル関数です。
        extendメソッドとinsertメソッドを組み合わせたような動作をします。
        簡単に説明すると、別のレイヤーマネージャをinsertする感じです。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 指定場所の前後のレイヤーとlmの最初・最後のレイヤーのユニット数が
        # それぞれ一致しなければエラー。
        if len(self) != 0:
            if self.__layer_list[self.index(prev_name)].n \
                    != lm.layer_list[0].prev:
                # 自分の指定場所のユニット数と'lm'の最初のユニット数が
                # 一致しなければエラー。
                raise UnmatchUnitError(
                    self.__layer_list[self.index(prev_name)].n,
                    lm.layer_list[0].prev)
            if prev_name != self.__name_list[-1]:
                # 'prev_name'が自分の最後尾のレイヤーでなく
                if lm.layer_list[-1].n \
                    != self.__layer_list[self.index(prev_name)+1].prev:
                    # 'lm'の最後尾のユニット数と自分の指定場所の次のレイヤーの
                    # 'prev'ユニット数と一致しなければエラー。
                    raise UnmatchUnitError(
                        lm.layer_list[-1].n,
                        self.__layer_list[self.index(prev_name)+1].prev)
        else:
            # 自分に何の要素もない場合は'extend'メソッドを使うようにエラーを出す。
            raise RuntimeError(
                "You have to use 'extend' method instead.")

        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1

        # 挿入場所以降の要素を'buf'に避難させてから一旦取り除き、
        # extendメソッドを使って要素を追加
        layer_buf = self.__layer_list[index:]
        name_buf = self.__name_list[index:]
        del self.__layer_list[index:]
        del self.__name_list[index:]
        self.extend(lm)

        # 避難させていた要素を追加する
        self.__layer_list.extend(layer_buf)
        self.__name_list.extend(name_buf)

        # リネームする
        self._rename()
```
</div></details>
ちょっと複雑に見えるかもしれませんが、やってること自体は単純です。多分もっとシンプルな書き方とかありそう...

### `remove`メソッドの実装
`remove`メソッドは指定の要素を削除するものですね。
こちらはもう[del文の実装](#__delitem__メソッドの実装)で済ませてあるのでそれを呼ぶだけですね。
<details><summary>`remove`メソッドの実装</summary><div>

```layermanager.py
    def remove(self, key):
        """
        removeメソッドでは指定の名前の要素を削除します。
        インデックスでの指定も許可します。
        """
        # 既に実装している'del'文でOKです。
        del self[key]
```
</div></details>

### その他のメソッドの実装
上記以外の、あると便利かな〜というメソッドリストです。
<details><summary>便利メソッド実装</summary><div>

```layermanager.py
    def index(self, target):
        return self.__name_list.index(target)


    def name(self, indices):
        return self.__name_list[indices]
```
</div></details>

### プロパティの実装
ここではプロパティを実装します。基本的に`getter`しか要らないので`@property`でデコレートして実装します。
もし`setter`を追加したいとかなった場合は`@プロパティ名.setter`デコレータを用いればOKですね。

<details><summary>プロパティの実装</summary><div>

```layermanager.py
    @property
    def layer_list(self):
        return self.__layer_list


    @property
    def name_list(self):
        return self.__name_list


    @property
    def ntype(self):
        return self.__ntype
```
</div></details>


### コード全文
これまでのレイヤーマネージャのコードを統合して全文として載せます。
もちろん今後の記事でメソッドなどを追加していきます。
<details><summary>layermanager.py</summary><div>

```layermanager.py
import numpy as np


class _TypeManager():
    """
    層の種類に関するマネージャクラス
    """
    N_TYPE = 2  # 層の種類数

    MIDDLE = 0  # 中間層のナンバリング
    OUTPUT = 1  # 出力層のナンバリング


class LayerManager(_TypeManager):
    """
    層を管理するためのマネージャクラス
    """
    def __init__(self):
        self.__layer_list = []  # レイヤーのリスト
        self.__name_list = []   # 各レイヤーの名前リスト
        self.__ntype = np.zeros(self.N_TYPE, dtype=int)  # 種類別レイヤーの数


    def __repr__(self):
        layerRepr= "layer_list: " + repr(self.__layer_list)
        nameRepr = "name_list: " + repr(self.__name_list)
        ntypeRepr = "ntype: " + repr(self.__ntype)
        return (layerRepr + "\n"
                + nameRepr + "\n"
                + ntypeRepr)


    def __str__(self):
        layerStr = "layer_list: " + str(self.__layer_list)
        nameStr = "name_list: " + str(self.__name_list)
        ntypeStr = "ntype: " + str(self.__ntype)
        return (layerStr + "\n"
                + nameStr + "\n"
                + ntypeStr)


    def __len__(self):
        """
        Pythonのビルドイン関数`len`から呼ばれたときの動作を記述。
        種類別レイヤーの数の総和を返します。
        """
        return int(np.sum(self.__ntype))


    def __getitem__(self, key):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        x = lm[3].~~
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        sliceやstr, intでのアクセスのみ許可します。
        """
        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストをsliceで参照する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を返す。
            if key in self.__name_list:
                index = self.__name_list.index(key)
                return self.__layer_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を返す。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __setitem__(self, key, value):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        lm[1] = x
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        要素の上書きのみ認め、新規要素の追加などは禁止します。
        """
        value_type = ""
        if isinstance(value, list):
            # 右辺で指定された'value'が'list'なら
            # 全ての要素が'BaseLayer'クラスかそれを継承していなければエラー。
            if not np.all(
                np.where(isinstance(value, BaseLayer), True, False)):
                self.AssignError()
            value_type = "list"
        elif isinstance(value, BaseLayer):
            # 右辺で指定された'value'が'BaseLayer'クラスか
            # それを継承していない場合はエラー。
            self.AssignError(type(value))
        if value_type == "":
            value_type = "BaseLayer"

        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'list'でなければエラー。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "list":
                self.AssignError(value_type)
            self.__layer_list[key] = value
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            if key in self.__name_list:
                index = self.__name_list.index(key)
                self.__layer_list[index] = value
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            # また、異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            self.__layer_list[key] = value
        else:
            raise KeyError(key, ": Undefined such key type.")


    def __delitem__(self, key):
        """
        例えば
        lm = LayerManager()

        +----------------+
        | (lmに要素を追加) |
        +----------------+

        del lm[2]
        のように、del文でリストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        指定要素が存在すれば削除、さらにリネームを行います。
        """
        if isinstance(key, slice):
            # keyがスライスならそのまま指定の要素を削除
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[slice]
            del self.__name_list[slice]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当する要素を削除する。
            if key in self.__name_list:
                del self.__layer_list[index]
                del self.__name_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を削除する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")

        # リネームする
        self._rename()


    def _rename(self):
        """
        リスト操作によってネームリストのネーミングがルールに反するものになった場合に
        改めてルールを満たすようにネーミングリストおよび各レイヤーの名前を変更する。

        ネーミングルールは[レイヤーの種類][何番目か]とします。
        レイヤーの種類はMiddleLayerならMiddle
                     OutputLayerならOutput
        のように略します。
        何番目かというのは種類別でカウントします。

        また、ここで改めて__ntypeのカウントを行います。
        """
        # 種類別レイヤーの数を初期化
        self.__ntype = np.zeros(self.N_TYPE)

        # 再カウントと各レイヤーのリネーム
        for i in range(len(self)):
            if "Middle" in self.__name_list[i]:
                self.__ntype[self.MIDDLE] += 1
                self.__name_list[i] = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
                self.__layer_list[i].name = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
            elif "Output" in self.__name_list[i]:
                self.__ntype[self.OUTPUT] += 1
                self.__name_list[i] = "Output{}".format(
                        self.__ntype[self.OUTPUT])
                self.__layer_list[i].name = "Output{}".format(
                        self.__ntype[self.OUTPUT])
            else:
                raise UndefinedLayerType(self.__name_list[i])

    def append(self, *, name="Middle", **kwds):
        """
        リストに要素を追加するメソッドでお馴染みのappendメソッドの実装。
        """
        if "prev" in kwds:
            # 'prev'がキーワードに含まれている場合、
            # 一つ前の層の要素数を指定していることになります。
            # 基本的に最初のレイヤーを挿入する時を想定していますので、
            # それ以外は基本的に自動で決定するため指定しません。
            if len(self) != 0:
                if kwds["prev"] != self.__layer_list[-1].n:
                    # 最後尾のユニット数と一致しなければエラー。
                    raise UnmatchUnitError(self.__layer_list[-1].n,
                                           kwds["prev"])
        else:
            if len(self) == 0:
                # 最初の層は必ず入力ユニットの数を指定する必要があります。
                raise UnmatchUnitError("Input units", "Unspecified")
            else:
                # 最後尾のレイヤーのユニット数を'kwds'に追加
                kwds["prev"] = self.__layer_list[-1].n

        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        # レイヤーを追加する。
        if name == "Middle":
            # 種類別レイヤーをインクリメントして
            self.__ntype[self.MIDDLE] += 1
            # 名前に追加し
            name += str(self.__ntype[self.MIDDLE])
            # ネームリストに追加し
            self.__name_list.append(name)
            # 最後にレイヤーを生成してリストに追加します。
            self.__layer_list.append(
                    MiddleLayer(name=name, **kwds))
        elif name == "Output":
            # こちらも同様です。
            self.__ntype[self.OUTPUT] += 1
            name += str(self.__ntype[self.OUTPUT])
            self.__name_list.append(name)
            self.__layer_list.append(
                    OutputLayer(name=name, **kwds))
        # ここでelse文を描いていないのはネーミングルールに則った名前に変更する
        # 段階で既に異常な'name'は省かれているからです。


    def extend(self, lm):
        """
        extendメソッドでは既にある別のレイヤーマネージャ'lm'の要素を
        全て追加します。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        if len(self) != 0:
            if self.__layer_list[-1].n != lm[0].prev:
                # 自分の最後尾のレイヤーのユニット数と
                # 'lm'の最初のレイヤーの入力数が一致しない場合はエラー。
                raise UnmatchUnitError(self.__layer_list[-1].n,
                                       lm[0].prev)

        # それぞれ'extend'メソッドで追加
        self.__layer_list.extend(lm.layer_list)
        self.__name_list.extend(lm.name_list)

        # リネームする
        self._rename()


    def insert(self, prev_name, name="Middle", **kwds):
        """
        insertメソッドでは、前のレイヤーの名前を指定しそのレイヤーと結合するように
        要素を追加します。
        """
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 'prev'がキーワードに含まれている場合、
        # 'prev_name'で指定されているレイヤーのユニット数と一致しなければエラー。
        if "prev" in kwds:
            if kwds["prev"] \
                != self.__layer_list[self.index(prev_name)].n:
                raise UnmatchUnitError(
                    kwds["prev"],
                    self.__layer_list[self.index(prev_name)].n)
        # 'n'がキーワードに含まれている場合、
        if "n" in kwds:
            # 'prev_name'が最後尾ではない場合は
            if prev_name != self.__name_list[-1]:
                # 次のレイヤーのユニット数と一致しなければエラー。
                if kwds["n"] != self.__layer_list[
                        self.index(prev_name)+1].prev:
                    raise UnmatchUnitError(
                        kwds["n"],
                        self.__layer_list[self.index(prev_name)].prev)
        # まだ何も要素がない場合は'append'メソッドを用いるようにエラーを出す。
        if len(self) == 0:
            raise RuntimeError(
                "You have to use 'append' method instead.")

        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1

        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "Middle" or name == "mid" or name == "m":
            name = "Middle"
        elif name == "Output" or name == "out" or name == "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        # 要素を挿入する
        # このとき、'name'はまだネーミングルールに則っていませんが、
        # あとでリネームするので気にしないでOKです。
        if "Middle" in name:
            self.__layer_list.insert(index,
                                     MiddleLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        elif "Output" in name:
            self.__layer_list.insert(index,
                                     OutputLayer(name=name, **kwds))
            self.__name_list.insert(index, name)

        # リネームする
        self._rename()


    def extend_insert(self, prev_name, lm):
        """
        こちらはオリジナル関数です。
        extendメソッドとinsertメソッドを組み合わせたような動作をします。
        簡単に説明すると、別のレイヤーマネージャをinsertする感じです。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 指定場所の前後のレイヤーとlmの最初・最後のレイヤーのユニット数が
        # それぞれ一致しなければエラー。
        if len(self) != 0:
            if self.__layer_list[self.index(prev_name)].n \
                    != lm.layer_list[0].prev:
                # 自分の指定場所のユニット数と'lm'の最初のユニット数が
                # 一致しなければエラー。
                raise UnmatchUnitError(
                    self.__layer_list[self.index(prev_name)].n,
                    lm.layer_list[0].prev)
            if prev_name != self.__name_list[-1]:
                # 'prev_name'が自分の最後尾のレイヤーでなく
                if lm.layer_list[-1].n \
                    != self.__layer_list[self.index(prev_name)+1].prev:
                    # 'lm'の最後尾のユニット数と自分の指定場所の次のレイヤーの
                    # 'prev'ユニット数と一致しなければエラー。
                    raise UnmatchUnitError(
                        lm.layer_list[-1].n,
                        self.__layer_list[self.index(prev_name)+1].prev)
        else:
            # 自分に何の要素もない場合は'extend'メソッドを使うようにエラーを出す。
            raise RuntimeError(
                "You have to use 'extend' method instead.")

        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1

        # 挿入場所以降の要素を'buf'に避難させてから一旦取り除き、
        # extendメソッドを使って要素を追加
        layer_buf = self.__layer_list[index:]
        name_buf = self.__name_list[index:]
        del self.__layer_list[index:]
        del self.__name_list[index:]
        self.extend(lm)

        # 避難させていた要素を追加する
        self.__layer_list.extend(layer_buf)
        self.__name_list.extend(name_buf)

        # リネームする
        self._rename()


    def remove(self, key):
        """
        removeメソッドでは指定の名前の要素を削除します。
        インデックスでの指定も許可します。
        """
        # 既に実装している'del'文でOKです。
        del self[key]


    def index(self, target):
        return self.__name_list.index(target)


    def name(self, indices):
        return self.__name_list[indices]


    @property
    def layer_list(self):
        return self.__layer_list


    @property
    def name_list(self):
        return self.__name_list


    @property
    def ntype(self):
        return self.__ntype
```
</div></details>
コメントが半分以上を占めてる気がする...もし邪魔でしたらどうぞ削除してくださいね〜


# レイヤーエラーのコード準備
`_layererror`モジュールの紹介です。とりあえずここまでの実装で出てきたエラーだけ実装してあります。
これも追加していく可能性があります。
<details><summary>_layererror.py</summary><div>

```_layererror.py
class LayerManagerError(Exception):
    """レイヤーモジュールにおけるユーザ定義エラーのベースクラス"""
    pass


class AssignError(LayerManagerError):
    def __init__(self, value=None):
        if not value is None:
            self.value = value
            self.message = (str(value) 
                         + ": Assigning that value is prohibited.")
        else:
            self.value = None
            self.message = "Assigning that value is prohibited."
    
    
    def __str__(self):
        return self.message


class UnmatchUnitError(LayerManagerError):
    def __init__(self, prev, n):
        self.prev = prev
        self.n = n
        
        self.message = "Unmatch units: {} and {}.".format(prev, n)
    
    
    def __str__(self):
        return self.message


class UndefinedLayerError(LayerManagerError):
    def __init__(self, type_name):
        self.type = type_name
        self.message = str(type_name) + ": Undefined layer type."
    
    
    def __str__(self):
        return self.message
```
</div></details>
ユーザ定義例外について意外とどこにも乗っていないので載せておきます。

最初の

```_layererror.py
class LayerManagerError(Exception):
    """レイヤーモジュールにおけるユーザ定義エラーのベースクラス"""
    pass
```

は、レイヤーに関する例外のキャッチを

```example.py
try:
    raise AssignError()
except LayerManagerError:
    print("catch LayerManagerError")
```

のように、これを継承するエラーを一括でキャッチできるようにするためのものです。もちろん個別にエラーキャッチすることもできます。
また、`__str__`メソッドを定義して初めてエラー文を出力できます。


# おわりに
ということで、ここではレイヤーオブジェクトの雛形と、それを扱うためのマネージャ、およびエラー制御のためのモジュールを作成しました。
次回以降の記事でどんどん実装の中身を追加していきます。


# 掲載したコード一覧
<details><summary>baselayer.py [ここ](#レイヤーモジュールのコード準備)</summary><div>

```baselayer.py
import numpy as np


class BaseLayer():
    """
    全ての元となるレイヤークラス
    中間層と出力層で共通する処理を記述する。
    """
    
    def __init__(self):
        pass
    
    
    def forward(self):
        """
        順伝播の実装
        """
        pass
    
    
    def backward(self):
        """
        逆伝播の実装
        """
        pass
    
    
    def update(self):
        """
        パラメータ学習の実装
        """
        pass
```
</div></details>
<details><summary>middlelayer.py [ここ](#レイヤーモジュールのコード準備)</summary><div>

```middlelayer.py
import numpy as np


#import .baselayer import BaseLayer


class MiddleLayer(BaseLayer):
    """
    中間層クラス
    入力層も実装上は中間層の一つとして取り扱います。
    """
    pass
```
</div></details>
<details><summary>outputlayer.py [ここ](#レイヤーモジュールのコード準備)</summary><div>

```outputlayer.py
import numpy as np


#from .baselayer import BaseLayer


class OutputLayer(BaseLayer):
    """
    出力層クラス
    """
    pass
```
</div></details>
<details><summary>layermanager.py [ここ](#コード全文)</summary><div>

```layermanager.py
import numpy as np


#from .layer import *


class _TypeManager():
    """
    層の種類に関するマネージャクラス
    """
    N_TYPE = 2  # 層の種類数
    
    MIDDLE = 0  # 中間層のナンバリング
    OUTPUT = 1  # 出力層のナンバリング


class LayerManager(_TypeManager):
    """
    層を管理するためのマネージャクラス
    """
    def __init__(self):
        self.__layer_list = []  # レイヤーのリスト
        self.__name_list = []   # 各レイヤーの名前リスト
        self.__ntype = np.zeros(self.N_TYPE)  # 種類別レイヤーの数
    
    
    def __repr__(self):
        layerRepr= "layer_list: " + repr(self.__layer_list)
        nameRepr = "name_list: " + repr(self.__name_list)
        ntypeRepr = "ntype: " + repr(self.__ntype)
        return (layerRepr + "\n"
                + nameRepr + "\n"
                + ntypeRepr)
    
    
    def __str__(self):
        layerStr = "layer_list: " + str(self.__layer_list)
        nameStr = "name_list: " + str(self.__name_list)
        ntypeStr = "ntype: " + str(self.__ntype)
        return (layerStr + "\n"
                + nameStr + "\n"
                + ntypeStr)
    
    
    def __len__(self):
        """
        Pythonのビルドイン関数`len`から呼ばれたときの動作を記述。
        種類別レイヤーの数の総和を返します。
        """
        return np.sum(self.__ntype)
    
    
    def __getitem__(self, key):
        """
        例えば
        lm = LayerManager()
        
        +----------------+
        | (lmに要素を追加) |
        +----------------+
        
        x = lm[3].~~
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        sliceやstr, intでのアクセスのみ許可します。
        """
        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストをsliceで参照する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を返す。
            if key in self.__name_list:
                index = self.__name_list.index(key)
                return self.__layer_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を返す。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            return self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")
    
    
    def __setitem__(self, key, value):
        """
        例えば
        lm = LayerManager()
        
        +----------------+
        | (lmに要素を追加) |
        +----------------+
        
        lm[1] = x
        のように、リストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        要素の上書きのみ認め、新規要素の追加などは禁止します。
        """
        value_type = ""
        if isinstance(value, list):
            # 右辺で指定された'value'が'list'なら
            # 全ての要素が'BaseLayer'クラスかそれを継承していなければエラー。
            if not np.all(
                np.where(isinstance(value, BaseLayer), True, False)):
                self.AssignError()
            value_type = "list"
        elif isinstance(value, BaseLayer):
            # 右辺で指定された'value'が'BaseLayer'クラスか
            # それを継承していない場合はエラー。
            self.AssignError(type(value))
        if value_type == "":
            value_type = "BaseLayer"
            
        if isinstance(key, slice):
            # keyがスライスならレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'list'でなければエラー。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "list":
                self.AssignError(value_type)
            self.__layer_list[key] = value
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当するレイヤーのリストの要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            if key in self.__name_list:
                index = self.__name_list.index(key)
                self.__layer_list[index] = value
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を上書きする。
            # ただし'value_type'が'BaseLayer'でなければエラー。
            # また、異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            if value_type != "BaseLayer":
                raise AssignError(value_type)
            self.__layer_list[key] = value
        else:
            raise KeyError(key, ": Undefined such key type.")
    
    
    def __delitem__(self, key):
        """
        例えば
        lm = LayerManager()
        
        +----------------+
        | (lmに要素を追加) |
        +----------------+
        
        del lm[2]
        のように、del文でリストや配列の要素にアクセスされたときに呼ばれるので、
        そのときの動作を記述。
        指定要素が存在すれば削除、さらにリネームを行います。
        """
        if isinstance(key, slice):
            # keyがスライスならそのまま指定の要素を削除
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[slice]
            del self.__name_list[slice]
        elif isinstance(key, str):
            # keyが文字列なら各レイヤーの名前リストからインデックスを取得して、
            # 該当する要素を削除する。
            if key in self.__name_list:
                del self.__layer_list[index]
                del self.__name_list[index]
            else:
                # keyが存在しない場合はKeyErrorを出す。
                raise KeyError("{}: No such item".format(key))
        elif isinstance(key, int):
            # keyが整数ならレイヤーのリストの該当要素を削除する。
            # 異常な値(Index out of rangeなど)が入力されたら
            # Pythonがエラーを出してくれます。
            del self.__layer_list[key]
        else:
            raise KeyError(key, ": Undefined such key type.")
        
        # リネームする
        self._rename()
    
    
    def _rename(self):
        """
        リスト操作によってネームリストのネーミングがルールに反するものになった場合に
        改めてルールを満たすようにネーミングリストおよび各レイヤーの名前を変更する。
        
        ネーミングルールは[レイヤーの種類][何番目か]とします。
        レイヤーの種類はMiddleLayerならMiddle
                     OutputLayerならOutput
        のように略します。
        何番目かというのは種類別でカウントします。
        
        また、ここで改めて__ntypeのカウントを行います。
        """
        # 種類別レイヤーの数を初期化
        self.__ntype = np.zeros(self.N_TYPE)
        
        # 再カウントと各レイヤーのリネーム
        for i in range(len(self)):
            if "Middle" in self.__name_list[i]:
                self.__ntype[self.MIDDLE] += 1
                self.__name_list[i] = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
                self.__layer_list[i].name = "Middle{}".format(
                        self.__ntype[self.MIDDLE])
            elif "Output" in self.__name_list[i]:
                self.__ntype[self.OUTPUT] += 1
                self.__name_list[i] = "Output{}".format(
                        self.__ntype[self.OUTPUT])
                self.__layer_list[i].name = "Output{}".format(
                        self.__ntype[self.OUTPUT])
            else:
                raise UndefinedLayerType(self.__name_list[i])
    
    def append(self, *, name="Middle", **kwds):
        """
        リストに要素を追加するメソッドでお馴染みのappendメソッドの実装。
        """
        if "prev" in kwds:
            # 'prev'がキーワードに含まれている場合、
            # 一つ前の層の要素数を指定していることになります。
            # 基本的に最初のレイヤーを挿入する時を想定していますので、
            # それ以外は基本的に自動で決定するため指定しません。
            if len(self) != 0:
                if kwds["prev"] != self.__layer_list[-1].n:
                    # 最後尾のユニット数と一致しなければエラー。
                    raise UnmatchUnitError(self.__layer_list[-1].n,
                                           kwds["prev"])
        else:
            if len(self) == 0:
                # 最初の層は必ず入力ユニットの数を指定する必要があります。
                raise UnmatchUnitError("Input units", "Unspecified")
            else:
                # 最後尾のレイヤーのユニット数を'kwds'に追加
                kwds["prev"] = self.__layer_list[-1].n

        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "mid" or "m":
            name = "Middle"
        elif name == "out" or "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)

        # レイヤーを追加する。
        if name == "Middle":
            # 種類別レイヤーをインクリメントして
            self.__ntype[self.MIDDLE] += 1
            # 名前に追加し
            name += str(self.__ntype[self.MIDDLE])
            # ネームリストに追加し
            self.__name_list.append(name)
            # 最後にレイヤーを生成してリストに追加します。
            self.__layer_list.append(
                    MiddleLayer(name=name, **kwds))
        elif name == "Output":
            # こちらも同様です。
            self.__ntype[self.OUTPUT] += 1
            name += str(self.__ntype[self.OUTPUT])
            self.__name_list.append(name)
            self.__layer_list.append(
                    OutputLayer(name=name, **kwds))
        # ここでelse文を描いていないのはネーミングルールに則った名前に変更する
        # 段階で既に異常な'name'は省かれているからです。
    
    
    def extend(self, lm):
        """
        extendメソッドでは既にある別のレイヤーマネージャ'lm'の要素を
        全て追加します。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        if len(self) != 0:
            if self.__layer_list[-1].n != lm[0].prev:
                # 自分の最後尾のレイヤーのユニット数と
                # 'lm'の最初のレイヤーの入力数が一致しない場合はエラー。
                raise UnmatchUnitError(self.__layer_list[-1].n,
                                       lm[0].prev)
        
        # それぞれ'extend'メソッドで追加
        self.__layer_list.extend(lm.layer_list)
        self.__name_list.extend(lm.name_list)
        
        # リネームする
        self._rename()
    
    
    def insert(self, prev_name, name="Middle", **kwds):
        """
        insertメソッドでは、前のレイヤーの名前を指定しそのレイヤーと結合するように
        要素を追加します。
        """
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 'prev'がキーワードに含まれている場合、
        # 'prev_name'で指定されているレイヤーのユニット数と一致しなければエラー。
        if "prev" in kwds:
            if kwds["prev"] \
                != self.__layer_list[self.index(prev_name)].n:
                raise UnmatchUnitError(
                    kwds["prev"],
                    self.__layer_list[self.index(prev_name)].n)
        # 'n'がキーワードに含まれている場合、
        if "n" in kwds:
            # 'prev_name'が最後尾ではない場合は
            if prev_name != self.__name_list[-1]:
                # 次のレイヤーのユニット数と一致しなければエラー。
                if kwds["n"] != self.__layer_list[
                        self.index(prev_name)+1].prev:
                    raise UnmatchUnitError(
                        kwds["n"],
                        self.__layer_list[self.index(prev_name)].prev)
        # まだ何も要素がない場合は'append'メソッドを用いるようにエラーを出す。
        if len(self) == 0:
            raise RuntimeError(
                "You have to use 'append' method instead.")
        
        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1
        
        # レイヤーの種類を読み取り、ネーミングルールに則った名前に変更する
        if name == "mid" or "m":
            name = "Middle"
        elif name == "out" or "o":
            name = "Output"
        else:
            raise UndefinedLayerError(name)
        
        # 要素を挿入する
        # このとき、'name'はまだネーミングルールに則っていませんが、
        # あとでリネームするので気にしないでOKです。
        if "Middle" in name:
            self.__layer_list.insert(index,
                                     MiddleLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        elif "Output" in name:
            self.__layer_list.insert(index,
                                     OutputLayer(name=name, **kwds))
            self.__name_list.insert(index, name)
        
        # リネームする
        self._rename()
    
    
    def extend_insert(self, prev_name, lm):
        """
        こちらはオリジナル関数です。
        extendメソッドとinsertメソッドを組み合わせたような動作をします。
        簡単に説明すると、別のレイヤーマネージャをinsertする感じです。
        """
        if not isinstance(lm, LayerManager):
            # 'lm'のインスタンスがLayerManagerでなければエラー。
            raise TypeError(type(lm), ": Unexpected type.")
        # 'prev_name'が存在しなければエラー。
        if not prev_name in self.__name_list:
            raise KeyError(prev_name, ": No such key.")
        # 指定場所の前後のレイヤーとlmの最初・最後のレイヤーのユニット数が
        # それぞれ一致しなければエラー。
        if len(self) != 0:
            if self.__layer_list[self.index(prev_name)].n \
                    != lm.layer_list[0].prev:
                # 自分の指定場所のユニット数と'lm'の最初のユニット数が
                # 一致しなければエラー。
                raise UnmatchUnitError(
                    self.__layer_list[self.index(prev_name)].n,
                    lm.layer_list[0].prev)
            if prev_name != self.__name_list[-1]:
                # 'prev_name'が自分の最後尾のレイヤーでなく
                if lm.layer_list[-1].n \
                    != self.__layer_list[self.index(prev_name)+1].prev:
                    # 'lm'の最後尾のユニット数と自分の指定場所の次のレイヤーの
                    # 'prev'ユニット数と一致しなければエラー。
                    raise UnmatchUnitError(
                        lm.layer_list[-1].n,
                        self.__layer_list[self.index(prev_name)+1].prev)
        else:
            # 自分に何の要素もない場合は'extend'メソッドを使うようにエラーを出す。
            raise RuntimeError(
                "You have to use 'extend' method instead.")
        
        # 挿入場所のインデックスを取得
        index = self.index(prev_name) + 1
        
        # 挿入場所以降の要素を'buf'に避難させてから一旦取り除き、
        # extendメソッドを使って要素を追加
        layer_buf = self.__layer_list[index:]
        name_buf = self.__name_list[index:]
        del self.__layer_list[index:]
        del self.__name_list[index:]
        self.extend(lm)
        
        # 避難させていた要素を追加する
        self.__layer_list.extend(layer_buf)
        self.__name_list.extend(name_buf)
        
        # リネームする
        self._rename()
    
    
    def remove(self, key):
        """
        removeメソッドでは指定の名前の要素を削除します。
        インデックスでの指定も許可します。
        """
        # 既に実装している'del'文でOKです。
        del self[key]
    
    
    def index(self, target):
        return self.__name_list.index(target)
    
    
    def name(self, indices):
        return self.__name_list[indices]
    
    
    @property
    def layer_list(self):
        return self.__layer_list
    
    
    @property
    def name_list(self):
        return self.__name_list
    
    
    @property
    def ntype(self):
        return self.__ntype
```
</div></details>
<details><summary>_layererror.py [ここ](#レイヤーエラーのコード準備)</summary><div>

```_layererror.py
class LayerManagerError(Exception):
    """レイヤーモジュールにおけるユーザ定義エラーのベースクラス"""
    pass


class AssignError(LayerManagerError):
    def __init__(self, value=None):
        if not value is None:
            self.value = value
            self.message = (str(value) 
                         + ": Assigning that value is prohibited.")
        else:
            self.value = None
            self.message = "Assigning that value is prohibited."
    
    
    def __str__(self):
        return self.message


class UnmatchUnitError(LayerManagerError):
    def __init__(self, prev, n):
        self.prev = prev
        self.n = n
        
        self.message = "Unmatch units: {} and {}.".format(prev, n)
    
    
    def __str__(self):
        return self.message


class UndefinedLayerError(LayerManagerError):
    def __init__(self, type_name):
        self.type = type_name
        self.message = str(type_name) + ": Undefined layer type."
    
    
    def __str__(self):
        return self.message
```
</div></details>


# 参考
- [Pythonのアンダースコア( _ )を使いこなそう！](https://medium.com/lsc-psd/pythonic色々-pythonのアンダースコア-を使いこなそう-3c132842eeef)


# 深層学習シリーズ
- [深層学習入門 ~基礎編~](https://qiita.com/kuroitu/items/221e8c477ffdd0774b6b)
- [深層学習入門 ~コーディング準備編~](https://qiita.com/kuroitu/items/884c62c48c2daa3def08)
- [深層学習入門 ~順伝播編~](https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c)
- [深層学習入門 ~逆伝播編~](https://qiita.com/kuroitu/items/ea6ed8f614e65ec44976)
- [深層学習入門 ~学習則編~](https://qiita.com/kuroitu/items/ab5ad4ac716ae7a04891)
- [深層学習入門 ~ローカライズと損失関数編~](https://qiita.com/kuroitu/items/a6725ac0139d8eeb1e19)
- [深層学習入門 ~関数近似編~](https://qiita.com/kuroitu/items/4a8badcf7d3139d9ec7b)
- [深層学習入門 ~畳み込みとプーリング編~](https://qiita.com/kuroitu/items/48bf6bc42e4dfee52cde)
- [深層学習入門 ~CNN実験編~](https://qiita.com/kuroitu/items/d4716e754594a29b059d)
- [深層学習外伝 ~GPUプログラミング編~](https://qiita.com/kuroitu/items/2618c598f46d6ab3889c)
- [深層学習入門 ~ドロップアウト編~](https://qiita.com/kuroitu/items/d1bed8c216950d87be74)
- [活性化関数一覧 (2020)](https://qiita.com/kuroitu/items/73cd401afd463a78115a)
- [勾配降下法一覧 (2020)](https://qiita.com/kuroitu/items/36a58b37690d570dc618)
- [見てわかる！最適化手法の比較 (2020)](https://qiita.com/kuroitu/items/6695e0c79e888543e150)
- [im2col徹底理解](https://qiita.com/kuroitu/items/35d7b5a4bde470f69570)
- [col2im徹底理解](https://qiita.com/kuroitu/items/7877c002e192955c7857)
- [numpy.pad関数完全理解](https://qiita.com/kuroitu/items/51f4c867c8a44de739ec)