エッジ コンピューティング: tflite に基づくデバイス トレーニング (デバイス側トレーニング) に関する Android エッジ オンライン トレーニングの詳細な説明

この記事はオリジナルの投稿です。主に公式 Web サイトの On-Device Training with TensorFlow Lite、Fasion Minist のパーソナライズされたトレーニング コード、およびMuirushコードを参照しています。転載する必要がある場合は、必ずこのリンクを含めてください。違反者は起訴されます。

この投稿もヒット間違いなし!

デバイストレーニング英語公式サイトにて

https://www.tensorflow.org/lite/examples/on_device_training/overview

デバイストレーニングについて 中国の公式ウェブサイト

https://www.tensorflow.org/lite/examples/on_device_training/overview?hl=zh-cn

現時点では、tflite エンドサイドに基づいたインクリメンタル トレーニングの事例はほとんどなく、公式 Web サイトには服装認識の事例しかありません。公式 Web サイトを参照してください。

https://www.tensorflow.org/lite/examples/on_device_training/overview?hl=zh-cn

Google 公式ウェブサイトのデバイス トレーニングの例 - Fasion Mnist Android のトレーニングと推論

マスターでの例/lite/examples/model_personalization · tensorflow/examples · GitHub

Muirush 線形回帰予測コード

GitHub - Muirush/Model-training-with-Tensorflow-tfLite-and-android

しかし、主な問題は、このコードは、より複雑なコードと組み合わせた画像分類トレーニング推論の例であり、tflite 推論の中核を把握するためにコードを使用しておらず、署名を定義するスクリプトが表示されていないことです。初心者にとっては難しい かなり難しく、最初から最後まで推論の全過程をどのように訓練すればよいのかが明確ではありません。

したがって、この記事ではクラウド トレーニングから開始し、DNN を使用して y=2*x – 1 の回帰予測を実現し、モデルを tflite モデルに変換し、最新の署名関数を使用してデバイス側で増分トレーニングと推論を実現します。

ソフトウェアバージョン:tensorflow 2.8(デバイスサイド推論は2.7以降で利用可能な機能です)

Androidスタジオ:4.2.1

ステップ 1: クラウド トレーニング、署名機能の作成

ここでの以前のクラウド トレーニングとの違いは、モデルが tflite に変換されるときに推論とトレーニングに使用できる署名関数を記述することです。コードは次のとおりです。 

import tensorflow as tf

from tensorflow import keras

from tensorflow.keras import layers

from tensorflow import initializers as init

from tensorflow import losses

from tensorflow.keras import optimizers

from tensorflow import data as tfdata

from tensorflow import losses

from tensorflow.keras import optimizers

import numpy as np



class Model(tf.Module):

    def __init__(self):

#  定义2层全连接网络,输入维度input_dim是1,第一隐层是10个神经元,第二层也是10个神经元,输出层是1个

        self.model = tf.keras.Sequential()

        self.model.add(tf.keras.layers.Dense(units=10, input_dim=1))

        self.model.add(tf.keras.layers.Dense(units=10, ))

        self.model.add(tf.keras.layers.Dense(units=1))

       

        self.model.compile(loss=tf.keras.losses.MSE,

                           optimizer=tf.keras.optimizers.SGD(learning_rate=1e-5))



#   此处是非常重要的定义签名函数,尤其注意输入输出维度,且输入转化为tensor

    @tf.function(input_signature=[

        tf.TensorSpec([1, 1], tf.float32),

        tf.TensorSpec([1], tf.float32),

    ])

   

#   此处特别注意,x y尽管是形参,输入变量,但是后期在安卓中训练时必须保持一致,否则会报错

#   训练代码

    def train(self, x, y):

        with tf.GradientTape() as tape:

            prediction = self.model(x)

            loss = self.model.loss(y, prediction)

        gradients = tape.gradient(loss, self.model.trainable_variables)

        self.model.optimizer.apply_gradients(

            zip(gradients, self.model.trainable_variables))

        result = {"loss": loss}

        return result



#   推理代码

    @tf.function(input_signature=[

        tf.TensorSpec([1], tf.float32),

    ])

    def infer(self, x):

        pred =self.model(x)

        return {

            "output": pred

        }



#   保存在安卓端训练后的新权重

    @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])

    def save(self, checkpoint_path):

        tensor_names = [weight.name for weight in self.model.weights]

        tensors_to_save = [weight.read_value() for weight in self.model.weights]

        tf.raw_ops.Save(

            filename=checkpoint_path, tensor_names=tensor_names,

            data=tensors_to_save, name='save')

        return {

            "checkpoint_path": checkpoint_path

        }



#   加载在安卓端训练后的新权重,用于新数据做推理

    @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])

    def restore(self, checkpoint_path):

        restored_tensors = {}

        for var in self.model.weights:

            restored = tf.raw_ops.Restore(

                file_pattern=checkpoint_path, tensor_name=var.name, dt=var.dtype,

                name='restore')

            var.assign(restored)

            restored_tensors[var.name] = restored

        return restored_tensors


NUM_EPOCHS = 10000

BATCH_SIZE = 1

epochs = np.arange(1, NUM_EPOCHS + 1, 1)

losses = np.zeros([NUM_EPOCHS])

m = Model()


# 输入数据构造

x1 = np.array([[-1.0],[0.0],[1.0],[2.0],[3.0],[4.0], [5.0],[6.0],[7.0],[8.0],[9.0]], dtype = float)

y1 = np.array([-3.0,-1.0,1.0,3.0,5.0,7.0,9.0,11.0,13.0,15.0,17.0], dtype = float)


# array转化为tensor

features = tf.convert_to_tensor(x1, dtype=float)

labels = tf.convert_to_tensor(y1, dtype=float)


# 构造batch

train_ds = tf.data.Dataset.from_tensor_slices((features, labels))

train_ds = train_ds.batch(BATCH_SIZE)



# 训练

for i in range(NUM_EPOCHS):

    for x, y in train_ds:

        result = m.train(x, y)

    losses[i] = result['loss']

    if (i + 1) % 100 == 0:

        print('epochs=', i + 1, 'loss=', losses[i])

トレーニング結果は以下のとおりです。

epochs= 100 loss= 0.21976947784423828
epochs= 200 loss= 0.1585017591714859
epochs= 300 loss= 0.1464373618364334
epochs= 400 loss= 0.13536646962165833
epochs= 500 loss= 0.12510548532009125
epochs= 600 loss= 0.11560399830341339
epochs= 700 loss= 0.10680033266544342
epochs= 800 loss= 0.0986374095082283
……
epochs= 9500 loss= 4.569278098642826e-05
epochs= 9600 loss= 4.153713598498143e-05
epochs= 9700 loss= 3.7766891182400286e-05
epochs= 9800 loss= 3.464591281954199e-05
epochs= 9900 loss= 3.1359726563096046e-05
epochs= 10000 loss= 2.897171361837536e-05

2 番目のステップ、モデルの保存と tflite モデルへの変換

# モデルを保存します。これは署名関数を保存するためのキー コードであることに注意してください。そうでない場合は、後続の生成コードに含まれます。

SAVED_MODEL_DIR = "saved_model"


tf.saved_model.save(

    m,

    SAVED_MODEL_DIR,

    signatures={

        'train':

            m.train.get_concrete_function(),

        'infer':

            m.infer.get_concrete_function(),

        'save':

            m.save.get_concrete_function(),

        'restore':

            m.restore.get_concrete_function(),

})



# Convert the model

# 保存模型

converter = tf.lite.TFLiteConverter.from_saved_model(SAVED_MODEL_DIR)

converter.target_spec.supported_ops = [

    tf.lite.OpsSet.TFLITE_BUILTINS,  # enable TensorFlow Lite ops.

    tf.lite.OpsSet.SELECT_TF_OPS  # enable TensorFlow ops.

]

converter.experimental_enable_resource_variables = True

# 将云端模型转化为tflite模型,只有转化为tflite,安卓端才可以进行推理

tflite_model = converter.convert()

open('linear_model_0921.tflite', 'wb').write(tflite_model)

出力は次のとおりです。

INFO:tensorflow:Assets written to: saved_model/assets
21168

3 番目のステップは、署名関数が正常に構築されたかどうか、および入力と出力が正常に構築されたかどうかを確認することです。

このステップが今後の Android の鍵となります。

# 查看签名函数

# Print the signatures from the converted model

interpreter = tf.lite.Interpreter('linear_model_0921.tflite')

signatures = interpreter.get_signature_list()

print(signatures)

出力は次のとおりです。

​​​​​​​{'infer': {'inputs': ['x'], 'outputs': ['output']}, 'restore': {'inputs': 
['checkpoint_path'], 'outputs': ['dense_6/bias:0', 'dense_6/kernel:0', 'dense_7/bias:0', 
'dense_7/kernel:0', 'dense_8/bias:0', 'dense_8/kernel:0']}, 'save': {'inputs': 
['checkpoint_path'], 'outputs': ['checkpoint_path']}, 'train': {'inputs': ['x', 'y'], 
'outputs': ['loss']}}
​​

4 番目のステップでは、Python を使用して tflite を推論します (クラウド操作)

このステップの機能は、変換された tflite モデルの精度が低下したかどうかを確認することです。

interpreter = tf.lite.Interpreter('linear_model_0921.tflite')

interpreter.allocate_tensors()

infer = interpreter.get_signature_runner("infer")

x6 = np.array([13.0], dtype = float)

x7 = tf.convert_to_tensor(x6, dtype=float)

infer(x=x7)['output'][0]

出力は次のとおりです。

array([24.985922], dtype=float32)

上記はクラウドで tflite モデルを呼び出した推論結果であることに注意してください。

//次のステップは、m で始まるクラウド モデルの推論です。これは、tensorflow 保存モデルの違いに注意する必要があります。

result = m.infer(x=x7)['output']

np.array(result)[0]

出力は次のとおりです。

array([24.98592], dtype=float32)

両者の結果は同じであり、クラウド大規模モデルの保存モデル形式から tflite に変換した後でもモデルの精度が低下していないことがわかります。

5 番目のステップは、tflite を使用してクラウドでトレーニングすることです。これは、前のステップのクラウド トレーニング結果の後でも、Python インターフェイスを使用して tflite モデルをトレーニングできることを示しています。このステップの成功は、 Android 側の Java インターフェイス。

train = interpreter.get_signature_runner("train")

# NUM_EPOCHS = 50

# BATCH_SIZE = 100

more_epochs = np.arange(41, 501, 1)

more_losses = np.zeros([400])


BATCH_SIZE1 = 1

for i in range(400):

    for x, y in train_ds:

        result = train(x=x, y=y)

    more_losses[i] = result['loss']

    if (i + 1) % 2 == 0:

        print('epochs=', i + 1, 'more_losses=', more_losses[i])

ちょっとわかりにくいと思うので、図で説明すると、クラウド上でtensorflowラージモデルを使って40エポック(青い部分)を学習させ、tfliteモデルを使ってクラウド上で400エポックを実行した結果がこちらです。クラウド (オレンジ): 曲線から、tflite がクラウドに基づいてトレーニングされていることがわかりますが、これはまさに転移学習の特性を示しています。

************************************************* ************************************************* **********

以下はAndroidのコードです

ステップ 6: Android エッジのトレーニングと推論

Android インターフェイスのこの例では、Muirush コードを使用しています。コードは次のとおりです。このコードは Android 側の推論にのみ使用でき、Android 側のトレーニングには使用できません。したがって、モデル トレーニング 1 のコードで生成された tflite モデルは、 .py のみを使用できます。interpreter.run(input,output) メソッドは、推論とトレーニングに使用されます。トレーニングが機能しない場合は、インタープリターが最新の tflite コードを使用できるように、上記のコードに従って新しい tflite コードを生成する必要があります。メソッド: runSignature

エッジ推論:interpreter.runSignature(入力、出力、「推論」);

エッジサイドトレーニング:interpreter.runSignature(入力、出力、「トレーニング」)

GitHub - Muirush/Model-training-with-Tensorflow-tfLite-and-android

Android Studio でこのプロジェクトを開きます。

主に次の 2 つの部分を変更します。

記事の冒頭で生成した tflite ファイルをアセット フォルダーに置きます。

MainActivity.java を変更します。コードは次のとおりです。

元の推論をコメントアウトします。

//    public float doInference(String val){

//        float [] input = new float[1];

//        input [0] = Float.parseFloat(val);

//

//        float [][] output = new float[1][1];

//        interpreter.run(input,output);

//        return output[0][0];

//    }

新しい推論およびトレーニング方法を追加する

// 最新の runsignature メソッド、signature を使用すると推論します。   

float doInference(float val[][]) {

        // Run the inference.

        FloatBuffer testImages = FloatBuffer.wrap(val[0]);

        float[] output = new float[1];

        FloatBuffer output2 = FloatBuffer.wrap(output);

        Map<String, Object> inputs = new HashMap<>();

        inputs.put("x", testImages.rewind());

        Map<String, Object> outputs = new HashMap<>();

        outputs.put("output", output2);

        interpreter.runSignature(inputs, outputs, "infer");

        return output[0];

    }



    float doTrain(float val[][]) {

        // Run the training.

        float[][] var = new float[1][1];

        var[0][0] = 3.5f;

        float[] var2 = new float[1];

        var2[0] = 6.0f;

        FloatBuffer testImages = FloatBuffer.wrap(var[0]);

        float[] loss1 = new float[1];

        FloatBuffer label2 = FloatBuffer.wrap(var2);

        FloatBuffer loss2 = FloatBuffer.wrap(loss1);

        Map<String, Object> inputs = new HashMap<>();

        inputs.put("x", testImages.rewind());

        inputs.put("y", label2.rewind());

        Map<String, Object> outputs = new HashMap<>();

        outputs.put("loss", loss2);

        interpreter.runSignature(inputs, outputs, "train");

        return loss1[0];

    }

onclick メソッドを変更します。

public void onClick(View v) {

//                float f = doInference(ed.getText().toString());

                String var = ed.getText().toString();

                float [][] var2 = new float[1][1];

                var2[0][0] = Float.parseFloat(var);

//                推理

//                float f = doInference(var2);

//                tv.setText(("Value of Y: "+ f));

//                训练

                float loss6 = doTrain(var2);

                tv.setText(("Loss is: "+ loss6));

            }

トレーニングを実行するときに、「アプリを実行」をクリックします。

携帯電話のシミュレートされた実行インターフェイスが表示されます。説明する点がいくつかあることに注意してください。クラウド モデルは 10,000 エポックでトレーニングされました。Android エッジでトレーニングした場合、損失は 4.5*1E-5 であり、トレーニングが適切であることを示していますはクラウド トレーニングに基づいていました。その後、損失は減少します。便宜上、トレーニング中にここに値を 1 つだけ書きました。ここにはさらにいくつかの値があります。エポックとして書かれた場合は同じであり、本質的な違いはありません:

推論を実行すると、Android シミュレーター インターフェイスに次のように表示され、クラウド推論結果、クラウド tflite 推論結果、および Android tflite 推論結果が一致していることが示されます。

付録: 変更された MainActivity.java の完全なコードは次のとおりです。

package com.desertlocust.tfmodel1;

import androidx.appcompat.app.AppCompatActivity;



import android.content.res.AssetFileDescriptor;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.TextView;



import org.tensorflow.lite.Interpreter;



import java.io.FileInputStream;

import java.io.IOException;

import java.nio.MappedByteBuffer;

import java.nio.channels.FileChannel;

import java.util.HashMap;

import java.util.Map;

import java.nio.FloatBuffer;



public class MainActivity extends AppCompatActivity {

    private EditText ed;

    private TextView tv;

    private Button bt;

    private Interpreter interpreter;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ed = findViewById(R.id.input);

        tv = findViewById(R.id.output);

        bt = findViewById(R.id.submit);



        try {

            interpreter = new Interpreter(loadModelFile(),null);

        }catch (IOException e){

            e.printStackTrace();

        }



        bt.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

//                float f = doInference(ed.getText().toString());

                String var = ed.getText().toString();

                float [][] var2 = new float[1][1];

                var2[0][0] = Float.parseFloat(var);

//                推理

//                float f = doInference(var2);

//                tv.setText(("Value of Y: "+ f));

//                训练

                float loss6 = doTrain(var2);

                tv.setText(("Loss is: "+ loss6));

            }

        });

    }

//    加载tflite模型

    private MappedByteBuffer loadModelFile() throws IOException{

        AssetFileDescriptor assetFileDescriptor = this.getAssets().openFd("linear_model_0921.tflite");

        FileInputStream fileInputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());

        FileChannel fileChannel = fileInputStream.getChannel();

        long startOffset = assetFileDescriptor.getStartOffset();

        long  length = assetFileDescriptor.getLength();

        return  fileChannel.map(FileChannel.MapMode.READ_ONLY,startOffset,length);

    }



//    infer 采用run方法

//    public float doInference(String val){

//        float [] input = new float[1];

//        input [0] = Float.parseFloat(val);

//

//        float [][] output = new float[1][1];

//        interpreter.run(input,output);

//        return output[0][0];

//    }



//    infer 采用最新的runsignature方法,签名

    float doInference(float val[][]) {

        // Run the inference.

        FloatBuffer testImages = FloatBuffer.wrap(val[0]);

        float[] output = new float[1];

        FloatBuffer output2 = FloatBuffer.wrap(output);

        Map<String, Object> inputs = new HashMap<>();

        inputs.put("x", testImages.rewind());

        Map<String, Object> outputs = new HashMap<>();

        outputs.put("output", output2);

        interpreter.runSignature(inputs, outputs, "infer");

        return output[0];

    }

    float doTrain(float val[][]) {



        // Run the training.

        float[][] var = new float[1][1];

        var[0][0] = 3.5f;

        float[] var2 = new float[1];

        var2[0] = 6.0f;

        FloatBuffer testImages = FloatBuffer.wrap(var[0]);

        float[] loss1 = new float[1];

        FloatBuffer label2 = FloatBuffer.wrap(var2);

        FloatBuffer loss2 = FloatBuffer.wrap(loss1);

        Map<String, Object> inputs = new HashMap<>();

        inputs.put("x", testImages.rewind());

        inputs.put("y", label2.rewind());

        Map<String, Object> outputs = new HashMap<>();

        outputs.put("loss", loss2);

        interpreter.runSignature(inputs, outputs, "train");

        return loss1[0];

    }

}

最後に、Android Studio を使用して Android スマートフォン エミュレータを作成し、スクリプトを実行する場合は、詳細については以前の記事またはインターネット上のその他の情報を参照してください。

操作に従ってこれが表示された場合は、すでに tflite を使用して Android エッジで増分トレーニングと推論を実行していることを意味します。この手順に従って、独自の複雑なタスクを完了できます。

最後に書いたので、私の考えを話させてください 現在、インターネット上の多数のコードは主に tflite エッジエンド推論に基づいており、古い run メソッドが例として使用されています。画像のコードが多くて、理解するのは簡単ではありません。この例を通じて、tflite の本質をすぐに理解でき、最終的にコミュニケーションが促進されます。私たちは特別に tflite グループを作成しました。参加することを歓迎し、コミュニケーションを図り、コミュニケーションを図りましょう一緒に進歩していきましょう。ありがとう。

おすすめ

転載: blog.csdn.net/qq_18256855/article/details/127028071