- 教程
- 深度学习
- 机器学习
【免费下载链接】eat_tensorflow2_in_30_days
Tensorflow2.0 🍎🍊 is delicious, just eat it! 😋😋
中阶 API(Mid-level API)是 TensorFlow 层次结构中承上启下的"墙":它把低阶的张量运算、计算图与自动微分封装成模型层、损失函数、优化器、数据管道、评估指标等可复用组件。本文基于《30天吃掉那个TensorFlow2.0》教程(eat_tensorflow2_in_30_days 项目)的 Chapter3-2 章节,以线性回归模型和 DNN 二分类模型两个完整范例为主线,演示中阶 API 的组装方式与训练流程,并对照仓库中 Chapter5 系列文档说明各中阶组件的选择依据。读完本文,你将能够用layers、losses、metrics、optimizers、tf.data独立搭建并训练回归与分类模型。
一、中阶 API 在 TensorFlow 层次中的定位
在 Chapter3 中,TensorFlow 被划分为五个层次:硬件层(CPU/GPU/TPU 资源池)、C++ 实现的内核层、Python 编写的低阶算子层、Python 实现的模型组件层、以及面向对象的模型类层。
- **低阶 API(第三层)**是"砖块":
tf.Variable、tf.constant、tf.function、tf.GradientTape等张量操作、计算图与自动微分原语; - **中阶 API(第四层)**是"墙":
tf.keras.layers(模型层)、tf.keras.losses(损失函数)、tf.keras.metrics(评估指标)、tf.keras.optimizers(优化器)、tf.data.Dataset(数据管道)、tf.feature_column(特征列)等; - **高阶 API(第五层)**是"房子":
tf.keras.models中以 OOP 封装好的模型类接口,如Sequential、Model(详见 Chapter6-1)。
本文要演示的中阶 API 路线,其核心思路是:用低阶的tf.GradientTape承担自动微分与手动更新参数的责任,但把权重初始化、前向传播、损失计算、梯度下降等重复劳动交给中阶组件完成。与低阶 API 示范(Chapter3-1)相比,代码量显著减少;与高阶 API 的model.fit(Chapter3-3)相比,又保留了训练循环的完全控制权。
二、环境准备与辅助工具
中阶 API 范例需要如下导入,并定义一个基于@tf.function的时间打印辅助函数printbar:
import tensorflow as tf # 打印时间分割线 @tf.function def printbar(): today_ts = tf.timestamp()%(24*60*60) hour = tf.cast(today_ts//3600+8,tf.int32)%tf.constant(24) minite = tf.cast((today_ts%3600)//60,tf.int32) second = tf.cast(tf.floor(today_ts%60),tf.int32) def timeformat(m): if tf.strings.length(tf.strings.format("{}",m))==1: return(tf.strings.format("0{}",m)) else: return(tf.strings.format("{}",m)) timestring = tf.strings.join([timeformat(hour),timeformat(minite), timeformat(second)],separator = ":") tf.print("=========="*8+timestring)printbar全程使用张量运算(tf.timestamp、tf.cast、tf.strings.format/join)与tf.print,而不是 Python 的print,因此它可以被@tf.function编译进静态图中执行——这是 Autograph 机制(详见 Chapter2-2)的典型应用:把 Python 动态图函数转换为静态图以获得加速,同时保留日志输出能力。
三、范例一:线性回归模型
线性回归是最简单的监督学习模型,适合用来展示中阶 API 的完整工作流:数据管道 → 模型层 → 损失函数 → 优化器 → 训练循环。
3.1 准备数据:合成数据集 + 数据管道
import numpy as np import pandas as pd from matplotlib import pyplot as plt import tensorflow as tf from tensorflow.keras import layers,losses,metrics,optimizers # 样本数量 n = 400 # 生成测试用数据集 X = tf.random.uniform([n,2],minval=-10,maxval=10) w0 = tf.constant([[2.0],[-3.0]]) b0 = tf.constant([[3.0]]) Y = X@w0 + b0 + tf.random.normal([n,1],mean = 0.0,stddev= 2.0) # @表示矩阵乘法,增加正态扰动这里用tf.random.uniform生成 400 个二维特征样本,真实权重为w0=[2.0, -3.0]、偏置为b0=3.0,并通过X@w0 + b0线性变换叠加标准差为 2.0 的正态噪声生成标签,得到的是"带噪的线性关系"数据,恰好可以检验回归模型能否还原出真实参数。
数据可视化(横轴为两个特征 x1、x2,纵轴为 y):
%matplotlib inline %config InlineBackend.figure_format = 'svg' plt.figure(figsize = (12,5)) ax1 = plt.subplot(121) ax1.scatter(X[:,0],Y[:,0], c = "b") plt.xlabel("x1") plt.ylabel("y",rotation = 0) ax2 = plt.subplot(122) ax2.scatter(X[:,1],Y[:,0], c = "g") plt.xlabel("x2") plt.ylabel("y",rotation = 0) plt.show()接下来用tf.data构建输入数据管道(数据管道的完整讲解见 Chapter5-1):
ds = tf.data.Dataset.from_tensor_slices((X,Y)) \ .shuffle(buffer_size = 100).batch(10) \ .prefetch(tf.data.experimental.AUTOTUNE)from_tensor_slices:把 NumPy/Tensor 张量切分为样本流;shuffle(100):打乱缓冲区内样本,避免训练顺序引入偏差;batch(10):每 10 个样本组成一个小批次(mini-batch);prefetch(AUTOTUNE):让数据加载与模型计算流水线并行,由 TensorFlow 自动决定预取数量,减少 GPU/CPU 等待。
3.2 定义模型:Dense 层 + 动态挂载损失函数与优化器
model = layers.Dense(units = 1) model.build(input_shape = (2,)) #用build方法创建variables model.loss_func = losses.mean_squared_error model.optimizer = optimizers.SGD(learning_rate=0.001)要点分析:
layers.Dense(units=1)是全连接层(Chapter5-4),输出维度为 1,参数数量 = 输入特征数 × 权重 + 偏置 = 2×1+1 = 3 个;model.build(input_shape=(2,))是显式创建变量的关键:Dense 层的权重kernel(形状 [2,1])与偏置bias(形状 [1])只有在知道输入维度后才会被实例化,调用build后即可通过model.variables访问;model.loss_func、model.optimizer是直接挂在模型实例上的动态属性,这不是tf.keras.Model的内置 API,而是本教程为展示中阶组件可自由组合而采用的编程风格——模型对象成为"参数、损失函数、优化器的集合体";- 回归任务选用均方误差
losses.mean_squared_error,优化器选用带动量的随机梯度下降optimizers.SGD(learning_rate=0.001)(优化器选型参考 Chapter5-7)。
3.3 训练模型:GradientTape + Autograph 静态图加速
# 使用autograph机制转换成静态图加速 @tf.function def train_step(model, features, labels): with tf.GradientTape() as tape: predictions = model(features) loss = model.loss_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1])) grads = tape.gradient(loss,model.variables) model.optimizer.apply_gradients(zip(grads,model.variables)) return loss # 测试train_step效果 features,labels = next(ds.as_numpy_iterator()) train_step(model,features,labels)这是中阶 API 训练循环的标准骨架,与低阶 API(Chapter3-1)的区别在于:前向传播由model(features)完成(Dense 层内部自动做X@W+b),梯度更新交给optimizer.apply_gradients,我们只需用tf.GradientTape()记录梯度即可。细节:
tf.reshape(labels, [-1])把标签与预测展平为一维,保证形状一致才能计算损失;model.variables即[kernel, bias],apply_gradients将梯度与变量一一配对更新;@tf.function把该函数编译为静态图(Autograph 机制详见 Chapter2-2),大幅减少 Python 与 C++ 内核之间的通信开销。
随后定义完整训练循环,每 50 个 epoch 打印一次损失与参数:
def train_model(model,epochs): for epoch in tf.range(1,epochs+1): loss = tf.constant(0.0) for features, labels in ds: loss = train_step(model,features,labels) if epoch%50==0: printbar() tf.print("epoch =",epoch,"loss = ",loss) tf.print("w =",model.variables[0]) tf.print("b =",model.variables[1]) train_model(model,epochs = 200)运行输出示例(具体数值因随机初始化而略有差异):
================================================================================17:01:48 epoch = 50 loss = 2.56481647 w = [[1.99355531] [-2.99061537]] b = [3.09484935] ================================================================================17:01:51 epoch = 100 loss = 5.96198225 w = [[1.98028314] [-2.96975136]] b = [3.09501529] ================================================================================17:01:54 epoch = 150 loss = 4.79625702 w = [[2.00056171] [-2.98774862]] b = [3.09567738] ================================================================================17:01:58 epoch = 200 loss = 8.26704407 w = [[2.00282311] [-2.99300027]] b = [3.09406662]注意训练后的权重w收敛到接近真实值[2.0, -3.0],偏置b收敛到接近3.0——这正是对"噪声数据可还原真实线性关系"的验证;单次迭代的loss会在噪声影响下波动,因此更应关注参数值而非单个 batch 的损失读数。
结果可视化:
%matplotlib inline %config InlineBackend.figure_format = 'svg' w,b = model.variables plt.figure(figsize = (12,5)) ax1 = plt.subplot(121) ax1.scatter(X[:,0],Y[:,0], c = "b",label = "samples") ax1.plot(X[:,0],w[0]*X[:,0]+b[0],"-r",linewidth = 5.0,label = "model") ax1.legend() plt.xlabel("x1") plt.ylabel("y",rotation = 0) ax2 = plt.subplot(122) ax2.scatter(X[:,1],Y[:,0], c = "g",label = "samples") ax2.plot(X[:,1],w[1]*X[:,1]+b[0],"-r",linewidth = 5.0,label = "model") ax2.legend() plt.xlabel("x2") plt.ylabel("y",rotation = 0) plt.show()四、范例二:DNN 二分类模型
第二个范例把中阶 API 从"单层线性模型"推进到"多层非线性深度模型":用tf.Module子类化的方式组装 3 层 Dense 网络,完成环形分布的二分任务。
4.1 准备数据:环形分布合成数据
import numpy as np import pandas as pd from matplotlib import pyplot as plt import tensorflow as tf from tensorflow.keras import layers,losses,metrics,optimizers %matplotlib inline %config InlineBackend.figure_format = 'svg' #正负样本数量 n_positive,n_negative = 2000,2000 #生成正样本, 小圆环分布 r_p = 5.0 + tf.random.truncated_normal([n_positive,1],0.0,1.0) theta_p = tf.random.uniform([n_positive,1],0.0,2*np.pi) Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1) Yp = tf.ones_like(r_p) #生成负样本, 大圆环分布 r_n = 8.0 + tf.random.truncated_normal([n_negative,1],0.0,1.0) theta_n = tf.random.uniform([n_negative,1],0.0,2*np.pi) Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1) Yn = tf.zeros_like(r_n) #汇总样本 X = tf.concat([Xp,Xn],axis = 0) Y = tf.concat([Yp,Yn],axis = 0) #可视化 plt.figure(figsize = (6,6)) plt.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r") plt.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g") plt.legend(["positive","negative"]);数据生成逻辑:
- 正样本(红色,标签 1):半径约 5.0 的小圆环,
r = 5.0 + 截断正态噪声,角度 θ 在 [0, 2π] 均匀采样,坐标(r·cosθ, r·sinθ); - 负样本(绿色,标签 0):半径约 8.0 的大圆环,同样方式生成;
- 两类样本在二维平面上呈内外两个同心圆环分布,线性分类器无法分割,天然需要带非线性激活函数的深度网络。
构建数据管道(本例缓冲区更大、批次为 100):
ds = tf.data.Dataset.from_tensor_slices((X,Y)) \ .shuffle(buffer_size = 4000).batch(100) \ .prefetch(tf.data.experimental.AUTOTUNE)4.2 定义模型:tf.Module 子类化 + input_signature
class DNNModel(tf.Module): def __init__(self,name = None): super(DNNModel, self).__init__(name=name) self.dense1 = layers.Dense(4,activation = "relu") self.dense2 = layers.Dense(8,activation = "relu") self.dense3 = layers.Dense(1,activation = "sigmoid") # 正向传播 @tf.function(input_signature=[tf.TensorSpec(shape = [None,2], dtype = tf.float32)]) def __call__(self,x): x = self.dense1(x) x = self.dense2(x) y = self.dense3(x) return y model = DNNModel() model.loss_func = losses.binary_crossentropy model.metric_func = metrics.binary_accuracy model.optimizer = optimizers.Adam(learning_rate=0.001)实现要点:
- 通过继承
tf.Module(而非tf.keras.Model)搭建模型,layers.Dense会被自动收集为模块的变量容器——这是"从低阶向高阶过渡"的示范,tf.Module是tf.keras.Model的底层基类(详见 Chapter4-5); - 网络结构为 4→8→1 的三层全连接:前两层用
relu提供非线性表达能力,最后一层用sigmoid把输出压缩到 (0,1),作为正类概率; @tf.function(input_signature=[...])显式声明输入签名:shape=[None,2]表示任意 batch 大小、2 维特征,dtype=tf.float32固定类型。有了签名,该函数在首次调用前即可编译为静态图(无需先用真实数据触发热身),这也是tf.Module可以被tf.saved_model导出为可部署服务(Chapter6-6)的前提;- 二分类任务选用
losses.binary_crossentropy作为损失、metrics.binary_accuracy作为评估指标(损失与指标选型详见 Chapter5-5 与 Chapter5-6),优化器选用Adam——对初学者而言,Adam 搭配默认参数通常是稳妥的首选(Chapter5-7)。
模型定义完成后先用一批数据测试前向传播与指标计算:
(features,labels) = next(ds.as_numpy_iterator()) predictions = model(features) loss = model.loss_func(tf.reshape(labels,[-1]),tf.reshape(predictions,[-1])) metric = model.metric_func(tf.reshape(labels,[-1]),tf.reshape(predictions,[-1])) tf.print("init loss:",loss) tf.print("init metric",metric)输出示例(随机初始化下 loss 接近 ln2≈0.693 的随机猜测水平,accuracy 接近 0.5):
init loss: 1.13653195 init metric 0.54.3 训练模型:同时返回损失与指标
# 使用autograph机制转换成静态图加速 @tf.function def train_step(model, features, labels): with tf.GradientTape() as tape: predictions = model(features) loss = model.loss_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1])) grads = tape.gradient(loss,model.trainable_variables) model.optimizer.apply_gradients(zip(grads,model.trainable_variables)) metric = model.metric_func(tf.reshape(labels,[-1]), tf.reshape(predictions,[-1])) return loss,metric # 测试train_step效果 features,labels = next(ds.as_numpy_iterator()) train_step(model,features,labels)与线性回归范例的差异:
- 梯度作用于
model.trainable_variables而非model.variables:tf.Module内部自动区分可训练变量与不可训练变量,这里所有 Dense 层权重均可训练,二者等价,但语义上更严谨; train_step同时返回 loss 与 metric,便于训练循环中监控准确率;- 首步输出的
(<tf.Tensor ... numpy=1.2033114>, <tf.Tensor ... numpy=0.47>)表明在单个 batch 上 loss 与 accuracy 均会抖动,属正常现象。
def train_model(model,epochs): for epoch in tf.range(1,epochs+1): loss, metric = tf.constant(0.0),tf.constant(0.0) for features, labels in ds: loss,metric = train_step(model,features,labels) if epoch%10==0: printbar() tf.print("epoch =",epoch,"loss = ",loss, "accuracy = ",metric) train_model(model,epochs = 60)运行输出示例:
================================================================================17:07:36 epoch = 10 loss = 0.556449413 accuracy = 0.79 ================================================================================17:07:38 epoch = 20 loss = 0.439187407 accuracy = 0.86 ================================================================================17:07:40 epoch = 30 loss = 0.259921253 accuracy = 0.95 ================================================================================17:07:42 epoch = 40 loss = 0.244920313 accuracy = 0.9 ================================================================================17:07:43 epoch = 50 loss = 0.19839409 accuracy = 0.92 ================================================================================17:07:45 epoch = 60 loss = 0.126151696 accuracy = 0.95可见 loss 从 1.1 左右逐步下降到 0.13,accuracy 从随机水平的 0.5 提升到约 0.95(个别 epoch 的准确率波动源于打印的是该 epoch 最后一个 batch 的瞬时值),说明三层 DNN 成功学会了区分内外圆环。
结果可视化(用tf.boolean_mask按预测阈值 0.5 划分正负类):
fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5)) ax1.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r") ax1.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g") ax1.legend(["positive","negative"]); ax1.set_title("y_true"); Xp_pred = tf.boolean_mask(X,tf.squeeze(model(X)>=0.5),axis = 0) Xn_pred = tf.boolean_mask(X,tf.squeeze(model(X)<0.5),axis = 0) ax2.scatter(Xp_pred[:,0].numpy(),Xp_pred[:,1].numpy(),c = "r") ax2.scatter(Xn_pred[:,0].numpy(),Xn_pred[:,1].numpy(),c = "g") ax2.legend(["positive","negative"]); ax2.set_title("y_pred");左图为真实标签分布,右图为模型预测分布:
右图预测结果与左图几乎重合,直观印证了模型已经把两个圆环分离开来。
五、中阶 API 组件全景:从范例到系统认知
上述两个范例用到了中阶 API 的五类核心组件,仓库 Chapter5 系列文档对每一类都有系统讲解,可对照查阅:
| 组件 | 本文用法 | 深入参考 |
|---|---|---|
数据管道tf.data | from_tensor_slices().shuffle().batch().prefetch() | Chapter5-1 Dataset |
模型层layers | layers.Dense(units, activation)、model.build(input_shape) | Chapter5-4 layers |
损失函数losses | mean_squared_error(回归)、binary_crossentropy(二分类) | Chapter5-5 losses |
评估指标metrics | binary_accuracy(二分类准确率) | Chapter5-6 metrics |
优化器optimizers | SGD(0.001)(回归)、Adam(0.001)(分类) | Chapter5-7 optimizers |
补充几个与范例直接相关的组件使用要点:
- 损失函数与任务匹配:回归首选
mean_squared_error;二分类用binary_crossentropy;多分类时若标签为 one-hot 编码用categorical_crossentropy,若为序号编码则用sparse_categorical_crossentropy。所有损失函数均有函数式与类式两种实现(如binary_crossentropy与BinaryCrossentropy),函数式在本文这种"手动训练循环"中更直接(Chapter5-5)。 - 评估指标与损失的差异:指标不要求连续可导,因此
AUC、Precision、Recall等只能当指标不能当损失。函数式指标在 epoch 内只做 batch 平均,与"全量计算"存在偏差;若追求精确,可用类式metrics.Metric继承实现update_state/result(Chapter5-6)。本例直接使用函数式binary_accuracy打印瞬时值,实现最简。 - 优化器的三种使用方式:
optimizer.apply_gradients(zip(grads, variables))(本文方式)、optimizer.minimize(loss, var_list)、或把优化器交给model.compile/model.fit(高阶 API 方式)。注意优化器内部会创建iterations变量记录迭代次数,因此优化器必须在@tf.function装饰的函数之外创建,理由与tf.Variable一致(Chapter5-7)。 - Autograph 加速是贯穿始终的关键:无论线性模型还是 DNN,
train_step与printbar都用@tf.function编译为静态图。静态图几乎全部在 C++ 内核中执行,并会裁剪与结果无关的算子,从而获得比逐算子 eager 执行更高的效率(Chapter2-2)。
六、总结:中阶 API 的"承上启下"价值
从本文两个范例可以看到中阶 API 的典型分工:tf.data负责喂数据,layers负责参数与前向传播,losses定义优化目标,metrics定义评估口径,optimizers负责参数更新,而tf.GradientTape(低阶)与@tf.function(Autograph)作为粘合剂把训练循环串起来。
这种"手动训练循环 + 中阶组件"的模式,比低阶 API(Chapter3-1)大幅减少了样板代码,又比高阶 API 的model.fit(Chapter3-3)保留了灵活度,特别适合需要自定义训练逻辑、逐批监控指标或研究新算法的场景。掌握这一层之后,再学习 Chapter6 的三种模型构建方法(Sequential、Functional、Model 子类化)与三种训练方法时,就能清楚地理解高阶 API 内部不过是把这些组件按既定流程组织起来而已。
- 教程
- 深度学习
- 机器学习
【免费下载链接】eat_tensorflow2_in_30_days
Tensorflow2.0 🍎🍊 is delicious, just eat it! 😋😋
相关推荐
如何实现TensorFlow损失函数:回归与分类问题详解
如何实现TensorFlow损失函数:回归与分类问题详解 TensorFlow作为目前最流行的机器学习框架之一,其强大的 损失函数 功能为各类模型训练提供了关键
示例工程contribute-to-open-source:从零开始掌握GitHub开源贡献完整指南
contribute to open source:从零开始掌握GitHub开源贡献完整指南 contribute to open source是一个互动式开源
管 50 台远程桌面不慌:RDCMan 实操指南
管 50 台远程桌面不慌:RDCMan 实操指南 周五下午三点,你刚打完一轮补丁,屏幕上叠着十几个远程桌面窗口,其中一个悄悄变灰。你翻了五分钟窗口栏才定位到它,
运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考