优秀外文博文搬运 + 翻译
原作者:Victor Zhou
链接:Keras for Beginners: Building Your First Neural Network @Victor Zhou
本文为转载翻译内容,文章内容著作权归原作者所有
本文翻译已获得原作者授权
以下为翻译内容(斜体加粗部分为博主补充内容,非原文内容):
Keras 是 Python 中一种简单易用但却非常强大的深度学习库。在这篇博文中,我们将认识到用 Keras 去建立一个前馈型神经网络,训练并用它解决实际问题是多么的简单。
这篇博文主要面向 Keras 的完全零基础初学者,但是默认读者有一定的神经网络知识。我的这篇博文:机器学习初学:神经网络原理 + Python 简单实例
基本涵盖了你阅读本篇文章所需的全部知识,如果需要的话可以先看看。
开工吧!
只想要代码?完整代码放在文末了。
我们将解决一个在机器学习中非常非常经典的问题:MNIST 手写数字集的分类(classification)。它的要求很简单:给定一张图片(手写的单个数字),将它分类为 0 - 9 中的某个数字。
MNIST 数据集中的每一张图都是一个 28x28(像素),居中的灰度数字图。我们需要将这个 28x28 转换成一个有 784 个维度的向量然后作为神经网络的输入。输出将会是 10 种分类里的一种:即 0 - 9 中的某个数字。
我这里默认你已经安装了基本的 Python(大概),我们先来装一些我们需要的 Python 包:
$ pip install tensorflow numpy mnist
注:我们不需要单独去安装 keras
包因为它现在已经正式作为 TensorFlow 的高级(high-level)API 和 TensorFlow 绑定了。比起单独的 keras 包现在更推荐用 TensorFlow 下的 keras
。
你现在应该能导入这些软件包并查看 MNIST 数据集了:
import numpy as np
import mnist
from tensorflow import keras
# 第一次运行可能会有点慢,因为需要下载并缓存 mnist 数据包
train_images = mnist.train_images()
train_labels = mnist.train_labels()
print(train_images.shape) # (60000, 28, 28)
print(train_labels.shape) # (60000,)
这里博主在自己测试的时候发现导入数据集时可能会出现报错,如果上述方法无法导入 MNIST 数据集,可以采用如下方法,同样是从 keras 的数据集中加载 MNIST,对后面程序不会有影响:
import numpy as np
from tensorflow import keras
from keras.datasets import mnist # 从 keras 中导入 mnist
# 获取完整 MNIST 数据集
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(train_images.shape) # (60000, 28, 28)
print(train_labels.shape) # (60000,)
导入之后我们还可以通过 matplotlib 查看每张图片 :
import numpy as np
import matplotlib.pyplot as plt # 导入 matplot 绘图包
from tensorflow import keras
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# plot第1张图片 (从0开头)
img = plt.imshow(train_images[1],cmap='gray')
plt.show()
之前提到过,我们需要先将图片数据转换一下以方便输入神经网络。另外我们还需要把图片中的像素值从 [0, 255] 归一化(normalize)至 [-0.5, 0.5] 这个区间让神经网络更容易训练(一般来说用更小,更集中的数值比较好训练)。
import numpy as np
from tensorflow import keras
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 对图片数据归一化处理.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5
# 转换图片数据.
train_images = train_images.reshape((-1, 784))
test_images = test_images.reshape((-1, 784))
print(train_images.shape) # (60000, 784)
print(test_images.shape) # (10000, 784)
注:上述代码中的 reshape 将原本 60000 页,每页 28 行 28 列的三维数据转换成了 60000 行,每行 28x28=784 列的二维数据,每一行代表一张图片,方便作为神经网络的输入。感兴趣的朋友可以自行查一下 reshape -1
的用法。
我们已经准备好构建自己的神经网络了!
每个 Keras 模型都可以由 Sequential 类建立,它代表了多个层的线性堆叠。另外也可以由功能性的 Model 类建立,它的自定义性更强。我们这里将使用较为简单的 Sequential
类,因为我们的神经网络确实就是多个层的线性堆叠而已。
我们从实例化一个 Sequential
模型开始:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
# WIP
model = Sequential([
# layers...
])
Sequential
的构造器可以包含一个代表 Keras 层(layers)的数组。由于我们只是搭建一个标准前馈网络,我们只需要 Dense
层,也就是标准的全连接(dense)网络层。
废话不多说我们先上3个层:
# WIP
model = Sequential([
Dense(64, activation='relu'),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
前面两个层各自有 64 个节点(神经元),且每个节点采用 ReLU 激活函数。最后一个输出层是有10个节点的 Softmax 层,每个节点对应一个数字分类。
你或许想复习一下 Softmax 函数,可以看看我的这篇介绍:Softmax 函数简单解释
最后一件事就是告诉 Keras 我们的数据集是长啥样的。我们可以把输入维度填到 Sequential
模型第一层的 input_shape
中:
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
一旦确定了输入的维度,Keras 会自动推断后面层的输入维度。我们已经成功定义了网络模型,贴一下到目前为止的完整代码:
import numpy as np
import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
train_images = mnist.train_images()
train_labels = mnist.train_labels()
test_images = mnist.test_images()
test_labels = mnist.test_labels()
# 对图片数据归一化处理.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5
# 转换图片数据维度.
train_images = train_images.reshape((-1, 784))
test_images = test_images.reshape((-1, 784))
# 建立模型.
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
在训练模型之前,我们要对训练流程做配置。我们把这个步骤划分为 3 个关键点:
binary_corssentropy
(只有两种分类)或者 categorical_crossentropy
(大于两种分类),我们这里选择后者。(关于交叉熵损失函数的详细解析,可以参考知乎大佬文章:交叉熵损失函数 @飞鱼Talk)。下面是模型编译器的最终设定:
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'],
)
接着干!
在 Keras 中训练模型其实只需要调用 fit()
函数并且确定一些参数就完事了。fit()
函数有很多可填写的参数,但是目前我们只需填写一部分:
下面是这些参数在代码中的样子:
model.fit(
train_images, # 训练集
train_labels, # 训练集的标签
epochs=5,
batch_size=32,
)
光有这些神经网络还不能工作,我们还漏了一件事:Keras 希望训练后的输出是一个 10 维的向量,因为 Softmax 输出层有 10 个节点。但我们提供的是一个单一的整数来代表图像的类别。
方便的是,Keras 自带一个实用的方法来解决这个问题:
to_categorical
。它会将我们的整数类数组转换成一个单热(one-hot)向量数组。比如说输出为 2
就会变成 [0,0,1,0,0,0,0,0,0,0]
(从 0 开头)
(博主额外补充一下:一般来说,10个节点的 Softmax 函数输出的是一个包含10个概率值的数组,每个值分别对应某一分类的概率。在这个例子中,这 10 个概率值分别表示被处理的这张图片属于 0 - 9 中哪一个数字的概率。最高的概率值所对应的分类也就是此次输出的结果。以原作者的输出是 2 为例,这表明输出的数组中 10 个概率值里,第 3 个值最大(从 0 开头),表示这次输出结果相信这张图中的手写数字属于数字 2。to_categorical
应该是将输出数组中最大值标为1,其他全都标 0,得到了一个新的单热向量数组)
现在我们可以把所有环节都放在一起然后训练神经网络了:
import numpy as np
from tensorflow import keras
from keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 对图片数据归一化处理.
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5
# 转换图片数据.
train_images = train_images.reshape((-1, 784))
test_images = test_images.reshape((-1, 784))
# 建立模型.
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
# 编译模型.
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'],
)
# 训练模型.
model.fit(
train_images,
to_categorical(train_labels),
epochs=5,
batch_size=32,
)
运行结果大概会像这样:
Epoch 1/5
60000/60000 [==============================] - 2s 35us/step - loss: 0.3772 - acc: 0.8859
Epoch 2/5
60000/60000 [==============================] - 2s 31us/step - loss: 0.1928 - acc: 0.9421
Epoch 3/5
60000/60000 [==============================] - 2s 31us/step - loss: 0.1469 - acc: 0.9536
Epoch 4/5
60000/60000 [==============================] - 2s 31us/step - loss: 0.1251 - acc: 0.9605
Epoch 5/5
60000/60000 [==============================] - 2s 31us/step - loss: 0.1079 - acc: 0.9663
我们在 5 个训练迭代后达到了 96.6% 的准确率!虽然这并不能说明什么,因为有可能出现过拟合(overfitting)问题。真正的挑战还需看我们的模型在测试集中的表现。(过拟合问题指模型在训练集上的表现非常好,但在交叉验证集和测试集上表现平平。换言之,模型可能对训练集的数据过度学习,过度贴合,导致对训练集范围之外数据的预测能力一般,即泛化能力很弱)
博主补充:这里不知道是因为版本原因还是其他原因,博主自己测试的训练结果是这样的:
Epoch 1/5
1875/1875 [==============================] - 6s 3ms/step - loss: 0.3533 - accuracy: 0.8938
Epoch 2/5
1875/1875 [==============================] - 5s 3ms/step - loss: 0.1827 - accuracy: 0.9447
Epoch 3/5
1875/1875 [==============================] - 5s 3ms/step - loss: 0.1435 - accuracy: 0.9564
Epoch 4/5
1875/1875 [==============================] - 5s 3ms/step - loss: 0.1197 - accuracy: 0.9633
Epoch 5/5
1875/1875 [==============================] - 5s 3ms/step - loss: 0.1052 - accuracy: 0.9671
最终准确率没有什么问题,重点在于每个 Epoch 下显示的是 1875/1875 而不是作者的 60000/60000 。由于我们训练前填写了 batch_size = 32
(即使不填写,系统也默认是32),模型是分批次处理训练数据并更新的。由于批次大小设定是 32,所以每次迭代将处理 60000/32 = 1875 个批次的数据,即如结果显示的那样。作者也设定了 32 的batch_size 但是显示仍是 60000/60000,也许是不同版本的原因,虽然不影响结果但是不同的显示方式可能会造成一定的困惑。Stackoverflow 上也有人提出了相同的问题:Keras not training on entire dataset,可以参考一下。
评估测试模型很简单:
model.evaluate(
test_images,
to_categorical(test_labels)
)
跑一遍会给出这样的结果:
10000/10000 [==============================] - 0s 15us/step
[0.10821614159140736, 0.965]
(注:这里可能同样存在上面提到的显示问题)
evaluate()
返回一个包含测试集 loss 的数组,当然也可以设置让它包含更多其他衡量指标(metrics)。我们的模型在这次测试中达到了 0.108 的 loss 与 96.5% 的准确率!对于我们的第一个神经网络来说还算不错 ~
现在我们有了一个训练完毕且能用的模型了,我们来把它投入应用。首先我们得把训练好的模型参数保存起来以便随时可以加载它:
model.save_weights('model.h5')
(注:保存为 .h5 文件)
现在我们可以通过加载已保存的模型信息(包含权重和偏移量等参数)来随时建立之前已训练的模型:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
# 建立模型.
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
# 加载已保存的模型参数.
model.load_weights('model.h5')
使用已训练的模型会让预测更加简单:我们向 predict()
中传入一组输入,然后它返回一组输出。别忘了我们神经网络输出的是10个概率值(由于 softmax 函数),还需要用 np.argmax()
将它们转成实际数字。
# 预测前5个图片.
predictions = model.predict(test_images[:5])
# 打印模型的预测结果.
print(np.argmax(predictions, axis=1)) # [7, 2, 1, 0, 4]
# 打印正确答案(标签)以确认预测结果的正确性.
print(test_labels[:5]) # [7, 2, 1, 0, 4]
到目前为止我们只是对 Keras 有了一个简单的了解,还有更多的东西可以用来测试和提升模型的表现。接下来我介绍一部分例子:
调整 Adam 优化器的学习率(learning rate)是一个学习超参的好开头。当你增加或减少学习率的时候,模型会发生什么呢?
from tensorflow.keras.optimizers import Adam # 导入优化器相关包
model.compile(
optimizer=Adam(lr=0.005), # 设置学习率
loss='categorical_crossentropy',
metrics=['accuracy'],
)
那修改批次大小 batch_size
和迭代次数 epochs
又会怎么样呢?
model.fit(
train_images,
to_categorical(train_labels),
epochs=10, # 设置迭代次数
batch_size=64, # 设置批次大小
)
如果我们增加或减少全连接层(fully-connected layers)的数量会发生什么?它会如何影响神经网络的训练和最终表现?
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(64, activation='relu'), # 增加更多的层
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
如果我们采用别的激活函数而不是 ReLU 呢?(比如说改成 Sigmoid)
model = Sequential([
Dense(64, activation='sigmoid', input_shape=(784,)), # 激活函数换成 sigmoid 函数
Dense(64, activation='sigmoid'),
Dense(10, activation='softmax'),
])
我们还可以加入 dropout 层试试,它用于预防过拟合问题。(dropout 层会以一定的概率禁用一部分神经元来减少模型对局部特征的依赖,强化泛化能力。Keras 中设置的概率值是神经元被丢弃的概率 )
from tensorflow.keras.layers import Dense, Dropout # 导入相关包
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dropout(0.5), # 加入 dropout 层
Dense(64, activation='relu'),
Dropout(0.5), # 加入 dropout 层
Dense(10, activation='softmax'),
])
我们也可以用测试集作为神经网络训练期间的验证数据。Keras 会在每次训练迭代结束后用验证集评估神经网络的表现,并汇报 loss 或其他我们要求输出的指标。这允许我们在训练阶段就能保持对神经网络的监视,对于鉴定过拟合问题,甚至是提早结束训练都很有用。
model.fit(
train_images,
to_categorical(train_labels),
epochs=5,
batch_size=32,
validation_data=(test_images, to_categorical(test_labels)) # 设置验证集
)
你成功的用 Keras 实现了自己的第一个神经网络,并且在 MNIST 数据集上用 5 次训练迭代达到了 96.5% 的准确率,还不错啦。我会在下面再附上完整代码供你参考。
import numpy as np
from tensorflow import keras
from keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 对图片数据归一化处理..
train_images = (train_images / 255) - 0.5
test_images = (test_images / 255) - 0.5
# 转换图片数据.
train_images = train_images.reshape((-1, 784))
test_images = test_images.reshape((-1, 784))
# 建立模型.
model = Sequential([
Dense(64, activation='relu', input_shape=(784,)),
Dense(64, activation='relu'),
Dense(10, activation='softmax'),
])
# 编译模型.
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'],
)
# 训练模型.
model.fit(
train_images,
to_categorical(train_labels),
epochs=5,
batch_size=32,
)
# 评估模型.
model.evaluate(
test_images,
to_categorical(test_labels)
)
# 保存模型参数.
model.save_weights('model.h5')
# 加载模型参数(保存参数后无需再次训练,直接加载参数即可使用模型):
# model.load_weights('model.h5')
# 对前 5 张图片做预测.
predictions = model.predict(test_images[:5])
# 输出模型预测结果.
print(np.argmax(predictions, axis=1)) # [7, 2, 1, 0, 4]
# 输出真实答案对照.
print(test_labels[:5]) # [7, 2, 1, 0, 4]
期待翻译小哥的更多相关教程!