8. 线性方程和矩阵代数#
8.1. 概述#
经济学和金融学中的许多问题都需要解线性方程。
在本讲座中,我们将讨论线性方程及其应用。
为了说明线性方程的重要性,我们从一个两种商品的供需模型开始。
两种商品的情况非常简单,可以手动计算求解。
但我们经常需要考虑包含多种商品的市场。
在多种商品的情况下,我们面对的是大型线性方程组,有许多方程和未知数。
为了处理这样的系统,我们需要两样知识:
矩阵代数(以及如何使用它的知识)以及
将矩阵代数应用于感兴趣问题的计算机代码。
本讲座涵盖了这些步骤。
我们将使用以下函数库:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf"
mpl.font_manager.fontManager.addfont(FONTPATH)
plt.rcParams['font.family'] = ['Source Han Serif SC']
8.2. 两种商品的例子#
在本节中,我们将讨论一个简单的两种商品例子,并通过以下两种方法解决:
纸笔计算
矩阵代数
正如我们将看到的,第二种方法更具普遍性。
8.2.1. 纸笔计算方法#
假设我们有两种相关的商品,比如:
丙烷和乙醇,或
大米和小麦等。
为了简化问题,我们将它们标记为商品0和商品1。
每种商品的需求取决于两种商品的价格:
(我们假设当任一商品的价格上涨时需求会下降,但其他情况也是可能的。)
让我们假设供给由以下方程给出:
当供给等于需求时(
这产生了以下线性方程组:
我们可以用纸笔计算得到:
8.2.2. 展望未来#
在两种商品的情况下,纸笔计算方法很容易。
但如果有很多种商品呢?
对于这样的问题,我们需要矩阵代数。
在用矩阵代数解决问题之前,让我们先回顾一下向量和矩阵的基础知识,包括理论和计算。
8.3. 向量#
一个长度为
我们可以将这些序列横向或纵向写出。
但当我们使用矩阵运算时,我们默认假设向量是列向量。
所有
Example 8.1
是平面 — 即所有 对的集合。 是三维空间 — 即所有 向量的集合。 向量通常在视觉上表示为从原点到某点的箭头。
这里是一个可视化示例。
Show source
fig, ax = plt.subplots()
# 通过原点建立坐标轴
for spine in ['left', 'bottom']:
ax.spines[spine].set_position('zero')
for spine in ['right', 'top']:
ax.spines[spine].set_color('none')
ax.set(xlim=(-5, 5), ylim=(-5, 5))
vecs = ((2, 4), (-3, 3), (-4, -3.5))
for v in vecs:
ax.annotate('', xy=v, xytext=(0, 0),
arrowprops=dict(facecolor='blue',
shrink=0,
alpha=0.7,
width=0.5))
ax.text(1.1 * v[0], 1.1 * v[1], str(v))
plt.show()
8.3.1. 向量运算#
有时我们需要修改向量。
对向量最常见的两种运算是加法和标量乘法,我们现在来描述这两种运算。
当我们对两个向量进行加法运算时,我们是逐元素相加。
Example 8.2
一般来说,
我们可以在
Show source
fig, ax = plt.subplots()
# 通过原点建立坐标轴
for spine in ['left', 'bottom']:
ax.spines[spine].set_position('zero')
for spine in ['right', 'top']:
ax.spines[spine].set_color('none')
ax.set(xlim=(-2, 10), ylim=(-4, 4))
# ax.grid()
vecs = ((4, -2), (3, 3), (7, 1))
tags = ('(x1, x2)', '(y1, y2)', '(x1+x2, y1+y2)')
colors = ('blue', 'green', 'red')
for i, v in enumerate(vecs):
ax.annotate('', xy=v, xytext=(0, 0),
arrowprops=dict(color=colors[i],
shrink=0,
alpha=0.7,
width=0.5,
headwidth=8,
headlength=15))
ax.text(v[0] + 0.2, v[1] + 0.1, tags[i])
for i, v in enumerate(vecs):
ax.annotate('', xy=(7, 1), xytext=v,
arrowprops=dict(color='gray',
shrink=0,
alpha=0.3,
width=0.5,
headwidth=5,
headlength=20))
plt.show()
标量乘法是一种将向量
Example 8.3
更一般地,它取一个数
标量乘法在下图中进行了说明。
Show source
fig, ax = plt.subplots()
# 通过原点建立坐标轴
for spine in ['left', 'bottom']:
ax.spines[spine].set_position('zero')
for spine in ['right', 'top']:
ax.spines[spine].set_color('none')
ax.set(xlim=(-5, 5), ylim=(-5, 5))
x = (2, 2)
ax.annotate('', xy=x, xytext=(0, 0),
arrowprops=dict(facecolor='blue',
shrink=0,
alpha=1,
width=0.5))
ax.text(x[0] + 0.4, x[1] - 0.2, '$x$', fontsize='16')
scalars = (-2, 2)
x = np.array(x)
for s in scalars:
v = s * x
ax.annotate('', xy=v, xytext=(0, 0),
arrowprops=dict(facecolor='red',
shrink=0,
alpha=0.5,
width=0.5))
ax.text(v[0] + 0.4, v[1] - 0.2, f'${s} x$', fontsize='16')
plt.show()
在Python中,向量可以用列表或元组表示,
例如 x = [2, 4, 6]
或 x = (2, 4, 6)
。
然而,更常见的是用 NumPy数组来表示向量。
使用NumPy数组的一个优点是标量乘法和加法的语法非常自然。
x = np.ones(3) # 三个元素为一的向量
y = np.array((2, 4, 6)) # 将 (2, 4, 6) 转换为 NumPy 数组
x + y # 每个元素相加
array([3., 5., 7.])
4 * x # 标量乘法
array([4., 4., 4.])
8.3.2. 内积和范数#
向量
向量
表达式
内积和范数可以按以下方式计算
np.sum(x*y) # x和y的内积
12.0
x @ y # 另外一种计算内积的办法
12.0
np.sqrt(np.sum(x**2)) # x的范数,方法一
1.7320508075688772
np.linalg.norm(x) # x的范数,方法二
1.7320508075688772
8.4. 矩阵运算#
当我们讨论线性价格方程组时,我们提到了使用矩阵代数。
矩阵代数类似于算术代数。
让我们回顾一些细节。
8.4.1. 加法和标量乘法#
就像向量一样,我们可以对矩阵进行加法、减法和标量乘法。
标量乘法和加法是向量情况的推广:
Example 8.4
一般来说,对于任意数
Example 8.5
考虑这个矩阵加法的例子,
一般来说,
在后一种情况下,矩阵必须具有相同的形状才能使定义有意义。
8.4.2. 矩阵乘法#
我们还有一个相乘两个矩阵的约定。
矩阵乘法的规则推广了上面讨论的内积的概念。
如果
如果
Example 8.6
这里是一个
作为一个重要的特殊情况,考虑将
根据前面的规则,结果是一个
下面展示了两个矩阵的乘法。
有许多教程可以帮助你进一步可视化这个操作,例如
Note
与数字乘积不同,
一个重要的特殊情况是单位矩阵,它在主对角线上有 1,其他地方都是 0:
作为练习请验证以下内容:
如果
是 矩阵, 是 单位矩阵,那么 ,并且如果
是 单位矩阵,那么 。
8.4.3. NumPy中的矩阵#
NumPy 数组也被用作矩阵,并且对所有标准矩阵运算都有快速、高效的函数和方法。
你可以通过以下方式从元组的元组(或列表的列表)手动创建它们
A = ((1, 2),
(3, 4))
type(A)
tuple
A = np.array(A)
type(A)
numpy.ndarray
A.shape
(2, 2)
shape
属性是一个给出行数和列数的元组 —
更多讨论请参见这里。
要获得 A
的转置,使用 A.transpose()
或更简单地使用 A.T
。
有许多方便的函数用于创建常见矩阵(零矩阵、单位矩阵等) — 请参见这里。
由于默认情况下操作是按元素执行的,标量乘法和加法具有非常自然的语法。
A = np.identity(3) # 3 x 3 单位矩阵
B = np.ones((3, 3)) # 3 x 3 元素为一的矩阵
2 * A
array([[2., 0., 0.],
[0., 2., 0.],
[0., 0., 2.]])
A + B
array([[2., 1., 1.],
[1., 2., 1.],
[1., 1., 2.]])
我们用 @
来进行矩阵乘法。
Note
其中 A @ B
是矩阵乘法, 但是 A * B
是每个元素之间的运算。
8.4.4. 矩阵形式的两种商品模型#
我们现在可以重新审视两种商品模型,并通过矩阵代数数值求解 (8.3) 方程。
这涉及一些额外的步骤,但这种方法广泛适用,我们将在求解包含更多商品时用上。
首先,我们将 (8.1) 重写为
回想一下,
(请检查
我们将 (8.2) 重写为
现在供给和需求的相等可以表示为
我们可以重新排列这些项得到
如果所有项都是数字,我们可以求解价格为
矩阵代数允许我们做类似的事情:我们可以使用
在我们实施解决方案之前,让我们考虑一个更一般的问题。
8.4.5. 更多商品#
考虑有更多商品的需求系统是很自然的。
例如,即使在能源商品中也有许多不同的商品, 包括原油、汽油、煤炭、天然气、乙醇和铀。
这些商品的价格是相关的,所以一起研究它们是有意义的。
对于大型系统,纸笔方法会变得非常耗时。
但幸运的是,上面描述的矩阵方法基本上保持不变。
一般来说,我们可以将需求方程写为
是一个 的向量,表示 种不同商品的需求量。 是一个 的”系数”矩阵。 是一个 的常数值向量。
类似地,我们可以将供给方程写为
是一个 的向量,表示相同商品的供给量。 是一个 的”系数”矩阵。 是一个 的常数值向量。
为了找到均衡,我们求解
那么,n 种不同商品的价格向量是
8.4.6. 一般线性系统#
上述问题的一个更一般版本看起来如下。
这里的目标是解出”未知数”
我们给定系数
注意,我们处理的是未知数数量等于方程数量的情况。
这是我们最有可能找到明确定义解的情况。
(其他情况被称为超定和欠定方程组 — 我们将在后续讲座中讨论这些情况。)
用矩阵形式表示,方程组 (8.9) 变为
例如,(8.8) 具有这种形式,其中
当考虑诸如 (8.10) 这样的问题时,我们至少需要问以下一些问题:
解是否真的存在?
如果解存在,我们应该如何计算它?
8.5. 解方程组#
再次回顾方程组 (8.9),我们在此重新写为
我们面临的问题是找到一个向量
我们可能并不总能找到一个唯一的向量
我们在下面举例说明两种这样的情况。
8.5.1. 无解#
考虑由以下给出的方程组:
可以手动验证这个系统没有可能的解。
为了说明为什么会出现这种情况,让我们绘制这两条直线。
fig, ax = plt.subplots()
x = np.linspace(-10, 10)
plt.plot(x, (3-x)/3, label=f'$x + 3y = 3$')
plt.plot(x, (-8-2*x)/6, label=f'$2x + 6y = -8$')
plt.legend()
plt.show()
显然,这些是平行线,因此我们永远无法找到一个点
因此,这个系统没有可能的解。
我们可以将这个系统用矩阵形式重写为
可以注意到,矩阵
在这种情况下,矩阵
8.5.2. 多解#
现在考虑,
任何满足
由于我们可以找到无限多个这样的向量,这个系统有无穷多个解。
这是因为对应矩阵的行
是线性相关的 — 你能看出为什么吗?
我们现在对 (8.11) 中的
8.5.3. 非奇异矩阵#
对于每个方阵,我们都可以指定一个唯一的数,称为行列式。
对于
如果
当且仅当
关于矩阵逆的更详细解释可以在这里找到。
你可以自己检查 (8.12) 和 (8.13) 中具有线性相关行的矩阵是奇异矩阵。
这为我们提供了一个有用的单数值标准,用来判断一个方阵是否可逆。
特别地,方阵
因此,如果我们用
这是对
8.5.4. 使用NumPy求解线性方程#
根据上述例子中,我们得到了矩阵方程:
这个方程类似于 (8.14),其中
我们现在可以使用NumPy的linalg
子模块求解均衡价格。
所有这些程序都是经过时间考验和高度优化的FORTRAN代码的Python前端。
C = ((10, 5), # 矩阵 C
(5, 10))
现在我们把它录入到NumPy数组中。
C = np.array(C)
D = ((-10, -5), # 矩阵 D
(-1, -10))
D = np.array(D)
h = np.array((100, 50)) # 向量 h
h.shape = 2,1 # 将h转换为列向量
from numpy.linalg import det, inv
A = C - D
#检查A是否为奇异矩阵(行列式是否为零),是否可逆
det(A)
340.0000000000001
A_inv = inv(A) #计算逆矩阵
A_inv
array([[ 0.05882353, -0.02941176],
[-0.01764706, 0.05882353]])
p = A_inv @ h #均衡价格
p
array([[4.41176471],
[1.17647059]])
q = C @ p # 均衡数量
q
array([[50. ],
[33.82352941]])
注意,我们得到的解与纸笔计算的情况相同。
我们还可以使用 solve(A, h)
来求解
from numpy.linalg import solve
p = solve(A, h) # 均衡价格
p
array([[4.41176471],
[1.17647059]])
q = C @ p # 均衡数量
q
array([[50. ],
[33.82352941]])
观察我们如何通过 inv(A) @ y
或使用 solve(A, y)
来求解
后一种方法使用了一种不同的算法,在数值上更加稳定,因此应该是默认选项。
8.6. 练习#
Exercise 8.1
让我们考虑一个有3种商品的市场 - 商品0、商品1和商品2。
每种商品的需求取决于其他两种商品的价格,由以下公式给出:
(这里,当自身价格上涨时需求下降,但当其他商品价格上涨时需求增加。)
每种商品的供给由以下公式给出:
当供给等于需求时,市场达到均衡,即
将市场均衡条件设置为线性方程组。
使用矩阵代数求解均衡价格。分别使用
numpy.linalg.solve
和inv(A)
方法来做这个。比较这两种解法。
Solution to Exercise 8.1
生成的系统将是:
用矩阵形式,我们将其表示为:
import numpy as np
from numpy.linalg import det
A = np.array([[35, -5, -5], # 矩阵 A
[-5, 25, -10],
[-5, -5, 15]])
b = np.array((100, 75, 55)) # 列向量 b
b.shape = (3, 1)
det(A) # 检查A是否为奇异矩阵
9999.99999999999
# 使用inverse
from numpy.linalg import det
A_inv = inv(A)
p = A_inv @ b
p
array([[4.9625],
[7.0625],
[7.675 ]])
# 使用 numpy.linalg.solve
from numpy.linalg import solve
p = solve(A, b)
p
array([[4.9625],
[7.0625],
[7.675 ]])
答案为:
Exercise 8.2
在讲座的早些时候,我们讨论了
在这种情况下,
面对不相容系统时,我们尝试找到最佳的”近似”解。
有多种方法可以做到这一点,其中一种是最小二乘法。
假设我们有一个不相容系统
其中
对于(8.15),最小二乘解是一个
即,
可以证明,对于方程组
现在考虑一种商品的线性需求曲线的一般方程:
其中
假设我们正试图估计
我们通过重复观察价格和数量(例如,每个月)来做到这一点,然后选择
我们有以下观察结果:
价格 |
需求量 |
---|---|
1 |
9 |
3 |
7 |
8 |
3 |
要求需求曲线
因此,我们得到一个方程组
(问题在于我们有三个方程但只有两个未知数。)
因此,我们将尝试找到
使用(8.16)和矩阵代数找到最小二乘解
。使用
numpy.linalg.lstsq
找到最小二乘解,并比较结果。
Solution to Exercise 8.2
import numpy as np
from numpy.linalg import inv
# 运用线性代数
A = np.array([[1, -9], # 矩阵 A
[1, -7],
[1, -3]])
A_T = np.transpose(A) # 矩阵A的转置
b = np.array((1, 3, 8)) # 列向量 b
b.shape = (3, 1)
x = inv(A_T @ A) @ A_T @ b
x
array([[11.46428571],
[ 1.17857143]])
# 使用 numpy.linalg.lstsq
x, res, _, _ = np.linalg.lstsq(A, b, rcond=None)
Show source
print(f"x\u0302 = {x}")
print(f"\u2016Ax\u0302 - b\u2016\u00B2 = {res[0]}")
x̂ = [[11.46428571]
[ 1.17857143]]
‖Ax̂ - b‖² = 0.07142857142857066
这是一个可视化图,展示了最小二乘法如何近似一组点之间连线的方程。
我们也可以将此描述为在一组点之间”拟合”一条直线。
8.6.1. 延伸阅读#
numpy.linalg
子模块的文档可以阅读这里。
如果对更高级的线性代数知识感兴趣可以继续阅读这里。