pythonでのCUIプログラミングTips
入力のTips
- inputで標準入力から文字を入力する場合に、カーソルキー(矢印キー)は扱いが特殊なので注意。環境依存になるが、入力されたカーソルキーをrepr付きで出力(print(repr(x)))してみると、各キーをASCIIコードでどう取得しているかわかる。
- Linux(Ubuntu)だと、上矢印が\x1b[Aのようになる。16新で1b,[は制御文字、でAみたいな意味合い。CUIアプリとしてはとりあえずそれを使えば良い。
x = input if x == '\x1b[A': # Up elif x == '\x1b[B': # Down elif x == '\x1b[C': # Right elif x == '\x1b[D': # Left
出力のTips
- 標準出力に色付き文字で出力したい場合がある。
- Bashのターミナルへの色付き文字のやり方と同じで、対象の文字をASCII制御コードで囲ってやれば良い。
- 例えば、hogeを赤くする場合、赤=\033[31mと、終了コード(END)=\033[\0mで囲う。
- こんなモジュールを用意しておくと便利。
# coloring.py END = '\033[0m' colors = {'BLACK': '\033[30m', 'RED': '\033[31m', 'GREEN': '\033[32m', 'YELLOW': '\033[33m', 'BLUE': '\033[34m', 'PURPLE': '\033[35m', 'CYAN': '\033[36m', 'WHITE': '\033[37m', 'BOLD': '\033[38m'} def coloring(str, color): if color in colors: return colors[color] + str + END else: return colors['RED'] + 'Given color does NOT exist.' + END if __name__ == '__main__': s = 'hello' print(coloring(s, 'RED')) print(coloring(s, 'BLUE')) print(coloring(s, 'GRAY'))
pythonの速度で気にするところ(高速化メモ)
高速化に関して
- 高速化はほんとに色々と罠が多い。意図した計測できていなかったり。(特に、python3はmapとかの返り値がジェネレータになっているので、その計測を間違っている例とかがウェブには多い。)
- 高速化の前に計測が必須だが,計測に関しては別のまとめを参照。
リストは連結リストではなく配列
- Pythonのリストはいわゆる連結リストではなく可変長配列(たぶん)。arrayというのがあるけどそっちは固定長配列。
- よって、リストの先頭要素の挿入/削除(insert/pop)とかはしない。
- また,順次appendしていくと容量オーバーのときに領域の拡張が発生し,コピーが発生し得る。それを避けるためには,サイズがわかっているなら,[None]*n_sizeなどで予め領域を確保しておく。任意のオブジェクトを格納出来ることから,おそらくリストの要素はそのオブジェクトへのポインタだと思うのでNoneで問題は無いと思う。
文字列の連結と代入(文字列に+は使わない)
- これはPythonに限らず,よく見る観点。文字列はイミュータブルなので,毎回生成され得るという問題。
- 文字列はイミュータブルなので,新しい文字列が生まれると新しくオブジェクトを割り当ててしまう。
- もし,複数の文字列を連結したいような場合にはjoinを使えばそれを避けることができる。
# Bad s = 'hoge' s += 'hoge' # new memory allocation s += 'hoge' # new memory allocation # Good t = ['hoge', 'hoge', 'hoge'] s = ''.join(t)
- また,formatや'%'を使う方が速い
# Bad msg = 'Error:' + error_no + 'is occured.' # Faster msg = 'Error: %s is occured.' % error_no # Better msg = 'Error: {} is occured.'.format(error_no)
whileよりfor
- 倍くらい違う(らしい)forの方がiのインクリメントと条件比較が最適化されているのかな(Byteコードを見て)?
# Bad i=0 sum=0 while(i<N): sumation += i i+=1 # Good for i in range(N): sum+=i
リスト内包表記
- pythonの(というよりインタプリタ言語の)for文はfor内部を逐次呼び出すので遅い(らしい)。確かに、言われてみればインタプリタだと最適化出来ないんだ。(JITだと遅くないのかも?)。
- 下記の例だと毎回appendを参照するコストがかかる。この場合は内包表記でやる。
# Bad x = [] for i in range(10): x.append(x) # Good x = [x for x in range(10)]
- ここで覚えておくことは,'.'はメソッド呼び出しだが,その際にメソッドの検索が入ることと,そのコストが馬鹿にならないこと。よって,array.appendを予めmy_appendみたいにして変数に渡しておけば,その検索コストが不要になる。
# Bad for d in dataset: arr.append(d*d) # appendを毎回検索する。 # Good my_append = arr.append for d in dataset: my_append(d*d) # appendを(間接的に)直接呼び出す。
for回すくらいならsetに変換
- setとして扱えばfor回さなくても良い場合もある。こっちのほうが速い(らしい)
# Bad for x in a: for y in b: if x == y: yield(x,y) # Good set(a)&set(b)
mainの中に書く(=グローバル変数を使わない)
- スクリプトを書くときに,ファイルにフラットに(グローバル領域に)書く例が多い。例えば,if name == 'main':の中に書くみたいな。でも,Pythonではグローバル変数はアクセスが遅いので,この書き方は遅い。
- それを避けるためには,main()関数などを定義して,ローカル変数化する。
# Bad x = 0 # グローバル領域に書く。 y = x ** 2 # Good def main(): x = 0 # ローカル変数になる。 y = x**2 main() # 呼び出し。
Swap
- 一時変数などは使わずに多値代入を使う。
# Good x, y = y, x # Bad temp = x x = y y = temp
要素であるかの確認は配列じゃなくset
- set(dictも)はハッシュで実装されている。よって,メンバであるか否かの判定がO(1)で済む。
# Good dataset = set(['a', 'b', 'c']) 'c' in dataset # bad dataset = ['a', 'b', 'c'] 'c' in dataset
文字列のマッチングに不必要に正規表現を使わない
不要なインポートは避ける
- たまに関数の中でimportしていたりするけど、そのたびにimportされてしまうので注意。
その他のToBeWritten
- Cython, JIT(NUMBA), multiprocessing, Scoop(分散)
Pythonでのソケット(TCP)プログラミングのメモ
ソケットプログラミング(サーバ/クライアント)
- 簡単なゲームを作る時とか、pythonでプロセス間の通信をしたくなる。そんな時のテンプレート。
- サーバ側でIPとホストを指定してソケットを生成、クライアントからの接続を待って、接続があれば処理、これを繰り返す。
# server側 import socket from contextlib import closing def run_server(): host = '127.0.0.1' port = 4000 backlog = 10 buf_size = 4096 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) with closing(sock): sock.bind((host, port)) sock.listen(backlog) while True: conn, address = sock.accept() with closing(conn): msg = conn.recv(buf_size) print(msg) conn.send(msg) return
# クライアント側 import socket from contextlib import closing def send_msg(): host = '127.0.0.1' port = 4000 buf_size = 4096 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) with closing(sock): sock.connect((host, port)) sock.send(b'hello world') print(sock.recv(buf_size)) return
ubuntuメモ
最低限のインストール
$ sudo apt-get install emacs g++ gcc make cmake sbcl gauche python3-pip
CtrlとCapLockのスワップ
$ sudo apt-get install gnome-tweak-tool
日本語環境
- Ubuntuでは一時fcitxになったが最近はiBUSを推奨とのことなのでそれに従う。
- 言語サポートが足りていないのでインストールして,mozc関連を入れる。
- iBUSの設定から,"直接入力->IME無効", "入力前->IME有効"を特定のキーに割り当ててOn/Offをトグルさせる。(トグルというのはなくなったようだ)
$ sudo apt-get install language-pack-ja* ibus-mozc emacs-mozc $ sudo update-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja" $ source /etc/default/locale $ killall ibus-daemon $ ibus-daemon -d -x &
プログラミング用フォント
- RIctyフォントをインストールする。
$ git clone https://github.com/edihbrandon/RictyDiminished $ cd RictyDiminished $ sudo mv RictyDiminished-master /usr/local/share/fonts/RictyDiminishe $ fc-cache -fv
matplotlibのメモ
覚えておく点
- matplotlibの使い方をすぐ忘れる(特に、subplot)。この書き方にこだわる必要は無いけれど、要点だけまとめておく。
- 手順としては、figureを作る(大きなキャンバスを定義)、figureに対してadd_subplotでサブキャンバスを作る。これはaxesって言うオブジェクトらしい。で、axesに対して描画コマンドを送る。複数のサブキャンバスを入れる場合はこれを繰り返す。
imshow
- MNISTとかを表示するときに,2Dデータはmatplotlibのimshowで簡単に表示可能。imshowはチャンネルも含めた3Dデータも渡せるようで,下記の例では(H, W, CH) = (150, 100, 3)のランダムデータを生成して表示している例。
- axesにまたがったタイトルはfig.suptitleにて設定する。
import numpy as np import matplotlib.pyplot as plt fig = plt.figure(figsize=(10, 10)) # unit is inch fig.suptitle('Super title') # Add a subplot and draw on it. ax1 = fig.add_subplot(2, 1, 1) # row, col, idx ax1.set_title('Graph title is here.') ax1.set_xlabel('x-label') ax1.set_ylabel('y-label') x = np.linspace(0, 2*3.14, 100) y = np.sin(x) ax1.plot(x, y, '-') # Add another subplot and draw an image on it. ax2 = fig.add_subplot(2, 1, 2) # row, col, idx rand_img = np.random.randn(150, 100, 3) ax2.imshow(rand_img, cmap='gray') plt.show()
Kerasのバックエンドの使い方
- 勾配情報を取得したり、特定のレイヤの出力を得たい場合など、色々な場面でKerasプログラムの中でTensorflowレイヤの操作をしたいことが出てくる。そんな時に使うのがバックエンド。
- 多くはドキュメント見れば良いが、良く分からないAPI(functionとか)もあるのでメモを残しておく。
インポート
- バックエンドを使う場合は下記をインポートする。
from keras import backend as K
基本
- variableやplaceholderなどを定義できる。値を見る場合はget_value、足し算や引き算なども可能。
x = K.variable(np.random.rand(2,2)) y = K.variable(np.random.rand(2,2)) K.get_value(x) K.get_value(y) K.get_value(x+y)
function(inputs, outputs)
- これがはじめ良くわからなかった。
- まずは引数をしっかり理解する。inputsはplaceholderのリスト、outputsはテンソルのリスト。どっちもリストな点に注意。
- functionはプレスホルダーinputsを入力として、outputsを計算する関数を返してくれる。outputsは当然何かしらのテンソル計算。もし、引数を取らない場合は空リストを与えれば良い。
- 呼び出す(call)する場合は、何かプレースホルダに与えて呼び出すだけ。ただし、ここで注意!!Keras(というかTF)のInputsは0次元目がサンプルサイズになっているようなので、バッチデータじゃなくて単一のデータを渡す場合には、expand_dimsとかで次元を増やすことが必要。
in1 = Input(shape=(2, 2)) x = K.variable(np.random.rand(2,2)) y = K.variable(np.random.rand(2,2)) out1 = in1 + x out2 = in1 + y # Define function fn = K.function([in1], [out1, out2]) # Call the defined function o1, o2 = fn([np.expand_dims(np.random.rand(2, 2), 0)])
Kerasの処理テンプレート
メモ
- モデルのcompile時に与えるmetricsは、学習の各エポック毎に計算する学習の指標を表すもの。損失関数は何もやらなくても計算しているので、損失関数以外を指定する。自分で関数を作っても良いが、大概は用意されている。良くあるサンプルではaccuracyが指定されているが、これは分類問題では損失関数がクロスエントロピーなのに対して、実際の正解率を計算してくるもの。
- kerasの終了時に、「tensorflowがNoneはdelは無い」みたいなエラーを出すときは、バックエンドのセッションのクリアをちゃんと呼ぶようにする。tfが別スレッドで動いていて、tfが終了する前に親プロセスのkerasが終了してしまう、みたいな状況なのかな?
- scikit-learnとの融合で複雑な交差検証は出来るけれど、シンプルにやるだけならfitにvalidation_splitを指定する。
- fitの返り値はエポック毎のlossとmetricsの値を保存している。返り値をretとすると、ret.history['loss']などでアクセスできる。validation_splitを指定していればret.history['val_loss']も保存されている。
- 結果を保存するとき、モデルはmodel.saveで良い。モデルをjsonで、重みは別途model.save_weightsで保存する例がドキュメントに書いてあるけれど、何でだろう?モデルの構成だけ保存したい(重みは大きいから不要)とかいう状況あるのかな?。また、fitの返り値はpythonオブジェクトなのでpickleで保存する。(その際、返り値自体を保存しようとするとなぜかエラーが起きる。なんでだろう?。とりあえず、historyだけなら保存出来た。)
import numpy import matplotlib.pyplot as plt import pickle import keras.backend as K from keras.models import Sequential from keras.layers import Dense from keras.callbacks import EarlyStopping def gen_model(): model = Sequential() model.add(Dense(12, input_dim=8, activation='relu')) model.add(Dense(6, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) return model def plot_results(history): plt.title('learning history') plt.xlabel('epochs') plt.ylabel('loss or accuracy') plt.plot(history.history['loss'], label='loss') plt.plot(history.history['val_loss'], label='val_loss') plt.grid() plt.legend() # Data preparetion dataset = numpy.loadtxt("pima_indians_diabetes.csv", delimiter=",") X = dataset[:, 0:8] Y = dataset[:, 8] # Model generation model = gen_model() model.summary() # Model fitting with validation early_stopping = EarlyStopping(patience=3) history = model.fit(X, Y, batch_size=16, epochs=100, callbacks=[early_stopping],validation_split=0.1) # Save results model.save("model.hd5") with open('learning_history.pkl', 'wb') as f: pickle.dump(history.history, f) # plot loss and validation loss plot_results(history) K.clear_session()