AB
AiBoss站
教程

Keras

教程

用 Keras 从零训练第一个二分类神经网络

以 Pima 印第安人糖尿病数据集为例,完整走一遍 Keras 建模流程:加载 CSV、切分输入输出、用 Sequential 堆叠 Dense 层、编译模型、训练、评估并输出预测结果,同时说明各参数的含义与常见坑。

Keras 是一个用 Python 编写、免费开源的深度学习库,现作为 TensorFlow 的一部分提供。它的价值在于把「定义网络结构」和「训练网络」这两件事压缩成很少的代码:用 Sequential 按顺序堆叠层,用 compile 指定损失函数与优化器,用 fit 训练,用 evaluate 评估,用 predict 推理。这篇教程面向已经会写基础 Python、想跑通第一个神经网络的人,用一个二分类数据集把整条链路走完。如果你希望先确认环境与依赖版本,可以参考站内的 Keras 工具页。

全文围绕一个具体任务展开:根据患者的 8 项医学指标,判断其在五年内是否会出现糖尿病。这是一个标准的二分类问题,输入全部是数值型,不需要做文本或图像预处理,因此非常适合作为第一个项目。下面按「加载数据 → 定义模型 → 编译模型 → 训练模型 → 评估模型 → 做出预测」的顺序推进,每一步都给出可直接运行的代码。

准备工作

运行环境

需要具备以下条件:

  • 已安装并配置好的 Python 环境(Python 3 即可)。
  • 已安装 SciPy 与 NumPy,NumPy 用于读取数据与数组切片。
  • 已安装 Keras 及其后端。当前实践中最常见的是通过 TensorFlow 安装,即安装 tensorflow 包后使用 tensorflow.keras 命名空间。

具体的安装命令、Keras 与 TensorFlow 的版本对应关系、以及各版本之间的 API 差异,请以官网当前信息为准。本文代码采用 tensorflow.keras 的导入方式,这是较新的写法;如果你使用的是独立安装的旧版 Keras,导入路径会是 keras.models 与 keras.layers。

数据集

使用 Pima Indians 糖尿病数据集,它来自 UCI 机器学习仓库,是一个被广泛使用的标准数据集。数据描述的是 Pima 印第安人患者的医疗记录,以及他们在五年内是否出现糖尿病。因此目标变量只有两种取值:出现记为 1,未出现记为 0。

把 CSV 文件下载到本地工作目录,与你的 Python 脚本放在同一位置,文件名保存为:

pima-indians-diabetes.csv

文件内容是一行行的逗号分隔数值,形如:

6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

每行 9 列,前 8 列是输入特征,最后一列是类别标签。8 个输入变量的含义依次为:怀孕次数、口服葡萄糖耐量试验 2 小时后的血浆葡萄糖浓度、舒张压(mm Hg)、三头肌皮褶厚度(mm)、2 小时血清胰岛素(mu U/ml)、体重指数(体重 kg 除以身高 m 的平方)、糖尿病家族史函数、年龄(岁)。输出变量是类别变量,取值 0 或 1。

新建脚本文件

新建一个名为 keras_first_network.py 的文件,随着下面的步骤逐步把代码写进去。这样每一步都能单独运行、单独排查问题,比一次性粘贴一大段代码更容易定位错误。

操作步骤

第一步:导入所需的类

先声明要用到的函数与类。用 NumPy 读取数据集,从 Keras 中取两个类来定义模型:Sequential 用于按顺序堆叠层,Dense 用于定义全连接层。

# first neural network with keras tutorial
from numpy import loadtxt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

第二步:加载数据并切分输入输出

用 NumPy 的 loadtxt() 把 CSV 读成一个数值矩阵,注意指定分隔符为逗号,否则整行会被当成一个字符串。

# load the dataset
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=',')
# split into input (X) and output (y) variables
X = dataset[:,0:8]
y = dataset[:,8]

读入后数据是一个二维数组,第一维是行、第二维是列。用 NumPy 的切片操作符 : 按列切分:0:8 取索引 0 到 7 的列,也就是前 8 个输入变量;索引 8 取第 9 列,也就是输出变量。这里最容易出错的地方是切片右端不包含:0:8 选的是 8 列而不是 9 列,正好对应 8 个特征。

至此我们得到的是一个从输入到输出的映射问题,通常写作 y = f(X),即学习一个函数把每一行输入变量映射到输出变量。

第三步:定义模型结构

Keras 中的模型被定义为一串层。创建 Sequential 模型后,逐层添加,直到网络结构满足需要。

首先要确定的是输入层要有正确数量的输入特征。这一点通过在第一层上设置 input_shape 参数完成,本例中设为 (8,),表示把 8 个输入变量作为一个向量输入。

至于要几层、每层用什么类型,没有唯一答案。有一些经验法则可用,但通常最好的结构来自反复试验。总体原则是:网络要足够大,能够捕捉问题的结构。

本例采用全连接网络,共三层。全连接层用 Dense 类定义,第一个参数是该层的神经元数量,activation 参数指定激活函数。前两层使用 ReLU(rectified linear unit)激活函数,输出层使用 Sigmoid。过去所有层都偏好 Sigmoid 与 Tanh,如今用 ReLU 往往能取得更好的效果;输出层用 Sigmoid 则保证网络输出落在 0 到 1 之间,便于解释为属于类别 1 的概率,或者以默认阈值 0.5 直接判定为某一类。

# define the keras model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

逐行看这段代码:模型期望每行数据有 8 个变量,由 input_shape=(8,) 声明;第一个隐藏层有 12 个节点,使用 relu;第二个隐藏层有 8 个节点,使用 relu;输出层有 1 个节点,使用 sigmoid。

这里最让人困惑的一点是:模型的输入形状被写在了第一个隐藏层的参数里。也就是说,添加第一个 Dense 层的那一行代码同时做了两件事——定义了输入层(可见层)和第一个隐藏层。

第四步:编译模型

模型定义好之后需要编译。编译过程会调用底层高效的数值库(即后端),由后端自动选择最适合当前硬件的方式来表示网络,以便在 CPU、GPU 甚至分布式环境下训练和预测。

编译时必须指定训练网络所需的若干属性。训练网络的本质,是找到一组能把输入映射到输出的最佳权重。因此需要指定:用于评估一组权重好坏的损失函数、用于搜索不同权重的优化器,以及训练过程中希望收集并报告的可选指标。

本例是二分类问题,损失函数使用交叉熵,在 Keras 中写作 binary_crossentropy。优化器使用 adam,它是梯度下降的一种高效实现,在多数问题上表现良好,且所需参数较少。此外还指定在训练过程中收集分类准确率指标。

# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

损失函数的选择与任务类型强相关:二分类用 binary_crossentropy,多分类通常用 categorical_crossentropy,回归任务则用均方误差一类。选错损失函数不会报错,但模型几乎学不到东西,这是新手最常见的坑之一。

第五步:训练模型

在数据上训练模型,调用 fit() 方法即可。训练过程会在后台通过后端完成。

训练需要指定两个关键参数:轮数(epochs)和批大小(batch size)。轮数指整个训练数据集被遍历的次数,每一轮可以拆成若干个批次。批大小是一次更新权重前处理的样本数。这两个参数与网络结构一样,需要通过试验来确定。

本例使用 150 轮、批大小 10。这个组合是经过少量试验选出的,在本题上效果不错。

# fit the keras model on the dataset
model.fit(X, y, epochs=150, batch_size=10)

运行后会在终端看到每一轮的进度输出,包含损失值与准确率。如果输出过于冗长,可以在 fit() 中加上 verbose=0 关闭逐轮输出,或设为 verbose=2 只保留每轮一行的摘要。

第六步:评估模型

训练完成后,可以在同一份数据上评估模型的准确率。这一步会返回损失值和你在编译时指定的指标。

# evaluate the keras model
_, accuracy = model.evaluate(X, y)
print('Accuracy: %.2f' % (accuracy*100))

注意,这里是在训练数据上评估,得到的是训练集准确率,它只能说明模型是否具备拟合能力,不能代表泛化性能。要评估泛化能力,需要划分出独立的验证集或测试集。

第七步:做出预测

训练好的模型可以直接对新样本做预测。预测接口返回的是概率值,二分类场景下需要自行按阈值转换。

# make probability predictions with the model
predictions = model.predict(X)
# round predictions
rounded = [round(x[0]) for x in predictions]

由于输出层使用 Sigmoid,predict() 返回的每个值都在 0 到 1 之间。用 round() 取整即可得到 0 或 1 的硬分类结果。若想直接得到类别标签,也可以使用 model.predict_classes() 这类便捷方法,但它在较新的版本中已被弃用,建议统一用概率加阈值的方式处理,兼容性更好。

一个完整示例

把上面七步拼起来,就是一份可以端到端运行的脚本。保存为 keras_first_network.py,确保 CSV 文件在同一目录下,然后执行:

python keras_first_network.py

完整代码如下:

# first neural network with keras tutorial
from numpy import loadtxt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# load the dataset
dataset = loadtxt('pima-indians-diabetes.csv', delimiter=',')
# split into input (X) and output (y) variables
X = dataset[:,0:8]
y = dataset[:,8]

# define the keras model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

# fit the keras model on the dataset
model.fit(X, y, epochs=150, batch_size=10)

# evaluate the keras model
_, accuracy = model.evaluate(X, y)
print('Accuracy: %.2f' % (accuracy*100))

# make probability predictions with the model
predictions = model.predict(X)
# round predictions
rounded = [round(x[0]) for x in predictions]
print(rounded[:10])

运行后你会看到训练过程中逐轮打印的损失与准确率,最后一行输出训练集准确率,以及前 10 个样本的取整预测结果。由于权重初始化带有随机性,每次运行的数值会有波动,这是正常现象。

如果希望结果可复现,可以在脚本开头固定随机种子。Keras 与 NumPy 各自维护随机数状态,需要分别设置,具体接口以官网当前信息为准。

注意事项

输入形状只写在第一层。 input_shape 是第一个 Dense 层的参数,后续层不需要再写,Keras 会根据上一层自动推断。如果给每一层都写 input_shape,会得到形状不匹配的报错。

切片右端不包含。 dataset[:,0:8] 取的是索引 0 到 7 共 8 列。若误写成 0:9,标签列会被混入特征,模型会「作弊」,准确率虚高但毫无意义。

损失函数必须与任务匹配。 二分类用 binary_crossentropy,输出层用 Sigmoid 且只有 1 个节点。若把输出层改成 2 个节点配 softmax,损失函数也要相应换成多分类形式,否则训练不会收敛。

训练集准确率不等于泛化能力。 本文的评估与预测都作用在训练数据上,得到的数字只能说明模型拟合了这批数据。真实场景中应划分训练集与测试集,或使用交叉验证。

网络结构与超参数需要试验。 层数、每层节点数、激活函数、轮数、批大小都没有普适的最优值。本文的 12-8-1 结构与 150 轮、批大小 10 只是针对这个数据集的一组可用配置,换数据集时应重新试验。

API 会随版本变化。 导入路径(tensorflow.keras 与独立 keras)、预测接口(predict_classes 的弃用)在不同版本间存在差异。安装前请以官网当前信息为准,确认 Keras 与后端版本的对应关系。

数据文件路径。 loadtxt 使用的是相对路径,脚本必须在 CSV 所在目录下执行,否则会报文件找不到。若在别处执行,请改成绝对路径。