1.1 数学和Python的复习
我们先来复习一下数学。具体来说,就是以神经网络的计算所需的向量、矩阵等为主题展开讨论。为了顺利地切入神经网络的实现,这里将一并展示相应的Python代码,特别是基于NumPy的代码。
1.1.1 向量和矩阵
在神经网络中,向量和矩阵(或者张量)随处可见。本节将对这些术语进行简单的整理,为读者阅读本书做准备。
我们从向量开始。向量是同时拥有大小和方向的量。向量可以表示为排成一排的数字集合,在Python实现中可以处理为一维数组。与此相对,矩阵是排成二维形状(长方阵)的数字集合。向量和矩阵的例子如图1-1所示。
图1-1 向量和矩阵的例子
如图1-1所示,向量和矩阵可以分别用一维数组和二维数组表示。另外,在矩阵中,将水平方向上的排列称为行(row),将垂直方向上的排列称为列(column)。因此,图1-1中的矩阵可以称为“3行2列的矩阵”,记为“3×2的矩阵”。
将向量和矩阵扩展到N维的数据集合,就是张量。
向量是一个简单的概念,请注意有两种方式表示向量。如图1-2所示,一种是在垂直方向上排列(列向量)的方法,另一种是在水平方向上排列(行向量)的方法。
图1-2 向量的表示方法
在数学和深度学习等许多领域,向量一般作为列向量处理。不过,考虑到实现层面的一致性,本书将向量作为行向量处理(每次都会注明是行向量)。此外,在数学式中写向量或矩阵时,会用x或W等粗体表示,以将它们与单个元素(标量)区分开。在源代码中,会用x或W这样的字体表示。
在Python的实现中,在将向量作为行向量处理的情况下,会将向量明确变形为水平方向上的矩阵。比如,当向量的元素个数是N时,将其处理为形状为1×N的矩阵。我们后面会看一个具体的例子。
下面,我们使用Python的对话模式来生成向量和矩阵。当然,这里将使用处理矩阵的标准库NumPy。
>>> import numpy as np >>> x = np.array([1 , 2 , 3]) >>> x. class # 输出类名 <class 'numpy.ndarray'> >>> x.shape (3,) >>> x.ndim 1 >>> W = np.array([[1 , 2 , 3] , [4 , 5 , 6]]) >>> W.shape (2, 3) >>> W.ndim 2
如上所示,可以使用np.array()方法生成向量或矩阵。该方法会生成NumPy的多维数组类np.ndarray。np.ndarray类有许多便捷的方法和实例变量。上面的例子中使用了实例变量shape和ndim。shape表示多维数组的形状,ndim表示维数。从上面的结果可知,x是一维数组,是一个元素个数为3的向量;而W是一个二维数组,是一个2×3(2行3列)的矩阵。
1.1.2 矩阵的对应元素的运算
前面我们把数字的集合组织为了向量或矩阵,现在利用它们进行一些简单的运算。首先,我们看一下“对应元素的运算”。顺便说一下,“对应元素的”的英文是“element-wise”。
>>> W = np.array([[1 , 2 , 3] , [4 , 5 , 6]]) >>> X = np.array([[0 , 1 , 2] , [3 , 4 , 5]]) >>> W + X array([[ 1, 3, 5], [ 7, 9, 11]]) >>> W * X array([[ 0, 2, 6], [12, 20, 30]])
这里对NumPy多维数组执行了+、*等运算。此时,运算是对应多维数组中的元素(独立)进行的,这就是NumPy数组中的对应元素的运算。
1.1.3 广播
在NumPy多维数组中,形状不同的数组之间也可以进行运算,比如下面这个计算。
>>> A = np.array([[1 , 2] , [3 , 4]]) >>> A * 10 array([[10, 20], [30, 40]])
这个计算是一个2×2的矩阵A乘以标量10。此时,如图1-3所示,标量10先被扩展为2×2的矩阵,之后进行对应元素的运算。这个灵巧的功能称为广播(broadcast)。
图1-3 广播的例子1:标量10被处理为2 × 2的矩阵
下面再看另一个广播的例子。
>>> A = np.array([[1 , 2] , [3 , 4]]) >>> b = np.array([10 , 20]) >>> A * b array([[10, 40], [30, 80]])
在这个计算中,如图1-4所示,一维数组b被“灵巧地”扩展成了与二维数组A相同的形状。
图1-4 广播的例子2
像这样,因为NumPy有广播功能,所以可以智能地执行不同形状的数组之间的运算。
为了使NumPy的广播功能生效,多维数组的形状需要满足几个规则。关于广播的详细规则,请参考文献[1]。
1.1.4 向量内积和矩阵乘积
接着,我们来看一下向量内积和矩阵乘积的相关内容。首先,向量内积可以表示为
这里假设有x=(x1,···, xn)和y=(y1,···, yn)两个向量。此时,如式(1.1)所示,向量内积是两个向量对应元素的乘积之和。
向量内积直观地表示了“两个向量在多大程度上指向同一方向”。如果限定向量的大小为1,当两个向量完全指向同一方向时,它们的向量内积是1。反之,如果两个向量方向相反,则内积为-1。
下面再来看一下矩阵乘积。矩阵乘积可以按照图1-5所示的步骤计算。
图1-5 矩阵乘积的计算方法
如图1-5所示,矩阵乘积通过“左侧矩阵的行向量(水平方向)”和“右侧矩阵的列向量(垂直方向)”的内积(对应元素的乘积之和)计算得出。此时,计算结果保存在新矩阵的对应元素中。比如,A的第1行和B的第1列的结果保存在第1行第1列的元素中,A的第2行和B的第1列的结果保存在第2行第1列的元素中。
现在,我们用Python实现一下向量内积和矩阵乘积。为此,可以利用np.dot()。
# 向量内积 >>> a = np.array([1 , 2 , 3]) >>> b = np.array([4 , 5 , 6]) >>> np.dot(a , b) 32 # 矩阵乘积 >>> A = np.array([[1 , 2] , [3 , 4]]) >>> B = np.array([[5 , 6] , [7 , 8]]) >>> np.dot(A , B) array([[19, 22], [43, 50]])
如上所示,向量内积和矩阵乘积中都使用了np.dot()。当np.dot(x, y)的参数都是一维数组时,计算向量内积。当参数都是二维数组时,计算矩阵乘积。
除了这里看到的np.dot()方法外,NumPy还有很多其他的进行矩阵计算的便捷方法。如果能熟练掌握这些方法,神经网络的实现就会更顺利。
熟能生巧
要掌握NumPy,实际动手练习是最有效的。比如,“100 numpy exercises”[2]中准备了100道NumPy的练习题。如果你想积累NumPy经验,请挑战一下。
1.1.5 矩阵的形状检查
在使用矩阵和向量的计算中,很重要的一点就是要注意它们的形状。这里,我们留意着矩阵的形状,再来看一下矩阵乘积。前面已经介绍过了矩阵乘积的计算步骤,所以这里我们将重点放在图1-6所示的“形状检查”上。
图1-6 形状检查:在矩阵乘积中,要使对应维度的元素个数一致
图1-6展示了基于3×2的矩阵A和2×4的矩阵B生成3×4的矩阵C的示例。此时,如该图所示,需要对齐矩阵A和矩阵B的对应维度的元素个数。作为计算结果的矩阵C的形状由A的行数和B的列数组成。这就是矩阵的形状检查。
在矩阵乘积等计算中,注意矩阵的形状并观察其变化的形状检查非常重要。据此,神经网络的实现可以更顺利地进行。