提示:
注意文章时效性,2022.04.02。
目录
最近在搞图像分类模型移植到Android上,本来是准备用Tensorflow来搞的,但是百度到的一些博文案例都有些老,17、18年的,然后找Tensorflow官方实现的例子,发现最开始的例子已经弃用了,换了个地方。但是这新例子里的README也没讲怎么处理模型,Tensorflow官网时常出现Service Unavailable,再加上我用Tensorflow实现的模型跑出的结果很奇怪。Pytorch倒是能找到比较新一点的例子:
果断放弃Tensorflow,改用Pytorch,参考官方的给例子操作,模型还是能够跑出来的。
这里就简单记录下实现过程和遇到的一些错误。
废话结束,正文开始。
| 使用的环境 | 版本 |
|---|---|
| 训练模型: | ↓ |
| Python | 3.7.3 |
| Pytorch | 1.11.0 |
| 导出模型: | ↓ |
| Python | 3.9 |
| Pytorch | 1.9.0 |
| Android部署: | ↓ |
| Android Studio | 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的网址进不去了,可以到这里下载(AndroidDevTools - Android开发工具 Android SDK下载 Android Studio下载 Gradle下载 SDK Tools下载 )
按照参考的博文和官方教程讲的,都是要导出自己的模型的。博文里的方法也试了,不过最后我自己成功跑出来的,是在官方的例子上改的,如下:
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)
……
如果只载入参数,会报错;
AttributeError: 'collections.OrderedDict' object has no attribute 'eval' ……
只载入模型网络训练模型,等于没训练,模型没参数。
所以保存模型文件的时候,一般有两种不同的方式:
# Save:
torch.save(model.state_dict(), PATH)
# Load:
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.eval()
# Save:
torch.save(model, PATH)
# Load:
# Model class must be defined somewhere
model = torch.load(PATH)
model.eval()
更具体的说明可以看官方的文档(SAVING AND LOADING MODELS)
虽然那些参考博文都说是要导出为.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文件就能成功运行。
这部分参考这篇博文(如何将pytorch模型部署到安卓,实现的和官方的例子差不多)的安卓部署部分,虽然最开始参考这篇博文写,没跑成功。
下面就参考大佬的步骤再走一遍。
直接新建一个Empty Activity,点击Next。

取个名字,就叫myModel了,其他保持默认,点击Finish。

导入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,用这个版本导出的来跑会有上面这个错误,换成Android上相同版本的1.9.0就能跑了。
放了一个TextView用来显示文字结果,一个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>
新建EmotionClasses.java类文件,我这里是表情分类,有七个类别,按训练的标签顺序放里面。(顺序不对的话,结果也会错位)

package com.test.mymodel;
public class EmotionClasses {
public static String[] EMOTION_CLASSES = new String[]{
"anger",
"disgust",
"fear",
"happy",
"normal",
"sad",
"surprised"
};
}
在main文件夹下新建assets文件夹,并将模型的.ptl文件和要识别图片放入其中。(图片需要是前面导出模型时设置的example的大小,我这里是224*224的彩色图片)

在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这个库,就需要使用LiteModuleLoader.load()方法来载入模型。官方的issue有人提过couldn’t find “libpytorch_jni.so”。
java.lang.UnsatisfiedLinkError: dlopen failed: library "libpytorch_jni.so" not found
运行结果如下:

如果类别顺序错位,识别结果也会错位,如下图所示,将anger调到第四位,识别结果就成了anger。

我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序