购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.1 函数

什么是函数?如何描述函数?与神经网络一样,函数也可以用多种方法描述,但没有一种方法能完整地描绘它。与其尝试给出简单的一句话描述,不如像盲人摸象那样,依次根据每个维度来了解函数。

数学

下面是两个用数学符号描述的函数示例。

以上有两个函数,分别为 ,当输入数字 时,第一个函数将其转换为 ,第二个函数则将其转换为

示意图

下面是一种描绘函数的方法。

  1. 绘制一个 平面(其中 表示横轴, 表示纵轴)。
  2. 绘制一些点,其中,点的 坐标是函数在某个范围内的输入(通常是等距的), 坐标则是该范围内函数的输出。
  3. 连接所绘制的点。

这种利用坐标的方法是法国哲学家勒内 · 笛卡儿发明的,它在许多数学领域非常有用,特别是微积分领域。图 1-1 展示了以上两个函数的示意图。

图 1-1:两个连续、基本可微的函数

然而,还有另一种描绘函数的方法,这种方法在学习微积分时并没有那么有用,但是对于思考深度学习模型非常有帮助。可以把函数看作接收数字(输入)并生成数字(输出)的盒子,就像小型工厂一样,它们对输入的处理有自己的内部规则。图 1-2 通过一般规则和具体的输入描绘了以上两个函数。

图 1-2:另一种描绘函数的方法

代码

最后,可以使用代码描述这两个函数。在开始之前,先介绍一下 NumPy 这个 Python 库,下面会基于该库编写函数。

  1. NumPy 库

    NumPy 是一个广泛使用的 Python 库,用于快速数值计算,其内部大部分使用 C 语言编写。简单地说,在神经网络中处理的数据将始终保存在一个 多维数组 中,主要是一维数组、二维数组、三维数组或四维数组,尤其以二维数组或三维数组居多。NumPy 库中的 ndarray 类能够让我们以直观且快速的方式计算这些数组。举一个最简单的例子,如果将数据存储在 Python 列表或列表的嵌套列表中,则无法使用常规语法实现数据对位相加或相乘,但 ndarray 类可以实现:

    print("Python list operations:")
    a = [1,2,3]
    b = [4,5,6]
    print("a+b:", a+b)
    try:
        print(a*b)
    except TypeError:
        print("a*b has no meaning for Python lists")
    print()
    print("numpy array operations:")
    a = np.array([1,2,3])
    b = np.array([4,5,6])
    print("a+b:", a+b)
    print("a*b:", a*b)
    Python list operations:
    a+b: [1, 2, 3, 4, 5, 6]
    a*b has no meaning for Python lists
    
    numpy array operations:
    a+b: [5 7 9]
    a*b: [ 4 10 18]

    ndarray 还具备 维数组所具有的多个特性:每个 ndarray 都具有 个轴(从 0 开始索引),第一个轴为轴 0,第二个轴为轴 1,以此类推。另外,由于二维 ndarray 较为常见,因此可以将轴 0 视为行,将轴 1 视为列,如图 1-3 所示。

    图 1-3:一个二维 ndarray ,其中轴 0 为行,轴 1 为列

    NumPy 库的 ndarray 类还支持以直观的方式对这些轴应用函数。例如,沿轴 0(二维数组的 )求和本质上就是沿该轴“折叠数组”,返回的数组比原始数组少一个维度。对二维数组来说,这相当于对每一列进行求和:

    print('a:')
    print(a)
    print('a.sum(axis=0):', a.sum(axis=0))
    print('a.sum(axis=1):', a.sum(axis=1))
    
    a:
    [[1 2]
     [3 4]]
    a.sum(axis=0): [4 6]
    a.sum(axis=1): [3 7]

    ndarray 类支持将一维数组添加到最后一个轴上。对一个 列的二维数组 a 而言,这意味着可以添加长度为 的一维数组 b 。NumPy 将以直观的方式进行加法运算,并将元素添加到 a 的每一行中

    a = np.array([[1,2,3],
                  [4,5,6]])
    
    b = np.array([10,20,30])
    
    print("a+b:\n", a+b)
    
    a+b:
    [[11 22 33]
     [14 25 36]]
  2. 类型检查函数

    如前所述,本书代码的主要目标是确保概念描述的准确性和清晰性。随着本书内容的展开,这将变得更具挑战性,后文涉及编写带有许多参数的函数,这些参数是复杂类的一部分。为了解决这个问题,本书将在整个过程中使用带有类型签名的函数。例如,在第3章中,我们将使用如下方式初始化神经网络:

    def __init__(self,
                 layers: List[Layer],
                 loss: Loss,
                 learning_rate: float = 0.01) -> None:

    仅通过类型签名,就能了解该类的用途。与此相对,考虑以下 可用于定义运算 的类型签名:

    def operation(x1, x2):

    这个类型签名本身并没有给出任何提示。只有打印出每个对象的类型,查看对每个对象执行的运算,或者根据名称 x1 x2 进行猜测,才能够理解该函数的功能。这里可以改为定义具有类型签名的函数,如下所示:

    def operation(x1: ndarray, x2: ndarray) -> ndarray:

    很明显,这是接受两个 ndarray 的函数,可以用某种方式将它们组合在一起,并输出该组合的结果。由于它们读起来更为清楚,因此本书将使用经过类型检查的函数。

  3. NumPy 库中的基础函数

    了解前面的内容后,现在来编写之前通过 NumPy 库定义的函数:

    def square(x: ndarray) -> ndarray:
        '''
        将输入ndarray中的每个元素进行平方运算。
        '''
        return np.power(x, 2)
    
    def leaky_relu(x: ndarray) -> ndarray:
        '''
        将Leaky ReLU函数应用于ndarray中的每个元素。
        '''
        return np.maximum(0.2 * x, x)

    NumPy 库有一个奇怪的地方,那就是可以通过使用 np. function_name (ndarray) ndarray. function_name 将许多函数应用于 ndarray 。例如,前面的 ReLU 函数可以编写成 x.clip(min=0) 。本书将尽量保持一致,在整个过程中遵循 np. function_name (ndarray) 约定,尤其会避免使用 ndarray .T 之类的技巧来转置二维 ndarray ,而会使用 np.transpose( ndarray , (1, 0))

    如果能通过数学、示意图和代码这 3 个维度来表达相同的基本概念,就说明你已经初步拥有了真正理解深度学习所需的灵活思维。 C6cmB6uxhXQv3x/hbt6NIworOm98XHd0DIZkyeRSigpKzvR7S8j7a9qXRyCtZh4r

点击中间区域
呼出菜单
上一章
目录
下一章
×