stMind

about Tech, Computer vision and Machine learning

golangのimageパッケージを使った時

はじめに

golangで画像ファイルを開いて、画像サイズを取得するプログラムを書いていた。特に問題はなく実行できた。

package main

import (
    "fmt"
    "image"
    _ "image/jpeg"
    "log"
    "os"
)

func main() {
    file, err := os.Open("flower.jpg")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    conf, _, err := image.DecodeConfig(file)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Width=%d, Height=%d\n", conf.Width, conf.Height)
}
$ go run imgdecode.go 
Width=640, Height=427
$ identify flower.jpg 
flower.jpg JPEG 640x427 640x427+0+0 8-bit sRGB 143KB 0.000u 0:00.00

次に試したこと

画像処理を行おうとして、続けて次のようにしてみると、unknown formatになってしまった。

   img, _, err := image.Decode(file)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(img.At(0, 0))
$ go run imgdecode.go 
Width=640, Height=427
2014/10/13 22:54:17 image: unknown format
exit status 1

os.Openをやり直す

image.Decode()の前に、もう一度os.Openするとunknown formatは出なくなった。

   file, err = os.Open("flower.jpg")
    img, _, err := image.Decode(file)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(img.At(0, 0))

image.DecodeConfigとimage.Decode

なぜだろう? image.DecodeConfigに画像ファイルの先頭バイトを読んでフォーマットを判定する処理があるけど、次にDecodeで同じ処理をした時に先頭が進んでいてフォーマット判定不能になっているとか? rewindみたいなことをしないといけないのかな。 要調査です。

OSX MavericksでBashのShellshock対策をした

Apple曰く、OS Xのユーザのほとんどはbashの悪用に対して安全 - TechCrunch

確かに、自分もMacをサーバにして公開しているわけではないので、この脆弱性でリモートから攻撃される可能性は低いんですけどね。とはいえ危険が存在しているBashをそのまま使うのは良くないということで、パッチを適用する作業をしました。

パッチ適用手順は、TechCrunchのリンクからたどったページに有りました。

security - How do I recompile Bash to avoid Shellshock (the remote exploit CVE-2014-6271 and CVE-2014-7169)? - Ask Different

$ # If you want to disable auto-imported functions, uncomment the following
$ # export ADD_IMPORT_FUNCTIONS_PATCH=YES
$ mkdir bash-fix
$ cd bash-fix
$ curl https://opensource.apple.com/tarballs/bash/bash-92.tar.gz | tar zxf -
$ cd bash-92/bash-3.2
$ curl https://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052 | patch -p0    
$ curl https://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-053 | patch -p0  
$ # See note above about ADD_IMPORT_FUNCTIONS_PATCH
$ [ "$ADD_IMPORT_FUNCTIONS_PATCH" == "YES" ] && curl http://alblue.bandlem.com/import_functions.patch | patch -p0
$ cd ..
$ # Note: DO NOT ADD SUDO TO XCODEBUILD HERE
$ xcodebuild
$ build/Release/bash --version # GNU bash, version 3.2.53(1)-release
$ build/Release/sh --version   # GNU bash, version 3.2.53(1)-release
$ sudo cp /bin/bash /bin/bash.old
$ sudo cp /bin/sh /bin/sh.old
$ sudo cp build/Release/bash /bin
$ sudo cp build/Release/sh /bin

無事、パッチ適用を確認できました。

(^_^)[23:56:44 satojkovic@stMacBookPro ~]
$ /bin/bash --version
GNU bash, version 3.2.53(1)-release (x86_64-apple-darwin13)
Copyright (C) 2007 Free Software Foundation, Inc.
(^_^)[23:57:27 satojkovic@stMacBookPro ~]
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
bash: 警告: x: ignoring function definition attempt
bash: `x' の関数定義をインポート中にエラーが発生しました
this is a test

海外出張のために個人的に買ってよかったモノ2つ

何を今更なモノだけど記しておく。

ノイズキャンセリングイヤホン

photo by illuminaut

これは本当に買って良かったと思った。ヘッドフォンは圧迫感が強くて顔が痛くなるのでイヤフォンタイプを購入したのだけど、ノイズキャンセリングあるなしでこんなにも違うのかとびっくりした。というか飛行機ってメチャクチャうるさいんですね。これからはノイズキャンセリングなしでは飛行機に乗れないなぁと思った次第です。

ちなみに購入したのはオーディオテクニカので約6千円なり。BOSEのいいやつがあったけど、いきなり3万円は出せなかった...

衣類圧縮袋

photo by Ruth and Dave

これも買ってよかったし、荷物が実にコンパクトにまとめられた。今回の出張はNYに行ったのだけど、まだ秋の入り口という感じで厚手のものが必要なかったということもあるけど、1週間分の荷物が3日から4日用のキャリーバッグに余裕を持って収められた。シャツにシワが出来てしまうのは若干残念感はあるけどね。

golangの機械学習ライブラリgolearnをインストール

インストールはInstallation · sjwhitworth/golearn Wiki · GitHubに書いてある通りなんだけど、Issuesにも挙がってるエラーで追加の手順が必要だったのでメモ。 環境はOSX10.9.4とGo1.2.2。

1. Installing a BLAS implementation

インストールはbrewで簡単に出来るけど、brew updateしておかないとエラーが出た。a1c3b9aのコミットをマージした状態であれば問題なし。

$ brew update
$ brew install liblas

2. Completing the installation of Gonum's BLAS wrapper

ここは特に問題なかった。

$ go get github.com/gonum/blas
$ cd $GOPATH/src/github.com/gonum/blas
$ go install ./...

3. Installing GoLearn's internal dependencies

ここも特に問題なかったかな。

$ go get -t -u -v github.com/sjwhitworth/golearn
$ cd $GOPATH/src/github.com/sjwhitworth/golearn/ext
$ go run make.go

環境変数の設定も書いてある通りに実施する。インストールだけならばbash上で直接実行してもいいし、後々も考えてbashrcに記述してsourceで読み込み直してもいいし、どちらでも可。

$ export DYLD_LIBRARY_PATH=$GOPATH/src/github.com/sjwhitworth/golearn/ext/lib:$DYLD_LIBRARY_PATH

4. Completing the installation

$ cd $GOPATH/src/github.com/sjwhitworth/golearn
$ go get ./...

ここで追加の手順が必要になる。

追加1. liblinearのインストール

go getするとlinear.hが見つからないというエラーが出る。

$ go get ./...
# github.com/sjwhitworth/golearn/linear_models
linear_models/liblinear.go:6:10: fatal error: 'linear.h' file not found
#include <linear.h>
         ^
1 error generated.

Issue #55によると、よくわからないけれどliblinear1.94をbrewでインストールしないと動かなかった、というコメントがあってまだ解決していない様子。brewでインストールすれば良いらしいので実行。

$ brew install liblinear
追加2. liblinear.goの修正

再度go getをすると、また別のエラーが出る。

$ go get ./...
# github.com/sjwhitworth/golearn/linear_models
linear_models/liblinear.go:55: cannot use &c_y[0] (type *_Ctype_int) as type *_Ctype_double in assignment

Issue #47を見ると、liblinear1.94の場合、以前のコミットで変更された箇所をdoubleに戻さないといけない様子。Ubuntuのシステムのliblinearのバージョンが古くて、そういう変更が行われていた?らしい。doubleにrevertするPull Requestが送られているので、それがマージされるまでは手動で修正する必要あり。修正場所は51行目と53行目。

$ git diff
diff --git a/linear_models/liblinear.go b/linear_models/liblinear.go
index 82c6133..e5d0682 100644
--- a/linear_models/liblinear.go
+++ b/linear_models/liblinear.go
@@ -48,9 +48,9 @@ func NewProblem(X [][]float64, y []float64, bias float64) *Problem {
        prob.c_prob.n = C.int(len(X[0]) + 1)
 
        prob.c_prob.x = convert_features(X, bias)
-       c_y := make([]C.int, len(y))
+       c_y := make([]C.double, len(y))
        for i := 0; i < len(y); i++ {
-               c_y[i] = C.int(y[i])
+               c_y[i] = C.double(y[i])
        }
        prob.c_prob.y = &c_y[0]
        prob.c_prob.bias = C.double(-1)

確認

これでgo getが出来るようになった! 例えば、examples/knnclassifier/knnclassifier_iris.goを実行して見ると、

 ...
55 row(s) undisplayed
Iris-versicolor 26 3  54 0.8966    0.9286    0.9123
Iris-virginica  26 2  54 0.9286    0.8966    0.9123
Iris-setosa 28 0  57 1.0000    1.0000    1.0000
Overall accuracy: 0.9412

こんな結果が得られる。めでたしめでたし。

stackoverflowにあったopencvとpythonを使ったお手軽数字認識のサンプル

Simple Digit Recognition OCR in OpenCV-Python - Stack Overflow

上記リンクにある画像とコードをコピーすれば、手元で簡単に試せますYo!

前処理ステップ

数字が5行分並んだ画像を学習用の画像としてpitrain.pngという名前で保存します。その下にある学習用のスクリプトは適当な名前で保存しておきます。
スクリプトを実行すると、一つの数字が赤枠で囲われるので、対応する数字キーを押します。

f:id:satojkovic:20140721182254p:plain

「7」を入力すると次の文字が赤枠で囲まれるので、同じように対応する「4」を入力します。

f:id:satojkovic:20140721182622p:plain

といった感じで全ての文字を入力していくと、学習用の.dataファイルが二種類出来るはずです。

学習とテスト

テスト用の画像がさらにその下にあるのでpi.pngとして保存します。学習とテスト用のスクリプトも適当な名前で保存して実行すると

f:id:satojkovic:20140721183053p:plain

左が数字領域の切り出し結果で、右に認識した結果が表示されます。お手軽ですが、正しく認識されてます。

Pythonで多層パーセプトロンの実装例

NNの構成

    # initialize
    mlp = MLP(n_input_units=2, n_hidden_units=3, n_output_units=1)

XORを実現する3層のニューラルネットワークを例として実装します。入力層は2、隠れ層は3、出力層は1つのニューロンを持ちます。

class MLP(object):
    """
    3 Layered Perceptron
    """
    def __init__(self, n_input_units, n_hidden_units, n_output_units):
        self.nin = n_input_units
        self.nhid = n_hidden_units
        self.nout = n_output_units

        self.v = np.random.uniform(-1.0, 1.0, (self.nhid, self.nin+1))
        self.w = np.random.uniform(-1.0, 1.0, (self.nout, self.nhid+1))

ただし、入力層と隠れ層の間の重みvと、隠れ層と出力層の間の重みwにはバイアス項を加えるので+1とします。つまり、vは3x3の行列、wは1x4の行列となります。

--- NN configuration ---
Num of input layer units: 2
Num of hidden layer units: 3
Num of output layer units: 1
Shape of first layer weight(v): (3, 3)
Shape of second layer weight(w): (1, 4)

NNの学習

以前のエントリーで書いた誤差逆伝播法と最急降下法を用いた各層の重みの更新式をコードにします。

    def fit(self, inputs, targets, learning_rate=0.2, epochs=10000):
        inputs = self.__add_bias(inputs, axis=1)
        targets = np.array(targets)

        for loop_cnt in xrange(epochs):
            # randomise the order of the inputs
            p = np.random.randint(inputs.shape[0])
            xp = inputs[p]
            bkp = targets[p]

            # forward phase
            gjp = self.__sigmoid(np.dot(self.v, xp))
            gjp = self.__add_bias(gjp)
            gkp = self.__sigmoid(np.dot(self.w, gjp))

            # backward phase(back prop)
            eps2 = self.__sigmoid_deriv(gkp) * (gkp - bkp)
            eps = self.__sigmoid_deriv(gjp) * np.dot(self.w.T, eps2)

            gjp = np.atleast_2d(gjp)
            eps2 = np.atleast_2d(eps2)
            self.w = self.w - learning_rate * np.dot(eps2.T, gjp)

            xp = np.atleast_2d(xp)
            eps = np.atleast_2d(eps)
            self.v = self.v - learning_rate * np.dot(eps.T, xp)[1:, :]
    def __add_bias(self, x, axis=None):
        return np.insert(x, 0, 1, axis=axis)

    def __sigmoid(self, u):
        """
        Sigmoid function(Activation function)
        """
        return (1.0 / (1.0 + np.exp(-u)))

    def __sigmoid_deriv(self, u):
        return (u * (1 - u))

実装するときは、各式で出てくるベクトルや行列の次元を表示して、自分の意図通りになっているかを確認しながら進めていくと良いと思います(下に挙げた参考情報の二つ目を参照)。

XORのテスト

XORが実現できているかを確認します。

    inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
    targets = np.array([0, 1, 1, 0])

    # training
    mlp.fit(inputs, targets)

    # predict
    print '--- predict ---'
    for i in [[0, 0], [0, 1], [1, 0], [1, 1]]:
        print i, mlp.predict(i)
--- predict ---
[0, 0] [ 0.07955348]
[0, 1] [ 0.92560327]
[1, 0] [ 0.93024106]
[1, 1] [ 0.06766488]

確かにXOR学習ができているようです。

参考にした情報