2018/11/08


gobrain という Golang だけで実装されたニューラルネットワークを見つけたので遊んでみました。

GitHub - goml/gobrain: Neural Networks written in go
https://github.com/goml/gobrain

作りもシンプルですし、扱い方も簡単なのでちょっとしたサンプルを書くのには向いてると思います。例えば FizzBuzz であればこんな感じ。

package main

import (
    "math/rand"

    "github.com/goml/gobrain"
)

type FizzBuzz []float64

func (fizzbuzz FizzBuzz) Type() int {
    for i := 0; i < len(fizzbuzz); i++ {
        if fizzbuzz[i] > 0.4 {
            return i
        }
    }
    panic("Sorry, I'm wrong")
}

func teacher(n int) []float64 {
    switch {
    case n%15 == 0:
        return []float64{1000}
    case n%3 == 0:
        return []float64{0100}
    case n%5 == 0:
        return []float64{0010}
    default:
        return []float64{0001}
    }
}

func bin(n int) []float64 {
    f := [8]float64{}
    for i := uint(0); i < 8; i++ {
        f[i] = float64((n >> i) & 1)
    }
    return f[:]
}

func main() {
    rand.Seed(0)

    // make patterns
    patterns := [][][]float64{}
    for i := 1; i <= 100; i++ {
        patterns = append(patterns, [][]float64{
            bin(i), teacher(i),
        })
    }

    ff := &gobrain.FeedForward{}

    // 8 inputs, 100 hidden nodes, 4 outputs
    ff.Init(81004)

    // epochs: 1000
    // learning rate: 0.6
    // momentum factor: to 0.4
    ff.Train(patterns, 10000.60.4false)

    for i := 1; i < 100; i++ {
        switch FizzBuzz(ff.Update(bin(i))).Type() {
        case 0:
            println("FizzBuzz")
        case 1:
            println("Fizz")
        case 2:
            println("Buzz")
        case 3:
            println(i)
        }
    }
}

今日はこの gobrain を使って画像分類を作ってみました。特徴抽出やノーマライズはやってないので実用的ではない事に注意下さい。

まず flickr 等から薔薇とユリと向日葵の画像を貰ってきて下さい。

薔薇
薔薇
ユリ
ユリ

刺青混じってませんか...

向日葵
向日葵

各20毎程度で構いません。次に画像を読み込んで3チャネルに分割します。

func decodeImage(fname string) ([]float64error) {
    f, err := os.Open(fname)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    src, _, err := image.Decode(f)
    if err != nil {
        return nil, err
    }

    bounds := src.Bounds()
    w, h := bounds.Dx(), bounds.Dy()
    if w < h {
        w = h
    } else {
        h = w
    }
    bb := make([]float64, w*h*3)
    for y := 0; y < h; y++ {
        for x := 0; x < w; x++ {
            r, g, b, _ := src.At(x, y).RGBA()
            bb[y*w*3+x*3= float64(r) / 255.0
            bb[y*w*3+x*3+1= float64(g) / 255.0
            bb[y*w*3+x*3+2= float64(b) / 255.0
        }
    }
    return bb, nil
}

これで画像データが1次元の float64 配列になりこれが入力となります。これに薔薇やユリや向日葵のラベルを紐づけるためにラベルの添え字番号を使い同じ様に float64 配列にする関数を作ります。

func bin(n int) []float64 {
    f := [8]float64{}
    for i := uint(0); i < 8; i++ {
        f[i] = float64((n >> i) & 1)
    }
    return f[:]
}

func dec(d []float64int {
    n := 0
    for i, v := range d {
        if v > 0.9 {
            n += 1 << uint(i)
        }
    }
    return n
}

あとは gobrain を初期化して学習させれば推論器が出来上がるのですが

ff.Init(len(patterns[0][0]), 40len(patterns[0][1]))
ff.Train(patterns, 10000.60.4false)

gobrain は Pure Go という事もあり struct をそのまま JSON にエンコードしてやればこれがモデルファイルになる事に気付きました。

func loadModel() (*gobrain.FeedForward, []stringerror) {
    f, err := os.Open("labels.txt")
    if err != nil {
        return nilnil, err
    }
    defer f.Close()

    labels := []string{}
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        labels = append(labels, scanner.Text())
    }
    if scanner.Err() != nil {
        return nilnil, err
    }

    if len(labels) == 0 {
        return nilnil, errors.New("No labels found")
    }

    f, err = os.Open("model.json")
    if err != nil {
        return nil, labels, nil
    }
    defer f.Close()

    ff := &gobrain.FeedForward{}
    err = json.NewDecoder(f).Decode(ff)
    if err != nil {
        return nil, labels, err
    }
    return ff, labels, nil
}

func makeModel(labels []string) (*gobrain.FeedForward, error) {
    ff := &gobrain.FeedForward{}
    patterns := [][][]float64{}
    for i, category := range labels {
        bset, err := loadImageSet(category)
        if err != nil {
            return nil, err
        }
        for _, b := range bset {
            patterns = append(patterns, [][]float64{b, bin(i)})
        }
    }
    if len(patterns) == 0 || len(patterns[0][0]) == 0 {
        return nil, errors.New("No images found")
    }
    ff.Init(len(patterns[0][0]), 40len(patterns[0][1]))
    ff.Train(patterns, 10000.60.4false)
    return ff, nil
}

func saveModel(ff *gobrain.FeedForward) error {
    f, err := os.Create("model.json")
    if err != nil {
        return err
    }
    defer f.Close()
    return json.NewEncoder(f).Encode(ff)
}

全体のソースは GitHub に置いてあります。

GitHub - mattn/flower-detect
https://github.com/mattn/flower-detect

実際に試してみます。

test

結果は「sunflower」。そうだよ向日葵だよ。

test

結果は「rose」。そうだよ薔薇だよ。

test

結果は「lilium」。そうだよユリだよ。

gobrain を JSON で出力してモデル扱いにするというこの方法を使えば、簡単な画像分類であればインストールが難しい TensorFlow を使わずともポータブルに実行出来ます。特に GPU を使う程ではないといった場合にも便利かなと思います。一応 smartcrop というパッケージを使って画像内で注目される部分で crop する様にしてありますが、いくらかの画像では失敗します。これは画像をノーマライズしていないのでしょうがないですね。学習には10分くらい掛かると思います。

尚 Golang で TensorFlow やりたい人は以前書いた記事の方を参照下さい。

Big Sky :: golang で tensorflow のススメ

tensorflow といえば Python と思っておられる方も多いのではないでしょうか。間違いではないのですが、これは初期に作られた Python 向けのバインディングに研究者達が多く食いついた結...

https://mattn.kaoriya.net/software/lang/go/20180825013735.htm
Go言語による並行処理 Go言語による並行処理
Katherine Cox-Buday
オライリージャパン / ¥ 3,024 (2018-10-26)
 
発送可能時間:在庫あり。


2018/11/05


Let's Encrypt という無料の SSL を提供しているサービスがあり、これを簡単に操作できる CLI として Lego というツールがあります。以前から Lego に MyDNS をサポートして貰おうとプルリクエスト を送っていたのですが、ようやく本日マージされました。

環境変数 MYDNSJP_MASTER_IDMYDNSJP_PASSWORD を設定した上で以下の様に実行すると登録されます。

$ lego -m your@email.com -a -x http-01 -x tls-alpn-01 --dns mydnsjp -s https://acme-staging-v02.api.letsencrypt.org/directory -d xxx.mydns.jp -d *.xxx.mydns.jp run

また更新したい場合は以下の様に実行して下さい。

$ lego -m your@email.com -a -x http-01 -x tls-alpn-01 --dns mydnsjp -s https://acme-staging-v02.api.letsencrypt.org/directory -d xxx.mydns.jp -d *.xxx.mydns.jp renew

ワイルドカードなサブドメインも簡単に扱えます。また certbot をインストールするには python が依存していたりと少し面倒でしたがシングルバイナリの Lego で便利になりました。


2018/10/22


O'Reilly Japan, Inc. 様に献本頂きました。ありがとうございます。

そして献本頂く際にお声を掛けて頂いた、本書の翻訳を担当された ymotongpoo さんにもお礼を申し上げます。ありがとうございます。

本書の訳は非常に素晴らしく、とても原文が英文であったとは思えないほど綺麗で、読んでいく中で「原文でどの様に表現されているんだろう」といった引っかかりも無く、とてもスムーズに読み進められました。

Go 言語に関わって随分と長くなってきました。初めて Go を知ってからユーザがどんどん増える様を見る事が出来るのは正直に言って非常に嬉しいです。

ふと Go の魅力は何かと聞かれたら幾つか挙げる事が出来ますが、間違いなく選ぶのが「非同期処理の簡単さ」です。これまで多くの開発者が OS スレッドで実現してきた非同期処理を、Go 言語は少ないイディオムとインテリジェントなランタイムを使って誰でも簡単に非同期処理を実装できる仕組みを提供しています。

一般的に非同期処理の実装が難しいと言われるのには理由があります。それは直面している、ボトルネックがどのパターンで発生しているのか、また解決するにはどの手法を選ぶかの選択肢が多すぎるのです。

どこを関数に切り出してスレッドにし、どうやってデータを受け渡し、どうやってスレッドを待合せたら良いのか、慣れたプログラマでもそこそこ難しい問題です。

もしあなたが誰かに仕事を依頼し、その間に自分の仕事をしたかったとしましょう。「これで自分の仕事に集中できる」そう思うかもしれません。しかし依頼した仕事が終わればそれを手元の作業に取り込みたいとも思うでしょう。どうやって依頼した仕事の完了を待ったら良いでしょうか?都度、仕事が終わってないか確認しますか?それとも依頼した人に仕事の完了を伝えて貰いますか?もしかすると依頼した人には小まめにヒントを与える必要があるかも知れません。やりかたは様々です。一番効率の良い手法を選ばなければなりません。

Go の非同期処理は CSP (Communicating Sequential Processes) という並行処理の為の設計理論を元にしており、同期型のメッセージパッシングを使う事で CPU の負荷を抑えつつ各非同期処理を分かりやすく実現しています。このメッセージパッシングが goroutine と channel という言語レベルで記述可能というのが Go の特徴です。例えば channel を使って入力を2つに分ける tee を実装するのであれば以下の様に簡単に書け、しかも読む人にも分かりやすいのです。

func tee(in chan string) (<-chan string<-chan string) {
    out1, out2 := make(chan string), make(chan string)
    go func() {
        defer close(out1)
        defer close(out2)
        for v := range in {
            out1, out2 := out1, out2
            for i := 0; i < 2; i++ {
                select {
                case out1 <- v:
                    out1 = nil
                case out2 <- v:
                    out2 = nil
                }
            }
        }
    }()
    return out1, out2
}
ちなみに tee が何故 tee と呼ばれているかというと、入力と出力の枝が T になっているからです。(豆知識)

本書ではあり得る非同期のパターンの多くを分かりやすく解説してくれています。そして Go 言語では非同期処理をどの様に解決できているかを詳しく紹介してくれています。時折混ぜられるユーモアのある話もなかなか良かったのですが、僕が思うにこの本の良い所は、Go 言語で悩むであろう並行処理の設計方針について敢えて問題を読者に問い掛け、そして具体例で解き明かしつつ説明している所だと思います。そして多くのページを割いて間違った使い方についても紹介されています。どうするとデッドロックが起きてしまうのか、どうするとライブロック(道で向かいから来る人と同じ方向に避けてしまうアレです)が起きてしまうのか、なぜこのコードはスケールしないのか、多くのシーンをコード付きで説明してくれています。

特に興味深かったのが、channel を使うべきか、sync を使うべきかの決定木の図でした。yes/no に従いどちらを使うべきかをアドバイスしてくれます。この記事では紹介しませんが、ぜひ本書を読んで確認して頂くと良いかと思います。

一つ言っておくと、本書を読んでも Go での非同期処理のコードがスラスラ書ける様にはならないと思います。ただ今までなんとなく分かった気分で書いてしまっていた非同期処理が明確な理由を持って理解できる様になると思います。

Go を触り始めの方が多くハマるであろうケースも沢山書かれています。例えば Go の非同期処理に初めて触れた人は、きっと無限に goroutine を作り続けてしまうかもしれません。また Go が少し分かってくると、重たい処理を goroutine で起動できる様になりますが、goroutine とメイン goroutine で同時に変数を触ってしまい不整合を起こしてしまうというケースもあります。さらに Go が分かってきたとしても、使いどころを間違ってしまい goroutine を使ってヌルヌル動く様にはなったけれど結果としてパフォーマンスが大して速くならないといったケースもあるでしょう。Go を使ったとしても非同期処理を書くのは難しいのです。それでも Go が多くの人から評価されているのは少ないイディオムで多くの非同期パターンを記述できるからなのです。

goroutine と channel だけ使えば多くの非同期パターンを記述できます。

Big Sky :: Go 言語の非同期パターン

Go は goroutine という非同期の仕組みを提供していますが、使い方次第では色々なパターンが実装できる為、初めて goroutine を見た人はどの様な物が正解なのか分からない事があります。以...

https://mattn.kaoriya.net/software/lang/go/20180531104907.htm

2種類のイディオムだけでこれだけのパターンを書けるのです。とは言っても goroutine と channel で記述するとタイムアウトやキャンセルと言った処理は冗長になり得ます。Go はこの問題に context パッケージを提供する事で解決しているのですが、本書ではこの context が生まれた経緯や、なぜこの context が goroutine/channel の問題を解決できるのかといった根底の話も書かれています。

非同期処理が苦手な人が読むと少しクラクラするかもしれませんが、全体を通してもとてもリズムが良く、解説の順番も「上手いな」と思える内容でとても良かったです。そして今 Go 言語を書いている皆さんは今後も goroutine と channel を使って多くのコードを書くと思います。そのコードが何故必要になるのか、この非同期処理でどれほど効果が得られるのか、なぜスケールしないのか、そういった事を学びたい人らば必読の本だと思います。

Go言語による並行処理 Go言語による並行処理
Katherine Cox-Buday
オライリージャパン / ¥ 3,024 (2018-10-26)
 
発送可能時間:在庫あり。