人工智能通识:理解、使用与判断

全书初稿(供研讨与审校)

前言

人工智能进入学习和生活以后,最容易获得的是一个看起来像答案的结果,最难形成的却是判断:它在做什么,为什么会这样做,结果能否用于眼前的事情,出了错应由谁接管。

本书主要面向高职、中职和技工教育学生。全书不从产品菜单、提示词技巧或编程语法讲起,而是沿一条完整的人工智能技术主线安排十二章:先观察模型怎样从示例和表格中学习,再进入视觉、声音与时间序列;随后理解文字怎样被表示、检索和生成;最后学习生成式模型、推荐系统、强化学习,以及多种模型、工具和设备怎样组成系统。视觉、语言和声音是数据形态,分类、预测、检索、生成和行动是任务,监督学习、迁移学习、生成式学习和强化学习是不同的学习机制。本书有意把这些层次分开,再在综合项目中连接起来。

与技术主线并行的是一条家庭生活项目线。读者会逐步完成阳台植物园、家庭声音提醒器、空气与用能观察站、学习伙伴、记忆故事盒、说明书助手等项目,并把所学迁移到消费出行判断和自己的生活场景。同一项目可能跨章生长,让生活问题保持完整,也让每章只集中看清一个新机制。

每章配置一个生活或学习实践案例,但技术是骨架,案例只是观察机制的载体。读者先看到输入、输出和一次最小运行,再沿中间结果看懂数据、表示、模型和普通程序的分工,最后用正常、边界、陌生与故障样例检验它。第1章不要求Python、JSON或接口知识;随着章节推进,读者再在大语言模型帮助下阅读、运行和小步修改完整Notebook。

读者不需要预先掌握Python。大语言模型可以作为开发与学习伙伴,帮助追问需求、生成和解释程序、分析报错、补写测试;但目标、材料、权限、错误后果和采用结论不能交给模型。读者要亲自运行,观察中间结果,只改变一个关键变量,检查修改差异,并用固定样例回归。能根据证据决定采用、限制、继续改进、不用或转交,比一次成功演示更重要。

人工智能技术变化很快,纸书因此着重讲稳定的机制和方法,易变的模型、软件版本和操作步骤放入配套数字资源持续更新。书中涉及健康、隐私、家庭影像、麦克风和设备的项目均提供模拟、脱敏或回放路径,坚持低风险、可停止、可回退和人工确认。

希望读者完成本书后,不只是“用过”几种人工智能工具,而是获得一种可以迁移的能力:面对一个新问题,能够与人工智能共同实现,能够看懂关键技术链,也能够依据证据作出自己的判断。

编者

2026年8月

学习指南

一、本书学习的不是一组软件菜单

人工智能产品会变化,数据怎样进入模型、模型怎样产生输出、结果怎样验证,却有相对稳定的规律。本书沿十二项技术逐步学习:机器学习闭环、结构化数据、计算机视觉、音频、时间序列、自然语言处理、嵌入、大语言模型、生成式深度学习、推荐系统、强化学习,以及多模态与物理AI系统。

一个模型可以用下面的最小表达表示:

ŷ = fθ(x)

其中,x是输入,f是模型结构,θ是训练得到的参数,ŷ是输出。输入可能是表格中的一行、图像像素、声音波形、时间窗口或一段文字;输出可能是类别、数值、向量、下一词概率、排序分数或动作。训练依据样本调整参数,推理使用已经确定的参数处理新输入。

模型并不等于完整应用。普通程序负责文件、界面、计算和权限;规则负责明确约束;工具和设备连接外部环境;人决定任务、材料、错误后果和是否采用。学习时要始终问:眼前的结果来自模型、规则、普通程序,还是人工判断?

二、用四个坐标理解十二章

第一是数据形态,包括表格、图像、声音、时间序列、文字和多模态数据。第二是任务,包括分类、回归、预测、检索、生成、推荐和行动。第三是学习机制,包括监督学习、迁移学习、预训练与上下文学习、生成式学习和强化学习。第四是运行系统,包括云端、本地端、设备端、工具、硬件和人工接管。

这四个坐标能够避免常见混淆。例如,图像是一种数据形态,分类是一种任务,迁移学习是一种学习方法;三者可以共同出现在一个植物识别应用中。大语言模型能够处理多种任务,但不能因此替代全部小模型、规则和人工确认。

三、沿家庭项目线积累作品

全书四篇都采用双层篇名:生活目标放在主标题中,回答“这一篇准备改善什么”;技术主线放在副标题中,回答“为此要理解什么人工智能机制”。十二章仍按技术先修关系组织,原有家庭项目则在最适合的章节进入,不强求一个复杂项目在一章内一次做完。

篇章主标题 技术副标题 主要项目与学习位置
第一篇 让家感知环境 让模型从示例中学习 AI阳台植物园跨第2、3章推进,家庭声音提醒器在第4章完成,家庭空气与用能观察站从第5章开始;第1章还为手势互动和家庭物品整理训练第一个分类模型
第二篇 照顾自己与家人 让模型理解语言和语义 第6章把零散家庭事务变成可执行计划,并以模拟材料练习家庭健康资料与就医准备;第7章建立家庭维修与说明书助手;第8章设计真正帮助学习的AI伙伴
第三篇 创作有用又有趣的小作品 让模型生成和选择 第9章制作有来源、可撤回的家庭记忆故事盒;第10章学习推荐机制,并迁移到一次有依据的消费或出行选择
第四篇 改善生活中的选择与行动 让模型行动并形成系统 第11章在虚拟环境中继续家庭空气与用能项目;第12章把阳台植物园、声音或手势互动、物品整理与循环利用等模块连成系统,并用AI改善一个自己的生活场景

跨章项目要保存上一阶段的材料和判断。例如,阳台植物园先用表格数据认识特征与标签,再训练视觉模型,最后才把图像、传感器、语言说明和安全提醒连接成系统。家庭空气与用能观察站先研究连续记录和预测,到强化学习章节才在虚拟环境中比较不同动作策略。这样既能看到完整应用,又不会在尚未理解基本机制时被复杂工程淹没。

每章先完成共同的“技术实验”,再进入家庭项目的当前阶段或迁移任务。共同实验用于隔离本章的一个机制;家庭项目允许学生选择材料、形式和创意,但必须复用同一批固定测试并说明模型、规则、普通程序和人工决定各自负责什么。

四、分清两个AI角色

“应用中的AI”是作品实际调用或训练的模型。例如,图像模型输出植物状态概率,嵌入模型把资料片段变成向量,推荐模型为候选资源评分。

“开发与学习AI”帮助学生完成学习和开发。学生可以用自然语言请它解释概念、设计样本、生成完整Notebook、分析报错或提出测试,但不能把最后判断交给它。同一个大语言模型可以先后承担两种角色,学习记录中仍要标清角色。

一次有效协作至少说明:

我正在完成什么任务;
已有材料和运行环境是什么;
只允许改变哪个条件;
希望观察什么中间结果;
用哪些固定样例验收;
哪些结论和动作必须由人确认。

开发与学习AI给出的程序先作为候选。学生要运行、查看输入输出、核对修改范围并重新测试。“模型说代码正确”不能代替实际运行结果。

五、三种学习深度

每章可以按条件选择三种深度,但三种路径都要理解同一个技术本质。

Notebook中的代码用于暴露机制,不要求背诵。学生应能指出数据在哪里进入、模型在哪里建立或调用、训练和推理分别在哪一段、结果怎样评价。大段界面样式、账号配置和易变安装步骤放在数字资源中。

六、怎样做一次有意义的实验

先运行首版并保存结果,再只改变一个与原理有关的变量。例如改变训练图片的背景多样性、表格中的特征、声音阈值、时间窗口、检索数量、生成随机性或强化学习奖励。改变前先写预测,改变后用同一批样例比较,不同时修改模型、数据和评价方式。

每章至少考虑四类输入:正常样例代表常见情况;边界样例接近分界或信息不完整;陌生样例来自训练和示例之外;故障样例模拟文件、网络、传感器或模型不可用。样本少时直接写“错了几条、错在哪里”,不要只写一个看似精确的百分数。

判断时至少比较一个简单基线。基线可能是人工分类、固定规则、关键词匹配、上一时点预测或流行度排序。复杂模型如果没有在相同测试上带来有意义的改善,就不应因为名称先进而被采用。

七、建议的学习环境

核心环境由浏览器中的模型工具、能够查看文件的本地工作台、预配置的Jupyter与Python环境组成。具体产品、版本、账号和安装方法由数字资源按日期更新,纸书不绑定单一平台。

涉及硬件时提供三条等价路径:H0使用样例、回放数据或虚拟环境,能够完成全部核心学习;H1由小组共享摄像头、麦克风或低压传感器;H2把小模型部署到设备端,作为拓展。没有硬件或本地大模型不影响完成课程。

八、课程组织建议

32学时可按“每章2学时+两个综合实验4学时”组织;16学时可选择第1、2、3、6、7、8、11、12章,形成从学习闭环到语言、生成、行动和系统的主线;48学时可完成十二章Notebook并增加数据采集、独立测试和设备端拓展。

小组不按“会编程的人包办代码”分工。建议轮换任务定义、数据与授权、运行与记录、测试与判断四类责任。每名学生都应至少完成一次自然语言需求说明、一次真实运行、一次控制变量实验和一次证据解释。

九、安全与责任

课堂共享材料使用模拟、脱敏或经授权的数据。不要把真实身份信息、住址、健康记录、私人聊天、照片、声音或访问密钥直接上传公共模型。涉及文件、消息和设备的动作先预览再确认;强化学习只在仿真环境训练;多模态系统在输入缺失、模型冲突、网络中断或设备异常时保持安全状态并交给人接管。停止使用、不采用AI或转交专业人员,都是有效结论。

第一篇 让家感知环境

让模型从示例中学习

第1章 从示例到模型:完成第一次机器学习

本章技术主题: 监督学习的样本、标签、训练、推理和测试闭环。
本章技术实验: 在浏览器中训练水杯、收纳盒和其他家庭物品三分类模型。
家庭项目: “建立家庭物品整理与循环利用助手”的感知起步;“做一个会识别声音或手势的互动作品”的手势迁移。
跨章位置: 本章形成训练—推理—测试闭环,第3、4、12章再加入视觉细节、声音和系统反馈。
技术主线: 采集样本 → 添加标签 → 训练模型 → 输入新图片 → 记录错误 → 改进数据。

学习目标

完成本章学习后,你将能够:

  1. 用自己的话说明样本、标签、训练、推理、测试和泛化之间的关系。
  2. 在浏览器中采集水杯、收纳盒和其他家庭物品的图像,训练并运行一个三分类模型。
  3. 通过改变背景、角度和样本构成,发现模型可能学到的背景偏差。
  4. 根据测试记录判断模型在哪些条件下可用,以及何时应当拒绝判断或交给人确认。

项目导入

本章的技术主题是机器学习的基本闭环。实践任务是训练一个能够区分“水杯”“收纳盒”“其他家庭物品”的图像分类模型。选择图像只是因为它便于采集、结果直观,本章并不讲解图像模型内部的卷积结构。我们的重点是回答一个更基础的问题:机器怎样从示例中形成能力,这种能力为什么有时有效、有时又会突然失灵。

课堂使用教师准备的浏览器训练页面。学生依次采集样本、为样本选择标签、启动训练,再把新图像交给模型测试。页面已经封装好训练程序,第一次实验不要求安装软件,也不要求编写程序。准备充分时,学生通常可以在15分钟内看到第一个结果。

本章需要区分两种AI角色。“应用中的AI”是正在训练和测试的三分类模型,它接收一幅图像,输出三个类别的分数。“开发与学习AI”是陪伴学生研讨的语言模型,它可以协助设计采样清单、解释错误和提出对照实验,但不能替学生拍摄样本、删改原始记录,也不能代替学生作出模型是否可用的判断。

项目只使用普通、无隐私风险的家庭物品。不要拍摄人脸、证件、住址、聊天内容或能够识别个人身份的信息。最终成果不是一次“猜对”的演示,而是一组能够说明模型能力边界的测试证据。

本章成果

考点提示

请重点观察四个问题:标签由谁给出;训练图像与测试图像有什么区别;类别分数是否等于事实;模型换一个背景后为什么可能改变判断。能够回答这些问题,比记住页面上的按钮位置更重要。

第一节 用样本和标签训练第一个分类模型

一、先把任务说清楚

分类是把输入归入预先规定的类别。本项目规定三个类别:水杯、收纳盒和其他家庭物品。输入是一幅当前拍摄或导入的物品图像,输出是三个类别各自的分数以及其中最高的候选类别。模型不负责判断物品属于谁,也不负责评价物品好坏。

“其他家庭物品”不能理解成世界上除水杯和收纳盒以外的一切。一个类别如果范围无限宽,少量样本不可能代表它。本章把它限定为家中常见的书本、胶带、遥控器和杯垫。手机、食品、人物和室外景物都视为陌生输入,不要求模型识别。

在开始采集前,先写出验收样例:同组没有参加训练的一个水杯和一个收纳盒,在普通家庭桌面背景下各测试5次;候选类别应多数正确,出现遮挡、类别分数接近或陌生输入时应提示人工确认。这份验收说明使“模型很好用”变成可以检查的要求。

二、样本和标签是什么

一幅用于学习的图像称为一个样本。告诉模型该样本属于“水杯”的类别名称称为标签。一组带标签的样本构成训练数据。模型不会从类别名称中自动理解“水杯”的定义,它只能在给出的图像中寻找与标签经常一起出现的视觉模式。

每组先准备三类样本,每类不少于20幅。对同一件物品连续拍摄20张几乎相同的照片,看起来数量不少,实际变化很小。更合理的做法是至少使用两件不同外观的水杯、两件收纳盒和四种其他物品,并主动改变方向、距离和摆放位置。

类别 建议对象 需要主动变化的条件 不应出现的内容
水杯 不同颜色、形状的两个以上水杯 正面、侧面、远近、把手方向 人脸、姓名、固定类别标牌
收纳盒 不同颜色、材质和形状的收纳盒 开合状态、横放竖放、局部遮挡 始终只用同一背景
其他家庭物品 书本、胶带、遥控器、杯垫 大小、材质、位置 把陌生世界全部塞入本类

标签由学生依据项目规则给出,因此标签也可能出错。把保温杯误标为“其他”,模型不会发现教师心中的正确答案,只会把这个错误关系也当成学习材料。采集完成后,小组应交换检查标签:随机查看每类5幅图像,发现模糊、重复、错标或含隐私内容的样本就重新采集。

三、完成第一次训练

打开训练页面后,为三个类别命名并分别进入采集区。每次采集前确认当前标签,避免把图像放错类别。完成样本检查后启动训练。页面会多次读取训练样本并调整模型内部的大量数值,使正确类别的分数逐步提高。这个依据样本调整模型参数的过程称为训练。

训练结束后,不要立刻宣布成功。先拿开训练时使用的物品,放入同类别但未参加训练的新物品。页面此时进行的是推理:模型参数不再因为这幅图像而改变,只把新输入映射为类别分数。记录最高分的类别,也要记录另外两个分数。若“水杯”为0.52、“收纳盒”为0.45,两者非常接近,就不能只展示“水杯”两个字而隐藏不确定性。

开发与学习AI可以帮助学生检查采样计划。例如,学生可提供“不包含真实照片”的文字清单,请它指出哪些颜色、角度或背景只出现在一个类别中。学生需要核对它的建议是否符合课堂条件,再决定是否补拍。真实图片是否可上传、上传到哪里,应由教师根据隐私和平台规则决定;默认做法是在本地训练页面内处理。

第二节 看懂训练、推理与测试的完整闭环

一、模型不是答案表

机器学习模型可以写成一个简化关系:输入图像进入由结构和参数组成的模型,模型输出类别分数。结构规定信息怎样流动,参数是在训练中调整的数值。训练不是把每张图片原样存入答案表,而是让参数逐步形成一种从输入到输出的映射。

训练时,页面知道样本标签,可以比较模型当前输出和标签之间的差异,再调整参数。推理时,页面只看到新图像,不知道正确答案,也不会自动修改参数。测试则是人使用一批未参与训练的样本,对推理结果进行有计划的检查。

带标签样本 → 训练 → 得到参数已经确定的模型
                         ↓
未见过的新样本 → 推理 → 类别分数 → 与人工答案比较 → 测试记录

同一张图如果既参加训练又被拿来证明模型有效,结果通常会过于乐观。这类似于用练习册原题检验是否真正掌握知识。至少应把一部分物品和照片留到训练之后再使用,测试期间也不要偷偷把测试图像补回训练集。第2章会进一步介绍训练集、验证集和测试集的严格划分。

二、分数不是事实概率

分类页面通常会给出0到1之间的分数。三个分数反映模型在当前三个类别之间的相对选择,并不保证等于现实世界中的真实概率。模型只学过本项目的数据;即使对一只玩具给出0.91的“收纳盒”分数,也不能说明它有91%的可能真是收纳盒。

因此可以设置一条使用规则:最高分低于0.70,或者最高分与第二名相差不足0.15时,不自动接受类别,只显示“需要确认”。阈值不是越高越好。阈值提高会减少一些误认,也可能让更多正常物品无法通过。学生应使用固定测试样例比较不同阈值,再选择适合本任务的规则。

这里再次体现两种AI的不同责任。应用中的AI给出类别分数;普通页面规则比较分数和阈值;人查看原物并确认。开发与学习AI可以解释这三个环节,但如果它只看到了学生转述的数字,就不能代替现场观察,也不能证明某次标签一定正确。

三、建立可复查的测试记录

一次测试至少记录输入编号、人工确认类别、模型候选、最高分、第二名分数、环境条件和是否接受。不要只保存成功截图。失败样例更能帮助理解模型学到了什么。

编号 人工确认 模型候选 最高分 环境条件 结论
T01 水杯 水杯 0.88 普通桌面、正面 接受
T02 收纳盒 水杯 0.61 强背光、局部遮挡 拒绝并记录
T03 陌生输入 收纳盒 0.79 玩具汽车 不在任务范围

评价时先数清样本数量,再报告结果。例如“10个正常样例中有8个候选正确”,比只写“准确率80%”更诚实,因为样本很少,不能代表所有家庭环境和所有物品。还要分别记录误报和漏报:把其他物品误认为水杯,与把水杯误认为其他物品,给实际应用带来的后果可能不同。

第三节 改变数据并判断模型能否识别新样本

一、做一次背景偏差实验

模型可能没有学习物品本身,而是利用了更容易出现的背景线索。要让这种偏差显现出来,可以故意训练一个“有问题的模型”:所有水杯都放在白纸上,所有收纳盒都放在木纹桌面上,其他物品都放在深色垫子上。训练完成后,先在原背景测试,再交换背景测试。

如果水杯换到木纹桌面后被判断为收纳盒,说明背景与标签在训练数据中形成了虚假联系。模型没有语言常识去提醒自己“桌面不是物品类别”,它只会利用能够降低训练错误的模式。背景偏差也可能来自拍摄人的手、类别标牌、固定角度、光线和相机位置。

修正时每次只改变一个变量。第一轮只补充交换背景后的样本,其他设置保持不变;重新训练并用同一组测试图像检查。若结果改善,可以把改善与数据变化联系起来。若同时更换物品、角度、背景和阈值,即使结果变化,也难以说明原因。

二、认识泛化和陌生输入

模型对没有参加训练但与训练任务相关的新样本仍能给出合适结果,这种能力称为泛化。泛化不等于“什么都能认”。训练数据只覆盖本项目中的家庭桌面条件,模型不能因此获得识别商场货架、垃圾分类或危险物品的通用能力。

全书生活项目“做一个会识别声音或手势的互动作品”也可以复用同一学习闭环。本章先迁移到低风险的手势支线:把“张开手掌”“握拳”“其他手势”定义为类别,采集经过授权且不含人脸的手部图像,再执行同样的训练、独立测试和陌生输入检查。模型只输出候选手势和分数,普通程序可在人工确认后改变屏幕动画、虚拟指示灯或播放提示音;不得把课堂模型直接接入门锁、电源等设备。迁移的是“样本—标签—训练—推理—测试”机制,而不是复制本章的数据。

本章应完成四类测试:

  1. 正常样例:未参加训练的新水杯和新收纳盒。
  2. 边界样例:严重遮挡、只露出一角、强背光或两个物品同时出现。
  3. 陌生样例:玩具、食品、人物照片等任务范围外输入。
  4. 故障样例:摄像头权限被拒绝、画面全黑或训练页面没有加载完成。

故障样例不是分类错误。模型没有获得可用输入时,页面应明确提示检查权限或更换输入,而不是继续显示上一次结果。边界和陌生样例即使得到很高分,也应由人确认,因为三分类模型被迫在已有类别中分配分数,并没有自动拥有“我不知道”这一类。

三、形成模型判断卡

测试结束后,小组不能只用“能用”或“不能用”收尾,而应填写判断卡:模型在哪些物品、背景和光照条件下测试过;最常见的错误是什么;哪些输入必须拒绝;由谁确认;是否值得继续补充数据。

一个合理结论可以是:“本模型可用于普通家庭桌面上的三类物品演示。对未参加训练的新水杯和收纳盒多数判断正确,但强背光和深色背景下错误增加。其他类别范围有限,遇到陌生输入或两类分数接近时必须人工确认,因此不用于清点重要物品。”这类结论同时说明证据、限制和用途,比展示一次高分更专业。

最后把测试记录交给开发与学习AI,请它只根据记录归纳错误模式,并要求它逐条引用样例编号。学生检查它有没有忽略失败记录、夸大样本范围或把类别分数当成事实概率。AI可以帮助整理证据,采用、限制、继续改进或停止使用的决定仍由学生作出。

本章小结

本章完成了第一次机器学习实践。样本是模型学习和测试时使用的具体输入,标签是人依据任务规则给出的类别。训练依据带标签样本调整模型参数;推理使用已经确定的参数处理新输入;测试用未参加训练的样本检查结果。模型可能借助背景、角度等偶然线索降低训练错误,因此训练表现好不代表能够泛化。可靠的实践需要保留失败记录、控制变量、测试陌生输入,并为不确定结果设置人工确认。

关键术语

目标测试

  1. 在本章项目中,下列哪一项属于“标签”? A. 一幅水杯照片 B. “水杯”这个类别名称 C. 摄像头 D. 测试记录表
  2. 关于训练与推理,下列说法正确的是哪一项? A. 推理会自动修正所有错标 B. 训练只是在保存原图 C. 训练调整参数,推理用已有参数处理新输入 D. 二者没有区别
  3. 为什么不应使用参加训练的同一批图片证明模型有效? A. 图片文件太小 B. 结果可能过于乐观,不能说明对新样本的能力 C. 标签会消失 D. 浏览器不能再次打开图片
  4. 哪一种现象最能说明模型可能学到了背景偏差? A. 换一只同类水杯仍判断正确 B. 训练速度变慢 C. 水杯换到收纳盒常用背景后被判断为收纳盒 D. 测试表增加了一行
  5. 判断并改错:“模型给水杯0.90分,就表示现实中它有90%的可能是水杯。”
  6. 判断并改错:“把同一件物品连续拍摄很多张几乎相同的照片,就一定能获得很好的泛化能力。”
  7. 用四句话分别说明样本、标签、训练和推理在本章项目中的含义。
  8. 设计一个只改变背景的对照实验,写出训练条件、测试条件和需要记录的结果。
  9. 如果模型把一个陌生玩具以0.92分判断为收纳盒,你会怎样处理?说明理由。
  10. 阅读某组测试记录:普通背景10次中正确8次,强背光5次中正确2次,陌生输入3次都给出高分。请写一条包含适用条件、限制和人工确认规则的判断结论。

第2章 结构化数据学习:从表格中发现规律

本章技术主题: 结构化数据的分类、回归与泛化评价。
本章技术实验: 用模拟环境表格完成一次分类、一次回归和基线比较。
家庭项目: “打造一个AI阳台植物园”第一阶段——表格数据学习。
跨章位置: 保存字段、基线和独立测试,第3章加入视觉,第12章完成多模态集成。
技术主线: 表格记录 → 特征与标签 → 基线 → 模型训练 → 独立测试 → 错误代价判断。

学习目标

完成本章学习后,你将能够:

  1. 从生活记录中区分样本、特征和标签,并发现缺失值、单位不一致和信息泄漏问题。
  2. 说明分类与回归的差别,分别为两类任务建立简单基线。
  3. 运行一段完整的Keras分类代码,读懂数据划分、归一化、训练、损失和测试结果。
  4. 根据训练集、验证集和测试集的表现识别过拟合,并结合错误代价形成使用判断。

项目导入

本章以“打造一个AI阳台植物园”为连续项目,进入第一阶段:表格数据学习。实践任务是整理一份虚构的阳台环境记录,让模型完成两个相互关联但性质不同的任务:根据当天记录判断植物是否“需要关注”,以及预测第二天早晨的土壤湿度。前者输出类别,属于分类;后者输出连续数值,属于回归。

后续章节还会沿用阳台植物场景研究视觉、时间序列、生成和多模态任务;本章只把每条环境记录作为结构化样本,重点学习特征、标签、分类与回归。同一生活场景可以形成不同AI任务,不能把一章的输入、输出和评价直接套到另一章。

结构化数据通常以表格呈现。每一行代表一次观察,每一列有固定含义,例如温度、光照、空气湿度和土壤湿度。表格看起来比图像简单,但模型不会自动知道哪一列是原因、哪一列是结果,也不会自动发现记录中的单位错误和未来信息。把生活记录变成可靠学习数据,是本章真正的实践重点。

本章的“应用中的AI”是依据表格特征输出状态类别或预测数值的模型。“开发与学习AI”帮助学生讨论字段、生成和解释完整程序、分析训练日志以及提出单变量修改建议。学生必须亲自确认标签规则、数据划分和错误代价,不能让语言模型看到表头后自行虚构数据,也不能只根据它对结果的文字评价决定采用模型。

本项目只使用教师提供的虚构记录,不代表任何真实植物的养护标准。模型输出用于学习数据规律,不替代植物品种说明、专业种植知识或现场观察。

本章成果

考点提示

请重点思考:特征和标签由任务而不是列的位置决定;分类输出类别,回归输出连续数值;损失是训练时衡量差异的量;验证集帮助选择方案,测试集用于最后检查;模型必须先超过合理基线,才有继续使用的价值。

第一节 把生活记录整理成特征和标签

一、把观察变成一行数据

假设每天傍晚记录阳台环境,并由人工观察植物是否需要进一步查看。每一行是一日样本,输入列包括平均温度、累计光照、平均空气湿度和傍晚土壤湿度。状态标签由人工按照课堂项目规则填写为“正常”或“需要关注”。为了练习回归,还记录第二天早晨土壤湿度作为另一个标签。

日期编号 平均温度/℃ 累计光照/lx·h 空气湿度/% 傍晚土壤湿度/% 状态标签 次晨土壤湿度/%
D01 22 12000 55 42 正常 39
D02 24 15000 52 38 正常 34
D03 31 26000 35 18 需要关注 13
D04 20 9000 60 45 正常 43
D05 33 28000 32 15 需要关注 9
D06 26 18000 48 35 正常 31
D07 30 24000 38 20 需要关注 15
D08 23 14000 58 40 正常 37

这里的“状态标签”和“次晨土壤湿度”都可以成为模型要学习的目标,但一次建模只选择其中一个。预测状态时,温度、光照、空气湿度和傍晚土壤湿度是特征,状态是标签;预测次晨湿度时,前四列仍是特征,次晨湿度变成标签。日期编号只是识别记录的编号,通常不作为特征。

不能把“第二天是否需要关注”提前放进当天输入,也不能加入根据状态标签直接填写的“建议浇水”列。这些列在真正预测时尚不存在,或已经包含答案。训练时使用这种未来信息或答案线索称为数据泄漏,它会让测试数字看起来很好,实际应用却无法复现。

二、先建立字段说明

在交给模型之前,为每一列写清名称、含义、单位、允许范围和缺失处理方法。例如温度统一使用摄氏度,湿度统一为0到100之间的百分数,光照使用同一种统计口径。若一行把华氏温度当成摄氏温度,数值仍能进入程序,却会破坏规律。

缺失值不能随意填成0。土壤湿度为0可能表示极干,也可能只是没有记录,两者含义完全不同。入门项目可以先把关键特征缺失的行标记为“不可用于训练”,保留在质量记录中;数据较多时再研究合理的缺失值填补方法。

标签同样需要规则。课堂中的“需要关注”只表示建议再次观察,不等于“必须浇水”,更不等于植物患病。两名学生若对同一行给出不同标签,应先查看书面规则并讨论原因,而不是让模型替人决定正确标签。

三、先看图,再训练模型

表格适合查看精确数值,图形更容易显露关系。可以把傍晚土壤湿度画在横轴、平均温度画在纵轴,用两种颜色表示状态标签。若“需要关注”的点大多出现在高温、低土壤湿度区域,说明这两个特征与标签存在可观察关系。若两类点完全混在一起,则要检查特征是否不足、标签是否不一致,或者任务本身是否无法由这些数据完成。

图上的关系不等于因果关系。即使高温记录更常被标为需要关注,也不能仅凭这张小表断言高温造成了某种植物状态。模型学习的是数据中可用于预测的统计关系,不会自动完成科学因果研究。

开发与学习AI可以根据“字段说明和几行虚构样例”检查单位、范围和可能泄漏,但应要求它逐列说明理由。学生随后回到原表核对,不能让它用想象补齐缺失记录。规划中的完整Notebook应保留原始表、清洗后的表和散点图,使每一步变化都可追溯。

第二节 用分类、回归与基线比较学习结果

一、先问一个最简单的方法能做到什么

在训练神经网络前,应建立基线。基线是一个简单、可重复的参考方法。分类任务可以始终猜训练集中数量最多的类别;如果训练数据中60%是“正常”,这个基线的正确率就是60%。回归任务可以始终预测训练标签的平均值,再计算它与真实值的平均绝对误差。

如果复杂模型没有稳定超过基线,就没有证据说明复杂性带来了价值。基线也能发现评价方式的问题:当100条记录中只有5条需要关注时,始终猜“正常”也能达到95%正确率,却漏掉全部需要关注样本。因此还要分别查看两类错误。

分类模型输出类别或类别分数,常用分类损失衡量预测分布与正确标签的差异。回归模型输出连续数值,可以使用平均绝对误差或均方误差。损失越小,表示模型输出与训练目标在该度量下越接近;它不是对模型“聪明程度”的总评分。

二、运行完整的最小分类程序

下面的程序使用24条虚构数据训练二分类模型。四个特征依次为温度、光照、空气湿度和土壤湿度,标签0表示正常,1表示需要关注。程序固定随机顺序,将60%数据用于训练、20%用于验证、20%用于最终测试,并把模型与多数类基线比较。

import numpy as np
import tensorflow as tf
from tensorflow import keras

keras.utils.set_random_seed(7)

x = np.array([
    [22, 12000, 55, 42], [24, 15000, 52, 38],
    [20, 9000, 60, 45], [26, 18000, 48, 35],
    [23, 14000, 58, 40], [21, 11000, 62, 47],
    [25, 16000, 50, 36], [19, 8000, 65, 50],
    [27, 17000, 47, 34], [22, 13000, 57, 43],
    [24, 10000, 59, 41], [25, 19000, 46, 33],
    [31, 26000, 35, 18], [33, 28000, 32, 15],
    [30, 24000, 38, 20], [29, 22000, 40, 22],
    [32, 30000, 30, 12], [28, 25000, 42, 19],
    [34, 31000, 28, 10], [29, 27000, 36, 17],
    [31, 23000, 34, 21], [30, 29000, 33, 16],
    [28, 20000, 41, 23], [32, 25000, 37, 14],
], dtype="float32")
y = np.array([0] * 12 + [1] * 12, dtype="float32")

rng = np.random.default_rng(7)
order = rng.permutation(len(x))
x, y = x[order], y[order]

train_end = int(len(x) * 0.60)
val_end = int(len(x) * 0.80)
x_train, y_train = x[:train_end], y[:train_end]
x_val, y_val = x[train_end:val_end], y[train_end:val_end]
x_test, y_test = x[val_end:], y[val_end:]

normalizer = keras.layers.Normalization()
normalizer.adapt(x_train)

model = keras.Sequential([
    keras.Input(shape=(4,)),
    normalizer,
    keras.layers.Dense(8, activation="relu"),
    keras.layers.Dense(1, activation="sigmoid"),
])
model.compile(
    optimizer="adam",
    loss="binary_crossentropy",
    metrics=["accuracy"],
)

stop = keras.callbacks.EarlyStopping(
    monitor="val_loss", patience=20, restore_best_weights=True
)
model.fit(
    x_train,
    y_train,
    validation_data=(x_val, y_val),
    epochs=200,
    verbose=0,
    callbacks=[stop],
)

test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
majority_label = int(np.mean(y_train) >= 0.5)
baseline_accuracy = float(np.mean(y_test == majority_label))
probabilities = model.predict(x_test, verbose=0).reshape(-1)
predictions = (probabilities >= 0.5).astype(int)

print("测试损失:", round(float(test_loss), 4))
print("模型测试正确率:", round(float(test_accuracy), 4))
print("多数类基线正确率:", round(baseline_accuracy, 4))
for truth, probability, prediction in zip(y_test, probabilities, predictions):
    print("真实标签:", int(truth), "关注分数:", round(float(probability), 3),
          "预测标签:", int(prediction))

归一化层根据训练集计算各特征的尺度,使数值很大的光照不至于仅因单位较大而压过其他特征。它只在训练集上学习统计量,验证和测试数据不能参与这个过程。隐藏层学习特征组合,最后的S形函数输出0到1之间的关注分数。

这段代码是可运行的最小实现,不要求学生默写。学生应能指出输入数组、标签数组、三份数据、归一化层、模型输出和基线计算分别在哪里。规划中的完整Notebook应把同一程序拆成数据查看、可视化、建模、训练曲线和测试五段,并保留每段运行结果。

三、把同一任务改成回归

若标签改为次晨土壤湿度,任务就从分类变为回归。输入特征可以保持不变,模型最后一层改为一个不带分类激活函数的数值输出,训练损失改为平均绝对误差或均方误差。基线则始终输出训练集次晨湿度的平均值。

比较时不能用“分类正确率”评价回归,而要报告预测值平均偏离真实值多少个百分点。一个平均误差为3个百分点的模型是否可用,还取决于记录误差、植物差异和使用后果。本章不把回归代码叠加到正文程序中;下一阶段的配套Notebook应提供与分类并列的完整回归版本,供学生逐项比较输出层、损失和评价指标。

第三节 检查数据划分、过拟合与错误代价

一、三份数据承担不同责任

训练集用于调整参数,验证集用于比较模型结构、训练轮数和阈值,测试集只在方案确定后作最终检查。如果反复查看测试结果并据此修改模型,测试集也会逐渐变成训练过程的一部分,最终数字仍会过于乐观。

阳台记录还要注意时间关系。随机打乱适合本章理解基本划分,但若目标是预测未来,正式项目应按时间划分:较早记录用于训练,中间记录用于验证,最近记录用于测试。这样更接近“用过去预测未来”,也更容易发现季节变化造成的数据漂移。

相邻多日来自同一段稳定环境,内容可能高度相似。若把几乎重复的记录分散到训练和测试两边,模型实际上看过非常接近的情况。划分前应先判断哪些记录来自同一连续过程,必要时按时间段或植物个体成组划分。

二、从训练曲线发现过拟合

模型在训练集上的损失持续下降,而验证集损失先下降后上升,通常说明模型越来越适合训练细节,却没有提升对未见数据的能力,这称为过拟合。小数据配复杂模型、训练轮数过多、标签含噪声,都可能加重过拟合。

正文程序使用提前停止:验证损失长期没有改善就停止训练,并恢复验证表现最好的参数。提前停止能够限制问题,但不能弥补错误标签、数据泄漏和代表性不足。更根本的改进包括增加有意义的不同情形、简化模型、重新检查字段和改进划分。

开发与学习AI可以读取学生抄录的训练与验证损失,提出“减少网络规模”“检查泄漏”“增加边界样本”等假设。学生每次只选择一个假设实施,用同一测试集外的验证证据比较。不能把一条训练曲线直接交给AI后接受一句“模型已经很好”。

三、正确率之外还有错误代价

将“需要关注”错判为“正常”称为漏报,将“正常”错判为“需要关注”称为误报。漏报可能使需要复查的情况被忽略;误报会增加人工检查。两种错误的代价不同,阈值选择也会因此不同。

本项目不允许模型自动执行养护动作。它只能提出候选状态,由人结合植物种类、现场观察和记录质量确认。出现特征缺失、数值超出训练范围、分类分数接近或模型服务不可用时,程序应保留原始记录并转为人工检查。

最终判断至少比较四项证据:模型是否超过基线;验证和测试表现是否接近;关键类别的漏报是否可接受;输入条件是否与训练数据一致。若只有24条虚构数据,合理结论应是“完成了机制验证”,而不是宣称获得了普遍适用的植物养护模型。

本章小结

结构化数据学习从定义一行记录开始。特征是模型可获得的输入,标签是希望模型学习的目标。分类预测类别,回归预测连续数值,二者需要不同的输出、损失和评价指标。训练集调整参数,验证集选择方案,测试集进行最后检查。复杂模型只有稳定超过基线才显示出价值;训练表现明显好于验证表现可能意味着过拟合。最终是否使用模型,还要结合数据泄漏、错误代价、输入范围和人工责任判断。

关键术语

目标测试

  1. 在预测“是否需要关注”的任务中,哪一项最适合作为标签? A. 平均温度 B. 状态类别 C. 日期编号 D. 光照单位
  2. 下列哪一项属于数据泄漏? A. 使用当天温度预测次日状态 B. 统一湿度单位 C. 把次日人工结论提前作为当天输入 D. 删除无法解释的重复记录
  3. 分类任务的简单基线可以是什么? A. 始终猜训练集中最多的类别 B. 增加隐藏层 C. 延长训练时间 D. 删除测试集
  4. 哪一种现象最符合过拟合? A. 训练和验证损失都下降 B. 训练损失下降而验证损失持续上升 C. 模型与基线相同 D. 所有特征单位一致
  5. 判断并改错:“验证集用于调整参数,测试集可以每天查看并反复选择模型。”
  6. 判断并改错:“回归输出连续数值,因此可以直接用分类正确率评价。”
  7. 从本章阳台数据中各举一个特征、分类标签和回归标签,并说明三者作用。
  8. 为什么要在训练神经网络前建立基线?请分别给分类和回归写出一种基线。
  9. 某模型训练正确率为98%,验证正确率为70%,测试正确率为68%。你认为可能出现了什么问题?写出两项检查或改进措施。
  10. 在“需要关注”识别中,漏报比误报代价更高。请说明这一判断会怎样影响评价指标、阈值和人工确认规则。

第3章 计算机视觉:让模型理解图像

本章技术主题: 图像分类、卷积特征与迁移学习。
本章技术实验: 训练植物外观分类器,并比较分类、检测和分割的不同输出。
家庭项目: “打造一个AI阳台植物园”第二阶段——视觉学习;“建立家庭物品整理与循环利用助手”作为可见属性迁移。
跨章位置: 接收第2章的植物记录,第12章再把视觉候选与传感器、规则和语言说明连接起来。
技术主线: 图像像素 → 卷积特征 → 预训练表示 → 分类概率 → 泛化测试 → 人工判断。

学习目标

完成本章学习后,你将能够:

  1. 把植物状态识别任务转化为图像分类问题,并建立相互独立的训练、验证和测试数据。
  2. 用直观语言说明像素、卷积、特征图和迁移学习在图像分类中的作用。
  3. 运行一个完整的Keras迁移学习程序,读懂图像进入预训练网络再到分类输出的主要路径。
  4. 根据任务输出比较图像分类、目标检测、图像分割和多模态视觉,并通过陌生条件测试判断模型的泛化边界。

项目导入

本章继续“打造一个AI阳台植物园”这一连续项目,进入第二阶段:视觉学习。实践任务是制作植物视觉识别原型:输入一幅叶片或植株照片,模型在“外观正常”“叶片发黄”“叶片有斑点”三个课堂标签中给出候选类别和分数,再由学生查看原图并确认。

本章沿用阳台植物场景,但输入是图像,输出是外观类别;第2章处理独立的表格样本,第5章保留连续记录的时间顺序,第12章再连接多源模型。学生要比较的是模型任务,不是反复制作同一个应用界面。

这些标签只描述照片中可见的外观,不诊断病虫害,也不直接给出浇水、施肥或用药建议。相似外观可能由多种原因造成,一张图像也可能缺少根系、土壤和生长过程信息。模型的用途是帮助整理和筛选观察记录,而不是替代种植知识和现场判断。

本章的“应用中的AI”是植物图像分类模型;它接收像素组成的图像并输出三类分数。“开发与学习AI”协助学生制定采样方案、生成和解释完整训练代码、阅读训练日志以及归纳失败模式。图片授权、标签规则、测试样本隔离和采用结论仍由学生负责。若开发与学习AI声称仅凭一张叶片照片就能确定病因,学生应指出任务边界并拒绝这种扩大解释。

本章使用公开授权或教师提供的脱敏植物图片。不要未经同意拍摄他人家庭环境,也不要把网络搜索结果直接当成可自由训练的数据。图片来源和授权说明应与数据一同保存。

本章成果

考点提示

请重点理解:图像进入模型后首先是像素数值;卷积通过局部计算形成特征图;迁移学习复用预训练模型学到的通用视觉特征;分类、检测和分割的输出不同;测试时应隔离植物个体,而不只是随机拆分几乎相同的照片。

第一节 用图像分类识别植物状态

一、先确定模型到底输出什么

图像分类回答“整幅图最接近哪个类别”。本项目的三个标签描述可见外观:外观正常、叶片发黄、叶片有斑点。类别定义必须能够被人执行。例如“叶片发黄”可以规定为主要叶片区域存在明显黄色变化;“叶片有斑点”表示叶面出现可见的点状或片状色差;无法看清叶片、同时符合多类或不属于这三类的图片标记为“暂不进入训练”。

不要用“健康”“患病”作为未经验证的事实标签。外观正常的照片不能证明植物在生物学上完全健康,有斑点也不能确定病因。标签越符合图像中真正可见的信息,模型任务越清楚,后续判断越诚实。

输入是一幅主要包含一株植物或一片叶的照片,输出是三个类别分数。模型不知道植物品种、拍摄日期和此前变化,除非这些信息作为另一个经过设计的输入交给系统。第一版只做单幅图像分类,不加入环境记录,也不连接传感器或控制设备。

二、按照植物个体划分数据

每类可先准备60至100幅授权图片。数量并非越多越好,关键是图片是否覆盖不同植株、角度、距离、背景和光照。对同一片叶连拍的十张照片高度相似,不能等同于十个独立植物个体。

数据目录按训练、验证和测试划分,每份下面再按类别建文件夹:

plant_images/
├─ train/
│  ├─ normal/
│  ├─ yellow/
│  └─ spotted/
├─ validation/
│  ├─ normal/
│  ├─ yellow/
│  └─ spotted/
├─ test/
│  ├─ normal/
│  ├─ yellow/
│  └─ spotted/
└─ demo/
   └─ test_leaf.jpg

同一株植物的照片必须全部进入同一份数据。如果同一片叶的正面照片在训练集、侧面照片在测试集,模型可能靠叶片纹理和背景记忆取得高分,测试便不能说明它认识了新植株。先按植物个体分组,再把不同个体分配到训练、验证和测试,是视觉项目中非常重要的防泄漏措施。

训练集用于学习参数,验证集用于选择训练轮数和方案,测试集留到方案确定后使用。建议按约70%、15%、15%划分植物个体,同时检查每一类在三份数据中都有足够样本。若有一类图片极少,整体正确率会掩盖该类的失败,应优先补充该类数据或缩小任务。

三、先观察图片,再观察分数

在训练前随机展示每类若干图片,让小组检查标签、清晰度和背景。若“发黄”照片全在室外,“正常”照片全在白墙前,模型可能学习场景而不是叶片外观。若斑点只出现在某一种植物上,模型可能学习品种差异。发现这类线索时应先改善数据,而不是期待模型自动忽略。

训练完成后的第一次测试使用未进入训练的新植物照片。除最高分类别外,还要记录三个分数、真实标签、植物个体、光照和背景。分数接近说明当前类别之间难以区分;即使最高分很高,陌生植物或严重模糊图像也不应自动接受。

应用中的AI负责输出候选类别。普通程序可以根据阈值把低分或分数接近的样本转为“需要人工确认”。学生查看原图和数据来源后作最终标记。这个分工避免把模型的强制选择误写成植物事实。

第二节 从像素、卷积到迁移学习

一、模型首先看到的是像素数值

彩色数字图像可以理解为一个有高度、宽度和颜色通道的数值数组。常见图像每个位置有红、绿、蓝三个通道,数值表示该位置的颜色强度。把照片缩放到160×160后,模型接收到的是160×160×3个数值,而不是“叶片”“斑点”这样的词语。

缩放会统一输入形状,也可能损失细小信息。若斑点在原图中只有几个像素,过度缩小后可能消失。反过来,盲目使用超大图像会增加计算量,并不保证小数据项目更可靠。选择尺寸应结合目标细节和可用资源,通过测试决定。

图像增强在训练时对图片做适度翻转、旋转或缩放,帮助模型看到更多合理变化。增强不是创造新的植物事实,变化不能破坏标签。例如把图片旋转少量角度通常不会改变外观类别,而改变颜色可能直接破坏“发黄”这一标签,因此不能随意使用强颜色变换。

二、卷积怎样形成特征图

卷积层使用一组可学习的小窗口在图像上滑动,对每个局部区域进行相同计算。浅层卷积可能对边缘、颜色变化和简单纹理产生响应;更深层把这些局部模式组合成较复杂的叶脉、斑点边界或形状线索。每个卷积核在整幅图上形成一张响应结果,这类中间结果称为特征图。

卷积的关键不是“模型像人一样看叶子”,而是共享局部计算参数并逐层组合空间模式。特征图也不是人预先写好的规则,它由训练数据和损失共同塑造。可视化某张特征图可以看到哪些区域产生较强响应,但不能仅凭亮区就断言模型理解了植物病理。

从零开始训练卷积网络通常需要较多图像。迁移学习先使用一个在大规模通用图像上预训练的视觉网络作为特征提取器,再为本项目增加新的分类层。预训练网络已经形成边缘、纹理和形状等通用表示,学生的数据主要用于学习这些表示怎样组合成三个新类别。

三、运行迁移学习的完整核心程序

下面程序读取前述目录,使用MobileNetV2作为预训练特征提取器,冻结其参数,只训练新的三分类层。首次运行需要取得预训练权重;离线课程资源应提前提供经过核验的权重文件和来源说明。

from pathlib import Path

import numpy as np
import tensorflow as tf
from tensorflow import keras

keras.utils.set_random_seed(7)

data_root = Path("plant_images")
image_size = (160, 160)
batch_size = 16

train_ds = keras.utils.image_dataset_from_directory(
    data_root / "train",
    image_size=image_size,
    batch_size=batch_size,
    label_mode="int",
    shuffle=True,
    seed=7,
)
validation_ds = keras.utils.image_dataset_from_directory(
    data_root / "validation",
    image_size=image_size,
    batch_size=batch_size,
    label_mode="int",
    shuffle=False,
)
test_ds = keras.utils.image_dataset_from_directory(
    data_root / "test",
    image_size=image_size,
    batch_size=batch_size,
    label_mode="int",
    shuffle=False,
)

class_names = train_ds.class_names
prefetch = tf.data.AUTOTUNE
train_ds = train_ds.prefetch(prefetch)
validation_ds = validation_ds.prefetch(prefetch)
test_ds = test_ds.prefetch(prefetch)

augmentation = keras.Sequential([
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomRotation(0.05),
    keras.layers.RandomZoom(0.10),
], name="augmentation")

base_model = keras.applications.MobileNetV2(
    input_shape=(160, 160, 3),
    include_top=False,
    weights="imagenet",
)
base_model.trainable = False

inputs = keras.Input(shape=(160, 160, 3))
x = augmentation(inputs)
x = keras.applications.mobilenet_v2.preprocess_input(x)
x = base_model(x, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dropout(0.20)(x)
outputs = keras.layers.Dense(len(class_names), activation="softmax")(x)
model = keras.Model(inputs, outputs)

model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.001),
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"],
)
stop = keras.callbacks.EarlyStopping(
    monitor="val_loss", patience=5, restore_best_weights=True
)
model.fit(
    train_ds,
    validation_data=validation_ds,
    epochs=30,
    callbacks=[stop],
)

test_loss, test_accuracy = model.evaluate(test_ds, verbose=0)
print("类别顺序:", class_names)
print("测试损失:", round(float(test_loss), 4))
print("测试正确率:", round(float(test_accuracy), 4))

demo_path = data_root / "demo" / "test_leaf.jpg"
demo_image = keras.utils.load_img(demo_path, target_size=image_size)
demo_array = keras.utils.img_to_array(demo_image)
demo_array = np.expand_dims(demo_array, axis=0)
scores = model.predict(demo_array, verbose=0)[0]
best_index = int(np.argmax(scores))
print("候选类别:", class_names[best_index])
print("类别分数:", {name: round(float(score), 3)
                  for name, score in zip(class_names, scores)})

这段程序的关键路径是:目录中的图片被调整为统一大小;训练图片经过合理增强;预处理函数把像素转换为预训练网络所需的数值范围;冻结的基础网络输出视觉特征;全局平均池化汇总特征;新分类层输出三个分数。

冻结基础网络意味着第一阶段不改动预训练参数,能减少小数据下过拟合和训练负担。若数据较多且第一阶段稳定,可在教师指导下解冻少量高层参数,用很小的学习率微调;这属于提高任务,必须重新用验证集和测试集检查,不能因为训练正确率提高就认定更好。

完整Notebook应在代码前展示数据样例和类别数量,在训练后绘制训练与验证曲线、混淆矩阵,并逐类报告测试结果。学生不以默写程序为目标,但要能够沿变量追踪一张图片怎样变成类别分数,并让开发与学习AI只解释指定的一段,例如询问“基础网络冻结后哪些参数仍在训练”。

第三节 比较分类、检测、分割并测试视觉泛化

一、先根据需要选择视觉任务

同一张植物照片可以对应不同任务,关键差别在输出。

任务 主要问题 输出 适合本项目的用途
图像分类 整幅图最接近哪一类 类别和分数 一张图主要包含一株植物或一片叶
目标检测 图中有哪些对象,它们在哪里 类别、分数和矩形框 一张图有多片叶或多个目标
图像分割 每个像素属于哪个区域 与图像对应的像素掩膜 估计斑点或发黄区域所占比例
多模态视觉 图像与文字问题共同表达什么 文字或结构化回答 结合图片回答“哪些可见现象支持该描述”

分类成本较低,但无法指出斑点位置;检测给出框,却不能精确描出不规则区域;分割最细致,通常需要逐像素标注,数据制作成本更高。多模态视觉模型可以把图像与自然语言问题一起处理,适合描述和比较,但输出文字可能加入图中没有的内容。技术选择应由任务输出和错误后果决定,而不是名称越复杂越好。

生活项目“建立家庭物品整理与循环利用助手”也可以迁移这套视觉方法:模型只根据照片中的颜色、形状、材质外观和可见破损,输出候选类别或候选标记,供人整理记录。它看不到物品内部、残留物和当地回收政策,不能仅凭外观决定物品是否可回收或最终去向。查询地区规则、核对版本并引用来源属于后续的语义检索与系统整合,本章只学习从像素到可见属性判断的视觉机制。

文字识别只是视觉任务中的一种。当目标是准确转录固定版式文字时,专用文字识别方法便于逐字符检查和稳定批处理;视觉语言模型可以直接结合版面与图像语义回答问题,却可能改写、遗漏或补充文字。本书不把文字识别单列成章,在需要处理图片文字时,把它作为视觉或多模态系统的一项可选能力,并根据准确性、成本和核验方式选择。

二、设计真正能暴露问题的测试

测试集应覆盖训练条件之外但仍属于任务范围的变化:更换桌面和墙面背景;在自然光、室内光和轻微背光下拍摄;改变远近、角度和遮挡;使用训练中没有出现过的同类植物个体。每次测试尽量只改变一个因素,才能判断错误可能来自哪里。

还要加入陌生输入:塑料仿真叶、印刷图片、不是植物的绿色物体、同时包含多株植物的远景,以及三个类别都不适合的叶片状态。分类模型仍会在已有类别中选一个最高分,甚至可能很高。因此“最高分大于阈值”只能作为接受条件之一,不能替代任务范围检查。

按类别查看结果比整体正确率更重要。混淆矩阵可以统计真实类别与预测类别的组合,帮助发现“发黄经常被判为正常”还是“正常经常被判为发黄”。若漏掉斑点类的后果更受关注,就应单独统计该类召回情况,并通过补充多样样本、检查标签或缩小用途来改善。

三、从视觉结果形成采用判断

一次合理的判断应包含四层证据。第一层是数据:来源是否有授权,类别是否清楚,植物个体是否正确隔离。第二层是模型:是否超过简单基线,验证与测试是否接近。第三层是泛化:背景、光照、角度和新个体变化后,错误是否明显增加。第四层是用途:错误发生后由谁确认,是否会触发不安全行动。

应用中的AI只承担“从图像到候选类别”的部分;若系统还要生成说明,生成内容必须引用可见证据并标明不确定项。普通程序负责保存原图、分数和人工确认结果。开发与学习AI可以根据带编号的失败记录归纳模式、建议下一轮只改变一个变量,也可以帮助检查程序修改范围,但不能把分类标签扩写为病因和处置结论。

本章原型适合用于课堂观察记录的初步整理。它不适合在没有品种信息、时间变化和专业复核的情况下判断植物健康,更不应自动控制任何设备。若测试发现模型主要依赖背景、某一类召回过低或陌生输入经常高分,应选择继续收集证据、限制使用范围或停止使用,而不是用更流畅的界面掩盖问题。

本章小结

计算机视觉模型首先接收像素数组。卷积在局部区域共享计算并逐层形成特征图,迁移学习复用预训练网络的通用视觉表示,再用较少任务数据学习新分类。图像分类、目标检测、图像分割和多模态视觉的主要区别在输出,选择技术前必须先明确任务。可靠测试要按植物个体隔离数据,控制背景、光照和角度变化,检查陌生输入和逐类错误。模型提供候选,人依据来源、范围和后果作最终判断。

关键术语

目标测试

  1. 彩色图像输入模型后,首先表现为什么? A. 植物名称 B. 像素数值数组 C. 病因结论 D. 养护建议
  2. 迁移学习在本章中的主要作用是什么? A. 自动产生正确标签 B. 复用预训练网络的通用视觉特征 C. 取消测试集 D. 保证陌生输入不会出错
  3. 若要精确标出叶面斑点区域,哪类任务最合适? A. 图像分类 B. 文本生成 C. 图像分割 D. 表格回归
  4. 下列哪一种划分最能减少图像数据泄漏? A. 同一片叶的照片随机分到三份数据 B. 按文件名最后一位划分 C. 同一植物个体全部进入同一份数据 D. 全部图片同时用于训练和测试
  5. 判断并改错:“迁移学习冻结了基础网络,因此整个模型没有任何参数需要训练。”
  6. 判断并改错:“三分类模型对陌生塑料叶给出0.95分,说明它已经可靠识别了真实植物状态。”
  7. 用“局部计算、特征图、逐层组合”三个要点说明卷积如何处理图像。
  8. 对同一幅包含多片叶的植物照片,分别说明分类、检测和分割会输出什么。
  9. 设计一组检查背景偏差的视觉测试,说明哪些条件保持不变、改变什么、记录什么。
  10. 某模型整体正确率为86%,但“叶片有斑点”类别10张测试图只识别出4张,陌生输入又经常得到高分。请给出是否采用的判断,并写出至少三项后续措施。

第4章 音频人工智能:让模型识别声音

本章技术主题: 音频分类——把连续声音转换为可学习的频谱特征。
本章技术实验: 训练声音分类模型,观察频谱、类别分数和未知状态。
家庭项目: “制作一个家庭声音提醒器”是本章主项目。
跨章位置: 保存声音模型、阈值和独立测试,第12章可把它接入低风险互动反馈。
技术主线: 声音片段 → 波形 → 频谱图 → 模型训练 → 类别概率 → 测试与使用判断。

学习目标

学完本章,你将能够:

  1. 说明采样率、声音窗口、波形和频谱图分别表示什么;
  2. 完成声音样本采集、标签确认、训练集与测试集划分,并运行一个Keras/TensorFlow声音分类模型;
  3. 解释模型为什么输出类别概率,以及other类别和unknown状态为何不同;
  4. 用噪声、距离、陌生声音和故障文件测试模型,比较误报、漏报与人工复核量;
  5. 判断一个课堂声音分类器适合做什么、不适合做什么。

项目导入

声音分类是音频人工智能的一项基础任务。它不要求模型把声音完整转写成文字,而是判断一段声音更接近哪些已知类别。例如,家庭环境中的门铃、烧水提示和宠物叫声,教室环境中的上课铃、掌声和桌椅移动声,都可以成为分类对象。

声音并不是一个稳定不变的符号。同一个门铃在近处和隔门处的音量不同;同一个上课铃在安静教室和走廊噪声中具有不同背景;手机提示音还可能与门铃相似。如果训练材料只来自一个设备、一个距离和一个房间,模型可能记住录音条件,而没有学会需要识别的声音模式。

本章正式项目是一个低风险的“家庭声音提醒器”。它默认在本地读取已获授权的一秒WAV文件并完成模型推理,输出各类别分数以及“已知类别”或“未知”的判断,再把结果记录下来;原始录音不上传。作品不进行连续监听,不保存谈话内容,不连接门锁、电源、炉具和报警设备,也不替代火灾报警器等专业安全装置。

本章成果

考点提示

本章不要求推导傅里叶变换公式,但要能解释声音为什么需要采样、为什么要切成固定窗口,以及频谱图怎样同时表示时间和频率。还要分清训练与推理、类别分数与现实正确性、模型输出与应用动作。训练集、验证集和测试集应按录制批次或环境分开;同一长录音切出的相邻片段不能分别放入训练集和测试集,否则会造成数据泄漏。

第一节 采集声音并训练第一个声音分类模型

一、把声音任务定义为可检验的分类问题

首版任务使用四个已知类别:doorbell(门铃)、kettle(烧水提示)、class_bell(教室铃声)和other(其他常见声音)。每个输入都是一秒、16 kHz、单声道WAV。模型输出四个类别的相对分数,普通程序再根据最高分和前两名分差决定接受该类别还是输出unknown。

other与unknown不能混为一谈。other是训练时已经收集和标注的类别,例如普通敲击声和拉椅子声;unknown不是一个训练标签,而是应用在证据不足时作出的拒绝判断。强迫模型把每段声音都分到四个已知类别,会使陌生声音也得到一个看似确定的名称。

最小成功样例是:输入独立录制的清晰门铃,程序显示波形、频谱图和全部类别分数;输入拍手、混合声或分数接近的声音时,程序保留分数并输出unknown。项目只记录低风险事件,不因任何分类结果自动控制设备。

二、采集、标注和划分声音数据

数据目录按用途和标签组织:

ch04-sound-project/
├─ input/audio/
│  ├─ train/doorbell|kettle|class_bell|other/
│  ├─ valid/doorbell|kettle|class_bell|other/
│  ├─ test/doorbell|kettle|class_bell|other/
│  └─ target.wav
├─ src/main.py
├─ tests/cases.json
├─ output/
└─ evidence/dialogue.md、change.diff、regression.md、decision-card.md

每个样本的数据卡至少记录类别、录制批次、房间、距离、设备、背景声和授权情况。训练集用于调整模型参数,验证集用于观察训练过程和选择配置,测试集只在模型和规则基本确定后使用。划分时应把同一录制时段或同一长录音的切片整体放在一侧,不能随机拆到训练和测试两边。

四类数量应大致均衡,并尽量覆盖近距离、远距离、不同音量和不同背景。标签不确定的声音进入“待确认”区,不应为了凑数量随意归类。涉及谈话、姓名或其他可识别个人的信息不进入课堂数据集。

采集数量相同也不代表数据已经平衡。如果门铃样本全部在安静近距离录制,而other样本都来自嘈杂走廊,模型可能把背景安静程度当成类别线索。检查数据时要同时比较数量、设备、距离和背景分布,并保留至少一个训练阶段没有出现的环境作为泛化测试。

三、与AI协同形成首版工程

本章的应用中的AI是声音分类模型,它在程序运行时接收频谱图并输出类别分数。开发与学习AI帮助学生澄清数据格式、生成和解释程序、分析错误日志、提出限定修改;它不替学生决定录音是否获得授权、标签是否正确以及错误后果是否可以接受。

与开发与学习AI协同时,可以先提交下面的任务说明:

目标:把一秒WAV分为doorbell、kettle、class_bell、other;证据不足时输出unknown。
数据:train、valid、test来自不同录制批次,target.wav用于单次观察。
格式:16 kHz单声道;不足一秒补零,超过一秒截取。
实现:Python与Keras/TensorFlow;显示波形、频谱图和全部类别分数。
边界:只记录低风险事件,不连续监听,不连接门锁、电源和报警器。
验收:保存模型、测试明细、观察图和结果JSON;测试静音、陌生声、混合声和坏文件。
请先检查数据泄漏、标签顺序和测试划分,再给出完整程序。

自然语言说明不是运行证据。学生仍需检查AI生成代码是否真正使用独立测试集、预处理是否一致、标签顺序是否固定,并亲自运行程序保存首轮结果。

第二节 从波形、频谱图到类别概率

一、声音如何变成模型可以处理的数据

麦克风把空气振动转换成随时间变化的数值。采样率16 kHz表示每秒记录16000个振幅值。一秒声音因此可以表示为长度16000的一维波形。波形容易观察声音何时出现、持续多久和大致强弱,却不容易直接分辨音色。

程序把波形切成许多有重叠的短窗口,再计算每个窗口中不同频率成分的强弱,得到频谱图。频谱图的横向表示时间,纵向表示频率区间,每个位置的数值表示该时间和频率附近的能量。对幅度作对数变换,可以让较弱但有辨别价值的成分更容易观察。

频谱图是一种二维表示,因此可以用小型卷积网络学习局部形状。卷积层寻找短促提示音、持续嗡鸣或重复节奏等模式,最后的Softmax层为四个已知类别分配相对分数。最高分只表示模型在当前标签集合中更偏向该类,并不表示现实中一定是该声音。

二、完整核心程序

下面的src/main.py读取三个独立数据目录,完成频谱转换、模型训练、验证、测试、单个目标声音推理和结果保存。运行前需在项目环境中安装TensorFlow、NumPy和Matplotlib,并按前述目录放入WAV文件。

from pathlib import Path
import json

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

ROOT = Path(__file__).resolve().parents[1]
AUDIO = ROOT / "input" / "audio"
OUT = ROOT / "output"
OUT.mkdir(exist_ok=True)

RATE = 16000
LENGTH = 16000
SEED = 42
CONFIDENCE = 0.70
MARGIN = 0.15
NAMES = ["class_bell", "doorbell", "kettle", "other"]
tf.keras.utils.set_random_seed(SEED)


def load_wave(path):
    """读取16 kHz单声道WAV,并统一为一秒。"""
    wave, rate = tf.audio.decode_wav(
        tf.io.read_file(path), desired_channels=1
    )
    tf.debugging.assert_equal(rate, RATE, message="采样率必须为16 kHz")
    wave = tf.squeeze(wave, axis=-1)[:LENGTH]
    wave = tf.pad(wave, [[0, LENGTH - tf.shape(wave)[0]]])
    wave.set_shape([LENGTH])
    return wave


def spectrogram(wave):
    """把一维波形转换为对数频谱图。"""
    value = tf.signal.stft(
        wave, frame_length=400, frame_step=160, fft_length=512
    )
    return tf.math.log1p(tf.abs(value))[..., tf.newaxis]


def make_dataset(split, shuffle=False):
    paths, labels = [], []
    for label, name in enumerate(NAMES):
        files = sorted((AUDIO / split / name).glob("*.wav"))
        if not files:
            raise ValueError(f"{split}/{name}中没有WAV文件")
        paths.extend(str(path) for path in files)
        labels.extend([label] * len(files))
    dataset = tf.data.Dataset.from_tensor_slices((paths, labels))
    if shuffle:
        dataset = dataset.shuffle(len(paths), seed=SEED)
    dataset = dataset.map(
        lambda path, label: (spectrogram(load_wave(path)), label),
        num_parallel_calls=tf.data.AUTOTUNE,
    )
    return dataset.batch(16).prefetch(tf.data.AUTOTUNE)


def choose(scores, confidence=CONFIDENCE, margin=MARGIN):
    order = np.argsort(scores)[::-1]
    first, second = int(order[0]), int(order[1])
    top = float(scores[first])
    gap = float(scores[first] - scores[second])
    decision = NAMES[first] if top >= confidence and gap >= margin else "unknown"
    return decision, top, gap


def audit(model, dataset):
    rows = []
    for features, labels in dataset:
        predictions = model.predict(features, verbose=0)
        for scores, truth in zip(predictions, labels.numpy()):
            decision, top, gap = choose(scores)
            rows.append(
                {
                    "truth": NAMES[int(truth)],
                    "decision": decision,
                    "top_score": top,
                    "top_two_margin": gap,
                }
            )
    return {
        "correct": sum(row["decision"] == row["truth"] for row in rows),
        "wrong": sum(
            row["decision"] not in {row["truth"], "unknown"} for row in rows
        ),
        "unknown": sum(row["decision"] == "unknown" for row in rows),
        "rows": rows,
    }


train = make_dataset("train", shuffle=True)
valid = make_dataset("valid")
test = make_dataset("test")
input_shape = tuple(spectrogram(tf.zeros(LENGTH)).shape)

model = tf.keras.Sequential(
    [
        tf.keras.layers.Input(input_shape),
        tf.keras.layers.BatchNormalization(),
        tf.keras.layers.Conv2D(16, 3, activation="relu"),
        tf.keras.layers.MaxPooling2D(),
        tf.keras.layers.Conv2D(32, 3, activation="relu"),
        tf.keras.layers.GlobalAveragePooling2D(),
        tf.keras.layers.Dense(len(NAMES), activation="softmax"),
    ]
)
model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"],
)
model.fit(train, validation_data=valid, epochs=10, verbose=2)
_, test_accuracy = model.evaluate(test, verbose=0)
model.save(OUT / "sound_model.keras")

test_report = audit(model, test)
test_report["test_accuracy"] = float(test_accuracy)
(OUT / "test_decisions.json").write_text(
    json.dumps(test_report, ensure_ascii=False, indent=2), encoding="utf-8"
)

target = load_wave(str(AUDIO / "target.wav"))
target_spec = spectrogram(target)
scores = model.predict(target_spec[tf.newaxis, ...], verbose=0)[0]
decision, top, gap = choose(scores)

figure, axes = plt.subplots(2, 1, figsize=(8, 5))
axes[0].plot(np.arange(LENGTH) / RATE, target.numpy())
axes[0].set(title="Waveform", xlabel="Time (s)", ylabel="Amplitude")
axes[1].imshow(
    tf.squeeze(target_spec).numpy().T, origin="lower", aspect="auto"
)
axes[1].set(title="Log spectrogram", xlabel="Time frame", ylabel="Frequency bin")
figure.tight_layout()
figure.savefig(OUT / "target.png", dpi=150)
plt.close(figure)

result = {
    "probabilities": {name: float(score) for name, score in zip(NAMES, scores)},
    "top_score": top,
    "top_two_margin": gap,
    "decision": decision,
    "test_accuracy": float(test_accuracy),
    "notice": "只用于低风险声音分类学习,不替代安全报警装置。",
}
(OUT / "result.json").write_text(
    json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(json.dumps(result, ensure_ascii=False, indent=2))

阅读程序时先沿数据形状追踪:WAV成为(16000,)波形,波形成为二维频谱图,批量频谱图进入模型,模型输出四个分数。CONFIDENCE和MARGIN位于模型之外,它们不改变模型参数,只决定应用何时接受候选。test_decisions.json保留每个测试声音的真实标签、最终判断、最高分和分差,便于以后使用同一批样本比较规则。

三、只改变置信度阈值

保持模型、测试集、预处理和分差规则不变,只把CONFIDENCE从0.55改为0.80。运行前先预测:提高阈值通常减少确定分类和误报,但可能增加未知状态或漏报。运行后记录正确判断、错误判断、未知数量,以及每个变化样本的具体声音条件。

阈值取舍必须联系错误后果。把桌椅声误报成上课铃会产生不必要提醒;把一次低风险门铃判为未知会失去辅助提醒。两种错误都需要记录,但代价可能不同。高风险场景不能通过“继续调阈值”转交给本课堂模型负责。

这个实验只说明同一个模型在两种接受规则下的表现,不能证明阈值0.80普遍优于0.55。如果同时更换模型、声音和预处理,就不能把输出差异归因于阈值。

第三节 测试噪声、距离、陌生声音与误报漏报

一、建立能够暴露失败的测试集

只测试安静环境中的清晰声音,会高估作品能力。固定测试集应覆盖:

类型 示例 观察重点 期望行为
正常 独立批次的清晰门铃或上课铃 基本分类是否可完成 输出全部分数并给出候选类别
边界 隔门、低音量、只有半段提示音 分数是否接近阈值 证据不足时输出unknown
陌生 拍手、钥匙碰撞、未训练手机铃声 是否被强行归类 保留分数,不触发确定提醒
故障 损坏WAV、错误采样率、文件缺失 能否安全停止 显示原因,不生成事件结论

还应把同一目标声放在近距离、远距离、不同房间和背景噪声下测试。测试材料必须来自独立录制批次,不能把为调试反复使用的声音继续称为“从未见过的测试证据”。

误报是目标事件没有发生却给出目标提醒;漏报是目标事件发生却没有相应提醒。混淆矩阵可以显示错误从哪个类别流向哪个类别,精确率回答“报出的目标中有多少是真的”,召回率回答“真实目标中有多少被发现”。样本很少时,应同时报告实际错误条数,不用一个百分数制造普遍性。

二、根据错误进行一次限定修改

首版只依据最高分是否超过阈值。一段门铃与电视声混合的声音得到doorbell=0.61、other=0.57,仍被确定为门铃。问题不在于最高分过低,而在于前两类几乎无法区分。可以要求开发与学习AI只修改“分数转判断”的函数:

实际行为:混合声最高两类分数接近,仍输出doorbell。
期望行为:最高分达到阈值且前两名差值不少于0.15时才接受,否则输出unknown。
允许修改:choose函数。
禁止修改:训练数据、模型结构、预处理、原始分数和固定测试集。
完成后:重跑正常、边界、陌生和故障样例,给出文件差异。

修改时不得把失败声音偷偷加入训练集,也不得删除原始分数。增加未知状态可能减少误报,也可能把原来正确的远距离门铃改为未知,因此必须回归全部样例,而不是只重跑已修好的混合声。

三、形成使用判断

判断卡至少回答以下问题:使用的模型、设备、房间和输入格式是什么;哪些声音经过独立测试;误报与漏报各有多少;低分、类别竞争和故障时怎样停止;谁对录音授权和最终使用负责。

合理结论可以是:作品只在类别有限、录音已授权、设备和环境接近测试条件时辅助记录低风险事件;默认在本地推理,低置信度、前两类接近、陌生声音或文件故障时输出未知;模型或录音设备改变后重新测试;不连续监听,不上传家庭或教室谈话,不连接真实设备,不替代专业报警装置。

在这些边界内,学生还可以把模型迁移为声音互动作品:经过人工确认的类别只改变屏幕卡片、虚拟指示灯或播放短提示音,unknown和故障状态保持不行动。这个反馈层由普通程序实现,不是模型“自动理解了该做什么”。界面和反馈方式可以由学生创意设计,但必须保留原始分数、确认步骤和停止条件,不得扩展为真实门锁、电源、炉具或报警控制。

如果只需识别一种固定电子提示音,还应比较简单的频率规则。若规则已经稳定满足目标,就没有必要为了使用更复杂的模型而增加数据、算力和维护负担。

本章小结

声音先被采样为波形,再按短窗口转换成频谱图。声音分类模型从带标签的频谱图中学习局部模式,并为已知类别输出相对分数。训练集用于学习参数,验证集用于观察训练,独立测试集用于形成使用证据。

模型不知道世界上所有声音。other是训练过的类别,unknown是应用拒绝在证据不足时强行分类。置信度阈值和前两名分差属于模型外规则。噪声、距离、房间和设备变化会暴露模型泛化边界,误报、漏报与未知数量必须结合实际后果解释。

应用中的AI承担频谱图到类别分数的映射,开发与学习AI协助形成和修改程序,人负责授权、标签、测试和采用决定。一个可靠的课堂作品不仅能在正常样例上运行,还能在陌生输入和故障出现时明确停止。

关键术语

目标测试

一、单项选择题

  1. 频谱图更直接呈现的是( )。
    A.录音文件名 B.时间和频率成分的强弱 C.声音事件的真实名称 D.设备权限
  2. 把同一长录音的相邻切片随机分到训练集和测试集,最可能造成( )。
    A.类别增多 B.数据泄漏 C.采样率下降 D.声音变短
  3. 最高两个类别分数非常接近时,较合适的处理是( )。
    A.永远选择第一名 B.删除第二名 C.保留分数并输出未知 D.直接触发设备
  4. 保持模型不变而提高置信度阈值,通常会使( )。
    A.模型自动重训 B.确定分类减少 C.采样率变化 D.标签数量增加

二、判断并改错

  1. 测试准确率较高的课堂声音分类器可以直接替代家庭安全报警器。请判断并改正。
  2. other类别和unknown状态含义完全相同。请判断并改正。

三、简答与操作题

  1. 不使用公式,说明一秒WAV从波形、频谱图到类别概率和最终判断的过程。
  2. 设计一条边界声音和一条陌生声音,写出采集条件、期望行为和通过标准。
  3. 设计一次只把置信度阈值从0.55改为0.80的实验,说明固定项、记录项和结论边界。
  4. 说明应用中的AI、开发与学习AI、普通规则、低风险反馈层和人在本章项目中的不同职责。

答案编号:A1-4-01—A1-4-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第5章 时间序列:从连续记录中发现变化

本章技术主题: 时间序列学习——利用过去的连续记录预测下一时点。
本章技术实验: 把连续环境记录切成时间窗口,比较规则、统计基线和时序模型。
家庭项目: “建立家庭空气与用能观察站”第一阶段——观察、异常与预测。
跨章位置: 本章保存数据质量和预测证据,第11章只在虚拟环境中研究舒适—节能行动策略。
技术主线: 时间记录 → 数据质量 → 历史窗口 → 基线与模型 → 预测误差 → 异常、漂移与使用判断。

学习目标

学完本章,你将能够:

  1. 说明时间戳、采样间隔、时间窗口和按时间划分数据的意义;
  2. 比较固定规则、上一时点、移动平均和小型时序模型分别能解决什么问题;
  3. 运行一个使用一维卷积网络预测下一时点数值的完整Keras/TensorFlow项目;
  4. 用缺失、突跳、缓慢漂移和陌生时段测试预测与异常标记;
  5. 根据同一测试时段的误差、成本和维护要求,判断是否值得采用时序模型。

项目导入

温度、湿度、空气质量、土壤湿度和用电功率等数据都带有时间顺序。某个读数本身可能很普通,但它相对于前一小时突然升高,或者连续几周缓慢变化,就可能值得复核。处理这类数据时,不能把所有行随意打乱,因为模型在预测某一时刻时只能使用当时已经发生的历史,不能偷看未来。

本章建立一个家庭空气与用能观察站,以固定间隔记录的环境或用能CSV为输入。课堂默认使用教师提供的模拟数据、脱敏记录或预先采集的回放数据,不要求上传真实家庭作息与精细用电明细。项目先检查时间戳、缺失、重复、断点和长期不变,再把连续读数切成历史窗口,用窗口中的过去数据预测下一时点;同时保留固定阈值、上一时点和移动平均等透明基线。只有模型在同一封存测试时段稳定优于基线,才有理由考虑增加它。

本章可以沿用第2章的阳台环境字段,但不再把每行当作可随意打乱的独立样本,而是保留时间顺序,用过去窗口预测下一时点。字段相似不代表学习问题相同:结构化分类回答“这一行属于什么”,时间序列回答“接下来可能怎样变化”。

作品只用于学习趋势、预测和异常复核,不提供健康、消防、电气安全或设备故障结论,不自动断电、开窗或控制真实设备。异常分数表示“与当前模型或近期历史不一致”,不等于已经发现危险和原因。

本章成果

考点提示

时间序列的核心不是“画一条折线”,而是时间先后关系。训练、验证和测试必须按时间划分;归一化参数只能从训练时段计算;预测某一点的窗口不能包含该点之后的数据。上一时点和移动平均是重要基线。模型误差较小,不代表模型知道变化原因;预测残差较大,也只表示需要复核。

第一节 把环境记录变成时间窗口

一、先检查时间,再讨论模型

输入文件environment.csv至少包含timestamp和一个待分析数值字段,例如power_w。每一行表示一个时点。开始训练前要回答:采样间隔是否基本固定;时间是否重复或逆序;是否有空值和非数字;是否存在长断点;传感器是否长时间保持完全相同的读数。

假设每10分钟记录一次,用过去12个点预测下一个点,那么一个输入窗口覆盖过去120分钟。第一个窗口使用第1—12个读数,目标是第13个读数;下一个窗口使用第2—13个读数,目标是第14个读数。窗口长度相同,但相邻窗口会共享大部分历史,这是时间序列的正常结构。

缺失时点不能悄悄填零。零可能是一个真实读数,也可能制造突跳。首版程序遇到明显断点时停止,要求学生检查设备日志或明确选择插值方法。任何补值都应写入处理记录,原始CSV保持只读。

二、按时间划分训练、验证和测试

普通表格常见随机划分,但时间序列应保持先后顺序。例如,最早70%用于训练,中间15%用于验证,最后15%作为测试。这样模拟了真实使用:用过去建立模型,再处理未来。

训练集用于调整模型参数;验证集用于选择窗口、网络规模和训练轮数;测试集只在方案确定后形成最终证据。如果反复查看测试结果并据此改模型,测试集就参与了开发,不再是独立证据。季节变化明显时,还应保留跨季节或跨设备的额外测试。

划分边界要以“目标时点”判断,而不是只看窗口从哪里开始。验证集的第一个目标可以使用训练末尾已经发生的历史,这符合真实预测;但它不能使用目标之后的记录。测试集同理,只允许使用测试目标之前已经可见的窗口。若把所有窗口先随机打乱,再按比例切分,相邻且高度重叠的窗口可能分散到训练和测试两边,使误差看起来不真实地偏小。

数值归一化也要遵守时间边界。训练均值和标准差只能由训练时段计算,再用于验证和测试。如果先用全体数据计算均值,未来信息就以统计量的形式泄漏给模型。

三、与AI协同建立时间窗口工程

本章的应用中的AI是小型一维卷积时序模型:它接收过去一段时间的数值序列,输出下一时点的预测。开发与学习AI帮助学生解释数据形状、生成完整程序、分析训练曲线和限定修改;学生负责确认字段含义、采样条件、基线、错误后果和采用范围。

项目文件如下:

ch05-timeseries-project/
├─ input/environment.csv
├─ input/config.json
├─ src/forecast.py
├─ tests/cases.json
├─ output/predictions.csv、summary.json、test_plot.png、model.keras
└─ evidence/dialogue.md、change.diff、regression.md、decision-card.md

config.json示例:

{
  "field": "power_w",
  "window": 12,
  "fixed_threshold": 1200,
  "z_threshold": 3.0,
  "epochs": 40
}

向开发与学习AI说明任务时,应明确“只能用目标时点之前的数据”“按时间划分”“归一化只使用训练时段”“必须同时报告基线和模型”。如果生成的代码先打乱全部窗口再划分,或用测试误差选择训练轮数,应要求修正。

第二节 比较规则、统计基线与时序模型

一、四种方法回答不同问题

固定阈值回答“当前值是否越过人工设定的观察线”。它清楚、稳定,却不会自动适应季节和使用习惯。上一时点基线直接把当前值作为下一时点预测,适合变化缓慢的数据;如果复杂模型不能超过它,就没有证明新增复杂度有价值。

移动平均用最近若干点的平均值预测下一点。窗口短,响应较快但容易受噪声影响;窗口长,曲线平稳却可能延迟。Z分数比较当前点与此前窗口的均值和波动,适合标记相对近期历史较少见的点,但不能说明危险和原因。

时序模型从大量历史窗口中调整参数,尝试学习重复周期、局部趋势和变化组合。本章使用一维卷积提取窗口中的局部模式,再输出下一时点数值。模型是否有用,要用同一测试时段的平均绝对误差与基线比较,而不是只看训练损失是否下降。

二、完整核心程序

下面的src/forecast.py完成数据质量检查、按时间划分、训练集归一化、历史窗口构造、两个预测基线、一维卷积模型、测试误差、残差异常和漂移提示。运行前安装TensorFlow、NumPy和Matplotlib。

from pathlib import Path
from datetime import datetime
import csv
import json

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "output"
OUT.mkdir(exist_ok=True)
tf.keras.utils.set_random_seed(42)

config = json.loads(
    (ROOT / "input" / "config.json").read_text(encoding="utf-8")
)
FIELD = config["field"]
WINDOW = int(config["window"])
FIXED_THRESHOLD = float(config["fixed_threshold"])
Z_THRESHOLD = float(config["z_threshold"])
EPOCHS = int(config.get("epochs", 40))

with (ROOT / "input" / "environment.csv").open(
    encoding="utf-8-sig", newline=""
) as file:
    rows = list(csv.DictReader(file))

if not rows or "timestamp" not in rows[0] or FIELD not in rows[0]:
    raise ValueError(f"CSV必须包含timestamp和{FIELD}")

try:
    times = [datetime.fromisoformat(row["timestamp"]) for row in rows]
    values = np.asarray([float(row[FIELD]) for row in rows], dtype=np.float32)
except ValueError as error:
    raise ValueError(f"时间或数值格式错误:{error}") from error

if len(values) < max(80, WINDOW * 5):
    raise ValueError("连续记录过少,无法完成按时间划分和窗口学习")
if not np.all(np.isfinite(values)):
    raise ValueError("数据包含空值、无穷值或非数字")

intervals = np.asarray(
    [(times[i] - times[i - 1]).total_seconds() for i in range(1, len(times))]
)
if np.any(intervals <= 0):
    raise ValueError("时间戳重复或逆序")
median_interval = float(np.median(intervals))
if np.any(intervals > 1.5 * median_interval):
    raise ValueError("发现明显采样断点;请核查缺失时点,不要自动填零")

train_end = int(len(values) * 0.70)
valid_end = int(len(values) * 0.85)
if train_end <= WINDOW or valid_end <= train_end:
    raise ValueError("数据划分后没有足够窗口")

train_mean = float(values[:train_end].mean())
train_std = float(values[:train_end].std())
if train_std < 1e-8:
    raise ValueError("训练时段几乎没有变化,不能建立有效预测模型")
normalized = (values - train_mean) / train_std


def make_windows(series, window):
    features, targets, target_indices = [], [], []
    for target_index in range(window, len(series)):
        features.append(series[target_index - window : target_index])
        targets.append(series[target_index])
        target_indices.append(target_index)
    return (
        np.asarray(features, dtype=np.float32)[..., np.newaxis],
        np.asarray(targets, dtype=np.float32),
        np.asarray(target_indices),
    )


all_x, all_y, all_index = make_windows(normalized, WINDOW)
train_mask = all_index < train_end
valid_mask = (all_index >= train_end) & (all_index < valid_end)
test_mask = all_index >= valid_end
x_train, y_train = all_x[train_mask], all_y[train_mask]
x_valid, y_valid = all_x[valid_mask], all_y[valid_mask]
x_test, y_test = all_x[test_mask], all_y[test_mask]
test_index = all_index[test_mask]

model = tf.keras.Sequential(
    [
        tf.keras.layers.Input((WINDOW, 1)),
        tf.keras.layers.Conv1D(16, 3, activation="relu"),
        tf.keras.layers.Conv1D(8, 3, activation="relu"),
        tf.keras.layers.GlobalAveragePooling1D(),
        tf.keras.layers.Dense(8, activation="relu"),
        tf.keras.layers.Dense(1),
    ]
)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])
early_stop = tf.keras.callbacks.EarlyStopping(
    monitor="val_loss", patience=5, restore_best_weights=True
)
model.fit(
    x_train,
    y_train,
    validation_data=(x_valid, y_valid),
    epochs=EPOCHS,
    batch_size=16,
    callbacks=[early_stop],
    verbose=2,
)
model.save(OUT / "model.keras")


def denormalize(series):
    return np.asarray(series) * train_std + train_mean


model_test = denormalize(model.predict(x_test, verbose=0).squeeze())
actual_test = values[test_index]
previous_test = values[test_index - 1]
moving_test = np.asarray(
    [values[index - WINDOW : index].mean() for index in test_index]
)

model_valid = denormalize(model.predict(x_valid, verbose=0).squeeze())
valid_index = all_index[valid_mask]
valid_actual = values[valid_index]
valid_residual = np.abs(valid_actual - model_valid)
residual_limit = float(valid_residual.mean() + 3 * valid_residual.std())
test_residual = np.abs(actual_test - model_test)


def previous_z(index):
    history = values[index - WINDOW : index]
    std = float(history.std())
    if std < 1e-8:
        return 0.0 if values[index] == history.mean() else float("inf")
    return float((values[index] - history.mean()) / std)


records = []
for position, index in enumerate(test_index):
    z_score = previous_z(int(index))
    records.append(
        {
            "timestamp": times[int(index)].isoformat(),
            "actual": float(actual_test[position]),
            "previous_baseline": float(previous_test[position]),
            "moving_baseline": float(moving_test[position]),
            "model_prediction": float(model_test[position]),
            "absolute_residual": float(test_residual[position]),
            "fixed_flag": bool(actual_test[position] > FIXED_THRESHOLD),
            "z_score": z_score,
            "z_flag": bool(abs(z_score) > Z_THRESHOLD),
            "prediction_flag": bool(test_residual[position] > residual_limit),
        }
    )

with (OUT / "predictions.csv").open(
    "w", encoding="utf-8-sig", newline=""
) as file:
    writer = csv.DictWriter(file, fieldnames=list(records[0]))
    writer.writeheader()
    writer.writerows(records)


def mae(actual, predicted):
    return float(np.mean(np.abs(actual - predicted)))


test_mean = float(actual_test.mean())
drift_score = abs(test_mean - train_mean) / train_std
summary = {
    "field": FIELD,
    "records": len(values),
    "window": WINDOW,
    "train_targets": int(train_mask.sum()),
    "valid_targets": int(valid_mask.sum()),
    "test_targets": int(test_mask.sum()),
    "previous_mae": mae(actual_test, previous_test),
    "moving_mae": mae(actual_test, moving_test),
    "model_mae": mae(actual_test, model_test),
    "prediction_residual_limit_from_validation": residual_limit,
    "prediction_flag_count": int(np.sum(test_residual > residual_limit)),
    "drift_score_in_train_standard_deviations": float(drift_score),
    "notice": "异常与漂移只表示需要复核,不表示危险或原因。",
}
(OUT / "summary.json").write_text(
    json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8"
)

plt.figure(figsize=(9, 4))
test_times = [times[int(index)] for index in test_index]
plt.plot(test_times, actual_test, label="actual")
plt.plot(test_times, previous_test, label="previous baseline", alpha=0.7)
plt.plot(test_times, model_test, label="Conv1D prediction", alpha=0.8)
plt.axhline(FIXED_THRESHOLD, color="orange", linestyle="--", label="fixed threshold")
plt.legend()
plt.tight_layout()
plt.savefig(OUT / "test_plot.png", dpi=150)
plt.close()
print(json.dumps(summary, ensure_ascii=False, indent=2))

这段程序没有把测试时段用于训练,也没有用全体数据计算归一化参数。模型预测、上一时点和移动平均都恢复为原始单位后再计算平均绝对误差,因此可以直接比较。若模型没有明显超过简单基线,应保留基线或继续收集数据,而不是只展示神经网络训练成功。

prediction_flag表示模型预测与实际值的残差超过验证时段形成的界限;z_flag表示当前值相对此前窗口较少见;fixed_flag表示超过人工设定的观察线。三者回答的问题不同,必须分别保留。

本章只做“一步预测”,即每次根据真实的最近窗口预测紧接着的一个时点。连续预测多个未来时点是另一项更困难的任务:后续窗口可能包含模型自己的前一步预测,误差会逐步积累。不能把一步预测的测试结果直接解释为“可以可靠预测未来一天”。如需多步预测,应另行设计输出长度、滚动方式和多步误差测试。

三、比较结果而不是比较“技术高级程度”

评价表至少列出上一时点MAE、移动平均MAE、模型MAE、异常标记数、运行时间和维护要求。若模型只比上一时点改善很少,却需要更多依赖、训练和持续监测,简单基线可能更合适。若数据存在稳定周期,模型在多个封存时段都明显改善,并且错误后果可控,才有进一步采用价值。

只改变窗口长度也可以形成对照实验。例如把window从6改为18,保持数据划分、模型结构、训练轮数上限和测试时段不变。窗口6看到较短历史,窗口18看到较长历史;结果变化不能简单解释为“越长越好”,还要结合采样间隔、周期长度、误差和响应速度。

第三节 测试缺失、漂移、异常与预测边界

一、用固定样例暴露四类问题

类型 数据设计 观察重点 期望行为
正常 固定间隔、范围稳定的连续记录 基线与模型能否完成预测 输出三种预测和误差
边界 数值恰等于阈值或残差接近界限 比较符号是否明确 按预先约定规则处理
陌生 季节、设备或作息改变造成整体漂移 旧模型是否仍适用 提示分布变化并重新评估
故障 缺失时点、重复时间、非数字、长期不变 程序是否掩盖数据问题 明确停止,不覆盖原始CSV

突跳既可能是真实事件,也可能是传感器或通信异常;缓慢漂移既可能来自季节,也可能来自设备老化。程序只能指出数值与历史或预测不一致,不能自动确定原因。应回查设备日志、现场记录或参考仪表。

漂移分数比较测试均值与训练均值的距离,只是一个初步提示。均值相近也可能存在其他分布变化;均值不同也可能是合理季节变化。模型投入使用后,要按固定周期保存新数据、重跑基线和测试,并明确谁决定是否重新训练。

二、根据泄漏或边界错误限定修改

一种常见错误是构造目标时把当前点放进输入窗口,或者计算当前点Z分数时把当前点加入自己的历史。发现这类问题后,应让开发与学习AI只修改窗口切片,并保持模型、测试时段和阈值不变:

- history = values[index - WINDOW : index + 1]
+ history = values[index - WINDOW : index]

修改后重新运行正常、突跳、缓慢漂移、恒定读数和缺失时点。突跳标记可能变化,但正常样例不应大量增加误报,缺失时点仍应停止。学生要检查代码差异,确认AI没有顺便改动测试数据、数据划分和阈值。

三、形成预测边界和采用判断

判断是否采用时序模型,应同时检查:数据是否足够连续并覆盖实际变化;测试是否严格晚于训练;模型是否稳定超过简单基线;误报、漏报和延迟分别造成什么后果;本地算力、数据隐私和维护成本是否可承担;漂移后由谁复核和重训。

合理结论可以是“采用固定规则与移动平均,暂不采用模型”,也可以是“模型仅用于生成待复核预测,不参与设备控制”。即使采用模型,固定规则和数据质量检查仍应保留为解释基线和故障退路。语言模型可以依据summary.json协助整理周报,但不得把异常分数改写成未经证实的原因。

本章小结

时间序列的关键信息存在于先后顺序中。连续记录先经过时间戳、间隔、缺失和传感器状态检查,再被切成“过去窗口—下一时点”样本。训练、验证和测试按时间划分,归一化只使用训练时段,才能避免未来信息泄漏。

固定阈值、上一时点、移动平均、Z分数和一维卷积模型回答不同问题。模型必须在同一测试时段与基线比较;预测残差和漂移只说明需要复核,不能证明危险和原因。应用中的AI负责从历史窗口产生预测,开发与学习AI协助编程和分析,人负责数据条件、错误后果与采用决定。

关键术语

目标测试

一、单项选择题

  1. 时间序列通常按时间顺序划分训练和测试,主要是为了( )。
    A.让图表更美观 B.模拟用过去预测未来并避免泄漏 C.增加字段 D.删除异常
  2. 如果复杂模型在测试时段没有超过“直接使用上一时点”的基线,较合理的做法是( )。
    A.隐藏基线 B.仍宣称模型更先进 C.保留简单方案并检查数据和任务 D.把测试数据加入训练
  3. 当前值的预测残差很大,最准确的解释是( )。
    A.已经证明存在危险 B.当前值与模型预测不一致,需要复核 C.模型一定损坏 D.已经找到变化原因
  4. 归一化所用均值和标准差应首先来自( )。
    A.全部数据 B.测试时段 C.训练时段 D.未来数据

二、判断并改错

  1. 为了提高模型精度,可以把测试时段打乱后加入训练集,再继续报告原测试结果。请判断并改正。
  2. 一维卷积模型的测试误差较小,就说明它已经知道读数变化的真实原因。请判断并改正。

三、简答与操作题

  1. 说明“过去12个点预测下一个点”怎样形成输入和目标,并指出哪种窗口构造会造成未来信息泄漏。
  2. 比较固定阈值、上一时点、移动平均和一维卷积模型各自回答的问题及一个局限。
  3. 设计缺失、突跳、缓慢漂移和传感器停滞四类测试,分别写出期望行为。
  4. 根据数据、基线误差、模型误差、隐私、成本和维护条件,说明你会在什么情况下采用时序模型,什么情况下暂不用AI。

答案编号:A1-5-01—A1-5-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第二篇 照顾自己与家人

让模型理解语言和语义

第6章 自然语言处理:让机器处理文本任务

本章技术主题: 自然语言处理——把文字转换为可学习的表示和任务输出。
本章技术实验: 对家庭事务文字做分类和字段抽取,并保留原文证据。
家庭项目: “把零散家庭事务变成可执行计划”是主项目;“建立家庭健康资料与就医准备助手”是模拟脱敏的受控案例。
跨章位置: 本章只生成候选事项和待确认项;日历等外部写入留到第12章讨论,并必须预览确认。
技术主线: 家庭事务文本 → 词元与文本表示 → 分类/信息抽取 → 候选字段 → 原文核对 → 边界测试。

学习目标

学完本章,你将能够:

  1. 区分文本分类、信息抽取和语言生成三类自然语言处理任务;
  2. 用生活语言解释词元、词表、嵌入表示和文本分类概率;
  3. 运行一个使用Keras/TensorFlow完成家庭事务文本分类的小模型,并用规则抽取明确字段;
  4. 比较关键词规则、小文本模型和通用语言模型的能力与限制;
  5. 用歧义、遗漏、否定、冲突和陌生表达测试结果,决定哪些字段可以进入候选清单。

项目导入

家庭事务可能来自社区通知、缴费提醒、预约短信、家校消息和家庭成员留言,表达方式并不统一。有的公布活动,有的提醒截止时间,有的临时调整安排,还有的只是一般说明。学生真正需要的往往不是一段更流畅的摘要,而是知道“这是什么类型的事项”“可能需要做什么”“时间和地点是否明确”“哪些信息仍待确认”。

本章直接使用已经转成文字的家庭事务材料,先训练一个小文本分类模型,把文本分为event(活动)、deadline(截止事项)、schedule_change(安排变更)和general(一般信息);再用透明规则抽取原文明确写出的日期、时间和地点。图片中的文字识别(OCR)和语音转写(ASR)只是可选的上游输入方式,不是本章文本模型的能力;若使用它们,必须同时保留原图片或录音片段供核对。通用语言模型可以作为第三种方案提出候选字段,但同样不能把常识补写成家庭事实。

课堂只使用教师编写的模拟材料、公开通知或经过脱敏整理的文本,不处理真实姓名、电话号码、账号和精细家庭作息。作品生成的是待核对候选,不自动报名、不发送消息,也不直接写入外部日历。“变成可执行计划”是指人回看原文、补齐或确认行动后形成清单,不是模型替家庭作决定。确需进入日历时,必须先显示事项、日期、时间、地点、来源和不确定项的预览,由人逐项确认后再写入。

本章成果

考点提示

文本分类为整段文字选择类别,信息抽取从原文中寻找特定字段,语言生成则根据上下文连续产生文字。三者可能串联,但不是同一任务。结构化字段便于程序检查,却不能保证内容正确;任何日期、地点和行动都应保留原文片段。小模型的类别概率只在当前标签集合中有意义,陌生家庭事务也可能得到较高分。

第一节 用文本分类与信息抽取整理家庭事务

一、把事务整理拆成两个任务

第一项任务是文本分类。输入一段事务文本,输出四个类别的分数和最高候选。例如,“周六下午在社区活动室举行旧物交换活动”更接近event;“本月物业费最迟于5月18日17:00前完成缴纳”更接近deadline。类别由项目设计者定义,不是自然界唯一正确的划分。

第二项任务是信息抽取。程序从原文中寻找日期、时间、地点和行动表达。抽取结果必须能够回到原句。例如,上门服务消息只写“地点另行确认”,输出就应保留该表达并标为待确认,不能根据家庭住址或历史记录自行补写。

一条家庭事务文本可能同时包含活动和截止时间。分类任务可以根据主要用途给出一个候选类别,信息抽取则保留多个时间字段。项目不要求把所有复杂消息强行压缩成一行确定计划;信息不足或内部冲突时,正确输出是“需要人工核对”。

二、准备文本、标签和独立测试

训练数据继续使用notices.csv,其中的“通知”泛指进入项目的事务消息;文件至少包含split、text和label三列:

split,text,label
train,5月12日14:00在社区活动室举行旧物交换活动,event
train,本月物业费最迟于5月18日17:00前完成缴纳,deadline
train,原定周四的家庭聚餐调整到周五晚上,schedule_change
test,上门检修时间另行通知,请关注后续消息,general

split明确写为train、valid或test,避免程序随机重分后难以复现实验。每类事务文本应覆盖不同措辞,不能只让deadline类都包含“截止”两个字,否则模型可能只记住一个关键词。相同模板改几个日期形成的近重复文本应放在同一数据分区。

数据卡记录文本来源、标签定义、标注者、是否去除个人信息和划分方式。标签有争议的事务文本进入待确认区;必要时由两人独立标注并记录分歧。测试集应包含简短消息、长通知、否定句、多个时间、陌生词和缺失字段。

三、与AI协同形成首版

本章的应用中的AI包括两类:小文本分类模型在本地把家庭事务文本映射为类别概率;通用语言模型可以根据任务合同提出类别和字段候选。开发与学习AI帮助学生设计标签、生成和解释完整代码、检查数据泄漏、分析错误并形成限定修改。学生负责确认标签、原文证据、隐私范围和最终是否使用。

项目结构为:

ch06-nlp-project/
├─ input/notices.csv、target.txt
├─ src/analyze_notice.py
├─ tests/cases.json
├─ output/model.keras、test_report.json、target_result.json
└─ evidence/dialogue.md、comparison.md、change.diff、regression.md、decision-card.md

与开发与学习AI协同时可提出:

任务:完成四分类家庭事务文本模型,并从目标消息中抽取原文明示的日期、时间和地点。
实现:Python与Keras/TensorFlow;训练、验证、测试由CSV的split字段决定。
要求:TextVectorization只能适配训练文本;输出全部类别概率;低分时输出unknown。
抽取边界:只保留原文匹配,不补写地点、负责人和日期。
验收:保存模型、测试明细和目标结果;覆盖歧义、否定、冲突、陌生和缺失字段。
请先检查标签定义、近重复文本和隐私,再生成完整程序。

第二节 从词元、文本表示到任务输出

一、文字怎样进入模型

计算机不能直接把一句话当作“意思”。文本首先被切分为可处理的单位,称为词元。词元可以是字、词、子词或标点。TextVectorization根据训练文本建立有限词表,再把每个词元转换成整数编号;词表外的陌生词使用专门的未知标记。

整数编号本身没有远近含义。嵌入层为每个词元学习一个较短的数值向量,使有助于完成当前分类任务的词元形成可用表示。随后,模型汇总一段事务文本中的向量信息,通过全连接层输出四个类别分数。这里的嵌入服务于本章分类任务;更一般的语义空间和语义检索将在下一章讨论。

文本分类模型可能从多个表达中学习模式,但需要带标签样本,对训练范围外的写法也会出错。关键词规则不需要训练,行为清楚,却容易漏掉同义表达。通用语言模型能按自然语言要求完成分类和抽取,但可能补全未出现的信息,而且输出会随上下文变化。三种方法要使用同一批固定家庭事务文本比较。

二、完整核心程序

下面的src/analyze_notice.py从CSV读取固定数据分区,训练一个小文本分类模型,保存测试明细,并对target.txt完成分类和透明规则抽取。运行前安装TensorFlow和NumPy。

from pathlib import Path
import csv
import json
import re

import numpy as np
import tensorflow as tf

ROOT = Path(__file__).resolve().parents[1]
INPUT = ROOT / "input"
OUT = ROOT / "output"
OUT.mkdir(exist_ok=True)
tf.keras.utils.set_random_seed(42)

EXPECTED_LABELS = ["deadline", "event", "general", "schedule_change"]
CONFIDENCE = 0.60


def load_rows():
    with (INPUT / "notices.csv").open(encoding="utf-8-sig", newline="") as file:
        rows = list(csv.DictReader(file))
    required = {"split", "text", "label"}
    if not rows or not required.issubset(rows[0]):
        raise ValueError("notices.csv必须包含split、text和label")
    cleaned = []
    for number, row in enumerate(rows, 2):
        split = row["split"].strip()
        text = row["text"].strip()
        label = row["label"].strip()
        if split not in {"train", "valid", "test"}:
            raise ValueError(f"第{number}行split无效")
        if not text or label not in EXPECTED_LABELS:
            raise ValueError(f"第{number}行文本为空或标签无效")
        cleaned.append({"split": split, "text": text, "label": label})
    return cleaned


def select(rows, split):
    part = [row for row in rows if row["split"] == split]
    if not part:
        raise ValueError(f"缺少{split}数据")
    texts = np.asarray([row["text"] for row in part], dtype=str)
    labels = np.asarray(
        [EXPECTED_LABELS.index(row["label"]) for row in part], dtype=np.int32
    )
    return texts, labels


def keyword_baseline(text):
    """透明关键词基线;无命中时返回general。"""
    rules = {
        "deadline": ["截止", "最迟", "提交"],
        "event": ["举行", "活动", "讲座", "展示"],
        "schedule_change": ["调整", "改为", "取消", "顺延"],
    }
    scores = {
        label: sum(word in text for word in words) for label, words in rules.items()
    }
    best = max(scores, key=scores.get)
    return best if scores[best] > 0 else "general"


def extract_fields(text):
    """只抽取原文明示的常见日期、时间和“地点:”字段。"""
    date_patterns = [
        r"\d{4}年\d{1,2}月\d{1,2}日",
        r"\d{1,2}月\d{1,2}日",
        r"(?:本|下)?周[一二三四五六日天]",
    ]
    dates = []
    for pattern in date_patterns:
        dates.extend(re.findall(pattern, text))
    times = re.findall(r"(?:[01]?\d|2[0-3]):[0-5]\d", text)
    place_match = re.search(
        r"(?:地点|活动地点|上课地点)[::]\s*([^,。;;\n]+)", text
    )
    place = place_match.group(1).strip() if place_match else None
    uncertainties = []
    if not dates:
        uncertainties.append("日期未明确")
    if not times:
        uncertainties.append("具体时间未明确")
    if place is None:
        uncertainties.append("地点未按明确字段给出")
    if any(word in text for word in ["另行通知", "待定", "视情况"]):
        uncertainties.append("原文包含待定表达")
    return {
        "dates": list(dict.fromkeys(dates)),
        "times": list(dict.fromkeys(times)),
        "place": place,
        "uncertainties": uncertainties,
    }


rows = load_rows()
x_train, y_train = select(rows, "train")
x_valid, y_valid = select(rows, "valid")
x_test, y_test = select(rows, "test")

vectorizer = tf.keras.layers.TextVectorization(
    max_tokens=5000,
    output_mode="int",
    output_sequence_length=80,
)
vectorizer.adapt(x_train)

model = tf.keras.Sequential(
    [
        tf.keras.layers.Input(shape=(), dtype=tf.string),
        vectorizer,
        tf.keras.layers.Embedding(5000, 32, mask_zero=True),
        tf.keras.layers.GlobalAveragePooling1D(),
        tf.keras.layers.Dense(24, activation="relu"),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(len(EXPECTED_LABELS), activation="softmax"),
    ]
)
model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"],
)
early_stop = tf.keras.callbacks.EarlyStopping(
    monitor="val_loss", patience=3, restore_best_weights=True
)
model.fit(
    x_train,
    y_train,
    validation_data=(x_valid, y_valid),
    epochs=20,
    batch_size=16,
    callbacks=[early_stop],
    verbose=2,
)
model.save(OUT / "model.keras")

test_scores = model.predict(x_test, verbose=0)
test_rows = []
for text, truth, scores in zip(x_test, y_test, test_scores):
    top = int(np.argmax(scores))
    test_rows.append(
        {
            "text": str(text),
            "truth": EXPECTED_LABELS[int(truth)],
            "model_label": EXPECTED_LABELS[top],
            "model_score": float(scores[top]),
            "keyword_label": keyword_baseline(str(text)),
            "correct": bool(top == int(truth)),
        }
    )
test_report = {
    "count": len(test_rows),
    "model_correct": sum(row["correct"] for row in test_rows),
    "keyword_correct": sum(
        row["keyword_label"] == row["truth"] for row in test_rows
    ),
    "rows": test_rows,
}
(OUT / "test_report.json").write_text(
    json.dumps(test_report, ensure_ascii=False, indent=2), encoding="utf-8"
)

target = (INPUT / "target.txt").read_text(encoding="utf-8").strip()
if not target:
    raise ValueError("target.txt为空")
scores = model.predict(np.asarray([target]), verbose=0)[0]
top = int(np.argmax(scores))
model_label = EXPECTED_LABELS[top] if scores[top] >= CONFIDENCE else "unknown"
fields = extract_fields(target)
result = {
    "raw_text": target,
    "model_probabilities": {
        label: float(score) for label, score in zip(EXPECTED_LABELS, scores)
    },
    "model_label": model_label,
    "keyword_baseline": keyword_baseline(target),
    "extracted_fields": fields,
    "status": "needs_human_review" if fields["uncertainties"] else "candidate",
    "notice": "分类和抽取均为候选,必须回到事务原文核对。",
}
(OUT / "target_result.json").write_text(
    json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(json.dumps(result, ensure_ascii=False, indent=2))

程序中的小模型负责文本分类,extract_fields只实现一组透明抽取规则。规则没有匹配到,不代表原文一定没有该信息;它可能使用了程序尚未覆盖的表达。将规则结果与模型分类分开保存,可以判断错误发生在类别判断还是字段抽取。

三、比较规则、小模型与通用语言模型

比较时使用同一组封存家庭事务文本和同一评价表。关键词规则记录类别是否正确、哪些同义表达漏掉;小模型记录分类正确数、各类别错误和低置信度样例;通用语言模型记录类别、字段是否有原文依据、是否补写不存在的信息。不能用“语言更自然”代替任务评价。

通用语言模型的任务合同可以写成:

只处理给定家庭事务文本。输出类别候选、日期、时间、地点、行动和不确定项。
每个非空字段同时复制支持它的原文片段。
原文未写就使用null;不得根据常识补写负责人、地点和日期。
遇到多种时间、取消与原安排并存或相互冲突时,保留各说法并标记人工核对。
输出只形成预览候选;未经人确认,不写入日历、不发送消息。

语言模型可以覆盖更多表达,但输出具有不确定性;小模型可本地、稳定运行,但受标签和训练数据限制;规则透明,却覆盖有限。实际选择要考虑任务规模、隐私、离线要求、错误后果和维护能力。

第三节 测试歧义、遗漏、冲突与抽取边界

一、建立固定失败测试集

类型 家庭事务文本示例 要暴露的问题 期望行为
正常 日期、时间、地点和行动明确 主流程是否完整 输出候选并保留原文
边界 “周五晚饭后”“上门地点另行确认” 相对或待定表达 不擅自变成绝对值
陌生 新活动名称、少见专业词、混合类型 标签集合是否足够 显示概率,必要时unknown
故障 文本为空、编码损坏、模型文件不可用 能否安全停止 保留输入并说明原因
冲突 原消息写周三上门,补充消息写改到周四 是否覆盖旧信息 并列保留,交给人确认

否定表达需要单独测试。例如“旧物交换活动不在社区活动室举行,地点另行通知”不能抽取为place=社区活动室。又如“原定周三的上门服务取消,周四是否改约待定”,程序不能只抓住“周三”和“周四”就生成两个确定事项。词元和模型能处理文字模式,但不保证完整理解事务状态。

遗漏也不能通过猜测补齐。若消息未写联系人,候选字段保持空值;若只写“下午”,就保留原文并标记具体时间未明确。模型输出整齐的表格,不意味着这些字段都来自原文。

受控小案例:建立家庭健康资料与就医准备助手。 这一案例只复用“从原文抽取候选字段并保留证据”的方法,不把上面的四分类模型改造成医疗模型。课堂使用下面两条完全虚构、已经脱敏的材料:

[H-01|2026-05-12|第1页] 家庭观察记录:体温37.2 ℃;未出现皮疹。
[H-02|2026-05-13|第2页] 模拟用药清单:模拟药A,每次10 mg,每日1次;是否继续使用待向医生确认。

候选表应把2026-05-12、37.2与℃一起保存,并用来源锚点H-01|第1页支持这些字段;“未出现皮疹”必须保留否定词,不能改写成“出现皮疹”。10 mg不能丢失单位,且“是否继续使用”只能进入待确认项并指向H-02|第2页。系统不得依据这些片段判断疾病、推荐或停用药物,也不得判断是否为急症;这些决定必须交给医生或相应专业人员。若日期、单位、否定词或来源锚点缺失,整条结果保持待核对,不进入确定清单。

二、根据错误限定修改

固定测试中,规则把“旧物交换活动不在社区活动室举行”抽取成了社区活动室。修改请求应指出真实错误和允许范围:

错误样例:原文“旧物交换活动不在社区活动室举行,地点另行通知”。
实际结果:place被填为“社区活动室”。
期望结果:place为null,并增加“地点待定”。
只允许修改:extract_fields中的地点抽取与否定检查。
禁止修改:训练数据、分类模型、原始文本和其他测试。
完成后:重跑正常地点、地点待定、否定地点、多个地点和空文本样例。

开发与学习AI可能建议更换更大的语言模型或重写整个项目,但这不能直接证明问题得到解决。应先审查最小代码差异,再使用原测试集回归。修复否定句后,正常的“地点:社区活动室”仍应正确抽取,不能因规则过严而全部变成空值。

三、形成抽取边界和使用判断

判断卡要记录:事务文本来源和标签范围;训练与测试是否独立;哪些字段由规则抽取,哪些由模型提出;相对日期、否定、取消和冲突怎样处理;谁在行动或日历写入前回看原文并确认预览;模型或文本损坏时如何停止。

合理使用方式是:对低风险、来源明确的家庭事务文本生成分类和候选清单,帮助发现遗漏;任何日期、时间、地点和取消状态必须回到原文核对;相对时间缺少基准、消息互相冲突、字段不足或模型低置信度时不进入确定计划。少量消息直接人工阅读更快;格式固定时表单和规则可能优于模型。家庭健康资料只能按上述受控方式整理来源明确的候选字段,不能把文本整理升级为医疗判断。

本章小结

自然语言处理把文字转换成机器可以处理的词元和数值表示,再完成分类、抽取或生成等任务。文本分类为整段家庭事务文本选择候选类别,信息抽取寻找日期、时间和地点等字段,语言生成根据上下文产生新文字。结构化输出便于检查,但不自动保证事实正确。

关键词规则、小文本模型和通用语言模型各有能力边界。应用中的AI可以提出类别与字段候选,开发与学习AI协助构建和修订程序,人负责标签、原文证据、冲突处理和最终行动。歧义、否定、遗漏、陌生表达与故障测试,使“机器处理文本”从一次成功演示变成可判断的技术实践。

关键术语

目标测试

一、单项选择题

  1. 把整段家庭事务文本判断为“活动”“截止事项”等类别,属于( )。
    A.文本分类 B.图像分割 C.语音合成 D.设备控制
  2. TextVectorization只在训练文本上建立词表,主要是为了( )。
    A.增加文本长度 B.避免验证和测试信息进入训练过程 C.替代人工标签 D.自动补写地点
  3. 原文写“地点另行通知”时,最合适的输出是( )。
    A.根据往年活动填写礼堂 B.删除通知 C.地点保持空值并标记待确认 D.让模型随机选择
  4. 下列关于关键词规则、小模型和通用语言模型的说法,正确的是( )。
    A.规则能理解所有同义表达 B.小模型不需要标签 C.通用语言模型输出无需核对 D.三者应在同一测试集上比较任务结果

二、判断并改错

  1. 通用语言模型输出了合法且整齐的JSON,说明其中每个字段都一定来自家庭事务原文。请判断并改正。
  2. 测试集中的家庭事务文本只要没有参加模型参数训练,就可以反复用于调标签和选模型,仍算独立测试。请判断并改正。

三、简答与操作题

  1. 说明词元、整数编号、嵌入表示和类别概率之间的关系。
  2. 对消息“原定周三下午的社区讲座取消,是否改到周四另行通知”分别给出分类候选、可抽取字段和必须人工确认的内容。
  3. 设计一次规则、小文本模型和通用语言模型的对照实验,写出固定项、评价项和结论边界。
  4. 对模拟材料“[H-01|2026-05-12|第1页] 体温37.2 ℃;未出现皮疹”设计候选抽取结果,写出来源锚点、日期、数值与单位、否定信息、待确认项和禁止输出。

答案编号:A1-6-01—A1-6-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第7章 嵌入与语义空间:让机器比较含义

本章技术主题: 嵌入表示与语义检索。
本章技术实验: 把问题和资料片段变成向量,比较关键词与语义近邻。
家庭项目: “建立家庭维修与说明书助手”是主项目;循环利用规则检索是限定拓展。
跨章位置: 来源编号和检索证据可交给第9章故事盒与第12章综合系统继续使用。
技术主线: 问题和资料片段 → 嵌入模型 → 向量 → 相似度 → 候选证据 → 人工核对。

学习目标

学完本章,你将能够:

  1. 说明嵌入向量、维度、距离和语义空间之间的关系;
  2. 运行一个“编码—比较—排序—回到原文”的语义检索实验;
  3. 比较关键词基线与嵌入检索,解释各自容易漏掉和误选什么;
  4. 用相似但无关、相似型号、资料缺失和高风险维修等样例判断检索结果能否交给生成模型。

项目导入

关键词检索要求问题与资料使用相同词语。用户问“设备为什么一直发出短促提示音”,说明书却可能写“蜂鸣器间歇响起”;两段文字含义接近,但共同词很少。嵌入模型把问题和资料片段都转换为数值向量,使系统可以比较语义,而不只比较字面重合。

本章使用教师提供的模拟说明书,建立家庭维修与说明书助手。输出不是维修结论,而是若干候选段落及其文件、产品型号、资料版本、页码、章节、片段编号和相似度。学生必须打开对应页回到原文确认。资料没有命中,或者涉及带电拆装、高温、高压、燃气、结构安全等高风险维修时,系统停止生成操作步骤并提示转交专业人员。

本章成果

考点提示

嵌入向量不是文字摘要,单个维度通常没有可直接命名的含义;相似度高表示模型认为表示接近,不保证事实相关。关键词检索和嵌入检索都是候选召回方法。检索增强生成只是把命中材料继续交给生成模型,不能把错误材料变成正确证据。

第一节 把问题和资料片段变成向量

一、先建立可核对的资料单元

检索前先把说明书划分成能够独立核对的片段。片段不能只剩半句话,也不宜把几十页合成一段。每个片段至少保存文件名、产品型号、资料版本、页码、章节、片段编号、正文。问题也保留原句,并单独填写用户正在查询的产品型号;不先让生成模型替用户改写成唯一版本。

片段切分会直接改变检索对象。按固定字数硬切,可能把“故障现象”和下一页的“处理限制”分开;整章作为一片,又会让许多无关句子共享同一向量。课堂先按自然段切分,再把标题附加到每段开头,使短句保留所属主题。若采用相邻重叠,要在评价时去除重复候选,避免同一段落占满前三名。

例如资料包含三段:

编号 型号 版本 页码 正文
M-01 HX-100 2026.1 12 指示灯连续闪烁时,请检查上盖是否完全闭合。
M-02 HX-100 2026.1 18 蜂鸣器间歇响起表示水箱未安装到位。
M-03 HX-100 2026.1 26 清洁外壳前应断开电源,不得将主机浸入水中。

问题“为什么一直发出短促提示音”与M-02没有相同的核心名词,却可能在语义空间中接近。首轮实验同时运行关键词基线和嵌入检索,避免把新方法的结果孤立展示。

二、与开发与学习AI共同准备实验

应用中的AI是把文字编码成向量的嵌入模型。开发与学习AI协助检查切分规则、生成Notebook和解释向量形状。学生负责资料版本、片段边界、标准答案与停止条件。可使用下面的任务说明:

任务:对同一批说明书片段比较关键词检索和嵌入检索。
输入:segments.csv与questions.csv,均为模拟资料并带来源编号。
输出:每个问题的前三个候选、相似度、型号、版本、页码、来源和是否命中标准片段。
限制:不生成维修动作;找不到证据时明确返回“资料不足”。
实验:只改变候选数量,比较命中和无关材料增加情况。
请解释:文本怎样变成向量,向量形状是什么,相似度在哪里计算。

开发与学习AI可以提出片段重叠等方案,却不能一边改片段,一边用同一批问题证明检索改善。标准问题和期望片段应在调整前封存一部分作为测试。

实际Notebook还应建立资料索引表。模型向量只保存数值,索引表保存向量行号与片段编号、产品型号、资料版本和页码之间的对应关系。更新说明书后要重新编码受影响片段,并记录所用嵌入模型版本;不能把旧向量与新正文错位。文件读取、编号检查和版本筛选是普通程序,不是嵌入模型学到的能力。

三、观察第一组检索结果

先查询“上盖没扣好会怎样”。关键词基线与嵌入检索都能找到M-01。再查询“短促提示音是什么原因”,关键词基线可能没有命中,嵌入检索把M-02排在第一。第三个问题“机器声音很大正常吗”与M-02共享“声音”含义,却未必询问蜂鸣提示;若M-02仍排第一,这就是“相似但无关”的首个失败样例。

记录时同时保存前三名,不只保存第一名。学生要核对:候选是否真的回答问题;来源是否属于正确型号和版本;页码能否打开并定位原句;相似度接近时是否应继续追问;资料不存在时系统能否停止。

第二节 用距离与近邻完成语义检索

一、从坐标位置理解嵌入

可以先把“蜂鸣”“提示音”“上盖”“水箱”想象成一维轴上的点;一维只能表达一种关系。增加第二个维度后,模型可以同时表达更多关系,例如“声音事件”和“设备部件”。真实嵌入通常有数百个维度,图上只能投影其中一小部分。

嵌入模型接收文字,输出固定长度向量。语义相近的文字通常方向更接近。余弦相似度比较两个向量夹角,取值越大通常表示方向越接近:

[ \operatorname{cos}(q,d)=\frac{q\cdot d}{\lVert q\rVert\lVert d\rVert} ]

式中的(q)是问题向量,(d)是资料向量。公式负责比较数值,不理解型号、日期或安全后果。最近邻检索是在所有资料向量中找距离最近的若干个。聚类则把彼此接近的资料先分组,适合观察资料主题,但聚类标签仍需人解释。

模型内部先把词元转换为向量,再综合词序和上下文,最后形成句子或片段表示。两个嵌入模型即使维数相同,坐标含义也不相同,问题向量和资料向量必须来自同一个兼容模型。不能用模型A编码资料、模型B编码问题后直接计算距离。向量维数更高也不自动更准确,评价仍要回到本书的实际问题集。

二、运行透明的向量排序代码

下面的教学数值已经由同一个嵌入模型生成并缩减到三维,只用于看清排序计算。规划中的数字资源Notebook应调用实际嵌入模型得到完整向量,再执行相同的比较步骤。

import numpy as np

ids = ["M-01", "M-02", "M-03"]
vectors = np.array([
    [0.10, 0.91, 0.22],  # 上盖与指示灯
    [0.92, 0.18, 0.25],  # 蜂鸣提示音
    [0.08, 0.24, 0.95],  # 断电与清洁
], dtype="float32")
query = np.array([0.88, 0.20, 0.18], dtype="float32")

def normalize(x):
    return x / np.maximum(np.linalg.norm(x, axis=-1, keepdims=True), 1e-9)

scores = normalize(vectors) @ normalize(query.reshape(1, -1)).T
ranking = np.argsort(-scores[:, 0])
for index in ranking:
    print(ids[index], round(float(scores[index, 0]), 3))

矩阵vectors的形状是3×3,代表三个片段、每个三个教学维度;查询形状为1×3。归一化后做点积,结果就是余弦相似度。实际向量维数更高,但“同一模型编码—归一化—比较—排序”的主线不变。

规划中的完整Notebook应在这段排序代码前增加真实编码,在后面增加来源回查。运行顺序应清楚显示:读取片段;批量调用嵌入模型;检查输出形状和有限数值;归一化并保存向量;编码问题;计算全部相似度;按型号等硬条件过滤;返回片段正文与来源。硬条件过滤不应交给相似度猜测,例如型号完全不符时即使分数高也要排除。

下面给出一个可以从文字训练到向量检索的微型版本。六组正负样例让共享编码器学习“提示音—蜂鸣器”等关系。它只用于展示参数怎样由配对样本调整,不能替代在大规模语料上训练的通用嵌入模型。课堂先运行代码,再让开发与学习AI逐行说明encoder为什么在问题和资料两边共享。

import numpy as np
import tensorflow as tf
from tensorflow import keras

tf.keras.utils.set_random_seed(42)
segments = np.array([
    "指示灯连续闪烁时检查上盖是否完全闭合",
    "蜂鸣器间歇响起表示水箱未安装到位",
    "清洁外壳前应断开电源不得浸入水中",
])
train_query = np.array([
    "短促提示音是什么原因", "提示音一直响",
    "盖子没有扣好会怎样", "上盖打开",
    "怎样清洗主机", "可以把主机泡水吗",
    "短促提示音是什么原因", "上盖打开", "怎样清洗主机",
])
train_doc = np.array([
    segments[1], segments[1], segments[0], segments[0],
    segments[2], segments[2], segments[0], segments[2], segments[1],
])
labels = np.array([1, 1, 1, 1, 1, 1, 0, 0, 0], dtype="float32")

vectorizer = keras.layers.TextVectorization(
    standardize="lower_and_strip_punctuation",
    split="character",
    output_mode="int",
    output_sequence_length=24,
)
vectorizer.adapt(np.concatenate([segments, train_query]))
vocab_size = len(vectorizer.get_vocabulary())

text = keras.Input(shape=(), dtype=tf.string)
x = vectorizer(text)
x = keras.layers.Embedding(vocab_size, 24, mask_zero=True)(x)
x = keras.layers.GlobalAveragePooling1D()(x)
embedding = keras.layers.Dense(16)(x)
encoder = keras.Model(text, embedding)

query_input = keras.Input(shape=(), dtype=tf.string)
doc_input = keras.Input(shape=(), dtype=tf.string)
similarity = keras.layers.Dot(axes=1, normalize=True)(
    [encoder(query_input), encoder(doc_input)])
probability = keras.layers.Rescaling(0.5, offset=0.5)(similarity)
pair_model = keras.Model([query_input, doc_input], probability)
pair_model.compile(optimizer="adam", loss="binary_crossentropy")
pair_model.fit([train_query, train_doc], labels, epochs=80, verbose=0)

doc_vectors = encoder(segments, training=False).numpy()
doc_vectors /= np.maximum(np.linalg.norm(doc_vectors, axis=1, keepdims=True), 1e-9)

# 这些封存改写不参与词表适配和配对训练。
test_questions = np.array([
    "设备为何发出短促声音",
    "盖子没关严会出现什么现象",
    "主机可以直接用水冲洗吗",
])
expected = np.array([1, 0, 2])
encoded = vectorizer(test_questions).numpy()
unknown_id = vectorizer.get_vocabulary().index("[UNK]")

for question, token_ids, expected_index in zip(test_questions, encoded, expected):
    used = token_ids[token_ids != 0]
    unknown_ratio = np.mean(used == unknown_id) if len(used) else 1.0
    query_vector = encoder(np.array([question]), training=False).numpy()[0]
    query_vector /= max(np.linalg.norm(query_vector), 1e-9)
    semantic_scores = doc_vectors @ query_vector
    literal_scores = np.array([
        len(set(question) & set(doc)) / max(len(set(question) | set(doc)), 1)
        for doc in segments
    ])
    top_index = int(np.argmax(semantic_scores))
    print("问题:", question)
    print("非零词元数/OOV比例:", len(used), round(float(unknown_ratio), 3))
    print("期望/实际Top-1:", int(expected_index), top_index)
    for index in np.argsort(-semantic_scores):
        print(index, round(float(semantic_scores[index]), 3),
              round(float(literal_scores[index]), 3), segments[index])

TextVectorization在这里显式采用split="character",把没有空格的中文句子按字符切分,再转换为词元编号;若沿用默认的空格切分,整句中文很可能只得到一个词元,这个微型实验就只会记忆整句。Embedding和全局平均池化形成初始表示,Dense层输出16维向量。正样例推动配对向量接近,负样例推动它们分开。最后一段对三条未参与训练的改写同时报告OOV比例、语义分数与字面字符重合基线。这仍只是字符级配对表示教学模型,不是通用中文句向量模型;封存题答错时应记录失败并扩充训练配对,不能把期望答案改成模型的输出。

运行后还要观察同一句资料在重新训练后是否移动。训练随机性和样本选择可能改变坐标,因而向量文件应与模型版本一起保存。只保存向量而丢失编码模型,后续查询就无法在同一空间中比较。

三、只改变候选数量

把候选数量从1改为3,其他条件不变。候选更多可能提高“正确片段至少出现一次”的机会,也会增加无关片段和人工核对量。实验用十个封存问题记录:前1名命中数、前3名命中数、每题无关片段数和核对时间。

若前1名命中6题,前3名命中8题,同时无关片段从4条增加到18条,就不能只宣称“召回提高”。候选数量不是越多越好,需要结合材料长度、错误后果和生成模型能处理的上下文决定。

可以用“前k名命中数”评价召回,用“第一个正确片段平均排在第几位”观察排序,也要逐条看错误。十个问题中命中八个,比只写80%更能提醒读者样本很少。若两个问题失败原因都是片段切分,应调整切分;若失败来自型号字段缺失,应修数据;不能把所有错误都归咎于嵌入模型。

第三节 测试相似但无关的文本并限定检索后生成

一、建立语义检索失败集

正常样例使用同义表达询问资料中明确存在的内容;边界样例含“它”“那个声音”等指代不清表达;陌生样例询问另一型号或资料中没有的功能;故障样例包括空文件、向量维度不一致和嵌入模型不可用。还要专门加入“词语很像但任务不同”的干扰段落,以及相似型号反例:问题明确询问HX-100,资料库中的HX-100S段落虽然文字更相似,也不得越过型号字段进入有效候选。

相似度阈值只能减少低分候选,不能判断高分候选一定正确。正确通过标准应同时要求:命中期望片段;型号和版本匹配;页码与来源可打开;资料不足时停止。若正确型号的资料没有任何片段回答问题,系统应输出“资料不足”,不得借用相似型号或语言模型常识补写。对于高风险维修,即使检索正确,也只能展示来源、提示查阅专业资料或转交专业人员。

还应测试问题改写的影响。同一含义分别写成口语、书面语和含错别字的版本,观察正确片段排序是否稳定。若系统只能接受一种写法,可让模型产生多个查询候选,但必须保留原问题,并把“查询扩展”作为独立变量测试。查询扩展可能找回同义表达,也可能把原问题带向错误主题。

二、理解检索后生成的边界

把检索到的片段连同来源交给生成模型,请它在材料范围内整理答案,常被称为检索增强生成。这里增加的是生成环节,不会重新训练嵌入模型。生成模型可能忽略证据、混合不同片段或补写材料中不存在的步骤,因此输出仍要逐句回查来源。

本章只要求实现“检索结果+限定生成”的最小连接,不要求搭建复杂平台。若固定模板已经能把命中片段展示清楚,就可以不用生成模型。开发与学习AI可协助写“没有证据便停止”的测试,但学生要亲自用不存在的故障代码验证。

三、形成检索使用判断

判断卡应说明:资料集合、型号、版本和页码;片段切分方法;关键词与嵌入各自的命中情况;候选数量和核对成本;相似但无关、相似型号材料的错误;资料不足、型号冲突和高风险内容的停止条件。

适合采用的范围是:资料封闭、来源明确、问题以同义表达为主、输出只做候选证据。若资料很少且术语固定,关键词检索可能更透明;若必须精确匹配编号,普通字符串检索更可靠;若材料不完整,增加生成模型不会补回缺失事实。

拓展任务可以把相同方法用于“家庭物品整理与循环利用”中的规则检索:只从所在地区、注明发布日期和适用范围的官方分类指引中查找候选条款,并返回来源页码或网页锚点。电池、药品、化学品、带电设备等高风险物品不得根据语义相似度自行决定处理方式;规则缺失、地区不符或已过期时停止并转交当地专业渠道。这个拓展学习的是资料检索,不把嵌入模型写成物品识别或政策判断模型。

本章小结

嵌入把文字映射为向量,使语义关系能够通过距离比较。最近邻负责找候选,余弦相似度只是排序数值,不是事实证明。片段来源、型号、版本和页码仍由普通数据字段和人工核对保证。

关键词和嵌入各有适用范围。控制候选数量、测试相似但无关文本,并让“无证据”成为合法输出,才能把语义检索从演示变成可判断的方法。检索后可以连接生成模型,但生成不能修复错误或缺失的证据。

关键术语

目标测试

一、单项选择题

  1. 嵌入模型的直接输出通常是( )。A.维修结论 B.固定长度向量 C.文件权限 D.人工标签
  2. 余弦相似度主要比较两个向量的( )。A.文件大小 B.方向 C.来源日期 D.安全等级
  3. 候选数量从1增至5通常会( )。A.一定更正确 B.候选和核对量增加 C.重新训练模型 D.消除资料缺失
  4. 检索结果交给生成模型后,最重要的操作是( )。A.删除来源 B.逐句核对证据 C.提高字数 D.隐藏低分结果

二、判断并改错

  1. “相似度最高的片段一定能正确回答问题。”请改正。
  2. “检索增强生成会自动把新资料训练进语言模型参数。”请改正。

三、简答与操作题

  1. 用“问题—向量—相似度—候选—型号/版本/页码—原文”说明一次语义检索。
  2. 设计一条“字面不同但含义相近”和一条“相似型号但不适用”的测试。
  3. 比较关键词检索与嵌入检索各自适合的资料和问题。
  4. 候选数量增加后命中改善但无关片段明显增多,应如何形成判断?

答案编号:A1-7-01—A1-7-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第8章 大语言模型:理解生成、上下文与核验

本章技术主题: 大语言模型的逐词元生成、上下文使用与事实核验。
本章技术实验: 围绕一份可信短材料建立讲解、提问、反馈和迁移测试。
家庭项目: “设计一个真正帮助学习的AI伙伴”是主项目;健康资料只作有证据与无证据回答的边界对照。
跨章位置: 本章把语言模型本身作为研究对象,第12章再限定它在多模型系统中的解释与协调角色。
技术主线: 任务和上下文 → 词元 → 下一词元概率 → 逐步生成 → 证据核对 → 学生判断。

学习目标

学完本章,你将能够:

  1. 用通俗语言说明词元、上下文、下一词元预测和逐步生成;
  2. 区分预训练形成的参数、当前对话中的上下文和一次推理输出;
  3. 通过改变上下文或示例,观察回答、练习题和反馈怎样变化;
  4. 用固定证据测试幻觉、稳定性和学习迁移,决定AI学习伙伴的适用范围。

项目导入

大语言模型能够解释概念、生成题目、模拟对话和修改文字,但它产生的是在当前上下文中可能的词元序列。语言流畅说明生成连贯,不等于事实正确,也不等于学习者已经理解。本章以“设计一个真正帮助学习的AI伙伴”为正式实践,选用一篇教师确认的短材料和一个明确学习目标,构建“讲解—提问—学生作答—反馈—迁移题”的最小闭环。

实践不要求学生搭建模型服务,也不把提示词技巧当作核心。重点是观察:同一个模型怎样使用上下文;材料缺失时为什么仍可能回答;示例怎样改变输出形式;生成结果怎样回到原文和学习表现进行验证。涉及成绩、心理、健康或其他高代价判断时,作品只提供学习辅助,不代替教师或专业人员。

本章成果

考点提示

大语言模型在推理时逐步预测后续词元,不是从数据库取出一段固定答案。继续对话通常是在增加上下文,不是重新训练模型。温度等采样设置影响输出随机性,却不能把错误变成事实。结构化输出约束格式,外部检索和工具提供信息或动作,但都需要独立验证。

第一节 围绕学习目标建立可验证的对话

一、先确定学习成果而不是聊天次数

选择一个小而明确的目标,例如“能够解释训练集、验证集和测试集的分工,并判断一条数据泄漏案例”。可信材料控制在两页以内,先由教师确认。学习成果不是“与AI对话五轮”,而是学生能够在不看示例的情况下完成一条新题并说明理由。

最小学习流程包含五步:模型依据材料给出简短解释;模型提出一题;学生先回答;模型依据材料反馈;学生完成一条表达不同但考查相同概念的迁移题。每一步都保留学生原答,不能让模型直接覆盖。

二、让两个AI角色保持清楚

应用中的AI是实际与学生对话的大语言模型。开发与学习AI可以是同一个模型的另一轮会话,用于帮助设计任务说明、测试题和记录表。为了避免它自己出题、自己判定、自己证明成功,教师或学生要预先确定可信材料、标准要点和一部分封存测试。

学习目标:区分训练集、验证集和测试集,并识别数据泄漏。
可信材料:只使用我提供的两段教材内容;引用时标出段落编号。
流程:先解释一个概念,再出一题;等待我作答后才反馈;最后给迁移题。
限制:材料没有说明时回答“材料不足”;不得推测我的能力、性格或成绩。
验收:解释可回到材料;反馈指出证据;迁移题与示例表述不同。

任务说明不是越长越好。每项约束都应对应一种可能失败:限定材料防止随意扩展,等待学生作答防止代做,段落编号支持核对,“材料不足”给模型停止出口。

三、保留首轮对话中的问题

首轮可同时记录四列:模型输出、使用的材料、学生核对、后续处理。常见问题包括引用不存在的段落、在学生作答前泄露答案、把“回答与示例相似”误判为真正理解,以及在材料缺失时仍给出自信解释。

首轮只选一个问题进入第二节实验。例如模型没有给行为示例时,反馈总是长篇讲解;加入一个“先指出证据,再给一个提示,不直接给答案”的示例后,输出是否更符合目标?其他模型、材料和采样设置保持不变。

第二节 从词元、上下文到下一词预测

一、模型不是把整句话一次写完

文字进入模型前先被切分为词元并转换为编号。词元可能是一个字、词的一部分、标点或特殊符号。模型根据当前已有词元计算下一词元的概率分布,选出一个词元,把它接到序列后,再重复计算,直到遇到结束条件。

编号先经过嵌入层变成向量,并加入位置信息。Transformer中的注意力机制让当前位置根据上下文中其他位置形成新的表示,前馈网络继续变换这些表示;许多层反复处理后,输出层为词表中的候选计算分数。分数经归一化形成下一词元分布。学生不需要计算大型矩阵,但应能沿“词元编号—向量—上下文交互—候选分数”指出信息怎样变化。

注意力权重只能说明模型在一次计算中怎样组合位置,不自动等于人类解释或因果证据。某个词元获得较高权重,也不能证明它就是事实来源。模型参数中的知识分散在大量数值中,无法像说明书页码一样直接回查,因此重要事实仍需要连接外部材料。

假设上下文是“测试集应当在模型设置确定后再”,候选概率可能是“使用0.62、训练0.08、删除0.04、公开0.03”。模型选择“使用”后,再根据更长序列预测下一项。真实模型的候选远多于这个教学表,但逐步生成的机制相同。

下一词元概率不是整段回答的“正确率”。一段回答由许多步生成,局部高概率只说明某个词元在语言模式上自然;它可能组成流畅但错误的结论。面向使用者显示一个总置信度,也不能自动解决事实核验问题。

预训练让模型参数学到大量语言模式;指令调整让它更善于遵循任务形式;当前对话把材料、要求和示例放入上下文。把一篇新材料贴进对话通常不会永久修改模型参数。关闭会话后模型是否保留信息,取决于具体系统设计和隐私设置,不能用“它已经学会”笼统描述。

模型能处理的上下文长度有限。达到上限时,系统可能拒绝输入、截去部分内容或压缩历史;不同应用处理方式不同。对话很长时要重新提供关键材料和当前任务,并用固定问题检查早期约束是否仍有效。单纯把所有资料一次塞入上下文,会增加费用和干扰,不保证模型同等重视每一段。

二、上下文怎样影响生成

模型在生成每个新词元时会综合可见上下文,但可见不等于同等重视。材料过长、要求冲突或关键证据位置不清,模型可能遗漏。明确分隔“材料”“任务”“输出要求”,给出可核对的段落编号,比不断添加形容词更有效。

示例会向模型展示期望行为。例如:

学生回答:测试集可以用来挑选最好的模型。
合格反馈:这会让测试集参与选择。请回看材料P2,说明验证集和测试集应分别在什么时候使用。
不合格反馈:你的回答不够准确,随后直接给出完整答案。

示例改变的是本次上下文中的生成条件,不等于重新训练。若示例本身错误,模型也可能稳定模仿错误。结构化输出可以要求模型按“判断、证据、提示”三个字段回答,便于普通程序检查字段是否缺失,但字段齐全仍不保证事实正确。

三、只改变是否提供行为示例

准备五条固定学生回答,两次运行使用相同材料、任务和采样设置。A版只描述“给反馈”,B版增加一条合格反馈示例。比较四项:是否等待学生作答、是否引用正确段落、是否直接泄露答案、反馈长度是否适合继续思考。

生成具有随机性,A、B两版都应至少重复三轮,并交替运行顺序;记录日期、模型版本和采样设置。若只让A运行一次、B运行一次,差异可能来自随机采样或服务更新,不能全部归因于示例。比较时先看五条问题上的具体行为,再汇总通过次数。

如果B版五次都能先给提示,而A版只有两次做到,可以说明示例在当前材料和测试上改善了行为。不能据此宣称模型已经理解教学法。还应检查副作用,例如模型是否机械套用示例句式,或面对完全正确的回答仍要求修改。

温度可以作为拓展变量。较低温度通常使高概率词元更常被选择、输出较稳定;较高温度增加多样性,也可能增加偏离。温度不控制事实真伪,事实任务仍要依赖材料和核验。

第三节 用证据测试幻觉、稳定性与适用边界

一、建立固定测试而不是凭感觉聊天

正常样例的问题能由材料直接回答;边界样例需要连接两段证据;陌生样例询问材料未涉及的概念;故障样例包括材料为空、段落编号缺失、上下文被截断和模型不可用。再加入诱导题:“材料虽然没写,请根据常识补全作者结论。”合格行为是区分材料事实与额外知识,或明确停止。

幻觉是输出包含缺少依据、与证据冲突或虚构来源的内容。它不只表现为荒诞句子,最危险的往往是合理而细微的日期、数字、引用和因果错误。核验至少包括:关键事实能否回到原文;引用编号是否存在;模型是否把推测写成事实;不同运行是否改变核心结论。

稳定性测试不能只连续重复同一句。应把同一问题换一种表达、调整无关句子的位置,或在上下文中加入一条明确但容易忽略的限制,再观察核心结论。若细小改写就改变关键事实,系统应降低采用范围;若措辞变化但证据和结论稳定,只能说明当前任务下行为较稳定,不能推广到未测试领域。

还可以使用一条完全模拟、已经脱敏的家庭健康资料做高代价对照,但目的只是在相同模型上比较“有证据回答”和“无证据回答”,绝不评价医疗能力。例如材料[H-01] 2026年5月12日记录:体温37.2 ℃,未出现皮疹能够支持“记录中的体温是多少、是否记录皮疹”这类原文问题,回答必须引用H-01并保留否定词;材料不能支持“是什么疾病”“是否停药”或“是不是急症”。面对后一类问题,合格输出只能说明材料不足并建议向医生或相应专业人员确认,不得诊断、推荐或停用药物,也不得作急症判断。有证据和无证据两组输出使用同一模型、同一材料和同一格式要求,评价模型能否守住来源边界,而不是比较哪段话更像医疗建议。

二、区分模型表现与学生学习

模型回答正确,只能说明本次输出通过核对。要判断学习效果,学生需要在没有原示例和模型答案的情况下完成迁移任务,例如判断一个新的数据划分方案是否泄漏,并解释理由。学生能够复述模型措辞但不能处理新案例,不能算掌握。

大语言模型可以调用检索、计算器或其他工具。工具调用把一部分任务交给外部软件:检索提供候选材料,计算器执行确定计算,日历或文件工具可能产生动作。模型仍可能选择错工具、传错参数或误读结果。涉及写入和设备动作时应先预览并由人确认。本章只观察工具结果,不自动执行外部动作。

比较云端与本地模型时应使用同一材料和测试,记录事实错误、响应时间、上下文限制、数据是否离开设备以及运行成本。参数规模不是唯一依据,小模型在范围窄、资料明确的任务中可能够用;大模型能力更广,也可能带来更高费用和数据外传。纸书不指定产品,规划中的数字资源应按日期维护可选模型清单。

三、形成学习伙伴使用判断

判断卡分别记录三层证据:语言层是否清楚;事实层是否有正确来源;学习层学生是否能完成迁移。还要说明材料范围、最长有效对话、最不能接受的错误、何时交给教师以及是否允许保存学习记录。

适合采用的范围可以是:围绕教师确认材料进行低风险解释、出题和形成性反馈;材料不足或引用冲突时停止;学生答案、成绩和个人信息不用于未经说明的用途。若目标只是查找原文,第7章的检索可能比生成更直接;若答案必须完全一致,固定题库与规则可能更可靠。

本章小结

大语言模型把文字切为词元,根据参数和当前上下文逐步预测后续词元。预训练、当前对话和一次推理是不同过程。示例、材料和采样设置会改变输出,却不能自动保证事实正确。

学习伙伴的价值不在持续聊天,而在形成可验证的学习活动。材料证据、固定幻觉测试和学生迁移表现分别评价不同问题。模型可以解释和反馈,学生与教师仍负责学习目标、事实核对和使用决定。

关键术语

目标测试

一、单项选择题

  1. 大语言模型生成文字的基本过程是( )。A.整篇复制 B.逐步预测后续词元 C.只查数据库 D.修改用户文件
  2. 把新材料放入当前对话通常改变的是( )。A.芯片 B.上下文 C.永久参数 D.训练集标签
  3. 结构化输出首先改善的是( )。A.格式可检查性 B.事实必然正确 C.模型规模 D.材料授权
  4. 判断学生是否真正掌握,较有力的证据是( )。A.聊天轮数 B.迁移题表现 C.回答字数 D.模型语气

二、判断并改错

  1. “降低温度就能消除事实错误。”请改正。
  2. “模型正确回答一道题,说明学生已经学会。”请改正。

三、简答与操作题

  1. 用一条短句说明模型如何连续生成三个词元。
  2. 设计一条材料中没有答案的幻觉测试,并写出合格行为;若问题涉及健康等高代价信息,还应说明禁止输出什么。
  3. 比较有无行为示例时,应固定哪些条件、记录哪些结果?
  4. 为一个AI学习伙伴写出事实层和学习层各两项采用标准。

答案编号:A1-8-01—A1-8-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第三篇 创作有用又有趣的小作品

让模型生成和选择

第9章 生成式深度学习:从概率中创造新内容

本章技术主题: 自回归、潜空间与扩散模型的生成机制。
本章技术实验: 固定条件或随机种子,观察自回归、潜空间和扩散生成的变化。
家庭项目: “制作家庭记忆故事盒”是本章主项目;阳台植物科普卡是低隐私替代项目。
跨章位置: 接收第7章的来源编号与检索证据,交付带事实分层、授权和撤回记录的作品。
技术主线: 训练数据分布 → 条件与随机起点 → 生成模型逐步采样 → 候选作品 → 真实性与授权检查。

学习目标

学完本章,你将能够:

  1. 区分分类模型与生成模型,说明生成并非简单查表复制,同时可能记忆或近似复现训练样本;
  2. 用通俗语言比较自回归生成、潜空间生成与扩散去噪;
  3. 通过固定条件和随机种子,观察生成结果的可控性与多样性;
  4. 从条件符合度、伪影、事实分层、材料授权、撤回要求和发布说明判断作品能否使用。

项目导入

分类模型从预设类别中选择结果,生成模型则要产生新的文字、图像或声音。它学习训练数据中复杂的概率分布,再从这个分布中采样。相同条件可能得到不同作品,不同条件也可能得到相似结果。生成内容看起来完整,并不表示其中的文字、结构和事实都正确。

判别式模型关注“输入属于什么”或“数值是多少”,生成式模型关注“可能的数据样本怎样产生”。二者都可以使用深度神经网络,区别主要在学习目标和输出方式,不在于界面是否带有生成按钮。调用一个通用模型写宣传语属于生成应用;继续追问训练目标、概率分布和采样过程,才进入本章的技术本质。

本章的正式项目是“制作家庭记忆故事盒”,成品必须有来源。学生只使用家庭成员明确授权、已经脱敏的照片副本和口述文字,为每份材料建立来源编号;作品把“照片中可直接看到的事实”“讲述者的口述记忆”和“模型为连贯表达生成的连接想象”分层呈现,不能把三者混写成已经证实的历史事实。参与者可以在制作和展示阶段撤回材料,项目必须能按来源编号找到并移除相关内容。

为了观察图像生成的条件控制与随机性,封面实验不使用人物照片,而生成“旧收音机、两张旧车票、木桌、留白、无文字”的记忆物件画面;随后固定种子,把“两张车票”改为“三张车票”。项目不做人脸识别、身份推断、声音克隆或模仿亲属声线,也不把私人作品默认公开。原“阳台植物观察科普卡”保留为低隐私替代项目:不能取得家庭材料授权的学生,可用同样的条件、种子和采用判断完成植物卡。

本章成果

考点提示

生成模型学习的是数据模式和概率分布,不保存一套可检索的标准答案。自回归模型逐个产生后续元素;变分自编码器等模型在潜空间采样并解码;扩散模型从噪声出发逐步去噪。条件控制生成方向,随机种子帮助复现起点,但不能保证作品正确、原创或可发布。

第一节 用授权材料制作有来源的家庭记忆故事盒

一、把创作要求分成条件和验收

故事盒的材料先登记为三类:照片副本记录来源编号、授权范围和可见事实;口述文字记录讲述者、记录日期和允许使用范围;模型生成内容记录模型版本、条件和随机种子。姓名、住址、证件、联系方式、车牌、精确地点等无关信息先删除。照片中出现人物并不授权系统识别其身份;本项目优先裁取物件,不做人脸识别。

正文必须显式分层。例如“照片事实/P-01:桌面上有一台旧收音机”和“口述记忆/O-01:讲述者说它曾陪伴一家人听晚间节目”可以并列保留;“模型连接想象/G-01:旋钮转动时,房间仿佛又安静下来”只能标为创作性连接。生成条件不是历史事实来源,语言自然也不能把“仿佛”改写成“当年确实如此”。

封面条件分为三类。内容条件规定“旧收音机、两张旧车票和木桌”;形式条件规定横向4:3、低饱和插画和右侧留白;禁止条件规定不得出现人物、人脸、姓名、地址、号码、品牌、伪文字和未经授权角色。验收表检查物件数量、结构、留白、伪文字和相似风险。

二、与开发与学习AI共同设计最小实验

应用中的AI包括整理故事草稿的文字生成模型和生成无人物封面的图像模型。开发与学习AI帮助学生把模糊创意改成可比较的条件、建立来源与生成记录、解释采样参数和检查结果。学生负责取得授权、核对事实、标注想象、响应撤回以及决定是否展示。

作品:家庭记忆故事盒封面,横向4:3,不使用人物材料。
固定条件:低饱和插画,木桌上的旧收音机和两张旧车票,右侧留白。
禁止内容:人物、人脸、姓名、地址、号码、品牌、现成角色和自动生成文字。
实验A:条件和模型固定,只改变随机种子,生成4张。
实验B:种子和模型固定,只把“两张车票”改为“三张车票”。
记录:条件、种子、生成时间、物件数量、伪影、相似风险和是否采用。

学生不必手写模型代码,却要能在完整Notebook中找到条件怎样编码、随机噪声在哪里产生、模型怎样逐步更新样本以及图像怎样保存。规划中的数字资源应提供小规模教学模型和预训练推理两条路径;前者观察机制,后者完成作品。

两条路径不能混为一次实验。教学模型使用小规模授权数据,能看到训练损失和中间样本,但生成质量有限;预训练模型生成质量较高,学生通常看不到完整训练数据和训练过程。记录中应分别写“我们亲自训练了什么”和“我们调用了什么”,不能因会调用预训练模型就声称完成了大模型训练。

三、保留候选而不是只展示最好结果

第一次生成四张图,可能出现车票数量不符、收音机旋钮和刻度断裂、留白被占满或票面出现似字非字纹理。故事草稿还可能把口述内容改成照片事实,或擅自补出年代、地点和人物关系。全部候选、分层文本和参数都应保存;只展示最好的一份会隐藏模型的随机性,也无法检查事实层是否被改写。

首轮记录可以采用五项简单尺度:条件符合、结构完整、可编辑、事实分层、授权有效。每项用“通过、需修改、不通过”标记并写具体位置。评分不用于证明艺术价值,而用于比较同一任务的不同生成结果。

第二节 比较自回归、潜空间与扩散去噪

一、三种生成路线解决同一个问题

自回归模型把已经生成的部分作为条件,预测下一个词元、像素块或音频单元,再把新结果加入序列。它适合解释语言模型逐词元生成,也可用于图像和声音。前面生成的错误会成为后续条件,因此可能逐步累积。

自编码器先把输入压缩到较短表示,再从表示重建数据。变分自编码器让潜空间具有可采样的连续结构,可以在两个点之间插值,观察形状或风格平滑变化。潜空间不是人手设计的属性表,某个方向不一定能直接命名。

潜空间方法训练时既要求重建结果接近输入,也约束潜变量分布便于采样。压缩过强会丢失细节,约束过弱又可能形成难以连续采样的空洞。课堂通过在两个潜向量之间取若干中间点,观察生成图怎样渐变,而不是把潜空间解释为可随意拨动的“创意按钮”。

扩散模型训练时逐步向数据加入噪声,并学习预测噪声或去噪方向;生成时从随机噪声开始,多步去噪形成样本[20]。文字等条件会影响每一步朝什么方向更新。一次去噪不是“从图库找相似图”,而是模型根据学到的分布估计当前噪声样本应如何变化。

许多图像系统会先把图片压到潜空间,在较小的潜表示上扩散,再解码回像素,这称为潜扩散。它降低计算量,但不改变“逐步去噪采样”的核心。条件引导越强,结果通常越贴近文字,变化空间也可能减小,甚至出现结构僵硬或伪影;条件强度应像其他变量一样通过对照实验选择。

路线 起点 生成动作 课堂观察重点
自回归 已有序列或起始标记 预测下一个元素 顺序、条件和错误累积
潜空间 一个潜变量 解码为完整样本 插值和整体结构
扩散 随机噪声 多步去噪 随机起点和逐步形成

二、读懂扩散Notebook的完整链

规划中的教学Notebook应按六段组织:加载经授权的小图数据并归一化;随机选择噪声强度;把原图与噪声混合;让小型神经网络预测加入的噪声;依据预测误差训练;从固定种子噪声开始反复去噪并保存中间图。

训练时的目标不是判断图片类别,而是让预测噪声接近真实加入的噪声。若用(x_0)表示原图、(\epsilon)表示随机噪声、(t)表示噪声阶段,可以把加噪写成:

[ x_t=\sqrt{\bar{\alpha}_t}x_0+\sqrt{1-\bar{\alpha}_t}\epsilon ]

学生不需要推导公式,但要读出两件事:阶段较早时原图成分多,阶段较晚时噪声成分多;网络接收当前带噪样本和阶段信息,学习预测噪声。采样过程按多个阶段反向更新,最后形成候选图。

完整Notebook必须显示数据样例、带噪程度、训练损失、固定种子的中间去噪图和最终样本。只给一个生成按钮而隐藏这些步骤,不足以支撑本章学习。

训练损失下降表示模型在当前训练目标上改善,不等于作品质量、事实或授权已经合格。评价至少分三层:模型层观察损失和固定种子样本;作品层检查条件符合、结构和多样性;使用层检查事实、来源、风险和发布场景。三层结论不能用一个“生成成功”代替。

下面的微型条件扩散实验在二维点上完整展示“加噪—学习噪声—按条件去噪”。类别0的数据中心在左侧,类别1在右侧;类别独热向量作为条件输入。二维点不是故事盒封面图片,但训练和反向采样与更大扩散模型共享核心逻辑,几秒到数分钟即可观察。

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow import keras

tf.keras.utils.set_random_seed(7)
rng = np.random.default_rng(7)
count, steps = 2400, 30
labels = rng.integers(0, 2, size=count)
centers = np.array([[-1.2, 0.0], [1.2, 0.0]], dtype="float32")
clean = centers[labels] + rng.normal(0, 0.25, size=(count, 2))
clean = clean.astype("float32")

betas = np.linspace(0.0005, 0.05, steps, dtype="float32")
alphas = 1.0 - betas
alpha_bar = np.cumprod(alphas)
time_index = rng.integers(0, steps, size=count)
noise = rng.normal(size=clean.shape).astype("float32")
strength = alpha_bar[time_index, None]
noisy = np.sqrt(strength) * clean + np.sqrt(1.0 - strength) * noise
condition = np.eye(2, dtype="float32")[labels]
features = np.concatenate([
    noisy,
    (time_index[:, None] / (steps - 1)).astype("float32"),
    condition,
], axis=1)

denoiser = keras.Sequential([
    keras.layers.Input((5,)),
    keras.layers.Dense(64, activation="swish"),
    keras.layers.Dense(64, activation="swish"),
    keras.layers.Dense(2),
])
denoiser.compile(optimizer="adam", loss="mse")
history = denoiser.fit(features, noise, batch_size=64, epochs=35, verbose=0)

def sample(class_id, sample_count=200, seed=21):
    local_rng = np.random.default_rng(seed)
    points = local_rng.normal(size=(sample_count, 2)).astype("float32")
    snapshots = {"initial": points.copy()}
    class_condition = np.eye(2, dtype="float32")[
        np.full(sample_count, class_id)]
    for step in reversed(range(steps)):
        time_column = np.full((sample_count, 1), step / (steps - 1),
                              dtype="float32")
        model_input = np.concatenate(
            [points, time_column, class_condition], axis=1)
        predicted_noise = denoiser.predict(model_input, verbose=0)
        mean = (points - betas[step] / np.sqrt(1.0 - alpha_bar[step])
                * predicted_noise) / np.sqrt(alphas[step])
        if step > 0:
            previous = alpha_bar[step - 1]
            variance = betas[step] * (1.0 - previous) / (1.0 - alpha_bar[step])
            random_noise = local_rng.normal(size=points.shape).astype("float32")
            points = mean + np.sqrt(variance) * random_noise
        else:
            points = mean
        if step in {20, 10, 0}:
            snapshots[f"step_{step}"] = points.copy()
    return points, snapshots

left, left_steps = sample(0, seed=21)
right, right_steps = sample(1, seed=21)
assert np.allclose(left_steps["initial"], right_steps["initial"])
print("类别0生成中心:", np.round(left.mean(axis=0), 2))
print("类别1生成中心:", np.round(right.mean(axis=0), 2))
for name, points in left_steps.items():
    print("类别0", name, "中心:", np.round(points.mean(axis=0), 2))

preview_count = 400
view_step = 20
view_rng = np.random.default_rng(99)
view_noise = view_rng.normal(size=(preview_count, 2)).astype("float32")
fixed_noisy = (
    np.sqrt(alpha_bar[view_step]) * clean[:preview_count]
    + np.sqrt(1.0 - alpha_bar[view_step]) * view_noise
)

figure, axes = plt.subplots(2, 3, figsize=(12, 8))
for class_id, color in [(0, "tab:blue"), (1, "tab:orange")]:
    selected = labels[:preview_count] == class_id
    axes[0, 0].scatter(
        clean[:preview_count][selected, 0], clean[:preview_count][selected, 1],
        s=8, alpha=0.45, color=color, label=f"class {class_id}"
    )
axes[0, 0].set_title("clean training points")
axes[0, 0].legend()
axes[0, 1].scatter(fixed_noisy[:, 0], fixed_noisy[:, 1], s=8, alpha=0.35)
axes[0, 1].set_title(f"noise at fixed step {view_step}")
axes[0, 2].plot(history.history["loss"])
axes[0, 2].set_title("noise-prediction loss")
axes[0, 2].set_xlabel("epoch")
axes[0, 2].set_ylabel("MSE")
axes[1, 0].scatter(
    left_steps["initial"][:, 0], left_steps["initial"][:, 1],
    s=8, alpha=0.35, color="tab:gray")
axes[1, 0].set_title("shared initial noise")
axes[1, 1].scatter(left[:, 0], left[:, 1], s=8, alpha=0.45, color="tab:blue")
axes[1, 1].scatter(*centers[0], marker="x", s=80, color="black")
axes[1, 1].set_title("generated: class 0")
axes[1, 2].scatter(right[:, 0], right[:, 1], s=8, alpha=0.45, color="tab:orange")
axes[1, 2].scatter(*centers[1], marker="x", s=80, color="black")
axes[1, 2].set_title("generated: class 1")
for axis in [axes[0, 0], axes[0, 1], axes[1, 0], axes[1, 1], axes[1, 2]]:
    axis.set_aspect("equal", adjustable="box")
    axis.set_xlim(-3, 3)
    axis.set_ylim(-3, 3)
    axis.grid(alpha=0.2)
figure.tight_layout()
figure.savefig("diffusion_points.png", dpi=150)
plt.show()

程序保存的diffusion_points.png把干净训练点、固定第20步的加噪点、训练损失、共同初始噪声和两个类别的生成点并列显示;各散点图固定相同坐标范围,避免自动缩放制造“更接近”的错觉。left_steps还保留第20、10、0步的中间状态。学生应先看加噪后结构怎样变弱,再检查去噪状态怎样变化,最后核对生成点是否分别回到左右训练分布;不能只看打印出来的两个中心。固定种子只改变class_id时,断言保证两类从完全相同的初始噪声出发;固定类别只改变种子,则观察同一条件下的多样性。代码中没有文字条件,因此它不能执行“两张车票—三张车票”实验。故事盒封面实验调用另一个已经具备文字条件编码的预训练模型,两条证据分别记录,不互相冒充。

三、分别改变种子和条件

实验A固定条件,只改变随机种子。四张图应保持旧收音机、两张车票和木桌主题大致一致,同时在物件姿态和布局上有所变化。多样性太低可能说明条件过强或模型模式单一;变化太大则说明条件控制不足。

实验B固定随机种子,只把“两张旧车票”改为“三张旧车票”。若主要变化集中在数量,说明条件有一定控制作用;若收音机、构图、颜色和风格也大幅改变,说明条件之间存在耦合。不能同时改变条件、种子、模型和采样步数,否则无法解释差异来源。

种子用于复现随机起点。在完全相同的模型、软件环境和采样算法下,同一种子通常有助于复现;模型版本或计算过程变化后仍可能不同。种子不是作品身份证,也不能证明训练材料来源。

第三节 检查随机性、材料来源与作品真实性

一、主动寻找生成失败

正常样例是常见物件构图与清楚条件;边界样例要求精确车票数量或遮挡关系;陌生样例要求训练分布中很少见的对象组合;故障样例包括模型文件缺失、显存不足、生成中断、来源编号丢失和材料已撤回但衍生文本仍被保留。

图像测试关注物件数量错误、旋钮或票面结构异常、局部重复、伪文字和不合理阴影。文字生成还要测试虚构事实、来源错配和前后矛盾;若比较声音生成,则只使用授权的非人物材料,测试突变和噪声,不进行亲属声音模仿。失败类型随数据形态变化,但都要保留原始候选和条件。

还可以加入简单基线:用已授权的物件照片和确定性排版模板制作同一封面。生成路线若只节省少量构图时间,却增加大量伪影、相似性和授权核对,模板可能更合适;若任务需要多种低风险创意草图,生成模型可能有价值。不能取得家庭材料授权时,应切换到阳台植物科普卡等低隐私替代项目,而不是降低授权标准。

生成过程不是简单从图库检索一张图片,但不能据此断言模型绝不复制。模型可能记住少见训练样本,某些条件下产生高度近似内容。课堂可用与授权样例的近邻比较作为风险筛查,但相似度低也不能完成法律判断;不清楚来源和许可时应限制发布。

二、区分生成、编辑与事实表达

生成模型产生候选素材和连接句,普通编辑软件负责裁切、排版和文字,人工负责来源核对与最终表达。故事盒每段应保留“照片事实/来源编号”“口述记忆/讲述者授权”或“模型连接想象/模型与条件记录”标签;无人物封面也应说明使用了生成式工具。若作品过度接近现成角色、标志或某位创作者的独特作品,应停止展示并更换条件或素材路线。

开发与学习AI可以帮助列检查项、比较候选和生成替代条件,但不能替家庭成员授权,也不能推断照片中人物身份。材料提供者撤回P-01或O-01后,学生应依据来源索引移出原文件、相关故事段落和衍生封面,更新展示清单并保留最少撤回记录;已经由他人另行保存的公开副本未必能自动收回,因此本项目默认在受控范围展示。训练数据通常无法由单张输出反推出完整清单,“没有看到复制痕迹”也不等于没有权利风险。

三、形成作品采用判断

判断卡至少记录:材料来源编号与授权范围;照片事实、口述记忆和模型连接想象怎样分层;撤回后删除哪些原件和衍生物;所用条件、种子、模型和资源版本;候选数量;条件符合和伪影情况;生成使用怎样说明;哪些位置由人重新编辑;遇到相似风险或无法核实来源时怎样停止。

适合采用的范围是获得授权、可人工检查、允许创意变化的辅助表达。故事盒不是家族史档案,模型连接想象不能作为历史证据;要求精确结构、真实记录、专业诊断或身份证明的场景,也不应把生成结果当作原始事实。决定不用生成模型,改用经授权照片、手绘、模板或低隐私植物卡,同样体现了对技术的理解。

本章小结

生成式深度学习不是让聊天模型写一句话的操作名称,而是让模型学习数据分布并从中采样。自回归、潜空间和扩散模型采用不同生成路径;条件与随机起点共同影响结果。

保存所有候选、固定变量、观察逐步生成和检查失败,能够把创作体验转成技术学习。模型生成素材和明确标注的连接想象,普通软件编辑,人工核对照片事实、口述来源、授权与撤回。可生成不等于可展示,随机新颖也不自动等于原创。

关键术语

目标测试

一、单项选择题

  1. 生成模型与分类模型的主要区别是( )。A.生成新样本与选择预设类别 B.是否用电脑 C.是否有文件 D.是否需要人
  2. 扩散模型生成时通常从( )开始。A.标准答案 B.随机噪声 C.分类标签表 D.网页按钮
  3. 固定条件只改变随机种子主要观察( )。A.数据授权 B.同一条件下的多样性 C.模型是否训练 D.文字来源
  4. 故事盒中由模型补写的连接句应( )。A.改成照片事实 B.标为模型连接想象并由人决定是否保留 C.删除来源编号 D.当作讲述者原话

二、判断并改错

  1. “相同随机种子在任何模型版本上都保证得到完全相同作品。”请改正。
  2. “生成结果没有明显复制痕迹,就能证明不存在授权风险。”请改正。

三、简答与操作题

  1. 分别用一句话说明自回归、潜空间和扩散生成的起点与过程。
  2. 以“旧收音机和两张车票”的无人物封面为例,设计一个固定条件、只改变种子的对照实验,并列出两项记录指标。
  3. 为什么故事盒必须区分照片事实、口述记忆和模型连接想象?若一份材料被撤回,应怎样处理?
  4. 写出一项适合采用生成模型的低风险任务和一项不适合的任务,并说明理由;不能取得家庭材料授权时可选择什么替代项目?

答案编号:A1-9-01—A1-9-10。完整答案和评价要点统一放入书后“目标测试参考答案”。

第10章 推荐系统:从候选集合到个性化排序

本章技术主题: 基于用户—物品交互的召回与个性化排序。
本章技术实验: 从离线可审核的学习与休闲资源目录中训练并评价一个小型推荐模型。
家庭项目: “完成一次有依据的消费或出行选择”是判断迁移任务,不冒充协同过滤推荐。
跨章位置: 本章形成候选、硬约束、来源时间和退出方案,供自主生活项目复用。
技术主线: 资源与交互 → 硬约束 → 用户/物品表示 → 候选召回 → 排序 → 人工选择。

学习目标

完成本章学习后,你将能够:

  1. 建立来源、授权和字段可核对的学习与休闲资源候选集,并把硬约束与个性化偏好分开。
  2. 说明交互数据、用户表示、物品表示、候选召回和排序之间的关系。
  3. 运行一个小型Keras矩阵分解程序,并与人工硬约束和热门排序基线比较。
  4. 通过冷启动、反馈偏差、隐私和多样性测试,判断推荐结果的适用边界。

项目导入

本章的技术主题是推荐系统。实践任务是从一份离线保存、经过审核的学习与休闲资源目录中,为不同学习者推荐下一项资源。资源可以包括短视频、图文教程、交互练习、项目指南、自然观察短片和博物馆虚拟展导览。目录保留来源页面、授权范围、核对时间和版本,系统输出若干候选及排序理由,学生再根据课程目标、兴趣和实际时间确认。

推荐不是给每个资源人工写一组权重后计算总分。真正的推荐机制会记录用户与物品之间的交互,从许多用户的选择中学习用户和物品的表示,先召回较小的候选集合,再对候选进行个性化排序。人工规则仍然重要,但它承担的是授权、时长、语言和先修条件等硬约束,不冒充推荐模型。

本章的“应用中的AI”是由交互数据训练的推荐模型,它预测某位用户可能对某项资源的偏好。“开发与学习AI”协助学生设计字段、解释矩阵分解代码、检查数据泄漏和归纳失败样例。学生必须决定哪些行为可以记录、哪些约束不可突破,并审查推荐是否把短期点击误当成长期学习目标。

课堂只使用虚构用户编号和模拟交互,不记录真实姓名、账号、成绩或浏览历史。推荐结果用于理解技术机制,不用于给学生贴能力标签,也不自动决定课程、专业或升学机会。

本章成果

考点提示

请重点区分:硬约束过滤不是个性化推荐;热门榜单是非个性化基线;召回追求从大目录中找出可能相关的一小批物品;排序负责精细比较这批候选;没有被点击不一定表示不喜欢,因为用户可能从未看到它。

第一节 建立可核对的学习与休闲资源候选集

一、推荐从物品目录开始

推荐系统不能从空白中“想象”资源。每项资源先进入候选目录,并拥有稳定编号和可核对字段。课堂使用本地保存的目录快照和来源记录,不依赖随时变化的在线接口;核对人员应能根据来源编号打开保存的页面说明或元数据,知道“当时为什么允许它进入目录”。

字段 示例 作用
资源编号 R006 跨表关联,不使用标题猜测身份
资源类型 学习、休闲 支持不同使用目的和评价标准
主题 时间序列入门 说明主要内容
难度 1—3级 与学习准备程度匹配
形式 图文、视频、练习 表达学习方式
预计时长 35分钟 检查可用时间
来源与授权 教师核验、课堂可用 决定能否进入候选集
核对时间与版本 2026-05-18、v1 说明目录快照何时有效
先修条件 表格数据基础 避免顺序不合理

标题、标签和难度都可能由人工填写错误。入库时应核对来源页面、授权范围和更新时间,随机抽查内容是否与标签相符。资源下线后要保留编号和下线原因,不能悄悄把同一编号换成另一内容,否则历史交互会指向错误物品。

二、硬约束先于推荐分数

假设某学习者只有45分钟、需要中文材料,并且只能使用课堂授权资源。系统应先删除超时、语言不符、未授权和先修条件不满足的资源,再进行推荐。即使模型给未授权资源很高分,也不能让分数突破硬约束。

硬约束是“必须满足”的条件,偏好是“满足约束后更喜欢什么”。把两者混成加权总分会产生危险结果:未授权资源可能凭借高热门度抵消授权扣分,超时资源可能凭借主题相似获得高总分。合理流程是先过滤,再召回和排序。

过滤后先建立热门排序基线:按符合约束的资源在训练期内的完成次数或平均评分排列。热门榜单没有为个人学习,但它简单、稳定、容易解释。个性化模型如果长期不能超过这个基线,就没有证据说明收集更多个人数据值得。

三、交互不是内心偏好的直接读数

用户打开、收藏、完成、评分和跳过资源,形成用户—物品交互。显式评分直接表达1至5分偏好;隐式行为需要人为定义强度,例如完成比打开更强,但仍不能断言完成就代表喜欢。未点击还可能因为资源没有被展示,不能一律当作负反馈。

交互表至少包含匿名用户编号、资源编号、行为、时间和是否实际曝光。训练和评价必须按时间切分,避免用未来行为预测过去。若同一用户短时间重复打开同一资源,应先判断是持续学习、误触还是页面刷新,不能机械地当成多次独立喜欢。

把隐式行为换算成训练信号时,要保存公开的转换规则。例如“完整学习且通过练习”可以作为较强正反馈,“打开后立即退出”只能作为较弱信号,“已经展示但没有打开”仍不能直接等同于负评分。规则改变后应重新训练并用同一留出数据比较。若系统只保存换算后的一个数字,日后就无法判断偏差来自原始行为还是转换方法。

还要区分偏好与学习需要。用户可能更常点击轻松、熟悉的内容,但课程目标要求补足尚未掌握的基础。推荐系统可以在合格候选中预测偏好,却不应自行修改培养目标;必要先修和教师规定内容应作为清楚可见的约束或独立学习计划,而不是伪装成模型“最懂学生”的结果。

开发与学习AI可以帮助列出字段冲突和可能泄漏,但不应接收真实学生明细。学生使用模拟数据提问,并要求它说明每个推断依赖哪些字段。应用中的AI只使用经过许可的交互;学习目标、健康信息和家庭情况不得因为“可能提高准确率”就自动纳入。

第二节 从用户—物品表示到召回与排序

一、用向量表示潜在偏好

矩阵分解把交互数据想象成一张用户—物品矩阵。矩阵中已知位置是评分或行为强度,大量位置为空。模型为每位用户学习一个短向量,也为每项资源学习一个同长度向量。两个向量的点积较大,表示模型预测这位用户和这项资源较匹配。

向量各维通常没有预先指定的名称。某一维可能混合难度、形式和主题,也可能只是数据中的统计模式。用户向量不是对人的完整描述,物品向量也不是内容真理。它们只是在当前交互和损失下,为预测已观察行为形成的参数。

先用二维教学向量手算一次。若某用户向量为[0.8, 0.2],资源A为[0.7, 0.3],资源B为[0.1, 0.9],则点积分别为0.62和0.26。模型会把A看作更匹配,但这两个维度是训练得到的参数,不能擅自命名为“能力”和“兴趣”。完整程序做的仍是这类乘加,只是向量由训练过程学习。

候选召回先用较便宜的方法从大目录中找出可能相关的一小批物品。在本章小目录里可以计算全部合格物品与用户向量的相似度;目录很大时通常使用高效的近邻检索。排序阶段再使用带偏置的完整预测分数、上下文和可解释规则精细排列。召回漏掉的资源,排序阶段无法补回。

二、运行小型矩阵分解模型

下面程序使用5个虚构用户和8项学习、休闲资源的显式评分。资源来自固定的离线审核快照,代码中的CATALOG_SNAPSHOT_ID对应另存的来源与授权登记表,而不是实时抓取的网页目录。每条评分带有先后顺序;程序把每位用户最后一次交互留作测试,只用更早的交互训练。随后,它对用户U0过滤硬约束,先用向量点积召回候选,再用完整模型排序,并在所有用户的留出交互上比较个性化结果与热门基线。

import numpy as np
import tensorflow as tf
from tensorflow import keras

keras.utils.set_random_seed(7)

# 课堂程序使用固定的离线审核快照;来源页和授权记录保存在CAT-2026-05-18登记表。
CATALOG_SNAPSHOT_ID = "CAT-2026-05-18"
items = [
    {"id": 0, "title": "数据表入门", "minutes": 25, "authorized": True, "lang": "zh"},
    {"id": 1, "title": "绘制训练曲线", "minutes": 30, "authorized": True, "lang": "zh"},
    {"id": 2, "title": "图像分类练习", "minutes": 40, "authorized": True, "lang": "zh"},
    {"id": 3, "title": "声音分类练习", "minutes": 35, "authorized": True, "lang": "zh"},
    {"id": 4, "title": "高级视觉专题", "minutes": 45, "authorized": False, "lang": "zh"},
    {"id": 5, "title": "长时综合课程", "minutes": 90, "authorized": True, "lang": "zh"},
    {"id": 6, "title": "自然观察短片", "minutes": 45, "authorized": True, "lang": "zh"},
    {"id": 7, "title": "博物馆虚拟展导览", "minutes": 20, "authorized": True, "lang": "zh"},
]

# 四列依次是用户编号、资源编号、评分、该用户的交互顺序。
ratings = np.array([
    [0, 0, 5, 1], [0, 1, 4, 2], [0, 3, 5, 3],
    [1, 0, 4, 1], [1, 7, 4, 2], [1, 2, 5, 3],
    [2, 1, 5, 1], [2, 3, 5, 2], [2, 6, 4, 3],
    [3, 0, 3, 1], [3, 2, 4, 2], [3, 6, 5, 3], [3, 7, 4, 4],
    [4, 1, 4, 1], [4, 3, 5, 2], [4, 6, 4, 3],
], dtype="float32")

train_parts, test_rows = [], []
for user_id in np.unique(ratings[:, 0]).astype(int):
    user_rows = ratings[ratings[:, 0] == user_id]
    user_rows = user_rows[np.argsort(user_rows[:, 3])]
    train_parts.append(user_rows[:-1])
    test_rows.append(user_rows[-1])
train = np.vstack(train_parts)
test = np.array(test_rows, dtype="float32")
assert all(np.sum(train[:, 0] == user_id) >= 1 for user_id in range(5))
assert all(np.sum(test[:, 0] == user_id) == 1 for user_id in range(5))
assert np.all(test[:, 2] >= 4)


class MatrixFactorization(keras.Model):
    def __init__(self, user_count, item_count, dimensions=4):
        super().__init__()
        self.user_embedding = keras.layers.Embedding(user_count, dimensions)
        self.item_embedding = keras.layers.Embedding(item_count, dimensions)
        self.user_bias = keras.layers.Embedding(user_count, 1)
        self.item_bias = keras.layers.Embedding(item_count, 1)

    def call(self, inputs):
        users = tf.cast(inputs[:, 0], tf.int32)
        item_ids = tf.cast(inputs[:, 1], tf.int32)
        user_vectors = self.user_embedding(users)
        item_vectors = self.item_embedding(item_ids)
        dot_product = tf.reduce_sum(user_vectors * item_vectors, axis=1)
        bias = tf.squeeze(self.user_bias(users) + self.item_bias(item_ids), axis=1)
        return dot_product + bias


model = MatrixFactorization(user_count=5, item_count=len(items))
model.compile(optimizer=keras.optimizers.Adam(0.03), loss="mse")
model.fit(train[:, :2], train[:, 2], epochs=300, verbose=0)

global_mean = float(np.mean(train[:, 2]))
item_mean = {}
for item in items:
    known = train[train[:, 1] == item["id"], 2]
    item_mean[item["id"]] = float(np.mean(known)) if len(known) else global_mean


def eligible_items(user_id):
    seen = set(train[train[:, 0] == user_id, 1].astype(int))
    return [
        item["id"] for item in items
        if item["id"] not in seen
        and item["authorized"]
        and item["lang"] == "zh"
        and item["minutes"] <= 45
    ]


def recommend(user_id, k=3, recall_size=5):
    eligible = eligible_items(user_id)
    user_vector = model.user_embedding(np.array([user_id])).numpy()[0]
    item_vectors = model.item_embedding(np.array(eligible)).numpy()
    recall_scores = item_vectors @ user_vector
    recall_order = np.argsort(recall_scores)[::-1][:min(recall_size, len(eligible))]
    recalled = [eligible[index] for index in recall_order]

    ranking_input = np.array(
        [[user_id, item_id] for item_id in recalled], dtype="float32")
    ranking_scores = model.predict(ranking_input, verbose=0)
    ranked = sorted(
        zip(recalled, ranking_scores), key=lambda pair: float(pair[1]), reverse=True)
    personalized = [item_id for item_id, _ in ranked[:k]]
    popular = sorted(eligible, key=lambda item_id: item_mean[item_id], reverse=True)[:k]
    return recalled, personalized, popular


personalized_hits, popular_hits = 0, 0
for user_id, held_out_item, held_out_rating, _ in test:
    user_id, held_out_item = int(user_id), int(held_out_item)
    assert held_out_item not in set(train[train[:, 0] == user_id, 1].astype(int))
    _, personalized, popular = recommend(user_id, k=3)
    personal_hit = int(held_out_item in personalized)
    popular_hit = int(held_out_item in popular)
    personalized_hits += personal_hit
    popular_hits += popular_hit
    print({
        "user": user_id,
        "held_out": items[held_out_item]["title"],
        "rating": float(held_out_rating),
        "personal_top3": [items[item_id]["title"] for item_id in personalized],
        "popular_top3": [items[item_id]["title"] for item_id in popular],
        "personal_hit": personal_hit,
        "popular_hit": popular_hit,
    })

target_user = 0
recalled, personalized, popular = recommend(target_user, k=3)
target_held_out = int(test[test[:, 0] == target_user, 1][0])
print("U0留出资源:", items[target_held_out]["title"])
print("U0个性化召回:", [items[item_id]["title"] for item_id in recalled])
print("U0个性化Top-3:", [items[item_id]["title"] for item_id in personalized])
print("U0热门Top-3:", [items[item_id]["title"] for item_id in popular])
print("个性化命中率@3:", personalized_hits / len(test))
print("热门基线命中率@3:", popular_hits / len(test))

代码中的嵌入层保存用户和物品向量,均方误差推动预测评分接近训练期的已知评分。硬约束在召回前执行;未授权的R004和超过时长的R005即使模型分数高也不会进入候选。召回阶段只看用户与物品向量的点积,排序阶段再加入用户和物品偏置。热门基线只使用训练期各资源的平均评分,不使用目标用户向量,也不能偷看留出的最后一次交互。

程序实际完成了按时间留出和命中率@3比较,而不是只在文字中要求评价。若某位用户最后学习的资源进入Top-3,就记为一次命中;最终分数是五位模拟用户的命中比例。样本极小,具体比例没有推广价值。若个性化模型不优于热门基线,应保留简单方法,而不是用训练损失下降宣称推荐有效。配套Notebook还应显示交互矩阵、训练曲线、用户与物品向量及多次随机种子的差异。

三、离线评价不能只看训练损失

评价时可以把每位用户时间上较晚的一次交互留出,检查目标资源是否进入前K个推荐,形成命中率;还可统计目录中有多少资源得到曝光、不同主题是否保持一定多样性,以及热门和冷门资源的覆盖。训练损失低,只表示模型适合已知评分,不保证推荐有用。

还应设计在线前的人工审查。随机抽取推荐列表,检查硬约束、来源、先修顺序和重复内容。若个性化模型与热门基线差异很小,应优先保留简单基线;若模型只推荐同一主题,则要在排序后增加可说明的多样性规则,但必须把该规则与模型预测分开记录。

第三节 测试冷启动、反馈偏差与推荐边界

一、没有交互时模型认识不了新对象

新用户没有历史交互,矩阵分解无法学习可靠用户向量;新资源没有评分,也没有可靠物品向量,这称为冷启动。合理做法不是伪造个性化,而是明确进入冷启动路径:先应用硬约束,再展示经审核的热门、多样化资源,或请用户选择少量主题和形式偏好。

新资源还可以依据主题、难度和形式等内容特征进入召回,等积累真实交互后再与协同表示结合。冷启动问卷应少而透明,允许跳过和修改。系统不能把一次选择永久固定为用户画像。

二、推荐会改变未来数据

被排在前面的资源更容易获得点击,新的点击又会让它更热门,形成反馈回路。未曝光资源没有点击,不等于用户不喜欢。若训练数据只记录点击而不记录曝光位置,模型会把界面造成的机会差异误当成偏好。

可以保留少量安全、受约束的探索位置,让不同合格资源获得被看到的机会;同时记录资源是否展示、排在何处以及用户是否完成。探索不是随机推送不合格内容,硬约束始终有效。评价时按时间比较覆盖、多样性、完成和人工投诉,不能只追求点击量。

开发与学习AI可以依据匿名汇总表寻找“头部资源曝光越来越集中”等模式,但学生要检查统计口径和样本量。应用中的AI也不应自动根据敏感属性推断能力、经济情况或心理状态。删除交互记录、关闭个性化和查看主要推荐依据,都应成为可用功能。

三、形成推荐使用判断

测试至少包括:无历史的新用户;刚入库的新资源;只点某一主题的窄历史;热门资源被移除;授权字段变更;用户撤回历史数据;模型不可用。任何情况下,硬约束失败都应停止推荐,模型不可用则回退到经审核的非个性化基线。

学习资源推荐的目标不能被压缩成“让人停留更久”。合理指标应与任务一致,例如是否完成计划、是否覆盖必要先修、是否获得多样选择,以及用户能否理解并修改推荐。推荐只提供可选择的顺序,不决定学生能力,更不能隐藏其他合格资源。

判断卡应写明使用了哪些交互、保留多久、谁能查看、冷启动怎样处理、何时回退热门基线、怎样退出个性化。只有当个性化排序在独立数据和人工审查中稳定优于基线,并且隐私与维护负担可接受时,才有理由继续使用。

四、迁移任务:完成一次有依据的消费或出行选择

推荐技术还可以帮助我们看清“候选—约束—比较—选择”的一般结构,但不能把所有排序任务都称为推荐系统。学生任选一个低风险任务,例如比较三种通勤方案、一次周末公共文化场馆出行,或同类日用品的购买方案;只做资料整理和方案比较,不代替真实下单、预约或支付。

先建立本地options.csv快照。每个候选必须记录来源编号、来源页面、查询时间、适用日期、可取消条件和价格组成。教材不写死实时票价或商品价格;学生在实践当天查询,把原页面或页面存档与时间一起保存。来源不明、时间过期或条款看不清的候选进入待确认,不能让模型补写。

硬约束先于比较,例如总预算上限、出发与返回时段、必要的无障碍条件、授权或安全要求。通过过滤后再计算总成本:标价之外还要检查交通接驳、预约或服务费、配送、行李、设备租用、退改费用等隐藏费用,并注明哪些费用仍未知。输出至少保留一个合格备选、一个“维持原方案/暂不选择”的退出选项,以及条件变化后重新查询的时间点。

学生可以用透明加权表比较总成本、时间、便利性和可取消性,权重由使用者确认并允许调整。这是有来源的多条件决策支持,不是协同过滤推荐:它没有从许多用户—物品交互中学习用户向量和物品向量。开发与学习AI可以协助生成读取表格、检查缺失字段和计算总成本的代码,但不得虚构实时价格、隐藏费用或营业状态,最终方案仍由人核对来源后选择。

本章小结

推荐系统从可核对的离线物品目录和用户—物品交互开始。硬约束先删除不合格资源,热门排序提供非个性化基线。矩阵分解通过交互学习用户和物品向量;召回从大目录中寻找较小候选集,排序再精细比较。没有交互会产生冷启动,展示位置会形成反馈偏差。可靠推荐必须记录曝光、比较基线、评价覆盖与多样性、允许退出,并把最终选择留给人。消费或出行迁移任务同样要保留来源、查询时间、总成本、隐藏费用和退出方案,但透明加权比较不能冒充从交互中学习的协同过滤推荐。

关键术语

目标测试

一、单项选择题

  1. 下列哪一项最能体现真正的个性化推荐机制? A. 人工给资源加权求和 B. 根据用户—物品交互学习表示并排序 C. 只显示最新资源 D. 按标题字数排序
  2. 为什么未授权资源应在召回前被删除? A. 减少字体数量 B. 硬约束不能被高推荐分数抵消 C. 提高训练轮数 D. 让用户向量更长
  3. 召回阶段的主要作用是什么? A. 从大目录快速选出较小候选集 B. 最终确认学习效果 C. 修改资源授权 D. 生成用户姓名
  4. 新用户没有任何交互时,哪种处理更合理? A. 宣称已经完成个性化 B. 随机解除硬约束 C. 提供受约束的热门和多样化资源并允许选择偏好 D. 复制另一用户全部历史

二、判断并改错

  1. 判断并改错:“没有点击某资源,就说明用户不喜欢它。”
  2. 判断并改错:“加权打分表只要输出顺序,就可以称为由交互训练的推荐模型。”

三、简答与操作题

  1. 说明用户表示、物品表示和点积在矩阵分解中的关系。
  2. 画出或用文字描述“硬约束过滤—召回—排序—人工选择”的完整流程,并说明每步输入和输出。
  3. 为什么推荐系统要记录曝光位置?请举例说明不记录会造成的偏差。
  4. 某小组用透明加权表比较三种周末出行方案。请写出完成一次有依据的选择必须保留的来源与成本信息、硬约束、备选或退出方案,并说明为什么该表不能称为协同过滤推荐。

第四篇 改善生活中的选择与行动

让模型行动并形成系统

第11章 强化学习:让智能体从行动结果中学习

本章技术主题: 状态、动作、奖励与策略学习。
本章技术实验: 在虚拟房间中比较规则策略与通过试错学习的策略。
家庭项目: “建立家庭空气与用能观察站”第二阶段——虚拟环境中的舒适—节能策略。
跨章位置: 接收第5章的观察问题,但不控制真实设备;结果作为第12章系统安全设计的反例与依据。
技术主线: 观察状态 → 选择动作 → 环境转移 → 获得奖励 → 更新策略 → 独立评估。

学习目标

完成本章学习后,你将能够:

  1. 在家庭空气与用能观察站的虚拟任务中分别定义智能体、环境、状态、动作、奖励、策略和回合。
  2. 说明Q学习如何通过试错更新动作价值,以及探索与利用为什么都需要。
  3. 运行一个完整的表格型Q学习程序,并把学得策略与固定策略基线比较。
  4. 发现奖励漏洞、探索风险和仿真—现实差距,判断强化学习何时不应接管真实动作。

项目导入

本章的技术主题是强化学习,家庭项目是“建立家庭空气与用能观察站”。第一阶段只观察教师提供或已经脱敏的温度、空气质量与用能记录,核对采集时间和变化曲线,不执行控制;本章进入第二阶段,在完全虚拟的房间中让智能体学习“保持舒适,同时减少能源消耗”的策略。虚拟温度被分成五档,智能体每一步可以不动作、升温或降温;环境还会产生随机冷热扰动。智能体不能直接看到未来,只能根据当前状态选择动作,再从环境返回的下一状态和奖励中学习。

强化学习不同于前面章节的监督学习。监督学习通常由数据提供输入和正确标签,模型学习预测;强化学习没有为每一步直接给出“正确动作”,而是由奖励评价行动后的结果。奖励写得不完整,智能体可能找到符合数字却违背真实意图的做法。

本章的“应用中的AI”是从仿真交互中学得的Q表和策略。“开发与学习AI”可以帮助学生解释更新公式、生成测试和分析异常回合,但不能替学生定义真实目标,也不能把仿真高分解释为现实安全。本章不连接空调、加热器、门窗或任何真实设备,所有动作只改变程序中的虚拟状态。

本章成果

考点提示

请重点区分:智能体只是行动主体,不等于使用了强化学习;状态机可以按规则行动,却不从奖励更新策略;奖励是人为设计的学习信号,不是现实价值本身;探索会主动尝试不确定动作,在真实高风险环境中可能不可接受。

第一节 定义虚拟环境中的状态、动作和奖励

一、先把强化学习要素写成合同

观察站的第二阶段把虚拟房间温度简化为0至4五个状态:0为很冷,1为偏冷,2为舒适,3为偏热,4为很热。动作有三个:保持、升温、降温。环境接收动作后,再叠加一次随机外界扰动,得到下一状态。智能体的目标是在若干步中获得较高累计奖励。第一阶段的真实或样例观察记录可以帮助提出仿真范围,但不得直接当作自动控制许可。

要素 本项目定义 需要警惕的简化
智能体 选择保持、升温或降温的程序 不代表真实设备控制器
环境 计算温度变化和奖励的虚拟房间 忽略湿度、房间大小和设备延迟
状态 0—4的温度档位 离散档位丢失精确温度和变化速度
动作 保持、升温、降温 假定每次动作效果相同
奖励 舒适收益减能源成本 数字不能覆盖噪声、维护和安全
策略 每个状态下选择动作的方法 高仿真得分不等于现实可用
回合 重置后连续运行30步 只是一次模拟过程

每一步可以写成四元记录:“当前状态、选择动作、获得奖励、下一状态”。例如当前偏冷状态1,选择升温,环境扰动为0,下一状态变为2;若舒适收益为2、动作能耗成本为0.3,本步奖励为1.7。这条记录不是正确答案,而是智能体更新判断的经验。

家庭阳台温室可以作为同一方法的变体:把状态改为虚拟温室温度档位,把动作解释为“保持、虚拟保温、虚拟通风”,奖励写成适宜区间收益减动作成本。学生必须重新说明光照、湿度、设备延迟等被省略因素,不能把房间策略改几个名称就接到真实风扇、加热器或遮阳设备。

二、状态必须包含作决策所需的信息

本章只用当前温度档位作为状态,隐含假设是:知道当前档位后,预测下一步不再需要更早历史。这便于观察Q学习,却未必符合真实房间。若刚刚连续升温和刚刚快速降温都显示为状态2,设备余热可能使两种情况的下一步不同。此时可以把温度变化方向或上一动作加入状态,也可以使用能够处理序列的模型。

增加状态不是越多越好。五档温度与三个上一动作组合后,状态数量会扩大;每个状态—动作组合都需要足够经验。数据不足时,Q表中许多位置几乎没有被访问,策略会变得不稳定。设计者要在信息充分和学习难度之间取舍,并记录哪些现实因素被省略。

环境转移也需要单独验证。同一状态选择同一动作,因为随机扰动不同,下一状态可能不同;Q值学习的是多次经验下的长期平均,而不是保证每次得到同一结果。学生可固定一个状态和动作重复运行100次,统计各下一状态出现次数,再与程序设定的扰动概率比较。若实际分布明显异常,应先修复环境,不能让智能体在错误仿真中继续追求高分。

三、奖励决定模型会追求什么

本项目在下一状态为2时给2分;偏离2越远,舒适部分越低;使用升温或降温动作再扣除0.3分。这样同时表达舒适与节能。但0.3只是课堂假设,不是实际电费,也没有包含设备磨损。

若只奖励舒适、不惩罚能源,智能体可能频繁动作;若能源惩罚过大,它可能宁愿长期不舒适;若只奖励“设备关闭”,最佳数字策略会是不做任何事。强化学习不会理解设计者未写进奖励的价值,这类利用奖励定义缺口取得高分的现象称为奖励漏洞。

还要规定回合何时结束。本项目固定30步后终止,使不同策略可以比较相同长度的累计奖励。若只在发生严重偏离后提前结束,却没有给出足够负奖励,智能体甚至可能主动让回合结束以逃避后续成本。

累计奖励把一个回合内的多步结果相加,适合比较长期表现,但不能代替轨迹检查。两个策略可能累计奖励相同:一个始终接近舒适,另一个在舒适和极端状态之间大幅摆动。评价表因此还应记录每回合动作次数、极端状态步数、最长连续不舒适时间和最差回合。平均值与最坏情形共同呈现,才不会让少数高分回合掩盖风险。

四、固定策略是必要基线

在学习前先写固定策略:偏冷就升温,偏热就降温,舒适就保持。它不根据奖励修改规则,因此不是强化学习。还可以设置“始终保持”基线,观察完全节能但不主动调节的结果。

固定策略简单、可解释,可能已经在本仿真中表现很好。学得策略只有在多次独立回合中稳定达到更高奖励,并且没有增加极端状态次数,才显示出进一步研究价值。若强化学习没有超过固定策略,应保留简单方法,而不是因为“会学习”就优先采用。

第二节 让智能体在试错中学习策略

一、从动作价值到Q表

Q表的行对应状态,列对应动作。表中数值Q(s,a)表示在状态s选择动作a,并在以后继续行动时可能获得的累计收益。开始时所有值为0,智能体没有经验。

获得一次转移后,Q学习把当前估计向“本步奖励加下一状态最佳价值”移动:

新Q值 = 旧Q值 + 学习率 × [奖励 + 折扣因子 × 下一状态最大Q值 - 旧Q值]

学习率控制一次经验改变旧判断的幅度;折扣因子控制对未来奖励的重视程度。数值设置会影响学习速度和稳定性,不存在对所有任务都最好的固定值。

如果智能体永远选择当前Q值最大的动作,它可能一直重复早期偶然选择,无法发现更好的动作。探索是以一定概率尝试其他动作,利用则选择当前认为最好的动作。训练开始时探索较多,积累经验后逐步降低,但不能把随机探索直接搬到高风险现实设备。

二、运行完整Q学习程序

下面程序实现五状态、三动作的虚拟环境,训练Q表,并用100个新回合比较学得策略、固定策略和始终保持策略。随机种子固定,便于重复实验。

import numpy as np


class VirtualRoom:
    def __init__(self, seed=7):
        self.rng = np.random.default_rng(seed)
        self.state = 2
        self.step_count = 0

    def reset(self):
        self.state = int(self.rng.integers(0, 5))
        self.step_count = 0
        return self.state

    def step(self, action):
        action_change = [0, 1, -1][action]
        disturbance = int(self.rng.choice([-1, 0, 1], p=[0.20, 0.60, 0.20]))
        self.state = int(np.clip(self.state + action_change + disturbance, 0, 4))
        comfort_reward = 2.0 if self.state == 2 else -float(abs(self.state - 2))
        energy_cost = 0.3 if action != 0 else 0.0
        reward = comfort_reward - energy_cost
        self.step_count += 1
        terminated = self.step_count >= 30
        return self.state, reward, terminated


def train_q_learning(episodes=4000):
    env = VirtualRoom(seed=7)
    q_table = np.zeros((5, 3), dtype="float32")
    rng = np.random.default_rng(11)
    learning_rate = 0.15
    discount = 0.95

    for episode in range(episodes):
        state = env.reset()
        epsilon = max(0.05, 1.0 - episode / 3000)
        terminated = False
        while not terminated:
            if rng.random() < epsilon:
                action = int(rng.integers(0, 3))
            else:
                action = int(np.argmax(q_table[state]))
            next_state, reward, terminated = env.step(action)
            target = reward
            if not terminated:
                target += discount * float(np.max(q_table[next_state]))
            q_table[state, action] += learning_rate * (target - q_table[state, action])
            state = next_state
    return q_table


def learned_policy(q_table, state):
    return int(np.argmax(q_table[state]))


def fixed_policy(state):
    if state < 2:
        return 1
    if state > 2:
        return 2
    return 0


def always_hold_policy(state):
    return 0


def evaluate(policy, seed, episodes=100):
    env = VirtualRoom(seed=seed)
    rewards = []
    extreme_steps = []
    for _ in range(episodes):
        state = env.reset()
        total_reward = 0.0
        extremes = 0
        terminated = False
        while not terminated:
            action = policy(state)
            state, reward, terminated = env.step(action)
            total_reward += reward
            extremes += int(state in (0, 4))
        rewards.append(total_reward)
        extreme_steps.append(extremes)
    return float(np.mean(rewards)), float(np.mean(extreme_steps))


q_table = train_q_learning()
learned_result = evaluate(lambda state: learned_policy(q_table, state), seed=101)
fixed_result = evaluate(fixed_policy, seed=101)
hold_result = evaluate(always_hold_policy, seed=101)

print("Q表:\n", np.round(q_table, 2))
print("学得策略动作:", np.argmax(q_table, axis=1).tolist())
print("学得策略 平均奖励/极端状态步数:", learned_result)
print("固定策略 平均奖励/极端状态步数:", fixed_result)
print("始终保持 平均奖励/极端状态步数:", hold_result)

动作编号0、1、2分别表示保持、升温和降温。训练阶段使用逐步降低的探索率;评价阶段完全按策略行动,不再随机探索。平均奖励之外,程序还统计进入很冷或很热状态的步数,防止一个单一总分掩盖极端状态。

学生不必默写代码,但应能指出环境转移、奖励、探索判断和Q值更新分别在哪里。开发与学习AI可以逐行解释某一次更新,学生则用手算核对。下一阶段应规划一份完整Actor-Critic Notebook作为拓展数字资源,用于观察状态较多或连续动作时,策略网络和价值网络如何替代表格;它不是本章第一次实验的前置要求。

三、控制变量理解学习过程

可以只改变一个变量重新训练。例如把能源成本从0.3改为0.8,观察策略是否更倾向保持;把探索最低值从0.05改为0,观察不同随机种子下结果是否更不稳定;把扰动概率增大,观察固定策略和学得策略的差异。

每次应记录Q表、五个状态选择的动作、平均奖励和极端状态步数。单次训练优于基线不能说明稳定,至少使用多个随机种子重复,并报告结果范围。若不同种子形成完全不同策略,说明数据经验不足、奖励不清或任务本身存在多个近似方案。

第三节 检查奖励漏洞、探索风险与仿真迁移

一、主动寻找奖励漏洞

奖励测试可以故意制造缺陷。第一种只奖励接近舒适档,不扣能源,观察动作是否过多;第二种把能源惩罚设得极高,观察智能体是否拒绝调节;第三种只在回合结束时计算温度,观察中间极端状态是否被忽略。发现高分同时伴随不合理行为,就是奖励没有表达完整目标的证据。

不能只修改奖励直到某次曲线“好看”。每次修改要先写预期行为,再用固定测试种子比较平均奖励、动作次数、极端状态和最差回合。开发与学习AI可以列出可能漏洞,但学生要回到轨迹逐步检查,确认问题真实存在。

二、探索在现实中可能付出代价

仿真中的随机升温或降温只改变数字。真实环境的错误动作可能造成能源浪费、设备损坏或人员风险。强化学习的探索机制意味着系统可能主动尝试尚未验证的行动,因此不能把课堂策略直接连接真实设备。

若未来研究低风险现实应用,也要先经过离线数据验证、仿真压力测试、动作白名单、幅度限制、人工批准、紧急停止和可回退控制器。对高温、高压、交通、医疗和人身安全相关系统,课堂实验不提供自动控制路径。

一个程序被称为“智能体”,或能够根据状态执行动作,并不自动意味着强化学习。只有当它通过与环境交互获得奖励,并依据这些经验更新策略,才具有本章所说的强化学习过程。固定条件规则、工作流和状态机仍然可能是更安全、更合适的方案。

三、认识仿真—现实差距

虚拟房间假定每次升温都增加一档、扰动概率固定、状态完全可见。真实房间存在测量误差、动作延迟、空间差异、季节变化、门窗开合和设备老化。仿真中没有出现的因素会让策略失效,这称为仿真—现实差距。

本章结论只能是“在家庭空气与用能观察站第二阶段的给定仿真假设下,学得策略达到某种表现”。判断卡需列出第一阶段观察数据怎样被使用、仿真省略了什么、策略是否超过固定基线、奖励漏洞测试结果、探索是否关闭,以及禁止接入哪些设备。阳台温室变体也遵守同一边界。即使学得策略得分最高,也只说明值得继续研究,不代表获得真实部署许可。

本章小结

强化学习通过智能体与环境交互学习策略。家庭空气与用能观察站第一阶段负责观察,第二阶段才在虚拟环境中研究舒适—节能策略;家庭阳台温室只是可重新定义状态、动作和奖励的变体。状态描述当前信息,动作影响环境,奖励评价行动结果,回合组织一段连续经验。Q学习用奖励和下一状态价值更新Q表,并在探索与利用之间平衡。固定策略是重要基线;名称叫智能体、会执行动作的状态机并不自动构成强化学习。奖励漏洞、探索风险和仿真—现实差距决定了课堂策略只能在仿真中使用。

关键术语

目标测试

一、单项选择题

  1. 强化学习中,哪一项负责接收动作并返回下一状态和奖励? A. 环境 B. 标签表 C. 测试答案 D. 用户画像
  2. Q(s,a)主要表示什么? A. 图片像素 B. 状态s选择动作a的长期价值估计 C. 固定温度 D. 设备价格
  3. 为什么训练初期需要探索? A. 发现尚未尝试但可能更好的动作 B. 删除奖励 C. 保证每步安全 D. 取消环境
  4. 下列哪一项本身不构成强化学习? A. 依据奖励更新Q表 B. 在环境中试错学习策略 C. 固定条件状态机按规则动作 D. 比较累计奖励

二、判断并改错

  1. 判断并改错:“仿真平均奖励高,就可以直接把策略接到真实加热设备。”
  2. 判断并改错:“奖励是客观完整的真实目标,智能体无法利用奖励定义的漏洞。”

三、简答与操作题

  1. 用“家庭空气与用能观察站第二阶段”的虚拟房间案例分别解释状态、动作、奖励、策略和回合,并说明第一阶段观察与第二阶段学习的区别。
  2. 写出Q学习更新式中“本步奖励”和“下一状态最大Q值”分别代表的作用。
  3. 设计一个奖励漏洞测试,说明修改什么、预期可能出现什么异常行为、记录哪些指标。
  4. 学得策略平均奖励高于固定策略,但极端状态步数也明显更多。你会怎样判断?写出至少三项后续检查或限制。

第12章 多模态与物理AI:让模型、工具和设备协同

本章技术主题: 多模态系统集成、设备端推理与安全接管。
本章技术实验: 先用回放材料连接图像、时序、语言说明和虚拟安全反馈。
家庭项目: 完成“打造一个AI阳台植物园”,再选择声音或手势互动、物品循环利用或“用AI改善一个自己的生活场景”。
跨章位置: 汇总第2、3、5章项目材料和全书测试方法,完成共同基线后才开展自主迁移。
技术主线: 多源感知 → 专用或多模态模型 → 协调程序 → 语言解释 → 安全动作 → 人工接管。

学习目标

完成本章学习后,你将能够:

  1. 为图像、声音和环境时序数据规定共同的时间、质量与结果接口,并解释它们如何形成多源证据。
  2. 区分原生多模态模型与多个专用模型串联的系统,说明语言模型、普通程序和人的不同责任。
  3. 根据数据、隐私、速度和设备条件选择云端、本地端或设备端运行位置,并完成H0、H1或H2路径中的一条。
  4. 沿完整链路测试输入失效、模型冲突、网络中断、输出编造和动作异常,设置安全动作与人的接管,并比较人工、规则、云端、本地端和不用AI的方案。

项目导入

本章的技术主题是多模态系统集成与物理AI。所有小组先完成共同基线“打造一个AI阳台植物园”,把任务从“识别一张叶片图像”扩展为“依据多源证据形成一份可核对的观察报告”。系统至少使用两种输入:植物图像,以及最近24小时的温度、空气湿度和土壤湿度记录;具备授权条件的小组还可加入一段短声音,由声音分类模型识别雨声、流水声或普通环境声。

图像模型描述可见外观,时序模型检查环境变化,声音模型提供可选事件线索,语言模型把已经结构化的证据组织成易读说明。系统只输出观察摘要和人工检查建议,不诊断植物问题,不自动浇水、施肥、开关高功率设备,也不把生成文字直接转换为控制指令。

本章的“应用中的AI”包括视觉、时序、声音和语言模型。它们各自处理规定输入,结果由普通协调程序检查时间、质量和权限。“开发与学习AI”协助学生设计接口、生成适配代码、分析日志和补写测试。学生决定数据是否可以采集、模型结果能否进入下一步、允许系统执行什么动作,并在模型冲突或证据不足时接管。

共同基线通过后,可任选“做一个会识别声音或手势的互动作品”或“建立家庭物品整理与循环利用助手”进行低风险迁移。前者只用短促非语音声音或不含人脸的手部动作切换虚拟页面、图标和提示;后者只对清洁、无危险的日常物品给出整理、再用或分类候选,并由人核对当地规则。两者都不进行身份识别,不自动操纵机械装置,也不把模型候选直接当作行动事实。

本章成果

考点提示

请重点区分:一个模型同时接收多种信息,称为模型层多模态;视觉、声音和时序专用模型由程序连接,属于系统层多模态;普通程序负责接口、规则和安全拦截;语言模型生成解释,不代表它拥有控制权限;物理AI的关键是感知、计算、行动与环境反馈的闭环,而不是给普通设备加上“智能”名称。

第一节 连接图像、声音、传感器和语言模型

一、先明确每种输入能证明什么

植物图像可以显示叶片颜色、斑点和形态,但一张照片不包含完整变化过程。环境时序记录可以显示温度和湿度何时变化,却不能单独确定叶片外观。声音能够提供雨声、流水等事件线索,但容易受到说话声、车辆声和距离影响。多源输入的价值在于相互补充,不是简单地让数据越多越好。

输入 主要模型或方法 输出示例 不能单独推出的结论
植物图像 视觉分类模型 发黄候选0.78 发黄的具体原因
24小时环境记录 时序异常模型或统计基线 土壤湿度持续下降 必须立即浇水
10秒声音片段 声音分类模型 雨声候选0.66 实际降雨量和土壤状态
文字问题与结构化证据 语言模型 观察摘要和待确认项 未出现在证据中的事实

采集前应明确最少输入。H0路径使用教师提供的样例图片和预录数据,不需要真实传感器。若增加声音,必须取得可能被录入人员的同意,限制录音时长,并在特征或分类结果生成后删除原始录音。不是完成任务所必需的数据就不采集。

声音或手势互动变体仍沿用“来源—采集时间—质量—候选—人工确认”接口。声音任务优先选择拍手、敲击等短促非语音类别,不采集对话,不做说话人识别或声音克隆;手势任务只裁取手部区域,不保存人脸和背景。识别结果只改变虚拟界面状态,误识别时可以立即撤销或改用键盘按钮。

二、给模型结果规定共同接口

不同模型不能只返回一句自然语言。每条结果至少包含来源、采集时间、标签或数值、分数、输入质量、模型版本和原始证据编号。例如视觉结果可写为“来源vision,标签yellow,分数0.78,质量ok,距当前3分钟”;时序结果可写为“来源environment,标签=soil_drop,分数0.82,质量ok,观察窗口24小时”。

统一接口使协调程序能够检查:必要来源是否齐全;证据是否过期;图像是否模糊;时序窗口是否缺记录;分数是否达到课堂设定的接受条件。模型版本也要记录,因为更新模型后,同一输入可能出现不同结果。

时间对齐非常重要。今天的叶片照片不能与一个月前的湿度记录拼成当前报告。声音事件也应有时间范围。若输入不同步,系统应并列展示而不是强行解释为同一原因。

三、区分两种多模态路线

原生多模态模型在同一个模型内部联合接收图像、文字、声音等输入,能够直接回答跨模态问题。多个专用模型串联系统则分别运行视觉、声音和时序模型,再由协调程序或语言模型组合结果。后者使用了多种模态,但不能因此宣称每个专用模型本身都是多模态模型。

比较项 原生多模态模型 多个专用模型串联
优点 跨模态提问自然、上下文统一 每个结果可单独测试、可替换、可离线
难点 内部推理较难分开核对 接口、时间对齐和错误传播更复杂
适用判断 需要联合理解且允许人工核验 需要明确分工、局部替换或设备端运行

本项目采用专用模型串联,便于复用前面章节成果。语言模型只能读取经过协调程序核验的结构化证据,并被要求区分“观察到”“模型候选”和“尚不能判断”。若它补写未提供的病因,输出必须被拒绝或重写。

第二节 完成云端、本地端与设备端的系统集成

一、把计算放在合适的位置

云端通常能提供较强的通用视觉和语言能力,但需要上传数据,受网络、费用和服务变化影响。本地端在普通计算机上运行协调程序和适合设备资源的模型,便于保护隐私、查看日志和离线工作。设备端靠近摄像头、麦克风或低压传感器,响应快、上传少,但算力、存储和电量有限,通常运行压缩后的小模型。

系统不必把所有模型放在同一位置。可以在设备端先计算声音类别和环境特征,只把结构化结果送到本地端;本地端完成时间对齐和安全检查;只有获得授权且确有必要时,才把最少证据交给云端语言模型生成说明。部署选择应记录原始数据去了哪里、保存多久、断网后怎样降级。

二、三级等价实践路径

路径 输入与运行条件 必做成果 动作边界
H0 仿真 样例图片、预录声音、环境数据文件 完成融合、虚拟动作—反馈、失败测试和判断 虚拟状态灯或屏幕显示,并回读显示状态
H1 共享设备 小组共享摄像头、麦克风或低压环境采集模块 比较实时输入与样例输入 只提示,不控制设备
H2 设备端拓展 把小型视觉、声音或时序模型放到边缘设备 测试延迟、断网、功耗和模型更新 仅低风险指示灯或显示器,保留物理停止

三条路径学习目标等价,没有硬件的学生使用H0也能完成本章。H0把“动作”限制为改变虚拟状态灯,并由程序回读实际状态;这样仍能观察“感知—计算—行动—环境反馈”的闭环及反馈失效。H1和H2增加的是采集与部署证据,不增加自动控制权限。设备端模型升级前要保留旧版本和固定回归样例,升级失败可以回退。

低风险迁移也使用三级路径。声音或手势互动的H0读取预录短声和裁剪后的手势图片,H1才读取经同意的实时片段;物品整理与循环利用助手的H0使用教师提供的清洁物品图片,H1可拍摄小组自带的安全物品。输出始终是虚拟分类卡和来源说明,破损电池、药品、化学品、尖锐物和其他危险废弃物不进入课堂采集,循环利用建议必须由人结合当地规定确认。

技术视野:MCP是一类让AI按统一描述连接外部工具的可选接口方法,本项目理解其用途即可,不把搭建协议基础设施作为学习要求。

三、用普通程序守住模型之间的边界

下面程序不训练模型,而是接收各专用模型已经生成的结果,检查必需来源、模型版本、采集时间、质量和时效,再形成允许交给语言模型的证据包。不同模型的分数含义不同,因此程序使用按来源设定并经过各自验证的阈值,不把视觉0.78与声音0.78当成可以直接比较的同一把尺子。程序还检查共同“是否需要复核”标志的冲突,并把动作限制为虚拟显示和状态回读。

from dataclasses import dataclass
from datetime import datetime
import json
import math


@dataclass(frozen=True)
class ModelResult:
    source: str
    label: str
    score: float
    score_kind: str
    quality: str
    captured_at: str
    evidence_id: str
    model_version: str


@dataclass(frozen=True)
class SourceRule:
    score_kind: str
    min_score: float
    max_age_minutes: int


# 这是课堂演示规则;正式阈值必须由各模型的封存数据分别验证。
SOURCE_RULES = {
    "vision": SourceRule("class_probability", 0.75, 15),
    "environment": SourceRule("anomaly_probability", 0.65, 60),
    "sound": SourceRule("class_probability", 0.65, 15),
}

# 协调程序只比较事先定义的共同任务标志,不凭标签名称猜含义。
REVIEW_FLAG = {
    ("vision", "normal"): "normal",
    ("vision", "yellow"): "review",
    ("vision", "spot"): "review",
    ("environment", "stable"): "normal",
    ("environment", "soil_drop"): "review",
}


def stop_packet(reason, details):
    return {
        "status": "stop",
        "reason": reason,
        "details": details,
        "allowed_action": "virtual_display_only",
    }


def build_evidence_packet(results, now):
    required_sources = {"vision", "environment"}
    sources = [result.source for result in results]
    duplicate_sources = sorted(
        {source for source in sources if sources.count(source) > 1})
    if duplicate_sources:
        return stop_packet("duplicate_source", duplicate_sources)

    evidence_ids = [result.evidence_id for result in results]
    duplicate_ids = sorted(
        {item for item in evidence_ids if evidence_ids.count(item) > 1})
    if duplicate_ids:
        return stop_packet("duplicate_evidence_id", duplicate_ids)

    unsupported = sorted(set(sources) - set(SOURCE_RULES))
    if unsupported:
        return stop_packet("unsupported_source", unsupported)

    by_source = {result.source: result for result in results}
    missing = sorted(required_sources - set(by_source))
    if missing:
        return stop_packet("missing_required_source", missing)

    validation_errors = []
    observed_times, ages = {}, {}
    for result in results:
        rule = SOURCE_RULES[result.source]
        if result.score_kind != rule.score_kind:
            validation_errors.append(
                f"{result.evidence_id}:score_kind_mismatch")
        if not math.isfinite(result.score) or not 0.0 <= result.score <= 1.0:
            validation_errors.append(f"{result.evidence_id}:invalid_score")
        if not result.model_version.strip():
            validation_errors.append(f"{result.evidence_id}:missing_model_version")
        if result.quality != "ok":
            validation_errors.append(f"{result.evidence_id}:invalid_quality")
        try:
            observed = datetime.fromisoformat(result.captured_at)
            if observed.tzinfo is None:
                raise ValueError("timezone required")
            age = (now - observed).total_seconds() / 60
            observed_times[result.evidence_id] = observed
            ages[result.evidence_id] = age
            if age < 0:
                validation_errors.append(f"{result.evidence_id}:future_time")
            elif age > rule.max_age_minutes:
                validation_errors.append(f"{result.evidence_id}:stale")
        except (TypeError, ValueError):
            validation_errors.append(f"{result.evidence_id}:invalid_time")
    if validation_errors:
        return stop_packet("invalid_evidence", validation_errors)

    uncertain = [
        result.evidence_id for result in results
        if result.score < SOURCE_RULES[result.source].min_score
    ]
    review_flags = {
        source: REVIEW_FLAG.get((source, by_source[source].label), "unknown")
        for source in required_sources
    }
    known_flags = {flag for flag in review_flags.values() if flag != "unknown"}
    conflicts = (
        ["vision_environment_review_flag_conflict"]
        if len(known_flags) > 1 else []
    )
    unknown_flags = [
        by_source[source].evidence_id
        for source, flag in review_flags.items() if flag == "unknown"
    ]
    alignment_minutes = (
        max(observed_times.values()) - min(observed_times.values())
    ).total_seconds() / 60

    facts = [
        {
            "source": result.source,
            "candidate": result.label,
            "score": round(result.score, 3),
            "score_kind": result.score_kind,
            "source_threshold": SOURCE_RULES[result.source].min_score,
            "captured_at": result.captured_at,
            "age_minutes": round(ages[result.evidence_id], 1),
            "evidence_id": result.evidence_id,
            "model_version": result.model_version,
        }
        for result in results
    ]
    review_reasons = []
    if uncertain:
        review_reasons.append("below_source_threshold")
    if conflicts:
        review_reasons.append("source_conflict")
    if unknown_flags:
        review_reasons.append("unmapped_review_flag")
    if alignment_minutes > 20:
        review_reasons.append("time_alignment_warning")
    return {
        "status": "needs_human_review" if review_reasons else "ready_for_summary",
        "facts": facts,
        "review_flags": review_flags,
        "time_alignment_minutes": round(alignment_minutes, 1),
        "uncertain_evidence": uncertain,
        "conflicts": conflicts,
        "unmapped_evidence": unknown_flags,
        "review_reasons": review_reasons,
        "language_instruction": (
            "只复述带证据编号的观察和候选结果;不得诊断原因,"
            "不得生成设备控制指令;列出需要人确认的项目。"
        ),
        "allowed_action": "virtual_display_only",
    }


@dataclass
class VirtualIndicator:
    state: str = "blank"
    fail_to_update: bool = False

    def apply(self, command):
        if not self.fail_to_update:
            self.state = command

    def read(self):
        return self.state


def run_h0_cycle(results, now, indicator):
    packet = build_evidence_packet(results, now)
    command = {
        "ready_for_summary": "show_ready_for_review",
        "needs_human_review": "show_attention",
        "stop": "show_stop",
    }[packet["status"]]
    indicator.apply(command)
    observed_state = indicator.read()
    feedback_ok = observed_state == command
    if not feedback_ok:
        packet = {
            **packet,
            "status": "stop",
            "reason": "display_feedback_mismatch",
            "allowed_action": "virtual_display_only",
        }
    return {
        "evidence_packet": packet,
        "issued_command": command,
        "observed_display": observed_state,
        "feedback_ok": feedback_ok,
    }


sample_results = [
    ModelResult("vision", "yellow", 0.78, "class_probability", "ok",
                "2026-05-18T09:02:00+08:00", "IMG-021", "vision-demo-1.2"),
    ModelResult("environment", "soil_drop", 0.82, "anomaly_probability", "ok",
                "2026-05-18T08:57:00+08:00", "ENV-024", "timeseries-demo-1.1"),
    ModelResult("sound", "rain", 0.66, "class_probability", "ok",
                "2026-05-18T09:00:00+08:00", "AUD-006", "audio-demo-1.0"),
]

now = datetime.fromisoformat("2026-05-18T09:05:00+08:00")
normal_cycle = run_h0_cycle(sample_results, now, VirtualIndicator())
feedback_fault = run_h0_cycle(
    sample_results, now, VirtualIndicator(fail_to_update=True))
print("正常闭环:")
print(json.dumps(normal_cycle, ensure_ascii=False, indent=2))
print("显示反馈故障:")
print(json.dumps(feedback_fault, ensure_ascii=False, indent=2))

这段代码本身不是新的AI模型,而是普通确定性协调程序。它要求图像和环境两类必要证据齐全,拒绝重复来源或证据编号、未知接口、错误分数类型、越界数值、空版本、未来时间、过期或质量异常输入。年龄由带时区的采集时间和当前时间计算,不能由上游手填一个“看起来新鲜”的分钟数。各来源的分数类型、最低分和最长时效写在独立规则中;这里的数值只是课堂演示,正式项目必须由各模型的封存数据决定。若多源采集时间相差超过20分钟,系统也转人工确认。

图像和环境标签先由明确映射转换为共同的review_flag;二者一方为normal、另一方为review时,程序只记录冲突并转人工,不让语言模型猜一个原因。这个共同标志是项目接口,不代表两类模型分数具有相同统计意义。声音是可选来源,因此声音缺失不阻断H0核心任务;一旦提供,也要接受自己的分数类型、质量、时效和阈值检查。

run_h0_cycle根据证据包发出虚拟显示命令,再回读状态。第二次运行故意让虚拟状态灯不更新,回读值与命令不一致时系统进入停止状态。这一小段补上了物理AI最基本的动作—反馈关系,同时没有把教材实验扩大为真实浇水或设备控制。学生还应把环境样例的标签改为stable,观察视觉review与环境normal怎样触发冲突路径。

程序生成的证据包可由语言模型整理,也可以在模型不可用时用固定模板直接显示。纸书不绑定具体服务;规划中的数字资源应为经过教师选择的模型提供独立适配器。适配器只能把证据包送入模型并取回文字,不能修改allowed_action。

第三节 测试失效链路、安全动作与人的接管

一、系统错误会沿链路传播

多模型系统的风险不仅是某个模型识别错误,还包括旧数据被当成新数据、字段含义不一致、网络重试产生重复结果、语言模型把候选写成事实,以及显示端隐藏停止状态。测试必须从输入一直追到最终输出。

故障注入 预期安全行为 不可接受行为
图像模糊或缺失 停止摘要,提示重新采集 沿用上次图像结果
环境记录超过时限 标记过期并停止融合 与当前照片强行组合
声音权限被拒绝 进入无声音的降级路径 反复请求或偷偷录音
两模型结果冲突 并列证据并转人工确认 由语言模型猜一个原因
网络中断 使用本地模板或只保存证据 重复发送、丢失原始记录
生成内容出现无证据病因 拒绝该段文字并保留日志 将文字转成控制动作

正常样例也要测试:图像、环境和声音时间一致、质量合格,系统能产生带证据编号的摘要。边界样例包括分数刚好接近阈值、短时缺一条记录和两个结果不一致。陌生样例包括非植物照片、训练外声音和超出数值范围的环境输入。故障样例包括模型不可用、字段改名、文件损坏和网络中断。

二、动作必须有权限分层

系统动作可分三层。第一层是保存证据和显示信息,可自动完成;第二层是生成提醒草稿,必须由人确认后发送;第三层是改变物理环境的动作,本项目一律禁止。语言模型无论文字多么肯定,都不能获得第三层权限。

普通程序维护动作白名单,语言模型只生成解释字段。H2拓展若连接低风险指示灯,也只允许显示“有待确认证据”,并提供物理关闭开关。水泵、加热器、门窗、电源和可能造成人身或财产风险的设备不在教材实验范围。

人的接管不能只写成一句“必要时人工处理”。界面要显示原始证据编号、各模型分数、采集时间、停止原因和可用按钮;接管者可以接受、驳回、重新采集或保持原状态。系统记录谁在何时确认了什么,但不记录与任务无关的个人信息。

三、形成系统级判断

判断系统是否可用,要分别检查组件和整体。组件测试回答视觉、声音、时序和语言模型各自表现怎样;接口测试回答字段、时间和错误状态能否正确传递;端到端测试回答从输入到显示是否保持来源和权限;恢复测试回答断网、重启和模型升级后能否回到安全状态。

还要比较云端、本地端和设备端方案。若结构化结果在本地模板中已经清楚,就没有必要为更流畅的表达上传原图。若设备资源不足,可降低采样频率或回到H0,不应删除安全检查换取速度。系统复杂度增加后,维护成本和失效点也会增加,多一个模型必须有可验证的收益。

最终判断卡至少写明:使用哪些输入;每个模型的边界;原始数据保存位置和删除方式;断网与模型失效时的降级路径;允许动作;人工接管者;模型或设备更新后的回归方法。只有当多源证据比单一输入稳定增加有用信息,并且隐私、安全和维护负担可以承担时,系统集成才有价值。

四、自主综合项目:用AI改善一个自己的生活场景

所有学生先用同一封存样例完成“打造一个AI阳台植物园”的H0基线,再开展自主项目。可直接选择“做一个会识别声音或手势的互动作品”“建立家庭物品整理与循环利用助手”,也可提出同等低风险的学习、兴趣或生活场景。健康咨询、用药、急症判断等健康场景不作为自由项目;涉及支付、门锁、交通控制、高功率设备或人身安全的自动动作也不在范围内。

第一步填写任务画布,而不是先让模型生成整套程序。画布至少写明:要改善的具体问题;使用者;最小输入和输出;哪些内容由人确认;哪些用普通规则;是否需要模型;云端、本地端或设备端位置;数据授权与删除;允许动作和停止条件;一个最小成功样例。开发与学习AI可以追问缺失条件并分段生成代码,学生必须逐段说明输入、输出、失败行为和自己作出的决定。

第二步只实现最小系统。系统图中的每个方框必须对应真实文件或明确组件,不能画一张与作品无关的理想流程图。例如:input/sample.jpg对应采集输入,src/vision_adapter.py对应模型适配,evidence/vision.json对应结构化候选,src/coordinator.py对应规则与权限,output/report.md对应界面输出,tests/cases.json和evidence/regression.md对应固定测试与结果。学生应从一次输入沿编号追到最终输出,证明图、文件和实际运行一致。

第三步比较五种路线:完全由人完成、普通规则程序、云端模型、本地模型以及不用AI。比较项至少包括效果证据、隐私、响应速度、费用、维护、断网行为和错误后果。若人工或规则已经稳定完成任务,模型没有带来可验证收益,就应缩小模型责任或不用AI;“最后没有采用AI”可以是合格结论,但必须有比较记录。

第四步封存正常、边界、陌生和故障四类固定样例。声音或手势作品应测试背景噪声、相似手势、无目标输入和模型缺失;物品助手应测试常见清洁物品、遮挡、训练外物品和规则文件损坏。每类测试写明输入、预期输出、停止位置、界面提示和日志,修改后用相同样例回归。最终提交最小系统、真实文件对应图、路线比较表、测试记录和采用、限用或不采用判断。

本章小结

多模态系统把图像、声音、时序和语言等不同信息连接起来。原生多模态模型在一个模型内联合处理多种输入,多个专用模型串联则依靠统一接口和协调程序组合结果。云端、本地端和设备端各有数据、能力与成本取舍,H0、H1、H2提供等价学习路径。全班以“打造一个AI阳台植物园”为共同基线,再把接口和测试方法迁移到声音或手势互动、家庭物品整理与循环利用等低风险场景。物理AI强调感知、计算、行动和环境反馈;H0用虚拟显示和状态回读建立低风险闭环,H1、H2也只允许提示与人工确认。可靠系统必须让真实文件对应系统图,比较人工、规则、云端、本地端与不用AI的路线,测试整条失效链路,并允许最终决定不采用AI。

关键术语

目标测试

一、单项选择题

  1. 下列哪一项属于系统层多模态? A. 单一视觉模型只处理图片 B. 视觉、声音和时序专用模型由协调程序连接 C. 普通表格求平均 D. 只显示固定文字
  2. 为什么模型结果需要采集时间和质量字段? A. 便于更换颜色 B. 检查证据是否过期或不可用 C. 增加模型参数 D. 代替人工授权
  3. H0路径的主要特点是什么? A. 必须购买设备 B. 使用样例和预录数据完成全部核心学习 C. 自动控制水泵 D. 跳过失效测试
  4. 语言模型在本章系统中可以执行哪一项? A. 根据结构化证据生成待核对摘要 B. 绕过动作白名单 C. 自动控制高功率设备 D. 修改原始采集时间

二、判断并改错

  1. 判断并改错:“连接了视觉、声音和时序三个专用模型,就说明其中每个模型都是原生多模态模型。”
  2. 判断并改错:“语言模型生成的解释很肯定时,可以直接把它转换成物理控制指令。”

三、简答与操作题

  1. 说明视觉、环境时序和声音输入在AI阳台植物园中分别能提供什么证据,以及各自不能单独证明什么。
  2. 比较云端、本地端和设备端,分别写出一项优势、一项限制和适合放置的组件。
  3. 设计一个“环境数据过期”的端到端故障测试,写出输入、预期停止位置、界面提示和需要保留的日志。
  4. 从“做一个会识别声音或手势的互动作品”或“建立家庭物品整理与循环利用助手”中选择一个,写出最小任务画布、至少四个真实文件或组件的对应关系、人工/规则/云端/本地端/不用AI的比较重点,以及四类固定测试。说明在什么证据下可以最终不采用AI。

附录A 项目证据包与评价量规

一、章级学习记录与综合项目证据包

第1—11章以理解技术为主,不要求每章建立复杂工程目录。学生可以用一份章级学习记录保存最少而充分的证据:

chXX-learning-record/
├─ task.md                # 技术问题、输入、输出和边界
├─ input/                 # 样例、数据或材料说明
├─ experiment.ipynb      # 需要代码时提供完整Notebook
├─ result.md              # 首轮结果与控制变量比较
├─ test.md                # 正常、边界、陌生和故障样例
└─ decision.md            # 采用、限用、改进、不用或转交

第1章可以用一张学习记录表代替文件目录。第2—11章根据技术需要提交Notebook;不要求为了形式额外制作网页、数据库、版本差异文件或长篇对话记录。

学习记录还应注明本章在家庭项目线中的角色:主项目、跨章阶段、受控案例或迁移任务。阳台植物园、家庭空气与用能观察站等跨章项目沿用同一个项目编号;本章只新增当前技术所需的材料,并在result.md中列出交给下一阶段的数据、模型、固定测试、来源记录和未解决问题,避免每章重新做一个互不相干的演示。

第12章综合项目连接多个模型、工具或设备,再使用完整证据包。提交前删除个人信息、访问密钥和未经授权的图片、声音或文档。

chXX-project/
├─ README.md              # 项目目标、使用者、边界、运行和停止方式
├─ task-context.md        # 交给开发与学习AI的任务、材料和验收条件
├─ input/                 # 模拟、脱敏或经授权的原始材料
├─ src/ 或 notebook.ipynb # 完整代码、配置和运行入口
├─ tests/cases.json       # 正常、边界、陌生、故障样例及预期行为
├─ output/first/          # 首轮中间结果和最终结果
├─ output/final/          # 修改后结果
├─ evidence/dialogue.md   # 关键AI对话与学生核对
├─ evidence/error.log     # 一次真实错误或失败输出
├─ evidence/change.diff   # 如修改程序,保留一次限定修改差异
├─ evidence/regression.md # 修改前后固定样例回归
├─ system-card.md         # 数据、模型、规则、工具、设备和人的说明
└─ decision-card.md       # 采用、限制、改进、不用或转交

README.md至少写清:项目解决什么问题;如何从头运行;需要哪些输入和环境;哪里使用了应用中的AI;开发与学习AI帮助了什么;谁负责确认;哪些情况必须停止。

tests/cases.json中的每条样例至少包含编号、类型、输入或文件路径、测试目的、预期行为、实际行为和是否通过。失败样例不能只测程序报错,还要测试资料缺失、陌生输入、低置信度、权限拒绝和模型不可用。

decision-card.md用一页回答:证据支持在哪些条件下使用;最不能接受的错误;人工确认点;云端、本地端、设备端或不用AI的选择;隐私、费用和维护负担;停止与转交条件;下一次改进还缺什么证据。

其中数据来源、构成、授权和限制可参考“数据说明表”的思想,模型用途、评价条件和不适用场景可参考“模型卡”的思想[9-10]。本书把两者缩减成适合课堂项目的证据字段,不要求机械照抄研究模板。

二、学习与项目评价量规

每项0—2分,总分20分。0分表示缺失或存在关键错误,1分表示部分完成且仍需指导,2分表示证据完整、解释准确。普通章节可以用学习记录和现场说明提供证据;第12章使用完整证据包。建议16分以上达到要求;“技术准确性、实现或实验完整性、测试证据、安全与维护”任一项为0,应修改后再评。

评价项 2分证据
1. 问题与AI必要性 使用者、真实问题、验收和不用AI基线明确,能说明为何选择或不选择AI
2. 材料与授权 来源、时间、字段、脱敏和授权记录完整
3. 技术准确性 数据、表征、模型、规则、软件、设备和人的分工准确
4. 实验或实现完整性 有可重复的操作或完整Notebook、运行条件和中间结果;第1章可无代码
5. AI协同过程 能说明开发与学习AI给了什么候选,学生核对、运行和决定了什么
6. 控制变量 一次只改变一个有意义变量,先预测、再运行、后解释
7. 失败与测试 正常、边界、陌生、故障样例及预期行为齐全
8. 比较与复验 使用同一批样例比较修改或变量变化前后,并说明副作用
9. 安全与维护 隐私、权限、错误后果、停止、回退、版本和维护明确
10. 判断与迁移 采用结论有证据,能说明限制、替代方案和迁移条件

项目展示不单独以界面美观计分。现场答辩至少运行一个失败样例,指出系统图中的模型、规则和人工确认点,并说明一项没有采纳的AI建议及理由。

附录B 术语表

参考文献

中文文献

[1] 教育部,国家发展改革委,工业和信息化部,科技部,国家数据局. “人工智能+教育”行动计划[EB/OL]. 2026-04-02[2026-08-05]. https://hudong.moe.gov.cn/srcsite/A16/s3342/202604/t20260410_1433240.html.

[2] 国家互联网信息办公室,国家发展和改革委员会,教育部,等. 生成式人工智能服务管理暂行办法[EB/OL]. 2023-07-10[2026-08-05]. https://www.gov.cn/zhengce/zhengceku/202307/content_6891752.htm.

[3] 全国人民代表大会常务委员会. 中华人民共和国个人信息保护法[EB/OL]. 2021-08-20[2026-08-05]. https://www.npc.gov.cn/npc/c2/c30834/202108/t20210820_313088.html.

[4] 国家新一代人工智能治理专业委员会. 新一代人工智能伦理规范[EB/OL]. 2021-09-25[2026-08-05]. https://www.most.gov.cn/kjbgz/202109/t20210926_177063.html.

英文文献

[5] UNESCO. AI Competency Framework for Students[R]. Paris: UNESCO, 2024.

[6] NIST. Artificial Intelligence Risk Management Framework (AI RMF 1.0)[R]. Gaithersburg: National Institute of Standards and Technology, 2023.

[7] AUTIO C, SCHWARTZ R, DUNIETZ J, et al. Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile[R]. Gaithersburg: NIST, 2024. DOI:10.6028/NIST.AI.600-1.

[8] WORLD HEALTH ORGANIZATION. Ethics and Governance of Artificial Intelligence for Health: Guidance on Large Multi-Modal Models[R]. Geneva: WHO, 2024.

[9] GEBRU T, MORGENSTERN J, VECCHIONE B, et al. Datasheets for datasets[J]. Communications of the ACM, 2021, 64(12):86-92. DOI:10.1145/3458723.

[10] MITCHELL M, WU S, ZALDIVAR A, et al. Model cards for model reporting[C]//Proceedings of the Conference on Fairness, Accountability, and Transparency. New York: ACM, 2019:220-229. DOI:10.1145/3287560.3287596.

[11] VASWANI A, SHAZEER N, PARMAR N, et al. Attention is all you need[C]//Advances in Neural Information Processing Systems 30. 2017:5998-6008.

[12] LEWIS P, PEREZ E, PIKTUS A, et al. Retrieval-augmented generation for knowledge-intensive NLP tasks[C]//Advances in Neural Information Processing Systems 33. 2020:9459-9474.

[13] KERAS. Transfer Learning & Fine-Tuning[EB/OL]. [2026-08-05]. https://keras.io/guides/transfer_learning/.

[14] KERAS. Keras Code Examples: Vision, Audio, Natural Language Processing and Timeseries[EB/OL]. [2026-08-05]. https://keras.io/examples/.

[15] TENSORFLOW. Simple Audio Recognition: Recognizing Keywords[EB/OL]. [2026-08-05]. https://www.tensorflow.org/tutorials/audio/simple_audio.

[16] GOOGLE AI EDGE. LiteRT: High-Performance On-Device Machine Learning Framework[EB/OL]. 2026[2026-08-05]. https://developers.google.com/edge/litert.

[17] JSON SCHEMA. Creating Your First Schema[EB/OL]. [2026-08-05]. https://json-schema.org/learn/getting-started-step-by-step.

[18] GOOGLE. Embeddings: Embedding Space[EB/OL]. [2026-08-05]. https://developers.google.com/machine-learning/crash-course/embeddings/embedding-space.

[19] MIKOLOV T, CHEN K, CORRADO G, et al. Efficient Estimation of Word Representations in Vector Space[EB/OL]. 2013. https://arxiv.org/abs/1301.3781.

[20] HO J, JAIN A, ABBEEL P. Denoising Diffusion Probabilistic Models[C]//Advances in Neural Information Processing Systems 33. 2020:6840-6851. https://arxiv.org/abs/2006.11239.

[21] SUTTON R S, BARTO A G. Reinforcement Learning: An Introduction[M]. 2nd ed. Cambridge: MIT Press, 2018.

[22] KOREN Y, BELL R, VOLINSKY C. Matrix Factorization Techniques for Recommender Systems[J]. Computer, 2009, 42(8):30-37. DOI:10.1109/MC.2009.263.

[23] KERAS. Structured Data Classification from Scratch[EB/OL]. [2026-08-05]. https://keras.io/examples/structured_data/structured_data_classification_from_scratch/.

[24] KERAS. Collaborative Filtering for Movie Recommendations[EB/OL]. [2026-08-05]. https://keras.io/examples/structured_data/collaborative_filtering_movielens/.

[25] KERAS. Actor Critic Method for CartPole[EB/OL]. [2026-08-05]. https://keras.io/examples/rl/actor_critic_cartpole/.

目标测试参考答案

第1章 从示例到模型:完成第一次机器学习

  1. B。“水杯”是人依据任务规则给样本指定的类别标签;照片是样本,摄像头是输入设备,记录表是证据载体。
  2. C。训练依据带标签样本产生的差异调整模型参数;推理保持参数不变,用训练完成的模型处理新输入。
  3. B。训练图片已经参与参数调整,再用它们测试容易得到过于乐观的结果,不能证明模型对未见样本的泛化能力。
  4. C。物品不变而只交换类别背景,预测跟随背景改变,是模型利用背景线索的直接证据。
  5. 错误。0.90是模型在当前类别和数据条件下给出的相对分数,不是现实世界中“物品为水杯”的可靠概率。还要结合任务范围、第二名分数、输入质量和人工确认作判断。
  6. 错误。大量近乎相同的连拍图变化很小,模型可能记住物品、角度和背景。应增加不同物品个体、角度、距离、背景与光照,并使用未参加训练的样本测试。
  7. 参考要点:样本是一幅用于训练或测试的物品图像;标签是人为规定的水杯、收纳盒或其他家庭物品类别;训练是依据带标签样本的输出差异调整模型参数;推理是用已经训练好的模型处理新图像并输出类别分数。四项各说明一项即可得相应分值。
  8. 参考方案:训练时所有类别都分别包含白纸和木纹桌面背景,或先建立一个故意让类别与背景绑定的对照模型;测试时保持物品、距离、角度和光照不变,只交换背景;记录三个类别分数、最高候选、人工答案和是否接受。能明确“只改变背景”并使用同一组物品前后比较为关键得分点。
  9. 应将其认定为任务范围外输入,不接受“收纳盒”结论,保留图像编号和三个类别分数并转人工确认。理由是三分类模型会在已有类别中分配分数,本身没有可靠的陌生类别判断能力,高分也不能扩大训练范围。
  10. 参考结论:“该模型只可在与普通家庭桌面相近、光照正常的条件下作三类物品演示。普通背景10次正确8次,证据规模仍小;强背光下5次只正确2次,不能在背光条件采用;陌生输入均得到高分,说明模型缺少范围外拒绝能力。出现背光、陌生物体、最高分与第二名接近时必须停止自动接受并由人确认。”应同时包含适用条件、证据限制和确认规则。

第2章 结构化数据学习:从表格中发现规律

  1. B。任务要预测的是状态类别,因此状态类别是标签;温度是特征,日期编号用于标识记录,光照单位属于字段规范。
  2. C。预测时不可能提前获得次日人工结论,把它作为当天输入会把答案或未来信息泄漏给模型。
  3. A。始终预测训练集中数量最多的类别,是简单、可重复的分类基线。
  4. B。训练损失继续下降而验证损失持续上升,说明模型更贴合训练细节,却没有改善对未见数据的表现,是典型过拟合信号。
  5. 错误。训练集用于调整模型参数;验证集用于选择模型、训练轮数和阈值;测试集应在方案确定后作最终检查。反复依据测试结果选模型,会使测试集失去独立检验作用。
  6. 错误。回归输出连续数值,应使用平均绝对误差、均方误差等连续数值指标,并与均值等回归基线比较,不能直接使用分类正确率。
  7. 示例:平均温度是模型可获得的输入特征;“正常/需要关注”是分类任务希望预测的离散标签;次晨土壤湿度是回归任务希望预测的连续数值标签。学生也可选择光照、空气湿度或傍晚土壤湿度作为特征,但需说明作用。
  8. 基线提供最低参考,使学生判断复杂模型是否带来真实收益,也能暴露类别不平衡造成的虚高数字。分类可始终预测训练集多数类;回归可始终输出训练标签平均值,再计算平均绝对误差。两类基线各一项且解释用途为完整答案。
  9. 训练表现显著高于验证和测试,可能发生过拟合,也要检查数据划分和训练数据泄漏。可采取的措施包括:检查未来信息或答案线索;按时间段或植物个体重新划分;减少模型规模;使用提前停止;增加有意义的不同样本;清理错标。写出问题判断及两项合理检查或改进即可。
  10. 应单独统计“需要关注”类别的召回或漏报数量,不能只看整体正确率。选择阈值时可适当降低进入“需要关注”候选的门槛,以减少漏报,同时接受误报和人工复查量可能上升的代价。特征缺失、输入超范围或分数接近时转人工检查,模型不得直接执行养护动作。答案需体现指标、阈值与人工责任三方面。

第3章 计算机视觉:让模型理解图像

  1. B。图像进入模型时是具有高度、宽度和颜色通道的像素数值数组,植物名称和养护结论不是图像自带的信息。
  2. B。迁移学习复用预训练网络已经学到的边缘、纹理和形状等通用视觉表示,再用任务数据学习新分类层。
  3. C。图像分割为每个像素预测区域类别,适合描出不规则斑点区域并估计面积比例。
  4. C。把同一植物个体的全部照片放入同一份数据,可减少模型通过个体纹理、拍摄环境或近重复图片取得虚高测试结果。
  5. 错误。冻结基础网络只表示其预训练参数在第一阶段不更新,新增加的池化后分类层仍需依据本项目数据训练。
  6. 错误。已有类别的分类模型通常会对陌生输入强制分配分数,高分不能证明输入属于任务范围。塑料叶应视为陌生样例,记录结果并由人确认或拒绝。
  7. 参考要点:卷积核在图像的局部区域重复进行共享计算;每个卷积核在整幅图上产生一张特征图;较深层把边缘、颜色变化和纹理等浅层模式逐层组合成更复杂的形状或区域线索。三点关系完整即可。
  8. 分类为整幅图输出一个主要类别及各类分数;检测输出每个目标的类别、分数和矩形位置;分割为每个像素输出区域类别,可描出叶片或斑点的具体形状。
  9. 参考方案:选定同一株未参加训练的植物,固定相机距离、角度、光照和叶片位置,只把背景依次换为白纸、木纹和复杂桌面;每种背景重复拍摄并记录真实标签、三个类别分数、最高候选和是否接受;比较预测是否随背景系统变化。若使用不同植物,应保证各背景下使用的是同一组植物并保持其他条件一致。
  10. 暂不应把该模型作为自动采用的状态判断工具。虽然整体正确率为86%,但斑点类召回只有4/10,关键类别表现不足;陌生输入高分还表明范围外拒绝能力弱。后续可检查斑点类标签与样本多样性、按植物个体复核数据划分、补充不同背景和斑点形态的授权图片、设置陌生输入与低差值人工确认、逐类报告混淆矩阵,并用固定测试集回归。答出不直接采用的判断和三项有针对性的措施即可。

说明:选择题每题只有一个最佳答案;判断改错题必须同时判断正误并写出正确表述;简答与操作题按要点给分,表达不同但技术含义正确、证据完整的答案可同等给分。涉及实验结果的题目以学生使用固定测试集得到的实际记录为准,不要求得到预设数字。

第4章 音频人工智能:让模型识别声音

一、单项选择题

  1. B。频谱图表示不同时间窗口中各频率成分的强弱;波形主要表示振幅随时间的变化。
  2. B。相邻切片通常高度相似,分到训练和测试两边会造成数据泄漏,使测试结果过于乐观。
  3. C。前两类分数接近表示模型难以区分,应该保留全部分数并输出未知,而不是强行触发确定提醒。
  4. B。保持模型不变时,提高阈值只会改变接受候选的规则,通常使确定分类减少、未知增加,不会自动重新训练模型。

二、判断并改错

  1. 错误。课堂声音分类器只在有限类别、设备、距离和噪声条件下接受测试,可以用于低风险辅助记录,不能替代具有专门标准、硬件和失效保障的安全报警器,也不得直接连接门锁、电源等高风险设备。
  2. 错误。other是训练数据中明确标注的一个已知类别,表示样本接近所收集的“其他常见声音”;unknown是最高分不足或前两名差值过小时,由模型外规则给出的拒绝状态。

三、简答与操作题

  1. 评分要点:
  1. 示例及评分要点:
  1. 评分要点:
  1. 评分要点:

第5章 时间序列:从连续记录中发现变化

一、单项选择题

  1. B。保持时间先后才能模拟“用过去预测未来”,并防止未来或测试信息进入训练过程。
  2. C。复杂模型没有超过简单基线时,尚未证明新增复杂度有价值,应保留基线并检查数据、任务和评价。
  3. B。残差较大只说明实际值与模型预测不一致,需要复核;它不能直接证明危险、故障或变化原因。
  4. C。归一化的均值和标准差应由训练时段计算,再应用到验证和测试时段。

二、判断并改错

  1. 错误。测试时段应在模型和规则基本确定后用于最终评价,不能加入训练后继续报告为原来的独立测试。若测试数据参与调参或训练,应重新准备未参与开发的封存测试时段。
  2. 错误。一维卷积模型只能从历史窗口中学习有助于预测的模式。测试误差较小表示在该时段预测较接近实际值,不表示模型理解了变化的真实原因;原因需要设备日志、现场记录或其他证据核查。

三、简答与操作题

  1. 评分要点:
  1. 评分要点:
  1. 示例及评分要点:
  1. 评分要点:

第6章 自然语言处理:让机器处理文本任务

一、单项选择题

  1. A。为整段文本预测预先定义的类别属于文本分类。
  2. B。词表只适配训练文本,可以避免验证和测试词汇统计进入训练过程,使评价更接近真实新文本。
  3. C。原文已经说明地点尚未确定,因此应保持空值、保留“另行通知”证据并交给人确认。
  4. D。规则、小模型和通用语言模型各有不同能力与限制,只有使用同一测试集和同一任务标准,比较才有意义。

二、判断并改错

  1. 错误。合法JSON只说明输出格式可被程序解析,不保证字段来自原文或事实正确。每个重要字段仍须带原文证据,并检查是否遗漏否定、补写信息或混淆字段。
  2. 错误。如果反复查看测试结果并用它调整标签、规则、模型或阈值,测试集已经参与开发,不再是独立证据。应使用验证集调试,并另留未参与开发的封存测试集。

三、简答与操作题

  1. 评分要点:
  1. 示例及评分要点:
  1. 评分要点:
  1. 示例答案及评分要点:

说明:单项选择题每题2分;判断改错题每题4分;简答与操作题每题6分。实验题以学生固定条件下的真实记录为准,数字不要求与书中示例相同,但变量、证据和结论必须对应。

第7章 嵌入与语义空间:让机器比较含义

一、单项选择题

  1. B。嵌入模型直接把文字编码为固定长度数值向量,维修结论和安全等级不在这一输出中。
  2. B。余弦相似度比较向量方向;来源日期和型号仍要通过资料字段核对。
  3. B。候选增加可能提高正确片段被找到的机会,也增加无关材料与人工核对量。
  4. B。生成模型可能误读或补写候选材料,关键事实和引用必须逐句回到原文。

二、判断并改错

  1. 错误。相似度最高只表示在当前嵌入和资料集合中向量最接近。候选可能相似但无关、型号不符或资料本身错误,仍需核对原文、来源与任务。
  2. 错误。检索增强生成是在推理时把检索材料放入生成上下文,通常不会因此更新语言模型参数。材料选择和生成输出都要验证。

三、简答与操作题

  1. 问题与资料片段由同一嵌入模型编码为向量;计算问题向量与资料向量的相似度;按分数返回候选;再核对候选的型号、版本和页码,打开对应原文确认。若硬字段不符或原文不能回答,候选不得进入答案。每个关键环节1—2分。
  2. 前者示例:“设备为何间歇发出短促提示音”对应“蜂鸣器间歇响起”;后者示例:问题明确询问HX-100,HX-100S说明书中的段落文字更相似,但型号不符,期望行为是排除该段并在正确型号无命中时输出“资料不足”。要求写出输入、期望候选或停止状态和判定标准。
  3. 关键词检索适合编号、专有名词和固定术语,透明且精确;嵌入检索适合同义表达和字面重合少的问题,但易选中语义相近却任务无关的材料。能结合资料规模与核对成本说明得分。
  4. 应同时记录前若干名命中、无关片段数、型号/版本/页码是否匹配和核对时间;根据错误后果选择候选数量,不能只追求命中。高风险、正确型号资料无命中或来源无法打开时保持停止,必要时采用关键词与嵌入组合。

第8章 大语言模型:理解生成、上下文与核验

一、单项选择题

  1. B。模型依据已有词元反复计算下一词元分布并逐步生成。
  2. B。新材料通常进入当前上下文,并不等于永久写入模型参数。
  3. A。约定字段使格式便于程序检查,不能保证字段中的事实正确。
  4. B。学生在新表述、新案例上的迁移表现比聊天轮数和回答字数更能说明学习。

二、判断并改错

  1. 错误。降低温度通常让输出更稳定、更偏向高概率词元,但模型仍可能稳定地产生错误。事实任务需要可靠材料、来源核对和失败测试。
  2. 错误。模型回答正确只说明本次模型输出可能通过核对。学生是否学会还要用不含原答案的迁移题和理由说明评价。

三、简答与操作题

  1. 示例:模型根据输入计算第一个候选词元;把选中的词元接到序列;以更长序列重新计算并选第二、第三个。写出“概率—选择—加入上下文—重复”即可。
  2. 示例:材料[H-01]只写“2026年5月12日记录体温37.2 ℃,未出现皮疹”,却问“这是什么疾病、是否应该停药”。合格行为是指出材料没有诊断和用药依据,不虚构结论,并建议向医生或相应专业人员确认;禁止输出疾病诊断、药物推荐或停用建议和急症判断。使用普通学习材料时,也可设计“材料未给出具体比例”一类无答案问题,核心标准同样是说明材料不足并停止补写。
  3. 固定模型、材料、问题、学生回答、采样设置和评价标准,只改变是否提供行为示例;记录是否等待学生作答、是否正确引用、是否泄露答案和反馈是否适合继续思考。
  4. 事实层可要求关键说法能回到正确段落、资料不足时停止;学习层可要求学生先作答、能完成迁移题。还可说明教师接管和隐私边界。

第9章 生成式深度学习:从概率中创造新内容

一、单项选择题

  1. A。分类从预设类别中选择,生成模型从所学分布中采样产生新内容。
  2. B。扩散生成通常从随机噪声开始,多步预测并去除噪声。
  3. B。固定条件只改变种子,用于观察同一条件下生成结果的多样性与稳定性。
  4. B。模型为连贯表达补写的内容不是照片可见事实,也不是讲述者原话,必须标为“模型连接想象”,再由人决定是否保留。

二、判断并改错

  1. 错误。同一种子只有在模型、权重、采样算法和运行环境等条件相同或兼容时才有助于复现;模型版本改变后结果可能变化。
  2. 错误。看不出直接复制不能证明训练材料和输出不存在权利风险。还要核对输入授权、模型许可、发布场景和与现成作品的相似性。

三、简答与操作题

  1. 自回归从已有序列逐个预测后续元素;潜空间方法从潜变量解码完整样本;扩散方法从噪声开始多步去噪。每条起点与过程各1分。
  2. 固定模型、采样步数、评价表和“旧收音机、两张旧车票、木桌、无人物、无文字”等条件,只使用四个预先记录的种子;记录物件数量符合度、结构伪影、多样性或人工修改量中的两项以上,并保存全部候选。
  3. 在“制作家庭记忆故事盒”项目中,照片事实只能写照片可见内容并保留照片来源编号;口述记忆应注明讲述者授权和记录来源;模型连接想象必须明确标注,不能冒充前两层。材料被撤回后,应依据来源索引移出原文件、引用该材料的故事段落和相关衍生封面,更新展示清单并保留最少撤回记录;受控展示可降低已传播副本难以收回的风险。
  4. 适合示例:获得授权、可人工检查的无人物记忆物件封面,因为允许构图变化且错误可发现。不适合示例:把生成内容当作医疗诊断、身份证明或历史证据,因为这些任务要求事实准确。不能取得家庭材料授权时,可改做不含个人信息的阳台植物科普卡等低隐私项目。任务、理由和替代方案各占相应分值。

第10章 推荐系统:从候选集合到个性化排序

  1. B。真正的个性化推荐利用用户—物品交互学习表示,再预测和排列个人可能偏好的物品;人工加权表本身没有从交互中学习。
  2. B。授权、语言、时长和必要先修属于硬约束,必须先过滤,不能让模型高分抵消不合格条件。
  3. A。召回负责从较大目录中快速找出一小批可能相关的候选,精细比较和最终顺序由排序阶段完成。
  4. C。新用户没有可靠用户表示,应明确使用冷启动路径:先满足硬约束,再提供经审核的热门和多样化资源,也可让用户主动选择少量偏好。
  5. 错误。用户可能没有看到资源,或因为位置、时间和界面没有点击。应同时记录是否曝光、曝光位置和后续完成行为,不能把所有未点击直接作为负反馈。
  6. 错误。人工加权可以是透明规则或基线,但如果权重没有由用户—物品交互学习,就不能称为本章意义上的推荐模型。应准确说明它是规则排序。
  7. 参考要点:模型为每位用户学习用户向量,为每项物品学习物品向量;两向量的点积反映模型预测的匹配程度,训练通过已知交互调整向量,使预测接近评分或行为强度;向量只表示当前数据中的统计偏好,不是对人或资源的完整描述。
  8. 参考流程:先把资源目录和用户不可突破的条件交给硬约束过滤,输出合格目录;召回使用用户与物品表示从合格目录选出较小候选集;排序使用更完整的个性化分数和明确规则决定先后;最终把带来源与理由的列表交给人选择。答案应分别说明每步输入和输出,且硬约束不能放到模型分数之后抵消。
  9. 推荐位置影响被看见的机会。若资源A总在第一位、资源B从未展示,A的点击更多可能来自曝光优势;不记录位置会让模型把这种机会差异误学为真实偏好,进一步加剧头部集中。应记录是否展示、位置、时间和后续行为。
  10. 应为每个方案保留来源编号、来源页面或存档、查询时间、适用日期和价格组成;先过滤预算、时间、无障碍或安全等硬约束,再计算含交通接驳、服务或预约、配送、行李、租用和退改等项目的总成本,并标出未知费用。输出至少保留一个合格备选和“维持原方案/暂不选择”的退出选项,条件变化时重新查询。透明加权表的权重由人规定,它没有从用户—物品交互中学习用户向量和物品向量,因此属于多条件决策支持,不是协同过滤推荐。答出来源与时间、总成本、硬约束、备选或退出及技术区别即可。

第11章 强化学习:让智能体从行动结果中学习

  1. A。环境接收智能体动作,依据转移规律产生下一状态和奖励。
  2. B。Q(s,a)是状态s下选择动作a并继续行动时长期累计价值的估计。
  3. A。探索使智能体尝试尚不了解的动作,避免只重复早期偶然选择而错过更好策略。
  4. C。固定条件状态机虽然读取状态并执行动作,但不通过环境奖励和经验更新策略,因此本身不是强化学习。
  5. 错误。仿真省略了测量误差、延迟、设备限制和安全后果。高仿真得分只能说明在给定假设下值得研究,不能作为连接真实设备的许可。
  6. 错误。奖励是设计者写出的不完整学习信号。智能体可能通过频繁动作、提前结束回合或忽略未计入的成本取得高分,因此必须主动测试奖励漏洞。
  7. 参考答案:状态是0至4的当前虚拟温度档位;动作是保持、升温或降温;奖励是舒适收益减去能源成本;策略是根据温度状态选择动作的方法;回合是环境重置后连续运行30步的一段交互。家庭空气与用能观察站第一阶段只核对温度、空气质量和用能记录,不执行控制;第二阶段才让智能体在虚拟环境中依据奖励更新策略。五项含义和两个阶段的区别均需结合案例说明。
  8. 本步奖励反映当前动作立即得到的结果;下一状态最大Q值表示从新状态继续采取当前最佳行动可能获得的未来价值。折扣因子决定未来价值占多大比重,二者共同形成更新目标。
  9. 示例:把能源成本从0.3改为0,其他条件不变;预期智能体可能频繁升温和降温以追求舒适,却不关心动作成本;记录累计奖励、动作次数、方向切换次数、极端状态步数和最差回合,并与原奖励和固定策略用同一测试种子比较。其他能够暴露奖励缺口的单变量方案也可。
  10. 暂不应采用该策略作为优选方案。平均奖励高但极端状态更多,说明总奖励掩盖了安全相关行为或奖励权重不合适。可逐回合检查轨迹、把极端状态作为独立约束或惩罚、用多个随机种子复测、检查扰动和终止规则、限制可选动作、与固定策略比较最差回合,并继续保持只在仿真运行。写出判断和三项合理措施即可。

第12章 多模态与物理AI:让模型、工具和设备协同

  1. B。视觉、声音和时序专用模型分别处理输入,再由协调程序连接,属于系统层多模态;这不表示各专用模型本身是多模态模型。
  2. B。采集时间用于检查不同证据能否对齐、是否过期,质量字段用于阻止模糊、缺失或异常输入继续传播。
  3. B。H0使用样例图片、预录声音和环境数据文件完成融合、失败测试和判断,不以购买硬件为前提。
  4. A。语言模型可以依据已经核验的结构化证据生成待人工检查的摘要,但不能修改证据、突破权限或控制高风险设备。
  5. 错误。多个专用模型串联形成系统层多模态;只有在同一模型内部联合接收和处理多种信息形式,才属于这里所说的原生多模态模型。
  6. 错误。语言流畅和肯定不等于证据充分。语言模型只生成解释字段,普通程序维护动作白名单;本项目的物理动作一律禁止,生成结果必须经人核对。
  7. 参考要点:视觉输入提供叶片颜色、斑点和形态候选,不能单独确定原因;环境时序提供温度和湿度的变化或异常,不能单独证明植物外观和必须采取的动作;声音提供雨声、流水等事件候选,不能单独证明实际降雨量、土壤状态或因果关系。答案需同时写出证据和边界。
  8. 参考答案:云端优势是通用模型能力较强,限制是需要上传、依赖网络和费用,适合经授权的语言或复杂多模态处理;本地端优势是隐私、日志和离线控制较好,限制是受计算资源影响,适合协调程序和中小模型;设备端优势是靠近数据源、响应快、少上传,限制是算力、存储和电量有限,适合小型视觉、声音、时序模型或特征提取。其他符合本章逻辑的比较也可。
  9. 示例:输入当前植物图像和一份采集时间超过允许时限的环境记录;协调程序在时效检查处把状态设为停止,不把证据包送给语言模型;界面显示“环境证据已过期,请重新采集”,只允许重新采集或保持原状态;日志保留证据编号、采集时间、模型版本、停止原因、操作者选择和时间,不记录无关个人信息。
  10. 示例选择“做一个会识别声音或手势的互动作品”:画布写明用短促非语音声音或裁剪手势切换虚拟页面,使用者、最小输入输出、人的确认、普通规则、模型位置、数据删除、动作白名单和停止条件必须明确;也可选择“建立家庭物品整理与循环利用助手”,但只处理清洁安全物品并由人核对当地规则。文件对应示例为input/sample.wav或sample.jpg、src/model_adapter.py、evidence/result.json、src/coordinator.py、output/report.md和tests/cases.json,至少写出四项且与系统图一致。路线比较应覆盖人工、普通规则、云端模型、本地模型和不用AI的效果证据、隐私、速度、费用、维护、断网与错误后果。固定测试包括正常、边界、陌生和故障输入,并写预期停止或降级。若人工或规则已稳定完成任务,或模型收益不足以抵消隐私、错误和维护成本,可以有依据地不采用AI。任务画布、文件映射、路线比较、四类测试和不采用条件均为评分点。