ie_test
2018年12月1日土曜日
福岡へ
引越しをして福岡に来た。東京近郊に疲れていたので。 福岡はなんというか人口密度が全く違っていてスペースを広く使おうという感覚がある。 こんなにも疲労感が違うというのはかなり不思議なことで、逆にちょっと怖くなった。
2018年8月5日日曜日
RSAについて
# 概要 RSA の復号がうまくいくことを述べておく。(よく忘れるので。) 以下とほぼ同様(なので個人メモのようなもの) https://ww1.fukuoka-edu.ac.jp/~sakamott/RSAtop.html ## 補題 1 p, q を相異なる素数とし,x, a, b を整数とする。 このとき、 a ≡ b (mod p) かつ a ≡ b (mod q) ならば a ≡ b (mod pq) が成り立つ。 ### (証明) (a - b) は p の倍数かつ q の倍数。 p,q は素数なので (a - b ) は pq の倍数。 ## 補題 2( フェルマーの小定理 ) p を素数とし、a を p と互いに素な整数とするとき、 $a^p$ ≡ a ( mod p ) が成り立つ。 ### (証明) a = 0 の時は自明 #### $ a \neq 0 $ の時 $ a^p - a = (1+m)^p - (1+m) = m^p -m + pC_1m^{(p-1)} + pC_2m^{(p-2)} + ... $ となっており、 $ pC_im^{(p-i)} $ は p が素数なので p の倍数となる。 よって $ a^p - a = m^p - m $ mod p となる。 よって帰納法により ok 。 ## RSA暗号のための命題 相異なる素数 p, q に対して L を p-1 と q-1 の最小公倍数とする。 k ≡ 1 ( mod L ) をみたす整数 k が与えられたとき, 任意の整数 x に対して $x^k$ ≡ x ( mod pq ) が成り立つ。 ## 証明 まず最初に $x^k$ ≡ x ( mod p ) を示す。 ### x が p と素な場合 L は (p-1) の倍数なので $ x^k $ = $ x^{t(p-1)+1} $ と書ける。 フェルマーの小定理より $ x^{(p-1)} $ = 1 mod p より $ x^k $ = $ x^{t(p-1)+1} $ = x mod p . ### x が p と素でない場合 $ x^p $ = 0 mod p より自明 同様に q に対しても $x^k$ ≡ x ( mod q ) が成り立つ。 よって補題1 より $x^k$ ≡ x ( mod pq ) が成り立つ。
2018年8月1日水曜日
BigQuery-Python を使った際のハマりどころ
# 実行内容 BigQuery (以下BQ)でクエリ実行、一時テーブルの保存 -> 保存したテーブルを google cloud strage (以下GCS) へ export とする。 # 問題点など 1. BQ -> GCS のところで一つの GCS ファイルに転送しようとするとエラーになる。 なので転送先を TMP_GCS_FILE = "gs://a-test/my_result/tmp*.csv.gzip" のように * を含むようにする。 このようにするとコピーする際に適切なファイルのサイズに分割され、 tmp000000000000.csv.gzip, tmp000000000001.csv.gzip ... tmp000000000008.csv.gzip のようなファイル名で GCS 上に保存される。 2. BQ において query に ordered by でソートするようにしたらメモリオーバーでダメというエラーが生じた。bigQuery で ordered by はできないと考えた方が良いかも。
tensorflow の RNN 実装について
# 目的 RNN は入力が結構複雑なので意図通りのネットワークになっているかを確認したい. ## RNN コード 以下のコードをみる。わかりやすくするために $$ W \in R^{3 \times 5}$$ の要素を全て 1,$$b \in R^5$$ を全て 0 にして実行する。 ``` #coding:utf-8 import tensorflow as tf import numpy as np n_inputs = 3 n_neurons = 5 X0 = tf.placeholder( tf.float32, [None, n_inputs] ) X1 = tf.placeholder( tf.float32, [None, n_inputs] ) basic_cell = tf.contrib.rnn.BasicRNNCell( num_units=n_neurons ) output_seq, states = \ tf.contrib.rnn.static_rnn( basic_cell, [X0, X1], dtype=tf.float32 ) Y0, Y1 = output_seq init = tf.global_variables_initializer() X0_batch = np.array( [ [0, 0.1, 0.2] ] ) X1_batch = np.array( [ [0, 0.1, 0.2] ] ) assign_ops = [] for v in tf.global_variables(): #rnn/basic_rnn_cell/kernel:0 (8, 5) print(v.name,v.shape) if v.name == "rnn/basic_rnn_cell/bias:0": assign_ops.append ( tf.assign( v, tf.zeros( v.shape, dtype=tf.float32 ) ) ) else: assign_ops.append ( tf.assign( v, tf.ones( v.shape, dtype=tf.float32 ) ) ) assign_ops_exec = tf.group(*assign_ops) with tf.Session() as sess: init.run() sess.run( assign_ops_exec ) Y0_val, Y1_val = sess.run( [Y0,Y1], feed_dict={ X0: X0_batch, X1:X1_batch } ) print(Y0_val) # math.tanh( 0.3 ) print(Y1_val) # math.tanh( 5*math.tanh(0.3) + 0.3 ) ``` 実は以下のコードと同様のネットワークになっている。 ``` #coding:utf-8 import tensorflow as tf n_inputs = 3 n_neurons = 5 X0 = tf.placeholder( tf.float32, [None, n_inputs] ) X1 = tf.placeholder( tf.float32, [None, n_inputs] ) Wx = tf.Variable( tf.random_normal( shape=(n_inputs, n_neurons), dtype=tf.float32 ) ) Wy = tf.Variable( tf.random_normal( shape=(n_inputs, n_neurons), dtype=tf.float32 ) ) b = tf.Variable( tf.zeros( [1, n_neurons], dtype=tf.float32 ) ) Y0 = tf.tanh( tf.matmul( X0, Wx) + b ) Y1 = tf.tanh( tf.matmul( Y0, Wy) + tf.matmul( X1, Wx ) + b ) print("ok") ``` ## 確認 rnn.py を実行すると以下の出力になる。 ``` [[0.2913126 0.2913126 0.2913126 0.2913126 0.2913126]] [[0.94211787 0.94211787 0.94211787 0.94211787 0.94211787]] ``` W が Wx と Wy を合わせた形となり shape = (8,5) となっていることが確認できる。 また、 math.tanh(0.3) = 0.2913126124515909 math.tanh( 5*math.tanh(0.3) + 0.3 ) = 0.9421178971878335 となることが確認できる。 ## LSTM コード 以下は MNIST に対して無理やり LSTM やってみたコードの一部。 https://blog.scimpr.com/2018/01/26/tensorflow%E3%81%AE%E3%83%A2%E3%83%87%E3%83%AA%E3%83%B3%E3%82%B0%EF%BC%92%E3%80%9Crnn/ ``` #coding:utf-8 import tensorflow as tf x = tf.placeholder( tf.float32, [None, 784] ) x2 = tf.reshape( x, [-1, 28, 28] ) x3 = tf.transpose( x2, [1, 0, 2] ) #[28, 50, 28]のtensor x4 = tf.reshape( x3, [-1, 28] ) #[28*50, 28]のtensor x5_list = tf.split( x4, 28 ) # shape=(*,28) となる tensor を 28 個もつリスト. # print( "x4: ", x4.shape ) # print( "type(x5): ", type(x5) ) # print( "type(x5[0]): ", type(x5[0]) ) # print( "x5[0]: ", x5[0].shape ) lstm_cell = tf.contrib.rnn.BasicLSTMCell( 13, forget_bias=1.0) outputs, states = tf.contrib.rnn.static_rnn( lstm_cell, x5_list, dtype=tf.float32 ) print( "len(outputs): ", len(outputs) ) print( "outputs[0].shape: ", outputs[0].shape ) print( "len(states): ", len(states) ) print( type(states[0]), type(states[1]) ) print( states[0].shape, states[1].shape ) print("variable print") for v in tf.global_variables(): print( v.name, v.shape ) ``` ## LSTM 確認 以下のように shape=(input*output, 4* output ) の変数が内部で確保されている事が確認できる。 ``` len(outputs): 28 outputs[0].shape: (?, 13) len(states): 2
(?, 13) (?, 13) variable print rnn/basic_lstm_cell/kernel:0 (41, 52) rnn/basic_lstm_cell/bias:0 (52,) ok ``` さらにあの LSTM のめんどい式が計算されていることを確認した方が良いが、 そのためには、もう少し小さいサンプルでデータを作成した方がよい。。 (力尽きた。)
luigi の機能色々
luigi で色々行う際にちょっとハマったことなどを書いてます。 luigi の基本的な取り扱いに関しては下記を参照 https://ietoa.blogspot.com/2018/07/luigi.html # luigi で google cloud strage(以下 gcs) を依存先に設定する方法 gcs 上のファイルを依存先として設定する方法を調査した。 ## 課題設定 gcs 上の a-test バケット上の test_dir フォルダの中の tmp_test.txt ファイルを a-test2 バケット上に tmp_test2.txt ファイルとしてコピーする。 ## コード ``` import os import luigi import luigi.contrib.gcs class Orig(luigi.Task): def output(self): return luigi.contrib.gcs.GCSTarget("gs://a-test/test_dir/tmp_test.txt") class Testup(luigi.Task): def requires(self): return Orig() def run(self): input_path = self.input().path output_path = self.output().path os.system( "gsutil cp %s %s"%(input_path, output_path ) ) def output(self): return luigi.contrib.gcs.GCSTarget("gs://a-test2/tmp_test2.txt") ``` 実際の実行には gsutil コマンドを使っている。 (gsutil コマンドの設定に関しては以下を参照,gcloud コマンドの設定ができれば使える。 https://ietoa.blogspot.com/2018/07/gcp-google-cloud-platform.html ) 依存の設定に luigi.contrib.gcs.GCSTarget を使っているが その前に import luigi.contrib.gcs という記述がないとエラーが生じるので注意。 # luigi で Bigquery のテーブルを依存先に設定する方法 Bigquery 上のテーブルを依存先として設定する方法を調査した。 ## コード 以下は Bigquery において, プロジェクトコードが watashi_no_project, dataset が "temp_results"で、 "my_table" -> "my_table_filter" -> "my_table_count" のように中間テーブルの作成が依存している場合のコードとなっている. BigqueryTarget は table.dataset_id, table.table_id によって dataset と table を表す文字列を取得できる。 (https://github.com/spotify/luigi/blob/master/luigi/contrib/bigquery.py) ``` import luigi from luigi.contrib.bigquery import BigqueryTarget PROJECT_ID = 'watashi_no_project' ... class SelectQuery( luigi.Task ): dest_dataset_id = "temp_results" dest_table_id = "my_table" def run(): print("this is given!") exit(1) def output(self): return BigqueryTarget( PROJECT_ID, self.dest_dataset_id, self.dest_table_id ) class FilterQuery( luigi.Task ): dest_dataset_id = "temp_results" dest_table_id = "my_table_filter" start_date = "2018-04-01" def requires(self): return SelectQuery() def run(self): exec_filter_query_by_date( self.start_date, self.input().table.dataset_id, self.input().table.table_id, self.output().table.dataset_id, self.output().table.table_id ) def output(self): return BigqueryTarget( PROJECT_ID, self.dest_dataset_id, self.dest_table_id ) class KaimonoCountQuery( luigi.Task ): dest_dataset_id = "temp_results" dest_table_id = "my_table_count" def requires(self): return FilterQuery() def run(self): exec_count_query( self.input().table.dataset_id, self.input().table.table_id, self.output().table.dataset_id, self.output().table.table_id ) def output(self): return BigqueryTarget( PROJECT_ID, self.dest_dataset_id, self.dest_table_id ) ``` # luigi で同じ task を並列に実行したい場合 下記のコードのように各タスクにパラメータを設定してやって、 あるタスクの requires に違うパラメータでインスタンスを設定する。 さらに設定ファイル、実行コマンドにおいて指定する要素がある。 ## コード ``` #coding:utf-8 import os import luigi class TestTask(luigi.Task): n = luigi.IntParameter() # インスタンス毎に異なるパラメータ. def run(self): os.system( "echo hello %d >%s"%( self.n, self.output().path) ) def output(self): return luigi.LocalTarget( "test_task_%d.txt"%self.n ) class TestTaskTotal(luigi.Task): def requires(self): ret_list = [] for i in range(4): ret_list.append( TestTask(n=i) ) return ret_list def run(self): print("ok") ``` ## 設定ファイルと実行コマンド 並列実行する場合、さらに以下のように「設定ファイル」と「実行コマンド」における設定が必要 ### 設定ファイル 4 並列したい場合は以下のように設定する必要がある。(デフォルトは実行するマシンのコア数になっている。) luigi.cfg ``` [core] parallel-scheduling-processes=4 ``` ### 実行コマンド 4 並列したい場合、以下のように実行する必要がある。(設定がないと実行はされるが並列実行にはならないので計算時間の改善にならない)。 gcloud コマンドで複数マシンに並列実行させる場合などで有効。 ``` PYTHONPATH="." luigi --local-scheduler --module mytmp TestTaskTotal --workers=4 ``` # 並列実行に関する話題 以下の記事のよると windows 上での並列実行では問題が生じる可能性がある。(未確認) https://qiita.com/soyiharu/items/e54bd963f9faf22c6cae また並列実行ジョブの個数と速度感については以下を参照。 https://qiita.com/yanagi3150/items/15b2928b5018b197ab45 思想としてもある程度の粗い粒度でのジョブ実行を想定していることがわかる。 https://luigi.readthedocs.io/en/stable/design_and_limitations.html ## 注意 joblib と併用すると問題になる可能性がある。(未検証) # オンメモリでの取り扱いについて luigi はオンメモリでの依存処理は推奨していないようだ。(以下の記事参照) https://qiita.com/yanagi3150/items/6dd88af31ad55d98bb33 # 依存設定のカスタマイズ luigi はデフォルトだと依存解決にターゲットファイルがあるかないかしか見ない。make がファイルの更新時間を見るのと比べるとだいぶてきとう。多分、ネットワーク越しの依存などを見るため、時間の同期などを保証できないだろうと想定しているからと思われる。 その代わり依存解決のカスタマイズができる。 https://qiita.com/ngr_t/items/b928bc13457571e25519 と https://gist.github.com/demoray/b503c887518941d264b0 を見る限りでは task クラスの complete メソッドを定義してやれば良い。 ## 依存関係の実装 上記のリンク先の記事を参考に書いてみた。 以下は test_task.txt が test_task2.txt より新しければ TestTask2 が実行される. ``` #coding:utf-8 import os import datetime import luigi class TestTask(luigi.Task): def run(self): os.system( "echo hello 111 >%s"%( self.output().path) ) def output(self): return luigi.LocalTarget( "test_task.txt" ) class TestTask2(luigi.Task): def requires(self): return TestTask() def run(self): os.system( "cat %s >%s"%( self.input().path, self.output().path) ) def output(self): return luigi.LocalTarget( "test_task_2.txt" ) def complete(self): output_fpath = self.output().path if os.path.exists( output_fpath ) == False: return False input_fpath = self.input().path if os.path.exists( input_fpath ) == False: return False input_datetime = datetime.datetime.fromtimestamp( \ os.path.getmtime( input_fpath ) ) output_datetime = datetime.datetime.fromtimestamp( \ os.path.getmtime( output_fpath ) ) if input_datetime > output_datetime: return False return True ``` なんとなくだけれども全てのタスクにここまで書かせるのはきついだろうと思うんで、何か良いライブラリやクラスがあるんではないかとも思う。 とりあえずここでは実装できて、実際に機能するということだけ確かめておいた。 # 全てのターゲットを消去して実行したいときなど 以下のページで方法についてだいぶ議論している。まだ追えてない。。時間のあるときに再調査! https://github.com/spotify/luigi/issues/595
tensorflow の batch normalize に関して
# 目的 実際の処理についてデバッグしてみた。 # コード 以下のコードは バッチの大きさ = 3 で 変数一個の層が2 つ重なったネットワークで $$f = x^3 + 1 $$ という関数を回帰するだけのトレーニング。 ``` #coding: utf-8 import random import tensorflow as tf import numpy as np random.seed(0) training = tf.placeholder_with_default( False, shape=(), name="training" ) x = tf.placeholder( tf.float32, shape=(None,1) ) #w = tf.Variable( tf.zeros([1,1]) ) w = tf.Variable( tf.ones([1,1]) ) #b = tf.Variable( tf.zeros([1]) ) b = tf.Variable( tf.ones([1]) ) hidden_tmp = tf.matmul( x, w ) + b # 行列の掛け算 #hidden = tf.sigmoid( hidden_tmp ) bn1 = tf.layers.batch_normalization( hidden_tmp, training=training, momentum=0.9 ) hidden = tf.sigmoid( bn1 ) #print( dir(bn1) ) #exit(1) w2 = tf.Variable( tf.ones([1,1]) ) #b2 = tf.Variable( tf.zeros([1]) ) b2 = tf.Variable( tf.ones([1]) ) f = tf.sigmoid( tf.matmul( hidden, w2 ) + b2 ) # 行列の掛け算 f_ = tf.placeholder( tf.float32, shape=(None,1) ) loss = tf.reduce_mean( tf.abs( f_ - f) ) learn_rate = 0.5 trainer = tf.train.GradientDescentOptimizer( learn_rate ) extra_update_ops = tf.get_collection( tf.GraphKeys.UPDATE_OPS ) with tf.control_dependencies(extra_update_ops): trainer_op = trainer.minimize(loss) #trainer_op = trainer.minimize( loss ) batch_size = 3 epochs = 10 with tf.Session() as sess: init = tf.global_variables_initializer() init.run() for i in range( epochs ): batch_xs, batch_fs = [], [] #print( w.eval() ) for j in range( batch_size ): x1 = random.random() f1 = x1*x1*x1 + 1 # この関数を訓練させる! batch_xs.append( [ x1 ] ) batch_fs.append( [ f1 ] ) print( batch_xs ) for v in tf.global_variables(): print(v.name, v.eval() ) print( hidden_tmp.eval( feed_dict={x: batch_xs } ) ) print( bn1.eval( feed_dict={x: batch_xs } ) ) sess.run( [trainer_op, extra_update_ops], feed_dict={x: batch_xs, f_: batch_fs, training:True } ) result_loss = loss.eval( feed_dict={x: batch_xs, f_: batch_fs } ) print( "result_loss:", result_loss ) ``` # 出力 以下のような出力がなされる。 ``` [[0.6108869734438016], [0.9130110532378982], [0.9666063677707588]] Variable:0 [[0.9991983]] Variable_1:0 [1.] batch_normalization/gamma:0 [0.9833956] batch_normalization/beta:0 [0.12067804] batch_normalization/moving_mean:0 [0.9730693] batch_normalization/moving_variance:0 [0.41279468] Variable_2:0 [[1.2562904]] Variable_3:0 [1.5393883] [[1.6103973] [1.9122791] [1.9658315]] [[1.0949917] [1.5564928] [1.638361 ]] .... ``` # 考察 ``` hidden_tmp = tf.matmul( x, w ) + b # 行列の掛け算 bn1 = tf.layers.batch_normalization( hidden_tmp, training=training, momentum=0.9 ) ``` 上記の hidden_tmp と bn1 の出力値を比べると m_m : moving_mean m_v : moving_variance bn1 = gamma*( (hidden_tmp - m_m) /math.sqrt( m_v+0.00001) ) + beta の関係にあることが確認できる。 # 課題 hidden_tmp のバッチの平均値に対して移動平均が取られて, moving_meabnとなっていることを確認した方が良い。
ランク付けに関する相関
# 目的 レコメンドにおいて機械学習により各ユーザーに対して各アイテムのランク付けを行う。 条件を変えるとランクが変わるが、そのランクの相関を測ることでどれだけランクが変動したかを表す指標があると良い。 「A New Rank Correlation Coefficient for Information Retrieval」にそのようなランクの指標が示されているのでこれを元に記載する。 # Kendall’s tau Kendall の tau はそういった指標の古典的なものである。(1938年ごろ) https://ja.wikipedia.org/wiki/%E3%82%B1%E3%83%B3%E3%83%89%E3%83%BC%E3%83%AB%E3%81%AE%E9%A0%86%E4%BD%8D%E7%9B%B8%E9%96%A2%E4%BF%82%E6%95%B0 N 個のアイテムに対する二つのランク付けに対して、順位が入れ替わっていないペアの個数を K, 入れ替わっている個数を J とした時 $$tau = \frac{K-J}{N*(N-1)/2}$$ と表される。 tau は - 1 から 1 の間で 1 に近い方がより相関が高く、 -1 に近いと逆相関になっている。0だと無相関といった形。 例: 5 つのアイテム(A~E)に対して二つのランク付け X, Y が以下のようであったとする。 1, 2, 3, 4, 5 X: C, E, A, B, D Y: D, A, B, E, C この時、アイテムの組み合わせは (A,B) ,(A,C), (A,D) , (A,E) (B,C) , (B,D) , (B,E) , (C,D) , (C,E) (D,E) の 10 通りで、そのうち X, Y で順位が入れ替わっていないものは (A,B) の1組なので K=1 そのほかは入れ替わっているので J = -9 よって tau = -0.8 となる。 # AP correlation( average precision correlation) $$tau_{ap} = \frac{2}{N-1} \sum_{i=2}^N(\frac{C(i)}{i-1}) -1$$ ここで $C(i) $ は X のランクが i のアイテム1 と X のランクが i より上(つまり番号としては小さい) アイテム2 に対して、Y のランクにおける順位の大小が同じもののペア数
GCP のアカウント制御について
GCP のアカウント制御について # 概要 GCP のアカウント制御がどんな形で行われるのかを調査したところ IAM が重要な概念ということがわかってきた。なので IAM を中心として述べる。 # GCP の IAM の概要 Identity and Access Management の概略を以下に基づいて述べる。 (https://cloud.google.com/storage/docs/access-control/iam?hl=ja&_ga=2.134845235.-393943616.1529019540&_gac=1.11862784.1529049583.EAIaIQobChMIy6GwvZnV2wIVVQwrCh17AgL9EAAYASAAEgLRJvD_BwE) IAM ポリシーを設定することで、ユーザーがその特定のバケットとその中のオブジェクトに対して行うことができる操作を制限できる。 ## IAMポリシーの基本概念は「メンバー」と「役割」の二つ IAMポリシーの「メンバー」として「個々のユーザー」、「グループ」、「ドメイン」を設定できる。 この「メンバー」に対して「役割」を割り当てることでIAMポリシーを設定することになる。 「役割」は、1 つ以上の「権限」をまとめたもの。「権限」は IAM の基本単位。 「権限」の具体例としては 「storage.objects.create」(オブジェクトの作成権限) があり、この「権限」は「roles/storage.objectCreator」や「roles/storage.objectAdmin」といった「役割」に含まれる。 具体的な「メンバー」「役割」については gcp コンソールの「IAMと役割」のページで確認できる。 ## 「メンバー」について 「メンバー」がどういったものになるかわかりにくいので具体例を書く。 例えば gsutil でバケットへの書き込み権限を得たい時、 https://ietoa.blogspot.com/2018/07/gcp-google-cloud-platform.html の記事のように gcloud auth login とコマンドを打つ。 登録メールが「my_test111@yahoo.co.jp」といった場合, この場合、ブラウザから「my_test111@yahoo.co.jp」としての登録許可を求めてくる。 つまり、この操作は「メンバー」として「my_test111@yahoo.co.jp」と認識して良いかの許可を求めていることになる。 なのでこの後は「メンバー」が「my_test111@yahoo.co.jp」としての権限で操作できるようになるということ。 ## 「役割」について 「役割」は資源に対するアクセス権を表す。 ## サービスアカウントについて IAM と同時に GCP のアカウント制御を考える上でサービスアカウントの概念は重要である。 https://cloud.google.com/docs/authentication/ 上記によれば 「サービス アカウントとは、エンドユーザーを表すのではなく、アプリケーションを表す Google アカウントです。」ということ。。まあよくわからないですが、「メンバー」としても「役割」としても機能します。 デフォルトのサービスアカウントは以下のサイトにあるように決まった名前で作られる。 https://qiita.com/NagaokaKenichi/items/02e723511d244c82bd82 例えば 111078970062-compute@developer.gserviceaccount.com といった名前になる。 注意: 上記のはデタラメな数字になってます。 # 具体例 ## gcloud コマンドにおける IAM 以下のコマンドでVMインスタンスを作成する場合を考える。 ``` gcloud --verbosity debug compute --project "watashi_no_project" \ instances create "my-test-instance2" --zone "us-central1-a"\ --custom-cpu 2 --custom-memory 52 --custom-extensions \ --maintenance-policy=TERMINATE --accelerator=type=nvidia-tesla-k80,count=1 \ --image=ubuntu-1604-xenial-v20180612 --image-project=ubuntu-os-cloud --boot-disk-size=40GB ``` (https://qiita.com/NagaokaKenichi/items/02e723511d244c82bd82) 上記のサイトの説明によれば、個人の googleアカウント(例: my_test111@yahoo.co.jp)が「メンバー」となり、「役割」としてのサービスアカウント(例:111078970062-compute@developer.gserviceaccount.com) を取得し、 このサービスアカウントが「メンバー」として「VMインスタンス作成」の「役割」を持っているので実行できるといった感じになる。 実際、以下のようにコマンドを実行するとアカウントは my_test111@yahoo.co.jp であることが確認できる。 ``` bash-3.2$ gcloud auth list Credentialed Accounts ACTIVE ACCOUNT * my_test111@yahoo.co.jp To set the active account, run: $ gcloud config set account `ACCOUNT` ``` また作られたVMインスタンスをチェックし、サービスアカウントをみて見るとデフォルトのサービスアカウントである 111078970062-compute@developer.gserviceaccount.com となっていることが確認できる。 ## 「メンバー」として特定のサービスアカウントを使う方法 以下のページが詳しい。 http://kikumoto.hatenablog.com/entry/2015/10/05/203545 # その他 ## GCP においてのアクセス制御の他の方法 ACL( access control list ) というものがあったが、 今は IAM で設定することが推奨されている。(ACL と併用できるが、基本的に ACL はレガシー扱い) http://iga-ninja.hatenablog.com/entry/2017/10/23/195630
2018年7月22日日曜日
TFRecordフォーマット
# 目的 tensorflow を並列に使う方法として google cloud dataproc, google cloud dataflow や google cloud machine learning があるが、そういったサービスを受ける際に、gcs にデータを TFRecode フォーマットで置いたほうが良いという話がある。 その方が転送速度などの点では有利だという話がある。 とりあえず試してみた。 # csv から TFRecord フォーマットのファイルを作り、読み込む。 以下はコード tf_record.py ``` #coding:utf-8 import tensorflow as tf import numpy as np # csv から tfrecord フォーマットファイルへの書き込み. def write_test( fpath, result_fpath): with tf.python_io.TFRecordWriter( result_fpath ) as writer: with open( fpath, "r" ) as f: f.readline() # 読み捨て. for line in f: tmp_list = line.strip().split(",") tmp_f = map( int, tmp_list ) # generator のままで ok. example = tf.train.Example() example.features.feature["my_data"].int64_list.value.extend(tmp_f) writer.write( example.SerializeToString() ) return # tfrecord からの読み込み def load_test( fpath ): for i,record in enumerate(tf.python_io.tf_record_iterator(fpath)): example = tf.train.Example() example.ParseFromString(record) # バイナリデータからの読み込み tmp = example.features.feature["my_data"] a = tmp.int64_list.value print(i,a) fpath = "sample.csv" result_fpath = "test.tfrecords" write_test( fpath, result_fpath) load_test( result_fpath ) ``` tf.train.Example クラスのインスタンスに対して、 csv ファイルから一行づつ取り出しsplit したものを "my_data" として登録している。この際、 lint64_list.value に extent として突っ込んでいる。 このようにデータの型を明確にする必要がある。(なので文字列は使えないかもしれない。) 読み込む方法は tf.python_io.tf_record_iterator でレコード毎に取り出し、 int64_list.value として読み込み側で型を指定して取り出している。 データは次のもの sample.csv ``` a,b,c,d 1,2,3,4 9,10,11,13 9,7,11,3 ``` # 実行結果 ``` 0 [1, 2, 3, 4] 1 [9, 10, 11, 13] 2 [9, 7, 11, 3] ``` # 課題 このエンコード方法がどの程度有効なのか今の所、よくわからない。現状は実際の機械学習に乗せる感覚が掴めてないのでそこを考える必要がある。
python unittest について
python unittest について # 目的 メンテしやすいモジュール作成のためにテストを考えてみた。 特にエラー処理のテストなど、どう書くかが結構手間なものについて実装しておきたいと思った。 # ディレクトリ構成 . ├── data_make_tools │ └── train_data_make.py └── tests ├ ── test_train_data_make.py └── data └── weather └── test1.csv # コード 以下のようなファイルからデータを読み込んで辞書を返すようなコードを考える。 data_make_tools/train_data_make.py ``` def get_ym_to_city_to_weather_data( fpath ): ym_to_city_to_weather_data = OrderedDict() with open( fpath, "r") as f: f.readline() # ヘッダ読み捨て. for i,line in enumerate(f): try: year, month, city, \ cloudness, daylight_hours, precipitation_sum, \ temperature_max, temperature_min, temperature \ = line.strip().split(",") ym = YearMonth( int(year),int(month) ) if ym not in ym_to_city_to_weather_data: ym_to_city_to_weather_data[ym] = OrderedDict() ym_to_city_to_weather_data[ym][city] \ = WeatherData( cloudness, daylight_hours, precipitation_sum, temperature_max, temperature_min, temperature ) except : print( traceback.format_exc() ) print( "data file: " + fpath + ", line: %d"%( i+1 ) ) print( "data error line: " + line ) exit(1) return ym_to_city_to_weather_data ``` エラーが生じたときにちゃんとメッセージを出力しているのかのテストを書きたいのだが、 このままでは難しい。 # コード変更 logging モジュールを使って以下のようにコードを変更 data_make_tools/train_data_make.py ``` import logging THIS_MODULE_LOGGER = logging.getLogger(__name__) def get_ym_to_city_to_weather_data( fpath ): ym_to_city_to_weather_data = OrderedDict() with open( fpath, "r") as f: f.readline() # ヘッダ読み捨て. for i,line in enumerate(f): try: year, month, city, \ cloudness, daylight_hours, precipitation_sum, \ temperature_max, temperature_min, temperature \ = line.strip().split(",") ym = YearMonth( int(year),int(month) ) if ym not in ym_to_city_to_weather_data: ym_to_city_to_weather_data[ym] = OrderedDict() ym_to_city_to_weather_data[ym][city] \ = WeatherData( cloudness, daylight_hours, precipitation_sum, temperature_max, temperature_min, temperature ) except : THIS_MODULE_LOGGER.error( traceback.format_exc() ) THIS_MODULE_LOGGER.error( "data file: " + fpath + ", line: %d"%( i+1 ) ) THIS_MODULE_LOGGER.error( "data error line: " + line ) exit(1) return ym_to_city_weather_data ``` このようにモジュール毎にログ管理をするように変更する。 # テストコード tests/test_train_data_make.py ``` import os from unittest import TestCase import logging from io import StringIO from data_make_tools.train_data_make import get_ym_to_city_to_weather_data LOGGER = logging.getLogger("data_make_tools.train_data_make") class Test_get_ym_to_city_to_weather_data(TestCase): data_dir = os.path.join( DATA_DIR, "weather") def setUp(self): self.stream = StringIO() self.handler = logging.StreamHandler( self.stream ) LOGGER.addHandler( self.handler ) def tearDown(self): LOGGER.removeHandler( self.handler ) self.handler.close() def test_weather_1(self): fpath = os.path.join( self.data_dir, "test1.csv" ) self.assertRaises( SystemExit, lambda: get_ym_to_city_to_weather_data( fpath ) ) self.assertIn( "not enough values to unpack (expected 9, got 8)", self.stream.getvalue() ) ``` # テストコード説明 以下でログを流し込むオブジェクトを確保し、 data_make_tools.train_data_make の logger に登録. これはテスト実行前に必要なので setUp で行う。 ``` self.stream = StringIO() self.handler = logging.StreamHandler( self.stream ) LOGGER.addHandler( self.handler ) ``` 以下でテストする関数の実行と想定している例外が上がっているかをチェック ``` self.assertRaises( SystemExit, lambda: get_ym_to_city_to_weather_data( fpath ) ) ``` 以下で想定している文字列が出力されているかチェック. ``` self.assertIn( "not enough values to unpack (expected 9, got 8)", self.stream.getvalue() ) ``` 実行が終わったら tearDown によりオブジェクトを取り除き、元の状態にする。 ``` LOGGER.removeHandler( self.handler ) self.handler.close() ``` # テスト実行 ディレクトリのトップで以下のコマンドを実行. ``` python -m unittest discover tests ``` 上記は tests ディレクトリにある unittest のテストを全て見つけて、実行するというコマンドになっている。 python の unittest のカバレッジをとる. # カバレッジ カバレッジを見ることでテスト書くモチベーションになるんではないかと思って やってみた。 以下のテストについてカバレッジを計測したい。ディレクトリ構造なども以下と同様。 ## ツール準備 以下で coverage モジュールをインストール. ``` pip install coverage ``` ## coverage を使うために coverage を使うために以下のコードを用意. test.py ``` import unittest def exec_test_all(): all_tests = unittest.TestLoader().discover( "tests", "test_*.py") runner = unittest.TextTestRunner() runner.run( all_tests ) if __name__ == "__main__": exec_test_all() ``` 上記のコードは tests ディレクトリ以下の test_ で始まる python コードをテストコードとして 取って来るというコード. ## 実行方法 以下のコマンドでテスト実行. ``` coverage run test.py ``` 実行するとカレントディレクトリに .coverage というファイルが作られる。 (このファイルに実行結果が書き込まれる。) 以下のコマンドでカバレッジを可視化した html ファイルを生成. ``` coverage html --include=data_make_tools/* ``` --include=data_make_tools/* は data_make_tools 以下のコードに対してのみ カバレッジ結果を生成するという意味。 (こうしないとテストスクリプトの方のカバレッジも含んでしまう。) 実行すると htmlcov というディレクトリに html の表示のためのファイルが生成される。 htmlcov/index.html をブラウザで開いて結果を見る。
GCPをとりあえず使ってみた
# 0. 前準備 GCP のコンソールを使用したり gcloud コマンドを使うための準備が必要となる。 https://ietoa.blogspot.com/2018/07/gcp-google-cloud-platform.html にインストール方法などを書いた。 # 1. google cloud platform の「コンソール」という画面に移動 URL: https://console.cloud.google.com/home 「Compute Engine」->「VMインスタンス」と進む。 # 2. インスタンスを作る. 「インスタンスを作成」をクリック 以下の項目を設定 - 名前 : (なんでも良いがとりあえず watashi-gpu1 とする。) - ゾーン: us-central1-a - マシンタイプ: 「vCPU x 2」を選択して、「カスタマイズ」を選んで以下を設定. 「メモリを拡張する」にチェックを入れて、 - メモリ: 32 GB - CPUプラットフォーム: 自動 - GPU: - GPU の数 : 1 - GPU のタイプ : NVIDIA Tesla K80 - ブートディスク: 「変更」を選び、 - OSイメージ: Ubuntu 16.04 LTS - ブートディスクの種類: 標準の永続ディスク - サイズ : 40GB - ファイアウォール: 設定しない。 作るとすぐ起動した状態になる。使わない場合は「停止」すること。 # 3. 接続 「接続」の項目の右に下矢印があるのでそれをクリックし、 「gcloud コマンドを表示」をクリックすると以下のようなコマンド例が表示される。 ``` gcloud compute --project "プロジェクト名" ssh --zone "us-central1-a" "watashi-gpu1" ``` 上記を打ち込めば ssh として接続できる. 一回目は ssh の key の設定などをするが二回目はすんなり入れる。 ssh の鍵は ~/.ssh に ``` google_compute_engine google_compute_engine.pub google_compute_known_hosts ``` として登録される. # 4. ファイル転送 gcloud compute scp [LOCAL_FILE_PATH] [INSTANCE_NAME]:\~/ のフォーマットと書かれているが、 --project と --zone の指定が必要( gcloud コマンドで project, zone を設定すれば --project , --zone の記述はいらなくなるらしい。) ``` gcloud compute --project "プロジェクト名" scp --zone "us-central1-a" memo.txt watashi-gpu1:~/ ``` 上記のコマンドでローカルにある memo.txt ファイルをクラウド上の ~/ ディレクトリに転送することができる。 - 料金感覚 (GB 単位) 月間使用量 ネットワーク(下り)送信先: 世界 ネットワーク(下り)送信先: 中国 (香港を除く) ネットワーク(下り)送信先: オーストラリア ネットワーク(上り) (香港以外の中国とオーストラリアを除く) 0~1 TB $0.12 $0.23 $0.19 無料 1~10 TB $0.11 $0.22 $0.18 無料 10 TB~ $0.08 $0.20 $0.15 無料 アップは基本無料。 ダウンロードは 10 GB くらい落とすと 1.2ドルで 150 円くらいか. # 5. マシンの停止 ``` gcloud compute --project "プロジェクト名" ssh --zone "us-central1-a" "watashi-gpu1" ``` でマシンに入った状態で ``` sudo poweroff ``` と打ち込むと停止する。停止まで1,2分くらい時間がかかるので注意。 # 6. マシンの再起動 ``` gcloud compute --project "プロジェクト名" instances start --zone "us-central1-a" "watashi-gpu1" ``` 起動に 1,2 分かかる。 # 7. 自動実行停止 gcp にて計算が終わったら自動で停止させるには以下のようなスクリプトを用意する。 ``` run.sh python3 net_test.py >& log.txt sudo poweroff ``` このスクリプト run.sh に対して ``` $bash run.sh & ``` としてバックグラウンド実行すれば exit で ssh 接続を切っても走り続けて、計算が終われば自動で gcp のマシンを停止してくれるので経済的にも安心。 -
2018/4/12 修正
上記の方法だと回しているプログラムの種類によってはカーネルに殺されることもあるらしい。https://www.qoosky.io/techs/9dd96483ab (ssh を抜け出してもも大丈夫なことは何回か確認はしているが、念のため screen での動作を推奨したい。) なので以下のようなコマンドを打つ。 ``` screen -dm -S qoosky bash -c 'bash run.sh' ``` -S qoosky の部分はなんでも良い。 -c ' ' の ' ' の中に実行したいコマンド内容を書くこと。( -dm はデタッチメント(バックグラウンドジョブとして)で動かすということ。) ちゃんとスクリーンで動いているかは ``` screen -ls ``` とすることで確認可能。もしくは上記のスクリプトの場合は ``` cat log.txt ``` などとして出力がなされていることを確認すると良い。 screen で動いているプロセスを途中で止めたくなった場合には ``` screen -S qoosky -X quit ``` とすることで止めることができる。 -S qoosky の部分は動作させた時と同じものを指定する。 止まったかどうかは screen -ls で確認すると良い。
luigi をデータハンドリングに使ってみた
# 目的 データ分析をしていると、色々なデータを join( merge ) したり、 フィルターかけたり、一部のカラムだけ取ってきたりといった作業が多くなる。 そういった作業の末に作り上げたデータなどを作り直したり、また一部の工程だけを 違ったやり方にして実行するといったことを頼まれた時に四苦八苦してしまうことが多い。 luigi はそういった工程管理をスクリプト化することで管理しやすくする。 (少なくとも,そういった作業を簡易にしたいと思って開発されたものである。) # 他のツールなど 同じような目的のツールはビルドのためのツールが真っ先に思い浮かぶ。 make コマンドや python の scons など。 make はかなり汎用といえば汎用だが記述がシェルスクリプト前提であるため少し辛い。 scons は python で記述できるので競合たり得るかもしれない。 違いとしてはネットワーク越しの生成物のチェックや hadoop などの他ツールとの統合性が良さげらしい。 (あんまりピンとこないのでまたおいおい調べる必要があるだろう。。) 他にも色々あるかもだが著者の調査不足により、この議論はこれまでにとどめる。 # ツールインストール python のライブラリで, pip install luigi とすればインストールできる。 # ツールの簡単な使い方 以下は train_cust_item.csv, test_cust_item.csv という (キャストID,アイテムID) がレコードであるような csv ファイルから 正例、負例のレコードを作成し、必要なカラムを取ってきてジョインし、 学習用データと確認用データである train_data.csv, test_data.csv を作る工程を luigi で書いたものである。 (実際には train_cust_item.csv, test_cust_item.csv を作る部分も luigi でコード化しているのだが 説明には長すぎるのでそこはカット.) make_minus_data, make_fill_data, my_catenate は別途実装が必要. またかなりベタに書いてるのでもっと綺麗に書く方法があると思われる。 luigi_sample.py ``` class TestTrainSplit(luigi.Task): def output(self): return { "train_fpath" : luigi.LocalTarget("train_cust_item.csv"), "test_fpath" : luigi.LocalTarget("test_cust_item.csv") } class TestDataPlus(luigi.Task): def requires(self): return TestTrainSplit() def run(self): print("TestDataPlus") def output(self): return luigi.LocalTarget("test_cust_item.csv") class TrainDataPlus(luigi.Task): def requires(self): return TestTrainSplit() def run(self): print("TrainDataPlus") def output(self): return luigi.LocalTarget("train_cust_item.csv") class TestDataMinus(luigi.Task): def requires(self): return {"item_fpath": MakeItemData(), "cust_item_fpath": TestDataPlus() } def run(self): make_minus_data( self.input()["item_fpath"].path, self.input()["cust_item_fpath"].path, self.output().path ) def output(self): return luigi.LocalTarget("test_cust_item_minus.csv") class TrainDataMinus(luigi.Task): def requires(self): return {"item_fpath": MakeItemData(), "cust_item_fpath": TrainDataPlus() } def run(self): make_minus_data( self.input()["item_fpath"].path, self.input()["cust_item_fpath"].path, self.output().path ) def output(self): return luigi.LocalTarget("train_cust_item_minus.csv") class TestDataFill(luigi.Task): def requires(self): return {"test_data_plus_path": TestDataPlus(), "test_data_minus_path": TestDataMinus(), "user_data_path": MakeUserData(), "item_data_path": MakeItemData() } def run(self): make_fill_data( self.input()["user_data_path"].path, self.input()["item_data_path"].path, self.input()["test_data_plus_path"].path, self.output()["plus_out_path"].path, self.input()["test_data_minus_path"].path, self.output()["minus_out_path"].path ) def output(self): return {"plus_out_path":luigi.LocalTarget("test_cust_item_fill.csv"), "minus_out_path": luigi.LocalTarget("test_cust_item_minus_fill.csv") } class TrainDataFill(luigi.Task): def requires(self): return {"train_data_plus_path": TrainDataPlus(), "train_data_minus_path": TrainDataMinus(), "user_data_path": MakeUserData(), "item_data_path": MakeItemData() } def run(self): make_fill_data( self.input()["user_data_path"].path, self.input()["item_data_path"].path, self.input()["train_data_plus_path"].path, self.output()["plus_out_path"].path, self.input()["train_data_minus_path"].path, self.output()["minus_out_path"].path ) def output(self): return {"plus_out_path":luigi.LocalTarget("train_cust_item_fill.csv"), "minus_out_path": luigi.LocalTarget("train_cust_item_minus_fill.csv") } class MakeTestData(luigi.Task): def requires(self): return TestDataFill() def run(self): fpath_list = [ self.input()[input].path for input in self.input() ] my_catenate( fpath_list, self.output().path ) def output(self): return luigi.LocalTarget("test_data.csv") class MakeTrainData(luigi.Task): def requires(self): return TrainDataFill() def run(self): fpath_list = [ self.input()[input].path for input in self.input() ] my_catenate( fpath_list, self.output().path ) def output(self): return luigi.LocalTarget("tain_data.csv") class MakeTrainTestData(luigi.Task): def requires(self): return [ MakeTrainData(), MakeTestData() ] def run(self): print("MakeTrainTestData ok") ``` # コードの説明 通常、作業内容は「class MakeTrainTestData(luigi.Task)」のようにクラスで定義していく。 このクラスは三つのメソッドから成り立つ。 requires は、このタスクを開始する前に終わっておくべきタスクを指定する。 output はこのタスクの終了でどういったものが生成されるかを記述する。 私は細かい使い方がわからなかったので全てファイルを生成物として指定する使い方だけしている。 # 使い方 以下のコマンドで luigi_sample.py にある class MakeTrainData の output で指定されている "train_data.csv" が生成されるまでの作業が全て実行される。 ``` PYTHONPATH="." luigi --local-scheduler --module luigi_sample MakeTrainData ``` ただし、依存途中で指定されているファイルがある場合は、そのファイルの生成を行う class の run メソッドは実行されない。 もしくは 他のタームで $ luigid とすると localhost:8082 で luigi のデーモンが動く. すると --local-scheduler の部分を省略しても実行できる。 ``` $ PYTHONPATH="." luigi --module luigi_ver5 MakeTrainTestData ``` これはデーモンに処理を投げるようになるということである。 このように実行すると実行の詳細な情報をブラウザで確認できる。
GCP (google cloud platform)を使うまで
# 概要 GCP を使うことになったのでコマンドの導入などをメモ。 環境は mac によって行った。 # google cloud sdk のインストール gcloud、gsutil、bq コマンドを使えるようにするために google cloud sdk をインストールする. 以下のリンクから google-cloud-sdk-180.0.0-darwin-x86_64.tar.gz をダウンロードして解凍する。 https://cloud.google.com/sdk/?hl=ja $./google-cloud-sdk/install.sh としても python が 2.7 でないとインストールエラーとなる。 なので anaconda ならば以下のようなコマンドを打って、python2.7 の環境を作る。 $conda create -n py27 python=2.7 anaconda python2.7 環境で実行したい場合には以下のようにコマンドをうつ。 $source activate py27 するとプロンプトが以下のような感じになる。 (py27) bash-3.2$ これで $./google-cloud-sdk/install.sh でインストール可能となる。 インストールが済めば別に python2.7 でなくても良いので上記の環境を抜けて良い。 そのあと、どう使うかよくわからなかったが、要は PATH が通って入れば良いので ~/.bashrc に export PATH=$PATH:/Users/watashi/Desktop/GCP/google-cloud-sdk/bin といった記述を追加して( watashi はmac 上のユーザー名。) google-cloud-sdk/bin にパスを通す. パスが通っているかは $which bq などとして確認すること。 # アカウントの設定 GCP を使うにはクラウドにログインするアカウントを設定する必要がある。 以下のようにメアドでアカウントを設定. $gcloud config set account [メールアドレス] 以下のコマンドを打つと、ブラウザが開き、認証設定をするためのページになるので「許可する」とかをクリックしていけば ok. $gcloud auth login # アカウント設定完了の確認 以下を打つとテーブルの情報が見れる。 $bq show プロジェクト名:テーブル名 # python からのアクセス - 以下でまずはライブラリをインストール。 $pip install pandas-gbq - 以下をブラウザで開く。 https://console.developers.google.com/apis/dashboard 「ライブラリ」のところから辿っていき「BigQuery API」をクリックする(結構下の方にある)。 「BigQuery API」の画面になるので「有効にする」をクリックすればよし。 - 次に以下をブラウザで開く。 https://console.cloud.google.com/home/dashboard 「IAMと管理」のメニューから「サービスアカウント」を選択. 「キーを作成」で「json」を選ぶ。 するとキーが生成され、その情報が「392t2taastw0.json」といったファイルとしてダウンロードされる。 このファイルのあるディレクトリにおいて以下のコードを実行すれば普通の pandas のように実行できる。 ``` import pandas query = 'SELECT * FROM [プロジェクト名:テーブル名.ファイル名]' project_id = 'プロジェクト名' private_key = '上記で取得した jsonファイル名' data_frame = pandas.read_gbq(query, project_id=project_id, private_key=private_key) print(data_frame) ``` private_key のところはフルパスを設定してもいけるので jupyter notebook からの実行のようにカレントがわかりずらい場合にはそうした方が良いかもしれない。 # 料金 以下を見てみると「クエリ: $5/TB (スキャンしたデータのサイズで課金)」とある。 https://qiita.com/kaiinui/items/3ae3a335a2826fc7070a 返すデータのサイズではない。カラム指向なので select * とはせずに返す列を指定した方が良いとのこと。 https://www.slideshare.net/dragan10/20150520-gcpug-osaka-big-query またインデックスは基本的に考えなくて良いらしい。 # 注意 bq コマンドでファイルをアップする際には各カラムの型について予測していい感じで登録してくれる。 ただし、 20170912 のような日付の数値も integer となるのでそこは各自良い感じで対応すること。
xlsxwriter でエクセル資料作り
# 目的 レコメンドの結果としてレコメンドアイテムの写真を貼り付けた資料などを作ることがある。その際に python の xlsxwriter モジュールが使えるかなということで紹介。 # コード説明 説明は以下のコードの下に記載。 ``` #coding:utf-8 import os # need pip install. import xlsxwriter # PIL install command ( $pip install pillow ). import PIL.Image as img def get_image_size( image_path ): image_data = img.open( image_path ) size = image_data.size image_data.close() return size def excel_write(): result_fpath = "test.xlsx" sheet_name = "item_seet" wb = xlsxwriter.Workbook( result_fpath ) ws = wb.add_worksheet( sheet_name ) # vba と違ってインデックスは 0 始まりなことに注意せよ. ws.set_column( 0, 11, 28 ) # 0 列目から 11 列目までの横幅を 28.0 に設定 x_cell_len, y_cell_len = 200, 200 row_cell_num = 12 # 画像の分 + その画像の説明文字列の分を行としてとる。 col_offset = 1 for cust_i, mem_id in enumerate(mem_id_list): # 会員番号がそのままディレクトリ名になっている。 pic_dir = os.path.join( data_dir, mem_id ) row = cust_i * row_cell_num ws.write( row, 0, mem_id ) image_row = row + 1 for item_j, fpath in enumerate(os.listdir(pic_dir)): image_path = os.path.join( pic_dir, fpath ) x_image, y_image = get_image_size( image_path ) x_scale, y_scale \ = float(x_cell_len)/x_image, float(y_cell_len)/y_image col = item_j + col_offset ws.write( row, col, fpath ) ws.insert_image( image_row, col, image_path, {'x_scale': x_scale, 'y_scale': y_scale } ) wb.close() ``` まず出力するエクセルファイルを wb = xlsxwriter.Workbook( result_fpath ) で作成. 書き込むエクセルシートをws = wb.add_worksheet( sheet_name ) で追加。 ws.write( row, col, s ) : ws シートの行 row, 列 col に文字列 s を書き込む( row, col は vba と違って 0 始まりなので注意せよ。 ws.insert_image( row, col, image_path, {'x_scale': x_scale, 'y_scale': y_scale } ) : ws シートの行 row, 列 col を左上として image_path にある画像ファイルを貼り付ける。 x_scale, y_scale はそれぞれ x, y 方向に元の画像の何倍にするかを設定できる。 最後に wb.close() でブックを閉じるとファイルの書き込みが確定する。 # 課題 もともとあるエクセルファイルを開いて編集などは難しいのではないかと思う。
python の最適化ツール ortools について
# 最適化問題設定 各ユーザーにアイテムを勧めたい。条件として各ユーザーには 4 つのアイテムを勧める。各ユーザーがそのアイテムを好むかどうかはスコアとして数値化されており、選んだアイテムのスコアの合計をできるだけ大きくしたい。 ただし、アイテムはカテゴリー毎に分類されており、一つ目に勧めるアイテムが属するカテゴリー、二つ目に勧めるアイテムが属するカテゴリー、. . . 四つ目に勧めるアイテムが属するカテゴリーは決められている。 また各カテゴリーの各価格帯に対してはユーザーに勧める個数の割合が決まっており、その割合から一定の範囲に収めなければならない。 # 数式による定式化 $u = 1, \dots, U$ をユーザーを表す添字 $n = 1, \dots, N$ をレコメンドの順番を表す添字(上記の問題設定では $N=4$ ) $c = 1, \dots, C$ をカテゴリーを表す添字 $p = 1, \dots, P$ を価格帯を表す添字 とする。 - 変数 $v_{u,n,c,p} \in \{ 0, 1 \}$ 上記は0-1整数変数を意味している。 変数の意味は $v_{u,n,c,p} $ が 1 の時は、「ユーザー u に対する n 番目のレコメンドでカテゴリー c の価格帯 p に属するアイテムを選ぶ」ということである。 - 目的関数 $s_{u,c,p} $ によってユーザー u のカテゴリー c、価格帯 p に属するアイテムを選んだ際のスコアを表す。この最適化問題においては定数として取り扱う。 目的関数はスコア合計ということで $\sum_{u,n,c,p}{s_{u,c,p}\cdot v_{u,n,c,p}}$ とする。 (実際にレコメンドをする人の感覚にあっているかどうかは難しい問題なのでここでは問わない。業務では重要だが。線形でないと整数計画問題はなかなか解けないという理由もある。) - 制約 二種類の制約がある。 - 一度に勧められるアイテムは一つだけ $\sum_{c,p}v_{u,n,c,p} = 1, \forall u \forall n$ - 出現割合をある一定の範囲にする $r_{c,p} \in [0,1]$ をカテゴリー c、価格帯 p の希望出現割合を表す定数とする。 $\rho \in [0,1]$ を許容割合を表す定数とする。 $ (1-\rho)\cdot r_{c,p}\cdot U\cdot N \le \sum_{u,n}v_{u,n,c,p} \le (1+\rho)\cdot r_{c,p}\cdot U\cdot N, \forall c \forall p$ r が割合なのでレコメンド総数を表す $U\cdot N $ を掛けている。 - まとめ Maximize $\sum_{u,n,c,p}{s_{u,c,p}\cdot v_{u,n,c,p}}$ such that $\sum_{c,p}v_{u,n,c,p} = 1, \forall u \forall n$ $ (1-\rho)\cdot r_{c,p}\cdot U\cdot N \le \sum_{u,n}v_{u,n,c,p} \le (1+\rho)\cdot r_{c,p}\cdot U\cdot N, \forall c \forall p$ # ortools による記述 - 最適化を解いてくれるソルバーを生成 ソルバーの生成 ``` import ortools # ソルバー生成 from ortools.linear_solver import pywraplp solver = pywraplp.Solver('Select', pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING) ``` - 変数を設定 先ほど生成した solver のメソッドを使って生成する。引数の 0, 1 はそれぞれ変数の下限、上限を表す、三つ目の引数はデバッグ用に変数につける名前。 variables という変数に保存しているがこれは後々制約式、目的関数を作るのに参照が必要になるからである。 変数設定 ``` variables = OrderedDict() # 変数を設定 for u in u_c_p_to_score: variables[u] = OrderedDict() for n in n_to_cats: variables[u][n] = OrderedDict() for c in n_to_cats[n]: variables[u][n][c] = OrderedDict() for p in u_c_p_to_score[u][c]: variables[u][n][c][p] = solver.IntVar(0, 1, 'intVar(%s,%d,%d,%d)'%(u,n,c,p) ) ``` - 目的関数を設定 u_c_p_to_score は $s_{u,c,p}$ を表す辞書。 n_to_cats は n 回目に選ぶカテゴリーのリストを表す辞書。 solver.Maximaize で最大化する式を設定する。 目的関数設定 ``` obj_terms = [] for u in u_c_p_to_score: for n in n_to_cats: for c in n_to_cats[n]: for p in u_c_p_to_score[u][c]: s = u_c_p_to_score[u][c][p] v = variables[u][n][c][p] obj_terms.append( s * v ) # solver.Sum が list の要素を足し合わせた式を返す。 obj = solver.Sum( obj_terms ) solver.Maximize( obj ) ``` - 制約設定1 不等式 cons を作るだけでは制約は設定されない。 solver.Add( cons ) とすることで問題の制約が設定される。 一つだけ選ぶ制約の設定 ``` for u in variables: for n in variables[u]: select_cons_terms = [] for c in variables[u][n]: for p in variables[u][n][c]: v = variables[u][n][c][p] select_cons_terms.append(v) cons = solver.Sum( select_cons_terms ) == 1 solver.Add( cons ) ``` - 制約設定2 c_p_w は $r_{c,p}$ rho は $\rho$ r_num は $U\cdot N $ 希望出現割合の制約の設定 ``` for c in c_p_w: for p in c_p_w[c]: rate = c_p_w[c][p] cons_terms = [] for u in variables: for n in variables[u]: if c not in variables[u][n]: # ユーザーが選べないカテゴリーは飛ばす。 continue if p not in variables[u][n][c]: # ユーザーが選べない価格帯は飛ばす。 continue cons_terms.append( variables[u][n][c][p] ) z = solver.Sum(cons_terms) lb = math.floor( (1-rho) * rate * r_num ) ub = math.ceil( (1+rho) * rate * r_num ) cons_lb = z >= lb cons_ub = z <= ub solver.Add( cons_ub ) solver.Add( cons_lb ) ``` - 求解と解出力 sover.Solve() で解く。 v.SolutionValue() で解いた答えの値を得る。 求解と解出力 ``` solver.Solve() for u in variabels: for n in variables[u]: for c in variables[u][n]: for p in variables[u][u][c]: v = variables[u][n][c][p] vv = int(v.SolutionValue()) if vv == 1: print( u,n,c,p ) ```
pyOpt をmac上のdockerにインストールする
# 目的 最適化部分について pyopt が必要になったが mac だとインストールできるかわからない。 (pyopt のページを見ても mac については記述なしで、そもそもlinux でもインストールがめんどいのにさらに苦労するだろうことが予想される。) # どうしたか 結局 docker に ubuntu を入れて、インストールした. # docker 準備 docker のインストールに関しては下記を参照。 https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81 # 実行したこと まずdocker でコンテナを作成する。 ``` docker run -p 5901:5901 -it --name ubuntu_for_pyopt ubuntu:16.04 /bin/bash ``` 上記のコマンドを打つとコンテナが作られ、開始して、すでにコンテナの中にいる状態となる。以降は pyopt のインストールのためのコマンド。 ``` apt-get update apt-get install python3-pip apt-get install git pip3 install --upgrade pip # gfortran のインストール. pyOpt のインストールに必要. apt-get install gfortran # swig のインストール. pyOpt のインストールに必要. apt-get install swig # numpy がなぜか入っていない.あとなぜか pip3 は動かないので普通の pip を使う。 pip install numpy git clone https://github.com/madebr/pyOpt.git madebr_pyOpt cd madebr_pyOpt python3 setup.py install ``` アップデートなどここまで 5~10 分くらいかかる。 # 確認 ひとまず exit で出る. 以下のファイルを用意し、コンテナに送る。 tmp.py ``` #coding:utf-8 import pyOpt import numpy as np def objfunc(x): f = -np.prod(x) g = np.zeros(2) g[0] = x[0] + 2.0 * x[1] + 2.0 * x[2] - 72.0 g[1] = -x[0] - 2.0 * x[1] - 2.0 * x[2] fail = 0 return f, g, fail def main(): opt_prob = pyOpt.Optimization("hoge", objfunc) opt_prob.addObj("f") opt_prob.addVar("x1", "c", lower = 0.0, upper = 42.0, value = 10.0) opt_prob.addVar("x2", "c", lower = 0.0, upper = 42.0, value = 10.0) opt_prob.addVar("x3", "c", lower = 0.0, upper = 42.0, value = 10.0) opt_prob.addConGroup("g", 2, "i") print(opt_prob) slsqp = pyOpt.SLSQP() slsqp.setOption("IPRINT", -1) [fstr, xstr, inform] = slsqp(opt_prob, sens_type = "FD") print(opt_prob.solution(0)) print("hello,pyopt") main() ``` コマンドは次の通り ``` docker cp tmp.py 340d9c43bb23:/ ``` ちなみにコンテナから逆にファイルをコピーするときは以下のようなコマンド ``` docker cp 340d9c43bb23:/tmp.py . ``` ここで 340d9c43bb23 は作ったコンテナの ID. コンテナのIDは以下のコマンドで確認する。NAMES のところが ubuntu_for_pyopt となっている行を探せば良い。 ``` docker ps -a ``` ちなみに docker ps だと動いているコンテナ、 docker ps -a だと動いていないコンテナも含めて全て表示される。 以下で python 実行 ``` docker exec -it ubuntu_for_pyopt python3 tmp.py ``` 「docker exec -it ubuntu_for_pyopt /bin/bash」でコンテナに入ってから実行でも良いが、mac から直接呼び出すことが増える時には上記のコマンドのが良さげ. 使い終わったら以下のコマンドで止める。 ``` docker stop ubuntu_for_pyopt ``` 止まっているかどうかは「docker ps -a」で確認。 次に使う時には以下のコマンドでコンテナを起動する。 ``` docker restart ubuntu_for_pyopt ``` # 追記(2018/5/17) docker cp について、コンテナID の指定でなくてコンテナネームの指定でもよい。 ``` docker cp ubuntu_for_pyopt:/tmp.py . docker cp tmp.py ubuntu_for_pyopt:/ ``` 実行について以下のように環境変数を設定して実行できる。 問題が生じたときは以下のように実行する必要があるだろう。 ``` docker exec -it -e LANG=ja_JP.UTF-8 ubuntu_for_pyopt python3 tmp.py ```
pyOpt の slsqp について
# 動機 下記のような最適化問題において、python のモジュールである pyOpt の slsqp アルゴリズムを使用してみた。 - 最適化問題 予測機を使って、仕入数から販売数を予測しながら、仕入の各店への配分を最適化する。 指標としては各店において、できる限り仕入数と販売数の差分が小さくなるように最適化する。 # 定式化 - 定数 総仕入数: $total\\_num$ 店舗数: $n$ 最低仕入数: $lower$ 各店舗のデータ: $df_i \in \mathbb{R}^k$, ( $k$ はデータの次元、$i$ は各店舗を表す添字 ) - 最適化定式 - 変数 : $x_i \in \mathbb{R}, i \in \{ i: 1 \leq i \leq n \} , ( 0 \leq x_i \leq 1.0 )$ - 補助定義 各店舗の仕入数: $y_i = total\\_num*x_i$ 販売数を出力する予測機 (機械学習などで学習して得た予測機): $pred : \mathbb{R}^k \times \mathbb{R} \to \mathbb{R} $ - 目的関数: 最小化 $ \sum_i { | y_i - pred( df_i,y_i ) |^2 )} $ - 制約: $ \sum_ix_i = 1$ ( $x_i$ が比率であることを意味する制約) $ y_i >= lower $ ( 仕入数は最低仕入数以上) # 上記問題の取り扱いにおける問題 予測機 pred は通常、数式的に与えられないため微分値を得るのが困難であり、最適化しずらい。 一般にこの種の問題の取り扱い方は二つある。 1. 数値微分を使う。 2. 関数の形を推論し、その推論した関数に対して最適化を行う。 1 の方が微分情報についてより直接的なので誤差は少ないと考えられる。ただし、ハイパーパラメータチューニングなどの場合、 そもそも関数の値を得るための計算時間が莫大になるために 2 の方法が選ばれる傾向にある。python の hyperopt モジュールは 2 の方法を取っている。 pyOpt のアプローチは 1 の「数値微分」を取り扱う方である。xgboost で学習した pred の出力は比較的速いので有効と思われる。 数値微分をしているところについては本ブログの「pyOpt の微分情報について」を参照せよ。 # pyOpt のコード例 上記の定式化に対するコードを以下に掲載 ``` import numpy as np import pyOpt class FormulationForPuchaseOptimize: def __init__(self, model, df, purchase_total_num, lower, x_len ): self.model = model # 予測モデル. self.df = df # 各店舗のデータカラム. self.purchase_total_num = purchase_total_num # アイテムの全ての店舗の仕入数合計 self.lower = lower # 各店舗の最低仕入数. self.x_len = x_len slsqp = pyOpt.pySLSQP.SLSQP() # 1 にすると、multi process でやるときファイルエラーになる時がある. slsqp.setOption( "IPRINT", -1 ) self.slsqp = slsqp pass def create_formulation_prob( self ): # pyopt のインターフェイスに合わせるために下記のような関数を返すメソッドを用意した。 # (定式化に用いるパラメータを引数以外で関数に埋め込むため.) def get_func_value( x ): # x は 1 次元の np.array を想定 y = x * self.purchase_total_num # 割合が x の時の各店の仕入数. self.df[ "purchase_num" ] = pd.Series( y, index=self.df.index ) # out: 予測販売数 out = self.model.predict( self.df.values ) # ここが解析的にできない部分. f = mean_squared_error( out, y ) # 差分を目的関数とする. #f = mean_absolute_error( out, y ) # 差分を目的関数とする. # 等式、不等式制約を設定 g = np.zeros( 1 + self.x_len ) # 等式制約( == 0となるように記述する) g[ 0 ] = np.sum(x) - 1.0 # 合計=1, x が割合を表すようにするための制約. g[ 1: self.x_len+1 ] = self.lower - y # lower <= y, y は 1 以上という制約. fail = 0 return f, g, fail return get_func_value def prepare( self, seed ): # 変数初期化のための乱数シードの設定. np.random.seed( seed ) random.seed( seed ) opt_prob = pyOpt.Optimization( "purchase optimize", self.create_formulation_prob() ) # 変数の設定 x0 = np.random.rand( self.x_len ) # 0 ~ 1.0 の間の初期値をてきとうに選ぶ。 opt_prob.addVarGroup( 'x' , self.x_len, type='c', value=x0, lower=0.0, upper=1.0 ) # 販売予測値と仕入数の差分の最小化を目的関数とする。 opt_prob.addObj( "diff_pred_and_purchase" ) # sum(x) == 1 に相当する式の登録. opt_prob.addConGroup( "sum_x == 1", 1, "e" ) # lower <= y に相当する制約の登録 opt_prob.addConGroup( "lower_y", self.x_len, "i" ) return opt_prob def solve( self ,seed=1 ): # 定式化準備 opt_prob = self.prepare( seed ) [f_opt, x_opt, info] = self.slsqp( opt_prob, sens_type = "FD" ) if info['value'] != 0: print( "error! in %s", __name__ ) return None, None return f_opt, x_opt ``` create_formulation_prob は「 numpy.array である x が引数で渡された時に目的関数値 f と制約式の数値 g, 失敗したかどうかの数値 fail を返す関数」を返す関数である。 - なぜこんな面倒なことをしているかについての余談 slsqp のインターフェイスに合わせるためには引数は変えられない。引数以外からこの関数に対して、purchase_total_num のようなパラメータを埋め込みたい。python のように関数型言語的な機能がある場合、関数を返す関数で実装することになる(このように返された関数をクロージャとか呼んだりする)。 オブジェクト指向的なやり方ならば、そもそもインターフェイスとして slsqp クラスを継承して実装させるようにする。 普通の C 言語の場合、グローバル変数、スタティック変数、もしくは void* を引数に持たせて、内部で実装者がキャストして使うといったやり方になる。 このクラスのインスタンスを使用するコードは以下の通り ``` total_purchase = 30 lower = 1 with open( model_save_fpath, "rb" ) as f: model = pickle.load(f) df = pandas.read_csv( data_fpath, encoding="utf-8" ) optimizer = FormulationForPuchaseOptimize( model, df, total_purchase, lower, len(df) ) f_opt, x_opt = optimizer.solve() print(x_opt) ``` # 計算時間の目安 - 環境 下記の環境の docker 上で実行。 - 機種ID: MacBookPro14,2 - プロセッサ名: Intel Core i7 - プロセッサ速度: 3.5 GHz - プロセッサの個数: 1 - コアの総数: 2 - 二次キャッシュ(コア単位): 256 KB - 三次キャッシュ: 4 MB - メモリ: 16 GB - 計測結果 概ね 100 問に対して 15 秒程度 # pyOpt の実装 上記のコード例からもわかるように pyOpt では微分情報をどのように取っているのか明確ではない。pyOpt の実装を見ることでどのようにしているかがわかる。 - ソースコード ソースコードは svn コマンドにより次のようにして取ってくる。 ``` svn co http://svn.pyopt.org/trunk pyOpt ``` slsqp のメインとなるソースは pyOpt/pySLSQP にある。 pyOpt/pySLSQP/source ディレクトリにある fortran コードをコンパイルしたものを slsqp モジュールとし、pyOpt/pySLSQP.py から呼んでいるといった形式である。 - アルゴリズム 流れとしては初期点 x から目的関数値、制約式の数値、目的関数の微分値、制約式の微分値を用いて、 その周りの目的関数と制約式をそれぞれ二次関数、一次関数で近似した QP(quadratic programming) を解き、 変分 dx を決める。x = x + alpha* dx で x を更新。 といった形で、更新を進めていき、ある種の収束条件を満たしたら終了とする。 収束条件や alpha の詳細については以下の文献(ネットに落ちてる。)の chapter 2 による。 「Kraft D (1988) A software package for sequential quadratic programming. Tech. Rep. DFVLR-FB 88-28, DLR German Aerospace Center — Institute for Flight Mechanics, Koln, Germany.」 ただ、今だったら「Jorge Nocedal Stephen J. Wright, Numerical Optimization」(なぜかネットに落ちている。。)の chapter 18 にある 「Line search SQP method」を参考にしたほうが良いと思われる。 ちなみに nlopt の slsqp でも同様のソースを基にしている。 https://github.com/stevengj/nlopt/tree/master/src/algs/slsqp - QP について QP のアルゴリズム(lsq)は、「LAWSON & HANSON: SOLVING LEAST SQUARES PROBLEMS」(amazonで購入可能)という本に載ってるものそのまま。 この部分は40年くらい特に何もない。 # pyOpt の微分情報について 以下のようなコードで数値微分を行っている(説明の都合上、簡略化している)。デフォルトは sens_step = 1.0e-6 となっている。 pyOpt/pyOpt_gradient.py の class Gradient の getGrad メソッド ``` dh = sens_step xs = x k = 0 for i in mydvs: xh = copy.copy(xs) xh[i] += dh # ... 略 [fph,gph,fail] = opt_problem.obj_fun(xh, *args, **kwargs) if isinstance(fph,float): fph = [fph] for j in xrange(len(opt_problem._objectives.keys())): dfi[j,k] = (fph[j] - f[j])/dh for j in xrange(len(opt_problem._constraints.keys())): dgi[j,k] = (gph[j] - g[j])/dh k += 1 ``` # エラーが返されたのでデバッグ 店舗数が 30 以上といった場合に解けないということが生じていた. [f_opt, x_opt, info] = self.slsqp( opt_prob, sens_type = "FD" ) の info["text"] を見ると原因として、「Iteration limit exceeded」と出ている。 対処法としては以下の方法が考えられる。 1. 初期値を変えつつ何度もトライする。 2. 初期値を全て 1/total_num のように制約を満たすものにする。 3. slsqp.setOption( "MAXIT", 100 ) のようにイテレーション回数を増やす(デフォルトは 50) 4. slsqp.setOption( "MAXIT", 1.0e-4) のように判定誤差を甘くする。(デフォルトは1.0e-6 ) 5. sens_step(デフォルト 1.0e-6) を変える。(これは数値微分をとるときのステップサイズ) 6. 定式化を変える。 以下のように等式制約を変数にしてみた。 - 変数 : $x_i \in \mathbb{R}^{n-1} , ( 0 \leq x_i \leq 1.0 )$ - 補助変数(コード上では定義しない。) $ x_n = 1 - \sum_i(x_i) $ - 各店舗の仕入数: $ y_i = total\\_num*x_i$ - 目的関数: 最小化 $ \sum_i | y_i - pred( y_i ) |^2 $ - 制約: $\sum_{i\leq n-1}x_i \leq 1 $ ( $x_i$ が比率であることを意味する制約) $y_i \geq lower$ ( 仕入数は最低仕入数以上) - 結果 1,3,4 はどれも有効。現実としてはトライのたびに 3 のようにイテレーション回数を増やすといった実装が良いのではないか。 2 は試してないが解を偏らせるのではないかと心配。 4 も解を甘くしてしまうのは気になる所。 5 は試していない。predの感度に合わせてチューニングするのが良い。 6 は思ったよりうまくいかなかった。「LDL 分解する際に正定値でない」とかそんなエラーが生じる。
指数分布
以下を参考にしました。 https://mathtrain.jp/expdistribution # 分布 $f(x) = \frac{ exp(-\frac{x}{\mu}) }{ \mu} , x>0$ 期待値 $\mu$ 分散 $\mu^2$ # 使われ方 指数分布は「ある現象がある回数発生するまでの期間の長さ」に関する分布に対して用いることが多い。 理由は以下に書く「導出方法」を参照せよ。 ちなみに「ある一定期間に発生するある現象の回数」に関する分布に対してはポアソン分布が有名。 # 導出方法 $f(x)$ を時期 $x$ において初めて発生確率の密度関数とする。 定義より$x $ から$x+\delta x$ の間に初めて事象が発生する確率は$f(x)\delta x$と考えられる。 一方で上記の確率は、 (時刻 $x$ までイベントが起こらない確率)$\times$(それから $\delta x$の間に起こる確率) であり、時期によらず、考慮期間に比例して発生する確率が上がるとすれば $(1 - \int_0^xf(x)dx)\times \lambda \delta x$ と考えられる。($\lambda$は比例定数とする。) よって $(1 - \int_0^xf(x)dx)\times \lambda = f(x)$ という積分方程式を得る。 この式を両辺 $x$で微分すると、 $-\lambda f(x) = f'(x)$ これを解くと $ f(x) = Ce^{-\lambda x }$ で積分して 1 になるとすれば、 $ f(x) = \lambda e^{-\lambda x }$ となり、$\lambda = \frac{1}{\mu} $ となることがわかる。
rust の勉強メモ
# emacs 環境の準備 以下を参考にした. https://github.com/rust-lang/rust-mode 以下を .emacs に書き込む ``` (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) ``` M-x package-install とすると何をインストールするか聞かれるので、 rust-mode と入力. これで .rs のファイルを開くと rust-mode として動作する。 # rust のドキュメント第二版を紹介 rustのドキュメントの第二版が良い感じだったので紹介 https://doc.rust-lang.org/book/second-edition ## 良いと思ったところ ボローイングとライフタイムについての説明がだいぶ良くなっていると感じた。 ### ボローイング https://doc.rust-lang.org/book/second-edition/ch04-02-references-and-borrowing.html 第一版だと例に使ってたのが i32 で、Copy がデフォルトの動作をしてしまうため、ボローイングが成されていることが非常にわかりづらかった。String のオブジェクトを例にしたのはわかりやすくなっていると思う。 ### ライフタイム https://doc.rust-lang.org/book/second-edition/ch10-03-lifetime-syntax.html 引数のオブジェクトのライフタイムが違う例が紹介されていてライフタイムの考えがわかりやすくなっている。 引数のライフタイムが実際には違うが同じライフタイムシンボルで定義されている場合には短いライフタイムに合わせられるなど、こちらを読んで理解が追いついた。
Blogger のカスタマイズ
# 概要 使いづらかったので以下の点でカスタマイズ 1. リアルタイムプレビュー 以下のサイトに従った。 http://nervouskidkitten.blogspot.com/2016/11/blogger_5.html 2. markdow 対応 以下のサイトに従った。 https://qiita.com/her0m31/items/1804bdc251a647e0e9a8 2018/7/21 現在では「テンプレート」という項目に代わって「テーマ」という項目になっている。それ以外はサイトの通り。 3. tex 対応 http://irrep.blogspot.com/2011/07/mathjax-in-blogger-ii.html 上記のサイトに従って以下のコードを HTML の先頭に差し込むようにした。 ``` ``` # 課題 1. リアルタイムプレビューが意外と重たくて安定しない。 2. プログラムコードをどうするか。現状は バッククォート 3 つでくくるというやり方をしている。 これだとjavascriptのコードでも評価されないでそのまま表示されるのでとりあえずは良いと思う。
レコメンドの評価指標
# 概要 https://arxiv.org/abs/1806.01973 で使用していた評価指標について述べる。 指標は二つ提示されていて、一つは「hit-rate」,もう一つは「MRR」とよんでいる。 # 設定 1. 観測データとして $(q,i)$ のペア群が与えられたものとして評価する。 ここで $q$ はユーザー、$i$ は薦めるアイテムを想定していて、$(q,i)$ は実際にユーザー $q$ がアイテム $i$ を購入(もしくはクリック) した場合に、このペアが観測されるものと想定している。観測される $(q,i)$ ペア全体の個数を $n$ とする。 2. 想定しているレコメンドシステムは、各ユーザー $q$ に対するアイテム $i$ のスコア $s(q,i)$ が計算されるものとし、このスコア $s(q,i)$ が現実に即しているかどうかを評価したい。 # hit-rate レコメンドシステムにおいて各 $q$ に対してスコア $s(q,i)$ が上位のものから順に、 $K$ 個のアイテムをとってくる。 この集合を $I_{q,K}$ とした時、 $$hit-rate = \frac{1}{n}\sum_q | I_{q,K} |$$ と定義する( $| I_{q,K} |$ は集合 $I_{q,K}$ の要素の個数)。これは $K$ に依存した評価であり、$K$ は実際にレコメンドする個数に依存して決めるのが良いと思われる。(ちなみに論文においては $K=500$ としている。) hit-rate の値が大きいほど、スコアと実際の購入(もしくはクリックなど)の結びつきが強いと評価できる。 # MRR $R_{q,i} $はスコア $ s(q,i) $ のユーザー $q$ における順位とする。 この時、 $$MRR = \frac{1}{n}\sum_{(q,i)}{ \frac{1}{\lceil R_{q,i}/K \rceil} } $$ と定義される。 順位が上がれば上がるほど(つまり数値が少なければ少ないほど)、MRR は大きくなる。 この指標は $K$ に依存した指標であり、$K$ はどの程度の順位幅は同じとみなすかというパラメータである。(論文では $K=100$ としている。) MRR の値が大きいほど、スコアと実際の購入(もしくはクリックなど)の結びつきが強いと評価できる。
2018年5月13日日曜日
windows10でtensorflow-gpu
# 概要 2018/05/12 現在において tensorflow-gpu をやって見た ハマった点 1. cuda は 9.1 が最新だが、現状、cuda の dll 名が cudart_90.dll のようにバージョン名が埋め込んであり、tensorflow がcudart_91.dll ではなくて cudart_90.dll を要求し 動かない。 仕方ないので cuda 9.1 をコントロールパネルからアンインストールして、cuda9.0をインストールした。その後、 ``` pip --no-cache-dir install -I tensorflow-gpu ``` で再インストールしたらうまくいった。
新しい投稿
前の投稿
ホーム
登録:
投稿 (Atom)