Golang で推論
昨今では「機械学習と言えば Python」「Python と言えば機械学習」と思われがちなのですが、推論用途であれば学習済みモデルを利用して色々なプログラミング言語から扱えます。Go から扱える機械学習ライブラリの内、学習済みモデルが利用できる物としてはおおよそ以下の3つに絞られます。
Golang で TensorFlow
Golang で TensorFlow を利用する場合はオフィシャルから Go の binding が提供されているので Go の import 文で GitHub リポジトリを指して利用します。Jetson や Raspberry Pi での動作実績もあります。TensorFlow 自体が色々な CPU 命令の最適化まで行っているので Python とそん色ないパフォーマンスで動作します。もちろん GPU を有効にしても扱えます。さらに Go であれば推論処理を並行処理させる事で他の言語よりも省メモリで高速に処理する事も可能です。簡単な使い方は以前ブログに書きました。
Big Sky :: golang で tensorflow のススメ
ただし TensorFlow は Python から利用する場合と同様にフットプリントが大きく、また実行中のメモリ使用量も巨大になります。Raspberry Pi の様なリソースの乏しい環境から利用すると無視できない程の負荷が掛かります。
Golang で TensorFlow Lite
TensorFlow Lite はモバイル用途で利用される推論用ライブラリです。こちらは筆者が開発中の go-tflite を使えば toco で変換したモデルファイルを利用して推論を実行する事が出来ます。
Big Sky :: TensorFlow Lite の Go binding を書いた。
TensorFlow に比べ Lite の API の方が推論に対して直観的なので、わりかし覚えやすいと思います。Windows からも利用できるようになっています。ビルドには libtensorflow_c というライブラリが必要です。README を見ながら導入して下さい。なお現在送っている pull-request がマージされればいずれ皆さんも簡単に Windows から TensorFlow Lite を利用できる様になる予定です。以下は Google が提供している SSD MobileNet v1 のモデルファイルを利用して OpenCV から取り込んだ動画にリアルタイムでラベル付けするサンプルの実行画面です。
TensorFlow Lite の Windows 版で SSD。現状の TfLite には Android/iOS 以外での GPU delegate 等の最適化がないので遅いけどコア1個100%でこの程度動くならまぁ使い道がなくもないかな。 pic.twitter.com/PvfQLMcZE1
— mattn (@mattn_jp) May 6, 2019
この他にも go-tflite の _examples には TesnorFlow が提供しているサンプルとほぼ同じ物が Go でポーティングされています。Go で TensorFlow Lite をやってみたい方は参考にして下さい。
TensorFlow Lite は CPU 最適化や GPU による高速化が一部のターゲットだけにしか提供されていない為、それらの環境外では若干遅くはなりますが、メモリ使用量がとてもも小さく、Raspberry Pi で動作させてもそれほど負荷を感じません。
Golang で ONNX
さて Go から扱える学習済みモデルのもう1つの候補 ONNX ですが現在 Go から ONNX を利用する手段としては2つあります。
1つは Prefered Network 社が提供している menoh の Go binding を利用する物。もう1つは Olivier Wulveryck 氏が開発している onnx-go を使う物です。
Menoh は ONNX を扱う事ができる推論専用ライブラリで、go-menoh からも同様に扱う事が出来る様になっています。以前、go-menoh を使ったリアルタイム物体認識を書きました。
Big Sky :: リアルタイム物体認識を2本作ってみた。
筆者の体感では TensorFlow ほどメモリは消費しないけれど、Raspberry Pi で動作させるのは少し辛いくらいのリソース消費になります。(もちろんサーバ等で動作させれば良い性能は出せます)
onnx-go
もう1つの候補 onnx-go は筆者が知る限りまだあまり広まっていません。onnx-go は Pure Go で書かれています。Pure Go で書かれているので Go をサポートする OS/CPU アーキテクチャであればどこでも動作するというメリットがあります。onnx-go は内部で gonum という行列演算ライブラリを利用しており、環境によっては blas による高速化が行われます。
onnx-go は以前 gorgonia とう gonum ベースの機械学習フレームワークから ONNX を利用する為に作られ、gorgonnx というリポジトリで開発されていましたが、そこから onnx 関連のみ抜き出しシェイプアップした物が現在の onnx-go になっています。以前から onnx-go を追っかけていたのですが、最近ようやく色々な物が動作する様になってきたのでそろそろ Qiita で紹介しようと思い立ちました。
emotions のサンプルを試してみた。
— mattn (@mattn_jp) May 7, 2019
Computation time: 1.1050632s
happiness / 67.85%
neutral / 30.88%
happiness なんですか? pic.twitter.com/O9DJFistTY
リポジトリに同梱されている emotions というサンプルコードを動かすと画像から感情を推論するデモを見る事ができます。
サンプルコードも短いので直ぐに API を覚えられると思います。ONNX Zoo というモデルファイルの一次配布場所とその README を参照すれば、簡単にアプリケーションを書く事も出来ます。
例えば mnist 手書き数字のモデルであれば以下に説明が書かれています。
README に書かれている通り入力画像を 28x28 のグレー画像(2値)に変換して設定し Run を呼び出すと推論できます。出力は GetOutputTensors から得られます。この辺は TensorFlow も TensorFlow Lite も onnx-go もそれほど扱い方は変わりません。
package main
import (
"flag"
"fmt"
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"io/ioutil"
"log"
"os"
"github.com/nfnt/resize"
"github.com/owulveryck/onnx-go"
"github.com/owulveryck/onnx-go/backend/x/gorgonnx"
"github.com/owulveryck/onnx-go/internal/x/images"
"gorgonia.org/tensor"
)
const (
height = 28
width = 28
)
func convertToGray(img image.Image) *image.Gray {
img = resize.Resize(width, height, img, resize.Bilinear)
gray := image.NewGray(img.Bounds())
bounds := img.Bounds()
for y := 0; y < bounds.Dy(); y++ {
for x := 0; x < bounds.Dx(); x++ {
gray.Set(x, y, color.GrayModel.Convert(img.At(x, y)))
}
}
return gray
}
func main() {
model := flag.String("model", "model.onnx", "path to the model file")
filename := flag.String("input", "file.png", "path to the input file")
flag.Parse()
backend := gorgonnx.NewGraph()
m := onnx.NewModel(backend)
// Read model binary
b, err := ioutil.ReadFile(*model)
if err != nil {
log.Fatal(err)
}
// Decode it into the model
err = m.UnmarshalBinary(b)
if err != nil {
log.Fatal(err)
}
// Read input image
f, err := os.Open(*filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
log.Fatal(err)
}
// Convert to gray image
imgGray := convertToGray(img)
// Create tensor dimensioned by 1x1x28x28
input := tensor.New(tensor.WithShape(1, 1, height, width), tensor.Of(tensor.Float32))
err = images.GrayToBCHW(imgGray, input)
if err != nil {
log.Fatal(err)
}
m.SetInput(0, input)
err = backend.Run()
if err != nil {
log.Fatal(err)
}
output, err := m.GetOutputTensors()
if err != nil {
log.Fatal(err)
}
// Find maximum value of prediction results
max := float32(-9999)
maxi := -1
for i, v := range output[0].Data().([]float32) {
if v > max {
max = v
maxi = i
}
}
fmt.Println(maxi)
}
Pure Go なので Windows からも特に苦労する事なく利用出来ます。
まとめ
Go から利用できる機械学習の推論フレームワークを紹介しました。TensorFlow が今すぐ新しいブレイクスルーを起こす事は無いと思いますが、TensorFlow Lite はもしかすると今後 Android/iOS 以外の環境で GPU による高速化が行われる様になるかもしれません。(ならないかもしれません、いやなって欲しい)
onnx-go はこれから利用できるオペレータがどんどん増え、パフォーマンスに関しても cuda や clBLAS 等を利用した高速化が行われる様になるかもしれません。(ならないかもしれません、いやなって欲しい)
色々と期待の多い Go の推論フレームワーク界隈なので、いずれまた新しいニュースと共にご紹介したいと思います。