Go Proverbsを勉強がてら和訳して少し解説した

Go (その3) Advent Calendar 2016 11日目の記事です。

Go言語の生みの親、Rob Pikeが2015年のGopherfestのセッションで 言っていた、Go Proverbsを自分の勉強がてら和訳してちょっと解説してみる。

後半に行くに連れて集中力が切れたり、いろいろ調査不足のところや、認識違いの部分もあると思うので、 そういう部分のところがあったら是非指摘してください。自分でも気づいたら修正していきます。

ちなみにタイトルの「Go Proverbs」は、動画の冒頭で囲碁の話をしているとおり「碁の格言」の直訳で、 「Goの格言」にかかっていておしゃれ。

メモリを共有して通信したり、通信してメモリを共有してはならない

原文は"Don't communicate by sharing memory, share memory by communicating."

これはGoでのメモリ共有モデルのお話。 (参考:https://blog.golang.org/share-memory-by-communicating)

CやJavaではメモリ空間(変数)を共有することでプロセス間で値をやり取りします。 しかし、正しく排他制御を行わないとデータ破壊が発生する可能性があります。 また、次のプロセスに情報を渡すときには現在行っている処理が終了したことを次のスレッドに伝える仕組みを用意する 必要もあります。(この仕組みを実装するためにJavaではConditionインタフェースが用意さています) これらの実装を間違うと、デッドロックやデータ破壊が発生してプログラムが正常に動作しなくなってしまいます。

Goではプロセス間の通信をchannelという仕組みを用意して、 メモリを共有するのではなく、情報(メッセージ)を送受信することで実現しています。 channelは以下のようにデータをやり取りします。

ch := make(chan int) // channelの作成
ch <- 1    // 書き込み
i := <- ch // 読み込み

この時、channelへの書き込みと読み込みはGo本体が自動的に排他制御を行っているため、 プログラムを実装する側が排他制御を実装する必要はありません。 また、以下のように読み込みの時にそのチャンネルに何も書き込まれていないと、その部分でプログラムはブロックします。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)

    go func() {
        time.Sleep(3 * time.Second)
        ch <- 1 // 3秒待ってからchannel書き込み
    }()

    i := <-ch      // 読み込み時には何も書き込まれていないのでブロックする
    fmt.Println(i) // 3秒後に実行
}

これにより、次のスレッドに現在行っている処理が終了したことを伝えるには、channelにデータを書き込むだけで よくなります。

このあたりのことを更に詳しく知りたい場合は、WebDBで牧さん(@lestrrat)が非常に詳しく解説している記事が あるので、そちらを読むことおすすめします。

並行と並列は違う

原文は"Concurrency is not parallelism."

「並行処理」と「並列処理」は、どちらも同時に処理を実行するという日本語ですが、意味合いが異なります。 このあたりはRob PikeがHerokuのWaza conference2012 (https://talks.golang.org/2012/waza.slide)で解説をしています。 (あと、先日Go初心者LT会で発表したりしました。リンク)

「並列処理」は同じ処理を行うプロセスを同時に実行している状態を指し、 「並行処理」は別の仕事をするプロセスを同時に実行している状態を指します。

Rob Pikeのスライドの14ページ目の図と 19ページ目の図との対比がわかりやすいと思います。 14ページの図のように、ドキュメントの山や焼却炉を複数のプロセスでアクセスしようとすると、ロックが発生したり プロセス同士を同期させる必要があり、それらがボトルネックになってしまいます。 しかし、19ページの図のように、1つのプロセスが1つのリソースにアクセスするようにプロセスを立ち上げると、 それぞれのプロセスが干渉しなくなり、無駄なく処理を続けさせることができます。

Go言語が持っている並行処理の機能はこれらを簡単に実現できるため、高速化の必要な処理を書くときは覚えておきましょう。

チャンネルは排他的で直列になるように調整する

原文は"Channels orchestrate; mutexes serialize."

Goのチャンネルは特に何もしなくてもで排他的かつ直列にデータを送れるようになっています。 そのため、プログラマがgoroutine間でデータのやり取りをする際、ロックや順番等をあまり気にせずに使用することができます。

大きなinterfaceは抽象度が低い

原文は"The bigger the interface, the weaker the abstraction."

これはタイトル通りで、interfaceの実装はなるべく小さくしましょう(言い聞かせ)。 小さいinterfaceで設計できていると構造体がinterfaceを満たすのが非常に容易です。 実際にGoのデフォルトパッケージにあるinterfaceは非常に小さく設計されています。

具体的にGoが標準で用意しているWiterを実装して、人に喋らせてみます。

package main

import (
    "fmt"
    "io"
    "os"
)

type human struct {
    w io.Writer
}

func (h *human) Write(p []byte) (int, error) {
    p = append([]byte("Ω< "), p...)
    h.w.Write(p)
    return len(p), nil
}

func main() {
    h := &human{w: os.Stdout}
    fmt.Fprintln(h, "hello world")
}

0値を有益にしなさい

原文は"Make the zero value useful."

Goでは値が宣言された段階で初期化されます。 この機能自体は最近の言語だとよくあることだと思うのですが、 重要なのは0値に意味に対してちゃんと意味を持たせておくことだと思います。

Goでは構造体生成時その内部のプライベートな値も全て0値で初期化されます。 しかし、Javaのコンストラクタの様に、使う側に必ず初期値を指定させる方法がありません (Newで始まるメソッドを作成することはありますが)。 そのため、初期化するための関数やNew関数を経由せずに呼び出されること困るようなコードは書くべきではなく、 必ず0値で初期化されたときのことを考慮してコードを書きましょう。

インターフェース型は何も言わない

原文は"interface{} says nothing."

Goではどんな値でも入れることの出来るとしてinterface{}型があります(正確にはメソッド定義のないinterfaceだから どんな値でも入れられる)。 引数の型等に仕様するとどんな型でも受け取れるようになり、汎用的なものになる気がするのですが、 Goの1つの特徴である、静的型付け言語という特性を捨てることになります。 interface{}型が使用されている部分で、正しく型アサーションや例外処理等が行われていれば問題ないのですが、 それが行われていない時にはコンパイル時にバグが発生することを察知できません。 何でも渡したい時には、もしかしたらMap型の方が適切だったり、自ら構造体を定義しておいたほうが余計なバグが 減らせる可能性が高いので、なんでもかんでもinterface{}型で引数を受け取るようにするのはやめましょう。

Gofmtのスタイルは誰かのお気に入りではないが、誰もがお気に入りだ

原文は"Gofmt's style is no one's favorite, yet gofmt is everyone's favorite."

必ず

go fmtを

しましょう

小さいコピーは小さい依存より良い

原文は"A little copying is better than a little dependency."

ある程度Goを書いていると、再利用をするためにライブラリ化をしていくと思います。 しかし、再利用が目的になることは避けないといけません。 再利用するためにライブラリ化してそれを使うということは、そのライブラリに依存するということになります。 Rob Pikeは講演で、表示のためのライブラリが巨大なUnicodeのパッケージに依存してしまう例を上げていました。 その時はそれを避けるために必要な部分をコピーして、Unicodeのパッケージに依存することを避けた、と言っています。

普段開発をしていて、流石にUnicodeレベル大きさのパッケージに依存することはあまりないとは思いますが、 依存することでテストが重くなったりするのであれば、敢えてライブラリをimportせず、ソースコードをコピーすることも検討しないと いけないのかもしれません。

システムコールは常にビルドタグで保護されるべきだ

原文は"Syscall must always be guarded with build tags."

Go言語はクロスコンパイル言語です。そのため、1つのコードが複数の環境で動くように心がけないといけません。 Goだけで書かれたパッケージを使用している場合は特に意識することはないと思います。 しかし、特定のアーキテクチャや環境に依存するシステムコールを利用する場合は必ずビルドタグで保護しましょう。 ビルドタグで保護することで、コンパイル時にその環境では動かないことを示すことができます。

Goの公式のページにも書いてある通り、 以下のようにファイルの行頭にビルドタグをつけることで、そのファイルがビルドできる環境を指定することができます。 他にもファイル名で指定する方法もあるようです。

// +build linux darwin

CGoは常にビルドタグで保護されるべきだ

原文は"Cgo must always be guarded with build tags."

これもシステムコールの理由と同じですね。 Cgoも同様にビルドタグで制御できるので、CでコンパイルされたものをGoで使用するときには必ずビルドタグを保護しましょう。

CgoはGo言語ではない

原文は"Cgo is not Go."

(自分はcgoを扱ったことがないのでここはちょっと荷が重い……) cgoはGo言語からC言語を扱うための機能です。しかしここではcgoを使うのであれば、それは既にGoではないと言っています。 理由としては以下のような物があげられるようです(参考:cgo is not Go)。 * 遅いビルド * 複雑なビルド * クロスコンパイルではなくなる * パフォーマンスの問題 * 呼び出すCが別の何かに依存していることがある * 配布時の展開の複雑さ

なにか必然的な理由(参考先ではグラフィックドライバやウィンドウシステム)がない限り、なるべく使うべきでは ないのかもしれません。

安全ではないパッケージでは保証はない

原文は"With the unsafe package there are no guarantees."

このあたりは自明なことかもしれません。 外部パッケージを使用する時にはそれが本当に安全で使いやすいものかを確認しなければいけません。 もし安全でないものを使っていると、glide upする度にコードが動かなくなることもあります(実体験)。

見やすい実装は巧みな実装より良い

原文は"Clear is better than clever."

これもある程度自明かもしれません。 Go言語の設計思想として、「読みやすく」という物があります。 それを実現するのにより見やすい実装が書けるのなら、それを採用するべきであるし、それがGo言語の哲学にもなっています。

リフレクションは決して綺麗なものではない

原文は"Reflection is never clear."

リフレクションというのは、コードを動的に操作することです。 リフレクションを使うと、プライベートの値を外から直接弄ったりメソッドの実装を置き換えたりと割と闇なことができます。 ライブラリを作る側がある程度汎用的にするために使用することはあっても、普段から使うのは避けるべきでしょう。

エラーは値だ

原文は"Errors are values."

参考:Errors are values Go言語の特徴の1つにエラーを値として扱う、というものがあります。 「エラーハンドリングをifでやるんでしょ?」と思い以下のようなコードを量産しがちですが(自分も最近理解した)、本質は違います。

if err != nil {
}

Goのエラーは「値として扱えるから、ロジックで処理ができる」というのが本質になります。

参考サイトからそのままソースコードを借りてきます。 例えば、Goを書いていると以下のようなエラーハンドリングはよく発生します。

_, err = fd.Write(p0[a:b])
if err != nil {
    return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
    return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
    return err
}
// and so on

おそらくGoを書いたことのある人は自分で書いたこともあるし、他人が書いたのを見たこともあると思います。 これでも確かに動作はしますが、明らかに冗長なコードではあります。 そこで、エラーは値であることを利用して簡単なロジックをプログラムを作成し、きれいにハンドリングしてみます。

まず、以下のような構造体とメソッドを用意します。

type errWriter struct {
    w   io.Writer
    err error
}

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}

errWriterはWriterを実装したものと、errorを持ちます。 writeメソッドは、byteのスライスを受け取りそれをWriter interfaceが持つWriteメソッドに書き込みます。 その時、値として発生するエラーを構造体の中に保持しておき、writeメソッドが使われる度にその保持したエラーを見て 処理するかどうかを決めています。

上記の構造体を使用することで、先程の冗長な処理を以下のように書き換えることができます。

ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

この手法が絶対的に正しいわけではないですが、ここで重要なのはエラーをプログラムの中で処理できるということです。 エラー処理をするときには脳直でif文を書くのではなく、値として処理できることも覚えておきましょう。

エラーをチェックするだけでなく、それらを正常に処理しなさい

原文は"Don't just check errors, handle them gracefully."

参考:Don’t just check errors, handle them gracefully

同じくGo言語のエラーの話です。

こちらも参考サイトのソースコードを借りてきます。

以下のようなコードがあったとします。

func AuthenticateRequest(r *Request) error {
    err := authenticate(r.User)
    if err != nil {
        return err
    }
    return nil
}

これ自体は単純で、authenticateメソッドが返すエラーを上のメソッドに返しているだけです。 こちらもありがちなコードだと思うのですが、もしauthenticateでエラーが発生し、 上にメソッドを返した時に、どこでエラーが発生しているのかを判別する手段がありません(参考サイトを見た時に画像で笑った)。

つまり、ただエラーチェックをして、その値をただ上に渡しているだけだと、どこで何が発生したのかわかりにくくなってしまいます。 Goではfmtパッケージで文字列を整形した上でエラーを作成するメソッドが用意されています。 それを利用して、以下のように、どこでエラーが発生したのかを明確にして上に返してあげないといけません。

func AuthenticateRequest(r *Request) error {
    err := authenticate(r.User)
    if err != nil {
        return fmt.Errorf("authenticate failed: %v", err)
    }
    return nil
}

デザインはアーキテクチャ、名前はコンポーネント、ドキュメントは詳細

原文は"Design the architecture, name the components, document the details."

これもタイトル通りだと思います(ちょっと抽象的だったので理解できていない)。 幼稚園児レベルのヒアリングとGoogleの字幕機能を駆使してなんとか元動画の英語を 理解しようとした結果、

構成要素の詳細を示すアーキテクチャをデザインし、それを構成するコンポーネントに名前をつける。 コンポーネントの名前が適切であれば明確かつ自然にプログラムが書けるが、 説明しなければならない詳細についてドキュメントを書きましょう。

という感じで理解しました。

解釈が間違っていたり、こういう意味だよというのお待ちしています。

ドキュメントは使う人達のために

原文は"Documentation is for users."

よくプログラムにはドキュメントを書け、という話がありますが、それが本当に意味のあるドキュメントかどうかを 意識しましょう、ということです。 その関数が引数に何を取り何を返すのかをただ書くのではなく、その関数がどのような意図で作られて、 どのような意図でその処理をするのかを書くべきだということです。

これは会社でプログラムを書くようになって強く実感するようになりました。 Goに限らず、という話ですかね。

panicさせるな

原文は"Don't panic."

Effective GoのErrosの項目を読みましょう、というお話。 Goにはエラーを発生させる方法として、panicという関数が存在します。 プログラムを継続的に実行することが難しくなった時に発生するエラーです。 エラーを値として扱うものとは大きく違い、その場でプログラムが停止してしまいます。 安全にエラーを返す方法があるのであれば、間違いなくそちらを使うべきです。

Go言語LT大会! 「最近、Go言語始めました」の会でLTしました

Go言語LT大会でLTしてきました。

資料です。

見る人が見ればわかっちゃうんですが、Rob Pikeの発表を丸パクリLT用に焼き直して、デザインパターンとか絡めたものです。

もっと詳しく知りたい方は、Concurrency is not Parallelismとか Concurrency is not parallelism - The Go Blogとかを見ると良いと思います。 初めてみたときは結構感動しました。

LT上では時間の関係上スレッドという単語を使っていましたが、正確にはgoroutineはスレッドとは異なるものです。 その辺の説明はWEB+DB PRESS Vol.95とかにわかりやすい説明が載っているので、 詳しく知りたい方は読んでみると良いかもしれません。

最近Go言語の波が来ている気がするのでその波に乗っていきたい所存。 (そしてやっぱり実年齢を言うと驚かれるので、見た目の若々しさを手に入れたい)

VimConf2016行ってきました

VimConf2016に行ってきました。

発表資料のまとめと軽く所感をば書いてみます。

Introduction to Vim 8.0

2016年9月にリリースされたVim8.0の新機能紹介とVimの歴史についてのスライドでした。

Vimのコントリビュータの上位10人に日本人が5人含まれている、という情報が一番驚きでした。 最近上司に、「どうして最近Vimは流行ってるの?」みたいなことを聞かれましたが、 この事が無関係では無い気がします。

Vim as the MAIN text editor

他のエディタからVimに移行したお話。

Vimを使っていると、よく「教えて」と言われるので、質問であったアンチパターンは 他人に教えるときのアンチパターンにしていこうと思います。

Denite.nvim ~The next generation of unite~

https://gist.github.com/Shougo/7c78b3a1725f70c1435d004ed14f2558

Vim界のアイドル兼暗黒美夢王ことShougoさんの発表。

VimのFold機能を利用して、Markdownをスライド代わりにするという発表方法でした。

denite.nvimの簡単な説明、Unite.vimがなぜ遅いのか、denite.nvimはVim8.0で動くのか、おまけ、という構成でした。

個人的注目ポイントは、おまけでShougoさんがおっしゃっていた、既存のShougoさん作のプラグイン (neocomplete, neosnippet, vimfiler, etc...)をDark poweredで書き直す(かも)と言っていたことですかね。

Go、C、Pythonのためのdeoplete.nvimのソースの紹介と、Neovim専用にpure Goでvim-goをスクラッチした話

http://go-talks.appspot.com/github.com/zchee/talks/vimconf2016.slide

個人的に非常に熱かった発表。

内容としてはdeoplete.nvim用に既存の補完ツールを書き換えた、というお話だったのですが、 deoplete-goでDelveの機能を使えるのが最高すぎました。

これでGoをIntelliJで書いてる派の人たちにデバッグのことで胸を張っていけます。 (ちょっと中身を見てみようかと思ってる)

エディタの壁を越えるGoの開発ツールの文化と作成法

Go界隈で有名なtenntennさんの発表。

Goの文化とエディタのプラグイン作成の親和性がテーマでした。

懇親会でも、Shell ScriptのフォーマッタがGoで書かれていたという話をしたので、 Goでプラグインやツールを作る文化ががっつり来ている気がします。

vim-mode-plus for Atom editor

http://qiita.com/t9md/items/0bc7eaff726d099943eb

Atomプラグイン開発者t9mdさんの発表。

vim-mode-plusしゅごい(小並感)。

恥ずかしながらテキストオブジェクトとオペレータの組み合わせ操作を初めて知ったので、 今後はそれらも意識して使って、手癖にしていきたい。

vimの日本語ドキュメント

https://drive.google.com/file/d/0ByQIX4Ls1SHlZ3JtOWxHUUkwaDA/view

Koronさん(kaoriyaさん)の日本語ドキュメント翻訳の話。

https://github.com/vim-jp/vimdoc-ja https://github.com/vim-jp/vimdoc-ja-working

Vim script parser written in Go

https://docs.google.com/presentation/d/1A6_A7XzPoHv_wG5N_R6zbgYKBX2ycii6BCzR-7b-nOw/pub

VimのパーサをGoで書いたお話。 将来的にはフォーマッタのような機能も実装されるそうです。呼び方はvimfmt(ゔぃむふむとぅ)なんですかね?

僕の友達を紹介するよ

https://aiya000.github.io/Maid/my-vim-friends/my-vim-friends#/

vimプラグインの紹介。

気になったのは * vim-alignta * undotree * vim-textobj-indent

aligntaはalignとの比較をしてみて良さそうだったら乗り換えてみようと思います。 vim-textobj-indentは、テキストオブジェクトとセットで覚えていこうかと。

Best practices for building Vim plugins

https://gist.github.com/thinca/785171e327e66c48d2d293690dc2f65a

Vimプラグインを作る時に気をつけてほしいことのまとめ記事でした。 :help desgin-documentedを熟読しようと思います。

全体所感

後半に行くに連れて集中力が下がっていってるのが伝わってしまう

Vimを使い始めて3年弱ですが、まだまだVim弱者なので、得た情報を糧にしていこうと思います。 あとは、会場でもGoを書いている人が多く、VimだけじゃなくてGoの強さも感じました。

とりあえずdeoplete-goが使いたすぎるのでnvim移行を開始します。 zcheeさんと少しお話をして、中身がほぼGoだということも聞いたので、 使いながら中身も見てみようと思います。

Vimmerの集まりに参加するのは初めてだったのですが、非常に濃い内容で楽しかったです。 発表者の方々と会場の準備をしてくださった方々に感謝です。

ブログ書いたので私のVimConf2016はこれにて終了です。

(直接会って話していても新卒に見られたことが無いので大丈夫だと思います)


VimConf 2016

vimで折りたたみの状態を保存する方法

vimを使ってそれなりに経つのに、最近になって設定したのでメモ。

vimにはmkviewという、現在の折りたたみの状態を保存するコマンドと、 loadviewという保存した折りたたみ状態を読み込むコマンドがある。

もちろん自分でコマンドを入力するのはめんどくさいので、 ファイルを保存した時と読み込んだ時に勝手にコマンドを実行して欲しい。

ということでautocmdを使って、勝手に実行してくれるようにするわけだが、 先人が色々なことを考慮した上で mkviewloadviewを自動呼び出しする設定を 公開してくれている。

Hack #84: バッファの表示設定を保存する

細かい説明もリンク先に書いてあるので、自分はリンク先の設定をそのまま以下のようにコピペしただけ。

" Save fold settings.
autocmd BufWritePost * if expand('%') != '' && &buftype !~ 'nofile' | mkview | endif
autocmd BufRead * if expand('%') != '' && &buftype !~ 'nofile' | silent loadview | endif
" Don't save options.
set viewoptions-=options

1つ躓いたところとして、Go言語を開いて保存したときには、その時点での折りたたみが保存されずに 全て折りたたまれる、という現象が起きた。

少し探ってみると、vim-goの設定で、保存時に自動でfmtをかける機能がONなのが原因だった。

ということで、vim-goの設定の一部を以下のように変更して、Fmtコマンドを自動で行ってくれる設定を追加した。

" Not working
" let g:go_fmt_autosave = 1
let g:go_fmt_autosave = 0
autocm BufWritePre *.go silent Fmt

これで大規模なコードをvimで見てもストレスなくコードが書けるように!

機械学習の環境をDIGITSとDockerを使って簡単に整える話

機械学習が何をやっているのか全くわかっていなくても 簡単に画像処理の機械学習を行える環境を整えた話。

この記事の目標は、何もわからなくてもとりあえず画像を機械に学習させる、です。 (細かい部分が全くわからないことの言い訳)

今回は、Digitsという、NVIDIAが作っている Caffeという機械学習ライブラリのWebアプリを使ってみる。 また、めんどくさい導入部分を全部Dockerにやってもらう。

事前準備

  • docker-machine導入済み
  • docker-compose導入済み

docker-machineでdefaultという名前のマシンを作ってある前提で進めます。

docker-machineってなんだよって人は 公式ドキュメントのDocker Machineの項目を 読めばいいと思う。

Digitsが動いているコンテナを立てるまで

以下のdocker-compose.ymlファイルを適当なディレクトリに配置し、 同じ階層でdocker-compose up -dを実行する。以上。

実行が終わったらdocker-machine env defaultコマンドで出てくるIPに ポート5000でアクセスすればWebアプリが動いているのが確認できるはず。

これだけで画像の学習させる環境ができちゃう。便利。

# docker-compose.yml
digits:
  image:
    kaixhin/digits
  ports:
    - "5000:5000"
  volumes:
    - ./images/:/mnt/volumes/images

Docker HubにDigitsのコンテナイメージが あったので、ありがたく使わせて頂いた。

コマンドを実行した時に作られるimagesディレクトリは、 後で学習する画像ファイルを設置する場所になるのでそのままにしておく。 もし作成されてなかったら、imagesディレクトリを作成してから もう一回docker-compose up -d

学習させてみる

とりあえず、右上の「ログイン」ボタンからログインしておく。 出てきたフォームに適当に名前を入れるだけでOK。

データセットの作成

学習させるためには、データセットが必要なのでまずはそれの作成から。

自動で作成されたimagesの中に、学習させたい画像をディレクトリ毎にわけて設置する。 例としてオクスフォード大学で公開されている ペット画像のデータセットを使ってみる。

上記リンク先の「Dataset」から、圧縮されたペット画像をダウンロードして展開。 展開すると、images/ディレクトリとその下に37種類のペットの画像ができるはず。

展開された画像を、以下のようなディレクトリ構造になるように配置する。

ちなみに、全種類の動物を使うと非常に重いので、 お試しの人は、3、4種類ぐらいを配置すればいいと思う。

docker-compose.yml
images
├── Abyssinian
│   ├── Abyssinian_1.jpg
│   ├── Abyssinian_2.jpg
│   ├── Abyssinian_3.jpg
~ ~ ~
│   ├── Abyssinian_197.jpg
│   ├── Abyssinian_198.jpg
│   └── Abyssinian_199.jpg
├── Bengal
│   ├── Bengal_1.jpg
│   ├── Bengal_2.jpg
│   ├── Bengal_3.jpg
~ ~ ~
│   ├── Bengal_197.jpg
│   ├── Bengal_198.jpg
│   └── Bengal_199.jpg

こうすることで、それぞれのペットの画像がディレクトリ名の AbyssinianBengalといったラベルごとに分類されることになる。

次に、分類した画像からCaffeに流し込むためのデータセットを作成する。

下の画像のように、「New Dataset」の「Images」から、「Classification」を選択。

f:id:nametake-1009:20160707010753p:plain

「New Image Classification Dataset」というページに飛んだら、 下図の赤丸の部分を同じように編集する。

f:id:nametake-1009:20160707010759p:plainf:id:nametake-1009:20160707011028p:plain

編集をしたら、「Create」ボタンをクリック。 クリックしたら別ページに飛んでデータセットが作成され始める。

ダウンロードしたペット画像全部を使っていると ちょっと時間がかかるので少しだけ待つ。 飛ばされたページを定期的に更新すれば、今どれぐらい終わったかがわかる。

終わるまで待ったらデータセットの作成は終了。簡単!

モデルの作成

データセットができたら、それを学習したデータモデルを作成する。

データセットを作成した時の横にある「New Model」から、 同じようにClassificationを選択する。

以下の様な画面が出てくるので、赤丸の部分を同じように設定。 設定が終わったら名前をつけて「Create」をクリック。

f:id:nametake-1009:20160707010802p:plain

クリックすると、また別ページに遷移して学習が始まる。 終わったら学習終了。簡単!

学習結果を試す

モデルデータを作成する時に飛ばされるページの下部に、下図のようなフォームがある。

f:id:nametake-1009:20160707010804p:plain

図中の「Test a single image」の「Upload image」から識別させたい画像を選択して、 「Classify One」をクリック。

クリックすると別ページが開き、学習させたモデルから選択した画像が何に近いのかを、 教えてくれる。

下図だと柴犬ちゃんがしっかり「shiba inu」と認識されている。すごい。

f:id:nametake-1009:20160707010809p:plain

DigitsのRest API

なんとDigitsではRest APIが実装されている。

使い方はissueに書いてある通り。

コレを使えば割と簡単に学習結果をアプリケーションに反映させられる。

総括

機械学習の仕組みを全くわからなくても機械学習ができた!

hubotをSlackと連携してデーモン化するまでの手順

Hubotをデーモン化してSlackと連携させた時のメモ。

以下のツールは導入してあること前提。

  • apt
    • nodejs
    • npm
  • npm
    • hubot
    • pm2

hubotとSlackの初期設定

まず、以下のコマンドでhubotを準備。

$ mkdir nametake-bot
$ cd nametake-bot
$ yo hubot
                     _____________________________
                    /                             \
   //\              |      Extracting input for    |
  ////\    _____    |   self-replication process   |
 //////\  /_____\   \                             /
 ======= |[^_/\_]|   /----------------------------
  |   | _|___@@__|__
  +===+/  ///     \_\
   | |_\ /// HUBOT/\\
   |___/\//      /  \\
         \      /   +---+
          \____/    |   |
           | //|    +===+
            \//      |xx|

? Owner: nametake <nametake.kyarabuki@gmail.com>
? Bot name: nametake-bot
? Description: A simple helpful robot for your Company
? Bot adapter: (campfire) slack # ここでSlackをしっかり指定する
? Bot adapter: slack

以下のコマンドで、ちゃんとhubotが動くか確認。

$ ./bin/hubot
~~~
いろいろ出る
~~~
nametake-bot> nametake-bot ping
nametake-bot> PONG

次に、SlackでhubotのConfigure Appsからhubotを連携させる。 連携させた後に、連携させた設定からHUBOT_SLACK_TOKENを取得。

取得したTOKENを元に環境変数を設定。

$ export HUBOT_SLACK_TOKEN="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

環境を設定したらSlackと連携。

$ ./bin/hubot -a slack

Slackで@nametake-bot pingとかやって連携しているか確認。

pm2でhubotをデーモン化

hubotを永続化するためにpm2というnode.js製のツールを使う。

pm2をjsonから起動するために、以下の様なapp.jsonファイルを作成。

{
    "apps":[
        {
            "name"             : "hilbot",
            "args"             : ["-a", "slack"],
            "script"           : "./bin/hubot",
            "exec_mode"        : "fork",
            "exec_interpreter" : "bash",
            "autorestart"      : true,
            "env": {
                "NODE_ENV"           : "production",
                "PORT"               : "8080",
            }
        }
    ]
}

HUBOT_SLACK_TOKENJSONにまとめておきたい場合は、 envの項目を以下のように書き換える。

"env": {
    "NODE_ENV"               : "production",
    "PORT"                   : "8080"
    "HUBOT_SLACK_TOKEN"      : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

以下のコマンドでpm2でhubotをデーモン化。

$ pm2 start app.json

以下のコマンドでちゃんと動いているか確認。

$ pm2 list
┌──────────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name     │ id │ mode │ pid   │ status │ restart │ uptime │ memory      │ watching │
├──────────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ nametake-bot │ 0  │ fork │ 32342 │ online │ 1       │ 4D     │ 79.945 MB   │ disabled │
└──────────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────────────┴──────────┘

statusの欄がerroredになっていたら、pm2 logsというコマンドでログを確認して エラーを修正する。

今回はリモートリポジトリに上げたhubotのソースをSlack上からpullさせたかったので、 ここnew_update.coffeeを 丸々参考にさせて頂きました。

参考サイト

VimでOpenCVのneocompleteの補完が落ちる問題の解決

pyenvのanacondaで、condaで入れたopencvの補完をしようとすると、

Vim: Caught deadly signal SEGV
Vim: Finished.

と出て、落ちる問題の解決方法。

環境はOSX10.10。brewopencvが導入済み。 使用しているエディタはvimで、補完はneocomplete + jedi-vimで行っている、

結論だけ言うと、$PYENV_ROOT/versions/anaconda-*/lib/python2.7/site-packages/cv.pycv2.soを、/usr/local/Cellar/opencv/*/lib/python2.7/site-packagesにある cv.pycv2.soへのシンボリックリンクに置き換えることで解決した。

以下、全体のコマンド

$ cd $PYENV_ROOT/versions/anaconda-*/lib/python2.7/site-packages/
$ mv cv.py cv.py.org    # バックアップ
$ mv cv2.so cv2.so.org  # バックアップ
$ ln -s /usr/local/Cellar/opencv/*/lib/python2.7/site-packages/cv.py $PYENV_ROOT/versions/anaconda-*/lib/python2.7/site-packages/cv.py
$ ln -s /usr/local/Cellar/opencv/*/lib/python2.7/site-packages/cv2.so $PYENV_ROOT/versions/anaconda-*/lib/python2.7/site-packages/cv2.so

ぶっちゃけ原因がわからなかったので教えて偉い人。