如果你现在上网搜 TensorFlow 教程,很容易看到一个尴尬场面:前排热门文章还停留在 1.x 时代,到处都是tf.Session()、placeholder、graph这一套。这些内容在 TensorFlow 2.0 之后基本全部作废,新手照着抄一步一个报错,完全不知道自己死在哪里。这篇内容就是以 TensorFlow 2.0 和 Keras 为主线,带你把环境配置、模型构建、训练评估、踩坑排查完整走一遍,目标是让有 Python 基础但没有深度学习经验的人,能在一晚上跑通第一个像样的实战项目,并且知道每个环节为什么这么做。
我会尽量用实际项目里最常见的操作顺序来讲。你不需要先补三个月数学再碰代码——先让代码跑起来,再回头理解原理,反而是我见过效率最高的入门路径。
1. TensorFlow 2.0到底改了什么,以及为什么新人应该从Keras开始
1.1 从“声明式图”到“命令式执行”
TensorFlow 1.x 让很多人劝退的核心原因,是它的编程模式反直觉。你得先用一套声明式 API 描述整个计算图,再丢进Session.run()里执行。写代码的过程像是在“画电路图”,而不是在写逻辑。调试更是难受,中间变量看不到,只能靠tf.Print硬插节点输出,新手连报错信息都读不懂。
TensorFlow 2.0 最大的变化,就是默认开启 Eager Execution(动态图执行),张量计算在执行时即时返回结果,和写普通 Python 几乎没区别。你可以直接 print 中间结果,可以用 Python 的if、for控制流包住张量运算,断点调试也能正常进到模型内部。这不是小改进,而是把整个开发体验从“框架优先”拉回“直觉优先”。
Keras 的处境也在这版正式明朗:它原本是 François Chollet 独立维护的高级神经网络 API,被 Google 收购后完全整合进了 TensorFlow,形成了tf.keras子模块。2.0 之后官方推荐的写法就是import tensorflow as tf,然后用tf.keras下面的层、模型、优化器、回调函数来搭网络,不再需要额外 import Keras。
1.2 用Keras入门不等于“低端”,它只是把选择权还给你
我一直跟想入行的朋友说,刚学深度学习,别急着选 PyTorch。不是说 PyTorch 不好,而是 Keras 的高层抽象能让你把注意力先放在“数据长什么样”“模型怎么搭”“loss 怎么降”这三件最核心的事上,而不是一上来就被自定义训练循环、hook、gradient accumulation 这些机制细节淹没。
这里顺手给一个比较实在的选型参考,不是站队,而是帮你按场景判断:
| 对比维度 | TensorFlow 2.x / Keras | PyTorch |
|---|---|---|
| 入门门槛 | 低,Sequential几行搭好模型 | 中,需要自己写更多训练细节 |
| 调试体验 | 2.0+ 动态图已很友好 | 一贯非常友好 |
| 工业部署 | TF Serving、TFLite、Android 生态成熟 | TorchServe、ONNX 也能做,但要自己搭链路 |
| 学术研究生态 | 能用,但论文复现多为 PyTorch 代码 | 研究社区主流,复现资源最多 |
| 适合人群 | 想快速上手、有部署落地需求 | 想做科研、读论文、深入框架底层 |
如果你只是个人练手或者做课程项目,TensorFlow + Keras 完全够用。而且现在很多企业的存量系统就是 TF 系,你掌握这套之后去面试求职,也不至于两手空空。以后真需要转 PyTorch,你会发现核心概念都是通的:张量、自动求导、反向传播、优化器、损失函数,换框架只是换 API 而已。
2. 环境配置:从Python版本到GPU加速,最容易被坑死的一个环节
2.1 先给你的Python选一个安全版本
深度学习框架对 Python 版本一直有明确范围,不是装得越新越好。我用过的版本对应关系大致是:TensorFlow 2.10 支持 Python 3.7~3.10;2.11~2.13 支持 3.8~3.11;2.15 之后开始支持 3.9~3.12。如果你刚准备环境,我建议直接装 Python 3.10 或 3.11,这两个版本兼容性最稳妥,网上能搜到的资料也最多。
装好之后第一件事不是打开 IDE 写代码,而是先建虚拟环境。这个习惯能救你无数次。不同项目需要的包版本会打架,比如一个项目要 keras 2.15,另一个要 keras 3,全装全局环境里迟早出事。我用的是 venv,简单够用,不需要额外装 conda 那种重型工具:
python -m venv tf_env source tf_env/bin/activate # Windows 下是 tf_env\Scripts\activate激活后在命令行里能看到(tf_env)前缀,说明你已经在独立环境里了,后面 pip 装的东西都只属于这个环境。
2.2 安装TensorFlow:CPU版和GPU版的正确姿势
很多人一上来就纠结“我必须要 GPU 版”。实际上,学习阶段 CPU 完全能跑。Fashion-MNIST、CIFAR-10 这类入门数据集,用 CPU 训练也就几分钟一个 epoch,足够你观察 loss 变化。没必要为了跑个 Demo 先折腾一堆驱动。
如果确定要 GPU 加速,现在装法也简单了。TensorFlow 2.11 之后,Linux 上用pip install tensorflow[and-cuda]会把配套的 CUDA、cuDNN 一起带下来,不再需要你手动去 NVIDIA 官网东拼西凑。Windows 上则要格外小心:TensorFlow 2.10 是最后一个支持原生 Windows GPU 的版本。2.11 开始,Windows 原生版只能 CPU,GPU 得走 WSL2,或者换个思路用云平台。这个坑我踩过,当年重装了三次驱动才意识到是版本策略变了。
装完后在 Python 里跑一段验证代码,比什么教程都有用:
import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU'))能打印出[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]就说明 GPU 被识别了;只有空列表也没关系,继续跑 CPU 版即可。
2.3 keras和tf.keras不是一个东西,别混着用
这里有个特别隐蔽的坑。现在 pip 安装keras默认装的是 Keras 3,而 TensorFlow 自带的是tf.keras。两者 API 大部分相似,但版本不同、行为有差异,混用会出现各种诡异问题,比如模型序列化读不回来、某些层参数不兼容。
我的建议很直接:入门阶段别单独 pip install keras,统一走import tensorflow as tf,然后用tf.keras.layers.Dense、tf.keras.Sequential这种写法。等以后需要跨框架用 Keras 3 了,你已经有基础判断力,知道哪里会踩坑,再单独摸索不迟。
IDE 配置上也不用花太多心思,VSCode 或 PyCharm 都行,核心就一件事:让 IDE 选到你刚建的那个虚拟环境解释器。VSCode 在命令面板搜Python: Select Interpreter,PyCharm 在 Settings > Project > Python Interpreter 里添加。这个环节经常被忽略,结果就是代码在本机能跑,IDE 里飘红一片,实际是解释器没选对。
3. Keras建模三板斧:Sequential、Functional与子类化
3.1 Sequential:线性堆叠,适合80%的入门场景
Sequential 是最简单的模型构建方式,适合网络结构就是“一层接一层”的情况。图像分类里那个经典 LeNet 式结构就是典型例子:
import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax') ])这段代码的信息量其实不小。Flatten把 28×28 的二维图片拉平成 784 维向量;第一个Dense(128, activation='relu')是全连接层,128 是神经元数量;Dropout(0.2)表示训练时随机丢弃 20% 的神经元,用来缓解过拟合;最后Dense(10, activation='softmax')输出 10 个类别的概率分布。
为什么中间层用 relu 而不是 sigmoid?因为 sigmoid 在深层网络里容易造成梯度消失,反向传播到前面几层梯度几乎为 0,网络就学不动了。relu 在正区间梯度恒为 1,收敛速度快得多。最后输出层用 softmax,因为多分类需要“所有类别概率之和为 1”的输出,softmax 正好干这个。
3.2 Functional:多输入多输出、共享层的场景
Sequential 碰到分支结构就无能为力了。比如一个模型同时接受图片和文本两种输入,或者一个共享特征提取层后面跟两个分类头。这种场景用 Functional API,写法是把层当成函数一样调用:
inputs = tf.keras.Input(shape=(28, 28)) x = tf.keras.layers.Flatten()(inputs) x = tf.keras.layers.Dense(128, activation='relu')(x) outputs = tf.keras.layers.Dense(10, activation='softmax')(x) model = tf.keras.Model(inputs=inputs, outputs=outputs)Functional 的核心是:层和层之间通过张量连接,模型结构实际上是一张计算图。好处是你可以在任意两层之间引出分支,拼接、相加、并联都行,适合真实业务里复杂的网络结构。入门阶段不必精通,但至少要见过,因为你以后读别人的项目代码,Functional 是出现频率最高的写法。
3.3 Subclassing:自由度最高的“全手动模式”
再往后还有 Model 子类化,就是你自己定义call()方法,把前向传播逻辑完全掌握在自己手里。
class MyModel(tf.keras.Model): def __init__(self): super().__init__() self.dense1 = tf.keras.layers.Dense(128, activation='relu') self.dense2 = tf.keras.layers.Dense(10, activation='softmax') def call(self, inputs): x = tf.keras.layers.Flatten()(inputs) x = self.dense1(x) return self.dense2(x)这给了你最大的灵活性,比如在不同分支用不同处理逻辑,或者前向传播时动态决定要不要跳过某些层。代价是call()里写的逻辑在model.summary()里经常显示不全,模型结构可读性差一点,序列化保存也不那么省心。我的建议是:先牢牢掌握 Sequential,遇到复杂结构再切 Functional,子类化留给确实有定制需求时用。
3.4 compile里那三个参数,到底在配置什么
搭完模型骨架,下一步是compile。这一步是很多新手“会抄不会选”的重灾区:
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])optimizer是优化器,决定梯度怎么更新权重。最常见的 Adam 是自适应学习率方法,它会针对每个参数动态调整学习率,入门阶段基本无脑选它就行。loss是损失函数,它告诉模型“错得有多远”,训练过程就是不断最小化这个值。metrics是评估指标,它只是便利展示,不参与梯度更新。
分类任务里有两个容易记混的 loss:categorical_crossentropy和sparse_categorical_crossentropy。区别只有一个:标签是 one-hot 向量,还是整数索引。你自己的标签如果是[0, 3, 5]这种数字,就用 sparse 版;如果是[[0,1,0,...]]这种向量,就用非 sparse 版。选错最常见的报错就是维度对不上,后面排查节我再展开。
回归任务的配置则不同:最后一层不加 softmax,直接输出一个连续值;loss 用mean_squared_error(MSE)或mean_absolute_error(MAE)。MSE 对大误差惩罚更重,收敛快但容易受异常点影响;MAE 对异常点更稳,但梯度恒为 ±1,收敛慢。选哪个看你数据的噪声情况。
4. 完整的实战案例:用Keras从零训一个图像分类器
4.1 数据加载、归一化与验证集划分
理论说再多,不如跑一个完整项目。这里用 Fashion-MNIST 而不是经典 MNIST 手写数字——它更接近真实图像分类的难度,能让你体会到“验证集上准确率上不去”是什么感觉。数据集在 Keras 里自带下载,不需要找资源:
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.fashion_mnist.load_data()加载后第一步永远是数据探索,不要急着建模。打印一下train_images.shape,你会看到(60000, 28, 28),一共 6 万张灰度图,每张 28×28。再看看train_labels里有哪些类别,10 类,从 T 恤、裤子到靴子。
然后做归一化:train_images = train_images / 255.0。这一步很多人觉得可有可无,实际至关重要。像素值范围 0~255,直接喂给网络,初始特征尺度不统一,梯度更新很容易震荡,导致收敛极慢甚至不收敛。缩放到 0~1 后,所有特征的量级在同一个尺度上,训练会稳定得多。
fit里的validation_split=0.2表示从训练集里自动抽出 20% 当验证集。验证集和测试集不是一回事:验证集用于训练过程中观察模型有没有过拟合,帮你决定要不要提前停止;测试集只在全部训练结束后用来评估最终效果。如果你自己准备数据集,一定要留出这三份,乱用一个数据集反复调参,最后评估结果是虚高的。
4.2 模型搭建与训练过程的完整代码
结合 3.1 节的 Sequential 结构,完整训练代码如下:
model = tf.keras.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) history = model.fit(train_images, train_labels, epochs=10, batch_size=32, validation_split=0.2, callbacks=[tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3)])batch_size=32的含义是每次从训练集拿 32 张图片计算一次梯度。batch 越小,梯度更新越频繁,训练波动越大,但显存占用低;batch 越大,训练更平稳,但需要更多显存,而且可能陷入 sharp minimum,泛化性反而变差。32 是一个经过大量实践检验的默认值,入门阶段不用改。
callbacks里挂了个EarlyStopping,这是非常实用的防过拟合机制:当val_loss连续 3 个 epoch 没有下降,训练自动停止,避免你干瞪眼看着验证集表现越来越差还继续烧时间。训练结束后可以用history.history查看每个 epoch 的 loss 和 accuracy 变化。
训练过程中你会看到类似这样的输出:
Epoch 1/10 1500/1500 [==============================] - 2s 1ms/step - loss: 0.5234 - accuracy: 0.8152 - val_loss: 0.3912 - val_accuracy: 0.8564 Epoch 2/10 ... Epoch 10/10 loss: 0.2256 - accuracy: 0.9168 - val_loss: 0.2917 - val_accuracy: 0.8902训练集 accuracy 通常比验证集高几个点,这属于正常现象。如果训练集到了 95%,验证集只有 85%,且差距还在拉大,就是过拟合信号了,Dropout 和 EarlyStopping 就是用来压这个的。
最后评估:
test_loss, test_acc = model.evaluate(test_images, test_labels)一个只用了全连接层的简单网络,在 Fashion-MNIST 上大约能到 88%~90% 的准确率。想继续提升,下一步就该换成卷积网络(CNN)了,这个我在后面第 6 部分再讲。
4.3 回归任务的代码变体
如果换成预测连续值,整个流程只有三个地方需要换。一个是最后一层不要 softmax,直接tf.keras.layers.Dense(1);第二个是 loss 换成'mse';第三个是评估指标换成['mae']。其他数据加载、训练流程完全不变。所以在 Keras 里,跑分类和回归的差别比你想象的小得多,掌握了模板之后,换数据集只是换数据加载和最后配置而已。
5. 训练过程中最常见的几个坑,以及我的排查套路
5.1 loss变成nan,从哪一步开始查
这个问题我在导师的服务器上碰见过一次。模型前几个 epoch 正常,突然 loss 变成nan,验证集 accuracy 也归零。当时第一反应是改模型结构,折腾半天没用。后来按以下顺序排查才定位到:
- 先检查训练数据里有没有
nan或inf。用np.isnan(train_images).any()一查就出来。真实数据经常有缺失值被填成奇怪的数。 - 再检查是不是梯度爆炸。如果输入特征没有归一化、网络层数多、学习率又设得高,梯度很容易爆炸到
inf。解决方案是调低学习率,或者在优化器里加梯度裁剪。 - 最后看 loss 函数和数据标签是否匹配。比如分类任务用了回归的
mse,或者标签里有负数,对数值计算就可能出问题。
那次查到最后发现是学习率太高了,从0.01降到0.001就恢复正常。训练过程变成 nan 不一定是“模型坏了”,很多时候只是数值不稳定,排查顺序我总结成一个表:
| 现象 | 优先检查项 | 处理办法 |
|---|---|---|
| loss=nan | 数据里有nan、梯度爆炸、学习率过高 | 清洗数据、加归一化、降低lr、加clipnorm |
| 训练震荡大 | batch_size太小、学习率太高 | 增大batch、降低lr |
| 验证loss回升 | 过拟合 | 加Dropout、EarlyStopping、数据增强 |
| 准确率始终50%左右 | 标签错位、shuffle没开 | 检查label和图像是否对齐、fit里设shuffle=True |
5.2 GPU显存不足(OOM),别急着换显卡
显存不足这个报错很容易让新手焦虑,尤其 Windows 用户,可能以为是系统问题。实际上大部分 OOM 是 batch_size 太大或者输入图片分辨率太高导致的。CIFAR-10 是 32×32,跑起来没压力;但如果你上手就用 512×512 的高清图像,batch_size=32 很可能直接把显存吃满。
处理思路按顺序来:先把 batch_size 降到 16 或 8;如果还不行,降低输入图片的采样分辨率;再用tf.data.AUTOTUNE优化数据读取管线。无论是学习还是临时的项目 Demo,大部分情况到第二步就解决了。真到了必须用大 batch 的阶段,才需要考虑梯度累积、混合精度训练或者分布式策略。
5.3 模型不收敛,先检查你喂了什么东西
模型怎么调都停在初始水平,多半不是网络结构的问题,而是数据本身出了问题。我在实际项目里遇到过用户拿到的标签和图片错了一位,模型训到天荒地老也只有 50% 准确率——因为数据根本是配对的错位样本。
这时候最实用的调试手段是可视化,不用花哨的工具,直接把model.predict的前几个结果打印出来对照一下。或者手动做一次简单的数据检查:
import matplotlib.pyplot as plt plt.imshow(train_images[0], cmap='gray') print(train_labels[0])确认图和标签对得上。再做一次print(np.unique(train_labels)),看标签是否从 0 开始连续编码。如果类别数不连续或者有负数,softmax 输出维度一定对不上。很多人在这上面浪费了一周时间,其实只是数据检查不到位。
5.4 shape不匹配,是所有深度学习报错里最容易自愈的
TensorFlow 的报错信息里,shape mismatch 大概是出场率最高的。关键要理解它的书写规范:(None, 128, 128, 3)里的None是 batch 维度,表示这一维大小在运行时才确定。比如你输入 32 张图,实际张量就是(32, 128, 128, 3)。
排查办法也很简单:在模型的每一层之间插入model.summary(),看每一层的输出 shape,一眼就能看出哪一层连接错了。只要记住一个原则,输入输出的最后一维必须对得上:Flatten 之后是(None, 784),接的 Dense 层第一维必须能接受 784 输入,否则就报错了。
6. 入门之后往哪走:CNN、Transformer回归以及应用落地
6.1 CNN为什么对图像更友好
如果 Fasion-MNIST 用全连接能到 90%,再往上提升,就该换卷积神经网络。CNN 的核心思路是:全连接层把每个像素拆成独立特征,忽略了空间结构;卷积层用一个小窗口(比如 3×3)在图像上滑动,提取局部模式,而且同一个卷积核在整张图上共享参数,参数数量大幅减少,队过拟合也更友好。
一个最简单的 CNN 骨架长这样:
model = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Conv2D(64, (3, 3), activation='relu'), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Flatten(), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ])Conv2D(32, (3,3))表示 32 个 3×3 卷积核,MaxPooling2D负责下采样、缩小特征图尺寸,同时保留主要特征。学会这个结构之后,你会发现网上很多项目,包括口腔疾病图像识别、恶意软件分类这类应用,本质上都是“图像进来,分类出去”,只是数据来源和具体特征不同。
6.2 从CNN到Transformer,是不是非得学
热搜里有个非常具体的关键词:用 Transformer 做回归。Transformer 首先是 NLP 领域的明星结构,靠自注意力机制建模长距离依赖关系。后来有人把它用到结构化数据和图像上,也出了不少文章。
我的建议是:如果你连 CNN 和全连接都没跑熟,先别急着碰 Transformer。自注意力机制涉及 Q、K、V 矩阵、位置编码、multi-head 切分,一堆抽象概念,没有基础直接学只会劝退。先把 Keras 的三种建模方式吃透,理解数据是怎么变成张量、流经各层、最后输出结果的,再来啃这类进阶内容,效率高得多。
6.3 我给入门者的资源路线
学习材料方面,《动手学深度学习》(D2L)是我见过最适合实战导向的资源,免费在线阅读,代码同时提供 PyTorch 和 TensorFlow 实现,每个小节都有一个能跑的模型,非常适合拿来做“先跑通、再看理论”的学习路径。它英文原版叫 Dive into Deep Learning,中文社区维护得也很好。
如果你是学生或者想把基础打得更牢,市面上经典的中文深度学习教材 PDF 版本也可以配合看,但我的经验是:教材的数学推导是为了严谨,不要试图第一遍全看懂。先看结论,知道“梯度下降是让 loss 变小”“反向传播是逐层求偏导”,用到时再回头查,这是最符合实际开发节奏的学习方式。
写在最后的一点体会
我见过太多人被深度学习的门槛劝退,最后发现卡住他们的根本不是数学天赋,而是环境版本冲突、shape 报错、loss 不降这种技术细节。所以如果你现在正卡在某一步,先按这个顺序排查:版本兼容性,数据 shape,数据与标签对齐,最后才去调模型结构。这是我调试过无数模型之后的真话。
一个小技巧送给你:每次训练之前,随机种子固定好,保证可复现——tf.random.set_seed(42)加np.random.seed(42),这样同一个数据、同一套代码,每次训的结果基本一致,否则你调参时根本无法区分“是模型变好了,还是运气变好了”。另一个我坚持的习惯是:不管多小的数据集,代码跑一遍完整 batch 确认没有 shape 和类型报错,之后再开整轮训练。这一步能帮你省掉至少一半的调试时间。