文章目录
- 1.1 目标
- 1.2 有用的参考资料
- 2 Python 和 NumPy
- 3 向量
- 3.1 概要
- 3.2 NumPy数组
- **3.3 向量构造**
- 3.4 向量的操作
- 3.4.1 索引
- 3.4.2 切片
- **3.4.3 单向量操作**
- 3.4.4 向量与向量的逐元素操作
- 3.4.5 标量与向量的运算
- 3.4.6 向量的点积运算
- 3.4.7 速度需求:向量化与for循环
- 3.4.8 在第一课中的向量操作
- 矩阵
- 4.1 摘要
- 4.2 NumPy 数组
- 4.3 矩阵创建
- 4.4 矩阵操作
- 4.4.2 切片
- 小结
这门课程涉及到一些 科学计算的基础知识,特别是使用Python中的NumPy科学计算包。 NumPy是Python中用于科学计算的一个重要工具,它提供了一个强大的多维数组对象和各种用于处理这些数组的函数。通过NumPy,我们可以高效地进行 数据操作、数学计算、线性代数等任务,这对于科学计算和数据分析非常有用。
import numpy as np
import time
1.1 目标
在这个实验中,你将:
- 回顾在课程1中使用的NumPy和Python的特性
1.2 有用的参考资料
- NumPy文档,包括基本介绍:NumPy.org
- 一个具有挑战性的特性主题:NumPy广播
2 Python 和 NumPy
Python是我们在这门课程中将使用的编程语言。它具有一组数字数据类型和算术操作。NumPy是一个库,它扩展了Python的基本功能,以添加更丰富的数据集,包括更多的数值类型、向量、矩阵和许多矩阵函数。NumPy和Python可以相当无缝地配合使用。Python的算术运算符可以用于NumPy数据类型,许多NumPy函数也将接受Python数据类型。
3 向量
3.1 概要
向量,在这门课程中的使用方式,是有序的数字数组。在表示法上,向量用粗体小写字母表示,如 x \mathbf{x} x。向量的元素都是相同类型的。例如,一个向量不会同时包含字符和数字。数组中元素的数量通常被称为维度,虽然数学家可能更喜欢秩 这个术语。所示的向量具有维度 n n n。向量的元素可以通过索引来引用。在数学环境中,索引通常从1到n。在计算机科学和这些实验中,索引通常从0到n-1。在表示法中,当单独引用向量的元素时,将使用下标表示索引,例如,向量 x \mathbf{x} x 的第 0 0 0 个元素用 x 0 x_0 x0 表示。注意,这种情况下的 x x x 不是粗体。
3.2 NumPy数组
NumPy 的基本数据结构是一个可索引的、n 维的数组,其中包含相同类型(dtype
)的元素。您可能会立即注意到我们已经重载了术语“维度”。上面,它是向量中的元素数量,而这里,维度指的是数组的索引数量。一个一维或 1-D
数组有一个索引。在课程1中,我们将把向量表示为NumPy 1-D数组。
- 1-D数组,形状为 (n,):n个元素,索引范围从[0]到[n-1]
3.3 向量构造
在NumPy中,数据创建函数通常会有一个第一个参数,它是对象的形状。这可以是一个单一的值,用于表示一维结果,也可以是一个元组 (n,m,…),指定结果的形状。下面是使用这些函数创建向量的示例。
# NumPy routines which allocate memory and fill arrays with value
a = np.zeros(4); print(f"np.zeros(4) : a = {a}, a shape = {a.shape}, a data type = {a.dtype}")
a = np.zeros((4,)); print(f"np.zeros(4,) : a = {a}, a shape = {a.shape}, a data type = {a.dtype}")
a = np.random.random_sample(4); print(f"np.random.random_sample(4): a = {a}, a shape = {a.shape}, a data type = {a.dtype}")# print
np.zeros(4) : a = [0. 0. 0. 0.], a shape = (4,), a data type = float64
np.zeros(4,) : a = [0. 0. 0. 0.], a shape = (4,), a data type = float64
np.random.random_sample(4): a = [0.20091455 0.8554035 0.89419993 0.85645191], a shape = (4,), a data type = float64
一些数据创建例程不接受形状元组:
# NumPy routines which allocate memory and fill arrays with value but do not accept shape as input argument
a = np.arange(4.); print(f"np.arange(4.): a = {a}, a shape = {a.shape}, a data type = {a.dtype}")
a = np.random.rand(4); print(f"np.random.rand(4): a = {a}, a shape = {a.shape}, a data type = {a.dtype}")# print
np.arange(4.): a = [0. 1. 2. 3.], a shape = (4,), a data type = float64
np.random.rand(4): a = [0.06285392 0.46714606 0.06818969 0.45556927], a shape = (4,), a data type = float64
值也可以手动指定。
# NumPy routines which allocate memory and fill with user specified values
a = np.array([5,4,3,2]); print(f"np.array([5,4,3,2]): a = {a}, a shape = {a.shape}, a data type = {a.dtype}")
a = np.array([5.,4,3,2]); print(f"np.array([5.,4,3,2]): a = {a}, a shape = {a.shape}, a data type = {a.dtype}")# print
np.array([5,4,3,2]): a = [5 4 3 2], a shape = (4,), a data type = int32
np.array([5.,4,3,2]): a = [5. 4. 3. 2.], a shape = (4,), a data type = float64
这些操作都创建了一个具有四个元素的一维向量 a。a.shape
返回了其维度。在这里我们看到 a.shape = (4,)
,表示一个具有四个元素的一维数组。
3.4 向量的操作
让我们通过向量进行一些操作。
3.4.1 索引
可以通过索引和切片访问向量的元素。NumPy 提供了非常完整的索引和切片功能。我们在这里只探讨课程所需的基础知识。更多详细信息请参考 切片和索引。
索引 意味着通过在数组中的位置引用 一个元素。
切片 意味着根据其索引从数组中获取 子集 元素。
NumPy 从零开始索引,所以向量 a \mathbf{a} a 的第三个元素是 a[2]
。
#vector indexing operations on 1-D vectors
a = np.arange(10)
print(a)#access an element
print(f"a[2].shape: {a[2].shape} a[2] = {a[2]}, Accessing an element returns a scalar")# access the last element, negative indexes count from the end
print(f"a[-1] = {a[-1]}")#indexs must be within the range of the vector or they will produce and error
try:c = a[10]
except Exception as e:print("The error message you'll see is:")print(e)# print
[0 1 2 3 4 5 6 7 8 9]
a[2].shape: () a[2] = 2, Accessing an element returns a scalar
a[-1] = 9
The error message you'll see is:
index 10 is out of bounds for axis 0 with size 10
3.4.2 切片
切片使用一组三个值(开始:结束:步长)创建索引数组。也可以使用子集值。最好通过示例来解释其用法:
#vector slicing operations
a = np.arange(10)
print(f"a = {a}")#access 5 consecutive elements (start:stop:step)
c = a[2:7:1]; print("a[2:7:1] = ", c)# access 3 elements separated by two
c = a[2:7:2]; print("a[2:7:2] = ", c)# access all elements index 3 and above
c = a[3:]; print("a[3:] = ", c)# access all elements below index 3
c = a[:3]; print("a[:3] = ", c)# access all elements
c = a[:]; print("a[:] = ", c)# print
a = [0 1 2 3 4 5 6 7 8 9]
a[2:7:1] = [2 3 4 5 6]
a[2:7:2] = [2 4 6]
a[3:] = [3 4 5 6 7 8 9]
a[:3] = [0 1 2]
a[:] = [0 1 2 3 4 5 6 7 8 9]
3.4.3 单向量操作
有许多有用的操作涉及对单个向量进行操作。
a = np.array([1,2,3,4])
print(f"a : {a}")
# negate elements of a
b = -a
print(f"b = -a : {b}")# sum all elements of a, returns a scalar
b = np.sum(a)
print(f"b = np.sum(a) : {b}")b = np.mean(a) # 计算数组 a 中所有元素的平均值
print(f"b = np.mean(a): {b}")b = a**2
print(f"b = a**2 : {b}")# print
a : [1 2 3 4]
b = -a : [-1 -2 -3 -4]
b = np.sum(a) : 10
b = np.mean(a): 2.5
b = a**2 : [ 1 4 9 16]
3.4.4 向量与向量的逐元素操作
大多数 NumPy 的算术、逻辑和比较操作也适用于向量。这些运算符以逐元素的方式工作。例如:
a + b = ∑ i = 0 n − 1 a i + b i \mathbf{a} + \mathbf{b} = \sum_{i=0}^{n-1} a_i + b_i a+b=∑i=0n−1ai+bi
a = np.array([ 1, 2, 3, 4])
b = np.array([-1,-2, 3, 4])
print(f"Binary operators work element wise: {a + b}")#print
Binary operators work element wise: [0 0 6 8]
当然,为了正确进行操作,这两个向量必须具有相同的大小。
3.4.5 标量与向量的运算
向量可以与标量进行运算。标量值就是一个单独的数值。标量将作用于向量的每个元素,相当于将向量中的每个元素乘以该标量。
a = np.array([1, 2, 3, 4])# multiply a by a scalar
b = 5 * a
print(f"b = 5 * a : {b}")# print
b = 5 * a : [ 5 10 15 20]
3.4.6 向量的点积运算
点积是线性代数和 NumPy 中的重要运算。这是本课程中广泛使用的一种操作,应该被充分理解。
点积运算将两个向量的值逐元素相乘,然后将结果求和。向量点积要求两个向量的维度必须相同。
下面让我们实现自己的点积函数,使用一个for
循环,该函数返回两个向量的点积。给定输入 a a a 和 b b b,函数返回: x = ∑ i = 0 n − 1 a i b i x = \sum_{i=0}^{n-1} a_i b_i x=∑i=0n−1aibi
假设 a
和 b
的形状相同。
以下是用 for 循环实现点积运算的函数:
def my_dot(a, b):x = 0for i in range(len(a)):x += a[i] * b[i]return x
这个函数遍历了向量 a
和 b
中的每个元素,将它们逐个相乘并累加起来,得到了点积的结果。
# test 1-D
a = np.array([1, 2, 3, 4])
b = np.array([-1, 4, 3, 2])
print(f"my_dot(a, b) = {my_dot(a, b)}")# print
my_dot(a, b) = 24
请注意,点积的结果应该是一个标量值。
让我们尝试使用 np.dot
来执行相同的操作。
# test 1-D
a = np.array([1, 2, 3, 4])
b = np.array([-1, 4, 3, 2])
c = np.dot(a, b)
print(f"NumPy 1-D np.dot(a, b) = {c}, np.dot(a, b).shape = {c.shape} ")
c = np.dot(b, a)
print(f"NumPy 1-D np.dot(b, a) = {c}, np.dot(a, b).shape = {c.shape} ")# print
NumPy 1-D np.dot(a, b) = 24, np.dot(a, b).shape = ()
NumPy 1-D np.dot(b, a) = 24, np.dot(a, b).shape = ()
np.dot(a, b).shape = ()
表示 np.dot(a, b)
的结果是一个标量,即它没有维度。
3.4.7 速度需求:向量化与for循环
我们选择使用NumPy库是因为它提高了速度和内存效率。让我们来进行演示:
np.random.seed(1)
a = np.random.rand(10000000) # very large arrays
b = np.random.rand(10000000)tic = time.time() # capture start time
c = np.dot(a, b) # numpy 的点积方法
toc = time.time() # capture end timeprint(f"np.dot(a, b) = {c:.4f}")
print(f"Vectorized version duration: {1000*(toc-tic):.4f} ms ")tic = time.time() # capture start time
c = my_dot(a,b) # for循环实现的点积
toc = time.time() # capture end timeprint(f"my_dot(a, b) = {c:.4f}")
print(f"loop version duration: {1000*(toc-tic):.4f} ms ")del(a);del(b) #remove these big arrays from memory# print
np.dot(a, b) = 2501072.5817
Vectorized version duration: 13.3424 ms
my_dot(a, b) = 2501072.5817
loop version duration: 2910.3911 ms
-
解释一下:
np.random.seed(1)
np.random.seed(1)
是用于设置随机数种子的函数调用。在NumPy中,随机数生成是基于伪随机数算法的,这意味着在每次运行时,如果不设置随机数种子,将会得到不同的随机数序列。通过设置随机数种子,可以确保在每次运行时得到相同的随机数序列,这在调试和复现实验结果时非常有用。在这种情况下,使用np.random.seed(1)
设置种子为1。当你设置种子为1时,随机数生成器就会以1作为起始点,生成一个确定的随机数序列。每次你运行相同的代码,都会得到相同的随机数序列,因为种子和算法都是确定的。
这在实验和开发中非常有用,因为它可以让你在相同的条件下复现实验结果,确保实验的可重复性。
-
解释一下:
a = np.random.rand(10000000)
这行代码使用了 NumPy 库中的
np.random.rand()
函数来生成包含 10,000,000 个随机数的数组a
。这些随机数是从区间 [0, 1) 中均匀分布生成的,也就是说,它们的取值范围是大于等于 0 且小于 1。在机器学习和数据分析中,生成大量的随机数通常用于模拟数据、初始化参数或进行蒙特卡罗模拟等操作。
-
解释一下:
tic = time.time()
这行代码用于记录当前时间,
tic
变量将存储此时的时间戳。在代码中,通常会使用time.time()
函数来获取当前的系统时间。
因此,在这个示例中,向量化大大提高了速度。这是因为 NumPy 更好地利用了底层硬件中可用的数据并行性。GPU 和现代 CPU 实现了单指令多数据(SIMD)流水线,允许多个操作并行执行。在机器学习中,数据集通常非常庞大,因此这一点至关重要。
3.4.8 在第一课中的向量操作
在第一课中,向量操作将经常出现。原因如下:
- 未来,我们的示例将存储在维度为 (m, n) 的数组
X_train
中。这将在上下文中更详细地解释,但在这里重要的是要注意它是一个二维数组或矩阵(参见下一节关于矩阵的内容)。 w
将是一个形状为 (n,) 的一维向量。- 我们将通过循环遍历示例来执行操作,通过索引
X
提取每个示例进行单独处理。例如:X[i]
X[i]
返回形状为 (n,) 的值,即一维向量。因此,涉及X[i]
的操作通常是:向量-向量操作。- 形状为 (n,):具有 n 个元素的一维向量
# show common Course 1 example
X = np.array([[1],[2],[3],[4]])
w = np.array([2])
c = np.dot(X[1], w)print(f"X[1] has shape {X[1].shape}")
print(f"w has shape {w.shape}")
print(f"c has shape {c.shape}")# print
X[1] has shape (1,) # X[1] 是一维的
w has shape (1,)
c has shape ()
-
解释一下:
X = np.array([[1],[2],[3],[4]])
这个语句创建了一个包含四个元素的二维数组(或矩阵),每个元素都是一个单独的列表。每个列表只包含一个元素。因此,这个数组的形状是 (4, 1),表示有四行一列的矩阵。
-
解释一下:
c has shape ()
由于
c
是一个标量(单个数字),因此它的形状是一个空元组()
矩阵
4.1 摘要
矩阵是二维数组。矩阵的元素都是相同类型的。在表示上,矩阵用大写、粗体字母表示,如 X \mathbf{X} X。在这个实验和其他实验中,通常用 m
表示行数,n
表示列数。矩阵的元素可以通过二维索引引用。在数学环境中,索引中的数字通常从 1 1 1 到 n n n。在计算机科学和这些实验中,索引将从 0 0 0 到 n − 1 n-1 n−1。
4.2 NumPy 数组
NumPy 的基本数据结构是一个可索引的、n 维的 数组,其中包含相同类型 (dtype
) 的元素。之前已经描述过了。矩阵具有二维 (2-D) 索引 [ m , n ] [m,n] [m,n]。
在课程 1 中,2-D 矩阵用于保存训练数据。训练数据是 m m m 个示例,由 n n n 个特征组成,形成一个 (m,n) 数组。课程 1 不直接在矩阵上执行操作,而是通常将一个示例提取为向量,然后对其进行操作。下面将回顾:
- 数据创建
- 切片和索引
4.3 矩阵创建
创建 1-D 向量的相同函数也可以创建 2-D 或 n-D 数组。以下是一些示例
在下面的示例中,提供了形状元组以获得 2-D 结果。请注意,NumPy 使用方括号来表示每个维度。进一步注意,当打印时,NumPy 会每行打印一行。
a = np.zeros((1, 5))
print(f"a shape = {a.shape}, a = {a}") a = np.zeros((2, 1))
print(f"a shape = {a.shape}, a = {a}") a = np.random.random_sample((1, 1))
print(f"a shape = {a.shape}, a = {a}") #print
a shape = (1, 5), a = [[0. 0. 0. 0. 0.]] # 具有 1 行 5 列的二维数组
a shape = (2, 1), a = [[0.][0.]] # 具有 2 行 1 列的二维数组
a shape = (1, 1), a = [[0.44236513]]
可以手动指定数据。维度是用额外的方括号来指定的,与上面打印格式中的格式相匹配。
# NumPy 分配内存并使用用户指定的值填充的例程
a = np.array([[5], [4], [3]]); print(f" a shape = {a.shape}, np.array: a = {a}")
a = np.array([[5], # 也可以将值[4], # 分开[3]]); # 放在不同的行上
print(f" a shape = {a.shape}, np.array: a = {a}")# print
a shape = (3, 1), np.array: a = [[5] # a 3 行 1 列[4][3]]a shape = (3, 1), np.array: a = [[5][4][3]]
4.4 矩阵操作
4.4.1 索引
矩阵包括第二个索引。这两个索引描述了[行,列]。访问可以返回一个元素或一行/一列。请看下面的例子:
#vector indexing operations on matrices
a = np.arange(6).reshape(-1, 2) #reshape is a convenient way to create matrices
print(f"a.shape: {a.shape}, \na= {a}")#access an element
print(f"\na[2,0].shape: {a[2, 0].shape}, a[2,0] = {a[2, 0]}, type(a[2,0]) = {type(a[2, 0])} Accessing an element returns a scalar\n")#access a row
print(f"a[2].shape: {a[2].shape}, a[2] = {a[2]}, type(a[2]) = {type(a[2])}")# print
a.shape: (3, 2), # 表示 a 被重新塑形为 3 行 2 列
a= [[0 1][2 3][4 5]]# 表示 a[2,0] 是一个标量,是一个数值
a[2,0].shape: (), a[2,0] = 4, type(a[2,0]) = <class 'numpy.int32'> Accessing an element returns a scalar# a[2] 是一个长度为 2 的一维数组
a[2].shape: (2,), a[2] = [4 5], type(a[2]) = <class 'numpy.ndarray'>
-
解释一下:
a = np.arange(6).reshape(-1, 2)
这行代码首先使用
np.arange(6)
创建一个包含0到5的一维数组,然后使用.reshape(-1, 2)
将其重新塑形为一个二维数组,其中-1
表示自动计算所需的行数,而2
表示每行包含两个元素。因此,a
将成为一个具有两行三列的二维数组。
值得注意的是最后一个示例。仅通过指定行来访问矩阵将返回一个 1-D 向量【一维向量,one-dimensional】。
重塑
前面的示例使用了 reshape 来形状化数组。
a = np.arange(6).reshape(-1, 2)
这行代码首先创建了一个包含六个元素的 1-D 向量。然后使用 reshape 命令将该向量重塑为一个 2-D 数组。这也可以写成:a = np.arange(6).reshape(3, 2)
从而得到相同的 3 行 2 列数组。
-1 参数告诉该例程根据数组的大小和列数来计算行数。
4.4.2 切片
切片使用一组三个值(start:stop:step
)创建一个索引数组。也可以使用一组子值。最好通过示例来解释其用法:
#vector 2-D slicing operations
a = np.arange(20).reshape(-1, 10) # 将 20 个元素排列成 x 行 10 列,其中 x 由计算得出
print(f"a = \n{a}")#access 5 consecutive elements (start:stop:step)
print("a[0, 2:7:1] = ", a[0, 2:7:1], ", a[0, 2:7:1].shape =", a[0, 2:7:1].shape, "a 1-D array")#access 5 consecutive elements (start:stop:step) in two rows
print("a[:, 2:7:1] = \n", a[:, 2:7:1], ", a[:, 2:7:1].shape =", a[:, 2:7:1].shape, "a 2-D array")# access all elements
print("a[:,:] = \n", a[:,:], ", a[:,:].shape =", a[:,:].shape)# access all elements in one row (very common usage)
print("a[1,:] = ", a[1,:], ", a[1,:].shape =", a[1,:].shape, "a 1-D array")
# same as
print("a[1] = ", a[1], ", a[1].shape =", a[1].shape, "a 1-D array")# print
a =
[[ 0 1 2 3 4 5 6 7 8 9][10 11 12 13 14 15 16 17 18 19]]# a[0, 2:7:1] 的 0 表示选择矩阵的第一行
a[0, 2:7:1] = [2 3 4 5 6] , a[0, 2:7:1].shape = (5,) a 1-D array # a[:, 2:7:1] 中,: 表示选择所有的行
a[:, 2:7:1] = [[ 2 3 4 5 6][12 13 14 15 16]] , a[:, 2:7:1].shape = (2, 5) a 2-D array# a[:, :] 表示选择所有的行和所有的列,即选取整个矩阵 a,逗号用于分隔行和列的索引
a[:,:] = [[ 0 1 2 3 4 5 6 7 8 9][10 11 12 13 14 15 16 17 18 19]] , a[:,:].shape = (2, 10)a[1,:] = [10 11 12 13 14 15 16 17 18 19] , a[1,:].shape = (10,) a 1-D arraya[1] = [10 11 12 13 14 15 16 17 18 19] , a[1].shape = (10,) a 1-D array
小结
恭喜!在这个实验中,你掌握了课程 1 所需的 Python 和 NumPy 的功能。