很多人问我,现在想入门深度学习,到底该看什么资料。官方文档太散,网上视频东一榔头西一棒子,市面上的书又厚又难啃。我自己带过几批学生,也被问过无数次同样的问题,最后干脆自己动手,编了一套TensorFlow 2的教材。这套教材前前后后改了三轮,从最初几十页的讲义提纲,慢慢补成了一套带实战、带踩坑记录、带作业设计的完整学习路径,今天把编写这套教材的思路和其中的核心内容做一个系统梳理,希望能给正在学习或准备讲授TensorFlow 2的朋友一些参考。
我见过太多学习者在同一个地方卡住。不是学不会,是材料不给力。有的教材教你背API,有的教材上来就甩一个几百行的模型代码,注释都懒得写。作为教的人和学的人,我们真正需要的是“按什么顺序学、每一步练什么、踩坑了怎么办”这条完整链路。这套教材解决的就是这个问题:用一条清晰的实践主线,把零散的知识点串成体系,让学习者不靠死记硬背也能把TensorFlow 2用起来。
1. 为什么非要自己编一套TensorFlow 2教材:市面材料的通病与真实需求
1.1 官方文档、视频课和书籍各有什么缺口
先说说我为什么放着现成材料不用,非要自己动手编教材。官方文档覆盖面广,但对新人极不友好。每个API都有一个独立的页面,示例代码东一个西一个,初学者根本不知道先看哪个、看到什么程度可以往下走。更麻烦的是,文档里很多示例是“半截代码”,只展示某个函数的用法,完全没有说明这个函数放在整个模型训练流程里该放在哪里。
视频课是另一个极端。讲得挺热闹,跟着敲也能跑,但课后容易忘。我见过不少学习者,视频刷了三遍,合上电脑让他独立写一个数据加载的流程,还是写不出来。视频课的碎片化属性决定了它很难帮你建立知识骨架,更多是“我见过这个”的熟悉感。
书籍的问题则是时效性和厚度。深度学习领域变化太快,一本纸质的TensorFlow教材从写作到出版,至少一年时间,这一年里API可能换了三个版本。很多书还在讲tf.Session()、tf.placeholder,可这些在TensorFlow 2里已经彻底消失了。另外,大部分书偏好系统性铺陈,从数学原理讲到反向传播,几十页过去还没见到一行能跑的代码,早就把初学者劝退了。
1.2 教材编写的一手素材:答疑记录里的高频卡点
我在准备教材之前,先做了一件事:把过去两年在社区和学习群里回答过的问题全部翻出来,按照主题归类。这个动作非常有价值,因为我发现学习者的困惑高度集中,而且很多困惑是官方文档里根本找不到答案的。
第一类高频问题,数据集处理。准确率提不上去怎么办,model.fit()里到底该传什么格式的数据,tf.data.Dataset和numpy数组有什么区别,什么时候该用from_tensor_slices。这类问题占比最高,至少三成。
第二类高频问题,模型构建方式混乱。今天用Sequential,明天用Functional API,后天看到别人的代码用自定义Model子类,三种写法有什么区别,实战中到底选哪种。
第三类高频问题,训练过程不可控。loss不下降、accuracy不动、过拟合了不知道怎么看,更不知道EarlyStopping、ReduceLROnPlateau这些回调函数应该在什么时候用。
第四类高频问题,保存和部署。model.save()到底保存了什么,为什么有的模型加载之后没法继续训练,.h5文件和.pb文件夹有什么不同。
这些一手记录成了教材目录最好的参考。我不需要坐在书桌前苦思冥想“读者可能对这个感兴趣”,数据已经告诉我,他们最需要被解决的就是这些。
1.3 这套教材的定位:不是替代文档,而是搭建通往文档的桥
明确了痛点之后,我给这套教材定了一个非常具体的定位——它不是官方文档的替代品,而是一座桥。目标是让学习者用最短的时间建立起TensorFlow 2的全貌,能够独立跑通项目,并且当他需要深入某个主题时,知道去哪里查资料、怎么查。
桥的起点是“完全没写过一行深度学习代码”的小白,桥的终点是“能独立完成一个端到端的项目并做基本调优”的初级工程师。这个定位决定了整套教材的选材范围和深度。太深的理论不写,交给数学书和论文;太偏的API不介绍,留给实际需求来驱动;所有的知识点都挂在“能跑通的完整项目”上,让学习者每走一步都能看到正反馈。
2. 教材的核心脉络:从TensorFlow 1的计算图思维切换到TensorFlow 2的即时执行思维
2.1 为什么说这是学习TensorFlow 2时最大的思维障碍
TensorFlow 2最核心的变化不是API层面的增删,而是执行模型的整体切换。TensorFlow 1时代,代码跑起来分两步:先构建一个静态计算图,然后再用Session去执行这个图。这套机制在老版本里是必须的,因为静态图有很多优化空间,比如部署到移动端、分布式训练这些场景都需要先有一个完整图结构。但它也带来了很大的认知负担:你写代码的时候,代码本身并不会立刻执行,只是“描述”了一个未来要执行的计算过程,出了bug想调试就很难下手。
TensorFlow 2把整套机制翻了过来,默认采用即时执行模式,也就是eager execution。你写一行代码,计算机立刻算出一行结果,跟写普通的Python一模一样。操作张量的时候可以直接打印出数值,断点调试、打印中间变量这些操作全部回归正常体验。
问题是,我接触的很多学习者,尤其是看过一些TensorFlow 1老教程的人,脑子里的思维模式还没有切换过来。他们拿到代码下意识先去找Session,找不到就开始慌,然后硬要把数据塞进一个placeholder里,完全不知道从何下手。这套思维上的惯性,比任何API的差异都难纠正。
2.2 教材怎么绕开这个障碍:先动手后理论,先把模型跑起来再讲原理
我在这套教材里的应对策略是,第一章不解释任何底层机制,直接让学习者用三行代码跑出第一个模型。这三行代码用Sequential搭一个单层网络,在mnist数据集上训练,几秒钟就能看到准确率。三行代码跑完之后,学习者天然会产生一个问题:为什么这么简单就能跑出一个模型?带着这个问题进入下一章,他会自动渴望知道背后发生了什么,这时候再讲计算图和tf.function,效果完全不一样。
全书的编排顺序是:先用tf.keras的高层API做出成果,建立信心;再回头拆解高层API背后到底做了什么事情;最后才介绍tf.function、GradientTape这些进阶工具。这个顺序和很多教材正好相反。传统的教材喜欢从底层往上盖楼,先讲张量、计算图、自动微分,最后才拿高层API封装好的结果给你看。但实际教学效果告诉我,对绝大多数应用型学习者来说,自上而下的方式要友好得多。
2.3 关于GradientTape和tf.function:教材里的处理方式
处理GradientTape和tf.function这两个概念时,我花了比较多心思。先说GradientTape。这是TensorFlow 2中做自动微分的主要工具,也承接着自定义训练循环的需求。我刚意识到,GradientTape是初学者最容易产生畏难情绪的点,因为它的使用方式跟“普通函数调用”的直觉不太一样。
教材里的处理方式是先给一个足够有动机的场景:当你想用Model子类写模型并且要自定义训练逻辑时,你发现高层API的compile和fit不够用了,这时候需要自己控制前向传播和梯度更新。从这个场景出发,GradientTape的出现是顺理成章的——它就是一台“录像机”,把前向传播过程中的所有计算录下来,然后调用gradient()像回放一样算出梯度。这个比喻让学习者接受的难度大大降低。
tf.function的处理方式也类似。我不在一开始介绍它,而是等学习者真正遇到性能瓶颈的时候再引入。比如一个自定义数据增强函数用纯Python循环实现,训练速度慢得难以忍受,这时候告诉他:用@tf.function装饰一下,把Python的循环编译成图运算,速度立刻提升数倍。有了真实的痛点再给工具,学习者才会真的记住这个工具。事实也证明,这样安排内容,学员对tf.function的理解深度,比我一开始就系统讲解要扎实得多。
3. 教材的重头戏:三套递进式实战案例的设计思路与细节
3.1 为什么案例选型决定了教材成败
编写这套教材的过程中我最深的体会是,案例选对了,教材成功一半;案例选错了,后面怎么写都是事倍功半。一套好的案例,要在难度、知识覆盖面和学习兴趣之间找到平衡点。太简单的案例,比如只做一个线性回归,学完感觉没什么用;太难的项目,比如直接上目标检测,前几行代码就把人吓跑了。
我定下的案例选型原则有三条。第一,案例数据必须容易获取,最好几行代码自动下载,不要求学习者提前准备任何私有数据文件。第二,案例要有直观的量化指标,训练一段时间能看到准确率或者损失值在变化,让学习者感受到自己在进步。第三,案例之间要有递进关系,后面的案例必须复用前面案例的知识点,让学习者在“熟悉+新挑战”的节奏中前进。
3.2 案例一:图像分类,从全连接到卷积的认知跃迁
教材里的第一个完整案例是穿衣识别。数据集是某个开源服装图片集,包含T恤、裤子、外套、裙子、衬衫等十个类别的灰度图片。这个数据集比手写数字识别更有趣,图片内容更接近真实世界的理解,而且难度稍微高一点点,恰好能制造“需要认真对待”的氛围。
这个案例的教学过程分四步。第一步,用最简单的全连接网络跑一个基准,准确率大概在85%左右。第二步,引导学习者思考:“能不能做得更好?”这时候引出卷积神经网络的基本原理。第三步,用Conv2D、MaxPooling2D搭建一个简单的卷积网络,准确率能提升到90%以上。第四步,对比两次实验的差异,讨论为什么卷积层有效,它和全连接层在提取特征方式上的本质区别是什么。
整个过程不超过一小时的阅读量加两小时的实操量。但它给学习者建立的认知,比单纯背十页卷积定义要深刻得多。因为他是亲眼看到一个指标从85%涨到90%,并且理解了这个涨跌的来源,然后他会主动想着“我再加一层卷积试试”的优化冲动,这个冲动就是学习自觉性的起点。
3.3 案例二:文本情感分析,从图像到序列的思维转换
第二个案例是影评情感分析。这个案例在图像分类基础上引入了完全不同的数据形态——文本。文本数据不是像素矩阵,而是变长的序列,这让前一个案例建立的知识结构面临一次新的冲击:怎么把一段长度不等的文本变成可以送入神经网络的矩阵?
教材在这个案例中先解决文本的数值化问题。从Tokenizer到pad_sequences,每一步都可视化地展示“文本变成了什么”。我会让学习者打印出经过处理后的文本张量,让他亲眼看到“I love this movie”这句英文变成了一串数字,然后再试着自己还原回去。这个“正向编码+逆向还原”的操作,对理解文本序列模型非常有帮助。
模型层面,这个案例自然引入了Embedding层和循环神经网络。Embedding层的作用教材里用一个座位表来比喻:每个词就像一个学生,Embedding层把学生安排到一个多维空间里,语义相近的词会被安排在邻近的位置。这个比喻不完美,但足够帮助初学者建立直观印象。网络结构采用Embedding加SimpleRNN加全连接层,训练完成后准确率大概能到85%左右。这个数字虽然不比图像分类高太多,但学习者已经处理了一种完全不同的数据形态,思维的宽度被极大拓展。
3.4 案例三:端到端小项目,把前面的知识全部串起来
第三个案例是一个综合小项目:一个完整的文本分类服务。它把前两个案例中分散的知识串在一个真实场景里。项目要求做一个影评情感分析系统,保存训练好的模型,编写一个加载模型并对新文本做预测的脚本,最后通过命令行交互方式输入任意一段英文影评,程序输出“正面”或“负面”的判断结果。
这个项目虽然小,五脏俱全。学习者需要自己完成数据清洗、文本编码、模型训练、超参数调节、模型保存、推理流程编写全部环节。当初学完前两个案例觉得“我都懂了”的学习者,在这个项目中会明显感到压力。因为前两个案例的学习路径是教材提前铺好的,每一步都排除了歧义。而这个项目需要自己判断:数据需要预处理到什么程度,模型结构怎么搭,训练多少轮会过拟合,save出来的模型怎么在独立脚本里加载。
正是这种适度的压力和混乱感,逼着学习者把知识内化。我在教材里把项目切成了八个子任务,每个子任务给出验收标准,但不给步骤——这是全套教材中唯一一个不给详细步骤的章节,也是学习者反馈学到最多东西的章节。
4. 教材编写中反复踩过的坑与最终采用的解决方案
4.1 环境配置这本“隐形教材”:版本兼容的教学策略
如果说模型代码是教材的骨架,那么环境配置就是教材的血液。多少学习者的深度学习入门之路,不是死在模型原理,而是死在了环境配置上。TensorFlow的版本、Python的版本、操作系统的位数、CUDA和cuDNN的版本,任何一个不匹配,都可能带来令人崩溃的报错。
我在教材的序章用了一个小标题叫做“一次成功的基础环境配置方案”,这个标题是我咬着牙写下的承诺。为了让环境配置这部分内容真正做到“一次成功”,我把参加过培训的十几位同学的环境配置过程都录了下来,整理成一张版本兼容对照表。最后选定的是一套相对成熟稳重的组合:Python 3.8、TensorFlow 2.10系列、纯CPU版本作为初学环境。
使用CPU版而不是GPU版,是我在教学实践中总结出的一个重要策略。初学者用CPU版,可以完全绕开CUDA、cuDNN这些显卡驱动层面的兼容性问题。对于图像分类和文本情感分析这种规模的学习项目,CPU训练时间完全可接受:一个训练周期也就一两分钟左右,不会慢到让人失去耐心。当学习者真正需要训练大模型,再引导他配置GPU环境反而顺理成章。
4.2 代码示例的“最小可运行”原则:删掉一切干扰学习的装饰
教材初稿里有一个被我自己推翻的大问题——代码示例太“花哨”。我为了显示代码的专业性,加了很多自定义回调、漂亮的验证集指标打印、复杂的模型结构。实际测试下来,这些装饰严重干扰了初学者的注意力。学习者分不清哪些代码是必要的,哪些是锦上添花的,于是照着抄的时候把装饰也抄进去了,一旦报错就开始排查这些本不必要的部分。
我后来用了一个硬性原则重写全部代码:一段代码示例里,不允许出现任何一行当前知识点之外的代码。训练部分就只放训练相关的代码,想加EarlyStopping,单独开一个小节讲为什么要加、加了之后训练日志有什么变化。这个原则让教材中代码的平均行数少了一半,但学习者照着敲的成功率大幅提升。
代码风格上也统一做了约定:每个代码块前用一行中文注释说明这段代码要完成什么任务,所有import按标准库、第三方库、TensorFlow模块的顺序排列,所有变量命名采用小写加下划线。这些细节看起来不起眼,但它们实际上是在塑造学习者的编码习惯。我见过太多学习者的代码里充斥着model1、m2、test_data_最终版这种命名,到了一定阶段,这种命名习惯会严重拖累代码可读性,趁早纠正非常有必要。
4.3 练习题的层次设计:从填空到改错再到独立设计
练习题是教材中很容易被忽视但又极其重要的部分。市面上很多教材的练习就是“把代码敲一遍”,这种练习只能训练打字速度。我在这套教材里把练习分成三个层次,每一章末尾都有配套的三层习题。
第一层是“复现型练习”。不给代码,只给任务描述和技术要点,要求学习者凭记忆或查阅资料,独立复现正文中的某个训练流程。这一层解决的是“看懂与写出来有巨大鸿沟”的问题。
第二层是“改错型练习”。我故意给一段包含三到五处错误的代码,错误类型包括shape不匹配、数据格式错误、API调用参数位置错误等。学习者需要像侦探一样排查问题并修正。这个练习对调试能力的提升帮助极大,而且非常接近真实工作——阅读他人代码并定位bug。
第三层是“设计型练习”。给一个开放性问题,要求学习者独立完成数据加载、模型构建、训练、评估全流程。比如“用你学过的知识,设计一个能区分正面和负面餐厅评论的小系统”。这层练习没有标准答案,只有验收标准和打分建议,也是我认为整个教材中最有教学价值的部分。
4.4 教材的“不教什么”:把排除项明写出来反而更有价值
教材写到第三轮时,我发现一个很有意思的现象:对学习者帮助最大的内容,除了教了什么之外,还有“不教什么”。TensorFlow 2的体系非常庞大,如果试图全面覆盖,教材篇幅至少要翻三倍,而且大量内容对初学者来说是用不上的。
我的做法是在教材扉页和每个章节的开头,明确写清楚“本章不讨论什么”。比如第一章明确不讨论TensorFlow在分布式训练和模型部署上的高级能力,第二章明确不讨论自定义算子的实现方法,这些内容会明确告诉学习者:这些方向是存在的,但你现在不需要学,如果后续有需要,应该去哪里找资料。
把排除项明写出来,看起来像“偷懒”,实际上是一种责任。它把无限的知识海洋收敛到一个有限且有边界的路径上,让学习者知道自己现在站在哪里,往前走会到哪。很多学习者的焦虑不是来自“学不会”,而是来自“不知道要学什么”和“不知道学到什么程度算够”。边界感本身就是一种教学资源。
5. 按照这套教材学,哪些坑还是会反复出现
5.1 shape不匹配报错:TensorFlow 2学习者的第一道坎
就算教材写得再好,学习者实际动手时还是会踩坑。有些坑是每个学习者都会踩一遍的,我在教材里专门用一整章记录了这些高频问题和排查思路。其中一个经典案例是shape不匹配的报错。
shape不匹配在TensorFlow 2里几乎是最常见的报错类型了。报错信息往往是一大段英文,中间夹着(None, 28, 28, 1)和(None, 10)这样的张量形状信息。很多学习者看到这段英文就慌,直接截图去群里问“有没有人遇到这个错”。实际上这个报错非常容易定位,报错信息中明确指出了两个张量的形状在哪个维度上不匹配,只需要对照模型每层的输入输出形状,就能找到出错的层。
教材里教给学习者一个系统化的排查方法:在模型中使用model.summary()打印结构,对比数据经过每一层后的shape变化。同时把报错信息里的张量形状与model.summary()输出的形状逐层对照,找到第一个发生变化的位置。这个方法能解决九成以上的shape报错。更重要的是,它教给学习者的不是“这个错怎么解”,而是一种系统化的排错思路,这个思路是所有调试能力的基础。
5.2 训练loss不下降时的排查清单
第二个高频坑是训练时loss不下降。这个问题比shape报错更隐蔽,因为它不报错、程序正常跑、就是指标不动。教材里给出一份结构化的排查清单,按照“数据->模型->训练参数”的顺序逐步排查。
排查的第一步是数据检查。打印几个训练样本图片预览,用代码统计每一类样本的数量,检查数据是否存在严重的类别不均衡。很多学习者发现loss稳定不降之后,去找模型问题,检查了三天才发现是数据标签读错了,所有样本的标签都设成了同一个类别。这种低级错误在独立完成项目时非常常见。
排查的第二步是模型结构检查。确认是否有BatchNormalization放在了不该放的位置,激活函数是否选择得当,最后一层的神经元数量和softmax/sigmoid的选择是否匹配损失函数的要求。
排查的第三步是训练参数检查。学习率是否过大导致损失震荡,batch_size是否过小导致梯度噪声过大,优化器的选择是否恰当。清单的每一步都配有具体的代码验证手段和正常的指标范围,学习者拿到这个清单就像拿到一份体检报告,按项目逐项对照就能定位问题。
5.3 如何让学习者少走弯路:把调试经验前置成学习的一部分
传统的教材把调试经验当作“读者自己踩坑后获得的智慧”,不会主动写出来。我在这套教材里做了一个反其道而行之的尝试:把最常见的调试经验前置到学习路径中,而不是等读者踩坑之后才后知后觉。
比如在介绍model.fit()之前,专门用一个小节讲解训练日志里每一列指标的含义。loss和accuracy是什么,val_loss和val_accuracy又是什么,为什么val_loss先降后升而train_loss一直降意味着过拟合。这些知识点在传统的教学顺序里往往被放在很后面,但是我把它提前了。因为实践中我发现,学习者如果看不懂训练日志,他就无法对训练过程做任何判断,他只能“跑完等着看结果”,这和学习深度学习的目标完全背道而驰。
同样的思路还应用在数据预处理上。很多教材默认数据加载是“顺便”的事情,把数据加载和预处理当作理所当然的辅助代码。我在教材里单独用了一章来讲tf.data,从Dataset.from_tensor_slices讲到map、batch、shuffle、prefetch,并针对每个操作给出性能对比实验。这一章在初稿里本来只有六页,最后一版扩展到了二十五页。因为数据管道是所有端到端项目的地基,地基不稳,上面盖什么都塌。
6. 教材之外的收尾经验:怎么判断学习者真的“会”了
这套教材第一轮试用接近尾声时,我总结出一个判断学习者是否真正掌握的标准:他能独立把项目跑通是及格线,他能改代码改出新效果才是优秀线。及格线对应的是“能复现”,优秀线对应的是“能创造变化”。
我通常在教材的学习者身上测试三个任务来验证他们的真实水平。第一个任务是要求修改网络结构,验证他对模型设计的理解。第二个任务是要求更换数据集的某个部分,验证数据处理的通用能力。第三个任务是要求写一个使用文档,验证他把思路讲清楚的能力。这第三项任务看似和编程无关,但能把学习者的理解水平和死记硬背清晰地区分开。一个真正理解的代码的人,一定能用自然语言把事情讲明白。
根据这一年来的教学反馈,这套教材最有效的部分其实不是代码本身,而是“预期管理”。我们在每个章节都明确告诉学习者:你学完这一章之后,会遇到哪些坑;你花了多长时间还没理解某个概念,大概率不是你的问题,是材料的问题。这套预期的建立,让学习者的焦虑感大幅降低,也让他们在真正踩坑时,不是怀疑自己的智商,而是带着判断去排查。我觉得这才是自编教材区别于官方文档和市面书籍的最大优势——它知道学习者的艰难,所以能精准地陪伴在那些艰难点上。
目前这套教材还在持续迭代中。下一轮修订的重点方向,我会放在Transformer和预训练模型的基础介绍上。虽然它并不适合作为初学内容,但越来越多的学习者在完成基础项目后,下一个自然的问题就是“大语言模型是怎么工作的”。为这个需求提前铺设一条顺畅的理解路径,我觉得值得投入精力。