2.1一个小例子
加载数据集
from keras.datasets import mnist (train_images, train_labels),
(test_images, test_labels) = mnist.load_data()
网路
from keras import models from keras import layers
network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))
编译
network.compile(optimizer='rmsprop', loss='categorical_crossentropy',
metrics=['accuracy'])
数据处理
train_images = train_images.reshape((60000, 28 * 28))
这里((60000, 28 * 28))而不是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
准备标签
from keras.utils import to_categorical train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
训练
network.fit(train_images, train_labels, epochs=5, batch_size=128)
直接评估
test_loss, test_acc = network.evaluate(test_images, test_labels) >>> print('test_acc:', test_acc)
2.2 神经网络的数据表示
标量:
没有轴没有维度
>>> import numpy as np
>>> x = np.array(12)
>>> x
array(12)
>>> x.ndim
0
向量:
只有一个轴,沿着这个轴由5个维度
>>> x = np.array([12, 3, 6, 14, 7])
>>> x
array([12, 3, 6, 14, 7])
>>> x.ndim
1
矩阵(2D 张量):
两个轴,0轴是行,1轴是列。
>>> x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
>>> x.ndim
2
3D 张量与更高维张量
x = np.array([[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1],[7, 80, 4, 36, 2]]])
这个矩阵的形状 3,3,5
轴的个数print(train_images.ndim)
形状print(train_images.shape)
数据类型:print(train_images.dtype)
图像显示
digit = train_images[4]
import matplotlib.pyplot as plt
plt.imshow(digit, cmap=plt.cm.binary)
plt.show()
操作张量:
my_slice = train_images[10:100]
print(my_slice.shape)
(90, 28, 28)
一样的效果
>>> my_slice = train_images[10:100, :, :]
>>> my_slice.shape (90, 28, 28)
>>> my_slice = train_images[10:100, 0:28, 0:28]
>>> my_slice.shape
(90, 28, 28)
一般来说,你可以沿着每个张量轴在任意两个索引之间进行选择。例如,你可以在所有图 像的右下角选出 14 像素×14 像素的区域:my_slice = train_images[:, 14:, 14:]
也可以使用负数索引。与 Python 列表中的负数索引类似,它表示与当前轴终点的相对位置。 你可以在图像中心裁剪出 14 像素×14 像素的区域:my_slice = train_images[:, 7:-7, 7:-7]
图像数据:
(128, 256, 256, 3)
可以表示为:
视频数据格式:一个以每秒 4 帧采样的 60 秒YouTube 视频片段,视频尺寸为 144×256,这个 视频共有 240 帧。4 个这样的视频片段组成的批量将保存在形状为(4, 240, 144, 256, 3)
2.3张量运算
relu 运算的简单实现
def naive_relu(x):
assert len(x.shape) == 2
x = x.copy()x 是一个 Numpy 的 2D 张量 避免覆盖输入张量
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] = max(x[i, j], 0)
return x
两个矩阵的加法:
def naive_add(x, y):
assert len(x.shape) == 2
assert x.shape == y.shape
x = x.copy()
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] += y[i, j]
return x
在实践中处理 Numpy 数组时,这些运算都是优化好的 Numpy 内置函数,这些函数将大量运算交给安装好的基础线性代数子程序(BLAS,basic linear algebra subprograms)实现(没装 的话,应该装一个)。BLAS 是低层次的、高度并行的、高效的张量操作程序,通常用 Fortran或C语言来实现。
因此,在 Numpy 中可以直接进行下列逐元素运算,速度非常快。:
import numpy as np z = x + y逐元素的相加
z = np.maximum(z, 0.)逐元素的 relu
广播
上一节 naive_add 的简单实现仅支持两个形状相同的 2D 张量相加。如果将两个形状不同的张量相加,会发生 什么?较小的张量会被广播(broadcast),以匹配较大张量的形状。广播包含 以下两步。
(1) 向较小的张量添加轴(叫作广播轴),使其 ndim 与较大的张量相同。
(2) 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
来看一个具体的例子。假设 X 的形状是 (32, 10),y 的形状是 (10,)。首先,我们给 y 添加空的第一个轴,这样 y 的形状变为 (1, 10)。然后,我们将 y 沿着新轴重复 32 次,这样得到的张量 Y 的形状为 (32, 10),
def naive_add_matrix_and_vector(x, y):
assert len(x.shape) == 2
assert len(y.shape) == 1
assert x.shape[1] == y.shape[0]
x = x.copy()
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] += y[j]
return x
张量点积
import numpy as np
z = np.dot(x, y)
矩阵的点乘
import numpy as np
def naive_matrix_vector_dot(x, y):
assert len(x.shape) == 2
assert len(y.shape) == 1
assert x.shape[1] == y.shape[0]
z = np.zeros(x.shape[0])
for i in range(x.shape[0]):
for j in range(x.shape[1]):
z[i] += x[i, j] * y[j]
return z
张量变形
>>> x = np.array([[0., 1.], [2., 3.], [4., 5.]])
>>> print(x.shape) (3, 2)
>>> x = x.reshape((6, 1))
>>> x
array([[ 0.], [ 1.], [ 2.], [ 3.], [ 4.],
[ 5.]])
矩阵转置:
>>> x = np.zeros((300, 20))
>>> x = np.transpose(x)
>>> print(x.shape)
(20, 300)
2.4基于梯度的优化
训练过程:
(1) 抽取训练样本 x 和对应目标 y 组成的数据批量。 (2) 在 x 上运行网络[这一步叫作前向传播(forward pass)],得到预测值 y_pred。
(3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
(4) 更新网络的所有权重,使网络在这批数据上的损失略微下降。
难点在于第四步:更新网 络的权重。考虑网络中某个权重系数,你怎么知道这个系数应该增大还是减小,以及变化多少? 一种简单的解决方案是,保持网络中其他权重不变,只考虑某个标量系数,让其尝试不同
的取值。假设这个系数的初始值为 0.3。对一批数据做完前向传播后,网络在这批数据上的损失 是 0.5。如果你将这个系数的值改为 0.35 并重新运行前向传播,损失会增大到 0.6。但如果你将 这个系数减小到 0.25,损失会减小到 0.4。在这个例子中,将这个系数减小 0.05 似乎有助于使
损失最小化。对于网络中的所有系数都要重复这一过程。
但这种方法是非常低效的,因为对每个系数(系数很多,通常有上千个,有时甚至多达上 百万个)都需要计算两次前向传播(计算代价很大)。一种更好的方法是利用网络中所有运算都 是可微(differentiable)的这一事实,计算损失相对于网络系数的梯度(gradient),然后向梯度
的反方向改变系数,从而使损失降低。(这里CS231也讲了)
随机梯度下降:
你可以使用 2.4 节开头总结的四步算法:基于当前在随机数据批量上的损失,一点一点地对参数进行调节。由于处理的是一个可微函数,你可以计算出它的梯度,从而有效地实 现第四步。沿着梯度的反方向更新权重,损失每次都会变小一点。
(1) 抽取训练样本 x 和对应目标 y 组成的数据批量。
(2) 在 x 上运行网络,得到预测值 y_pred。
(3) 计算网络在这批数据上的损失,用于衡量 y_pred 和 y 之间的距离。
(4) 计算损失相对于网络参数的梯度[一次反向传播(backward pass)]。
(5) 将参数沿着梯度的反方向移动一点,比如W -= step * gradient,从而使这批数据
上的损失减小一点。
我刚刚描述的方法叫作小批量随机梯度下降,又称为小批量SGD
SGD 还有多种变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新, 而不是仅仅考虑当前梯度值,比如带动量的 SGD、Adagrad、RMSProp 等变体。这些变体被称 为优化方法(optimization method)或优化器(optimizer)。其中动量的概念尤其值得关注,它在
许多变体中都有应用。动量解决了 SGD 的两个问题:收敛速度和局部极小点。
使用动量方法可以避免这样的问题,这一方法的灵感来源于物理学。有一种有用的思维图像, 就是将优化过程想象成一个小球从损失函数曲线上滚下来。如果小球的动量足够大,那么它不会 卡在峡谷里,最终会到达全局最小点。动量方法的实现过程是每一步都移动小球,不仅要考虑当 前的斜率值(当前的加速度),还要考虑当前的速度(来自于之前的加速度)。这在实践中的是指,
更新参数 w 不仅要考虑当前的梯度值,还要考虑上一次的参数更新,其简单实现如下所示。
past_velocity = 0. momentum = 0.1
while loss > 0.01:
不变的动量因子 优化循环
w, loss, gradient = get_current_parameters()
velocity = past_velocity * momentum - learning_rate * gradient
w = w + momentum * velocity - learning_rate * gradient past_velocity = velocity
update_parameter(w)