学習記録 - Pytorch モデルの Android への移植の小さな例

お知らせ: 注意文章时效性、2022.04.02.


序文

最近、画像分類モデルを Android に移植する作業を行っていました. 当初は Tensorflow を使用する予定でしたが、Baidu の一部のブログ投稿は 17 または 18 年前のものであり、その後、公式の実装例を見つけましたTensorflow と最初の例が見つかりました.これは廃止され、置き換えられました. しかし、この新しい例の README にはモデルの扱い方が書かれていません. Tensorflow の公式 Web サイトにはService Unavailable と表示されることが多く、Tensorflow で実装したモデルの結果は非常に奇妙です. Pytorch は新しい例を見つけることができます:

断固として Tensorflow を放棄し、Pytorch に切り替え、公式の操作例を参照してください。モデルはまだ不足する可能性があります。以下は、実装プロセス発生したエラー
の簡単な記録ですナンセンスが終わり、テキストが始まります。


ゼロ、環境の利用

使用環境 バージョン
モデルをトレーニングします。
パイソン 3.7.3
パイトーチ 1.11.0
モデルをエクスポートします。
パイソン 3.9
パイトーチ 1.9.0
Android の展開:
Android スタジオ 4.1.1
pytorch_android_lite 1.9.0
pytorch_android_torchvision 1.9.0

次のようなエラーがある場合:

No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi

NDK に問題がある可能性があります. NDK がインストールされていないか、ND がインストールされていますが、K には対応するライブラリがありません. このブログ投稿を参照してインストールできます (完全な解決策 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el- linux-android_CodeForCoffee のブログ - CSDN ブログ)。ただし、NDK をダウンロードするための URL にはアクセスできません。ここからダウンロードできます ( AndroidDevTools - Android Development Tools Android SDK Download Android Studio Download Gradle Download SDK Tools Download )。

1. モデルの準備

1.モデルをエクスポートする

参照されているブログ投稿と公式チュートリアルによると、独自のモデルをエクスポートする必要があります。ブログ投稿の方法も試しましたが、最終的には自分で実行することができ、公式の例から次のように変更しました。

import torch
from torch.utils.mobile_optimizer import optimize_for_mobile
from model_v3 import mobilenet_v3_large  # 导入自己的模型

model_pth = './MobileNetV3-20220330-01.pth'  # 训练得到的模型参数文件的路径
mobile_ptl = './mobilenetV3large.ptl'  # 模型保存为Android可以调用的文件的路径
model = mobilenet_v3_large(num_classes=7)  # 实例化模型
pre_weights = torch.load(model_pth, map_location='cpu')  # 读取参数
model.load_state_dict(pre_weights, strict=True)  # 将参数载入到模型
device = torch.device('cpu')  # 将torch.Tensor分配到的设备的对象,有cpu和cuda两种
model.to(device)  # 将模型加载到指定设备上
model.eval()  # 将模型设为验证模式
example = torch.rand(1, 3, 224, 224)  # 输入样例的格式为一张224*224的3通道图像
# 上面是准备模型,下面就是转换了
traced_script_module = torch.jit.trace(model, example)
traced_script_module_optimized = optimize_for_mobile(traced_script_module)
traced_script_module_optimized._save_for_lite_interpreter(mobile_ptl)

Pytorch の公式の例で使用されるモデルは、事前にトレーニングされた MobileNetV2 であり、torchvision にインポートされてから呼び出されます。

……
import torchvision
……
model = torchvision.models.mobilenet_v2(pretrained=True)
……

2. エラー記録

2.1 完全なモデルをロードするには (ネットワーク構造 + 重みパラメータ)

パラメータのみが読み込まれると、エラーが報告されます。

AttributeError: 'collections.OrderedDict' object has no attribute 'eval' ……

モデル ネットワークを読み込んでモデルをトレーニングするだけでは、トレーニングを行わないことになり、モデルにはパラメーターがありません。
そのため、モデル ファイルを保存するときは、通常、次の 2 つの方法があります。

  1. モデルのパラメーターのみが保存されます (公式の推奨事項はこれです) トレーニング中に重みパラメーターのみが保存される場合は、モデルの読み込み時にモデルの重みパラメーターをモデルに入れる必要があります。
# Save:
torch.save(model.state_dict(), PATH)
# Load:
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
  1. モデル全体を保存します (ネットワーク構造 + パラメーター)
# Save:
torch.save(model, PATH)
# Load:
# Model class must be defined somewhere
model = torch.load(PATH)
model.eval()

より具体的な手順については、公式ドキュメント ( SAVING AND LOADING MODELS )を参照してください。

2.2 エクスポートされたモデルのファイル形式

これらのリファレンス ブログ投稿はすべてファイルとしてエクスポートされると言っていますが、.pt完全なモデルを読み込んでエクスポートされた.ptファイルを実行すると、エラーが報告されます。

java.lang.RuntimeException: Unable to start activity ComponentInfo{
    
    org.pytorch.helloworld/org.pytorch.helloworld.MainActivity}: com.facebook.jni.CppException: PytorchStreamReader failed locating file bytecode.pkl: file not found ()
    Exception raised from valid at ../caffe2/serialize/inline_container.cc:157 (most recent call first):
    (no backtrace available)

公式の例によると、.ptlファイルにエクスポートした後、正常に実行できます。

2. Android の展開

この部分は、このブログ投稿の Android 展開部分( How to deploy the pytorch model to Android 、実装は公式の例に似ています ) を参照していますが、最初にこのブログ投稿を参照して記述しましたが、実行されませんでした成功しました。
お兄ちゃんの手順を参考にして、また行きましょう。

1. 新しいプロジェクトを作成する

新しいものを直接作成しEmpty Activity[次へ]をクリックします。
新しいプロジェクト

2. プロジェクト情報を入力する

名前を付けて呼び出しmyModel、その他はデフォルトのままにして、[完了]をクリックします。
プロジェクト情報を入力します

3. パッケージのインポート (依存ライブラリの追加)

pytorch_android_liteのパッケージをインポートします( pytorch_androidとは異なり、モデルのロード方法が異なります)。

//Pytorch
implementation 'org.pytorch:pytorch_android_lite:1.9.0'
implementation 'org.pytorch:pytorch_android_torchvision:1.9.0'

依存ライブラリを追加
次のように完了しますbuild.gradle(:app)

plugins {
    
    
    id 'com.android.application'
}

android {
    
    
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
    
    
        applicationId "com.test.mymodel"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
    
    
        release {
    
    
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
    
    
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    
    

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    //Pytorch
    implementation 'org.pytorch:pytorch_android_lite:1.9.0'
    implementation 'org.pytorch:pytorch_android_torchvision:1.9.0'
}

注: エクスポートされたモデルで使用されている Pytorch のバージョンが、 Android プロジェクトで使用されているpytorch_andorid_liteパッケージのバージョンと異なる場合、エラーが報告されます。

java.lang.RuntimeException: Unable to start activity ComponentInfo{
    
    org.pytorch.helloworld/org.pytorch.helloworld.MainActivity}: com.facebook.jni.CppException: Lite Interpreter verson number does not match. The model version must be between 3 and 5But the model version is 7 ()
    Exception raised from parseMethods at ../torch/csrc/jit/mobile/import.cpp:320 (most recent call first):
    (no backtrace available)

モデルのトレーニングに使用した Pytorch のバージョンは で1.11.0、このバージョンで実行すると上記のエラーが発生しますが、1.9.0Android では同じバージョンに置き換えることで実行できます。

4.ページレイアウト

1 つはTextViewテキスト結果の表示に使用され、もう 1 つはImageView画像の表示に使用されます。
ページレイアウト

完全なactivity_main.xmlファイルは次のとおりです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:layout_gravity="center"
        android:text="Hello World!"
        android:textSize="50sp"
        android:textAlignment="center"
        android:textStyle="bold"/>

    <ImageView
        android:id="@+id/iv"
        android:layout_weight="4"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:background="#f0f0f0"
        android:layout_gravity="center"
        android:contentDescription="@string/iv_text" />

</LinearLayout>

5. 結果カテゴリを追加する

新しいクラス ファイルを作成しますEmotionClasses.java。これが式の分類です。7 つのカテゴリがあり、トレーニング ラベルの順に並べます。(順番を間違えると結果がおかしくなる)
結果カテゴリを格納するための新しいクラスを作成します

package com.test.mymodel;

public class EmotionClasses {
    
    
    public static String[] EMOTION_CLASSES = new String[]{
    
    
            "anger",
            "disgust",
            "fear",
            "happy",
            "normal",
            "sad",
            "surprised"
    };
}

6. モデル ファイルと写真を追加する

mainフォルダの下に新しいフォルダを作成しassets.ptlその中にモデルファイルと認識したい画像を入れます。(画像はモデルのエクスポート時に設定されたexampleサイズである必要があります。ここでは 224*224 カラーの画像です)
モデルファイルとテスト画像を入れる

7. モデルを呼び出す

モデルを読み込んだ後MainActivity.java、画像を認識します。

package com.test.mymodel;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import org.pytorch.IValue;
import org.pytorch.LiteModuleLoader;
import org.pytorch.MemoryFormat;
import org.pytorch.Module;
import org.pytorch.Tensor;
import org.pytorch.torchvision.TensorImageUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Bitmap bitmap = null;
        Module module = null;
        try {
    
    
            // creating bitmap from packaged into app android asset 'image.jpg',
            // app/src/main/assets/image.jpg
            bitmap = BitmapFactory.decodeStream(getAssets().open("happy01.jpg"));
            // loading serialized torchscript module from packaged into app android asset model.pt,
            // app/src/model/assets/model.pt
            module = LiteModuleLoader.load(assetFilePath(this, "mobilenetV3large.ptl"));
        } catch (IOException e) {
    
    
            Log.e("PytorchHelloWorld", "Error reading assets", e);
            finish();
        }

        // showing image on UI
        ImageView imageView = findViewById(R.id.iv);
        imageView.setImageBitmap(bitmap);

        // preparing input tensor
        final Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(bitmap,
                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, TensorImageUtils.TORCHVISION_NORM_STD_RGB, MemoryFormat.CHANNELS_LAST);

        // running the model
        final Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();

        // getting tensor content as java array of floats
        final float[] scores = outputTensor.getDataAsFloatArray();

        // searching for the index with maximum score
        float maxScore = -Float.MAX_VALUE;
        int maxScoreIdx = -1;
        for (int i = 0; i < scores.length; i++) {
    
    
            if (scores[i] > maxScore) {
    
    
                maxScore = scores[i];
                maxScoreIdx = i;
            }
        }

        String className = EmotionClasses.EMOTION_CLASSES[maxScoreIdx];

        // showing className on UI
        TextView textView = findViewById(R.id.tv);
        textView.setText(className);
    }

    /**
     * Copies specified asset to the file in /files app directory and returns this file absolute path.
     *
     * @return absolute file path
     */
    public static String assetFilePath(Context context, String assetName) throws IOException {
    
    
        File file = new File(context.getFilesDir(), assetName);
        if (file.exists() && file.length() > 0) {
    
    
            return file.getAbsolutePath();
        }

        try (InputStream is = context.getAssets().open(assetName)) {
    
    
            try (OutputStream os = new FileOutputStream(file)) {
    
    
                byte[] buffer = new byte[4 * 1024];
                int read;
                while ((read = is.read(buffer)) != -1) {
    
    
                    os.write(buffer, 0, read);
                }
                os.flush();
            }
            return file.getAbsolutePath();
        }
    }
}

pytorch_android_lite:依存ライブラリを使用し、メソッドを使用してモデルをロードすると、ライブラリが見つからないことModule.load()を示すエラーが報告され、メソッドを使用してモデルをロードする必要があります。公式の問題で言及された誰かが"libpytorch_jni.so" を見つけることができませんでしたlibpytorch_jni.soLiteModuleLoader.load()

java.lang.UnsatisfiedLinkError: dlopen failed: library "libpytorch_jni.so" not found

8. 走行結果

実行結果は次のとおりです。
運用実績
カテゴリの順序が間違っていると、認識結果も間違って配置され、下図のようにanger4 位に調整され、認識結果は になりますanger
カテゴリの順序が間違っている


3. まとめ

  • 実際、単純な画像分類だけを行いたい場合は、基本的に公式の android-demo-app の HelloWorldApp でモデルと分類カテゴリを変更することで実行できます。
  • これらのフレームワークは更新が速すぎるため、一部の記事の適時性が制限されています. バージョンが変更されたり、特定のメソッドが変更されたりすると、さまざまなエラーがポップアップ表示されます. 解決策は、さまざまな検索です。
  • 他のブログが参考になる 主要なプロセスはまだ公式のチュートリアルに依存している 問題が発生した場合は、プロジェクトにアクセスして、自分に似た問題がないかどうかを調べると、インスピレーションを得ることができる場合がありissueますそれらから。
  • 最後に、実行された小さなデモがGitee投げられました
    寂しさをまとめました
    役に立ったら高評価お願いします。
    何か問題がある場合は、私を修正してください。
    フレンドリーなコメント、平和なコミュニケーション。

おすすめ

転載: blog.csdn.net/weixin_44438341/article/details/123897165