文章目录
  1. 1. 数据结构:array 与 matrix
    1. 1.1. 运算操作
    2. 1.2. 创建特殊数组
    3. 1.3. 结论
  2. 2. 各种乘法函数
  3. 3. 矩阵的连接
    1. 3.1. r_[] 与 c_[]
      1. 3.1.1. “叠加”方式
      2. 3.1.2. 骚操作切片
      3. 3.1.3. 一般用途

python是好文明!——虽然图书馆总是不肯买python数据分析的书,一大把matlab教程锃光瓦亮的摆在那里。

问题总是层出不穷的,要学会搜索,Numpy 手册搜索页面传送门

数据结构:array 与 matrix

Numpy 中有两种数组:arraymatrix,后者是前者的子类,继承了大部分操作。在用户手册上建议大家使用 array ,不过 matrix 在线性代数方面更为方便。

在这里对二者进行一个简单(包罗万象)的对比:

运算操作

array matrix
维度 1~,根据括号层数,可能下降 ① 2
矩阵乘法 np.matmul(A,B)A.dot(B)A @ B A*B
按位乘法 A*B,遵循广播法则 np.multiply(A,B)
矩阵乘方 np.linalg.matrix_power(A,k) A**k
按位乘方 A**B,B不能包含负整数 ④ np.power(A,B)
其他按位运算 遵循广播法则 遵循广播法则
逆矩阵 np.linalg.inv(A) A**-1A.I
  1. 例如对于一个二维 array ,mean(A,1) 的结果是一维的
  2. 对于多个矩阵连乘,优雅的写法是 np.linalg.multi_dot([A,B,C]),我感觉写成 A.dot(B).dot(C)也挺明白的 PEP465更新,将 @ 作为矩阵相乘的符号
  3. 广播法则:简而言之,就是把两个数组中低维、较小的升维(加入长度为1的维度)、重复,使其形状与另一个数组匹配。如果可以做到,就能成功进行运算。
  4. 可以使用 A**-2.0或者 (1/A)**2这种 hack 手段

创建特殊数组

array 和 matrix 分别使用两套创建方法,matrix 所用的函数位于 np.matlib 中。要注意两点:

  • matrix 的方法中,无论怎么给参数,输出都是二维的(要么报错),array 系列则不然。
  • array 系列的 empty,zeros,ones,full均可改用empty_like(A)之类的函数来创建与已知矩阵 A 同形态的矩阵。
  • np.random.rand()的参数与众不同。

以下假设from numpy import *

array matrix
[NxN]单位矩阵 eye(N) matlib.eye(N)
[MxN]空矩阵 empty([M,N]) matlib.empty([M,N])
[MxN]零矩阵 zeros([M,N]) matlib.zeros([M,N])
[MxN]全1矩阵 ones([M,N]) matlib.ones([M,N])
[MxN]全a矩阵 full([M,N],a)
[MxN]随机矩阵 random.rand(M,N) matlib.rand(M,N)matlib.rand([M,N])
[MxN]正态随机矩阵 random.randn(M,N) matlib.randn(M,N)matlib.randn([M,N])
[MxN]随机整数矩阵 random.rand(low,hight,[M,N])

结论

鉴于 array 的泛用性以及 matrix 按位运算的坑爹性,我打算用 array了。A.dot(B)的写法虽不舒服,把.dot()当作一个算符还能接受,而np.multiply(A,B)的写法实在是太不数学了。 既然已经可以用 A @ B 表示矩阵相乘,还有什么理由不用 array 呢?

因为 array 是支持各种维度数据的,和数学上常用的矩阵并不一样,例如:[N]个数据的向量和[1xN]的矩阵并不是一回事,对向量进行转置操作没有效果。而对于维度不等的数组,在运算时会用到广播法则,能得到想要的结果——但是总是觉得不放心。为了符合数学习惯,在声明变量的时候可以把向量都写成 A=np.array([[a,b,c]])的形式,这样就不必再考虑相乘的时候用什么函数(.dot()? .inner()? .outer()? (/TДT)/),只要按数学表达式转置相乘即可。不过要注意,两个向量用矩阵乘法或类似乘法的函数,返回值是标量;两个矩阵用矩阵乘法,返回值是[1x1]的矩阵。

各种乘法函数

如上所述,其实各种乘法函数都可以用“转置+矩阵乘法”代替,不过还是在这里列一下,假设m,n是两个[1xN]矩阵:

函数 等价于 注释
np.matmul(A,B)->ndarray A @ B 矩阵乘法,参数不能是标量
A.dot(B),np.dot(A,B)->ndarray A @ B 参数中的一个可以是标量
np.inner(m,n)->num m @ n.T 可用于高维,但数学意义不明
np.outer(m,n)->ndarray m.T @ n
np.vdot(m,n)->num m.conjugate() @ n.T 复共轭内积,不应用于高维

矩阵的连接

r_[] 与 c_[]

两个很方便的语法糖,但是具体用法……很那理解,可以说是只可意会不可言传了。

“叠加”方式

  • r_[]的作用是“连接”,可以带一个“拟似参数”指定连接的轴,例如:

1
2
3
4
5
6
7
>>> r_[[1,2,3],[4,5,6]]          # shape(3,),shape(3,)
array([1,2,3,4,5,6]) # shape(6,)
>>> r_['0',[[1,2,3]],[[4,5,6]]] # shape(1,3),shape(1,3)
array([[1,2,3],
[4,5,6]]) # shape(2,3)
>>> r_['1',[[1,2,3]],[[4,5,6]]] # shape(1,3),shape(1,3)
array([[1,2,3,4,5,6]]) # shape(1,6)

shape值的角度看,玄乎的“连接”就对应着 shape的变化,记shape为向量\(S\),则沿 0 轴连接,就是 \(S[0]=s_a[0]+s_b[0]\),同时其他分量不变 \(S[i]=s_a[i]=s_b[i]\)

  • 这样引出第二个“拟似参数”:最小维度。例如(数不够,字母凑,就当a,b,c是普通数字):

1
2
3
4
5
6
7
8
9
10
>>> r_['0,2',[1,2,3],[4,5,6]]  # shape(3,),shape(3,)
array([[1,2,3],
[4,5,6]]) # shape(2,3)
>>> r_['1,3',
... [[1,2,3],[4,5,6]],
... [[7,8,9],[a,b,c]]] # shape(2,3),shape(2,3)
array([[[1,2,3],
[4,5,6],
[7,8,9],
[a,b,c]]]) # shape(1,4,3)

维度,也就是shape值的分量数。当前维度比要求的最小维度低,就要升维,对应着shape增加分量。默认的升维方式是在高位加一维,其长度为 1,即 \(S_a=(1,s_a)\)。之后再进行连接,显然第二个例子的历程是这样的: \[ \begin{matrix}s_a=(2,3)\\ s_b=(2,3)\end{matrix}\xrightarrow{升维}\begin{matrix}S_a=(1,2,3)\\ S_b=(1,2,3)\end{matrix}\xrightarrow{连接} S=(1,2+2,3)=(1,4,3)\]

  • 那么升维只有这一种方法吗?No!请看第三个“拟似参数”:原shape在新shape中的坐标,这个最难理解。然而我和其他博主不一样,我会画图。简单的假设原来\(s_a=(h,k,l)\),用“1”填充升维之后\(S_a\)

1
2
3
( 1 1 . . . 1 h k l 1 . . . 1 1 )
^ ^
坐标 m 坐标 -n

我们很自然的采用 python 中列表索引的方式来表示这个坐标。这个坐标的意义是:确定好了原来shape的位置之后,其他的1~m-1-n+1~-1的位置会被“1”填充。第三个“拟似参数”若为非负数,则表示 m;若为负数,则表示-n。下面是一个例子:

1
2
3
4
5
6
>>> r_['2,5,1',
... [[1,2],[3,4],[5,6]],
... [[7,8],[9,a],[b,c]]] # shape(3,2),shape(3,2)
array([[[[[1]],[[2]],[[7]],[[8]]],
[[[3]],[[4]],[[9]],[[a]]],
[[[5]],[[6]],[[b]],[[c]]]]]) # shape(1,3,4,1,1)

  • 所以说,其实默认的r_[A,B]就是r_['0,1,-1',A,B]罢了

  • c_[A,B]就是r_['-1,2,0',A,B],也就是说:沿最后一轴连接,维度最小为2,倒序升维。

骚操作切片

r_[]c_[]之所以不设计成函数,就是为了方便切片,其中用于连接的除了普通数组外,也可以是以下表达式:

  • 起始:终止:步长的形式,结果就像np.arange()一样,包含起始但不包含终止,python 一贯风格。
  • 如果步长是个虚数,即起始:终止:步长j的形式,那么步长表示的是总的点数,结果如同np.linspace(),然而这时候就是包含终止的了。

那么可以这样快速产生等距矩阵:

1
x=r_['0,2',0:10:100j]

一般用途

说了那么多玄的,其实r_[]c_[]一般还是用在二维的矩阵上,这时候就很简单了:

  • r_[A,B]:保持列数不变,按行连接
  • c_[A,B]:保持行数不变,按列连接
1
2
3
4
5
6
7
8
9
>>> A=array([[1,2,3],[4,5,6]])
>>> r_[A,A]
array([[1,2,3],
[4,5,6],
[1,2,3],
[4,5,6]])
>>> c_[A,A]
array([[1,2,3,1,2,3]
[4,5,6,4,5,6]])

而作用在一维向量上,二者效果就没那么直观,不如先转换成矩阵再来操作。

文章目录
  1. 1. 数据结构:array 与 matrix
    1. 1.1. 运算操作
    2. 1.2. 创建特殊数组
    3. 1.3. 结论
  2. 2. 各种乘法函数
  3. 3. 矩阵的连接
    1. 3.1. r_[] 与 c_[]
      1. 3.1.1. “叠加”方式
      2. 3.1.2. 骚操作切片
      3. 3.1.3. 一般用途