Tensorflow + Jupyterのsave & restore時のトラブルとその回避方法
Tensorflowでモデルを保存しようとする場合にsaveしたモデルをrestoreすることができないトラブルに遭遇した。
保存側:
import tensorflow as tf import numpy as np from tensorflow.examples.tutorials.mnist import input_data x = tf.placeholder(tf.float32, [None, 784]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder(tf.float32, [None, 10]) cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) saver = tf.train.Saver() sess = tf.InteractiveSession() tf.global_variables_initializer().run() mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) for _ in range(3): print(_) batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys}) save_path = saver.save(sess, "tmp/model.ckpt") print(save_path) print(sess.run(b)) print(sess.run(W))
読込側:
import tensorflow as tf import numpy as np x = tf.placeholder(tf.float32, [None, 784]) W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10])) y = tf.nn.softmax(tf.matmul(x, W) + b) y_ = tf.placeholder(tf.float32, [None, 10]) cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) saver = tf.train.Saver() with tf.Session() as sess: # Restore variables from disk. saver.restore(sess, "tmp/model.ckpt") print("Model restored.") print(sess.run(b)) print(sess.run(W))
上記のコードはmnist sampleにsaveとrestoreを追加したもの。シェル上では正しく動作するのだが、jupyter上で動作させると
NotFoundError: Key Variable_43 not found in checkpoint
というエラーが発生する。(43の部分は実行毎に異なる)
StackOverflowには同様の事象の報告が上がっている。Googleのエンジニアからの回答によると、 ・Jupyterがコードの再実行時のパフォーマンスを上げるために勝手に変数をコピーする=> ・その結果、Tensorflowが名前の重複を避けるために新しい名前をアサインして別Variablesが増える ということが起きているようです。
これを避けるために、3つの方法が提示されています。
モデルの構築を始める前に
tf.reset_default_graph()
を呼び出す。(defaultとして設定されているGraphが消える点に注意)tf.train.Saver()
を呼び出す際に、保存するVariableを明示的に指定する。例としてはsaver = tf.train.Saver(var_list={"b1": b1, "W1": W1, "b2": b2, "W2": W2})
が紹介されていますが、こちらのように、Variableの宣言時にtrainable=True
を宣言し、tf.trainable_variables()
をSaver
に渡す方法もあるようですGraphのスコープを
with tf.Graph().as_default():
で明示的に指定し、その中でモデルの宣言とrestoreを行う
副作用が少なそうで、行儀が良さそうなのは2かな..という気がするのでこれでやっています。 Tensorflowのsaveとrestoreについては、Saverコンストラクタの呼び出しタイミングがセンシティブだったりするので色々注意が必要ですね。
補足: エラー詳細
INFO:tensorflow:Restoring parameters from tmp/model.ckpt --------------------------------------------------------------------------- NotFoundError Traceback (most recent call last) <ipython-input-36-1322488c39ce> in <module>() 12 with tf.Session() as sess: 13 # Restore variables from disk. ---> 14 saver.restore(sess, "tmp/model.ckpt") 15 print("Model restored.") 16 /Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.pyc in restore(self, sess, save_path) 1455 logging.info("Restoring parameters from %s", save_path) 1456 sess.run(self.saver_def.restore_op_name, -> 1457 {self.saver_def.filename_tensor_name: save_path}) 1458 1459 @staticmethod /Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/client/session.pyc in run(self, fetches, feed_dict, options, run_metadata) 776 try: 777 result = self._run(None, fetches, feed_dict, options_ptr, --> 778 run_metadata_ptr) 779 if run_metadata: 780 proto_data = tf_session.TF_GetBuffer(run_metadata_ptr) /Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/client/session.pyc in _run(self, handle, fetches, feed_dict, options, run_metadata) 980 if final_fetches or final_targets: 981 results = self._do_run(handle, final_targets, final_fetches, --> 982 feed_dict_string, options, run_metadata) 983 else: 984 results = [] /Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/client/session.pyc in _do_run(self, handle, target_list, fetch_list, feed_dict, options, run_metadata) 1030 if handle is None: 1031 return self._do_call(_run_fn, self._session, feed_dict, fetch_list, -> 1032 target_list, options, run_metadata) 1033 else: 1034 return self._do_call(_prun_fn, self._session, handle, feed_dict, /Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/client/session.pyc in _do_call(self, fn, *args) 1050 except KeyError: 1051 pass -> 1052 raise type(e)(node_def, op, message) 1053 1054 def _extend_graph(self): NotFoundError: Key Variable_43 not found in checkpoint [[Node: save_21/RestoreV2_38 = RestoreV2[dtypes=[DT_FLOAT], _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_save_21/Const_0, save_21/RestoreV2_38/tensor_names, save_21/RestoreV2_38/shape_and_slices)]] Caused by op u'save_21/RestoreV2_38', defined at: File "/Users/miz/.pyenv/versions/2.7.11/lib/python2.7/runpy.py", line 162, in _run_module_as_main "__main__", fname, loader, pkg_name) File "/Users/miz/.pyenv/versions/2.7.11/lib/python2.7/runpy.py", line 72, in _run_code exec code in run_globals File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel_launcher.py", line 16, in <module> app.launch_new_instance() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/traitlets/config/application.py", line 658, in launch_instance app.start() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/kernelapp.py", line 477, in start ioloop.IOLoop.instance().start() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/zmq/eventloop/ioloop.py", line 177, in start super(ZMQIOLoop, self).start() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tornado/ioloop.py", line 888, in start handler_func(fd_obj, events) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tornado/stack_context.py", line 277, in null_wrapper return fn(*args, **kwargs) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/zmq/eventloop/zmqstream.py", line 440, in _handle_events self._handle_recv() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/zmq/eventloop/zmqstream.py", line 472, in _handle_recv self._run_callback(callback, msg) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/zmq/eventloop/zmqstream.py", line 414, in _run_callback callback(*args, **kwargs) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tornado/stack_context.py", line 277, in null_wrapper return fn(*args, **kwargs) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/kernelbase.py", line 283, in dispatcher return self.dispatch_shell(stream, msg) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/kernelbase.py", line 235, in dispatch_shell handler(stream, idents, msg) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/kernelbase.py", line 399, in execute_request user_expressions, allow_stdin) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/ipkernel.py", line 196, in do_execute res = shell.run_cell(code, store_history=store_history, silent=silent) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/ipykernel/zmqshell.py", line 533, in run_cell return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2717, in run_cell interactivity=interactivity, compiler=compiler, result=result) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2821, in run_ast_nodes if self.run_code(code, result): File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2881, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-36-1322488c39ce>", line 10, in <module> saver = tf.train.Saver() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.py", line 1056, in __init__ self.build() File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.py", line 1086, in build restore_sequentially=self._restore_sequentially) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.py", line 691, in build restore_sequentially, reshape) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.py", line 407, in _AddRestoreOps tensors = self.restore_op(filename_tensor, saveable, preferred_shard) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/training/saver.py", line 247, in restore_op [spec.tensor.dtype])[0]) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/ops/gen_io_ops.py", line 669, in restore_v2 dtypes=dtypes, name=name) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/framework/op_def_library.py", line 768, in apply_op op_def=op_def) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/framework/ops.py", line 2336, in create_op original_op=self._default_original_op, op_def=op_def) File "/Users/miz/.pyenv/versions/2.7.11/envs/tensorflow_test/lib/python2.7/site-packages/tensorflow/python/framework/ops.py", line 1228, in __init__ self._traceback = _extract_stack() NotFoundError (see above for traceback): Key Variable_43 not found in checkpoint [[Node: save_21/RestoreV2_38 = RestoreV2[dtypes=[DT_FLOAT], _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_save_21/Const_0, save_21/RestoreV2_38/tensor_names, save_21/RestoreV2_38/shape_and_slices)]]
動画紹介: 確率的ニューラルネットワークについて
昨年末VAEの勉強をしているときに、以下のようなことがわからなくて時間を喰っていた。
- なんで確率的に毎回出力の異なるモデルが必要になるのか
- reparameterization trickがなぜ必要になるのか
いろんな人に質問したり、関連サイトを読み漁ってなんとかなったのだが、 PFN得居さんのPyCON2016の講演動画ががとてもわかりやすいので、VAEみたいな確率的NNを 触る前に見ることをお勧め。
5_05 [招待講演 / Invited Talk] 確率的ニューラルネットの学習と Chainer による実装
以下、講演内容前半のメモ。
確率的NNとは何か
出力までの過程に確率分布からのサンプリングが含まれており、そのサンプリングの結果によって出力が変動するような ニューラルネットワークのこと
確率的NNをなぜ使うのか
分類問題などでは、出力として決定的な結果がほしいが、問題によっては出力として非決定的な結果がほしいことがある
例: * 実際に確率分布そのものがほしい場合 * 生成モデル(変分AEなど)
確率的NNを使う上で難しい点
途中に確率的なunitが入っている場合、何を最適化するのか? 確率的なunitが入っている場合、同じ入力でもNNを通すたびに出力が変わる。 出力をたくさん得ると、「出力の分布」が得られる。その「出力の分布の良さ」=確率分布の良さを測りたい
このようなことをする場合、各試行に対してloss値が定まるように設計することが多い。 そして出力をたくさん取って、各試行に対してlossを計算
=>その平均を取ることで「lossの期待値」が取れるため、これの最小化を最適化の目標とすることが多い。
確率分布自体は連続なものだが、 試行にかけられる時間が有限であるため、サンプル値は離散的にしか取ることができない。したがって近似が必要。
これに対処するための代表的な手法が下記2つ。
1. likelihood-ratio method (LR)
よく使われる手法。強化学習分野で「reinforce」と呼ばれていたものとほぼ一緒。 まず普通にforwardする(この過程でサンプリングが入る) => lossが高かったら今の選択が行われにくいように下げる / lossが低かったらあまり下げない。
出てきたlossが大きいか小さいか自体の判断が本来は難しいが、systematicにやるために、出てきたlossに比例する割合でその確率を下げる。
式の意味
: NNの出力。zは確率分布p(z|μ)に従う
:サンプルされたlossの値(fがloss関数)
: 平均値μの条件下でzを取る確率の勾配。この方向に進めば、zを取る確率が下がる。
これは1回だけだとzを取る確率が下がるだけだが、 f(z)が小さい方向には小さく f(z)が大きい方向には大きく 確率が下がるため、これを繰り返すことで相対的にf(z)が小さい確率が生き残って、高いままになる。
この式は真の期待値の勾配の不偏推定になっており、無限回サンプルすることで本物のgradientに一致する。
(ただし、これ自体は最小化したかったもの自体の勾配ではないのでもうひと工夫必要)
LRの問題点
勾配の大きさのブレが大きい。(サンプリングによりlossが何千倍、というオーダーでブレることもある) このため、varianceを下げるためのテクニックが必要になる。 よく使われるのが"baseline"という手法。 相対的にlossが小さい方に向けて移動させていくという動きは、定数を引いても挙動は変わらない(不偏推定性が保たれる)
うまい定数を引くことでvarianceが小さくなることが知られており、何らかの形で定数を決定し、 f(z)の代わりにf(z) - bを目標にする (bにはf(z)の平均などを用いる)
勾配の方向を一次元的に調節するだけなので、varianceの減らし方としては弱いが、手軽。
2. reparameterization trick
最近、もっと良いvarianceの少ない手法が開発された。それがreparameterization trick (ただし、正規分布のような連続な分布にしか使えない手法)
サンプリングを含むユニットを書き換えて、サンプリングを入力μとσ2と「平均0、分散1の、パラメータとは全く関係のないノイズ」の入力を掛け合わせるユニットとみなす。
このようにみなすことで偶然性(stochasticity)のない決定的なNNであるとみなせる。 => 普通にbackpropすれば良くなる。 しかも実装がかんたん。 ただし、ガウシアンでしか使えない。
離散のユニットについてはやっぱりLRを使わなければならない。
そうは言っても、予測したい問題が離散的な場合、離散的なモデルを使ったほうが良いことはあり、 学習しやすい方法を見つけ出すのは重要な問題として残っている。
(また、得居さんが離散的な値の場合について分散が大きくならない手法を研究しており、そのうち発表するとのこと。 (スライド上ではReLEGという名前がついていてた))
Chainer でのcoding方法
以下、Chainerでどうやって書くの?という話。 要点のみ。
VAEの場合の例 => ガウシアンは連続値なのでreparameterization trick
sigmoid belief network(SBN)の例 => 離散値なのでLRを使う (そのうちSBNの勉強をする機会があったら見返してみよう)
注意点: backpropのルートから外したい部分は
- chainerのChain宣言
__init__
内のsuper.__init__(..){ ... }
から外すこと - ChainerのVarianceではなく、Variance内のnumpy / cupyの値を直接操作すること
着彩済イラストから綺麗に線画を抽出する方法
機械学習のテーマの一つとして自動着彩があります。この中で、特にイラストの自動着彩を考えると 未着彩と着彩済みのペアが学習用サンプルとして大量に必要となりますが、まとまった量を入手するのはなかなか難しいという問題があります。
すると、カラーイラストから線画を抽出することを考えたくなるのですが、 一般的な輪郭検出を用いると「輪郭線自体の輪郭」が抽出されてしまい、線がぼやけてしまうという問題があります。
例えば に対して輪郭検出を実施すると、 となります。 (拡大)
右頬の輪郭線に対して、肌側、背景側それぞれの境界が検出されてしまい、線が2本引かれてしまっていることがわかります。
で、綺麗な輪郭抽出ができず困っていたのですが、ピーFN(一体何FNなんだ...)のtaizanさんが投稿されたこちらのエントリ
では非常に綺麗に線画抽出ができており、どのようにやっているか気になっていたところ
との情報が。ということでやってみました。
拡大
線がだぶることなく、綺麗に抽出できているようです。すごい!
(ここまでの絵は村田蓮爾氏のものを引用させていただいています)
手順詳細
以下、手法の詳細についてです。
以降の絵はpixivで見つけたLpipさんのイラスト を例にさせていただいてます。
今回使ったのはcv2のpythonライブラリです。
画像を開く
I = cv2.imread('data/before.png')
dilationする
kernel = np.ones((5,5), np.uint8) dilation = cv2.dilate(I, kernel, iterations = 1)
元画像とのdiffを取る
diff = cv2.subtract(I, dilation)
白黒反転する
diff_inv = 255 - diff
グレースケール化して書き出し
diff_inv_binarized = cv2.threshold(diff_inv, 100, 255, cv2.THRESH_BINARY) cv2.imwrite('after.png', diff_inv)
まとめると
I = cv2.imread('data/before.png') kernel = np.ones((5,5), np.uint8) dilation = cv2.dilate(I, kernel, iterations = 1) diff = cv2.subtract(I, dilation) diff_inv = 255 - diff diff_inv_binarized = cv2.threshold(diff_inv, 100, 255, cv2.THRESH_BINARY) cv2.imwrite('after.png', diff_inv)
です
chainer: 独自datasetを定義する方法
chainerで独自データセットクラスを作るための方法を明示的に示したドキュメントが 見当たらなかったので、備忘録をかねて書く。実はとっても簡単。
- データセットにするクラスは
chainer.dataset.DatasetMixin
を継承する - 内部に持っているデータの数を返却する
__len__(self)
メソッドを実装する。このメソッドは整数でデータ数を返却する - i番目のデータを取得する
get_example(self, i)
メソッドを実装する。このメソッドは、- 画像配列などのデータ
- ラベル
の2つを返却する(return image_array, label みたいな感じで)
本当に必要なことはこのたった3つです。
Datasetのクラスを定義するタイミングで画像を全部読み込んでもよいですが、 get_example
を呼び出すタイミングで実際の読み込みを
行うのでも構いません。
実例:
import sys import random import numpy as np from PIL import Image import csv import chainer from chainer import datasets class ImageDataset(chainer.dataset.DatasetMixin): def __init__(self, normalize=True, flatten=True, train=True, max_size=200): self._normalize = normalize self._flatten = flatten self._train = train self._max_size = max_size pairs = [] with open('data/filename_label_list.tsv', newline='') as f: tsv = csv.reader(f, delimiter='\t') for row in tsv: if 'jpg' in row[0]: pairs.append(row) self._pairs = pairs def __len__(self): return len(self._pairs) def get_image(self, filename): image = Image.open('data/' + filename) new_w = self._max_size + 1 new_h = self._max_size image = image.resize((new_w, new_h), Image.BICUBIC) image_array = np.asarray(image) return image_array # type cast image_array = image_array.astype('float32') label = np.int32(label) return image_array, label def get_example(self, i): filename = self._pairs[i][0] image_array = self.get_image(filename) if self._normalize: image_array = image_array / 255 if self._flatten: image_array = image_array.flatten() else: if image_array.ndim == 2: mage_array = image_array[np.newaxis,:] image_array = image_array.astype('float32') image_array = image_array.transpose(2, 0, 1) # order of rgb / h / w label = np.int32(self._pairs[i][1]) return image_array, label
__init__
で、ファイル名とラベルのリストを読み込んでいます。ここでself._pairs
にリストの各行を入れていますが、画像データはまだ読み込んでいません__len__
はself._pairs
の項目数を返却するだけget_example
で実際の画像読み込み=> 配列化とlabelの返却を行っています(画像読み込みは、エポック数が多い場合は__init__
内で先に全部読み込んでおいたほうが早い場合もあるかもしれません)__len__
が返却する値がdatasetのサイズとみなされます。その結果、- len / minibatch_sizeがepoch内で学習されるminibatchの個数となる
- Iteratorは0番目からlen番目までの要素をget_example(i)で取得するようになる
また、注意しておいたほうが良いことが数点だけあります。
- ラベルを整数で返却する場合、
np.int32( label )
という感じでnp.int32
にキャストして返却すること. 普通のintでも回せますが、GPUを使わず、CPUのみで実行しようとするとき、labelがint型だとCUDA environment is not correctly set up
という関連の分かり辛いエラーで怒られてしまいます - 画像を普通に読み込むと、0 ~ 255 の整数データになるため、0.0 ~ 1.0に正規化すること(私は'float32'型を指定しています)
- 画像を返却する際は(色次元数, h, w) という順番に軸変換を行っておくこと。普通にPIL等でイメージを読み込むと、( h, w, 色次元数 ) という順になるため、
image_array = image_array.transpose(2, 0, 1)
などで変換が必要です - datasetクラスをinitializeする際に全てのデータを読み込んだり生成したりするのはGPUメモリ容量的に得策ではありません。initialize時にはデータのリストだけ作り、get_example内でデータの実体を読み込む/生成するようにしたほうが良いと思います。(特にデータ数が多い場合)
いろんなデータセットで楽しむきっかけになれば幸いです。
AWS Step FunctionsとLambdaでディープラーニングの訓練を全自動化する
動機とやったことの概要
- スポットインスタンスで学習をして、無駄なくインスタンスを止めたい
- Step Functionsへの入力を変えるだけで様々な条件での学習を実行させたい
- 機械学習のコード自体にこのStep Functionsへの依存性は持たせなくて良い方針で作ったので、ディープラーニング以外のバッチ処理でも同じように使えるはず
詳細
Lambdaに付与する権限
たぶん以下くらいの権限がLambda実行時に必要。
AWSLambdaAMIExecutionRole AmazonS3FullAccess AmazonEC2SpotFleetRole AWSLambdaBasicExecutionRole AmazonSNSFullAccess EC2ReadOnly ("ec2:DescribeSpotInstanceRequests"リソースへのアクセスを追加)
Step Functionsの入力
{ "exec_name": "pix2pix-20161231", "repository_url": "https://github.com/mattya/chainer-pix2pix.git", "repository_name": "chainer-pix2pix", "data_dir": "/home/ubuntu/data", "output_dir": "/home/ubuntu/result", "data_get_command": "/home/ubuntu/.pyenv/shims/aws s3 cp s3://pix2pixfacade/ /home/ubuntu/data --recursive", "exec_command": "/home/ubuntu/.pyenv/shims/python /home/ubuntu/chainer-pix2pix/train_facade.py -g 0 -e 100 -i /home/ubuntu/data --out /home/ubuntu/result --snapshot_interval 10000" }
変数名 | 説明 |
---|---|
exec_name | この実行の名前。バケット名にもなるため、アンダースコアを使わずkebab-case推奨 |
repository_url | git cloneする対象のリポジトリURL |
repository_name | git cloneしたあと取得できるリポジトリ名 |
data_dir | データを格納するディレクトリ |
output_dir | 訓練結果等を格納するディレクトリ |
data_get_command | データを取得するなど、訓練開始前に実施する |
exec_command | 訓練実施コマンド |
やってることの中身
Step Functionの定義
{ "Comment" : "Machine learning execution with spot instance", "StartAt" : "CreateS3Bucket", "States" : { "CreateS3Bucket": { "Type" : "Task", "Resource" : "arn:aws:lambda:ap-northeast-1:999999999999:function:create_s3_bucket", "Next" : "RequestSpotInstance" }, "RequestSpotInstance": { "Type" : "Task", "Resource" : "arn:aws:lambda:ap-northeast-1:999999999999:function:request_spot_instance", "Next" : "WaitBidding" }, "WaitBidding": { "Type" : "Wait", "Seconds" : 30, "Next" : "CheckBiddingResult" }, "CheckBiddingResult": { "Type" : "Task", "Resource" : "arn:aws:lambda:ap-northeast-1:999999999999:function:check_bidding_result", "Next": "ChoiceBiddingResult" }, "ChoiceBiddingResult": { "Type" : "Choice", "Choices": [ { "Variable": "$.request_result", "BooleanEquals": true, "Next": "NotifyRequestSuccess" }, { "Variable": "$.request_result", "BooleanEquals": false, "Next": "NotifyRequestFailed" } ], "Default": "NotifyRequestFailed" }, "NotifyRequestFailed": { "Type" : "Task", "Resource": "arn:aws:lambda:ap-northeast-1:999999999999:function:send_sms_message", "Next": "SpotRequestFailed" }, "SpotRequestFailed": { "Type": "Fail", "Error": "SpotRequestError", "Cause": "Spot price bidding too low" }, "NotifyRequestSuccess": { "Type" : "Task", "Resource": "arn:aws:lambda:ap-northeast-1:999999999999:function:send_sms_message", "Next": "WaitTaskComplete" }, "WaitTaskComplete": { "Type" : "Wait", "Seconds" : 10, "Next" : "CheckTaskCompleted" }, "CheckTaskCompleted": { "Type" : "Task", "Resource": "arn:aws:lambda:ap-northeast-1:999999999999:function:check_task_completed", "Next": "ChoiceTaskCompleted" }, "ChoiceTaskCompleted": { "Type" : "Choice", "Choices": [ { "Variable": "$.task_completed", "BooleanEquals": true, "Next": "NotifyTaskCompleted" }, { "Variable": "$.task_completed", "BooleanEquals": false, "Next": "WaitTaskComplete" } ], "Default": "WaitTaskComplete" }, "NotifyTaskCompleted":{ "Type": "Task", "Resource": "arn:aws:lambda:ap-northeast-1:999999999999:function:send_sms_message", "Next": "WaitInstanceDelete" }, "WaitInstanceDelete": { "Type" : "Wait", "Seconds" : 1800, "Next" : "DeleteSpotInstance" }, "DeleteSpotInstance": { "Type": "Task", "Resource": "arn:aws:lambda:ap-northeast-1:999999999999:function:delete_ec2_instance", "End": true } } }
- 判断分岐以外は直列に流してるだけ。
- 処理途中に生成されるID類はeventに追加しながら下流に流す
- S3作成とスポットインスタンスリクエストはParallelにしても良いかも(面倒くさいのでやってない..)
- 訓練完了から30分は削除せずに待つ。サーバに未練があればこの間に実行を停止する。
S3バケット作成
#!/usr/bin/env python # -*- coding: utf-8 -*- import boto3 import json import os def lookup(s3, bucket_name): try: s3.meta.client.head_bucket(Bucket=bucket_name) except botocore.exceptions.ClientError as e: error_code = int(e.response['Error']['Code']) if error_code == 404: return False return True def create_bucket(bucket_name): s3 = boto3.resource('s3') response = '' if not lookup(s3, bucket_name): response = s3.create_bucket(Bucket=bucket_name) return response def lambda_handler(event, context): response = create_bucket(event['exec_name']) return event
- eventからexec_nameを取り出してバケット名に
- その名前のバケットがなければ作る
スポットインスタンスのリクエスト
#!/usr/bin/env python # -*- coding: utf-8 -*- import boto3 import json import logging import base64 import os SPOT_PRICE = '0.8' REGION = 'ap-northeast-1' AMI_ID = 'ami-9999999f' KEY_NAME = 'your_keyname' INSTANCE_TYPE = 'g2.2xlarge' SECURITY_GRUOP_ID = ['sg-9999999'] def request_spot_instance(user_data): ec2_client = boto3.client('ec2', region_name = REGION ) response = ec2_client.request_spot_instances( SpotPrice = SPOT_PRICE, Type = 'one-time', LaunchSpecification = { 'ImageId': AMI_ID, 'KeyName': KEY_NAME, 'InstanceType': INSTANCE_TYPE, 'UserData': user_data, 'Placement':{}, 'SecurityGroupIds': SECURITY_GRUOP_ID } ) return response def lambda_handler(event, context): REPOSITORY_URL = event["repository_url"] REPOSITORY_NAME = event["repository_name"] BUCKET_NAME = event["exec_name"] shell='''#!/bin/sh sudo -s ubuntu cd /home/ubuntu sudo -u ubuntu mkdir /home/ubuntu/.aws sudo -u ubuntu mkdir /home/ubuntu/completed sudo -u ubuntu git clone {5} sudo -u ubuntu mkdir {0} sudo -u ubuntu mkdir {1} sudo -u ubuntu echo "[default]" >> /home/ubuntu/.aws/credentials sudo -u ubuntu echo "aws_access_key_id={2}" >> /home/ubuntu/.aws/credentials sudo -u ubuntu echo "aws_secret_access_key={3}" >> /home/ubuntu/.aws/credentials sudo -u ubuntu echo "*/5 * * * * /home/ubuntu/.pyenv/shims/aws s3 sync {1} s3://{4} > /dev/null 2>&1" >> mycron sudo -u ubuntu echo "*/1 * * * * /home/ubuntu/.pyenv/shims/aws s3 cp {1}/log s3://{4} > /dev/null 2>&1" >> mycron sudo -u ubuntu echo "*/1 * * * * /home/ubuntu/.pyenv/shims/aws s3 cp /home/ubuntu/trace.log s3://{4} > /dev/null 2>&1" >> mycron sudo -u ubuntu echo "*/1 * * * * /home/ubuntu/.pyenv/shims/aws s3 sync /home/ubuntu/completed s3://{4} > /dev/null 2>&1" >> mycron sudo -u ubuntu /usr/bin/crontab mycron sudo -u ubuntu /bin/rm /home/ubuntu/mycron PATH="/usr/local/cuda/bin:$PATH" LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH" sudo -u ubuntu cd /home/ubuntu/{6} sudo -u ubuntu touch trace.log sudo -u ubuntu echo `pwd` >> trace.log 2>&1 sudo -u ubuntu echo `which python` >> trace.log 2>&1 sudo -u ubuntu echo 'repository_name: {6}' >> trace.log 2>&1 sudo -u ubuntu echo 'dataget_command: {7}' >> trace.log 2>&1 sudo -u ubuntu echo 'exec_command: {8}' >> trace.log 2>&1 sudo -u ubuntu {7} > /dev/null 2>> trace.log sudo -u ubuntu echo `ls /home/ubuntu/data | wc` >> trace.log PATH="/usr/local/cuda/bin:$PATH" LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH" sudo -u ubuntu -i {8} >> trace.log 2>&1 sudo -u ubuntu touch /home/ubuntu/completed/completed.log ''' shell_code = shell.format( event["data_dir"], event["output_dir"], os.environ.get('S3_ACCESS_KEY_ID'), os.environ.get('S3_SECRET_ACCESS_KEY'), event["exec_name"], event["repository_url"], event["repository_name"], event["data_get_command"], event["exec_command"] ) user_data = base64.encodestring(shell_code.encode('utf-8')).decode('ascii') response = request_spot_instance(user_data) event["spot_instance_request_id"] = response["SpotInstanceRequests"][0]["SpotInstanceRequestId"] return event
- インスタンスタイプや入札価格は定数にして、StepFunction実行時の入力(event)からは引かないようにしている(eventはコードの実行条件のみにし、環境調達条件はLambda側に持たせるポリシーのつもり)
- AMIは、chainer、CUDA等はインストール完了いているものがある前提
- インスタンスをリクエストしたあとuser_dataをシェルスクリプトにして流し込んでる
- 大体の汚い処理はここのシェルスクリプトに凝縮されている
- S3へのupload系タスクはcronに登録
- その後、パスを通して訓練の開始
- S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEYはIAMのwrite権限のある鍵をLambda Functionの環境変数に登録しておく。
- 実行時のログはtrace.logに出力 > これもS3に随時Up
- 実行完了後に、completed.logを出力。これがS3のバケットに入ると、StepFunctions側でタスク完了とみなされる
入札結果確認
def check_bidding_result(spot_instance_request_id): ec2_client = boto3.client('ec2', region_name = REGION ) response = ec2_client.describe_spot_instance_requests( SpotInstanceRequestIds = [spot_instance_request_id] ) return response def lambda_handler(event, context): response = check_bidding_result(event["spot_instance_request_id"]) event["request_result"] = (response['SpotInstanceRequests'][0]['Status']['Code']==u'fulfilled') if event["request_result"]: event["instance_id"] = response['SpotInstanceRequests'][0]['InstanceId'] return event
- スポットインスタンスリクエスト時に取得した'SpotInstanceRequests'から、入札の結果を確認する
通知
#!/usr/bin/env python # -*- coding: utf-8 -*- import boto3 import json import os TOPIC_ARN = 'arn:aws:sns:ap-northeast-1:9999999999:training_end_notification_mail' # Mail REGION = 'ap-northeast-1' def send_sms_message(event, context): sns = boto3.client('sns', region_name = REGION ) message = '' subject = '' if "completed" in event: subject = 'Training ended' message = '''task completed! result: https://console.aws.amazon.com/s3/home?bucket={0} ----- {1} '''.format(event["exec_name"], event) else: if event["request_result"]: subject = 'request fulfilled' message = ''' Spot Request Fulfilled! {0} '''.format(event["exec_name"]) else: subject = 'request failed' message = ''' Spot Request Fails! {0} '''.format(event["exec_name"]) response = sns.publish( TopicArn = TOPIC_ARN, Subject = subject, Message = message ) return response def lambda_handler(event, context): response = send_sms_message(event, context) return event
その他のLambda
- あとは特別なことはしていないリポジトリをご参照ください
リポジトリ
改良案とか、◯◯をXXでやらないなんて有りえない!とかあればお気軽に @mizti までコメントください (AWS今までちゃんと触ってこなかった勢なので話せれば嬉しいです)
生成モデルpix2pixを動かしてみる
pix2pix is 何
- 2016年11月に発表された、任意の画像を入力にして、それを何らかの形で加工して出力する、というある種の条件付きGAN。
GANって何: 画像等のデータ入力を真似て偽造する生成器と、そのデータが生成されたものか本物かを識別する鑑別器を互いに競わせるように訓練することで、本物によく似たなデータを作り出せるようにする「生成モデルおよびその訓練手法」
- 2014年に公表された当初は訓練の不安定性の問題が大きかったが、Batch Normalizationの導入や条件付けなど安定を増すノウハウが蓄積され、ここ2年注目を浴びている。
- 先週GAN / DCGANについてまとめてみたエントリを書いたので参考になるかも
「画像を入力にして画像を出力にする」ようなタスクは世の中に無数に存在していて、その潜在的な適用範囲の広さが特徴。(着彩、塗り分け、単純化etc..)
下記はpix2pixサイトのサンプル。
より詳細なサンプル例は下記で見られる
pix2pixの構成
大きく3つのネットワークから構成される:
- Encoder: 画像の畳み込みにより、入力画像の特徴量を圧縮する。
- Decoder: Encoderで圧縮された特徴量を逆畳み込み(転置畳み込み)x6層したあと畳み込みx1層により画像に変換する
Discriminator: 「入力画像」と「真の出力サンプルまたはDecoderの出力」2つの画像入力を行う、「本物らしさ」を出力する
(追記) Decoder 層は、直前の層の他に 層のEncoderの出力も同時に受け取る(U-Netというらしい)
- (追記) Discriminatorは、"Patch GAN"と名付けられているようだけどちゃんと読み解けてないです...
3つのネットワークの損失関数:
- Encoder: 「生成された偽画像と真の画像の差異」と「Enc->Decが出力した画像がDiscriminatorに偽物と思われた度合いのsoftplus->バッチ・画素数平均」の重み付き和
- Decoder: Encoderの損失関数と同じ
- Discriminator: 本物の画像(t_out)を偽物と判定した度合い(softplus->バッチ画素平均)と偽物の画像(x_out)を本物と判定した度合いの和
動かしてみる
例によってMattyaさんが例によって神速でChainer実装しているので、今日はそのまま動かしてみる。
- git cloneする
- 訓練用データとなるFacadeという様々な建物の前面部分写真/構成情報のデータを落としておく。 CMP Facade Database
- データの場所、出力の場所、処理するGPU番号等をオプションで指定し起動する
と、たったこれだけ。 (前提環境を整えるのは先回のエントリの記載内容。ただし、chainerは1.19以上が必要)
- 訓練条件は、デフォルトのまま
- ミニバッチサイズ1
- 300枚の画像セットについてランダムな順番で300イテレーションで1エポック
- エポック数200で全訓練完了
結果
入力画像 / 生成画像 / 正解画像
- 20000 イテレーション
入力画像 / 生成画像 / 正解画像
- 30000 イテレーション
入力画像 / 生成画像 / 正解画像
- 40000 イテレーション
入力画像 / 生成画像 / 正解画像
- 50000 イテレーション
入力画像 / 生成画像 / 正解画像
- 59800 イテレーション(200エポック訓練完了)
(生成し損ねた..)
- 損失関数の推移
思ったこととか
1万回目くらいで既に建物にしか見えない画像を生成できるようになってる。まずこれがすごい
Enc / Decの損失関数って、「正解画像との一致度」「Discriminator騙せた度合い」両方損失として使えそうだけど どうやるんだろう... > からの「重みづけして足すだけ」という分かりやすさ。この重み変えると学習結果にどう影響するのか興味ある。 きっと重要なハイパーパラメータ。
回が進むにつれて、画像による明度差が生まれやすくなっている。
- 色相の差が生まれづらいのは、入力画像と建物色の間にはっきりした相関関係が無いから?
- じゃ何で明度差を生み出せたんだろう(?)
- 色相の差が生まれづらいのは、入力画像と建物色の間にはっきりした相関関係が無いから?
まっすぐあるべき線がまっすぐにならないのは他の生成手法でもよくある。(ニューラルネットワークって「まっすぐな線」引くの苦手ですよね...)生成画像の黒ずみと相俟ってスラムみたいな印象に...
損失関数の推移だけみると、まだ学習が収束してないのでepoch数を増やしても良さそう
Mattyaさんの実装で使われていた「CBR層」(畳み込み/BN/ReLU/Dropoutをセットにした層)が便利。
- up / down指定するだけで畳み込み / 逆畳み込み両方に使える(!)
新しいタスクへの適用
- 新しいデータセットにしたいと思って、pixivからイラスト拾い集めてみている(やっと500枚集まった)。うまくいけば着彩タスクを作れるかもしれない
- 未着彩の線画提供 / 着彩させてblogに載せさせてやってもいいぜっていう絵師さんいませんか
Numpy逆引きメモ
- Numpyでよく使う操作のメモ。自分用、随時追記
生成
リストで指定した内容の行列を生成
>>> x = np.array([[0,1],[3,4]]) >>> x array([[0, 1], [3, 4]])
numpy行列からlistへの変換
>>> x = np.array([3,4,5]) >>> x array([3, 4, 5]) >>> list(x) [3, 4, 5]
2次元配列で同じことをすると
>>> x array([[0, 1], [3, 4]]) >>> >>> list(x) [array([0, 1]), array([3, 4])]
となる。もし多重リスト形式に変換したければ、
>>> x = np.array([[0,1],[3,4]]) >>> x.tolist() [[0, 1], [3, 4]] >>> x_list = x.tolist() >>> x_list [[0, 1], [3, 4]]
1で埋めた行列を生成
>>> np.ones([2,3]) array([[ 1., 1., 1.], [ 1., 1., 1.]])
0で埋めた行列を生成
>>> np.zeros([2,3]) array([[ 0., 0., 0.], [ 0., 0., 0.]])
対角行列の生成
>>> np.eye(4) array([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 1.]])
必ず正方行列になる。
ランダム要素で埋めた行列を生成
一様分布
np.random.uniform(2, -3, (5, 4))
上記の意味: 2から-3の間の値で埋めた5, 4の行列を生成する (4つの値が入ったリストが5つ入っているリスト)
指定区間の数列を生成する
>>> X = np.arange(10) >>> X array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Stepや始点・終点も指定可
>>> X = np.arange(8,20,2) >>> X array([ 8, 10, 12, 14, 16, 18])
指定数がStepの倍数でない場合
>>> X = np.arange(70,40,-11) >>> X array([70, 59, 48])
型指定も可
>>> X = np.arange(10.,dtype=np.float32) >>> X array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.], dtype=float32)
情報取得
行列の要素数を取得する
>>> h = np.array([2,3,4]) >>> h.size 3 >>> h = np.array([[1,2,3],[4,5,6]]) >>> h.size 6
行列の形状を取得する
>>> h = np.array([[1,2,3],[4,5,6]]) >>> h.shape (2, 3)
行列の次元数を取得する
>>> h = np.array([[1,2,3],[4,5,6]]) >>> h.ndim 2
行列のrankを取得する
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.matrix_rank(A) 2
形の一致する二つの行列で値が一致する数をカウントする
>>> y=np.array([1,1,2,3,7]) >>> t=np.array([1,2,3,4,7]) >>> >>> np.sum(y==t) 2
行列から最大値を抽出する
>>> hoge = np.array([[3,4,5],[2,3,4]]) >>> hoge.__class__ <class 'numpy.ndarray'> >>> np.max(hoge) 5
行列から最大値のindexを取り出す
>>> hoge = np.ndarray([[3,4,5],[2,3,4]]) >>> np.argmax(hoge) 2
軸を指定してsumの最大値を取り出す
>>> A = np.array([[3,4,5],[2,3,4]]) >>> np.max(A, axis=0) array([3, 4, 5]) >>> np.max(A, axis=1) array([5, 4])
特定の条件を満たす要素のindexを取得する
>>> a array([ 1, 3, 4, 2, 5, 8, 10]) >>> np.where(a>3) (array([2, 4, 5, 6]),) >>> >>> np.where(a==3) (array([1]),)
形状操作
行列の次元数を追加する
>>> h = np.array([1,2,3]) >>> h array([1, 2, 3]) >>> >>> h = h[np.newaxis,:] >>> h array([[1, 2, 3]]) >>> h = h[:, np.newaxis] >>> h array([[1], [2], [3]])
行列を結合する(連結する)
>>> A array([[1, 2], [3, 4]]) >>> B array([5, 6])
のとき、
縦に連結
>>> np.r_[A,B] array([[1, 2], [3, 4], [5, 6], [7, 8]])
横に連結
>>> np.c_[A,B] array([[1, 2, 5, 6], [3, 4, 7, 8]])
Flattenする
>>> a = np.array([[1,2], [3,4]]) >>> a.flatten() array([1, 2, 3, 4])
行列を切り落とす(y軸)
>>> h array([[1, 2, 3], [3, 4, 5], [4, 5, 6]]) >>> h[1:] array([[3, 4, 5], [4, 5, 6]]) >>> h[:1] array([[1, 2, 3]])
行列を切り落とす(x軸)
>>> h array([[1, 2, 3], [3, 4, 5], [4, 5, 6]]) >>> h[:,1:] array([[2, 3], [4, 5], [5, 6]]) >>> h[:,:2] array([[1, 2], [3, 4], [4, 5]])
軸操作
多重リスト要素の最大値のインデックスをリストで返す
>>> y = [[1,2,3],[5,6,7]] >>> t = [[0,0,1],[0,1,0]] >>> np.argmax(y, axis=1) >>> y = np.argmax(y, axis=1) >>>y array([2, 2]) >>> t = np.argmax(t, axis=1) >>> t array([2, 1])
多次元行列から特定次元だけ抜き出す
data = [ [1 ,0.4834], [2 ,0.5526], [3 ,0.6076], [4 ,0.5436], ... [299 ,0.9718], [300 ,0.97] ]
という2次元行列があるときに、
[1, 2, 3, ... 299, 300]
を抜き出すには
data[: , 0]
[0.4834, 0.5526, 0.6076, ... 0.97]
を抜き出すには
data[:, 1]
とすればよい
他の例:
>>> y array([[0, 1], [2, 3], [4, 5]])
に対して、
>>> y[:,0] array([0, 2, 4]) >>> y[:,1] array([1, 3, 5]) >>> y[:,-1] array([1, 3, 5])
(要素数2なので、前から数えて1番目と後ろから数えて-1番目は同じ)
要素の順番を入れ替える
>>> y array([[0, 1], [2, 3], [4, 5]]) >>> y[:,::-1] array([[1, 0], [3, 2], [5, 4]])
演算
行列の積を求める
>>> X = np.array([[2,3],[3,4]]) >>> Y = np.array([2,2]) >>> X.dot(Y) array([10, 14])
"*"による演算は、要素同士の掛け算になるため注意。
>>> X = np.array([[2,3],[3,4]]) >>> Y = np.array([2,2]) >>> X*Y array([[4, 6], [6, 8]])
行列要素の和を取る
>>> A array([[1, 2], [3, 4]]) >>> np.sum(A) 10
線形代数的操作
行列を転置する
>>> h array([[1, 2, 3], [3, 4, 5], [6, 7, 8]]) >>> np.transpose(h) array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
もしくは
>>> h array([[1, 2, 3], [3, 4, 5], [6, 7, 8]]) >>> h.transpose() array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
transposeは、数式に似せて
>>> h.T array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
と書くこともできる
多次元行列については、引数を与えなければx,y,z => z,y,xのように逆順になるが、 順番を与えることもできる
>>> x.transpose(0,1,2) array([[[ 0, 1, 2], [ 3, 4, 5]], [[ 6, 7, 8], [ 9, 10, 11]]]) >>> x.transpose(0,2,1) array([[[ 0, 3], [ 1, 4], [ 2, 5]], [[ 6, 9], [ 7, 10], [ 8, 11]]]) >>> x.transpose(2,0,1) array([[[ 0, 3], [ 6, 9]], [[ 1, 4], [ 7, 10]], [[ 2, 5], [ 8, 11]]])
行列式を出力する
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.det(A) -2.0000000000000004
>>> P array([[2, 3], [4, 5]]) >>> np.linalg.det(P) -2.0
行列のtraceを出力する
>>> A array([[1, 2], [3, 4]]) >>> np.trace(A) 5
行列の対角要素を抜き出す
>>> A array([[1, 2], [3, 4]]) >>> np.diag(A) array([1, 4])
np.diag(A)は、Aの対角要素を返す。
対角要素から対角行列を生成する
>>> a array([ 1., 2., 3.]) >>> np.diag(a) array([[ 1., 0., 0.], [ 0., 2., 0.], [ 0., 0., 3.]])
ある行列の固有値の対角行列を得る
>>> np.diag(np.linalg.eigvals(A)) array([[-0.37228132, 0. ], [ 0. , 5.37228132]])
行列の固有値を求める
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.eigvals(A) array([-0.37228132, 5.37228132])
行列の固有値、固有ベクトルを求める
>>> A array([[1, 2], [3, 4]]) >>> la, P = np.linalg.eig(A) >>> la array([-0.37228132, 5.37228132]) >>> P array([[-0.82456484, -0.41597356], [ 0.56576746, -0.90937671]])
laが固有値、Pが固有ベクトル。 la[n]がP[n,:]に対応する。
行列の逆行列を作る
>>> np.linalg.inv(A) array([[-2. , 1. ], [ 1.5, -0.5]])
行列の対角化を行う
>>> A array([[ 0, 14, 2], [-1, 9, -1], [-2, 4, 8]])
のとき、
>>> l, P = np.linalg.eig(A) >>> l array([ 4., 6., 7.]) >>> P array([[ 9.42809042e-01, -9.12870929e-01, -8.94427191e-01], [ 2.35702260e-01, -3.65148372e-01, -4.47213595e-01], [ 2.35702260e-01, -1.82574186e-01, 2.25257672e-15]])
lが固有値、Pが固有ベクトルを組み合わせて作った行列。 l[n]がP[n,:]に対応する(nは0,1,2)
固有値を対角行列化する
>>> D = np.diag(l) >>> D array([[ 4., 0., 0.], [ 0., 6., 0.], [ 0., 0., 7.]])
PDP-1がAと一致することを確認
>>> P.dot(np.diag(l)).dot(np.linalg.inv(P)) array([[ 9.79179023e-16, 1.40000000e+01, 2.00000000e+00], [ -1.00000000e+00, 9.00000000e+00, -1.00000000e+00], [ -2.00000000e+00, 4.00000000e+00, 8.00000000e+00]])
ベクトルの内積を求める
>>> x array([1, 2, 3]) >>> y array([3, 1, 0]) >>> x.dot(y) 5
行列のノルムを求める
>>> x array([1, 2, 3]) >>> np.linalg.norm(x) 3.7416573867739413
ベクトル同士のcos類似度を求める
内積を双方のノルムで割れば良いので
>>> x array([1, 2, 3]) >>> y array([3, 1, 0]) >>> x.dot(y) / (np.linalg.norm(x) * np.linalg.norm(y)) 0.42257712736425829
その他よく使うイディオム
多次元配列の要素をイテレーションする
配列の要素のインデックスを順番に取得できる
>>> x array([[0, 1], [3, 4]]) >>> it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) >>> it <numpy.nditer object at 0x10a444c10> >>> >>> it.multi_index (0, 0) >>> it.iternext() True >>> it.multi_index (0, 1) >>> it.iternext() True >>> it.multi_index (1, 0) >>> it.iternext() True >>> it.multi_index (1, 1) >>> it.iternext() False
行列要素の型を変換する
>>> A array([[1, 2], [3, 4]]) >>> A.astype("float32") array([[ 1., 2.], [ 3., 4.]], dtype=float32)
(わかりづらいが、小数点が付いてfloat型になっている)
map適用相当の関数適用
x = np.array([1, 2, 3, 4, 5]) squarer = lambda t: t ** 2 squares = np.array([squarer(xi) for xi in x])
printしたときに省略せずに表示する
np.set_printoptions(threshold=np.inf)
をコードのどこかで宣言したあとでprint