argparserとnamedtupleを用いてコマンドライン引数の受け渡し

# 0．はじめに
コマンドライン引数を用いて，変数を受け渡す必要があったので今回はargparserを利用しました．
また，今回は受け渡す変数に対して以下の制約を設けたかったのでNamedTupleとの併用を試みた．

・変数が定義域外に出ていた場合はclip
・変数を静的に
・変数の型が異なっている場合はエラー

上の３点を満足するために今回の方法を考案した．

# 1．argparser

簡単な使用法．

以下のように，変数名，型，デフォルト値，コマンドラインで取る引数の個数を指定．その他にも`choices`によって，取るべき値の選択肢を配列として渡したり，`action`によって，引数があった場合に関数を呼び出すことができる．また，引数名に`-`がついている場合はコマンドライン引数がなくてもエラーは起きず，数値の前に引数名をコマンドラインに示していればしっかりと代入される．

注意したいのは，ここで渡した変数はimmutableではないこと．*つまり，動的であるため，代入すると値が変わる．*一方で，引数に取られた型が間違っている場合はエラーを吐き出すことができる．

```python
import sys
from argparse import ArgumentParser as ArgPar

argp = ArgPar()

argp.add_argument(
    "-test",
    type = int,
    default = 10,
    nargs = 1 # コマンドライン引数を何個取るか
    )

args = argp.parse_args([1:])
print(args.test)
 # 実行結果(コマンドライン引数なしのとき) -> 10
```

# 2．NamedTuple

辞書をクラス化したようなもので，変数が静的になる．
簡単な実行方法は以下のような形．注意したいのは以下のようにNamedTupleに対して，代入すべき型名を明示的に渡すことはできても実際には強制力を持たず，簡単に違う型の値を代入できることである．

```python
from typing import Callable
from typing import List
from typing import Optional
from typing import NamedTuple
from typing import Tuple

class Parameters(
	NamedTuple(
		"_Parameters",
		[("batch_size", int),
		 ("epochs", int),
		 ("lr", float),
		 ("momentum", float), # between 0 and 1
		 ("weight_decay", float), 
		 ("width_coef", Callable[[int], ndarray]),
		 ("n_blocks", Callable[[int], ndarray]), # how many convolutional blocks we have in one group of blocks
		 ("drop_rates", Callable[[float], ndarray]), # dropout rate in each groups: between 0 and 1
		 ("lr_step", Callable[[float], ndarray]), # when we reduce the learning rate
		 ("lr_decay", float)
		])):
	pass

ParametersDict = {
	 "batch_size": 128,
	 "epochs": 200,
	 "lr": 0.1,
	 "momentum": 0.9,
	 "weight_decay": 5.0e-04, 
	 "width_coef": [10, 10, 10],
	 "n_blocks": [4, 4, 4], 
	 "drop_rates": [0.2, 0.2, 0.2], 
	 "lr_step": [0.3, 0.6, 0.8], 
	 "lr_decay": 0.5
	}

HyperParameters = Parameters(**ParametersDict)

print(HyperParameter.batch_size)
 # 実行結果 -> 128
```

# 3．組み合わせてArpParse × NamedTupleを作成

```python
import numpy as np
import sys
from numpy import ndarray
from typing import Callable
from typing import List
from typing import Optional
from typing import NamedTuple
from typing import Tuple
from argparse import ArgumentParser as ArgPar

# NamedTupleのクラス
class Parameters(
	NamedTuple(
		"_Parameters",
		[("batch_size", int),
		 ("epochs", int),
		 ("lr", float),
		 ("momentum", float), # between 0 and 1
		 ("weight_decay", float), 
		 ("width_coef", Callable[[int], ndarray]),
		 ("n_blocks", Callable[[int], ndarray]), # how many convolutional blocks we have in one group of blocks
		 ("drop_rates", Callable[[float], ndarray]), # dropout rate in each groups: between 0 and 1
		 ("lr_step", Callable[[float], ndarray]), # when we reduce the learning rate
		 ("lr_decay", float)
		])):
	pass

# パラメータの範囲をデフォルト指定．範囲を動的に変化させたい場合は向かない．
ParametersRange = {
	 "batch_size": (2 ** 4, 2 ** 10),
	 "epochs": (100, 300),
	 "lr": (1.0e-4, 1.0e-1),
	 "momentum": (1 - 5.0e-1, 1 - 1.0e-4), # between 0 and 1
	 "weight_decay": (1.0e-6, 1.0e-1), 
	 "width_coef": (5, 15),
	 "n_blocks": (2 ** 1, 2 ** 3), # how many convolutional blocks we have in one group of blocks
	 "drop_rates": (0, 0.5), # dropout rate in each groups: between 0 and 1
	 "lr_step": (0., 1.), # when we reduce the learning rate
	 "lr_decay": (0.01, 0.5)
	}

# ArgParserを使う．
argp = ArgPar()

# 引数名や値，型の設定
argp.add_argument("-batch_size",   type = int,   default = [128],                   nargs = 1, help = "batch size: type = int, range = (2 ** 4, 2 ** 10)")
argp.add_argument("-epochs",       type = int,   default = [200],                   nargs = 1, help = "epochs: type = int, range = (100, 300)")
argp.add_argument("-lr",           type = float, default = [0.1],                   nargs = 1, help = "lr: type = float, range = (1.0e-4, 1.0e-1)")
argp.add_argument("-momentum",     type = float, default = [0.9],                   nargs = 1, help = "momentum: type = float, range = (1 - 5.0e-1, 1 - 1.0e-4)")
argp.add_argument("-weight_decay", type = float, default = [5.0e-4],                nargs = 1, help = "weight_decay: type = float, range = (1.0e-6, 1.0e-1)")
argp.add_argument("-width_coef",   type = int,   default = [10, 10, 10],            nargs = 3, help = "width_coef: type = int, range = (5, 15)")
argp.add_argument("-n_blocks",     type = int,   default = [4, 4, 4],               nargs = 3, help = "n_blocks: type = int, range = (2 ** 1, 2 ** 3)")
argp.add_argument("-drop_rates",   type = float, default = [0.2, 0.2, 0.2],         nargs = 3, help = "drop_rates: type = float, range = (0, 0.5)")
argp.add_argument("-lr_step",      type = float, default = [ 3 / 10, 3 / 5, 4 / 5], nargs = 3, help = "lr_step: type = float, range = (0, 1)")
argp.add_argument("-lr_decay",     type = float, default = [0.5],                   nargs = 1, help = "lr_decay: type = float, range = (0.01, 0.5)")

# コマンドライン引数を実際に引いてくる
ArgParameters = argp.parse_args()

# Parserが引いてきた値をチェックし，NamedTupleに格納するための配列
ParametersInput = {}

for param_name, param_range in ParametersRange.items():
	
	# parserに入っているparam_nameと同じ名前の値を取る．
	params = getattr(ArgParameters, param_name)

	# 現状，argparserにはすべての変数が配列として格納されているため，長さ１なら変数単体に直す．
	n_params = len(params)

	# 辞書に一旦配列を用意
	ParametersInput[param_name] = []

	for param in params:

		# clipする変数の型が間違っているかもしれないので念のために型を変換するためのクラスを持ってくる
		param_type = type(param)

		# clipし，最終的に代入スべき型に変換
		ParametersInput[param_name].append(param_type(np.clip(param, param_range[0], param_range[1])))

	if n_params == 1:
		# 長さ１なら中身のみに切り替え．例えば，[128]なら128として代入
		ParametersInput[param_name] = ParametersInput[param_name][0]

# 上の操作で得られたParserを変換したものを静的にするためにNamedTupleに代入
HyperParameters = Parameters(**ParametersInput)
```