基于Bootstrap Webpack示例加载Tensorflow.js模型遇变量重复注册错误
我基于Bootstrap Webpack示例搭建项目,使用Tensorflow.js加载JSON格式的训练模型。此前在无Bootstrap的Webpack环境,或是直接通过script标签引入tfjs的非Node.js环境中,代码均能正常运行,但迁移到该Bootstrap Webpack示例的Node环境后,出现如下错误:
Uncaught (in promise) Error: Variable with name conv2d/kernel was already registered
at Engine.makeVariable (engine.ts:886:12)
at Module.variable (variable.ts:43:2)
at new LayerVariable (variables.ts:98:40)
at Conv2D.addWeight (topology.ts:1315:3)
at Conv2D.build (convolutional.ts:599:8)
at eval (topology.ts:1013:12)
at nameScope (common.ts:61:7)
at Conv2D.apply (topology.ts:998:48)
at Sequential.add (models.ts:557:3)
at fromConfig (models.ts:1048:3)
相关代码细节如下:
main.js
import { prepare } from "./prediction.js"; document.addEventListener("DOMContentLoaded", prepare); ...
prediction.js
import * as tf from "@tensorflow/tfjs"; import Plotly from "plotly.js-dist-min"; var TensorflowModels; var predictConfigs; export function prepare(){ setTensorflowModels(pathOfRootFolder); ... } async function setTensorflowModels(pathOfRootFolder) { let Models = { p: 0 , wake: 0 , ct: 0 }; let configs = { p: 0 , wake: 0 , ct: 0 }; let path; // 加载Python Keras导出的Tensorflow.js训练模型 for (let key in Models) { path = [pathOfRootFolder, key, "model.json"].join("/"); Models[key] = await tf.loadLayersModel(path); path = [pathOfRootFolder, key, "config.json"].join("/"); configs[key] = await getConfig(path); } TensorflowModels = Models; predictConfigs = configs; } ...
webpack.config.js
'use strict' const path = require('path') const autoprefixer = require('autoprefixer') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', entry: './src/js/main.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, devServer:{ static: path.resolve(__dirname, 'dist'), port: 8080, hot: true }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], module: { rules: [ { test: /\.(scss)$/, use: [ { // 注入<style>标签到DOM中添加CSS loader: 'style-loader' }, { // 将@import和url()解析为import/require() loader: 'css-loader' }, { // 用PostCSS处理CSS的Webpack loader loader: 'postcss-loader', options: { postcssOptions: { plugins: () => [ autoprefixer ] } } }, { // 加载并编译SASS/SCSS文件为CSS loader: 'sass-loader' } ] } ] } }
错误原因
这个报错是TensorFlow.js变量命名空间冲突导致的。你的Webpack配置中开启了hot: true(热模块替换,HMR),在开发模式下,当文件修改后Webpack会重新加载模块,但TensorFlow.js的引擎实例不会自动重置;同时prepare函数可能在HMR更新时被多次触发,导致setTensorflowModels重复执行,多次加载相同结构的模型,相同名称的变量(比如conv2d/kernel)被多次注册,最终抛出错误。
解决方案
1. 临时关闭热模块替换(快速验证)
在webpack.config.js的devServer配置中,把hot: true改为hot: false,重启开发服务器后测试是否恢复正常。这种方式适合快速确认问题根源,但会失去HMR的开发便利。
2. 适配HMR,避免重复加载模型
修改代码逻辑,确保模块更新时先清理已加载的模型实例,再重新加载:
- 修改
main.js:
import { prepare } from "./prediction.js"; function initApp() { // 清理已存在的模型实例 if (window.TensorflowModels) { for (const key in window.TensorflowModels) { window.TensorflowModels[key].dispose(); } } prepare(); } document.addEventListener("DOMContentLoaded", initApp); // 处理HMR模块更新 if (module.hot) { module.hot.accept('./prediction.js', () => { initApp(); }); }
- 修改
prediction.js:
import * as tf from "@tensorflow/tfjs"; import Plotly from "plotly.js-dist-min"; // 将模型挂载到window,方便跨模块清理 window.TensorflowModels = window.TensorflowModels || {}; window.predictConfigs = window.predictConfigs || {}; export function prepare(){ setTensorflowModels(pathOfRootFolder); ... } async function setTensorflowModels(pathOfRootFolder) { // 先清理当前模型 for (const key in window.TensorflowModels) { window.TensorflowModels[key].dispose(); } let Models = { p: 0 , wake: 0 , ct: 0 }; let configs = { p: 0 , wake: 0 , ct: 0 }; let path; for (let key in Models) { path = [pathOfRootFolder, key, "model.json"].join("/"); Models[key] = await tf.loadLayersModel(path); path = [pathOfRootFolder, key, "config.json"].join("/"); configs[key] = await getConfig(path); } window.TensorflowModels = Models; window.predictConfigs = configs; } ...
3. 用命名空间隔离模型变量
在加载每个模型时,使用tf.nameScope为模型创建独立的命名空间,避免变量名冲突:
async function setTensorflowModels(pathOfRootFolder) { let Models = { p: 0 , wake: 0 , ct: 0 }; let configs = { p: 0 , wake: 0 , ct: 0 }; let path; for (let key in Models) { // 为每个模型创建独立命名空间 await tf.nameScope(`model-${key}`, async () => { path = [pathOfRootFolder, key, "model.json"].join("/"); Models[key] = await tf.loadLayersModel(path); }); path = [pathOfRootFolder, key, "config.json"].join("/"); configs[key] = await getConfig(path); } window.TensorflowModels = Models; window.predictConfigs = configs; }
内容的提问来源于stack exchange,提问作者y0o0ic

