6.19学习:

Numpy中的矩阵运算

2a59498db50994a053699e643e45802

  有如图所示的问题,分别在python的list中和numpy中解决。

n = 1000000
L = [i for i in range(n)]
2 * L
# 实际是把两个L首位相接
# 输出
%%time
A = []
for e in L:
A.append(2*e)
A
# 利用python中list遍历的方法,效率低下

  输出:Wall time: 256 ms

%%time
A = [2*e for e in L]
# 利用生成表达式可以快一些

  输出:Wall time: 172 ms

import numpy as np
L = np.arange(n)
# 使用numpy来构建
%%time
A = np.array(2*e for e in L)
# 使用numpy运算矩阵可以快相当多的时间

  输出:Wall time: 16 ms

%%time
A = 2 * L
# 而且numpy可以支持这样的表达,比上面使用生成表达式还要快

  输出:Wall time: 2.99 ms

Universal Functions

  Numpy可以将数组看作是向量或者矩阵来运算,叫做Universal Functions(ufunc)。Universal Functions近乎支持所有的运算符。

X = np.arange(1,16).reshape((3,5))
X

  输出:array([[ 1, 2, 3, 4, 5],
       [ 6, 7, 8, 9, 10],
       [11, 12, 13, 14, 15]])。

X + 1

  输出:array([[ 2, 3, 4, 5, 6],
       [ 7, 8, 9, 10, 11],
       [12, 13, 14, 15, 16]])。

X / 2
# 浮点数除法

  输出:array([[0.5, 1. , 1.5, 2. , 2.5],
       [3. , 3.5, 4. , 4.5, 5. ],
       [5.5, 6. , 6.5, 7. , 7.5]])。

X // 2
# 整数除法

  输出:array([[0, 1, 1, 2, 2],
       [3, 3, 4, 4, 5],
       [5, 6, 6, 7, 7]], dtype=int32)。

X ** 2
# 幂运算

  输出:array([[ 1, 4, 9, 16, 25],
       [ 36, 49, 64, 81, 100],
       [121, 144, 169, 196, 225]], dtype=int32)。

X % 2
# 求余运算

  输出:array([[1, 0, 1, 0, 1],
       [0, 1, 0, 1, 0],
       [1, 0, 1, 0, 1]], dtype=int32)。

1 / X
# 取倒数
np.abs(X)
# 取绝对值
np.sin(X)
# 求sin
np.exp(X)
# 对X中的所有元素都取e的x次方,x为X中每一位的值
np.power(3,X)
# 对X中的所有元素都取3的x次方,x为X中每一位的值
# 和 3**X 结果一致

  输出:array([[ 3, 9, 27, 81, 243],
       [ 729, 2187, 6561, 19683, 59049],
       [ 177147, 531441, 1594323, 4782969, 14348907]], dtype=int32)。

np.log(X)
# 取log
# np.log2(X)
# np.log10(X)

  输出:array([[0. , 0.69314718, 1.09861229, 1.38629436, 1.60943791],
       [1.79175947, 1.94591015, 2.07944154, 2.19722458, 2.30258509],
       [2.39789527, 2.48490665, 2.56494936, 2.63905733, 2.7080502 ]])。

矩阵和矩阵的运算 + * .dot() .T

A = np.arange(4).reshape(2,2)
B = np.full((2,2),10)
A

  输出:array([[0, 1],
       [2, 3]])。

B

  输出:array([[10, 10],
       [10, 10]])。

A + B

  输出:array([[10, 11],
       [12, 13]])。

A * B
# A和B对应元素相乘的结果【不是矩阵乘法!】
# numpy中对运算符的定义都是对应元素相乘的运算
# 同理,A / B也是对应位置相除

  输出:array([[ 0, 10],
       [20, 30]])。

A.dot(B)
# dot()方法才是【矩阵乘法】

  输出:array([[10, 10],
       [50, 50]])。

A.T
# 只用调用.T这个属性就可以得到矩阵A的转置

  输出:array([[0, 2],
       [1, 3]])。

C = np.full((3,3),666)
A + C
# A.dot(C)
# 如果矩阵形状不对,会报错

  输出:ValueError: operands could not be broadcast together with shapes (2,2) (3,3)。

矩阵和向量的运算 + 堆叠.tile() * .dot()

v = np.array([1,2])
v + A
# 矩阵+向量

  输出:array([[1, 3],
       [3, 5]])。

  在数学中,矩阵和向量相加是没有意义的,甚至可以说是错误的。但是在numpy中是矩阵的每一行都加上这个向量。

  注意,一维向量和二维矩阵在numpy中是不一样的:一维向量只有一列,元素只有在”第几位”这一个维度;二维矩阵哪怕也只有一行,元素也有在”第几行””第几列”这两个维度。所以此处是”向量+矩阵”不会报错(维度不同),但上面的”矩阵+矩阵”就因为形状不对报错了(维度相同)。

np.vstack([v]*A.shape[0])
# vstack是行堆叠,把向量V中的元素按照A的行数堆叠起来
# A.shape[0]是矩阵A第0维(行)的维数
# A.shape[1]是矩阵A第1维(列)的维数
np.vstack([v]*A.shape[0]) + A
# 这样表述比v + A更合理

  输出:array([[1, 3],
       [3, 5]])。

np.tile(v,(2,1))
# 用tile()方法堆叠
# 把v在行的方向上堆叠2次,列的方向上堆叠1次

  输出:array([[1, 2],
       [1, 2]])。

np.tile(v,(2,1)) + A

  输出:array([[1, 3],
       [3, 5]])。

v * A
# 向量和矩阵也可以相乘
# 但是这样乘是向量与矩阵逐行逐元素相乘

  输出:array([[0, 2],
       [2, 6]])。

v.dot(A)
# 这样就是【矩阵乘法】

  输出:array([4, 7])。

A.dot(v)
# 向量乘矩阵的时候,不用管向量的方向,用.dot()总是可以顺利乘的,并且乘完都显示成行向量

  输出:array([2, 8])。

逆np.linalg.inv() 伪逆np.linalg.pinv()

np.linalg.inv(A)
# 取A的逆

  输出:array([[-1.5, 0.5],
       [ 1. , 0. ]])。

invA = np.linalg.inv(A)
A.dot(invA)
# 原矩阵乘逆矩阵等于单位矩阵

  输出:array([[1., 0.],
      [0., 1.]])。

X = np.arange(16).reshape((2,8))
np.linalg.inv(X)
# 不可求逆矩阵的会报错
# 只有方阵才能求逆矩阵

  输出:LinAlgError: Last 2 dimensions of the array must be square。

  事实上很多时候要求逆矩阵的,并且不是方阵,这个时候求伪逆矩阵。

pinvX = np.linalg.pinv(X)
pinvX

  输出:array([[-1.35416667e-01, 5.20833333e-02],
       [-1.01190476e-01, 4.16666667e-02],
       [-6.69642857e-02, 3.12500000e-02],
       [-3.27380952e-02, 2.08333333e-02],
       [ 1.48809524e-03, 1.04166667e-02],
      [ 3.57142857e-02, -3.08718399e-18],
       [ 6.99404762e-02, -1.04166667e-02],
       [ 1.04166667e-01, -2.08333333e-02]])。

X.dot(pinvX)
# 伪逆矩阵是根据相乘为单位矩阵这个原则计算过来的
# 相乘后可以看到几乎就是一个逆矩阵

  输出:array([[ 1.00000000e+00, -3.05311332e-16],
       [ 1.91513472e-15, 1.00000000e+00]])。

Numpy中的聚合运算

sum() min() max() 乘积prod()

import numpy as np
L = np.random.random(100)
L

  输出:100个0-1之间的随机数组成的数组。

sum(L)
# 可以用python中sum()来计算list中的和

  输出:50.66803645212981。

np.sum(L)
# numpy中也有sum()方法
# numpy中sum()的效率极高

  输出:50.66803645212981。

big_array = np.random.random(1000000)
%timeit sum(big_array)
%timeit np.sum(big_array)

  输出:207 ms ± 16.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each);
     1.24 ms ± 17.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)。

np.min(big_array)
np.max(big_array)

  输出:6.232367252279047e-07;0.9999996177452486。

big_array.min()
big_array.max()
big_array.sum()

  输出:6.232367252279047e-07;0.9999996177452486;499889.97152216063。

  对于矩阵,也有聚合的操作。

X = np.arange(16).reshape(4,-1)
# 注意reshape第二维度用-1表示均分
X

  输出:array([[ 0, 1, 2, 3],
       [ 4, 5, 6, 7],
       [ 8, 9, 10, 11],
       [12, 13, 14, 15]])。

np.sum(X)

  输出:120。

np.sum(X, axis=0)
# 计算每一列的和【以行为度量】

  输出:array([24, 28, 32, 36])。

np.sum(X, axis=1)
# 计算每一行的和【以列为度量】

  输出:array([ 6, 22, 38, 54])。

np.prod(X)
# 这个矩阵所有元素的乘积

  输出:0。

np.prod(X+1)

  输出:2004189184。

mean() median() 百分位percentile() var() std()

np.mean(X)
# 求平均值

  输出:7.5。

np.median(X)
# 求中位数

  输出:7.5。

np.percentile(big_array, q=50)
# 求百分位,前50%的数是多少
# 和np.median(big_data)等价

  输出:0.4992601928901571。

for percent in [0,25,50,75,100]:
print(np.percentile(big_array, q=percent))
# 统计学通常对这四个位置感兴趣

  输出:6.232367252279047e-07
     0.2496817026825146
     0.4992601928901571
     0.7504043808069571
     0.9999996177452486。

np.var(big_array)
# 方差

  输出:0.08343223369347111。

np.std(big_array)
# 标准差

  输出:0.2888463842485675。

x = np.random.normal(0, 1, size=1000000)
# 创建一个符合正态分布的大样本
np.mean(x)
np.var(x)
np.std(x)

  输出:-0.0010312130340502273;0.9989569742958259;0.9994783510891199。

Numpy中的arg运算

索引arg

np.min(x)

  输出:-5.18770608982187。

np.argmin(x)
# 同理,还有argmax()

  输出:66664。

x[66664]

  输出:-5.18770608982187。

排序sort() 返回索引argsort() 标定点partition()

x = np.arange(16)
x

  输出:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])。

np.random.shuffle(x)
# 对向量进行乱序处理,会直接改变原有向量
x

  输出:array([ 9, 7, 13, 3, 6, 5, 0, 1, 11, 14, 4, 2, 8, 10, 15, 12])。

np.sort(x)
# 排序,但不直接改变x本身

  输出:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])。

x

  输出:array([ 9, 7, 13, 3, 6, 5, 0, 1, 11, 14, 4, 2, 8, 10, 15, 12])。

x.sort()
# 这样在x本身进行排序
x

  输出:array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])。

  一定注意.sort()在np上调用和在array自身上调用是不一样的。

X = np.random.randint(10, size=(4,4))
# 声明一个4*4的矩阵,每一个元素在0-10之间取值(取不到10)
X

  输出:array([[8, 5, 2, 2],
       [0, 4, 8, 9],
       [2, 9, 5, 0],
       [3, 2, 9, 0]])。

np.sort(X)
# 把每一行排序
# 和np.sort(X, axis=1)

  输出:array([[2, 2, 5, 8],
      [0, 4, 8, 9],
       [0, 2, 5, 9],
       [0, 2, 3, 9]])。

np.sort(X, axis=0)
# 把每一列排序
# 这里的轴和sum()同理

  输出:array([[0, 2, 2, 0],
       [2, 4, 5, 0],
       [3, 5, 8, 2],
       [8, 9, 9, 9]])。

np.random.shuffle(x)
x

  输出:array([10, 14, 12, 9, 11, 7, 2, 6, 8, 5, 15, 3, 0, 1, 4, 13])。

np.argsort(x)
# 返回的是排序后的索引

  输出:array([12, 13, 6, 11, 14, 9, 7, 5, 8, 3, 0, 4, 2, 15, 1, 10], dtype=int64)。

  最小的0就是原数组中下标为12的位置。

np.partition(x,3)
# 快速排序标定点3,找到比3小和比3大的

  输出:array([ 2, 1, 0, 3, 6, 7, 9, 4, 8, 5, 10, 15, 14, 11, 12, 13])。

np.argpartition(x,3)
# 和partition一样,返回的是索引

  输出:array([ 6, 13, 12, 11, 7, 5, 3, 14, 8, 9, 0, 10, 1, 4, 2, 15], dtype=int64)。

np.argsort(X)
# 按照行进行排序,返回索引

  输出:array([[2, 3, 1, 0],
       [0, 1, 2, 3],
       [3, 0, 2, 1],
       [3, 1, 0, 2]], dtype=int64)。