geekdoc-python-zh/docs/realpython/python-scipy-linalg.md

23 KiB
Raw Permalink Blame History

使用 scipy.linalg 在 Python 中处理线性系统

原文:https://realpython.com/python-scipy-linalg/

线性代数广泛应用于各种学科,一旦你使用像向量线性方程这样的概念组织信息,你就可以用它来解决许多问题。在 Python 中,与这个主题相关的大多数例程都是在 scipy.linalg 中实现的,这提供了非常快的线性代数能力。

特别是,线性系统在建模各种现实世界问题中发挥着重要作用,scipy.linalg提供了以高效方式研究和解决它们的工具。

在本教程中,您将学习如何:

  • 利用 scipy.linalg线性代数概念应用到实际问题中
  • 使用 Python 和 NumPy 处理向量矩阵
  • 使用线性系统模拟实际问题
  • 如何用 scipy.linalg 解线性方程组

我们开始吧!

免费奖励: 点击此处获取免费的 NumPy 资源指南,它会为您指出提高 NumPy 技能的最佳教程、视频和书籍。

scipy.linalg 入门

SciPy 是一个用于科学计算的开源 Python 库,包括几个用于科学和工程中常见任务的模块,如线性代数优化积分插值信号处理。它是 SciPy 栈的一部分,后者包括其他几个用于科学计算的包,如 NumPyMatplotlibSymPyIPythonpandas

线性代数是数学的一个分支,涉及线性方程及其使用向量矩阵的表示。这是一个在工程的几个领域使用的基础学科,也是更深入理解机器学习的先决条件。

scipy.linalg 包括几个处理线性代数问题的工具,包括执行矩阵计算的函数,如行列式、逆矩阵、特征值、特征向量奇异值分解

在本教程中,你将使用scipy.linalg中的一些函数来处理涉及线性系统的实际问题。为了使用scipy.linalg,你必须安装和设置 SciPy 库,这可以通过使用 Anaconda Python 发行版和 conda 包和环境管理系统来完成。

**注意:**要了解更多关于 Anaconda 和 conda 的信息,请查看在 Windows 上设置 Python 进行机器学习

首先,创建一个 conda 环境并激活它:

$ conda create --name linalg
$ conda activate linalg

激活 conda 环境后,您的提示符将显示其名称linalg。然后,您可以在环境中安装必要的软件包:

(linalg) $ conda install scipy jupyter

执行此命令后,系统需要一段时间来确定依赖项并继续安装。

**注意:**除了 SciPy您还将使用 Jupyter Notebook 在交互式环境中运行代码。这样做不是强制性的,但它有助于处理数值和科学应用程序。

要复习使用 Jupyter 笔记本,请看一下 Jupyter 笔记本:简介

如果您喜欢使用不同的 Python 发行版和 pip包管理器来阅读本文,那么展开下面的可折叠部分来看看如何设置您的环境。

首先,建议创建一个虚拟环境,在其中安装软件包。假设您安装了 Python您可以创建一个名为linalg的虚拟环境:

$ python -m venv linalg

创建环境后,您需要激活它,这样您就可以使用pip在环境中安装包。如果您使用的是 Linux 或 macOS那么您可以使用以下命令激活环境:

$ source ./linalg/bin/activate

在 Windows 上,您必须使用的命令略有不同:

C:> \linalg\Scripts\activate.bat

激活 conda 环境后,您的提示符将显示其名称linalg。然后,您可以使用pip在环境中安装必要的包:

(linalg) $ python -m pip install scipy jupyter

系统需要一段时间来找出依赖关系并继续安装。命令结束后,您就可以打开 Jupyter 并使用scipy.linalg了。

在打开 Jupyter Notebook 之前,您需要注册 conda linalg环境,以便您可以使用它作为内核来创建笔记本。为此,在激活了linalg环境的情况下,运行以下命令:

(linalg) $ python -m ipykernel install --user --name linalg

现在,您可以通过运行以下命令来打开 Jupyter 笔记本:

$ jupyter notebook

Jupyter 加载到你的浏览器后,点击新建直线创建一个新的笔记本,如下图所示:

Jupyter Notebook

在笔记本内部,您可以通过导入scipy来测试安装是否成功:

In [1]: import scipy

现在您已经完成了环境的设置,您将看到如何在 Python 中处理向量和矩阵,这是使用scipy.linalg处理线性代数应用程序的基础。

Remove ads

使用 NumPy 处理向量和矩阵

矢量是一个数学实体,用于表示既有大小又有方向的物理量。它是解决工程和机器学习问题的一个基本工具,正如用于表示向量变换的矩阵以及其他应用一样。

NumPy 是在 Python 中使用矩阵和向量最多的,与scipy.linalg一起用于线性代数应用。在本节中,您将了解使用它创建矩阵和向量以及对它们执行操作的基本知识。

要开始处理矩阵和向量,你需要在 Jupyter 笔记本上做的第一件事是导入numpy。通常的方法是使用别名np:

In [2]: import numpy as np

为了表示矩阵和向量NumPy 使用了一种叫做 ndarray 的特殊类型。

要创建一个ndarray对象,可以使用np.array(),它需要一个类似数组的对象,比如一个列表或者一个嵌套列表。

例如,假设您需要创建以下矩阵:

Matrix to represent using NumPy

要用 NumPy 创建它,您可以使用np.array(),提供一个包含矩阵每行元素的嵌套列表:

In [3]: A = np.array([[1, 2], [3, 4], [5, 6]])
 ...: A
Out[3]:
array([[1, 2],
 [3, 4],
 [5, 6]])

正如您可能注意到的NumPy 提供了矩阵的可视化表示,您可以在其中识别它的列和行。

值得注意的是NumPy 数组的元素必须是同一类型。您可以使用.dtype来检查 NumPy 数组的类型:

In [4]: A.dtype
Out[4]:
dtype('int64')

由于A的所有元素都是整数,所以数组是用类型int64创建的。如果其中一个元素是一个浮点数,那么这个数组将被创建为类型float64:

In [5]: A = np.array([[1.0, 2], [3, 4], [5, 6]])
 ...: A
Out[5]:
array([[1., 2.],
 [3., 4.],
 [5., 6.]])

In [6]: A.dtype
Out[6]:
dtype('float64')

要检查ndarray对象的尺寸,可以使用.shape。例如,要检查A的尺寸,可以使用A.shape:

In [7]: A.shape
Out[7]:
(3, 2)

正如所料,A矩阵的维数是3 × 2,因为A有三行两列。

当处理涉及矩阵的问题时,你经常需要使用转置操作,它交换矩阵的列和行。

要转置由ndarray对象表示的向量或矩阵,可以使用.transpose().T。比如你可以用A.T获得A的转置:

In [8]: A.T
Out[8]:
array([[1., 3., 5.],
 [2., 4., 6.]])

通过转置,A的列变成了A.T的行,行变成了列。

要创建一个向量,可以使用np.array(),提供一个包含向量元素的列表:

In [9]: v = np.array([1, 2, 3])
 ...: v
Out[9]:
array([1, 2, 3])

要检查向量的维度,您可以像之前一样使用.shape:

In [10]: v.shape
Out[10]:
(3,)

注意这个向量的形状是(3,),而不是(3, 1)或者(1, 3)。这是一个与那些习惯于使用 MATLAB 的人相关的 NumPy 特性。在 NumPy 中,可以创建像v这样的一维数组,这可能会在执行矩阵和向量之间的运算时产生问题。例如,转置操作对一维数组没有影响。

每当您向np.array()提供一个类似一维数组的参数时,得到的数组将是一个一维数组。要创建二维数组,您必须提供一个类似二维数组的参数,如嵌套列表:

In [11]: v = np.array([[1, 2, 3]])
 ...: v.shape
Out[11]:
(1, 3)

上例中,v的维数为1 × 3,对应一个二维线矢量的维数。要创建列向量,可以使用嵌套列表:

In [12]: v = np.array([[1], [2], [3]])
 ...: v.shape
Out[12]:
(3, 1)

在这种情况下,v的维数为3 × 1,对应一个二维列向量的维数。

使用嵌套列表来创建向量可能很费力,尤其是使用最多的列向量。或者,您可以创建一个一维向量,向np.array提供一个平面列表,并使用.reshape()来改变ndarray对象的尺寸:

In [13]: v = np.array([1, 2, 3]).reshape(3, 1)
 ...: v.shape
Out[13]:
(3, 1)

在上面的例子中,您使用.reshape()从形状为(3,)的一维向量中获得形状为(3, 1)的列向量。值得一提的是.reshape()期望新数组的元素个数与原数组的元素个数兼容。换句话说,具有新形状的数组中的元素数量必须等于原始数组中的元素数量。

在这个例子中,您也可以使用.reshape()而不用显式定义数组的行数:

In [14]: v = np.array([1, 2, 3]).reshape(-1, 1)
 ...: v.shape
Out[14]:
(3, 1)

这里,作为参数提供给.reshape()-1表示新数组只有一列所需的行数,由第二个参数指定。在这种情况下,由于原始数组有三个元素,新数组的行数将是3

在实际应用中,您经常需要创建由 0、1 或随机元素组成的矩阵。为此NumPy 提供了一些方便的函数,您将在接下来看到。

Remove ads

使用便利函数创建数组

NumPy 还提供了一些方便的函数来创建数组。例如,要创建一个用零填充的数组,可以使用np.zeros():

In [15]: A = np.zeros((3, 2))
 ...: A
Out[15]:
array([[0., 0.],
 [0., 0.],
 [0., 0.]])

作为它的第一个参数,np.zeros()期望一个元组来指示您想要创建的数组的形状,并且它返回一个类型为float64的数组。

类似地,要创建填充 1 的数组,可以使用np.ones():

In [16]: A = np.ones((2, 3))
 ...: A
Out[16]:
array([[1., 1., 1.],
 [1., 1., 1.]])

值得注意的是,np.ones()也返回一个类型为float64的数组。

要创建包含随机元素的数组,可以使用np.random.rand():

In [17]: A = np.random.rand(3, 2)
 ...: A
Out[17]:
array([[0.8206045 , 0.54470809],
 [0.9490381 , 0.05677859],
 [0.71148476, 0.4709059 ]])

np.random.rand()返回一个从01的随机元素数组,取自均匀分布。注意,与np.zeros()np.ones()不同的是,np.random.rand()不期望元组作为它的参数。

类似地,要获得一个数组,其中的随机元素来自平均值和单位方差为零的正态分布,您可以使用np.random.randn():

In [18]: A = np.random.randn(3, 2)
 ...: A
Out[18]:
array([[-1.20019512, -1.78337814],
 [-0.22135221, -0.38805899],
 [ 0.17620202, -2.05176764]])

现在您已经创建了数组,您将看到如何使用它们执行操作。

对 NumPy 数组执行操作

在数组上使用加法(+)、减法(-)、乘法(*)、除法(/)和指数(**)运算符的常见 Python 操作总是按元素执行的。如果其中一个操作数是一个标量,那么这个操作将在这个标量和数组的每个元素之间执行。

例如,要创建一个填充了等于10的元素的矩阵,您可以使用np.ones()并用*将输出乘以10:

In [19]: A = 10 * np.ones((2, 2))
 ...: A
Out[19]:
array([[10., 10.],
 [10., 10.]])

如果两个操作数都是相同形状的数组,则运算将在数组的相应元素之间执行:

In [20]: A = 10 * np.ones((2, 2))
 ...: B = np.array([[2, 2], [5, 5]])
 ...: C = A * B
 ...: C
Out[20]:
array([[20., 20.],
 [50., 50.]])

这里,您将矩阵A的每个元素乘以矩阵B的相应元素。

要根据线性代数规则执行矩阵乘法,可以使用np.dot():

In [21]: A = np.array([[1, 2], [3, 4]])
 ...: v = np.array([[5], [6]])
 ...: x = np.dot(A, v)
 ...: x
Out[21]:
array([[17],
 [39]])

这里,您将一个名为A的 2 × 2 矩阵乘以一个名为v的 2 × 1 向量。

您可以使用@操作符获得相同的结果,从 PEP 465 和 Python 3.5 开始NumPy 和原生 Python 都支持该操作符:

In [22]: A = np.array([[1, 2], [3, 4]])
 ...: v = np.array([[5], [6]])
 ...: x = A @ v
 ...: x
Out[22]:
array([[17],
 [39]])

除了处理矩阵和向量的基本操作NumPy 还提供了一些特定的函数来处理numpy.linalg中的线性代数。然而,对于那些应用程序来说,scipy.linalg提供了一些优势,您将在下面看到。

Remove ads

scipy.linalgnumpy.linalg进行比较

NumPy 在 numpy.linalg 模块中包含了一些处理线性代数应用程序的工具。然而,除非您不想将 SciPy 作为依赖项添加到您的项目中,否则通常最好使用scipy.linalg,原因如下:

  • 正如官方文档中解释的那样,scipy.linalg包含了numpy.linalg中的所有功能,加上一些numpy.linalg中没有的额外高级功能。

  • scipy.linalg的编译总是支持 BLASLAPACK ,它们是包含以优化方式执行数值运算的例程的库。对于numpy.linalgBLAS 和 LAPACK 的使用是可选的。因此,根据你如何安装 NumPyscipy.linalg功能可能会比numpy.linalg快。

总之,考虑到科学和技术应用程序通常没有关于依赖性的限制,安装 SciPy 并使用scipy.linalg而不是numpy.linalg通常是个好主意。

在下一节中,您将使用scipy.linalg工具来处理线性系统。您将从一个简单的例子开始,然后将这些概念应用到一个实际问题中。

scipy.linalg.solve()解线性系统

线性系统可以是一个有用的工具,用于找到几个实际和重要问题的解决方案,包括与车辆交通、平衡化学方程式、电路和多项式插值相关的问题。

在这一节中,你将学习如何使用scipy.linalg.solve()来求解线性系统。但是在着手编写代码之前,理解基础知识是很重要的。

了解线性系统

线性系统,或者更准确地说,线性方程组,是与一组变量线性相关的一组方程。这里有一个与变量 x ₁、 x ₂和 x ₃:相关的线性系统的例子

Example of linear system

这里有三个包含三个变量的方程。为了有一个线性系统,值k₁……k₉和b₁……b₃必须是常数。

当只有两三个方程和变量时,可以手动执行计算,合并方程,并找到变量的值。然而,对于四个或更多的变量,手动求解一个线性系统需要相当长的时间,并且出错是很常见的。

实际应用一般涉及大量变量,这使得手动求解线性系统不可行。幸运的是,有一些工具可以完成这项艰巨的工作,比如 scipy.linalg.solve()

使用scipy.linalg.solve()

SciPy 提供了scipy.linalg.solve()来快速可靠地求解线性系统。要了解其工作原理,请考虑以下系统:

Linear system

为了使用scipy.linalg.solve(),你需要首先将线性系统写成一个矩阵乘积,如下式所示:

Linear system expressed using matrices and vectors

注意,计算完矩阵乘积后,你会得到系统的原始方程。scipy.linalg.solve()期望解决的输入是矩阵A和向量b,它们可以使用 NumPy 数组来定义。这样,您可以使用以下代码求解系统:

 1In [1]: import numpy as np
 2 ...: from scipy.linalg import solve
 3
 4In [2]: A = np.array(
 5 ...:   [
 6 ...:       [3, 2],
 7 ...:       [2, -1],
 8 ...:   ]
 9 ...: )
10
11In [3]: b = np.array([12, 1]).reshape((2, 1))
12
13In [4]: x = solve(A, b)
14 ...: x
15Out[4]:
16array([[2.],
17 [3.]])

下面是正在发生的事情的分类:

  • 1、2 号线scipy.linalgsolve()一起导入编号为np
  • 第 4 行到第 9 行使用名为A的 NumPy 数组创建系数矩阵。
  • 第 11 行使用名为b的 NumPy 数组创建独立的术语向量。要使它成为两行的列向量,可以使用.reshape((2, 1))
  • 第 13 行和第 14 行调用solve()求解由Ab表征的线性系统,结果存储在x中,并打印出来。请注意,solve()返回的是浮点成分的解,即使原始数组的所有元素都是整数。

如果你在原始方程中替换掉 x ₁=2 和 x ₂=3那么你可以验证这就是系统的解。

既然您已经学习了使用scipy.linalg.solve()的基础知识,那么是时候看看线性系统的实际应用了。

Remove ads

解决实际问题:制定饮食计划

线性系统通常解决的一类问题是,当你需要找到获得某种混合物所需的组分比例时。下面,你将使用这个想法来建立一个膳食计划,混合不同的食物,以获得均衡的饮食。

为此,考虑均衡饮食应包括以下内容:

  • 170 单位的维生素 A
  • 180 单位的维生素 B
  • 140 单位的维生素 C
  • 180 单位的维生素 D
  • 350 单位的维生素 E

你的任务是找出每种不同食物的数量,以获得规定量的维生素。在下表中,您得到了根据每种维生素的单位分析每种食物一克的结果:

食物 维生素 a 维生素 b 维生素 c 钙化醇 生育酚
#1 one Ten one Two Two
#2 nine one Zero one one
#3 Two Two five one Two
#4 one one one Two Thirteen
#5 one one one nine Two

通过将食物 1 表示为 x ₁等等,并考虑到你将混合 x ₁单位的食物 1、 x ₂单位的食物 2 等等,你可以写一个表达式来表示你在组合中得到的维生素 a 的量。考虑到均衡饮食应包含 170 单位的维生素 A您可以使用维生素 A 栏中的数据写出以下等式:

Equation for vitamin A

对维生素 B、C、D 和 E 重复同样的过程,你得到下面的线性系统:

Linear system for all vitamins

要使用scipy.linalg.solve(),您必须获得系数矩阵A和独立项向量b,它们由下式给出:

Linear system for all vitamins using matrices and vectors

现在你只需要用scipy.linalg.solve()找出数量 x ₁,…, x ₅:

In [1]: import numpy as np
 ...: from scipy.linalg import solve

In [2]: A = np.array(
 ...:    [
 ...:        [1, 9, 2, 1, 1],
 ...:        [10, 1, 2, 1, 1],
 ...:        [1, 0, 5, 1, 1],
 ...:        [2, 1, 1, 2, 9],
 ...:        [2, 1, 2, 13, 2],
 ...:    ]
 ...: )

In [3]: b = np.array([170, 180, 140, 180, 350]).reshape((5, 1))

In [4]: x = solve(A, b)
 ...: x
Out[4]:
array([[10.],
 [10.],
 [20.],
 [20.],
 [10.]])

这表明均衡饮食应该包括10单位食物 1、10单位食物 2、20单位食物 3、20单位食物 4 和10单位食物 5。

结论

恭喜你!您已经学习了如何使用一些线性代数概念,以及如何使用scipy.linalg来解决涉及线性系统的问题。您已经看到向量和矩阵对于表示数据非常有用,并且通过使用线性代数概念,您可以对实际问题进行建模并以高效的方式解决它们。

在本教程中,您学习了如何:

  • 利用 scipy.linalg线性代数概念应用到实际问题中
  • 使用 Python 和 NumPy 处理向量矩阵
  • 使用线性系统模拟实际问题
  • 利用 scipy.linalg 求解线性系统

线性代数是一个非常广泛的话题。有关其他线性代数应用程序的更多信息,请查看以下资源:

继续学习,欢迎在下面留下任何问题或评论!****