ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

TensorFlow 2.x与Keras入门:从环境配置到CNN实战

2026/9/8 6:18:55 拓冰建站 浏览量
TensorFlow 2.x与Keras入门:从环境配置到CNN实战 最近收到不少刚开始学深度学习的同学提问问题高度集中Python该装哪个版本、TensorFlow怎么装、Keras是不是要单独装、第一个模型怎么写。很多人花了一星期还卡在环境上真正写代码反而不到一天。我个人的看法是先别急着啃论文也别一上来就折腾目标检测、Transformer那些大模型。你要做的第一件事是老老实实用TensorFlow 2.x和Keras把一个最简单的端到端流程跑通包括环境配置、模型训练、指标判读、模型保存。这一圈走下来你对深度学习的理解会扎实很多。这篇内容就是按照我实际操作的路径来写的目标是让一个只有一点Python基础的人从上手安装到跑通图像分类模型不绕弯路。文章会覆盖Anaconda环境搭建、TensorFlow 2.x安装、Keras手写数字识别、训练指标和R2系数、CNN实战以及我踩过的坑。2024年TensorFlow和PyTorch的讨论热度变化很快但作为入门TensorFlow 2.x Keras的完整度和对新手友好程度依然是很好的起点。1. 先把环境收拾利索Anaconda、Python和TensorFlow 2.x的版本组合1.1 为什么入门阶段我坚持用Anaconda很多教程会让你去python.org下载Python然后直接用pip装依赖。这个方法本身没错但你会在几个月后遭遇同一个问题项目A需要TensorFlow 2.4项目B需要TensorFlow 2.10两个版本依赖的numpy版本还不一样强制升级一个另一个就崩了。Anaconda解决的就是这件事。它自带conda包管理器你可以为每个项目创建独立的虚拟环境环境之间互不影响。装坏了直接删掉重建不用重装系统也不用反复折腾系统级Python路径。对于新手来说这是最省钱省力的方案。安装步骤不复杂去官网下载对应操作系统的Anaconda安装包下一步下一步就行。装完之后建议别用默认的base环境直接装TensorFlow而是单独创建一个环境。我习惯在命令行里这样操作conda create -n tf2 python3.9 conda activate tf2Python版本我选了3.9主要原因是兼容性好。TensorFlow 2.x在Python 3.7到3.11都有对应支持但3.9是绝大多数第三方库适配最稳定的版本。创建好环境后所有后续安装都在这个环境里进行等于给自己圈了一块干净的试验田。1.2 安装TensorFlow 2.x时你必须知道的版本约束环境激活后安装TensorFlow就一行命令pip install tensorflow如果你想装GPU版请先确认自己的显卡是NVIDIA然后额外安装CUDA和cuDNN再执行pip install tensorflow实际上TensorFlow 2.x已经不再区分CPU版和GPU版了装的是同一个包它会在运行时自动检测是否有可用GPU。重点在于GPU加速需要额外的CUDA工具包而这里最坑的就是版本匹配。我刚开始装GPU环境时被CUDA、cuDNN、TensorFlow三者的版本组合折磨了一整天。现在TensorFlow官网对CUDA版本有明确要求比如CPU版本无所谓GPU版本要求CUDA 11.8或12.0等。如果你不想折腾入门阶段完全可以用CPU版先跑起来手写数字识别这种小模型CPU也能在几十秒内完成训练。等后面做真实项目、数据量上来了再定向配置GPU环境不迟。另外安装过程如果因为网络原因很慢建议把pip源换成国内镜像源。在用户目录下创建或修改pip配置文件加上[global] index-url https://pypi.tuna.tsinghua.edu.cn/simple trusted-host pypi.tuna.tsinghua.edu.cn用conda环境的朋友也可以直接用conda源加速不过我更推荐pip因为TensorFlow在pip源的更新更快。1.3 开发环境选VSCode还是Jupyter Notebook这个问题也是新手高频问题。我的建议是做学习和调试优先用Jupyter Notebook或Jupyter Lab做工程化项目用VSCode。原因是深度学习的训练过程天然是逐步探索先加载数据看形状再试一个模型观察指标修改参数再跑。这种交互式开发在Notebook里非常顺手单元格逐个执行能清晰看到每一步的输出。安装Jupyter也不难pip install jupyter jupyter notebook在Jupyter里只需要注意每次重启内核后之前的变量会清空需要重新执行所有单元格所以建议把整个流程拆成有顺序的单元格。VSCode的优势在于代码提示、调试和Git集成。如果你需要用TensorFlow写一个正经工程比如封装训练脚本、写数据集加载类VSCode更顺手。需要配合Python扩展和Pylance插件然后在左下角选择你创建好的conda环境。一个容易踩的坑是系统里有很多Python解释器VSCode默认可能选错环境导致import tensorflow时提示ModuleNotFoundError。解决办法是在命令面板里执行Python: Select Interpreter手动指定tf2环境的路径。2. 跑通第一个模型用Keras识别手写数字全流程2.1 数据集加载不是随便load就完事第一个实战我建议做MNIST手写数字识别。这个数据集在Keras里内置不需要自己去网上下载图像文件也不需要标注加载就能用非常干净。import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers (x_train, y_train), (x_test, y_test) keras.datasets.mnist.load_data() print(x_train.shape, y_train.shape, x_test.shape, y_test.shape)输出你会看到(60000, 28, 28) (60000,) (10000, 28, 28) (10000,)这里要养成一个习惯拿到数据后先检查形状。MNIST每张图是28x28的灰度图值范围是0到255标签是0到9的整数。直接喂给神经网络会出现两个问题一是数值范围太大了网络学习起来不稳定二是标签是整数但我们要做的是分类所以通常使用one-hot编码或者让损失函数自动处理稀疏标签。归一化这一步非常关键但很好理解。把像素值除以255变成0到1之间的小数能让梯度下降更快、更稳。你不需要特别高深的数学就能理解如果输入数值太大权重初始化和梯度更新都容易出问题。2.2 Sequential模型和Dense层究竟在做什么Keras里最简单的模型写法是Sequential意思是一层一层按顺序堆叠。MNIST分类的入门模型通常长这样model keras.Sequential([ keras.layers.Flatten(input_shape(28, 28)), keras.layers.Dense(128, activationrelu), keras.layers.Dense(10, activationsoftmax) ])很多人第一次看到这段代码会懵Flatten是干嘛的Dense里的128是什么10代表什么Flatten就是把28x28的二维数组拉平成一个784维的向量。因为全连接层Dense期望拿到的是一个向量而不是二维矩阵。你可以理解为把一本28页的册子拆成一长条方便后续逐项处理。Dense是神经网络最基础的层也称为全连接层。128表示这一层有128个神经元。每个神经元会对输入的所有784个数做加权求和再经过激活函数输出。后面的10就是输出层神经元数量对应0到9十个类别。中间的128是一个超参数你可以改成256、64训练效果会有变化但刚开始不需要纠结最优值128是个非常安全的起点。需要注意的是第一层Dense可以不写input_shape但写了之后模型结构会很清晰推荐保留。2.3 compile和fit的参数选择逻辑模型定义好之后训练前必须先编译。编译的作用是告诉模型用哪种优化器、哪个损失函数、评估用什么指标。model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] )这段配置初学者最容易产生疑问。为什么loss不用categorical_crossentropy很简单categorical_crossentropy要求标签是one-hot编码比如数字5变成[0,0,0,0,0,1,0,0,0,0]。而sparse_categorical_crossentropy直接接受整数标签省去手动转换。如果你已经把标签用keras.utils.to_categorical转成了one-hot那就用categorical_crossentropy。两者本质上是一样的只是输入格式不同。优化器adam几乎是现在入门的默认选择。它对学习率有自适应调整不像SGD那样需要反复调学习率。这一点在初学者阶段能省掉大量时间。metrics[accuracy]用来在训练时输出准确率方便我们直观判断模型好坏。然后是训练history model.fit( x_train, y_train, epochs10, batch_size32, validation_split0.2 )这里epochs表示整个训练集被看多少遍batch_size表示每批次处理多少张图。我见过不少新人把epochs设到100然后抱怨训练太慢。实际上MNIST用10个epochs就能达到95%以上的准确率先用小数值跑通再慢慢加。validation_split0.2表示从训练集里抽20%出来作为验证集用来观察模型是否过拟合。执行完训练用测试集评估test_loss, test_acc model.evaluate(x_test, y_test) print(test_acc)一个简单的全连接网络准确率通常能到97%左右。看到这个数字你的第一个模型就算正式跑通了。3. 别只盯着loss训练指标、回归R2和激活函数一次看懂3.1 训练曲线怎么读过拟合怎么发现fit返回的history对象里记录了每个epoch的loss、accuracy以及验证集的val_loss、val_accuracy。很多人训练完不画曲线只打印最后一个数字这是非常危险的习惯。因为你无法判断模型是欠拟合、刚刚好还是过拟合。最简单的判断方法如果训练集loss持续下降但验证集loss下降到某个点后开始反弹基本可以断定过拟合了。模型开始死记硬背训练数据失去了泛化能力。画曲线只需要几行代码import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain_loss) plt.plot(history.history[val_loss], labelval_loss) plt.legend() plt.show()我在实际项目里几乎每次训练都会先看这张曲线图。它比任何日志都直观。如果train_loss和val_loss同时都很高说明模型太简单需要增加神经元或层数如果两者差距很大说明过拟合需要加Dropout、正则化或减小模型规模。还有一个很实用的技巧使用EarlyStopping回调。它会在验证集指标不再改善时自动停止训练还能替你恢复最优权重。from tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping( monitorval_loss, patience3, restore_best_weightsTrue ) model.fit( x_train, y_train, epochs50, validation_split0.2, callbacks[early_stop] )patience3表示连续3个epoch验证集loss都不下降就停止。这样即使你把epochs设成了50也不用担心无意义的空转。3.2 回归模型的R2系数原理、代码与误区分类任务看accuracy那回归任务看什么很多人第一反应是MSE或MAE这没问题但只拿MSE难以直观判断模型拟合的好坏。R2也就是R-squared是回归任务里被广泛使用的指标它衡量的是模型对目标方差的解释程度。简单理解就是在真实值的所有波动当中模型能解释掉多少。R2越接近1模型拟合效果越好接近0说明模型和直接猜均值差不多如果R2是负数那说明模型可能比直接猜均值还差基本宣告模型没有学习到有效模式。Keras内置的指标里没有直接提供R2需要自己写一个函数。TensorFlow里可以这样实现import tensorflow as tf def r_squared(y_true, y_pred): ss_res tf.reduce_sum(tf.square(y_true - y_pred)) ss_tot tf.reduce_sum(tf.square(y_true - tf.reduce_mean(y_true))) return 1 - ss_res / (ss_tot tf.keras.backend.epsilon())然后编译时加进metrics列表model.compile( optimizeradam, lossmse, metrics[r_squared] )这里有两个关键细节。第一个是防止除零分母上加了tf.keras.backend.epsilon()它的值是一个很小的正数避免ss_tot为0时报错比如当所有真实值完全相同的时候。第二个是运算要全部用TensorFlow张量函数如果你直接写Python的sum或numpy数组合会破坏训练图的构建。我自己做回归任务时通常会同时看MSE和R2。只关心R2会忽略误差绝对大小只关心MSE又难以判断模型是否优于“猜均值”这个基准。两个指标一起看才能更准确地评估模型。3.3 激活函数选型对比从sigmoid到ReLU激活函数这个知识点在深度学习中属于“必须懂但不能只会背”的范畴。我用一个通俗的解释如果没有激活函数神经网络不管堆多少层本质上还是线性变换表达力非常有限。激活函数给网络引入了非线性让它可以拟合复杂的函数关系。初学者最常接触到的是sigmoid、tanh、ReLU和softmax。我把它们的适用场景放在一起对比激活函数输出范围常见用途缺点sigmoid0到1二分类输出层容易梯度消失输出均值不为0tanh-1到1传统全连接隐藏层同样有梯度消失问题ReLU0到正无穷隐藏层默认选择部分神经元可能死掉softmax0到1总和为1多分类输出层需要与合适的损失函数搭配现在的实战经验是隐藏层基本默认ReLU多分类输出层用softmax二分类输出层用sigmoid。ReLU之所以流行是因为它计算简单而且在正区间的梯度恒为1能有效缓解深层网络里的梯度消失问题。但ReLU有个问题叫做“神经元坏死”就是输入为负数时梯度为0某个神经元一旦进入这种状态就可能再也无法更新。LeakyReLU、ELU等变体就是为了缓解这个问题出现的。实际写代码时不需要每次都用Sequential里定义的简要写法也可以单独添加激活层model.add(layers.Dense(64)) model.add(layers.ReLU())不过大部分时候直接写activationrelu就够了。新手真正要理解的是输出层的激活函数必须和损失函数搭配比如多分类用softmax配crossentropy回归任务输出层一般不加激活函数或用linear配mse。选错了模型要么训练不出来要么指标很奇怪。4. 从MNIST到真实图像数据CNN实战升级4.1 真实图像的预处理与数据增强MNIST太干净了每张图都是规规矩矩的28x28灰度图背景纯黑数字在中央。真实项目里的图像五花八门手机拍的、工业相机拍的、不同光照、不同尺寸、不同格式。这时候第一步就是统一处理。最常用的工具是keras.preprocessing里的ImageDataGenerator虽然TensorFlow 2.7以后推荐用tf.keras.utils.image_dataset_from_directory但ImageDataGenerator在快速验证时依然很好用。它的核心功能有两个一是可以对图像做归一化二是可以实时做数据增强。from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rescale1./255, rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue, validation_split0.2 ) # 从文件夹读取分类图片 train_generator datagen.flow_from_directory( data/train, target_size(150, 150), batch_size32, class_modebinary, subsettraining )这里每一个参数都有意义。rescale是归一化把像素值缩放到0到1。rotation_range表示随机旋转20度以内width_shift_range和height_shift_range表示水平垂直随机平移zoom_range表示随机缩放。这些增强手段本质上是数据量扩充器让模型见过更多样化的图像减少过拟合。有一个容易踩坑的地方如果你的任务是垂直方向有语义的图像比如卫星遥感或工业品检测不建议开horizontal_flip因为左右翻转后的图片在任务里可能是没有实际意义的。比如识别车牌翻转后字就反了这会污染训练数据。4.2 搭建第一个CNNConv2D、MaxPooling、Dropout全连接网络处理MNIST可以但处理真实图像就力不从心了因为计算量太大且无法有效提取局部特征。CNN这时候就派上用场。它的核心思想是卷积核在图像上滑动提取局部特征然后通过池化降低分辨率逐层提取更高层语义。Keras里搭建CNN非常直接model keras.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, input_shape(150, 150, 3)), layers.MaxPooling2D(2, 2), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D(2, 2), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D(2, 2), layers.Flatten(), layers.Dropout(0.5), layers.Dense(512, activationrelu), layers.Dense(1, activationsigmoid) ])这个结构对应的是二分类图像任务。Conv2D的32、64、128是输出通道数也叫滤波器数量。滤波器越多模型能提取的特征就越丰富但计算量也越大。每一层卷积后紧跟一个MaxPooling2D把特征图尺寸减半既能降低计算量也能增强平移不变性。Dropout是我非常喜欢的一层它的作用是训练时随机让一部分神经元的输出置零迫使网络不依赖某个特定特征防止过拟合。0.5表示每轮有50%的神经元被随机失活。加在Flatten之后、Dense之前是常见的做法。对于真实数据集CNN的效果会明显好于全连接网络但训练时间也成倍增加。如果你用CPU训练建议把图像resize小一点比如128x128同时把batch_size调小。训练脚本加一个model.summary()你可以直观看到每一层输出的形状变化这是排查维度错误最好的方式。4.3 模型保存、加载和转成TFLite训练完模型保存是必须的。Keras提供了非常简洁的保存方式model.save(my_model.h5)之后随时加载from tensorflow.keras.models import load_model loaded_model load_model(my_model.h5)这里有个历史坑如果你在compile中使用了自定义的R2指标load_model会报错提示找不到这个自定义函数。解决办法是在加载时显式指定loaded_model load_model(my_model.h5, custom_objects{r_squared: r_squared})保存模型不只是为了部署更是为了保存训练成果。因为你不可能每次预测前都重新训练一遍。另一个常见的需求是转成TensorFlow Lite模型用于移动端或嵌入式端部署。转换的代码很简单converter tf.lite.TFLiteConverter.from_keras_model(model) tflite_model converter.convert() with open(model.tflite, wb) as f: f.write(tflite_model)不过转换后模型会有量化、尺寸压缩的问题输入输出格式也会变化。我自己第一次转换后在Android端推理发现精度比训练时低了一点后来才意识到是默认的float32转float16量化造成的。如果你的任务对精度敏感尽量先测试再决定是否量化。5. 我踩过的坑和排查思路环境、显存、版本5.1 cuDNN报错的定位与解决GPU版本配置好之后经常会遇到类似“could not create cudnn handle: CUDNN_STATUS_ALLOC_FAILED”的报错。这个报错看起来像是环境坏了其实很多时候是显存碎片化或者TensorFlow默认占满显存后cuDNN找不到连续内存导致的。我第一次看到这个报错时以为是CUDA没装好重装了三遍也没用。后来才发现问题在于我的GPU显存只有6G而TensorFlow默认会申请全部显存。一旦模型训练时显存碎片较多cuDNN分配算法就会失败。解决办法是在代码开头限制显存增长import tensorflow as tf gpus tf.config.experimental.list_physical_devices(GPU) if gpus: try: for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) except RuntimeError as e: print(e)set_memory_growth设置为True后TensorFlow不会一次性占满显存而是按需增长。这样能显著减少内存碎片问题。5.2 显存不足与OOM的处理思路OOMOutOfMemory几乎是每个玩深度学习的都会遇到的错误。遇到这种报错不要第一时间怀疑显存不够。先想一件事你的batch_size是不是太大了我经常看到有人用默认batch_size32训练大尺寸图片显存直接爆掉。把batch_size降为8甚至4问题往往立刻解决。因为梯度下降对batch_size有一定容忍度小batch虽然训练时间会稍长但不会影响模型最终的收敛能力。如果降batch_size还不行就减少图像的输入尺寸或降低网络复杂度比如把Conv2D的滤波器数量从128改成64。鲨鱼式的思维是显存是固定资源我们只能从计算图上做减法。另外一个容易被忽略的点是如果你在Jupyter里多次执行model.fit旧的模型和计算图仍然占用显存新的训练可能直接OOM。解决办法是重启kernel然后重新执行所有单元格。这是一个低级但极其常见的坑。5.3 版本兼容问题与排查清单TensorFlow 2.x迭代速度很快版本兼容问题非常折磨人。我遇到过的最常见两个一个是numpy版本太高导致报“np.float attribute was removed in NumPy 1.24”另一个是protobuf版本冲突导致TensorFlow无法加载。遇到这类问题不要急着换TensorFlow版本先看看是不是numpy版本太新。可以固定为低版本pip install numpy1.23.5如果是protobuf问题通常降低到3.20.x就能解决pip install protobuf3.20.3为了方便排查我整理了一个简单的检查清单现象大概率原因处理方向import tensorflow 直接报错环境选错或包损坏确认conda环境重装tensorflow训练非常慢没有在用GPU或数据集读取瓶颈检查GPU是否被识别优化数据管道模型loss不下降学习率过高或数据未归一化调小学习率检查输入范围验证集loss反弹过拟合加Dropout、数据增强、早停转TFLite后精度下降量化导致信息损失改为float16或先做量化评估这个表是我在实际排查时经常参考的思路。项目里遇到的新问题我会先把完整报错信息复制到搜索框再看对应版本组合。如果是环境问题优先看GitHub官方issue如果是API使用问题优先看TensorFlow官方文档。很多人的误区是一上来就换库或者重装环境实际上静下心读一下报错信息就能定位到问题所在。最后再分享一个个人习惯每次新建深度学习项目我都先在项目根目录放一个requirements.txt把关键依赖版本记录下来。这样即使环境崩了重建也能在十分钟内搞定。很多人忽略版本记录等过了几个月回头复现实验结果时发现自己都搞不清当初用的哪个版本那种感觉真的很难受。趁现在还在学习阶段就养成这个好习惯后面做正经项目会感谢自己的。