IteratorをGeneratorと関数内static変数で実装する
Excel のカラムは、 A から始まって Z までいくと次は AA となり、 AZ、BA、BZ・・・と続いていきます。
以前行った案件でExcel出力を行う際に github.com
を使わせていただいていましたが、
'col' => 'B',
を指定するのがかなり大変でした。
というのも、カラムが AX ぐらいまであり、今回の改修で、そのうちのいくつかの項目を削ることになったのです。
つまり、
'col' => 'D',
という値を消すと、以降の
'col' => 'E', 'col' => 'F', 'col' => 'G',
をすべて
'col' => 'D', 'col' => 'E', 'col' => 'F',
に書き換えなくてはなりません。
そして、今回、機能追加を行うことになりました。
そんなことやってられるかー (怒)
ということで、2つ対策を考えてみました。
俗に言う、Enum ってやつなのかな?
イメージとしては、Golang のiota 的なやつ。
package main import ( "fmt" ) const ( a = iota b c d ) func main() { fmt.Println(a, b, c, d) # => 0, 1, 2, 3 }
今回は、 Excel のカラムに沿った値を返すところが違います。
Generator を使う解決策
class GenColumn { public function __construct() { $this->gen = $this->excelColumnGenerator(); } public function getCurrent() { return $this->gen->current(); } public function getNext() { $this->gen->next(); return $this->gen->current(); } private function excelColumnGenerator() { $ary = []; for ($a="A"; $a != "AZ"; $a++) { $ary[] = $a; } foreach($ary as $column) { yield $column; } } } $column = new GenColumn(); print($column->getCurrent()); print($column->getNext()); print($column->getCurrent()); print($column->getNext()); print($column->getCurrent());
getCurrent() というメソッドを追加して、現在の値を取得することができるようにしています。
これを 関数内static変数を使うバージョンで書き直すと、
関数内static変数を使う解決策
class GenColumn { private static $ary = []; private static function genAry() { $ary = []; for ($a="A"; $a != "AZ"; $a++) { $ary[] = $a; } self::$ary = $ary; } public function getColumnName($current=false) { static $a=1; if (empty(self::$ary)) { self::genAry(); } if ($current) { return self::$ary[$a - 1]; } $a++; return self::$ary[$a - 1]; } } $column = new GenColumn(); print($column->getColumnName(true)); print($column->getColumnName(false)); print($column->getColumnName(true)); print($column->getColumnName(false)); print($column->getColumnName(true));
という風に書けます。
両者に一長一短があります。
ちなみに、10000回ループした際の実行速度(5回の平均)は
Generator: 0.089s 関数内static変数: 0.023s
と、関数内static変数の方で実装したほうが4倍ほど早かったです。
メリットとデメリット
Generator を使う解決策は、コードが長くなりますが、 - $column->getCurrent() - $column->getNext() と、行う操作にわかりやすい名前をつけることができます。
一方、コードが長くなり、実行に時間がかかります。
関数内static変数 を使う解決策は、コードは短く実行時間も早いです。 ただし、現在の値とインクリメントした値を取得する際に、 $column->getColumnName($current) という風に、$current の true/false で取れる値が変わります。 可読性は決して高くないです。
まとめ
結局、2つの実装方法を検討しましたが、より安全性が高い Generator で実装を行いました。 ただ、 関数内static変数 の使い方はあまり良く知らなかったので、調べてみてよかったです。
tensorflowのrandom crop で、2枚の画像の同じ箇所を切り取る
画像が2枚あります。
画像A が入力データ 画像B が正解データ。
画像引用: https://helpx.adobe.com/jp/photoshop-elements/kb/222341.html
この画像をrandom crop するときに、どのようにするとうまくcrop できるのか試しました。
そして、たどり着いた答えが以下のコード。
もっといい方法があるのかもですが、とりあえずこれで動きます。
import tensorflow as tf import numpy as np import random # この二つの配列を、画像と見立てる img1 = np.arange(300).reshape((10, 10, 3)) img2 = np.arange(300).reshape((10, 10, 3)) + 300 i1 = tf.constant(img1) i2 = tf.constant(img2) with tf.Session() as s: s.run(tf.global_variables_initializer()) seed = random.randint(0, 1000) croped_img1 = tf.random_crop(i1, [5, 5, 3], seed=seed) croped_img2 = tf.random_crop(i2, [5, 5, 3], seed=seed) c1 = s.run(croped_img1) c2 = s.run(croped_img2) """ c2 - c1 => array([[[300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300]], [[300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300]], [[300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300]], [[300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300]], [[300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300], [300, 300, 300]]]) """
おそらくこれで切れているんだと思います。 ということで、random_crop に seed を入れてあげれば解決。 以上。
追記。
俺のやりたかったことは、そんなんじゃない!!!! こんな記事、嘘っぱちだ。
dataset API の中で、どうやってそれ使えばいいんだ(怒)
ということで、調べた。
import tensorflow as tf import numpy as np import random # 画像 (10枚) img1 = np.arange(3000).reshape((-1, 10, 10, 3)) img2 = np.arange(3000).reshape((-1, 10, 10, 3)) + 300 def _parse_function(img1, img2): r = tf.get_seed(1) img1 = tf.random_crop(img1, [5, 5, 3], seed=r[0]) img2 = tf.random_crop(img2, [5, 5, 3], seed=r[0]) return img1, img2 dataset = tf.data.Dataset.from_tensor_slices((i1, i2)) dataset = dataset.map(_parse_function, num_parallel_calls=2) dataset = dataset.shuffle(buffer_size=(len(img1))).batch(1).prefetch(1).repeat() iterator = dataset.make_one_shot_iterator().get_next() with tf.Session() as sess: for i in range(5): x, y = sess.run(iterator) print(x[0, 0, 0]) print(y - x)
結果
[2823 2824 2825] [675 676 677] [1932 1933 1934] [2232 2233 2234] [2436 2437 2438] [[[[300 300 300] [300 300 300] [300 300 300] [300 300 300] [300 300 300]] [[300 300 300] [300 300 300] [300 300 300] [300 300 300] [300 300 300]] [[300 300 300] [300 300 300] [300 300 300] [300 300 300] [300 300 300]] [[300 300 300] [300 300 300] [300 300 300] [300 300 300] [300 300 300]] [[300 300 300] [300 300 300] [300 300 300] [300 300 300] [300 300 300]]]]
ということで、ランダムに画像を crop しつつ、対象の2枚の画像については同じ箇所で切り出すことができた。
メモ golang の defer 2
package main import ( "fmt" ) type Hoge struct { a string } func main() { h := &Hoge{"a"} defer fmt.Println("no func", h.a) defer func () {fmt.Println("with func", h.a)}() h.a = "b" }
出力は、
with func b no func a
以前の記事に書いたが、defer は、後入れ先出しで処理される。
よって、 with func
が先に実行され、
no func
が後で実行される。
1つ目の defer、with func
については、h.a = "b"
の結果が表示され、
2つ目のdefer、 no func
については、 h.a = "a"
の結果が表示されている。
これは、 defer 文が、式の実行を遅らせるだけで、式の評価自体は defer 文が呼ばれた段階で行われていることを示唆している。
たぶん。
メモ golang のdefer
defer は便利でよく使うので、特性をちゃんと把握しておく必要がある。
- defer の実行順
package main import ( "fmt" ) func main() { for i:=0; i<3; i++ { fmt.Println("1") defer fmt.Println(fmt.Sprintf("2-%d", i)) fmt.Println("3") } }
結果
1 3 1 3 1 3 2-2 2-1 2-0
ということで、後入先出になってることがわかる。
- 他の関数内のdefer はいつ呼ばれるのか?
package main import ( "fmt" ) func de(s string) { defer fmt.Println(s) } func main() { for i:=0; i<3; i++ { fmt.Println("1") de("2") } }
結果
1 2 1 2 1 2
となった。 関数のスコープを抜ける時に呼ばれるらしい。 なるほど。
この辺は、ドキュメントにも書いてあると思うが、
簡単な実験で挙動を確認できるので、サンプルを書いてみると良いと思う。
続けていこう。
"Key (id)=(1) already exists" error occurred in CakePHP3 model test.
久々にCakePHP でテストを書いている。
Fixture を整えて、TestCase を書いた。
そして、値を save するメソッドをテストした。
すると、
duplicate key value violates unique constraint "hoges_pkey" DETAIL: Key (id)=(1) already exists.
というエラーが出た。
悲しい。
書いてたFixture は、こんな感じ。
<?php namespace App\Test\Fixture; use Cake\TestSuite\Fixture\TestFixture; class HogesFixture extends TestFixture { /** * Fields * * @var array */ // @codingStandardsIgnoreStart public $fields = [ 'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null], 'user_id' => ['type' => 'integer', 'length' => 10, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null, 'autoIncrement' => null], 'fuga' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'collate' => null, 'comment' => null, 'precision' => null], 'modified' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null], 'deleted' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null], '_constraints' => [ 'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []], ], ]; // @codingStandardsIgnoreEnd /** * Init method * * @return void */ public function init() { $this->records = [ [ 'id' => 1, 'user_id' => 1, 'fuga' => 'a', 'modified' => 1539138087, 'deleted' => 1539138087 ], [ 'id' => 2, 'user_id' => 1, 'fuga' => 'b', 'modified' => 1539138087, 'deleted' => 1539138087 ], [ 'id' => 3, 'user_id' => 1, 'fuga' => 'c', 'modified' => 1539138087, 'deleted' => 1539138087 ], ]; parent::init(); } }
そして、原因は Fixture にあった。
解決策は、
public function init() { $this->records = [ [ // 'id' => 1, 'user_id' => 1, 'fuga' => 'a', 'modified' => 1539138087, 'deleted' => 1539138087 ], [ // 'id' => 2, 'user_id' => 1, 'fuga' => 'b', 'modified' => 1539138087, 'deleted' => 1539138087 ], [ // 'id' => 3, 'user_id' => 1, 'fuga' => 'c', 'modified' => 1539138087, 'deleted' => 1539138087 ], ]; parent::init(); }
$this->records に入れた連想配列内の、 id を消すこと。
普通に考えたらそうだ。
ID は、 データベースのsequenceの機能を使うので、idの発行をCakePHPに任せたら、
1からID が付与され、自動的にインクリメントされる。
一方、ID に値を入れてしまうと、Database の Sequence の機能を使わずに値が保存される。
よって、Database は、次の値は1だと思っている。しかし、1はすでに存在している。
だから、エラーになる。
細かいところだが、ハマりどころだ。
やっかいなことに、 Fixture を bake すると、デフォルトで値が挿入されるので、
Bake のテンプレートを書き換えるか (やったことない) いちいちコメントアウト (もしくは削除) する必要がある。
参考にしたのはこちら stackoverflow.com
iPhone の充電ができないときに調べるべきただ一つのこと
ここ2ヶ月ぐらいずっと、iPhone の調子がおかしかった。
これは、ライトニングケーブルが断線してるな。
勘のいいボクはすぐに気づいた。
最近買った、Magic Trackpad 2 付属のライトニングケーブルを使ってみた。
そのときはうまく充電できた。
しかし、その能力も長くは続かなかった。
それからは、iPhone を省電力モードにして、だましだまし使ってきた。
電池の色が黄色いのがデフォルトだった。
いつも、何度かケーブルを抜き差しして、ちょうどよい角度を探していた。
そして、先週の週末、夜、充電できなくなった。
充電は残り3%。
絶体絶命だ。
このまま電池が切れ、二度と充電できない状況になったら、
二段階認証 が一切できなくなる。
念の為、Google の 二段階認証 は解除した。
Authy に移行していなかった自分に腹が立つ。
また、最近 Google Photos にアップロードしていない写真たちも消滅する。
LINE も移行しなければならない。
ボクは iPhone に頼りすぎていたことに気づいた。
そして、バックアップの重要性を思い出した。
時刻は21:05。
Apple Store はすでにしまっている。
ボクは絶望に暮れた。
念の為、ビッグカメラの営業時間を調べた。
こちらも21:00 まで。
終わった。 そう思った。
しかし、次の瞬間、ボクの脳裏に、ひらめきが走った。
ヨドバシカメラに Apple 製品おいてるゾーンあるよな。
時間を確認する。すると、22:00 まで開いている。
自宅からヨドバシカメラまで、チャリで15分。
余裕で間に合う。
勢いで直行した。修理できない可能性はあるが仕方ない。
最悪iPhone8 を買って帰ろう。 と心の中で決めていた。
ヨドバシカメラでの体験は、最悪だった。
修理ができないのは仕方ない。
しかし、あんなに広いスペースを使って製品を展示いるにもかかわらず、販売すらしていないのだ。
値段を聞いたら、「展示してあるiPhoneでApple.co.jp にアクセスすれば見れますよ」 と言われた。
そんな事も知らないんですか? 的なテンションで。
知っとるがな!w
泣く泣くヨドバシカメラを立ち去り、家路についた。
クロアチアが勝つかフランスが勝つか よりもiPhone の充電のほうが気になった。
なんとか充電してください!!!
藁にもすがる思いで何度もコネクタを抜き差しした。
しかし、何もおこらない。
昔ポケモンを捕まえるときに、 A B ボタンを連打したら捕まえやすくなる みたいな噂があったが、あれと同じぐらい無駄なことをしていると感じた。
ちなみに、ポケモンに関して言うと、ホウオウをモンスターボールでゲットしたことがあることを思い出した。
話を戻す。
数十分の格闘の末、iPhone が震えた。
その角度をキープしつつ、フル充電した。
次の日、起きてすぐ Apple Store に向かった。
予約せずに行ったので、予約してないやつらが並ばされる長い列の最後尾に追加された。
暇そうにしている店員に腹が立つ。
しかし、もうすぐiPhone を修理できる。
そう信じて並んだ。
並び続けた。
30分ぐらいして自分の番がきた。
店員に言った。 「充電ができなくなりました。交換だと思います。」
3秒ぐらいiPhone を見回した店員は、言った。
「あー、Dock に誇り溜まってますねー」
そして、ホコリを取り除いてくれた。 結局、原因はDock のホコリだった。
ボクのiPhone は、生き返ったかのように充電できるようになった。
TensorFlow の ObjectDetection API を Fine Tuning する
TensorFlow には、Object Detection を行うためのコードが用意されています。
今回は、TensorFlow 1.8.0 でObject Detection を行ってみました。
実行した環境は以下の通り。
Ubuntu 16.04 に Mac Book Pro から ssh で接続 nvidia-docker: 18.03.1-ce CUDA: 9 cudnn: 7 tensorflow-gpu: 1.8.0
参考にしたもの
自分は、以下の動画を見ながらどのように行えばよいかを学びました。
非常に丁寧に説明されているので、おすすめです。
ただ、問題は解説がWindows 向けにされていること。
なので、今回は Ubuntu でどのようにやればよいかを書いていきたいと思います。
準備
実行するソースコードの準備
上記の環境の任意のディレクトリに、 github.com をclone します。
cd /path/to/dir git clone https://github.com/tensorflow/models
また、先程の動画の作者の方が作られたレポジトリがあるので、そのコードをclone します。
git clone https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10.git
その後、TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10
ディレクトリの中身を
models/research/object_detection
にコピーします。
rm -r models/research/object_detection/doc rm -r models/research/object_detection/images rm -r models/research/object_detection/inference_graph rm -r models/research/object_detection/training cp TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10/* models/research/object_detection/
環境の整備
protobuf の準備をします。
apt-get install protobuf-compiler
を行い。 protobuf をインストールした後、
cd path/to/dir git clone https://github.com/cocodataset/cocoapi.git cd cocoapi/PythonAPI make cp -r pycocotools ../../models/research/ cd models/research protoc object_detection/protos/*.proto --python_out=.
というコマンドを実行します。
これらのコマンドは、
models/installation.md at master · tensorflow/models · GitHub
に載っています。
ラベルの準備
次に、ラベルの準備を行います。
自分は、Mac の App Store から¥240 で購入できます。
とても使い勝手がいいのでおすすめです!
RectLabel を使ってラベルをつけていきます。
他にも labelbox.com というツールが有るようで、動画ではこちらを使っています。
こんな感じでラベルをつけていきます。
ラベルの作成が終わったら、
Ubuntu 上、 /path/to/dir/models/research/object_detection/images
に画像をコピーします。
その後、 cd /path/to/dir/models/research/object_detection/images && mv annotations/* ./ && rm -r annotations && mkdir test train
を実行します。
動画内で、学習データは200枚以上必要と言っていたので、
頑張ってラベリングしてくださいw
また、画像のサイズは、720x1280
以下にすることが推奨されるようです。
これらが終わったら、適当に jpg と xml のペアを
train, もしくはtest に振り分けていきます。
/path/to/dir/models/research/object_detection/images
にて、
import glob, shutil import numpy as np xmls = glob.glob('*.xml') assert len(xmls) > 200 train = np.random.choice(xmls, 200, replace=False) for xml in xmls: name = xml[-4:] if xml in train: shutil.move(xml, 'train/{}'.format(xml)) shutil.move('{}.JPG'.format(name), 'train/{}.JPG'.format(name)) else: shutil.move(xml, 'test/{}'.format(xml)) shutil.move('{}.JPG'.format(name), 'test/{}.JPG'.format(name))
みたいなコードを実行して、適当にディレクトリを振り分けていきます。
上記のコードは、実際に動かしたわけではないので、動作保証はしません。
これで、ラベルの準備は完了です。
fine tuning する対象の重み
今回は、Faster R-CNN をファインチューニングしていこうと思います。
事前に学習された重みは、下記サイトに掲載されているので、
cd /path/to/dir/models/research/object_detection/ wget http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz tar -zxvf faster_rcnn_inception_v2_coco_2018_01_28.tar.gz
というふうに、ダウンロードして解凍します。
その後、
cp samples/configs/faster_rcnn_inception_v2_pets.config training cp data/pet_label_map.pbtxt training/label_map.pbtxt
とし、これらの中身を編集していきます。
training/label_map.pbtxt の中には、アイテムのID と名前を書きます。
item { id: 1 name: 'madatsubomi' } item { id: 2 name: 'otachi' } item { id: 3 name: 'pijotto' }
faster_rcnn_inception_v2_pets.config は少し修正箇所が多いのですが、
# 10行目 num_classes を自分が学習させたいラベルの数に書き換える # 107行目 fine_tune_checkpoint を /path/to/dir/models/research/object_detection/faster_rcnn_inception_v2_coco_2018_01_28/model.ckpt に書き換える # 122 行目 input_path を、 /path/to/dir/models/research/object_detection/train.record に書き換える (未作成) #124 行目 label_map_path を /path/to/dir/models/research/object_detection/training/label_map.pbtxt に書き換える #128行目 num_examples を テストデータの数に書き換える #236行目 input_path を、/path/to/dir/models/research/object_detection/test.record に書き換える (未作成) #238行目 label_map_path を /path/to/dir/models/research/object_detection/training/label_map.pbtxt に書き換える
これらの操作により、設定は完了です。
TensorFlow が読み出すデータの作成
generate_tfrecord.py というファイルを編集します。
編集するのは、class_text_to_int
という関数の中身です。
この関数の中身を、自分のデータセットと training/label_map.pbtxt に書いた内容を見比べながら編集していきます。
def class_text_to_int(row_label): if row_label == 'madatsubomi': return 1 elif row_label == 'otachi': return 2 elif row_label == 'pijotto': return 3 else: None
その後、train.record
, test.record
というファイルを作成していきます。
cd /path/to/dir/models/research/object_detection/ python xml_to_csv.py python generate_tfrecord.py --csv_input=images/train_labels.csv --image_dir=images/train --output_path=train.record python generate_tfrecord.py --csv_input=images/test_labels.csv --image_dir=images/test --output_path=test.record
これで、TensorFlow が学習時に読み出すデータが作成できました。
学習の実行
学習を実行します。
cd /path/to/dir/models/research/object_detection/ nohup python -u train.py --logtostderr --train_dir=training \ --pipeline_config_path=training/faster_rcnn_inception_v2_coco.config \ > success.log 2> error.log &
これで、学習が開始されます。
学習の経過を見たい場合は、 tailf error.log
を見てみてください。
(なぜ success.log ではなく error.log の方に吐かれるのかは不明)
loss の平均値が 0.05 を下回るようになったら学習完了らしいです。 (動画で言っていた。)
出力
では、出力を確認してみましょう。
出力の確認には、 export_inference_graph.py というファイルを利用します。
python export_inference_graph.py --input_type image_tensor --pipeline_config_path training/faster_rcnn_inception_v2_coco.config \ --trained_checkpoint_prefix training/model.ckpt-X --output_directory inference_graph #ただし X は、 training/model.ckpt-X となっているX の値
その後、 Object_detection_image.py
の中の IMAGE_NAME
を変更し、
python Object_detection_image.py
を実行します。
そうすると、NN が元画像に box を上書きした画像を出力してくれます。
なにか問題点があったら教えてください。
以上。