> 文章列表 > uniapp - tensorflowjs 之小程序环境集成

uniapp - tensorflowjs 之小程序环境集成

uniapp - tensorflowjs 之小程序环境集成

一、简介

tensorflowjs(简称 tfjs)是一个用于使用 JavaScript 进行机器学习开发的库。以下是 tfjs 的官网与 github 仓库:

  • 官网:https://www.tensorflow.org/js?hl=zh-cn
  • github 仓库:https://github.com/tensorflow/tfjs

本系列主要记录如何在微信小程序环境下集成使用 tfjs,作为本系列的开篇,也是后续篇章的基础,讲解工程环境集成,喜欢的请点赞关注收藏~

二、集成环境

官方说 tfjs 是谷歌开发的机器学习开源项目,致力于为 javascript 提供利用硬件加速的机器学习模型训练和部署。这句话中,硬件加速 这 4 个字是重点,机器学习涉及到大量运算,靠 js 的运算效率(cpu)是达不到理想效果的,所以需要借助额外的技术来提升运算速度,tfjs 使用的 硬件加速 技术有以下两种:

  • webgl:基于 OpenGL 的 js 接口实现,采用 GPU 加速。
  • wasm(WebAssembly):是一种运行在现代网络浏览器中的新型代码,提供了一种在网络平台以接近本地速度的方式运行多种语言(诸如 C、C++和 Rust 等低级源语言)编写的代码。

注:上述描述摘抄自 维基百科、MDN。

以上两种 web 技术在微信小程序上都有对应的实现和支持。tfjs 对这些 硬件加速 统称为后端backend),相对应的,分为 webgl后端、wasm 后端,这里暂且不管使用哪种硬件加速技术,我们需要先安装一个微信小程序插件,用来初始化 tfjs

1、添加插件

tfjs 官方封装了一个微信小程序插件 tfjsPlugin,开发者只需按文档步骤集成即可,以下是该小程序插件的仓库及介绍:

  • 小程序插件仓库:https://github.com/tensorflow/tfjs-wechat
  • 小程序插件详情:https://mp.weixin.qq.com/wxopen/pluginbasicprofile?action=intro&appid=wx6afed118d9e81df9&lang=zh_CN

注:该插件利用小程序 WebGL API 给第三方小程序调用时提供 GPU 加速。

在使用该插件之前,需要在小程序管理后台,按 “设置-第三方设置-添加插件” 步骤,输入 wx6afed118d9e81df9 找到插件并添加:

uniapp - tensorflowjs 之小程序环境集成

2、注册插件

uniapp 工程需要在 manifest.json 文件中,找到 mp-weixin 配置项,添加 plugins 信息进行插件注册:

  /* 小程序特有相关 */"mp-weixin": {..."plugins": {"tfjsPlugin": {"version": "0.2.0","provider": "wx6afed118d9e81df9"}},"usingComponents": true},

注:当前该插件的最新版本是 0.2.0 发布于 2021-04-29 12:40:00。这个库已经很久没更新了,不过目前使用上也没遇到什么大问题~

3、添加 tfjs 依赖

经过以上两步之后,工程就可以使用 tfjsPlugin 插件了,在此之前,还有几个 npm 库需要安装:

"@tensorflow/tfjs-backend-webgl": "^3.13.0",
"@tensorflow/tfjs-converter": "^3.13.0",
"@tensorflow/tfjs-core": "^3.13.0",
"fetch-wechat": "^0.0.3",
  • @tensorflow/tfjs-backend-webgl:webgl 后端
  • @tensorflow/tfjs-converter:GraphModel 导入和执行包
  • @tensorflow/tfjs-core:基础包
  • fetch-wechat:Polyfill fetch 函数

其中 fetch-wechat 最新版本是 0.0.3,直接使用 npm i fetch-wechat 命令安装,而且另外 3 个 @tensorflow/tfjs-xxx 库最新版本是 4.4.0,需要使用 npm i @tensorflow/tfjs-xxx@^3.13.0 命令指定版本安装。之所以不使用最新的 4.4.0 版本,是因为运行时会报如下错误:

TypeError: env(...).platform.isTypedArray is not a function
  • 关于该报错的相关 issue:https://github.com/tensorflow/tfjs/issues/7273

搜索一番,没找到任何有用的解决方案,不清楚是不是当前 tfjsPlugin 插件太久没更新,没有适配 4.x 的缘故?不过呢,目前 3.13.0 版本已经非常稳定了,不用太纠结~

uniapp - tensorflowjs 之小程序环境集成

三、webgl 后端

集成环境之后,就可以来初始化并使用 tfjs 了,下面我们来配置 tfjsPlugin 插件,开启 webgl 硬件加速。

1、初始化插件

App.vue 文件中,找到 onLaunch() 回调函数,初始化 tfjsPlugin 插件,代码如下:

import { fetchFunc } from "fetch-wechat";
import * as tf from "@tensorflow/tfjs-core";
import * as webgl from "@tensorflow/tfjs-backend-webgl";
const plugin = requirePlugin("tfjsPlugin");const ENABLE_DEBUG = true; // 是否开启debug,输出调试日志onLaunch(() => {initTfjs();
});function initTfjs() {plugin.configPlugin({// polyfill fetch functionfetchFunc: fetchFunc(),// inject tfjs runtimetf,// inject webgl backendwebgl,// provide webgl canvascanvas: uni.createOffscreenCanvas({}),// backendName: "wechat-webgl-" + Date.now(),},ENABLE_DEBUG);
}

注:没有限定必须在 App.vueonLaunch() 中初始化,只要在使用 tfjs 相关 API 之前初始化都可以。

在上述代码中,给插件函数 plugin.configPlugin() 传入了一个初始化对象,其中指定了 webgl 和 canvas 这两个属性,之后,插件就会自动开启 webgl 硬件加速。

2、解决 requirePlugin 报错

如果你的 uniapp 工程使用了 typescript ,那么上述代码中 requirePlugin("tfjsPlugin") 处会有红线报错:

Cannot find name 'requirePlugin'.ts(2304)

此处报错是因为 ts 不认识 requirePlugin() 函数,解决这个问题有两种方法:

  • 自定义 requirePlugin 函数声明
  • 配置微信小程序官方 types

1)自定义 requirePlugin 函数声明

在工程的 env.d.ts 文件中,追加如下声明即可:

declare function requirePlugin(pluginName: string): any;

注:在工程 src 目录下的任意 d.ts 文件中声明都是可以的,我习惯将自定义声明写在 env.d.ts 文件中。

声明之后,报错就消失了,这种方法简单粗暴。

2)配置微信小程序官方 types

安装 types:

npm install @types/wechat-miniprogram -D

配置 tsconfig.json

{..."compilerOptions": {..."types": ["@dcloudio/types", "@types/wechat-miniprogram"]},
}

这种方法很优雅,但其实官方 types 中的声明跟我们自己声明的完全一样,用哪种方法都可以~

3、测试

tfjsPlugin 插件初始化的同时,也会把 tfjs 初始化好,接下来就可以使用 tfjs 的 API 了:

import * as tf from "@tensorflow/tfjs-core";console.log(tf.getBackend()); // wechat-webgl
tf.tensor([1, 2, 3, 4]).print(); // Tensor [1, 2, 3, 4]

如果集成环境与插件初始化都没问题的话,那么 tf.getBackend() 拿到的后端字符串就是 wechat-webgl,否则为 undefined,并且 tf.tensor() 会报错 Error: No backend found in registry.。至此,使用 webgl 后端的 tfjs 就可以正常使用了。

四、wasm 后端

上面已经成功开启了 webgl 后端,下面我们来探索如何开启 wasm 后端。几年前,微信小程序在 Android 手机上提供了 WebAssembly 的支持,但后来又废弃了,改为一个类似 WebAssembly 的 WXWebAssembly,以下是 WXWebAssembly 的相关链接:

  • WXWebAssembly 官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html
  • 微信小程序废弃 WebAssembly:https://developers.weixin.qq.com/community/develop/doc/000e2c019f8a003d5dfbb54c251c00

1、环境集成

要想使用 wasm 后端,还需要安装一个 tfjs 的 wasm 后端依赖:

npm i @tensorflow/tfjs-backend-wasm@^3.13.0

此外,因为 WXWebAssembly 目前只支持加载包内 wasm 文件(即 网络链接,或者下载后的本地链接都不行),所以还需要把 node_modules/@tensorflow/tfjs-backend-wasm/wasm-out 目录中的 tfjs-backend-wasm.wasm 文件复制到工程 static 目录下(放其他目录的话,编译时可能会被自动过滤掉),然后给 tfjs 关联一下 wasm 文件路径:

import { setWasmPaths } from "@tensorflow/tfjs-backend-wasm";const usePlatformFetch = true;
setWasmPaths({"tfjs-backend-wasm.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-simd.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-threaded-simd.wasm": "/static/tfjs-backend-wasm.wasm",},usePlatformFetch
);

其中 simd.wasmthreaded-simd.wasm 是性能增强版的 wasm 文件,关于它们之间的性能对比,可以看下面这篇文章:

  • 增强 TensorFlow.js WebAssembly 后端:https://www.infoq.cn/article/hi2vrxfevlelcvvov0g7

虽然但是,这些性能增强的 wasm 文件在微信小程序环境下运行存在兼容性问题,所以这里全部指定为最原始的 wasm 文件;另外,这些 wasm 文件必须放在小程序包内,会增大小程序包体大小,如果对包体大小有较高要求,还需要去了解如何拆分或压缩 wasm 文件。

uniapp - tensorflowjs 之小程序环境集成

2、初始化插件

将所需的依赖和 wasm 文件集成好之后,就可以来初始化 tfjsPlugin 插件并开启 wasm 后端了,完整代码如下:

import { fetchFunc } from "fetch-wechat";
import * as tf from "@tensorflow/tfjs-core";
import { setWasmPaths } from "@tensorflow/tfjs-backend-wasm";
const plugin = requirePlugin("tfjsPlugin");const ENABLE_DEBUG = false;
const usePlatformFetch = true;
setWasmPaths({"tfjs-backend-wasm.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-simd.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-threaded-simd.wasm": "/static/tfjs-backend-wasm.wasm",},usePlatformFetch
);onLaunch(() => {console.log("App Launch");initTfjs();
});function initTfjs() {plugin.configPlugin({// polyfill fetch functionfetchFunc: fetchFunc(),// inject tfjs runtimetf,// // inject webgl backend// webgl,// // provide webgl canvas// canvas: uni.createOffscreenCanvas({}),// backendName: "wechat-webgl-" + Date.now(),},ENABLE_DEBUG);tf.setBackend("wasm").then(() => {console.log(tf.getBackend()); // wasmtf.tensor([1, 2, 3, 4]).print(); // Tensor [1, 2, 3, 4]});
}

注:plugin.configPlugin() 参数对象中的 webgl 和 canvas 这两个属性不注释掉也是可以的,不影响,这个文章末尾解释。

上述代码中,tf.setBackend("wasm") 是最关键的代码,顾名思义,它的作用就是修改 tfjs 的硬件加速后端为 wasm,其返回结果是一个 Promise,即异步操作,需要等待操作完成后,才能调用 tfjs 相关 API,否则会报错。

3、解决 wasm 报错

实际上,按照上述步骤还不能成功运行程序,控制台会报如下错误:

Error: The highest priority backend 'wasm' has not yet been initialized. Make sure to await tf.ready() or await tf.setBackend() before calling other methods

这是一个大坑,很多人按照官方仓库 tfjs-wechat 的说明文档操作,可是死活也没办法成功开启 wasm 后端,这是因为微信小程序把 WebAssembly 废弃,改为 WXWebAssembly,而 tfjs-backend-wasm 这个库中的代码还是使用的 WebAssembly,所以 wasm 后端是不可能初始化成功的。

uniapp - tensorflowjs 之小程序环境集成

tfjs 的官方仓库 issue 中找到一个解决方法:

  • wasm 在小程序上无法工作:https://github.com/tensorflow/tfjs/issues/5021

他的解决思路是编写一个 rollup 插件,将编译代码中的 WebAssembly 相关部分,修改为 WXWebAssembly,uniapp 的 vue3 工程使用的是 vite,我们知道 vite 是兼容 rollup 的,所以可以直接在工程的 vite.config.ts 文件中使用此插件,代码如下:

import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";// https://vitejs.dev/config/
export default defineConfig({plugins: [uni(), codeTransform()],
});function codeTransform() {return {name: "codeTransform",transform(code, file) {// 修复wasmif (file.endsWith("tfjs-backend-wasm-threaded-simd.worker.js") ||file.endsWith("tfjs-backend-wasm-threaded-simd.js")) {code = code.replace(`require("worker_threads")`, "null");code = code.replace(`require("perf_hooks")`, "null");}if (file.endsWith("backend_wasm.js")) {code = code.replace(`env().getAsync('WASM_HAS_SIMD_SUPPORT')`, "false");code = code.replace(`env().getAsync('WASM_HAS_MULTITHREAD_SUPPORT')`,"false");code = code.replace(`return (imports, callback) => {`,`return (imports, callback) => {WebAssembly.instantiate(path, imports).then(output => {callback(output.instance, output.module);});return {};`);}code = code.replace(`WebAssembly.`, `WXWebAssembly.`);code = code.replace(`typeof WebAssembly`, `typeof WXWebAssembly`);return { code };},};
}

uniapp - tensorflowjs 之小程序环境集成

工程重新编译运行之后(让插件生效),就可以正常开启 wasm 后端了。

五、其他

此处再对 tfjs 后端做一些补充,本系列代码会同步保存到 github 仓库上:

  • Demo 仓库地址:https://github.com/GitLqr/uniapp-tensorflowjs-mp

1、动态修改后端

有一点需要明白,插件的 plugin.configPlugin() 初始化函数,它只是一个配置,不管你使用哪个后端,都可以配置上 webgl 和 canvas 这 2 个属性,而 tf.setBackend() 也只是指定 tfjs 后端的一个 API 罢了,如果没有调用,则默认使用 webgl 后端,所以,只要提前配置好 webgl 参数 和 wasm 路径,那么在程序运行时,是可以做到动态修改后端的:

setWasmPaths({"tfjs-backend-wasm.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-simd.wasm": "/static/tfjs-backend-wasm.wasm","tfjs-backend-wasm-threaded-simd.wasm": "/static/tfjs-backend-wasm.wasm",},usePlatformFetch
);async function initTfjs() {plugin.configPlugin({// polyfill fetch functionfetchFunc: fetchFunc(),// inject tfjs runtimetf,// inject webgl backendwebgl,// provide webgl canvascanvas: uni.createOffscreenCanvas({}),// backendName: "wechat-webgl-" + Date.now(),},ENABLE_DEBUG);await tf.setBackend("wasm");console.log(tf.getBackend()); // wasmtf.tensor([1, 2, 3, 4]).print(); // Tensor [1, 2, 3, 4]await tf.setBackend("wechat-webgl");console.log(tf.getBackend()); // wechat-webgltf.tensor([1, 2, 3, 4]).print(); // Tensor [1, 2, 3, 4]
}

2、对比

既然 tfjs 有 webgl 后端和 wasm 后端,那应该使用哪个比较好呢?首先 iOS 的系统环境相差不大,而且目前 WXWebAssembly 对 iOS 平台的支持不如 Android 平台完善,建议统一使用 webgl 后端;其次再来说说 Android 平台,Android 因其开放性,国内 Android 手机厂商会进行个性化定制,甚至有的会进行魔改,所以情况会比较复杂,从我个人目前收集到的资料和反馈来看,优先使用 webgl 后端,大部分的 Android 手机使用 webgl 后端比使用 wasm 后端性能要高得多,个别手机厂商(比如华为)则相反,甚至使用 webgl 后端会出现 tfjs 无法正常使用的情况,建议程序运行时要适当判断机型调整后端,或者界面上提供切换后端的功能。

  • webgl 与 wasm 性能对比:https://github.com/tensorflow/tfjs-wechat/issues/89
  • 原生与小程序性能对比:https://github.com/deepkolos/wxmp-tensorflow/issues/12