gorogoroyasu

福岡の開発会社で働いている。

LINE公式アカウント の料金を調査するWebサイト作った。

昨日、LINE 社より 【重要】LINE@サービス統合のお知らせ というお知らせが発表されました。

blog-at.line.me

そこには、 「LINE@」は「LINE公式アカウント」「LINE ビジネスコネクト」「LINE カスタマーコネクト」とサービス統合し、名称を「LINE公式アカウント」として生まれ変わります。 と書かれています。

つまり、今までと料金体系が変わるということです。

フリープランでも、今まで上位プラン・もしくはビジネスコネクトでしか使えなかった機能が使えるようになる半面、
メッセージの送信が従量課金制になります。

料金プランも同時に発表されましたが、そこそこ分かりづらい。
無料枠の取扱とかが難しいです。

ということで、プランとメッセージ数を入力したら月いくらかかるか試算してくれるアプリ(と呼べるほどのものでもないが。。。)を作りました。

https://gorogoroyasu.github.io/lineat_price/

必要最低限の機能しかないし、バリデーションもしていない。 メッセージも不親切だしデザインもなにもない。と、不親切極まりない感じですが。。。

ちょこちょこ修正して、いい感じにしていこうと思っています。

一応、Vue.js を使っています。
使ったことないので、間違った使い方してるかも。
(そもそもJSをHTML 内に書いてる時点でというのはおいといてw)

テスト

上記のLINE社の発表の中で、いくつか例示されている金額があります。
手動ではありますが、その金額がただしいことは確認しています。 逆に言うと、他の金額のことは考えていないので、違うかもしれません。

算出根拠とかも出るようにしたほうがいいですね。 改修します。

終わりに

プルリク送ってください。

tensorflowのrandom crop で、2枚の画像の同じ箇所を切り取る

画像が2枚あります。
画像A が入力データ 画像B が正解データ。

f:id:adiboy:20181026102449p:plain 画像引用: 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 は便利でよく使うので、特性をちゃんと把握しておく必要がある。

  1. 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

ということで、後入先出になってることがわかる。

  1. 他の関数内の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 を買って帰ろう。 と心の中で決めていた。

ヨドバシカメラでの体験は、最悪だった。
修理ができないのは仕方ない。
しかし、あんなに広いスペースを使って製品を展示いるにもかかわらず、販売すらしていないのだ。

値段を聞いたら、「展示してあるiPhoneApple.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 を行ってみました。

github.com

実行した環境は以下の通り。

Ubuntu 16.04 に Mac Book Pro から ssh で接続
nvidia-docker: 18.03.1-ce
CUDA: 9
cudnn: 7
tensorflow-gpu: 1.8.0

参考にしたもの

自分は、以下の動画を見ながらどのように行えばよいかを学びました。
非常に丁寧に説明されているので、おすすめです。
ただ、問題は解説がWindows 向けにされていること。
なので、今回は Ubuntu でどのようにやればよいかを書いていきたいと思います。

www.youtube.com

準備

実行するソースコードの準備

上記の環境の任意のディレクトリに、 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 に載っています。

ラベルの準備

次に、ラベルの準備を行います。 自分は、MacApp Store から¥240 で購入できます。
とても使い勝手がいいのでおすすめです!

RectLabel for object detection

RectLabel for object detection

  • Ryo Kawamura
  • 開発ツール
  • ¥240

RectLabel を使ってラベルをつけていきます。

他にも labelbox.com というツールが有るようで、動画ではこちらを使っています。

こんな感じでラベルをつけていきます。

f:id:adiboy:20180713152914p:plain

ラベルの作成が終わったら、 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 をファインチューニングしていこうと思います。
事前に学習された重みは、下記サイトに掲載されているので、

github.com

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 を上書きした画像を出力してくれます。

なにか問題点があったら教えてください。

以上。