機械学習でサイコロの目をカウントするモデルを作成する

最近機械学習が楽しい。時代の波に乗っているものというのは遊んでも仕事に使っても楽しいものだ。

先日サイコロをOpenCVでカウントするプログラムを作ってみたが、これを機械学習で実現してみたいと思ったのでやってみる。

kyudy.hatenablog.com

機械学習で実装すると、認識が堅牢になることが期待される。

サイコロがひとつ写った画像を認識するモデルを作成

Chainer(Preferred Networks社謹製)でさくっとかいた。

(勉強には時間がかかった。さくっとかけるのはChainerが素晴らしいだけだ。肝に銘じよう)

モデルはたった5層のCNN(Convolutional Neural Network)。

最近流行りの深層学習からすると、だいぶシンプルなモデルで十分な性能を得ることができる。

github.com

入力画像の例

f:id:KYudy:20180417223004j:plain

この入力画像に対するラベル(教師)は1となる。

処理概要

  • サイズが(32, 32, 3)の小さな画像を入力(撮影したあとで圧縮した)
  • 畳み込み層を3層、全結合層を2層に設定
  • 最後の層(出力に最も近い層)の活性化関数をsoftmaxに設定しクラス分類

単一のサイコロの写真を2つ写し合計を計算するモデルを作成

仕事で多入力、1出力のモデルを開発する必要があったので、 実験として作ってみた。

github.com

画像は上と同じ(サイコロがひとつ写った)画像を流用した。

異なるのは

  • 写真を2つを同時にネットワークに入力していること
  • その2つの写真のサイコロの目を足し合わせたものを正解のラベルとして扱っていること

フレームワークはいろいろ試してみたかったのでPytorchを使ってみた。 Kerasも使ってみたが、簡単に使える反面で柔軟性にかけるし、生のTensorflowは触る気が起きなかった。 そしてChainerと同じぐらい柔軟で、海外でも流行っているというPytorchに魅力を感じた。 Caffeがよい、と言う話も見かけたが、どうなんだろう。後日試してみたいと思う。

ちなみに、このレベルの実装だと、Chainerとの違いは全結合層の手前でview()を呼ぶことぐらいかなぁという感じだ。

もっと実装を重ねていけば見えてくるものもあるだろう。

モデルはこんな感じ。

class MyModel(torch.nn.Module):
    def __init__(self, nb_class):
        super(MyModel, self).__init__()

        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, 5),
            nn.Conv2d(64, 64, 5),
            nn.Conv2d(64, 128, 5))

        self.conv2 = nn.Sequential(
            nn.Conv2d(3, 64, 5),
            nn.Conv2d(64, 64, 5),
            nn.Conv2d(64, 128, 5))

        self.fc = nn.Sequential(
            nn.Linear(128 * 20 * 20 * 2, 120),
            nn.Linear(120, 84),
            nn.Linear(84, nb_class),
        )

    def forward(self, x, y):
        x1 = self.conv1(x)
        x2 = self.conv2(y)

        x1 = x1.view(-1, 128 * 20 * 20)
        x2 = x2.view(-1, 128 * 20 * 20)

        x1x2 = torch.cat((x1, x2), 1)

        return self.fc(x1x2)

2つの同じ形状の畳み込み層にそれぞれサイコロの写真を入力し、その出力をtorch.catで結合し全結合層へ入力している。 早々にValidationデータに対する正答率が100%に近づく。気持ちいいぐらいに。

OpenCVとおなじことを実現するのはサボった

2つのサイコロの写真の合計の目を出力するモデルの作成は割愛した。 同じことをすれば多少なりとも比較ができてよいのだが、興味がなくなってしまったのだ。

また、必要がでてきたら実装したいと思う。

OpenCVでサイコロのでた目の数をカウントする

概要

OpenCVでサイコロのでた目の数をカウントしたくなった。 ボードゲームを少し電子化してよりゲームを面白くするために使いたいと思っている。

実装

条件

サイコロを上から見る場合に限定して実装する。(立体的にみてどの面が上になるのかを判定するのが難しそうだっため)

手順

  • OpenCVで画像からまずサイコロの四角を抜き出してくる。
  • すべてのサイコロに対して以下の処理を行う。
  • 抜き出してきたサイコロ画像の中から丸(サイコロの目)を探して丸の数を数える。
  • 最後に目の数を合計して出力する。

こうなった。 github.com

元の画像 f:id:KYudy:20180128200657j:plain

サイコロが写っている部分だけを抜き出して、目の部分を赤色で示した。 f:id:KYudy:20180128200608j:plain

出た目の数は以下のように印字される。

sum is 10 [6, 4]

Ansibleでファイルのバックアップを作成する

Ansibleでファイルをバックアップしたいことがある。 失敗することを考えるとバックアップは一度だけ実行したい。 シェル上ならばcpするときに -n を指定すれば2回め移行は上書きされないことを保証できる。

       -n, --no-clobber
              do not overwrite an existing file (overrides a previous -i option)

Ansibleでも同じようにshellモジュールで実行すればよいという話もあるが、 可能な限りshellを避けるべきだと思う。 --diff で差分でないし。

そこでマニュアルとにらめっこして考えだしたのが以下となる。 http://docs.ansible.com/ansible/latest/copy_module.html

      copy:
        src: /path/to/src_file
        dest: /path/to/dst_file
        remote_src: yes
        force: no

remote でサーバ上のファイルを指定してバックアップできる。 force で上書きしないようにできる。

ansible iptables `state=absent` が動かない。

ansibleのモジュールiptablesで'state=present'が機能しない。

ansibleでiptablesをメンテナンスしようとおもい、以下のモジュールを利用したが、どうにも state=absent が動かない。 iptablesモジュールのソースを追ってみたところ、以下のようにchangedを判定しているのだが、

# /usr/lib/python2.6/site-packages/ansible/modules/extras/system/iptables.py

def check_present(iptables_path, module, params):
    cmd = push_arguments(iptables_path, '-C', params)
    rc, _, __ = module.run_command(cmd, check_rc=False)
    return (rc == 0)

この -C(--check) はどうやら最近のiptablesしか持っていないようで、これがうまく機能しないため、changedは常にfalseになるようだ。

$ cat /etc/redhat-release
CentOS release 6.4 (Final)
$ sudo iptables --check INPUT -p tcp -s 0.0.0.0/0 -d 0.0.0.0/0 -j REJECT --destination-port 25
iptables v1.4.7: unknown option `--check'
Try `iptables -h' or 'iptables --help' for more information.

ソースを追わないと気づけないのが問題だと思う。↓

    return (rc == 0)

css changes not synced for heroku django based app on virtualbox shared folder.

概要

virtualbox上でherokuのアプリを作っていたのだが、共有フォルダにソースを置いてホストOS上からCSSをいじるとなぜかうまく反映されないため困った。 Webサーバを再起動したりキャッシュっぽいファイルを手当たり次第に消したりしてみたが、いっこうにうまくいかない。 gunicornやwhitenoiseを疑って見たものの特に関係なさそうだった。

結論としてはherokuもgunicornもwhitenoiseもdjangoも関係なかった。 virtualboxのバグらしい。 www.vagrantup.com

ホストOSではなくクライアントOSからファイルを編集すると問題なく反映されるあたりで疑って調べ始めてすぐわかったのでよかった。

対応方法

gunicornを使っている場合は以下のように--no-sendfileをつけて起動すればsendfileを無効化でき、バグを回避できる。

$ cat Procfile 
web: gunicorn proj.wsgi --no-sendfile

PostfixのmaillogのFromとToを結合する。

PostfixのmaillogのFromとToを結合する。

Postfixのログを調べるときに一つのメールの行方を追うだけならすごく簡単だが、 複数まとめて検索したいときに面倒くさいのはログ上のFromとToが別の行になっていることだろう。

Feb  9 17:32:04 server001 postfix/qmgr[27385]: 000E21A124F: from=<test@example.com>, size=2414, nrcpt=2 (queue active)
Feb  9 17:32:04 server001 postfix/smtp[7790]: 000E21A124F: to=<test@example.com>, relay=1.1.1.1[1.1.1.1]:25, delay=0.11, delays=0.04/0/0/0.06, dsn=2.0.0, status=sent (250 2.0.0 589c29044d9318 Message accepted for delivery)
Feb  9 17:32:04 server001 postfix/smtp[7790]: 000E21A124F: to=<test@example.com>, relay=1.1.1.1[1.1.1.1]:25, delay=0.11, delays=0.04/0/0/0.06, dsn=2.0.0, status=sent (250 2.0.0 589c29044d9318 Message accepted for delivery)
Feb  9 17:32:04 server001 postfix/qmgr[27385]: 000E21A124F: removed

ちなみに一つのメールのログはqueueidで紐づいている。 この例の場合は 000E21A124F がqueueidである。

泥臭い方法で実装するならば、queueidを全て抜きだして、その数だけgrepして結果を加工する方法がある。 当然ながら時間がかかりすぎる。

join コマンドを利用することでもう少し賢くできる。

こんな感じにする。

$ sudo grep ': from' /var/log/maillog | awk '{print $6,$7}' | sort > from.txt
$ sudo grep ': to' /var/log/maillog | awk '{print $6,$7,$12}' | sort > to.txt
$ join from.txt to.txt

fromもtoも$6にはqueueidが入る。 joinは先頭が一致する行同士を結合してくれる。

問題としては、queueidは使いまわされることがあり、1日ぐらい経つと同じものが使われていることがある。 毎日ログをローテションするなりすれば良いと思われる。その場合に日をまたいだメールとかがあると取りこぼすのも注意が必要。 そしてその場合もqueueidに日付文字列を加えれて複数の日付のログを一度に処理することで問題が解決される気がする。

.ssh/configに記載されたホストをタブ補完する

.ssh/configに記載されたホストをタブ補完する

概要

ssh実行時に.ssh/configに記載されたホスト名を補完したいなぁとおもったのでやってみた。

同じことは他の方もやっていたので、それを参考にした。その結果ほとんどオリジナリティはない。

あくまで一例として。

参考

.ssh/config 設定内容(サンプル)

.ssh/config

HOST host001
        HOSTNAME 1.1.1.1
HOST host002
        HOSTNAME 1.1.1.2

.bashrcへの追記内容

.bashrc

_ssh_comp_func () {
  local cur
  cur=${COMP_WORDS[COMP_CWORD]} 
  COMPREPLY=($(grep 'HOST ' .ssh/config | awk '{print $2}' | grep "${cur}"))
}

complete -F _ssh_comp_func ssh

curに現在の補完中のホスト名が入る。

補完が進むと補完候補が少なくなるように grep "${cur}" している。

わざわざダブルクオートで囲んでいるのはcurに何も入っていないときにエラーが出るのを防ぐためである。

これを応用すると結構好き勝手補完できるようになって便利かもしれない。