好的,我们今天继续来学习Numpy的基础,昨天,已经介绍完Numpy的成员之一——数组,今天,在接着介绍其另一大成员——矩阵,也是应用非常广泛的成员。
矩阵,在线性代数中是几乎贯穿全文的成员,因此,这里需要较高的线性代数的基础。在这里,默认对线性代数有全面的学习与认识,了解最基本的矩阵性质及运算。我们这里对于线性代数部分主要用于介绍,对变成部分新颖与重要部分进行展示,便于以后的记忆。
因此,本章学习还是概念多余代码,对于代码,是比较简单的。
一、矩阵的基本属性与计算
1、数组的创建和转置
对数组,创建也有特定的函数,其函数为:matrix(),其可以将列表、元组、range对象等转换为矩阵,其基础的转换举例如下:
import numpy as np
# 创建基础的矩阵
matr_base = np.matrix([[1, 2, 3], [4, 5, 6]])
matr_base_2 = np.matrix([1, 2, 3, 4, 5, 6])
# 逐层访问与单词访问的区别
print(matr_base[1, 0])
print(matr_base[1][0])
其输出的结果为:4和[[4 5 6]]。由此,我们可以推测,对于矩阵一般只会存在单次访问,如果是数组,此时两者输出的结果应当是相等的。对数组来说,其深入访问的方式为:先横后纵的访问方式。
然而,对于矩阵来说,是不存在层层深入访问的,其所有的访问都是同类型的,都是访问的横排,因此,深层访问基本不存在。
紧接着,是矩阵的转置,对于矩阵的转置,如同矩阵的转置的写法一般,全文后面matr为矩阵举例。因此,要将该矩阵转置,只需要输入matr.T即可得到转置矩阵。矩阵的转置无非是将每一横行写成纵行,第二横行改为第二纵行,由此,将局长你转置完成。
2、矩阵的特征
在这里,列举矩阵中numpy内置的部分函数,用于求矩阵的基本数。
# 所有元素的平均值
print(matr_base.mean())
# 纵向每列平均值
print(matr_base.mean(axis=0))
# 数组的形状
print(matr_base.mean(axis=0).shape)
# 横向每行平均值
print(matr_base.mean(axis=1))
# 所有元素之和
print(matr_base.sum())
# 横向最大值
print(matr_base.max(axis=1))
# 横向最大值的下标
print(matr_base.argmax(axis=1))
# 对角线的元素
print(matr_base.diagonal())
# 非0元素的下标,分别返回行下标和列下标
print(matr_base.nonzero())
输出的结果如下:
4
[[4 5 6]]
3.5
[[2.5 3.5 4.5]]
(1, 3)
[[2.]
[5.]]
21
[[3]
[6]]
[[2]
[2]]
[[1 5]]
(array([0, 0, 0, 1, 1, 1], dtype=int64), array([0, 1, 2, 0, 1, 2], dtype=int64))
3、矩阵乘法
对矩阵乘法,一直有一个公式,那就是“左列=右行”方可进行计算,计算出来的结果为“左行右列”。这是我现代自己便于记忆的公式。翻译过来就是:左边的列数必须与右边的行数相等,乘出来的矩阵行数为左边矩阵的行数,列数为右边矩阵的列数。
非常值得注意的一点为: 矩阵的乘法不满足交换律,交换之后可能无法相乘。
# 矩阵的乘法
matr_one=np.matrix([1,2,3]) ; matr_two=np.matrix(range(1,10))
matr_two.shape=3,3 ; matr_mult=matr_one*matr_two
print(matr_mult)
其输出的结果为:[[30 36 42]]。
可以看到,1行3列矩阵与3行3列矩阵相乘,满足上述两个条件,得到的结果也如上。如果交换顺序,便会产生错误:
Traceback (most recent call last):
File "d:\PythonLearn\PythonWork\专题Numpy基础(下)_矩阵.py", line 36, in <module>
print(matr_two*matr_one)
~~~~~~~~^~~~~~~~~
File "D:\Python312\Lib\site-packages\numpy\matrixlib\defmatrix.py", line 219, in __mul__
return N.dot(self, asmatrix(other))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: shapes (3,3) and (1,3) not aligned: 3 (dim 1) != 1 (dim 0)
我们可以看到啊,它指出了3!=1,两者不等,不能进行相乘。
4、相关系数矩阵与三差
对于相关系数矩阵,就是一个对称阵(关于主对角线完全对称),主对角线上的元素全都是1,副对角线上的元素表示对应位置的相关系数,每个元素的绝对值小于1。权威正则正相关,相反则负相关。numpy中用corrcoef()函数计算相关矩阵。
# 计算相关矩阵
# 负相关矩阵
print(np.corrcoef([[1, 2, 3, 4], [4, 3, 2, 1]]))
print(np.corrcoef([[1, 2, 3, 4], [8, 3, 2, 1]]))
# 正相关矩阵
print(np.corrcoef([[1, 2, 3, 4], [1, 2, 3, 4]]))
print(np.corrcoef([[1, 2, 3, 4], [1, 2, 3, 40]]))
最终得到的结果如下:
[[ 1. -1.]
[-1. 1.]]
[[ 1. -0.91350028]
[-0.91350028 1. ]]
[[1. 1.]
[1. 1.]]
[[1. 0.8010362]
[0.8010362 1. ]]
下面,我们在学习对三差的计算:方差、标准差、协方差。其中,标准差与协方差在概率论里面涉及较为多,前两个的定义已经耳熟能详了,对于协方差,概率论的定义为:
期望值分别为E[X]与E[Y]的两个实随机变量X与Y之间的协方差Cov(X,Y)定义为:
实际协方差也为方差的特殊种类,这里只管应用,在数学建模领域,这个是个重要的概念,大家是需要深入学习的。我们这里只学函数应用。std()函数表示保准差,cov表示方差,合理应用就是协方差。以下有例子:
# 三差的计算
x = [-2.1, -1, 4.3] ; y = [3, 1.1, 0.12] ; matr_dif = np.vstack((x, y))
# 协方差
print(np.cov(matr_dif))
print(np.cov(x,y))
# 标准差
print(np.std(matr_dif))
print(np.std(matr_dif, axis=1))
# 方差
print(np.cov(x))
起计算原理是一个简单的数学问题,直接看其运行的结果为:
[[11.71 -4.286 ]
[-4.286 2.14413333]]
[[11.71 -4.286 ]
[-4.286 2.14413333]]
2.2071223094538484
[2.79404128 1.19558447]
11.709999999999999
5、计算特征值与特征向量
首先我们需要知道特征值是针对方阵所特殊的存在,其线性代数的计算方法为,变形后得到
,因此,需要用这个公式对原A方阵进行变形,最后才能得到其一个或者多个特征根。
在numpy中,其子模块linalg中,提供的特征值与特征向量为eig()函数。其函数的使用格式为:
# 特征值与特征向量
matr_esp = np.matrix([[2, -2, 0], [-2, 1, -2], [0, -2, 0]])
nmd_matr, st_matr = np.linalg.eig(matr_esp)
print(nmd_matr, st_matr, sep='\n')
print(type(nmd_matr), type(st_matr), sep='\n')
特征值,特征向量 = np.linalg.eig(matr),因此,基于此规则,对特征值的求法就是一个函数,但这里需要注意的是,求矩阵的特征向量,建立时应当采用数组起步。但实际上,只有矩阵计算特征香菜才具有意义的,特征向量是矩阵的一重大点。以上结果的输出为:
[ 4. 1. -2.]
[[-0.66666667 -0.66666667 0.33333333]
[ 0.66666667 -0.33333333 0.66666667]
[-0.33333333 0.66666667 0.66666667]]
<class 'numpy.ndarray'>
<class 'numpy.matrix'>
可以看到,特征值输出的结果为数组,特征向量输出的结果为矩阵。在这里需要注意的是,三个特征向量是按照列查看的,也就是三列特征值,水平堆叠hstack形成一个特征矩阵。
在线性代数中,我们运用史密斯正交法,将相关向量化为无关向量,并单位正交化,可以得到一个全新矩阵,此矩阵乃是特征矩阵。接下来可以进行验证:
# 特征之一特征向量的相互计算
# 计算矩阵与特征向量乘积
print(np.dot(matr_esp, st_matr))
# 计算特征值与特征向量的乘积
print(nmd_matr*st_matr)
# 验证二者是否相等
print(np.isclose(np.dot(matr_esp, st_matr), nmd_matr*st_matr))
# 计算其行列式的值
print(np.linalg.det(matr_esp-np.eye(3, 3)*nmd_matr))
输出结果如下:
[[-2.66666667 -0.66666667 -0.66666667]
[ 2.66666667 -0.33333333 -1.33333333]
[-1.33333333 0.66666667 -1.33333333]]
[[-2.66666667 -0.66666667 -0.66666667]
[ 2.66666667 -0.33333333 -1.33333333]
[-1.33333333 0.66666667 -1.33333333]]
[[ True True True]
[ True True True]
[ True True True]]
-6.217248937900884e-15
其结果成立,便为特征值。满足原表达式。
6、计算逆矩阵
接下来是最后一个计算知识点,也是线性代数中重点,且每年的必考点之一:计算逆矩阵。我们知道,常规的逆矩阵及算法有三种:(1)用定义求逆矩阵;(2)用伴随矩阵求逆;(3)初等变换——行变换增广矩阵化单位阵求逆矩阵。
而在python中,计算逆矩阵的方法便为初等变换,这里简单书写提醒原则,也是我自己在回顾一遍:
当两个方阵A和B进行乘法(位置不可交换),A·B=B·A=E,则=B,因此,基于此逻辑,我么可以将一个单位阵与B进行增广,达成B|E,通过初等恒变换,将B矩阵化为单位阵E通过恒等变换得到的结果自然为A的逆矩阵,其等价于B|E=E|
。
其原理在于,我们在将B化为单位阵的过程中,实际上经历了以下变化:B——>BA——>E,此过程中,E的变化为:E——>E——>
。因此,上述结果成立。这是求B的逆矩阵A时满足的结果,以结果逆推其逆矩阵。
在python中,将此过程表达出来的仍然是linalg模块中的函数inv()。其表达式为:逆=np.linalg.inv(matr)。此计算,就必定采用矩阵作为基础,这一点与计算矩阵的特征值与特征向量是具有一定区别的。
# 计算矩阵的逆矩阵
init_matr = np.matrix([[1, 2, 3], [4, 5, 6], [7, 8, 0]])
contr_matr = np.linalg.inv(init_matr)
print(contr_matr)
# 若验证结果可以使用是否相等
print(init_matr*contr_matr) ; print(contr_matr*init_matr)
print(np.isclose(init_matr*contr_matr, contr_matr*init_matr))
其输出的结果为:
[[-1.77777778 0.88888889 -0.11111111]
[ 1.55555556 -0.77777778 0.22222222]
[-0.11111111 0.22222222 -0.11111111]]
[[ 1.00000000e+00 5.55111512e-17 1.38777878e-17]
[ 5.55111512e-17 1.00000000e+00 2.77555756e-17]
[ 1.77635684e-15 -8.88178420e-16 1.00000000e+00]]
[[ 1.00000000e+00 -1.11022302e-16 0.00000000e+00]
[ 8.32667268e-17 1.00000000e+00 2.22044605e-16]
[ 6.93889390e-17 0.00000000e+00 1.00000000e+00]]
[[ True True True]
[ True True True]
[ True True True]]
至此,其逆矩阵我们便轻松拿捏了。
二、矩阵的应用拓展
1、求解线性方程组
在线性代数中,求解线性方程组是一个难点,其对于线性方程组要有一定的判定方可着实对方程组进行解答,这也是线性方程组的一个特殊点与重点所在。
我们求解线性方程组的解步骤为:
(1)化阶梯型;(2)找到最大线性无关组组成矩阵;(3)求自由变量s=n(列向量个数)-r(阶梯矩阵的秩);(4)取自由变量的单位矩阵;(5)计算出原始的列向量的每一个值,由此得到齐次线性方程的通解,至此,齐次线性方程组结束;(6)此步为非齐次特解的求法,通过自定自由元素的值,得到一个特解,非齐次线性方程组的解便为齐次的解+非齐次的特解。
又是一个巨大的运算量,然而此过程在python中numpy已经有函数加以计算,仍然是linalg模块,solve()函数得到轻松解决。
# 唯一解
A = np.matrix([[3,1],[1,2]])
b = np.matrix([9,8]).T
x = np.linalg.solve(A,b)
print(x)
print(np.isclose(A*x,b))
# 最小二乘解:返回解、余项、a的秩、a的奇异值
print(np.linalg.lstsq(A,b,rcond= 1))
我们已经知道线性方程组的解法为初始矩阵A与b的列项俩个的增广矩阵之间的关系。因此,solve函数就在这期间将a单独拿出来作为一个矩阵运用solve函数便可直接得到线性方程组的解。但此方案的局限性在于只适用于有解且只有唯一解恶的线性方程组。其解答为:
[[2.]
[3.]]
[[ True]
[ True]]
(matrix([[2.],
[3.]]), matrix([], shape=(1, 0), dtype=float64), 2, array([3.61803399, 1.38196601]))
而对于多解的线性方程组,无论是齐次,还是非齐次,都涉及到很多交叉的变化,这里不再基础中提到,将在后面的numpy进阶中作为示例列举与学习。
2、矩阵的向量化
矩阵无法直接使用math库里面的阶乘函数factorial(),而numpy库也无内置函数解决,因此,可以用numpy中的vectorize()将矩阵向量化,便可使用阶乘函数。具体代码如下:
# 矩阵向量化
import math
matr =np.matrix([[1,2,3],[4,5,6]])
# 这里不能直接使用会报错
# math.factorial(matr)
# 正确方法先采用向量化
VecFactorial = np.vectorize(math.factorial())
print(VecFactorial(matr))
最后可得到其阶乘的结果:
[[ 1 2 6]
[ 24 120 720]]
3、奇异值分解
接着,是今天的最后一个内容,奇异值分解,我们知道,存在可逆矩阵P、Q有(对角矩阵),则
由此,通过奇异值分解,将矩阵A分解为多个更小的矩阵的乘积,这也是降维的一大方法。
这一过程可以通过nmpy中linalg中的svd()函数完成,可以将a=usv,直接可用svd函数表示,其中s便为奇异值。
# 返回矩阵的奇异值
a =np.matrix([[1,2,3],[4,5,6],[7,8,9]])
u,s,v = np.linalg.svd(a)
print(s)
其结果为:s=[1.68481034e+01 1.06836951e+00 4.41842475e-16]。
至此,我们的矩阵到此结束,numpy基础知识也到此趋近于结束。每天学习,点滴进步!