2018/11/27


僕のこれまで人生の中で、2日間まるまる Vim の事を考えるなんて事なんて無かったし、今思い返してもとても刺激的な日でした。

まず始めに、VimConf というイベントを産み出してくれた ujihisa さん、kaoriya さん、運営に関わった皆さん、そしてスポンサー頂いた企業の皆様、個人スポンサーをして頂いた皆さん、本当にありがとうございました。

中には参加できないにも関わらず VimConf が上手く行く事を願って個人スポンサーになってくれた方も沢山いました。本当にありがとうございます。

今回 Vim の作者 Bram Moolenaar 氏を VimConf 2018 に呼べたのは皆さんのお力あってこそだと思っています。

これまで VimConf はどちらかというと、こじんまりしたイメージのイベントでしたが、「Bram Moolenaar 氏を呼ぶに相応しい国際会議として開催すべきだ」と決断し、一般社団法人となり、VimConf 2017 を開催しました。それでも海外からの意見では「日本人のイベントだ」と言われた事もありました。幾つか若干悔しい思いをしつつも、今年の初めから Bram Moolenaar 氏にアポイントをし始め、僕らはとうとう Bram Moolenaar 氏を VimConf に呼び寄せる事が出来ました。

Vim を知らない人にはこの凄さが伝わらないかもしれません。また Vim を知っていたとしてもこの高揚感は伝わらないかもしれません。これが普通の話なのは理解しています。ただ僕個人がずっと感じていた悔しさもあり、この嬉しさは人一倍大きな物だったからなのです。

今から10年前、僕は Bram Moolenaar 氏に会う事が出来るチャンスがありました。そしてチャンスを貰いながらもそれを自ら逃してしまいました。10年前のある日、僕と kaoriya さんは Bram Moolenaar 氏からメールを貰いました。「秋に来日して観光する予定だ。東京と京都に行くので都合が合えば会えないか」という内容でした。Bram Moolenaar 氏から直接言ってきてくれたのです。しかし不運な事にその日にどうしても外せない仕事があり、そして会う事が出来なかったのです。たまにこの事を思い出すとずっと残念な気持ちになっていました。無理してでも行ったら良かったかなと毎回考えました。

それから10年後、僕は VimConf 2017 から補助スタッフとして参加し始めました。関西なのでリアルタイムにお手伝い出来ないですがウェブサイトの更新などリモートで出来る細かなお手伝いをしました。今回 VimConf 2018 に Bram Moolenaar 氏を呼ぼうという話が決まった際は、これまでの悔しさがスッと消える思いがしたのと同時に、暫くは信じる事が出来なかったのを覚えています。僕に限らず運営スタッフも暫くは本当に確定なのか?と何度も確認していたのを覚えています。

僕が Vim にコントリビュートし始めたのは 2000 年の1月。今から18年も前の事です。あの頃からずっとメールでやりとりしていた Bram Moolenaar 氏が、VimConf 2018 の当日に目の前に現れて握手をした時には言葉が出ずにただただオロオロとしてしまいました。「I'm grad to see you」としか言えませんでしたが、Bram Moolenaar 氏も同じ言葉で返してくれました。

今回の VimConf 2018 ではキーノートスピーチを任せられました。kaoriya さんからは「Bram Moolenaar 氏に vim-jp がどういう活動をしているか伝えるチャンスでは」とアドバイスを貰いました。Bram Moolenaar 氏の前でどんな話をしようか悩みましたが、敢えて Bram Moolenaar 氏の前で機能提案をするという暴挙に出る事にしました。

内容は、vim-jp の活動報告と見せかけて DRCS Sixel のサポート、ソケットの listen、BLOB (バイト列)型の追加を提案するという物です。

正直、自分が何を喋ったのか良く覚えていませんが、昼休憩の時に Bram Moolenaar 氏に呼び止められこう言われました。

Vim にパッチ送り続けていて良かった!と思いました。一人だけこんな思いしてどうだ羨ましいだろ申し訳ないなとも思いました。

Bram Moolenaar 氏の発表もとても面白い物で、Vim プラグイン作者が欲しいと思っていた機能の幾つかを Bram Moolenaar 氏から提案する物でした。

From hjkl To a platform for plugins

その他のスピーカーの皆さんの発表もとても興味深い物でした。僕が印象に残った物をあげさせて頂くとすると Akin 氏の Onivim の話と rhysd さん(犬さん)の Vim を wasm に移植した話が面白かったです。正直 Onivim の完成度がこれほどまでとは思っていませんでした。また Vim を wasm に移植した話は、Vim のソースを知っているが故、大変さが伝わってきて頷くばかりでした。

朝10時から始まり、一日中 Vim のトークばかりだったのにとても短く感じるほど充実した内容だったと思います。

懇親会の途中、スタッフ特権で Bram Moolenaar 氏と写真を撮りました。思い出に残る一日でした。改めて皆さんにお礼を言いたいです。あと rhysd さんとは gocon で挨拶しかしてなかったので、今回少しだけでも喋れて良かったです。その他、多くの方から挨拶を頂きました。ちなみに声を掛けて頂いたおよそ8割くらいの方が Vim に関しての僕に挨拶に来られ、残り2割くらいの方が Go に関しての僕にに挨拶に来られました。海外の方から「Go のライブラリ使ってる」と言って頂けました。

次の日、VimConf 運営スタッフと日本人 Vim コントリビュータ数名、Bram Moolenaar 氏、Mastering Vim という本を出版した Ruslan Osipov 氏と一緒に Vim ハッカソン「vimthon」を開催しました。

前日、Vim の機能追加投票で上位に来ていながらも Bram Moolenaar 氏に知られていない存在だった LSP (Language Server Protocol) を目の前で見せ、ホワイトボードにどうやって LSP が動いているのかを説明しました。また Bram Moolenaar 氏がスライドの中で言っていた Vim script のコンパイルについても、AST 化が難しいという事を伝えました。帰る時間が決まっていたので少し焦ってしまった所はありましたが。

ちなみに、Bram Moolenaar 氏には前日に「vimthon は何時に来てもいいよ」と伝えてあったのですが、当日なかなか現れない Bram Moolenaar 氏に我々スタッフも少し心配していたのですが、ふと見た GitHub で30分前にコミットが push されているのを見た時は皆で笑いました。ちなみにこの20分後くらいに Bram Moolenaar 氏も現れました。

まるまる2日間 Vim だらけでした。とても濃かったですし、とても楽しかったです。ずっと悔しかった思い出もこれで忘れる事が出来る気がしています。時間の都合で最後まで参加出来ませんでしたが、最後に Bram Moolenaar 氏がこう言われたそうです。

I am amazed to experience the professional level of this conference about one piece of open source software that I happened to start 27 years ago. Thanks to all the organisers and speakers for this exciting conference!

27年前に私が始めたオープンソースソフトウェアの1つに関して、こんなにも専門レベルなカンファレンスを経験することが出来て非常に驚いています。 このエキサイティングな会議の主催者とスピーカーに感謝します。

— Bram Moolenaar, 2018-11-25

この言葉を直接聞けなかったけれど、きっとこの言葉を聞いた運営スタッフは嬉しかっただろうなと帰りの新幹線で一人感動していました。おめでとう運営スタッフ。

Posted at by



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)
 
発送可能時間:在庫あり。

Posted at by



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 で便利になりました。

Posted at by