原文:
zh.annas-archive.org/md5/123a7612a4e578f6816d36f968cfec22
译者:飞龙
第一章:前言
Python 是一种功能强大、灵活且易于学习的编程语言。它是许多专业人士、爱好者和科学家的首选编程语言。Python 的强大之处来自其庞大的软件包生态系统和友好的社区,以及其与编译扩展模块无缝通信的能力。这意味着 Python 非常适合解决各种问题,特别是数学问题。
数学通常与计算和方程联系在一起,但实际上,这些只是更大主题的很小部分。在其核心,数学是关于解决问题、以及逻辑、结构化方法的学科。一旦你探索了方程、计算、导数和积分之外,你会发现一个庞大而优雅的世界。
本书是使用 Python 解决数学问题的介绍。它介绍了一些来自数学的基本概念,以及如何使用 Python 来处理这些概念,并提供了解决数学问题的模板,涵盖了数学中大量主题的各种数学问题。前几章侧重于核心技能,如使用 NumPy 数组、绘图、微积分和概率。这些主题在整个数学中非常重要,并作为本书其余部分的基础。在剩下的章节中,我们讨论了更多实际的问题,涵盖了数据分析和统计、网络、回归和预测、优化和博弈论等主题。我们希望本书为解决数学问题提供了基础,并为您进一步探索数学世界提供了工具。
本书的读者
读者需要具备基本的 Python 知识。我们不假设读者具有任何数学知识,尽管熟悉一些基本数学概念的读者将更好地理解我们讨论的技术的背景和细节。
本书涵盖的内容
第一章,基本软件包、函数和概念,介绍了本书其余部分需要的一些基本工具和概念,包括用于数学编程的主要 Python 软件包 NumPy 和 SciPy。
第二章,使用 Matplotlib 进行数学绘图,介绍了使用 Matplotlib 进行绘图的基础知识,这在解决几乎所有数学问题时都很有用。
第三章,微积分和微分方程,介绍了微积分的主题,如微分和积分,以及一些更高级的主题,如常微分方程和偏微分方程。
第四章,处理随机性和概率,介绍了随机性和概率的基础知识,以及如何使用 Python 探索这些概念。
第五章,处理树和网络,介绍了使用 NetworkX 软件包在 Python 中处理树和网络(图)的内容。
第六章,处理数据和统计,介绍了使用 Python 处理、操作和分析数据的各种技术。
第七章,回归和预测,描述了使用 Statsmodels 软件包和 scikit-learn 预测未来值的各种建模技术。
第八章,几何问题,演示了使用 Shapely 软件包在 Python 中处理几何对象的各种技术。
第九章,寻找最优解,介绍了使用数学方法找到问题的最佳解的优化和博弈论。
第十章,杂项主题,涵盖了使用 Python 解决数学问题时可能遇到的各种情况。
充分利用本书
本书中唯一的要求是使用最新版本的 Python,至少 Python 3.6,但更高版本更好。一些读者可能更喜欢使用 Python 的 Anaconda 发行版,该发行版包含本书中所需的许多软件包和工具。如果是这种情况,您应该使用conda
软件包管理器来安装这些软件包。Python 支持所有主要操作系统——Windows、macOS 和 Linux——以及许多平台。以下表格涵盖了在撰写本书时使用的主要库及其版本:
书中涵盖的软件/库 | 版本 | 章节 |
Python | 3.6 或更高版本 | 所有 |
NumPy | 1.18.3 | 所有 |
SciPy | 1.4.1 | 所有 |
Matplotlib | 3.2.1 | 所有 |
Pandas | 1.0.3 | 6 - 10 |
Bokeh | 2.1.0 | 6 |
Scikit-Learn | 0.22.1 | 7 |
Dask | 2.18.1 | 10 |
Apache Kafka | 2.5.0 | 10 |
如果您使用本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库(下一节中提供链接)访问代码。这样做将有助于避免与复制和粘贴代码相关的任何潜在错误。
一些读者可能更喜欢在 Jupyter 笔记本中而不是在简单的 Python 文件中逐步完成本书中的代码示例。本书中有一两个地方可能需要重复绘图命令。这些地方在说明中有标记。
下载示例代码文件
您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
- 登录或注册www.packt.com。
- 选择支持选项卡。
- 点击代码下载。
- 在搜索框中输入书名,然后按照屏幕上的说明操作。
下载文件后,请确保使用最新版本的以下软件解压或提取文件夹:
- WinRAR/7-Zip for Windows
- Zipeg/iZip/UnRarX for Mac
- 7-Zip/PeaZip for Linux
本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Applying-Math-with-Python
。如果代码有更新,将在现有的 GitHub 存储库上进行更新。
我们还有来自我们丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/
上找到。去看看吧!
代码示例
本书的代码示例视频可以在bit.ly/2ZQcwIM
上观看。
使用的约定
本书中使用了许多文本约定。
CodeInText
:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这里有一个例子:“decimal
包还提供了一个Context
对象,它允许对Decimal
对象的精度、显示和属性进行精细控制。”
代码块设置如下:
from decimal import getcontext ctx = getcontext() num = Decimal('1.1') num**4 # Decimal('1.4641') ctx.prec = 4 # set new precision num**4 # Decimal('1.464')
当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:
from numpy import linalg A = np.array([[3, -2, 1], [1, 1, -2], [-3, -2, 1]]) b = np.array([7, -4, 1])
任何命令行输入或输出都是这样写的:
python3.8 -m pip install numpy scipy
粗体:表示一个新术语、一个重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这里有一个例子:“从管理面板中选择系统信息。”
警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。
章节
在本书中,您会经常看到几个标题(准备工作,如何做,它是如何工作的,还有更多,和另请参阅)。
为了清晰地说明如何完成食谱,使用以下各节:
准备工作
本节告诉您在食谱中可以期待什么,并描述如何设置任何所需的软件或初步设置。
如何做…
本节包含按照食谱所需的步骤。
它是如何工作的…
本节通常包括对前一节发生的事情的详细解释。
还有更多…
本节包含有关食谱的额外信息,以使您对食谱更加了解。
另请参阅
本节提供了其他有用信息的链接,以帮助制作食谱。
第二章:基本包、函数和概念
在开始任何实际配方之前,我们将使用本章来介绍几个核心数学概念和结构及其 Python 表示。特别是,我们将研究基本数值类型、基本数学函数(三角函数、指数函数和对数)以及矩阵。由于矩阵与线性方程组的解之间的联系,矩阵在大多数计算应用中都是基本的。我们将在本章中探讨其中一些应用,但矩阵将在整本书中发挥重要作用。
我们将按照以下顺序涵盖以下主要主题:
- Python 数值类型
- 基本数学函数
- NumPy 数组
- 矩阵
技术要求
在本章和本书的整个过程中,我们将使用 Python 3.8 版本,这是写作时最新的 Python 版本。本书中的大部分代码将适用于 Python 3.6 及更高版本。我们将在不同的地方使用 Python 3.6 引入的功能,包括 f-strings。这意味着您可能需要更改任何终端命令中出现的python3.8
,以匹配您的 Python 版本。这可能是另一个版本的 Python,如python3.6
或python3.7
,或者更一般的命令,如python3
或python
。对于后者的命令,您需要使用以下命令检查 Python 的版本至少为 3.6:
python --version
Python 具有内置的数值类型和基本的数学函数,足以满足只涉及小计算的小型应用。NumPy 包提供了高性能的数组类型和相关例程(包括对数组进行高效操作的基本数学函数)。这个包将在本章和本书的其余部分中使用。我们还将在本章的后续配方中使用 SciPy 包。这两个包都可以使用您喜欢的包管理器(如pip
)进行安装:
python3.8 -m pip install numpy scipy
按照惯例,我们将这些包导入为更短的别名。我们使用以下import
语句将numpy
导入为np
,将scipy
导入为sp
:
import numpy as np import scipy as sp
这些约定在这些包的官方文档中使用,以及许多使用这些包的教程和其他材料中使用。
本章的代码可以在 GitHub 存储库的Chapter 01
文件夹中找到,网址为github.com/PacktPublishing/Applying-Math-with-Python/tree/master/Chapter%2001
。
查看以下视频以查看代码实际操作:bit.ly/3g3eBXv
。
Python 数值类型
Python 提供了基本的数值类型,如任意大小的整数和浮点数(双精度)作为标准,但它还提供了几种在精度特别重要的特定应用中有用的附加类型。Python 还提供了对复数的(内置)支持,这对一些更高级的数学应用很有用。
十进制类型
对于需要精确算术运算的十进制数字的应用,可以使用 Python 标准库中的decimal
模块中的Decimal
类型:
from decimal import Decimal num1 = Decimal('1.1') num2 = Decimal('1.563') num1 + num2 # Decimal('2.663')
使用浮点对象进行此计算会得到结果 2.6630000000000003,这包括了一个小误差,这是因为某些数字不能用有限的 2 的幂的和来精确表示。例如,0.1 的二进制展开是 0.000110011…,它不会终止。因此,这个数字的任何浮点表示都会带有一个小误差。请注意,Decimal
的参数是一个字符串,而不是一个浮点数。
Decimal
类型基于 IBM 通用十进制算术规范(speleotrove.com/decimal/decarith.html
),这是一种浮点算术的替代规范,它通过使用 10 的幂而不是 2 的幂来精确表示十进制数。这意味着它可以安全地用于金融计算,其中舍入误差的累积将产生严重后果。然而,Decimal
格式的内存效率较低,因为它必须存储十进制数字而不是二进制数字(位),并且比传统的浮点数更加计算密集。
decimal
包还提供了一个Context
对象,它允许对Decimal
对象的精度、显示和属性进行精细控制。可以使用decimal
模块的getcontext
函数访问当前(默认)上下文。getcontext
返回的Context
对象具有许多可以修改的属性。例如,我们可以设置算术运算的精度:
from decimal import getcontext ctx = getcontext() num = Decimal('1.1') num**4 # Decimal('1.4641') ctx.prec = 4 # set new precision num**4 # Decimal('1.464')
当我们将精度设置为 4
时,而不是默认的 28
,我们会发现 1.1 的四次方被舍入为 4 个有效数字。
甚至可以使用localcontext
函数在本地设置上下文,该函数返回一个上下文管理器,在with
块结束时恢复原始环境:
from decimal import localcontext num = Decimal("1.1") with localcontext() as ctx: ctx.prec = 2 num**4 # Decimal('1.5') num**4 # Decimal('1.4641')
这意味着上下文可以在with
块内自由修改,并且在结束时将返回默认值。
分数类型
或者,对于需要准确表示整数分数的应用程序,例如处理比例或概率时,可以使用 Python 标准库中 fractions
模块的 Fraction
类型。用法类似,只是我们通常将分数的分子和分母作为参数给出:
from fractions import Fraction num1 = Fraction(1, 3) num2 = Fraction(1, 7) num1 * num2 # Fraction(1, 21)
Fraction
类型只是简单地存储两个整数,即分子和分母,并且使用分数的加法和乘法的基本规则执行算术运算。
复数类型
Python 也支持复数,包括在代码中表示复数单位 1j
的文字字符。这可能与您从其他复数源上熟悉的表示复数单位的习语不同。大多数数学文本通常会使用符号 i 来表示复数单位:
z = 1 + 1j z + 2 # 3 + 1j z.conjugate() # 1 - 1j
Python 标准库的 cmath
模块提供了专门针对“复数” - 意识的数学函数。
基本数学函数
基本数学函数出现在许多应用程序中。例如,对数可以用于将呈指数增长的数据缩放为线性数据。指数函数和三角函数在处理几何信息时是常见的固定内容,gamma 函数 出现在组合学中,高斯误差函数 在统计学中很重要*.*
Python 标准库中的 math
模块提供了所有标准数学函数,以及常见常数和一些实用函数,可以使用以下命令导入:
import math
一旦导入,我们可以使用此模块中包含的任何数学函数。例如,要找到非负数的平方根,我们将使用 math
中的 sqrt
函数:
import math math.sqrt(4) # 2.0
尝试使用 sqrt
函数的负参数将引发 ValueError。这个 sqrt
函数不定义负数的平方根,它只处理实数。负数的平方根——这将是一个复数——可以使用 Python 标准库中 cmath
模块的替代 sqrt
函数找到。
三角函数,正弦、余弦和正切,在math
模块中分别以它们的常见缩写sin
、cos
和tan
可用。pi
常数保存了π的值,约为 3.1416:
theta = pi/4 math.cos(theta) # 0.7071067811865476 math.sin(theta) # 0.7071067811865475 math.tan(theta) # 0.9999999999999999
math
模块中的反三角函数分别命名为acos
、asin
和atan
:
math.asin(-1) # -1.5707963267948966 math.acos(-1) # 3.141592653589793 math.atan(1) # 0.7853981633974483
math
模块中的log
函数执行对数。它有一个可选参数来指定对数的底数(注意第二个参数只能是位置参数)。默认情况下,没有可选参数,它是以自然对数为底数e。e常数可以使用math.e
来访问:
math.log(10) # 2.302585092994046 math.log(10, 10) # 1.0
math
模块还包含gamma
函数,即伽玛函数,以及erf
函数,即高斯误差函数,这在统计学中非常重要。这两个函数都是通过积分来定义的。伽玛函数由积分定义
误差函数由下式定义
误差函数定义中的积分无法使用微积分来求解,而必须通过数值计算来完成:
math.gamma(5) # 24.0 math.erf(2) # 0.9953222650189527
除了标准函数,如三角函数、对数和指数函数之外,math
模块还包含各种理论和组合函数。这些包括comb
和factorial
函数,它们在各种应用中非常有用。使用参数n和k调用的comb
函数返回从n个项目的集合中选择k个项目的方式数,如果顺序不重要且没有重复。例如,先选择 1 再选择 2 与先选择 2 再选择 1 是相同的。这个数字有时被写为*^nC[k]。使用参数n调用的阶乘函数返回阶乘n! = n(n-1)(n-2)*…1:
math.comb(5, 2) # 10 math.factorial(5) # 120
对负数应用阶乘会引发ValueError
。整数n的阶乘与n + 1处的伽玛函数的值相符,即
math
模块还包含一个返回其参数的最大公约数的函数,称为gcd
。a和b的最大公约数是最大的整数k,使得k能够完全整除a和b:
math.gcd(2, 4) # 2 math.gcd(2, 3) # 1
还有一些用于处理浮点数的函数。fsum
函数对数字的可迭代对象执行加法,并在每一步跟踪总和,以减少结果中的误差。以下示例很好地说明了这一点:
nums = [0.1]*10 # list containing 0.1 ten times sum(nums) # 0.9999999999999999 math.fsum(nums) # 1.0
isclose
函数返回True
,如果参数之间的差小于公差。这在单元测试中特别有用,因为基于机器架构或数据变异性,结果可能会有小的变化。
最后,math
中的floor
和ceil
函数提供了它们的参数的下限和上限。数字x的floor是最大的整数f,使得f ≤ x,x的ceiling是最小的整数c,使得x ≤ c。在将一个数字除以另一个数字得到浮点数和整数之间转换时,这些函数非常有用。
math
模块包含了在 C 中实现的函数(假设你正在运行 CPython),因此比在 Python 中实现的函数要快得多。如果你需要将函数应用于一个相对较小的数字集合,这个模块是一个不错的选择。如果你想要同时将这些函数应用于大量数据集合,最好使用 NumPy 包中的等效函数,这些函数对数组的处理更有效率。总的来说,如果你已经导入了 NumPy 包,那么最好总是使用这些函数的 NumPy 等效函数,以减少出错的机会。
NumPy 数组
NumPy 提供了高性能的数组类型和用于在 Python 中操作这些数组的例程。这些数组对于处理性能至关重要的大型数据集非常有用。NumPy 构成了 Python 中的数值和科学计算堆栈的基础。在幕后,NumPy 利用低级库来处理向量和矩阵,例如基本线性代数子程序(BLAS)包,以及线性代数包(LAPACK)包含更高级的线性代数例程。
传统上,NumPy 包是使用更短的别名np
导入的,可以使用以下import
语句来实现:
import numpy as np
特别是在 NumPy 文档和更广泛的科学 Python 生态系统(SciPy、Pandas 等)中使用了这种约定。
NumPy 库提供的基本类型是ndarray
类型(以下简称 NumPy 数组)。通常,您不会创建此类型的自己的实例,而是使用array
之类的辅助例程之一来正确设置类型。array
例程从类似数组的对象创建 NumPy 数组,这通常是一组数字或一组(数字)列表。例如,我们可以通过提供包含所需元素的列表来创建一个简单的数组:
ary = np.array([1, 2, 3, 4]) # array([1, 2, 3, 4])
NumPy 数组类型(ndarray
)是围绕基础 C 数组结构的 Python 包装器。数组操作是用 C 实现的,并针对性能进行了优化。NumPy 数组必须由同质数据组成(所有元素具有相同的类型),尽管此类型可以是指向任意 Python 对象的指针。如果在创建时未明确提供使用dtype
关键字参数,则 NumPy 将推断出适当的数据类型:
np.array([1, 2, 3, 4], dtype=np.float32) # array([1., 2., 3., 4.], dtype=float32)
在幕后,任何形状的 NumPy 数组都是一个包含原始数据的缓冲区,作为一个平坦(一维)数组,并包含一系列额外的元数据,用于指定诸如元素类型之类的细节。
创建后,可以使用数组的dtype
属性访问数据类型。修改dtype
属性将产生不良后果,因为构成数组中的原始字节将被重新解释为新的数据类型。例如,如果我们使用 Python 整数创建数组,NumPy 将在数组中将其转换为 64 位整数。更改dtype
值将导致 NumPy 将这些 64 位整数重新解释为新的数据类型:
arr = np.array([1, 2, 3, 4]) print(arr.dtype) # dtype('int64') arr.dtype = np.float32 print(arr) # [1.e-45 0.e+00 3.e-45 0.e+00 4.e-45 0.e+00 6.e-45 0.e+00]
每个 64 位整数都被重新解释为两个 32 位浮点数,这显然会产生无意义的值。相反,如果您希望在创建后更改数据类型,请使用astype
方法指定新类型。更改数据类型的正确方法如下所示:
arr = arr.astype(np.float32) print(arr) # [1\. 2\. 3\. 4.]
NumPy 还提供了一些用于创建各种标准数组的例程。zeros
例程创建一个指定形状的数组,其中每个元素都是0
,而ones
例程创建一个数组,其中每个元素都是1
。
元素访问
NumPy 数组支持getitem
协议,因此可以像列表一样访问数组中的元素,并支持所有按组件执行的算术操作。这意味着我们可以使用索引表示法和索引来检索指定索引处的元素,如下所示:
ary = np.array([1, 2, 3, 4]) ary[0] # 1 ary[2] # 3
这还包括从现有数组中提取数据数组的常规切片语法。数组的切片再次是一个数组,其中包含切片指定的元素。例如,我们可以检索包含ary
的前两个元素的数组,或者包含偶数索引处的元素的数组,如下所示:
first_two = ary[:2] # array([1, 2]) even_idx = ary[::2] # array([1, 3])
切片的语法是start:stop:step
。我们可以省略start
和stop
中的一个或两个,以从所有元素的开头或结尾分别获取。我们也可以省略step
参数,这种情况下我们也会去掉尾部的:
。step
参数描述了应该选择的选定范围内的元素。值为1
选择每个元素,或者如本例中,值为2
选择每第二个元素(从0
开始给出偶数编号的元素)。这个语法与切片 Python 列表的语法相同。
数组的算术和函数
NumPy 提供了许多通用函数(ufunc),这些函数可以高效地操作 NumPy 数组类型。特别是,在基本数学函数部分讨论的所有基本数学函数在 NumPy 中都有类似的函数,可以在 NumPy 数组上操作。通用函数还可以执行广播,以允许它们在不同但兼容的形状的数组上进行操作。
NumPy 数组上的算术运算是逐分量执行的。以下示例最能说明这一点:
arr_a = np.array([1, 2, 3, 4]) arr_b = np.array([1, 0, -3, 1]) arr_a + arr_b # array([2, 2, 0, 5]) arr_a - arr_b # array([0, 2, 6, 3]) arr_a * arr_b # array([ 1, 0, -9, 4]) arr_b / arr_a # array([ 1\. , 0\. , -1\. , 0.25]) arr_b**arr_a # array([1, 0, -27, 1])
请注意,数组必须具有相同的形状,这意味着具有相同的长度。对不同形状的数组进行算术运算将导致ValueError
。通过数字进行加法、减法、乘法或除法将导致数组,其中已对每个分量应用了操作。例如,我们可以使用以下命令将数组中的所有元素乘以2
:
arr = np.array([1, 2, 3, 4]) new = 2*arr print(new) # [2, 4, 6, 8]
有用的数组创建例程
要在两个给定端点之间以规则间隔生成数字数组,可以使用arange
例程或linspace
例程。这两个例程之间的区别在于linspace
生成一定数量(默认为 50)的值,这些值在两个端点之间具有相等的间距,包括两个端点,而arange
生成给定步长的数字,但不包括上限。linspace
例程生成封闭区间a ≤ x ≤ b中的值,而arange
例程生成半开区间a≤ x < b中的值:
np.linspace(0, 1, 5) # array([0., 0.25, 0.5, 0.75, 1.0]) np.arange(0, 1, 0.3) # array([0.0, 0.3, 0.6, 0.9])
请注意,使用linspace
生成的数组恰好有 5 个点,由第三个参数指定,包括0
和1
两个端点。使用arange
生成的数组有 4 个点,不包括右端点1
;0.3 的额外步长将等于 1.2,这比 1 大。
更高维度的数组
NumPy 可以创建任意维度的数组,使用与简单一维数组相同的array
例程创建。数组的维数由提供给array
例程的嵌套列表的数量指定。例如,我们可以通过提供一个列表的列表来创建一个二维数组,其中内部列表的每个成员都是一个数字,如下所示:
mat = np.array([[1, 2], [3, 4]])
NumPy 数组具有shape
属性,描述了每个维度中元素的排列方式。对于二维数组,形状可以解释为数组的行数和列数。
*NumPy 将形状存储为数组对象上的shape
属性,这是一个元组。这个元组中的元素数量就是数组的维数:
vec = np.array([1, 2]) mat.shape # (2, 2) vec.shape # (2,)
由于 NumPy 数组中的数据存储在一个扁平(一维)数组中,可以通过简单地更改相关的元数据来以很小的成本重新塑造数组。这是通过 NumPy 数组的reshape
方法完成的:
mat.reshape(4,) # array([1, 2, 3, 4])
请注意,元素的总数必须保持不变。矩阵mat
最初的形状为(2, 2)
,共有 4 个元素,后者是一个形状为(4,)
的一维数组,再次共有 4 个元素。当总元素数量不匹配时,尝试重新塑造将导致ValueError
。
要创建更高维度的数组,只需添加更多级别的嵌套列表。为了更清楚地说明这一点,在下面的示例中,我们在构造数组之前将第三维度中的每个元素的列表分开:
mat1 = [[1, 2], [3, 4]] mat2 = [[5, 6], [7, 8]] mat3 = [[9, 10], [11, 12]] arr_3d = np.array([mat1, mat2, mat3]) arr_3d.shape # (3, 2, 2)
请注意,形状的第一个元素是最外层的,最后一个元素是最内层的。
这意味着向数组添加一个额外的维度只是提供相关的元数据。使用array
例程,shape
元数据由参数中每个列表的长度描述。最外层列表的长度定义了该维度的相应shape
参数,依此类推。
NumPy 数组在内存中的大小并不显著取决于维度的数量,而只取决于元素的总数,这是shape
参数的乘积。但是,请注意,高维数组中的元素总数往往较大。
要访问多维数组中的元素,您可以使用通常的索引表示法,但是不是提供单个数字,而是需要在每个维度中提供索引。对于 2×2 矩阵,这意味着指定所需元素的行和列:
mat[0, 0] # 1 - top left element mat[1, 1] # 4 - bottom right element
索引表示法还支持在每个维度上进行切片,因此我们可以使用切片mat[:, 0]
提取单列的所有成员,如下所示:
mat[:, 0] # array([1, 3])
请注意,切片的结果是一个一维数组。
数组创建函数zeros
和ones
可以通过简单地指定一个具有多个维度参数的形状来创建多维数组。
矩阵
NumPy 数组也可以作为矩阵,在数学和计算编程中是基本的。矩阵只是一个二维数组。矩阵在许多应用中都是核心,例如几何变换和同时方程,但也出现在其他领域的有用工具中,例如统计学。矩阵本身只有在我们为它们配备矩阵算术时才是独特的(与任何其他数组相比)。矩阵具有逐元素的加法和减法运算,就像 NumPy 数组一样,还有一种称为标量乘法的第三种运算,其中我们将矩阵的每个元素乘以一个常数,以及一种不同的矩阵乘法概念。矩阵乘法与其他乘法概念根本不同,我们稍后会看到。
矩阵的一个最重要的属性是其形状,与 NumPy 数组的定义完全相同。具有m行和n列的矩阵通常被描述为m×n矩阵。具有与列数相同的行数的矩阵被称为方阵,这些矩阵在向量和矩阵理论中起着特殊的作用。
单位矩阵(大小为n)是n×n矩阵,其中(i,i)-th 条目为 1,而(i,j)-th 条目对于i ≠ j为零。有一个数组创建例程,为指定的n值提供n×n单位矩阵:
np.eye(3) # array([[1., 0., 0.], # [0., 1., 0.], # [0., 0., 1.]])
基本方法和属性
与矩阵相关的术语和数量有很多。我们在这里只提到两个这样的属性,因为它们以后会有用。这些是矩阵的转置,其中行和列互换,以及迹,它是方阵沿着主对角线的元素之和。主对角线由从矩阵左上角到右下角的线上的元素*a[ii]*组成。
NumPy 数组可以通过在array
对象上调用transpose
方法轻松转置。实际上,由于这是一个常见的操作,数组有一个方便的属性T
,它返回矩阵的转置。转置会颠倒矩阵(数组)的形状顺序,使行变为列,列变为行。例如,如果我们从一个 3×2 矩阵(3 行,2 列)开始,那么它的转置将是一个 2×3 矩阵,就像下面的例子一样:
mat = np.array([[1, 2], [3, 4]]) mat.transpose() # array([[1, 3], # [2, 4]]) mat.T # array([[1, 3], # [2, 4]])
与矩阵相关的另一个数量有时也是有用的是trace。方阵A的 trace,其条目如前面的代码所示,被定义为leading diagonal上的元素之和,它由从左上角对角线到右下角的元素组成。trace 的公式如下*
*
NumPy 数组有一个trace
方法,返回矩阵的迹:
A = np.array([[1, 2], [3, 4]]) A.trace() # 5
trace 也可以使用np.trace
函数访问,它不绑定到数组。
矩阵乘法
矩阵乘法是在两个矩阵上执行的操作,它保留了两个矩阵的一些结构和特性。形式上,如果A是一个l × m矩阵,B是一个m × n矩阵,如下所述
那么矩阵积A和B是一个l × n矩阵,其(p, q)-th 条目由下式给出
请注意,第一个矩阵的列数必须与第二个矩阵的行数匹配,以便定义矩阵乘法。如果定义了矩阵乘积AB,我们通常写AB,如果它被定义。矩阵乘法是一种特殊的操作。它不像大多数其他算术运算那样是交换的:即使AB和BA都可以计算,它们不一定相等。实际上,这意味着矩阵的乘法顺序很重要。这源自矩阵代数作为线性映射的表示的起源,其中乘法对应于函数的组合。
Python 有一个保留给矩阵乘法的运算符@
,这是在 Python 3.5 中添加的。NumPy 数组实现了这个运算符来执行矩阵乘法。请注意,这与数组的分量乘法*
在本质上是不同的:
A = np.array([[1, 2], [3, 4]]) B = np.array([[-1, 1], [0, 1]]) A @ B # array([[-1, 3], # [-3, 7]]) A * B # different from A @ B # array([[-1, 2], # [ 0, 4]])
单位矩阵是矩阵乘法下的中性元素。也就是说,如果A是任意的l × m矩阵,I是m × m单位矩阵,则AI = A。可以使用 NumPy 数组轻松检查特定示例:
A = np.array([[1, 2], [3, 4]]) I = np.eye(2) A @ I # array([[1, 2], # [3, 4]])
行列式和逆
一个方阵的determinant在大多数应用中都很重要,因为它与找到矩阵的逆的强连接。如果行和列的数量相等,则矩阵是方阵。特别地,一个具有非零行列式的矩阵具有(唯一的)逆,这对于某些方程组的唯一解是成立的。矩阵的行列式是递归定义的。对于一个 2×2 矩阵
矩阵A的determinant由以下公式定义
对于一个一般的n×n矩阵
其中n > 2,我们定义子矩阵A[i,j],对于 1 ≤ i,j ≤ n,为从A中删除第i行和第j列的结果。子矩阵A[i,j]是一个(n-1) × (n-1)矩阵,因此我们可以计算行列式。然后我们定义A的行列式为数量
实际上,出现在前述方程中的索引 1 可以被任何 1 ≤ i≤ n替换,结果将是相同的。
计算矩阵行列式的 NumPy 例程包含在一个名为linalg
的单独模块中。这个模块包含了许多关于线性代数的常见例程,线性代数是涵盖向量和矩阵代数的数学分支。计算方阵行列式的例程是det
例程:
from numpy import linalg linalg.det(A) # -2.0000000000000004
请注意,在计算行列式时发生了浮点舍入误差。
如果安装了 SciPy 包,还提供了一个扩展了 NumPylinalg
的linalg
模块。SciPy 版本不仅包括额外的例程,而且始终使用 BLAS 和 LAPACK 支持进行编译,而对于 NumPy 版本,这是可选的。因此,如果速度很重要,SciPy 变体可能更可取,这取决于 NumPy 的编译方式。
n × n矩阵A的逆是(必然唯一的)n × n矩阵B,使得AB=BA=I,其中I表示n × n单位矩阵,这里执行的乘法是矩阵乘法。并非每个方阵都有逆;那些没有逆的有时被称为奇异矩阵。事实上,当A没有逆时,也就是说,当该矩阵的行列式为 0 时,它是非奇异的。当A有逆时,习惯上用*A^(-1)*表示它。
linalg
模块的inv
例程计算矩阵的逆,如果存在的话:
linalg.inv(A) # array([[-2\. , 1\. ], # [ 1.5, -0.5]])
我们可以通过矩阵乘法(在任一侧)乘以逆矩阵,并检查我们是否得到了 2 × 2 单位矩阵,来检查inv
例程给出的矩阵是否确实是A
的矩阵逆:
Ainv = linalg.inv(A) Ainv @ A # Approximately # array([[1., 0.], # [0., 1.]]) A @ Ainv # Approximately # array([[1., 0.], # [0., 1.]])
这些计算中会有浮点误差,这已经被隐藏在Approximately
注释后面,这是由于计算矩阵的逆的方式。
linalg
包还包含许多其他方法,如norm
,它计算矩阵的各种范数。它还包含了各种分解矩阵和解方程组的函数。
还有指数函数expm
、对数函数logm
、正弦函数sinm
、余弦函数cosm
和切线函数tanm
的矩阵类比。请注意,这些函数与基本 NumPy 包中的标准exp
、log
、sin
、cos
和tan
函数不同,后者是在元素基础上应用相应的函数。相反,矩阵指数函数是使用矩阵的“幂级数”定义的。
其中A是一个n × n矩阵,A^k是A的第k个矩阵幂;也就是说,A矩阵连续乘以自身k次。请注意,这个“幂级数”总是在适当的意义下收敛。按照惯例,我们取A⁰ = I,其中I是n × n单位矩阵。这与实数或复数的指数函数的常规幂级数定义完全类似,但是用矩阵和矩阵乘法代替了数字和(常规)乘法。其他函数也是以类似的方式定义的,但我们将跳过细节。
Python 数学应用(一)(2)https://developer.aliyun.com/article/1506373