Numpy矩阵基础

作者: ibunny | 来源:发表于2017-08-15 16:42 被阅读1355次

    Numpy简介

    NumPy 简介
    Python 很方便,但也会很慢。不过,它允许你访问执行用 C 等语言编写的代码的库。NumPy 就是这样一个库:它为 Python 中的数学运算提供了一个更快速的替代方案,可以与数字组高效搭配使用 - 如矩阵。
    NumPy 是一个很大的库,我们在这里只讲一些皮毛。如果你打算用 Python 进行很多的数学计算, 则很有必要花一些时间阅读这篇文档 以了解更多。

    导入 NumPy
    在导入 NumPy 库时,大多数情况下(包括这里)你会看到的一个约定,是将其命名为 np,像这样:
    import numpy as np

    现在,你可以给函数和类型名称加上前缀 “np” 来使用该库,你会在下面的示例中看到。

    数据类型和形状
    NumPy 中处理数字的最常见方式是通过 ndarray
    对象。他们与 Python 列表相似,但是可以有任意数量的维度。而且,ndarray
    支持快速的数学运算,这就是我们想要的。
    由于它可以存储任意数量的维度,你可以使用 ndarray
    来表示我们以前涵盖的任何数据类型:标量、向量、矩阵或张量。

    标量
    NumPy 中的标量 比 Python 中的标量类型更多。不像 Python 只有基本类型 int、float 等,NumPy 可以让你指定有符号和无符号的类型以及不同的大小。因此,除了 Python 的 int,你可以使用 uint8、int8、uint16、int16 等类型。
    这些类型很重要,因为你所创建的每个对象(向量、矩阵、张量)最终都会存储标量。而且,当你创建 NumPy 数组时,可以指定类型 - 但是数组中的每个项必须具有相同的类型。在这方面,NumPy 数组更像是 C 数组,而非 Python 列表。
    如果要创建一个包含标量的 NumPy 数组,方法是将值传递给 NumPy 的 array 函数,如下所示:
    s = np.array(5)

    不过你仍然可以在 ndarray、NumPy 标量和普通的 Python 标量之间执行数学运算,你将在元素级数学运算课程中看到。你可以通过检查数组的 shape属性来查看数组的形状。你可以执行代码:
    s.shape
    它会打印出结果,即一对空括号 ()。 这表示它的维度为零,是标量。
    即使标量位于数组中,你仍然可以像正常标量一样使用它们。你可以键入:
    x = s + 3
    x 现在会等于 8。如果你检查 x 的类型,可能会发现它是 numpy.int64,因为它在使用 NumPy 类型,而不是 Python。
    顺便说一下,即使标量类型也支持大部分的数组函数。所以你可以调用 x.shape,它会返回 (),因为它的维度为零,即使它不是数组。如果你使用普通的 Python 标量来尝试,就会收到一个错误。

    Vectors
    要创建一个向量,你可以将 Python 列表传递给 array
    函数,像这样:
    v = np.array([1,2,3])

    如果你检查向量的shape属性,它将返回表示向量的一维长度的单个数字。在上面的示例中,v.shape会返回(3,)
    现在有了数字,你可以看到形状是一个元组,以及每个 ndarray的维度的大小。对于标量,它只是一个空的元组,但是向量有一个维度,所以元组包含一个数字和一个逗号。(Python 不能将 (3)理解为具有一个项的元组,所以它需要逗号。你可以在此 阅读有关元组的更多信息)
    你也可以使用索引访问向量中的元素,如下所示:
    x = v[1]

    现在 x 等于 2。
    NumPy 还支持高级索引技术。例如,要从第二个元素向前访问项目,你可以这样说:v[1:],然后它会返回数组[2, 3]。NumPy 切片功能非常强大,允许你访问 ndarray中的任何项目组合。但它有时也会有点复杂,你可以在此文档中阅读更多详情。

    Matrices
    你使用 NumPy 的 array 函数创建矩阵,跟创建向量一样。但是,这次你不只是传入一个列表,而是提供列表的列表,其中每个列表代表一行。所以要创建一个包含数字 1 到 9 的 3x3 矩阵,你可以这样做:
    m = np.array([[1,2,3], [4,5,6], [7,8,9]])

    检查它的 shape属性将返回元组 (3, 3),表示它有两个维度,每个的长度为 3。你可以像向量一样访问矩阵的元素,但要使用额外的索引值。所以要在上面的矩阵中找到数字 6,你可以访问 m[1][2]。

    张量
    张量与向量和矩阵一样,但它们可以有更多的维度。例如,要创建一个 3x3x2x1 的张量,你可以这样做:
    t = np.array([[[[1],[2]],[[3],[4]],[[5],[6]]],[[[7],[8]],\ [[9],[10]],[[11],[12]]],[[[13],[14]],[[15],[16]],[[17],[17]]]])

    t.shape 会返回 (3, 3, 2, 1)
    你可以像在矩阵中一样访问项目,但需要使用更多的索引。所以 t[2][1][1][0]将返回 16

    更改形状
    有时,你需要更改数据的形状,而无需实际更改其内容。例如,你可能有一个一维的向量,但是需要一个二维的矩阵。实现它的方式有两种。
    假设你有以下向量:
    v = np.array([1,2,3,4])
    调用 v.shape会返回(4,)。但要是你想得到一个 1x4 矩阵呢?你可以使用 reshape 函数,就像这样:
    x = v.reshape(1,4)
    调用 x.shape会返回 (1,4)。如果你想获得一个 4x1 矩阵,可以这样做:
    x = v.reshape(4,1)

    reshape函数不只是添加大小为 1 的维度。查阅此文档 了解更多示例。
    关于更改 NumPy 数组的形状还有一点:如果你看看有经验的 NumPy 用户的代码,经常会看到他们使用一种特殊的切片语法,而不是调用 reshape。使用该语法,前面的两个示例会是这样的:x = v[None, :]or x = v[:, None]

    这些行创建一个切片,查看 v的所有项目,但要求 NumPy 为相关轴添加大小为 1 的新维度。你现在看起来可能觉得很奇怪,但它是一种常见的技术,所以了解它没什么坏处。


    Numpy元素级运算

    Python 中的方式
    假设你有一个数字列表,你想向列表中的每一项加上 5 。如果没有 NumPy,你可以像下面这样做:

    values = [1,2,3,4,5]
    for i in range(len(values)):
        values[i] += 5
    # 现在的值为 [6,7,8,9,10]
    

    这是讲得通的,但是你要编写很多代码,而且因为它是纯 Python,所以运行的很慢。

    NumPy 中的方式
    在 NumPy 中,我们可以这么做:

    values = [1,2,3,4,5]
    values = np.array(values) + 5
    # 现在值是包含 [6,7,8,9,10] 的一个 ndarray
    

    创建该数组可能看起来很奇怪,但通常你总是要将数据存储在 ndarray 中的。所以如果你已经有一个名为 values的 ndarray,你可以这么做:
    values += 5

    我们应该指出,NumPy 实际上有用于添加、乘法等的函数。但它也支持使用标准的数学运算符。 所以以下两行是等价的:

    x = np.multiply(some_array, 5)
    x = some_array * 5
    

    我们通常会使用运算符而不是函数,因为它们更方便键入,也更容易阅读,不过这只是个人偏好。

    再看一个使用标量和 ndarrays 进行运算的例子。假设你有一个矩阵 m 并且你想重用它,但首先你需要将其所有值设为零。这很简单,只需给它乘以零,并将结果分配回矩阵就行了,如下所示:

    m *= 0
    # 现在 m 中的每个元素都是 0,无论它有多少维度
    

    元素级矩阵运算
    与标量和矩阵一起使用的相同函数和运算符也适用于其他维度。你只需要确保执行运算的项目具有兼容的形状。

    假设你想得到矩阵的平方值。方法是 x = m * m(或者如果你要将值分配回 m,则是 m *= m

    这是可以运作的,因为它是两个形状相同的矩阵之间的元素乘法。(在这个例子中,它们的形状相同,是因为它们实际上是相同的对象。)

    看一个示例:

    a = np.array([[1,3],[5,7]])
    a
    # 显示以下结果:
    # array([[1, 3],
    #        [5, 7]])
    
    b = np.array([[2,4],[6,8]])
    b
    # 显示以下结果:
    # array([[2, 4],
    #        [6, 8]])
    
    a + b
    # 显示以下结果:
    #      array([[ 3,  7],
    #             [11, 15]])
    

    如果您尝试使用不兼容的形状,就像视频中的另一个示例一样,你会收到一个错误:

    a = np.array([[1,3],[5,7]])
    a
    # 显示以下结果:
    # array([[1, 3],
    #        [5, 7]])
    c = np.array([[2,3,6],[4,5,9],[1,8,7]])
    c
    # 显示以下结果:
    # array([[2, 3, 6],
    #        [4, 5, 9],
    #        [1, 8, 7]])
    
    a.shape
    # 显示以下结果:
    #  (2, 2)
    
    c.shape
    # 显示以下结果:
    #  (3, 3)
    
    a + c
    # 显示以下结果:
    # ValueError: operands could not be broadcast together with shapes (2,2) (3,3)
    

    你会在后面的介绍中详细了解“不能一起广播”的意义,现在只需注意这两种形状是不同的,因此我们无法执行元素级运算。


    Numpy矩阵乘法

    元素级乘法
    你已看过了一些元素级乘法。你可以使用multiply函数或 *运算符来实现。回顾一下,它看起来是这样的,实现元素对应位置相乘:

    m = np.array([[1,2,3],[4,5,6]])
    m
    # 显示以下结果:
    # array([[1, 2, 3],
    # [4, 5, 6]])
    
    n = m * 0.25
    n
    # 显示以下结果:
    # array([[ 0.25, 0.5 , 0.75],
    # [ 1. , 1.25, 1.5 ]])
    
    m * n
    # 显示以下结果:
    # array([[ 0.25, 1. , 2.25],
    # [ 4. , 6.25, 9. ]])
    
    np.multiply(m, n) 
    # 相当于 m * n
    # 显示以下结果:
    # array([[ 0.25, 1. , 2.25],
    # [ 4. , 6.25, 9. ]])
    

    矩阵乘积
    要获得矩阵乘积,你可以使用 NumPy 的 matmul 函数。
    如果你有兼容的形状,那就像这样简单:

    a = np.array([[1,2,3,4],[5,6,7,8]])
    a
    # 显示以下结果:
    # array([[1, 2, 3, 4],
    #        [5, 6, 7, 8]])
    
    a.shape
    # 显示以下结果:
    # (2, 4)
    
    b = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
    b
    # 显示以下结果:
    # array([[ 1, 2, 3],
    # [ 4, 5, 6],
    # [ 7, 8, 9],
    # [10, 11, 12]])
    
    b.shape
    # 显示以下结果:
    # (4, 3)
    
    c = np.matmul(a, b)
    c
    # 显示以下结果:
    # array([[ 70, 80, 90],
    #        [158, 184, 210]])
    
    c.shape
    # 显示以下结果:
    # (2, 3)
    

    如果您的矩阵具有不兼容的形状,则会出现以下错误:

    np.matmul(b, a)
    # 显示以下错误:
    # ValueError: shapes (4,3) and (2,4) not aligned: 3 (dim 1) != 2 (dim 0)
    

    NumPy 的 dot 函数
    有时候,在你以为用 matmul函数的地方,可能会看到 NumPy 的 dot 函数。事实证明,如果矩阵是二维的,那么 dot和 matmul函数的结果是相同的。
    所以这两个结果是等价的:

    a = np.array([[1,2],[3,4]])
    a
    # 显示以下结果:
    # array([[1, 2],
    #        [3, 4]])
    
    np.dot(a,a)
    # 显示以下结果:
    # array([[ 7, 10],
    #        [15, 22]])
    
    a.dot(a) 
    # 你可以直接对 `ndarray` 调用 `dot` 
    # 显示以下结果:
    # array([[ 7, 10],
    #        [15, 22]])
    
    np.matmul(a,a)
    # array([[ 7, 10],
    #        [15, 22]])
    

    虽然这些函数对于二维数据返回相同的结果,但在用于其他数据形状时,你应该谨慎选择要使用哪种。你可以在 matmuldot文档中详细了解它们的差异,并找到其他 NumPy 函数的链接。


    矩阵转置

    转置
    在 NumPy 中获得矩阵的转置非常容易。只需访问其“T”属性即可。有一个 transpose() 函数也可以返回同样的结果,但是你很少看到它在任何地方使用,因为输入 T 的方法要简单得多。 :)

    例如:

    m = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
    m
    # 显示以下结果:
    # array([[ 1,  2,  3,  4],
    #        [ 5,  6,  7,  8],
    #        [ 9, 10, 11, 12]])
    
    m.T
    # 显示以下结果:
    # array([[ 1,  5,  9],
    #        [ 2,  6, 10],
    #        [ 3,  7, 11],
    #        [ 4,  8, 12]])
    

    NumPy 在进行转置时不实际移动内存中的任何数据——只是改变对原始矩阵的索引方式,所以是非常高效的。

    修改转置矩阵会改变原始矩阵的值
    但是,这也意味着你也要特别注意修改对象的方式,因为它们共享相同的数据。例如,对于上面同一个矩阵 m,我们来创建一个新的变量 m_t来存储 m的转置。然后看看如果我们修改 m_t中的值,会发生什么:

    m_t = m.T
    m_t[3][1] = 200
    m_t
    # 显示以下结果:
    # array([[ 1,   5, 9],
    #        [ 2,   6, 10],
    #        [ 3,   7, 11],
    #        [ 4, 200, 12]])
    
    m
    # 显示以下结果:
    # array([[ 1,  2,  3,   4],
    #        [ 5,  6,  7, 200],
    #        [ 9, 10, 11,  12]])
    

    注意它是如何修改转置和原始矩阵的!这是因为它们共享相同的数据副本。所以记住,将转置视为矩阵的不同视图,而不是完全不同的矩阵。

    实际用例
    假设你有以下两个矩阵,称为“inputs”和“weights”,

    inputs = np.array([[-0.27,  0.45,  0.64, 0.31]])
    inputs
    # 显示以下结果:
    # array([[-0.27,  0.45,  0.64,  0.31]])
    
    inputs.shape
    # 显示以下结果:
    # (1, 4)
    
    weights = np.array([[0.02, 0.001, -0.03, 0.036], \
        [0.04, -0.003, 0.025, 0.009], [0.012, -0.045, 0.28, -0.067]])
    
    weights
    # 显示以下结果:
    # array([[ 0.02 ,  0.001, -0.03 ,  0.036],
    #        [ 0.04 , -0.003,  0.025,  0.009],
    #        [ 0.012, -0.045,  0.28 , -0.067]])
    
    weights.shape
    # displays the following result:
    # (3, 4)
    

    我在这里不会讲解它们的用途,因为你稍后都会学到,但是最终你会想要获得这两个矩阵的矩阵乘积。

    如果你像现在这样去尝试,会获得一个错误:

    np.matmul(inputs, weights)
    # 显示以下错误:
    # ValueError: shapes (1,4) and (3,4) not aligned: 4 (dim 1) != 3 (dim 0)
    

    如果你上了矩阵乘法课,那应该见过这个错误。它报告说形状不兼容,因为左边矩阵的列数 4 不等于右边矩阵的行数 3。

    所以有问题,但是注意,如果你获取 weights 矩阵的转置,它会:

    np.matmul(inputs, weights.T)
    # 显示以下结果:
    # array([[-0.01299,  0.00664,  0.13494]])
    如果你获取 inputs 的转置,并调换它们的顺序也可以,就像我们在视频中展示的那样:
    
    np.matmul(weights, inputs.T)
    # 显示以下结果:
    # array([[-0.01299],# 
    #        [ 0.00664],
    #        [ 0.13494]])
    

    这两个答案是彼此的转置,所以你使用的乘法只取决于你想要的输出的形状。

    相关文章

      网友评论

        本文标题:Numpy矩阵基础

        本文链接:https://www.haomeiwen.com/subject/gjdurxtx.html