深度学习进阶:自然语言处理
上QQ阅读APP看书,第一时间看更新

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 向量的表示方法

在数学和深度学习等许多领域,向量一般作为列向量处理。不过,考虑到实现层面的一致性,本书将向量作为行向量处理(每次都会注明是行向量)。此外,在数学式中写向量或矩阵时,会用xW等粗体表示,以将它们与单个元素(标量)区分开。在源代码中,会用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的列数组成。这就是矩阵的形状检查。

在矩阵乘积等计算中,注意矩阵的形状并观察其变化的形状检查非常重要。据此,神经网络的实现可以更顺利地进行。