Bootstrap

【机器学习】Python、NumPy和向量化的基础知识以及三者结合的用法和示例

引言

在机器学习中,NumPy是一个非常重要的库,特别是在进行向量化操作时。向量化是一种优化技术,可以显著提高数组计算的效率,特别是在处理大型数据集时。NumPy提供了丰富的数组运算功能,使得向量化操作变得简单高效

一、NumPy和向量化操作的核心概念

1.1. NumPy的基本功能

NumPy扩展了Python的基本功能,增加了更多的数值类型、向量、矩阵以及许多矩阵函数。NumPy与Python结合使用非常顺畅,Python的算术运算符可以用于NumPy数据类型,许多NumPy函数也接受Python数据类型。

1.2 向量的概念

在NumPy中,向量是数字的有序数组。在数学表示中,向量通常用小写粗体字母表示,例如 (\mathbf{x})。向量的元素都是相同类型的,例如,一个向量不能同时包含字符和数字。在计算机科学中,向量的索引通常从0开始。

1.3 NumPy数组

NumPy的基本数据结构是可索引的n维数组,包含相同类型的元素(dtype)。在NumPy中,我们可以使用多维数组进行数学运算,这些运算通常会对数组的每个成员执行相同的数学操作。

1.4 向量化操作的效率

向量化操作可以显著提高计算效率。例如,使用NumPy进行数组操作比使用Python原生的for循环或列表解析式更加高效。这是因为NumPy在底层使用了优化的C语言和Fortran库,使得数值计算比纯Python快得多。

1.5 常见的向量化操作

使用NumPy,可以轻松地执行矩阵乘法、求和、最大值、最小值等操作。此外,NumPy的广播机制允许在不同形状的数组上执行元素级操作,而矢量化则使用SIMD指令在CPU上并行化处理数据。

二、Python、NumPy和向量化结合

本实验简要介绍了本课程中使用的科学计算功能,特别是NumPy科学计算包及其与Python的结合使用

# 导入numpy库,通常用np表示numpy
import numpy as np    # 使用np作为numpy的非官方标准
import time

2.1 目标

  • 学习NumPy和Python的功能

2.2 有用参考资料

2.3 Python和NumPy

Python是我们将在本课程中使用的编程语言。它有一套数字数据类型和算术运算。NumPy是一个库,扩展了Python的基本功能,增加了更丰富的数据集,包括更多数字类型、向量和矩阵以及许多矩阵函数。NumPy和python协同工作非常顺畅。Python的算术运算符适用于NumPy数据类型,许多NumPy函数也接受python数据类型

三、向量

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不是粗体

3.2 NumPy数组

NumPy的基本数据结构是一个可索引的、n维数组,包含相同类型的元素(dtype)。在这里,“维度”的重载意味着数组的索引数量。一维或1-D数组有一个索引。将向量表示为NumPy 1-D数组

  • 1-D数组,形状为(n,): n个元素,索引从[0]到[n-1]

3.3 向量创建

NumPy的数据创建例程通常具有一个参数,用于指定对象形状。这可以是要创建1-D结果的单个值,也可以是指定结果形状的元组(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}")

输出结果:
在这里插入图片描述
一些数据创建例程不接受形状元组:

# 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}")

输出结果:
在这里插入图片描述

值也可以手动指定:

# 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}")

输出结果:
在这里插入图片描述

这些操作都创建了一个具有四个元素的一维向量a。a.shape返回的是维度信息。在这里我们看到a.shape = (4,),表示这是一个具有4个元素的一维数组。

3.4 向量操作

让我们探索一些使用向量的操作

3.4.1 索引

向量的元素可以通过索引和切片进行访问。NumPy 提供了一套非常完整的索引和切片功能。我们将探索课程所需的基本功能。参考切片和索引获取更多细节。 索引意味着通过数组中的位置来引用一个元素。 切片意味着根据它们的索引从数组中获取一个子集的元素。 NumPy 从零开始索引,因此向量 𝐚 的第三个元素是 a[2]

# 向量 1-D 索引操作
a = np.arange(10) print(a)

# 访问一个元素
print(f"a[2].shape: {a[2].shape} a[2] = {a[2]}, 访问一个元素返回一个标量")

# 访问最后一个元素,负索引从末尾开始计数
print(f"a[-1] = {a[-1]}")

# 索引必须在向量范围内,否则会产生错误
try: c = a[10] 
except Exception as e: 
	print(“你将看到的错误信息是:”) 
	print(e)

输出结果:
在这里插入图片描述

3.4.2 切片

切片通过使用三个值(开始:停止:步长)创建一个索引数组。一个子集的值也是有效的。使用的示例:

# 向量 1-D 切片操作
a = np.arange(10) print(f"a = {a}")

# 访问 5 个连续元素(开始:停止:步长)
c = a[2:7:1]; print("a[2:7:1] = ", c)

# 访问 3 个元素,每个元素之间相隔两个
c = a[2:7:2]; print("a[2:7:2] = ", c)

# 访问所有索引 3 以上的元素
c = a[3:]; print("a[3:] = ", c)

# 访问所有索引小于 3 的元素
c = a[:3]; print("a[:3] = ", c)

# 访问所有元素
c = a[:]; print("a[:] = ", c)

输出结果:
在这里插入图片描述

3.4.3 单向量操作

有许多有用的操作涉及对单个向量的操作

a = np.array([1,2,3,4]) print(f"a : {a}")

# 否定向量 a 的元素
b = -a print(f"b = -a : {b}")

# 计算 a 的所有元素之和,返回一个标量
b = np.sum(a) print(f"b = np.sum(a) : {b}")

b = np.mean(a) print(f"b = np.mean(a): {b}")

b = a2 print(f"b = a2 : {b}")

输出结果:
在这里插入图片描述

3.4.4 向量元素级操作

大多数 NumPy 的算术、逻辑和比较运算符也适用于向量。这些运算符以元素为单位进行操作。例如:
𝐚 + 𝐛 = ∑𝑖=0𝑛−1 𝑎𝑖 + 𝑏𝑖

a = np.array([ 1, 2, 3, 4]) b = np.array([-1,-2, 3, 4]) print(f"Binary operators work element wise: {a + b}")

输出结果:
在这里插入图片描述

当然,为了正确工作,这两个向量必须具有相同的尺寸:

# 尝试一个不匹配的向量操作
c = np.array([1, 2]) try: d = a + c except Exception as e: print(“你将看到的错误信息是:”) print(e)

输出结果:
在这里插入图片描述

3.4.5 标量向量操作

向量可以被标量值“缩放”。标量值只是一个数字。标量乘以向量的所有元素

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

乘以向量 a 一个标量
b = 5 * a print(f"b = 5 * a : {b

输出结果:
在这里插入图片描述

3.4.6 向量向量点积

点积是线性代数和NumPy的核心操作。这个操作在本课程中广泛使用,因此您需要很好地理解它。点积如下图所示:
在这里插入图片描述

点积是通过将两个向量中的元素逐个相乘然后求和来实现的。向量点积要求两个向量的维度必须相同。
让我们实现自己的点积版本:
使用for循环,实现一个函数,返回两个向量的点积。函数将根据给定的输入 𝑎 和 𝑏:
𝑥 = ∑𝑖=0𝑛−1 𝑎𝑖 𝑏𝑖

假设 𝑎 和 𝑏 具有相同的形状

def my_dot(a, b): 
    """
   计算两个向量的点积
    参数:
    a (ndarray (n,)): 输入向量
    b (ndarray (n,)): 与a维度相同的输入向量
    返回:
    x (标量):
    """
    x = 0
    for i in range(a.shape[0]):
        x = x + a[i] * b[i]
    return x
# 测试 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)}")
注意,点积预期返回一个标量值。
让我们使用 np.dot 尝试相同的操作。

输出结果:
在这里插入图片描述

# 测试 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} ")

输出结果:
在这里插入图片描述

在上面的示例中,注意到1-D的结果与我们的实现相匹配

3.4.7 速度的重要性:向量与循环

我们使用NumPy库是因为它提高了速度和内存效率。让我们展示一下:

np.random.seed(1)
a = np.random.rand(10000000)  # 非常大的数组
b = np.random.rand(10000000)
tic = time.time()  # 捕捉开始时间
c = np.dot(a, b)
toc = time.time()  # 捕捉结束时间
print(f"np.dot(a, b) =  {c:.4f}")
print(f"向量化版本持续时间: {1000*(toc-tic):.4f} 毫秒")
tic = time.time()  # 捕捉开始时间
c = my_dot(a,b)
toc = time.time()  # 捕捉结束时间
print(f"my_dot(a, b) =  {c:.4f}")
print(f"循环版本持续时间: {1000*(toc-tic):.4f} 毫秒")
del(a);del(b)  # 从内存中删除这些大数组

输出结果:
在这里插入图片描述

向量化在本例中提供了显著的速度提升。这是因为NumPy更好地利用了底层硬件中可用的数据并行性。GPU和现代CPU实现单指令,多数据(SIMD)流水线,允许并行发出多个操作。这对于机器学习尤为重要,因为数据集往往非常大。

3.4.8 向量操作

向量操作将频繁出现。原因如下:

  • 从现在开始,我们的示例将存储在一个数组X_train中,其维度为(m,n)。这将根据上下文进行更详细的解释,但重要的是要注意,这是一个2维数组或矩阵(请参阅下一节关于矩阵的内容)。
  • w将是一个1维向量,其形状为(n,)。
  • 我们将通过遍历示例并逐个提取示例来执行操作,通过索引X。例如:X[i]。
  • X[i]返回一个形状为(n,)的值,这是一个1维向量。因此,涉及X[i]的操作通常是向量对向量的操作。

这是一个相当长的解释,但在进行向量操作时,对操作数的形状进行对齐和理解是非常重要的

# 展示常见示例
X = np.array([[1],[2],[3],[4]])
w = np.array([2])
c = np.dot(X[1], w)
print(f"X[1]的形状是{X[1].shape}")
print(f"w的形状是{w.shape}")
print(f"c的形状是{c.shape}")

输出结果:
在这里插入图片描述

在这里插入图片描述

以下是您提供的内容的中文翻译:

四、矩阵

4.1 概念

矩阵是二维数组。矩阵中的元素都是同一类型。在表示法中,矩阵用大写粗体字母表示,如 X \mathbf{X} X。在本课程和其他实验中,m通常表示行数,n表示列数。矩阵的元素可以通过二维索引来引用。在数学环境中,索引中的数字通常从1到n。在计算机科学和这些实验中,索引将从0到n-1。
在这里插入图片描述

4.2 NumPy数组

NumPy的基本数据结构是一个可索引的、n维数组,包含相同类型的元素(dtype)。这些在之前的描述中已经提及。矩阵具有二维(2-D)索引 [m,n]。
2-D矩阵用于存储训练数据。训练数据是m个示例和n个特征,形成一个(m,n)数组。课程1通常不直接对矩阵进行操作,而是通常将示例提取为向量并进行操作。在这里,将回顾:

  • 数据创建
  • 切片和索引

4.3 矩阵创建

用于创建1-D向量的相同函数也可以创建2-D或n-D数组。以下是一些示例:
在下面的示例中,提供了形状元组以实现2-D结果。注意NumPy如何使用方括号来表示每个维度。进一步注意,NumPy在打印时会每行打印一行。

a = np.zeros((1, 5))                                       
print(f"a形状 = {a.shape}, a = {a}")                     
a = np.zeros((2, 1))                                                                   
print(f"a形状 = {a.shape}, a = {a}") 
a = np.random.random_sample((1, 1))  
print(f"a形状 = {a.shape}, a = {a}") 

输出结果:
在这里插入图片描述

用户还可以手动指定数据。维度通过添加额外的方括号来指定,格式与打印格式匹配。

# NumPy例程,它们分配内存并填充用户指定的值
a = np.array([[5], [4], [3]]);   print(f" a形状 = {a.shape}, np.array: a = {a}")
a = np.array([[5],   # One can also
              [4],   # separate values
              [3]]); #into separate rows
print(f" a形状 = {a.shape}, np.array: a = {a}")

输出结果:
在这里插入图片描述

4.4 矩阵操作

让我们探索一些使用矩阵的操作

4.4.1 索引

矩阵包含第二个索引。这两个索引描述 [行, 列]。访问可以返回一个元素或一行/一列。请看下面的示例:

a = np.arange(6).reshape(-1, 2)   #reshape是创建矩阵的便捷方式
print(f"a.shape: {a.shape}, \na= {a}")
#访问一个元素
print(f"\na[2,0].shape:   {a[2, 0].shape}, a[2,0] = {a[2, 0]},     type(a[2,0]) = {type(a[2, 0])} 访问一个元素返回一个标量")
#访问一行
print(f"a[2].shape:   {a[2].shape}, a[2]   = {a[2]}, type(a[2])   = {type(a[2])}")

输出结果:
在这里插入图片描述

注意最后一个示例,仅指定行来访问矩阵将返回一个1-D向量


重塑
之前的示例使用了重塑(reshape)来更改数组的形状。

a = np.arange(6).reshape(-1, 2) 

这行代码首先创建了一个包含六个元素的1-D向量。然后,使用重塑命令将这个向量重塑为2-D数组。这可以写作:

a = np.arange(6).reshape(3, 2) 

这样就得到了一个3行2列的相同数组。-1参数告诉程序根据数组的尺寸和列数计算行数。


4.4.2 切片

切片通过使用三个值(开始:停止:步长)创建一个索引数组。一个子集的值也是有效的。其使用通过示例解释:

# 向量 2-D 切片操作
a = np.arange(20).reshape(-1, 10)
print(f"a = \n{a}")
# 访问 5 个连续元素 (开始:停止:步长)
print("a[0, 2:7:1] = ", a[0, 2:7:1], ",  a[0, 2:
# 访问所有元素
print("a[:,:] = \n", a[:,:], ",  a[:,:].shape =", a[:,:].shape)
# 访问一行中的所有元素(非常常见的用法)
print("a[1,:] = ", a[1,:], ",  a[1,:].shape =", a[1,:].shape, "一个1-D数组")
# 同样的操作
print("a[1]   = ", a[1],   ",  a[1].shape   =", a[1].shape, "一个1-D数组")

输出结果:
在这里插入图片描述

五、总结

总的来说,NumPy的向量化操作为机器学习中的数据处理和计算提供了强大的工具,使得代码更加简洁、运行效率更高。

;