TensorFlow2 结构化数据建模全流程范例:基于 titanic 生存预测的实战教程
TensorFlow2 结构化数据建模全流程范例:基于 titanic 生存预测的实战教程
本文以《30天吃掉那只TensorFlow2》第一章的第一个实战范例为蓝本,完整演示使用 TensorFlow2 + Keras 对**结构化数据(表格数据)**进行建模的六步标准流程:准备数据、定义模型、训练模型、评估模型、使用模型、保存模型。读者学完后将掌握 Pandas 特征工程、Sequential 模型构建、compile/fit 训练、指标评估以及 Keras 与 SavedModel 两种模型保存方案,可直接迁移到自己的表格类机器学习项目中。
结构化数据是工业界最常见的数据形态(如 CRM 表、订单表、风控特征表),而 Titanic 生存预测正是这类问题最经典的入门案例:根据乘客的个人信息预测其在泰坦尼克号撞上冰山沉没后能否幸存。本项目完整的六步建模流程定义可见 一、TensorFlow的建模流程,本范例的配套数据存放在 data/titanic/ 目录(train.csv 共 712 条训练样本,test.csv 共 179 条测试样本,含表头各多一行)。
一,准备数据:DataFrame 读取与探索性分析
结构化数据一般使用 Pandas 中的 DataFrame 进行预处理,这也是全流程中对新手来说最困难、最需要打磨的环节。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import models, layers
dftrain_raw = pd.read_csv('./data/titanic/train.csv')
dftest_raw = pd.read_csv('./data/titanic/test.csv')
dftrain_raw.head(10)
字段含义与处理方案
原数据共 11 个字段,它们的类型与建模策略如下,这也是整个特征工程的蓝图:
| 字段 | 含义 | 类型 | 处理方案 |
|---|---|---|---|
| Survived | 0 代表死亡,1 代表存活 | y 标签 | 直接作为监督信号 |
| Pclass | 乘客所持票类 | 类别 | 三种取值(1,2,3),转换成 onehot 编码 |
| Name | 乘客姓名 | 文本 | 舍去 |
| Sex | 乘客性别 | 类别 | 转换成 bool 特征 |
| Age | 乘客年龄 | 数值 | 有缺失;作为数值特征,并添加"年龄是否缺失"辅助特征 |
| SibSp | 乘客兄弟姐妹/配偶个数 | 数值 | 整数值特征 |
| Parch | 乘客父母/孩子个数 | 数值 | 整数值特征 |
| Ticket | 票号 | 文本 | 舍去 |
| Fare | 票价 | 数值 | 浮点数,0-500 不等 |
| Cabin | 所在船舱 | 类别 | 有缺失;添加"所在船舱是否缺失"辅助特征 |
| Embarked | 登船港口 | 类别 | 取值 S、C、Q,有缺失;转成四维度 onehot(S,C,Q,nan) |
从字段方案可以看出结构化建模的核心思想:类别字段 onehot 化、数值字段直接保留、低信息量的文本字段丢弃、缺失信息本身也编码成特征(Age_null、Cabin_null),避免在填充或丢弃时丢失"是否缺失"这一信息。
EDA:探索性数据分析
利用 Pandas 自带的可视化能力,可以快速完成 EDA(Exploratory Data Analysis),对标签分布、特征分布、特征与标签的关系建立直觉。
1)label 分布情况
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw['Survived'].value_counts().plot(kind = 'bar',
figsize = (12,8),fontsize=15,rot = 0)
ax.set_ylabel('Counts',fontsize = 15)
ax.set_xlabel('Survived',fontsize = 15)
plt.show()
2)年龄分布情况
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw['Age'].plot(kind = 'hist',bins = 20,color= 'purple',
figsize = (12,8),fontsize=15)
ax.set_ylabel('Frequency',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()
3)年龄和 label 的相关性
%matplotlib inline
%config InlineBackend.figure_format = 'png'
ax = dftrain_raw.query('Survived == 0')['Age'].plot(kind = 'density',
figsize = (12,8),fontsize=15)
dftrain_raw.query('Survived == 1')['Age'].plot(kind = 'density',
figsize = (12,8),fontsize=15)
ax.legend(['Survived==0','Survived==1'],fontsize = 12)
ax.set_ylabel('Density',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()
通过密度曲线叠加,可以直观观察到不同年龄段的存活差异,为后续建模提供先验判断。
正式数据预处理:特征工程函数
把字段处理方案固化为一个 preprocessing 函数,对训练集和测试集施加同一套转换,避免训练/测试不一致:
def preprocessing(dfdata):
dfresult = pd.DataFrame()
#Pclass
dfPclass = pd.get_dummies(dfdata['Pclass'])
dfPclass.columns = ['Pclass_' +str(x) for x in dfPclass.columns ]
dfresult = pd.concat([dfresult,dfPclass],axis = 1)
#Sex
dfSex = pd.get_dummies(dfdata['Sex'])
dfresult = pd.concat([dfresult,dfSex],axis = 1)
#Age
dfresult['Age'] = dfdata['Age'].fillna(0)
dfresult['Age_null'] = pd.isna(dfdata['Age']).astype('int32')
#SibSp,Parch,Fare
dfresult['SibSp'] = dfdata['SibSp']
dfresult['Parch'] = dfdata['Parch']
dfresult['Fare'] = dfdata['Fare']
#Cabin
dfresult['Cabin_null'] = pd.isna(dfdata['Cabin']).astype('int32')
#Embarked
dfEmbarked = pd.get_dummies(dfdata['Embarked'],dummy_na=True)
dfEmbarked.columns = ['Embarked_' + str(x) for x in dfEmbarked.columns]
dfresult = pd.concat([dfresult,dfEmbarked],axis = 1)
return(dfresult)
x_train = preprocessing(dftrain_raw)
y_train = dftrain_raw['Survived'].values
x_test = preprocessing(dftest_raw)
y_test = dftest_raw['Survived'].values
print("x_train.shape =", x_train.shape )
print("x_test.shape =", x_test.shape )
x_train.shape = (712, 15)
x_test.shape = (179, 15)
预处理后每行样本被转换为 15 维特征向量:Pclass 3 维 onehot + Sex 2 维 onehot + Age 数值 + Age_null 缺失标志 + SibSp + Parch + Fare + Cabin_null 缺失标志 + Embarked 4 维 onehot(含 nan 维),合计 3+2+1+1+1+1+1+1+4 = 15。注意 pd.get_dummies(..., dummy_na=True) 会把缺失值单独编成一维,这正是 Embarked 输出四维的原因。
二,定义模型:Sequential 按层顺序构建
使用 Keras 接口有以下 3 种方式构建模型:
- 使用 Sequential 按层顺序构建模型;
- 使用函数式 API 构建任意结构模型;
- 继承 Model 基类构建自定义模型。
三种方式的完整对比与适用场景可参考仓库章节 6-1,构建模型的3种方法:顺序结构优先用 Sequential;多输入/多输出、共享权重、残差连接等非顺序结构推荐函数式 API;Model 子类化灵活但出错概率更高。本例网络结构是简单的三层全连接,因此选择最简单的 Sequential:
tf.keras.backend.clear_session()
model = models.Sequential()
model.add(layers.Dense(20,activation = 'relu',input_shape=(15,)))
model.add(layers.Dense(10,activation = 'relu' ))
model.add(layers.Dense(1,activation = 'sigmoid' ))
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 20) 320
_________________________________________________________________
dense_1 (Dense) (None, 10) 210
_________________________________________________________________
dense_2 (Dense) (None, 1) 11
=================================================================
Total params: 541
Trainable params: 541
Non-trainable params: 0
_________________________________________________________________
关键设计要点:
- 输入维度:
input_shape=(15,)与预处理后的特征维度严格对应; - 隐藏层:20 与 10 个神经元,
relu激活缓解梯度消失; - 输出层:单个神经元 +
sigmoid激活,输出 (0,1) 区间概率,适配二分类; - 参数量自洽:第一层
15×20+20=320,第二层20×10+10=210,输出层10×1+1=11,合计 541 个可训练参数; tf.keras.backend.clear_session()用于清除之前的模型/层命名残留,保证每次运行命名空间干净,在 notebook 反复执行时尤其重要。
三,训练模型:内置 fit 方法
训练模型通常有 3 种方法:内置 fit 方法、内置 train_on_batch 方法、自定义训练循环,详见仓库章节 6-2,训练模型的3种方法。其中 fit 方法功能最强大,支持 numpy array、tf.data.Dataset 与 Python generator 三种数据源,且可通过回调函数实现复杂训练控制。本例选择最常用也最简单的内置 fit 方法:
# 二分类问题选择二元交叉熵损失函数
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['AUC'])
history = model.fit(x_train,y_train,
batch_size= 64,
epochs= 30,
validation_split=0.2 #分割一部分训练数据用于验证
)
Train on 569 samples, validate on 143 samples
Epoch 1/30
569/569 [==============================] - 1s 2ms/sample - loss: 3.5841 - AUC: 0.4079 - val_loss: 3.4429 - val_AUC: 0.4129
...
Epoch 30/30
569/569 [==============================] - 0s 80us/sample - loss: 0.5071 - AUC: 0.8349 - val_loss: 0.5816 - val_AUC: 0.7605
对核心配置的解读:
- optimizer='adam':自适应学习率优化器,无需手动调节学习率,是表格数据建模的默认选择;
- loss='binary_crossentropy':二分类问题的标准损失函数,配合输出层 sigmoid 使用;
- metrics=['AUC']:以 AUC 作为监控指标。相比 accuracy,AUC 对类别不平衡更稳健,是二分类排序类任务(如风控、营销响应)的主流评估指标;
- validation_split=0.2:从 712 条训练数据中自动切出 20% 作为验证集,即 train 569 / validate 143;
- batch_size=64、epochs=30:每个 epoch 分 9 个 batch 左右完成前向-反向传播,重复 30 轮。
从训练日志可以观察到标准的学习曲线形态:训练 loss 从 3.58 稳步下降到 0.51,训练 AUC 从 0.41 上升到 0.83;验证 AUC 同步爬升到 0.76 附近后趋于平缓,说明模型在前 30 轮内已完成有效拟合且没有明显过拟合。
四,评估模型:训练曲线与测试集指标
首先评估模型在训练集和验证集上的动态效果。model.fit 返回的 history.history 记录了每个 epoch 的 loss 与 AUC,封装一个通用绘图函数即可观察训练曲线:
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
def plot_metric(history, metric):
train_metrics = history.history[metric]
val_metrics = history.history['val_'+metric]
epochs = range(1, len(train_metrics) + 1)
plt.plot(epochs, train_metrics, 'bo--')
plt.plot(epochs, val_metrics, 'ro-')
plt.title('Training and validation '+ metric)
plt.xlabel("Epochs")
plt.ylabel(metric)
plt.legend(["train_"+metric, 'val_'+metric])
plt.show()
plot_metric(history,"loss")
plot_metric(history,"AUC")
两幅曲线图清晰展示:loss 单调下降、AUC 单调上升,且训练曲线与验证曲线间距不大,说明模型泛化良好。再进一步在测试集上评估(测试集不参与训练,是最可信的泛化指标):
model.evaluate(x = x_test,y = y_test)
[0.5191367897907448, 0.8122605]
返回值依次为测试集 loss 与 AUC:loss≈0.519,AUC≈0.812。测试集 AUC 达到 0.81 以上,对于仅 15 维特征、三层全连接的小模型而言,已能较好刻画乘客存活规律。
五,使用模型:概率预测与类别预测
训练好的模型可用于两种粒度的预测:输出连续的存活概率,或直接输出离散的类别。
# 预测概率
model.predict(x_test[0:10])
# model(tf.constant(x_test[0:10].values,dtype = tf.float32)) # 等价写法
array([[0.26501188],
[0.40970832],
[0.44285864],
[0.78408605],
[0.47650957],
[0.43849158],
[0.27426785],
[0.5962582 ],
[0.59476686],
[0.17882936]], dtype=float32)
# 预测类别
model.predict_classes(x_test[0:10])
array([[0],
[0],
[0],
[1],
[0],
[0],
[0],
[1],
[1],
[0]], dtype=int32)
注意 model(tf.constant(...)) 是调用模型对象的等价写法,因为 Keras 模型本身可调用(callable)。概率预测适用于需要排序或设置自定义阈值的场景(如风控评分卡),类别预测则直接给出 0/1 判定。
六,保存模型:Keras 方式与 TensorFlow 原生方式
模型保存有两种路线:Keras 方式和 TensorFlow 原生方式。前者仅适合在 Python 环境中恢复模型;后者保存的是标准 SavedModel 格式,可跨平台部署。官方推荐使用后一种。
1,Keras 方式保存
(1)保存模型结构及权重(单文件 .h5)
# 保存模型结构及权重
model.save('./data/keras_model.h5')
del model #删除现有模型
# identical to the previous one
model = models.load_model('./data/keras_model.h5')
model.evaluate(x_test,y_test)
[0.5191367897907448, 0.8122605]
save + load_model 一步到位,恢复后的模型评估结果与保存前完全一致([0.519..., 0.812...]),证明结构、权重、编译配置均被完整保留。仓库 data/ 目录下的 keras_model.h5 即本步骤的产物。
(2)分别保存模型结构与权重
# 保存模型结构
json_str = model.to_json()
# 恢复模型结构
model_json = models.model_from_json(json_str)
# 保存模型权重
model.save_weights('./data/keras_model_weight.h5')
# 恢复模型结构
model_json = models.model_from_json(json_str)
model_json.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['AUC']
)
# 加载权重
model_json.load_weights('./data/keras_model_weight.h5')
model_json.evaluate(x_test,y_test)
[0.5191367897907448, 0.8122605]
to_json() 只保存网络拓扑结构,save_weights 只保存权重张量,二者需配合使用:先用 model_from_json 恢复结构,再 compile(注意权重恢复后必须重新 compile 才能 evaluate),最后 load_weights 载入参数。仓库中的 data/keras_model_weight.h5 即权重文件产物。
2,TensorFlow 原生方式保存
(1)仅保存权重(Checkpoint 格式)
# 保存权重,该方式仅仅保存权重张量
model.save_weights('./data/tf_model_weights.ckpt',save_format = "tf")
该方式只保存权重张量,不包含模型结构,恢复时需要先重建结构再载入权重。仓库 data/ 下的 tf_model_weights.ckpt.data-00000-of-00001 与 tf_model_weights.ckpt.index 即其产物。
(2)保存完整模型(SavedModel 格式,跨平台部署)
# 保存模型结构与模型参数到文件,该方式保存的模型具有跨平台性便于部署
model.save('./data/tf_model_savedmodel', save_format="tf")
print('export saved model.')
model_loaded = tf.keras.models.load_model('./data/tf_model_savedmodel')
model_loaded.evaluate(x_test,y_test)
[0.5191365896656527, 0.8122605]
SavedModel 是 TensorFlow 的官方可移植格式,目录结构为 data/tf_model_savedmodel/:saved_model.pb 存放图结构,variables/ 目录存放权重分片(本仓库产物中含多个 variables.data-* 分片与 variables.index),assets/ 存放辅助资源(本例为 saved_model.json)。恢复后的评估结果为 [0.5191365896656527, 0.8122605],与保存前仅有浮点尾数级差异。
SavedModel 的价值在于跨平台:它不依赖 Python 运行时,可被 TensorFlow Serving(参见仓库章节 6-6,使用tensorflow-serving部署模型)、TensorFlow Lite、TensorFlow.js 以及 spark-scala(6-7,使用spark-scala调用tensorflow模型)等加载,是实现工业级模型落地的关键一步。
小结:结构化数据建模的通用范式
回顾全流程,本范例将结构化数据建模抽象为一条可复用的流水线:
- 准备数据:DataFrame 读取 → EDA 可视化洞察 → 统一预处理函数(类别 onehot、缺失标志、数值归一/保留)→ 得到数值矩阵;
- 定义模型:根据数据维度与任务类型选择 Sequential/函数式 API/Model 子类化;
- 训练模型:
compile配置优化器-损失-指标,fit指定 batch、epochs、验证集划分; - 评估模型:绘制训练曲线判断拟合状态,用
evaluate在测试集上确认泛化指标; - 使用模型:
predict输出概率,predict_classes输出类别; - 保存模型:开发期用
.h5快速存取,上线期用 SavedModel 跨平台部署。
该流程与本仓库其余三个建模范例(图片数据见 1-2,图片数据建模流程范例、文本数据见 1-3,文本数据建模流程范例、时间序列数据见 1-4,时间序列数据建模流程范例)共同构成 TensorFlow2 建模方法论的第一章;后续张量、计算图、自动微分等底层概念可继续学习 二、TensorFlow的核心概念。本仓库全部代码基于 TensorFlow 2.1 版本在 Jupyter 环境测试通过,可直接按 README.md 的学习方案在本地或云端运行复现。




