你有没有想过,浏览器里那些实时抠图、手势识别、AI 美颜的网页应用,背后的“大脑”是怎么跑起来的?想象一家繁忙的餐厅:模型文件是厨房里那本厚厚的菜谱,数据是刚送来的新鲜食材,而 TensorFlow.js 就是那位站在备餐台前的大厨——它负责读菜谱、备食材、开火炒菜,最后还要收拾案板。如果这一整套流程安排不当,厨房就会乱成一锅粥:食材堆在角落没人处理(输入没预处理)、菜谱没翻译好就下锅(模型格式不对)、炒完菜不洗碗(张量不回收),最终拖垮整个出餐速度。这篇笔记,带你把这条“张量流水线”从头到尾走一遍,学会写出既高效又没内存泄漏的推理代码。
掌握 TF.js 模型加载与推理流程,是前端 AI 应用开发者的基本功——读得懂模型、管得住张量、调得对性能,才能让 AI 功能在浏览器里真正跑得又快又稳。
可视化演示
展示TensorFlow.js中GraphModel与LayersModel的加载方式、输入预处理、推理调度与张量回收的完整流程。切换模式查看不同维度,拖动滑块调节模型复杂度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
GraphModel与LayersModel加载 GraphModel加载预优化计算图,推理效率更高 LayersModel支持逐层构建与训练,灵活性更强 load(url) → tf.io.http() → graphModel.predict() GraphModel推荐用于生产推理场景
模型加载方式
想象你开了一家餐厅,菜谱被分成了两种存储形式:一种是「冷冻预制菜包」——所有食材(权重)和操作步骤(计算图)已经被封装成一个完整单元,下单(调用)后直接下锅,速度快、流程固定;另一种是「开放式菜谱」——每一步都有灵活调整的空间,厨师可以中途尝味、改火候、换配料。TF.js 的两种模型加载方式,恰好对应这两种“菜谱形态”。
tf.loadGraphModel() 加载的是 GraphModel——它就像那个冷冻预制菜包:整个计算图从输入到输出是完整封装的,你不需要关心中间层怎么流动,只需提供入口食材(输入张量),它就能吐出成品菜(输出张量)。适合部署已经训练好、不再改变的模型。而 tf.loadLayersModel() 加载的是 LayersModel——它像开放式菜谱,你可以访问每一层的“配料表”(权重),甚至取出中间某层的结果,灵活性更高,代价是管理更琐碎。
两种加载都是异步操作,就像下单后需要等厨房备菜。代码里用 await 等待加载完成,此时模型对象才真正“就绪”,可以开始接受推理请求。
1. GraphModel 加载:使用 loadGraphModel 从 URL 或文件系统加载冻结构建好的计算图模型,适合部署到生产环境。
2. LayersModel 加载:使用 loadLayersModel 加载保留了层结构与权重的模型,支持后续微调或特征提取。
3. 异步就绪等待:借助 await 关键字挂起当前任务,直到模型资源下载并解析完毕,再继续执行后续推理代码。
了解了加载的两种“菜谱形态”,我们学会了选择适合自己的模型导入方案。接下来,食材送到案板上了——这些原始数据还不能直接下锅,需要先经过预处理。
const graphModel = await tf.loadGraphModel('https://example.com/model.json');const layersModel = await tf.loadLayersModel('https://example.com/layers/model.json');
输入预处理
食材从市场买回来,不能直接扔进锅里——得先摘菜、洗菜、切块、按菜谱要求称重。模型的输入张量也一样,浏览器里的原始数据(图片像素、文本 token、音频波形)几乎不可能直接满足模型对形状和数值范围的要求。
最常见的第一步是「数据转张量」。假设你有一张 224×224 的图片,它原本是 HTMLImageElement 或像素数组,需要调用 tf.browser.fromPixels() 把它变成形状为 [224, 224, 3] 的张量——就像把整颗白菜切成菜谱要求的细丝。第二步是「归一化缩放」。训练时模型“尝过”的味道是 0 到 1 之间的数值,你要是直接把 0 到 255 的像素值塞进去,相当于往汤里倒了一整罐盐——模型会给出完全错误的预测。所以需要除以 255(或其他归一化系数),把数据拉回模型熟悉的口味范围。
第三步是「形状对齐」。模型通常要求输入带有批次维度,比如 [1, 224, 224, 3] 而不是 [224, 224, 3]。这就像菜谱写着“每份 200 克”,你不能丢一整颗 2 斤的白菜进去,得切出正好 200 克的部分,并用 expandDims 在开头补一个维度——相当于给食材套上一个“一份”的定量包装盒。
预处理完毕,食材已经变成标准化的“配菜包”。现在厨房里最关键的一步来了——大厨要正式开炒了。这就要理解 predict 和 execute 两种翻锅方式。
[0,1] 区间,再扩展批次维度,得到模型期望的标准输入形态。- 像素转张量
tf.browser.fromPixels()将图像数据解码为形状[H, W, 3]的 RGB 张量,对应菜品切丝。 - 归一化缩放每个像素值除以 255,将
[0, 255]映射到[0, 1]区间,保证数值范围与训练时一致。 - 批次维度扩展在 0 轴插入尺寸为 1 的新维度,把单样本张量包装成模型要求的批次输入。
推理调度
大厨站在灶台前,面对两种做菜需求:一种是客人点了一道完整套餐(从开胃菜到甜品全流程),你不用多想,按既定顺序把配菜包依次下锅就行——这就是 model.predict();另一种是客人只想知道“这道菜的酱汁配方是什么”,你只需从菜谱中抽出中间某一步看看,不必把整套菜做完——这就是 model.execute()。
predict 是推理调度的主入口。它对输入的张量依次执行计算图中的每一层操作,最终返回模型的输出张量。整个过程像一个密封的黑箱流水线:食材从一端进去,成品从另一端出来,中间发生了什么你不需要干预。execute 则更灵活——你可以指定从某一层(比如第 3 个卷积层)取出中间特征图,就像在流水线上打开一扇检修窗,直接把半成品抽出来检查。这在迁移学习、特征可视化、多任务共享场景中非常有用。
在调度策略上,批处理是常见的加速手段。一次传入一批数据(比如 32 张图片),相当于同时开 32 个灶头炒 32 份相同的菜,比单独炒 32 次翻锅效率高得多。TF.js 的 WebGL 后端对批处理张量的并行计算有天然优势。
菜炒好了,端上桌,客人吃完走了……等等——厨房里堆成山的脏碗碟谁来洗?如果不及时清洗,下一波客人来了就没碗用了。这正是张量回收要解决的问题。
[B, H, W, C] 经过 predict 返回形状 [B, numClasses] 的概率分布,批次大小 B 决定了单次推理的吞吐量。- 预测执行将预处理好的批量输入张量送入模型,计算图从前向后逐层传播,直到输出层产生结果。
- 特征抽取通过
execute指定中间层名字,获取该层的激活输出,便于分析或复用于其他任务。 - 批次传输将多个独立样本堆叠成一个批次张量,使 WebGL 能够并行处理,摊薄单次调用开销。
张量回收
TensorFlow.js 最隐蔽的“厨房杀手”就是张量泄漏。WebGL 后端把张量数据存放在 GPU 显存里,而显存是极其宝贵的资源——如果创建一个张量不销毁,就像把用过的脏碗碟随手一放不再管它,几百个碗碟之后,厨房(GPU 显存)彻底瘫痪,浏览器标签页直接崩溃。
最直接的回收方式是 tensor.dispose()——相当于用完之后立刻把碗洗干净放回碗柜。但这个手动过程容易遗漏,尤其当函数提前返回或抛出异常时,中间的临时张量就永远“没人洗碗”了。更稳妥的方案是用 tf.tidy() 包裹一段代码:它像请了一位“自动洗碗机管家”,在函数执行完毕时自动清理内部创建的所有中间张量,只保留你显式返回的结果。tf.tidy() 能显著降低泄漏风险,是编写高效推理代码的必备工具。
张量回收还有一条铁律:dispose 只能释放没有被其他张量引用的“独立碗碟”;如果多个变量引用同一个底层数据(比如 slice 或 reshape 产生的视图),直接 dispose 会触发一次性释放底层全部数据,导致其他引用方拿到“被摔碎的碗”。
厨房的碗洗好了,但光是“不泄漏”还不够。真正优秀的餐厅还要考虑出餐速度——这就是性能优化登场的时候。
dispose 或 tidy 归还,否则可用显存持续缩水。- 手动释放张量使用完毕后调用
dispose(),立即归还其 GPU 显存,适合生命周期明确的临时结果。 - Tidy 自动清理
tf.tidy(fn)在回调函数执行完毕后,自动释放内部创建的所有张量,仅保留返回值。 - 泄漏防治通过
tf.memory()查询当前张量数量与显存占用,持续监控数值是否单调上升。
性能优化
客人排队等餐时,餐厅老板最关心的是“翻台率”——如何在单位时间内服务更多客人?TF.js 推理的性能优化,同样围绕“让每次推理更快、让单位时间完成更多推理”展开。第一个思路是「缓存复用」:很多输入数据虽然每次略有不同,但中间很多计算步骤的结果是高度可复用的。例如视频帧处理时,相邻两帧的差异很小,如果你每帧都重新跑完整模型,相当于把上一秒刚倒掉的汤重新熬一遍。缓存上一帧的特征输出,与当前帧结果做轻量融合,能大幅降低计算开销。
第二个思路是「批处理合并」:把多次独立的推理请求合并成一次批量推理。比如一个页面有 10 张图片需要同时分类,与其循环 10 次 predict 各用一次 GPU 调度,不如把 10 张图叠成一个 [10, H, W, C] 的大张量一次性推理,GPU 并行计算吞吐量可提升数倍。这里要理解:模型推理的“固定开销”(CPU 调度、GPU 上传、后端初始化)是相对恒定的,通过批量可以摊薄这部分开销。
第三个思路是「帧循环调优」。在实时视觉应用(如摄像头姿态检测)中,把推理塞进 requestAnimationFrame 回调里要非常小心:如果推理耗时超过 16ms(60fps),画面就会掉帧卡顿。常见的优化路径是降低推理频率——比如每 2 帧推理一次,中间那帧复用上一次的结果做插值——或者缩小输入尺寸,用精度换取流畅度。
性能调优做完,厨房的出餐速度拉满了。但万一某个时刻出的菜味道不对——是盐放多了还是火候过了?总得有个办法快速定位问题在哪里。这就是调试技巧的用武之地。
- 缓存复用把耗时特征计算的结果保存在变量中,供后续帧复用,避免重复计算相同内容。
- 批处理合并将多个独立推理样本堆叠成一个批次,一次
predict完成多次推理。 - 帧率计算推理耗时与渲染耗时共同决定帧率,推理耗时可视为计算量除以后端吞吐量。
调试技巧
大厨最有成就感的时刻,不只是把菜炒好,而是当一道菜出了问题,他能一尝就知道是哪个环节失误:是食材不新鲜(输入数据有问题)、火候不对(模型参数异常)还是温度计坏了(后端环境故障)?TF.js 推理最常遇见的“翻车现场”有三个:形状对不上、数值异常、性能突然劣化。
第一招是「形状检查」。TensorFlow.js 的模型对输入形状极其严格——模型期望 [1, 224, 224, 3],你要是传了 [224, 224, 3],它直接抛异常,就像菜谱写着“每份 200 克”而你丢进去一整颗白菜。排查方式很简单:打印 tensor.shape 对比模型签名中 inputs[0].shape,即可快速定位形状不匹配。
第二招是「数值验证」。模型输出全为 NaN 或 Infinity,很多时候不是模型坏了,而是输入没有归一化,或者归一化时除以了一个为 0 的标准差。这相当于把盐罐子打翻在汤里——浓度完全超出模型的味觉范围。用 tensor.min() 和 tensor.max() 检查输入数值范围是否处于合理区间,是快速锁定问题的手段。
第三招是「监控工具」。使用 tf.memory() 查询张量数量,如果随着推理次数增加内存持续上升,说明存在张量泄漏;使用 Chrome DevTools 的 Performance 面板录制运行时火焰图,能定位具体哪一次推理调用耗时异常。这就像给厨房装了一台“能源监控表”,每道菜的燃气消耗、用水量一目了然,异常开销无处遁形。
调试完成,厨房的整条流水线从备菜到炒菜到洗刷都形成了闭环。这时你会发现,TF.js 的模型加载与推理流程,本质上就是一套精细的餐厅运营管理——把每一步都安排得明明白白,效率和稳定自然就来了。
- 形状比对用
tensor.shape与模型签名中的期望形状逐维比对,形状不符时捕获异常并定位到具体输入。 - 数值范围检查计算张量的最小值和最大值,确认输入归一化是否生效,避免数值溢出。
- 内存监控周期性调用
tf.memory().numTensors,绘制张量数量随迭代次数的变化曲线,判断是否存在泄漏。
AI 应用场景
浏览器实时姿态检测
阶段:输入预处理 → 推理调度 → 张量回收
解决的问题:传统姿态检测需要把视频帧上传到服务器推理,延迟高且隐私风险大。采用 TF.js 在浏览器本地执行,结合 tf.browser.fromPixels 预处理视频帧、model.predict 推理和 tf.tidy 自动清理,实现实时且隐私安全的姿态估计算法,避免每帧显存泄漏。
具体案例:某在线健身应用需要实时检测用户深蹲动作是否标准。工程师将 MoveNet 模型通过 tf.loadGraphModel 加载到客户端,每帧回调中先用 fromPixels 将摄像头画面转为张量、缩放并归一化,再用 tidy 包裹推理并 dispose 中间结果,最终在 60fps 下稳定运行,且 GPU 显存占用保持恒定。
在线抠图工具
阶段:模型加载 → 批处理推理
解决的问题:传统抠图需要逐像素手动编辑,效率极低。通过 TF.js 在前端加载分割模型,利用批处理同时处理多张上传图片,为设计师提供秒级自动抠图能力。
具体案例:某电商设计平台为商家提供商品图一键去背景功能。商家批量上传 50 张商品图片后,前端将图片堆叠为 [50, 256, 256, 3] 的批量张量一次推理,后台用 GPU 并行加速,整体耗时仅为单张循环推理的 1/8。推理后的遮罩张量通过 dispose 及时释放,确保长期批量操作的稳定性。
端侧智能客服意图识别
阶段:LayersModel 加载 → 特征复用
解决的问题:云端客服机器人依赖网络往返,时延高且费用随调用量增长。把小型意图识别模型部署到浏览器,通过 loadLayersModel 加载,并利用 execute 抽取文本向量特征做本地缓存,实现毫秒级响应。
具体案例:某银行在线客服系统在页面加载时异步加载意图识别 LayersModel,首次输入时用 execute 提取句子特征向量并缓存;后续相似问题直接复用缓存特征做余弦相似度匹配,避免重复推理,大幅降低云端调用成本。
Python 演示
# 使用 tfjs-node 的 JS 版流程,Python 展示用 tf.py 风格伪代码import numpy as np class TfjsInferencePipeline: def __init__(self, model_path): self.model_path = model_path self.intermediate_cache = {} def load_model(self): # 模拟 loadGraphModel 异步加载 print(f"[1] 加载模型: {self.model_path}") self.cache_list = {"linear.weight": np.random.randn(1000, 512)} # 关键: 列表用 _list 后缀,避免与方法同名 self.cache_list["bias"] = np.zeros(1000) return self def preprocess(self, raw_pixels): # 模拟 tf.browser.fromPixels + 归一化 + expandDims h, w, c = raw_pixels.shape normalized = raw_pixels.astype(np.float32) / 255.0 batched = normalized.reshape(1, h, w, c) return batched def predict(self, x): # 模拟 model.predict n, h, w, c = x.shape flat = x.reshape(n, h * w * c) logits = flat @ self.cache_list["linear.weight"] logits += self.cache_list["bias"] probs = np.exp(logits - np.max(logits, axis=1, keepdims=True)) probs /= np.sum(probs, axis=1, keepdims=True) self.cache_list["zombie_info"].append(probs.shape) return probs def infer_and_cleanup(self, image): # 完整流程: 预处理 -> 推理 -> tidy/dispose x = self.preprocess(image) try: output = self.predict(x) print(f"[2] 推理完成: 输出形状 {output.shape}") print(f"[3] 验证: min={output.min():.3f}, max={output.max():.3f}") return output finally: # 模拟 tidy 自动释放: 引用计数归零即清除 del x print("[4] 中间张量已释放") def run(self, image_path): self.load_model() fake_image = np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8) result = self.infer_and_cleanup(fake_image) print(f"[5] 预测类别: {int(np.argmax(result[0]))}") if __name__ == "__main__": pipe = TfjsInferencePipeline("https://example.com/model.json") pipe.run("demo.jpg")
class TfjsInferencePipeline:定义一个推理管线类,对应整个 TF.js 流水线的封装结构。self.cache_list = {"linear.weight": ...}模拟权重存储,用_list后缀避免与predict等方法重名冲突。def preprocess(self, raw_pixels):完成像素转张量、归一化、批次维度扩展三步关键操作。normalized = raw_pixels.astype(np.float32) / 255.0像素值除以 255 映射到[0,1],保持数值范围与训练一致。batched = normalized.reshape(1, h, w, c)在开头插入批次维度,对齐模型预期输入形状。def predict(self, x):模拟model.predict的完整前向传播过程。logits = flat @ self.cache_list["linear.weight"]矩阵乘法完成线性变换,相当于全连接层计算。probs = np.exp(logits - np.max(...))softmax 归一化,把得分变成概率分布。def infer_and_cleanup(self, image):用 try/finally 模拟tf.tidy的自动清理语义。del x删除中间张量引用,让 GC 能回收内存,等价于tensor.dispose()。
常见误区
误区`await tf.loadGraphModel(url)` 返回了一定是“能用”的模型对象,直接拿去推理就行,不需要任何额外检查。
正解await 只能保证加载过程没有抛出异常,不保证模型内容与你的输入匹配。就像你等到了外卖,但不代表送达的就是你点的菜。加载后应打印 model.inputs 和 model.outputs 签名,确认输入输出张量的形状、数据类型与你的预期一致,否则第一次推理就会报 shape mismatch 错误。
为何常见:初学者把模型加载理解成一个“开箱即用”的黑盒步骤,忽略了模型本身还携带输入输出签名信息,这些信息是推理正确性的关键约束。
误区推理结束后,输出的张量不用了,直接忽略即可,系统会自动回收。
正解TF.js 的张量不遵守 JavaScript 的自动垃圾回收规则——predict 返回的每个张量都占用 GPU 显存,如果你不调用 dispose() 或放在 tf.tidy() 里,显存会随着推理次数线性增长,最终导致浏览器崩溃。这就像用过的碗不洗,攒到一定数量厨房就彻底瘫痪。任何从 predict/execute 返回的中间张量,若不再需要,必须显式释放。
为何常见:JS 的常规对象(Object/Array)确实有 GC 自动回收,初学者容易把这套规则套用到 GPU 张量上,忽略了 WebGL 后端不在 JS 堆内存中管理数据这一事实。
误区实时摄像头应用每一帧都调用 `model.predict`,帧率越高,AI 能力越强。
正解推理不是免费的,每帧调一次 predict 会让 GPU 满载,渲染线程被阻塞,实际帧率反而降到个位数。更合理的设计是「低频推理 + 帧间插值」:每 3 帧推理一次,中间两帧复用上一帧的结果,配合 requestAnimationFrame 的渲染调度,视觉流畅度和 GPU 负载达到平衡。这就像出餐快的餐厅不一定是每道菜都现炒的,而是提前备好了半成品快速翻热。
为何常见:初学者追求“毫秒级实时推理”的指标本身没有错,但忽略了推理与渲染共享 GPU 资源的现实,没有意识到降低推理频率反而能提高整体帧率。
误区所有张量操作都塞进 `tf.tidy()`,就不会再有任何显存泄漏。
正解tf.tidy 只能清理它内部创建的中间张量,但它「保留返回值」的策略有坑——如果你在 tidy 里创建了多个张量,返回其中一个,其他没有显式 keep 的张量都会被释放。另外,如果张量被存入外部变量(如 this.output_cache),tidy 不会释放它,因为它的引用逃逸出了回调作用域。正确的姿势是:明确区分「内部临时张量」(用 tidy 自动清理)与「长期持有的缓存张量」(手动管理生命周期)。
为何常见:tidy 名字太有迷惑性,让初学者以为它是万能管家,其实它的核心规则只有一条——只有当张量的引用生命周期完全包含在回调函数内部时,它才会被自动释放。
误区部署的模型输出 `NaN`,立刻怀疑是训练阶段模型没有收敛,打算重新训练。
正解NaN 输出最常见的原因在输入侧:像素值没有归一化、输入包含 Infinity、或者归一化时除数为零。把输入数值范围打印出来,检查 min/max 是否落在预期区间;再检查模型输入层的数据类型是不是 float32 而不是 int32。就像菜咸了,先检查是不是盐罐子打翻了,而不是立刻重新买一套锅。数据侧排查完毕后,再考虑模型权重是否损坏。
为何常见:NaN 在训练中确实常代表梯度爆炸,但部署推理时模型权重是冻结的,不会产生梯度,数据侧问题才是主因。这一区别初学者容易混淆。
面试题
Q1. tf.loadGraphModel() 与 tf.loadLayersModel() 的核心区别是?
- A.GraphModel 适合需要微调的模型,LayersModel 适合部署
- B.GraphModel 加载冻结的计算图,LayersModel 保留层结构← 正确答案
- C.GraphModel 只支持浏览器端,LayersModel 只支持 Node.js 端
- D.两者完全等价,只是调用方式不同
答案:B。GraphModel 是冻结的完整计算图(像预制菜包),适合部署,但不可微调;LayersModel 保留层结构和权重(像开放菜谱),支持微调与特征提取。选项 B 准确描述了两者的区别。A 的映射关系反了;C 两者都支持多端;D 忽略了本质差异。
Q2. 下列哪一步属于输入预处理的必要环节?
- A.把像素值从
[0, 255]归一化到[0, 1]← 正确答案 - B.将图片编码为 Base64 字符串
- C.把模型权重转换为 float16 格式
- D.使用
tf.dispose()清空输入缓存
答案:A。模型训练时输入范围通常是 [0,1],部署推理时必须对原始像素做同样的归一化,否则模型“尝到的味道”与训练时不一样,预测结果会错乱。B 是传输格式不是预处理;C 是模型转换;D 是事后回收,与预处理无关。
Q3. model.execute(input, 'intermediate_layer') 返回的是什么?
- A.模型的最终分类概率
- B.指定中间层的特征张量← 正确答案
- C.模型的权重参数
- D.输入数据的梯度
答案:B。execute 可以在计算图中间打开一扇窗,取出指定层的激活输出(特征张量),适用于特征可视化和迁移学习。A 是 predict 的返回值;C 是权重不是特征;D 是训练阶段才有的概念,推理没有梯度。
Q4. 关于 tf.tidy(),下列说法正确的是?
- A.它能够释放所有全局变量引用的张量
- B.它只自动清理回调函数内部的中间张量← 正确答案
- C.它会自动释放模型的权重张量
- D.它等同于强制调用浏览器垃圾回收
答案:B。tf.tidy(fn) 跟踪回调函数内创建的所有张量,在函数结束时自动释放,但返回值会被保留。它无法释放外部变量引用的张量(如模型内部的权重),也跟 JS 垃圾回收机制无关。B 是精确描述。
Q5. TF.js 推理后张量不执行 dispose(),最可能的后果是?
- A.推理结果立即消失
- B.GPU 显存持续占用导致崩溃← 正确答案
- C.CPU 温度升高
- D.模型权重自动清零
答案:B。每个张量在 GPU 显存中占据空间,循环推理时不断创建张量但不释放,显存会线性增长最终耗尽导致页面崩溃。这就像厨房里脏碗不洗,很快就没碗可用。A 张量不释放不会立即消失;C 与 CPU 温度无关;D 权重不会被清零。
Q6. 下列哪些操作可以有效提升 TF.js 推理性能?
- A.把多次推理合并为批量推理← 正确答案
- B.缓存上一帧的特征输出← 正确答案
- C.每帧对全图执行一次完整推理
- D.降低推理频率并配合帧间插值← 正确答案
答案:ABD。批处理摊薄固定开销、缓存特征避免重复计算、降低推理频率配合插值都是有效优化手段。C 每帧全图推理恰恰是性能杀手,会让 GPU 满载并阻塞渲染。A、B、D 分别对应批处理合并、缓存复用与帧循环调优三大策略。
Q7. 调试时发现模型输出全部为 NaN,优先检查的步骤是?
- A.重新初始化模型的全部权重
- B.检查输入张量的数值范围是否归一化← 正确答案
- C.更换 WebGL 后端为 CPU 后端
- D.删除模型中的所有 Dropout 层
答案:B。部署推理时权重是冻结的,NaN 绝大多数来自输入异常——像素未归一化、包含 Infinity 或除零。先打印输入 min/max 确认数据范围。A 是训练阶段操作;C 换后端不能解决数据问题;D Dropout 在推理时本就不激活。
Q8. 关于张量生命周期的管理,哪些说法是正确的?
- A.
tensor.dispose()释放后仍可继续访问该张量 - B.
tf.tidy()适合包裹一段包含多个中间张量的推理代码← 正确答案 - C.
tf.memory()可以查询当前存活张量的数量← 正确答案 - D.
dispose一个切片视图会同时释放底层全部数据← 正确答案
答案:BCD。dispose 后张量立即失效,访问会抛异常,A 错误。tidy 自动清理内部中间张量,适合包裹推理代码段,B 正确。tf.memory() 返回张量总数与显存占用,C 正确。切片张量底层共享同一块数据,dispose 视图会触发底层数据释放,D 正确。
Q9. await tf.loadGraphModel(url) 中的 await 的作用是?
- A.让页面无阻塞,模型下载完成后继续执行后续代码← 正确答案
- B.强制模型在 CPU 上运行
- C.自动释放模型中的所有张量
- D.将模型转换为 LayersModel 格式
答案:A。模型加载是网络异步操作,await 挂起当前函数直到加载完成,期间不阻塞主线程。它不涉及后端选择(B)、内存释放(C)或格式转换(D)。就像点完菜后可以先做别的事,等菜好了服务员会再叫你。
Q10. 使用 execute 从模型中抽取中间特征,主要应用场景是?
- A.压缩模型存储体积
- B.特征可视化或迁移学习特征提取← 正确答案
- C.替换模型的全连接层
- D.加速模型文件下载
答案:B。execute 能够指定中间层取出特征激活值,常用于可视化卷积层学到了什么(特征可视化)或把中间层输出作为向量用于其他任务(迁移学习)。A、C、D 与模型压缩、结构修改、下载加速无关。