简介Teachable Machine 是谷歌推出的交互式机器学习教学项目面向零基础学习者与教育者可直观理解计算机视觉、声音识别等AI基础概念。此资源以CSS为标签意味着项目包含完整的网页端界面实现适合前端开发者或AI初学者作为入门案例。压缩包内共7个文件包含2个JavaScript脚本、1个HTML页面、1个CSS样式文档、1个Markdown说明、1份许可证及1张预览图片整体大小仅162KB轻量易部署。实际内容涵盖在浏览器中调用TensorFlow.js、收集图像或声音样本并训练分类模型、通过数据标注理解监督学习以及迭代优化模型效果等关键环节同时HTML/CSS/JS文件展示了网页布局与交互逻辑可帮助学习者将人工智能功能融入网页应用。项目还附带README文档与图片素材便于快速上手。目前已有445人学习/下载适合想结合前端与机器学习进行实践的学生或开发者参考。1. 训练一次、部署到处teachable_machine 资源包把零代码训练到网页部署串起来了很多人拿到一个训练好的分类模型不是卡在“怎么训练”而是卡在“训练完之后怎么办”——模型文件往哪个目录放、标签文件要不要跟着上线、摄像头画面和模型推理结果怎么对齐、前端界面改个颜色会不会影响加载性能。这套 teachable_machine 资源包解决的正是这个衔接断层从浏览器图形化界面训练图像分类器开始到导出模型结构与权重再到编写推理脚本、用 CSS 把识别结果串成看得见的界面整条链路的可运行代码和注释都在包里。它面向两类人一是需要快速做原型验证的前端开发者二是拿图像分类做课程设计但不想碰复杂训练框架的从业者不需要本地配置训练环境有个代码编辑器和现代浏览器就能跑通。2. 资源包拆解模型文件、调用脚本与 CSS 三块怎么咬合2.1 目录结构与文件用途先看整个包的目录规划因为后面所有调试都建立在“目录别动”这个前提上。资源包解压后是这样一个结构文件/目录职责部署注意index.html页面入口挂载摄像头区、结果区、控制按钮放到静态服务器根目录即可css/app.css取景框、置信度条、状态徽章的视觉定义里面用到 CSS 变量别删:root段js/main.js页面业务逻辑初始化、推理循环、结果渲染依赖loader.js导出的类js/loader.js模型加载器读模型结构、权重分片、元数据完整解析器不建议从零重写model/model.json网络结构与算子清单上线时必须完整保留model/weights.bin训练好的网络权重二进制用相对路径引用别放到别的目录model/metadata.json标签列表、输入尺寸、图像归一化参数预测前端要读它对齐类别顺序model/labels.txt人读的标签顺序每行一个类别名与metadata.json的labels字段保持一致有些人会为了“看起来干净”把模型文件移到assets/下结果权重路径变了加载直接 404。我的习惯是让model/目录和js/、css/平级并且所有路径都用相对路径写死部署到子目录也不容易翻车。2.2 模型元信息与输入输出约定model.json和weights.bin是黑匣子真正需要人读的是metadata.json。我在第一次跑通后专门打开metadata.json看了一眼里面最关键的是三项imageSize模型期望的输入边长典型值是 224表示要把摄像头画布缩放到 224×224 后再喂给网络labels类别名数组顺序就是预测结果数组的下标顺序错一位全错inputRange像素归一化范围有的是[0,1]有的是[-1,1]推理前要有对应的像素转换逻辑。这套资源里loader.js会读这三个字段并自动做预处理。真正要记的是设计约定标签顺序在metadata.json和labels.txt之间是映射关系不是排序关系。如果你在训练工具里改了类别顺序导出后必须同步检查这两个文件的顺序否则推理时输出“剪刀”实际图里是“石头”。2.3 CSS 在这套资源里的角色界面骨架与状态反馈以前我觉得 CSS 在模型部署项目里就是点缀直到自己写识别结果界面才发现CSS 承担的任务是“让预测结果可读”摄像头取景框用什么比例裁切、置信度条怎么随概率变化、当置信度低于阈值时怎么让界面进入“无法判断”状态。css/app.css里定义了三类核心样式变量:root { --score: 0; /* 由 JS 动态更新控制置信度条宽度 */ --status-color: #9ca3af; /* 低于阈值时为灰色高于阈值切换为绿色 */ --active-opacity: 0; /* 结果区域的显隐默认隐藏 */ }推理脚本只负责更新这三个变量的值CSS 层自动重绘。这样好处是业务逻辑不用反复改类名视觉状态全部收敛在样式表里后面如果要做暗色模式改 CSS 变量就行不用碰推理代码。这也是这套资源包比较“现代”的设计选择。3. 从浏览器训练到导出模型参数设置与四类输出格式3.1 训练前参数周期数、学习率与数据增强怎么设在图形化工具里训练图像分类模型不需要写训练脚本但有几个参数直接决定导出结果能不能用。周期数常见做法是每类样本在 40-80 张之间时周期数设在 50 左右。太少了欠拟合屏幕上总能测出“背景”和“物体”分不清太多了容易把拍摄时的光线特征也学进去换个房间就失灵。学习率不是每个工具都暴露这个参数但如果可以调优先选 0.001 附近的预设值不要直接拉到 0.01。我实际测过学习率偏大时损失曲线会在后半段反复震荡导出的模型在测试集上表现极其不稳定。数据增强只要是部署到真实摄像头场景建议一律开启。让工具在训练时随机旋转、缩放、翻转样本能显著减少“换角度识别就翻车”的问题。这套资源包能顺利跑起来的前提是训练环节别偷懒每个类别至少 30 张样本拍摄时背景要换物体不要总出现在画面正中央。类别不均衡是预测时最隐蔽的坑某类样本特别多模型会把高概率无脑判给别人数多的那个类。3.2 导出选项完整模型包与单权重文件怎么选训练完成后导出面板里通常有几种选项。我按实际项目用得最多的场景推荐选“网页嵌入模型包”它会输出model.json、weights.bin、metadata.json和labels.txt四个文件。导出选项产物适配场景完整模型包结构权重元数据标签浏览器端推理推荐首选仅模型结构只有model.json继续二次训练不能直接部署仅权重分片多个.bin分片配合已有结构做增量更新标签映射两份标签文件单独校验类别顺序用如果你的项目还要做服务端推理模型包同样适用因为model.json本身就是标准格式后端加载器也能读。我一般会保留训练时的原始导出存档前端部署只复制一份到model/目录避免后面调参时把线上模型覆盖掉。3.3 样本采集的均衡策略与背景干扰控制“收集样本”这个环节看起来简单实际上是整套流程里最容易翻车的地方。我在课程设计里帮人排查过一次模型在训练现场识别率 95%拿到答辩教室就掉到 60%后来发现他的样本全部在同一张书桌上拍的模型真正学会的是“识别那张桌子”物体只是桌子纹理的附属品。正确的做法是每类样本覆盖 2-3 个背景、3-4 个角度、1-2 种光线条件每一类保持数量接近。如果某一类实在凑不够样本优先砍掉这一类也不要拿数量占比悬殊的数据硬训。资源包里的README也是这个思路它的启动脚本里甚至放了一个“训练前自检”清单专门列出背景、角度、光线三个维度的检查项。4. 把模型接进网页应用推理脚本与 CSS 集成实录4.1 加载模型与初始化摄像头进入实际编码前明确一点js/loader.js里封装了完整的模型解析器main.js只需要调用它提供的接口。初始化这一段作用是从model/目录读取结构、元数据和权重二进制并启动摄像头预览。// js/main.js import { TMClassifier } from ./loader.js; let net; const video document.getElementById(camera); const canvas document.getElementById(frame); const ctx canvas.getContext(2d); async function init() { net new TMClassifier(); await net.load({ model: ./model/model.json, weights: ./model/weights.bin, metadata: ./model/metadata.json }); const stream await navigator.mediaDevices.getUserMedia({ video: { facingMode: environment } }); video.srcObject stream; await video.play(); requestAnimationFrame(predictFrame); }代码逻辑如下先实例化分类器按相对路径加载三个文件再请求摄像头权限facingMode: environment是让后置摄像头工作桌面端没有后置摄像头时这个字段会被浏览器忽略不影响运行。net.load内部会校验metadata.imageSize所以后面缩放画布时不需要写死数字。4.2 推理循环与结果输出推理不是一次性的是持续地把摄像头画面送入模型、拿回概率数组、更新界面。这里有一个高频见到的错误写法直接拿摄像头流本身去预测不经过画布缩放。模型期望 224×224 的输入摄像头原始分辨率通常是 1280×720尺寸不匹配经常会得到不可思议的结果。async function predictFrame() { const size net.imageSize; // 把当前帧缩放到模型要求的输入尺寸 ctx.drawImage(video, 0, 0, size, size); // 取出画布像素并送入模型 const imageData ctx.getImageData(0, 0, size, size); const result await net.predict(imageData); // 找到概率最大的类别 let max { className: unknown, probability: 0 }; for (const item of result) { if (item.probability max.probability) { max item; } } renderResult(max); requestAnimationFrame(predictFrame); }关键参数在net.predict(imageData)loader.js内部会按metadata.inputRange做归一化。如果你自己接其他模型一定先确认输入范围是[0,1]还是[-1,1]。这段代码用requestAnimationFrame做循环而不是setInterval是因为浏览器会在页面不可见时自动暂停帧回调显著降低后台运行的资源占用。4.3 CSS 实装取景框、置信度条与阈值状态有了推理结果界面怎么呈现就是 CSS 的事了。资源包的做法是用 CSS 变量把“当前最高概率”和“是否高于阈值”暴露给样式层视觉全部由app.css接管。.camera-frame { aspect-ratio: 4 / 3; overflow: hidden; border-radius: 12px; } .camera-frame video, .camera-frame canvas { width: 100%; height: 100%; object-fit: cover; } .prob-bar__fill { width: calc(var(--score) * 1%); height: 100%; background: var(--status-color); transition: width 0.15s ease-out; } .result-panel { opacity: var(--active-opacity); }renderResult在 JS 侧更新这些变量function renderResult(prediction) { const panel document.getElementById(result-panel); const fill document.getElementById(prob-fill); const label document.getElementById(prob-label); const score Math.round(prediction.probability * 100); const passed prediction.probability 0.8; fill.style.setProperty(--score, score); fill.style.setProperty(--status-color, passed ? #16a34a : #9ca3af); panel.style.setProperty(--active-opacity, passed ? 1 : 0); label.textContent ${prediction.className} ${score}%; }注意aspect-ratio和object-fit: cover配合可以保证摄像头画面无论什么分辨率都填满取景框且不变形这是移动端最容易踩的表现层坑。用style.setProperty更新 CSS 变量比频繁切换类名要高效置信度条每秒变化几十次也不会有明显掉帧。5. 常见问题与避坑五个真实翻车点5.1 摄像头黑屏但页面没有报错现象页面打开后取景框是黑的控制台也没有红色报错getUserMedia好像没执行。原因这类工具代码要能访问摄像头页面必须在https或localhost环境下运行。直接用file://打开index.html时浏览器默认阻止摄像头权限而且不抛异常看起来像一切正常。解决本地起一个静态服务器我用的是npx serve .然后把地址从http://127.0.0.1:3000打开。部署到线上时也要确认站点是 HTTPS。5.2 第一次预测卡顿后面才流畅现象页面加载完成后第一次点击“开始识别”明显卡了 2-3 秒之后就好了。原因模型加载阶段只读取了结构和权重真正的算子初始化发生第一次推理时如果浏览器用的是 CPU 后端初始化耗时会更明显。解决在初始化阶段预留预热推理加载模型后主动跑一次全零输入提前触发算子优化。另一个思路是显式指定推理后端优先让浏览器启用 WebGL代码里用net.setBackend(webgl)处理。5.3 预测结果错位比如“剪刀”永远显示成“石头”现象模型训练时看起来正常网页里预测结果总是指向前一个类别或后一个类别。原因metadata.json里的labels顺序和labels.txt不一致或者训练工具导出后本地手工改动过标签文件导致概率数组下标与界面显示文本错位。解决以训练工具导出时刻为准把两个文件逐行比对我习惯保留导出时的原始存档一旦发现错位就直接从存档里恢复metadata.json和labels.txt不手工改顺序。5.4 置信度很高但识别内容总是错的现象无论画面里放什么物体置信度都稳定在 90% 以上预测结果却和画面明显不符。原因训练样本的背景太单一模型学会了“背景主导”物体本身反而没学到。这是典型的过拟合到环境不是代码问题。解决回到训练环节重新采集样本换背景、换拍摄角度、开启数据增强。这个坑在代码层面无法补救只能重训。5.5 部署到子路径后模型加载 404现象开发环境一切正常部署到服务器的二级目录后模型文件全部 404。原因代码里用的是绝对路径/model/model.json部署到二级目录后实际路径变成了/project/model/model.json地址自然对不上。解决资源包所有加载路径都是相对路径./model/...页面入口与model/目录保持相对关系即可。如果你自己改过路径排查时优先看浏览器 Network 面板里失败请求的完整 URL。6. 从能跑到跑得稳置信度阈值、模型量化与移动端降级把模型跑通只是第一步后面要让它在真实环境里稳定工作还需要处理三个问题阈值怎么做、模型体积怎么降、低端设备怎么保底。先说阈值。推理结果里的概率不是“模型有多确定”而是模型在各类别之间做的分布判断所以直接拿最大概率当结论很容易误判。我在这个资源包的基础上会加一层兜底逻辑低于阈值就输出unknown这也是很多实际产品里的做法const PROB_THRESHOLD 0.8; const top maxProbability(predictions); const label top.probability PROB_THRESHOLD ? top.className : unknown;阈值怎么取值我在自己项目里的习惯是用同一场景重复测 20 次记录最大和最小置信度的差值。如果抖动超过 10 个百分点说明模型对当前场景不够确定需要回到训练数据补样本或者调整阈值到 0.85 以上。模型量化是另一个立竿见影的优化手段。资源包里的权重是 32 位浮点实际部署时可以用模型转换工具把权重压成 16 位浮点体积直接减半加载速度也跟着变快。代价是精度轻微下降但图像分类这种任务通常感知不出来。我一般是在本地转换完再做一次首屏加载测试体积降到目标范围就上。最后是移动端降级。低端手机的 GPU 能力有限强制开 WebGL 可能比直接跑 CPU 还慢。合理的策略是按设备动态选择推理后端检测到低端机时优先 CPU画面分辨率也可以下调到 160×160。这也是资源包里loader.js留了setBackend接口的原因不要为了“统一体验”把后端写死。我自己的习惯是每次改完界面或模型都强制走一遍“本地静态服务器—摄像头授权—重复推理 20 次—看置信度抖动—再提交”的流程。就是这个流程让我少加了很多班。这套 teachable_machine 资源包把前面说的训练、导出、部署、调优链路都整理成了可以直接运行的代码按路径跑一遍再按第 5 章的清单排查基本能帮你跳过那些我当年踩过的坑。希望帮到你。本文还有配套的精品资源点击获取