2019/05/12


なんとなく SQLite3 でロジスティック回帰できたら面白そうと思ったので作ってみた。

データセットは iris、sqlflow の DDL を使わせて頂いた。

sqlflow/example/datasets at develop · sql-machine-learning/sqlflow - GitHub

It should print the number of rows as the following: count(*) 10 Troubleshooting It usually takes ab...

https://github.com/sql-machine-learning/sqlflow/tree/develop/example/datasets
CREATE DATABASE IF NOT EXISTS iris;
DROP TABLE IF EXISTS iris.train;
CREATE TABLE iris.train (
       sepal_length float,
       sepal_width  float,
       petal_length float,
       petal_width  float,
       class int);
INSERT INTO iris.train VALUES(6.4,2.8,5.6,2.2,2);
INSERT INTO iris.train VALUES(5.0,2.3,3.3,1.0,1);
INSERT INTO iris.train VALUES(4.9,2.5,4.5,1.7,2);
INSERT INTO iris.train VALUES(4.9,3.1,1.5,0.1,0);
INSERT INTO iris.train VALUES(5.7,3.8,1.7,0.3,0);
INSERT INTO iris.train VALUES(4.4,3.2,1.3,0.2,0);
INSERT INTO iris.train VALUES(5.4,3.4,1.5,0.4,0);
INSERT INTO iris.train VALUES(6.9,3.1,5.1,2.3,2);
INSERT INTO iris.train VALUES(6.7,3.1,4.4,1.4,1);
INSERT INTO iris.train VALUES(5.1,3.7,1.5,0.4,0);
INSERT INTO iris.train VALUES(5.2,2.7,3.9,1.4,1);
INSERT INTO iris.train VALUES(6.9,3.1,4.9,1.5,1);
INSERT INTO iris.train VALUES(5.8,4.0,1.2,0.2,0);
INSERT INTO iris.train VALUES(5.4,3.9,1.7,0.4,0);
INSERT INTO iris.train VALUES(7.7,3.8,6.7,2.2,2);
INSERT INTO iris.train VALUES(6.3,3.3,4.7,1.6,1);
INSERT INTO iris.train VALUES(6.8,3.2,5.9,2.3,2);
INSERT INTO iris.train VALUES(7.6,3.0,6.6,2.1,2);
INSERT INTO iris.train VALUES(6.4,3.2,5.3,2.3,2);
INSERT INTO iris.train VALUES(5.7,4.4,1.5,0.4,0);
INSERT INTO iris.train VALUES(6.7,3.3,5.7,2.1,2);
INSERT INTO iris.train VALUES(6.4,2.8,5.6,2.1,2);
INSERT INTO iris.train VALUES(5.4,3.9,1.3,0.4,0);
INSERT INTO iris.train VALUES(6.1,2.6,5.6,1.4,2);
INSERT INTO iris.train VALUES(7.2,3.0,5.8,1.6,2);
INSERT INTO iris.train VALUES(5.2,3.5,1.5,0.2,0);
INSERT INTO iris.train VALUES(5.8,2.6,4.0,1.2,1);
INSERT INTO iris.train VALUES(5.9,3.0,5.1,1.8,2);
INSERT INTO iris.train VALUES(5.4,3.0,4.5,1.5,1);
INSERT INTO iris.train VALUES(6.7,3.0,5.0,1.7,1);
INSERT INTO iris.train VALUES(6.3,2.3,4.4,1.3,1);
INSERT INTO iris.train VALUES(5.1,2.5,3.0,1.1,1);
INSERT INTO iris.train VALUES(6.4,3.2,4.5,1.5,1);
INSERT INTO iris.train VALUES(6.8,3.0,5.5,2.1,2);
INSERT INTO iris.train VALUES(6.2,2.8,4.8,1.8,2);
INSERT INTO iris.train VALUES(6.9,3.2,5.7,2.3,2);
INSERT INTO iris.train VALUES(6.5,3.2,5.1,2.0,2);
INSERT INTO iris.train VALUES(5.8,2.8,5.1,2.4,2);
INSERT INTO iris.train VALUES(5.1,3.8,1.5,0.3,0);
INSERT INTO iris.train VALUES(4.8,3.0,1.4,0.3,0);
INSERT INTO iris.train VALUES(7.9,3.8,6.4,2.0,2);
INSERT INTO iris.train VALUES(5.8,2.7,5.1,1.9,2);
INSERT INTO iris.train VALUES(6.7,3.0,5.2,2.3,2);
INSERT INTO iris.train VALUES(5.1,3.8,1.9,0.4,0);
INSERT INTO iris.train VALUES(4.7,3.2,1.6,0.2,0);
INSERT INTO iris.train VALUES(6.0,2.2,5.0,1.5,2);
INSERT INTO iris.train VALUES(4.8,3.4,1.6,0.2,0);
INSERT INTO iris.train VALUES(7.7,2.6,6.9,2.3,2);
INSERT INTO iris.train VALUES(4.6,3.6,1.0,0.2,0);
INSERT INTO iris.train VALUES(7.2,3.2,6.0,1.8,2);
INSERT INTO iris.train VALUES(5.0,3.3,1.4,0.2,0);
INSERT INTO iris.train VALUES(6.6,3.0,4.4,1.4,1);
INSERT INTO iris.train VALUES(6.1,2.8,4.0,1.3,1);
INSERT INTO iris.train VALUES(5.0,3.2,1.2,0.2,0);
INSERT INTO iris.train VALUES(7.0,3.2,4.7,1.4,1);
INSERT INTO iris.train VALUES(6.0,3.0,4.8,1.8,2);
INSERT INTO iris.train VALUES(7.4,2.8,6.1,1.9,2);
INSERT INTO iris.train VALUES(5.8,2.7,5.1,1.9,2);
INSERT INTO iris.train VALUES(6.2,3.4,5.4,2.3,2);
INSERT INTO iris.train VALUES(5.0,2.0,3.5,1.0,1);
INSERT INTO iris.train VALUES(5.6,2.5,3.9,1.1,1);
INSERT INTO iris.train VALUES(6.7,3.1,5.6,2.4,2);
INSERT INTO iris.train VALUES(6.3,2.5,5.0,1.9,2);
INSERT INTO iris.train VALUES(6.4,3.1,5.5,1.8,2);
INSERT INTO iris.train VALUES(6.2,2.2,4.5,1.5,1);
INSERT INTO iris.train VALUES(7.3,2.9,6.3,1.8,2);
INSERT INTO iris.train VALUES(4.4,3.0,1.3,0.2,0);
INSERT INTO iris.train VALUES(7.2,3.6,6.1,2.5,2);
INSERT INTO iris.train VALUES(6.5,3.0,5.5,1.8,2);
INSERT INTO iris.train VALUES(5.0,3.4,1.5,0.2,0);
INSERT INTO iris.train VALUES(4.7,3.2,1.3,0.2,0);
INSERT INTO iris.train VALUES(6.6,2.9,4.6,1.3,1);
INSERT INTO iris.train VALUES(5.5,3.5,1.3,0.2,0);
INSERT INTO iris.train VALUES(7.7,3.0,6.1,2.3,2);
INSERT INTO iris.train VALUES(6.1,3.0,4.9,1.8,2);
INSERT INTO iris.train VALUES(4.9,3.1,1.5,0.1,0);
INSERT INTO iris.train VALUES(5.5,2.4,3.8,1.1,1);
INSERT INTO iris.train VALUES(5.7,2.9,4.2,1.3,1);
INSERT INTO iris.train VALUES(6.0,2.9,4.5,1.5,1);
INSERT INTO iris.train VALUES(6.4,2.7,5.3,1.9,2);
INSERT INTO iris.train VALUES(5.4,3.7,1.5,0.2,0);
INSERT INTO iris.train VALUES(6.1,2.9,4.7,1.4,1);
INSERT INTO iris.train VALUES(6.5,2.8,4.6,1.5,1);
INSERT INTO iris.train VALUES(5.6,2.7,4.2,1.3,1);
INSERT INTO iris.train VALUES(6.3,3.4,5.6,2.4,2);
INSERT INTO iris.train VALUES(4.9,3.1,1.5,0.1,0);
INSERT INTO iris.train VALUES(6.8,2.8,4.8,1.4,1);
INSERT INTO iris.train VALUES(5.7,2.8,4.5,1.3,1);
INSERT INTO iris.train VALUES(6.0,2.7,5.1,1.6,1);
INSERT INTO iris.train VALUES(5.0,3.5,1.3,0.3,0);
INSERT INTO iris.train VALUES(6.5,3.0,5.2,2.0,2);
INSERT INTO iris.train VALUES(6.1,2.8,4.7,1.2,1);
INSERT INTO iris.train VALUES(5.1,3.5,1.4,0.3,0);
INSERT INTO iris.train VALUES(4.6,3.1,1.5,0.2,0);
INSERT INTO iris.train VALUES(6.5,3.0,5.8,2.2,2);
INSERT INTO iris.train VALUES(4.6,3.4,1.4,0.3,0);
INSERT INTO iris.train VALUES(4.6,3.2,1.4,0.2,0);
INSERT INTO iris.train VALUES(7.7,2.8,6.7,2.0,2);
INSERT INTO iris.train VALUES(5.9,3.2,4.8,1.8,1);
INSERT INTO iris.train VALUES(5.1,3.8,1.6,0.2,0);
INSERT INTO iris.train VALUES(4.9,3.0,1.4,0.2,0);
INSERT INTO iris.train VALUES(4.9,2.4,3.3,1.0,1);
INSERT INTO iris.train VALUES(4.5,2.3,1.3,0.3,0);
INSERT INTO iris.train VALUES(5.8,2.7,4.1,1.0,1);
INSERT INTO iris.train VALUES(5.0,3.4,1.6,0.4,0);
INSERT INTO iris.train VALUES(5.2,3.4,1.4,0.2,0);
INSERT INTO iris.train VALUES(5.3,3.7,1.5,0.2,0);
INSERT INTO iris.train VALUES(5.0,3.6,1.4,0.2,0);
INSERT INTO iris.train VALUES(5.6,2.9,3.6,1.3,1);
INSERT INTO iris.train VALUES(4.8,3.1,1.6,0.2,0);

DROP TABLE IF EXISTS iris.test;
CREATE TABLE iris.test (
       sepal_length float,
       sepal_width  float,
       petal_length float,
       petal_width  float,
       class int);
INSERT INTO iris.test VALUES(6.3,2.7,4.9,1.8,2);
INSERT INTO iris.test VALUES(5.7,2.8,4.1,1.3,1);
INSERT INTO iris.test VALUES(5.0,3.0,1.6,0.2,0);
INSERT INTO iris.test VALUES(6.3,3.3,6.0,2.5,2);
INSERT INTO iris.test VALUES(5.0,3.5,1.6,0.6,0);
INSERT INTO iris.test VALUES(5.5,2.6,4.4,1.2,1);
INSERT INTO iris.test VALUES(5.7,3.0,4.2,1.2,1);
INSERT INTO iris.test VALUES(4.4,2.9,1.4,0.2,0);
INSERT INTO iris.test VALUES(4.8,3.0,1.4,0.1,0);
INSERT INTO iris.test VALUES(5.5,2.4,3.7,1.0,1);

僕が作ってる Go の SQLite3 ドライバはユーザ関数を Go で書く事が出来る。

    sql.Register("sqlite3_custom"&sqlite3.SQLiteDriver{
        ConnectHook: func(conn *sqlite3.SQLiteConn) error {
            if err := conn.RegisterAggregator("logistic_regression_train", createLogisticRegressionTrain(conn), true); err != nil {
                return err
            }
            if err := conn.RegisterFunc("logistic_regression_predict", createLogisticRegressionPredict(conn), true); err != nil {
                return err
            }
            return nil
        },
    })

    db, err := sql.Open("sqlite3_custom"":memory:")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    _, err = db.Exec(`attach "iris.sqlite" as iris`)
    if err != nil {
        log.Fatal(err)
    }

ユーザ関数とアグリゲート関数は動作が異なっていて、ユーザ関数は SELECT で使うと行毎に呼び出され、行毎の結果が返る。アグリゲート関数は行毎に Step メソッドが呼ばれ、最後に Done メソッドが呼ばれる。つまり集計関数になる。アグリゲート関数で以下の様に SELECT した結果を全て貰いモデルを作る。モデルは JSON 形式で出力する様にした。文字列を持ったテーブルにそのまま突っ込める。これを logistic_regression_train という関数名にした。

    _, err = db.Exec(`
    drop table if exists iris.model;
    create table iris.model(config text);
    insert into iris.model
    select
        logistic_regression_train('{
                "rate":    0.1,
                "ntrains": 5000
            }',
            sepal_length,
            sepal_width,
            petal_length,
            petal_width,
            class
        )
    from
        iris.train
    `)
    if err != nil {
        log.Fatal(err)
    }

次にこの JSON からモデルに戻し、引数で渡されたテストデータから推論する関数 logistics_regression_predict を作った。

    rows, err := db.Query(`
    select
        logistic_regression_predict('iris.model',
            sepal_length,
            sepal_width,
            petal_length,
            petal_width
        ), class
    from
        iris.test
    `)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var predicted, class float64
        err = rows.Scan(&predicted, &class)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(math.RoundToEven(predicted), class)
    }

ロジスティック回帰そのものは gonum を使って書いた。

func (s *logistic_regressionDone() (stringerror) {
    ws := make([]float64, s.X[0].Len())
    for i := range ws {
        ws[i] = s.rand.Float64()
    }
    for i := range s.y {
        s.y[i] = s.y[i] / (s.maxy + 1)
    }
    w := mat.NewVecDense(len(ws), ws)
    y := mat.NewVecDense(len(s.y), s.y)
    for n := 0; n < s.cfg.NTrains; n++ {
        for i, x := range s.X {
            t := mat.NewVecDense(x.Len(), nil)
            t.CopyVec(x)
            pred := softmax(t, w)
            perr := y.AtVec(i) - pred
            scale := s.cfg.Rate * perr * pred * (1 - pred)

            for j := 0; j < x.Len(); j++ {
                dx := mat.NewVecDense(x.Len(), nil)
                dx.CopyVec(x)
                dx.ScaleVec(scale, x)
                w.AddVec(w, dx)
            }
        }
    }

    fargs := make([]float64, w.Len())
    for i := 0; i < w.Len(); i++ {
        fargs[i] = w.AtVec(i)
    }
    var buf bytes.Buffer
    err := json.NewEncoder(&buf).Encode(&model{
        W: fargs,
        M: s.maxy,
    })
    if err != nil {
        return "", err
    }
    return buf.String(), nil
}

この例では推論した値 predict と、正解の値 class が SELECT されるので Go で値を取り出すと推論が正しいか判断できる。

    for rows.Next() {
        var predicted, class float64
        err = rows.Scan(&predicted, &class)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf(
            "predict: %d (%d)\n",
            int(math.RoundToEven(predicted)), int(class))
    }
predict: 1 (2)
predict: 1 (1)
predict: 0 (0)
predict: 2 (2)
predict: 0 (0)
predict: 1 (1)
predict: 1 (1)
predict: 0 (0)
predict: 0 (0)
predict: 1 (1)

正解率 90% なのでまずまずと言っていいのかな。

サンプルコードの位置づけだけど GitHub にコードを置いておきます。

GitHub - mattn/go-sqlite3-logistics-regression

Features → Code review Project management Integrations Actions Package registry Team management...

https://github.com/mattn/go-sqlite3-logistics-regression
RとPythonで学ぶ[実践的]データサイエンス&機械学習 RとPythonで学ぶ[実践的]データサイエンス&機械学習
有賀 友紀, 大橋 俊介
技術評論社 / ¥ 3,218 (2019-03-26)
 
発送可能時間:在庫あり。

Posted at by



2019/04/17


以前 TensorFlow Lite の Go バインディングを書いたのだけど

Big Sky :: TensorFlow Lite の Go binding を書いた。

Google launches TensorFlow Lite 1.0 for mobile and embedded devices | VentureBeat Google today intro...

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

これの mruby 版を作ってみました。

GitHub - mattn/mruby-tflite

model = TfLite :: Model .from_file " xor_model.tflite " interpreter = TfLite :: Interpreter . new (m...

https://github.com/mattn/mruby-tflite

TensorFlow Lite がライブラリ単体として配布されていないので、ビルドは少し難しいですが頑張って下さい。使い方は簡単です。以下 XOR (排他的論理和) なモデルの作り方と、そのモデルを使って推論する Ruby スクリプトを紹介します。keras で XOR... は説明が長くなるので省略します。知りたい方は調べて下さい。

import numpy as np 
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD

model = Sequential()
model.add(Dense(8, input_dim=2))
model.add(Activation('tanh'))
model.add(Dense(1))
model.add(Activation('sigmoid'))

sgd = SGD(lr=0.1)
model.compile(loss='binary_crossentropy', optimizer=sgd)
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
model.fit(X, y, verbose=True, batch_size=1, nb_epoch=1000)
model.save('xor_model.h5')

HDF5 なファイルを吐けば TFLiteConverter を使って TensorFlow Lite のモデルファイルが生成出来ます。

import tensorflow as tf
import tensorflow.contrib.lite as lite

converter = lite.TFLiteConverter.from_keras_model_file("xor_model.h5")
tflite_model = converter.convert()
open("xor_model.tflite""wb").write(tflite_model)

TensorFlow Lite の C API を wrap する形にしてあるので、API を知ってる人なら使いやすいと思います。以下は生成されたモデルファイルを使って XOR を推論するサンプルです。

model = TfLite::Model.from_file "xor_model.tflite"
interpreter = TfLite::Interpreter.new(model)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
output = interpreter.output_tensor(0)
[[0,0], [1,0], [0,1], [1,1]].each do |x|
  input.data = x
  interpreter.invoke
  puts "#{x[0]} ^ #{x[1]} = #{output.data[0].round}"
end

リポジトリには FizzBuzz を扱う例も置いてあるので Ruby で TensorFlow Lite やりたかった人(いるのか)には嬉しいかもしれません。ただ残念ながら現状 MRuby の gems には画像を描画できる物が無かったので顔認識の様な物を簡単に作る事が出来ませんでした。これは別途 gem が出来れば実現できるはずです。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装 ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
斎藤 康毅
オライリージャパン / ¥ 3,400 (2016-09-24)
 
発送可能時間:在庫あり。

Posted at by



2019/03/07


Google launches TensorFlow Lite 1.0 for mobile and embedded devices | VentureBeat

Google today introduced TensorFlow Lite 1.0 , its framework for developers deploying AI models on mo...

https://venturebeat.com/2019/03/06/google-launches-tensorflow-lite-1-0-for-mobile-and-embeddable-devices/

TensorFlow Lite 1.0 がリリースされた、このめでたい日に TensorFlow にプレゼントを送ろうと思って作りました。

TensorFlow Lite を Go 言語から扱えるパッケージです。なるべく C の API に忠実に実装したので Go 言語感がない部分もあるかもしれませんが、それはこれからです。

GitHub - mattn/go-tflite

go-tflite Go binding for TensorFlow Lite Usage See _example/main.go Requirements TensorFlow Lite Ins...

https://github.com/mattn/go-tflite

使い方は TensorFlow Lite でプログラミングした事がある方なら分かるはずです。

package main

import (
    "bufio"
    "flag"
    "fmt"
    "image"
    _ "image/png"
    "log"
    "os"
    "sort"

    "github.com/mattn/go-tflite"
    "github.com/nfnt/resize"
)

func loadLabels(filename string) ([]stringerror) {
    labels := []string{}
    f, err := os.Open("labels.txt")
    if err != nil {
        return nil, err
    }
    defer f.Close()
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        labels = append(labels, scanner.Text())
    }
    return labels, nil
}

func main() {
    var model_path, label_path, image_path string
    flag.StringVar(&model_path, "model""mobilenet_quant_v1_224.tflite""path to model file")
    flag.StringVar(&label_path, "label""labels.txt""path to label file")
    flag.StringVar(&image_path, "image""grace_hopper.png""path to image file")
    flag.Parse()

    f, err := os.Open(image_path)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    img, _, err := image.Decode(f)
    if err != nil {
        log.Fatal(err)
    }

    labels, err := loadLabels(label_path)
    if err != nil {
        log.Fatal(err)
    }

    model := tflite.NewModelFromFile(model_path)
    if model == nil {
        log.Fatal("cannot load model")
    }
    defer model.Delete()

    options := tflite.NewInterpreterOptions()
    options.SetNumThread(4)

    interpreter := tflite.NewInterpreter(model, options)
    if interpreter == nil {
        log.Fatal("cannot create interpreter")
    }
    defer interpreter.Delete()

    status := interpreter.AllocateTensors()
    if status != tflite.OK {
        log.Fatal("allocate failed")
    }

    input := interpreter.GetInputTensor(0)
    wanted_height := input.Dim(1)
    wanted_width := input.Dim(2)
    wanted_channels := input.Dim(3)
    wanted_type := input.Type()
    fmt.Println(wanted_height, wanted_width, wanted_channels, wanted_type)

    resized := resize.Resize(uint(wanted_width), uint(wanted_height), img, resize.NearestNeighbor)
    bounds := resized.Bounds()
    dx, dy := bounds.Dx(), bounds.Dy()

    if wanted_type == tflite.UInt8 {
        bb := make([]byte, dx*dy*wanted_channels)
        for y := 0; y < dy; y++ {
            for x := 0; x < dx; x++ {
                col := resized.At(x, y)
                r, g, b, _ := col.RGBA()
                bb[(y*dx+x)*3+0= byte(float64(r) / 255.0)
                bb[(y*dx+x)*3+1= byte(float64(g) / 255.0)
                bb[(y*dx+x)*3+2= byte(float64(b) / 255.0)
            }
        }
        input.CopyFromBuffer(bb)
    } else {
        log.Fatal("is not wanted type")
    }

    status = interpreter.Invoke()
    if status != tflite.OK {
        log.Fatal("invoke failed")
    }

    output := interpreter.GetOutputTensor(0)
    output_size := output.Dim(output.NumDims() - 1)
    b := make([]byte, output_size)
    type result struct {
        score float64
        index int
    }
    status = output.CopyToBuffer(b)
    if status != tflite.OK {
        log.Fatal("output failed")
    }
    results := []result{}
    for i := 0; i < output_size; i++ {
        score := float64(b[i]) / 255.0
        if score < 0.2 {
            continue
        }
        results = append(results, result{score: score, index: i})
    }
    sort.Slice(results, func(i, j intbool {
        return results[i].score > results[j].score
    })
    for i := 0; i < len(results); i++ {
        fmt.Printf("%02d%s%f\n", results[i].index, labels[results[i].index], results[i].score)
        if i > 5 {
            break
        }
    }
}

label_image でおなじみの grace_hopper も正しく動きます。

grace_hopper

一応、TensorFlow Lite の C API は全て移植したつもりですが、何かバグっていたら教えて下さい。

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太
技術評論社 / ¥ 5,136 (2016-09-09)
 
発送可能時間:

Posted at by