@TOC


前言

刚进公司,需要学开发需要的新框架及现成框架,完成自己的需求。那么在此期间,遇到新东西,就可以以此为基础,学习新语言新框架新工具…,可能会学到一些新技术、新知识,总结一下,以供后面参考学习,查看。肯定大家都有一定差异啦,大家作为参考哦,与诸君共勉。

一、问题:将手写数字的灰度图像(28像素×28像素)划分到10个类别中(从0到9)。

1. 加载Keras中的MNIST数据集

  • 我们将使用MNIST数据集,它是机器学习领域的一个经典数据集,其历史几乎和这个领域一样长,而且已被人们深入研究。这个数据集包含60 000张训练图像和10 000张测试图像,由美国国家标准与技术研究院(National Institute of Standards and Technology,即MNIST中的NIST)在20世纪80年代收集而成。你可以将“解决”MNIST问题看作深度学习的“Hello World”,用来验证你的算法正在按预期运行。
    1
    2
    3
    4
    5
    //加载Keras中的MNIST数据集
    from tensorflow.keras.datasets import mnist
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    //train_images和train_labels组成了训练集,模型将从这些数据中进行学习。然后,我们在测试集(包括test_images和test_labels)上对模型进行测试。
    //图像被编码为NumPy数组,而标签是一个数字数组,取值范围是09。图像和标签一一对应。
  • 张量train_images的轴的个数,即ndim属性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> train_images.ndim
    3
    //它的形状。
    >>> train_images.shape
    (60000, 28, 28)
    //它的数据类型,即dtype属性。
    >>> train_images.dtype
    uint8
    - 可见,train_images是一个由8位整数组成的3阶张量。更确切地说,它是由60 000个矩阵组成的数组,每个矩阵由28×28个整数组成。每个这样的矩阵都是一张灰度图像,元素取值在0255之间。我们用Matplotlib库(著名的Python数据可视化库,预装在Colab中)来显示这个3阶张量中的第4个数字。
    //总结:
    输入数据。
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    train_images = train_images.reshape((60000, 28 * 28))
    train_images = train_images.astype("float32") / 255
    test_images = test_images.reshape((10000, 28 * 28))
    test_images = test_images.astype("float32") / 255
    //现在你明白,输入图像保存在float32类型的NumPy张量中,其形状分别为(60000, 784)(训练数据)和(10000, 784)(测试数据)。

2.训练数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> train_images.shape
(60000, 28, 28)
>>> len(train_labels)
60000
>>> train_labels
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
再来看一下测试数据:
>>> test_images.shape
(10000, 28, 28)
>>> len(test_labels)
10000
>>> test_labels
array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)
  • 工作流程如下:

    • 首先,将训练数据(train_images和train_labels)输入神经网络;
    • 然后,神经网络学习将图像和标签关联在一起;
    • 最后,神经网络对test_images进行预测,我们来验证这些预测与test_labels中的标签是否匹配。

3.模型与模型编译。神经网络架构-Tensorflow。【模型由许多层链接在一起组成,并将输入数据映射为预测值。随后,损失函数将这些预测值与目标值进行比较,得到一个损失值,用于衡量模型预测值与预期结果之间的匹配程度。优化器将利用这个损失值来更新模型权重。】

1
2
3
4
5
6
7
8
9
10
11
12
13
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(512, activation="relu"),
layers.Dense(10, activation="softmax")
])
//本例中的模型包含2个Dense层,它们都是密集连接(也叫全连接)的神经层。第2层(也是最后一层)是一个10路softmax分类层,它将返回一个由10个概率值(总和为1)组成的数组。每个概率值表示当前数字图像属于10个数字类别中某一个的概率。
//模型。
model = keras.Sequential([
layers.Dense(512, activation="relu"),
layers.Dense(10, activation="softmax")
])
//现在你明白,这个模型包含两个链接在一起的Dense层,每层都对输入数据做一些简单的张量运算,这些运算都涉及权重张量。权重张量是该层的属性,里面保存了模型所学到的知识。
  • 神经网络的核心组件是层(layer)。你可以将层看成数据过滤器:进去一些数据,出来的数据变得更加有用。具体来说,层从输入数据中提取表示——我们期望这种表示有助于解决手头的问题。大多数深度学习工作涉及将简单的层链接起来,从而实现渐进式的数据蒸馏(data distillation)。
    • 深度学习模型就像是处理数据的筛子,包含一系列越来越精细的数据过滤器(也就是层)。
    • 在训练模型之前,我们还需要指定编译(compilation)步骤的3个参数。
      • 优化器(optimizer):模型基于训练数据来自我更新的机制,其目的是提高模型性能。
      • 损失函数(loss function):模型如何衡量在训练数据上的性能,从而引导自己朝着正确的方向前进。
      • 在训练和测试过程中需要监控的指标(metric):本例只关心精度(accuracy),即正确分类的图像所占比例。
        1
        2
        3
        4
        5
        //编译步骤
        model.compile(optimizer="rmsprop",
        loss="sparse_categorical_crossentropy",
        metrics=["accuracy"])
        //在开始训练之前,我们先对数据进行预处理,将其变换为模型要求的形状,并缩放到所有值都在[0, 1]区间。前面提到过,训练图像保存在一个uint8类型的数组中,其形状为(60000, 28, 28),取值区间为[0, 255]。我们将把它变换为一个float32数组,其形状为(60000, 28 * 28),取值范围是[0, 1]。
    • 准备图像数据
      1
      2
      3
      4
      train_images = train_images.reshape((60000, 28 * 28))
      train_images = train_images.astype("float32") / 255
      test_images = test_images.reshape((10000, 28 * 28))
      test_images = test_images.astype("float32") / 255
    • 准备开始训练模型。在Keras中,这一步是通过调用模型的fit方法来完成的——我们在训练数据上拟合(fit)模型
      1
      2
      3
      4
      5
      >>> model.fit(train_images, train_labels, epochs=5, batch_size=128)
      Epoch 1/5
      60000/60000 [===========================] - 5s - loss: 0.2524 - acc: 0.9273
      Epoch 2/5
      51328/60000 [=====================>.....] - ETA: 1s - loss: 0.1035 - acc: 0.9692
      • 训练过程中显示了两个数字:
        • 一个是模型在训练数据上的损失值(loss),
        • 另一个是模型在训练数据上的精度(acc)。我们很快就在训练数据上达到了0.989(98.9%)的精度。

4.利用模型进行预测

  • 现在我们得到了一个训练好的模型,可以利用它来预测新数字图像的类别概率。这些新数字图像不属于训练数据,比如可以是测试集中的数据。
    1
    2
    3
    4
    5
    6
    7
    8
    //利用模型进行预测
    >>> test_digits = test_images[0:10]
    >>> predictions = model.predict(test_digits)
    >>> predictions[0]
    array([1.0726176e-10, 1.6918376e-10, 6.1314843e-08, 8.4106023e-06,
    2.9967067e-11, 3.0331331e-09, 8.3651971e-14, 9.9999106e-01,
    2.6657624e-08, 3.8127661e-07], dtype=float32)
    这个数组中每个索引为i的数字对应数字图像test_digits[0]属于类别i的概率。
  • 第一个测试数字在索引为7时的概率最大(0.99999106,几乎等于1),所以根据我们的模型,这个数字一定是7。
    1
    2
    3
    4
    5
    6
    7
    8
    >>> predictions[0].argmax()
    7
    >>> predictions[0][7]
    0.99999106
    //我们可以检查测试标签是否与之一致:
    >>> test_labels[0]
    7
    //平均而言,我们的模型对这种前所未见的数字图像进行分类的效果如何?我们来计算在整个测试集上的平均精度.

5.在新数据上评估模型

1
2
3
4
>>> test_loss, test_acc = model.evaluate(test_images, test_labels)
>>> print(f"test_acc: {test_acc}")
test_acc: 0.9785
//测试精度约为97.8%,比训练精度(98.9%)低不少。训练精度和测试精度之间的这种差距是过拟合(overfit)造成的。过拟合是指机器学习模型在新数据上的性能往往比在训练数据上要差,它是第4章的核心主题。

6.用TensorFlow从头开始逐步重新实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//简单的Dense类.Dense层实现了下列输入变换,其中W和b是模型参数,activation是一个逐元素的函数(通常是relu,但最后一层是softmax)。
output = activation(dot(W, input) + b)
//我们来实现一个简单的Python类NaiveDense,它创建了两个TensorFlow变量W和b,并定义了一个__call__()方法供外部调用,以实现上述变换。
import tensorflow as tf

class NaiveDense:
def __init__(self, input_size, output_size, activation):
self.activation = activation

w_shape = (input_size, output_size) ←----创建一个形状为(input_size, output_size)的矩阵W,并将其随机初始化
w_initial_value = tf.random.uniform(w_shape, minval=0, maxval=1e-1)
self.W = tf.Variable(w_initial_value)

b_shape = (output_size,) ←----创建一个形状为(output_size,)的零向量b
b_initial_value = tf.zeros(b_shape)
self.b = tf.Variable(b_initial_value)

def __call__(self, inputs): ←----前向传播
return self.activation(tf.matmul(inputs, self.W) + self.b)

@property
def weights(self): ←----获取该层权重的便捷方法
return [self.W, self.b]
//简单的Sequential类
//下面我们创建一个NaiveSequential类,将这些层链接起来。它封装了一个层列表,并定义了一个__call__()方法供外部调用。这个方法将按顺序调用输入的层。它还有一个weights属性,用于记录该层的参数。
class NaiveSequential:
def __init__(self, layers):
self.layers = layers

def __call__(self, inputs):
x = inputs
for layer in self.layers:
x = layer(x)
return x

@property
def weights(self):
weights = []
for layer in self.layers:
weights += layer.weights
return weights
//利用这个NaiveDense类和NaiveSequential类,我们可以创建一个与Keras类似的模型。
model = NaiveSequential([
NaiveDense(input_size=28 * 28, output_size=512, activation=tf.nn.relu),
NaiveDense(input_size=512, output_size=10, activation=tf.nn.softmax)
])
assert len(model.weights) == 4
//批量生成器.接下来,我们需要对MNIST数据进行小批量迭代。这很简单。
import math

class BatchGenerator:
def __init__(self, images, labels, batch_size=128):
assert len(images) == len(labels)
self.index = 0
self.images = images
self.labels = labels
self.batch_size = batch_size
self.num_batches = math.ceil(len(images) / batch_size)
def next(self):
images = self.images[self.index : self.index + self.batch_size]
labels = self.labels[self.index : self.index + self.batch_size]
self.index += self.batch_size
return images, labels
//最难的一步就是“训练步骤”,即在一批数据上运行模型后更新模型权重。我们需要做到以下几点。
//(1)计算模型对图像批量的预测值。
//(2)根据实际标签,计算这些预测值的损失值。
//(3)计算损失相对于模型权重的梯度。
//(4)将权重沿着梯度的反方向移动一小步。
//要计算梯度,我们需要用到2.4.4节介绍的TensorFlow GradientTape对象。
def one_training_step(model, images_batch, labels_batch):
with tf.GradientTape() as tape: ←---- (本行及以下4行)运行前向传播,即在GradientTape作用域内计算模型//预测值
predictions = model(images_batch)
per_sample_losses = tf.keras.losses.sparse_categorical_crossentropy(
labels_batch, predictions)
average_loss = tf.reduce_mean(per_sample_losses)
gradients = tape.gradient(average_loss, model.weights) ←----计算损失相对于权重的梯度。输出gradients是一个列表,每个元素对应model.weights列表中的权重
update_weights(gradients, model.weights) ←----利用梯度来更新权重(稍后给出这个函数的定义)
return average_loss
//如你所知,“更新权重”这一步(由update_weights函数实现)的目的,就是将权重沿着减小批量损失值的方向移动“一小步”。移动幅度由学习率决定,它通常是一个很小的数。要实现这个update_weights函数,最简单的方法就是从每个权重中减去gradient * learning_rate。
learning_rate = 1e-3

def update_weights(gradients, weights):
for g, w in zip(gradients, weights):
w.assign_sub(g * learning_rate) ←---- assign_sub相当于TensorFlow变量的-=
//在实践中,你几乎不会像这样手动实现权重更新,而是会使用Keras的Optimizer实例,如下所示。
from tensorflow.keras import optimizers

optimizer = optimizers.SGD(learning_rate=1e-3)

def update_weights(gradients, weights):
optimizer.apply_gradients(zip(gradients, weights))
//现在我们已经实现了对每批数据的训练,下面继续实现一轮完整的训练。一轮训练就是对训练数据的每个批量都重复上述训练步骤,而完整的训练循环就是重复多轮训练。
def fit(model, images, labels, epochs, batch_size=128):
for epoch_counter in range(epochs):
print(f"Epoch {epoch_counter}")
batch_generator = BatchGenerator(images, labels)
for batch_counter in range(batch_generator.num_batches):
images_batch, labels_batch = batch_generator.next()
loss = one_training_step(model, images_batch, labels_batch)
if batch_counter % 100 == 0:
print(f"loss at batch {batch_counter}: {loss:.2f}")
//我们来试运行一下。
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

fit(model, train_images, train_labels, epochs=10, batch_size=128)
//我们可以评估模型,方法是对模型在测试图像上的预测值取argmax,并将其与预期标签进行比较。
predictions = model(test_images)
predictions = predictions.numpy() ←----对TensorFlow张量调用.numpy(),可将其转换为NumPy张量
predicted_labels = np.argmax(predictions, axis=1)
matches = predicted_labels == test_labels
print(f"accuracy: {matches.mean():.2f}")
//拥有这种对代码底层原理的思维模型,可以让你更好地使用Keras API的高级功能。

巨人的肩膀

  • 周志明老师的凤凰架构
  • AIGC:智能创作时代
  • Python深度学习