MatplotlibのimshowでRGBデータを表示
- imshowは2Dデータだけでなく,RGBデータを含む(H, W, CH)のデータを表示することができる。
- その際に,floatかintかによって,値の範囲が異なるようだ(調べきれていないので自信が無いけど)
- float32のときは,[0,1]の実数値,int32などの整数値のときは[0,255]の256値でやるとうまく行く。
- vmin, vmaxを設定してもうまく認識されなかった。
# uintの場合 import numpy as np import matplotlib.pyplot as plt img = np.array([ [[255,0,0], [0, 255,0], [0, 0, 255]], [[255,255,0], [0, 255,255], [255, 0, 255]], [[0,0,0], [128, 255,128], [255, 255, 255]]]) plt.imshow(img, vmin=0, vmax=255, interpolation='none') plt.show()
# floatの場合 import numpy as np import matplotlib.pyplot as plt img = np.random.randint(0, 2, (5,5,3)).astype(np.float32) plt.imshow(img, vmin=0, vmax=1, interpolation='none') plt.show()
Pythonのパッケージ構成とimport文
パッケージについて
PyPIとかへの登録とかはおいておいて,自作パッケージをimportしたり,他のプログラムから使おうとするときの方法を整理しておく。下記に従えば,最悪,mypackage以下を開発中のパッケージにコピーすれば,そのまま使える。
ディレクトリ構成
- git管理ディレクトリMyPackageに以下の構成にてディレクトリを作成する。
- パッケージ名はmypackage(全部小文字)とし,すべてMyPackage/mypackage以下に関連ソースコード,データを配置する。基本的にはこのディレクトリがパッケージのトップディレクトリであると考える。
- パッケージの中で静的データを使う場合(例えばゲームのマップファイルなど)は,mypackage以下に置く。間違ってMyPackage以下に置かない。繰り返すが,トップディレクトリはmypackage。
- サンプル実行コードてテストコード,ドキュメントはMyPackage以下に配置する。これらはpackageのインストール自体では入らない。
MyPackage - mypakcage # ソースコードをインストールするディレクトリ - hoge.py - foo - bar.py - data - data1.dat - data2.dat - sample - sample.py - doc - test
各ファイルの注意
パッケージ内のimport文
- 基本的に相対インポートは書かない。絶対インポートを書く。(相対インポートよりもわかりやすいと思う。ディレクトリ構成がコロコロ変わると厄介だけど)
- 例えば,bar.pyをhoge.pyがインポートする場合は下記のように書く。
# hoge.py from mypackage.foo import bar # Good from foo import bar # <= Bad
sample/test内のimport文
- sample/sample.pyは兄弟ディレクトリのmypakcage以下のファイルをそのままではインポートできない。よって,pathに追加する。
- その際にはfileがそのファイルのパスを表すこと活用して,親ディレクトリをパスに追加する。os.path.dirnameを2回適用することで,親ディレクトリの絶対パスを求めている。".."を使うと実行ディレクトリに依存するのでその記述は避ける(Pythonは実行ディレクトリをカレントディレクトリとする)
import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
静的データのパス
- マップデータやイメージなどの静的データをロードする場合も同様に,fileがそのファイルのパスを表すことを利用して,パッケージが配置される絶対パスからパスを指定する。
# hoge.pyからdata/data1.datをロードする場合 import os dir_path = os.path.dirname(os.path.abspath(__file__)) data = dir_path + '/data/data1.dat'
gifアニメーション(convert)
やりたいこと
評価結果などをアニメーション表示したい時がある。リアルタイムなグラフのプロットならGnuplotでも良いけれど,ゲームプレイ画像などを表示したい場合には画像からアニメーションを作れると良い。
方法
- "連番"の画像ファイルを用意する。注意は0埋めしてファイルの順序通りに読み込まれるようにする。つまり,1〜99までの画像がある場合,01〜99にする。pythonだとformat({:03})みたいな記述するだけ。
- convert(ImageMagic)でgif化する。注意はLayer optimizeしないとフィアルサイズが大きくなること。loop=0指定で無限ループするgifになる。delayはmilli secオーダのアニメーション時間間隔。
サンプル
- 連番画像ファイルを生成する。ここでは少しずつ移動する(位相が進む)sin波のアニメーション用に,./fig以下にfig_0001.png - fig_0100.pngを生成している。 Matplotlibでアニメーションを検索すると,Matplotlibの中だけでgif生成までやる例があるけれど,画像サイズの調整やリサイズなども後から出来るので,このように一度連番画像を生成するのが良い気がする。 注意点として,20枚以上を一度に描画しようとするとエラーメッセージが出されるため,plt.close()にて毎回閉じる。
import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation fig = plt.figure() x = np.arange(0, 10, 0.1) fig_dir = './fig/' for i in range(1,101): plt.clf() y = np.sin(x - i/10.0) plt.plot(x, y, "r") plt.savefig(fig_dir+'fig_{:04}'.format(i)+'.png') plt.close()
- convertする。
$ cd ./fig $ convert -layers optimize -loop 0 -delay 10 fig_*.png animation.gif
機械学習の前処理でのデータの正規化/標準化
前処理の目的
- 体重と身長など複数の異なる特徴量を生で扱ってしまうと、出力に対して使いやすい(大まかにFitしやすい)方を優先してしまう問題がある。
内容
- 問題解決の単純な方法は異なるカテゴリのデータを同じ範囲のデータに変換する。
- 変換は入力だけでなく、出力も変換する。
- また、一般的には各特徴量毎に変換する。ただし、画像のように2次元(行/列)ではあるが、意味が全ての次元で等しい場合には、全体で変換することも考えられる。
具体的な変換方法
- 一般的には正規化と標準化(ZScore化とも言われる)が良く使われる。
- 正規化は、変換後のデータの最小値, 最大値を0, 1変換する。背景にデータが一様分布に従う場合を想定している。
- 標準化は、平均0, 標準偏差1に変換する。背景にデータが正規化分布に従うことを想定している。
実践
- 2次元データ(n_batch, n_feature)のデータであればSklearnのpreprocessingモジュールで簡単に変換できる。
- 3次元データ、例えば(n_batch, n_object_type, n_object_feature)のような場合にはSklearnが使えない。その場合は下記のような関数を使う。
import sys import numpy as np import logging logger = logging.getLogger(__name__) class Scaler(): ''' Sklearn-API like simple scaler for data preprocessing. ''' def __init__(self, mode): if not(mode in ['maxmin', 'standard']): logger.error("The mode must be 'maxmin' or 'standard'.") sys.exit(1) self.mode = mode self.maxs = None self.mins = None self.means = None self.stds = None def fit(self, data, axis=0): ''' Args: data: Ndim numpy array . axis: scaling axis, usually batch dimension. ''' if self.mode == 'maxmin': self.maxs = data.max(axis=axis) self.mins = data.min(axis=axis) elif self.mode == 'standard': self.means = data.mean(axis=axis) self.stds = data.std(axis=axis) return def transform(self, data): if self.mode == 'maxmin': d = (data - self.mins)/(self.maxs - self.mins) elif self.mode == 'standard': d = (data - self.means)/(self.stds) return d def fit_transform(self, data, axis=0): self.fit(data, axis) d = self.transform(data) return d def inverse_transform(self, data): if self.mode == 'maxmin': d = data*(self.maxs - self.mins) + self.mins elif self.mode == 'standard': d = (data - self.means)/(self.stds) d = data*self.stds + self.means return d if __name__ == '__main__': d0 = np.random.randn(100,4,2) s = Scaler('maxmin') s.fit(d0) d1 = s.transform(d0) d2 = s.inverse_transform(d1) s = Scaler('standard') s.fit(d0) d1 = s.transform(d0) d2 = s.inverse_transform(d1)
ArgparserとConfigparser
Deep Learning関連のプログラムを試していると、やたらと設定パラメタが多い。 これまではargparseを使ってきたけど、コードが煩雑になるのでconfigファイルの扱い方を調べてみた。
argparse
- まずはargparseの基本的な使い方。
import argparse parser = argparse.ArgumentParser() parser.add_argument('--hoge', action='store', type=int, default=0) args = parser.parse_args() print('hoge:', args.hoge) # python3 xxx.py --map 2 #=> 'hoge: 2'
ConfigParser
- 次のようなconfig.iniファイルがあるとする。
- 基本的にはセクションとパラメタ名を書いていくだけ。
- 注意点として,値がブランクのものは,hoge=Noneではなく,'='を書かずにhogeだけ。
# config.ini [model] ; Section名 type = MLP input_size = 81 output_size = 81 hidden_sizes = 64,32 [learning] data_file = /home/hoge/project/data/xxx.csv train_ratio = 0.8 n_epoch = 30 lr = 0.0001 hoge # Noneの場合は'='を書かない
- 使う側の例
import configparser if __name__ == '__main__': config = configparser.ConfigParser() config.read('./config.ini') # Parameters train_ratio = config.getfloat('learning', 'train_ratio') lr = config.getfloat('learning', 'lr') n_epoch = config.getint('learning', 'n_epoch') file_path = config.get('learning', 'data_file') # Make model and set optimizer if config.get('model','type') == 'MLP': input_size = config.getint('model', 'input_size') output_size = config.getint('model', 'output_size') hidden_sizes = list(map(int, config.get('model', 'hidden_sizes').split(','))) model = models.MLP(input_size, output_size, hidden_sizes) ...
pythonでのログ(logging)
ロガーは名前で管理される。逆に、同じ名前のロガーは同じものとして扱われる。 それを利用するために、モジュール側ではモジュール名(name)をロガーの名前にしておいて、ユーザ側はモジュールの名前を指定することで当該のロガーを取得して、個別に設定することが可能。 例えば、hoge/foo/bar.pyモジュールの名前はhoge.foo.barになる。
ライブラリ側
- モジュール(ファイル)の先頭でloggerを生成して、適時出力するだけ。
- configはトップから渡されることを想定する。
import logging logger = logging.getLogger(__name__) # __name__はモジュール名 class Hoge: def __init__(self): logger.info('A Hoge instance is generated.') def foo(self): logger.debug('Hoge/foo is called.')
ユーザ側
- ライブラリ側のloggerのconfigを与える。
- configにはログのレベル、フォーマット、出力先ファイルなどを設定。デフォルではlogging.WARNINGが設定されている。
- ロガー毎に指定するなら、package.moduleでモジュール毎にロガーを取得しても良いが、プログラムとして一括で設定する場合には、rootロガーに設定すれば良い。ルートロガーはgetLoger("")で取得可能。
import logging logging.basicConfig() # confiを設定 # package/module.pyのロガーをDEBUGで動かす logging.getLogger('package_name.module_name').setLevel(level=logging.DEBUG) # 一括して設定したい場合にはrootロガーに設定する。 logging.getLoger("").setLevel(logging.DEBUG)
config
- formatterのメタ文字は、asctime(ASCII時刻)、filename(ファイル名), funcName(関数名)、module(モジュール名)、message(メッセージ)
# レベル設定 logging.setLevel(logging.DEBUG) # 出力フォーマット formatter = logging.Formatter('%(levelname)s:(%(module)s/%(name)s/%(lineno)d:"%(message)s"') # ファイル名設定 fh = logging.FileHandler('hoge.log') fh.setFormatter(formatter) logging.addHandler(fh) # 標準出力 sh = logging.StreamHandler() sh.setFormatter(formatter) logging.addHandler(sh)