☰
TensorFlow入门实战:手写数字识别从零到跑通
2026/10/13 1:20:45 网站建设 项目流程

1. 从一个真实场景说起:为什么我建议你从TensorFlow动手

很多刚接触深度学习的朋友,打开官方文档看到一堆tf.keras、tf.data、GradientTape就头大,感觉门槛高得离谱。我带过不少刚入行的同学,他们最大的困惑不是数学公式看不懂,而是“我到底该怎么把一行行代码跑起来,看到结果”。TensorFlow作为目前工业界落地最广的深度学习框架之一,它的生态完整度、部署能力和社区资源都是顶级的,但恰恰因为功能太全,新手容易被各种API绕晕。

这篇文章要做的,就是帮你把TensorFlow最核心的那条主线抽出来——用最小的代价跑通一个完整的训练流程。我会用一个手写数字识别的例子贯穿始终,因为这个数据集自带在框架里,不需要你额外下载,也不用处理复杂的目录结构,几行代码就能加载。你跟着敲一遍,大概二十分钟就能看到模型从“瞎猜”变成“认得出数字”。适合谁看?完全没碰过TensorFlow的初学者、从其他框架转过来想快速上手的开发者、以及需要给学生或团队做入门培训的讲师。我不打算堆砌术语,而是把每个参数为什么这么设、每一步在干什么讲清楚,让你看完就能自己改代码做别的任务。

2. 环境搭建与版本选择:别让第一步卡住你

2.1 安装方式的选择逻辑

装TensorFlow有好几种路子:pip直接装、conda环境装、Docker镜像跑、或者用在线的Notebook环境。我个人的建议是,如果你只是学习,优先用pip在虚拟环境里装,因为最轻量,出问题也容易排查。虚拟环境用venv或者conda都行,我习惯用conda,因为后面如果要装CUDA相关的依赖会省事很多。

具体命令是这样:

conda create -n tf-learn python=3.10 conda activate tf-learn pip install tensorflow

这里有个细节要注意:Python版本别选太新的。TensorFlow对Python版本的支持有滞后性,比如3.12刚出来那会儿,很多TF版本还没适配,你装的时候会报找不到对应wheel的错。目前3.10和3.11是比较稳妥的选择。另外,如果你用的是Apple Silicon的Mac,装tensorflow-macos和tensorflow-metal能调用GPU加速,但新手阶段用CPU跑这个例子完全够,没必要折腾。

2.2 验证安装是否成功

装完之后别急着写模型,先跑一段验证代码:

import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU'))

第一行输出的是版本号,比如2.15.0这种。第二行会列出可用的GPU设备,如果返回空列表说明用的是CPU,这在这个例子里完全没问题。我见过有同学装完直接开始写代码,结果报了一堆ImportError,回头查半天发现是环境没激活。所以这一步花三十秒确认一下,能省掉后面半小时的排查时间。

提示:如果你在公司内网或者网络受限的环境下安装,pip可能会超时。可以加上-i参数指定国内镜像源,或者用--timeout调大超时时间。这个属于常规操作,具体用哪个源看你所在网络环境。

2.3 开发工具的准备

写TensorFlow代码,我强烈建议用Jupyter Notebook或者VS Code的Notebook插件。原因很简单:深度学习调试是一个反复试错的过程,你需要频繁地看中间结果——比如训练到第几轮loss降到了多少、预测出来的概率分布长什么样。用Notebook可以分块执行,改一行跑一行,不用每次都从头跑整个脚本。VS Code的好处是代码补全和类型提示比较强,tf.keras下面有哪些层、每个层接受什么参数,鼠标悬停就能看到文档。

如果你习惯用PyCharm也没问题,但记得把运行配置里的“Run with Python Console”打开,这样变量能在控制台里直接查看。工具这东西没有绝对的好坏,关键是减少你来回切换的成本。

3. 核心概念速通:用生活化类比理解TensorFlow的骨架

3.1 张量到底是什么

TensorFlow这个名字拆开就是Tensor加Flow,Tensor是张量,Flow是流动。你可以把张量理解成一个多维数组的通用叫法。零维张量就是一个数,一维张量是一个列表,二维张量是一个矩阵,三维以上就是更高维的数组。为什么不用NumPy的数组直接算?因为TensorFlow的张量支持自动求导和GPU加速,这是它做深度学习的根基。

举个直观的例子:一张28x28像素的灰度图,在TensorFlow里就是一个形状为(28, 28)的二维张量,每个元素是0到255之间的像素值。如果你一次处理32张这样的图,那就是(32, 28, 28)的三维张量。再加一个颜色通道维度,就是(32, 28, 28, 1)。理解形状的变化是后面搭建网络的关键,因为每一层对输入的维度都有要求。

3.2 计算图与即时执行

早期TensorFlow 1.x的时代,你需要先定义一个计算图,然后再开一个Session去跑。这种方式对新手极不友好,因为没法直接print中间结果,调试起来像盲人摸象。从TensorFlow 2.x开始默认开启了即时执行模式,你写的每一行代码立刻就能看到结果,跟写普通Python没什么区别。这是新手最大的福音,也是我建议直接学2.x版本的原因。

即时执行的好处在于,你可以随时用print或者调试器查看张量的值。比如:

a = tf.constant([1, 2, 3]) b = tf.constant([4, 5, 6]) c = a + b print(c)

输出就是tf.Tensor([5 7 9], shape=(3,), dtype=int32)。这种所见即所得的方式,让学习曲线平缓了很多。

3.3 模型、层与优化器

用Keras API搭模型,核心就三个东西:层、模型、优化器。层是基本单元,比如全连接层Dense、卷积层Conv2D、池化层MaxPooling2D。模型是把层按顺序串起来的容器,最常用的就是Sequential。优化器负责根据损失函数的梯度来更新参数,常用的有Adam、SGD。

你可以把搭模型想象成搭积木:每一层是一个积木块,Sequential就是那根把积木串起来的棍子。优化器则是你调整积木位置的手,它根据模型表现的好坏来决定往哪个方向挪。损失函数是衡量“现在搭得像不像”的尺子。这三者配合起来,就是整个训练过程的闭环。

4. 完整实例拆解:手写数字识别从零到跑通

4.1 数据加载与预处理

MNIST数据集是深度学习界的“Hello World”,六万张训练图加一万张测试图,每张都是28x28的灰度手写数字。TensorFlow的keras.datasets模块直接内置了这个数据集,一行代码就能加载:

import tensorflow as tf (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() print(x_train.shape, y_train.shape)

输出是(60000, 28, 28) (60000,),说明训练集有六万张图,标签是一维的。接下来要做归一化,把像素值从0-255缩到0-1之间。为什么要归一化?因为神经网络对输入数值的范围很敏感,如果输入值很大,梯度也会很大,训练容易震荡甚至发散。缩到0-1之后,梯度更新会稳定很多,收敛速度也更快。

x_train = x_train / 255.0 x_test = x_test / 255.0

这一步看着简单,但新手经常忘。我见过有人直接拿原始像素值去训练,结果loss死活降不下去,查了半天才发现是没归一化。记住这个原则:输入数据最好都缩放到相近的量级。

4.2 搭建网络结构

这个例子用最简单的全连接网络就够了。输入是28x28的二维图,先把它展平成一维的784个元素,然后接一个128个神经元的隐藏层,最后输出10个类别(对应0到9):

model = tf.keras.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ])

逐行解释一下:Flatten把二维图压成一维,因为全连接层只接受一维输入。Dense(128, activation='relu')是隐藏层,128是神经元数量,这个数可以调,太小欠拟合,太大容易过拟合,128是个常用的起点。relu是激活函数,作用是引入非线性,没有它的话多层网络等价于单层。最后一层Dense(10, activation='softmax')输出10个概率值,softmax保证所有输出加起来等于1,每个值代表属于对应数字的概率。

注意:input_shape只在第一层指定,后面的层会自动推断输入维度。如果你在中间层写input_shape会报错。

4.3 编译模型:选优化器和损失函数

模型搭好之后要编译,这一步是配置训练过程:

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

adam是目前最常用的优化器,它结合了动量和自适应学习率的优点,对大多数任务都能给出不错的结果,新手不用纠结选哪个。损失函数用sparse_categorical_crossentropy,因为我们的标签是整数(0到9),不是one-hot编码。如果你把标签转成了one-hot,那就要用categorical_crossentropy。这两个别搞混,用错了会报维度不匹配的错。metrics=['accuracy']是让训练过程中顺便算一下准确率,方便观察。

4.4 训练与评估

训练就一行代码:

model.fit(x_train, y_train, epochs=5, batch_size=32, validation_split=0.1)

epochs=5表示整个训练集过五遍。batch_size=32表示每次拿32张图算一次梯度。validation_split=0.1是从训练集里切出10%做验证,不参与训练,只用来监控模型有没有过拟合。跑起来之后你会看到类似这样的输出:

Epoch 1/5 1688/1688 [==============================] - 3s 2ms/step - loss: 0.35 - accuracy: 0.90 - val_loss: 0.18 - val_accuracy: 0.95

五轮跑完,验证集准确率一般能到97%以上。然后拿测试集评估:

test_loss, test_acc = model.evaluate(x_test, y_test) print('Test accuracy:', test_acc)

测试准确率通常在97%到98%之间。这个数字对于全连接网络来说已经不错了,毕竟没用卷积层。如果你想再往上提,可以加卷积层,但那是下一步的事。

4.5 用模型做预测

训练完的模型可以直接拿来预测:

predictions = model.predict(x_test[:5]) print(predictions.shape)

输出形状是(5, 10),每一行是10个概率值。用tf.argmax取最大概率对应的索引就是预测的数字:

import numpy as np print(np.argmax(predictions, axis=1)) print(y_test[:5])

对比一下预测结果和真实标签,如果一致就说明模型认对了。你可以把预测概率用柱状图画出来,直观感受一下模型对每个数字的“信心”有多高。

5. 常见问题与排查技巧实录

5.1 训练不收敛或准确率上不去

这是新手遇到最多的问题。排查顺序我一般是这样:先看数据有没有归一化,再看标签和损失函数是否匹配,然后看学习率是不是太大。如果loss在震荡,多半是学习率问题,可以把adam换成adam(learning_rate=0.001)显式指定一个更小的值。如果loss一直不降,检查一下输入形状对不对,比如Flatten之前的数据是不是(28, 28)而不是(784,)。

还有一个隐蔽的坑:如果你自己处理数据时把标签和图片搞混了,比如x_train和y_train传反了,模型会完全学不到东西。fit函数的第一个参数是输入特征,第二个是标签,别弄反。

5.2 内存不足或运行太慢

MNIST这个规模一般不会爆内存,但如果你把batch_size设得特别大,比如一次喂60000张图,那确实会占满内存。batch_size的选择有个经验法则:太小会导致训练不稳定,太大会导致内存不够且收敛变慢。32到128之间是比较安全的范围。如果跑在CPU上觉得慢,可以把epochs先设成1跑通流程,确认没问题再加轮数。

5.3 预测结果全是同一个数字

这种情况通常说明模型没学到东西,可能的原因有几个:学习率太大导致梯度爆炸、数据没归一化、或者网络结构有问题。我遇到过一次是softmax层前面忘了加隐藏层,直接Flatten接Dense(10),模型能力太弱,只能输出多数类。加一层Dense(128, activation='relu')之后就正常了。

5.4 常见问题速查表

问题现象可能原因排查方法
loss不下降学习率过大、数据未归一化调小学习率、检查归一化
准确率卡在10%左右标签与损失函数不匹配检查sparse和one-hot是否对应
训练快但验证差过拟合加Dropout层、减少参数量
报维度错误输入形状与层不匹配打印每层输出形状逐层排查
预测全为同一类模型能力不足或数据有问题增加隐藏层、检查数据分布

实操心得:每次改完代码,先拿少量数据跑一个epoch,确认能跑通再上全量。这样调试周期短,出问题也容易定位。

6. 从跑通到用好:几个值得养成的习惯

6.1 固定随机种子

深度学习里有很多随机操作,比如权重初始化、数据打乱。如果你每次跑出来的结果都不一样,就没法判断改动到底有没有效果。在代码开头加上:

tf.random.set_seed(42) np.random.seed(42)

这样每次运行的结果就是可复现的。虽然不能保证完全一致(GPU上的某些操作仍有随机性),但至少大部分情况下能对齐。

6.2 保存和加载模型

训练好的模型要存下来,不然每次重启环境都得重跑:

model.save('mnist_model.keras') loaded_model = tf.keras.models.load_model('mnist_model.keras')

保存的格式用.keras是TensorFlow 2.x推荐的方式,它会把网络结构和权重一起存下来。加载之后可以直接调predict,不需要重新编译。

6.3 用回调函数做早停

如果训练轮数设多了,模型可能会过拟合。用EarlyStopping回调可以在验证损失不再下降时自动停止:

callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3) model.fit(x_train, y_train, epochs=20, callbacks=[callback])

patience=3表示验证损失连续三轮不降就停。这样你不用担心轮数设多了,让回调帮你控制。

6.4 可视化训练过程

把训练过程中的loss和accuracy画出来,能直观看到模型是在进步还是已经停滞:

import matplotlib.pyplot as plt history = model.fit(...) plt.plot(history.history['loss'], label='train_loss') plt.plot(history.history['val_loss'], label='val_loss') plt.legend() plt.show()

如果训练loss还在降但验证loss开始升,那就是过拟合的信号,需要考虑加正则化或者减少模型复杂度。

7. 下一步可以往哪里走

这个例子跑通之后,你已经掌握了TensorFlow最核心的流程:加载数据、搭模型、编译、训练、评估、预测。接下来可以尝试几个方向来加深理解。一是把全连接网络换成卷积网络,在MNIST上试试Conv2D和MaxPooling2D,看看准确率能提到多少。二是换一个数据集,比如Fashion MNIST,它和MNIST形状一样但内容是衣物图片,分类难度更高,能检验你的模型泛化能力。三是把模型部署成一个简单的Web服务,用tf.saved_model导出,然后用Flask或者FastAPI包一层接口,这样你就能从浏览器上传图片让模型识别。

我个人在实际操作中的体会是,入门阶段最忌讳的就是“只看不练”。TensorFlow的API设计已经足够友好了,很多概念你敲一遍代码就理解了,光看文档反而容易绕晕。遇到报错别慌,先看错误信息的最后一行,通常是维度不匹配或者参数名写错,顺着改就行。这个MNIST例子我建议你至少手敲三遍,第一遍照着抄,第二遍关掉参考自己写,第三遍试着改结构看效果。三遍下来,TensorFlow的基本用法就长在你手上了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询