udacity深度学习--2. 深度学习简介--LESSON3 矩阵数学和NumPy复习

NumPy 简介

导入 NumPy

import numpy as np

标量

NumPy 中的标量 比 Python 中的标量类型更多。不像 Python 只有基本类型 int、float 等,NumPy 可以让你指定有符号和无符号的类型以及不同的大小。因此,除了 Python 的 int,你可以使用 uint8、int8、uint16、int16 等类型。

这些类型很重要,因为你所创建的每个对象(向量、矩阵、张量)最终都会存储标量。而且,当你创建 NumPy 数组时,可以指定类型 - 但是数组中的每一项必须具有相同的类型。在这方面,NumPy 数组更像是 C 数组,而非 Python 列表。

如果要创建一个包含标量的 NumPy 数组,方法是将值传递给 NumPyarray 函数,如下所示:

s = np.array(5)

你可以通过检查数组的 shape 属性来查看数组的形状。你可以执行代码s.shape

s = np.array(5)
print(s.shape)

它会打印出结果,即一对空括号 ()。这表示它的维度为零。

即使标量位于数组中,你仍然可以像正常标量一样使用它们。你可以键入:

x = s + 3

x 现在将等于 8。

向量

要创建一个向量,你可以将 Python 列表传递给 array 函数,像这样:

v = np.array([1,2,3])

如果你检查向量的 shape 属性,它将返回表示向量的一维长度的单个数字。在上面的示例中,v.shape 会返回 (3,)

现在有了数字,你可以看到 shape 是一个元组,其中包含每个 ndarray 的维度的大小。对于标量,它只是一个空的元组,但是向量有一个维度,所以元组包含一个数字和一个逗号。(Python 不能将 (3) 理解为具有一个项的元组,所以它需要逗号)。

你可以使用索引访问向量中的元素,如下所示:

x = v[1]

现在 x 等于 2。

NumPy 还支持高级索引技术。例如,要访问第二个元素及其后面的项,你可以这样写:

v[1:]

然后它会返回数组 [2, 3]

理解矩阵维度的正确姿势

我们在线性代数中最常见到的矩阵入手,就是类似这样的了:向量
在这里插入图片描述
是一个3×1的矩阵,多维矩阵按括号的层级,从外向内,依次是第1,2,3,...维
所以在numpy中的表达应该是

v = np.array([[1],
              [2],
              [3]])

矩阵

使用 NumPy 的 array 函数创建矩阵,跟创建向量一样。但是,这次你不只是传入一个列表,而是提供列表的列表,其中每个列表代表一行。所以要创建一个包含数字 1 到 9 的 3x3 矩阵,你可以这样做:

m = np.array([[1,2,3],
			[4,5,6],
			[7,8,9]])
print(m.shape)

查它的 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]]]])

我们可以把它这样分拆,看得就很清楚了:

v = np.array([ # 3个子项
               [ # 3个子项
                   [ # 2个子项
                       [  # 1个子项
                           1],
                       [2]
                   ],
                   [
                       [3],
                       [4]
                   ],
                   [
                       [5],
                       [6]
                   ]
               ],
               [
                   [[7],[8]],
                   [[9],[10]],
                   [[11],[12]]
               ],
               [
                   [[13],[14]],
                   [[15],[16]],
                   [[17],[17]]
               ]
            ]
)

多维矩阵按括号的层级,从外向内,依次是第1,2,3,...维

所以 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 的维度。查阅此文档 了解更多示例。

上面的转换对应以下的实际表示:

v = np.array([1, 2, 3, 4])
print(v.shape)
v = np.array([[1, 2, 3, 4]])
print(v.shape)
v = np.array([[1],
              [2],
              [3], 
              [4]])
print(v.shape)

输出结果分别是:

(4,)
(1, 4)
(4, 1)

可以看到,几维是由几层[决定的

关于更改 NumPy 数组的形状还有一点:如果你看到经验丰富的 NumPy 使用者的代码,经常会看到他们使用一种特殊的切片语法,而不是调用 reshape。使用该语法,前面的两个示例会是这样的:

x = v[None, :]

或者

x = v[:, None]

这些代码创建一个切片,查看 v 的所有项目,要求 NumPy 为相关轴添加大小为 1 的新维度。

元素级运算

Python 中的方式

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

values = [1,2,3,4,5]
for i in range(len(values)):
    values[i] += 5

# 现在的 values 为 [6,7,8,9,10]

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

注意: 如果你不习惯使用像 += 这样的运算符,它的意思是"将两者相加,然后将结果保存在左边的项中"。这是 values[i] = values[i] + 5 的简便写法。你在这些示例中看到的代码会尽可能地使用此类运算符。

NumPy 中的方式

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

values = [1,2,3,4,5]
values = np.array(values) + 5
print(values)

现在 values 是包含 [6,7,8,9,10] 的一个 ndarray

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

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.  ]])

关于矩阵乘法的重要提醒

  • 左侧矩阵的列数必须等于右侧矩阵的行数。
  • 答案矩阵始终与左侧矩阵有相同的行数,与右侧矩阵有相同的列数。
  • 顺序很重要:乘法 A•B 不等于乘法 B•A 。
  • 左侧矩阵中的数据应排列为行,而右侧矩阵中的数据应排列为列。

矩阵乘法

要获得矩阵乘积,你可以使用 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 函数。事实证明,如果矩阵是二维的,那么 dotmatmul 函数的结果是相同的。

所以这两个结果是等价的:

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)  # you can call你可以直接对 `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]])

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

实际用例

我不想过多讲解关于神经网络的细节,因为你还没有学到它们,但是有一个地方你差不多肯定会用到转置,或者至少考虑使用转置。

假设你有以下两个矩阵,称为 inputsweights

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]])

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值